Версия: 1.0.0
Дата: 2025-12-24
Статус: Active
Каждый поставщик присылает прайс-лист в своём формате:
- Разные структуры (xlsx, csv, xml)
- Разные названия колонок
- Разные единицы измерения
- Разные правила расчета цен
Без стандартизации импорт становится хаосом.
ПРАЙС-ЛИСТ → АДАПТЕР ТОВАРОВ → PIM → АДАПТЕР ЦЕН → ERP
(маппинг полей) ↑ (формулы)
|
Хранит всё из прайса
Важно: Всё из прайса (включая price_raw) сначала попадает в PIM.
Адаптер цен потом читает из PIM и рассчитывает цены для ERP.
Назначение: Извлечь товарные данные из прайса и привести к единому формату
Ответственность:
- Маппинг полей (колонка "Артикул" → article)
- Форматирование (строка → число, дата)
- Валидация (обязательные поля)
- Извлечение атрибутов (калибр, залпы, размер)
НЕ делает:
- Не рассчитывает цены
- Не применяет скидки
- Не знает о бизнес-логике
Выход:
PIM запись:
article: "СС7335"
name: "С Днем Рождения (1"х12)"
brand: "Супер Салют"
supplier: "ИП Гордеев"
price_raw: 1784.00 ← базовая из прайса (как есть)
stock_qty: 177
...
Назначение: Рассчитать все цены по бизнес-правилам для работы ERP
Ответственность:
- Расчет себестоимости (cost_price) со скидками по брендам
- Расчет базовой цены (base_price) с наценками
- Расчет розничной цены (retail_price)
- Учет маржинальности
- Применение персональных скидок B2B клиентам
Выход:
ERP запись:
article: "СС7335"
price_raw: 1784.00 ← из прайса Гордеев
base_price: 1784.00 ← базовая (× 1 для обычных)
cost_price: 892.00 ← себестоимость (СуперСалют -50%)
retail_price: 1784.00 ← розничная (без доп. наценки)
margin: 892.00 ← маржа (base - cost)
margin_percent: 50%
┌─────────────────────────────────────────────────────────────────┐
│ ПРАЙС-ЛИСТ (xlsx) │
│ Гордеев: [Артикул, Номенклатура, Производитель, Базовая, ...] │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ АДАПТЕР ТОВАРОВ (supplier_gordeev_products.py) │
│ Извлекает: article, name, brand, stock_qty, price_raw │
│ Не считает цены - только маппинг полей │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ PIM (NocoDB) │
│ Хранит: товары с базовыми ценами из прайса │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ АДАПТЕР ЦЕН (supplier_gordeev_pricing.py) │
│ Рассчитывает: │
│ base_price = price_raw × markup (1 или 2) │
│ cost_price = base_price × discount (по бренду) │
│ retail_price = base_price × retail_markup │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ ERP (Odoo / 1C) │
│ Использует: полный набор цен для продаж и учета │
└─────────────────────────────────────────────────────────────────┘
# data/connectors/gordeev/adapter_products.py
class GordeevProductsAdapter:
"""Адаптер товаров Гордеев → PIM"""
FIELD_MAPPING = {
'Артикул': 'article',
'Номенклатура': 'name',
'Производитель': 'brand',
'Базовая': 'price_raw', # ← сохраняем как есть
'Остаток на складе': 'stock_qty',
'Видео': 'video_url',
'Калибр в дюймах': 'caliber',
'Количество залпов': 'shots',
}
def extract(self, row):
"""Извлечь товар из строки прайса"""
return {
'article': row['Артикул'].strip(),
'name': row['Номенклатура'].strip(),
'brand': row['Производитель'].strip(),
'price_raw': float(row['Базовая']), # как есть!
'stock_qty': int(row['Остаток на складе']),
'supplier': 'ИП Гордеев',
# ... остальные поля
}
# data/connectors/gordeev/adapter_pricing.py
class GordeevPricingAdapter:
"""Адаптер цен Гордеев → ERP"""
BRAND_DISCOUNTS = {
'Супер Салют': 0.50, # -50%
'default': 0.60, # -40%
}
def calculate_prices(self, product):
"""Рассчитать цены по правилам Гордеев"""
price_raw = product['price_raw']
brand = product['brand']
is_fireworks = self._is_fireworks(product['name'])
# Базовая цена (для продажи)
base_price = price_raw * 2 if is_fireworks else price_raw
# Себестоимость (со скидкой по бренду)
discount = self.BRAND_DISCOUNTS.get(
brand,
self.BRAND_DISCOUNTS['default']
)
cost_price = base_price * discount
# Розничная цена (пока = базовая)
retail_price = base_price
return {
'base_price': base_price,
'cost_price': cost_price,
'retail_price': retail_price,
'margin': base_price - cost_price,
}
def _is_fireworks(self, name):
"""Проверка на хлопушки/бенгалки"""
return bool(re.search(r'(хлопушк|бенгал)', name, re.I))
data/connectors/
└── gordeev/
├── adapter_products.py ← Адаптер товаров → PIM
├── adapter_pricing.py ← Адаптер цен → ERP
├── import_to_pim.py ← Использует adapter_products
└── sync_to_erp.py ← Использует adapter_pricing
data/connectors/
└── jf_pyro/
├── adapter_products.py
├── adapter_pricing.py
├── import_to_pim.py
└── sync_to_erp.py
| Вопрос | Адаптер товаров | Адаптер цен |
|---|---|---|
| Извлечь название? | ✅ Да | ❌ Нет |
| Извлечь остаток? | ✅ Да | ❌ Нет |
| Извлечь базовую цену из прайса? | ✅ Да (price_raw) | ❌ Нет |
| Рассчитать себестоимость? | ❌ Нет | ✅ Да |
| Рассчитать розничную цену? | ❌ Нет | ✅ Да |
| Применить скидки по брендам? | ❌ Нет | ✅ Да |
| Знать о маржинальности? | ❌ Нет | ✅ Да |
Разделение ответственности
- PIM не знает о бизнес-логике цен
- ERP не знает о форматах прайсов
Переиспользование
- Один адаптер товаров → много адаптеров цен
- Разные цены для опта, розницы, маркетплейсов
Тестирование
- Тестируем маппинг полей отдельно
- Тестируем формулы цен отдельно
Масштабирование
- Легко добавить нового поставщика
- Легко изменить правила ценообразования
from data.connectors.gordeev import GordeevProductsAdapter
adapter = GordeevProductsAdapter()
df = pd.read_excel('gordeev.xlsx')
for row in df.iterrows():
product = adapter.extract(row)
pim.upsert(product) # Только товары, цена price_raw
from data.connectors.gordeev import GordeevPricingAdapter
pricing = GordeevPricingAdapter()
products = pim.get_all(supplier='ИП Гордеев')
for product in products:
prices = pricing.calculate_prices(product)
erp.update_prices(product['article'], prices)
| Адаптер | Имя файла | Класс |
|---|---|---|
| Товары | adapter_products.py |
{Supplier}ProductsAdapter |
| Цены | adapter_pricing.py |
{Supplier}PricingAdapter |
Пример:
- GordeevProductsAdapter
- GordeevPricingAdapter
- JFPyroProductsAdapter
- JFPyroPricingAdapter
Версия: 1.0.0
Дата: 2025-12-24
Автор: Claude Sonnet 4.5