/// <summary> /// Gets the tax code. /// </summary> /// <param name="reader">The reader.</param> /// <param name="taxableItem">The taxable item.</param> /// <returns>The taxcode object</returns> protected override TaxCode GetTaxCode(SqlDataReader reader, ITaxableItem taxableItem) { if (reader == null) { throw new ArgumentNullException("reader"); } if ((TaxTypes)reader["TAXTYPE_IN"] == TaxTypes.None) { return(base.GetTaxCode(reader, taxableItem)); } else { return(new TaxCodeIndia( reader["TAXCODE"] as string, taxableItem, reader["TAXITEMGROUP"] as string, reader["TAXCURRENCYCODE"] as string, (decimal)reader["TAXVALUE"], (decimal)reader["TAXLIMITMIN"], (decimal)reader["TAXLIMITMAX"], ((int)reader["EXEMPTTAX"] == 1), (TaxBase)reader["TAXBASE"], (TaxLimitBase)reader["TAXLIMITBASE"], (TaxCalculationMode)reader["TAXCALCMETHOD"], reader["TAXONTAX"] as string, reader["TAXUNIT"] as string, (decimal)reader["TAXMIN"], (decimal)reader["TAXMAX"], (decimal)reader["ABATEMENTPERCENT_IN"], (TaxTypes)reader["TAXTYPE_IN"], this)); } }
public TaxCode( string code, ITaxableItem lineItem, string taxGroup, string currency, decimal value, decimal limitMin, decimal limitMax, bool exempt, TaxBase taxBase, TaxLimitBase limitBase, TaxCalculationMode method, string taxOnTax, string unit, decimal collectMin, decimal collectMax, bool groupRounding, TaxCodeProvider provider) : this(value, limitMin, limitMax, provider) { this.Code = code; this.LineItem = lineItem; this.TaxGroup = taxGroup; this.Currency = currency; this.Exempt = exempt; this.TaxBase = taxBase; this.TaxLimitBase = limitBase; this.TaxCalculationMethod = method; this.TaxOnTax = taxOnTax; this.Unit = unit; this.CollectLimitMax = collectMax; this.CollectLimitMin = collectMin; this.TaxGroupRounding = groupRounding; }
/// <summary> /// Gets the tax code. /// </summary> /// <param name="reader">The reader.</param> /// <param name="taxableItem">The taxable item.</param> /// <returns>The taxcode object</returns> protected virtual TaxCode GetTaxCode(SqlDataReader reader, ITaxableItem taxableItem) { if (reader == null) { throw new ArgumentNullException("reader"); } return(new TaxCode( reader["TAXCODE"] as string, taxableItem, reader["TAXITEMGROUP"] as string, reader["TAXCURRENCYCODE"] as string, (decimal)reader["TAXVALUE"], (decimal)reader["TAXLIMITMIN"], (decimal)reader["TAXLIMITMAX"], ((int)reader["EXEMPTTAX"] == 1), (TaxBase)reader["TAXBASE"], (TaxLimitBase)reader["TAXLIMITBASE"], (TaxCalculationMode)reader["TAXCALCMETHOD"], reader["TAXONTAX"] as string, reader["TAXUNIT"] as string, (decimal)reader["TAXMIN"], (decimal)reader["TAXMAX"], ((int)reader["TAXGROUPROUNDING"] == 1), this)); }
public void CalculateTax(ITaxableItem taxableItem, IRetailTransaction retailTransaction) { var codes = GetTaxCodes(taxableItem); LineTaxResult lineTaxResult = new LineTaxResult { HasExempt = false, TaxRatePercent = decimal.Zero, TaxAmount = decimal.Zero, ExemptAmount = decimal.Zero }; foreach (TaxCode code in codes) { lineTaxResult.TaxAmount += code.CalculateTaxAmount(codes); // sum up the amounts that are exempt if (code.Exempt) { lineTaxResult.HasExempt = true; lineTaxResult.ExemptAmount += lineTaxResult.TaxAmount; } } // Set the 'virtual tax rate', if extended price is ZERO, then just add the full amount decimal extendedPrice = (taxableItem.Price * Math.Abs(taxableItem.Quantity)); if (extendedPrice == decimal.Zero) { extendedPrice = decimal.One; } lineTaxResult.TaxRatePercent = ((lineTaxResult.TaxAmount * 100) / extendedPrice); SetLineItemTaxRate(taxableItem, lineTaxResult); }
protected static void SetLineItemTaxRate(ITaxableItem taxableItem, LineTaxResult lineTaxResult) { // Ignore any portion of the TaxAmount that is 'Exempt' when computing the rate. decimal amount = lineTaxResult.TaxAmount - lineTaxResult.ExemptAmount; SetLineItemTaxRate(taxableItem, amount); }
public override decimal GetBasePriceForTaxIncluded(ITaxableItem taxableItem, ReadOnlyCollection <TaxCode> codes) { if (taxableItem == null) { throw new ArgumentNullException("taxableItem"); } // Even though we use TaxIncludedInPrice, we have to adopt NetAmountPerUnit to mimic the Fiscal Printer behavior return(taxableItem.NetAmountPerUnit); }
protected override ReadOnlyCollection <TaxCode> GetTaxCodes(ITaxableItem taxableItem) { // Only use codes that are not processed as India tax codes, or those that are and are // supported. return(new ReadOnlyCollection <TaxCode>(base.GetTaxCodes(taxableItem).Where(c => { var codeIndia = c as TaxCodeIndia; return codeIndia == null || SupportedTax(codeIndia); }).ToList <TaxCode>())); }
protected static void SetLineItemTaxRate(ITaxableItem taxableItem, decimal taxAmount) { decimal extendedPrice = (taxableItem.Price * taxableItem.Quantity); if (extendedPrice == decimal.Zero) { extendedPrice = 1; } taxableItem.TaxRatePct += ((taxAmount * 100) / extendedPrice); }
/// <summary> /// Simple version of TaxIncluded algorithm for Tax Code collections that are not based on: /// - intervals, /// - limits, /// - collection limits /// - total invoice /// </summary> /// <param name="lineItem">The taxable item.</param> /// <param name="codes">The codes.</param> /// <returns>base price</returns> private static decimal GetBasePriceSimpleTaxIncluded( ITaxableItem lineItem, ReadOnlyCollection <TaxCode> codes) { // accumulation of % based tax decimal fullLineTaxRate = decimal.Zero; // accumulation of amount based tax decimal fullLineUnitTax = decimal.Zero; decimal nonExemptLineUnitTax = decimal.Zero; // 1. Determine sum of all AmountByUnit taxes (ref: AX\Classes\Tax.AmountExclTax() - line 222) decimal codeValue = decimal.Zero; // Reference dev item 5747 foreach (TaxCode code in codes.Where(c => c.TaxBase == TaxBase.AmountByUnit)) { codeValue = code.Calculate(codes, false); // Amount by units don't depend on basePrice fullLineUnitTax += codeValue; nonExemptLineUnitTax += (code.Exempt) ? decimal.Zero : codeValue; } // 2. Determine sum of all tax rates for non-AmountByUnit taxes (ref: AX\Classes\Tax.AmountExclTax() - line 331) foreach (TaxCode code in codes.Where(c => c.TaxBase != TaxBase.AmountByUnit)) { if (code.TaxBase == TaxBase.PercentPerGross && string.IsNullOrEmpty(code.TaxOnTax)) { //Sum all OTHER taxes... codeValue = codes.Sum(c => (c.TaxBase == TaxBase.AmountByUnit) ? decimal.Zero : c.PercentPerTax()); //...and then apply the Gross tax on top of that codeValue *= code.PercentPerTax() / 100; // Add this rate to the running total. fullLineTaxRate += codeValue; } else { // Add this rate to the running total. codeValue = code.PercentPerTax(); fullLineTaxRate += codeValue; } } // 3. Back calculate the Price based on tax rates, start with the Price that includes ALL taxes decimal taxBase = lineItem.NetAmountWithAllInclusiveTaxPerUnit - fullLineUnitTax; return((taxBase * 100) / (100 + fullLineTaxRate)); }
/// <summary> /// Get MRP of the sales line item. /// </summary> /// <param name="taxableItem">sales line item</param> /// <returns>MRP of the sales line item</returns> public static decimal GetMRP(ITaxableItem taxableItem) { SaleLineItem saleItem = taxableItem as SaleLineItem; if (saleItem == null) { return(decimal.Zero); } decimal mrp = GetMRPFromTradeAgreement(saleItem); if (mrp != decimal.Zero) { return(mrp); } return(GetMRPFromItemMaster(saleItem.ItemId)); }
private void AddTaxCode(CacheKey cacheKey, SqlDataReader reader, ITaxableItem taxableItem, Dictionary <string, TaxCode> codes) { TaxCode code = GetTaxCode(reader, taxableItem); codes.Add(code.Code, code); if (!taxCodeCache.ContainsKey(cacheKey)) { taxCodeCache[cacheKey] = new List <TaxCode>() { code } } ; else { taxCodeCache[cacheKey].Add(code); } }
public virtual decimal GetBasePriceForTaxIncluded(ITaxableItem taxableItem, ReadOnlyCollection <TaxCode> codes) { // check to see if we can do the 'simple' Inclusive algorithm bool simpleBasis = codes.All(c => (c.TaxBase == TaxBase.PercentPerNet || c.TaxBase == TaxBase.PercentGrossOnNet) && (c.TaxLimitMin == decimal.Zero && c.TaxLimitMax == decimal.Zero)); bool collectLimits = codes.Any(c => (c.CollectLimitMax != decimal.Zero || c.CollectLimitMin != decimal.Zero)); bool multiplePercentage = codes.Any(c => (c.TaxIntervals.Count > 1)); if (simpleBasis && !collectLimits && !multiplePercentage) { // Get base price for Simple TaxInclusive calculation return(GetBasePriceSimpleTaxIncluded(taxableItem, codes)); } else { // Get base price for Full TaxInclusive calculation return(GetBasePriceAdvancedTaxIncluded(taxableItem, codes, collectLimits)); } }
public TaxCodeIndia( string code, ITaxableItem lineItem, string taxGroup, string currency, decimal value, decimal limitMin, decimal limitMax, bool exempt, TaxBase taxBase, TaxLimitBase limitBase, TaxCalculationMode method, string taxOnTax, string unit, decimal collectMin, decimal collectMax, decimal abatementPercent, TaxTypes taxType, TaxCodeProvider provider) : base(value, limitMin, limitMax, provider) { this.Code = code; this.LineItem = lineItem; this.TaxGroup = taxGroup; this.Currency = currency; this.Exempt = exempt; this.TaxBase = taxBase; this.TaxLimitBase = limitBase; this.TaxCalculationMethod = method; this.TaxOnTax = taxOnTax; this.Unit = unit; this.CollectLimitMax = collectMax; this.CollectLimitMin = collectMin; this.AbatementPercent = abatementPercent; this.TaxType = taxType; }
/// <summary> /// Return a sum of all the currently applied tax amounts /// </summary> /// <param name="lineItem"></param> /// <returns></returns> private static decimal SumAllTaxAmounts(ITaxableItem lineItem) { decimal allTaxAmounts = lineItem.TaxLines.Sum(t => t.Exempt ? decimal.Zero : t.Amount); return(allTaxAmounts); }
private static decimal GetBasePriceAdvancedTaxIncluded( ITaxableItem taxableItem, ReadOnlyCollection <TaxCode> codes, bool collectLimits) { // accumulation of amount based tax decimal fullLineUnitTax = decimal.Zero; decimal nonExemptLineUnitTax = decimal.Zero; decimal codeValue = decimal.Zero; //AX variables decimal endAmount = taxableItem.NetAmountWithAllInclusiveTaxPerUnit; //endAmount will be the final price w/o tax int sign = 1; // 3. decimal taxLimitMax = decimal.Zero; decimal taxLimitMin = decimal.Zero; decimal startAmount = decimal.Zero; // 3a... decimal taxCalc = decimal.Zero; decimal baseCur; // Tax Amount deducted for a given Code Dictionary <string, decimal> deductedTax = new Dictionary <string, decimal>(); // 3b ... decimal percentTotal; decimal tmpBase; // 3c.. // Whether or not a Code needs to be removed from the sum of percent rates Dictionary <string, bool> removePercent = new Dictionary <string, bool>(); //3d. decimal totalTax = decimal.Zero; // Whether or not the Code needs to be calculated Dictionary <string, bool> calcTax = new Dictionary <string, bool>(); // // Begin Tax included calculation // //0. Initialize the supporting collections foreach (TaxCode code in codes) { deductedTax[code.Code] = decimal.Zero; removePercent[code.Code] = false; calcTax[code.Code] = true; } // // 1. Remove all AmountByUnit taxes // foreach (TaxCode code in codes.Where(c => c.TaxBase == TaxBase.AmountByUnit)) { codeValue = code.Calculate(codes, false); // Reference dev item 5748. fullLineUnitTax += codeValue; nonExemptLineUnitTax += (code.Exempt) ? decimal.Zero : codeValue; calcTax[code.Code] = false; } endAmount -= nonExemptLineUnitTax; // // 2. Record the sign, and then continue using the magnitude of endAmount // sign = (endAmount < decimal.Zero) ? -1 : 1; endAmount = Math.Abs(endAmount); // // 3. // while (startAmount < endAmount) { // 3a Consider interval limits taxCalc = decimal.Zero; taxLimitMax = decimal.Zero; foreach (TaxCode code in codes) { if (code.TaxCalculationMethod == TaxCalculationMode.FullAmounts) { taxLimitMax = decimal.Zero; } else { if (IsStoreCurrency(code)) { baseCur = TaxService.Tax.InternalApplication.Services.Currency.CurrencyToCurrency( ApplicationSettings.Terminal.StoreCurrency, code.Currency, taxLimitMin); } else { baseCur = taxLimitMin; } baseCur += 1; // if 'baseCur' falls into an interval if (code.TaxIntervals.Exists(baseCur)) { // get the Upper limit of the interval that 'baseCur'/'taxLimitMin' falls into decimal amount = code.TaxIntervals.Find((taxLimitMin + 1)).TaxLimitMax; taxLimitMax = (amount != decimal.Zero && amount < endAmount) ? amount : endAmount; } } taxCalc += deductedTax[code.Code]; } // 3b. Sum up all the Tax Percentage Rates percentTotal = 0; tmpBase = (taxLimitMax > decimal.Zero) ? taxLimitMax : endAmount; foreach (TaxCode code in codes.Where(c => calcTax[c.Code])) { percentTotal += GetPercentPerTax(code, tmpBase, codes); } decimal taxMax; decimal baseInclTax; decimal baseExclTax; // 3c. // if this is the last interval?? if (taxLimitMax == decimal.Zero) { // Forward calculate taxes to see if we exceed the CollectLimit foreach (TaxCode code in codes.Where(c => calcTax[c.Code])) { taxMax = code.CollectLimitMax; baseInclTax = endAmount - taxLimitMin - taxCalc; baseExclTax = baseInclTax * 100 / (100 + percentTotal); if (taxMax != decimal.Zero) { tmpBase = endAmount; decimal percent = GetPercentPerTax(code, tmpBase, codes); if ((deductedTax[code.Code] + baseExclTax * percent / 100) > taxMax) { deductedTax[code.Code] = taxMax; removePercent[code.Code] = true; } } } //3d. // Now remove any rates that exceed their LimitMax foreach (TaxCode code in codes) { if (removePercent[code.Code] && calcTax[code.Code]) { tmpBase = endAmount; percentTotal -= GetPercentPerTax(code, tmpBase, codes); calcTax[code.Code] = false; } taxCalc += deductedTax[code.Code]; } } //4. Compute tax adjusted for limits totalTax = decimal.Zero; foreach (TaxCode code in codes.Where(c => c.TaxBase != TaxBase.AmountByUnit)) { if (calcTax[code.Code]) { tmpBase = (taxLimitMax > decimal.Zero) ? taxLimitMax : endAmount; decimal percent = GetPercentPerTax(code, tmpBase, codes); if (taxLimitMax > decimal.Zero && taxLimitMax < endAmount) { deductedTax[code.Code] += (taxLimitMax - taxLimitMin) * percent / 100; } else { baseInclTax = endAmount - taxLimitMin - taxCalc; baseExclTax = baseInclTax * 100 / (100 + percentTotal); deductedTax[code.Code] += baseExclTax * percent / 100; } taxMax = code.CollectLimitMax; if (taxMax > decimal.Zero && deductedTax[code.Code] > taxMax) { deductedTax[code.Code] = taxMax; } } totalTax += deductedTax[code.Code]; } if (taxLimitMax > decimal.Zero) { taxLimitMin = taxLimitMax; startAmount = taxLimitMin + totalTax; } else { startAmount = endAmount; } } // END if( startAmount < endAmount) // 5a. Total up taxes foreach (TaxCode code in codes) { if (collectLimits && (deductedTax[code.Code] < code.CollectLimitMin)) { totalTax -= deductedTax[code.Code]; deductedTax[code.Code] = decimal.Zero; } if (IsStoreCurrency(code)) { taxCalc = TaxService.Tax.InternalApplication.Services.Rounding.TaxRound( deductedTax[code.Code], code.Code); } else { taxCalc = deductedTax[code.Code]; } totalTax += (taxCalc - deductedTax[code.Code]); deductedTax[code.Code] = taxCalc; } //5b. Determine base price return((endAmount - totalTax) * sign); }
protected virtual ReadOnlyCollection <TaxCode> GetTaxCodes(ITaxableItem taxableItem) { if (taxableItem == null) { throw new ArgumentNullException("taxableItem"); } RetailTransaction transaction = (RetailTransaction)taxableItem.RetailTransaction; string customerId = string.Empty; // If the line has an EndDate specified (usually because it's a Returned line), // then use that value to calculate taxes, otherwise use BeginDate DateTime itemSaleDateTime = (taxableItem.EndDateTime <= NoDate) ? taxableItem.BeginDateTime : taxableItem.EndDateTime; if (transaction != null && transaction.Customer != null) { customerId = transaction.Customer.CustomerId; } CacheKey cacheKey = new CacheKey(taxableItem.ItemId, customerId, taxableItem.TaxGroupId, taxableItem.SalesTaxGroupId, itemSaleDateTime); if (taxCodeCache.ContainsKey(cacheKey)) { List <TaxCode> taxCodes = taxCodeCache[cacheKey]; // Update the lineItem object in cached Taxcode object (Everytime we get new SalesLine Object) taxCodes.ForEach(t => t.LineItem = taxableItem); return(SortCodes(taxCodes)); } NetTracer.Information("TaxCodeProvider::GetTaxCodes(): Quering database."); SqlConnection connection = Application.Settings.Database.Connection; string dataAreaId = Application.Settings.Database.DataAreaID; try { Dictionary <string, TaxCode> codes = new Dictionary <string, TaxCode>(); bool useDefaultTaxGroups = (cacheKey.TaxGroupID == null) || (cacheKey.SalesTaxGroupID == null); using (SqlCommand command = new SqlCommand()) { command.Connection = connection; string sb = string.Format(@"SELECT DISTINCT {0} FROM TAXGROUPHEADING ", TaxSelectSqlText); if (useDefaultTaxGroups) { // #1 Look in the DB for the default Customer/Store tax group mapping if (String.IsNullOrWhiteSpace(cacheKey.CustomerID)) { sb += @"INNER JOIN RETAILSTORETABLE ON TAXGROUPHEADING.TAXGROUP = RETAILSTORETABLE.TAXGROUP AND RETAILSTORETABLE.STORENUMBER = @STOREID "; command.Parameters.AddWithValue("@STOREID", ApplicationSettings.Terminal.StoreId); } else { sb += @"INNER JOIN CUSTTABLE ON TAXGROUPHEADING.TAXGROUP = CUSTTABLE.TAXGROUP AND CUSTTABLE.DATAAREAID = @DATAAREAID AND CUSTTABLE.ACCOUNTNUM = @CUSTOMERID "; command.Parameters.AddWithValue("@CUSTOMERID", cacheKey.CustomerID); } } sb += @"INNER JOIN TAXGROUPDATA ON TAXGROUPDATA.DATAAREAID = @DATAAREAID AND TAXGROUPHEADING.TAXGROUP = TAXGROUPDATA.TAXGROUP INNER JOIN TAXONITEM ON TAXONITEM.DATAAREAID = @DATAAREAID AND TAXGROUPDATA.TAXCODE = TAXONITEM.TAXCODE "; if (useDefaultTaxGroups) { // Join against the Item's default Item tax group sb += @"INNER JOIN INVENTTABLEMODULE ON INVENTTABLEMODULE.DATAAREAID = @DATAAREAID AND INVENTTABLEMODULE.TAXITEMGROUPID = TAXONITEM.TAXITEMGROUP "; } sb += @"INNER JOIN TAXDATA ON TAXDATA.DATAAREAID = @DATAAREAID AND TAXONITEM.TAXCODE = TAXDATA.TAXCODE INNER JOIN TAXTABLE ON TAXTABLE.DATAAREAID = @DATAAREAID AND TAXTABLE.TAXCODE = TAXDATA.TAXCODE LEFT JOIN TAXCOLLECTLIMIT ON TAXCOLLECTLIMIT.DATAAREAID = @DATAAREAID AND TAXCOLLECTLIMIT.TAXCODE = TAXDATA.TAXCODE AND (TAXCOLLECTLIMIT.TAXFROMDATE IS NULL OR @TRANSACTIONDATE >= TAXCOLLECTLIMIT.TAXFROMDATE OR TAXCOLLECTLIMIT.TAXFROMDATE < @NODATEBOUNDRY ) AND (TAXCOLLECTLIMIT.TAXTODATE IS NULL OR @TRANSACTIONDATE < DATEADD(d, 1, TAXCOLLECTLIMIT.TAXTODATE) OR TAXCOLLECTLIMIT.TAXTODATE < @NODATEBOUNDRY) WHERE (TAXGROUPHEADING.DATAAREAID = @DATAAREAID) "; command.Parameters.AddWithValue("@DATAAREAID", dataAreaId); if (useDefaultTaxGroups) { sb += @"AND (INVENTTABLEMODULE.ITEMID = @ITEMID) AND (INVENTTABLEMODULE.MODULETYPE = @MODULETYPE) "; command.Parameters.AddWithValue("@ITEMID", cacheKey.ItemID); command.Parameters.AddWithValue("@MODULETYPE", (int)ModuleType.Sales); } else { // Filter against the item's current Item Tax Group and Customer/Store tax group sb += @"AND TAXONITEM.TAXITEMGROUP = @ITEMTAXGROUP AND TAXGROUPHEADING.TAXGROUP = @SALESTAXGROUP "; command.Parameters.AddWithValue("@SALESTAXGROUP", cacheKey.SalesTaxGroupID ?? string.Empty); command.Parameters.AddWithValue("@ITEMTAXGROUP", cacheKey.TaxGroupID ?? string.Empty); } // Currently only evaluate taxes against the current time. // Note that the date value of '1900-01-01 00:00.000' is the marker for "no boundry". sb += @"AND ((@TRANSACTIONDATE >= TAXDATA.TAXFROMDATE OR TAXDATA.TAXFROMDATE < @NODATEBOUNDRY ) AND (@TRANSACTIONDATE < DATEADD(d, 1, TAXDATA.TAXTODATE) OR TAXDATA.TAXTODATE < @NODATEBOUNDRY)) "; command.Parameters.AddWithValue("@NODATEBOUNDRY", NoDate); command.Parameters.AddWithValue("@TRANSACTIONDATE", cacheKey.SaleDateTime); sb += AddTaxSelectSqlCondition(command); command.CommandText = sb.ToString(); if (connection.State != ConnectionState.Open) { connection.Open(); } using (SqlDataReader reader = command.ExecuteReader()) { string taxCodeKey = string.Empty; while (reader.Read()) { taxCodeKey = reader["TAXCODE"] as string; if (codes.ContainsKey(taxCodeKey)) { // Add a new 'value' entry for an existing tax code codes[taxCodeKey].TaxIntervals.Add( new TaxInterval( (decimal)reader["TAXLIMITMIN"], (decimal)reader["TAXLIMITMAX"], (decimal)reader["TAXVALUE"])); } else { AddTaxCode(cacheKey, reader, taxableItem, codes); } } } } // Link any taxes which rely on other taxes foreach (TaxCode tax in codes.Values) { if (!string.IsNullOrEmpty(tax.TaxOnTax) && (tax.TaxBase == TaxBase.PercentPerTax || tax.TaxBase == TaxBase.PercentPerGross) && codes.Keys.Contains(tax.TaxOnTax)) { tax.TaxOnTaxInstance = codes[tax.TaxOnTax] as TaxCode; } } return(SortCodes(codes.Values)); } catch (Exception ex) { NetTracer.Error(ex, "GetTaxCodes() failed in an Exception"); throw; } finally { if (connection.State == ConnectionState.Open) { connection.Close(); } } }