Skip to content

fix(pricing): CEO can repair PCR factory, origin country and line name - #1283

Open
waritctd wants to merge 3 commits into
developfrom
fix/pcr-ceo-factory-origin-rename
Open

waritctd wants to merge 3 commits into
developfrom
fix/pcr-ceo-factory-origin-rename

Conversation

@waritctd

@waritctd waritctd commented Nov 10, 2026 •

Copy link
Copy Markdown
Owner

Why

Prod PCR-2026-0005: sales typed the factory as "Padana marmoker". CEO costing failed with "ไม่พบ factory config", then with "ไม่มีประเทศต้นทาง". The only way out was a hand-written DB fix. This PR stops typed factory names getting in, and lets the CEO repair all of these on the page.

What changes

  1. Typed factory normalisation: PricingRequestService#resolveItem matches a typed factory to the master price_catalog.factories name, ignoring case and spaces. With no match it saves null, and Import picks the factory with the existing "ระบุโรงงาน" picker.
  2. CEO re-points a factory quote: PUT /api/pricing-requests/{id}/factory-quotes/{fqId}/factory {factoryId}.
    • Rewrites the quote's factory, its lines, and the deal lines that still carry the old name. Import's prices are unchanged.
    • Recomputes cost when a review is open.
    • Also allowed at READY_FOR_CEO_REVIEW with no decision yet (save only). That was the real incident: startReview itself 422'd.
  3. Origin country:
    • LandedCostCalculator falls back to the sales-entered origin_country (อิตาลี/สเปน/จีน → IT/ES/CN) when the line has no catalog country.
    • A line whose fallback origin has no freight band is set aside as uncostable instead of failing the whole costing.
    • The CEO can set the country: PUT …/items/{itemId}/origin-country.
  4. CEO renames a line: PUT …/items/{itemId}/name {brand, model}.
    • Model is capped at 80 to match ticket_item.model.
    • Synced to the deal line only while it still has the old pair.

The three actions:

UI: inline fix forms on the CEO pricing page (prototype reviewed with Ploy). SearchableCombobox gains disabled options. Both API digests were regenerated (+3 operations).

Test coverage

Every new test was shown failing on an assertion before the fix. A reviewer reverted key changes and saw them go red.

  • PricingRequestServiceTest:
    • unknown typed factory → null
    • " padana " → "Padana"
    • exact name kept
    • blank → null
  • LandedCostCalculatorFormulaIntegrationTest:
    • re-point rewrites quote, lines and ticket item, then costing succeeds
    • leaves a ticket item with another factory alone
    • 404/409 cases: unknown factory, same name, duplicate name or factory_id, foreign quote
    • real incident path: startReview 422 → fix with no decision → startReview succeeds
    • fix survives a recompute failure on a second broken quote
    • resolved name stays consistent
    • origin fallback; อื่นๆ stays uncostable; catalog country wins
    • no-freight-band line set aside alone
    • set origin: saves and recomputes; refuses stock/blank/unknown
    • rename: trims, needs a name, 81-char model → 400, item must belong
    • rename syncs the ticket item only while unchanged, including clearing brand
    • non-CEO 403 on all three; 409 after return-to-import
  • PricingFactoryQuoteCostingIntegrationTest: the free-text-factory fixture now seeds a master factory.
  • PricingRequestDetailPage.test.jsx, "CEO fixes during price review":
    • factory fix form shows with context and sends the right call, including before a decision exists
    • hidden for master-factory quotes or a non-editable decision
    • disabled option for an already-used factory
    • country select defaults to the sales value
    • hidden when the line has a catalog link
    • rename saves the trimmed pair
    • costingError renders inline with a neutral toast

Results (throwaway local Postgres, fork 1; Docker hung):

Suite Result
LandedCostCalculatorFormulaIntegrationTest 31/31
PricingDecisionIntegrationTest 28/28
PricingFactoryQuoteCostingIntegrationTest 46/46
PricingRequestServiceTest 116/116
frontend src/features/pricingRequests 768/768
frontend src/api 668/668

