architect/arh/design/PRO_SCHEMA.md

PRO.seller1 — Схема базы данных

База: PostgreSQL (NocoDB)
Схема: pt7k98pv0fwi1el (существующая) или pro_seller1 (новая)


ПРЕФИКСЫ ТАБЛИЦ

Префикс Модуль Описание
usr_ CORE Пользователи, роли, настройки
cat_ CATALOG Товары, категории, склады
ord_ ORDERS Заказы, позиции
shp_ SHIPMENTS Отгрузки, доставка
prc_ PROCUREMENT Закупки, поставщики
mp_ MARKETPLACES Маркетплейсы (общее)
ozon_ OZON OZON данные
wb_ WB Wildberries данные
ym_ YM Яндекс.Маркет данные
crm_ CRM Клиенты, сегменты
mkt_ MARKETING Реклама, акции
fin_ FINANCE Финансы, транзакции
int_ INTEGRATIONS Интеграции (1С, банки)
bot_ AUTOMATION Роботы, триггеры
log_ LOGS Логи, аудит

МОДУЛЬ: CORE (usr_)

usr_users — Пользователи

CREATE TABLE usr_users (
    id SERIAL PRIMARY KEY,
    email VARCHAR(255) UNIQUE NOT NULL,
    password_hash VARCHAR(255) NOT NULL,
    name VARCHAR(255),
    phone VARCHAR(50),
    avatar_url VARCHAR(500),
    role_id INTEGER REFERENCES usr_roles(id),
    is_active BOOLEAN DEFAULT true,
    last_login TIMESTAMP,
    created_at TIMESTAMP DEFAULT NOW(),
    updated_at TIMESTAMP DEFAULT NOW()
);

CREATE INDEX idx_usr_users_email ON usr_users(email);

usr_roles — Роли

CREATE TABLE usr_roles (
    id SERIAL PRIMARY KEY,
    code VARCHAR(50) UNIQUE NOT NULL,   -- 'admin', 'manager', 'operator'
    name VARCHAR(100) NOT NULL,
    permissions JSONB DEFAULT '[]',      -- ['orders.view', 'orders.edit']
    created_at TIMESTAMP DEFAULT NOW()
);

-- Предустановленные роли
INSERT INTO usr_roles (code, name, permissions) VALUES
('admin', 'Администратор', '["*"]'),
('manager', 'Менеджер', '["orders.*", "catalog.view", "crm.*"]'),
('operator', 'Оператор', '["orders.view", "catalog.view"]'),
('warehouse', 'Кладовщик', '["catalog.*", "shipments.*"]');

usr_sessions — Сессии

CREATE TABLE usr_sessions (
    id SERIAL PRIMARY KEY,
    user_id INTEGER REFERENCES usr_users(id) ON DELETE CASCADE,
    token VARCHAR(500) NOT NULL,
    ip_address VARCHAR(50),
    user_agent TEXT,
    expires_at TIMESTAMP NOT NULL,
    created_at TIMESTAMP DEFAULT NOW()
);

CREATE INDEX idx_usr_sessions_token ON usr_sessions(token);
CREATE INDEX idx_usr_sessions_user ON usr_sessions(user_id);

usr_settings — Настройки системы

CREATE TABLE usr_settings (
    id SERIAL PRIMARY KEY,
    key VARCHAR(100) UNIQUE NOT NULL,
    value JSONB,
    description TEXT,
    updated_at TIMESTAMP DEFAULT NOW()
);

-- Примеры настроек
INSERT INTO usr_settings (key, value) VALUES
('company_name', '"PRO.seller1"'),
('default_warehouse', '1'),
('ozon_sync_interval', '15'),
('notification_email', '"admin@example.com"');

МОДУЛЬ: CATALOG (cat_)

cat_categories — Категории

CREATE TABLE cat_categories (
    id SERIAL PRIMARY KEY,
    parent_id INTEGER REFERENCES cat_categories(id),
    name VARCHAR(255) NOT NULL,
    slug VARCHAR(255),
    sort_order INTEGER DEFAULT 0,
    is_active BOOLEAN DEFAULT true,
    created_at TIMESTAMP DEFAULT NOW()
);

