Refactor types service into modular services and repositories

This commit is contained in:
MatthieuTD
2025-09-25 16:15:37 +02:00
parent 2ce164784f
commit b10a6baf47
21 changed files with 1533 additions and 589 deletions

View File

@@ -0,0 +1,58 @@
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 },
});
}
}

View File

@@ -0,0 +1,71 @@
import { ModelTypesRepository } from './model-types.repository';
describe('ModelTypesRepository', () => {
const prismaMock = {
modelType: {
findUnique: jest.fn(),
create: jest.fn(),
findMany: jest.fn(),
findFirst: jest.fn(),
update: jest.fn(),
delete: jest.fn(),
},
customField: {
deleteMany: jest.fn(),
createMany: jest.fn(),
},
} as any;
const repository = new ModelTypesRepository(prismaMock);
beforeEach(() => {
jest.clearAllMocks();
});
it('should generate unique code when conflicts exist', async () => {
prismaMock.modelType.findUnique
.mockResolvedValueOnce({ id: 'existing' })
.mockResolvedValueOnce(null);
const code = await repository.generateUniqueCode('My Type');
expect(code).toBe('my-type-1');
expect(prismaMock.modelType.findUnique).toHaveBeenCalledTimes(2);
});
it('should create component type custom fields with relation id', async () => {
await repository.createComponentTypeCustomFields('comp-id', [
{ name: 'Field', type: 'string', required: true, options: [] },
]);
expect(prismaMock.customField.createMany).toHaveBeenCalledWith({
data: [
{
name: 'Field',
type: 'string',
required: true,
options: [],
typeComposantId: 'comp-id',
},
],
});
});
it('should create piece type custom fields with relation id', async () => {
await repository.createPieceTypeCustomFields('piece-id', [
{ name: 'Field', type: 'string', required: false, options: [] },
]);
expect(prismaMock.customField.createMany).toHaveBeenCalledWith({
data: [
{
name: 'Field',
type: 'string',
required: false,
options: [],
typePieceId: 'piece-id',
},
],
});
});
});

View File

