51 lines
2.0 KiB
TypeScript
51 lines
2.0 KiB
TypeScript
// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0
|
|
import { badRequest, conflict } from "@conusai-kit/core"
|
|
import type { InvoiceTerms } from "../contract/index.js"
|
|
import type { InvoicingService } from "./ports.js"
|
|
export const operationKey = (operation: string, requestId: string) => `invoicing:${operation}:${requestId}`
|
|
export function validateTerms(data: InvoiceTerms): InvoiceTerms {
|
|
if (
|
|
data.lines.some((line) => line.net_price.currency !== data.currency) ||
|
|
data.allowances_charges?.some((charge) => charge.amount.currency !== data.currency)
|
|
)
|
|
throw badRequest("Invoice currencies must match.")
|
|
return data
|
|
}
|
|
export async function updateDraft(
|
|
service: InvoicingService,
|
|
input: { invoiceId: string; data: InvoiceTerms; expectedVersion: string; requestId: string },
|
|
) {
|
|
const current = await service.get(input.invoiceId)
|
|
// Prior revisions reach the service's idempotency ledger on response-loss retries.
|
|
if (BigInt(input.expectedVersion) > BigInt(current.revision)) throw conflict("Reload the invoice before editing.")
|
|
return service.update(
|
|
input.invoiceId,
|
|
validateTerms(input.data),
|
|
input.expectedVersion,
|
|
operationKey("update", input.requestId),
|
|
)
|
|
}
|
|
export async function transitionInvoice(
|
|
service: InvoicingService,
|
|
operation: "issue" | "credit" | "void",
|
|
input: {
|
|
invoiceId: string
|
|
expectedVersion: string
|
|
requestId: string
|
|
confirmed: true
|
|
reason?: string
|
|
seriesId?: string
|
|
},
|
|
) {
|
|
if (input.confirmed !== true) throw badRequest("Explicit confirmation is required.")
|
|
// The service owns state/revision, retries, immutable numbering and fiscal retention.
|
|
// No local allocation or predicted revision may substitute for that transaction.
|
|
return service.transition(operation, {
|
|
invoiceId: input.invoiceId,
|
|
expectedVersion: input.expectedVersion,
|
|
key: operationKey(operation, input.requestId),
|
|
...(input.reason ? { reason: input.reason } : {}),
|
|
...(input.seriesId ? { seriesId: input.seriesId } : {}),
|
|
})
|
|
}
|