From f0bba301e92b35eb12eef4ef0fc19a3f5084329d Mon Sep 17 00:00:00 2001 From: Thomas Joise Date: Sun, 19 Jul 2026 18:02:38 +0530 Subject: [PATCH] feat(docs): require a reason when cancelling a document MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cancelling an issued document now needs a reason: cancelDocument takes it, refuses an empty one, and records it on the 'cancelled' event (visible in the doc timeline) and in the audit after-image. The Cancel dialog gains a required reason box (the confirm stays open with an error until one is given). Supersede & recreate cancels with 'Superseded & recreated'. Documents are never hard-deleted (immutability) — cancel is the path, now always explained. Full suite 416 green. Co-Authored-By: Claude Fable 5 --- apps/hq-web/src/pages/DocumentView.tsx | 25 +++++++++++++++++++------ apps/hq/src/api.ts | 4 ++-- apps/hq/src/repos-documents.ts | 10 ++++++---- apps/hq/test/documents.test.ts | 4 ++-- apps/hq/test/quote-followup.test.ts | 2 +- 5 files changed, 30 insertions(+), 15 deletions(-) diff --git a/apps/hq-web/src/pages/DocumentView.tsx b/apps/hq-web/src/pages/DocumentView.tsx index c87ace0..e35dd90 100644 --- a/apps/hq-web/src/pages/DocumentView.tsx +++ b/apps/hq-web/src/pages/DocumentView.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react' +import { useEffect, useRef, useState, type ReactNode } from 'react' import { Link, useNavigate, useParams } from 'react-router-dom' import { formatINR } from '@sims/domain' import { @@ -14,7 +14,7 @@ import { DOC_TONE, DOC_TYPE_TONE, DOC_TYPE_LABEL, useData } from './Clients' import { PaymentForm } from './client-forms' /** One pending confirmation — replaces the browser confirm() popup with the styled dialog. */ -interface Confirm { title: string; body: string; label: string; tone?: 'danger'; run: () => void } +interface Confirm { title: string; body: ReactNode; label: string; tone?: 'danger'; run: () => void | Promise } const TITLE: Record = { QUOTATION: 'Quotation', PROFORMA: 'Proforma Invoice', INVOICE: 'Tax Invoice', @@ -43,6 +43,7 @@ export function DocumentView() { const [paying, setPaying] = useState(false) const [sending, setSending] = useState(false) const [confirm, setConfirm] = useState() + const cancelReasonRef = useRef('') // read in the confirm's run() closure (state would be stale there) // PDF preview via blob URL — an