* feat(invoices): draft invoice delete on v1 and MCP with staged approval
Draft customer-invoice deletion was web-only. This makes the same
semantics available on the v1 API-key surface and as an MCP write tool:
unnumbered drafts are hard deleted (no F-series number was consumed, so
no gap arises), numbered drafts are makulerade (status 'cancelled',
number retained so the F-series stays gap-free per ML 17 kap 24 and
BFNAR 2013:2). Non-drafts are refused; posted invoices can only be
reversed via a credit note.
- extract the web DELETE logic into lib/invoices/delete-draft-invoice.ts
with an explicit userId param (service-role clients null auth.uid());
the cookie route behavior is unchanged
- add DELETE /api/v1/companies/{companyId}/invoices/{id}: 409
INVOICE_DELETE_NOT_DRAFT for non-drafts (status override; the cookie
route keeps its 400), 404 generic NOT_FOUND, dry-run preview of the
outcome, mandatory Idempotency-Key; scope invoices:write
- fix the stale v1 PATCH pitfall that claimed a DELETE handler existed
- new MCP tool gnubok_delete_draft_invoice: staged operation requiring
approval, risk 'high' (both outcomes irreversible, never
auto-committed), catalogVisibility 'search' (tools/list budget at zero
headroom)
- delete_draft_invoice commit executor delegating to the shared service,
plus pending_operations CHECK constraint migration pair
(20260830100000/100001), risk tier, scope map, Granskning vocabulary
and sv/en labels
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NtvffGr6uVk2J2Skuz6L98
* fix(migrations): renumber delete_draft_invoice pair after 20260830101500 on main
Merging origin/main brought 20260830101500_seed_agent_atom_bodies; the
constraint pair must sort after every version already on main so it
never applies out of order at merge time.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NtvffGr6uVk2J2Skuz6L98
* docs(api-skill): regenerate accounted-api skill for the new invoices.delete endpoint
apiskill:check failed on CI: registering DELETE /invoices/{id} makes the
generated skills/accounted-api docs stale. Output of npm run
apiskill:generate, no hand edits.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NtvffGr6uVk2J2Skuz6L98
* fix(invoices): pin staged delete outcome and align v1 risk metadata
Skeptic findings on PR #2036:
- Outcome pin: gnubok_delete_draft_invoice stages
expected_invoice_number alongside invoice_id; the executor passes it to
deleteDraftInvoice, which refuses with INVOICE_CANCEL_RACE when the
draft's number changed since staging. An unnumbered draft finalized
between staging and approval is now auto-rejected with a message naming
the new number, instead of silently switching from the approved hard
delete to a makulering. Ops staged without the pin keep legacy
semantics; single-phase callers (web, v1) are unaffected.
- v1 invoices.delete registerEndpoint risk raised medium -> high to match
the delete_draft_invoice pending-op tier (both outcomes irreversible);
generated accounted-api docs regenerated.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NtvffGr6uVk2J2Skuz6L98
* fix(migrations): renumber delete_draft_invoice pair after skattekonto collision
Merging origin/main brought PR #2039's 20260830130000/130001 pair, which
collides with this branch's versions AND re-creates the same
pending_operations CHECK wholesale. Renumber to 20260830150000/150001 and
rebuild the value list as a strict superset (skattekonto list plus
delete_draft_invoice) so applying last revokes nothing.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NtvffGr6uVk2J2Skuz6L98
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
106 lines
3.6 KiB
SQL
106 lines
3.6 KiB
SQL
-- Add 'delete_draft_invoice' to the pending_operations operation_type CHECK
|
|
-- constraint.
|
|
--
|
|
-- gnubok_delete_draft_invoice (MCP) stages removal of a DRAFT customer
|
|
-- invoice. The user approves it in Granskning and commitDeleteDraftInvoice in
|
|
-- lib/pending-operations/commit.ts delegates to the shared
|
|
-- lib/invoices/delete-draft-invoice.ts service (the same code behind the
|
|
-- cookie-session and v1 DELETE routes): an unnumbered draft is hard deleted
|
|
-- (no F-series number was consumed, so no gap arises; ML 17 kap 24) and a
|
|
-- numbered draft is makulerad (status 'cancelled', number retained so the
|
|
-- F-series stays gap-free per BFNAR 2013:2). Non-drafts are refused at both
|
|
-- staging and commit time; posted invoices can only be reversed via a credit
|
|
-- note. Risk 'high': both outcomes are irreversible (row gone, or the number
|
|
-- permanently consumed), so the op is never auto-committed.
|
|
--
|
|
-- NOTE on the value list: this constraint is re-created wholesale (the
|
|
-- established pattern here), so the list below is every value of the
|
|
-- constraint as left by 20260830130000 (which added book_skattekonto_row /
|
|
-- book_skattekonto_rows) PLUS the new value. Dropping any existing value
|
|
-- here would silently revoke it.
|
|
--
|
|
-- NOT VALID + separate VALIDATE migration (paired file, same pattern as
|
|
-- 20260830130000 / 20260830130001).
|
|
--
|
|
-- pg-test: tests/pg/pending-operations-op-type-audit.pg.test.ts asserts every
|
|
-- op type staged in server.ts or tiered in risk-tiers.ts is accepted here.
|
|
ALTER TABLE public.pending_operations
|
|
DROP CONSTRAINT IF EXISTS pending_operations_operation_type_check;
|
|
|
|
ALTER TABLE public.pending_operations
|
|
ADD CONSTRAINT pending_operations_operation_type_check
|
|
CHECK (operation_type IN (
|
|
'categorize_transaction',
|
|
'create_customer',
|
|
'create_invoice',
|
|
'mark_invoice_paid',
|
|
'send_invoice',
|
|
'mark_invoice_sent',
|
|
'match_transaction_invoice',
|
|
'close_period',
|
|
'lock_period',
|
|
'unlock_period',
|
|
'set_opening_balances',
|
|
'run_year_end',
|
|
'post_kontantmetod_cutoff',
|
|
'run_currency_revaluation',
|
|
'import_sie',
|
|
'explain_voucher_gap',
|
|
'uncategorize_transaction',
|
|
'approve_supplier_invoice',
|
|
'credit_supplier_invoice',
|
|
'credit_invoice',
|
|
'convert_invoice',
|
|
'delete_draft_invoice',
|
|
'create_transaction',
|
|
'attach_document_to_transaction',
|
|
'create_voucher',
|
|
'correct_entry',
|
|
'reverse_entry',
|
|
'create_supplier',
|
|
'create_supplier_invoice_from_inbox',
|
|
'post_annual_depreciation',
|
|
'link_invoice_voucher',
|
|
'undo_sie_import',
|
|
'match_batch_allocate',
|
|
'bulk_book_transactions',
|
|
'create_salary_run',
|
|
'generate_agi',
|
|
'link_transaction_journal_entry',
|
|
'link_supplier_invoice_voucher',
|
|
'submit_vat_declaration',
|
|
'submit_agi',
|
|
'create_article',
|
|
'update_article',
|
|
'bulk_book_inbox_items',
|
|
'create_dimension_value',
|
|
'retag_line_dimensions',
|
|
'link_document_to_voucher',
|
|
'update_payslip_line',
|
|
'set_run_salary',
|
|
'register_absence',
|
|
'create_employee',
|
|
'update_employee',
|
|
'set_employee_opening_balances',
|
|
'vacation_year_close',
|
|
'create_account',
|
|
'update_account',
|
|
'set_voucher_note',
|
|
'book_salary_run',
|
|
'delete_absence',
|
|
'update_company_settings',
|
|
'update_customer',
|
|
'update_invoice',
|
|
'create_recurring_schedule',
|
|
'update_recurring_schedule',
|
|
'log_mileage_trip',
|
|
'book_mileage_period',
|
|
'link_documents_to_vouchers',
|
|
'reconciliation_match',
|
|
'reconciliation_unmatch',
|
|
'reconciliation_signoff',
|
|
'reconciliation_residual',
|
|
'book_skattekonto_row',
|
|
'book_skattekonto_rows'
|
|
)) NOT VALID;
|