diff --git a/src/mail_order_bot/abcp_api/__init__.py b/src/mail_order_bot/abcp_api/__init__.py new file mode 100644 index 0000000..1e269b5 --- /dev/null +++ b/src/mail_order_bot/abcp_api/__init__.py @@ -0,0 +1,5 @@ +""" +Классы для работы с API платформы ABCP +""" + +from .abcp_provider import AbcpProvider diff --git a/src/mail_order_bot/email_processor/handlers/abcp/abcp_provider.py b/src/mail_order_bot/abcp_api/abcp_provider.py similarity index 97% rename from src/mail_order_bot/email_processor/handlers/abcp/abcp_provider.py rename to src/mail_order_bot/abcp_api/abcp_provider.py index c35c228..5502853 100644 --- a/src/mail_order_bot/email_processor/handlers/abcp/abcp_provider.py +++ b/src/mail_order_bot/abcp_api/abcp_provider.py @@ -31,6 +31,9 @@ class AbcpProvider: params = {"number": sku, "brand": manufacturer, "withOutAnalogs": "1"} return self._execute(path, method, params) + def save_order(self, order): + pass + def _execute(self, path, method="GET", params={}, data=None): params["userlogin"] = self.login params["userpsw"] = hashlib.md5(self.password.encode("utf-8")).hexdigest() diff --git a/src/mail_order_bot/configs/amtel.club.yml b/src/mail_order_bot/configs/amtel.club.yml index b4e8d69..3645a5a 100644 --- a/src/mail_order_bot/configs/amtel.club.yml +++ b/src/mail_order_bot/configs/amtel.club.yml @@ -1,3 +1,13 @@ +#========================================= +config: + + +pipeline: + - + + + +#========================================= pipeline: # Настраиваем парсинг экселя - handler: BasicExcelParser diff --git a/src/mail_order_bot/excel_parcer/__init__.py b/src/mail_order_bot/deprecated/old_excel_parcer/__init__.py similarity index 100% rename from src/mail_order_bot/excel_parcer/__init__.py rename to src/mail_order_bot/deprecated/old_excel_parcer/__init__.py diff --git a/src/mail_order_bot/excel_parcer/configurable_parser.py b/src/mail_order_bot/deprecated/old_excel_parcer/configurable_parser.py similarity index 100% rename from src/mail_order_bot/excel_parcer/configurable_parser.py rename to src/mail_order_bot/deprecated/old_excel_parcer/configurable_parser.py diff --git a/src/mail_order_bot/excel_parcer/custom_parser_autoeuro.py b/src/mail_order_bot/deprecated/old_excel_parcer/custom_parser_autoeuro.py similarity index 100% rename from src/mail_order_bot/excel_parcer/custom_parser_autoeuro.py rename to src/mail_order_bot/deprecated/old_excel_parcer/custom_parser_autoeuro.py diff --git a/src/mail_order_bot/excel_parcer/excel_parser.py b/src/mail_order_bot/deprecated/old_excel_parcer/excel_parser.py similarity index 100% rename from src/mail_order_bot/excel_parcer/excel_parser.py rename to src/mail_order_bot/deprecated/old_excel_parcer/excel_parser.py diff --git a/src/mail_order_bot/excel_parcer/order_position.py b/src/mail_order_bot/deprecated/old_excel_parcer/order_position.py similarity index 100% rename from src/mail_order_bot/excel_parcer/order_position.py rename to src/mail_order_bot/deprecated/old_excel_parcer/order_position.py diff --git a/src/mail_order_bot/excel_parcer/parser_factory.py b/src/mail_order_bot/deprecated/old_excel_parcer/parser_factory.py similarity index 100% rename from src/mail_order_bot/excel_parcer/parser_factory.py rename to src/mail_order_bot/deprecated/old_excel_parcer/parser_factory.py diff --git a/src/mail_order_bot/excel_parcer/processor.py b/src/mail_order_bot/deprecated/old_excel_parcer/processor.py similarity index 100% rename from src/mail_order_bot/excel_parcer/processor.py rename to src/mail_order_bot/deprecated/old_excel_parcer/processor.py diff --git a/src/mail_order_bot/email_client/__init__.py b/src/mail_order_bot/email_client/__init__.py index ce9557f..55443a8 100644 --- a/src/mail_order_bot/email_client/__init__.py +++ b/src/mail_order_bot/email_client/__init__.py @@ -1,2 +1,3 @@ from .client import EmailClient from .objects import EmailMessage, EmailAttachment +from .utils import EmailUtils diff --git a/src/mail_order_bot/email_client/client.py b/src/mail_order_bot/email_client/client.py index c176f31..8fad6e7 100644 --- a/src/mail_order_bot/email_client/client.py +++ b/src/mail_order_bot/email_client/client.py @@ -11,6 +11,10 @@ from email.header import decode_header import imaplib import smtplib +import logging + +logger = logging.getLogger(__name__) + # from .objects import EmailMessage, EmailAttachment diff --git a/src/mail_order_bot/email_client/utils.py b/src/mail_order_bot/email_client/utils.py index 9d8a04e..19e0386 100644 --- a/src/mail_order_bot/email_client/utils.py +++ b/src/mail_order_bot/email_client/utils.py @@ -1,16 +1,11 @@ -from email.header import decode_header, make_header import re -from datetime import datetime -from typing import List, Optional -from dataclasses import dataclass +from typing import List import email -from email import encoders -from email.mime.text import MIMEText -from email.mime.multipart import MIMEMultipart -from email.mime.base import MIMEBase -from email.header import decode_header -import imaplib -import smtplib +from email.header import make_header, decode_header + +import logging + +logger = logging.getLogger(__name__) from .objects import EmailMessage, EmailAttachment @@ -99,8 +94,3 @@ class EmailUtils: return None # убираем пробелы по краям и берём часть после '@' return email_message.strip().split("@", 1)[1] - - - - - diff --git a/src/mail_order_bot/email_processor/__init__.py b/src/mail_order_bot/email_processor/__init__.py deleted file mode 100644 index 1c72469..0000000 --- a/src/mail_order_bot/email_processor/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .processor import EmailProcessor \ No newline at end of file diff --git a/src/mail_order_bot/email_processor/handlers/excel2/excel_handler.py b/src/mail_order_bot/email_processor/handlers/excel2/excel_handler.py deleted file mode 100644 index ce729c4..0000000 --- a/src/mail_order_bot/email_processor/handlers/excel2/excel_handler.py +++ /dev/null @@ -1,32 +0,0 @@ -from io import BytesIO -from typing import Optional - - -class ExcelHandler: - """ - Класс для обработки Excel файлов. - Хранит файл в формате BytesIO и предоставляет методы для работы с ним. - """ - - def __init__(self, file_bytes: BytesIO): - """ - Инициализация обработчика Excel файла. - - Args: - file_bytes: Файл электронных таблиц в формате BytesIO - """ - if not isinstance(file_bytes, BytesIO): - raise TypeError("file_bytes должен быть экземпляром BytesIO") - - self.data: BytesIO = file_bytes - - def get_file(self) -> BytesIO: - """ - Возвращает сохраненный файл в формате BytesIO. - - Returns: - BytesIO: Файл электронных таблиц - """ - # Перемещаем указатель в начало файла для корректного чтения - self.data.seek(0) - return self.data diff --git a/src/mail_order_bot/email_processor/order/__init__.py b/src/mail_order_bot/order/__init__.py similarity index 56% rename from src/mail_order_bot/email_processor/order/__init__.py rename to src/mail_order_bot/order/__init__.py index fe279a2..4c074da 100644 --- a/src/mail_order_bot/email_processor/order/__init__.py +++ b/src/mail_order_bot/order/__init__.py @@ -1,2 +1,3 @@ +"""Классы для работы с сущностью заказа и позиции""" from .auto_part_order import AutoPartOrder, OrderStatus from .auto_part_position import AutoPartPosition, PositionStatus diff --git a/src/mail_order_bot/email_processor/order/auto_part_order.py b/src/mail_order_bot/order/auto_part_order.py similarity index 92% rename from src/mail_order_bot/email_processor/order/auto_part_order.py rename to src/mail_order_bot/order/auto_part_order.py index a6747b6..fe61ed9 100644 --- a/src/mail_order_bot/email_processor/order/auto_part_order.py +++ b/src/mail_order_bot/order/auto_part_order.py @@ -1,9 +1,11 @@ from typing import List, Optional from .auto_part_position import AutoPartPosition, PositionStatus +from mail_order_bot.task_processor.handlers.abcp.stock_selector import StockSelector from enum import Enum + class OrderStatus(Enum): NEW = "new" IN_PROGRESS = "in progress" @@ -14,7 +16,8 @@ class OrderStatus(Enum): class AutoPartOrder: - def __init__(self): + def __init__(self, client_id): + self.client_id = client_id self.positions: List[AutoPartPosition] = [] self.status = OrderStatus.NEW self.delivery_period = 0 @@ -40,14 +43,12 @@ class AutoPartOrder: Выбирает оптимального поставщика для всех позиций заказа. Предполагается, что остатки уже получены и обработаны. """ - from mail_order_bot.email_processor.handlers.abcp.stock_selector import StockSelector - + + for position in self.positions: selector = StockSelector(position, self.delivery_period) selector.select_optimal_supplier() - - def check_order(self, config) -> None: """ Проверяет заказ на возможность исполнения""" # 1. Проверка общего количества отказов @@ -61,6 +62,5 @@ class AutoPartOrder: f"({refusal_positions_count} из {len(self.positions)})") self.status = OrderStatus.OPERATOR_REQUIRED - def __len__(self): - return len(self.positions) + return len(self.positions) \ No newline at end of file diff --git a/src/mail_order_bot/email_processor/order/auto_part_position.py b/src/mail_order_bot/order/auto_part_position.py similarity index 77% rename from src/mail_order_bot/email_processor/order/auto_part_position.py rename to src/mail_order_bot/order/auto_part_position.py index 2ac65ed..f4d777a 100644 --- a/src/mail_order_bot/email_processor/order/auto_part_position.py +++ b/src/mail_order_bot/order/auto_part_position.py @@ -1,26 +1,28 @@ -from turtle import st from typing import List, Optional from dataclasses import dataclass, field from typing import Dict, Any from decimal import Decimal from enum import Enum + class PositionStatus(Enum): NEW = "new" # Новая позиция STOCK_RECIEVED = "stock_received" # Получен остаток - STOCK_FAILED = "stock_failed" # Остаток не получен - NO_AVAILABLE_STOCK = "no_available_stock" #Нет доступных складов + STOCK_FAILED = "stock_failed" # Остаток не получен + NO_AVAILABLE_STOCK = "no_available_stock" # Нет доступных складов READY = "ready" ORDERED = "ordered" # Заказано REFUSED = "refused" # Отказано + +@dataclass class AutoPartPosition: """ Унифицированная модель позиции для заказа. Все контрагенты приводятся к этой структуре. """ - DISTRIBUTOR_ID = "1577730" # ID локального склада + DISTRIBUTOR_ID = 1577730 # ID локального склада sku: str # Артикул товара manufacturer: str # Производитель @@ -30,11 +32,10 @@ class AutoPartPosition: total: Decimal = 0 # Общая сумма name: str = "" # Наименование - + order_delivery_period: int = 0 order_quantity: int = 0 # Количество для заказа order_price: Decimal = Decimal('0.0') # Цена в заказе order_item: Dict[str, Any] = field(default_factory=dict) - order_delivery_period = 0 stock: List[Dict[str, Any]] = None additional_attrs: Dict[str, Any] = field(default_factory=dict) @@ -50,22 +51,22 @@ class AutoPartPosition: raise ValueError(f"Цена не может быть отрицательной: {self.requested_price}") def set_stock(self, stock): - if stock is not None: - self.stock = stock + if stock.get("success"): + self.stock = stock["data"] if len(self.stock): self.status = PositionStatus.STOCK_RECIEVED else: self.status = PositionStatus.NO_AVAILABLE_STOCK else: self.status = PositionStatus.STOCK_FAILED - + def set_order_item(self): """Выбирает позицию для заказа""" if self.status == PositionStatus.STOCK_RECIEVED: available_distributors = self.stock # BR-1. Отсекаем склады для заказов из наличия (только локальный склад) - if self.delivery_period == 0: + if self.order_delivery_period == 0: available_distributors = self._filter_only_local_storage(available_distributors) # BR-2. Цена не должна превышать цену из заказа @@ -77,56 +78,32 @@ class AutoPartPosition: # BR-4. Без отрицательных остатков available_distributors = self._filter_proper_availability(available_distributors) - # Приоритет на склады с полным стоком # BR-5. Сначала оборачиваем локальный склад, потом удаленные - # BR-6. Выбираем цену максимально близкую к цене из заказа (максимальная) available_distributors.sort(key=lambda item: Decimal(item["price"]), reverse=True) - # BR-7. - - - for stock_item in self.stock: - available_quantity = min(self.requested_quantity, stock_item["availability"]) - stock_item["profit"] = available_quantity * stock_item["price"] - available_quantity * stock_item["system_price"] - - self.stock.sort(key=lambda item: Decimal(item["profit"]), reverse=True) - self.order_item = self.stock[0] - + if len(available_distributors): + self.order_item = self.stock[0] + self.status = PositionStatus.READY + else: + self.status = PositionStatus.NO_AVAILABLE_STOCK def _filter_only_local_storage(self, distributors: List[Dict[str, Any]]) -> List[Dict[str, Any]]: """Фильтрует только локальные склады""" - return [item for item in distributors if str(item["distributorId"]) == self.DISTRIBUTOR_ID] - + + return [item for item in distributors if item["distributorId"] == self.DISTRIBUTOR_ID] + def _filter_proper_delivery_time(self, distributors: List[Dict[str, Any]]) -> List[Dict[str, Any]]: """Фильтрует склады по сроку доставки""" return [item for item in distributors if item["deliveryPeriod"] <= self.order_delivery_period] - + def _filter_proper_price(self, distributors: List[Dict[str, Any]]) -> List[Dict[str, Any]]: """Фильтрует склады по цене (убирает дорогие)""" - return [item for item in distributors if Decimal(item["price"]) <= self.position.requested_price] - + return [item for item in distributors if Decimal(item["price"]) <= self.requested_price] + def _filter_proper_availability(self, distributors: List[Dict[str, Any]]) -> List[Dict[str, Any]]: """Фильтрует склады с положительным остатком""" - return [item for item in distributors if Decimal(item["availability"]) > 0] - - def _filter_proper_availability(self, distributors: List[Dict[str, Any]]) -> List[Dict[str, Any]]: - """Фильтрует склады с положительным остатком""" - return [item for item in distributors if Decimal(item["availability"]) > 0] - - - - - - - - - - - - - - + return [item for item in distributors if Decimal(item["availability"]) > 0] \ No newline at end of file diff --git a/src/mail_order_bot/parsers/__init__.py b/src/mail_order_bot/parsers/__init__.py new file mode 100644 index 0000000..cf820e1 --- /dev/null +++ b/src/mail_order_bot/parsers/__init__.py @@ -0,0 +1,4 @@ +"""Данный пакет содержит модули и классы дял парсинга объектов""" + +from .excel_parcer import ExcelFileParcer +from .order_parcer import OrderParser \ No newline at end of file diff --git a/src/mail_order_bot/parsers/excel_parcer.py b/src/mail_order_bot/parsers/excel_parcer.py new file mode 100644 index 0000000..b1895be --- /dev/null +++ b/src/mail_order_bot/parsers/excel_parcer.py @@ -0,0 +1,106 @@ +import logging +import pandas as pd +from io import BytesIO + +logger = logging.getLogger(__name__) + + +class ExcelFileParcer: + def __init__(self, file_bytes, config): + self.config = config + self.bytes = file_bytes + self.sheet_name = self.config.get("sheet_name", 0) + self.df = self._parse_file(file_bytes) + + def _parse_file(self, file_bytes): + """Парсит вложение в формате эл таблиц""" + try: + df = pd.read_excel(file_bytes, sheet_name=self.sheet_name, header=None) + except Exception as e: + df = None + logger.warning("Не удалось распарсить значение файла") + return df + + def set_value(self, sku, manufacturer, column, value): + """Устанавливает значение в строке позиции в заданной колонке""" + # Находим строку (ось Y) + attr_row = self._get_attr_row(sku, manufacturer) + + # Находим колонку (ось X) + attr_col = self._get_attr_column(column) + + self.df.iloc[attr_row, attr_col] = value + logger.debug( + f"Установлено значение {value} в колонке {column} для строки {attr_row} ( {sku} | {manufacturer} )") + + def get_file_bytes(self): + "Этот метод будет возвращать эксель из датафрейма в виде байтов" + buf = BytesIO() + + with pd.ExcelWriter(buf, engine="xlsxwriter") as writer: + self.df.to_excel(writer, sheet_name="Sheet1", index=False, header=False) + + buf.seek(0) + return buf + + def get_order_rows(self): + "Будет такой метод или какой-то другой который формирует файл с заказом" + # Получаем все данные из файла + + # Находим индекс строки с заголовком + key_field = self.config.get("key_field") + header_row_idx = self.df[ + self.df.apply(lambda row: row.astype(str).str.contains(key_field, case=False, na=False).any(), + axis=1)].index[0] + + # Считываем таблицу с правильным заголовком + df = pd.read_excel(self.bytes, header=header_row_idx, sheet_name=self.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 + + def _get_header_row(self): + """Метод возвращает строку заголовка по наличию в ней ключевого слова. Поиск заголовка нужен при определении колонок с данными.""" + + key_column = self.config.get("key_field") + header_row_idx = int( + self.df.apply(lambda row: row.astype(str).str.contains(key_column, na=False).any(), axis=1).idxmax()) + # todo надо выкинуть ошибку если в файле не найдено ключевое поле + # todo надо выкинуть ошибку если найдено несколько строк с CONFIG_KEY_COLUMN + logger.debug(f"Индекс строки заголовка - {header_row_idx}") + return header_row_idx + + def _get_attr_column(self, col_name): + """Поиск по оси Х - метод возвращает индекс колонки по названию атрибута""" + header_row_idx = self._get_header_row() + + header_row = self.df.iloc[header_row_idx] + + col_id = header_row[header_row == col_name].index[0] + # todo добавить перехват ошибок и выброс понятного и сключения при отсутствии колонки + logger.debug(f"Индекс колонки {col_name} - {col_id}") + return int(col_id) + + def _get_attr_row(self, sku, manufacturer): + """Поиск по оси Y - метод возвращает индекс строки по бренду и артикулу""" + + sku_col_name = self.config["mapping"]["article"] + sku_col_idx = self._get_attr_column(sku_col_name) + + man_col_name = self.config["mapping"]["manufacturer"] + man_col_idx = self._get_attr_column(man_col_name) + + matching_rows = self.df[ + (self.df.iloc[:, sku_col_idx] == sku) & (self.df.iloc[:, man_col_idx] == manufacturer)].index + + # todo сделать проверку на наличие дублей + logger.info(f"Индекс строки позиции {sku}/{manufacturer} - {matching_rows}") + + return matching_rows.values[0] + + diff --git a/src/mail_order_bot/parsers/order_parcer.py b/src/mail_order_bot/parsers/order_parcer.py new file mode 100644 index 0000000..f726b32 --- /dev/null +++ b/src/mail_order_bot/parsers/order_parcer.py @@ -0,0 +1,83 @@ +import logging +import pandas as pd +from typing import Dict, Any, Optional +from decimal import Decimal +from io import BytesIO + +from mail_order_bot.order import AutoPartPosition + +logger = logging.getLogger(__name__) + + +class OrderParser: + """ + Универсальный парсер, настраиваемый через конфигурацию. + Подходит для большинства стандартных случаев. + """ + + def __init__(self, mapping, delivery_period): + self.mapping = mapping + self.delivery_period = delivery_period + + def parse(self, order, df): + # Парсим строки + positions = [] + for idx, row in df.iterrows(): + position = self._parse_row(row, self.mapping) + if position: + order.add_position(position) + + logger.info(f"Успешно обработано {len(order)} позиций из {len(df)} строк") + + # except Exception as e: + # logger.error(f"Ошибка при обработке файла: {e}") + # else: + return order + + def _parse_row(self, row: pd.Series, mapping: Dict[str, str]) -> Optional[AutoPartPosition]: + """Парсит одну строку 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 + + if "name" in mapping: + name = str(row[mapping.get('name', "")]).strip() + else: + name = "" + + # Создаем объект позиции + position = AutoPartPosition( + sku=str(row[mapping['article']]).strip(), + manufacturer=str(row[mapping.get('manufacturer', "")]).strip(), + name=name, + requested_price=price, + requested_quantity=quantity, + total=total, + order_delivery_period=self.delivery_period, + 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 diff --git a/src/mail_order_bot/task_processor/__init__.py b/src/mail_order_bot/task_processor/__init__.py new file mode 100644 index 0000000..fb90fb8 --- /dev/null +++ b/src/mail_order_bot/task_processor/__init__.py @@ -0,0 +1 @@ +from .processor import TaskProcessor \ No newline at end of file diff --git a/src/mail_order_bot/email_processor/handlers/abstract_task.py b/src/mail_order_bot/task_processor/abstract_task.py similarity index 100% rename from src/mail_order_bot/email_processor/handlers/abstract_task.py rename to src/mail_order_bot/task_processor/abstract_task.py diff --git a/src/mail_order_bot/email_processor/handlers/__init__.py b/src/mail_order_bot/task_processor/handlers/__init__.py similarity index 84% rename from src/mail_order_bot/email_processor/handlers/__init__.py rename to src/mail_order_bot/task_processor/handlers/__init__.py index 1171a5d..abe25cc 100644 --- a/src/mail_order_bot/email_processor/handlers/__init__.py +++ b/src/mail_order_bot/task_processor/handlers/__init__.py @@ -1,13 +1,8 @@ + + from .attachment_handler.attachment_handler import AttachmentHandler from .excel_parcers.order_parcer_basic import BasicExcelParser from .destination_time.local_store import DeliveryPeriodLocalStore - - - -from .abcp.api_get_stock import APIGetStock - - - from .notifications.test_notifier import TestNotifier diff --git a/src/mail_order_bot/email_processor/handlers/abcp/api_get_stock.py b/src/mail_order_bot/task_processor/handlers/abcp/api_get_stock.py similarity index 69% rename from src/mail_order_bot/email_processor/handlers/abcp/api_get_stock.py rename to src/mail_order_bot/task_processor/handlers/abcp/api_get_stock.py index 96df048..f42f068 100644 --- a/src/mail_order_bot/email_processor/handlers/abcp/api_get_stock.py +++ b/src/mail_order_bot/task_processor/handlers/abcp/api_get_stock.py @@ -1,9 +1,8 @@ import logging -from mail_order_bot.email_processor.handlers.abstract_task import AbstractTask -from .abcp_provider import AbcpProvider +from mail_order_bot.task_processor.abstract_task import AbstractTask +from mail_order_bot.abcp_api.abcp_provider import AbcpProvider from mail_order_bot.credential_provider import CredentialProvider -from mail_order_bot.email_processor.handlers.abcp.stock_selector import StockSelector logger = logging.getLogger(__name__) @@ -24,12 +23,8 @@ class APIGetStock(AbstractTask): for position in order.positions: # Получаем остатки из-под учетной записи клиента client_stock = self.client_provider.get_stock(position.sku, position.manufacturer) - - # Используем StockSelector для фильтрации неподходящих поставщиков - selector = StockSelector(position, order.delivery_period) - available_distributors = selector.filter_stock(client_stock) - - position.set_stock(available_distributors) + + position.set_stock(client_stock) position.set_order_item() logger.info(f"Получены позиции со склада для файла {attachment.get('name', "no name")}") diff --git a/src/mail_order_bot/email_processor/handlers/abcp/stock_selector.py b/src/mail_order_bot/task_processor/handlers/abcp/stock_selector.py similarity index 100% rename from src/mail_order_bot/email_processor/handlers/abcp/stock_selector.py rename to src/mail_order_bot/task_processor/handlers/abcp/stock_selector.py diff --git a/src/mail_order_bot/email_processor/handlers/attachment_handler/attachment_handler.py b/src/mail_order_bot/task_processor/handlers/attachment_handler/attachment_handler.py similarity index 87% rename from src/mail_order_bot/email_processor/handlers/attachment_handler/attachment_handler.py rename to src/mail_order_bot/task_processor/handlers/attachment_handler/attachment_handler.py index f5c7737..7bdd752 100644 --- a/src/mail_order_bot/email_processor/handlers/attachment_handler/attachment_handler.py +++ b/src/mail_order_bot/task_processor/handlers/attachment_handler/attachment_handler.py @@ -1,4 +1,4 @@ -from mail_order_bot.email_processor.handlers.abstract_task import AbstractTask +from mail_order_bot.task_processor.abstract_task import AbstractTask from mail_order_bot.email_client.utils import EmailUtils import logging diff --git a/src/mail_order_bot/email_processor/handlers/destination_time/from_subject.py b/src/mail_order_bot/task_processor/handlers/destination_time/from_subject.py similarity index 97% rename from src/mail_order_bot/email_processor/handlers/destination_time/from_subject.py rename to src/mail_order_bot/task_processor/handlers/destination_time/from_subject.py index f745e43..7e2602b 100644 --- a/src/mail_order_bot/email_processor/handlers/destination_time/from_subject.py +++ b/src/mail_order_bot/task_processor/handlers/destination_time/from_subject.py @@ -1,5 +1,4 @@ -from mail_order_bot.email_processor.handlers.abstract_task import AbstractTask -from mail_order_bot.email_client.utils import EmailUtils +from mail_order_bot.task_processor.abstract_task import AbstractTask import logging import re diff --git a/src/mail_order_bot/email_processor/handlers/destination_time/local_store.py b/src/mail_order_bot/task_processor/handlers/destination_time/local_store.py similarity index 77% rename from src/mail_order_bot/email_processor/handlers/destination_time/local_store.py rename to src/mail_order_bot/task_processor/handlers/destination_time/local_store.py index b27d61d..9a648b2 100644 --- a/src/mail_order_bot/email_processor/handlers/destination_time/local_store.py +++ b/src/mail_order_bot/task_processor/handlers/destination_time/local_store.py @@ -1,5 +1,4 @@ -from mail_order_bot.email_processor.handlers.abstract_task import AbstractTask -from mail_order_bot.email_client.utils import EmailUtils +from mail_order_bot.task_processor.abstract_task import AbstractTask import logging diff --git a/src/mail_order_bot/email_processor/handlers/email/send_email.py b/src/mail_order_bot/task_processor/handlers/email/send_email.py similarity index 100% rename from src/mail_order_bot/email_processor/handlers/email/send_email.py rename to src/mail_order_bot/task_processor/handlers/email/send_email.py diff --git a/src/mail_order_bot/email_processor/handlers/excel_parcers/order_parcer_basic.py b/src/mail_order_bot/task_processor/handlers/excel_parcers/deprecated/order_parser.py similarity index 91% rename from src/mail_order_bot/email_processor/handlers/excel_parcers/order_parcer_basic.py rename to src/mail_order_bot/task_processor/handlers/excel_parcers/deprecated/order_parser.py index 0d61472..40ad76b 100644 --- a/src/mail_order_bot/email_processor/handlers/excel_parcers/order_parcer_basic.py +++ b/src/mail_order_bot/task_processor/handlers/excel_parcers/deprecated/order_parser.py @@ -3,16 +3,16 @@ import pandas as pd from typing import Dict, Any, Optional from decimal import Decimal from io import BytesIO -#from mail_order_bot.email_processor.handlers.order_position import OrderPosition -from mail_order_bot.email_processor.handlers.abstract_task import AbstractTask +#from mail_order_bot.task_processor.handlers.order_position import OrderPosition +from mail_order_bot.task_processor.abstract_task import AbstractTask -from mail_order_bot.email_processor.order.auto_part_position import AutoPartPosition -from mail_order_bot.email_processor.order.auto_part_order import AutoPartOrder +from mail_order_bot.task_processor.order.auto_part_position import AutoPartPosition +from mail_order_bot.task_processor.order.auto_part_order import AutoPartOrder logger = logging.getLogger(__name__) -class BasicExcelParser(AbstractTask): +class OrderParser(AbstractTask): """ Универсальный парсер, настраиваемый через конфигурацию. Подходит для большинства стандартных случаев. @@ -30,7 +30,8 @@ class BasicExcelParser(AbstractTask): delivery_period = attachment.get("delivery_period", 0) #try: df = self._make_dataframe(file_bytes) - mapping = self.config['mapping'] + mapping = self.config["mapping"] + client_id = self.config["client_id"] order = AutoPartOrder() attachment["order"] = order diff --git a/src/mail_order_bot/email_processor/handlers/excel_parcers/sheet_parcer.py b/src/mail_order_bot/task_processor/handlers/excel_parcers/deprecated/sheet_parcer.py similarity index 98% rename from src/mail_order_bot/email_processor/handlers/excel_parcers/sheet_parcer.py rename to src/mail_order_bot/task_processor/handlers/excel_parcers/deprecated/sheet_parcer.py index 41d78ea..e24b500 100644 --- a/src/mail_order_bot/email_processor/handlers/excel_parcers/sheet_parcer.py +++ b/src/mail_order_bot/task_processor/handlers/excel_parcers/deprecated/sheet_parcer.py @@ -3,7 +3,7 @@ import pandas as pd from typing import Dict, Any, Optional from decimal import Decimal from io import BytesIO -#from mail_order_bot.email_processor.handlers.order_position import OrderPosition +#from mail_order_bot.task_processor.handlers.order_position import OrderPosition from mail_order_bot.email_processor.handlers.abstract_task import AbstractTask from ...order.auto_part_position import AutoPartPosition diff --git a/src/mail_order_bot/task_processor/handlers/excel_parcers/excel_extractor.py b/src/mail_order_bot/task_processor/handlers/excel_parcers/excel_extractor.py new file mode 100644 index 0000000..2e1e5d2 --- /dev/null +++ b/src/mail_order_bot/task_processor/handlers/excel_parcers/excel_extractor.py @@ -0,0 +1,34 @@ +import logging +import pandas as pd +from io import BytesIO +#from mail_order_bot.task_processor.handlers.order_position import OrderPosition +from mail_order_bot.task_processor.abstract_task import AbstractTask + +logger = logging.getLogger(__name__) + + +class ExcelExtractor(AbstractTask): + """ + Хендлер для каждого вложения считывает эксель файл и сохраняет его контекст + """ + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def do(self) -> None: + + # todo сделать проверку на наличие файла и его тип + + attachments = self.context.data.get("attachments", []) + for attachment in attachments: + file_bytes = BytesIO(attachment['bytes']) + + # Получаем все данные из файла + sheet_name = self.config.get("sheet_name", 0) + try: + attachment["sheet"] = pd.read_excel(file_bytes, sheet_name=sheet_name, header=None) + except Exception as e: + attachment["sheet"] = None + logger.warning("Не удалось распарсить значение файла") + + + diff --git a/src/mail_order_bot/email_processor/handlers/notifications/test_notifier.py b/src/mail_order_bot/task_processor/handlers/notifications/test_notifier.py similarity index 100% rename from src/mail_order_bot/email_processor/handlers/notifications/test_notifier.py rename to src/mail_order_bot/task_processor/handlers/notifications/test_notifier.py diff --git a/src/mail_order_bot/email_processor/processor.py b/src/mail_order_bot/task_processor/processor.py similarity index 94% rename from src/mail_order_bot/email_processor/processor.py rename to src/mail_order_bot/task_processor/processor.py index f2a6098..92ce4c1 100644 --- a/src/mail_order_bot/email_processor/processor.py +++ b/src/mail_order_bot/task_processor/processor.py @@ -8,9 +8,9 @@ from mail_order_bot.context import Context from mail_order_bot.email_client.utils import EmailUtils from enum import Enum -from mail_order_bot.email_processor.handlers import * +from mail_order_bot.task_processor.handlers import * -from mail_order_bot.email_processor.handlers import AttachmentHandler +from mail_order_bot.task_processor.handlers import AttachmentHandler logger = logging.getLogger(__name__) @@ -24,7 +24,7 @@ class RequestStatus(Enum): INVALID = "invalid" -class EmailProcessor: +class TaskProcessor: def __init__(self, configs_path: str): super().__init__() self.context = Context()