diff --git a/tests/golden/test_rush.py b/tests/golden/test_rush.py index d6e3bb7..c164592 100644 --- a/tests/golden/test_rush.py +++ b/tests/golden/test_rush.py @@ -109,3 +109,85 @@ def test_rush_evaluate_existing_order(): assert not target.get("isRush") # 主干未改 assert impact["baseline"]["orderCount"] == 7 assert impact["after"]["orderCount"] == 7 + + +def _trial_flex_world() -> dict: + """试排世界夹具:主干种子数据 + planningContext.trialOnly + 最小柔性主数据。 + + 模拟「文件夹快试导入」后的真实形态:排产只认 flex* 主数据, + 主干 RULE 引擎在此类世界上只能产出 0 工单的空壳版本。 + """ + world = seed_world() + world["planningContext"] = {"trialOnly": True, "dataDate": "2026-08-04"} + world["flexOperations"] = [{"id": 1, "code": "CUT", "name": "下料", "changeoverMin": 10}] + world["flexTeams"] = [{ + "id": 1, "code": "TEAM-CUT", "name": "下料班", "memberCount": 4, + "supportOps": ["CUT"], + }] + world["flexRoutings"] = [{ + "id": 1, "productCode": "CTRL-A", "productName": "控制柜 A", "seq": 1, + "operationCode": "CUT", "operationName": "下料", "stdTimePerUnit": 5.0, + }] + world["flexMaterials"] = [{ + "id": 1, "code": "CTRL-A", "name": "控制柜 A", "type": "FINISHED_PRODUCT", + "unit": "件", "stock": 0, "inTransit": 0, "safetyStock": 0, + "procurementLeadTime": 0, "sourcingType": "MAKE", + }] + world["flexEquipment"] = [{ + "id": 1, "code": "EQ-1", "name": "切割机", "status": "RUNNING", + "capabilities": ["CUT"], "availabilityRate": 1.0, + }] + return world + + +def test_rush_apply_in_trial_world_routes_to_flex_engine(): + """试排世界采用急单必须走柔性引擎(回归:急单入池却排不进/看不见)。 + + 锁定三点:急单桥接进 flexOrders(source=SALES/RELEASED);生成 FV 柔性 + 草稿版本且急单真实排出虚拟产线;主轨 scheduleVersions 不再新增空壳版本。 + """ + world = _trial_flex_world() + n_trunk_versions = len(world["scheduleVersions"]) + n_flex_versions = len(world.get("flexScheduleVersions") or []) + + applied = apply_rush(world, _next_id_factory(world), { + "payload": { + "customerName": "试排急单客户", + "customerLevel": "VIP", + "productCode": "CTRL-A", + "quantity": 3, + "deliveryDate": fmt_date(add_minutes(today0(), 5 * 24 * 60)), + "priority": 1, + }, + "strategy": "DELIVERY_FIRST", + }) + order = applied["order"] + result = applied["result"] + + assert order["isRush"] is True + assert order["status"] == "APPROVED" + + # 急单已桥接进柔性订单池 + flex_row = next( + (o for o in world["flexOrders"] if o.get("orderNo") == order["orderNo"]), + None, + ) + assert flex_row is not None, "急单未桥接进 flexOrders,柔性排产必然看不到它" + assert flex_row["source"] == "SALES" + assert flex_row["status"] == "RELEASED" + assert flex_row["isRush"] is True + + # 生成的是柔性 FV 草稿版本,而不是主轨 RULE 空壳版本 + assert str(result.versionNo).startswith("FV") + assert len(world["scheduleVersions"]) == n_trunk_versions, "主轨不应新增空壳版本" + assert len(world["flexScheduleVersions"]) == n_flex_versions + 1 + assert world["flexScheduleVersions"][-1]["versionNo"] == result.versionNo + assert result.orderCount == len(world["flexOrders"]) + + # 急单真实排出虚拟产线(修复前在此类世界为 0 工单) + rush_vls = [ + v for v in world["flexVirtualLines"] + if v.get("orderNo") == order["orderNo"] + and v.get("versionId") == world["flexScheduleVersions"][-1]["id"] + ] + assert rush_vls, "急单在柔性版本中应有至少一条虚拟产线"