feat: add more detailed history and group per day
This commit is contained in:
@@ -8,10 +8,11 @@ import '../models/closed_tab_item.dart';
|
||||
import '../models/payment_method.dart';
|
||||
import '../models/product.dart';
|
||||
import '../models/tab_item.dart';
|
||||
import '../models/tab_item_purchase.dart';
|
||||
import 'product_service.dart';
|
||||
|
||||
abstract class BarTabService {
|
||||
Future<List<BarTab>> getOpenTabs();
|
||||
Future<List<BarTab>> getOpenTabs({bool includeRemoved = false});
|
||||
|
||||
Future<BarTab?> getTabById(String id);
|
||||
|
||||
@@ -58,7 +59,16 @@ class DriftBarTabService implements BarTabService {
|
||||
|
||||
DriftBarTabService({required this.database});
|
||||
|
||||
TabItem _mapItemRow(TabItemRow row) {
|
||||
TabItemPurchase _mapPurchaseRow(TabItemPurchaseRow row) {
|
||||
return TabItemPurchase(
|
||||
id: row.id,
|
||||
tabItemId: row.tabItemId,
|
||||
purchasedAt: row.purchasedAt,
|
||||
removedAt: row.removedAt,
|
||||
);
|
||||
}
|
||||
|
||||
TabItem _mapItemRow(TabItemRow row, List<TabItemPurchase> purchases) {
|
||||
return TabItem(
|
||||
id: row.id,
|
||||
tabId: row.tabId,
|
||||
@@ -66,6 +76,7 @@ class DriftBarTabService implements BarTabService {
|
||||
productName: row.productName,
|
||||
quantity: row.quantity,
|
||||
unitPriceInCents: row.unitPriceInCents,
|
||||
purchases: purchases,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -88,6 +99,8 @@ class DriftBarTabService implements BarTabService {
|
||||
productName: row.productName,
|
||||
quantity: row.quantity,
|
||||
unitPriceInCents: row.unitPriceInCents,
|
||||
purchasedAt: row.purchasedAt,
|
||||
removedAt: row.removedAt,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -98,7 +111,97 @@ class DriftBarTabService implements BarTabService {
|
||||
|
||||
final rows = await query.get();
|
||||
|
||||
return rows.map(_mapItemRow).toList();
|
||||
final items = <TabItem>[];
|
||||
|
||||
for (final row in rows) {
|
||||
if (row.quantity <= 0) continue;
|
||||
|
||||
final purchases = await _getPurchaseRecords(row);
|
||||
items.add(_mapItemRow(row, purchases));
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
Future<List<TabItem>> _getItemsForHistory(String tabId) async {
|
||||
final query = database.select(database.tabItems)
|
||||
..where((item) => item.tabId.equals(tabId))
|
||||
..orderBy([(item) => OrderingTerm.asc(item.createdAt)]);
|
||||
|
||||
final rows = await query.get();
|
||||
final items = <TabItem>[];
|
||||
|
||||
for (final row in rows) {
|
||||
final purchases = await _getPurchaseRecords(row);
|
||||
if (purchases.isEmpty) continue;
|
||||
|
||||
items.add(_mapItemRow(row, purchases));
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
Future<List<TabItemPurchaseRow>> _getPurchaseRows(String tabItemId) async {
|
||||
final query = database.select(database.tabItemPurchases)
|
||||
..where((purchase) => purchase.tabItemId.equals(tabItemId))
|
||||
..orderBy([
|
||||
(purchase) => OrderingTerm.asc(purchase.purchasedAt),
|
||||
(purchase) => OrderingTerm.asc(purchase.id),
|
||||
]);
|
||||
|
||||
return query.get();
|
||||
}
|
||||
|
||||
Future<List<TabItemPurchase>> _getPurchaseRecords(TabItemRow item) async {
|
||||
final rows = await _getPurchaseRows(item.id);
|
||||
final purchases = rows.map(_mapPurchaseRow).toList();
|
||||
final activeCount = purchases
|
||||
.where((purchase) => !purchase.isRemoved)
|
||||
.length;
|
||||
final missingActivePurchases = item.quantity - activeCount;
|
||||
|
||||
if (missingActivePurchases <= 0) return purchases;
|
||||
|
||||
return [
|
||||
...purchases,
|
||||
for (var index = 0; index < missingActivePurchases; index++)
|
||||
TabItemPurchase(
|
||||
id: 'legacy-${item.id}-$index',
|
||||
tabItemId: item.id,
|
||||
purchasedAt: item.createdAt,
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
Future<List<TabItemPurchaseRow>> _ensurePurchaseRows(TabItemRow item) async {
|
||||
final rows = await _getPurchaseRows(item.id);
|
||||
final activeCount = rows
|
||||
.where((purchase) => purchase.removedAt == null)
|
||||
.length;
|
||||
final missingActivePurchases = item.quantity - activeCount;
|
||||
|
||||
for (var index = 0; index < missingActivePurchases; index++) {
|
||||
await _recordPurchase(tabItemId: item.id, purchasedAt: item.createdAt);
|
||||
}
|
||||
|
||||
if (missingActivePurchases <= 0) return rows;
|
||||
|
||||
return _getPurchaseRows(item.id);
|
||||
}
|
||||
|
||||
Future<void> _recordPurchase({
|
||||
required String tabItemId,
|
||||
DateTime? purchasedAt,
|
||||
}) async {
|
||||
await database
|
||||
.into(database.tabItemPurchases)
|
||||
.insert(
|
||||
TabItemPurchasesCompanion.insert(
|
||||
id: _uuid.v4(),
|
||||
tabItemId: tabItemId,
|
||||
purchasedAt: purchasedAt ?? DateTime.now(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _decreaseProductStock(String productId, int amount) async {
|
||||
@@ -150,7 +253,7 @@ class DriftBarTabService implements BarTabService {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<BarTab>> getOpenTabs() async {
|
||||
Future<List<BarTab>> getOpenTabs({bool includeRemoved = false}) async {
|
||||
final query = database.select(database.barTabs)
|
||||
..where((tab) => tab.status.equals('open'))
|
||||
..orderBy([(tab) => OrderingTerm.desc(tab.openedAt)]);
|
||||
@@ -160,7 +263,9 @@ class DriftBarTabService implements BarTabService {
|
||||
final tabs = <BarTab>[];
|
||||
|
||||
for (final tabRow in tabRows) {
|
||||
final items = await _getItemsForTab(tabRow.id);
|
||||
final items = includeRemoved
|
||||
? await _getItemsForHistory(tabRow.id)
|
||||
: await _getItemsForTab(tabRow.id);
|
||||
|
||||
tabs.add(_mapTabRow(tabRow, items));
|
||||
}
|
||||
@@ -286,15 +391,18 @@ class DriftBarTabService implements BarTabService {
|
||||
await updateQuery.write(
|
||||
TabItemsCompanion(quantity: Value(existingItem.quantity + 1)),
|
||||
);
|
||||
await _recordPurchase(tabItemId: existingItem.id);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
final tabItemId = _uuid.v4();
|
||||
|
||||
await database
|
||||
.into(database.tabItems)
|
||||
.insert(
|
||||
TabItemsCompanion.insert(
|
||||
id: _uuid.v4(),
|
||||
id: tabItemId,
|
||||
tabId: tabId,
|
||||
productId: product.id,
|
||||
productName: product.name,
|
||||
@@ -303,6 +411,8 @@ class DriftBarTabService implements BarTabService {
|
||||
createdAt: DateTime.now(),
|
||||
),
|
||||
);
|
||||
|
||||
await _recordPurchase(tabItemId: tabItemId);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -316,24 +426,47 @@ class DriftBarTabService implements BarTabService {
|
||||
database.tabItems,
|
||||
)..where((row) => row.id.equals(tabItemId))).getSingleOrNull();
|
||||
|
||||
if (item == null || delta == 0) return 0;
|
||||
if (item == null || delta == 0 || (item.quantity == 0 && delta < 0)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
final requestedQuantity = item.quantity + delta;
|
||||
final actualDelta = requestedQuantity <= 0 ? -item.quantity : delta;
|
||||
|
||||
if (actualDelta > 0) {
|
||||
await _decreaseProductStock(item.productId, actualDelta);
|
||||
|
||||
for (var index = 0; index < actualDelta; index++) {
|
||||
await _recordPurchase(tabItemId: item.id);
|
||||
}
|
||||
} else {
|
||||
await _increaseProductStock(item.productId, -actualDelta);
|
||||
|
||||
final purchaseRows = await _ensurePurchaseRows(item);
|
||||
final activePurchaseRows =
|
||||
purchaseRows
|
||||
.where((purchase) => purchase.removedAt == null)
|
||||
.toList()
|
||||
..sort((a, b) => b.purchasedAt.compareTo(a.purchasedAt));
|
||||
final removedAt = DateTime.now();
|
||||
|
||||
for (final purchase in activePurchaseRows.take(-actualDelta)) {
|
||||
await (database.update(database.tabItemPurchases)
|
||||
..where((row) => row.id.equals(purchase.id)))
|
||||
.write(TabItemPurchasesCompanion(removedAt: Value(removedAt)));
|
||||
}
|
||||
}
|
||||
|
||||
if (requestedQuantity <= 0) {
|
||||
final deletedRows = await (database.delete(
|
||||
database.tabItems,
|
||||
)..where((row) => row.id.equals(tabItemId))).go();
|
||||
final updatedRows =
|
||||
await (database.update(database.tabItems)
|
||||
..where((row) => row.id.equals(tabItemId)))
|
||||
.write(const TabItemsCompanion(quantity: Value(0)));
|
||||
|
||||
if (deletedRows != 1) {
|
||||
throw StateError('Tab item was changed before it could be deleted');
|
||||
if (updatedRows != 1) {
|
||||
throw StateError(
|
||||
'Tab item was changed before its quantity was updated',
|
||||
);
|
||||
}
|
||||
} else {
|
||||
final updatedRows =
|
||||
@@ -367,7 +500,7 @@ class DriftBarTabService implements BarTabService {
|
||||
return;
|
||||
}
|
||||
|
||||
final items = await _getItemsForTab(tabId);
|
||||
final items = await _getItemsForHistory(tabId);
|
||||
|
||||
if (items.isEmpty) {
|
||||
return;
|
||||
@@ -388,18 +521,22 @@ class DriftBarTabService implements BarTabService {
|
||||
);
|
||||
|
||||
for (final item in items) {
|
||||
await database
|
||||
.into(database.closedTabItems)
|
||||
.insert(
|
||||
ClosedTabItemsCompanion.insert(
|
||||
id: _uuid.v4(),
|
||||
closedTabId: closedTabId,
|
||||
productId: item.productId,
|
||||
productName: item.productName,
|
||||
quantity: item.quantity,
|
||||
unitPriceInCents: item.unitPriceInCents,
|
||||
),
|
||||
);
|
||||
for (final purchase in item.purchases) {
|
||||
await database
|
||||
.into(database.closedTabItems)
|
||||
.insert(
|
||||
ClosedTabItemsCompanion.insert(
|
||||
id: _uuid.v4(),
|
||||
closedTabId: closedTabId,
|
||||
productId: item.productId,
|
||||
productName: item.productName,
|
||||
quantity: 1,
|
||||
unitPriceInCents: item.unitPriceInCents,
|
||||
purchasedAt: Value(purchase.purchasedAt),
|
||||
removedAt: Value(purchase.removedAt),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final deleteQuery = database.delete(database.tabItems)
|
||||
@@ -437,7 +574,11 @@ class DriftBarTabService implements BarTabService {
|
||||
|
||||
for (final row in closedTabRows) {
|
||||
final itemsQuery = database.select(database.closedTabItems)
|
||||
..where((item) => item.closedTabId.equals(row.id));
|
||||
..where((item) => item.closedTabId.equals(row.id))
|
||||
..orderBy([
|
||||
(item) => OrderingTerm.desc(item.purchasedAt),
|
||||
(item) => OrderingTerm.desc(item.id),
|
||||
]);
|
||||
|
||||
final itemRows = await itemsQuery.get();
|
||||
|
||||
|
||||
@@ -49,19 +49,27 @@ class DefaultExportService implements ExportService {
|
||||
|
||||
final itemRows = await itemsQuery.get();
|
||||
|
||||
result.add(_ClosedTabExport(
|
||||
id: tabRow.id,
|
||||
originalTabId: tabRow.originalTabId,
|
||||
customerName: tabRow.customerName,
|
||||
closedAt: tabRow.closedAt.toIso8601String(),
|
||||
paymentMethod: tabRow.paymentMethod,
|
||||
items: itemRows.map((row) => _ClosedTabItemExport(
|
||||
productId: row.productId,
|
||||
productName: row.productName,
|
||||
quantity: row.quantity,
|
||||
unitPriceInCents: row.unitPriceInCents,
|
||||
)).toList(),
|
||||
));
|
||||
result.add(
|
||||
_ClosedTabExport(
|
||||
id: tabRow.id,
|
||||
originalTabId: tabRow.originalTabId,
|
||||
customerName: tabRow.customerName,
|
||||
closedAt: tabRow.closedAt.toIso8601String(),
|
||||
paymentMethod: tabRow.paymentMethod,
|
||||
items: itemRows
|
||||
.map(
|
||||
(row) => _ClosedTabItemExport(
|
||||
productId: row.productId,
|
||||
productName: row.productName,
|
||||
quantity: row.quantity,
|
||||
unitPriceInCents: row.unitPriceInCents,
|
||||
purchasedAt: row.purchasedAt?.toIso8601String(),
|
||||
removedAt: row.removedAt?.toIso8601String(),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -71,22 +79,39 @@ class DefaultExportService implements ExportService {
|
||||
return {
|
||||
'exportedAt': DateTime.now().toIso8601String(),
|
||||
'appVersion': '1.0.7',
|
||||
'closedTabs': tabs.map((tab) => {
|
||||
'id': tab.id,
|
||||
'originalTabId': tab.originalTabId,
|
||||
'customerName': tab.customerName,
|
||||
'closedAt': tab.closedAt,
|
||||
'paymentMethod': tab.paymentMethod,
|
||||
'items': tab.items.map((item) => {
|
||||
'productId': item.productId,
|
||||
'productName': item.productName,
|
||||
'quantity': item.quantity,
|
||||
'unitPriceInCents': item.unitPriceInCents,
|
||||
'lineTotalInCents': item.quantity * item.unitPriceInCents,
|
||||
}).toList(),
|
||||
'itemCount': tab.items.fold(0, (sum, item) => sum + item.quantity),
|
||||
'totalInCents': tab.items.fold(0, (sum, item) => sum + item.quantity * item.unitPriceInCents),
|
||||
}).toList(),
|
||||
'closedTabs': tabs
|
||||
.map(
|
||||
(tab) => {
|
||||
'id': tab.id,
|
||||
'originalTabId': tab.originalTabId,
|
||||
'customerName': tab.customerName,
|
||||
'closedAt': tab.closedAt,
|
||||
'paymentMethod': tab.paymentMethod,
|
||||
'items': tab.items
|
||||
.map(
|
||||
(item) => {
|
||||
'productId': item.productId,
|
||||
'productName': item.productName,
|
||||
'quantity': item.quantity,
|
||||
'unitPriceInCents': item.unitPriceInCents,
|
||||
'purchasedAt': item.purchasedAt,
|
||||
'removedAt': item.removedAt,
|
||||
'isRemoved': item.isRemoved,
|
||||
'lineTotalInCents': item.lineTotalInCents,
|
||||
},
|
||||
)
|
||||
.toList(),
|
||||
'itemCount': tab.items.fold(
|
||||
0,
|
||||
(sum, item) => sum + (item.isRemoved ? 0 : item.quantity),
|
||||
),
|
||||
'totalInCents': tab.items.fold(
|
||||
0,
|
||||
(sum, item) => sum + item.lineTotalInCents,
|
||||
),
|
||||
},
|
||||
)
|
||||
.toList(),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -96,13 +121,21 @@ class _ClosedTabItemExport {
|
||||
final String productName;
|
||||
final int quantity;
|
||||
final int unitPriceInCents;
|
||||
final String? purchasedAt;
|
||||
final String? removedAt;
|
||||
|
||||
_ClosedTabItemExport({
|
||||
required this.productId,
|
||||
required this.productName,
|
||||
required this.quantity,
|
||||
required this.unitPriceInCents,
|
||||
required this.purchasedAt,
|
||||
required this.removedAt,
|
||||
});
|
||||
|
||||
bool get isRemoved => removedAt != null;
|
||||
|
||||
int get lineTotalInCents => isRemoved ? 0 : quantity * unitPriceInCents;
|
||||
}
|
||||
|
||||
class _ClosedTabExport {
|
||||
@@ -121,4 +154,4 @@ class _ClosedTabExport {
|
||||
required this.paymentMethod,
|
||||
required this.items,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user