Persist V3 catalog advanced calculation settings

This PR fixes the V3 Product Catalog drawer so AdvancedSection calculation controls round-trip through save, merge, and reopen.

Author: @mehulshinde PR: dealops#6439 Issue: DEA-7029 Status: open Area: V3 catalog drawer Files: 7 Diff: +455 / -30

What it fixes

Disable volume ramp, tiered pricing, ramp strategy, blending, proration, and fixed-volume settings now survive save and reopen in V3 attribute-based catalog orgs.

What it adds

A shared converter at @dealops/types/v2/advancedCalcAttrs defines the V2 form ↔ canonical V3 attribute mapping once.

What it protects

The server clear list remains allowlisted. New clearable calc keys can be deleted, but arbitrary structural fields still cannot be stripped.

Verification

34/34 productDto.test.ts tests pass locally, including regression coverage for write → read round trips.

AdvancedSection form
Client DTO converter
Shared types converter
v3Admin merge / clear
Quote editor behavior

1. Why this exists

Problem today

In V3 attribute-based catalog orgs, AdvancedSection calc settings could look saved in the admin drawer but reopen with stale or missing values.

  • disableVolumeRamp did not reliably persist.
  • Cleared strategy fields could survive server-side key-wise merge.
  • V3 proration could be written but read through the older V2 key.
Operational impact

The quote editor depends on these persisted attributes to decide ramp behavior and related calculation affordances.

  • Admins in Merge Test Org saw advanced settings revert.
  • Quote ramp behavior could be incorrect after catalog edits.
  • The bug sat at the V2-shaped form ↔ V3-shaped catalog boundary.
Scope: This is not a schema migration. It is a V3 Product Catalog drawer round-trip fix across client DTO conversion, shared type helpers, and the existing v3Admin tRPC update path.

2. What changes

AdvancedSection
Form uses V2-shaped fields like disableVolumeRamp.
Shared converter
Maps to canonical V3 attrs and back with one polarity rule.
productDto.ts
Writes set, computes clear, hydrates drawer state.
v3Admin update
Merges content, then deletes allowlisted clear keys.
Quote editor
Reads persisted calc attrs for ramp and pricing behavior.

Key mapping

AdvancedSection field Canonical V3 attribute Behavior
disableVolumeRamp volumeRampEnabled Inverted polarity: disabling ramp writes volumeRampEnabled = false.
disableTieredPricing tieredPricingEnabled Inverted polarity: disabling tiered pricing writes tieredPricingEnabled = false.
defaultFlattenRampStrategy flattenRampStrategy Set when chosen; explicitly cleared when emptied.
defaultBlendTieredStrategy blendTieredStrategy Set when chosen; explicitly cleared when emptied.
prorateFirstAndLastMonth proration Maps count_fractional_monthfractional_month and count_full_monthfull_month.
fixedVolume fixedVolume Set when present; explicitly cleared when turned off.

Meaningful file changes

packages/types/v2/advancedCalcAttrs.ts
New shared converter for form-shaped advanced calc settings and V3 system attributes.
+140
apps/client/.../ProductCatalog/utils/productDto.ts
Uses the shared converter for both save payloads and drawer hydration.
+54 / -6
apps/server/src/trpc/router/v3Admin/catalog.ts
Extends clearAttributeKeys allowlist to the three clearable calc attributes.
+19 / -6
apps/client/.../__tests__/productDto.test.ts
Adds round-trip regression coverage for DEA-7029.
+190 / -17
apps/server/src/dealops3/__tests__/catalogWrite.test.ts and updateProductSchema.test.ts
Covers merge-then-clear behavior and schema acceptance for new clear keys.
+51 / -1
packages/types/package.json
Exports the new shared helper module.
+1

3. How it works

Single source of truth

v3CalcAttrsFromAdvanced serializes form values into two buckets:

  • set: values to write into systemAttributes
  • clear: V3 keys that must be deleted after merge
Inverse read path

advancedFromV3CalcAttrs hydrates drawer values from stored attributes using a caller-provided read(key).

That keeps legacy fallback behavior inside productDto.ts intact.

Safe delete path

attributeKeysToClear now appends only the converter’s clearable calc keys.

The server schema must allow the same keys before the mutation accepts them.

Before / after: save semantics

Before

Omitting a cleared value was not enough.

base.systemAttributes = {
  flattenRampStrategy: "last",
  fixedVolume: 1
}

payload.systemAttributes = {
  name: "Widget"
}

merge(base, payload)
// stale flattenRampStrategy and fixedVolume survive
After

Clearable calc attributes are named explicitly.

payload.clearAttributeKeys = [
  "flattenRampStrategy",
  "blendTieredStrategy",
  "fixedVolume"
]

withClearedAttributes(merged, payload.clearAttributeKeys)
// stale calc attrs are removed

Polarity is preserved

Form field

disableVolumeRamp: true

AdvancedSection speaks in disabled toggles.

V3 attribute

volumeRampEnabled: false

V3 stores canonical enabled flags, so the converter intentionally inverts booleans.

Hydration path used by the drawer

Read chain covered by tests

The new regression test exercises the same chain the drawer uses:

dtoToFamilyAndSku(dto)
  → buildProductSKUFormInitialData(product, productSKU)
  → advanced

This catches mismatches where save writes a V3 key but reopen reads the old V2 key.

4. What it doesn't change

Explicit non-goals

5. Risks / rollback / open questions

Risk: The important compatibility point is the shared converter export from @dealops/types/v2/advancedCalcAttrs. Consumers must have the package build output available, and the monorepo package export was updated for that.
Rollback

Revert the PR to restore prior V3 catalog behavior.

No data migration is introduced, so rollback is code-only. Any attributes already saved by the fixed path are ordinary V3 system attributes.

Verification status
  • Passing: productDto.test.ts 34/34 locally.
  • Added: server tests for schema allowlist and merge-clear behavior.
  • Blocked: full client Jest suite by pre-existing @dealops/types / src/telemetry environment issue.
  • Recommended: manual QA in Merge Test Org before merge.
Reviewer focus: Confirm the V2-shaped field names, V3 attribute names, and clearable-key allowlist stay aligned across advancedCalcAttrs.ts, productDto.ts, and v3Admin/catalog.ts.