Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Discounts applied per line item price instead of sum #35

Merged
merged 2 commits into from
Aug 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 3 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,12 @@ Unlike other tax regimes, Italy requires simplified invoices to include the cust
You can find copies of the Italian FatturaPA schema in the [schemas folder](./schema).

Key websites:
- [FatturaPA & SDI service documentation page on on Italy's tax authority's website](https://www.agenziaentrate.gov.it/portale/web/guest/fatturazione-elettronica-e-dati-fatture-transfrontaliere-new/)

- [FatturaPA & SDI service documentation page on on Italy's tax authority's website](https://www.agenziaentrate.gov.it/portale/web/guest/fatturazione-elettronica-e-dati-fatture-transfrontaliere-new/)
- [FatturaPA documentation page on FatturaPA's dedicated website](https://www.fatturapa.gov.it/en/norme-e-regole/documentazione-fattura-elettronica/formato-fatturapa/)

Useful files:

- [Ordinary Schema V1.2.1 Spec Table View (EN)](https://www.fatturapa.gov.it/export/documenti/fatturapa/v1.2.1/Table-view-B2B-Ordinary-invoice.pdf) - by far the most comprehensible spec doc. Since the difference between 1.2.2 and 1.2.1 is minimal, this is perfectly usable.
- [Ordinary Schema V1.2.2 PDF (IT)](https://www.fatturapa.gov.it/export/documenti/Specifiche_tecniche_del_formato_FatturaPA_v1.3.1.pdf) - most up-to-date but difficult
- [XSD V1.2.2](https://www.fatturapa.gov.it/export/documenti/fatturapa/v1.2.2/Schema_del_file_xml_FatturaPA_v1.2.2.xsd)
Expand Down Expand Up @@ -154,13 +156,3 @@ cat input.json > ./gobl.fatturapa output.xml
## Notes

- In all cases Go structures have been written using the same naming from the XML style document. This means names are not repeated in tags and generally makes it a bit easier to map the XML output to the internal structures.

## Integration Tests

There are some integration and XML generation tests available in the `/test` path. to generate the FatturaPA XML documents from the GOBL sources, use the digital certificates that are available in the `/test/certificates` path:

```
mage -v TestConversion
```

Sample data sources are contained in the `/test/data` directory. JSON (for tests) documents are stored in the Git repository, but the XML must be generated using the above commands.
6 changes: 3 additions & 3 deletions body.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,9 @@ type datiBollo struct {
// scontoMaggiorazione contains data about price adjustments like discounts and
// charges.
type scontoMaggiorazione struct {
Tipo string
Percentuale string
Importo string
Tipo string `xml:"Tipo"`
Percentuale string `xml:"Percentuale,omitempty"`
Importo string `xml:"Importo,omitempty"`
}

func newFatturaElettronicaBody(inv *bill.Invoice) (*fatturaElettronicaBody, error) {
Expand Down
10 changes: 8 additions & 2 deletions helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,18 @@ import "github.com/invopop/gobl/num"

func formatPercentage(p *num.Percentage) string {
if p == nil {
return num.MakePercentage(0, 4).StringWithoutSymbol()
return ""
}

return p.Rescale(4).StringWithoutSymbol()
}

func formatPercentageWithZero(p *num.Percentage) string {
if p == nil {
p = num.NewPercentage(0, 4)
}
return formatPercentage(p)
}

func formatAmount(a *num.Amount) string {
if a == nil {
return ""
Expand Down
37 changes: 21 additions & 16 deletions items.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ type datiBeniServizi struct {

// dettaglioLinee contains line data such as description, quantity, price, etc.
type dettaglioLinee struct {
NumeroLinea string
Descrizione string
Quantita string
PrezzoUnitario string
ScontoMaggiorazione []*scontoMaggiorazione `xml:",omitempty"`
PrezzoTotale string
AliquotaIVA string
Natura string `xml:",omitempty"`
NumeroLinea string `xml:"NumeroLinea"`
Descrizione string `xml:"Descrizione"`
Quantita string `xml:"Quantita"`
PrezzoUnitario string `xml:"PrezzoUnitario"`
ScontoMaggiorazione []*scontoMaggiorazione `xml:"ScontoMaggiorazione,omitempty"`
PrezzoTotale string `xml:"PrezzoTotale"`
AliquotaIVA string `xml:"AliquotaIVA"`
Natura string `xml:"Natura,omitempty"`
}

// datiRiepilogo contains tax summary data such as tax rate, tax amount, etc.
Expand Down Expand Up @@ -58,7 +58,7 @@ func generateLineDetails(inv *bill.Invoice) []*dettaglioLinee {
if line.Taxes != nil && len(line.Taxes) > 0 {
vatTax := line.Taxes.Get(tax.CategoryVAT)
if vatTax != nil {
d.AliquotaIVA = formatPercentage(vatTax.Percent)
d.AliquotaIVA = formatPercentageWithZero(vatTax.Percent)
d.Natura = vatTax.Ext[it.ExtKeySDINature].String()
}
}
Expand All @@ -82,7 +82,7 @@ func generateTaxSummary(inv *bill.Invoice) []*datiRiepilogo {

for _, rateTotal := range vatRateTotals {
dr = append(dr, &datiRiepilogo{
AliquotaIVA: formatPercentage(rateTotal.Percent),
AliquotaIVA: formatPercentageWithZero(rateTotal.Percent),
Natura: rateTotal.Ext[it.ExtKeySDINature].String(),
ImponibileImporto: formatAmount(&rateTotal.Base),
Imposta: formatAmount(&rateTotal.Amount),
Expand All @@ -94,25 +94,30 @@ func generateTaxSummary(inv *bill.Invoice) []*datiRiepilogo {
}

func extractLinePriceAdjustments(line *bill.Line) []*scontoMaggiorazione {
var scontiMaggiorazioni []*scontoMaggiorazione
list := make([]*scontoMaggiorazione, 0)

for _, discount := range line.Discounts {
scontiMaggiorazioni = append(scontiMaggiorazioni, &scontoMaggiorazione{
// Unlike most formats, FatturaPA applies the discount to the unit price
// instead of the line sum.
// Quick ref: https://fex-app.com/FatturaElettronica/FatturaElettronicaBody/DatiBeniServizi/DettaglioLinee/PrezzoTotale
a := discount.Amount.Divide(line.Quantity)
list = append(list, &scontoMaggiorazione{
Tipo: scontoMaggiorazioneTypeDiscount,
Percentuale: formatPercentage(discount.Percent),
Importo: formatAmount(&discount.Amount),
Importo: formatAmount(&a),
})
}

for _, charge := range line.Charges {
scontiMaggiorazioni = append(scontiMaggiorazioni, &scontoMaggiorazione{
a := charge.Amount.Divide(line.Quantity)
list = append(list, &scontoMaggiorazione{
Tipo: scontoMaggiorazioneTypeCharge,
Percentuale: formatPercentage(charge.Percent),
Importo: formatAmount(&charge.Amount),
Importo: formatAmount(&a),
})
}

return scontiMaggiorazioni
return list
}

func findRiferimentoNormativo(rateTotal *tax.RateTotal) string {
Expand Down
2 changes: 1 addition & 1 deletion items_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func TestDettaglioLinee(t *testing.T) {

assert.Equal(t, "SC", sm.Tipo)
assert.Equal(t, "10.00", sm.Percentuale)
assert.Equal(t, "180.00", sm.Importo)
assert.Equal(t, "9.00", sm.Importo)

dl = doc.FatturaElettronicaBody[0].DatiBeniServizi.DettaglioLinee[1]

Expand Down
109 changes: 0 additions & 109 deletions mage.go

This file was deleted.

36 changes: 21 additions & 15 deletions test/data/invoice-hotel.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"uuid": "679a2f25-7483-11ec-9722-7ea2cb436ff6",
"dig": {
"alg": "sha256",
"val": "aa6690ff798fe637147f95713634f33e34f8c03b1b5bba1b12150a8665c3fb2a"
"val": "4b6ec1bb24bdd707520a89e632279966d887f0a3339dc737c30f69f7664c5345"
}
},
"doc": {
Expand Down Expand Up @@ -85,20 +85,26 @@
},
{
"i": 2,
"quantity": "1",
"quantity": "2",
"item": {
"name": "Camera Matrimoniale",
"price": "125.00"
},
"sum": "125.00",
"sum": "250.00",
"discounts": [
{
"amount": "10.00",
"reason": "Sconto"
}
],
"taxes": [
{
"cat": "VAT",
"rate": "intermediate",
"percent": "10.0%"
}
],
"total": "125.00"
"total": "240.00"
}
],
"payment": {
Expand All @@ -112,9 +118,9 @@
]
},
"totals": {
"sum": "126.00",
"tax_included": "11.36",
"total": "114.64",
"sum": "241.00",
"tax_included": "21.82",
"total": "219.18",
"taxes": {
"categories": [
{
Expand All @@ -130,21 +136,21 @@
},
{
"key": "intermediate",
"base": "113.64",
"base": "218.18",
"percent": "10.0%",
"amount": "11.36"
"amount": "21.82"
}
],
"amount": "11.36"
"amount": "21.82"
}
],
"sum": "11.36"
"sum": "21.82"
},
"tax": "11.36",
"total_with_tax": "126.00",
"payable": "126.00",
"tax": "21.82",
"total_with_tax": "241.00",
"payable": "241.00",
"advance": "29.00",
"due": "97.00"
"due": "212.00"
}
}
}
18 changes: 11 additions & 7 deletions test/data/out/invoice-hotel.xml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
<Divisa>EUR</Divisa>
<Data>2023-05-21</Data>
<Numero>SAMPLE-002</Numero>
<ImportoTotaleDocumento>126.00</ImportoTotaleDocumento>
<ImportoTotaleDocumento>241.00</ImportoTotaleDocumento>
</DatiGeneraliDocumento>
</DatiGenerali>
<DatiBeniServizi>
Expand All @@ -78,9 +78,13 @@
<DettaglioLinee>
<NumeroLinea>2</NumeroLinea>
<Descrizione>Camera Matrimoniale</Descrizione>
<Quantita>1.00</Quantita>
<Quantita>2.00</Quantita>
<PrezzoUnitario>113.6364</PrezzoUnitario>
<PrezzoTotale>113.6364</PrezzoTotale>
<ScontoMaggiorazione>
<Tipo>SC</Tipo>
<Importo>4.5455</Importo>
</ScontoMaggiorazione>
<PrezzoTotale>218.1819</PrezzoTotale>
<AliquotaIVA>10.00</AliquotaIVA>
</DettaglioLinee>
<DatiRiepilogo>
Expand All @@ -92,8 +96,8 @@
</DatiRiepilogo>
<DatiRiepilogo>
<AliquotaIVA>10.00</AliquotaIVA>
<ImponibileImporto>113.64</ImponibileImporto>
<Imposta>11.36</Imposta>
<ImponibileImporto>218.18</ImponibileImporto>
<Imposta>21.82</Imposta>
</DatiRiepilogo>
</DatiBeniServizi>
</FatturaElettronicaBody>
Expand All @@ -106,7 +110,7 @@
<ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"></ds:Transform>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha512"></ds:DigestMethod>
<ds:DigestValue>jYM7kYt27X2bFcXWHZD/pG60gB8fJoJB7rk1U8aOL+9iPydC3Y4Cp3NhBnN/LBpsV9z6cyOkXwGxFJ9BkmVxTQ==</ds:DigestValue>
<ds:DigestValue>4KAycPCYtM1f6vffnVi5gWc6nFXhyM2gMCRF61WzyF8/jWna05ffdB89Fz1bMn8ijwrJrJDA7iM3sFWBmMgY6g==</ds:DigestValue>
</ds:Reference>
<ds:Reference URI="#Certificate-679a2f25-7483-11ec-9722-7ea2cb436ff6">
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha512"></ds:DigestMethod>
Expand All @@ -117,7 +121,7 @@
<ds:DigestValue>7Tj/Vr2iDe5KuUvZfrT8ntgjkAtz6zIeztpC/liVkbigGbZLGFHcMpSsVtsRc+WIqqwsB7AwFVjqjSIKIDI3uw==</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue Id="Signature-679a2f25-7483-11ec-9722-7ea2cb436ff6-SignatureValue">SEsDrtqY+j1efLLDlmmOdpVRA1LqFp2a1uigdfpUmeHB+YpGEQrmrImXqoxcw4sLyKYFDg81PptRg1lkAmA0+JaRw0RdX2FofnuUe72yzZiHvUcMTg9WjiEUrkrMJ2i6DDKpFC3ewCb6UqA6NQmhBUSKwq8MQ9AhQdgfC5M/yA9diAgT8Z9ntMckryFKH5U27LNXD/06kVvulyOxsA4cRRPevk4jxu55v/Oeu5C6MMy5ALgKLuJqIto6G67U1tYgco4Mpg4QIZ/5siajMrMrv0ohu8sLnD2FWQTuI8ngaveULpmPg9TSQeBShuMB+i84hSWZbLwt9H9svISRQNnY0w==</ds:SignatureValue>
<ds:SignatureValue Id="Signature-679a2f25-7483-11ec-9722-7ea2cb436ff6-SignatureValue">RewHnpuyoODyss60TEGSUPnYl9F4hpP7zPJkSenU8Sf2m1I53DD7shn+ioYVssu4pDtKvGE/EPaggktb9F14ov7kc+oPVAoX9vtho+yQelUtHFxFVRq1qgHcath9q1H2Iekm14HiK6Xm7ZUqQlxJn4LDtVU35eVfIeRGGu2j1vxdKdXNxQMZSd4wO2/7Xk5FuKNoHPYchkQUI+udWpW2SasxxXHvT1QFfLsBjHbIH735GhDw2f+rR/0GHolxFM/SSrCJzuCcBeGUAY9bDMCCQUWFMFf/UZnaWNPJnpfrQWbo2ja90JLfClofRY5nYkwB2HPhpKppu/b/gg3GYIMxFw==</ds:SignatureValue>
<ds:KeyInfo Id="Certificate-679a2f25-7483-11ec-9722-7ea2cb436ff6">
<ds:X509Data>
<ds:X509Certificate>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</ds:X509Certificate>
Expand Down
Loading
Loading