This repository has been archived on 2026-04-01. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Inventory_backend/src/custom-fields/custom-fields.service.ts
Matthieu 6cf2b566ce feat: add product domain and machine integration
- extend Prisma schema with products, product constructs and link tables\n- introduce product service, DTOs and includes with constructeur support\n- integrate product selections across model type skeletons, composants, pièces and machines\n- validate product requirements when building machine skeletons and payloads
2025-11-05 15:34:42 +01:00

306 lines
8.3 KiB
TypeScript

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,
},
});
}
}