no message

This commit is contained in:
2025-12-12 23:25:39 +03:00
parent 1222488aec
commit 0c39af460f
7 changed files with 85 additions and 76 deletions

View File

@@ -16,7 +16,7 @@ pipeline:
- handler: DeliveryPeriodLocalStore
# Запрос остатков со склада
- handler: GetStock
- handler: APIGetStock

View File

@@ -4,8 +4,7 @@ from .destination_time.local_store import DeliveryPeriodLocalStore
from .abcp.api_get_stock import GetStock
from .abcp.api_create_order import InstantOrderTest
from .abcp.api_get_stock import APIGetStock

View File

@@ -1,37 +0,0 @@
import logging
import requests
from mail_order_bot.email_processor.handlers.abstract_task import AbstractTask
from mail_order_bot.email_processor.order.auto_part_order import OrderStatus
logger = logging.getLogger(__name__)
class InstantOrderTest(AbstractTask):
URL = "https://api.telegram.org/bot{0}/sendMessage?chat_id={1}&text={2}"
def do(self) -> None:
api_key = self.config["api_key"]
chat_id = self.config["chat_id"]
if self.order.status == OrderStatus.IN_PROGRESS:
positions = self.order.positions
message = f"Запрос на создание заказа от {self.context['client']}:\n"
message += "\n".join(f"{pos.sku}: {pos.name} ({pos.order_quantity} x {pos.order_price} = {pos.total})" for pos in positions)
elif self.order.status == OrderStatus.OPERATOR_REQUIRED:
message = f"Запрос на создание заказа от {self.context['client']} отклонен - необходима ручная обработка.\n"
message += f"Причина: {self.order.reason}"
else:
message = f"Запрос на создание заказа от {self.context['client']} отклонен.\n"
message += f" Статус заказа: {self.order.status}"
#url = self.URL.format(api_key, chat_id, message)
#resp = requests.get(url).json()
print(message)
#logger.info(resp)

View File

@@ -28,11 +28,14 @@ class APIGetStock(AbstractTask):
for position in order.positions:
# Получаем остатки из-под учетной записи клиента
client_stock = self.client_provider.get_stock(position.sku, position.manufacturer)
system_stock = self.system_provider.get_stock(position.sku, position.manufacturer)
# Используем StockSelector для обработки остатков и выбора оптимального поставщика
# Используем StockSelector для фильтрации неподходящих поставщиков
selector = StockSelector(position, order.delivery_period)
selector.process_stock(client_stock)
selector.select_optimal_supplier()
available_distributors = selector.filter_stock(client_stock, system_stock)
position.set_stock(available_distributors)
position.set_order_item()
logger.info(f"Получены позиции со склада для файла {attachment.get('name', "no name")}")

View File

