feat(expenses): let one expense carry many receipt images
An expense is one payment, not one piece of paper. A month of fuel, or a kit
order settled across a dozen shops, arrives at the desk as a single expense
with a hundred receipts behind it. The schema had one set of `attachment_*`
columns on `expenses`, which encoded the opposite assumption, and
attachReceipt() deleted the file it displaced — so uploading page two of an
invoice destroyed page one and nothing said so.
Receipts move to their own `expense_attachments` table. The migration is
additive and idempotent (guarded by hasTable, backfill deduped by path, so it
is safe against the migrate-on-every-boot entrypoint), and it copies the
existing single attachments across, defaulting the pre-metadata rows to the
`public` disk exactly as attachmentDiskName() did in PHP. The old columns are
deliberately left in place rather than dropped: a client DB is migrated in
place on a live container and a dropped column is not recoverable.
The desk can now select several files at once and keep adding in batches —
selections accumulate instead of replacing, which is what a hundred-receipt
expense actually needs. Deleting one receipt leaves the others alone, and the
delete re-resolves the id through the expense's own relation so an id off the
wire cannot erase another expense's evidence. Streaming moves to the
attachment's own uuid route, with the branch check read from the parent
expense without global scopes so the guard cannot fail open.
Co-Authored-By:
Claude Opus 5 (1M context) <noreply@anthropic.com>
Showing
This diff is collapsed.
This diff is collapsed.
Please register or sign in to comment