feat: add historical data

This commit is contained in:
2026-10-19 23:31:54 +02:00
parent b0074aeb3f
commit 5754ae742d
10 changed files with 1211 additions and 4 deletions
+158
View File
@@ -0,0 +1,158 @@
import 'package:uuid/uuid.dart';
import 'payment_method.dart';
/// Types of [TabEvent] the server understands.
enum TabEventType {
lineAdded('line_added'),
lineRemoved('line_removed'),
settled('settled');
final String value;
const TabEventType(this.value);
}
/// Something that happened on a tab, reported to the server for sales
/// statistics. [payload] is the JSON object that is stored in the outbox and
/// sent as is.
class TabEvent {
final String id;
final Map<String, dynamic> payload;
const TabEvent({required this.id, required this.payload});
}
/// One settled line in a `settled` event.
typedef SettledLine = ({
String productName,
int quantity,
int unitPriceInCents,
});
/// Builds the events, so their JSON shape lives in one place.
class TabEventFactory {
final _uuid = const Uuid();
final DateTime Function() _now;
TabEventFactory({DateTime Function()? now}) : _now = now ?? DateTime.now;
/// [quantity] is how many pieces were added; always positive.
TabEvent lineAdded({
required String tabId,
required String customerName,
required String productName,
required int unitPriceInCents,
required int quantity,
}) {
return _lineEvent(
TabEventType.lineAdded,
tabId: tabId,
customerName: customerName,
productName: productName,
unitPriceInCents: unitPriceInCents,
quantity: quantity,
);
}
/// [quantity] is how many pieces were removed; always positive.
TabEvent lineRemoved({
required String tabId,
required String customerName,
required String productName,
required int unitPriceInCents,
required int quantity,
}) {
return _lineEvent(
TabEventType.lineRemoved,
tabId: tabId,
customerName: customerName,
productName: productName,
unitPriceInCents: unitPriceInCents,
quantity: quantity,
);
}
TabEvent settled({
required String tabId,
required String customerName,
required int amountInCents,
required PaymentMethod paymentMethod,
required List<SettledLine> items,
String? commandId,
}) {
return _event(
TabEventType.settled,
tabId: tabId,
customerName: customerName,
fields: {
'amountInCents': amountInCents,
'paymentMethod': paymentMethodValue(paymentMethod),
'commandId': ?commandId,
'items': [
for (final item in items)
{
'productName': item.productName,
'quantity': item.quantity,
'unitPriceInCents': item.unitPriceInCents,
},
],
},
);
}
/// The server's payment method names: `cash`, `payconiq`,
/// `payment_pending` or `other`.
static String paymentMethodValue(PaymentMethod method) {
return switch (method) {
PaymentMethod.cash => 'cash',
PaymentMethod.payconiq => 'payconiq',
PaymentMethod.paymentPending => 'payment_pending',
};
}
TabEvent _lineEvent(
TabEventType type, {
required String tabId,
required String customerName,
required String productName,
required int unitPriceInCents,
required int quantity,
}) {
if (quantity < 1) {
throw ArgumentError.value(quantity, 'quantity', 'Must be at least 1');
}
return _event(
type,
tabId: tabId,
customerName: customerName,
fields: {
'productName': productName,
'unitPriceInCents': unitPriceInCents,
'quantity': quantity,
},
);
}
TabEvent _event(
TabEventType type, {
required String tabId,
required String customerName,
required Map<String, dynamic> fields,
}) {
final id = _uuid.v4();
return TabEvent(
id: id,
payload: {
'id': id,
'type': type.value,
'tabId': tabId,
'customerName': customerName,
'occurredAt': _now().toUtc().toIso8601String(),
...fields,
},
);
}
}