import { err, ok, type CommandContext } from "@tailor-platform/erp-kit/core"; import type { Transaction } from "../generated/kysely-tailordb"; import { ItemNotFoundError, LabelAlreadyAttachedError, LabelNotFoundError, LabelNotOnItemPipelineError, } from "../lib/errors.generated"; export interface AttachLabelToPipelineItemInput { itemId: string; labelId: string; } const ITEM_LABEL_OWN_KEYS = new Set(["itemId", "labelId"]); export async function run>( db: Transaction, input: AttachLabelToPipelineItemInput & CF, ctx: CommandContext, ) { // Consumer extension fields (declared via defineModule's pipelineItemLabel.fields) // pass through to the insert untouched. const customFields: Record = {}; for (const [key, value] of Object.entries(input as Record)) { if (!ITEM_LABEL_OWN_KEYS.has(key)) { customFields[key] = value; } } const item = await db .selectFrom("PipelineItem") .selectAll() .where("id", "=", input.itemId) .forUpdate() .executeTakeFirst(); if (!item) { return err(new ItemNotFoundError(input.itemId)); } const label = await db .selectFrom("PipelineLabel") .selectAll() .where("id", "=", input.labelId) .executeTakeFirst(); if (!label) { return err(new LabelNotFoundError(input.labelId)); } if (label.pipelineId !== item.pipelineId) { return err(new LabelNotOnItemPipelineError(input.labelId)); } const existing = await db .selectFrom("PipelineItemLabel") .selectAll() .where("itemId", "=", input.itemId) .where("labelId", "=", input.labelId) .executeTakeFirst(); if (existing) { return err(new LabelAlreadyAttachedError(input.labelId)); } const now = new Date(); const pipelineItemLabel = await db .insertInto("PipelineItemLabel") .values({ ...customFields, itemId: input.itemId, labelId: input.labelId, createdAt: now, }) .returningAll() .executeTakeFirstOrThrow(); await db .insertInto("PipelineItemChange") .values({ itemId: input.itemId, changedByUserId: ctx.actorId, changeType: "LABEL", fieldName: "LABEL", prevValue: null, newValue: input.labelId, createdAt: now, }) .execute(); return ok({ pipelineItemLabel }); }