feat: gérer l'ordre des champs personnalisés
This commit is contained in:
@@ -6,12 +6,15 @@ const CUSTOM_FIELD_SELECT = {
|
||||
type: true,
|
||||
required: true,
|
||||
options: true,
|
||||
orderIndex: true,
|
||||
} as const;
|
||||
|
||||
export const COMPONENT_WITH_RELATIONS_INCLUDE = {
|
||||
typeComposant: {
|
||||
include: {
|
||||
customFields: true,
|
||||
customFields: {
|
||||
orderBy: { orderIndex: 'asc' },
|
||||
},
|
||||
},
|
||||
},
|
||||
constructeurs: true,
|
||||
|
||||
@@ -4,4 +4,5 @@ export const CUSTOM_FIELD_SELECT = {
|
||||
type: true,
|
||||
required: true,
|
||||
options: true,
|
||||
orderIndex: true,
|
||||
} as const;
|
||||
|
||||
@@ -43,7 +43,10 @@ describe('ModelTypeMapper', () => {
|
||||
description: 'Desc',
|
||||
notes: 'Desc',
|
||||
});
|
||||
expect(input.customFields?.create?.[0]).toMatchObject({ name: 'Field' });
|
||||
expect(input.customFields?.create?.[0]).toMatchObject({
|
||||
name: 'Field',
|
||||
orderIndex: 0,
|
||||
});
|
||||
expect((input as any).componentSkeleton).toEqual({
|
||||
pieces: [
|
||||
{
|
||||
|
||||
@@ -12,12 +12,18 @@ import type {
|
||||
import { CUSTOM_FIELD_SELECT } from '../constants/custom-field.constant';
|
||||
|
||||
export const COMPONENT_TYPE_INCLUDE: Prisma.ModelTypeInclude = {
|
||||
customFields: { select: CUSTOM_FIELD_SELECT },
|
||||
customFields: {
|
||||
select: CUSTOM_FIELD_SELECT,
|
||||
orderBy: { orderIndex: 'asc' },
|
||||
},
|
||||
composants: true,
|
||||
};
|
||||
|
||||
export const PIECE_TYPE_INCLUDE: Prisma.ModelTypeInclude = {
|
||||
pieceCustomFields: { select: CUSTOM_FIELD_SELECT },
|
||||
pieceCustomFields: {
|
||||
select: CUSTOM_FIELD_SELECT,
|
||||
orderBy: { orderIndex: 'asc' },
|
||||
},
|
||||
pieceRequirements: true,
|
||||
pieces: true,
|
||||
};
|
||||
@@ -42,11 +48,12 @@ export class ModelTypeMapper {
|
||||
notes: description ?? null,
|
||||
customFields: customFields
|
||||
? {
|
||||
create: customFields.map((field) => ({
|
||||
create: customFields.map((field, index) => ({
|
||||
name: field.name,
|
||||
type: field.type,
|
||||
required: field.required ?? false,
|
||||
options: field.options,
|
||||
orderIndex: field.orderIndex ?? index,
|
||||
})),
|
||||
}
|
||||
: undefined,
|
||||
@@ -97,11 +104,12 @@ export class ModelTypeMapper {
|
||||
notes: description ?? null,
|
||||
pieceCustomFields: customFields
|
||||
? {
|
||||
create: customFields.map((field) => ({
|
||||
create: customFields.map((field, index) => ({
|
||||
name: field.name,
|
||||
type: field.type,
|
||||
required: field.required ?? false,
|
||||
options: field.options,
|
||||
orderIndex: field.orderIndex ?? index,
|
||||
})),
|
||||
}
|
||||
: undefined,
|
||||
@@ -165,11 +173,12 @@ export class ModelTypeMapper {
|
||||
return [];
|
||||
}
|
||||
|
||||
return fields.map((field) => ({
|
||||
return fields.map((field, index) => ({
|
||||
name: field.name,
|
||||
type: field.type,
|
||||
required: field.required ?? false,
|
||||
options: field.options,
|
||||
orderIndex: field.orderIndex ?? index,
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -180,11 +189,12 @@ export class ModelTypeMapper {
|
||||
return [];
|
||||
}
|
||||
|
||||
return fields.map((field) => ({
|
||||
return fields.map((field, index) => ({
|
||||
name: field.name,
|
||||
type: field.type,
|
||||
required: field.required ?? false,
|
||||
options: field.options,
|
||||
orderIndex: field.orderIndex ?? index,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,9 @@ describe('TypeMachineMapper', () => {
|
||||
const input = TypeMachineMapper.toCreateInput(baseDto as any);
|
||||
|
||||
expect(input.customFields?.create).toHaveLength(1);
|
||||
expect(input.customFields?.create?.[0]).toMatchObject({
|
||||
orderIndex: 0,
|
||||
});
|
||||
expect(input.componentRequirements?.create?.[0]).toMatchObject({
|
||||
label: 'Comp',
|
||||
minCount: 2,
|
||||
@@ -61,6 +64,7 @@ describe('TypeMachineMapper', () => {
|
||||
type: 'string',
|
||||
required: true,
|
||||
options: ['a'],
|
||||
orderIndex: 0,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -17,7 +17,10 @@ type RequirementDto = {
|
||||
};
|
||||
|
||||
export const TYPE_MACHINE_DEFAULT_INCLUDE: Prisma.TypeMachineInclude = {
|
||||
customFields: { select: CUSTOM_FIELD_SELECT },
|
||||
customFields: {
|
||||
select: CUSTOM_FIELD_SELECT,
|
||||
orderBy: { orderIndex: 'asc' },
|
||||
},
|
||||
componentRequirements: {
|
||||
include: { typeComposant: true },
|
||||
orderBy: { orderIndex: 'asc' },
|
||||
@@ -81,11 +84,12 @@ export class TypeMachineMapper {
|
||||
}
|
||||
|
||||
return {
|
||||
create: fields.map((field) => ({
|
||||
create: fields.map((field, index) => ({
|
||||
name: field.name,
|
||||
type: field.type,
|
||||
required: field.required ?? false,
|
||||
options: field.options,
|
||||
orderIndex: field.orderIndex ?? index,
|
||||
})),
|
||||
};
|
||||
}
|
||||
@@ -95,11 +99,12 @@ export class TypeMachineMapper {
|
||||
return [];
|
||||
}
|
||||
|
||||
return fields.map((field) => ({
|
||||
return fields.map((field, index) => ({
|
||||
name: field.name,
|
||||
type: field.type,
|
||||
required: field.required ?? false,
|
||||
options: field.options,
|
||||
orderIndex: field.orderIndex ?? index,
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ describe('ModelTypesRepository', () => {
|
||||
type: 'string',
|
||||
required: true,
|
||||
options: [],
|
||||
orderIndex: 0,
|
||||
typeComposantId: 'comp-id',
|
||||
},
|
||||
],
|
||||
@@ -63,6 +64,7 @@ describe('ModelTypesRepository', () => {
|
||||
type: 'string',
|
||||
required: false,
|
||||
options: [],
|
||||
orderIndex: 0,
|
||||
typePieceId: 'piece-id',
|
||||
},
|
||||
],
|
||||
|
||||
@@ -44,6 +44,7 @@ describe('TypeMachinesRepository', () => {
|
||||
type: 'string',
|
||||
required: true,
|
||||
options: [],
|
||||
orderIndex: 0,
|
||||
typeMachineId: 'machine-id',
|
||||
},
|
||||
],
|
||||
|
||||
@@ -209,12 +209,20 @@ export class CustomFieldsService {
|
||||
if (existingField) {
|
||||
targetCustomFieldId = existingField.id;
|
||||
} else {
|
||||
const normalizedType = (customFieldType || 'text').trim() || 'text';
|
||||
const normalizedRequired = !!customFieldRequired;
|
||||
const orderScope = { [customFieldTypeField]: typeId } as const;
|
||||
const nextOrderIndex = await this.prisma.customField.count({
|
||||
where: orderScope,
|
||||
});
|
||||
|
||||
const createdField = await this.prisma.customField.create({
|
||||
data: {
|
||||
name: normalizedName,
|
||||
type: (customFieldType || 'text').trim() || 'text',
|
||||
required: !!customFieldRequired,
|
||||
type: normalizedType,
|
||||
required: normalizedRequired,
|
||||
options: normalizedOptions,
|
||||
orderIndex: nextOrderIndex,
|
||||
[customFieldTypeField]: typeId,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -17,15 +17,21 @@ const CUSTOM_FIELD_SELECT = {
|
||||
type: true,
|
||||
required: true,
|
||||
options: true,
|
||||
orderIndex: true,
|
||||
} as const;
|
||||
|
||||
const TYPE_MACHINE_CONFIGURATION_INCLUDE: Prisma.TypeMachineInclude = {
|
||||
customFields: { select: CUSTOM_FIELD_SELECT },
|
||||
customFields: {
|
||||
select: CUSTOM_FIELD_SELECT,
|
||||
orderBy: { orderIndex: 'asc' },
|
||||
},
|
||||
componentRequirements: {
|
||||
include: {
|
||||
typeComposant: {
|
||||
include: {
|
||||
customFields: true,
|
||||
customFields: {
|
||||
orderBy: { orderIndex: 'asc' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -34,7 +40,9 @@ const TYPE_MACHINE_CONFIGURATION_INCLUDE: Prisma.TypeMachineInclude = {
|
||||
include: {
|
||||
typePiece: {
|
||||
include: {
|
||||
customFields: true,
|
||||
customFields: {
|
||||
orderBy: { orderIndex: 'asc' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -52,7 +60,9 @@ const MACHINE_PIECE_LINK_INCLUDE: Prisma.MachinePieceLinkInclude = {
|
||||
constructeurs: true,
|
||||
typePiece: {
|
||||
include: {
|
||||
customFields: true,
|
||||
customFields: {
|
||||
orderBy: { orderIndex: 'asc' },
|
||||
},
|
||||
},
|
||||
},
|
||||
documents: true,
|
||||
@@ -62,7 +72,9 @@ const MACHINE_PIECE_LINK_INCLUDE: Prisma.MachinePieceLinkInclude = {
|
||||
include: {
|
||||
typePiece: {
|
||||
include: {
|
||||
customFields: true,
|
||||
customFields: {
|
||||
orderBy: { orderIndex: 'asc' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -78,7 +90,9 @@ const buildComponentLinkInclude = (
|
||||
constructeurs: true,
|
||||
typeComposant: {
|
||||
include: {
|
||||
customFields: true,
|
||||
customFields: {
|
||||
orderBy: { orderIndex: 'asc' },
|
||||
},
|
||||
},
|
||||
},
|
||||
customFieldValues: {
|
||||
@@ -93,7 +107,9 @@ const buildComponentLinkInclude = (
|
||||
include: {
|
||||
typeComposant: {
|
||||
include: {
|
||||
customFields: true,
|
||||
customFields: {
|
||||
orderBy: { orderIndex: 'asc' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -1663,12 +1679,23 @@ export class MachinesService {
|
||||
let targetCustomFieldId = existingCustomFieldId;
|
||||
|
||||
if (!targetCustomFieldId) {
|
||||
const whereClause = typeMachineId
|
||||
? { typeMachineId }
|
||||
: { typeMachineId: null };
|
||||
const nextOrderIndex =
|
||||
typeof customField.orderIndex === 'number'
|
||||
? customField.orderIndex
|
||||
: await prisma.customField.count({
|
||||
where: whereClause,
|
||||
});
|
||||
|
||||
const createdCustomField = await prisma.customField.create({
|
||||
data: {
|
||||
name: customField.name,
|
||||
type: customField.type,
|
||||
required: customField.required || false,
|
||||
options: customField.options || [],
|
||||
orderIndex: nextOrderIndex,
|
||||
typeMachineId: typeMachineId ?? null,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -19,6 +19,7 @@ describe('PiecesService', () => {
|
||||
customField: {
|
||||
findMany: jest.fn(),
|
||||
create: jest.fn(),
|
||||
update: jest.fn(),
|
||||
},
|
||||
customFieldValue: {
|
||||
findMany: jest.fn(),
|
||||
|
||||
@@ -8,7 +8,9 @@ import type { PieceModelStructure } from '../shared/types/inventory';
|
||||
const PIECE_WITH_RELATIONS_INCLUDE = {
|
||||
typePiece: {
|
||||
include: {
|
||||
pieceCustomFields: true,
|
||||
pieceCustomFields: {
|
||||
orderBy: { orderIndex: 'asc' },
|
||||
},
|
||||
},
|
||||
},
|
||||
constructeurs: true,
|
||||
@@ -286,23 +288,35 @@ export class PiecesService {
|
||||
|
||||
const existing = await this.prisma.customField.findMany({
|
||||
where: { typePieceId },
|
||||
select: { id: true, name: true },
|
||||
select: { id: true, name: true, orderIndex: true },
|
||||
});
|
||||
|
||||
const existingByName = new Map(
|
||||
existing.map((field) => [
|
||||
this.normalizeIdentifier(field.name) ?? field.name,
|
||||
field.id,
|
||||
field,
|
||||
]),
|
||||
);
|
||||
|
||||
for (const field of customFields) {
|
||||
for (let index = 0; index < customFields.length; index += 1) {
|
||||
const field = customFields[index];
|
||||
if (!field) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const name = this.normalizeIdentifier(field.name);
|
||||
if (!name || existingByName.has(name)) {
|
||||
if (!name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const existingField = existingByName.get(name);
|
||||
if (existingField) {
|
||||
if (existingField.orderIndex !== index) {
|
||||
await this.prisma.customField.update({
|
||||
where: { id: existingField.id },
|
||||
data: { orderIndex: index },
|
||||
});
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -316,12 +330,13 @@ export class PiecesService {
|
||||
type,
|
||||
required,
|
||||
options,
|
||||
orderIndex: index,
|
||||
typePieceId,
|
||||
},
|
||||
select: { id: true },
|
||||
select: { id: true, name: true, orderIndex: true },
|
||||
});
|
||||
|
||||
existingByName.set(name, created.id);
|
||||
existingByName.set(name, created);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,10 @@ export class CreateCustomFieldDto {
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
options?: string[]; // Pour les champs de type SELECT
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
orderIndex?: number;
|
||||
}
|
||||
|
||||
export class UpdateCustomFieldDto {
|
||||
@@ -54,6 +58,10 @@ export class UpdateCustomFieldDto {
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
options?: string[];
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
orderIndex?: number;
|
||||
}
|
||||
|
||||
export class TypeMachineComponentRequirementDto {
|
||||
|
||||
Reference in New Issue
Block a user