added product import and billing integration
This commit is contained in:
@@ -15,13 +15,22 @@ import '../../../core/utils/extensions.dart';
|
||||
import '../../../core/utils/formatters.dart';
|
||||
import '../../../core/widgets/glass_card.dart';
|
||||
import '../../../core/widgets/primary_button.dart';
|
||||
import '../../../data/sync/sync_engine.dart';
|
||||
import '../../../domain/entities/transaction.dart';
|
||||
import '../../modules/providers/printer_settings.dart';
|
||||
import '../../pos/providers/cart_controller.dart';
|
||||
import '../../pos/providers/catalog_providers.dart';
|
||||
import '../../sync/providers/sync_controller.dart';
|
||||
import '../widgets/receipt_preview.dart';
|
||||
|
||||
/// Confirmation screen. Counts down and starts the next sale on its own so an
|
||||
/// unattended terminal never sits on a finished bill.
|
||||
/// Confirmation screen.
|
||||
///
|
||||
/// The bill is written to SQLite the instant checkout completes, but is
|
||||
/// deliberately held back from the server for [AppConstants.postSaleResetDelay]
|
||||
/// — long enough to catch a mistake. Counts down and starts the next sale (and
|
||||
/// releases the bill to the sync engine) on its own so an unattended terminal
|
||||
/// never sits on a finished bill forever; "Cancel sale" inside the window
|
||||
/// voids it instead and hands the cart straight back.
|
||||
class ReceiptScreen extends ConsumerStatefulWidget {
|
||||
const ReceiptScreen({super.key, required this.transaction});
|
||||
|
||||
@@ -32,9 +41,13 @@ class ReceiptScreen extends ConsumerStatefulWidget {
|
||||
}
|
||||
|
||||
class _ReceiptScreenState extends ConsumerState<ReceiptScreen> {
|
||||
late int _seconds = AppConstants.postSaleResetDelay.inSeconds + 5;
|
||||
late int _seconds = AppConstants.postSaleResetDelay.inSeconds;
|
||||
Timer? _timer;
|
||||
|
||||
/// True while a cancellation is being written to disk — guards against a
|
||||
/// second tap voiding a sale that is already half-reversed.
|
||||
bool _voiding = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@@ -82,13 +95,36 @@ class _ReceiptScreenState extends ConsumerState<ReceiptScreen> {
|
||||
|
||||
void _newSale() {
|
||||
_timer?.cancel();
|
||||
// This is the one moment the bill is actually released to the server —
|
||||
// whether the window ran out on its own or New Sale was pressed early,
|
||||
// the cashier has let this bill stand.
|
||||
ref.read(syncEngineProvider).nudge(SyncTrigger.saleCommitted);
|
||||
ref.read(cartControllerProvider.notifier).reset();
|
||||
if (mounted) context.go(AppRoutes.pos);
|
||||
}
|
||||
|
||||
void _continueBilling() {
|
||||
/// Undoes the sale: deletes the order, puts the stock back, restores an
|
||||
/// attached shopper's loyalty balance, and hands the exact same cart back
|
||||
/// to the billing screen. Never touches the sync engine — this bill must
|
||||
/// never reach the server.
|
||||
Future<void> _cancelSale() async {
|
||||
if (_voiding) return;
|
||||
setState(() => _voiding = true);
|
||||
_timer?.cancel();
|
||||
ref.read(cartControllerProvider.notifier).reset();
|
||||
|
||||
final txn = widget.transaction;
|
||||
await ref.read(transactionRepositoryProvider).voidSale(
|
||||
transaction: txn,
|
||||
stockMovements: {
|
||||
for (final line in txn.cart.lines) line.product.id: line.quantity,
|
||||
},
|
||||
);
|
||||
|
||||
ref.invalidate(allProductsProvider);
|
||||
ref.invalidate(visibleProductsProvider);
|
||||
ref.read(orderVersionProvider.notifier).state++;
|
||||
ref.read(cartControllerProvider.notifier).restore(txn.cart);
|
||||
|
||||
if (mounted) context.go(AppRoutes.pos);
|
||||
}
|
||||
|
||||
@@ -293,12 +329,23 @@ class _ReceiptScreenState extends ConsumerState<ReceiptScreen> {
|
||||
: 'New Sale',
|
||||
icon: Icons.add_shopping_cart_rounded,
|
||||
large: true,
|
||||
onPressed: _newSale,
|
||||
onPressed: _voiding ? null : _newSale,
|
||||
),
|
||||
const SizedBox(height: AppSpacing.sm),
|
||||
TextButton(
|
||||
onPressed: _continueBilling,
|
||||
child: const Text('Back to billing screen'),
|
||||
TextButton.icon(
|
||||
onPressed: _voiding ? null : _cancelSale,
|
||||
icon: _voiding
|
||||
? const SizedBox(
|
||||
width: 14,
|
||||
height: 14,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
color: AppColors.danger,
|
||||
),
|
||||
)
|
||||
: const Icon(Icons.undo_rounded, size: 16),
|
||||
label: Text(_voiding ? 'Cancelling…' : 'Cancel sale'),
|
||||
style: TextButton.styleFrom(foregroundColor: AppColors.danger),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user