ConusAI vendor v0.1.0-beta.114
ci / gate (push) Waiting to run

This commit is contained in:
ConusAI vendor v0.1.0-beta.114
2026-11-14 09:56:45 +00:00
commit 1ce8d23c35
867 changed files with 122104 additions and 0 deletions
+240
View File
@@ -0,0 +1,240 @@
"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<InvoiceView>({
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 (
<ResourceList
{...list}
label={labels.title}
columns={[
{ id: "number", header: labels.number, cell: (row) => 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<Record<string, unknown>, 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 <p>{labels.missingSeries}</p>
return (
<PageSection title={invoice ? labels.save : labels.create} variant="card">
<FormLayout
describeError={(error) => describeFailure(error, labels)}
submitLabel={invoice ? labels.save : labels.create}
busyLabel={labels.busy}
onSubmit={submit}
>
<DraftFields labels={labels} {...(invoice ? { data: invoice.data } : {})} series={series} count={count} />
<Button type="button" onClick={() => setCount((old) => Math.min(old + 1, 200))}>
{labels.addLine}
</Button>
</FormLayout>
</PageSection>
)
}
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<Record<string, unknown>, 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 (
<PageSection title={labels[operation]}>
<FormLayout
describeError={(error) => describeFailure(error, labels)}
submitLabel={labels[operation]}
busyLabel={labels.busy}
onSubmit={submit}
>
{operation === "credit" ? (
<SelectField
name="series"
label={labels.series}
required
options={series
.filter((row) => row.active && row.kind === "credit_note")
.map((row) => ({ value: row.seriesId, label: row.code }))}
/>
) : null}
{operation !== "issue" ? <TextField name="reason" label={labels.reason} required maxLength={500} /> : null}
<CheckField name="confirmed" label={labels.confirm} required />
</FormLayout>
</PageSection>
)
}
export function SeriesForm({ slugs, labels }: { slugs: ProjectSlugs; labels: Labels }) {
const navigation = usePageNavigation()
const key = useSubmissionId()
const write = useWrite<Record<string, unknown>, 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 (
<PageSection title={labels.createSeries}>
<FormLayout
describeError={(error) => describeFailure(error, labels)}
submitLabel={labels.createSeries}
busyLabel={labels.busy}
onSubmit={submit}
>
<TextField name="code" label={labels.seriesCode} required maxLength={32} />
<TextField name="prefix" label={labels.seriesPrefix} required maxLength={32} />
<SelectField
name="kind"
label={labels.kind}
required
options={[
{ value: "invoice", label: labels.invoiceKind },
{ value: "credit_note", label: labels.creditKind },
]}
/>
</FormLayout>
</PageSection>
)
}
export function UblDownload({ slugs, invoice, labels }: { slugs: ProjectSlugs; invoice: InvoiceView; labels: Labels }) {
const client = useDataClient()
const [problem, setProblem] = useState<string>()
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 (
<>
<Button
type="button"
onClick={() => {
void download()
}}
>
{labels.download}
</Button>
{problem ? <p role="alert">{problem}</p> : null}
</>
)
}