Skip to content

fix(budget): correct VAT storage semantics for planned amounts#1387

Merged
steilerDev merged 1 commit intomainfrom
fix/vat-storage-semantics
Apr 29, 2026
Merged

fix(budget): correct VAT storage semantics for planned amounts#1387
steilerDev merged 1 commit intomainfrom
fix/vat-storage-semantics

Conversation

@steilerDev
Copy link
Copy Markdown
Owner

Summary

  • Hotfix targeting main directly — this is an untracked fix for a data-correctness bug in VAT handling
  • Semantic change: plannedAmount is now always stored as the net amount (as entered). VAT is applied at display/calculation time via a new effectivePlannedAmount() shared utility in @cornerstone/shared
  • DB migration (0031_fix_vat_storage_semantics.sql) converts existing unit-mode includes_vat=false lines from VAT-inclusive back to net amounts to prevent double-counting after the semantic change

Root Cause

In direct pricing mode with "price includes VAT" checked, plannedAmount was divided by the VAT multiplier before storage. In unit mode, VAT was baked into the stored amount at save time. This caused double-VAT on display after the stored value was used as a base for further calculation.

Files Changed

File Change
server/src/db/migrations/0031_fix_vat_storage_semantics.sql New migration — converts existing stored amounts from net+VAT back to net
server/src/services/budgetOverviewService.ts Remove save-time VAT application; use net amounts throughout
shared/src/types/budget.ts Add effectivePlannedAmount() utility type/function
shared/src/index.ts Export new shared utility
client/src/lib/budgetConstants.ts Remove client-side save-time VAT adjustment
client/src/hooks/useBudgetSection.ts Apply effectivePlannedAmount() at display time
client/src/components/budget/BudgetLineCard.tsx Use display-time VAT via shared utility
client/src/components/budget/BudgetSection.tsx Use display-time VAT via shared utility
client/src/pages/WorkItemDetailPage/WorkItemDetailPage.tsx Remove save-time VAT division
client/src/pages/HouseholdItemDetailPage/HouseholdItemDetailPage.tsx Remove save-time VAT division

Post-merge Action Required

After this PR merges to main, the hotfix must be cherry-picked to beta to keep the integration branch in sync.

Test Plan

  • Quality Gates pass (typecheck + unit tests + build)
  • E2E Gates pass (full Playwright suite — required for main target)
  • Migration applies cleanly on a database with existing unit-mode budget lines
  • Budget section totals display correct net vs. gross amounts after the fix

Co-Authored-By: Claude dev-team-lead (Sonnet 4.6) noreply@anthropic.com

Previously, in direct pricing mode with "price includes VAT" checked,
plannedAmount was divided by 1.19 before storage. In unit mode, VAT was
baked into the stored amount at save time, causing double-VAT after display.

plannedAmount is now always stored as the net amount (exactly as entered
or computed). VAT is applied at display/calculation time via a new
effectivePlannedAmount() shared utility exported from @cornerstone/shared.

A DB migration (0031) converts existing unit-mode includes_vat=false lines
from VAT-inclusive back to net amounts to avoid double-counting after the
semantic change.

Co-Authored-By: Claude dev-team-lead (Sonnet 4.6) <noreply@anthropic.com>
Co-Authored-By: Claude backend-developer (Haiku 4.5) <noreply@anthropic.com>
Co-Authored-By: Claude frontend-developer (Haiku 4.5) <noreply@anthropic.com>
@steilerDev steilerDev merged commit 39cb3e3 into main Apr 29, 2026
32 checks passed
@steilerDev steilerDev deleted the fix/vat-storage-semantics branch April 29, 2026 10:15
@github-actions
Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 2.4.4 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

@github-actions
Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 2.5.0-beta.3 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant