Files
zcglxt/backend/app/services/allocation_service.py

470 lines
16 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.allocation import allocation_order, allocation_item
from app.crud.asset import asset
from app.schemas.allocation import (
AllocationOrderCreate,
AllocationOrderUpdate,
AllocationOrderApproval
)
from app.core.exceptions import NotFoundException, BusinessException
class AllocationService:
"""资产分配服务类"""
async def get_order(
self,
db: Session,
order_id: int
) -> Dict[str, Any]:
"""获取分配单详情"""
# 使用selectinload预加载关联数据避免N+1查询
from app.models.allocation import AllocationOrder
from app.models.organization import Organization
from app.models.user import User
from app.models.allocation import AllocationItem
obj = db.query(
AllocationOrder
).options(
selectinload(AllocationOrder.items),
selectinload(AllocationOrder.source_organization.of_type(Organization)),
selectinload(AllocationOrder.target_organization.of_type(Organization)),
selectinload(AllocationOrder.applicant.of_type(User)),
selectinload(AllocationOrder.approver.of_type(User)),
selectinload(AllocationOrder.executor.of_type(User))
).filter(
AllocationOrder.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,
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:
"""获取分配单列表"""
items, total = allocation_order.get_multi(
db=db,
skip=skip,
limit=limit,
order_type=order_type,
approval_status=approval_status,
execute_status=execute_status,
applicant_id=applicant_id,
target_organization_id=target_organization_id,
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: AllocationOrderCreate,
applicant_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_allocate(asset_obj.status, obj_in.order_type):
raise BusinessException(
f"资产 {asset_obj.asset_code} 当前状态为 {asset_obj.status},不允许{self._get_order_type_name(obj_in.order_type)}操作"
)
# 生成分配单号
order_code = await self._generate_order_code(db, obj_in.order_type)
# 创建分配单
db_obj = allocation_order.create(
db=db,
obj_in=obj_in,
order_code=order_code,
applicant_id=applicant_id
)
return self._load_order_relations(db, db_obj)
def update_order(
self,
db: Session,
order_id: int,
obj_in: AllocationOrderUpdate,
updater_id: int
):
"""更新分配单"""
db_obj = allocation_order.get(db, order_id)
if not db_obj:
raise NotFoundException("分配单")
# 只有草稿或待审批状态可以更新
if db_obj.approval_status not in ["pending", "draft"]:
raise BusinessException("只有待审批状态的分配单可以更新")
return allocation_order.update(db, db_obj, obj_in, updater_id)
async def approve_order(
self,
db: Session,
order_id: int,
approval_in: AllocationOrderApproval,
approver_id: int
):
"""审批分配单"""
db_obj = allocation_order.get(db, order_id)
if not db_obj:
raise NotFoundException("分配单")
# 检查状态
if db_obj.approval_status != "pending":
raise BusinessException("该分配单已审批,无法重复审批")
# 审批
db_obj = allocation_order.approve(
db=db,
db_obj=db_obj,
approval_status=approval_in.approval_status,
approver_id=approver_id,
approval_remark=approval_in.approval_remark
)
# 如果审批通过,执行分配逻辑
if approval_in.approval_status == "approved":
await self._execute_allocation_logic(db, db_obj)
return self._load_order_relations(db, db_obj)
async def execute_order(
self,
db: Session,
order_id: int,
executor_id: int
):
"""执行分配单"""
db_obj = allocation_order.get(db, order_id)
if not db_obj:
raise NotFoundException("分配单")
# 检查状态
if db_obj.approval_status != "approved":
raise BusinessException("该分配单未审批通过,无法执行")
if db_obj.execute_status == "completed":
raise BusinessException("该分配单已执行完成")
# 执行分配单
db_obj = allocation_order.execute(db, db_obj, executor_id)
return self._load_order_relations(db, db_obj)
def cancel_order(
self,
db: Session,
order_id: int
) -> bool:
"""取消分配单"""
db_obj = allocation_order.get(db, order_id)
if not db_obj:
raise NotFoundException("分配单")
# 检查状态
if db_obj.execute_status == "completed":
raise BusinessException("已完成的分配单无法取消")
allocation_order.cancel(db, db_obj)
return True
def delete_order(
self,
db: Session,
order_id: int
) -> bool:
"""删除分配单"""
db_obj = allocation_order.get(db, order_id)
if not db_obj:
raise NotFoundException("分配单")
# 只有草稿或已取消的可以删除
if db_obj.approval_status not in ["draft", "rejected", "cancelled"]:
raise BusinessException("只能删除草稿、已拒绝或已取消的分配单")
return allocation_order.delete(db, order_id)
def get_order_items(
self,
db: Session,
order_id: int
) -> List:
"""获取分配单明细"""
# 验证分配单存在
if not allocation_order.get(db, order_id):
raise NotFoundException("分配单")
return allocation_item.get_by_order(db, order_id)
def get_statistics(
self,
db: Session,
applicant_id: Optional[int] = None
) -> Dict[str, int]:
"""获取分配单统计信息"""
return allocation_order.get_statistics(db, applicant_id)
async def _execute_allocation_logic(
self,
db: Session,
order_obj
):
"""执行分配逻辑(审批通过后自动执行)"""
# 根据单据类型执行不同的逻辑
if order_obj.order_type == "allocation":
await self._execute_allocation(db, order_obj)
elif order_obj.order_type == "transfer":
await self._execute_transfer(db, order_obj)
elif order_obj.order_type == "recovery":
await self._execute_recovery(db, order_obj)
elif order_obj.order_type == "maintenance":
await self._execute_maintenance_allocation(db, order_obj)
elif order_obj.order_type == "scrap":
await self._execute_scrap_allocation(db, order_obj)
async def _execute_allocation(self, db: Session, order_obj):
"""执行资产分配"""
# 更新明细状态为执行中
allocation_item.batch_update_execute_status(db, order_obj.id, "executing")
# 获取明细
items = allocation_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:
# 变更资产状态
await asset_service.change_asset_status(
db=db,
asset_id=item.asset_id,
status_transition=AssetStatusTransition(
new_status=item.to_status,
remark=f"分配单: {order_obj.order_code}"
),
operator_id=order_obj.applicant_id
)
# 更新明细状态为完成
allocation_item.update_execute_status(db, item.id, "completed")
except Exception as e:
# 更新明细状态为失败
allocation_item.update_execute_status(
db,
item.id,
"failed",
failure_reason=str(e)
)
async def _execute_transfer(self, db: Session, order_obj):
"""执行资产调拨"""
# 调拨逻辑与分配类似,但需要记录调出和调入网点
await self._execute_allocation(db, order_obj)
async def _execute_recovery(self, db: Session, order_obj):
"""执行资产回收"""
# 回收逻辑
await self._execute_allocation(db, order_obj)
async def _execute_maintenance_allocation(self, db: Session, order_obj):
"""执行维修分配"""
# 维修分配逻辑
await self._execute_allocation(db, order_obj)
async def _execute_scrap_allocation(self, db: Session, order_obj):
"""执行报废分配"""
# 报废分配逻辑
await self._execute_allocation(db, order_obj)
def _load_order_relations(
self,
db: Session,
obj
) -> Dict[str, Any]:
"""加载分配单关联信息"""
from app.models.user import User
from app.models.organization import Organization
result = {
"id": obj.id,
"order_code": obj.order_code,
"order_type": obj.order_type,
"title": obj.title,
"source_organization_id": obj.source_organization_id,
"target_organization_id": obj.target_organization_id,
"applicant_id": obj.applicant_id,
"approver_id": obj.approver_id,
"approval_status": obj.approval_status,
"approval_time": obj.approval_time,
"approval_remark": obj.approval_remark,
"expect_execute_date": obj.expect_execute_date,
"actual_execute_date": obj.actual_execute_date,
"executor_id": obj.executor_id,
"execute_status": obj.execute_status,
"remark": obj.remark,
"created_at": obj.created_at,
"updated_at": obj.updated_at
}
# 加载关联信息
if obj.source_organization_id:
source_org = db.query(Organization).filter(
Organization.id == obj.source_organization_id
).first()
if source_org:
result["source_organization"] = {
"id": source_org.id,
"org_name": source_org.org_name,
"org_type": source_org.org_type
}
if obj.target_organization_id:
target_org = db.query(Organization).filter(
Organization.id == obj.target_organization_id
).first()
if target_org:
result["target_organization"] = {
"id": target_org.id,
"org_name": target_org.org_name,
"org_type": target_org.org_type
}
if obj.applicant_id:
applicant = db.query(User).filter(User.id == obj.applicant_id).first()
if applicant:
result["applicant"] = {
"id": applicant.id,
"real_name": applicant.real_name,
"username": applicant.username
}
if obj.approver_id:
approver = db.query(User).filter(User.id == obj.approver_id).first()
if approver:
result["approver"] = {
"id": approver.id,
"real_name": approver.real_name,
"username": approver.username
}
if obj.executor_id:
executor = db.query(User).filter(User.id == obj.executor_id).first()
if executor:
result["executor"] = {
"id": executor.id,
"real_name": executor.real_name,
"username": executor.username
}
# 加载明细
items = allocation_item.get_by_order(db, obj.id)
result["items"] = [
{
"id": item.id,
"asset_id": item.asset_id,
"asset_code": item.asset_code,
"asset_name": item.asset_name,
"from_status": item.from_status,
"to_status": item.to_status,
"execute_status": item.execute_status,
"failure_reason": item.failure_reason
}
for item in items
]
return result
def _can_allocate(self, asset_status: str, order_type: str) -> bool:
"""判断资产是否可以分配"""
# 库存中或使用中的资产可以分配
if order_type in ["allocation", "transfer"]:
return asset_status in ["in_stock", "in_use"]
elif order_type == "recovery":
return asset_status == "in_use"
elif order_type == "maintenance":
return asset_status in ["in_stock", "in_use"]
elif order_type == "scrap":
return asset_status in ["in_stock", "in_use", "maintenance"]
return False
def _get_order_type_name(self, order_type: str) -> str:
"""获取单据类型中文名"""
type_names = {
"allocation": "分配",
"transfer": "调拨",
"recovery": "回收",
"maintenance": "维修",
"scrap": "报废"
}
return type_names.get(order_type, "操作")
async def _generate_order_code(self, db: Session, order_type: str) -> str:
"""生成分配单号"""
from datetime import datetime
import random
import string
# 单据类型前缀
prefix_map = {
"allocation": "AL",
"transfer": "TF",
"recovery": "RC",
"maintenance": "MT",
"scrap": "SC"
}
prefix = prefix_map.get(order_type, "AL")
# 日期部分
date_str = datetime.now().strftime("%Y%m%d")
# 序号部分4位随机数
sequence = "".join(random.choices(string.digits, k=4))
# 组合单号: AL202501240001
order_code = f"{prefix}{date_str}{sequence}"
# 检查是否重复,如果重复则重新生成
while allocation_order.get_by_code(db, order_code):
sequence = "".join(random.choices(string.digits, k=4))
order_code = f"{prefix}{date_str}{sequence}"
return order_code
# 创建全局实例
allocation_service = AllocationService()