From 8bbfb2516c7cd71ecf21db5250e390fceda654fc Mon Sep 17 00:00:00 2001 From: Sam Lown Date: Wed, 30 Oct 2024 22:15:49 +0000 Subject: [PATCH] Refining charges, discounts, with keys, removing outlays --- CHANGELOG.md | 13 +- addons/co/dian/invoices.go | 4 - addons/eu/en16931/bill.go | 65 + addons/eu/en16931/bill_test.go | 84 ++ addons/eu/en16931/en16931.go | 8 + bill/charges.go | 129 +- bill/discounts.go | 139 +- bill/invoice.go | 15 +- bill/invoice_convert.go | 17 - bill/invoice_convert_test.go | 11 +- bill/invoice_test.go | 20 +- bill/line.go | 2 + bill/line_charge.go | 52 + bill/line_charge_test.go | 59 + bill/line_discount.go | 50 + bill/line_discount_test.go | 59 + bill/ordering.go | 6 +- bill/outlay.go | 78 -- bill/outlay_test.go | 44 - catalogues/iso/extensions.go | 28 - catalogues/iso/iso.go | 9 +- catalogues/iso/scheme_id.go | 26 + catalogues/untdid/allowance.go | 105 ++ catalogues/untdid/charge.go | 753 ++++++++++ catalogues/untdid/document_type.go | 246 ++++ catalogues/untdid/extensions.go | 746 ---------- catalogues/untdid/payment_means.go | 364 +++++ catalogues/untdid/tax_category.go | 156 +++ catalogues/untdid/untdid.go | 13 +- cbc/code.go | 12 +- cbc/code_test.go | 9 + cmd/gobl/testdata/Test_build_do_not_envelop | 8 - cmd/gobl/testdata/Test_build_envelop | 10 +- cmd/gobl/testdata/Test_build_explicit_stdout | 4 +- cmd/gobl/testdata/Test_build_input_file | 4 +- cmd/gobl/testdata/Test_build_merge_values | 4 +- .../testdata/Test_build_output_file_outfile | 4 +- .../Test_build_overwrite_input_file_outfile | 10 +- .../Test_build_overwrite_output_file_outfile | 4 +- cmd/gobl/testdata/Test_build_recalculate | 10 +- cmd/gobl/testdata/Test_build_success | 10 +- cmd/gobl/testdata/Test_build_valid_file | 10 +- cmd/gobl/testdata/Test_sign_explicit_stdout | 4 +- cmd/gobl/testdata/Test_sign_input_file | 4 +- cmd/gobl/testdata/Test_sign_merge_values | 4 +- .../testdata/Test_sign_output_file_outfile | 4 +- .../Test_sign_overwrite_input_file_outfile | 10 +- .../Test_sign_overwrite_output_file_outfile | 4 +- cmd/gobl/testdata/Test_sign_recalculate | 10 +- cmd/gobl/testdata/Test_sign_success | 10 +- cmd/gobl/testdata/Test_sign_valid_file | 10 +- data/catalogues/untdid.json | 1228 +++++++++++++++++ data/regimes/it.json | 13 - data/schemas/bill/invoice.json | 416 ++++-- data/schemas/cbc/code.json | 2 +- data/schemas/org/inbox.json | 27 +- data/schemas/pay/advance.json | 6 + data/schemas/pay/instructions.json | 10 +- data/schemas/tax/regime-def.json | 8 - internal/cli/testdata/TestBuild_draft | 10 +- internal/cli/testdata/TestBuild_explicit_type | 10 +- internal/cli/testdata/TestBuild_merge_YAML | 10 +- internal/cli/testdata/TestBuild_success | 10 +- .../TestBuild_template_with_empty_input | 10 +- internal/cli/testdata/TestBuild_with_template | 10 +- internal/cli/testdata/TestSign_draft_envelope | 12 +- internal/cli/testdata/TestSign_success | 12 +- internal/cli/testdata/draft.json | 13 +- org/inbox.go | 59 +- org/inbox_test.go | 104 ++ org/party.go | 1 + pay/instructions.go | 10 +- pay/instructions_test.go | 2 + regimes/it/charges.go | 25 - regimes/it/it.go | 1 - tax/regime_def.go | 4 - 76 files changed, 4063 insertions(+), 1410 deletions(-) create mode 100644 bill/line_charge.go create mode 100644 bill/line_charge_test.go create mode 100644 bill/line_discount.go create mode 100644 bill/line_discount_test.go delete mode 100644 bill/outlay.go delete mode 100644 bill/outlay_test.go delete mode 100644 catalogues/iso/extensions.go create mode 100644 catalogues/iso/scheme_id.go create mode 100644 catalogues/untdid/allowance.go create mode 100644 catalogues/untdid/charge.go create mode 100644 catalogues/untdid/document_type.go delete mode 100644 catalogues/untdid/extensions.go create mode 100644 catalogues/untdid/payment_means.go create mode 100644 catalogues/untdid/tax_category.go create mode 100644 org/inbox_test.go delete mode 100644 regimes/it/charges.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c227658..bfeb3591 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,21 +10,28 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p - `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`, and `untdid-tax-category`. +- `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` support for extensions. +- `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. -### Modified +### 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. ## [v0.203.0] diff --git a/addons/co/dian/invoices.go b/addons/co/dian/invoices.go index 6016e828..6e6bd4ec 100644 --- a/addons/co/dian/invoices.go +++ b/addons/co/dian/invoices.go @@ -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, - ), ) } diff --git a/addons/eu/en16931/bill.go b/addons/eu/en16931/bill.go index 1e41ebff..1a81a54b 100644 --- a/addons/eu/en16931/bill.go +++ b/addons/eu/en16931/bill.go @@ -7,6 +7,71 @@ import ( "github.com/invopop/validation" ) +var discountKeyMap = tax.Extensions{ + bill.DiscountKeyEarlyCompletion: "41", + bill.DiscountKeyMilitary: "62", + bill.DiscountKeyWorkAccident: "63", + bill.DiscountKeySpecialAgreement: "64", + bill.DiscountKeyProductionError: "65", + bill.DiscountKeyNewOutlet: "66", + bill.DiscountKeySample: "67", + bill.DiscountKeyEndOfRange: "68", + bill.DiscountKeyIncoterm: "70", + bill.DiscountKeyPOSThreshold: "71", + bill.DiscountKeySpecialRebate: "100", + bill.DiscountKeyTemporary: "103", + bill.DiscountKeyStandard: "104", + bill.DiscountKeyYarlyTurnover: "105", +} + +// The following map is useful to get started, but for most users it will make +// sense to use the UNTDID codes directly in the extensions. +var chargeKeyMap = tax.Extensions{ + bill.ChargeKeyStampDuty: "ST", + bill.ChargeKeyOutlay: "AAE", + bill.ChargeKeyTax: "TX", + bill.ChargeKeyCustoms: "ABW", + bill.ChargeKeyDelivery: "DL", + bill.ChargeKeyPacking: "PC", + bill.ChargeKeyHandling: "HD", + bill.ChargeKeyInsurance: "IN", + bill.ChargeKeyStorage: "ABA", + bill.ChargeKeyAdmin: "AEM", + bill.ChargeKeyCleaning: "CG", +} + +func normalizeBillDiscount(m *bill.Discount) { + if val, ok := discountKeyMap[m.Key]; ok { + m.Ext = m.Ext.Merge(tax.Extensions{ + untdid.ExtKeyAllowance: val, + }) + } +} + +func normalizeBillLineDiscount(m *bill.LineDiscount) { + if val, ok := discountKeyMap[m.Key]; ok { + m.Ext = m.Ext.Merge(tax.Extensions{ + untdid.ExtKeyAllowance: val, + }) + } +} + +func normalizeBillCharge(m *bill.Charge) { + if val, ok := chargeKeyMap[m.Key]; ok { + m.Ext = m.Ext.Merge(tax.Extensions{ + untdid.ExtKeyCharge: val, + }) + } +} + +func normalizeBillLineCharge(m *bill.LineCharge) { + if val, ok := chargeKeyMap[m.Key]; ok { + m.Ext = m.Ext.Merge(tax.Extensions{ + untdid.ExtKeyCharge: val, + }) + } +} + func validateBillInvoice(inv *bill.Invoice) error { return validation.ValidateStruct(inv, validation.Field(&inv.Tax, diff --git a/addons/eu/en16931/bill_test.go b/addons/eu/en16931/bill_test.go index a54abaec..1d819671 100644 --- a/addons/eu/en16931/bill_test.go +++ b/addons/eu/en16931/bill_test.go @@ -114,3 +114,87 @@ func testInvoiceStandard(t *testing.T) *bill.Invoice { } return inv } + +func TestNormalizeBillLineDiscount(t *testing.T) { + ad := tax.AddonForKey(en16931.V2017) + t.Run("with key", func(t *testing.T) { + l := &bill.LineDiscount{ + Key: "sample", + Reason: "Product sample", + Amount: num.MakeAmount(100, 2), + } + ad.Normalizer(l) + assert.Equal(t, "67", l.Ext[untdid.ExtKeyAllowance].String()) + }) + t.Run("without key", func(t *testing.T) { + l := &bill.LineDiscount{ + Reason: "Product sample", + Amount: num.MakeAmount(100, 2), + } + ad.Normalizer(l) + assert.Nil(t, l.Ext) + }) +} + +func TestNormalizeBillDiscount(t *testing.T) { + ad := tax.AddonForKey(en16931.V2017) + t.Run("with key", func(t *testing.T) { + l := &bill.Discount{ + Key: "sample", + Reason: "Product sample", + Amount: num.MakeAmount(100, 2), + } + ad.Normalizer(l) + assert.Equal(t, "67", l.Ext[untdid.ExtKeyAllowance].String()) + }) + t.Run("without key", func(t *testing.T) { + l := &bill.Discount{ + Reason: "Product sample", + Amount: num.MakeAmount(100, 2), + } + ad.Normalizer(l) + assert.Nil(t, l.Ext) + }) +} + +func TestNormalizeBillLineCharge(t *testing.T) { + ad := tax.AddonForKey(en16931.V2017) + t.Run("with key", func(t *testing.T) { + l := &bill.LineCharge{ + Key: "outlay", + Reason: "Notary costs", + Amount: num.MakeAmount(1000, 2), + } + ad.Normalizer(l) + assert.Equal(t, "AAE", l.Ext[untdid.ExtKeyCharge].String()) + }) + t.Run("without key", func(t *testing.T) { + l := &bill.LineCharge{ + Reason: "Additional costs", + Amount: num.MakeAmount(3000, 2), + } + ad.Normalizer(l) + assert.Nil(t, l.Ext) + }) +} + +func TestNormalizeBillCharge(t *testing.T) { + ad := tax.AddonForKey(en16931.V2017) + t.Run("with key", func(t *testing.T) { + l := &bill.Charge{ + Key: "outlay", + Reason: "Notary costs", + Amount: num.MakeAmount(1000, 2), + } + ad.Normalizer(l) + assert.Equal(t, "AAE", l.Ext[untdid.ExtKeyCharge].String()) + }) + t.Run("without key", func(t *testing.T) { + l := &bill.Charge{ + Reason: "Additional costs", + Amount: num.MakeAmount(3000, 2), + } + ad.Normalizer(l) + assert.Nil(t, l.Ext) + }) +} diff --git a/addons/eu/en16931/en16931.go b/addons/eu/en16931/en16931.go index 5048ab4e..2c150e05 100644 --- a/addons/eu/en16931/en16931.go +++ b/addons/eu/en16931/en16931.go @@ -48,6 +48,14 @@ func normalize(doc any) { normalizePayInstructions(obj) case *tax.Combo: normalizeTaxCombo(obj) + case *bill.Discount: + normalizeBillDiscount(obj) + case *bill.LineDiscount: + normalizeBillLineDiscount(obj) + case *bill.Charge: + normalizeBillCharge(obj) + case *bill.LineCharge: + normalizeBillLineCharge(obj) } } diff --git a/bill/charges.go b/bill/charges.go index 3c13789d..65f715a2 100644 --- a/bill/charges.go +++ b/bill/charges.go @@ -5,57 +5,102 @@ import ( "github.com/invopop/gobl/cbc" "github.com/invopop/gobl/currency" + "github.com/invopop/gobl/i18n" "github.com/invopop/gobl/num" "github.com/invopop/gobl/tax" "github.com/invopop/gobl/uuid" + "github.com/invopop/jsonschema" "github.com/invopop/validation" ) -// LineCharge represents an amount added to the line, and will be -// applied before taxes. -// TODO: use UNTDID 7161 code list -type LineCharge struct { - // Percentage if fixed amount not applied - Percent *num.Percentage `json:"percent,omitempty" jsonschema:"title=Percent"` - // Fixed or resulting charge amount to apply (calculated if percent present). - Amount num.Amount `json:"amount" jsonschema:"title=Amount" jsonschema_extras:"calculated=true"` - // Reference code. - Code string `json:"code,omitempty" jsonschema:"title=Code"` - // Text description as to why the charge was applied - Reason string `json:"reason,omitempty" jsonschema:"title=Reason"` -} +// Charge keys for identifying the type of charge being applied. +// These are based on a subset of the UN/CEFACT UNTDID 7161 codes, +// and are intentionally kept lean. +const ( + ChargeKeyStampDuty cbc.Key = "stamp-duty" + ChargeKeyOutlay cbc.Key = "outlay" + ChargeKeyTax cbc.Key = "tax" + ChargeKeyCustoms cbc.Key = "customs" + ChargeKeyDelivery cbc.Key = "delivery" + ChargeKeyPacking cbc.Key = "packing" + ChargeKeyHandling cbc.Key = "handling" + ChargeKeyInsurance cbc.Key = "insurance" + ChargeKeyStorage cbc.Key = "storage" + ChargeKeyAdmin cbc.Key = "admin" // administration + ChargeKeyCleaning cbc.Key = "cleaning" +) -// Validate checks the line charge's fields. -func (lc *LineCharge) Validate() error { - return validation.ValidateStruct(lc, - validation.Field(&lc.Percent), - validation.Field(&lc.Amount, validation.Required), - ) +var chargeKeyDefinitions = []*cbc.KeyDefinition{ + { + Key: ChargeKeyStampDuty, + Name: i18n.NewString("Stamp Duty"), + }, + { + Key: ChargeKeyOutlay, + Name: i18n.NewString("Outlay"), + }, + { + Key: ChargeKeyTax, + Name: i18n.NewString("Tax"), + }, + { + Key: ChargeKeyCustoms, + Name: i18n.NewString("Customs"), + }, + { + Key: ChargeKeyDelivery, + Name: i18n.NewString("Delivery"), + }, + { + Key: ChargeKeyPacking, + Name: i18n.NewString("Packing"), + }, + { + Key: ChargeKeyHandling, + Name: i18n.NewString("Handling"), + }, + { + Key: ChargeKeyInsurance, + Name: i18n.NewString("Insurance"), + }, + { + Key: ChargeKeyStorage, + Name: i18n.NewString("Storage"), + }, + { + Key: ChargeKeyAdmin, + Name: i18n.NewString("Administration"), + }, + { + Key: ChargeKeyCleaning, + Name: i18n.NewString("Cleaning"), + }, } // Charge represents a surchange applied to the complete document // independent from the individual lines. type Charge struct { uuid.Identify - // Key for grouping or identifying charges for tax purposes. - Key cbc.Key `json:"key,omitempty" jsonschema:"title=Key"` // Line number inside the list of charges (calculated). Index int `json:"i" jsonschema:"title=Index" jsonschema_extras:"calculated=true"` - // Code to used to refer to the this charge - Ref string `json:"ref,omitempty" jsonschema:"title=Reference"` + // Key for grouping or identifying charges for tax purposes. A suggested list of + // keys is provided, but these may be extended by the issuer. + Key cbc.Key `json:"key,omitempty" jsonschema:"title=Key"` + // Code to used to refer to the this charge by the issuer + Code cbc.Code `json:"code,omitempty" jsonschema:"title=Code"` + // Text description as to why the charge was applied + Reason string `json:"reason,omitempty" jsonschema:"title=Reason"` // Base represents the value used as a base for percent calculations instead // of the invoice's sum of lines. Base *num.Amount `json:"base,omitempty" jsonschema:"title=Base"` - // Percentage to apply to the Base or Invoice Sum + // Percentage to apply to the sum of all lines Percent *num.Percentage `json:"percent,omitempty" jsonschema:"title=Percent"` // Amount to apply (calculated if percent present) Amount num.Amount `json:"amount" jsonschema:"title=Amount" jsonschema_extras:"calculated=true"` // List of taxes to apply to the charge Taxes tax.Set `json:"taxes,omitempty" jsonschema:"title=Taxes"` - // Code for why was this charge applied? - Code string `json:"code,omitempty" jsonschema:"title=Reason Code"` - // Text description as to why the charge was applied - Reason string `json:"reason,omitempty" jsonschema:"title=Reason"` + // Extension codes that apply to the charge + Ext tax.Extensions `json:"ext,omitempty" jsonschema:"title=Extensions"` // Additional semi-structured information. Meta cbc.Meta `json:"meta,omitempty" jsonschema:"title=Meta"` @@ -66,7 +111,9 @@ type Charge struct { // Normalize performs normalization on the line and embedded objects using the // provided list of normalizers. func (m *Charge) Normalize(normalizers tax.Normalizers) { + m.Code = cbc.NormalizeCode(m.Code) m.Taxes = tax.CleanSet(m.Taxes) + m.Ext = tax.CleanExtensions(m.Ext) normalizers.Each(m) tax.Normalize(normalizers, m.Taxes) } @@ -75,6 +122,8 @@ func (m *Charge) Normalize(normalizers tax.Normalizers) { func (m *Charge) ValidateWithContext(ctx context.Context) error { return tax.ValidateStructWithContext(ctx, m, validation.Field(&m.UUID), + validation.Field(&m.Key), + validation.Field(&m.Code), validation.Field(&m.Base), validation.Field(&m.Percent, validation.When( @@ -84,6 +133,7 @@ func (m *Charge) ValidateWithContext(ctx context.Context) error { ), validation.Field(&m.Amount, validation.Required), validation.Field(&m.Taxes), + validation.Field(&m.Ext), validation.Field(&m.Meta), ) } @@ -116,6 +166,11 @@ func (m *Charge) convertInto(ex *currency.ExchangeRate) *Charge { return &m2 } +// JSONSchemaExtend adds the charge key definitions to the schema. +func (Charge) JSONSchemaExtend(schema *jsonschema.Schema) { + extendJSONSchemaWithChargeKey(schema) +} + func calculateCharges(lines []*Charge, sum, zero num.Amount) { // COPIED FROM discount.go if len(lines) == 0 { @@ -151,3 +206,21 @@ func calculateChargeSum(charges []*Charge, zero num.Amount) *num.Amount { } return &total } + +func extendJSONSchemaWithChargeKey(schema *jsonschema.Schema) { + prop, ok := schema.Properties.Get("key") + if !ok { + return + } + prop.AnyOf = make([]*jsonschema.Schema, len(chargeKeyDefinitions)) + for i, v := range chargeKeyDefinitions { + prop.AnyOf[i] = &jsonschema.Schema{ + Const: v.Key, + Title: v.Name.String(), + } + } + prop.AnyOf = append(prop.AnyOf, &jsonschema.Schema{ + Title: "Other", + Pattern: cbc.KeyPattern, + }) +} diff --git a/bill/discounts.go b/bill/discounts.go index 31ff5d42..fadc7361 100644 --- a/bill/discounts.go +++ b/bill/discounts.go @@ -5,33 +5,91 @@ import ( "github.com/invopop/gobl/cbc" "github.com/invopop/gobl/currency" + "github.com/invopop/gobl/i18n" "github.com/invopop/gobl/num" "github.com/invopop/gobl/tax" "github.com/invopop/gobl/uuid" + "github.com/invopop/jsonschema" "github.com/invopop/validation" ) -// LineDiscount represents an amount deducted from the line, and will be -// applied before taxes. -type LineDiscount struct { - // Percentage if fixed amount not applied - Percent *num.Percentage `json:"percent,omitempty" jsonschema:"title=Percent"` - // Fixed discount amount to apply (calculated if percent present). - Amount num.Amount `json:"amount" jsonschema:"title=Amount" jsonschema_extras:"calculated=true"` - // Reason code. - Code string `json:"code,omitempty" jsonschema:"title=Code"` - // Text description as to why the discount was applied - Reason string `json:"reason,omitempty" jsonschema:"title=Reason"` - - // TODO: support UNTDID 5189 codes -} +// Discount keys for identifying the type of discount being applied. +// These are based on the UN/CEFACT UNTDID 5189 code list subset defined +// in the EN16931 code lists and are mean as suggestions. +const ( + DiscountKeyEarlyCompletion cbc.Key = "early-completion" + DiscountKeyMilitary cbc.Key = "military" + DiscountKeyWorkAccident cbc.Key = "work-accident" + DiscountKeySpecialAgreement cbc.Key = "special-agreement" + DiscountKeyProductionError cbc.Key = "production-error" + DiscountKeyNewOutlet cbc.Key = "new-outlet" + DiscountKeySample cbc.Key = "sample" + DiscountKeyEndOfRange cbc.Key = "end-of-range" + DiscountKeyIncoterm cbc.Key = "incoterm" + DiscountKeyPOSThreshold cbc.Key = "pos-threshold" + DiscountKeySpecialRebate cbc.Key = "special-rebate" + DiscountKeyTemporary cbc.Key = "temporary" + DiscountKeyStandard cbc.Key = "standard" + DiscountKeyYarlyTurnover cbc.Key = "yearly-turnover" +) -// Validate checks the line discount's fields. -func (ld *LineDiscount) Validate() error { - return validation.ValidateStruct(ld, - validation.Field(&ld.Percent), - validation.Field(&ld.Amount, validation.Required), - ) +var discountKeyDefinitions = []*cbc.KeyDefinition{ + { + Key: DiscountKeyEarlyCompletion, + Name: i18n.NewString("Bonus for works ahead of schedule"), + }, + { + Key: DiscountKeyMilitary, + Name: i18n.NewString("Military Discount"), + }, + { + Key: DiscountKeyWorkAccident, + Name: i18n.NewString("Work Accident Discount"), + }, + { + Key: DiscountKeySpecialAgreement, + Name: i18n.NewString("Special Agreement Discount"), + }, + { + Key: DiscountKeyProductionError, + Name: i18n.NewString("Production Error Discount"), + }, + { + Key: DiscountKeyNewOutlet, + Name: i18n.NewString("New Outlet Discount"), + }, + { + Key: DiscountKeySample, + Name: i18n.NewString("Sample Discount"), + }, + { + Key: DiscountKeyEndOfRange, + Name: i18n.NewString("End of Range Discount"), + }, + { + Key: DiscountKeyIncoterm, + Name: i18n.NewString("Incoterm Discount"), + }, + { + Key: DiscountKeyPOSThreshold, + Name: i18n.NewString("Point of Sale Threshold Discount"), + }, + { + Key: DiscountKeySpecialRebate, + Name: i18n.NewString("Special Rebate"), + }, + { + Key: DiscountKeyTemporary, + Name: i18n.NewString("Temporary"), + }, + { + Key: DiscountKeyStandard, + Name: i18n.NewString("Standard"), + }, + { + Key: DiscountKeyYarlyTurnover, + Name: i18n.NewString("Yearly Turnover"), + }, } // Discount represents an allowance applied to the complete document @@ -42,8 +100,12 @@ type Discount struct { uuid.Identify // Line number inside the list of discounts (calculated) Index int `json:"i" jsonschema:"title=Index" jsonschema_extras:"calculated=true"` - // Reference or ID for this Discount - Ref string `json:"ref,omitempty" jsonschema:"title=Reference"` + // Key for identifying the type of discount being applied. + Key cbc.Key `json:"key,omitempty" jsonschema:"title=Key"` + // Code to used to refer to the this discount by the issuer + Code cbc.Code `json:"code,omitempty" jsonschema:"title=Code"` + // Text description as to why the discount was applied + Reason string `json:"reason,omitempty" jsonschema:"title=Reason"` // Base represents the value used as a base for percent calculations instead // of the invoice's sum of lines. Base *num.Amount `json:"base,omitempty" jsonschema:"title=Base"` @@ -53,10 +115,8 @@ type Discount struct { Amount num.Amount `json:"amount" jsonschema:"title=Amount" jsonschema_extras:"calculated=true"` // List of taxes to apply to the discount Taxes tax.Set `json:"taxes,omitempty" jsonschema:"title=Taxes"` - // Code for the reason this discount applied - Code string `json:"code,omitempty" jsonschema:"title=Reason Code"` - // Text description as to why the discount was applied - Reason string `json:"reason,omitempty" jsonschema:"title=Reason"` + // Extension codes that apply to the discount + Ext tax.Extensions `json:"ext,omitempty" jsonschema:"title=Extensions"` // Additional semi-structured information. Meta cbc.Meta `json:"meta,omitempty" jsonschema:"title=Meta"` @@ -67,7 +127,9 @@ type Discount struct { // Normalize performs normalization on the line and embedded objects using the // provided list of normalizers. func (m *Discount) Normalize(normalizers tax.Normalizers) { + m.Code = cbc.NormalizeCode(m.Code) m.Taxes = tax.CleanSet(m.Taxes) + m.Ext = tax.CleanExtensions(m.Ext) normalizers.Each(m) tax.Normalize(normalizers, m.Taxes) } @@ -76,6 +138,7 @@ func (m *Discount) Normalize(normalizers tax.Normalizers) { func (m *Discount) ValidateWithContext(ctx context.Context) error { return tax.ValidateStructWithContext(ctx, m, validation.Field(&m.UUID), + validation.Field(&m.Code), validation.Field(&m.Base), validation.Field(&m.Percent, validation.When( @@ -85,6 +148,7 @@ func (m *Discount) ValidateWithContext(ctx context.Context) error { ), validation.Field(&m.Amount, validation.Required), validation.Field(&m.Taxes), + validation.Field(&m.Ext), validation.Field(&m.Meta), ) } @@ -118,6 +182,11 @@ func (m *Discount) convertInto(ex *currency.ExchangeRate) *Discount { return &m2 } +// JSONSchemaExtend adds the discount key definitions to the schema. +func (Discount) JSONSchemaExtend(schema *jsonschema.Schema) { + extendJSONSchemaWithDiscountKey(schema) +} + func calculateDiscounts(lines []*Discount, sum, zero num.Amount) { if len(lines) == 0 { return @@ -152,3 +221,21 @@ func calculateDiscountSum(discounts []*Discount, zero num.Amount) *num.Amount { } return &total } + +func extendJSONSchemaWithDiscountKey(schema *jsonschema.Schema) { + prop, ok := schema.Properties.Get("key") + if !ok { + return + } + prop.AnyOf = make([]*jsonschema.Schema, len(discountKeyDefinitions)) + for i, v := range discountKeyDefinitions { + prop.AnyOf[i] = &jsonschema.Schema{ + Const: v.Key, + Title: v.Name.String(), + } + } + prop.AnyOf = append(prop.AnyOf, &jsonschema.Schema{ + Title: "Other", + Pattern: cbc.KeyPattern, + }) +} diff --git a/bill/invoice.go b/bill/invoice.go index 4842ea79..9a0bff9f 100644 --- a/bill/invoice.go +++ b/bill/invoice.go @@ -65,7 +65,7 @@ type Invoice struct { // Special tax configuration for billing. Tax *Tax `json:"tax,omitempty" jsonschema:"title=Tax"` - // The taxable entity supplying the goods or services. + // The entity supplying the goods or services and usually responsible for paying taxes. Supplier *org.Party `json:"supplier" jsonschema:"title=Supplier"` // Legal entity receiving the goods or services, may be nil in certain circumstances such as simplified invoices. Customer *org.Party `json:"customer,omitempty" jsonschema:"title=Customer"` @@ -76,8 +76,6 @@ type Invoice struct { Discounts []*Discount `json:"discounts,omitempty" jsonschema:"title=Discounts"` // Charges or surcharges applied to the complete invoice Charges []*Charge `json:"charges,omitempty" jsonschema:"title=Charges"` - // Expenses paid for by the supplier but invoiced directly to the customer. - Outlays []*Outlay `json:"outlays,omitempty" jsonschema:"title=Outlays"` // Ordering details including document references and buyer or seller parties. Ordering *Ordering `json:"ordering,omitempty" jsonschema:"title=Ordering Details"` @@ -157,7 +155,6 @@ func (inv *Invoice) ValidateWithContext(ctx context.Context) error { ), validation.Field(&inv.Discounts), validation.Field(&inv.Charges), - validation.Field(&inv.Outlays), validation.Field(&inv.Ordering), validation.Field(&inv.Payment), validation.Field(&inv.Delivery), @@ -223,9 +220,6 @@ func (inv *Invoice) Invert() error { for _, row := range inv.Discounts { row.Amount = row.Amount.Invert() } - for _, row := range inv.Outlays { - row.Amount = row.Amount.Invert() - } if inv.Payment != nil { for _, row := range inv.Payment.Advances { row.Amount = row.Amount.Invert() @@ -252,7 +246,6 @@ func (inv *Invoice) Empty() { inv.Lines = make([]*Line, 0) inv.Charges = make([]*Charge, 0) inv.Discounts = make([]*Discount, 0) - inv.Outlays = make([]*Outlay, 0) inv.Totals = nil inv.Payment.ResetAdvances() } @@ -502,12 +495,6 @@ func (inv *Invoice) calculate() error { t.Taxes = nil } - // Outlays - t.Outlays = calculateOutlays(zero, inv.Outlays) - if t.Outlays != nil { - t.Payable = t.Payable.Add(*t.Outlays) - } - if inv.Payment != nil { inv.Payment.calculateAdvances(zero, t.TotalWithTax) diff --git a/bill/invoice_convert.go b/bill/invoice_convert.go index 084802fc..23868449 100644 --- a/bill/invoice_convert.go +++ b/bill/invoice_convert.go @@ -40,7 +40,6 @@ func (inv *Invoice) ConvertInto(cur currency.Code) (*Invoice, error) { i2.Lines = inv.convertLines(ex) i2.Discounts = inv.convertDiscounts(ex) i2.Charges = inv.convertCharges(ex) - i2.Outlays = inv.convertOutlays(ex) i2.Payment = inv.convertPayment(ex) i2.Currency = cur @@ -136,22 +135,6 @@ func (inv *Invoice) convertCharges(ex *currency.ExchangeRate) []*Charge { return charges } -func (inv *Invoice) convertOutlays(ex *currency.ExchangeRate) []*Outlay { - if len(inv.Outlays) == 0 { - return nil - } - outlays := make([]*Outlay, len(inv.Outlays)) - for i, o := range inv.Outlays { - o2 := *o - o2.Amount = o2.Amount. - Upscale(defaultCurrencyConversionAccuracy). - Multiply(ex.Amount). - Downscale(defaultCurrencyConversionAccuracy) - outlays[i] = &o2 - } - return outlays -} - func (inv *Invoice) convertPayment(ex *currency.ExchangeRate) *Payment { if inv.Payment == nil { return nil diff --git a/bill/invoice_convert_test.go b/bill/invoice_convert_test.go index d7fd9da5..5c502a85 100644 --- a/bill/invoice_convert_test.go +++ b/bill/invoice_convert_test.go @@ -148,12 +148,6 @@ func TestInvoiceConvertInto(t *testing.T) { }, }, }, - Outlays: []*bill.Outlay{ - { - Description: "Something paid in advance", - Amount: num.MakeAmount(1000, 2), - }, - }, Payment: &bill.Payment{ Advances: []*pay.Advance{ { @@ -166,10 +160,9 @@ func TestInvoiceConvertInto(t *testing.T) { i2, err := i.ConvertInto(currency.USD) assert.NoError(t, err) require.NotNil(t, i2) - assert.Equal(t, "11.20", i2.Outlays[0].Amount.String()) assert.Equal(t, "643.72", i2.Payment.Advances[0].Amount.String()) assert.Equal(t, "1064.00", i2.Totals.Sum.String()) - assert.Equal(t, "1298.64", i2.Totals.Payable.String()) - assert.Equal(t, "654.92", i2.Totals.Due.String()) + assert.Equal(t, "1287.44", i2.Totals.Payable.String()) + assert.Equal(t, "643.72", i2.Totals.Due.String()) }) } diff --git a/bill/invoice_test.go b/bill/invoice_test.go index a57c9907..a963843d 100644 --- a/bill/invoice_test.go +++ b/bill/invoice_test.go @@ -936,12 +936,6 @@ func TestCalculate(t *testing.T) { }, }, }, - Outlays: []*bill.Outlay{ - { - Description: "Something paid in advance", - Amount: num.MakeAmount(1000, 2), - }, - }, Payment: &bill.Payment{ Advances: []*pay.Advance{ { @@ -959,8 +953,8 @@ func TestCalculate(t *testing.T) { assert.Equal(t, i.Totals.TotalWithTax.String(), "950.00") assert.Equal(t, i.Payment.Advances[0].Amount.String(), "285.00") assert.Equal(t, i.Totals.Advances.String(), "285.00") - assert.Equal(t, i.Totals.Payable.String(), "960.00") - assert.Equal(t, i.Totals.Due.String(), "675.00") + assert.Equal(t, i.Totals.Payable.String(), "950.00") + assert.Equal(t, i.Totals.Due.String(), "665.00") assert.False(t, i.Totals.Paid()) } @@ -1010,12 +1004,6 @@ func TestCalculateInverted(t *testing.T) { }, }, }, - Outlays: []*bill.Outlay{ - { - Description: "Something paid in advance", - Amount: num.MakeAmount(1000, 2), - }, - }, Payment: &bill.Payment{ Advances: []*pay.Advance{ { @@ -1028,11 +1016,11 @@ func TestCalculateInverted(t *testing.T) { require.NoError(t, i.Calculate()) assert.Equal(t, i.Totals.Sum.String(), "950.00") - assert.Equal(t, i.Totals.Due.String(), "710.00") + assert.Equal(t, i.Totals.Due.String(), "700.00") require.NoError(t, i.Invert()) assert.Equal(t, i.Totals.Sum.String(), "-950.00") - assert.Equal(t, i.Totals.Due.String(), "-710.00") + assert.Equal(t, i.Totals.Due.String(), "-700.00") } func TestInvoiceForUnknownRegime(t *testing.T) { diff --git a/bill/line.go b/bill/line.go index e1997280..f6a6f89e 100644 --- a/bill/line.go +++ b/bill/line.go @@ -75,6 +75,8 @@ func (l *Line) Normalize(normalizers tax.Normalizers) { normalizers.Each(l) tax.Normalize(normalizers, l.Taxes) tax.Normalize(normalizers, l.Item) + tax.Normalize(normalizers, l.Discounts) + tax.Normalize(normalizers, l.Charges) } // calculate figures out the totals according to quantity and discounts. diff --git a/bill/line_charge.go b/bill/line_charge.go new file mode 100644 index 00000000..beb3bef1 --- /dev/null +++ b/bill/line_charge.go @@ -0,0 +1,52 @@ +package bill + +import ( + "github.com/invopop/gobl/cbc" + "github.com/invopop/gobl/num" + "github.com/invopop/gobl/tax" + "github.com/invopop/jsonschema" + "github.com/invopop/validation" +) + +// LineCharge represents an amount added to the line, and will be +// applied before taxes. +type LineCharge struct { + // Key for grouping or identifying charges for tax purposes. A suggested list of + // keys is provided, but these are for reference only and may be extended by + // the issuer. + Key cbc.Key `json:"key,omitempty" jsonschema:"title=Key"` + // Reference or ID for this charge defined by the issuer + Code cbc.Code `json:"code,omitempty" jsonschema:"title=Code"` + // Text description as to why the charge was applied + Reason string `json:"reason,omitempty" jsonschema:"title=Reason"` + // Percentage if fixed amount not applied + Percent *num.Percentage `json:"percent,omitempty" jsonschema:"title=Percent"` + // Fixed or resulting charge amount to apply (calculated if percent present). + Amount num.Amount `json:"amount" jsonschema:"title=Amount" jsonschema_extras:"calculated=true"` + // Extension codes that apply to the charge + Ext tax.Extensions `json:"ext,omitempty" jsonschema:"title=Extensions"` +} + +// Normalize performs normalization on the charge and embedded objects using the +// provided list of normalizers. +func (lc *LineCharge) Normalize(normalizers tax.Normalizers) { + lc.Code = cbc.NormalizeCode(lc.Code) + lc.Ext = tax.CleanExtensions(lc.Ext) + normalizers.Each(lc) +} + +// Validate checks the line charge's fields. +func (lc *LineCharge) Validate() error { + return validation.ValidateStruct(lc, + validation.Field(&lc.Key), + validation.Field(&lc.Code), + validation.Field(&lc.Percent), + validation.Field(&lc.Amount, validation.Required, num.NotZero), + validation.Field(&lc.Ext), + ) +} + +// JSONSchemaExtend adds the charge key definitions to the schema. +func (LineCharge) JSONSchemaExtend(schema *jsonschema.Schema) { + extendJSONSchemaWithChargeKey(schema) +} diff --git a/bill/line_charge_test.go b/bill/line_charge_test.go new file mode 100644 index 00000000..8943ef1d --- /dev/null +++ b/bill/line_charge_test.go @@ -0,0 +1,59 @@ +package bill_test + +import ( + "encoding/json" + "testing" + + "github.com/invopop/gobl/bill" + "github.com/invopop/gobl/num" + "github.com/invopop/gobl/tax" + "github.com/invopop/jsonschema" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestLineChargeNormalize(t *testing.T) { + l := &bill.LineCharge{ + Code: " FOO--BAR ", + Percent: num.NewPercentage(200, 3), + Ext: tax.Extensions{}, + } + l.Normalize(nil) + assert.Equal(t, "20.0%", l.Percent.String()) + assert.Equal(t, "FOO-BAR", l.Code.String()) + assert.Nil(t, l.Ext) +} + +func TestLineChargeValidation(t *testing.T) { + l := &bill.LineCharge{ + Key: "foo", + Code: "BAR", + Amount: num.MakeAmount(100, 2), + } + err := l.Validate() + assert.NoError(t, err) + + l.Amount = num.MakeAmount(0, 2) + err = l.Validate() + assert.ErrorContains(t, err, "amount: must not be zero") +} + +func TestLineChargeJSONSchema(t *testing.T) { + eg := `{ + "type": "object", + "properties": { + "key": { + "$ref": "https://gobl.org/draft-0/cbc/key" + } + } + }` + js := new(jsonschema.Schema) + require.NoError(t, json.Unmarshal([]byte(eg), js)) + schema := bill.LineCharge{} + schema.JSONSchemaExtend(js) + + props, ok := js.Properties.Get("key") + assert.True(t, ok) + assert.NotNil(t, props) + assert.Equal(t, 12, len(props.AnyOf)) +} diff --git a/bill/line_discount.go b/bill/line_discount.go new file mode 100644 index 00000000..0a13bd5c --- /dev/null +++ b/bill/line_discount.go @@ -0,0 +1,50 @@ +package bill + +import ( + "github.com/invopop/gobl/cbc" + "github.com/invopop/gobl/num" + "github.com/invopop/gobl/tax" + "github.com/invopop/jsonschema" + "github.com/invopop/validation" +) + +// LineDiscount represents an amount deducted from the line, and will be +// applied before taxes. +type LineDiscount struct { + // Key for identifying the type of discount being applied. + Key cbc.Key `json:"key,omitempty" jsonschema:"title=Key"` + // Code or reference for this discount defined by the issuer + Code cbc.Code `json:"code,omitempty" jsonschema:"title=Code"` + // Text description as to why the discount was applied + Reason string `json:"reason,omitempty" jsonschema:"title=Reason"` + // Percentage to apply to the line total to calcaulte the discount amount + Percent *num.Percentage `json:"percent,omitempty" jsonschema:"title=Percent"` + // Fixed discount amount to apply (calculated if percent present) + Amount num.Amount `json:"amount" jsonschema:"title=Amount" jsonschema_extras:"calculated=true"` + // Extension codes that apply to the discount + Ext tax.Extensions `json:"ext,omitempty" jsonschema:"title=Extensions"` +} + +// Normalize performs normalization on the discount and embedded objects using the +// provided list of normalizers. +func (ld *LineDiscount) Normalize(normalizers tax.Normalizers) { + ld.Code = cbc.NormalizeCode(ld.Code) + ld.Ext = tax.CleanExtensions(ld.Ext) + normalizers.Each(ld) +} + +// Validate checks the line discount's fields. +func (ld *LineDiscount) Validate() error { + return validation.ValidateStruct(ld, + validation.Field(&ld.Key), + validation.Field(&ld.Code), + validation.Field(&ld.Percent), + validation.Field(&ld.Amount, validation.Required, num.NotZero), + validation.Field(&ld.Ext), + ) +} + +// JSONSchemaExtend adds the discount key definitions to the schema. +func (LineDiscount) JSONSchemaExtend(schema *jsonschema.Schema) { + extendJSONSchemaWithDiscountKey(schema) +} diff --git a/bill/line_discount_test.go b/bill/line_discount_test.go new file mode 100644 index 00000000..df09cc04 --- /dev/null +++ b/bill/line_discount_test.go @@ -0,0 +1,59 @@ +package bill_test + +import ( + "encoding/json" + "testing" + + "github.com/invopop/gobl/bill" + "github.com/invopop/gobl/num" + "github.com/invopop/gobl/tax" + "github.com/invopop/jsonschema" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestLineDiscountNormalize(t *testing.T) { + l := &bill.LineDiscount{ + Code: " FOO--BAR ", + Percent: num.NewPercentage(200, 3), + Ext: tax.Extensions{}, + } + l.Normalize(nil) + assert.Equal(t, "20.0%", l.Percent.String()) + assert.Equal(t, "FOO-BAR", l.Code.String()) + assert.Nil(t, l.Ext) +} + +func TestLineDiscountValidation(t *testing.T) { + l := &bill.LineDiscount{ + Key: "foo", + Code: "BAR", + Amount: num.MakeAmount(100, 2), + } + err := l.Validate() + assert.NoError(t, err) + + l.Amount = num.MakeAmount(0, 2) + err = l.Validate() + assert.ErrorContains(t, err, "amount: must not be zero") +} + +func TestLineDiscountJSONSchema(t *testing.T) { + eg := `{ + "type": "object", + "properties": { + "key": { + "$ref": "https://gobl.org/draft-0/cbc/key" + } + } + }` + js := new(jsonschema.Schema) + require.NoError(t, json.Unmarshal([]byte(eg), js)) + schema := bill.LineDiscount{} + schema.JSONSchemaExtend(js) + + props, ok := js.Properties.Get("key") + assert.True(t, ok) + assert.NotNil(t, props) + assert.True(t, len(props.AnyOf) > 10) +} diff --git a/bill/ordering.go b/bill/ordering.go index 558c0620..5c5a6ac5 100644 --- a/bill/ordering.go +++ b/bill/ordering.go @@ -19,11 +19,9 @@ type Ordering struct { // Period of time that the invoice document refers to often used in addition to the details // provided in the individual line items. Period *cal.Period `json:"period,omitempty" jsonschema:"title=Period"` - // Party who is responsible for making the purchase, but is not responsible - // for handling taxes. + // Party who is responsible for issuing payment, if not the same as the customer. Buyer *org.Party `json:"buyer,omitempty" jsonschema:"title=Buyer"` - // Party who is selling the goods but is not responsible for taxes like the - // supplier. + // Seller is the party liable to pay taxes on the transaction if not the same as the supplier. Seller *org.Party `json:"seller,omitempty" jsonschema:"title=Seller"` // Projects this invoice refers to. Projects []*org.DocumentRef `json:"projects,omitempty" jsonschema:"title=Projects"` diff --git a/bill/outlay.go b/bill/outlay.go deleted file mode 100644 index 7f238d6d..00000000 --- a/bill/outlay.go +++ /dev/null @@ -1,78 +0,0 @@ -package bill - -import ( - "encoding/json" - - "github.com/invopop/gobl/cal" - "github.com/invopop/gobl/num" - "github.com/invopop/gobl/org" - "github.com/invopop/gobl/uuid" - "github.com/invopop/validation" -) - -// Outlay represents a reimbursable expense that was paid for by the supplier and invoiced separately -// by the third party directly to the customer. -// Most suppliers will want to include the expenses of their providers as part of their -// own operational costs. However, outlays are common in countries like Spain where it is typical -// for an accountant or lawyer to pay for notary fees, but forward the invoice to the -// customer. -type Outlay struct { - uuid.Identify - // Outlay number index inside the invoice for ordering (calculated). - Index int `json:"i" jsonschema:"title=Index" jsonschema_extras:"calculated=true"` - // When was the outlay made. - Date *cal.Date `json:"date,omitempty" jsonschema:"title=Date"` - // Invoice number or other reference detail used to identify the outlay. - Code string `json:"code,omitempty" jsonschema:"title=Code"` - // Series of the outlay invoice. - Series string `json:"series,omitempty" jsonschema:"title=Series"` - // Details on what the outlay was. - Description string `json:"description" jsonschema:"title=Description"` - // Who was the supplier of the outlay - Supplier *org.Party `json:"supplier,omitempty" jsonschema:"title=Supplier"` - // Amount paid by the supplier. - Amount num.Amount `json:"amount" jsonschema:"title=Amount"` -} - -// Validate ensures the outlay contains everything required. -func (o *Outlay) Validate() error { - return validation.ValidateStruct(o, - validation.Field(&o.UUID), - validation.Field(&o.Index, validation.Required), - validation.Field(&o.Date), - validation.Field(&o.Description, validation.Required), - validation.Field(&o.Supplier), - validation.Field(&o.Amount, validation.Required), - ) -} - -// UnmarshalJSON helps migrate the desc field to description. -func (o *Outlay) UnmarshalJSON(data []byte) error { - type Alias Outlay - aux := struct { - Desc string `json:"desc"` - *Alias - }{ - Alias: (*Alias)(o), - } - if err := json.Unmarshal(data, &aux); err != nil { - return err - } - if aux.Desc != "" { - o.Description = aux.Desc - } - return nil -} - -func calculateOutlays(zero num.Amount, outlays []*Outlay) *num.Amount { - if len(outlays) == 0 { - return nil - } - total := zero - for i, o := range outlays { - o.Amount = o.Amount.MatchPrecision(zero) - o.Index = i + 1 - total = total.Add(o.Amount) - } - return &total -} diff --git a/bill/outlay_test.go b/bill/outlay_test.go deleted file mode 100644 index 1120771a..00000000 --- a/bill/outlay_test.go +++ /dev/null @@ -1,44 +0,0 @@ -package bill - -import ( - "encoding/json" - "testing" - - "github.com/invopop/gobl/num" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestOutlayUnmarshal(t *testing.T) { - o := new(Outlay) - err := json.Unmarshal([]byte(`{"desc":"foo"}`), o) - require.NoError(t, err) - assert.Equal(t, "foo", o.Description) - err = json.Unmarshal([]byte(`{"description":"foo"}`), o) - require.NoError(t, err) - assert.Equal(t, "foo", o.Description) -} - -func TestOutlayTotals(t *testing.T) { - os := []*Outlay{ - { - Description: "First outlay", - Amount: num.MakeAmount(10000, 2), - }, - { - Description: "Second outlay", - Amount: num.MakeAmount(200, 0), - }, - } - zero := num.MakeAmount(0, 2) - sum := calculateOutlays(zero, os) - require.NotNil(t, sum) - assert.Equal(t, 1, os[0].Index) - assert.Equal(t, 2, os[1].Index) - assert.Equal(t, "300.00", sum.String()) - assert.Equal(t, "200.00", os[1].Amount.String()) - - os = []*Outlay{} - sum = calculateOutlays(zero, os) - assert.Nil(t, sum) -} diff --git a/catalogues/iso/extensions.go b/catalogues/iso/extensions.go deleted file mode 100644 index 6497ebc7..00000000 --- a/catalogues/iso/extensions.go +++ /dev/null @@ -1,28 +0,0 @@ -package iso - -import ( - "github.com/invopop/gobl/cbc" - "github.com/invopop/gobl/i18n" - "github.com/invopop/gobl/pkg/here" -) - -const ( - // ExtKeySchemeID is used by the ISO 6523 scheme identifier. - ExtKeySchemeID cbc.Key = "iso-scheme-id" -) - -var extensions = []*cbc.KeyDefinition{ - { - Key: ExtKeySchemeID, - Name: i18n.NewString("ISO/IEC 6523 Identifier scheme code"), - Desc: i18n.NewString(here.Doc(` - Defines a global structure for uniquely identifying organizations or entities. - This standard is essential in environments where electronic communications require - unambiguous identification of organizations, especially in automated systems or - electronic data interchange (EDI). - - The ISO 6523 set of identifies is used by the EN16931 standard for electronic invoicing. - `)), - Pattern: `^\d{4}$`, - }, -} diff --git a/catalogues/iso/iso.go b/catalogues/iso/iso.go index 1c6b4cfe..d6e0210c 100644 --- a/catalogues/iso/iso.go +++ b/catalogues/iso/iso.go @@ -3,6 +3,7 @@ package iso import ( + "github.com/invopop/gobl/cbc" "github.com/invopop/gobl/i18n" "github.com/invopop/gobl/tax" ) @@ -13,8 +14,10 @@ func init() { func newCatalogue() *tax.CatalogueDef { return &tax.CatalogueDef{ - Key: "iso", - Name: i18n.NewString("ISO/IEC Data Elements"), - Extensions: extensions, + Key: "iso", + Name: i18n.NewString("ISO/IEC Data Elements"), + Extensions: []*cbc.KeyDefinition{ + extSchemeID, + }, } } diff --git a/catalogues/iso/scheme_id.go b/catalogues/iso/scheme_id.go new file mode 100644 index 00000000..24aa96c5 --- /dev/null +++ b/catalogues/iso/scheme_id.go @@ -0,0 +1,26 @@ +package iso + +import ( + "github.com/invopop/gobl/cbc" + "github.com/invopop/gobl/i18n" + "github.com/invopop/gobl/pkg/here" +) + +const ( + // ExtKeySchemeID is used by the ISO 6523 scheme identifier. + ExtKeySchemeID cbc.Key = "iso-scheme-id" +) + +var extSchemeID = &cbc.KeyDefinition{ + Key: ExtKeySchemeID, + Name: i18n.NewString("ISO/IEC 6523 Identifier scheme code"), + Desc: i18n.NewString(here.Doc(` + Defines a global structure for uniquely identifying organizations or entities. + This standard is essential in environments where electronic communications require + unambiguous identification of organizations, especially in automated systems or + electronic data interchange (EDI). + + The ISO 6523 set of identifies is used by the EN16931 standard for electronic invoicing. + `)), + Pattern: `^\d{4}$`, +} diff --git a/catalogues/untdid/allowance.go b/catalogues/untdid/allowance.go new file mode 100644 index 00000000..0fe92589 --- /dev/null +++ b/catalogues/untdid/allowance.go @@ -0,0 +1,105 @@ +package untdid + +import ( + "github.com/invopop/gobl/cbc" + "github.com/invopop/gobl/i18n" + "github.com/invopop/gobl/pkg/here" +) + +const ( + // ExtKeyAllowance is used to identify the UNTDID 5189 allownce codes + // used in discounts. + ExtKeyAllowance cbc.Key = "untdid-allowance" +) + +var extAllowance = &cbc.KeyDefinition{ + Key: ExtKeyAllowance, + Name: i18n.String{ + i18n.EN: "UNTDID 5189 Allowance", + }, + Desc: i18n.String{ + i18n.EN: here.Doc(` + UNTDID 5189 code used to describe the allowance type. This list is based on the + [EN16931 code list](https://ec.europa.eu/digital-building-blocks/sites/display/DIGITAL/Registry+of+supporting+artefacts+to+implement+EN16931#RegistryofsupportingartefactstoimplementEN16931-Codelists) + values table which focusses on invoices and payments. + `), + }, + Values: []*cbc.ValueDefinition{ + { + Value: "41", + Name: i18n.NewString("Bonus for works ahead of schedule"), + }, + { + Value: "42", + Name: i18n.NewString("Other bonus"), + }, + { + Value: "60", + Name: i18n.NewString("Manufacturer’s consumer discount"), + }, + { + Value: "62", + Name: i18n.NewString("Due to military status"), + }, + { + Value: "63", + Name: i18n.NewString("Due to work accident"), + }, + { + Value: "64", + Name: i18n.NewString("Special agreement"), + }, + { + Value: "65", + Name: i18n.NewString("Production error discount"), + }, + { + Value: "66", + Name: i18n.NewString("New outlet discount"), + }, + { + Value: "67", + Name: i18n.NewString("Sample discount"), + }, + { + Value: "68", + Name: i18n.NewString("End-of-range discount"), + }, + { + Value: "70", + Name: i18n.NewString("Incoterm discount"), + }, + { + Value: "71", + Name: i18n.NewString("Point of sales threshold allowance"), + }, + { + Value: "88", + Name: i18n.NewString("Material surcharge/deduction"), + }, + { + Value: "95", + Name: i18n.NewString("Discount"), + }, + { + Value: "100", + Name: i18n.NewString("Special rebate"), + }, + { + Value: "102", + Name: i18n.NewString("Fixed long term"), + }, + { + Value: "103", + Name: i18n.NewString("Temporary"), + }, + { + Value: "104", + Name: i18n.NewString("Standard"), + }, + { + Value: "105", + Name: i18n.NewString("Yearly turnover"), + }, + }, +} diff --git a/catalogues/untdid/charge.go b/catalogues/untdid/charge.go new file mode 100644 index 00000000..2ba7bf81 --- /dev/null +++ b/catalogues/untdid/charge.go @@ -0,0 +1,753 @@ +package untdid + +import ( + "github.com/invopop/gobl/cbc" + "github.com/invopop/gobl/i18n" + "github.com/invopop/gobl/pkg/here" +) + +const ( + // ExtKeyCharge is used to identify the UNTDID 7161 charge codes. + ExtKeyCharge cbc.Key = "untdid-charge" +) + +var extCharge = &cbc.KeyDefinition{ + Key: ExtKeyCharge, + Name: i18n.NewString("UNTDID 7161 Charge"), + Desc: i18n.String{ + i18n.EN: here.Doc(` + UNTDID 7161 code used to describe the charge. List is based on the + EN16931 code lists with extensions for taxes and duties. + `), + }, + Values: []*cbc.ValueDefinition{ + { + Value: "AA", + Name: i18n.NewString("Advertising"), + }, + { + Value: "AAA", + Name: i18n.NewString("Telecommunication"), + }, + { + Value: "AAC", + Name: i18n.NewString("Technical modification"), + }, + { + Value: "AAD", + Name: i18n.NewString("Job-order production"), + }, + { + Value: "AAE", + Name: i18n.NewString("Outlays"), + }, + { + Value: "AAF", + Name: i18n.NewString("Off-premises"), + }, + { + Value: "AAH", + Name: i18n.NewString("Additional processing"), + }, + { + Value: "AAI", + Name: i18n.NewString("Attesting"), + }, + { + Value: "AAS", + Name: i18n.NewString("Acceptance"), + }, + { + Value: "AAT", + Name: i18n.NewString("Rush delivery"), + }, + { + Value: "AAV", + Name: i18n.NewString("Special construction"), + }, + { + Value: "AAY", + Name: i18n.NewString("Airport facilities"), + }, + { + Value: "AAZ", + Name: i18n.NewString("Concession"), + }, + { + Value: "ABA", + Name: i18n.NewString("Compulsory storage"), + }, + { + Value: "ABB", + Name: i18n.NewString("Fuel removal"), + }, + { + Value: "ABC", + Name: i18n.NewString("Into plane"), + }, + { + Value: "ABD", + Name: i18n.NewString("Overtime"), + }, + { + Value: "ABF", + Name: i18n.NewString("Tooling"), + }, + { + Value: "ABK", + Name: i18n.NewString("Miscellaneous"), + }, + { + Value: "ABL", + Name: i18n.NewString("Additional packaging"), + }, + { + Value: "ABN", + Name: i18n.NewString("Dunnage"), + }, + { + Value: "ABR", + Name: i18n.NewString("Containerisation"), + }, + { + Value: "ABS", + Name: i18n.NewString("Carton packing"), + }, + { + Value: "ABT", + Name: i18n.NewString("Hessian wrapped"), + }, + { + Value: "ABU", + Name: i18n.NewString("Polyethylene wrap packing"), + }, + { + Value: "ABW", // not in EN16931 + Name: i18n.NewString("Customs duty charge"), + }, + { + Value: "ACF", + Name: i18n.NewString("Miscellaneous treatment"), + }, + { + Value: "ACG", + Name: i18n.NewString("Enamelling treatment"), + }, + { + Value: "ACH", + Name: i18n.NewString("Heat treatment"), + }, + { + Value: "ACI", + Name: i18n.NewString("Plating treatment"), + }, + { + Value: "ACJ", + Name: i18n.NewString("Painting"), + }, + { + Value: "ACK", + Name: i18n.NewString("Polishing"), + }, + { + Value: "ACL", + Name: i18n.NewString("Priming"), + }, + { + Value: "ACM", + Name: i18n.NewString("Preservation treatment"), + }, + { + Value: "ACS", + Name: i18n.NewString("Fitting"), + }, + { + Value: "ADC", + Name: i18n.NewString("Consolidation"), + }, + { + Value: "ADE", + Name: i18n.NewString("Bill of lading"), + }, + { + Value: "ADJ", + Name: i18n.NewString("Airbag"), + }, + { + Value: "ADK", + Name: i18n.NewString("Transfer"), + }, + { + Value: "ADL", + Name: i18n.NewString("Slipsheet"), + }, + { + Value: "ADM", + Name: i18n.NewString("Binding"), + }, + { + Value: "ADN", + Name: i18n.NewString("Repair or replacement of broken returnable package"), + }, + { + Value: "ADO", + Name: i18n.NewString("Efficient logistics"), + }, + { + Value: "ADP", + Name: i18n.NewString("Merchandising"), + }, + { + Value: "ADQ", + Name: i18n.NewString("Product mix"), + }, + { + Value: "ADR", + Name: i18n.NewString("Other services"), + }, + { + Value: "ADT", + Name: i18n.NewString("Pick-up"), + }, + { + Value: "ADW", + Name: i18n.NewString("Chronic illness"), + }, + { + Value: "ADY", + Name: i18n.NewString("New product introduction"), + }, + { + Value: "ADZ", + Name: i18n.NewString("Direct delivery"), + }, + { + Value: "AEA", + Name: i18n.NewString("Diversion"), + }, + { + Value: "AEB", + Name: i18n.NewString("Disconnect"), + }, + { + Value: "AEC", + Name: i18n.NewString("Distribution"), + }, + { + Value: "AED", + Name: i18n.NewString("Handling of hazardous cargo"), + }, + { + Value: "AEF", + Name: i18n.NewString("Rents and leases"), + }, + { + Value: "AEH", + Name: i18n.NewString("Location differential"), + }, + { + Value: "AEI", + Name: i18n.NewString("Aircraft refueling"), + }, + { + Value: "AEJ", + Name: i18n.NewString("Fuel shipped into storage"), + }, + { + Value: "AEK", + Name: i18n.NewString("Cash on delivery"), + }, + { + Value: "AEL", + Name: i18n.NewString("Small order processing service"), + }, + { + Value: "AEM", + Name: i18n.NewString("Clerical or administrative services"), + }, + { + Value: "AEN", + Name: i18n.NewString("Guarantee"), + }, + { + Value: "AEO", + Name: i18n.NewString("Collection and recycling"), + }, + { + Value: "AEP", + Name: i18n.NewString("Copyright fee collection"), + }, + { + Value: "AES", + Name: i18n.NewString("Veterinary inspection service"), + }, + { + Value: "AET", + Name: i18n.NewString("Pensioner service"), + }, + { + Value: "AEU", + Name: i18n.NewString("Medicine free pass holder"), + }, + { + Value: "AEV", + Name: i18n.NewString("Environmental protection service"), + }, + { + Value: "AEW", + Name: i18n.NewString("Environmental clean-up service"), + }, + { + Value: "AEX", + Name: i18n.NewString("National cheque processing service outside account area"), + }, + { + Value: "AEY", + Name: i18n.NewString("National payment service outside account area"), + }, + { + Value: "AEZ", + Name: i18n.NewString("National payment service within account area"), + }, + { + Value: "AJ", + Name: i18n.NewString("Adjustments"), + }, + { + Value: "AU", + Name: i18n.NewString("Authentication"), + }, + { + Value: "CA", + Name: i18n.NewString("Cataloguing"), + }, + { + Value: "CAB", + Name: i18n.NewString("Cartage"), + }, + { + Value: "CAD", + Name: i18n.NewString("Certification"), + }, + { + Value: "CAE", + Name: i18n.NewString("Certificate of conformance"), + }, + { + Value: "CAF", + Name: i18n.NewString("Certificate of origin"), + }, + { + Value: "CAI", + Name: i18n.NewString("Cutting"), + }, + { + Value: "CAJ", + Name: i18n.NewString("Consular service"), + }, + { + Value: "CAK", + Name: i18n.NewString("Customer collection"), + }, + { + Value: "CAL", + Name: i18n.NewString("Payroll payment service"), + }, + { + Value: "CAM", + Name: i18n.NewString("Cash transportation"), + }, + { + Value: "CAN", + Name: i18n.NewString("Home banking service"), + }, + { + Value: "CAO", + Name: i18n.NewString("Bilateral agreement service"), + }, + { + Value: "CAP", + Name: i18n.NewString("Insurance brokerage service"), + }, + { + Value: "CAQ", + Name: i18n.NewString("Cheque generation"), + }, + { + Value: "CAR", + Name: i18n.NewString("Preferential merchandising location"), + }, + { + Value: "CAS", + Name: i18n.NewString("Crane"), + }, + { + Value: "CAT", + Name: i18n.NewString("Special colour service"), + }, + { + Value: "CAU", + Name: i18n.NewString("Sorting"), + }, + { + Value: "CAV", + Name: i18n.NewString("Battery collection and recycling"), + }, + { + Value: "CAW", + Name: i18n.NewString("Product take back fee"), + }, + { + Value: "CAX", + Name: i18n.NewString("Quality control released"), + }, + { + Value: "CAY", + Name: i18n.NewString("Quality control held"), + }, + { + Value: "CAZ", + Name: i18n.NewString("Quality control embargo"), + }, + { + Value: "CD", + Name: i18n.NewString("Car loading"), + }, + { + Value: "CG", + Name: i18n.NewString("Cleaning"), + }, + { + Value: "CS", + Name: i18n.NewString("Cigarette stamping"), + }, + { + Value: "CT", + Name: i18n.NewString("Count and recount"), + }, + { + Value: "DAB", + Name: i18n.NewString("Layout/design"), + }, + { + Value: "DAC", + Name: i18n.NewString("Assortment allowance"), + }, + { + Value: "DAD", + Name: i18n.NewString("Driver assigned unloading"), + }, + { + Value: "DAF", + Name: i18n.NewString("Debtor bound"), + }, + { + Value: "DAG", + Name: i18n.NewString("Dealer allowance"), + }, + { + Value: "DAH", + Name: i18n.NewString("Allowance transferable to the consumer"), + }, + { + Value: "DAI", + Name: i18n.NewString("Growth of business"), + }, + { + Value: "DAJ", + Name: i18n.NewString("Introduction allowance"), + }, + { + Value: "DAK", + Name: i18n.NewString("Multi-buy promotion"), + }, + { + Value: "DAL", + Name: i18n.NewString("Partnership"), + }, + { + Value: "DAM", + Name: i18n.NewString("Return handling"), + }, + { + Value: "DAN", + Name: i18n.NewString("Minimum order not fulfilled charge"), + }, + { + Value: "DAO", + Name: i18n.NewString("Point of sales threshold allowance"), + }, + { + Value: "DAP", + Name: i18n.NewString("Wholesaling discount"), + }, + { + Value: "DAQ", + Name: i18n.NewString("Documentary credits transfer commission"), + }, + { + Value: "DL", + Name: i18n.NewString("Delivery"), + }, + { + Value: "EG", + Name: i18n.NewString("Engraving"), + }, + { + Value: "EP", + Name: i18n.NewString("Expediting"), + }, + { + Value: "ER", + Name: i18n.NewString("Exchange rate guarantee"), + }, + { + Value: "FAA", + Name: i18n.NewString("Fabrication"), + }, + { + Value: "FAB", + Name: i18n.NewString("Freight equalization"), + }, + { + Value: "FAC", + Name: i18n.NewString("Freight extraordinary handling"), + }, + { + Value: "FC", + Name: i18n.NewString("Freight service"), + }, + { + Value: "FH", + Name: i18n.NewString("Filling/handling"), + }, + { + Value: "FI", + Name: i18n.NewString("Financing"), + }, + { + Value: "GAA", + Name: i18n.NewString("Grinding"), + }, + { + Value: "HAA", + Name: i18n.NewString("Hose"), + }, + { + Value: "HD", + Name: i18n.NewString("Handling"), + }, + { + Value: "HH", + Name: i18n.NewString("Hoisting and hauling"), + }, + { + Value: "IAA", + Name: i18n.NewString("Installation"), + }, + { + Value: "IAB", + Name: i18n.NewString("Installation and warranty"), + }, + { + Value: "ID", + Name: i18n.NewString("Inside delivery"), + }, + { + Value: "IF", + Name: i18n.NewString("Inspection"), + }, + { + Value: "IN", // not in EN16931 + Name: i18n.NewString("Insurance"), + }, + { + Value: "IR", + Name: i18n.NewString("Installation and training"), + }, + { + Value: "IS", + Name: i18n.NewString("Invoicing"), + }, + { + Value: "KO", + Name: i18n.NewString("Koshering"), + }, + { + Value: "L1", + Name: i18n.NewString("Carrier count"), + }, + { + Value: "LA", + Name: i18n.NewString("Labelling"), + }, + { + Value: "LAA", + Name: i18n.NewString("Labour"), + }, + { + Value: "LAB", + Name: i18n.NewString("Repair and return"), + }, + { + Value: "LF", + Name: i18n.NewString("Legalisation"), + }, + { + Value: "MAE", + Name: i18n.NewString("Mounting"), + }, + { + Value: "MI", + Name: i18n.NewString("Mail invoice"), + }, + { + Value: "ML", + Name: i18n.NewString("Mail invoice to each location"), + }, + { + Value: "NAA", + Name: i18n.NewString("Non-returnable containers"), + }, + { + Value: "OA", + Name: i18n.NewString("Outside cable connectors"), + }, + { + Value: "PA", + Name: i18n.NewString("Invoice with shipment"), + }, + { + Value: "PAA", + Name: i18n.NewString("Phosphatizing (steel treatment)"), + }, + { + Value: "PC", + Name: i18n.NewString("Packing"), + }, + { + Value: "PL", + Name: i18n.NewString("Palletizing"), + }, + { + Value: "PRV", + Name: i18n.NewString("Price variation"), + }, + { + Value: "RAB", + Name: i18n.NewString("Repacking"), + }, + { + Value: "RAC", + Name: i18n.NewString("Repair"), + }, + { + Value: "RAD", + Name: i18n.NewString("Returnable container"), + }, + { + Value: "RAF", + Name: i18n.NewString("Restocking"), + }, + { + Value: "RE", + Name: i18n.NewString("Re-delivery"), + }, + { + Value: "RF", + Name: i18n.NewString("Refurbishing"), + }, + { + Value: "RH", + Name: i18n.NewString("Rail wagon hire"), + }, + { + Value: "RV", + Name: i18n.NewString("Loading"), + }, + { + Value: "SA", + Name: i18n.NewString("Salvaging"), + }, + { + Value: "SAA", + Name: i18n.NewString("Shipping and handling"), + }, + { + Value: "SAD", + Name: i18n.NewString("Special packaging"), + }, + { + Value: "SAE", + Name: i18n.NewString("Stamping"), + }, + { + Value: "SAI", + Name: i18n.NewString("Consignee unload"), + }, + { + Value: "SG", + Name: i18n.NewString("Shrink-wrap"), + }, + { + Value: "SH", + Name: i18n.NewString("Special handling"), + }, + { + Value: "SM", + Name: i18n.NewString("Special finish"), + }, + { + Value: "ST", // not in EN16931 + Name: i18n.NewString("Stamp duties"), + }, + { + Value: "SU", + Name: i18n.NewString("Set-up"), + }, + { + Value: "TAB", + Name: i18n.NewString("Tank renting"), + }, + { + Value: "TAC", + Name: i18n.NewString("Testing"), + }, + { + Value: "TT", + Name: i18n.NewString("Transportation - third party billing"), + }, + { + Value: "TV", + Name: i18n.NewString("Transportation by vendor"), + }, + { + Value: "TX", // not in EN16931 + Name: i18n.NewString("Tax"), + }, + { + Value: "V1", + Name: i18n.NewString("Drop yard"), + }, + { + Value: "V2", + Name: i18n.NewString("Drop dock"), + }, + { + Value: "WH", + Name: i18n.NewString("Warehousing"), + }, + { + Value: "XAA", + Name: i18n.NewString("Combine all same day shipment"), + }, + { + Value: "YY", + Name: i18n.NewString("Split pick-up"), + }, + { + Value: "ZZZ", + Name: i18n.NewString("Mutually defined"), + }, + }, +} diff --git a/catalogues/untdid/document_type.go b/catalogues/untdid/document_type.go new file mode 100644 index 00000000..c09742d6 --- /dev/null +++ b/catalogues/untdid/document_type.go @@ -0,0 +1,246 @@ +package untdid + +import ( + "github.com/invopop/gobl/cbc" + "github.com/invopop/gobl/i18n" + "github.com/invopop/gobl/pkg/here" +) + +const ( + // ExtKeyDocumentType is used to identify the UNTDID 1001 document type code. + ExtKeyDocumentType cbc.Key = "untdid-document-type" +) + +var extDocumentTypes = &cbc.KeyDefinition{ + Key: ExtKeyDocumentType, + Name: i18n.String{ + i18n.EN: "UNTDID 1001 Document Type", + }, + Desc: i18n.String{ + i18n.EN: here.Doc(` + UNTDID 1001 code used to describe the type of document. Ths list is based + on the [EN16931 code list](https://ec.europa.eu/digital-building-blocks/sites/display/DIGITAL/Registry+of+supporting+artefacts+to+implement+EN16931#RegistryofsupportingartefactstoimplementEN16931-Codelists) + values table which focusses on invoices and payments. + + Other tax regimes and addons may use their own subset of codes. + `), + }, + Values: []*cbc.ValueDefinition{ + { + Value: "71", + Name: i18n.NewString("Request for payment"), + }, + { + Value: "80", + Name: i18n.NewString("Debit note related to goods or services"), + }, + { + Value: "81", + Name: i18n.NewString("Credit note related to goods or services"), + }, + { + Value: "82", + Name: i18n.NewString("Metered services invoice"), + }, + { + Value: "83", + Name: i18n.NewString("Credit note related to financial adjustments"), + }, + { + Value: "84", + Name: i18n.NewString("Debit note related to financial adjustments"), + }, + { + Value: "102", + Name: i18n.NewString("Tax notification"), + }, + { + Value: "130", + Name: i18n.NewString("Invoicing data sheet"), + }, + { + Value: "202", + Name: i18n.NewString("Direct payment valuation"), + }, + { + Value: "203", + Name: i18n.NewString("Provisional payment valuation"), + }, + { + Value: "204", + Name: i18n.NewString("Payment valuation"), + }, + { + Value: "211", + Name: i18n.NewString("Interim application for payment"), + }, + { + Value: "218", + Name: i18n.NewString("Final payment request based on completion of work"), + }, + { + Value: "219", + Name: i18n.NewString("Payment request for completed units"), + }, + { + Value: "261", + Name: i18n.NewString("Self billed credit note"), + }, + { + Value: "262", + Name: i18n.NewString("Consolidated credit note - goods and services"), + }, + { + Value: "295", + Name: i18n.NewString("Price variation invoice"), + }, + { + Value: "296", + Name: i18n.NewString("Credit note for price variation"), + }, + { + Value: "308", + Name: i18n.NewString("Delcredere credit note"), + }, + { + Value: "325", + Name: i18n.NewString("Proforma invoice"), + }, + { + Value: "326", + Name: i18n.NewString("Partial invoice"), + }, + { + Value: "380", + Name: i18n.NewString("Standard Invoice"), + }, + { + Value: "381", + Name: i18n.NewString("Credit note"), + }, + { + Value: "382", + Name: i18n.NewString("Commission note"), + }, + { + Value: "383", + Name: i18n.NewString("Debit note"), + }, + { + Value: "384", + Name: i18n.NewString("Corrected invoice"), + }, + { + Value: "385", + Name: i18n.NewString("Consolidated invoice"), + }, + { + Value: "386", + Name: i18n.NewString("Prepayment invoice"), + }, + { + Value: "387", + Name: i18n.NewString("Hire invoice"), + }, + { + Value: "388", + Name: i18n.NewString("Tax invoice"), + }, + { + Value: "389", + Name: i18n.NewString("Self-billed invoice"), + }, + { + Value: "390", + Name: i18n.NewString("Delcredere invoice"), + }, + { + Value: "393", + Name: i18n.NewString("Factored invoice"), + }, + { + Value: "394", + Name: i18n.NewString("Lease invoice"), + }, + { + Value: "395", + Name: i18n.NewString("Consignment invoice"), + }, + { + Value: "396", + Name: i18n.NewString("Factored credit note"), + }, + { + Value: "420", + Name: i18n.NewString("Optical Character Reading (OCR) payment credit note"), + }, + { + Value: "456", + Name: i18n.NewString("Debit advice"), + }, + { + Value: "457", + Name: i18n.NewString("Reversal of debit"), + }, + { + Value: "458", + Name: i18n.NewString("Reversal of credit"), + }, + { + Value: "527", + Name: i18n.NewString("Self billed debit note"), + }, + { + Value: "532", + Name: i18n.NewString("Forwarder's credit note"), + }, + { + Value: "553", + Name: i18n.NewString("Forwarder's invoice discrepancy report"), + }, + { + Value: "575", + Name: i18n.NewString("Insurer's invoice"), + }, + { + Value: "623", + Name: i18n.NewString("Forwarder's invoice"), + }, + { + Value: "633", + Name: i18n.NewString("Port charges documents"), + }, + { + Value: "751", + Name: i18n.NewString("Invoice information for accounting purposes"), + }, + { + Value: "780", + Name: i18n.NewString("Freight invoice"), + }, + { + Value: "817", + Name: i18n.NewString("Claim notification"), + }, + { + Value: "870", + Name: i18n.NewString("Consular invoice"), + }, + { + Value: "875", + Name: i18n.NewString("Partial construction invoice"), + }, + { + Value: "876", + Name: i18n.NewString("Partial final construction invoice"), + }, + { + Value: "877", + Name: i18n.NewString("Final construction invoice"), + }, + { + Value: "935", + Name: i18n.NewString("Customs invoice"), + }, + }, +} diff --git a/catalogues/untdid/extensions.go b/catalogues/untdid/extensions.go deleted file mode 100644 index ed2b66d8..00000000 --- a/catalogues/untdid/extensions.go +++ /dev/null @@ -1,746 +0,0 @@ -package untdid - -import ( - "github.com/invopop/gobl/cbc" - "github.com/invopop/gobl/i18n" - "github.com/invopop/gobl/pkg/here" -) - -const ( - // ExtKeyDocumentType is used to identify the UNTDID 1001 document type code. - ExtKeyDocumentType cbc.Key = "untdid-document-type" - // ExtKeyPaymentMeans is used to identify the UNTDID 4461 payment means code. - ExtKeyPaymentMeans cbc.Key = "untdid-payment-means" - // ExtKeyTaxCategory is used to identify the UNTDID 5305 duty/tax/fee category code. - ExtKeyTaxCategory cbc.Key = "untdid-tax-category" -) - -var extensions = []*cbc.KeyDefinition{ - { - Key: ExtKeyDocumentType, - Name: i18n.String{ - i18n.EN: "UNTDID 1001 Document Type", - }, - Desc: i18n.String{ - i18n.EN: here.Doc(` - UNTDID 1001 code used to describe the type of document. Ths list is based - on the [EN16931 code list](https://ec.europa.eu/digital-building-blocks/sites/display/DIGITAL/Registry+of+supporting+artefacts+to+implement+EN16931#RegistryofsupportingartefactstoimplementEN16931-Codelists) - values table which focusses on invoices and payments. - - Other tax regimes and addons may use their own subset of codes. - `), - }, - Values: []*cbc.ValueDefinition{ - { - Value: "71", - Name: i18n.NewString("Request for payment"), - }, - { - Value: "80", - Name: i18n.NewString("Debit note related to goods or services"), - }, - { - Value: "81", - Name: i18n.NewString("Credit note related to goods or services"), - }, - { - Value: "82", - Name: i18n.NewString("Metered services invoice"), - }, - { - Value: "83", - Name: i18n.NewString("Credit note related to financial adjustments"), - }, - { - Value: "84", - Name: i18n.NewString("Debit note related to financial adjustments"), - }, - { - Value: "102", - Name: i18n.NewString("Tax notification"), - }, - { - Value: "130", - Name: i18n.NewString("Invoicing data sheet"), - }, - { - Value: "202", - Name: i18n.NewString("Direct payment valuation"), - }, - { - Value: "203", - Name: i18n.NewString("Provisional payment valuation"), - }, - { - Value: "204", - Name: i18n.NewString("Payment valuation"), - }, - { - Value: "211", - Name: i18n.NewString("Interim application for payment"), - }, - { - Value: "218", - Name: i18n.NewString("Final payment request based on completion of work"), - }, - { - Value: "219", - Name: i18n.NewString("Payment request for completed units"), - }, - { - Value: "261", - Name: i18n.NewString("Self billed credit note"), - }, - { - Value: "262", - Name: i18n.NewString("Consolidated credit note - goods and services"), - }, - { - Value: "295", - Name: i18n.NewString("Price variation invoice"), - }, - { - Value: "296", - Name: i18n.NewString("Credit note for price variation"), - }, - { - Value: "308", - Name: i18n.NewString("Delcredere credit note"), - }, - { - Value: "325", - Name: i18n.NewString("Proforma invoice"), - }, - { - Value: "326", - Name: i18n.NewString("Partial invoice"), - }, - { - Value: "380", - Name: i18n.NewString("Standard Invoice"), - }, - { - Value: "381", - Name: i18n.NewString("Credit note"), - }, - { - Value: "382", - Name: i18n.NewString("Commission note"), - }, - { - Value: "383", - Name: i18n.NewString("Debit note"), - }, - { - Value: "384", - Name: i18n.NewString("Corrected invoice"), - }, - { - Value: "385", - Name: i18n.NewString("Consolidated invoice"), - }, - { - Value: "386", - Name: i18n.NewString("Prepayment invoice"), - }, - { - Value: "387", - Name: i18n.NewString("Hire invoice"), - }, - { - Value: "388", - Name: i18n.NewString("Tax invoice"), - }, - { - Value: "389", - Name: i18n.NewString("Self-billed invoice"), - }, - { - Value: "390", - Name: i18n.NewString("Delcredere invoice"), - }, - { - Value: "393", - Name: i18n.NewString("Factored invoice"), - }, - { - Value: "394", - Name: i18n.NewString("Lease invoice"), - }, - { - Value: "395", - Name: i18n.NewString("Consignment invoice"), - }, - { - Value: "396", - Name: i18n.NewString("Factored credit note"), - }, - { - Value: "420", - Name: i18n.NewString("Optical Character Reading (OCR) payment credit note"), - }, - { - Value: "456", - Name: i18n.NewString("Debit advice"), - }, - { - Value: "457", - Name: i18n.NewString("Reversal of debit"), - }, - { - Value: "458", - Name: i18n.NewString("Reversal of credit"), - }, - { - Value: "527", - Name: i18n.NewString("Self billed debit note"), - }, - { - Value: "532", - Name: i18n.NewString("Forwarder's credit note"), - }, - { - Value: "553", - Name: i18n.NewString("Forwarder's invoice discrepancy report"), - }, - { - Value: "575", - Name: i18n.NewString("Insurer's invoice"), - }, - { - Value: "623", - Name: i18n.NewString("Forwarder's invoice"), - }, - { - Value: "633", - Name: i18n.NewString("Port charges documents"), - }, - { - Value: "751", - Name: i18n.NewString("Invoice information for accounting purposes"), - }, - { - Value: "780", - Name: i18n.NewString("Freight invoice"), - }, - { - Value: "817", - Name: i18n.NewString("Claim notification"), - }, - { - Value: "870", - Name: i18n.NewString("Consular invoice"), - }, - { - Value: "875", - Name: i18n.NewString("Partial construction invoice"), - }, - { - Value: "876", - Name: i18n.NewString("Partial final construction invoice"), - }, - { - Value: "877", - Name: i18n.NewString("Final construction invoice"), - }, - { - Value: "935", - Name: i18n.NewString("Customs invoice"), - }, - }, - }, - { - Key: ExtKeyPaymentMeans, - Name: i18n.String{ - i18n.EN: "UNTDID 4461 Payment Means", - }, - Desc: i18n.String{ - i18n.EN: here.Doc(` - UNTDID 4461 code used to describe the means of payment. This list is based on the - [EN16931 code list](https://ec.europa.eu/digital-building-blocks/sites/display/DIGITAL/Registry+of+supporting+artefacts+to+implement+EN16931#RegistryofsupportingartefactstoimplementEN16931-Codelists) - values table which focusses on invoices and payments. - `), - }, - Values: []*cbc.ValueDefinition{ - { - Value: "1", - Name: i18n.NewString("Instrument not defined"), - }, - { - Value: "2", - Name: i18n.NewString("Automated clearing house credit"), - }, - { - Value: "3", - Name: i18n.NewString("Automated clearing house debit"), - }, - { - Value: "4", - Name: i18n.NewString("ACH demand debit reversal"), - }, - { - Value: "5", - Name: i18n.NewString("ACH demand credit reversal"), - }, - { - Value: "6", - Name: i18n.NewString("ACH demand credit"), - }, - { - Value: "7", - Name: i18n.NewString("ACH demand debit"), - }, - { - Value: "8", - Name: i18n.NewString("Hold"), - }, - { - Value: "9", - Name: i18n.NewString("National or regional clearing"), - }, - { - Value: "10", - Name: i18n.NewString("In cash"), - }, - { - Value: "11", - Name: i18n.NewString("ACH savings credit reversal"), - }, - { - Value: "12", - Name: i18n.NewString("ACH savings debit reversal"), - }, - { - Value: "13", - Name: i18n.NewString("ACH savings credit"), - }, - { - Value: "14", - Name: i18n.NewString("ACH savings debit"), - }, - { - Value: "15", - Name: i18n.NewString("Bookentry credit"), - }, - { - Value: "16", - Name: i18n.NewString("Bookentry debit"), - }, - { - Value: "17", - Name: i18n.NewString("ACH demand cash concentration/disbursement (CCD) credit"), - }, - { - Value: "18", - Name: i18n.NewString("ACH demand cash concentration/disbursement (CCD) debit"), - }, - { - Value: "19", - Name: i18n.NewString("ACH demand corporate trade payment (CTP) credit"), - }, - { - Value: "20", - Name: i18n.NewString("Cheque"), - }, - { - Value: "21", - Name: i18n.NewString("Banker's draft"), - }, - { - Value: "22", - Name: i18n.NewString("Certified banker's draft"), - }, - { - Value: "23", - Name: i18n.NewString("Bank cheque (issued by a banking or similar establishment)"), - }, - { - Value: "24", - Name: i18n.NewString("Bill of exchange awaiting acceptance"), - }, - { - Value: "25", - Name: i18n.NewString("Certified cheque"), - }, - { - Value: "26", - Name: i18n.NewString("Local cheque"), - }, - { - Value: "27", - Name: i18n.NewString("ACH demand corporate trade payment (CTP) debit"), - }, - { - Value: "28", - Name: i18n.NewString("ACH demand corporate trade exchange (CTX) credit"), - }, - { - Value: "29", - Name: i18n.NewString("ACH demand corporate trade exchange (CTX) debit"), - }, - { - Value: "30", - Name: i18n.NewString("Credit transfer"), - }, - { - Value: "31", - Name: i18n.NewString("Debit transfer"), - }, - { - Value: "32", - Name: i18n.NewString("ACH demand cash concentration/disbursement plus (CCD+)"), - }, - { - Value: "33", - Name: i18n.NewString("ACH demand cash concentration/disbursement plus (CCD+)"), - }, - { - Value: "34", - Name: i18n.NewString("ACH prearranged payment and deposit (PPD)"), - }, - { - Value: "35", - Name: i18n.NewString("ACH savings cash concentration/disbursement (CCD) credit"), - }, - { - Value: "36", - Name: i18n.NewString("ACH savings cash concentration/disbursement (CCD) debit"), - }, - { - Value: "37", - Name: i18n.NewString("ACH savings corporate trade payment (CTP) credit"), - }, - { - Value: "38", - Name: i18n.NewString("ACH savings corporate trade payment (CTP) debit"), - }, - { - Value: "39", - Name: i18n.NewString("ACH savings corporate trade exchange (CTX) credit"), - }, - { - Value: "40", - Name: i18n.NewString("ACH savings corporate trade exchange (CTX) debit"), - }, - { - Value: "41", - Name: i18n.NewString("ACH savings cash concentration/disbursement plus (CCD+)"), - }, - { - Value: "42", - Name: i18n.NewString("Payment to bank account"), - }, - { - Value: "43", - Name: i18n.NewString("ACH savings cash concentration/disbursement plus (CCD+)"), - }, - { - Value: "44", - Name: i18n.NewString("Accepted bill of exchange"), - }, - { - Value: "45", - Name: i18n.NewString("Referenced home-banking credit transfer"), - }, - { - Value: "46", - Name: i18n.NewString("Interbank debit transfer"), - }, - { - Value: "47", - Name: i18n.NewString("Home-banking debit transfer"), - }, - { - Value: "48", - Name: i18n.NewString("Bank card"), - }, - { - Value: "49", - Name: i18n.NewString("Direct debit"), - }, - { - Value: "50", - Name: i18n.NewString("Payment by postgiro"), - }, - { - Value: "51", - Name: i18n.NewString("FR, norme 6 97-Telereglement CFONB (French Organisation for"), - }, - { - Value: "52", - Name: i18n.NewString("Urgent commercial payment"), - }, - { - Value: "53", - Name: i18n.NewString("Urgent Treasury Payment"), - }, - { - Value: "54", - Name: i18n.NewString("Credit card"), - }, - { - Value: "55", - Name: i18n.NewString("Debit card"), - }, - { - Value: "56", - Name: i18n.NewString("Bankgiro"), - }, - { - Value: "57", - Name: i18n.NewString("Standing agreement"), - }, - { - Value: "58", - Name: i18n.NewString("SEPA credit transfer"), - }, - { - Value: "59", - Name: i18n.NewString("SEPA direct debit"), - }, - { - Value: "60", - Name: i18n.NewString("Promissory note"), - }, - { - Value: "61", - Name: i18n.NewString("Promissory note signed by the debtor"), - }, - { - Value: "62", - Name: i18n.NewString("Promissory note signed by the debtor and endorsed by a bank"), - }, - { - Value: "63", - Name: i18n.NewString("Promissory note signed by the debtor and endorsed by a"), - }, - { - Value: "64", - Name: i18n.NewString("Promissory note signed by a bank"), - }, - { - Value: "65", - Name: i18n.NewString("Promissory note signed by a bank and endorsed by another"), - }, - { - Value: "66", - Name: i18n.NewString("Promissory note signed by a third party"), - }, - { - Value: "67", - Name: i18n.NewString("Promissory note signed by a third party and endorsed by a"), - }, - { - Value: "68", - Name: i18n.NewString("Online payment service"), - }, - { - Value: "69", - Name: i18n.NewString("Transfer Advice"), - }, - { - Value: "70", - Name: i18n.NewString("Bill drawn by the creditor on the debtor"), - }, - { - Value: "74", - Name: i18n.NewString("Bill drawn by the creditor on a bank"), - }, - { - Value: "75", - Name: i18n.NewString("Bill drawn by the creditor, endorsed by another bank"), - }, - { - Value: "76", - Name: i18n.NewString("Bill drawn by the creditor on a bank and endorsed by a"), - }, - { - Value: "77", - Name: i18n.NewString("Bill drawn by the creditor on a third party"), - }, - { - Value: "78", - Name: i18n.NewString("Bill drawn by creditor on third party, accepted and"), - }, - { - Value: "91", - Name: i18n.NewString("Not transferable banker's draft"), - }, - { - Value: "92", - Name: i18n.NewString("Not transferable local cheque"), - }, - { - Value: "93", - Name: i18n.NewString("Reference giro"), - }, - { - Value: "94", - Name: i18n.NewString("Urgent giro"), - }, - { - Value: "95", - Name: i18n.NewString("Free format giro"), - }, - { - Value: "96", - Name: i18n.NewString("Requested method for payment was not used"), - }, - { - Value: "97", - Name: i18n.NewString("Clearing between partners"), - }, - { - Value: "98", - Name: i18n.NewString("JP, Electronically Recorded Monetary Claims"), - }, - { - Value: "ZZZ", - Name: i18n.NewString("Mutually defined"), - }, - }, - }, - { - Key: ExtKeyTaxCategory, - Name: i18n.String{ - i18n.EN: "UNTDID 3505 Tax Category", - }, - Desc: i18n.String{ - i18n.EN: here.Doc(` - UNTDID 5305 code used to describe the applicable duty/tax/fee category. There are - multiple versions and subsets of this table so regimes and addons may need to filter - options for a specific subset of values. - - Data from https://unece.org/fileadmin/DAM/trade/untdid/d16b/tred/tred5305.htm. - `), - }, - Values: []*cbc.ValueDefinition{ - { - Value: "A", - Name: i18n.String{ - i18n.EN: "Mixed tax rate", - }, - }, - { - Value: "AA", - Name: i18n.String{ - i18n.EN: "Lower rate", - }, - }, - { - Value: "AB", - Name: i18n.String{ - i18n.EN: "Exempt for resale", - }, - }, - { - Value: "AC", - Name: i18n.String{ - i18n.EN: "Exempt for resale", - }, - }, - { - Value: "AD", - Name: i18n.String{ - i18n.EN: "Value Added Tax (VAT) due from a previous invoice", - }, - }, - { - Value: "AE", - Name: i18n.String{ - i18n.EN: "VAT Reverse Charge", - }, - }, - { - Value: "B", - Name: i18n.String{ - i18n.EN: "Transferred (VAT)", - }, - }, - { - Value: "C", - Name: i18n.String{ - i18n.EN: "Duty paid by supplier", - }, - }, - { - Value: "D", - Name: i18n.String{ - i18n.EN: "Value Added Tax (VAT) margin scheme - travel agents", - }, - }, - { - Value: "E", - Name: i18n.String{ - i18n.EN: "Exempt from tax", - }, - }, - { - Value: "F", - Name: i18n.String{ - i18n.EN: "Value Added Tax (VAT) margin scheme - second-hand goods", - }, - }, - { - Value: "G", - Name: i18n.String{ - i18n.EN: "Free export item, tax not charged", - }, - }, - { - Value: "H", - Name: i18n.String{ - i18n.EN: "Higher rate", - }, - }, - { - Value: "I", - Name: i18n.String{ - i18n.EN: "Value Added Tax (VAT) margin scheme - works of art", - }, - }, - { - Value: "J", - Name: i18n.String{ - i18n.EN: "Value Added Tax (VAT) margin scheme - collector's items and antiques", - }, - }, - { - Value: "K", - Name: i18n.String{ - i18n.EN: "VAT exempt for EEA intra-community supply of goods and services", - }, - }, - { - Value: "L", - Name: i18n.String{ - i18n.EN: "Canary Islands general indirect tax", - }, - }, - { - Value: "M", - Name: i18n.String{ - i18n.EN: "Tax for production, services and importation in Ceuta and Melilla", - }, - }, - { - Value: "O", - Name: i18n.String{ - i18n.EN: "Services outside scope of tax", - }, - }, - { - Value: "S", - Name: i18n.String{ - i18n.EN: "Standard Rate", - }, - }, - { - Value: "Z", - Name: i18n.String{ - i18n.EN: "Zero rated goods", - }, - }, - }, - }, -} diff --git a/catalogues/untdid/payment_means.go b/catalogues/untdid/payment_means.go new file mode 100644 index 00000000..76b5e2b8 --- /dev/null +++ b/catalogues/untdid/payment_means.go @@ -0,0 +1,364 @@ +package untdid + +import ( + "github.com/invopop/gobl/cbc" + "github.com/invopop/gobl/i18n" + "github.com/invopop/gobl/pkg/here" +) + +const ( + // ExtKeyPaymentMeans is used to identify the UNTDID 4461 payment means code. + ExtKeyPaymentMeans cbc.Key = "untdid-payment-means" +) + +var extPaymentMeans = &cbc.KeyDefinition{ + Key: ExtKeyPaymentMeans, + Name: i18n.String{ + i18n.EN: "UNTDID 4461 Payment Means", + }, + Desc: i18n.String{ + i18n.EN: here.Doc(` + UNTDID 4461 code used to describe the means of payment. This list is based on the + [EN16931 code list](https://ec.europa.eu/digital-building-blocks/sites/display/DIGITAL/Registry+of+supporting+artefacts+to+implement+EN16931#RegistryofsupportingartefactstoimplementEN16931-Codelists) + values table which focusses on invoices and payments. + `), + }, + Values: []*cbc.ValueDefinition{ + { + Value: "1", + Name: i18n.NewString("Instrument not defined"), + }, + { + Value: "2", + Name: i18n.NewString("Automated clearing house credit"), + }, + { + Value: "3", + Name: i18n.NewString("Automated clearing house debit"), + }, + { + Value: "4", + Name: i18n.NewString("ACH demand debit reversal"), + }, + { + Value: "5", + Name: i18n.NewString("ACH demand credit reversal"), + }, + { + Value: "6", + Name: i18n.NewString("ACH demand credit"), + }, + { + Value: "7", + Name: i18n.NewString("ACH demand debit"), + }, + { + Value: "8", + Name: i18n.NewString("Hold"), + }, + { + Value: "9", + Name: i18n.NewString("National or regional clearing"), + }, + { + Value: "10", + Name: i18n.NewString("In cash"), + }, + { + Value: "11", + Name: i18n.NewString("ACH savings credit reversal"), + }, + { + Value: "12", + Name: i18n.NewString("ACH savings debit reversal"), + }, + { + Value: "13", + Name: i18n.NewString("ACH savings credit"), + }, + { + Value: "14", + Name: i18n.NewString("ACH savings debit"), + }, + { + Value: "15", + Name: i18n.NewString("Bookentry credit"), + }, + { + Value: "16", + Name: i18n.NewString("Bookentry debit"), + }, + { + Value: "17", + Name: i18n.NewString("ACH demand cash concentration/disbursement (CCD) credit"), + }, + { + Value: "18", + Name: i18n.NewString("ACH demand cash concentration/disbursement (CCD) debit"), + }, + { + Value: "19", + Name: i18n.NewString("ACH demand corporate trade payment (CTP) credit"), + }, + { + Value: "20", + Name: i18n.NewString("Cheque"), + }, + { + Value: "21", + Name: i18n.NewString("Banker's draft"), + }, + { + Value: "22", + Name: i18n.NewString("Certified banker's draft"), + }, + { + Value: "23", + Name: i18n.NewString("Bank cheque (issued by a banking or similar establishment)"), + }, + { + Value: "24", + Name: i18n.NewString("Bill of exchange awaiting acceptance"), + }, + { + Value: "25", + Name: i18n.NewString("Certified cheque"), + }, + { + Value: "26", + Name: i18n.NewString("Local cheque"), + }, + { + Value: "27", + Name: i18n.NewString("ACH demand corporate trade payment (CTP) debit"), + }, + { + Value: "28", + Name: i18n.NewString("ACH demand corporate trade exchange (CTX) credit"), + }, + { + Value: "29", + Name: i18n.NewString("ACH demand corporate trade exchange (CTX) debit"), + }, + { + Value: "30", + Name: i18n.NewString("Credit transfer"), + }, + { + Value: "31", + Name: i18n.NewString("Debit transfer"), + }, + { + Value: "32", + Name: i18n.NewString("ACH demand cash concentration/disbursement plus (CCD+)"), + }, + { + Value: "33", + Name: i18n.NewString("ACH demand cash concentration/disbursement plus (CCD+)"), + }, + { + Value: "34", + Name: i18n.NewString("ACH prearranged payment and deposit (PPD)"), + }, + { + Value: "35", + Name: i18n.NewString("ACH savings cash concentration/disbursement (CCD) credit"), + }, + { + Value: "36", + Name: i18n.NewString("ACH savings cash concentration/disbursement (CCD) debit"), + }, + { + Value: "37", + Name: i18n.NewString("ACH savings corporate trade payment (CTP) credit"), + }, + { + Value: "38", + Name: i18n.NewString("ACH savings corporate trade payment (CTP) debit"), + }, + { + Value: "39", + Name: i18n.NewString("ACH savings corporate trade exchange (CTX) credit"), + }, + { + Value: "40", + Name: i18n.NewString("ACH savings corporate trade exchange (CTX) debit"), + }, + { + Value: "41", + Name: i18n.NewString("ACH savings cash concentration/disbursement plus (CCD+)"), + }, + { + Value: "42", + Name: i18n.NewString("Payment to bank account"), + }, + { + Value: "43", + Name: i18n.NewString("ACH savings cash concentration/disbursement plus (CCD+)"), + }, + { + Value: "44", + Name: i18n.NewString("Accepted bill of exchange"), + }, + { + Value: "45", + Name: i18n.NewString("Referenced home-banking credit transfer"), + }, + { + Value: "46", + Name: i18n.NewString("Interbank debit transfer"), + }, + { + Value: "47", + Name: i18n.NewString("Home-banking debit transfer"), + }, + { + Value: "48", + Name: i18n.NewString("Bank card"), + }, + { + Value: "49", + Name: i18n.NewString("Direct debit"), + }, + { + Value: "50", + Name: i18n.NewString("Payment by postgiro"), + }, + { + Value: "51", + Name: i18n.NewString("FR, norme 6 97-Telereglement CFONB (French Organisation for"), + }, + { + Value: "52", + Name: i18n.NewString("Urgent commercial payment"), + }, + { + Value: "53", + Name: i18n.NewString("Urgent Treasury Payment"), + }, + { + Value: "54", + Name: i18n.NewString("Credit card"), + }, + { + Value: "55", + Name: i18n.NewString("Debit card"), + }, + { + Value: "56", + Name: i18n.NewString("Bankgiro"), + }, + { + Value: "57", + Name: i18n.NewString("Standing agreement"), + }, + { + Value: "58", + Name: i18n.NewString("SEPA credit transfer"), + }, + { + Value: "59", + Name: i18n.NewString("SEPA direct debit"), + }, + { + Value: "60", + Name: i18n.NewString("Promissory note"), + }, + { + Value: "61", + Name: i18n.NewString("Promissory note signed by the debtor"), + }, + { + Value: "62", + Name: i18n.NewString("Promissory note signed by the debtor and endorsed by a bank"), + }, + { + Value: "63", + Name: i18n.NewString("Promissory note signed by the debtor and endorsed by a"), + }, + { + Value: "64", + Name: i18n.NewString("Promissory note signed by a bank"), + }, + { + Value: "65", + Name: i18n.NewString("Promissory note signed by a bank and endorsed by another"), + }, + { + Value: "66", + Name: i18n.NewString("Promissory note signed by a third party"), + }, + { + Value: "67", + Name: i18n.NewString("Promissory note signed by a third party and endorsed by a"), + }, + { + Value: "68", + Name: i18n.NewString("Online payment service"), + }, + { + Value: "69", + Name: i18n.NewString("Transfer Advice"), + }, + { + Value: "70", + Name: i18n.NewString("Bill drawn by the creditor on the debtor"), + }, + { + Value: "74", + Name: i18n.NewString("Bill drawn by the creditor on a bank"), + }, + { + Value: "75", + Name: i18n.NewString("Bill drawn by the creditor, endorsed by another bank"), + }, + { + Value: "76", + Name: i18n.NewString("Bill drawn by the creditor on a bank and endorsed by a"), + }, + { + Value: "77", + Name: i18n.NewString("Bill drawn by the creditor on a third party"), + }, + { + Value: "78", + Name: i18n.NewString("Bill drawn by creditor on third party, accepted and"), + }, + { + Value: "91", + Name: i18n.NewString("Not transferable banker's draft"), + }, + { + Value: "92", + Name: i18n.NewString("Not transferable local cheque"), + }, + { + Value: "93", + Name: i18n.NewString("Reference giro"), + }, + { + Value: "94", + Name: i18n.NewString("Urgent giro"), + }, + { + Value: "95", + Name: i18n.NewString("Free format giro"), + }, + { + Value: "96", + Name: i18n.NewString("Requested method for payment was not used"), + }, + { + Value: "97", + Name: i18n.NewString("Clearing between partners"), + }, + { + Value: "98", + Name: i18n.NewString("JP, Electronically Recorded Monetary Claims"), + }, + { + Value: "ZZZ", + Name: i18n.NewString("Mutually defined"), + }, + }, +} diff --git a/catalogues/untdid/tax_category.go b/catalogues/untdid/tax_category.go new file mode 100644 index 00000000..392d4885 --- /dev/null +++ b/catalogues/untdid/tax_category.go @@ -0,0 +1,156 @@ +package untdid + +import ( + "github.com/invopop/gobl/cbc" + "github.com/invopop/gobl/i18n" + "github.com/invopop/gobl/pkg/here" +) + +const ( + // ExtKeyTaxCategory is used to identify the UNTDID 5305 duty/tax/fee category code. + ExtKeyTaxCategory cbc.Key = "untdid-tax-category" +) + +var extTaxCategory = &cbc.KeyDefinition{ + Key: ExtKeyTaxCategory, + Name: i18n.String{ + i18n.EN: "UNTDID 3505 Tax Category", + }, + Desc: i18n.String{ + i18n.EN: here.Doc(` + UNTDID 5305 code used to describe the applicable duty/tax/fee category. There are + multiple versions and subsets of this table so regimes and addons may need to filter + options for a specific subset of values. + + Data from https://unece.org/fileadmin/DAM/trade/untdid/d16b/tred/tred5305.htm. + `), + }, + Values: []*cbc.ValueDefinition{ + { + Value: "A", + Name: i18n.String{ + i18n.EN: "Mixed tax rate", + }, + }, + { + Value: "AA", + Name: i18n.String{ + i18n.EN: "Lower rate", + }, + }, + { + Value: "AB", + Name: i18n.String{ + i18n.EN: "Exempt for resale", + }, + }, + { + Value: "AC", + Name: i18n.String{ + i18n.EN: "Exempt for resale", + }, + }, + { + Value: "AD", + Name: i18n.String{ + i18n.EN: "Value Added Tax (VAT) due from a previous invoice", + }, + }, + { + Value: "AE", + Name: i18n.String{ + i18n.EN: "VAT Reverse Charge", + }, + }, + { + Value: "B", + Name: i18n.String{ + i18n.EN: "Transferred (VAT)", + }, + }, + { + Value: "C", + Name: i18n.String{ + i18n.EN: "Duty paid by supplier", + }, + }, + { + Value: "D", + Name: i18n.String{ + i18n.EN: "Value Added Tax (VAT) margin scheme - travel agents", + }, + }, + { + Value: "E", + Name: i18n.String{ + i18n.EN: "Exempt from tax", + }, + }, + { + Value: "F", + Name: i18n.String{ + i18n.EN: "Value Added Tax (VAT) margin scheme - second-hand goods", + }, + }, + { + Value: "G", + Name: i18n.String{ + i18n.EN: "Free export item, tax not charged", + }, + }, + { + Value: "H", + Name: i18n.String{ + i18n.EN: "Higher rate", + }, + }, + { + Value: "I", + Name: i18n.String{ + i18n.EN: "Value Added Tax (VAT) margin scheme - works of art", + }, + }, + { + Value: "J", + Name: i18n.String{ + i18n.EN: "Value Added Tax (VAT) margin scheme - collector's items and antiques", + }, + }, + { + Value: "K", + Name: i18n.String{ + i18n.EN: "VAT exempt for EEA intra-community supply of goods and services", + }, + }, + { + Value: "L", + Name: i18n.String{ + i18n.EN: "Canary Islands general indirect tax", + }, + }, + { + Value: "M", + Name: i18n.String{ + i18n.EN: "Tax for production, services and importation in Ceuta and Melilla", + }, + }, + { + Value: "O", + Name: i18n.String{ + i18n.EN: "Services outside scope of tax", + }, + }, + { + Value: "S", + Name: i18n.String{ + i18n.EN: "Standard Rate", + }, + }, + { + Value: "Z", + Name: i18n.String{ + i18n.EN: "Zero rated goods", + }, + }, + }, +} diff --git a/catalogues/untdid/untdid.go b/catalogues/untdid/untdid.go index 57cfdf67..1f79c807 100644 --- a/catalogues/untdid/untdid.go +++ b/catalogues/untdid/untdid.go @@ -2,6 +2,7 @@ package untdid import ( + "github.com/invopop/gobl/cbc" "github.com/invopop/gobl/i18n" "github.com/invopop/gobl/tax" ) @@ -12,8 +13,14 @@ func init() { func newCatalogue() *tax.CatalogueDef { return &tax.CatalogueDef{ - Key: "untdid", - Name: i18n.NewString("UN/EDIFACT Data Elements"), - Extensions: extensions, + Key: "untdid", + Name: i18n.NewString("UN/EDIFACT Data Elements"), + Extensions: []*cbc.KeyDefinition{ + extDocumentTypes, // 1001 + extPaymentMeans, // 4461 + extAllowance, // 5189 + extTaxCategory, // 5305 + extCharge, // 7161 + }, } } diff --git a/cbc/code.go b/cbc/code.go index 34a67f57..8cb8aebd 100644 --- a/cbc/code.go +++ b/cbc/code.go @@ -20,9 +20,9 @@ const ( // be more easily set and used by humans within definitions than IDs or UUIDs. // Codes are standardised so that when validated they must contain between // 1 and 32 inclusive english alphabet letters or numbers with optional -// periods (`.`), dashes (`-`), underscores (`_`), forward slashes (`/`), or -// spaces (` `) to separate blocks. Each block must only be separated by a -// single symbol. +// periods (`.`), dashes (`-`), underscores (`_`), forward slashes (`/`), +// colons (`:`) or spaces (` `) to separate blocks. +// Each block must only be separated by a single symbol. // // The objective is to have a code that is easy to read and understand, while // still being unique and easy to validate. @@ -34,15 +34,15 @@ type CodeMap map[Key]Code // Basic code constants. var ( - CodePattern = `^[A-Za-z0-9]+([\.\-\/ _]?[A-Za-z0-9]+)*$` + CodePattern = `^[A-Za-z0-9]+([\.\-\/ _\:]?[A-Za-z0-9]+)*$` CodePatternRegexp = regexp.MustCompile(CodePattern) CodeMinLength uint64 = 1 CodeMaxLength uint64 = 32 ) var ( - codeSeparatorRegexp = regexp.MustCompile(`([\.\-\/ _])[^A-Za-z0-9]+`) - codeInvalidCharsRegexp = regexp.MustCompile(`[^A-Za-z0-9\.\-\/ _]`) + codeSeparatorRegexp = regexp.MustCompile(`([\.\-\/ _\:])[^A-Za-z0-9]+`) + codeInvalidCharsRegexp = regexp.MustCompile(`[^A-Za-z0-9\.\-\/ _\:]`) codeNonAlphanumericalRegexp = regexp.MustCompile(`[^A-Z\d]`) codeNonNumericalRegexp = regexp.MustCompile(`[^\d]`) ) diff --git a/cbc/code_test.go b/cbc/code_test.go index 45505643..ab4100c9 100644 --- a/cbc/code_test.go +++ b/cbc/code_test.go @@ -103,6 +103,11 @@ func TestNormalizeCode(t *testing.T) { code: cbc.Code("FOO BAR--DOME"), want: cbc.Code("FOO BAR-DOME"), }, + { + name: "colons", + code: cbc.Code("0088:1234567891234"), // peppol example + want: cbc.Code("0088:1234567891234"), + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -281,6 +286,10 @@ func TestCode_Validate(t *testing.T) { name: "valid with space", code: cbc.Code("FR 12/BX"), }, + { + name: "valid with colon", + code: cbc.Code("FR:12/BX"), + }, { name: "empty", code: cbc.Code(""), diff --git a/cmd/gobl/testdata/Test_build_do_not_envelop b/cmd/gobl/testdata/Test_build_do_not_envelop index f7aa9196..ac4967b9 100644 --- a/cmd/gobl/testdata/Test_build_do_not_envelop +++ b/cmd/gobl/testdata/Test_build_do_not_envelop @@ -133,13 +133,6 @@ "total": "15.00" } ], - "outlays": [ - { - "i": 1, - "description": "Something paid for by us", - "amount": "0.00" - } - ], "payment": { "terms": { "key": "instant" @@ -195,7 +188,6 @@ }, "tax": "450.00", "total_with_tax": "5637.50", - "outlays": "0.00", "payable": "5637.50" } } diff --git a/cmd/gobl/testdata/Test_build_envelop b/cmd/gobl/testdata/Test_build_envelop index 568ced8e..dd121548 100644 --- a/cmd/gobl/testdata/Test_build_envelop +++ b/cmd/gobl/testdata/Test_build_envelop @@ -4,7 +4,7 @@ "uuid": "00000000-0000-0000-0000-000000000000", "dig": { "alg": "sha256", - "val": "38fd37f6c05f2d0fe2f52e1c4bf8bb7f6c6fe289e03e07f9e0948d54794efd3d" + "val": "bb73b39a60e535d5cc02e5b4661b8e8029731e4613aab98389c87f6b9681c699" } }, "doc": { @@ -142,13 +142,6 @@ "total": "15.00" } ], - "outlays": [ - { - "i": 1, - "description": "Something paid for by us", - "amount": "0.00" - } - ], "payment": { "terms": { "key": "instant" @@ -204,7 +197,6 @@ }, "tax": "450.00", "total_with_tax": "5637.50", - "outlays": "0.00", "payable": "5637.50" } } diff --git a/cmd/gobl/testdata/Test_build_explicit_stdout b/cmd/gobl/testdata/Test_build_explicit_stdout index 564a2bfe..157f1a98 100644 --- a/cmd/gobl/testdata/Test_build_explicit_stdout +++ b/cmd/gobl/testdata/Test_build_explicit_stdout @@ -65,9 +65,9 @@ "sum": "1800.00", "discounts": [ { + "reason": "Special discount", "percent": "10%", - "amount": "180.00", - "reason": "Special discount" + "amount": "180.00" } ], "taxes": [ diff --git a/cmd/gobl/testdata/Test_build_input_file b/cmd/gobl/testdata/Test_build_input_file index 564a2bfe..157f1a98 100644 --- a/cmd/gobl/testdata/Test_build_input_file +++ b/cmd/gobl/testdata/Test_build_input_file @@ -65,9 +65,9 @@ "sum": "1800.00", "discounts": [ { + "reason": "Special discount", "percent": "10%", - "amount": "180.00", - "reason": "Special discount" + "amount": "180.00" } ], "taxes": [ diff --git a/cmd/gobl/testdata/Test_build_merge_values b/cmd/gobl/testdata/Test_build_merge_values index 89068d91..dd9ec1a7 100644 --- a/cmd/gobl/testdata/Test_build_merge_values +++ b/cmd/gobl/testdata/Test_build_merge_values @@ -65,9 +65,9 @@ "sum": "1800.00", "discounts": [ { + "reason": "Special discount", "percent": "10%", - "amount": "180.00", - "reason": "Special discount" + "amount": "180.00" } ], "taxes": [ diff --git a/cmd/gobl/testdata/Test_build_output_file_outfile b/cmd/gobl/testdata/Test_build_output_file_outfile index 564a2bfe..157f1a98 100644 --- a/cmd/gobl/testdata/Test_build_output_file_outfile +++ b/cmd/gobl/testdata/Test_build_output_file_outfile @@ -65,9 +65,9 @@ "sum": "1800.00", "discounts": [ { + "reason": "Special discount", "percent": "10%", - "amount": "180.00", - "reason": "Special discount" + "amount": "180.00" } ], "taxes": [ diff --git a/cmd/gobl/testdata/Test_build_overwrite_input_file_outfile b/cmd/gobl/testdata/Test_build_overwrite_input_file_outfile index 9a177a46..18aaffe1 100644 --- a/cmd/gobl/testdata/Test_build_overwrite_input_file_outfile +++ b/cmd/gobl/testdata/Test_build_overwrite_input_file_outfile @@ -4,7 +4,7 @@ "uuid": "9d8eafd5-77be-11ec-b485-5405db9a3e49", "dig": { "alg": "sha256", - "val": "38fd37f6c05f2d0fe2f52e1c4bf8bb7f6c6fe289e03e07f9e0948d54794efd3d" + "val": "bb73b39a60e535d5cc02e5b4661b8e8029731e4613aab98389c87f6b9681c699" } }, "doc": { @@ -142,13 +142,6 @@ "total": "15.00" } ], - "outlays": [ - { - "i": 1, - "description": "Something paid for by us", - "amount": "0.00" - } - ], "payment": { "terms": { "key": "instant" @@ -204,7 +197,6 @@ }, "tax": "450.00", "total_with_tax": "5637.50", - "outlays": "0.00", "payable": "5637.50" } } diff --git a/cmd/gobl/testdata/Test_build_overwrite_output_file_outfile b/cmd/gobl/testdata/Test_build_overwrite_output_file_outfile index 564a2bfe..157f1a98 100644 --- a/cmd/gobl/testdata/Test_build_overwrite_output_file_outfile +++ b/cmd/gobl/testdata/Test_build_overwrite_output_file_outfile @@ -65,9 +65,9 @@ "sum": "1800.00", "discounts": [ { + "reason": "Special discount", "percent": "10%", - "amount": "180.00", - "reason": "Special discount" + "amount": "180.00" } ], "taxes": [ diff --git a/cmd/gobl/testdata/Test_build_recalculate b/cmd/gobl/testdata/Test_build_recalculate index a266975a..0367f2cb 100644 --- a/cmd/gobl/testdata/Test_build_recalculate +++ b/cmd/gobl/testdata/Test_build_recalculate @@ -4,7 +4,7 @@ "uuid": "9d8eafd5-77be-11ec-b485-5405db9a3e49", "dig": { "alg": "sha256", - "val": "e47164849e23ea243e5a76d9503e861d6af4a715d9211f70b7606feaf6f5de40" + "val": "7ea57c3e785626d1c68b32a83d47299a70794799b5bb10be96c942171e09873c" } }, "doc": { @@ -139,13 +139,6 @@ "total": "15.00" } ], - "outlays": [ - { - "i": 1, - "description": "Something paid for by us", - "amount": "0.00" - } - ], "payment": { "terms": { "key": "instant" @@ -199,7 +192,6 @@ }, "tax": "450.00", "total_with_tax": "5637.50", - "outlays": "0.00", "payable": "5637.50" } } diff --git a/cmd/gobl/testdata/Test_build_success b/cmd/gobl/testdata/Test_build_success index a266975a..0367f2cb 100644 --- a/cmd/gobl/testdata/Test_build_success +++ b/cmd/gobl/testdata/Test_build_success @@ -4,7 +4,7 @@ "uuid": "9d8eafd5-77be-11ec-b485-5405db9a3e49", "dig": { "alg": "sha256", - "val": "e47164849e23ea243e5a76d9503e861d6af4a715d9211f70b7606feaf6f5de40" + "val": "7ea57c3e785626d1c68b32a83d47299a70794799b5bb10be96c942171e09873c" } }, "doc": { @@ -139,13 +139,6 @@ "total": "15.00" } ], - "outlays": [ - { - "i": 1, - "description": "Something paid for by us", - "amount": "0.00" - } - ], "payment": { "terms": { "key": "instant" @@ -199,7 +192,6 @@ }, "tax": "450.00", "total_with_tax": "5637.50", - "outlays": "0.00", "payable": "5637.50" } } diff --git a/cmd/gobl/testdata/Test_build_valid_file b/cmd/gobl/testdata/Test_build_valid_file index 12e5b331..c21e65a8 100644 --- a/cmd/gobl/testdata/Test_build_valid_file +++ b/cmd/gobl/testdata/Test_build_valid_file @@ -4,7 +4,7 @@ "uuid": "9d8eafd5-77be-11ec-b485-5405db9a3e49", "dig": { "alg": "sha256", - "val": "cd45a3373b70f8cbf1247dc25698baba8462dd6c47781e66b790490f129a87ce" + "val": "68715e28422644b7d2ce4fc0f88fd47febcb581cfc8d3f1e8980a849e3235cfc" } }, "doc": { @@ -139,13 +139,6 @@ "total": "15.00" } ], - "outlays": [ - { - "i": 1, - "description": "Something paid for by us", - "amount": "0.00" - } - ], "payment": { "terms": { "key": "instant" @@ -199,7 +192,6 @@ }, "tax": "450.00", "total_with_tax": "5637.50", - "outlays": "0.00", "payable": "5637.50" } } diff --git a/cmd/gobl/testdata/Test_sign_explicit_stdout b/cmd/gobl/testdata/Test_sign_explicit_stdout index cdc9d1d2..c7370d63 100644 --- a/cmd/gobl/testdata/Test_sign_explicit_stdout +++ b/cmd/gobl/testdata/Test_sign_explicit_stdout @@ -65,9 +65,9 @@ "sum": "1800.00", "discounts": [ { + "reason": "Special discount", "percent": "10%", - "amount": "180.00", - "reason": "Special discount" + "amount": "180.00" } ], "taxes": [ diff --git a/cmd/gobl/testdata/Test_sign_input_file b/cmd/gobl/testdata/Test_sign_input_file index cdc9d1d2..c7370d63 100644 --- a/cmd/gobl/testdata/Test_sign_input_file +++ b/cmd/gobl/testdata/Test_sign_input_file @@ -65,9 +65,9 @@ "sum": "1800.00", "discounts": [ { + "reason": "Special discount", "percent": "10%", - "amount": "180.00", - "reason": "Special discount" + "amount": "180.00" } ], "taxes": [ diff --git a/cmd/gobl/testdata/Test_sign_merge_values b/cmd/gobl/testdata/Test_sign_merge_values index ec97d3cf..abf20708 100644 --- a/cmd/gobl/testdata/Test_sign_merge_values +++ b/cmd/gobl/testdata/Test_sign_merge_values @@ -65,9 +65,9 @@ "sum": "1800.00", "discounts": [ { + "reason": "Special discount", "percent": "10%", - "amount": "180.00", - "reason": "Special discount" + "amount": "180.00" } ], "taxes": [ diff --git a/cmd/gobl/testdata/Test_sign_output_file_outfile b/cmd/gobl/testdata/Test_sign_output_file_outfile index cdc9d1d2..c7370d63 100644 --- a/cmd/gobl/testdata/Test_sign_output_file_outfile +++ b/cmd/gobl/testdata/Test_sign_output_file_outfile @@ -65,9 +65,9 @@ "sum": "1800.00", "discounts": [ { + "reason": "Special discount", "percent": "10%", - "amount": "180.00", - "reason": "Special discount" + "amount": "180.00" } ], "taxes": [ diff --git a/cmd/gobl/testdata/Test_sign_overwrite_input_file_outfile b/cmd/gobl/testdata/Test_sign_overwrite_input_file_outfile index b2cd9564..386f80a7 100644 --- a/cmd/gobl/testdata/Test_sign_overwrite_input_file_outfile +++ b/cmd/gobl/testdata/Test_sign_overwrite_input_file_outfile @@ -4,7 +4,7 @@ "uuid": "9d8eafd5-77be-11ec-b485-5405db9a3e49", "dig": { "alg": "sha256", - "val": "38fd37f6c05f2d0fe2f52e1c4bf8bb7f6c6fe289e03e07f9e0948d54794efd3d" + "val": "bb73b39a60e535d5cc02e5b4661b8e8029731e4613aab98389c87f6b9681c699" } }, "doc": { @@ -142,13 +142,6 @@ "total": "15.00" } ], - "outlays": [ - { - "i": 1, - "description": "Something paid for by us", - "amount": "0.00" - } - ], "payment": { "terms": { "key": "instant" @@ -204,7 +197,6 @@ }, "tax": "450.00", "total_with_tax": "5637.50", - "outlays": "0.00", "payable": "5637.50" } }, diff --git a/cmd/gobl/testdata/Test_sign_overwrite_output_file_outfile b/cmd/gobl/testdata/Test_sign_overwrite_output_file_outfile index cdc9d1d2..c7370d63 100644 --- a/cmd/gobl/testdata/Test_sign_overwrite_output_file_outfile +++ b/cmd/gobl/testdata/Test_sign_overwrite_output_file_outfile @@ -65,9 +65,9 @@ "sum": "1800.00", "discounts": [ { + "reason": "Special discount", "percent": "10%", - "amount": "180.00", - "reason": "Special discount" + "amount": "180.00" } ], "taxes": [ diff --git a/cmd/gobl/testdata/Test_sign_recalculate b/cmd/gobl/testdata/Test_sign_recalculate index 7f532d19..876d2078 100644 --- a/cmd/gobl/testdata/Test_sign_recalculate +++ b/cmd/gobl/testdata/Test_sign_recalculate @@ -4,7 +4,7 @@ "uuid": "9d8eafd5-77be-11ec-b485-5405db9a3e49", "dig": { "alg": "sha256", - "val": "e47164849e23ea243e5a76d9503e861d6af4a715d9211f70b7606feaf6f5de40" + "val": "7ea57c3e785626d1c68b32a83d47299a70794799b5bb10be96c942171e09873c" } }, "doc": { @@ -139,13 +139,6 @@ "total": "15.00" } ], - "outlays": [ - { - "i": 1, - "description": "Something paid for by us", - "amount": "0.00" - } - ], "payment": { "terms": { "key": "instant" @@ -199,7 +192,6 @@ }, "tax": "450.00", "total_with_tax": "5637.50", - "outlays": "0.00", "payable": "5637.50" } }, diff --git a/cmd/gobl/testdata/Test_sign_success b/cmd/gobl/testdata/Test_sign_success index 7f532d19..876d2078 100644 --- a/cmd/gobl/testdata/Test_sign_success +++ b/cmd/gobl/testdata/Test_sign_success @@ -4,7 +4,7 @@ "uuid": "9d8eafd5-77be-11ec-b485-5405db9a3e49", "dig": { "alg": "sha256", - "val": "e47164849e23ea243e5a76d9503e861d6af4a715d9211f70b7606feaf6f5de40" + "val": "7ea57c3e785626d1c68b32a83d47299a70794799b5bb10be96c942171e09873c" } }, "doc": { @@ -139,13 +139,6 @@ "total": "15.00" } ], - "outlays": [ - { - "i": 1, - "description": "Something paid for by us", - "amount": "0.00" - } - ], "payment": { "terms": { "key": "instant" @@ -199,7 +192,6 @@ }, "tax": "450.00", "total_with_tax": "5637.50", - "outlays": "0.00", "payable": "5637.50" } }, diff --git a/cmd/gobl/testdata/Test_sign_valid_file b/cmd/gobl/testdata/Test_sign_valid_file index bdd090ba..3025131f 100644 --- a/cmd/gobl/testdata/Test_sign_valid_file +++ b/cmd/gobl/testdata/Test_sign_valid_file @@ -4,7 +4,7 @@ "uuid": "9d8eafd5-77be-11ec-b485-5405db9a3e49", "dig": { "alg": "sha256", - "val": "cd45a3373b70f8cbf1247dc25698baba8462dd6c47781e66b790490f129a87ce" + "val": "68715e28422644b7d2ce4fc0f88fd47febcb581cfc8d3f1e8980a849e3235cfc" } }, "doc": { @@ -139,13 +139,6 @@ "total": "15.00" } ], - "outlays": [ - { - "i": 1, - "description": "Something paid for by us", - "amount": "0.00" - } - ], "payment": { "terms": { "key": "instant" @@ -199,7 +192,6 @@ }, "tax": "450.00", "total_with_tax": "5637.50", - "outlays": "0.00", "payable": "5637.50" } }, diff --git a/data/catalogues/untdid.json b/data/catalogues/untdid.json index b3074f37..677a46c7 100644 --- a/data/catalogues/untdid.json +++ b/data/catalogues/untdid.json @@ -856,6 +856,131 @@ } ] }, + { + "key": "untdid-allowance", + "name": { + "en": "UNTDID 5189 Allowance" + }, + "desc": { + "en": "UNTDID 5189 code used to describe the allowance type. This list is based on the\n[EN16931 code list](https://ec.europa.eu/digital-building-blocks/sites/display/DIGITAL/Registry+of+supporting+artefacts+to+implement+EN16931#RegistryofsupportingartefactstoimplementEN16931-Codelists)\nvalues table which focusses on invoices and payments." + }, + "values": [ + { + "value": "41", + "name": { + "en": "Bonus for works ahead of schedule" + } + }, + { + "value": "42", + "name": { + "en": "Other bonus" + } + }, + { + "value": "60", + "name": { + "en": "Manufacturer’s consumer discount" + } + }, + { + "value": "62", + "name": { + "en": "Due to military status" + } + }, + { + "value": "63", + "name": { + "en": "Due to work accident" + } + }, + { + "value": "64", + "name": { + "en": "Special agreement" + } + }, + { + "value": "65", + "name": { + "en": "Production error discount" + } + }, + { + "value": "66", + "name": { + "en": "New outlet discount" + } + }, + { + "value": "67", + "name": { + "en": "Sample discount" + } + }, + { + "value": "68", + "name": { + "en": "End-of-range discount" + } + }, + { + "value": "70", + "name": { + "en": "Incoterm discount" + } + }, + { + "value": "71", + "name": { + "en": "Point of sales threshold allowance" + } + }, + { + "value": "88", + "name": { + "en": "Material surcharge/deduction" + } + }, + { + "value": "95", + "name": { + "en": "Discount" + } + }, + { + "value": "100", + "name": { + "en": "Special rebate" + } + }, + { + "value": "102", + "name": { + "en": "Fixed long term" + } + }, + { + "value": "103", + "name": { + "en": "Temporary" + } + }, + { + "value": "104", + "name": { + "en": "Standard" + } + }, + { + "value": "105", + "name": { + "en": "Yearly turnover" + } + } + ] + }, { "key": "untdid-tax-category", "name": { @@ -992,6 +1117,1109 @@ } } ] + }, + { + "key": "untdid-charge", + "name": { + "en": "UNTDID 7161 Charge" + }, + "desc": { + "en": "UNTDID 7161 code used to describe the charge. List is based on the\nEN16931 code lists with extensions for taxes and duties." + }, + "values": [ + { + "value": "AA", + "name": { + "en": "Advertising" + } + }, + { + "value": "AAA", + "name": { + "en": "Telecommunication" + } + }, + { + "value": "AAC", + "name": { + "en": "Technical modification" + } + }, + { + "value": "AAD", + "name": { + "en": "Job-order production" + } + }, + { + "value": "AAE", + "name": { + "en": "Outlays" + } + }, + { + "value": "AAF", + "name": { + "en": "Off-premises" + } + }, + { + "value": "AAH", + "name": { + "en": "Additional processing" + } + }, + { + "value": "AAI", + "name": { + "en": "Attesting" + } + }, + { + "value": "AAS", + "name": { + "en": "Acceptance" + } + }, + { + "value": "AAT", + "name": { + "en": "Rush delivery" + } + }, + { + "value": "AAV", + "name": { + "en": "Special construction" + } + }, + { + "value": "AAY", + "name": { + "en": "Airport facilities" + } + }, + { + "value": "AAZ", + "name": { + "en": "Concession" + } + }, + { + "value": "ABA", + "name": { + "en": "Compulsory storage" + } + }, + { + "value": "ABB", + "name": { + "en": "Fuel removal" + } + }, + { + "value": "ABC", + "name": { + "en": "Into plane" + } + }, + { + "value": "ABD", + "name": { + "en": "Overtime" + } + }, + { + "value": "ABF", + "name": { + "en": "Tooling" + } + }, + { + "value": "ABK", + "name": { + "en": "Miscellaneous" + } + }, + { + "value": "ABL", + "name": { + "en": "Additional packaging" + } + }, + { + "value": "ABN", + "name": { + "en": "Dunnage" + } + }, + { + "value": "ABR", + "name": { + "en": "Containerisation" + } + }, + { + "value": "ABS", + "name": { + "en": "Carton packing" + } + }, + { + "value": "ABT", + "name": { + "en": "Hessian wrapped" + } + }, + { + "value": "ABU", + "name": { + "en": "Polyethylene wrap packing" + } + }, + { + "value": "ABW", + "name": { + "en": "Customs duty charge" + } + }, + { + "value": "ACF", + "name": { + "en": "Miscellaneous treatment" + } + }, + { + "value": "ACG", + "name": { + "en": "Enamelling treatment" + } + }, + { + "value": "ACH", + "name": { + "en": "Heat treatment" + } + }, + { + "value": "ACI", + "name": { + "en": "Plating treatment" + } + }, + { + "value": "ACJ", + "name": { + "en": "Painting" + } + }, + { + "value": "ACK", + "name": { + "en": "Polishing" + } + }, + { + "value": "ACL", + "name": { + "en": "Priming" + } + }, + { + "value": "ACM", + "name": { + "en": "Preservation treatment" + } + }, + { + "value": "ACS", + "name": { + "en": "Fitting" + } + }, + { + "value": "ADC", + "name": { + "en": "Consolidation" + } + }, + { + "value": "ADE", + "name": { + "en": "Bill of lading" + } + }, + { + "value": "ADJ", + "name": { + "en": "Airbag" + } + }, + { + "value": "ADK", + "name": { + "en": "Transfer" + } + }, + { + "value": "ADL", + "name": { + "en": "Slipsheet" + } + }, + { + "value": "ADM", + "name": { + "en": "Binding" + } + }, + { + "value": "ADN", + "name": { + "en": "Repair or replacement of broken returnable package" + } + }, + { + "value": "ADO", + "name": { + "en": "Efficient logistics" + } + }, + { + "value": "ADP", + "name": { + "en": "Merchandising" + } + }, + { + "value": "ADQ", + "name": { + "en": "Product mix" + } + }, + { + "value": "ADR", + "name": { + "en": "Other services" + } + }, + { + "value": "ADT", + "name": { + "en": "Pick-up" + } + }, + { + "value": "ADW", + "name": { + "en": "Chronic illness" + } + }, + { + "value": "ADY", + "name": { + "en": "New product introduction" + } + }, + { + "value": "ADZ", + "name": { + "en": "Direct delivery" + } + }, + { + "value": "AEA", + "name": { + "en": "Diversion" + } + }, + { + "value": "AEB", + "name": { + "en": "Disconnect" + } + }, + { + "value": "AEC", + "name": { + "en": "Distribution" + } + }, + { + "value": "AED", + "name": { + "en": "Handling of hazardous cargo" + } + }, + { + "value": "AEF", + "name": { + "en": "Rents and leases" + } + }, + { + "value": "AEH", + "name": { + "en": "Location differential" + } + }, + { + "value": "AEI", + "name": { + "en": "Aircraft refueling" + } + }, + { + "value": "AEJ", + "name": { + "en": "Fuel shipped into storage" + } + }, + { + "value": "AEK", + "name": { + "en": "Cash on delivery" + } + }, + { + "value": "AEL", + "name": { + "en": "Small order processing service" + } + }, + { + "value": "AEM", + "name": { + "en": "Clerical or administrative services" + } + }, + { + "value": "AEN", + "name": { + "en": "Guarantee" + } + }, + { + "value": "AEO", + "name": { + "en": "Collection and recycling" + } + }, + { + "value": "AEP", + "name": { + "en": "Copyright fee collection" + } + }, + { + "value": "AES", + "name": { + "en": "Veterinary inspection service" + } + }, + { + "value": "AET", + "name": { + "en": "Pensioner service" + } + }, + { + "value": "AEU", + "name": { + "en": "Medicine free pass holder" + } + }, + { + "value": "AEV", + "name": { + "en": "Environmental protection service" + } + }, + { + "value": "AEW", + "name": { + "en": "Environmental clean-up service" + } + }, + { + "value": "AEX", + "name": { + "en": "National cheque processing service outside account area" + } + }, + { + "value": "AEY", + "name": { + "en": "National payment service outside account area" + } + }, + { + "value": "AEZ", + "name": { + "en": "National payment service within account area" + } + }, + { + "value": "AJ", + "name": { + "en": "Adjustments" + } + }, + { + "value": "AU", + "name": { + "en": "Authentication" + } + }, + { + "value": "CA", + "name": { + "en": "Cataloguing" + } + }, + { + "value": "CAB", + "name": { + "en": "Cartage" + } + }, + { + "value": "CAD", + "name": { + "en": "Certification" + } + }, + { + "value": "CAE", + "name": { + "en": "Certificate of conformance" + } + }, + { + "value": "CAF", + "name": { + "en": "Certificate of origin" + } + }, + { + "value": "CAI", + "name": { + "en": "Cutting" + } + }, + { + "value": "CAJ", + "name": { + "en": "Consular service" + } + }, + { + "value": "CAK", + "name": { + "en": "Customer collection" + } + }, + { + "value": "CAL", + "name": { + "en": "Payroll payment service" + } + }, + { + "value": "CAM", + "name": { + "en": "Cash transportation" + } + }, + { + "value": "CAN", + "name": { + "en": "Home banking service" + } + }, + { + "value": "CAO", + "name": { + "en": "Bilateral agreement service" + } + }, + { + "value": "CAP", + "name": { + "en": "Insurance brokerage service" + } + }, + { + "value": "CAQ", + "name": { + "en": "Cheque generation" + } + }, + { + "value": "CAR", + "name": { + "en": "Preferential merchandising location" + } + }, + { + "value": "CAS", + "name": { + "en": "Crane" + } + }, + { + "value": "CAT", + "name": { + "en": "Special colour service" + } + }, + { + "value": "CAU", + "name": { + "en": "Sorting" + } + }, + { + "value": "CAV", + "name": { + "en": "Battery collection and recycling" + } + }, + { + "value": "CAW", + "name": { + "en": "Product take back fee" + } + }, + { + "value": "CAX", + "name": { + "en": "Quality control released" + } + }, + { + "value": "CAY", + "name": { + "en": "Quality control held" + } + }, + { + "value": "CAZ", + "name": { + "en": "Quality control embargo" + } + }, + { + "value": "CD", + "name": { + "en": "Car loading" + } + }, + { + "value": "CG", + "name": { + "en": "Cleaning" + } + }, + { + "value": "CS", + "name": { + "en": "Cigarette stamping" + } + }, + { + "value": "CT", + "name": { + "en": "Count and recount" + } + }, + { + "value": "DAB", + "name": { + "en": "Layout/design" + } + }, + { + "value": "DAC", + "name": { + "en": "Assortment allowance" + } + }, + { + "value": "DAD", + "name": { + "en": "Driver assigned unloading" + } + }, + { + "value": "DAF", + "name": { + "en": "Debtor bound" + } + }, + { + "value": "DAG", + "name": { + "en": "Dealer allowance" + } + }, + { + "value": "DAH", + "name": { + "en": "Allowance transferable to the consumer" + } + }, + { + "value": "DAI", + "name": { + "en": "Growth of business" + } + }, + { + "value": "DAJ", + "name": { + "en": "Introduction allowance" + } + }, + { + "value": "DAK", + "name": { + "en": "Multi-buy promotion" + } + }, + { + "value": "DAL", + "name": { + "en": "Partnership" + } + }, + { + "value": "DAM", + "name": { + "en": "Return handling" + } + }, + { + "value": "DAN", + "name": { + "en": "Minimum order not fulfilled charge" + } + }, + { + "value": "DAO", + "name": { + "en": "Point of sales threshold allowance" + } + }, + { + "value": "DAP", + "name": { + "en": "Wholesaling discount" + } + }, + { + "value": "DAQ", + "name": { + "en": "Documentary credits transfer commission" + } + }, + { + "value": "DL", + "name": { + "en": "Delivery" + } + }, + { + "value": "EG", + "name": { + "en": "Engraving" + } + }, + { + "value": "EP", + "name": { + "en": "Expediting" + } + }, + { + "value": "ER", + "name": { + "en": "Exchange rate guarantee" + } + }, + { + "value": "FAA", + "name": { + "en": "Fabrication" + } + }, + { + "value": "FAB", + "name": { + "en": "Freight equalization" + } + }, + { + "value": "FAC", + "name": { + "en": "Freight extraordinary handling" + } + }, + { + "value": "FC", + "name": { + "en": "Freight service" + } + }, + { + "value": "FH", + "name": { + "en": "Filling/handling" + } + }, + { + "value": "FI", + "name": { + "en": "Financing" + } + }, + { + "value": "GAA", + "name": { + "en": "Grinding" + } + }, + { + "value": "HAA", + "name": { + "en": "Hose" + } + }, + { + "value": "HD", + "name": { + "en": "Handling" + } + }, + { + "value": "HH", + "name": { + "en": "Hoisting and hauling" + } + }, + { + "value": "IAA", + "name": { + "en": "Installation" + } + }, + { + "value": "IAB", + "name": { + "en": "Installation and warranty" + } + }, + { + "value": "ID", + "name": { + "en": "Inside delivery" + } + }, + { + "value": "IF", + "name": { + "en": "Inspection" + } + }, + { + "value": "IN", + "name": { + "en": "Insurance" + } + }, + { + "value": "IR", + "name": { + "en": "Installation and training" + } + }, + { + "value": "IS", + "name": { + "en": "Invoicing" + } + }, + { + "value": "KO", + "name": { + "en": "Koshering" + } + }, + { + "value": "L1", + "name": { + "en": "Carrier count" + } + }, + { + "value": "LA", + "name": { + "en": "Labelling" + } + }, + { + "value": "LAA", + "name": { + "en": "Labour" + } + }, + { + "value": "LAB", + "name": { + "en": "Repair and return" + } + }, + { + "value": "LF", + "name": { + "en": "Legalisation" + } + }, + { + "value": "MAE", + "name": { + "en": "Mounting" + } + }, + { + "value": "MI", + "name": { + "en": "Mail invoice" + } + }, + { + "value": "ML", + "name": { + "en": "Mail invoice to each location" + } + }, + { + "value": "NAA", + "name": { + "en": "Non-returnable containers" + } + }, + { + "value": "OA", + "name": { + "en": "Outside cable connectors" + } + }, + { + "value": "PA", + "name": { + "en": "Invoice with shipment" + } + }, + { + "value": "PAA", + "name": { + "en": "Phosphatizing (steel treatment)" + } + }, + { + "value": "PC", + "name": { + "en": "Packing" + } + }, + { + "value": "PL", + "name": { + "en": "Palletizing" + } + }, + { + "value": "PRV", + "name": { + "en": "Price variation" + } + }, + { + "value": "RAB", + "name": { + "en": "Repacking" + } + }, + { + "value": "RAC", + "name": { + "en": "Repair" + } + }, + { + "value": "RAD", + "name": { + "en": "Returnable container" + } + }, + { + "value": "RAF", + "name": { + "en": "Restocking" + } + }, + { + "value": "RE", + "name": { + "en": "Re-delivery" + } + }, + { + "value": "RF", + "name": { + "en": "Refurbishing" + } + }, + { + "value": "RH", + "name": { + "en": "Rail wagon hire" + } + }, + { + "value": "RV", + "name": { + "en": "Loading" + } + }, + { + "value": "SA", + "name": { + "en": "Salvaging" + } + }, + { + "value": "SAA", + "name": { + "en": "Shipping and handling" + } + }, + { + "value": "SAD", + "name": { + "en": "Special packaging" + } + }, + { + "value": "SAE", + "name": { + "en": "Stamping" + } + }, + { + "value": "SAI", + "name": { + "en": "Consignee unload" + } + }, + { + "value": "SG", + "name": { + "en": "Shrink-wrap" + } + }, + { + "value": "SH", + "name": { + "en": "Special handling" + } + }, + { + "value": "SM", + "name": { + "en": "Special finish" + } + }, + { + "value": "ST", + "name": { + "en": "Stamp duties" + } + }, + { + "value": "SU", + "name": { + "en": "Set-up" + } + }, + { + "value": "TAB", + "name": { + "en": "Tank renting" + } + }, + { + "value": "TAC", + "name": { + "en": "Testing" + } + }, + { + "value": "TT", + "name": { + "en": "Transportation - third party billing" + } + }, + { + "value": "TV", + "name": { + "en": "Transportation by vendor" + } + }, + { + "value": "TX", + "name": { + "en": "Tax" + } + }, + { + "value": "V1", + "name": { + "en": "Drop yard" + } + }, + { + "value": "V2", + "name": { + "en": "Drop dock" + } + }, + { + "value": "WH", + "name": { + "en": "Warehousing" + } + }, + { + "value": "XAA", + "name": { + "en": "Combine all same day shipment" + } + }, + { + "value": "YY", + "name": { + "en": "Split pick-up" + } + }, + { + "value": "ZZZ", + "name": { + "en": "Mutually defined" + } + } + ] } ] } \ No newline at end of file diff --git a/data/regimes/it.json b/data/regimes/it.json index a12c65ec..ded4f53d 100644 --- a/data/regimes/it.json +++ b/data/regimes/it.json @@ -74,19 +74,6 @@ } } ], - "charge_keys": [ - { - "key": "stamp-duty", - "name": { - "en": "Stamp Duty", - "it": "Imposta di bollo" - }, - "desc": { - "en": "A fixed-price tax applied to the production, request or presentation of certain documents: civil, commercial, judicial and extrajudicial documents, on notices, on posters.", - "it": "Un'imposta applicata alla produzione, richiesta o presentazione di determinati documenti: atti civili, commerciali, giudiziali ed extragiudiziali, sugli avvisi, sui manifesti." - } - } - ], "scenarios": [ { "schema": "bill/invoice", diff --git a/data/schemas/bill/invoice.json b/data/schemas/bill/invoice.json index 348cde88..cc5574fc 100644 --- a/data/schemas/bill/invoice.json +++ b/data/schemas/bill/invoice.json @@ -11,21 +11,76 @@ "title": "UUID", "description": "Universally Unique Identifier." }, - "key": { - "$ref": "https://gobl.org/draft-0/cbc/key", - "title": "Key", - "description": "Key for grouping or identifying charges for tax purposes." - }, "i": { "type": "integer", "title": "Index", "description": "Line number inside the list of charges (calculated).", "calculated": true }, - "ref": { + "key": { + "$ref": "https://gobl.org/draft-0/cbc/key", + "anyOf": [ + { + "const": "stamp-duty", + "title": "Stamp Duty" + }, + { + "const": "outlay", + "title": "Outlay" + }, + { + "const": "tax", + "title": "Tax" + }, + { + "const": "customs", + "title": "Customs" + }, + { + "const": "delivery", + "title": "Delivery" + }, + { + "const": "packing", + "title": "Packing" + }, + { + "const": "handling", + "title": "Handling" + }, + { + "const": "insurance", + "title": "Insurance" + }, + { + "const": "storage", + "title": "Storage" + }, + { + "const": "admin", + "title": "Administration" + }, + { + "const": "cleaning", + "title": "Cleaning" + }, + { + "pattern": "^(?:[a-z]|[a-z0-9][a-z0-9-+]*[a-z0-9])$", + "title": "Other" + } + ], + "title": "Key", + "description": "Key for grouping or identifying charges for tax purposes. A suggested list of\nkeys is provided, but these may be extended by the issuer." + }, + "code": { + "$ref": "https://gobl.org/draft-0/cbc/code", + "title": "Code", + "description": "Code to used to refer to the this charge by the issuer" + }, + "reason": { "type": "string", - "title": "Reference", - "description": "Code to used to refer to the this charge" + "title": "Reason", + "description": "Text description as to why the charge was applied" }, "base": { "$ref": "https://gobl.org/draft-0/num/amount", @@ -35,7 +90,7 @@ "percent": { "$ref": "https://gobl.org/draft-0/num/percentage", "title": "Percent", - "description": "Percentage to apply to the Base or Invoice Sum" + "description": "Percentage to apply to the sum of all lines" }, "amount": { "$ref": "https://gobl.org/draft-0/num/amount", @@ -48,15 +103,10 @@ "title": "Taxes", "description": "List of taxes to apply to the charge" }, - "code": { - "type": "string", - "title": "Reason Code", - "description": "Code for why was this charge applied?" - }, - "reason": { - "type": "string", - "title": "Reason", - "description": "Text description as to why the charge was applied" + "ext": { + "$ref": "https://gobl.org/draft-0/tax/extensions", + "title": "Extensions", + "description": "Extension codes that apply to the charge" }, "meta": { "$ref": "https://gobl.org/draft-0/cbc/meta", @@ -119,10 +169,82 @@ "description": "Line number inside the list of discounts (calculated)", "calculated": true }, - "ref": { + "key": { + "$ref": "https://gobl.org/draft-0/cbc/key", + "anyOf": [ + { + "const": "early-completion", + "title": "Bonus for works ahead of schedule" + }, + { + "const": "military", + "title": "Military Discount" + }, + { + "const": "work-accident", + "title": "Work Accident Discount" + }, + { + "const": "special-agreement", + "title": "Special Agreement Discount" + }, + { + "const": "production-error", + "title": "Production Error Discount" + }, + { + "const": "new-outlet", + "title": "New Outlet Discount" + }, + { + "const": "sample", + "title": "Sample Discount" + }, + { + "const": "end-of-range", + "title": "End of Range Discount" + }, + { + "const": "incoterm", + "title": "Incoterm Discount" + }, + { + "const": "pos-threshold", + "title": "Point of Sale Threshold Discount" + }, + { + "const": "special-rebate", + "title": "Special Rebate" + }, + { + "const": "temporary", + "title": "Temporary" + }, + { + "const": "standard", + "title": "Standard" + }, + { + "const": "yearly-turnover", + "title": "Yearly Turnover" + }, + { + "pattern": "^(?:[a-z]|[a-z0-9][a-z0-9-+]*[a-z0-9])$", + "title": "Other" + } + ], + "title": "Key", + "description": "Key for identifying the type of discount being applied." + }, + "code": { + "$ref": "https://gobl.org/draft-0/cbc/code", + "title": "Code", + "description": "Code to used to refer to the this discount by the issuer" + }, + "reason": { "type": "string", - "title": "Reference", - "description": "Reference or ID for this Discount" + "title": "Reason", + "description": "Text description as to why the discount was applied" }, "base": { "$ref": "https://gobl.org/draft-0/num/amount", @@ -145,15 +267,10 @@ "title": "Taxes", "description": "List of taxes to apply to the discount" }, - "code": { - "type": "string", - "title": "Reason Code", - "description": "Code for the reason this discount applied" - }, - "reason": { - "type": "string", - "title": "Reason", - "description": "Text description as to why the discount was applied" + "ext": { + "$ref": "https://gobl.org/draft-0/tax/extensions", + "title": "Extensions", + "description": "Extension codes that apply to the discount" }, "meta": { "$ref": "https://gobl.org/draft-0/cbc/meta", @@ -398,7 +515,7 @@ "supplier": { "$ref": "https://gobl.org/draft-0/org/party", "title": "Supplier", - "description": "The taxable entity supplying the goods or services." + "description": "The entity supplying the goods or services and usually responsible for paying taxes." }, "customer": { "$ref": "https://gobl.org/draft-0/org/party", @@ -429,14 +546,6 @@ "title": "Charges", "description": "Charges or surcharges applied to the complete invoice" }, - "outlays": { - "items": { - "$ref": "#/$defs/Outlay" - }, - "type": "array", - "title": "Outlays", - "description": "Expenses paid for by the supplier but invoiced directly to the customer." - }, "ordering": { "$ref": "#/$defs/Ordering", "title": "Ordering Details", @@ -573,6 +682,71 @@ }, "LineCharge": { "properties": { + "key": { + "$ref": "https://gobl.org/draft-0/cbc/key", + "anyOf": [ + { + "const": "stamp-duty", + "title": "Stamp Duty" + }, + { + "const": "outlay", + "title": "Outlay" + }, + { + "const": "tax", + "title": "Tax" + }, + { + "const": "customs", + "title": "Customs" + }, + { + "const": "delivery", + "title": "Delivery" + }, + { + "const": "packing", + "title": "Packing" + }, + { + "const": "handling", + "title": "Handling" + }, + { + "const": "insurance", + "title": "Insurance" + }, + { + "const": "storage", + "title": "Storage" + }, + { + "const": "admin", + "title": "Administration" + }, + { + "const": "cleaning", + "title": "Cleaning" + }, + { + "pattern": "^(?:[a-z]|[a-z0-9][a-z0-9-+]*[a-z0-9])$", + "title": "Other" + } + ], + "title": "Key", + "description": "Key for grouping or identifying charges for tax purposes. A suggested list of\nkeys is provided, but these are for reference only and may be extended by\nthe issuer." + }, + "code": { + "$ref": "https://gobl.org/draft-0/cbc/code", + "title": "Code", + "description": "Reference or ID for this charge defined by the issuer" + }, + "reason": { + "type": "string", + "title": "Reason", + "description": "Text description as to why the charge was applied" + }, "percent": { "$ref": "https://gobl.org/draft-0/num/percentage", "title": "Percent", @@ -584,15 +758,10 @@ "description": "Fixed or resulting charge amount to apply (calculated if percent present).", "calculated": true }, - "code": { - "type": "string", - "title": "Code", - "description": "Reference code." - }, - "reason": { - "type": "string", - "title": "Reason", - "description": "Text description as to why the charge was applied" + "ext": { + "$ref": "https://gobl.org/draft-0/tax/extensions", + "title": "Extensions", + "description": "Extension codes that apply to the charge" } }, "type": "object", @@ -603,26 +772,98 @@ }, "LineDiscount": { "properties": { + "key": { + "$ref": "https://gobl.org/draft-0/cbc/key", + "anyOf": [ + { + "const": "early-completion", + "title": "Bonus for works ahead of schedule" + }, + { + "const": "military", + "title": "Military Discount" + }, + { + "const": "work-accident", + "title": "Work Accident Discount" + }, + { + "const": "special-agreement", + "title": "Special Agreement Discount" + }, + { + "const": "production-error", + "title": "Production Error Discount" + }, + { + "const": "new-outlet", + "title": "New Outlet Discount" + }, + { + "const": "sample", + "title": "Sample Discount" + }, + { + "const": "end-of-range", + "title": "End of Range Discount" + }, + { + "const": "incoterm", + "title": "Incoterm Discount" + }, + { + "const": "pos-threshold", + "title": "Point of Sale Threshold Discount" + }, + { + "const": "special-rebate", + "title": "Special Rebate" + }, + { + "const": "temporary", + "title": "Temporary" + }, + { + "const": "standard", + "title": "Standard" + }, + { + "const": "yearly-turnover", + "title": "Yearly Turnover" + }, + { + "pattern": "^(?:[a-z]|[a-z0-9][a-z0-9-+]*[a-z0-9])$", + "title": "Other" + } + ], + "title": "Key", + "description": "Key for identifying the type of discount being applied." + }, + "code": { + "$ref": "https://gobl.org/draft-0/cbc/code", + "title": "Code", + "description": "Code or reference for this discount defined by the issuer" + }, + "reason": { + "type": "string", + "title": "Reason", + "description": "Text description as to why the discount was applied" + }, "percent": { "$ref": "https://gobl.org/draft-0/num/percentage", "title": "Percent", - "description": "Percentage if fixed amount not applied" + "description": "Percentage to apply to the line total to calcaulte the discount amount" }, "amount": { "$ref": "https://gobl.org/draft-0/num/amount", "title": "Amount", - "description": "Fixed discount amount to apply (calculated if percent present).", + "description": "Fixed discount amount to apply (calculated if percent present)", "calculated": true }, - "code": { - "type": "string", - "title": "Code", - "description": "Reason code." - }, - "reason": { - "type": "string", - "title": "Reason", - "description": "Text description as to why the discount was applied" + "ext": { + "$ref": "https://gobl.org/draft-0/tax/extensions", + "title": "Extensions", + "description": "Extension codes that apply to the discount" } }, "type": "object", @@ -654,12 +895,12 @@ "buyer": { "$ref": "https://gobl.org/draft-0/org/party", "title": "Buyer", - "description": "Party who is responsible for making the purchase, but is not responsible\nfor handling taxes." + "description": "Party who is responsible for issuing payment, if not the same as the customer." }, "seller": { "$ref": "https://gobl.org/draft-0/org/party", "title": "Seller", - "description": "Party who is selling the goods but is not responsible for taxes like the\nsupplier." + "description": "Seller is the party liable to pay taxes on the transaction if not the same as the supplier." }, "projects": { "items": { @@ -721,59 +962,6 @@ "type": "object", "description": "Ordering provides additional information about the ordering process including references to other documents and alternative parties involved in the order-to-delivery process." }, - "Outlay": { - "properties": { - "uuid": { - "type": "string", - "format": "uuid", - "title": "UUID", - "description": "Universally Unique Identifier." - }, - "i": { - "type": "integer", - "title": "Index", - "description": "Outlay number index inside the invoice for ordering (calculated).", - "calculated": true - }, - "date": { - "$ref": "https://gobl.org/draft-0/cal/date", - "title": "Date", - "description": "When was the outlay made." - }, - "code": { - "type": "string", - "title": "Code", - "description": "Invoice number or other reference detail used to identify the outlay." - }, - "series": { - "type": "string", - "title": "Series", - "description": "Series of the outlay invoice." - }, - "description": { - "type": "string", - "title": "Description", - "description": "Details on what the outlay was." - }, - "supplier": { - "$ref": "https://gobl.org/draft-0/org/party", - "title": "Supplier", - "description": "Who was the supplier of the outlay" - }, - "amount": { - "$ref": "https://gobl.org/draft-0/num/amount", - "title": "Amount", - "description": "Amount paid by the supplier." - } - }, - "type": "object", - "required": [ - "i", - "description", - "amount" - ], - "description": "Outlay represents a reimbursable expense that was paid for by the supplier and invoiced separately by the third party directly to the customer." - }, "Payment": { "properties": { "payee": { diff --git a/data/schemas/cbc/code.json b/data/schemas/cbc/code.json index 23b65f04..a11f94e2 100644 --- a/data/schemas/cbc/code.json +++ b/data/schemas/cbc/code.json @@ -7,7 +7,7 @@ "type": "string", "maxLength": 32, "minLength": 1, - "pattern": "^[A-Za-z0-9]+([\\.\\-\\/ _]?[A-Za-z0-9]+)*$", + "pattern": "^[A-Za-z0-9]+([\\.\\-\\/ _\\:]?[A-Za-z0-9]+)*$", "title": "Code", "description": "Alphanumerical text identifier with upper-case letters and limits on using\nspecial characters or whitespace to separate blocks." } diff --git a/data/schemas/org/inbox.json b/data/schemas/org/inbox.json index ef290809..ac1ff438 100644 --- a/data/schemas/org/inbox.json +++ b/data/schemas/org/inbox.json @@ -11,6 +11,11 @@ "title": "UUID", "description": "Universally Unique Identifier." }, + "label": { + "type": "string", + "title": "Label", + "description": "Label for the inbox." + }, "key": { "$ref": "https://gobl.org/draft-0/cbc/key", "title": "Key", @@ -21,21 +26,23 @@ "title": "Role", "description": "Role assigned to this inbox that may be relevant for the consumer." }, - "name": { - "type": "string", - "title": "Name", - "description": "Human name for the inbox." - }, "code": { + "$ref": "https://gobl.org/draft-0/cbc/code", + "title": "Code", + "description": "Code or ID that identifies the Inbox." + }, + "url": { "type": "string", - "description": "Actual Code or ID that identifies the Inbox." + "title": "URL", + "description": "URL of the inbox that includes the protocol, server, and path. May\nbe used instead of the Code to identify the inbox." + }, + "ext": { + "$ref": "https://gobl.org/draft-0/tax/extensions", + "title": "Extensions", + "description": "Extension code map for any additional regime or addon specific codes that may be required." } }, "type": "object", - "required": [ - "key", - "code" - ], "description": "Inbox is used to store data about a connection with a service that is responsible for potentially receiving copies of GOBL envelopes or other document formats defined locally." } } diff --git a/data/schemas/pay/advance.json b/data/schemas/pay/advance.json index e6f9b779..b3e92362 100644 --- a/data/schemas/pay/advance.json +++ b/data/schemas/pay/advance.json @@ -157,6 +157,11 @@ }, "Card": { "properties": { + "first6": { + "type": "string", + "title": "First 6", + "description": "First 6 digits of the card's Primary Account Number (PAN)." + }, "last4": { "type": "string", "title": "Last 4", @@ -170,6 +175,7 @@ }, "type": "object", "required": [ + "first6", "last4", "holder" ], diff --git a/data/schemas/pay/instructions.json b/data/schemas/pay/instructions.json index 5e3597d4..c84c9be0 100644 --- a/data/schemas/pay/instructions.json +++ b/data/schemas/pay/instructions.json @@ -5,6 +5,11 @@ "$defs": { "Card": { "properties": { + "first6": { + "type": "string", + "title": "First 6", + "description": "First 6 digits of the card's Primary Account Number (PAN)." + }, "last4": { "type": "string", "title": "Last 4", @@ -18,6 +23,7 @@ }, "type": "object", "required": [ + "first6", "last4", "holder" ], @@ -164,9 +170,9 @@ "description": "Optional text description of the payment method" }, "ref": { - "type": "string", + "$ref": "https://gobl.org/draft-0/cbc/code", "title": "Reference", - "description": "Remittance information or concept, a text value used to link the payment with the invoice." + "description": "Remittance information or concept, a code value used to link the payment with the invoice." }, "credit_transfer": { "items": { diff --git a/data/schemas/tax/regime-def.json b/data/schemas/tax/regime-def.json index 86306c84..0f7ba3d0 100644 --- a/data/schemas/tax/regime-def.json +++ b/data/schemas/tax/regime-def.json @@ -294,14 +294,6 @@ "title": "Identity Keys", "description": "Identity keys used in addition to regular tax identities and specific for the\nregime that may be validated against." }, - "charge_keys": { - "items": { - "$ref": "https://gobl.org/draft-0/cbc/key-definition" - }, - "type": "array", - "title": "Charge Keys", - "description": "Charge keys specific for the regime and may be validated or used in the UI as suggestions" - }, "payment_means_keys": { "items": { "$ref": "https://gobl.org/draft-0/cbc/key-definition" diff --git a/internal/cli/testdata/TestBuild_draft b/internal/cli/testdata/TestBuild_draft index 91ae81eb..8ba9ddbd 100644 --- a/internal/cli/testdata/TestBuild_draft +++ b/internal/cli/testdata/TestBuild_draft @@ -96,13 +96,6 @@ "total": "15.00" } ], - "outlays": [ - { - "amount": "0.00", - "description": "Something paid for by us", - "i": 1 - } - ], "payment": { "instructions": { "credit_transfer": [ @@ -155,7 +148,6 @@ ] }, "totals": { - "outlays": "0.00", "payable": "5637.50", "sum": "5187.50", "tax": "450.00", @@ -204,7 +196,7 @@ "head": { "dig": { "alg": "sha256", - "val": "c8efedb81922d41509b6df8f94194a877677d4c3a4062ac8c1fb1b1896ab28c9" + "val": "c793f04d5ee66340bb1a8042a6ba80e71271d50325ca24942d3df50350170d1c" }, "uuid": "9d8eafd5-77be-11ec-b485-5405db9a3e49" } diff --git a/internal/cli/testdata/TestBuild_explicit_type b/internal/cli/testdata/TestBuild_explicit_type index 4dba69f1..7e778ddb 100644 --- a/internal/cli/testdata/TestBuild_explicit_type +++ b/internal/cli/testdata/TestBuild_explicit_type @@ -96,13 +96,6 @@ "total": "15.00" } ], - "outlays": [ - { - "amount": "0.00", - "description": "Something paid for by us", - "i": 1 - } - ], "payment": { "instructions": { "credit_transfer": [ @@ -155,7 +148,6 @@ ] }, "totals": { - "outlays": "0.00", "payable": "5654.75", "sum": "5187.50", "tax": "467.25", @@ -204,7 +196,7 @@ "head": { "dig": { "alg": "sha256", - "val": "9e39d6434df855fc708682cf705719285ad9b384846e513fb7debe999e16454a" + "val": "a432f74e898b155348a74f90a3abefc2f7bde9a9dbbe069c2a3a09b0bd5edb4a" }, "uuid": "9d8eafd5-77be-11ec-b485-5405db9a3e49" } diff --git a/internal/cli/testdata/TestBuild_merge_YAML b/internal/cli/testdata/TestBuild_merge_YAML index aee6cc6d..7ae45f71 100644 --- a/internal/cli/testdata/TestBuild_merge_YAML +++ b/internal/cli/testdata/TestBuild_merge_YAML @@ -96,13 +96,6 @@ "total": "15.00" } ], - "outlays": [ - { - "amount": "0.00", - "description": "Something paid for by us", - "i": 1 - } - ], "payment": { "instructions": { "credit_transfer": [ @@ -155,7 +148,6 @@ ] }, "totals": { - "outlays": "0.00", "payable": "5637.50", "sum": "5187.50", "tax": "450.00", @@ -204,7 +196,7 @@ "head": { "dig": { "alg": "sha256", - "val": "e78495e85c5d6935e4d91d0d43a410c958f69f7a78944fd6104756217ca0d254" + "val": "950eddcd07547556e5ae7bb77d1f92ac54bca98d77266a9a8b64db66262e3eec" }, "uuid": "9d8eafd5-77be-11ec-b485-5405db9a3e49" } diff --git a/internal/cli/testdata/TestBuild_success b/internal/cli/testdata/TestBuild_success index 380a74ef..89783f43 100644 --- a/internal/cli/testdata/TestBuild_success +++ b/internal/cli/testdata/TestBuild_success @@ -96,13 +96,6 @@ "total": "15.00" } ], - "outlays": [ - { - "amount": "0.00", - "description": "Something paid for by us", - "i": 1 - } - ], "payment": { "instructions": { "credit_transfer": [ @@ -155,7 +148,6 @@ ] }, "totals": { - "outlays": "0.00", "payable": "5637.50", "sum": "5187.50", "tax": "450.00", @@ -204,7 +196,7 @@ "head": { "dig": { "alg": "sha256", - "val": "2d4d4d81d49dc453f338a27b13fb8e54373f3a412bb7ff16c3d0b28d4a4a8a46" + "val": "02b5bfcb333c82b4f9ba02bcf2128b5649a964820cade97f45d926829fb811fa" }, "uuid": "9d8eafd5-77be-11ec-b485-5405db9a3e49" } diff --git a/internal/cli/testdata/TestBuild_template_with_empty_input b/internal/cli/testdata/TestBuild_template_with_empty_input index 9226cfde..d31a7e1f 100644 --- a/internal/cli/testdata/TestBuild_template_with_empty_input +++ b/internal/cli/testdata/TestBuild_template_with_empty_input @@ -96,13 +96,6 @@ "total": "15.00" } ], - "outlays": [ - { - "amount": "0.00", - "description": "Something paid for by us", - "i": 1 - } - ], "payment": { "instructions": { "credit_transfer": [ @@ -155,7 +148,6 @@ ] }, "totals": { - "outlays": "0.00", "payable": "5637.50", "sum": "5187.50", "tax": "450.00", @@ -204,7 +196,7 @@ "head": { "dig": { "alg": "sha256", - "val": "38fd37f6c05f2d0fe2f52e1c4bf8bb7f6c6fe289e03e07f9e0948d54794efd3d" + "val": "bb73b39a60e535d5cc02e5b4661b8e8029731e4613aab98389c87f6b9681c699" }, "uuid": "9d8eafd5-77be-11ec-b485-5405db9a3e49" } diff --git a/internal/cli/testdata/TestBuild_with_template b/internal/cli/testdata/TestBuild_with_template index 9dd0d2b6..46e6dc56 100644 --- a/internal/cli/testdata/TestBuild_with_template +++ b/internal/cli/testdata/TestBuild_with_template @@ -96,13 +96,6 @@ "total": "15.00" } ], - "outlays": [ - { - "amount": "0.00", - "description": "Something paid for by us", - "i": 1 - } - ], "payment": { "instructions": { "credit_transfer": [ @@ -155,7 +148,6 @@ ] }, "totals": { - "outlays": "0.00", "payable": "5637.50", "sum": "5187.50", "tax": "450.00", @@ -204,7 +196,7 @@ "head": { "dig": { "alg": "sha256", - "val": "1bc85f70750755ec5a49fb9ebacd3824c4fbe0f4286d5fef7712aa157a973ae0" + "val": "8a61751c35789a37712837e66f09581c9dd9f0b2f218dec4364e90f859f4ae22" }, "uuid": "9d8eafd5-77be-11ec-b485-5405db9a3e49" } diff --git a/internal/cli/testdata/TestSign_draft_envelope b/internal/cli/testdata/TestSign_draft_envelope index 44adbc7b..b23a26d7 100644 --- a/internal/cli/testdata/TestSign_draft_envelope +++ b/internal/cli/testdata/TestSign_draft_envelope @@ -96,13 +96,6 @@ "total": "15.00" } ], - "outlays": [ - { - "amount": "0.00", - "description": "Something paid for by us", - "i": 1 - } - ], "payment": { "instructions": { "credit_transfer": [ @@ -155,7 +148,6 @@ ] }, "totals": { - "outlays": "0.00", "payable": "5637.50", "sum": "5187.50", "tax": "450.00", @@ -204,11 +196,11 @@ "head": { "dig": { "alg": "sha256", - "val": "c8efedb81922d41509b6df8f94194a877677d4c3a4062ac8c1fb1b1896ab28c9" + "val": "c793f04d5ee66340bb1a8042a6ba80e71271d50325ca24942d3df50350170d1c" }, "uuid": "9d8eafd5-77be-11ec-b485-5405db9a3e49" }, "sigs": [ - "eyJhbGciOiJFUzI1NiIsImtpZCI6ImI3Y2VlNjBmLTIwNGUtNDM4Yi1hODhmLTAyMWQyOGFmNjk5MSJ9.eyJ1dWlkIjoiOWQ4ZWFmZDUtNzdiZS0xMWVjLWI0ODUtNTQwNWRiOWEzZTQ5IiwiZGlnIjp7ImFsZyI6InNoYTI1NiIsInZhbCI6ImM4ZWZlZGI4MTkyMmQ0MTUwOWI2ZGY4Zjk0MTk0YTg3NzY3N2Q0YzNhNDA2MmFjOGMxZmIxYjE4OTZhYjI4YzkifX0.uhIm58KfFHPYnqEzpsdfGXCkjRvM5cKSB1o1q2jdL5KaAvpqCsDKmBsMz7K7qsBbFFWleBFrlWNpJ5FVdMw-Gg" + "eyJhbGciOiJFUzI1NiIsImtpZCI6ImI3Y2VlNjBmLTIwNGUtNDM4Yi1hODhmLTAyMWQyOGFmNjk5MSJ9.eyJ1dWlkIjoiOWQ4ZWFmZDUtNzdiZS0xMWVjLWI0ODUtNTQwNWRiOWEzZTQ5IiwiZGlnIjp7ImFsZyI6InNoYTI1NiIsInZhbCI6ImM3OTNmMDRkNWVlNjYzNDBiYjFhODA0MmE2YmE4MGU3MTI3MWQ1MDMyNWNhMjQ5NDJkM2RmNTAzNTAxNzBkMWMifX0.III-TN4WWYsyPupPdDUBXjgfAOH7wPU8UrMMcBw6NJ1tAEGYWJ30f_nIEkHPIHz9_g72aeq3obht4teR8RgiRA" ] } \ No newline at end of file diff --git a/internal/cli/testdata/TestSign_success b/internal/cli/testdata/TestSign_success index ff1a589d..1abc1a62 100644 --- a/internal/cli/testdata/TestSign_success +++ b/internal/cli/testdata/TestSign_success @@ -96,13 +96,6 @@ "total": "15.00" } ], - "outlays": [ - { - "amount": "0.00", - "description": "Something paid for by us", - "i": 1 - } - ], "payment": { "instructions": { "credit_transfer": [ @@ -155,7 +148,6 @@ ] }, "totals": { - "outlays": "0.00", "payable": "5637.50", "sum": "5187.50", "tax": "450.00", @@ -204,11 +196,11 @@ "head": { "dig": { "alg": "sha256", - "val": "2d4d4d81d49dc453f338a27b13fb8e54373f3a412bb7ff16c3d0b28d4a4a8a46" + "val": "02b5bfcb333c82b4f9ba02bcf2128b5649a964820cade97f45d926829fb811fa" }, "uuid": "9d8eafd5-77be-11ec-b485-5405db9a3e49" }, "sigs": [ - "eyJhbGciOiJFUzI1NiIsImtpZCI6ImI3Y2VlNjBmLTIwNGUtNDM4Yi1hODhmLTAyMWQyOGFmNjk5MSJ9.eyJ1dWlkIjoiOWQ4ZWFmZDUtNzdiZS0xMWVjLWI0ODUtNTQwNWRiOWEzZTQ5IiwiZGlnIjp7ImFsZyI6InNoYTI1NiIsInZhbCI6IjJkNGQ0ZDgxZDQ5ZGM0NTNmMzM4YTI3YjEzZmI4ZTU0MzczZjNhNDEyYmI3ZmYxNmMzZDBiMjhkNGE0YThhNDYifX0.jziDu6YrXRm0pk30OfvA4TKf7BzOb_CK8aSbHJfHLPOI8thoZdQ_DxS0AyTu3OdXaUuva7mb9ebK99twAESMYA" + "eyJhbGciOiJFUzI1NiIsImtpZCI6ImI3Y2VlNjBmLTIwNGUtNDM4Yi1hODhmLTAyMWQyOGFmNjk5MSJ9.eyJ1dWlkIjoiOWQ4ZWFmZDUtNzdiZS0xMWVjLWI0ODUtNTQwNWRiOWEzZTQ5IiwiZGlnIjp7ImFsZyI6InNoYTI1NiIsInZhbCI6IjAyYjViZmNiMzMzYzgyYjRmOWJhMDJiY2YyMTI4YjU2NDlhOTY0ODIwY2FkZTk3ZjQ1ZDkyNjgyOWZiODExZmEifX0.jjzi2ci3FpZUHb-TeGqfr5YbwNcVRvsmvbtOXTxZFdRTT1g52gB-nE05_39rkOeb8suaix0i5DtbQAIS1Wz00w" ] } \ No newline at end of file diff --git a/internal/cli/testdata/draft.json b/internal/cli/testdata/draft.json index d164beb4..460157b9 100644 --- a/internal/cli/testdata/draft.json +++ b/internal/cli/testdata/draft.json @@ -4,9 +4,8 @@ "uuid": "9d8eafd5-77be-11ec-b485-5405db9a3e49", "dig": { "alg": "sha256", - "val": "c8efedb81922d41509b6df8f94194a877677d4c3a4062ac8c1fb1b1896ab28c9" - }, - "draft": true + "val": "c793f04d5ee66340bb1a8042a6ba80e71271d50325ca24942d3df50350170d1c" + } }, "doc": { "$schema": "https://gobl.org/draft-0/bill/invoice", @@ -143,13 +142,6 @@ "total": "15.00" } ], - "outlays": [ - { - "i": 1, - "description": "Something paid for by us", - "amount": "0.00" - } - ], "payment": { "terms": { "key": "instant" @@ -205,7 +197,6 @@ }, "tax": "450.00", "total_with_tax": "5637.50", - "outlays": "0.00", "payable": "5637.50" } } diff --git a/org/inbox.go b/org/inbox.go index 0ce423b3..8dffb22a 100644 --- a/org/inbox.go +++ b/org/inbox.go @@ -7,6 +7,7 @@ import ( "github.com/invopop/gobl/tax" "github.com/invopop/gobl/uuid" "github.com/invopop/validation" + "github.com/invopop/validation/is" ) // Inbox is used to store data about a connection with a service that is responsible @@ -14,14 +15,30 @@ import ( // defined locally. type Inbox struct { uuid.Identify + // Label for the inbox. + Label string `json:"label,omitempty" jsonschema:"title=Label"` // Type of inbox being defined. - Key cbc.Key `json:"key" jsonschema:"title=Key"` + Key cbc.Key `json:"key,omitempty" jsonschema:"title=Key"` // Role assigned to this inbox that may be relevant for the consumer. Role cbc.Key `json:"role,omitempty" jsonschema:"title=Role"` - // Human name for the inbox. - Name string `json:"name,omitempty" jsonschema:"title=Name"` - // Actual Code or ID that identifies the Inbox. - Code string `json:"code"` + // Code or ID that identifies the Inbox. + Code cbc.Code `json:"code,omitempty" jsonschema:"title=Code"` + // URL of the inbox that includes the protocol, server, and path. May + // be used instead of the Code to identify the inbox. + URL string `json:"url,omitempty" jsonschema:"title=URL"` + // Extension code map for any additional regime or addon specific codes that may be required. + Ext tax.Extensions `json:"ext,omitempty" jsonschema:"title=Extensions"` +} + +// Normalize will try to clean the inbox's data. +func (i *Inbox) Normalize(normalizers tax.Normalizers) { + if i == nil { + return + } + uuid.Normalize(&i.UUID) + i.Code = cbc.NormalizeCode(i.Code) + i.Ext = tax.CleanExtensions(i.Ext) + normalizers.Each(i) } // Validate ensures the inbox's fields look good. @@ -33,8 +50,36 @@ func (i *Inbox) Validate() error { func (i *Inbox) ValidateWithContext(ctx context.Context) error { return tax.ValidateStructWithContext(ctx, i, validation.Field(&i.UUID), - validation.Field(&i.Key, validation.Required), + validation.Field(&i.Key), validation.Field(&i.Role), - validation.Field(&i.Code, validation.Required), + validation.Field(&i.Code, + validation.When( + i.URL == "", + validation.Required.Error("cannot be blank without url"), + ), + ), + validation.Field(&i.URL, + is.URL, + validation.When( + i.Code != "", + validation.Empty.Error("mutually exclusive with code"), + ), + ), + validation.Field(&i.Ext), ) } + +// AddInbox makes it easier to add a new inbox to a list and replace an +// existing value with a matching key. +func AddInbox(in []*Inbox, i *Inbox) []*Inbox { + if in == nil { + return []*Inbox{i} + } + for _, v := range in { + if v.Key == i.Key { + *v = *i // copy in place + return in + } + } + return append(in, i) +} diff --git a/org/inbox_test.go b/org/inbox_test.go new file mode 100644 index 00000000..f769c381 --- /dev/null +++ b/org/inbox_test.go @@ -0,0 +1,104 @@ +package org_test + +import ( + "testing" + + "github.com/invopop/gobl/catalogues/iso" + "github.com/invopop/gobl/cbc" + "github.com/invopop/gobl/org" + "github.com/invopop/gobl/tax" + "github.com/stretchr/testify/assert" +) + +func TestAddInbox(t *testing.T) { + key := cbc.Key("test-inbox") + st := struct { + Inboxes []*org.Inbox + }{ + Inboxes: []*org.Inbox{ + { + Key: key, + Code: "BAR", + }, + }, + } + st.Inboxes = org.AddInbox(st.Inboxes, &org.Inbox{ + Key: key, + Code: "BARDOM", + }) + assert.Len(t, st.Inboxes, 1) + assert.Equal(t, "BARDOM", st.Inboxes[0].Code.String()) +} + +func TestInboxNormalize(t *testing.T) { + t.Run("with nil", func(t *testing.T) { + var id *org.Inbox + assert.NotPanics(t, func() { + id.Normalize(nil) + }) + }) + t.Run("missing extensions", func(t *testing.T) { + id := &org.Inbox{ + Key: cbc.Key("inbox"), + Code: "BAR", + Ext: tax.Extensions{}, + } + id.Normalize(nil) + assert.Equal(t, "inbox", id.Key.String()) + assert.Nil(t, id.Ext) + }) + t.Run("with extension", func(t *testing.T) { + id := &org.Inbox{ + Code: "BAR", + Ext: tax.Extensions{ + iso.ExtKeySchemeID: "0004", + }, + } + id.Normalize(nil) + assert.Equal(t, "BAR", id.Code.String()) + assert.Equal(t, "0004", id.Ext[iso.ExtKeySchemeID].String()) + }) +} + +func TestInboxValidate(t *testing.T) { + t.Run("with basics", func(t *testing.T) { + id := &org.Inbox{ + Code: "BAR", + Ext: tax.Extensions{ + iso.ExtKeySchemeID: "0004", + }, + } + err := id.Validate() + assert.NoError(t, err) + }) + t.Run("with both key", func(t *testing.T) { + id := &org.Inbox{ + Key: "fiscal-code", + Code: "1234567890", + } + err := id.Validate() + assert.NoError(t, err) + }) + t.Run("missing code", func(t *testing.T) { + id := &org.Inbox{ + Key: "fiscal-code", + } + err := id.Validate() + assert.ErrorContains(t, err, "code: cannot be blank without url") + }) + t.Run("with URL", func(t *testing.T) { + id := &org.Inbox{ + URL: "https://inbox.example.com", + } + err := id.Validate() + assert.NoError(t, err) + }) + t.Run("with code and URL", func(t *testing.T) { + id := &org.Inbox{ + Code: "FOOO", + URL: "https://inbox.example.com", + } + err := id.Validate() + assert.ErrorContains(t, err, "url: mutually exclusive with code") + }) +} diff --git a/org/party.go b/org/party.go index 4f4b3c90..867b7af1 100644 --- a/org/party.go +++ b/org/party.go @@ -80,6 +80,7 @@ func (p *Party) Normalize(normalizers tax.Normalizers) { normalizers.Each(p) tax.Normalize(normalizers, p.Identities) + tax.Normalize(normalizers, p.Inboxes) tax.Normalize(normalizers, p.Addresses) tax.Normalize(normalizers, p.Emails) } diff --git a/pay/instructions.go b/pay/instructions.go index d7157c33..6567f8e0 100644 --- a/pay/instructions.go +++ b/pay/instructions.go @@ -20,8 +20,8 @@ type Instructions struct { Key cbc.Key `json:"key" jsonschema:"title=Key"` // Optional text description of the payment method Detail string `json:"detail,omitempty" jsonschema:"title=Detail"` - // Remittance information or concept, a text value used to link the payment with the invoice. - Ref string `json:"ref,omitempty" jsonschema:"title=Reference"` + // Remittance information or concept, a code value used to link the payment with the invoice. + Ref cbc.Code `json:"ref,omitempty" jsonschema:"title=Reference"` // Instructions for sending payment via a bank transfer. CreditTransfer []*CreditTransfer `json:"credit_transfer,omitempty" jsonschema:"title=Credit Transfer"` // Details of the payment that will be made via a credit or debit card. @@ -39,7 +39,11 @@ type Instructions struct { } // Card contains simplified card holder data as a reference for the customer. +// PCI compliance requires only the first 6 and last 4 digits of the card number +// to be stored openly. type Card struct { + // First 6 digits of the card's Primary Account Number (PAN). + First6 string `json:"first6" jsonschema:"title=First 6"` // Last 4 digits of the card's Primary Account Number (PAN). Last4 string `json:"last4" jsonschema:"title=Last 4"` // Name of the person whom the card belongs to. @@ -86,6 +90,7 @@ func (i *Instructions) Normalize(normalizers tax.Normalizers) { if i == nil { return } + i.Ref = cbc.NormalizeCode(i.Ref) i.Ext = tax.CleanExtensions(i.Ext) normalizers.Each(i) } @@ -131,6 +136,7 @@ func (i *Instructions) Validate() error { func (i *Instructions) ValidateWithContext(ctx context.Context) error { return tax.ValidateStructWithContext(ctx, i, validation.Field(&i.Key, validation.Required, HasValidMeansKey), + validation.Field(&i.Ref), validation.Field(&i.CreditTransfer), validation.Field(&i.DirectDebit), validation.Field(&i.Online), diff --git a/pay/instructions_test.go b/pay/instructions_test.go index df4d78df..650f8f13 100644 --- a/pay/instructions_test.go +++ b/pay/instructions_test.go @@ -13,6 +13,7 @@ import ( func TestInstructionsNormalize(t *testing.T) { i := &pay.Instructions{ Key: "online", + Ref: " fooo ", Detail: "Some random payment", Ext: tax.Extensions{ "random": "", @@ -20,6 +21,7 @@ func TestInstructionsNormalize(t *testing.T) { } i.Normalize(nil) assert.Empty(t, i.Ext) + assert.Equal(t, "fooo", i.Ref.String()) i = nil assert.NotPanics(t, func() { diff --git a/regimes/it/charges.go b/regimes/it/charges.go deleted file mode 100644 index a01ffdea..00000000 --- a/regimes/it/charges.go +++ /dev/null @@ -1,25 +0,0 @@ -package it - -import ( - "github.com/invopop/gobl/cbc" - "github.com/invopop/gobl/i18n" -) - -// List of charge types specific to the italian regime. -const ( - ChargeKeyStampDuty cbc.Key = "stamp-duty" -) - -var chargeKeyDefinitions = []*cbc.KeyDefinition{ - { - Key: ChargeKeyStampDuty, - Name: i18n.String{ - i18n.EN: "Stamp Duty", - i18n.IT: "Imposta di bollo", - }, - Desc: i18n.String{ - i18n.EN: "A fixed-price tax applied to the production, request or presentation of certain documents: civil, commercial, judicial and extrajudicial documents, on notices, on posters.", - i18n.IT: "Un'imposta applicata alla produzione, richiesta o presentazione di determinati documenti: atti civili, commerciali, giudiziali ed extragiudiziali, sugli avvisi, sui manifesti.", - }, - }, -} diff --git a/regimes/it/it.go b/regimes/it/it.go index aefb9eeb..56a83aad 100644 --- a/regimes/it/it.go +++ b/regimes/it/it.go @@ -25,7 +25,6 @@ func New() *tax.RegimeDef { i18n.IT: "Italia", }, TimeZone: "Europe/Rome", - ChargeKeys: chargeKeyDefinitions, // charges.go IdentityKeys: identityKeyDefinitions, // identities.go Scenarios: scenarios, // scenarios.go Tags: []*tax.TagSet{ diff --git a/tax/regime_def.go b/tax/regime_def.go index 111d5d39..94d68308 100644 --- a/tax/regime_def.go +++ b/tax/regime_def.go @@ -70,9 +70,6 @@ type RegimeDef struct { // regime that may be validated against. IdentityKeys []*cbc.KeyDefinition `json:"identity_keys,omitempty" jsonschema:"title=Identity Keys"` - // Charge keys specific for the regime and may be validated or used in the UI as suggestions - ChargeKeys []*cbc.KeyDefinition `json:"charge_keys,omitempty" jsonschema:"title=Charge Keys"` - // PaymentMeansKeys specific for the regime that extend the original // base payment means keys. PaymentMeansKeys []*cbc.KeyDefinition `json:"payment_means_keys,omitempty" jsonschema:"title=Payment Means Keys"` @@ -286,7 +283,6 @@ func (r *RegimeDef) ValidateWithContext(ctx context.Context) error { validation.Field(&r.TaxIdentityTypeKeys), validation.Field(&r.IdentityKeys), validation.Field(&r.Extensions), - validation.Field(&r.ChargeKeys), validation.Field(&r.PaymentMeansKeys), validation.Field(&r.InboxKeys), validation.Field(&r.Scenarios),