Build staff management, store details editing, and a forced PIN change

Wires the last two dead buttons in Settings and closes the loop on the
credential work: hashed PINs are only worth having if a shop can actually
change them.

Users & roles (Manage)
- Add, rename, re-role and remove staff. Admin-only at the door, because
  anyone who can edit staff can make themselves an admin.
- PIN and confirmation are both required and must match. There is no email to
  reset a PIN with, so a typo nobody can verify locks the account out until an
  admin intervenes.
- Editing someone leaves their PIN alone unless a new one is typed. An admin
  setting another person's PIN counts as a reset and re-arms must-change.
- Removal is a deactivation with a confirmation that explains why: bills
  already rung keep the cashier's name, so shift reports stay correct.
- Anyone still on a shipped PIN is flagged in the list and in Settings.

Store details (Edit)
- Name, address, GSTIN and phone now editable and persisted. GSTIN is format
  and state-code validated; it prints on every invoice as a legal requirement,
  so a typo is a compliance problem across hundreds of bills.
- Admin-only: changing the GSTIN changes what every future invoice claims
  about who collected the tax.

Forced PIN change
- Shown once after sign-in while must-change is set, and not dismissable. The
  seeded PINs are in the source of the build, so a terminal still running one
  is effectively unprotected.

Fixed while testing: the role dropdown laid its items out at natural width and
"Manager — Sales, inventory and reports" overflowed the dialog by 222px. Now
isExpanded with the description spelled out below, where it is readable.

Tests: 160 -> 168. Covers both role guards, the mismatched and too-short PIN
paths, the default-PIN flag, and GSTIN and seller-name validation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Suriya
2026-08-01 13:10:49 +05:30
parent e17937e8f1
commit 3513281a11
5 changed files with 950 additions and 6 deletions

View File

@@ -12,6 +12,8 @@ import '../../../domain/entities/store_account.dart';
import '../../auth/providers/auth_controller.dart';
import '../providers/printer_settings.dart';
import '../widgets/back_office_dialog.dart';
import '../widgets/staff_dialogs.dart';
import '../widgets/store_details_dialog.dart';
import '../../sync/providers/sync_controller.dart';
import '../widgets/module_widgets.dart';
@@ -82,7 +84,10 @@ class _SettingsViewState extends ConsumerState<SettingsView> {
Widget _storeCard(StoreAccount? store) => PanelCard(
title: 'Store details',
subtitle: 'Printed on every invoice',
action: TextButton(onPressed: () {}, child: const Text('Edit')),
action: TextButton(
onPressed: () => showStoreDetailsDialog(context),
child: const Text('Edit'),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
@@ -296,7 +301,10 @@ class _SettingsViewState extends ConsumerState<SettingsView> {
Widget _staffCard(StoreAccount? store, StaffUser? current) => PanelCard(
title: 'Users & roles',
action: TextButton(onPressed: () {}, child: const Text('Manage')),
action: TextButton(
onPressed: () => showStaffDialog(context),
child: const Text('Manage'),
),
child: ResponsiveTable(
stackBelow: 360,
columns: const [
@@ -308,10 +316,12 @@ class _SettingsViewState extends ConsumerState<SettingsView> {
.map((s) => [
Cell(s.name, bold: true),
Cell(s.role.label, color: AppColors.textSecondary),
s.id == current?.id
? const TagChip('Signed in',
color: AppColors.success,)
: const SizedBox.shrink(),
if (s.id == current?.id)
const TagChip('Signed in', color: AppColors.success)
else if (s.mustChangePin)
const TagChip('Default PIN', color: AppColors.warning)
else
const SizedBox.shrink(),
],)
.toList(),
),