feat: 重构质量管理和设备巡检模块

质量管理: 由平铺记录改为任务制工作流(qc_task/qc_task_item/qc_defect三表)
设备巡检: 由点位+记录改为巡检模板制(eqp_checklist/item/record/detail四表)
前端: Quality.vue 支持任务列表+检验项详情+缺陷记录双Tab
前端: Inspection.vue 支持模板管理+项目维护+巡检记录+明细查看

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-28 16:29:02 +08:00
parent 6ae24cb14d
commit b461f0d2f8
11 changed files with 1645 additions and 749 deletions

View File

@@ -1,13 +1,16 @@
from fastapi import APIRouter, Depends
from fastapi import APIRouter, Depends, HTTPException
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.inspection import InspectionLocation, InspectionRecord
from app.models.inspection import EqpChecklist, EqpChecklistItem, EqpInspectionRecord, EqpInspectionDetail
from app.schemas.inspection import (
InspectionLocationCreate, InspectionLocationOut,
InspectionRecordCreate, InspectionRecordOut,
EqpChecklistCreate, EqpChecklistUpdate, EqpChecklistOut,
EqpChecklistItemCreate, EqpChecklistItemOut,
EqpInspectionRecordCreate, EqpInspectionRecordOut,
EqpInspectionDetailOut,
)
from app.schemas.common import Response, PageResponse
from app.services.auth_service import get_current_user
@@ -15,61 +18,117 @@ from app.services.auth_service import get_current_user
router = APIRouter()
@router.get("/locations", response_model=Response[list[InspectionLocationOut]])
async def list_locations(
# ─── 巡检模板 ────────────────────────────────────────────────────────────────
@router.get("/checklists", response_model=Response[list[EqpChecklistOut]])
async def list_checklists(
equipment_code: Optional[str] = None,
db: AsyncSession = Depends(get_db),
_ = Depends(get_current_user),
_=Depends(get_current_user),
):
result = await db.execute(
select(InspectionLocation).order_by(InspectionLocation.sort_order, InspectionLocation.id)
)
items = [InspectionLocationOut.model_validate(r) for r in result.scalars()]
return Response.ok(items)
query = select(EqpChecklist).order_by(EqpChecklist.id)
if equipment_code:
query = query.where(EqpChecklist.equipment_code == equipment_code)
r = await db.execute(query)
return Response.ok([EqpChecklistOut.model_validate(c) for c in r.scalars()])
@router.post("/locations", response_model=Response[InspectionLocationOut])
async def create_location(
body: InspectionLocationCreate,
db: AsyncSession = Depends(get_db),
_ = Depends(get_current_user),
):
loc = InspectionLocation(**body.model_dump())
db.add(loc)
@router.post("/checklists", response_model=Response[EqpChecklistOut])
async def create_checklist(body: EqpChecklistCreate, db: AsyncSession = Depends(get_db), _=Depends(get_current_user)):
items_data = body.items or []
data = body.model_dump(exclude={"items"})
checklist = EqpChecklist(**data)
db.add(checklist)
await db.flush()
return Response.ok(InspectionLocationOut.model_validate(loc))
for item in items_data:
ci = EqpChecklistItem(checklist_id=checklist.id, **item.model_dump())
db.add(ci)
await db.flush()
return Response.ok(EqpChecklistOut.model_validate(checklist))
@router.get("/records", response_model=Response[PageResponse[InspectionRecordOut]])
@router.put("/checklists/{checklist_id}", response_model=Response[EqpChecklistOut])
async def update_checklist(checklist_id: int, body: EqpChecklistUpdate, db: AsyncSession = Depends(get_db), _=Depends(get_current_user)):
r = await db.execute(select(EqpChecklist).where(EqpChecklist.id == checklist_id))
checklist = r.scalar_one_or_none()
if not checklist:
raise HTTPException(status_code=404, detail="模板不存在")
for k, v in body.model_dump(exclude_none=True).items():
setattr(checklist, k, v)
await db.flush()
return Response.ok(EqpChecklistOut.model_validate(checklist))
@router.get("/checklists/{checklist_id}/items", response_model=Response[list[EqpChecklistItemOut]])
async def list_checklist_items(checklist_id: int, db: AsyncSession = Depends(get_db), _=Depends(get_current_user)):
r = await db.execute(
select(EqpChecklistItem).where(EqpChecklistItem.checklist_id == checklist_id)
.order_by(EqpChecklistItem.sort_order, EqpChecklistItem.id)
)
return Response.ok([EqpChecklistItemOut.model_validate(i) for i in r.scalars()])
@router.post("/checklist-items", response_model=Response[EqpChecklistItemOut])
async def create_checklist_item(
body: EqpChecklistItemCreate,
checklist_id: int,
db: AsyncSession = Depends(get_db),
_=Depends(get_current_user),
):
item = EqpChecklistItem(checklist_id=checklist_id, **body.model_dump())
db.add(item)
await db.flush()
return Response.ok(EqpChecklistItemOut.model_validate(item))
# ─── 巡检记录 ────────────────────────────────────────────────────────────────
@router.get("/records", response_model=Response[PageResponse[EqpInspectionRecordOut]])
async def list_records(
page: int = 1,
page_size: int = 30,
location_id: Optional[int] = None,
page_size: int = 20,
checklist_id: Optional[int] = None,
status: Optional[str] = None,
start_date: Optional[str] = None,
end_date: Optional[str] = None,
db: AsyncSession = Depends(get_db),
_ = Depends(get_current_user),
_=Depends(get_current_user),
):
query = select(InspectionRecord).order_by(desc(InspectionRecord.created_at))
if location_id:
query = query.where(InspectionRecord.location_id == location_id)
query = select(EqpInspectionRecord).order_by(desc(EqpInspectionRecord.created_at))
if checklist_id:
query = query.where(EqpInspectionRecord.checklist_id == checklist_id)
if status:
query = query.where(EqpInspectionRecord.status == status)
if start_date:
query = query.where(EqpInspectionRecord.inspect_time >= datetime.fromisoformat(start_date))
if end_date:
query = query.where(EqpInspectionRecord.inspect_time <= datetime.fromisoformat(end_date + "T23:59:59"))
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 = [InspectionRecordOut.model_validate(r) for r in result.scalars()]
rows = await db.execute(query.offset((page - 1) * page_size).limit(page_size))
items = [EqpInspectionRecordOut.model_validate(r) for r in rows.scalars()]
return Response.ok(PageResponse(total=total, page=page, page_size=page_size, items=items))
@router.post("/records", response_model=Response[InspectionRecordOut])
async def create_record(
body: InspectionRecordCreate,
db: AsyncSession = Depends(get_db),
_ = Depends(get_current_user),
):
loc_result = await db.execute(
select(InspectionLocation).where(InspectionLocation.id == body.location_id)
)
loc = loc_result.scalar_one_or_none()
record = InspectionRecord(
**body.model_dump(),
location_name=loc.name if loc else None,
)
@router.post("/records", response_model=Response[EqpInspectionRecordOut])
async def create_record(body: EqpInspectionRecordCreate, db: AsyncSession = Depends(get_db), _=Depends(get_current_user)):
details_data = body.details or []
# get checklist name
cl_r = await db.execute(select(EqpChecklist).where(EqpChecklist.id == body.checklist_id))
cl = cl_r.scalar_one_or_none()
data = body.model_dump(exclude={"details"})
record = EqpInspectionRecord(**data, checklist_name=cl.name if cl else None)
db.add(record)
await db.flush()
return Response.ok(InspectionRecordOut.model_validate(record))
for d in details_data:
detail = EqpInspectionDetail(record_id=record.id, **d.model_dump())
db.add(detail)
await db.flush()
return Response.ok(EqpInspectionRecordOut.model_validate(record))
@router.get("/records/{record_id}/details", response_model=Response[list[EqpInspectionDetailOut]])
async def get_record_details(record_id: int, db: AsyncSession = Depends(get_db), _=Depends(get_current_user)):
r = await db.execute(
select(EqpInspectionDetail).where(EqpInspectionDetail.record_id == record_id).order_by(EqpInspectionDetail.id)
)
return Response.ok([EqpInspectionDetailOut.model_validate(d) for d in r.scalars()])

View File

@@ -5,128 +5,177 @@ from typing import Optional
from datetime import datetime
from app.database import get_db
from app.models.quality import QualityRecord
from app.schemas.quality import QualityRecordCreate, QualityRecordUpdate, QualityRecordOut
from app.models.quality import QcTask, QcTaskItem, QcDefect
from app.schemas.quality import (
QcTaskCreate, QcTaskUpdate, QcTaskOut,
QcTaskItemCreate, QcTaskItemUpdate, QcTaskItemOut,
QcDefectCreate, QcDefectUpdate, QcDefectOut,
)
from app.schemas.common import Response, PageResponse
from app.services.auth_service import get_current_user
from app.services.prediction import QualityPredictionModel
router = APIRouter()
@router.get("/summary", response_model=Response[dict])
async def quality_summary(
# ─── 检验任务 ───────────────────────────────────────────────────────────────
@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),
):
"""合格率、平均评分、班次分组统计"""
total_q = await db.execute(select(func.count()).select_from(QualityRecord))
passed_q = await db.execute(
select(func.count()).select_from(QualityRecord).where(QualityRecord.is_passed == True)
)
avg_pi_q = await db.execute(select(func.avg(QualityRecord.pi_score)).select_from(QualityRecord))
avg_suf_q = await db.execute(select(func.avg(QualityRecord.surface_score)).select_from(QualityRecord))
# Grade distribution
grade_counts: dict = {}
for grade in ["A1", "A2", "B1", "B2", "C"]:
cnt_q = await db.execute(
select(func.count()).select_from(QualityRecord).where(QualityRecord.overall_grade == grade)
)
grade_counts[grade] = cnt_q.scalar() or 0
total = total_q.scalar() or 0
passed = passed_q.scalar() or 0
pass_rate = round(passed / total * 100, 1) if total > 0 else 0.0
return Response.ok({
"total": total,
"passed": passed,
"pass_rate": pass_rate,
"avg_pi_score": round(avg_pi_q.scalar() or 0, 1),
"avg_surface_score": round(avg_suf_q.scalar() or 0, 1),
"grade_distribution": grade_counts,
})
@router.get("/", response_model=Response[PageResponse[QualityRecordOut]])
async def list_quality(
page: int = 1,
page_size: int = 20,
coil_no: Optional[str] = None,
overall_grade: Optional[str] = None,
start_date: Optional[datetime] = None,
end_date: Optional[datetime] = None,
db: AsyncSession = Depends(get_db),
_=Depends(get_current_user),
):
query = select(QualityRecord).order_by(desc(QualityRecord.created_at))
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(QualityRecord.coil_no.ilike(f"%{coil_no}%"))
if overall_grade:
query = query.where(QualityRecord.overall_grade == overall_grade)
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(QualityRecord.created_at >= start_date)
query = query.where(QcTask.created_at >= datetime.fromisoformat(start_date))
if end_date:
query = query.where(QualityRecord.created_at <= end_date)
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 = [QualityRecordOut.model_validate(r) for r in result.scalars()]
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("/", response_model=Response[QualityRecordOut])
async def create_quality(
body: QualityRecordCreate,
db: AsyncSession = Depends(get_db),
_=Depends(get_current_user),
):
"""
创建质量记录。若未提供 pi_score / surface_score / overall_grade
则尝试用 QualityPredictionModel 自动填充需要thickness_actual和相关参数
"""
data = body.model_dump()
if (
data.get("pi_score") is None
and data.get("thickness_actual") is not None
):
# Use default avg values for auto-prediction when detailed params are absent
avg_speed = 100.0 # m/min default
acid_conc_avg = 160.0 # g/L default
acid_temp_avg = 75.0 # °C default
try:
pred = QualityPredictionModel(
thickness=data["thickness_actual"],
avg_speed=avg_speed,
acid_conc_avg=acid_conc_avg,
acid_temp_avg=acid_temp_avg,
).calculate()
data["pi_score"] = pred["pi_score"]
data["surface_score"] = pred["surface_score"]
data["overall_grade"] = pred["overall_grade"]
except Exception:
pass # best-effort, do not block creation
record = QualityRecord(**data)
db.add(record)
@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(QualityRecordOut.model_validate(record))
return Response.ok(QcTaskOut.model_validate(task))
@router.put("/{quality_id}", response_model=Response[QualityRecordOut])
async def update_quality(
quality_id: int,
body: QualityRecordUpdate,
db: AsyncSession = Depends(get_db),
_=Depends(get_current_user),
):
result = await db.execute(select(QualityRecord).where(QualityRecord.id == quality_id))
record = result.scalar_one_or_none()
if not record:
raise HTTPException(status_code=404, detail="质量记录不存在")
@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(record, k, v)
setattr(task, k, v)
await db.flush()
return Response.ok(QualityRecordOut.model_validate(record))
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.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({})

