+**Receipts (migration v112, #583):** an entry can carry documents from the Documents module as receipts — link an existing document or upload a new file straight from the entry modal. Receipts live in `budget_entry_attachments` (`entry_id`, `document_id`, `created_by`, `UNIQUE(entry_id, document_id)`), so one purchase may hold several (till receipt plus invoice plus warranty). The file itself always belongs to the Documents module: deleting the entry drops the link, not the document; deleting the document drops the link and leaves the entry. **Document visibility keeps applying** — a receipt filed as private stays invisible to everyone else even when it hangs on a shared entry, and there is no admin bypass. You can only link what you may see, and saving an entry only removes the links you can see, so another member's private receipt survives your edit. Receipts belong to the single entry, not to a recurring series: updating a series leaves them untouched (each month's bill has its own receipt). The API takes `attachment_document_ids` on create/update — omitting the field leaves existing receipts alone — and returns the visible ones as `attachments`. **Name and id belong to the Documents module (#1358):** without read access there (member right `documents: none`, or an API token without `documents:read`) each link comes masked - `document_id`, `name`, `original_name`, `mime_type` and `file_size` are `null` - and a non-empty `attachment_document_ids` is the same 403 for every id, while an empty list keeps everything. The same rule, from `mayReadDocuments()` in `server/services/document-links.js`, covers shared-expense receipts and payment proofs, inventory documents and the housekeeping receipt.
0 commit comments