Skip to content

Add Luxembourg (LU) tax regime - #858

Open
Lluc24 wants to merge 7 commits into
invopop:mainfrom
Lluc24:regime/lu
Open

Lluc24 wants to merge 7 commits into
invopop:mainfrom
Lluc24:regime/lu

Conversation

@Lluc24

@Lluc24 Lluc24 commented Jun 12, 2026

Copy link
Copy Markdown

Adds Luxembourg (LU) as a new tax regime.

What's included

  • regimes/lu — regime definition following the structure of the recently merged Norway regime (Feat/regime no #849):
    • Four VAT rates with full Since-dated history: standard 17%, intermediate ("parking") 14%, reduced 8%, super-reduced 3%. The history includes the temporary one-point reduction in force throughout 2023 (Law of 26 October 2022, Mémorial A No. 534) — the standard, intermediate, and reduced rates dropped to 16/13/7% and were restored on 2024-01-01; the super-reduced rate was excluded from the measure. An invoice dated within 2023 automatically calculates with the temporary rates.
    • TVA number validation: 8 digits where the last two must equal the first six mod 89, plus normalization of the LU prefix, spaces, and dashes.
    • RCS identity (RCS): Luxembourg Business Registers company number, format <section letter> + 1–6 digits (e.g. B263475). Any single uppercase section letter is accepted — the published list of sections (A sole traders, B commercial companies, C GIEs, E civil companies, …) has grown over time, so a closed list risks rejecting valid registrations.
    • Invoice rule: the supplier must carry either a TVA code or an RCS identity. Note this is a structural baseline so businesses below the VAT registration threshold can still issue valid invoices — not a claim that an RCS number legally substitutes for the TVA number, which VAT-registered suppliers must always state.
    • Corrections: credit notes and debit notes, both recognised in Luxembourg practice.
  • examples/lu — four documents with golden outputs: standard B2B invoice (17%), multi-rate invoice (17/8/3%), a 2023-dated invoice demonstrating the historical 16%/7% rates, and a credit note.
  • Generated data (data/regimes/lu.json, data/rules/lu.json, regime-code schema) and a CHANGELOG entry.

Scope decisions

  • No addon. Luxembourg has no domestic B2B e-invoicing mandate; B2G uses Peppol/EN 16931, already covered by the existing eu-en16931-v2017 addon. A lu-* addon would be speculative at this point (pending ViDA).
  • No withholding taxes, OSS/IOSS, or VAT-group handling — respectively income-tax mechanisms, EU-level machinery, and registration arrangements invisible at the single-invoice level.
  • Pre-2015 rate history (15/12/6/3%) is anchored at a conservative 1992-01-01 baseline; the values are well documented but I could not verify the exact original effective dates under the amended 1979 law against primary sources. Happy to adjust if you have a preferred convention for historical anchors.

Sources

Structured sources are also embedded in the regime and category definitions.

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. (97.3% of statements)
  • 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.

@codecov

codecov Bot commented Jun 12, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.57143% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 94.79%. Comparing base (166bec4) to head (088601f).

Files with missing lines Patch % Lines
regimes/lu/tax_identity.go 93.10% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #858      +/-   ##
==========================================
+ Coverage   94.76%   94.79%   +0.02%     
==========================================
  Files         343      347       +4     
  Lines       18754    18894     +140     
==========================================
+ Hits        17773    17911     +138     
- Misses        597      598       +1     
- Partials      384      385       +1     

☔ View full report in Codecov by Harness.
📢 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.

@samlown samlown left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Great PR! A few style changes I think would help.

Comment thread regimes/lu/bill_invoices.go Outdated
Comment thread regimes/lu/bill_invoices.go Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds Luxembourg (LU) as a first-class tax regime in GOBL, including VAT rate definitions with historical changes, LU-specific identity validation/normalization, and regime-scoped invoice validation rules, plus examples and regenerated data outputs.

Changes:

  • Register new LU regime and expose it through the global regimes registry.
  • Implement LU VAT categories (4 rates) with since-dated history (incl. 2023 temporary reduction) and identity validations (TVA mod-89, RCS format).
  • Add LU examples (YAML + golden JSON) and regenerate schema/rules/regime JSON, plus a changelog entry.

Reviewed changes

Copilot reviewed 18 out of 22 changed files in this pull request and generated no comments.

Show a summary per file
File Description
regimes/regimes.go Registers the LU regime package via blank import.
regimes/lu/lu.go Defines the LU regime (currency, timezone, corrections, categories, identities) and registers rules/normalizers.
regimes/lu/tax_categories.go Adds LU VAT category with 4 rates and since-dated history (+ sources).
regimes/lu/tax_identity.go Adds LU TVA normalization and mod-89 validation rules.
regimes/lu/org_identities.go Adds LU RCS identity type, normalization, and format validation rules.
regimes/lu/bill_invoices.go Adds LU invoice rule requiring supplier to have either TVA tax ID code or an RCS identity.
regimes/lu/*_test.go Unit tests for LU regime construction, identity normalization/validation, and invoice rule.
examples/lu/*.yaml Adds LU example documents (standard, multi-rate, 2023 historical rate example, credit note).
examples/lu/out/*.json Golden outputs for the LU examples.
data/schemas/tax/regime-code.json Adds LU to the regime-code schema enum.
data/rules/lu.json Generated LU ruleset output.
data/regimes/lu.json Generated LU regime definition output.
CHANGELOG.md Notes the addition of the LU regime.

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

@samlown samlown left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Still a few issues with the approaches for handling test functions: we should be re-using as much as possible.

Comment thread regimes/lu/bill_invoices.go Outdated
Comment thread regimes/lu/org_identities.go Outdated
Comment thread regimes/lu/tax_identity.go Outdated
@samlown samlown added enhancement New feature or request regime Related to a specific regime. labels Jun 16, 2026
@Lluc24
Lluc24 requested review from Copilot and samlown June 28, 2026 04:30

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 18 out of 22 changed files in this pull request and generated 2 comments.

Comment thread data/rules/lu.json
Comment thread data/rules/lu.json
Copilot AI review requested due to automatic review settings July 5, 2026 05:47

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 18 out of 22 changed files in this pull request and generated no new comments.

Lluc24 and others added 7 commits July 15, 2026 09:25
Luxembourg (LU) applies VAT (Taxe sur la Valeur Ajoutée, TVA) administered
by the AED. As an EU member it follows Directive 2006/112/EC.

Four rates are defined with full historical data going back to 1992:
- Standard (general):    17 % / 16 % (2023 temp) / 17 % / 15 %
- Intermediate (parking): 14 % / 13 % (2023 temp) / 14 % / 12 %
- Reduced:                 8 % /  7 % (2023 temp) /  8 % /  6 %
- Super-reduced:           3 % (unchanged since 2015)

The 2023 temporary reduction (Law of 30 Nov 2022) is encoded as
Since-dated RateValueDef entries, so GOBL automatically picks the
correct rate for any invoice date.

TVA numbers are validated with a mod-89 check (last two digits of the
8-digit code must equal the first six digits mod 89). RCS numbers
(Registre de Commerce et des Sociétés) are supported as an org identity
with format [BFGH] + 1–6 digits. Suppliers must carry either a TVA code
or an RCS identity on invoices.

Four example invoices are included: standard B2B (17 %), multi-rate,
a 2023-dated invoice that exercises the historical 16 %/7 % rates, and
a credit note.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Three corrections after review:

- The temporary 2023 rate reduction was enacted by the Law of
  26 October 2022 (Mémorial A No. 534), not 30 November 2022.
  Citation and source URL fixed, and the description now correctly
  states the super-reduced rate was excluded from the reduction.

- RCS validation accepted only [BFGH] section letters, which would
  have rejected valid registrations such as section A (sole traders)
  and E (civil companies). Any single uppercase section letter is now
  accepted: the list of sections has grown over time and the registry
  is the authority on which exist. Also documented that the RCS number
  is a registry reference, not a tax identifier — accepting it in
  place of a TVA code on invoices is a structural baseline for
  non-VAT-registered businesses, not a legal equivalence.

- The 3% super-reduced rate predates 2015 by decades, so its single
  value is now anchored at the regime's 1992 history baseline instead
  of 2015.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Run the full `go generate .` to pick up the regime-code schema enum
and the data/rules export that the regimes-only generator misses.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Per CONTRIBUTING.md, the preferred language is English (American).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replace is.Func with is.AnyOf for the TVA/RCS assertion and condense
the comment to one line.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Split TVA validation into discrete length, digit, and checksum rules so
error codes pinpoint the exact failure. Inline the RCS format check using
is.MatchesRegexp to remove an unnecessary wrapper. Rework supplier identity
rule to only require an RCS identity when no TVA code is present, rather
than treating the two as alternatives.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The rules in tax_identity.go and bill_invoices.go were refined after the
last `go generate .` run, leaving data/rules/lu.json stale. Regenerate so
the JSON reflects the three TVA assertions (length, digits-only, mod-89
checksum), the RCS format regex, and the conditional supplier identities
check.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 15, 2026 16:27

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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

Labels

enhancement New feature or request regime Related to a specific regime.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants