import { plainToInstance } from 'class-transformer'; import { validateSync } from 'class-validator'; import { ReportedProductDto, StockAdjustmentResultDto } from './dto/stock-sync.dto'; import { describe } from './tab-events'; export type Rejected = { id: string | null; reason: string }; /** * Validates entries one by one, like tab events: one bad product or result is skipped on its own * instead of failing the sync (and with it the open tabs and sales). */ function parseEach(cls: new () => T, raw: unknown[]): { accepted: T[]; rejected: Rejected[] } { const accepted: T[] = []; const rejected: Rejected[] = []; for (const value of raw) { const id = typeof (value as { id?: unknown })?.id === 'string' ? (value as { id: string }).id : null; if (typeof value !== 'object' || value === null || Array.isArray(value)) { rejected.push({ id, reason: 'must be an object' }); continue; } const entry = plainToInstance(cls, value); const errors = validateSync(entry, { whitelist: true }); if (errors.length > 0) { rejected.push({ id, reason: describe(errors) }); continue; } accepted.push(entry); } return { accepted, rejected }; } export function parseProducts(raw: unknown[]) { const { accepted, rejected } = parseEach(ReportedProductDto, raw); // A duplicated id would make the upserts fight; the last one reported wins const byId = new Map(accepted.map((product) => [product.id, product])); return { products: [...byId.values()].map((p) => ({ id: p.id, name: p.name.trim(), category: p.category?.trim() || null, priceInCents: p.priceInCents, stock: p.stock ?? null, })), rejected, }; } export type ParsedProduct = ReturnType['products'][number]; export function parseStockResults(raw: unknown[]) { return parseEach(StockAdjustmentResultDto, raw); }