Fix API compatibility and add user/role/permission and asset import/export

This commit is contained in:
2026-01-25 23:36:23 +08:00
commit 501d11e14e
371 changed files with 68853 additions and 0 deletions

View File

View 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()

View 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()

View 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()

View 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()

View 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()

View 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()

View 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()

View 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()

View 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()

View 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()

View 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()

View 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()

View 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()