Add payments with TDS, invoice detail view and filterable History (Phase E2)
- Migration M8 adds payments (cash, TDS, mode, reference). Payment status is derived, never stored: unpaid, partially paid, paid, or overdue. Overpayment is rejected, TDS counts toward settling, and only issued invoices accept payments. list_invoices and get_invoice carry the paid, TDS and balance summary. - New invoice detail view: archived-PDF preview, payments list, Record payment modal, Export, Duplicate (as a draft), Re-render and Cancel. An invoice with payments cannot be cancelled until they are deleted. - History gets search, status, financial-year, month and client filters, sortable columns, pagination, footer totals and an FY summary. Cancelled invoices are excluded from totals. The per-row archive-status calls are gone; archiving is on the detail page. - Rust tests for payments and the list summary; vitest for fiscal-year, money, filter and duplicate helpers. PDF output and fingerprint goldens are unchanged. The new UI has not been run in a webview yet.
This commit is contained in:
+26
-1
@@ -6,6 +6,7 @@ import { AppShell, type AppView } from "./components/AppShell";
|
||||
import Onboarding from "./views/Onboarding";
|
||||
import NewInvoice from "./views/NewInvoice";
|
||||
import InvoiceHistory from "./views/InvoiceHistory";
|
||||
import InvoiceDetail from "./views/InvoiceDetail";
|
||||
import Clients from "./views/Clients";
|
||||
import { ToastProvider, useToast } from "./components/ToastProvider";
|
||||
import AppSettings from "./views/AppSettings";
|
||||
@@ -24,6 +25,9 @@ function AppInner() {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [view, setView] = useState<AppView>("new");
|
||||
const [detailId, setDetailId] = useState<number | null>(null);
|
||||
// A draft made by "Duplicate" that the New invoice form should open once it is shown.
|
||||
const [openDraftId, setOpenDraftId] = useState<number | null>(null);
|
||||
|
||||
const refreshSettings = useCallback(async () => {
|
||||
try {
|
||||
@@ -104,10 +108,31 @@ function AppInner() {
|
||||
onSettingsChange={refreshSettings}
|
||||
active={view === "new"}
|
||||
onActivate={() => setView("new")}
|
||||
openDraftId={openDraftId}
|
||||
onDraftOpened={() => setOpenDraftId(null)}
|
||||
/>
|
||||
<div hidden={view !== "invoices"}>
|
||||
<InvoiceHistory settings={settings} active={view === "invoices"} />
|
||||
<InvoiceHistory
|
||||
settings={settings}
|
||||
active={view === "invoices"}
|
||||
onOpen={(id) => {
|
||||
setDetailId(id);
|
||||
setView("invoice");
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{view === "invoice" && detailId !== null ? (
|
||||
<InvoiceDetail
|
||||
key={detailId}
|
||||
invoiceId={detailId}
|
||||
settings={settings}
|
||||
onBack={() => setView("invoices")}
|
||||
onDuplicated={(draftId) => {
|
||||
setOpenDraftId(draftId);
|
||||
setView("new");
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
<div hidden={view !== "clients"}>
|
||||
<Clients active={view === "clients"} />
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user