И еще один шаг к успеху
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
from .abcp_clients.check_avaiability import CheckAvailabilityTest
|
||||
from .abcp_clients.order_creator import InstantOrderTest
|
||||
from .abcp_clients.check_stock import GetStock
|
||||
from .abcp_clients.create_order import InstantOrderTest
|
||||
|
||||
from .excel_parcers.basic_excel_parcer import BasicExcelParser
|
||||
|
||||
from .notifications.test_notifier import TestNotifier
|
||||
|
||||
|
||||
from .validators.price_quantity_ckecker import CheckOrder
|
||||
@@ -9,7 +9,7 @@ logger = logging.getLogger(__name__)
|
||||
def get_stock(brand, part_number):
|
||||
return random.randint(0, 10)
|
||||
|
||||
class CheckAvailabilityTest(AbstractTask):
|
||||
class GetStock(AbstractTask):
|
||||
|
||||
def do(self) -> None:
|
||||
positions = self.order.positions
|
||||
@@ -18,8 +18,9 @@ class CheckAvailabilityTest(AbstractTask):
|
||||
|
||||
def _update_stock(self, position):
|
||||
# Эмулируем получение данных
|
||||
stock = random.randint(0, 10)
|
||||
price = position.price
|
||||
max_stock = self.config.get('max_stock',10)
|
||||
stock = random.randint(0, max_stock)
|
||||
price = position.requested_price
|
||||
|
||||
position.stock_price = price
|
||||
position.stock_remaining = stock
|
||||
position.stock_quantity = stock
|
||||
@@ -0,0 +1,37 @@
|
||||
import logging
|
||||
import requests
|
||||
from mail_order_bot.task_handler.handlers.abstract_task import AbstractTask
|
||||
from mail_order_bot.task_handler.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)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
import logging
|
||||
import requests
|
||||
from mail_order_bot.task_handler.handlers.abstract_task import AbstractTask
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class InstantOrderTest(AbstractTask):
|
||||
URL = "https://api.telegram.org/bot{0}/sendMessage?chat_id={1}&text={2}"
|
||||
|
||||
def do(self) -> None:
|
||||
|
||||
positions = self.order.positions
|
||||
|
||||
message = f"Запрос на создание заказа от {self.context['client']}:\n"
|
||||
message += "\n".join(f"{pos.article}: {pos.name} ({pos.quantity} x {pos.price} = {pos.total})" for pos in positions)
|
||||
|
||||
api_key = self.config["api_key"]
|
||||
chat_id = self.config["chat_id"]
|
||||
url = self.URL.format(api_key, chat_id, message)
|
||||
resp = requests.get(url).json()
|
||||
logger.info(resp)
|
||||
@@ -74,11 +74,11 @@ class BasicExcelParser(AbstractTask):
|
||||
|
||||
# Создаем объект позиции
|
||||
position = AutoPartPosition(
|
||||
article=str(row[mapping['article']]).strip(),
|
||||
sku=str(row[mapping['article']]).strip(),
|
||||
manufacturer=str(row[mapping.get('manufacturer', "")]).strip(),
|
||||
name=name,
|
||||
price=price,
|
||||
quantity=quantity,
|
||||
requested_price=price,
|
||||
requested_quantity=quantity,
|
||||
total=total,
|
||||
additional_attrs=self._extract_additional_attrs(row, mapping)
|
||||
)
|
||||
|
||||
@@ -11,5 +11,5 @@ class TestNotifier(AbstractTask):
|
||||
|
||||
print(f"\nПолучено {len(positions)} позиций от {self.context["client"]}:")
|
||||
for pos in positions: # Первые 5
|
||||
print(f" - {pos.article}: {pos.name} "
|
||||
f"({pos.quantity} x {pos.price} = {pos.total})")
|
||||
print(f" - {pos.sku}: {pos.name} "
|
||||
f"({pos.requested_quantity} x {pos.requested_price} = {pos.total})")
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import random
|
||||
import logging
|
||||
from mail_order_bot.task_handler.handlers.abstract_task import AbstractTask
|
||||
from mail_order_bot.task_handler.order.auto_part_order import OrderStatus
|
||||
from decimal import Decimal
|
||||
import random
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CheckOrder(AbstractTask):
|
||||
|
||||
def do(self) -> None:
|
||||
refused = 0
|
||||
positions = self.order.positions
|
||||
for position in positions:
|
||||
self._set_order_price(position)
|
||||
self._set_order_quantity(position)
|
||||
|
||||
if position.order_price == 0 or position.order_quantity == 0:
|
||||
refused += 1
|
||||
|
||||
self._check_refusal_threshold(refused)
|
||||
|
||||
|
||||
def _set_order_price(self, position):
|
||||
# Эмулируем получение данных
|
||||
acceptable_price_reduction = self.config.get("acceptable_price_reduction")
|
||||
acceptable_price = position.stock_price* Decimal(str((1-acceptable_price_reduction/100)))
|
||||
|
||||
if position.requested_price < acceptable_price:
|
||||
position.order_price = 0
|
||||
else:
|
||||
position.order_price = position.requested_price
|
||||
|
||||
def _set_order_quantity(self, position):
|
||||
max_stock = self.config.get("max_stock", 100)
|
||||
min_stock = self.config.get("min_stock", 0)
|
||||
|
||||
stock_quantity = random.randint(min_stock, max_stock)
|
||||
|
||||
position.order_quantity = max(0, min(position.stock_quantity, stock_quantity))
|
||||
|
||||
def _check_refusal_threshold(self, refused):
|
||||
refusal_threshold_limit = self.config.get("refusal_threshold", 1)
|
||||
refusal_level = refused/len(self.order.positions)
|
||||
|
||||
if refusal_level > refusal_threshold_limit:
|
||||
self.order.status = OrderStatus.OPERATOR_REQUIRED
|
||||
self.order.reason = "Превышен порог отказов"
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
from typing import List, Optional
|
||||
from .auto_part_position import AutoPartPosition
|
||||
from .ordet_status import OrderStatus
|
||||
from enum import Enum
|
||||
|
||||
class OrderStatus(Enum):
|
||||
NEW = "new"
|
||||
IN_PROGRESS = "in progress"
|
||||
FAILED = "failed"
|
||||
COMPLETED = "completed"
|
||||
OPERATOR_REQUIRED = "operator required"
|
||||
INVALID = "invalid"
|
||||
|
||||
|
||||
class AutoPartOrder:
|
||||
def __init__(self):
|
||||
self.positions: List[AutoPartPosition] = []
|
||||
self.status = OrderStatus.NEW
|
||||
self.reason = ""
|
||||
|
||||
def add_position(self, position: AutoPartPosition) -> None:
|
||||
self.positions.append(position)
|
||||
@@ -15,7 +25,7 @@ class AutoPartOrder:
|
||||
def find_positions(self, brand: Optional[str] = None, sku: Optional[str] = None) -> List[AutoPartPosition]:
|
||||
results = self.positions
|
||||
if brand is not None:
|
||||
results = [p for p in results if p.brand == brand]
|
||||
results = [p for p in results if p.manufacturer == brand]
|
||||
if sku is not None:
|
||||
results = [p for p in results if p.sku == sku]
|
||||
return results
|
||||
|
||||
@@ -10,22 +10,24 @@ class AutoPartPosition:
|
||||
Унифицированная модель позиции для заказа.
|
||||
Все контрагенты приводятся к этой структуре.
|
||||
"""
|
||||
article: str # Артикул товара
|
||||
sku: str # Артикул товара
|
||||
manufacturer: str # Производитель
|
||||
name: str # Наименование
|
||||
price: Decimal # Цена за единицу
|
||||
quantity: int # Количество
|
||||
requested_price: Decimal # Цена за единицу
|
||||
requested_quantity: int # Количество
|
||||
total: Decimal # Общая сумма
|
||||
stock_remaining: int = 0 # Остаток на складе
|
||||
stock_quantity: int = 0 # Остаток на складе
|
||||
stock_price: Decimal = Decimal('0.0') # Цена на складе
|
||||
order_quantity: int = 0 # Количество для заказа
|
||||
order_price: Decimal = Decimal('0.0') # Цена в заказе
|
||||
additional_attrs: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
def __post_init__(self):
|
||||
"""Валидация после инициализации"""
|
||||
if self.quantity < 0:
|
||||
raise ValueError(f"Количество не может быть отрицательным: {self.quantity}")
|
||||
if self.price < 0:
|
||||
raise ValueError(f"Цена не может быть отрицательной: {self.price}")
|
||||
if self.requested_quantity < 0:
|
||||
raise ValueError(f"Количество не может быть отрицательным: {self.requested_quantity}")
|
||||
if self.requested_price < 0:
|
||||
raise ValueError(f"Цена не может быть отрицательной: {self.requested_price}")
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class OrderStatus(Enum):
|
||||
NEW = 1
|
||||
IN_PROGRESS = 2
|
||||
FAILED = 4
|
||||
COMPLETED = 3
|
||||
OPERATOR_REQUIRED = 5
|
||||
INVALID = 6
|
||||
@@ -1,12 +1,10 @@
|
||||
# Конфигурационный файл для контрагента todx.ru
|
||||
|
||||
# Конфигурационный файл для контрагента mikado-parts.ru
|
||||
pipeline:
|
||||
-
|
||||
# Обработчик вложений
|
||||
- handler: "BasicExcelParser"
|
||||
# Обработчик вложений - извлекает из экселя данные
|
||||
- handler: BasicExcelParser
|
||||
config:
|
||||
sheet_name: 0 # Можно указать индекс листа
|
||||
key_field: "артикул"
|
||||
key_field: "артикул" # Поле, по которому будет определяться заголовок блока с данными и будут отсекаться незаполненные строки
|
||||
mapping:
|
||||
article: "артикул"
|
||||
manufacturer: "бренд"
|
||||
@@ -14,14 +12,26 @@ pipeline:
|
||||
price: "цена"
|
||||
quantity: "количество"
|
||||
|
||||
- handler: "CheckAvailabilityTest"
|
||||
# Обработчик получает данные со склада о цене и остатках по каждой позиций
|
||||
- handler: GetStock
|
||||
config:
|
||||
max_stock: 2
|
||||
min_stock: 0
|
||||
|
||||
# Обработчик проверяет заказ на возможность автоматической обработки
|
||||
- handler: CheckOrder
|
||||
config:
|
||||
acceptable_price_reduction: 2
|
||||
refusal_threshold: 0.1
|
||||
|
||||
# Создание заказа
|
||||
- handler: InstantOrderTest
|
||||
config:
|
||||
api_key: "8056899069:AAFEfw9QRMvmEwQyH0CI4e_v_sZuOSdNWcE"
|
||||
chat_id: 211945135
|
||||
|
||||
- handler: "TestNotifier"
|
||||
# Отправка уведомлений менеджерам
|
||||
#- handler: "TestNotifier"
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user