fix(purchasing): preserve unit price precision per company setting (#1236)#1240
fix(purchasing): preserve unit price precision per company setting (#1236)#1240carbon-agent wants to merge 5 commits into
Conversation
…1203) PO unit prices were rounded to 2 decimals, losing precision for fractionally-priced items (metals trading, high-precision manufacturing). The database columns are already unbounded NUMERIC and the zod validators impose no precision limit, so the rounding was purely a front-end formatting artifact: the currency-styled react-aria NumberField inputs inherited the currency's default of 2 fraction digits. - PurchaseOrderLineForm: allow up to 5 fraction digits (min 2) on the item and indirect Unit Price inputs so 0.12345 is entered/stored intact. - PurchaseOrderSummary: format the Unit Price rows with up to 5 fraction digits so the entered precision is visible; line/order totals stay at 2 decimals. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
🚧 Files skipped from review as they are similar to previous changes (3)
📝 WalkthroughWalkthroughPurchase-order unit-price precision is configurable per company, persisted in company settings, and applied to purchasing inputs, summaries, and PDF line-item rendering. Supported values are two through five decimal places, with localized settings labels and descriptions. ChangesPurchase-order precision configuration
Estimated code review effort: 3 (Moderate) | ~30 minutes Sequence Diagram(s)sequenceDiagram
participant SettingsForm
participant CompanySettings
participant PurchaseOrderLoader
participant PurchaseOrderSummary
participant PurchaseOrderPDF
SettingsForm->>CompanySettings: save purchaseOrderUnitPricePrecision
PurchaseOrderLoader->>CompanySettings: read configured precision
PurchaseOrderLoader->>PurchaseOrderSummary: provide precision
PurchaseOrderSummary->>PurchaseOrderSummary: format unit prices
PurchaseOrderPDF->>CompanySettings: read configured precision
PurchaseOrderPDF->>PurchaseOrderPDF: format PDF unit prices
Possibly related issues
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/erp/app/modules/purchasing/ui/PurchaseOrder/PurchaseOrderLineForm.tsx (1)
727-733: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winUse the company base currency for both purchase-order unit-price inputs.
Both fields still prefer
routeData?.purchaseOrder?.currencyCode, so non-base-currency orders can display unit prices under the wrong currency contract.
apps/erp/app/modules/purchasing/ui/PurchaseOrder/PurchaseOrderLineForm.tsx#L727-L733: set the item input currency directly tocompany.baseCurrencyCode.apps/erp/app/modules/purchasing/ui/PurchaseOrder/PurchaseOrderLineForm.tsx#L1019-L1025: apply the same direct base-currency setting to the indirect input.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/erp/app/modules/purchasing/ui/PurchaseOrder/PurchaseOrderLineForm.tsx` around lines 727 - 733, Update both unit-price input formatOptions in PurchaseOrderLineForm.tsx at lines 727-733 and 1019-1025 to set currency directly to company.baseCurrencyCode, removing the purchase-order currency fallback from the item and indirect inputs.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@apps/erp/app/modules/purchasing/ui/PurchaseOrder/PurchaseOrderLineForm.tsx`:
- Around line 727-733: Update both unit-price input formatOptions in
PurchaseOrderLineForm.tsx at lines 727-733 and 1019-1025 to set currency
directly to company.baseCurrencyCode, removing the purchase-order currency
fallback from the item and indirect inputs.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 0a1d659a-7736-4554-9d52-5dea8becc84d
📒 Files selected for processing (2)
apps/erp/app/modules/purchasing/ui/PurchaseOrder/PurchaseOrderLineForm.tsxapps/erp/app/modules/purchasing/ui/PurchaseOrder/PurchaseOrderSummary.tsx
The initial #1203 fix updated the PO line inputs and the expanded summary row, but two surfaces still rendered the unit price at 2 decimals: - PurchaseOrderSummary: the collapsed line badge used the 2-decimal `formatter`; switch it to the existing `unitPriceFormatter` (max 5). - PurchaseOrder PDF: LineItemsBlock formatted the Unit Price column with the shared 2-decimal `numberFormatter`. Add a dedicated `unitPriceFormatter` (max 5) threaded through the block data bag and use it for that cell only; line totals/tax/extended values stay at 2 dp. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…1236) PO unit prices were rounded to 2 decimals (or, after #1203, hardcoded to 5). Replace the hardcoded precision with a company-level Purchasing setting so each company chooses how many decimal places (2-5) to keep for purchase order unit prices — mirroring the quoteLine.unitPricePrecision pattern, applied company-wide. - migration: add companySettings.purchaseOrderUnitPricePrecision INTEGER NOT NULL DEFAULT 5 CHECK IN (2,3,4,5) (default 5 preserves #1203 behavior). - settings: purchaseOrderUnitPricePrecisionValidator + updatePurchaseOrderUnitPricePrecisionSetting; new "Unit Price Precision" card on the Purchasing settings page. - purchase order $orderId loader now returns the configured precision; the PO line form (item + indirect Unit Price inputs), the summary unit-price formatters, and the PO PDF unit-price column all use it instead of a literal 5. The new column isn't in the generated DB types (no local DB to regenerate), so it is read via a narrow cast and written via the existing sanitize/`as any` updater pattern — never by hand-editing generated types. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 13
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@packages/database/supabase/migrations/20260726003450_po-unit-price-precision-setting.sql`:
- Around line 7-9: Regenerate the database types so
Database["public"]["Tables"]["companySettings"] includes
purchaseOrderUnitPricePrecision. In
apps/erp/app/modules/settings/settings.service.ts at lines 1062-1064, remove the
as any suppression; in apps/erp/app/routes/x+/settings+/purchasing.tsx at lines
359-361, remove the structural cast and rely on the regenerated types. The
migration requires no direct change.
In `@packages/locale/locales/de/erp.po`:
- Around line 3555-3556: Complete the German translations for all three new
purchasing precision entries in the locale catalog, including the “Decimal
places” entry by reusing the existing “Dezimalstellen” translation. Run the
Lingui extraction workflow to update the catalog while preserving the translated
msgstr values.
In `@packages/locale/locales/es/erp.po`:
- Around line 3555-3556: Complete the empty Spanish translations in
packages/locale/locales/es/erp.po at lines 3555-3556, 8476-8477, and
13387-13388: set “Decimal places” to “Lugares decimales,” translate the
purchasing precision description into Spanish, and set “Unit Price Precision” to
“Precisión del precio unitario.”
In `@packages/locale/locales/fr/erp.po`:
- Around line 3555-3556: Complete the French translations for all three entries
in packages/locale/locales/fr/erp.po: set the Decimal places entry to
“Décimales”, translate the purchase-order unit-price precision description at
lines 8476-8477, and translate Unit Price Precision at lines 13387-13388 as
“Précision du prix unitaire”; ensure none of these msgstr values remains empty.
In `@packages/locale/locales/hi/erp.po`:
- Around line 3555-3556: Fill the empty Hindi msgstr values for “Decimal
places”, the purchase-order unit-price precision guidance, and “Unit Price
Precision” in packages/locale/locales/hi/erp.po at lines 3555-3556, 8476-8477,
and 13387-13388. After updating these three translations, run the Lingui
extraction workflow to refresh the catalogs.
In `@packages/locale/locales/it/erp.po`:
- Around line 3555-3556: Fill the empty msgstr values for all three new
precision-related entries in the Italian locale, including “Decimal places” and
the entries at the referenced locations, with appropriate Italian translations
so users do not receive English fallbacks.
In `@packages/locale/locales/ja/erp.po`:
- Around line 3555-3556: Complete the Japanese translations for all three newly
added entries, including “Decimal places” and the entries at the referenced
locations, by replacing each empty msgstr with the appropriate Japanese text.
Update them through the Lingui extraction workflow so the locale file remains
consistent with generated translation data.
In `@packages/locale/locales/ko/erp.po`:
- Around line 3555-3556: Complete the three new Korean catalog entries in the
locale extraction workflow: set “Decimal places” to “소수 자릿수”, translate the full
explanatory purchase-order unit-price text, and set “Unit Price Precision” to
“단가 정밀도”. Ensure the generated ko catalog contains non-empty msgstr values for
all three entries.
In `@packages/locale/locales/pl/erp.po`:
- Around line 3555-3557: Complete the Polish translations by replacing the empty
msgstr values for “Decimal places” at
packages/locale/locales/pl/erp.po:3555-3557, the purchase-order unit-price
precision help text at packages/locale/locales/pl/erp.po:8476-8478, and “Unit
Price Precision” at packages/locale/locales/pl/erp.po:13387-13389 with accurate
Polish translations.
In `@packages/locale/locales/pt/erp.po`:
- Around line 3555-3556: Update the Portuguese locale entries for “Decimal
places” and the additional referenced new entries so each has an appropriate
Portuguese msgstr instead of falling back to English. Make the changes through
the project’s Lingui extraction workflow, preserving the generated catalog
format and existing translations.
In `@packages/locale/locales/ru/erp.po`:
- Around line 3555-3556: Complete the empty Russian msgstr entries in
packages/locale/locales/ru/erp.po at lines 3555-3556, 8476-8477, and 13387-13388
by adding accurate Russian translations for “Decimal places,” the purchase-order
precision description, and “Unit Price Precision.”
In `@packages/locale/locales/tr/erp.po`:
- Around line 3555-3556: Translate the three newly added Turkish catalog
entries, including “Decimal places,” its help text, and the setting name, by
filling their empty msgstr values with accurate Turkish translations. Regenerate
or update the Turkish catalog through the Lingui extraction workflow so the
changes are recorded consistently.
In `@packages/locale/locales/zh/erp.po`:
- Around line 3555-3556: Complete the empty Chinese translations for all three
entries in packages/locale/locales/zh/erp.po: translate “Decimal places” at
lines 3555-3556, the purchase-order unit-price precision description at lines
8476-8477, and “Unit Price Precision” at lines 13387-13388. Populate each
corresponding msgstr with accurate, consistent Chinese wording.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: e7ac0c57-5ba8-405e-92fb-8ca8a4807da0
📒 Files selected for processing (22)
apps/erp/app/modules/purchasing/ui/PurchaseOrder/PurchaseOrderLineForm.tsxapps/erp/app/modules/purchasing/ui/PurchaseOrder/PurchaseOrderSummary.tsxapps/erp/app/modules/settings/settings.models.tsapps/erp/app/modules/settings/settings.service.tsapps/erp/app/routes/api+/mcp+/lib/tool-metadata.jsonapps/erp/app/routes/x+/purchase-order+/$orderId.tsxapps/erp/app/routes/x+/settings+/purchasing.tsxpackages/database/supabase/migrations/20260726003450_po-unit-price-precision-setting.sqlpackages/documents/src/pdf/PurchaseOrderPDF.tsxpackages/locale/locales/de/erp.popackages/locale/locales/en/erp.popackages/locale/locales/es/erp.popackages/locale/locales/fr/erp.popackages/locale/locales/hi/erp.popackages/locale/locales/it/erp.popackages/locale/locales/ja/erp.popackages/locale/locales/ko/erp.popackages/locale/locales/pl/erp.popackages/locale/locales/pt/erp.popackages/locale/locales/ru/erp.popackages/locale/locales/tr/erp.popackages/locale/locales/zh/erp.po
🚧 Files skipped from review as they are similar to previous changes (3)
- packages/documents/src/pdf/PurchaseOrderPDF.tsx
- apps/erp/app/modules/purchasing/ui/PurchaseOrder/PurchaseOrderLineForm.tsx
- apps/erp/app/modules/purchasing/ui/PurchaseOrder/PurchaseOrderSummary.tsx
| ALTER TABLE "companySettings" | ||
| ADD COLUMN IF NOT EXISTS "purchaseOrderUnitPricePrecision" INTEGER NOT NULL DEFAULT 5 | ||
| CHECK ("purchaseOrderUnitPricePrecision" IN (2, 3, 4, 5)); |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== migration file =="
if [ -f packages/database/supabase/migrations/20260726003450_po-unit-price-precision-setting.sql ]; then
cat -n packages/database/supabase/migrations/20260726003450_po-unit-price-precision-setting.sql
else
echo "migration file not found"
fi
echo
echo "== settings service relevant lines =="
if [ -f apps/erp/app/modules/settings/settings.service.ts ]; then
sed -n '1045,1070p' apps/erp/app/modules/settings/settings.service.ts | cat -n
else
echo "settings.service.ts not found"
fi
echo
echo "== purchasing route relevant lines =="
if [ -f 'apps/erp/app/routes/x+/settings+/purchasing.tsx' ]; then
sed -n '345,370p' 'apps/erp/app/routes/x+/settings+/purchasing.tsx' | cat -n
else
echo "purchasing.tsx not found"
fi
echo
echo "== generated schema/type references for purchaseOrderUnitPricePrecision =="
rg -n "purchaseOrderUnitPricePrecision|companySettings" packages/dashboard packages/database apps/erp --glob '!node_modules' --glob '!dist' --glob '!build' | head -200 || true
echo
echo "== generated supabase types files =="
find packages/database apps/erp -path '*/supabase*' -type f \( -name '*.ts' -o -name '*.sql' \) | sed -n '1,120p'Repository: crbnos/carbon
Length of output: 37921
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== current changes stat =="
git diff --stat || true
echo
echo "== diff for target files =="
git diff -- packages/database/supabase/functions/lib/types.ts apps/erp/app/modules/settings/settings.service.ts 'apps/erp/app/routes/x+/settings+/purchasing.tsx' packages/database/supabase/migrations/20260726003450_po-unit-price-precision-setting.sql | sed -n '1,260p' || true
echo
echo "== types.ts companySettings Row around relevant slice =="
sed -n '6656,6705p' packages/database/supabase/functions/lib/types.ts | cat -n || true
echo
echo "== deterministic type presence probe =="
python3 - <<'PY'
from pathlib import Path
import re
path = Path("packages/database/supabase/functions/lib/types.ts")
text = path.read_text()
m = re.search(r'companySettings:\s*\{[\s\S]*?\n\s*\},', text)
if not m:
print("companySettings block not found")
else:
block = m.group(0)
print("companySettings block present:", bool(m))
print("purchaseOrderUnitPricePrecision in companySettings Row:", bool(re.search(r'purchaseOrder\b[^,\n]*Precis', block)))
if "updateLeadTimesOnReceipt" in block or "showSupplierReadableId" in block:
print("recent generated columns present:", sorted({k for k in ("updateLeadTimesOnReceipt","showSupplierReadableId","purchasePriceUpdateTiming") if k in block}))
else:
print("generated columns likely stale")
print("purchaseOrderUnitPricePrecision anywhere in types.ts:", bool(re.search(r'purchaseOrderUnitPricePrecision', text)))
PYRepository: crbnos/carbon
Length of output: 3166
Regenerate database types and remove the type suppressions.
generate:types has not been run after adding purchaseOrderUnitPricePrecision, so Database["public"]["Tables"]["companySettings"] is missing the field and the consumers bypass typing. Remove as any in settings.service.ts and the structural cast in purchasing.tsx instead.
📍 Affects 3 files
packages/database/supabase/migrations/20260726003450_po-unit-price-precision-setting.sql#L7-L9(this comment)apps/erp/app/modules/settings/settings.service.ts#L1062-L1064apps/erp/app/routes/x+/settings+/purchasing.tsx#L359-L361
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@packages/database/supabase/migrations/20260726003450_po-unit-price-precision-setting.sql`
around lines 7 - 9, Regenerate the database types so
Database["public"]["Tables"]["companySettings"] includes
purchaseOrderUnitPricePrecision. In
apps/erp/app/modules/settings/settings.service.ts at lines 1062-1064, remove the
as any suppression; in apps/erp/app/routes/x+/settings+/purchasing.tsx at lines
359-361, remove the structural cast and rely on the regenerated types. The
migration requires no direct change.
Sources: Coding guidelines, Learnings
Fill the three newly added purchasing i18n strings ("Decimal places",
"Unit Price Precision", and the precision help text) across all 12
non-English catalogs (de, es, fr, hi, it, ja, ko, pl, pt, ru, tr, zh),
addressing the CodeRabbit review on #1240.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/locale/locales/de/erp.po`:
- Around line 8476-8477: Update the German msgstr for the decimal-places
description to replace “Bruchpreise” with clearer wording such as “Preise mit
mehreren Dezimalstellen,” while preserving the existing meaning and context
about preventing rounding.
In `@packages/locale/locales/hi/erp.po`:
- Around line 8476-8477: Update the Hindi msgstr for the purchase order unit
price decimal-places description so the final clause clearly means decimal
values are not rounded, using wording such as “गोल न किये जाएँ” instead of
language implying conversion to integers.
In `@packages/locale/locales/ko/erp.po`:
- Around line 8476-8477: Update the Korean msgstr for the decimal-places
description to use “구매주문” instead of “발주서” and “소수점 이하 단가” instead of “소수점 가격,”
preserving the existing meaning and guidance about avoiding rounding.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 55b9dcf6-648e-48f7-a47c-8712615d53a7
📒 Files selected for processing (12)
packages/locale/locales/de/erp.popackages/locale/locales/es/erp.popackages/locale/locales/fr/erp.popackages/locale/locales/hi/erp.popackages/locale/locales/it/erp.popackages/locale/locales/ja/erp.popackages/locale/locales/ko/erp.popackages/locale/locales/pl/erp.popackages/locale/locales/pt/erp.popackages/locale/locales/ru/erp.popackages/locale/locales/tr/erp.popackages/locale/locales/zh/erp.po
Address CodeRabbit wording suggestions on #1240: - de: replace "Bruchpreise" with "Preise mit mehreren Dezimalstellen" and tidy phrasing so the setting's purpose is unambiguous. - hi: use "गोल न किया जाए" so it reads as "not rounded" rather than implying conversion to an integer. - ko: use the catalog's established "구매주문" (not "발주서") and "소수점 이하 단가" for natural terminology. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fixes #1236.
Problem
Purchase order unit prices were silently rounded to 2 decimal places even when suppliers quote more precision (metals trading, high-precision parts quote 3–5 dp). The DB stores
supplierUnitPriceas unboundedNUMERICand the zod validators impose no precision limit — the loss was purely a front-end formatting artifact (currency-styledNumberFieldinheriting the currency's default of 2 fraction digits). An interim fix (#1203) hardcoded 5 dp; this PR makes it configurable.Solution
A company-level Purchasing setting for unit price precision, mirroring the
quoteLine.unitPricePrecisionpattern but applied company-wide.companySettings.purchaseOrderUnitPricePrecision INTEGER NOT NULL DEFAULT 5 CHECK IN (2,3,4,5). Default 5 preserves the behavior shipped in Purchase order unit prices are rounded to 2 decimal places (no way to increase precision) #1203.purchaseOrderUnitPricePrecisionValidator+updatePurchaseOrderUnitPricePrecisionSetting; a new Unit Price Precision card on the Purchasing settings page (/x/settings/purchasing).$orderIdPO loader returns the configured precision; the PO line form (item + indirect Unit Price inputs), the PurchaseOrder summary unit-price formatters, and the PurchaseOrder PDF unit-price column all use it instead of a literal5. Line/order totals stay at 2 dp;minimumFractionDigitsstays 2 so round prices still render normally.purchaseOrderLineprice columns are unboundedGENERATEDNUMERIC; only display formatting changed.Note on generated types
There is no local DB in this environment to regenerate
@carbon/databasetypes, so the new column is read via a narrow cast and written via the existingsanitize/as anyupdater pattern (same asupdateLeadTimesOnReceiptSetting) — never by hand-editing generated types. After the migration is applied and types regenerated, the casts become no-ops.Verification
pnpm exec turbo run typecheck --filter=erp— ✅ (note: the app package is namederp;@carbon/erp-appmatches no workspace package)pnpm exec turbo run typecheck --filter=@carbon/documents— ✅pnpm run lint— ✅ (32/32 tasks; only pre-existing unrelated warnings)12.3456in a PO line Unit Price with precision 4 → keeps12.3456; with precision 2 → rounds to12.35.🤖 Generated with Claude Code
Summary by CodeRabbit