feat: накидка по строке в таблице «Цена продажи» + минимальный CSV

Sale-таблица показывает LOT/Описание/Кол-во/Накидка,%/Цена вместо Estimate/
Склад/Конкуренты/Ручная цена; итоговая цена строки = база (raw estimate или
доля от общей «Ручная цена») × (Аплифт к estimate + своя Накидка%). Экспорт
CSV этой таблицы теперь выводит только LOT;Описание;Кол-во;Цена. Buy-таблица
и массовый экспорт по проекту не затронуты — новые поля запроса опциональны
и включаются только кнопкой «Экспорт CSV» у Sale-таблицы.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Mikhail Chusavitin
2026-09-15 16:39:54 +03:00
co-authored by Claude Sonnet 5
parent fb412a4227
commit 7263dd4572
5 changed files with 385 additions and 100 deletions
+31 -1
View File
@@ -62,13 +62,43 @@ Rules:
## Pricing tab layout
The Pricing tab (Ценообразование) has two tables: Buy (Цена покупки) and Sale (Цена продажи).
Their column sets differ (see below); the shared per-LOT row expansion/grouping rules apply to both.
Column order (both tables):
Buy table column order:
```
PN вендора | Описание | LOT | Кол-во | Estimate | Склад | Конкуренты | Ручная цена
```
Sale table column order:
```
PN вендора | Описание | LOT | Кол-во | Накидка, % | Цена
```
Sale per-row pricing (`_saleRowPrice`/`recomputeSalePrices` in `index.html`,
`applySaleRowPricing` in `internal/services/export.go`):
- each row's base is its raw Estimate unit price × qty, unless the table-wide "Ручная
цена" input is set — then the base is that row's proportional share of the manual
total (same distribution as before this existed, last row absorbs the rounding
remainder), overwriting the row's own base rather than filling a separate column;
- `Цена = base × (Аплифт к estimate + row's own Накидка% / 100)` — Накидка is a
per-row percentage, additive to the table-wide uplift, entered directly in the
row's own input; unset rows default to 0% (no change to older configs' totals);
Склад/Конкуренты are not shown or exported for the Sale table;
- per-row Накидка is keyed by `"<vendorPN>::<lot>"` (uppercased, "NONE" when there's
no vendor PN) — plain `lot_name` isn't a safe key since the same LOT can appear
under more than one vendor-PN group (see row expansion rules below) — and persisted
as an additive key, `Notes.pricing_ui.sale_row_markups`, alongside the existing
`sale_uplift`/`sale_custom_price` (see
[decisions/2026-09-15-sale-row-markup-and-minimal-csv.md](decisions/2026-09-15-sale-row-markup-and-minimal-csv.md));
- the Sale table's own "Экспорт CSV" button additionally sends
`minimal_sale_columns: true` + `sale_row_markups`, which collapses
`POST /api/configs/:uuid/export/pricing` output to exactly `LOT;Описание;Кол-во;Цена`
(`ProjectPricingExportOptions.MinimalSaleColumns` in `internal/services/export.go`).
This is scoped to that one request: Buy export and the project-level bulk export
never set it and are unaffected.
Per-LOT row expansion rules:
- each `lot_mappings` entry in a BOM row becomes its own table row with its own quantity and prices;
- `baseLot` (resolved LOT without an explicit mapping) is treated as the first sub-row with `quantity_per_pn` from `_getRowLotQtyPerPN`;