Skip to content

Add Finland regime#748

Open
rilla wants to merge 5 commits intoinvopop:mainfrom
rilla:finland-regime
Open

Add Finland regime#748
rilla wants to merge 5 commits intoinvopop:mainfrom
rilla:finland-regime

Conversation

@rilla
Copy link
Copy Markdown
Contributor

@rilla rilla commented Mar 1, 2026

Summary

  • Introduced new tax regime for Finland (FI) with the following VAT categories:
    • standard (25.5%)
    • reduced (13.5%)
    • super-reduced (10%)
  • Added historical VAT rates (2013-2026)
  • Implemented Y-tunnus (Business ID) validation based on MOD 11
  • Added a B2B invoice example GOBL document
  • Added tests for all regime-specific logic
  • Corrections left undefined: Finnish VAT law doesn't restrict correction to specific document types, so GOBL's default permissive behavior applies
  • No supplier tax ID validation: Finland has a €20,000 annual turnover threshold for VAT registration (since 2025). Sellers below it aren't VAT-registered and have no obligation to include a VAT identification number on invoices.
  • Updated CONTRIBUTING.md to remove stalled references to deleted template and provide more detailed instructions for new regime additions.

Sources

Pre-Review Checklist

  • Opened this PR as a draft
  • Read the CONTRIBUTING.md guide.
  • Performed a self-review of my code.
  • Added thorough tests with at least 90% code coverage.
  • Modified or created example GOBL documents to show my changes in use, if appropriate.
  • Added links to the source of the changes in tax regimes or addons, either structured or in the comments.
  • Run go generate . to ensure that the Schemas and Regime data are up to date.
  • Reviewed and fixed all linter warnings.
  • Been obsessive with pointer nil checks to avoid panics.
  • Updated the CHANGELOG.md with an overview of my changes.
  • Marked this PR as ready for review.

And if you are part of the org:

  • Requested a review from Copilot and fixed or dismissed (with a reason) all the feedback raised.
  • Requested a review from @samlown.

@rilla rilla force-pushed the finland-regime branch 5 times, most recently from b8c8646 to 514fb69 Compare March 1, 2026 23:03
@samlown samlown requested a review from Copilot March 2, 2026 08:57
@codecov
Copy link
Copy Markdown

codecov bot commented Mar 2, 2026

Codecov Report

❌ Patch coverage is 97.56098% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 93.11%. Comparing base (72beee4) to head (d23162a).

Files with missing lines Patch % Lines
regimes/fi/tax_identity.go 92.00% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #748      +/-   ##
==========================================
+ Coverage   93.09%   93.11%   +0.01%     
==========================================
  Files         368      370       +2     
  Lines       19624    19706      +82     
==========================================
+ Hits        18269    18349      +80     
- Misses        919      920       +1     
- Partials      436      437       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new Finland (FI) tax regime to GOBL, including VAT categories/rates, Y-tunnus (Business ID) validation, invoice requirements, and supporting generated data/examples.

Changes:

  • Registered the new fi regime and added FI to the regime-code JSON schema enum.
  • Implemented FI VAT categories (with historical rates) plus FI-specific validation/normalization (tax identity + invoice requirements) with tests.
  • Added FI example invoice documents and generated regime definition data, plus updated contributor guidance and changelog.

Reviewed changes

Copilot reviewed 12 out of 13 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
regimes/regimes.go Registers the new Finland regime via blank import.
regimes/fi/fi.go Defines FI regime metadata and dispatches validation/normalization.
regimes/fi/tax_categories.go Declares FI VAT categories and historical rate values.
regimes/fi/tax_identity.go Adds Y-tunnus checksum validation logic.
regimes/fi/invoices.go Enforces FI invoice supplier Tax ID requirements.
regimes/fi/tax_identity_test.go Tests for FI tax ID validation and normalization.
regimes/fi/invoices_test.go Tests invoice validation requirement and date-based VAT calculations.
examples/fi/invoice-fi-fi.yaml Adds an FI invoice example input document.
examples/fi/out/invoice-fi-fi.json Adds generated JSON output for the FI example.
data/schemas/tax/regime-code.json Adds "FI" to the schema enum list.
data/regimes/fi.json Adds generated FI regime definition data.
CONTRIBUTING.md Updates regime-addition guidance and references.
CHANGELOG.md Notes the new FI regime addition.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread CONTRIBUTING.md
Comment thread regimes/fi/tax_identity_test.go Outdated
Copy link
Copy Markdown
Collaborator

@samlown samlown left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice! Not much to change here. We just need to double check the situation with Finish Tax Identities to ensure a VAT ID is required for any invoice.

Comment thread regimes/fi/invoices.go Outdated
Comment thread CONTRIBUTING.md Outdated
@rilla rilla force-pushed the finland-regime branch 2 times, most recently from b7f3cfd to 3364e4c Compare March 2, 2026 09:44
Comment thread regimes/fi/tax_categories.go Outdated
@rilla rilla marked this pull request as ready for review March 2, 2026 09:55
@rilla
Copy link
Copy Markdown
Contributor Author

