Настройка парсера

This commit is contained in:
2025-11-10 21:45:14 +03:00
parent f6d186ab56
commit 0db1509f0f
7 changed files with 602 additions and 135 deletions

View File

@@ -9,6 +9,7 @@ from .order_position import OrderPosition
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class ConfigurableExcelParser(ExcelParser): class ConfigurableExcelParser(ExcelParser):
""" """
Универсальный парсер, настраиваемый через конфигурацию. Универсальный парсер, настраиваемый через конфигурацию.
@@ -18,10 +19,7 @@ class ConfigurableExcelParser(ExcelParser):
def parse(self, file_bytes: str) -> List[OrderPosition]: def parse(self, file_bytes: str) -> List[OrderPosition]:
try: try:
# Читаем Excel # Читаем Excel
df = self._read_excel(file_bytes) df = self._make_dataframe(file_bytes)
# Удаляем пустые строки
df = df.dropna(how='all')
# Получаем маппинг колонок из конфигурации # Получаем маппинг колонок из конфигурации
mapping = self.config['mapping'] mapping = self.config['mapping']
@@ -34,36 +32,50 @@ class ConfigurableExcelParser(ExcelParser):
if position: if position:
positions.append(position) positions.append(position)
except Exception as e: except Exception as e:
logger.warning(f"Ошибка парсинга строки {idx}: {e}") logger.error(f"Ошибка парсинга строки {idx}: {e}, {row}")
continue continue
logger.info(f"Успешно обработано {len(positions)} позиций из {len(df)} строк") logger.info(f"Успешно обработано {len(positions)} позиций из {len(df)} строк")
return positions return positions
except Exception as e: except Exception as e:
logger.error(f"Ошибка при обработке файла {filepath}: {e}") logger.error(f"Ошибка при обработке файла: {e}")
raise raise Exception from e
def _parse_row(self, row: pd.Series, mapping: Dict[str, str]) -> Optional[OrderPosition]: def _parse_row(self, row: pd.Series, mapping: Dict[str, str]) -> Optional[OrderPosition]:
"""Парсит одну строку Excel в OrderPosition""" """Парсит одну строку Excel в OrderPosition"""
# Проверяем обязательные поля # Проверяем обязательные поля
required_fields = ['article', 'manufacturer', 'name', 'price', 'quantity', 'total'] required_fields = ['article', 'price', 'quantity']
for field in required_fields: for field in required_fields:
if pd.isna(row.get(mapping[field])): if pd.isna(row.get(mapping[field])):
logger.warning(f"Позиция не создана - не заполнено поле {mapping[field]}")
return None return None
price = Decimal(str(row[mapping['price']]).replace(",", ".").strip())
quantity = int(row[mapping['quantity']])
if "total" in mapping.keys():
total = Decimal(str(row[mapping['total']]).replace(",", ".").strip())
else:
total = price * quantity
if mapping.get('name',"") in mapping.keys():
name = str(row[mapping.get('name', "")]).strip()
else:
name = ""
# Создаем объект позиции # Создаем объект позиции
position = OrderPosition( position = OrderPosition(
article=str(row[mapping['article']]).strip(), article=str(row[mapping['article']]).strip(),
manufacturer=str(row[mapping['manufacturer']]).strip(), manufacturer=str(row[mapping.get('manufacturer',"")]).strip(),
name=str(row[mapping['name']]).strip(), name=name,
price=Decimal(str(row[mapping['price']])), price=price,
quantity=int(row[mapping['quantity']]), quantity=quantity,
total=Decimal(str(row[mapping['total']])), total=total,
additional_attrs=self._extract_additional_attrs(row, mapping) additional_attrs=self._extract_additional_attrs(row, mapping)
) )
return position return position
def _extract_additional_attrs(self, row: pd.Series, mapping: Dict[str, str]) -> Dict[str, Any]: def _extract_additional_attrs(self, row: pd.Series, mapping: Dict[str, str]) -> Dict[str, Any]:
@@ -76,3 +88,26 @@ class ConfigurableExcelParser(ExcelParser):
additional[col] = row[col] additional[col] = row[col]
return additional return additional
def _make_dataframe(self, bio) -> pd.DataFrame:
# Получаем все данные из файла
sheet_name = self.config.get("sheet_name", 0)
df_full = pd.read_excel(bio, sheet_name=sheet_name, header=None)
# Находим индекс строки с заголовком
key_field = self.config.get("key_field")
header_row_idx = df_full[
df_full.apply(lambda row: row.astype(str).str.contains(key_field, case=False, na=False).any(),
axis=1)].index[0]
# Считываем таблицу с правильным заголовком
df = pd.read_excel(bio, header=header_row_idx, sheet_name=sheet_name, engine='calamine') #openpyxl calamine
# Находим индекс первой строки с пустым 'Артикул'
first_empty_index = df[df[key_field].isna()].index.min()
# Обрезаем DataFrame до первой пустой строки (не включая её)
df_trimmed = df.loc[:first_empty_index - 1]
return df_trimmed

