diff --git a/Frontend/erp-system/lib/format-uom.ts b/Frontend/erp-system/lib/format-uom.ts new file mode 100644 index 0000000..fbcea27 --- /dev/null +++ b/Frontend/erp-system/lib/format-uom.ts @@ -0,0 +1,41 @@ +const UOM_LABELS: Record = { + BAG: "Bag", + BOX: "Box", + BTL: "Bottle", + CAN: "Can", + CM: "Centimeter", + CTN: "Carton", + DOZ: "Dozen", + EA: "Each", + G: "Gram", + KG: "Kilogram", + L: "Litre", + M: "Meter", + ML: "Millilitre", + MM: "Millimeter", + PACK: "Pack", + PCS: "Pieces", + PK: "Pack", + PKT: "Packet", + ROLL: "Roll", + SET: "Set", +} + +export function formatUomName(name: string | null | undefined): string { + const normalized = name?.trim() + if (!normalized) return "" + + const mapped = UOM_LABELS[normalized.toUpperCase()] + if (mapped) return mapped + + if (/^[A-Z0-9/_-]+$/.test(normalized)) { + return normalized.charAt(0) + normalized.slice(1).toLowerCase() + } + + return normalized +} + +export function findUomLabel(uoms: Array<{ uomId: number; name: string }>, uomId: number): string { + const name = uoms.find((uom) => uom.uomId === uomId)?.name + return name ? formatUomName(name) : `#${uomId}` +} diff --git a/Frontend/erp-system/lib/sales-validation.ts b/Frontend/erp-system/lib/sales-validation.ts new file mode 100644 index 0000000..c5b2eda --- /dev/null +++ b/Frontend/erp-system/lib/sales-validation.ts @@ -0,0 +1,101 @@ +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 +}