feat: 产品接口暴露lifecycle_phase并做阶段感知状态校验

This commit is contained in:
2026-09-14 14:49:34 +08:00
parent b9f9b897a1
commit b40340ea55
3 changed files with 37 additions and 3 deletions

View File

@ -6,6 +6,13 @@ from sqlalchemy import select, or_, cast, String, delete, update
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload
from app.core.lifecycle import (
ALL_OVERALL_STEPS,
allowed_steps,
is_step_allowed,
phase_label,
resolve_phase_for_step,
)
from app.models.product import Product
from app.models.production_order import ProductionOrder
from app.models.task import Task
@ -279,6 +286,7 @@ async def get_product_by_serial(db: AsyncSession, serial_number: str) -> Product
current_location_id=product.current_location_id,
overall_status=product.overall_status,
status=product.status,
lifecycle_phase=product.lifecycle_phase,
created_at=product.created_at,
top_level_tasks=[
TaskSummaryResponse.model_validate(t) for t in top_tasks
@ -365,6 +373,7 @@ async def create_product(db: AsyncSession, data: ProductCreate, creator_username
current_location_name=creator_display_name or None,
overall_status=product.overall_status,
status=product.status,
lifecycle_phase=product.lifecycle_phase,
created_at=product.created_at,
)
@ -413,11 +422,14 @@ async def update_product(db: AsyncSession, product_id: uuid.UUID, data: ProductU
current_location_id=product.current_location_id,
overall_status=product.overall_status,
status=product.status,
lifecycle_phase=product.lifecycle_phase,
created_at=product.created_at,
)
VALID_OVERALL_STATUS = {"备货", "生产", "测试", "维修", "在库", "待仓库收货", "已入库", "已出库"}
# 全部合法宏观状态(两阶段并集)— 仅作"完全非法取值"的第一道粗筛;
# 阶段内的细分校验(售后回流设备禁止改回「备货 / 生产」)见下方 is_step_allowed
VALID_OVERALL_STATUS = ALL_OVERALL_STEPS
async def update_overall_status(
@ -434,7 +446,7 @@ async def update_overall_status(
if status_value not in VALID_OVERALL_STATUS:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"无效状态: {status_value},合法值: {', '.join(sorted(VALID_OVERALL_STATUS))}",
detail=f"无效状态: {status_value},合法值: {'、'.join(sorted(ALL_OVERALL_STEPS))}",
)
result = await db.execute(
@ -480,6 +492,19 @@ async def update_overall_status(
detail="只有 SUPER_ADMIN 或当前操作该产品主线任务的人才能修改宏观状态",
)
# ── 选项隔离:阶段感知校验 ──
# 售后回流设备禁止被改回「备货 / 生产」;选定售后专属工序(发货测试 / 售后维修)
# 则设备随即进入售后生命周期(无历史记录的老设备由此进入售后阶段)。
phase = resolve_phase_for_step(product.lifecycle_phase, status_value)
if not is_step_allowed(phase, status_value):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=(
f"产品当前处于{phase_label(product.lifecycle_phase)},不允许改为「{status_value}」。"
f"该阶段可选:{'、'.join(allowed_steps(product.lifecycle_phase))}"
),
)
product.lifecycle_phase = phase
product.overall_status = status_value
# 同步 status 字段,保证与整体状态口径一致(修复"只改整体状态不改 status"的旧缺陷)
_OVERALL_TO_STATUS = {
@ -829,6 +854,7 @@ async def get_all_products(
macro_status=_resolve_macro_status(p),
overall_status=overall_names.get(p.id) or p.overall_status,
status=p.status,
lifecycle_phase=p.lifecycle_phase,
created_at=p.created_at,
latest_record_time=latest_record_map.get(p.id, (None, None, False, None))[0],
latest_record_content=latest_record_map.get(p.id, (None, None, False, None))[1],