check
This commit is contained in:
@@ -146,87 +146,6 @@ class StaffDao {
|
||||
);
|
||||
}
|
||||
|
||||
/// Writes a staff member the back office owns, creating or updating the row.
|
||||
///
|
||||
/// Deliberately skips [_assertPinIsAcceptable] and the duplicate-PIN check.
|
||||
/// Those rules exist to stop *this terminal* from accepting a weak PIN
|
||||
/// someone typed at the counter; they are not this terminal's to enforce on
|
||||
/// a list the back office has already published. Applying them here would
|
||||
/// mean a shop whose head office issued `1111` simply never receives its
|
||||
/// staff, and the till falls back to seeded demo accounts — a worse outcome
|
||||
/// than a guessable PIN.
|
||||
///
|
||||
/// [id] is derived from the server's `user_id` rather than minted, so a
|
||||
/// second sign-in updates the same row instead of duplicating the person.
|
||||
///
|
||||
/// A null [pin] leaves an existing PIN alone, and on a new row stores an
|
||||
/// unusable hash: the person appears on the staff list and can be attributed
|
||||
/// bills, but nothing typed at the keypad will ever match them. That is the
|
||||
/// honest representation of "the back office did not give us their PIN".
|
||||
Future<StaffUser> upsertFromServer({
|
||||
required String id,
|
||||
required String name,
|
||||
required StaffRole role,
|
||||
String? pin,
|
||||
bool isActive = true,
|
||||
}) async {
|
||||
final trimmed = name.trim().isEmpty ? 'Staff' : name.trim();
|
||||
final now = DateTime.now().millisecondsSinceEpoch;
|
||||
final existing = await findById(id);
|
||||
|
||||
// Minted once and reused for both columns. PinHasher.newSalt() is random,
|
||||
// so calling it twice in one statement would store a hash the stored salt
|
||||
// cannot reproduce.
|
||||
final salt = PinHasher.newSalt();
|
||||
|
||||
if (existing != null) {
|
||||
await _db.update(
|
||||
Tables.staff,
|
||||
{
|
||||
'name': trimmed,
|
||||
'role': role.name,
|
||||
'is_active': isActive ? 1 : 0,
|
||||
if (pin != null) ...{
|
||||
'pin_hash': PinHasher.hash(pin, salt),
|
||||
'pin_salt': salt,
|
||||
'must_change_pin': 0,
|
||||
},
|
||||
'updated_at': now,
|
||||
},
|
||||
where: 'id = ?',
|
||||
whereArgs: [id],
|
||||
);
|
||||
return StaffUser(id: id, name: trimmed, role: role, isActive: isActive);
|
||||
}
|
||||
|
||||
// No PIN from the server means no PIN that can ever be entered: hashing a
|
||||
// random value is how that is stored, rather than a sentinel a future
|
||||
// reader might treat as "any PIN accepted".
|
||||
final secret = pin ?? _uuid.v4();
|
||||
|
||||
await _db.insert(
|
||||
Tables.staff,
|
||||
{
|
||||
'id': id,
|
||||
'name': trimmed,
|
||||
'role': role.name,
|
||||
'pin_hash': PinHasher.hash(secret, salt),
|
||||
'pin_salt': salt,
|
||||
'must_change_pin': 0,
|
||||
'is_active': isActive ? 1 : 0,
|
||||
'created_at': now,
|
||||
'updated_at': now,
|
||||
},
|
||||
conflictAlgorithm: ConflictAlgorithm.replace,
|
||||
);
|
||||
|
||||
return StaffUser(id: id, name: trimmed, role: role, isActive: isActive);
|
||||
}
|
||||
|
||||
/// A stable local id for a back-office user, so a second sign-in updates the
|
||||
/// same row instead of duplicating the person.
|
||||
static String serverId(int userId) => 'srv-$userId';
|
||||
|
||||
Future<void> updateDetails({
|
||||
required String id,
|
||||
String? name,
|
||||
|
||||
Reference in New Issue
Block a user