Files
kooltab/lib/models/remote_command.dart
T
2026-10-19 23:31:54 +02:00

223 lines
5.3 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<String, dynamic> 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<String, dynamic> json) {
return RejectedEvent(
id: json['id'] as String?,
reason: json['reason'] as String? ?? '',
);
}
}
class SyncResponse {
final List<RemoteCommand> commands;
final String? cursor;
final List<String> plannedTabNames;
final List<String> acknowledgedEventIds;
final List<RejectedEvent> rejectedEvents;
const SyncResponse({
required this.commands,
this.cursor,
this.plannedTabNames = const [],
this.acknowledgedEventIds = const [],
this.rejectedEvents = const [],
});
factory SyncResponse.fromJson(Map<String, dynamic> json) {
return SyncResponse(
commands: [
for (final command in json['commands'] as List<dynamic>? ?? [])
RemoteCommand.fromJson(command as Map<String, dynamic>),
],
cursor: json['cursor'] as String?,
plannedTabNames: [
for (final name in json['plannedTabNames'] as List<dynamic>? ?? [])
name as String,
],
acknowledgedEventIds: [
for (final id in json['acknowledgedEventIds'] as List<dynamic>? ?? [])
id as String,
],
rejectedEvents: [
for (final event in json['rejectedEvents'] as List<dynamic>? ?? [])
RejectedEvent.fromJson(event as Map<String, dynamic>),
],
);
}
}
/// 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<String, dynamic> 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<SnapshotTabItem> items;
const SnapshotTab({
required this.id,
required this.customerName,
required this.version,
required this.totalInCents,
required this.itemCount,
this.items = const [],
});
Map<String, dynamic> 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<SnapshotTab> openTabs;
/// Outbox tab events, oldest first, as their stored JSON objects.
final List<Map<String, dynamic>> events;
const SyncRequest({
required this.deviceId,
this.cursor,
required this.openTabs,
this.events = const [],
});
Map<String, dynamic> 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<String, dynamic> 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,
});
}