feat: add alot
This commit is contained in:
@@ -1,157 +1,89 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||
|
||||
import '../database/app_database.dart';
|
||||
import '../models/closed_tab.dart';
|
||||
import '../models/closed_tab_item.dart';
|
||||
import '../utils/app_config.dart';
|
||||
import 'bar_tab_service.dart';
|
||||
|
||||
abstract class ExportService {
|
||||
Future<void> uploadToServer();
|
||||
}
|
||||
|
||||
/// Uploads the closed-tab history to the backup server. Best effort: a
|
||||
/// failed backup is logged but never interrupts the bar.
|
||||
class DefaultExportService implements ExportService {
|
||||
final AppDatabase database;
|
||||
final BarTabService barTabService;
|
||||
final http.Client _client;
|
||||
|
||||
DefaultExportService({required this.database});
|
||||
DefaultExportService({required this.barTabService, http.Client? client})
|
||||
: _client = client ?? http.Client();
|
||||
|
||||
@override
|
||||
Future<void> uploadToServer() async {
|
||||
try {
|
||||
final closedTabs = await _getAllClosedTabs();
|
||||
final exportData = _buildExportPayload(closedTabs);
|
||||
final closedTabs = await barTabService.getClosedTabs();
|
||||
final appVersion = (await PackageInfo.fromPlatform()).version;
|
||||
|
||||
final response = await http.post(
|
||||
final response = await _client.post(
|
||||
Uri.parse(kExportServerUrl),
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: jsonEncode(exportData),
|
||||
body: jsonEncode(_buildPayload(closedTabs, appVersion: appVersion)),
|
||||
);
|
||||
|
||||
if (response.statusCode != 200 && response.statusCode != 201) {
|
||||
// Should prob log this one tho
|
||||
unawaited(
|
||||
Sentry.captureMessage(
|
||||
'Backup upload failed with HTTP ${response.statusCode}',
|
||||
level: SentryLevel.warning,
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (_) {
|
||||
// if this fails who cares
|
||||
} on Exception catch (e) {
|
||||
// Mostly offline tablets; not worth an error report.
|
||||
debugPrint('ExportService: backup upload failed: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<_ClosedTabExport>> _getAllClosedTabs() async {
|
||||
final tabQuery = database.select(database.closedTabs)
|
||||
..orderBy([(tab) => OrderingTerm.desc(tab.closedAt)]);
|
||||
|
||||
final tabRows = await tabQuery.get();
|
||||
|
||||
final result = <_ClosedTabExport>[];
|
||||
|
||||
for (final tabRow in tabRows) {
|
||||
final itemsQuery = database.select(database.closedTabItems)
|
||||
..where((item) => item.closedTabId.equals(tabRow.id));
|
||||
|
||||
final itemRows = await itemsQuery.get();
|
||||
|
||||
result.add(
|
||||
_ClosedTabExport(
|
||||
id: tabRow.id,
|
||||
originalTabId: tabRow.originalTabId,
|
||||
customerName: tabRow.customerName,
|
||||
closedAt: tabRow.closedAt.toIso8601String(),
|
||||
paymentMethod: tabRow.paymentMethod,
|
||||
items: itemRows
|
||||
.map(
|
||||
(row) => _ClosedTabItemExport(
|
||||
productId: row.productId,
|
||||
productName: row.productName,
|
||||
quantity: row.quantity,
|
||||
unitPriceInCents: row.unitPriceInCents,
|
||||
purchasedAt: row.purchasedAt?.toIso8601String(),
|
||||
removedAt: row.removedAt?.toIso8601String(),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Map<String, dynamic> _buildExportPayload(List<_ClosedTabExport> tabs) {
|
||||
Map<String, dynamic> _buildPayload(
|
||||
List<ClosedTab> tabs, {
|
||||
required String appVersion,
|
||||
}) {
|
||||
return {
|
||||
'exportedAt': DateTime.now().toIso8601String(),
|
||||
'appVersion': '1.0.7',
|
||||
'closedTabs': tabs
|
||||
.map(
|
||||
(tab) => {
|
||||
'id': tab.id,
|
||||
'originalTabId': tab.originalTabId,
|
||||
'customerName': tab.customerName,
|
||||
'closedAt': tab.closedAt,
|
||||
'paymentMethod': tab.paymentMethod,
|
||||
'items': tab.items
|
||||
.map(
|
||||
(item) => {
|
||||
'productId': item.productId,
|
||||
'productName': item.productName,
|
||||
'quantity': item.quantity,
|
||||
'unitPriceInCents': item.unitPriceInCents,
|
||||
'purchasedAt': item.purchasedAt,
|
||||
'removedAt': item.removedAt,
|
||||
'isRemoved': item.isRemoved,
|
||||
'lineTotalInCents': item.lineTotalInCents,
|
||||
},
|
||||
)
|
||||
.toList(),
|
||||
'itemCount': tab.items.fold(
|
||||
0,
|
||||
(sum, item) => sum + (item.isRemoved ? 0 : item.quantity),
|
||||
),
|
||||
'totalInCents': tab.items.fold(
|
||||
0,
|
||||
(sum, item) => sum + item.lineTotalInCents,
|
||||
),
|
||||
},
|
||||
)
|
||||
.toList(),
|
||||
'appVersion': appVersion,
|
||||
'closedTabs': tabs.map(_tabToJson).toList(),
|
||||
};
|
||||
}
|
||||
|
||||
Map<String, dynamic> _tabToJson(ClosedTab tab) {
|
||||
return {
|
||||
'id': tab.id,
|
||||
'originalTabId': tab.originalTabId,
|
||||
'customerName': tab.customerName,
|
||||
'closedAt': tab.closedAt.toIso8601String(),
|
||||
'paymentMethod': tab.paymentMethod.value,
|
||||
'items': tab.items.map(_itemToJson).toList(),
|
||||
'itemCount': tab.itemCount,
|
||||
'totalInCents': tab.totalInCents,
|
||||
};
|
||||
}
|
||||
|
||||
Map<String, dynamic> _itemToJson(ClosedTabItem item) {
|
||||
return {
|
||||
'productId': item.productId,
|
||||
'productName': item.productName,
|
||||
'quantity': item.quantity,
|
||||
'unitPriceInCents': item.unitPriceInCents,
|
||||
'purchasedAt': item.purchasedAt?.toIso8601String(),
|
||||
'removedAt': item.removedAt?.toIso8601String(),
|
||||
'isRemoved': item.isRemoved,
|
||||
'lineTotalInCents': item.lineTotalInCents,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class _ClosedTabItemExport {
|
||||
final String productId;
|
||||
final String productName;
|
||||
final int quantity;
|
||||
final int unitPriceInCents;
|
||||
final String? purchasedAt;
|
||||
final String? removedAt;
|
||||
|
||||
_ClosedTabItemExport({
|
||||
required this.productId,
|
||||
required this.productName,
|
||||
required this.quantity,
|
||||
required this.unitPriceInCents,
|
||||
required this.purchasedAt,
|
||||
required this.removedAt,
|
||||
});
|
||||
|
||||
bool get isRemoved => removedAt != null;
|
||||
|
||||
int get lineTotalInCents => isRemoved ? 0 : quantity * unitPriceInCents;
|
||||
}
|
||||
|
||||
class _ClosedTabExport {
|
||||
final String id;
|
||||
final String originalTabId;
|
||||
final String customerName;
|
||||
final String closedAt;
|
||||
final String paymentMethod;
|
||||
final List<_ClosedTabItemExport> items;
|
||||
|
||||
_ClosedTabExport({
|
||||
required this.id,
|
||||
required this.originalTabId,
|
||||
required this.customerName,
|
||||
required this.closedAt,
|
||||
required this.paymentMethod,
|
||||
required this.items,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user