feat: add more detailed history and group per day

This commit is contained in:
2026-08-07 05:07:41 +02:00
parent 1dd7e69aa8
commit 799c1c4045
23 changed files with 2506 additions and 346 deletions
+132 -59
View File
@@ -1,3 +1,4 @@
import 'dart:math';
import 'dart:io';
import 'package:flutter/foundation.dart';
@@ -194,16 +195,18 @@ class DevMenuViewModel extends ChangeNotifier {
final (name, priceInCents, stock) = demoProducts[i];
final category = categories[i ~/ 5];
await database.into(database.products).insert(
ProductsCompanion.insert(
id: uuid.v4(),
name: name,
category: category,
stockQuantity: stock,
lowStockThreshold: 10,
priceInCents: priceInCents,
),
);
await database
.into(database.products)
.insert(
ProductsCompanion.insert(
id: uuid.v4(),
name: name,
category: category,
stockQuantity: stock,
lowStockThreshold: 10,
priceInCents: priceInCents,
),
);
}
_lastAction = 'Seeded ${demoProducts.length} demo products';
@@ -241,10 +244,7 @@ class DevMenuViewModel extends ChangeNotifier {
final randomProducts = products.take(3).toList();
for (final product in randomProducts) {
await barTabService.addProductToTab(
tabId: tab.id,
product: product,
);
await barTabService.addProductToTab(tabId: tab.id, product: product);
}
_lastAction = 'Created test tab with ${randomProducts.length} items';
@@ -262,16 +262,18 @@ class DevMenuViewModel extends ChangeNotifier {
final uuid = const Uuid();
for (var i = 0; i < count; i++) {
await database.into(database.products).insert(
ProductsCompanion.insert(
id: uuid.v4(),
name: 'Test Product $i',
category: 'Test Category ${i % 5}',
stockQuantity: 50 + (i % 50),
lowStockThreshold: 5,
priceInCents: 100 + (i * 10),
),
);
await database
.into(database.products)
.insert(
ProductsCompanion.insert(
id: uuid.v4(),
name: 'Test Product $i',
category: 'Test Category ${i % 5}',
stockQuantity: 50 + (i % 50),
lowStockThreshold: 5,
priceInCents: 100 + (i * 10),
),
);
}
_lastAction = 'Added $count test products';
@@ -341,59 +343,130 @@ class DevMenuViewModel extends ChangeNotifier {
}
final uuid = const Uuid();
final random = Random();
final today = DateTime.now();
final startOfToday = DateTime(today.year, today.month, today.day);
final customerNames = [
'Alice', 'Bob', 'Charlie', 'Diana', 'Eve', 'Frank',
'Grace', 'Hank', 'Ivy', 'Jack', 'Kate', 'Leo',
'Mia', 'Noah', 'Olivia', 'Pete', 'Quinn', 'Rose',
'Sam', 'Tina', 'Umar', 'Vera', 'Wes', 'Xena',
'Yves', 'Zara',
'Alice',
'Bob',
'Charlie',
'Diana',
'Eve',
'Frank',
'Grace',
'Hank',
'Ivy',
'Jack',
'Kate',
'Leo',
'Mia',
'Noah',
'Olivia',
'Pete',
'Quinn',
'Rose',
'Sam',
'Tina',
'Umar',
'Vera',
'Wes',
'Xena',
'Yves',
'Zara',
];
const paymentMethods = [PaymentMethod.cash, PaymentMethod.payconiq];
final usedCustomers = <String>{};
await database.transaction(() async {
for (var i = 0; i < count; i++) {
final customer = customerNames[i % customerNames.length];
final customer = customerNames[random.nextInt(customerNames.length)];
usedCustomers.add(customer);
final closedTabId = uuid.v4();
final itemCount = 1 + (i % 5);
final distinctProductCount = min(
products.length,
1 + random.nextInt(5),
);
final shuffledProducts = [...products]..shuffle(random);
final closedAt = DateTime.now().subtract(
final orderDay = startOfToday.subtract(
Duration(days: random.nextInt(60)),
);
final closedAt = DateTime(
orderDay.year,
orderDay.month,
orderDay.day,
12 + random.nextInt(10),
random.nextInt(60),
);
// A few mock tabs span multiple days so the history day dropdowns
// also get realistic multi-day data.
final spanDays = random.nextInt(5) == 0 ? 1 + random.nextInt(2) : 0;
final firstPurchaseAt = closedAt.subtract(
Duration(
days: i % 90,
hours: i % 24,
minutes: i % 60,
days: spanDays,
hours: random.nextInt(4),
minutes: random.nextInt(60),
),
);
final purchaseWindowMinutes = closedAt
.difference(firstPurchaseAt)
.inMinutes;
await database.into(database.closedTabs).insert(
ClosedTabsCompanion.insert(
id: closedTabId,
originalTabId: uuid.v4(),
customerName: customer,
closedAt: closedAt,
paymentMethod: Value(paymentMethods[(i * 3) % paymentMethods.length].value),
),
);
await database
.into(database.closedTabs)
.insert(
ClosedTabsCompanion.insert(
id: closedTabId,
originalTabId: uuid.v4(),
customerName: customer,
closedAt: closedAt,
paymentMethod: Value(
paymentMethods[random.nextInt(paymentMethods.length)].value,
),
),
);
for (var j = 0; j < itemCount; j++) {
final product = products[(i + j) % products.length];
final quantity = 1 + ((i * 7 + j * 13) % 6);
for (var j = 0; j < distinctProductCount; j++) {
final product = shuffledProducts[j];
final quantity = 1 + random.nextInt(4);
await database.into(database.closedTabItems).insert(
ClosedTabItemsCompanion.insert(
id: uuid.v4(),
closedTabId: closedTabId,
productId: product.id,
productName: product.name,
quantity: quantity,
unitPriceInCents: product.priceInCents,
),
);
for (var k = 0; k < quantity; k++) {
var purchasedAt = firstPurchaseAt.add(
Duration(minutes: random.nextInt(purchaseWindowMinutes + 1)),
);
if (spanDays > 0 && j == 0 && k == 0) {
purchasedAt = firstPurchaseAt;
} else if (spanDays > 0 &&
j == distinctProductCount - 1 &&
k == quantity - 1) {
purchasedAt = closedAt.subtract(
Duration(minutes: random.nextInt(30)),
);
}
await database
.into(database.closedTabItems)
.insert(
ClosedTabItemsCompanion.insert(
id: uuid.v4(),
closedTabId: closedTabId,
productId: product.id,
productName: product.name,
quantity: 1,
unitPriceInCents: product.priceInCents,
purchasedAt: Value(purchasedAt),
),
);
}
}
}
});
_lastAction = 'Generated $count mock orders across '
'${customerNames.length} customers';
_lastAction =
'Generated $count mock orders across '
'${usedCustomers.length} customers';
} finally {
_isLoading = false;
notifyListeners();
+44 -14
View File
@@ -1,5 +1,6 @@
import 'package:flutter/foundation.dart';
import '../models/bar_tab.dart';
import '../models/closed_tab.dart';
import '../services/bar_tab_service.dart';
@@ -11,6 +12,7 @@ class HistoryViewModel extends ChangeNotifier {
static const int _pageSize = 20;
List<ClosedTab> _closedTabs = [];
List<BarTab> _openTabs = [];
List<String> _customerNames = [];
String? _selectedCustomer;
int _offset = 0;
@@ -22,14 +24,14 @@ class HistoryViewModel extends ChangeNotifier {
String _searchQuery = '';
List<ClosedTab> get closedTabs {
if (_searchQuery.isEmpty) return _closedTabs;
return _closedTabs
.where(
(tab) => tab.customerName.toLowerCase().contains(
_searchQuery.toLowerCase(),
),
)
.where((tab) => _matchesCustomer(tab.customerName))
.toList();
}
List<BarTab> get openTabs {
return _openTabs
.where((tab) => _matchesCustomer(tab.customerName))
.toList();
}
@@ -63,15 +65,19 @@ class HistoryViewModel extends ChangeNotifier {
try {
final results = await Future.wait([
_fetchPage(offset: 0),
barTabService.getClosedTabCount(
customerName: _selectedCustomer,
),
barTabService.getClosedTabCount(customerName: _selectedCustomer),
barTabService.getDistinctCustomerNames(),
barTabService.getOpenTabs(includeRemoved: true),
]);
_closedTabs = results[0] as List<ClosedTab>;
_closedTabs = (results[0] as List<ClosedTab>).toList()
..sort(_compareClosedTabs);
_totalCount = results[1] as int;
_customerNames = results[2] as List<String>;
_openTabs = (results[3] as List<BarTab>).toList()..sort(_compareOpenTabs);
_customerNames = {
...(results[2] as List<String>),
..._openTabs.map((tab) => tab.customerName),
}.toList()..sort();
_offset = _closedTabs.length;
} catch (e) {
debugPrint('HistoryViewModel: load error: $e');
@@ -91,7 +97,7 @@ class HistoryViewModel extends ChangeNotifier {
try {
final more = await _fetchPage(offset: _offset);
_closedTabs = [..._closedTabs, ...more];
_closedTabs = [..._closedTabs, ...more]..sort(_compareClosedTabs);
_offset = _closedTabs.length;
} catch (e) {
debugPrint('HistoryViewModel: loadMore error: $e');
@@ -110,11 +116,35 @@ class HistoryViewModel extends ChangeNotifier {
);
}
int _compareClosedTabs(ClosedTab a, ClosedTab b) {
final byClosedAt = b.closedAt.compareTo(a.closedAt);
if (byClosedAt != 0) return byClosedAt;
return b.id.compareTo(a.id);
}
int _compareOpenTabs(BarTab a, BarTab b) {
final byOpenedAt = b.openedAt.compareTo(a.openedAt);
if (byOpenedAt != 0) return byOpenedAt;
return b.id.compareTo(a.id);
}
void search(String query) {
_searchQuery = query;
notifyListeners();
}
bool _matchesCustomer(String customerName) {
if (_selectedCustomer != null && customerName != _selectedCustomer) {
return false;
}
if (_searchQuery.isEmpty) return true;
return customerName.toLowerCase().contains(_searchQuery.toLowerCase());
}
Future<void> filterByCustomer(String? customerName) async {
_selectedCustomer = customerName;
_searchQuery = '';
@@ -123,4 +153,4 @@ class HistoryViewModel extends ChangeNotifier {
await load();
}
}
}