architect/_archive/2025-11-09-marketplace-old/marketplace-mvp/WORKFLOWS.md

🔄 WORKFLOWS - Рабочие процессы

Проект: Marketplace MVP (mp1)
Версия: 1.0.0
Последнее обновление: 2025-11-08


📑 СОДЕРЖАНИЕ

  1. Добавление новой функции
  2. Работа с БД
  3. Интеграция нового маркетплейса
  4. Интеграция службы доставки
  5. Релиз новой версии
  6. Troubleshooting

➕ ДОБАВЛЕНИЕ НОВОЙ ФУНКЦИИ {#добавление-новой-функции}

Checklist

Процесс

1. Планирование

# Создать feature branch
git checkout -b feature/cdek-integration

# Изучить документацию
# - MP1-PROJECT-MASTER.md (текущее состояние)
# - MP1-ROADMAP.md (приоритеты)
# - MP1-API-GUIDE.md (API справка)
# - CODE-GUIDE.md (стандарты кода)

2. Реализация

Структура:

modules/
├── delivery/
│   ├── __init__.py
│   ├── cdek.py           # Новый модуль
│   └── base.py           # Базовый класс (если нужен)
tests/
├── test_cdek_api.py      # Тесты
pages/
├── 90_Доставка.py        # Новая страница (если нужна)

Код:

# modules/delivery/cdek.py
from typing import List, Dict, Optional
import requests
import logging

logger = logging.getLogger(__name__)

class CdekAPI:
    """
    Клиент для работы с СДЭК API v2.

    Docs: https://api-docs.cdek.ru/
    """

    def __init__(self, account: str, secure_password: str, timeout: int = 10):
        self.account = account
        self.secure_password = secure_password
        self.timeout = timeout
        self.base_url = "https://api.cdek.ru/v2"
        self.token = None

    def _get_token(self) -> str:
        """Получить OAuth токен"""
        url = f"{self.base_url}/oauth/token"
        data = {
            "grant_type": "client_credentials",
            "client_id": self.account,
            "client_secret": self.secure_password
        }

        try:
            response = requests.post(url, data=data, timeout=self.timeout)
            response.raise_for_status()
            return response.json()["access_token"]
        except Exception as e:
            logger.error(f"Ошибка получения токена СДЭК: {e}")
            raise

    def create_order(self, order_data: dict) -> dict:
        """Создать заказ в СДЭК"""
        if not self.token:
            self.token = self._get_token()

        url = f"{self.base_url}/orders"
        headers = {"Authorization": f"Bearer {self.token}"}

        try:
            response = requests.post(url, json=order_data, headers=headers, timeout=self.timeout)
            response.raise_for_status()
            return response.json()
        except Exception as e:
            logger.error(f"Ошибка создания заказа СДЭК: {e}")
            raise

Тесты:

# tests/test_cdek_api.py
import pytest
from modules.delivery.cdek import CdekAPI

def test_cdek_connection():
    api = CdekAPI(
        account="test_account",
        secure_password="test_password"
    )
    # Мок для теста
    assert api.base_url == "https://api.cdek.ru/v2"

@pytest.mark.integration
def test_cdek_create_order_real():
    """Интеграционный тест с реальным API (sandbox)"""
    api = CdekAPI(
        account="z9GRRu7FxmO53CQ9cFfI6qiy32wpfTkd",
        secure_password="w24JTCv4MnAcuRTx0oHjHLDtyt3I6IBq"
    )

    order_data = {
        "tariff_code": 136,
        # ... данные заказа
    }

    result = api.create_order(order_data)
    assert "entity" in result
    assert "uuid" in result["entity"]

3. Документирование

Обновить MP1-API-GUIDE.md:

## 🚚 СДЭК API

**Статус:** ✅ Реализовано
**Модуль:** `modules/delivery/cdek.py`

### create_order(order_data: dict) → dict
Создать заказ в СДЭК

\`\`\`python
from modules.delivery.cdek import CdekAPI

api = CdekAPI(account="...", secure_password="...")
order = api.create_order({
    "tariff_code": 136,
    ...
})
\`\`\`

Обновить MP1-CHANGELOG.md:

## [2.5.0] - 2025-11-09

### Added
- Интеграция СДЭК API (создание заказов, получение этикеток)
- Страница "Доставка" для управления отправками

### Changed
- Обновлён MP1-API-GUIDE.md (добавлен СДЭК)

4. Review и Merge

# Проверить код
flake8 modules/delivery/cdek.py
mypy modules/delivery/cdek.py

# Запустить тесты
pytest tests/test_cdek_api.py -v

# Коммит
git add .
git commit -m "feat: интеграция СДЭК API"

# Merge
git checkout master
git merge feature/cdek-integration

# Push
git push origin master

🗄️ РАБОТА С БД {#работа-с-бд}

Добавление новой таблицы

1. Создать модель

# modules/database/models.py
from sqlalchemy import Column, Integer, String, DateTime, ForeignKey
from sqlalchemy.orm import relationship
from datetime import datetime

class Shipment(Base):
    """Отправления СДЭК/Почты"""
    __tablename__ = "shipments"

    id = Column(Integer, primary_key=True, index=True)
    order_id = Column(Integer, ForeignKey("orders.id"), nullable=False)
    delivery_service = Column(String(20), nullable=False, index=True)  # cdek, pochta
    tracking_number = Column(String(100), unique=True, nullable=False, index=True)
    status = Column(String(50), nullable=False)
    created_at = Column(DateTime, default=datetime.utcnow, nullable=False)

    # Relationships
    order = relationship("Order", back_populates="shipments")

    def __repr__(self):
        return f"<Shipment {self.tracking_number}>"

2. Создать миграцию

# Активировать venv
source venv/bin/activate

# Создать миграцию
alembic revision --autogenerate -m "add shipments table"

# Проверить миграцию
cat alembic/versions/xxx_add_shipments_table.py

# Применить
alembic upgrade head

# Проверить текущую версию
alembic current

3. Проверить в БД

# SQLite
sqlite3 data/marketplace.db

sqlite> .schema shipments
sqlite> SELECT * FROM alembic_version;
sqlite> .quit

# PostgreSQL
psql -U postgres -d marketplace_db

\d shipments
SELECT * FROM alembic_version;
\q

Изменение существующей таблицы

1. Добавить поле в модель

# modules/database/models.py
class Order(Base):
    # ... существующие поля ...

    # Новое поле
    delivery_deadline = Column(DateTime, nullable=True)

2. Создать миграцию

alembic revision --autogenerate -m "add delivery_deadline to orders"

3. Проверить миграцию

# alembic/versions/xxx_add_delivery_deadline_to_orders.py

def upgrade():
    # ✅ Проверить что поле добавляется корректно
    op.add_column('orders', 
        sa.Column('delivery_deadline', sa.DateTime(), nullable=True)
    )

def downgrade():
    # ✅ Проверить что downgrade работает
    op.drop_column('orders', 'delivery_deadline')

4. Применить

# Применить
alembic upgrade head

# Если что-то пошло не так - откатить
alembic downgrade -1

Откат миграции

# Откатить последнюю миграцию
alembic downgrade -1

# Откатить к конкретной версии
alembic downgrade abc123

# Откатить все миграции
alembic downgrade base

# Посмотреть историю
alembic history

🛒 ИНТЕГРАЦИЯ НОВОГО МАРКЕТПЛЕЙСА {#интеграция-нового-маркетплейса}

Checklist

Процесс (на примере Wildberries)

1. Изучение API

Документация: https://openapi.wildberries.ru/
Авторизация: API токен
Rate limit: 200 запросов/минуту

2. Создание клиента

# modules/api/wildberries.py
import requests
import logging
from typing import List, Dict, Optional

logger = logging.getLogger(__name__)

class WildberriesAPI:
    """
    Клиент для работы с Wildberries API.

    Docs: https://openapi.wildberries.ru/
    """

    def __init__(self, api_key: str, timeout: int = 10):
        self.api_key = api_key
        self.timeout = timeout
        self.base_url = "https://suppliers-api.wildberries.ru"

    def test_connection(self) -> bool:
        """Проверка подключения к API"""
        try:
            # Тестовый запрос
            response = self._request("GET", "/api/v2/ping")
            return response.status_code == 200
        except Exception as e:
            logger.error(f"Ошибка подключения к WB API: {e}")
            return False

    def fetch_orders(self, date_from: str, date_to: str) -> List[Dict]:
        """Получить заказы за период"""
        endpoint = "/api/v2/orders"
        params = {
            "dateFrom": date_from,
            "dateTo": date_to
        }

        response = self._request("GET", endpoint, params=params)
        return response.json().get("orders", [])

    def _request(self, method: str, endpoint: str, **kwargs):
        """Базовый метод для запросов"""
        url = f"{self.base_url}{endpoint}"
        headers = {
            "Authorization": f"Bearer {self.api_key}",
            "Content-Type": "application/json"
        }

        try:
            response = requests.request(
                method, url, headers=headers, timeout=self.timeout, **kwargs
            )
            response.raise_for_status()
            return response
        except requests.exceptions.RequestException as e:
            logger.error(f"WB API error: {e}")
            raise

3. Добавление в БД

# modules/database/models.py

class Channel(Base):
    # ... существующие поля ...

    # Добавить новый тип маркетплейса
    marketplace = Column(String(20), nullable=False)  # ozon, wildberries, yandex

Создать миграцию для изменения ENUM (если используется PostgreSQL).

4. Интеграция в UI

# pages/20_Каналы.py

# Визард подключения
if marketplace == "Wildberries":
    st.info("📘 Получите API ключ в личном кабинете WB: Настройки → API")

    api_key = st.text_input("API ключ Wildberries", type="password")

    if st.button("Проверить подключение"):
        from modules.api.wildberries import WildberriesAPI

        api = WildberriesAPI(api_key=api_key)
        if api.test_connection():
            st.success("✅ Подключение успешно!")
        else:
            st.error("❌ Ошибка подключения")

5. Обновление документации

MP1-API-GUIDE.md:

## 🛒 WILDBERRIES API

**Документация:** https://openapi.wildberries.ru/
**Модуль:** `modules/api/wildberries.py`
**Класс:** `WildberriesAPI`

### Аутентификация

\`\`\`python
from modules.api.wildberries import WildberriesAPI

api = WildberriesAPI(api_key="your_api_key")
\`\`\`

MP1-CHANGELOG.md:

## [2.6.0] - 2025-11-10

### Added
- Интеграция Wildberries API
- Поддержка загрузки заказов WB

🚚 ИНТЕГРАЦИЯ СЛУЖБЫ ДОСТАВКИ {#интеграция-службы-доставки}

Процесс аналогичен интеграции маркетплейса

См. раздел "Добавление новой функции" → пример с СДЭК API.

Ключевые отличия:

  1. Модуль создаётся в modules/delivery/ (не modules/api/)
  2. Базовый класс для унификации (опционально):
# modules/delivery/base.py
from abc import ABC, abstractmethod
from typing import Dict

class DeliveryServiceBase(ABC):
    """Базовый класс для служб доставки"""

    @abstractmethod
    def create_order(self, order_data: dict) -> dict:
        """Создать заказ"""
        pass

    @abstractmethod
    def get_label(self, order_id: str) -> bytes:
        """Получить этикетку (PDF)"""
        pass

    @abstractmethod
    def track(self, tracking_number: str) -> dict:
        """Трекинг отправления"""
        pass
  1. Реализация для каждой службы:
# modules/delivery/cdek.py
from modules.delivery.base import DeliveryServiceBase

class CdekAPI(DeliveryServiceBase):
    def create_order(self, order_data: dict) -> dict:
        # Реализация для СДЭК
        pass

🚀 РЕЛИЗ НОВОЙ ВЕРСИИ {#релиз-новой-версии}

Checklist

Процесс

1. Подготовка

# Убедиться что все изменения закоммичены
git status

# Запустить тесты
pytest tests/ -v

# Проверить код
flake8 .
mypy modules/

2. Обновление версии

MP1-CHANGELOG.md:

## [2.5.0] - 2025-11-09

### Added
- Интеграция СДЭК API
- Автоматическая обработка realFBS заказов
- Страница "Доставка"

### Changed
- Оптимизирован OzonAPI.fetch_orders
- Обновлён UI страницы заказов

### Fixed
- Исправлена загрузка трек-номеров для realFBS

version.py (если есть):

# version.py
__version__ = "2.5.0"

3. Создание тега

# Создать аннотированный тег
git tag -a v2.5.0 -m "Release v2.5.0: СДЭК integration"

# Проверить
git tag -l

# Push тега
git push origin v2.5.0

4. Deploy (опционально)

# Обновить на production сервере
ssh user@server
cd /path/to/project
git pull origin master
source venv/bin/activate
alembic upgrade head
systemctl restart marketplace-app

🔧 TROUBLESHOOTING {#troubleshooting}

Проблема: Streamlit не запускается

Симптомы:

streamlit run app.py
# Ошибка: ModuleNotFoundError: No module named 'streamlit'

Решение:

# Проверить venv
which python
# Должно быть: /path/to/marketplace-mvp/venv/bin/python

# Если нет - активировать
source venv/bin/activate

# Переустановить зависимости
pip install -r requirements.txt

Проблема: Ошибка миграции БД

Симптомы:

alembic upgrade head
# sqlalchemy.exc.IntegrityError: UNIQUE constraint failed

Решение:

# Откатить миграцию
alembic downgrade -1

# Проверить текущую версию
alembic current

# Исправить миграцию (добавить обработку дубликатов)
# alembic/versions/xxx_migration.py

# Применить снова
alembic upgrade head

Проблема: Ozon API возвращает 401

Симптомы:

api = OzonAPI(client_id="...", api_key="...")
orders = api.fetch_orders(...)
# Ошибка: 401 Unauthorized

Решение:

# 1. Проверить .env
cat .env | grep OZON

# 2. Проверить что ключи валидны
# Зайти в личный кабинет Ozon → Настройки → API

# 3. Проверить права ключа
# Ключ должен иметь права на чтение заказов

# 4. Тест подключения
python -c "
from modules.api.ozon import OzonAPI
api = OzonAPI(client_id='...', api_key='...')
print(api.test_connection())
"

Проблема: Медленная загрузка страницы

Симптомы:

Страница "Заказы" загружается 10+ секунд

Решение:

# 1. Добавить кеширование
import streamlit as st

@st.cache_data(ttl=600)  # Кеш на 10 минут
def load_orders():
    return db.query(Order).all()

# 2. Пагинация
page = st.number_input("Страница", min_value=1, value=1)
page_size = 50
orders = db.query(Order).limit(page_size).offset((page - 1) * page_size).all()

# 3. Lazy loading для relationships
from sqlalchemy.orm import lazyload

orders = db.query(Order).options(lazyload(Order.items)).all()

Проблема: Git merge conflicts

Симптомы:

git merge feature/my-branch
# CONFLICT (content): Merge conflict in modules/database/models.py

Решение:

# 1. Посмотреть конфликты
git status

# 2. Открыть файл и разрешить конфликт вручную
vim modules/database/models.py

# Найти:
<<<<<<< HEAD
# Ваша версия
=======
# Версия из ветки
>>>>>>> feature/my-branch

# 3. Оставить нужную версию, удалить маркеры

# 4. Добавить файл
git add modules/database/models.py

# 5. Завершить merge
git commit -m "Merge feature/my-branch"

Проблема: Тесты падают локально, но работают в CI

Симптомы:

pytest tests/
# 5 failed, 10 passed

# В CI все тесты проходят

Решение:

# 1. Проверить версии зависимостей
pip freeze > local_requirements.txt
# Сравнить с requirements.txt

# 2. Пересоздать venv
deactivate
rm -rf venv
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt

# 3. Очистить кеши
rm -rf __pycache__
rm -rf .pytest_cache
rm -rf .mypy_cache

# 4. Запустить снова
pytest tests/ -v

Проблема: Memory leak в Streamlit

Симптомы:

Приложение использует всё больше памяти со временем

Решение:

# 1. Проверить session_state
# Не сохранять большие объекты
if 'large_data' in st.session_state:
    del st.session_state.large_data

# 2. Ограничить ttl кеша
@st.cache_data(ttl=600, max_entries=10)  # Макс 10 записей в кеше
def load_data():
    pass

# 3. Закрывать DB сессии
db = get_db_session()
try:
    # работа с БД
finally:
    db.close()  # ✅ Всегда закрывать

# 4. Перезапускать приложение периодически (cron)
# crontab -e
# 0 4 * * * systemctl restart streamlit-app

Последнее обновление: 2025-11-08
Автор: MP1 Team