projects/org/pirotehnika/app/pim/docs/PRICING_PLATFORM.md

Ценообразование в PIM — Концепция платформы

Версия: 2.0.0
Дата: 2025-12-28
Проект: pirotehnikaPIM


Что это

PIM (Product Information Management) — каталог товаров с базовыми характеристиками.

Цены в PIM: ТОЛЬКО входящие цены (от производителей и поставщиков).

Расчётные цены: В отдельном модуле price_* (НЕ часть PIM).


Архитектура

┌──────────────────────────────────────────────────────────┐
│                    PIM (каталог)                         │
│                                                          │
│  pim_products:                                           │
│    - article                                             │
│    - name                                                │
│    - brand                                               │
│    - category                                            │
│    - base_price    ← Базовая цена бренда                │
│    - fixed_price   ← Фиксированная цена поставщика      │
│                                                          │
│  ВСЁ. Других цен в PIM НЕТ.                             │
└──────────────────────────────────────────────────────────┘
                           │
                           ▼
┌──────────────────────────────────────────────────────────┐
│              МОДУЛЬ ПРАЙС (price_*)                      │
│                                                          │
│  price_cost_rules          ← Скидки поставщиков         │
│  price_sale_rules          ← Правила наценки            │
│  price_supplier_cost       ← Реальная себестоимость     │
│  price_history             ← История изменений          │
│                                                          │
│  Функции calculate_*()     ← Расчёты на лету            │
│  VIEW v_retail_prices      ← Представления              │
└──────────────────────────────────────────────────────────┘

Два поля цен в pim_products

1. base_price — Базовая цена бренда

Что это:
- Базовая цена, установленная брендом/производителем
- Одинаковая для всех, кто продаёт этот бренд
- НЕ зависит от конкретного поставщика

Примеры:

JF-DM30 (JF Pyro):     base_price = 1000 ₽
MC150 (Гордеев):       base_price = 2000 ₽
CC2600 (КТС):          base_price = 1800 ₽

Источник:
- Прайс-лист производителя
- Импорт из 1С
- Ручной ввод

Когда заполнять:
- Есть официальный прайс бренда
- Товар от известного производителя


2. fixed_price — Фиксированная цена поставщика

Что это:
- Финальная цена от конкретного поставщика
- Может отличаться у разных поставщиков
- Скидка УЖЕ учтена (или её нет)

Примеры:

OLGINO-CC2600:        fixed_price = 850 ₽  (от Ольгино)
PIROSNAB-VH100:       fixed_price = 920 ₽  (от ПироСнаб)

Источник:
- Прайс-лист поставщика (Excel)
- API поставщика
- Счёт от поставщика

Когда заполнять:
- Поставщик НЕ даёт базовый прайс бренда
- Покупаем у посредника
- Разовая закупка


Логика приоритета

Если заполнены ОБА поля:

-- Приоритет у base_price
price = COALESCE(base_price, fixed_price)

Почему base_price приоритетнее:
- Базовая цена бренда — более стабильная
- Используется для расчётов в модуле прайс
- fixed_price — это частный случай (конкретный поставщик)

Можно ли оба NULL:
- ❌ НЕТ — валидация запрещает
- Хотя бы ОДНО поле должно быть заполнено


Валидация

-- Обязательность
ALTER TABLE pim_products
ADD CONSTRAINT check_price_exists
CHECK (base_price IS NOT NULL OR fixed_price IS NOT NULL);

-- Положительные значения
ALTER TABLE pim_products
ADD CONSTRAINT check_base_price_positive
CHECK (base_price IS NULL OR base_price > 0);

ALTER TABLE pim_products
ADD CONSTRAINT check_fixed_price_positive
CHECK (fixed_price IS NULL OR fixed_price > 0);

Обновление цен

Импорт из 1С → base_price

