import { BadRequestException, Injectable, NotFoundException, } from '@nestjs/common'; import { PrismaService } from '../prisma/prisma.service'; import { CreateCustomFieldValueDto, UpdateCustomFieldValueDto, CustomFieldEntityType, UpsertCustomFieldValueDto, } from '../shared/dto/custom-field.dto'; @Injectable() export class CustomFieldsService { constructor(private prisma: PrismaService) {} // Créer une valeur de champ personnalisé async createCustomFieldValue( createCustomFieldValueDto: CreateCustomFieldValueDto, ) { return this.prisma.customFieldValue.create({ data: createCustomFieldValueDto, include: { customField: true, }, }); } // Trouver toutes les valeurs de champs personnalisés pour une entité private getCustomFieldValueKey(entityType: CustomFieldEntityType) { switch (entityType) { case CustomFieldEntityType.MACHINE: return 'machineId' as const; case CustomFieldEntityType.COMPOSANT: return 'composantId' as const; case CustomFieldEntityType.PIECE: return 'pieceId' as const; case CustomFieldEntityType.PRODUCT: return 'productId' as const; default: throw new BadRequestException( "Type d'entité de champ personnalisé invalide.", ); } } private async resolveEntityContext( entityType: CustomFieldEntityType, entityId: string, ) { switch (entityType) { case CustomFieldEntityType.MACHINE: { const machine = await this.prisma.machine.findUnique({ where: { id: entityId }, select: { typeMachineId: true }, }); if (!machine) { throw new NotFoundException('Machine introuvable.'); } if (!machine.typeMachineId) { throw new BadRequestException( 'La machine ne possède pas de type associé pour les champs personnalisés.', ); } return { typeId: machine.typeMachineId, customFieldTypeField: 'typeMachineId' as const, valueKey: 'machineId' as const, }; } case CustomFieldEntityType.COMPOSANT: { const composant = await this.prisma.composant.findUnique({ where: { id: entityId }, select: { typeComposantId: true }, }); if (!composant) { throw new NotFoundException('Composant introuvable.'); } if (!composant.typeComposantId) { throw new BadRequestException( 'Le composant ne possède pas de type associé pour les champs personnalisés.', ); } return { typeId: composant.typeComposantId, customFieldTypeField: 'typeComposantId' as const, valueKey: 'composantId' as const, }; } case CustomFieldEntityType.PIECE: { const piece = await this.prisma.piece.findUnique({ where: { id: entityId }, select: { typePieceId: true }, }); if (!piece) { throw new NotFoundException('Pièce introuvable.'); } if (!piece.typePieceId) { throw new BadRequestException( 'La pièce ne possède pas de type associé pour les champs personnalisés.', ); } return { typeId: piece.typePieceId, customFieldTypeField: 'typePieceId' as const, valueKey: 'pieceId' as const, }; } case CustomFieldEntityType.PRODUCT: { const product = await this.prisma.product.findUnique({ where: { id: entityId }, select: { typeProductId: true }, }); if (!product) { throw new NotFoundException('Produit introuvable.'); } if (!product.typeProductId) { throw new BadRequestException( 'Le produit ne possède pas de type associé pour les champs personnalisés.', ); } return { typeId: product.typeProductId, customFieldTypeField: 'typeProductId' as const, valueKey: 'productId' as const, }; } default: throw new BadRequestException( "Type d'entité de champ personnalisé invalide.", ); } } async findCustomFieldValuesByEntity( entityType: CustomFieldEntityType, entityId: string, ) { const key = this.getCustomFieldValueKey(entityType); const whereClause = { [key]: entityId, }; return this.prisma.customFieldValue.findMany({ where: whereClause, include: { customField: true, }, }); } // Trouver une valeur de champ personnalisé par ID async findOneCustomFieldValue(id: string) { return this.prisma.customFieldValue.findUnique({ where: { id }, include: { customField: true, }, }); } // Mettre à jour une valeur de champ personnalisé async updateCustomFieldValue( id: string, updateCustomFieldValueDto: UpdateCustomFieldValueDto, ) { return this.prisma.customFieldValue.update({ where: { id }, data: updateCustomFieldValueDto, include: { customField: true, }, }); } // Supprimer une valeur de champ personnalisé async removeCustomFieldValue(id: string) { return this.prisma.customFieldValue.delete({ where: { id }, }); } // Créer ou mettre à jour une valeur de champ personnalisé async upsertCustomFieldValue(dto: UpsertCustomFieldValueDto) { const { customFieldId: rawCustomFieldId, customFieldName, customFieldType, customFieldOptions, customFieldRequired, entityType, entityId, value, } = dto; const { typeId, customFieldTypeField, valueKey } = await this.resolveEntityContext(entityType, entityId); let targetCustomFieldId = rawCustomFieldId?.trim() || null; if (!targetCustomFieldId) { const normalizedName = customFieldName?.trim(); if (!normalizedName) { throw new BadRequestException( 'customFieldId ou customFieldName est requis pour sauvegarder une valeur.', ); } const normalizedOptions = Array.isArray(customFieldOptions) ? customFieldOptions.map((option) => String(option)) : []; const existingField = await this.prisma.customField.findFirst({ where: { name: normalizedName, [customFieldTypeField]: typeId, }, }); if (existingField) { targetCustomFieldId = existingField.id; } else { const normalizedType = (customFieldType || 'text').trim() || 'text'; const normalizedRequired = !!customFieldRequired; const orderScope = { [customFieldTypeField]: typeId } as const; const nextOrderIndex = await this.prisma.customField.count({ where: orderScope, }); const createdField = await this.prisma.customField.create({ data: { name: normalizedName, type: normalizedType, required: normalizedRequired, options: normalizedOptions, orderIndex: nextOrderIndex, [customFieldTypeField]: typeId, }, }); targetCustomFieldId = createdField.id; } } else { const allowedCustomField = await this.prisma.customField.findFirst({ where: { id: targetCustomFieldId, [customFieldTypeField]: typeId, }, }); if (!allowedCustomField) { throw new BadRequestException( "Le champ personnalisé n'est pas autorisé pour cette entité.", ); } } if (!targetCustomFieldId) { throw new BadRequestException( 'Impossible de déterminer le champ personnalisé ciblé.', ); } const existingValue = await this.prisma.customFieldValue.findFirst({ where: { customFieldId: targetCustomFieldId, [valueKey]: entityId, }, }); if (existingValue) { return this.prisma.customFieldValue.update({ where: { id: existingValue.id }, data: { value }, include: { customField: true, }, }); } return this.prisma.customFieldValue.create({ data: { customFieldId: targetCustomFieldId, value, [valueKey]: entityId, }, include: { customField: true, }, }); } }