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:
@@ -2,7 +2,9 @@ import { normalizeComponentModelStructure } from '../../component-models/structu
|
||||
import type {
|
||||
ComponentModelStructure,
|
||||
PieceModelCustomField,
|
||||
PieceModelProduct,
|
||||
PieceModelStructure,
|
||||
ProductModelStructure,
|
||||
} from '../types/inventory';
|
||||
|
||||
export class ComponentModelStructureValidationError extends Error {
|
||||
@@ -28,6 +30,67 @@ function sanitizeOptionalString(value: unknown): string | undefined {
|
||||
return String(value);
|
||||
}
|
||||
|
||||
function validateProducts(
|
||||
products: ComponentModelStructure['products'],
|
||||
): ComponentModelStructure['products'] {
|
||||
return products.map((product, index) => {
|
||||
if ('typeProductId' in product) {
|
||||
const typeProductId = assertString(
|
||||
product.typeProductId,
|
||||
`products[${index}].typeProductId`,
|
||||
).trim();
|
||||
if (!typeProductId) {
|
||||
throw new ComponentModelStructureValidationError(
|
||||
`products[${index}].typeProductId ne peut pas être vide`,
|
||||
);
|
||||
}
|
||||
const payload: ComponentModelStructure['products'][number] = {
|
||||
typeProductId,
|
||||
role: sanitizeOptionalString(product.role),
|
||||
};
|
||||
if ('familyCode' in product && product.familyCode) {
|
||||
const familyCode = assertString(
|
||||
product.familyCode,
|
||||
`products[${index}].familyCode`,
|
||||
).trim();
|
||||
if (familyCode) {
|
||||
(payload as Record<string, unknown>).familyCode = familyCode;
|
||||
}
|
||||
}
|
||||
if ('reference' in product && product.reference) {
|
||||
(payload as Record<string, unknown>).reference = sanitizeOptionalString(
|
||||
product.reference,
|
||||
);
|
||||
}
|
||||
if ('typeProductLabel' in product && product.typeProductLabel) {
|
||||
(payload as Record<string, unknown>).typeProductLabel =
|
||||
sanitizeOptionalString(product.typeProductLabel);
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
|
||||
if ('familyCode' in product) {
|
||||
const familyCode = assertString(
|
||||
product.familyCode,
|
||||
`products[${index}].familyCode`,
|
||||
).trim();
|
||||
if (!familyCode) {
|
||||
throw new ComponentModelStructureValidationError(
|
||||
`products[${index}].familyCode ne peut pas être vide`,
|
||||
);
|
||||
}
|
||||
return {
|
||||
familyCode,
|
||||
role: sanitizeOptionalString(product.role),
|
||||
};
|
||||
}
|
||||
|
||||
throw new ComponentModelStructureValidationError(
|
||||
`products[${index}] doit définir "familyCode" ou "typeProductId"`,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function validatePieces(
|
||||
pieces: ComponentModelStructure['pieces'],
|
||||
): ComponentModelStructure['pieces'] {
|
||||
@@ -148,6 +211,7 @@ export const ComponentModelStructureSchema = {
|
||||
const normalized = normalizeComponentModelStructure(input);
|
||||
|
||||
return {
|
||||
products: validateProducts(normalized.products),
|
||||
pieces: validatePieces(normalized.pieces),
|
||||
customFields: validateCustomFields(normalized.customFields),
|
||||
subcomponents: validateSubcomponents(normalized.subcomponents),
|
||||
@@ -230,10 +294,57 @@ function normalizePieceModelCustomFields(
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function normalizePieceModelProducts(products: unknown): PieceModelProduct[] {
|
||||
if (!Array.isArray(products)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return products.map((entry, index) => {
|
||||
if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
|
||||
throw new PieceModelStructureValidationError(
|
||||
`products[${index}] doit être un objet`,
|
||||
);
|
||||
}
|
||||
|
||||
const record = entry as Record<string, unknown>;
|
||||
|
||||
const rawTypeProductId =
|
||||
typeof record.typeProductId === 'string'
|
||||
? record.typeProductId
|
||||
: typeof (record.typeProduct as { id?: unknown })?.id === 'string'
|
||||
? (record.typeProduct as { id: string }).id
|
||||
: undefined;
|
||||
const typeProductId = rawTypeProductId ? rawTypeProductId.trim() : '';
|
||||
|
||||
const rawFamilyCode =
|
||||
typeof record.familyCode === 'string'
|
||||
? record.familyCode
|
||||
: typeof (record.typeProduct as { code?: unknown })?.code === 'string'
|
||||
? (record.typeProduct as { code: string }).code
|
||||
: undefined;
|
||||
const familyCode = rawFamilyCode ? rawFamilyCode.trim() : '';
|
||||
|
||||
const rawRole = typeof record.role === 'string' ? record.role.trim() : '';
|
||||
const role = rawRole ? rawRole : undefined;
|
||||
|
||||
if (typeProductId) {
|
||||
return role ? { typeProductId, role } : { typeProductId };
|
||||
}
|
||||
|
||||
if (familyCode) {
|
||||
return role ? { familyCode, role } : { familyCode };
|
||||
}
|
||||
|
||||
throw new PieceModelStructureValidationError(
|
||||
`products[${index}] doit définir "familyCode" ou "typeProductId"`,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export const PieceModelStructureSchema = {
|
||||
parse(input: unknown): PieceModelStructure {
|
||||
if (input === undefined || input === null) {
|
||||
return { customFields: [] };
|
||||
return { customFields: [], products: [] };
|
||||
}
|
||||
|
||||
if (typeof input !== 'object' || Array.isArray(input)) {
|
||||
@@ -250,6 +361,11 @@ export const PieceModelStructureSchema = {
|
||||
structure.customFields = customFields;
|
||||
}
|
||||
|
||||
const products = normalizePieceModelProducts(record.products);
|
||||
if (products.length > 0 || 'products' in record) {
|
||||
structure.products = products;
|
||||
}
|
||||
|
||||
const normalizedTypePiece = toStringOrNull(record.typePieceId);
|
||||
if (normalizedTypePiece) {
|
||||
structure.typePieceId = normalizedTypePiece;
|
||||
@@ -260,3 +376,34 @@ export const PieceModelStructureSchema = {
|
||||
return structure;
|
||||
},
|
||||
};
|
||||
|
||||
export class ProductModelStructureValidationError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = 'ProductModelStructureValidationError';
|
||||
}
|
||||
}
|
||||
|
||||
export const ProductModelStructureSchema = {
|
||||
parse(input: unknown): ProductModelStructure {
|
||||
if (input === undefined || input === null) {
|
||||
return { customFields: [] };
|
||||
}
|
||||
|
||||
if (typeof input !== 'object' || Array.isArray(input)) {
|
||||
throw new ProductModelStructureValidationError(
|
||||
'La structure de produit doit être un objet JSON.',
|
||||
);
|
||||
}
|
||||
|
||||
const record = input as Record<string, unknown>;
|
||||
const structure: ProductModelStructure = { ...record };
|
||||
const customFields = normalizePieceModelCustomFields(record.customFields);
|
||||
|
||||
if (customFields.length > 0 || 'customFields' in record) {
|
||||
structure.customFields = customFields;
|
||||
}
|
||||
|
||||
return structure;
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user