View File

@@ -0,0 +1,105 @@
import logging
import pandas as pd
from typing import Dict, Any, Optional, List
from decimal import Decimal
import xlrd
from io import BytesIO
from .excel_parser import ExcelParser
from .order_position import OrderPosition
logger = logging.getLogger(__name__)
class CustomExcelParserAutoeuro(ExcelParser):
"""
Универсальный парсер, настраиваемый через конфигурацию.
Подходит для большинства стандартных случаев.
"""
def parse(self, file_bytes: BytesIO) -> List[OrderPosition]:
try:
# Читаем Excel
df = self._make_dataframe(file_bytes)
# Получаем маппинг колонок из конфигурации
mapping = self.config['mapping']
# Парсим строки
positions = []
for idx, row in df.iterrows():
try:
position = self._parse_row(row, mapping)
if position:
positions.append(position)
except Exception as e:
logger.error(f"Ошибка парсинга строки {idx}: {e}, {row}")
continue
logger.info(f"Успешно обработано {len(positions)} позиций из {len(df)} строк")
return positions
except Exception as e:
logger.error(f"Ошибка при обработке файла: {e}")
raise Exception from e
def _parse_row(self, row: pd.Series, mapping: Dict[str, str]) -> Optional[OrderPosition]:
"""Парсит одну строку Excel в OrderPosition"""
# Проверяем обязательные поля
required_fields = ['article', 'price', 'quantity']
for field in required_fields:
if pd.isna(row.get(mapping[field])):
logger.warning(f"Позиция не создана - не заполнено поле {mapping[field]}")
return None
price = Decimal(str(row[mapping['price']]).replace(",", ".").strip())
quantity = int(row[mapping['quantity']])
if "total" in mapping.keys():
total = Decimal(str(row[mapping['total']]).replace(",", ".").strip())
else:
total = price * quantity
# Создаем объект позиции
position = OrderPosition(
article=str(row[mapping['article']]).strip(),
manufacturer=str(row[mapping.get('manufacturer', "")]).strip(),
name="", #str(row[mapping.get('name', "name")]).strip(),
price=price,
quantity=quantity,
total=total,
additional_attrs=self._extract_additional_attrs(row, mapping)
)
return position
def _extract_additional_attrs(self, row: pd.Series, mapping: Dict[str, str]) -> Dict[str, Any]:
"""Извлекает дополнительные атрибуты, не входящие в основную модель"""
additional = {}
mapped_columns = set(mapping.values())
for col in row.index:
if col not in mapped_columns and not pd.isna(row[col]):
additional[col] = row[col]
return additional
def _make_dataframe(self, bio) -> pd.DataFrame:
file_bytes = bio.read()
book = xlrd.open_workbook(file_contents=file_bytes, encoding_override='cp1251')
sheet = book.sheet_by_index(self.config.get("sheet_index", 0))
data = [sheet.row_values(row) for row in range(sheet.nrows)]
df_full = pd.DataFrame(data)
key_field = self.config.get("key_field")
header_row_idx = df_full[
df_full.apply(lambda row: row.astype(str).str.contains(key_field, case=False, na=False).any(),
axis=1)].index[0]
df = df_full[header_row_idx:]
df.columns = df.iloc[0] # первая строка становится заголовком
df = df.reset_index(drop=True).drop(0).reset_index(drop=True) # удаляем первую строку и сбрасываем индекс
return df

View File

