no message
This commit is contained in:
@@ -13,22 +13,20 @@ class AbcpProvider:
|
||||
"Content-Type": "application/x-www-form-urlencoded"
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, account="SYSTEM"):
|
||||
self.base_url = self.HOST
|
||||
self.login = os.getenv("ABCP_LOGIN")
|
||||
password = os.getenv("ABCP_PASSWORD")
|
||||
self.password = hashlib.md5(password.encode("utf-8")).hexdigest()
|
||||
|
||||
def get_stock(self, sku, manufacturer):
|
||||
def get_stock(self, sku, manufacturer, partner="SYSTEM"):
|
||||
method = "GET"
|
||||
path = "/search/articles"
|
||||
|
||||
params = {"number": sku, "brand": manufacturer, "withOutAnalogs": "1"}
|
||||
return self._execute(path, method, params)
|
||||
return self._execute(partner, path, method, params)
|
||||
|
||||
def _execute(self, partner, path, method="GET", params={}, data=None, ):
|
||||
params["userlogin"] = os.getenv(f"ABCP_LOGIN_{partner}")
|
||||
params["userpsw"] = hashlib.md5(os.getenv(f"ABCP_PASSWORD_{partner}").encode("utf-8")).hexdigest()
|
||||
|
||||
def _execute(self, path, method="GET", params={}, data=None):
|
||||
params["userlogin"] = self.login
|
||||
params["userpsw"] = self.password
|
||||
response = requests.request(method, self.HOST+path, data=data, headers=self.HEADERS, params=params)
|
||||
payload = response.json()
|
||||
if response.status_code == 200:
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
pipeline:
|
||||
-
|
||||
# Настраиваем парсинг экселя
|
||||
- handler: BasicExcelParser
|
||||
config:
|
||||
@@ -13,11 +12,13 @@ pipeline:
|
||||
quantity: "Кол-во"
|
||||
total: "Сумма"
|
||||
|
||||
# Определяем логику обработки заказа (в данном случае все с локального склада)
|
||||
- handler: DeliveryPeriodLocalStore
|
||||
|
||||
# Запрос остатков со склада
|
||||
- handler: GetStock
|
||||
|
||||
# Определяем логику обработки заказа (в данном случае все с локального склада)
|
||||
- handler: LocalStoreOrder
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
from .attachment_handler.attachment_handler import AttachmentHandler
|
||||
from .excel_parcers.order_parcer_basic import BasicExcelParser
|
||||
from .destination_time.local_store import DeliveryPeriodLocalStore
|
||||
from .logic.only_local_store_order import LocalStoreOrder
|
||||
|
||||
|
||||
from .abcp.get_stock import GetStock
|
||||
from .abcp.create_order import InstantOrderTest
|
||||
|
||||
from .abcp.api_get_stock import GetStock
|
||||
from .abcp.api_create_order import InstantOrderTest
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ from mail_order_bot.abcp_api.abcp_provider import AbcpProvider
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GetStock(AbstractTask):
|
||||
class APIGetStock(AbstractTask):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.abcp_provider = AbcpProvider()
|
||||
@@ -15,10 +15,12 @@ class GetStock(AbstractTask):
|
||||
def do(self) -> None:
|
||||
attachments = self.context.data.get("attachments", [])
|
||||
for attachment in attachments:
|
||||
for position in attachment["order"].positions:
|
||||
position.update_stock(self.get_stock(position.sku, position.manufacturer))
|
||||
order = attachment["order"]
|
||||
for position in order.positions:
|
||||
stock = self.get_stock(position.sku, position.manufacturer)
|
||||
position.update_stock(stock, order.delivery_period)
|
||||
position.fill_from_stock()
|
||||
logger.info(f"Получены позиции со склада для файла {attachment.get('name', "no name")}")
|
||||
|
||||
|
||||
def get_stock(self, sku: str, manufacturer: str) -> int:
|
||||
return self.abcp_provider.get_stock(sku, manufacturer)
|
||||
@@ -14,7 +14,7 @@ class DeliveryPeriodLocalStore(AbstractTask):
|
||||
for attachment in attachments:
|
||||
order = attachment["order"]
|
||||
order.set_delivery_period(0)
|
||||
logger.debug(f"Доставка только с локального склада, срок 1 день.")
|
||||
logger.info(f"Доставка только с локального склада, срок 1 день.")
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
import random
|
||||
import logging
|
||||
from mail_order_bot.email_processor.handlers.abstract_task import AbstractTask
|
||||
from mail_order_bot.email_processor.order.auto_part_order import OrderStatus
|
||||
from mail_order_bot.email_processor.order.auto_part_position import AutoPartPosition, PositionStatus
|
||||
from decimal import Decimal
|
||||
import random
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LocalStoreOrder(AbstractTask):
|
||||
"""Сейчас логика такая
|
||||
- ищем на складе наш сапплиер код, берем самую дешевую позицию и делаем заказ из нее
|
||||
|
||||
Другие чуть более дорогие не рассматриваем
|
||||
|
||||
"""
|
||||
# это код нашего склада
|
||||
|
||||
def do(self) -> None:
|
||||
attachments = self.context.data["attachments"]
|
||||
for attachment in attachments:
|
||||
order = attachment["order"]
|
||||
order.fill_from_local_supplier()
|
||||
@@ -33,13 +33,16 @@ class AutoPartOrder:
|
||||
|
||||
def set_delivery_period(self, delivery_period: int) -> None:
|
||||
self.delivery_period = delivery_period
|
||||
ы
|
||||
def fill_from_local_supplier(self) -> None:
|
||||
|
||||
def fill_from_local_supplier(self) -> None:
|
||||
for position in self.positions:
|
||||
errors = position.fill_from_local_supplier()
|
||||
errors = position.fill_from_stock()
|
||||
self.errors += errors
|
||||
|
||||
|
||||
|
||||
def check_order(self, config) -> None:
|
||||
""" Проверяет заказ на возможность исполнения"""
|
||||
# 1. Проверка общего количества отказов
|
||||
order_refusal_threshold = config.get("order_refusal_threshold", 1)
|
||||
refusal_positions_count = len([position for position in self.positions if str(position.status) in
|
||||
|
||||
@@ -8,6 +8,7 @@ class PositionStatus(Enum):
|
||||
NEW = "new" # Новая позиция
|
||||
STOCK_RECIEVED = "stock_received" # Получен остаток
|
||||
STOCK_FAILED = "stock_failed" # Остаток не получен
|
||||
NO_AVAILABLE_STOCK = "no_available_stock" #Нет доступных складов
|
||||
READY = "ready"
|
||||
READY_PARTIAL = "ready_partial"
|
||||
ORDERED = "ordered" # Заказано
|
||||
@@ -16,7 +17,7 @@ class PositionStatus(Enum):
|
||||
|
||||
@dataclass
|
||||
class AutoPartPosition:
|
||||
SUPPLIER_CODE = "11282996"
|
||||
DISTRIBUTOR_ID = "1577730"
|
||||
"""
|
||||
Унифицированная модель позиции для заказа.
|
||||
Все контрагенты приводятся к этой структуре.
|
||||
@@ -47,46 +48,68 @@ class AutoPartPosition:
|
||||
if self.requested_price < 0:
|
||||
raise ValueError(f"Цена не может быть отрицательной: {self.requested_price}")
|
||||
|
||||
def update_stock(self, stock: Dict[str, Any]):
|
||||
def update_stock(self, stock: Dict[str, Any], delivery_period: int = 0) -> None:
|
||||
if stock["success"]:
|
||||
self.stock = stock["data"]
|
||||
self.status = PositionStatus.STOCK_RECIEVED
|
||||
available_distributors = stock["data"]
|
||||
|
||||
# Для доставки только с локального склада сперва убираем все остальные склады
|
||||
if delivery_period == 0:
|
||||
available_distributors = self._filter_only_local_storage(available_distributors)
|
||||
|
||||
#Отбираем склады по сроку доставки
|
||||
available_distributors = self._filter_proper_delivery_time(available_distributors, delivery_period)
|
||||
|
||||
# Убираем дорогие склады с ценой выше запрошенной
|
||||
available_distributors = self._filter_proper_price(available_distributors)
|
||||
|
||||
# Убираем отрицательные остатки
|
||||
available_distributors = self._filter_proper_availability(available_distributors)
|
||||
|
||||
# Сортируем по цене
|
||||
available_distributors.sort(key=lambda item: Decimal(item["price"]), reverse=False)
|
||||
|
||||
self.stock = available_distributors
|
||||
if len (self.stock):
|
||||
self.status = PositionStatus.STOCK_RECIEVED
|
||||
else:
|
||||
self.status = PositionStatus.NO_AVAILABLE_STOCK
|
||||
else:
|
||||
self.status = PositionStatus.STOCK_FAILED
|
||||
|
||||
|
||||
def fill_from_local_supplier(self):
|
||||
if self.status != PositionStatus.STOCK_RECIEVED:
|
||||
return []
|
||||
def fill_from_stock(self):
|
||||
if self.status == PositionStatus.STOCK_RECIEVED:
|
||||
|
||||
supplier_stock_items = [item for item in self.stock if str(item["supplierCode"]) == self.SUPPLIER_CODE]
|
||||
supplier_stock_items.sort(key=lambda item: Decimal(item["price"]), reverse=False)
|
||||
for distributor in self.stock:
|
||||
distributor["profit"] = int(distributor["availability"]) * self.requested_price - int(distributor["availability"]) * Decimal(distributor["price"])
|
||||
|
||||
if len(supplier_stock_items) == 0:
|
||||
self.status = PositionStatus.REFUSED
|
||||
self.desc = "Нет на складе"
|
||||
return
|
||||
self.stock.sort(key=lambda item: item["profit"], reverse=True)
|
||||
|
||||
self.order_quantity = self.stock[0]["availability"]
|
||||
self.order_price = self.requested_price
|
||||
self.order_item = self.stock[0]
|
||||
|
||||
elif self.requested_quantity <= supplier_stock_items[0]["availability"]:
|
||||
self.order_quantity = self.requested_quantity
|
||||
self.status = PositionStatus.READY
|
||||
self.desc = "Готов к заказу"
|
||||
self._set_price(supplier_stock_items[0])
|
||||
|
||||
else:
|
||||
self.order_quantity = supplier_stock_items[0]["availability"]
|
||||
self.status = PositionStatus.READY
|
||||
self.status = PositionStatus.READY_PARTIAL
|
||||
self.desc = "Частичный остаток"
|
||||
self._set_price(supplier_stock_items[0])
|
||||
|
||||
def _set_price(self, stok_item: Dict[str, Any]):
|
||||
stok_item_price = Decimal(stok_item["price"])
|
||||
if stok_item_price <= self.requested_price:
|
||||
self.order_price = stok_item_price
|
||||
else:
|
||||
self.status = PositionStatus.REFUSED
|
||||
self.desc = "Превышение по цене"
|
||||
def _filter_only_local_storage(self, distributors):
|
||||
return [item for item in distributors if str(item["distributorId"]) == self.DISTRIBUTOR_ID]
|
||||
|
||||
def _filter_proper_delivery_time(self, distributors, delivery_period):
|
||||
return [item for item in distributors if item["deliveryPeriod"] <= delivery_period]
|
||||
|
||||
def _filter_proper_price(self, distributors):
|
||||
return [item for item in distributors if Decimal(item["price"]) <= self.requested_price]
|
||||
|
||||
def _filter_proper_availability(self, distributors):
|
||||
return [item for item in distributors if Decimal(item["availability"]) > 0]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ class EmailProcessor:
|
||||
logger.error(f"Конфиг для клиента {client} не найден")
|
||||
|
||||
for attachment in self.context.data["attachments"]:
|
||||
print(attachment["order"])
|
||||
print(attachment["order"].__dict__)
|
||||
#except Exception as e:
|
||||
# logger.error(f"Произошла другая ошибка: {e}")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user