private static PriceResult FindPriceAgreement(
                IDictionary <string, IList <TradeAgreement> > tradeAgreementRules,
                DiscountParameters priceParameters,
                RetailPriceArgs args,
                IEnumerable <SalesLine> salesLines,
                PriceContext priceContext,
                DateTimeOffset activeDate)
            {
                // First we get the price according to the base UOM
                PriceAgreementArgs p      = args.AgreementArgsForDefaultSales();
                PriceResult        result = ApplyPriceTradeAgreements(tradeAgreementRules, p, priceParameters, salesLines, priceContext, activeDate);

                // Is the current UOM something different than the base UOM?
                if (args.SalesUOM != args.DefaultSalesUnitOfMeasure)
                {
                    // Then let's see if we find some price agreement for that UOM
                    p = args.ArgreementArgsForSale();
                    PriceResult salesUOMResult = ApplyPriceTradeAgreements(tradeAgreementRules, p, priceParameters, salesLines, priceContext, activeDate);

                    // If there is a price found then we return that as the price
                    if (salesUOMResult.Price > decimal.Zero)
                    {
                        return(salesUOMResult);
                    }
                    else
                    {
                        return(new PriceResult(result.Price * args.UnitOfMeasureConversion.GetFactorForQuantity(args.Quantity), result.IncludesTax, custPriceGroup: result.CustPriceGroup));
                    }
                }

                // else we return baseUOM price mulitplied with the unit qty factor.
                return(result);
            }
            /// <summary>
            /// This function takes arguments (customer, item, currency, etc.) related to price (trade) agreement
            /// as well as the set of currently enabled trade agreement types. It returns the best trade agreement
            /// price for the given constraints.
            /// As in AX, the method searches for a price on the given item which has been given to a
            /// customer, price group, or anyone (in given precedence order). If a price is found and marked as
            /// SearchAgain=False, the search will terminate. Otherwise, search for lowest price will continue.
            /// To recap, the logic is that three searches are done for customer, price group, and all, each bracket
            /// will return the lowest price it has for the constraints. If it has SearchAgain=True, then the search
            /// for lowest price continues to the next bracket.
            /// </summary>
            /// <param name="tradeAgreementRules">Trade agreements applicable to each item, keyed by item relation (i.e. item Id).</param>
            /// <param name="args">Arguments for price agreement search.</param>
            /// <param name="priceParameters">Set of enabled price agreement types.</param>
            /// <param name="salesLines">Sales lines.</param>
            /// <param name="priceContext">Price context.</param>
            /// <param name="activeDate">Date to use for querying trade agreement rules.</param>
            /// <returns>
            /// Most applicable price for the given price agreement constraints.
            /// </returns>
            private static PriceResult ApplyPriceTradeAgreements(
                IDictionary <string, IList <TradeAgreement> > tradeAgreementRules,
                PriceAgreementArgs args,
                DiscountParameters priceParameters,
                IEnumerable <SalesLine> salesLines,
                PriceContext priceContext,
                DateTimeOffset activeDate)
            {
                PriceResult priceResult = new PriceResult(0M, PriceGroupIncludesTax.NotSpecified);

                var itemCodes    = new PriceDiscountItemCode[] { PriceDiscountItemCode.Item, PriceDiscountItemCode.ItemGroup, PriceDiscountItemCode.AllItems };
                var accountCodes = new PriceDiscountAccountCode[] { PriceDiscountAccountCode.Customer, PriceDiscountAccountCode.CustomerGroup, PriceDiscountAccountCode.AllCustomers };

                // Search through combinations of item/account codes from most to least specific.
                // This needs to match the behavior of AX code PriceDisc.findPriceAgreement().
                foreach (var accountCode in accountCodes)
                {
                    foreach (var itemCode in itemCodes)
                    {
                        if (priceParameters.Activation(PriceDiscountType.PriceSales, accountCode, itemCode))
                        {
                            IList <string> accountRelations = args.GetAccountRelations(accountCode);
                            string         itemRelation     = args.GetItemRelation(itemCode);

                            if (accountRelations.All(a => ValidRelation(accountCode, a)) &&
                                ValidRelation(itemCode, itemRelation))
                            {
                                bool searchAgain;
                                IEnumerable <TradeAgreement> tradeAgreements = FindPriceAgreements(tradeAgreementRules, args, itemCode, accountCode, salesLines, priceContext, activeDate);
                                PriceResult currentPriceResult = GetBestPriceAgreement(tradeAgreements, out searchAgain);

                                if (priceResult.Price == 0M ||
                                    (currentPriceResult.Price > 0M && currentPriceResult.Price < priceResult.Price))
                                {
                                    priceResult = currentPriceResult;
                                }

                                if (!searchAgain)
                                {
                                    break;
                                }
                            }
                        }
                    }
                }

                return(priceResult);
            }
            private static IEnumerable <TradeAgreement> FindPriceAgreements(
                IDictionary <string, IList <TradeAgreement> > tradeAgreementRules,
                PriceAgreementArgs args,
                PriceDiscountItemCode itemCode,
                PriceDiscountAccountCode accountCode,
                IEnumerable <SalesLine> salesLines,
                PriceContext priceContext,
                DateTimeOffset activeDate)
            {
                string         itemRelation     = args.GetItemRelation(itemCode);
                IList <string> accountRelations = args.GetAccountRelations(accountCode);
                string         unitId           = args.GetUnitId(itemCode);

                // price trade agreements are always item-specific, so first filter by itemId.
                IList <TradeAgreement> rulesForItem;

                if (!tradeAgreementRules.TryGetValue(itemRelation, out rulesForItem))
                {
                    return(new List <TradeAgreement>(0));
                }

                List <TradeAgreement> tradeAgreementsOfVariantUnFilteredByQuantity = new List <TradeAgreement>();
                List <TradeAgreement> tradeAgreementsOfMasterOrProduct             = new List <TradeAgreement>();

                // Get the initial filtered trade agreements, not checking quantity.
                for (int i = 0; i < rulesForItem.Count; i++)
                {
                    var t = rulesForItem[i];
                    if (t.ItemRelation.Equals(itemRelation, StringComparison.OrdinalIgnoreCase) &&
                        t.ItemCode == itemCode &&
                        t.AccountCode == accountCode &&
                        accountRelations.Contains(t.AccountRelation) &&
                        t.Currency.Equals(args.CurrencyCode, StringComparison.OrdinalIgnoreCase) &&
                        (t.FromDate.DateTime <= activeDate.Date || t.FromDate.DateTime <= NoDate) &&
                        (t.ToDate.DateTime >= activeDate.Date || t.ToDate.DateTime <= NoDate) &&
                        (string.IsNullOrWhiteSpace(unitId) ||
                         t.UnitOfMeasureSymbol.Equals(unitId, StringComparison.OrdinalIgnoreCase)) &&
                        t.IsVariantMatch(args.Dimensions))
                    {
                        if (t.IsVariant)
                        {
                            tradeAgreementsOfVariantUnFilteredByQuantity.Add(t);
                        }
                        else
                        {
                            if (t.IsQuantityMatch(args.Quantity))
                            {
                                tradeAgreementsOfMasterOrProduct.Add(t);
                            }
                        }
                    }
                }

                // For variants
                if (args.IsVariant)
                {
                    List <TradeAgreement> tradeAgreementsOfVariant           = new List <TradeAgreement>();
                    List <TradeAgreement> tradeAgreementsOfVariantExactMatch = new List <TradeAgreement>();

                    foreach (TradeAgreement t in tradeAgreementsOfVariantUnFilteredByQuantity)
                    {
                        if (t.IsVariant)
                        {
                            decimal aggregatedQuantityByAgreementVariant = decimal.Zero;
                            foreach (SalesLine salesLine in salesLines)
                            {
                                if (string.Equals(args.ItemId, salesLine.ItemId, StringComparison.OrdinalIgnoreCase) && t.IsVariantMatch(salesLine.Variant))
                                {
                                    aggregatedQuantityByAgreementVariant += salesLine.Quantity;
                                }
                            }

                            if (aggregatedQuantityByAgreementVariant == decimal.Zero)
                            {
                                aggregatedQuantityByAgreementVariant = 1m;
                            }

                            if (t.IsQuantityMatch(aggregatedQuantityByAgreementVariant))
                            {
                                if (t.IsVariantExactMatch(args.Dimensions))
                                {
                                    tradeAgreementsOfVariantExactMatch.Add(t);
                                }

                                tradeAgreementsOfVariant.Add(t);
                            }
                        }
                    }

                    // 1. Return exact matches if any
                    if (tradeAgreementsOfVariantExactMatch != null && tradeAgreementsOfVariantExactMatch.Any())
                    {
                        if (accountCode == PriceDiscountAccountCode.CustomerGroup)
                        {
                            RetainTopPriorityTradeAgreements(tradeAgreementsOfVariantExactMatch, priceContext);
                        }

                        tradeAgreementsOfVariantExactMatch.Sort(AgreementSortMethod);
                        return(tradeAgreementsOfVariantExactMatch);
                    }

                    // 2. Return (partial) variant matches if any.
                    if (tradeAgreementsOfVariant.Count > 0)
                    {
                        if (accountCode == PriceDiscountAccountCode.CustomerGroup)
                        {
                            RetainTopPriorityTradeAgreements(tradeAgreementsOfVariant, priceContext);
                        }

                        TradeAgreementComparer tradeAgreementComparator = new TradeAgreementComparer(tradeAgreementsOfVariant, args.Dimensions);
                        tradeAgreementComparator.SortTradeAgreement();
                        return(tradeAgreementsOfVariant);
                    }
                }

                // 3. Return non-variant matches.
                if (accountCode == PriceDiscountAccountCode.CustomerGroup)
                {
                    RetainTopPriorityTradeAgreements(tradeAgreementsOfMasterOrProduct, priceContext);
                }

                tradeAgreementsOfMasterOrProduct.Sort(AgreementSortMethod);

                return(tradeAgreementsOfMasterOrProduct);
            }