102 lines
3.0 KiB
Dart
102 lines
3.0 KiB
Dart
import 'dart:async';
|
|
import 'dart:convert';
|
|
|
|
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 '../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 BarTabService barTabService;
|
|
final Future<String?> Function()? authToken;
|
|
final http.Client _client;
|
|
|
|
DefaultExportService({
|
|
required this.barTabService,
|
|
this.authToken,
|
|
http.Client? client,
|
|
}) : _client = client ?? http.Client();
|
|
|
|
static const _timeout = Duration(seconds: 30);
|
|
|
|
@override
|
|
Future<void> uploadToServer() async {
|
|
try {
|
|
final closedTabs = await barTabService.getClosedTabs();
|
|
final appVersion = (await PackageInfo.fromPlatform()).version;
|
|
final token = await authToken?.call();
|
|
|
|
final response = await _client
|
|
.post(
|
|
Uri.parse(kExportServerUrl),
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
if (token != null) 'Authorization': 'Bearer $token',
|
|
},
|
|
body: jsonEncode(_buildPayload(closedTabs, appVersion: appVersion)),
|
|
)
|
|
.timeout(_timeout);
|
|
|
|
if (response.statusCode != 200 && response.statusCode != 201) {
|
|
unawaited(
|
|
Sentry.captureMessage(
|
|
'Backup upload failed with HTTP ${response.statusCode}',
|
|
level: SentryLevel.warning,
|
|
),
|
|
);
|
|
}
|
|
} on Exception catch (e) {
|
|
// Mostly offline tablets; not worth an error report.
|
|
debugPrint('ExportService: backup upload failed: $e');
|
|
}
|
|
}
|
|
|
|
Map<String, dynamic> _buildPayload(
|
|
List<ClosedTab> tabs, {
|
|
required String appVersion,
|
|
}) {
|
|
return {
|
|
'exportedAt': DateTime.now().toIso8601String(),
|
|
'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,
|
|
};
|
|
}
|
|
}
|