CREATE INDEX idx_cat_categories_parent ON cat_categories(parent_id);

cat_products — Товары

CREATE TABLE cat_products (
    id SERIAL PRIMARY KEY,
    sku VARCHAR(100) UNIQUE,            -- Внутренний артикул
    name VARCHAR(500) NOT NULL,
    description TEXT,
    category_id INTEGER REFERENCES cat_categories(id),
    brand VARCHAR(255),

    -- Цены
    cost_price DECIMAL(12,2),           -- Себестоимость
    base_price DECIMAL(12,2),           -- Базовая цена продажи

    -- Габариты
    weight_g INTEGER,                   -- Вес в граммах
    length_mm INTEGER,
    width_mm INTEGER,
    height_mm INTEGER,

    -- Статус
    is_active BOOLEAN DEFAULT true,

    -- Мета
    created_at TIMESTAMP DEFAULT NOW(),
    updated_at TIMESTAMP DEFAULT NOW()
);

CREATE INDEX idx_cat_products_sku ON cat_products(sku);
CREATE INDEX idx_cat_products_category ON cat_products(category_id);
CREATE INDEX idx_cat_products_brand ON cat_products(brand);

cat_product_images — Изображения товаров

CREATE TABLE cat_product_images (
    id SERIAL PRIMARY KEY,
    product_id INTEGER REFERENCES cat_products(id) ON DELETE CASCADE,
    url VARCHAR(500) NOT NULL,
    sort_order INTEGER DEFAULT 0,
    is_main BOOLEAN DEFAULT false
);

cat_warehouses — Склады

CREATE TABLE cat_warehouses (
    id SERIAL PRIMARY KEY,
    code VARCHAR(50) UNIQUE NOT NULL,
    name VARCHAR(255) NOT NULL,
    address TEXT,
    is_active BOOLEAN DEFAULT true,
    is_default BOOLEAN DEFAULT false,
    created_at TIMESTAMP DEFAULT NOW()
);

cat_stock — Остатки

CREATE TABLE cat_stock (
    id SERIAL PRIMARY KEY,
    product_id INTEGER REFERENCES cat_products(id) ON DELETE CASCADE,
    warehouse_id INTEGER REFERENCES cat_warehouses(id),
    quantity INTEGER DEFAULT 0,         -- Всего на складе
    reserved INTEGER DEFAULT 0,         -- Зарезервировано
    available INTEGER GENERATED ALWAYS AS (quantity - reserved) STORED,
    updated_at TIMESTAMP DEFAULT NOW(),

    UNIQUE(product_id, warehouse_id)
);

CREATE INDEX idx_cat_stock_product ON cat_stock(product_id);
CREATE INDEX idx_cat_stock_warehouse ON cat_stock(warehouse_id);

cat_prices — Цены по каналам

CREATE TABLE cat_prices (
    id SERIAL PRIMARY KEY,
    product_id INTEGER REFERENCES cat_products(id) ON DELETE CASCADE,
    channel VARCHAR(50) NOT NULL,       -- 'ozon', 'wb', 'site', 'wholesale'
    price DECIMAL(12,2) NOT NULL,
    min_price DECIMAL(12,2),
    max_price DECIMAL(12,2),
    is_active BOOLEAN DEFAULT true,
    updated_at TIMESTAMP DEFAULT NOW(),

    UNIQUE(product_id, channel)
);

МОДУЛЬ: ORDERS (ord_)

ord_orders — Заказы

CREATE TABLE ord_orders (
    id SERIAL PRIMARY KEY,
    order_number VARCHAR(50) UNIQUE,    -- Внутренний номер

    -- Источник
    source VARCHAR(50) NOT NULL,        -- 'ozon', 'wb', 'site', 'manual'
    source_id VARCHAR(100),             -- ID в источнике
    source_number VARCHAR(100),         -- Номер в источнике

    -- Статус
    status VARCHAR(50) DEFAULT 'new',
    substatus VARCHAR(50),

    -- Клиент
    customer_id INTEGER REFERENCES crm_clients(id),
    customer_name VARCHAR(255),
    customer_phone VARCHAR(50),
    customer_email VARCHAR(255),

    -- Доставка
    delivery_type VARCHAR(50),          -- 'pickup', 'courier', 'cdek', 'pochta'
    delivery_address TEXT,
    delivery_city VARCHAR(255),
    delivery_price DECIMAL(10,2) DEFAULT 0,
    delivery_date DATE,
    delivery_time_from TIME,
    delivery_time_to TIME,

    -- Суммы
    items_amount DECIMAL(12,2),         -- Сумма товаров
    discount_amount DECIMAL(10,2) DEFAULT 0,
    total_amount DECIMAL(12,2),         -- Итого к оплате

    -- Оплата
    payment_type VARCHAR(50),           -- 'prepaid', 'cod', 'card'
    payment_status VARCHAR(50),         -- 'pending', 'paid', 'refunded'
    paid_at TIMESTAMP,

    -- Комментарии
    customer_comment TEXT,
    manager_comment TEXT,

    -- Мета
    created_at TIMESTAMP DEFAULT NOW(),
    updated_at TIMESTAMP DEFAULT NOW()
);

CREATE INDEX idx_ord_orders_source ON ord_orders(source, source_id);
CREATE INDEX idx_ord_orders_status ON ord_orders(status);
CREATE INDEX idx_ord_orders_customer ON ord_orders(customer_id);
CREATE INDEX idx_ord_orders_created ON ord_orders(created_at);

ord_items — Позиции заказа

CREATE TABLE ord_items (
    id SERIAL PRIMARY KEY,
    order_id INTEGER REFERENCES ord_orders(id) ON DELETE CASCADE,
    product_id INTEGER REFERENCES cat_products(id),
    sku VARCHAR(100),
    name VARCHAR(500),
    quantity INTEGER NOT NULL,
    price DECIMAL(12,2) NOT NULL,       -- Цена за единицу
    discount DECIMAL(10,2) DEFAULT 0,
    total DECIMAL(12,2),                -- quantity * price - discount

    -- Для маркетплейсов
    source_sku VARCHAR(100),            -- SKU в источнике
    source_item_id VARCHAR(100)         -- ID позиции в источнике
);

CREATE INDEX idx_ord_items_order ON ord_items(order_id);
CREATE INDEX idx_ord_items_product ON ord_items(product_id);

ord_status_history — История статусов

CREATE TABLE ord_status_history (
    id SERIAL PRIMARY KEY,
    order_id INTEGER REFERENCES ord_orders(id) ON DELETE CASCADE,
    status VARCHAR(50) NOT NULL,
    comment TEXT,
    user_id INTEGER REFERENCES usr_users(id),
    created_at TIMESTAMP DEFAULT NOW()
);

CREATE INDEX idx_ord_history_order ON ord_status_history(order_id);

МОДУЛЬ: SHIPMENTS (shp_)

shp_shipments — Отгрузки

CREATE TABLE shp_shipments (
    id SERIAL PRIMARY KEY,
    shipment_number VARCHAR(50) UNIQUE,

    -- Связь с заказом
    order_id INTEGER REFERENCES ord_orders(id),

    -- Служба доставки
    carrier VARCHAR(50) NOT NULL,       -- 'ozon', 'cdek', 'pochta', 'own'
    carrier_service VARCHAR(100),       -- Тариф/услуга

    -- Трекинг
    tracking_number VARCHAR(100),
    tracking_url VARCHAR(500),

    -- Статус
    status VARCHAR(50) DEFAULT 'pending',

    -- Отправка
    warehouse_id INTEGER REFERENCES cat_warehouses(id),
    shipped_at TIMESTAMP,
    delivered_at TIMESTAMP,

    -- Стоимость
    shipping_cost DECIMAL(10,2),

    -- Документы
    label_url VARCHAR(500),             -- Этикетка
    waybill_url VARCHAR(500),           -- Накладная

    created_at TIMESTAMP DEFAULT NOW(),
    updated_at TIMESTAMP DEFAULT NOW()
);