@@ -20,30 +20,9 @@ class ExcelParser(ABC):
self.config = config self.config = config
@abstractmethod @abstractmethod
def parse(self, filepath: str) -> List[OrderPosition]: def parse(self, file: BytesIO) -> List[OrderPosition]:
""" """
Парсит Excel файл и возвращает список позиций. Парсит Excel файл и возвращает список позиций.
Должен быть реализован в каждом конкретном парсере. Должен быть реализован в каждом конкретном парсере.
""" """
pass pass
def _read_excel_from_file(self, filepath: str) -> pd.DataFrame:
"""Общий метод для чтения Excel файлов"""
return pd.read_excel(
filepath,
sheet_name=self.config.get('sheet_name', 0),
header=self.config.get('header_row', 0),
#engine='openpyxl'
engine='calamine'
)
def _read_excel(self, file_content: bytes) -> pd.DataFrame:
"""Общий метод для чтения Excel файлов из байтового содержимого файла"""
bio = BytesIO(file_content)
return pd.read_excel(
bio,
sheet_name=self.config.get('sheet_name', 0),
header=self.config.get('header_row', 0),
engine='calamine'
)

View File

@@ -6,6 +6,7 @@ from typing import Dict, Any, List
from .excel_parser import ExcelParser from .excel_parser import ExcelParser
from .configurable_parser import ConfigurableExcelParser from .configurable_parser import ConfigurableExcelParser
from .custom_parser_autoeuro import CustomExcelParserAutoeuro
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -18,48 +19,36 @@ class ParserFactory:
# Реестр кастомных парсеров # Реестр кастомных парсеров
CUSTOM_PARSERS = { CUSTOM_PARSERS = {
#'supplier_a': SupplierAParser, 'autoeuro.ru': CustomExcelParserAutoeuro,
# Добавляйте сюда специализированные парсеры # Добавляйте сюда специализированные парсеры
} }
def __init__(self, config_path: str): def __init__(self, config: Dict[str, Any]):
self.config_path = Path(config_path) self.config = config
self.suppliers_config = self._load_config()
def _load_config(self) -> Dict[str, Any]:
"""Загружает конфигурацию из YAML или JSON"""
if self.config_path.suffix in ['.yaml', '.yml']:
with open(self.config_path, 'r', encoding='utf-8') as f:
return yaml.safe_load(f)
elif self.config_path.suffix == '.json':
with open(self.config_path, 'r', encoding='utf-8') as f:
return json.load(f)
else:
raise ValueError(f"Неподдерживаемый формат конфига: {self.config_path.suffix}")
def get_parser(self, supplier_name: str) -> ExcelParser: def get_parser(self, supplier_name: str) -> ExcelParser:
""" """
Возвращает парсер для указанного контрагента. Возвращает парсер для указанного контрагента.
Использует кастомный парсер если есть, иначе конфигурируемый. Использует кастомный парсер если есть, иначе конфигурируемый.
""" """
if supplier_name not in self.suppliers_config['suppliers']: if supplier_name not in self.config['suppliers']:
raise ValueError( raise ValueError(
f"Контрагент '{supplier_name}' не найден в конфигурации. " f"Контрагент '{supplier_name}' не найден в конфигурации. "
f"Доступные: {list(self.suppliers_config['suppliers'].keys())}" f"Доступные: {list(self.config['suppliers'].keys())}"
) )
config = self.suppliers_config['suppliers'][supplier_name] config = self.config['suppliers'][supplier_name]
# Проверяем, есть ли кастомный парсер # Проверяем, есть ли кастомный парсер
if supplier_name in self.CUSTOM_PARSERS: if supplier_name in self.CUSTOM_PARSERS:
parser_class = self.CUSTOM_PARSERS[supplier_name] parser_class = self.CUSTOM_PARSERS[supplier_name]
logger.info(f"Используется кастомный парсер для {supplier_name}") logger.debug(f"Используется кастомный парсер для {supplier_name}")
else: else:
parser_class = ConfigurableExcelParser parser_class = ConfigurableExcelParser
logger.info(f"Используется конфигурируемый парсер для {supplier_name}") logger.debug(f"Используется конфигурируемый парсер для {supplier_name}")
return parser_class(config) return parser_class(config)
def list_suppliers(self) -> List[str]: def list_suppliers(self) -> List[str]:
"""Возвращает список всех доступных контрагентов""" """Возвращает список всех доступных контрагентов"""
return list(self.suppliers_config['suppliers'].keys()) return list(self.config['suppliers'].keys())

