Migrate away from legacy component and piece models
This commit is contained in:
@@ -16,7 +16,6 @@ export const COMPONENT_WITH_RELATIONS_INCLUDE = {
|
||||
customFields: true,
|
||||
},
|
||||
},
|
||||
composantModel: true,
|
||||
typeMachineComponentRequirement: {
|
||||
include: {
|
||||
typeComposant: {
|
||||
@@ -40,7 +39,6 @@ export const COMPONENT_WITH_RELATIONS_INCLUDE = {
|
||||
},
|
||||
},
|
||||
constructeur: true,
|
||||
pieceModel: true,
|
||||
typeMachinePieceRequirement: {
|
||||
include: {
|
||||
typePiece: {
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { ModelTypeMapper } from './model-type.mapper';
|
||||
import {
|
||||
ComponentModelStructureSchema,
|
||||
PieceModelStructureSchema,
|
||||
} from '../../shared/schemas/inventory';
|
||||
|
||||
describe('ModelTypeMapper', () => {
|
||||
it('should map component create input', () => {
|
||||
@@ -8,9 +12,30 @@ describe('ModelTypeMapper', () => {
|
||||
customFields: [
|
||||
{ name: 'Field', type: 'string', required: false, options: [] },
|
||||
],
|
||||
structure: {
|
||||
pieces: [
|
||||
{
|
||||
familyCode: 'bolt',
|
||||
role: 'Fixation',
|
||||
},
|
||||
],
|
||||
customFields: [
|
||||
{
|
||||
key: 'color',
|
||||
value: 'red',
|
||||
},
|
||||
],
|
||||
subcomponents: [
|
||||
{
|
||||
familyCode: 'sub-family',
|
||||
alias: 'Secondary',
|
||||
},
|
||||
],
|
||||
},
|
||||
} as any;
|
||||
|
||||
const input = ModelTypeMapper.toComponentCreateInput(dto, 'code');
|
||||
const skeleton = ComponentModelStructureSchema.parse(dto.structure);
|
||||
const input = ModelTypeMapper.toComponentCreateInput(dto, 'code', skeleton);
|
||||
|
||||
expect(input).toMatchObject({
|
||||
name: 'Comp',
|
||||
@@ -19,6 +44,72 @@ describe('ModelTypeMapper', () => {
|
||||
notes: 'Desc',
|
||||
});
|
||||
expect(input.customFields?.create?.[0]).toMatchObject({ name: 'Field' });
|
||||
expect((input as any).componentSkeleton).toEqual({
|
||||
pieces: [
|
||||
{
|
||||
familyCode: 'bolt',
|
||||
role: 'Fixation',
|
||||
},
|
||||
],
|
||||
customFields: [
|
||||
{
|
||||
key: 'color',
|
||||
value: 'red',
|
||||
},
|
||||
],
|
||||
subcomponents: [
|
||||
{
|
||||
familyCode: 'sub-family',
|
||||
alias: 'Secondary',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('should map piece create input with skeleton', () => {
|
||||
const dto = {
|
||||
name: 'Piece type',
|
||||
description: 'Desc',
|
||||
customFields: [],
|
||||
structure: {
|
||||
customFields: [
|
||||
{
|
||||
name: 'Length',
|
||||
value: 12,
|
||||
type: 'number',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
key: 'color',
|
||||
value: 'blue',
|
||||
optionsText: 'blue\nred',
|
||||
},
|
||||
],
|
||||
typePieceId: ' piece-id ',
|
||||
standard: 'ISO',
|
||||
},
|
||||
} as any;
|
||||
|
||||
const skeleton = PieceModelStructureSchema.parse(dto.structure);
|
||||
const input = ModelTypeMapper.toPieceCreateInput(dto, 'code', skeleton);
|
||||
|
||||
expect((input as any).pieceSkeleton).toEqual({
|
||||
customFields: [
|
||||
{
|
||||
name: 'Length',
|
||||
value: 12,
|
||||
type: 'number',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'color',
|
||||
value: 'blue',
|
||||
options: ['blue', 'red'],
|
||||
},
|
||||
],
|
||||
typePieceId: 'piece-id',
|
||||
standard: 'ISO',
|
||||
});
|
||||
});
|
||||
|
||||
it('should map piece model type to DTO shape', () => {
|
||||
@@ -26,7 +117,7 @@ describe('ModelTypeMapper', () => {
|
||||
id: '1',
|
||||
name: 'Piece',
|
||||
pieceCustomFields: [{ id: 'cf' }],
|
||||
pieceModels: [{ id: 'model' }],
|
||||
pieceSkeleton: { customFields: [{ name: 'Length' }] },
|
||||
pieceRequirements: [{ id: 'req' }],
|
||||
pieces: [{ id: 'piece' }],
|
||||
});
|
||||
@@ -34,18 +125,21 @@ describe('ModelTypeMapper', () => {
|
||||
expect(mapped).toMatchObject({
|
||||
id: '1',
|
||||
customFields: [{ id: 'cf' }],
|
||||
models: [{ id: 'model' }],
|
||||
pieceRequirements: [{ id: 'req' }],
|
||||
pieces: [{ id: 'piece' }],
|
||||
structure: { customFields: [{ name: 'Length' }] },
|
||||
});
|
||||
});
|
||||
|
||||
it('should map piece update input', () => {
|
||||
const input = ModelTypeMapper.toPieceUpdateInput({
|
||||
const dto: any = {
|
||||
name: 'New',
|
||||
description: 'D',
|
||||
customFields: [],
|
||||
} as any);
|
||||
structure: { customFields: [{ name: 'Length' }] },
|
||||
};
|
||||
const skeleton = PieceModelStructureSchema.parse(dto.structure);
|
||||
const input = ModelTypeMapper.toPieceUpdateInput(dto, skeleton);
|
||||
|
||||
expect(input).toMatchObject({
|
||||
name: 'New',
|
||||
@@ -53,5 +147,36 @@ describe('ModelTypeMapper', () => {
|
||||
notes: 'D',
|
||||
});
|
||||
expect(input.pieceCustomFields).toBeUndefined();
|
||||
expect((input as any).pieceSkeleton).toEqual({
|
||||
customFields: [
|
||||
{
|
||||
name: 'Length',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('should map component update input with skeleton', () => {
|
||||
const dto: any = {
|
||||
name: 'Updated',
|
||||
structure: {
|
||||
pieces: [{ typePieceId: 'piece-1' }],
|
||||
customFields: [],
|
||||
subcomponents: [],
|
||||
},
|
||||
};
|
||||
const skeleton = ComponentModelStructureSchema.parse(dto.structure);
|
||||
const input = ModelTypeMapper.toComponentUpdateInput(dto, skeleton);
|
||||
|
||||
expect(input).toMatchObject({ name: 'Updated' });
|
||||
expect((input as any).componentSkeleton).toEqual({
|
||||
pieces: [
|
||||
{
|
||||
typePieceId: 'piece-1',
|
||||
},
|
||||
],
|
||||
customFields: [],
|
||||
subcomponents: [],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,17 +5,19 @@ import {
|
||||
UpdateTypeComposantDto,
|
||||
UpdateTypePieceDto,
|
||||
} from '../../shared/dto/type.dto';
|
||||
import type {
|
||||
ComponentModelStructure,
|
||||
PieceModelStructure,
|
||||
} from '../../shared/types/inventory';
|
||||
import { CUSTOM_FIELD_SELECT } from '../constants/custom-field.constant';
|
||||
|
||||
export const COMPONENT_TYPE_INCLUDE: Prisma.ModelTypeInclude = {
|
||||
customFields: { select: CUSTOM_FIELD_SELECT },
|
||||
composants: true,
|
||||
models: true,
|
||||
};
|
||||
|
||||
export const PIECE_TYPE_INCLUDE: Prisma.ModelTypeInclude = {
|
||||
pieceCustomFields: { select: CUSTOM_FIELD_SELECT },
|
||||
pieceModels: true,
|
||||
pieceRequirements: true,
|
||||
pieces: true,
|
||||
};
|
||||
@@ -29,6 +31,7 @@ export class ModelTypeMapper {
|
||||
static toComponentCreateInput(
|
||||
dto: CreateTypeComposantDto,
|
||||
code: string,
|
||||
skeleton?: ComponentModelStructure,
|
||||
): ModelTypeCreateWithoutCategory {
|
||||
const { customFields, description, name } = dto;
|
||||
|
||||
@@ -47,11 +50,13 @@ export class ModelTypeMapper {
|
||||
})),
|
||||
}
|
||||
: undefined,
|
||||
...(skeleton ? { componentSkeleton: skeleton as Prisma.InputJsonValue } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
static toComponentUpdateInput(
|
||||
dto: UpdateTypeComposantDto,
|
||||
skeleton?: ComponentModelStructure,
|
||||
): Prisma.ModelTypeUpdateInput {
|
||||
const { customFields, description, name } = dto;
|
||||
const data: Prisma.ModelTypeUpdateInput = {};
|
||||
@@ -69,12 +74,17 @@ export class ModelTypeMapper {
|
||||
data.customFields = undefined;
|
||||
}
|
||||
|
||||
if (skeleton !== undefined) {
|
||||
data.componentSkeleton = skeleton as Prisma.InputJsonValue;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
static toPieceCreateInput(
|
||||
dto: CreateTypePieceDto,
|
||||
code: string,
|
||||
skeleton?: PieceModelStructure,
|
||||
): ModelTypeCreateWithoutCategory {
|
||||
const { customFields, description, name } = dto;
|
||||
|
||||
@@ -93,11 +103,13 @@ export class ModelTypeMapper {
|
||||
})),
|
||||
}
|
||||
: undefined,
|
||||
...(skeleton ? { pieceSkeleton: skeleton as Prisma.InputJsonValue } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
static toPieceUpdateInput(
|
||||
dto: UpdateTypePieceDto,
|
||||
skeleton?: PieceModelStructure,
|
||||
): Prisma.ModelTypeUpdateInput {
|
||||
const { customFields, description, name } = dto;
|
||||
const data: Prisma.ModelTypeUpdateInput = {};
|
||||
@@ -115,6 +127,10 @@ export class ModelTypeMapper {
|
||||
data.pieceCustomFields = undefined;
|
||||
}
|
||||
|
||||
if (skeleton !== undefined) {
|
||||
data.pieceSkeleton = skeleton as Prisma.InputJsonValue;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -125,18 +141,18 @@ export class ModelTypeMapper {
|
||||
|
||||
const {
|
||||
pieceCustomFields,
|
||||
pieceModels,
|
||||
pieceRequirements,
|
||||
pieces,
|
||||
pieceSkeleton,
|
||||
...rest
|
||||
} = modelType;
|
||||
|
||||
return {
|
||||
...rest,
|
||||
customFields: pieceCustomFields ?? [],
|
||||
models: pieceModels ?? [],
|
||||
pieceRequirements: pieceRequirements ?? [],
|
||||
pieces: pieces ?? [],
|
||||
structure: pieceSkeleton ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Prisma, PrismaClient } from '@prisma/client';
|
||||
import { PrismaService } from '../../prisma/prisma.service';
|
||||
|
||||
@Injectable()
|
||||
export class ComposantModelsRepository {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
private get client(): PrismaClient {
|
||||
return this.prisma;
|
||||
}
|
||||
|
||||
async create(
|
||||
data: Prisma.ComposantModelCreateInput,
|
||||
include?: Prisma.ComposantModelInclude,
|
||||
) {
|
||||
return this.client.composantModel.create({
|
||||
data,
|
||||
include,
|
||||
});
|
||||
}
|
||||
|
||||
async findAll(
|
||||
typeComposantId?: string,
|
||||
include?: Prisma.ComposantModelInclude,
|
||||
) {
|
||||
return this.client.composantModel.findMany({
|
||||
where: typeComposantId ? { typeComposantId } : undefined,
|
||||
include,
|
||||
orderBy: { name: 'asc' },
|
||||
});
|
||||
}
|
||||
|
||||
async findOne(id: string, include?: Prisma.ComposantModelInclude) {
|
||||
return this.client.composantModel.findUnique({
|
||||
where: { id },
|
||||
include,
|
||||
});
|
||||
}
|
||||
|
||||
async update(
|
||||
id: string,
|
||||
data: Prisma.ComposantModelUpdateInput,
|
||||
include?: Prisma.ComposantModelInclude,
|
||||
) {
|
||||
return this.client.composantModel.update({
|
||||
where: { id },
|
||||
data,
|
||||
include,
|
||||
});
|
||||
}
|
||||
|
||||
async delete(id: string) {
|
||||
return this.client.composantModel.delete({
|
||||
where: { id },
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Prisma, PrismaClient } from '@prisma/client';
|
||||
import { PrismaService } from '../../prisma/prisma.service';
|
||||
|
||||
@Injectable()
|
||||
export class PieceModelsRepository {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
private get client(): PrismaClient {
|
||||
return this.prisma;
|
||||
}
|
||||
|
||||
async create(
|
||||
data: Prisma.PieceModelCreateInput,
|
||||
include?: Prisma.PieceModelInclude,
|
||||
) {
|
||||
return this.client.pieceModel.create({
|
||||
data,
|
||||
include,
|
||||
});
|
||||
}
|
||||
|
||||
async findAll(typePieceId?: string, include?: Prisma.PieceModelInclude) {
|
||||
return this.client.pieceModel.findMany({
|
||||
where: typePieceId ? { typePieceId } : undefined,
|
||||
include,
|
||||
orderBy: { name: 'asc' },
|
||||
});
|
||||
}
|
||||
|
||||
async findOne(id: string, include?: Prisma.PieceModelInclude) {
|
||||
return this.client.pieceModel.findUnique({
|
||||
where: { id },
|
||||
include,
|
||||
});
|
||||
}
|
||||
|
||||
async update(
|
||||
id: string,
|
||||
data: Prisma.PieceModelUpdateInput,
|
||||
include?: Prisma.PieceModelInclude,
|
||||
) {
|
||||
return this.client.pieceModel.update({
|
||||
where: { id },
|
||||
data,
|
||||
include,
|
||||
});
|
||||
}
|
||||
|
||||
async delete(id: string) {
|
||||
return this.client.pieceModel.delete({
|
||||
where: { id },
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -13,10 +13,21 @@ describe('ComposantsService', () => {
|
||||
composant: {
|
||||
create: jest.fn(),
|
||||
findUnique: jest.fn(),
|
||||
findMany: jest.fn(),
|
||||
},
|
||||
machine: {
|
||||
findUnique: jest.fn(),
|
||||
},
|
||||
customField: {
|
||||
findMany: jest.fn(),
|
||||
},
|
||||
customFieldValue: {
|
||||
findMany: jest.fn(),
|
||||
create: jest.fn(),
|
||||
},
|
||||
piece: {
|
||||
create: jest.fn(),
|
||||
},
|
||||
};
|
||||
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
@@ -45,15 +56,48 @@ describe('ComposantsService', () => {
|
||||
id: 'machine-1',
|
||||
typeMachine: {
|
||||
componentRequirements: [
|
||||
{ id: 'req-1', typeComposantId: 'type-comp-1' },
|
||||
{
|
||||
id: 'req-1',
|
||||
typeComposantId: 'type-comp-1',
|
||||
typeComposant: {
|
||||
id: 'type-comp-1',
|
||||
name: 'Comp type',
|
||||
code: 'comp-type',
|
||||
componentSkeleton: null,
|
||||
},
|
||||
},
|
||||
],
|
||||
pieceRequirements: [],
|
||||
},
|
||||
});
|
||||
|
||||
const created = { id: 'component-1' };
|
||||
const created = {
|
||||
id: 'component-1',
|
||||
name: 'Comp A',
|
||||
machineId: 'machine-1',
|
||||
typeComposantId: 'type-comp-1',
|
||||
};
|
||||
prisma.composant.create.mockResolvedValue(created);
|
||||
prisma.composant.findUnique.mockResolvedValue({
|
||||
...created,
|
||||
machine: null,
|
||||
parentComposant: null,
|
||||
typeComposant: {
|
||||
id: 'type-comp-1',
|
||||
name: 'Comp type',
|
||||
code: 'comp-type',
|
||||
componentSkeleton: null,
|
||||
customFields: [],
|
||||
},
|
||||
typeMachineComponentRequirement: null,
|
||||
constructeur: null,
|
||||
customFieldValues: [],
|
||||
pieces: [],
|
||||
documents: [],
|
||||
});
|
||||
prisma.composant.findMany.mockResolvedValue([]);
|
||||
|
||||
await expect(service.create(dto)).resolves.toEqual(created);
|
||||
await expect(service.create(dto)).resolves.toMatchObject({ id: 'component-1' });
|
||||
|
||||
expect(prisma.composant.create).toHaveBeenCalled();
|
||||
expect(prisma.composant.create.mock.calls[0][0].data.typeComposantId).toBe(
|
||||
@@ -75,6 +119,7 @@ describe('ComposantsService', () => {
|
||||
componentRequirements: [
|
||||
{ id: 'req-1', typeComposantId: 'type-comp-1' },
|
||||
],
|
||||
pieceRequirements: [],
|
||||
},
|
||||
});
|
||||
|
||||
@@ -84,4 +129,155 @@ describe('ComposantsService', () => {
|
||||
|
||||
expect(prisma.composant.create).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should create nested components, pieces, and custom field values from the type skeleton', async () => {
|
||||
const dto: CreateComposantDto = {
|
||||
name: 'Comp B',
|
||||
machineId: 'machine-1',
|
||||
typeMachineComponentRequirementId: 'req-root',
|
||||
} as any;
|
||||
|
||||
prisma.machine.findUnique.mockResolvedValue({
|
||||
id: 'machine-1',
|
||||
typeMachine: {
|
||||
componentRequirements: [
|
||||
{
|
||||
id: 'req-root',
|
||||
typeComposantId: 'type-root',
|
||||
typeComposant: {
|
||||
id: 'type-root',
|
||||
name: 'Root type',
|
||||
code: 'root',
|
||||
componentSkeleton: {
|
||||
customFields: [{ key: 'color', value: 'red' }],
|
||||
pieces: [
|
||||
{
|
||||
typePieceId: 'type-piece',
|
||||
role: 'Primary piece',
|
||||
},
|
||||
],
|
||||
subcomponents: [
|
||||
{
|
||||
typeComposantId: 'type-child',
|
||||
alias: 'Child component',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
maxCount: null,
|
||||
},
|
||||
{
|
||||
id: 'req-child',
|
||||
typeComposantId: 'type-child',
|
||||
typeComposant: {
|
||||
id: 'type-child',
|
||||
name: 'Child type',
|
||||
code: 'child',
|
||||
componentSkeleton: null,
|
||||
},
|
||||
maxCount: null,
|
||||
},
|
||||
],
|
||||
pieceRequirements: [
|
||||
{
|
||||
id: 'req-piece',
|
||||
typePieceId: 'type-piece',
|
||||
typePiece: {
|
||||
id: 'type-piece',
|
||||
name: 'Piece type',
|
||||
code: 'piece',
|
||||
},
|
||||
maxCount: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
prisma.customField.findMany.mockResolvedValue([{ id: 'cf-color', name: 'color' }]);
|
||||
prisma.customFieldValue.findMany.mockResolvedValue([]);
|
||||
|
||||
const rootComponent = {
|
||||
id: 'component-1',
|
||||
name: 'Comp B',
|
||||
machineId: 'machine-1',
|
||||
typeComposantId: 'type-root',
|
||||
typeComposant: {
|
||||
id: 'type-root',
|
||||
name: 'Root type',
|
||||
code: 'root',
|
||||
componentSkeleton: {
|
||||
customFields: [{ key: 'color', value: 'red' }],
|
||||
pieces: [],
|
||||
subcomponents: [],
|
||||
},
|
||||
customFields: [],
|
||||
},
|
||||
machine: null,
|
||||
parentComposant: null,
|
||||
typeMachineComponentRequirement: null,
|
||||
constructeur: null,
|
||||
customFieldValues: [],
|
||||
pieces: [],
|
||||
documents: [],
|
||||
};
|
||||
|
||||
prisma.composant.create
|
||||
.mockResolvedValueOnce(rootComponent)
|
||||
.mockResolvedValueOnce({
|
||||
id: 'component-child',
|
||||
name: 'Child component',
|
||||
machineId: 'machine-1',
|
||||
parentComposantId: 'component-1',
|
||||
typeComposantId: 'type-child',
|
||||
});
|
||||
|
||||
prisma.composant.findUnique.mockResolvedValue(rootComponent);
|
||||
prisma.composant.findMany.mockResolvedValue([
|
||||
{ ...rootComponent, parentComposantId: null },
|
||||
{
|
||||
id: 'component-child',
|
||||
name: 'Child component',
|
||||
machineId: 'machine-1',
|
||||
parentComposantId: 'component-1',
|
||||
typeComposantId: 'type-child',
|
||||
machine: null,
|
||||
parentComposant: rootComponent,
|
||||
typeComposant: null,
|
||||
typeMachineComponentRequirement: null,
|
||||
constructeur: null,
|
||||
customFieldValues: [],
|
||||
pieces: [],
|
||||
documents: [],
|
||||
},
|
||||
]);
|
||||
|
||||
await service.create(dto);
|
||||
|
||||
expect(prisma.customField.findMany).toHaveBeenCalledWith({
|
||||
where: { typeComposantId: 'type-root' },
|
||||
select: { id: true, name: true },
|
||||
});
|
||||
expect(prisma.customFieldValue.create).toHaveBeenCalledWith({
|
||||
data: {
|
||||
customFieldId: 'cf-color',
|
||||
composantId: 'component-1',
|
||||
value: 'red',
|
||||
},
|
||||
});
|
||||
expect(prisma.piece.create).toHaveBeenCalledWith({
|
||||
data: {
|
||||
name: 'Primary piece',
|
||||
machineId: 'machine-1',
|
||||
composantId: 'component-1',
|
||||
typePieceId: 'type-piece',
|
||||
typeMachinePieceRequirementId: 'req-piece',
|
||||
},
|
||||
});
|
||||
expect(prisma.composant.create).toHaveBeenCalledTimes(2);
|
||||
expect(prisma.composant.create.mock.calls[1][0].data).toMatchObject({
|
||||
parentComposantId: 'component-1',
|
||||
typeComposantId: 'type-child',
|
||||
typeMachineComponentRequirementId: 'req-child',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import {
|
||||
CreateComposantDto,
|
||||
@@ -12,6 +13,19 @@ import {
|
||||
buildComponentHierarchy,
|
||||
buildComponentSubtree,
|
||||
} from '../common/utils/component-tree.util';
|
||||
import { ComponentModelStructureSchema } from '../shared/schemas/inventory';
|
||||
import type { ComponentModelStructure } from '../shared/types/inventory';
|
||||
|
||||
type ComponentRequirementWithType =
|
||||
Prisma.TypeMachineComponentRequirementGetPayload<{
|
||||
include: { typeComposant: true };
|
||||
}>;
|
||||
type PieceRequirementWithType =
|
||||
Prisma.TypeMachinePieceRequirementGetPayload<{
|
||||
include: { typePiece: true };
|
||||
}>;
|
||||
type ModelTypeWithSkeleton = ComponentRequirementWithType['typeComposant'];
|
||||
type PieceTypeWithSkeleton = PieceRequirementWithType['typePiece'];
|
||||
|
||||
@Injectable()
|
||||
export class ComposantsService {
|
||||
@@ -80,7 +94,16 @@ export class ComposantsService {
|
||||
include: {
|
||||
typeMachine: {
|
||||
include: {
|
||||
componentRequirements: true,
|
||||
componentRequirements: {
|
||||
include: {
|
||||
typeComposant: true,
|
||||
},
|
||||
},
|
||||
pieceRequirements: {
|
||||
include: {
|
||||
typePiece: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -92,7 +115,12 @@ export class ComposantsService {
|
||||
);
|
||||
}
|
||||
|
||||
const requirement = machine.typeMachine.componentRequirements.find(
|
||||
const componentRequirements =
|
||||
(machine.typeMachine.componentRequirements as ComponentRequirementWithType[]) ?? [];
|
||||
const pieceRequirements =
|
||||
(machine.typeMachine.pieceRequirements as PieceRequirementWithType[]) ?? [];
|
||||
|
||||
const requirement = componentRequirements.find(
|
||||
(componentRequirement) => componentRequirement.id === requirementId,
|
||||
);
|
||||
|
||||
@@ -111,20 +139,38 @@ export class ComposantsService {
|
||||
);
|
||||
}
|
||||
|
||||
const data = {
|
||||
...createComposantDto,
|
||||
machineId,
|
||||
typeComposantId:
|
||||
createComposantDto.typeComposantId ?? requirement.typeComposantId,
|
||||
};
|
||||
const typeComposantId =
|
||||
createComposantDto.typeComposantId ?? requirement.typeComposantId;
|
||||
|
||||
const created = (await this.prisma.composant.create({
|
||||
data,
|
||||
const created = await this.prisma.composant.create({
|
||||
data: {
|
||||
...createComposantDto,
|
||||
machineId,
|
||||
typeComposantId,
|
||||
},
|
||||
include: COMPONENT_WITH_RELATIONS_INCLUDE,
|
||||
})) as ComposantWithRelations;
|
||||
});
|
||||
|
||||
const componentRequirementUsage = new Map<string, number>();
|
||||
componentRequirementUsage.set(requirement.id, 1);
|
||||
const pieceRequirementUsage = new Map<string, number>();
|
||||
|
||||
await this.populateComponentFromSkeleton({
|
||||
componentId: created.id,
|
||||
componentName: created.name,
|
||||
componentType:
|
||||
(requirement.typeComposant as ModelTypeWithSkeleton | null) ??
|
||||
(created.typeComposant as ModelTypeWithSkeleton | null) ??
|
||||
null,
|
||||
machineId,
|
||||
componentRequirements,
|
||||
pieceRequirements,
|
||||
componentRequirementUsage,
|
||||
pieceRequirementUsage,
|
||||
});
|
||||
|
||||
const component = await this.getComponentWithHierarchy(created.id);
|
||||
return component ?? created;
|
||||
return (component as ComposantWithRelations | null) ?? (created as ComposantWithRelations);
|
||||
}
|
||||
|
||||
async findAll() {
|
||||
@@ -156,11 +202,379 @@ export class ComposantsService {
|
||||
include: COMPONENT_WITH_RELATIONS_INCLUDE,
|
||||
})) as ComposantWithRelations;
|
||||
|
||||
await this.syncComponentModelCustomFields(updated);
|
||||
|
||||
return this.getComponentWithHierarchy(updated.id);
|
||||
}
|
||||
|
||||
private async populateComponentFromSkeleton({
|
||||
componentId,
|
||||
componentName,
|
||||
componentType,
|
||||
machineId,
|
||||
componentRequirements,
|
||||
pieceRequirements,
|
||||
componentRequirementUsage,
|
||||
pieceRequirementUsage,
|
||||
}: {
|
||||
componentId: string;
|
||||
componentName?: string;
|
||||
componentType: ModelTypeWithSkeleton | null;
|
||||
machineId: string;
|
||||
componentRequirements: ComponentRequirementWithType[];
|
||||
pieceRequirements: PieceRequirementWithType[];
|
||||
componentRequirementUsage: Map<string, number>;
|
||||
pieceRequirementUsage: Map<string, number>;
|
||||
}) {
|
||||
const skeleton = this.parseComponentSkeleton(
|
||||
(componentType as { componentSkeleton?: Prisma.JsonValue | null } | null)?.
|
||||
componentSkeleton,
|
||||
);
|
||||
if (!skeleton) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.createComponentCustomFieldValues(
|
||||
componentId,
|
||||
componentType?.id ?? null,
|
||||
skeleton.customFields,
|
||||
);
|
||||
|
||||
await this.createPiecesFromSkeleton({
|
||||
componentId,
|
||||
componentName,
|
||||
machineId,
|
||||
pieces: skeleton.pieces,
|
||||
pieceRequirements,
|
||||
pieceRequirementUsage,
|
||||
});
|
||||
|
||||
for (const subcomponent of skeleton.subcomponents ?? []) {
|
||||
const requirement = this.resolveComponentRequirement(
|
||||
subcomponent,
|
||||
componentRequirements,
|
||||
componentRequirementUsage,
|
||||
);
|
||||
|
||||
if (!requirement?.typeComposant) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const name = this.buildComponentName(
|
||||
subcomponent,
|
||||
requirement.typeComposant,
|
||||
componentName,
|
||||
);
|
||||
|
||||
const createdChild = await this.prisma.composant.create({
|
||||
data: {
|
||||
name,
|
||||
machineId,
|
||||
parentComposantId: componentId,
|
||||
typeComposantId: requirement.typeComposantId,
|
||||
typeMachineComponentRequirementId: requirement.id,
|
||||
},
|
||||
});
|
||||
|
||||
this.incrementRequirementUsage(
|
||||
componentRequirementUsage,
|
||||
requirement.id,
|
||||
);
|
||||
|
||||
await this.populateComponentFromSkeleton({
|
||||
componentId: createdChild.id,
|
||||
componentName: createdChild.name,
|
||||
componentType: requirement.typeComposant as ModelTypeWithSkeleton,
|
||||
machineId,
|
||||
componentRequirements,
|
||||
pieceRequirements,
|
||||
componentRequirementUsage,
|
||||
pieceRequirementUsage,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private parseComponentSkeleton(
|
||||
value: unknown,
|
||||
): ComponentModelStructure | null {
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return ComponentModelStructureSchema.parse(value);
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private async createComponentCustomFieldValues(
|
||||
componentId: string,
|
||||
typeComposantId: string | null,
|
||||
customFields: ComponentModelStructure['customFields'],
|
||||
) {
|
||||
if (!typeComposantId || !Array.isArray(customFields) || customFields.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const definitions = await this.prisma.customField.findMany({
|
||||
where: { typeComposantId },
|
||||
select: { id: true, name: true },
|
||||
});
|
||||
|
||||
if (definitions.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const definitionMap = new Map(definitions.map((field) => [field.name, field.id]));
|
||||
const existingValues = await this.prisma.customFieldValue.findMany({
|
||||
where: { composantId: componentId },
|
||||
select: { customFieldId: true },
|
||||
});
|
||||
const existingIds = new Set(existingValues.map((value) => value.customFieldId));
|
||||
|
||||
for (const field of customFields) {
|
||||
const key = this.normalizeIdentifier(field?.key);
|
||||
if (!key) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const definitionId = definitionMap.get(key);
|
||||
if (!definitionId || existingIds.has(definitionId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
await this.prisma.customFieldValue.create({
|
||||
data: {
|
||||
customFieldId: definitionId,
|
||||
composantId: componentId,
|
||||
value: this.toCustomFieldValue(field?.value),
|
||||
},
|
||||
});
|
||||
|
||||
existingIds.add(definitionId);
|
||||
}
|
||||
}
|
||||
|
||||
private async createPiecesFromSkeleton({
|
||||
componentId,
|
||||
componentName,
|
||||
machineId,
|
||||
pieces,
|
||||
pieceRequirements,
|
||||
pieceRequirementUsage,
|
||||
}: {
|
||||
componentId: string;
|
||||
componentName?: string;
|
||||
machineId: string;
|
||||
pieces: ComponentModelStructure['pieces'];
|
||||
pieceRequirements: PieceRequirementWithType[];
|
||||
pieceRequirementUsage: Map<string, number>;
|
||||
}) {
|
||||
if (!Array.isArray(pieces) || pieces.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const entry of pieces) {
|
||||
const requirement = this.resolvePieceRequirement(
|
||||
entry,
|
||||
pieceRequirements,
|
||||
pieceRequirementUsage,
|
||||
);
|
||||
|
||||
if (!requirement?.typePiece) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const name = this.buildPieceName(entry, requirement.typePiece, componentName);
|
||||
|
||||
await this.prisma.piece.create({
|
||||
data: {
|
||||
name,
|
||||
machineId,
|
||||
composantId: componentId,
|
||||
typePieceId: requirement.typePieceId,
|
||||
typeMachinePieceRequirementId: requirement.id,
|
||||
},
|
||||
});
|
||||
|
||||
this.incrementRequirementUsage(pieceRequirementUsage, requirement.id);
|
||||
}
|
||||
}
|
||||
|
||||
private resolveComponentRequirement(
|
||||
entry: ComponentModelStructure['subcomponents'][number],
|
||||
requirements: ComponentRequirementWithType[],
|
||||
usage: Map<string, number>,
|
||||
): ComponentRequirementWithType | null {
|
||||
const typeComposantId = this.normalizeIdentifier(
|
||||
(entry as { typeComposantId?: string }).typeComposantId,
|
||||
);
|
||||
const familyCode = this.normalizeCode(
|
||||
(entry as { familyCode?: string }).familyCode,
|
||||
);
|
||||
|
||||
const candidates = requirements.filter((requirement) => {
|
||||
if (typeComposantId && requirement.typeComposantId === typeComposantId) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (familyCode && requirement.typeComposant?.code) {
|
||||
return this.normalizeCode(requirement.typeComposant.code) === familyCode;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
if (candidates.length === 0) {
|
||||
if (typeComposantId || familyCode) {
|
||||
throw new BadRequestException(
|
||||
`Aucun requirement de composant ne correspond au squelette (${typeComposantId ?? familyCode}).`,
|
||||
);
|
||||
}
|
||||
|
||||
throw new BadRequestException(
|
||||
'Le squelette du composant référence un sous-composant sans identifiant de type.',
|
||||
);
|
||||
}
|
||||
|
||||
for (const candidate of candidates) {
|
||||
if (this.hasRequirementCapacity(candidate, usage)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
throw new BadRequestException(
|
||||
`La capacité maximale du requirement de composant (${typeComposantId ?? familyCode}) est atteinte pour la machine visée.`,
|
||||
);
|
||||
}
|
||||
|
||||
private resolvePieceRequirement(
|
||||
entry: ComponentModelStructure['pieces'][number],
|
||||
requirements: PieceRequirementWithType[],
|
||||
usage: Map<string, number>,
|
||||
): PieceRequirementWithType | null {
|
||||
const typePieceId = this.normalizeIdentifier(
|
||||
(entry as { typePieceId?: string }).typePieceId,
|
||||
);
|
||||
const familyCode = this.normalizeCode(
|
||||
(entry as { familyCode?: string }).familyCode,
|
||||
);
|
||||
|
||||
const candidates = requirements.filter((requirement) => {
|
||||
if (typePieceId && requirement.typePieceId === typePieceId) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (familyCode && requirement.typePiece?.code) {
|
||||
return this.normalizeCode(requirement.typePiece.code) === familyCode;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
if (candidates.length === 0) {
|
||||
if (typePieceId || familyCode) {
|
||||
throw new BadRequestException(
|
||||
`Aucun requirement de pièce ne correspond au squelette (${typePieceId ?? familyCode}).`,
|
||||
);
|
||||
}
|
||||
|
||||
throw new BadRequestException(
|
||||
'Le squelette du composant référence une pièce sans identifiant de type.',
|
||||
);
|
||||
}
|
||||
|
||||
for (const candidate of candidates) {
|
||||
if (this.hasRequirementCapacity(candidate, usage)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
throw new BadRequestException(
|
||||
`La capacité maximale du requirement de pièce (${typePieceId ?? familyCode}) est atteinte pour la machine visée.`,
|
||||
);
|
||||
}
|
||||
|
||||
private hasRequirementCapacity(
|
||||
requirement: { id: string; maxCount: number | null | undefined },
|
||||
usage: Map<string, number>,
|
||||
): boolean {
|
||||
const max = requirement.maxCount;
|
||||
if (max === null || max === undefined) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const current = usage.get(requirement.id) ?? 0;
|
||||
return current < max;
|
||||
}
|
||||
|
||||
private incrementRequirementUsage(usage: Map<string, number>, id: string) {
|
||||
usage.set(id, (usage.get(id) ?? 0) + 1);
|
||||
}
|
||||
|
||||
private buildComponentName(
|
||||
subcomponent: ComponentModelStructure['subcomponents'][number],
|
||||
typeComposant: ModelTypeWithSkeleton | null,
|
||||
parentName?: string,
|
||||
): string {
|
||||
const alias = this.normalizeIdentifier((subcomponent as { alias?: string }).alias);
|
||||
if (alias) {
|
||||
return alias;
|
||||
}
|
||||
|
||||
if (typeComposant?.name) {
|
||||
return typeComposant.name;
|
||||
}
|
||||
|
||||
if (parentName) {
|
||||
return `${parentName} - Sous-composant`;
|
||||
}
|
||||
|
||||
return 'Sous-composant';
|
||||
}
|
||||
|
||||
private buildPieceName(
|
||||
piece: ComponentModelStructure['pieces'][number],
|
||||
typePiece: PieceTypeWithSkeleton | null,
|
||||
componentName?: string,
|
||||
): string {
|
||||
const role = this.normalizeIdentifier((piece as { role?: string }).role);
|
||||
if (role) {
|
||||
return role;
|
||||
}
|
||||
|
||||
if (typePiece?.name) {
|
||||
return typePiece.name;
|
||||
}
|
||||
|
||||
if (componentName) {
|
||||
return `${componentName} - Pièce`;
|
||||
}
|
||||
|
||||
return 'Pièce';
|
||||
}
|
||||
|
||||
private normalizeIdentifier(value: unknown): string | null {
|
||||
if (typeof value !== 'string') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const trimmed = value.trim();
|
||||
return trimmed.length > 0 ? trimmed : null;
|
||||
}
|
||||
|
||||
private normalizeCode(value: unknown): string | null {
|
||||
const identifier = this.normalizeIdentifier(value);
|
||||
return identifier ? identifier.toLowerCase() : null;
|
||||
}
|
||||
|
||||
private toCustomFieldValue(value: unknown): string {
|
||||
if (value === undefined || value === null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return String(value);
|
||||
}
|
||||
|
||||
private async resolveMachineIdFromComposant(
|
||||
composantId: string,
|
||||
): Promise<string> {
|
||||
@@ -197,155 +611,4 @@ 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 rawSubcomponents =
|
||||
(structure as any)?.subcomponents ?? structure?.subComponents;
|
||||
const subComponents = Array.isArray(rawSubcomponents)
|
||||
? rawSubcomponents
|
||||
: rawSubcomponents
|
||||
? [rawSubcomponents]
|
||||
: [];
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,24 @@ import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { MachinesController } from './machines.controller';
|
||||
import { MachinesService } from './machines.service';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { ComposantsService } from '../composants/composants.service';
|
||||
import { PiecesService } from '../pieces/pieces.service';
|
||||
|
||||
describe('MachinesController', () => {
|
||||
let controller: MachinesController;
|
||||
|
||||
beforeEach(async () => {
|
||||
const mockComposantsService = { create: jest.fn() } as Partial<ComposantsService>;
|
||||
const mockPiecesService = { create: jest.fn() } as Partial<PiecesService>;
|
||||
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [MachinesController],
|
||||
providers: [MachinesService, PrismaService],
|
||||
providers: [
|
||||
MachinesService,
|
||||
PrismaService,
|
||||
{ provide: ComposantsService, useValue: mockComposantsService },
|
||||
{ provide: PiecesService, useValue: mockPiecesService },
|
||||
],
|
||||
}).compile();
|
||||
|
||||
controller = module.get<MachinesController>(MachinesController);
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { MachinesController } from './machines.controller';
|
||||
import { MachinesService } from './machines.service';
|
||||
import { ComposantsService } from '../composants/composants.service';
|
||||
import { PiecesService } from '../pieces/pieces.service';
|
||||
|
||||
@Module({
|
||||
controllers: [MachinesController],
|
||||
providers: [MachinesService],
|
||||
providers: [MachinesService, ComposantsService, PiecesService],
|
||||
})
|
||||
export class MachinesModule {}
|
||||
|
||||
@@ -1,13 +1,23 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { MachinesService } from './machines.service';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { ComposantsService } from '../composants/composants.service';
|
||||
import { PiecesService } from '../pieces/pieces.service';
|
||||
|
||||
describe('MachinesService', () => {
|
||||
let service: MachinesService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const mockComposantsService = { create: jest.fn() } as Partial<ComposantsService>;
|
||||
const mockPiecesService = { create: jest.fn() } as Partial<PiecesService>;
|
||||
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [MachinesService, PrismaService],
|
||||
providers: [
|
||||
MachinesService,
|
||||
PrismaService,
|
||||
{ provide: ComposantsService, useValue: mockComposantsService },
|
||||
{ provide: PiecesService, useValue: mockPiecesService },
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<MachinesService>(MachinesService);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -12,6 +12,10 @@ describe('PiecesService', () => {
|
||||
prisma = {
|
||||
piece: {
|
||||
create: jest.fn(),
|
||||
findMany: jest.fn(),
|
||||
findUnique: jest.fn(),
|
||||
update: jest.fn(),
|
||||
delete: jest.fn(),
|
||||
},
|
||||
machine: {
|
||||
findUnique: jest.fn(),
|
||||
@@ -19,6 +23,14 @@ describe('PiecesService', () => {
|
||||
composant: {
|
||||
findUnique: jest.fn(),
|
||||
},
|
||||
customField: {
|
||||
findMany: jest.fn(),
|
||||
create: jest.fn(),
|
||||
},
|
||||
customFieldValue: {
|
||||
findMany: jest.fn(),
|
||||
create: jest.fn(),
|
||||
},
|
||||
};
|
||||
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
@@ -43,18 +55,94 @@ describe('PiecesService', () => {
|
||||
prisma.machine.findUnique.mockResolvedValue({
|
||||
id: 'machine-1',
|
||||
typeMachine: {
|
||||
pieceRequirements: [{ id: 'req-1', typePieceId: 'type-piece-1' }],
|
||||
pieceRequirements: [
|
||||
{
|
||||
id: 'req-1',
|
||||
typePieceId: 'type-piece-1',
|
||||
typePiece: {
|
||||
id: 'type-piece-1',
|
||||
pieceSkeleton: {
|
||||
customFields: [
|
||||
{
|
||||
name: 'Numéro de série',
|
||||
value: 'AUTO',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const created = { id: 'piece-1' };
|
||||
const created = {
|
||||
id: 'piece-1',
|
||||
typePieceId: 'type-piece-1',
|
||||
typePiece: {
|
||||
id: 'type-piece-1',
|
||||
pieceSkeleton: {
|
||||
customFields: [
|
||||
{
|
||||
name: 'Numéro de série',
|
||||
value: 'AUTO',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
prisma.piece.create.mockResolvedValue(created);
|
||||
|
||||
await expect(service.create(dto)).resolves.toEqual(created);
|
||||
expect(prisma.piece.create).toHaveBeenCalled();
|
||||
expect(prisma.piece.create.mock.calls[0][0].data.machineId).toBe(
|
||||
'machine-1',
|
||||
);
|
||||
prisma.customField.findMany
|
||||
.mockResolvedValueOnce([])
|
||||
.mockResolvedValueOnce([
|
||||
{ id: 'field-1', name: 'Numéro de série' },
|
||||
]);
|
||||
prisma.customField.create.mockResolvedValue({ id: 'field-1' });
|
||||
prisma.customFieldValue.findMany.mockResolvedValue([]);
|
||||
prisma.customFieldValue.create.mockResolvedValue({
|
||||
id: 'value-1',
|
||||
});
|
||||
|
||||
const finalPiece = { ...created, customFieldValues: [] };
|
||||
prisma.piece.findUnique.mockResolvedValue(finalPiece);
|
||||
|
||||
await expect(service.create(dto)).resolves.toEqual(finalPiece);
|
||||
|
||||
expect(prisma.piece.create).toHaveBeenCalledWith({
|
||||
data: expect.objectContaining({
|
||||
machineId: 'machine-1',
|
||||
typePieceId: 'type-piece-1',
|
||||
}),
|
||||
include: expect.any(Object),
|
||||
});
|
||||
|
||||
expect(prisma.customField.create).toHaveBeenCalledWith({
|
||||
data: {
|
||||
name: 'Numéro de série',
|
||||
type: 'text',
|
||||
required: true,
|
||||
options: undefined,
|
||||
typePieceId: 'type-piece-1',
|
||||
},
|
||||
select: { id: true },
|
||||
});
|
||||
|
||||
expect(prisma.customFieldValue.create).toHaveBeenCalledWith({
|
||||
data: {
|
||||
customFieldId: 'field-1',
|
||||
pieceId: 'piece-1',
|
||||
value: 'AUTO',
|
||||
},
|
||||
});
|
||||
|
||||
expect(prisma.piece.findUnique).toHaveBeenCalledWith({
|
||||
where: { id: 'piece-1' },
|
||||
include: expect.any(Object),
|
||||
});
|
||||
});
|
||||
|
||||
it('should refuse creation when requirement does not belong to machine skeleton', async () => {
|
||||
|
||||
@@ -1,23 +1,25 @@
|
||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { CreatePieceDto, UpdatePieceDto } from '../shared/dto/piece.dto';
|
||||
import { PieceModelStructureSchema } from '../shared/schemas/inventory';
|
||||
import type { PieceModelStructure } from '../shared/types/inventory';
|
||||
|
||||
const PIECE_WITH_RELATIONS_INCLUDE = {
|
||||
machine: true,
|
||||
composant: true,
|
||||
typePiece: {
|
||||
include: {
|
||||
customFields: true,
|
||||
pieceCustomFields: true,
|
||||
},
|
||||
},
|
||||
documents: true,
|
||||
constructeur: true,
|
||||
pieceModel: true,
|
||||
typeMachinePieceRequirement: {
|
||||
include: {
|
||||
typePiece: {
|
||||
include: {
|
||||
customFields: true,
|
||||
pieceCustomFields: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -63,7 +65,11 @@ export class PiecesService {
|
||||
include: {
|
||||
typeMachine: {
|
||||
include: {
|
||||
pieceRequirements: true,
|
||||
pieceRequirements: {
|
||||
include: {
|
||||
typePiece: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -100,73 +106,35 @@ export class PiecesService {
|
||||
typePieceId: createPieceDto.typePieceId ?? requirement.typePieceId,
|
||||
};
|
||||
|
||||
return this.prisma.piece.create({
|
||||
const created = await this.prisma.piece.create({
|
||||
data,
|
||||
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,
|
||||
});
|
||||
|
||||
await this.applyPieceSkeleton({
|
||||
pieceId: created.id,
|
||||
typePiece:
|
||||
(requirement.typePiece as PieceTypeWithSkeleton | null) ??
|
||||
(created.typePiece as PieceTypeWithSkeleton | null) ??
|
||||
null,
|
||||
});
|
||||
|
||||
return this.prisma.piece.findUnique({
|
||||
where: { id: created.id },
|
||||
include: PIECE_WITH_RELATIONS_INCLUDE,
|
||||
});
|
||||
}
|
||||
|
||||
async findAll() {
|
||||
return this.prisma.piece.findMany({
|
||||
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 findOne(id: string) {
|
||||
return this.prisma.piece.findUnique({
|
||||
where: { id },
|
||||
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,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -220,9 +188,15 @@ export class PiecesService {
|
||||
include: PIECE_WITH_RELATIONS_INCLUDE,
|
||||
});
|
||||
|
||||
await this.syncPieceModelCustomFields(updated);
|
||||
await this.applyPieceSkeleton({
|
||||
pieceId: updated.id,
|
||||
typePiece: updated.typePiece as PieceTypeWithSkeleton | null,
|
||||
});
|
||||
|
||||
return updated;
|
||||
return this.prisma.piece.findUnique({
|
||||
where: { id: updated.id },
|
||||
include: PIECE_WITH_RELATIONS_INCLUDE,
|
||||
});
|
||||
}
|
||||
|
||||
async remove(id: string) {
|
||||
@@ -231,136 +205,213 @@ export class PiecesService {
|
||||
});
|
||||
}
|
||||
|
||||
private async syncPieceModelCustomFields(piece: any) {
|
||||
const pieceModelId = piece?.pieceModelId;
|
||||
|
||||
if (!pieceModelId) {
|
||||
private async applyPieceSkeleton({
|
||||
pieceId,
|
||||
typePiece,
|
||||
}: {
|
||||
pieceId: string;
|
||||
typePiece: PieceTypeWithSkeleton | null;
|
||||
}) {
|
||||
if (!typePiece?.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
const model = await this.prisma.pieceModel.findUnique({
|
||||
where: { id: pieceModelId },
|
||||
select: { structure: true },
|
||||
});
|
||||
const skeleton = this.parsePieceSkeleton(
|
||||
(typePiece as { pieceSkeleton?: Prisma.JsonValue | null } | null)?.
|
||||
pieceSkeleton,
|
||||
);
|
||||
|
||||
if (!model?.structure) {
|
||||
if (!skeleton) {
|
||||
return;
|
||||
}
|
||||
|
||||
const structure = this.asRecord(model.structure);
|
||||
const customFields = this.extractCustomFields(structure);
|
||||
const customFields = skeleton.customFields ?? [];
|
||||
|
||||
const targetTypePieceId = this.getTypePieceIdForPiece(piece, structure);
|
||||
if (!targetTypePieceId) {
|
||||
return;
|
||||
}
|
||||
await this.ensurePieceCustomFieldDefinitions(
|
||||
typePiece.id,
|
||||
customFields,
|
||||
);
|
||||
|
||||
await this.ensureCustomFieldsForType(
|
||||
targetTypePieceId,
|
||||
await this.createPieceCustomFieldValues(
|
||||
pieceId,
|
||||
typePiece.id,
|
||||
customFields,
|
||||
);
|
||||
}
|
||||
|
||||
private async ensureCustomFieldsForType(
|
||||
private parsePieceSkeleton(value: unknown): PieceModelStructure | null {
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return PieceModelStructureSchema.parse(value);
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private async ensurePieceCustomFieldDefinitions(
|
||||
typePieceId: string,
|
||||
fields: any,
|
||||
customFields: PieceModelStructure['customFields'],
|
||||
) {
|
||||
if (!typePieceId || !Array.isArray(fields)) {
|
||||
if (!typePieceId || !Array.isArray(customFields) || customFields.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const field of fields) {
|
||||
if (!field || typeof field !== 'object') {
|
||||
const existing = await this.prisma.customField.findMany({
|
||||
where: { typePieceId },
|
||||
select: { id: true, name: true },
|
||||
});
|
||||
|
||||
const existingByName = new Map(
|
||||
existing.map((field) => [this.normalizeIdentifier(field.name) ?? field.name, field.id]),
|
||||
);
|
||||
|
||||
for (const field of customFields) {
|
||||
if (!field) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const name = typeof field.name === 'string' ? field.name.trim() : '';
|
||||
const name = this.normalizeIdentifier(field.name);
|
||||
if (!name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const type = typeof field.type === 'string' && field.type.trim()
|
||||
? field.type.trim()
|
||||
: 'text';
|
||||
const required = !!field.required;
|
||||
if (existingByName.has(name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const type = this.normalizeIdentifier(field.type) ?? 'text';
|
||||
const required = Boolean(field.required);
|
||||
const options = this.normalizeOptions(field);
|
||||
|
||||
const existing = await this.prisma.customField.findFirst({
|
||||
where: {
|
||||
const created = await this.prisma.customField.create({
|
||||
data: {
|
||||
name,
|
||||
type,
|
||||
required,
|
||||
options,
|
||||
typePieceId,
|
||||
},
|
||||
select: { id: true },
|
||||
});
|
||||
|
||||
if (!existing) {
|
||||
await this.prisma.customField.create({
|
||||
data: {
|
||||
name,
|
||||
type,
|
||||
required,
|
||||
options,
|
||||
typePieceId,
|
||||
},
|
||||
});
|
||||
}
|
||||
existingByName.set(name, created.id);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
private async createPieceCustomFieldValues(
|
||||
pieceId: string,
|
||||
typePieceId: string,
|
||||
customFields: PieceModelStructure['customFields'],
|
||||
) {
|
||||
if (!typePieceId || !Array.isArray(customFields) || customFields.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof field?.optionsText === 'string') {
|
||||
const normalized = field.optionsText
|
||||
const definitions = await this.prisma.customField.findMany({
|
||||
where: { typePieceId },
|
||||
select: { id: true, name: true },
|
||||
});
|
||||
|
||||
if (definitions.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const definitionMap = new Map(
|
||||
definitions.map((field) => [this.normalizeIdentifier(field.name) ?? field.name, field.id]),
|
||||
);
|
||||
|
||||
const existingValues = await this.prisma.customFieldValue.findMany({
|
||||
where: { pieceId },
|
||||
select: { customFieldId: true },
|
||||
});
|
||||
|
||||
const existingIds = new Set(existingValues.map((value) => value.customFieldId));
|
||||
|
||||
for (const field of customFields) {
|
||||
if (!field) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const name = this.normalizeIdentifier(field.name);
|
||||
if (!name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const definitionId = definitionMap.get(name);
|
||||
if (!definitionId || existingIds.has(definitionId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
await this.prisma.customFieldValue.create({
|
||||
data: {
|
||||
customFieldId: definitionId,
|
||||
pieceId,
|
||||
value: this.toCustomFieldValue(field.value),
|
||||
},
|
||||
});
|
||||
|
||||
existingIds.add(definitionId);
|
||||
}
|
||||
}
|
||||
|
||||
private normalizeOptions(
|
||||
field: PieceCustomFieldEntry | undefined,
|
||||
): string[] | undefined {
|
||||
const rawOptions = field?.options;
|
||||
if (Array.isArray(rawOptions)) {
|
||||
const normalized = rawOptions
|
||||
.map((option) =>
|
||||
typeof option === 'string' ? option.trim() : '',
|
||||
)
|
||||
.filter((option) => option.length > 0);
|
||||
|
||||
return normalized.length > 0 ? normalized : undefined;
|
||||
}
|
||||
|
||||
const optionsTextValue =
|
||||
field !== undefined
|
||||
? (field as unknown as { optionsText?: unknown }).optionsText
|
||||
: undefined;
|
||||
|
||||
if (typeof optionsTextValue === 'string') {
|
||||
const normalized = optionsTextValue
|
||||
.split(/\r?\n/)
|
||||
.map((option: string) => option.trim())
|
||||
.filter((option: string) => option.length > 0);
|
||||
|
||||
return normalized.length ? normalized : undefined;
|
||||
return normalized.length > 0 ? 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)) {
|
||||
private normalizeIdentifier(value: unknown): string | null {
|
||||
if (typeof value !== 'string') {
|
||||
return null;
|
||||
}
|
||||
return value as Record<string, any>;
|
||||
|
||||
const trimmed = value.trim();
|
||||
return trimmed.length > 0 ? trimmed : null;
|
||||
}
|
||||
|
||||
private extractCustomFields(structure: Record<string, any> | null): any[] {
|
||||
if (!structure) {
|
||||
return [];
|
||||
private toCustomFieldValue(value: unknown): string {
|
||||
if (value === undefined || value === null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const { customFields } = structure;
|
||||
return Array.isArray(customFields) ? customFields : [];
|
||||
return String(value);
|
||||
}
|
||||
}
|
||||
|
||||
type PieceRequirementWithType = Prisma.TypeMachinePieceRequirementGetPayload<{
|
||||
include: { typePiece: true };
|
||||
}>;
|
||||
|
||||
type PieceTypeWithSkeleton = PieceRequirementWithType['typePiece'];
|
||||
|
||||
type PieceCustomFieldEntry = NonNullable<
|
||||
PieceModelStructure['customFields']
|
||||
>[number];
|
||||
|
||||
@@ -32,10 +32,6 @@ export class CreateComposantDto {
|
||||
|
||||
@IsString()
|
||||
typeMachineComponentRequirementId: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
composantModelId?: string;
|
||||
}
|
||||
|
||||
export class UpdateComposantDto {
|
||||
@@ -59,8 +55,4 @@ export class UpdateComposantDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
typeComposantId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
composantModelId?: string;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ export class MachineComponentSelectionDto {
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
componentModelId?: string;
|
||||
typeComposantId?: string;
|
||||
|
||||
@IsOptional()
|
||||
definition?: any;
|
||||
@@ -18,8 +18,12 @@ export class MachinePieceSelectionDto {
|
||||
@IsString()
|
||||
requirementId: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
pieceModelId: string;
|
||||
typePieceId?: string;
|
||||
|
||||
@IsOptional()
|
||||
definition?: any;
|
||||
}
|
||||
|
||||
export class CreateMachineDto {
|
||||
|
||||
@@ -32,10 +32,6 @@ export class CreatePieceDto {
|
||||
|
||||
@IsString()
|
||||
typeMachinePieceRequirementId: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
pieceModelId?: string;
|
||||
}
|
||||
|
||||
export class UpdatePieceDto {
|
||||
@@ -59,8 +55,4 @@ export class UpdatePieceDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
typePieceId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
pieceModelId?: string;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,10 @@ import {
|
||||
} from 'class-validator';
|
||||
import { Type } from 'class-transformer';
|
||||
import { ValidateNested } from 'class-validator';
|
||||
import type { ComponentModelStructure } from '../types/inventory';
|
||||
import type {
|
||||
ComponentModelStructure,
|
||||
PieceModelStructure,
|
||||
} from '../types/inventory';
|
||||
|
||||
export enum CustomFieldType {
|
||||
TEXT = 'text',
|
||||
@@ -197,6 +200,10 @@ export class CreateTypeComposantDto {
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
customFields?: CreateCustomFieldDto[];
|
||||
|
||||
@IsOptional()
|
||||
@IsObject()
|
||||
structure?: ComponentModelStructure;
|
||||
}
|
||||
|
||||
export class UpdateTypeComposantDto {
|
||||
@@ -211,6 +218,10 @@ export class UpdateTypeComposantDto {
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
customFields?: CreateCustomFieldDto[];
|
||||
|
||||
@IsOptional()
|
||||
@IsObject()
|
||||
structure?: ComponentModelStructure;
|
||||
}
|
||||
|
||||
export class CreateTypePieceDto {
|
||||
@@ -224,6 +235,10 @@ export class CreateTypePieceDto {
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
customFields?: CreateCustomFieldDto[];
|
||||
|
||||
@IsOptional()
|
||||
@IsObject()
|
||||
structure?: PieceModelStructure;
|
||||
}
|
||||
|
||||
export class UpdateTypePieceDto {
|
||||
@@ -238,68 +253,9 @@ export class UpdateTypePieceDto {
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
customFields?: CreateCustomFieldDto[];
|
||||
|
||||
@IsOptional()
|
||||
@IsObject()
|
||||
structure?: PieceModelStructure;
|
||||
}
|
||||
|
||||
export class CreateComposantModelDto {
|
||||
@IsString()
|
||||
name: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
description?: string;
|
||||
|
||||
@IsString()
|
||||
typeComposantId: string;
|
||||
|
||||
@IsOptional()
|
||||
structure?: ComponentModelStructure;
|
||||
}
|
||||
|
||||
export class UpdateComposantModelDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
name?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
description?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
typeComposantId?: string;
|
||||
|
||||
@IsOptional()
|
||||
structure?: ComponentModelStructure;
|
||||
}
|
||||
|
||||
export class CreatePieceModelDto {
|
||||
@IsString()
|
||||
name: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
description?: string;
|
||||
|
||||
@IsString()
|
||||
typePieceId: string;
|
||||
|
||||
@IsOptional()
|
||||
structure?: any;
|
||||
}
|
||||
|
||||
export class UpdatePieceModelDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
name?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
description?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
typePieceId?: string;
|
||||
|
||||
@IsOptional()
|
||||
structure?: any;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { normalizeComponentModelStructure } from '../../component-models/structure.normalizer';
|
||||
import type { ComponentModelStructure } from '../types/inventory';
|
||||
import type {
|
||||
ComponentModelStructure,
|
||||
PieceModelCustomField,
|
||||
PieceModelStructure,
|
||||
} from '../types/inventory';
|
||||
|
||||
export class ComponentModelStructureValidationError extends Error {
|
||||
constructor(message: string) {
|
||||
@@ -150,3 +154,109 @@ export const ComponentModelStructureSchema = {
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export class PieceModelStructureValidationError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = 'PieceModelStructureValidationError';
|
||||
}
|
||||
}
|
||||
|
||||
function toStringOrNull(value: unknown): string | null {
|
||||
if (value === undefined || value === null) {
|
||||
return null;
|
||||
}
|
||||
const trimmed = String(value).trim();
|
||||
return trimmed ? trimmed : null;
|
||||
}
|
||||
|
||||
function normalizePieceModelCustomFields(
|
||||
customFields: unknown,
|
||||
): PieceModelCustomField[] {
|
||||
if (!Array.isArray(customFields)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const normalized: PieceModelCustomField[] = [];
|
||||
|
||||
customFields.forEach((entry, index) => {
|
||||
if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const record = entry as Record<string, unknown>;
|
||||
const rawName =
|
||||
(typeof record.name === 'string' ? record.name : undefined) ??
|
||||
(typeof record.key === 'string' ? record.key : undefined) ??
|
||||
undefined;
|
||||
|
||||
const name = rawName ? rawName.trim() : '';
|
||||
|
||||
if (!name) {
|
||||
throw new PieceModelStructureValidationError(
|
||||
`customFields[${index}].name doit être une chaîne non vide`,
|
||||
);
|
||||
}
|
||||
|
||||
const field: PieceModelCustomField = { name };
|
||||
|
||||
if ('value' in record) {
|
||||
field.value = record.value;
|
||||
}
|
||||
|
||||
if (typeof record.type === 'string') {
|
||||
field.type = record.type;
|
||||
}
|
||||
|
||||
if ('required' in record) {
|
||||
field.required = Boolean(record.required);
|
||||
}
|
||||
|
||||
if (Array.isArray(record.options)) {
|
||||
field.options = record.options;
|
||||
} else if (typeof record.optionsText === 'string') {
|
||||
const options = record.optionsText
|
||||
.split(/\r?\n/)
|
||||
.map((option) => option.trim())
|
||||
.filter((option) => option.length > 0);
|
||||
if (options.length > 0) {
|
||||
field.options = options;
|
||||
}
|
||||
}
|
||||
|
||||
normalized.push(field);
|
||||
});
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
export const PieceModelStructureSchema = {
|
||||
parse(input: unknown): PieceModelStructure {
|
||||
if (input === undefined || input === null) {
|
||||
return { customFields: [] };
|
||||
}
|
||||
|
||||
if (typeof input !== 'object' || Array.isArray(input)) {
|
||||
throw new PieceModelStructureValidationError(
|
||||
'La structure de pièce doit être un objet JSON.',
|
||||
);
|
||||
}
|
||||
|
||||
const record = input as Record<string, unknown>;
|
||||
|
||||
const structure: PieceModelStructure = { ...record };
|
||||
const customFields = normalizePieceModelCustomFields(record.customFields);
|
||||
if (customFields.length > 0 || 'customFields' in record) {
|
||||
structure.customFields = customFields;
|
||||
}
|
||||
|
||||
const normalizedTypePiece = toStringOrNull(record.typePieceId);
|
||||
if (normalizedTypePiece) {
|
||||
structure.typePieceId = normalizedTypePiece;
|
||||
} else if ('typePieceId' in record) {
|
||||
delete (structure as Record<string, unknown>).typePieceId;
|
||||
}
|
||||
|
||||
return structure;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -39,3 +39,16 @@ export type ComponentModelStructure = {
|
||||
}
|
||||
>;
|
||||
};
|
||||
|
||||
export type PieceModelCustomField = {
|
||||
name: string;
|
||||
value?: unknown;
|
||||
type?: string;
|
||||
required?: boolean;
|
||||
options?: unknown;
|
||||
};
|
||||
|
||||
export type PieceModelStructure = {
|
||||
customFields?: PieceModelCustomField[];
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ComposantModelsRepository } from '../../common/repositories/composant-models.repository';
|
||||
import type { Prisma } from '@prisma/client';
|
||||
import {
|
||||
CreateComposantModelDto,
|
||||
UpdateComposantModelDto,
|
||||
} from '../../shared/dto/type.dto';
|
||||
import { ComponentModelStructureSchema } from '../../shared/schemas/inventory';
|
||||
import type { ComponentModelStructure } from '../../shared/types/inventory';
|
||||
|
||||
const COMPOSANT_MODEL_INCLUDE = {
|
||||
typeComposant: true,
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export class ComposantModelService {
|
||||
constructor(private readonly repository: ComposantModelsRepository) {}
|
||||
|
||||
async create(dto: CreateComposantModelDto) {
|
||||
const { typeComposantId, structure, ...data } = dto;
|
||||
const parsedStructure = this.parseStructure(structure);
|
||||
|
||||
const created = await this.repository.create(
|
||||
{
|
||||
...data,
|
||||
structure: parsedStructure as Prisma.InputJsonValue,
|
||||
typeComposant: { connect: { id: typeComposantId } },
|
||||
},
|
||||
COMPOSANT_MODEL_INCLUDE,
|
||||
);
|
||||
|
||||
return this.withParsedStructure(created);
|
||||
}
|
||||
|
||||
async findAll(typeComposantId?: string) {
|
||||
const models = await this.repository.findAll(
|
||||
typeComposantId,
|
||||
COMPOSANT_MODEL_INCLUDE,
|
||||
);
|
||||
|
||||
return models.map((model) => this.mapStructure(model));
|
||||
}
|
||||
|
||||
async findOne(id: string) {
|
||||
const model = await this.repository.findOne(id, COMPOSANT_MODEL_INCLUDE);
|
||||
return this.withParsedStructure(model);
|
||||
}
|
||||
|
||||
async update(id: string, dto: UpdateComposantModelDto) {
|
||||
const { typeComposantId, structure, ...data } = dto;
|
||||
|
||||
const parsedStructure =
|
||||
structure !== undefined ? this.parseStructure(structure) : undefined;
|
||||
|
||||
const updated = await this.repository.update(
|
||||
id,
|
||||
{
|
||||
...data,
|
||||
...(parsedStructure
|
||||
? { structure: parsedStructure as Prisma.InputJsonValue }
|
||||
: {}),
|
||||
...(typeComposantId
|
||||
? { typeComposant: { connect: { id: typeComposantId } } }
|
||||
: {}),
|
||||
},
|
||||
COMPOSANT_MODEL_INCLUDE,
|
||||
);
|
||||
|
||||
return this.withParsedStructure(updated);
|
||||
}
|
||||
|
||||
async remove(id: string) {
|
||||
return this.repository.delete(id);
|
||||
}
|
||||
|
||||
private parseStructure(
|
||||
structure: unknown | undefined,
|
||||
): ComponentModelStructure {
|
||||
return ComponentModelStructureSchema.parse(structure);
|
||||
}
|
||||
|
||||
private mapStructure<T extends { structure?: unknown }>(
|
||||
model: T,
|
||||
): T & { structure: ComponentModelStructure } {
|
||||
const structure = this.parseStructure((model as any).structure);
|
||||
return {
|
||||
...model,
|
||||
structure,
|
||||
};
|
||||
}
|
||||
|
||||
private withParsedStructure<T extends { structure?: unknown }>(
|
||||
model: T | null,
|
||||
): (T & { structure: ComponentModelStructure }) | null {
|
||||
return model ? this.mapStructure(model) : null;
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { PieceModelsRepository } from '../../common/repositories/piece-models.repository';
|
||||
import {
|
||||
CreatePieceModelDto,
|
||||
UpdatePieceModelDto,
|
||||
} from '../../shared/dto/type.dto';
|
||||
|
||||
const PIECE_MODEL_INCLUDE = {
|
||||
typePiece: true,
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export class PieceModelService {
|
||||
constructor(private readonly repository: PieceModelsRepository) {}
|
||||
|
||||
async create(dto: CreatePieceModelDto) {
|
||||
const { typePieceId, ...data } = dto;
|
||||
|
||||
return this.repository.create(
|
||||
{
|
||||
...data,
|
||||
typePiece: { connect: { id: typePieceId } },
|
||||
},
|
||||
PIECE_MODEL_INCLUDE,
|
||||
);
|
||||
}
|
||||
|
||||
async findAll(typePieceId?: string) {
|
||||
return this.repository.findAll(typePieceId, PIECE_MODEL_INCLUDE);
|
||||
}
|
||||
|
||||
async findOne(id: string) {
|
||||
return this.repository.findOne(id, PIECE_MODEL_INCLUDE);
|
||||
}
|
||||
|
||||
async update(id: string, dto: UpdatePieceModelDto) {
|
||||
const { typePieceId, ...data } = dto;
|
||||
|
||||
return this.repository.update(
|
||||
id,
|
||||
{
|
||||
...data,
|
||||
...(typePieceId ? { typePiece: { connect: { id: typePieceId } } } : {}),
|
||||
},
|
||||
PIECE_MODEL_INCLUDE,
|
||||
);
|
||||
}
|
||||
|
||||
async remove(id: string) {
|
||||
return this.repository.delete(id);
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
CreateTypeComposantDto,
|
||||
UpdateTypeComposantDto,
|
||||
} from '../../shared/dto/type.dto';
|
||||
import { ComponentModelStructureSchema } from '../../shared/schemas/inventory';
|
||||
|
||||
@Injectable()
|
||||
export class TypeComponentService {
|
||||
@@ -15,7 +16,11 @@ export class TypeComponentService {
|
||||
|
||||
async create(dto: CreateTypeComposantDto) {
|
||||
const code = await this.repository.generateUniqueCode(dto.name);
|
||||
const data = ModelTypeMapper.toComponentCreateInput(dto, code);
|
||||
const skeleton =
|
||||
dto.structure !== undefined
|
||||
? ComponentModelStructureSchema.parse(dto.structure)
|
||||
: undefined;
|
||||
const data = ModelTypeMapper.toComponentCreateInput(dto, code, skeleton);
|
||||
|
||||
return this.repository.createComponentType(data, COMPONENT_TYPE_INCLUDE);
|
||||
}
|
||||
@@ -37,7 +42,11 @@ export class TypeComponentService {
|
||||
await this.repository.createComponentTypeCustomFields(id, fields);
|
||||
}
|
||||
|
||||
const data = ModelTypeMapper.toComponentUpdateInput(dto);
|
||||
const skeleton =
|
||||
dto.structure !== undefined
|
||||
? ComponentModelStructureSchema.parse(dto.structure)
|
||||
: undefined;
|
||||
const data = ModelTypeMapper.toComponentUpdateInput(dto, skeleton);
|
||||
return this.repository.updateComponentType(
|
||||
id,
|
||||
data,
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
CreateTypePieceDto,
|
||||
UpdateTypePieceDto,
|
||||
} from '../../shared/dto/type.dto';
|
||||
import { PieceModelStructureSchema } from '../../shared/schemas/inventory';
|
||||
|
||||
@Injectable()
|
||||
export class TypePieceService {
|
||||
@@ -15,7 +16,11 @@ export class TypePieceService {
|
||||
|
||||
async create(dto: CreateTypePieceDto) {
|
||||
const code = await this.repository.generateUniqueCode(dto.name);
|
||||
const data = ModelTypeMapper.toPieceCreateInput(dto, code);
|
||||
const skeleton =
|
||||
dto.structure !== undefined
|
||||
? PieceModelStructureSchema.parse(dto.structure)
|
||||
: undefined;
|
||||
const data = ModelTypeMapper.toPieceCreateInput(dto, code, skeleton);
|
||||
|
||||
const created = await this.repository.createPieceType(
|
||||
data,
|
||||
@@ -43,7 +48,11 @@ export class TypePieceService {
|
||||
await this.repository.createPieceTypeCustomFields(id, fields);
|
||||
}
|
||||
|
||||
const data = ModelTypeMapper.toPieceUpdateInput(dto);
|
||||
const skeleton =
|
||||
dto.structure !== undefined
|
||||
? PieceModelStructureSchema.parse(dto.structure)
|
||||
: undefined;
|
||||
const data = ModelTypeMapper.toPieceUpdateInput(dto, skeleton);
|
||||
const updated = await this.repository.updatePieceType(
|
||||
id,
|
||||
data,
|
||||
|
||||
@@ -5,12 +5,8 @@ import { PrismaService } from '../prisma/prisma.service';
|
||||
import { TypeMachineService } from './services/type-machine.service';
|
||||
import { TypeComponentService } from './services/type-component.service';
|
||||
import { TypePieceService } from './services/type-piece.service';
|
||||
import { ComposantModelService } from './services/composant-model.service';
|
||||
import { PieceModelService } from './services/piece-model.service';
|
||||
import { TypeMachinesRepository } from '../common/repositories/type-machines.repository';
|
||||
import { ModelTypesRepository } from '../common/repositories/model-types.repository';
|
||||
import { ComposantModelsRepository } from '../common/repositories/composant-models.repository';
|
||||
import { PieceModelsRepository } from '../common/repositories/piece-models.repository';
|
||||
|
||||
describe('TypesController', () => {
|
||||
let controller: TypesController;
|
||||
@@ -23,12 +19,8 @@ describe('TypesController', () => {
|
||||
TypeMachineService,
|
||||
TypeComponentService,
|
||||
TypePieceService,
|
||||
ComposantModelService,
|
||||
PieceModelService,
|
||||
TypeMachinesRepository,
|
||||
ModelTypesRepository,
|
||||
ComposantModelsRepository,
|
||||
PieceModelsRepository,
|
||||
PrismaService,
|
||||
],
|
||||
}).compile();
|
||||
|
||||
@@ -1,13 +1,4 @@
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Body,
|
||||
Patch,
|
||||
Param,
|
||||
Delete,
|
||||
Query,
|
||||
} from '@nestjs/common';
|
||||
import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
|
||||
import { TypesService } from './types.service';
|
||||
import {
|
||||
CreateTypeMachineDto,
|
||||
@@ -16,10 +7,6 @@ import {
|
||||
UpdateTypeComposantDto,
|
||||
CreateTypePieceDto,
|
||||
UpdateTypePieceDto,
|
||||
CreateComposantModelDto,
|
||||
UpdateComposantModelDto,
|
||||
CreatePieceModelDto,
|
||||
UpdatePieceModelDto,
|
||||
} from '../shared/dto/type.dto';
|
||||
|
||||
@Controller('types')
|
||||
@@ -66,37 +53,6 @@ export class TypesController {
|
||||
return this.typesService.findAllTypeComposants();
|
||||
}
|
||||
|
||||
// ComposantModel routes
|
||||
@Post('composants/models')
|
||||
createComposantModel(
|
||||
@Body() createComposantModelDto: CreateComposantModelDto,
|
||||
) {
|
||||
return this.typesService.createComposantModel(createComposantModelDto);
|
||||
}
|
||||
|
||||
@Get('composants/models')
|
||||
findAllComposantModels(@Query('typeComposantId') typeComposantId?: string) {
|
||||
return this.typesService.findAllComposantModels(typeComposantId);
|
||||
}
|
||||
|
||||
@Get('composants/models/:id')
|
||||
findOneComposantModel(@Param('id') id: string) {
|
||||
return this.typesService.findOneComposantModel(id);
|
||||
}
|
||||
|
||||
@Patch('composants/models/:id')
|
||||
updateComposantModel(
|
||||
@Param('id') id: string,
|
||||
@Body() updateComposantModelDto: UpdateComposantModelDto,
|
||||
) {
|
||||
return this.typesService.updateComposantModel(id, updateComposantModelDto);
|
||||
}
|
||||
|
||||
@Delete('composants/models/:id')
|
||||
removeComposantModel(@Param('id') id: string) {
|
||||
return this.typesService.removeComposantModel(id);
|
||||
}
|
||||
|
||||
@Get('composants/:id')
|
||||
findOneTypeComposant(@Param('id') id: string) {
|
||||
return this.typesService.findOneTypeComposant(id);
|
||||
@@ -126,35 +82,6 @@ export class TypesController {
|
||||
return this.typesService.findAllTypePieces();
|
||||
}
|
||||
|
||||
// PieceModel routes
|
||||
@Post('pieces/models')
|
||||
createPieceModel(@Body() createPieceModelDto: CreatePieceModelDto) {
|
||||
return this.typesService.createPieceModel(createPieceModelDto);
|
||||
}
|
||||
|
||||
@Get('pieces/models')
|
||||
findAllPieceModels(@Query('typePieceId') typePieceId?: string) {
|
||||
return this.typesService.findAllPieceModels(typePieceId);
|
||||
}
|
||||
|
||||
@Get('pieces/models/:id')
|
||||
findOnePieceModel(@Param('id') id: string) {
|
||||
return this.typesService.findOnePieceModel(id);
|
||||
}
|
||||
|
||||
@Patch('pieces/models/:id')
|
||||
updatePieceModel(
|
||||
@Param('id') id: string,
|
||||
@Body() updatePieceModelDto: UpdatePieceModelDto,
|
||||
) {
|
||||
return this.typesService.updatePieceModel(id, updatePieceModelDto);
|
||||
}
|
||||
|
||||
@Delete('pieces/models/:id')
|
||||
removePieceModel(@Param('id') id: string) {
|
||||
return this.typesService.removePieceModel(id);
|
||||
}
|
||||
|
||||
@Get('pieces/:id')
|
||||
findOneTypePiece(@Param('id') id: string) {
|
||||
return this.typesService.findOneTypePiece(id);
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ComposantModelsRepository } from '../common/repositories/composant-models.repository';
|
||||
import { ModelTypesRepository } from '../common/repositories/model-types.repository';
|
||||
import { PieceModelsRepository } from '../common/repositories/piece-models.repository';
|
||||
import { TypeMachinesRepository } from '../common/repositories/type-machines.repository';
|
||||
import { TypesController } from './types.controller';
|
||||
import { TypesService } from './types.service';
|
||||
import { ComposantModelService } from './services/composant-model.service';
|
||||
import { PieceModelService } from './services/piece-model.service';
|
||||
import { TypeComponentService } from './services/type-component.service';
|
||||
import { TypeMachineService } from './services/type-machine.service';
|
||||
import { TypePieceService } from './services/type-piece.service';
|
||||
@@ -18,12 +14,8 @@ import { TypePieceService } from './services/type-piece.service';
|
||||
TypeMachineService,
|
||||
TypeComponentService,
|
||||
TypePieceService,
|
||||
ComposantModelService,
|
||||
PieceModelService,
|
||||
TypeMachinesRepository,
|
||||
ModelTypesRepository,
|
||||
ComposantModelsRepository,
|
||||
PieceModelsRepository,
|
||||
],
|
||||
})
|
||||
export class TypesModule {}
|
||||
|
||||
@@ -4,12 +4,8 @@ import { PrismaService } from '../prisma/prisma.service';
|
||||
import { TypeMachineService } from './services/type-machine.service';
|
||||
import { TypeComponentService } from './services/type-component.service';
|
||||
import { TypePieceService } from './services/type-piece.service';
|
||||
import { ComposantModelService } from './services/composant-model.service';
|
||||
import { PieceModelService } from './services/piece-model.service';
|
||||
import { TypeMachinesRepository } from '../common/repositories/type-machines.repository';
|
||||
import { ModelTypesRepository } from '../common/repositories/model-types.repository';
|
||||
import { ComposantModelsRepository } from '../common/repositories/composant-models.repository';
|
||||
import { PieceModelsRepository } from '../common/repositories/piece-models.repository';
|
||||
|
||||
describe('TypesService', () => {
|
||||
let service: TypesService;
|
||||
@@ -21,12 +17,8 @@ describe('TypesService', () => {
|
||||
TypeMachineService,
|
||||
TypeComponentService,
|
||||
TypePieceService,
|
||||
ComposantModelService,
|
||||
PieceModelService,
|
||||
TypeMachinesRepository,
|
||||
ModelTypesRepository,
|
||||
ComposantModelsRepository,
|
||||
PieceModelsRepository,
|
||||
PrismaService,
|
||||
],
|
||||
}).compile();
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ComposantModelService } from './services/composant-model.service';
|
||||
import { PieceModelService } from './services/piece-model.service';
|
||||
import { TypeComponentService } from './services/type-component.service';
|
||||
import { TypeMachineService } from './services/type-machine.service';
|
||||
import { TypePieceService } from './services/type-piece.service';
|
||||
@@ -11,10 +9,6 @@ import {
|
||||
UpdateTypeComposantDto,
|
||||
CreateTypePieceDto,
|
||||
UpdateTypePieceDto,
|
||||
CreateComposantModelDto,
|
||||
UpdateComposantModelDto,
|
||||
CreatePieceModelDto,
|
||||
UpdatePieceModelDto,
|
||||
} from '../shared/dto/type.dto';
|
||||
|
||||
@Injectable()
|
||||
@@ -23,8 +17,6 @@ export class TypesService {
|
||||
private readonly typeMachineService: TypeMachineService,
|
||||
private readonly typeComponentService: TypeComponentService,
|
||||
private readonly typePieceService: TypePieceService,
|
||||
private readonly composantModelService: ComposantModelService,
|
||||
private readonly pieceModelService: PieceModelService,
|
||||
) {}
|
||||
|
||||
// TypeMachine
|
||||
@@ -89,46 +81,4 @@ export class TypesService {
|
||||
removeTypePiece(id: string) {
|
||||
return this.typePieceService.remove(id);
|
||||
}
|
||||
|
||||
// ComposantModel
|
||||
createComposantModel(dto: CreateComposantModelDto) {
|
||||
return this.composantModelService.create(dto);
|
||||
}
|
||||
|
||||
findAllComposantModels(typeComposantId?: string) {
|
||||
return this.composantModelService.findAll(typeComposantId);
|
||||
}
|
||||
|
||||
findOneComposantModel(id: string) {
|
||||
return this.composantModelService.findOne(id);
|
||||
}
|
||||
|
||||
updateComposantModel(id: string, dto: UpdateComposantModelDto) {
|
||||
return this.composantModelService.update(id, dto);
|
||||
}
|
||||
|
||||
removeComposantModel(id: string) {
|
||||
return this.composantModelService.remove(id);
|
||||
}
|
||||
|
||||
// PieceModel
|
||||
createPieceModel(dto: CreatePieceModelDto) {
|
||||
return this.pieceModelService.create(dto);
|
||||
}
|
||||
|
||||
findAllPieceModels(typePieceId?: string) {
|
||||
return this.pieceModelService.findAll(typePieceId);
|
||||
}
|
||||
|
||||
findOnePieceModel(id: string) {
|
||||
return this.pieceModelService.findOne(id);
|
||||
}
|
||||
|
||||
updatePieceModel(id: string, dto: UpdatePieceModelDto) {
|
||||
return this.pieceModelService.update(id, dto);
|
||||
}
|
||||
|
||||
removePieceModel(id: string) {
|
||||
return this.pieceModelService.remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user