import '../entities/shift_report.dart'; import '../entities/sync_event.dart'; import '../entities/transaction.dart'; /// Result of one end-of-day upload. class SyncOutcome { const SyncOutcome({ required this.attempted, required this.uploaded, this.error, }); final int attempted; final int uploaded; final String? error; bool get isSuccess => error == null; bool get hadNothingToDo => attempted == 0; int get remaining => attempted - uploaded; } /// One row of the order sync log. class OrderSyncRow { const OrderSyncRow({ required this.orderId, required this.invoiceNumber, required this.total, required this.createdAt, required this.isSynced, this.syncedAt, this.attempts = 0, this.error, }); final String orderId; final String invoiceNumber; final double total; final DateTime createdAt; final bool isSynced; final DateTime? syncedAt; final int attempts; final String? error; } /// The terminal's two network touchpoints. /// /// Morning: pull the catalogue. End of day: upload every order still at /// `sync_status = 0`. Nothing else leaves the device. abstract class SyncRepository { bool get hasCatalogue; DateTime? get lastImportAt; String? get catalogueRevision; /// Morning step — downloads products and writes them to SQLite. Future importCatalogue({ void Function(double progress, String stage)? onProgress, }); /// How many bills are still held locally. Future unsyncedCount(); Future> unsyncedOrders(); /// Today's trading totals, read back from SQLite. /// /// Terminal-wide by default. Set [scopeToCashier] to cover only the bills /// [cashierName] rang — what a till is actually settled against. Future todayReport({ required String terminalId, required String cashierName, bool scopeToCashier = false, }); /// End-of-day step — uploads pending orders and flips the accepted ones to /// `sync_status = 1`. Failures leave every row untouched at 0. Future syncOrders({ void Function(double progress, String stage)? onProgress, }); Future> orderSyncRows({int limit = 200}); List get events; }