Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mx RFC symbol fix #404

Closed
wants to merge 27 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
5bc7008
Initial Draft for DE Addon
Oct 11, 2024
49d930e
Supplier and Payment Means Validation
Oct 14, 2024
55acc87
Corrective Invoices, Rule References and Instructions
Oct 14, 2024
6e03fb7
Credit transfer & Direct debit instructions
Oct 14, 2024
6b8c2de
Cleaned code and fixes
Oct 14, 2024
866684b
Added Extensions and Initial Tests
Oct 14, 2024
04d1a8e
Added Extensions and Initial Tests
Oct 14, 2024
a083cab
Tax Combo tests
Oct 15, 2024
3cb34c1
Invoice tests
Oct 15, 2024
978cd87
Corrective Invoice Tests
Oct 15, 2024
07f83d9
Delivery Address Tests
Oct 15, 2024
c89eba7
Tax ID/Number Validation and Tests
Oct 15, 2024
be4ded1
Instruction Tests
Oct 15, 2024
c7f9083
Lint Fix
Oct 15, 2024
3508528
Fixed Validation.Skip and Instructions Testing, started work on Scena…
Oct 18, 2024
5ab5d84
Update Extensions and Scenarios
Oct 22, 2024
f52229d
Update Tags and Scenarios
Oct 22, 2024
e80e161
Final proposal for EN16931 addon and catalogues with XRechnung
samlown Oct 25, 2024
da3c6b1
Fixing linter issues
samlown Oct 25, 2024
d31742d
Updating CHANGELOG, extra test
samlown Oct 25, 2024
34c696b
CHANGELOG update
samlown Oct 25, 2024
c8f364b
Allowing extended tax rate keys
samlown Oct 25, 2024
e59a696
Additional test case for EN16931 rate key with reverse charge
samlown Oct 25, 2024
2777bb5
Defining additional export rates for EN16931
samlown Oct 25, 2024
8bbfb25
Refining charges, discounts, with keys, removing outlays
samlown Oct 30, 2024
3e42b81
MX: Fix RFC checks with symbols
samlown Oct 31, 2024
57ed5b5
Updating CHANGELOG
samlown Oct 31, 2024
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
33 changes: 33 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,39 @@ All notable changes to GOBL will be documented in this file.

The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). See also the [GOBL versions](https://docs.gobl.org/overview/versions) documentation site for more details.

## [Unreleased]

### Added

- `tax`: New "tax catalogues" used for defining extensions for specific standards.
- `iso`: catalogue created with `iso-schema-id` extensions.
- `untdid`: catalogue created with extensions: `untdid-document-type`, `untdid-payment-means`, `untdid-tax-category`, `untdid-allowance`, and `untdid-charge`.
- `eu-en16931-v2017`: addon for underlying support of the EN16931 semantic specifications.
- `de-xrechnung-v3`: addon with extra normalization for XRechnung specification in Germany.
- `pay`: Added `sepa` payment means key extension in main definition to be used with Credit Transfers and Direct Debit.
- `org`: `Identity` and `Inbox` support for extensions.
- `tax`: tags for `export` and `eea` (european economic area) for use with rates.
- `bill`: support for extensions in `Discount`, `Charge`, `LineDiscount`, and `LineCharge`.
- `bill`: specifically defined keys for Discounts and Charges.

### Changed

- `tax`: rate keys can now be extended, so `exempt+reverse-charge` will be accepted and may be used by addons to included additional codes.
- `tax`: Addons can now depend on other addons, whose keys will be automatically added during normalization.
- `cbc`: Code now allows `:` separator.

### Removed

- `pay`: UNTDID 4461 mappings from payment means table, now provided by catalogues
- `bill`: `Outlay` has been removed in favour of Charges, we've also not seen any evidence this field has been used.
- `bill`: `ref` field from discounts and charges in favour of `code`.
- `tax`: Regime `ChargeKeys` removed. Keys now provided in `bill` package.
- `it`: Charge keys no longer defined, no migration required, already supported.

### Fixed

- `mx`: Tax ID validation now correctly supports `&` and `Ñ` symbols in codes.

## [v0.203.0]

### Added
Expand Down
2 changes: 2 additions & 0 deletions addons/addons.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ package addons
import (
// Import all the addons to ensure they're ready to use.
_ "github.com/invopop/gobl/addons/co/dian"
_ "github.com/invopop/gobl/addons/de/xrechnung"
_ "github.com/invopop/gobl/addons/es/facturae"
_ "github.com/invopop/gobl/addons/es/tbai"
_ "github.com/invopop/gobl/addons/eu/en16931"
_ "github.com/invopop/gobl/addons/gr/mydata"
_ "github.com/invopop/gobl/addons/it/sdi"
_ "github.com/invopop/gobl/addons/mx/cfdi"
Expand Down
4 changes: 0 additions & 4 deletions addons/co/dian/invoices.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,6 @@ func validateInvoice(inv *bill.Invoice) error {
validation.Each(validation.By(validateInvoicePreceding(inv.Type))),
validation.Skip,
),
validation.Field(&inv.Outlays,
validation.Empty,
validation.Skip,
),
)
}

Expand Down
79 changes: 79 additions & 0 deletions addons/de/xrechnung/instructions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package xrechnung

import (
"github.com/invopop/gobl/pay"
"github.com/invopop/validation"
)

// ValidatePaymentInstructions validates the payment instructions according to the XRechnung standard
func validatePaymentInstructions(value interface{}) error {
instr, ok := value.(*pay.Instructions)
if !ok || instr == nil {
return nil
}
return validation.ValidateStruct(instr,
// BR-DE-23
validation.Field(&instr.CreditTransfer,
validation.When(
instr.Key.Has(pay.MeansKeyCreditTransfer),
validation.Required,
validation.Each(validation.By(validateCreditTransfer)),
),
validation.Skip,
),
// BR-DE-24
validation.Field(&instr.Card,
validation.When(
instr.Key.Has(pay.MeansKeyCard),
validation.Required,
),
validation.Skip,
),
// BR-DE-25
validation.Field(&instr.DirectDebit,
validation.When(
instr.Key.Has(pay.MeansKeyDirectDebit),
validation.Required,
validation.By(validateInstructionsDirectDebit),
validation.Skip,
),
),
)
}

func validateInstructionsDirectDebit(value interface{}) error {
dd, ok := value.(*pay.DirectDebit)
if !ok || dd == nil {
return nil
}
return validation.ValidateStruct(dd,
// BR-DE-29 - Changed to Peppol-EN16931-R061
validation.Field(&dd.Ref,
validation.Required,
),
// BR-DE-30
validation.Field(&dd.Creditor,
validation.Required,
),
// BR-DE-31
validation.Field(&dd.Account,
validation.Required,
),
)
}

// BR-DE-19
func validateCreditTransfer(value interface{}) error {
ct, ok := value.(*pay.CreditTransfer)
if ct == nil || !ok {
return nil
}
return validation.ValidateStruct(ct,
validation.Field(&ct.Number,
validation.When(
ct.IBAN == "",
validation.Required,
),
),
)
}
110 changes: 110 additions & 0 deletions addons/de/xrechnung/instructions_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package xrechnung_test

import (
"testing"

"github.com/invopop/gobl/bill"
"github.com/invopop/gobl/cbc"
"github.com/invopop/gobl/pay"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

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: "credit-transfer+sepa",
CreditTransfer: []*pay.CreditTransfer{
{
IBAN: "DE89370400440532013000",
BIC: "DEUTDEFF",
},
},
},
}
require.NoError(t, inv.Calculate())
assert.NoError(t, inv.Validate())
})

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: pay.MeansKeyCreditTransfer.With(pay.MeansKeySEPA),
CreditTransfer: []*pay.CreditTransfer{
{
BIC: "DEUTDEFF",
},
},
},
}
require.NoError(t, inv.Calculate())
err := inv.Validate()
assert.ErrorContains(t, err, "payment: (instructions: (credit_transfer: (0: (number: cannot be blank.).).).)")
})

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{},
},
}
require.NoError(t, inv.Calculate())
assert.NoError(t, inv.Validate())
})

t.Run("valid invoice with SEPA direct debit", func(t *testing.T) {
inv := invoiceTemplate(t)
inv.Payment = &bill.Payment{
Instructions: &pay.Instructions{
Key: "direct-debit+sepa",
DirectDebit: &pay.DirectDebit{
Ref: "MANDATE123",
Creditor: "DE98ZZZ09999999999",
Account: "DE89370400440532013000",
},
},
}
require.NoError(t, inv.Calculate())
assert.NoError(t, inv.Validate())
})

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: "direct-debit+sepa",
DirectDebit: &pay.DirectDebit{
Creditor: "DE98ZZZ09999999999",
Account: "DE89370400440532013000",
},
},
}
require.NoError(t, inv.Calculate())
err := inv.Validate()
assert.ErrorContains(t, err, "payment: (instructions: (direct_debit: (ref: cannot be blank.).).)")
})

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"),
},
}
require.NoError(t, inv.Calculate())
err := inv.Validate()
assert.ErrorContains(t, err, "payment: (instructions: (key: must be or start with a valid key.).)")
})
}
54 changes: 54 additions & 0 deletions addons/de/xrechnung/invoices.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package xrechnung

import (
"github.com/invopop/gobl/bill"
"github.com/invopop/gobl/catalogues/untdid"
"github.com/invopop/gobl/tax"
"github.com/invopop/validation"
)

// BR-DE-17 - restricted subset of UNTDID document type codes
var validInvoiceUNTDIDDocumentTypeValues = []tax.ExtValue{
"326", // Partial
"380", // Commercial
"384", // Corrected
"389", // Self-billed
"381", // Credit note
"875", // Partial construction invoice
"876", // Partial Final construction invoice
"877", // Final construction invoice
}

// validateInvoice validates the invoice according to the XRechnung standard
func validateInvoice(inv *bill.Invoice) error {
return validation.ValidateStruct(inv,
// BR-DE-17
validation.Field(&inv.Tax,
validation.By(validateInvoiceTax),
validation.Skip,
),
validation.Field(&inv.Preceding,
validation.When(
inv.Type.In(
bill.InvoiceTypeCorrective,
bill.InvoiceTypeCreditNote,
),
validation.Required,
),
validation.Skip,
),
)
}

func validateInvoiceTax(value any) error {
tx, ok := value.(*bill.Tax)
if !ok || tx == nil {
return nil
}
return validation.ValidateStruct(tx,
validation.Field(&tx.Ext,
tax.ExtensionsHasValues(untdid.ExtKeyTaxCategory, validInvoiceUNTDIDDocumentTypeValues...),
validation.Skip,
),
)
}
Loading
Loading