feat: make server own products

This commit is contained in:
2026-10-21 12:10:42 +02:00
parent 93acfe15a9
commit b15d94a805
21 changed files with 1717 additions and 106 deletions
+51
View File
@@ -157,6 +157,40 @@ class ServerStock {
}
}
/// A product as the website manages it.
class ServerProduct {
final String id;
final String name;
final String? category;
final int priceInCents;
/// Null: no low stock warning.
final int? lowStockThreshold;
/// Deleted on the website: hidden, kept for open tabs and the history.
final bool archived;
const ServerProduct({
required this.id,
required this.name,
this.category,
required this.priceInCents,
this.lowStockThreshold,
this.archived = false,
});
factory ServerProduct.fromJson(Map<String, dynamic> json) {
return ServerProduct(
id: json['id'] as String,
name: json['name'] as String,
category: json['category'] as String?,
priceInCents: json['priceInCents'] as int,
lowStockThreshold: json['lowStockThreshold'] as int?,
archived: json['archived'] as bool? ?? false,
);
}
}
class SyncResponse {
final List<RemoteCommand> commands;
final String? cursor;
@@ -183,6 +217,10 @@ class SyncResponse {
/// sales and applies [stockAdjustments] itself.
final List<ServerStock>? stock;
/// Every product of the venue, managed on the website. Null from a server
/// that doesn't manage the catalog; the tablet then manages its own.
final List<ServerProduct>? catalog;
const SyncResponse({
required this.commands,
this.cursor,
@@ -196,6 +234,7 @@ class SyncResponse {
this.settlements = const [],
this.settlementsCursor,
this.stock,
this.catalog,
});
factory SyncResponse.fromJson(Map<String, dynamic> json) {
@@ -251,6 +290,13 @@ class SyncResponse {
],
_ => null,
},
catalog: switch (json['catalog']) {
final List<dynamic> catalog => [
for (final product in catalog)
ServerProduct.fromJson(product as Map<String, dynamic>),
],
_ => null,
},
);
}
@@ -391,10 +437,15 @@ class RemoteSyncEvent {
final String? requestedBy;
final String? message;
/// A failed sync that only lacked a connection: the bar keeps working and
/// everything is sent once it is back.
final bool offline;
const RemoteSyncEvent({
required this.type,
this.tabCustomerName,
this.requestedBy,
this.message,
this.offline = false,
});
}
+3 -1
View File
@@ -207,7 +207,9 @@ class TabEventFactory {
customerName: customerName,
fields: {
'lineId': lineId,
'productId': productId,
// Lines taken over from a tablet's own tabs have no product: the
// server then matches them by name and price, and refuses an empty id
if (productId.isNotEmpty) 'productId': productId,
'productName': productName,
'unitPriceInCents': unitPriceInCents,
'quantity': quantity,