fix: improve transfer recognition and local data safety
This commit is contained in:
@@ -1,3 +1,6 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:crypto/crypto.dart';
|
||||
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';
|
||||
@@ -17,12 +20,23 @@ class GuestMergeService {
|
||||
|
||||
static final _dio = ApiClient.instance.dio;
|
||||
|
||||
static Future<GuestMergeResult> merge(Map<String, dynamic> snapshot) async {
|
||||
final ledgerResponse = await _dio.post(
|
||||
'/api/ledgers',
|
||||
data: {'name': '游客数据', 'iconKey': 'wallet'},
|
||||
);
|
||||
final ledgerId = (ledgerResponse.data['id'] as num).toInt();
|
||||
static Future<GuestMergeResult> merge(
|
||||
Map<String, dynamic> snapshot, {
|
||||
String ledgerName = '游客数据',
|
||||
}) async {
|
||||
final ledgersResponse = await _dio.get('/api/ledgers');
|
||||
final existingLedger = (ledgersResponse.data as List<dynamic>)
|
||||
.cast<Map<String, dynamic>>()
|
||||
.where((item) => item['name'] == ledgerName)
|
||||
.firstOrNull;
|
||||
final ledgerId = existingLedger == null
|
||||
? ((await _dio.post(
|
||||
'/api/ledgers',
|
||||
data: {'name': ledgerName, 'iconKey': 'wallet'},
|
||||
)).data['id']
|
||||
as num)
|
||||
.toInt()
|
||||
: (existingLedger['id'] as num).toInt();
|
||||
final categoryMap = <int, int>{};
|
||||
final available = <Map<String, dynamic>>[];
|
||||
for (final type in ['expense', 'income']) {
|
||||
@@ -37,6 +51,7 @@ class GuestMergeService {
|
||||
|
||||
for (final value in snapshot['categories'] as List<dynamic>? ?? const []) {
|
||||
final category = value as Map<String, dynamic>;
|
||||
if (category['isDeleted'] == true) continue;
|
||||
final existing = available.where(
|
||||
(item) =>
|
||||
item['type'] == category['type'] &&
|
||||
@@ -45,6 +60,8 @@ class GuestMergeService {
|
||||
Map<String, dynamic> remote;
|
||||
if (existing.isNotEmpty) {
|
||||
remote = existing.first;
|
||||
} else if (category['isCustom'] == false) {
|
||||
continue;
|
||||
} else {
|
||||
final response = await _dio.post(
|
||||
'/api/categories',
|
||||
@@ -66,14 +83,15 @@ class GuestMergeService {
|
||||
final oldId = (transaction['categoryId'] as num).toInt();
|
||||
final custom = categoryMap[oldId];
|
||||
if (custom != null) return custom;
|
||||
final categoryType = _transactionCategoryType(transaction);
|
||||
final exact = available.where(
|
||||
(item) =>
|
||||
item['type'] == transaction['type'] &&
|
||||
item['type'] == categoryType &&
|
||||
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'] == '其他',
|
||||
(item) => item['type'] == categoryType && item['name'] == '其他',
|
||||
);
|
||||
return (fallback['id'] as num).toInt();
|
||||
}
|
||||
@@ -82,6 +100,7 @@ class GuestMergeService {
|
||||
for (final value
|
||||
in snapshot['transactions'] as List<dynamic>? ?? const []) {
|
||||
final transaction = value as Map<String, dynamic>;
|
||||
if (transaction['isDeleted'] == true) continue;
|
||||
await _dio.post(
|
||||
'/api/transactions',
|
||||
data: {
|
||||
@@ -91,9 +110,12 @@ class GuestMergeService {
|
||||
'amount': transaction['amount'],
|
||||
'note': transaction['note'],
|
||||
'paymentMethod': transaction['paymentMethod'],
|
||||
'transferDirection': transaction['transferDirection'],
|
||||
'counterparty': transaction['counterparty'],
|
||||
'occurredAt': transaction['occurredAt'],
|
||||
'source': 'manual',
|
||||
'sourceText': null,
|
||||
'clientRequestId': _importRequestId(transaction),
|
||||
},
|
||||
);
|
||||
transactionCount++;
|
||||
@@ -136,14 +158,42 @@ class GuestMergeService {
|
||||
) {
|
||||
final transaction = (snapshot['transactions'] as List<dynamic>? ?? const [])
|
||||
.cast<Map<String, dynamic>>()
|
||||
.where((item) => item['categoryId'] == oldCategoryId)
|
||||
.where(
|
||||
(item) =>
|
||||
item['categoryId'] == oldCategoryId && item['isDeleted'] != true,
|
||||
)
|
||||
.firstOrNull;
|
||||
if (transaction == null) return null;
|
||||
final match = available.where(
|
||||
(item) =>
|
||||
item['type'] == transaction['type'] &&
|
||||
item['type'] == _transactionCategoryType(transaction) &&
|
||||
item['name'] == transaction['categoryName'],
|
||||
);
|
||||
return match.isEmpty ? null : (match.first['id'] as num).toInt();
|
||||
}
|
||||
|
||||
static String _transactionCategoryType(Map<String, dynamic> transaction) {
|
||||
if (transaction['type'] != 'transfer') {
|
||||
return transaction['type'] as String;
|
||||
}
|
||||
return transaction['transferDirection'] == 'in' ? 'income' : 'expense';
|
||||
}
|
||||
|
||||
static String _importRequestId(Map<String, dynamic> transaction) {
|
||||
final existing = transaction['clientRequestId']?.toString().trim();
|
||||
if (existing != null && existing.isNotEmpty && existing.length <= 64) {
|
||||
return existing;
|
||||
}
|
||||
final identity = jsonEncode([
|
||||
transaction['id'],
|
||||
transaction['ledgerId'],
|
||||
transaction['categoryId'],
|
||||
transaction['type'],
|
||||
transaction['transferDirection'],
|
||||
transaction['amount'],
|
||||
transaction['occurredAt'],
|
||||
transaction['note'],
|
||||
]);
|
||||
return 'local-import-${sha256.convert(utf8.encode(identity)).toString().substring(0, 48)}';
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user