CREATE INDEX idx_shp_shipments_order ON shp_shipments(order_id);
CREATE INDEX idx_shp_shipments_tracking ON shp_shipments(tracking_number);

shp_carriers — Службы доставки

CREATE TABLE shp_carriers (
    id SERIAL PRIMARY KEY,
    code VARCHAR(50) UNIQUE NOT NULL,
    name VARCHAR(255) NOT NULL,
    type VARCHAR(50),                   -- 'courier', 'post', 'pvz'
    api_enabled BOOLEAN DEFAULT false,
    api_config JSONB,                   -- API credentials
    is_active BOOLEAN DEFAULT true
);

INSERT INTO shp_carriers (code, name, type) VALUES
('cdek', 'СДЭК', 'courier'),
('pochta', 'Почта России', 'post'),
('dpd', 'DPD', 'courier'),
('boxberry', 'Boxberry', 'pvz'),
('own', 'Своя доставка', 'courier');

shp_couriers — Свои курьеры

CREATE TABLE shp_couriers (
    id SERIAL PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    phone VARCHAR(50),
    is_active BOOLEAN DEFAULT true,
    created_at TIMESTAMP DEFAULT NOW()
);

shp_delivery_zones — Зоны доставки

CREATE TABLE shp_delivery_zones (
    id SERIAL PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    cities TEXT[],                      -- Список городов
    delivery_days INTEGER,              -- Срок доставки
    delivery_price DECIMAL(10,2),
    free_from DECIMAL(12,2),            -- Бесплатно от суммы
    is_active BOOLEAN DEFAULT true
);

МОДУЛЬ: PROCUREMENT (prc_)

prc_suppliers — Поставщики

CREATE TABLE prc_suppliers (
    id SERIAL PRIMARY KEY,
    code VARCHAR(50) UNIQUE,
    name VARCHAR(255) NOT NULL,
    contact_name VARCHAR(255),
    phone VARCHAR(50),
    email VARCHAR(255),
    address TEXT,
    payment_terms TEXT,                 -- Условия оплаты
    delivery_days INTEGER,              -- Срок поставки
    min_order_amount DECIMAL(12,2),
    is_active BOOLEAN DEFAULT true,
    created_at TIMESTAMP DEFAULT NOW()
);

prc_price_lists — Прайсы поставщиков

CREATE TABLE prc_price_lists (
    id SERIAL PRIMARY KEY,
    supplier_id INTEGER REFERENCES prc_suppliers(id),
    product_id INTEGER REFERENCES cat_products(id),
    supplier_sku VARCHAR(100),          -- Артикул поставщика
    supplier_name VARCHAR(500),         -- Название у поставщика
    price DECIMAL(12,2) NOT NULL,
    currency VARCHAR(3) DEFAULT 'RUB',
    min_quantity INTEGER DEFAULT 1,
    in_stock BOOLEAN DEFAULT true,
    updated_at TIMESTAMP DEFAULT NOW(),

    UNIQUE(supplier_id, product_id)
);

CREATE INDEX idx_prc_prices_supplier ON prc_price_lists(supplier_id);
CREATE INDEX idx_prc_prices_product ON prc_price_lists(product_id);

prc_price_history — История цен

CREATE TABLE prc_price_history (
    id SERIAL PRIMARY KEY,
    supplier_id INTEGER REFERENCES prc_suppliers(id),
    product_id INTEGER REFERENCES cat_products(id),
    price DECIMAL(12,2) NOT NULL,
    recorded_at TIMESTAMP DEFAULT NOW()
);

-- Партиционирование по месяцам для больших объёмов
CREATE INDEX idx_prc_history_date ON prc_price_history(recorded_at);

prc_purchases — Закупки

CREATE TABLE prc_purchases (
    id SERIAL PRIMARY KEY,
    purchase_number VARCHAR(50) UNIQUE,
    supplier_id INTEGER REFERENCES prc_suppliers(id),

    status VARCHAR(50) DEFAULT 'draft', -- draft, sent, confirmed, received, cancelled

    -- Суммы
    total_amount DECIMAL(12,2),

    -- Даты
    ordered_at TIMESTAMP,
    expected_at DATE,
    received_at TIMESTAMP,

    -- Связь с заказами клиентов
    related_orders INTEGER[],           -- ID заказов под которые закупаем

    comment TEXT,
    created_by INTEGER REFERENCES usr_users(id),
    created_at TIMESTAMP DEFAULT NOW(),
    updated_at TIMESTAMP DEFAULT NOW()
);

prc_purchase_items — Позиции закупки

CREATE TABLE prc_purchase_items (
    id SERIAL PRIMARY KEY,
    purchase_id INTEGER REFERENCES prc_purchases(id) ON DELETE CASCADE,
    product_id INTEGER REFERENCES cat_products(id),
    supplier_sku VARCHAR(100),
    quantity INTEGER NOT NULL,
    price DECIMAL(12,2) NOT NULL,
    received_quantity INTEGER DEFAULT 0,
    total DECIMAL(12,2)
);

МОДУЛЬ: MARKETPLACES (mp_, ozon_, wb_, ym_)

mp_accounts — Аккаунты маркетплейсов

CREATE TABLE mp_accounts (
    id SERIAL PRIMARY KEY,
    marketplace VARCHAR(50) NOT NULL,   -- 'ozon', 'wb', 'ym'
    code VARCHAR(50) NOT NULL,          -- 'O1', 'O2', 'WB1'
    name VARCHAR(255),
    client_id VARCHAR(100),
    api_key VARCHAR(500),
    api_secret VARCHAR(500),
    is_active BOOLEAN DEFAULT true,
    last_sync_at TIMESTAMP,
    settings JSONB,

    UNIQUE(marketplace, code)
);

mp_product_mapping — Сопоставление товаров

CREATE TABLE mp_product_mapping (
    id SERIAL PRIMARY KEY,
    product_id INTEGER REFERENCES cat_products(id),
    account_id INTEGER REFERENCES mp_accounts(id),
    external_sku VARCHAR(100),          -- SKU на маркетплейсе
    external_id VARCHAR(100),           -- ID на маркетплейсе
    external_url VARCHAR(500),
    is_active BOOLEAN DEFAULT true,

    UNIQUE(product_id, account_id)
);

CREATE INDEX idx_mp_mapping_external ON mp_product_mapping(account_id, external_sku);

ozon_orders — Заказы OZON (детальные данные)

CREATE TABLE ozon_orders (
    id SERIAL PRIMARY KEY,
    account_id INTEGER REFERENCES mp_accounts(id),
    order_id INTEGER REFERENCES ord_orders(id),

    posting_number VARCHAR(100) UNIQUE,
    order_number VARCHAR(100),

    -- OZON специфичные поля
    in_process_at TIMESTAMP,
    shipment_date DATE,
    delivering_date DATE,

    analytics_data JSONB,               -- Аналитика OZON
    financial_data JSONB,               -- Финансы OZON

    raw_data JSONB,                     -- Полный ответ API

    created_at TIMESTAMP DEFAULT NOW(),
    updated_at TIMESTAMP DEFAULT NOW()
);

CREATE INDEX idx_ozon_orders_posting ON ozon_orders(posting_number);

ozon_products — Товары на OZON

