Дата: 2025-12-26
Автор: Claude Opus (Архитектор)
Статус: КРИТИЧЕСКИЙ АНАЛИЗ
Рефакторинг от 2025-12-26 был применён к НЕПРАВИЛЬНОЙ базе данных (SQLite локальная), вместо production (PostgreSQL NocoDB). В результате:
База: localhost:5433, nocodb, schema pt7k98pv0fwi1el
Статус: Production, активно используется
Последнее изменение: 2025-12-26 (импорт Pirosnab)
article TEXT PRIMARY KEY
name TEXT
brand TEXT
supplier TEXT -- Производитель (JF, Gordеev, PREMIER)
category TEXT
cost_price NUMERIC -- ❌ Цена в самой таблице
base_price NUMERIC
stock_qty INTEGER -- ❌ Остаток в самой таблице
warehouse_id -- -- ❌ НЕТ связи с конкретным складом
id SERIAL PRIMARY KEY
article TEXT -- Артикул товара
warehouse_id INTEGER -- FK → warehouses
quantity INTEGER -- Остаток
reserved INTEGER -- Зарезервировано
available INTEGER -- Доступно
id SERIAL PRIMARY KEY
code TEXT UNIQUE -- pirosnab, gordeev_1, ozon_o1...
name TEXT
warehouse_type TEXT -- supplier, marketplace, internal
marketplace TEXT -- ozon, wb, null
supplier_code TEXT -- Связь с поставщиком
Примеры складов:
id=1: gordeev_1 (ИП Гордеев, склад основной)
id=9: pirosnab (Pirosnab, склад поставщика)
id=2: ozon_o1_fbo (OZON FBO, маркетплейс)
pricelist_id BIGINT -- FK → PriceList
article TEXT
name TEXT
brand_code TEXT
base_price NUMERIC -- Базовая цена
cost_price NUMERIC -- Себестоимость
name TEXT -- "Pirosnab прайс-лист 2025-12"
supplier_code TEXT -- pirosnab, all
source_file TEXT -- pirosnab.spb.ru (API)
items_count BIGINT
supplier_code TEXT
supplier_name TEXT
discount_percent NUMERIC
is_active BOOLEAN
Смешанная модель:
- Склады (warehouses) = физические места хранения
- Поставщики (supplier в pim_products) = производитель/бренд
- Остатки (stocks) = quantity ПО СКЛАДАМ
- Цены (PriceListItem) = цены ИЗ ПРАЙС-ЛИСТОВ поставщиков
Проблемы:
1. ❌ cost_price и stock_qty дублируются в pim_products и других таблицах
2. ❌ supplier в pim_products = производитель, НЕ склад
3. ❌ Нет чёткой связи "товар → цена от поставщика X"
4. ❌ Нет истории цен по поставщикам
Плюсы:
1. ✅ Работает прямо сейчас
2. ✅ Разделены склады (warehouses) и остатки (stocks)
3. ✅ Есть прайс-листы (PriceList/PriceListItem)
4. ✅ 4626 товаров, 122 остатка, 1716 цен
База: /opt/claude-workspace/projects/org/pirotehnika/data/nocodb/pim.db
Статус: Локальная, экспериментальная
Дата рефакторинга: 2025-12-26 10:20
Документация: migrations/data_architecture/COMPLETION_REPORT.md
article TEXT PRIMARY KEY
name TEXT
brand TEXT
category TEXT
product_type TEXT
base_price REAL -- Базовая от производителя
-- ✅ НЕТ cost_price (удалена!)
-- ✅ НЕТ stock_qty (удалена!)
id INTEGER PRIMARY KEY
article TEXT -- FK → pim_products
supplier_code TEXT -- FK → suppliers (jf, maxsem, 1c)
cost_price REAL -- Цена от этого поставщика
currency TEXT DEFAULT 'RUB'
valid_from TEXT
valid_to TEXT
UNIQUE(article, supplier_code)
id INTEGER PRIMARY KEY
article TEXT -- FK → pim_products
supplier_code TEXT -- FK → suppliers
quantity INTEGER -- Остаток у поставщика
UNIQUE(article, supplier_code)
code TEXT PRIMARY KEY -- jf, maxsem, 1c
name TEXT
api_type TEXT
Чистая нормализация:
- Товары (pim_products) = ТОЛЬКО характеристики
- Цены (supplier_cost) = цены ОТ КАЖДОГО поставщика отдельно
- Остатки (supplier_stock) = остатки У КАЖДОГО поставщика
- Поставщики (suppliers) = справочник
Плюсы:
1. ✅ Чистая нормализация (3NF)
2. ✅ Один товар → много цен от разных поставщиков
3. ✅ История цен (valid_from/valid_to)
4. ✅ Нет дублирования данных
Проблемы:
1. ❌ НЕ применено к production базе
2. ❌ Только 424 товара (vs 4626 в production)
3. ❌ Нет складов (warehouses) — только поставщики
4. ❌ supplier_stock = остаток у поставщика, НЕ на физическом складе
| Аспект | PostgreSQL (Production) | SQLite (Рефакторинг) |
|---|---|---|
| Товары | 4626 | 424 |
| Цены | PriceListItem (1716) | supplier_cost (424) |
| Остатки | stocks (122 по складам) | supplier_stock (27 по поставщикам) |
| Склады | ✅ warehouses (9 складов) | ❌ Нет |
| Поставщики | supplier в pim_products | ✅ suppliers (справочник) |
| Дубликаты | ❌ cost_price в pim_products И PriceListItem | ✅ Нет дубликатов |
| Нормализация | ⚠️ Смешанная | ✅ Чистая 3NF |
| Статус | ✅ Production | ❌ Экспериментальная |
Товар: JF-001 "Петарда Чесночок"
├─ supplier: JF (производитель/бренд)
├─ cost_price: 43.92₽ (в самой таблице)
├─ stock_qty: 100 (в самой таблице)
└─ Остатки по складам:
├─ Склад #1 (Гордеев): 50 шт
├─ Склад #9 (Pirosnab): 30 шт
└─ Склад #2 (OZON FBO): 20 шт
Модель: Физические склады, где лежат товары
Товар: JF-001 "Петарда Чесночок"
├─ brand: JF
├─ Цены от поставщиков:
│ ├─ jf: 43.92₽ (valid 2025-11-01 → ∞)
│ ├─ maxsem: 45.00₽
│ └─ 1c: 50.00₽
└─ Остатки у поставщиков:
├─ jf: 100 шт
└─ maxsem: 50 шт
Модель: Источники данных (откуда получаем товар и цену)
СКЛАД (warehouse) = физическое место хранения
- Pirosnab склад на Софийской
- OZON FBO (склад маркетплейса)
- Гордеев основной склад
ПОСТАВЩИК (supplier) = источник закупки
- JF-Pyro (производитель)
- Pirosnab (дилер JF, Maxsem)
- Maxsem (производитель)
Один товар может:
- Быть на складе Pirosnab (остаток 30 шт)
- Иметь цену от JF-Pyro (закупка 100₽)
- Иметь цену от Pirosnab как дилера (110₽)
-- 1. Товары (характеристики)
pim_products
└─ article, name, brand, category
-- 2. Цены от поставщиков (РЕФАКТОРИНГ)
supplier_cost
└─ article + supplier_code → cost_price
-- 3. Остатки на складах (PRODUCTION)
stocks
└─ article + warehouse_id → quantity
-- 4. Справочники
warehouses (физические места)
suppliers (источники закупки)
Применить рефакторинг к PostgreSQL + оставить warehouses/stocks:
-- ТОВАРЫ (только характеристики)
pim_products
article, name, brand, category
-- УДАЛИТЬ: cost_price, stock_qty
-- ЦЕНЫ ОТ ПОСТАВЩИКОВ (новое из рефакторинга)
supplier_cost
article + supplier_code → cost_price
valid_from, valid_to
-- ОСТАТКИ НА СКЛАДАХ (оставить как есть)
stocks
article + warehouse_id → quantity, reserved
-- СПРАВОЧНИКИ
suppliers (jf, maxsem, pirosnab, gordeev...)
warehouses (pirosnab_warehouse, gordeev_main, ozon_fbo...)
CREATE TABLE pt7k98pv0fwi1el.supplier_cost (
id SERIAL PRIMARY KEY,
article TEXT NOT NULL,
supplier_code TEXT NOT NULL,
cost_price NUMERIC NOT NULL,
currency TEXT DEFAULT 'RUB',
valid_from TIMESTAMP DEFAULT NOW(),
valid_to TIMESTAMP,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW(),
UNIQUE(article, supplier_code, valid_from)
);
CREATE TABLE pt7k98pv0fwi1el.supplier_stock (
id SERIAL PRIMARY KEY,
article TEXT NOT NULL,
supplier_code TEXT NOT NULL,
quantity INTEGER NOT NULL DEFAULT 0,
updated_at TIMESTAMP DEFAULT NOW(),
UNIQUE(article, supplier_code)
);
-- Перенести цены из pim_products в supplier_cost
INSERT INTO supplier_cost (article, supplier_code, cost_price)
SELECT
article,
COALESCE(supplier, 'unknown') as supplier_code,
cost_price
FROM pim_products
WHERE cost_price IS NOT NULL AND cost_price > 0;
-- Перенести цены из PriceListItem
INSERT INTO supplier_cost (article, supplier_code, cost_price)
SELECT
pli.article,
pl.supplier_code,
pli.cost_price
FROM "PriceListItem" pli
JOIN "PriceList" pl ON pli.pricelist_id = pl.id
WHERE pli.cost_price IS NOT NULL;
ALTER TABLE pim_products
DROP COLUMN cost_price,
DROP COLUMN stock_qty;
product.cost_price на get_supplier_cost(article, supplier_code)product.stock_qty на get_warehouse_stock(article, warehouse_id)Устаревшие документы (переместить в archive/2025-12-26-refactoring/):
REFACTORING_REPORT.mdREFACTORING_FINAL_REPORT.mdREFACTORING_REPORT_3.mdCOMBINED_REFACTORING_PLAN.mdDATA_REFACTORING_REPORT.mdCOMPLETION_REPORT.mdPRICING_STOCK_TABLES.md (описывает несуществующие таблицы)Оставить актуальными:
1. standards/DATABASE.md (обновить до v3.0)
2. PRODUCT_DATA_STANDARD.md
3. PRODUCT_RULES.md
Рефакторинг применён к SQLite (локальная), а не к PostgreSQL (production).
Правильная архитектура:
- Товары = характеристики
- Цены = от поставщиков (supplier_cost)
- Остатки = на складах (stocks + warehouses)
- Без дубликатов, с историей, гибкая
Версия: 1.0.0
Дата: 2025-12-26
Автор: Claude Opus (Архитектор)