/// <summary> /// Convert unit of measure for item availabilities. /// </summary> /// <param name="context">The request context.</param> /// <param name="itemAvailableQuantities">The item availabilities.</param> /// <param name="itemUnits">The desired item unit of measures.</param> /// <returns>The converted item available quantities.</returns> internal static IEnumerable <ItemAvailableQuantity> ConvertUnitOfMeasure(RequestContext context, IEnumerable <ItemAvailableQuantity> itemAvailableQuantities, IEnumerable <ItemUnit> itemUnits) { List <ItemAvailableQuantity> convertedItemAvailableQuantities = new List <ItemAvailableQuantity>(); IEnumerable <ItemUnit> distinctItemUnits = itemUnits.Distinct(); IEnumerable <ItemUnitQuantity> itemUnitQuantities = itemAvailableQuantities.Select(itemAvailableQuantity => itemAvailableQuantity.GetItemUnitQuantity()); Dictionary <ItemUnitConversion, UnitOfMeasureConversion> conversions = GetUnitOfMeasureConversions(context, itemUnitQuantities, distinctItemUnits); ILookup <ItemVariantInventoryDimension, ItemUnit> itemUnitLookupByItem = distinctItemUnits.ToLookup(itemUnit => itemUnit.GetItem()); foreach (ItemAvailableQuantity itemAvailableQuantity in itemAvailableQuantities) { bool hasInventoryUnit = false; foreach (ItemUnit itemUnit in itemUnitLookupByItem[itemAvailableQuantity.GetItem()]) { ItemUnitConversion itemUnitConversion = ChannelAvailabilityHelper.GetItemUnitConversion(itemAvailableQuantity.GetItemUnitQuantity(), itemUnit); UnitOfMeasureConversion unitOfMeasureConversion; if (!conversions.TryGetValue(itemUnitConversion, out unitOfMeasureConversion)) { if (!StringDataHelper.Equals(itemAvailableQuantity.UnitOfMeasure, itemUnit.UnitOfMeasure)) { context.Notify(new UnableToConvertUnitOfMeasureNotification(itemUnitConversion)); } if (!hasInventoryUnit) { convertedItemAvailableQuantities.Add(itemAvailableQuantity); hasInventoryUnit = true; } } else { ItemAvailableQuantity convertedItemAvailableQuantity = new ItemAvailableQuantity(); convertedItemAvailableQuantity.CopyPropertiesFrom(itemAvailableQuantity); convertedItemAvailableQuantity.AvailableQuantity = unitOfMeasureConversion.Convert(itemAvailableQuantity.AvailableQuantity); convertedItemAvailableQuantity.UnitOfMeasure = itemUnitConversion.ToUnitOfMeasure; convertedItemAvailableQuantities.Add(convertedItemAvailableQuantity); } } } return(convertedItemAvailableQuantities.AsEnumerable()); }
private static Dictionary <string, decimal> GetSalesLineInventoryQuantities(RequestContext context, IEnumerable <SalesLine> salesLines, Dictionary <ItemUnitConversion, UnitOfMeasureConversion> unitOfMeasureConversions) { // Convert quantities from sales to inventory unit of measure and calculate total converted quantities. Dictionary <string, decimal> salesLineInventoryQuantities = new Dictionary <string, decimal>(); foreach (SalesLine salesLine in salesLines) { decimal salesLineInventoryQuantity = 0; UnitOfMeasureConversion unitOfMeasureConversion; ItemUnitConversion itemUnitConversion = salesLine.GetItemUnitConversion(); if (string.IsNullOrWhiteSpace(salesLine.SalesOrderUnitOfMeasure)) { var notification = new EmptySalesUnitOfMeasureNotification(salesLine.LineId); context.Notify(notification); } else if (string.IsNullOrWhiteSpace(salesLine.InventOrderUnitOfMeasure)) { var notification = new EmptyInventoryUnitOfMeasureNotification(salesLine.ItemId); context.Notify(notification); } else if (string.Equals(salesLine.SalesOrderUnitOfMeasure, salesLine.InventOrderUnitOfMeasure, StringComparison.OrdinalIgnoreCase)) { salesLineInventoryQuantity = salesLine.Quantity; } else if (unitOfMeasureConversions.TryGetValue(itemUnitConversion, out unitOfMeasureConversion)) { salesLineInventoryQuantity = unitOfMeasureConversion.Convert(salesLine.Quantity); } else { var notification = new UnableToConvertUnitOfMeasureNotification(itemUnitConversion); context.Notify(notification); } salesLineInventoryQuantities[salesLine.LineId] = salesLineInventoryQuantity; } return(salesLineInventoryQuantities); }
/// <summary> /// Convert unit of measure for item reservations. /// </summary> /// <param name="context">The request context.</param> /// <param name="itemReservations">The item reservations.</param> /// <param name="itemUnitConversions">The item unit conversions.</param> /// <exception cref="DataValidationException">Thrown if there is no conversion rules defined for any of item reservations.</exception> internal static void ConvertUnitOfMeasure(RequestContext context, IEnumerable <ItemReservation> itemReservations, IEnumerable <ItemUnitConversion> itemUnitConversions) { Collection <DataValidationFailure> validationFailures = new Collection <DataValidationFailure>(); Dictionary <ItemUnitConversion, UnitOfMeasureConversion> conversions = GetUnitOfMeasureConversions(context, itemUnitConversions); IEnumerator <ItemReservation> itemReservationEnumerator = itemReservations.GetEnumerator(); IEnumerator <ItemUnitConversion> itemUnitConversionEnumerator = itemUnitConversions.GetEnumerator(); while (itemReservationEnumerator.MoveNext() && itemUnitConversionEnumerator.MoveNext()) { ItemUnitConversion itemUnitConversion = itemUnitConversionEnumerator.Current; if (!itemUnitConversion.IsNop) { UnitOfMeasureConversion unitOfMeasureConversion; if (conversions.TryGetValue(itemUnitConversion, out unitOfMeasureConversion)) { ItemReservation itemReservation = itemReservationEnumerator.Current; itemReservation.Quantity = unitOfMeasureConversion.Convert(itemReservation.Quantity); } else { DataValidationFailure validationFailure = new DataValidationFailure(DataValidationErrors.Microsoft_Dynamics_Commerce_Runtime_UnitOfMeasureConversionNotFound); validationFailure.ErrorContext = string.Format(CultureInfo.InvariantCulture, "No conversion rules defined for ItemUnitConversion:{0}", itemUnitConversion); validationFailures.Add(validationFailure); } } } if (validationFailures.Count > 0) { throw new DataValidationException( DataValidationErrors.Microsoft_Dynamics_Commerce_Runtime_AggregateValidationError, validationFailures, string.Format("There are {0} item reservations whose unit of measures cannot be converted.", validationFailures.Count)); } }
/// <summary> /// Calculate the tax bases calculationBase and limitBase (which is zero for India). /// </summary> /// <param name="codes">The tax codes.</param> /// <param name="taxInStoreCurrency">If set to <c>true</c> [tax in store currency].</param> /// <param name="calculateBasePrice">If set to <c>true</c> [Calculate the base price].</param> /// <returns>The calculation base as Item1 and The limit base as Item2 in Tuple.</returns> protected override Tuple <decimal, decimal> GetBases(ReadOnlyCollection <TaxCode> codes, bool taxInStoreCurrency, bool calculateBasePrice) { const decimal LimitBase = decimal.Zero; decimal calculationBase; // For amount by unit calculation base is just the quantity. if (this.TaxBase == TaxBase.AmountByUnit) { calculationBase = this.TaxableEntity.Quantity; // If the tax is calculated in a different UOM, then convert if possible // this is only applicable for lineItem taxes. SalesLine salesLine = this.TaxableEntity as SalesLine; if (salesLine != null && !string.Equals(this.Unit, this.TaxableEntity.SalesOrderUnitOfMeasure, StringComparison.OrdinalIgnoreCase)) { ItemUnitConversion conversion = new ItemUnitConversion { FromUnitOfMeasure = this.TaxableEntity.SalesOrderUnitOfMeasure, ToUnitOfMeasure = this.Unit, ItemId = this.TaxableEntity.ItemId }; var conversions = new List <ItemUnitConversion>(); conversions.Add(conversion); var getUomConvertionDataRequest = new GetUnitOfMeasureConversionDataRequest(conversions, QueryResultSettings.SingleRecord); UnitOfMeasureConversion converter = this.RequestContext.Runtime .Execute <GetUnitOfMeasureConversionDataResponse>(getUomConvertionDataRequest, this.RequestContext).UnitConversions.SingleOrDefault(); calculationBase *= converter.GetFactorForQuantity(this.TaxableEntity.Quantity); } return(new Tuple <decimal, decimal>(calculationBase, LimitBase)); } // Determine the starting calculation base (includes the line price or not) switch (this.Formula.TaxableBasis) { case TaxableBasisIndia.LineAmount: calculationBase = this.TaxableEntity.NetAmountWithAllInclusiveTaxPerUnit; break; case TaxableBasisIndia.MaxRetailPrice: calculationBase = this.GetItemMaxRetailPrice(); break; default: calculationBase = decimal.Zero; break; } if (this.TaxIncludedInPrice) { calculationBase = GetBasePriceForTaxIncluded(calculationBase, codes, this.Formula, this.RequestContext); } calculationBase *= Math.Abs(this.TaxableEntity.Quantity); // Calculation expression is of the form: +[BCD]+[CVD]+[E-CESS_CVD]+[PE-C_CVD]+[SHE-C_CVD] // where the brackets are replaced with the delimiter char(164) // and BCD, CVD ... are tax codes. // The operator may be + - / *. string[] tokens = this.Formula.ParseExpression(); for (int index = 1; index < tokens.Length; index += 2) { TaxLine taxLine = (from line in this.TaxableEntity.TaxLines where line.TaxCode == tokens[index] select line).FirstOrDefault(); if (taxLine != null) { this.IsTaxOnTax = true; if (!this.taxCodesInFormula.Contains(taxLine.TaxCode)) { this.taxCodesInFormula.Add(taxLine.TaxCode); } } decimal amount = taxLine == null ? decimal.Zero : taxLine.Amount * Math.Sign(this.TaxableEntity.Quantity); int tokenNumber = index - 1; switch (tokens[tokenNumber]) { case "+": calculationBase += amount; break; case "-": calculationBase -= amount; break; case "*": calculationBase *= amount; break; case "/": calculationBase = amount == decimal.Zero ? calculationBase : calculationBase /= amount; break; default: RetailLogger.Log.CrtServicesTaxCodeIndiaTaxServiceInvalidOperatorFoundInBaseCalculation(tokens[tokenNumber]); break; } } // Knock any abatement off of the taxable basis calculationBase *= (100 - this.AbatementPercent) / 100; return(new Tuple <decimal, decimal>(calculationBase, LimitBase)); }
/// <summary> /// Gets the bases for tax calculations. /// </summary> /// <param name="codes">The collection of tax codes.</param> /// <param name="taxInStoreCurrency">If set to <c>true</c>, tax amount is in the store's currency.</param> /// <param name="calculateBasePrice">If set to <c>true</c>, calculate the base price.</param> /// <returns>The calculation base and The limit base in <see cref="Tuple">Tuple</see>.</returns> protected virtual Tuple <decimal, decimal> GetBases( ReadOnlyCollection <TaxCode> codes, bool taxInStoreCurrency, bool calculateBasePrice) { decimal calculationBase; decimal limitBase; decimal basePrice = decimal.Zero; if (calculateBasePrice) { basePrice = this.TaxIncludedInPrice ? TaxCodeProvider.GetBasePriceForTaxIncluded(this.TaxableEntity, codes, this.TaxContext) : this.TaxableEntity.NetAmountPerUnit + GetApplicableDutyTaxes(this.TaxableEntity, codes); } // 1. Get initial value for the Calculation Base switch (this.TaxBase) { case TaxBase.PercentPerTax: // Base is the amount of the other tax switch (this.TaxLimitBase) { case TaxLimitBase.InvoiceWithoutVat: case TaxLimitBase.InvoiceWithVat: calculationBase = Math.Abs(this.CalculateTaxOnTax(this.Transaction)); break; case TaxLimitBase.UnitWithoutVat: case TaxLimitBase.UnitWithVat: // if this tax's Limit is per-unit, then we need to convert the existing tax amounts from per-line to per-unit decimal quantity = (this.TaxableEntity.Quantity == decimal.Zero) ? decimal.One : this.TaxableEntity.Quantity; calculationBase = Math.Abs(this.CalculateTaxOnTax()) / Math.Abs(quantity); break; default: calculationBase = Math.Abs(this.CalculateTaxOnTax()); break; } break; case TaxBase.PercentPerGross: // Base is the price + other taxes calculationBase = basePrice; // If the Limit base is NOT per-unit, then we need to factor in the line quanity if (TaxLimitBase != TaxLimitBase.UnitWithoutVat && TaxLimitBase != TaxLimitBase.UnitWithVat) { calculationBase *= Math.Abs(this.TaxableEntity.Quantity); } if (!string.IsNullOrEmpty(this.TaxOnTax)) { // Base is the Price + the amount of a single other tax calculationBase += Math.Abs(this.CalculateTaxOnTax()); } else { // Base is the Price + all other taxes calculationBase += Math.Abs(TaxCode.SumAllTaxAmounts(this.TaxableEntity)); } break; case TaxBase.AmountByUnit: calculationBase = this.AmountPerUnitCalculationBase; break; case TaxBase.PercentPerNet: case TaxBase.PercentGrossOnNet: default: // Base is the Price calculationBase = basePrice; // If the Limit base is NOT per-unit, then we need to factor in the line quanity if (TaxLimitBase != TaxLimitBase.UnitWithoutVat && TaxLimitBase != TaxLimitBase.UnitWithVat) { calculationBase *= Math.Abs(this.TaxableEntity.Quantity); } break; } // 3. Set Limit Base if (this.TaxBase == TaxBase.AmountByUnit) { // Base for limits/intervals is base-quantity * price limitBase = calculationBase * basePrice; // Convert limit base to Tax currency, if different if (!taxInStoreCurrency) { limitBase = this.TaxContext.TaxCurrencyOperations.ConvertCurrency(this.TaxContext.ChannelCurrency, this.Currency, limitBase); } // If the tax is calculated in a different UOM, then convert if possible // this is only applicable for lineItem taxes. SalesLine salesLine = this.TaxableEntity as SalesLine; if (salesLine != null && !string.Equals(this.Unit, this.TaxableEntity.SalesOrderUnitOfMeasure, StringComparison.OrdinalIgnoreCase)) { ItemUnitConversion conversion = new ItemUnitConversion { FromUnitOfMeasure = this.TaxableEntity.SalesOrderUnitOfMeasure, ToUnitOfMeasure = this.Unit, ItemId = this.TaxableEntity.ItemId }; var conversions = new List <ItemUnitConversion>(); conversions.Add(conversion); var getUomConvertionDataRequest = new GetUnitOfMeasureConversionDataRequest(conversions, QueryResultSettings.SingleRecord); UnitOfMeasureConversion converter = this.RequestContext.Runtime .Execute <GetUnitOfMeasureConversionDataResponse>(getUomConvertionDataRequest, this.RequestContext).UnitConversions.SingleOrDefault(); calculationBase *= converter.GetFactorForQuantity(this.TaxableEntity.Quantity); } } else { // Convert base to Tax currency, if different if (!taxInStoreCurrency) { calculationBase = this.TaxContext.TaxCurrencyOperations.ConvertCurrency(this.TaxContext.ChannelCurrency, this.Currency, calculationBase); } // Base for limits/intervals is same for Calculations limitBase = calculationBase; } return(new Tuple <decimal, decimal>(calculationBase, limitBase)); }