feat: synchronize backend and frontend custom field handling
This commit is contained in:
@@ -4,11 +4,52 @@ import {
|
||||
CreateComposantDto,
|
||||
UpdateComposantDto,
|
||||
} from '../shared/dto/composant.dto';
|
||||
import {
|
||||
COMPONENT_WITH_RELATIONS_INCLUDE,
|
||||
ComposantWithRelations,
|
||||
} from '../common/constants/component-includes';
|
||||
import {
|
||||
buildComponentHierarchy,
|
||||
buildComponentSubtree,
|
||||
} from '../common/utils/component-tree.util';
|
||||
|
||||
@Injectable()
|
||||
export class ComposantsService {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
|
||||
private async fetchComponentsByMachine(
|
||||
machineId: string,
|
||||
): Promise<ComposantWithRelations[]> {
|
||||
return this.prisma.composant.findMany({
|
||||
where: { machineId },
|
||||
include: COMPONENT_WITH_RELATIONS_INCLUDE,
|
||||
}) as Promise<ComposantWithRelations[]>;
|
||||
}
|
||||
|
||||
private async getComponentWithHierarchy(
|
||||
id: string,
|
||||
): Promise<ComposantWithRelations | null> {
|
||||
const baseComponent = (await this.prisma.composant.findUnique({
|
||||
where: { id },
|
||||
include: COMPONENT_WITH_RELATIONS_INCLUDE,
|
||||
})) as ComposantWithRelations | null;
|
||||
|
||||
if (!baseComponent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!baseComponent.machineId) {
|
||||
baseComponent.sousComposants = [];
|
||||
return baseComponent;
|
||||
}
|
||||
|
||||
const components = await this.fetchComponentsByMachine(
|
||||
baseComponent.machineId,
|
||||
);
|
||||
const subtree = buildComponentSubtree(components, id);
|
||||
return subtree ?? baseComponent;
|
||||
}
|
||||
|
||||
async create(createComposantDto: CreateComposantDto) {
|
||||
const requirementId = createComposantDto.typeMachineComponentRequirementId;
|
||||
|
||||
@@ -77,434 +118,46 @@ export class ComposantsService {
|
||||
createComposantDto.typeComposantId ?? requirement.typeComposantId,
|
||||
};
|
||||
|
||||
return this.prisma.composant.create({
|
||||
const created = (await this.prisma.composant.create({
|
||||
data,
|
||||
include: {
|
||||
machine: true,
|
||||
parentComposant: true,
|
||||
typeComposant: true,
|
||||
composantModel: true,
|
||||
typeMachineComponentRequirement: {
|
||||
include: {
|
||||
typeComposant: true,
|
||||
},
|
||||
},
|
||||
constructeur: true,
|
||||
sousComposants: {
|
||||
include: {
|
||||
typeComposant: true,
|
||||
composantModel: true,
|
||||
typeMachineComponentRequirement: {
|
||||
include: {
|
||||
typeComposant: true,
|
||||
},
|
||||
},
|
||||
pieces: {
|
||||
include: {
|
||||
pieceModel: true,
|
||||
typeMachinePieceRequirement: {
|
||||
include: {
|
||||
typePiece: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
pieces: {
|
||||
include: {
|
||||
pieceModel: true,
|
||||
typeMachinePieceRequirement: {
|
||||
include: {
|
||||
typePiece: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
documents: true,
|
||||
},
|
||||
});
|
||||
include: COMPONENT_WITH_RELATIONS_INCLUDE,
|
||||
})) as ComposantWithRelations;
|
||||
|
||||
return this.getComponentWithHierarchy(created.id);
|
||||
}
|
||||
|
||||
async findAll() {
|
||||
return this.prisma.composant.findMany({
|
||||
include: {
|
||||
machine: true,
|
||||
parentComposant: true,
|
||||
typeComposant: true,
|
||||
composantModel: true,
|
||||
typeMachineComponentRequirement: {
|
||||
include: {
|
||||
typeComposant: true,
|
||||
},
|
||||
},
|
||||
constructeur: true,
|
||||
customFieldValues: {
|
||||
include: {
|
||||
customField: true,
|
||||
},
|
||||
},
|
||||
sousComposants: {
|
||||
include: {
|
||||
typeComposant: true,
|
||||
composantModel: true,
|
||||
typeMachineComponentRequirement: {
|
||||
include: {
|
||||
typeComposant: true,
|
||||
},
|
||||
},
|
||||
customFieldValues: {
|
||||
include: {
|
||||
customField: true,
|
||||
},
|
||||
},
|
||||
pieces: {
|
||||
include: {
|
||||
customFieldValues: {
|
||||
include: {
|
||||
customField: true,
|
||||
},
|
||||
},
|
||||
constructeur: true,
|
||||
pieceModel: true,
|
||||
typeMachinePieceRequirement: {
|
||||
include: {
|
||||
typePiece: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
constructeur: true,
|
||||
},
|
||||
},
|
||||
pieces: {
|
||||
include: {
|
||||
customFieldValues: {
|
||||
include: {
|
||||
customField: true,
|
||||
},
|
||||
},
|
||||
constructeur: true,
|
||||
pieceModel: true,
|
||||
typeMachinePieceRequirement: {
|
||||
include: {
|
||||
typePiece: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
documents: true,
|
||||
},
|
||||
});
|
||||
const components = (await this.prisma.composant.findMany({
|
||||
include: COMPONENT_WITH_RELATIONS_INCLUDE,
|
||||
})) as ComposantWithRelations[];
|
||||
|
||||
return buildComponentHierarchy(components);
|
||||
}
|
||||
|
||||
async findOne(id: string) {
|
||||
return this.prisma.composant.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
machine: true,
|
||||
parentComposant: true,
|
||||
typeComposant: true,
|
||||
composantModel: true,
|
||||
typeMachineComponentRequirement: {
|
||||
include: {
|
||||
typeComposant: true,
|
||||
},
|
||||
},
|
||||
constructeur: true,
|
||||
customFieldValues: {
|
||||
include: {
|
||||
customField: true,
|
||||
},
|
||||
},
|
||||
sousComposants: {
|
||||
include: {
|
||||
typeComposant: true,
|
||||
composantModel: true,
|
||||
typeMachineComponentRequirement: {
|
||||
include: {
|
||||
typeComposant: true,
|
||||
},
|
||||
},
|
||||
customFieldValues: {
|
||||
include: {
|
||||
customField: true,
|
||||
},
|
||||
},
|
||||
pieces: {
|
||||
include: {
|
||||
customFieldValues: {
|
||||
include: {
|
||||
customField: true,
|
||||
},
|
||||
},
|
||||
constructeur: true,
|
||||
pieceModel: true,
|
||||
typeMachinePieceRequirement: {
|
||||
include: {
|
||||
typePiece: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
constructeur: true,
|
||||
},
|
||||
},
|
||||
pieces: {
|
||||
include: {
|
||||
customFieldValues: {
|
||||
include: {
|
||||
customField: true,
|
||||
},
|
||||
},
|
||||
constructeur: true,
|
||||
pieceModel: true,
|
||||
typeMachinePieceRequirement: {
|
||||
include: {
|
||||
typePiece: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
documents: true,
|
||||
},
|
||||
});
|
||||
return this.getComponentWithHierarchy(id);
|
||||
}
|
||||
|
||||
async findByMachine(machineId: string) {
|
||||
return this.prisma.composant.findMany({
|
||||
where: { machineId },
|
||||
include: {
|
||||
machine: true,
|
||||
parentComposant: true,
|
||||
typeComposant: true,
|
||||
composantModel: true,
|
||||
typeMachineComponentRequirement: {
|
||||
include: {
|
||||
typeComposant: true,
|
||||
},
|
||||
},
|
||||
constructeur: true,
|
||||
sousComposants: {
|
||||
include: {
|
||||
typeComposant: true,
|
||||
composantModel: true,
|
||||
typeMachineComponentRequirement: {
|
||||
include: {
|
||||
typeComposant: true,
|
||||
},
|
||||
},
|
||||
pieces: true,
|
||||
constructeur: true,
|
||||
},
|
||||
},
|
||||
pieces: {
|
||||
include: {
|
||||
customFieldValues: {
|
||||
include: {
|
||||
customField: true,
|
||||
},
|
||||
},
|
||||
constructeur: true,
|
||||
pieceModel: true,
|
||||
typeMachinePieceRequirement: {
|
||||
include: {
|
||||
typePiece: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
customFieldValues: {
|
||||
include: {
|
||||
customField: true,
|
||||
},
|
||||
},
|
||||
documents: true,
|
||||
},
|
||||
});
|
||||
const components = await this.fetchComponentsByMachine(machineId);
|
||||
return buildComponentHierarchy(components);
|
||||
}
|
||||
|
||||
async findHierarchy(machineId: string) {
|
||||
// Récupérer tous les composants de premier niveau (sans parent)
|
||||
const rootComposants = await this.prisma.composant.findMany({
|
||||
where: {
|
||||
machineId,
|
||||
parentComposantId: null,
|
||||
},
|
||||
include: {
|
||||
typeComposant: true,
|
||||
composantModel: true,
|
||||
typeMachineComponentRequirement: {
|
||||
include: {
|
||||
typeComposant: true,
|
||||
},
|
||||
},
|
||||
constructeur: true,
|
||||
customFieldValues: {
|
||||
include: {
|
||||
customField: true,
|
||||
},
|
||||
},
|
||||
sousComposants: {
|
||||
include: {
|
||||
typeComposant: true,
|
||||
composantModel: true,
|
||||
typeMachineComponentRequirement: {
|
||||
include: {
|
||||
typeComposant: true,
|
||||
},
|
||||
},
|
||||
constructeur: true,
|
||||
customFieldValues: {
|
||||
include: {
|
||||
customField: true,
|
||||
},
|
||||
},
|
||||
pieces: {
|
||||
include: {
|
||||
customFieldValues: {
|
||||
include: {
|
||||
customField: true,
|
||||
},
|
||||
},
|
||||
constructeur: true,
|
||||
pieceModel: true,
|
||||
typeMachinePieceRequirement: {
|
||||
include: {
|
||||
typePiece: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
sousComposants: {
|
||||
include: {
|
||||
typeComposant: true,
|
||||
composantModel: true,
|
||||
typeMachineComponentRequirement: {
|
||||
include: {
|
||||
typeComposant: true,
|
||||
},
|
||||
},
|
||||
constructeur: true,
|
||||
customFieldValues: {
|
||||
include: {
|
||||
customField: true,
|
||||
},
|
||||
},
|
||||
pieces: {
|
||||
include: {
|
||||
customFieldValues: {
|
||||
include: {
|
||||
customField: true,
|
||||
},
|
||||
},
|
||||
constructeur: true,
|
||||
pieceModel: true,
|
||||
typeMachinePieceRequirement: {
|
||||
include: {
|
||||
typePiece: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
pieces: {
|
||||
include: {
|
||||
customFieldValues: {
|
||||
include: {
|
||||
customField: true,
|
||||
},
|
||||
},
|
||||
constructeur: true,
|
||||
pieceModel: true,
|
||||
typeMachinePieceRequirement: {
|
||||
include: {
|
||||
typePiece: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return rootComposants;
|
||||
const components = await this.fetchComponentsByMachine(machineId);
|
||||
return buildComponentHierarchy(components);
|
||||
}
|
||||
|
||||
async update(id: string, updateComposantDto: UpdateComposantDto) {
|
||||
return this.prisma.composant.update({
|
||||
const updated = (await this.prisma.composant.update({
|
||||
where: { id },
|
||||
data: updateComposantDto,
|
||||
include: {
|
||||
machine: true,
|
||||
parentComposant: true,
|
||||
typeComposant: true,
|
||||
composantModel: true,
|
||||
typeMachineComponentRequirement: {
|
||||
include: {
|
||||
typeComposant: true,
|
||||
},
|
||||
},
|
||||
constructeur: true,
|
||||
customFieldValues: {
|
||||
include: {
|
||||
customField: true,
|
||||
},
|
||||
},
|
||||
sousComposants: {
|
||||
include: {
|
||||
typeComposant: true,
|
||||
composantModel: true,
|
||||
typeMachineComponentRequirement: {
|
||||
include: {
|
||||
typeComposant: true,
|
||||
},
|
||||
},
|
||||
constructeur: true,
|
||||
customFieldValues: {
|
||||
include: {
|
||||
customField: true,
|
||||
},
|
||||
},
|
||||
pieces: {
|
||||
include: {
|
||||
customFieldValues: {
|
||||
include: {
|
||||
customField: true,
|
||||
},
|
||||
},
|
||||
constructeur: true,
|
||||
pieceModel: true,
|
||||
typeMachinePieceRequirement: {
|
||||
include: {
|
||||
typePiece: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
pieces: {
|
||||
include: {
|
||||
customFieldValues: {
|
||||
include: {
|
||||
customField: true,
|
||||
},
|
||||
},
|
||||
constructeur: true,
|
||||
pieceModel: true,
|
||||
typeMachinePieceRequirement: {
|
||||
include: {
|
||||
typePiece: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
documents: true,
|
||||
},
|
||||
});
|
||||
include: COMPONENT_WITH_RELATIONS_INCLUDE,
|
||||
})) as ComposantWithRelations;
|
||||
|
||||
await this.syncComponentModelCustomFields(updated);
|
||||
|
||||
return this.getComponentWithHierarchy(updated.id);
|
||||
}
|
||||
|
||||
private async resolveMachineIdFromComposant(
|
||||
@@ -543,4 +196,151 @@ export class ComposantsService {
|
||||
where: { id },
|
||||
});
|
||||
}
|
||||
|
||||
private async syncComponentModelCustomFields(
|
||||
component: ComposantWithRelations,
|
||||
) {
|
||||
const { composantModelId, typeComposantId } = component;
|
||||
if (!composantModelId || !typeComposantId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const model = await this.prisma.composantModel.findUnique({
|
||||
where: { id: composantModelId },
|
||||
select: { structure: true },
|
||||
});
|
||||
|
||||
if (!model?.structure) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.syncComponentStructureCustomFields(
|
||||
model.structure,
|
||||
typeComposantId,
|
||||
);
|
||||
}
|
||||
|
||||
private async syncComponentStructureCustomFields(
|
||||
structure: any,
|
||||
typeComposantId: string | null,
|
||||
) {
|
||||
if (typeComposantId) {
|
||||
await this.ensureCustomFieldsForType(
|
||||
'typeComposantId',
|
||||
typeComposantId,
|
||||
structure?.customFields,
|
||||
);
|
||||
}
|
||||
|
||||
const pieces = Array.isArray(structure?.pieces) ? structure.pieces : [];
|
||||
for (const piece of pieces) {
|
||||
const typePieceId = this.extractTypePieceId(piece);
|
||||
if (typePieceId) {
|
||||
await this.ensureCustomFieldsForType(
|
||||
'typePieceId',
|
||||
typePieceId,
|
||||
piece?.customFields,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const subComponents = Array.isArray(structure?.subComponents)
|
||||
? structure.subComponents
|
||||
: [];
|
||||
for (const sub of subComponents) {
|
||||
const subTypeId = this.extractTypeComposantId(sub);
|
||||
if (!subTypeId) {
|
||||
continue;
|
||||
}
|
||||
await this.syncComponentStructureCustomFields(sub, subTypeId);
|
||||
}
|
||||
}
|
||||
|
||||
private extractTypePieceId(entry: any): string | null {
|
||||
if (!entry || typeof entry !== 'object') {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
entry.typePieceId ||
|
||||
entry.typePiece?.id ||
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
private extractTypeComposantId(entry: any): string | null {
|
||||
if (!entry || typeof entry !== 'object') {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
entry.typeComposantId ||
|
||||
entry.typeComposant?.id ||
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
private async ensureCustomFieldsForType(
|
||||
typeKey: 'typeComposantId' | 'typePieceId',
|
||||
typeId: string | null,
|
||||
fields: any,
|
||||
) {
|
||||
if (!typeId || !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,
|
||||
[typeKey]: typeId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!existing) {
|
||||
await this.prisma.customField.create({
|
||||
data: {
|
||||
name,
|
||||
type,
|
||||
required,
|
||||
options,
|
||||
[typeKey]: typeId,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private normalizeOptions(field: any): string[] | undefined {
|
||||
if (Array.isArray(field?.options)) {
|
||||
const options = field.options
|
||||
.map((option: any) =>
|
||||
typeof option === 'string' ? option.trim() : '',
|
||||
)
|
||||
.filter((option: string) => option.length > 0);
|
||||
return options.length ? options : undefined;
|
||||
}
|
||||
|
||||
if (typeof field?.optionsText === 'string') {
|
||||
const options = field.optionsText
|
||||
.split(/\r?\n/)
|
||||
.map((option: string) => option.trim())
|
||||
.filter((option: string) => option.length > 0);
|
||||
return options.length ? options : undefined;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user