Repository navigation
fix(revenue)!: remove crm_quote_line_item.tax_rate — a quote line's total is its subtotal - #2089
Merged
Merged
Conversation
…ts subtotal A quote line carried a Tax Rate % that reached only that line's Total, while the quote rollup sums pre-tax subtotals and adds the quote's own typed Tax. On a quote whose line carried a rate, the lines summed to more than the header beside them (lines 10,945 against a 9,810 total on QTE-0007's shape). The quote's typed Tax is the one tax model: it is seeded on every demo quote, on the default form and grid, and is what the docs tell users to fill. So the line rate is removed, and the line's total_price is the same expression as its subtotal. The quote rollup never read the rate and is unchanged: the header is still subtotal - discount + tax + shipping. - crm_quote_line_item.tax_rate and its four locale rows are removed; the comments that described it (object, twin, seed) are corrected. - The quotes and products pages say a line carries no tax, in all three locales; whats-new's 3.1.0 entry notes the later retirement. - docs/STATUS.md's validator transcript goes from 358 to 357 fields. - hooks-runtime pins QTE-0007's shape: the line totals add up to the header's subtotal and a rate on a line is refused (INVALID_FIELD / 400). - line-item-conventions pins total_price == subtotal; the undeclared-key probe's declared control moves from the line's tax_rate to crm_quote.tax. Claude-Session: https://claude.ai/code/session_01BCDqr1DqnQjEh5jA9gGkUG Co-authored-by: Claude <noreply@anthropic.com>
States what an install that filled a line rate sees: that line's Total changes (945 to 900 on a 1 x 1,000 line at 10% off and 5%), the quote's Total Price does not, and the stored rates stay in an orphaned column that os migrate plan lists as a destructive drop and os migrate unmapped-columns reads by record id. Measured by upgrading a database written by the base build. Claude-Session: https://claude.ai/code/session_01BCDqr1DqnQjEh5jA9gGkUG Co-authored-by: Claude <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
The quote line's tax rate is retired on this branch, so the row states what the line total now is: the line after its own discount, with no tax of its own. Claude-Session: https://claude.ai/code/session_01BCDqr1DqnQjEh5jA9gGkUG Co-authored-by: Claude <noreply@anthropic.com>
This was referenced Oct 13, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #2069
What and why
This executes ruling B on the card (comment 6142078386, the maintainer's 「hotcrm 三个决裁都同意」): the quote's typed Tax is the only tax model.
crm_quote_line_item.tax_rateis removed. It is a published field, so this is a minor release by the 3.1.0 precedent (1486ec0c, which removedcrm_product.tax_rate). The removal is named in this title and in the changeset.total_priceis now the same expression as itssubtotal:* (1 + record.tax_rate / 100)is gone. The line totals add up to the quote's Subtotal.quote_total_rollupnever read the rate and is unchanged. The header still reads subtotal − discount + tax + shipping.Every reader of the field
git grep -n tax_rateover the whole tree at base0f84c9cfound the following. Views, pages, field groups, hooks, flows, datasets, reports, mappings,assets/import-templates/ande2e/have none.src/revenue/objects/quote_line_item.object.tssrc/revenue/objects/opportunity_line_item.object.tssrc/revenue/data/revenue.seed.tstax_rateat 0 is removed.src/sales/translations/{en,es-ES,ja-JP,zh-CN}/objects.commerce.tscontent/docs/sales/quotes×3 localescontent/docs/revenue/products×3 localescontent/docs/whats-new×3 localestest/line-item-conventions.test.ts,test/undeclared-key-probe.test.tsCHANGELOG.mdkeeps its history. After this PR,git grep -n tax_rate -- srcreturns 0 hits, and the built artifact carries 0"tax_rate"(base: 5).Acceptance 2: one pin, red before and green after
The new test in
test/hooks-runtime.test.ts(quote_total_rollup) uses QTE-0007's shape: a quote at 10% discount, an untaxed line of 2 × 5,000, and a line of 1 × 1,000 at 10% line discount. The rep then sets a 5% rate on the second line. It asserts that the line totals equal the header subtotal (10,900), that the total is 9,810, and that the rate write is refused as['INVALID_FIELD', 400].Red on the unfixed tree (HEAD
0f84c9c7, only the test file modified), under the shared lock:It is green after the fix, inside the full
pnpm testrun below.Re-pointed tests
line-item-conventions: "quote line total still applies tax on top" becomes "quote line total is its subtotal". Both formulas evaluate to 360 on 4 × 100 at 10%.undeclared-key-probe: control D needed a key that is declared somewhere and undeclared oncrm_opportunity_line_item. It moves from the quote line'stax_ratetocrm_quote.tax: it is the same key, with the same value 10, and the stored value is read back. The R81 report suggestedcrm_quote(the lookup) instead. I did not use it because a lookup's value must be a real quote id, which breaks the same-key-same-value control and makes it depend on reference semantics. All 3 drivers pass.What an existing install that filled a line rate sees (measured)
0f84c9c7and booted it on a scratch SQLite file.tax_rate: 5. The base reproduces the card: line B total 945, lines 10,945, header subtotal 10,900, total 9,810, tax null.9b4516d7) on the same file.[schema-drift] crm_quote_line_item.tax_rate: column exists in the database but not in metadata (orphaned) — "os migrate apply --allow-destructive" to drop it.total_price900, and the response has notax_ratekey. Line A is 10,000.PATCHline B{tax_rate: 5}{"error":"Unknown field 'tax_rate' on object 'crm_quote_line_item'","code":"INVALID_FIELD"}os migrate plan✗ crm_quote_line_item.tax_rate [drop_column]. It is the only destructive change.os migrate unmapped-columns --object crm_quote_line_item --jsoncolumns: [{column: tax_rate, actual: float}], 22 rows; line B readstax_rate: 5, every other row reads 0.The changeset carries this: the line's total changes (945 → 900), the quote total does not, and the stored rates stay readable until
os migrate apply --allow-destructivedrops the column.Browser check on a fresh DB
I ran
pnpm devon port 4001 against a fresh database and signed in asadmin@objectos.ai. Then I wrote QTE-0007's shape over REST:INVALID_FIELD, so I wrote line B without the rate./opt/pw-browsers/chromium):{"rows":2,"sumLineTotal":10900,"sumLineSubtotal":10900,"headerSubtotal":10900,"headerTotal":9810,"agree":true}.Gates
All gates ran on
fe3f05ee, under the shared lock, with each exit captured before any pipe:pnpm validate✓ Validation passed,Data: 18 Objects 357 Fieldspnpm typecheckpnpm lintpnpm lint:i18n-gate✓ i18n lint gate: 0 i18n/missing-* issuespnpm hygiene✓ source hygiene cleanpnpm hygiene:tokens✓ source token ratchet cleanpnpm buildpnpm testTest Files 179 passed (179),Tests 3691 passed, 1 skipped (3692);VERDICT command-exit 0hygiene:tokens,src/revenue:Deviations and judgment calls
docs/STATUS.mdwas not in the claimed file surface. Its validator transcript goes from 358 to 357 Fields, becausetest/docs-declared-versions.test.tspins it against the registered stack. The 3.1.0 precedent did the same, and the build transcript confirms 357.1486ec0c's. The ruling's "whats-new" item is the 3.1.0 entry that says tax "is actually computed" on "the quote line item's own rate". That entry keeps its dated text and gains a forward note in all three locales: a later release retires the line rate too.crm_quote.tax, not thecrm_quotelookup R81 suggested. The reason is given above.Acceptance notes (not changed here)
relatedListColumnsis unchanged.10000beside5,000.00for Sales Price). This predates the change and is visible in both base and head.docs/feature-inventory.mdQUO-007 still says 行级税率公式 (line-level tax-rate formula). This PR makes that false, but branding: replace the app icon with the flat "H" mark and align primaryColor to its orange (#EA580C) — one asset that every surface renders (maintainer ruling 2026-10-12) #2066's claim holds that file, so it is untouched here. It is reported to the seat.Generated by Claude Code