View File

@@ -1,6 +1,11 @@
import logging import logging
from typing import List
from pathlib import Path from pathlib import Path
from decimal import Decimal
from io import BytesIO
from typing import Dict, Any, List
import yaml
import json
from .parser_factory import ParserFactory from .parser_factory import ParserFactory
from .order_position import OrderPosition from .order_position import OrderPosition
@@ -15,29 +20,17 @@ class ExcelProcessor:
""" """
def __init__(self, config_path: str = 'config/suppliers.yaml', ): def __init__(self, config_path: str = 'config/suppliers.yaml', ):
self.factory = ParserFactory(config_path) self.config_path = Path(config_path)
self._setup_logging() self.config = self._load_config()
self.factory = ParserFactory(self.config)
def _setup_logging(self): def process(self, file_bytes: BytesIO, file_name: str, supplier_name: str, validate: bool = False) -> List[OrderPosition]:
"""Настройка логирования"""
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
def process_file(
self,
#filepath: str,
file_bytes: str,
file_name: str,
supplier_name: str,
validate: bool = True
) -> List[OrderPosition]:
""" """
Обрабатывает Excel файл от контрагента. Обрабатывает Excel файл от контрагента.
Args: Args:
filepath: Путь к Excel файлу file_bytes: Байты файла
file_name: Имя файла
supplier_name: Название контрагента (из конфигурации) supplier_name: Название контрагента (из конфигурации)
validate: Выполнять ли дополнительную валидацию validate: Выполнять ли дополнительную валидацию
@@ -46,15 +39,9 @@ class ExcelProcessor:
Raises: Raises:
ValueError: Если контрагент не найден ValueError: Если контрагент не найден
FileNotFoundError: Если файл не найден
""" """
logger.info(f"Начало обработки файла: {file_name} для {supplier_name}") logger.info(f"Обработка файла: {file_name} для {supplier_name}")
# Проверка существования файла
#if not Path(filepath).exists():
# raise FileNotFoundError(f"Файл не найден: {filepath}")
# Получаем парсер и обрабатываем
parser = self.factory.get_parser(supplier_name) parser = self.factory.get_parser(supplier_name)
positions = parser.parse(file_bytes) positions = parser.parse(file_bytes)
@@ -62,9 +49,24 @@ class ExcelProcessor:
if validate: if validate:
positions = self._validate_positions(positions) positions = self._validate_positions(positions)
logger.info(f"Обработка завершена: получено {len(positions)} позиций") logger.debug(f"Обработка завершена: получено {len(positions)} позиций")
return positions return positions
def process_file(self, file_path: str, supplier_name: str, validate: bool = False) -> List[OrderPosition]:
# Проверка существования файла
logger.debug(f"Чтение файла: {file_path}")
if not Path(file_path).exists():
raise FileNotFoundError(f"Файл не найден: {file_path}")
with open(file_path, 'rb') as file: # бинарный режим
raw_data = file.read()
bio = BytesIO(raw_data)
positions = self.process(bio, file_path, supplier_name, validate=validate)
return positions
def _validate_positions(self, positions: List[OrderPosition]) -> List[OrderPosition]: def _validate_positions(self, positions: List[OrderPosition]) -> List[OrderPosition]:
"""Дополнительная валидация позиций""" """Дополнительная валидация позиций"""
valid_positions = [] valid_positions = []
@@ -95,3 +97,14 @@ class ExcelProcessor:
def get_available_suppliers(self) -> List[str]: def get_available_suppliers(self) -> List[str]:
"""Возвращает список доступных контрагентов""" """Возвращает список доступных контрагентов"""
return self.factory.list_suppliers() return self.factory.list_suppliers()
def _load_config(self) -> Dict[str, Any]:
"""Загружает конфигурацию из YAML или JSON"""
if self.config_path.suffix in ['.yaml', '.yml']:
with open(self.config_path, 'r', encoding='utf-8') as f:
return yaml.safe_load(f)
elif self.config_path.suffix == '.json':
with open(self.config_path, 'r', encoding='utf-8') as f:
return json.load(f)
else:
raise ValueError(f"Неподдерживаемый формат конфига: {self.config_path.suffix}")

View File

