""" 资产回收业务服务层 """ from typing import List, Optional, Dict, Any from sqlalchemy.orm import Session, selectinload from app.crud.recovery import recovery_order, recovery_item from app.crud.asset import asset from app.schemas.recovery import ( AssetRecoveryOrderCreate, AssetRecoveryOrderUpdate ) from app.core.exceptions import NotFoundException, BusinessException class RecoveryService: """资产回收服务类""" async def get_order( self, db: Session, order_id: int ) -> Dict[str, Any]: """获取回收单详情""" # 使用selectinload预加载关联数据,避免N+1查询 from app.models.recovery import AssetRecoveryOrder from app.models.user import User from app.models.recovery import AssetRecoveryItem obj = db.query( AssetRecoveryOrder ).options( selectinload(AssetRecoveryOrder.items), selectinload(AssetRecoveryOrder.applicant.of_type(User)), selectinload(AssetRecoveryOrder.approver.of_type(User)), selectinload(AssetRecoveryOrder.executor.of_type(User)) ).filter( AssetRecoveryOrder.id == order_id ).first() if not obj: raise NotFoundException("回收单") # 加载关联信息 return self._load_order_relations(db, obj) def get_orders( 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: """获取回收单列表""" items, total = recovery_order.get_multi( db=db, skip=skip, limit=limit, recovery_type=recovery_type, approval_status=approval_status, execute_status=execute_status, keyword=keyword ) # 加载关联信息 items_with_relations = [self._load_order_relations(db, item) for item in items] return items_with_relations, total async def create_order( self, db: Session, obj_in: AssetRecoveryOrderCreate, apply_user_id: int ): """创建回收单""" # 验证资产存在性和状态 assets = [] for asset_id in obj_in.asset_ids: asset_obj = asset.get(db, asset_id) if not asset_obj: raise NotFoundException(f"资产ID {asset_id}") assets.append(asset_obj) # 验证资产状态是否允许回收 for asset_obj in assets: if not self._can_recover(asset_obj.status, obj_in.recovery_type): raise BusinessException( f"资产 {asset_obj.asset_code} 当前状态为 {asset_obj.status},不允许{self._get_recovery_type_name(obj_in.recovery_type)}操作" ) # 生成回收单号 order_code = await self._generate_order_code(db) # 创建回收单 db_obj = recovery_order.create( db=db, obj_in=obj_in, order_code=order_code, apply_user_id=apply_user_id ) return self._load_order_relations(db, db_obj) def update_order( self, db: Session, order_id: int, obj_in: AssetRecoveryOrderUpdate ): """更新回收单""" db_obj = recovery_order.get(db, order_id) if not db_obj: raise NotFoundException("回收单") # 只有待审批状态可以更新 if db_obj.approval_status != "pending": raise BusinessException("只有待审批状态的回收单可以更新") return recovery_order.update(db, db_obj, obj_in) def approve_order( self, db: Session, order_id: int, approval_status: str, approval_user_id: int, approval_remark: Optional[str] = None ): """审批回收单""" db_obj = recovery_order.get(db, order_id) if not db_obj: raise NotFoundException("回收单") # 检查状态 if db_obj.approval_status != "pending": raise BusinessException("该回收单已审批,无法重复审批") # 审批 db_obj = recovery_order.approve( db=db, db_obj=db_obj, approval_status=approval_status, approval_user_id=approval_user_id, approval_remark=approval_remark ) return self._load_order_relations(db, db_obj) def start_order( self, db: Session, order_id: int, execute_user_id: int ): """开始回收""" db_obj = recovery_order.get(db, order_id) if not db_obj: raise NotFoundException("回收单") # 检查状态 if db_obj.approval_status != "approved": raise BusinessException("该回收单未审批通过,无法开始执行") if db_obj.execute_status != "pending": raise BusinessException("该回收单已开始或已完成") # 开始回收 db_obj = recovery_order.start(db, db_obj, execute_user_id) # 更新明细状态为回收中 recovery_item.batch_update_recovery_status(db, order_id, "recovering") return self._load_order_relations(db, db_obj) async def complete_order( self, db: Session, order_id: int, execute_user_id: int ): """完成回收""" db_obj = recovery_order.get(db, order_id) if not db_obj: raise NotFoundException("回收单") # 检查状态 if db_obj.execute_status not in ["pending", "executing"]: raise BusinessException("该回收单状态不允许完成操作") # 完成回收单 db_obj = recovery_order.complete(db, db_obj, execute_user_id) # 更新资产状态 await self._execute_recovery_logic(db, db_obj) # 更新明细状态为完成 recovery_item.batch_update_recovery_status(db, order_id, "completed") return self._load_order_relations(db, db_obj) def cancel_order( self, db: Session, order_id: int ) -> bool: """取消回收单""" db_obj = recovery_order.get(db, order_id) if not db_obj: raise NotFoundException("回收单") # 检查状态 if db_obj.execute_status == "completed": raise BusinessException("已完成的回收单无法取消") recovery_order.cancel(db, db_obj) return True def delete_order( self, db: Session, order_id: int ) -> bool: """删除回收单""" db_obj = recovery_order.get(db, order_id) if not db_obj: raise NotFoundException("回收单") # 只有已取消或已拒绝的可以删除 if db_obj.approval_status not in ["rejected", "cancelled"]: raise BusinessException("只能删除已拒绝或已取消的回收单") return recovery_order.delete(db, order_id) def get_order_items( self, db: Session, order_id: int ) -> List: """获取回收单明细""" # 验证回收单存在 if not recovery_order.get(db, order_id): raise NotFoundException("回收单") return recovery_item.get_by_order(db, order_id) def get_statistics( self, db: Session ) -> Dict[str, int]: """获取回收单统计信息""" return recovery_order.get_statistics(db) async def _execute_recovery_logic( self, db: Session, order_obj ): """执行回收逻辑(完成回收时自动执行)""" # 获取明细 items = recovery_item.get_by_order(db, order_obj.id) # 更新资产状态 from app.services.asset_service import asset_service from app.schemas.asset import AssetStatusTransition for item in items: try: # 根据回收类型确定目标状态 if order_obj.recovery_type == "scrap": target_status = "scrapped" remark = f"报废回收: {order_obj.order_code}" else: target_status = "in_stock" remark = f"资产回收: {order_obj.order_code}" # 变更资产状态 await asset_service.change_asset_status( db=db, asset_id=item.asset_id, status_transition=AssetStatusTransition( new_status=target_status, remark=remark ), operator_id=order_obj.execute_user_id ) except Exception as e: # 记录失败日志 print(f"回收资产 {item.asset_code} 失败: {str(e)}") raise def _load_order_relations( self, db: Session, obj ) -> Dict[str, Any]: """加载回收单关联信息""" from app.models.user import User result = { "id": obj.id, "order_code": obj.order_code, "recovery_type": obj.recovery_type, "title": obj.title, "asset_count": obj.asset_count, "apply_user_id": obj.apply_user_id, "apply_time": obj.apply_time, "approval_status": obj.approval_status, "approval_user_id": obj.approval_user_id, "approval_time": obj.approval_time, "approval_remark": obj.approval_remark, "execute_status": obj.execute_status, "execute_user_id": obj.execute_user_id, "execute_time": obj.execute_time, "remark": obj.remark, "created_at": obj.created_at, "updated_at": obj.updated_at } # 加载申请人 if obj.apply_user_id: apply_user = db.query(User).filter(User.id == obj.apply_user_id).first() if apply_user: result["apply_user"] = { "id": apply_user.id, "real_name": apply_user.real_name, "username": apply_user.username } # 加载审批人 if obj.approval_user_id: approval_user = db.query(User).filter(User.id == obj.approval_user_id).first() if approval_user: result["approval_user"] = { "id": approval_user.id, "real_name": approval_user.real_name, "username": approval_user.username } # 加载执行人 if obj.execute_user_id: execute_user = db.query(User).filter(User.id == obj.execute_user_id).first() if execute_user: result["execute_user"] = { "id": execute_user.id, "real_name": execute_user.real_name, "username": execute_user.username } # 加载明细 items = recovery_item.get_by_order(db, obj.id) result["items"] = [ { "id": item.id, "asset_id": item.asset_id, "asset_code": item.asset_code, "recovery_status": item.recovery_status } for item in items ] # Frontend-friendly aliases result["recovery_no"] = obj.order_code result["status"] = obj.approval_status result["reason"] = obj.title result["applicant"] = result.get("apply_user") # Asset details and total value (best effort) asset_ids = [item.asset_id for item in items] if asset_ids: from app.models.asset import Asset assets = db.query(Asset).filter(Asset.id.in_(asset_ids)).all() assets_detail = [] total_value = 0 organization = None for asset_obj in assets: price = asset_obj.purchase_price or 0 total_value += float(price) if organization is None and asset_obj.organization: organization = { "id": asset_obj.organization.id, "org_name": asset_obj.organization.org_name, "org_type": asset_obj.organization.org_type } assets_detail.append({ "id": asset_obj.id, "asset_code": asset_obj.asset_code, "asset_name": asset_obj.asset_name, "model": asset_obj.model, "serial_number": asset_obj.serial_number, "purchase_price": float(price) if asset_obj.purchase_price is not None else None, "device_type": { "type_name": asset_obj.device_type.type_name } if asset_obj.device_type else None, "brand": { "brand_name": asset_obj.brand.brand_name } if asset_obj.brand else None, "organization": { "org_name": asset_obj.organization.org_name } if asset_obj.organization else None, }) result["assets"] = assets_detail result["total_value"] = total_value if organization: result["organization"] = organization return result def _can_recover(self, asset_status: str, recovery_type: str) -> bool: """判断资产是否可以回收""" # 使用中的资产可以回收 if recovery_type in ["user", "org"]: return asset_status == "in_use" # 报废回收可以使用中或维修中的资产 elif recovery_type == "scrap": return asset_status in ["in_use", "maintenance", "in_stock"] return False def _get_recovery_type_name(self, recovery_type: str) -> str: """获取回收类型中文名""" type_names = { "user": "使用人回收", "org": "机构回收", "scrap": "报废回收" } return type_names.get(recovery_type, "回收") async def _generate_order_code(self, db: Session) -> str: """生成回收单号""" from datetime import datetime import random import string # 日期部分 date_str = datetime.now().strftime("%Y%m%d") # 序号部分(5位随机数) sequence = "".join(random.choices(string.digits, k=5)) # 组合单号: RO-20250124-00001 order_code = f"RO-{date_str}-{sequence}" # 检查是否重复,如果重复则重新生成 while recovery_order.get_by_code(db, order_code): sequence = "".join(random.choices(string.digits, k=5)) order_code = f"RO-{date_str}-{sequence}" return order_code # 创建全局实例 recovery_service = RecoveryService()