Open stephanstapel opened 4 months ago
sum up taxes, probably easier with LINQ
Hi! // 11. ApplicableTradeTax (optional) _writeOptionalTaxes(Writer); //_writeOptionalTaxesNew(Writer); //check
With the original _writeOptionalTaxes, I get
With my "new" instead
I'm still trying to fix it; even when DB accepts the invoice in both cases, Kosit fails for both, but then "new" is less erroneous :-)
Possibly it would be better to move this from issue to discussion?
As far as I suspect some roundings or value misunderstanding, I'm experimenting a lot. Current trial:
private void _writeOptionalTaxesNew(ProfileAwareXmlTextWriter writer)
{
List<BG23> BG23List = new List<BG23>();
// collect tax info
foreach (Tax tax in this.Descriptor.Taxes)
{
// new bg32 object
BG23 bg23 = new BG23();
bg23.AllowanceChargeBasisAmount = tax.AllowanceChargeBasisAmount;
bg23.Percent = tax.Percent;
bg23.ExemptionReasonCode = tax.ExemptionReasonCode;
bg23.ExemptionReason = tax.ExemptionReason;
bg23.BasisAmount = tax.BasisAmount;
bg23.TaxAmount = tax.TaxAmount;
bg23.CategoryCode = tax.CategoryCode;
bg23.TypeCode = tax.TypeCode;
BG23 checkBG23 = BG23List.Find(x => x.CategoryCode == bg23.CategoryCode);
// add BT values
if (checkBG23 == null) // neu
{
bg23.BT116 = tax.BasisAmount;
bg23.TypeCode = bg23.TypeCode;
bg23.BT131 = tax.BasisAmount;
BG23List.Add(bg23);
}
else // vorhanden
{
bg23 = checkBG23;
bg23.BT131 += tax.BasisAmount;
bg23.BT116 += tax.BasisAmount;
bg23.TaxAmount += tax.TaxAmount;
}
// BT-116 sum of Invoice line net amounts (BT-131) plus the sum of document level charge amounts (BT-99) minus the sum of document level allowance amounts (BT-92)
Console.WriteLine(string.Format("1 BG-23 BT-116({0} BT-131({1})", bg23.BT116, bg23.BT131));
}
foreach (var c in this.Descriptor.GetTradeAllowanceCharges())
{
BG23 bg23 = BG23List.Find(x => x.CategoryCode == c.Tax.CategoryCode);
if (bg23 == null)
{
// cannot be!
throw (new Exception("BG23List error"));
}
if (c.ChargeIndicator == true)
{
bg23.BT99 += c.ActualAmount;
bg23.BT116 += c.ActualAmount;
}
else
{
bg23.BT92 += c.ActualAmount;
bg23.BT116 -= c.ActualAmount;
}
Console.WriteLine(string.Format("2 BG-23 BT-116({0} BT-131({1})", bg23.BT116, bg23.BT131));
}
foreach (BG23 bg23 in BG23List)
{
#if DEBUG
Console.WriteLine(string.Format("BG-23 BT-116({0}/{1})", bg23.BT116, bg23.BasisAmount));
#endif
writer.WriteStartElement("ram:ApplicableTradeTax");
writer.WriteStartElement("ram:CalculatedAmount");
writer.WriteValue(_formatDecimal(bg23.TaxAmount));
writer.WriteEndElement(); // !CalculatedAmount
writer.WriteElementString("ram:TypeCode", bg23.TypeCode.EnumToString());
writer.WriteOptionalElementString("ram:ExemptionReason", bg23.ExemptionReason);
writer.WriteStartElement("ram:BasisAmount");
// sum of Invoice line net amounts (BT-131) plus the sum of document level charge amounts (BT-99) minus the sum of document level allowance amounts (BT-92)
writer.WriteValue(_formatDecimal(bg23.BT131));
//writer.WriteValue(_formatDecimal(bg23.BT116));
writer.WriteEndElement(); // !BasisAmount
if (bg23.CategoryCode.HasValue)
{
writer.WriteElementString("ram:CategoryCode", bg23.CategoryCode?.EnumToString());
}
if (bg23.ExemptionReasonCode.HasValue)
{
writer.WriteElementString("ram:ExemptionReasonCode", bg23.ExemptionReasonCode?.EnumToString());
}
writer.WriteElementString("ram:RateApplicablePercent", _formatDecimal(bg23.Percent));
writer.WriteEndElement(); // !RateApplicablePercent
}
} // !_writeOptionalTaxesNew()
and a new class
public class BG23 : Tax
{
public new decimal TaxAmount { get; set; } // comes from tax
public decimal BT116 { get; set; } // comes from tax
public decimal BT131 { get; set; } // comes from tax
public decimal BT99 { get; set; }
public decimal BT92 { get; set; }
}
Are you sure that grouping the taxes by category code is correct? I guess that at least the percent need to be taken into account, otherwise the result will be wrong. And also, I thought the idea behind the taxes is that you can set different exemption reasons and type codes for each tax.
Hi! No I'm not sure, a bit confused, see
https://docs.peppol.eu/poacc/billing/3.0/syntax/ubl-invoice/cac-TaxTotal/cac-TaxSubtotal/
However, with my current output, the invoices are accepted by DB. Could be shortsighted... With the original code was not accepted... I'll try it again, later, a bit busy today
Stephan @.***> schrieb am Di., 14. Mai 2024, 21:04:
Are you sure that grouping the taxes by category code is correct? I guess that at least the percent need to be taken into account, otherwise the result will be wrong. And also, I thought the idea behind the taxes is that you can set different exemption reasons and type codes for each tax.
— Reply to this email directly, view it on GitHub https://github.com/stephanstapel/ZUGFeRD-csharp/issues/261#issuecomment-2110951230, or unsubscribe https://github.com/notifications/unsubscribe-auth/AEYZBNOBYYCJ7TDUOOWAYCDZCJN4VAVCNFSM6AAAAABHJELPFKVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDCMJQHE2TCMRTGA . You are receiving this because you were mentioned.Message ID: @.***>
yep, good link!
The text says: "Sum of all taxable amounts subject to a specific VAT category code and VAT category rate (if the VAT category rate is applicable)."
I.e. this would be the taxes grouped by category and rate (percentage). This reflects the data structure of Peppol which only comes with these two categorizations:
XRechnung comes with more detailed categorizations:
i.e. I guess in this case, the grouping must happen by
Hi I post the current version (which in combination with the linetotalamoun change works). Don't forget #269...Without the changes in invoicedesriptor, thus providing the correct LineTotalAmount, it will not work.
private void _writeOptionalTaxesNew(ProfileAwareXmlTextWriter writer)
{
List<BG23> BG23List = new List<BG23>();
// collect tax info
foreach (Tax tax in this.Descriptor.Taxes)
{
// new bg32 object
BG23 bg23 = new BG23();
bg23.AllowanceChargeBasisAmount = tax.AllowanceChargeBasisAmount;
bg23.Percent = tax.Percent;
bg23.ExemptionReasonCode = tax.ExemptionReasonCode;
bg23.ExemptionReason = tax.ExemptionReason;
bg23.BasisAmount = tax.BasisAmount;
bg23.TaxAmount = tax.TaxAmount;
bg23.CategoryCode = tax.CategoryCode;
bg23.TypeCode = tax.TypeCode;
BG23 checkBG23 = BG23List.Find(x => x.CategoryCode == bg23.CategoryCode);
// add BT values
if (checkBG23 == null) // neu
{
bg23.BT116 = tax.BasisAmount;
bg23.TypeCode = bg23.TypeCode;
bg23.BT131 = tax.BasisAmount;
BG23List.Add(bg23);
}
else // vorhanden
{
bg23 = checkBG23;
bg23.BT131 += tax.BasisAmount;
bg23.BT116 += tax.BasisAmount;
bg23.BasisAmount += tax.BasisAmount;
bg23.TaxAmount += tax.TaxAmount;
}
// BT-116 sum of Invoice line net amounts (BT-131) plus the sum of document level charge amounts (BT-99) minus the sum of document level allowance amounts (BT-92)
//Console.WriteLine(string.Format("1 BG-23 BT-116:{0} BT-131:{1} BT-117:{2})", bg23.BT116, bg23.BT131, bg23.TaxAmount));
}
foreach (var c in this.Descriptor.GetTradeAllowanceCharges())
{
BG23 bg23 = BG23List.Find(x => x.CategoryCode == c.Tax.CategoryCode);
if (bg23 == null)
{
// cannot be!
throw (new Exception("BG23List error"));
}
if (c.ChargeIndicator == true)
{
bg23.BT99 += c.ActualAmount;
bg23.BT116 += c.ActualAmount;
}
else
{
bg23.BT92 += c.ActualAmount;
bg23.BT116 -= c.ActualAmount;
}
//Console.WriteLine(string.Format("2 BG-23 BT-116:{0} BT-131:{1} BT-117:{2})", bg23.BT116, bg23.BT131, bg23.TaxAmount));
}
foreach (BG23 bg23 in BG23List)
{
//Console.WriteLine(string.Format("BG-23 BT-116: {0}/BT-131: {1})", bg23.BT116, bg23.BT131));
writer.WriteStartElement("ram:ApplicableTradeTax");
writer.WriteStartElement("ram:CalculatedAmount");
writer.WriteValue(_formatDecimal(bg23.TaxAmount));
writer.WriteEndElement(); // !CalculatedAmount
writer.WriteElementString("ram:TypeCode", bg23.TypeCode.EnumToString());
writer.WriteOptionalElementString("ram:ExemptionReason", bg23.ExemptionReason);
writer.WriteStartElement("ram:BasisAmount");
// sum of Invoice line net amounts (BT-131) plus the sum of document level charge amounts (BT-99) minus the sum of document level allowance amounts (BT-92)
writer.WriteValue(_formatDecimal(bg23.BT131));
//writer.WriteValue(_formatDecimal(bg23.BT116));
writer.WriteEndElement(); // !BasisAmount
if (bg23.CategoryCode.HasValue)
{
writer.WriteElementString("ram:CategoryCode", bg23.CategoryCode?.EnumToString());
}
if (bg23.ExemptionReasonCode.HasValue)
{
writer.WriteElementString("ram:ExemptionReasonCode", bg23.ExemptionReasonCode?.EnumToString());
}
writer.WriteElementString("ram:RateApplicablePercent", _formatDecimal(bg23.Percent));
writer.WriteEndElement(); // !RateApplicablePercent
}
} // !_writeOptionalTaxesNew()
The code above can't be correct. E.g. it wouldn't work with two VAT rates. Also, if different type codes or exemption reasons are used, they also would be wiped out.
Hi, I prototyped this having to output my actual invoices ... I will now verify with a standard test case (three different taxes, like a Hotel bill 19% 7% 0%) and verify the code. I was not sure if developing it furthermore was desired... so please give me some day... TIA!
Yep. IThanks for your patience! Instead of
BG23 checkBG23 = BG23List.Find(x => x.CategoryCode == bg23.CategoryCode);
put
BG23 checkBG23 = BG23List.Find(x => x.CategoryCode == bg23.CategoryCode && x.Percent == bg23.Percent && x.ExemptionReasonCode == bg23.ExemptionReasonCode
and it works.
Testcase was giving
cool, that sounds more reasonable now. Could you test one last thing? Could you try two VATs with same category code, same percentage, same exemption reason code but different exemption reason messages? I wonder if this also needs to be a grouping criteria.
I took another look at the code. I now understand better that you add/reduce the tax that is given in the TradeAllowanceCharge from the global Tax (of same category, percentage etc.). However, how does one know if the user that fills in the values in his application did not already take the TradeAllowanceCharge into account when filling the document level Tax?
Hi! Ich switch mal auf Deutsch, wird mir sonst zu mühsam... :-) Wenn es um BT-92 geht: ich mache im aufrufenden Programm die Berechnung, um dem User die Mühe abzunehmen. Macht im Endeffekt das ERP, wo die Rechnung herkommt, ja auch.
if (allowancePerc > 0)
{
desc.AllowanceTotalAmount += rabatt;
decimal? preisrabatt = tradeLineItem.GrossUnitPrice - tradeLineItem.NetUnitPrice;
tradeLineItem.AddTradeAllowanceCharge(true, desc.Currency, neuesbrutto, (decimal)preisrabatt, allowancePerc, "Rabatt");
desc.AddTradeAllowanceCharge(true, altesbrutto, desc.Currency, (decimal)rabatt, allowancePerc, "Rabatt2", tradeLineItem.TaxType, tradeLineItem.TaxCategoryCode, tradeLineItem.TaxPercent);
desc.AddApplicableTradeTax(tax.BasisAmount, tax.Percent, tax.TypeCode, tax.CategoryCode, (decimal)rabatt, null, null);
}
if (allowancePerc == 0)
{
desc.AllowanceTotalAmount += rabatt;
decimal? preisrabatt = tradeLineItem.GrossUnitPrice - tradeLineItem.NetUnitPrice;
tax.BasisAmount = (decimal)neuesbrutto;
desc.AddApplicableTradeTax(tax.BasisAmount, tax.Percent, tax.TypeCode, tax.CategoryCode, (decimal)rabatt, null, null);
}
Hier könnte man - auf deine Frage hin - einen Fehler bei Abweichung der Eingabe zur Berechnung ausgeben- Da der Validator sowieso den Gesamtwert mit der Summe der einzelnen Positionen abgleicht, müssen die Zahlen sowieso übereinstimmen.
Insofern: wenn überhaupt, dann den Fehler auf Anwendungsebene managen und nicht erst nach gescheiterter Validierung... ist sicher Geschmacksache. VG
broken down from #254, submitted by @goedo
InvoiceDescriptor22Writer.cs