И еще один шаг к успеху

This commit is contained in:
2025-11-13 22:46:49 +03:00
parent 285948170d
commit b8d4e0ddd1
12 changed files with 142 additions and 60 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)
)

View File

@@ -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})")

View File

@@ -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 = "Превышен порог отказов"

View File

@@ -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

View File

@@ -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}")

View File

@@ -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