no message

This commit is contained in:
2025-12-08 23:22:19 +03:00
parent 084dc53baa
commit 67b75e531a
21 changed files with 318 additions and 200 deletions

View File

@@ -1,7 +1,9 @@
import os import os
import hashlib import hashlib
import requests import requests
import logging
logger = logging.getLogger(__name__)
class AbcpProvider: class AbcpProvider:
HOST = "https://id23089.public.api.abcp.ru" HOST = "https://id23089.public.api.abcp.ru"
@@ -17,19 +19,30 @@ class AbcpProvider:
password = os.getenv("ABCP_PASSWORD") password = os.getenv("ABCP_PASSWORD")
self.password = hashlib.md5(password.encode("utf-8")).hexdigest() self.password = hashlib.md5(password.encode("utf-8")).hexdigest()
def get_stock(self, order): def get_stock(self, sku, manufacturer):
method = "GET" method = "GET"
path = "/search/articles" path = "/search/articles"
for position in order.positions: params = {"number": sku, "brand": manufacturer, "withOutAnalogs": "1"}
params = {"number": position.sku, "brand": position.manufacturer, "withOutAnalogs": "1"} return self._execute(path, method, params)
position.stock = self._execute(path, method, params)
def _execute(self, path, method="GET", params={}, data=None): def _execute(self, path, method="GET", params={}, data=None):
params["userlogin"] = self.login params["userlogin"] = self.login
params["userpsw"] = self.password params["userpsw"] = self.password
response = requests.request(method, self.HOST+path, data=data, headers=self.HEADERS, params=params) response = requests.request(method, self.HOST+path, data=data, headers=self.HEADERS, params=params)
if response.status_code != 200: payload = response.json()
raise Exception(response.text) if response.status_code == 200:
return response.json() logger.debug(f"Получены данные об остатках на складе")
result = {
"success": True,
"data": payload
}
else:
logger.warning(f"ошибка получения данных об остатках на складе: {payload}")
result = {
"success": False,
"error": payload
}
return result

View File

@@ -1,8 +1,8 @@
# Настройки обработки ================================================================= # Настройки обработки =================================================================
# Раздел с общими конфигурационными параметрами =============================== # Раздел с общими конфигурационными параметрами ===============================
update_interval: 10 update_interval: 1
work_interval: 30 work_interval: 60
email_dir: "spareparts" email_dir: "spareparts"
# Логирование ================================================================= # Логирование =================================================================
@@ -44,7 +44,7 @@ log:
loggers: loggers:
'': '':
handlers: [console, file, telegram] handlers: [console, file, telegram]
level: INFO level: DEBUG
propagate: False propagate: False
__main__: __main__:

View File

@@ -0,0 +1,25 @@
pipeline:
# Настраиваем парсинг экселя
- handler: BasicExcelParser
config:
sheet_name: 0
key_field: "Номер"
mapping:
article: "Номер"
manufacturer: "Фирма"
name: "Наименование"
price: "Цена"
quantity: "Кол-во"
total: "Сумма"
- handler: GetStock
- handler: LocalStoreOrder

View File

@@ -1,6 +1,5 @@
pipeline: pipeline:
- handler: "ConfigurableExcelParser" - handler: BasicExcelParser
result_section: "positions"
config: config:
sheet_name: 0 sheet_name: 0
key_field: "Код детали" key_field: "Код детали"
@@ -12,6 +11,9 @@ pipeline:
quantity: "Кол-\nво" quantity: "Кол-\nво"
total: "Сумма" total: "Сумма"
- handler: GetStock

View File

