feat: add account groups, scheduled payments and cash flow calendar

This commit is contained in:
陈煜 committed 2026-10-02 17:42:57 +08:00
1 parent a7e0a0fe58
commit 86c6daf695
46 files changed
+2548 -290

No files matched your search

+66 -2
View File
@@ -14,6 +14,7 @@ const empty = () =>
currencies: ['CNY'],
icons: [],
transfers: [],
schedules: [],
positions: [],
rates: [],
links: [],
@@ -43,6 +44,7 @@ test('ZIP contains separate JSON files and restores settings without authenticat
'links.json',
'manifest.json',
'rates.json',
'schedules.json',
'settings.json',
'transfers.json',
]);
@@ -102,10 +104,11 @@ test('legacy v3 ZIP remains readable without icons', async () => {
const contents = packBackup(empty());
delete contents['icons.json'];
delete contents['transfers.json'];
delete contents['schedules.json'];
const manifest = JSON.parse(contents['manifest.json']);
manifest.version = 3;
manifest.files = manifest.files.filter(
(f: { name: string }) => !['icons.json', 'transfers.json'].includes(f.name),
(f: { name: string }) => !['icons.json', 'transfers.json', 'schedules.json'].includes(f.name),
);
contents['manifest.json'] = JSON.stringify(manifest);
const restored = validateBackup(await readBackupZip(await archive(contents)));
@@ -116,9 +119,12 @@ test('legacy v3 ZIP remains readable without icons', async () => {
test('legacy v4 ZIP remains readable without transfers', async () => {
const contents = packBackup(empty());
delete contents['transfers.json'];
delete contents['schedules.json'];
const manifest = JSON.parse(contents['manifest.json']);
manifest.version = 4;
manifest.files = manifest.files.filter((f: { name: string }) => f.name !== 'transfers.json');
manifest.files = manifest.files.filter(
(f: { name: string }) => !['transfers.json', 'schedules.json'].includes(f.name),
);
contents['manifest.json'] = JSON.stringify(manifest);
const restored = validateBackup(await readBackupZip(await archive(contents)));
assert.deepEqual(restored.icons, []);
@@ -126,3 +132,61 @@ test('legacy v4 ZIP remains readable without transfers', async () => {
assert.equal(restored.preferences?.showNotes, false);
assert.deepEqual(restored.preferences?.hiddenMenus, ['asset']);
});
test('ZIP v7 restores groups and schedules while v6 remains readable', async () => {
const id = randomUUID(),
stamp = new Date().toISOString();
const b = validateBackup({
...empty(),
positions: [
{
id,
name: '账户',
groupName: '日常',
kind: 'account',
side: 'asset',
category: 'bank',
currency: 'CNY',
notes: '',
archived: false,
hidden: false,
createdAt: stamp,
updatedAt: stamp,
revisions: [
{
id: randomUUID(),
amount: '-10',
date: '2026-09-01T10:00',
notes: '',
reason: 'balance',
createdAt: stamp,
updatedAt: stamp,
},
],
},
],
schedules: [
{
id: randomUUID(),
name: '租金',
operation: 'expense',
sourceId: id,
targetId: null,
amount: '100',
received: '0',
nextAt: '2026-11-01T10:00',
intervalDays: 30,
enabled: true,
notes: '',
},
],
});
const contents = packBackup(b);
assert.deepEqual(validateBackup(await readBackupZip(await archive(contents))), b);
delete contents['schedules.json'];
const manifest = JSON.parse(contents['manifest.json']);
manifest.version = 6;
manifest.files = manifest.files.filter((f: { name: string }) => f.name !== 'schedules.json');
contents['manifest.json'] = JSON.stringify(manifest);
assert.equal(validateBackup(await readBackupZip(await archive(contents))).schedules, undefined);
});