import { createContext, DomainError } from "@tailor-platform/erp-kit/app"; import { createResolver, t } from "@tailor-platform/sdk"; import { getDB } from "@/generated/kysely-tailordb"; import { apModulesExport } from "@/modules"; // Empty string from the settings form means "no override" for that field, i.e. // strict matching — persist it as null so the register-time 3-way match falls // back to an exact comparison. const norm = (v: string | null | undefined): string | null => { if (v == null) return null; const trimmed = v.trim(); return trimmed === "" ? null : trimmed; }; // Upsert the supplier's InvoiceToleranceConfig for a company. The tolerance // config is unique per (company, supplier); the settings panel always submits // all four fields, so this creates the row on first save and patches it // thereafter (a null field clears the override back to strict matching). The // four tolerances relax the unit-price / quantity variance checks that would // otherwise raise a SYSTEM payment hold during AP document registration. export default createResolver({ name: "setInvoiceToleranceConfig", operation: "mutation", input: { companyId: t.string().description("Company the tolerance config applies to"), supplierAccountId: t.string().description("Supplier account the tolerance config applies to"), quantityAbsoluteTolerance: t .string({ optional: true }) .description("Absolute quantity tolerance; empty clears the override (strict matching)"), quantityPercentageTolerance: t .string({ optional: true }) .description("Percentage quantity tolerance as a fraction (0.05 = 5%); empty clears it"), unitPriceAbsoluteTolerance: t .string({ optional: true }) .description("Absolute unit-price tolerance; empty clears the override (strict matching)"), unitPricePercentageTolerance: t .string({ optional: true }) .description("Percentage unit-price tolerance as a fraction (0.05 = 5%); empty clears it"), }, body: async (context) => { const ctx = createContext(context); const db = getDB("main-db"); const { companyId, supplierAccountId } = context.input; const tolerances = { quantityAbsoluteTolerance: norm(context.input.quantityAbsoluteTolerance), quantityPercentageTolerance: norm(context.input.quantityPercentageTolerance), unitPriceAbsoluteTolerance: norm(context.input.unitPriceAbsoluteTolerance), unitPricePercentageTolerance: norm(context.input.unitPricePercentageTolerance), }; return db .transaction() .execute(async (trx) => { // Decide create vs. update by presence (the row is unique per company + // supplier), so the caller has a single "set" mutation regardless of // whether a config already exists. const existing = await trx .selectFrom("InvoiceToleranceConfig") .select(["id"]) .where("companyId", "=", companyId) .where("supplierAccountId", "=", supplierAccountId) .executeTakeFirst(); const result = existing ? await apModulesExport.commands.updateInvoiceToleranceConfig( trx, { companyId, supplierAccountId, patch: tolerances }, ctx, ) : await apModulesExport.commands.createInvoiceToleranceConfig( trx, { companyId, supplierAccountId, ...tolerances }, ctx, ); if (!result.ok) { switch (result.error.code) { case "ACCOUNT_PAYABLE_COMPANY_NOT_FOUND": throw new DomainError(`Company not found: ${companyId}`); case "ACCOUNT_PAYABLE_AP_SUPPLIER_ACCOUNT_NOT_FOUND": throw new DomainError(`Supplier not found: ${supplierAccountId}`); case "ACCOUNT_PAYABLE_AP_INVALID_SUPPLIER_ACCOUNT": throw new DomainError(result.error.message); case "ACCOUNT_PAYABLE_INVALID_TOLERANCE_VALUE": throw new DomainError("Tolerance values must not be negative"); case "ACCOUNT_PAYABLE_INVOICE_TOLERANCE_CONFIG_ALREADY_EXISTS": throw new DomainError( "An invoice tolerance config already exists for this company and supplier", ); case "ACCOUNT_PAYABLE_INVOICE_TOLERANCE_CONFIG_NOT_FOUND": throw new DomainError( "Invoice tolerance config not found for this company and supplier", ); case "UNAUTHENTICATED": throw new DomainError("Authentication is required"); case "INSUFFICIENT_PERMISSION": throw new DomainError("You do not have permission to perform this action"); default: throw result.error satisfies never; } } return { id: result.value.invoiceToleranceConfig.id }; }) .catch((err: unknown) => { if (err instanceof DomainError) throw err; throw new Error("Failed to set the invoice tolerance config", { cause: err }); }); }, output: t .object({ id: t.string().description("Invoice tolerance config ID"), }) .description("Upserted invoice tolerance config"), });