создана часть бекенда

This commit is contained in:
2025-11-30 19:31:06 +03:00
parent 4ae09c36b3
commit dc85e7c0c9
35 changed files with 1573 additions and 149 deletions
+16 -16
View File
@@ -4,7 +4,7 @@ from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker
from sqlalchemy.exc import InvalidRequestError
from sqlalchemy.pool import NullPool
import config
from utils import loggerDB
from utils import logger
DATABASE_URL = f"postgresql+asyncpg://{config.DB_USER}:{config.DB_PASS}@{config.DB_HOST}:{config.DB_PORT}/{config.DB_NAME}"
@@ -22,43 +22,43 @@ class CRUD:
is_lst = isinstance(db_data, list)
async with SessionLocal() as db:
if is_lst:
loggerDB.info(f"Создаю {len(db_data)} записей")
logger.info(f"Создаю {len(db_data)} записей")
try:
db.add_all(db_data)
except InvalidRequestError:
for data in db_data:
await db.merge(data)
else:
loggerDB.info("Создаю запись")
logger.info("Создаю запись")
db.add(db_data)
await db.commit()
if refresh:
if is_lst:
loggerDB.info(f"Обновляю {len(db_data)} записей")
logger.info(f"Обновляю {len(db_data)} записей")
for data in db_data:
await db.refresh(data)
else:
loggerDB.info("Обновляю запись")
logger.info("Обновляю запись")
await db.refresh(db_data)
loggerDB.info("Запись создана")
logger.info("Запись создана")
return db_data if refresh else None
except Exception as e:
loggerDB.error(f"Ошибка создания: {str(e)}", exc_info=True)
logger.error(f"Ошибка создания: {str(e)}", exc_info=True)
return None
async def read(query, all: bool = False):
try:
async with SessionLocal() as db:
loggerDB.info(f"Чтение записей. Все: {all}")
logger.info(f"Чтение записей. Все: {all}")
results = await db.execute(query)
loggerDB.info(f"Чтение завершено")
logger.info(f"Чтение завершено")
return (
results.unique().scalars().all()
if all
else results.unique().scalars().first()
)
except Exception as e:
loggerDB.error(f"Ошибка чтения: {str(e)}", exc_info=True)
logger.error(f"Ошибка чтения: {str(e)}", exc_info=True)
return None
async def delete(db_data) -> bool:
@@ -87,18 +87,18 @@ class CRUD:
async with SessionLocal() as db:
try:
if isinstance(db_data, list):
loggerDB.info(f"Удаляю записей: {len(db_data)}")
logger.info(f"Удаляю записей: {len(db_data)}")
for data in db_data:
await deleteFromDB(data, db)
else:
loggerDB.info("Удаляю запись")
logger.info("Удаляю запись")
await deleteFromDB(db_data, db)
await db.commit()
loggerDB.info("Запись удалена")
logger.info("Запись удалена")
return True
except Exception as e:
await db.rollback()
loggerDB.error(f"Ошибка удаления: {str(e)}", exc_info=True)
logger.error(f"Ошибка удаления: {str(e)}", exc_info=True)
return False
async def update(db_data, id, **kwargs):
@@ -107,9 +107,9 @@ class CRUD:
query = update(db_data).where(db_data.id == id).values(**kwargs)
item = await db.execute(query)
await db.commit()
loggerDB.info("Запись обновлена")
logger.info("Запись обновлена")
return item
except Exception as e:
await db.rollback()
loggerDB.error(f"Ошибка обновления: {str(e)}", exc_info=True)
logger.error(f"Ошибка обновления: {str(e)}", exc_info=True)
return None
+39
View File
@@ -0,0 +1,39 @@
from .user import *
from .access import *
from .toolbox import *
from .categories import *
from .stock import *
from .toolkit import *
from .records import *
class InitializeDatabase:
def __init__(self):
self.userHandler = UserHandler()
self.accessHandler = AccessLevelHandler()
self.toolboxHandler = ToolboxHandler()
self.categoryHandler = CategoryHandler()
self.stockHandler = StockHandler()
self.toolkitHandler = ToolkitHandler()
self.stocksRecordHandler = StocksRecordsHandler()
self.servicesRecordHandler = ServiceRecordsHandler()
async def initialize(self):
await self.accessHandler.initialize()
await self.userHandler.initialize()
await self.toolboxHandler.initialize()
await self.categoryHandler.initialize()
await self.toolkitHandler.initialize()
await self.stockHandler.initialize()
__all__ = [
"UserHandler",
"AccessLevelHandler",
"ToolboxHandler",
"CategoryHandler",
"StockHandler",
"ToolkitHandler",
"StocksRecords",
"ServicesRecords",
]
+146 -18
View File
@@ -2,25 +2,153 @@ from sqlalchemy import select
from utils import logger
from db import CRUD
from db.schemas import AccessLevel
from db.handlers import ServiceRecordsHandler
async def getAccessdata(accessId: int) -> dict:
query = select(AccessLevel).where(AccessLevel.id == accessId)
accessData = await CRUD.read(query)
if not accessData:
logger.error("Уровень доступа не найден")
return {}
return accessData.toDict()
class AccessLevelHandler:
async def add(**kwargs):
title = kwargs.get("title", None)
if not title:
logger.error("Не указано название уровня доступа")
return {}
exists = await CRUD.read(select(AccessLevel).where(AccessLevel.title == title))
if exists:
logger.error("Уровень доступа с таким названием уже существует")
return {}
try:
logger.info(f"Создание уровня доступа {title}")
user_id = kwargs.pop("user_id", None)
accessData = await AccessLevel(**kwargs).save()
await ServiceRecordsHandler.add(
user_id, {"Добавлен уровень доступа": accessData.toDict()}
)
except Exception as e:
logger.error(f"Ошибка создания уровня доступа: {str(e)}")
return {}
logger.info(f"Уровень доступа {accessData.title} успешно создан")
return accessData.toDict()
async def get(accessId: int) -> dict:
query = select(AccessLevel).where(AccessLevel.id == accessId)
accessData = await CRUD.read(query)
if not accessData:
logger.error("Уровень доступа не найден")
return {}
return accessData.toDict()
async def editAccessData(accessId: int, **kwargs):
query = select(AccessLevel).where(AccessLevel.id == accessId)
accessData = await CRUD.read(query)
if not accessData:
logger.error("Уровень доступа не найден")
return {}
editedAccessData = await accessData.edit(**kwargs)
logger.info(
f"Уровень доступа {editedAccessData.title} успешно обновлен, изменены данные: {kwargs.keys()}"
)
return editedAccessData.toDict()
async def edit(accessId: int, **kwargs):
query = select(AccessLevel).where(AccessLevel.id == accessId)
accessData = await CRUD.read(query)
if not accessData:
logger.error("Уровень доступа не найден")
return {}
try:
user_id = kwargs.pop("user_id", None)
editedAccessData = await accessData.edit(**kwargs)
await ServiceRecordsHandler.add(
user_id, {"Обновлен уровень доступа": editedAccessData.toDict()}
)
except Exception as e:
logger.error(f"Ошибка обновления уровня доступа: {str(e)}")
return {}
logger.info(
f"Уровень доступа {editedAccessData.title} успешно обновлен, изменены данные: {kwargs.keys()}"
)
return editedAccessData.toDict()
async def getAll() -> list:
query = select(AccessLevel)
accessLevels = await CRUD.read(query, True)
return (
[accessLevel.toDict() for accessLevel in accessLevels]
if accessLevels
else []
)
async def delete(accessId: int, user_id: int = None):
query = select(AccessLevel).where(AccessLevel.id == accessId)
accessData = await CRUD.read(query)
if not accessData:
logger.error("Уровень доступа не найден")
return False
try:
title = accessData.title
result = await CRUD.delete(accessData)
await ServiceRecordsHandler.add(
user_id, {"Удален уровень доступа": f"Название: {title}"}
)
except Exception as e:
logger.error(f"Ошибка удаления уровня доступа: {str(e)}")
return False
logger.info(
f"Уровень доступа {accessData.title} {'успешно удален' if result else 'не удален'}"
)
return result
async def initialize(self):
baseAcessLevels = {
"admin": {
"title": "Администратор",
"description": "Администратор. Полный доступ",
"receiving_edit": True,
"refund_request_edit": True,
"refund_request_confirm": True,
"debit_request_edit": True,
"debit_request_confirm": True,
"tools_creation": True,
"tools_registration": True,
"tools_registration_edit": True,
"tools_edit": True,
"tools_delete": True,
"users_creation": True,
"users_edit": True,
"users_disabling": True,
"users_view": True,
"available_own_toolbox": False,
"view_all_toolboxes": True,
"view_requests": True,
"view_services": True,
"access_level_view": True,
"access_level_edit": True,
"manage_toolboxes": True,
},
"manager": {
"title": "Менеджер",
"description": "Менеджер. Доступ к просмотру и редактированию рабочей информации",
"refund_request_confirm": True,
"debit_request_confirm": True,
"tools_creation": True,
"tools_registration": True,
"tools_edit": True,
"users_disabling": True,
"users_view": True,
"view_all_toolboxes": True,
"view_requests": True,
"view_services": True,
"access_level_view": True,
"manage_toolboxes": True,
},
"storekeeper": {
"title": "Кладовщик",
"description": "Кладовщик. Доступ к управлению складом",
"refund_request_confirm": True,
"debit_request_confirm": True,
"tools_creation": True,
"tools_registration": True,
"tools_edit": True,
"users_view": True,
"view_requests": True,
"view_all_toolboxes": True,
},
"employee": {
"title": "Сотрудник",
"description": "Сотрудник. Управление собственной рабочей информацией",
"available_own_toolbox": True,
},
}
for accessLevel in baseAcessLevels.values():
await self.add(**accessLevel)
logger.info("Уровни доступа успешно инициализированы")
return
+92
View File
@@ -0,0 +1,92 @@
from sqlalchemy import select
from utils import logger
from db import CRUD
from db.schemas import Category
from db.handlers import ServiceRecordsHandler
class CategoryHandler:
async def add(newCategoryData: dict, user_id: int = None):
title = newCategoryData.get("title", None)
if not title:
logger.error("Не указано название категории")
return {}
query = select(Category).where(Category.title == title)
category = await CRUD.read(query)
if category:
logger.error("Категория с таким названием уже существует")
return {}
try:
newCategory = await Category(**newCategoryData).save()
except Exception as e:
logger.error(f"Ошибка сохранения категории: {str(e)}")
return {}
if not newCategory:
logger.error("Категория не сохранена")
return {}
await ServiceRecordsHandler.add(
user_id, {"Добавлена категория": newCategory.toDict()}
)
logger.info(
f"Категория {newCategory.title} успешно добавлена, id: {newCategory.id}"
)
return newCategory.toDict()
async def edit(categoryId: int, **kwargs):
query = select(Category).where(Category.id == categoryId)
category = await CRUD.read(query)
if not category:
logger.error("Категория не найдена")
return {}
try:
user_id = kwargs.get("user_id", None)
editedCategory = await category.edit(**kwargs)
except Exception as e:
logger.error(f"Ошибка обновления категории: {str(e)}")
return {}
if not editedCategory:
logger.error("Категория не обновлена")
return {}
await ServiceRecordsHandler.add(
user_id, {f"Обновлена категория {category.title}": editedCategory.toDict()}
)
logger.info(f"Категория {editedCategory.title} успешно обновлена")
return editedCategory.toDict()
async def getAll() -> list[dict]:
query = select(Category)
categories = await CRUD.read(query, True)
return [category.toDict() for category in categories] if categories else []
async def delete(categoryId: int, user_id: int = None):
query = select(Category).where(Category.id == categoryId)
category = await CRUD.read(query)
if not category:
logger.error("Категория не найдена")
return False
try:
categoryTitle = category.title
result = await CRUD.delete(category)
except Exception as e:
logger.error(f"Ошибка удаления категории: {str(e)}")
return False
await ServiceRecordsHandler.add(
user_id, {"Удалена категория": f"Название: {categoryTitle}"}
)
logger.info(
f"Категория {categoryTitle} {'успешно удалена' if result else 'не удалена'}"
)
return result
async def initialize():
baseCategories = [
{"title": "Фрезеровка", "description": "Инструмент для фрезерного цеха"},
{"title": "Токарка", "description": "Инструмент для токарного цеха"},
{"title": "Слесарка", "description": "Инструмент для слесарного цеха"},
]
for categoryData in baseCategories:
await CategoryHandler.add(categoryData)
logger.info("Категории успешно созданы")
return
+118
View File
@@ -0,0 +1,118 @@
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,
toolkit_id: int,
init_user_id: int,
reason: str,
quantity: int,
):
recordData = {
"action": action,
"source_toolbox_id": source_toolbox_id,
"toolkit_id": toolkit_id,
"init_user_id": init_user_id,
"reason": reason,
"quantity": quantity,
}
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
except Exception as e:
logger.error(f"Ошибка создания записи: {str(e)}")
return False
async def accept(record_id: int, accept_user_id: int):
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()
await record.save()
logger.info(
f"Запись {record_id} успешно принята {accept_user_id} в {record.accepted_at.strftime('%Y-%m-%d %H:%M:%S')}"
)
return True
except Exception as e:
logger.error(f"Ошибка принятия записи: {str(e)}")
return False
async def edit(record_id: int, edit_user_id: int, **kwargs):
try:
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()
edited = {}
for key, value in kwargs.items():
originalValue = getattr(record, key)
setattr(record, key, value)
edited[key] = {"original": originalValue, "new": value}
record.edited = edited
await record.save()
logger.info(
f"Запись {record_id} успешно обновлена {edit_user_id} в {record.updated_at.strftime('%Y-%m-%d %H:%M:%S')}"
)
logger.debug(edited)
return True
except Exception as e:
logger.error(f"Ошибка обновления записи: {str(e)}")
return False
async def get(user_id: int = None, days: int = 30):
from db import CRUD
try:
if user_id:
userInfo = f"пользователя {user_id} "
query = select(StocksRecords).where(
StocksRecords.init_user_id == user_id,
StocksRecords.created_at > datetime.now() - timedelta(days=days),
)
else:
userInfo = "всех пользователей "
query = select(StocksRecords).where(
StocksRecords.created_at > datetime.now() - timedelta(days=days),
)
logger.info(f"Получение всех записей {userInfo}за последние {days} дн.")
records = await CRUD.read(query, True)
logger.info(
f"{len(records)} записей {userInfo}за последние {days} дн. успешно получены"
)
if len(records) == 0:
return []
records.sort(key=lambda x: x.created_at, reverse=True)
recordsData = [record.toDict() for record in records]
logger.debug(recordsData)
return recordsData
except Exception as e:
logger.error(f"Ошибка получения записей: {str(e)}")
return False
class ServiceRecordsHandler:
async def add(user_id: int, details: dict):
try:
logger.info(f"Создание записи: {user_id}")
logger.debug(details)
record = ServicesRecords(user_id=user_id, details=details)
await record.save()
logger.info(f"Запись успешно создана, id: {record.id}")
return True
except Exception as e:
logger.error(f"Ошибка создания записи: {str(e)}")
return False
+88
View File
@@ -0,0 +1,88 @@
from sqlalchemy import select
from db.schemas import Stock
from utils import logger
def filterQuantity(stocksData, filtered):
def filterStock(stock):
if stock.quantity > 0:
return stock
else:
return False
if isinstance(stocksData, list):
if len(stocksData) == 0:
return []
stocksData.sort(key=lambda stock: stock.created_at)
filteredStocks = (
list(filter(filterStock, stocksData)) if filtered else stocksData
)
return [stock.toDict() for stock in filteredStocks] if filteredStocks else []
else:
stock = filterStock(stocksData) if filtered else stocksData
if stock:
return stock.toDict()
else:
return {}
class StockHandler:
async def add(**kwargs):
newStock = await Stock(**kwargs).save()
logger.info(
f"Новая запись об инструменте {newStock.toolkit_data.title} на складе {newStock.toolbox_data.title} успешно создана"
)
return newStock.toDict()
async def getAll(filtered: bool = True):
from db import CRUD
stocks = await CRUD.read(select(Stock), all=True)
return filterQuantity(stocks, filtered)
async def get(stockId: int, filtered: bool = True):
from db import CRUD
stock = await CRUD.read(select(Stock).where(Stock.id == stockId))
if not stock:
logger.error("Запись об остатках не найдена")
return {}
return filterQuantity(stock, filtered)
async def getByToolboxId(toolboxId: int, filtered: bool = True):
from db import CRUD
query = select(Stock).where(Stock.toolbox_id == toolboxId)
stocks = await CRUD.read(query, True)
return filterQuantity(stocks, filtered)
async def getByToolkitId(toolkitId: int, filtered: bool = True):
from db import CRUD
query = select(Stock).where(Stock.toolkit_id == toolkitId)
stocks = await CRUD.read(query, True)
return filterQuantity(stocks, filtered)
async def edit(stockId: int, **kwargs):
from db import CRUD
stock = await CRUD.read(select(Stock).where(Stock.id == stockId))
if not stock:
logger.error("Запись об остатках не найдена")
return {}
try:
stockInfo = f"инструмента {stock.toolkit_data.title} на складе {stock.toolbox_data.title}"
editedStock = await stock.edit(**kwargs)
except Exception as e:
logger.error(f"Ошибка обновления записи об остатках: {str(e)}")
return {}
if not editedStock:
logger.error("Запись об остатках не обновлена")
return {}
logger.info(
f"Запись об остатках {stockInfo} успешно обновлена, изменены данные: {kwargs.keys()}"
)
return editedStock.toDict()
async def initialize():
pass
+107 -13
View File
@@ -2,18 +2,112 @@ from utils import logger
from db import CRUD
from db.schemas import Toolbox
from sqlalchemy import select
from db.handlers import ServiceRecordsHandler
async def addNewToolbox(toolboxData: dict):
title = toolboxData.get("title", None)
if not title:
logger.error("Не указано Назавание тулбокса")
return {}
query = select(Toolbox).where(Toolbox.title == title)
toolbox = await CRUD.read(query)
if toolbox:
logger.error("Тулбокс с таким названием уже существует")
return {}
newToolbox = await Toolbox(**toolboxData).save()
logger.info(f"Тулбокс {newToolbox.title} успешно создан")
return newToolbox.toDict()
class ToolboxHandler:
async def add(toolboxData: dict, user_id: int = None):
title = toolboxData.get("title", None)
if not title:
logger.error("Не указано Назавание тулбокса")
return {}
query = select(Toolbox).where(Toolbox.title == title)
toolbox = await CRUD.read(query)
if toolbox:
logger.error("Тулбокс с таким названием уже существует")
return {}
try:
logger.info(f"Создание тулбокса {title}")
newToolbox = await Toolbox(**toolboxData).save()
except Exception as e:
logger.error(f"Ошибка сохранения тулбокса: {str(e)}")
return {}
if not newToolbox:
logger.error("Тулбокс не сохранен")
return {}
await ServiceRecordsHandler.add(
user_id, {"Добавлен тулбокс": newToolbox.toDict()}
)
logger.info(f"Тулбокс {newToolbox.title} успешно создан")
return newToolbox.toDict()
async def edit(toolboxId: int, **kwargs):
query = select(Toolbox).where(Toolbox.id == toolboxId)
toolbox = await CRUD.read(query)
if not toolbox:
logger.error("Тулбокс не найден")
return {}
try:
user_id = kwargs.pop("user_id", None)
editedToolbox = await toolbox.edit(**kwargs)
except Exception as e:
logger.error(f"Ошибка обновления тулбокса: {str(e)}")
return {}
if not editedToolbox:
logger.error("Тулбокс не обновлен")
return {}
logger.info(
f"Тулбокс {editedToolbox.title} успешно обновлен, изменены данные: {kwargs.keys()}"
)
await ServiceRecordsHandler.add(
user_id, {f"Обновлен тулбокс {toolbox.title}": editedToolbox.toDict()}
)
return editedToolbox.toDict()
async def getAll() -> list:
query = select(Toolbox)
toolboxes = await CRUD.read(query, True)
return [toolbox.toDict() for toolbox in toolboxes] if toolboxes else []
async def get(toolboxId: int) -> dict:
query = select(Toolbox).where(Toolbox.id == toolboxId)
toolbox = await CRUD.read(query)
if not toolbox:
logger.error("Тулбокс не найден")
return {}
return toolbox.toDict()
async def delete(toolboxId: int, user_id: int = None):
query = select(Toolbox).where(Toolbox.id == toolboxId)
toolbox = await CRUD.read(query)
if not toolbox:
logger.error("Тулбокс не найден")
return False
try:
toolboxTitle = toolbox.title
result = await CRUD.delete(toolbox)
except Exception as e:
logger.error(f"Ошибка удаления тулбокса: {str(e)}")
return False
logger.info(
f"Тулбокс {toolboxTitle} {'успешно удален' if result else 'не удален'}"
)
await ServiceRecordsHandler.add(
user_id, {"Удален тулбокс": f"Название: {toolboxTitle}"}
)
return result
async def initialize():
baseToolsboxes = [
{
"title": "Стеллаж",
"description": "Основной стеллаж с режущим инструментом",
"owner_id": None,
"monitoring": True,
},
{
"title": "Шкаф",
"description": "Шкаф для хранения инструментов",
"owner_id": None,
"monitoring": True,
},
]
for toolboxData in baseToolsboxes:
await ToolboxHandler.add(toolboxData)
logger.info("Тулбоксы успешно созданы")
return
+288
View File
@@ -0,0 +1,288 @@
from utils import logger, saveImage, safeFilename, deleteImage
from db import CRUD
from db.schemas import Toolkit
from sqlalchemy import select
from db.handlers import ServiceRecordsHandler
def handleToolkitImage(imageData, title: str):
title = safeFilename(title)
fileName = f"tools/{title}.png"
if not saveImage(imageData, fileName):
return None
return fileName
class ToolkitHandler:
async def add(toolkitData: dict, user_id: int = None):
title = toolkitData.get("title", None)
if not title:
logger.error("Не указано название инструмента")
return {}
query = select(Toolkit).where(Toolkit.title == title)
toolkit = await CRUD.read(query)
if toolkit:
logger.error("Инструмент с таким названием уже существует")
return {}
try:
imageDict = {"main": "images/tools/default.png", "additional": []}
if "image" in toolkitData:
imageData = toolkitData.pop("image")
mainImage = imageData.get("main")
imageFileName = handleToolkitImage(mainImage, title)
if imageFileName:
imageDict["main"] = imageFileName
additionalImages = imageData.get("additional", [])
if len(additionalImages) > 0:
for image in additionalImages:
imageFileName = handleToolkitImage(image, title)
if imageFileName:
imageDict["additional"].append(imageFileName)
toolkitData["image"] = imageDict
newToolkit = await Toolkit(**toolkitData).save()
except Exception as e:
logger.error(f"Ошибка сохранения инструмента: {str(e)}")
return {}
if not newToolkit:
logger.error("Инструмент не сохранен")
return {}
logger.info(f"Инструмент {newToolkit.title} успешно создан")
await ServiceRecordsHandler.add(
user_id, {"Добавлен инструмент": toolkitData.toDict()}
)
return newToolkit.toDict()
async def edit(toolkitId: int, **kwargs):
query = select(Toolkit).where(Toolkit.id == toolkitId)
toolkit = await CRUD.read(query)
if not toolkit:
logger.error("Инструмент не найден")
return {}
try:
if "image" in kwargs:
title = kwargs.get("title", toolkit.title)
imageData = kwargs.pop("image")
imageDict = {"main": "", "additional": []}
existImagesList = [toolkit.image.get("main")]
existImagesList.extend(toolkit.image.get("additional"))
newImagesList = [imageData.get("main")]
newImagesList.extend(imageData.get("additional"))
for existImage in existImagesList:
if existImage not in newImagesList:
deleteImage(existImage)
if toolkit.image.get("main") != imageData.get("main"):
if imageData.get("main") in existImagesList:
imageDict["main"] = imageData.get("main")
else:
imageFileName = handleToolkitImage(imageData.get("main"), title)
if imageFileName:
imageDict["main"] = imageFileName
else:
imageDict["main"] = "images/tools/default.png"
imageDict["additional"].extend(imageData.get("additional"))
uploadList = imageData.get("upload", [])
if len(uploadList) > 0:
for image in uploadList:
imageFileName = handleToolkitImage(image, title)
if imageFileName:
imageDict["additional"].append(imageFileName)
kwargs["image"] = imageDict
user_id = kwargs.pop("user_id", None)
editedToolkit = await toolkit.edit(**kwargs)
except Exception as e:
logger.error(f"Ошибка обновления инструмента: {str(e)}")
return {}
if not editedToolkit:
logger.error("Инструмент не обновлен")
return {}
logger.info(
f"Инструмент {editedToolkit.title} успешно обновлен, изменены данные: {kwargs.keys()}"
)
await ServiceRecordsHandler.add(
user_id, {f"Обновлен инструмент {toolkit.title}": editedToolkit.toDict()}
)
return editedToolkit.toDict()
async def getAll():
query = select(Toolkit)
toolkits = await CRUD.read(query, True)
return [toolkit.toDict() for toolkit in toolkits] if toolkits else []
async def get(toolkitId: int):
query = select(Toolkit).where(Toolkit.id == toolkitId)
toolkit = await CRUD.read(query)
if not toolkit:
logger.error("Инструмент не найден")
return {}
return toolkit.toDict()
async def getSeveral(toolkitIds: list[int]) -> list[dict]:
query = select(Toolkit).where(Toolkit.id.in_(toolkitIds))
toolkits = await CRUD.read(query, True)
return [toolkit.toDict() for toolkit in toolkits] if toolkits else []
async def delete(toolkitId: int, user_id: int = None):
query = select(Toolkit).where(Toolkit.id == toolkitId)
toolkit = await CRUD.read(query)
if not toolkit:
logger.error("Инструмент не найден")
return False
try:
toolkitTitle = toolkit.title
result = await CRUD.delete(toolkit)
except Exception as e:
logger.error(f"Ошибка удаления инструмента: {str(e)}")
return False
logger.info(
f"Инструмент {toolkitTitle} {'успешно удален' if result else 'не удален'}"
)
await ServiceRecordsHandler.add(
user_id, {"Удален инструмент": f"Название: {toolkitTitle}"}
)
return result
async def initialize(self):
from .categories import CategoryHandler
categoriesList = await CategoryHandler.getAll()
categories = {category["title"]: category["id"] for category in categoriesList}
baseToolkits = [
{
"title": "Фреза №1",
"description": "Фреза такая сякая этакая #1",
"specifications": {
"Диаметр": "10",
"Длина": "20",
"Ещё что-то": "Ещё столько-то",
},
"category_id": categories["Фрезеровка"],
"quantity_min": 20,
"quantity_min_extra": 10,
"external_link": "https://nazv.ru",
},
{
"title": "Фреза №2",
"description": "Фреза такая сякая этакая #2",
"specifications": {
"Диаметр": "10",
"Длина": "20",
"Ещё что-то": "Ещё столько-то",
},
"category_id": categories["Фрезеровка"],
"quantity_min": 20,
"quantity_min_extra": 10,
"external_link": "https://nazv.ru",
},
{
"title": "Фреза №3",
"description": "Фреза такая сякая этакая #3",
"specifications": {
"Диаметр": "10",
"Длина": "20",
"Ещё что-то": "Ещё столько-то",
},
"category_id": categories["Фрезеровка"],
"quantity_min": 20,
"quantity_min_extra": 10,
"external_link": "https://nazv.ru",
},
{
"title": "Псластина №1",
"description": "Пластина такая сякая этакая #1",
"specifications": {
"Размер": "10",
"Радиус": "0.4",
"Ещё что-то": "Ещё столько-то",
},
"category_id": categories["Токарка"],
"quantity_min": 20,
"quantity_min_extra": 10,
"external_link": "https://nazv.ru",
},
{
"title": "Псластина №2",
"description": "Пластина такая сякая этакая #2",
"specifications": {
"Размер": "10",
"Радиус": "0.4",
"Ещё что-то": "Ещё столько-то",
},
"category_id": categories["Токарка"],
"quantity_min": 20,
"quantity_min_extra": 10,
"external_link": "https://nazv.ru",
},
{
"title": "Псластина №3",
"description": "Пластина такая сякая этакая #3",
"specifications": {
"Размер": "10",
"Радиус": "0.4",
"Ещё что-то": "Ещё столько-то",
},
"category_id": categories["Токарка"],
"quantity_min": 20,
"quantity_min_extra": 10,
"external_link": "https://nazv.ru",
},
{
"title": "Сверло №1",
"description": "Сверло такое сякое этакое #1",
"specifications": {
"Длина": "30",
"Диаметр": "5",
"Ещё что-то": "Ещё столько-то",
},
"category_id": categories["Слесарка"],
"quantity_min": 20,
"quantity_min_extra": 10,
"external_link": "https://nazv.ru",
},
{
"title": "Сверло №2",
"description": "Сверло такое сякое этакое #2",
"specifications": {
"Длина": "30",
"Диаметр": "5",
"Ещё что-то": "Ещё столько-то",
},
"category_id": categories["Слесарка"],
"quantity_min": 20,
"quantity_min_extra": 10,
"external_link": "https://nazv.ru",
},
{
"title": "Сверло №3",
"description": "Сверло такое сякое этакое #3",
"specifications": {
"Длина": "30",
"Диаметр": "5",
"Ещё что-то": "Ещё столько-то",
},
"category_id": categories["Слесарка"],
"quantity_min": 20,
"quantity_min_extra": 10,
"external_link": "https://nazv.ru",
},
]
for toolkit in baseToolkits:
await self.add(toolkit)
logger.info("Базовые инструменты успешно созданы")
return
+209 -68
View File
@@ -1,81 +1,222 @@
from sqlalchemy import or_, select
from db import CRUD
from db.handlers.toolbox import addNewToolbox
from utils import logger, pwd_hash, saveImage, safeFilename, deleteImage, pwd_verify
from db.schemas import User
from utils import logger, pwd_hash
from db.handlers import ServiceRecordsHandler
async def addNewUser(userData: dict) -> dict:
login = userData.get("login", None)
if not login:
logger.error("Не указан логин")
return {}
userName = userData.get("username")
if not userName:
logger.error("Не указано имя пользователя")
return {}
query = select(User).where(or_(User.login == login, User.username == userName))
user = await CRUD.read(query)
if user:
logger.error("Пользователь с таким логином или именем уже существует")
return {}
if "access_level_id" not in userData:
logger.error("Не указан уровень доступа")
return {}
if "password" not in userData:
logger.error("Не указан пароль")
return {}
userData["hashed_password"] = pwd_hash(userData.pop("password"))
newUser = await User(**userData).save()
if not newUser:
logger.error("Ошибка сохранения пользователя")
return {}
logger.info(f"Пользователь {newUser.username} успешно добавлен, id: {newUser.id}")
if newUser.available_own_toolbox:
newToolboxData = {
"title": f"Тулбокс {newUser.username}",
"description": f"Оборудование, полученное сотрудником {newUser.username}, под личную материальную ответственность",
"owner_id": newUser.id,
}
newToolbox = await addNewToolbox(newToolboxData)
def handleUserPhoto(imageData, login: str):
login = safeFilename(login)
fileName = f"users/{login}.png"
if not saveImage(imageData, fileName):
return None
return fileName
class UserHandler:
async def add(userData: dict, user_id: int = None) -> dict:
login = userData.get("login", None)
if not login:
logger.error("Не указан логин")
return {}
userName = userData.get("username", None)
if not userName:
logger.error("Не указано имя пользователя")
return {}
query = select(User).where(or_(User.login == login, User.username == userName))
user = await CRUD.read(query)
if user:
logger.error("Пользователь с таким логином или именем уже существует")
return {}
if "access_level_id" not in userData:
logger.error("Не указан уровень доступа")
return {}
if "password" not in userData:
logger.error("Не указан пароль")
return {}
userData["hashed_password"] = pwd_hash(userData.pop("password"))
if "photo" in userData:
imageData = userData.pop("photo")
photoFile = handleUserPhoto(imageData, login)
if photoFile:
userData["photo"] = photoFile
try:
newUser = await User(**userData).save()
except Exception as e:
logger.error(f"Ошибка сохранения пользователя: {str(e)}")
return {}
if not newUser:
logger.error("Пользователь не сохранен")
return {}
logger.info(
f"Тулбокс {newToolbox['title']} успешно создан и закреплен за пользователем {newUser.username}"
f"Пользователь {newUser.username} успешно добавлен, id: {newUser.id}"
)
return newUser.toDict()
async def editUser(userData: dict) -> dict:
id = userData.get("id", None)
if not id:
logger.error("Не указан id пользователя")
return {}
query = select(User).where(User.id == id)
user = await CRUD.read(query)
if not user:
logger.error("Пользователь с таким id не найден")
return {}
changedUserData = userData.get("changedUserData", {})
if len(changedUserData.keys()) == 0:
logger.error("Не указаны изменяемые данные")
return {}
if "password" in changedUserData:
userData["hashed_password"] = pwd_hash(changedUserData.pop("password"))
editedUser = await user.edit(**changedUserData)
if not editedUser:
logger.error("Ошибка обновления пользователя")
return {}
logger.info(
f"Пользователь {editedUser.username} успешно обновлен, изменены данные: {changedUserData.keys()}"
)
if not user.available_own_toolbox:
if editedUser.available_own_toolbox:
if newUser.available_own_toolbox:
newToolboxData = {
"title": f"Тулбокс {editedUser.username}",
"description": f"Оборудование, полученное сотрудником {editedUser.username}, под личную материальную ответственность",
"owner_id": editedUser.id,
"title": f"Тулбокс {newUser.username}",
"description": f"Оборудование, полученное сотрудником {newUser.username}, под личную материальную ответственность",
"owner_id": newUser.id,
}
newToolbox = await addNewToolbox(newToolboxData)
logger.info(
f"Тулбокс {newToolbox['title']} успешно создан и закреплен за пользователем {editedUser.username}"
f"Тулбокс {newToolbox['title']} успешно создан и закреплен за пользователем {newUser.username}"
)
return editedUser.toDict()
await ServiceRecordsHandler.add(
user_id, {"Добавлен пользователь": newUser.toDict()}
)
return newUser.toDict()
async def edit(userData: dict, user_id: int = None) -> dict:
id = userData.get("id", None)
if not id:
logger.error("Не указан id пользователя")
return {}
query = select(User).where(User.id == id)
user = await CRUD.read(query)
if not user:
logger.error("Пользователь с таким id не найден")
return {}
changedUserData = userData.get("changedUserData", {})
if len(changedUserData.keys()) == 0:
logger.error("Не указаны изменяемые данные")
return {}
if "password" in changedUserData:
userData["hashed_password"] = pwd_hash(changedUserData.pop("password"))
if "photo" in changedUserData:
imageData = changedUserData.pop("photo")
photoFile = handleUserPhoto(imageData, user.login)
if photoFile:
changedUserData["photo"] = photoFile
deleteImage(user.photo)
try:
editedUser = await user.edit(**changedUserData)
except Exception as e:
logger.error(f"Ошибка обновления пользователя: {str(e)}")
return {}
if not editedUser:
logger.error("Ошибка обновления пользователя")
return {}
logger.info(
f"Пользователь {editedUser.username} успешно обновлен, изменены данные: {changedUserData.keys()}"
)
if not user.available_own_toolbox:
if editedUser.available_own_toolbox:
newToolboxData = {
"title": f"Тулбокс {editedUser.username}",
"description": f"Оборудование, полученное сотрудником {editedUser.username}, под личную материальную ответственность",
"owner_id": editedUser.id,
}
newToolbox = await addNewToolbox(newToolboxData)
logger.info(
f"Тулбокс {newToolbox['title']} успешно создан и закреплен за пользователем {editedUser.username}"
)
await ServiceRecordsHandler.add(
user_id, {"Изменен пользователь": editedUser.toDict()}
)
return editedUser.toDict()
async def getAll() -> list[dict]:
query = select(User)
users = await CRUD.read(query, True)
return [user.toDict() for user in users]
async def get(id: int) -> dict:
query = select(User).where(User.id == id)
user = await CRUD.read(query)
if not user:
logger.error("Пользователь с таким id не найден")
return {}
return user.toDict()
async def delete(id: int, user_id: int = None) -> bool:
query = select(User).where(User.id == id)
user = await CRUD.read(query)
if not user:
logger.error("Пользователь с таким id не найден")
return False
try:
userName = user.username
result = await CRUD.delete(user)
except Exception as e:
logger.error(f"Ошибка удаления пользователя: {str(e)}")
return False
logger.info(
f"Пользователь {userName} {'успешно удален' if result else 'не удален'}"
)
await ServiceRecordsHandler.add(user_id, {"Удален пользователь": userName})
return result
async def deletePhoto(id: int, user_id: int = None) -> bool:
query = select(User).where(User.id == id)
user = await CRUD.read(query)
if not user:
logger.error("Пользователь с таким id не найден")
return False
try:
deleteImage(user.photo)
user.photo = "images/users/default.png"
await user.save()
except Exception as e:
logger.error(f"Ошибка удаления фото пользователя: {str(e)}")
return False
logger.info(f"Фото пользователя {user.username} успешно удалено")
await ServiceRecordsHandler.add(
user_id, {"Удалено фото пользователя": user.username}
)
return True
async def auth(login: str, password: str) -> dict:
query = select(User).where(User.login == login)
user = await CRUD.read(query)
if not user:
logger.error("Пользователь с таким логином не найден")
return {}
if not pwd_verify(password, user.hashed_password):
logger.error("Неверный пароль")
return {}
userData = user.toDict()
userData.pop("hashed_password")
await ServiceRecordsHandler.add(
user.id, {"Авторизован пользователь": user.username}
)
return userData
async def initialize(self):
from .access import AccessLevelHandler
accessLevelsList = await AccessLevelHandler.getAll()
acessLevels = {
accessLevel["title"]: accessLevel["id"] for accessLevel in accessLevelsList
}
baseUsers = {
"admin": {
"login": "admin",
"username": "Администратор",
"password": "Alex0172",
"access_level_id": acessLevels["Администратор"],
},
"manager": {
"login": "manager",
"username": "Менеджер",
"password": "Alex0172",
"access_level_id": acessLevels["Менеджер"],
},
"storekeeper": {
"login": "storekeeper",
"username": "Кладовщик",
"password": "Alex0172",
"access_level_id": acessLevels["Кладовщик"],
},
"employee": {
"login": "employee",
"username": "Сотрудник",
"password": "Alex0172",
"access_level_id": acessLevels["Сотрудник"],
},
}
for user in baseUsers.values():
await self.add(user)
logger.info("Инициализация модуля пользователей завершена")
return
+144 -6
View File
@@ -1,6 +1,144 @@
from user import *
from access import *
from toolkit import *
from categories import *
from db.schemas.toolbox import *
from stock import *
from .user import *
from .access import *
from .toolkit import *
from .categories import *
from .toolbox import *
from .stock import *
from typing import Optional
from sqlalchemy import inspect, text
from sqlalchemy.ext.asyncio import create_async_engine, AsyncEngine
from sqlalchemy.schema import CreateTable
from utils import logger
# Импортируем все модели
from db import Base
from db.handlers import InitializeDatabase
class DatabaseInitializer:
existing_tables: Optional[list[str]] = None
def __init__(self, database_url: str):
self.database_url = database_url
self.engine: Optional[AsyncEngine] = None
self.metadata = Base.metadata
async def initialize(self, force: bool = False, reNewDB: bool = False):
"""Main database initialization method"""
try:
self.engine = create_async_engine(self.database_url)
async with self.engine.begin() as conn:
if force:
logger.info("Принудительное удаление и создание баз...")
await self._drop_all()
await self._create_tables_directly()
await self._initialize_data()
elif not await self._check_tables_exist(conn):
logger.info("Не все необходимые таблицы существуют. Создаем...")
await self._create_tables_directly()
# Проверяем после создания
async with self.engine.begin() as conn:
if not await self._check_tables_exist(conn):
raise RuntimeError("Не все необходимые таблицы существуют!")
if reNewDB:
logger.info("Принудительная загрузка данных...")
await self._initialize_data()
else:
logger.info("Все необходимые таблицы существуют. Пропускаем...")
except Exception as e:
logger.error(f"Инициализация базы завершилась ошибкой: {str(e)}")
raise
finally:
if self.engine:
await self.engine.dispose()
async def _check_tables_exist(self, conn) -> bool:
"""Check if all tables from metadata exist"""
try:
DatabaseInitializer.existing_tables = await conn.run_sync(
lambda sync_conn: inspect(sync_conn).get_table_names()
)
required_tables = set(self.metadata.tables.keys())
if not required_tables:
logger.error("Нет данных о таблицах в метаданных")
return False
missing_tables = required_tables - set(DatabaseInitializer.existing_tables)
if missing_tables:
logger.warning("Существующие таблицы:")
logger.info(DatabaseInitializer.existing_tables)
logger.warning("Необходимые таблицы:")
logger.info(required_tables)
logger.warning("Отсутствующие таблицы:")
logger.info(missing_tables)
return False
return True
except Exception as e:
logger.warning(f"Проверка таблиц завершилась ошибкой: {str(e)}")
return False
async def _create_tables_directly(self):
"""Create tables directly using SQLAlchemy (bypass Alembic)"""
async with self.engine.begin() as conn:
# Создаем все таблицы из метаданных
for table in self.metadata.sorted_tables:
try:
if (
DatabaseInitializer.existing_tables
and table.name in DatabaseInitializer.existing_tables
):
logger.debug(
f"Таблица {table.name} уже существует. Пропускаем..."
)
continue
create_stmt = CreateTable(table)
logger.info(f"Создаем таблицу: {table.name}")
await conn.execute(create_stmt)
except Exception as e:
logger.error(f"Ошибка создания таблицы: {str(e)}")
logger.warning("Все таблицы успешно созданы")
async def _drop_all(self):
"""Drop all tables"""
async with self.engine.begin() as conn:
# Получаем список всех таблиц
existing_tables = await conn.run_sync(
lambda sync_conn: inspect(sync_conn).get_table_names()
)
for table in existing_tables:
drop_stmt = text(
f'DROP TABLE "{table}" CASCADE'
) # Кавычки на случай спец. символов
logger.info(f"Удаляем таблицу: {table}")
await conn.execute(drop_stmt)
logger.warning("Все таблицы успешно удалены")
async def _initialize_data(self):
"""Initialize required data"""
try:
logger.info("Инициализация данных...")
await InitializeDatabase.initialize()
logger.info("Данные успешно инициализированы")
except Exception as e:
logger.error(f"Инициализация данных завершилась ошибкой: {str(e)}")
raise
__all__ = [
"User",
"Access",
"Toolbox",
"Category",
"Stock",
"Toolkit",
]
+2 -1
View File
@@ -23,7 +23,7 @@ class AccessLevel(Base):
tools_registration = Column(Boolean, default=False)
tools_registration_edit = Column(Boolean, default=False)
tools_edit = Column(Boolean, default=False)
tools_achievement = Column(Boolean, default=False)
tools_delete = Column(Boolean, default=False)
users_creation = Column(Boolean, default=False)
users_edit = Column(Boolean, default=False)
users_disabling = Column(Boolean, default=False)
@@ -31,6 +31,7 @@ class AccessLevel(Base):
available_own_toolbox = Column(Boolean, default=False)
view_all_toolboxes = Column(Boolean, default=False)
view_requests = Column(Boolean, default=False)
view_services = Column(Boolean, default=False)
access_level_view = Column(Boolean, default=False)
access_level_edit = Column(Boolean, default=False)
manage_toolboxes = Column(Boolean, default=False)
+1 -1
View File
@@ -9,7 +9,7 @@ class Category(Base):
id = Column(Integer, primary_key=True, index=True)
title = Column(String, unique=True, index=True)
description = Column(Text)
description = Column(Text, nullable=True)
created_at = Column(DateTime, default=datetime.now)
updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now)
+75
View File
@@ -0,0 +1,75 @@
from datetime import datetime
from sqlalchemy import Column, DateTime, ForeignKey, Integer, String, Text
from sqlalchemy.dialects.postgresql import JSONB
from db import Base
import utils
class StocksRecords(Base):
__tablename__ = "stocks_records"
id = Column(Integer, primary_key=True, index=True)
action = Column(String, nullable=False)
source_toolbox_id = Column(
Integer, ForeignKey("toolboxes.id", ondelete="CASCADE"), nullable=False
)
target_toolbox_id = Column(
Integer, ForeignKey("toolboxes.id", ondelete="CASCADE"), nullable=True
)
toolkit_id = Column(
Integer, ForeignKey("toolkits.id", ondelete="CASCADE"), nullable=False
)
init_user_id = Column(
Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False
)
accept_user_id = Column(
Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=True
)
reason = Column(Text, nullable=False)
quantity = Column(Integer, nullable=False)
edit_user_id = Column(
Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=True
)
edited = Column(JSONB, nullable=True)
created_at = Column(DateTime, default=datetime.now)
accepted_at = Column(DateTime, nullable=True)
edited_at = Column(DateTime, nullable=True)
updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now)
def __init__(self, **kwargs):
for key, value in kwargs.items():
setattr(self, key, value)
def toDict(self):
return utils.toDict(self)
async def save(self):
from db import CRUD
return await CRUD.create(self, refresh=True)
async def edit(self, **kwargs):
from db import CRUD
return await CRUD.update(StocksRecords, self.id, **kwargs)
class ServicesRecords(Base):
__tablename__ = "services_records"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=True)
details = Column(JSONB, nullable=False)
created_at = Column(DateTime, default=datetime.now)
def __init__(self, **kwargs):
for key, value in kwargs.items():
setattr(self, key, value)
def toDict(self):
return utils.toDict(self)
async def save(self):
from db import CRUD
return await CRUD.create(self, refresh=True)
+2 -1
View File
@@ -1,5 +1,5 @@
from datetime import datetime
from sqlalchemy import Column, DateTime, Float, ForeignKey, Integer
from sqlalchemy import Column, DateTime, Float, ForeignKey, Integer, String
from sqlalchemy.orm import relationship
from db import Base
@@ -24,6 +24,7 @@ class Stock(Base):
)
quantity = Column(Integer, nullable=False)
price = Column(Float, nullable=False)
placement = Column(String, nullable=True)
created_at = Column(DateTime, default=datetime.now)
updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now)
+4 -3
View File
@@ -1,5 +1,5 @@
from datetime import datetime
from sqlalchemy import Column, DateTime, ForeignKey, Integer, String, Text
from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, String, Text
from db import Base
import utils
@@ -9,10 +9,11 @@ class Toolbox(Base):
id = Column(Integer, primary_key=True, index=True)
title = Column(String, unique=True, index=True)
description = Column(Text)
description = Column(Text, nullable=True)
owner_id = Column(
Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=True
Integer, ForeignKey("users.id", ondelete="CASCADE"), default=None, nullable=True
)
monitoring = Column(Boolean, default=False)
created_at = Column(DateTime, default=datetime.now)
updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now)
+3 -4
View File
@@ -11,14 +11,13 @@ class Toolkit(Base):
id = Column(Integer, primary_key=True, index=True)
title = Column(String, unique=True, index=True)
description = Column(Text)
description = Column(Text, nullable=True)
specifications = Column(JSONB, default={})
category_id = Column(Integer, ForeignKey("categories.id", ondelete="CASCADE"))
category_data = relationship(
"Category", cascade="all, delete-orphan", lazy="joined", uselist=False
)
image = Column(
JSONB, default={"main": "images/tools/default.png", "additional": []}
)
image = Column(JSONB)
quantity_min = Column(Integer, nullable=True)
quantity_min_extra = Column(Integer, nullable=True)
external_link = Column(String, nullable=True)