diff --git a/src/mail_order_bot/config.yml b/src/mail_order_bot/config.yml index f6b32b5..f4c0c44 100644 --- a/src/mail_order_bot/config.yml +++ b/src/mail_order_bot/config.yml @@ -1,60 +1,4 @@ # Настройки обработки ================================================================= -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 diff --git a/src/mail_order_bot/config2.yml b/src/mail_order_bot/config2.yml deleted file mode 100644 index 6133f76..0000000 --- a/src/mail_order_bot/config2.yml +++ /dev/null @@ -1,28 +0,0 @@ -clients: - "todx.ru": - - handler: "OrderParser", - config: - sheet_name: "Лист1" - key_value: "Артикул" - mapping: - name: "Наименование" - manufacturer: "Производитель" - price: "Цена\nдетали" - quantity: "Кол-\nво" - total: "Сумма" - - handler: "OrderCreator" - - handler: "ExcelWriter" - config: "output.xlsx" - - handler: "EmailSender" - config: - to: "todx@yandex.ru" - - Notifier: - - channel: "email" - to : "status@zapchastiya.ru" - - channel: "telegram" - to: "123454323" - - - - - diff --git a/src/mail_order_bot/email_client/objects.py b/src/mail_order_bot/email_client/objects.py index f4ebb24..b05a00f 100644 --- a/src/mail_order_bot/email_client/objects.py +++ b/src/mail_order_bot/email_client/objects.py @@ -1,5 +1,6 @@ from dataclasses import dataclass from datetime import datetime +from email.message import EmailMessage from typing import List @@ -13,6 +14,8 @@ class EmailAttachment: @dataclass class EmailMessage: """Класс для представления электронного письма""" + message: EmailMessage + attachments: List[EmailAttachment] from_addr: str from_email: str subj: str diff --git a/src/mail_order_bot/suppliers.yml b/src/mail_order_bot/suppliers.yml deleted file mode 100644 index 7fc6855..0000000 --- a/src/mail_order_bot/suppliers.yml +++ /dev/null @@ -1,56 +0,0 @@ -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/src/mail_order_bot/task_handler/excel_parsers/order_position.py b/src/mail_order_bot/task_handler/excel_parsers/order_position.py deleted file mode 100644 index b0e1aac..0000000 --- a/src/mail_order_bot/task_handler/excel_parsers/order_position.py +++ /dev/null @@ -1,25 +0,0 @@ -from dataclasses import dataclass, field -from typing import Dict, Any -from decimal import Decimal - - -@dataclass -class OrderPosition: - """ - Унифицированная модель позиции для заказа. - Все контрагенты приводятся к этой структуре. - """ - article: str # Артикул товара - manufacturer: str # Производитель - name: str # Наименование - price: Decimal # Цена за единицу - quantity: int # Количество - total: Decimal # Общая сумма - additional_attrs: Dict[str, Any] = field(default_factory=dict) - - def __post_init__(self): - """Валидация после инициализации""" - if self.quantity < 0: - raise ValueError(f"Количество не может быть отрицательным: {self.quantity}") - if self.price < 0: - raise ValueError(f"Цена не может быть отрицательной: {self.price}") \ No newline at end of file diff --git a/src/mail_order_bot/task_handler/handlers/__init__.py b/src/mail_order_bot/task_handler/handlers/__init__.py new file mode 100644 index 0000000..7852316 --- /dev/null +++ b/src/mail_order_bot/task_handler/handlers/__init__.py @@ -0,0 +1,6 @@ +from .abcp_clients.check_avaiability import CheckAvailabilityTest +from .abcp_clients.order_creator import InstantOrderTest + +from .excel_parcers.basic_excel_parcer import BasicExcelParser + +from .notifications.test_notifier import TestNotifier diff --git a/src/mail_order_bot/task_handler/handlers/abcp_clients/check_avaiability.py b/src/mail_order_bot/task_handler/handlers/abcp_clients/check_avaiability.py new file mode 100644 index 0000000..b2476c6 --- /dev/null +++ b/src/mail_order_bot/task_handler/handlers/abcp_clients/check_avaiability.py @@ -0,0 +1,25 @@ +import random +import logging +from mail_order_bot.task_handler.handlers.abstract_task import AbstractTask + + +logger = logging.getLogger(__name__) + + +def get_stock(brand, part_number): + return random.randint(0, 10) + +class CheckAvailabilityTest(AbstractTask): + + def do(self) -> None: + positions = self.order.positions + for position in positions: + self._update_stock(position) + + def _update_stock(self, position): + # Эмулируем получение данных + stock = random.randint(0, 10) + price = position.price + + position.stock_price = price + position.stock_remaining = stock diff --git a/src/mail_order_bot/task_handler/abcp_client/OrderCreator.py b/src/mail_order_bot/task_handler/handlers/abcp_clients/order_creator.py similarity index 84% rename from src/mail_order_bot/task_handler/abcp_client/OrderCreator.py rename to src/mail_order_bot/task_handler/handlers/abcp_clients/order_creator.py index dfbc444..6af89ad 100644 --- a/src/mail_order_bot/task_handler/abcp_client/OrderCreator.py +++ b/src/mail_order_bot/task_handler/handlers/abcp_clients/order_creator.py @@ -1,6 +1,6 @@ import logging import requests -from ..abstract_task import AbstractTask +from mail_order_bot.task_handler.handlers.abstract_task import AbstractTask logger = logging.getLogger(__name__) @@ -9,7 +9,7 @@ class InstantOrderTest(AbstractTask): def do(self) -> None: - positions = self.context["positions"] + positions = self.order.positions message = f"Запрос на создание заказа от {self.context['client']}:\n" message += "\n".join(f"{pos.article}: {pos.name} ({pos.quantity} x {pos.price} = {pos.total})" for pos in positions) diff --git a/src/mail_order_bot/task_handler/abstract_task.py b/src/mail_order_bot/task_handler/handlers/abstract_task.py similarity index 79% rename from src/mail_order_bot/task_handler/abstract_task.py rename to src/mail_order_bot/task_handler/handlers/abstract_task.py index 550df0e..f41d11a 100644 --- a/src/mail_order_bot/task_handler/abstract_task.py +++ b/src/mail_order_bot/task_handler/handlers/abstract_task.py @@ -1,15 +1,17 @@ import logging from abc import ABC, abstractmethod from typing import Dict, Any - +from ..order.auto_part_order import AutoPartOrder class AbstractTask(ABC): + RESULT_SECTION = "section" """ Абстрактный базовый класс для всех хэндлеров. """ - def __init__(self, config: Dict[str, Any], context: Dict[str, Any],*args, **kwargs) -> None: + def __init__(self, config: Dict[str, Any], context: Dict[str, Any], order: AutoPartOrder, *args, **kwargs) -> None: self.config = config self.context = context + self.order = order @abstractmethod def do(self) -> None: diff --git a/src/mail_order_bot/task_handler/excel_parsers/__init__.py b/src/mail_order_bot/task_handler/handlers/checks/price_quantity_ckecker.py similarity index 100% rename from src/mail_order_bot/task_handler/excel_parsers/__init__.py rename to src/mail_order_bot/task_handler/handlers/checks/price_quantity_ckecker.py diff --git a/src/mail_order_bot/task_handler/handlers/email/send_email.py b/src/mail_order_bot/task_handler/handlers/email/send_email.py new file mode 100644 index 0000000..31a9093 --- /dev/null +++ b/src/mail_order_bot/task_handler/handlers/email/send_email.py @@ -0,0 +1,141 @@ + +import smtplib +from email.mime.multipart import MIMEMultipart +from email.mime.text import MIMEText +from email.mime.base import MIMEBase +from email.utils import formatdate +from email import encoders +from abc import ABC, abstractmethod +import os + + +class AbstractTask(ABC): + """Базовый класс для задач""" + + def __init__(self, context, config): + self.context = context + self.config = config + + @abstractmethod + def do(self): + """Метод для реализации РІ подклассах""" + pass + + +class EmailReplyTask(AbstractTask): + """Класс для ответа РЅР° электронные РїРёСЃСЊРјР°""" + + def do(self): + """ + Отправляет ответ РЅР° входящее РїРёСЃСЊРјРѕ + + Ожидает РІ self.context: + - message: email.message.Message объект входящего РїРёСЃСЊРјР° + - attachment: путь Рє файлу для вложения + + Ожидает РІ self.config: + - reply_to: адрес электронной почты для РєРѕРїРёРё + - smtp_host: С…РѕСЃС‚ SMTP сервера + - smtp_port: РїРѕСЂС‚ SMTP сервера + - smtp_user: пользователь SMTP + - smtp_password: пароль SMTP + - from_email: адрес отправителя + """ + + incoming_message = self.context.get("message") + attachment_path = self.context.get("attacnment") + + if not incoming_message: + raise ValueError("Р’ context РЅРµ найдено РїРёСЃСЊРјРѕ (message)") + + # Получаем адрес отправителя входящего РїРёСЃСЊРјР° + from_addr = incoming_message.get("From") + if not from_addr: + raise ValueError("Входящее РїРёСЃСЊРјРѕ РЅРµ содержит адреса отправителя") + + # Создаем ответное РїРёСЃСЊРјРѕ + reply_message = MIMEMultipart() + + # Заголовки ответного РїРёСЃСЊРјР° + reply_message["From"] = self.config.get("from_email", "noreply@example.com") + reply_message["To"] = from_addr + reply_message["Cc"] = self.config.get("reply_to", "") + reply_message["Subject"] = f"Re: {incoming_message.get('Subject', '')}" + reply_message["Date"] = formatdate(localtime=True) + + # Тело РїРёСЃСЊРјР° + body = "Ваш заказ создан" + reply_message.attach(MIMEText(body, "plain", "utf-8")) + + # Добавляем вложение если указан путь Рє файлу + if attachment_path and os.path.isfile(attachment_path): + self._attach_file(reply_message, attachment_path) + + # Отправляем РїРёСЃСЊРјРѕ + self._send_email(reply_message, from_addr) + + def _attach_file(self, message, file_path): + """ + Добавляет файл РІ качестве вложения Рє РїРёСЃСЊРјСѓ + + Args: + message: MIMEMultipart объект + file_path: путь Рє файлу для вложения + """ + try: + with open(file_path, "rb") as attachment: + part = MIMEBase("application", "octet-stream") + part.set_payload(attachment.read()) + + encoders.encode_base64(part) + + file_name = os.path.basename(file_path) + part.add_header( + "Content-Disposition", + f"attachment; filename= {file_name}" + ) + + message.attach(part) + except FileNotFoundError: + raise FileNotFoundError(f"Файл РЅРµ найден: {file_path}") + except Exception as e: + raise Exception(f"Ошибка РїСЂРё добавлении вложения: {str(e)}") + + def _send_email(self, message, recipient): + """ + Отправляет РїРёСЃСЊРјРѕ через SMTP + + Args: + message: MIMEMultipart объект РїРёСЃСЊРјР° + recipient: адрес получателя + """ + try: + smtp_host = self.config.get("smtp_host") + smtp_port = self.config.get("smtp_port", 587) + smtp_user = self.config.get("smtp_user") + smtp_password = self.config.get("smtp_password") + + if not all([smtp_host, smtp_user, smtp_password]): + raise ValueError("РќРµ указаны параметры SMTP РІ config") + + with smtplib.SMTP(smtp_host, smtp_port) as server: + server.starttls() + server.login(smtp_user, smtp_password) + + # Получаем адреса получателей (РѕСЃРЅРѕРІРЅРѕР№ + РєРѕРїРёСЏ) + recipients = [recipient] + reply_to = self.config.get("reply_to") + if reply_to: + recipients.append(reply_to) + + server.sendmail( + self.config.get("from_email"), + recipients, + message.as_string() + ) + except smtplib.SMTPException as e: + raise Exception(f"Ошибка SMTP: {str(e)}") + except Exception as e: + raise Exception(f"Ошибка РїСЂРё отправке РїРёСЃСЊРјР°: {str(e)}") + + diff --git a/src/mail_order_bot/task_handler/excel_parsers/basic_excel_parcer.py b/src/mail_order_bot/task_handler/handlers/excel_parcers/basic_excel_parcer.py similarity index 90% rename from src/mail_order_bot/task_handler/excel_parsers/basic_excel_parcer.py rename to src/mail_order_bot/task_handler/handlers/excel_parcers/basic_excel_parcer.py index e23aa08..785cd24 100644 --- a/src/mail_order_bot/task_handler/excel_parsers/basic_excel_parcer.py +++ b/src/mail_order_bot/task_handler/handlers/excel_parcers/basic_excel_parcer.py @@ -1,10 +1,12 @@ import logging import pandas as pd -from typing import Dict, Any, Optional, List +from typing import Dict, Any, Optional from decimal import Decimal from io import BytesIO -from .order_position import OrderPosition -from ..abstract_task import AbstractTask +#from mail_order_bot.task_handler.handlers.order_position import OrderPosition +from mail_order_bot.task_handler.handlers.abstract_task import AbstractTask + +from ...order.auto_part_position import AutoPartPosition logger = logging.getLogger(__name__) @@ -19,7 +21,7 @@ class BasicExcelParser(AbstractTask): def do(self) -> None: # todo сделать проверку на наличие файла и его тип - file_bytes = BytesIO(self.context.get("attachment")) # self.context.get("attachment") # + file_bytes = BytesIO(self.context.get("attachment").content) # self.context.get("attachment") # try: df = self._make_dataframe(file_bytes) # Получаем маппинг колонок из конфигурации @@ -32,6 +34,7 @@ class BasicExcelParser(AbstractTask): 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 @@ -40,11 +43,12 @@ class BasicExcelParser(AbstractTask): self.context[self.RESULT_SECTION] = positions + except Exception as e: logger.error(f"Ошибка при обработке файла: {e}") raise Exception from e - def _parse_row(self, row: pd.Series, mapping: Dict[str, str]) -> Optional[OrderPosition]: + def _parse_row(self, row: pd.Series, mapping: Dict[str, str]) -> Optional[AutoPartPosition]: """Парсит одну строку Excel в OrderPosition""" # Проверяем обязательные поля @@ -69,7 +73,7 @@ class BasicExcelParser(AbstractTask): name = "" # Создаем объект позиции - position = OrderPosition( + position = AutoPartPosition( article=str(row[mapping['article']]).strip(), manufacturer=str(row[mapping.get('manufacturer', "")]).strip(), name=name, diff --git a/src/mail_order_bot/task_handler/notifiers/test_notifier.py b/src/mail_order_bot/task_handler/handlers/notifications/test_notifier.py similarity index 76% rename from src/mail_order_bot/task_handler/notifiers/test_notifier.py rename to src/mail_order_bot/task_handler/handlers/notifications/test_notifier.py index 8e7f3ba..c2a8e64 100644 --- a/src/mail_order_bot/task_handler/notifiers/test_notifier.py +++ b/src/mail_order_bot/task_handler/handlers/notifications/test_notifier.py @@ -1,9 +1,6 @@ import logging -import pandas as pd -from typing import Dict, Any, Optional, List -from decimal import Decimal -from ..abstract_task import AbstractTask +from mail_order_bot.task_handler.handlers.abstract_task import AbstractTask logger = logging.getLogger(__name__) diff --git a/src/mail_order_bot/task_handler/order.py b/src/mail_order_bot/task_handler/order.py deleted file mode 100644 index f865d0f..0000000 --- a/src/mail_order_bot/task_handler/order.py +++ /dev/null @@ -1,15 +0,0 @@ -from enum import Enum - -class OrderStatus(Enum): - NEW = 1 - IN_PROGRESS = 2 - COMPLETED = 3 - FAILED = 4 - OPERATOR_HANDLING = 5 - INVALID = 6 - - -class Order: - def __init__(self, context: dict): - attachment = context["attachment"] - self.context = context \ No newline at end of file diff --git a/src/mail_order_bot/task_handler/notifiers/__init__.py b/src/mail_order_bot/task_handler/order/__init__.py similarity index 100% rename from src/mail_order_bot/task_handler/notifiers/__init__.py rename to src/mail_order_bot/task_handler/order/__init__.py diff --git a/src/mail_order_bot/task_handler/order/auto_part_order.py b/src/mail_order_bot/task_handler/order/auto_part_order.py new file mode 100644 index 0000000..33aed49 --- /dev/null +++ b/src/mail_order_bot/task_handler/order/auto_part_order.py @@ -0,0 +1,24 @@ +from typing import List, Optional +from .auto_part_position import AutoPartPosition +from .ordet_status import OrderStatus + +class AutoPartOrder: + def __init__(self): + self.positions: List[AutoPartPosition] = [] + self.status = OrderStatus.NEW + + 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 + if brand is not None: + results = [p for p in results if p.brand == brand] + if sku is not None: + results = [p for p in results if p.sku == sku] + return results + + def __len__(self): + return len(self.positions) diff --git a/src/mail_order_bot/task_handler/order/auto_part_position.py b/src/mail_order_bot/task_handler/order/auto_part_position.py new file mode 100644 index 0000000..afbc1cd --- /dev/null +++ b/src/mail_order_bot/task_handler/order/auto_part_position.py @@ -0,0 +1,76 @@ +from typing import List, Optional +from dataclasses import dataclass, field +from typing import Dict, Any +from decimal import Decimal + + +@dataclass +class AutoPartPosition: + """ + Унифицированная модель позиции для заказа. + Все контрагенты приводятся к этой структуре. + """ + article: str # Артикул товара + manufacturer: str # Производитель + name: str # Наименование + price: Decimal # Цена за единицу + quantity: int # Количество + total: Decimal # Общая сумма + stock_remaining: int = 0 # Остаток на складе + stock_price: Decimal = Decimal('0.0') # Цена на складе + additional_attrs: Dict[str, Any] = field(default_factory=dict) + + def __post_init__(self): + """Валидация после инициализации""" + if self.quantity < 0: + raise ValueError(f"Количество не может быть отрицательным: {self.quantity}") + if self.price < 0: + raise ValueError(f"Цена не может быть отрицательной: {self.price}") + + + + +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/task_handler/order/ordet_status.py b/src/mail_order_bot/task_handler/order/ordet_status.py new file mode 100644 index 0000000..d43e558 --- /dev/null +++ b/src/mail_order_bot/task_handler/order/ordet_status.py @@ -0,0 +1,10 @@ +from enum import Enum + + +class OrderStatus(Enum): + NEW = 1 + IN_PROGRESS = 2 + FAILED = 4 + COMPLETED = 3 + OPERATOR_REQUIRED = 5 + INVALID = 6 \ No newline at end of file diff --git a/src/mail_order_bot/task_handler/processor.py b/src/mail_order_bot/task_handler/processor.py index ea8b6a9..436300f 100644 --- a/src/mail_order_bot/task_handler/processor.py +++ b/src/mail_order_bot/task_handler/processor.py @@ -1,33 +1,34 @@ -from pathlib import Path import os import yaml import logging from typing import Dict, Any from pathlib import Path -from ..task_handler.excel_parsers.basic_excel_parcer import BasicExcelParser -from ..task_handler.notifiers.test_notifier import TestNotifier -from ..task_handler.abcp_client.OrderCreator import InstantOrderTest - logger = logging.getLogger(__name__) +from .order.auto_part_order import AutoPartOrder +from .handlers import * + class TaskProcessor: def __init__(self, config_path: Path): self.config_path = config_path self.context = dict() + self.order = None def process(self, client, attachment): config = self._load_config(client) self.context = dict() + self.order = AutoPartOrder() + self.context["client"] = client - self.context["attachment"] = attachment.content + self.context["attachment"] = attachment self.context["status"] = client 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), self.context, self.order) task.do() return self.context diff --git a/tests/excel_processor/configs/mikado-parts.ru.yml b/tests/excel_processor/configs/mikado-parts.ru.yml new file mode 100644 index 0000000..589ae2b --- /dev/null +++ b/tests/excel_processor/configs/mikado-parts.ru.yml @@ -0,0 +1,32 @@ +# Конфигурационный файл для контрагента todx.ru + +pipeline: + - + # Обработчик вложений + - handler: "BasicExcelParser" + config: + sheet_name: 0 # Можно указать индекс листа + key_field: "артикул" + mapping: + article: "артикул" + manufacturer: "бренд" + name: "наименование" + price: "цена" + quantity: "количество" + + - handler: "CheckAvailabilityTest" + + - handler: InstantOrderTest + config: + api_key: "8056899069:AAFEfw9QRMvmEwQyH0CI4e_v_sZuOSdNWcE" + chat_id: 211945135 + + - handler: "TestNotifier" + + + + + + + + diff --git a/tests/excel_processor/hanler_test.py b/tests/excel_processor/hanler_test.py index 92872ec..c3a53bf 100644 --- a/tests/excel_processor/hanler_test.py +++ b/tests/excel_processor/hanler_test.py @@ -32,6 +32,7 @@ for provider_name in os.listdir(BASE_PATH): # Создаем объект EmailAttachment att = EmailAttachment(file_name, raw_data) email = EmailMessage( + message=None, from_addr=provider_name, from_email='test@gmail.com', subj='order request',