1 Commits

Author SHA1 Message Date
ac6297415c no message 2026-01-13 23:38:45 +03:00
9 changed files with 127 additions and 106 deletions

View File

@@ -6,6 +6,7 @@
import logging import logging
from ...abstract_task import AbstractTask, pass_if_error, handle_errors from ...abstract_task import AbstractTask, pass_if_error, handle_errors
from ...exceptions import TaskExceptionWithEmailNotify
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -13,12 +14,12 @@ class DeliveryPeriodFromConfig(AbstractTask):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@pass_if_error #@pass_if_error
def do(self, attachment) -> None: def do(self, attachment) -> None:
try: try:
delivery_period = self.config.get("delivery_period") delivery_period = self.config.get("delivery_period")
attachment["delivery_period"] = delivery_period attachment["delivery_period"] = delivery_period
logger.warning(f"Срок доставки установлен из конфига - {delivery_period} (ч.)") logger.warning(f"Срок доставки установлен из конфига - {delivery_period} (ч.)")
except Exception as e: except Exception as e:
raise Exception(f"Ошибка при установке срока доставки из конфига. Детали ошибки: {e}") raise TaskExceptionWithEmailNotify(f"Ошибка при установке срока доставки из конфига. Детали ошибки: {e}")

View File

@@ -3,6 +3,7 @@
""" """
from ...abstract_task import AbstractTask, pass_if_error, handle_errors from ...abstract_task import AbstractTask, pass_if_error, handle_errors
from ...exceptions import TaskExceptionWithEmailNotify
import logging import logging
import re import re
@@ -48,7 +49,7 @@ class DeliveryPeriodFromSubject(AbstractTask):
logger.debug(f"Срок доставки для файла {attachment["name"]} установлен как {delivery_time}") logger.debug(f"Срок доставки для файла {attachment["name"]} установлен как {delivery_time}")
except Exception as e: except Exception as e:
logger.error(e) raise TaskExceptionWithEmailNotify(f"Ошибка при установке срока доставки из темы письма. Детали ошибки: {e}")
def _parse_delivery_period(self, subject: str) -> int: def _parse_delivery_period(self, subject: str) -> int:

View File

@@ -13,8 +13,6 @@ class DeliveryPeriodLocalStore(AbstractTask):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
def do(self) -> None: def do(self, attachment) -> None:
attachments = self.context.data["attachments"] attachment["delivery_period"] = 0
for attachment in attachments: logger.info(f"Срок доставки для файла {attachment["name"]} - только из наличия")
attachment["delivery_period"] = 0
logger.info(f"Срок доставки для файла {attachment["name"]} - только из наличия")

View File

@@ -8,6 +8,7 @@ from email import encoders
from ...abstract_task import AbstractTask, pass_if_error, handle_errors from ...abstract_task import AbstractTask, pass_if_error, handle_errors
from ...exceptions import TaskExceptionWithEmailNotify
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -19,40 +20,41 @@ class EmailReplyTask(AbstractTask):
"""Формирует ответ на входящее письмо с запросом на заказ°""" """Формирует ответ на входящее письмо с запросом на заказ°"""
EMAIl = "zosimovaa@yandex.ru" #"noreply@zapchastiya.ru" EMAIl = "zosimovaa@yandex.ru" #"noreply@zapchastiya.ru"
@pass_if_error #@pass_if_error
#@handle_errors #@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:
raise ValueError("В контексте нет входящего сообщения")
if not email: email_from = self.context.data.get("email_from")
raise ValueError("В контексте нет входящего сообщения") if not email_from:
raise ValueError("В контексте не определен адрес отправителя")
email_from = self.context.data.get("email_from")
if not email_from:
raise ValueError("В контексте не определен адрес отправителя")
reply_message = MIMEMultipart() reply_message = MIMEMultipart()
email_subj = self.context.data.get("email_subj") email_subj = self.context.data.get("email_subj")
reply_message["From"] = os.environ.get("EMAIL_USER") reply_message["From"] = os.environ.get("EMAIL_USER")
reply_message["To"] = email_from reply_message["To"] = email_from
#reply_message["Cc"] = self.config.get("reply_to", "") #reply_message["Cc"] = self.config.get("reply_to", "")
reply_message["Subject"] = f"Re: {email_subj}" reply_message["Subject"] = f"Re: {email_subj}"
reply_message["Date"] = formatdate(localtime=True) reply_message["Date"] = formatdate(localtime=True)
body = "Автоматический ответ на создание заказа" body = "Автоматический ответ на создание заказа"
reply_message.attach(MIMEText(body, "plain", "utf-8")) reply_message.attach(MIMEText(body, "plain", "utf-8"))
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)
logger.warning(f"Сформирован ответ на заказ на email")
logger.warning(f"Сформирован ответ на заказ на email")
except Exception as e:
raise TaskExceptionWithEmailNotify("Произошла ошибка при отправке уведомления клиенту об успешном заказе")
def _attach_file(self, reply_message, attachment): def _attach_file(self, reply_message, attachment):
""" """

