feat/tax-regime
blockedviewing older commit1bda877 · fullPR #302reviewed 2026-07-13 02:30 UTC12H · 12M · 9L- Purpose
- Fix systematic over-taxation of northern-border sites on calculated bills — CFE scrapes read real IVA from recibos (CVEIVA field) but calculated bills (THOR, baselines, grid) hardcoded 16%.
- Goal
- Add per-contract tax_regime field (iva=16% / iva_frontera=8%) and wire it through the bill-compute path so border sites are taxed correctly.
- Sub-goals
- SG-1: Schema — tax_regime text NOT NULL DEFAULT 'iva' on utility_contracts (migration 0058, additive+safe)
- SG-2: Domain — TAX_REGIMES/TaxRegime/TAX_REGIME_RATES constants, threaded through type/mapper/decisions/queries/insert
- SG-3: Billing — resolveSiteTariffContext returns taxRegime; computeBillShell applies it; persistBillForContract resolves it from contract for pinned-tariff path
- SG-4: Tests — tax-regime.test.ts (mapping) + bill-calculator 8% case; manual validation against WE LOVE BURGERS (16%) and PETCO (8%)
- What
- New column tax_regime on utility_contracts with safe DEFAULT 'iva'. Constants TAX_REGIMES/TAX_REGIME_RATES on both domain type and (separately) DB schema. Thread through mapper, CreateContractCommand, insert query. resolveSiteTariffContext now returns taxRegime. computeBillShell uses TAX_REGIME_RATES[taxRegime ?? 'iva'] replacing MX_IVA=0.16. persistBillForContract Strategy A adds a findById call to resolve taxRegime when tariff is pinned.
- Why
- IVA is per-location (franja fronteriza ~20km strip), not per-tariff/zone. Same GDMTH THOR × Baja California has 15 sites at 16% and 5 at 8%. Must live on the contract.
- Areas
- domains/cross-domain/src+29−4domains/utility/src+38−1packages/database+13050−0
- Blast
- 12 non-generated files changed (+68/-5 excl. snapshot). Touches the bill-compute critical path, two auto-creation decision functions, and one cross-domain query. Migration is additive-safe.
Findings · 33
correctness6
persistCalculatedBillShell pins tariff fields but never forwards taxRegime — border sites always 16%
domains/cross-domain/src/calculated-bill-persist.shells.ts:119
Same root cause as above. Fix: read ctx.value.taxRegime after resolveSiteTariffContext and pass it into the computeBillShell call.
UpdateContractCommand omits taxRegime — regime cannot be corrected on existing contracts
domains/utility/src/utility-contract/utility-contract.decisions.ts:72
All pre-existing contracts were migrated to 'iva' default. No code path lets operators correct 'iva_frontera' on them through normal API flow. Must be added to UpdateContractCommand + patch + decision + query.
decideEnsureContractForTariff drops taxRegime on replacement
domains/utility/src/utility-contract/utility-contract.decisions.ts:452
Add taxRegime: existingContract.taxRegime to buildContractInput return in the CreateContractForTariff branch.
Strategy A silently falls back to 16% if findById returns null
domains/cross-domain/src/bill-for-contract.shells.ts:149
A soft-deleted contract returns null; taxRegime becomes undefined; border bill is over-taxed with no error raised. Return explicit error for null contract.
TAX_REGIME_RATES lookup has no runtime guard — invalid DB value yields NaN
domains/cross-domain/src/bill-compute.shells.ts:184
Add a runtime guard or rely on DB CHECK constraint (preferred).
Strategy B (PPA) silently ignores taxRegime — no docs
domains/cross-domain/src/bill-for-contract.shells.ts:179
Document in PersistBillForContractInput that taxRegime is ignored for PPA contracts.
security7
No DB-level CHECK constraint — corrupt tax_regime produces NaN bill totals
packages/database/drizzle/0058_material_kitty_pryde.sql:1
text() with { enum } is TypeScript-only. A raw SQL UPDATE or future migration can write arbitrary text; TAX_REGIME_RATES[unknown] = undefined; undefined × subtotal = NaN persisted silently. Add CHECK (tax_regime IN ('iva','iva_frontera')) to migration.
Self-reported border rate — no geographic validation
domains/utility/src/utility-contract/utility-contract.decisions.ts:59
taxRegime is accepted from callers without checking the contract's pricing zone falls in the franja fronteriza. An operator can falsely claim 8% (iva_frontera) on a mainland site, under-paying IVA by 50%.
TOCTOU: findById for taxRegime runs outside bill-write transaction
domains/cross-domain/src/bill-for-contract.shells.ts:151
Contract read and bill INSERT are not atomic. A concurrent regime update produces a bill with stale rate, idempotency-locked. Wrap in a single transaction or pass taxRegime from caller.
Silent fallback to 16% when contract deleted mid-computation — no error or log
domains/cross-domain/src/bill-for-contract.shells.ts:149
Should be an explicit ContractNotFound error so callers can alert rather than persist an incorrect bill.
RLS contracts_update_admin WITH CHECK (true) — taxRegime writable if added to UpdateContractCommand
packages/database/src/schema/utility-contracts.ts:116
No column-level restriction. Document the dependency: UpdateContractCommand must never include taxRegime without a privileged endpoint.
savings-report-compute.shells.ts extra findById per contract
domains/cross-domain/src/savings-report-compute.shells.ts:229
Addressed by improvement finding — add taxRegime to SavingsSiteContract.
bill-pdf handler missing taxRegime override — parity gap
apps/platform/src/api/handlers/bill-pdf.handler.ts:104
computeBillShell supports taxRegime override but the PDF endpoint doesn't expose it.
conventions7
TAX_REGIMES const duplicated in domain type AND DB schema — violates one-directional type flow
packages/database/src/schema/utility-contracts.ts:41
TAX_REGIMES defined identically in utility-contract.type.ts and utility-contracts.ts. DB schema must import from domain, not redefine. A silent divergence compiles fine but breaks at runtime.
taxRegime not propagated in decideEnsureContractForTariff clone path
domains/utility/src/utility-contract/utility-contract.decisions.ts:366
buildContractInput clones fields from existingContract but omits taxRegime; replacement contract defaults to 'iva'. Border contracts lose their 8% regime on tariff reconciliation.
taxRegime not propagated in decideEnsureContractFromBills (CFE pipeline path)
domains/utility/src/utility-contract/utility-contract.decisions.ts:450
buildContractInput used by both CreateContractFromBills and TerminateAndReplace never sets taxRegime. Same loss on auto-replacement by the CFE pipeline.
UpdateContractCommand omits taxRegime — no route to correct mis-set regime
domains/utility/src/utility-contract/utility-contract.decisions.ts:72
If intentional (immutable-after-creation), document it with a comment. If not, add to the command.
Out-of-transaction contract read in Strategy A violates FCIS atomicity
domains/cross-domain/src/bill-for-contract.shells.ts:149
ADR-016: fetch → decide → write atomically. The findById for taxRegime is outside any transaction; the bill write is in a separate nested transaction. Not atomic.
type-check.ts not updated — implicitly correct but unverified
domains/utility/src/utility-contract/utility-contract.type-check.ts:22
AssertEqual covers taxRegime implicitly. Confirm typecheck passes.
TAX_REGIMES not re-exported from DB schema index — inconsistent with CONTRACT_STATUSES
packages/database/src/schema/index.ts:48
Either export both or neither.
tests7
persistCalculatedBillShell does not thread taxRegime into computeBillShell — border bills taxed at 16%
domains/cross-domain/src/calculated-bill-persist.shells.ts:115
resolveSiteTariffContext now returns taxRegime but persistCalculatedBillShell forwards only tariffId/pricingZoneId/tariffCode/isTou to computeBillShell. computeBillShell's guard (`if tariffId === undefined...`) is false so the taxRegime assignment is skipped. TAX_REGIME_RATES[undefined ?? 'iva'] = 0.16 always. Every iva_frontera site billed through this shell is over-taxed. No test covers this path.
resolveSiteTariffContext taxRegime propagation never asserted
domains/cross-domain/src/site-tariff.queries.ts:184
Fixtures in site-tariff.test.ts never set taxRegime; the field is never asserted in returned SiteTariffContext. The JOIN column could be miswired with no failing test.
persistBillForContract tariff-strategy path has zero test coverage
domains/cross-domain/src/bill-for-contract.shells.ts:143
bill-for-contract.test.ts only tests the PPA pure mapper. The tariff strategy with the new findById lookup has no unit or integration test.
No integration test for iva_frontera contract → bill total end-to-end
domains/utility/src/utility-contract/__tests__/utility-contract.integration.test.ts:1
Manual validation against PETCO (8%) is documented in the PR but not automated. Any future refactor of the shell chain can silently break this.
projectSiteTariffContext unit tests never assert taxRegime
domains/cross-domain/src/__tests__/site-tariff.test.ts:1
Add a parametrized case for taxRegime: 'iva_frontera' through the pure projection.
bill-calculator test bypasses TAX_REGIME_RATES lookup wiring
domains/utility/src/bill-calculator/__tests__/bill-calculator.decisions.test.ts:63
calculateBill(gdmthInput({ taxRate: 0.08 })) passes rate directly; doesn't test TAX_REGIME_RATES['iva_frontera'] lookup.
No round-trip test for taxRegime: 'iva_frontera' through insert query
domains/utility/src/utility-contract/utility-contract.queries.ts:355
Add one integration test creating a contract with iva_frontera and reading it back.
improvement6
Extra findById per savings contract — taxRegime absent from SavingsSiteContract
domains/cross-domain/src/savings-contracts.queries.ts:93
resolveSiteContractsForSavings omits taxRegime from SELECT, causing a separate findById per contract on every savings compute. Add taxRegime to the SELECT + SavingsSiteContract + billInputFor to eliminate the redundant query.
TAX_REGIMES duplicated — DB schema should import from domain type
packages/database/src/schema/utility-contracts.ts:41
Same fix as conventions finding above.
taxRegime not in UpdateContractCommand — no API path to correct mis-set regime
domains/utility/src/utility-contract/utility-contract.decisions.ts:72
All pre-migration contracts defaulted to 'iva'. Correction path needed.
No PostgreSQL CHECK constraint on tax_regime
packages/database/drizzle/0058_material_kitty_pryde.sql:1
Add CHECK (tax_regime IN ('iva','iva_frontera')) to the migration to prevent NaN corruption from raw SQL.
Conditional spread obscures intent
domains/cross-domain/src/bill-for-contract.shells.ts:159
Replace ...(taxRegime ? { taxRegime } : {}) with taxRegime: taxRegime ?? 'iva' for clarity.
taxRegime not exposed in API response — clients cannot read it
apps/platform/src/api/mappers/utility-contract.mapper.ts:62
Correction flows from the UI are impossible if the field isn't in the response.
History · 6 commits
- a1f0898needs attentionincremental1H · 1M · 4L2026-07-13 20:37
- a72b534safeincremental0H · 0M · 0L2026-07-13 19:28
- 92e5afaneeds attentionincremental1H · 1M · 8L2026-07-13 16:47
- 7f40d56needs attentionincremental1H · 4M · 5L2026-07-13 16:38
- 5088e00needs attentionincremental3H · 4M · 4L2026-07-13 02:47
- 1bda877blockedfull12H · 12M · 9L2026-07-13 02:30current