feat(linkage): 鞍座改为预备生产位 + 在线改人工触发 + 计划删除
- 上卷鞍座=预备生产位(不生产);投入生产后离开鞍座、进入生产中并转入物料跟踪,鞍座随即空出
- 在线状态改为人工触发:移动到入口端才变在线,引擎不再自动上线(ensure_online 置空)
- 单卷在产:投入生产时若已有在产卷则拒绝
- 物料跟踪显示在产卷实时进度(客户端外推带头长度/进度条)
- 入口跟踪鞍座卡片改为预备生产展示(去掉进度)
- 计划管理新增删除按钮 + DELETE /plan/{id}(生产中不可删)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -51,9 +51,9 @@ async def place_at_position(db: AsyncSession, plan: ProductionPlan, position: st
|
||||
plan.run_speed = 0
|
||||
plan.run_length_m = 0
|
||||
plan.position = position
|
||||
if plan.status == "producing":
|
||||
# 移动到入口端即变「在线」(人工触发,非自动)
|
||||
if plan.status != "produced":
|
||||
plan.status = "online"
|
||||
await ensure_online(db)
|
||||
|
||||
|
||||
def _shift_of(dt: datetime) -> str:
|
||||
@@ -66,70 +66,40 @@ async def _saddle_plan(db: AsyncSession):
|
||||
|
||||
|
||||
async def ensure_online(db: AsyncSession):
|
||||
"""单卷在产:有卷在上卷鞍座/生产中时不保留在线队首;否则把最早 ready 置为唯一在线。"""
|
||||
res = await db.execute(
|
||||
select(ProductionPlan).where(ProductionPlan.status == "online", ProductionPlan.on_saddle != 1)
|
||||
)
|
||||
online = list(res.scalars())
|
||||
|
||||
# 是否已有在产/在鞍座的卷
|
||||
res2 = await db.execute(
|
||||
select(ProductionPlan).where(
|
||||
(ProductionPlan.status == "producing") | (ProductionPlan.on_saddle == 1)
|
||||
)
|
||||
)
|
||||
active = res2.scalars().first() is not None
|
||||
|
||||
if active:
|
||||
# 正在生产时,不再保留「在线」队首,全部回退 ready,等当前卷完成
|
||||
for p in online:
|
||||
p.status = "ready"
|
||||
return
|
||||
|
||||
if len(online) > 1:
|
||||
online.sort(key=lambda p: (p.plan_date or datetime.max, p.id))
|
||||
for p in online[1:]:
|
||||
p.status = "ready"
|
||||
online = online[:1]
|
||||
if not online:
|
||||
res = await db.execute(
|
||||
select(ProductionPlan)
|
||||
.where(ProductionPlan.status == "ready")
|
||||
.order_by(asc(ProductionPlan.plan_date), asc(ProductionPlan.id))
|
||||
.limit(1)
|
||||
)
|
||||
head = res.scalar_one_or_none()
|
||||
if head:
|
||||
head.status = "online"
|
||||
"""在线为人工触发:用户手动「移动到入口」才变在线,引擎不再自动上线。"""
|
||||
return
|
||||
|
||||
|
||||
async def move_to_saddle(db: AsyncSession, plan: ProductionPlan):
|
||||
"""把计划移动到上卷鞍座:上卷即获得速度 → 生产中。"""
|
||||
"""把计划移动到上卷鞍座(预备生产位,尚未生产)。"""
|
||||
occupied = await _saddle_plan(db)
|
||||
if occupied and occupied.id != plan.id:
|
||||
raise ValueError("上卷鞍座已被占用,请等待当前钢卷生产完成")
|
||||
now = datetime.now()
|
||||
raise ValueError("上卷鞍座已有预备卷,请先投入生产或移走")
|
||||
plan.on_saddle = 1
|
||||
plan.position = SADDLE_NAME
|
||||
plan.saddle_at = now
|
||||
plan.saddle_at = datetime.now()
|
||||
plan.run_started_at = None
|
||||
plan.run_speed = 0
|
||||
plan.run_length_m = 0
|
||||
if plan.status != "produced":
|
||||
plan.run_started_at = now
|
||||
plan.run_speed = SIM_SPEED_M_MIN
|
||||
plan.run_length_m = 0
|
||||
plan.status = "producing"
|
||||
plan.status = "online" # 在鞍座预备(待投入生产)
|
||||
await ensure_online(db)
|
||||
|
||||
|
||||
async def commit_plan(db: AsyncSession, plan: ProductionPlan):
|
||||
"""投入生产(兜底):鞍座计划置为生产中。"""
|
||||
if plan.on_saddle != 1:
|
||||
await move_to_saddle(db, plan)
|
||||
return
|
||||
if plan.run_started_at is None:
|
||||
plan.run_started_at = datetime.now()
|
||||
"""投入生产:鞍座预备卷进入生产中,离开鞍座进入产线(物料跟踪)。"""
|
||||
res = await db.execute(select(ProductionPlan).where(ProductionPlan.status == "producing"))
|
||||
if res.scalars().first() is not None:
|
||||
raise ValueError("产线已有钢卷在生产,请等待当前卷完成")
|
||||
now = datetime.now()
|
||||
plan.on_saddle = 0 # 离开上卷鞍座
|
||||
plan.position = None
|
||||
plan.saddle_at = None
|
||||
plan.run_started_at = now
|
||||
plan.run_speed = SIM_SPEED_M_MIN
|
||||
plan.run_length_m = 0
|
||||
plan.status = "producing"
|
||||
await ensure_online(db)
|
||||
await ensure_online(db) # 鞍座空出 → 队首自动上线
|
||||
|
||||
|
||||
async def _produce(db: AsyncSession, plan: ProductionPlan):
|
||||
@@ -183,17 +153,17 @@ async def _produce(db: AsyncSession, plan: ProductionPlan):
|
||||
logger.info(f"生产完成并产生实绩: {plan.cold_coil_no or plan.plan_no}")
|
||||
|
||||
|
||||
async def advance_saddle(db: AsyncSession):
|
||||
"""推进鞍座计划:累计带头长度到 2000m → 生产完成。"""
|
||||
plan = await _saddle_plan(db)
|
||||
if not plan:
|
||||
async def advance_production(db: AsyncSession):
|
||||
"""推进产线在产卷(已离开鞍座):累计带头长度到 2000m → 生产完成。"""
|
||||
res = await db.execute(select(ProductionPlan).where(ProductionPlan.status == "producing"))
|
||||
plan = res.scalars().first()
|
||||
if not plan or not plan.run_started_at:
|
||||
return
|
||||
now = datetime.now()
|
||||
if plan.status == "producing" and plan.run_started_at:
|
||||
elapsed = (now - plan.run_started_at).total_seconds()
|
||||
plan.run_length_m = min(TARGET_LENGTH_M, (plan.run_speed or 0) / 60.0 * elapsed)
|
||||
if plan.run_length_m >= TARGET_LENGTH_M:
|
||||
await _produce(db, plan)
|
||||
elapsed = (now - plan.run_started_at).total_seconds()
|
||||
plan.run_length_m = min(TARGET_LENGTH_M, (plan.run_speed or 0) / 60.0 * elapsed)
|
||||
if plan.run_length_m >= TARGET_LENGTH_M:
|
||||
await _produce(db, plan)
|
||||
|
||||
|
||||
async def _get_line_state(db: AsyncSession) -> LineState:
|
||||
@@ -243,9 +213,8 @@ async def detect_downtime(db: AsyncSession):
|
||||
|
||||
|
||||
async def tick(db: AsyncSession):
|
||||
"""引擎单步:上线 + 推进鞍座 + 停机检测。"""
|
||||
await ensure_online(db)
|
||||
await advance_saddle(db)
|
||||
"""引擎单步:推进在产卷 + 停机检测(在线为人工触发,不自动上线)。"""
|
||||
await advance_production(db)
|
||||
await detect_downtime(db)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user