Skip to content

Commit e81cde1

Browse files
committed
feat(sweden): improve support for simplified invoices
1 parent b5de549 commit e81cde1

File tree

2 files changed

+98
-24
lines changed

2 files changed

+98
-24
lines changed

regimes/se/invoices.go

Lines changed: 55 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,27 @@ import (
1111
// invoiceValidator adds validation checks to invoices which are relevant
1212
// for the region.
1313
func validateInvoice(inv *bill.Invoice) error {
14+
if inv.Tags.HasTags(tax.TagSimplified) {
15+
// Simplified invoices only require a supplier tax ID.
16+
return validation.ValidateStruct(inv,
17+
validation.Field(&inv.Customer,
18+
validation.When(
19+
inv.Customer != nil,
20+
validation.Empty,
21+
),
22+
validation.Nil,
23+
validation.Skip,
24+
),
25+
validation.Field(&inv.Supplier,
26+
validation.Required,
27+
validation.By(validateSupplier),
28+
validation.By(validateSupplierSimplifiedInvoice),
29+
validation.Skip,
30+
),
31+
)
32+
}
33+
34+
// Standard invoices require a supplier and customer.
1435
return validation.ValidateStruct(inv,
1536
validation.Field(&inv.Supplier,
1637
validation.Required,
@@ -21,18 +42,19 @@ func validateInvoice(inv *bill.Invoice) error {
2142
validation.Field(&inv.Customer,
2243
validation.Required,
2344
validation.By(validateOrgParty),
24-
validation.By(validateCustomer),
2545
validation.Skip,
2646
),
2747
)
2848
}
2949

50+
// validateOrgParty holds the common checks for both the supplier and customer.
3051
func validateOrgParty(value any) error {
3152
party, ok := value.(*org.Party)
3253
if !ok || party == nil {
3354
return nil
3455
}
3556

57+
// Name and addresses are always required.
3658
return validation.ValidateStruct(party,
3759
validation.Field(&party.Name,
3860
validation.Required,
@@ -42,11 +64,31 @@ func validateOrgParty(value any) error {
4264
validation.Required,
4365
validation.Skip,
4466
),
67+
validation.Field(&party.Identities,
68+
// If the party is registered in Sweden for tax purposes,
69+
// then its identities must be one of the allowed types.
70+
validation.When(
71+
party.TaxID != nil && party.TaxID.Country == l10n.TaxCountryCode(l10n.SE),
72+
validation.Each(
73+
validation.By(func(value any) error {
74+
id, ok := value.(*org.Identity)
75+
if !ok || id == nil {
76+
return nil
77+
}
78+
if !id.Type.In(IdentityTypeOrgNr, IdentityTypePersonNr, IdentityTypeCoordinationNr) {
79+
return validation.NewError("type", "must be one of: SE-ON, SE-PN, SE-CN")
80+
}
81+
return nil
82+
}),
83+
),
84+
),
85+
validation.Skip,
86+
),
4587
)
4688
}
4789

48-
// validateSupplier checks the supplier's tax ID and organization number requirements.
49-
// The supplier's VAT number and ID Number are always required.
90+
// validateSupplier checks the supplier's tax ID requirements.
91+
// The supplier's VAT number is always required.
5092
func validateSupplier(value any) error {
5193
party, ok := value.(*org.Party)
5294
if !ok || party == nil {
@@ -58,7 +100,8 @@ func validateSupplier(value any) error {
58100
// and must have the correct format.
59101
validation.Field(&party.TaxID,
60102
validation.Required,
61-
validation.By(func(value interface{}) error {
103+
tax.RequireIdentityCode,
104+
validation.By(func(value any) error {
62105
tID, ok := value.(*tax.Identity)
63106
if !ok || tID == nil {
64107
return nil
@@ -67,35 +110,23 @@ func validateSupplier(value any) error {
67110
}),
68111
validation.Skip,
69112
),
70-
validation.Field(&party.Identities,
71-
validation.Required,
72-
validation.In(
73-
org.RequireIdentityType(IdentityTypeOrgNr),
74-
org.RequireIdentityType(IdentityTypePersonNr),
75-
org.RequireIdentityType(IdentityTypeCoordinationNr),
76-
),
77-
),
78113
)
79114
}
80115

81-
// validateCustomer checks the customer's tax ID and organization number requirements.
82-
// However, the customer may not include any identities, just a name and address.
83-
func validateCustomer(value any) error {
116+
func validateSupplierSimplifiedInvoice(value any) error {
84117
party, ok := value.(*org.Party)
85118
if !ok || party == nil {
86119
return nil
87120
}
88121

89122
return validation.ValidateStruct(party,
90-
validation.Field(&party.Identities,
91-
validation.When(
92-
party.TaxID != nil && party.TaxID.Country == l10n.TaxCountryCode(l10n.SE),
93-
validation.In(
94-
org.RequireIdentityType(IdentityTypeOrgNr),
95-
org.RequireIdentityType(IdentityTypePersonNr),
96-
org.RequireIdentityType(IdentityTypeCoordinationNr),
97-
),
98-
),
123+
validation.Field(&party.Name,
124+
validation.NilOrNotEmpty,
125+
validation.Skip,
126+
),
127+
validation.Field(&party.Addresses,
128+
validation.NilOrNotEmpty,
129+
validation.Skip,
99130
),
100131
)
101132
}

regimes/se/invoices_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"testing"
55

66
"github.com/invopop/gobl/bill"
7+
"github.com/invopop/gobl/cbc"
78
"github.com/invopop/gobl/currency"
89
"github.com/invopop/gobl/l10n"
910
"github.com/invopop/gobl/num"
@@ -84,6 +85,40 @@ func testInvoiceStandard(t *testing.T) *bill.Invoice {
8485
return i
8586
}
8687

88+
func testInvoiceSimplified(t *testing.T) *bill.Invoice {
89+
t.Helper()
90+
i := &bill.Invoice{
91+
Series: "TEST",
92+
Code: "0002",
93+
Currency: currency.SEK,
94+
Supplier: &org.Party{
95+
// This is required due to bill.Invoice.validateSupplier only.
96+
// In Sweden, simplified invoices only require a supplier tax ID.
97+
Name: "Simplified Supplier",
98+
TaxID: &tax.Identity{
99+
Country: l10n.TaxCountryCode(l10n.SE),
100+
Code: "556036079301",
101+
},
102+
},
103+
Lines: []*bill.Line{
104+
{
105+
Quantity: num.MakeAmount(1, 0),
106+
Item: &org.Item{
107+
Name: "Software Engineering Services",
108+
Price: num.NewAmount(40000, 2),
109+
Unit: org.UnitHour,
110+
},
111+
},
112+
},
113+
Tags: tax.Tags{
114+
List: []cbc.Key{
115+
tax.TagSimplified,
116+
},
117+
},
118+
}
119+
return i
120+
}
121+
87122
func TestInvoiceValidation(t *testing.T) {
88123
t.Parallel()
89124
t.Run("standard invoice", func(t *testing.T) {
@@ -169,4 +204,12 @@ func TestInvoiceValidation(t *testing.T) {
169204
require.NoError(t, inv.Calculate())
170205
require.NoError(t, inv.Validate())
171206
})
207+
208+
t.Run("simplified invoice", func(t *testing.T) {
209+
t.Parallel()
210+
inv := testInvoiceSimplified(t)
211+
inv.SetRegime("SE")
212+
require.NoError(t, inv.Calculate())
213+
require.NoError(t, inv.Validate())
214+
})
172215
}

0 commit comments

Comments
 (0)