feat: Add Model gestion for piece and component

This commit is contained in:
Matthieu
2025-09-23 15:05:33 +02:00
parent bc225bf3e8
commit e64fba3ae7
54 changed files with 1640 additions and 569 deletions

View File

@@ -156,9 +156,12 @@ class InMemoryPrismaService {
private sites: SiteRecord[] = [];
private typeComposants: TypeComposantRecord[] = [];
private typePieces: TypePieceRecord[] = [];
private modelTypeCodeCounter = 0;
private typeMachines: TypeMachineRecord[] = [];
private typeMachineComponentRequirements: TypeMachineComponentRequirementRecord[] = [];
private typeMachinePieceRequirements: TypeMachinePieceRequirementRecord[] = [];
private typeMachineComponentRequirements: TypeMachineComponentRequirementRecord[] =
[];
private typeMachinePieceRequirements: TypeMachinePieceRequirementRecord[] =
[];
private machines: MachineRecord[] = [];
private composants: ComposantRecord[] = [];
private pieces: PieceRecord[] = [];
@@ -179,6 +182,7 @@ class InMemoryPrismaService {
this.sites = [];
this.typeComposants = [];
this.typePieces = [];
this.modelTypeCodeCounter = 0;
this.typeMachines = [];
this.typeMachineComponentRequirements = [];
this.typeMachinePieceRequirements = [];
@@ -268,7 +272,9 @@ class InMemoryPrismaService {
return include?.customFields
? {
...record,
customFields: this.customFields.filter((field) => field.typeComposantId === record.id),
customFields: this.customFields.filter(
(field) => field.typeComposantId === record.id,
),
}
: { ...record };
},
@@ -280,7 +286,9 @@ class InMemoryPrismaService {
return this.typeComposants.find((item) => item.id === where.id) ?? null;
}
if (where.name) {
return this.typeComposants.find((item) => item.name === where.name) ?? null;
return (
this.typeComposants.find((item) => item.name === where.name) ?? null
);
}
return null;
},
@@ -325,7 +333,9 @@ class InMemoryPrismaService {
return include?.customFields
? {
...record,
customFields: this.customFields.filter((field) => field.typePieceId === record.id),
customFields: this.customFields.filter(
(field) => field.typePieceId === record.id,
),
}
: { ...record };
},
@@ -383,20 +393,22 @@ class InMemoryPrismaService {
let componentRequirements: TypeMachineComponentRequirementRecord[] = [];
if (data.componentRequirements?.create) {
componentRequirements = data.componentRequirements.create.map((requirement) => {
const req: TypeMachineComponentRequirementRecord = {
id: generateId('tmc_req'),
typeMachineId: record.id,
typeComposantId: requirement.typeComposant.connect.id,
label: requirement.label ?? null,
minCount: requirement.minCount ?? 1,
maxCount: requirement.maxCount ?? null,
required: requirement.required ?? true,
allowNewModels: requirement.allowNewModels ?? true,
};
this.typeMachineComponentRequirements.push(req);
return req;
});
componentRequirements = data.componentRequirements.create.map(
(requirement) => {
const req: TypeMachineComponentRequirementRecord = {
id: generateId('tmc_req'),
typeMachineId: record.id,
typeComposantId: requirement.typeComposant.connect.id,
label: requirement.label ?? null,
minCount: requirement.minCount ?? 1,
maxCount: requirement.maxCount ?? null,
required: requirement.required ?? true,
allowNewModels: requirement.allowNewModels ?? true,
};
this.typeMachineComponentRequirements.push(req);
return req;
},
);
}
let pieceRequirements: TypeMachinePieceRequirementRecord[] = [];
@@ -417,28 +429,49 @@ class InMemoryPrismaService {
});
}
return this.buildTypeMachine(record, include ?? {}, componentRequirements, pieceRequirements);
return this.buildTypeMachine(
record,
include ?? {},
componentRequirements,
pieceRequirements,
);
},
findUnique: async ({ where, include }: any) => {
const typeMachine = this.typeMachines.find((item) => item.id === where.id);
const typeMachine = this.typeMachines.find(
(item) => item.id === where.id,
);
if (!typeMachine) {
return null;
}
const componentRequirements = this.typeMachineComponentRequirements.filter(
(item) => item.typeMachineId === typeMachine.id,
);
const componentRequirements =
this.typeMachineComponentRequirements.filter(
(item) => item.typeMachineId === typeMachine.id,
);
const pieceRequirements = this.typeMachinePieceRequirements.filter(
(item) => item.typeMachineId === typeMachine.id,
);
return this.buildTypeMachine(typeMachine, include ?? {}, componentRequirements, pieceRequirements);
return this.buildTypeMachine(
typeMachine,
include ?? {},
componentRequirements,
pieceRequirements,
);
},
findMany: async ({ include }: any = {}) => {
return this.typeMachines.map((item) => {
const componentRequirements = this.typeMachineComponentRequirements.filter(
const componentRequirements =
this.typeMachineComponentRequirements.filter(
(req) => req.typeMachineId === item.id,
);
const pieceRequirements = this.typeMachinePieceRequirements.filter(
(req) => req.typeMachineId === item.id,
);
const pieceRequirements = this.typeMachinePieceRequirements.filter((req) => req.typeMachineId === item.id);
return this.buildTypeMachine(item, include ?? {}, componentRequirements, pieceRequirements);
return this.buildTypeMachine(
item,
include ?? {},
componentRequirements,
pieceRequirements,
);
});
},
};
@@ -471,9 +504,13 @@ class InMemoryPrismaService {
findMany: async ({ include, where }: any = {}) => {
let machines = this.machines;
if (where?.siteId) {
machines = machines.filter((machine) => machine.siteId === where.siteId);
machines = machines.filter(
(machine) => machine.siteId === where.siteId,
);
}
return machines.map((machine) => this.buildMachine(machine, include ?? {}));
return machines.map((machine) =>
this.buildMachine(machine, include ?? {}),
);
},
delete: async ({ where }: any) => {
const index = this.machines.findIndex((item) => item.id === where.id);
@@ -498,7 +535,8 @@ class InMemoryPrismaService {
parentComposantId: data.parentComposantId ?? null,
typeComposantId: data.typeComposantId ?? null,
composantModelId: data.composantModelId ?? null,
typeMachineComponentRequirementId: data.typeMachineComponentRequirementId ?? null,
typeMachineComponentRequirementId:
data.typeMachineComponentRequirementId ?? null,
constructeurId: data.constructeurId ?? null,
createdAt: now,
updatedAt: now,
@@ -509,10 +547,14 @@ class InMemoryPrismaService {
findMany: async ({ where }: any) => {
let composants = this.composants;
if (where?.machineId !== undefined) {
composants = composants.filter((item) => item.machineId === where.machineId);
composants = composants.filter(
(item) => item.machineId === where.machineId,
);
}
if (where?.parentComposantId !== undefined) {
composants = composants.filter((item) => item.parentComposantId === where.parentComposantId);
composants = composants.filter(
(item) => item.parentComposantId === where.parentComposantId,
);
}
return composants.map((item) => ({ ...item }));
},
@@ -531,7 +573,8 @@ class InMemoryPrismaService {
composantId: data.composantId ?? null,
typePieceId: data.typePieceId ?? null,
pieceModelId: data.pieceModelId ?? null,
typeMachinePieceRequirementId: data.typeMachinePieceRequirementId ?? null,
typeMachinePieceRequirementId:
data.typeMachinePieceRequirementId ?? null,
constructeurId: data.constructeurId ?? null,
createdAt: now,
updatedAt: now,
@@ -545,7 +588,9 @@ class InMemoryPrismaService {
pieces = pieces.filter((item) => item.machineId === where.machineId);
}
if (where?.composantId !== undefined) {
pieces = pieces.filter((item) => item.composantId === where.composantId);
pieces = pieces.filter(
(item) => item.composantId === where.composantId,
);
}
return pieces.map((item) => ({ ...item }));
},
@@ -574,14 +619,18 @@ class InMemoryPrismaService {
return this.customFields
.filter((field) => {
if (!where) return true;
return Object.entries(where).every(([key, value]) => (field as any)[key] === value);
return Object.entries(where).every(
([key, value]) => (field as any)[key] === value,
);
})
.map((field) => ({ ...field }));
},
deleteMany: async ({ where }: any) => {
const before = this.customFields.length;
this.customFields = this.customFields.filter((field) => {
return !Object.entries(where).every(([key, value]) => (field as any)[key] === value);
return !Object.entries(where).every(
([key, value]) => (field as any)[key] === value,
);
});
return { count: before - this.customFields.length };
},
@@ -606,12 +655,18 @@ class InMemoryPrismaService {
findMany: async ({ where, include }: any) => {
const records = this.customFieldValues.filter((value) => {
if (!where) return true;
return Object.entries(where).every(([key, v]) => (value as any)[key] === v);
return Object.entries(where).every(
([key, v]) => (value as any)[key] === v,
);
});
return records.map((record) => this.buildCustomFieldValue(record, include ?? {}));
return records.map((record) =>
this.buildCustomFieldValue(record, include ?? {}),
);
},
findUnique: async ({ where, include }: any) => {
const record = this.customFieldValues.find((value) => value.id === where.id);
const record = this.customFieldValues.find(
(value) => value.id === where.id,
);
if (!record) {
return null;
}
@@ -625,7 +680,9 @@ class InMemoryPrismaService {
);
},
update: async ({ where, data, include }: any) => {
const record = this.customFieldValues.find((value) => value.id === where.id);
const record = this.customFieldValues.find(
(value) => value.id === where.id,
);
if (!record) {
throw new Error('Custom field value not found');
}
@@ -636,7 +693,9 @@ class InMemoryPrismaService {
return this.buildCustomFieldValue(record, include ?? {});
},
delete: async ({ where }: any) => {
const index = this.customFieldValues.findIndex((value) => value.id === where.id);
const index = this.customFieldValues.findIndex(
(value) => value.id === where.id,
);
if (index === -1) {
throw new Error('Custom field value not found');
}
@@ -649,24 +708,32 @@ class InMemoryPrismaService {
count: async ({ where }: any) => {
return this.profiles.filter((profile) => {
if (!where) return true;
return Object.entries(where).every(([key, value]) => (profile as any)[key] === value);
return Object.entries(where).every(
([key, value]) => (profile as any)[key] === value,
);
}).length;
},
findMany: async ({ where, orderBy, select }: any) => {
let profiles = this.profiles;
if (where) {
profiles = profiles.filter((profile) =>
Object.entries(where).every(([key, value]) => (profile as any)[key] === value),
Object.entries(where).every(
([key, value]) => (profile as any)[key] === value,
),
);
}
if (orderBy?.createdAt === 'asc') {
profiles = [...profiles].sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());
profiles = [...profiles].sort(
(a, b) => a.createdAt.getTime() - b.createdAt.getTime(),
);
}
return profiles.map((profile) => this.applySelect(profile, select));
},
findFirst: async ({ where, select }: any) => {
const profile = this.profiles.find((item) =>
Object.entries(where).every(([key, value]) => (item as any)[key] === value),
Object.entries(where).every(
([key, value]) => (item as any)[key] === value,
),
);
return profile ? this.applySelect(profile, select) : null;
},
@@ -713,8 +780,12 @@ class InMemoryPrismaService {
private buildSite(site: SiteRecord, include: any) {
const base: any = { ...site };
if (include?.machines) {
const machines = this.machines.filter((machine) => machine.siteId === site.id);
base.machines = machines.map((machine) => this.buildMachine(machine, include.machines.include ?? {}));
const machines = this.machines.filter(
(machine) => machine.siteId === site.id,
);
base.machines = machines.map((machine) =>
this.buildMachine(machine, include.machines.include ?? {}),
);
}
if (include?.documents) {
base.documents = [];
@@ -730,13 +801,17 @@ class InMemoryPrismaService {
) {
const base: any = { ...record };
if (include?.customFields) {
base.customFields = this.customFields.filter((field) => field.typeMachineId === record.id);
base.customFields = this.customFields.filter(
(field) => field.typeMachineId === record.id,
);
}
if (include?.componentRequirements) {
base.componentRequirements = componentRequirements.map((requirement) => ({
...requirement,
typeComposant: include.componentRequirements.include?.typeComposant
? this.typeComposants.find((item) => item.id === requirement.typeComposantId) ?? null
? (this.typeComposants.find(
(item) => item.id === requirement.typeComposantId,
) ?? null)
: undefined,
}));
}
@@ -744,14 +819,18 @@ class InMemoryPrismaService {
base.pieceRequirements = pieceRequirements.map((requirement) => ({
...requirement,
typePiece: include.pieceRequirements.include?.typePiece
? this.typePieces.find((item) => item.id === requirement.typePieceId) ?? null
? (this.typePieces.find(
(item) => item.id === requirement.typePieceId,
) ?? null)
: undefined,
}));
}
if (include?.machines) {
base.machines = this.machines
.filter((machine) => machine.typeMachineId === record.id)
.map((machine) => this.buildMachine(machine, include.machines.include ?? {}));
.map((machine) =>
this.buildMachine(machine, include.machines.include ?? {}),
);
}
return base;
}
@@ -768,9 +847,10 @@ class InMemoryPrismaService {
? this.typeMachines.find((item) => item.id === machine.typeMachineId)
: null;
if (typeMachine) {
const componentRequirements = this.typeMachineComponentRequirements.filter(
(req) => req.typeMachineId === typeMachine.id,
);
const componentRequirements =
this.typeMachineComponentRequirements.filter(
(req) => req.typeMachineId === typeMachine.id,
);
const pieceRequirements = this.typeMachinePieceRequirements.filter(
(req) => req.typeMachineId === typeMachine.id,
);
@@ -790,20 +870,35 @@ class InMemoryPrismaService {
}
if (include?.composants) {
const composants = this.composants.filter((component) => component.machineId === machine.id);
const composants = this.composants.filter(
(component) => component.machineId === machine.id,
);
base.composants = composants
.filter((component) => component.parentComposantId === null)
.map((component) => this.buildComponent(component, include.composants.include ?? {}));
.map((component) =>
this.buildComponent(component, include.composants.include ?? {}),
);
}
if (include?.pieces) {
const machinePieces = this.pieces.filter((piece) => piece.machineId === machine.id && piece.composantId === null);
base.pieces = machinePieces.map((piece) => this.buildPiece(piece, include.pieces.include ?? {}));
const machinePieces = this.pieces.filter(
(piece) => piece.machineId === machine.id && piece.composantId === null,
);
base.pieces = machinePieces.map((piece) =>
this.buildPiece(piece, include.pieces.include ?? {}),
);
}
if (include?.customFieldValues) {
const values = this.customFieldValues.filter((value) => value.machineId === machine.id);
base.customFieldValues = values.map((value) => this.buildCustomFieldValue(value, include.customFieldValues.include ?? {}));
const values = this.customFieldValues.filter(
(value) => value.machineId === machine.id,
);
base.customFieldValues = values.map((value) =>
this.buildCustomFieldValue(
value,
include.customFieldValues.include ?? {},
),
);
}
if (include?.documents) {
@@ -818,7 +913,9 @@ class InMemoryPrismaService {
if (include?.typeComposant) {
base.typeComposant = component.typeComposantId
? this.typeComposants.find((item) => item.id === component.typeComposantId) ?? null
? (this.typeComposants.find(
(item) => item.id === component.typeComposantId,
) ?? null)
: null;
}
@@ -828,26 +925,42 @@ class InMemoryPrismaService {
if (include?.typeMachineComponentRequirement) {
const requirement = component.typeMachineComponentRequirementId
? this.typeMachineComponentRequirements.find((item) => item.id === component.typeMachineComponentRequirementId) ?? null
? (this.typeMachineComponentRequirements.find(
(item) => item.id === component.typeMachineComponentRequirementId,
) ?? null)
: null;
base.typeMachineComponentRequirement = requirement
? {
...requirement,
typeComposant: include.typeMachineComponentRequirement.include?.typeComposant
? this.typeComposants.find((item) => item.id === requirement.typeComposantId) ?? null
typeComposant: include.typeMachineComponentRequirement.include
?.typeComposant
? (this.typeComposants.find(
(item) => item.id === requirement.typeComposantId,
) ?? null)
: undefined,
}
: null;
}
if (include?.sousComposants) {
const children = this.composants.filter((item) => item.parentComposantId === component.id);
base.sousComposants = children.map((child) => this.buildComponent(child, include.sousComposants.include ?? {}));
const children = this.composants.filter(
(item) => item.parentComposantId === component.id,
);
base.sousComposants = children.map((child) =>
this.buildComponent(child, include.sousComposants.include ?? {}),
);
}
if (include?.customFieldValues) {
const values = this.customFieldValues.filter((value) => value.composantId === component.id);
base.customFieldValues = values.map((value) => this.buildCustomFieldValue(value, include.customFieldValues.include ?? {}));
const values = this.customFieldValues.filter(
(value) => value.composantId === component.id,
);
base.customFieldValues = values.map((value) =>
this.buildCustomFieldValue(
value,
include.customFieldValues.include ?? {},
),
);
}
if (include?.constructeur) {
@@ -855,8 +968,12 @@ class InMemoryPrismaService {
}
if (include?.pieces) {
const relatedPieces = this.pieces.filter((piece) => piece.composantId === component.id);
base.pieces = relatedPieces.map((piece) => this.buildPiece(piece, include.pieces.include ?? {}));
const relatedPieces = this.pieces.filter(
(piece) => piece.composantId === component.id,
);
base.pieces = relatedPieces.map((piece) =>
this.buildPiece(piece, include.pieces.include ?? {}),
);
}
return base;
@@ -866,8 +983,15 @@ class InMemoryPrismaService {
const base: any = { ...piece };
if (include?.customFieldValues) {
const values = this.customFieldValues.filter((value) => value.pieceId === piece.id);
base.customFieldValues = values.map((value) => this.buildCustomFieldValue(value, include.customFieldValues.include ?? {}));
const values = this.customFieldValues.filter(
(value) => value.pieceId === piece.id,
);
base.customFieldValues = values.map((value) =>
this.buildCustomFieldValue(
value,
include.customFieldValues.include ?? {},
),
);
}
if (include?.constructeur) {
@@ -880,13 +1004,17 @@ class InMemoryPrismaService {
if (include?.typeMachinePieceRequirement) {
const requirement = piece.typeMachinePieceRequirementId
? this.typeMachinePieceRequirements.find((item) => item.id === piece.typeMachinePieceRequirementId) ?? null
? (this.typeMachinePieceRequirements.find(
(item) => item.id === piece.typeMachinePieceRequirementId,
) ?? null)
: null;
base.typeMachinePieceRequirement = requirement
? {
...requirement,
typePiece: include.typeMachinePieceRequirement.include?.typePiece
? this.typePieces.find((item) => item.id === requirement.typePieceId) ?? null
? (this.typePieces.find(
(item) => item.id === requirement.typePieceId,
) ?? null)
: undefined,
}
: null;
@@ -894,7 +1022,8 @@ class InMemoryPrismaService {
if (include?.typePiece) {
base.typePiece = piece.typePieceId
? this.typePieces.find((item) => item.id === piece.typePieceId) ?? null
? (this.typePieces.find((item) => item.id === piece.typePieceId) ??
null)
: null;
}
@@ -904,7 +1033,9 @@ class InMemoryPrismaService {
private buildCustomFieldValue(record: CustomFieldValueRecord, include: any) {
const base: any = { ...record };
if (include?.customField) {
base.customField = this.customFields.find((field) => field.id === record.customFieldId) ?? null;
base.customField =
this.customFields.find((field) => field.id === record.customFieldId) ??
null;
}
return base;
}
@@ -918,7 +1049,6 @@ describe('Inventory flow (e2e)', () => {
beforeAll(async () => {
prismaStub = new InMemoryPrismaService();
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
})
@@ -945,14 +1075,16 @@ describe('Inventory flow (e2e)', () => {
});
it('should create a type, create a machine with new component/piece selections, and edit technical fields', async () => {
const siteResponse = await request(app.getHttpServer()).post('/sites').send({
name: 'Site Principal',
contactName: 'Responsable Maintenance',
contactPhone: '+33 1 23 45 67 89',
contactAddress: '1 rue de la Paix',
contactPostalCode: '75000',
contactCity: 'Paris',
});
const siteResponse = await request(app.getHttpServer())
.post('/sites')
.send({
name: 'Site Principal',
contactName: 'Responsable Maintenance',
contactPhone: '+33 1 23 45 67 89',
contactAddress: '1 rue de la Paix',
contactPostalCode: '75000',
contactCity: 'Paris',
});
expect(siteResponse.status).toBe(201);
const siteId = siteResponse.body.id;
@@ -1069,7 +1201,9 @@ describe('Inventory flow (e2e)', () => {
expect(machineResponse.status).toBe(201);
const machineId = machineResponse.body.id;
const machineDetailsResponse = await request(app.getHttpServer()).get(`/machines/${machineId}`);
const machineDetailsResponse = await request(app.getHttpServer()).get(
`/machines/${machineId}`,
);
expect(machineDetailsResponse.status).toBe(200);
const machine = machineDetailsResponse.body;
@@ -1092,7 +1226,9 @@ describe('Inventory flow (e2e)', () => {
expect(updateResponse.status).toBe(200);
expect(updateResponse.body.value).toBe('8 kW');
const refreshedMachineResponse = await request(app.getHttpServer()).get(`/machines/${machine.id}`);
const refreshedMachineResponse = await request(app.getHttpServer()).get(
`/machines/${machine.id}`,
);
expect(refreshedMachineResponse.status).toBe(200);
const refreshedComponent = refreshedMachineResponse.body.composants[0];
expect(refreshedComponent.customFieldValues[0].value).toBe('8 kW');
@@ -1110,7 +1246,9 @@ describe('Inventory flow (e2e)', () => {
} as any);
const created = { id: 'component-1' };
const createSpy = jest.spyOn(prisma.composant, 'create').mockResolvedValue(created as any);
const createSpy = jest
.spyOn(prisma.composant, 'create')
.mockResolvedValue(created as any);
const response = await request(app.getHttpServer())
.post('/composants')
@@ -1157,14 +1295,14 @@ describe('Inventory flow (e2e)', () => {
jest.spyOn(prisma.machine, 'findUnique').mockResolvedValue({
id: 'machine-1',
typeMachine: {
pieceRequirements: [
{ id: 'req-1', typePieceId: 'type-piece-1' },
],
pieceRequirements: [{ id: 'req-1', typePieceId: 'type-piece-1' }],
},
} as any);
const created = { id: 'piece-1' };
const createSpy = jest.spyOn(prisma.piece, 'create').mockResolvedValue(created as any);
const createSpy = jest
.spyOn(prisma.piece, 'create')
.mockResolvedValue(created as any);
const response = await request(app.getHttpServer())
.post('/pieces')
@@ -1184,9 +1322,7 @@ describe('Inventory flow (e2e)', () => {
jest.spyOn(prisma.machine, 'findUnique').mockResolvedValue({
id: 'machine-1',
typeMachine: {
pieceRequirements: [
{ id: 'req-1', typePieceId: 'type-piece-1' },
],
pieceRequirements: [{ id: 'req-1', typePieceId: 'type-piece-1' }],
},
} as any);
@@ -1203,7 +1339,6 @@ describe('Inventory flow (e2e)', () => {
.expect(400);
expect(createSpy).not.toHaveBeenCalled();
});
});
});