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

Проверка миграции 029: Упрощение полей цен

Дата проверки: 2025-12-28
Проверяющий: Claude Sonnet 4.5
Статус: ✅ ГОТОВА К ПРИМЕНЕНИЮ


Что проверено

1. SQL Миграция (migrations/029_simplify_price_fields.sql)

Структура: ✅ Корректна
- ШАГ 1: Добавление fixed_price (IF NOT EXISTS) ✅
- ШАГ 2: Миграция данных (fixed_cost_price → fixed_price) ✅
- ШАГ 2: Миграция данных (is_premium → tier) ✅
- ШАГ 3: Удаление дубликатов ✅
- ШАГ 4: Функция calculate_cost_price ✅
- ШАГ 5: VIEW v_products_with_cost ✅
- ШАГ 6: Функция calculate_retail_price ✅
- ШАГ 7: VIEW v_retail_prices ✅
- ШАГ 8: Проверка товаров без цен ✅
- ТЕСТЫ: Встроенные проверки ✅

Логика расчёта цен: ✅ Корректна

calculate_cost_price(article):
  IF base_price IS NOT NULL:
    discount = SELECT FROM price_cost_rules WHERE brand = ...
    RETURN base_price * (1 - discount/100)
  ELSE IF fixed_price IS NOT NULL:
    RETURN fixed_price
  ELSE:
    RETURN NULL

Обработка NULL: ✅ Правильно
- COALESCE(v_brand_discount, 0) - защита от NULL
- IF v_cost IS NULL OR v_cost <= 0 - проверка перед расчётом retail

Округление: ✅ Везде ROUND(, 2)

Транзакция: ✅ BEGIN ... COMMIT


2. Скрипт применения (apply_price_migration.sh)

Безопасность: ✅ Есть
- Автоматический бэкап перед миграцией ✅
- Подтверждение пользователя (yes/no) ✅
- ON_ERROR_STOP=1 для psql ✅
- Инструкция по откату в выводе ✅

Информативность: ✅ Отлично
- Показывает текущие поля ДО миграции ✅
- Показывает результаты ПОСЛЕ миграции ✅
- Примеры расчёта цен ✅


3. Документация

PRICING_PLATFORM.md: ✅ Соответствует
- Описывает base_price и fixed_price ✅
- Объясняет два сценария (производитель/поставщик) ✅
- Fallback логика COALESCE корректна ✅
- Нет упоминаний удалённых полей ✅

PRICING_INBOUND.md: ✅ Соответствует
- Процесс импорта из 1С (base_price) ✅
- Процесс импорта прайса (fixed_price) ✅
- Таблица price_cost_rules описана ✅
- Формулы расчёта совпадают с SQL ✅

PRICING_OUTBOUND.md: ✅ Соответствует
- Использует calculate_cost_price() ✅
- Иерархия правил (product→category→brand→global) ✅
- Формулы наценки корректны ✅

PRICE_FIELDS_REFACTORING.md: ✅ Полный
- Анализ текущего состояния ✅
- План миграции ✅
- Инструкции по применению ✅
- Проверка после миграции ✅
- Откат описан ✅


Найденные проблемы

⚠️ Производительность VIEW v_retail_prices (НЕ критично)

Проблема:

CREATE VIEW v_retail_prices AS
SELECT
    calculate_cost_price(p.article) as cost_price,        -- вызов 1
    calculate_retail_price(p.article) as retail_price,    -- вызов 2 (внутри вызывает опять calculate_cost_price)

    CASE
        WHEN calculate_cost_price(p.article) > 0 THEN     -- вызов 3
            ROUND(
                (calculate_retail_price(p.article) / calculate_cost_price(p.article) - 1) * 100, -- вызов 4 и 5
                2
            )
        ELSE NULL
    END as actual_markup_percent
FROM pim_products p;

Количество вызовов для ОДНОЙ строки:
- calculate_cost_price() — 4 раза
- calculate_retail_price() — 2 раза

Решение (опционально):

CREATE VIEW v_retail_prices AS
WITH prices AS (
    SELECT
        p.article,
        p.name,
        p.brand,
        p.category,
        p.base_price,
        p.fixed_price,
        calculate_cost_price(p.article) as cost_price,
        p.tier,
        p.is_active
    FROM pim_products p
),
retail AS (
    SELECT
        *,
        calculate_retail_price(article) as retail_price
    FROM prices
)
SELECT
    article,
    name,
    brand,
    category,
    base_price,
    fixed_price,
    cost_price,
    retail_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,
    CASE
        WHEN cost_price > 0 THEN
            ROUND((retail_price / cost_price - 1) * 100, 2)
        ELSE NULL
    END as actual_markup_percent,
    tier,
    is_active
