import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/core"; import { accountLifecycle } from "../db/account.lifecycle.generated"; import type { Transaction } from "../generated/kysely-tailordb"; import { AccountNotFoundError, InvalidStateTransitionError, OpenBalancesExistError, } from "../lib/errors.generated"; export interface DeactivateAccountInput { id: string; preDeactivationHook?: (accountId: string) => Promise; } /** * Function: deactivateAccount * * Transitions a GL account from ACTIVE to INACTIVE status, preventing new * journal postings while preserving history. */ export async function run(db: Transaction, input: DeactivateAccountInput, _ctx: CommandContext) { const { id, preDeactivationHook } = input; // 1. Find account with forUpdate const account = await db .selectFrom("Account") .selectAll() .where("id", "=", id) .forUpdate() .executeTakeFirst(); if (!account) { return err(new AccountNotFoundError(id)); } const nextStatus = accountLifecycle.tryTransition(account.status, "deactivate"); if (!nextStatus) { return err(new InvalidStateTransitionError(id)); } // 3. Pre-deactivation hook (GL balance check) if (preDeactivationHook) { const canDeactivate = await preDeactivationHook(id); if (!canDeactivate) { return err(new OpenBalancesExistError(id)); } } // 4. Update status. const updated = await db .updateTable("Account") .set({ status: nextStatus }) .where("id", "=", id) .returningAll() .executeTakeFirstOrThrow(); return ok({ account: updated }); }