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

PIM — Универсальная архитектура PIM ↔ ERP

Версия: 1.0.0
Дата: 2025-12-24


КОНЦЕПЦИЯ

┌─────────────────────────────────────────────────────────────────────┐
│                    УНИВЕРСАЛЬНАЯ АРХИТЕКТУРА                         │
├─────────────────────────────────────────────────────────────────────┤
│                                                                      │
│                         ┌─────────────┐                              │
│                         │     PIM     │                              │
│                         │  (Товары)   │                              │
│                         └──────┬──────┘                              │
│                                │                                     │
│              ┌─────────────────┼─────────────────┐                   │
│              │                 │                 │                   │
│              ▼                 ▼                 ▼                   │
│       ┌──────────┐      ┌──────────┐      ┌──────────┐              │
│       │   1С     │      │ Наша ERP │      │  Другая  │              │
│       │(докум.)  │      │ (учёт)   │      │   ERP    │              │
│       └──────────┘      └──────────┘      └──────────┘              │
│                                                                      │
│       Генератор          Основной          Возможное                │
│       документов         учёт              расширение               │
│                                                                      │
└─────────────────────────────────────────────────────────────────────┘

Принцип: PIM — универсальный каталог, который работает с любой ERP через стандартизированный интерфейс.


РАЗДЕЛЕНИЕ ОТВЕТСТВЕННОСТИ

Три слоя системы

┌─────────────────────────────────────────────────────────────────────┐
│                                                                      │
│  СЛОЙ 1: PIM (Product Information Management)                       │
│  ─────────────────────────────────────────────                      │
│  Отвечает за:                                                       │
│  • Товарный каталог (name, description, specs)                      │
│  • Медиа (images, video)                                            │
│  • Категории и характеристики                                       │
│  • Ценообразование (retail_price, wholesale_price)                  │
│  • Выгрузка на маркетплейсы (OZON, WB)                              │
│  • Выгрузка на сайты (OpenCart)                                     │
│                                                                      │
├─────────────────────────────────────────────────────────────────────┤
│                                                                      │
│  СЛОЙ 2: ERP (Enterprise Resource Planning)                         │
│  ──────────────────────────────────────────                         │
│  Отвечает за:                                                       │
│  • Остатки (stock)                                                  │
│  • Себестоимость (cost_price)                                       │
│  • Заказы и резервы                                                 │
│  • Поставщики и закупки                                             │
│  • Клиенты и продажи                                                │
│  • Финансы                                                          │
│                                                                      │
│  Реализации:                                                        │
│  ┌────────────────┐  ┌────────────────┐  ┌────────────────┐         │
│  │   Наша ERP     │  │     Odoo       │  │    Другие      │         │
│  │  (PostgreSQL)  │  │   (Odoo 18)    │  │   (SAP, etc)   │         │
│  └────────────────┘  └────────────────┘  └────────────────┘         │
│                                                                      │
├─────────────────────────────────────────────────────────────────────┤
│                                                                      │
│  СЛОЙ 3: ДОКУМЕНТООБОРОТ (1С или другой)                            │
│  ───────────────────────────────────────                            │
│  Отвечает за:                                                       │
│  • Первичные документы (накладные, счета)                           │
│  • Налоговая отчётность                                             │
│  • Бухгалтерский учёт                                               │
│  • ЭДО (электронный документооборот)                                │
│                                                                      │
│  ┌────────────────┐                                                 │
│  │    1С:УНФ      │  ← Генератор документов                         │
│  │  (SaaS 1С)     │                                                 │
│  └────────────────┘                                                 │
│                                                                      │
└─────────────────────────────────────────────────────────────────────┘

ЭВОЛЮЦИЯ СИСТЕМЫ

Этап 1: Сейчас (1С = ERP + Документы)

┌─────────┐         ┌─────────┐         ┌─────────┐
│   PIM   │◄───────►│   1С    │────────►│  OZON   │
│         │ товары  │ ERP+Док │  ручной │         │
└─────────┘ остатки └─────────┘         └─────────┘