View File

@@ -6,9 +6,9 @@ from app.models.downtime import DowntimeRecord, DowntimeCategory
from app.models.equipment import Equipment, EquipmentMaintenance
from app.models.message import MessageLog
from app.models.pdi import PDIRecord
from app.models.quality import QualityRecord
from app.models.quality import QcTask, QcTaskItem, QcDefect
from app.models.energy import EnergyRecord
from app.models.inspection import InspectionLocation, InspectionRecord
from app.models.inspection import EqpChecklist, EqpChecklistItem, EqpInspectionRecord, EqpInspectionDetail
__all__ = [
"User",
@@ -19,7 +19,7 @@ __all__ = [
"Equipment", "EquipmentMaintenance",
"MessageLog",
"PDIRecord",
"QualityRecord",
"QcTask", "QcTaskItem", "QcDefect",
"EnergyRecord",
"InspectionLocation", "InspectionRecord",
"EqpChecklist", "EqpChecklistItem", "EqpInspectionRecord", "EqpInspectionDetail",
]

View File

@@ -1,28 +1,54 @@
from sqlalchemy import Column, Integer, String, Text, DateTime, ForeignKey, func
from sqlalchemy import Column, Integer, String, DateTime, Text, Boolean, ForeignKey, func
from app.database import Base
class InspectionLocation(Base):
__tablename__ = "inspection_locations"
class EqpChecklist(Base):
"""设备巡检模板"""
__tablename__ = "eqp_checklist"
id = Column(Integer, primary_key=True, index=True)
code = Column(String(30), unique=True, nullable=False, index=True)
name = Column(String(100), nullable=False)
description = Column(Text)
description = Column(Text, nullable=True)
equipment_code = Column(String(30), nullable=True, index=True)
equipment_name = Column(String(100), nullable=True)
period = Column(String(20), default="daily") # daily/weekly/monthly
is_active = Column(Boolean, default=True)
created_at = Column(DateTime, server_default=func.now())
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
class EqpChecklistItem(Base):
"""巡检模板项目"""
__tablename__ = "eqp_checklist_item"
id = Column(Integer, primary_key=True, index=True)
checklist_id = Column(Integer, ForeignKey("eqp_checklist.id"), nullable=False, index=True)
item_name = Column(String(100), nullable=False)
item_standard = Column(String(200), nullable=True)
sort_order = Column(Integer, default=0)
created_at = Column(DateTime, server_default=func.now())
class InspectionRecord(Base):
__tablename__ = "inspection_records"
class EqpInspectionRecord(Base):
"""巡检记录"""
__tablename__ = "eqp_inspection_record"
id = Column(Integer, primary_key=True, index=True)
location_id = Column(Integer, ForeignKey("inspection_locations.id"), nullable=False)
location_name = Column(String(100))
equipment_code = Column(String(30), index=True)
equipment_name = Column(String(100))
scan_code = Column(String(200))
checklist_id = Column(Integer, ForeignKey("eqp_checklist.id"), nullable=False, index=True)
checklist_name = Column(String(100), nullable=True)
inspector = Column(String(50), nullable=False)
result = Column(String(20), default="normal")
notes = Column(Text)
inspect_time = Column(DateTime, nullable=False)
status = Column(String(20), default="ok") # ok/issue/urgent
overall_result = Column(String(20), nullable=True) # pass/fail
remark = Column(Text, nullable=True)
created_at = Column(DateTime, server_default=func.now())
class EqpInspectionDetail(Base):
"""巡检记录明细"""
__tablename__ = "eqp_inspection_detail"
id = Column(Integer, primary_key=True, index=True)
record_id = Column(Integer, ForeignKey("eqp_inspection_record.id"), nullable=False, index=True)
checklist_item_id = Column(Integer, nullable=True)
item_name = Column(String(100), nullable=False)
actual_value = Column(String(100), nullable=True)
is_ok = Column(Boolean, default=True)
notes = Column(Text, nullable=True)
created_at = Column(DateTime, server_default=func.now())

View File

@@ -2,37 +2,64 @@ from sqlalchemy import Column, Integer, String, Float, DateTime, Text, Boolean,
from app.database import Base
class QualityRecord(Base):
"""质量检验记录"""
__tablename__ = "quality_records"
class QcTask(Base):
"""检验任务"""
__tablename__ = "qc_task"
id = Column(Integer, primary_key=True, index=True)
task_code = Column(String(50), unique=True, nullable=False, index=True)
coil_no = Column(String(30), nullable=True, index=True)
task_type = Column(String(30), nullable=True) # e.g. incoming/process/final
scheme_name = Column(String(100), nullable=True)
status = Column(Integer, default=0) # 0=待检验 1=检验中 2=待审核 3=完成
inspect_user = Column(String(50), nullable=True)
inspect_time = Column(DateTime, nullable=True)
audit_user = Column(String(50), nullable=True)
audit_time = Column(DateTime, nullable=True)
result = Column(String(20), nullable=True) # qualified/unqualified
remark = Column(Text, nullable=True)
del_flag = Column(Integer, default=0)
created_at = Column(DateTime, server_default=func.now())
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
id = Column(Integer, primary_key=True, index=True)
coil_no = Column(String(30), nullable=False, index=True, comment="卷号")
production_record_id = Column(Integer, ForeignKey("production_records.id"), nullable=True, comment="关联生产实绩")
# 实测规格
thickness_actual = Column(Float, nullable=True, comment="实测厚度 mm")
width_actual = Column(Float, nullable=True, comment="实测宽度 mm")
flatness = Column(Float, nullable=True, comment="平直度 IU")
crown = Column(Float, nullable=True, comment="凸度 μm")
class QcTaskItem(Base):
"""检验项目"""
__tablename__ = "qc_task_item"
id = Column(Integer, primary_key=True, index=True)
task_id = Column(Integer, ForeignKey("qc_task.id"), nullable=False, index=True)
item_name = Column(String(100), nullable=False)
item_type = Column(String(20), nullable=True) # quantitative/qualitative
standard_value = Column(Float, nullable=True)
upper_limit = Column(Float, nullable=True)
lower_limit = Column(Float, nullable=True)
unit = Column(String(20), nullable=True)
inspect_value = Column(String(50), nullable=True)
is_qualified = Column(Integer, nullable=True) # 1=qualified 0=unqualified
judge_result = Column(String(100), nullable=True)
inspect_user = Column(String(50), nullable=True)
inspect_time = Column(DateTime, nullable=True)
created_at = Column(DateTime, server_default=func.now())
# 表面缺陷
surface_defect_type = Column(String(50), nullable=True, comment="表面缺陷类型")
defect_length_m = Column(Float, nullable=True, comment="缺陷长度 m")
defect_position = Column(String(50), nullable=True, comment="缺陷位置")
# 质量模型评分
pi_score = Column(Float, nullable=True, comment="酸洗指数评分 0-100")
surface_score = Column(Float, nullable=True, comment="表面质量评分 0-100")
overall_grade = Column(String(5), nullable=True, comment="综合等级 A1/A2/B1/B2/C")
# 残酸 / 粗糙度
acid_residual = Column(Float, nullable=True, comment="残酸量 g/m²")
roughness_ra = Column(Float, nullable=True, comment="粗糙度 Ra μm")
# 检验信息
inspector = Column(String(50), nullable=True, comment="检验员")
inspect_time = Column(DateTime, nullable=True, comment="检验时间")
is_passed = Column(Boolean, default=True, comment="是否合格")
created_at = Column(DateTime, server_default=func.now())
class QcDefect(Base):
"""钢卷缺陷记录"""
__tablename__ = "qc_defect"
id = Column(Integer, primary_key=True, index=True)
coil_no = Column(String(30), nullable=True, index=True)
production_line = Column(String(50), nullable=True)
position = Column(String(50), nullable=True)
plate_surface = Column(String(20), nullable=True)
defect_code = Column(String(30), nullable=True)
defect_type = Column(String(50), nullable=True, index=True)
defect_rate = Column(Float, nullable=True)
defect_weight = Column(Float, nullable=True)
degree = Column(String(20), nullable=True) # light/normal/serious
judge_level = Column(String(20), nullable=True)
judge_by = Column(String(50), nullable=True)
judge_time = Column(DateTime, nullable=True)
main_mark = Column(Integer, nullable=True)
whole_coil_mark = Column(Integer, nullable=True)
remark = Column(Text, nullable=True)
del_flag = Column(Integer, default=0)
created_at = Column(DateTime, server_default=func.now())
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())

