From 9f314c4881575e434a5f87a05ee3b199b43c443e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luismi=20Cavall=C3=A9?= Date: Fri, 13 Dec 2024 12:18:31 +0000 Subject: [PATCH] Add support for other taxes to myDATA Greece --- CHANGELOG.md | 1 + addons/gr/mydata/extensions.go | 145 ++++++++++++++++++++- addons/gr/mydata/invoices.go | 25 ++-- addons/gr/mydata/invoices_test.go | 15 +++ examples/gr/invoice-accommodation.yaml | 49 +++++++ examples/gr/out/invoice-accommodation.json | 96 ++++++++++++++ regimes/gr/README.md | 47 ++++++- 7 files changed, 361 insertions(+), 17 deletions(-) create mode 100644 examples/gr/invoice-accommodation.yaml create mode 100644 examples/gr/out/invoice-accommodation.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e6dcf57..c87e1288 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p - `bill`: `Tax.MergeExtensions` convenience method for adding extensions to tax objects and avoid nil panics. - `cbc`: `Key.Pop` method for splitting keys with sub-keys, e.g. `cbc.Key("a+b").Pop() == cbc.Key("a")`. - `in`: added Indian regime +- `gr-mydata`: added `gr-mydata-other-tax` extension to set the category of other taxes in charges. ### Changed diff --git a/addons/gr/mydata/extensions.go b/addons/gr/mydata/extensions.go index 7d4233a7..2f038f0a 100644 --- a/addons/gr/mydata/extensions.go +++ b/addons/gr/mydata/extensions.go @@ -13,8 +13,7 @@ const ( ExtKeyIncomeCat = "gr-mydata-income-cat" ExtKeyIncomeType = "gr-mydata-income-type" ExtKeyPaymentMeans = "gr-mydata-payment-means" - - InvoiceTypeRetailPrefix = "11." + ExtKeyOtherTax = "gr-mydata-other-tax" ) var extensions = []*cbc.Definition{ @@ -1024,4 +1023,146 @@ var extensions = []*cbc.Definition{ }, }, }, + { + Key: ExtKeyOtherTax, + Name: i18n.String{ + i18n.EN: "Other taxes category", + i18n.EL: "Κατηγορία Λοιπών Φόρων", + }, + Values: []*cbc.Definition{ + { + Code: "1", + Name: i18n.String{ + i18n.EN: "a1) 20% fire insurance premiums", + i18n.EL: "α1) Ασφάλιστρα κλάδου πυρός 20%", + }, + }, + { + Code: "2", + Name: i18n.String{ + i18n.EN: "a2) 20% fire insurance premiums", + i18n.EL: "α2) Ασφάλιστρα κλάδου πυρός 20%", + }, + }, + { + Code: "3", + Name: i18n.String{ + i18n.EN: "b) 4% life insurance premiums", + i18n.EL: "β) Ασφάλιστρα κλάδου ζωής 4%", + }, + }, + { + Code: "4", + Name: i18n.String{ + i18n.EN: "c) 15% other insurance premiums", + i18n.EL: "γ) Ασφάλιστρα λοιπών κλάδων 15%", + }, + }, + { + Code: "5", + Name: i18n.String{ + i18n.EN: "d) 0% tax-exempt insurance premiums", + i18n.EL: "δ) Απαλλασσόμενα φόρου ασφάλιστρα 0%", + }, + }, + { + Code: "6", + Name: i18n.String{ + i18n.EN: "Hotels 1-2 stars 0,50 €", + i18n.EL: "Ξενοδοχεία 1-2 αστέρων 0,50 €", + }, + }, + { + Code: "7", + Name: i18n.String{ + i18n.EN: "Hotels 3 stars 1,50 €", + i18n.EL: "Ξενοδοχεία 3 αστέρων 1,50 €", + }, + }, + { + Code: "8", + Name: i18n.String{ + i18n.EN: "Hotels 4 stars 3,00 €", + i18n.EL: "Ξενοδοχεία 4 αστέρων 3,00 €", + }, + }, + { + Code: "9", + Name: i18n.String{ + i18n.EN: "Hotels 5 stars 4,00 €", + i18n.EL: "Ξενοδοχεία 5 αστέρων 4,00 €", + }, + }, + { + Code: "10", + Name: i18n.String{ + i18n.EN: "Rental rooms - Furnished rooms - Apartments 0,50 €", + i18n.EL: "Ενοικιαζόμενα δωμάτια - Επιπλωμένα δωμάτια - Διαμερίσματα 0,50 €", + }, + }, + { + Code: "11", + Name: i18n.String{ + i18n.EN: "Special 5% tax on tv-broadcast commercials (EFTD)", + i18n.EL: "Ειδικός φόρος στις διαφημίσεις που προβάλλονται από την τηλεόραση (ΕΦΔΤ) 5%", + }, + }, + { + Code: "12", + Name: i18n.String{ + i18n.EN: "10% luxury tax on the taxable value of intra-community acquired goods and those imported from third countries", + i18n.EL: "Φόρος πολυτελείας 10% επί της φορολογητέας αξίας για τα ενδοκοινοτικά αποκτήματα και εισαγόμενα από τρίτες χώρες", + }, + }, + { + Code: "13", + Name: i18n.String{ + i18n.EN: "10% luxury tax on the selling price before VAT for domestically produced goods", + i18n.EL: "Φόρος πολυτελείας 10% επί της τιμής πώλησης προ Φ.Π.Α. για τα εγχωρίως παραγόμενα", + }, + }, + { + Code: "14", + Name: i18n.String{ + i18n.EN: "80% Public fees on the admission ticket price for casinos", + i18n.EL: "Δικαιώματα του Δημοσίου στα εισιτήρια των καζίνο (80% επί του εισιτηρίου)", + }, + }, + { + Code: "15", + Name: i18n.String{ + i18n.EN: "Fire industry insurance premiums 20%", + i18n.EL: "Ασφάλιστρα κλάδου πυρός 20%", + }, + }, + { + Code: "16", + Name: i18n.String{ + i18n.EN: "Customs duties- Taxes", + i18n.EL: "Λοιποί Τελωνειακοί Δασμοί-Φόροι", + }, + }, + { + Code: "17", + Name: i18n.String{ + i18n.EN: "Other Taxes", + i18n.EL: "Λοιποί Φόροι", + }, + }, + { + Code: "18", + Name: i18n.String{ + i18n.EN: "Charges of other Taxes", + i18n.EL: "Επιβαρύνσεις Λοιπών Φόρων", + }, + }, + { + Code: "19", + Name: i18n.String{ + i18n.EN: "Special consumption tax", + i18n.EL: "ΕΦΚ", + }, + }, + }, + }, } diff --git a/addons/gr/mydata/invoices.go b/addons/gr/mydata/invoices.go index e3209664..2187bd9d 100644 --- a/addons/gr/mydata/invoices.go +++ b/addons/gr/mydata/invoices.go @@ -26,7 +26,7 @@ func validateInvoice(inv *bill.Invoice) error { ), validation.Field(&inv.Customer, validation.When( - !IsRetail(inv), + requiresValidCustomer(inv), validation.Required, validation.By(validateBusinessParty), validation.By(validateBusinessCustomer), @@ -40,10 +40,6 @@ func validateInvoice(inv *bill.Invoice) error { ), validation.Skip, ), - validation.Field(&inv.Charges, - validation.Empty.Error("not supported by mydata"), - validation.Skip, - ), validation.Field(&inv.Discounts, validation.Empty.Error("not supported by mydata"), validation.Skip, @@ -182,13 +178,18 @@ func validateInvoicePreceding(value any) error { ) } -// IsRetail returns true if the invoice type corresponds to a retail invoice. -func IsRetail(inv *bill.Invoice) bool { - if inv.Tax == nil || inv.Tax.Ext == nil { - return false - } +// requiresValidCustomer returns true if the invoice type requires a customer. +func requiresValidCustomer(inv *bill.Invoice) bool { + // Invoice type categories that require a valid customer. + typeCats := []string{"1", "2", "5"} + + it := inv.Tax.Ext[ExtKeyInvoiceType].String() - it := inv.Tax.Ext[ExtKeyInvoiceType] + for _, prefix := range typeCats { + if strings.HasPrefix(it, prefix+".") { + return true + } + } - return strings.HasPrefix(string(it), InvoiceTypeRetailPrefix) + return false } diff --git a/addons/gr/mydata/invoices_test.go b/addons/gr/mydata/invoices_test.go index 9a7c2986..6ce96df5 100644 --- a/addons/gr/mydata/invoices_test.go +++ b/addons/gr/mydata/invoices_test.go @@ -102,6 +102,21 @@ func TestSimplifiedInvoiceValidation(t *testing.T) { assert.NoError(t, inv.Validate()) } +func TestOtherInvoiceTypeValidation(t *testing.T) { + inv := validInvoice() + inv.Type = bill.InvoiceTypeOther + inv.Tax = &bill.Tax{ + Ext: tax.Extensions{ + mydata.ExtKeyInvoiceType: "8.2", + }, + } + inv.Customer.TaxID = nil + inv.Customer.Addresses = nil + + require.NoError(t, inv.Calculate()) + assert.NoError(t, inv.Validate()) +} + func TestPrecedingValidation(t *testing.T) { inv := validInvoice() diff --git a/examples/gr/invoice-accommodation.yaml b/examples/gr/invoice-accommodation.yaml new file mode 100644 index 00000000..34798250 --- /dev/null +++ b/examples/gr/invoice-accommodation.yaml @@ -0,0 +1,49 @@ +$schema: "https://gobl.org/draft-0/bill/invoice" +$addons: ["gr-mydata-v1"] +uuid: "019035bd-4522-7eb3-83bf-9185ead05ee6" +currency: "EUR" +issue_date: "2022-02-01" +series: "SAMPLE" +code: "001" +type: "other" +tax: + ext: + "gr-mydata-invoice-type": "8.2" + +supplier: + tax_id: + country: "EL" + code: "177472438" + name: "Ελληνικά Τρόφιμα Α.Ε." + emails: + - addr: "hellenicfoods@example.com" + addresses: + - num: "12" + street: "Λεωφόρος Βουλιαγμένης" + locality: "Αθήνα" + code: "11636" + country: "GR" + +customer: + tax_id: + country: "EL" + code: "841442160" + name: "Αιγαίο Λιανική Α.Ε." + emails: + - addr: "aegeanretail@example.com" + addresses: + - num: "45" + street: "Οδός Εγνατίας" + locality: "Θεσσαλονίκη" + code: "54625" + country: "GR" + +charges: + - reason: "Accommodation tax" + amount: "3.00" + ext: + "gr-mydata-other-tax": "8" + +payment: + instructions: + key: cash diff --git a/examples/gr/out/invoice-accommodation.json b/examples/gr/out/invoice-accommodation.json new file mode 100644 index 00000000..1368f170 --- /dev/null +++ b/examples/gr/out/invoice-accommodation.json @@ -0,0 +1,96 @@ +{ + "$schema": "https://gobl.org/draft-0/envelope", + "head": { + "uuid": "8a51fd30-2a27-11ee-be56-0242ac120002", + "dig": { + "alg": "sha256", + "val": "66ba51da8b4d361f1df79c094057ac3eade2f70680699a424531658c15c89631" + } + }, + "doc": { + "$schema": "https://gobl.org/draft-0/bill/invoice", + "$regime": "EL", + "$addons": [ + "gr-mydata-v1" + ], + "uuid": "019035bd-4522-7eb3-83bf-9185ead05ee6", + "type": "other", + "series": "SAMPLE", + "code": "001", + "issue_date": "2022-02-01", + "currency": "EUR", + "tax": { + "ext": { + "gr-mydata-invoice-type": "8.2" + } + }, + "supplier": { + "name": "Ελληνικά Τρόφιμα Α.Ε.", + "tax_id": { + "country": "EL", + "code": "177472438" + }, + "addresses": [ + { + "num": "12", + "street": "Λεωφόρος Βουλιαγμένης", + "locality": "Αθήνα", + "code": "11636", + "country": "GR" + } + ], + "emails": [ + { + "addr": "hellenicfoods@example.com" + } + ] + }, + "customer": { + "name": "Αιγαίο Λιανική Α.Ε.", + "tax_id": { + "country": "EL", + "code": "841442160" + }, + "addresses": [ + { + "num": "45", + "street": "Οδός Εγνατίας", + "locality": "Θεσσαλονίκη", + "code": "54625", + "country": "GR" + } + ], + "emails": [ + { + "addr": "aegeanretail@example.com" + } + ] + }, + "charges": [ + { + "i": 1, + "reason": "Accommodation tax", + "amount": "3.00", + "ext": { + "gr-mydata-other-tax": "8" + } + } + ], + "payment": { + "instructions": { + "key": "cash", + "ext": { + "gr-mydata-payment-means": "3" + } + } + }, + "totals": { + "sum": "0.00", + "charge": "3.00", + "total": "3.00", + "tax": "0.00", + "total_with_tax": "3.00", + "payable": "3.00" + } + } +} \ No newline at end of file diff --git a/regimes/gr/README.md b/regimes/gr/README.md index 931e3818..7655f122 100644 --- a/regimes/gr/README.md +++ b/regimes/gr/README.md @@ -14,7 +14,7 @@ Find example GR GOBL files in the [`examples`](../../examples/gr) (uncalculated ### Invoice Type -The Greek tax authority (IAPR) requires the invoice type to be specified as part of the invoice. In GOBL, this type can be set using the `gr-mydata-invoice-type` extension in the tax section. +The Greek tax authority (IAPR) requires the invoice type to be specified as part of the invoice. In GOBL, this type can be set using the `gr-mydata-invoice-type` extension in the tax section and setting the GOBL invoice type to `other`. Alternatively, GOBL will set the extension for you based on the type and the tax tags you set in your GOBL invoice. The table below shows how this mapping is done: @@ -40,9 +40,10 @@ For example, this is how you set the IAPR invoice type explicitly: { "$schema": "https://gobl.org/draft-0/bill/invoice", // ... + "type": "other", "tax": { "ext": { - "gr-mydata-invoice-type": "2.1" + "gr-mydata-invoice-type": "2.3" } } } @@ -56,7 +57,7 @@ And this is how you'll get the same result by using the GOBL type and tags: // ... "type": "standard", "tax": { - "tags": ["services"] + "tags": ["services", "export"] } } ``` @@ -275,3 +276,43 @@ For example: } ] ``` + +### Other Taxes + +Certain myDATA invoice types (_e.g._, 8.2 for the accommodation tax) require a category for other taxes to be provided. In GOBL, you can use the `gr-mydata-other-tax` extension at charge level with any of values in the table below: + +| Value | Description | +| ----- | ------------------------------------------------------------------------------------------------------------- | +| 1 | a1) 20% fire insurance premiums | +| 2 | a2) 20% fire insurance premiums | +| 3 | b) 4% life insurance premiums | +| 4 | c) 15% other insurance premiums | +| 5 | d) 0% tax-exempt insurance premiums | +| 6 | Hotels 1-2 stars 0,50 € | +| 7 | Hotels 3 stars 1,50 € | +| 8 | Hotels 4 stars 3,00 € | +| 9 | Hotels 5 stars 4,00 € | +| 10 | Rental rooms - Furnished rooms - Apartments 0,50 € | +| 11 | Special 5% tax on tv-broadcast commercials (EFTD) | +| 12 | 10% luxury tax on the taxable value of intra-community acquired goods and those imported from third countries | +| 13 | 10% luxury tax on the selling price before VAT for domestically produced goods | +| 14 | 80% Public fees on the admission ticket price for casinos | +| 15 | Fire industry insurance premiums 20% | +| 16 | Customs duties- Taxes | +| 17 | Other Taxes | +| 18 | Charges of other Taxes | +| 19 | Special consumption tax | + +For example: + +```js +"charges": [ + { + "amount": "3.00", + "reason": "Accommodation tax", + "ext": { + "gr-mydata-other-tax": "8", + } + } +] +```