Этап 2: Переход (Наша ERP + 1С документы)

┌─────────┐         ┌─────────┐         ┌─────────┐
│   PIM   │◄───────►│Наша ERP │────────►│  OZON   │
│         │ товары  │ (учёт)  │ автомат │         │
└─────────┘ остатки └─────────┘         └─────────┘
                          │
                          │ документы
                          ▼
                    ┌─────────┐
                    │   1С    │
                    │(1С-Асс.)│
                    └─────────┘

Этап 3: Цель (Полная автономия)

┌─────────┐         ┌─────────┐         ┌─────────┐
│   PIM   │◄───────►│Наша ERP │◄───────►│  OZON   │
│         │         │         │         │   WB    │
└─────────┘         └─────────┘         │  Сайты  │
                          │             └─────────┘
                          │
              ┌───────────┼───────────┐
              ▼           ▼           ▼
        ┌─────────┐ ┌─────────┐ ┌─────────┐
        │1С-Асс.  │ │   ЭДО   │ │ Банк API│
        │(докум.) │ │(Диадок) │ │         │
        └─────────┘ └─────────┘ └─────────┘

НАША ERP: ТАБЛИЦЫ

Ядро (PostgreSQL)

-- ============================================
-- ТОВАРЫ (связь с PIM)
-- ============================================

CREATE TABLE erp_products (
    id SERIAL PRIMARY KEY,
    pim_id INT REFERENCES pim_products(id),   -- Связь с PIM

    -- ERP данные
    sku VARCHAR(50) UNIQUE NOT NULL,
    stock INT DEFAULT 0,
    reserved INT DEFAULT 0,
    available INT GENERATED ALWAYS AS (stock - reserved) STORED,

    -- Себестоимость
    cost_price DECIMAL(12,2),
    cost_currency VARCHAR(3) DEFAULT 'RUB',

    -- Поставщик
    supplier_id INT REFERENCES erp_suppliers(id),
    supplier_sku VARCHAR(50),

    -- Метаданные
    is_active BOOLEAN DEFAULT true,
    created_at TIMESTAMP DEFAULT NOW(),
    updated_at TIMESTAMP DEFAULT NOW()
);

-- ============================================
-- ПОСТАВЩИКИ
-- ============================================

CREATE TABLE erp_suppliers (
    id SERIAL PRIMARY KEY,
    code VARCHAR(20) UNIQUE NOT NULL,
    name VARCHAR(200) NOT NULL,
    inn VARCHAR(12),
    kpp VARCHAR(9),

    -- Контакты
    contact_name VARCHAR(100),
    contact_phone VARCHAR(20),
    contact_email VARCHAR(100),

    -- Условия
    payment_days INT DEFAULT 0,          -- Отсрочка
    min_order DECIMAL(12,2),             -- Мин. заказ
    delivery_days INT,                   -- Срок доставки

    is_active BOOLEAN DEFAULT true
);

-- ============================================
-- ЗАКАЗЫ ПОСТАВЩИКУ
-- ============================================

CREATE TABLE erp_purchase_orders (
    id SERIAL PRIMARY KEY,
    order_number VARCHAR(20) UNIQUE NOT NULL,
    supplier_id INT REFERENCES erp_suppliers(id),

    -- Статус
    status VARCHAR(20) DEFAULT 'draft',  -- draft, sent, confirmed, shipped, received
    order_date DATE,
    expected_date DATE,
    received_date DATE,

    -- Суммы
    total_amount DECIMAL(12,2),
    currency VARCHAR(3) DEFAULT 'RUB',

    -- 1С документ (после создания)
    doc_1c_id VARCHAR(50),
    doc_1c_number VARCHAR(20),

    created_at TIMESTAMP DEFAULT NOW()
);

CREATE TABLE erp_purchase_order_lines (
    id SERIAL PRIMARY KEY,
    order_id INT REFERENCES erp_purchase_orders(id),
    product_id INT REFERENCES erp_products(id),

    quantity INT NOT NULL,
    price DECIMAL(12,2) NOT NULL,
    amount DECIMAL(12,2) GENERATED ALWAYS AS (quantity * price) STORED,

    received_qty INT DEFAULT 0
);

