diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 468f209..47c63c4 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -9,7 +9,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v1 with: - go-version: "1.20.4" + go-version: "1.21.5" id: go - name: Check out code diff --git a/address.go b/address.go index 1044666..67fe621 100644 --- a/address.go +++ b/address.go @@ -1,9 +1,14 @@ package fatturapa import ( + "github.com/invopop/gobl/l10n" "github.com/invopop/gobl/org" ) +const ( + foreignCAP = "00000" +) + // address from IndirizzoType type address struct { Indirizzo string // Street @@ -15,14 +20,19 @@ type address struct { } func newAddress(addr *org.Address) *address { - return &address{ + ad := &address{ Indirizzo: addressStreet(addr), NumeroCivico: addr.Number, - CAP: addr.Code, Comune: addr.Locality, Provincia: addr.Region, Nazione: addr.Country.String(), } + if addr.Country == l10n.IT { + ad.CAP = addr.Code + } else { + ad.CAP = foreignCAP + } + return ad } func addressStreet(address *org.Address) string { diff --git a/go.mod b/go.mod index 494fdfb..e458c2e 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,9 @@ module github.com/invopop/gobl.fatturapa -go 1.19 +go 1.20 require ( - github.com/invopop/gobl v0.67.0 + github.com/invopop/gobl v0.67.3 github.com/invopop/xmldsig v0.8.0 github.com/magefile/mage v1.14.0 github.com/spf13/cobra v1.7.0 diff --git a/go.sum b/go.sum index 0f52a41..0fd600d 100644 --- a/go.sum +++ b/go.sum @@ -21,8 +21,8 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/invopop/gobl v0.67.0 h1:9c28iBunAWYUILIMGkx1zFH/cC5srH5wp9pAb4223MA= -github.com/invopop/gobl v0.67.0/go.mod h1:Jau+ajdfUCBPVH9VMor6aeYq3S9o7HuSNm07QxxxomE= +github.com/invopop/gobl v0.67.3 h1:My3e65q+ERZVBEtksjV3nMs5nw+wlOohe8Jptf6vKwA= +github.com/invopop/gobl v0.67.3/go.mod h1:Jau+ajdfUCBPVH9VMor6aeYq3S9o7HuSNm07QxxxomE= github.com/invopop/jsonschema v0.12.0 h1:6ovsNSuvn9wEQVOyc72aycBMVQFKz7cPdMJn10CvzRI= github.com/invopop/jsonschema v0.12.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0= github.com/invopop/validation v0.3.0 h1:o260kbjXzoBO/ypXDSSrCLL7SxEFUXBsX09YTE9AxZw= diff --git a/items.go b/items.go index a6c3c52..cc1b077 100644 --- a/items.go +++ b/items.go @@ -4,7 +4,6 @@ import ( "strconv" "github.com/invopop/gobl/bill" - "github.com/invopop/gobl/cbc" "github.com/invopop/gobl/i18n" "github.com/invopop/gobl/regimes/it" "github.com/invopop/gobl/tax" @@ -119,9 +118,9 @@ func extractLinePriceAdjustments(line *bill.Line) []*scontoMaggiorazione { func findRiferimentoNormativo(rateTotal *tax.RateTotal) string { def := regime.ExtensionDef(it.ExtKeySDINature) - nature := rateTotal.Ext[it.ExtKeySDINature] + nature := rateTotal.Ext[it.ExtKeySDINature].Code() for _, c := range def.Codes { - if c.Code == cbc.Code(nature) { + if c.Code == nature { return c.Name[i18n.IT] } } diff --git a/parties.go b/parties.go index 0a51c8e..24d7043 100644 --- a/parties.go +++ b/parties.go @@ -8,9 +8,9 @@ import ( ) const ( - statoLiquidazioneDefault = "LN" - euCitizenTaxCodeDefault = "0000000" - nonEUCitizenTaxCodeDefault = "99999999999" + statoLiquidazioneDefault = "LN" + nonITCitizenTaxCodeDefault = "0000000" + nonEUBusinessTaxCodeDefault = "OO99999999999" ) type supplier struct { @@ -111,10 +111,8 @@ func newCessionarioCommittente(c *org.Party) *customer { if c.TaxID != nil { if isCodiceFiscale(c.TaxID) { da.CodiceFiscale = c.TaxID.Code.String() - } else if isEUCountry(c.TaxID.Country) { - da.IdFiscaleIVA = customerFiscaleIVA(c.TaxID, euCitizenTaxCodeDefault) } else { - da.IdFiscaleIVA = customerFiscaleIVA(c.TaxID, nonEUCitizenTaxCodeDefault) + da.IdFiscaleIVA = customerFiscaleIVA(c.TaxID) } } @@ -153,11 +151,17 @@ func newContatti(party *org.Party) *contatti { return c } -func customerFiscaleIVA(id *tax.Identity, fallBack string) *taxID { +func customerFiscaleIVA(id *tax.Identity) *taxID { idCodice := id.Code.String() if idCodice == "" { - idCodice = fallBack + // Assume private individual + idCodice = nonITCitizenTaxCodeDefault + } else { + // Must be a company with a local tax ID + if !isEUCountry(id.Country) { + idCodice = nonEUBusinessTaxCodeDefault + } } return &taxID{ diff --git a/parties_test.go b/parties_test.go index 614d4ad..9cdc4f4 100644 --- a/parties_test.go +++ b/parties_test.go @@ -116,7 +116,7 @@ func TestPartiesCustomer(t *testing.T) { assert.Equal(t, "0000000", c.DatiAnagrafici.IdFiscaleIVA.IdCodice) }) - t.Run("should contain customer info for non-EU citizen with Tax ID given", func(t *testing.T) { + t.Run("should replace customer ID info for non-EU citizen with Tax ID given", func(t *testing.T) { env := test.LoadTestFile("invoice-simple.json") test.ModifyInvoice(env, func(inv *bill.Invoice) { inv.Customer.TaxID.Code = "09823876432" @@ -129,7 +129,7 @@ func TestPartiesCustomer(t *testing.T) { c := doc.FatturaElettronicaHeader.CessionarioCommittente assert.Equal(t, "GB", c.DatiAnagrafici.IdFiscaleIVA.IdPaese) - assert.Equal(t, "09823876432", c.DatiAnagrafici.IdFiscaleIVA.IdCodice) + assert.Equal(t, "OO99999999999", c.DatiAnagrafici.IdFiscaleIVA.IdCodice) }) t.Run("should contain customer info for non-EU citizen with no Tax ID given", func(t *testing.T) { @@ -145,7 +145,7 @@ func TestPartiesCustomer(t *testing.T) { c := doc.FatturaElettronicaHeader.CessionarioCommittente assert.Equal(t, "JP", c.DatiAnagrafici.IdFiscaleIVA.IdPaese) - assert.Equal(t, "99999999999", c.DatiAnagrafici.IdFiscaleIVA.IdCodice) + assert.Equal(t, "0000000", c.DatiAnagrafici.IdFiscaleIVA.IdCodice) }) t.Run("should not fail if missing key data", func(t *testing.T) { diff --git a/test/data/invoice-hotel-private.json b/test/data/invoice-hotel-private.json new file mode 100644 index 0000000..3eb6030 --- /dev/null +++ b/test/data/invoice-hotel-private.json @@ -0,0 +1,141 @@ +{ + "$schema": "https://gobl.org/draft-0/envelope", + "head": { + "uuid": "679a2f25-7483-11ec-9722-7ea2cb436ff6", + "dig": { + "alg": "sha256", + "val": "AAAA" + } + }, + "doc": { + "$schema": "https://gobl.org/draft-0/bill/invoice", + "type": "standard", + "series": "SAMPLE", + "code": "003", + "issue_date": "2023-05-21", + "currency": "EUR", + "tax": { + "prices_include": "VAT" + }, + "supplier": { + "name": "Hotel California", + "tax_id": { + "country": "IT", + "code": "12345678903" + }, + "addresses": [ + { + "num": "102", + "street": "Via California", + "locality": "Palermo", + "region": "PA", + "code": "33213", + "country": "IT" + } + ], + "registration": { + "currency": "EUR", + "office": "RM", + "entry": "123456" + } + }, + "customer": { + "name": "Random Person", + "tax_id": { + "country": "GB" + }, + "addresses": [ + { + "num": "23", + "street": "Main Street", + "locality": "London", + "code": "W1 A2", + "country": "GB" + } + ] + }, + "lines": [ + { + "i": 1, + "quantity": "1", + "item": { + "name": "Tassa di Soggiorno", + "price": "1.00" + }, + "sum": "1.00", + "taxes": [ + { + "cat": "VAT", + "rate": "exempt", + "ext": { + "it-sdi-nature": "N1" + } + } + ], + "total": "1.00" + }, + { + "i": 2, + "quantity": "1", + "item": { + "name": "Camera Matrimoniale", + "price": "125.00" + }, + "sum": "125.00", + "taxes": [ + { + "cat": "VAT", + "rate": "intermediate", + "percent": "10.0%" + } + ], + "total": "125.00" + } + ], + "payment": { + "advances": [ + { + "date": "2023-05-01", + "key": "card", + "desc": "deposit", + "amount": "29.00" + } + ] + }, + "totals": { + "sum": "126.00", + "tax_included": "11.36", + "total": "114.64", + "taxes": { + "categories": [ + { + "code": "VAT", + "rates": [ + { + "key": "exempt", + "ext": { + "it-sdi-nature": "N1" + }, + "base": "1.00", + "amount": "0.00" + }, + { + "key": "intermediate", + "base": "113.6364", + "percent": "10.0%", + "amount": "11.3636" + } + ], + "amount": "11.3636" + } + ], + "sum": "11.3636" + }, + "tax": "11.36", + "total_with_tax": "126.00", + "payable": "126.00", + "advance": "29.00", + "due": "97.00" + } + } +}