projects/org/pirotehnika/data/connectors/ARCHITECTURE_COMPARISON.md

СРАВНЕНИЕ АРХИТЕКТУР PIM

Дата: 2025-12-26
Автор: Claude Opus (Архитектор)
Статус: КРИТИЧЕСКИЙ АНАЛИЗ


ПРОБЛЕМА

Рефакторинг от 2025-12-26 был применён к НЕПРАВИЛЬНОЙ базе данных (SQLite локальная), вместо production (PostgreSQL NocoDB). В результате:

  1. ❌ Production база не изменилась
  2. ❌ Документация описывает несуществующую в production архитектуру
  3. ✅ SQLite база имеет новую архитектуру, но не используется

АРХИТЕКТУРА A: PostgreSQL (PRODUCTION) ✅ РАБОТАЕТ СЕЙЧАС

База: localhost:5433, nocodb, schema pt7k98pv0fwi1el
Статус: Production, активно используется
Последнее изменение: 2025-12-26 (импорт Pirosnab)

Таблицы

1. pim_products (4626 товаров)

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    --          -- ❌ НЕТ связи с конкретным складом

2. stocks (122 записи)

id              SERIAL PRIMARY KEY
article         TEXT        -- Артикул товара
warehouse_id    INTEGER     -- FK → warehouses
quantity        INTEGER     -- Остаток
reserved        INTEGER     -- Зарезервировано
available       INTEGER     -- Доступно

3. warehouses (9 складов)

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, маркетплейс)

4. PriceListItem (1716 позиций)

pricelist_id    BIGINT      -- FK → PriceList
article         TEXT
name            TEXT
brand_code      TEXT
base_price      NUMERIC     -- Базовая цена
cost_price      NUMERIC     -- Себестоимость

5. PriceList (3 прайса)

name            TEXT        -- "Pirosnab прайс-лист 2025-12"
supplier_code   TEXT        -- pirosnab, all
source_file     TEXT        -- pirosnab.spb.ru (API)
items_count     BIGINT

6. prices_suppliers (скидки)

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 цен


АРХИТЕКТУРА B: SQLite (РЕФАКТОРИНГ) ⚠️ ЭКСПЕРИМЕНТАЛЬНАЯ

База: /opt/claude-workspace/projects/org/pirotehnika/data/nocodb/pim.db
Статус: Локальная, экспериментальная
Дата рефакторинга: 2025-12-26 10:20
Документация: migrations/data_architecture/COMPLETION_REPORT.md

Таблицы

1. pim_products (424 товара)

article         TEXT PRIMARY KEY
name            TEXT
brand           TEXT
category        TEXT
product_type    TEXT
base_price      REAL        -- Базовая от производителя
-- ✅ НЕТ cost_price (удалена!)
-- ✅ НЕТ stock_qty (удалена!)

2. supplier_cost (424 записи)

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)

3. supplier_stock (27 записей)

id              INTEGER PRIMARY KEY
article         TEXT        -- FK → pim_products
supplier_code   TEXT        -- FK → suppliers
quantity        INTEGER     -- Остаток у поставщика
UNIQUE(article, supplier_code)

4. suppliers (3 поставщика)

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 ❌ Экспериментальная

КОНЦЕПТУАЛЬНАЯ РАЗНИЦА

PostgreSQL: "СКЛАДЫ vs ПОСТАВЩИКИ"

Товар: JF-001 "Петарда Чесночок"
  ├─ supplier: JF (производитель/бренд)
  ├─ cost_price: 43.92 (в самой таблице)
  ├─ stock_qty: 100 (в самой таблице)
  └─ Остатки по складам:
      ├─ Склад #1 (Гордеев): 50 шт
      ├─ Склад #9 (Pirosnab): 30 шт
      └─ Склад #2 (OZON FBO): 20 шт

Модель: Физические склады, где лежат товары

SQLite: "ПОСТАВЩИКИ-ИСТОЧНИКИ"

Товар: JF-001 "Петарда Чесночок"
  ├─ brand: JF
  ├─ Цены от поставщиков:
     ├─ jf: 43.92 (valid 2025-11-01  )
     ├─ maxsem: 45.00
     └─ 1c: 50.00
  └─ Остатки у поставщиков:
      ├─ jf: 100 шт
      └─ maxsem: 50 шт

Модель: Источники данных (откуда получаем товар и цену)


ПРАВИЛЬНАЯ АРХИТЕКТУРА = ГИБРИД

Проблема: Две разные сущности

  1. СКЛАД (warehouse) = физическое место хранения
    - Pirosnab склад на Софийской
    - OZON FBO (склад маркетплейса)
    - Гордеев основной склад

  2. ПОСТАВЩИК (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...)

Что даст:

  1. ✅ Чистая модель без дубликатов
  2. ✅ История цен от каждого поставщика
  3. ✅ Учёт остатков по физическим складам
  4. ✅ Гибкость: один товар → много цен → много складов

ПЛАН ДЕЙСТВИЙ

1. Создать supplier_cost и supplier_stock в PostgreSQL

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

2. Мигрировать данные

-- Перенести цены из 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;

3. Удалить дубликаты из pim_products

ALTER TABLE pim_products
  DROP COLUMN cost_price,
  DROP COLUMN stock_qty;

4. Обновить код


ДОКУМЕНТАЦИЯ К АРХИВУ

Устаревшие документы (переместить в archive/2025-12-26-refactoring/):

  1. REFACTORING_REPORT.md
  2. REFACTORING_FINAL_REPORT.md
  3. REFACTORING_REPORT_3.md
  4. COMBINED_REFACTORING_PLAN.md
  5. DATA_REFACTORING_REPORT.md
  6. COMPLETION_REPORT.md
  7. PRICING_STOCK_TABLES.md (описывает несуществующие таблицы)

Оставить актуальными:
1. standards/DATABASE.md (обновить до v3.0)
2. PRODUCT_DATA_STANDARD.md
3. PRODUCT_RULES.md


ИТОГО

❌ ПРОБЛЕМА

Рефакторинг применён к SQLite (локальная), а не к PostgreSQL (production).

✅ РЕШЕНИЕ

  1. Создать supplier_cost/supplier_stock в PostgreSQL
  2. Мигрировать данные из pim_products и PriceListItem
  3. Удалить cost_price/stock_qty из pim_products
  4. Оставить warehouses/stocks (склады)
  5. Обновить код и документацию

🎯 РЕЗУЛЬТАТ

Правильная архитектура:
- Товары = характеристики
- Цены = от поставщиков (supplier_cost)
- Остатки = на складах (stocks + warehouses)
- Без дубликатов, с историей, гибкая


Версия: 1.0.0
Дата: 2025-12-26
Автор: Claude Opus (Архитектор)