156 lines
4.5 KiB
Dart
156 lines
4.5 KiB
Dart
import 'dart:async';
|
|
import 'dart:convert';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
import 'package:connectivity_plus/connectivity_plus.dart';
|
|
import 'package:supabase_flutter/supabase_flutter.dart';
|
|
|
|
class OfflineQueueService {
|
|
static final OfflineQueueService _instance = OfflineQueueService._internal();
|
|
factory OfflineQueueService() => _instance;
|
|
OfflineQueueService._internal();
|
|
|
|
final SupabaseClient _supabase = Supabase.instance.client;
|
|
final Connectivity _connectivity = Connectivity();
|
|
List<Map<String, dynamic>> _queue = [];
|
|
bool _isOnline = true;
|
|
bool _syncing = false;
|
|
|
|
static const Duration _timeout = Duration(seconds: 30);
|
|
|
|
Future<T> _withTimeout<T>(Future<T> future, [String? operation]) async {
|
|
try {
|
|
return await future.timeout(
|
|
_timeout,
|
|
onTimeout: () => throw TimeoutException('${operation ?? 'Operasi'} timeout setelah $_timeout'),
|
|
);
|
|
} on TimeoutException catch (e) {
|
|
print('OfflineQueue timeout: $e');
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
List<Map<String, dynamic>> get queue => List.unmodifiable(_queue);
|
|
bool get isOnline => _isOnline;
|
|
bool get hasPendingItems => _queue.isNotEmpty;
|
|
|
|
Future<void> init() async {
|
|
await _loadQueue();
|
|
_connectivity.onConnectivityChanged.listen(_updateConnectionStatus);
|
|
await _checkConnection();
|
|
if (_isOnline) await _processQueue();
|
|
}
|
|
|
|
Future<void> _loadQueue() async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
final queueJson = prefs.getString('offline_queue');
|
|
if (queueJson != null) {
|
|
final List<dynamic> decoded = json.decode(queueJson);
|
|
_queue = decoded.cast<Map<String, dynamic>>();
|
|
}
|
|
}
|
|
|
|
Future<void> _saveQueue() async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
await prefs.setString('offline_queue', json.encode(_queue));
|
|
}
|
|
|
|
Future<void> _checkConnection() async {
|
|
final result = await _connectivity.checkConnectivity();
|
|
_updateConnectionStatus(result);
|
|
}
|
|
|
|
void _updateConnectionStatus(List<ConnectivityResult> result) {
|
|
_isOnline = result.any((r) => r != ConnectivityResult.none);
|
|
if (_isOnline && _queue.isNotEmpty) {
|
|
_processQueue();
|
|
}
|
|
}
|
|
|
|
Future<void> addToQueue(Map<String, dynamic> transaction) async {
|
|
_queue.add(transaction);
|
|
await _saveQueue();
|
|
if (_isOnline) {
|
|
await _processQueue();
|
|
}
|
|
}
|
|
|
|
Future<void> _processQueue() async {
|
|
if (_syncing || !_isOnline || _queue.isEmpty) return;
|
|
_syncing = true;
|
|
|
|
while (_queue.isNotEmpty && _isOnline) {
|
|
final item = _queue.first;
|
|
try {
|
|
await _processTransaction(item);
|
|
_queue.removeAt(0);
|
|
await _saveQueue();
|
|
} catch (e) {
|
|
_syncing = false;
|
|
return;
|
|
}
|
|
}
|
|
_syncing = false;
|
|
}
|
|
|
|
Future<void> _processTransaction(Map<String, dynamic> transaction) async {
|
|
final sale = transaction['sale'] as Map<String, dynamic>;
|
|
final items = (transaction['items'] as List?) ?? [];
|
|
|
|
final saleId = sale['id'] as String;
|
|
final existing = await _withTimeout(
|
|
_supabase
|
|
.from('sales')
|
|
.select('id, total_cost')
|
|
.eq('id', saleId)
|
|
.maybeSingle(),
|
|
'Check existing sale',
|
|
);
|
|
|
|
if (existing == null) {
|
|
await _withTimeout(
|
|
_supabase.from('sales').insert(sale),
|
|
'Insert queued sale',
|
|
);
|
|
} else {
|
|
final existingTotalCost = (existing['total_cost'] as num?)?.toDouble() ?? 0;
|
|
if (existingTotalCost <= 0 && sale['total_cost'] != null) {
|
|
await _withTimeout(
|
|
_supabase
|
|
.from('sales')
|
|
.update({'total_cost': sale['total_cost']})
|
|
.eq('id', saleId),
|
|
'Update sale total_cost',
|
|
);
|
|
}
|
|
}
|
|
|
|
for (final it in items) {
|
|
final productId = it['product_id'] as String;
|
|
final qty = (it['qty'] as num).toInt();
|
|
final product = await _withTimeout(
|
|
_supabase
|
|
.from('products')
|
|
.select()
|
|
.eq('id', productId)
|
|
.maybeSingle(),
|
|
'Load product for stock update',
|
|
);
|
|
if (product != null) {
|
|
final currentStock = (product['stock'] as num).toInt();
|
|
final newStock = (currentStock - qty).clamp(0, 1 << 62);
|
|
await _withTimeout(
|
|
_supabase
|
|
.from('products')
|
|
.update({'stock': newStock})
|
|
.eq('id', productId),
|
|
'Update product stock',
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
Future<void> clearQueue() async {
|
|
_queue.clear();
|
|
await _saveQueue();
|
|
}
|
|
} |