import { z } from "zod";
import { defineContract } from "@boundary/contract";
const schema = z.object({
vendor: z.string(),
invoiceNumber: z.string(),
date: z.string(),
lineItems: z.array(z.object({
description: z.string(),
quantity: z.number(),
unitPrice: z.number(),
amount: z.number(),
})),
subtotal: z.number(),
tax: z.number(),
total: z.number(),
currency: z.string(),
});
const invoiceContract = defineContract({
schema,
rules: [
// invoice must have at least one line item
(invoice) =>
invoice.lineItems.length > 0
|| "invoice must have at least one line item",
// line item amounts must add up to subtotal
(invoice) => {
const sum = invoice.lineItems.reduce((s, i) => s + i.amount, 0);
return Math.abs(sum - invoice.subtotal) < 0.01
|| `line items sum to ${sum}, but subtotal is ${invoice.subtotal}`;
},
// subtotal + tax must equal total
(invoice) =>
Math.abs(invoice.subtotal + invoice.tax - invoice.total) < 0.01
|| `subtotal (${invoice.subtotal}) + tax (${invoice.tax}) = ${invoice.subtotal + invoice.tax}, but total is ${invoice.total}`,
// each line item: quantity × unitPrice must equal amount
(invoice) => {
const bad = invoice.lineItems.find(
(i) => Math.abs(i.quantity * i.unitPrice - i.amount) >= 0.01
);
return !bad
|| `${bad.description}: ${bad.quantity} × ${bad.unitPrice} = ${bad.quantity * bad.unitPrice}, but amount is ${bad.amount}`;
},
],
});