private Dictionary <string, decimal> GetPriceGroupSalesQuantityLookup(SalesTransaction transaction) { // Sum up all the linegroup discount lines in the same group // Consider calculable lines only. Ignore voided or return-by-receipt lines. Dictionary <string, decimal> priceGroupSalesQuantityLookup = new Dictionary <string, decimal>(StringComparer.OrdinalIgnoreCase); foreach (SalesLine salesLine in transaction.PriceCalculableSalesLines) { Item item = PriceContextHelper.GetItem(this.priceContext, salesLine.ItemId); if (item != null && !string.IsNullOrEmpty(item.MultilineDiscountGroupId)) { decimal quantity = decimal.Zero; if (priceGroupSalesQuantityLookup.TryGetValue(item.MultilineDiscountGroupId, out quantity)) { priceGroupSalesQuantityLookup[item.MultilineDiscountGroupId] = quantity + salesLine.Quantity; } else { priceGroupSalesQuantityLookup.Add(item.MultilineDiscountGroupId, salesLine.Quantity); } } } return(priceGroupSalesQuantityLookup); }
/// <summary> /// Check whether the discount is allowed for the item. /// </summary> /// <param name="priceContext">Price context.</param> /// <param name="itemId">Item identifier.</param> /// <returns>True if the discount is allowed for the item, otherwise false.</returns> public static bool IsDiscountAllowed(PriceContext priceContext, string itemId) { Item item = PriceContextHelper.GetItem(priceContext, itemId); bool isDiscountAllowed = item != null ? !item.NoDiscountAllowed : true; return(isDiscountAllowed); }
private bool IsTotalDiscountAllowed(string itemId) { Item item = PriceContextHelper.GetItem(this.priceContext, itemId); bool isTotalDiscountAllowed = item != null && item.IsTotalDiscountAllowed && !item.NoDiscountAllowed; return(isTotalDiscountAllowed); }
/// <summary> /// The calculation of a customer multiline discount. /// </summary> /// <remarks> /// Calculation of multiline discount is done as follows: /// 1. Create working table for calculation. /// 2. Populate working table with total quantities for all the multiline groups encountered on the sales lines. /// 3. For all rows (and therefore multiline groups) found, search for trade agreements in the database. /// a. The search is first for customer-specific, then customer multiline discount group, then all customers. /// b. The search stops when a trade agreement is encountered with "Find next" unmarked. /// c. If nothing is found for the store currency the search is attempted again with the company accounting currency. /// 4. All found agreements are summed in the working table and applied to each sales line which matches the multiline groups. /// 5. If there are sales lines which weren't discounted with a multiline discount. /// a. Find their total quantity and search for any multiline trade agreements marked for "All items". /// b. If any agreements were found apply them to any lines that weren't already discounted with a multiline discount. /// </remarks> /// <param name="tradeAgreements">Trade agreement collection to calculate on. If null, uses the pricing data manager to find agreements.</param> /// <param name="transaction">The sales transaction which needs multiline discounts attached.</param> /// <returns> /// The sales transaction. /// </returns> public SalesTransaction CalcMultiLineDiscount(List <TradeAgreement> tradeAgreements, SalesTransaction transaction) { if (tradeAgreements != null && tradeAgreements.Any()) { // collection of salesLine not discounted by multiline discount group // Consider calculable lines only. Ignore voided or return-by-receipt lines. var nondiscountedSalesLines = new List <SalesLine>(transaction.PriceCalculableSalesLines); Dictionary <string, decimal> priceGroupSalesQuantityLookup = this.GetPriceGroupSalesQuantityLookup(transaction); decimal percent1 = decimal.Zero; decimal percent2 = decimal.Zero; decimal discountAmount = decimal.Zero; // Find discounts for the different multiline discount groups foreach (KeyValuePair <string, decimal> priceGroupQuantityPair in priceGroupSalesQuantityLookup) { // we've found some multiline discount groups, so clear non-discounted lines from the default of "all" nondiscountedSalesLines.Clear(); // find multiline discounts for this multiline discount row this.GetMultiLineDiscountLine(tradeAgreements, PriceDiscountItemCode.ItemGroup, transaction, priceGroupQuantityPair.Key, priceGroupQuantityPair.Value, out percent1, out percent2, out discountAmount); // Update the sale items. // Consider calculable lines only. Ignore voided or return-by-receipt lines. foreach (var saleItem in transaction.PriceCalculableSalesLines) { Item item = PriceContextHelper.GetItem(this.priceContext, saleItem.ItemId); string discountGroupId = item != null ? item.MultilineDiscountGroupId : string.Empty; if (string.Equals(discountGroupId, priceGroupQuantityPair.Key, StringComparison.OrdinalIgnoreCase)) { // if line is part of discounted item group, apply the discount ApplyMultilineDiscount(saleItem, percent1, percent2, discountAmount); } else { // otherwise, add to non-discounted lines nondiscountedSalesLines.Add(saleItem); } } } // find total quantity of items on lines still eligible for multiline discount decimal lineSum = nondiscountedSalesLines.Aggregate(0M, (acc, sl) => acc + sl.Quantity); // find any multiline discounts to apply to "all items" this.GetMultiLineDiscountLine(tradeAgreements, PriceDiscountItemCode.AllItems, transaction, string.Empty, lineSum, out percent1, out percent2, out discountAmount); // Update the sale items. foreach (var saleItem in nondiscountedSalesLines) { ApplyMultilineDiscount(saleItem, percent1, percent2, discountAmount); } } return(transaction); }
/// <summary> /// Calculate the manual line discount. /// </summary> /// <param name="transaction">The transaction receiving total discount lines.</param> /// <param name="saleItem">The sale item that contains the discount lines.</param> /// <param name="lineDiscountItem">The line discount amount to discount the transaction.</param> private void AddLineDiscount(SalesTransaction transaction, SalesLine saleItem, DiscountLine lineDiscountItem) { Item item = PriceContextHelper.GetItem(this.priceContext, saleItem.ItemId); bool isDiscountAllowed = item != null ? !item.NoDiscountAllowed : true; if (isDiscountAllowed) { saleItem.DiscountLines.Add(lineDiscountItem); SalesLineTotaller.CalculateLine(transaction, saleItem, d => this.priceContext.CurrencyAndRoundingHelper.Round(d)); } }
/// <summary> /// For all sales lines on the transaction, retrieve the product rec id if it's not already set. /// </summary> /// <param name="pricingDataManager">Provides data access to the calculation.</param> /// <param name="priceContext">Price context.</param> /// <param name="salesLines">Sales lines.</param> private static void PopulateProductIds(IPricingDataAccessor pricingDataManager, PriceContext priceContext, IEnumerable <SalesLine> salesLines) { var itemVariantIds = new HashSet <ItemVariantInventoryDimension>(); foreach (var line in salesLines) { if ((line.Variant == null || line.Variant.DistinctProductVariantId == 0) && !string.IsNullOrWhiteSpace(line.InventoryDimensionId)) { var itemVariantId = new ItemVariantInventoryDimension(line.ItemId, line.InventoryDimensionId); itemVariantIds.Add(itemVariantId); } } // We make a single database call to retrieve all variant identifiers that we need // and create a map using the ItemVariantInventoryDimension as its key. var variantsMap = new Dictionary <ItemVariantInventoryDimension, ProductVariant>(); if (itemVariantIds.Any()) { variantsMap = ((IEnumerable <ProductVariant>)pricingDataManager.GetVariants(itemVariantIds)).ToDictionary(key => new ItemVariantInventoryDimension(key.ItemId, key.InventoryDimensionId)); } // Consider calculable lines only. Ignore voided or return-by-receipt lines. foreach (var line in salesLines) { if (line.MasterProductId == 0) { Item item = PriceContextHelper.GetItem(priceContext, line.ItemId); line.MasterProductId = (item != null) ? item.Product : 0L; if (item != null && string.IsNullOrWhiteSpace(line.OriginalSalesOrderUnitOfMeasure)) { line.OriginalSalesOrderUnitOfMeasure = item.SalesUnitOfMeasure; } } if ((line.Variant == null || line.Variant.DistinctProductVariantId == 0) && !string.IsNullOrWhiteSpace(line.InventoryDimensionId)) { ProductVariant variant; var itemVariant = new ItemVariantInventoryDimension(line.ItemId, line.InventoryDimensionId); if (variantsMap.TryGetValue(itemVariant, out variant)) { line.Variant = variant; } } if (line.ProductId == 0) { line.ProductId = line.Variant != null ? line.Variant.DistinctProductVariantId : line.MasterProductId; } } }
private void GetLineDiscountLines( List <TradeAgreement> tradeAgreements, SalesLine saleItem, ref decimal absQty, ref decimal discountAmount, ref decimal percent1, ref decimal percent2, ref decimal minQty) { int idx = 0; while (idx < 9) { PriceDiscountItemCode itemCode = (PriceDiscountItemCode)(idx % 3); // Mod divsion PriceDiscountAccountCode accountCode = (PriceDiscountAccountCode)(idx / 3); string accountRelation = string.Empty; if (accountCode == PriceDiscountAccountCode.Customer) { accountRelation = this.priceContext.CustomerAccount; } else if (accountCode == PriceDiscountAccountCode.CustomerGroup) { accountRelation = this.priceContext.CustomerLinePriceGroup; } accountRelation = accountRelation ?? string.Empty; string itemRelation; if (itemCode == PriceDiscountItemCode.Item) { itemRelation = saleItem.ItemId; } else { Item item = PriceContextHelper.GetItem(this.priceContext, saleItem.ItemId); itemRelation = item != null ? item.LineDiscountGroupId : string.Empty; } itemRelation = itemRelation ?? string.Empty; PriceDiscountType relation = PriceDiscountType.LineDiscountSales; // Sales line discount - 5 if (this.discountParameters.Activation(relation, accountCode, itemCode)) { if (DiscountParameters.ValidRelation(accountCode, accountRelation) && DiscountParameters.ValidRelation(itemCode, itemRelation)) { bool dimensionDiscountFound = false; if (saleItem.Variant != null && !string.IsNullOrEmpty(saleItem.Variant.VariantId)) { var dimensionPriceDiscTable = Discount.GetPriceDiscData(tradeAgreements, relation, itemRelation, accountRelation, itemCode, accountCode, absQty, this.priceContext, saleItem.Variant, true); foreach (TradeAgreement row in dimensionPriceDiscTable) { bool unitsAreUndefinedOrEqual = string.IsNullOrEmpty(row.UnitOfMeasureSymbol) || string.Equals(row.UnitOfMeasureSymbol, saleItem.SalesOrderUnitOfMeasure, StringComparison.OrdinalIgnoreCase); if (unitsAreUndefinedOrEqual) { percent1 += row.PercentOne; percent2 += row.PercentTwo; discountAmount += row.Amount; minQty += row.QuantityAmountFrom; } if (percent1 > 0M || percent2 > 0M || discountAmount > 0M) { dimensionDiscountFound = true; } if (!row.ShouldSearchAgain) { idx = 9; } } } if (!dimensionDiscountFound) { var priceDiscTable = Discount.GetPriceDiscData(tradeAgreements, relation, itemRelation, accountRelation, itemCode, accountCode, absQty, this.priceContext, saleItem.Variant, false); foreach (TradeAgreement row in priceDiscTable) { // Apply default if the unit of measure is not set from the cart. string unitOfMeasure = Discount.GetUnitOfMeasure(saleItem); bool unitsAreUndefinedOrEqual = string.IsNullOrEmpty(row.UnitOfMeasureSymbol) || string.Equals(row.UnitOfMeasureSymbol, unitOfMeasure, StringComparison.OrdinalIgnoreCase); if (unitsAreUndefinedOrEqual) { percent1 += row.PercentOne; percent2 += row.PercentTwo; discountAmount += row.Amount; minQty += row.QuantityAmountFrom; } if (!row.ShouldSearchAgain) { idx = 9; } } } } } idx++; } }