From a4c5741eddf327e3f3244778c84a11c302b38c3b Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 11 Nov 2024 14:10:22 +0100 Subject: [PATCH 01/22] First commit Arab Emirates Regime --- regimes/ae/README.md | 42 +++++++++ regimes/ae/ae.go | 78 +++++++++++++++++ regimes/ae/invoices.go | 32 +++++++ regimes/ae/invoices_test.go | 67 +++++++++++++++ regimes/ae/scenarios.go | 32 +++++++ regimes/ae/tax_categories.go | 145 ++++++++++++++++++++++++++++++++ regimes/ae/tax_identity.go | 39 +++++++++ regimes/ae/tax_identity_test.go | 43 ++++++++++ 8 files changed, 478 insertions(+) create mode 100644 regimes/ae/README.md create mode 100644 regimes/ae/ae.go create mode 100644 regimes/ae/invoices.go create mode 100644 regimes/ae/invoices_test.go create mode 100644 regimes/ae/scenarios.go create mode 100644 regimes/ae/tax_categories.go create mode 100644 regimes/ae/tax_identity.go create mode 100644 regimes/ae/tax_identity_test.go diff --git a/regimes/ae/README.md b/regimes/ae/README.md new file mode 100644 index 00000000..e5554623 --- /dev/null +++ b/regimes/ae/README.md @@ -0,0 +1,42 @@ +# United Arab Emirates (AE) Tax Regime + +This document provides an overview of the tax regime in the United Arab Emirates. The UAE tax regime primarily involves two types of indirect taxes: **Value-Added Tax (VAT)** and **Excise Tax**. + +## Types of Indirect Taxes in the UAE + +1. **Value-Added Tax (VAT)**: A general tax applied to most goods and services at a rate of 5%. +2. **Excise Tax**: Is a form of indirect tax applied to specific categories of products that are considered harmful to health or the environment, such as tobacco and sugary drinks. + +### 1. Value-Added Tax (VAT) + +The UAE VAT system categorizes goods and services into three main VAT rates: + +- **Standard Rate (5%)**: Applies to most goods and services in the UAE. +- **Zero Rate (0%)**: Applies to certain essential goods, exports, and specific services. +- **Exempt**: Certain types of goods and services are exempt from VAT, including some financial services or residential real estate. + +Businesses required to charge VAT must register with the UAE Federal Tax Authority to get the Tax Registration Number (TRN). Registration can be completed online via the [VAT registration portal](https://tax.gov.ae/en/services/vat.registration.aspx). + +The algorithm to validate the TRN number was not located. Validation must be conducted directly on the official government website. + +### 2. Excise Tax + +Excise Tax is a form of indirect tax on certain goods considered harmful to consumer health or the environment. The following products are subject to Excise Tax in the UAE: + +- **Tobacco products** (including vapes and vape liquids): 100% +- **Sugary drinks**: 50% +- **Energy drinks**: 100% +- **Carbonated drinks**: 50% + +#### Excise Tax Registration + +Businesses that intend to sell goods subject to Excise Tax must register with the UAE Federal Tax Authority. Registration can be completed online via the [official UAE tax authority website](https://tax.gov.ae/en/taxes/excise.tax/excise.tax.topics/excise.tax.registration.aspx). + +## VAT Invoicing Requirements + +In the UAE, businesses must issue VAT-compliant invoices. There are two types of VAT invoices. The standard and the simplified invoice: + +**Simplified VAT Invoice**: Allowed in the following cases: + +- When the recipient of goods or services is **not VAT registered**. +- When the recipient of goods or services **is VAT registered**, and the transaction value does not exceed **AED 10,000** . diff --git a/regimes/ae/ae.go b/regimes/ae/ae.go new file mode 100644 index 00000000..fd3be11a --- /dev/null +++ b/regimes/ae/ae.go @@ -0,0 +1,78 @@ +// Package ae provides the tax region definition for United Arab Emirates. +package ae + +import ( + "github.com/invopop/gobl/bill" + "github.com/invopop/gobl/cbc" + "github.com/invopop/gobl/currency" + "github.com/invopop/gobl/i18n" + "github.com/invopop/gobl/regimes/common" + "github.com/invopop/gobl/tax" +) + +func init() { + tax.RegisterRegimeDef(New()) +} + +// Local tax category definition which is not considered standard. +const ( + TaxCategoryExcise cbc.Code = "EXCISE" +) + +// Specific tax rate codes. +const ( + TaxRateSmokingProducts cbc.Key = "smoking-product" + TaxRateCarbonatedDrinks cbc.Key = "carbonated-drink" + TaxRateEnergyDrinks cbc.Key = "energy-drink" + TaxRateSweetenedDrinks cbc.Key = "sweetened-drink" +) + +// New provides the tax region definition for UAE. +func New() *tax.RegimeDef { + return &tax.RegimeDef{ + Country: "AE", + Currency: currency.AED, + Name: i18n.String{ + i18n.EN: "United Arab Emirates", + i18n.AR: "الإمارات العربية المتحدة", + }, + TimeZone: "Asia/Dubai", + Tags: []*tax.TagSet{ + common.InvoiceTags(), + }, + Scenarios: []*tax.ScenarioSet{ + invoiceScenarios, + }, + Corrections: []*tax.CorrectionDefinition{ + { + Schema: bill.ShortSchemaInvoice, + Types: []cbc.Key{ + bill.InvoiceTypeCreditNote, + }, + }, + }, + Validator: Validate, + Normalizer: Normalize, + Categories: taxCategories, + } +} + +// Validate checks the document type and determines if it can be validated. +func Validate(doc interface{}) error { + switch obj := doc.(type) { + case *bill.Invoice: + return validateInvoice(obj) + case *tax.Identity: + return validateTaxIdentity(obj) + } + return nil +} + +// Normalize attempts to clean up the object passed to it. +func Normalize(doc any) { + switch obj := doc.(type) { + case *tax.Identity: + tax.NormalizeIdentity(obj) + + } +} diff --git a/regimes/ae/invoices.go b/regimes/ae/invoices.go new file mode 100644 index 00000000..fba12f08 --- /dev/null +++ b/regimes/ae/invoices.go @@ -0,0 +1,32 @@ +// Package ae provides the tax region definition for the United Arab Emirates. +package ae + +import ( + "github.com/invopop/gobl/bill" + "github.com/invopop/gobl/org" + "github.com/invopop/gobl/tax" + "github.com/invopop/validation" +) + +// validateInvoice validates the invoice, ensuring the TRN is always required. +func validateInvoice(inv *bill.Invoice) error { + return validation.ValidateStruct(inv, + validation.Field(&inv.Supplier, + validation.By(validateInvoiceSupplier), + ), + ) +} + +// validateInvoiceSupplier validates that the supplier (Party) has a Tax Registration Number (TRN). +func validateInvoiceSupplier(value any) error { + p, ok := value.(*org.Party) + if !ok || p == nil { + return nil + } + return validation.ValidateStruct(p, + validation.Field(&p.TaxID, + validation.Required, + tax.RequireIdentityCode, + ), + ) +} diff --git a/regimes/ae/invoices_test.go b/regimes/ae/invoices_test.go new file mode 100644 index 00000000..66640653 --- /dev/null +++ b/regimes/ae/invoices_test.go @@ -0,0 +1,67 @@ +// Package ae_test provides tests for the UAE invoice validation. +package ae_test + +import ( + "testing" + + "github.com/invopop/gobl/bill" + "github.com/invopop/gobl/num" + "github.com/invopop/gobl/org" + "github.com/invopop/gobl/tax" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// validInvoice creates a sample valid invoice for UAE. +func validInvoice() *bill.Invoice { + return &bill.Invoice{ + Regime: tax.WithRegime("AE"), + Series: "TEST", + Code: "0002", + Supplier: &org.Party{ + Name: "Test Supplier", + TaxID: &tax.Identity{ + Country: "AE", + Code: "123456789012345", + }, + }, + Customer: &org.Party{ + Name: "Test Customer", + TaxID: &tax.Identity{ + Country: "AE", + Code: "187654321098765", + }, + }, + Lines: []*bill.Line{ + { + Quantity: num.MakeAmount(1, 0), + Item: &org.Item{ + Name: "Test Item", + Price: num.MakeAmount(10000, 2), + Unit: org.UnitPackage, + }, + Taxes: tax.Set{ + { + Category: "VAT", + Rate: "standard", + }, + }, + }, + }, + } +} + +func TestInvoiceValidation(t *testing.T) { + t.Run("invoice with required TRN", func(t *testing.T) { + inv := validInvoice() + require.NoError(t, inv.Calculate()) + assert.NoError(t, inv.Validate()) + }) + + t.Run("invoice with missing TRN", func(t *testing.T) { + inv := validInvoice() + inv.Supplier.TaxID = nil + require.NoError(t, inv.Calculate()) + assert.ErrorContains(t, inv.Validate(), "supplier: (tax_id: cannot be blank.).") + }) +} diff --git a/regimes/ae/scenarios.go b/regimes/ae/scenarios.go new file mode 100644 index 00000000..0fbbbb55 --- /dev/null +++ b/regimes/ae/scenarios.go @@ -0,0 +1,32 @@ +// Package ae provides tax scenarios specific to UAE VAT regulations. +package ae + +import ( + "github.com/invopop/gobl/bill" + "github.com/invopop/gobl/cbc" + "github.com/invopop/gobl/tax" +) + +var invoiceScenarios = &tax.ScenarioSet{ + Schema: bill.ShortSchemaInvoice, + List: []*tax.Scenario{ + // Reverse Charges + { + Tags: []cbc.Key{tax.TagReverseCharge}, + Note: &cbc.Note{ + Key: cbc.NoteKeyLegal, + Src: tax.TagReverseCharge, + Text: "Reverse Charge / التحويل العكسي", + }, + }, + // Simplified Tax Invoice + { + Tags: []cbc.Key{tax.TagSimplified}, + Note: &cbc.Note{ + Key: cbc.NoteKeyLegal, + Src: tax.TagSimplified, + Text: "Simplified Tax Invoice / فاتورة ضريبية مبسطة", + }, + }, + }, +} diff --git a/regimes/ae/tax_categories.go b/regimes/ae/tax_categories.go new file mode 100644 index 00000000..97fd7b06 --- /dev/null +++ b/regimes/ae/tax_categories.go @@ -0,0 +1,145 @@ +// Package ae defines VAT tax categories specific to the United Arab Emirates. +package ae + +import ( + "github.com/invopop/gobl/cal" + "github.com/invopop/gobl/i18n" + "github.com/invopop/gobl/num" + "github.com/invopop/gobl/tax" +) + +var taxCategories = []*tax.CategoryDef{ + { + Code: tax.CategoryVAT, + Name: i18n.String{ + i18n.EN: "VAT", + i18n.AR: "ضريبة القيمة المضافة", + }, + Title: i18n.String{ + i18n.EN: "Value Added Tax", + i18n.AR: "ضريبة القيمة المضافة", + }, + Sources: []*tax.Source{ + { + Title: i18n.String{ + i18n.EN: "Federal Tax Authority - UAE VAT Regulations", + i18n.AR: "الهيئة الاتحادية للضرائب", + }, + URL: "https://www.tax.gov.ae", + }, + }, + Retained: false, + Rates: []*tax.RateDef{ + { + Key: tax.RateZero, + Name: i18n.String{ + i18n.EN: "Zero Rate", + i18n.AR: "معدل صفر", + }, + Description: i18n.String{ + i18n.EN: "A VAT rate of 0% applicable to specific exports, designated areas, and essential services.", + i18n.AR: "نسبة ضريبة قيمة مضافة 0٪ تطبق على الصادرات المحددة والمناطق المعينة والخدمات الأساسية.", + }, + Values: []*tax.RateValueDef{ + { + Percent: num.MakePercentage(0, 3), + }, + }, + }, + { + Key: tax.RateStandard, + Name: i18n.String{ + i18n.EN: "Standard Rate", + i18n.AR: "معدل قياسي", + }, + Description: i18n.String{ + i18n.EN: "Applies to most goods and services unless specified otherwise.", + i18n.AR: "ينطبق على معظم السلع والخدمات ما لم ينص على خلاف ذلك.", + }, + Values: []*tax.RateValueDef{ + { + Since: cal.NewDate(2018, 1, 1), + Percent: num.MakePercentage(5, 2), + }, + }, + }, + { + Key: tax.RateExempt, + Name: i18n.String{ + i18n.EN: "Exempt", + i18n.AR: "معفى", + }, + Exempt: true, + Description: i18n.String{ + i18n.EN: "Certain goods and services, such as financial services and residential real estate, are exempt from VAT.", + i18n.AR: "بعض السلع والخدمات، مثل الخدمات المالية والعقارات السكنية، معفاة من ضريبة القيمة المضافة.", + }, + }, + }, + }, + // + // Excise Tax + // + { + Code: TaxCategoryExcise, + Retained: false, + Name: i18n.String{ + i18n.EN: "Excise Tax", + i18n.AR: "ضريبة السلع الانتقائية", + }, + Title: i18n.String{ + i18n.EN: "UAE Excise Tax", + i18n.AR: "ضريبة السلع الانتقائية في الإمارات", + }, + Rates: []*tax.RateDef{ + { + Key: TaxRateSmokingProducts, + Name: i18n.String{ + i18n.EN: "Smoking Products Rate", + i18n.AR: "معدل منتجات التدخين", + }, + Values: []*tax.RateValueDef{ + { + Percent: num.MakePercentage(100, 3), + }, + }, + }, + { + Key: TaxRateCarbonatedDrinks, + Name: i18n.String{ + i18n.EN: "Carbonated Drinks Rate", + i18n.AR: "معدل المشروبات الغازية", + }, + Values: []*tax.RateValueDef{ + { + Percent: num.MakePercentage(50, 3), + }, + }, + }, + { + Key: TaxRateEnergyDrinks, + Name: i18n.String{ + i18n.EN: "Energy Drinks Rate", + i18n.AR: "معدل مشروبات الطاقة", + }, + Values: []*tax.RateValueDef{ + { + Percent: num.MakePercentage(100, 3), + }, + }, + }, + { + Key: TaxRateSweetenedDrinks, + Name: i18n.String{ + i18n.EN: "Sweetened Drinks Rate", + i18n.AR: "معدل المشروبات المحلاة", + }, + Values: []*tax.RateValueDef{ + { + Percent: num.MakePercentage(50, 3), + }, + }, + }, + }, + }, +} diff --git a/regimes/ae/tax_identity.go b/regimes/ae/tax_identity.go new file mode 100644 index 00000000..96183234 --- /dev/null +++ b/regimes/ae/tax_identity.go @@ -0,0 +1,39 @@ +// Package ae provides the tax identity validation specific to the United Arab Emirates. +package ae + +import ( + "errors" + "regexp" + + "github.com/invopop/gobl/cbc" + "github.com/invopop/gobl/tax" + "github.com/invopop/validation" +) + +var ( + // TRN in UAE is a 15-digit number + trnRegex = regexp.MustCompile(`^\d{15}$`) +) + +// validateTaxIdentity checks to ensure the UAE TRN format is correct. +func validateTaxIdentity(tID *tax.Identity) error { + return validation.ValidateStruct(tID, + validation.Field(&tID.Code, validation.By(validateTRNCode)), + ) +} + +// validateTRNCode checks that the TRN is a valid 15-digit format. +func validateTRNCode(value interface{}) error { + code, ok := value.(cbc.Code) + if !ok || code == "" { + return nil + } + val := code.String() + + // Check if TRN matches the 15-digit pattern + if !trnRegex.MatchString(val) { + return errors.New("invalid format: TRN must be a 15-digit number") + } + + return nil +} diff --git a/regimes/ae/tax_identity_test.go b/regimes/ae/tax_identity_test.go new file mode 100644 index 00000000..1730fc9a --- /dev/null +++ b/regimes/ae/tax_identity_test.go @@ -0,0 +1,43 @@ +// Package ae_test provides tests for the UAE TRN (Tax Registration Number) validation. +package ae_test + +import ( + "testing" + + "github.com/invopop/gobl/cbc" + "github.com/invopop/gobl/regimes/ae" + "github.com/invopop/gobl/tax" + "github.com/stretchr/testify/assert" +) + +func TestValidateTaxIdentity(t *testing.T) { + tests := []struct { + name string + code cbc.Code + err string + }{ + {name: "good 1", code: "123456789012345"}, + {name: "good 2", code: "187654321098765"}, + {name: "good 3", code: "100111222333444"}, + + // Invalid formats + {name: "too short", code: "12345678901234", err: "invalid format: TRN must be a 15-digit number"}, + {name: "too long", code: "1234567890123456", err: "invalid format: TRN must be a 15-digit number"}, + {name: "non-numeric", code: "12345678ABCD345", err: "invalid format: TRN must be a 15-digit number"}, + {name: "not normalized", code: "1234-5678-9012-345", err: "invalid format: TRN must be a 15-digit number"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tID := &tax.Identity{Country: "AE", Code: tt.code} + err := ae.Validate(tID) + if tt.err == "" { + assert.NoError(t, err) + } else { + if assert.Error(t, err) { + assert.Contains(t, err.Error(), tt.err) + } + } + }) + } +} From e13904b9156d8da278f02b568c720be1e7da0ef9 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 11 Nov 2024 23:14:48 +0100 Subject: [PATCH 02/22] Initial Arab Emirates regime adjustments --- regimes/ae/README.md | 37 +++++++------ regimes/ae/ae.go | 20 ++----- regimes/ae/invoices.go | 32 ----------- regimes/ae/invoices_test.go | 67 ----------------------- regimes/ae/scenarios_test.go | 101 +++++++++++++++++++++++++++++++++++ regimes/ae/tax_categories.go | 65 ---------------------- 6 files changed, 122 insertions(+), 200 deletions(-) delete mode 100644 regimes/ae/invoices.go delete mode 100644 regimes/ae/invoices_test.go create mode 100644 regimes/ae/scenarios_test.go diff --git a/regimes/ae/README.md b/regimes/ae/README.md index e5554623..24264da1 100644 --- a/regimes/ae/README.md +++ b/regimes/ae/README.md @@ -1,13 +1,8 @@ # United Arab Emirates (AE) Tax Regime -This document provides an overview of the tax regime in the United Arab Emirates. The UAE tax regime primarily involves two types of indirect taxes: **Value-Added Tax (VAT)** and **Excise Tax**. +This document provides an overview of the tax regime in the United Arab Emirates. -## Types of Indirect Taxes in the UAE - -1. **Value-Added Tax (VAT)**: A general tax applied to most goods and services at a rate of 5%. -2. **Excise Tax**: Is a form of indirect tax applied to specific categories of products that are considered harmful to health or the environment, such as tobacco and sugary drinks. - -### 1. Value-Added Tax (VAT) +## Value-Added Tax (VAT) The UAE VAT system categorizes goods and services into three main VAT rates: @@ -15,28 +10,32 @@ The UAE VAT system categorizes goods and services into three main VAT rates: - **Zero Rate (0%)**: Applies to certain essential goods, exports, and specific services. - **Exempt**: Certain types of goods and services are exempt from VAT, including some financial services or residential real estate. -Businesses required to charge VAT must register with the UAE Federal Tax Authority to get the Tax Registration Number (TRN). Registration can be completed online via the [VAT registration portal](https://tax.gov.ae/en/services/vat.registration.aspx). +Businesses required to charge VAT must register with the UAE Federal Tax Authority to obtain a Tax Registration Number (TRN). Registration can be completed online via the [VAT registration portal](https://tax.gov.ae/en/services/vat.registration.aspx). + +## VAT Registration Requirements + +Businesses in the UAE must evaluate their eligibility for VAT registration based on their revenue and expenses. The registration requirements are as follows: + +- **Mandatory Registration**: Businesses are required to register for VAT if the total value of taxable supplies and imports exceeds **AED 375,000** in a 12-month period. +- **Voluntary Registration**: Businesses may choose to register voluntarily if the total value of taxable supplies and imports, or taxable expenses, exceeds **AED 187,500** within a 12-month period. -The algorithm to validate the TRN number was not located. Validation must be conducted directly on the official government website. +**Note:** Businesses below the voluntary registration threshold are not permitted to register for VAT and, therefore, will issue invoices without a TRN. -### 2. Excise Tax +For more information, visit the ([Federal Tax Authority website](https://tax.gov.ae/en/taxes/Vat/vat.topics/registration.for.vat.aspx)) -Excise Tax is a form of indirect tax on certain goods considered harmful to consumer health or the environment. The following products are subject to Excise Tax in the UAE: +### TRN Validation -- **Tobacco products** (including vapes and vape liquids): 100% -- **Sugary drinks**: 50% -- **Energy drinks**: 100% -- **Carbonated drinks**: 50% +Currently, no checksum method is available for validating the TRN. Therefore, verification must be performed directly through the official UAE government website. -#### Excise Tax Registration +### Alcohol Sales Tax -Businesses that intend to sell goods subject to Excise Tax must register with the UAE Federal Tax Authority. Registration can be completed online via the [official UAE tax authority website](https://tax.gov.ae/en/taxes/excise.tax/excise.tax.topics/excise.tax.registration.aspx). +In Dubai, a **30% sales tax** on alcohol previously existed but has since been abolished. Some sources suggest that a similar tax persists in Abu Dhabi, but without official confirmation, this has not been included in the UAE tax regime. ## VAT Invoicing Requirements -In the UAE, businesses must issue VAT-compliant invoices. There are two types of VAT invoices. The standard and the simplified invoice: +In the UAE, businesses must issue VAT-compliant invoices. There are two types of VAT invoices: the standard and the simplified invoice. **Simplified VAT Invoice**: Allowed in the following cases: - When the recipient of goods or services is **not VAT registered**. -- When the recipient of goods or services **is VAT registered**, and the transaction value does not exceed **AED 10,000** . +- When the recipient of goods or services **is VAT registered**, and the transaction value does not exceed **AED 10,000**. diff --git a/regimes/ae/ae.go b/regimes/ae/ae.go index fd3be11a..4dcb564a 100644 --- a/regimes/ae/ae.go +++ b/regimes/ae/ae.go @@ -14,20 +14,7 @@ func init() { tax.RegisterRegimeDef(New()) } -// Local tax category definition which is not considered standard. -const ( - TaxCategoryExcise cbc.Code = "EXCISE" -) - -// Specific tax rate codes. -const ( - TaxRateSmokingProducts cbc.Key = "smoking-product" - TaxRateCarbonatedDrinks cbc.Key = "carbonated-drink" - TaxRateEnergyDrinks cbc.Key = "energy-drink" - TaxRateSweetenedDrinks cbc.Key = "sweetened-drink" -) - -// New provides the tax region definition for UAE. +// New provides the tax region definition for AE. func New() *tax.RegimeDef { return &tax.RegimeDef{ Country: "AE", @@ -57,11 +44,10 @@ func New() *tax.RegimeDef { } } -// Validate checks the document type and determines if it can be validated. +// Validate function assesses the document type to determine if validation is required. +// Note that, under the AE tax regime, validation of the supplier's tax ID is not necessary if it does not meet the specified threshold (refer to README section for more details). func Validate(doc interface{}) error { switch obj := doc.(type) { - case *bill.Invoice: - return validateInvoice(obj) case *tax.Identity: return validateTaxIdentity(obj) } diff --git a/regimes/ae/invoices.go b/regimes/ae/invoices.go deleted file mode 100644 index fba12f08..00000000 --- a/regimes/ae/invoices.go +++ /dev/null @@ -1,32 +0,0 @@ -// Package ae provides the tax region definition for the United Arab Emirates. -package ae - -import ( - "github.com/invopop/gobl/bill" - "github.com/invopop/gobl/org" - "github.com/invopop/gobl/tax" - "github.com/invopop/validation" -) - -// validateInvoice validates the invoice, ensuring the TRN is always required. -func validateInvoice(inv *bill.Invoice) error { - return validation.ValidateStruct(inv, - validation.Field(&inv.Supplier, - validation.By(validateInvoiceSupplier), - ), - ) -} - -// validateInvoiceSupplier validates that the supplier (Party) has a Tax Registration Number (TRN). -func validateInvoiceSupplier(value any) error { - p, ok := value.(*org.Party) - if !ok || p == nil { - return nil - } - return validation.ValidateStruct(p, - validation.Field(&p.TaxID, - validation.Required, - tax.RequireIdentityCode, - ), - ) -} diff --git a/regimes/ae/invoices_test.go b/regimes/ae/invoices_test.go deleted file mode 100644 index 66640653..00000000 --- a/regimes/ae/invoices_test.go +++ /dev/null @@ -1,67 +0,0 @@ -// Package ae_test provides tests for the UAE invoice validation. -package ae_test - -import ( - "testing" - - "github.com/invopop/gobl/bill" - "github.com/invopop/gobl/num" - "github.com/invopop/gobl/org" - "github.com/invopop/gobl/tax" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// validInvoice creates a sample valid invoice for UAE. -func validInvoice() *bill.Invoice { - return &bill.Invoice{ - Regime: tax.WithRegime("AE"), - Series: "TEST", - Code: "0002", - Supplier: &org.Party{ - Name: "Test Supplier", - TaxID: &tax.Identity{ - Country: "AE", - Code: "123456789012345", - }, - }, - Customer: &org.Party{ - Name: "Test Customer", - TaxID: &tax.Identity{ - Country: "AE", - Code: "187654321098765", - }, - }, - Lines: []*bill.Line{ - { - Quantity: num.MakeAmount(1, 0), - Item: &org.Item{ - Name: "Test Item", - Price: num.MakeAmount(10000, 2), - Unit: org.UnitPackage, - }, - Taxes: tax.Set{ - { - Category: "VAT", - Rate: "standard", - }, - }, - }, - }, - } -} - -func TestInvoiceValidation(t *testing.T) { - t.Run("invoice with required TRN", func(t *testing.T) { - inv := validInvoice() - require.NoError(t, inv.Calculate()) - assert.NoError(t, inv.Validate()) - }) - - t.Run("invoice with missing TRN", func(t *testing.T) { - inv := validInvoice() - inv.Supplier.TaxID = nil - require.NoError(t, inv.Calculate()) - assert.ErrorContains(t, inv.Validate(), "supplier: (tax_id: cannot be blank.).") - }) -} diff --git a/regimes/ae/scenarios_test.go b/regimes/ae/scenarios_test.go new file mode 100644 index 00000000..28db4cda --- /dev/null +++ b/regimes/ae/scenarios_test.go @@ -0,0 +1,101 @@ +package ae_test + +import ( + "testing" + + "github.com/invopop/gobl/bill" + "github.com/invopop/gobl/cal" + "github.com/invopop/gobl/num" + "github.com/invopop/gobl/org" + "github.com/invopop/gobl/tax" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func testInvoiceReverseCharge(t *testing.T) *bill.Invoice { + t.Helper() + return &bill.Invoice{ + Code: "123TEST", + Currency: "AED", + Supplier: &org.Party{ + Name: "Test Supplier", + TaxID: &tax.Identity{ + Country: "AE", + Code: "123456789012345", + }, + }, + Customer: &org.Party{ + Name: "Test Customer", + TaxID: &tax.Identity{ + Country: "AE", + Code: "123456789012346", + }, + }, + IssueDate: cal.MakeDate(2023, 1, 15), + Tags: tax.WithTags(tax.TagReverseCharge), + Lines: []*bill.Line{ + { + Quantity: num.MakeAmount(5, 0), + Item: &org.Item{ + Name: "Service Item", + Price: num.MakeAmount(5000, 2), + }, + Taxes: tax.Set{ + { + Category: "VAT", + Rate: "standard", + }, + }, + }, + }, + } +} + +func testInvoiceSimplified(t *testing.T) *bill.Invoice { + t.Helper() + return &bill.Invoice{ + Code: "123TEST", + Currency: "AED", + Supplier: &org.Party{ + Name: "Test Supplier", + TaxID: &tax.Identity{ + Country: "AE", + Code: "123456789012345", + }, + }, + IssueDate: cal.MakeDate(2023, 1, 15), + Tags: tax.WithTags(tax.TagSimplified), + Lines: []*bill.Line{ + { + Quantity: num.MakeAmount(3, 0), + Item: &org.Item{ + Name: "Product Item", + Price: num.MakeAmount(2000, 2), + }, + Taxes: tax.Set{ + { + Category: "VAT", + Rate: "standard", + }, + }, + }, + }, + } +} + +func TestInvoiceScenarios(t *testing.T) { + i := testInvoiceReverseCharge(t) + require.NoError(t, i.Calculate()) + require.NoError(t, i.Validate()) + assert.Len(t, i.Notes, 1) + assert.Equal(t, i.Notes[0].Src, tax.TagReverseCharge) + assert.Equal(t, i.Notes[0].Text, "Reverse Charge / التحويل العكسي") + + i = testInvoiceSimplified(t) + require.NoError(t, i.Calculate()) + require.NoError(t, i.Validate()) + assert.Len(t, i.Notes, 1) + assert.Equal(t, i.Notes[0].Src, tax.TagSimplified) + assert.Equal(t, i.Notes[0].Text, "Simplified Tax Invoice / فاتورة ضريبية مبسطة") +} diff --git a/regimes/ae/tax_categories.go b/regimes/ae/tax_categories.go index 97fd7b06..69154e49 100644 --- a/regimes/ae/tax_categories.go +++ b/regimes/ae/tax_categories.go @@ -77,69 +77,4 @@ var taxCategories = []*tax.CategoryDef{ }, }, }, - // - // Excise Tax - // - { - Code: TaxCategoryExcise, - Retained: false, - Name: i18n.String{ - i18n.EN: "Excise Tax", - i18n.AR: "ضريبة السلع الانتقائية", - }, - Title: i18n.String{ - i18n.EN: "UAE Excise Tax", - i18n.AR: "ضريبة السلع الانتقائية في الإمارات", - }, - Rates: []*tax.RateDef{ - { - Key: TaxRateSmokingProducts, - Name: i18n.String{ - i18n.EN: "Smoking Products Rate", - i18n.AR: "معدل منتجات التدخين", - }, - Values: []*tax.RateValueDef{ - { - Percent: num.MakePercentage(100, 3), - }, - }, - }, - { - Key: TaxRateCarbonatedDrinks, - Name: i18n.String{ - i18n.EN: "Carbonated Drinks Rate", - i18n.AR: "معدل المشروبات الغازية", - }, - Values: []*tax.RateValueDef{ - { - Percent: num.MakePercentage(50, 3), - }, - }, - }, - { - Key: TaxRateEnergyDrinks, - Name: i18n.String{ - i18n.EN: "Energy Drinks Rate", - i18n.AR: "معدل مشروبات الطاقة", - }, - Values: []*tax.RateValueDef{ - { - Percent: num.MakePercentage(100, 3), - }, - }, - }, - { - Key: TaxRateSweetenedDrinks, - Name: i18n.String{ - i18n.EN: "Sweetened Drinks Rate", - i18n.AR: "معدل المشروبات المحلاة", - }, - Values: []*tax.RateValueDef{ - { - Percent: num.MakePercentage(50, 3), - }, - }, - }, - }, - }, } From 3ea95106bc01a833c80e9f3af80308417bf6107c Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 11 Nov 2024 23:15:31 +0100 Subject: [PATCH 03/22] add Arab Emirates examples --- examples/ae/invoice-ae-ae-stnr.yaml | 48 ++++++++++++++++++++++++++ examples/ae/invoice-ae-simplified.yaml | 35 +++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 examples/ae/invoice-ae-ae-stnr.yaml create mode 100644 examples/ae/invoice-ae-simplified.yaml diff --git a/examples/ae/invoice-ae-ae-stnr.yaml b/examples/ae/invoice-ae-ae-stnr.yaml new file mode 100644 index 00000000..dd274b2f --- /dev/null +++ b/examples/ae/invoice-ae-ae-stnr.yaml @@ -0,0 +1,48 @@ +$schema: "https://gobl.org/draft-0/bill/invoice" +uuid: "3aea7b56-59d8-4beb-90bd-f8f280d852a0" +currency: "AED" +issue_date: "2022-02-01" +series: "SAMPLE" +code: "001" + +supplier: + tax_id: + country: "AE" + code: "123456789012346" + name: "Provide One LLC" + emails: + - addr: "billing@example.ae" + addresses: + - num: "16" + street: "Sheikh Zayed Road" + locality: "Dubai" + code: "00000" + country: "AE" + +customer: + tax_id: + country: "AE" + code: "123456789012345" + name: "Sample Consumer" + emails: + - addr: "email@sample.ae" + addresses: + - num: "25" + street: "Al Maryah Island" + locality: "Abu Dhabi" + code: "00000" + country: "AE" + +lines: + - quantity: 20 + item: + name: "Development services" + price: "90.00" + unit: "h" + discounts: + - percent: "5%" + reason: "Special discount" + taxes: + - cat: VAT + rate: "5%" + reason: "Standard VAT rate in UAE" diff --git a/examples/ae/invoice-ae-simplified.yaml b/examples/ae/invoice-ae-simplified.yaml new file mode 100644 index 00000000..a3f2a6a8 --- /dev/null +++ b/examples/ae/invoice-ae-simplified.yaml @@ -0,0 +1,35 @@ +$schema: "https://gobl.org/draft-0/bill/invoice" +uuid: "3aea7b56-59d8-4beb-90bd-f8f280d852a0" +currency: "AED" +issue_date: "2022-02-01" +series: "SAMPLE" +code: "001" +tax: + tags: + - "simplified" +supplier: + tax_id: + country: "AE" + code: "123456789012346" + name: "Provide One LLC" + emails: + - addr: "billing@example.ae" + addresses: + - num: "16" + street: "Sheikh Zayed Road" + locality: "Dubai" + code: "00000" + country: "AE" + +lines: + - quantity: 20 + item: + name: "Development services" + price: "90.00" + unit: "h" + discounts: + - percent: "10%" + reason: "Special discount" + taxes: + - cat: VAT + rate: "standard" \ No newline at end of file From 8ed59d985425432b282103c6fc911bb8663f1b46 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 11 Nov 2024 23:16:34 +0100 Subject: [PATCH 04/22] add Arab Emirates tax regime --- CHANGELOG.md | 4 ++ data/schemas/regimes/mx/food-vouchers.json | 44 ++++-------- .../regimes/mx/fuel-account-balance.json | 68 ++++++------------- 3 files changed, 39 insertions(+), 77 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 33e1fe67..0174682f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -591,3 +591,7 @@ New number formatting support! Expect some possible breaking SDK changes with th ## [v0.70.1] - 2024-03-25 - Last version before CHANGELOG.md. + +### Added + +- United Arab Emirates (AE) tax regime. diff --git a/data/schemas/regimes/mx/food-vouchers.json b/data/schemas/regimes/mx/food-vouchers.json index 9cedd146..07de9d70 100644 --- a/data/schemas/regimes/mx/food-vouchers.json +++ b/data/schemas/regimes/mx/food-vouchers.json @@ -7,18 +7,15 @@ "properties": { "employer_registration": { "type": "string", - "title": "Employer Registration", - "description": "Customer's employer registration number (maps to `registroPatronal`)." + "title": "Employer Registration" }, "account_number": { "type": "string", - "title": "Account Number", - "description": "Customer's account number (maps to `numeroDeCuenta`)." + "title": "Account Number" }, "total": { "$ref": "https://gobl.org/draft-0/num/amount", "title": "Total", - "description": "Sum of all line amounts (calculated, maps to `total`).", "calculated": true }, "lines": { @@ -26,8 +23,7 @@ "$ref": "#/$defs/FoodVouchersLine" }, "type": "array", - "title": "Lines", - "description": "List of food vouchers issued to the customer's employees (maps to `Conceptos`)." + "title": "Lines" } }, "type": "object", @@ -35,30 +31,25 @@ "account_number", "total", "lines" - ], - "description": "FoodVouchers carries the data to produce a CFDI's \"Complemento de Vales de Despensa\" (version 1.0) providing detailed information about food vouchers issued by an e-wallet supplier to its customer's employees." + ] }, "FoodVouchersEmployee": { "properties": { "tax_code": { "$ref": "https://gobl.org/draft-0/cbc/code", - "title": "Employee's Tax Identity Code", - "description": "Employee's tax identity code (maps to `rfc`)." + "title": "Employee's Tax Identity Code" }, "curp": { "$ref": "https://gobl.org/draft-0/cbc/code", - "title": "Employee's CURP", - "description": "Employee's CURP (\"Clave Única de Registro de Población\", maps to `curp`)." + "title": "Employee's CURP" }, "name": { "type": "string", - "title": "Employee's Name", - "description": "Employee's name (maps to `nombre`)." + "title": "Employee's Name" }, "social_security": { "$ref": "https://gobl.org/draft-0/cbc/code", - "title": "Employee's Social Security Number", - "description": "Employee's Social Security Number (maps to `numSeguridadSocial`)." + "title": "Employee's Social Security Number" } }, "type": "object", @@ -66,36 +57,30 @@ "tax_code", "curp", "name" - ], - "description": "FoodVouchersEmployee represents an employee that received a food voucher." + ] }, "FoodVouchersLine": { "properties": { "i": { "type": "integer", "title": "Index", - "description": "Line number starting from 1 (calculated).", "calculated": true }, "e_wallet_id": { "$ref": "https://gobl.org/draft-0/cbc/code", - "title": "E-wallet Identifier", - "description": "Identifier of the e-wallet that received the food voucher (maps to `Identificador`)." + "title": "E-wallet Identifier" }, "issue_date_time": { "$ref": "https://gobl.org/draft-0/cal/date-time", - "title": "Issue Date and Time", - "description": "Date and time of the food voucher's issue (maps to `Fecha`)." + "title": "Issue Date and Time" }, "employee": { "$ref": "#/$defs/FoodVouchersEmployee", - "title": "Employee", - "description": "Employee that received the food voucher." + "title": "Employee" }, "amount": { "$ref": "https://gobl.org/draft-0/num/amount", - "title": "Amount", - "description": "Amount of the food voucher (maps to `importe`)." + "title": "Amount" } }, "type": "object", @@ -104,8 +89,7 @@ "e_wallet_id", "issue_date_time", "amount" - ], - "description": "FoodVouchersLine represents a single food voucher issued to the e-wallet of one of the customer's employees." + ] } } } \ No newline at end of file diff --git a/data/schemas/regimes/mx/fuel-account-balance.json b/data/schemas/regimes/mx/fuel-account-balance.json index e8c05531..7fd39dd6 100644 --- a/data/schemas/regimes/mx/fuel-account-balance.json +++ b/data/schemas/regimes/mx/fuel-account-balance.json @@ -7,19 +7,16 @@ "properties": { "account_number": { "type": "string", - "title": "Account Number", - "description": "Customer's account number (maps to `NumeroDeCuenta`)." + "title": "Account Number" }, "subtotal": { "$ref": "https://gobl.org/draft-0/num/amount", "title": "Subtotal", - "description": "Sum of all line totals (i.e. taxes not included) (calculated, maps to `SubTotal`).", "calculated": true }, "total": { "$ref": "https://gobl.org/draft-0/num/amount", "title": "Total", - "description": "Grand total after taxes have been applied (calculated, maps to `Total`).", "calculated": true }, "lines": { @@ -27,8 +24,7 @@ "$ref": "#/$defs/FuelAccountLine" }, "type": "array", - "title": "Lines", - "description": "List of fuel purchases made with the customer's e-wallets (maps to `Conceptos`)." + "title": "Lines" } }, "type": "object", @@ -37,30 +33,25 @@ "subtotal", "total", "lines" - ], - "description": "FuelAccountBalance carries the data to produce a CFDI's \"Complemento de Estado de Cuenta de Combustibles para Monederos Electrónicos\" (version 1.2 revision B) providing detailed information about fuel purchases made with electronic wallets." + ] }, "FuelAccountItem": { "properties": { "type": { "$ref": "https://gobl.org/draft-0/cbc/code", - "title": "Type", - "description": "Type of fuel (one of `c_ClaveTipoCombustible` codes, maps to `TipoCombustible`)." + "title": "Type" }, "unit": { "$ref": "https://gobl.org/draft-0/org/unit", - "title": "Unit", - "description": "Reference unit of measure used in the price and the quantity (maps to `Unidad`)." + "title": "Unit" }, "name": { "type": "string", - "title": "Name", - "description": "Name of the fuel (maps to `NombreCombustible`)." + "title": "Name" }, "price": { "$ref": "https://gobl.org/draft-0/num/amount", - "title": "Price", - "description": "Base price of a single unit of the fuel without taxes (maps to `ValorUnitario`)." + "title": "Price" } }, "type": "object", @@ -68,56 +59,46 @@ "type", "name", "price" - ], - "description": "FuelAccountItem provides the details of a fuel purchase." + ] }, "FuelAccountLine": { "properties": { "i": { "type": "integer", "title": "Index", - "description": "Index of the line starting from 1 (calculated)", "calculated": true }, "e_wallet_id": { "$ref": "https://gobl.org/draft-0/cbc/code", - "title": "E-wallet Identifier", - "description": "Identifier of the e-wallet used to make the purchase (maps to `Identificador`)." + "title": "E-wallet Identifier" }, "purchase_date_time": { "$ref": "https://gobl.org/draft-0/cal/date-time", - "title": "Purchase Date and Time", - "description": "Date and time of the purchase (maps to `Fecha`)." + "title": "Purchase Date and Time" }, "vendor_tax_code": { "$ref": "https://gobl.org/draft-0/cbc/code", - "title": "Vendor's Tax Identity Code", - "description": "Tax Identity Code of the fuel's vendor (maps to `Rfc`)" + "title": "Vendor's Tax Identity Code" }, "service_station_code": { "$ref": "https://gobl.org/draft-0/cbc/code", - "title": "Service Station Code", - "description": "Code of the service station where the purchase was made (maps to `ClaveEstacion`)." + "title": "Service Station Code" }, "quantity": { "$ref": "https://gobl.org/draft-0/num/amount", - "title": "Quantity", - "description": "Amount of fuel units purchased (maps to `Cantidad`)" + "title": "Quantity" }, "item": { "$ref": "#/$defs/FuelAccountItem", - "title": "Item", - "description": "Details of the fuel purchased." + "title": "Item" }, "purchase_code": { "$ref": "https://gobl.org/draft-0/cbc/code", - "title": "Purchase Code", - "description": "Identifier of the purchase (maps to `FolioOperacion`)." + "title": "Purchase Code" }, "total": { "$ref": "https://gobl.org/draft-0/num/amount", "title": "Total", - "description": "Result of quantity multiplied by the unit price (maps to `Importe`).", "calculated": true }, "taxes": { @@ -125,8 +106,7 @@ "$ref": "#/$defs/FuelAccountTax" }, "type": "array", - "title": "Taxes", - "description": "Map of taxes applied to the purchase (maps to `Traslados`)." + "title": "Taxes" } }, "type": "object", @@ -141,30 +121,25 @@ "purchase_code", "total", "taxes" - ], - "description": "FuelAccountLine represents a single fuel purchase made with an e-wallet issued by the invoice's supplier." + ] }, "FuelAccountTax": { "properties": { "cat": { "$ref": "https://gobl.org/draft-0/cbc/code", - "title": "Category", - "description": "Category that identifies the tax (\"VAT\" or \"IEPS\", maps to `Impuesto`)" + "title": "Category" }, "percent": { "$ref": "https://gobl.org/draft-0/num/percentage", - "title": "Percent", - "description": "Percent applicable to the line total (tasa) to use instead of Rate (maps to `TasaoCuota`)" + "title": "Percent" }, "rate": { "$ref": "https://gobl.org/draft-0/num/amount", - "title": "Rate", - "description": "Rate is a fixed fee to apply to the line quantity (cuota) (maps to `TasaOCuota`)" + "title": "Rate" }, "amount": { "$ref": "https://gobl.org/draft-0/num/amount", "title": "Amount", - "description": "Total amount of the tax once the percent or rate has been applied (maps to `Importe`).", "calculated": true } }, @@ -172,8 +147,7 @@ "required": [ "cat", "amount" - ], - "description": "FuelAccountTax represents a single tax applied to a fuel purchase." + ] } } } \ No newline at end of file From d89f3ef00dd5cd5991fd36f7c5c1cb649608469b Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 11 Nov 2024 23:20:57 +0100 Subject: [PATCH 05/22] Initial Arab Emirates regime adjustments --- regimes/ae/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/regimes/ae/README.md b/regimes/ae/README.md index 24264da1..401f40c2 100644 --- a/regimes/ae/README.md +++ b/regimes/ae/README.md @@ -21,7 +21,7 @@ Businesses in the UAE must evaluate their eligibility for VAT registration based **Note:** Businesses below the voluntary registration threshold are not permitted to register for VAT and, therefore, will issue invoices without a TRN. -For more information, visit the ([Federal Tax Authority website](https://tax.gov.ae/en/taxes/Vat/vat.topics/registration.for.vat.aspx)) +For more information, visit the [Federal Tax Authority website](https://tax.gov.ae/en/taxes/Vat/vat.topics/registration.for.vat.aspx) ### TRN Validation @@ -33,7 +33,7 @@ In Dubai, a **30% sales tax** on alcohol previously existed but has since been a ## VAT Invoicing Requirements -In the UAE, businesses must issue VAT-compliant invoices. There are two types of VAT invoices: the standard and the simplified invoice. +There are two types of VAT invoices: the standard and the simplified invoice. **Simplified VAT Invoice**: Allowed in the following cases: From 2a81adb234e42f7c0727982f320e8a6468b9bd69 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 11 Nov 2024 23:54:32 +0100 Subject: [PATCH 06/22] Initial Arab Emirates regime adjustments --- regimes/ae/ae.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/regimes/ae/ae.go b/regimes/ae/ae.go index 4dcb564a..23fe55f5 100644 --- a/regimes/ae/ae.go +++ b/regimes/ae/ae.go @@ -45,7 +45,7 @@ func New() *tax.RegimeDef { } // Validate function assesses the document type to determine if validation is required. -// Note that, under the AE tax regime, validation of the supplier's tax ID is not necessary if it does not meet the specified threshold (refer to README section for more details). +// Note that, under the AE tax regime, validation of the supplier's tax ID is not necessary if it does not meet the specified threshold (refer to the README section for more details). func Validate(doc interface{}) error { switch obj := doc.(type) { case *tax.Identity: From ea8651f48c4102b9eb1596cb1d5fe046c04b452e Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 12 Nov 2024 10:30:15 +0100 Subject: [PATCH 07/22] Updating schemas --- data/schemas/regimes/mx/food-vouchers.json | 44 ++++++++---- .../regimes/mx/fuel-account-balance.json | 68 +++++++++++++------ 2 files changed, 77 insertions(+), 35 deletions(-) diff --git a/data/schemas/regimes/mx/food-vouchers.json b/data/schemas/regimes/mx/food-vouchers.json index 07de9d70..9cedd146 100644 --- a/data/schemas/regimes/mx/food-vouchers.json +++ b/data/schemas/regimes/mx/food-vouchers.json @@ -7,15 +7,18 @@ "properties": { "employer_registration": { "type": "string", - "title": "Employer Registration" + "title": "Employer Registration", + "description": "Customer's employer registration number (maps to `registroPatronal`)." }, "account_number": { "type": "string", - "title": "Account Number" + "title": "Account Number", + "description": "Customer's account number (maps to `numeroDeCuenta`)." }, "total": { "$ref": "https://gobl.org/draft-0/num/amount", "title": "Total", + "description": "Sum of all line amounts (calculated, maps to `total`).", "calculated": true }, "lines": { @@ -23,7 +26,8 @@ "$ref": "#/$defs/FoodVouchersLine" }, "type": "array", - "title": "Lines" + "title": "Lines", + "description": "List of food vouchers issued to the customer's employees (maps to `Conceptos`)." } }, "type": "object", @@ -31,25 +35,30 @@ "account_number", "total", "lines" - ] + ], + "description": "FoodVouchers carries the data to produce a CFDI's \"Complemento de Vales de Despensa\" (version 1.0) providing detailed information about food vouchers issued by an e-wallet supplier to its customer's employees." }, "FoodVouchersEmployee": { "properties": { "tax_code": { "$ref": "https://gobl.org/draft-0/cbc/code", - "title": "Employee's Tax Identity Code" + "title": "Employee's Tax Identity Code", + "description": "Employee's tax identity code (maps to `rfc`)." }, "curp": { "$ref": "https://gobl.org/draft-0/cbc/code", - "title": "Employee's CURP" + "title": "Employee's CURP", + "description": "Employee's CURP (\"Clave Única de Registro de Población\", maps to `curp`)." }, "name": { "type": "string", - "title": "Employee's Name" + "title": "Employee's Name", + "description": "Employee's name (maps to `nombre`)." }, "social_security": { "$ref": "https://gobl.org/draft-0/cbc/code", - "title": "Employee's Social Security Number" + "title": "Employee's Social Security Number", + "description": "Employee's Social Security Number (maps to `numSeguridadSocial`)." } }, "type": "object", @@ -57,30 +66,36 @@ "tax_code", "curp", "name" - ] + ], + "description": "FoodVouchersEmployee represents an employee that received a food voucher." }, "FoodVouchersLine": { "properties": { "i": { "type": "integer", "title": "Index", + "description": "Line number starting from 1 (calculated).", "calculated": true }, "e_wallet_id": { "$ref": "https://gobl.org/draft-0/cbc/code", - "title": "E-wallet Identifier" + "title": "E-wallet Identifier", + "description": "Identifier of the e-wallet that received the food voucher (maps to `Identificador`)." }, "issue_date_time": { "$ref": "https://gobl.org/draft-0/cal/date-time", - "title": "Issue Date and Time" + "title": "Issue Date and Time", + "description": "Date and time of the food voucher's issue (maps to `Fecha`)." }, "employee": { "$ref": "#/$defs/FoodVouchersEmployee", - "title": "Employee" + "title": "Employee", + "description": "Employee that received the food voucher." }, "amount": { "$ref": "https://gobl.org/draft-0/num/amount", - "title": "Amount" + "title": "Amount", + "description": "Amount of the food voucher (maps to `importe`)." } }, "type": "object", @@ -89,7 +104,8 @@ "e_wallet_id", "issue_date_time", "amount" - ] + ], + "description": "FoodVouchersLine represents a single food voucher issued to the e-wallet of one of the customer's employees." } } } \ No newline at end of file diff --git a/data/schemas/regimes/mx/fuel-account-balance.json b/data/schemas/regimes/mx/fuel-account-balance.json index 7fd39dd6..e8c05531 100644 --- a/data/schemas/regimes/mx/fuel-account-balance.json +++ b/data/schemas/regimes/mx/fuel-account-balance.json @@ -7,16 +7,19 @@ "properties": { "account_number": { "type": "string", - "title": "Account Number" + "title": "Account Number", + "description": "Customer's account number (maps to `NumeroDeCuenta`)." }, "subtotal": { "$ref": "https://gobl.org/draft-0/num/amount", "title": "Subtotal", + "description": "Sum of all line totals (i.e. taxes not included) (calculated, maps to `SubTotal`).", "calculated": true }, "total": { "$ref": "https://gobl.org/draft-0/num/amount", "title": "Total", + "description": "Grand total after taxes have been applied (calculated, maps to `Total`).", "calculated": true }, "lines": { @@ -24,7 +27,8 @@ "$ref": "#/$defs/FuelAccountLine" }, "type": "array", - "title": "Lines" + "title": "Lines", + "description": "List of fuel purchases made with the customer's e-wallets (maps to `Conceptos`)." } }, "type": "object", @@ -33,25 +37,30 @@ "subtotal", "total", "lines" - ] + ], + "description": "FuelAccountBalance carries the data to produce a CFDI's \"Complemento de Estado de Cuenta de Combustibles para Monederos Electrónicos\" (version 1.2 revision B) providing detailed information about fuel purchases made with electronic wallets." }, "FuelAccountItem": { "properties": { "type": { "$ref": "https://gobl.org/draft-0/cbc/code", - "title": "Type" + "title": "Type", + "description": "Type of fuel (one of `c_ClaveTipoCombustible` codes, maps to `TipoCombustible`)." }, "unit": { "$ref": "https://gobl.org/draft-0/org/unit", - "title": "Unit" + "title": "Unit", + "description": "Reference unit of measure used in the price and the quantity (maps to `Unidad`)." }, "name": { "type": "string", - "title": "Name" + "title": "Name", + "description": "Name of the fuel (maps to `NombreCombustible`)." }, "price": { "$ref": "https://gobl.org/draft-0/num/amount", - "title": "Price" + "title": "Price", + "description": "Base price of a single unit of the fuel without taxes (maps to `ValorUnitario`)." } }, "type": "object", @@ -59,46 +68,56 @@ "type", "name", "price" - ] + ], + "description": "FuelAccountItem provides the details of a fuel purchase." }, "FuelAccountLine": { "properties": { "i": { "type": "integer", "title": "Index", + "description": "Index of the line starting from 1 (calculated)", "calculated": true }, "e_wallet_id": { "$ref": "https://gobl.org/draft-0/cbc/code", - "title": "E-wallet Identifier" + "title": "E-wallet Identifier", + "description": "Identifier of the e-wallet used to make the purchase (maps to `Identificador`)." }, "purchase_date_time": { "$ref": "https://gobl.org/draft-0/cal/date-time", - "title": "Purchase Date and Time" + "title": "Purchase Date and Time", + "description": "Date and time of the purchase (maps to `Fecha`)." }, "vendor_tax_code": { "$ref": "https://gobl.org/draft-0/cbc/code", - "title": "Vendor's Tax Identity Code" + "title": "Vendor's Tax Identity Code", + "description": "Tax Identity Code of the fuel's vendor (maps to `Rfc`)" }, "service_station_code": { "$ref": "https://gobl.org/draft-0/cbc/code", - "title": "Service Station Code" + "title": "Service Station Code", + "description": "Code of the service station where the purchase was made (maps to `ClaveEstacion`)." }, "quantity": { "$ref": "https://gobl.org/draft-0/num/amount", - "title": "Quantity" + "title": "Quantity", + "description": "Amount of fuel units purchased (maps to `Cantidad`)" }, "item": { "$ref": "#/$defs/FuelAccountItem", - "title": "Item" + "title": "Item", + "description": "Details of the fuel purchased." }, "purchase_code": { "$ref": "https://gobl.org/draft-0/cbc/code", - "title": "Purchase Code" + "title": "Purchase Code", + "description": "Identifier of the purchase (maps to `FolioOperacion`)." }, "total": { "$ref": "https://gobl.org/draft-0/num/amount", "title": "Total", + "description": "Result of quantity multiplied by the unit price (maps to `Importe`).", "calculated": true }, "taxes": { @@ -106,7 +125,8 @@ "$ref": "#/$defs/FuelAccountTax" }, "type": "array", - "title": "Taxes" + "title": "Taxes", + "description": "Map of taxes applied to the purchase (maps to `Traslados`)." } }, "type": "object", @@ -121,25 +141,30 @@ "purchase_code", "total", "taxes" - ] + ], + "description": "FuelAccountLine represents a single fuel purchase made with an e-wallet issued by the invoice's supplier." }, "FuelAccountTax": { "properties": { "cat": { "$ref": "https://gobl.org/draft-0/cbc/code", - "title": "Category" + "title": "Category", + "description": "Category that identifies the tax (\"VAT\" or \"IEPS\", maps to `Impuesto`)" }, "percent": { "$ref": "https://gobl.org/draft-0/num/percentage", - "title": "Percent" + "title": "Percent", + "description": "Percent applicable to the line total (tasa) to use instead of Rate (maps to `TasaoCuota`)" }, "rate": { "$ref": "https://gobl.org/draft-0/num/amount", - "title": "Rate" + "title": "Rate", + "description": "Rate is a fixed fee to apply to the line quantity (cuota) (maps to `TasaOCuota`)" }, "amount": { "$ref": "https://gobl.org/draft-0/num/amount", "title": "Amount", + "description": "Total amount of the tax once the percent or rate has been applied (maps to `Importe`).", "calculated": true } }, @@ -147,7 +172,8 @@ "required": [ "cat", "amount" - ] + ], + "description": "FuelAccountTax represents a single tax applied to a fuel purchase." } } } \ No newline at end of file From bb27687b86fbd54ed49af6557d8ab3d5614f97cb Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 12 Nov 2024 10:33:50 +0100 Subject: [PATCH 08/22] add Arab Emirates tax regime --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0578aaeb..9cc7d816 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -602,4 +602,4 @@ New number formatting support! Expect some possible breaking SDK changes with th ### Added -- United Arab Emirates (AE) tax regime. +- ae: added UAE regime From 99d792e3bed9a982702d2be1ee87f8d6593397e7 Mon Sep 17 00:00:00 2001 From: Sam Lown Date: Mon, 18 Nov 2024 09:08:33 +0000 Subject: [PATCH 09/22] Adding dates to CHANGELOG --- CHANGELOG.md | 35 +++++++++++++++-------------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c3b6348..166af027 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p - `bill`: `Invoice` `GetExtensions` method now works correctly if missing totals [Issue #424](https://github.com/invopop/gobl/issues/424). -## [v0.205.0] +## [v0.205.0] - 2024-11-12 ### Added @@ -36,7 +36,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p - `cal`: Fixing json schema issue with date times. -## [v0.204.1] +## [v0.204.1] - 2024-11-04 ### Added @@ -46,7 +46,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p - `tax`: identity code handling will skip default validation for specific countries that use special characters. -## [v0.204.0] +## [v0.204.0] - 2024-10-31 ### Added @@ -85,7 +85,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p - `mx`: Tax ID validation now correctly supports `&` and `Ñ` symbols in codes. -## [v0.203.0] +## [v0.203.0] - 2024-20-21 ### Added @@ -99,7 +99,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p - `tax.Identity`: support Calculate method to normalize IDs. - `tax.Regime`: properly set regime when alternative codes is given. -## [v0.202.0] +## [v0.202.0] - 2024-10-14 ### Changed @@ -115,7 +115,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p - `mx`: fixed panic when normalizing an invoice with `tax` but no `ext` inside. -## [v0.201.0] +## [v0.201.0] - 2024-10-07 ### Fixed @@ -128,13 +128,13 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p - `pt`: reduced rate category for PT-MA was updated to reflect latest value of 4% - `co-dian-v2`: moved from `co` tax regime into own addon. -## [v0.200.1] +## [v0.200.1] - 2024-09-30 ### Fixed - `pt`: moving invoice tags from saft addon to regime, ensure defaults present. -## [v0.200.0] +## [v0.200.0] - 2024-09-26 Another ~~significant~~ epic release. Introducing "add-ons" which move the normalization and validation rules from Tax Regimes to specific packages that need to be enabled inside a document to be used. @@ -176,13 +176,13 @@ Finally, the `draft` flag has been removed from the header, and much more emphas - `org`: `DocumentRef` consolidates references to previous documents in a single place. - `bill`: invoice type option `other` for usage when regular scenarios do not apply. -## [v0.115.1] +## [v0.115.1] - 2024-09-10 ### Fixes - `tax`: totals calculator was ignoring tax combos with rate and percent, when they should be classed as exempt. -## [v0.115.0] +## [v0.115.0] - 2024-09-10 This one is big... @@ -235,20 +235,20 @@ Invoices in GOBL can now also finally produced for any country in the world, eve - `tax.Scenario`: potential issue around matching notes. - `tax.Set`: improved validation embedded error handling. -## [v0.114.0] +## [v0.114.0] - 2024-08-26 ### Changed - `org.Name`: either given **or** surname are required, as opposed to both at the same time. -## [v0.113.0] +## [v0.113.0] - 2024-08-01 ### Added - `head`: validation rule to check for the presence of stamps - GR: support for credit notes -## [v0.112.0] +## [v0.112.0] - 2024-07-29 Significant set of small changes related to renaming of the `l10n.CountryCode` type. The main reason for this is an attempt to reduce confusion between regular ISO country selection, and the specific country codes used for tax purposes. Normally they coincide, but exception cases like for Greece, whose ISO code is `GR` but use `EL` for tax purposes, or `XI` for companies in Northern Ireland, mean that there needs to be a clear selection. @@ -264,6 +264,8 @@ Significant set of small changes related to renaming of the `l10n.CountryCode` t - GR: support for simplified invoices - `l10n`: ISO and Tax lists of country definitions available, e.g. `l10n.Countries().ISO()` - `tax`: support for alternative country codes +- `tax`: Scenarios now handle extension key and value for filtering. +- PT: exemption text handling moved to scenarios. ### Upgraded @@ -273,13 +275,6 @@ Significant set of small changes related to renaming of the `l10n.CountryCode` t - GR: fixed certain tax combos not getting calculated by the regime -## [v0.112.0] - 2024-07-26 - -### Added - -- `tax`: Scenarios now handle extension key and value for filtering. -- PT: exemption text handling moved to scenarios. - ## [v0.111.1] - 2024-07-25 ### Added From ed7cc61078cd71d789f59cdee5722de97f227c50 Mon Sep 17 00:00:00 2001 From: Menendez6 Date: Mon, 18 Nov 2024 17:57:58 +0000 Subject: [PATCH 10/22] Deleting supplier validation --- regimes/ch/ch.go | 2 -- regimes/ch/invoices.go | 31 ------------------- regimes/ch/invoices_test.go | 60 ------------------------------------- 3 files changed, 93 deletions(-) delete mode 100644 regimes/ch/invoices.go delete mode 100644 regimes/ch/invoices_test.go diff --git a/regimes/ch/ch.go b/regimes/ch/ch.go index 0b89dd05..666da54c 100644 --- a/regimes/ch/ch.go +++ b/regimes/ch/ch.go @@ -46,8 +46,6 @@ func New() *tax.RegimeDef { // Validate checks the document type and determines if it can be validated. func Validate(doc any) error { switch obj := doc.(type) { - case *bill.Invoice: - return validateInvoice(obj) case *tax.Identity: return validateTaxIdentity(obj) } diff --git a/regimes/ch/invoices.go b/regimes/ch/invoices.go deleted file mode 100644 index e3fd9c5d..00000000 --- a/regimes/ch/invoices.go +++ /dev/null @@ -1,31 +0,0 @@ -package ch - -import ( - "github.com/invopop/gobl/bill" - "github.com/invopop/gobl/org" - "github.com/invopop/gobl/tax" - "github.com/invopop/validation" -) - -func validateInvoice(inv *bill.Invoice) error { - return validation.ValidateStruct(inv, - validation.Field(&inv.Supplier, - validation.By(validateInvoiceSupplier), - validation.Skip, - ), - ) -} - -func validateInvoiceSupplier(value any) error { - p, ok := value.(*org.Party) - if !ok || p == nil { - return nil - } - return validation.ValidateStruct(p, - validation.Field(&p.TaxID, - validation.Required, - tax.RequireIdentityCode, - validation.Skip, - ), - ) -} diff --git a/regimes/ch/invoices_test.go b/regimes/ch/invoices_test.go deleted file mode 100644 index df71a841..00000000 --- a/regimes/ch/invoices_test.go +++ /dev/null @@ -1,60 +0,0 @@ -package ch_test - -import ( - "testing" - - "github.com/invopop/gobl/bill" - "github.com/invopop/gobl/num" - "github.com/invopop/gobl/org" - "github.com/invopop/gobl/tax" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func validInvoice() *bill.Invoice { - return &bill.Invoice{ - Series: "TEST", - Code: "0002", - Supplier: &org.Party{ - Name: "Test Supplier", - TaxID: &tax.Identity{ - Country: "CH", - Code: "E100416306", - }, - }, - Customer: &org.Party{ - Name: "Test Customer", - TaxID: &tax.Identity{ - Country: "CH", - Code: "E432825998", - }, - }, - Lines: []*bill.Line{ - { - Quantity: num.MakeAmount(1, 0), - Item: &org.Item{ - Name: "bogus", - Price: num.MakeAmount(10000, 2), - Unit: org.UnitPackage, - }, - Taxes: tax.Set{ - { - Category: "VAT", - Rate: "standard", - }, - }, - }, - }, - } -} - -func TestInvoiceValidation(t *testing.T) { - inv := validInvoice() - require.NoError(t, inv.Calculate()) - assert.NoError(t, inv.Validate()) - - inv = validInvoice() - inv.Supplier.TaxID.Code = "" - require.NoError(t, inv.Calculate()) - assert.ErrorContains(t, inv.Validate(), "supplier: (tax_id: (code: cannot be blank.).)") -} From 87fe0de6f4417ce7745d0f1cabdb35b9d8bdeb46 Mon Sep 17 00:00:00 2001 From: Menendez6 Date: Mon, 18 Nov 2024 18:07:59 +0000 Subject: [PATCH 11/22] Changes in Changelog.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 166af027..c9bcb805 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p ### Fixes +- `ch`: Deleted Supplier validation (not needed for under 2300 CHF/year) - `bill`: `Invoice` `GetExtensions` method now works correctly if missing totals [Issue #424](https://github.com/invopop/gobl/issues/424). ## [v0.205.0] - 2024-11-12 From fe98d991396c100d53624410aaa98c451f1bb35d Mon Sep 17 00:00:00 2001 From: Menendez6 Date: Tue, 19 Nov 2024 08:32:07 +0000 Subject: [PATCH 12/22] Adding test for no tax id supplier --- regimes/ch/invoices_test.go | 60 +++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 regimes/ch/invoices_test.go diff --git a/regimes/ch/invoices_test.go b/regimes/ch/invoices_test.go new file mode 100644 index 00000000..7a13aebb --- /dev/null +++ b/regimes/ch/invoices_test.go @@ -0,0 +1,60 @@ +package ch_test + +import ( + "testing" + + "github.com/invopop/gobl/bill" + "github.com/invopop/gobl/num" + "github.com/invopop/gobl/org" + "github.com/invopop/gobl/tax" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func validInvoice() *bill.Invoice { + return &bill.Invoice{ + Series: "TEST", + Code: "0002", + Supplier: &org.Party{ + Name: "Test Supplier", + TaxID: &tax.Identity{ + Country: "CH", + Code: "E100416306", + }, + }, + Customer: &org.Party{ + Name: "Test Customer", + TaxID: &tax.Identity{ + Country: "CH", + Code: "E432825998", + }, + }, + Lines: []*bill.Line{ + { + Quantity: num.MakeAmount(1, 0), + Item: &org.Item{ + Name: "bogus", + Price: num.MakeAmount(10000, 2), + Unit: org.UnitPackage, + }, + Taxes: tax.Set{ + { + Category: "VAT", + Rate: "standard", + }, + }, + }, + }, + } +} + +func TestInvoiceValidation(t *testing.T) { + inv := validInvoice() + require.NoError(t, inv.Calculate()) + assert.NoError(t, inv.Validate()) + + inv = validInvoice() + inv.Supplier.TaxID.Code = "" + require.NoError(t, inv.Calculate()) + assert.Error(t, inv.Validate()) +} From 1ac0530b82a1209179a7901e26bbdd9d5e39041b Mon Sep 17 00:00:00 2001 From: Menendez6 Date: Tue, 19 Nov 2024 08:39:31 +0000 Subject: [PATCH 13/22] Correcting test --- regimes/ch/invoices_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/regimes/ch/invoices_test.go b/regimes/ch/invoices_test.go index 7a13aebb..7305a0e6 100644 --- a/regimes/ch/invoices_test.go +++ b/regimes/ch/invoices_test.go @@ -56,5 +56,5 @@ func TestInvoiceValidation(t *testing.T) { inv = validInvoice() inv.Supplier.TaxID.Code = "" require.NoError(t, inv.Calculate()) - assert.Error(t, inv.Validate()) + assert.NoError(t, inv.Validate()) } From e0d27f3058a8a41166c8c53f61c49674646c9209 Mon Sep 17 00:00:00 2001 From: Sam Lown Date: Tue, 19 Nov 2024 14:08:40 +0000 Subject: [PATCH 14/22] Release 0.205.1 --- CHANGELOG.md | 2 ++ version.go | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c9bcb805..ce2fae89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p ## [Unreleased] +## [v0.205.1] - 2024-11-19 + ### Added - `org`: `Address` includes `LineOne()`, `LineTwo()`, `CompleteNumber()` methods to help with conversion to other formats with some regional formatting. diff --git a/version.go b/version.go index f112a3e3..e118e41e 100644 --- a/version.go +++ b/version.go @@ -8,7 +8,7 @@ import ( type Version string // VERSION is the current version of the GOBL library. -const VERSION Version = "v0.205.0" +const VERSION Version = "v0.205.1" // Semver parses and returns semver func (v Version) Semver() *semver.Version { From b288e836f8f62e1a54f34a8ad09f1b1d96178157 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 20 Nov 2024 08:41:53 +0100 Subject: [PATCH 15/22] Arab Emirates regime adjustments --- regimes/ae/scenarios.go | 4 ++-- regimes/ae/scenarios_test.go | 4 ++-- regimes/ae/tax_identity.go | 2 +- regimes/ae/tax_identity_test.go | 8 ++++---- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/regimes/ae/scenarios.go b/regimes/ae/scenarios.go index 0fbbbb55..071fb240 100644 --- a/regimes/ae/scenarios.go +++ b/regimes/ae/scenarios.go @@ -16,7 +16,7 @@ var invoiceScenarios = &tax.ScenarioSet{ Note: &cbc.Note{ Key: cbc.NoteKeyLegal, Src: tax.TagReverseCharge, - Text: "Reverse Charge / التحويل العكسي", + Text: "Reverse Charge", }, }, // Simplified Tax Invoice @@ -25,7 +25,7 @@ var invoiceScenarios = &tax.ScenarioSet{ Note: &cbc.Note{ Key: cbc.NoteKeyLegal, Src: tax.TagSimplified, - Text: "Simplified Tax Invoice / فاتورة ضريبية مبسطة", + Text: "Simplified Tax Invoice", }, }, }, diff --git a/regimes/ae/scenarios_test.go b/regimes/ae/scenarios_test.go index 28db4cda..1d02870b 100644 --- a/regimes/ae/scenarios_test.go +++ b/regimes/ae/scenarios_test.go @@ -90,12 +90,12 @@ func TestInvoiceScenarios(t *testing.T) { require.NoError(t, i.Validate()) assert.Len(t, i.Notes, 1) assert.Equal(t, i.Notes[0].Src, tax.TagReverseCharge) - assert.Equal(t, i.Notes[0].Text, "Reverse Charge / التحويل العكسي") + assert.Equal(t, i.Notes[0].Text, "Reverse Charge") i = testInvoiceSimplified(t) require.NoError(t, i.Calculate()) require.NoError(t, i.Validate()) assert.Len(t, i.Notes, 1) assert.Equal(t, i.Notes[0].Src, tax.TagSimplified) - assert.Equal(t, i.Notes[0].Text, "Simplified Tax Invoice / فاتورة ضريبية مبسطة") + assert.Equal(t, i.Notes[0].Text, "Simplified Tax Invoice") } diff --git a/regimes/ae/tax_identity.go b/regimes/ae/tax_identity.go index 96183234..52580e58 100644 --- a/regimes/ae/tax_identity.go +++ b/regimes/ae/tax_identity.go @@ -32,7 +32,7 @@ func validateTRNCode(value interface{}) error { // Check if TRN matches the 15-digit pattern if !trnRegex.MatchString(val) { - return errors.New("invalid format: TRN must be a 15-digit number") + return errors.New("must be a 15-digit number") } return nil diff --git a/regimes/ae/tax_identity_test.go b/regimes/ae/tax_identity_test.go index 1730fc9a..fc305160 100644 --- a/regimes/ae/tax_identity_test.go +++ b/regimes/ae/tax_identity_test.go @@ -21,10 +21,10 @@ func TestValidateTaxIdentity(t *testing.T) { {name: "good 3", code: "100111222333444"}, // Invalid formats - {name: "too short", code: "12345678901234", err: "invalid format: TRN must be a 15-digit number"}, - {name: "too long", code: "1234567890123456", err: "invalid format: TRN must be a 15-digit number"}, - {name: "non-numeric", code: "12345678ABCD345", err: "invalid format: TRN must be a 15-digit number"}, - {name: "not normalized", code: "1234-5678-9012-345", err: "invalid format: TRN must be a 15-digit number"}, + {name: "too short", code: "12345678901234", err: "must be a 15-digit number"}, + {name: "too long", code: "1234567890123456", err: "must be a 15-digit number"}, + {name: "non-numeric", code: "12345678ABCD345", err: "must be a 15-digit number"}, + {name: "not normalized", code: "1234-5678-9012-345", err: "must be a 15-digit number"}, } for _, tt := range tests { From b4517df1d00019b6f62bb4453288bb9521c0d4a0 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 20 Nov 2024 08:42:36 +0100 Subject: [PATCH 16/22] add Arab Emirates tax regime --- regimes/regimes.go | 1 + 1 file changed, 1 insertion(+) diff --git a/regimes/regimes.go b/regimes/regimes.go index 8a3b49c4..9f6279ab 100644 --- a/regimes/regimes.go +++ b/regimes/regimes.go @@ -5,6 +5,7 @@ package regimes import ( // Import all the regime definitions which will automatically // add themselves to the tax regime register. + _ "github.com/invopop/gobl/regimes/ae" _ "github.com/invopop/gobl/regimes/at" _ "github.com/invopop/gobl/regimes/be" _ "github.com/invopop/gobl/regimes/br" From 099b01e76d7b3ecb0dcd8e03014c47e2db31dc16 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 20 Nov 2024 08:43:59 +0100 Subject: [PATCH 17/22] add Arab Emirates tax regime --- CHANGELOG.md | 58 +++++++++++++-------- examples/ae/invoice-ae-ae-stnr.yaml | 3 +- examples/ae/out/invoice-ae-ae-stnr.json | Bin 0 -> 3562 bytes examples/ae/out/invoice-ae-simplified.json | Bin 0 -> 3262 bytes 4 files changed, 36 insertions(+), 25 deletions(-) create mode 100644 examples/ae/out/invoice-ae-ae-stnr.json create mode 100644 examples/ae/out/invoice-ae-simplified.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 9cc7d816..fad9a04b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,27 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p ### Added +- ae: added UAE regime + +## [v0.205.1] - 2024-11-19 + +### Added + +- `org`: `Address` includes `LineOne()`, `LineTwo()`, `CompleteNumber()` methods to help with conversion to other formats with some regional formatting. + +### Changes + +- `bill`: `Invoice` can now have empty lines if discounts or charges present. + +### Fixes + +- `ch`: Deleted Supplier validation (not needed for under 2300 CHF/year) +- `bill`: `Invoice` `GetExtensions` method now works correctly if missing totals [Issue #424](https://github.com/invopop/gobl/issues/424). + +## [v0.205.0] - 2024-11-12 + +### Added + - `org`: `Address` now includes a `state` code, for countries that require them. - `es-tbai-v1`: normalize address information to automatically add new `es-tbai-region` extension to invoices. - `org`: `Inbox` now supports `email` field, with auto-normalization of URLs and emails in the `code` field. @@ -22,7 +43,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p - `cal`: Fixing json schema issue with date times. -## [v0.204.1] +## [v0.204.1] - 2024-11-04 ### Added @@ -32,7 +53,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p - `tax`: identity code handling will skip default validation for specific countries that use special characters. -## [v0.204.0] +## [v0.204.0] - 2024-10-31 ### Added @@ -71,7 +92,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p - `mx`: Tax ID validation now correctly supports `&` and `Ñ` symbols in codes. -## [v0.203.0] +## [v0.203.0] - 2024-20-21 ### Added @@ -85,7 +106,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p - `tax.Identity`: support Calculate method to normalize IDs. - `tax.Regime`: properly set regime when alternative codes is given. -## [v0.202.0] +## [v0.202.0] - 2024-10-14 ### Changed @@ -101,7 +122,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p - `mx`: fixed panic when normalizing an invoice with `tax` but no `ext` inside. -## [v0.201.0] +## [v0.201.0] - 2024-10-07 ### Fixed @@ -114,13 +135,13 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p - `pt`: reduced rate category for PT-MA was updated to reflect latest value of 4% - `co-dian-v2`: moved from `co` tax regime into own addon. -## [v0.200.1] +## [v0.200.1] - 2024-09-30 ### Fixed - `pt`: moving invoice tags from saft addon to regime, ensure defaults present. -## [v0.200.0] +## [v0.200.0] - 2024-09-26 Another ~~significant~~ epic release. Introducing "add-ons" which move the normalization and validation rules from Tax Regimes to specific packages that need to be enabled inside a document to be used. @@ -162,13 +183,13 @@ Finally, the `draft` flag has been removed from the header, and much more emphas - `org`: `DocumentRef` consolidates references to previous documents in a single place. - `bill`: invoice type option `other` for usage when regular scenarios do not apply. -## [v0.115.1] +## [v0.115.1] - 2024-09-10 ### Fixes - `tax`: totals calculator was ignoring tax combos with rate and percent, when they should be classed as exempt. -## [v0.115.0] +## [v0.115.0] - 2024-09-10 This one is big... @@ -221,20 +242,20 @@ Invoices in GOBL can now also finally produced for any country in the world, eve - `tax.Scenario`: potential issue around matching notes. - `tax.Set`: improved validation embedded error handling. -## [v0.114.0] +## [v0.114.0] - 2024-08-26 ### Changed - `org.Name`: either given **or** surname are required, as opposed to both at the same time. -## [v0.113.0] +## [v0.113.0] - 2024-08-01 ### Added - `head`: validation rule to check for the presence of stamps - GR: support for credit notes -## [v0.112.0] +## [v0.112.0] - 2024-07-29 Significant set of small changes related to renaming of the `l10n.CountryCode` type. The main reason for this is an attempt to reduce confusion between regular ISO country selection, and the specific country codes used for tax purposes. Normally they coincide, but exception cases like for Greece, whose ISO code is `GR` but use `EL` for tax purposes, or `XI` for companies in Northern Ireland, mean that there needs to be a clear selection. @@ -250,6 +271,8 @@ Significant set of small changes related to renaming of the `l10n.CountryCode` t - GR: support for simplified invoices - `l10n`: ISO and Tax lists of country definitions available, e.g. `l10n.Countries().ISO()` - `tax`: support for alternative country codes +- `tax`: Scenarios now handle extension key and value for filtering. +- PT: exemption text handling moved to scenarios. ### Upgraded @@ -259,13 +282,6 @@ Significant set of small changes related to renaming of the `l10n.CountryCode` t - GR: fixed certain tax combos not getting calculated by the regime -## [v0.112.0] - 2024-07-26 - -### Added - -- `tax`: Scenarios now handle extension key and value for filtering. -- PT: exemption text handling moved to scenarios. - ## [v0.111.1] - 2024-07-25 ### Added @@ -599,7 +615,3 @@ New number formatting support! Expect some possible breaking SDK changes with th ## [v0.70.1] - 2024-03-25 - Last version before CHANGELOG.md. - -### Added - -- ae: added UAE regime diff --git a/examples/ae/invoice-ae-ae-stnr.yaml b/examples/ae/invoice-ae-ae-stnr.yaml index dd274b2f..a0de1e97 100644 --- a/examples/ae/invoice-ae-ae-stnr.yaml +++ b/examples/ae/invoice-ae-ae-stnr.yaml @@ -44,5 +44,4 @@ lines: reason: "Special discount" taxes: - cat: VAT - rate: "5%" - reason: "Standard VAT rate in UAE" + rate: standard diff --git a/examples/ae/out/invoice-ae-ae-stnr.json b/examples/ae/out/invoice-ae-ae-stnr.json new file mode 100644 index 0000000000000000000000000000000000000000..a75b109cef77e71de3a402673ead5e708cce2ebf GIT binary patch literal 3562 zcmdT{O;6iE5S?o(^*=7f!8sHu7=s*p(xu?Qq}5=vC@*W13g+hJ#I z2N0L4vTSd>v-9!h&Ftp)ub=W#UPwdMcpZmnNPgnd6#bZeKQWUC2bv zWGowa7x=X)L%dTtht#^X@cT@1%#Sh3R_B=Y(1IF64B8+ z@52mnr0{!)XMv}cok6aEM;Xvi`Mz}Jqa4d&$@c-`9vZzZ*i9mBIke0WVJ4@}+T2LD zA>GDBMEx^fJHWaER|Zc9h(Ns|g1yKJElhEZo!`Y@NzFR*evL?N7LK8Jg1iy=3>-Lu z1mB0a-WlCoORR3;f5=kM%z2`)!1_6K#$Eto@rkg zVWj;g?$`9vSE$M+Zm+J_y#{V_&kQ}TiZT6-ai9%$?i4H85!6y27-Z0uV}|um)?JQ^ zdbpx+C=of;)REU1$Cy!?9MauSWi4(26Wa?LVtX?6_+C}VsQPHRzXb2T;C%AA)<2%w z?3rWr$%l4$J&eH9iwz(x_V}eqtQ(FbXV8s)vKEM^ZQEf$z3T)MefU3+R~FmPNjwSZ7Ji~ zlL@XN_O@jeS62pdEPYwQIKtbibTGE$1X9aV!~3zM*zaSO%9)%)E`{C%x*ix?Bdm0> zbLw`ytsQxqk(xkr7q-1O2RG;G?bw|i!cGd?DYSI3>SH-HmZmbrF892Rb!bd*Z^F-p zY{2J+Ji`7KW)HEJ;9kZ`4Pyth6YQMG8lE*L)xf-kxs7Hbmq3USdK+V4(L(GUJV$t1 z*)ils&R>nHB2C$m=dv$*nfTklu4DApV7DG=OM$}}5hilvtWAvc8l=~7F)RKVpKasZ z2v-76+lWBDXBKuND~#siX8%gA4Z5^F#KkNyF0Vs5#XOzitj+O0qy}IGbCQ5zFCf8j z57!f8m1n)(Ba^?Nnft_Nit`ibjQtOYmmIgVNY;VKI`T~hY$CVJx2|nKv*%IC#7cWD z_)NAZ4s||#yn&Y`u^#&Pr-)y%t;1gI8)e8BYE3YzWYbk*DsM5CJqgdRlNMiwrsDue|>M;jM@fGo6PzWR$+Ius7TWH^o|G(5}Ls z(v`>1c7cda9rqG+BUW|DTkM7R$MTj4k#~7dZ2l?^G1hebALl)v-T_fo_#}fgYc9r( zI#`v;0juBhBd(8K$e!b7E{CpiXr&BKSzoLh_EP$--IN|t(-8aGX?-6et5;k$^U|yv zdZzgHFw?xnyr*u~xkgX{8tjI}x+`ENyHemv9%K3&<3Jm%fEG>;;F)^WL{tfMrP%2M zg{ccC~DE2RMJd2Fhd_aU~=xJp@$a$VovEGm+ zt7o1Uu{6I`AqT-GQSpfL&x3z2{AQ_d2ZsGc?9BGb#n~#>T*L&fRnX}Z)3X=n*q>rw zXKz@HjdEiVI|7>aL{XJ&p%Y}8pl6g3`W~2}E5(&JPp8!v>&kMeNW-=2Z1WSJ$bXy8T-_8gGv;#47ux8;8f!Ip z!Ca4BH`Hj0|C{3z>@^pNRL?XKz5mjNf9*1Q6)UYQ&(LSXUw#??{bgCI{*sw|#{CiV upBO(>XYvU Date: Wed, 20 Nov 2024 08:46:13 +0100 Subject: [PATCH 18/22] add Arab Emirates tax regime --- data/regimes/ae.json | 172 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 data/regimes/ae.json diff --git a/data/regimes/ae.json b/data/regimes/ae.json new file mode 100644 index 00000000..c8af2bc0 --- /dev/null +++ b/data/regimes/ae.json @@ -0,0 +1,172 @@ +{ + "$schema": "https://gobl.org/draft-0/tax/regime-def", + "name": { + "ar": "الإمارات العربية المتحدة", + "en": "United Arab Emirates" + }, + "time_zone": "Asia/Dubai", + "country": "AE", + "currency": "AED", + "tags": [ + { + "schema": "bill/invoice", + "list": [ + { + "key": "simplified", + "name": { + "de": "Vereinfachte Rechnung", + "en": "Simplified Invoice", + "es": "Factura Simplificada", + "it": "Fattura Semplificata" + }, + "desc": { + "de": "Wird für B2C-Transaktionen verwendet, wenn die Kundendaten nicht verfügbar sind. Bitte wenden Sie sich an die örtlichen Behörden, um die Grenzwerte zu ermitteln.", + "en": "Used for B2C transactions when the client details are not available, check with local authorities for limits.", + "es": "Usado para transacciones B2C cuando los detalles del cliente no están disponibles, consulte con las autoridades locales para los límites.", + "it": "Utilizzato per le transazioni B2C quando i dettagli del cliente non sono disponibili, controllare con le autorità locali per i limiti." + } + }, + { + "key": "reverse-charge", + "name": { + "de": "Umkehr der Steuerschuld", + "en": "Reverse Charge", + "es": "Inversión del Sujeto Pasivo", + "it": "Inversione del soggetto passivo" + } + }, + { + "key": "self-billed", + "name": { + "de": "Rechnung durch den Leistungsempfänger", + "en": "Self-billed", + "es": "Facturación por el destinatario", + "it": "Autofattura" + } + }, + { + "key": "customer-rates", + "name": { + "de": "Kundensätze", + "en": "Customer rates", + "es": "Tarifas aplicables al destinatario", + "it": "Aliquote applicabili al destinatario" + } + }, + { + "key": "partial", + "name": { + "de": "Teilweise", + "en": "Partial", + "es": "Parcial", + "it": "Parziale" + } + } + ] + } + ], + "scenarios": [ + { + "schema": "bill/invoice", + "list": [ + { + "tags": [ + "reverse-charge" + ], + "note": { + "key": "legal", + "src": "reverse-charge", + "text": "Reverse Charge" + } + }, + { + "tags": [ + "simplified" + ], + "note": { + "key": "legal", + "src": "simplified", + "text": "Simplified Tax Invoice" + } + } + ] + } + ], + "corrections": [ + { + "schema": "bill/invoice", + "types": [ + "credit-note" + ] + } + ], + "categories": [ + { + "code": "VAT", + "name": { + "ar": "ضريبة القيمة المضافة", + "en": "VAT" + }, + "title": { + "ar": "ضريبة القيمة المضافة", + "en": "Value Added Tax" + }, + "rates": [ + { + "key": "zero", + "name": { + "ar": "معدل صفر", + "en": "Zero Rate" + }, + "desc": { + "ar": "نسبة ضريبة قيمة مضافة 0٪ تطبق على الصادرات المحددة والمناطق المعينة والخدمات الأساسية.", + "en": "A VAT rate of 0% applicable to specific exports, designated areas, and essential services." + }, + "values": [ + { + "percent": "0.0%" + } + ] + }, + { + "key": "standard", + "name": { + "ar": "معدل قياسي", + "en": "Standard Rate" + }, + "desc": { + "ar": "ينطبق على معظم السلع والخدمات ما لم ينص على خلاف ذلك.", + "en": "Applies to most goods and services unless specified otherwise." + }, + "values": [ + { + "since": "2018-01-01", + "percent": "5%" + } + ] + }, + { + "key": "exempt", + "name": { + "ar": "معفى", + "en": "Exempt" + }, + "desc": { + "ar": "بعض السلع والخدمات، مثل الخدمات المالية والعقارات السكنية، معفاة من ضريبة القيمة المضافة.", + "en": "Certain goods and services, such as financial services and residential real estate, are exempt from VAT." + }, + "exempt": true + } + ], + "sources": [ + { + "title": { + "ar": "الهيئة الاتحادية للضرائب", + "en": "Federal Tax Authority - UAE VAT Regulations" + }, + "url": "https://www.tax.gov.ae" + } + ] + } + ] +} \ No newline at end of file From 940b2aa7247c8cb8b8836c9e3ac5c313b982b18e Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 20 Nov 2024 09:08:40 +0100 Subject: [PATCH 19/22] add Arab Emirates tax regime --- examples/ae/out/invoice-ae-simplified.json | Bin 3262 -> 3184 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/examples/ae/out/invoice-ae-simplified.json b/examples/ae/out/invoice-ae-simplified.json index 9924226cff33901b000113cfc5083ba72a91b28b..cc875719af07b55e32b228dd520957de0ab9184a 100644 GIT binary patch delta 11 Scmdld`9Wes5YOZVJaPaXxdcl9 delta 89 zcmew$u}^YC5Rb0{gFb@-gM{iIhVu-&fOrj%{R&7=Qa#VG4k&Y;;SiW7q51+y?+40) YXpoGA>I1OIc?Kq++5;fb$$>ob0QdwPc>n+a From e049a8833c64ca1a972bc1d81dc3cc819ae5eb5e Mon Sep 17 00:00:00 2001 From: Sam Lown Date: Thu, 21 Nov 2024 09:57:03 +0000 Subject: [PATCH 20/22] Fixing changelog --- CHANGELOG.md | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 57bb0793..fad9a04b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,23 +6,6 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p ## [Unreleased] -## [v0.205.1] - 2024-11-19 - -### Added - -- `org`: `Address` includes `LineOne()`, `LineTwo()`, `CompleteNumber()` methods to help with conversion to other formats with some regional formatting. - -### Changes - -- `bill`: `Invoice` can now have empty lines if discounts or charges present. - -### Fixes - -- `ch`: Deleted Supplier validation (not needed for under 2300 CHF/year) -- `bill`: `Invoice` `GetExtensions` method now works correctly if missing totals [Issue #424](https://github.com/invopop/gobl/issues/424). - -## [v0.205.0] - 2024-11-12 - ### Added - ae: added UAE regime From b8a4fe3f18647bc0876498371be1bc0e419bc82f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luismi=20Cavall=C3=A9?= Date: Thu, 21 Nov 2024 16:11:22 +0000 Subject: [PATCH 21/22] Fix bad example out --- examples/ae/out/invoice-ae-ae-stnr.json | Bin 3562 -> 2016 bytes examples/ae/out/invoice-ae-simplified.json | Bin 3184 -> 1827 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/examples/ae/out/invoice-ae-ae-stnr.json b/examples/ae/out/invoice-ae-ae-stnr.json index a75b109cef77e71de3a402673ead5e708cce2ebf..7412c118dd5d5f096c08a16081229cf7418a6011 100644 GIT binary patch literal 2016 zcmcIk!H(ND5It+ZLLkyxwWhR|Eawy>n?r#O8f1$e8U%}>IJSt$k}FDXj9~x0GbCks zlSR-Xh!2J^GaSD6=5fAUMdF36YTSc(8H;)t4)*0@vFlWG-SxYL?qNG5a)Ip;o9=+( zS2|E5XsZ5l6-8nk4S$Og@^q_jWRgL)O41ZDQJ5Er%$6BcX(px2_;4h&*@eTAfQG+? zts%<`myW~{8jh)KT>#(`wiW0cGPzvgO}f%~n&+u3${`T82{d4e0TTSRYzig&kt^v zt~e4^H?~6`TKXCedR%FCv&@TCxt9D@u-Hy&x&^fE(OSpp1IHuZknSJ%3@{Cordm7n zh=U_vqxn?Ff58cL{JtZDv03ORny!Mz3=YWcsKA^v6BzQA^cr*hQ{1iz`5=lux`u+B zZW`A6DK%v7zXa(GK1I8Ex;~vG{e@+L81A(jU+^ z|6kf>`B|M=+f5U{h5iI}{F`mad*_l%X*X(&Z);FLS6-+7EBjq-N&neA!HkAxlX^#p zzei|?V2Es*yD$T?yUtz%-=FvV6GZR{);9ryKlEPFJa;&PDtn91t;Zn&~_3 z#UxXx4G-Y!79kuMgeenn+;w3ee(+GjK7f!8sHu7=s*p(xu?Qq}5=vC@*W13g+hJ#I z2N0L4vTSd>v-9!h&Ftp)ub=W#UPwdMcpZmnNPgnd6#bZeKQWUC2bv zWGowa7x=X)L%dTtht#^X@cT@1%#Sh3R_B=Y(1IF64B8+ z@52mnr0{!)XMv}cok6aEM;Xvi`Mz}Jqa4d&$@c-`9vZzZ*i9mBIke0WVJ4@}+T2LD zA>GDBMEx^fJHWaER|Zc9h(Ns|g1yKJElhEZo!`Y@NzFR*evL?N7LK8Jg1iy=3>-Lu z1mB0a-WlCoORR3;f5=kM%z2`)!1_6K#$Eto@rkg zVWj;g?$`9vSE$M+Zm+J_y#{V_&kQ}TiZT6-ai9%$?i4H85!6y27-Z0uV}|um)?JQ^ zdbpx+C=of;)REU1$Cy!?9MauSWi4(26Wa?LVtX?6_+C}VsQPHRzXb2T;C%AA)<2%w z?3rWr$%l4$J&eH9l}Sns8P-^&s|44Z zOP)xFksVFq2(`;Ep5{*ZF}r&C4@<$9=CRnjO+FfPveb)?LdJq)F)Dd-5(lT zTQuDFs}P-}xdDWANm^tZQ!G{`mWz!p&7v;qw9ux=QqcZR_e2V_9u8Uqz46fjdyCvk zJ>35F;Xd@QIwQ4OC%()M!-$UtoP<-IrX>Di%2s3FV_Q9W3U{F&oap~&)`%#K2cm#I zM}$)gp7DpiJ6ePB-z~=X_rJ^0NI5t?%IKvyQdKv$t`GP78V-7pNqV!&@^!J%{FSpt zG8t+M#`HK0LQu4f{DqNf$33G>f-xAP>k%CXA9iTJ?c&eygeLx{BR?_+o<6x`UcKaSrKnB2TK;g#Dqbul- z*OqE#f3u1dfXm{dSJS zo;@PrLi&l?UMs&dG!OFCELAi&u0T$Hy8Sp`*ax$}+7}Z36@=`q=lp%2&iRKX$=zGDUQRwZO@(g%d8PP#s+v@quM+pmOj#x$ztHaS zKkIgnG;tAetp$S5AK(ef#`6gY!sfTB?c8jUpmNZq!7WYI6I4Ub&;I4x91;#sVy1`l Ws~vxYQ~YOm@>v9N0{whkz5Ea5V40u* literal 3184 zcma)8+iuf95S`Z`@eff1Z?sKX(v&_?Q67+rS`owpRTbspBo?`mOKDa0*MW0(JM65z zA(dr&?VanHGh_ex{U9&pg;b;_uVf{C8A~Qp>EiiRs<@72jbnkmQ#rsflDYI`BKx>6 z@pK>q+`BS_)SlGw+>;#p6RdKX$t~n^*v(+;jq$a_n-O-VZpZsNlaB?dH7t+d+edT# zq|azqPIm!6Ieh1^GQeA(%Z>50kqvga=Y1T(VurJYIGb{en49tr`zKhv#k&mW9^TY( zA7C}a&QK0mp{dJ#(n@>hle}tcZ=!=f6Sz zifse_;@BudzR+rhyGpiQC+6}4_o{q{?|bYIu>OLq8A;jB1Ur4~u^Qy0+ByeHJ-p8o zIz?p2F@Kcp{||3PjF{;<^dYnKbA_|vIk+j-nuAUi&Xke7hqXIoGA;$ zJ|4?kB1GPmBeC_XImFo0jenf?eEJDQ+2QK~((JhyH`-uVDhI56&yTo2&LMk?leJv9 z&Y_noB4vNEZ#YXCw@y<=L`w_o>!kH_h^*ds)htW1Zy1^4JH|@u7W1CA+2@);4QOy0 zrft{2Oirc1l`_VRH^zZJ*a2N=&JmeL)kansY~|RQ0EMFMVqP8xB1M#loLXui-q(1F zQKLNZ*EtfTnW17AaIw6qh~+me`FWLMRXSt#YKZU~z5-v95%T+LC;RsAi|4|26q*i$ z=jStbIZY6iXB6ibIbK9Ydp;mSG*nv|5Ncj#RBSg?$=cZ#MJ&y4Rj5JmNmM-I{PW=7 z3%^+!+qGeTik-zdxw~3v%PA)CT@8~yGc$X4jlCA{Z_=H&&8D>%+bVJ?W#Q5Ez4oahX5~ZfmUkWbkCT7Pwmz{&`P(#eeUoBl%;lIb^f7`r_G*ZNwO+YtsM)6fn_~^mnmc5wI&Ea{ ezqH|ByNX%GPOHi%%vt-FuhT^ Date: Thu, 21 Nov 2024 17:18:05 +0100 Subject: [PATCH 22/22] Fix wrongly merged CHANGELOG --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 91af5ed2..c2aeea31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,14 +8,14 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p ### Added -- ae: added UAE regime +- `ae`: added UAE regime +- `br`: supplier extensions, validations & identities ## [v0.205.1] - 2024-11-19 ### Added - `org`: `Address` includes `LineOne()`, `LineTwo()`, `CompleteNumber()` methods to help with conversion to other formats with some regional formatting. -- `br`: supplier extensions, validations & identities ### Changes