feat(constructeurs): introduce constructors management

This commit is contained in:
Matthieu
2025-09-17 15:09:54 +02:00
parent 339f46ec24
commit 83251b532c
13 changed files with 335 additions and 14 deletions

View File

@@ -0,0 +1,78 @@
-- Create constructeurs table
CREATE TABLE "constructeurs" (
"id" TEXT PRIMARY KEY,
"name" TEXT NOT NULL,
"email" TEXT,
"phone" TEXT,
"createdAt" TIMESTAMP NOT NULL DEFAULT NOW(),
"updatedAt" TIMESTAMP NOT NULL DEFAULT NOW()
);
CREATE UNIQUE INDEX "constructeurs_name_unique_idx" ON "constructeurs"(LOWER("name"));
-- Add foreign key columns to machines, composants, pieces
ALTER TABLE "machines" ADD COLUMN "constructeurId" TEXT;
ALTER TABLE "composants" ADD COLUMN "constructeurId" TEXT;
ALTER TABLE "pieces" ADD COLUMN "constructeurId" TEXT;
-- Populate constructeurs from existing data
WITH existing_names AS (
SELECT DISTINCT TRIM(constructeur) AS name FROM "machines" WHERE constructeur IS NOT NULL AND TRIM(constructeur) <> ''
UNION
SELECT DISTINCT TRIM(constructeur) FROM "composants" WHERE constructeur IS NOT NULL AND TRIM(constructeur) <> ''
UNION
SELECT DISTINCT TRIM(constructeur) FROM "pieces" WHERE constructeur IS NOT NULL AND TRIM(constructeur) <> ''
),
prepared AS (
SELECT DISTINCT ON (LOWER(name)) name
FROM existing_names
WHERE name IS NOT NULL AND name <> ''
ORDER BY LOWER(name), name
)
INSERT INTO "constructeurs" ("id", "name")
SELECT substr(md5(random()::text || clock_timestamp()::text), 1, 25), name
FROM prepared
ON CONFLICT (LOWER("name")) DO NOTHING;
-- Link existing records to constructeurs
UPDATE "machines" m
SET "constructeurId" = c.id
FROM "constructeurs" c
WHERE m.constructeur IS NOT NULL
AND TRIM(m.constructeur) <> ''
AND LOWER(c.name) = LOWER(TRIM(m.constructeur));
UPDATE "composants" co
SET "constructeurId" = c.id
FROM "constructeurs" c
WHERE co.constructeur IS NOT NULL
AND TRIM(co.constructeur) <> ''
AND LOWER(c.name) = LOWER(TRIM(co.constructeur));
UPDATE "pieces" p
SET "constructeurId" = c.id
FROM "constructeurs" c
WHERE p.constructeur IS NOT NULL
AND TRIM(p.constructeur) <> ''
AND LOWER(c.name) = LOWER(TRIM(p.constructeur));
-- Drop legacy constructeur columns
ALTER TABLE "machines" DROP COLUMN "constructeur";
ALTER TABLE "composants" DROP COLUMN "constructeur";
ALTER TABLE "pieces" DROP COLUMN "constructeur";
-- Add foreign key constraints
ALTER TABLE "machines"
ADD CONSTRAINT "machines_constructeurId_fkey"
FOREIGN KEY ("constructeurId") REFERENCES "constructeurs"("id")
ON DELETE SET NULL ON UPDATE CASCADE;
ALTER TABLE "composants"
ADD CONSTRAINT "composants_constructeurId_fkey"
FOREIGN KEY ("constructeurId") REFERENCES "constructeurs"("id")
ON DELETE SET NULL ON UPDATE CASCADE;
ALTER TABLE "pieces"
ADD CONSTRAINT "pieces_constructeurId_fkey"
FOREIGN KEY ("constructeurId") REFERENCES "constructeurs"("id")
ON DELETE SET NULL ON UPDATE CASCADE;