View File

@@ -2,6 +2,7 @@ import logging
from io import BytesIO from io import BytesIO
from mail_order_bot.task_processor.abstract_task import AbstractTask, pass_if_error, handle_errors from mail_order_bot.task_processor.abstract_task import AbstractTask, pass_if_error, handle_errors
from mail_order_bot.task_processor.handlers.excel_parcers.order_extractor import ExcelFileParcer from mail_order_bot.task_processor.handlers.excel_parcers.order_extractor import ExcelFileParcer
from ...exceptions import TaskExceptionWithEmailNotify
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -15,10 +16,13 @@ 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 #@pass_if_error
#@handle_errors #@handle_errors
def do(self, attachment) -> None: def do(self, attachment) -> None:
file_bytes = BytesIO(attachment['bytes']) try:
excel_file = ExcelFileParcer(file_bytes, self.excel_config) file_bytes = BytesIO(attachment['bytes'])
attachment["excel"] = excel_file excel_file = ExcelFileParcer(file_bytes, self.excel_config)
logger.warning(f"Произведен успешный парсинг файла {attachment.get('name', 'неизвестный файл')}") attachment["excel"] = excel_file
logger.warning(f"Произведен успешный парсинг файла {attachment.get('name', 'неизвестный файл')}")
except Exception as e:
raise TaskExceptionWithEmailNotify("Произошла ошибка при парсинге эксель файла. Детали ошибки: {e}")

View File

@@ -3,6 +3,7 @@ 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 ...abstract_task import AbstractTask, pass_if_error, handle_errors from ...abstract_task import AbstractTask, pass_if_error, handle_errors
from ...exceptions import TaskExceptionWithEmailNotify
from mail_order_bot.parsers.excel_parcer import ExcelFileParcer from mail_order_bot.parsers.excel_parcer import ExcelFileParcer
@@ -19,23 +20,26 @@ class OrderExtractor(AbstractTask):
self.excel_config = self.config.get("excel", {}) self.excel_config = self.config.get("excel", {})
@pass_if_error @pass_if_error
#@handle_errors #@handle_errors("Произошла ошибка при парсинге заказа")
def do(self, attachment) -> None: def do(self, attachment) -> None:
# todo сделать проверку на наличие файла и его тип try:
delivery_period = attachment.get("delivery_period", 0) # todo сделать проверку на наличие файла и его тип
mapping = self.excel_config.get("mapping") delivery_period = attachment.get("delivery_period", 0)
mapping = self.excel_config.get("mapping")
excel_file = attachment.get("excel") excel_file = attachment.get("excel")
client_id = self.config.get("client_id") client_id = self.config.get("client_id")
order_parcer = OrderParser(mapping, delivery_period, client_id) order_parcer = OrderParser(mapping, delivery_period, client_id)
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)
attachment["order"] = order attachment["order"] = order
logger.warning(f"Файл заказа обработан успешно, извлечено {len(order.positions)} позиций") logger.warning(f"Файл заказа обработан успешно, извлечено {len(order.positions)} позиций")
except Exception as e:
raise TaskExceptionWithEmailNotify(f"Ошибка при парсинге заказа. Детали ошибки: {e}")

View File

@@ -1,6 +1,6 @@
import logging import logging
from ...abstract_task import AbstractTask, pass_if_error, handle_errors from ...abstract_task import AbstractTask, pass_if_error, handle_errors
from ...exceptions import TaskExceptionWithEmailNotify
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -15,31 +15,34 @@ 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 #@pass_if_error
#@handle_errors #@handle_errors
def do(self, attachment) -> None: def do(self, attachment) -> None:
# todo сделать проверку на наличие файла и его тип try:
excel_file = attachment.get("excel") # todo сделать проверку на наличие файла и его тип
order = attachment.get("order") excel_file = attachment.get("excel")
config = self.context.data.get("config", {}) order = attachment.get("order")
excel_config = config.get("excel", {}) config = self.context.data.get("config", {})
updatable_fields = excel_config.get("updatable_fields", {}) excel_config = config.get("excel", {})
updatable_fields = excel_config.get("updatable_fields", {})
for position in order.positions: for position in order.positions:
sku = position.sku sku = position.sku
manufacturer = position.manufacturer manufacturer = position.manufacturer
for key, value in updatable_fields.items(): for key, value in updatable_fields.items():
if key == "ordered_quantity": if key == "ordered_quantity":
column = value column = value
value = position.order_quantity value = position.order_quantity
excel_file.set_value(sku, manufacturer, column, value) excel_file.set_value(sku, manufacturer, column, value)
if key == "ordered_price": if key == "ordered_price":
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)
logger.warning(f"Файла {attachment.get('name', 'неизвестный файл')} отредактирован") logger.warning(f"Файла {attachment.get('name', 'неизвестный файл')} отредактирован")
except Exception as e:
raise TaskExceptionWithEmailNotify(f"Не удалось отредактировать исходный файл с заказом. Детали ошибки: {e}")

View File

