diff --git a/src/mail_order_bot/abcp_api/abcp_provider.py b/src/mail_order_bot/abcp_api/abcp_provider.py index 0fab1ba..13e3c1d 100644 --- a/src/mail_order_bot/abcp_api/abcp_provider.py +++ b/src/mail_order_bot/abcp_api/abcp_provider.py @@ -1,7 +1,9 @@ import os import hashlib import requests +import logging +logger = logging.getLogger(__name__) class AbcpProvider: HOST = "https://id23089.public.api.abcp.ru" @@ -17,19 +19,30 @@ class AbcpProvider: password = os.getenv("ABCP_PASSWORD") self.password = hashlib.md5(password.encode("utf-8")).hexdigest() - def get_stock(self, order): + def get_stock(self, sku, manufacturer): method = "GET" path = "/search/articles" - for position in order.positions: - params = {"number": position.sku, "brand": position.manufacturer, "withOutAnalogs": "1"} - position.stock = self._execute(path, method, params) + params = {"number": sku, "brand": manufacturer, "withOutAnalogs": "1"} + return self._execute(path, method, params) def _execute(self, path, method="GET", params={}, data=None): params["userlogin"] = self.login params["userpsw"] = self.password response = requests.request(method, self.HOST+path, data=data, headers=self.HEADERS, params=params) - if response.status_code != 200: - raise Exception(response.text) - return response.json() + payload = response.json() + if response.status_code == 200: + logger.debug(f"Получены данные об остатках на складе") + result = { + "success": True, + "data": payload + } + else: + logger.warning(f"ошибка получения данных об остатках на складе: {payload}") + + result = { + "success": False, + "error": payload + } + return result diff --git a/src/mail_order_bot/config.yml b/src/mail_order_bot/config.yml index f4c0c44..f854a33 100644 --- a/src/mail_order_bot/config.yml +++ b/src/mail_order_bot/config.yml @@ -1,8 +1,8 @@ # Настройки обработки ================================================================= # Раздел с общими конфигурационными параметрами =============================== -update_interval: 10 -work_interval: 30 +update_interval: 1 +work_interval: 60 email_dir: "spareparts" # Логирование ================================================================= @@ -44,7 +44,7 @@ log: loggers: '': handlers: [console, file, telegram] - level: INFO + level: DEBUG propagate: False __main__: diff --git a/src/mail_order_bot/configs/amtel.club.yml b/src/mail_order_bot/configs/amtel.club.yml new file mode 100644 index 0000000..24af454 --- /dev/null +++ b/src/mail_order_bot/configs/amtel.club.yml @@ -0,0 +1,25 @@ +pipeline: + # Настраиваем парсинг экселя + - handler: BasicExcelParser + config: + sheet_name: 0 + key_field: "Номер" + mapping: + article: "Номер" + manufacturer: "Фирма" + name: "Наименование" + price: "Цена" + quantity: "Кол-во" + total: "Сумма" + + + - handler: GetStock + + - handler: LocalStoreOrder + + + + + + + diff --git a/src/mail_order_bot/configs/todx.ru.yml b/src/mail_order_bot/configs/todx.ru.yml index 614cbad..ab9c2df 100644 --- a/src/mail_order_bot/configs/todx.ru.yml +++ b/src/mail_order_bot/configs/todx.ru.yml @@ -1,6 +1,5 @@ pipeline: - - handler: "ConfigurableExcelParser" - result_section: "positions" + - handler: BasicExcelParser config: sheet_name: 0 key_field: "Код детали" @@ -12,6 +11,9 @@ pipeline: quantity: "Кол-\nво" total: "Сумма" + - handler: GetStock + + diff --git a/src/mail_order_bot/context.py b/src/mail_order_bot/context.py index bc00bfe..9b02a7d 100644 --- a/src/mail_order_bot/context.py +++ b/src/mail_order_bot/context.py @@ -4,32 +4,75 @@ import logging logger = logging.getLogger() -class _SingletonMeta(type): +import threading +from typing import Any + +class SingletonMeta(type): _instances = {} - _lock = threading.Lock() def __call__(cls, *args, **kwargs): if cls not in cls._instances: - with cls._lock: - if cls not in cls._instances: - instance = super().__call__(*args, **kwargs) - cls._instances[cls] = instance + instance = super().__call__(*args, **kwargs) + cls._instances[cls] = instance return cls._instances[cls] -class Context(metaclass=_SingletonMeta): - def __init__(self): - # будет вызван только при первом создании - self.context = {} - self.email_client = None - def clear_context(self): + +class Context2(metaclass=SingletonMeta): + def __init__(self): + if not hasattr(self, 'initialized'): + self.data = {} + self.email_client = None + self.initialized = True + logger.debug(f"Context создан {id}") # опциональный лог + + + + # будет вызван только при первом создании + + + def clear(self): """Очищает self.context, устанавливая его в None или пустой словарь""" - with self._lock: # потокобезопасная очистка - self.context = {} + self.data = {} + logger.debug("Context очищен") # опциональный лог + + def set(self, new_context: Dict[str, Any]): + """Устанавливает новый контекст (бонусный метод)""" + self.data = new_context + logger.debug("Новый контекст установлен") + + +class ThreadSafeSingletonMeta(type): + _instances = {} + _lock = threading.Lock() + + def __call__(cls, *args: Any, **kwargs: Any) -> Any: + if cls not in cls._instances: + with cls._lock: + if cls not in cls._instances: + # Инициализация ТУТ, не в __init__ + instance = super().__call__(*args, **kwargs) + instance.data = {} + instance.email_client = None + instance._lock = threading.RLock() + cls._instances[cls] = instance + + return cls._instances[cls] + + +class Context(metaclass=ThreadSafeSingletonMeta): + def __init__(self): + print(f"Context: {id(self)}, поток {threading.get_ident()}") + # будет вызван только при первом создании + + def clear(self): + """Очищает self.context, устанавливая его в None или пустой словарь""" + with self._lock: + self.data = {} logger.debug("Context очищен") # опциональный лог - def set_context(self, new_context: Dict[str, Any]): + def set(self, new_context: Dict[str, Any]): """Устанавливает новый контекст (бонусный метод)""" with self._lock: - self.context = new_context + self.data = new_context logger.debug("Новый контекст установлен") \ No newline at end of file diff --git a/src/mail_order_bot/email_client/utils.py b/src/mail_order_bot/email_client/utils.py index bbb0672..9d8a04e 100644 --- a/src/mail_order_bot/email_client/utils.py +++ b/src/mail_order_bot/email_client/utils.py @@ -72,7 +72,7 @@ class EmailUtils: return None @staticmethod - def _extract_attachments(msg: email.message.Message) -> List[EmailAttachment]: + def extract_attachments(msg: email.message.Message) -> List[EmailAttachment]: """Извлечь вложения из письма.""" attachments = [] @@ -87,16 +87,18 @@ class EmailUtils: # Получаем содержимое content = part.get_payload(decode=True) if content: - attachments.append(EmailAttachment(filename=filename, content=content)) + #attachments.append(EmailAttachment(filename=filename, content=content)) + attachments.append({"name": filename, "bytes": content}) + return attachments @staticmethod - def extract_domain(email: str) -> str | None: + def extract_domain(email_message: str) -> str | None: """Вернуть домен из email либо None, если формат странный.""" - if "@" not in email: + if "@" not in email_message: return None # убираем пробелы по краям и берём часть после '@' - return email.strip().split("@", 1)[1] + return email_message.strip().split("@", 1)[1] diff --git a/src/mail_order_bot/email_processor/handlers/__init__.py b/src/mail_order_bot/email_processor/handlers/__init__.py index d2de743..dc4cade 100644 --- a/src/mail_order_bot/email_processor/handlers/__init__.py +++ b/src/mail_order_bot/email_processor/handlers/__init__.py @@ -1,9 +1,16 @@ -from .abcp_clients.check_stock import GetStock -from .abcp_clients.create_order import InstantOrderTest - +from .attachment_handler.attachment_handler import AttachmentHandler from .excel_parcers.order_parcer_basic import BasicExcelParser +from .destination_time.local_store import DeliveryPeriodLocalStore +from .logic.only_local_store_order import LocalStoreOrder + + +from .abcp.get_stock import GetStock +from .abcp.create_order import InstantOrderTest + + from .notifications.test_notifier import TestNotifier -from .validators.price_quantity_ckecker import CheckOrder \ No newline at end of file + + diff --git a/src/mail_order_bot/email_processor/handlers/abcp_clients/create_order.py b/src/mail_order_bot/email_processor/handlers/abcp/create_order.py similarity index 100% rename from src/mail_order_bot/email_processor/handlers/abcp_clients/create_order.py rename to src/mail_order_bot/email_processor/handlers/abcp/create_order.py diff --git a/src/mail_order_bot/email_processor/handlers/abcp/get_stock.py b/src/mail_order_bot/email_processor/handlers/abcp/get_stock.py new file mode 100644 index 0000000..8cd93a0 --- /dev/null +++ b/src/mail_order_bot/email_processor/handlers/abcp/get_stock.py @@ -0,0 +1,24 @@ +import random +import logging + +from mail_order_bot.email_processor.handlers.abstract_task import AbstractTask +from mail_order_bot.abcp_api.abcp_provider import AbcpProvider + +logger = logging.getLogger(__name__) + + +class GetStock(AbstractTask): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.abcp_provider = AbcpProvider() + + def do(self) -> None: + attachments = self.context.data.get("attachments", []) + for attachment in attachments: + for position in attachment["order"].positions: + position.update_stock(self.get_stock(position.sku, position.manufacturer)) + logger.info(f"Получены позиции со склада для файла {attachment.get('name', "no name")}") + + + def get_stock(self, sku: str, manufacturer: str) -> int: + return self.abcp_provider.get_stock(sku, manufacturer) \ No newline at end of file diff --git a/src/mail_order_bot/email_processor/handlers/abcp_clients/check_stock.py b/src/mail_order_bot/email_processor/handlers/abcp_clients/check_stock.py deleted file mode 100644 index 07bef4d..0000000 --- a/src/mail_order_bot/email_processor/handlers/abcp_clients/check_stock.py +++ /dev/null @@ -1,27 +0,0 @@ -import random -import logging - -from mail_order_bot.email_processor.handlers.abstract_task import AbstractTask - - -logger = logging.getLogger(__name__) - - -def get_stock(brand, part_number): - return random.randint(0, 10) - -class GetStock(AbstractTask): - - def do(self) -> None: - positions = self.order.positions - for position in positions: - self._update_stock(position) - - def _update_stock(self, position): - # Эмулируем получение данных - max_stock = self.config.get('max_stock',10) - stock = random.randint(0, max_stock) - price = position.requested_price - - position.stock_price = price - position.stock_quantity = stock diff --git a/src/mail_order_bot/email_processor/handlers/abstract_task.py b/src/mail_order_bot/email_processor/handlers/abstract_task.py index a7760bb..78f97c1 100644 --- a/src/mail_order_bot/email_processor/handlers/abstract_task.py +++ b/src/mail_order_bot/email_processor/handlers/abstract_task.py @@ -4,13 +4,13 @@ from typing import Dict, Any from mail_order_bot.context import Context -class AbstractTask(ABC, Context): +class AbstractTask(): RESULT_SECTION = "section" """ Абстрактный базовый класс для всех хэндлеров. """ - def __init__(self, config: Dict[str, Any]) -> None: - Context.__init__(self, {}) + def __init__(self, config: Dict[str, Any]={}) -> None: + self.context = Context() self.config = config @abstractmethod diff --git a/src/mail_order_bot/email_processor/handlers/attachment_handler/attachment_handler.py b/src/mail_order_bot/email_processor/handlers/attachment_handler/attachment_handler.py index 5610d67..f5c7737 100644 --- a/src/mail_order_bot/email_processor/handlers/attachment_handler/attachment_handler.py +++ b/src/mail_order_bot/email_processor/handlers/attachment_handler/attachment_handler.py @@ -1,13 +1,21 @@ -import logging - from mail_order_bot.email_processor.handlers.abstract_task import AbstractTask from mail_order_bot.email_client.utils import EmailUtils + +import logging + logger = logging.getLogger(__name__) class AttachmentHandler(AbstractTask): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + def do(self) -> None: - - email = self.context["email"] + email = self.context.data["email"] + attachments = EmailUtils.extract_attachments(email) + self.context.data["attachments"] = attachments + logger.debug(f"AttachmentHandler отработал, извлек вложений: {len(attachments)} ") + + diff --git a/src/mail_order_bot/email_processor/handlers/destination_time/local_store.py b/src/mail_order_bot/email_processor/handlers/destination_time/local_store.py new file mode 100644 index 0000000..fb8d023 --- /dev/null +++ b/src/mail_order_bot/email_processor/handlers/destination_time/local_store.py @@ -0,0 +1,21 @@ +from mail_order_bot.email_processor.handlers.abstract_task import AbstractTask +from mail_order_bot.email_client.utils import EmailUtils + +import logging + +logger = logging.getLogger(__name__) + +class DeliveryPeriodLocalStore(AbstractTask): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def do(self) -> None: + attachments = self.context.data["attachments"] + for attachment in attachments: + order = attachment["order"] + order.set_delivery_period(0) + logger.debug(f"Доставка только с локального склада, срок 1 день.") + + + + diff --git a/src/mail_order_bot/email_processor/handlers/excel_parcers/order_parcer_basic.py b/src/mail_order_bot/email_processor/handlers/excel_parcers/order_parcer_basic.py index 41d78ea..78a9c95 100644 --- a/src/mail_order_bot/email_processor/handlers/excel_parcers/order_parcer_basic.py +++ b/src/mail_order_bot/email_processor/handlers/excel_parcers/order_parcer_basic.py @@ -7,46 +7,46 @@ from io import BytesIO from mail_order_bot.email_processor.handlers.abstract_task import AbstractTask from ...order.auto_part_position import AutoPartPosition +from ...order.auto_part_order import AutoPartOrder logger = logging.getLogger(__name__) class BasicExcelParser(AbstractTask): - RESULT_SECTION = "positions" """ Универсальный парсер, настраиваемый через конфигурацию. Подходит для большинства стандартных случаев. """ + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) def do(self) -> None: # todo сделать проверку на наличие файла и его тип - file_bytes = BytesIO(self.context.get("attachment").content) # self.context.get("attachment") # - try: - df = self._make_dataframe(file_bytes) - # Получаем маппинг колонок из конфигурации - mapping = self.config['mapping'] - # Парсим строки - positions = [] - for idx, row in df.iterrows(): - try: + attachments = self.context.data.get("attachments", []) + for attachment in attachments: + file_bytes = BytesIO(attachment['bytes']) # self.context.get("attachment") # + try: + df = self._make_dataframe(file_bytes) + mapping = self.config['mapping'] + order = AutoPartOrder() + + # Парсим строки + positions = [] + for idx, row in df.iterrows(): position = self._parse_row(row, mapping) if position: - positions.append(position) - self.order.add_position(position) - except Exception as e: - logger.error(f"Ошибка парсинга строки {idx}: {e}, {row}") - continue + order.add_position(position) - logger.info(f"Успешно обработано {len(positions)} позиций из {len(df)} строк") + logger.info(f"Успешно обработано {len(order)} позиций из {len(df)} строк") - self.context[self.RESULT_SECTION] = positions + except Exception as e: + logger.error(f"Ошибка при обработке файла: {e}") + else: + attachment["order"] = order - except Exception as e: - logger.error(f"Ошибка при обработке файла: {e}") - raise Exception from e def _parse_row(self, row: pd.Series, mapping: Dict[str, str]) -> Optional[AutoPartPosition]: """Парсит одну строку Excel в OrderPosition""" diff --git a/src/mail_order_bot/email_processor/handlers/logic/only_local_store_order.py b/src/mail_order_bot/email_processor/handlers/logic/only_local_store_order.py new file mode 100644 index 0000000..a76e317 --- /dev/null +++ b/src/mail_order_bot/email_processor/handlers/logic/only_local_store_order.py @@ -0,0 +1,24 @@ +import random +import logging +from mail_order_bot.email_processor.handlers.abstract_task import AbstractTask +from mail_order_bot.email_processor.order.auto_part_order import OrderStatus +from mail_order_bot.email_processor.order.auto_part_position import AutoPartPosition, PositionStatus +from decimal import Decimal +import random +logger = logging.getLogger(__name__) + + +class LocalStoreOrder(AbstractTask): + """Сейчас логика такая + - ищем на складе наш сапплиер код, берем самую дешевую позицию и делаем заказ из нее + + Другие чуть более дорогие не рассматриваем + + """ + # это код нашего склада + + def do(self) -> None: + attachments = self.context.data["attachments"] + for attachment in attachments: + order = attachment["order"] + order.fill_from_local_supplier() diff --git a/src/mail_order_bot/email_processor/handlers/validators/price_quantity_ckecker.py b/src/mail_order_bot/email_processor/handlers/validators/price_quantity_ckecker.py deleted file mode 100644 index 9417ff9..0000000 --- a/src/mail_order_bot/email_processor/handlers/validators/price_quantity_ckecker.py +++ /dev/null @@ -1,50 +0,0 @@ -import random -import logging -from mail_order_bot.email_processor.handlers.abstract_task import AbstractTask -from mail_order_bot.email_processor.order.auto_part_order import OrderStatus -from decimal import Decimal -import random -logger = logging.getLogger(__name__) - - -class CheckOrder(AbstractTask): - - def do(self) -> None: - refused = 0 - positions = self.order.positions - for position in positions: - self._set_order_price(position) - self._set_order_quantity(position) - - if position.order_price == 0 or position.order_quantity == 0: - refused += 1 - - self._check_refusal_threshold(refused) - - - def _set_order_price(self, position): - # Эмулируем получение данных - acceptable_price_reduction = self.config.get("acceptable_price_reduction") - acceptable_price = position.stock_price* Decimal(str((1-acceptable_price_reduction/100))) - - if position.requested_price < acceptable_price: - position.order_price = 0 - else: - position.order_price = position.requested_price - - def _set_order_quantity(self, position): - max_stock = self.config.get("max_stock", 100) - min_stock = self.config.get("min_stock", 0) - - stock_quantity = random.randint(min_stock, max_stock) - - position.order_quantity = max(0, min(position.stock_quantity, stock_quantity)) - - def _check_refusal_threshold(self, refused): - refusal_threshold_limit = self.config.get("refusal_threshold", 1) - refusal_level = refused/len(self.order.positions) - - if refusal_level > refusal_threshold_limit: - self.order.status = OrderStatus.OPERATOR_REQUIRED - self.order.reason = "Превышен порог отказов" - diff --git a/src/mail_order_bot/email_processor/order/__init__.py b/src/mail_order_bot/email_processor/order/__init__.py index e69de29..fe279a2 100644 --- a/src/mail_order_bot/email_processor/order/__init__.py +++ b/src/mail_order_bot/email_processor/order/__init__.py @@ -0,0 +1,2 @@ +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/email_processor/order/auto_part_order.py index 24e0f91..816bf93 100644 --- a/src/mail_order_bot/email_processor/order/auto_part_order.py +++ b/src/mail_order_bot/email_processor/order/auto_part_order.py @@ -1,5 +1,6 @@ from typing import List, Optional -from .auto_part_position import AutoPartPosition +from .auto_part_position import AutoPartPosition, PositionStatus + from enum import Enum class OrderStatus(Enum): @@ -15,12 +16,11 @@ class AutoPartOrder: def __init__(self): self.positions: List[AutoPartPosition] = [] self.status = OrderStatus.NEW + self.delivery_period = 0 self.reason = "" def add_position(self, position: AutoPartPosition) -> None: self.positions.append(position) - if self.status == OrderStatus.NEW: - self.status = OrderStatus.IN_PROGRESS def find_positions(self, brand: Optional[str] = None, sku: Optional[str] = None) -> List[AutoPartPosition]: results = self.positions @@ -30,5 +30,13 @@ class AutoPartOrder: results = [p for p in results if p.sku == sku] return results + def set_delivery_period(self, delivery_period: int) -> None: + self.delivery_period = delivery_period + + def fill_from_local_supplier(self) -> None: + for position in self.positions: + position.fill_from_local_supplier() + + def __len__(self): return len(self.positions) diff --git a/src/mail_order_bot/email_processor/order/auto_part_position.py b/src/mail_order_bot/email_processor/order/auto_part_position.py index d43fab1..8ae9f03 100644 --- a/src/mail_order_bot/email_processor/order/auto_part_position.py +++ b/src/mail_order_bot/email_processor/order/auto_part_position.py @@ -2,25 +2,44 @@ 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" # Остаток не получен + READY = "ready" + READY_PARTIAL = "ready_partial" + ORDERED = "ordered" # Заказано + REFUSED = "refused" # Отказано @dataclass class AutoPartPosition: + SUPPLIER_CODE = "11282996" """ Унифицированная модель позиции для заказа. Все контрагенты приводятся к этой структуре. """ sku: str # Артикул товара manufacturer: str # Производитель + + requested_price: Decimal # Цена за единицу requested_quantity: int # Количество + total: Decimal = 0 # Общая сумма name: str = "" # Наименование - requested_price: Decimal = 0 # Цена за единицу + order_quantity: int = 0 # Количество для заказа order_price: Decimal = Decimal('0.0') # Цена в заказе + order_item: Dict[str, Any] = field(default_factory=dict) + stock: List[Dict[str, Any]] = None additional_attrs: Dict[str, Any] = field(default_factory=dict) + status: PositionStatus = PositionStatus.NEW + desc: str = "" + def __post_init__(self): """Валидация после инициализации""" if self.requested_quantity < 0: @@ -28,50 +47,35 @@ class AutoPartPosition: if self.requested_price < 0: raise ValueError(f"Цена не может быть отрицательной: {self.requested_price}") + def update_stock(self, stock: Dict[str, Any]): + if stock["success"]: + self.stock = stock["data"] + self.status = PositionStatus.STOCK_RECIEVED + else: + self.status = PositionStatus.STOCK_FAILED + + + def fill_from_local_supplier(self): + if self.status != PositionStatus.STOCK_RECIEVED: + return + + supplier_stock_items = [item for item in self.stock if str(item["supplierCode"]) == self.SUPPLIER_CODE] + supplier_stock_items.sort(key=lambda item: Decimal(item["price"]), reverse=False) + + if len(supplier_stock_items) == 0: + self.status = PositionStatus.REFUSED + self.desc = "Нет на складе" + return + + elif self.requested_quantity <= supplier_stock_items[0]["availability"]: + self.order_quantity = self.requested_quantity + self.status = PositionStatus.READY + self.desc = "Готов к заказу" + + else: + self.order_quantity = supplier_stock_items[0]["availability"] + self.status = PositionStatus.READY_PARTIAL + self.desc = "Частичный остаток" -class AutoPartPosition2: - brand: str - sku: str - name: str - customer_price: float - customer_quantity: int - supplier_price: float - stock_remaining: int - - def __init__(self, brand: str, sku: str, name: str, - customer_price: float, customer_quantity: int, - supplier_price: float, stock_remaining: int): - self.brand = brand - self.sku = sku - self.name = name - self.customer_price = customer_price - self.customer_quantity = customer_quantity - self.supplier_price = supplier_price - self.stock_remaining = stock_remaining - - def customer_cost(self) -> float: - return self.customer_price * self.customer_quantity - - def supplier_cost(self) -> float: - return self.supplier_price * self.customer_quantity - - def is_available(self) -> bool: - return self.stock_remaining >= self.customer_quantity - - def restock(self, amount: int) -> None: - if amount < 0: - raise ValueError("Restock amount must be non-negative") - self.stock_remaining += amount - - def __post_init__(self): - if self.customer_price < 0: - raise ValueError("Customer price cannot be negative") - if self.customer_quantity < 0: - raise ValueError("Customer quantity cannot be negative") - if self.supplier_price < 0: - raise ValueError("Supplier price cannot be negative") - if self.stock_remaining < 0: - raise ValueError("Stock remaining cannot be negative") - diff --git a/src/mail_order_bot/email_processor/processor.py b/src/mail_order_bot/email_processor/processor.py index 7199ff1..8340f46 100644 --- a/src/mail_order_bot/email_processor/processor.py +++ b/src/mail_order_bot/email_processor/processor.py @@ -3,13 +3,17 @@ import yaml import logging from typing import Dict, Any from pathlib import Path - -logger = logging.getLogger(__name__) - +import threading 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.email_processor.handlers import AttachmentHandler + +logger = logging.getLogger(__name__) + class RequestStatus(Enum): NEW = "new" @@ -20,9 +24,10 @@ class RequestStatus(Enum): INVALID = "invalid" -class EmailProcessor(Context): +class EmailProcessor: def __init__(self, configs_path: str): super().__init__() + self.context = Context() self.configs_path = configs_path self.status = RequestStatus.NEW @@ -31,7 +36,7 @@ class EmailProcessor(Context): self.context.clear() # Сохранить письмо в контекст - self.context["email"] = email + self.context.data["email"] = email # Определить клиента email_body = EmailUtils.extract_body(email) @@ -41,17 +46,25 @@ class EmailProcessor(Context): try: # Определить конфиг для пайплайна config = self._load_config(client) - self.context["config"] = config + self.context.data["config"] = config + + # Обработка вложений + attachments_handler_task = AttachmentHandler() + attachments_handler_task.do() # Запустить обработку пайплайна for stage in config["pipeline"]: handler_name = stage["handler"] logger.info(f"Processing handler: {handler_name}") - task = globals()[handler_name](stage.get("config", None), self.context) + task = globals()[handler_name](stage.get("config", None)) task.do() + except FileNotFoundError: logger.error(f"Конфиг для клиента {client} не найден") + + for attachment in self.context.data["attachments"]: + print(attachment["order"]) #except Exception as e: # logger.error(f"Произошла другая ошибка: {e}") diff --git a/src/mail_order_bot/main.py b/src/mail_order_bot/main.py index e065e36..24d1f72 100644 --- a/src/mail_order_bot/main.py +++ b/src/mail_order_bot/main.py @@ -1,4 +1,4 @@ - +import threading from config_manager import ConfigManager from dotenv import load_dotenv import asyncio @@ -9,7 +9,7 @@ from dotenv import load_dotenv from email_client import EmailClient from email_processor import EmailProcessor -from context import Context +from mail_order_bot.context import Context logger = logging.getLogger() @@ -39,8 +39,6 @@ class MailOrderBot(ConfigManager): def execute(self): - logger.info(f"Проверяем почту на наличие новых писем") - # Получить список айдишников письма unread_email_ids = self.email_client.get_emails_id(folder="spareparts") @@ -53,6 +51,7 @@ class MailOrderBot(ConfigManager): # Получить письмо по идентификатору и запустить его обработку email = self.email_client.get_email(email_id) self.email_processor.process_email(email) + pass logger = logging.getLogger()