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 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 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 fields, }) { final id = _uuid.v4(); return TabEvent( id: id, payload: { 'id': id, 'type': type.value, 'tabId': tabId, 'customerName': customerName, 'occurredAt': _now().toUtc().toIso8601String(), ...fields, }, ); } }