"use client" import { formatMoney } from "@conusai-kit/i18n/values" import { Money } from "@conusai-kit/money" import type { ProjectSlugs } from "@conusai-kit/ui" import { useDataClient, useWrite } from "@conusai-kit/ui/data" // SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0 import { CheckField, FormLayout, type FormValues, PageSection, ResourceList, SelectField, TextField, usePageNavigation, useResourceList, } from "@conusai-kit/ui/patterns" import { Button } from "@conusai-kit/ui/primitives" import { useRef, useState } from "react" import type { InvoiceView, SeriesView } from "../contract/index.js" import { DraftFields } from "./fields.js" import { draftData } from "./form-data.js" import { describeFailure, type Labels } from "./labels.js" // A lost response retries the same input/key. Editing terms starts a different operation. function useSubmissionId() { const current = useRef<{ value: string; key: string } | null>(null) return (input: unknown) => { const value = JSON.stringify(input) if (current.current?.value !== value) current.current = { value, key: crypto.randomUUID() } return current.current.key } } export function Invoices({ slugs, seed, href, labels, }: { slugs: ProjectSlugs seed: { rows: InvoiceView[]; after?: string | undefined } href: string labels: Labels }) { const client = useDataClient() const { list } = useResourceList({ seed, rowId: (row) => row.invoiceId, read: async ({ after }) => ( await client.call<{ rows: InvoiceView[]; after?: string }>("invoicing.invoice.list", { org: slugs.org, ...(after ? { after } : {}), }) ).value, }) return ( row.number ?? labels.draft }, { id: "status", header: labels.status, cell: (row) => labels[row.status] }, { id: "payable", header: labels.payable, cell: (row) => formatMoney(Money.parse(row.payable), labels.locale) }, ]} rowHref={(row) => `${href}/${row.invoiceId}`} rowLabel={(row) => row.number ?? labels.draft} empty={{ title: labels.empty }} /> ) } export function InvoiceForm({ slugs, invoice, series, labels, href, }: { slugs: ProjectSlugs invoice?: InvoiceView series: SeriesView[] labels: Labels href: string }) { const navigation = usePageNavigation() const [count, setCount] = useState(invoice?.data.lines.length ?? 1) const key = useSubmissionId() const write = useWrite, InvoiceView>( invoice ? "invoicing.invoice.update" : "invoicing.invoice.draft", ) const submit = async (values: FormValues) => { const data = draftData(values, count, labels.locale, invoice?.data) const input = { org: slugs.org, data, ...(invoice ? { invoiceId: invoice.invoiceId, expectedVersion: invoice.revision } : {}), } const saved = await write.mutate({ ...input, requestId: key(input) }) navigation.navigate(`${href}/${saved.invoiceId}`) } if (!series.some((row) => row.active && row.kind === (invoice?.data.type_code === "381" ? "credit_note" : "invoice"))) return

{labels.missingSeries}

return ( describeFailure(error, labels)} submitLabel={invoice ? labels.save : labels.create} busyLabel={labels.busy} onSubmit={submit} > ) } export function FiscalAction({ slugs, invoice, operation, series, labels, }: { slugs: ProjectSlugs invoice: InvoiceView operation: "issue" | "credit" | "void" series: SeriesView[] labels: Labels }) { const navigation = usePageNavigation() const key = useSubmissionId() const action = useWrite, InvoiceView>(`invoicing.invoice.${operation}`) const submit = async (values: FormValues) => { const input = { org: slugs.org, invoiceId: invoice.invoiceId, expectedVersion: invoice.revision, confirmed: values.confirmed === "on", ...(operation === "credit" ? { seriesId: values.series } : {}), ...(operation !== "issue" ? { reason: values.reason } : {}), } const result = await action.mutate({ ...input, requestId: key(input) }) if (operation === "credit") navigation.navigate( `/${slugs.org}/${slugs.workspace}/${slugs.project}/invoicing/${encodeURIComponent(result.invoiceId)}`, ) else await navigation.refresh() } return ( describeFailure(error, labels)} submitLabel={labels[operation]} busyLabel={labels.busy} onSubmit={submit} > {operation === "credit" ? ( row.active && row.kind === "credit_note") .map((row) => ({ value: row.seriesId, label: row.code }))} /> ) : null} {operation !== "issue" ? : null} ) } export function SeriesForm({ slugs, labels }: { slugs: ProjectSlugs; labels: Labels }) { const navigation = usePageNavigation() const key = useSubmissionId() const write = useWrite, SeriesView>("invoicing.series.create") const submit = async (values: FormValues) => { const input = { org: slugs.org, code: values.code, prefix: values.prefix, kind: values.kind } await write.mutate({ ...input, requestId: key(input) }) await navigation.refresh() } return ( describeFailure(error, labels)} submitLabel={labels.createSeries} busyLabel={labels.busy} onSubmit={submit} > ) } export function UblDownload({ slugs, invoice, labels }: { slugs: ProjectSlugs; invoice: InvoiceView; labels: Labels }) { const client = useDataClient() const [problem, setProblem] = useState() const download = async () => { try { const { value } = await client.call<{ xml: string; filename: string }>("invoicing.invoice.ubl", { org: slugs.org, invoiceId: invoice.invoiceId, }) const url = URL.createObjectURL(new Blob([value.xml], { type: "application/xml" })) const link = document.createElement("a") link.href = url link.download = value.filename link.click() setTimeout(() => URL.revokeObjectURL(url), 1000) setProblem(undefined) } catch (error) { setProblem(describeFailure(error, labels)) } } return ( <> {problem ?

{problem}

: null} ) }