From f6d186ab5647de673c0698f5d498c1a3de71ffee Mon Sep 17 00:00:00 2001 From: zosimovaa Date: Sat, 8 Nov 2025 21:46:34 +0300 Subject: [PATCH] Try parse first excel file --- .gitignore | 3 +- src/MailOrderBot.egg-info/PKG-INFO | 9 ++- src/MailOrderBot.egg-info/SOURCES.txt | 9 ++- src/MailOrderBot.egg-info/requires.txt | 1 + src/mail_order_bot/config.yml | 64 ++++++++++++++- .../excel_processor/__init__.py | 1 + .../excel_processor/configurable_parser.py | 4 +- .../excel_processor/excel_parser.py | 14 +++- .../{excel_processor.py => processor.py} | 14 ++-- src/mail_order_bot/main.py | 5 +- src/mail_order_bot/suppliers.yml | 56 ++++++++++++++ tests/excel_processor/processor_test.py | 36 +++++++++ tests/excel_processor/suppliers.yml | 77 +++++++++++++++++++ 13 files changed, 270 insertions(+), 23 deletions(-) create mode 100644 src/mail_order_bot/excel_processor/__init__.py rename src/mail_order_bot/excel_processor/{excel_processor.py => processor.py} (92%) create mode 100644 src/mail_order_bot/suppliers.yml create mode 100644 tests/excel_processor/processor_test.py create mode 100644 tests/excel_processor/suppliers.yml diff --git a/.gitignore b/.gitignore index 80238fb..6c19f7c 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ __pycache__ .env .cursorignore -logs/ \ No newline at end of file +logs/ +files/ \ No newline at end of file diff --git a/src/MailOrderBot.egg-info/PKG-INFO b/src/MailOrderBot.egg-info/PKG-INFO index 496bbdf..01554ec 100644 --- a/src/MailOrderBot.egg-info/PKG-INFO +++ b/src/MailOrderBot.egg-info/PKG-INFO @@ -1,11 +1,12 @@ Metadata-Version: 2.4 Name: MailOrderBot -Version: 1.0.2 +Version: 1.0.4 Summary: Config manager for building applications Author-email: Aleksei Zosimov -Project-URL: Homepage, https://git.lesha.spb.ru/alex/config_manager -Project-URL: Documentation, https://git.lesha.spb.ru/alex/config_manager -Project-URL: Repository, https://git.lesha.spb.ru/alex/config_manager +Project-URL: Homepage, https://git.lesha.spb.ru/alex/mail_order_bot +Project-URL: Documentation, https://git.lesha.spb.ru/alex/mail_order_bot +Project-URL: Repository, https://git.lesha.spb.ru/alex/mail_order_bot Requires-Python: >=3.12 Description-Content-Type: text/markdown Requires-Dist: python-dotenv>=1.0.0 +Requires-Dist: config_manager@ git+https://git.lesha.spb.ru/alex/config_manager.git@master diff --git a/src/MailOrderBot.egg-info/SOURCES.txt b/src/MailOrderBot.egg-info/SOURCES.txt index de6e99c..5c20e52 100644 --- a/src/MailOrderBot.egg-info/SOURCES.txt +++ b/src/MailOrderBot.egg-info/SOURCES.txt @@ -8,10 +8,11 @@ src/MailOrderBot.egg-info/top_level.txt src/mail_order_bot/__init__.py src/mail_order_bot/main.py src/mail_order_bot/email_client/__init__.py -src/mail_order_bot/email_client/email_client.py -src/mail_order_bot/email_client/email_objects.py +src/mail_order_bot/email_client/client.py +src/mail_order_bot/email_client/objects.py +src/mail_order_bot/excel_processor/__init__.py src/mail_order_bot/excel_processor/configurable_parser.py src/mail_order_bot/excel_processor/excel_parser.py -src/mail_order_bot/excel_processor/excel_processor.py src/mail_order_bot/excel_processor/order_position.py -src/mail_order_bot/excel_processor/parser_factory.py \ No newline at end of file +src/mail_order_bot/excel_processor/parser_factory.py +src/mail_order_bot/excel_processor/processor.py \ No newline at end of file diff --git a/src/MailOrderBot.egg-info/requires.txt b/src/MailOrderBot.egg-info/requires.txt index 0eed382..aebebbc 100644 --- a/src/MailOrderBot.egg-info/requires.txt +++ b/src/MailOrderBot.egg-info/requires.txt @@ -1 +1,2 @@ python-dotenv>=1.0.0 +config_manager@ git+https://git.lesha.spb.ru/alex/config_manager.git@master diff --git a/src/mail_order_bot/config.yml b/src/mail_order_bot/config.yml index 8ce0a0e..f6b32b5 100644 --- a/src/mail_order_bot/config.yml +++ b/src/mail_order_bot/config.yml @@ -1,11 +1,67 @@ -# === Раздел с общими конфигурационными параметрами === +# Настройки обработки ================================================================= +suppliers: + # Контрагент A - стандартный формат + autostels: + sheet_name: "Лист1" # Название листа Excel + header_row: 2 # Номер строки с заголовками (0 = первая) + + # Маппинг: внутреннее_поле -> название_колонки_в_Excel + mapping: + article: "№ Детали" + manufacturer: "Производитель" + name: "Наименование" + price: "Прайс" + quantity: "Количество" + total: "Сумма" + + # Дополнительные настройки (опционально) + options: + decimal_separator: "," + encoding: "utf-8" + + # Контрагент B - формат с английскими названиями + parterra: + sheet_name: "TDSheet" + header_row: 6 # Заголовки во второй строке + + mapping: + article: "Артикул поставщика" + manufacturer: "Производитель Поставщика" + name: "Номенклатура" + price: "Цена" + quantity: "Количество (в единицах хранения)" + total: "Сумма с НДС" + + options: + decimal_separator: "," + encoding: "utf-8" + #thousand_separator: "," + + # Контрагент C - с запятой как разделителем + part-kom: + sheet_name: "Лист_1" # Можно указать индекс листа + header_row: 5 + + mapping: + article: "Артикул" + manufacturer: "Изготовитель" + name: "Наименование товара" + price: "Цена" + quantity: "Кол-во" + total: "Сумма" + + options: + #skip_footer_rows: 3 + decimal_separator: "," + + +# Раздел с общими конфигурационными параметрами =============================== update_interval: 10 work_interval: 30 - email_dir: "spareparts" -# === Логирование === +# Логирование ================================================================= log: version: 1 disable_existing_loggers: False @@ -40,7 +96,7 @@ log: alias: "Mail order bot" -# -- Логгеры -- + # Логгеры loggers: '': handlers: [console, file, telegram] diff --git a/src/mail_order_bot/excel_processor/__init__.py b/src/mail_order_bot/excel_processor/__init__.py new file mode 100644 index 0000000..329f916 --- /dev/null +++ b/src/mail_order_bot/excel_processor/__init__.py @@ -0,0 +1 @@ +from .processor import ExcelProcessor diff --git a/src/mail_order_bot/excel_processor/configurable_parser.py b/src/mail_order_bot/excel_processor/configurable_parser.py index a07e4a2..ea55c5a 100644 --- a/src/mail_order_bot/excel_processor/configurable_parser.py +++ b/src/mail_order_bot/excel_processor/configurable_parser.py @@ -15,10 +15,10 @@ class ConfigurableExcelParser(ExcelParser): Подходит для большинства стандартных случаев. """ - def parse(self, filepath: str) -> List[OrderPosition]: + def parse(self, file_bytes: str) -> List[OrderPosition]: try: # Читаем Excel - df = self._read_excel(filepath) + df = self._read_excel(file_bytes) # Удаляем пустые строки df = df.dropna(how='all') diff --git a/src/mail_order_bot/excel_processor/excel_parser.py b/src/mail_order_bot/excel_processor/excel_parser.py index 8ae12f9..8d41117 100644 --- a/src/mail_order_bot/excel_processor/excel_parser.py +++ b/src/mail_order_bot/excel_processor/excel_parser.py @@ -2,6 +2,7 @@ import logging import pandas as pd from abc import ABC, abstractmethod from typing import Dict, Any, List +from io import BytesIO from .order_position import OrderPosition @@ -26,7 +27,7 @@ class ExcelParser(ABC): """ pass - def _read_excel(self, filepath: str) -> pd.DataFrame: + def _read_excel_from_file(self, filepath: str) -> pd.DataFrame: """Общий метод для чтения Excel файлов""" return pd.read_excel( filepath, @@ -35,3 +36,14 @@ class ExcelParser(ABC): #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' + ) + diff --git a/src/mail_order_bot/excel_processor/excel_processor.py b/src/mail_order_bot/excel_processor/processor.py similarity index 92% rename from src/mail_order_bot/excel_processor/excel_processor.py rename to src/mail_order_bot/excel_processor/processor.py index 3fa59e2..e07bdda 100644 --- a/src/mail_order_bot/excel_processor/excel_processor.py +++ b/src/mail_order_bot/excel_processor/processor.py @@ -14,7 +14,7 @@ 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._setup_logging() @@ -27,7 +27,9 @@ class ExcelProcessor: def process_file( self, - filepath: str, + #filepath: str, + file_bytes: str, + file_name: str, supplier_name: str, validate: bool = True ) -> List[OrderPosition]: @@ -46,15 +48,15 @@ class ExcelProcessor: ValueError: Если контрагент не найден FileNotFoundError: Если файл не найден """ - logger.info(f"Начало обработки файла: {filepath} для {supplier_name}") + logger.info(f"Начало обработки файла: {file_name} для {supplier_name}") # Проверка существования файла - if not Path(filepath).exists(): - raise FileNotFoundError(f"Файл не найден: {filepath}") + #if not Path(filepath).exists(): + # raise FileNotFoundError(f"Файл не найден: {filepath}") # Получаем парсер и обрабатываем parser = self.factory.get_parser(supplier_name) - positions = parser.parse(filepath) + positions = parser.parse(file_bytes) # Дополнительная валидация если нужна if validate: diff --git a/src/mail_order_bot/main.py b/src/mail_order_bot/main.py index 51d3768..9e70ceb 100644 --- a/src/mail_order_bot/main.py +++ b/src/mail_order_bot/main.py @@ -7,6 +7,7 @@ import os from dotenv import load_dotenv from email_client import EmailClient +from excel_proceccor import ExcelProcessor logger = logging.getLogger() @@ -27,8 +28,10 @@ class MailOrderBot(ConfigManager): def execute(self): logger.debug(f"Check emails for new orders") - emails = self.email_client.get_emails(folder="spareparts", only_unseen=True, mark_as_read=True) + + + for email in emails: logger.info(email.subj) logger.info(email.from_addr) diff --git a/src/mail_order_bot/suppliers.yml b/src/mail_order_bot/suppliers.yml new file mode 100644 index 0000000..7fc6855 --- /dev/null +++ b/src/mail_order_bot/suppliers.yml @@ -0,0 +1,56 @@ +suppliers: + # order@stparts.ru + "order@stparts.ru": + sheet_name: "TDSheet" # Название листа Excel + header_row: 0 # Номер строки с заголовками (0 = первая) + + # Маппинг: внутреннее_поле -> название_колонки_в_Excel + mapping: + article: "Номер" + manufacturer: "Бренд" + name: "Описание" + price: "Цена" + quantity: "Количество" + #total: "Сумма" + #Вопросы: что за поле "Фактическая_отгрузка"? + + # Дополнительные настройки (опционально) + options: + decimal_separator: "," + encoding: "utf-8" + + # Рай Авто СПб + EMPTY-FROM: + sheet_name: 0 + header_row: 2 # Заголовки во второй строке + + mapping: + article: "Артикул" + manufacturer: "Производитель" + name: "Название" + price: "Цена" + quantity: "Количество" + #total: "Сумма с НДС" + + options: + decimal_separator: "," + encoding: "utf-8" + #thousand_separator: "," + # Примечание: гемор - нет имейла + + # АвтоТО + "order@avtoto.ru": + sheet_name: "Заказы" # Можно указать индекс листа + header_row: 4 + + mapping: + article: "Артикул" + manufacturer: "Изготовитель" + name: "Наименование товара" + price: "Цена" + quantity: "Кол-во" + total: "Сумма" + + options: + #skip_footer_rows: 3 + decimal_separator: "," diff --git a/tests/excel_processor/processor_test.py b/tests/excel_processor/processor_test.py new file mode 100644 index 0000000..2277e23 --- /dev/null +++ b/tests/excel_processor/processor_test.py @@ -0,0 +1,36 @@ +import os +import chardet # pip install chardet + +from ..src.mail_order_bot.excel_processor import ExcelProcessor + + +BASE_PATH = './files' + + +ep = ExcelProcessor("./suppliers.yml") + +print("================================================") +for provider_name in os.listdir(BASE_PATH): + print(f'Провайдер: {provider_name}') + + + provider_folder = os.path.join(BASE_PATH, provider_name) + if os.path.isdir(provider_folder): + for file_name in os.listdir(provider_folder): + file_path = os.path.join(provider_folder, file_name) + if os.path.isfile(file_path): + with open(file_path, 'rb') as file: # бинарный режим + raw_data = file.read() + detected = chardet.detect(raw_data) + encoding = detected['encoding'] or 'utf-8' + try: + data = raw_data.decode(encoding) + except (UnicodeDecodeError, TypeError): + # Если декодировать не удалось, попробуем utf-8 игнорируя ошибки + data = raw_data.decode('utf-8', errors='ignore') + + print(f'Файл: {file_name}') + + + + #print(f'Содержимое: {data}') diff --git a/tests/excel_processor/suppliers.yml b/tests/excel_processor/suppliers.yml new file mode 100644 index 0000000..5abacf3 --- /dev/null +++ b/tests/excel_processor/suppliers.yml @@ -0,0 +1,77 @@ +suppliers: + # order@stparts.ru + "order@stparts.ru": + sheet_name: "TDSheet" # Название листа Excel + header_row: 0 # Номер строки с заголовками (0 = первая) + + # Маппинг: внутреннее_поле -> название_колонки_в_Excel + mapping: + article: "Номер" + manufacturer: "Бренд" + name: "Описание" + price: "Цена" + quantity: "Количество" + #total: "Сумма" + #Вопросы: что за поле "Фактическая_отгрузка"? + + # Дополнительные настройки (опционально) + options: + decimal_separator: "," + encoding: "utf-8" + + # Рай Авто СПб + EMPTY-FROM: + sheet_name: 0 + header_row: 2 # Заголовки во второй строке + + mapping: + article: "Артикул" + manufacturer: "Производитель" + name: "Название" + price: "Цена" + quantity: "Количество" + #total: "Сумма с НДС" + + options: + decimal_separator: "," + encoding: "utf-8" + #thousand_separator: "," + # Примечание: гемор - нет имейла + + # АвтоТО + "order@avtoto.ru": + sheet_name: "Заказы" # Можно указать индекс листа + header_row: 4 + + mapping: + article: "Артикул" + manufacturer: "Изготовитель" + name: "Наименование товара" + price: "Цена" + quantity: "Кол-во" + total: "Сумма" + + options: + #skip_footer_rows: 3 + decimal_separator: "," + + + + # автолига.рф + "автолига.рф": + sheet_name: 0 # Можно указать индекс листа + header_row: 8 + + mapping: + article: "Артикул" + manufacturer: "Производитель" + name: "Название детали" + price: "Цена, р." + quantity: "Количество" + total: "Сумма, р." + + options: + #skip_footer_rows: 3 + decimal_separator: "," + + \ No newline at end of file