no message
This commit is contained in:
@@ -16,7 +16,7 @@ pipeline:
|
||||
- handler: DeliveryPeriodLocalStore
|
||||
|
||||
# Запрос остатков со склада
|
||||
- handler: GetStock
|
||||
- handler: APIGetStock
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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")}")
|
||||
|
||||
|
||||
@@ -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
|
||||
else:
|
||||
self.position.status = PositionStatus.STOCK_FAILED
|
||||
return available_distributors
|
||||
|
||||
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"])
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
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]]:
|
||||
"""Фильтрует только локальные склады"""
|
||||
|
||||
@@ -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]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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:
|
||||
# Определить конфиг для пайплайна
|
||||
|
||||
Reference in New Issue
Block a user