CREATE TABLE ozon_products (
    id SERIAL PRIMARY KEY,
    account_id INTEGER REFERENCES mp_accounts(id),
    product_id INTEGER REFERENCES cat_products(id),

    ozon_product_id BIGINT,
    ozon_sku BIGINT,
    ozon_fbo_sku BIGINT,
    ozon_fbs_sku BIGINT,

    -- Статус на OZON
    visibility VARCHAR(50),
    status VARCHAR(50),

    -- Цены
    price DECIMAL(12,2),
    old_price DECIMAL(12,2),
    min_price DECIMAL(12,2),

    -- Остатки
    fbo_stock INTEGER DEFAULT 0,
    fbs_stock INTEGER DEFAULT 0,

    raw_data JSONB,
    updated_at TIMESTAMP DEFAULT NOW()
);

МОДУЛЬ: CRM (crm_)

crm_clients — Клиенты

CREATE TABLE crm_clients (
    id SERIAL PRIMARY KEY,

    -- Основное
    name VARCHAR(255),
    phone VARCHAR(50),
    email VARCHAR(255),

    -- Тип
    type VARCHAR(50) DEFAULT 'retail',  -- retail, wholesale, vip

    -- Сегментация
    segment VARCHAR(50),                -- 'new', 'active', 'sleeping', 'lost'
    tags TEXT[],

    -- Статистика (обновляется триггером)
    orders_count INTEGER DEFAULT 0,
    orders_total DECIMAL(12,2) DEFAULT 0,
    last_order_at TIMESTAMP,
    avg_order_value DECIMAL(12,2),

    -- Лояльность
    loyalty_points INTEGER DEFAULT 0,
    loyalty_level VARCHAR(50),

    -- Источник
    source VARCHAR(50),                 -- 'ozon', 'site', 'manual'
    source_id VARCHAR(100),

    -- Согласия
    consent_sms BOOLEAN DEFAULT false,
    consent_email BOOLEAN DEFAULT false,
    consent_whatsapp BOOLEAN DEFAULT false,

    created_at TIMESTAMP DEFAULT NOW(),
    updated_at TIMESTAMP DEFAULT NOW()
);

CREATE INDEX idx_crm_clients_phone ON crm_clients(phone);
CREATE INDEX idx_crm_clients_email ON crm_clients(email);
CREATE INDEX idx_crm_clients_segment ON crm_clients(segment);

crm_communications — Коммуникации

CREATE TABLE crm_communications (
    id SERIAL PRIMARY KEY,
    client_id INTEGER REFERENCES crm_clients(id),

    channel VARCHAR(50) NOT NULL,       -- 'call', 'sms', 'email', 'whatsapp'
    direction VARCHAR(10),              -- 'in', 'out'

    subject VARCHAR(255),
    content TEXT,

    status VARCHAR(50),                 -- 'sent', 'delivered', 'read', 'failed'

    user_id INTEGER REFERENCES usr_users(id),
    created_at TIMESTAMP DEFAULT NOW()
);

CREATE INDEX idx_crm_comm_client ON crm_communications(client_id);

crm_segments — Сегменты

CREATE TABLE crm_segments (
    id SERIAL PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    code VARCHAR(50) UNIQUE,
    description TEXT,

    -- Правила сегментации (JSON)
    rules JSONB,                        -- {"orders_count": {"gte": 3}, "last_order_days": {"lte": 30}}

    clients_count INTEGER DEFAULT 0,    -- Обновляется периодически
    is_auto BOOLEAN DEFAULT true,       -- Автоматическое обновление

    created_at TIMESTAMP DEFAULT NOW()
);

МОДУЛЬ: MARKETING (mkt_)

mkt_campaigns — Рекламные кампании

CREATE TABLE mkt_campaigns (
    id SERIAL PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    type VARCHAR(50),                   -- 'email', 'sms', 'push', 'ads'
    status VARCHAR(50) DEFAULT 'draft',

    -- Аудитория
    segment_id INTEGER REFERENCES crm_segments(id),
    clients_count INTEGER,

    -- Контент
    subject VARCHAR(255),
    content TEXT,
    template_id INTEGER,

    -- Расписание
    scheduled_at TIMESTAMP,
    sent_at TIMESTAMP,

    -- Статистика
    sent_count INTEGER DEFAULT 0,
    delivered_count INTEGER DEFAULT 0,
    opened_count INTEGER DEFAULT 0,
    clicked_count INTEGER DEFAULT 0,

    created_by INTEGER REFERENCES usr_users(id),
    created_at TIMESTAMP DEFAULT NOW()
);

