import { CalcDiscPrice, GetMarginValue } from "../../shared/math-operations"; import { IsNotNull, IsNull } from "../../shared/util"; export function GetItemPriceForPriceList( item: any, priceList: any, includeMargin: boolean = false, quantity: number = 1, pricingContext: any = {} ): any { if (!priceList) { return item; } const resolvedQty = Math.max(1, Number(quantity) || 1); const calcDisc: any = priceList.CalcDisc ?? {}; const markUp: boolean = calcDisc.Mark ?? false; const priceListId = priceList?._id?.toString?.(); let salePricesByPriceListId: any[] = []; if (priceListId && IsNotNull(item.SalePrices) && item.SalePrices.length > 0) { salePricesByPriceListId = item.SalePrices.filter( (price: any) => price?.PrListId && price.PrListId.toString() === priceListId ); } item.PriceTiers = priceList?.QtyTiers ?? undefined; item.AppliedPriceTier = undefined; if (salePricesByPriceListId.length > 0) { item.SalePrices = salePricesByPriceListId; const activeSalePrice = salePricesByPriceListId[0]; const activeTierList = activeSalePrice?.QtyTiers ?? priceList?.QtyTiers; if (activeTierList) { item.PriceTiers = activeTierList; } if (item.Track === "TR" || item.Track === "SN") { item.Sale["MRP"] = item.Sale.Price; const tier = ResolveTier(resolvedQty, activeSalePrice?.QtyTiers); if (tier) { const baseForTier = IsNotNull(activeSalePrice.Price) ? activeSalePrice.Price : item.Sale.Price; item.Sale.Price = ApplyTierAdjustment(baseForTier, tier, priceList, false); item.AppliedPriceTier = BuildAppliedTierMeta("ITEM", tier); } else if (IsNotNull(activeSalePrice.Price) && IsNotNull(activeSalePrice.Disc)) { item.Sale.Price = CalcItemFinalSalePrice(activeSalePrice.Price, activeSalePrice.Disc, priceList, markUp); } else if (IsNotNull(activeSalePrice.Price) && IsNull(activeSalePrice.Disc)) { item.Sale.Price = ApplyRounding(activeSalePrice.Price, priceList); } else if (IsNull(activeSalePrice.Price) && IsNotNull(activeSalePrice.Disc)) { item.Sale.Price = CalcItemFinalSalePrice(item.Sale.Price, activeSalePrice.Disc, priceList, markUp); } if (includeMargin && IsNotNull(item.Pur) && IsNotNull(item.Pur.Cost)) { item.Margin = GetMarginValue(item.Sale.Price, item.Pur.Cost); } } else if (item.Track === "BN" && Array.isArray(item.Batches)) { for (const element of item.Batches) { const batchSalePrice = item.SalePrices.filter( (batch: any) => priceListId === batch?.PrListId?.toString() && (element.BN === batch.BN || batch.BN == null) ); const activeBatchSale = batchSalePrice.length > 0 ? batchSalePrice[0] : undefined; const tierList = activeBatchSale?.QtyTiers ?? activeSalePrice?.QtyTiers ?? priceList?.QtyTiers; if (tierList) { element.PriceTiers = tierList; } element.MRP = element.Price; const tier = ResolveTier(resolvedQty, activeBatchSale?.QtyTiers ?? activeSalePrice?.QtyTiers); if (tier) { const baseForTier = IsNotNull(activeBatchSale?.Price) ? activeBatchSale.Price : element.Price; element.Price = ApplyTierAdjustment(baseForTier, tier, priceList, false); element.AppliedPriceTier = BuildAppliedTierMeta("ITEM", tier); } else if (activeBatchSale) { if (IsNotNull(activeBatchSale.Price) && IsNotNull(activeBatchSale.Disc)) { element.Price = CalcItemFinalSalePrice(activeBatchSale.Price, activeBatchSale.Disc, priceList, markUp); } else if (IsNotNull(activeBatchSale.Price) && IsNull(activeBatchSale.Disc)) { element.Price = ApplyRounding(activeBatchSale.Price, priceList); } else if (IsNull(activeBatchSale.Price) && IsNotNull(activeBatchSale.Disc)) { element.Price = CalcItemFinalSalePrice(element.Price, activeBatchSale.Disc, priceList, markUp); } } if (includeMargin && IsNotNull(element.Cost)) { element.Margin = GetMarginValue(element.Price, element.Cost); } } } return item; } return CalculatePriceBasedOnPriceList(item, priceList, includeMargin, resolvedQty); } /** * Calculates the final sale price of an item after applying a discount and optional markup. * Optionally includes margin calculation based on cost. * * @param price - The original price of the item. * @param disc - The discount percentage to apply. * @param priceList - The price list object containing rounding and other configurations. * @param markup - Whether to apply markup instead of discount (default is false). * @param includeMargin - Whether to include margin calculation in the result (default is false). * @param cost - The cost of the item, used for margin calculation (default is 0). * @returns The final price or an object containing margin and final price if includeMargin is true. */ export function CalcItemFinalSalePrice( price: number, disc: number, priceList: any, markup: boolean = false, includeMargin: boolean = false, cost: number = 0 ): any { const calculatedPrice = CalcDiscPrice(price, disc, markup); const finalPrice = ApplyRounding(calculatedPrice, priceList); if (includeMargin) { const margin = GetMarginValue(finalPrice, cost); return { margin: margin, finalPrice: finalPrice }; } return finalPrice; } function CalculatePriceBasedOnPriceList( item: any, priceList: any, includeMargin: boolean, quantity: number ): any { if (!priceList) { return item; } const calcDisc: any = priceList.CalcDisc ?? {}; const markUp: boolean = calcDisc.Mark ?? false; const resolvedQty = Math.max(1, Number(quantity) || 1); const tier = ResolveTier(resolvedQty, priceList.QtyTiers); if (tier) { item.PriceTiers = priceList.QtyTiers ?? undefined; if (item.Track === "TR" || item.Track === "SN") { item.Sale["MRP"] = item.Sale.Price; const basePrice = DetermineBasePriceForTier(item, priceList); item.Sale.Price = ApplyTierAdjustment(basePrice, tier, priceList, markUp); item.AppliedPriceTier = BuildAppliedTierMeta("PRICELIST", tier); if (includeMargin && IsNotNull(item.Pur) && IsNotNull(item.Pur.Cost)) { item.Margin = GetMarginValue(item.Sale.Price, item.Pur.Cost); } } else if (item.Track === "BN" && Array.isArray(item.Batches)) { for (const batch of item.Batches) { batch.MRP = batch.Price; const basePrice = DetermineBatchBasePriceForTier(batch, item, priceList); batch.Price = ApplyTierAdjustment(basePrice, tier, priceList, markUp); batch.AppliedPriceTier = BuildAppliedTierMeta("PRICELIST", tier); if (includeMargin && IsNotNull(batch.Cost)) { batch.Margin = GetMarginValue(batch.Price, batch.Cost); } } } return item; } if (priceList.PrCalc === "FIXED") { if (priceList.Fixed && IsNotNull(priceList.Fixed.Price)) { const fixedPrice = priceList.Fixed.Price; const fixedDisc = priceList.Fixed.Disc || 0; if (item.Track === "TR" || item.Track === "SN") { item.Sale["MRP"] = item.Sale.Price; if (IsNotNull(fixedDisc) && fixedDisc > 0) { item.Sale.Price = CalcItemFinalSalePrice(fixedPrice, fixedDisc, priceList); } else { item.Sale.Price = ApplyRounding(fixedPrice, priceList); } if (includeMargin && IsNotNull(item.Pur) && IsNotNull(item.Pur.Cost)) { item.Margin = GetMarginValue(item.Sale.Price, item.Pur.Cost); } } else if (item.Track === "BN" && Array.isArray(item.Batches)) { for (const batch of item.Batches) { batch.MRP = batch.Price; if (IsNotNull(fixedDisc) && fixedDisc > 0) { batch.Price = CalcItemFinalSalePrice(fixedPrice, fixedDisc, priceList); } else { batch.Price = ApplyRounding(fixedPrice, priceList); } if (includeMargin && IsNotNull(batch.Cost)) { batch.Margin = GetMarginValue(batch.Price, batch.Cost); } } } } } else if (priceList.PrCalc === "DISC") { const discPerc: number = calcDisc.Perc || 0; if (priceList.BasedOn === "SP") { if (item.Track === "TR" || item.Track === "SN") { item.Sale["MRP"] = item.Sale.Price; item.Sale.Price = CalcItemFinalSalePrice(item.Sale.Price, discPerc, priceList, markUp); if (includeMargin && IsNotNull(item.Pur) && IsNotNull(item.Pur.Cost)) { item.Margin = GetMarginValue(item.Sale.Price, item.Pur.Cost); } } else if (item.Track === "BN" && Array.isArray(item.Batches)) { for (const batch of item.Batches) { batch.MRP = batch.Price; batch.Price = CalcItemFinalSalePrice(batch.Price, discPerc, priceList, markUp); if (includeMargin && IsNotNull(batch.Cost)) { batch.Margin = GetMarginValue(batch.Price, batch.Cost); } } } } else if (priceList.BasedOn === "PC") { if (item.Track === "TR" || item.Track === "SN") { item.Sale["MRP"] = item.Sale.Price; if (IsNotNull(item.Pur) && IsNotNull(item.Pur.Cost)) { item.Sale.Price = CalcItemFinalSalePrice(item.Pur.Cost, discPerc, priceList, markUp); if (includeMargin) { item.Margin = GetMarginValue(item.Sale.Price, item.Pur.Cost); } } } else if (item.Track === "BN" && Array.isArray(item.Batches)) { for (const batch of item.Batches) { batch.MRP = batch.Price; if (IsNotNull(batch.Cost)) { batch.Price = CalcItemFinalSalePrice(batch.Cost, discPerc, priceList, markUp); if (includeMargin) { batch.Margin = GetMarginValue(batch.Price, batch.Cost); } } } } } } return item; } function ApplyRounding(price: number, priceList: any): number { if (!priceList || !priceList.Round) return price; switch (priceList.Round) { case "-1": return price; case "0": return Math.round(price); case "0.99": return Math.floor(price) + 0.99; case "0.50": return Math.round(price * 2) / 2; case "0.49": return Math.floor(price) + 0.49; case "Dec": if (IsNotNull(priceList.Deci)) { const factor = Math.pow(10, priceList.Deci); return Math.round(price * factor) / factor; } return price; default: return price; } } function ResolveTier(quantity: number, tiers: any[] | undefined): any | null { if (!Array.isArray(tiers) || tiers.length === 0) { return null; } const sorted = [...tiers] .filter((tier: any) => IsNotNull(tier) && IsNotNull(tier.MinQty)) .sort((a: any, b: any) => a.MinQty - b.MinQty); let selected: any | null = null; for (const tier of sorted) { if (quantity >= tier.MinQty) { selected = tier; } else { break; } } return selected; } function ApplyTierAdjustment( basePrice: number, tier: any, priceList: any, markUp: boolean ): number { if (IsNotNull(tier?.Price)) { return ApplyRounding(tier.Price, priceList); } if (IsNotNull(tier?.Disc)) { const targetBase = IsNotNull(basePrice) ? basePrice : 0; return CalcItemFinalSalePrice(targetBase, tier.Disc, priceList, markUp); } return ApplyRounding(basePrice, priceList); } function BuildAppliedTierMeta(source: "ITEM" | "PRICELIST", tier: any) { if (!tier) { return undefined; } return { Source: source, MinQty: IsNotNull(tier.MinQty) ? tier.MinQty : null, Price: IsNotNull(tier.Price) ? tier.Price : null, Disc: IsNotNull(tier.Disc) ? tier.Disc : null, }; } function DetermineBasePriceForTier(item: any, priceList: any): number { if (priceList.PrCalc === "FIXED") { if (IsNotNull(priceList.Fixed?.Price)) { return priceList.Fixed.Price; } return item.Sale.Price; } if (priceList.PrCalc === "DISC") { if (priceList.BasedOn === "PC" && IsNotNull(item.Pur) && IsNotNull(item.Pur.Cost)) { return item.Pur.Cost; } return item.Sale.Price; } return item.Sale.Price; } function DetermineBatchBasePriceForTier(batch: any, item: any, priceList: any): number { if (priceList.PrCalc === "FIXED") { if (IsNotNull(priceList.Fixed?.Price)) { return priceList.Fixed.Price; } return batch.Price; } if (priceList.PrCalc === "DISC") { if (priceList.BasedOn === "PC" && IsNotNull(batch.Cost)) { return batch.Cost; } return batch.Price; } return batch.Price; }