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
from ...abstract_task import AbstractTask, pass_if_error, handle_errors
from ...exceptions import TaskExceptionWithEmailNotify
logger = logging.getLogger(__name__)
@@ -13,12 +14,12 @@ class DeliveryPeriodFromConfig(AbstractTask):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@pass_if_error
#@pass_if_error
def do(self, attachment) -> None:
try:
delivery_period = self.config.get("delivery_period")
attachment["delivery_period"] = delivery_period
logger.warning(f"Срок доставки установлен из конфига - {delivery_period} (ч.)")
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 ...exceptions import TaskExceptionWithEmailNotify
import logging
import re
@@ -48,7 +49,7 @@ class DeliveryPeriodFromSubject(AbstractTask):
logger.debug(f"Срок доставки для файла {attachment["name"]} установлен как {delivery_time}")
except Exception as e:
logger.error(e)
raise TaskExceptionWithEmailNotify(f"Ошибка при установке срока доставки из темы письма. Детали ошибки: {e}")
def _parse_delivery_period(self, subject: str) -> int:

View File

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

View File

@@ -2,6 +2,7 @@ import logging
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.handlers.excel_parcers.order_extractor import ExcelFileParcer
from ...exceptions import TaskExceptionWithEmailNotify
logger = logging.getLogger(__name__)
@@ -15,10 +16,13 @@ class ExcelExtractor(AbstractTask):
super().__init__(*args, **kwargs)
self.excel_config = self.config.get("excel", {})
@pass_if_error
#@pass_if_error
#@handle_errors
def do(self, attachment) -> None:
file_bytes = BytesIO(attachment['bytes'])
excel_file = ExcelFileParcer(file_bytes, self.excel_config)
attachment["excel"] = excel_file
logger.warning(f"Произведен успешный парсинг файла {attachment.get('name', 'неизвестный файл')}")
try:
file_bytes = BytesIO(attachment['bytes'])
excel_file = ExcelFileParcer(file_bytes, self.excel_config)
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 mail_order_bot.parsers.order_parcer import OrderParser
from ...abstract_task import AbstractTask, pass_if_error, handle_errors
from ...exceptions import TaskExceptionWithEmailNotify
from mail_order_bot.parsers.excel_parcer import ExcelFileParcer
@@ -19,23 +20,26 @@ class OrderExtractor(AbstractTask):
self.excel_config = self.config.get("excel", {})
@pass_if_error
#@handle_errors
#@handle_errors("Произошла ошибка при парсинге заказа")
def do(self, attachment) -> None:
# todo сделать проверку на наличие файла и его тип
delivery_period = attachment.get("delivery_period", 0)
mapping = self.excel_config.get("mapping")
try:
# todo сделать проверку на наличие файла и его тип
delivery_period = attachment.get("delivery_period", 0)
mapping = self.excel_config.get("mapping")
excel_file = attachment.get("excel")
client_id = self.config.get("client_id")
excel_file = attachment.get("excel")
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 = order_parcer.parse(order_dataframe)
order_dataframe = excel_file.get_order_rows()
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
from ...abstract_task import AbstractTask, pass_if_error, handle_errors
from ...exceptions import TaskExceptionWithEmailNotify
logger = logging.getLogger(__name__)
@@ -15,31 +15,34 @@ class UpdateExcelFile(AbstractTask):
super().__init__(*args, **kwargs)
self.excel_config = self.config.get("excel", {})
@pass_if_error
#@pass_if_error
#@handle_errors
def do(self, attachment) -> None:
# todo сделать проверку на наличие файла и его тип
excel_file = attachment.get("excel")
order = attachment.get("order")
config = self.context.data.get("config", {})
excel_config = config.get("excel", {})
updatable_fields = excel_config.get("updatable_fields", {})
try:
# todo сделать проверку на наличие файла и его тип
excel_file = attachment.get("excel")
order = attachment.get("order")
config = self.context.data.get("config", {})
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
manufacturer = position.manufacturer
sku = position.sku
manufacturer = position.manufacturer
for key, value in updatable_fields.items():
for key, value in updatable_fields.items():
if key == "ordered_quantity":
column = value
value = position.order_quantity
excel_file.set_value(sku, manufacturer, column, value)
if key == "ordered_quantity":
column = value
value = position.order_quantity
excel_file.set_value(sku, manufacturer, column, value)
if key == "ordered_price":
column = value
value = position.order_price
excel_file.set_value(sku, manufacturer, column, value)
if key == "ordered_price":
column = value
value = position.order_price
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 mail_order_bot.abcp_api.abcp_provider import AbcpProvider
from mail_order_bot.credential_provider import CredentialProvider
from ...exceptions import TaskExceptionSilent
from mail_order_bot.telegram.client import TelegramClient
@@ -19,23 +20,26 @@ class TelegramNotifier(AbstractTask):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@pass_if_error
#@pass_if_error
# @handle_errors
def do(self, attachment) -> None:
message = self.build_message(attachment)
try:
message = self.build_message(attachment)
client = TelegramClient()
result = client.send_message(message)
client = TelegramClient()
result = client.send_message(message)
# Отправка экселя в телеграм
excel = attachment["excel"]
file = excel.get_file_bytes()
client.send_document(
document=file,
filename=attachment.get("name", "document.xlsx")
)
# Отправка экселя в телеграм
excel = attachment["excel"]
file = excel.get_file_bytes()
client.send_document(
document=file,
filename=attachment.get("name", "document.xlsx")
)
logger.warning("Инфо по заказу отправлено в телеграм")
logger.warning("Инфо по заказу отправлено в телеграм")
except Exception as e:
raise TaskExceptionSilent(f"Ошибка при отправке в телеграм. Детали ошибки: {e}")
def build_message(self, attachment):
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.order.auto_part_order import OrderStatus
from ...exceptions import TaskException
from ...exceptions import TaskExceptionWithEmailNotify
from typing import Dict, Any
from typing import List, Optional
logger = logging.getLogger(__name__)
class StockSelectorException(TaskException):
pass
class RefusalLevelExceededException(TaskException):
class RefusalLevelExceededException(TaskExceptionWithEmailNotify):
pass
@@ -46,39 +44,45 @@ class StockSelector(AbstractTask):
#@handle_errors
def do(self, attachment) -> None:
# todo сделать проверку на наличие файла и его тип
order = attachment.get("order", None)
delivery_period = attachment.get("delivery_period")
for position in order.positions:
try:
order = attachment.get("order", None)
delivery_period = attachment.get("delivery_period")
for position in order.positions:
#1. Получаем остатки со складов
stock_data = self.client_provider.get_stock(position.sku, position.manufacturer)
#1. Получаем остатки со складов
stock_data = self.client_provider.get_stock(position.sku, position.manufacturer)
#2. Из данных остатков выбираем оптимальное значение по стратегии
if stock_data["success"]:
stock_list = stock_data.get("data", [])
asking_price = position.asking_price
asking_quantity = position.asking_quantity
#2. Из данных остатков выбираем оптимальное значение по стратегии
if stock_data["success"]:
stock_list = stock_data.get("data", [])
asking_price = position.asking_price
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. Устанавливаем выбранное значение в позицию
if len(optimal_stock_positions):
position.set_order_item(optimal_stock_positions[0])
# 3. Устанавливаем выбранное значение в позицию
if len(optimal_stock_positions):
position.set_order_item(optimal_stock_positions[0])
else:
position.status = PositionStatus.NO_AVAILABLE_STOCK
# Мне не очень нравится управление статусами в этом месте, кажется что лучше это делать внутри AutoPartPosition
else:
position.status = PositionStatus.NO_AVAILABLE_STOCK
# Мне не очень нравится управление статусами в этом месте, кажется что лучше это делать внутри AutoPartPosition
else:
position.status = PositionStatus.STOCK_FAILED
position.status = PositionStatus.STOCK_FAILED
refusal_threshold = self.config.get("refusal_threshold", 1)
refusal_level = order.get_refusal_level()
refusal_threshold = self.config.get("refusal_threshold", 1)
refusal_level = order.get_refusal_level()
if refusal_level > refusal_threshold:
raise RefusalLevelExceededException(f"Превышен лимит по отказам, необходима ручная обработка. "
f"Уровень отказов: {refusal_level:.2%}, допустимый лимит: {refusal_threshold:.2%}")
if refusal_level > refusal_threshold:
raise RefusalLevelExceededException(f"Превышен лимит по отказам, необходима ручная обработка. "
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):
"""Выбирает позицию для заказа"""