View File

@@ -81,7 +81,6 @@ model Machine {
id String @id @default(cuid())
name String
reference String?
constructeur String?
prix Decimal? @db.Decimal(10, 2)
emplacement String?
createdAt DateTime @default(now())
@@ -94,6 +93,9 @@ model Machine {
typeMachineId String?
typeMachine TypeMachine? @relation(fields: [typeMachineId], references: [id])
constructeurId String?
constructeur Constructeur? @relation(fields: [constructeurId], references: [id], onDelete: SetNull)
composants Composant[]
pieces Piece[]
documents Document[] @relation("MachineDocuments")
@@ -106,7 +108,6 @@ model Composant {
id String @id @default(cuid())
name String
reference String?
constructeur String?
prix Decimal? @db.Decimal(10, 2)
emplacement String?
createdAt DateTime @default(now())
@@ -123,6 +124,9 @@ model Composant {
typeComposantId String?
typeComposant TypeComposant? @relation(fields: [typeComposantId], references: [id])
constructeurId String?
constructeur Constructeur? @relation(fields: [constructeurId], references: [id], onDelete: SetNull)
pieces Piece[]
documents Document[] @relation("ComposantDocuments")
customFieldValues CustomFieldValue[] @relation("ComposantCustomFieldValues")
@@ -134,7 +138,6 @@ model Piece {
id String @id @default(cuid())
name String
reference String?
constructeur String?
prix Decimal? @db.Decimal(10, 2)
emplacement String?
createdAt DateTime @default(now())
@@ -150,12 +153,30 @@ model Piece {
typePieceId String?
typePiece TypePiece? @relation(fields: [typePieceId], references: [id])
constructeurId String?
constructeur Constructeur? @relation(fields: [constructeurId], references: [id], onDelete: SetNull)
documents Document[] @relation("PieceDocuments")
customFieldValues CustomFieldValue[] @relation("PieceCustomFieldValues")
@@map("pieces")
}
model Constructeur {
id String @id @default(cuid())
name String @unique
email String?
phone String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
machines Machine[]
composants Composant[]
pieces Piece[]
@@map("constructeurs")
}
model Document {
id String @id @default(cuid())
name String

View File

@@ -10,6 +10,7 @@ import { PiecesModule } from './pieces/pieces.module';
import { DocumentsModule } from './documents/documents.module';
import { TypesModule } from './types/types.module';
import { CustomFieldsModule } from './custom-fields/custom-fields.module';
import { ConstructeursModule } from './constructeurs/constructeurs.module';
@Module({
imports: [
@@ -24,6 +25,7 @@ import { CustomFieldsModule } from './custom-fields/custom-fields.module';
DocumentsModule,
TypesModule,
CustomFieldsModule,
ConstructeursModule,
],
controllers: [AppController],
providers: [AppService],

View File

@@ -13,6 +13,7 @@ export class ComposantsService {
machine: true,
parentComposant: true,
typeComposant: true,
constructeur: true,
sousComposants: {
include: {
typeComposant: true,
@@ -31,6 +32,7 @@ export class ComposantsService {
machine: true,
parentComposant: true,
typeComposant: true,
constructeur: true,
customFieldValues: {
include: {
customField: true,
@@ -51,8 +53,10 @@ export class ComposantsService {
customField: true,
},
},
constructeur: true,
},
},
constructeur: true,
},
},
pieces: {
@@ -62,6 +66,7 @@ export class ComposantsService {
customField: true,
},
},
constructeur: true,
},
},
documents: true,
@@ -76,6 +81,7 @@ export class ComposantsService {
machine: true,
parentComposant: true,
typeComposant: true,
constructeur: true,
customFieldValues: {
include: {
customField: true,
@@ -96,8 +102,10 @@ export class ComposantsService {
customField: true,
},
},
constructeur: true,
},
},
constructeur: true,
},
},
pieces: {
@@ -107,6 +115,7 @@ export class ComposantsService {
customField: true,
},
},
constructeur: true,
},
},
documents: true,
@@ -121,10 +130,12 @@ export class ComposantsService {
machine: true,
parentComposant: true,
typeComposant: true,
constructeur: true,
sousComposants: {
include: {
typeComposant: true,
pieces: true,
constructeur: true,
},
},
pieces: {
@@ -134,6 +145,7 @@ export class ComposantsService {
customField: true,
},
},
constructeur: true,
},
},
customFieldValues: {
@@ -155,6 +167,7 @@ export class ComposantsService {
},
include: {
typeComposant: true,
constructeur: true,
customFieldValues: {
include: {
customField: true,
@@ -163,6 +176,7 @@ export class ComposantsService {
sousComposants: {
include: {
typeComposant: true,
constructeur: true,
customFieldValues: {
include: {
customField: true,
@@ -175,11 +189,13 @@ export class ComposantsService {
customField: true,
},
},
constructeur: true,
},
},
sousComposants: {
include: {
typeComposant: true,
constructeur: true,
customFieldValues: {
include: {
customField: true,
@@ -192,6 +208,7 @@ export class ComposantsService {
customField: true,
},
},
constructeur: true,
},
},
},
@@ -205,6 +222,7 @@ export class ComposantsService {
customField: true,
},
},
constructeur: true,
},
},
},
@@ -221,6 +239,7 @@ export class ComposantsService {
machine: true,
parentComposant: true,
typeComposant: true,
constructeur: true,
customFieldValues: {
include: {
customField: true,
@@ -229,6 +248,7 @@ export class ComposantsService {
sousComposants: {
include: {
typeComposant: true,
constructeur: true,
customFieldValues: {
include: {
customField: true,
@@ -241,6 +261,7 @@ export class ComposantsService {
customField: true,
},
},
constructeur: true,
},
},
},
@@ -252,6 +273,7 @@ export class ComposantsService {
customField: true,
},
},
constructeur: true,
},
},
documents: true,

View File

@@ -0,0 +1,33 @@
import { Controller, Get, Post, Patch, Delete, Param, Body, Query } from '@nestjs/common'
import { ConstructeursService } from './constructeurs.service'
import { CreateConstructeurDto, UpdateConstructeurDto } from '../shared/dto/constructeur.dto'
@Controller('constructeurs')
export class ConstructeursController {
constructor(private readonly constructeursService: ConstructeursService) {}
@Post()
create(@Body() payload: CreateConstructeurDto) {
return this.constructeursService.create(payload)
}
@Get()
findAll(@Query('search') search?: string) {
return this.constructeursService.findAll(search)
}
@Get(':id')
findOne(@Param('id') id: string) {
return this.constructeursService.findOne(id)
}
@Patch(':id')
update(@Param('id') id: string, @Body() payload: UpdateConstructeurDto) {
return this.constructeursService.update(id, payload)
}
@Delete(':id')
remove(@Param('id') id: string) {
return this.constructeursService.remove(id)
}
}

View File

@@ -0,0 +1,10 @@
import { Module } from '@nestjs/common'
import { ConstructeursService } from './constructeurs.service'
import { ConstructeursController } from './constructeurs.controller'
@Module({
controllers: [ConstructeursController],
providers: [ConstructeursService],
exports: [ConstructeursService],
})
export class ConstructeursModule {}

View File

@@ -0,0 +1,64 @@
import { Injectable } from '@nestjs/common'
import { PrismaService } from '../prisma/prisma.service'
import { CreateConstructeurDto, UpdateConstructeurDto } from '../shared/dto/constructeur.dto'
import { Prisma } from '@prisma/client'
@Injectable()
export class ConstructeursService {
constructor(private prisma: PrismaService) {}
private buildSearchWhere(search?: string): Prisma.ConstructeurWhereInput {
if (!search) return {}
const term = search.trim()
if (!term) return {}
return {
OR: [
{ name: { contains: term, mode: 'insensitive' } },
{ email: { contains: term, mode: 'insensitive' } },
{ phone: { contains: term, mode: 'insensitive' } },
],
}
}
async create(data: CreateConstructeurDto) {
return this.prisma.constructeur.create({
data: {
...data,
name: data.name.trim(),
email: data.email?.trim(),
phone: data.phone?.trim(),
},
})
}
async findAll(search?: string) {
return this.prisma.constructeur.findMany({
where: this.buildSearchWhere(search),
orderBy: { name: 'asc' },
})
}
async findOne(id: string) {
return this.prisma.constructeur.findUnique({
where: { id },
})
}
async update(id: string, data: UpdateConstructeurDto) {
return this.prisma.constructeur.update({
where: { id },
data: {
...data,
name: data.name?.trim(),
email: data.email?.trim(),
phone: data.phone?.trim(),
},
})
}
async remove(id: string) {
return this.prisma.constructeur.delete({
where: { id },
})
}
}

View File

@@ -27,6 +27,7 @@ export class MachinesService {
include: {
site: true,
typeMachine: true,
constructeur: true,
},
});
@@ -57,6 +58,7 @@ export class MachinesService {
customFields: true,
},
},
constructeur: true,
composants: {
include: {
typeComposant: true,
@@ -68,8 +70,10 @@ export class MachinesService {
customField: true,
},
},
constructeur: true,
},
},
constructeur: true,
},
},
pieces: {
@@ -79,6 +83,7 @@ export class MachinesService {
customField: true,
},
},
constructeur: true,
},
},
customFieldValues: {
@@ -133,7 +138,7 @@ export class MachinesService {
data: {
name: component.name,
reference: component.reference || '',
constructeur: component.constructeur || '',
constructeurId: await this.resolveConstructeurId(prisma, component.constructeur),
emplacement: component.emplacement || '',
prix: component.prix || null,
machineId,
@@ -202,7 +207,7 @@ export class MachinesService {
data: {
name: piece.name,
reference: piece.reference || '',
constructeur: piece.constructeur || '',
constructeurId: await this.resolveConstructeurId(prisma, piece.constructeur),
emplacement: piece.emplacement || '',
prix: piece.prix || null,
composantId: createdComposant.id,
@@ -245,6 +250,7 @@ export class MachinesService {
data: {
name: piece.name,
machineId,
constructeurId: await this.resolveConstructeurId(prisma, piece.constructeur),
},
});
@@ -312,6 +318,7 @@ export class MachinesService {
customFields: true,
},
},
constructeur: true,
composants: {
include: {
typeComposant: true,
@@ -321,6 +328,7 @@ export class MachinesService {
customField: true,
},
},
constructeur: true,
pieces: {
include: {
customFieldValues: {
@@ -328,6 +336,7 @@ export class MachinesService {
customField: true,
},
},
constructeur: true,
},
},
},
@@ -339,6 +348,7 @@ export class MachinesService {
customField: true,
},
},
constructeur: true,
},
},
customFieldValues: {
@@ -361,6 +371,7 @@ export class MachinesService {
customFields: true,
},
},
constructeur: true,
composants: {
include: {
typeComposant: true,
@@ -370,6 +381,7 @@ export class MachinesService {
customField: true,
},
},
constructeur: true,
pieces: {
include: {
customFieldValues: {
@@ -377,6 +389,7 @@ export class MachinesService {
customField: true,
},
},
constructeur: true,
},
},
},
@@ -388,6 +401,7 @@ export class MachinesService {
customField: true,
},
},
constructeur: true,
},
},
customFieldValues: {
@@ -411,14 +425,34 @@ export class MachinesService {
customFields: true,
},
},
constructeur: true,
composants: {
include: {
typeComposant: true,
sousComposants: true,
pieces: true,
constructeur: true,
pieces: {
include: {
constructeur: true,
customFieldValues: {
include: {
customField: true,
},
},
},
},
},
},
pieces: {
include: {
constructeur: true,
customFieldValues: {
include: {
customField: true,
},
},
},
},
pieces: true,
customFieldValues: {
include: {
customField: true,
@@ -429,6 +463,29 @@ export class MachinesService {
});
}
private async resolveConstructeurId(prisma: any, rawName?: string) {
if (!rawName) return null
const name = String(rawName).trim()
if (!name) return null
const existing = await prisma.constructeur.findFirst({
where: {
name: {
equals: name,
mode: 'insensitive',
},
},
})
if (existing) return existing.id
const created = await prisma.constructeur.create({
data: { name },
})
return created.id
}
async remove(id: string) {
// Vérifier que la machine existe
const machine = await this.prisma.machine.findUnique({

View File

@@ -14,6 +14,7 @@ export class PiecesService {
composant: true,
typePiece: true,
documents: true,
constructeur: true,
},
});
}
@@ -25,6 +26,7 @@ export class PiecesService {
composant: true,
typePiece: true,
documents: true,
constructeur: true,
},
});
}
@@ -37,6 +39,7 @@ export class PiecesService {
composant: true,
typePiece: true,
documents: true,
constructeur: true,
},
});
}
@@ -49,6 +52,7 @@ export class PiecesService {
composant: true,
typePiece: true,
documents: true,
constructeur: true,
},
});
}
@@ -61,6 +65,7 @@ export class PiecesService {
composant: true,
typePiece: true,
documents: true,
constructeur: true,
},
});
}
@@ -74,6 +79,7 @@ export class PiecesService {
composant: true,
typePiece: true,
documents: true,
constructeur: true,
},
});
}