-- ============================================
-- ЗАКАЗЫ КЛИЕНТОВ
-- ============================================

CREATE TABLE erp_sales_orders (
    id SERIAL PRIMARY KEY,
    order_number VARCHAR(20) UNIQUE NOT NULL,

    -- Источник
    source VARCHAR(20) NOT NULL,         -- ozon, wb, site, manual
    external_order_id VARCHAR(50),       -- ID в маркетплейсе

    -- Клиент
    customer_name VARCHAR(200),
    customer_phone VARCHAR(20),
    customer_email VARCHAR(100),
    delivery_address TEXT,

    -- Статус
    status VARCHAR(20) DEFAULT 'new',    -- new, confirmed, packed, shipped, delivered, cancelled

    -- Суммы
    total_amount DECIMAL(12,2),
    delivery_amount DECIMAL(12,2),
    commission_amount DECIMAL(12,2),     -- Комиссия маркетплейса

    -- 1С документ
    doc_1c_id VARCHAR(50),
    doc_1c_number VARCHAR(20),

    created_at TIMESTAMP DEFAULT NOW()
);

-- ============================================
-- ДВИЖЕНИЕ ТОВАРОВ
-- ============================================

CREATE TABLE erp_stock_movements (
    id SERIAL PRIMARY KEY,
    product_id INT REFERENCES erp_products(id),

    movement_type VARCHAR(20) NOT NULL,  -- receipt, sale, return, adjustment, reserve
    quantity INT NOT NULL,               -- + приход, - расход
    reference_type VARCHAR(20),          -- purchase_order, sales_order, manual
    reference_id INT,

    -- До/После
    stock_before INT,
    stock_after INT,

    created_at TIMESTAMP DEFAULT NOW(),
    created_by VARCHAR(50)
);

Связь PIM ↔ ERP

-- Представление: товар со всеми данными
CREATE VIEW v_product_full AS
SELECT
    p.id as pim_id,
    p.sku,
    p.name,
    p.description,
    p.category_slug,
    p.specifications,
    p.retail_price,
    p.wholesale_price,
    p.images,

    -- ERP данные
    e.stock,
    e.reserved,
    e.available,
    e.cost_price,
    e.supplier_id,

    -- Вычисляемые
    (p.retail_price - e.cost_price) as margin,
    CASE WHEN e.cost_price > 0
         THEN ((p.retail_price - e.cost_price) / e.cost_price * 100)
         ELSE 0
    END as margin_percent

FROM pim_products p
LEFT JOIN erp_products e ON e.pim_id = p.id
WHERE p.is_active = true;

1С-АССИСТЕНТ (AI + Скрипты)

Концепция

┌─────────────────────────────────────────────────────────────────────┐
│                       1С-АССИСТЕНТ                                   │
├─────────────────────────────────────────────────────────────────────┤
│                                                                      │
│  ┌──────────────────────────────────────────────────────────┐       │
│  │                      AI АГЕНТ                             │       │
│  │  • Понимает контекст (заказ, накладная, счёт)            │       │
│  │  • Формирует данные для документа                        │       │
│  │  • Валидирует перед отправкой                            │       │
│  │  • Обрабатывает ошибки                                   │       │
│  └──────────────────────────────────────────────────────────┘       │
│                              │                                       │
│                              ▼                                       │
│  ┌──────────────────────────────────────────────────────────┐       │
│  │                    КОННЕКТОР 1С                           │       │
│  │  • OData API (справочники, документы)                    │       │
│  │  • HTTP Service (кастомные операции)                     │       │
│  │  • Очередь операций                                      │       │
│  │  • Retry логика                                          │       │
│  └──────────────────────────────────────────────────────────┘       │
│                              │                                       │
│                              ▼                                       │
│  ┌──────────────────────────────────────────────────────────┐       │
│  │                      1С:УНФ                               │       │
│  │  Создаёт:                                                │       │
│  │  • Заказ поставщику → Приходная накладная                │       │
│  │  • Заказ покупателя → Расходная накладная                │       │
│  │  • Счёт на оплату → Платёжное поручение                  │       │
│  └──────────────────────────────────────────────────────────┘       │
│                                                                      │
└─────────────────────────────────────────────────────────────────────┘

