fix: 修复多个关键问题
- 修复前端路由守卫:未登录时不显示提示,直接跳转登录页 - 修复API拦截器:401错误不显示提示,直接跳转 - 增强验证码显示:图片尺寸从120x40增加到200x80 - 增大验证码字体:从28号增加到48号 - 优化验证码字符:排除易混淆的0和1 - 减少干扰线:从5条减少到3条,添加背景色优化 - 增强登录API日志:添加详细的调试日志 - 增强验证码生成和验证日志 - 优化异常处理和错误追踪 影响文件: - src/router/index.ts - src/api/request.ts - app/services/auth_service.py - app/api/v1/auth.py - app/schemas/user.py 测试状态: - 前端构建通过 - 后端语法检查通过 - 验证码显示效果优化完成 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
0
app/crud/__init__.py
Normal file
0
app/crud/__init__.py
Normal file
332
app/crud/allocation.py
Normal file
332
app/crud/allocation.py
Normal file
@@ -0,0 +1,332 @@
|
||||
"""
|
||||
资产分配相关CRUD操作
|
||||
"""
|
||||
from typing import List, Optional, Tuple
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import and_, or_
|
||||
from app.models.allocation import AssetAllocationOrder, AssetAllocationItem
|
||||
from app.models.asset import Asset
|
||||
from app.schemas.allocation import AllocationOrderCreate, AllocationOrderUpdate
|
||||
|
||||
|
||||
class AllocationOrderCRUD:
|
||||
"""分配单CRUD操作"""
|
||||
|
||||
def get(self, db: Session, id: int) -> Optional[AssetAllocationOrder]:
|
||||
"""根据ID获取分配单"""
|
||||
return db.query(AssetAllocationOrder).filter(
|
||||
AssetAllocationOrder.id == id
|
||||
).first()
|
||||
|
||||
def get_by_code(self, db: Session, order_code: str) -> Optional[AssetAllocationOrder]:
|
||||
"""根据单号获取分配单"""
|
||||
return db.query(AssetAllocationOrder).filter(
|
||||
AssetAllocationOrder.order_code == order_code
|
||||
).first()
|
||||
|
||||
def get_multi(
|
||||
self,
|
||||
db: Session,
|
||||
skip: int = 0,
|
||||
limit: int = 20,
|
||||
order_type: Optional[str] = None,
|
||||
approval_status: Optional[str] = None,
|
||||
execute_status: Optional[str] = None,
|
||||
applicant_id: Optional[int] = None,
|
||||
target_organization_id: Optional[int] = None,
|
||||
keyword: Optional[str] = None
|
||||
) -> Tuple[List[AssetAllocationOrder], int]:
|
||||
"""获取分配单列表"""
|
||||
query = db.query(AssetAllocationOrder)
|
||||
|
||||
# 筛选条件
|
||||
if order_type:
|
||||
query = query.filter(AssetAllocationOrder.order_type == order_type)
|
||||
if approval_status:
|
||||
query = query.filter(AssetAllocationOrder.approval_status == approval_status)
|
||||
if execute_status:
|
||||
query = query.filter(AssetAllocationOrder.execute_status == execute_status)
|
||||
if applicant_id:
|
||||
query = query.filter(AssetAllocationOrder.applicant_id == applicant_id)
|
||||
if target_organization_id:
|
||||
query = query.filter(AssetAllocationOrder.target_organization_id == target_organization_id)
|
||||
if keyword:
|
||||
query = query.filter(
|
||||
or_(
|
||||
AssetAllocationOrder.order_code.like(f"%{keyword}%"),
|
||||
AssetAllocationOrder.title.like(f"%{keyword}%")
|
||||
)
|
||||
)
|
||||
|
||||
# 排序
|
||||
query = query.order_by(AssetAllocationOrder.created_at.desc())
|
||||
|
||||
# 总数
|
||||
total = query.count()
|
||||
|
||||
# 分页
|
||||
items = query.offset(skip).limit(limit).all()
|
||||
|
||||
return items, total
|
||||
|
||||
def create(
|
||||
self,
|
||||
db: Session,
|
||||
obj_in: AllocationOrderCreate,
|
||||
order_code: str,
|
||||
applicant_id: int
|
||||
) -> AssetAllocationOrder:
|
||||
"""创建分配单"""
|
||||
# 创建分配单
|
||||
db_obj = AssetAllocationOrder(
|
||||
order_code=order_code,
|
||||
order_type=obj_in.order_type,
|
||||
title=obj_in.title,
|
||||
source_organization_id=obj_in.source_organization_id,
|
||||
target_organization_id=obj_in.target_organization_id,
|
||||
applicant_id=applicant_id,
|
||||
expect_execute_date=obj_in.expect_execute_date,
|
||||
remark=obj_in.remark,
|
||||
created_by=applicant_id,
|
||||
approval_status="pending",
|
||||
execute_status="pending"
|
||||
)
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
|
||||
# 创建分配单明细
|
||||
self._create_items(
|
||||
db=db,
|
||||
order_id=db_obj.id,
|
||||
asset_ids=obj_in.asset_ids,
|
||||
target_org_id=obj_in.target_organization_id
|
||||
)
|
||||
|
||||
return db_obj
|
||||
|
||||
def update(
|
||||
self,
|
||||
db: Session,
|
||||
db_obj: AssetAllocationOrder,
|
||||
obj_in: AllocationOrderUpdate,
|
||||
updater_id: int
|
||||
) -> AssetAllocationOrder:
|
||||
"""更新分配单"""
|
||||
update_data = obj_in.model_dump(exclude_unset=True)
|
||||
for field, value in update_data.items():
|
||||
setattr(db_obj, field, value)
|
||||
db_obj.updated_by = updater_id
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
def approve(
|
||||
self,
|
||||
db: Session,
|
||||
db_obj: AssetAllocationOrder,
|
||||
approval_status: str,
|
||||
approver_id: int,
|
||||
approval_remark: Optional[str] = None
|
||||
) -> AssetAllocationOrder:
|
||||
"""审批分配单"""
|
||||
from datetime import datetime
|
||||
|
||||
db_obj.approval_status = approval_status
|
||||
db_obj.approver_id = approver_id
|
||||
db_obj.approval_time = datetime.utcnow()
|
||||
db_obj.approval_remark = approval_remark
|
||||
|
||||
# 如果审批通过,自动设置为可执行状态
|
||||
if approval_status == "approved":
|
||||
db_obj.execute_status = "pending"
|
||||
elif approval_status == "rejected":
|
||||
db_obj.execute_status = "cancelled"
|
||||
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
def execute(
|
||||
self,
|
||||
db: Session,
|
||||
db_obj: AssetAllocationOrder,
|
||||
executor_id: int
|
||||
) -> AssetAllocationOrder:
|
||||
"""执行分配单"""
|
||||
from datetime import datetime, date
|
||||
|
||||
db_obj.execute_status = "completed"
|
||||
db_obj.actual_execute_date = date.today()
|
||||
db_obj.executor_id = executor_id
|
||||
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
def cancel(self, db: Session, db_obj: AssetAllocationOrder) -> AssetAllocationOrder:
|
||||
"""取消分配单"""
|
||||
db_obj.approval_status = "cancelled"
|
||||
db_obj.execute_status = "cancelled"
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
def delete(self, db: Session, id: int) -> bool:
|
||||
"""删除分配单"""
|
||||
obj = self.get(db, id)
|
||||
if obj:
|
||||
db.delete(obj)
|
||||
db.commit()
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_statistics(
|
||||
self,
|
||||
db: Session,
|
||||
applicant_id: Optional[int] = None
|
||||
) -> dict:
|
||||
"""获取分配单统计信息"""
|
||||
query = db.query(AssetAllocationOrder)
|
||||
|
||||
if applicant_id:
|
||||
query = query.filter(AssetAllocationOrder.applicant_id == applicant_id)
|
||||
|
||||
total = query.count()
|
||||
pending = query.filter(AssetAllocationOrder.approval_status == "pending").count()
|
||||
approved = query.filter(AssetAllocationOrder.approval_status == "approved").count()
|
||||
rejected = query.filter(AssetAllocationOrder.approval_status == "rejected").count()
|
||||
executing = query.filter(AssetAllocationOrder.execute_status == "executing").count()
|
||||
completed = query.filter(AssetAllocationOrder.execute_status == "completed").count()
|
||||
|
||||
return {
|
||||
"total": total,
|
||||
"pending": pending,
|
||||
"approved": approved,
|
||||
"rejected": rejected,
|
||||
"executing": executing,
|
||||
"completed": completed
|
||||
}
|
||||
|
||||
def _create_items(
|
||||
self,
|
||||
db: Session,
|
||||
order_id: int,
|
||||
asset_ids: List[int],
|
||||
target_org_id: int
|
||||
):
|
||||
"""创建分配单明细"""
|
||||
# 查询资产信息
|
||||
assets = db.query(Asset).filter(Asset.id.in_(asset_ids)).all()
|
||||
|
||||
for asset in assets:
|
||||
item = AssetAllocationItem(
|
||||
order_id=order_id,
|
||||
asset_id=asset.id,
|
||||
asset_code=asset.asset_code,
|
||||
asset_name=asset.asset_name,
|
||||
from_organization_id=asset.organization_id,
|
||||
to_organization_id=target_org_id,
|
||||
from_status=asset.status,
|
||||
to_status=self._get_target_status(asset.status),
|
||||
execute_status="pending"
|
||||
)
|
||||
db.add(item)
|
||||
|
||||
db.commit()
|
||||
|
||||
def _get_target_status(self, current_status: str) -> str:
|
||||
"""根据当前状态获取目标状态"""
|
||||
status_map = {
|
||||
"in_stock": "transferring",
|
||||
"in_use": "transferring",
|
||||
"maintenance": "in_stock"
|
||||
}
|
||||
return status_map.get(current_status, "transferring")
|
||||
|
||||
|
||||
class AllocationItemCRUD:
|
||||
"""分配单明细CRUD操作"""
|
||||
|
||||
def get_by_order(self, db: Session, order_id: int) -> List[AssetAllocationItem]:
|
||||
"""根据分配单ID获取明细列表"""
|
||||
return db.query(AssetAllocationItem).filter(
|
||||
AssetAllocationItem.order_id == order_id
|
||||
).order_by(AssetAllocationItem.id).all()
|
||||
|
||||
def get_multi(
|
||||
self,
|
||||
db: Session,
|
||||
skip: int = 0,
|
||||
limit: int = 20,
|
||||
order_id: Optional[int] = None,
|
||||
execute_status: Optional[str] = None
|
||||
) -> Tuple[List[AssetAllocationItem], int]:
|
||||
"""获取明细列表"""
|
||||
query = db.query(AssetAllocationItem)
|
||||
|
||||
if order_id:
|
||||
query = query.filter(AssetAllocationItem.order_id == order_id)
|
||||
if execute_status:
|
||||
query = query.filter(AssetAllocationItem.execute_status == execute_status)
|
||||
|
||||
total = query.count()
|
||||
items = query.offset(skip).limit(limit).all()
|
||||
|
||||
return items, total
|
||||
|
||||
def update_execute_status(
|
||||
self,
|
||||
db: Session,
|
||||
item_id: int,
|
||||
execute_status: str,
|
||||
failure_reason: Optional[str] = None
|
||||
) -> AssetAllocationItem:
|
||||
"""更新明细执行状态"""
|
||||
from datetime import datetime
|
||||
|
||||
item = db.query(AssetAllocationItem).filter(
|
||||
AssetAllocationItem.id == item_id
|
||||
).first()
|
||||
|
||||
if item:
|
||||
item.execute_status = execute_status
|
||||
item.execute_time = datetime.utcnow()
|
||||
item.failure_reason = failure_reason
|
||||
db.add(item)
|
||||
db.commit()
|
||||
db.refresh(item)
|
||||
|
||||
return item
|
||||
|
||||
def batch_update_execute_status(
|
||||
self,
|
||||
db: Session,
|
||||
order_id: int,
|
||||
execute_status: str
|
||||
):
|
||||
"""批量更新明细执行状态"""
|
||||
from datetime import datetime
|
||||
|
||||
items = db.query(AssetAllocationItem).filter(
|
||||
and_(
|
||||
AssetAllocationItem.order_id == order_id,
|
||||
AssetAllocationItem.execute_status == "pending"
|
||||
)
|
||||
).all()
|
||||
|
||||
for item in items:
|
||||
item.execute_status = execute_status
|
||||
item.execute_time = datetime.utcnow()
|
||||
db.add(item)
|
||||
|
||||
db.commit()
|
||||
|
||||
|
||||
# 创建全局实例
|
||||
allocation_order = AllocationOrderCRUD()
|
||||
allocation_item = AllocationItemCRUD()
|
||||
266
app/crud/asset.py
Normal file
266
app/crud/asset.py
Normal file
@@ -0,0 +1,266 @@
|
||||
"""
|
||||
资产CRUD操作
|
||||
"""
|
||||
from typing import List, Optional, Tuple, Dict, Any
|
||||
from sqlalchemy import and_, or_, func
|
||||
from sqlalchemy.orm import Session
|
||||
from app.models.asset import Asset, AssetStatusHistory
|
||||
from app.schemas.asset import AssetCreate, AssetUpdate
|
||||
|
||||
|
||||
class AssetCRUD:
|
||||
"""资产CRUD操作类"""
|
||||
|
||||
def get(self, db: Session, id: int) -> Optional[Asset]:
|
||||
"""根据ID获取资产"""
|
||||
return db.query(Asset).filter(
|
||||
and_(
|
||||
Asset.id == id,
|
||||
Asset.deleted_at.is_(None)
|
||||
)
|
||||
).first()
|
||||
|
||||
def get_by_code(self, db: Session, code: str) -> Optional[Asset]:
|
||||
"""根据编码获取资产"""
|
||||
return db.query(Asset).filter(
|
||||
and_(
|
||||
Asset.asset_code == code,
|
||||
Asset.deleted_at.is_(None)
|
||||
)
|
||||
).first()
|
||||
|
||||
def get_by_serial_number(self, db: Session, serial_number: str) -> Optional[Asset]:
|
||||
"""根据序列号获取资产"""
|
||||
return db.query(Asset).filter(
|
||||
and_(
|
||||
Asset.serial_number == serial_number,
|
||||
Asset.deleted_at.is_(None)
|
||||
)
|
||||
).first()
|
||||
|
||||
def get_multi(
|
||||
self,
|
||||
db: Session,
|
||||
skip: int = 0,
|
||||
limit: int = 20,
|
||||
keyword: Optional[str] = None,
|
||||
device_type_id: Optional[int] = None,
|
||||
organization_id: Optional[int] = None,
|
||||
status: Optional[str] = None,
|
||||
purchase_date_start: Optional[Any] = None,
|
||||
purchase_date_end: Optional[Any] = None
|
||||
) -> Tuple[List[Asset], int]:
|
||||
"""获取资产列表"""
|
||||
query = db.query(Asset).filter(Asset.deleted_at.is_(None))
|
||||
|
||||
# 关键词搜索
|
||||
if keyword:
|
||||
query = query.filter(
|
||||
or_(
|
||||
Asset.asset_code.ilike(f"%{keyword}%"),
|
||||
Asset.asset_name.ilike(f"%{keyword}%"),
|
||||
Asset.model.ilike(f"%{keyword}%"),
|
||||
Asset.serial_number.ilike(f"%{keyword}%")
|
||||
)
|
||||
)
|
||||
|
||||
# 筛选条件
|
||||
if device_type_id:
|
||||
query = query.filter(Asset.device_type_id == device_type_id)
|
||||
if organization_id:
|
||||
query = query.filter(Asset.organization_id == organization_id)
|
||||
if status:
|
||||
query = query.filter(Asset.status == status)
|
||||
if purchase_date_start:
|
||||
query = query.filter(Asset.purchase_date >= purchase_date_start)
|
||||
if purchase_date_end:
|
||||
query = query.filter(Asset.purchase_date <= purchase_date_end)
|
||||
|
||||
# 排序
|
||||
query = query.order_by(Asset.id.desc())
|
||||
|
||||
# 总数
|
||||
total = query.count()
|
||||
|
||||
# 分页
|
||||
items = query.offset(skip).limit(limit).all()
|
||||
|
||||
return items, total
|
||||
|
||||
def create(
|
||||
self,
|
||||
db: Session,
|
||||
obj_in: AssetCreate,
|
||||
asset_code: str,
|
||||
creator_id: Optional[int] = None
|
||||
) -> Asset:
|
||||
"""创建资产"""
|
||||
# 计算保修到期日期
|
||||
warranty_expire_date = None
|
||||
if obj_in.purchase_date and obj_in.warranty_period:
|
||||
from datetime import timedelta
|
||||
warranty_expire_date = obj_in.purchase_date + timedelta(days=obj_in.warranty_period * 30)
|
||||
|
||||
db_obj = Asset(
|
||||
**obj_in.model_dump(),
|
||||
asset_code=asset_code,
|
||||
status="pending",
|
||||
warranty_expire_date=warranty_expire_date,
|
||||
created_by=creator_id
|
||||
)
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
def update(
|
||||
self,
|
||||
db: Session,
|
||||
db_obj: Asset,
|
||||
obj_in: AssetUpdate,
|
||||
updater_id: Optional[int] = None
|
||||
) -> Asset:
|
||||
"""更新资产"""
|
||||
obj_data = obj_in.model_dump(exclude_unset=True)
|
||||
|
||||
# 重新计算保修到期日期
|
||||
if "purchase_date" in obj_data or "warranty_period" in obj_data:
|
||||
purchase_date = obj_data.get("purchase_date", db_obj.purchase_date)
|
||||
warranty_period = obj_data.get("warranty_period", db_obj.warranty_period)
|
||||
|
||||
if purchase_date and warranty_period:
|
||||
from datetime import timedelta
|
||||
obj_data["warranty_expire_date"] = purchase_date + timedelta(days=warranty_period * 30)
|
||||
|
||||
for field, value in obj_data.items():
|
||||
setattr(db_obj, field, value)
|
||||
|
||||
db_obj.updated_by = updater_id
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
def delete(self, db: Session, id: int, deleter_id: Optional[int] = None) -> bool:
|
||||
"""删除资产(软删除)"""
|
||||
obj = self.get(db, id)
|
||||
if not obj:
|
||||
return False
|
||||
|
||||
obj.deleted_at = func.now()
|
||||
obj.deleted_by = deleter_id
|
||||
db.add(obj)
|
||||
db.commit()
|
||||
return True
|
||||
|
||||
def get_by_ids(self, db: Session, ids: List[int]) -> List[Asset]:
|
||||
"""根据ID列表获取资产"""
|
||||
return db.query(Asset).filter(
|
||||
and_(
|
||||
Asset.id.in_(ids),
|
||||
Asset.deleted_at.is_(None)
|
||||
)
|
||||
).all()
|
||||
|
||||
def update_status(
|
||||
self,
|
||||
db: Session,
|
||||
asset_id: int,
|
||||
new_status: str,
|
||||
updater_id: Optional[int] = None
|
||||
) -> Optional[Asset]:
|
||||
"""更新资产状态"""
|
||||
obj = self.get(db, asset_id)
|
||||
if not obj:
|
||||
return None
|
||||
|
||||
obj.status = new_status
|
||||
obj.updated_by = updater_id
|
||||
db.add(obj)
|
||||
db.commit()
|
||||
db.refresh(obj)
|
||||
return obj
|
||||
|
||||
def search_by_dynamic_field(
|
||||
self,
|
||||
db: Session,
|
||||
field_name: str,
|
||||
field_value: Any,
|
||||
skip: int = 0,
|
||||
limit: int = 20
|
||||
) -> Tuple[List[Asset], int]:
|
||||
"""
|
||||
根据动态字段搜索资产
|
||||
|
||||
使用JSONB的@>操作符进行高效查询
|
||||
"""
|
||||
query = db.query(Asset).filter(
|
||||
and_(
|
||||
Asset.deleted_at.is_(None),
|
||||
Asset.dynamic_attributes.has_key(field_name)
|
||||
)
|
||||
)
|
||||
|
||||
# 根据值类型进行不同的查询
|
||||
if isinstance(field_value, str):
|
||||
query = query.filter(Asset.dynamic_attributes[field_name].astext == field_value)
|
||||
else:
|
||||
query = query.filter(Asset.dynamic_attributes[field_name] == field_value)
|
||||
|
||||
total = query.count()
|
||||
items = query.offset(skip).limit(limit).all()
|
||||
|
||||
return items, total
|
||||
|
||||
|
||||
class AssetStatusHistoryCRUD:
|
||||
"""资产状态历史CRUD操作类"""
|
||||
|
||||
def create(
|
||||
self,
|
||||
db: Session,
|
||||
asset_id: int,
|
||||
old_status: Optional[str],
|
||||
new_status: str,
|
||||
operation_type: str,
|
||||
operator_id: int,
|
||||
operator_name: Optional[str] = None,
|
||||
organization_id: Optional[int] = None,
|
||||
remark: Optional[str] = None,
|
||||
extra_data: Optional[Dict[str, Any]] = None
|
||||
) -> AssetStatusHistory:
|
||||
"""创建状态历史记录"""
|
||||
db_obj = AssetStatusHistory(
|
||||
asset_id=asset_id,
|
||||
old_status=old_status,
|
||||
new_status=new_status,
|
||||
operation_type=operation_type,
|
||||
operator_id=operator_id,
|
||||
operator_name=operator_name,
|
||||
organization_id=organization_id,
|
||||
remark=remark,
|
||||
extra_data=extra_data
|
||||
)
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
def get_by_asset(
|
||||
self,
|
||||
db: Session,
|
||||
asset_id: int,
|
||||
skip: int = 0,
|
||||
limit: int = 50
|
||||
) -> List[AssetStatusHistory]:
|
||||
"""获取资产的状态历史"""
|
||||
return db.query(AssetStatusHistory).filter(
|
||||
AssetStatusHistory.asset_id == asset_id
|
||||
).order_by(
|
||||
AssetStatusHistory.created_at.desc()
|
||||
).offset(skip).limit(limit).all()
|
||||
|
||||
|
||||
# 创建全局实例
|
||||
asset = AssetCRUD()
|
||||
asset_status_history = AssetStatusHistoryCRUD()
|
||||
198
app/crud/brand_supplier.py
Normal file
198
app/crud/brand_supplier.py
Normal file
@@ -0,0 +1,198 @@
|
||||
"""
|
||||
品牌和供应商CRUD操作
|
||||
"""
|
||||
from typing import List, Optional, Tuple
|
||||
from sqlalchemy import and_, or_, func
|
||||
from sqlalchemy.orm import Session
|
||||
from app.models.brand_supplier import Brand, Supplier
|
||||
from app.schemas.brand_supplier import (
|
||||
BrandCreate,
|
||||
BrandUpdate,
|
||||
SupplierCreate,
|
||||
SupplierUpdate
|
||||
)
|
||||
|
||||
|
||||
class BrandCRUD:
|
||||
"""品牌CRUD操作类"""
|
||||
|
||||
def get(self, db: Session, id: int) -> Optional[Brand]:
|
||||
"""根据ID获取品牌"""
|
||||
return db.query(Brand).filter(
|
||||
and_(
|
||||
Brand.id == id,
|
||||
Brand.deleted_at.is_(None)
|
||||
)
|
||||
).first()
|
||||
|
||||
def get_by_code(self, db: Session, code: str) -> Optional[Brand]:
|
||||
"""根据代码获取品牌"""
|
||||
return db.query(Brand).filter(
|
||||
and_(
|
||||
Brand.brand_code == code,
|
||||
Brand.deleted_at.is_(None)
|
||||
)
|
||||
).first()
|
||||
|
||||
def get_multi(
|
||||
self,
|
||||
db: Session,
|
||||
skip: int = 0,
|
||||
limit: int = 20,
|
||||
status: Optional[str] = None,
|
||||
keyword: Optional[str] = None
|
||||
) -> Tuple[List[Brand], int]:
|
||||
"""获取品牌列表"""
|
||||
query = db.query(Brand).filter(Brand.deleted_at.is_(None))
|
||||
|
||||
if status:
|
||||
query = query.filter(Brand.status == status)
|
||||
if keyword:
|
||||
query = query.filter(
|
||||
or_(
|
||||
Brand.brand_code.ilike(f"%{keyword}%"),
|
||||
Brand.brand_name.ilike(f"%{keyword}%")
|
||||
)
|
||||
)
|
||||
|
||||
query = query.order_by(Brand.sort_order.asc(), Brand.id.desc())
|
||||
total = query.count()
|
||||
items = query.offset(skip).limit(limit).all()
|
||||
|
||||
return items, total
|
||||
|
||||
def create(self, db: Session, obj_in: BrandCreate, creator_id: Optional[int] = None) -> Brand:
|
||||
"""创建品牌"""
|
||||
if self.get_by_code(db, obj_in.brand_code):
|
||||
raise ValueError(f"品牌代码 '{obj_in.brand_code}' 已存在")
|
||||
|
||||
db_obj = Brand(**obj_in.model_dump(), created_by=creator_id)
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
def update(
|
||||
self,
|
||||
db: Session,
|
||||
db_obj: Brand,
|
||||
obj_in: BrandUpdate,
|
||||
updater_id: Optional[int] = None
|
||||
) -> Brand:
|
||||
"""更新品牌"""
|
||||
obj_data = obj_in.model_dump(exclude_unset=True)
|
||||
for field, value in obj_data.items():
|
||||
setattr(db_obj, field, value)
|
||||
|
||||
db_obj.updated_by = updater_id
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
def delete(self, db: Session, id: int, deleter_id: Optional[int] = None) -> bool:
|
||||
"""删除品牌(软删除)"""
|
||||
obj = self.get(db, id)
|
||||
if not obj:
|
||||
return False
|
||||
|
||||
obj.deleted_at = func.now()
|
||||
obj.deleted_by = deleter_id
|
||||
db.add(obj)
|
||||
db.commit()
|
||||
return True
|
||||
|
||||
|
||||
class SupplierCRUD:
|
||||
"""供应商CRUD操作类"""
|
||||
|
||||
def get(self, db: Session, id: int) -> Optional[Supplier]:
|
||||
"""根据ID获取供应商"""
|
||||
return db.query(Supplier).filter(
|
||||
and_(
|
||||
Supplier.id == id,
|
||||
Supplier.deleted_at.is_(None)
|
||||
)
|
||||
).first()
|
||||
|
||||
def get_by_code(self, db: Session, code: str) -> Optional[Supplier]:
|
||||
"""根据代码获取供应商"""
|
||||
return db.query(Supplier).filter(
|
||||
and_(
|
||||
Supplier.supplier_code == code,
|
||||
Supplier.deleted_at.is_(None)
|
||||
)
|
||||
).first()
|
||||
|
||||
def get_multi(
|
||||
self,
|
||||
db: Session,
|
||||
skip: int = 0,
|
||||
limit: int = 20,
|
||||
status: Optional[str] = None,
|
||||
keyword: Optional[str] = None
|
||||
) -> Tuple[List[Supplier], int]:
|
||||
"""获取供应商列表"""
|
||||
query = db.query(Supplier).filter(Supplier.deleted_at.is_(None))
|
||||
|
||||
if status:
|
||||
query = query.filter(Supplier.status == status)
|
||||
if keyword:
|
||||
query = query.filter(
|
||||
or_(
|
||||
Supplier.supplier_code.ilike(f"%{keyword}%"),
|
||||
Supplier.supplier_name.ilike(f"%{keyword}%")
|
||||
)
|
||||
)
|
||||
|
||||
query = query.order_by(Supplier.id.desc())
|
||||
total = query.count()
|
||||
items = query.offset(skip).limit(limit).all()
|
||||
|
||||
return items, total
|
||||
|
||||
def create(self, db: Session, obj_in: SupplierCreate, creator_id: Optional[int] = None) -> Supplier:
|
||||
"""创建供应商"""
|
||||
if self.get_by_code(db, obj_in.supplier_code):
|
||||
raise ValueError(f"供应商代码 '{obj_in.supplier_code}' 已存在")
|
||||
|
||||
db_obj = Supplier(**obj_in.model_dump(), created_by=creator_id)
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
def update(
|
||||
self,
|
||||
db: Session,
|
||||
db_obj: Supplier,
|
||||
obj_in: SupplierUpdate,
|
||||
updater_id: Optional[int] = None
|
||||
) -> Supplier:
|
||||
"""更新供应商"""
|
||||
obj_data = obj_in.model_dump(exclude_unset=True)
|
||||
for field, value in obj_data.items():
|
||||
setattr(db_obj, field, value)
|
||||
|
||||
db_obj.updated_by = updater_id
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
def delete(self, db: Session, id: int, deleter_id: Optional[int] = None) -> bool:
|
||||
"""删除供应商(软删除)"""
|
||||
obj = self.get(db, id)
|
||||
if not obj:
|
||||
return False
|
||||
|
||||
obj.deleted_at = func.now()
|
||||
obj.deleted_by = deleter_id
|
||||
db.add(obj)
|
||||
db.commit()
|
||||
return True
|
||||
|
||||
|
||||
# 创建全局实例
|
||||
brand = BrandCRUD()
|
||||
supplier = SupplierCRUD()
|
||||
369
app/crud/device_type.py
Normal file
369
app/crud/device_type.py
Normal file
@@ -0,0 +1,369 @@
|
||||
"""
|
||||
设备类型CRUD操作
|
||||
"""
|
||||
from typing import List, Optional, Tuple
|
||||
from sqlalchemy import select, and_, or_, func
|
||||
from sqlalchemy.orm import Session
|
||||
from app.models.device_type import DeviceType, DeviceTypeField
|
||||
from app.schemas.device_type import DeviceTypeCreate, DeviceTypeUpdate, DeviceTypeFieldCreate, DeviceTypeFieldUpdate
|
||||
|
||||
|
||||
class DeviceTypeCRUD:
|
||||
"""设备类型CRUD操作类"""
|
||||
|
||||
def get(self, db: Session, id: int) -> Optional[DeviceType]:
|
||||
"""
|
||||
根据ID获取设备类型
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
id: 设备类型ID
|
||||
|
||||
Returns:
|
||||
DeviceType对象或None
|
||||
"""
|
||||
return db.query(DeviceType).filter(
|
||||
and_(
|
||||
DeviceType.id == id,
|
||||
DeviceType.deleted_at.is_(None)
|
||||
)
|
||||
).first()
|
||||
|
||||
def get_by_code(self, db: Session, code: str) -> Optional[DeviceType]:
|
||||
"""
|
||||
根据代码获取设备类型
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
code: 设备类型代码
|
||||
|
||||
Returns:
|
||||
DeviceType对象或None
|
||||
"""
|
||||
return db.query(DeviceType).filter(
|
||||
and_(
|
||||
DeviceType.type_code == code,
|
||||
DeviceType.deleted_at.is_(None)
|
||||
)
|
||||
).first()
|
||||
|
||||
def get_multi(
|
||||
self,
|
||||
db: Session,
|
||||
skip: int = 0,
|
||||
limit: int = 20,
|
||||
category: Optional[str] = None,
|
||||
status: Optional[str] = None,
|
||||
keyword: Optional[str] = None
|
||||
) -> Tuple[List[DeviceType], int]:
|
||||
"""
|
||||
获取设备类型列表
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
skip: 跳过条数
|
||||
limit: 返回条数
|
||||
category: 设备分类筛选
|
||||
status: 状态筛选
|
||||
keyword: 搜索关键词
|
||||
|
||||
Returns:
|
||||
(设备类型列表, 总数)
|
||||
"""
|
||||
query = db.query(DeviceType).filter(DeviceType.deleted_at.is_(None))
|
||||
|
||||
# 筛选条件
|
||||
if category:
|
||||
query = query.filter(DeviceType.category == category)
|
||||
if status:
|
||||
query = query.filter(DeviceType.status == status)
|
||||
if keyword:
|
||||
query = query.filter(
|
||||
or_(
|
||||
DeviceType.type_code.ilike(f"%{keyword}%"),
|
||||
DeviceType.type_name.ilike(f"%{keyword}%")
|
||||
)
|
||||
)
|
||||
|
||||
# 排序
|
||||
query = query.order_by(DeviceType.sort_order.asc(), DeviceType.id.desc())
|
||||
|
||||
# 总数
|
||||
total = query.count()
|
||||
|
||||
# 分页
|
||||
items = query.offset(skip).limit(limit).all()
|
||||
|
||||
return items, total
|
||||
|
||||
def create(self, db: Session, obj_in: DeviceTypeCreate, creator_id: Optional[int] = None) -> DeviceType:
|
||||
"""
|
||||
创建设备类型
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
obj_in: 创建数据
|
||||
creator_id: 创建人ID
|
||||
|
||||
Returns:
|
||||
创建的DeviceType对象
|
||||
"""
|
||||
# 检查代码是否已存在
|
||||
if self.get_by_code(db, obj_in.type_code):
|
||||
raise ValueError(f"设备类型代码 '{obj_in.type_code}' 已存在")
|
||||
|
||||
db_obj = DeviceType(**obj_in.model_dump(), created_by=creator_id)
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
def update(
|
||||
self,
|
||||
db: Session,
|
||||
db_obj: DeviceType,
|
||||
obj_in: DeviceTypeUpdate,
|
||||
updater_id: Optional[int] = None
|
||||
) -> DeviceType:
|
||||
"""
|
||||
更新设备类型
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
db_obj: 数据库对象
|
||||
obj_in: 更新数据
|
||||
updater_id: 更新人ID
|
||||
|
||||
Returns:
|
||||
更新后的DeviceType对象
|
||||
"""
|
||||
obj_data = obj_in.model_dump(exclude_unset=True)
|
||||
for field, value in obj_data.items():
|
||||
setattr(db_obj, field, value)
|
||||
|
||||
db_obj.updated_by = updater_id
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
def delete(self, db: Session, id: int, deleter_id: Optional[int] = None) -> bool:
|
||||
"""
|
||||
删除设备类型(软删除)
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
id: 设备类型ID
|
||||
deleter_id: 删除人ID
|
||||
|
||||
Returns:
|
||||
是否删除成功
|
||||
"""
|
||||
obj = self.get(db, id)
|
||||
if not obj:
|
||||
return False
|
||||
|
||||
obj.deleted_at = func.now()
|
||||
obj.deleted_by = deleter_id
|
||||
db.add(obj)
|
||||
db.commit()
|
||||
return True
|
||||
|
||||
def get_all_categories(self, db: Session) -> List[str]:
|
||||
"""
|
||||
获取所有设备分类
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
|
||||
Returns:
|
||||
设备分类列表
|
||||
"""
|
||||
result = db.query(DeviceType.category).filter(
|
||||
and_(
|
||||
DeviceType.deleted_at.is_(None),
|
||||
DeviceType.category.isnot(None)
|
||||
)
|
||||
).distinct().all()
|
||||
return [r[0] for r in result if r[0]]
|
||||
|
||||
|
||||
class DeviceTypeFieldCRUD:
|
||||
"""设备类型字段CRUD操作类"""
|
||||
|
||||
def get(self, db: Session, id: int) -> Optional[DeviceTypeField]:
|
||||
"""
|
||||
根据ID获取字段
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
id: 字段ID
|
||||
|
||||
Returns:
|
||||
DeviceTypeField对象或None
|
||||
"""
|
||||
return db.query(DeviceTypeField).filter(
|
||||
and_(
|
||||
DeviceTypeField.id == id,
|
||||
DeviceTypeField.deleted_at.is_(None)
|
||||
)
|
||||
).first()
|
||||
|
||||
def get_by_device_type(
|
||||
self,
|
||||
db: Session,
|
||||
device_type_id: int,
|
||||
status: Optional[str] = None
|
||||
) -> List[DeviceTypeField]:
|
||||
"""
|
||||
获取设备类型的所有字段
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
device_type_id: 设备类型ID
|
||||
status: 状态筛选
|
||||
|
||||
Returns:
|
||||
字段列表
|
||||
"""
|
||||
query = db.query(DeviceTypeField).filter(
|
||||
and_(
|
||||
DeviceTypeField.device_type_id == device_type_id,
|
||||
DeviceTypeField.deleted_at.is_(None)
|
||||
)
|
||||
)
|
||||
|
||||
if status:
|
||||
query = query.filter(DeviceTypeField.status == status)
|
||||
|
||||
return query.order_by(DeviceTypeField.sort_order.asc(), DeviceTypeField.id.asc()).all()
|
||||
|
||||
def create(
|
||||
self,
|
||||
db: Session,
|
||||
obj_in: DeviceTypeFieldCreate,
|
||||
device_type_id: int,
|
||||
creator_id: Optional[int] = None
|
||||
) -> DeviceTypeField:
|
||||
"""
|
||||
创建字段
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
obj_in: 创建数据
|
||||
device_type_id: 设备类型ID
|
||||
creator_id: 创建人ID
|
||||
|
||||
Returns:
|
||||
创建的DeviceTypeField对象
|
||||
"""
|
||||
# 检查字段代码是否已存在
|
||||
existing = db.query(DeviceTypeField).filter(
|
||||
and_(
|
||||
DeviceTypeField.device_type_id == device_type_id,
|
||||
DeviceTypeField.field_code == obj_in.field_code,
|
||||
DeviceTypeField.deleted_at.is_(None)
|
||||
)
|
||||
).first()
|
||||
|
||||
if existing:
|
||||
raise ValueError(f"字段代码 '{obj_in.field_code}' 已存在")
|
||||
|
||||
db_obj = DeviceTypeField(
|
||||
**obj_in.model_dump(),
|
||||
device_type_id=device_type_id,
|
||||
created_by=creator_id
|
||||
)
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
def update(
|
||||
self,
|
||||
db: Session,
|
||||
db_obj: DeviceTypeField,
|
||||
obj_in: DeviceTypeFieldUpdate,
|
||||
updater_id: Optional[int] = None
|
||||
) -> DeviceTypeField:
|
||||
"""
|
||||
更新字段
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
db_obj: 数据库对象
|
||||
obj_in: 更新数据
|
||||
updater_id: 更新人ID
|
||||
|
||||
Returns:
|
||||
更新后的DeviceTypeField对象
|
||||
"""
|
||||
obj_data = obj_in.model_dump(exclude_unset=True)
|
||||
for field, value in obj_data.items():
|
||||
setattr(db_obj, field, value)
|
||||
|
||||
db_obj.updated_by = updater_id
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
def delete(self, db: Session, id: int, deleter_id: Optional[int] = None) -> bool:
|
||||
"""
|
||||
删除字段(软删除)
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
id: 字段ID
|
||||
deleter_id: 删除人ID
|
||||
|
||||
Returns:
|
||||
是否删除成功
|
||||
"""
|
||||
obj = self.get(db, id)
|
||||
if not obj:
|
||||
return False
|
||||
|
||||
obj.deleted_at = func.now()
|
||||
obj.deleted_by = deleter_id
|
||||
db.add(obj)
|
||||
db.commit()
|
||||
return True
|
||||
|
||||
def batch_create(
|
||||
self,
|
||||
db: Session,
|
||||
fields_in: List[DeviceTypeFieldCreate],
|
||||
device_type_id: int,
|
||||
creator_id: Optional[int] = None
|
||||
) -> List[DeviceTypeField]:
|
||||
"""
|
||||
批量创建字段
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
fields_in: 字段创建列表
|
||||
device_type_id: 设备类型ID
|
||||
creator_id: 创建人ID
|
||||
|
||||
Returns:
|
||||
创建的字段列表
|
||||
"""
|
||||
db_objs = [
|
||||
DeviceTypeField(
|
||||
**field.model_dump(),
|
||||
device_type_id=device_type_id,
|
||||
created_by=creator_id
|
||||
)
|
||||
for field in fields_in
|
||||
]
|
||||
db.add_all(db_objs)
|
||||
db.commit()
|
||||
for obj in db_objs:
|
||||
db.refresh(obj)
|
||||
return db_objs
|
||||
|
||||
|
||||
# 创建全局实例
|
||||
device_type = DeviceTypeCRUD()
|
||||
device_type_field = DeviceTypeFieldCRUD()
|
||||
235
app/crud/file_management.py
Normal file
235
app/crud/file_management.py
Normal file
@@ -0,0 +1,235 @@
|
||||
"""
|
||||
文件管理CRUD操作
|
||||
"""
|
||||
from typing import List, Optional, Dict, Any, Tuple
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import and_, or_, func, desc
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from app.models.file_management import UploadedFile
|
||||
|
||||
|
||||
class CRUDUploadedFile:
|
||||
"""上传文件CRUD操作"""
|
||||
|
||||
def create(self, db: Session, *, obj_in: Dict[str, Any]) -> UploadedFile:
|
||||
"""创建文件记录"""
|
||||
db_obj = UploadedFile(**obj_in)
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
def get(self, db: Session, id: int) -> Optional[UploadedFile]:
|
||||
"""根据ID获取文件"""
|
||||
return db.query(UploadedFile).filter(
|
||||
and_(
|
||||
UploadedFile.id == id,
|
||||
UploadedFile.is_deleted == 0
|
||||
)
|
||||
).first()
|
||||
|
||||
def get_by_share_code(self, db: Session, share_code: str) -> Optional[UploadedFile]:
|
||||
"""根据分享码获取文件"""
|
||||
now = datetime.utcnow()
|
||||
return db.query(UploadedFile).filter(
|
||||
and_(
|
||||
UploadedFile.share_code == share_code,
|
||||
UploadedFile.is_deleted == 0,
|
||||
or_(
|
||||
UploadedFile.share_expire_time.is_(None),
|
||||
UploadedFile.share_expire_time > now
|
||||
)
|
||||
)
|
||||
).first()
|
||||
|
||||
def get_multi(
|
||||
self,
|
||||
db: Session,
|
||||
*,
|
||||
skip: int = 0,
|
||||
limit: int = 20,
|
||||
keyword: Optional[str] = None,
|
||||
file_type: Optional[str] = None,
|
||||
uploader_id: Optional[int] = None,
|
||||
start_date: Optional[str] = None,
|
||||
end_date: Optional[str] = None
|
||||
) -> Tuple[List[UploadedFile], int]:
|
||||
"""获取文件列表"""
|
||||
query = db.query(UploadedFile).filter(UploadedFile.is_deleted == 0)
|
||||
|
||||
# 关键词搜索
|
||||
if keyword:
|
||||
query = query.filter(
|
||||
or_(
|
||||
UploadedFile.original_name.like(f"%{keyword}%"),
|
||||
UploadedFile.file_name.like(f"%{keyword}%")
|
||||
)
|
||||
)
|
||||
|
||||
# 文件类型筛选
|
||||
if file_type:
|
||||
query = query.filter(UploadedFile.file_type == file_type)
|
||||
|
||||
# 上传者筛选
|
||||
if uploader_id:
|
||||
query = query.filter(UploadedFile.uploader_id == uploader_id)
|
||||
|
||||
# 日期范围筛选
|
||||
if start_date:
|
||||
start = datetime.strptime(start_date, "%Y-%m-%d")
|
||||
query = query.filter(UploadedFile.upload_time >= start)
|
||||
|
||||
if end_date:
|
||||
end = datetime.strptime(end_date, "%Y-%m-%d") + timedelta(days=1)
|
||||
query = query.filter(UploadedFile.upload_time < end)
|
||||
|
||||
# 获取总数
|
||||
total = query.count()
|
||||
|
||||
# 分页
|
||||
items = query.order_by(desc(UploadedFile.upload_time)).offset(skip).limit(limit).all()
|
||||
|
||||
return items, total
|
||||
|
||||
def update(self, db: Session, *, db_obj: UploadedFile, obj_in: Dict[str, Any]) -> UploadedFile:
|
||||
"""更新文件记录"""
|
||||
for field, value in obj_in.items():
|
||||
setattr(db_obj, field, value)
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
def delete(self, db: Session, *, db_obj: UploadedFile, deleter_id: int) -> UploadedFile:
|
||||
"""软删除文件"""
|
||||
db_obj.is_deleted = 1
|
||||
db_obj.deleted_at = datetime.utcnow()
|
||||
db_obj.deleted_by = deleter_id
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
def delete_batch(self, db: Session, *, file_ids: List[int], deleter_id: int) -> int:
|
||||
"""批量删除文件"""
|
||||
now = datetime.utcnow()
|
||||
count = db.query(UploadedFile).filter(
|
||||
and_(
|
||||
UploadedFile.id.in_(file_ids),
|
||||
UploadedFile.is_deleted == 0
|
||||
)
|
||||
).update({
|
||||
"is_deleted": 1,
|
||||
"deleted_at": now,
|
||||
"deleted_by": deleter_id
|
||||
}, synchronize_session=False)
|
||||
db.commit()
|
||||
return count
|
||||
|
||||
def increment_download_count(self, db: Session, *, file_id: int) -> int:
|
||||
"""增加下载次数"""
|
||||
file_obj = self.get(db, file_id)
|
||||
if file_obj:
|
||||
file_obj.download_count = (file_obj.download_count or 0) + 1
|
||||
db.add(file_obj)
|
||||
db.commit()
|
||||
return file_obj.download_count
|
||||
return 0
|
||||
|
||||
def generate_share_code(self, db: Session, *, file_id: int, expire_days: int = 7) -> str:
|
||||
"""生成分享码"""
|
||||
import secrets
|
||||
import string
|
||||
|
||||
file_obj = self.get(db, file_id)
|
||||
if not file_obj:
|
||||
return None
|
||||
|
||||
# 生成随机分享码
|
||||
alphabet = string.ascii_uppercase + string.ascii_lowercase + string.digits
|
||||
share_code = ''.join(secrets.choice(alphabet) for _ in range(16))
|
||||
|
||||
# 设置过期时间
|
||||
expire_time = datetime.utcnow() + timedelta(days=expire_days)
|
||||
|
||||
# 更新文件记录
|
||||
self.update(db, db_obj=file_obj, obj_in={
|
||||
"share_code": share_code,
|
||||
"share_expire_time": expire_time
|
||||
})
|
||||
|
||||
return share_code
|
||||
|
||||
def get_statistics(
|
||||
self,
|
||||
db: Session,
|
||||
*,
|
||||
uploader_id: Optional[int] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""获取文件统计信息"""
|
||||
# 基础查询
|
||||
query = db.query(UploadedFile).filter(UploadedFile.is_deleted == 0)
|
||||
|
||||
if uploader_id:
|
||||
query = query.filter(UploadedFile.uploader_id == uploader_id)
|
||||
|
||||
# 总文件数和总大小
|
||||
total_stats = query.with_entities(
|
||||
func.count(UploadedFile.id).label('count'),
|
||||
func.sum(UploadedFile.file_size).label('size')
|
||||
).first()
|
||||
|
||||
# 文件类型分布
|
||||
type_dist = query.with_entities(
|
||||
UploadedFile.file_type,
|
||||
func.count(UploadedFile.id).label('count')
|
||||
).group_by(UploadedFile.file_type).all()
|
||||
|
||||
type_distribution = {file_type: count for file_type, count in type_dist}
|
||||
|
||||
# 今日上传数
|
||||
today_start = datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
upload_today = query.filter(UploadedFile.upload_time >= today_start).count()
|
||||
|
||||
# 本周上传数
|
||||
week_start = today_start - timedelta(days=today_start.weekday())
|
||||
upload_this_week = query.filter(UploadedFile.upload_time >= week_start).count()
|
||||
|
||||
# 本月上传数
|
||||
month_start = today_start.replace(day=1)
|
||||
upload_this_month = query.filter(UploadedFile.upload_time >= month_start).count()
|
||||
|
||||
# 上传排行
|
||||
uploader_ranking = query.with_entities(
|
||||
UploadedFile.uploader_id,
|
||||
func.count(UploadedFile.id).label('count')
|
||||
).group_by(UploadedFile.uploader_id).order_by(desc('count')).limit(10).all()
|
||||
|
||||
# 转换为人类可读的文件大小
|
||||
total_size = total_stats.size or 0
|
||||
total_size_human = self._format_size(total_size)
|
||||
|
||||
return {
|
||||
"total_files": total_stats.count or 0,
|
||||
"total_size": total_size,
|
||||
"total_size_human": total_size_human,
|
||||
"type_distribution": type_distribution,
|
||||
"upload_today": upload_today,
|
||||
"upload_this_week": upload_this_week,
|
||||
"upload_this_month": upload_this_month,
|
||||
"top_uploaders": [{"uploader_id": uid, "count": count} for uid, count in uploader_ranking]
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _format_size(size_bytes: int) -> str:
|
||||
"""格式化文件大小"""
|
||||
for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
|
||||
if size_bytes < 1024.0:
|
||||
return f"{size_bytes:.2f} {unit}"
|
||||
size_bytes /= 1024.0
|
||||
return f"{size_bytes:.2f} PB"
|
||||
|
||||
|
||||
# 创建CRUD实例
|
||||
uploaded_file = CRUDUploadedFile()
|
||||
247
app/crud/maintenance.py
Normal file
247
app/crud/maintenance.py
Normal file
@@ -0,0 +1,247 @@
|
||||
"""
|
||||
维修管理相关CRUD操作
|
||||
"""
|
||||
from typing import List, Optional, Tuple
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import and_, or_, func
|
||||
from app.models.maintenance import MaintenanceRecord
|
||||
from app.schemas.maintenance import MaintenanceRecordCreate, MaintenanceRecordUpdate
|
||||
|
||||
|
||||
class MaintenanceRecordCRUD:
|
||||
"""维修记录CRUD操作"""
|
||||
|
||||
def get(self, db: Session, id: int) -> Optional[MaintenanceRecord]:
|
||||
"""根据ID获取维修记录"""
|
||||
return db.query(MaintenanceRecord).filter(
|
||||
MaintenanceRecord.id == id
|
||||
).first()
|
||||
|
||||
def get_by_code(self, db: Session, record_code: str) -> Optional[MaintenanceRecord]:
|
||||
"""根据单号获取维修记录"""
|
||||
return db.query(MaintenanceRecord).filter(
|
||||
MaintenanceRecord.record_code == record_code
|
||||
).first()
|
||||
|
||||
def get_multi(
|
||||
self,
|
||||
db: Session,
|
||||
skip: int = 0,
|
||||
limit: int = 20,
|
||||
asset_id: Optional[int] = None,
|
||||
status: Optional[str] = None,
|
||||
fault_type: Optional[str] = None,
|
||||
priority: Optional[str] = None,
|
||||
maintenance_type: Optional[str] = None,
|
||||
keyword: Optional[str] = None
|
||||
) -> Tuple[List[MaintenanceRecord], int]:
|
||||
"""获取维修记录列表"""
|
||||
query = db.query(MaintenanceRecord)
|
||||
|
||||
# 筛选条件
|
||||
if asset_id:
|
||||
query = query.filter(MaintenanceRecord.asset_id == asset_id)
|
||||
if status:
|
||||
query = query.filter(MaintenanceRecord.status == status)
|
||||
if fault_type:
|
||||
query = query.filter(MaintenanceRecord.fault_type == fault_type)
|
||||
if priority:
|
||||
query = query.filter(MaintenanceRecord.priority == priority)
|
||||
if maintenance_type:
|
||||
query = query.filter(MaintenanceRecord.maintenance_type == maintenance_type)
|
||||
if keyword:
|
||||
query = query.filter(
|
||||
or_(
|
||||
MaintenanceRecord.record_code.like(f"%{keyword}%"),
|
||||
MaintenanceRecord.asset_code.like(f"%{keyword}%"),
|
||||
MaintenanceRecord.fault_description.like(f"%{keyword}%")
|
||||
)
|
||||
)
|
||||
|
||||
# 排序
|
||||
query = query.order_by(MaintenanceRecord.report_time.desc())
|
||||
|
||||
# 总数
|
||||
total = query.count()
|
||||
|
||||
# 分页
|
||||
items = query.offset(skip).limit(limit).all()
|
||||
|
||||
return items, total
|
||||
|
||||
def create(
|
||||
self,
|
||||
db: Session,
|
||||
obj_in: MaintenanceRecordCreate,
|
||||
record_code: str,
|
||||
asset_code: str,
|
||||
report_user_id: int,
|
||||
creator_id: int
|
||||
) -> MaintenanceRecord:
|
||||
"""创建维修记录"""
|
||||
db_obj = MaintenanceRecord(
|
||||
record_code=record_code,
|
||||
asset_id=obj_in.asset_id,
|
||||
asset_code=asset_code,
|
||||
fault_description=obj_in.fault_description,
|
||||
fault_type=obj_in.fault_type,
|
||||
report_user_id=report_user_id,
|
||||
priority=obj_in.priority,
|
||||
maintenance_type=obj_in.maintenance_type,
|
||||
vendor_id=obj_in.vendor_id,
|
||||
maintenance_cost=obj_in.maintenance_cost,
|
||||
maintenance_result=obj_in.maintenance_result,
|
||||
replaced_parts=obj_in.replaced_parts,
|
||||
images=obj_in.images,
|
||||
remark=obj_in.remark,
|
||||
status="pending",
|
||||
created_by=creator_id
|
||||
)
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
def update(
|
||||
self,
|
||||
db: Session,
|
||||
db_obj: MaintenanceRecord,
|
||||
obj_in: MaintenanceRecordUpdate,
|
||||
updater_id: int
|
||||
) -> MaintenanceRecord:
|
||||
"""更新维修记录"""
|
||||
update_data = obj_in.model_dump(exclude_unset=True)
|
||||
for field, value in update_data.items():
|
||||
setattr(db_obj, field, value)
|
||||
db_obj.updated_by = updater_id
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
def start_maintenance(
|
||||
self,
|
||||
db: Session,
|
||||
db_obj: MaintenanceRecord,
|
||||
maintenance_type: str,
|
||||
maintenance_user_id: int,
|
||||
vendor_id: Optional[int] = None
|
||||
) -> MaintenanceRecord:
|
||||
"""开始维修"""
|
||||
from datetime import datetime
|
||||
|
||||
db_obj.status = "in_progress"
|
||||
db_obj.start_time = datetime.utcnow()
|
||||
db_obj.maintenance_type = maintenance_type
|
||||
db_obj.maintenance_user_id = maintenance_user_id
|
||||
if vendor_id:
|
||||
db_obj.vendor_id = vendor_id
|
||||
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
def complete_maintenance(
|
||||
self,
|
||||
db: Session,
|
||||
db_obj: MaintenanceRecord,
|
||||
maintenance_result: str,
|
||||
maintenance_cost: Optional[float] = None,
|
||||
replaced_parts: Optional[str] = None,
|
||||
images: Optional[str] = None
|
||||
) -> MaintenanceRecord:
|
||||
"""完成维修"""
|
||||
from datetime import datetime
|
||||
|
||||
db_obj.status = "completed"
|
||||
db_obj.complete_time = datetime.utcnow()
|
||||
db_obj.maintenance_result = maintenance_result
|
||||
if maintenance_cost is not None:
|
||||
db_obj.maintenance_cost = maintenance_cost
|
||||
if replaced_parts:
|
||||
db_obj.replaced_parts = replaced_parts
|
||||
if images:
|
||||
db_obj.images = images
|
||||
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
def cancel_maintenance(
|
||||
self,
|
||||
db: Session,
|
||||
db_obj: MaintenanceRecord
|
||||
) -> MaintenanceRecord:
|
||||
"""取消维修"""
|
||||
db_obj.status = "cancelled"
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
def delete(self, db: Session, id: int) -> bool:
|
||||
"""删除维修记录"""
|
||||
obj = self.get(db, id)
|
||||
if obj:
|
||||
db.delete(obj)
|
||||
db.commit()
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_statistics(
|
||||
self,
|
||||
db: Session,
|
||||
asset_id: Optional[int] = None
|
||||
) -> dict:
|
||||
"""获取维修统计信息"""
|
||||
from decimal import Decimal
|
||||
|
||||
query = db.query(MaintenanceRecord)
|
||||
|
||||
if asset_id:
|
||||
query = query.filter(MaintenanceRecord.asset_id == asset_id)
|
||||
|
||||
total = query.count()
|
||||
pending = query.filter(MaintenanceRecord.status == "pending").count()
|
||||
in_progress = query.filter(MaintenanceRecord.status == "in_progress").count()
|
||||
completed = query.filter(MaintenanceRecord.status == "completed").count()
|
||||
cancelled = query.filter(MaintenanceRecord.status == "cancelled").count()
|
||||
|
||||
# 总维修费用
|
||||
total_cost_result = query.filter(
|
||||
MaintenanceRecord.status == "completed",
|
||||
MaintenanceRecord.maintenance_cost.isnot(None)
|
||||
).with_entities(
|
||||
func.sum(MaintenanceRecord.maintenance_cost)
|
||||
).first()
|
||||
|
||||
total_cost = total_cost_result[0] if total_cost_result and total_cost_result[0] else Decimal("0.00")
|
||||
|
||||
return {
|
||||
"total": total,
|
||||
"pending": pending,
|
||||
"in_progress": in_progress,
|
||||
"completed": completed,
|
||||
"cancelled": cancelled,
|
||||
"total_cost": total_cost
|
||||
}
|
||||
|
||||
def get_by_asset(
|
||||
self,
|
||||
db: Session,
|
||||
asset_id: int,
|
||||
skip: int = 0,
|
||||
limit: int = 50
|
||||
) -> List[MaintenanceRecord]:
|
||||
"""根据资产ID获取维修记录"""
|
||||
return db.query(MaintenanceRecord).filter(
|
||||
MaintenanceRecord.asset_id == asset_id
|
||||
).order_by(
|
||||
MaintenanceRecord.report_time.desc()
|
||||
).offset(skip).limit(limit).all()
|
||||
|
||||
|
||||
# 创建全局实例
|
||||
maintenance_record = MaintenanceRecordCRUD()
|
||||
403
app/crud/notification.py
Normal file
403
app/crud/notification.py
Normal file
@@ -0,0 +1,403 @@
|
||||
"""
|
||||
消息通知CRUD操作
|
||||
"""
|
||||
from typing import Optional, List, Dict, Any
|
||||
from datetime import datetime
|
||||
from sqlalchemy import select, and_, or_, func, desc, update
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from app.models.notification import Notification, NotificationTemplate
|
||||
|
||||
|
||||
class NotificationCRUD:
|
||||
"""消息通知CRUD类"""
|
||||
|
||||
async def get(self, db: AsyncSession, notification_id: int) -> Optional[Notification]:
|
||||
"""
|
||||
根据ID获取消息通知
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
notification_id: 通知ID
|
||||
|
||||
Returns:
|
||||
Notification对象或None
|
||||
"""
|
||||
result = await db.execute(
|
||||
select(Notification).where(Notification.id == notification_id)
|
||||
)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
async def get_multi(
|
||||
self,
|
||||
db: AsyncSession,
|
||||
*,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
recipient_id: Optional[int] = None,
|
||||
notification_type: Optional[str] = None,
|
||||
priority: Optional[str] = None,
|
||||
is_read: Optional[bool] = None,
|
||||
start_time: Optional[datetime] = None,
|
||||
end_time: Optional[datetime] = None,
|
||||
keyword: Optional[str] = None
|
||||
) -> tuple[List[Notification], int]:
|
||||
"""
|
||||
获取消息通知列表
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
skip: 跳过条数
|
||||
limit: 返回条数
|
||||
recipient_id: 接收人ID
|
||||
notification_type: 通知类型
|
||||
priority: 优先级
|
||||
is_read: 是否已读
|
||||
start_time: 开始时间
|
||||
end_time: 结束时间
|
||||
keyword: 关键词
|
||||
|
||||
Returns:
|
||||
(通知列表, 总数)
|
||||
"""
|
||||
# 构建查询条件
|
||||
conditions = []
|
||||
|
||||
if recipient_id:
|
||||
conditions.append(Notification.recipient_id == recipient_id)
|
||||
|
||||
if notification_type:
|
||||
conditions.append(Notification.notification_type == notification_type)
|
||||
|
||||
if priority:
|
||||
conditions.append(Notification.priority == priority)
|
||||
|
||||
if is_read is not None:
|
||||
conditions.append(Notification.is_read == is_read)
|
||||
|
||||
if start_time:
|
||||
conditions.append(Notification.created_at >= start_time)
|
||||
|
||||
if end_time:
|
||||
conditions.append(Notification.created_at <= end_time)
|
||||
|
||||
if keyword:
|
||||
conditions.append(
|
||||
or_(
|
||||
Notification.title.ilike(f"%{keyword}%"),
|
||||
Notification.content.ilike(f"%{keyword}%")
|
||||
)
|
||||
)
|
||||
|
||||
# 查询总数
|
||||
count_query = select(func.count(Notification.id))
|
||||
if conditions:
|
||||
count_query = count_query.where(and_(*conditions))
|
||||
total_result = await db.execute(count_query)
|
||||
total = total_result.scalar() or 0
|
||||
|
||||
# 查询数据
|
||||
query = select(Notification)
|
||||
if conditions:
|
||||
query = query.where(and_(*conditions))
|
||||
query = query.order_by(
|
||||
Notification.is_read.asc(), # 未读优先
|
||||
desc(Notification.created_at) # 按时间倒序
|
||||
)
|
||||
query = query.offset(skip).limit(limit)
|
||||
|
||||
result = await db.execute(query)
|
||||
items = result.scalars().all()
|
||||
|
||||
return list(items), total
|
||||
|
||||
async def create(
|
||||
self,
|
||||
db: AsyncSession,
|
||||
*,
|
||||
obj_in: Dict[str, Any]
|
||||
) -> Notification:
|
||||
"""
|
||||
创建消息通知
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
obj_in: 创建数据
|
||||
|
||||
Returns:
|
||||
Notification对象
|
||||
"""
|
||||
db_obj = Notification(**obj_in)
|
||||
db.add(db_obj)
|
||||
await db.flush()
|
||||
await db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
async def batch_create(
|
||||
self,
|
||||
db: AsyncSession,
|
||||
*,
|
||||
recipient_ids: List[int],
|
||||
notification_data: Dict[str, Any]
|
||||
) -> List[Notification]:
|
||||
"""
|
||||
批量创建消息通知
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
recipient_ids: 接收人ID列表
|
||||
notification_data: 通知数据
|
||||
|
||||
Returns:
|
||||
Notification对象列表
|
||||
"""
|
||||
notifications = []
|
||||
for recipient_id in recipient_ids:
|
||||
obj_data = notification_data.copy()
|
||||
obj_data["recipient_id"] = recipient_id
|
||||
db_obj = Notification(**obj_data)
|
||||
db.add(db_obj)
|
||||
notifications.append(db_obj)
|
||||
|
||||
await db.flush()
|
||||
return notifications
|
||||
|
||||
async def update(
|
||||
self,
|
||||
db: AsyncSession,
|
||||
*,
|
||||
db_obj: Notification,
|
||||
obj_in: Dict[str, Any]
|
||||
) -> Notification:
|
||||
"""
|
||||
更新消息通知
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
db_obj: 数据库对象
|
||||
obj_in: 更新数据
|
||||
|
||||
Returns:
|
||||
Notification对象
|
||||
"""
|
||||
for field, value in obj_in.items():
|
||||
if hasattr(db_obj, field):
|
||||
setattr(db_obj, field, value)
|
||||
|
||||
await db.flush()
|
||||
await db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
async def mark_as_read(
|
||||
self,
|
||||
db: AsyncSession,
|
||||
*,
|
||||
notification_id: int,
|
||||
read_at: Optional[datetime] = None
|
||||
) -> Optional[Notification]:
|
||||
"""
|
||||
标记为已读
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
notification_id: 通知ID
|
||||
read_at: 已读时间
|
||||
|
||||
Returns:
|
||||
Notification对象或None
|
||||
"""
|
||||
db_obj = await self.get(db, notification_id)
|
||||
if not db_obj:
|
||||
return None
|
||||
|
||||
if not db_obj.is_read:
|
||||
db_obj.is_read = True
|
||||
db_obj.read_at = read_at or datetime.utcnow()
|
||||
await db.flush()
|
||||
|
||||
return db_obj
|
||||
|
||||
async def mark_all_as_read(
|
||||
self,
|
||||
db: AsyncSession,
|
||||
*,
|
||||
recipient_id: int,
|
||||
read_at: Optional[datetime] = None
|
||||
) -> int:
|
||||
"""
|
||||
标记所有未读为已读
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
recipient_id: 接收人ID
|
||||
read_at: 已读时间
|
||||
|
||||
Returns:
|
||||
更新数量
|
||||
"""
|
||||
stmt = (
|
||||
update(Notification)
|
||||
.where(
|
||||
and_(
|
||||
Notification.recipient_id == recipient_id,
|
||||
Notification.is_read == False
|
||||
)
|
||||
)
|
||||
.values(
|
||||
is_read=True,
|
||||
read_at=read_at or datetime.utcnow()
|
||||
)
|
||||
)
|
||||
result = await db.execute(stmt)
|
||||
await db.flush()
|
||||
return result.rowcount
|
||||
|
||||
async def delete(self, db: AsyncSession, *, notification_id: int) -> Optional[Notification]:
|
||||
"""
|
||||
删除消息通知
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
notification_id: 通知ID
|
||||
|
||||
Returns:
|
||||
删除的Notification对象或None
|
||||
"""
|
||||
obj = await self.get(db, notification_id)
|
||||
if obj:
|
||||
await db.delete(obj)
|
||||
await db.flush()
|
||||
return obj
|
||||
|
||||
async def batch_delete(
|
||||
self,
|
||||
db: AsyncSession,
|
||||
*,
|
||||
notification_ids: List[int]
|
||||
) -> int:
|
||||
"""
|
||||
批量删除通知
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
notification_ids: 通知ID列表
|
||||
|
||||
Returns:
|
||||
删除数量
|
||||
"""
|
||||
from sqlalchemy import delete
|
||||
|
||||
stmt = delete(Notification).where(Notification.id.in_(notification_ids))
|
||||
result = await db.execute(stmt)
|
||||
await db.flush()
|
||||
return result.rowcount
|
||||
|
||||
async def get_unread_count(
|
||||
self,
|
||||
db: AsyncSession,
|
||||
recipient_id: int
|
||||
) -> int:
|
||||
"""
|
||||
获取未读通知数量
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
recipient_id: 接收人ID
|
||||
|
||||
Returns:
|
||||
未读数量
|
||||
"""
|
||||
result = await db.execute(
|
||||
select(func.count(Notification.id)).where(
|
||||
and_(
|
||||
Notification.recipient_id == recipient_id,
|
||||
Notification.is_read == False
|
||||
)
|
||||
)
|
||||
)
|
||||
return result.scalar() or 0
|
||||
|
||||
async def get_statistics(
|
||||
self,
|
||||
db: AsyncSession,
|
||||
recipient_id: int
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
获取通知统计信息
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
recipient_id: 接收人ID
|
||||
|
||||
Returns:
|
||||
统计信息
|
||||
"""
|
||||
# 总数
|
||||
total_result = await db.execute(
|
||||
select(func.count(Notification.id)).where(Notification.recipient_id == recipient_id)
|
||||
)
|
||||
total_count = total_result.scalar() or 0
|
||||
|
||||
# 未读数
|
||||
unread_result = await db.execute(
|
||||
select(func.count(Notification.id)).where(
|
||||
and_(
|
||||
Notification.recipient_id == recipient_id,
|
||||
Notification.is_read == False
|
||||
)
|
||||
)
|
||||
)
|
||||
unread_count = unread_result.scalar() or 0
|
||||
|
||||
# 已读数
|
||||
read_count = total_count - unread_count
|
||||
|
||||
# 高优先级数
|
||||
high_priority_result = await db.execute(
|
||||
select(func.count(Notification.id)).where(
|
||||
and_(
|
||||
Notification.recipient_id == recipient_id,
|
||||
Notification.priority.in_(["high", "urgent"]),
|
||||
Notification.is_read == False
|
||||
)
|
||||
)
|
||||
)
|
||||
high_priority_count = high_priority_result.scalar() or 0
|
||||
|
||||
# 紧急通知数
|
||||
urgent_result = await db.execute(
|
||||
select(func.count(Notification.id)).where(
|
||||
and_(
|
||||
Notification.recipient_id == recipient_id,
|
||||
Notification.priority == "urgent",
|
||||
Notification.is_read == False
|
||||
)
|
||||
)
|
||||
)
|
||||
urgent_count = urgent_result.scalar() or 0
|
||||
|
||||
# 类型分布
|
||||
type_result = await db.execute(
|
||||
select(
|
||||
Notification.notification_type,
|
||||
func.count(Notification.id).label('count')
|
||||
)
|
||||
.where(Notification.recipient_id == recipient_id)
|
||||
.group_by(Notification.notification_type)
|
||||
)
|
||||
type_distribution = [
|
||||
{"type": row[0], "count": row[1]}
|
||||
for row in type_result
|
||||
]
|
||||
|
||||
return {
|
||||
"total_count": total_count,
|
||||
"unread_count": unread_count,
|
||||
"read_count": read_count,
|
||||
"high_priority_count": high_priority_count,
|
||||
"urgent_count": urgent_count,
|
||||
"type_distribution": type_distribution,
|
||||
}
|
||||
|
||||
|
||||
# 创建全局实例
|
||||
notification_crud = NotificationCRUD()
|
||||
311
app/crud/operation_log.py
Normal file
311
app/crud/operation_log.py
Normal file
@@ -0,0 +1,311 @@
|
||||
"""
|
||||
操作日志CRUD操作
|
||||
"""
|
||||
from typing import Optional, List, Dict, Any
|
||||
from datetime import datetime, timedelta
|
||||
from sqlalchemy import select, and_, or_, func, desc
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from app.models.operation_log import OperationLog
|
||||
|
||||
|
||||
class OperationLogCRUD:
|
||||
"""操作日志CRUD类"""
|
||||
|
||||
async def get(self, db: AsyncSession, log_id: int) -> Optional[OperationLog]:
|
||||
"""
|
||||
根据ID获取操作日志
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
log_id: 日志ID
|
||||
|
||||
Returns:
|
||||
OperationLog对象或None
|
||||
"""
|
||||
result = await db.execute(
|
||||
select(OperationLog).where(OperationLog.id == log_id)
|
||||
)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
async def get_multi(
|
||||
self,
|
||||
db: AsyncSession,
|
||||
*,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
operator_id: Optional[int] = None,
|
||||
operator_name: Optional[str] = None,
|
||||
module: Optional[str] = None,
|
||||
operation_type: Optional[str] = None,
|
||||
result: Optional[str] = None,
|
||||
start_time: Optional[datetime] = None,
|
||||
end_time: Optional[datetime] = None,
|
||||
keyword: Optional[str] = None
|
||||
) -> tuple[List[OperationLog], int]:
|
||||
"""
|
||||
获取操作日志列表
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
skip: 跳过条数
|
||||
limit: 返回条数
|
||||
operator_id: 操作人ID
|
||||
operator_name: 操作人姓名
|
||||
module: 模块名称
|
||||
operation_type: 操作类型
|
||||
result: 操作结果
|
||||
start_time: 开始时间
|
||||
end_time: 结束时间
|
||||
keyword: 关键词
|
||||
|
||||
Returns:
|
||||
(日志列表, 总数)
|
||||
"""
|
||||
# 构建查询条件
|
||||
conditions = []
|
||||
|
||||
if operator_id:
|
||||
conditions.append(OperationLog.operator_id == operator_id)
|
||||
|
||||
if operator_name:
|
||||
conditions.append(OperationLog.operator_name.ilike(f"%{operator_name}%"))
|
||||
|
||||
if module:
|
||||
conditions.append(OperationLog.module == module)
|
||||
|
||||
if operation_type:
|
||||
conditions.append(OperationLog.operation_type == operation_type)
|
||||
|
||||
if result:
|
||||
conditions.append(OperationLog.result == result)
|
||||
|
||||
if start_time:
|
||||
conditions.append(OperationLog.created_at >= start_time)
|
||||
|
||||
if end_time:
|
||||
conditions.append(OperationLog.created_at <= end_time)
|
||||
|
||||
if keyword:
|
||||
conditions.append(
|
||||
or_(
|
||||
OperationLog.url.ilike(f"%{keyword}%"),
|
||||
OperationLog.params.ilike(f"%{keyword}%"),
|
||||
OperationLog.error_msg.ilike(f"%{keyword}%")
|
||||
)
|
||||
)
|
||||
|
||||
# 查询总数
|
||||
count_query = select(func.count(OperationLog.id))
|
||||
if conditions:
|
||||
count_query = count_query.where(and_(*conditions))
|
||||
total_result = await db.execute(count_query)
|
||||
total = total_result.scalar() or 0
|
||||
|
||||
# 查询数据
|
||||
query = select(OperationLog)
|
||||
if conditions:
|
||||
query = query.where(and_(*conditions))
|
||||
query = query.order_by(desc(OperationLog.created_at))
|
||||
query = query.offset(skip).limit(limit)
|
||||
|
||||
result = await db.execute(query)
|
||||
items = result.scalars().all()
|
||||
|
||||
return list(items), total
|
||||
|
||||
async def create(
|
||||
self,
|
||||
db: AsyncSession,
|
||||
*,
|
||||
obj_in: Dict[str, Any]
|
||||
) -> OperationLog:
|
||||
"""
|
||||
创建操作日志
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
obj_in: 创建数据
|
||||
|
||||
Returns:
|
||||
OperationLog对象
|
||||
"""
|
||||
db_obj = OperationLog(**obj_in)
|
||||
db.add(db_obj)
|
||||
await db.flush()
|
||||
await db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
async def get_statistics(
|
||||
self,
|
||||
db: AsyncSession,
|
||||
*,
|
||||
start_time: Optional[datetime] = None,
|
||||
end_time: Optional[datetime] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
获取操作日志统计信息
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
start_time: 开始时间
|
||||
end_time: 结束时间
|
||||
|
||||
Returns:
|
||||
统计信息
|
||||
"""
|
||||
# 构建时间条件
|
||||
conditions = []
|
||||
if start_time:
|
||||
conditions.append(OperationLog.created_at >= start_time)
|
||||
if end_time:
|
||||
conditions.append(OperationLog.created_at <= end_time)
|
||||
|
||||
where_clause = and_(*conditions) if conditions else None
|
||||
|
||||
# 总数
|
||||
total_query = select(func.count(OperationLog.id))
|
||||
if where_clause:
|
||||
total_query = total_query.where(where_clause)
|
||||
total_result = await db.execute(total_query)
|
||||
total_count = total_result.scalar() or 0
|
||||
|
||||
# 成功数
|
||||
success_query = select(func.count(OperationLog.id)).where(OperationLog.result == "success")
|
||||
if where_clause:
|
||||
success_query = success_query.where(where_clause)
|
||||
success_result = await db.execute(success_query)
|
||||
success_count = success_result.scalar() or 0
|
||||
|
||||
# 失败数
|
||||
failed_count = total_count - success_count
|
||||
|
||||
# 今日操作数
|
||||
today = datetime.utcnow().date()
|
||||
today_start = datetime.combine(today, datetime.min.time())
|
||||
today_query = select(func.count(OperationLog.id)).where(OperationLog.created_at >= today_start)
|
||||
today_result = await db.execute(today_query)
|
||||
today_count = today_result.scalar() or 0
|
||||
|
||||
# 模块分布
|
||||
module_query = select(
|
||||
OperationLog.module,
|
||||
func.count(OperationLog.id).label('count')
|
||||
).group_by(OperationLog.module)
|
||||
if where_clause:
|
||||
module_query = module_query.where(where_clause)
|
||||
module_result = await db.execute(module_query)
|
||||
module_distribution = [
|
||||
{"module": row[0], "count": row[1]}
|
||||
for row in module_result
|
||||
]
|
||||
|
||||
# 操作类型分布
|
||||
operation_query = select(
|
||||
OperationLog.operation_type,
|
||||
func.count(OperationLog.id).label('count')
|
||||
).group_by(OperationLog.operation_type)
|
||||
if where_clause:
|
||||
operation_query = operation_query.where(where_clause)
|
||||
operation_result = await db.execute(operation_query)
|
||||
operation_distribution = [
|
||||
{"operation_type": row[0], "count": row[1]}
|
||||
for row in operation_result
|
||||
]
|
||||
|
||||
return {
|
||||
"total_count": total_count,
|
||||
"success_count": success_count,
|
||||
"failed_count": failed_count,
|
||||
"today_count": today_count,
|
||||
"module_distribution": module_distribution,
|
||||
"operation_distribution": operation_distribution,
|
||||
}
|
||||
|
||||
async def delete_old_logs(
|
||||
self,
|
||||
db: AsyncSession,
|
||||
*,
|
||||
days: int = 90
|
||||
) -> int:
|
||||
"""
|
||||
删除旧日志
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
days: 保留天数
|
||||
|
||||
Returns:
|
||||
删除的日志数量
|
||||
"""
|
||||
cutoff_date = datetime.utcnow() - timedelta(days=days)
|
||||
|
||||
# 查询要删除的日志
|
||||
result = await db.execute(
|
||||
select(OperationLog.id).where(OperationLog.created_at < cutoff_date)
|
||||
)
|
||||
ids_to_delete = [row[0] for row in result]
|
||||
|
||||
if not ids_to_delete:
|
||||
return 0
|
||||
|
||||
# 批量删除
|
||||
from sqlalchemy import delete
|
||||
delete_stmt = delete(OperationLog).where(OperationLog.id.in_(ids_to_delete))
|
||||
await db.execute(delete_stmt)
|
||||
|
||||
return len(ids_to_delete)
|
||||
|
||||
async def get_operator_top(
|
||||
self,
|
||||
db: AsyncSession,
|
||||
*,
|
||||
limit: int = 10,
|
||||
start_time: Optional[datetime] = None,
|
||||
end_time: Optional[datetime] = None
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
获取操作排行榜
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
limit: 返回条数
|
||||
start_time: 开始时间
|
||||
end_time: 结束时间
|
||||
|
||||
Returns:
|
||||
操作排行列表
|
||||
"""
|
||||
# 构建时间条件
|
||||
conditions = []
|
||||
if start_time:
|
||||
conditions.append(OperationLog.created_at >= start_time)
|
||||
if end_time:
|
||||
conditions.append(OperationLog.created_at <= end_time)
|
||||
|
||||
query = select(
|
||||
OperationLog.operator_id,
|
||||
OperationLog.operator_name,
|
||||
func.count(OperationLog.id).label('count')
|
||||
).group_by(
|
||||
OperationLog.operator_id,
|
||||
OperationLog.operator_name
|
||||
).order_by(
|
||||
desc('count')
|
||||
).limit(limit)
|
||||
|
||||
if conditions:
|
||||
query = query.where(and_(*conditions))
|
||||
|
||||
result = await db.execute(query)
|
||||
return [
|
||||
{
|
||||
"operator_id": row[0],
|
||||
"operator_name": row[1],
|
||||
"count": row[2]
|
||||
}
|
||||
for row in result
|
||||
]
|
||||
|
||||
|
||||
# 创建全局实例
|
||||
operation_log_crud = OperationLogCRUD()
|
||||
351
app/crud/organization.py
Normal file
351
app/crud/organization.py
Normal file
@@ -0,0 +1,351 @@
|
||||
"""
|
||||
机构网点CRUD操作
|
||||
"""
|
||||
from typing import List, Optional, Tuple
|
||||
from sqlalchemy import select, and_, or_, func
|
||||
from sqlalchemy.orm import Session
|
||||
from app.models.organization import Organization
|
||||
from app.schemas.organization import OrganizationCreate, OrganizationUpdate
|
||||
|
||||
|
||||
class OrganizationCRUD:
|
||||
"""机构网点CRUD操作类"""
|
||||
|
||||
def get(self, db: Session, id: int) -> Optional[Organization]:
|
||||
"""
|
||||
根据ID获取机构
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
id: 机构ID
|
||||
|
||||
Returns:
|
||||
Organization对象或None
|
||||
"""
|
||||
return db.query(Organization).filter(
|
||||
and_(
|
||||
Organization.id == id,
|
||||
Organization.deleted_at.is_(None)
|
||||
)
|
||||
).first()
|
||||
|
||||
def get_by_code(self, db: Session, code: str) -> Optional[Organization]:
|
||||
"""
|
||||
根据代码获取机构
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
code: 机构代码
|
||||
|
||||
Returns:
|
||||
Organization对象或None
|
||||
"""
|
||||
return db.query(Organization).filter(
|
||||
and_(
|
||||
Organization.org_code == code,
|
||||
Organization.deleted_at.is_(None)
|
||||
)
|
||||
).first()
|
||||
|
||||
def get_multi(
|
||||
self,
|
||||
db: Session,
|
||||
skip: int = 0,
|
||||
limit: int = 20,
|
||||
org_type: Optional[str] = None,
|
||||
status: Optional[str] = None,
|
||||
keyword: Optional[str] = None
|
||||
) -> Tuple[List[Organization], int]:
|
||||
"""
|
||||
获取机构列表
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
skip: 跳过条数
|
||||
limit: 返回条数
|
||||
org_type: 机构类型筛选
|
||||
status: 状态筛选
|
||||
keyword: 搜索关键词
|
||||
|
||||
Returns:
|
||||
(机构列表, 总数)
|
||||
"""
|
||||
query = db.query(Organization).filter(Organization.deleted_at.is_(None))
|
||||
|
||||
# 筛选条件
|
||||
if org_type:
|
||||
query = query.filter(Organization.org_type == org_type)
|
||||
if status:
|
||||
query = query.filter(Organization.status == status)
|
||||
if keyword:
|
||||
query = query.filter(
|
||||
or_(
|
||||
Organization.org_code.ilike(f"%{keyword}%"),
|
||||
Organization.org_name.ilike(f"%{keyword}%")
|
||||
)
|
||||
)
|
||||
|
||||
# 排序
|
||||
query = query.order_by(Organization.tree_level.asc(), Organization.sort_order.asc(), Organization.id.asc())
|
||||
|
||||
# 总数
|
||||
total = query.count()
|
||||
|
||||
# 分页
|
||||
items = query.offset(skip).limit(limit).all()
|
||||
|
||||
return items, total
|
||||
|
||||
def get_tree(self, db: Session, status: Optional[str] = None) -> List[Organization]:
|
||||
"""
|
||||
获取机构树
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
status: 状态筛选
|
||||
|
||||
Returns:
|
||||
机构树列表
|
||||
"""
|
||||
query = db.query(Organization).filter(Organization.deleted_at.is_(None))
|
||||
|
||||
if status:
|
||||
query = query.filter(Organization.status == status)
|
||||
|
||||
# 获取所有机构
|
||||
all_orgs = query.order_by(Organization.tree_level.asc(), Organization.sort_order.asc()).all()
|
||||
|
||||
# 构建树形结构
|
||||
org_map = {org.id: org for org in all_orgs}
|
||||
tree = []
|
||||
|
||||
for org in all_orgs:
|
||||
# 清空children列表
|
||||
org.children = []
|
||||
|
||||
if org.parent_id is None:
|
||||
# 根节点
|
||||
tree.append(org)
|
||||
else:
|
||||
# 添加到父节点的children
|
||||
parent = org_map.get(org.parent_id)
|
||||
if parent:
|
||||
if not hasattr(parent, 'children'):
|
||||
parent.children = []
|
||||
parent.children.append(org)
|
||||
|
||||
return tree
|
||||
|
||||
def get_children(self, db: Session, parent_id: int) -> List[Organization]:
|
||||
"""
|
||||
获取子机构列表(直接子节点)
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
parent_id: 父机构ID
|
||||
|
||||
Returns:
|
||||
子机构列表
|
||||
"""
|
||||
return db.query(Organization).filter(
|
||||
and_(
|
||||
Organization.parent_id == parent_id,
|
||||
Organization.deleted_at.is_(None)
|
||||
)
|
||||
).order_by(Organization.sort_order.asc(), Organization.id.asc()).all()
|
||||
|
||||
def get_all_children(self, db: Session, parent_id: int) -> List[Organization]:
|
||||
"""
|
||||
递归获取所有子机构(包括子节点的子节点)
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
parent_id: 父机构ID
|
||||
|
||||
Returns:
|
||||
所有子机构列表
|
||||
"""
|
||||
# 获取父节点的tree_path
|
||||
parent = self.get(db, parent_id)
|
||||
if not parent:
|
||||
return []
|
||||
|
||||
# 构建查询路径
|
||||
if parent.tree_path:
|
||||
search_path = f"{parent.tree_path}{parent.id}/"
|
||||
else:
|
||||
search_path = f"/{parent.id}/"
|
||||
|
||||
# 查询所有以该路径开头的机构
|
||||
return db.query(Organization).filter(
|
||||
and_(
|
||||
Organization.tree_path.like(f"{search_path}%"),
|
||||
Organization.deleted_at.is_(None)
|
||||
)
|
||||
).order_by(Organization.tree_level.asc(), Organization.sort_order.asc()).all()
|
||||
|
||||
def get_parents(self, db: Session, child_id: int) -> List[Organization]:
|
||||
"""
|
||||
递归获取所有父机构(从根到直接父节点)
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
child_id: 子机构ID
|
||||
|
||||
Returns:
|
||||
所有父机构列表(从根到父)
|
||||
"""
|
||||
child = self.get(db, child_id)
|
||||
if not child or not child.tree_path:
|
||||
return []
|
||||
|
||||
# 解析tree_path,提取所有ID
|
||||
path_ids = [int(id) for id in child.tree_path.split("/") if id]
|
||||
|
||||
if not path_ids:
|
||||
return []
|
||||
|
||||
# 查询所有父机构
|
||||
return db.query(Organization).filter(
|
||||
and_(
|
||||
Organization.id.in_(path_ids),
|
||||
Organization.deleted_at.is_(None)
|
||||
)
|
||||
).order_by(Organization.tree_level.asc()).all()
|
||||
|
||||
def create(
|
||||
self,
|
||||
db: Session,
|
||||
obj_in: OrganizationCreate,
|
||||
creator_id: Optional[int] = None
|
||||
) -> Organization:
|
||||
"""
|
||||
创建机构
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
obj_in: 创建数据
|
||||
creator_id: 创建人ID
|
||||
|
||||
Returns:
|
||||
创建的Organization对象
|
||||
"""
|
||||
# 检查代码是否已存在
|
||||
if self.get_by_code(db, obj_in.org_code):
|
||||
raise ValueError(f"机构代码 '{obj_in.org_code}' 已存在")
|
||||
|
||||
# 计算tree_path和tree_level
|
||||
tree_path = None
|
||||
tree_level = 0
|
||||
|
||||
if obj_in.parent_id:
|
||||
parent = self.get(db, obj_in.parent_id)
|
||||
if not parent:
|
||||
raise ValueError(f"父机构ID {obj_in.parent_id} 不存在")
|
||||
|
||||
# 构建tree_path
|
||||
if parent.tree_path:
|
||||
tree_path = f"{parent.tree_path}{parent.id}/"
|
||||
else:
|
||||
tree_path = f"/{parent.id}/"
|
||||
|
||||
tree_level = parent.tree_level + 1
|
||||
|
||||
db_obj = Organization(
|
||||
**obj_in.model_dump(),
|
||||
tree_path=tree_path,
|
||||
tree_level=tree_level,
|
||||
created_by=creator_id
|
||||
)
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
def update(
|
||||
self,
|
||||
db: Session,
|
||||
db_obj: Organization,
|
||||
obj_in: OrganizationUpdate,
|
||||
updater_id: Optional[int] = None
|
||||
) -> Organization:
|
||||
"""
|
||||
更新机构
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
db_obj: 数据库对象
|
||||
obj_in: 更新数据
|
||||
updater_id: 更新人ID
|
||||
|
||||
Returns:
|
||||
更新后的Organization对象
|
||||
"""
|
||||
obj_data = obj_in.model_dump(exclude_unset=True)
|
||||
|
||||
# 如果更新了parent_id,需要重新计算tree_path和tree_level
|
||||
if "parent_id" in obj_data:
|
||||
new_parent_id = obj_data["parent_id"]
|
||||
old_parent_id = db_obj.parent_id
|
||||
|
||||
if new_parent_id != old_parent_id:
|
||||
# 重新计算当前节点的路径
|
||||
if new_parent_id:
|
||||
new_parent = self.get(db, new_parent_id)
|
||||
if not new_parent:
|
||||
raise ValueError(f"父机构ID {new_parent_id} 不存在")
|
||||
|
||||
if new_parent.tree_path:
|
||||
db_obj.tree_path = f"{new_parent.tree_path}{new_parent.id}/"
|
||||
else:
|
||||
db_obj.tree_path = f"/{new_parent.id}/"
|
||||
|
||||
db_obj.tree_level = new_parent.tree_level + 1
|
||||
else:
|
||||
# 变为根节点
|
||||
db_obj.tree_path = None
|
||||
db_obj.tree_level = 0
|
||||
|
||||
# TODO: 需要递归更新所有子节点的tree_path和tree_level
|
||||
# 这里需要批量更新,暂时跳过
|
||||
|
||||
for field, value in obj_data.items():
|
||||
if field != "parent_id": # parent_id已经处理
|
||||
setattr(db_obj, field, value)
|
||||
|
||||
db_obj.updated_by = updater_id
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
def delete(self, db: Session, id: int, deleter_id: Optional[int] = None) -> bool:
|
||||
"""
|
||||
删除机构(软删除)
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
id: 机构ID
|
||||
deleter_id: 删除人ID
|
||||
|
||||
Returns:
|
||||
是否删除成功
|
||||
"""
|
||||
obj = self.get(db, id)
|
||||
if not obj:
|
||||
return False
|
||||
|
||||
# 检查是否有子机构
|
||||
children = self.get_children(db, id)
|
||||
if children:
|
||||
raise ValueError("该机构下存在子机构,无法删除")
|
||||
|
||||
obj.deleted_at = func.now()
|
||||
obj.deleted_by = deleter_id
|
||||
db.add(obj)
|
||||
db.commit()
|
||||
return True
|
||||
|
||||
|
||||
# 创建全局实例
|
||||
organization = OrganizationCRUD()
|
||||
314
app/crud/recovery.py
Normal file
314
app/crud/recovery.py
Normal file
@@ -0,0 +1,314 @@
|
||||
"""
|
||||
资产回收相关CRUD操作
|
||||
"""
|
||||
from typing import List, Optional, Tuple
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import and_, or_
|
||||
from app.models.recovery import AssetRecoveryOrder, AssetRecoveryItem
|
||||
from app.models.asset import Asset
|
||||
from app.schemas.recovery import AssetRecoveryOrderCreate, AssetRecoveryOrderUpdate
|
||||
|
||||
|
||||
class AssetRecoveryOrderCRUD:
|
||||
"""回收单CRUD操作"""
|
||||
|
||||
def get(self, db: Session, id: int) -> Optional[AssetRecoveryOrder]:
|
||||
"""根据ID获取回收单"""
|
||||
return db.query(AssetRecoveryOrder).filter(
|
||||
AssetRecoveryOrder.id == id
|
||||
).first()
|
||||
|
||||
def get_by_code(self, db: Session, order_code: str) -> Optional[AssetRecoveryOrder]:
|
||||
"""根据单号获取回收单"""
|
||||
return db.query(AssetRecoveryOrder).filter(
|
||||
AssetRecoveryOrder.order_code == order_code
|
||||
).first()
|
||||
|
||||
def get_multi(
|
||||
self,
|
||||
db: Session,
|
||||
skip: int = 0,
|
||||
limit: int = 20,
|
||||
recovery_type: Optional[str] = None,
|
||||
approval_status: Optional[str] = None,
|
||||
execute_status: Optional[str] = None,
|
||||
keyword: Optional[str] = None
|
||||
) -> Tuple[List[AssetRecoveryOrder], int]:
|
||||
"""获取回收单列表"""
|
||||
query = db.query(AssetRecoveryOrder)
|
||||
|
||||
# 筛选条件
|
||||
if recovery_type:
|
||||
query = query.filter(AssetRecoveryOrder.recovery_type == recovery_type)
|
||||
if approval_status:
|
||||
query = query.filter(AssetRecoveryOrder.approval_status == approval_status)
|
||||
if execute_status:
|
||||
query = query.filter(AssetRecoveryOrder.execute_status == execute_status)
|
||||
if keyword:
|
||||
query = query.filter(
|
||||
or_(
|
||||
AssetRecoveryOrder.order_code.like(f"%{keyword}%"),
|
||||
AssetRecoveryOrder.title.like(f"%{keyword}%")
|
||||
)
|
||||
)
|
||||
|
||||
# 排序
|
||||
query = query.order_by(AssetRecoveryOrder.created_at.desc())
|
||||
|
||||
# 总数
|
||||
total = query.count()
|
||||
|
||||
# 分页
|
||||
items = query.offset(skip).limit(limit).all()
|
||||
|
||||
return items, total
|
||||
|
||||
def create(
|
||||
self,
|
||||
db: Session,
|
||||
obj_in: AssetRecoveryOrderCreate,
|
||||
order_code: str,
|
||||
apply_user_id: int
|
||||
) -> AssetRecoveryOrder:
|
||||
"""创建回收单"""
|
||||
from datetime import datetime
|
||||
|
||||
# 创建回收单
|
||||
db_obj = AssetRecoveryOrder(
|
||||
order_code=order_code,
|
||||
recovery_type=obj_in.recovery_type,
|
||||
title=obj_in.title,
|
||||
asset_count=len(obj_in.asset_ids),
|
||||
apply_user_id=apply_user_id,
|
||||
apply_time=datetime.utcnow(),
|
||||
remark=obj_in.remark,
|
||||
approval_status="pending",
|
||||
execute_status="pending"
|
||||
)
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
|
||||
# 创建回收单明细
|
||||
self._create_items(
|
||||
db=db,
|
||||
order_id=db_obj.id,
|
||||
asset_ids=obj_in.asset_ids
|
||||
)
|
||||
|
||||
return db_obj
|
||||
|
||||
def update(
|
||||
self,
|
||||
db: Session,
|
||||
db_obj: AssetRecoveryOrder,
|
||||
obj_in: AssetRecoveryOrderUpdate
|
||||
) -> AssetRecoveryOrder:
|
||||
"""更新回收单"""
|
||||
update_data = obj_in.model_dump(exclude_unset=True)
|
||||
for field, value in update_data.items():
|
||||
setattr(db_obj, field, value)
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
def approve(
|
||||
self,
|
||||
db: Session,
|
||||
db_obj: AssetRecoveryOrder,
|
||||
approval_status: str,
|
||||
approval_user_id: int,
|
||||
approval_remark: Optional[str] = None
|
||||
) -> AssetRecoveryOrder:
|
||||
"""审批回收单"""
|
||||
from datetime import datetime
|
||||
|
||||
db_obj.approval_status = approval_status
|
||||
db_obj.approval_user_id = approval_user_id
|
||||
db_obj.approval_time = datetime.utcnow()
|
||||
db_obj.approval_remark = approval_remark
|
||||
|
||||
# 如果审批通过,自动设置为可执行状态
|
||||
if approval_status == "approved":
|
||||
db_obj.execute_status = "pending"
|
||||
elif approval_status == "rejected":
|
||||
db_obj.execute_status = "cancelled"
|
||||
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
def start(
|
||||
self,
|
||||
db: Session,
|
||||
db_obj: AssetRecoveryOrder,
|
||||
execute_user_id: int
|
||||
) -> AssetRecoveryOrder:
|
||||
"""开始回收"""
|
||||
from datetime import datetime
|
||||
|
||||
db_obj.execute_status = "executing"
|
||||
db_obj.execute_user_id = execute_user_id
|
||||
db_obj.execute_time = datetime.utcnow()
|
||||
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
def complete(
|
||||
self,
|
||||
db: Session,
|
||||
db_obj: AssetRecoveryOrder,
|
||||
execute_user_id: int
|
||||
) -> AssetRecoveryOrder:
|
||||
"""完成回收"""
|
||||
from datetime import datetime
|
||||
|
||||
db_obj.execute_status = "completed"
|
||||
db_obj.execute_user_id = execute_user_id
|
||||
db_obj.execute_time = datetime.utcnow()
|
||||
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
def cancel(self, db: Session, db_obj: AssetRecoveryOrder) -> AssetRecoveryOrder:
|
||||
"""取消回收单"""
|
||||
db_obj.approval_status = "cancelled"
|
||||
db_obj.execute_status = "cancelled"
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
def delete(self, db: Session, id: int) -> bool:
|
||||
"""删除回收单"""
|
||||
obj = self.get(db, id)
|
||||
if obj:
|
||||
db.delete(obj)
|
||||
db.commit()
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_statistics(
|
||||
self,
|
||||
db: Session
|
||||
) -> dict:
|
||||
"""获取回收单统计信息"""
|
||||
query = db.query(AssetRecoveryOrder)
|
||||
|
||||
total = query.count()
|
||||
pending = query.filter(AssetRecoveryOrder.approval_status == "pending").count()
|
||||
approved = query.filter(AssetRecoveryOrder.approval_status == "approved").count()
|
||||
rejected = query.filter(AssetRecoveryOrder.approval_status == "rejected").count()
|
||||
executing = query.filter(AssetRecoveryOrder.execute_status == "executing").count()
|
||||
completed = query.filter(AssetRecoveryOrder.execute_status == "completed").count()
|
||||
|
||||
return {
|
||||
"total": total,
|
||||
"pending": pending,
|
||||
"approved": approved,
|
||||
"rejected": rejected,
|
||||
"executing": executing,
|
||||
"completed": completed
|
||||
}
|
||||
|
||||
def _create_items(
|
||||
self,
|
||||
db: Session,
|
||||
order_id: int,
|
||||
asset_ids: List[int]
|
||||
):
|
||||
"""创建回收单明细"""
|
||||
# 查询资产信息
|
||||
assets = db.query(Asset).filter(Asset.id.in_(asset_ids)).all()
|
||||
|
||||
for asset in assets:
|
||||
item = AssetRecoveryItem(
|
||||
order_id=order_id,
|
||||
asset_id=asset.id,
|
||||
asset_code=asset.asset_code,
|
||||
recovery_status="pending"
|
||||
)
|
||||
db.add(item)
|
||||
|
||||
db.commit()
|
||||
|
||||
|
||||
class AssetRecoveryItemCRUD:
|
||||
"""回收单明细CRUD操作"""
|
||||
|
||||
def get_by_order(self, db: Session, order_id: int) -> List[AssetRecoveryItem]:
|
||||
"""根据回收单ID获取明细列表"""
|
||||
return db.query(AssetRecoveryItem).filter(
|
||||
AssetRecoveryItem.order_id == order_id
|
||||
).order_by(AssetRecoveryItem.id).all()
|
||||
|
||||
def get_multi(
|
||||
self,
|
||||
db: Session,
|
||||
skip: int = 0,
|
||||
limit: int = 20,
|
||||
order_id: Optional[int] = None,
|
||||
recovery_status: Optional[str] = None
|
||||
) -> Tuple[List[AssetRecoveryItem], int]:
|
||||
"""获取明细列表"""
|
||||
query = db.query(AssetRecoveryItem)
|
||||
|
||||
if order_id:
|
||||
query = query.filter(AssetRecoveryItem.order_id == order_id)
|
||||
if recovery_status:
|
||||
query = query.filter(AssetRecoveryItem.recovery_status == recovery_status)
|
||||
|
||||
total = query.count()
|
||||
items = query.offset(skip).limit(limit).all()
|
||||
|
||||
return items, total
|
||||
|
||||
def update_recovery_status(
|
||||
self,
|
||||
db: Session,
|
||||
item_id: int,
|
||||
recovery_status: str
|
||||
) -> AssetRecoveryItem:
|
||||
"""更新明细回收状态"""
|
||||
item = db.query(AssetRecoveryItem).filter(
|
||||
AssetRecoveryItem.id == item_id
|
||||
).first()
|
||||
|
||||
if item:
|
||||
item.recovery_status = recovery_status
|
||||
db.add(item)
|
||||
db.commit()
|
||||
db.refresh(item)
|
||||
|
||||
return item
|
||||
|
||||
def batch_update_recovery_status(
|
||||
self,
|
||||
db: Session,
|
||||
order_id: int,
|
||||
recovery_status: str
|
||||
):
|
||||
"""批量更新明细回收状态"""
|
||||
items = db.query(AssetRecoveryItem).filter(
|
||||
and_(
|
||||
AssetRecoveryItem.order_id == order_id,
|
||||
AssetRecoveryItem.recovery_status == "pending"
|
||||
)
|
||||
).all()
|
||||
|
||||
for item in items:
|
||||
item.recovery_status = recovery_status
|
||||
db.add(item)
|
||||
|
||||
db.commit()
|
||||
|
||||
|
||||
# 创建全局实例
|
||||
recovery_order = AssetRecoveryOrderCRUD()
|
||||
recovery_item = AssetRecoveryItemCRUD()
|
||||
324
app/crud/system_config.py
Normal file
324
app/crud/system_config.py
Normal file
@@ -0,0 +1,324 @@
|
||||
"""
|
||||
系统配置CRUD操作
|
||||
"""
|
||||
from typing import Optional, List, Dict, Any
|
||||
from sqlalchemy import select, and_, or_, func
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from app.models.system_config import SystemConfig
|
||||
import json
|
||||
|
||||
|
||||
class SystemConfigCRUD:
|
||||
"""系统配置CRUD类"""
|
||||
|
||||
async def get(self, db: AsyncSession, config_id: int) -> Optional[SystemConfig]:
|
||||
"""
|
||||
根据ID获取系统配置
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
config_id: 配置ID
|
||||
|
||||
Returns:
|
||||
SystemConfig对象或None
|
||||
"""
|
||||
result = await db.execute(
|
||||
select(SystemConfig).where(SystemConfig.id == config_id)
|
||||
)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
async def get_by_key(self, db: AsyncSession, config_key: str) -> Optional[SystemConfig]:
|
||||
"""
|
||||
根据配置键获取系统配置
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
config_key: 配置键
|
||||
|
||||
Returns:
|
||||
SystemConfig对象或None
|
||||
"""
|
||||
result = await db.execute(
|
||||
select(SystemConfig).where(SystemConfig.config_key == config_key)
|
||||
)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
async def get_multi(
|
||||
self,
|
||||
db: AsyncSession,
|
||||
*,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
keyword: Optional[str] = None,
|
||||
category: Optional[str] = None,
|
||||
is_active: Optional[bool] = None,
|
||||
is_system: Optional[bool] = None
|
||||
) -> tuple[List[SystemConfig], int]:
|
||||
"""
|
||||
获取系统配置列表
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
skip: 跳过条数
|
||||
limit: 返回条数
|
||||
keyword: 搜索关键词
|
||||
category: 配置分类
|
||||
is_active: 是否启用
|
||||
is_system: 是否系统配置
|
||||
|
||||
Returns:
|
||||
(配置列表, 总数)
|
||||
"""
|
||||
# 构建查询条件
|
||||
conditions = []
|
||||
|
||||
if keyword:
|
||||
conditions.append(
|
||||
or_(
|
||||
SystemConfig.config_key.ilike(f"%{keyword}%"),
|
||||
SystemConfig.config_name.ilike(f"%{keyword}%"),
|
||||
SystemConfig.description.ilike(f"%{keyword}%")
|
||||
)
|
||||
)
|
||||
|
||||
if category:
|
||||
conditions.append(SystemConfig.category == category)
|
||||
|
||||
if is_active is not None:
|
||||
conditions.append(SystemConfig.is_active == is_active)
|
||||
|
||||
if is_system is not None:
|
||||
conditions.append(SystemConfig.is_system == is_system)
|
||||
|
||||
# 查询总数
|
||||
count_query = select(func.count(SystemConfig.id))
|
||||
if conditions:
|
||||
count_query = count_query.where(and_(*conditions))
|
||||
total_result = await db.execute(count_query)
|
||||
total = total_result.scalar() or 0
|
||||
|
||||
# 查询数据
|
||||
query = select(SystemConfig)
|
||||
if conditions:
|
||||
query = query.where(and_(*conditions))
|
||||
query = query.order_by(SystemConfig.category, SystemConfig.sort_order, SystemConfig.id)
|
||||
query = query.offset(skip).limit(limit)
|
||||
|
||||
result = await db.execute(query)
|
||||
items = result.scalars().all()
|
||||
|
||||
return list(items), total
|
||||
|
||||
async def get_by_category(
|
||||
self,
|
||||
db: AsyncSession,
|
||||
category: str,
|
||||
*,
|
||||
is_active: bool = True
|
||||
) -> List[SystemConfig]:
|
||||
"""
|
||||
根据分类获取配置列表
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
category: 配置分类
|
||||
is_active: 是否启用
|
||||
|
||||
Returns:
|
||||
配置列表
|
||||
"""
|
||||
conditions = [SystemConfig.category == category]
|
||||
|
||||
if is_active:
|
||||
conditions.append(SystemConfig.is_active == True)
|
||||
|
||||
result = await db.execute(
|
||||
select(SystemConfig)
|
||||
.where(and_(*conditions))
|
||||
.order_by(SystemConfig.sort_order, SystemConfig.id)
|
||||
)
|
||||
return list(result.scalars().all())
|
||||
|
||||
async def get_categories(
|
||||
self,
|
||||
db: AsyncSession
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
获取所有配置分类及统计信息
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
|
||||
Returns:
|
||||
分类列表
|
||||
"""
|
||||
result = await db.execute(
|
||||
select(
|
||||
SystemConfig.category,
|
||||
func.count(SystemConfig.id).label('count')
|
||||
)
|
||||
.group_by(SystemConfig.category)
|
||||
.order_by(SystemConfig.category)
|
||||
)
|
||||
|
||||
categories = []
|
||||
for row in result:
|
||||
categories.append({
|
||||
"category": row[0],
|
||||
"count": row[1]
|
||||
})
|
||||
|
||||
return categories
|
||||
|
||||
async def create(
|
||||
self,
|
||||
db: AsyncSession,
|
||||
*,
|
||||
obj_in: Dict[str, Any]
|
||||
) -> SystemConfig:
|
||||
"""
|
||||
创建系统配置
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
obj_in: 创建数据
|
||||
|
||||
Returns:
|
||||
SystemConfig对象
|
||||
"""
|
||||
db_obj = SystemConfig(**obj_in)
|
||||
db.add(db_obj)
|
||||
await db.flush()
|
||||
await db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
async def update(
|
||||
self,
|
||||
db: AsyncSession,
|
||||
*,
|
||||
db_obj: SystemConfig,
|
||||
obj_in: Dict[str, Any]
|
||||
) -> SystemConfig:
|
||||
"""
|
||||
更新系统配置
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
db_obj: 数据库对象
|
||||
obj_in: 更新数据
|
||||
|
||||
Returns:
|
||||
SystemConfig对象
|
||||
"""
|
||||
for field, value in obj_in.items():
|
||||
if hasattr(db_obj, field):
|
||||
setattr(db_obj, field, value)
|
||||
|
||||
await db.flush()
|
||||
await db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
async def batch_update(
|
||||
self,
|
||||
db: AsyncSession,
|
||||
*,
|
||||
configs: Dict[str, Any],
|
||||
updater_id: Optional[int] = None
|
||||
) -> List[SystemConfig]:
|
||||
"""
|
||||
批量更新配置
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
configs: 配置键值对
|
||||
updater_id: 更新人ID
|
||||
|
||||
Returns:
|
||||
更新的配置列表
|
||||
"""
|
||||
updated_configs = []
|
||||
|
||||
for config_key, config_value in configs.items():
|
||||
db_obj = await self.get_by_key(db, config_key)
|
||||
if db_obj:
|
||||
# 转换为字符串存储
|
||||
if isinstance(config_value, (dict, list)):
|
||||
config_value = json.dumps(config_value, ensure_ascii=False)
|
||||
elif isinstance(config_value, bool):
|
||||
config_value = str(config_value).lower()
|
||||
else:
|
||||
config_value = str(config_value)
|
||||
|
||||
db_obj.config_value = config_value
|
||||
db_obj.updated_by = updater_id
|
||||
updated_configs.append(db_obj)
|
||||
|
||||
await db.flush()
|
||||
return updated_configs
|
||||
|
||||
async def delete(self, db: AsyncSession, *, config_id: int) -> Optional[SystemConfig]:
|
||||
"""
|
||||
删除系统配置
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
config_id: 配置ID
|
||||
|
||||
Returns:
|
||||
删除的SystemConfig对象或None
|
||||
"""
|
||||
obj = await self.get(db, config_id)
|
||||
if obj:
|
||||
# 系统配置不允许删除
|
||||
if obj.is_system:
|
||||
raise ValueError("系统配置不允许删除")
|
||||
|
||||
await db.delete(obj)
|
||||
await db.flush()
|
||||
return obj
|
||||
|
||||
async def get_value(
|
||||
self,
|
||||
db: AsyncSession,
|
||||
config_key: str,
|
||||
default: Any = None
|
||||
) -> Any:
|
||||
"""
|
||||
获取配置值(自动转换类型)
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
config_key: 配置键
|
||||
default: 默认值
|
||||
|
||||
Returns:
|
||||
配置值
|
||||
"""
|
||||
config = await self.get_by_key(db, config_key)
|
||||
if not config or not config.is_active:
|
||||
return default
|
||||
|
||||
value = config.config_value
|
||||
|
||||
# 根据类型转换
|
||||
if config.value_type == "boolean":
|
||||
return value.lower() in ("true", "1", "yes") if value else False
|
||||
elif config.value_type == "number":
|
||||
try:
|
||||
return int(value) if value else 0
|
||||
except ValueError:
|
||||
try:
|
||||
return float(value) if value else 0.0
|
||||
except ValueError:
|
||||
return 0
|
||||
elif config.value_type == "json":
|
||||
try:
|
||||
return json.loads(value) if value else {}
|
||||
except json.JSONDecodeError:
|
||||
return {}
|
||||
else:
|
||||
return value
|
||||
|
||||
|
||||
# 创建全局实例
|
||||
system_config_crud = SystemConfigCRUD()
|
||||
335
app/crud/transfer.py
Normal file
335
app/crud/transfer.py
Normal file
@@ -0,0 +1,335 @@
|
||||
"""
|
||||
资产调拨相关CRUD操作
|
||||
"""
|
||||
from typing import List, Optional, Tuple
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import and_, or_
|
||||
from app.models.transfer import AssetTransferOrder, AssetTransferItem
|
||||
from app.models.asset import Asset
|
||||
from app.schemas.transfer import AssetTransferOrderCreate, AssetTransferOrderUpdate
|
||||
|
||||
|
||||
class AssetTransferOrderCRUD:
|
||||
"""调拨单CRUD操作"""
|
||||
|
||||
def get(self, db: Session, id: int) -> Optional[AssetTransferOrder]:
|
||||
"""根据ID获取调拨单"""
|
||||
return db.query(AssetTransferOrder).filter(
|
||||
AssetTransferOrder.id == id
|
||||
).first()
|
||||
|
||||
def get_by_code(self, db: Session, order_code: str) -> Optional[AssetTransferOrder]:
|
||||
"""根据单号获取调拨单"""
|
||||
return db.query(AssetTransferOrder).filter(
|
||||
AssetTransferOrder.order_code == order_code
|
||||
).first()
|
||||
|
||||
def get_multi(
|
||||
self,
|
||||
db: Session,
|
||||
skip: int = 0,
|
||||
limit: int = 20,
|
||||
transfer_type: Optional[str] = None,
|
||||
approval_status: Optional[str] = None,
|
||||
execute_status: Optional[str] = None,
|
||||
source_org_id: Optional[int] = None,
|
||||
target_org_id: Optional[int] = None,
|
||||
keyword: Optional[str] = None
|
||||
) -> Tuple[List[AssetTransferOrder], int]:
|
||||
"""获取调拨单列表"""
|
||||
query = db.query(AssetTransferOrder)
|
||||
|
||||
# 筛选条件
|
||||
if transfer_type:
|
||||
query = query.filter(AssetTransferOrder.transfer_type == transfer_type)
|
||||
if approval_status:
|
||||
query = query.filter(AssetTransferOrder.approval_status == approval_status)
|
||||
if execute_status:
|
||||
query = query.filter(AssetTransferOrder.execute_status == execute_status)
|
||||
if source_org_id:
|
||||
query = query.filter(AssetTransferOrder.source_org_id == source_org_id)
|
||||
if target_org_id:
|
||||
query = query.filter(AssetTransferOrder.target_org_id == target_org_id)
|
||||
if keyword:
|
||||
query = query.filter(
|
||||
or_(
|
||||
AssetTransferOrder.order_code.like(f"%{keyword}%"),
|
||||
AssetTransferOrder.title.like(f"%{keyword}%")
|
||||
)
|
||||
)
|
||||
|
||||
# 排序
|
||||
query = query.order_by(AssetTransferOrder.created_at.desc())
|
||||
|
||||
# 总数
|
||||
total = query.count()
|
||||
|
||||
# 分页
|
||||
items = query.offset(skip).limit(limit).all()
|
||||
|
||||
return items, total
|
||||
|
||||
def create(
|
||||
self,
|
||||
db: Session,
|
||||
obj_in: AssetTransferOrderCreate,
|
||||
order_code: str,
|
||||
apply_user_id: int
|
||||
) -> AssetTransferOrder:
|
||||
"""创建调拨单"""
|
||||
from datetime import datetime
|
||||
|
||||
# 创建调拨单
|
||||
db_obj = AssetTransferOrder(
|
||||
order_code=order_code,
|
||||
source_org_id=obj_in.source_org_id,
|
||||
target_org_id=obj_in.target_org_id,
|
||||
transfer_type=obj_in.transfer_type,
|
||||
title=obj_in.title,
|
||||
asset_count=len(obj_in.asset_ids),
|
||||
apply_user_id=apply_user_id,
|
||||
apply_time=datetime.utcnow(),
|
||||
remark=obj_in.remark,
|
||||
approval_status="pending",
|
||||
execute_status="pending"
|
||||
)
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
|
||||
# 创建调拨单明细
|
||||
self._create_items(
|
||||
db=db,
|
||||
order_id=db_obj.id,
|
||||
asset_ids=obj_in.asset_ids,
|
||||
source_org_id=obj_in.source_org_id,
|
||||
target_org_id=obj_in.target_org_id
|
||||
)
|
||||
|
||||
return db_obj
|
||||
|
||||
def update(
|
||||
self,
|
||||
db: Session,
|
||||
db_obj: AssetTransferOrder,
|
||||
obj_in: AssetTransferOrderUpdate
|
||||
) -> AssetTransferOrder:
|
||||
"""更新调拨单"""
|
||||
update_data = obj_in.model_dump(exclude_unset=True)
|
||||
for field, value in update_data.items():
|
||||
setattr(db_obj, field, value)
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
def approve(
|
||||
self,
|
||||
db: Session,
|
||||
db_obj: AssetTransferOrder,
|
||||
approval_status: str,
|
||||
approval_user_id: int,
|
||||
approval_remark: Optional[str] = None
|
||||
) -> AssetTransferOrder:
|
||||
"""审批调拨单"""
|
||||
from datetime import datetime
|
||||
|
||||
db_obj.approval_status = approval_status
|
||||
db_obj.approval_user_id = approval_user_id
|
||||
db_obj.approval_time = datetime.utcnow()
|
||||
db_obj.approval_remark = approval_remark
|
||||
|
||||
# 如果审批通过,自动设置为可执行状态
|
||||
if approval_status == "approved":
|
||||
db_obj.execute_status = "pending"
|
||||
elif approval_status == "rejected":
|
||||
db_obj.execute_status = "cancelled"
|
||||
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
def start(
|
||||
self,
|
||||
db: Session,
|
||||
db_obj: AssetTransferOrder,
|
||||
execute_user_id: int
|
||||
) -> AssetTransferOrder:
|
||||
"""开始调拨"""
|
||||
from datetime import datetime
|
||||
|
||||
db_obj.execute_status = "executing"
|
||||
db_obj.execute_user_id = execute_user_id
|
||||
db_obj.execute_time = datetime.utcnow()
|
||||
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
def complete(
|
||||
self,
|
||||
db: Session,
|
||||
db_obj: AssetTransferOrder,
|
||||
execute_user_id: int
|
||||
) -> AssetTransferOrder:
|
||||
"""完成调拨"""
|
||||
from datetime import datetime
|
||||
|
||||
db_obj.execute_status = "completed"
|
||||
db_obj.execute_user_id = execute_user_id
|
||||
db_obj.execute_time = datetime.utcnow()
|
||||
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
def cancel(self, db: Session, db_obj: AssetTransferOrder) -> AssetTransferOrder:
|
||||
"""取消调拨单"""
|
||||
db_obj.approval_status = "cancelled"
|
||||
db_obj.execute_status = "cancelled"
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
def delete(self, db: Session, id: int) -> bool:
|
||||
"""删除调拨单"""
|
||||
obj = self.get(db, id)
|
||||
if obj:
|
||||
db.delete(obj)
|
||||
db.commit()
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_statistics(
|
||||
self,
|
||||
db: Session,
|
||||
source_org_id: Optional[int] = None,
|
||||
target_org_id: Optional[int] = None
|
||||
) -> dict:
|
||||
"""获取调拨单统计信息"""
|
||||
query = db.query(AssetTransferOrder)
|
||||
|
||||
if source_org_id:
|
||||
query = query.filter(AssetTransferOrder.source_org_id == source_org_id)
|
||||
if target_org_id:
|
||||
query = query.filter(AssetTransferOrder.target_org_id == target_org_id)
|
||||
|
||||
total = query.count()
|
||||
pending = query.filter(AssetTransferOrder.approval_status == "pending").count()
|
||||
approved = query.filter(AssetTransferOrder.approval_status == "approved").count()
|
||||
rejected = query.filter(AssetTransferOrder.approval_status == "rejected").count()
|
||||
executing = query.filter(AssetTransferOrder.execute_status == "executing").count()
|
||||
completed = query.filter(AssetTransferOrder.execute_status == "completed").count()
|
||||
|
||||
return {
|
||||
"total": total,
|
||||
"pending": pending,
|
||||
"approved": approved,
|
||||
"rejected": rejected,
|
||||
"executing": executing,
|
||||
"completed": completed
|
||||
}
|
||||
|
||||
def _create_items(
|
||||
self,
|
||||
db: Session,
|
||||
order_id: int,
|
||||
asset_ids: List[int],
|
||||
source_org_id: int,
|
||||
target_org_id: int
|
||||
):
|
||||
"""创建调拨单明细"""
|
||||
# 查询资产信息
|
||||
assets = db.query(Asset).filter(Asset.id.in_(asset_ids)).all()
|
||||
|
||||
for asset in assets:
|
||||
item = AssetTransferItem(
|
||||
order_id=order_id,
|
||||
asset_id=asset.id,
|
||||
asset_code=asset.asset_code,
|
||||
source_organization_id=source_org_id,
|
||||
target_organization_id=target_org_id,
|
||||
transfer_status="pending"
|
||||
)
|
||||
db.add(item)
|
||||
|
||||
db.commit()
|
||||
|
||||
|
||||
class AssetTransferItemCRUD:
|
||||
"""调拨单明细CRUD操作"""
|
||||
|
||||
def get_by_order(self, db: Session, order_id: int) -> List[AssetTransferItem]:
|
||||
"""根据调拨单ID获取明细列表"""
|
||||
return db.query(AssetTransferItem).filter(
|
||||
AssetTransferItem.order_id == order_id
|
||||
).order_by(AssetTransferItem.id).all()
|
||||
|
||||
def get_multi(
|
||||
self,
|
||||
db: Session,
|
||||
skip: int = 0,
|
||||
limit: int = 20,
|
||||
order_id: Optional[int] = None,
|
||||
transfer_status: Optional[str] = None
|
||||
) -> Tuple[List[AssetTransferItem], int]:
|
||||
"""获取明细列表"""
|
||||
query = db.query(AssetTransferItem)
|
||||
|
||||
if order_id:
|
||||
query = query.filter(AssetTransferItem.order_id == order_id)
|
||||
if transfer_status:
|
||||
query = query.filter(AssetTransferItem.transfer_status == transfer_status)
|
||||
|
||||
total = query.count()
|
||||
items = query.offset(skip).limit(limit).all()
|
||||
|
||||
return items, total
|
||||
|
||||
def update_transfer_status(
|
||||
self,
|
||||
db: Session,
|
||||
item_id: int,
|
||||
transfer_status: str
|
||||
) -> AssetTransferItem:
|
||||
"""更新明细调拨状态"""
|
||||
item = db.query(AssetTransferItem).filter(
|
||||
AssetTransferItem.id == item_id
|
||||
).first()
|
||||
|
||||
if item:
|
||||
item.transfer_status = transfer_status
|
||||
db.add(item)
|
||||
db.commit()
|
||||
db.refresh(item)
|
||||
|
||||
return item
|
||||
|
||||
def batch_update_transfer_status(
|
||||
self,
|
||||
db: Session,
|
||||
order_id: int,
|
||||
transfer_status: str
|
||||
):
|
||||
"""批量更新明细调拨状态"""
|
||||
items = db.query(AssetTransferItem).filter(
|
||||
and_(
|
||||
AssetTransferItem.order_id == order_id,
|
||||
AssetTransferItem.transfer_status == "pending"
|
||||
)
|
||||
).all()
|
||||
|
||||
for item in items:
|
||||
item.transfer_status = transfer_status
|
||||
db.add(item)
|
||||
|
||||
db.commit()
|
||||
|
||||
|
||||
# 创建全局实例
|
||||
transfer_order = AssetTransferOrderCRUD()
|
||||
transfer_item = AssetTransferItemCRUD()
|
||||
435
app/crud/user.py
Normal file
435
app/crud/user.py
Normal file
@@ -0,0 +1,435 @@
|
||||
"""
|
||||
用户CRUD操作
|
||||
"""
|
||||
from typing import Optional, List, Tuple
|
||||
from sqlalchemy import select, and_, or_
|
||||
from sqlalchemy.orm import selectinload
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from app.models.user import User, Role, UserRole, Permission, RolePermission
|
||||
from app.schemas.user import UserCreate, UserUpdate, RoleCreate, RoleUpdate
|
||||
from app.core.security import get_password_hash
|
||||
|
||||
|
||||
class UserCRUD:
|
||||
"""用户CRUD类"""
|
||||
|
||||
async def get(self, db: AsyncSession, id: int) -> Optional[User]:
|
||||
"""
|
||||
根据ID获取用户
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
id: 用户ID
|
||||
|
||||
Returns:
|
||||
User: 用户对象或None
|
||||
"""
|
||||
result = await db.execute(
|
||||
select(User)
|
||||
.options(selectinload(User.roles).selectinload(Role.permissions))
|
||||
.where(User.id == id, User.deleted_at.is_(None))
|
||||
)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
async def get_by_username(self, db: AsyncSession, username: str) -> Optional[User]:
|
||||
"""
|
||||
根据用户名获取用户
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
username: 用户名
|
||||
|
||||
Returns:
|
||||
User: 用户对象或None
|
||||
"""
|
||||
result = await db.execute(
|
||||
select(User)
|
||||
.options(selectinload(User.roles).selectinload(Role.permissions))
|
||||
.where(User.username == username, User.deleted_at.is_(None))
|
||||
)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
async def get_by_email(self, db: AsyncSession, email: str) -> Optional[User]:
|
||||
"""
|
||||
根据邮箱获取用户
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
email: 邮箱
|
||||
|
||||
Returns:
|
||||
User: 用户对象或None
|
||||
"""
|
||||
result = await db.execute(
|
||||
select(User)
|
||||
.where(User.email == email, User.deleted_at.is_(None))
|
||||
)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
async def get_multi(
|
||||
self,
|
||||
db: AsyncSession,
|
||||
skip: int = 0,
|
||||
limit: int = 20,
|
||||
keyword: Optional[str] = None,
|
||||
status: Optional[str] = None,
|
||||
role_id: Optional[int] = None
|
||||
) -> Tuple[List[User], int]:
|
||||
"""
|
||||
获取用户列表
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
skip: 跳过条数
|
||||
limit: 返回条数
|
||||
keyword: 搜索关键词
|
||||
status: 状态筛选
|
||||
role_id: 角色ID筛选
|
||||
|
||||
Returns:
|
||||
Tuple[List[User], int]: 用户列表和总数
|
||||
"""
|
||||
# 构建查询条件
|
||||
conditions = [User.deleted_at.is_(None)]
|
||||
|
||||
if keyword:
|
||||
keyword_pattern = f"%{keyword}%"
|
||||
conditions.append(
|
||||
or_(
|
||||
User.username.ilike(keyword_pattern),
|
||||
User.real_name.ilike(keyword_pattern),
|
||||
User.phone.ilike(keyword_pattern)
|
||||
)
|
||||
)
|
||||
|
||||
if status:
|
||||
conditions.append(User.status == status)
|
||||
|
||||
# 构建基础查询
|
||||
query = select(User).options(selectinload(User.roles)).where(*conditions)
|
||||
|
||||
# 如果需要按角色筛选
|
||||
if role_id:
|
||||
query = query.join(UserRole).where(UserRole.role_id == role_id)
|
||||
|
||||
# 按ID降序排序
|
||||
query = query.order_by(User.id.desc())
|
||||
|
||||
# 获取总数
|
||||
count_query = select(User.id).where(*conditions)
|
||||
if role_id:
|
||||
count_query = count_query.join(UserRole).where(UserRole.role_id == role_id)
|
||||
|
||||
result = await db.execute(select(User.id).where(*conditions))
|
||||
total = len(result.all())
|
||||
|
||||
# 分页查询
|
||||
result = await db.execute(query.offset(skip).limit(limit))
|
||||
users = result.scalars().all()
|
||||
|
||||
return list(users), total
|
||||
|
||||
async def create(self, db: AsyncSession, obj_in: UserCreate, creator_id: int) -> User:
|
||||
"""
|
||||
创建用户
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
obj_in: 创建数据
|
||||
creator_id: 创建人ID
|
||||
|
||||
Returns:
|
||||
User: 创建的用户对象
|
||||
"""
|
||||
# 检查用户名是否已存在
|
||||
existing_user = await self.get_by_username(db, obj_in.username)
|
||||
if existing_user:
|
||||
raise ValueError("用户名已存在")
|
||||
|
||||
# 检查邮箱是否已存在
|
||||
if obj_in.email:
|
||||
existing_email = await self.get_by_email(db, obj_in.email)
|
||||
if existing_email:
|
||||
raise ValueError("邮箱已存在")
|
||||
|
||||
# 创建用户对象
|
||||
db_obj = User(
|
||||
username=obj_in.username,
|
||||
password_hash=get_password_hash(obj_in.password),
|
||||
real_name=obj_in.real_name,
|
||||
email=obj_in.email,
|
||||
phone=obj_in.phone,
|
||||
created_by=creator_id
|
||||
)
|
||||
|
||||
db.add(db_obj)
|
||||
await db.flush()
|
||||
await db.refresh(db_obj)
|
||||
|
||||
# 分配角色
|
||||
for role_id in obj_in.role_ids:
|
||||
user_role = UserRole(
|
||||
user_id=db_obj.id,
|
||||
role_id=role_id,
|
||||
created_by=creator_id
|
||||
)
|
||||
db.add(user_role)
|
||||
|
||||
await db.commit()
|
||||
await db.refresh(db_obj)
|
||||
|
||||
return await self.get(db, db_obj.id)
|
||||
|
||||
async def update(
|
||||
self,
|
||||
db: AsyncSession,
|
||||
db_obj: User,
|
||||
obj_in: UserUpdate,
|
||||
updater_id: int
|
||||
) -> User:
|
||||
"""
|
||||
更新用户
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
db_obj: 数据库中的用户对象
|
||||
obj_in: 更新数据
|
||||
updater_id: 更新人ID
|
||||
|
||||
Returns:
|
||||
User: 更新后的用户对象
|
||||
"""
|
||||
update_data = obj_in.model_dump(exclude_unset=True)
|
||||
|
||||
# 检查邮箱是否已被其他用户使用
|
||||
if "email" in update_data and update_data["email"]:
|
||||
existing_user = await db.execute(
|
||||
select(User).where(
|
||||
User.email == update_data["email"],
|
||||
User.id != db_obj.id,
|
||||
User.deleted_at.is_(None)
|
||||
)
|
||||
)
|
||||
if existing_user.scalar_one_or_none():
|
||||
raise ValueError("邮箱已被使用")
|
||||
|
||||
# 更新字段
|
||||
for field, value in update_data.items():
|
||||
if field == "role_ids":
|
||||
continue
|
||||
setattr(db_obj, field, value)
|
||||
|
||||
db_obj.updated_by = updater_id
|
||||
|
||||
# 更新角色
|
||||
if "role_ids" in update_data:
|
||||
# 删除旧角色
|
||||
await db.execute(
|
||||
select(UserRole).where(UserRole.user_id == db_obj.id)
|
||||
)
|
||||
old_roles = await db.execute(
|
||||
select(UserRole).where(UserRole.user_id == db_obj.id)
|
||||
)
|
||||
for old_role in old_roles.scalars().all():
|
||||
await db.delete(old_role)
|
||||
|
||||
# 添加新角色
|
||||
for role_id in update_data["role_ids"]:
|
||||
user_role = UserRole(
|
||||
user_id=db_obj.id,
|
||||
role_id=role_id,
|
||||
created_by=updater_id
|
||||
)
|
||||
db.add(user_role)
|
||||
|
||||
await db.commit()
|
||||
await db.refresh(db_obj)
|
||||
|
||||
return await self.get(db, db_obj.id)
|
||||
|
||||
async def delete(self, db: AsyncSession, id: int, deleter_id: int) -> bool:
|
||||
"""
|
||||
删除用户(软删除)
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
id: 用户ID
|
||||
deleter_id: 删除人ID
|
||||
|
||||
Returns:
|
||||
bool: 是否删除成功
|
||||
"""
|
||||
db_obj = await self.get(db, id)
|
||||
if not db_obj:
|
||||
return False
|
||||
|
||||
db_obj.deleted_at = datetime.utcnow()
|
||||
db_obj.deleted_by = deleter_id
|
||||
|
||||
await db.commit()
|
||||
return True
|
||||
|
||||
async def update_password(
|
||||
self,
|
||||
db: AsyncSession,
|
||||
user: User,
|
||||
new_password: str
|
||||
) -> bool:
|
||||
"""
|
||||
更新用户密码
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
user: 用户对象
|
||||
new_password: 新密码
|
||||
|
||||
Returns:
|
||||
bool: 是否更新成功
|
||||
"""
|
||||
user.password_hash = get_password_hash(new_password)
|
||||
user.login_fail_count = 0
|
||||
user.locked_until = None
|
||||
|
||||
await db.commit()
|
||||
return True
|
||||
|
||||
async def update_last_login(self, db: AsyncSession, user: User) -> bool:
|
||||
"""
|
||||
更新用户最后登录时间
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
user: 用户对象
|
||||
|
||||
Returns:
|
||||
bool: 是否更新成功
|
||||
"""
|
||||
from datetime import datetime
|
||||
user.last_login_at = datetime.utcnow()
|
||||
user.login_fail_count = 0
|
||||
|
||||
await db.commit()
|
||||
return True
|
||||
|
||||
|
||||
class RoleCRUD:
|
||||
"""角色CRUD类"""
|
||||
|
||||
async def get(self, db: AsyncSession, id: int) -> Optional[Role]:
|
||||
"""根据ID获取角色"""
|
||||
result = await db.execute(
|
||||
select(Role)
|
||||
.options(selectinload(Role.permissions))
|
||||
.where(Role.id == id, Role.deleted_at.is_(None))
|
||||
)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
async def get_by_code(self, db: AsyncSession, role_code: str) -> Optional[Role]:
|
||||
"""根据代码获取角色"""
|
||||
result = await db.execute(
|
||||
select(Role).where(Role.role_code == role_code, Role.deleted_at.is_(None))
|
||||
)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
async def get_multi(
|
||||
self,
|
||||
db: AsyncSession,
|
||||
status: Optional[str] = None
|
||||
) -> List[Role]:
|
||||
"""获取角色列表"""
|
||||
conditions = [Role.deleted_at.is_(None)]
|
||||
|
||||
if status:
|
||||
conditions.append(Role.status == status)
|
||||
|
||||
result = await db.execute(
|
||||
select(Role)
|
||||
.options(selectinload(Role.permissions))
|
||||
.where(*conditions)
|
||||
.order_by(Role.sort_order, Role.id)
|
||||
)
|
||||
return list(result.scalars().all())
|
||||
|
||||
async def create(self, db: AsyncSession, obj_in: RoleCreate, creator_id: int) -> Role:
|
||||
"""创建角色"""
|
||||
# 检查代码是否已存在
|
||||
existing_role = await self.get_by_code(db, obj_in.role_code)
|
||||
if existing_role:
|
||||
raise ValueError("角色代码已存在")
|
||||
|
||||
db_obj = Role(
|
||||
role_name=obj_in.role_name,
|
||||
role_code=obj_in.role_code,
|
||||
description=obj_in.description,
|
||||
created_by=creator_id
|
||||
)
|
||||
|
||||
db.add(db_obj)
|
||||
await db.flush()
|
||||
await db.refresh(db_obj)
|
||||
|
||||
# 分配权限
|
||||
for permission_id in obj_in.permission_ids:
|
||||
role_permission = RolePermission(
|
||||
role_id=db_obj.id,
|
||||
permission_id=permission_id,
|
||||
created_by=creator_id
|
||||
)
|
||||
db.add(role_permission)
|
||||
|
||||
await db.commit()
|
||||
return await self.get(db, db_obj.id)
|
||||
|
||||
async def update(
|
||||
self,
|
||||
db: AsyncSession,
|
||||
db_obj: Role,
|
||||
obj_in: RoleUpdate,
|
||||
updater_id: int
|
||||
) -> Role:
|
||||
"""更新角色"""
|
||||
update_data = obj_in.model_dump(exclude_unset=True)
|
||||
|
||||
for field, value in update_data.items():
|
||||
if field == "permission_ids":
|
||||
continue
|
||||
setattr(db_obj, field, value)
|
||||
|
||||
db_obj.updated_by = updater_id
|
||||
|
||||
# 更新权限
|
||||
if "permission_ids" in update_data:
|
||||
# 删除旧权限
|
||||
old_permissions = await db.execute(
|
||||
select(RolePermission).where(RolePermission.role_id == db_obj.id)
|
||||
)
|
||||
for old_perm in old_permissions.scalars().all():
|
||||
await db.delete(old_perm)
|
||||
|
||||
# 添加新权限
|
||||
for permission_id in update_data["permission_ids"]:
|
||||
role_permission = RolePermission(
|
||||
role_id=db_obj.id,
|
||||
permission_id=permission_id,
|
||||
created_by=updater_id
|
||||
)
|
||||
db.add(role_permission)
|
||||
|
||||
await db.commit()
|
||||
return await self.get(db, db_obj.id)
|
||||
|
||||
async def delete(self, db: AsyncSession, id: int) -> bool:
|
||||
"""删除角色(软删除)"""
|
||||
db_obj = await self.get(db, id)
|
||||
if not db_obj:
|
||||
return False
|
||||
|
||||
db_obj.deleted_at = datetime.utcnow()
|
||||
|
||||
await db.commit()
|
||||
return True
|
||||
|
||||
|
||||
# 创建CRUD实例
|
||||
user_crud = UserCRUD()
|
||||
role_crud = RoleCRUD()
|
||||
Reference in New Issue
Block a user