# 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` (`exportProject` → `POST /api/projects/:uuid/export` → `ExportProjectPricingCSV`). 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.