AI-агент: specialists/1c.ai.md

# 1С-Ассистент

**Тип:** Специалист
**Триггеры:** `1с`, `документ`, `накладная`, `счёт`, `создай в 1с`

## РОЛЬ

AI-ассистент для работы с 1С. Создаёт документы на основе данных из ERP.

## ВОЗМОЖНОСТИ

### Документы

| Документ | Команда | Источник данных |
|----------|---------|-----------------|
| Заказ поставщику | "создай заказ поставщику" | erp_purchase_orders |
| Приходная накладная | "создай приход" | erp_purchase_orders (received) |
| Расходная накладная | "создай расход" | erp_sales_orders |
| Счёт на оплату | "выстави счёт" | erp_sales_orders |
| Возврат | "оформи возврат" | erp_returns |

### Алгоритм создания документа

1. Получить данные из ERP
2. Проверить контрагента в 1С (создать если нет)
3. Проверить номенклатуру (сопоставить по артикулу)
4. Сформировать документ
5. Отправить в 1С
6. Получить номер документа
7. Обновить ERP (doc_1c_id, doc_1c_number)

### Примеры

"создай приходную накладную по заказу ЗП-00123"
→ Находит заказ в ERP
→ Проверяет поставщика в 1С
→ Создаёт документ "Приходная накладная"
→ Возвращает номер

"выстави счёт клиенту Иванов на заказ 12345"
→ Находит заказ в ERP
→ Проверяет контрагента
→ Создаёт "Счёт на оплату"
→ Генерирует PDF
→ Отправляет клиенту (опционально)

Коннектор: library/connectors/api/1c/

# library/connectors/api/1c/client.py

class Client1C:
    """Универсальный клиент к 1С OData API"""

    def __init__(self, base_url: str, auth: tuple):
        self.base_url = base_url
        self.auth = auth

    # ═══════════════════════════════════════════
    # СПРАВОЧНИКИ
    # ═══════════════════════════════════════════

    async def get_contractor(self, inn: str) -> dict | None:
        """Найти контрагента по ИНН"""
        ...

    async def create_contractor(self, data: ContractorCreate) -> str:
        """Создать контрагента, вернуть ref_key"""
        ...

    async def get_nomenclature(self, article: str) -> dict | None:
        """Найти номенклатуру по артикулу"""
        ...

    # ═══════════════════════════════════════════
    # ДОКУМЕНТЫ
    # ═══════════════════════════════════════════

    async def create_purchase_order(self, data: PurchaseOrderCreate) -> str:
        """Создать Заказ поставщику"""
        ...

    async def create_receipt(self, data: ReceiptCreate) -> str:
        """Создать Приходную накладную"""
        ...

    async def create_shipment(self, data: ShipmentCreate) -> str:
        """Создать Расходную накладную"""
        ...

    async def create_invoice(self, data: InvoiceCreate) -> str:
        """Создать Счёт на оплату"""
        ...

    # ═══════════════════════════════════════════
    # ОТЧЁТЫ
    # ═══════════════════════════════════════════

    async def get_stock_report(self) -> list[dict]:
        """Отчёт по остаткам"""
        ...

    async def get_sales_report(self, date_from: date, date_to: date) -> list[dict]:
        """Отчёт по продажам"""
        ...


# library/connectors/api/1c/assistant.py

