Add the ERPNext settings UI, History and detail controls, and voiced.invoice.v1 export (Phase F3)
- Settings gets an Integrations tab: connection fields with a write-only API secret, a CA PEM option, Test connection with versions and warnings, live mapping lists loaded after a test, naming mode, toggles and its own Save. - Pushing for a GST-registered vendor is refused until a connection test result is stored. The UI shows the reason and disables Send. - History shows an ERPNext status column from one bulk call, a row Send and Open in ERPNext, and a bulk Send selected with a draft-or-submit confirm step and per-row results that never abort the batch. The detail view gets an ERPNext panel and per-payment send. All of it is hidden or disabled when the integration is not configured. - Optional auto-push after issue runs in the background and never blocks or fails the issue. - Generic export: a deterministic voiced.invoice.v1 JSON (and CSV line items) for selected invoices, documented in docs/voiced-invoice-v1.md with a golden test. Money is integer paise plus a decimal string. The new screens have not been run in a webview yet. The ERPNext Payment Entry deduction fields still need a live check.
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
import { useCallback, useState } from "react";
|
||||
import { save } from "@tauri-apps/plugin-dialog";
|
||||
import { api } from "../lib/api";
|
||||
import { exportInvoicesV1, type InvoiceExportFormat } from "../lib/invoiceExportV1";
|
||||
import { useToast } from "../components/ToastProvider";
|
||||
|
||||
/** "Export as voiced.invoice.v1": JSON or line-item CSV of the given invoices, through the save dialog. */
|
||||
export function useGenericExport(): { busy: boolean; run: (ids: number[], format: InvoiceExportFormat) => Promise<void> } {
|
||||
const toast = useToast();
|
||||
const [busy, setBusy] = useState(false);
|
||||
const run = useCallback(
|
||||
async (ids: number[], format: InvoiceExportFormat) => {
|
||||
setBusy(true);
|
||||
try {
|
||||
const done = await exportInvoicesV1(
|
||||
{
|
||||
getInvoice: api.getInvoice,
|
||||
listPayments: api.listPayments,
|
||||
listClients: api.listClients,
|
||||
getLastExportDir: api.getLastExportDir,
|
||||
save: ({ defaultPath, format: fmt }) =>
|
||||
save({ defaultPath, filters: [fmt === "csv" ? { name: "CSV (line items)", extensions: ["csv"] } : { name: "JSON (voiced.invoice.v1)", extensions: ["json"] }] }),
|
||||
writeExportFile: api.writeExportFile,
|
||||
now: () => new Date(),
|
||||
},
|
||||
ids,
|
||||
format,
|
||||
);
|
||||
if (done) toast.success(`Exported ${done.count} ${done.count === 1 ? "invoice" : "invoices"}`, done.path);
|
||||
} catch (e) {
|
||||
toast.error("Could not export the invoices", String(e));
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
},
|
||||
[toast],
|
||||
);
|
||||
return { busy, run };
|
||||
}
|
||||
Reference in New Issue
Block a user