third commit
This commit is contained in:
@@ -171,17 +171,111 @@ class OrderDao {
|
||||
);
|
||||
|
||||
// ----------------------------------------------------------------- Sync
|
||||
/// Flips accepted orders to `sync_status = 1`.
|
||||
Future<void> markSynced(List<String> orderIds) async {
|
||||
if (orderIds.isEmpty) return;
|
||||
final now = DateTime.now().millisecondsSinceEpoch;
|
||||
final placeholders = List.filled(orderIds.length, '?').join(',');
|
||||
/// Folds accepted orders into the day archive, then deletes them.
|
||||
///
|
||||
/// Once the server holds a bill the terminal has no reason to keep it, so
|
||||
/// the rows go. Their figures are added to [Tables.dayArchive] first, so the
|
||||
/// shift totals a cashier sees do not collapse after a mid-shift sync.
|
||||
/// Both steps run in one transaction: if the delete fails the archive is
|
||||
/// rolled back with it, and nothing is counted twice.
|
||||
Future<void> archiveAndDelete(List<SaleTransaction> orders) async {
|
||||
if (orders.isEmpty) return;
|
||||
|
||||
await _db.rawUpdate(
|
||||
'UPDATE ${Tables.orders} SET sync_status = ?, synced_at = ?, '
|
||||
'sync_error = NULL WHERE id IN ($placeholders)',
|
||||
[synced, now, ...orderIds],
|
||||
await _db.transaction((txn) async {
|
||||
final byDate = <String, List<SaleTransaction>>{};
|
||||
for (final o in orders) {
|
||||
byDate.putIfAbsent(businessDateOf(o.createdAt), () => []).add(o);
|
||||
}
|
||||
|
||||
for (final entry in byDate.entries) {
|
||||
final date = entry.key;
|
||||
final batchOrders = entry.value;
|
||||
|
||||
final prior = await txn.query(
|
||||
Tables.dayArchive,
|
||||
where: 'business_date = ?',
|
||||
whereArgs: [date],
|
||||
limit: 1,
|
||||
);
|
||||
final existing = prior.isEmpty ? null : prior.first;
|
||||
|
||||
final priorPayments = existing == null
|
||||
? <String, double>{}
|
||||
: (jsonDecode(existing['payments_json']! as String)
|
||||
as Map<String, Object?>)
|
||||
.map((k, v) => MapEntry(k, (v! as num).toDouble()));
|
||||
|
||||
for (final o in batchOrders) {
|
||||
for (final p in o.payments) {
|
||||
priorPayments[p.method.name] =
|
||||
(priorPayments[p.method.name] ?? 0) + p.amount;
|
||||
}
|
||||
}
|
||||
|
||||
final firsts = batchOrders
|
||||
.map((o) => o.createdAt.millisecondsSinceEpoch)
|
||||
.toList()
|
||||
..sort();
|
||||
|
||||
await txn.insert(
|
||||
Tables.dayArchive,
|
||||
{
|
||||
'business_date': date,
|
||||
'bill_count':
|
||||
((existing?['bill_count'] as int?) ?? 0) + batchOrders.length,
|
||||
'item_count': ((existing?['item_count'] as num?)?.toDouble() ?? 0) +
|
||||
batchOrders.fold(0.0, (s, o) => s + o.cart.totalQuantity),
|
||||
'gross_sales':
|
||||
((existing?['gross_sales'] as num?)?.toDouble() ?? 0) +
|
||||
batchOrders.fold(0.0, (s, o) => s + o.total),
|
||||
'tax_collected':
|
||||
((existing?['tax_collected'] as num?)?.toDouble() ?? 0) +
|
||||
batchOrders.fold(0.0, (s, o) => s + o.cart.taxAmount),
|
||||
'discount_given':
|
||||
((existing?['discount_given'] as num?)?.toDouble() ?? 0) +
|
||||
batchOrders.fold(
|
||||
0.0,
|
||||
(s, o) =>
|
||||
s +
|
||||
o.cart.billDiscountTotal +
|
||||
o.cart.lineDiscountTotal,
|
||||
),
|
||||
'round_off': ((existing?['round_off'] as num?)?.toDouble() ?? 0) +
|
||||
batchOrders.fold(0.0, (s, o) => s + o.cart.roundOff),
|
||||
'points_issued': ((existing?['points_issued'] as int?) ?? 0) +
|
||||
batchOrders.fold(0, (s, o) => s + o.pointsEarned),
|
||||
'points_redeemed': ((existing?['points_redeemed'] as int?) ?? 0) +
|
||||
batchOrders.fold(0, (s, o) => s + o.pointsRedeemed),
|
||||
'payments_json': jsonEncode(priorPayments),
|
||||
'first_bill_at': existing?['first_bill_at'] ?? firsts.first,
|
||||
'last_bill_at': firsts.last,
|
||||
'synced_bills':
|
||||
((existing?['synced_bills'] as int?) ?? 0) + batchOrders.length,
|
||||
},
|
||||
conflictAlgorithm: ConflictAlgorithm.replace,
|
||||
);
|
||||
}
|
||||
|
||||
// order_items goes with it via ON DELETE CASCADE.
|
||||
final ids = orders.map((o) => o.id).toList();
|
||||
final placeholders = List.filled(ids.length, '?').join(',');
|
||||
await txn.delete(
|
||||
Tables.orders,
|
||||
where: 'id IN ($placeholders)',
|
||||
whereArgs: ids,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/// Archived figures for a business day, or null if nothing has synced yet.
|
||||
Future<Map<String, Object?>?> dayArchive(DateTime day) async {
|
||||
final rows = await _db.query(
|
||||
Tables.dayArchive,
|
||||
where: 'business_date = ?',
|
||||
whereArgs: [businessDateOf(day)],
|
||||
limit: 1,
|
||||
);
|
||||
return rows.isEmpty ? null : rows.first;
|
||||
}
|
||||
|
||||
/// Records a failed attempt. The rows stay at `sync_status = 0`.
|
||||
|
||||
Reference in New Issue
Block a user