feat: synchronize backend and frontend custom field handling
This commit is contained in:
64
src/common/constants/component-includes.ts
Normal file
64
src/common/constants/component-includes.ts
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
import { Prisma } from '@prisma/client';
|
||||||
|
|
||||||
|
const CUSTOM_FIELD_SELECT = {
|
||||||
|
id: true,
|
||||||
|
name: true,
|
||||||
|
type: true,
|
||||||
|
required: true,
|
||||||
|
options: true,
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const COMPONENT_WITH_RELATIONS_INCLUDE = {
|
||||||
|
machine: true,
|
||||||
|
parentComposant: true,
|
||||||
|
typeComposant: {
|
||||||
|
include: {
|
||||||
|
customFields: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
composantModel: true,
|
||||||
|
typeMachineComponentRequirement: {
|
||||||
|
include: {
|
||||||
|
typeComposant: {
|
||||||
|
include: {
|
||||||
|
customFields: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
constructeur: true,
|
||||||
|
customFieldValues: {
|
||||||
|
include: {
|
||||||
|
customField: { select: CUSTOM_FIELD_SELECT },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pieces: {
|
||||||
|
include: {
|
||||||
|
customFieldValues: {
|
||||||
|
include: {
|
||||||
|
customField: { select: CUSTOM_FIELD_SELECT },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
constructeur: true,
|
||||||
|
pieceModel: true,
|
||||||
|
typeMachinePieceRequirement: {
|
||||||
|
include: {
|
||||||
|
typePiece: {
|
||||||
|
include: {
|
||||||
|
customFields: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
documents: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
documents: true,
|
||||||
|
} satisfies Prisma.ComposantInclude;
|
||||||
|
|
||||||
|
export interface ComposantWithRelations
|
||||||
|
extends Prisma.ComposantGetPayload<{
|
||||||
|
include: typeof COMPONENT_WITH_RELATIONS_INCLUDE;
|
||||||
|
}> {
|
||||||
|
sousComposants?: ComposantWithRelations[];
|
||||||
|
}
|
||||||
51
src/common/utils/component-tree.util.ts
Normal file
51
src/common/utils/component-tree.util.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
export interface HierarchicalComponent<T = any> {
|
||||||
|
id: string;
|
||||||
|
parentComposantId?: string | null;
|
||||||
|
sousComposants?: T[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildComponentHierarchy<T extends HierarchicalComponent<T>>(
|
||||||
|
components: readonly T[],
|
||||||
|
): T[] {
|
||||||
|
if (!Array.isArray(components) || components.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const byParent = new Map<string | null, T[]>();
|
||||||
|
|
||||||
|
components.forEach((raw) => {
|
||||||
|
const component = raw as HierarchicalComponent<T>;
|
||||||
|
const parentId = component.parentComposantId ?? null;
|
||||||
|
if (!byParent.has(parentId)) {
|
||||||
|
byParent.set(parentId, [] as T[]);
|
||||||
|
}
|
||||||
|
component.sousComposants = [] as T[];
|
||||||
|
byParent.get(parentId)!.push(component as T);
|
||||||
|
});
|
||||||
|
|
||||||
|
const attach = (component: T): T => {
|
||||||
|
const children = byParent.get(component.id) ?? [];
|
||||||
|
component.sousComposants = children.map(attach);
|
||||||
|
return component;
|
||||||
|
};
|
||||||
|
|
||||||
|
const roots = byParent.get(null) ?? [];
|
||||||
|
return roots.map(attach);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildComponentSubtree<T extends HierarchicalComponent<T>>(
|
||||||
|
components: T[],
|
||||||
|
rootId: string,
|
||||||
|
): T | null {
|
||||||
|
if (!Array.isArray(components) || components.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const map = new Map<string, T>();
|
||||||
|
components.forEach((component) => {
|
||||||
|
map.set(component.id, component);
|
||||||
|
});
|
||||||
|
|
||||||
|
buildComponentHierarchy(components);
|
||||||
|
return map.get(rootId) ?? null;
|
||||||
|
}
|
||||||
@@ -4,11 +4,52 @@ import {
|
|||||||
CreateComposantDto,
|
CreateComposantDto,
|
||||||
UpdateComposantDto,
|
UpdateComposantDto,
|
||||||
} from '../shared/dto/composant.dto';
|
} 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()
|
@Injectable()
|
||||||
export class ComposantsService {
|
export class ComposantsService {
|
||||||
constructor(private prisma: PrismaService) {}
|
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) {
|
async create(createComposantDto: CreateComposantDto) {
|
||||||
const requirementId = createComposantDto.typeMachineComponentRequirementId;
|
const requirementId = createComposantDto.typeMachineComponentRequirementId;
|
||||||
|
|
||||||
@@ -77,434 +118,46 @@ export class ComposantsService {
|
|||||||
createComposantDto.typeComposantId ?? requirement.typeComposantId,
|
createComposantDto.typeComposantId ?? requirement.typeComposantId,
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.prisma.composant.create({
|
const created = (await this.prisma.composant.create({
|
||||||
data,
|
data,
|
||||||
include: {
|
include: COMPONENT_WITH_RELATIONS_INCLUDE,
|
||||||
machine: true,
|
})) as ComposantWithRelations;
|
||||||
parentComposant: true,
|
|
||||||
typeComposant: true,
|
return this.getComponentWithHierarchy(created.id);
|
||||||
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,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async findAll() {
|
async findAll() {
|
||||||
return this.prisma.composant.findMany({
|
const components = (await this.prisma.composant.findMany({
|
||||||
include: {
|
include: COMPONENT_WITH_RELATIONS_INCLUDE,
|
||||||
machine: true,
|
})) as ComposantWithRelations[];
|
||||||
parentComposant: true,
|
|
||||||
typeComposant: true,
|
return buildComponentHierarchy(components);
|
||||||
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,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async findOne(id: string) {
|
async findOne(id: string) {
|
||||||
return this.prisma.composant.findUnique({
|
return this.getComponentWithHierarchy(id);
|
||||||
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,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async findByMachine(machineId: string) {
|
async findByMachine(machineId: string) {
|
||||||
return this.prisma.composant.findMany({
|
const components = await this.fetchComponentsByMachine(machineId);
|
||||||
where: { machineId },
|
return buildComponentHierarchy(components);
|
||||||
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,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async findHierarchy(machineId: string) {
|
async findHierarchy(machineId: string) {
|
||||||
// Récupérer tous les composants de premier niveau (sans parent)
|
const components = await this.fetchComponentsByMachine(machineId);
|
||||||
const rootComposants = await this.prisma.composant.findMany({
|
return buildComponentHierarchy(components);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async update(id: string, updateComposantDto: UpdateComposantDto) {
|
async update(id: string, updateComposantDto: UpdateComposantDto) {
|
||||||
return this.prisma.composant.update({
|
const updated = (await this.prisma.composant.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: updateComposantDto,
|
data: updateComposantDto,
|
||||||
include: {
|
include: COMPONENT_WITH_RELATIONS_INCLUDE,
|
||||||
machine: true,
|
})) as ComposantWithRelations;
|
||||||
parentComposant: true,
|
|
||||||
typeComposant: true,
|
await this.syncComponentModelCustomFields(updated);
|
||||||
composantModel: true,
|
|
||||||
typeMachineComponentRequirement: {
|
return this.getComponentWithHierarchy(updated.id);
|
||||||
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,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async resolveMachineIdFromComposant(
|
private async resolveMachineIdFromComposant(
|
||||||
@@ -543,4 +196,151 @@ export class ComposantsService {
|
|||||||
where: { id },
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { ModelCategory } from '@prisma/client';
|
import { Prisma, ModelCategory } from '@prisma/client';
|
||||||
import { PrismaService } from '../prisma/prisma.service';
|
import { PrismaService } from '../prisma/prisma.service';
|
||||||
import {
|
import {
|
||||||
CreateMachineDto,
|
CreateMachineDto,
|
||||||
@@ -8,6 +8,11 @@ import {
|
|||||||
MachineComponentSelectionDto,
|
MachineComponentSelectionDto,
|
||||||
MachinePieceSelectionDto,
|
MachinePieceSelectionDto,
|
||||||
} from '../shared/dto/machine.dto';
|
} from '../shared/dto/machine.dto';
|
||||||
|
import {
|
||||||
|
COMPONENT_WITH_RELATIONS_INCLUDE,
|
||||||
|
ComposantWithRelations,
|
||||||
|
} from '../common/constants/component-includes';
|
||||||
|
import { buildComponentHierarchy } from '../common/utils/component-tree.util';
|
||||||
|
|
||||||
const CUSTOM_FIELD_SELECT = {
|
const CUSTOM_FIELD_SELECT = {
|
||||||
id: true,
|
id: true,
|
||||||
@@ -17,16 +22,24 @@ const CUSTOM_FIELD_SELECT = {
|
|||||||
options: true,
|
options: true,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
const TYPE_MACHINE_CONFIGURATION_INCLUDE = {
|
const TYPE_MACHINE_CONFIGURATION_INCLUDE: Prisma.TypeMachineInclude = {
|
||||||
customFields: { select: CUSTOM_FIELD_SELECT },
|
customFields: { select: CUSTOM_FIELD_SELECT },
|
||||||
componentRequirements: {
|
componentRequirements: {
|
||||||
include: {
|
include: {
|
||||||
typeComposant: true,
|
typeComposant: {
|
||||||
|
include: {
|
||||||
|
customFields: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
pieceRequirements: {
|
pieceRequirements: {
|
||||||
include: {
|
include: {
|
||||||
typePiece: true,
|
typePiece: {
|
||||||
|
include: {
|
||||||
|
customFields: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -38,38 +51,7 @@ const MACHINE_DEFAULT_INCLUDE = {
|
|||||||
},
|
},
|
||||||
constructeur: true,
|
constructeur: true,
|
||||||
composants: {
|
composants: {
|
||||||
include: {
|
include: COMPONENT_WITH_RELATIONS_INCLUDE,
|
||||||
typeComposant: true,
|
|
||||||
composantModel: true,
|
|
||||||
typeMachineComponentRequirement: {
|
|
||||||
include: {
|
|
||||||
typeComposant: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
sousComposants: true,
|
|
||||||
customFieldValues: {
|
|
||||||
include: {
|
|
||||||
customField: { select: CUSTOM_FIELD_SELECT },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
constructeur: true,
|
|
||||||
pieces: {
|
|
||||||
include: {
|
|
||||||
customFieldValues: {
|
|
||||||
include: {
|
|
||||||
customField: { select: CUSTOM_FIELD_SELECT },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
constructeur: true,
|
|
||||||
pieceModel: true,
|
|
||||||
typeMachinePieceRequirement: {
|
|
||||||
include: {
|
|
||||||
typePiece: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
pieces: {
|
pieces: {
|
||||||
include: {
|
include: {
|
||||||
@@ -79,12 +61,22 @@ const MACHINE_DEFAULT_INCLUDE = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
constructeur: true,
|
constructeur: true,
|
||||||
|
typePiece: {
|
||||||
|
include: {
|
||||||
|
customFields: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
pieceModel: true,
|
pieceModel: true,
|
||||||
typeMachinePieceRequirement: {
|
typeMachinePieceRequirement: {
|
||||||
include: {
|
include: {
|
||||||
typePiece: true,
|
typePiece: {
|
||||||
|
include: {
|
||||||
|
customFields: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
documents: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
customFieldValues: {
|
customFieldValues: {
|
||||||
@@ -93,12 +85,36 @@ const MACHINE_DEFAULT_INCLUDE = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
documents: true,
|
documents: true,
|
||||||
};
|
} satisfies Prisma.MachineInclude;
|
||||||
|
|
||||||
|
type MachineWithRelations = Prisma.MachineGetPayload<{
|
||||||
|
include: typeof MACHINE_DEFAULT_INCLUDE;
|
||||||
|
}>;
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MachinesService {
|
export class MachinesService {
|
||||||
constructor(private prisma: PrismaService) {}
|
constructor(private prisma: PrismaService) {}
|
||||||
|
|
||||||
|
private hydrateMachine(
|
||||||
|
machine: MachineWithRelations | null,
|
||||||
|
): MachineWithRelations | null {
|
||||||
|
if (!machine || !Array.isArray(machine.composants)) {
|
||||||
|
return machine;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hierarchy = buildComponentHierarchy(
|
||||||
|
machine.composants as ComposantWithRelations[],
|
||||||
|
);
|
||||||
|
machine.composants = hierarchy as typeof machine.composants;
|
||||||
|
return machine;
|
||||||
|
}
|
||||||
|
|
||||||
|
private hydrateMachines(
|
||||||
|
machines: MachineWithRelations[],
|
||||||
|
): MachineWithRelations[] {
|
||||||
|
return machines.map((machine) => this.hydrateMachine(machine)!);
|
||||||
|
}
|
||||||
|
|
||||||
private slugifyName(name: string): string {
|
private slugifyName(name: string): string {
|
||||||
return name
|
return name
|
||||||
.normalize('NFD')
|
.normalize('NFD')
|
||||||
@@ -378,7 +394,7 @@ export class MachinesService {
|
|||||||
: []
|
: []
|
||||||
) as any[];
|
) as any[];
|
||||||
|
|
||||||
return this.prisma.$transaction(async (prisma) => {
|
const machine = await this.prisma.$transaction(async (prisma) => {
|
||||||
const machine = await prisma.machine.create({
|
const machine = await prisma.machine.create({
|
||||||
data: machineData,
|
data: machineData,
|
||||||
include: {
|
include: {
|
||||||
@@ -449,6 +465,7 @@ export class MachinesService {
|
|||||||
prisma,
|
prisma,
|
||||||
machine.id,
|
machine.id,
|
||||||
typeMachine.customFields,
|
typeMachine.customFields,
|
||||||
|
typeMachine.id,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -457,6 +474,8 @@ export class MachinesService {
|
|||||||
include: MACHINE_DEFAULT_INCLUDE,
|
include: MACHINE_DEFAULT_INCLUDE,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return this.hydrateMachine(machine);
|
||||||
}
|
}
|
||||||
|
|
||||||
private cloneStructure(definition: any): any {
|
private cloneStructure(definition: any): any {
|
||||||
@@ -896,26 +915,36 @@ export class MachinesService {
|
|||||||
prisma: any,
|
prisma: any,
|
||||||
machineId: string,
|
machineId: string,
|
||||||
machineCustomFields: any[],
|
machineCustomFields: any[],
|
||||||
|
typeMachineId?: string,
|
||||||
) {
|
) {
|
||||||
for (const customField of machineCustomFields) {
|
for (const customField of machineCustomFields) {
|
||||||
if (!customField || !customField.name) continue;
|
if (!customField || !customField.name) continue;
|
||||||
|
|
||||||
const createdCustomField = await prisma.customField.create({
|
const existingCustomFieldId =
|
||||||
data: {
|
customField.id ?? customField.customFieldId ?? null;
|
||||||
name: customField.name,
|
|
||||||
type: customField.type,
|
let targetCustomFieldId = existingCustomFieldId;
|
||||||
required: customField.required || false,
|
|
||||||
options: customField.options || [],
|
if (!targetCustomFieldId) {
|
||||||
typeMachineId: null, // Ce champ sera lié à la machine individuelle
|
const createdCustomField = await prisma.customField.create({
|
||||||
},
|
data: {
|
||||||
});
|
name: customField.name,
|
||||||
|
type: customField.type,
|
||||||
|
required: customField.required || false,
|
||||||
|
options: customField.options || [],
|
||||||
|
typeMachineId: typeMachineId ?? null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
targetCustomFieldId = createdCustomField.id;
|
||||||
|
}
|
||||||
|
|
||||||
const providedValue = this.extractCustomFieldValue(customField);
|
const providedValue = this.extractCustomFieldValue(customField);
|
||||||
if (providedValue !== undefined) {
|
if (providedValue !== undefined && targetCustomFieldId) {
|
||||||
await prisma.customFieldValue.create({
|
await prisma.customFieldValue.create({
|
||||||
data: {
|
data: {
|
||||||
value: providedValue,
|
value: providedValue,
|
||||||
customFieldId: createdCustomField.id,
|
customFieldId: targetCustomFieldId,
|
||||||
machineId,
|
machineId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -924,16 +953,20 @@ export class MachinesService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async findAll() {
|
async findAll() {
|
||||||
return this.prisma.machine.findMany({
|
const machines = await this.prisma.machine.findMany({
|
||||||
include: MACHINE_DEFAULT_INCLUDE,
|
include: MACHINE_DEFAULT_INCLUDE,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return this.hydrateMachines(machines);
|
||||||
}
|
}
|
||||||
|
|
||||||
async findOne(id: string) {
|
async findOne(id: string) {
|
||||||
return this.prisma.machine.findUnique({
|
const machine = await this.prisma.machine.findUnique({
|
||||||
where: { id },
|
where: { id },
|
||||||
include: MACHINE_DEFAULT_INCLUDE,
|
include: MACHINE_DEFAULT_INCLUDE,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return this.hydrateMachine(machine);
|
||||||
}
|
}
|
||||||
|
|
||||||
async reconfigure(id: string, reconfigureMachineDto: ReconfigureMachineDto) {
|
async reconfigure(id: string, reconfigureMachineDto: ReconfigureMachineDto) {
|
||||||
@@ -983,7 +1016,7 @@ export class MachinesService {
|
|||||||
: []
|
: []
|
||||||
) as any[];
|
) as any[];
|
||||||
|
|
||||||
return this.prisma.$transaction(async (prisma) => {
|
const updatedMachine = await this.prisma.$transaction(async (prisma) => {
|
||||||
await prisma.customFieldValue.deleteMany({
|
await prisma.customFieldValue.deleteMany({
|
||||||
where: {
|
where: {
|
||||||
OR: [
|
OR: [
|
||||||
@@ -1074,14 +1107,18 @@ export class MachinesService {
|
|||||||
include: MACHINE_DEFAULT_INCLUDE,
|
include: MACHINE_DEFAULT_INCLUDE,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return this.hydrateMachine(updatedMachine);
|
||||||
}
|
}
|
||||||
|
|
||||||
async update(id: string, updateMachineDto: UpdateMachineDto) {
|
async update(id: string, updateMachineDto: UpdateMachineDto) {
|
||||||
return this.prisma.machine.update({
|
const machine = await this.prisma.machine.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: updateMachineDto,
|
data: updateMachineDto,
|
||||||
include: MACHINE_DEFAULT_INCLUDE,
|
include: MACHINE_DEFAULT_INCLUDE,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return this.hydrateMachine(machine);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async resolveConstructeurId(prisma: any, rawName?: string) {
|
private async resolveConstructeurId(prisma: any, rawName?: string) {
|
||||||
@@ -1200,23 +1237,40 @@ export class MachinesService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!existingValue) {
|
if (!existingValue) {
|
||||||
// Créer le champ personnalisé pour la machine
|
const resolvedCustomFieldId = customField.id
|
||||||
const createdCustomField = await this.prisma.customField.create({
|
? customField.id
|
||||||
data: {
|
: (
|
||||||
name: customField.name,
|
await this.prisma.customField.findFirst({
|
||||||
type: customField.type,
|
where: {
|
||||||
required: customField.required || false,
|
name: customField.name,
|
||||||
options: customField.options || [],
|
typeMachineId: machine.typeMachineId,
|
||||||
typeMachineId: null, // Ce champ sera lié à la machine individuelle
|
},
|
||||||
},
|
select: { id: true },
|
||||||
});
|
})
|
||||||
|
)?.id;
|
||||||
|
|
||||||
|
let targetCustomFieldId = resolvedCustomFieldId;
|
||||||
|
|
||||||
|
if (!targetCustomFieldId) {
|
||||||
|
const createdCustomField = await this.prisma.customField.create({
|
||||||
|
data: {
|
||||||
|
name: customField.name,
|
||||||
|
type: customField.type,
|
||||||
|
required: customField.required || false,
|
||||||
|
options: customField.options || [],
|
||||||
|
typeMachineId: machine.typeMachineId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
targetCustomFieldId = createdCustomField.id;
|
||||||
|
}
|
||||||
|
|
||||||
const providedValue = this.extractCustomFieldValue(customField);
|
const providedValue = this.extractCustomFieldValue(customField);
|
||||||
if (providedValue !== undefined) {
|
if (providedValue !== undefined && targetCustomFieldId) {
|
||||||
await this.prisma.customFieldValue.create({
|
await this.prisma.customFieldValue.create({
|
||||||
data: {
|
data: {
|
||||||
value: providedValue,
|
value: providedValue,
|
||||||
customFieldId: createdCustomField.id,
|
customFieldId: targetCustomFieldId,
|
||||||
machineId,
|
machineId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,6 +2,33 @@ import { BadRequestException, Injectable } from '@nestjs/common';
|
|||||||
import { PrismaService } from '../prisma/prisma.service';
|
import { PrismaService } from '../prisma/prisma.service';
|
||||||
import { CreatePieceDto, UpdatePieceDto } from '../shared/dto/piece.dto';
|
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()
|
@Injectable()
|
||||||
export class PiecesService {
|
export class PiecesService {
|
||||||
constructor(private prisma: PrismaService) {}
|
constructor(private prisma: PrismaService) {}
|
||||||
@@ -146,24 +173,7 @@ export class PiecesService {
|
|||||||
async findByMachine(machineId: string) {
|
async findByMachine(machineId: string) {
|
||||||
return this.prisma.piece.findMany({
|
return this.prisma.piece.findMany({
|
||||||
where: { machineId },
|
where: { machineId },
|
||||||
include: {
|
include: PIECE_WITH_RELATIONS_INCLUDE,
|
||||||
machine: true,
|
|
||||||
composant: true,
|
|
||||||
typePiece: true,
|
|
||||||
documents: true,
|
|
||||||
constructeur: true,
|
|
||||||
pieceModel: true,
|
|
||||||
typeMachinePieceRequirement: {
|
|
||||||
include: {
|
|
||||||
typePiece: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
customFieldValues: {
|
|
||||||
include: {
|
|
||||||
customField: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,39 +209,20 @@ export class PiecesService {
|
|||||||
async findByComposant(composantId: string) {
|
async findByComposant(composantId: string) {
|
||||||
return this.prisma.piece.findMany({
|
return this.prisma.piece.findMany({
|
||||||
where: { composantId },
|
where: { composantId },
|
||||||
include: {
|
include: PIECE_WITH_RELATIONS_INCLUDE,
|
||||||
machine: true,
|
|
||||||
composant: true,
|
|
||||||
typePiece: true,
|
|
||||||
documents: true,
|
|
||||||
constructeur: true,
|
|
||||||
pieceModel: true,
|
|
||||||
typeMachinePieceRequirement: {
|
|
||||||
include: {
|
|
||||||
typePiece: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
customFieldValues: {
|
|
||||||
include: {
|
|
||||||
customField: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async update(id: string, updatePieceDto: UpdatePieceDto) {
|
async update(id: string, updatePieceDto: UpdatePieceDto) {
|
||||||
return this.prisma.piece.update({
|
const updated = await this.prisma.piece.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: updatePieceDto,
|
data: updatePieceDto,
|
||||||
include: {
|
include: PIECE_WITH_RELATIONS_INCLUDE,
|
||||||
machine: true,
|
|
||||||
composant: true,
|
|
||||||
typePiece: true,
|
|
||||||
documents: true,
|
|
||||||
constructeur: true,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await this.syncPieceModelCustomFields(updated);
|
||||||
|
|
||||||
|
return updated;
|
||||||
}
|
}
|
||||||
|
|
||||||
async remove(id: string) {
|
async remove(id: string) {
|
||||||
@@ -239,4 +230,137 @@ export class PiecesService {
|
|||||||
where: { id },
|
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 : [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user