import '../utils/app_config.dart'; enum CommandType { closeTab('close_tab'); final String value; const CommandType(this.value); static CommandType? fromValue(String value) { for (final type in CommandType.values) { if (type.value == value) return type; } return null; } } enum CommandResult { completed, conflict, failed, expired } class RemoteCommand { final String id; final String rawType; final String tabId; final int expectedVersion; final String paymentMethod; final String requestedBy; final DateTime createdAt; const RemoteCommand({ required this.id, required this.rawType, required this.tabId, required this.expectedVersion, required this.paymentMethod, required this.requestedBy, required this.createdAt, }); factory RemoteCommand.fromJson(Map json) { return RemoteCommand( id: json['id'] as String, rawType: json['type'] as String, tabId: json['tabId'] as String, expectedVersion: json['expectedVersion'] as int, paymentMethod: json['paymentMethod'] as String? ?? 'cash', requestedBy: json['requestedBy'] as String? ?? '', createdAt: DateTime.parse(json['createdAt'] as String), ); } /// Null when the server sends a command this app version doesn't know. CommandType? get type => CommandType.fromValue(rawType); bool get isExpired { final cutoff = DateTime.now().subtract( const Duration(hours: kCommandExpiryHours), ); return createdAt.isBefore(cutoff); } } /// A tab event the server refused to store. Re-sending won't help. class RejectedEvent { final String? id; final String reason; const RejectedEvent({this.id, required this.reason}); factory RejectedEvent.fromJson(Map json) { return RejectedEvent( id: json['id'] as String?, reason: json['reason'] as String? ?? '', ); } } class SyncResponse { final List commands; final String? cursor; final List plannedTabNames; final List acknowledgedEventIds; final List rejectedEvents; const SyncResponse({ required this.commands, this.cursor, this.plannedTabNames = const [], this.acknowledgedEventIds = const [], this.rejectedEvents = const [], }); factory SyncResponse.fromJson(Map json) { return SyncResponse( commands: [ for (final command in json['commands'] as List? ?? []) RemoteCommand.fromJson(command as Map), ], cursor: json['cursor'] as String?, plannedTabNames: [ for (final name in json['plannedTabNames'] as List? ?? []) name as String, ], acknowledgedEventIds: [ for (final id in json['acknowledgedEventIds'] as List? ?? []) id as String, ], rejectedEvents: [ for (final event in json['rejectedEvents'] as List? ?? []) RejectedEvent.fromJson(event as Map), ], ); } } /// One line on a tab as shown in the dashboard, e.g. "10× Cola". class SnapshotTabItem { final String productName; final int quantity; final int unitPriceInCents; const SnapshotTabItem({ required this.productName, required this.quantity, required this.unitPriceInCents, }); Map toJson() => { 'productName': productName, 'quantity': quantity, 'unitPriceInCents': unitPriceInCents, }; } class SnapshotTab { final String id; final String customerName; final int version; final int totalInCents; final int itemCount; final List items; const SnapshotTab({ required this.id, required this.customerName, required this.version, required this.totalInCents, required this.itemCount, this.items = const [], }); Map toJson() => { 'id': id, 'customerName': customerName, 'version': version, 'totalInCents': totalInCents, 'itemCount': itemCount, 'items': items.map((item) => item.toJson()).toList(), }; } class SyncRequest { final String deviceId; final String? cursor; final List openTabs; /// Outbox tab events, oldest first, as their stored JSON objects. final List> events; const SyncRequest({ required this.deviceId, this.cursor, required this.openTabs, this.events = const [], }); Map toJson() => { 'deviceId': deviceId, if (cursor != null) 'cursor': cursor, 'openTabs': openTabs.map((tab) => tab.toJson()).toList(), 'events': events, }; } class CommandAck { final String commandId; final CommandResult status; final String? result; const CommandAck({ required this.commandId, required this.status, this.result, }); Map toJson() => { 'commandId': commandId, 'status': status.name, if (result != null) 'result': result, }; } enum RemoteSyncEventType { tabClosedRemotely, syncCompleted, syncFailed, conflict, unauthorized, } class RemoteSyncEvent { final RemoteSyncEventType type; final String? tabCustomerName; final String? requestedBy; final String? message; const RemoteSyncEvent({ required this.type, this.tabCustomerName, this.requestedBy, this.message, }); }