Дата проверки: 2025-12-28
Проверяющий: Claude Sonnet 4.5
Статус: ✅ ГОТОВА К ПРИМЕНЕНИЮ
Структура: ✅ Корректна
- ШАГ 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
Безопасность: ✅ Есть
- Автоматический бэкап перед миграцией ✅
- Подтверждение пользователя (yes/no) ✅
- ON_ERROR_STOP=1 для psql ✅
- Инструкция по откату в выводе ✅
Информативность: ✅ Отлично
- Показывает текущие поля ДО миграции ✅
- Показывает результаты ПОСЛЕ миграции ✅
- Примеры расчёта цен ✅
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: ✅ Полный
- Анализ текущего состояния ✅
- План миграции ✅
- Инструкции по применению ✅
- Проверка после миграции ✅
- Откат описан ✅
Проблема:
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 должен совпадать с ожидаемым значением.
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 секунд
Риски: Минимальные
- Есть автоматический бэкап
- Транзакция откатится при ошибке
- Время выполнения ~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