Add Slovenia (SI) tax regime#893
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #893 +/- ##
========================================
Coverage 94.73% 94.73%
========================================
Files 316 320 +4
Lines 17198 17337 +139
========================================
+ Hits 16292 16425 +133
- Misses 560 563 +3
- Partials 346 349 +3 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
# Conflicts: # CHANGELOG.md
samlown
left a comment
There was a problem hiding this comment.
Thanks! A few style changes for the rule handling required.
| is.InContext(tax.RegimeIn(CountryCode)), | ||
| rules.Field("supplier", | ||
| rules.Assert("01", "invoice SI supplier must have a tax ID code", | ||
| is.Func("has tax ID code", hasSupplierTaxIDCode), |
There was a problem hiding this comment.
Its better to use direct rule checks here instead of a helper.
There was a problem hiding this comment.
Done — replaced the helper with direct nested field rules using is.Present on tax_id and code.
| }, | ||
| Sources: []*cbc.Source{ | ||
| { | ||
| Title: i18n.NewString("Uredba o vodenju in vzdrževanju Poslovnega registra Slovenije"), |
There was a problem hiding this comment.
Missing English translation.
There was a problem hiding this comment.
Fixed, the source title now carries both English and Slovenian.
| // IdentityTypeMaticna represents the Slovenian registration number | ||
| // (matična številka) assigned by AJPES to every entity entered in | ||
| // the Slovenian Business Register (e.g. "5043611000"). | ||
| IdentityTypeMaticna cbc.Code = "MATICNA" |
There was a problem hiding this comment.
Best to avoid local names in variables, there is likely to be a good translation. I'd also question the MATICNA code itself, it looks like a name that's been capitalized as opposed to a name, I'd expect there to be some type of abbreviated version.
There was a problem hiding this comment.
I changed the code from the spelled-out word to MŠ, the official Slovenian abbreviation for matična številka. f you'd rather avoid the non-ASCII character, I'm happy to use an ASCII form (MS).
| org.IdentityTypeIn(IdentityTypeMaticna), | ||
| rules.Field("code", | ||
| rules.Assert("01", "identity code for type MATICNA must be valid", | ||
| is.Func("valid", isValidRegistrationNumber), |
There was a problem hiding this comment.
Checks here could be split up to help users differentiate between length and check digit.
There was a problem hiding this comment.
Done, split into three separate rules: presence, format and the check digit. Each failure now reports its own code and message.
| func validateTaxCode(code cbc.Code) error { | ||
| if code == "" { | ||
| return nil | ||
| } | ||
| if len(code) != taxCodeLen { | ||
| return errors.New("invalid length") | ||
| } | ||
| if code[0] == '0' { | ||
| return errors.New("invalid format") | ||
| } | ||
| return validateMod11(code, taxCodeMultipliers) | ||
| } |
There was a problem hiding this comment.
These errors will never reach anywhere, so the checks should probably be split into separate rules.
There was a problem hiding this comment.
Done, removed validateTaxCode and split it into two rules: a format check (01) using a regex that covers length, digits and the non-zero first digit, and the check digit (02).
| // assigned (no valid number produces it), a remainder of 1 means a check | ||
| // digit of 0, and any other remainder means a check digit of 11 minus the | ||
| // remainder. | ||
| func validateMod11(code cbc.Code, weights []int) error { |
There was a problem hiding this comment.
Again, for a private method, the errors returned are not useful.
There was a problem hiding this comment.
Done, validMod11 now returns a bool rather than building error values that were discarded; the user-facing message lives in the check-digit rule.
|
Thanks for the thorough review, Sam — really helpful. I've addressed all the points: split the identifier validation into separate format/check-digit rules with distinct codes, switched the invoice rule to direct field checks, and cleaned up the naming and translations. |
This PR adds Slovenia (SI) as a new tax regime.
I picked Slovenia because it wasn't supported yet and fits GOBL's model well: an EU member
state using EUR, with tax identifiers that carry real modulo-11 check digits, so the regime
can do genuine validation rather than only format checks. I used the Netherlands (
nl)regime as a structural reference, since it's close to Slovenia: EU member, EUR, and a VAT
identity with a checksum.
What's included
Europe/Ljubljana, the standard invoice scenarios, and credit notes for corrections.Scope and design notes
A few things I decided deliberately, with the reasoning in case it helps the review:
Sincemechanism. The reduced rates have been stable for years, so I kept only their current values rather than pad the base regime with historical data — happy to add the full history if you'd prefer.tax_identity.go) and takes the weights as a parameter, since the two identifiers share the same scheme with different weights. The registration-number check strips its three-digit branch suffix (which has no check digit of its own) and reuses it.Testing
SI82646716and registration number5043611000) and exercised every branch of both algorithms, including theremainder == 0case that can never yield a valid number.gofmt,go vetandgolangci-lintare clean.Sources
https://www.fu.gov.si/en/taxes_and_other_duties/areas_of_work/value_added_tax_vat
https://taxation-customs.ec.europa.eu/system/files/2021-06/vat_rates_en.pdf
https://spot.gov.si/en/info/accountancy/invoicing
https://www.fu.gov.si/en/taxes_and_other_duties/work_with_us/entry_into_the_tax_register_and_tax_number
https://pisrs.si/Pis.web/pregledPredpisa?id=URED7599
based them on a widely used open-source implementation (python-stdnum) and verified them
against the public identifiers above:
https://github.com/arthurdejong/python-stdnum
Pre-Review Checklist
go generate .to ensure that the Schemas and Regime data are up to date.