From b461f0d2f867f7d19eff68d79dbdde0ffa9029b3 Mon Sep 17 00:00:00 2001 From: wangyu <823267011@qq.com> Date: Thu, 28 May 2026 16:29:02 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=87=8D=E6=9E=84=E8=B4=A8=E9=87=8F?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E5=92=8C=E8=AE=BE=E5=A4=87=E5=B7=A1=E6=A3=80?= =?UTF-8?q?=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 质量管理: 由平铺记录改为任务制工作流(qc_task/qc_task_item/qc_defect三表) 设备巡检: 由点位+记录改为巡检模板制(eqp_checklist/item/record/detail四表) 前端: Quality.vue 支持任务列表+检验项详情+缺陷记录双Tab 前端: Inspection.vue 支持模板管理+项目维护+巡检记录+明细查看 Co-Authored-By: Claude Sonnet 4.6 --- .DS_Store | Bin 8196 -> 10244 bytes backend/app/api/inspection.py | 149 +++-- backend/app/api/quality.py | 259 +++++---- backend/app/models/__init__.py | 8 +- backend/app/models/inspection.py | 58 +- backend/app/models/quality.py | 87 ++- backend/app/schemas/inspection.py | 81 ++- backend/app/schemas/quality.py | 182 ++++-- frontend/src/api/index.js | 23 +- frontend/src/views/Inspection.vue | 640 +++++++++++++++------ frontend/src/views/Quality.vue | 907 +++++++++++++++++++----------- 11 files changed, 1645 insertions(+), 749 deletions(-) diff --git a/.DS_Store b/.DS_Store index 828a82fe32499b86a7b89717a12a0cee301e5dde..c4f49fd27dd405b5deea1bb0dbdc443a2bb27c55 100644 GIT binary patch delta 653 zcmZp1XbF&DU|?W$DortDU{C-uIe-{M3-C-V6q~50C@2EtFar4uIq8PM$@#ej8w;ng zPizp}%+A5WQ4f-3VbEhpXUJqIfvEtBF)*a9bw!rT&3AE0%E?axig6tO^mALqzOBP1}Xw7K~{n62vL0xJE$Wt zR3V&#TNT(r+`#YvyM?uYVKR@vZGLDNfyIR8T-kg}K$$H7+1exiZ{|xpg8DrL=#ylI z;@o_Mh1ld#{4TVBVe&^Y>3R-^G=?Ie{yc^fY~Hti4RiyB8f2H~n;nwTgt!EYI)sa` zs}qG-isCdDVA^=jCddr-J%a)_kOrm!0iKP8-= 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()]) diff --git a/backend/app/api/quality.py b/backend/app/api/quality.py index 8427671..256be56 100644 --- a/backend/app/api/quality.py +++ b/backend/app/api/quality.py @@ -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({}) diff --git a/backend/app/models/__init__.py b/backend/app/models/__init__.py index 504fa3c..8dc928a 100644 --- a/backend/app/models/__init__.py +++ b/backend/app/models/__init__.py @@ -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", ] diff --git a/backend/app/models/inspection.py b/backend/app/models/inspection.py index 5f59f0f..64b1a33 100644 --- a/backend/app/models/inspection.py +++ b/backend/app/models/inspection.py @@ -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()) diff --git a/backend/app/models/quality.py b/backend/app/models/quality.py index 56aeeda..e63a267 100644 --- a/backend/app/models/quality.py +++ b/backend/app/models/quality.py @@ -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()) diff --git a/backend/app/schemas/inspection.py b/backend/app/schemas/inspection.py index 815d250..cbc8562 100644 --- a/backend/app/schemas/inspection.py +++ b/backend/app/schemas/inspection.py @@ -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 diff --git a/backend/app/schemas/quality.py b/backend/app/schemas/quality.py index 040932d..2e3e49c 100644 --- a/backend/app/schemas/quality.py +++ b/backend/app/schemas/quality.py @@ -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 diff --git a/frontend/src/api/index.js b/frontend/src/api/index.js index c17361d..95d0aa6 100644 --- a/frontend/src/api/index.js +++ b/frontend/src/api/index.js @@ -52,13 +52,24 @@ export const calibrateQuality = data => request.post('/prediction/calibr export const resetCalibration = model => request.post(`/prediction/calibration/reset/${model}`) // 设备巡检 -export const getInspectionLocations = () => request.get('/inspection/locations') -export const createInspectionLocation = data => request.post('/inspection/locations', data) +export const getChecklists = params => request.get('/inspection/checklists', { params }) +export const createChecklist = data => request.post('/inspection/checklists', data) +export const updateChecklist = (id, data) => request.put(`/inspection/checklists/${id}`, data) +export const getChecklistItems = id => request.get(`/inspection/checklists/${id}/items`) +export const createChecklistItem = (checklistId, data) => request.post('/inspection/checklist-items', data, { params: { checklist_id: checklistId } }) export const getInspectionRecords = params => request.get('/inspection/records', { params }) export const createInspectionRecord = data => request.post('/inspection/records', data) +export const getInspectionRecordDetails = id => request.get(`/inspection/records/${id}/details`) // 质量管理 -export const getQualityList = params => request.get('/quality/', { params }) -export const createQuality = data => request.post('/quality/', data) -export const updateQuality = (id, data) => request.put(`/quality/${id}`, data) -export const getQualitySummary = () => request.get('/quality/summary') +export const getQcTasks = params => request.get('/quality/tasks', { params }) +export const createQcTask = data => request.post('/quality/tasks', data) +export const updateQcTask = (id, data) => request.put(`/quality/tasks/${id}`, data) +export const deleteQcTask = id => request.delete(`/quality/tasks/${id}`) +export const getQcTaskItems = taskId => request.get(`/quality/tasks/${taskId}/items`) +export const createQcTaskItem = data => request.post('/quality/task-items', data) +export const updateQcTaskItem = (id, data) => request.put(`/quality/task-items/${id}`, data) +export const getQcDefects = params => request.get('/quality/defects', { params }) +export const createQcDefect = data => request.post('/quality/defects', data) +export const updateQcDefect = (id, data) => request.put(`/quality/defects/${id}`, data) +export const deleteQcDefect = id => request.delete(`/quality/defects/${id}`) diff --git a/frontend/src/views/Inspection.vue b/frontend/src/views/Inspection.vue index 783a55f..cc6b9bc 100644 --- a/frontend/src/views/Inspection.vue +++ b/frontend/src/views/Inspection.vue @@ -1,168 +1,374 @@