diff --git a/DECISIONS.md b/DECISIONS.md index 534835e6..eea2d968 100644 --- a/DECISIONS.md +++ b/DECISIONS.md @@ -385,3 +385,5 @@ One line per decision: `[YYYY-MM-DD] : `. Appended by agents and [2026-07-25] Reverted the settings panel-sheet redesign on bug/resend-and-invoices back to main: Emil prefers the settings UI as it stands on main. The routed sheet, the sheet/ primitives (SettingsMasterDetail, SettingsAccordion, SettingsFieldRow), the *Subsections.tsx decompositions, the cold-load sheet and the settings_sheet i18n namespace were removed; every app/(dashboard)/settings/* page, components/settings/** file and MainContainer scroll exception now matches origin/main byte for byte. Unrelated branch work (invoice delivery outcomes, Stripe feed-only, article currency/deactivation, PDF logo) is untouched. [2026-07-25] Settings UI on bug/resend-and-invoices now comes from feat/settings-fonster-redesign (dbae8792, Jakob) instead of the panel-sheet work reverted earlier the same day: Emil chose the Fonster concept (flat hairline rows, help behind "?", sticky dirty-only save bar, 920x680 modal, switches instead of checkboxes). Applied as a patch rather than a merge because the redesign branch forks from b5e3c476 and merging would have dragged that older main in; every file applied cleanly since no settings file changed on main since that fork point. The 10 settings_payments keys the redesign still carries (needs_review_*, reason_*, sync_done_description/transactions) were deliberately NOT restored: the Stripe feed-only commit on this branch deleted both them and their call sites. [2026-07-26] Fixed cross-user attachment access (Odin Aero support case) at the call sites with service-role clients after company-scoped authorization, instead of rewriting the documents bucket storage policy to be company-scoped like sie-files got in 20260416120000: the documents path layout (documents/{userId}/...) carries no company_id, so a company-scoped policy needs a per-object join against document_attachments on every storage op, and the authorize-then-service-client pattern was already the established model (inline proxy route, v1 download route, MCP tools). Sweep found and fixed the same defect in the metadata/sign route, the integrity probe, verifyIntegrity, invoice-inbox retry-extraction, and cloud-backup archive generation. Known leftover, deliberately unfixed: deleteDocument and the upload-failure cleanups call storage remove() with a user-bound client, which silently no-ops (no DELETE policy, WORM), orphaning storage objects; harmless for compliance, needs a separate decision on whether files should ever be hard-deleted. +[2026-07-26] reverseEntry now blocks only source_type='storno', no longer 'correction': BFL 5 kap 5 § requires traceability, not immunity for rättelseverifikat, and blocking corrections left users with no sanctioned exit when a rättelse duplicated an affärshändelse booked elsewhere (support case 2026-07-26); it also broke uncategorize-after-rättelse since transactions are relinked to the correction entry. Storno-of-storno stays blocked (chain ambiguity). +[2026-07-26] Supplier-invoice DELETE allows 'approved' (not only 'registered'/'overdue'): the overdue cron flips BOTH registered and approved invoices to 'overdue', so excluding 'approved' would make deletability depend on whether the cron ran yet; the orphan-safety checks (no registration verifikat, no payments, no accrual schedule) are the real guard, and an attested but unbooked, unpaid invoice deletes nothing from the books. diff --git a/app/(dashboard)/supplier-invoices/[id]/page.tsx b/app/(dashboard)/supplier-invoices/[id]/page.tsx index 3b699c4f..5de975f7 100644 --- a/app/(dashboard)/supplier-invoices/[id]/page.tsx +++ b/app/(dashboard)/supplier-invoices/[id]/page.tsx @@ -491,15 +491,23 @@ export default function SupplierInvoiceDetailPage() { size="default" /> {invoice.status === 'registered' && !invoice.is_credit_note && ( - <> - + + )} + {/* Delete is allowed while nothing would be orphaned: no booking, no + payments (server re-checks). 'approved'/'overdue' are included + because the overdue cron flips unbooked invoices there and a + registered-only gate made them undeletable just by aging. */} + {['registered', 'approved', 'overdue'].includes(invoice.status) && + !invoice.is_credit_note && + !invoice.registration_journal_entry_id && + payments.length === 0 && ( - - )} + )} {['approved', 'overdue', 'partially_paid'].includes(invoice.status) && ( <>