Compare commits
2 Commits
67b75e531a
...
features/f
| Author | SHA1 | Date | |
|---|---|---|---|
| 816da1eb16 | |||
| 03aca0ecb9 |
@@ -13,22 +13,20 @@ class AbcpProvider:
|
|||||||
"Content-Type": "application/x-www-form-urlencoded"
|
"Content-Type": "application/x-www-form-urlencoded"
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, account="SYSTEM"):
|
||||||
self.base_url = self.HOST
|
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"
|
method = "GET"
|
||||||
path = "/search/articles"
|
path = "/search/articles"
|
||||||
|
|
||||||
params = {"number": sku, "brand": manufacturer, "withOutAnalogs": "1"}
|
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)
|
response = requests.request(method, self.HOST+path, data=data, headers=self.HEADERS, params=params)
|
||||||
payload = response.json()
|
payload = response.json()
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
|
|||||||
@@ -12,10 +12,15 @@ pipeline:
|
|||||||
quantity: "Кол-во"
|
quantity: "Кол-во"
|
||||||
total: "Сумма"
|
total: "Сумма"
|
||||||
|
|
||||||
|
# Определяем логику обработки заказа (в данном случае все с локального склада)
|
||||||
|
- handler: DeliveryPeriodLocalStore
|
||||||
|
|
||||||
|
# Запрос остатков со склада
|
||||||
- handler: GetStock
|
- handler: GetStock
|
||||||
|
|
||||||
- handler: LocalStoreOrder
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
from .attachment_handler.attachment_handler import AttachmentHandler
|
from .attachment_handler.attachment_handler import AttachmentHandler
|
||||||
from .excel_parcers.order_parcer_basic import BasicExcelParser
|
from .excel_parcers.order_parcer_basic import BasicExcelParser
|
||||||
from .destination_time.local_store import DeliveryPeriodLocalStore
|
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__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class GetStock(AbstractTask):
|
class APIGetStock(AbstractTask):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.abcp_provider = AbcpProvider()
|
self.abcp_provider = AbcpProvider()
|
||||||
@@ -15,10 +15,12 @@ class GetStock(AbstractTask):
|
|||||||
def do(self) -> None:
|
def do(self) -> None:
|
||||||
attachments = self.context.data.get("attachments", [])
|
attachments = self.context.data.get("attachments", [])
|
||||||
for attachment in attachments:
|
for attachment in attachments:
|
||||||
for position in attachment["order"].positions:
|
order = attachment["order"]
|
||||||
position.update_stock(self.get_stock(position.sku, position.manufacturer))
|
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")}")
|
logger.info(f"Получены позиции со склада для файла {attachment.get('name', "no name")}")
|
||||||
|
|
||||||
|
|
||||||
def get_stock(self, sku: str, manufacturer: str) -> int:
|
def get_stock(self, sku: str, manufacturer: str) -> int:
|
||||||
return self.abcp_provider.get_stock(sku, manufacturer)
|
return self.abcp_provider.get_stock(sku, manufacturer)
|
||||||
@@ -14,7 +14,7 @@ class DeliveryPeriodLocalStore(AbstractTask):
|
|||||||
for attachment in attachments:
|
for attachment in attachments:
|
||||||
order = attachment["order"]
|
order = attachment["order"]
|
||||||
order.set_delivery_period(0)
|
order.set_delivery_period(0)
|
||||||
logger.debug(f"Доставка только с локального склада, срок 1 день.")
|
logger.info(f"Доставка только с локального склада, срок 1 день.")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -21,4 +21,4 @@ class LocalStoreOrder(AbstractTask):
|
|||||||
attachments = self.context.data["attachments"]
|
attachments = self.context.data["attachments"]
|
||||||
for attachment in attachments:
|
for attachment in attachments:
|
||||||
order = attachment["order"]
|
order = attachment["order"]
|
||||||
order.fill_from_local_supplier()
|
|
||||||
@@ -18,6 +18,7 @@ class AutoPartOrder:
|
|||||||
self.status = OrderStatus.NEW
|
self.status = OrderStatus.NEW
|
||||||
self.delivery_period = 0
|
self.delivery_period = 0
|
||||||
self.reason = ""
|
self.reason = ""
|
||||||
|
self.errors = []
|
||||||
|
|
||||||
def add_position(self, position: AutoPartPosition) -> None:
|
def add_position(self, position: AutoPartPosition) -> None:
|
||||||
self.positions.append(position)
|
self.positions.append(position)
|
||||||
@@ -35,7 +36,23 @@ class AutoPartOrder:
|
|||||||
|
|
||||||
def fill_from_local_supplier(self) -> None:
|
def fill_from_local_supplier(self) -> None:
|
||||||
for position in self.positions:
|
for position in self.positions:
|
||||||
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
|
||||||
|
[PositionStatus.REFUSED, PositionStatus.STOCK_FAILED]])
|
||||||
|
|
||||||
|
order_refusal_rate = refusal_positions_count / len(self.positions)
|
||||||
|
if order_refusal_rate > order_refusal_threshold:
|
||||||
|
self.errors.append(f"Превышен порог отказов в заказе - {order_refusal_rate:.0%} "
|
||||||
|
f"({refusal_positions_count} из {len(self.positions)})")
|
||||||
|
self.status = OrderStatus.OPERATOR_REQUIRED
|
||||||
|
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ class PositionStatus(Enum):
|
|||||||
NEW = "new" # Новая позиция
|
NEW = "new" # Новая позиция
|
||||||
STOCK_RECIEVED = "stock_received" # Получен остаток
|
STOCK_RECIEVED = "stock_received" # Получен остаток
|
||||||
STOCK_FAILED = "stock_failed" # Остаток не получен
|
STOCK_FAILED = "stock_failed" # Остаток не получен
|
||||||
|
NO_AVAILABLE_STOCK = "no_available_stock" #Нет доступных складов
|
||||||
READY = "ready"
|
READY = "ready"
|
||||||
READY_PARTIAL = "ready_partial"
|
READY_PARTIAL = "ready_partial"
|
||||||
ORDERED = "ordered" # Заказано
|
ORDERED = "ordered" # Заказано
|
||||||
@@ -16,7 +17,7 @@ class PositionStatus(Enum):
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class AutoPartPosition:
|
class AutoPartPosition:
|
||||||
SUPPLIER_CODE = "11282996"
|
DISTRIBUTOR_ID = "1577730"
|
||||||
"""
|
"""
|
||||||
Унифицированная модель позиции для заказа.
|
Унифицированная модель позиции для заказа.
|
||||||
Все контрагенты приводятся к этой структуре.
|
Все контрагенты приводятся к этой структуре.
|
||||||
@@ -47,35 +48,71 @@ class AutoPartPosition:
|
|||||||
if self.requested_price < 0:
|
if self.requested_price < 0:
|
||||||
raise ValueError(f"Цена не может быть отрицательной: {self.requested_price}")
|
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"]:
|
if stock["success"]:
|
||||||
self.stock = stock["data"]
|
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
|
self.status = PositionStatus.STOCK_RECIEVED
|
||||||
|
else:
|
||||||
|
self.status = PositionStatus.NO_AVAILABLE_STOCK
|
||||||
else:
|
else:
|
||||||
self.status = PositionStatus.STOCK_FAILED
|
self.status = PositionStatus.STOCK_FAILED
|
||||||
|
|
||||||
|
|
||||||
def fill_from_local_supplier(self):
|
def fill_from_stock(self):
|
||||||
if self.status != PositionStatus.STOCK_RECIEVED:
|
if self.status == PositionStatus.STOCK_RECIEVED:
|
||||||
return
|
|
||||||
|
|
||||||
supplier_stock_items = [item for item in self.stock if str(item["supplierCode"]) == self.SUPPLIER_CODE]
|
for distributor in self.stock:
|
||||||
supplier_stock_items.sort(key=lambda item: Decimal(item["price"]), reverse=False)
|
distributor["profit"] = int(distributor["availability"]) * self.requested_price - int(distributor["availability"]) * Decimal(distributor["price"])
|
||||||
|
|
||||||
if len(supplier_stock_items) == 0:
|
self.stock.sort(key=lambda item: item["profit"], reverse=True)
|
||||||
self.status = PositionStatus.REFUSED
|
|
||||||
self.desc = "Нет на складе"
|
self.order_quantity = self.stock[0]["availability"]
|
||||||
return
|
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.status = PositionStatus.READY
|
||||||
self.desc = "Готов к заказу"
|
|
||||||
|
|
||||||
else:
|
|
||||||
self.order_quantity = supplier_stock_items[0]["availability"]
|
def _filter_only_local_storage(self, distributors):
|
||||||
self.status = PositionStatus.READY_PARTIAL
|
return [item for item in distributors if str(item["distributorId"]) == self.DISTRIBUTOR_ID]
|
||||||
self.desc = "Частичный остаток"
|
|
||||||
|
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} не найден")
|
logger.error(f"Конфиг для клиента {client} не найден")
|
||||||
|
|
||||||
for attachment in self.context.data["attachments"]:
|
for attachment in self.context.data["attachments"]:
|
||||||
print(attachment["order"])
|
print(attachment["order"].__dict__)
|
||||||
#except Exception as e:
|
#except Exception as e:
|
||||||
# logger.error(f"Произошла другая ошибка: {e}")
|
# logger.error(f"Произошла другая ошибка: {e}")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user