Skip to content
Open
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
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module github.com/invopop/gobl.fatturapa
go 1.25.0

require (
github.com/invopop/gobl v0.503.0
github.com/invopop/gobl v0.504.0
github.com/invopop/xmlctx v0.13.0
github.com/invopop/xmldsig v0.10.0
github.com/lestrrat-go/libxml2 v0.0.0-20240905100032-c934e3fcb9d3
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/invopop/gobl v0.503.0 h1:i15dqw6OlHdEWZzCgvM7MquMKKzBq7K8VMF8MQUJhGo=
github.com/invopop/gobl v0.503.0/go.mod h1:HmiEdQreTSQYyNbhs81VKTmI7BAJKYC/6enh9RDwnE0=
github.com/invopop/gobl v0.504.0 h1:RLmZmeAk9RL4rkMUc7xQfC70/RGiBeekRwVgMsMthQA=
github.com/invopop/gobl v0.504.0/go.mod h1:HmiEdQreTSQYyNbhs81VKTmI7BAJKYC/6enh9RDwnE0=
github.com/invopop/jsonschema v0.14.0 h1:MHQqLhvpNUZfw+hM3AZDYK7jxO8FZoQeQM77g8iyZjg=
github.com/invopop/jsonschema v0.14.0/go.mod h1:ygm6C2EaVNMBDPpaPlnOA2pFAxBnxGjFlMZABxm9n2I=
github.com/invopop/xmlctx v0.13.0 h1:ZNRMC0O/A5h8InoLVSA7tIjjrhJn/NDBYfByBUpSb+g=
Expand Down
18 changes: 12 additions & 6 deletions items.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,11 @@ func generateLineDetails(inv *bill.Invoice) []*LineDetail {
}

// attributesToOtherData maps an item's attributes to AltriDatiGestionali blocks.
// The attribute's type (or key as a fallback) becomes the TipoDato, and its value
// goes to the reference field that matches: text and code to RiferimentoTesto,
// amounts to RiferimentoNumero, and dates to RiferimentoData.
// The attribute's type (or key as a fallback) becomes the TipoDato, and each
// value fills the reference field that matches: text and code to
// RiferimentoTesto, amounts to RiferimentoNumero, and dates to RiferimentoData.
// A block may carry several references at once, so an attribute holding more
// than one value still maps to a single block.
func attributesToOtherData(attrs []*org.Attribute) []*OtherData {
var out []*OtherData
for _, a := range attrs {
Expand All @@ -147,16 +149,20 @@ func attributesToOtherData(attrs []*org.Attribute) []*OtherData {
continue
}
od := &OtherData{DataType: name}
// Text and code share RiferimentoTesto, so only one of them fits.
switch {
case a.Text != "":
od.TextReference = a.Text
case a.Code != "":
od.TextReference = a.Code.String()
case a.Amount != nil:
}
if a.Amount != nil {
od.NumReference = formatAmount8(a.Amount)
case a.Date != nil:
}
if a.Date != nil {
od.DateReference = a.Date.String()
default:
}
if od.TextReference == "" && od.NumReference == "" && od.DateReference == "" {
// No value to map, so skip instead of emitting an empty block.
continue
}
Expand Down
38 changes: 17 additions & 21 deletions items_parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,9 +154,9 @@ func goblBillInvoiceAddLineDetails(inv *bill.Invoice, lineDetails []*LineDetail,
}

// otherDataToAttribute converts an AltriDatiGestionali block into a GOBL item
// attribute. TipoDato becomes the attribute type, and one of the reference
// fields becomes the value. A block may carry more than one reference, but an
// attribute holds a single value, so date wins over number, which wins over text.
// attribute. TipoDato becomes the attribute type, and each reference field
// becomes the value that matches it. A block may carry several references at
// once, and they all end up in the same attribute.
func otherDataToAttribute(od *OtherData) *org.Attribute {
if od == nil {
return nil
Expand All @@ -167,34 +167,30 @@ func otherDataToAttribute(od *OtherData) *org.Attribute {
if dataType == "" {
return nil
}
text := strings.TrimSpace(od.TextReference)
number := strings.TrimSpace(od.NumReference)
dateRef := strings.TrimSpace(od.DateReference)

a := &org.Attribute{Type: cbc.Code(dataType)}
if dateRef != "" {
if date, err := parseDate(dateRef); err == nil {
a.Date = &date
return a
}
a := &org.Attribute{
Type: cbc.Code(dataType),
Text: strings.TrimSpace(od.TextReference),
}
// A reference that doesn't parse is kept as text rather than dropped, but
// only while the text is still free.
if number != "" {
if amount, err := parseAmount(number); err == nil {
a.Amount = &amount
return a
} else if a.Text == "" {
a.Text = number
}
}
if text != "" {
a.Text = text
return a
if dateRef != "" {
if date, err := parseDate(dateRef); err == nil {
a.Date = &date
} else if a.Text == "" {
a.Text = dateRef
}
}
// Nothing parsed cleanly, so keep the raw value instead of dropping it.
switch {
case dateRef != "":
a.Text = dateRef
case number != "":
a.Text = number
default:
if a.Text == "" && a.Amount == nil && a.Date == nil {
return nil
}
return a
Expand Down
25 changes: 13 additions & 12 deletions items_parse_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,33 +31,34 @@ func TestOtherDataToAttribute(t *testing.T) {
assert.Equal(t, "not-a-number", a.Text)
})

t.Run("prefers the date over the number and the text", func(t *testing.T) {
t.Run("keeps every reference of a block in one attribute", func(t *testing.T) {
a := otherDataToAttribute(&OtherData{
DataType: "SCADENZA",
TextReference: "ABC-123",
NumReference: "12.50",
DateReference: "2024-03-15",
})
require.NotNil(t, a)
assert.Equal(t, "ABC-123", a.Text)
require.NotNil(t, a.Amount)
assert.Equal(t, "12.50", a.Amount.String())
require.NotNil(t, a.Date)
assert.Equal(t, "2024-03-15", a.Date.String())
assert.Nil(t, a.Amount)
assert.Empty(t, a.Text)
})

t.Run("prefers the number over the text", func(t *testing.T) {
t.Run("keeps both the text and the number", func(t *testing.T) {
a := otherDataToAttribute(&OtherData{
DataType: "PESO",
TextReference: "ABC-123",
NumReference: "12.50",
DataType: "CASSA-PREV",
TextReference: "ENASARCO TC07",
NumReference: "80.00",
})
require.NotNil(t, a)
assert.Equal(t, "ENASARCO TC07", a.Text)
require.NotNil(t, a.Amount)
assert.Equal(t, "12.50", a.Amount.String())
assert.Empty(t, a.Text)
assert.Equal(t, "80.00", a.Amount.String())
})

t.Run("falls back to the next reference when the preferred one is invalid", func(t *testing.T) {
t.Run("keeps the other references when the date is invalid", func(t *testing.T) {
a := otherDataToAttribute(&OtherData{
DataType: "SCADENZA",
TextReference: "ABC-123",
Expand All @@ -68,10 +69,10 @@ func TestOtherDataToAttribute(t *testing.T) {
assert.Nil(t, a.Date)
require.NotNil(t, a.Amount)
assert.Equal(t, "12.50", a.Amount.String())
assert.Empty(t, a.Text)
assert.Equal(t, "ABC-123", a.Text)
})

t.Run("falls back to the text when the number is also invalid", func(t *testing.T) {
t.Run("drops an unparseable reference when the text is already taken", func(t *testing.T) {
a := otherDataToAttribute(&OtherData{
DataType: "SCADENZA",
TextReference: "ABC-123",
Expand Down
46 changes: 46 additions & 0 deletions items_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,52 @@ func TestAltriDatiGestionaliAttributes(t *testing.T) {
assert.Equal(t, "2024-03-15", od[3].DateReference)
})

t.Run("should map every value of an attribute into a single block", func(t *testing.T) {
env := test.LoadTestFile("invoice-simple.json", test.PathGOBLFatturaPA)
date := cal.MakeDate(2025, 3, 10)
amount := num.MakeAmount(8000, 2)
test.ModifyInvoice(env, func(inv *bill.Invoice) {
inv.Lines[0].Item.Attributes = []*org.Attribute{
{Type: "INTENTO", Text: "08060120341234567-000001", Date: &date},
{Type: "CASSA-PREV", Text: "ENASARCO TC07", Amount: &amount},
}
})

doc, err := test.ConvertFromGOBL(env)
require.NoError(t, err)

od := doc.Body[0].GoodsServices.LineDetails[0].OtherData
require.Len(t, od, 2)

// Declaration of intent: protocol number and the AdE receipt date.
assert.Equal(t, "INTENTO", od[0].DataType)
assert.Equal(t, "08060120341234567-000001", od[0].TextReference)
assert.Equal(t, "2025-03-10", od[0].DateReference)
assert.Empty(t, od[0].NumReference)

// Pension fund: which fund and how much was contributed.
assert.Equal(t, "CASSA-PREV", od[1].DataType)
assert.Equal(t, "ENASARCO TC07", od[1].TextReference)
assert.Equal(t, "80.00", od[1].NumReference)
assert.Empty(t, od[1].DateReference)
})

t.Run("should prefer the text over the code, as both share RiferimentoTesto", func(t *testing.T) {
env := test.LoadTestFile("invoice-simple.json", test.PathGOBLFatturaPA)
test.ModifyInvoice(env, func(inv *bill.Invoice) {
inv.Lines[0].Item.Attributes = []*org.Attribute{
{Type: "COLORE", Text: "rosso", Code: "RAL5010"},
}
})

doc, err := test.ConvertFromGOBL(env)
require.NoError(t, err)

od := doc.Body[0].GoodsServices.LineDetails[0].OtherData
require.Len(t, od, 1)
assert.Equal(t, "rosso", od[0].TextReference)
})

t.Run("should fall back to the attribute key when no type is set", func(t *testing.T) {
env := test.LoadTestFile("invoice-simple.json", test.PathGOBLFatturaPA)
test.ModifyInvoice(env, func(inv *bill.Invoice) {
Expand Down
8 changes: 4 additions & 4 deletions payments.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ func prepareAdvancePaymentDetails(inv *bill.Invoice) []*PaymentDetailRow {
row.Date = advance.Date.String()
}
if advance.CreditTransfer != nil {
row.IBAN = advance.CreditTransfer.IBAN
row.BIC = advance.CreditTransfer.BIC
row.IBAN = advance.CreditTransfer.IBAN.String()
row.BIC = advance.CreditTransfer.BIC.String()
}
dp = append(dp, row)
}
Expand All @@ -96,8 +96,8 @@ func preparePaymentDetails(inv *bill.Invoice) []*PaymentDetailRow {
}
if len(payment.Instructions.CreditTransfer) > 0 {
ct1 := payment.Instructions.CreditTransfer[0]
br.IBAN = ct1.IBAN
br.BIC = ct1.BIC
br.IBAN = ct1.IBAN.String()
br.BIC = ct1.BIC.String()
br.FinancialInstitution = ct1.Name
}

Expand Down
4 changes: 2 additions & 2 deletions payments_parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,8 +181,8 @@ func goblBillPaymentAddPaymentInstructions(payment *bill.PaymentDetails, payment
// Add credit transfer if IBAN or BIC is available
if paymentDetail.IBAN != "" || paymentDetail.BIC != "" {
creditTransfer := pay.CreditTransfer{
IBAN: paymentDetail.IBAN,
BIC: paymentDetail.BIC,
IBAN: cbc.Code(paymentDetail.IBAN),
BIC: cbc.Code(paymentDetail.BIC),
}
if paymentDetail.FinancialInstitution != "" {
creditTransfer.Name = paymentDetail.FinancialInstitution
Expand Down
5 changes: 3 additions & 2 deletions payments_parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/invopop/gobl.fatturapa/test"
"github.com/invopop/gobl/bill"
"github.com/invopop/gobl/cbc"
"github.com/invopop/gobl/num"
"github.com/invopop/gobl/pay"
"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -70,8 +71,8 @@ func TestPaymentsInConversion(t *testing.T) {
// Check credit transfer
require.NotEmpty(t, invoice.Payment.Instructions.CreditTransfer)
creditTransfer := invoice.Payment.Instructions.CreditTransfer[0]
assert.Equal(t, "IT60X0542811101000000123456", creditTransfer.IBAN)
assert.Equal(t, "BCITITMM", creditTransfer.BIC)
assert.Equal(t, cbc.Code("IT60X0542811101000000123456"), creditTransfer.IBAN)
assert.Equal(t, cbc.Code("BCITITMM"), creditTransfer.BIC)
})

t.Run("should convert multiple due dates correctly", func(t *testing.T) {
Expand Down
5 changes: 3 additions & 2 deletions test/data/gobl.fatturapa/invoice-despatch.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": "2837c4f5ee910dd4e583548ba5ab67173ff09bcf4716037badb7e3e602c2463f"
"val": "69753172e2d48a904682d139f4536fa8128ad532268cec822d28d435ca00279f"
}
},
"doc": {
Expand Down Expand Up @@ -113,7 +113,8 @@
"attributes": [
{
"type": "LOTTO",
"text": "CEM-2025-07"
"text": "CEM-2025-07",
"date": "2027-07-01"
},
{
"type": "PESO",
Expand Down
5 changes: 3 additions & 2 deletions test/data/gobl.fatturapa/out/invoice-despatch.xml
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@
<AltriDatiGestionali>
<TipoDato>LOTTO</TipoDato>
<RiferimentoTesto>CEM-2025-07</RiferimentoTesto>
<RiferimentoData>2027-07-01</RiferimentoData>
</AltriDatiGestionali>
<AltriDatiGestionali>
<TipoDato>PESO</TipoDato>
Expand All @@ -124,7 +125,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>GW4pWcB8xBGrMF/XRtEJNqF8RcEVrn0ASbvWmDZUNajnCn9otIqzDVlS6eD7xzfSPCZCqMdpePYm02Igtybh6w==</ds:DigestValue>
<ds:DigestValue>/NREZsygXpAhAclOfhjNpF1lsdT6029lLYC5fhThcgbFdrxULgxBf4GNqZtclOv3djrAqLrLyeLhyyTnoyo71g==</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 @@ -135,7 +136,7 @@
<ds:DigestValue>7Tj/Vr2iDe5KuUvZfrT8ntgjkAtz6zIeztpC/liVkbigGbZLGFHcMpSsVtsRc+WIqqwsB7AwFVjqjSIKIDI3uw==</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue Id="Signature-679a2f25-7483-11ec-9722-7ea2cb436ff6-SignatureValue">WcSjD9nKBmb0XR+KI5OXziAxOCUOKJefDdcYhmuW4QvbT2YCJxfTndnQHTZxTRoUx7MJaSPqVdf5eJgH5vIyquxxjSmQlugQ63muof+1odMP35OFgmE1r05hav+kQLt67NsFX8rd1QoM16hqou/PqLeZd8LfJPJ2LqdN6BDfcGKyxX8U+/i0pwdfH76qyu+pJvzfab/jSckD3YNJ6SB3S5CXjA0QOOm0JEMfno3rZSjRfPhCbN5eXUNqdsoRGucnKwkMiUfzKM7LrAHqSHam39t7ZuJGTbS3AeRYAm0RcwI5zqAOeu9y8kNgpvqYpxNff7IDRUVYgH5tsPagg7tjfQ==</ds:SignatureValue>
<ds:SignatureValue Id="Signature-679a2f25-7483-11ec-9722-7ea2cb436ff6-SignatureValue">I2rwxC3/E/G7hSiQRYi8dZrVlRB3DOR9qOxapLkFHGawuUgMveGrma5FIl7w9EmzIuKFUN72iHdTnX+/+gogEiCgMfrOPvHaUPqOCh1hOqi/8avNscxMrNda1OT+/T75ZpXEtGBw2Fy50bRlXyktBwWd9qnkHuVYygtE/T25IAMZ+Hu32Fj1Hui7bLKvVu9VosLdrwJZzzrzv0SgSNcf5io7d14CNUJeSGjlzNRg1jqmVlbvi7JhGGF2DdkQV6QGbOxB2NLhqc5Uk9JRnQnZ+lN55gYESVMvYAMDqOn6OubRyxsZcgQlsI9TWdiw8b+9RrIEuWe/O0KdUrgITwhHSw==</ds:SignatureValue>
<ds:KeyInfo Id="Certificate-679a2f25-7483-11ec-9722-7ea2cb436ff6">
<ds:X509Data>
<ds:X509Certificate>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</ds:X509Certificate>
Expand Down
Loading