import { createContext, DomainError } from "@tailor-platform/erp-kit/app"; import { createResolver, t } from "@tailor-platform/sdk"; import { getDB } from "@/generated/kysely-tailordb"; import { finModulesExport } from "@/modules"; /** * createAccountingPeriod — add an accounting period (会計期間) to a fiscal year. * * Thin wrapper over the native `createAccountingPeriod`. The company is derived * from the fiscal year. Dates must fall within the fiscal year and must not * overlap another OPERATING period. The period is created NEVER_OPENED and must * be opened before journal entries can post to it. */ export default createResolver({ name: "createAccountingPeriod", operation: "mutation", input: { fiscalYearId: t.string().description("Fiscal year the period belongs to"), name: t.string().description("Period name (e.g. 2026-07)"), startDate: t.string().description("Start date (YYYY-MM-DD)"), endDate: t.string().description("End date (YYYY-MM-DD); must be after the start date"), periodType: t.string({ optional: true }).description("OPERATING (default) or ADJUSTMENT"), }, body: async (context) => { const ctx = createContext(context); const db = getDB("main-db"); return db .transaction() .execute(async (trx) => { const fiscalYear = await trx .selectFrom("FiscalYear") .select(["companyId"]) .where("id", "=", context.input.fiscalYearId) .executeTakeFirst(); if (!fiscalYear) { throw new DomainError(`Fiscal year not found: ${context.input.fiscalYearId}`); } const periodType = context.input.periodType === "ADJUSTMENT" ? "ADJUSTMENT" : "OPERATING"; const result = await finModulesExport.commands.createAccountingPeriod( trx, { companyId: fiscalYear.companyId, fiscalYearId: context.input.fiscalYearId, name: context.input.name, startDate: new Date(context.input.startDate), endDate: new Date(context.input.endDate), periodType, }, ctx, ); if (!result.ok) { switch (result.error.code) { case "FINANCIAL_ACCOUNTING_FISCAL_YEAR_NOT_FOUND": throw new DomainError(`Fiscal year not found: ${context.input.fiscalYearId}`); case "FINANCIAL_ACCOUNTING_NAME_REQUIRED": throw new DomainError("Period name must not be empty."); case "FINANCIAL_ACCOUNTING_INVALID_DATE_RANGE": throw new DomainError( "The period dates must be within the fiscal year and the end date after the start date.", ); case "FINANCIAL_ACCOUNTING_DUPLICATE_PERIOD_DATES": throw new DomainError("Another period already covers these exact dates."); case "FINANCIAL_ACCOUNTING_OVERLAPPING_PERIOD": throw new DomainError("The dates overlap an existing operating period."); 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.accountingPeriod.id, status: result.value.accountingPeriod.status, }; }) .catch((err: unknown) => { if (err instanceof DomainError) throw err; throw new Error("Failed to create the accounting period", { cause: err }); }); }, output: t .object({ id: t.string().description("Accounting period ID"), status: t.string().description("Period status (NEVER_OPENED)"), }) .description("CreateAccountingPeriod response"), });