View File

@@ -19,7 +19,7 @@ export class CreateComposantDto {
@IsOptional()
@IsString()
constructeur?: string;
constructeurId?: string;
@IsOptional()
@Transform(({ value }) => value === '' ? null : value)
@@ -46,7 +46,7 @@ export class UpdateComposantDto {
@IsOptional()
@IsString()
constructeur?: string;
constructeurId?: string;
@IsOptional()
@Transform(({ value }) => value === '' ? null : value)

View File

@@ -0,0 +1,28 @@
import { IsEmail, IsOptional, IsString } from 'class-validator'
export class CreateConstructeurDto {
@IsString()
name: string
@IsOptional()
@IsEmail()
email?: string
@IsOptional()
@IsString()
phone?: string
}
export class UpdateConstructeurDto {
@IsOptional()
@IsString()
name?: string
@IsOptional()
@IsEmail()
email?: string
@IsOptional()
@IsString()
phone?: string
}

View File

@@ -1,4 +1,4 @@
import { IsString, IsOptional, IsNumber, IsDecimal } from 'class-validator';
import { IsString, IsOptional, IsDecimal } from 'class-validator';
export class CreateMachineDto {
@IsString()
@@ -13,7 +13,7 @@ export class CreateMachineDto {
@IsOptional()
@IsString()
constructeur?: string;
constructeurId?: string;
@IsOptional()
@IsDecimal()
@@ -39,7 +39,7 @@ export class UpdateMachineDto {
@IsOptional()
@IsString()
constructeur?: string;
constructeurId?: string;
@IsOptional()
@IsDecimal()

View File

@@ -19,7 +19,7 @@ export class CreatePieceDto {
@IsOptional()
@IsString()
constructeur?: string;
constructeurId?: string;
@IsOptional()
@Transform(({ value }) => value === '' ? null : value)
@@ -46,7 +46,7 @@ export class UpdatePieceDto {
@IsOptional()
@IsString()
constructeur?: string;
constructeurId?: string;
@IsOptional()
@Transform(({ value }) => value === '' ? null : value)