diff --git a/db/handlers/actions.py b/db/handlers/actions.py new file mode 100644 index 0000000..3bb42a8 --- /dev/null +++ b/db/handlers/actions.py @@ -0,0 +1,276 @@ +from db.handlers import StockHandler, StocksRecordsHandler +from utils import logger + + +class StocksActions: + + async def registration( + toolkit_id: int, + toolbox_id: int, + user_id: int, + quantity: int, + price: int, + placement: str, + reason: str, + ): + 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 False + + recorded = await StocksRecordsHandler.add( + action="Оприходование", + source_stock_id=None, + target_stock_id=newStocks.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, + ) + logger.info( + f"Оприходование инструмента {toolkit_id} на складе {toolbox_id} прошло {'успешно' if recorded else 'не успешно'}" + ) + return recorded + + async def moving( + action: str, + source_toolbox_id: int, + target_toolbox_id: int, + toolkit_id: int, + quantity: int, + user_id: int, + reason: str, + target_placement: str = None, + ): + 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 + writeDownLIst = [] + + 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']} успешно завершена" + ) + + if not target_toolbox_id: + writeDownLIst.append( + { + "id": sourceEdit["id"], + "quantity": takeQuantity, + "price": 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"], + placement=target_placement, + ) + 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']} успешно завершено" + ) + + recorded = await StocksRecordsHandler.add( + action=action, + source_stock_id=stock["id"], + target_stock_id=targetStock.get("id"), + 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=stock["price"], + target_placement=target_placement, + ) + if not recorded: + logger.error( + f"Ошибка создания записи о {action} инструмента {toolkit_id} со склада {source_toolbox_id} на склад {target_toolbox_id}" + ) + return False + + logger.info( + f"{action} инструмента {toolkit_id} со склада {source_toolbox_id} на склад {target_toolbox_id} прошло успешно" + ) + return True if target_toolbox_id else writeDownLIst + + async def writeDownRequest( + toolkit_id: int, + toolbox_id: int, + quantity: int, + reason: str, + user_id: int, + price: int = 0, + ): + logger.info( + f"Запрос на списание инструмента {toolkit_id} со склада {toolbox_id} в количестве {quantity} по цене {price} ..." + ) + recorded = await StocksRecordsHandler.add( + action="Списание", + source_stock_id=None, + target_stock_id=None, + source_toolbox_id=toolbox_id, + target_toolbox_id=None, + toolkit_id=toolkit_id, + init_user_id=user_id, + reason=reason, + quantity=quantity, + price=price, + ) + logger.info( + f"Запрос на списание инструмента {toolkit_id} со склада {toolbox_id} в количестве {quantity} по цене {price} {'успешно завершен' if recorded else 'завершен с ошибкой'}" + ) + return recorded + + async def writeDownAcceptance(self, record_id: int, user_id: int): + logger.info(f"Принятие записи о списании инструмента {record_id} ...") + writeDownARecord = await StocksRecordsHandler.getById(record_id, True) + if not writeDownARecord: + return False + if writeDownARecord.action != "Списание": + logger.error( + f"Запись {record_id} не является записью о списании инструмента" + ) + return False + if writeDownARecord.accepted_at is not None: + logger.error(f"Запись {record_id} уже была принята") + return False + stocksMovements = await self.moving( + action=writeDownARecord.action, + source_toolbox_id=writeDownARecord.source_toolbox_id, + target_toolbox_id=writeDownARecord.target_toolbox_id, + toolkit_id=writeDownARecord.toolkit_id, + quantity=writeDownARecord.quantity, + user_id=user_id, + reason=writeDownARecord.reason, + ) + if not stocksMovements: + logger.error(f"Ошибка при списании инструмента") + return False + + accept = await StocksRecordsHandler.accept( + record_id, + user_id, + stocksMovements[0].get("id"), + stocksMovements[0].get("quantity"), + stocksMovements[0].get("price"), + ) + if not accept: + return accept + + totalRecordsIds = [record_id] + + if len(stocksMovements) > 1: + for stock in stocksMovements[1:]: + recorded = await StocksRecordsHandler.add( + action=writeDownARecord.action, + source_stock_id=stock.get("id"), + target_stock_id=None, + source_toolbox_id=writeDownARecord.source_toolbox_id, + target_toolbox_id=writeDownARecord.target_toolbox_id, + toolkit_id=writeDownARecord.toolkit_id, + init_user_id=writeDownARecord.init_user_id, + reason=writeDownARecord.reason, + quantity=stock.get("quantity"), + price=stock.get("price"), + return_record_id=True, + ) + if not recorded: + return False + accept = await StocksRecordsHandler.accept( + record_id=recorded, + accept_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) + + logger.info( + f"Записи {', '.join(map(str, totalRecordsIds))} о списании инструмента успешно приняты {user_id}" + ) + return True + + # TODO Дописать возврат на склад + + async def initialize(): + pass diff --git a/db/handlers/records.py b/db/handlers/records.py index 2fad8d5..1d5948f 100644 --- a/db/handlers/records.py +++ b/db/handlers/records.py @@ -1,47 +1,64 @@ from datetime import datetime, timedelta from sqlalchemy import select + from db.schemas import StocksRecords, ServicesRecords from utils import logger class StocksRecordsHandler: + async def add( action: str, - source_toolbox_id: int, - target_toolbox_id: int, + source_stock_id: int, + target_stock_id: int, toolkit_id: int, init_user_id: int, reason: str, quantity: int, + price: float, + source_toolbox_id: int = None, + target_toolbox_id: int = None, + return_record_id: bool = False, ): recordData = { "action": action, + "source_stock_id": source_stock_id, + "target_stock_id": target_stock_id, "source_toolbox_id": source_toolbox_id, + "target_toolbox_id": target_toolbox_id, "toolkit_id": toolkit_id, "init_user_id": init_user_id, "reason": reason, "quantity": quantity, + "price": price, } - if target_toolbox_id: - recordData["target_toolbox_id"] = target_toolbox_id try: logger.info(f"Создание записи: {action} от {init_user_id}") logger.debug(recordData) record = StocksRecords(**recordData) await record.save() logger.info(f"Запись успешно создана, id: {record.id}") - return True + return True if not return_record_id else record.id except Exception as e: logger.error(f"Ошибка создания записи: {str(e)}") return False - async def accept(record_id: int, accept_user_id: int): + async def accept( + record_id: int, + accept_user_id: int, + source_stock_id: int, + quantity: int, + price: float, + ): try: logger.info(f"Принятие записи {record_id} от {accept_user_id}") record = await StocksRecords.get(id=record_id) record.accept_user_id = accept_user_id record.accepted_at = datetime.now() + record.source_stock_id = source_stock_id + record.quantity = quantity + record.price = price await record.save() logger.info( f"Запись {record_id} успешно принята {accept_user_id} в {record.accepted_at.strftime('%Y-%m-%d %H:%M:%S')}" @@ -52,11 +69,58 @@ class StocksRecordsHandler: return False async def edit(record_id: int, edit_user_id: int, **kwargs): + def updateStockRecord(recordDB, editData): + whatchList = ["toolkit_id", "quantity", "price", "placement"] + for key in whatchList: + if key in editData: + setattr(recordDB, key, editData[key]) + return recordDB + try: + from db.handlers.stock import StockHandler + logger.info(f"Обновление записи {record_id} от {edit_user_id}") + record = await StocksRecords.get(id=record_id) record.edit_user_id = edit_user_id record.edited_at = datetime.now() + + logger.info("Вносим изменения в записи о движении инструмента") + + if record.source_stock_id: + logger.info( + "Вносим изменения в записи о движении инструмента из исходного склада" + ) + sourceStockRecord = await StockHandler.get( + record.source_stock_id, record=True + ) + if "source_toolbox_id" in kwargs: + sourceStockRecord.toolbox_id = kwargs["source_toolbox_id"] + + sourceStockRecord = updateStockRecord(sourceStockRecord, kwargs) + await sourceStockRecord.save() + logger.info( + "Внесли изменения в записи о движении инструмента из исходного склада" + ) + + if record.target_stock_id: + logger.info( + "Вносим изменения в записи о движении инструмента в целевой склад" + ) + targetStockRecord = await StockHandler.get( + record.target_stock_id, record=True + ) + if "target_toolbox_id" in kwargs: + targetStockRecord.toolbox_id = kwargs["target_toolbox_id"] + + targetStockRecord = updateStockRecord(targetStockRecord, kwargs) + await targetStockRecord.save() + logger.info( + "Внесли изменения в записи о движении инструмента в целевой склад" + ) + + logger.info("Внесли изменения в записи о движении инструмента") + edited = {} for key, value in kwargs.items(): originalValue = getattr(record, key) @@ -69,6 +133,7 @@ class StocksRecordsHandler: ) logger.debug(edited) return True + except Exception as e: logger.error(f"Ошибка обновления записи: {str(e)}") return False @@ -103,6 +168,22 @@ class StocksRecordsHandler: logger.error(f"Ошибка получения записей: {str(e)}") return False + async def getById(record_id: int, record: bool = False): + from db import CRUD + + try: + logger.info(f"Получение записи {record_id}") + query = select(StocksRecords).where(StocksRecords.id == record_id) + stocksRecord = await CRUD.read(query) + if not stocksRecord: + logger.info(f"Запись {record_id} не найдена") + return False + logger.info(f"Запись {record_id} успешно получена") + return stocksRecord.toDict() if not record else stocksRecord + except Exception as e: + logger.error(f"Ошибка получения записи: {str(e)}") + return False + class ServiceRecordsHandler: async def add(user_id: int, details: dict): diff --git a/db/handlers/stock.py b/db/handlers/stock.py index fa014ca..1dcb741 100644 --- a/db/handlers/stock.py +++ b/db/handlers/stock.py @@ -27,7 +27,7 @@ def filterQuantity(stocksData, filtered): class StockHandler: - async def add(**kwargs): + async def add(**kwargs) -> dict: newStock = await Stock(**kwargs).save() logger.info( f"Новая запись об инструменте {newStock.toolkit_data.title} на складе {newStock.toolbox_data.title} успешно создана" @@ -40,14 +40,14 @@ class StockHandler: stocks = await CRUD.read(select(Stock), all=True) return filterQuantity(stocks, filtered) - async def get(stockId: int, filtered: bool = True): + async def get(stockId: int, filtered: bool = True, record: bool = False): from db import CRUD stock = await CRUD.read(select(Stock).where(Stock.id == stockId)) if not stock: logger.error("Запись об остатках не найдена") return {} - return filterQuantity(stock, filtered) + return filterQuantity(stock, filtered) if not record else stock async def getByToolboxId(toolboxId: int, filtered: bool = True): from db import CRUD @@ -63,7 +63,34 @@ class StockHandler: stocks = await CRUD.read(query, True) return filterQuantity(stocks, filtered) - async def edit(stockId: int, **kwargs): + async def getByToolboxIdAndToolkitId( + toolboxId: int, toolkitId: int, filtered: bool = True + ) -> list[dict]: + from db import CRUD + + query = ( + select(Stock) + .where(Stock.toolbox_id == toolboxId) + .where(Stock.toolkit_id == toolkitId) + ) + stocks = await CRUD.read(query, True) + return filterQuantity(stocks, filtered) + + async def getByToolboxIdAndToolkitIdAndQPrice( + toolboxId: int, toolkitId: int, price: float, filtered: bool = True + ) -> list[dict]: + from db import CRUD + + query = ( + select(Stock) + .where(Stock.toolbox_id == toolboxId) + .where(Stock.toolkit_id == toolkitId) + .where(Stock.price == price) + ) + stocks = await CRUD.read(query, True) + return filterQuantity(stocks, filtered) + + async def edit(stockId: int, **kwargs) -> dict: from db import CRUD stock = await CRUD.read(select(Stock).where(Stock.id == stockId)) diff --git a/db/schemas/records.py b/db/schemas/records.py index ef6281b..b84b19b 100644 --- a/db/schemas/records.py +++ b/db/schemas/records.py @@ -1,5 +1,5 @@ from datetime import datetime -from sqlalchemy import Column, DateTime, ForeignKey, Integer, String, Text +from sqlalchemy import Column, DateTime, Float, ForeignKey, Integer, String, Text from sqlalchemy.dialects.postgresql import JSONB from db import Base import utils @@ -9,9 +9,15 @@ class StocksRecords(Base): __tablename__ = "stocks_records" id = Column(Integer, primary_key=True, index=True) + source_stock_id = Column( + Integer, ForeignKey("stocks.id", ondelete="CASCADE"), nullable=True + ) + target_stock_id = Column( + Integer, ForeignKey("stocks.id", ondelete="CASCADE"), nullable=True + ) action = Column(String, nullable=False) source_toolbox_id = Column( - Integer, ForeignKey("toolboxes.id", ondelete="CASCADE"), nullable=False + Integer, ForeignKey("toolboxes.id", ondelete="CASCADE"), nullable=True ) target_toolbox_id = Column( Integer, ForeignKey("toolboxes.id", ondelete="CASCADE"), nullable=True @@ -27,6 +33,7 @@ class StocksRecords(Base): ) reason = Column(Text, nullable=False) quantity = Column(Integer, nullable=False) + price = Column(Float, nullable=False) edit_user_id = Column( Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=True ) diff --git a/utils/__init__.py b/utils/__init__.py index d1a5ce9..8205756 100644 --- a/utils/__init__.py +++ b/utils/__init__.py @@ -2,4 +2,4 @@ from .loggers import * from .for_DB import * from .password import * from .image import * -from .safe_filemane import * \ No newline at end of file +from .safe_filename import * diff --git a/utils/safe_filemane.py b/utils/safe_filename.py similarity index 100% rename from utils/safe_filemane.py rename to utils/safe_filename.py