From ff278f55490dc61e6df602913021565428c68cda Mon Sep 17 00:00:00 2001 From: Matthieu Date: Thu, 22 Jan 2026 11:48:06 +0100 Subject: [PATCH] fix: correct Product-Constructeur join table orientation The _ProductConstructeurs table was created with wrong column order: - Before: A=product, B=constructeur - After: A=constructeur, B=product (alphabetical order expected by Prisma) This caused Prisma to fail loading constructeurs relations, resulting in empty constructeurs arrays in API responses. Changes: - Added migration to swap A/B columns and recreate foreign keys - Added debug logs in products.service.ts and constructeur-link.util.ts Co-Authored-By: Claude Sonnet 4.5 --- .../migration.sql | 29 +++++++++++++++++++ src/common/utils/constructeur-link.util.ts | 13 ++++++++- src/products/products.service.ts | 15 ++++++++++ 3 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 prisma/migrations/20260122_fix_product_constructeurs_orientation/migration.sql diff --git a/prisma/migrations/20260122_fix_product_constructeurs_orientation/migration.sql b/prisma/migrations/20260122_fix_product_constructeurs_orientation/migration.sql new file mode 100644 index 0000000..fe12805 --- /dev/null +++ b/prisma/migrations/20260122_fix_product_constructeurs_orientation/migration.sql @@ -0,0 +1,29 @@ +-- Fix _ProductConstructeurs table orientation +-- Issue: Table was created with A=product, B=constructeur +-- But Prisma expects alphabetical order: A=constructeur, B=product + +-- Step 1: Save existing data to temp table +CREATE TEMP TABLE _ProductConstructeurs_backup AS +SELECT "A" as old_A, "B" as old_B FROM "_ProductConstructeurs"; + +-- Step 2: Drop foreign key constraints +ALTER TABLE "_ProductConstructeurs" DROP CONSTRAINT IF EXISTS "_ProductConstructeurs_A_fkey"; +ALTER TABLE "_ProductConstructeurs" DROP CONSTRAINT IF EXISTS "_ProductConstructeurs_B_fkey"; + +-- Step 3: Clear the table +TRUNCATE TABLE "_ProductConstructeurs"; + +-- Step 4: Reinsert data with swapped columns (A and B inverted) +INSERT INTO "_ProductConstructeurs" ("A", "B") +SELECT old_B, old_A FROM _ProductConstructeurs_backup; + +-- Step 5: Recreate foreign key constraints with correct orientation +ALTER TABLE "_ProductConstructeurs" + ADD CONSTRAINT "_ProductConstructeurs_A_fkey" + FOREIGN KEY ("A") REFERENCES "constructeurs"("id") + ON DELETE CASCADE ON UPDATE CASCADE; + +ALTER TABLE "_ProductConstructeurs" + ADD CONSTRAINT "_ProductConstructeurs_B_fkey" + FOREIGN KEY ("B") REFERENCES "products"("id") + ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/src/common/utils/constructeur-link.util.ts b/src/common/utils/constructeur-link.util.ts index dad9c67..be7ad05 100644 --- a/src/common/utils/constructeur-link.util.ts +++ b/src/common/utils/constructeur-link.util.ts @@ -168,6 +168,8 @@ export async function syncConstructeurLinks( ), ); let targetConstructeurIds = uniqueConstructeurIds; + console.log('[syncConstructeurLinks] Table:', tableName, 'ParentId:', parentId); + console.log('[syncConstructeurLinks] Unique IDs:', uniqueConstructeurIds); const constructeurDelegate = (executor as any)?.constructeur as | { @@ -183,13 +185,16 @@ export async function syncConstructeurLinks( where: { id: { in: targetConstructeurIds } }, select: { id: true }, }); + console.log('[syncConstructeurLinks] Existing constructeurs in DB:', existing.map(c => c.id)); const existingIds = new Set(existing.map(({ id }) => id)); targetConstructeurIds = targetConstructeurIds.filter((id) => existingIds.has(id), ); + console.log('[syncConstructeurLinks] Filtered target IDs:', targetConstructeurIds); } const orientation = await resolveOrientation(executor, tableName); + console.log('[syncConstructeurLinks] Orientation:', orientation); if (typeof executor.__syncConstructeurLinks === 'function') { await executor.__syncConstructeurLinks( @@ -200,6 +205,7 @@ export async function syncConstructeurLinks( return targetConstructeurIds; } + console.log('[syncConstructeurLinks] Deleting old links...'); await prisma.$executeRaw( Prisma.sql`DELETE FROM ${table} WHERE ${Prisma.raw( `"${orientation.parentColumn}"`, @@ -207,6 +213,7 @@ export async function syncConstructeurLinks( ); if (targetConstructeurIds.length === 0) { + console.log('[syncConstructeurLinks] No IDs to insert, returning empty array'); return []; } @@ -214,6 +221,7 @@ export async function syncConstructeurLinks( (constructeurId) => Prisma.sql`(${parentId}, ${constructeurId})`, ); + console.log('[syncConstructeurLinks] Inserting', targetConstructeurIds.length, 'new links...'); await prisma.$executeRaw( Prisma.sql` INSERT INTO ${table} ( @@ -224,7 +232,10 @@ export async function syncConstructeurLinks( ON CONFLICT DO NOTHING `, ); - return fetchConstructeurIds(executor, tableName, parentId, orientation); + console.log('[syncConstructeurLinks] Links inserted, fetching final IDs...'); + const finalIds = await fetchConstructeurIds(executor, tableName, parentId, orientation); + console.log('[syncConstructeurLinks] Final fetched IDs:', finalIds); + return finalIds; } export async function fetchConstructeurIds( diff --git a/src/products/products.service.ts b/src/products/products.service.ts index b16a8b2..1a02af8 100644 --- a/src/products/products.service.ts +++ b/src/products/products.service.ts @@ -124,22 +124,30 @@ export class ProductsService { const constructeurIds = this.normalizeConstructeurIds( createProductDto.constructeurIds, ); + console.log('[CREATE] Normalized constructeurIds:', constructeurIds); + const resolvedConstructeurIds = await this.resolveExistingConstructeurIds(constructeurIds); + console.log('[CREATE] Resolved constructeurIds:', resolvedConstructeurIds); const created = await this.prisma.product.create({ data, include: PRODUCT_WITH_RELATIONS_INCLUDE, }); + console.log('[CREATE] Product created:', created.id); let syncedConstructeurIds: string[] = []; if (resolvedConstructeurIds.length > 0) { + console.log('[CREATE] Calling syncConstructeurLinks with:', resolvedConstructeurIds); syncedConstructeurIds = await syncConstructeurLinks( this.prisma, '_ProductConstructeurs', created.id, resolvedConstructeurIds, ); + console.log('[CREATE] Synced constructeurIds:', syncedConstructeurIds); + } else { + console.log('[CREATE] No constructeurIds to sync'); } const refreshed = await this.prisma.product.findUnique({ @@ -192,8 +200,10 @@ export class ProductsService { const constructeurIds = this.normalizeConstructeurIds( updateProductDto.constructeurIds, ); + console.log('[UPDATE] Normalized constructeurIds:', constructeurIds); resolvedConstructeurIds = await this.resolveExistingConstructeurIds(constructeurIds); + console.log('[UPDATE] Resolved constructeurIds:', resolvedConstructeurIds); } let syncedConstructeurIds: string[] | undefined; @@ -203,14 +213,19 @@ export class ProductsService { where: { id }, data, }); + console.log('[UPDATE] Product updated:', id); if (resolvedConstructeurIds !== undefined) { + console.log('[UPDATE] Calling syncConstructeurLinks with:', resolvedConstructeurIds); syncedConstructeurIds = await syncConstructeurLinks( tx, '_ProductConstructeurs', id, resolvedConstructeurIds, ); + console.log('[UPDATE] Synced constructeurIds:', syncedConstructeurIds); + } else { + console.log('[UPDATE] No constructeurIds to sync'); } });