rilla commented Mar 2, 2026

Thanks for the early review, @samlown, caught me in the middle of some final polish. I've addressed your (and Copilot's) comments and this is now ready for a final review.

@samlown
Copy link
Copy Markdown
Collaborator

samlown commented Mar 2, 2026

Sorry! Too keen :-) I should have waited for the non-draft state! Just let me know when ready.

@rilla
Copy link
Copy Markdown
Contributor Author

rilla commented Mar 2, 2026

Since #750 has been approved, I've amended the historic rates definitions so they reflect effective dates. I've also removed invoices_test.go as that behavior will be covered in tax/rate_def_test.go once the fix is merged.

Copy link
Copy Markdown
Collaborator

@samlown samlown left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excelent.

# Conflicts:
#	CHANGELOG.md
@rilla rilla force-pushed the finland-regime branch 2 times, most recently from 28c7ddb to 567561d Compare April 15, 2026 15:51
@rilla
Copy link
Copy Markdown
Contributor Author

rilla commented Apr 15, 2026

Hey @samlown, I've updated this branch with master and ported the regime to use validation rules.

@rilla rilla requested a review from samlown April 15, 2026 16:39
@samlown samlown requested a review from Copilot April 15, 2026 16:39
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 12 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +28 to +36
func validateTaxCode(value any) bool {
val := value.(cbc.Code).String()

for _, re := range taxCodeRegexps {
if re.MatchString(val) {
return validateTaxCodeChecksum(val)
}
}
return false
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

validateTaxCode uses a direct type assertion (value.(cbc.Code)), which will panic if the rules engine ever calls the test with a non-cbc.Code value (e.g., due to reflection/indirection changes). Other regimes defensively do code, ok := value.(cbc.Code) and return false when the assertion fails; mirroring that pattern would make this validator more robust.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I had this but lost it during a small refactor. Adding it back.

Comment thread CONTRIBUTING.md Outdated
Comment on lines +40 to +41
Both regimes and addons use a `switch obj := doc.(type)` pattern to dispatch normalization and validation to the
appropriate handler based on the document type. See the `Normalize` and `Validate` functions in any existing regime for an example.
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This section references a Validate function in regime packages, but there are no func Validate(doc any) implementations under regimes/ (validation is done via rules.Register(...) + rules.Validate(...)). Please update the docs to point at the rules registration pattern instead of a non-existent Validate dispatcher.

Suggested change
Both regimes and addons use a `switch obj := doc.(type)` pattern to dispatch normalization and validation to the
appropriate handler based on the document type. See the `Normalize` and `Validate` functions in any existing regime for an example.
Normalization in regimes and addons commonly uses a `switch obj := doc.(type)` pattern to dispatch to the
appropriate handler based on the document type. Validation is rule-based: regimes register validators with
`rules.Register(...)`, and validation is executed via `rules.Validate(...)` rather than a regime-level
`Validate(doc any)` dispatcher. See existing regime `Normalize` functions and rule registrations for examples.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated this section to reflect the current rules-based validations.

Comment thread CONTRIBUTING.md Outdated
Comment on lines +161 to +169
A minimal regime needs four files:

- `<cc>.go`: declares the regime and defines its metadata.
- `tax_categories.go`: declares tax categories with rates and, optionally, historical values.
- `tax_identity.go`: implements tax ID normalization, validation, classification, etc.
- `invoices.go`: implements invoice-level validation (e.g. supplier must have a tax ID).

Depending on the requirements you may also need:

Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The guide says a minimal regime needs an invoices.go file, but there are no invoices.go files in regimes/ (invoice rules, when needed, are typically in files like bill_invoices.go, and some regimes have no invoice-specific rules at all). Please adjust the wording to make invoice-level validation optional and/or align the filename guidance with the existing convention.

Suggested change
A minimal regime needs four files:
- `<cc>.go`: declares the regime and defines its metadata.
- `tax_categories.go`: declares tax categories with rates and, optionally, historical values.
- `tax_identity.go`: implements tax ID normalization, validation, classification, etc.
- `invoices.go`: implements invoice-level validation (e.g. supplier must have a tax ID).
Depending on the requirements you may also need:
A minimal regime needs three files:
- `<cc>.go`: declares the regime and defines its metadata.
- `tax_categories.go`: declares tax categories with rates and, optionally, historical values.
- `tax_identity.go`: implements tax ID normalization, validation, classification, etc.
Depending on the requirements you may also need:
- `bill_invoices.go` (or another domain-specific file): implements invoice-level validation when the regime requires it (e.g. supplier must have a tax ID).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Amended this section to reference bill_invoices.go (adopted across many regimes) instead of invoices.go, and org_identities.go instead of identities.go.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants