fix: support offline manual bookkeeping
This commit is contained in:
@@ -16,12 +16,12 @@ class LocalDatabase {
|
||||
static const _secureStorage = FlutterSecureStorage();
|
||||
|
||||
@visibleForTesting
|
||||
static LocalDatabase inMemoryForTesting() {
|
||||
static LocalDatabase inMemoryForTesting({bool seedDefaults = true}) {
|
||||
final database = LocalDatabase._();
|
||||
database._database = sqlite3.openInMemory();
|
||||
database._namespace = 'test';
|
||||
database._migrate();
|
||||
database._seedDefaults();
|
||||
if (seedDefaults) database._seedDefaults();
|
||||
return database;
|
||||
}
|
||||
|
||||
@@ -69,7 +69,11 @@ class LocalDatabase {
|
||||
_database = database;
|
||||
_namespace = namespace;
|
||||
_migrate();
|
||||
if (namespace == 'guest') _seedDefaults();
|
||||
if (namespace == 'guest') {
|
||||
_seedDefaults();
|
||||
} else {
|
||||
ensureDefaultCategories();
|
||||
}
|
||||
} catch (_) {
|
||||
database.close();
|
||||
rethrow;
|
||||
@@ -295,6 +299,80 @@ class LocalDatabase {
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensures an account can still render the category picker and create a
|
||||
/// manual transaction before its first successful category refresh.
|
||||
///
|
||||
/// Account fallback IDs mirror the server's seeded system categories. The
|
||||
/// remote refresh remains authoritative and will update these rows in place.
|
||||
void ensureDefaultCategories({String? type}) {
|
||||
final isGuestNamespace = _namespace == 'guest';
|
||||
const expense = <(int, String, String, String, String, int)>[
|
||||
(1, '餐饮', 'food', 'coral', 'expense', 0),
|
||||
(2, '饮品', 'cup', 'cyan', 'expense', 1),
|
||||
(3, '购物', 'cart', 'blue', 'expense', 2),
|
||||
(4, '交通', 'metro', 'teal', 'expense', 3),
|
||||
(5, '住房', 'house', 'sand', 'expense', 4),
|
||||
(6, '娱乐', 'game', 'violet', 'expense', 5),
|
||||
(7, '医疗', 'pill', 'red', 'expense', 6),
|
||||
(8, '学习', 'book', 'indigo', 'expense', 7),
|
||||
(9, '服饰', 'shirt', 'plum', 'expense', 8),
|
||||
(10, '人情', 'gift', 'rose', 'expense', 9),
|
||||
(11, '旅行', 'plane', 'sky', 'expense', 10),
|
||||
(12, '其他', 'tag', 'graphite', 'expense', 11),
|
||||
];
|
||||
const accountIncome = <(int, String, String, String, String, int)>[
|
||||
(13, '工资', 'money', 'mint', 'income', 0),
|
||||
(14, '兼职', 'briefcase', 'forest', 'income', 1),
|
||||
(15, '理财', 'chart', 'navy', 'income', 2),
|
||||
(16, '红包', 'gift', 'orange', 'income', 3),
|
||||
(17, '报销', 'card', 'amber', 'income', 4),
|
||||
(18, '奖金', 'sparkle', 'aqua', 'income', 5),
|
||||
(19, '其他', 'tag', 'lime', 'income', 6),
|
||||
];
|
||||
const guestIncome = <(int, String, String, String, String, int)>[
|
||||
(101, '工资', 'money', 'mint', 'income', 10),
|
||||
(102, '兼职', 'parttime', 'forest', 'income', 20),
|
||||
(103, '理财', 'invest', 'navy', 'income', 30),
|
||||
(104, '红包', 'redpacket', 'orange', 'income', 40),
|
||||
(105, '报销', 'reimburse', 'amber', 'income', 50),
|
||||
(106, '奖金', 'bonus', 'aqua', 'income', 60),
|
||||
(107, '其他', 'tag', 'lime', 'income', 70),
|
||||
];
|
||||
final catalogs = <String, List<(int, String, String, String, String, int)>>{
|
||||
'expense': expense,
|
||||
'income': isGuestNamespace ? guestIncome : accountIncome,
|
||||
};
|
||||
final now = DateTime.now().toUtc().toIso8601String();
|
||||
for (final entry in catalogs.entries) {
|
||||
if (type != null && entry.key != type) continue;
|
||||
final active = _db.select(
|
||||
'''SELECT 1 FROM categories
|
||||
WHERE type = ? AND is_deleted = 0 AND is_custom = 0 LIMIT 1''',
|
||||
[entry.key],
|
||||
);
|
||||
if (active.isNotEmpty) continue;
|
||||
for (final item in entry.value) {
|
||||
_db.execute(
|
||||
'''
|
||||
INSERT INTO categories
|
||||
(id, name, icon_key, color_key, type, sort_order, is_custom, is_deleted, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, 0, 0, ?)
|
||||
ON CONFLICT(id) DO UPDATE SET
|
||||
name = excluded.name,
|
||||
icon_key = excluded.icon_key,
|
||||
color_key = excluded.color_key,
|
||||
type = excluded.type,
|
||||
sort_order = excluded.sort_order,
|
||||
is_custom = 0,
|
||||
is_deleted = 0,
|
||||
updated_at = excluded.updated_at
|
||||
''',
|
||||
[item.$1, item.$2, item.$3, item.$4, item.$5, item.$6, now],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, dynamic> guestSnapshot() {
|
||||
final customCategories = _db
|
||||
.select('''
|
||||
|
||||
Reference in New Issue
Block a user