feat(tax): Argentina IVA pack + manual-config for unsupported countries - #219
feat(tax): Argentina IVA pack + manual-config for unsupported countries#219carvalab wants to merge 1 commit into
Conversation
1405e7f to
d462626
Compare
|
Thanks for the work here - the overall direction is great. The versioning, audit trail, dormant overrides, category remapping, registration-label fix, and inclusive/exclusive examples are valuable foundations. Before resuming work, could you coordinate with @itsbkm on the implementation direction? This PR overlaps with the newer manual-tax builder and tax-ordering work already on Once aligned, please rebase this PR onto current |
b297d51 to
d74aadf
Compare
|
thanks @khaira777, @itsbkm |
d74aadf to
cbb60fa
Compare
The spec's Decision F (docs/tax-engine-v2-spec.md:541) said manual
configuration would be a synthetic local country pack; the PR adds the
pack, then the UI, then makes the UI always reachable and auditable.
Argentina pack
- main/tax-packs/argentina.json: id official-argentina, country AR,
currency ARS, registrationNumberLabel CUIT. Three IVA rate
categories (iva_21, iva_105, iva_27) plus line-kind defaults and
iva_exempt unclassified. Effective 2026-08-07, minFloVersion 2.4.0.
- Not bundled — ships via the signed catalog flow
(tax-pack-official-argentina-v0.1.0 → FloCafe-Plugins release).
- docs/tax-packs.md: added to the official-pack examples list.
Manual-config UI
- POST /api/tax-packs/manual-config (owner-only) builds a synthetic
local pack (publisher 'local', signature null) with one tax percent
rule applied to all six categories. Runs the same 24-check
validationChecklist every other pack runs. Re-save creates a new
version, demotes the previous, and re-points tax_overrides so
merchant overrides carry across.
- TaxConfigurationPanel: the manual rate form is now always visible,
not only after a 404. Shows rate, menu prices (inclusive / exclusive)
with a plain-language explainer and a worked example recomputed live
at the entered rate, registration label with hint, and an amber
warning when an official pack is active. Button copy switches to
"Replace with manual rate" with a window.confirm that names the
pack before sending override: true.
- The form reloads the saved rate / inclusive / label on mount, so a
blank form cannot silently overwrite an existing configuration.
- Every label, toast, audit line, and support-ticket payload resolves
through t(); the panel is fully translated in en/es/pt (1769 keys).
Pack replacement (override)
- manual-config accepts override: true. Without it an active official
pack still 409s (with can_override: true so the panel knows to ask).
- Merchant overrides are left dormant, not re-pointed: a manual pack
applies one rate to every category, so re-pointing changes no tax
outcome but overwrites the categoryId the owner chose — data loss
for zero benefit. Dormant overrides keep the original value and
revive if the official pack is activated again. The count is
surfaced in the response, the audit, and a warning toast.
- replacedPackId and dormantOverrides are written to the
activate_pack audit details. auditDescription renders them:
"Manual configuration (rate 21%, inclusive) · replaced ar-iva;
3 merchant overrides stopped applying".
Category remap (audited)
- backfillCategoryDefaults (shared by manual-config and
ensure-country) is the only thing that prevents checkout from
400-ing on stale tax_category_id values when the incoming pack
does not define the row's category.
- Decision K required that the mapping be recorded. The function
now returns {entity, from, to, count}[] and both call sites write
a remap_categories audit row with the full mapping. The mapping
is also returned in the response so the panel can warn the owner
(⚠ N product/add-on tax categories were remapped and M merchant
overrides stopped applying. Check the audit history.). NULL rows
remain a backfill, not a loss, and are not reported.
Country label fix
- local-generic carries country: '*'. resolveTaxIdLabel was returning
its "Tax registration" label to every country, which leaked to TH.
Now trusts a pack's label only when its country matches the
requested country; otherwise falls through to countries.ts, then
"Tax ID".
One-POS-is-one-country
- The country check on the manual-pack prefill is gone. The real
discriminator was never the country: the bundled no-tax pack is
publisher 'local' too. A "has a rate" test (pack.rules[].rate)
tells a configured manual pack apart from a pristine install. A
new test asserts both halves of that invariant.
Helpers
- main/services/tax.ts: resolveTaxIdLabel (country-scoped),
backfillCategoryDefaults → CategoryRemap[].
- main/routes/tax-packs.ts: manual-config + override, ensure-country,
remap_categories audit on both.
Tests
- tax-pack-management 172/172 (sections 9-13 + invariant):
* section 9: signs Argentina pack via Ed25519, asserts 24-check
validation, ARS 173.55 inclusive extraction on ARS 1000.
* section 10: saves a manual pack (rate 21, CUIT), audit +
backfill + tax calc.
* section 11: rejects manual-config when an official pack is
active; with override: true it replaces, demotes the official
one, remaps stale categories, writes a remap_categories audit,
reports remapped + dormant_overrides in the response.
* section 12: re-save path. Same pack_id, fresh patch version,
previous version demoted, audit previousVersionId set.
* section 13: resolveTaxIdLabel fallback paths.
* invariant: local-generic has no rules, a saved manual-<cc>
has exactly one — the two halves of the panel's prefill test.
- e2e-argentina-flow 35/35: real DB product, ARS 17.36 inclusive at
21%, "IVA 21%" component label.
- integration-tax 94/94, integration-happy-path 24/24, tax-engine
11/11, upgrade-path.
- Both new tests wired into npm test via package.json.
Verification
- npm run build (backend) ✓, npm run build:frontend ✓
- npm run lint ✓ (0 errors)
- npx tsc --noEmit ✓
Out of scope (open spec items)
- Spec activation rule "show a clear before/after summary and
effective date" before activating. The window.confirm names the
pack; old rate → new rate and effective date are not shown yet.
Add when an owner asks for a pre-flight review.
- taxPoint: 'finalized_at' is hardcoded on the synthetic local pack.
Decision I makes held orders get silently repriced at checkout.
order_created is the conservative default; switch when the
held-order stale-warning banner ships (tax-engine-v2-spec.md:646).
Co-Authored-By: Claude <noreply@anthropic.com>
Review follow-up (squashed into the PR head):
- main/tax-packs/argentina.json: end the file with a trailing newline so
the signed release bytes match a conventional POSIX file.
- TaxConfigurationPanel: the manual-config worked example and the
official-pack replacement warning now resolve through useI18n with new
settings.taxConfig* keys in en/es/pt.
- tax-pack-management.test.ts section 9: activate the catalog-installed
Argentina pack through the real owner route
(POST /:packId/versions/:versionId/activate) instead of direct
country_packs updates, with the trusted-key binding pointed at the test
keypair (ts-node CommonJS live property access) because the route
re-validates with the app's trusted key. Asserts the route response and
the activate_pack audit row; the products/addons backfill stays in the
test because the activation route leaves product data alone.
- frontend/src/lib/i18n/fa.json: add the six new keys in Persian (the PR
branch was rebased onto main, which now ships the fa locale; the
translations integrity test enforces parity across en/es/pt/fa).
Verification: test:translations clean across en/es/pt/fa (1674 keys each),
test:tax-pack-management 112/112, test:e2e-argentina-flow 35/35,
frontend eslint clean, git diff --check clean.
cbb60fa to
b740408
Compare
What this does
Adds the official Argentina tax pack (IVA 21%, 10.5% and 27%, CUIT label) and cleans up the manual-rate UX that's already in main.
Argentina pack
main/tax-packs/argentina.json: catalog-only official pack, effective 2026-08-07, requires FloCafe 2.4.0+docs/tax-packs.md: added to the official-pack examples listTests
tax-pack-management.test.ts: signs and installs the pack through the catalog flow, runs the 24-check validation, and checks that ARS 1000 inclusive at 21% extracts ARS 173.55e2e-argentina-flow.test.ts: installs the pack and verifies a real DB product (ARS 17.36 inclusive at 21%, "IVA 21%" label) instead of assuming the pack is absentManual-rate UX
default_inclusivefield).Diff size
10 files, +258 / −7 against
main.