feat: New Open Tab dialog with search and optional customer email. Drinks are now grouped per order. Payments trigger cha-ching and money rain.
This commit is contained in:
+11
-1
@@ -5,6 +5,7 @@ import 'tab_item.dart';
|
||||
class BarTab {
|
||||
final String id;
|
||||
final String customerName;
|
||||
final String? email;
|
||||
final DateTime openedAt;
|
||||
final int version;
|
||||
final List<TabItem> items;
|
||||
@@ -12,6 +13,7 @@ class BarTab {
|
||||
const BarTab({
|
||||
required this.id,
|
||||
required this.customerName,
|
||||
this.email,
|
||||
required this.openedAt,
|
||||
this.version = 0,
|
||||
required this.items,
|
||||
@@ -31,11 +33,19 @@ class BarTab {
|
||||
other is BarTab &&
|
||||
id == other.id &&
|
||||
customerName == other.customerName &&
|
||||
email == other.email &&
|
||||
openedAt == other.openedAt &&
|
||||
version == other.version &&
|
||||
listEquals(items, other.items);
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
Object.hash(id, customerName, openedAt, version, Object.hashAll(items));
|
||||
Object.hash(
|
||||
id,
|
||||
customerName,
|
||||
email,
|
||||
openedAt,
|
||||
version,
|
||||
Object.hashAll(items),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ class ClosedTabItem {
|
||||
final int unitPriceInCents;
|
||||
final DateTime? purchasedAt;
|
||||
final DateTime? removedAt;
|
||||
final String? orderId;
|
||||
|
||||
ClosedTabItem({
|
||||
required this.id,
|
||||
@@ -17,6 +18,7 @@ class ClosedTabItem {
|
||||
required this.unitPriceInCents,
|
||||
this.purchasedAt,
|
||||
this.removedAt,
|
||||
this.orderId,
|
||||
});
|
||||
|
||||
bool get isRemoved => removedAt != null;
|
||||
@@ -34,7 +36,8 @@ class ClosedTabItem {
|
||||
quantity == other.quantity &&
|
||||
unitPriceInCents == other.unitPriceInCents &&
|
||||
purchasedAt == other.purchasedAt &&
|
||||
removedAt == other.removedAt;
|
||||
removedAt == other.removedAt &&
|
||||
orderId == other.orderId;
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
@@ -46,5 +49,6 @@ class ClosedTabItem {
|
||||
unitPriceInCents,
|
||||
purchasedAt,
|
||||
removedAt,
|
||||
orderId,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,15 @@ class ServerUnit {
|
||||
final DateTime addedAt;
|
||||
final DateTime? removedAt;
|
||||
|
||||
const ServerUnit({required this.id, required this.addedAt, this.removedAt});
|
||||
/// The order it was rung up in; null for drinks from older app versions.
|
||||
final String? orderId;
|
||||
|
||||
const ServerUnit({
|
||||
required this.id,
|
||||
required this.addedAt,
|
||||
this.removedAt,
|
||||
this.orderId,
|
||||
});
|
||||
|
||||
factory ServerUnit.fromJson(Map<String, dynamic> json) {
|
||||
return ServerUnit(
|
||||
@@ -18,6 +26,7 @@ class ServerUnit {
|
||||
final String value => DateTime.parse(value).toLocal(),
|
||||
_ => null,
|
||||
},
|
||||
orderId: json['orderId'] as String?,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -58,6 +67,7 @@ class ServerTabLine {
|
||||
class ServerTab {
|
||||
final String id;
|
||||
final String customerName;
|
||||
final String? email;
|
||||
final DateTime openedAt;
|
||||
final int version;
|
||||
final List<ServerTabLine> lines;
|
||||
@@ -65,6 +75,7 @@ class ServerTab {
|
||||
const ServerTab({
|
||||
required this.id,
|
||||
required this.customerName,
|
||||
this.email,
|
||||
required this.openedAt,
|
||||
required this.version,
|
||||
this.lines = const [],
|
||||
@@ -74,6 +85,7 @@ class ServerTab {
|
||||
return ServerTab(
|
||||
id: json['id'] as String,
|
||||
customerName: json['customerName'] as String,
|
||||
email: json['email'] as String?,
|
||||
openedAt: DateTime.parse(json['openedAt'] as String).toLocal(),
|
||||
version: json['version'] as int? ?? 0,
|
||||
lines: [
|
||||
|
||||
@@ -47,6 +47,10 @@ class TabEvent {
|
||||
const TabEvent({required this.id, required this.payload});
|
||||
}
|
||||
|
||||
/// The order a removal takes drinks off: `orderId`, or (when null) the
|
||||
/// drinks rung up before orders were tracked.
|
||||
typedef OrderRef = ({String? orderId});
|
||||
|
||||
/// One settled line in a `settled` event.
|
||||
typedef SettledLine = ({
|
||||
String productId,
|
||||
@@ -66,12 +70,16 @@ class TabEventFactory {
|
||||
required String tabId,
|
||||
required String customerName,
|
||||
required DateTime openedAt,
|
||||
String? email,
|
||||
}) {
|
||||
return _event(
|
||||
TabEventType.tabOpened,
|
||||
tabId: tabId,
|
||||
customerName: customerName,
|
||||
fields: {'openedAt': openedAt.toUtc().toIso8601String()},
|
||||
fields: {
|
||||
'openedAt': openedAt.toUtc().toIso8601String(),
|
||||
'email': ?email,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -96,7 +104,8 @@ class TabEventFactory {
|
||||
}
|
||||
|
||||
/// [quantity] is how many pieces were added; always positive. [lineId] is
|
||||
/// the local line, so the server can keep its id.
|
||||
/// the local line, so the server can keep its id. [orderId] is the order
|
||||
/// they are part of.
|
||||
TabEvent lineAdded({
|
||||
required String tabId,
|
||||
required String customerName,
|
||||
@@ -105,6 +114,7 @@ class TabEventFactory {
|
||||
required String productName,
|
||||
required int unitPriceInCents,
|
||||
required int quantity,
|
||||
required String? orderId,
|
||||
}) {
|
||||
return _lineEvent(
|
||||
TabEventType.lineAdded,
|
||||
@@ -115,10 +125,13 @@ class TabEventFactory {
|
||||
productName: productName,
|
||||
unitPriceInCents: unitPriceInCents,
|
||||
quantity: quantity,
|
||||
order: (orderId: orderId),
|
||||
);
|
||||
}
|
||||
|
||||
/// [quantity] is how many pieces were removed; always positive.
|
||||
/// [quantity] is how many pieces were removed; always positive. With
|
||||
/// [order] they are taken off that order only; without, the latest drinks
|
||||
/// of the line (as before orders).
|
||||
TabEvent lineRemoved({
|
||||
required String tabId,
|
||||
required String customerName,
|
||||
@@ -127,6 +140,7 @@ class TabEventFactory {
|
||||
required String productName,
|
||||
required int unitPriceInCents,
|
||||
required int quantity,
|
||||
OrderRef? order,
|
||||
}) {
|
||||
return _lineEvent(
|
||||
TabEventType.lineRemoved,
|
||||
@@ -137,6 +151,7 @@ class TabEventFactory {
|
||||
productName: productName,
|
||||
unitPriceInCents: unitPriceInCents,
|
||||
quantity: quantity,
|
||||
order: order,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -246,6 +261,7 @@ class TabEventFactory {
|
||||
required String productName,
|
||||
required int unitPriceInCents,
|
||||
required int quantity,
|
||||
required OrderRef? order,
|
||||
}) {
|
||||
if (quantity < 1) {
|
||||
throw ArgumentError.value(quantity, 'quantity', 'Must be at least 1');
|
||||
@@ -263,6 +279,9 @@ class TabEventFactory {
|
||||
'productName': productName,
|
||||
'unitPriceInCents': unitPriceInCents,
|
||||
'quantity': quantity,
|
||||
// Also when null: a removal then only takes drinks without an order,
|
||||
// where a removal without `orderId` takes the latest of the line
|
||||
if (order != null) 'orderId': order.orderId,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,11 +4,15 @@ class TabItemPurchase {
|
||||
final DateTime purchasedAt;
|
||||
final DateTime? removedAt;
|
||||
|
||||
/// The order it was rung up in; null for drinks from before orders.
|
||||
final String? orderId;
|
||||
|
||||
const TabItemPurchase({
|
||||
required this.id,
|
||||
required this.tabItemId,
|
||||
required this.purchasedAt,
|
||||
this.removedAt,
|
||||
this.orderId,
|
||||
});
|
||||
|
||||
bool get isRemoved => removedAt != null;
|
||||
@@ -20,8 +24,10 @@ class TabItemPurchase {
|
||||
id == other.id &&
|
||||
tabItemId == other.tabItemId &&
|
||||
purchasedAt == other.purchasedAt &&
|
||||
removedAt == other.removedAt;
|
||||
removedAt == other.removedAt &&
|
||||
orderId == other.orderId;
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(id, tabItemId, purchasedAt, removedAt);
|
||||
int get hashCode =>
|
||||
Object.hash(id, tabItemId, purchasedAt, removedAt, orderId);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user