/// <summary> /// Round tax code amounts at the Tax Group level /// </summary> /// <param name="lineItem"></param> /// <param name="taxGroup"></param> private static void RoundTaxGroup(BaseSaleItem lineItem, string taxGroup) { if (lineItem.TaxLines.Count > 0) { decimal roundedAmount = decimal.Zero; decimal roundedSum = decimal.Zero; decimal rawSum = decimal.Zero; decimal diff = decimal.Zero; IRounding rounding = TaxService.Tax.InternalApplication.Services.Rounding; string storeCurrency = ApplicationSettings.Terminal.StoreCurrency; // Sum up raw and rounded tax amounts foreach (ITaxItem taxLine in lineItem.TaxLines.Where(t => (!t.Exempt) && string.Equals(taxGroup, t.TaxGroup))) { // Accumulate rounded and unrounded/raw amounts. Tax.Amount is the raw value. roundedAmount = rounding.Round(taxLine.Amount, storeCurrency, true, 1); rawSum += taxLine.Amount; roundedSum += roundedAmount; // Set Tax.Amount to the rounded amount taxLine.Amount = roundedAmount; // Compute the difference between the sums. // If we have accumulated enough extra decimals to cause raw to round up, then we'll see a difference. diff = rounding.Round(rawSum, storeCurrency, true, 1) - roundedSum; // Apply the difference against the current line if (diff != decimal.Zero) { taxLine.Amount += diff; roundedSum += diff; } } } }
/// <summary> /// Get 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 in store currency].</param> /// <param name="calculateBasePrice">if set to <c>true</c> [Calculate the base price].</param> /// <param name="calculationBase">The calculation base.</param> /// <param name="limitBase">The limit base.</param> protected virtual void GetBases(ReadOnlyCollection <TaxCode> codes, bool taxInStoreCurrency, bool calculateBasePrice, out decimal calculationBase, out decimal limitBase) { decimal basePrice = decimal.Zero; if (calculateBasePrice) { basePrice = this.TaxIncludedInPrice ? Provider.GetBasePriceForTaxIncluded(this.LineItem, codes) : LineItem.NetAmountPerUnit; } //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.LineItem.RetailTransaction)); 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.LineItem.Quantity == decimal.Zero) ? decimal.One : this.LineItem.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.LineItem.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.LineItem)); } break; case TaxBase.AmountByUnit: calculationBase = 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.LineItem.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 = TaxService.Tax.InternalApplication.Services.Currency.CurrencyToCurrency( ApplicationSettings.Terminal.StoreCurrency, this.Currency, limitBase); } // If the tax is calculated in a different UOM, then convert if possible // this is only applicable for lineItem taxes. BaseSaleItem saleLineItem = this.LineItem as BaseSaleItem; if (saleLineItem != null && !string.Equals(this.Unit, this.LineItem.SalesOrderUnitOfMeasure, StringComparison.OrdinalIgnoreCase)) { UnitOfMeasureData uomData = new UnitOfMeasureData( ApplicationSettings.Database.LocalConnection, ApplicationSettings.Database.DATAAREAID, ApplicationSettings.Terminal.StorePrimaryId, TaxService.Tax.InternalApplication); UnitQtyConversion uomConversion = uomData.GetUOMFactor(this.LineItem.SalesOrderUnitOfMeasure, this.Unit, saleLineItem); calculationBase *= uomConversion.GetFactorForQty(this.LineItem.Quantity); } } else { // Convert base to Tax currency, if different if (!taxInStoreCurrency) { calculationBase = TaxService.Tax.InternalApplication.Services.Currency.CurrencyToCurrency( ApplicationSettings.Terminal.StoreCurrency, this.Currency, calculationBase); } // Base for limits/intervals is same for Calculations limitBase = calculationBase; } }
/// <summary> /// Given a set of promotion lines, tentative item price, and item, calculate the price after promotions are applied /// </summary> /// <param name="promotionLines">List of promotion configurations active for this item</param> /// <param name="price">Price of the item before the promotion, derived from trade agreement or base item price</param> /// <param name="saleItem">The sale item whose price is being determined</param> /// <returns>Unrounded price after applying all promotions</returns> public static decimal CalculatePromotionPrice(IEnumerable <PromotionInfo> promotionLines, decimal price, BaseSaleItem saleItem) { if (saleItem == null) { NetTracer.Warning("saleItem parameter is null"); throw new ArgumentNullException("saleItem"); } if (promotionLines == null || promotionLines.Count() == 0) { return(price); } decimal promoPrice = price; IList <PromoPrice> promoPrices = new List <PromoPrice>(); foreach (PromotionInfo promo in promotionLines) { PromoPrice promoLine = new PromoPrice(); promoLine.PromoId = promo.PromoId; promoLine.Concurrency = promo.Concurrency; promoLine.IsCompoundable = false; switch (promo.DiscountMethod) { case DiscountMethod.DiscountPercent: promoLine.PercentOff = promo.MaxDiscPct; promoLine.IsCompoundable = true; promoPrices.Add(promoLine); break; case DiscountMethod.OfferPrice: if (!saleItem.Transaction.TaxIncludedInPrice) { promoLine.AmountOff = price - promo.Price; promoPrices.Add(promoLine); } break; case DiscountMethod.OfferPriceInclTax: if (saleItem.Transaction.TaxIncludedInPrice) { promoLine.AmountOff = price - promo.PriceInclTax; promoPrices.Add(promoLine); } break; case DiscountMethod.DiscountAmount: promoLine.AmountOff = promo.MaxDiscAmount; promoLine.IsCompoundable = true; promoPrices.Add(promoLine); break; } } promoPrice = price - FindConcurrentPromoAmount(promoPrices, price); return(promoPrice); }
/// <summary> /// Calculate the tax bases calculationBase and limitBase (which is zero for India). /// </summary> /// <param name="basePrice">The base price.</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> /// <param name="calculationBase">The calculation base.</param> /// <param name="limitBase">The limit base.</param> protected override void GetBases(ReadOnlyCollection <TaxCode> codes, bool taxInStoreCurrency, bool calculateBasePrice, out decimal calculationBase, out decimal limitBase) { limitBase = decimal.Zero; // For amount by unit calculation base is just the quantity. if (this.TaxBase == TaxBase.AmountByUnit) { calculationBase = LineItem.Quantity; // If the tax is calculated in a different UOM, then convert if possible // this is only applicable for lineItem taxes. BaseSaleItem saleLineItem = this.LineItem as BaseSaleItem; if (saleLineItem != null && !string.Equals(this.Unit, this.LineItem.SalesOrderUnitOfMeasure, StringComparison.OrdinalIgnoreCase)) { UnitOfMeasureData uomData = new UnitOfMeasureData( ApplicationSettings.Database.LocalConnection, ApplicationSettings.Database.DATAAREAID, ApplicationSettings.Terminal.StorePrimaryId, TaxService.Tax.InternalApplication); UnitQtyConversion uomConversion = uomData.GetUOMFactor(this.LineItem.SalesOrderUnitOfMeasure, this.Unit, saleLineItem); calculationBase *= uomConversion.GetFactorForQty(this.LineItem.Quantity); } return; } // Determine the starting calculation base (includes the line price or not) switch (Formula.TaxableBasis) { case TaxableBases.LineAmount: calculationBase = this.LineItem.NetAmountWithAllInclusiveTaxPerUnit; break; case TaxableBases.MRP: calculationBase = GetMRP(); break; default: calculationBase = decimal.Zero; break; } if (this.TaxIncludedInPrice) { calculationBase = GetBasePriceForTaxIncluded(calculationBase, codes, Formula); } calculationBase *= Math.Abs(this.LineItem.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 = Formula.ParseExpression(); for (int index = 1; index < tokens.Length; index += 2) { ITaxItem taxItem = (from line in this.LineItem.TaxLines where line.TaxCode == tokens[index] select line).FirstOrDefault(); if (taxItem != null) { this.IsTaxOnTax = true; this.TaxCodesInFormula.Add(taxItem.TaxCode); } decimal amount = taxItem == null ? decimal.Zero : taxItem.Amount * Math.Sign(this.LineItem.Quantity); switch (tokens[index - 1]) { case "+": calculationBase += amount; break; case "-": calculationBase -= amount; break; case "*": calculationBase *= amount; break; case "/": calculationBase = (amount == decimal.Zero ? calculationBase : calculationBase /= amount); break; default: NetTracer.Error("GetBases(): Invalid operator in formula. tokens[{0}]: {1}", index - 1, tokens[index - 1]); System.Diagnostics.Debug.Fail("Invalid operator in formula"); break; } } // Knock any abatement off of the taxable basis calculationBase *= (100 - AbatementPercent) / 100; }