feat: Add Model gestion for piece and component
This commit is contained in:
@@ -13,6 +13,7 @@ import { CustomFieldsModule } from './custom-fields/custom-fields.module';
|
||||
import { ConstructeursModule } from './constructeurs/constructeurs.module';
|
||||
import { ProfilesModule } from './profiles/profiles.module';
|
||||
import { SessionModule } from './session/session.module';
|
||||
import { ModelTypeModule } from './model-type/model-type.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -30,6 +31,7 @@ import { SessionModule } from './session/session.module';
|
||||
ConstructeursModule,
|
||||
ProfilesModule,
|
||||
SessionModule,
|
||||
ModelTypeModule,
|
||||
],
|
||||
controllers: [AppController],
|
||||
providers: [AppService],
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Body,
|
||||
Patch,
|
||||
Param,
|
||||
Delete,
|
||||
} from '@nestjs/common';
|
||||
import { ComposantsService } from './composants.service';
|
||||
import { CreateComposantDto, UpdateComposantDto } from '../shared/dto/composant.dto';
|
||||
import {
|
||||
CreateComposantDto,
|
||||
UpdateComposantDto,
|
||||
} from '../shared/dto/composant.dto';
|
||||
|
||||
@Controller('composants')
|
||||
export class ComposantsController {
|
||||
@@ -32,7 +43,10 @@ export class ComposantsController {
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
update(@Param('id') id: string, @Body() updateComposantDto: UpdateComposantDto) {
|
||||
update(
|
||||
@Param('id') id: string,
|
||||
@Body() updateComposantDto: UpdateComposantDto,
|
||||
) {
|
||||
return this.composantsService.update(id, updateComposantDto);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,6 @@ import { ComposantsService } from './composants.service';
|
||||
|
||||
@Module({
|
||||
controllers: [ComposantsController],
|
||||
providers: [ComposantsService]
|
||||
providers: [ComposantsService],
|
||||
})
|
||||
export class ComposantsModule {}
|
||||
|
||||
@@ -20,7 +20,10 @@ describe('ComposantsService', () => {
|
||||
};
|
||||
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [ComposantsService, { provide: PrismaService, useValue: prisma }],
|
||||
providers: [
|
||||
ComposantsService,
|
||||
{ provide: PrismaService, useValue: prisma },
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<ComposantsService>(ComposantsService);
|
||||
@@ -53,9 +56,9 @@ describe('ComposantsService', () => {
|
||||
await expect(service.create(dto)).resolves.toEqual(created);
|
||||
|
||||
expect(prisma.composant.create).toHaveBeenCalled();
|
||||
expect(
|
||||
prisma.composant.create.mock.calls[0][0].data.typeComposantId,
|
||||
).toBe('type-comp-1');
|
||||
expect(prisma.composant.create.mock.calls[0][0].data.typeComposantId).toBe(
|
||||
'type-comp-1',
|
||||
);
|
||||
});
|
||||
|
||||
it('should refuse creation when requirement does not belong to machine skeleton', async () => {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { CreateComposantDto, UpdateComposantDto } from '../shared/dto/composant.dto';
|
||||
import {
|
||||
CreateComposantDto,
|
||||
UpdateComposantDto,
|
||||
} from '../shared/dto/composant.dto';
|
||||
|
||||
@Injectable()
|
||||
export class ComposantsService {
|
||||
@@ -324,9 +327,9 @@ export class ComposantsService {
|
||||
async findHierarchy(machineId: string) {
|
||||
// Récupérer tous les composants de premier niveau (sans parent)
|
||||
const rootComposants = await this.prisma.composant.findMany({
|
||||
where: {
|
||||
where: {
|
||||
machineId,
|
||||
parentComposantId: null
|
||||
parentComposantId: null,
|
||||
},
|
||||
include: {
|
||||
typeComposant: true,
|
||||
|
||||
@@ -1,6 +1,18 @@
|
||||
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'
|
||||
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 {
|
||||
@@ -8,26 +20,26 @@ export class ConstructeursController {
|
||||
|
||||
@Post()
|
||||
create(@Body() payload: CreateConstructeurDto) {
|
||||
return this.constructeursService.create(payload)
|
||||
return this.constructeursService.create(payload);
|
||||
}
|
||||
|
||||
@Get()
|
||||
findAll(@Query('search') search?: string) {
|
||||
return this.constructeursService.findAll(search)
|
||||
return this.constructeursService.findAll(search);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
findOne(@Param('id') id: string) {
|
||||
return this.constructeursService.findOne(id)
|
||||
return this.constructeursService.findOne(id);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
update(@Param('id') id: string, @Body() payload: UpdateConstructeurDto) {
|
||||
return this.constructeursService.update(id, payload)
|
||||
return this.constructeursService.update(id, payload);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
remove(@Param('id') id: string) {
|
||||
return this.constructeursService.remove(id)
|
||||
return this.constructeursService.remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Module } from '@nestjs/common'
|
||||
import { ConstructeursService } from './constructeurs.service'
|
||||
import { ConstructeursController } from './constructeurs.controller'
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ConstructeursService } from './constructeurs.service';
|
||||
import { ConstructeursController } from './constructeurs.controller';
|
||||
|
||||
@Module({
|
||||
controllers: [ConstructeursController],
|
||||
|
||||
@@ -1,23 +1,26 @@
|
||||
import { Injectable } from '@nestjs/common'
|
||||
import { PrismaService } from '../prisma/prisma.service'
|
||||
import { CreateConstructeurDto, UpdateConstructeurDto } from '../shared/dto/constructeur.dto'
|
||||
import { Prisma } from '@prisma/client'
|
||||
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 {}
|
||||
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) {
|
||||
@@ -28,20 +31,20 @@ export class ConstructeursService {
|
||||
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) {
|
||||
@@ -53,12 +56,12 @@ export class ConstructeursService {
|
||||
email: data.email?.trim(),
|
||||
phone: data.phone?.trim(),
|
||||
},
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
async remove(id: string) {
|
||||
return this.prisma.constructeur.delete({
|
||||
where: { id },
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Body,
|
||||
Patch,
|
||||
Param,
|
||||
Delete,
|
||||
} from '@nestjs/common';
|
||||
import { CustomFieldsService } from './custom-fields.service';
|
||||
import {
|
||||
CreateCustomFieldValueDto,
|
||||
@@ -12,8 +20,12 @@ export class CustomFieldsController {
|
||||
constructor(private readonly customFieldsService: CustomFieldsService) {}
|
||||
|
||||
@Post('values')
|
||||
createCustomFieldValue(@Body() createCustomFieldValueDto: CreateCustomFieldValueDto) {
|
||||
return this.customFieldsService.createCustomFieldValue(createCustomFieldValueDto);
|
||||
createCustomFieldValue(
|
||||
@Body() createCustomFieldValueDto: CreateCustomFieldValueDto,
|
||||
) {
|
||||
return this.customFieldsService.createCustomFieldValue(
|
||||
createCustomFieldValueDto,
|
||||
);
|
||||
}
|
||||
|
||||
@Get('values/:entityType/:entityId')
|
||||
@@ -34,7 +46,10 @@ export class CustomFieldsController {
|
||||
@Param('id') id: string,
|
||||
@Body() updateCustomFieldValueDto: UpdateCustomFieldValueDto,
|
||||
) {
|
||||
return this.customFieldsService.updateCustomFieldValue(id, updateCustomFieldValueDto);
|
||||
return this.customFieldsService.updateCustomFieldValue(
|
||||
id,
|
||||
updateCustomFieldValueDto,
|
||||
);
|
||||
}
|
||||
|
||||
@Delete('values/:id')
|
||||
@@ -51,4 +66,4 @@ export class CustomFieldsController {
|
||||
body.value,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,4 +9,4 @@ import { PrismaModule } from '../prisma/prisma.module';
|
||||
providers: [CustomFieldsService],
|
||||
exports: [CustomFieldsService],
|
||||
})
|
||||
export class CustomFieldsModule {}
|
||||
export class CustomFieldsModule {}
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
|
||||
import {
|
||||
BadRequestException,
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
} from '@nestjs/common';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import {
|
||||
CreateCustomFieldValueDto,
|
||||
@@ -11,7 +15,9 @@ export class CustomFieldsService {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
|
||||
// Créer une valeur de champ personnalisé
|
||||
async createCustomFieldValue(createCustomFieldValueDto: CreateCustomFieldValueDto) {
|
||||
async createCustomFieldValue(
|
||||
createCustomFieldValueDto: CreateCustomFieldValueDto,
|
||||
) {
|
||||
return this.prisma.customFieldValue.create({
|
||||
data: createCustomFieldValueDto,
|
||||
include: {
|
||||
@@ -30,11 +36,16 @@ export class CustomFieldsService {
|
||||
case CustomFieldEntityType.PIECE:
|
||||
return 'pieceId' as const;
|
||||
default:
|
||||
throw new BadRequestException('Type d\'entité de champ personnalisé invalide.');
|
||||
throw new BadRequestException(
|
||||
"Type d'entité de champ personnalisé invalide.",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async resolveEntityContext(entityType: CustomFieldEntityType, entityId: string) {
|
||||
private async resolveEntityContext(
|
||||
entityType: CustomFieldEntityType,
|
||||
entityId: string,
|
||||
) {
|
||||
switch (entityType) {
|
||||
case CustomFieldEntityType.MACHINE: {
|
||||
const machine = await this.prisma.machine.findUnique({
|
||||
@@ -103,7 +114,9 @@ export class CustomFieldsService {
|
||||
};
|
||||
}
|
||||
default:
|
||||
throw new BadRequestException('Type d\'entité de champ personnalisé invalide.');
|
||||
throw new BadRequestException(
|
||||
"Type d'entité de champ personnalisé invalide.",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,7 +148,10 @@ export class CustomFieldsService {
|
||||
}
|
||||
|
||||
// Mettre à jour une valeur de champ personnalisé
|
||||
async updateCustomFieldValue(id: string, updateCustomFieldValueDto: UpdateCustomFieldValueDto) {
|
||||
async updateCustomFieldValue(
|
||||
id: string,
|
||||
updateCustomFieldValueDto: UpdateCustomFieldValueDto,
|
||||
) {
|
||||
return this.prisma.customFieldValue.update({
|
||||
where: { id },
|
||||
data: updateCustomFieldValueDto,
|
||||
@@ -159,10 +175,8 @@ export class CustomFieldsService {
|
||||
entityId: string,
|
||||
value: string,
|
||||
) {
|
||||
const { typeId, customFieldTypeField, valueKey } = await this.resolveEntityContext(
|
||||
entityType,
|
||||
entityId,
|
||||
);
|
||||
const { typeId, customFieldTypeField, valueKey } =
|
||||
await this.resolveEntityContext(entityType, entityId);
|
||||
|
||||
const allowedCustomField = await this.prisma.customField.findFirst({
|
||||
where: {
|
||||
@@ -173,7 +187,7 @@ export class CustomFieldsService {
|
||||
|
||||
if (!allowedCustomField) {
|
||||
throw new BadRequestException(
|
||||
'Le champ personnalisé n\'est pas autorisé pour cette entité.',
|
||||
"Le champ personnalisé n'est pas autorisé pour cette entité.",
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Body,
|
||||
Patch,
|
||||
Param,
|
||||
Delete,
|
||||
} from '@nestjs/common';
|
||||
import { DocumentsService } from './documents.service';
|
||||
import { CreateDocumentDto, UpdateDocumentDto } from '../shared/dto/document.dto';
|
||||
import {
|
||||
CreateDocumentDto,
|
||||
UpdateDocumentDto,
|
||||
} from '../shared/dto/document.dto';
|
||||
|
||||
@Controller('documents')
|
||||
export class DocumentsController {
|
||||
@@ -42,7 +53,10 @@ export class DocumentsController {
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
update(@Param('id') id: string, @Body() updateDocumentDto: UpdateDocumentDto) {
|
||||
update(
|
||||
@Param('id') id: string,
|
||||
@Body() updateDocumentDto: UpdateDocumentDto,
|
||||
) {
|
||||
return this.documentsService.update(id, updateDocumentDto);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,6 @@ import { DocumentsService } from './documents.service';
|
||||
|
||||
@Module({
|
||||
controllers: [DocumentsController],
|
||||
providers: [DocumentsService]
|
||||
providers: [DocumentsService],
|
||||
})
|
||||
export class DocumentsModule {}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { CreateDocumentDto, UpdateDocumentDto } from '../shared/dto/document.dto';
|
||||
import {
|
||||
CreateDocumentDto,
|
||||
UpdateDocumentDto,
|
||||
} from '../shared/dto/document.dto';
|
||||
|
||||
@Injectable()
|
||||
export class DocumentsService {
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Body,
|
||||
Patch,
|
||||
Param,
|
||||
Delete,
|
||||
} from '@nestjs/common';
|
||||
import { MachinesService } from './machines.service';
|
||||
import {
|
||||
CreateMachineDto,
|
||||
|
||||
@@ -4,6 +4,6 @@ import { MachinesService } from './machines.service';
|
||||
|
||||
@Module({
|
||||
controllers: [MachinesController],
|
||||
providers: [MachinesService]
|
||||
providers: [MachinesService],
|
||||
})
|
||||
export class MachinesModule {}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ModelCategory } from '@prisma/client';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import {
|
||||
CreateMachineDto,
|
||||
@@ -90,6 +91,36 @@ const MACHINE_DEFAULT_INCLUDE = {
|
||||
export class MachinesService {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
|
||||
private slugifyName(name: string): string {
|
||||
return name
|
||||
.normalize('NFD')
|
||||
.replace(/[\u0300-\u036f]/g, '')
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, '-')
|
||||
.replace(/^-+|-+$/g, '')
|
||||
.replace(/-+/g, '-');
|
||||
}
|
||||
|
||||
private async generateUniqueComponentTypeCode(
|
||||
prisma: any,
|
||||
name: string,
|
||||
): Promise<string> {
|
||||
const base = this.slugifyName(name) || 'type';
|
||||
let candidate = base;
|
||||
let suffix = 1;
|
||||
|
||||
while (
|
||||
await prisma.modelType.findUnique({
|
||||
where: { code: candidate },
|
||||
select: { id: true },
|
||||
})
|
||||
) {
|
||||
candidate = `${base}-${suffix++}`;
|
||||
}
|
||||
|
||||
return candidate;
|
||||
}
|
||||
|
||||
private async getTypeMachineConfiguration(typeMachineId: string) {
|
||||
const typeMachine = await this.prisma.typeMachine.findUnique({
|
||||
where: { id: typeMachineId },
|
||||
@@ -108,25 +139,40 @@ export class MachinesService {
|
||||
componentSelections: MachineComponentSelectionDto[],
|
||||
pieceSelections: MachinePieceSelectionDto[],
|
||||
) {
|
||||
const componentRequirements = (Array.isArray(typeMachine.componentRequirements)
|
||||
? typeMachine.componentRequirements
|
||||
: []) as any[];
|
||||
const pieceRequirements = (Array.isArray(typeMachine.pieceRequirements)
|
||||
? typeMachine.pieceRequirements
|
||||
: []) as any[];
|
||||
const componentRequirements = (
|
||||
Array.isArray(typeMachine.componentRequirements)
|
||||
? typeMachine.componentRequirements
|
||||
: []
|
||||
) as any[];
|
||||
const pieceRequirements = (
|
||||
Array.isArray(typeMachine.pieceRequirements)
|
||||
? typeMachine.pieceRequirements
|
||||
: []
|
||||
) as any[];
|
||||
|
||||
const componentRequirementMap = new Map(
|
||||
componentRequirements.map((requirement: any) => [requirement.id, requirement]),
|
||||
componentRequirements.map((requirement: any) => [
|
||||
requirement.id,
|
||||
requirement,
|
||||
]),
|
||||
);
|
||||
const pieceRequirementMap = new Map(
|
||||
pieceRequirements.map((requirement: any) => [requirement.id, requirement]),
|
||||
pieceRequirements.map((requirement: any) => [
|
||||
requirement.id,
|
||||
requirement,
|
||||
]),
|
||||
);
|
||||
|
||||
const componentSelectionMap = new Map<string, MachineComponentSelectionDto[]>();
|
||||
const componentSelectionMap = new Map<
|
||||
string,
|
||||
MachineComponentSelectionDto[]
|
||||
>();
|
||||
for (const selection of componentSelections) {
|
||||
const requirement = componentRequirementMap.get(selection.requirementId);
|
||||
if (!requirement) {
|
||||
throw new Error(`Sélection de composant invalide: requirementId=${selection.requirementId}`);
|
||||
throw new Error(
|
||||
`Sélection de composant invalide: requirementId=${selection.requirementId}`,
|
||||
);
|
||||
}
|
||||
if (!componentSelectionMap.has(requirement.id)) {
|
||||
componentSelectionMap.set(requirement.id, []);
|
||||
@@ -138,7 +184,16 @@ export class MachinesService {
|
||||
for (const selection of pieceSelections) {
|
||||
const requirement = pieceRequirementMap.get(selection.requirementId);
|
||||
if (!requirement) {
|
||||
throw new Error(`Sélection de pièce invalide: requirementId=${selection.requirementId}`);
|
||||
throw new Error(
|
||||
`Sélection de pièce invalide: requirementId=${selection.requirementId}`,
|
||||
);
|
||||
}
|
||||
if (!selection.pieceModelId) {
|
||||
throw new Error(
|
||||
`Le groupe de pièces "${
|
||||
requirement.label || requirement.typePiece?.name || requirement.id
|
||||
}" nécessite la sélection d'un modèle de pièce.`,
|
||||
);
|
||||
}
|
||||
if (!pieceSelectionMap.has(requirement.id)) {
|
||||
pieceSelectionMap.set(requirement.id, []);
|
||||
@@ -147,24 +202,36 @@ export class MachinesService {
|
||||
}
|
||||
|
||||
const componentModelIds = Array.from(
|
||||
new Set(componentSelections.map((selection) => selection.componentModelId).filter(Boolean)),
|
||||
new Set(
|
||||
componentSelections
|
||||
.map((selection) => selection.componentModelId)
|
||||
.filter(Boolean),
|
||||
),
|
||||
) as string[];
|
||||
const componentModels = componentModelIds.length
|
||||
? await this.prisma.composantModel.findMany({
|
||||
where: { id: { in: componentModelIds } },
|
||||
})
|
||||
: [];
|
||||
const componentModelMap = new Map(componentModels.map((model) => [model.id, model]));
|
||||
const componentModelMap = new Map(
|
||||
componentModels.map((model) => [model.id, model]),
|
||||
);
|
||||
|
||||
const pieceModelIds = Array.from(
|
||||
new Set(pieceSelections.map((selection) => selection.pieceModelId).filter(Boolean)),
|
||||
new Set(
|
||||
pieceSelections
|
||||
.map((selection) => selection.pieceModelId)
|
||||
.filter(Boolean),
|
||||
),
|
||||
) as string[];
|
||||
const pieceModels = pieceModelIds.length
|
||||
? await this.prisma.pieceModel.findMany({
|
||||
where: { id: { in: pieceModelIds } },
|
||||
})
|
||||
: [];
|
||||
const pieceModelMap = new Map(pieceModels.map((model) => [model.id, model]));
|
||||
const pieceModelMap = new Map(
|
||||
pieceModels.map((model) => [model.id, model]),
|
||||
);
|
||||
|
||||
for (const requirement of componentRequirements) {
|
||||
const selections = componentSelectionMap.get(requirement.id) ?? [];
|
||||
@@ -184,7 +251,9 @@ export class MachinesService {
|
||||
}
|
||||
|
||||
if (!requirement.allowNewModels) {
|
||||
const missingModel = selections.find((selection) => !selection.componentModelId);
|
||||
const missingModel = selections.find(
|
||||
(selection) => !selection.componentModelId,
|
||||
);
|
||||
if (missingModel) {
|
||||
throw new Error(
|
||||
`Le groupe de composants "${requirement.label || requirement.typeComposant?.name || requirement.id}" n'autorise que la sélection de modèles existants.`,
|
||||
@@ -210,14 +279,6 @@ export class MachinesService {
|
||||
);
|
||||
}
|
||||
|
||||
if (!requirement.allowNewModels) {
|
||||
const missingModel = selections.find((selection) => !selection.pieceModelId);
|
||||
if (missingModel) {
|
||||
throw new Error(
|
||||
`Le groupe de pièces "${requirement.label || requirement.typePiece?.name || requirement.id}" n'autorise que la sélection de modèles existants.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const selection of componentSelections) {
|
||||
@@ -226,11 +287,15 @@ export class MachinesService {
|
||||
}
|
||||
const model = componentModelMap.get(selection.componentModelId);
|
||||
if (!model) {
|
||||
throw new Error(`Modèle de composant introuvable: ${selection.componentModelId}`);
|
||||
throw new Error(
|
||||
`Modèle de composant introuvable: ${selection.componentModelId}`,
|
||||
);
|
||||
}
|
||||
const requirement = componentRequirementMap.get(selection.requirementId);
|
||||
if (!requirement) {
|
||||
throw new Error(`Requirement de composant introuvable: ${selection.requirementId}`);
|
||||
throw new Error(
|
||||
`Requirement de composant introuvable: ${selection.requirementId}`,
|
||||
);
|
||||
}
|
||||
if (model.typeComposantId !== requirement.typeComposantId) {
|
||||
throw new Error(
|
||||
@@ -240,16 +305,17 @@ export class MachinesService {
|
||||
}
|
||||
|
||||
for (const selection of pieceSelections) {
|
||||
if (!selection.pieceModelId) {
|
||||
continue;
|
||||
}
|
||||
const model = pieceModelMap.get(selection.pieceModelId);
|
||||
if (!model) {
|
||||
throw new Error(`Modèle de pièce introuvable: ${selection.pieceModelId}`);
|
||||
throw new Error(
|
||||
`Modèle de pièce introuvable: ${selection.pieceModelId}`,
|
||||
);
|
||||
}
|
||||
const requirement = pieceRequirementMap.get(selection.requirementId);
|
||||
if (!requirement) {
|
||||
throw new Error(`Requirement de pièce introuvable: ${selection.requirementId}`);
|
||||
throw new Error(
|
||||
`Requirement de pièce introuvable: ${selection.requirementId}`,
|
||||
);
|
||||
}
|
||||
if (model.typePieceId !== requirement.typePieceId) {
|
||||
throw new Error(
|
||||
@@ -274,24 +340,36 @@ export class MachinesService {
|
||||
} = createMachineDto;
|
||||
|
||||
if (!machineData.typeMachineId) {
|
||||
throw new Error('typeMachineId est requis pour créer une machine à partir d\'un squelette.');
|
||||
throw new Error(
|
||||
"typeMachineId est requis pour créer une machine à partir d'un squelette.",
|
||||
);
|
||||
}
|
||||
|
||||
const typeMachine = await this.getTypeMachineConfiguration(machineData.typeMachineId);
|
||||
const typeMachine = await this.getTypeMachineConfiguration(
|
||||
machineData.typeMachineId,
|
||||
);
|
||||
|
||||
const {
|
||||
componentSelectionMap,
|
||||
pieceSelectionMap,
|
||||
componentModelMap,
|
||||
pieceModelMap,
|
||||
} = await this.buildConfigurationContext(typeMachine, componentSelections, pieceSelections);
|
||||
} = await this.buildConfigurationContext(
|
||||
typeMachine,
|
||||
componentSelections,
|
||||
pieceSelections,
|
||||
);
|
||||
|
||||
const componentRequirements = (Array.isArray(typeMachine.componentRequirements)
|
||||
? typeMachine.componentRequirements
|
||||
: []) as any[];
|
||||
const pieceRequirements = (Array.isArray(typeMachine.pieceRequirements)
|
||||
? typeMachine.pieceRequirements
|
||||
: []) as any[];
|
||||
const componentRequirements = (
|
||||
Array.isArray(typeMachine.componentRequirements)
|
||||
? typeMachine.componentRequirements
|
||||
: []
|
||||
) as any[];
|
||||
const pieceRequirements = (
|
||||
Array.isArray(typeMachine.pieceRequirements)
|
||||
? typeMachine.pieceRequirements
|
||||
: []
|
||||
) as any[];
|
||||
|
||||
return this.prisma.$transaction(async (prisma) => {
|
||||
const machine = await prisma.machine.create({
|
||||
@@ -307,15 +385,27 @@ export class MachinesService {
|
||||
for (const requirement of componentRequirements) {
|
||||
const selections = componentSelectionMap.get(requirement.id) ?? [];
|
||||
for (const selection of selections) {
|
||||
const model = selection.componentModelId ? componentModelMap.get(selection.componentModelId) : undefined;
|
||||
const definition = this.normalizeComponentSelection(selection, requirement, model);
|
||||
await this.createComponentsFromType(prisma, machine.id, [definition]);
|
||||
const model = selection.componentModelId
|
||||
? componentModelMap.get(selection.componentModelId)
|
||||
: undefined;
|
||||
const definition = this.normalizeComponentSelection(
|
||||
selection,
|
||||
requirement,
|
||||
model,
|
||||
);
|
||||
await this.createComponentsFromType(prisma, machine.id, [
|
||||
definition,
|
||||
]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const legacyComponents = (typeMachine as any).components;
|
||||
if (legacyComponents) {
|
||||
await this.createComponentsFromType(prisma, machine.id, legacyComponents);
|
||||
await this.createComponentsFromType(
|
||||
prisma,
|
||||
machine.id,
|
||||
legacyComponents,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,20 +413,36 @@ export class MachinesService {
|
||||
for (const requirement of pieceRequirements) {
|
||||
const selections = pieceSelectionMap.get(requirement.id) ?? [];
|
||||
for (const selection of selections) {
|
||||
const model = selection.pieceModelId ? pieceModelMap.get(selection.pieceModelId) : undefined;
|
||||
const definition = this.normalizePieceSelection(selection, requirement, model);
|
||||
await this.createMachinePiecesFromType(prisma, machine.id, [definition]);
|
||||
const model = selection.pieceModelId
|
||||
? pieceModelMap.get(selection.pieceModelId)
|
||||
: undefined;
|
||||
const definition = this.normalizePieceSelection(
|
||||
selection,
|
||||
requirement,
|
||||
model,
|
||||
);
|
||||
await this.createMachinePiecesFromType(prisma, machine.id, [
|
||||
definition,
|
||||
]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const legacyPieces = (typeMachine as any).machinePieces;
|
||||
if (legacyPieces) {
|
||||
await this.createMachinePiecesFromType(prisma, machine.id, legacyPieces);
|
||||
await this.createMachinePiecesFromType(
|
||||
prisma,
|
||||
machine.id,
|
||||
legacyPieces,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (typeMachine.customFields && typeMachine.customFields.length > 0) {
|
||||
await this.createMachineCustomFieldsFromType(prisma, machine.id, typeMachine.customFields);
|
||||
await this.createMachineCustomFieldsFromType(
|
||||
prisma,
|
||||
machine.id,
|
||||
typeMachine.customFields,
|
||||
);
|
||||
}
|
||||
|
||||
return prisma.machine.findUnique({
|
||||
@@ -371,28 +477,43 @@ export class MachinesService {
|
||||
requirement: any,
|
||||
model?: any,
|
||||
): any {
|
||||
const baseDefinition = selection.definition ?? (model?.structure ?? {});
|
||||
const baseDefinition = selection.definition ?? model?.structure ?? {};
|
||||
const definition = this.cloneStructure(baseDefinition);
|
||||
const prepared: any = definition && typeof definition === 'object' && !Array.isArray(definition) ? definition : {};
|
||||
const prepared: any =
|
||||
definition && typeof definition === 'object' && !Array.isArray(definition)
|
||||
? definition
|
||||
: {};
|
||||
|
||||
prepared.name = prepared.name || model?.name || requirement?.typeComposant?.name || 'Composant';
|
||||
prepared.reference = prepared.reference ?? model?.structure?.reference ?? '';
|
||||
prepared.emplacement = prepared.emplacement ?? model?.structure?.emplacement ?? '';
|
||||
prepared.name =
|
||||
prepared.name ||
|
||||
model?.name ||
|
||||
requirement?.typeComposant?.name ||
|
||||
'Composant';
|
||||
prepared.reference =
|
||||
prepared.reference ?? model?.structure?.reference ?? '';
|
||||
prepared.emplacement =
|
||||
prepared.emplacement ?? model?.structure?.emplacement ?? '';
|
||||
prepared.prix = prepared.prix ?? model?.structure?.prix ?? null;
|
||||
|
||||
prepared.customFields = Array.isArray(prepared.customFields) ? prepared.customFields : [];
|
||||
prepared.customFields = Array.isArray(prepared.customFields)
|
||||
? prepared.customFields
|
||||
: [];
|
||||
prepared.pieces = Array.isArray(prepared.pieces)
|
||||
? prepared.pieces
|
||||
: prepared.pieces
|
||||
? [prepared.pieces]
|
||||
: [];
|
||||
? [prepared.pieces]
|
||||
: [];
|
||||
prepared.subComponents = Array.isArray(prepared.subComponents)
|
||||
? prepared.subComponents
|
||||
: prepared.subComponents
|
||||
? [prepared.subComponents]
|
||||
: [];
|
||||
? [prepared.subComponents]
|
||||
: [];
|
||||
|
||||
prepared.typeComposantId = prepared.typeComposantId || requirement?.typeComposantId || model?.typeComposantId || null;
|
||||
prepared.typeComposantId =
|
||||
prepared.typeComposantId ||
|
||||
requirement?.typeComposantId ||
|
||||
model?.typeComposantId ||
|
||||
null;
|
||||
prepared.__componentModelId = selection.componentModelId ?? null;
|
||||
prepared.__requirementId = requirement?.id ?? null;
|
||||
|
||||
@@ -404,43 +525,80 @@ export class MachinesService {
|
||||
requirement: any,
|
||||
model?: any,
|
||||
): any {
|
||||
const baseDefinition = selection.definition ?? (model?.structure ?? {});
|
||||
const definition = this.cloneStructure(baseDefinition);
|
||||
const prepared: any = definition && typeof definition === 'object' && !Array.isArray(definition) ? definition : {};
|
||||
if (!model) {
|
||||
throw new Error(
|
||||
`Modèle de pièce introuvable: ${selection.pieceModelId}`,
|
||||
);
|
||||
}
|
||||
|
||||
prepared.name = prepared.name || model?.name || requirement?.typePiece?.name || 'Pièce';
|
||||
prepared.customFields = Array.isArray(prepared.customFields) ? prepared.customFields : [];
|
||||
prepared.typePieceId = prepared.typePieceId || requirement?.typePieceId || model?.typePieceId || null;
|
||||
prepared.__pieceModelId = selection.pieceModelId ?? null;
|
||||
const baseDefinition = model.structure ?? {};
|
||||
const definition = this.cloneStructure(baseDefinition);
|
||||
const prepared: any =
|
||||
definition && typeof definition === 'object' && !Array.isArray(definition)
|
||||
? definition
|
||||
: {};
|
||||
|
||||
prepared.name = prepared.name || model.name || 'Pièce';
|
||||
prepared.customFields = Array.isArray(prepared.customFields)
|
||||
? prepared.customFields
|
||||
: [];
|
||||
prepared.typePieceId =
|
||||
prepared.typePieceId ||
|
||||
model.typePieceId ||
|
||||
requirement?.typePieceId ||
|
||||
null;
|
||||
prepared.__pieceModelId = selection.pieceModelId;
|
||||
prepared.__requirementId = requirement?.id ?? null;
|
||||
|
||||
return prepared;
|
||||
}
|
||||
|
||||
private async createComponentsFromType(prisma: any, machineId: string, components: any[], parentComposantId?: string) {
|
||||
private async createComponentsFromType(
|
||||
prisma: any,
|
||||
machineId: string,
|
||||
components: any[],
|
||||
parentComposantId?: string,
|
||||
) {
|
||||
for (const component of components) {
|
||||
if (!component || !component.name) continue;
|
||||
|
||||
const customFields = Array.isArray(component.customFields) ? component.customFields : [];
|
||||
const componentPieces = Array.isArray(component.pieces) ? component.pieces : [];
|
||||
const subComponents = Array.isArray(component.subComponents) ? component.subComponents : [];
|
||||
const customFields = Array.isArray(component.customFields)
|
||||
? component.customFields
|
||||
: [];
|
||||
const componentPieces = Array.isArray(component.pieces)
|
||||
? component.pieces
|
||||
: [];
|
||||
const subComponents = Array.isArray(component.subComponents)
|
||||
? component.subComponents
|
||||
: [];
|
||||
|
||||
const componentModelId = component.__componentModelId ?? null;
|
||||
const requirementId = component.__requirementId ?? null;
|
||||
const providedTypeComposantId = component.typeComposantId
|
||||
?? (component.typeComposant && component.typeComposant.id ? component.typeComposant.id : null);
|
||||
const providedTypeComposantId =
|
||||
component.typeComposantId ??
|
||||
(component.typeComposant && component.typeComposant.id
|
||||
? component.typeComposant.id
|
||||
: null);
|
||||
|
||||
let typeComposantId: string | null = providedTypeComposantId ?? null;
|
||||
|
||||
if (!typeComposantId && customFields.length > 0) {
|
||||
let typeComposant = await prisma.typeComposant.findFirst({
|
||||
where: { name: component.name },
|
||||
let typeComposant = await prisma.modelType.findFirst({
|
||||
where: {
|
||||
name: component.name,
|
||||
category: ModelCategory.COMPONENT,
|
||||
},
|
||||
});
|
||||
|
||||
if (!typeComposant) {
|
||||
typeComposant = await prisma.typeComposant.create({
|
||||
typeComposant = await prisma.modelType.create({
|
||||
data: {
|
||||
name: component.name,
|
||||
code: await this.generateUniqueComponentTypeCode(
|
||||
prisma,
|
||||
component.name,
|
||||
),
|
||||
category: ModelCategory.COMPONENT,
|
||||
description: component.description || '',
|
||||
},
|
||||
});
|
||||
@@ -466,7 +624,10 @@ export class MachinesService {
|
||||
data: {
|
||||
name: component.name,
|
||||
reference: component.reference || '',
|
||||
constructeurId: await this.resolveConstructeurId(prisma, component.constructeur),
|
||||
constructeurId: await this.resolveConstructeurId(
|
||||
prisma,
|
||||
component.constructeur,
|
||||
),
|
||||
emplacement: component.emplacement || '',
|
||||
prix: component.prix ?? null,
|
||||
machineId,
|
||||
@@ -483,7 +644,9 @@ export class MachinesService {
|
||||
});
|
||||
|
||||
for (const customField of typeCustomFields) {
|
||||
const defaultValue = customFields.find((cf) => cf.name === customField.name)?.defaultValue || '';
|
||||
const defaultValue =
|
||||
customFields.find((cf) => cf.name === customField.name)
|
||||
?.defaultValue || '';
|
||||
await prisma.customFieldValue.create({
|
||||
data: {
|
||||
value: defaultValue,
|
||||
@@ -497,11 +660,14 @@ export class MachinesService {
|
||||
for (const piece of componentPieces) {
|
||||
if (!piece || !piece.name) continue;
|
||||
|
||||
const pieceCustomFields = Array.isArray(piece.customFields) ? piece.customFields : [];
|
||||
const pieceCustomFields = Array.isArray(piece.customFields)
|
||||
? piece.customFields
|
||||
: [];
|
||||
const pieceModelId = piece.__pieceModelId ?? null;
|
||||
const pieceRequirementId = piece.__requirementId ?? null;
|
||||
const providedTypePieceId = piece.typePieceId
|
||||
?? (piece.typePiece && piece.typePiece.id ? piece.typePiece.id : null);
|
||||
const providedTypePieceId =
|
||||
piece.typePieceId ??
|
||||
(piece.typePiece && piece.typePiece.id ? piece.typePiece.id : null);
|
||||
|
||||
let typePieceId: string | null = providedTypePieceId ?? null;
|
||||
|
||||
@@ -539,7 +705,10 @@ export class MachinesService {
|
||||
data: {
|
||||
name: piece.name,
|
||||
reference: piece.reference || '',
|
||||
constructeurId: await this.resolveConstructeurId(prisma, piece.constructeur),
|
||||
constructeurId: await this.resolveConstructeurId(
|
||||
prisma,
|
||||
piece.constructeur,
|
||||
),
|
||||
emplacement: piece.emplacement || '',
|
||||
prix: piece.prix ?? null,
|
||||
composantId: createdComposant.id,
|
||||
@@ -555,7 +724,9 @@ export class MachinesService {
|
||||
});
|
||||
|
||||
for (const customField of typePieceCustomFields) {
|
||||
const defaultValue = pieceCustomFields.find((cf) => cf.name === customField.name)?.defaultValue || '';
|
||||
const defaultValue =
|
||||
pieceCustomFields.find((cf) => cf.name === customField.name)
|
||||
?.defaultValue || '';
|
||||
await prisma.customFieldValue.create({
|
||||
data: {
|
||||
value: defaultValue,
|
||||
@@ -568,20 +739,32 @@ export class MachinesService {
|
||||
}
|
||||
|
||||
if (subComponents.length > 0) {
|
||||
await this.createComponentsFromType(prisma, machineId, subComponents, createdComposant.id);
|
||||
await this.createComponentsFromType(
|
||||
prisma,
|
||||
machineId,
|
||||
subComponents,
|
||||
createdComposant.id,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async createMachinePiecesFromType(prisma: any, machineId: string, machinePieces: any[]) {
|
||||
private async createMachinePiecesFromType(
|
||||
prisma: any,
|
||||
machineId: string,
|
||||
machinePieces: any[],
|
||||
) {
|
||||
for (const piece of machinePieces) {
|
||||
if (!piece || !piece.name) continue;
|
||||
|
||||
const customFields = Array.isArray(piece.customFields) ? piece.customFields : [];
|
||||
const customFields = Array.isArray(piece.customFields)
|
||||
? piece.customFields
|
||||
: [];
|
||||
const pieceModelId = piece.__pieceModelId ?? null;
|
||||
const requirementId = piece.__requirementId ?? null;
|
||||
const providedTypePieceId = piece.typePieceId
|
||||
?? (piece.typePiece && piece.typePiece.id ? piece.typePiece.id : null);
|
||||
const providedTypePieceId =
|
||||
piece.typePieceId ??
|
||||
(piece.typePiece && piece.typePiece.id ? piece.typePiece.id : null);
|
||||
|
||||
let typePieceId: string | null = providedTypePieceId ?? null;
|
||||
|
||||
@@ -619,7 +802,10 @@ export class MachinesService {
|
||||
data: {
|
||||
name: piece.name,
|
||||
reference: piece.reference || '',
|
||||
constructeurId: await this.resolveConstructeurId(prisma, piece.constructeur),
|
||||
constructeurId: await this.resolveConstructeurId(
|
||||
prisma,
|
||||
piece.constructeur,
|
||||
),
|
||||
emplacement: piece.emplacement || '',
|
||||
prix: piece.prix ?? null,
|
||||
machineId,
|
||||
@@ -635,7 +821,9 @@ export class MachinesService {
|
||||
});
|
||||
|
||||
for (const customField of typePieceCustomFields) {
|
||||
const defaultValue = customFields.find((cf) => cf.name === customField.name)?.defaultValue || '';
|
||||
const defaultValue =
|
||||
customFields.find((cf) => cf.name === customField.name)
|
||||
?.defaultValue || '';
|
||||
await prisma.customFieldValue.create({
|
||||
data: {
|
||||
value: defaultValue,
|
||||
@@ -669,10 +857,14 @@ export class MachinesService {
|
||||
}
|
||||
}
|
||||
|
||||
private async createMachineCustomFieldsFromType(prisma: any, machineId: string, machineCustomFields: any[]) {
|
||||
private async createMachineCustomFieldsFromType(
|
||||
prisma: any,
|
||||
machineId: string,
|
||||
machineCustomFields: any[],
|
||||
) {
|
||||
for (const customField of machineCustomFields) {
|
||||
if (!customField || !customField.name) continue;
|
||||
|
||||
|
||||
// Créer le champ personnalisé pour la machine
|
||||
const createdCustomField = await prisma.customField.create({
|
||||
data: {
|
||||
@@ -710,10 +902,8 @@ export class MachinesService {
|
||||
}
|
||||
|
||||
async reconfigure(id: string, reconfigureMachineDto: ReconfigureMachineDto) {
|
||||
const {
|
||||
componentSelections = [],
|
||||
pieceSelections = [],
|
||||
} = reconfigureMachineDto;
|
||||
const { componentSelections = [], pieceSelections = [] } =
|
||||
reconfigureMachineDto;
|
||||
|
||||
const machine = await this.prisma.machine.findUnique({
|
||||
where: { id },
|
||||
@@ -729,7 +919,9 @@ export class MachinesService {
|
||||
}
|
||||
|
||||
if (!machine.typeMachineId || !machine.typeMachine) {
|
||||
throw new Error('Impossible de reconfigurer une machine sans type de machine associé.');
|
||||
throw new Error(
|
||||
'Impossible de reconfigurer une machine sans type de machine associé.',
|
||||
);
|
||||
}
|
||||
|
||||
const typeMachine = machine.typeMachine;
|
||||
@@ -739,14 +931,22 @@ export class MachinesService {
|
||||
pieceSelectionMap,
|
||||
componentModelMap,
|
||||
pieceModelMap,
|
||||
} = await this.buildConfigurationContext(typeMachine, componentSelections, pieceSelections);
|
||||
} = await this.buildConfigurationContext(
|
||||
typeMachine,
|
||||
componentSelections,
|
||||
pieceSelections,
|
||||
);
|
||||
|
||||
const componentRequirements = (Array.isArray(typeMachine.componentRequirements)
|
||||
? typeMachine.componentRequirements
|
||||
: []) as any[];
|
||||
const pieceRequirements = (Array.isArray(typeMachine.pieceRequirements)
|
||||
? typeMachine.pieceRequirements
|
||||
: []) as any[];
|
||||
const componentRequirements = (
|
||||
Array.isArray(typeMachine.componentRequirements)
|
||||
? typeMachine.componentRequirements
|
||||
: []
|
||||
) as any[];
|
||||
const pieceRequirements = (
|
||||
Array.isArray(typeMachine.pieceRequirements)
|
||||
? typeMachine.pieceRequirements
|
||||
: []
|
||||
) as any[];
|
||||
|
||||
return this.prisma.$transaction(async (prisma) => {
|
||||
await prisma.customFieldValue.deleteMany({
|
||||
@@ -797,7 +997,11 @@ export class MachinesService {
|
||||
const model = selection.componentModelId
|
||||
? componentModelMap.get(selection.componentModelId)
|
||||
: undefined;
|
||||
const definition = this.normalizeComponentSelection(selection, requirement, model);
|
||||
const definition = this.normalizeComponentSelection(
|
||||
selection,
|
||||
requirement,
|
||||
model,
|
||||
);
|
||||
await this.createComponentsFromType(prisma, id, [definition]);
|
||||
}
|
||||
}
|
||||
@@ -815,7 +1019,11 @@ export class MachinesService {
|
||||
const model = selection.pieceModelId
|
||||
? pieceModelMap.get(selection.pieceModelId)
|
||||
: undefined;
|
||||
const definition = this.normalizePieceSelection(selection, requirement, model);
|
||||
const definition = this.normalizePieceSelection(
|
||||
selection,
|
||||
requirement,
|
||||
model,
|
||||
);
|
||||
await this.createMachinePiecesFromType(prisma, id, [definition]);
|
||||
}
|
||||
}
|
||||
@@ -842,9 +1050,9 @@ export class MachinesService {
|
||||
}
|
||||
|
||||
private async resolveConstructeurId(prisma: any, rawName?: string) {
|
||||
if (!rawName) return null
|
||||
const name = String(rawName).trim()
|
||||
if (!name) return null
|
||||
if (!rawName) return null;
|
||||
const name = String(rawName).trim();
|
||||
if (!name) return null;
|
||||
|
||||
const existing = await prisma.constructeur.findFirst({
|
||||
where: {
|
||||
@@ -853,15 +1061,15 @@ export class MachinesService {
|
||||
mode: 'insensitive',
|
||||
},
|
||||
},
|
||||
})
|
||||
});
|
||||
|
||||
if (existing) return existing.id
|
||||
if (existing) return existing.id;
|
||||
|
||||
const created = await prisma.constructeur.create({
|
||||
data: { name },
|
||||
})
|
||||
});
|
||||
|
||||
return created.id
|
||||
return created.id;
|
||||
}
|
||||
|
||||
async remove(id: string) {
|
||||
@@ -983,17 +1191,31 @@ export class MachinesService {
|
||||
|
||||
// Traiter les composants existants
|
||||
for (const component of machine.composants) {
|
||||
const typeComponent = components.find((c: any) => c.name === component.name);
|
||||
if (typeComponent && typeComponent.customFields && typeComponent.customFields.length > 0) {
|
||||
const typeComponent = components.find(
|
||||
(c: any) => c.name === component.name,
|
||||
);
|
||||
if (
|
||||
typeComponent &&
|
||||
typeComponent.customFields &&
|
||||
typeComponent.customFields.length > 0
|
||||
) {
|
||||
// Créer le type de composant s'il n'existe pas
|
||||
let typeComposant = await this.prisma.typeComposant.findFirst({
|
||||
where: { name: component.name },
|
||||
let typeComposant = await this.prisma.modelType.findFirst({
|
||||
where: {
|
||||
name: component.name,
|
||||
category: ModelCategory.COMPONENT,
|
||||
},
|
||||
});
|
||||
|
||||
if (!typeComposant) {
|
||||
typeComposant = await this.prisma.typeComposant.create({
|
||||
typeComposant = await this.prisma.modelType.create({
|
||||
data: {
|
||||
name: component.name,
|
||||
code: await this.generateUniqueComponentTypeCode(
|
||||
this.prisma,
|
||||
component.name,
|
||||
),
|
||||
category: ModelCategory.COMPONENT,
|
||||
description: typeComponent.description || '',
|
||||
},
|
||||
});
|
||||
@@ -1042,7 +1264,10 @@ export class MachinesService {
|
||||
});
|
||||
|
||||
if (!existingValue) {
|
||||
const defaultValue = typeComponent.customFields.find((cf: any) => cf.name === customField.name)?.defaultValue || '';
|
||||
const defaultValue =
|
||||
typeComponent.customFields.find(
|
||||
(cf: any) => cf.name === customField.name,
|
||||
)?.defaultValue || '';
|
||||
await this.prisma.customFieldValue.create({
|
||||
data: {
|
||||
value: defaultValue,
|
||||
@@ -1055,18 +1280,33 @@ export class MachinesService {
|
||||
|
||||
// Traiter les pièces du composant
|
||||
for (const piece of component.pieces) {
|
||||
const typePiece = typeComponent.pieces?.find((p: any) => p.name === piece.name);
|
||||
if (typePiece && typePiece.customFields && typePiece.customFields.length > 0) {
|
||||
const typePiece = typeComponent.pieces?.find(
|
||||
(p: any) => p.name === piece.name,
|
||||
);
|
||||
if (
|
||||
typePiece &&
|
||||
typePiece.customFields &&
|
||||
typePiece.customFields.length > 0
|
||||
) {
|
||||
// Créer le type de pièce s'il n'existe pas
|
||||
let typePieceEntity = await this.prisma.typePiece.findFirst({
|
||||
where: { name: piece.name },
|
||||
let typePieceEntity = await this.prisma.modelType.findFirst({
|
||||
where: {
|
||||
name: piece.name,
|
||||
category: ModelCategory.PIECE,
|
||||
},
|
||||
});
|
||||
|
||||
if (!typePieceEntity) {
|
||||
typePieceEntity = await this.prisma.typePiece.create({
|
||||
typePieceEntity = await this.prisma.modelType.create({
|
||||
data: {
|
||||
name: piece.name,
|
||||
code: await this.generateUniqueComponentTypeCode(
|
||||
this.prisma,
|
||||
piece.name,
|
||||
),
|
||||
category: ModelCategory.PIECE,
|
||||
description: typePiece.description || '',
|
||||
notes: typePiece.description || '',
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -1106,15 +1346,19 @@ export class MachinesService {
|
||||
});
|
||||
|
||||
for (const customField of customFields) {
|
||||
const existingValue = await this.prisma.customFieldValue.findFirst({
|
||||
where: {
|
||||
customFieldId: customField.id,
|
||||
pieceId: piece.id,
|
||||
},
|
||||
});
|
||||
const existingValue =
|
||||
await this.prisma.customFieldValue.findFirst({
|
||||
where: {
|
||||
customFieldId: customField.id,
|
||||
pieceId: piece.id,
|
||||
},
|
||||
});
|
||||
|
||||
if (!existingValue) {
|
||||
const defaultValue = typePiece.customFields.find((cf: any) => cf.name === customField.name)?.defaultValue || '';
|
||||
const defaultValue =
|
||||
typePiece.customFields.find(
|
||||
(cf: any) => cf.name === customField.name,
|
||||
)?.defaultValue || '';
|
||||
await this.prisma.customFieldValue.create({
|
||||
data: {
|
||||
value: defaultValue,
|
||||
@@ -1132,17 +1376,30 @@ export class MachinesService {
|
||||
// Traiter les pièces de machine
|
||||
for (const piece of machine.pieces) {
|
||||
const typePiece = machinePieces.find((p: any) => p.name === piece.name);
|
||||
if (typePiece && typePiece.customFields && typePiece.customFields.length > 0) {
|
||||
if (
|
||||
typePiece &&
|
||||
typePiece.customFields &&
|
||||
typePiece.customFields.length > 0
|
||||
) {
|
||||
// Créer le type de pièce s'il n'existe pas
|
||||
let typePieceEntity = await this.prisma.typePiece.findFirst({
|
||||
where: { name: piece.name },
|
||||
let typePieceEntity = await this.prisma.modelType.findFirst({
|
||||
where: {
|
||||
name: piece.name,
|
||||
category: ModelCategory.PIECE,
|
||||
},
|
||||
});
|
||||
|
||||
if (!typePieceEntity) {
|
||||
typePieceEntity = await this.prisma.typePiece.create({
|
||||
typePieceEntity = await this.prisma.modelType.create({
|
||||
data: {
|
||||
name: piece.name,
|
||||
code: await this.generateUniqueComponentTypeCode(
|
||||
this.prisma,
|
||||
piece.name,
|
||||
),
|
||||
category: ModelCategory.PIECE,
|
||||
description: typePiece.description || '',
|
||||
notes: typePiece.description || '',
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -1190,7 +1447,10 @@ export class MachinesService {
|
||||
});
|
||||
|
||||
if (!existingValue) {
|
||||
const defaultValue = typePiece.customFields.find((cf: any) => cf.name === customField.name)?.defaultValue || '';
|
||||
const defaultValue =
|
||||
typePiece.customFields.find(
|
||||
(cf: any) => cf.name === customField.name,
|
||||
)?.defaultValue || '';
|
||||
await this.prisma.customFieldValue.create({
|
||||
data: {
|
||||
value: defaultValue,
|
||||
|
||||
28
src/model-type/dto/create-model-type.dto.ts
Normal file
28
src/model-type/dto/create-model-type.dto.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { IsEnum, IsOptional, IsString, Length, Matches } from 'class-validator';
|
||||
|
||||
export enum ModelCategory {
|
||||
COMPONENT = 'COMPONENT',
|
||||
PIECE = 'PIECE',
|
||||
}
|
||||
|
||||
export class CreateModelTypeDto {
|
||||
@IsString()
|
||||
@Length(2, 120)
|
||||
name!: string;
|
||||
|
||||
@IsString()
|
||||
@Length(2, 60)
|
||||
@Matches(/^[a-z0-9\-_.]+$/i)
|
||||
code!: string;
|
||||
|
||||
@IsEnum(ModelCategory)
|
||||
category!: ModelCategory;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
notes?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
description?: string;
|
||||
}
|
||||
5
src/model-type/dto/update-model-type.dto.ts
Normal file
5
src/model-type/dto/update-model-type.dto.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { PartialType } from '@nestjs/mapped-types';
|
||||
import { CreateModelTypeDto } from './create-model-type.dto';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
||||
export class UpdateModelTypeDto extends PartialType(CreateModelTypeDto) {}
|
||||
80
src/model-type/model-type.controller.ts
Normal file
80
src/model-type/model-type.controller.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
Header,
|
||||
HttpCode,
|
||||
HttpStatus,
|
||||
Param,
|
||||
Patch,
|
||||
Post,
|
||||
Query,
|
||||
} from '@nestjs/common';
|
||||
import { CreateModelTypeDto, ModelCategory } from './dto/create-model-type.dto';
|
||||
import { UpdateModelTypeDto } from './dto/update-model-type.dto';
|
||||
import { ModelTypeService } from './model-type.service';
|
||||
|
||||
@Controller('api/model-types')
|
||||
export class ModelTypeController {
|
||||
constructor(private readonly modelTypeService: ModelTypeService) {}
|
||||
|
||||
@Get()
|
||||
list(
|
||||
@Query('q') q?: string,
|
||||
@Query('category') category?: string,
|
||||
@Query('sort') sort?: 'name' | 'code' | 'createdAt',
|
||||
@Query('dir') dir?: 'asc' | 'desc',
|
||||
@Query('limit') limit?: string,
|
||||
@Query('offset') offset?: string,
|
||||
) {
|
||||
return this.modelTypeService.list({
|
||||
q,
|
||||
category: this.normalizeCategory(category),
|
||||
sort,
|
||||
dir,
|
||||
limit: this.parsePositiveNumber(limit),
|
||||
offset: this.parsePositiveNumber(offset),
|
||||
});
|
||||
}
|
||||
|
||||
@Post()
|
||||
@Header('Cache-Control', 'no-store')
|
||||
create(@Body() dto: CreateModelTypeDto) {
|
||||
return this.modelTypeService.create(dto);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
findOne(@Param('id') id: string) {
|
||||
return this.modelTypeService.findOne(id);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
@Header('Cache-Control', 'no-store')
|
||||
update(@Param('id') id: string, @Body() dto: UpdateModelTypeDto) {
|
||||
return this.modelTypeService.update(id, dto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@Header('Cache-Control', 'no-store')
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
async remove(@Param('id') id: string) {
|
||||
await this.modelTypeService.remove(id);
|
||||
}
|
||||
|
||||
private normalizeCategory(value?: string) {
|
||||
if (!value) return undefined;
|
||||
return Object.values(ModelCategory).includes(value as ModelCategory)
|
||||
? (value as ModelCategory)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
private parsePositiveNumber(value?: string) {
|
||||
if (value === undefined) return undefined;
|
||||
const parsed = Number.parseInt(value, 10);
|
||||
if (Number.isNaN(parsed) || parsed < 0) {
|
||||
return undefined;
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
}
|
||||
9
src/model-type/model-type.module.ts
Normal file
9
src/model-type/model-type.module.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ModelTypeController } from './model-type.controller';
|
||||
import { ModelTypeService } from './model-type.service';
|
||||
|
||||
@Module({
|
||||
controllers: [ModelTypeController],
|
||||
providers: [ModelTypeService],
|
||||
})
|
||||
export class ModelTypeModule {}
|
||||
159
src/model-type/model-type.service.ts
Normal file
159
src/model-type/model-type.service.ts
Normal file
@@ -0,0 +1,159 @@
|
||||
import {
|
||||
ConflictException,
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
} from '@nestjs/common';
|
||||
import { ModelType as PrismaModelType, Prisma } from '@prisma/client';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { CreateModelTypeDto, ModelCategory } from './dto/create-model-type.dto';
|
||||
import { UpdateModelTypeDto } from './dto/update-model-type.dto';
|
||||
|
||||
type SortField = 'name' | 'code' | 'createdAt';
|
||||
type SortDirection = 'asc' | 'desc';
|
||||
|
||||
interface ListParams {
|
||||
q?: string;
|
||||
category?: ModelCategory;
|
||||
sort?: SortField;
|
||||
dir?: SortDirection;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class ModelTypeService {
|
||||
private readonly allowedSortFields: SortField[] = [
|
||||
'name',
|
||||
'code',
|
||||
'createdAt',
|
||||
];
|
||||
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
async list(params: ListParams): Promise<{
|
||||
items: PrismaModelType[];
|
||||
total: number;
|
||||
offset: number;
|
||||
limit: number;
|
||||
}> {
|
||||
const {
|
||||
q,
|
||||
category,
|
||||
sort = 'createdAt',
|
||||
dir = 'desc',
|
||||
limit = 20,
|
||||
offset = 0,
|
||||
} = params;
|
||||
|
||||
const cappedLimit = Math.min(Math.max(limit ?? 20, 1), 100);
|
||||
const safeOffset = Math.max(offset ?? 0, 0);
|
||||
|
||||
const orderByField = this.allowedSortFields.includes(sort)
|
||||
? sort
|
||||
: 'createdAt';
|
||||
const orderByDir: SortDirection = dir === 'asc' ? 'asc' : 'desc';
|
||||
|
||||
const where: Prisma.ModelTypeWhereInput = {};
|
||||
|
||||
if (category) {
|
||||
where.category = category;
|
||||
}
|
||||
|
||||
if (q?.trim()) {
|
||||
const term = q.trim();
|
||||
where.OR = [
|
||||
{ name: { contains: term, mode: 'insensitive' } },
|
||||
{ code: { contains: term, mode: 'insensitive' } },
|
||||
];
|
||||
}
|
||||
|
||||
const [items, total] = await this.prisma.$transaction([
|
||||
this.prisma.modelType.findMany({
|
||||
where,
|
||||
orderBy: { [orderByField]: orderByDir },
|
||||
skip: safeOffset,
|
||||
take: cappedLimit,
|
||||
}),
|
||||
this.prisma.modelType.count({ where }),
|
||||
]);
|
||||
|
||||
return {
|
||||
items,
|
||||
total,
|
||||
offset: safeOffset,
|
||||
limit: cappedLimit,
|
||||
};
|
||||
}
|
||||
|
||||
async create(dto: CreateModelTypeDto): Promise<PrismaModelType> {
|
||||
try {
|
||||
return await this.prisma.modelType.create({
|
||||
data: {
|
||||
name: dto.name,
|
||||
code: dto.code,
|
||||
category: dto.category,
|
||||
notes: dto.notes,
|
||||
description: dto.description ?? null,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
this.handlePrismaError(error);
|
||||
}
|
||||
}
|
||||
|
||||
async update(id: string, dto: UpdateModelTypeDto): Promise<PrismaModelType> {
|
||||
try {
|
||||
return await this.prisma.modelType.update({
|
||||
where: { id },
|
||||
data: {
|
||||
...dto,
|
||||
description:
|
||||
dto.description === undefined ? undefined : dto.description ?? null,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
this.handlePrismaError(error);
|
||||
}
|
||||
}
|
||||
|
||||
async remove(id: string): Promise<void> {
|
||||
try {
|
||||
await this.prisma.modelType.delete({ where: { id } });
|
||||
} catch (error) {
|
||||
this.handlePrismaError(error);
|
||||
}
|
||||
}
|
||||
|
||||
async findOne(id: string): Promise<PrismaModelType> {
|
||||
const modelType = await this.prisma.modelType.findUnique({ where: { id } });
|
||||
if (!modelType) {
|
||||
throw new NotFoundException('Type de modèle introuvable.');
|
||||
}
|
||||
return modelType;
|
||||
}
|
||||
|
||||
private handlePrismaError(error: unknown): never {
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
if (error.code === 'P2002' && this.isUniqueCodeConstraint(error)) {
|
||||
throw new ConflictException('Ce code est déjà utilisé.');
|
||||
}
|
||||
|
||||
if (error.code === 'P2025') {
|
||||
throw new NotFoundException('Type de modèle introuvable.');
|
||||
}
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
private isUniqueCodeConstraint(error: Prisma.PrismaClientKnownRequestError) {
|
||||
const { target } = error.meta ?? {};
|
||||
if (Array.isArray(target)) {
|
||||
return target.includes('code');
|
||||
}
|
||||
if (typeof target === 'string') {
|
||||
return target === 'code';
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,12 @@
|
||||
import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Body,
|
||||
Patch,
|
||||
Param,
|
||||
Delete,
|
||||
} from '@nestjs/common';
|
||||
import { PiecesService } from './pieces.service';
|
||||
import { CreatePieceDto, UpdatePieceDto } from '../shared/dto/piece.dto';
|
||||
|
||||
|
||||
@@ -4,6 +4,6 @@ import { PiecesService } from './pieces.service';
|
||||
|
||||
@Module({
|
||||
controllers: [PiecesController],
|
||||
providers: [PiecesService]
|
||||
providers: [PiecesService],
|
||||
})
|
||||
export class PiecesModule {}
|
||||
|
||||
@@ -43,9 +43,7 @@ describe('PiecesService', () => {
|
||||
prisma.machine.findUnique.mockResolvedValue({
|
||||
id: 'machine-1',
|
||||
typeMachine: {
|
||||
pieceRequirements: [
|
||||
{ id: 'req-1', typePieceId: 'type-piece-1' },
|
||||
],
|
||||
pieceRequirements: [{ id: 'req-1', typePieceId: 'type-piece-1' }],
|
||||
},
|
||||
});
|
||||
|
||||
@@ -70,9 +68,7 @@ describe('PiecesService', () => {
|
||||
prisma.machine.findUnique.mockResolvedValue({
|
||||
id: 'machine-1',
|
||||
typeMachine: {
|
||||
pieceRequirements: [
|
||||
{ id: 'req-1', typePieceId: 'type-piece-1' },
|
||||
],
|
||||
pieceRequirements: [{ id: 'req-1', typePieceId: 'type-piece-1' }],
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -180,9 +180,7 @@ export class PiecesService {
|
||||
});
|
||||
|
||||
if (!composant) {
|
||||
throw new BadRequestException(
|
||||
'Le composant spécifié est introuvable.',
|
||||
);
|
||||
throw new BadRequestException('Le composant spécifié est introuvable.');
|
||||
}
|
||||
|
||||
if (composant.machineId) {
|
||||
|
||||
@@ -6,4 +6,4 @@ import { PrismaService } from './prisma.service';
|
||||
providers: [PrismaService],
|
||||
exports: [PrismaService],
|
||||
})
|
||||
export class PrismaModule {}
|
||||
export class PrismaModule {}
|
||||
|
||||
@@ -2,7 +2,10 @@ import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
@Injectable()
|
||||
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
|
||||
export class PrismaService
|
||||
extends PrismaClient
|
||||
implements OnModuleInit, OnModuleDestroy
|
||||
{
|
||||
constructor() {
|
||||
super({
|
||||
log: ['query', 'info', 'warn', 'error'],
|
||||
@@ -16,4 +19,4 @@ export class PrismaService extends PrismaClient implements OnModuleInit, OnModul
|
||||
async onModuleDestroy() {
|
||||
await this.$disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Body, Controller, Delete, Get, Param, Post } from '@nestjs/common'
|
||||
import { ProfilesService } from './profiles.service'
|
||||
import { CreateProfileDto } from '../shared/dto/profile.dto'
|
||||
import { Body, Controller, Delete, Get, Param, Post } from '@nestjs/common';
|
||||
import { ProfilesService } from './profiles.service';
|
||||
import { CreateProfileDto } from '../shared/dto/profile.dto';
|
||||
|
||||
@Controller('profiles')
|
||||
export class ProfilesController {
|
||||
@@ -8,16 +8,16 @@ export class ProfilesController {
|
||||
|
||||
@Get()
|
||||
async findAll() {
|
||||
return this.profilesService.findAllActive()
|
||||
return this.profilesService.findAllActive();
|
||||
}
|
||||
|
||||
@Post()
|
||||
async create(@Body() dto: CreateProfileDto) {
|
||||
return this.profilesService.create(dto)
|
||||
return this.profilesService.create(dto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
async delete(@Param('id') id: string) {
|
||||
return this.profilesService.deactivate(id)
|
||||
return this.profilesService.deactivate(id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Module } from '@nestjs/common'
|
||||
import { ProfilesController } from './profiles.controller'
|
||||
import { ProfilesService } from './profiles.service'
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ProfilesController } from './profiles.controller';
|
||||
import { ProfilesService } from './profiles.service';
|
||||
|
||||
@Module({
|
||||
controllers: [ProfilesController],
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
import { Injectable, NotFoundException, BadRequestException, OnModuleInit } from '@nestjs/common'
|
||||
import { PrismaService } from '../prisma/prisma.service'
|
||||
import { CreateProfileDto } from '../shared/dto/profile.dto'
|
||||
import {
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
BadRequestException,
|
||||
OnModuleInit,
|
||||
} from '@nestjs/common';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { CreateProfileDto } from '../shared/dto/profile.dto';
|
||||
|
||||
@Injectable()
|
||||
export class ProfilesService implements OnModuleInit {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
async onModuleInit() {
|
||||
await this.ensureDefaultProfile()
|
||||
await this.ensureDefaultProfile();
|
||||
}
|
||||
|
||||
async findAllActive() {
|
||||
@@ -21,11 +26,11 @@ export class ProfilesService implements OnModuleInit {
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
},
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
async findActiveById(profileId: string) {
|
||||
if (!profileId) return null
|
||||
if (!profileId) return null;
|
||||
|
||||
return this.prisma.profile.findFirst({
|
||||
where: {
|
||||
@@ -40,15 +45,15 @@ export class ProfilesService implements OnModuleInit {
|
||||
updatedAt: true,
|
||||
isActive: true,
|
||||
},
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
async create(dto: CreateProfileDto) {
|
||||
const firstName = dto.firstName.trim()
|
||||
const lastName = dto.lastName.trim()
|
||||
const firstName = dto.firstName.trim();
|
||||
const lastName = dto.lastName.trim();
|
||||
|
||||
if (!firstName || !lastName) {
|
||||
throw new BadRequestException('Le prénom et le nom sont obligatoires.')
|
||||
throw new BadRequestException('Le prénom et le nom sont obligatoires.');
|
||||
}
|
||||
|
||||
return this.prisma.profile.create({
|
||||
@@ -64,13 +69,15 @@ export class ProfilesService implements OnModuleInit {
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
},
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
async deactivate(profileId: string) {
|
||||
const existing = await this.prisma.profile.findUnique({ where: { id: profileId } })
|
||||
const existing = await this.prisma.profile.findUnique({
|
||||
where: { id: profileId },
|
||||
});
|
||||
if (!existing) {
|
||||
throw new NotFoundException('Profil introuvable')
|
||||
throw new NotFoundException('Profil introuvable');
|
||||
}
|
||||
|
||||
if (!existing.isActive) {
|
||||
@@ -84,7 +91,7 @@ export class ProfilesService implements OnModuleInit {
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
},
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
return this.prisma.profile.update({
|
||||
@@ -98,27 +105,34 @@ export class ProfilesService implements OnModuleInit {
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
},
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
private async ensureDefaultProfile() {
|
||||
const count = await this.prisma.profile.count({ where: { isActive: true } }).catch((err) => {
|
||||
console.error('Failed to count profiles during ensureDefaultProfile:', err.message)
|
||||
return 0
|
||||
})
|
||||
if (count > 0) return
|
||||
const count = await this.prisma.profile
|
||||
.count({ where: { isActive: true } })
|
||||
.catch((err) => {
|
||||
console.error(
|
||||
'Failed to count profiles during ensureDefaultProfile:',
|
||||
err.message,
|
||||
);
|
||||
return 0;
|
||||
});
|
||||
if (count > 0) return;
|
||||
|
||||
const firstName = process.env.DEFAULT_PROFILE_FIRST_NAME?.trim() || 'Admin'
|
||||
const lastName = process.env.DEFAULT_PROFILE_LAST_NAME?.trim() || 'Général'
|
||||
const firstName = process.env.DEFAULT_PROFILE_FIRST_NAME?.trim() || 'Admin';
|
||||
const lastName = process.env.DEFAULT_PROFILE_LAST_NAME?.trim() || 'Général';
|
||||
|
||||
await this.prisma.profile.create({
|
||||
data: {
|
||||
firstName,
|
||||
lastName,
|
||||
isActive: true,
|
||||
},
|
||||
}).catch((err) => {
|
||||
console.error('Failed to create default profile:', err.message)
|
||||
})
|
||||
await this.prisma.profile
|
||||
.create({
|
||||
data: {
|
||||
firstName,
|
||||
lastName,
|
||||
isActive: true,
|
||||
},
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('Failed to create default profile:', err.message);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,10 +7,10 @@ import {
|
||||
Post,
|
||||
Req,
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common'
|
||||
import { Request } from 'express'
|
||||
import { ProfilesService } from '../profiles/profiles.service'
|
||||
import { ActivateProfileDto } from '../shared/dto/profile.dto'
|
||||
} from '@nestjs/common';
|
||||
import { Request } from 'express';
|
||||
import { ProfilesService } from '../profiles/profiles.service';
|
||||
import { ActivateProfileDto } from '../shared/dto/profile.dto';
|
||||
|
||||
@Controller('session/profile')
|
||||
export class ProfileSessionController {
|
||||
@@ -19,46 +19,50 @@ export class ProfileSessionController {
|
||||
@Get()
|
||||
async getActiveProfile(@Req() req: Request) {
|
||||
if (!req.session?.profileId) {
|
||||
throw new UnauthorizedException('Aucun profil actif.')
|
||||
throw new UnauthorizedException('Aucun profil actif.');
|
||||
}
|
||||
|
||||
const profile = await this.profilesService.findActiveById(req.session.profileId)
|
||||
const profile = await this.profilesService.findActiveById(
|
||||
req.session.profileId,
|
||||
);
|
||||
if (!profile) {
|
||||
req.session.profileId = undefined
|
||||
throw new UnauthorizedException('Profil introuvable ou inactif.')
|
||||
req.session.profileId = undefined;
|
||||
throw new UnauthorizedException('Profil introuvable ou inactif.');
|
||||
}
|
||||
|
||||
return profile
|
||||
return profile;
|
||||
}
|
||||
|
||||
@Post()
|
||||
async activateProfile(@Req() req: Request, @Body() dto: ActivateProfileDto) {
|
||||
if (!dto.profileId) {
|
||||
throw new BadRequestException('profileId est requis.')
|
||||
throw new BadRequestException('profileId est requis.');
|
||||
}
|
||||
|
||||
const profile = await this.profilesService.findActiveById(dto.profileId)
|
||||
const profile = await this.profilesService.findActiveById(dto.profileId);
|
||||
if (!profile) {
|
||||
throw new UnauthorizedException('Profil introuvable ou inactif.')
|
||||
throw new UnauthorizedException('Profil introuvable ou inactif.');
|
||||
}
|
||||
|
||||
req.session.profileId = profile.id
|
||||
return profile
|
||||
req.session.profileId = profile.id;
|
||||
return profile;
|
||||
}
|
||||
|
||||
@Delete()
|
||||
async logout(@Req() req: Request) {
|
||||
if (!req.session) {
|
||||
return { success: true }
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
req.session.destroy((err) => {
|
||||
if (err) {
|
||||
return reject(new BadRequestException('Impossible de déconnecter la session.'))
|
||||
return reject(
|
||||
new BadRequestException('Impossible de déconnecter la session.'),
|
||||
);
|
||||
}
|
||||
resolve({ success: true })
|
||||
})
|
||||
})
|
||||
resolve({ success: true });
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Module } from '@nestjs/common'
|
||||
import { ProfileSessionController } from './session.controller'
|
||||
import { ProfilesModule } from '../profiles/profiles.module'
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ProfileSessionController } from './session.controller';
|
||||
import { ProfilesModule } from '../profiles/profiles.module';
|
||||
|
||||
@Module({
|
||||
imports: [ProfilesModule],
|
||||
|
||||
@@ -22,7 +22,7 @@ export class CreateComposantDto {
|
||||
constructeurId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@Transform(({ value }) => value === '' ? null : value)
|
||||
@Transform(({ value }) => (value === '' ? null : value))
|
||||
@IsNumber({}, { message: 'prix must be a valid number' })
|
||||
prix?: number | null;
|
||||
|
||||
@@ -56,7 +56,7 @@ export class UpdateComposantDto {
|
||||
constructeurId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@Transform(({ value }) => value === '' ? null : value)
|
||||
@Transform(({ value }) => (value === '' ? null : value))
|
||||
@IsNumber({}, { message: 'prix must be a valid number' })
|
||||
prix?: number | null;
|
||||
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
import { IsEmail, IsOptional, IsString } from 'class-validator'
|
||||
import { IsEmail, IsOptional, IsString } from 'class-validator';
|
||||
|
||||
export class CreateConstructeurDto {
|
||||
@IsString()
|
||||
name: string
|
||||
name: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsEmail()
|
||||
email?: string
|
||||
email?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
phone?: string
|
||||
phone?: string;
|
||||
}
|
||||
|
||||
export class UpdateConstructeurDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
name?: string
|
||||
name?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsEmail()
|
||||
email?: string
|
||||
email?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
phone?: string
|
||||
phone?: string;
|
||||
}
|
||||
|
||||
@@ -58,4 +58,4 @@ export class UpdateCustomFieldValueDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
value?: string;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,12 +18,8 @@ export class MachinePieceSelectionDto {
|
||||
@IsString()
|
||||
requirementId: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
pieceModelId?: string;
|
||||
|
||||
@IsOptional()
|
||||
definition?: any;
|
||||
pieceModelId: string;
|
||||
}
|
||||
|
||||
export class CreateMachineDto {
|
||||
|
||||
@@ -22,7 +22,7 @@ export class CreatePieceDto {
|
||||
constructeurId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@Transform(({ value }) => value === '' ? null : value)
|
||||
@Transform(({ value }) => (value === '' ? null : value))
|
||||
@IsNumber({}, { message: 'prix must be a valid number' })
|
||||
prix?: number | null;
|
||||
|
||||
@@ -56,7 +56,7 @@ export class UpdatePieceDto {
|
||||
constructeurId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@Transform(({ value }) => value === '' ? null : value)
|
||||
@Transform(({ value }) => (value === '' ? null : value))
|
||||
@IsNumber({}, { message: 'prix must be a valid number' })
|
||||
prix?: number | null;
|
||||
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import { IsNotEmpty, IsString, MaxLength } from 'class-validator'
|
||||
import { IsNotEmpty, IsString, MaxLength } from 'class-validator';
|
||||
|
||||
export class CreateProfileDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@MaxLength(100)
|
||||
firstName!: string
|
||||
firstName!: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@MaxLength(100)
|
||||
lastName!: string
|
||||
lastName!: string;
|
||||
}
|
||||
|
||||
export class ActivateProfileDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
profileId!: string
|
||||
profileId!: string;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
import { IsString, IsOptional, IsArray, IsObject, IsBoolean, IsEnum, IsInt } from 'class-validator';
|
||||
import {
|
||||
IsString,
|
||||
IsOptional,
|
||||
IsArray,
|
||||
IsObject,
|
||||
IsBoolean,
|
||||
IsEnum,
|
||||
IsInt,
|
||||
} from 'class-validator';
|
||||
import { Type } from 'class-transformer';
|
||||
import { ValidateNested } from 'class-validator';
|
||||
|
||||
@@ -7,7 +15,7 @@ export enum CustomFieldType {
|
||||
NUMBER = 'number',
|
||||
SELECT = 'select',
|
||||
BOOLEAN = 'boolean',
|
||||
DATE = 'date'
|
||||
DATE = 'date',
|
||||
}
|
||||
|
||||
export class CreateCustomFieldDto {
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Body,
|
||||
Patch,
|
||||
Param,
|
||||
Delete,
|
||||
} from '@nestjs/common';
|
||||
import { SitesService } from './sites.service';
|
||||
import { CreateSiteDto, UpdateSiteDto } from '../shared/dto/site.dto';
|
||||
|
||||
|
||||
@@ -4,6 +4,6 @@ import { SitesService } from './sites.service';
|
||||
|
||||
@Module({
|
||||
controllers: [SitesController],
|
||||
providers: [SitesService]
|
||||
providers: [SitesService],
|
||||
})
|
||||
export class SitesModule {}
|
||||
|
||||
@@ -70,10 +70,10 @@ export class SitesService {
|
||||
});
|
||||
|
||||
if (machinesInSite.length > 0) {
|
||||
const machineNames = machinesInSite.map(m => m.name).join(', ');
|
||||
const machineNames = machinesInSite.map((m) => m.name).join(', ');
|
||||
throw new Error(
|
||||
`Impossible de supprimer ce site car il contient ${machinesInSite.length} machine(s): ${machineNames}. ` +
|
||||
`Veuillez d'abord supprimer ou déplacer ces machines vers un autre site.`
|
||||
`Veuillez d'abord supprimer ou déplacer ces machines vers un autre site.`,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
4
src/types/express-session.d.ts
vendored
4
src/types/express-session.d.ts
vendored
@@ -1,7 +1,7 @@
|
||||
import 'express-session'
|
||||
import 'express-session';
|
||||
|
||||
declare module 'express-session' {
|
||||
interface SessionData {
|
||||
profileId?: string
|
||||
profileId?: string;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
import { Controller, Get, Post, Body, Patch, Param, Delete, Query } from '@nestjs/common';
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Body,
|
||||
Patch,
|
||||
Param,
|
||||
Delete,
|
||||
Query,
|
||||
} from '@nestjs/common';
|
||||
import { TypesService } from './types.service';
|
||||
import {
|
||||
CreateTypeMachineDto,
|
||||
import {
|
||||
CreateTypeMachineDto,
|
||||
UpdateTypeMachineDto,
|
||||
CreateTypeComposantDto,
|
||||
UpdateTypeComposantDto,
|
||||
@@ -10,7 +19,7 @@ import {
|
||||
CreateComposantModelDto,
|
||||
UpdateComposantModelDto,
|
||||
CreatePieceModelDto,
|
||||
UpdatePieceModelDto
|
||||
UpdatePieceModelDto,
|
||||
} from '../shared/dto/type.dto';
|
||||
|
||||
@Controller('types')
|
||||
@@ -34,7 +43,10 @@ export class TypesController {
|
||||
}
|
||||
|
||||
@Patch('machines/:id')
|
||||
updateTypeMachine(@Param('id') id: string, @Body() updateTypeMachineDto: UpdateTypeMachineDto) {
|
||||
updateTypeMachine(
|
||||
@Param('id') id: string,
|
||||
@Body() updateTypeMachineDto: UpdateTypeMachineDto,
|
||||
) {
|
||||
return this.typesService.updateTypeMachine(id, updateTypeMachineDto);
|
||||
}
|
||||
|
||||
@@ -56,7 +68,9 @@ export class TypesController {
|
||||
|
||||
// ComposantModel routes
|
||||
@Post('composants/models')
|
||||
createComposantModel(@Body() createComposantModelDto: CreateComposantModelDto) {
|
||||
createComposantModel(
|
||||
@Body() createComposantModelDto: CreateComposantModelDto,
|
||||
) {
|
||||
return this.typesService.createComposantModel(createComposantModelDto);
|
||||
}
|
||||
|
||||
@@ -71,7 +85,10 @@ export class TypesController {
|
||||
}
|
||||
|
||||
@Patch('composants/models/:id')
|
||||
updateComposantModel(@Param('id') id: string, @Body() updateComposantModelDto: UpdateComposantModelDto) {
|
||||
updateComposantModel(
|
||||
@Param('id') id: string,
|
||||
@Body() updateComposantModelDto: UpdateComposantModelDto,
|
||||
) {
|
||||
return this.typesService.updateComposantModel(id, updateComposantModelDto);
|
||||
}
|
||||
|
||||
@@ -86,7 +103,10 @@ export class TypesController {
|
||||
}
|
||||
|
||||
@Patch('composants/:id')
|
||||
updateTypeComposant(@Param('id') id: string, @Body() updateTypeComposantDto: UpdateTypeComposantDto) {
|
||||
updateTypeComposant(
|
||||
@Param('id') id: string,
|
||||
@Body() updateTypeComposantDto: UpdateTypeComposantDto,
|
||||
) {
|
||||
return this.typesService.updateTypeComposant(id, updateTypeComposantDto);
|
||||
}
|
||||
|
||||
@@ -123,7 +143,10 @@ export class TypesController {
|
||||
}
|
||||
|
||||
@Patch('pieces/models/:id')
|
||||
updatePieceModel(@Param('id') id: string, @Body() updatePieceModelDto: UpdatePieceModelDto) {
|
||||
updatePieceModel(
|
||||
@Param('id') id: string,
|
||||
@Body() updatePieceModelDto: UpdatePieceModelDto,
|
||||
) {
|
||||
return this.typesService.updatePieceModel(id, updatePieceModelDto);
|
||||
}
|
||||
|
||||
@@ -138,7 +161,10 @@ export class TypesController {
|
||||
}
|
||||
|
||||
@Patch('pieces/:id')
|
||||
updateTypePiece(@Param('id') id: string, @Body() updateTypePieceDto: UpdateTypePieceDto) {
|
||||
updateTypePiece(
|
||||
@Param('id') id: string,
|
||||
@Body() updateTypePieceDto: UpdateTypePieceDto,
|
||||
) {
|
||||
return this.typesService.updateTypePiece(id, updateTypePieceDto);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,6 @@ import { TypesService } from './types.service';
|
||||
|
||||
@Module({
|
||||
controllers: [TypesController],
|
||||
providers: [TypesService]
|
||||
providers: [TypesService],
|
||||
})
|
||||
export class TypesModule {}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ModelCategory } from '@prisma/client';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import {
|
||||
CreateTypeMachineDto,
|
||||
import {
|
||||
CreateTypeMachineDto,
|
||||
UpdateTypeMachineDto,
|
||||
CreateTypeComposantDto,
|
||||
UpdateTypeComposantDto,
|
||||
@@ -10,53 +11,93 @@ import {
|
||||
CreateComposantModelDto,
|
||||
UpdateComposantModelDto,
|
||||
CreatePieceModelDto,
|
||||
UpdatePieceModelDto
|
||||
UpdatePieceModelDto,
|
||||
} from '../shared/dto/type.dto';
|
||||
|
||||
@Injectable()
|
||||
export class TypesService {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
|
||||
private slugifyName(name: string): string {
|
||||
return name
|
||||
.normalize('NFD')
|
||||
.replace(/[\u0300-\u036f]/g, '')
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, '-')
|
||||
.replace(/^-+|-+$/g, '')
|
||||
.replace(/-+/g, '-');
|
||||
}
|
||||
|
||||
private async generateUniqueModelTypeCode(name: string): Promise<string> {
|
||||
const base = this.slugifyName(name) || 'type';
|
||||
let candidate = base;
|
||||
let suffix = 1;
|
||||
|
||||
while (
|
||||
await this.prisma.modelType.findUnique({
|
||||
where: { code: candidate },
|
||||
select: { id: true },
|
||||
})
|
||||
) {
|
||||
candidate = `${base}-${suffix++}`;
|
||||
}
|
||||
|
||||
return candidate;
|
||||
}
|
||||
|
||||
// TypeMachine methods
|
||||
async createTypeMachine(createTypeMachineDto: CreateTypeMachineDto) {
|
||||
const { customFields, componentRequirements, pieceRequirements, ...typeData } = createTypeMachineDto;
|
||||
|
||||
const {
|
||||
customFields,
|
||||
componentRequirements,
|
||||
pieceRequirements,
|
||||
...typeData
|
||||
} = createTypeMachineDto;
|
||||
|
||||
return this.prisma.typeMachine.create({
|
||||
data: {
|
||||
...typeData,
|
||||
customFields: customFields ? {
|
||||
create: customFields.map(field => ({
|
||||
name: field.name,
|
||||
type: field.type,
|
||||
required: field.required || false,
|
||||
defaultValue: field.defaultValue,
|
||||
options: field.options
|
||||
}))
|
||||
} : undefined,
|
||||
componentRequirements: componentRequirements && componentRequirements.length > 0 ? {
|
||||
create: componentRequirements.map(requirement => ({
|
||||
label: requirement.label,
|
||||
minCount: requirement.minCount ?? 1,
|
||||
maxCount: requirement.maxCount ?? null,
|
||||
required: requirement.required ?? true,
|
||||
allowNewModels: requirement.allowNewModels ?? true,
|
||||
typeComposant: {
|
||||
connect: { id: requirement.typeComposantId },
|
||||
},
|
||||
}))
|
||||
} : undefined,
|
||||
pieceRequirements: pieceRequirements && pieceRequirements.length > 0 ? {
|
||||
create: pieceRequirements.map(requirement => ({
|
||||
label: requirement.label,
|
||||
minCount: requirement.minCount ?? 0,
|
||||
maxCount: requirement.maxCount ?? null,
|
||||
required: requirement.required ?? false,
|
||||
allowNewModels: requirement.allowNewModels ?? true,
|
||||
typePiece: {
|
||||
connect: { id: requirement.typePieceId },
|
||||
},
|
||||
}))
|
||||
} : undefined,
|
||||
customFields: customFields
|
||||
? {
|
||||
create: customFields.map((field) => ({
|
||||
name: field.name,
|
||||
type: field.type,
|
||||
required: field.required || false,
|
||||
defaultValue: field.defaultValue,
|
||||
options: field.options,
|
||||
})),
|
||||
}
|
||||
: undefined,
|
||||
componentRequirements:
|
||||
componentRequirements && componentRequirements.length > 0
|
||||
? {
|
||||
create: componentRequirements.map((requirement) => ({
|
||||
label: requirement.label,
|
||||
minCount: requirement.minCount ?? 1,
|
||||
maxCount: requirement.maxCount ?? null,
|
||||
required: requirement.required ?? true,
|
||||
allowNewModels: requirement.allowNewModels ?? true,
|
||||
typeComposant: {
|
||||
connect: { id: requirement.typeComposantId },
|
||||
},
|
||||
})),
|
||||
}
|
||||
: undefined,
|
||||
pieceRequirements:
|
||||
pieceRequirements && pieceRequirements.length > 0
|
||||
? {
|
||||
create: pieceRequirements.map((requirement) => ({
|
||||
label: requirement.label,
|
||||
minCount: requirement.minCount ?? 0,
|
||||
maxCount: requirement.maxCount ?? null,
|
||||
required: requirement.required ?? false,
|
||||
allowNewModels: requirement.allowNewModels ?? true,
|
||||
typePiece: {
|
||||
connect: { id: requirement.typePieceId },
|
||||
},
|
||||
})),
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
include: {
|
||||
customFields: true,
|
||||
@@ -113,27 +154,35 @@ export class TypesService {
|
||||
});
|
||||
}
|
||||
|
||||
async updateTypeMachine(id: string, updateTypeMachineDto: UpdateTypeMachineDto) {
|
||||
const { customFields, componentRequirements, pieceRequirements, ...typeData } = updateTypeMachineDto;
|
||||
|
||||
async updateTypeMachine(
|
||||
id: string,
|
||||
updateTypeMachineDto: UpdateTypeMachineDto,
|
||||
) {
|
||||
const {
|
||||
customFields,
|
||||
componentRequirements,
|
||||
pieceRequirements,
|
||||
...typeData
|
||||
} = updateTypeMachineDto;
|
||||
|
||||
// Si des champs personnalisés sont fournis, on les met à jour
|
||||
if (customFields !== undefined) {
|
||||
// Supprimer tous les champs personnalisés existants
|
||||
await this.prisma.customField.deleteMany({
|
||||
where: { typeMachineId: id }
|
||||
where: { typeMachineId: id },
|
||||
});
|
||||
|
||||
|
||||
// Créer les nouveaux champs personnalisés
|
||||
if (customFields.length > 0) {
|
||||
await this.prisma.customField.createMany({
|
||||
data: customFields.map(field => ({
|
||||
data: customFields.map((field) => ({
|
||||
name: field.name,
|
||||
type: field.type,
|
||||
required: field.required || false,
|
||||
defaultValue: field.defaultValue,
|
||||
options: field.options,
|
||||
typeMachineId: id
|
||||
}))
|
||||
typeMachineId: id,
|
||||
})),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -145,7 +194,7 @@ export class TypesService {
|
||||
|
||||
if (componentRequirements.length > 0) {
|
||||
await this.prisma.typeMachineComponentRequirement.createMany({
|
||||
data: componentRequirements.map(requirement => ({
|
||||
data: componentRequirements.map((requirement) => ({
|
||||
label: requirement.label ?? null,
|
||||
minCount: requirement.minCount ?? 1,
|
||||
maxCount: requirement.maxCount ?? null,
|
||||
@@ -166,7 +215,7 @@ export class TypesService {
|
||||
|
||||
if (pieceRequirements.length > 0) {
|
||||
await this.prisma.typeMachinePieceRequirement.createMany({
|
||||
data: pieceRequirements.map(requirement => ({
|
||||
data: pieceRequirements.map((requirement) => ({
|
||||
label: requirement.label ?? null,
|
||||
minCount: requirement.minCount ?? 0,
|
||||
maxCount: requirement.maxCount ?? null,
|
||||
@@ -179,7 +228,7 @@ export class TypesService {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return this.prisma.typeMachine.update({
|
||||
where: { id },
|
||||
data: typeData,
|
||||
@@ -207,10 +256,10 @@ export class TypesService {
|
||||
});
|
||||
|
||||
if (machinesWithType.length > 0) {
|
||||
const machineNames = machinesWithType.map(m => m.name).join(', ');
|
||||
const machineNames = machinesWithType.map((m) => m.name).join(', ');
|
||||
throw new Error(
|
||||
`Impossible de supprimer ce type de machine car il est utilisé par ${machinesWithType.length} machine(s): ${machineNames}. ` +
|
||||
`Veuillez d'abord supprimer ou modifier ces machines.`
|
||||
`Veuillez d'abord supprimer ou modifier ces machines.`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -224,22 +273,30 @@ export class TypesService {
|
||||
});
|
||||
}
|
||||
|
||||
// TypeComposant methods
|
||||
// TypeComposant methods (backed by ModelType with COMPONENT category)
|
||||
async createTypeComposant(createTypeComposantDto: CreateTypeComposantDto) {
|
||||
const { customFields, ...typeData } = createTypeComposantDto;
|
||||
|
||||
return this.prisma.typeComposant.create({
|
||||
const { customFields, description, name } = createTypeComposantDto;
|
||||
|
||||
const code = await this.generateUniqueModelTypeCode(name);
|
||||
|
||||
return this.prisma.modelType.create({
|
||||
data: {
|
||||
...typeData,
|
||||
customFields: customFields ? {
|
||||
create: customFields.map(field => ({
|
||||
name: field.name,
|
||||
type: field.type,
|
||||
required: field.required || false,
|
||||
defaultValue: field.defaultValue,
|
||||
options: field.options
|
||||
}))
|
||||
} : undefined
|
||||
name,
|
||||
code,
|
||||
category: ModelCategory.COMPONENT,
|
||||
description: description ?? null,
|
||||
notes: description ?? null,
|
||||
customFields: customFields
|
||||
? {
|
||||
create: customFields.map((field) => ({
|
||||
name: field.name,
|
||||
type: field.type,
|
||||
required: field.required || false,
|
||||
defaultValue: field.defaultValue,
|
||||
options: field.options,
|
||||
})),
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
include: {
|
||||
customFields: true,
|
||||
@@ -248,7 +305,8 @@ export class TypesService {
|
||||
}
|
||||
|
||||
async findAllTypeComposants() {
|
||||
return this.prisma.typeComposant.findMany({
|
||||
return this.prisma.modelType.findMany({
|
||||
where: { category: ModelCategory.COMPONENT },
|
||||
include: {
|
||||
composants: true,
|
||||
customFields: true,
|
||||
@@ -258,8 +316,11 @@ export class TypesService {
|
||||
}
|
||||
|
||||
async findOneTypeComposant(id: string) {
|
||||
return this.prisma.typeComposant.findUnique({
|
||||
where: { id },
|
||||
return this.prisma.modelType.findFirst({
|
||||
where: {
|
||||
id,
|
||||
category: ModelCategory.COMPONENT,
|
||||
},
|
||||
include: {
|
||||
composants: true,
|
||||
customFields: true,
|
||||
@@ -268,34 +329,43 @@ export class TypesService {
|
||||
});
|
||||
}
|
||||
|
||||
async updateTypeComposant(id: string, updateTypeComposantDto: UpdateTypeComposantDto) {
|
||||
const { customFields, ...typeData } = updateTypeComposantDto;
|
||||
|
||||
// Si des champs personnalisés sont fournis, on les met à jour
|
||||
async updateTypeComposant(
|
||||
id: string,
|
||||
updateTypeComposantDto: UpdateTypeComposantDto,
|
||||
) {
|
||||
const { customFields, description, name } = updateTypeComposantDto;
|
||||
|
||||
if (customFields !== undefined) {
|
||||
// Supprimer tous les champs personnalisés existants
|
||||
await this.prisma.customField.deleteMany({
|
||||
where: { typeComposantId: id }
|
||||
where: { typeComposantId: id },
|
||||
});
|
||||
|
||||
// Créer les nouveaux champs personnalisés
|
||||
|
||||
if (customFields.length > 0) {
|
||||
await this.prisma.customField.createMany({
|
||||
data: customFields.map(field => ({
|
||||
data: customFields.map((field) => ({
|
||||
name: field.name,
|
||||
type: field.type,
|
||||
required: field.required || false,
|
||||
defaultValue: field.defaultValue,
|
||||
options: field.options,
|
||||
typeComposantId: id
|
||||
}))
|
||||
typeComposantId: id,
|
||||
})),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return this.prisma.typeComposant.update({
|
||||
|
||||
const data: Record<string, unknown> = {};
|
||||
if (name !== undefined) {
|
||||
data.name = name;
|
||||
}
|
||||
if (description !== undefined) {
|
||||
data.description = description;
|
||||
data.notes = description;
|
||||
}
|
||||
|
||||
return this.prisma.modelType.update({
|
||||
where: { id },
|
||||
data: typeData,
|
||||
data,
|
||||
include: {
|
||||
customFields: true,
|
||||
},
|
||||
@@ -303,91 +373,145 @@ export class TypesService {
|
||||
}
|
||||
|
||||
async removeTypeComposant(id: string) {
|
||||
return this.prisma.typeComposant.delete({
|
||||
return this.prisma.modelType.delete({
|
||||
where: { id },
|
||||
});
|
||||
}
|
||||
|
||||
// TypePiece methods
|
||||
// TypePiece methods backed by ModelType
|
||||
private mapModelTypePiece(modelType: any) {
|
||||
if (!modelType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const {
|
||||
pieceCustomFields,
|
||||
pieceModels,
|
||||
pieceRequirements,
|
||||
pieces,
|
||||
...rest
|
||||
} = modelType;
|
||||
|
||||
return {
|
||||
...rest,
|
||||
customFields: pieceCustomFields ?? [],
|
||||
models: pieceModels ?? [],
|
||||
pieceRequirements: pieceRequirements ?? [],
|
||||
pieces: pieces ?? [],
|
||||
};
|
||||
}
|
||||
|
||||
async createTypePiece(createTypePieceDto: CreateTypePieceDto) {
|
||||
const { customFields, ...typeData } = createTypePieceDto;
|
||||
|
||||
return this.prisma.typePiece.create({
|
||||
const { customFields, description, name } = createTypePieceDto;
|
||||
|
||||
const code = await this.generateUniqueModelTypeCode(name);
|
||||
|
||||
const created = await this.prisma.modelType.create({
|
||||
data: {
|
||||
...typeData,
|
||||
customFields: customFields ? {
|
||||
create: customFields.map(field => ({
|
||||
name: field.name,
|
||||
type: field.type,
|
||||
required: field.required || false,
|
||||
defaultValue: field.defaultValue,
|
||||
options: field.options
|
||||
}))
|
||||
} : undefined
|
||||
name,
|
||||
code,
|
||||
category: ModelCategory.PIECE,
|
||||
description: description ?? null,
|
||||
notes: description ?? null,
|
||||
pieceCustomFields: customFields
|
||||
? {
|
||||
create: customFields.map((field) => ({
|
||||
name: field.name,
|
||||
type: field.type,
|
||||
required: field.required || false,
|
||||
defaultValue: field.defaultValue,
|
||||
options: field.options,
|
||||
})),
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
include: {
|
||||
customFields: true,
|
||||
pieceCustomFields: true,
|
||||
pieceModels: true,
|
||||
pieceRequirements: true,
|
||||
pieces: true,
|
||||
},
|
||||
});
|
||||
|
||||
return this.mapModelTypePiece(created);
|
||||
}
|
||||
|
||||
async findAllTypePieces() {
|
||||
return this.prisma.typePiece.findMany({
|
||||
const items = await this.prisma.modelType.findMany({
|
||||
where: { category: ModelCategory.PIECE },
|
||||
include: {
|
||||
pieceCustomFields: true,
|
||||
pieceModels: true,
|
||||
pieceRequirements: true,
|
||||
pieces: true,
|
||||
customFields: true,
|
||||
models: true,
|
||||
},
|
||||
orderBy: { name: 'asc' },
|
||||
});
|
||||
|
||||
return items.map((item) => this.mapModelTypePiece(item));
|
||||
}
|
||||
|
||||
async findOneTypePiece(id: string) {
|
||||
return this.prisma.typePiece.findUnique({
|
||||
where: { id },
|
||||
const item = await this.prisma.modelType.findFirst({
|
||||
where: { id, category: ModelCategory.PIECE },
|
||||
include: {
|
||||
pieceCustomFields: true,
|
||||
pieceModels: true,
|
||||
pieceRequirements: true,
|
||||
pieces: true,
|
||||
customFields: true,
|
||||
models: true,
|
||||
},
|
||||
});
|
||||
|
||||
return this.mapModelTypePiece(item);
|
||||
}
|
||||
|
||||
async updateTypePiece(id: string, updateTypePieceDto: UpdateTypePieceDto) {
|
||||
const { customFields, ...typeData } = updateTypePieceDto;
|
||||
|
||||
// Si des champs personnalisés sont fournis, on les met à jour
|
||||
const { customFields, description, name } = updateTypePieceDto;
|
||||
|
||||
if (customFields !== undefined) {
|
||||
// Supprimer tous les champs personnalisés existants
|
||||
await this.prisma.customField.deleteMany({
|
||||
where: { typePieceId: id }
|
||||
where: { typePieceId: id },
|
||||
});
|
||||
|
||||
// Créer les nouveaux champs personnalisés
|
||||
|
||||
if (customFields.length > 0) {
|
||||
await this.prisma.customField.createMany({
|
||||
data: customFields.map(field => ({
|
||||
data: customFields.map((field) => ({
|
||||
name: field.name,
|
||||
type: field.type,
|
||||
required: field.required || false,
|
||||
defaultValue: field.defaultValue,
|
||||
options: field.options,
|
||||
typePieceId: id
|
||||
}))
|
||||
typePieceId: id,
|
||||
})),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return this.prisma.typePiece.update({
|
||||
|
||||
const updatePayload: Record<string, unknown> = {};
|
||||
if (name !== undefined) {
|
||||
updatePayload.name = name;
|
||||
}
|
||||
if (description !== undefined) {
|
||||
updatePayload.description = description;
|
||||
updatePayload.notes = description;
|
||||
}
|
||||
|
||||
const updated = await this.prisma.modelType.update({
|
||||
where: { id },
|
||||
data: typeData,
|
||||
data: updatePayload,
|
||||
include: {
|
||||
customFields: true,
|
||||
pieceCustomFields: true,
|
||||
pieceModels: true,
|
||||
pieceRequirements: true,
|
||||
pieces: true,
|
||||
},
|
||||
});
|
||||
|
||||
return this.mapModelTypePiece(updated);
|
||||
}
|
||||
|
||||
async removeTypePiece(id: string) {
|
||||
return this.prisma.typePiece.delete({
|
||||
return this.prisma.modelType.delete({
|
||||
where: { id },
|
||||
});
|
||||
}
|
||||
@@ -430,7 +554,10 @@ export class TypesService {
|
||||
});
|
||||
}
|
||||
|
||||
async updateComposantModel(id: string, updateComposantModelDto: UpdateComposantModelDto) {
|
||||
async updateComposantModel(
|
||||
id: string,
|
||||
updateComposantModelDto: UpdateComposantModelDto,
|
||||
) {
|
||||
const { typeComposantId, ...data } = updateComposantModelDto;
|
||||
|
||||
return this.prisma.composantModel.update({
|
||||
|
||||
Reference in New Issue
Block a user