@@ -1,19 +1,24 @@
import os import os
import chardet # pip install chardet import chardet # pip install chardet
import traceback
from mail_order_bot.excel_processor import ExcelProcessor
from ..src.mail_order_bot.excel_processor import ExcelProcessor # установим рабочую директорию
import os
os.chdir(os.path.dirname(os.path.abspath(__file__)))
from io import BytesIO
import logging
logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.WARNING, format='%(module)s - %(message)s') #%(asctime)s -
BASE_PATH = './files' BASE_PATH = './files'
ep = ExcelProcessor("./suppliers.yml") processor = ExcelProcessor("./suppliers.yml")
print("================================================")
for provider_name in os.listdir(BASE_PATH): for provider_name in os.listdir(BASE_PATH):
print(f'Провайдер: {provider_name}')
provider_folder = os.path.join(BASE_PATH, provider_name) provider_folder = os.path.join(BASE_PATH, provider_name)
if os.path.isdir(provider_folder): if os.path.isdir(provider_folder):
for file_name in os.listdir(provider_folder): for file_name in os.listdir(provider_folder):
@@ -21,16 +26,23 @@ for provider_name in os.listdir(BASE_PATH):
if os.path.isfile(file_path): if os.path.isfile(file_path):
with open(file_path, 'rb') as file: # бинарный режим with open(file_path, 'rb') as file: # бинарный режим
raw_data = file.read() raw_data = file.read()
detected = chardet.detect(raw_data) bio = BytesIO(raw_data)
encoding = detected['encoding'] or 'utf-8'
print("========================================================")
print(f'Обработка: {provider_name} - {file_name}')
try: try:
data = raw_data.decode(encoding) positions_a = processor.process(
except (UnicodeDecodeError, TypeError): file_bytes=bio,
# Если декодировать не удалось, попробуем utf-8 игнорируя ошибки file_name=file_name,
data = raw_data.decode('utf-8', errors='ignore') supplier_name=provider_name
)
print(f'Файл: {file_name}') print(f"\nПолучено {len(positions_a)} позиций от {provider_name}:")
for pos in positions_a: # Первые 5
print(f" - {pos.article}: {pos.name} "
f"({pos.quantity} x {pos.price} = {pos.total})")
except Exception as e:
print(f"Ошибка обработки: {e}", traceback.format_exc())
#print(f'Содержимое: {data}')

View File