@@ -4,32 +4,75 @@ import logging
logger = logging.getLogger() logger = logging.getLogger()
class _SingletonMeta(type): import threading
from typing import Any
class SingletonMeta(type):
_instances = {} _instances = {}
_lock = threading.Lock()
def __call__(cls, *args, **kwargs): def __call__(cls, *args, **kwargs):
if cls not in cls._instances: if cls not in cls._instances:
with cls._lock: instance = super().__call__(*args, **kwargs)
if cls not in cls._instances: cls._instances[cls] = instance
instance = super().__call__(*args, **kwargs)
cls._instances[cls] = instance
return cls._instances[cls] 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 или пустой словарь""" """Очищает self.context, устанавливая его в None или пустой словарь"""
with self._lock: # потокобезопасная очистка self.data = {}
self.context = {} 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 очищен") # опциональный лог logger.debug("Context очищен") # опциональный лог
def set_context(self, new_context: Dict[str, Any]): def set(self, new_context: Dict[str, Any]):
"""Устанавливает новый контекст (бонусный метод)""" """Устанавливает новый контекст (бонусный метод)"""
with self._lock: with self._lock:
self.context = new_context self.data = new_context
logger.debug("Новый контекст установлен") logger.debug("Новый контекст установлен")

View File

@@ -72,7 +72,7 @@ class EmailUtils:
return None return None
@staticmethod @staticmethod
def _extract_attachments(msg: email.message.Message) -> List[EmailAttachment]: def extract_attachments(msg: email.message.Message) -> List[EmailAttachment]:
"""Извлечь вложения из письма.""" """Извлечь вложения из письма."""
attachments = [] attachments = []
@@ -87,16 +87,18 @@ class EmailUtils:
# Получаем содержимое # Получаем содержимое
content = part.get_payload(decode=True) content = part.get_payload(decode=True)
if content: if content:
attachments.append(EmailAttachment(filename=filename, content=content)) #attachments.append(EmailAttachment(filename=filename, content=content))
attachments.append({"name": filename, "bytes": content})
return attachments return attachments
@staticmethod @staticmethod
def extract_domain(email: str) -> str | None: def extract_domain(email_message: str) -> str | None:
"""Вернуть домен из email либо None, если формат странный.""" """Вернуть домен из email либо None, если формат странный."""
if "@" not in email: if "@" not in email_message:
return None return None
# убираем пробелы по краям и берём часть после '@' # убираем пробелы по краям и берём часть после '@'
return email.strip().split("@", 1)[1] return email_message.strip().split("@", 1)[1]

View File

@@ -1,9 +1,16 @@
from .abcp_clients.check_stock import GetStock from .attachment_handler.attachment_handler import AttachmentHandler
from .abcp_clients.create_order import InstantOrderTest
from .excel_parcers.order_parcer_basic import BasicExcelParser 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 .notifications.test_notifier import TestNotifier
from .validators.price_quantity_ckecker import CheckOrder

View File

@@ -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)

View File

@@ -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

View File

@@ -4,13 +4,13 @@ from typing import Dict, Any
from mail_order_bot.context import Context from mail_order_bot.context import Context
class AbstractTask(ABC, Context): class AbstractTask():
RESULT_SECTION = "section" RESULT_SECTION = "section"
""" """
Абстрактный базовый класс для всех хэндлеров. Абстрактный базовый класс для всех хэндлеров.
""" """
def __init__(self, config: Dict[str, Any]) -> None: def __init__(self, config: Dict[str, Any]={}) -> None:
Context.__init__(self, {}) self.context = Context()
self.config = config self.config = config
@abstractmethod @abstractmethod

View File

@@ -1,13 +1,21 @@
import logging
from mail_order_bot.email_processor.handlers.abstract_task import AbstractTask from mail_order_bot.email_processor.handlers.abstract_task import AbstractTask
from mail_order_bot.email_client.utils import EmailUtils from mail_order_bot.email_client.utils import EmailUtils
import logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class AttachmentHandler(AbstractTask): class AttachmentHandler(AbstractTask):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def do(self) -> None: def do(self) -> None:
email = self.context.data["email"]
email = self.context["email"] attachments = EmailUtils.extract_attachments(email)
self.context.data["attachments"] = attachments
logger.debug(f"AttachmentHandler отработал, извлек вложений: {len(attachments)} ")

View File

@@ -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 день.")

View File

@@ -7,46 +7,46 @@ from io import BytesIO
from mail_order_bot.email_processor.handlers.abstract_task import AbstractTask from mail_order_bot.email_processor.handlers.abstract_task import AbstractTask
from ...order.auto_part_position import AutoPartPosition from ...order.auto_part_position import AutoPartPosition
from ...order.auto_part_order import AutoPartOrder
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class BasicExcelParser(AbstractTask): class BasicExcelParser(AbstractTask):
RESULT_SECTION = "positions"
""" """
Универсальный парсер, настраиваемый через конфигурацию. Универсальный парсер, настраиваемый через конфигурацию.
Подходит для большинства стандартных случаев. Подходит для большинства стандартных случаев.
""" """
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def do(self) -> None: def do(self) -> None:
# todo сделать проверку на наличие файла и его тип # todo сделать проверку на наличие файла и его тип
file_bytes = BytesIO(self.context.get("attachment").content) # self.context.get("attachment") #
try:
df = self._make_dataframe(file_bytes)
# Получаем маппинг колонок из конфигурации
mapping = self.config['mapping']
# Парсим строки attachments = self.context.data.get("attachments", [])
positions = [] for attachment in attachments:
for idx, row in df.iterrows(): file_bytes = BytesIO(attachment['bytes']) # self.context.get("attachment") #
try: 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) position = self._parse_row(row, mapping)
if position: if position:
positions.append(position) order.add_position(position)
self.order.add_position(position)
except Exception as e:
logger.error(f"Ошибка парсинга строки {idx}: {e}, {row}")
continue
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]: def _parse_row(self, row: pd.Series, mapping: Dict[str, str]) -> Optional[AutoPartPosition]:
"""Парсит одну строку Excel в OrderPosition""" """Парсит одну строку Excel в OrderPosition"""

View File

@@ -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()

View File

@@ -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 = "Превышен порог отказов"

View File

@@ -0,0 +1,2 @@
from .auto_part_order import AutoPartOrder, OrderStatus
from .auto_part_position import AutoPartPosition, PositionStatus

View File

@@ -1,5 +1,6 @@
from typing import List, Optional from typing import List, Optional
from .auto_part_position import AutoPartPosition from .auto_part_position import AutoPartPosition, PositionStatus
from enum import Enum from enum import Enum
class OrderStatus(Enum): class OrderStatus(Enum):
@@ -15,12 +16,11 @@ class AutoPartOrder:
def __init__(self): def __init__(self):
self.positions: List[AutoPartPosition] = [] self.positions: List[AutoPartPosition] = []
self.status = OrderStatus.NEW self.status = OrderStatus.NEW
self.delivery_period = 0
self.reason = "" self.reason = ""
def add_position(self, position: AutoPartPosition) -> None: def add_position(self, position: AutoPartPosition) -> None:
self.positions.append(position) 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]: def find_positions(self, brand: Optional[str] = None, sku: Optional[str] = None) -> List[AutoPartPosition]:
results = self.positions results = self.positions
@@ -30,5 +30,13 @@ class AutoPartOrder:
results = [p for p in results if p.sku == sku] results = [p for p in results if p.sku == sku]
return results 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): def __len__(self):
return len(self.positions) return len(self.positions)

View File

@@ -2,25 +2,44 @@ from typing import List, Optional
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import Dict, Any from typing import Dict, Any
from decimal import Decimal 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 @dataclass
class AutoPartPosition: class AutoPartPosition:
SUPPLIER_CODE = "11282996"
""" """
Унифицированная модель позиции для заказа. Унифицированная модель позиции для заказа.
Все контрагенты приводятся к этой структуре. Все контрагенты приводятся к этой структуре.
""" """
sku: str # Артикул товара sku: str # Артикул товара
manufacturer: str # Производитель manufacturer: str # Производитель
requested_price: Decimal # Цена за единицу
requested_quantity: int # Количество requested_quantity: int # Количество
total: Decimal = 0 # Общая сумма total: Decimal = 0 # Общая сумма
name: str = "" # Наименование name: str = "" # Наименование
requested_price: Decimal = 0 # Цена за единицу
order_quantity: int = 0 # Количество для заказа order_quantity: int = 0 # Количество для заказа
order_price: Decimal = Decimal('0.0') # Цена в заказе order_price: Decimal = Decimal('0.0') # Цена в заказе
order_item: Dict[str, Any] = field(default_factory=dict)
stock: List[Dict[str, Any]] = None stock: List[Dict[str, Any]] = None
additional_attrs: Dict[str, Any] = field(default_factory=dict) additional_attrs: Dict[str, Any] = field(default_factory=dict)
status: PositionStatus = PositionStatus.NEW
desc: str = ""
def __post_init__(self): def __post_init__(self):
"""Валидация после инициализации""" """Валидация после инициализации"""
if self.requested_quantity < 0: if self.requested_quantity < 0:
@@ -28,50 +47,35 @@ class AutoPartPosition:
if self.requested_price < 0: if self.requested_price < 0:
raise ValueError(f"Цена не может быть отрицательной: {self.requested_price}") 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")

View File

@@ -3,13 +3,17 @@ import yaml
import logging import logging
from typing import Dict, Any from typing import Dict, Any
from pathlib import Path from pathlib import Path
import threading
logger = logging.getLogger(__name__)
from mail_order_bot.context import Context from mail_order_bot.context import Context
from mail_order_bot.email_client.utils import EmailUtils from mail_order_bot.email_client.utils import EmailUtils
from enum import Enum 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): class RequestStatus(Enum):
NEW = "new" NEW = "new"
@@ -20,9 +24,10 @@ class RequestStatus(Enum):
INVALID = "invalid" INVALID = "invalid"
class EmailProcessor(Context): class EmailProcessor:
def __init__(self, configs_path: str): def __init__(self, configs_path: str):
super().__init__() super().__init__()
self.context = Context()
self.configs_path = configs_path self.configs_path = configs_path
self.status = RequestStatus.NEW self.status = RequestStatus.NEW
@@ -31,7 +36,7 @@ class EmailProcessor(Context):
self.context.clear() self.context.clear()
# Сохранить письмо в контекст # Сохранить письмо в контекст
self.context["email"] = email self.context.data["email"] = email
# Определить клиента # Определить клиента
email_body = EmailUtils.extract_body(email) email_body = EmailUtils.extract_body(email)
@@ -41,17 +46,25 @@ class EmailProcessor(Context):
try: try:
# Определить конфиг для пайплайна # Определить конфиг для пайплайна
config = self._load_config(client) 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"]: for stage in config["pipeline"]:
handler_name = stage["handler"] handler_name = stage["handler"]
logger.info(f"Processing handler: {handler_name}") 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() task.do()
except FileNotFoundError: except FileNotFoundError:
logger.error(f"Конфиг для клиента {client} не найден") logger.error(f"Конфиг для клиента {client} не найден")
for attachment in self.context.data["attachments"]:
print(attachment["order"])
#except Exception as e: #except Exception as e:
# logger.error(f"Произошла другая ошибка: {e}") # logger.error(f"Произошла другая ошибка: {e}")

View File

@@ -1,4 +1,4 @@
import threading
from config_manager import ConfigManager from config_manager import ConfigManager
from dotenv import load_dotenv from dotenv import load_dotenv
import asyncio import asyncio
@@ -9,7 +9,7 @@ from dotenv import load_dotenv
from email_client import EmailClient from email_client import EmailClient
from email_processor import EmailProcessor from email_processor import EmailProcessor
from context import Context from mail_order_bot.context import Context
logger = logging.getLogger() logger = logging.getLogger()
@@ -39,8 +39,6 @@ class MailOrderBot(ConfigManager):
def execute(self): def execute(self):
logger.info(f"Проверяем почту на наличие новых писем")
# Получить список айдишников письма # Получить список айдишников письма
unread_email_ids = self.email_client.get_emails_id(folder="spareparts") 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) email = self.email_client.get_email(email_id)
self.email_processor.process_email(email) self.email_processor.process_email(email)
pass
logger = logging.getLogger() logger = logging.getLogger()