diff --git a/src/mail_order_bot/abcp_api/abcp_provider.py b/src/mail_order_bot/abcp_api/abcp_provider.py index 13e3c1d..a0597f7 100644 --- a/src/mail_order_bot/abcp_api/abcp_provider.py +++ b/src/mail_order_bot/abcp_api/abcp_provider.py @@ -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: diff --git a/src/mail_order_bot/configs/amtel.club.yml b/src/mail_order_bot/configs/amtel.club.yml index 19a2ab0..8982936 100644 --- a/src/mail_order_bot/configs/amtel.club.yml +++ b/src/mail_order_bot/configs/amtel.club.yml @@ -1,5 +1,4 @@ pipeline: - - # Настраиваем парсинг экселя - handler: BasicExcelParser config: @@ -13,11 +12,13 @@ pipeline: quantity: "Кол-во" total: "Сумма" + # Определяем логику обработки заказа (в данном случае все с локального склада) + - handler: DeliveryPeriodLocalStore + # Запрос остатков со склада - handler: GetStock - # Определяем логику обработки заказа (в данном случае все с локального склада) - - handler: LocalStoreOrder + diff --git a/src/mail_order_bot/email_processor/handlers/__init__.py b/src/mail_order_bot/email_processor/handlers/__init__.py index dc4cade..72398c4 100644 --- a/src/mail_order_bot/email_processor/handlers/__init__.py +++ b/src/mail_order_bot/email_processor/handlers/__init__.py @@ -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 diff --git a/src/mail_order_bot/email_processor/handlers/abcp/create_order.py b/src/mail_order_bot/email_processor/handlers/abcp/api_create_order.py similarity index 100% rename from src/mail_order_bot/email_processor/handlers/abcp/create_order.py rename to src/mail_order_bot/email_processor/handlers/abcp/api_create_order.py diff --git a/src/mail_order_bot/email_processor/handlers/abcp/get_stock.py b/src/mail_order_bot/email_processor/handlers/abcp/api_get_stock.py similarity index 70% rename from src/mail_order_bot/email_processor/handlers/abcp/get_stock.py rename to src/mail_order_bot/email_processor/handlers/abcp/api_get_stock.py index 8cd93a0..985f73c 100644 --- a/src/mail_order_bot/email_processor/handlers/abcp/get_stock.py +++ b/src/mail_order_bot/email_processor/handlers/abcp/api_get_stock.py @@ -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) \ No newline at end of file diff --git a/src/mail_order_bot/email_processor/handlers/destination_time/local_store.py b/src/mail_order_bot/email_processor/handlers/destination_time/local_store.py index fb8d023..e1c1878 100644 --- a/src/mail_order_bot/email_processor/handlers/destination_time/local_store.py +++ b/src/mail_order_bot/email_processor/handlers/destination_time/local_store.py @@ -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 день.") diff --git a/src/mail_order_bot/email_processor/handlers/logic/only_local_store_order.py b/src/mail_order_bot/email_processor/handlers/logic/only_local_store_order.py deleted file mode 100644 index a76e317..0000000 --- a/src/mail_order_bot/email_processor/handlers/logic/only_local_store_order.py +++ /dev/null @@ -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() diff --git a/src/mail_order_bot/email_processor/order/auto_part_order.py b/src/mail_order_bot/email_processor/order/auto_part_order.py index 968b4a9..ada6b17 100644 --- a/src/mail_order_bot/email_processor/order/auto_part_order.py +++ b/src/mail_order_bot/email_processor/order/auto_part_order.py @@ -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 diff --git a/src/mail_order_bot/email_processor/order/auto_part_position.py b/src/mail_order_bot/email_processor/order/auto_part_position.py index d024991..45089bd 100644 --- a/src/mail_order_bot/email_processor/order/auto_part_position.py +++ b/src/mail_order_bot/email_processor/order/auto_part_position.py @@ -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] + + + + + + + diff --git a/src/mail_order_bot/email_processor/processor.py b/src/mail_order_bot/email_processor/processor.py index 8340f46..3ab2b00 100644 --- a/src/mail_order_bot/email_processor/processor.py +++ b/src/mail_order_bot/email_processor/processor.py @@ -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}")