Skip to content

Commit

Permalink
Update Tags and Scenarios
Browse files Browse the repository at this point in the history
  • Loading branch information
apardods committed Oct 22, 2024
1 parent 6f9d154 commit e449bfe
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 78 deletions.
17 changes: 17 additions & 0 deletions addons/de/xrechnung/extensions.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package xrechnung
import (
"github.com/invopop/gobl/cbc"
"github.com/invopop/gobl/i18n"
"github.com/invopop/gobl/pkg/here"
)

// ExtKeyTaxRate is the key for the tax rate extension in XRechnung
Expand All @@ -18,6 +19,14 @@ var extensions = []*cbc.KeyDefinition{
i18n.EN: "Tax Rate",
i18n.DE: "Steuersatz",
},
Desc: i18n.String{
i18n.EN: here.Doc(`
Code used to describe the applicable tax rate. Taken from the UNTDID 5305 code list.
`),
i18n.DE: here.Doc(`
Code verwendet um den anwendbaren Steuersatz zu beschreiben. Entnommen aus der UNTDID 5305 Code-Liste.
`),
},
Values: []*cbc.ValueDefinition{
{
Value: "S",
Expand Down Expand Up @@ -90,6 +99,14 @@ var extensions = []*cbc.KeyDefinition{
i18n.EN: "Document Type",
i18n.DE: "Dokumentenart",
},
Desc: i18n.String{
i18n.EN: here.Doc(`
Code used to describe the type of document.
`),
i18n.DE: here.Doc(`
Code verwendet um die Art des Dokuments zu beschreiben.
`),
},
Values: []*cbc.ValueDefinition{
{
Value: "326",
Expand Down
13 changes: 7 additions & 6 deletions addons/de/xrechnung/instructions.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ var validPaymentKeys = []cbc.Key{
}

// ValidatePaymentInstructions validates the payment instructions according to the XRechnung standard
func ValidatePaymentInstructions(value interface{}) error {
func validatePaymentInstructions(value interface{}) error {
instr, ok := value.(*pay.Instructions)
if !ok || instr == nil {
return nil
Expand All @@ -48,6 +48,7 @@ func ValidatePaymentInstructions(value interface{}) error {
validation.When(instr.Key == pay.MeansKeyCard,
validation.Required,
),
validation.Skip,
),
// BR-DE-25
validation.Field(&instr.DirectDebit,
Expand All @@ -66,7 +67,7 @@ func validatePaymentKey(value interface{}) error {
return validation.NewError("invalid_key", "invalid payment key")
}
if !t.In(validPaymentKeys...) {
return validation.NewError("invalid", "Invalid payment key")
return validation.NewError("invalid", "invalid payment key")
}
return nil
}
Expand All @@ -79,15 +80,15 @@ func validateDirectDebit(value interface{}) error {
return validation.ValidateStruct(dd,
// BR-DE-29 - Changed to Peppol-EN16931-R061
validation.Field(&dd.Ref,
validation.Required.Error("Mandate reference is mandatory for direct debit"),
validation.Required,
),
// BR-DE-30
validation.Field(&dd.Creditor,
validation.Required.Error("Creditor identifier is mandatory for direct debit"),
validation.Required,
),
// BR-DE-31
validation.Field(&dd.Account,
validation.Required.Error("Debited account identifier is mandatory for direct debit"),
validation.Required,
),
)
}
Expand All @@ -101,7 +102,7 @@ func validateCreditTransfer(value interface{}) error {
return validation.ValidateStruct(creditTransfer,
validation.Field(&creditTransfer.Number,
validation.When(creditTransfer.IBAN == "",
validation.Required.Error("IBAN must be provided for SEPA credit transfer"),
validation.Required,
),
),
)
Expand Down
106 changes: 66 additions & 40 deletions addons/de/xrechnung/instructions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,72 +4,98 @@ import (
"testing"

"github.com/invopop/gobl/addons/de/xrechnung"
"github.com/invopop/gobl/bill"
"github.com/invopop/gobl/cbc"
"github.com/invopop/gobl/pay"
"github.com/stretchr/testify/assert"
)

func TestPaymentInstructions(t *testing.T) {
t.Run("valid SEPA credit transfer", func(t *testing.T) {
instr := &pay.Instructions{
Key: xrechnung.KeyPaymentMeansSEPACreditTransfer,
CreditTransfer: []*pay.CreditTransfer{
{
IBAN: "DE89370400440532013000",
BIC: "DEUTDEFF",
func invoiceTemplate(t *testing.T) *bill.Invoice {
t.Helper()
inv := testInvoiceStandard(t)
inv.Payment = nil
return inv
}

func TestValidateInvoice(t *testing.T) {
t.Run("valid invoice with SEPA credit transfer", func(t *testing.T) {
inv := invoiceTemplate(t)
inv.Payment = &bill.Payment{
Instructions: &pay.Instructions{
Key: cbc.Key("sepa-credit-transfer"),
CreditTransfer: []*pay.CreditTransfer{
{
IBAN: "DE89370400440532013000",
BIC: "DEUTDEFF",
},
},
},
}
assert.NoError(t, xrechnung.ValidatePaymentInstructions(instr))
assert.NoError(t, xrechnung.ValidateInvoice(inv))
})

t.Run("missing IBAN for SEPA credit transfer", func(t *testing.T) {
instr := &pay.Instructions{
Key: xrechnung.KeyPaymentMeansSEPACreditTransfer,
CreditTransfer: []*pay.CreditTransfer{
{
BIC: "DEUTDEFF",
t.Run("invalid invoice with missing IBAN for SEPA credit transfer", func(t *testing.T) {
inv := invoiceTemplate(t)
inv.Payment = &bill.Payment{
Instructions: &pay.Instructions{
Key: xrechnung.KeyPaymentMeansSEPACreditTransfer,
CreditTransfer: []*pay.CreditTransfer{
{
BIC: "DEUTDEFF",
},
},
},
}
assert.Error(t, xrechnung.ValidatePaymentInstructions(instr))
assert.Error(t, xrechnung.ValidateInvoice(inv))
})

t.Run("valid card payment", func(t *testing.T) {
instr := &pay.Instructions{
Key: pay.MeansKeyCard,
Card: &pay.Card{},
t.Run("valid invoice with card payment", func(t *testing.T) {
inv := invoiceTemplate(t)
inv.Payment = &bill.Payment{
Instructions: &pay.Instructions{
Key: pay.MeansKeyCard,
Card: &pay.Card{},
},
}
assert.NoError(t, xrechnung.ValidatePaymentInstructions(instr))
assert.NoError(t, xrechnung.ValidateInvoice(inv))
})

t.Run("valid SEPA direct debit", func(t *testing.T) {
instr := &pay.Instructions{
Key: xrechnung.KeyPaymentMeansSEPADirectDebit,
DirectDebit: &pay.DirectDebit{
Ref: "MANDATE123",
Creditor: "DE98ZZZ09999999999",
Account: "DE89370400440532013000",
t.Run("valid invoice with SEPA direct debit", func(t *testing.T) {
inv := invoiceTemplate(t)
inv.Payment = &bill.Payment{
Instructions: &pay.Instructions{
Key: xrechnung.KeyPaymentMeansSEPADirectDebit,
DirectDebit: &pay.DirectDebit{
Ref: "MANDATE123",
Creditor: "DE98ZZZ09999999999",
Account: "DE89370400440532013000",
},
},
}
assert.NoError(t, xrechnung.ValidatePaymentInstructions(instr))
assert.NoError(t, xrechnung.ValidateInvoice(inv))
})

t.Run("missing mandate reference for direct debit", func(t *testing.T) {
instr := &pay.Instructions{
Key: xrechnung.KeyPaymentMeansSEPADirectDebit,
DirectDebit: &pay.DirectDebit{
Creditor: "DE98ZZZ09999999999",
Account: "DE89370400440532013000",
t.Run("invalid invoice with missing mandate reference for direct debit", func(t *testing.T) {
inv := invoiceTemplate(t)
inv.Payment = &bill.Payment{
Instructions: &pay.Instructions{
Key: xrechnung.KeyPaymentMeansSEPADirectDebit,
DirectDebit: &pay.DirectDebit{
Creditor: "DE98ZZZ09999999999",
Account: "DE89370400440532013000",
},
},
}
assert.Error(t, xrechnung.ValidatePaymentInstructions(instr))
assert.Error(t, xrechnung.ValidateInvoice(inv))
})

t.Run("invalid payment key", func(t *testing.T) {
instr := &pay.Instructions{
Key: cbc.Key("invalid-key"),
t.Run("invalid invoice with invalid payment key", func(t *testing.T) {
inv := invoiceTemplate(t)
inv.Payment = &bill.Payment{
Instructions: &pay.Instructions{
Key: cbc.Key("invalid-key"),
},
}
assert.Error(t, xrechnung.ValidatePaymentInstructions(instr))
assert.Error(t, xrechnung.ValidateInvoice(inv))
})
}
64 changes: 36 additions & 28 deletions addons/de/xrechnung/invoices.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,36 +19,19 @@ func ValidateInvoice(inv *bill.Invoice) error {
// BR-DE-17
validation.Field(&inv.Type,
validation.By(validateInvoiceType),
validation.Skip,
),
// BR-DE-01
validation.Field(&inv.Payment, validation.Required),
validation.Field(&inv.Payment,
validation.By(func(value interface{}) error {
payment, ok := value.(*bill.Payment)
if !ok || payment == nil {
return validation.NewError("payment_type", "must be a valid non-empty Payment type")
}
return validation.ValidateStruct(payment,
validation.Field(&payment.Instructions,
validation.Required,
validation.By(ValidatePaymentInstructions),
validation.Skip,
),
)
}),
validation.Required,
validation.By(validatePayment),
validation.Skip,
),
// BR-DE-15
validation.Field(&inv.Ordering, validation.Required),
validation.Field(&inv.Ordering,
validation.By(func(value interface{}) error {
ordering, ok := value.(*bill.Ordering)
if !ok || ordering == nil {
return validation.NewError("ordering_type", "must be a valid Ordering type")
}
return validation.ValidateStruct(ordering,
validation.Field(&ordering.Code, validation.Required),
)
}),
validation.Required,
validation.By(validateOrdering),
validation.Skip,
),
validation.Field(&inv.Supplier,
validation.By(validateSupplier),
Expand All @@ -75,13 +58,38 @@ func ValidateInvoice(inv *bill.Invoice) error {
)
}

func validatePayment(value interface{}) error {
payment, ok := value.(*bill.Payment)
if !ok || payment == nil {
return nil
}
return validation.ValidateStruct(payment,
validation.Field(&payment.Instructions,
validation.Required,
validation.By(validatePaymentInstructions),
),
)
}

func validateOrdering(value interface{}) error {
ordering, ok := value.(*bill.Ordering)
if !ok || ordering == nil {
return nil
}
return validation.ValidateStruct(ordering,
validation.Field(&ordering.Code,
validation.Required,
),
)
}

func validateInvoiceType(value interface{}) error {
t, ok := value.(cbc.Key)
if !ok {
return validation.NewError("type", "Invalid invoice type")
return validation.NewError("type", "invalid invoice type")
}
if !t.In(validTypes...) {
return validation.NewError("invalid", "Invalid invoice type")
return validation.NewError("invalid", "invalid invoice type")
}
return nil
}
Expand Down Expand Up @@ -142,10 +150,10 @@ func validateSupplierTaxInfo(value interface{}) error {
func validateTaxNumber(value interface{}) error {
identities, ok := value.([]*org.Identity)
if !ok {
return validation.NewError("invalid_identities", "Identities are invalid")
return validation.NewError("invalid_identities", "identities are invalid")
}
if org.IdentityForKey(identities, "de-tax-number") == nil {
return validation.NewError("missing_tax_identifier", "German tax identifier (de-tax-number) is required")
return validation.NewError("missing_tax_identifier", "tax identifier (de-tax-number) is required")
}
return nil
}
Expand Down
9 changes: 5 additions & 4 deletions addons/de/xrechnung/scenarios.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ const (

// Invoice type constants
const (
invoiceTypeSelfBilled = "380"
invoiceTypeSelfBilled = "389"
invoiceTypePartial = "326"
invoiceTypePartialConstruction = "80"
invoiceTypePartialFinalConstruction = "84"
invoiceTypeFinalConstruction = "389"
invoiceTypePartialConstruction = "875"
invoiceTypePartialFinalConstruction = "876"
invoiceTypeFinalConstruction = "877"
)

var invoiceTags = &tax.TagSet{
Expand Down Expand Up @@ -150,6 +150,7 @@ var scenarios = []*tax.ScenarioSet{
ExtKeyTaxRate: "AE",
},
},
// TODO: Map Scenarios
{
Ext: tax.Extensions{
ExtKeyTaxRate: "K",
Expand Down
4 changes: 4 additions & 0 deletions addons/de/xrechnung/xrechnung.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ func newAddon() *tax.AddonDef {
https://www.xrechnung.de/
`),
},
Tags: []*tax.TagSet{
invoiceTags,
},
Scenarios: scenarios,
Extensions: extensions,
Normalizer: normalize,
Validator: validate,
Expand Down

0 comments on commit e449bfe

Please sign in to comment.