PricingDecisionIntegrationTest and PricingFactoryQuoteCostingIntegrationTest were last run one small change earlier (the rename COALESCE), which neither class touches.

Deliberately not covered

  • No concurrent-save race test (the lock exists).
  • No pre-decision UI for origin country (API only).
  • The freight probe checks each line alone, not the combined group quantity.
  • ไทย-สต็อก is not mapped to TH (owner decision pending).
  • Testcontainers path not rerun.

Deploy note

Backend change: it reaches prod only with a new backend image. A cleared brand is written as '' on ticket_item (NOT NULL column).

Update: ไทย-สต็อก, CEO freight, code-review fixes (commit 4457494)

  • ไทย-สต็อก (no catalogue country): costed as a domestic purchase with the owner's formula TC = [(C+i+F)(1+T/100)×1.07]+S and i = F = T = S = 0, so ×1.07 is kept (Thai prices are pre-VAT, owner confirmed). These lines are left out of freight grouping and clearance, so the IT/ES/CN numbers don't change.
  • CEO freight for table gaps: PUT …/items/{itemId}/freight {freightThb} takes the total THB for the line. It applies only when the group's table lookup has no band.
    • Behaviour change: a no-band line is now uncostable per line instead of 422ing the whole costing.
  • V207 (additive, nullable): pricing_request_item.manual_freight_thb and pricing_costing_item.uncostable_code. The UI picks the CEO fix from the code, not from the Thai text.
  • All 10 /code-review findings fixed:
    1. The fix shown is driven by the reason code.
    2. Rename sync works when the brand is blank or null.
    3. The "no freight band" decision is made per shipment group.
    4. History events name the line position, not the database id.
    5. 409 for setting an origin on a catalogue-country line.
    6. The reason shows the Thai origin name, not the ISO code.
    7. The mock caps model at 80 characters, like the backend.
    8. Factory names are read once per save.
    9. The CeoFixResult Javadoc sits on its own record.
    10. The fix guard reads the request once.
  • Tests: the Opus reviewer hand-checked the money and reran:
Suite Result
LandedCostCalculatorFormulaIntegrationTest 40/40
PricingDecisionIntegrationTest 28/28
PricingFactoryQuoteCostingIntegrationTest 46/46
PricingRequestServiceTest 117/117
CeoReviewNoticeTest 10/10
frontend 1439/1439

With the domestic and manual-freight branches disabled, 6 new tests go red.

  • Known limits:
    • When the group lookup fails, the retry costs the remaining lines as if they shipped alone. This errs toward over-costing.
    • "No band" is detected by the Thai message prefix.
    • Rows costed before V207 show no fix form until they are recalculated.
    • Thai-stock lines show no formula breakdown.
    • The CEO's manual freight is not copied to a revised PCR.

…e on a PCR

- Normalise a typed PCR line factory to the master factory name (case/space-insensitive), else leave it blank for Import's picker.
- CEO can re-point a factory quote (and its lines and deal lines) to a master factory, also before review starts.
- Costing falls back to the sales-entered origin country; the CEO can set it when missing. A line with no freight band is set aside instead of failing the whole costing.
- CEO can rename a line (brand/model), synced to the deal line.
- Fixes persist when another problem still blocks costing; the remaining error is shown inline.
@vercel

vercel Bot commented Nov 10, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
demo-glr Ready Ready Preview Nov 10, 2026 8:00pm UTC

Request Review

… fixes

- ไทย-สต็อก lines cost as a domestic purchase: same formula with insurance, freight, duty, clearance = 0 (x1.07 kept); excluded from freight grouping and clearance.
- CEO can enter a line's total freight when the freight table has no band (V207: pricing_request_item.manual_freight_thb); a no-band line is now uncostable per line instead of failing the whole costing.
- Structured uncostable_code (V207) drives which CEO fix the page offers.
- Code-review fixes: rename sync with blank brand, event line positions, 409 for origin on catalogue-country lines, Thai origin name in reason, mock model limit, one factory-name read per save, Javadoc placement, single request read in the fix guard.

This branch was successfully deployed

1 active deployment
Preview — 403f4079 Deployed Nov 10, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant