Добавлен статус заказа и общие действия в декораторах
This commit is contained in:
@@ -14,10 +14,7 @@ class ExcelFileParcer:
|
|||||||
|
|
||||||
def _parse_file(self, file_bytes):
|
def _parse_file(self, file_bytes):
|
||||||
"""Парсит вложение в формате эл таблиц"""
|
"""Парсит вложение в формате эл таблиц"""
|
||||||
try:
|
|
||||||
df = pd.read_excel(file_bytes, sheet_name=self.sheet_name, header=None)
|
df = pd.read_excel(file_bytes, sheet_name=self.sheet_name, header=None)
|
||||||
except Exception as e:
|
|
||||||
df = None
|
|
||||||
return df
|
return df
|
||||||
|
|
||||||
def set_value(self, sku, manufacturer, column, value):
|
def set_value(self, sku, manufacturer, column, value):
|
||||||
|
|||||||
@@ -1,2 +1,4 @@
|
|||||||
from .processor import TaskProcessor
|
from .processor import TaskProcessor
|
||||||
from .message import LogMessage, LogMessageLevel, LogMessageStorage
|
from .message import LogMessage, LogMessageLevel, LogMessageStorage
|
||||||
|
|
||||||
|
from .abstract_task import AbstractTask, pass_if_error, handle_errors
|
||||||
@@ -1,11 +1,60 @@
|
|||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from typing import Dict, Any
|
import logging
|
||||||
|
import functools
|
||||||
|
|
||||||
from mail_order_bot.context import Context
|
from mail_order_bot.context import Context
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def handle_errors(func):
|
||||||
|
"""
|
||||||
|
Декоратор для обработки ошибок в методе do класса AbstractTask.
|
||||||
|
Оборачивает выполнение метода в try-except, при ошибке устанавливает статус "error",
|
||||||
|
логирует ошибку и пробрасывает исключение дальше.
|
||||||
|
Применяется везде к методу do.
|
||||||
|
"""
|
||||||
|
@functools.wraps(func)
|
||||||
|
def wrapper(self, attachment) -> None:
|
||||||
|
file_name = attachment.get("name", "неизвестный файл")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Выполняем метод do
|
||||||
|
return func(self, attachment)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
# При ошибке устанавливаем статус и логируем
|
||||||
|
if attachment:
|
||||||
|
attachment["status"] = "error"
|
||||||
|
logger.error(f"Ошибка при обработке файла {file_name} на стадии {self.STEP} \n{e}", exc_info=True)
|
||||||
|
# Пробрасываем исключение дальше
|
||||||
|
# raise
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
def pass_if_error(func):
|
||||||
|
"""
|
||||||
|
Декоратор для проверки статуса attachment перед выполнением метода do.
|
||||||
|
Если статус attachment["status"] != "ok", метод не выполняется.
|
||||||
|
Применяется опционально в конкретных классах, где нужна проверка статуса.
|
||||||
|
"""
|
||||||
|
@functools.wraps(func)
|
||||||
|
def wrapper(self, attachment) -> None:
|
||||||
|
# Проверяем статус перед выполнением
|
||||||
|
if attachment and attachment.get("status") != "ok":
|
||||||
|
file_name = attachment.get("name", "неизвестный файл")
|
||||||
|
logger.warning(f"Пропускаем шаг для файла {file_name}, статус {attachment.get('status')}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Выполняем метод do
|
||||||
|
return func(self, attachment)
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
class AbstractTask():
|
class AbstractTask():
|
||||||
RESULT_SECTION = "section"
|
STEP = "Название шага обработки"
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Абстрактный базовый класс для всех хэндлеров.
|
Абстрактный базовый класс для всех хэндлеров.
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
from .abcp.api_get_stock import APIGetStock
|
from .abcp._api_get_stock import APIGetStock
|
||||||
from .delivery_time.local_store import DeliveryPeriodLocalStore
|
from .delivery_time.local_store import DeliveryPeriodLocalStore
|
||||||
from .delivery_time.from_config import DeliveryPeriodFromConfig
|
from .delivery_time.from_config import DeliveryPeriodFromConfig
|
||||||
from .notifications.test_notifier import TestNotifier
|
from .notifications.test_notifier import TestNotifier
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ class APIGetStock(AbstractTask):
|
|||||||
self.client_provider = AbcpProvider(login=client_login, password=client_password)
|
self.client_provider = AbcpProvider(login=client_login, password=client_password)
|
||||||
|
|
||||||
def do(self, attachment) -> None:
|
def do(self, attachment) -> None:
|
||||||
|
#
|
||||||
order = attachment.get("order", None)
|
order = attachment.get("order", None)
|
||||||
for position in order.positions:
|
for position in order.positions:
|
||||||
# Получаем остатки из-под учетной записи клиента
|
# Получаем остатки из-под учетной записи клиента
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from mail_order_bot.task_processor.abstract_task import AbstractTask
|
from ...abstract_task import AbstractTask, pass_if_error, handle_errors
|
||||||
from mail_order_bot.abcp_api.abcp_provider import AbcpProvider
|
from mail_order_bot.abcp_api.abcp_provider import AbcpProvider
|
||||||
from mail_order_bot.credential_provider import CredentialProvider
|
from mail_order_bot.credential_provider import CredentialProvider
|
||||||
|
|
||||||
@@ -14,14 +14,16 @@ from mail_order_bot.telegram.client import TelegramClient
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class SaveOrderToTelegram(AbstractTask):
|
class SaveOrderToTelegram(AbstractTask):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
@pass_if_error
|
||||||
|
@handle_errors
|
||||||
def do(self, attachment) -> None:
|
def do(self, attachment) -> None:
|
||||||
client = TelegramClient()
|
client = TelegramClient()
|
||||||
|
|
||||||
try:
|
|
||||||
order = attachment["order"]
|
order = attachment["order"]
|
||||||
positions = order.positions
|
positions = order.positions
|
||||||
message = "\nОбработка заказа {указать название контрагента}\n"
|
message = "\nОбработка заказа {указать название контрагента}\n"
|
||||||
@@ -51,9 +53,7 @@ class SaveOrderToTelegram(AbstractTask):
|
|||||||
document=file,
|
document=file,
|
||||||
filename="document.xlsx"
|
filename="document.xlsx"
|
||||||
)
|
)
|
||||||
except Exception as e:
|
|
||||||
logger.error("Ошибка при отправке инфо по заказу в телеграм")
|
|
||||||
else:
|
|
||||||
logger.warning("Инфо по заказу отправлено в телеграм")
|
logger.warning("Инфо по заказу отправлено в телеграм")
|
||||||
|
|
||||||
#===============================
|
#===============================
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from mail_order_bot.task_processor.abstract_task import AbstractTask
|
from ...abstract_task import AbstractTask, pass_if_error, handle_errors
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -13,17 +13,9 @@ class DeliveryPeriodFromConfig(AbstractTask):
|
|||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
@pass_if_error
|
||||||
|
@handle_errors
|
||||||
def do(self, attachment) -> None:
|
def do(self, attachment) -> None:
|
||||||
try:
|
|
||||||
delivery_period = self.config.get("delivery_period")
|
delivery_period = self.config.get("delivery_period")
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Ошибка при получении срока доставки из конфига: {e}")
|
|
||||||
|
|
||||||
else:
|
|
||||||
attachment["delivery_period"] = delivery_period
|
attachment["delivery_period"] = delivery_period
|
||||||
logger.warning(f"Срок доставки установлен из конфига - {delivery_period} (ч.)")
|
logger.warning(f"Срок доставки установлен из конфига - {delivery_period} (ч.)")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
Парсер срока доставки из темы письма
|
Парсер срока доставки из темы письма
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from mail_order_bot.task_processor.abstract_task import AbstractTask
|
from ...abstract_task import AbstractTask, pass_if_error, handle_errors
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from mail_order_bot.task_processor.abstract_task import AbstractTask
|
from ...abstract_task import AbstractTask, pass_if_error, handle_errors
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from mail_order_bot.task_processor.abstract_task import AbstractTask
|
from ...abstract_task import AbstractTask, pass_if_error, handle_errors
|
||||||
from mail_order_bot.email_client.utils import EmailUtils
|
from mail_order_bot.email_client.utils import EmailUtils
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from email.utils import formatdate
|
|||||||
from email import encoders
|
from email import encoders
|
||||||
|
|
||||||
|
|
||||||
from mail_order_bot.task_processor.abstract_task import AbstractTask
|
from ...abstract_task import AbstractTask, pass_if_error, handle_errors
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -19,9 +19,10 @@ class EmailReplyTask(AbstractTask):
|
|||||||
"""Формирует ответ на входящее письмо с запросом на заказ°"""
|
"""Формирует ответ на входящее письмо с запросом на заказ°"""
|
||||||
EMAIl = "zosimovaa@yandex.ru" #"noreply@zapchastiya.ru"
|
EMAIl = "zosimovaa@yandex.ru" #"noreply@zapchastiya.ru"
|
||||||
|
|
||||||
|
@pass_if_error
|
||||||
|
@handle_errors
|
||||||
def do(self, attachment):
|
def do(self, attachment):
|
||||||
|
|
||||||
try:
|
|
||||||
email = self.context.data.get("email")
|
email = self.context.data.get("email")
|
||||||
|
|
||||||
if not email:
|
if not email:
|
||||||
@@ -49,11 +50,10 @@ class EmailReplyTask(AbstractTask):
|
|||||||
self._attach_file(reply_message, attachment)
|
self._attach_file(reply_message, attachment)
|
||||||
|
|
||||||
self.context.email_client.send_email(reply_message)
|
self.context.email_client.send_email(reply_message)
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Ошибка при отправке ответа по заказу на email \n{e}")
|
|
||||||
else:
|
|
||||||
logger.warning(f"Сформирован ответ на заказ на email")
|
logger.warning(f"Сформирован ответ на заказ на email")
|
||||||
|
|
||||||
|
|
||||||
def _attach_file(self, reply_message, attachment):
|
def _attach_file(self, reply_message, attachment):
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
import logging
|
import logging
|
||||||
import pandas as pd
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
from ...abstract_task import AbstractTask, pass_if_error, handle_errors
|
||||||
from mail_order_bot.email_client import EmailUtils
|
from ....parsers.excel_parcer import ExcelFileParcer
|
||||||
#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.parsers.excel_parcer import ExcelFileParcer
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -19,20 +14,10 @@ class ExcelExtractor(AbstractTask):
|
|||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.excel_config = self.config.get("excel", {})
|
self.excel_config = self.config.get("excel", {})
|
||||||
|
|
||||||
|
@pass_if_error
|
||||||
|
@handle_errors
|
||||||
def do(self, attachment) -> None:
|
def do(self, attachment) -> None:
|
||||||
try:
|
|
||||||
file_bytes = BytesIO(attachment['bytes'])
|
file_bytes = BytesIO(attachment['bytes'])
|
||||||
excel_file = ExcelFileParcer(file_bytes, self.excel_config)
|
excel_file = ExcelFileParcer(file_bytes, self.excel_config)
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Не удалось распарсить файл: \n{e}")
|
|
||||||
attachment["excel"] = None
|
|
||||||
|
|
||||||
else:
|
|
||||||
attachment["excel"] = excel_file
|
attachment["excel"] = excel_file
|
||||||
logger.warning(f"Произведен успешный парсинг файла")
|
logger.warning(f"Произведен успешный парсинг файла {attachment.get('name', 'неизвестный файл')}")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import logging
|
|||||||
import pandas as pd
|
import pandas as pd
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from mail_order_bot.parsers.order_parcer import OrderParser
|
from mail_order_bot.parsers.order_parcer import OrderParser
|
||||||
from mail_order_bot.task_processor.abstract_task import AbstractTask
|
from ...abstract_task import AbstractTask, pass_if_error, handle_errors
|
||||||
|
|
||||||
from mail_order_bot.parsers.excel_parcer import ExcelFileParcer
|
from mail_order_bot.parsers.excel_parcer import ExcelFileParcer
|
||||||
|
|
||||||
@@ -10,6 +10,7 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
class OrderExtractor(AbstractTask):
|
class OrderExtractor(AbstractTask):
|
||||||
|
STEP = "Извлечение заказа"
|
||||||
"""
|
"""
|
||||||
Хендлер для каждого вложения считывает эксель файл и сохраняет его контекст
|
Хендлер для каждого вложения считывает эксель файл и сохраняет его контекст
|
||||||
"""
|
"""
|
||||||
@@ -17,11 +18,10 @@ class OrderExtractor(AbstractTask):
|
|||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.excel_config = self.config.get("excel", {})
|
self.excel_config = self.config.get("excel", {})
|
||||||
|
|
||||||
|
@pass_if_error
|
||||||
|
@handle_errors
|
||||||
def do(self, attachment) -> None:
|
def do(self, attachment) -> None:
|
||||||
# todo сделать проверку на наличие файла и его тип
|
# todo сделать проверку на наличие файла и его тип
|
||||||
|
|
||||||
try:
|
|
||||||
delivery_period = attachment.get("delivery_period", 0)
|
delivery_period = attachment.get("delivery_period", 0)
|
||||||
mapping = self.excel_config.get("mapping")
|
mapping = self.excel_config.get("mapping")
|
||||||
|
|
||||||
@@ -33,12 +33,9 @@ class OrderExtractor(AbstractTask):
|
|||||||
order_dataframe = excel_file.get_order_rows()
|
order_dataframe = excel_file.get_order_rows()
|
||||||
order = order_parcer.parse(order_dataframe)
|
order = order_parcer.parse(order_dataframe)
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Ошибка при парсинге заказа файла: \n{e}")
|
|
||||||
|
|
||||||
else:
|
|
||||||
attachment["order"] = order
|
attachment["order"] = order
|
||||||
logger.warning(f"Обработан файл с заказом, извлечено позиций, {len(order.positions)}")
|
|
||||||
|
logger.warning(f"Файл заказа обработан успешно, извлечено {len(order.positions)} позиций")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,5 @@
|
|||||||
import logging
|
import logging
|
||||||
import pandas as pd
|
from ...abstract_task import AbstractTask, pass_if_error, handle_errors
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
from mail_order_bot.order.auto_part_position import PositionStatus
|
|
||||||
from mail_order_bot.parsers.excel_parcer import ExcelFileParcer
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -20,10 +13,10 @@ class UpdateExcelFile(AbstractTask):
|
|||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.excel_config = self.config.get("excel", {})
|
self.excel_config = self.config.get("excel", {})
|
||||||
|
|
||||||
|
@pass_if_error
|
||||||
|
@handle_errors
|
||||||
def do(self, attachment) -> None:
|
def do(self, attachment) -> None:
|
||||||
# todo сделать проверку на наличие файла и его тип
|
# todo сделать проверку на наличие файла и его тип
|
||||||
|
|
||||||
try:
|
|
||||||
excel_file = attachment.get("excel")
|
excel_file = attachment.get("excel")
|
||||||
order = attachment.get("order")
|
order = attachment.get("order")
|
||||||
config = self.context.data.get("config", {})
|
config = self.context.data.get("config", {})
|
||||||
@@ -46,9 +39,5 @@ class UpdateExcelFile(AbstractTask):
|
|||||||
column = value
|
column = value
|
||||||
value = position.order_price
|
value = position.order_price
|
||||||
excel_file.set_value(sku, manufacturer, column, value)
|
excel_file.set_value(sku, manufacturer, column, value)
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Ошибка при правке excel файла: \n{e}")
|
|
||||||
|
|
||||||
else:
|
|
||||||
logger.warning(f"Файл excel успешно обновлен")
|
|
||||||
|
|
||||||
|
logger.warning(f"Файла {attachment.get('name', 'неизвестный файл')} отредактирован")
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from mail_order_bot.order.auto_part_position import AutoPartPosition, PositionSt
|
|||||||
from mail_order_bot.parsers.excel_parcer import ExcelFileParcer
|
from mail_order_bot.parsers.excel_parcer import ExcelFileParcer
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
from mail_order_bot.task_processor.abstract_task import AbstractTask
|
from ...abstract_task import AbstractTask, pass_if_error, handle_errors
|
||||||
from mail_order_bot.abcp_api.abcp_provider import AbcpProvider
|
from mail_order_bot.abcp_api.abcp_provider import AbcpProvider
|
||||||
from mail_order_bot.credential_provider import CredentialProvider
|
from mail_order_bot.credential_provider import CredentialProvider
|
||||||
from mail_order_bot.order.auto_part_order import OrderStatus
|
from mail_order_bot.order.auto_part_order import OrderStatus
|
||||||
@@ -35,10 +35,10 @@ class StockSelector(AbstractTask):
|
|||||||
client_login, client_password = credential_provider.get_system_credentials()
|
client_login, client_password = credential_provider.get_system_credentials()
|
||||||
self.client_provider = AbcpProvider(login=client_login, password=client_password)
|
self.client_provider = AbcpProvider(login=client_login, password=client_password)
|
||||||
|
|
||||||
|
@pass_if_error
|
||||||
|
@handle_errors
|
||||||
def do(self, attachment) -> None:
|
def do(self, attachment) -> None:
|
||||||
# todo сделать проверку на наличие файла и его тип
|
# todo сделать проверку на наличие файла и его тип
|
||||||
|
|
||||||
try:
|
|
||||||
order = attachment.get("order", None)
|
order = attachment.get("order", None)
|
||||||
delivery_period = attachment.get("delivery_period")
|
delivery_period = attachment.get("delivery_period")
|
||||||
for position in order.positions:
|
for position in order.positions:
|
||||||
@@ -63,14 +63,12 @@ class StockSelector(AbstractTask):
|
|||||||
# Мне не очень нравится управление статусами в этом месте, кажется что лучше это делать внутри AutoPartPosition
|
# Мне не очень нравится управление статусами в этом месте, кажется что лучше это делать внутри AutoPartPosition
|
||||||
else:
|
else:
|
||||||
position.status = PositionStatus.STOCK_FAILED
|
position.status = PositionStatus.STOCK_FAILED
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Ошибка при выборе позиции со складов: {e}")
|
|
||||||
|
|
||||||
else:
|
|
||||||
logger.warning("Определены оптимальные позиции со складов")
|
logger.warning("Определены оптимальные позиции со складов")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def get_optimal_stock(self, stock_list, asking_price, asking_quantity, delivery_period):
|
def get_optimal_stock(self, stock_list, asking_price, asking_quantity, delivery_period):
|
||||||
"""Выбирает позицию для заказа"""
|
"""Выбирает позицию для заказа"""
|
||||||
|
|
||||||
|
|||||||
@@ -90,7 +90,8 @@ class TaskProcessor:
|
|||||||
file_name = attachment["name"]
|
file_name = attachment["name"]
|
||||||
logger.warning(f"Начата обработка файла: {file_name} =>")
|
logger.warning(f"Начата обработка файла: {file_name} =>")
|
||||||
|
|
||||||
attachment["log_messages"] = LogMessageStorage(file_name)
|
#attachment["log_messages"] = LogMessageStorage(file_name)
|
||||||
|
attachment["status"] = "ok"
|
||||||
|
|
||||||
# Запустить обработку пайплайна
|
# Запустить обработку пайплайна
|
||||||
for handler_name in pipeline:
|
for handler_name in pipeline:
|
||||||
|
|||||||
Reference in New Issue
Block a user