FROM retail;

Вердикт: Текущая реализация РАБОТАЕТ правильно, оптимизация опциональна.


✅ Схема БД (проверено)

Вопрос: Нужно ли указывать схему для price_cost_rules и price_sale_rules?

Проверка:

grep "price_cost_rules\|price_sale_rules" TABLE_STRUCTURES.md

Результат: Таблицы существуют в той же схеме (pt7k98pv0fwi1el).

Вывод: Схему указывать НЕ нужно, PostgreSQL найдёт таблицы по search_path.


Тесты

До применения миграции

-- Проверить наличие полей
SELECT column_name, data_type
FROM information_schema.columns
WHERE table_schema = 'pt7k98pv0fwi1el'
  AND table_name = 'pim_products'
  AND column_name IN ('base_price', 'fixed_price', 'cost_price', 'fixed_cost_price', 'target_cost', 'is_premium', 'premium_reason', 'tier')
ORDER BY column_name;

Ожидаемый результат: 8 полей (все перечисленные)

После применения миграции

-- Проверить наличие полей
SELECT column_name, data_type
FROM information_schema.columns
WHERE table_schema = 'pt7k98pv0fwi1el'
  AND table_name = 'pim_products'
  AND column_name IN ('base_price', 'fixed_price', 'cost_price', 'fixed_cost_price', 'target_cost', 'is_premium', 'premium_reason', 'tier')
ORDER BY column_name;

Ожидаемый результат: 3 поля (base_price, fixed_price, tier)

Проверка вычислений

-- Проверить расчёт себестоимости
SELECT
    article,
    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 expected_source
FROM pim_products
WHERE is_active = true
LIMIT 10;

Проверить вручную: cost_price должен совпадать с ожидаемым значением.

Проверка VIEW

SELECT * FROM v_products_with_cost LIMIT 5;
SELECT * FROM v_retail_prices LIMIT 5;

Проверить: Нет ошибок, данные корректны.


Безопасность

Бэкап

Автоматически создаётся:

backups/pim_products_before_029_YYYYMMDD_HHMMSS.sql

Размер: ~5-10 MB (5922 записи)

Откат

Команда:

psql -h localhost -U nc_o71z -d nc_o71z_db < backups/pim_products_before_029_*.sql

Время отката: ~5-10 секунд


Рекомендации

Перед применением

  1. ✅ Убедиться что есть доступ к БД
  2. ✅ Проверить что нет активных процессов импорта
  3. ✅ Уведомить команду (если есть)
  4. ✅ Запланировать окно обслуживания (миграция ~30 сек)

После применения

  1. ✅ Проверить структуру таблицы (должно остаться 3 поля)
  2. ✅ Проверить расчёт цен на 5-10 товарах
  3. ✅ Проверить VIEW (v_products_with_cost, v_retail_prices)
  4. ✅ Обновить код Python (если использует cost_price напрямую)
  5. ✅ Запустить тесты приложения

Если что-то пошло не так

  1. ✅ Остановить миграцию (Ctrl+C если ещё идёт)
  2. ✅ Восстановить из бэкапа (команда в выводе скрипта)
  3. ✅ Сообщить об ошибке
  4. ✅ Не применять повторно без анализа

Финальный вердикт

✅ МИГРАЦИЯ ГОТОВА К ПРИМЕНЕНИЮ

Риски: Минимальные
- Есть автоматический бэкап
- Транзакция откатится при ошибке
- Время выполнения ~30 секунд
- Не влияет на работающие процессы

Преимущества:
- Убирает 5 дубликатов полей
- Упрощает логику (себестоимость = функция)
- Улучшает консистентность (один источник правды)
- Документация на 100% соответствует коду

Команда запуска:

cd /opt/claude-workspace/projects/org/pirotehnika/app/pim
./apply_price_migration.sh

Время: 30-60 секунд (зависит от скорости БД)

Даунтайм: 0 секунд (если не обновлять код Python одновременно)


Чеклист применения


Версия: 1.0.0
Проверено: 2025-12-28