Add daily expense tracking with sort/split and CSV export

Standalone expenses (date, description, category, amount) with the same
equal/own/payer split machinery as entry costs, merged into the costs
panel and settle-up as a single expense bucket. Self-fetching card
with per-day grouping, client-side sorting, and quick-add. CSV export
interleaves expenses with priced entries, one share column per member
(UTF-8 BOM, RFC 4180, formula-injection guard on text cells).

Also fixes trip deletion, which hit a foreign-key violation and rolled
back for any trip with checklist items.
This commit is contained in:
2026-08-06 17:55:04 +07:00
parent e342cd9a91
commit f272e74b84
19 changed files with 2123 additions and 6 deletions
+14
View File
@@ -231,6 +231,20 @@ test('access control: non-member gets 404 on every trip-scoped and item-scoped r
assert.equal((await outsider.delete(`/api/checklist/${item.id}`)).status, 404);
});
test('deleting a trip removes its checklist items', async () => {
const { agent } = await createAccount();
const trip = await makeTrip(agent);
await agent.post(`/api/trips/${trip.id}/checklist`).send({ text: 'Passport' });
// checklist_items.trip_id has a real FK to trips(id) and foreign_keys is ON,
// so a trip delete that forgets this table fails the whole transaction (500).
assert.equal((await agent.delete(`/api/trips/${trip.id}`)).status, 204);
const left = app.locals.db
.prepare('SELECT COUNT(*) AS c FROM checklist_items WHERE trip_id = ?')
.get(trip.id).c;
assert.equal(left, 0);
});
test('access control: unknown item id -> 404', async () => {
const { agent } = await createAccount();
await makeTrip(agent);