Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 88a9b70d53 | |||
| 550882b803 | |||
| be2f359ab6 | |||
| e8fa5f8bc6 | |||
| 68e2823d64 | |||
| e977bd83bd | |||
| 7fe6d0db4b | |||
| ccd3e38346 | |||
| 1cbf066658 | |||
| b732944f7a | |||
| 9044560833 | |||
|
|
e2c7165c8c | ||
|
|
4bfa21d4b3 |
@@ -1,8 +0,0 @@
|
|||||||
steps:
|
|
||||||
test:
|
|
||||||
image: alpine
|
|
||||||
commands:
|
|
||||||
- echo "Woodpecker CI fonctionne parfaitement !"
|
|
||||||
- uname -a
|
|
||||||
- ls -la
|
|
||||||
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
# Configuration de la base de données
|
# Configuration de la base de données
|
||||||
DATABASE_URL="postgresql://postgres:password@localhost:5432/inventory_db"
|
DATABASE_URL="postgresql://postgres:password@localhost:5432/inventory_db"
|
||||||
|
|
||||||
# Configuration du serveur
|
# Configuration du serveurte
|
||||||
PORT=3000
|
PORT=3000
|
||||||
NODE_ENV=development
|
NODE_ENV=development
|
||||||
|
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
-- Fix _ProductConstructeurs table orientation
|
|
||||||
-- Issue: Table was created with A=product, B=constructeur
|
|
||||||
-- But Prisma expects alphabetical order: A=constructeur, B=product
|
|
||||||
|
|
||||||
-- This migration swaps the A and B columns to fix the orientation
|
|
||||||
|
|
||||||
DO $$
|
|
||||||
DECLARE
|
|
||||||
backup_count INTEGER;
|
|
||||||
BEGIN
|
|
||||||
-- Step 1: Create temp table with swapped data
|
|
||||||
CREATE TEMP TABLE IF NOT EXISTS _ProductConstructeurs_backup AS
|
|
||||||
SELECT "B" as new_A, "A" as new_B FROM "_ProductConstructeurs";
|
|
||||||
|
|
||||||
GET DIAGNOSTICS backup_count = ROW_COUNT;
|
|
||||||
RAISE NOTICE 'Backed up % rows', backup_count;
|
|
||||||
|
|
||||||
-- 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";
|
|
||||||
|
|
||||||
RAISE NOTICE 'Dropped foreign key constraints';
|
|
||||||
|
|
||||||
-- Step 3: Clear the table
|
|
||||||
DELETE FROM "_ProductConstructeurs";
|
|
||||||
|
|
||||||
RAISE NOTICE 'Cleared table';
|
|
||||||
|
|
||||||
-- Step 4: Reinsert data with swapped columns
|
|
||||||
INSERT INTO "_ProductConstructeurs" ("A", "B")
|
|
||||||
SELECT new_A, new_B FROM _ProductConstructeurs_backup;
|
|
||||||
|
|
||||||
GET DIAGNOSTICS backup_count = ROW_COUNT;
|
|
||||||
RAISE NOTICE 'Reinserted % rows with swapped columns', backup_count;
|
|
||||||
|
|
||||||
-- 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;
|
|
||||||
|
|
||||||
RAISE NOTICE 'Recreated foreign key constraints';
|
|
||||||
RAISE NOTICE 'Migration completed successfully';
|
|
||||||
END $$;
|
|
||||||
@@ -12,7 +12,7 @@ const DEFAULT_ORIENTATIONS: Record<string, LinkOrientation> = {
|
|||||||
_MachineConstructeurs: { parentColumn: 'A', constructeurColumn: 'B' },
|
_MachineConstructeurs: { parentColumn: 'A', constructeurColumn: 'B' },
|
||||||
_ComposantConstructeurs: { parentColumn: 'A', constructeurColumn: 'B' },
|
_ComposantConstructeurs: { parentColumn: 'A', constructeurColumn: 'B' },
|
||||||
_PieceConstructeurs: { parentColumn: 'A', constructeurColumn: 'B' },
|
_PieceConstructeurs: { parentColumn: 'A', constructeurColumn: 'B' },
|
||||||
_ProductConstructeurs: { parentColumn: 'B', constructeurColumn: 'A' },
|
_ProductConstructeurs: { parentColumn: 'A', constructeurColumn: 'B' },
|
||||||
};
|
};
|
||||||
|
|
||||||
const sanitizeTableName = (tableName: string): string => {
|
const sanitizeTableName = (tableName: string): string => {
|
||||||
@@ -168,8 +168,6 @@ export async function syncConstructeurLinks(
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
let targetConstructeurIds = uniqueConstructeurIds;
|
let targetConstructeurIds = uniqueConstructeurIds;
|
||||||
console.log('[syncConstructeurLinks] Table:', tableName, 'ParentId:', parentId);
|
|
||||||
console.log('[syncConstructeurLinks] Unique IDs:', uniqueConstructeurIds);
|
|
||||||
|
|
||||||
const constructeurDelegate = (executor as any)?.constructeur as
|
const constructeurDelegate = (executor as any)?.constructeur as
|
||||||
| {
|
| {
|
||||||
@@ -185,16 +183,13 @@ export async function syncConstructeurLinks(
|
|||||||
where: { id: { in: targetConstructeurIds } },
|
where: { id: { in: targetConstructeurIds } },
|
||||||
select: { id: true },
|
select: { id: true },
|
||||||
});
|
});
|
||||||
console.log('[syncConstructeurLinks] Existing constructeurs in DB:', existing.map(c => c.id));
|
|
||||||
const existingIds = new Set(existing.map(({ id }) => id));
|
const existingIds = new Set(existing.map(({ id }) => id));
|
||||||
targetConstructeurIds = targetConstructeurIds.filter((id) =>
|
targetConstructeurIds = targetConstructeurIds.filter((id) =>
|
||||||
existingIds.has(id),
|
existingIds.has(id),
|
||||||
);
|
);
|
||||||
console.log('[syncConstructeurLinks] Filtered target IDs:', targetConstructeurIds);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const orientation = await resolveOrientation(executor, tableName);
|
const orientation = await resolveOrientation(executor, tableName);
|
||||||
console.log('[syncConstructeurLinks] Orientation:', orientation);
|
|
||||||
|
|
||||||
if (typeof executor.__syncConstructeurLinks === 'function') {
|
if (typeof executor.__syncConstructeurLinks === 'function') {
|
||||||
await executor.__syncConstructeurLinks(
|
await executor.__syncConstructeurLinks(
|
||||||
@@ -205,7 +200,6 @@ export async function syncConstructeurLinks(
|
|||||||
return targetConstructeurIds;
|
return targetConstructeurIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('[syncConstructeurLinks] Deleting old links...');
|
|
||||||
await prisma.$executeRaw(
|
await prisma.$executeRaw(
|
||||||
Prisma.sql`DELETE FROM ${table} WHERE ${Prisma.raw(
|
Prisma.sql`DELETE FROM ${table} WHERE ${Prisma.raw(
|
||||||
`"${orientation.parentColumn}"`,
|
`"${orientation.parentColumn}"`,
|
||||||
@@ -213,7 +207,6 @@ export async function syncConstructeurLinks(
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (targetConstructeurIds.length === 0) {
|
if (targetConstructeurIds.length === 0) {
|
||||||
console.log('[syncConstructeurLinks] No IDs to insert, returning empty array');
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,7 +214,6 @@ export async function syncConstructeurLinks(
|
|||||||
(constructeurId) => Prisma.sql`(${parentId}, ${constructeurId})`,
|
(constructeurId) => Prisma.sql`(${parentId}, ${constructeurId})`,
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log('[syncConstructeurLinks] Inserting', targetConstructeurIds.length, 'new links...');
|
|
||||||
await prisma.$executeRaw(
|
await prisma.$executeRaw(
|
||||||
Prisma.sql`
|
Prisma.sql`
|
||||||
INSERT INTO ${table} (
|
INSERT INTO ${table} (
|
||||||
@@ -232,10 +224,7 @@ export async function syncConstructeurLinks(
|
|||||||
ON CONFLICT DO NOTHING
|
ON CONFLICT DO NOTHING
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
console.log('[syncConstructeurLinks] Links inserted, fetching final IDs...');
|
return fetchConstructeurIds(executor, tableName, parentId, orientation);
|
||||||
const finalIds = await fetchConstructeurIds(executor, tableName, parentId, orientation);
|
|
||||||
console.log('[syncConstructeurLinks] Final fetched IDs:', finalIds);
|
|
||||||
return finalIds;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchConstructeurIds(
|
export async function fetchConstructeurIds(
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
import { ConflictException, Injectable } from '@nestjs/common';
|
import { ConflictException, Injectable } from '@nestjs/common';
|
||||||
import { Prisma } from '@prisma/client';
|
import { Prisma } from '@prisma/client';
|
||||||
import { PrismaService } from '../prisma/prisma.service';
|
import { PrismaService } from '../prisma/prisma.service';
|
||||||
import {
|
import { syncConstructeurLinks } from '../common/utils/constructeur-link.util';
|
||||||
fetchConstructeurIds,
|
|
||||||
syncConstructeurLinks,
|
|
||||||
} from '../common/utils/constructeur-link.util';
|
|
||||||
import { CreatePieceDto, UpdatePieceDto } from '../shared/dto/piece.dto';
|
import { CreatePieceDto, UpdatePieceDto } from '../shared/dto/piece.dto';
|
||||||
import { PieceModelStructureSchema } from '../shared/schemas/inventory';
|
import { PieceModelStructureSchema } from '../shared/schemas/inventory';
|
||||||
import type { PieceModelStructure } from '../shared/types/inventory';
|
import type { PieceModelStructure } from '../shared/types/inventory';
|
||||||
@@ -123,39 +120,30 @@ export class PiecesService {
|
|||||||
include: PIECE_WITH_RELATIONS_INCLUDE,
|
include: PIECE_WITH_RELATIONS_INCLUDE,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!refreshed) {
|
if (refreshed && syncedConstructeurIds.length > 0) {
|
||||||
return null;
|
(
|
||||||
|
refreshed as typeof refreshed & { constructeurIds?: string[] }
|
||||||
|
).constructeurIds = [...syncedConstructeurIds];
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapped = await this.mapPiece(refreshed);
|
return refreshed;
|
||||||
if (syncedConstructeurIds.length > 0) {
|
|
||||||
mapped.constructeurIds = [...syncedConstructeurIds];
|
|
||||||
}
|
|
||||||
|
|
||||||
return mapped;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.handlePrismaError(error);
|
this.handlePrismaError(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async findAll() {
|
async findAll() {
|
||||||
const items = await this.prisma.piece.findMany({
|
return this.prisma.piece.findMany({
|
||||||
include: PIECE_WITH_RELATIONS_INCLUDE,
|
include: PIECE_WITH_RELATIONS_INCLUDE,
|
||||||
orderBy: { name: 'asc' },
|
orderBy: { name: 'asc' },
|
||||||
});
|
});
|
||||||
const hydrated = await Promise.all(items.map((piece) => this.mapPiece(piece)));
|
|
||||||
return hydrated;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async findOne(id: string) {
|
async findOne(id: string) {
|
||||||
const piece = await this.prisma.piece.findUnique({
|
return this.prisma.piece.findUnique({
|
||||||
where: { id },
|
where: { id },
|
||||||
include: PIECE_WITH_RELATIONS_INCLUDE,
|
include: PIECE_WITH_RELATIONS_INCLUDE,
|
||||||
});
|
});
|
||||||
if (!piece) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return this.mapPiece(piece);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async update(id: string, updatePieceDto: UpdatePieceDto) {
|
async update(id: string, updatePieceDto: UpdatePieceDto) {
|
||||||
@@ -229,16 +217,13 @@ export class PiecesService {
|
|||||||
include: PIECE_WITH_RELATIONS_INCLUDE,
|
include: PIECE_WITH_RELATIONS_INCLUDE,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!refreshed) {
|
if (refreshed && syncedConstructeurIds) {
|
||||||
return null;
|
(
|
||||||
|
refreshed as typeof refreshed & { constructeurIds?: string[] }
|
||||||
|
).constructeurIds = [...syncedConstructeurIds];
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapped = await this.mapPiece(refreshed);
|
return refreshed;
|
||||||
if (syncedConstructeurIds) {
|
|
||||||
mapped.constructeurIds = [...syncedConstructeurIds];
|
|
||||||
}
|
|
||||||
|
|
||||||
return mapped;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.handlePrismaError(error);
|
this.handlePrismaError(error);
|
||||||
}
|
}
|
||||||
@@ -683,43 +668,6 @@ export class PiecesService {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async mapPiece(piece: any) {
|
|
||||||
const idsFromConstructeurs = Array.isArray(piece.constructeurs)
|
|
||||||
? piece.constructeurs
|
|
||||||
.map((c) => (c && typeof c.id === 'string' ? c.id : null))
|
|
||||||
.filter((id): id is string => Boolean(id))
|
|
||||||
: [];
|
|
||||||
|
|
||||||
const idsFromPayload = Array.isArray(piece.constructeurIds)
|
|
||||||
? piece.constructeurIds
|
|
||||||
.map((value) => (typeof value === 'string' ? value.trim() : ''))
|
|
||||||
.filter((value) => value.length > 0)
|
|
||||||
: [];
|
|
||||||
|
|
||||||
let ids = Array.from(new Set([...idsFromConstructeurs, ...idsFromPayload]));
|
|
||||||
|
|
||||||
if (!ids.length) {
|
|
||||||
ids = await fetchConstructeurIds(
|
|
||||||
this.prisma,
|
|
||||||
'_PieceConstructeurs',
|
|
||||||
piece.id,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let constructeurs = piece.constructeurs;
|
|
||||||
if ((!constructeurs || !constructeurs.length) && ids.length) {
|
|
||||||
constructeurs = await this.prisma.constructeur.findMany({
|
|
||||||
where: { id: { in: ids } },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...piece,
|
|
||||||
constructeurs,
|
|
||||||
constructeurIds: ids,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type PieceTypeWithSkeleton = Prisma.ModelTypeGetPayload<{
|
type PieceTypeWithSkeleton = Prisma.ModelTypeGetPayload<{
|
||||||
|
|||||||
@@ -124,30 +124,22 @@ export class ProductsService {
|
|||||||
const constructeurIds = this.normalizeConstructeurIds(
|
const constructeurIds = this.normalizeConstructeurIds(
|
||||||
createProductDto.constructeurIds,
|
createProductDto.constructeurIds,
|
||||||
);
|
);
|
||||||
console.log('[CREATE] Normalized constructeurIds:', constructeurIds);
|
|
||||||
|
|
||||||
const resolvedConstructeurIds =
|
const resolvedConstructeurIds =
|
||||||
await this.resolveExistingConstructeurIds(constructeurIds);
|
await this.resolveExistingConstructeurIds(constructeurIds);
|
||||||
console.log('[CREATE] Resolved constructeurIds:', resolvedConstructeurIds);
|
|
||||||
|
|
||||||
const created = await this.prisma.product.create({
|
const created = await this.prisma.product.create({
|
||||||
data,
|
data,
|
||||||
include: PRODUCT_WITH_RELATIONS_INCLUDE,
|
include: PRODUCT_WITH_RELATIONS_INCLUDE,
|
||||||
});
|
});
|
||||||
console.log('[CREATE] Product created:', created.id);
|
|
||||||
|
|
||||||
let syncedConstructeurIds: string[] = [];
|
let syncedConstructeurIds: string[] = [];
|
||||||
if (resolvedConstructeurIds.length > 0) {
|
if (resolvedConstructeurIds.length > 0) {
|
||||||
console.log('[CREATE] Calling syncConstructeurLinks with:', resolvedConstructeurIds);
|
|
||||||
syncedConstructeurIds = await syncConstructeurLinks(
|
syncedConstructeurIds = await syncConstructeurLinks(
|
||||||
this.prisma,
|
this.prisma,
|
||||||
'_ProductConstructeurs',
|
'_ProductConstructeurs',
|
||||||
created.id,
|
created.id,
|
||||||
resolvedConstructeurIds,
|
resolvedConstructeurIds,
|
||||||
);
|
);
|
||||||
console.log('[CREATE] Synced constructeurIds:', syncedConstructeurIds);
|
|
||||||
} else {
|
|
||||||
console.log('[CREATE] No constructeurIds to sync');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const refreshed = await this.prisma.product.findUnique({
|
const refreshed = await this.prisma.product.findUnique({
|
||||||
@@ -200,10 +192,8 @@ export class ProductsService {
|
|||||||
const constructeurIds = this.normalizeConstructeurIds(
|
const constructeurIds = this.normalizeConstructeurIds(
|
||||||
updateProductDto.constructeurIds,
|
updateProductDto.constructeurIds,
|
||||||
);
|
);
|
||||||
console.log('[UPDATE] Normalized constructeurIds:', constructeurIds);
|
|
||||||
resolvedConstructeurIds =
|
resolvedConstructeurIds =
|
||||||
await this.resolveExistingConstructeurIds(constructeurIds);
|
await this.resolveExistingConstructeurIds(constructeurIds);
|
||||||
console.log('[UPDATE] Resolved constructeurIds:', resolvedConstructeurIds);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let syncedConstructeurIds: string[] | undefined;
|
let syncedConstructeurIds: string[] | undefined;
|
||||||
@@ -213,19 +203,14 @@ export class ProductsService {
|
|||||||
where: { id },
|
where: { id },
|
||||||
data,
|
data,
|
||||||
});
|
});
|
||||||
console.log('[UPDATE] Product updated:', id);
|
|
||||||
|
|
||||||
if (resolvedConstructeurIds !== undefined) {
|
if (resolvedConstructeurIds !== undefined) {
|
||||||
console.log('[UPDATE] Calling syncConstructeurLinks with:', resolvedConstructeurIds);
|
|
||||||
syncedConstructeurIds = await syncConstructeurLinks(
|
syncedConstructeurIds = await syncConstructeurLinks(
|
||||||
tx,
|
tx,
|
||||||
'_ProductConstructeurs',
|
'_ProductConstructeurs',
|
||||||
id,
|
id,
|
||||||
resolvedConstructeurIds,
|
resolvedConstructeurIds,
|
||||||
);
|
);
|
||||||
console.log('[UPDATE] Synced constructeurIds:', syncedConstructeurIds);
|
|
||||||
} else {
|
|
||||||
console.log('[UPDATE] No constructeurIds to sync');
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -316,57 +301,9 @@ export class ProductsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private mapProduct(product: ProductWithRelations) {
|
private mapProduct(product: ProductWithRelations) {
|
||||||
const constructeurs = Array.isArray(product.constructeurs)
|
|
||||||
? product.constructeurs
|
|
||||||
.map((constructeur) => {
|
|
||||||
if (!constructeur || typeof constructeur !== 'object') {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const { id, name, email, phone, createdAt, updatedAt } =
|
|
||||||
constructeur as {
|
|
||||||
id?: string;
|
|
||||||
name?: string | null;
|
|
||||||
email?: string | null;
|
|
||||||
phone?: string | null;
|
|
||||||
createdAt?: Date;
|
|
||||||
updatedAt?: Date;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!id) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
id,
|
|
||||||
name: name ?? null,
|
|
||||||
email: email ?? null,
|
|
||||||
phone: phone ?? null,
|
|
||||||
createdAt: createdAt ?? null,
|
|
||||||
updatedAt: updatedAt ?? null,
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.filter(
|
|
||||||
(entry): entry is {
|
|
||||||
id: string;
|
|
||||||
name: string | null;
|
|
||||||
email: string | null;
|
|
||||||
phone: string | null;
|
|
||||||
createdAt: Date | null;
|
|
||||||
updatedAt: Date | null;
|
|
||||||
} => Boolean(entry),
|
|
||||||
)
|
|
||||||
: [];
|
|
||||||
|
|
||||||
const constructeurIds = constructeurs.map((item) => item.id);
|
|
||||||
const constructeursLabel = constructeurs
|
|
||||||
.map((item) => (item.name || '').trim())
|
|
||||||
.filter((name) => name.length > 0)
|
|
||||||
.join(', ');
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...product,
|
...product,
|
||||||
constructeurs,
|
constructeurIds: product.constructeurs.map((item) => item.id),
|
||||||
constructeurIds,
|
|
||||||
constructeursLabel: constructeursLabel || null,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user