projects/org/pirotehnika/app/pim/PRICING_FINAL_CONCEPT.md

Финальная концепция ценообразования PIM

Дата: 2025-12-28
Версия: 1.0.0
Статус: ✅ УТВЕРЖДЕНО


Архитектура (5 компонентов)

┌──────────────────────────────────────────────────────────┐
              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                   
└──────────────────────────────────────────────────────────┘

Расчёт себестоимости (cost_price)

Функция: 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 ₽

Расчёт розничной цены (retail_price)

Функция: calculate_retail_price(article)

Шаг 1: Поиск правила по иерархии

1. product    JF-DM30 (высший приоритет)
2. category   Цветной дым
3. brand      JF Pyro
4. global     Розница (по умолчанию)

Шаг 2: Определение базы для наценки

Поле 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)

Шаг 3: Применение наценки

Пример 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 

Шаг 4: Fallback (нет правил)

Формула по умолчанию:

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);

Цены для feyerverk.spb.ru

Формула:

# 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


VIEW для быстрого доступа

v_products_with_cost

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;

v_retail_prices

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;

Миграция 029

Файл: migrations/029_simplify_price_fields_v2.sql

Что делает:

  1. Добавляет fixed_price (если нет)
  2. Мигрирует данные: fixed_cost_pricefixed_price
  3. Мигрирует данные: is_premiumtier (эконом/стандарт/премиум)
  4. Удаляет дубликаты: cost_price, fixed_cost_price, target_cost, is_premium, premium_reason
  5. Добавляет markup_base в price_sale_rules
  6. Создаёт функцию calculate_cost_price(article)
  7. Создаёт функцию calculate_retail_price(article) с поддержкой markup_base
  8. Создаёт VIEW v_products_with_cost
  9. Создаёт VIEW v_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 Проверка миграции

Ключевые принципы

  1. PIM = ТОЛЬКО входящие цены (base_price, fixed_price)
  2. PRICE модуль = ВСЕ расчёты (cost_price, retail_price, wholesale_price)
  3. Скидки от поставщиков на бренды (supplier + brand → discount)
  4. Наценки с иерархией (product → category → brand → global)
  5. Выбор базы для наценки (base_price, cost_price, fixed_price)
  6. Fallback: MAX(base_price × 1.2, cost_price × 2)
  7. Три сущности раздельно: бренд, поставщик, склад

Версия: 1.0.0
Утверждено: 2025-12-28