import { err, ok, type CommandContext } from "@tailor-platform/erp-kit/core"; import type { Transaction } from "../generated/kysely-tailordb"; import { ItemNotFoundError, ItemNotOpenError } from "../lib/errors.generated"; export interface ReorderPipelineItemInput { itemId: string; position: number; } export async function run(db: Transaction, input: ReorderPipelineItemInput, ctx: CommandContext) { void ctx; const item = await db .selectFrom("PipelineItem") .selectAll() .where("id", "=", input.itemId) .forUpdate() .executeTakeFirst(); if (!item) { return err(new ItemNotFoundError(input.itemId)); } if (item.lifecycle !== "OPEN") { return err(new ItemNotOpenError(input.itemId)); } const siblings = await db .selectFrom("PipelineItem") .selectAll() .where("stageId", "=", item.stageId) .orderBy("position", "asc") .execute(); const others = siblings.filter((it) => it.id !== item.id); const insertIndex = Math.max(0, Math.min(input.position - 1, others.length)); const reordered = [...others]; reordered.splice(insertIndex, 0, item); const now = new Date(); for (const [index, current] of reordered.entries()) { const nextPosition = index + 1; if (current.position !== nextPosition) { await db .updateTable("PipelineItem") .set({ position: nextPosition, updatedAt: now }) .where("id", "=", current.id) .execute(); } } // Mirror exactly what was persisted: updatedAt moves only if the row was // actually rewritten (a no-op reorder keeps the stored timestamp). const nextPosition = insertIndex + 1; const moved = item.position !== nextPosition; return ok({ item: { ...item, position: nextPosition, updatedAt: moved ? now : item.updatedAt }, }); }