import 'package:miaoji_zhang/shared/api/api_client.dart'; import 'package:miaoji_zhang/shared/services/current_ledger_store.dart'; import 'package:miaoji_zhang/shared/services/shanghai_time.dart'; class GuestMergeResult { final int ledgerId; final int transactionCount; const GuestMergeResult({ required this.ledgerId, required this.transactionCount, }); } class GuestMergeService { GuestMergeService._(); static final _dio = ApiClient.instance.dio; static Future merge(Map snapshot) async { final ledgerResponse = await _dio.post( '/api/ledgers', data: {'name': '游客数据', 'iconKey': 'wallet'}, ); final ledgerId = (ledgerResponse.data['id'] as num).toInt(); final categoryMap = {}; final available = >[]; for (final type in ['expense', 'income']) { final response = await _dio.get( '/api/categories', queryParameters: {'type': type}, ); available.addAll( (response.data as List).map((item) => item as Map), ); } for (final value in snapshot['categories'] as List? ?? const []) { final category = value as Map; final existing = available.where( (item) => item['type'] == category['type'] && item['name'] == category['name'], ); Map remote; if (existing.isNotEmpty) { remote = existing.first; } else { final response = await _dio.post( '/api/categories', data: { 'name': category['name'], 'iconKey': category['iconKey'], 'colorKey': category['colorKey'], 'type': category['type'], }, ); remote = response.data as Map; available.add(remote); } categoryMap[(category['id'] as num).toInt()] = (remote['id'] as num) .toInt(); } int mapCategory(Map transaction) { final oldId = (transaction['categoryId'] as num).toInt(); final custom = categoryMap[oldId]; if (custom != null) return custom; final exact = available.where( (item) => item['type'] == transaction['type'] && item['name'] == transaction['categoryName'], ); if (exact.isNotEmpty) return (exact.first['id'] as num).toInt(); final fallback = available.firstWhere( (item) => item['type'] == transaction['type'] && item['name'] == '其他', ); return (fallback['id'] as num).toInt(); } var transactionCount = 0; for (final value in snapshot['transactions'] as List? ?? const []) { final transaction = value as Map; await _dio.post( '/api/transactions', data: { 'ledgerId': ledgerId, 'categoryId': mapCategory(transaction), 'type': transaction['type'], 'amount': transaction['amount'], 'note': transaction['note'], 'paymentMethod': transaction['paymentMethod'], 'occurredAt': transaction['occurredAt'], 'source': 'manual', 'sourceText': null, }, ); transactionCount++; } for (final value in snapshot['budgets'] as List? ?? const []) { final budget = value as Map; final period = (budget['period'] as num).toInt(); final recurring = budget['recurring'] == true || period == 0; final current = ShanghaiTime.now; final year = period == 0 ? current.year : period ~/ 100; final month = period == 0 ? current.month : period % 100; final oldCategoryId = (budget['categoryId'] as num?)?.toInt(); await _dio.put( '/api/budgets', queryParameters: {'year': year, 'month': month, 'ledgerId': ledgerId}, data: { 'categoryId': oldCategoryId == null ? null : categoryMap[oldCategoryId] ?? _findMappedDefault(available, snapshot, oldCategoryId), 'amount': budget['amount'], 'recurring': recurring, }, ); } await CurrentLedgerStore.instance.ensureLoaded(force: true); await CurrentLedgerStore.instance.select(ledgerId); return GuestMergeResult( ledgerId: ledgerId, transactionCount: transactionCount, ); } static int? _findMappedDefault( List> available, Map snapshot, int oldCategoryId, ) { final transaction = (snapshot['transactions'] as List? ?? const []) .cast>() .where((item) => item['categoryId'] == oldCategoryId) .firstOrNull; if (transaction == null) return null; final match = available.where( (item) => item['type'] == transaction['type'] && item['name'] == transaction['categoryName'], ); return match.isEmpty ? null : (match.first['id'] as num).toInt(); } }