Files
QuoteForge/bible-local/decisions/2026-08-12-project-export-per-config-sale-uplift.md
T
Mikhail ChusavitinandClaude Sonnet 5 c83e4ec9f9 fix: DDP-экспорт с проекта игнорировал свой аплифт каждой конфигурации
ProjectPricingExportOptions.SaleMarkup всегда был 0 при экспорте с
проекта, поэтому применялся единый дефолт 1,3 вместо сохранённого в
Notes конфигурации значения. buildPricingExportBlock теперь берёт
аплифт из настроек каждой конфигурации — экспорт с проекта идёт по
тому же конвейеру, что и экспорт с конкретной конфигурации.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-12 17:29:25 +03:00

2.7 KiB

Decision: project-level DDP export uses each configuration's own sale uplift

Date: 2026-08-12 Status: active

Context

Two code paths produce the same "Ценообразование" (pricing) CSV: exporting a single configuration from index.html (exportPricingCSV), and exporting an entire project from project_detail.html (exportProjectPOST /api/projects/:uuid/exportExportProjectPricingCSV). Both end up in ExportService.buildPricingExportBlock.

Each configuration's "Аплифт к estimate" (#pricing-uplift-sale in the pricing tab) is saved per configuration, piggybacked into Configuration.Notes as {"pricing_ui":{"sale_uplift":...}} (see serializeConfigNotes/ restorePricingStateFromNotes in index.html). The single-config export sends this value explicitly as sale_markup in the request, so it always applies the right uplift.

The project-level export modal never had an uplift input and never sent sale_markup, so ProjectPricingExportOptions.SaleMarkup was always 0. buildPricingExportBlock used to fall back to a single hardcoded defaultSaleMarkup (1.3) for every configuration in the project, silently ignoring each configuration's own saved uplift — a bulk DDP export did not match what exporting those same configurations individually would produce.

Decision

buildPricingExportBlock resolves the DDP estimate factor per configuration via ProjectPricingExportOptions.effectiveSaleMarkupFactor(cfg):

  • an explicit opts.SaleMarkup (only ever sent by the single-config pricing tab, which knows the live-edited value that may not be saved yet) always wins;
  • otherwise it reads that configuration's own saved uplift from Notes (configSavedSaleUplift);
  • otherwise it falls back to defaultSaleMarkup (1.3), same as before, for configurations that never had an uplift saved.

Both applyDDPMarkup call sites in buildPricingExportBlock (the BOM-driven branch and the plain-items fallback) use this per-configuration factor. Stock/Competitor keep using the fixed stockCompetitorMarkupFactor (1.3) — only the Estimate uplift is configurable.

Consequences

  • Project-level DDP export now goes through the same "pipeline" as single-config export: each row's Estimate is scaled by that configuration's own saved uplift, not a project-wide constant.
  • If a future caller needs to force one uplift across an entire project export regardless of individual configs' saved settings, it must do so explicitly via sale_markup in the request — do not reintroduce a silent global default that overrides saved per-config values.
  • configSavedSaleUplift only reads Notes; it does not fail loudly on malformed/foreign JSON in that field — it just returns 0 and lets the 1.3 fallback apply.