View File

@@ -1,37 +1,88 @@
from pydantic import BaseModel
from typing import Optional
from typing import Optional, List
from datetime import datetime
class InspectionLocationCreate(BaseModel):
code: str
name: str
description: Optional[str] = None
class EqpChecklistItemCreate(BaseModel):
item_name: str
item_standard: Optional[str] = None
sort_order: int = 0
class InspectionLocationOut(InspectionLocationCreate):
class EqpChecklistItemOut(EqpChecklistItemCreate):
id: int
checklist_id: int
created_at: datetime
class Config:
from_attributes = True
class InspectionRecordCreate(BaseModel):
location_id: int
class EqpChecklistCreate(BaseModel):
name: str
description: Optional[str] = None
equipment_code: Optional[str] = None
equipment_name: Optional[str] = None
scan_code: Optional[str] = None
inspector: str
result: str = "normal"
period: str = "daily"
items: Optional[List[EqpChecklistItemCreate]] = None
class EqpChecklistUpdate(BaseModel):
name: Optional[str] = None
description: Optional[str] = None
equipment_code: Optional[str] = None
equipment_name: Optional[str] = None
period: Optional[str] = None
is_active: Optional[bool] = None
class EqpChecklistOut(BaseModel):
id: int
name: str
description: Optional[str]
equipment_code: Optional[str]
equipment_name: Optional[str]
period: str
is_active: bool
created_at: datetime
class Config:
from_attributes = True
class EqpInspectionDetailCreate(BaseModel):
checklist_item_id: Optional[int] = None
item_name: str
actual_value: Optional[str] = None
is_ok: bool = True
notes: Optional[str] = None
class InspectionRecordOut(InspectionRecordCreate):
class EqpInspectionDetailOut(EqpInspectionDetailCreate):
id: int
location_name: Optional[str] = None
record_id: int
created_at: datetime
class Config:
from_attributes = True
class EqpInspectionRecordCreate(BaseModel):
checklist_id: int
inspector: str
inspect_time: datetime
status: str = "ok"
overall_result: Optional[str] = None
remark: Optional[str] = None
details: Optional[List[EqpInspectionDetailCreate]] = None
class EqpInspectionRecordOut(BaseModel):
id: int
checklist_id: int
checklist_name: Optional[str]
inspector: str
inspect_time: datetime
status: str
overall_result: Optional[str]
remark: Optional[str]
created_at: datetime
class Config:
from_attributes = True

View File

@@ -1,66 +1,136 @@
from pydantic import BaseModel
from typing import Optional
from typing import Optional, List
from datetime import datetime
class QualityRecordCreate(BaseModel):
coil_no: str
production_record_id: Optional[int] = None
thickness_actual: Optional[float] = None
width_actual: Optional[float] = None
flatness: Optional[float] = None
crown: Optional[float] = None
surface_defect_type: Optional[str] = None
defect_length_m: Optional[float] = None
defect_position: Optional[str] = None
pi_score: Optional[float] = None
surface_score: Optional[float] = None
overall_grade: Optional[str] = None
acid_residual: Optional[float] = None
roughness_ra: Optional[float] = None
inspector: Optional[str] = None
inspect_time: Optional[datetime] = None
is_passed: Optional[bool] = True
class QcTaskCreate(BaseModel):
task_code: str
coil_no: Optional[str] = None
task_type: Optional[str] = None
scheme_name: Optional[str] = None
inspect_user: Optional[str] = None
remark: Optional[str] = None
class QualityRecordUpdate(BaseModel):
thickness_actual: Optional[float] = None
width_actual: Optional[float] = None
flatness: Optional[float] = None
crown: Optional[float] = None
surface_defect_type: Optional[str] = None
defect_length_m: Optional[float] = None
defect_position: Optional[str] = None
pi_score: Optional[float] = None
surface_score: Optional[float] = None
overall_grade: Optional[str] = None
acid_residual: Optional[float] = None
roughness_ra: Optional[float] = None
inspector: Optional[str] = None
inspect_time: Optional[datetime] = None
is_passed: Optional[bool] = None
class QcTaskUpdate(BaseModel):
coil_no: Optional[str] = None
task_type: Optional[str] = None
scheme_name: Optional[str] = None
status: Optional[int] = None
inspect_user: Optional[str] = None
inspect_time: Optional[datetime] = None
audit_user: Optional[str] = None
audit_time: Optional[datetime] = None
result: Optional[str] = None
remark: Optional[str] = None
class QualityRecordOut(BaseModel):
id: int
coil_no: str
production_record_id: Optional[int]
thickness_actual: Optional[float]
width_actual: Optional[float]
flatness: Optional[float]
crown: Optional[float]
surface_defect_type: Optional[str]
defect_length_m: Optional[float]
defect_position: Optional[str]
pi_score: Optional[float]
surface_score: Optional[float]
overall_grade: Optional[str]
acid_residual: Optional[float]
roughness_ra: Optional[float]
inspector: Optional[str]
inspect_time: Optional[datetime]
is_passed: Optional[bool]
created_at: datetime
class QcTaskItemOut(BaseModel):
id: int
task_id: int
item_name: str
item_type: Optional[str]
standard_value: Optional[float]
upper_limit: Optional[float]
lower_limit: Optional[float]
unit: Optional[str]
inspect_value: Optional[str]
is_qualified: Optional[int]
judge_result: Optional[str]
inspect_user: Optional[str]
inspect_time: Optional[datetime]
created_at: datetime
class Config:
from_attributes = True
class QcTaskItemCreate(BaseModel):
task_id: int
item_name: str
item_type: Optional[str] = None
standard_value: Optional[float] = None
upper_limit: Optional[float] = None
lower_limit: Optional[float] = None
unit: Optional[str] = None
inspect_value: Optional[str] = None
is_qualified: Optional[int] = None
judge_result: Optional[str] = None
inspect_user: Optional[str] = None
inspect_time: Optional[datetime] = None
class QcTaskItemUpdate(BaseModel):
inspect_value: Optional[str] = None
is_qualified: Optional[int] = None
judge_result: Optional[str] = None
inspect_user: Optional[str] = None
inspect_time: Optional[datetime] = None
class QcTaskOut(BaseModel):
id: int
task_code: str
coil_no: Optional[str]
task_type: Optional[str]
scheme_name: Optional[str]
status: int
inspect_user: Optional[str]
inspect_time: Optional[datetime]
audit_user: Optional[str]
audit_time: Optional[datetime]
result: Optional[str]
remark: Optional[str]
created_at: datetime
class Config:
from_attributes = True
class QcDefectCreate(BaseModel):
coil_no: Optional[str] = None
production_line: Optional[str] = None
position: Optional[str] = None
plate_surface: Optional[str] = None
defect_code: Optional[str] = None
defect_type: Optional[str] = None
defect_rate: Optional[float] = None
defect_weight: Optional[float] = None
degree: Optional[str] = None
judge_level: Optional[str] = None
judge_by: Optional[str] = None
judge_time: Optional[datetime] = None
main_mark: Optional[int] = None
whole_coil_mark: Optional[int] = None
remark: Optional[str] = None
class QcDefectUpdate(BaseModel):
defect_type: Optional[str] = None
defect_rate: Optional[float] = None
defect_weight: Optional[float] = None
degree: Optional[str] = None
judge_level: Optional[str] = None
judge_by: Optional[str] = None
judge_time: Optional[datetime] = None
remark: Optional[str] = None
class QcDefectOut(BaseModel):
id: int
coil_no: Optional[str]
production_line: Optional[str]
position: Optional[str]
plate_surface: Optional[str]
defect_code: Optional[str]
defect_type: Optional[str]
defect_rate: Optional[float]
defect_weight: Optional[float]
degree: Optional[str]
judge_level: Optional[str]
judge_by: Optional[str]
judge_time: Optional[datetime]
main_mark: Optional[int]
whole_coil_mark: Optional[int]
remark: Optional[str]
created_at: datetime
class Config:
from_attributes = True