@@ -0,0 +1,172 @@
import { Injectable } from '@nestjs/common';
import { ModelCategory, Prisma, PrismaClient } from '@prisma/client';
import { PrismaService } from '../../prisma/prisma.service';
import { slugifyName } from '../utils/slug.util';
type CustomFieldInput = Omit<
Prisma.CustomFieldCreateManyInput,
'id' | 'typeMachineId' | 'typePieceId' | 'typeComposantId'
>;
type PieceCustomFieldInput = Omit<
Prisma.CustomFieldCreateManyInput,
'id' | 'typeMachineId' | 'typePieceId' | 'typeComposantId'
>;
type ModelTypeCreateData = Omit<Prisma.ModelTypeCreateInput, 'category'>;
@Injectable()
export class ModelTypesRepository {
constructor(private readonly prisma: PrismaService) {}
private get client(): PrismaClient {
return this.prisma;
}
async generateUniqueCode(name: string): Promise<string> {
const base = slugifyName(name) || 'type';
let candidate = base;
let suffix = 1;
while (await this.client.modelType.findUnique({ where: { code: candidate } })) {
candidate = `${base}-${suffix++}`;
}
return candidate;
}
async createComponentType(
data: ModelTypeCreateData,
include?: Prisma.ModelTypeInclude,
) {
return this.client.modelType.create({
data: {
...data,
category: ModelCategory.COMPONENT,
},
include,
});
}
async findComponentTypes(include?: Prisma.ModelTypeInclude) {
return this.client.modelType.findMany({
where: { category: ModelCategory.COMPONENT },
include,
});
}
async findComponentType(id: string, include?: Prisma.ModelTypeInclude) {
return this.client.modelType.findFirst({
where: { id, category: ModelCategory.COMPONENT },
include,
});
}
async updateComponentType(
id: string,
data: Prisma.ModelTypeUpdateInput,
include?: Prisma.ModelTypeInclude,
) {
return this.client.modelType.update({
where: { id },
data,
include,
});
}
async deleteComponentType(id: string) {
return this.client.modelType.delete({
where: { id },
});
}
async deleteComponentTypeCustomFields(typeComposantId: string) {
await this.client.customField.deleteMany({
where: { typeComposantId },
});
}
async createComponentTypeCustomFields(
typeComposantId: string,
customFields: CustomFieldInput[],
) {
if (!customFields.length) {
return;
}
await this.client.customField.createMany({
data: customFields.map((field) => ({
...field,
typeComposantId,
})),
});
}
async createPieceType(
data: ModelTypeCreateData,
include?: Prisma.ModelTypeInclude,
) {
return this.client.modelType.create({
data: {
...data,
category: ModelCategory.PIECE,
},
include,
});
}
async findPieceTypes(include?: Prisma.ModelTypeInclude) {
return this.client.modelType.findMany({
where: { category: ModelCategory.PIECE },
include,
orderBy: { name: 'asc' },
});
}
async findPieceType(id: string, include?: Prisma.ModelTypeInclude) {
return this.client.modelType.findFirst({
where: { id, category: ModelCategory.PIECE },
include,
});
}
async updatePieceType(
id: string,
data: Prisma.ModelTypeUpdateInput,
include?: Prisma.ModelTypeInclude,
) {
return this.client.modelType.update({
where: { id },
data,
include,
});
}
async deletePieceType(id: string) {
return this.client.modelType.delete({
where: { id },
});
}
async deletePieceTypeCustomFields(typePieceId: string) {
await this.client.customField.deleteMany({
where: { typePieceId },
});
}
async createPieceTypeCustomFields(
typePieceId: string,
customFields: PieceCustomFieldInput[],
) {
if (!customFields.length) {
return;
}
await this.client.customField.createMany({
data: customFields.map((field) => ({
...field,
typePieceId,
})),
});
}
}

View File

@@ -0,0 +1,55 @@
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 },
});
}
}

View File

@@ -0,0 +1,117 @@
import { TypeMachinesRepository } from './type-machines.repository';
describe('TypeMachinesRepository', () => {
const prismaMock = {
customField: {
deleteMany: jest.fn(),
createMany: jest.fn(),
},
typeMachineComponentRequirement: {
deleteMany: jest.fn(),
createMany: jest.fn(),
},
typeMachinePieceRequirement: {
deleteMany: jest.fn(),
createMany: jest.fn(),
},
machine: {
findMany: jest.fn().mockResolvedValue([{ id: '1', name: 'Machine' }]),
},
typeMachine: {
create: jest.fn(),
findMany: jest.fn(),
findUnique: jest.fn(),
update: jest.fn(),
delete: jest.fn(),
},
} as any;
const repository = new TypeMachinesRepository(prismaMock);
beforeEach(() => {
jest.clearAllMocks();
});
it('should append typeMachineId when creating custom fields', async () => {
await repository.createCustomFields('machine-id', [
{ name: 'Field', type: 'string', required: true, options: [] },
]);
expect(prismaMock.customField.createMany).toHaveBeenCalledWith({
data: [
{
name: 'Field',
type: 'string',
required: true,
options: [],
typeMachineId: 'machine-id',
},
],
});
});
it('should append typeMachineId when creating component requirements', async () => {
await repository.createComponentRequirements('machine-id', [
{
label: 'Comp',
minCount: 1,
maxCount: 2,
required: true,
allowNewModels: true,
typeComposantId: 'comp',
},
]);
expect(
prismaMock.typeMachineComponentRequirement.createMany,
).toHaveBeenCalledWith({
data: [
{
label: 'Comp',
minCount: 1,
maxCount: 2,
required: true,
allowNewModels: true,
typeComposantId: 'comp',
typeMachineId: 'machine-id',
},
],
});
});
it('should append typeMachineId when creating piece requirements', async () => {
await repository.createPieceRequirements('machine-id', [
{
label: 'Piece',
minCount: 0,
maxCount: 1,
required: false,
allowNewModels: true,
typePieceId: 'piece',
},
]);
expect(prismaMock.typeMachinePieceRequirement.createMany).toHaveBeenCalledWith({
data: [
{
label: 'Piece',
minCount: 0,
maxCount: 1,
required: false,
allowNewModels: true,
typePieceId: 'piece',
typeMachineId: 'machine-id',
},
],
});
});
it('should find machines using type', async () => {
const result = await repository.findMachinesUsingType('machine-id');
expect(result).toEqual([{ id: '1', name: 'Machine' }]);
expect(prismaMock.machine.findMany).toHaveBeenCalledWith({
where: { typeMachineId: 'machine-id' },
select: { id: true, name: true },
});
});
});

