// 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 } : {}), }) }