@@ -2,7 +2,7 @@ suppliers:
# order@stparts.ru # order@stparts.ru
"order@stparts.ru": "order@stparts.ru":
sheet_name: "TDSheet" # Название листа Excel sheet_name: "TDSheet" # Название листа Excel
header_row: 0 # Номер строки с заголовками (0 = первая) key_field: "Номер"
# Маппинг: внутреннее_поле -> названиеолонки_в_Excel # Маппинг: внутреннее_поле -> названиеолонки_в_Excel
mapping: mapping:
@@ -14,15 +14,9 @@ suppliers:
#total: "Сумма" #total: "Сумма"
#Вопросы: что за поле "Фактическая_отгрузка"? #Вопросы: что за поле "Фактическая_отгрузка"?
# Дополнительные настройки (опционально)
options:
decimal_separator: ","
encoding: "utf-8"
# Рай Авто СПб
EMPTY-FROM: EMPTY-FROM:
sheet_name: 0 sheet_name: 0
header_row: 2 # Заголовки во второй строке key_field: "Артикул" # Заголовки во второй строке
mapping: mapping:
article: "Артикул" article: "Артикул"
@@ -38,10 +32,9 @@ suppliers:
#thousand_separator: "," #thousand_separator: ","
# Примечание: гемор - нет имейла # Примечание: гемор - нет имейла
# АвтоТО
"order@avtoto.ru": "order@avtoto.ru":
sheet_name: "Заказы" # Можно указать индекс листа sheet_name: "Заказы" # Можно указать индекс листа
header_row: 4 key_field: "Артикул"
mapping: mapping:
article: "Артикул" article: "Артикул"
@@ -51,16 +44,9 @@ suppliers:
quantity: "Кол-во" quantity: "Кол-во"
total: "Сумма" total: "Сумма"
options:
#skip_footer_rows: 3
decimal_separator: ","
# автолига.рф
"автолига.рф": "автолига.рф":
sheet_name: 0 # Можно указать индекс листа sheet_name: 0 # Можно указать индекс листа
header_row: 8 key_field: "Артикул"
mapping: mapping:
article: "Артикул" article: "Артикул"
@@ -70,8 +56,356 @@ suppliers:
quantity: "Количество" quantity: "Количество"
total: "Сумма, р." total: "Сумма, р."
options: abstd.ru:
#skip_footer_rows: 3 sheet_name: 0 # Можно указать индекс листа
decimal_separator: "," key_field: "Артикул поставщика"
mapping:
article: "Артикул поставщика"
manufacturer: "Бренд поставщика"
name: "Наименование"
price: "Цена поставщика"
quantity: "Кол-во"
total: "Сумма"
adeo.pro:
sheet_name: 0 # Можно указать индекс листа
key_field: "Каталожный номер"
mapping:
article: "Каталожный номер"
manufacturer: "Производитель"
name: "Наименование"
price: "Цена"
quantity: "Кол-во"
total: "Сумма"
amtel.club:
sheet_name: 0 # Можно указать индекс листа
key_field: "Номер"
mapping:
article: "Номер"
manufacturer: "Фирма"
name: "Наименование"
price: "Цена"
quantity: "Кол-во"
total: "Сумма"
auto-sputnik.ru:
sheet_name: 0 # Можно указать индекс листа
key_field: "Артикул"
mapping:
article: "Артикул"
manufacturer: "Производитель"
name: "Наименование"
price: "Цена"
quantity: "Кол-во"
total: "Сумма"
autocode.ru:
sheet_name: 0 # Можно указать индекс листа
key_field: "Артикул"
mapping:
article: "Артикул"
manufacturer: "Бренд"
name: "Наименование"
price: "Цена"
quantity: "Кол-во"
# Надо ли как-то учитывать доп поля типа Кол-во в отказ?
autopiter.ru:
sheet_name: 0 # Можно указать индекс листа
key_field: "Номер"
mapping:
article: "Номер"
manufacturer: "Каталог"
price: "Цена"
quantity: "Кол-во"
autostels.ru:
sheet_name: 0 # Можно указать индекс листа
key_field: "№ Детали"
mapping:
article: "№ Детали"
manufacturer: "Производитель"
name: "Наименование"
price: "Прайс"
quantity: "Количество"
total: "Сумма"
avtoformula.ru:
sheet_name: 0 # Можно указать индекс листа
key_field: "№ Детали"
mapping:
article: "№ Детали"
manufacturer: "Производитель"
name: "Наименование"
price: "Прайс"
quantity: "Количество"
total: "Сумма"
autoeuro.ru:
sheet_name: 0 # Можно указать индекс листа
key_field: "Номер Производителя"
mapping:
article: "Номер Производителя"
manufacturer: "Производитель"
price: "Цена"
quantity: "Количество"
avtogut.ru:
sheet_name: 0 # Можно указать индекс листа
key_field: "Артикул"
mapping:
article: "Артикул"
manufacturer: "Производитель"
name: "Наименование"
price: "Цена, р."
quantity: "Количество"
total: "Сумма, р."
avtokrep.spb.ru:
sheet_name: 0 # Можно указать индекс листа
key_field: "Артикул"
mapping:
article: "Артикул"
manufacturer: "Бренд"
name: "Наименование"
price: "Цена"
quantity: "Количество"
avtolavka.net:
sheet_name: 0 # Можно указать индекс листа
key_field: "Номер"
mapping:
article: "Номер"
manufacturer: "Бренд"
name: "Описание"
price: "Цена закупки"
quantity: "Количество"
avtoto.ru:
sheet_name: 0 # Можно указать индекс листа
key_field: "Артикул"
mapping:
article: "Артикул"
manufacturer: "Производитель"
name: "Название"
price: "Цена (рубли)"
quantity: "Кол-во"
detal.msk.ru:
sheet_name: 0 # Можно указать индекс листа
key_field: "Артикул"
mapping:
article: "Артикул"
manufacturer: "Производитель"
name: "Наименование"
price: "Цена"
quantity: "Количество"
total: "Сумма"
detali.ru:
sheet_name: 0 # Можно указать индекс листа
key_field: "Код поставщика"
mapping:
article: "Код поставщика"
manufacturer: "Бренд"
name: "Наименование"
price: "Цена"
quantity: "Количество"
e-tape.ru:
sheet_name: 0 # Можно указать индекс листа
key_field: "Артикул"
mapping:
article: "Артикул"
manufacturer: "Производитель"
name: "Название детали"
price: "Цена, р."
quantity: "Количество"
total: "Сумма, р."
forum-auto.ru:
sheet_name: 0 # Можно указать индекс листа
key_field: "Номер по каталогу"
mapping:
article: "Номер по каталогу"
manufacturer: "Производитель"
name: "Название детали"
price: "Цена"
quantity: "Количество"
"info_avtor@mail.ru":
sheet_name: 0 # Можно указать индекс листа
key_field: "Артикул"
mapping:
article: "Артикул"
manufacturer: "Бренд"
name: "Товары (работы, услуги)"
price: "Цена"
quantity: "Количество"
total: "Сумма"
"mikado-parts.ru":
sheet_name: 0 # Можно указать индекс листа
key_field: "артикул"
mapping:
article: "артикул"
manufacturer: "бренд"
name: "наименование"
price: "цена"
quantity: "количество"
"multikrep.com":
sheet_name: 0 # Можно указать индекс листа
key_field: "Артикул"
mapping:
article: "Артикул"
manufacturer: "Бренд"
name: "Наименование"
price: "Цена"
quantity: "Количество"
"only-original.parts":
sheet_name: 0 # Можно указать индекс листа
key_field: "Номер"
mapping:
article: "Номер"
manufacturer: "Бренд"
name: "Описание"
price: "Цена закупки"
quantity: "Количество"
"part-kom.ru":
sheet_name: 0 # Можно указать индекс листа
key_field: "Код\nпоставщика"
mapping:
article: "Код\nпоставщика"
manufacturer: "Изготовитель"
name: "Наименование товара"
price: "Цена"
quantity: "Кол-во"
total: "Сумма"
"parterra.ru":
sheet_name: 0 # Можно указать индекс листа
key_field: "Артикул поставщика"
mapping:
article: "Артикул поставщика"
manufacturer: "Производитель Поставщика"
name: "Номенклатура"
price: "Цена"
quantity: "Количество (в единицах хранения)"
total: "Сумма с НДС"
"pmmobile.ru":
sheet_name: 0 # Можно указать индекс листа
key_field: "Номер"
mapping:
article: "Номер"
manufacturer: "Бренд"
name: "Описание"
price: "Цена закупки"
quantity: "Количество"
"rmsauto.ru":
sheet_name: 0 # Можно указать индекс листа
key_field: "Номер детали"
mapping:
article: "Номер детали"
manufacturer: "Производитель"
name: "Наименование детали"
price: "Цена, рублей (с НДС)"
quantity: "Количество заказанное, штук"
total: "Сумма, рублей (с НДС)"
"rnsprice.ru":
sheet_name: 0 # Можно указать индекс листа
key_field: "Артикул"
mapping:
article: "Артикул"
manufacturer: "Марка"
name: "Наименование"
price: "Цена"
quantity: "Кол-во"
total: "Сумма"
"stutzen.ru":
sheet_name: 0 # Можно указать индекс листа
key_field: "Артикул"
mapping:
article: "Артикул"
manufacturer: "Производитель"
name: "Название детали"
price: "Цена, р."
quantity: "Количество"
total: "Сумма, р."
"sz-snab.ru":
sheet_name: 0 # Можно указать индекс листа
key_field: "Номер"
mapping:
article: "Номер"
manufacturer: "Бренд"
name: "Описание"
price: "Цена закупки"
quantity: "Количество"
"todx.ru":
sheet_name: 0 # Можно указать индекс листа
key_field: "Код детали"
mapping:
article: "Код детали"
manufacturer: "Производитель"
name: "Наименование"
price: "Цена\nдетали"
quantity: "Кол-\nво"
total: "Сумма"
"uniqom.ru":
sheet_name: 0 # Можно указать индекс листа
key_field: "Артикул"
mapping:
article: "Артикул"
manufacturer: "Брэнд"
name: "Наименование товара"
price: "Цена"
quantity: "Заказ, кол-во"
total: "Сумма"
"Рай Авто СПб":
sheet_name: 0 # Можно указать индекс листа
key_field: "Артикул"
mapping:
article: "Артикул"
manufacturer: "Производитель"
name: "Название"
price: "Цена"
quantity: "Количество"