Дата: 2025-12-28
Версия: 1.0.0
Статус: ✅ УТВЕРЖДЕНО
┌──────────────────────────────────────────────────────────┐
│ 1. PIM (каталог товаров) │
│ │
│ pim_products: │
│ - base_price ← Цена бренда │
│ - fixed_price ← Цена поставщика (финальная) │
│ │
│ ВСЁ. Других цен в PIM НЕТ. │
└──────────────────┬───────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────┐
│ 2. PRICE_COST_RULES (скидки поставщиков) │
│ │
│ PRIMARY KEY (supplier, brand) │
│ - supplier ← ПироСнаб, Другой │
│ - brand ← JF Pyro, Гордеев │
│ - discount_percent ← 20% │
└──────────────────┬───────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────┐
│ 3. PRICE_SALE_RULES (правила наценки) │
│ │
│ Иерархия: product → category → brand → global │
│ - level ← product/category/brand/global │
│ - entity_id ← article/category/brand/NULL │
│ - price_type ← retail/wholesale/marketplace │
│ - markup ← 50.00% │
│ - markup_base ← base_price/cost_price/fixed_price │
└──────────────────┬───────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────┐
│ 4. PRICE_SUPPLIER_COST (реальные закупки) │
│ │
│ - supplier ← ПироСнаб │
│ - article ← JF-DM30 │
│ - real_cost_price ← 820.00 (реальная цена покупки) │
│ - quantity_received ← 50 │
│ - received_at ← 2025-12-20 │
│ - warehouse ← Ольгино, Гатчина │
└──────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────┐
│ 5. PRICE_HISTORY (история изменений) │
│ │
│ - article ← JF-DM30 │
│ - price_field ← base_price/fixed_price │
│ - old_value ← 1000.00 │
│ - new_value ← 1050.00 │
│ - changed_at ← 2025-12-28 10:30 │
│ - changed_by ← import_1c/admin │
└──────────────────────────────────────────────────────────┘
Функция: calculate_cost_price(article)
IF base_price IS NOT NULL THEN
-- Взять скидку поставщика на бренд
discount = SELECT discount_percent
FROM price_cost_rules
WHERE supplier = 'ПироСнаб' AND brand = 'JF Pyro'
cost_price = base_price × (1 - discount/100)
ELSE IF fixed_price IS NOT NULL THEN
cost_price = fixed_price -- финальная цена поставщика
ELSE
cost_price = NULL
END IF
Пример:
base_price = 1000 ₽
discount = 20% (ПироСнаб → JF Pyro)
cost_price = 1000 × (1 - 20/100) = 800 ₽
Функция: calculate_retail_price(article)
1. product → JF-DM30 (высший приоритет)
2. category → Цветной дым
3. brand → JF Pyro
4. global → Розница (по умолчанию)
Поле markup_base в правиле:
| markup_base | База | Формула |
|---|---|---|
| base_price (по умолчанию) | Цена бренда | retail = base_price × (1 + markup/100) |
| cost_price | Себестоимость | retail = cost_price × (1 + markup/100) |
| fixed_price | Цена поставщика | retail = fixed_price × (1 + markup/100) |
Пример 1 (от base_price):
Правило: global, markup=50%, markup_base='base_price'
base_price = 1000 ₽
retail_price = 1000 × 1.5 = 1500 ₽
Пример 2 (от cost_price):
Правило: category='Дым', markup=70%, markup_base='cost_price'
base_price = 1000 ₽
cost_price = 800 ₽ (скидка 20%)
retail_price = 800 × 1.7 = 1360 ₽
Формула по умолчанию:
retail_price = GREATEST(
base_price × 1.2, -- вариант А: +20% к базовой цене
cost_price × 2 -- вариант Б: двойная себестоимость
)
Пример:
base_price = 1000 ₽
cost_price = 800 ₽
Вариант А: 1000 × 1.2 = 1200 ₽
Вариант Б: 800 × 2 = 1600 ₽
retail_price = MAX(1200, 1600) = 1600 ₽ ✓
INSERT INTO price_sale_rules (level, entity_id, price_type, markup, markup_base) VALUES
-- Глобальное правило (по умолчанию от base_price)
('global', NULL, 'retail', 50.00, 'base_price'),
('global', NULL, 'wholesale', 30.00, 'base_price'),
('global', NULL, 'marketplace', 45.00, 'base_price'),
-- По брендам (от base_price)
('brand', 'JF Pyro', 'retail', 55.00, 'base_price'),
('brand', 'Гордеев', 'retail', 48.00, 'base_price'),
-- По категориям (можно от cost_price)
('category', 'Цветной дым', 'retail', 60.00, 'cost_price'),
('category', 'Батареи салютов', 'wholesale', 25.00, 'cost_price'),
-- Конкретный товар (высший приоритет)
('product', 'JF-DM30', 'retail', 65.00, 'base_price');
| Сущность | Что это | Примеры |
|---|---|---|
| БРЕНД (brand) | Производитель | JF Pyro, Гордеев, КТС |
| ПОСТАВЩИК (supplier) | Дистрибьютор | ПироСнаб, Другой |
| СКЛАД (warehouse) | Место хранения | Ольгино, Гатчина |
Скидки: Поставщик даёт скидку на бренд (не на склад!)
INSERT INTO price_cost_rules (supplier, brand, discount_percent) VALUES
('ПироСнаб', 'JF Pyro', 20.00),
('ПироСнаб', 'Гордеев', 25.00),
('ПироСнаб', 'КТС', 18.00),
('Другой поставщик', 'JF Pyro', 15.00),
('Другой поставщик', 'Гордеев', 22.00);
Формула:
# 1. Рассчитать retail_price из модуля прайс
retail_price = calculate_retail_price(article) # 1287
# 2. Добавить +1%
feyerverk_price = retail_price × 1.01 # 1299.87
# 3. Округлить до 10
final_price = round(feyerverk_price / 10) × 10 # 1300
Где применяется: В скрипте синхронизации sync_pim_to_webasyst.py
SELECT
article,
name,
base_price,
fixed_price,
calculate_cost_price(article) as cost_price,
CASE
WHEN base_price IS NOT NULL THEN 'base_price'
WHEN fixed_price IS NOT NULL THEN 'fixed_price'
ELSE 'none'
END as price_source
FROM pim_products;
SELECT
article,
name,
base_price,
cost_price,
calculate_retail_price(article) as retail_price,
CASE
WHEN cost_price > 0 THEN
ROUND((retail_price / cost_price - 1) × 100, 2)
ELSE NULL
END as actual_markup_percent
FROM pim_products;
Файл: migrations/029_simplify_price_fields_v2.sql
Что делает:
fixed_price (если нет)fixed_cost_price → fixed_priceis_premium → tier (эконом/стандарт/премиум)cost_price, fixed_cost_price, target_cost, is_premium, premium_reasonmarkup_base в price_sale_rulescalculate_cost_price(article)calculate_retail_price(article) с поддержкой markup_basev_products_with_costv_retail_pricesПрименение:
cd /opt/claude-workspace/projects/org/pirotehnika/app/pim
./apply_price_migration.sh
Время: ~30 секунд
Риски: Минимальные (есть автоматический бэкап)
| Документ | Описание |
|---|---|
| docs/PRICING_PLATFORM.md | Концепция платформы (2 цены в PIM) |
| docs/PRICING_INBOUND.md | Входящие цены (base_price, fixed_price) |
| docs/PRICING_OUTBOUND.md | Исходящие цены (retail, wholesale, marketplace) |
| PRICE_FIELDS_REFACTORING.md | План рефакторинга |
| MIGRATION_029_REVIEW.md | Проверка миграции |
MAX(base_price × 1.2, cost_price × 2)Версия: 1.0.0
Утверждено: 2025-12-28