feat: add product domain and machine integration
- extend Prisma schema with products, product constructs and link tables\n- introduce product service, DTOs and includes with constructeur support\n- integrate product selections across model type skeletons, composants, pièces and machines\n- validate product requirements when building machine skeletons and payloads
This commit is contained in:
@@ -9,8 +9,10 @@ async function deleteExistingData() {
|
||||
await prisma.machinePieceLink.deleteMany();
|
||||
await prisma.machine.deleteMany();
|
||||
await prisma.customFieldValue.deleteMany();
|
||||
await prisma.product.deleteMany();
|
||||
await prisma.composant.deleteMany();
|
||||
await prisma.piece.deleteMany();
|
||||
await prisma.typeMachineProductRequirement.deleteMany();
|
||||
|
||||
await prisma.modelType.deleteMany({
|
||||
where: {
|
||||
@@ -22,6 +24,7 @@ async function deleteExistingData() {
|
||||
'cooling-module',
|
||||
'structural-frame',
|
||||
'hydraulic-power-unit',
|
||||
'hydraulic-product',
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -239,6 +242,135 @@ async function createComponent(options: {
|
||||
});
|
||||
}
|
||||
|
||||
async function createProductType(
|
||||
name: string,
|
||||
code: string,
|
||||
description: string,
|
||||
fields: Array<{
|
||||
name: string;
|
||||
type: 'text' | 'number' | 'select' | 'boolean' | 'date';
|
||||
required?: boolean;
|
||||
options?: string[];
|
||||
}>,
|
||||
skeleton?: Record<string, unknown>,
|
||||
) {
|
||||
const type = await prisma.modelType.create({
|
||||
data: {
|
||||
name,
|
||||
code,
|
||||
category: 'PRODUCT',
|
||||
description,
|
||||
productSkeleton: skeleton
|
||||
? (skeleton as Prisma.InputJsonValue)
|
||||
: Prisma.JsonNull,
|
||||
productCustomFields: {
|
||||
create: fields.map((field, index) => ({
|
||||
name: field.name,
|
||||
type: field.type,
|
||||
required: field.required ?? false,
|
||||
options: field.options ?? [],
|
||||
orderIndex: index,
|
||||
})),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const customFields = await prisma.customField.findMany({
|
||||
where: { typeProductId: type.id },
|
||||
});
|
||||
|
||||
const fieldMap: CreatedFields = {};
|
||||
customFields.forEach((field) => {
|
||||
fieldMap[field.name] = field.id;
|
||||
});
|
||||
|
||||
return { type, fieldMap };
|
||||
}
|
||||
|
||||
async function createProduct(options: {
|
||||
name: string;
|
||||
reference?: string;
|
||||
supplierPrice?: number | null;
|
||||
constructeurIds?: string[] | null;
|
||||
typeId?: string | null;
|
||||
fieldValues?: Record<string, string>;
|
||||
}) {
|
||||
const fieldValues = options.fieldValues ?? {};
|
||||
|
||||
const customFields = options.typeId
|
||||
? await prisma.customField.findMany({
|
||||
where: { typeProductId: options.typeId },
|
||||
})
|
||||
: [];
|
||||
|
||||
const customFieldValues = Object.entries(fieldValues).flatMap(
|
||||
([fieldName, value]) => {
|
||||
if (typeof value !== 'string') {
|
||||
return [];
|
||||
}
|
||||
|
||||
const target = customFields.find((field) => field.name === fieldName);
|
||||
if (!target) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
value,
|
||||
customFieldId: target.id,
|
||||
},
|
||||
];
|
||||
},
|
||||
);
|
||||
|
||||
const constructeurIds = Array.isArray(options.constructeurIds)
|
||||
? Array.from(
|
||||
new Set(
|
||||
options.constructeurIds
|
||||
.filter((value): value is string => typeof value === 'string')
|
||||
.map((value) => value.trim())
|
||||
.filter((value) => value.length > 0),
|
||||
),
|
||||
)
|
||||
: [];
|
||||
|
||||
const data: Prisma.ProductCreateInput = {
|
||||
name: options.name,
|
||||
reference: options.reference ?? null,
|
||||
supplierPrice:
|
||||
options.supplierPrice === undefined || options.supplierPrice === null
|
||||
? null
|
||||
: new Prisma.Decimal(options.supplierPrice),
|
||||
};
|
||||
|
||||
if (options.typeId) {
|
||||
data.typeProduct = {
|
||||
connect: { id: options.typeId },
|
||||
};
|
||||
}
|
||||
|
||||
if (constructeurIds.length) {
|
||||
data.constructeurs = {
|
||||
connect: constructeurIds.map((id) => ({ id })),
|
||||
};
|
||||
}
|
||||
|
||||
if (customFieldValues.length) {
|
||||
data.customFieldValues = {
|
||||
create: customFieldValues.map((entry) => ({
|
||||
value: entry.value,
|
||||
customField: {
|
||||
connect: { id: entry.customFieldId },
|
||||
},
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
return prisma.product.create({
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('Nettoyage des données existantes…');
|
||||
await deleteExistingData();
|
||||
@@ -371,6 +503,63 @@ async function main() {
|
||||
},
|
||||
});
|
||||
|
||||
console.log('Création des types de produits…');
|
||||
const hydraulicProductFields: {
|
||||
name: string;
|
||||
type: 'text' | 'number' | 'select' | 'boolean' | 'date';
|
||||
required?: boolean;
|
||||
options?: string[];
|
||||
}[] = [
|
||||
{ name: 'Fournisseur', type: 'text', required: true },
|
||||
{ name: 'Garantie (mois)', type: 'number', required: true },
|
||||
{
|
||||
name: 'Délai d’approvisionnement (jours)',
|
||||
type: 'number',
|
||||
},
|
||||
];
|
||||
|
||||
const hydraulicProductType = await createProductType(
|
||||
'Produit hydraulique standard',
|
||||
'hydraulic-product',
|
||||
'Produits compatibles avec les centrales hydrauliques',
|
||||
hydraulicProductFields,
|
||||
);
|
||||
|
||||
console.log('Création des produits…');
|
||||
const pumpProduct = await createProduct({
|
||||
name: 'Pompe PX-300 Fournisseur A',
|
||||
reference: 'PRD-PX-300-A',
|
||||
supplierPrice: 1520,
|
||||
typeId: hydraulicProductType.type.id,
|
||||
fieldValues: {
|
||||
Fournisseur: 'HydrauParts',
|
||||
'Garantie (mois)': '24',
|
||||
'Délai d’approvisionnement (jours)': '21',
|
||||
},
|
||||
});
|
||||
|
||||
const coolingProduct = await createProduct({
|
||||
name: 'Module de refroidissement AC-50 - OEM',
|
||||
reference: 'PRD-AC-50',
|
||||
supplierPrice: 1980,
|
||||
typeId: hydraulicProductType.type.id,
|
||||
fieldValues: {
|
||||
Fournisseur: 'ThermoTech',
|
||||
'Garantie (mois)': '18',
|
||||
'Délai d’approvisionnement (jours)': '28',
|
||||
},
|
||||
});
|
||||
|
||||
console.log('Association des produits aux pièces…');
|
||||
await prisma.piece.update({
|
||||
where: { id: pumpPiece.id },
|
||||
data: {
|
||||
product: {
|
||||
connect: { id: pumpProduct.id },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
console.log('Création des types de composants…');
|
||||
const coolingComponentFields: {
|
||||
name: string;
|
||||
@@ -509,6 +698,15 @@ async function main() {
|
||||
} as Prisma.InputJsonValue,
|
||||
});
|
||||
|
||||
await prisma.composant.update({
|
||||
where: { id: coolingModule.id },
|
||||
data: {
|
||||
product: {
|
||||
connect: { id: coolingProduct.id },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const structuralFrame = await createComponent({
|
||||
name: 'Châssis structurel XC-800',
|
||||
reference: 'FRAME-XC800',
|
||||
|
||||
Reference in New Issue
Block a user