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
+11
View File
@@ -0,0 +1,11 @@
import { describeError } from "@conusai-kit/ui/data"
import { serviceNotice } from "@conusai-kit/ui/patterns"
import { LEDGER_ERRORS } from "../contract/index.js"
import type { LedgerLabels } from "./labels.js"
/** What a form shows for a refused write: the module's drift fence, a Ledger refusal in the reader's words, or the kit's text. */
export function ledgerFailure(error: unknown, labels: LedgerLabels): string {
const code = (error as { problem?: { code?: unknown } } | null)?.problem?.code
if (code === LEDGER_ERRORS.drift) return labels.drift
return serviceNotice(error, labels.refusals)?.text ?? describeError(error)
}
+305
View File
@@ -0,0 +1,305 @@
"use client"
import { useWrite } from "@conusai-kit/ui/data"
import {
CheckField,
ConfirmDialog,
FactList,
FormLayout,
type FormValues,
Page,
PageSection,
ResourceList,
SelectField,
TextField,
usePageNavigation,
} from "@conusai-kit/ui/patterns"
import { Button } from "@conusai-kit/ui/primitives"
import { useRef, useState } from "react"
import { type AccountView, recordTerms, type TransactionView } from "../contract/index.js"
import { ledgerFailure } from "./errors.js"
import type { LedgerLabels, LedgerMessages } from "./labels.js"
import { retryIdentity } from "./retry.js"
const options = (labels: LedgerLabels, values: (keyof LedgerMessages)[]) =>
values.map((value) => ({ value, label: labels[value] }))
export function AccountForm({
org,
account,
labels,
base,
}: {
org: string
account?: AccountView
labels: LedgerLabels
base: string
}) {
const navigation = usePageNavigation()
const write = useWrite<Record<string, unknown>, AccountView>(
account ? "ledger.accounts.update" : "ledger.accounts.create",
)
const identity = useRef(retryIdentity())
const [pending, setPending] = useState<Record<string, unknown>>()
return (
<PageSection title={account ? labels.configure : labels.create}>
<FormLayout
describeError={(error) => ledgerFailure(error, labels)}
submitLabel={labels.review}
busyLabel={labels.busy}
onSubmit={async (values: FormValues) => {
setPending(
account
? {
org,
accountId: account.accountId,
expectedVersion: account.revision,
name: values.name,
status: values.status,
allowNegative: values.allowNegative === "on",
}
: { org, ...values, allowNegative: values.allowNegative === "on" },
)
}}
>
{!account ? (
<>
<TextField name="code" label={labels.code} required pattern="[A-Za-z0-9][A-Za-z0-9._\-]{0,63}" />
<TextField name="currency" label={labels.currency} required pattern="[A-Z]{3}" />
<SelectField
name="type"
label={labels.type}
defaultValue="asset"
options={options(labels, ["asset", "liability", "equity", "income", "expense"])}
/>
<SelectField
name="normalBalance"
label={labels.normalBalance}
defaultValue="debit"
options={options(labels, ["debit", "credit"])}
/>
</>
) : (
<SelectField
name="status"
label={labels.status}
defaultValue={account.status}
options={options(labels, ["active", "archived"])}
/>
)}
<TextField name="name" label={labels.name} defaultValue={account?.name} required maxLength={200} />
<CheckField name="allowNegative" label={labels.allowNegative} defaultChecked={account?.allowNegative} />
</FormLayout>
<ConfirmDialog
describeError={(error) => ledgerFailure(error, labels)}
open={pending !== undefined}
onOpenChange={(open) => {
if (!open) setPending(undefined)
}}
title={labels.confirm}
description={labels.archiveWarning}
confirmLabel={labels.save}
cancelLabel={labels.cancel}
onConfirm={async () => {
if (!pending) return
const saved = await write.mutate({ ...pending, requestId: identity.current(pending) })
navigation.navigate(`${base}/accounts/${saved.accountId}`)
}}
>
<Page>
{pending ? (
<FactList
facts={[
{ label: labels.name, value: String(pending.name) },
{ label: labels.code, value: account?.code ?? String(pending.code) },
{ label: labels.currency, value: account?.currency ?? String(pending.currency) },
{ label: labels.type, value: labels[(account?.type ?? String(pending.type)) as keyof LedgerMessages] },
{
label: labels.normalBalance,
value: labels[(account?.normalBalance ?? String(pending.normalBalance)) as keyof LedgerMessages],
},
{ label: labels.status, value: pending.status === "archived" ? labels.archived : labels.active },
{ label: labels.allowNegative, value: pending.allowNegative ? labels.yes : labels.no },
]}
/>
) : null}
</Page>
</ConfirmDialog>
</PageSection>
)
}
export function RecordForm({
org,
labels,
base,
fenced,
}: {
org: string
labels: LedgerLabels
base: string
fenced: boolean
}) {
const navigation = usePageNavigation()
const write = useWrite<Record<string, unknown>, TransactionView>("ledger.transactions.record")
const identity = useRef(retryIdentity())
const [lines, setLines] = useState(2)
const [pending, setPending] = useState<ReturnType<typeof recordTerms.parse>>()
return (
<PageSection title={labels.record}>
<FormLayout
describeError={(error) => ledgerFailure(error, labels)}
submitLabel={labels.review}
busyLabel={labels.busy}
submitDisabled={fenced}
onSubmit={async (values: FormValues) => {
if (fenced) throw new Error(labels.drift)
const result = recordTerms.safeParse({
effectiveAt: values.effectiveAt,
description: values.description,
...(values.reference ? { reference: values.reference } : {}),
status: values.status,
entries: Array.from({ length: lines }, (_, i) => ({
accountId: values[`account-${i}`],
direction: values[`direction-${i}`],
amountMinor: values[`amount-${i}`],
})),
})
if (!result.success) throw new Error(labels.invalid)
setPending(result.data)
}}
>
<TextField name="effectiveAt" label={labels.effectiveAt} required />
<TextField name="description" label={labels.description} required maxLength={200} />
<TextField name="reference" label={labels.reference} maxLength={200} />
<SelectField
name="status"
label={labels.status}
defaultValue="pending"
options={options(labels, ["pending", "posted"])}
/>
{Array.from({ length: lines }, (_, i) => (
<PageSection key={i} title={`${labels.entries} ${i + 1}`}>
<TextField name={`account-${i}`} label={labels.account} required />
<SelectField
name={`direction-${i}`}
label={labels.normalBalance}
defaultValue={i === 0 ? "debit" : "credit"}
options={options(labels, ["debit", "credit"])}
/>
<TextField
name={`amount-${i}`}
label={labels.amountMinor}
required
pattern="[1-9][0-9]{0,18}"
inputMode="numeric"
/>
</PageSection>
))}
<Button type="button" disabled={lines >= 200 || fenced} onClick={() => setLines((n) => n + 1)}>
{labels.addLine}
</Button>
<Button type="button" disabled={lines <= 2} onClick={() => setLines((n) => n - 1)}>
{labels.removeLine}
</Button>
</FormLayout>
<ConfirmDialog
describeError={(error) => ledgerFailure(error, labels)}
open={pending !== undefined}
onOpenChange={(open) => {
if (!open) setPending(undefined)
}}
title={labels.confirm}
description={labels.confirmDescription}
confirmLabel={labels.record}
cancelLabel={labels.cancel}
onConfirm={async () => {
if (!pending || fenced) throw new Error(labels.drift)
const saved = await write.mutate({
org,
terms: pending,
confirmed: true,
requestId: identity.current({ org, terms: pending }),
})
navigation.navigate(`${base}/transactions/${saved.transactionId}`)
}}
>
<Page>
{pending ? (
<>
<FactList
facts={[
{ label: labels.effectiveAt, value: pending.effectiveAt },
{ label: labels.description, value: pending.description },
{ label: labels.reference, value: pending.reference },
{ label: labels.status, value: labels[pending.status] },
]}
/>
<ResourceList
label={labels.entries}
data={pending.entries.map((entry, index) => ({ ...entry, id: String(index) }))}
columns={[
{ id: "accountId", header: labels.account },
{ id: "direction", header: labels.normalBalance, cell: (row) => labels[row.direction] },
{ id: "amountMinor", header: labels.amountMinor },
]}
empty={{ title: labels.empty }}
/>
</>
) : null}
</Page>
</ConfirmDialog>
</PageSection>
)
}
export function Transition({
org,
transaction,
action,
labels,
base,
fenced,
}: {
org: string
transaction: TransactionView
action: "post" | "void" | "reverse"
labels: LedgerLabels
base: string
fenced: boolean
}) {
const navigation = usePageNavigation()
const write = useWrite<Record<string, unknown>, TransactionView>(`ledger.transactions.${action}`)
const identity = useRef(retryIdentity())
const payload = {
org,
transactionId: transaction.transactionId,
expectedVersion: transaction.revision,
confirmed: true,
}
return (
<ConfirmDialog
describeError={(error) => ledgerFailure(error, labels)}
title={labels.confirm}
description={labels.confirmDescription}
confirmLabel={labels[action]}
cancelLabel={labels.cancel}
trigger={<Button disabled={fenced && action !== "void"}>{labels[action]}</Button>}
onConfirm={async () => {
if (fenced && action !== "void") throw new Error(labels.drift)
const saved = await write.mutate({ ...payload, requestId: identity.current(payload) })
navigation.navigate(`${base}/transactions/${saved.transactionId}`)
}}
>
<Page>
<FactList
facts={[
{ label: labels.transaction, value: transaction.transactionId },
{ label: labels.revision, value: transaction.revision },
{ label: labels.status, value: labels[transaction.status] },
]}
/>
</Page>
</ConfirmDialog>
)
}
+22
View File
@@ -0,0 +1,22 @@
import { businessRefusalNotices } from "@conusai-kit/business/refusals"
import type { Locale } from "@conusai-kit/i18n"
import { en as keys } from "../i18n/en.js"
export type LedgerMessages = { [K in keyof typeof keys]: string }
/**
* The module's messages, and every Ledger refusal's problem code with the sentence a person reads for it
* (the kit's, in the loader's locale; a revision or idempotency conflict in this module's own words). Plain
* strings, so the loader hands them to the forms, which read a refusal with `serviceNotice`.
*/
export type LedgerLabels = LedgerMessages & { readonly refusals: Readonly<Record<string, string>> }
export function ledgerLabels(t: (key: string) => string, locale: Locale): LedgerLabels {
const messages = Object.fromEntries(Object.keys(keys).map((key) => [key, t(key)])) as LedgerMessages
return {
...messages,
refusals: businessRefusalNotices(locale, {
revision_conflict: messages.revisionConflict,
idempotency_conflict: messages.idempotencyConflict,
}),
}
}
+243
View File
@@ -0,0 +1,243 @@
import { businessServiceNotices } from "@conusai-kit/business/refusals"
import { type Container, notFound, type TransportCaller } from "@conusai-kit/core"
import type { I18n } from "@conusai-kit/i18n"
import { type ProjectPage, projectPage } from "@conusai-kit/ui"
import { Can } from "@conusai-kit/ui/data"
import {
FactList,
Page,
PageHeader,
PageSection,
type ResourceColumn,
type ServiceNotice,
ServiceStatePage,
serviceNotice,
} from "@conusai-kit/ui/patterns"
import * as c from "../contract/index.js"
import { AccountForm, RecordForm, Transition } from "./forms.js"
import { type LedgerLabels, ledgerLabels } from "./labels.js"
import { LedgerList } from "./resource.js"
type Loaded = { labels: LedgerLabels; verification: c.VerificationView } & (
| {
kind: "list"
accounts: { rows: c.AccountView[]; after?: string | undefined }
transactions: { rows: c.TransactionView[]; after?: string | undefined }
}
| {
kind: "account"
account: c.AccountView
balances: { rows: c.BalanceView[]; after?: string | undefined }
entries: { rows: c.EntryView[]; after?: string | undefined }
}
| { kind: "transaction"; transaction: c.TransactionView }
)
type Data = Loaded | { kind: "state"; labels: LedgerLabels; notice: ServiceNotice }
export type LedgerPage = ProjectPage<TransportCaller, I18n, Container, Data>
export const ledgerPage: LedgerPage = projectPage({
async load({ caller, slugs, segments, i18n }): Promise<Data> {
const labels = ledgerLabels((key) => i18n.message(`ledger.${key}`), i18n.locale)
const input = { org: slugs.org }
const [kind, id, ...rest] = segments
if (rest.length || (kind !== undefined && ((kind !== "accounts" && kind !== "transactions") || !id)))
throw notFound()
try {
const verification = await caller.call(c.verify, input)
if (kind === "accounts" && id) {
const [account, balances, entries] = await Promise.all([
caller.call(c.account, { ...input, accountId: id }),
caller.call(c.balances, { ...input, accountId: id }),
caller.call(c.entries, { ...input, accountId: id }),
])
return { kind: "account", labels, verification, account, balances, entries }
}
if (kind === "transactions" && id)
return {
kind: "transaction",
labels,
verification,
transaction: await caller.call(c.transaction, { ...input, transactionId: id }),
}
const [accounts, transactions] = await Promise.all([
caller.call(c.accounts, input),
caller.call(c.transactions, input),
])
return { kind: "list", labels, verification, accounts, transactions }
} catch (error) {
const notice = serviceNotice(error, businessServiceNotices(i18n.locale))
if (!notice) throw error
return { kind: "state", labels, notice }
}
},
View({ slugs, data }) {
if (data.kind === "state") return <ServiceStatePage title={data.labels.title} notice={data.notice} />
const { labels: l, verification } = data
const base = `/${slugs.org}/${slugs.workspace}/${slugs.project}/ledger`
const org = slugs.org
const fenced = verification.drift.length > 0
const entryColumns: ResourceColumn<c.EntryView>[] = [
{ id: "accountId", header: l.account },
{ id: "direction", header: l.normalBalance, cell: (row: c.EntryView) => l[row.direction] },
{ id: "amountMinor", header: l.amountMinor },
{ id: "currency", header: l.currency },
{ id: "transactionId", header: l.transaction },
]
return (
<Page>
<PageHeader
title={l.title}
description={l.intro}
breadcrumb={data.kind === "list" ? undefined : <a href={base}>{l.back}</a>}
/>
<PageSection title={l.verifier}>
<FactList
facts={[
{ label: l.status, value: fenced ? l.drift : l.healthy },
{ label: l.checked, value: String(verification.checkedAccounts) },
]}
/>
</PageSection>
{data.kind === "list" ? (
<>
<PageSection title={l.accounts}>
<LedgerList
seed={data.accounts}
procedure="ledger.accounts.list"
input={{ org }}
rowId={(row) => row.accountId}
href={(row) => `${base}/accounts/${row.accountId}`}
labels={l}
title={l.accounts}
columns={[
{ id: "code", header: l.code },
{ id: "name", header: l.name },
{ id: "currency", header: l.currency },
{ id: "status", header: l.status, cell: (row) => l[row.status] },
]}
/>
</PageSection>
<Can permission="ledger.accounts.manage">
<AccountForm org={org} labels={l} base={base} />
</Can>
<PageSection title={l.transactions}>
<LedgerList
seed={data.transactions}
procedure="ledger.transactions.list"
input={{ org }}
rowId={(row) => row.transactionId}
href={(row) => `${base}/transactions/${row.transactionId}`}
labels={l}
title={l.transactions}
columns={[
{ id: "transactionId", header: l.transaction },
{ id: "description", header: l.description },
{ id: "status", header: l.status, cell: (row) => l[row.status] },
{ id: "effectiveAt", header: l.effectiveAt },
]}
/>
</PageSection>
<Can permission="ledger.transactions.write">
<RecordForm org={org} labels={l} base={base} fenced={fenced} />
</Can>
</>
) : data.kind === "account" ? (
<>
<FactList
facts={[
{ label: l.account, value: data.account.accountId },
{ label: l.code, value: data.account.code },
{ label: l.name, value: data.account.name },
{ label: l.currency, value: data.account.currency },
{ label: l.type, value: l[data.account.type] },
{ label: l.normalBalance, value: l[data.account.normalBalance] },
{ label: l.allowNegative, value: data.account.allowNegative ? l.yes : l.no },
{ label: l.status, value: l[data.account.status] },
{ label: l.revision, value: data.account.revision },
]}
/>
<PageSection title={l.balances}>
<LedgerList
seed={data.balances}
procedure="ledger.balances.list"
input={{ org, accountId: data.account.accountId }}
rowId={(row) => row.accountId}
labels={l}
title={l.balances}
columns={[
{ id: "currency", header: l.currency },
{ id: "posted", header: l.postedBalance },
{ id: "pending", header: l.pendingBalance },
{ id: "available", header: l.availableBalance },
]}
/>
</PageSection>
<PageSection title={l.entries}>
<LedgerList
seed={data.entries}
procedure="ledger.entries.list"
input={{ org, accountId: data.account.accountId }}
rowId={(row) => row.entryId}
href={(row) => `${base}/transactions/${row.transactionId}`}
labels={l}
title={l.entries}
columns={entryColumns}
/>
</PageSection>
<Can permission="ledger.accounts.manage">
<AccountForm org={org} account={data.account} labels={l} base={base} />
</Can>
</>
) : (
<>
<FactList
facts={[
{ label: l.transaction, value: data.transaction.transactionId },
{ label: l.description, value: data.transaction.description },
{ label: l.reference, value: data.transaction.reference },
{ label: l.effectiveAt, value: data.transaction.effectiveAt },
{ label: l.status, value: l[data.transaction.status] },
{ label: l.revision, value: data.transaction.revision },
{ label: l.reverses, value: data.transaction.reverses },
]}
/>
<LedgerList
seed={{ rows: data.transaction.entries }}
procedure="ledger.transactions.get"
input={{ org, transactionId: data.transaction.transactionId }}
rowId={(row) => row.entryId}
labels={l}
title={l.entries}
columns={entryColumns}
/>
{data.transaction.status === "pending" ? (
<Can permission="ledger.transactions.write">
{(["post", "void"] as const).map((action) => (
<Transition
key={action}
org={org}
transaction={data.transaction}
action={action}
labels={l}
base={base}
fenced={fenced}
/>
))}
</Can>
) : data.transaction.status === "posted" ? (
<Can permission="ledger.transactions.reverse">
<Transition
org={org}
transaction={data.transaction}
action="reverse"
labels={l}
base={base}
fenced={fenced}
/>
</Can>
) : null}
</>
)}
</Page>
)
},
})
+67
View File
@@ -0,0 +1,67 @@
"use client"
import { useDataClient } from "@conusai-kit/ui/data"
import { type ResourceColumn, ResourceList } from "@conusai-kit/ui/patterns"
import { Button } from "@conusai-kit/ui/primitives"
import { useState } from "react"
import type { LedgerLabels } from "./labels.js"
/** Cursor reads happen only on user action; no local financial persistence. */
export function LedgerList<T extends object>({
seed,
procedure,
input,
columns,
rowId,
href,
labels,
title,
}: {
seed: { rows: T[]; after?: string | undefined }
procedure: string
input: Record<string, unknown>
columns: ResourceColumn<T>[]
rowId: (row: T) => string
href?: ((row: T) => string) | undefined
labels: LedgerLabels
title: string
}) {
const client = useDataClient()
const [page, setPage] = useState(seed)
const [busy, setBusy] = useState(false)
const [failed, setFailed] = useState(false)
return (
<>
<ResourceList
label={title}
data={page.rows}
columns={columns}
rowId={rowId}
rowHref={href}
rowLabel={rowId}
rowLinkLabel={() => labels.open}
empty={{ title: labels.empty }}
/>
{failed ? <p role="alert">{labels.unavailable}</p> : null}
{page.after ? (
<Button
disabled={busy}
onClick={async () => {
setBusy(true)
setFailed(false)
try {
const next = (await client.call<typeof seed>(procedure, { ...input, after: page.after })).value
setPage({ ...next, rows: [...page.rows, ...next.rows] })
} catch {
setFailed(true)
} finally {
setBusy(false)
}
}}
>
{labels.more}
</Button>
) : null}
</>
)
}
+13
View File
@@ -0,0 +1,13 @@
/** Component-memory only. Identical retries retain their key; every changed submission gets a new one. */
export function retryIdentity() {
let previous: string | undefined
let key: string
return (payload: unknown): string => {
const next = JSON.stringify(payload)
if (next !== previous) {
previous = next
key = crypto.randomUUID()
}
return key
}
}