feat: 实时数据持久化到计划/实绩 + 质量管理改版(钢卷信息+异常记录+继承)
- 在产计划持久化实时数据快照(run_data),物料跟踪每次轮询上报;生产完成写入实绩 process_data - 实绩页点击行展开生产阶段数据 + 实时数据快照(测点列表) - 质量管理默认进入异常管理,改为全宽:顶部选卷 + 钢卷信息(5列) + 异常记录表 - 异常记录新增「继承来源」列与「继承」按钮(从来源卷复制缺陷),进入即预置6行 - qc_defect 增加 inherit_source 字段 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, Body
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select, func, desc
|
||||
from typing import Optional
|
||||
@@ -192,6 +192,18 @@ async def commit_producing(plan_id: int, db: AsyncSession = Depends(get_db), _ =
|
||||
return Response.ok(PlanOut.model_validate(plan))
|
||||
|
||||
|
||||
@router.patch("/{plan_id}/runtime", response_model=Response[dict])
|
||||
async def save_runtime(plan_id: int, body: dict = Body(...), db: AsyncSession = Depends(get_db), _ = Depends(get_current_user)):
|
||||
"""持久化在产卷的实时数据快照到对应计划。"""
|
||||
result = await db.execute(select(ProductionPlan).where(ProductionPlan.id == plan_id))
|
||||
plan = result.scalar_one_or_none()
|
||||
if not plan:
|
||||
raise HTTPException(status_code=404, detail="计划不存在")
|
||||
if plan.status == "producing":
|
||||
plan.run_data = body
|
||||
return Response.ok({"ok": True})
|
||||
|
||||
|
||||
@router.get("/saddle/current", response_model=Response[Optional[PlanOut]])
|
||||
async def get_saddle(db: AsyncSession = Depends(get_db), _ = Depends(get_current_user)):
|
||||
"""上卷鞍座当前计划(含实时速度/已生产长度),并推进联动。"""
|
||||
|
||||
@@ -67,6 +67,9 @@ async def _run_migrations(conn):
|
||||
"ALTER TABLE production_plans ADD COLUMN IF NOT EXISTS run_speed DOUBLE PRECISION DEFAULT 0",
|
||||
"ALTER TABLE production_plans ADD COLUMN IF NOT EXISTS run_length_m DOUBLE PRECISION DEFAULT 0",
|
||||
"ALTER TABLE production_plans ADD COLUMN IF NOT EXISTS produced_at TIMESTAMP",
|
||||
"ALTER TABLE production_plans ADD COLUMN IF NOT EXISTS run_data JSONB",
|
||||
"ALTER TABLE production_records ADD COLUMN IF NOT EXISTS process_data JSONB",
|
||||
"ALTER TABLE qc_defect ADD COLUMN IF NOT EXISTS inherit_source VARCHAR(30)",
|
||||
# 状态列改为 VARCHAR 以适配新值
|
||||
"ALTER TABLE production_plans ALTER COLUMN status TYPE VARCHAR(20) USING status::text",
|
||||
# production_records 新字段
|
||||
|
||||
@@ -43,6 +43,7 @@ class ProductionPlan(Base):
|
||||
run_speed = Column(Float, default=0, comment="当前线速度 m/min")
|
||||
run_length_m = Column(Float, default=0, comment="带头已生产长度 m")
|
||||
produced_at = Column(DateTime, comment="生产完成时间")
|
||||
run_data = Column(JSON, comment="生产期间实时数据快照")
|
||||
|
||||
# 兼容历史字段
|
||||
shift = Column(String(10), comment="班次")
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from sqlalchemy import Column, Integer, String, Float, DateTime, Text, ForeignKey, func
|
||||
from sqlalchemy import Column, Integer, String, Float, DateTime, Text, ForeignKey, JSON, func
|
||||
from app.database import Base
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ class ProductionRecord(Base):
|
||||
length_per_ton = Column(Float, comment="吨钢长度 m/t")
|
||||
offline_time = Column(DateTime, comment="下线时间")
|
||||
status = Column(String(20), default="UNWEIGH", comment="状态: UNWEIGH/PRODUCT")
|
||||
process_data = Column(JSON, comment="生产阶段实时数据快照")
|
||||
|
||||
# 兼容历史字段
|
||||
shift_date = Column(DateTime)
|
||||
|
||||
@@ -57,6 +57,7 @@ class QcDefect(Base):
|
||||
side_middle = Column(Boolean, default=False, comment="中间")
|
||||
side_drive = Column(Boolean, default=False, comment="驱动侧")
|
||||
is_main = Column(Boolean, default=False, comment="主缺陷")
|
||||
inherit_source = Column(String(30), nullable=True, comment="继承来源卷号")
|
||||
image_url = Column(String(255), nullable=True, comment="缺陷图片URL")
|
||||
|
||||
# 兼容旧字段
|
||||
|
||||
@@ -86,6 +86,7 @@ class PlanOut(BaseModel):
|
||||
run_speed: Optional[float] = 0
|
||||
run_length_m: Optional[float] = 0
|
||||
produced_at: Optional[datetime] = None
|
||||
run_data: Optional[dict] = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
@@ -94,6 +94,7 @@ class ProductionRecordOut(BaseModel):
|
||||
inlet_width: Optional[float] = None
|
||||
quality_grade: Optional[str] = None
|
||||
operator: Optional[str] = None
|
||||
process_data: Optional[dict] = None
|
||||
created_at: datetime
|
||||
|
||||
class Config:
|
||||
|
||||
@@ -98,6 +98,7 @@ class QcDefectBase(BaseModel):
|
||||
side_middle: Optional[bool] = False
|
||||
side_drive: Optional[bool] = False
|
||||
is_main: Optional[bool] = False
|
||||
inherit_source: Optional[str] = None
|
||||
image_url: Optional[str] = None
|
||||
defect_code: Optional[str] = None
|
||||
defect_type: Optional[str] = None
|
||||
@@ -130,6 +131,7 @@ class QcDefectUpdate(BaseModel):
|
||||
side_middle: Optional[bool] = None
|
||||
side_drive: Optional[bool] = None
|
||||
is_main: Optional[bool] = None
|
||||
inherit_source: Optional[str] = None
|
||||
image_url: Optional[str] = None
|
||||
defect_code: Optional[str] = None
|
||||
defect_type: Optional[str] = None
|
||||
@@ -156,6 +158,7 @@ class QcDefectOut(BaseModel):
|
||||
side_middle: Optional[bool] = None
|
||||
side_drive: Optional[bool] = None
|
||||
is_main: Optional[bool] = None
|
||||
inherit_source: Optional[str] = None
|
||||
image_url: Optional[str] = None
|
||||
production_line: Optional[str] = None
|
||||
position: Optional[str] = None
|
||||
|
||||
@@ -148,6 +148,7 @@ async def _produce(db: AsyncSession, plan: ProductionPlan):
|
||||
inlet_width=plan.incoming_width,
|
||||
quality_grade="A",
|
||||
operator="系统",
|
||||
process_data=plan.run_data,
|
||||
)
|
||||
db.add(rec)
|
||||
logger.info(f"生产完成并产生实绩: {plan.cold_coil_no or plan.plan_no}")
|
||||
|
||||
Reference in New Issue
Block a user