class Assistant1C:
    """AI-ассистент для работы с 1С"""

    def __init__(self, client: Client1C, erp_db: AsyncSession):
        self.client = client
        self.erp = erp_db

    async def create_document(self, command: str) -> DocumentResult:
        """
        Создать документ по команде на естественном языке.

        Примеры:
        - "создай приход по заказу ЗП-00123"
        - "выстави счёт на заказ 12345"
        - "оформи возврат товара от клиента Петров"
        """

        # 1. Парсинг команды (AI)
        intent = await self._parse_intent(command)

        # 2. Получение данных из ERP
        erp_data = await self._get_erp_data(intent)

        # 3. Валидация
        validation = await self._validate(intent, erp_data)
        if not validation.ok:
            return DocumentResult(error=validation.error)

        # 4. Создание документа
        doc_id = await self._create_document(intent, erp_data)

        # 5. Обновление ERP
        await self._update_erp(intent, doc_id)

        return DocumentResult(
            success=True,
            doc_type=intent.doc_type,
            doc_id=doc_id,
            doc_number=await self._get_doc_number(doc_id)
        )

ПОТОКИ ДАННЫХ

Новая архитектура

┌─────────────────────────────────────────────────────────────────────┐
│                      ПОТОКИ ДАННЫХ                                   │
├─────────────────────────────────────────────────────────────────────┤
│                                                                      │
│  ТОВАРЫ:                                                            │
│  ────────                                                           │
│  Прайс ──► PIM ──► Обогащение ──► Наша ERP (stock=0)               │
│                         │                                           │
│                         ├──► OZON                                   │
│                         ├──► WB                                     │
│                         └──► OpenCart                               │
│                                                                      │
│  ОСТАТКИ:                                                           │
│  ─────────                                                          │
│  Наша ERP ◄─── Приход ◄─── Поставщик                               │
│      │                                                              │
│      ├───► PIM (для маркетплейсов)                                  │
│      └───► 1С-Асс. (документ "Приходная накладная")                │
│                                                                      │
│  ЗАКАЗЫ:                                                            │
│  ────────                                                           │
│  OZON ──► Наша ERP ──► Обработка ──► 1С-Асс. (накладная)           │
│   WB ──►      │                                                     │
│  Сайт ──►     └──► Отгрузка ──► Обновление остатков                │
│                                                                      │
│  ДОКУМЕНТЫ:                                                         │
│  ───────────                                                        │
│  Наша ERP ──► 1С-Ассистент ──► 1С:УНФ                              │
│                   │                                                 │
│                   ├── Заказ поставщику                              │
│                   ├── Приходная накладная                           │
│                   ├── Расходная накладная                           │
│                   ├── Счёт на оплату                                │
│                   └── Возвраты                                      │
│                                                                      │
└─────────────────────────────────────────────────────────────────────┘

ИНТЕРФЕЙС ERP

Абстракция для PIM

# library/connectors/erp/interface.py

from abc import ABC, abstractmethod

class ERPInterface(ABC):
    """Абстрактный интерфейс ERP для PIM"""

    @abstractmethod
    async def get_stock(self, sku: str) -> int:
        """Получить остаток по артикулу"""
        ...

    @abstractmethod
    async def get_cost_price(self, sku: str) -> Decimal:
        """Получить себестоимость"""
        ...

    @abstractmethod
    async def reserve_stock(self, sku: str, qty: int, order_id: str) -> bool:
        """Зарезервировать товар"""
        ...

    @abstractmethod
    async def release_reserve(self, sku: str, qty: int, order_id: str) -> bool:
        """Снять резерв"""
        ...

    @abstractmethod
    async def sync_stock_updates(self, since: datetime) -> list[StockUpdate]:
        """Получить обновления остатков"""
        ...


# Реализации:

class OurERP(ERPInterface):
    """Наша ERP (PostgreSQL)"""
    ...

class Connector1C(ERPInterface):
    """1С как ERP (legacy)"""
    ...

class OdooConnector(ERPInterface):
    """Odoo ERP"""
    ...

ПЛАН МИГРАЦИИ

Этап Что делаем Результат
0 Создаём таблицы Нашей ERP Структура готова
1 Импорт товаров 1С → PIM → ERP Товары в обеих системах
2 Параллельная работа (1С + ERP) Сверка данных
3 Переключение учёта на ERP ERP = master
4 1С-Ассистент для документов Документы через AI
5 Отключение учёта в 1С 1С = только документы

СВЯЗИ


Версия: 1.0.0