mkt_promotions — Акции

CREATE TABLE mkt_promotions (
    id SERIAL PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    code VARCHAR(50) UNIQUE,            -- Промокод
    type VARCHAR(50),                   -- 'percent', 'fixed', 'gift'

    -- Условия
    discount_value DECIMAL(10,2),
    min_order_amount DECIMAL(12,2),
    max_discount DECIMAL(10,2),

    -- Применимость
    applicable_to VARCHAR(50),          -- 'all', 'category', 'product'
    applicable_ids INTEGER[],

    -- Период
    starts_at TIMESTAMP,
    ends_at TIMESTAMP,

    -- Лимиты
    usage_limit INTEGER,
    usage_count INTEGER DEFAULT 0,
    per_client_limit INTEGER DEFAULT 1,

    is_active BOOLEAN DEFAULT true,
    created_at TIMESTAMP DEFAULT NOW()
);

МОДУЛЬ: FINANCE (fin_)

fin_transactions — Транзакции

CREATE TABLE fin_transactions (
    id SERIAL PRIMARY KEY,

    type VARCHAR(50) NOT NULL,          -- 'income', 'expense', 'transfer'
    category VARCHAR(100),              -- 'order_payment', 'supplier_payment', 'salary'

    amount DECIMAL(12,2) NOT NULL,
    currency VARCHAR(3) DEFAULT 'RUB',

    -- Связи
    order_id INTEGER REFERENCES ord_orders(id),
    purchase_id INTEGER REFERENCES prc_purchases(id),
    supplier_id INTEGER REFERENCES prc_suppliers(id),

    account VARCHAR(50),                -- 'cash', 'bank', 'ozon', 'wb'

    description TEXT,

    transaction_date DATE NOT NULL,
    created_by INTEGER REFERENCES usr_users(id),
    created_at TIMESTAMP DEFAULT NOW()
);

CREATE INDEX idx_fin_trans_date ON fin_transactions(transaction_date);
CREATE INDEX idx_fin_trans_order ON fin_transactions(order_id);

fin_accounts — Счета

CREATE TABLE fin_accounts (
    id SERIAL PRIMARY KEY,
    code VARCHAR(50) UNIQUE NOT NULL,
    name VARCHAR(255) NOT NULL,
    type VARCHAR(50),                   -- 'cash', 'bank', 'marketplace'
    balance DECIMAL(12,2) DEFAULT 0,
    currency VARCHAR(3) DEFAULT 'RUB',
    is_active BOOLEAN DEFAULT true
);

INSERT INTO fin_accounts (code, name, type) VALUES
('cash', 'Касса', 'cash'),
('bank_main', 'Расчётный счёт', 'bank'),
('ozon', 'OZON баланс', 'marketplace'),
('wb', 'WB баланс', 'marketplace');

МОДУЛЬ: INTEGRATIONS (int_)

int_1c_queue — Очередь синхронизации 1С

CREATE TABLE int_1c_queue (
    id SERIAL PRIMARY KEY,
    direction VARCHAR(10) NOT NULL,     -- 'to_1c', 'from_1c'
    entity_type VARCHAR(50) NOT NULL,   -- 'order', 'product', 'client'
    entity_id INTEGER,

    status VARCHAR(50) DEFAULT 'pending',

    payload JSONB,
    response JSONB,
    error_message TEXT,

    attempts INTEGER DEFAULT 0,
    processed_at TIMESTAMP,
    created_at TIMESTAMP DEFAULT NOW()
);

CREATE INDEX idx_int_1c_status ON int_1c_queue(status);

МОДУЛЬ: AUTOMATION (bot_)

bot_robots — Роботы

CREATE TABLE bot_robots (
    id SERIAL PRIMARY KEY,
    code VARCHAR(50) UNIQUE NOT NULL,
    name VARCHAR(255) NOT NULL,
    description TEXT,

    type VARCHAR(50),                   -- 'price', 'stock', 'order', 'notification'

    is_active BOOLEAN DEFAULT true,
    schedule VARCHAR(100),              -- Cron expression

    config JSONB,                       -- Настройки робота

    last_run_at TIMESTAMP,
    last_run_status VARCHAR(50),
    last_run_message TEXT,

    created_at TIMESTAMP DEFAULT NOW()
);

bot_logs — Логи роботов

CREATE TABLE bot_logs (
    id SERIAL PRIMARY KEY,
    robot_id INTEGER REFERENCES bot_robots(id),

    status VARCHAR(50),                 -- 'started', 'success', 'error'
    message TEXT,
    details JSONB,

    duration_ms INTEGER,
    records_processed INTEGER,

    created_at TIMESTAMP DEFAULT NOW()
);

CREATE INDEX idx_bot_logs_robot ON bot_logs(robot_id);
CREATE INDEX idx_bot_logs_date ON bot_logs(created_at);

МОДУЛЬ: LOGS (log_)

log_audit — Аудит действий

CREATE TABLE log_audit (
    id SERIAL PRIMARY KEY,
    user_id INTEGER REFERENCES usr_users(id),

    action VARCHAR(50) NOT NULL,        -- 'create', 'update', 'delete'
    entity_type VARCHAR(50) NOT NULL,
    entity_id INTEGER,

    old_values JSONB,
    new_values JSONB,

    ip_address VARCHAR(50),
    user_agent TEXT,

    created_at TIMESTAMP DEFAULT NOW()
);

CREATE INDEX idx_log_audit_user ON log_audit(user_id);
CREATE INDEX idx_log_audit_entity ON log_audit(entity_type, entity_id);
CREATE INDEX idx_log_audit_date ON log_audit(created_at);

-- Партиционирование по месяцам
-- CREATE TABLE log_audit_2024_12 PARTITION OF log_audit FOR VALUES FROM ('2024-12-01') TO ('2025-01-01');

ДИАГРАММА СВЯЗЕЙ

                                    ┌─────────────┐
                                    │  usr_users  │
                                    └──────┬──────┘
                                           │
           ┌───────────────────────────────┼───────────────────────────────┐
           │                               │                               │
           ▼                               ▼                               ▼
    ┌─────────────┐                ┌─────────────┐                ┌─────────────┐
    │ cat_products│◄───────────────│  ord_orders │───────────────►│ crm_clients │
    └──────┬──────┘                └──────┬──────┘                └─────────────┘
           │                               │
     ┌─────┴─────┐                   ┌─────┴─────┐
     ▼           ▼                   ▼           ▼
┌─────────┐ ┌─────────┐       ┌──────────┐ ┌──────────┐
│cat_stock│ │cat_prices│      │ord_items │ │shp_ship. │
└─────────┘ └─────────┘       └──────────┘ └──────────┘
     │                               │
     │                               │
     ▼                               ▼
┌──────────────┐              ┌──────────────┐
│prc_suppliers │              │ mp_accounts  │
│prc_purchases │              │ ozon_orders  │
└──────────────┘              └──────────────┘

СУЩЕСТВУЮЩИЕ ТАБЛИЦЫ (NocoDB)

Эти таблицы уже есть, можно использовать или мигрировать:

-- Товары (5623)
pim_products  cat_products (миграция)

-- Категории
pim_categories  cat_categories

-- Заказы OZON
OZON_Orders  ozon_orders

-- Аккаунты
OZON_Accounts  mp_accounts

-- Цены поставщиков (4126)
Prices_History  prc_price_history

СЛЕДУЮЩИЙ ШАГ

  1. Создать миграции Alembic
  2. Настроить SQLAlchemy модели
  3. Начать с CORE

Продолжаем?