Files
zcglxt/backend_new/app/services/recovery_service.py

410 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
资产回收业务服务层
"""
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
]
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()