fix: support offline manual bookkeeping

This commit is contained in:
2026-08-21 15:43:01 +08:00
parent 9bb45aac83
commit 979684a06e
5 changed files with 167 additions and 30 deletions
+47 -24
View File
@@ -8,6 +8,7 @@ import 'package:miaoji_zhang/shared/services/current_ledger_store.dart';
import 'package:miaoji_zhang/shared/services/local_database.dart';
import 'package:miaoji_zhang/shared/services/session_store.dart';
import 'package:miaoji_zhang/shared/services/shanghai_time.dart';
import 'package:miaoji_zhang/shared/services/sync_service.dart';
import 'package:shared_preferences/shared_preferences.dart';
class TxItem {
@@ -370,10 +371,7 @@ class TxApi {
static Future<List<CategoryItem>> categories(String type) async {
final session = SessionStore.instance;
if (session.shouldUseLocalOnly) {
return LocalDatabase.instance
.categories(type)
.map(CategoryItem.fromJson)
.toList();
return categoriesLocal(type);
}
try {
final response = await _dio.get(
@@ -387,18 +385,17 @@ class TxApi {
.toList();
} catch (error) {
if (!isConnectivityError(error) || !session.isAccount) rethrow;
return LocalDatabase.instance
.categories(type)
.map(CategoryItem.fromJson)
.toList();
return categoriesLocal(type);
}
}
static List<CategoryItem> categoriesLocal(String type) => LocalDatabase
.instance
.categories(type)
.map(CategoryItem.fromJson)
.toList();
static List<CategoryItem> categoriesLocal(String type) {
LocalDatabase.instance.ensureDefaultCategories(type: type);
return LocalDatabase.instance
.categories(type)
.map(CategoryItem.fromJson)
.toList();
}
static Future<List<CategoryItem>> categoriesRemote(String type) async {
final response = await _dio.get(
@@ -407,8 +404,10 @@ class TxApi {
);
final values = response.data as List;
LocalDatabase.instance.cacheCategories(values, replaceType: type);
return values
.map((item) => CategoryItem.fromJson(item as Map<String, dynamic>))
LocalDatabase.instance.ensureDefaultCategories(type: type);
return LocalDatabase.instance
.categories(type)
.map(CategoryItem.fromJson)
.toList();
}
@@ -429,6 +428,7 @@ class TxApi {
String? recognitionOccurrenceId,
String? evidenceFingerprint,
String? recognitionConfidence,
bool localFirst = false,
}) async {
final payload = <String, dynamic>{
'ledgerId': _ledgerId,
@@ -456,7 +456,10 @@ class TxApi {
'recognitionConfidence': recognitionConfidence,
};
final session = SessionStore.instance;
if (session.shouldUseLocalOnly || categoryId < 0 || _ledgerId < 0) {
if (localFirst ||
session.shouldUseLocalOnly ||
categoryId < 0 ||
_ledgerId < 0) {
final local = LocalDatabase.instance.createTransaction(payload);
if (_queueOfflineChanges) {
LocalDatabase.instance.enqueueSync(
@@ -465,6 +468,7 @@ class TxApi {
'create',
payload,
);
_scheduleBackgroundSync();
}
return TxItem.fromJson(local);
}
@@ -1698,8 +1702,9 @@ class CategoryApi {
String name,
String iconKey,
String colorKey,
String type,
) async {
String type, {
bool localFirst = false,
}) async {
final payload = {
'name': name,
'iconKey': iconKey,
@@ -1707,7 +1712,8 @@ class CategoryApi {
'type': type,
};
final session = SessionStore.instance;
if (session.shouldUseLocalOnly ||
if (localFirst ||
session.shouldUseLocalOnly ||
ApiClient.availability.value == BackendAvailability.offline) {
final local = LocalDatabase.instance.createCategory(
name,
@@ -1722,6 +1728,7 @@ class CategoryApi {
'create',
payload,
);
_scheduleBackgroundSync();
}
return CategoryItem.fromJson(local);
}
@@ -1754,6 +1761,7 @@ class CategoryApi {
required String iconKey,
required String colorKey,
required int sortOrder,
bool localFirst = false,
}) async {
final payload = {
'name': name,
@@ -1762,7 +1770,8 @@ class CategoryApi {
'sortOrder': sortOrder,
};
final session = SessionStore.instance;
if (session.shouldUseLocalOnly ||
if (localFirst ||
session.shouldUseLocalOnly ||
ApiClient.availability.value == BackendAvailability.offline ||
id < 0) {
final local = LocalDatabase.instance.updateCategory(
@@ -1774,6 +1783,7 @@ class CategoryApi {
);
if (session.isAccount && session.cloudSyncEnabled) {
LocalDatabase.instance.enqueueSync('category', id, 'update', payload);
_scheduleBackgroundSync();
}
return CategoryItem.fromJson(local);
}
@@ -1796,10 +1806,15 @@ class CategoryApi {
}
}
static Future<void> reorder(String type, List<int> categoryIds) async {
static Future<void> reorder(
String type,
List<int> categoryIds, {
bool localFirst = false,
}) async {
LocalDatabase.instance.reorderCategories(type, categoryIds);
final session = SessionStore.instance;
if (session.shouldUseLocalOnly ||
if (localFirst ||
session.shouldUseLocalOnly ||
ApiClient.availability.value == BackendAvailability.offline ||
categoryIds.any((id) => id < 0)) {
if (session.isAccount && session.cloudSyncEnabled) {
@@ -1807,6 +1822,7 @@ class CategoryApi {
'type': type,
'categoryIds': categoryIds,
});
_scheduleBackgroundSync();
}
return;
}
@@ -1824,9 +1840,10 @@ class CategoryApi {
}
}
static Future<void> delete(int id) async {
static Future<void> delete(int id, {bool localFirst = false}) async {
final session = SessionStore.instance;
if (session.shouldUseLocalOnly ||
if (localFirst ||
session.shouldUseLocalOnly ||
ApiClient.availability.value == BackendAvailability.offline ||
id < 0) {
LocalDatabase.instance.deleteCategory(id);
@@ -1834,6 +1851,7 @@ class CategoryApi {
LocalDatabase.instance.enqueueSync('category', id, 'delete', {
'id': id,
});
_scheduleBackgroundSync();
}
return;
}
@@ -1847,3 +1865,8 @@ class CategoryApi {
}
}
}
void _scheduleBackgroundSync() {
SyncService.instance.refreshLocalStatus();
unawaited(SyncService.instance.run());
}