This commit is contained in:
2026-08-07 15:31:20 +05:30
parent 09e5e29df2
commit ad44402232
17 changed files with 598 additions and 1187 deletions

View File

@@ -14,17 +14,16 @@ import '../../sync/widgets/sign_out_dialog.dart';
/// Asks what "signing out" means before doing it.
///
/// A cashier stepping off the counter for a few minutes and a cashier
/// finishing for the day want different things from the *shift*, not from the
/// terminal. A temporary logout leaves the shift open — bills stay queued and
/// today's totals keep accumulating — and simply locks the screen. Ending the
/// shift means counting the drawer, reconciling it, and handing the till back.
/// Both cashier paths clear the products — the till never keeps a catalogue
/// across a sign-out, whatever the reason. What they differ on is the drawer:
/// a temporary logout locks the screen and leaves the money alone, while
/// ending the shift counts it and reconciles it against what was rung.
/// Treating both as one button meant either the drawer was never settled, or a
/// cashier stepping away for ten minutes had to count it first.
///
/// What both do identically: the products come off this terminal. A cashier
/// session never leaves a catalogue sitting on an unattended screen, so an
/// admin re-imports it before the counter is worked again.
///
/// Admins see the plain sign-out dialog: they have no drawer to settle.
/// Admins see the plain sign-out dialog: they have no drawer to settle, and
/// their sign-out deliberately leaves the catalogue in place for whoever picks
/// the terminal up.
Future<void> showSessionEndSheet(BuildContext context, WidgetRef ref) async {
if (!ref.read(isCashierModeProvider)) {
return showSignOutDialog(context, ref);
@@ -40,20 +39,17 @@ Future<void> showSessionEndSheet(BuildContext context, WidgetRef ref) async {
class _SessionEndDialog extends ConsumerWidget {
const _SessionEndDialog();
/// Closes the session without closing the shift.
/// Locks the screen and returns to the login screen.
///
/// Explicitly *not* a shift end: unsynced bills stay queued and today's
/// totals keep accumulating against the same day, so the drawer is still
/// settled once, at the end. What it does not leave behind is the
/// catalogue — every cashier sign-out takes the products with it, this one
/// included, so an admin imports them again before billing resumes.
/// Explicitly *not* a shift end — the drawer is left alone, unsynced bills
/// stay queued, and today's totals keep accumulating against the same day.
/// The catalogue still goes: a terminal sitting unattended at a login screen
/// must not be holding a shop's prices and stock, and an admin re-imports in
/// seconds.
Future<void> _temporaryLogout(BuildContext context, WidgetRef ref) async {
ref.read(cartControllerProvider.notifier).reset();
await ref.read(authControllerProvider.notifier).signOut();
// Bump the version so catalogueReadyProvider re-reads hasCatalogue, and
// drop the cached lists so the next session's grid does not flash this
// session's products before it re-checks what is on disk.
ref.read(catalogueVersionProvider.notifier).state++;
ref.invalidate(allProductsProvider);
ref.invalidate(visibleProductsProvider);
@@ -65,22 +61,8 @@ class _SessionEndDialog extends ConsumerWidget {
ref.read(selectedCategoryProvider.notifier).state = null;
if (!context.mounted) return;
// Resolved while this context is still mounted — the messenger lives above
// the router, so the bar survives the route change below.
final messenger = ScaffoldMessenger.of(context);
Navigator.of(context).pop();
context.go(AppRoutes.login);
messenger
..hideCurrentSnackBar()
..showSnackBar(const SnackBar(
content: Text(
'Logged out. The shift is still open, and the product catalogue has '
'been removed from this terminal.',
),
),);
}
@override
@@ -119,8 +101,8 @@ class _SessionEndDialog extends ConsumerWidget {
const SizedBox(width: AppSpacing.sm),
Expanded(
child: Text(
'The current bill has ${cart.lineCount} item(s) and '
'will be cleared either way.',
'The current bill has ${cart.lineCount} item(s), and '
'the imported products are cleared either way.',
style: const TextStyle(
fontSize: 12.5,
color: AppColors.warning,
@@ -132,44 +114,13 @@ class _SessionEndDialog extends ConsumerWidget {
),
),
// The one thing both choices do, said once here rather than
// repeated in each card and discovered at the next login.
Container(
margin: const EdgeInsets.only(bottom: AppSpacing.md),
padding: const EdgeInsets.all(AppSpacing.md),
decoration: const BoxDecoration(
color: AppColors.infoSurface,
borderRadius: AppRadius.brSm,
),
child: const Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(Icons.delete_sweep_outlined,
size: 18, color: AppColors.info,),
SizedBox(width: AppSpacing.sm),
Expanded(
child: Text(
'Either way, the product catalogue is removed from '
'this terminal. An admin imports it again before the '
'counter is worked.',
style: TextStyle(
fontSize: 12.5,
color: AppColors.info,
height: 1.45,
),
),
),
],
),
),
_Choice(
icon: Icons.lock_clock_outlined,
icon: Icons.lock_outline_rounded,
tone: AppColors.info,
title: 'Temporary logout',
body: 'Locks the terminal without closing the shift. Bills '
'stay queued and todays totals keep running, so the '
'drawer is still counted once at the end.',
body: 'Locks the screen. The drawer is left as it is and the '
'day keeps running — an admin re-imports the products when '
'you come back.',
onTap: () => _temporaryLogout(context, ref),
),
const SizedBox(height: AppSpacing.md),
@@ -178,10 +129,11 @@ class _SessionEndDialog extends ConsumerWidget {
tone: AppColors.primary,
title: 'End shift',
body: pending == 0
? 'Count the drawer, check it against what was rung, then '
'hand the till over.'
: 'Count the drawer, check it against what was rung, then '
'upload the $pending bill(s) still held here.',
? 'Count the drawer. It has to match what was rung before '
'the till can be handed over.'
: 'Count the drawer and upload the $pending bill(s) still '
'held here. The count has to match before you can sign '
'out.',
onTap: () {
Navigator.of(context).pop();
context.push(AppRoutes.endShift);