type SalesEditableLine = { itemId: number uomId: number warehouseId: number qty: number freeQty: number unitPrice?: number | null discountPct?: number taxPct?: number } type SalesDocumentValidationInput = { customerId: number | null warehouseId: number | null cashierUserId?: number | null requireCashierUser?: boolean lines: SalesEditableLine[] lineLabel?: string } type BundleEditableLine = { itemId: number uomId: number qty: number unitPrice: number } type BundleValidationInput = { customerId: number | null warehouseId: number | null cashierUserId: number | null templateId: number | null bundleName: string bundlePrice: number lines: BundleEditableLine[] } function invalidNumber(value: number | null | undefined) { return value === null || value === undefined || !Number.isFinite(value) } export function validateSalesDocument(input: SalesDocumentValidationInput): string | null { const { customerId, warehouseId, cashierUserId, requireCashierUser = false, lines, lineLabel = "line", } = input if (!customerId) return "Select a customer." if (!warehouseId) return "Select a warehouse." if (requireCashierUser && !cashierUserId) return "Select a cashier." if (lines.length === 0) return `Add at least one ${lineLabel}.` for (const [index, line] of lines.entries()) { const row = index + 1 if (!line.itemId) return `Select an item for ${lineLabel} ${row}.` if (!line.uomId) return `Select a valid UOM for ${lineLabel} ${row}.` if (!line.warehouseId) return `Select a warehouse for ${lineLabel} ${row}.` if (warehouseId && line.warehouseId !== warehouseId) { return `${lineLabel[0]?.toUpperCase() ?? "L"}${lineLabel.slice(1)} ${row} warehouse must match the selected header warehouse.` } if (invalidNumber(line.qty) || line.qty <= 0) return `Enter a quantity greater than zero for ${lineLabel} ${row}.` if (invalidNumber(line.freeQty) || line.freeQty < 0) return `Enter a valid free quantity for ${lineLabel} ${row}.` if (line.unitPrice !== null && line.unitPrice !== undefined && (invalidNumber(line.unitPrice) || line.unitPrice < 0)) { return `Enter a valid unit price for ${lineLabel} ${row}.` } if (line.discountPct !== undefined && (invalidNumber(line.discountPct) || line.discountPct < 0 || line.discountPct > 100)) { return `Enter a discount percentage between 0 and 100 for ${lineLabel} ${row}.` } if (line.taxPct !== undefined && (invalidNumber(line.taxPct) || line.taxPct < 0 || line.taxPct > 100)) { return `Enter a tax percentage between 0 and 100 for ${lineLabel} ${row}.` } } return null } export function validateBundleSale(input: BundleValidationInput): string | null { const { customerId, warehouseId, cashierUserId, templateId, bundleName, bundlePrice, lines } = input if (!customerId) return "Select a customer." if (!warehouseId) return "Select a warehouse." if (!cashierUserId) return "Select a cashier." if (!templateId) return "Select a bundle template." if (!bundleName.trim()) return "Enter a bundle name." if (invalidNumber(bundlePrice) || bundlePrice < 0) return "Enter a valid bundle price." if (lines.length === 0) return "Add at least one bundle component line." for (const [index, line] of lines.entries()) { const row = index + 1 if (!line.itemId) return `Select an item for component line ${row}.` if (!line.uomId) return `Select a valid UOM for component line ${row}.` if (invalidNumber(line.qty) || line.qty <= 0) return `Enter a quantity greater than zero for component line ${row}.` if (invalidNumber(line.unitPrice) || line.unitPrice < 0) return `Enter a valid unit price for component line ${row}.` } return null }