import { ok, err } from "@tailor-platform/erp-kit/core"; import type { Transaction, Updateable } from "../generated/kysely-tailordb"; import { DepartmentNotFoundError, InvalidNameError, SelfReferenceError, ParentNotFoundError, CircularReferenceError, } from "../lib/errors.generated"; import { run as detectCircularReference } from "../query/detectDepartmentCircularReference"; export type UpdateDepartmentInput = { departmentId: string; name?: string; parentDepartmentId?: string | null; }; /** * Function: updateDepartment * * Updates a department's name or parent department. Validates against * self-reference and circular hierarchies when reparenting. */ export async function run>( db: Transaction, input: UpdateDepartmentInput & Omit, "status">, ) { const { name, parentDepartmentId, ...rest } = input; const customFields = { ...rest } as Record; delete customFields.departmentId; // 1. Find department const existing = await db .selectFrom("Department") .selectAll() .where("id", "=", input.departmentId) .forUpdate() .executeTakeFirst(); if (!existing) { return err(new DepartmentNotFoundError(input.departmentId)); } // 2. Validate name if provided if (name !== undefined && !name?.trim()) { return err(new InvalidNameError(name)); } // 3. Validate parentDepartmentId if provided if (parentDepartmentId !== undefined && parentDepartmentId !== null) { // Self-reference check if (parentDepartmentId === existing.id) { return err(new SelfReferenceError(existing.id)); } // Find parent department const parent = await db .selectFrom("Department") .selectAll() .where("id", "=", parentDepartmentId) .executeTakeFirst(); if (!parent) { return err(new ParentNotFoundError(parentDepartmentId)); } // Verify parent belongs to same company if (parent.companyId !== existing.companyId) { return err(new ParentNotFoundError(parentDepartmentId)); } // Circular reference detection via shared query const circularResult = await detectCircularReference(db, { departmentId: existing.id, newParentId: parentDepartmentId, }); if (circularResult.ok && circularResult.value.isCircular) { return err(new CircularReferenceError(existing.id)); } } // 4. Build update set — strip reserved model columns from customFields const RESERVED_KEYS = new Set([ "id", "code", "companyId", "status", "name", "parentDepartmentId", "createdAt", "updatedAt", ]); const safeCustomFields: Record = {}; for (const [key, value] of Object.entries(customFields)) { if (!RESERVED_KEYS.has(key)) { safeCustomFields[key] = value; } } const updateSet: Updateable<"Department"> = { ...(safeCustomFields as Updateable<"Department">), }; if (name !== undefined) { updateSet.name = name; } if (parentDepartmentId !== undefined) { updateSet.parentDepartmentId = parentDepartmentId; } if (Object.keys(updateSet).length === 0) { return ok({ department: existing }); } // 5. Update department const department = await db .updateTable("Department") .set(updateSet) .where("id", "=", existing.id) .returningAll() .executeTakeFirstOrThrow(); return ok({ department }); }