# import_from_1c.py
def import_product(product_data):
    article = product_data['Артикул']
    brand_price = product_data['Цена']  # из прайса бренда

    db.execute("""
        INSERT INTO pim_products (article, name, brand, base_price)
        VALUES (?, ?, ?, ?)
        ON CONFLICT (article) DO UPDATE
        SET base_price = EXCLUDED.base_price
    """, article, name, brand, brand_price)

Импорт прайса поставщика → fixed_price

# import_price.py
def import_supplier_price(file_path, supplier):
    for row in read_excel(file_path):
        article = row['Артикул']
        price = row['Цена']

        db.execute("""
            UPDATE pim_products
            SET fixed_price = ?
            WHERE article = ?
        """, price, article)

История изменений

Таблица: price_history (в модуле прайс)

CREATE TABLE price_history (
    id SERIAL PRIMARY KEY,
    article VARCHAR(100),
    price_field VARCHAR(20),    -- 'base_price' или 'fixed_price'
    old_value NUMERIC(10,2),
    new_value NUMERIC(10,2),
    changed_at TIMESTAMP DEFAULT NOW(),
    changed_by VARCHAR(100)
);

Триггер:

CREATE TRIGGER price_change_log
AFTER UPDATE ON pim_products
FOR EACH ROW
WHEN (OLD.base_price IS DISTINCT FROM NEW.base_price
   OR OLD.fixed_price IS DISTINCT FROM NEW.fixed_price)
EXECUTE FUNCTION log_price_change();

Связь с модулем прайс

PIM предоставляет:
- base_price — базовую цену бренда
- fixed_price — фиксированную цену поставщика

Модуль прайс использует:
- base_price ИЛИ fixed_price для расчёта себестоимости
- Применяет скидки из price_cost_rules
- Рассчитывает наценки из price_sale_rules
- Возвращает retail_price, wholesale_price и т.д.

Документация модуля прайс:
- PRICING_INBOUND.md — входящие цены
- PRICING_OUTBOUND.md — исходящие цены


API

Получить товар с ценами

GET /api/products/{article}

Ответ:

{
  "article": "JF-DM30",
  "name": "Цветной дым JF-DM30",
  "brand": "JF Pyro",
  "base_price": 1000.00,
  "fixed_price": null
}

Обновить base_price

PATCH /api/products/{article}
Content-Type: application/json

{
  "base_price": 1050.00
}

Обновить fixed_price

PATCH /api/products/{article}
Content-Type: application/json

{
  "fixed_price": 920.00
}

Быстрые команды

# Товары с base_price
psql -c "SELECT article, name, base_price FROM pim_products WHERE base_price IS NOT NULL LIMIT 10;"

# Товары с fixed_price
psql -c "SELECT article, name, fixed_price FROM pim_products WHERE fixed_price IS NOT NULL LIMIT 10;"

# Товары с обеими ценами
psql -c "SELECT article, base_price, fixed_price FROM pim_products WHERE base_price IS NOT NULL AND fixed_price IS NOT NULL;"

# Товары БЕЗ цен (ошибка!)
psql -c "SELECT article, name FROM pim_products WHERE base_price IS NULL AND fixed_price IS NULL;"

Примеры

Товар с base_price

INSERT INTO pim_products (article, name, brand, base_price)
VALUES ('JF-DM30', 'Цветной дым JF Pyro 30 сек', 'JF Pyro', 1000.00);

Товар с fixed_price

INSERT INTO pim_products (article, name, supplier, fixed_price)
VALUES ('OLGINO-CC2600', 'Фонтан Золотой дождь', 'Ольгино', 850.00);

Товар с обеими ценами

INSERT INTO pim_products (article, name, brand, base_price, fixed_price)
VALUES ('MC150', 'Батарея MC150', 'Гордеев', 2000.00, 1600.00);

-- Для расчётов будет использоваться base_price = 2000

Связанные документы

Документ Описание
PRICING_INBOUND.md Как импортировать base_price и fixed_price
PRICING_OUTBOUND.md Как модуль прайс рассчитывает продажные цены
../ARCHITECTURE.md Общая архитектура PIM

Версия: 2.0.0