diff --git a/backend/app/api/plan.py b/backend/app/api/plan.py index fcbe658..2aa5a02 100644 --- a/backend/app/api/plan.py +++ b/backend/app/api/plan.py @@ -142,6 +142,28 @@ async def move_to_saddle(plan_id: int, db: AsyncSession = Depends(get_db), _ = D return Response.ok(PlanOut.model_validate(plan)) +@router.get("/positions/all", response_model=Response[list[str]]) +async def list_positions(_ = Depends(get_current_user)): + """可移动到的入口位置列表(含唯一上卷鞍座)。""" + return Response.ok(line_service.ENTRY_POSITIONS) + + +@router.patch("/{plan_id}/move", response_model=Response[PlanOut]) +async def move_plan(plan_id: int, position: str = Query(...), 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="计划不存在") + try: + await line_service.place_at_position(db, plan, position) + except ValueError as e: + raise HTTPException(status_code=400, detail=str(e)) + await db.flush() + await db.refresh(plan) + return Response.ok(PlanOut.model_validate(plan)) + + @router.patch("/{plan_id}/commit", response_model=Response[PlanOut]) async def commit_producing(plan_id: int, db: AsyncSession = Depends(get_db), _ = Depends(get_current_user)): """投入生产:把鞍座上的计划置为生产中(兜底未实时变化的数据)。""" diff --git a/backend/app/database.py b/backend/app/database.py index 805f279..174976e 100644 --- a/backend/app/database.py +++ b/backend/app/database.py @@ -59,7 +59,8 @@ async def _run_migrations(conn): "ALTER TABLE production_plans ADD COLUMN IF NOT EXISTS incoming_weight DOUBLE PRECISION", "ALTER TABLE production_plans ADD COLUMN IF NOT EXISTS incoming_od DOUBLE PRECISION", "ALTER TABLE production_plans ADD COLUMN IF NOT EXISTS split_weights JSONB", - # 上卷鞍座 / 生产联动字段 + # 入口位置 / 上卷鞍座 / 生产联动字段 + "ALTER TABLE production_plans ADD COLUMN IF NOT EXISTS position VARCHAR(30)", "ALTER TABLE production_plans ADD COLUMN IF NOT EXISTS on_saddle INTEGER DEFAULT 0", "ALTER TABLE production_plans ADD COLUMN IF NOT EXISTS saddle_at TIMESTAMP", "ALTER TABLE production_plans ADD COLUMN IF NOT EXISTS run_started_at TIMESTAMP", diff --git a/backend/app/models/plan.py b/backend/app/models/plan.py index ea677e1..5b6746f 100644 --- a/backend/app/models/plan.py +++ b/backend/app/models/plan.py @@ -35,7 +35,8 @@ class ProductionPlan(Base): incoming_od = Column(Float, comment="来料外径 mm") split_weights = Column(JSON, comment="分卷重量 [t,...]") - # 上卷鞍座 / 生产联动 + # 入口位置 / 上卷鞍座 / 生产联动 + position = Column(String(30), comment="当前入口位置(上卷小车/称重位/地辊/倒卷小车/上卷鞍座)") on_saddle = Column(Integer, default=0, comment="是否在上卷鞍座 0/1") saddle_at = Column(DateTime, comment="移动到鞍座时间") run_started_at = Column(DateTime, comment="投入生产(有速度)时间") diff --git a/backend/app/schemas/plan.py b/backend/app/schemas/plan.py index 3e685c0..f6da6ad 100644 --- a/backend/app/schemas/plan.py +++ b/backend/app/schemas/plan.py @@ -78,7 +78,8 @@ class PlanOut(BaseModel): remark: Optional[str] = None created_by: Optional[str] = None created_at: datetime - # 上卷鞍座 / 生产联动 + # 入口位置 / 上卷鞍座 / 生产联动 + position: Optional[str] = None on_saddle: Optional[int] = 0 saddle_at: Optional[datetime] = None run_started_at: Optional[datetime] = None diff --git a/backend/app/services/line_service.py b/backend/app/services/line_service.py index c447ae9..b7aa844 100644 --- a/backend/app/services/line_service.py +++ b/backend/app/services/line_service.py @@ -17,12 +17,45 @@ from app.models.production import ProductionRecord from app.models.downtime import DowntimeRecord from app.models.line_state import LineState +# ── 入口位置 ── +SADDLE_NAME = "上卷鞍座" # 唯一会触发生产联动的位置 +ENTRY_POSITIONS = [ + "1#上卷小车", "2#上卷小车", + "1#称重位", "2#称重位", + "1#地辊", "2#地辊", + "1#倒卷小车", "2#倒卷小车", + SADDLE_NAME, +] + # ── 仿真参数 ── TARGET_LENGTH_M = 2000.0 # 带头目标长度(生产完成阈值) SIM_SPEED_M_MIN = 600.0 # 仿真线速度 m/min(2000m ≈ 200s) DOWNTIME_THRESHOLD_S = 600 # 速度为0持续超过该秒数判定停机(10min) +async def place_at_position(db: AsyncSession, plan: ProductionPlan, position: str): + """把计划放到任意入口位置;位置唯一占用。上卷鞍座单独触发生产联动。""" + if position not in ENTRY_POSITIONS: + raise ValueError("未知入口位置") + if position == SADDLE_NAME: + await move_to_saddle(db, plan) + return + # 普通位置:清除同位置上的其它计划,移出鞍座(若在) + res = await db.execute( + select(ProductionPlan).where(ProductionPlan.position == position, ProductionPlan.id != plan.id) + ) + for o in res.scalars(): + o.position = None + plan.on_saddle = 0 + plan.run_started_at = None + plan.run_speed = 0 + plan.run_length_m = 0 + plan.position = position + if plan.status == "producing": + plan.status = "online" + await ensure_online(db) + + def _shift_of(dt: datetime) -> str: return "甲" if 8 <= dt.hour < 20 else "乙" @@ -60,6 +93,7 @@ async def move_to_saddle(db: AsyncSession, plan: ProductionPlan): if occupied and occupied.id != plan.id: raise ValueError("上卷鞍座已被占用,请等待当前钢卷生产完成") plan.on_saddle = 1 + plan.position = SADDLE_NAME plan.saddle_at = datetime.now() plan.run_started_at = None plan.run_speed = 0 @@ -86,6 +120,7 @@ async def _produce(db: AsyncSession, plan: ProductionPlan): plan.status = "produced" plan.produced_at = now plan.on_saddle = 0 + plan.position = None plan.run_speed = 0 plan.run_length_m = TARGET_LENGTH_M diff --git a/frontend/src/api/index.js b/frontend/src/api/index.js index a0f3ff5..a9cc5e1 100644 --- a/frontend/src/api/index.js +++ b/frontend/src/api/index.js @@ -20,8 +20,10 @@ export const getPlans = params => request.get('/plan/', { params }) export const createPlan = data => request.post('/plan/', data) export const updatePlan = (id, data) => request.put(`/plan/${id}`, data) export const confirmPlan = id => request.patch(`/plan/${id}/confirm`) -export const startProducing = id => request.patch(`/plan/${id}/start`) // 移动到上卷鞍座 +export const startProducing = id => request.patch(`/plan/${id}/start`) // 移动到上卷鞍座(兼容) export const moveToSaddle = id => request.patch(`/plan/${id}/start`) +export const movePlan = (id, position) => request.patch(`/plan/${id}/move`, null, { params: { position } }) +export const getPositions = () => request.get('/plan/positions/all') export const commitProducing = id => request.patch(`/plan/${id}/commit`) // 投入生产 export const getSaddle = () => request.get('/plan/saddle/current') export const seedPlans = (count = 50) => request.post('/plan/seed', null, { params: { count } }) diff --git a/frontend/src/views/EntryTracking.vue b/frontend/src/views/EntryTracking.vue index 4cfe2c5..eaa1ae6 100644 --- a/frontend/src/views/EntryTracking.vue +++ b/frontend/src/views/EntryTracking.vue @@ -51,34 +51,31 @@