589 lines
25 KiB
Python
589 lines
25 KiB
Python
import random
|
||
from db.handlers.stock import StockHandler
|
||
from db.handlers.toolbox import ToolboxHandler
|
||
from db.handlers.toolkit import ToolkitHandler
|
||
from db.handlers.user import UserHandler
|
||
from db.handlers.access import AccessLevelHandler
|
||
from db.handlers.records import StocksRecordsHandler
|
||
|
||
from db.schemas.records import StocksRecords
|
||
from utils import logger
|
||
|
||
|
||
class StocksActions:
|
||
|
||
async def registration(
|
||
toolkit_id: int,
|
||
toolbox_id: int,
|
||
user_id: int,
|
||
quantity: int,
|
||
price: float,
|
||
placement: str,
|
||
reason: str,
|
||
) -> dict:
|
||
logger.info(f"Приход инструмента {toolkit_id} на складе {toolbox_id} ...")
|
||
newStocks = await StockHandler.add(
|
||
toolkit_id=toolkit_id,
|
||
toolbox_id=toolbox_id,
|
||
quantity=quantity,
|
||
price=price,
|
||
placement=placement,
|
||
)
|
||
if newStocks is None:
|
||
logger.error(
|
||
f"Приход инструмента {toolkit_id} на складе {toolbox_id} не удалось"
|
||
)
|
||
return {"errorMessage": "Приход инструмента не удалось"}
|
||
|
||
recorded = await StocksRecordsHandler.add(
|
||
action="Приход",
|
||
source_stock_id=None,
|
||
target_stock_id=newStocks.get("id"),
|
||
source_toolbox_id=None,
|
||
target_toolbox_id=toolbox_id,
|
||
toolkit_id=toolkit_id,
|
||
init_user_id=user_id,
|
||
reason=reason,
|
||
quantity=quantity,
|
||
price=price,
|
||
return_record=True,
|
||
)
|
||
logger.info(
|
||
f"Приход инструмента {toolkit_id} на складе {toolbox_id} прошло {'успешно' if recorded else 'не успешно'}"
|
||
)
|
||
if recorded:
|
||
accepted = await StocksRecordsHandler.decide(
|
||
recorded, user_id, None, quantity, price
|
||
)
|
||
if not accepted:
|
||
logger.error(
|
||
f"Принятие записи о оприходовании инструмента {toolkit_id} на складе {toolbox_id} не удалось"
|
||
)
|
||
refillUpdated = await ToolkitHandler.updateRefillDate(toolkit_id)
|
||
if not refillUpdated:
|
||
logger.error(
|
||
f"Обновление даты последнего заполнения инструмента {toolkit_id} не удалось"
|
||
)
|
||
return (
|
||
{"errorMessage": "Приход инструмента не удалось"}
|
||
if not accepted
|
||
else {}
|
||
)
|
||
return {"errorMessage": "Приход инструмента не удалось"}
|
||
|
||
async def moving(
|
||
action: str,
|
||
source_toolbox_id: int,
|
||
target_toolbox_id: int,
|
||
toolkit_id: int,
|
||
quantity: int,
|
||
):
|
||
logger.info(
|
||
f"{action} инструмента {toolkit_id} со склада {source_toolbox_id} на склад {target_toolbox_id} ..."
|
||
)
|
||
availability = await StockHandler.getByToolboxIdAndToolkitId(
|
||
source_toolbox_id, toolkit_id
|
||
)
|
||
if len(availability) == 0:
|
||
logger.error(f"На складе {source_toolbox_id} нет инструмента {toolkit_id}")
|
||
return False
|
||
|
||
totalQuantity = sum([stock["quantity"] for stock in availability])
|
||
if quantity > totalQuantity:
|
||
logger.error(
|
||
f"На складе {source_toolbox_id} нет в количестве {quantity} инструмента {toolkit_id}"
|
||
)
|
||
return False
|
||
|
||
totalTakeQuantity = 0
|
||
movementsList = []
|
||
|
||
for stock in availability:
|
||
if quantity == totalTakeQuantity:
|
||
break
|
||
takeQuantity = min(stock["quantity"], quantity - totalTakeQuantity)
|
||
totalTakeQuantity += takeQuantity
|
||
|
||
logger.info(
|
||
f"Списание инструмента {toolkit_id} со склада {source_toolbox_id} на склад {target_toolbox_id} в количестве {takeQuantity} по цене {stock['price']} ..."
|
||
)
|
||
sourceEdit = await StockHandler.edit(
|
||
stock["id"], quantity=stock["quantity"] - takeQuantity
|
||
)
|
||
if not sourceEdit:
|
||
logger.error(
|
||
f"Ошибка обновления записи об остатках инструмента {toolkit_id} на складе {source_toolbox_id}"
|
||
)
|
||
return False
|
||
|
||
logger.info(
|
||
f"Списание инструмента {toolkit_id} со склада {source_toolbox_id} на склад {target_toolbox_id} в количестве {takeQuantity} по цене {stock['price']} успешно завершена"
|
||
)
|
||
|
||
movementsList.append(
|
||
{
|
||
"id": sourceEdit["id"],
|
||
"quantity": takeQuantity,
|
||
"price": stock["price"],
|
||
}
|
||
)
|
||
|
||
if not target_toolbox_id:
|
||
logger.warning(
|
||
f"Производится списание инструмента {toolkit_id} со склада {source_toolbox_id} в количестве {takeQuantity} по цене {stock['price']}"
|
||
)
|
||
continue
|
||
|
||
existing = await StockHandler.getByToolboxIdAndToolkitIdAndQPrice(
|
||
target_toolbox_id, toolkit_id, stock["price"]
|
||
)
|
||
if len(existing) == 0:
|
||
logger.info(
|
||
f"Добавление инструмента {toolkit_id} на склад {target_toolbox_id} по цене {stock['price']} ..."
|
||
)
|
||
targetStock = await StockHandler.add(
|
||
toolkit_id=toolkit_id,
|
||
toolbox_id=target_toolbox_id,
|
||
quantity=takeQuantity,
|
||
price=stock["price"],
|
||
)
|
||
if not targetStock:
|
||
logger.error(
|
||
f"Ошибка добавления инструмента {toolkit_id} на склад {target_toolbox_id}"
|
||
)
|
||
return False
|
||
logger.info(
|
||
f"Добавление инструмента {toolkit_id} на склад {target_toolbox_id} по цене {stock['price']} успешно завершено"
|
||
)
|
||
else:
|
||
logger.info(
|
||
f"Изменение остатков инструмента {toolkit_id} на склад {target_toolbox_id} по цене {stock['price']} ..."
|
||
)
|
||
targetStock = await StockHandler.edit(
|
||
existing[0]["id"],
|
||
quantity=existing[0]["quantity"] + takeQuantity,
|
||
)
|
||
if not targetStock:
|
||
logger.error(
|
||
f"Ошибка обновления записи об остатках инструмента {toolkit_id} на складе {target_toolbox_id}"
|
||
)
|
||
return False
|
||
logger.info(
|
||
f"Изменение остатков инструмента {toolkit_id} на склад {target_toolbox_id} по цене {stock['price']} успешно завершено"
|
||
)
|
||
|
||
logger.info(
|
||
f"{action} инструмента {toolkit_id} со склада {source_toolbox_id} на склад {target_toolbox_id} прошло успешно"
|
||
)
|
||
moveUpdated = await ToolkitHandler.updateMovindDate(toolkit_id)
|
||
if not moveUpdated:
|
||
logger.error(f"Ошибка обновления даты движения инструмента {toolkit_id}")
|
||
return False
|
||
return movementsList
|
||
|
||
async def movingRequest(
|
||
action: str,
|
||
toolkit_id: int,
|
||
source_toolbox_id: int,
|
||
target_toolbox_id: int,
|
||
quantity: int,
|
||
reason: str,
|
||
user_id: int,
|
||
price: int = 0,
|
||
return_record: bool = False,
|
||
):
|
||
logger.info(
|
||
f"Запрос на {action} инструмента {toolkit_id} со склада {source_toolbox_id} в количестве {quantity} по цене {price} ..."
|
||
)
|
||
recorded = await StocksRecordsHandler.add(
|
||
action=action,
|
||
source_stock_id=None,
|
||
target_stock_id=None,
|
||
source_toolbox_id=source_toolbox_id,
|
||
target_toolbox_id=target_toolbox_id,
|
||
toolkit_id=toolkit_id,
|
||
init_user_id=user_id,
|
||
reason=reason,
|
||
quantity=quantity,
|
||
price=price,
|
||
return_record=return_record,
|
||
)
|
||
logger.info(
|
||
f"Запрос на {action} инструмента {toolkit_id} со склада {source_toolbox_id} в количестве {quantity} по цене {price} {'успешно создан' if recorded else 'завершен с ошибкой'}"
|
||
)
|
||
return recorded
|
||
|
||
async def movingDecision(
|
||
record_id: int,
|
||
user_id: int,
|
||
accepted: bool = True,
|
||
record: StocksRecords = None,
|
||
):
|
||
logger.info(
|
||
f"{'Принятие' if accepted else 'Отклонение'} записи {record_id if not record else record.id} о движении инструмента {'' if not record else record.toolkit_id} ..."
|
||
)
|
||
movingRecord = (
|
||
await StocksRecordsHandler.getById(record_id, True)
|
||
if not record
|
||
else record
|
||
)
|
||
|
||
if not movingRecord:
|
||
logger.error(f"Запись {record_id} не найдена")
|
||
return False
|
||
if movingRecord.accepted is not None:
|
||
logger.error(
|
||
f"Запись {record_id} уже была {'принята' if movingRecord.accepted else 'отклонена'}"
|
||
)
|
||
return False
|
||
if not accepted:
|
||
return await StocksRecordsHandler.decide(
|
||
movingRecord,
|
||
user_id,
|
||
movingRecord.source_stock_id,
|
||
movingRecord.quantity,
|
||
movingRecord.price,
|
||
accepted,
|
||
)
|
||
|
||
target_toolbox_id = (
|
||
movingRecord.target_toolbox_id
|
||
if movingRecord.action != "Списание"
|
||
else None
|
||
)
|
||
logger.warning(f"{target_toolbox_id = }, {movingRecord.action = }")
|
||
|
||
stocksMovements = await StocksActions.moving(
|
||
action=movingRecord.action,
|
||
source_toolbox_id=movingRecord.source_toolbox_id,
|
||
target_toolbox_id=target_toolbox_id,
|
||
toolkit_id=movingRecord.toolkit_id,
|
||
quantity=movingRecord.quantity,
|
||
)
|
||
if not stocksMovements:
|
||
logger.error(f"Ошибка при {movingRecord.action} инструмента")
|
||
return False
|
||
|
||
accept = await StocksRecordsHandler.decide(
|
||
movingRecord,
|
||
user_id,
|
||
stocksMovements[0].get("id"),
|
||
stocksMovements[0].get("quantity"),
|
||
stocksMovements[0].get("price"),
|
||
)
|
||
if not accept:
|
||
return accept
|
||
|
||
totalRecordsIds = [record_id] if not record else [record.id]
|
||
|
||
if len(stocksMovements) > 1:
|
||
for stock in stocksMovements[1:]:
|
||
recorded = await StocksRecordsHandler.add(
|
||
action=movingRecord.action,
|
||
source_stock_id=stock.get("id"),
|
||
target_stock_id=None,
|
||
source_toolbox_id=movingRecord.source_toolbox_id,
|
||
target_toolbox_id=target_toolbox_id,
|
||
toolkit_id=movingRecord.toolkit_id,
|
||
init_user_id=movingRecord.init_user_id,
|
||
reason=movingRecord.reason,
|
||
quantity=stock.get("quantity"),
|
||
price=stock.get("price"),
|
||
return_record=True,
|
||
)
|
||
if not recorded:
|
||
return False
|
||
accept = await StocksRecordsHandler.decide(
|
||
record=recorded,
|
||
decision_user_id=user_id,
|
||
source_stock_id=stock.get("id"),
|
||
quantity=stock.get("quantity"),
|
||
price=stock.get("price"),
|
||
)
|
||
if not accept:
|
||
return False
|
||
totalRecordsIds.append(recorded.id)
|
||
|
||
logger.info(
|
||
f"Записи {', '.join(map(str, totalRecordsIds))} о {movingRecord.action} инструмента успешно приняты {user_id}"
|
||
)
|
||
return True
|
||
|
||
async def takeToolkit(
|
||
source_toolbox_id: int,
|
||
target_toolbox_id: int,
|
||
toolkit_id: int,
|
||
quantity: int,
|
||
reason: str,
|
||
user_id: int,
|
||
price: int = 0,
|
||
):
|
||
logger.info(
|
||
f"Формирование запроса на получение инструмента {toolkit_id} на склад {target_toolbox_id} со склада {source_toolbox_id} в количестве {quantity} ..."
|
||
)
|
||
takeRequest = await StocksActions.movingRequest(
|
||
action="Получение",
|
||
source_toolbox_id=source_toolbox_id,
|
||
target_toolbox_id=target_toolbox_id,
|
||
toolkit_id=toolkit_id,
|
||
quantity=quantity,
|
||
user_id=user_id,
|
||
reason=reason,
|
||
price=price,
|
||
return_record=True,
|
||
)
|
||
if not takeRequest:
|
||
logger.error(
|
||
f"Формирование запроса на получение инструмента {toolkit_id} не удалось"
|
||
)
|
||
return False
|
||
logger.info(
|
||
f"Формирование запроса на получение инструмента {toolkit_id} успешно завершено"
|
||
)
|
||
logger.info(
|
||
f"Принятие запроса {takeRequest.id} на получение инструмента {toolkit_id} ..."
|
||
)
|
||
accepted = await StocksActions.movingDecision(None, user_id, record=takeRequest)
|
||
if not accepted:
|
||
logger.error(
|
||
f"Принятие запроса {takeRequest.id} на получение инструмента {toolkit_id} не удалось"
|
||
)
|
||
return False
|
||
logger.info(
|
||
f"Принятие запроса {takeRequest.id} на получение инструмента {toolkit_id} успешно завершено"
|
||
)
|
||
return True
|
||
|
||
async def initialize():
|
||
|
||
toolboxes = await ToolboxHandler.getAll()
|
||
toolboxesDict = {
|
||
toolbox.get("title"): toolbox.get("id") for toolbox in toolboxes
|
||
}
|
||
toolboxesOwners = {
|
||
toolbox.get("owner_id"): toolbox.get("id")
|
||
for toolbox in toolboxes
|
||
if toolbox.get("owner_id")
|
||
}
|
||
|
||
users = await UserHandler.getAll()
|
||
usersDict = {user.get("login"): user.get("id") for user in users}
|
||
|
||
toolkits = await ToolkitHandler.getAll()
|
||
|
||
logger.warning("Наполнение складов ...")
|
||
for toolkit in toolkits:
|
||
if "Сверло" in toolkit.get("title"):
|
||
toolboxId = toolboxesDict.get("Шкаф")
|
||
placement = None
|
||
else:
|
||
toolboxId = toolboxesDict.get("Стеллаж")
|
||
placement = chr(65 + random.randint(0, 25)) + str(random.randint(1, 19))
|
||
|
||
registryCount = 5
|
||
|
||
logger.warning(
|
||
f">> Наполнение склада {toolboxId} инструментом {toolkit.get('title')}"
|
||
)
|
||
|
||
for i in range(registryCount):
|
||
quantity = random.randint(20, 30)
|
||
price = round(300 + random.random() * 500, 2)
|
||
|
||
logger.warning(f">>>> Количество: {quantity}, Цена: {price}")
|
||
|
||
success = await StocksActions.registration(
|
||
toolkit_id=toolkit.get("id"),
|
||
toolbox_id=toolboxId,
|
||
user_id=usersDict.get("storekeeper"),
|
||
quantity=quantity,
|
||
price=price,
|
||
placement=placement,
|
||
reason=f"Приход инструмента {toolkit.get('title')}. Счёт-фактура № {random.randint(1000, 10000)}-{i+1}",
|
||
)
|
||
if not success:
|
||
logger.error(f"Приход инструмента {toolkit.get('title')} не удался")
|
||
return False
|
||
|
||
logger.warning("Наполнение складов завершено")
|
||
|
||
logger.warning("Получение инструментов из общих складов ...")
|
||
|
||
accessLevels = await AccessLevelHandler.getAll()
|
||
accessLevelsDict = {
|
||
accessLevel.get("id"): accessLevel.get("available_own_toolbox")
|
||
for accessLevel in accessLevels
|
||
}
|
||
|
||
users = await UserHandler.getAll()
|
||
usersDict = {user.get("login"): user.get("id") for user in users}
|
||
|
||
for user in users:
|
||
userAccessLevelId = user.get("access_level_id")
|
||
if not accessLevelsDict.get(userAccessLevelId):
|
||
continue
|
||
|
||
own_toolbox_id = toolboxesOwners.get(user.get("id"))
|
||
|
||
logger.warning(
|
||
f"Получение инструментов из общего склада пользователем {user.get('login')} ..."
|
||
)
|
||
|
||
for toolkit in toolkits:
|
||
|
||
logger.warning(
|
||
f">> Выдача инструмента {toolkit.get('title')} пользователю {user.get('login')} ..."
|
||
)
|
||
|
||
if "Сверло" in toolkit.get("title"):
|
||
main_toolbox_id = toolboxesDict.get("Шкаф")
|
||
else:
|
||
main_toolbox_id = toolboxesDict.get("Стеллаж")
|
||
|
||
actionsCount = random.randint(3, 5)
|
||
|
||
for i in range(actionsCount):
|
||
success = await StocksActions.takeToolkit(
|
||
source_toolbox_id=main_toolbox_id,
|
||
target_toolbox_id=own_toolbox_id,
|
||
toolkit_id=toolkit.get("id"),
|
||
quantity=random.randint(10, 19),
|
||
reason=f"Получение инструмента {toolkit.get('title')}. Для выполнения заказа № {random.randint(1000, 10000)}",
|
||
user_id=user.get("id"),
|
||
)
|
||
if not success:
|
||
logger.error(
|
||
f"Получение инструмента {toolkit.get('title')} пользователю {user.get('login')} не удалось"
|
||
)
|
||
return False
|
||
|
||
logger.warning(
|
||
f">>>> Получение инструмента {toolkit.get('title')} пользователю {user.get('login')} успешно завершено"
|
||
)
|
||
|
||
logger.warning("Получение инструментов из общих складов завершено")
|
||
|
||
logger.warning("Направление запросов на возврат и списание инструментов ...")
|
||
|
||
requestsDict = {"Списание": [], "Возврат": []}
|
||
|
||
for user in users:
|
||
userAccessLevelId = user.get("access_level_id")
|
||
if not accessLevelsDict.get(userAccessLevelId):
|
||
continue
|
||
|
||
own_toolbox_id = toolboxesOwners.get(user.get("id"))
|
||
|
||
logger.warning(
|
||
f"Направление запросов на возврат и списание инструмента от пользователя {user.get('login')} ..."
|
||
)
|
||
|
||
for action in requestsDict.keys():
|
||
for toolkit in toolkits:
|
||
if action == "Списание":
|
||
main_toolbox_id = None
|
||
else:
|
||
if "Сверло" in toolkit.get("title"):
|
||
main_toolbox_id = toolboxesDict.get("Шкаф")
|
||
else:
|
||
main_toolbox_id = toolboxesDict.get("Стеллаж")
|
||
|
||
actionsCount = random.randint(1, 3)
|
||
|
||
for i in range(actionsCount):
|
||
success = await StocksActions.movingRequest(
|
||
action=action,
|
||
toolkit_id=toolkit.get("id"),
|
||
source_toolbox_id=own_toolbox_id,
|
||
target_toolbox_id=main_toolbox_id,
|
||
quantity=random.randint(3, 5),
|
||
reason=f"{action} инструмента {toolkit.get('title')}. После выполнения заказа",
|
||
user_id=user.get("id"),
|
||
return_record=True,
|
||
)
|
||
if not success:
|
||
logger.error(
|
||
f"{action} инструмента {toolkit.get('title')} пользователю {user.get('login')} не удалось"
|
||
)
|
||
return False
|
||
|
||
requestsDict.get(action).append(success)
|
||
|
||
logger.warning(
|
||
f">>>> {action} инструмента пользователю {user.get('login')} успешно завершено"
|
||
)
|
||
|
||
logger.warning(
|
||
"Направление запросов на возврат и списание инструментов завершено"
|
||
)
|
||
|
||
logger.warning("Обработка запросов на возврат и списание инструментов ...")
|
||
|
||
decisionsDict = {
|
||
"manager": {"accept": [], "reject": [], "ignore": []},
|
||
"storekeeper": {"accept": [], "reject": [], "ignore": []},
|
||
}
|
||
logger.info("Разбивка списоков запросов ...")
|
||
for recordsList in requestsDict.values():
|
||
managerList, storekeeperList = (
|
||
recordsList[: len(recordsList) // 2],
|
||
recordsList[len(recordsList) // 2 :],
|
||
)
|
||
|
||
(
|
||
decisionsDict["manager"]["accept"],
|
||
decisionsDict["manager"]["reject"],
|
||
decisionsDict["manager"]["ignore"],
|
||
) = (
|
||
managerList[: len(managerList) // 3],
|
||
managerList[len(managerList) // 3 : len(managerList) // 3 * 2],
|
||
managerList[len(managerList) // 3 * 2 :],
|
||
)
|
||
|
||
(
|
||
decisionsDict["storekeeper"]["accept"],
|
||
decisionsDict["storekeeper"]["reject"],
|
||
decisionsDict["storekeeper"]["ignore"],
|
||
) = (
|
||
storekeeperList[: len(storekeeperList) // 3],
|
||
storekeeperList[
|
||
len(storekeeperList) // 3 : len(storekeeperList) // 3 * 2
|
||
],
|
||
storekeeperList[len(storekeeperList) // 3 * 2 :],
|
||
)
|
||
|
||
logger.warning("Применение решений ...")
|
||
for role in decisionsDict.keys():
|
||
logger.warning(f"Принятие записей пользователем {role} ...")
|
||
user_id = usersDict.get(role)
|
||
for decision in decisionsDict.get(role).keys():
|
||
logger.warning(f"Принятие решения {decision} пользователем {role} ...")
|
||
match decision:
|
||
case "accept":
|
||
accepted = True
|
||
case "reject":
|
||
accepted = False
|
||
case "ignore":
|
||
continue
|
||
for record in decisionsDict.get(role).get(decision):
|
||
logger.info(
|
||
f"{'Принятие' if accepted else 'Отклонение'} записи {record.id} пользователем {role} ... "
|
||
)
|
||
success = await StocksActions.movingDecision(
|
||
record_id=None,
|
||
user_id=user_id,
|
||
accepted=accepted,
|
||
record=record,
|
||
)
|
||
if not success:
|
||
logger.error(
|
||
f"{'Принятие' if accepted else 'Отклонение'} записи {record.id} пользователем {role} не удалось"
|
||
)
|
||
return False
|
||
|
||
logger.warning(
|
||
"Обработка запросов на возврат и списание инструментов завершено"
|
||
)
|
||
|
||
return True
|