From fbf65d99d69fbdfd3fab581cd964974ed7362b93 Mon Sep 17 00:00:00 2001 From: apardods Date: Fri, 18 Oct 2024 15:39:37 +0000 Subject: [PATCH] Fixed Validation.Skip and Instructions Testing, started work on Scenarios --- addons/de/xrechnung/extensions.go | 49 ++++++++++- addons/de/xrechnung/instructions.go | 11 +-- addons/de/xrechnung/instructions_test.go | 91 ++++++++------------- addons/de/xrechnung/invoices.go | 25 +++--- addons/de/xrechnung/scenarios.go | 100 +++++++++++++++++++++++ 5 files changed, 198 insertions(+), 78 deletions(-) create mode 100644 addons/de/xrechnung/scenarios.go diff --git a/addons/de/xrechnung/extensions.go b/addons/de/xrechnung/extensions.go index 8b63dbf5..cb4c92f7 100644 --- a/addons/de/xrechnung/extensions.go +++ b/addons/de/xrechnung/extensions.go @@ -6,7 +6,10 @@ import ( ) // ExtKeyTaxRate is the key for the tax rate extension in XRechnung -var ExtKeyTaxRate cbc.Key = "de-xrechnung-tax-rate" +const ( + ExtKeyTaxRate cbc.Key = "de-xrechnung-tax-rate" + ExtKeyDocType cbc.Key = "de-xrechnung-doc-type" +) var extensions = []*cbc.KeyDefinition{ { @@ -81,4 +84,48 @@ var extensions = []*cbc.KeyDefinition{ }, }, }, + { + Key: ExtKeyDocType, + Name: i18n.String{ + i18n.EN: "Document Type", + i18n.DE: "Dokumentenart", + }, + Values: []*cbc.ValueDefinition{ + { + Value: string(invoiceTypeSelfBilled), + Name: i18n.String{ + i18n.EN: "Self-Billed Invoice", + i18n.DE: "Gutschrift", + }, + }, + { + Value: string(invoiceTypePartial), + Name: i18n.String{ + i18n.EN: "Partial Invoice", + i18n.DE: "Teilrechnung", + }, + }, + { + Value: string(invoiceTypePartialConstruction), + Name: i18n.String{ + i18n.EN: "Partial Construction Invoice", + i18n.DE: "Teilrechnung für Bauleistungen", + }, + }, + { + Value: string(invoiceTypePartialFinalConstruction), + Name: i18n.String{ + i18n.EN: "Partial Final Construction Invoice", + i18n.DE: "Schlussrechnung für Bauleistungen mit Teilrechnungen", + }, + }, + { + Value: string(invoiceTypeFinalConstruction), + Name: i18n.String{ + i18n.EN: "Final Construction Invoice", + i18n.DE: "Schlussrechnung für Bauleistungen", + }, + }, + }, + }, } diff --git a/addons/de/xrechnung/instructions.go b/addons/de/xrechnung/instructions.go index 2d4ac913..13b9bf91 100644 --- a/addons/de/xrechnung/instructions.go +++ b/addons/de/xrechnung/instructions.go @@ -1,7 +1,6 @@ package xrechnung import ( - "github.com/invopop/gobl/bill" "github.com/invopop/gobl/cbc" "github.com/invopop/gobl/pay" "github.com/invopop/validation" @@ -26,15 +25,15 @@ var validPaymentKeys = []cbc.Key{ // ValidatePaymentInstructions validates the payment instructions according to the XRechnung standard func ValidatePaymentInstructions(value interface{}) error { - inv, ok := value.(*bill.Invoice) - if !ok || inv == nil || inv.Payment == nil || inv.Payment.Instructions == nil { + instr, ok := value.(*pay.Instructions) + if !ok || instr == nil { return nil } - instr := inv.Payment.Instructions return validation.ValidateStruct(instr, validation.Field(&instr.Key, validation.Required, validation.By(validatePaymentKey), + validation.Skip, ), // BR-DE-23 validation.Field(&instr.CreditTransfer, @@ -42,6 +41,7 @@ func ValidatePaymentInstructions(value interface{}) error { validation.Required, validation.Each(validation.By(validateCreditTransfer)), ), + validation.Skip, ), // BR-DE-24 validation.Field(&instr.Card, @@ -54,6 +54,7 @@ func ValidatePaymentInstructions(value interface{}) error { validation.When(instr.Key == KeyPaymentMeansSEPADirectDebit || instr.Key == pay.MeansKeyDirectDebit, validation.Required, validation.By(validateDirectDebit), + validation.Skip, ), ), ) @@ -62,7 +63,7 @@ func ValidatePaymentInstructions(value interface{}) error { func validatePaymentKey(value interface{}) error { t, ok := value.(cbc.Key) if !ok { - return validation.NewError("type", "Invalid payment key") + return validation.NewError("invalid_key", "invalid payment key") } if !t.In(validPaymentKeys...) { return validation.NewError("invalid", "Invalid payment key") diff --git a/addons/de/xrechnung/instructions_test.go b/addons/de/xrechnung/instructions_test.go index 92e95d71..9d208944 100644 --- a/addons/de/xrechnung/instructions_test.go +++ b/addons/de/xrechnung/instructions_test.go @@ -4,7 +4,6 @@ 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" @@ -12,89 +11,65 @@ import ( func TestPaymentInstructions(t *testing.T) { t.Run("valid SEPA credit transfer", func(t *testing.T) { - inv := &bill.Invoice{ - Payment: &bill.Payment{ - Instructions: &pay.Instructions{ - Key: xrechnung.KeyPaymentMeansSEPACreditTransfer, - CreditTransfer: []*pay.CreditTransfer{ - { - IBAN: "DE89370400440532013000", - BIC: "DEUTDEFF", - }, - }, + instr := &pay.Instructions{ + Key: xrechnung.KeyPaymentMeansSEPACreditTransfer, + CreditTransfer: []*pay.CreditTransfer{ + { + IBAN: "DE89370400440532013000", + BIC: "DEUTDEFF", }, }, } - assert.NoError(t, xrechnung.ValidatePaymentInstructions(inv)) + assert.NoError(t, xrechnung.ValidatePaymentInstructions(instr)) }) t.Run("missing IBAN for SEPA credit transfer", func(t *testing.T) { - inv := &bill.Invoice{ - Payment: &bill.Payment{ - Instructions: &pay.Instructions{ - Key: xrechnung.KeyPaymentMeansSEPACreditTransfer, - CreditTransfer: []*pay.CreditTransfer{ - { - BIC: "DEUTDEFF", - }, - }, + instr := &pay.Instructions{ + Key: xrechnung.KeyPaymentMeansSEPACreditTransfer, + CreditTransfer: []*pay.CreditTransfer{ + { + BIC: "DEUTDEFF", }, }, } - assert.Error(t, xrechnung.ValidatePaymentInstructions(inv)) + assert.Error(t, xrechnung.ValidatePaymentInstructions(instr)) }) t.Run("valid card payment", func(t *testing.T) { - inv := &bill.Invoice{ - Payment: &bill.Payment{ - Instructions: &pay.Instructions{ - Key: pay.MeansKeyCard, - Card: &pay.Card{}, - }, - }, + instr := &pay.Instructions{ + Key: pay.MeansKeyCard, + Card: &pay.Card{}, } - assert.NoError(t, xrechnung.ValidatePaymentInstructions(inv)) + assert.NoError(t, xrechnung.ValidatePaymentInstructions(instr)) }) t.Run("valid SEPA direct debit", func(t *testing.T) { - inv := &bill.Invoice{ - Payment: &bill.Payment{ - Instructions: &pay.Instructions{ - Key: xrechnung.KeyPaymentMeansSEPADirectDebit, - DirectDebit: &pay.DirectDebit{ - Ref: "MANDATE123", - Creditor: "DE98ZZZ09999999999", - Account: "DE89370400440532013000", - }, - }, + instr := &pay.Instructions{ + Key: xrechnung.KeyPaymentMeansSEPADirectDebit, + DirectDebit: &pay.DirectDebit{ + Ref: "MANDATE123", + Creditor: "DE98ZZZ09999999999", + Account: "DE89370400440532013000", }, } - assert.NoError(t, xrechnung.ValidatePaymentInstructions(inv)) + assert.NoError(t, xrechnung.ValidatePaymentInstructions(instr)) }) t.Run("missing mandate reference for direct debit", func(t *testing.T) { - inv := &bill.Invoice{ - Payment: &bill.Payment{ - Instructions: &pay.Instructions{ - Key: xrechnung.KeyPaymentMeansSEPADirectDebit, - DirectDebit: &pay.DirectDebit{ - Creditor: "DE98ZZZ09999999999", - Account: "DE89370400440532013000", - }, - }, + instr := &pay.Instructions{ + Key: xrechnung.KeyPaymentMeansSEPADirectDebit, + DirectDebit: &pay.DirectDebit{ + Creditor: "DE98ZZZ09999999999", + Account: "DE89370400440532013000", }, } - assert.Error(t, xrechnung.ValidatePaymentInstructions(inv)) + assert.Error(t, xrechnung.ValidatePaymentInstructions(instr)) }) t.Run("invalid payment key", func(t *testing.T) { - inv := &bill.Invoice{ - Payment: &bill.Payment{ - Instructions: &pay.Instructions{ - Key: cbc.Key("invalid-key"), - }, - }, + instr := &pay.Instructions{ + Key: cbc.Key("invalid-key"), } - assert.Error(t, xrechnung.ValidatePaymentInstructions(inv)) + assert.Error(t, xrechnung.ValidatePaymentInstructions(instr)) }) } diff --git a/addons/de/xrechnung/invoices.go b/addons/de/xrechnung/invoices.go index 7b9c451d..e768d7e0 100644 --- a/addons/de/xrechnung/invoices.go +++ b/addons/de/xrechnung/invoices.go @@ -7,23 +7,10 @@ import ( "github.com/invopop/validation" ) -const ( - invoiceTypeSelfBilled cbc.Key = "self-billed" - invoiceTypePartial cbc.Key = "partial" - invoiceTypePartialConstruction cbc.Key = "partial-construction" - invoiceTypePartialFinalConstruction cbc.Key = "partial-final-construction" - invoiceTypeFinalConstruction cbc.Key = "final-construction" -) - var validTypes = []cbc.Key{ bill.InvoiceTypeStandard, bill.InvoiceTypeCreditNote, bill.InvoiceTypeCorrective, - invoiceTypeSelfBilled, - invoiceTypePartial, - invoiceTypePartialConstruction, - invoiceTypePartialFinalConstruction, - invoiceTypeFinalConstruction, } // ValidateInvoice validates the invoice according to the XRechnung standard @@ -45,6 +32,7 @@ func ValidateInvoice(inv *bill.Invoice) error { validation.Field(&payment.Instructions, validation.Required, validation.By(ValidatePaymentInstructions), + validation.Skip, ), ) }), @@ -64,15 +52,19 @@ func ValidateInvoice(inv *bill.Invoice) error { ), validation.Field(&inv.Supplier, validation.By(validateSupplier), + validation.Skip, ), validation.Field(&inv.Supplier, validation.By(validateSupplierTaxInfo), + validation.Skip, ), validation.Field(&inv.Customer, validation.By(validateCustomerReceiver), + validation.Skip, ), validation.Field(&inv.Delivery, validation.By(validateDelivery), + validation.Skip, ), // BR-DE-26 validation.Field(&inv.Preceding, @@ -108,6 +100,7 @@ func validateSupplier(value interface{}) error { validation.Field(&p.Addresses, validation.Required, validation.Each(validation.By(validatePartyAddress)), + validation.Skip, ), // BR-DE-06 validation.Field(&p.People, @@ -140,6 +133,7 @@ func validateSupplierTaxInfo(value interface{}) error { validation.When(supplier.TaxID == nil || supplier.TaxID.Code == "", validation.Required, validation.By(validateTaxNumber), + validation.Skip, ), ), ) @@ -163,7 +157,9 @@ func validateDelivery(value interface{}) error { } return validation.ValidateStruct(d, validation.Field(&d.Receiver, - validation.By(validateCustomerReceiver)), + validation.By(validateCustomerReceiver), + validation.Skip, + ), ) } @@ -179,6 +175,7 @@ func validateCustomerReceiver(value interface{}) error { validation.Field(&p.Addresses, validation.Required, validation.Each(validation.By(validatePartyAddress)), + validation.Skip, ), ) } diff --git a/addons/de/xrechnung/scenarios.go b/addons/de/xrechnung/scenarios.go new file mode 100644 index 00000000..ace8f08c --- /dev/null +++ b/addons/de/xrechnung/scenarios.go @@ -0,0 +1,100 @@ +package xrechnung + +import ( + "github.com/invopop/gobl/bill" + "github.com/invopop/gobl/cbc" + "github.com/invopop/gobl/tax" +) + +var scenarios = []*tax.ScenarioSet{ + { + Schema: bill.ShortSchemaInvoice, + List: []*tax.Scenario{ + // ** Invoice Document Types ** + { + Types: []cbc.Key{ + bill.InvoiceTypeStandard, + bill.InvoiceTypeCorrective, + bill.InvoiceTypeCreditNote, + bill.InvoiceTypeDebitNote, + }, + }, + { + Tags: []cbc.Key{ + tax.TagSelfBilled, + }, + Ext: tax.Extensions{ + ExtKeyDocType: tax.ExtValue(invoiceTypeSelfBilled), + }, + }, + { + Tags: []cbc.Key{ + tax.TagPartial, + }, + Ext: tax.Extensions{ + ExtKeyDocType: tax.ExtValue(invoiceTypePartial), + }, + }, + { + Tags: []cbc.Key{ + tax.TagPartial, + }, + Ext: tax.Extensions{ + ExtKeyDocType: tax.ExtValue(invoiceTypePartialConstruction), + }, + }, + { + Tags: []cbc.Key{ + tax.TagPartial, + }, + Ext: tax.Extensions{ + ExtKeyDocType: tax.ExtValue(invoiceTypePartialFinalConstruction), + }, + }, + { + Ext: tax.Extensions{ + ExtKeyDocType: tax.ExtValue(invoiceTypeFinalConstruction), + }, + }, + // ** Tax Rates ** + { + Ext: tax.Extensions{ + ExtKeyTaxRate: "S", + }, + }, + { + Ext: tax.Extensions{ + ExtKeyTaxRate: "Z", + }, + }, + { + Ext: tax.Extensions{ + ExtKeyTaxRate: "E", + }, + }, + { + Tags: []cbc.Key{ + tax.TagReverseCharge, + }, + Ext: tax.Extensions{ + ExtKeyTaxRate: "AE", + }, + }, + { + Ext: tax.Extensions{ + ExtKeyTaxRate: "K", + }, + }, + { + Ext: tax.Extensions{ + ExtKeyTaxRate: "G", + }, + }, + { + Ext: tax.Extensions{ + ExtKeyTaxRate: "O", + }, + }, + }, + }, +}