feat: 移除PDI和订单号字段,新增设备巡检模块

- 从物料跟踪页面移除订单号列和表单字段
- 从导航菜单移除PDI管理,添加设备巡检
- 新增InspectionLocation和InspectionRecord后端模型和API
- 新增设备巡检前端页面(左侧点位列表,右侧设备和历史记录)
This commit is contained in:
2026-05-27 16:38:40 +08:00
commit 193da0018f
86 changed files with 11379 additions and 0 deletions

112
backend/app/api/pdi.py Normal file
View File

@@ -0,0 +1,112 @@
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, func, desc
from typing import Optional
from datetime import datetime
from app.database import get_db
from app.models.pdi import PDIRecord, L3Status, L2Status
from app.schemas.pdi import PDIRecordCreate, PDIRecordUpdate, PDIRecordOut
from app.schemas.common import Response, PageResponse
from app.services.auth_service import get_current_user
router = APIRouter()
@router.get("/stats", response_model=Response[dict])
async def get_pdi_stats(
db: AsyncSession = Depends(get_db),
_=Depends(get_current_user),
):
"""各状态PDI数量统计"""
total_q = await db.execute(select(func.count()).select_from(PDIRecord))
pending_q = await db.execute(
select(func.count()).select_from(PDIRecord).where(PDIRecord.l2_status == L2Status.pending)
)
proc_q = await db.execute(
select(func.count()).select_from(PDIRecord).where(PDIRecord.l2_status == L2Status.processing)
)
done_q = await db.execute(
select(func.count()).select_from(PDIRecord).where(PDIRecord.l2_status == L2Status.done)
)
confirmed_q = await db.execute(
select(func.count()).select_from(PDIRecord).where(PDIRecord.l3_status == L3Status.confirmed)
)
return Response.ok({
"total": total_q.scalar(),
"pending": pending_q.scalar(),
"processing": proc_q.scalar(),
"done": done_q.scalar(),
"confirmed": confirmed_q.scalar(),
})
@router.get("/", response_model=Response[PageResponse[PDIRecordOut]])
async def list_pdi(
page: int = 1,
page_size: int = 20,
coil_no: Optional[str] = None,
l3_status: Optional[str] = None,
l2_status: Optional[str] = None,
db: AsyncSession = Depends(get_db),
_=Depends(get_current_user),
):
query = select(PDIRecord).order_by(desc(PDIRecord.created_at))
if coil_no:
query = query.where(PDIRecord.coil_no.ilike(f"%{coil_no}%"))
if l3_status:
query = query.where(PDIRecord.l3_status == l3_status)
if l2_status:
query = query.where(PDIRecord.l2_status == l2_status)
total = (await db.execute(select(func.count()).select_from(query.subquery()))).scalar()
result = await db.execute(query.offset((page - 1) * page_size).limit(page_size))
items = [PDIRecordOut.model_validate(r) for r in result.scalars()]
return Response.ok(PageResponse(total=total, page=page, page_size=page_size, items=items))
@router.post("/", response_model=Response[PDIRecordOut])
async def create_pdi(
body: PDIRecordCreate,
db: AsyncSession = Depends(get_db),
_=Depends(get_current_user),
):
record = PDIRecord(**body.model_dump())
db.add(record)
await db.flush()
return Response.ok(PDIRecordOut.model_validate(record))
@router.put("/{pdi_id}", response_model=Response[PDIRecordOut])
async def update_pdi(
pdi_id: int,
body: PDIRecordUpdate,
db: AsyncSession = Depends(get_db),
_=Depends(get_current_user),
):
result = await db.execute(select(PDIRecord).where(PDIRecord.id == pdi_id))
record = result.scalar_one_or_none()
if not record:
raise HTTPException(status_code=404, detail="PDI记录不存在")
for k, v in body.model_dump(exclude_none=True).items():
setattr(record, k, v)
await db.flush()
return Response.ok(PDIRecordOut.model_validate(record))
@router.patch("/{pdi_id}/confirm", response_model=Response[PDIRecordOut])
async def confirm_pdi(
pdi_id: int,
db: AsyncSession = Depends(get_db),
_=Depends(get_current_user),
):
"""L2确认PDI将状态设置为processing"""
result = await db.execute(select(PDIRecord).where(PDIRecord.id == pdi_id))
record = result.scalar_one_or_none()
if not record:
raise HTTPException(status_code=404, detail="PDI记录不存在")
record.l2_status = L2Status.processing
record.confirm_time = datetime.utcnow()
await db.flush()
return Response.ok(PDIRecordOut.model_validate(record))