@@ -1,10 +1,23 @@
from typing import Dict, Any, List
import sys
from turtle import position
from typing import Dict, Any, List, Optional
from decimal import Decimal
from mail_order_bot.email_processor.order.auto_part_position import AutoPartPosition, PositionStatus
logger = __import__('logging').getLogger(__name__)
"""
1. Получить 2 вида складских остатков
2. Добавляем цену закупки
3. Применяем правила фильтрации
- можно указать какие правила применены
4. Выбираем цену по профиту
- можно указать ограничения
"""
class StockSelector:
"""
Класс для выбора оптимального поставщика для позиции заказа.
@@ -23,15 +36,22 @@ class StockSelector:
"""
self.position = position
self.delivery_period = delivery_period
self.client_stock = None
self.system_stock = None
def process_stock(self, client_stock: Dict[str, Any]) -> None:
def filter_stock(self, client_stock, system_stock) -> None:
"""
Обрабатывает результат запроса остатков из-под учетной записи клиента и обновляет позицию.
Args:
client_stock: Результат запроса остатков от API из-под учетной записи клиента
system_stock: Результат запроса остатков от API из-под системной учетной записи
"""
if client_stock["success"]:
self.client_stock = client_stock
self.system_stock = system_stock
if client_stock["success"] and system_stock["success"]:
available_distributors = client_stock["data"]
# Для доставки только с локального склада сперва убираем все остальные склады
@@ -47,39 +67,43 @@ class StockSelector:
# Убираем отрицательные остатки
available_distributors = self._filter_proper_availability(available_distributors)
# Добавляем данные о закупочных ценах
available_distributors = self._set_system_price(available_distributors)
# Сортируем по цене
available_distributors.sort(key=lambda item: Decimal(item["price"]), reverse=False)
self.position.stock = available_distributors
if len(self.position.stock):
self.position.status = PositionStatus.STOCK_RECIEVED
else:
self.position.status = PositionStatus.NO_AVAILABLE_STOCK
return available_distributors
else:
self.position.status = PositionStatus.STOCK_FAILED
return None
def select_optimal_supplier(self) -> None:
"""
Выбирает оптимального поставщика из отфильтрованных складов
и обновляет позицию заказа.
"""
if self.position.status == PositionStatus.STOCK_RECIEVED:
# Вычисляем прибыль для каждого склада
for distributor in self.position.stock:
distributor["profit"] = (
int(distributor["availability"]) * self.position.requested_price -
int(distributor["availability"]) * Decimal(distributor["price"])
def _set_system_price(self, available_distributors):
"""Добавляет закупочную (системную) цену к позициям"""
for distributor in available_distributors:
matching_system_item = self._find_matching_system_item(
distributor.get("distributorId"),
distributor.get("supplierCode")
)
if matching_system_item:
# Добавляем поле system_price со значением price из найденного элемента
distributor["system_price"] = matching_system_item.get("price")
else:
# Если соответствие не найдено, устанавливаем None
distributor["system_price"] = None
return available_distributors
# Сортируем по прибыли (по убыванию)
self.position.stock.sort(key=lambda item: item["profit"], reverse=True)
# Выбираем лучший вариант
self.position.order_quantity = self.position.stock[0]["availability"]
self.position.order_price = self.position.requested_price
self.position.order_item = self.position.stock[0]
self.position.status = PositionStatus.READY
def _find_matching_system_item(self, distributor_id: Any, supplier_code: Any) -> Optional[Dict[str, Any]]:
"""Находит соответствующий элемент в system_stock_data по distributorId и supplierCode"""
for system_item in self.system_stock_data:
if (str(system_item.get("distributorId")) == str(distributor_id) and
str(system_item.get("supplierCode")) == str(supplier_code)):
return system_item
return None
def _filter_only_local_storage(self, distributors: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""Фильтрует только локальные склады"""

View File

@@ -1,3 +1,4 @@
from turtle import st
from typing import List, Optional
from dataclasses import dataclass, field
from typing import Dict, Any
@@ -10,11 +11,9 @@ class PositionStatus(Enum):
STOCK_FAILED = "stock_failed" # Остаток не получен
NO_AVAILABLE_STOCK = "no_available_stock" #Нет доступных складов
READY = "ready"
READY_PARTIAL = "ready_partial"
ORDERED = "ordered" # Заказано
REFUSED = "refused" # Отказано
@dataclass
class AutoPartPosition:
"""
@@ -47,6 +46,26 @@ class AutoPartPosition:
if self.requested_price < 0:
raise ValueError(f"Цена не может быть отрицательной: {self.requested_price}")
def set_stock(self, stock):
if stock is not None:
self.stock = stock
if len(self.stock):
self.status = PositionStatus.STOCK_RECIEVED
else:
self.status = PositionStatus.NO_AVAILABLE_STOCK
else:
self.status = PositionStatus.STOCK_FAILED
def set_order_item(self):
"""Выбирает позицию для заказа по максимальному профиту"""
if self.status == PositionStatus.STOCK_RECIEVED:
for stock_item in self.stock:
available_quantity = min(self.requested_quantity, stock_item["availability"])
stock_item["profit"] = available_quantity * stock_item["price"] - available_quantity * stock_item["system_price"]
self.stock.sort(key=lambda item: Decimal(item["profit"]), reverse=True)
self.order_item = self.stock[0]

View File

@@ -42,6 +42,7 @@ class EmailProcessor:
email_body = EmailUtils.extract_body(email)
email_from = EmailUtils.extract_first_sender(email_body)
client = EmailUtils.extract_domain(email_from)
self.context.data["client"] = client
try:
# Определить конфиг для пайплайна