import { ok, err } from "@tailor-platform/erp-kit/core"; import type { Transaction } from "../generated/kysely-tailordb"; import { ProductNotFoundError, AttributeNotFoundError, ProductArchivedError, AxisStructureLockedError, InvalidValueError, } from "../lib/errors.generated"; export interface AssignAttributeValueToProductInput { productId: string; attributeId: string; valueId: string; } /** * Function: assignAttributeValueToProduct * * Adds an attribute value assignment to a product (multi-value). * Each record is uniquely identified by (productId, attributeId, valueId). * If the exact triple already exists, the operation is a no-op (idempotent). * All attributes are variant axes, so axis locking rules always apply. */ export async function run(db: Transaction, input: AssignAttributeValueToProductInput) { const { productId, attributeId, valueId } = input; // Check product exists const product = await db .selectFrom("Product") .selectAll() .where("id", "=", productId) .executeTakeFirst(); if (!product) { return err(new ProductNotFoundError(productId)); } // Check attribute exists const attribute = await db .selectFrom("ProductAttribute") .select("id") .where("id", "=", attributeId) .executeTakeFirst(); if (!attribute) { return err(new AttributeNotFoundError(attributeId)); } // Validate value: must reference a valid ProductAttributeValue for the attribute const attrValue = await db .selectFrom("ProductAttributeValue") .selectAll() .where("id", "=", valueId) .where("attributeId", "=", attributeId) .executeTakeFirst(); if (!attrValue) { return err(new InvalidValueError(valueId)); } // Read existing assignments (with lock for insert safety) const existingAssignments = await db .selectFrom("ProductAttributeAssignment") .selectAll() .where("productId", "=", productId) .where("attributeId", "=", attributeId) .forUpdate() .execute(); // Idempotent no-op: if exact triple already exists, return existing const exactMatch = existingAssignments.find((a) => a.valueId === valueId); if (exactMatch) { return ok({ productAttributeAssignment: exactMatch }); } // Status-based rules if (product.status === "ARCHIVED") { return err(new ProductArchivedError(productId)); } if (product.status === "ACTIVE") { // ACTIVE: can add new values to already-assigned attribute (additive-only) // but cannot assign a new attribute (structure locked) if (existingAssignments.length === 0) { return err(new AxisStructureLockedError(productId)); } // existingAssignments.length > 0 means attribute is already assigned → additive OK } // Insert new assignment const assignment = await db .insertInto("ProductAttributeAssignment") .values({ productId, attributeId, valueId, }) .returningAll() .executeTakeFirstOrThrow(); return ok({ productAttributeAssignment: assignment }); }