""" 资产管理业务服务层 """ from typing import List, Optional, Tuple, Dict, Any from sqlalchemy import func, select from sqlalchemy.ext.asyncio import AsyncSession from app.models.asset import Asset from app.crud.asset import asset, asset_status_history from app.schemas.asset import ( AssetCreate, AssetUpdate, AssetStatusTransition ) from app.services.state_machine_service import state_machine_service from app.utils.asset_code import generate_asset_code from app.utils.qrcode import generate_qr_code, delete_qr_code from app.core.exceptions import NotFoundException, AlreadyExistsException, StateTransitionException class AssetService: """资产服务类""" def __init__(self): self.state_machine = state_machine_service async def get_asset(self, db: AsyncSession, asset_id: int): """获取资产详情""" obj = await asset.get(db, asset_id) if not obj: raise NotFoundException("资产") # 加载关联信息 return await self._load_relations(db, obj) async def get_assets( self, db: AsyncSession, 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, int]: """获取资产列表""" return await asset.get_multi( db=db, skip=skip, limit=limit, keyword=keyword, device_type_id=device_type_id, organization_id=organization_id, status=status, purchase_date_start=purchase_date_start, purchase_date_end=purchase_date_end ) async def create_asset( self, db: AsyncSession, obj_in: AssetCreate, creator_id: int ): """创建资产""" # 检查序列号是否已存在 if obj_in.serial_number: existing = await asset.get_by_serial_number(db, obj_in.serial_number) if existing: raise AlreadyExistsException("该序列号已被使用") # 生成资产编码 asset_code = await generate_asset_code(db) # 创建资产 db_obj = await asset.create(db, obj_in, asset_code, creator_id) # 生成二维码 try: qr_code_url = generate_qr_code(asset_code) db_obj.qr_code_url = qr_code_url db.add(db_obj) await db.commit() await db.refresh(db_obj) except Exception as e: # 二维码生成失败不影响资产创建 pass # 记录状态历史 await self._record_status_change( db=db, asset_id=db_obj.id, old_status=None, new_status="pending", operation_type="create", operator_id=creator_id, operator_name=None, # 可以从用户表获取 remark="资产创建" ) return db_obj async def update_asset( self, db: AsyncSession, asset_id: int, obj_in: AssetUpdate, updater_id: int ): """更新资产""" db_obj = await asset.get(db, asset_id) if not db_obj: raise NotFoundException("资产") # 如果更新序列号,检查是否重复 if obj_in.serial_number and obj_in.serial_number != db_obj.serial_number: existing = await asset.get_by_serial_number(db, obj_in.serial_number) if existing: raise AlreadyExistsException("该序列号已被使用") return await asset.update(db, db_obj, obj_in, updater_id) async def delete_asset( self, db: AsyncSession, asset_id: int, deleter_id: int ) -> bool: """删除资产""" if not await asset.get(db, asset_id): raise NotFoundException("资产") return await asset.delete(db, asset_id, deleter_id) async def change_asset_status( self, db: AsyncSession, asset_id: int, status_transition: AssetStatusTransition, operator_id: int, operator_name: Optional[str] = None ): """变更资产状态""" db_obj = await asset.get(db, asset_id) if not db_obj: raise NotFoundException("资产") # 验证状态转换 error = self.state_machine.validate_transition( db_obj.status, status_transition.new_status ) if error: raise StateTransitionException(db_obj.status, status_transition.new_status) # 更新状态 old_status = db_obj.status await asset.update_status( db=db, asset_id=asset_id, new_status=status_transition.new_status, updater_id=operator_id ) # 记录状态历史 await self._record_status_change( db=db, asset_id=asset_id, old_status=old_status, new_status=status_transition.new_status, operation_type=self._get_operation_type(old_status, status_transition.new_status), operator_id=operator_id, operator_name=operator_name, remark=status_transition.remark, extra_data=status_transition.extra_data ) # 刷新对象 await db.refresh(db_obj) return db_obj async def get_asset_status_history( self, db: AsyncSession, asset_id: int, skip: int = 0, limit: int = 50 ) -> List: """获取资产状态历史""" if not await asset.get(db, asset_id): raise NotFoundException("资产") return await asset_status_history.get_by_asset(db, asset_id, skip, limit) async def scan_asset_by_code( self, db: AsyncSession, asset_code: str ): """扫码查询资产""" obj = await asset.get_by_code(db, asset_code) if not obj: raise NotFoundException("资产") return await self._load_relations(db, obj) async def get_statistics( self, db: AsyncSession, organization_id: Optional[int] = None ) -> Dict[str, Any]: """获取资产统计信息""" query = select( func.count(Asset.id).label("total"), func.sum(Asset.purchase_price).label("total_value") ).where(Asset.deleted_at.is_(None)) if organization_id: query = query.where(Asset.organization_id == organization_id) result = await db.execute(query) row = result.first() # 按状态统计 status_query = select( Asset.status, func.count(Asset.id).label("count") ).where( Asset.deleted_at.is_(None) ) if organization_id: status_query = status_query.where(Asset.organization_id == organization_id) status_query = status_query.group_by(Asset.status) status_result = await db.execute(status_query) status_distribution = {row.status: row.count for row in status_result.all()} return { "total": row.total or 0 if row else 0, "total_value": float(row.total_value or 0) if row else 0, "status_distribution": status_distribution } async def _load_relations(self, db: AsyncSession, obj): """加载关联信息""" # 预加载关联对象,避免懒加载导致的async问题 await db.refresh( obj, attribute_names=["device_type", "brand", "supplier", "organization", "status_history"] ) return obj async def _record_status_change( self, db: AsyncSession, 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 ): """记录状态变更历史""" await asset_status_history.create( db=db, 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 ) def _get_operation_type(self, old_status: str, new_status: str) -> str: """根据状态转换获取操作类型""" operation_map = { ("pending", "in_stock"): "in_stock", ("in_stock", "in_use"): "allocate", ("in_use", "in_stock"): "recover", ("in_stock", "transferring"): "transfer", ("in_use", "transferring"): "transfer", ("transferring", "in_use"): "transfer_complete", ("in_stock", "maintenance"): "maintenance", ("in_use", "maintenance"): "maintenance", ("maintenance", "in_stock"): "maintenance_complete", ("maintenance", "in_use"): "maintenance_complete", ("in_stock", "pending_scrap"): "pending_scrap", ("in_use", "pending_scrap"): "pending_scrap", ("pending_scrap", "scrapped"): "scrap", ("pending_scrap", "in_stock"): "cancel_scrap", } return operation_map.get((old_status, new_status), "status_change") # 创建全局实例 asset_service = AssetService()