- 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
306 lines
8.3 KiB
TypeScript
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,
|
|
},
|
|
});
|
|
}
|
|
}
|