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.quality import QcTask, QcTaskItem, QcDefect from app.schemas.quality import ( QcTaskCreate, QcTaskUpdate, QcTaskOut, QcTaskItemCreate, QcTaskItemUpdate, QcTaskItemOut, QcDefectCreate, QcDefectUpdate, QcDefectOut, QcDefectBulkSave, ) from app.schemas.common import Response, PageResponse from app.services.auth_service import get_current_user router = APIRouter() # ─── 检验任务 ─────────────────────────────────────────────────────────────── @router.get("/tasks", response_model=Response[PageResponse[QcTaskOut]]) async def list_tasks( page: int = 1, page_size: int = 20, task_code: Optional[str] = None, coil_no: Optional[str] = None, status: Optional[int] = None, result: Optional[str] = None, start_date: Optional[str] = None, end_date: Optional[str] = None, db: AsyncSession = Depends(get_db), _=Depends(get_current_user), ): query = select(QcTask).where(QcTask.del_flag == 0).order_by(desc(QcTask.created_at)) if task_code: query = query.where(QcTask.task_code.ilike(f"%{task_code}%")) if coil_no: query = query.where(QcTask.coil_no.ilike(f"%{coil_no}%")) if status is not None: query = query.where(QcTask.status == status) if result: query = query.where(QcTask.result == result) if start_date: query = query.where(QcTask.created_at >= datetime.fromisoformat(start_date)) if end_date: query = query.where(QcTask.created_at <= datetime.fromisoformat(end_date + "T23:59:59")) total = (await db.execute(select(func.count()).select_from(query.subquery()))).scalar() result_rows = await db.execute(query.offset((page - 1) * page_size).limit(page_size)) items = [QcTaskOut.model_validate(r) for r in result_rows.scalars()] return Response.ok(PageResponse(total=total, page=page, page_size=page_size, items=items)) @router.post("/tasks", response_model=Response[QcTaskOut]) async def create_task(body: QcTaskCreate, db: AsyncSession = Depends(get_db), _=Depends(get_current_user)): task = QcTask(**body.model_dump()) db.add(task) await db.flush() return Response.ok(QcTaskOut.model_validate(task)) @router.get("/tasks/{task_id}", response_model=Response[QcTaskOut]) async def get_task(task_id: int, db: AsyncSession = Depends(get_db), _=Depends(get_current_user)): r = await db.execute(select(QcTask).where(QcTask.id == task_id, QcTask.del_flag == 0)) task = r.scalar_one_or_none() if not task: raise HTTPException(status_code=404, detail="任务不存在") return Response.ok(QcTaskOut.model_validate(task)) @router.put("/tasks/{task_id}", response_model=Response[QcTaskOut]) async def update_task(task_id: int, body: QcTaskUpdate, db: AsyncSession = Depends(get_db), _=Depends(get_current_user)): r = await db.execute(select(QcTask).where(QcTask.id == task_id, QcTask.del_flag == 0)) task = r.scalar_one_or_none() if not task: raise HTTPException(status_code=404, detail="任务不存在") for k, v in body.model_dump(exclude_none=True).items(): setattr(task, k, v) await db.flush() return Response.ok(QcTaskOut.model_validate(task)) @router.delete("/tasks/{task_id}", response_model=Response[dict]) async def delete_task(task_id: int, db: AsyncSession = Depends(get_db), _=Depends(get_current_user)): r = await db.execute(select(QcTask).where(QcTask.id == task_id)) task = r.scalar_one_or_none() if not task: raise HTTPException(status_code=404, detail="任务不存在") task.del_flag = 1 await db.flush() return Response.ok({}) # ─── 检验项目 ──────────────────────────────────────────────────────────────── @router.get("/tasks/{task_id}/items", response_model=Response[list[QcTaskItemOut]]) async def list_task_items(task_id: int, db: AsyncSession = Depends(get_db), _=Depends(get_current_user)): r = await db.execute(select(QcTaskItem).where(QcTaskItem.task_id == task_id).order_by(QcTaskItem.id)) return Response.ok([QcTaskItemOut.model_validate(i) for i in r.scalars()]) @router.post("/task-items", response_model=Response[QcTaskItemOut]) async def create_task_item(body: QcTaskItemCreate, db: AsyncSession = Depends(get_db), _=Depends(get_current_user)): item = QcTaskItem(**body.model_dump()) db.add(item) await db.flush() return Response.ok(QcTaskItemOut.model_validate(item)) @router.put("/task-items/{item_id}", response_model=Response[QcTaskItemOut]) async def update_task_item(item_id: int, body: QcTaskItemUpdate, db: AsyncSession = Depends(get_db), _=Depends(get_current_user)): r = await db.execute(select(QcTaskItem).where(QcTaskItem.id == item_id)) item = r.scalar_one_or_none() if not item: raise HTTPException(status_code=404, detail="检验项不存在") for k, v in body.model_dump(exclude_none=True).items(): setattr(item, k, v) await db.flush() return Response.ok(QcTaskItemOut.model_validate(item)) # ─── 缺陷记录 ──────────────────────────────────────────────────────────────── @router.get("/defects", response_model=Response[PageResponse[QcDefectOut]]) async def list_defects( page: int = 1, page_size: int = 20, coil_no: Optional[str] = None, defect_type: Optional[str] = None, degree: Optional[str] = None, start_date: Optional[str] = None, end_date: Optional[str] = None, db: AsyncSession = Depends(get_db), _=Depends(get_current_user), ): query = select(QcDefect).where(QcDefect.del_flag == 0).order_by(desc(QcDefect.created_at)) if coil_no: query = query.where(QcDefect.coil_no.ilike(f"%{coil_no}%")) if defect_type: query = query.where(QcDefect.defect_type.ilike(f"%{defect_type}%")) if degree: query = query.where(QcDefect.degree == degree) if start_date: query = query.where(QcDefect.created_at >= datetime.fromisoformat(start_date)) if end_date: query = query.where(QcDefect.created_at <= datetime.fromisoformat(end_date + "T23:59:59")) total = (await db.execute(select(func.count()).select_from(query.subquery()))).scalar() rows = await db.execute(query.offset((page - 1) * page_size).limit(page_size)) items = [QcDefectOut.model_validate(r) for r in rows.scalars()] return Response.ok(PageResponse(total=total, page=page, page_size=page_size, items=items)) @router.post("/defects", response_model=Response[QcDefectOut]) async def create_defect(body: QcDefectCreate, db: AsyncSession = Depends(get_db), _=Depends(get_current_user)): defect = QcDefect(**body.model_dump()) db.add(defect) await db.flush() return Response.ok(QcDefectOut.model_validate(defect)) @router.get("/defects/by-coil/{coil_no}", response_model=Response[list[QcDefectOut]]) async def list_defects_by_coil(coil_no: str, db: AsyncSession = Depends(get_db), _=Depends(get_current_user)): r = await db.execute( select(QcDefect) .where(QcDefect.coil_no == coil_no, QcDefect.del_flag == 0) .order_by(QcDefect.seq_no.asc().nulls_last(), QcDefect.id.asc()) ) return Response.ok([QcDefectOut.model_validate(d) for d in r.scalars()]) @router.post("/defects/bulk-save", response_model=Response[list[QcDefectOut]]) async def bulk_save_defects(body: QcDefectBulkSave, db: AsyncSession = Depends(get_db), _=Depends(get_current_user)): """按卷号替换全部缺陷记录(软删旧记录后批量插入)""" coil_no = body.coil_no # 软删旧记录 old = await db.execute(select(QcDefect).where(QcDefect.coil_no == coil_no, QcDefect.del_flag == 0)) for d in old.scalars(): d.del_flag = 1 # 插入新记录 saved: list[QcDefect] = [] for idx, item in enumerate(body.defects, start=1): data = item.model_dump() data["coil_no"] = coil_no if not data.get("seq_no"): data["seq_no"] = idx # 自动算长度 if data.get("start_position") is not None and data.get("end_position") is not None and not data.get("length_val"): data["length_val"] = round(float(data["end_position"]) - float(data["start_position"]), 3) d = QcDefect(**data) db.add(d) saved.append(d) await db.flush() return Response.ok([QcDefectOut.model_validate(d) for d in saved]) @router.put("/defects/{defect_id}", response_model=Response[QcDefectOut]) async def update_defect(defect_id: int, body: QcDefectUpdate, db: AsyncSession = Depends(get_db), _=Depends(get_current_user)): r = await db.execute(select(QcDefect).where(QcDefect.id == defect_id, QcDefect.del_flag == 0)) defect = r.scalar_one_or_none() if not defect: raise HTTPException(status_code=404, detail="缺陷记录不存在") for k, v in body.model_dump(exclude_none=True).items(): setattr(defect, k, v) await db.flush() return Response.ok(QcDefectOut.model_validate(defect)) @router.delete("/defects/{defect_id}", response_model=Response[dict]) async def delete_defect(defect_id: int, db: AsyncSession = Depends(get_db), _=Depends(get_current_user)): r = await db.execute(select(QcDefect).where(QcDefect.id == defect_id)) defect = r.scalar_one_or_none() if not defect: raise HTTPException(status_code=404, detail="缺陷记录不存在") defect.del_flag = 1 await db.flush() return Response.ok({})