@@ -9,6 +9,7 @@ import logging
from ...abstract_task import AbstractTask, pass_if_error, handle_errors 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 ...exceptions import TaskExceptionSilent
from mail_order_bot.telegram.client import TelegramClient from mail_order_bot.telegram.client import TelegramClient
@@ -19,23 +20,26 @@ class TelegramNotifier(AbstractTask):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@pass_if_error #@pass_if_error
# @handle_errors # @handle_errors
def do(self, attachment) -> None: def do(self, attachment) -> None:
message = self.build_message(attachment) try:
message = self.build_message(attachment)
client = TelegramClient() client = TelegramClient()
result = client.send_message(message) result = client.send_message(message)
# Отправка экселя в телеграм # Отправка экселя в телеграм
excel = attachment["excel"] excel = attachment["excel"]
file = excel.get_file_bytes() file = excel.get_file_bytes()
client.send_document( client.send_document(
document=file, document=file,
filename=attachment.get("name", "document.xlsx") filename=attachment.get("name", "document.xlsx")
) )
logger.warning("Инфо по заказу отправлено в телеграм") logger.warning("Инфо по заказу отправлено в телеграм")
except Exception as e:
raise TaskExceptionSilent(f"Ошибка при отправке в телеграм. Детали ошибки: {e}")
def build_message(self, attachment): def build_message(self, attachment):
order = attachment["order"] order = attachment["order"]

View File

@@ -15,17 +15,15 @@ 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
from ...exceptions import TaskException from ...exceptions import TaskExceptionWithEmailNotify
from typing import Dict, Any from typing import Dict, Any
from typing import List, Optional from typing import List, Optional
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class StockSelectorException(TaskException):
pass
class RefusalLevelExceededException(TaskException): class RefusalLevelExceededException(TaskExceptionWithEmailNotify):
pass pass
@@ -46,39 +44,45 @@ class StockSelector(AbstractTask):
#@handle_errors #@handle_errors
def do(self, attachment) -> None: def do(self, attachment) -> None:
# todo сделать проверку на наличие файла и его тип # todo сделать проверку на наличие файла и его тип
order = attachment.get("order", None) try:
delivery_period = attachment.get("delivery_period") order = attachment.get("order", None)
for position in order.positions: delivery_period = attachment.get("delivery_period")
for position in order.positions:
#1. Получаем остатки со складов #1. Получаем остатки со складов
stock_data = self.client_provider.get_stock(position.sku, position.manufacturer) stock_data = self.client_provider.get_stock(position.sku, position.manufacturer)
#2. Из данных остатков выбираем оптимальное значение по стратегии #2. Из данных остатков выбираем оптимальное значение по стратегии
if stock_data["success"]: if stock_data["success"]:
stock_list = stock_data.get("data", []) stock_list = stock_data.get("data", [])
asking_price = position.asking_price asking_price = position.asking_price
asking_quantity = position.asking_quantity asking_quantity = position.asking_quantity
optimal_stock_positions = self.get_optimal_stock(stock_list, asking_price, asking_quantity, delivery_period) optimal_stock_positions = self.get_optimal_stock(stock_list, asking_price, asking_quantity, delivery_period)
# 3. Устанавливаем выбранное значение в позицию # 3. Устанавливаем выбранное значение в позицию
if len(optimal_stock_positions): if len(optimal_stock_positions):
position.set_order_item(optimal_stock_positions[0]) position.set_order_item(optimal_stock_positions[0])
else:
position.status = PositionStatus.NO_AVAILABLE_STOCK
# Мне не очень нравится управление статусами в этом месте, кажется что лучше это делать внутри AutoPartPosition
else: else:
position.status = PositionStatus.NO_AVAILABLE_STOCK position.status = PositionStatus.STOCK_FAILED
# Мне не очень нравится управление статусами в этом месте, кажется что лучше это делать внутри AutoPartPosition
else:
position.status = PositionStatus.STOCK_FAILED
refusal_threshold = self.config.get("refusal_threshold", 1) refusal_threshold = self.config.get("refusal_threshold", 1)
refusal_level = order.get_refusal_level() refusal_level = order.get_refusal_level()
if refusal_level > refusal_threshold: if refusal_level > refusal_threshold:
raise RefusalLevelExceededException(f"Превышен лимит по отказам, необходима ручная обработка. " raise RefusalLevelExceededException(f"Превышен лимит по отказам, необходима ручная обработка. "
f"Уровень отказов: {refusal_level:.2%}, допустимый лимит: {refusal_threshold:.2%}") f"Уровень отказов: {refusal_level:.2%}, допустимый лимит: {refusal_threshold:.2%}")
logger.warning("Определены оптимальные позиции со складов") logger.warning("Определены оптимальные позиции со складов")
except RefusalLevelExceededException:
raise RefusalLevelExceededException
except Exception as e:
raise TaskExceptionWithEmailNotify(f"Произошла ошибка при выборе позиций со складов. Детали ошибки: {e}")
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):
"""Выбирает позицию для заказа""" """Выбирает позицию для заказа"""