added product import and billing integration

This commit is contained in:
2026-08-05 18:15:21 +05:30
parent 33b4337933
commit 6c0266c9c7
32 changed files with 889 additions and 457 deletions

View File

@@ -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),
),
],
),