Add Slovakia (SK) tax regime - #922
Conversation
Implements Slovakia's VAT regime with: - VAT categories and rates (standard 23%, reduced 19%, super-reduced 5% since 2025-01-01, plus the prior 20% standard rate since 2011-01-01) - IČ DPH tax identity validation: 10 digits, first non-zero, modulo-11 checksum - Tax identity normalization stripping the SK prefix, spaces and hyphens - EUR currency and Europe/Bratislava time zone - Slovak (sk) translations alongside English - Table-driven tests for validation and normalization - Example invoice exercising all three VAT rates Reduced and super-reduced rates are not backfilled before 2025: the reform restructured which goods fall in each tier, so a pre-2025 lineage would misrepresent which goods were taxed at which rate. Sources: - Slov-Lex, zákon č. 222/2004 Z. z. (§ 27): https://www.slov-lex.sk/ezbierky/pravne-predpisy/SK/ZZ/2004/222/ - Slov-Lex, zákon č. 490/2010 Z. z. (§ 85j): https://www.slov-lex.sk/ezbierky/pravne-predpisy/SK/ZZ/2010/490/ - Finančná správa - daň z pridanej hodnoty: https://www.financnasprava.sk/sk/podnikatelia/dane/dan-z-pridanej-hodnoty
samlown
left a comment
There was a problem hiding this comment.
Looks good thanks! I'd recommend changing the tax ID validations to more specific rules.
| rules.AssertIfPresent("01", "tax identity code for SK must be 10 digits starting with 1-9 and divisible by 11", | ||
| is.Func("valid", validateTaxCode), |
There was a problem hiding this comment.
Preference here is to split into specific rules rather than a is.Func catch all. For example, there could be a specific assertion around the pattern matching of the string, and a second for the checksum.
There was a problem hiding this comment.
Done. Split into two assertions: IDENTITY-01 for the 10-digit pattern and IDENTITY-02 for the modulo-11 checksum, each with its own message. Since assertions are evaluated independently, the checksum no longer depends on the pattern rule having run first, so it now parses with strconv.ParseInt and rejects non-digit input on its own. Dropping the is.Func wrapper also removed the cbc.Code type assertion, which took the package to 100% coverage.
# Conflicts: # CHANGELOG.md
Review feedback: a single is.Func catch-all reported the same fault whether the code had a malformed pattern or a failed checksum. - IDENTITY-01 now asserts the format only, via is.MatchesRegexp. - IDENTITY-02 asserts the modulo-11 checksum, via is.StringFunc. - is.StringFunc receives the string directly, which removes the cbc.Code type assertion and the cbc import. - Assertions are evaluated independently, so the checksum no longer relies on the pattern rule having run first; strconv.ParseInt rejects non-digits. - Tests assert the exact set of fault codes each input produces, including that the other code is absent. Follows the pattern already used by regimes/gb.
# Conflicts: # CHANGELOG.md
Summary
Adds the Slovak (SK) tax regime, structured on
regimes/fi.tax.GlobalVATKeys(), as in other regimes.SKprefix, spaces and hyphens; validation requires 10 digits with a non-zero first digit, the whole number divisible by 11.Europe/Bratislavatime zone, Slovak (sk) translations alongside English.Decisions
Rate history covers the current structure plus the prior standard rate only. Before 2025 there was a single 10% reduced rate, covering the goods listed in annex 7 of the VAT act (medicines, books). The 2025 reform abolished that tier rather than renaming it, introducing 19% over a different set of goods, while the old 10% scope maps more closely to today's 5%. Attaching that history to either current rate would misstate which goods were taxed at which rate, so it is left out. Invoices dated before 2025-01-01 using
reducedorsuper-reducedtherefore fail withrate value unavailable; the standard rate resolves back to 2011. Several regimes have the same shape:fr(reduced tiers only from 2014),se(from 1996),ie(general only from 2012 while reduced reaches 2003).Why the 20% rate cites two laws. Slovakia's VAT act (no. 222/2004) normally states its rates in section 27, but the 20% rate did not start there. A 2010 amendment (no. 490/2010) introduced it in section 85j instead, as a temporary measure due to expire once the national deficit fell below 3%. So between 2011 and 2014, section 27 still read 19% while the rate actually charged was 20%. The deficit condition was never met, and from 2015 the 20% was written into section 27 itself, where it stayed until the 2025 reform. The rate therefore ran unbroken at 20% from 2011 to 2024, and only the part of the law holding it changed. Citing section 27 alone would not support the 2011 start date, so both laws are listed as sources.
Tax identity validation stops short of the third-digit rule. The reference implementation also restricts the third digit to
[2346-9]. That is not adopted here: sources disagree on it, the algorithm is not published by the tax authority, and wrongly rejecting a valid VAT number blocks a real business from invoicing whereas wrongly accepting one is only a data-quality issue. The non-zero first digit is adopted, since without it0000000000validates (0 % 11 == 0).Deliberately out of scope
Regime only: no addon, no format conversion, no tax-authority transmission, no invoice chaining. Slovakia's e-invoicing requirements belong in an addon rather than the regime. There are also no supplier-specific invoice rules and no requirement for a tax ID on invoices, since Slovakia has a VAT-registration threshold below which a business has none and the regime resolves from
tax_id.countryalone.Open question
The tax authority does not publish the IČ DPH check algorithm. The modulo-11 rule is consistently documented across independent validators and holds for every valid identifier tested, but it is not an official specification. This is flagged in a code comment, following the precedent in
regimes/sg.How to test
Sources
ltns35/go-vat, already cited byregimes/deandregimes/dk, and VatDBAll sources are also recorded in code as
cbc.Sourceentries with retrieval dates.Pre-Review Checklist
go generate .to ensure that the Schemas and Regime data are up to date.And if you are part of the org: