From ce381152557fd6d4feeaa2ffc7cdc0a56f5d2c6d Mon Sep 17 00:00:00 2001 From: caoqianming Date: Thu, 27 Mar 2025 13:36:34 +0800 Subject: [PATCH] fix: validate_and_return_gjson bug --- apps/mtm/models.py | 123 +++++++++++++++------------------------------ 1 file changed, 41 insertions(+), 82 deletions(-) diff --git a/apps/mtm/models.py b/apps/mtm/models.py index eef70bcf..3540ee2f 100644 --- a/apps/mtm/models.py +++ b/apps/mtm/models.py @@ -238,98 +238,53 @@ class RoutePack(CommonADModel): all_routes = Route.objects.filter(routepack=self).order_by('sort', 'process__sort', 'create_time') result = {} - # 1. 找出所有最终产品(未被任何material_in引用的out_id) - all_out_ids = {route.material_out.id for route in all_routes} - all_in_ids = {route.material_in.id for route in all_routes} + # 1. 构建正向和反向图(material_id作为节点) + forward_graph = defaultdict(list) # out_id -> [in_ids] + reverse_graph = defaultdict(list) # in_id -> [out_ids] + route_edges = defaultdict(list) # (in_id, out_id) -> [Route] + + for route in all_routes: + if not (route.material_in and route.material_out): + raise ParseError(f"Route(ID:{route.id}) 缺失material_in或material_out") + in_id, out_id = route.material_in.id, route.material_out.id + forward_graph[out_id].append(in_id) + reverse_graph[in_id].append(out_id) + route_edges[(in_id, out_id)].append(route.id) + + # 2. 识别所有最终节点(未被任何in_id引用的out_id) + all_out_ids = {r.material_out.id for r in all_routes} + all_in_ids = {r.material_in.id for r in all_routes} final_ids = all_out_ids - all_in_ids - + if not final_ids: - raise ParseError("未找到任何最终产品") + raise ParseError("未找到最终产品节点") - # 4. 为每个最终产品构建子图 + # 3. 为每个最终产品构建子图 for final_id in final_ids: - graph = defaultdict(list) - # BFS收集子图所有material_id + # BFS逆向遍历所有上游节点 visited = set() queue = deque([final_id]) - subgraph_materials = set() + while queue: - current_id = queue.popleft() - if current_id in visited: + current_out_id = queue.popleft() + if current_out_id in visited: continue - visited.add(current_id) - subgraph_materials.add(current_id) - queue.extend(graph.get(current_id, [])) - - # 收集子图关联的Route ID + visited.add(current_out_id) + + # 将当前节点的所有上游in_id加入队列 + for in_id in forward_graph.get(current_out_id, []): + if in_id not in visited: + queue.append(in_id) + + # 收集所有关联Route(只要out_id在子图内) subgraph_route_ids = [] for route in all_routes: if route.material_out.id in visited: subgraph_route_ids.append(route.id) - - # 校验子图是否有效(无循环、路径可达) - is_valid, errors, levels = self._validate_subgraph(subgraph_materials, graph, final_id) - if errors and raise_exception: - raise ParseError(errors) - # 记录子图信息 - result[final_id] = { - "routes": subgraph_route_ids, - "meta": { - "is_valid": is_valid, - "errors": errors, - "levels": levels # 层级结构,用于生产排程 - } - } + result[final_id] = {"routes": subgraph_route_ids} return result - def _validate_subgraph(self, materials, graph, final_id): - """校验单个子图的有效性并返回层级结构""" - errors = [] - levels = {} - - # 层级计算(从最终物料逆向遍历) - queue = deque([(final_id, 0)]) - while queue: - current_id, level = queue.popleft() - if current_id in levels: - if level > levels[current_id]: - levels[current_id] = level - continue - levels[current_id] = level - for in_id in graph.get(current_id, []): - queue.append((in_id, level + 1)) - - # 检查循环依赖 - visited = set() - path = set() - - def detect_cycle(m_id): - if m_id in path: - return True - if m_id in visited: - return False - visited.add(m_id) - path.add(m_id) - for out_id in graph.get(m_id, []): - if detect_cycle(out_id): - return True - path.remove(m_id) - return False - - for m_id in materials: - if detect_cycle(m_id): - errors.append(f"物料(ID:{m_id})存在循环依赖") - return False, errors, {} - - # 检查孤立节点(所有物料必须能到达最终产品) - unreachable = materials - set(levels.keys()) - if unreachable: - errors.append(f"存在孤立物料: {unreachable}") - return False, errors, {} - - return True, [], levels - def get_gjson(self, need_update=False, final_material_id=None): if not self.gjson or need_update: self.gjson = self.validate_and_return_gjson() @@ -338,18 +293,22 @@ class RoutePack(CommonADModel): def get_dags(self): gjson = self.get_gjson() - finalMaterialIdList = [] + materialIdList = [] routeIdList = [] for final_material_id, data in gjson.items(): - finalMaterialIdList.append(final_material_id) + materialIdList.append(final_material_id) routeIdList.extend(data['routes']) - mat_qs = Material.objects.filter(id__in=finalMaterialIdList).order_by("process__sort", "create_time") - mat_dict = {mat.id: {"id": mat.id, "label": str(mat), "shape": "rect"}for mat in mat_qs} route_qs = Route.objects.filter(id__in=routeIdList).select_related("material_in", "material_out", "process") + matids1 = route_qs.values_list("material_in__id", flat=True).distinct() + matids2 = route_qs.values_list("material_out__id", flat=True).distinct() + materialIdList.extend(list(matids1)) + materialIdList.extend(list(matids2)) route_dict = {r.id: {"label": r.process.name, "source": r.material_in.id, "target": r.material_out.id} for r in route_qs} + mat_qs = Material.objects.filter(id__in=materialIdList).order_by("process__sort", "create_time") + mat_dict = {mat.id: {"id": mat.id, "label": str(mat), "shape": "rect"}for mat in mat_qs} res = {} for final_material_id, data in gjson.items(): - item = {"name": mat_dict[final_material_id]} + item = {"name": mat_dict[final_material_id]["label"]} edges = [] nodes_set = set() for route_id in data['routes']: