300 lines
7.2 KiB
Dart
300 lines
7.2 KiB
Dart
import 'package:drift/drift.dart';
|
|
import 'package:uuid/uuid.dart';
|
|
|
|
import '../database/app_database.dart';
|
|
import '../models/bar_tab.dart';
|
|
import '../models/closed_tab.dart';
|
|
import '../models/closed_tab_item.dart';
|
|
import '../models/product.dart';
|
|
import '../models/tab_item.dart';
|
|
|
|
abstract class BarTabService {
|
|
Future<List<BarTab>> getOpenTabs();
|
|
|
|
Future<BarTab?> getTabById(String id);
|
|
|
|
Future<BarTab> createTab({
|
|
required String customerName,
|
|
});
|
|
|
|
Future<void> addProductToTab({
|
|
required String tabId,
|
|
required Product product,
|
|
});
|
|
|
|
Future<void> updateTabItemQuantity({
|
|
required String tabItemId,
|
|
required int quantity,
|
|
});
|
|
|
|
/// Archives the tab's current items into history and clears them.
|
|
/// The tab itself stays open under the same customer name.
|
|
Future<void> closeTab(String tabId);
|
|
|
|
Future<List<ClosedTab>> getClosedTabs();
|
|
}
|
|
|
|
class DriftBarTabService implements BarTabService {
|
|
final AppDatabase database;
|
|
final _uuid = const Uuid();
|
|
|
|
DriftBarTabService({
|
|
required this.database,
|
|
});
|
|
|
|
TabItem _mapItemRow(TabItemRow row) {
|
|
return TabItem(
|
|
id: row.id,
|
|
tabId: row.tabId,
|
|
productId: row.productId,
|
|
productName: row.productName,
|
|
quantity: row.quantity,
|
|
unitPriceInCents: row.unitPriceInCents,
|
|
);
|
|
}
|
|
|
|
BarTab _mapTabRow(
|
|
BarTabRow row,
|
|
List<TabItem> items,
|
|
) {
|
|
return BarTab(
|
|
id: row.id,
|
|
customerName: row.customerName,
|
|
status: row.status,
|
|
openedAt: row.openedAt,
|
|
closedAt: row.closedAt,
|
|
items: items,
|
|
);
|
|
}
|
|
|
|
ClosedTabItem _mapClosedItemRow(ClosedTabItemRow row) {
|
|
return ClosedTabItem(
|
|
id: row.id,
|
|
closedTabId: row.closedTabId,
|
|
productId: row.productId,
|
|
productName: row.productName,
|
|
quantity: row.quantity,
|
|
unitPriceInCents: row.unitPriceInCents,
|
|
);
|
|
}
|
|
|
|
Future<List<TabItem>> _getItemsForTab(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();
|
|
|
|
return rows.map(_mapItemRow).toList();
|
|
}
|
|
|
|
@override
|
|
Future<List<BarTab>> getOpenTabs() async {
|
|
final query = database.select(database.barTabs)
|
|
..where((tab) => tab.status.equals('open'))
|
|
..orderBy([
|
|
(tab) => OrderingTerm.desc(tab.openedAt),
|
|
]);
|
|
|
|
final tabRows = await query.get();
|
|
|
|
final tabs = <BarTab>[];
|
|
|
|
for (final tabRow in tabRows) {
|
|
final items = await _getItemsForTab(tabRow.id);
|
|
|
|
tabs.add(_mapTabRow(tabRow, items));
|
|
}
|
|
|
|
return tabs;
|
|
}
|
|
|
|
@override
|
|
Future<BarTab?> getTabById(String id) async {
|
|
final query = database.select(database.barTabs)
|
|
..where((tab) => tab.id.equals(id));
|
|
|
|
final tabRow = await query.getSingleOrNull();
|
|
|
|
if (tabRow == null) {
|
|
return null;
|
|
}
|
|
|
|
final items = await _getItemsForTab(tabRow.id);
|
|
|
|
return _mapTabRow(tabRow, items);
|
|
}
|
|
|
|
@override
|
|
Future<BarTab> createTab({
|
|
required String customerName,
|
|
}) async {
|
|
final id = _uuid.v4();
|
|
|
|
await database.into(database.barTabs).insert(
|
|
BarTabsCompanion.insert(
|
|
id: id,
|
|
customerName: customerName,
|
|
status: const Value('open'),
|
|
openedAt: DateTime.now(),
|
|
),
|
|
);
|
|
|
|
final tab = await getTabById(id);
|
|
|
|
if (tab == null) {
|
|
throw Exception('Could not create tab.');
|
|
}
|
|
|
|
return tab;
|
|
}
|
|
|
|
@override
|
|
Future<void> addProductToTab({
|
|
required String tabId,
|
|
required Product product,
|
|
}) async {
|
|
await database.transaction(() async {
|
|
final existingItemQuery = database.select(database.tabItems)
|
|
..where(
|
|
(item) =>
|
|
item.tabId.equals(tabId) &
|
|
item.productId.equals(product.id),
|
|
);
|
|
|
|
final existingItem = await existingItemQuery.getSingleOrNull();
|
|
|
|
if (existingItem != null) {
|
|
final updateQuery = database.update(database.tabItems)
|
|
..where((item) => item.id.equals(existingItem.id));
|
|
|
|
await updateQuery.write(
|
|
TabItemsCompanion(
|
|
quantity: Value(existingItem.quantity + 1),
|
|
),
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
await database.into(database.tabItems).insert(
|
|
TabItemsCompanion.insert(
|
|
id: _uuid.v4(),
|
|
tabId: tabId,
|
|
productId: product.id,
|
|
productName: product.name,
|
|
quantity: 1,
|
|
unitPriceInCents: product.priceInCents,
|
|
createdAt: DateTime.now(),
|
|
),
|
|
);
|
|
});
|
|
}
|
|
|
|
@override
|
|
Future<void> updateTabItemQuantity({
|
|
required String tabItemId,
|
|
required int quantity,
|
|
}) async {
|
|
if (quantity <= 0) {
|
|
final deleteQuery = database.delete(database.tabItems)
|
|
..where((item) => item.id.equals(tabItemId));
|
|
|
|
await deleteQuery.go();
|
|
return;
|
|
}
|
|
|
|
final updateQuery = database.update(database.tabItems)
|
|
..where((item) => item.id.equals(tabItemId));
|
|
|
|
await updateQuery.write(
|
|
TabItemsCompanion(
|
|
quantity: Value(quantity),
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Future<void> closeTab(String tabId) async {
|
|
await database.transaction(() async {
|
|
final tabQuery = database.select(database.barTabs)
|
|
..where((tab) => tab.id.equals(tabId));
|
|
|
|
final tabRow = await tabQuery.getSingleOrNull();
|
|
|
|
if (tabRow == null) {
|
|
return;
|
|
}
|
|
|
|
final items = await _getItemsForTab(tabId);
|
|
|
|
if (items.isEmpty) {
|
|
// Nothing to settle, leave the tab as-is.
|
|
return;
|
|
}
|
|
|
|
final closedTabId = _uuid.v4();
|
|
|
|
await database.into(database.closedTabs).insert(
|
|
ClosedTabsCompanion.insert(
|
|
id: closedTabId,
|
|
originalTabId: tabId,
|
|
customerName: tabRow.customerName,
|
|
closedAt: DateTime.now(),
|
|
),
|
|
);
|
|
|
|
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,
|
|
),
|
|
);
|
|
}
|
|
|
|
final deleteQuery = database.delete(database.tabItems)
|
|
..where((item) => item.tabId.equals(tabId));
|
|
|
|
await deleteQuery.go();
|
|
|
|
// Note: tab status/customerName untouched on purpose — the tab
|
|
// stays open so it keeps showing in the open tabs list.
|
|
});
|
|
}
|
|
|
|
@override
|
|
Future<List<ClosedTab>> getClosedTabs() async {
|
|
final query = database.select(database.closedTabs)
|
|
..orderBy([
|
|
(tab) => OrderingTerm.desc(tab.closedAt),
|
|
]);
|
|
|
|
final closedTabRows = await query.get();
|
|
|
|
final closedTabs = <ClosedTab>[];
|
|
|
|
for (final row in closedTabRows) {
|
|
final itemsQuery = database.select(database.closedTabItems)
|
|
..where((item) => item.closedTabId.equals(row.id));
|
|
|
|
final itemRows = await itemsQuery.get();
|
|
|
|
closedTabs.add(ClosedTab(
|
|
id: row.id,
|
|
originalTabId: row.originalTabId,
|
|
customerName: row.customerName,
|
|
closedAt: row.closedAt,
|
|
items: itemRows.map<ClosedTabItem>(_mapClosedItemRow).toList(),
|
|
));
|
|
}
|
|
|
|
return closedTabs;
|
|
}
|
|
} |