import { createContext, DomainError } from "@tailor-platform/erp-kit/app"; import { createResolver, t } from "@tailor-platform/sdk"; import { getDB } from "@/generated/kysely-tailordb"; import { coaModulesExport } from "@/modules"; // The five GL account classes coa-management supports. const ACCOUNT_TYPES = ["ASSET", "LIABILITY", "EQUITY", "REVENUE", "EXPENSE"] as const; type AccountType = (typeof ACCOUNT_TYPES)[number]; /** * createAccount — add a GL account to a company's chart of accounts. * * The bundled chart of accounts is otherwise seed-only (there is no other way to * add an account to a running workspace), so this resolver lets an operator add * missing accounts — e.g. a Cash / Bank Deposits (ASSET) account for outgoing * payments to credit — without a destructive re-seed. The native command * validates the owning company, requires a non-empty name and a well-formed * code, and rejects a code that already exists in the company. The account is * created ACTIVE. */ export default createResolver({ name: "createAccount", operation: "mutation", input: { companyId: t.string().description("Owning company whose chart of accounts to add to"), code: t.string().description("GL account code, unique within the company (e.g. 1100)"), name: t.string().description("Account name (e.g. Cash)"), accountType: t.string().description(`Account class: ${ACCOUNT_TYPES.join(" | ")}`), }, body: async (context) => { const ctx = createContext(context); const db = getDB("main-db"); const accountType = context.input.accountType as AccountType; if (!ACCOUNT_TYPES.includes(accountType)) { throw new DomainError( `Invalid accountType: ${context.input.accountType}. Expected one of ${ACCOUNT_TYPES.join(", ")}.`, ); } return db .transaction() .execute(async (trx) => { const result = await coaModulesExport.commands.createAccount( trx, { companyId: context.input.companyId, code: context.input.code, name: context.input.name, accountType, }, ctx, ); if (!result.ok) { switch (result.error.code) { case "COA_MANAGEMENT_COMPANY_NOT_FOUND": throw new DomainError(`Company not found: ${context.input.companyId}`); case "COA_MANAGEMENT_COMPANY_INACTIVE": throw new DomainError(`Company is not active: ${context.input.companyId}`); case "COA_MANAGEMENT_DUPLICATE_ACCOUNT_CODE": throw new DomainError( `An account with code ${context.input.code} already exists in this company.`, ); case "COA_MANAGEMENT_NAME_REQUIRED": throw new DomainError("Account name is required."); case "COA_MANAGEMENT_INVALID_ACCOUNT_CODE": throw new DomainError(`Invalid account code: ${context.input.code}.`); 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.account.id, code: result.value.account.code }; }) .catch((err: unknown) => { if (err instanceof DomainError) throw err; throw new Error("Failed to create the account", { cause: err }); }); }, output: t .object({ id: t.string().description("Created account ID"), code: t.string().description("Account code"), }) .description("Created GL account"), });