feat: synchronize backend and frontend custom field handling

This commit is contained in:
Matthieu
2025-09-30 15:35:32 +02:00
parent bd058cd533
commit 5a366595e6
5 changed files with 613 additions and 520 deletions

View File

@@ -2,6 +2,33 @@ import { BadRequestException, Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { CreatePieceDto, UpdatePieceDto } from '../shared/dto/piece.dto';
const PIECE_WITH_RELATIONS_INCLUDE = {
machine: true,
composant: true,
typePiece: {
include: {
customFields: true,
},
},
documents: true,
constructeur: true,
pieceModel: true,
typeMachinePieceRequirement: {
include: {
typePiece: {
include: {
customFields: true,
},
},
},
},
customFieldValues: {
include: {
customField: true,
},
},
} as const;
@Injectable()
export class PiecesService {
constructor(private prisma: PrismaService) {}
@@ -146,24 +173,7 @@ export class PiecesService {
async findByMachine(machineId: string) {
return this.prisma.piece.findMany({
where: { machineId },
include: {
machine: true,
composant: true,
typePiece: true,
documents: true,
constructeur: true,
pieceModel: true,
typeMachinePieceRequirement: {
include: {
typePiece: true,
},
},
customFieldValues: {
include: {
customField: true,
},
},
},
include: PIECE_WITH_RELATIONS_INCLUDE,
});
}
@@ -199,39 +209,20 @@ export class PiecesService {
async findByComposant(composantId: string) {
return this.prisma.piece.findMany({
where: { composantId },
include: {
machine: true,
composant: true,
typePiece: true,
documents: true,
constructeur: true,
pieceModel: true,
typeMachinePieceRequirement: {
include: {
typePiece: true,
},
},
customFieldValues: {
include: {
customField: true,
},
},
},
include: PIECE_WITH_RELATIONS_INCLUDE,
});
}
async update(id: string, updatePieceDto: UpdatePieceDto) {
return this.prisma.piece.update({
const updated = await this.prisma.piece.update({
where: { id },
data: updatePieceDto,
include: {
machine: true,
composant: true,
typePiece: true,
documents: true,
constructeur: true,
},
include: PIECE_WITH_RELATIONS_INCLUDE,
});
await this.syncPieceModelCustomFields(updated);
return updated;
}
async remove(id: string) {
@@ -239,4 +230,137 @@ export class PiecesService {
where: { id },
});
}
private async syncPieceModelCustomFields(piece: any) {
const pieceModelId = piece?.pieceModelId;
if (!pieceModelId) {
return;
}
const model = await this.prisma.pieceModel.findUnique({
where: { id: pieceModelId },
select: { structure: true },
});
if (!model?.structure) {
return;
}
const structure = this.asRecord(model.structure);
const customFields = this.extractCustomFields(structure);
const targetTypePieceId = this.getTypePieceIdForPiece(piece, structure);
if (!targetTypePieceId) {
return;
}
await this.ensureCustomFieldsForType(
targetTypePieceId,
customFields,
);
}
private async ensureCustomFieldsForType(
typePieceId: string,
fields: any,
) {
if (!typePieceId || !Array.isArray(fields)) {
return;
}
for (const field of fields) {
if (!field || typeof field !== 'object') {
continue;
}
const name = typeof field.name === 'string' ? field.name.trim() : '';
if (!name) {
continue;
}
const type = typeof field.type === 'string' && field.type.trim()
? field.type.trim()
: 'text';
const required = !!field.required;
const options = this.normalizeOptions(field);
const existing = await this.prisma.customField.findFirst({
where: {
name,
type,
typePieceId,
},
});
if (!existing) {
await this.prisma.customField.create({
data: {
name,
type,
required,
options,
typePieceId,
},
});
}
}
}
private normalizeOptions(field: any): string[] | undefined {
if (Array.isArray(field?.options)) {
const normalized = field.options
.map((option: any) =>
typeof option === 'string' ? option.trim() : '',
)
.filter((option: string) => option.length > 0);
return normalized.length ? normalized : undefined;
}
if (typeof field?.optionsText === 'string') {
const normalized = field.optionsText
.split(/\r?\n/)
.map((option: string) => option.trim())
.filter((option: string) => option.length > 0);
return normalized.length ? normalized : undefined;
}
return undefined;
}
private getTypePieceIdForPiece(
piece: any,
modelStructure: Record<string, any> | null,
): string | null {
const structure = this.asRecord(modelStructure);
const structureTypePiece = this.asRecord(structure?.typePiece ?? null);
return (
piece?.typePieceId ||
piece?.typePiece?.id ||
piece?.typeMachinePieceRequirement?.typePieceId ||
piece?.typeMachinePieceRequirement?.typePiece?.id ||
structure?.typePieceId ||
structureTypePiece?.id ||
null
);
}
private asRecord(value: unknown): Record<string, any> | null {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
return null;
}
return value as Record<string, any>;
}
private extractCustomFields(structure: Record<string, any> | null): any[] {
if (!structure) {
return [];
}
const { customFields } = structure;
return Array.isArray(customFields) ? customFields : [];
}
}