diff --git a/components/bill/line_support.go b/components/bill/line_support.go
index 9b6a3b7..aabcbb8 100644
--- a/components/bill/line_support.go
+++ b/components/bill/line_support.go
@@ -1,6 +1,7 @@
package bill
import (
+ "github.com/invopop/gobl.html/components/t"
"github.com/invopop/gobl/bill"
"github.com/invopop/gobl/cbc"
"github.com/invopop/gobl/org"
@@ -31,7 +32,7 @@ func prepareLineSupport(reg *tax.RegimeDef, lines []*bill.Line, dss []*bill.Disc
if len(l.Charges) > 0 {
ls.charges = true
}
- if l.Item.Unit != "" {
+ if t.ItemHasUnit(l.Item) {
ls.units = true
}
if l.Item.Price != nil {
diff --git a/components/bill/line_support_test.go b/components/bill/line_support_test.go
new file mode 100644
index 0000000..1e9c367
--- /dev/null
+++ b/components/bill/line_support_test.go
@@ -0,0 +1,22 @@
+package bill
+
+import (
+ "testing"
+
+ gbill "github.com/invopop/gobl/bill"
+ "github.com/invopop/gobl/org"
+ "github.com/invopop/gobl/tax"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestPrepareLineSupportUNTDIDUnit(t *testing.T) {
+ lines := []*gbill.Line{
+ {
+ Item: &org.Item{
+ Ext: tax.MakeExtensions().Set("untdid-unit", "E48"),
+ },
+ },
+ }
+
+ assert.True(t, prepareLineSupport(new(tax.RegimeDef), lines, nil, nil).units)
+}
diff --git a/components/bill/lines.templ b/components/bill/lines.templ
index c679fa2..36ebada 100644
--- a/components/bill/lines.templ
+++ b/components/bill/lines.templ
@@ -124,8 +124,8 @@ templ line(l *bill.Line, ls *lineSupport, st *subtotal) {
if ls.units {
- if l.Item.Unit != "" {
- { t.UnitName(ctx, l.Item.Unit) }
+ if t.ItemHasUnit(l.Item) {
+ { t.ItemUnitName(ctx, l.Item) }
} else {
}
@@ -223,8 +223,8 @@ templ breakdownLine(sl *bill.SubLine, ls *lineSupport, st *subtotal) {
|
if ls.units {
- if sl.Item.Unit != "" {
- { t.UnitName(ctx, sl.Item.Unit) }
+ if t.ItemHasUnit(sl.Item) {
+ { t.ItemUnitName(ctx, sl.Item) }
} else {
}
diff --git a/components/bill/lines_templ.go b/components/bill/lines_templ.go
index c45c76f..f7ac3c6 100644
--- a/components/bill/lines_templ.go
+++ b/components/bill/lines_templ.go
@@ -440,11 +440,11 @@ func line(l *bill.Line, ls *lineSupport, st *subtotal) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- if l.Item.Unit != "" {
+ if t.ItemHasUnit(l.Item) {
var templ_7745c5c3_Var12 string
- templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(t.UnitName(ctx, l.Item.Unit))
+ templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(t.ItemUnitName(ctx, l.Item))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/bill/lines.templ`, Line: 128, Col: 35}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/bill/lines.templ`, Line: 128, Col: 34}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
if templ_7745c5c3_Err != nil {
@@ -743,11 +743,11 @@ func breakdownLine(sl *bill.SubLine, ls *lineSupport, st *subtotal) templ.Compon
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- if sl.Item.Unit != "" {
+ if t.ItemHasUnit(sl.Item) {
var templ_7745c5c3_Var19 string
- templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(t.UnitName(ctx, sl.Item.Unit))
+ templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(t.ItemUnitName(ctx, sl.Item))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/bill/lines.templ`, Line: 227, Col: 36}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/bill/lines.templ`, Line: 227, Col: 35}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
if templ_7745c5c3_Err != nil {
diff --git a/components/t/units.go b/components/t/units.go
index 270b960..6c0c5ed 100644
--- a/components/t/units.go
+++ b/components/t/units.go
@@ -2,11 +2,15 @@ package t
import (
"context"
+ "fmt"
"github.com/invopop/ctxi18n/i18n"
+ "github.com/invopop/gobl/cbc"
"github.com/invopop/gobl/org"
)
+const untdidUnitExtension cbc.Key = "untdid-unit"
+
var unitDefs = func() map[org.Unit]*org.DefUnit {
m := make(map[org.Unit]*org.DefUnit, len(org.UnitDefinitions))
for i := range org.UnitDefinitions {
@@ -30,3 +34,27 @@ func UnitName(ctx context.Context, u org.Unit) string {
}
return i18n.T(ctx, "units."+string(u), i18n.Default(def.Name))
}
+
+// ItemUnitName provides the display name for an item's unit. When the item
+// contains a UNTDID unit extension, the code is appended to the GOBL unit
+// name, or used on its own if the item does not have a GOBL unit.
+func ItemUnitName(ctx context.Context, item *org.Item) string {
+ if item == nil {
+ return ""
+ }
+ name := UnitName(ctx, item.Unit)
+ code := item.Ext.Get(untdidUnitExtension).String()
+ if name == "" {
+ return code
+ }
+ if code == "" {
+ return name
+ }
+ return fmt.Sprintf("%s (%s)", name, code)
+}
+
+// ItemHasUnit reports whether an item has either a GOBL unit or a UNTDID unit
+// extension that can be displayed.
+func ItemHasUnit(item *org.Item) bool {
+ return item != nil && (item.Unit != "" || item.Ext.Has(untdidUnitExtension))
+}
diff --git a/components/t/units_test.go b/components/t/units_test.go
index 0a263d4..f275d2a 100644
--- a/components/t/units_test.go
+++ b/components/t/units_test.go
@@ -8,6 +8,7 @@ import (
ct "github.com/invopop/gobl.html/components/t"
srclocales "github.com/invopop/gobl.html/locales"
"github.com/invopop/gobl/org"
+ "github.com/invopop/gobl/tax"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -70,3 +71,47 @@ func TestUnitName(t *testing.T) {
}
})
}
+
+func TestItemUnitName(t *testing.T) {
+ locales := new(i18n.Locales)
+ require.NoError(t, locales.LoadWithDefault(srclocales.Content, "en"))
+ ctx := locales.Get("en").WithContext(context.Background())
+
+ tests := []struct {
+ name string
+ item *org.Item
+ want string
+ }{
+ {name: "nil item", want: ""},
+ {name: "empty item", item: &org.Item{}, want: ""},
+ {name: "GOBL unit", item: &org.Item{Unit: org.UnitKilogram}, want: "kg"},
+ {
+ name: "GOBL and UNTDID units",
+ item: &org.Item{
+ Unit: org.UnitKilogram,
+ Ext: tax.MakeExtensions().Set("untdid-unit", "KGM"),
+ },
+ want: "kg (KGM)",
+ },
+ {
+ name: "translated GOBL and UNTDID units",
+ item: &org.Item{
+ Unit: org.UnitHour,
+ Ext: tax.MakeExtensions().Set("untdid-unit", "HUR"),
+ },
+ want: "hours (HUR)",
+ },
+ {
+ name: "UNTDID unit only",
+ item: &org.Item{Ext: tax.MakeExtensions().Set("untdid-unit", "E48")},
+ want: "E48",
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ assert.Equal(t, test.want, ct.ItemUnitName(ctx, test.item))
+ assert.Equal(t, test.want != "", ct.ItemHasUnit(test.item))
+ })
+ }
+}
diff --git a/examples/fr-invoice-units.fr.json b/examples/fr-invoice-units.fr.json
index 9358442..549e6ce 100644
--- a/examples/fr-invoice-units.fr.json
+++ b/examples/fr-invoice-units.fr.json
@@ -4,7 +4,7 @@
"uuid": "8a51fd30-2a27-11ee-be56-0242ac120002",
"dig": {
"alg": "sha256",
- "val": "e588d036427eab5deef69c8855be9be469cb7a95f63be5f90a6ccb87d01926a5"
+ "val": "5169c45e6bb7dc8ba4e49f960ac5a2b0718beb4b0a6b258e90916aee1dabb63b"
}
},
"doc": {
@@ -155,7 +155,10 @@
"item": {
"name": "Café en grains",
"price": "9.80",
- "unit": "kg"
+ "unit": "kg",
+ "ext": {
+ "untdid-unit": "KGM"
+ }
},
"sum": "245.00",
"taxes": [
@@ -186,11 +189,32 @@
}
],
"total": "14.40"
+ },
+ {
+ "i": 8,
+ "quantity": "1",
+ "item": {
+ "name": "Prestation avec unité UNTDID",
+ "price": "10.00",
+ "ext": {
+ "untdid-unit": "E48"
+ }
+ },
+ "sum": "10.00",
+ "taxes": [
+ {
+ "cat": "VAT",
+ "key": "standard",
+ "rate": "general",
+ "percent": "20%"
+ }
+ ],
+ "total": "10.00"
}
],
"totals": {
- "sum": "4350.40",
- "total": "4350.40",
+ "sum": "4360.40",
+ "total": "4360.40",
"taxes": {
"categories": [
{
@@ -198,19 +222,19 @@
"rates": [
{
"key": "standard",
- "base": "4350.40",
+ "base": "4360.40",
"percent": "20%",
- "amount": "870.08"
+ "amount": "872.08"
}
],
- "amount": "870.08"
+ "amount": "872.08"
}
],
- "sum": "870.08"
+ "sum": "872.08"
},
- "tax": "870.08",
- "total_with_tax": "5220.48",
- "payable": "5220.48"
+ "tax": "872.08",
+ "total_with_tax": "5232.48",
+ "payable": "5232.48"
}
}
}
\ No newline at end of file
diff --git a/examples/out/fr-invoice-units.fr.html b/examples/out/fr-invoice-units.fr.html
index 3e18e47..7a08a58 100644
--- a/examples/out/fr-invoice-units.fr.html
+++ b/examples/out/fr-invoice-units.fr.html
@@ -299,7 +299,7 @@
25
|
- kg
+ kg (KGM)
|
€9,80
@@ -338,6 +338,33 @@
€14,40
|
+
+ |
+ 8
+ |
+
+
+
+ Prestation avec unité UNTDID
+
+
+ |
+
+ 1
+ |
+
+ E48
+ |
+
+ €10,00
+ |
+
+ 20%
+ |
+
+ €10,00
+ |
+
@@ -354,7 +381,7 @@
Somme
|
- €4.350,40
+ €4.360,40
|
@@ -362,7 +389,7 @@
Taxe
|
- €870,08
+ €872,08
|
@@ -370,7 +397,7 @@
Total à payer
|
- €5.220,48
+ €5.232,48
|
@@ -403,13 +430,13 @@
TVA
|
- €4.350,40
+ €4.360,40
|
20%
|
- €870,08
+ €872,08
|