View File

@@ -0,0 +1,141 @@
import { Injectable } from '@nestjs/common';
import { Prisma, PrismaClient } from '@prisma/client';
import { PrismaService } from '../../prisma/prisma.service';
type CustomFieldInput = Omit<
Prisma.CustomFieldCreateManyInput,
'id' | 'typeMachineId' | 'typePieceId' | 'typeComposantId'
>;
type ComponentRequirementInput = Omit<
Prisma.TypeMachineComponentRequirementCreateManyInput,
'id' | 'typeMachineId'
>;
type PieceRequirementInput = Omit<
Prisma.TypeMachinePieceRequirementCreateManyInput,
'id' | 'typeMachineId'
>;
@Injectable()
export class TypeMachinesRepository {
constructor(private readonly prisma: PrismaService) {}
private get client(): PrismaClient {
return this.prisma;
}
async create(
data: Prisma.TypeMachineCreateInput,
include?: Prisma.TypeMachineInclude,
) {
return this.client.typeMachine.create({
data,
include,
});
}
async findAll(include?: Prisma.TypeMachineInclude) {
return this.client.typeMachine.findMany({
include,
});
}
async findOne(id: string, include?: Prisma.TypeMachineInclude) {
return this.client.typeMachine.findUnique({
where: { id },
include,
});
}
async update(
id: string,
data: Prisma.TypeMachineUpdateInput,
include?: Prisma.TypeMachineInclude,
) {
return this.client.typeMachine.update({
where: { id },
data,
include,
});
}
async delete(id: string) {
return this.client.typeMachine.delete({
where: { id },
});
}
async deleteCustomFields(typeMachineId: string) {
await this.client.customField.deleteMany({
where: { typeMachineId },
});
}
async createCustomFields(
typeMachineId: string,
customFields: CustomFieldInput[],
) {
if (!customFields.length) {
return;
}
await this.client.customField.createMany({
data: customFields.map((field) => ({
...field,
typeMachineId,
})),
});
}
async deleteComponentRequirements(typeMachineId: string) {
await this.client.typeMachineComponentRequirement.deleteMany({
where: { typeMachineId },
});
}
async createComponentRequirements(
typeMachineId: string,
requirements: ComponentRequirementInput[],
) {
if (!requirements.length) {
return;
}
await this.client.typeMachineComponentRequirement.createMany({
data: requirements.map((requirement) => ({
...requirement,
typeMachineId,
})),
});
}
async deletePieceRequirements(typeMachineId: string) {
await this.client.typeMachinePieceRequirement.deleteMany({
where: { typeMachineId },
});
}
async createPieceRequirements(
typeMachineId: string,
requirements: PieceRequirementInput[],
) {
if (!requirements.length) {
return;
}
await this.client.typeMachinePieceRequirement.createMany({
data: requirements.map((requirement) => ({
...requirement,
typeMachineId,
})),
});
}
async findMachinesUsingType(typeMachineId: string) {
return this.client.machine.findMany({
where: { typeMachineId },
select: { id: true, name: true },
});
}
}