-
Mahmoud Aglan authored
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>
b3c232ab