From ce841ddf85c48bae32f9e3068403fdfe98ac8711 Mon Sep 17 00:00:00 2001
From: zzw <228544128@qq.com>
Date: 星期三, 07 十月 2026 12:35:09 +0800
Subject: [PATCH] feat: add extension defaults for fault reporting

---
 emergency-repair-server/src/main/java/com/emergencyrepair/system/service/SystemService.java |  255 ++++++++++++++++++++++++++++++++++++++++++++------
 1 files changed, 223 insertions(+), 32 deletions(-)

diff --git a/emergency-repair-server/src/main/java/com/emergencyrepair/system/service/SystemService.java b/emergency-repair-server/src/main/java/com/emergencyrepair/system/service/SystemService.java
index c2b4797..e14467e 100644
--- a/emergency-repair-server/src/main/java/com/emergencyrepair/system/service/SystemService.java
+++ b/emergency-repair-server/src/main/java/com/emergencyrepair/system/service/SystemService.java
@@ -13,7 +13,9 @@
 import com.emergencyrepair.business.mapper.ProductNodeMapper;
 import com.emergencyrepair.business.mapper.TeamMajorMapper;
 import com.emergencyrepair.common.BusinessException;
+import com.emergencyrepair.common.audit.CurrentUserContext;
 import com.emergencyrepair.system.entity.SysMenu;
+import com.emergencyrepair.system.entity.SysErrorLog;
 import com.emergencyrepair.system.entity.SysLoginLog;
 import com.emergencyrepair.system.entity.SysOperationLog;
 import com.emergencyrepair.system.entity.SysRole;
@@ -21,6 +23,7 @@
 import com.emergencyrepair.system.entity.SysRoleUser;
 import com.emergencyrepair.system.entity.SysTenantMenu;
 import com.emergencyrepair.system.mapper.SysMenuMapper;
+import com.emergencyrepair.system.mapper.SysErrorLogMapper;
 import com.emergencyrepair.system.mapper.SysLoginLogMapper;
 import com.emergencyrepair.system.mapper.SysOperationLogMapper;
 import com.emergencyrepair.system.mapper.SysRoleMapper;
@@ -59,6 +62,7 @@
     private final SysRoleUserMapper roleUserMapper;
     private final SysLoginLogMapper loginLogMapper;
     private final SysOperationLogMapper operationLogMapper;
+    private final SysErrorLogMapper errorLogMapper;
     private final ButtonPermissionCatalog buttonPermissionCatalog;
     private final JdbcTemplate jdbcTemplate;
     private final EngineeringMajorMapper engineeringMajorMapper;
@@ -105,11 +109,20 @@
             menu.setMenuType(menu.getId() == null ? 1 : menu.getMenuType());
         }
         boolean isRoot = menu.getPid() == null || menu.getPid() == 0L;
-        if (menu.getMenuType() == 2) {
-            if (isRoot) {
-                throw new BusinessException("鎸夐挳绫诲瀷鍙兘寤哄湪鑿滃崟绫诲瀷涓嬬骇");
+        if (menu.getId() != null && !isRoot && menu.getId().equals(menu.getPid())) {
+            throw new BusinessException("涓婄骇鑿滃崟涓嶈兘閫夋嫨褰撳墠鑿滃崟");
+        }
+        SysMenu parent = null;
+        if (!isRoot) {
+            parent = menuMapper.selectById(menu.getPid());
+            if (parent == null) {
+                throw new BusinessException("涓婄骇鑿滃崟涓嶅瓨鍦�");
             }
-            SysMenu parent = menuMapper.selectById(menu.getPid());
+            if (menu.getId() != null && isMenuDescendant(menu.getPid(), menu.getId())) {
+                throw new BusinessException("涓婄骇鑿滃崟涓嶈兘閫夋嫨褰撳墠鑿滃崟鐨勪笅绾�");
+            }
+        }
+        if (menu.getMenuType() == 2) {
             if (parent == null || parent.getMenuType() == null || parent.getMenuType() != 1) {
                 throw new BusinessException("鎸夐挳绫诲瀷鍙兘寤哄湪鑿滃崟绫诲瀷涓嬬骇");
             }
@@ -118,11 +131,8 @@
             }
             menu.setUrl(null);
             menu.setIcon(null);
-        } else if (!isRoot) {
-            SysMenu parent = menuMapper.selectById(menu.getPid());
-            if (parent != null && parent.getMenuType() != null && parent.getMenuType() == 2) {
-                throw new BusinessException("鎸夐挳绫诲瀷涓嬬骇涓嶈兘鍐嶅缓鑿滃崟");
-            }
+        } else if (parent != null && parent.getMenuType() != null && parent.getMenuType() == 2) {
+            throw new BusinessException("鎸夐挳绫诲瀷涓嬬骇涓嶈兘鍐嶅缓鑿滃崟");
         }
         LocalDateTime now = LocalDateTime.now();
         menu.setUpdateTime(now);
@@ -139,6 +149,19 @@
             menuMapper.updateById(menu);
         }
         return menuMapper.selectById(menu.getId());
+    }
+
+    private boolean isMenuDescendant(Long candidateId, Long ancestorId) {
+        Long cursor = candidateId;
+        int guard = 0;
+        while (cursor != null && cursor != 0L && guard++ < 64) {
+            if (cursor.equals(ancestorId)) {
+                return true;
+            }
+            SysMenu current = menuMapper.selectById(cursor);
+            cursor = current == null ? null : current.getPid();
+        }
+        return false;
     }
 
     @Transactional
@@ -392,30 +415,11 @@
         if (!StringUtils.hasText(xNo)) {
             return productTreeRoots();
         }
-        List<ProductNode> rootNodes = productNodeMapper.selectList(Wrappers.<ProductNode>lambdaQuery()
-                .and(wrapper -> wrapper.isNull(ProductNode::getParentId).or().eq(ProductNode::getParentId, 0L))
-                .orderByAsc(ProductNode::getSortNo).orderByAsc(ProductNode::getId));
         List<ProductNode> sideNodes = productNodeMapper.selectList(Wrappers.<ProductNode>lambdaQuery()
                 .in(ProductNode::getLayer, Arrays.asList("side", "SIDE"))
                 .eq(ProductNode::getName, xNo.trim())
                 .orderByAsc(ProductNode::getSortNo).orderByAsc(ProductNode::getId));
-        Map<Long, ProductNode> roots = uniqueProductNodes(rootNodes);
-        Map<Long, ProductNode> sides = uniqueProductNodes(sideNodes);
-        Set<Long> branchIds = new HashSet<>();
-        branchIds.addAll(roots.keySet());
-        branchIds.addAll(sides.keySet());
-        Map<Long, Map<String, Object>> rootRows = new LinkedHashMap<>();
-        for (ProductNode root : sortProductNodes(roots.values())) {
-            rootRows.put(root.getId(), mapProduct(root, branchIds));
-        }
-        for (ProductNode side : sortProductNodes(sides.values())) {
-            ProductNode root = findProductRoot(side);
-            Map<String, Object> rootRow = root == null ? null : rootRows.get(root.getId());
-            if (rootRow != null) {
-                children(rootRow).add(mapProduct(side, branchIds));
-            }
-        }
-        return new ArrayList<>(rootRows.values());
+        return mapProductTreeNodes(sideNodes, Collections.emptySet());
     }
 
     public List<String> productXNumbers() {
@@ -598,8 +602,10 @@
     public Map<String, Object> userPage(String keyword, Long deptId, Long roleId, int current, int size) {
         List<Object> args = new ArrayList<>();
         StringBuilder sql = new StringBuilder();
-        sql.append("SELECT u.USER_ID, u.USER_NAME, u.NICK_NAME, u.DEPT_ID, d.DEPT_NAME, u.PHONENUMBER, u.STATUS, u.LOGIN_DATE ");
+        sql.append("SELECT u.USER_ID, u.USER_NAME, u.NICK_NAME, u.DEPT_ID, d.DEPT_NAME, u.PHONENUMBER, u.STATUS, u.LOGIN_DATE, ");
+        sql.append("ue.ENGINEERING_MAJOR, ue.TEAM_MAJOR, ue.RESPONSIBLE_X_NO, CASE WHEN ue.USER_ID IS NULL THEN 0 ELSE 1 END AS EXT_CONFIGURED ");
         sql.append("FROM CSICZB_ZB.SYS_USER u LEFT JOIN CSICZB_ZB.SYS_DEPT d ON d.DEPT_ID = u.DEPT_ID ");
+        sql.append("LEFT JOIN LX.T_SYS_USER_EXT ue ON ue.USER_ID = u.USER_ID AND (ue.DEL_FLAG = 0 OR ue.DEL_FLAG IS NULL) ");
         sql.append("WHERE (u.DEL_FLAG = '0' OR u.DEL_FLAG IS NULL) ");
         if (StringUtils.hasText(keyword)) {
             sql.append("AND (u.USER_NAME LIKE ? OR u.NICK_NAME LIKE ?) ");
@@ -628,6 +634,10 @@
             row.put("status", rs.getString("STATUS"));
             java.sql.Timestamp loginDate = rs.getTimestamp("LOGIN_DATE");
             row.put("loginDate", loginDate == null ? null : loginDate.toLocalDateTime().toString().replace('T', ' '));
+            row.put("engineeringMajor", rs.getString("ENGINEERING_MAJOR"));
+            row.put("teamMajor", rs.getString("TEAM_MAJOR"));
+            row.put("responsibleXNo", rs.getString("RESPONSIBLE_X_NO"));
+            row.put("extConfigured", rs.getInt("EXT_CONFIGURED") == 1);
             row.put("roles", new ArrayList<Map<String, Object>>());
             return row;
         });
@@ -672,7 +682,11 @@
 
     public List<Map<String, Object>> depts() {
         return jdbcTemplate.query(
-                "SELECT DEPT_ID, DEPT_NAME, PARENT_ID, ORDER_NUM, DEPT_TYPE, LEADER, PHONE, STATUS, DEPT_CODE FROM CSICZB_ZB.SYS_DEPT WHERE (DEL_FLAG = '0' OR DEL_FLAG IS NULL) ORDER BY ORDER_NUM, DEPT_ID",
+                "SELECT d.DEPT_ID, d.DEPT_NAME, d.PARENT_ID, d.ORDER_NUM, d.DEPT_TYPE, d.LEADER, d.PHONE, d.STATUS, d.DEPT_CODE, "
+                        + "e.RESPONSIBLE_X_NO, CASE WHEN e.DEPT_ID IS NULL THEN 0 ELSE 1 END AS EXT_CONFIGURED "
+                        + "FROM CSICZB_ZB.SYS_DEPT d "
+                        + "LEFT JOIN LX.T_SYS_DEPT_EXT e ON e.DEPT_ID = d.DEPT_ID AND (e.DEL_FLAG = 0 OR e.DEL_FLAG IS NULL) "
+                        + "WHERE (d.DEL_FLAG = '0' OR d.DEL_FLAG IS NULL) ORDER BY d.ORDER_NUM, d.DEPT_ID",
                 (rs, rowNum) -> {
                     Map<String, Object> row = new LinkedHashMap<>();
                     row.put("id", String.valueOf(rs.getLong("DEPT_ID")));
@@ -685,8 +699,143 @@
                     row.put("phone", rs.getString("PHONE"));
                     row.put("status", rs.getString("STATUS"));
                     row.put("deptCode", rs.getString("DEPT_CODE"));
+                    row.put("responsibleXNo", rs.getString("RESPONSIBLE_X_NO"));
+                    row.put("extConfigured", rs.getInt("EXT_CONFIGURED") == 1);
                     return row;
                 });
+    }
+
+    public Map<String, Object> deptExt(Long deptId) {
+        List<Map<String, Object>> rows = jdbcTemplate.query(
+                "SELECT d.DEPT_NAME, e.RESPONSIBLE_X_NO FROM CSICZB_ZB.SYS_DEPT d "
+                        + "LEFT JOIN LX.T_SYS_DEPT_EXT e ON e.DEPT_ID = d.DEPT_ID AND (e.DEL_FLAG = 0 OR e.DEL_FLAG IS NULL) "
+                        + "WHERE d.DEPT_ID = ? AND (d.DEL_FLAG = '0' OR d.DEL_FLAG IS NULL)",
+                new Object[]{deptId}, (rs, rowNum) -> {
+                    Map<String, Object> row = new LinkedHashMap<>();
+                    row.put("deptId", String.valueOf(deptId));
+                    row.put("deptName", rs.getString("DEPT_NAME"));
+                    row.put("responsibleXNo", rs.getString("RESPONSIBLE_X_NO"));
+                    row.put("configured", StringUtils.hasText(rs.getString("RESPONSIBLE_X_NO")));
+                    return row;
+                });
+        if (rows.isEmpty()) {
+            throw new BusinessException("閮ㄩ棬涓嶅瓨鍦�");
+        }
+        return rows.get(0);
+    }
+
+    @Transactional
+    public void saveDeptExt(Long deptId, String responsibleXNo) {
+        assertExternalDept(deptId);
+        String xNo = normalizeExtValue(responsibleXNo);
+        assertKnownXNo(xNo);
+        String username = CurrentUserContext.get();
+        LocalDateTime now = LocalDateTime.now();
+        int delFlag = xNo == null ? 1 : 0;
+        int updated = jdbcTemplate.update(
+                "UPDATE LX.T_SYS_DEPT_EXT SET RESPONSIBLE_X_NO = ?, DEL_FLAG = ?, UPDATE_BY = ?, UPDATE_TIME = ? WHERE DEPT_ID = ?",
+                xNo, delFlag, username, now, deptId);
+        if (updated == 0) {
+            jdbcTemplate.update(
+                    "INSERT INTO LX.T_SYS_DEPT_EXT (DEPT_ID, RESPONSIBLE_X_NO, CREATE_BY, UPDATE_BY, DEL_FLAG, CREATE_TIME, UPDATE_TIME) VALUES (?, ?, ?, ?, ?, ?, ?)",
+                    deptId, xNo, username, username, delFlag, now, now);
+        }
+    }
+
+    public Map<String, Object> userExt(Long userId) {
+        List<Map<String, Object>> rows = jdbcTemplate.query(
+                "SELECT u.USER_NAME, u.NICK_NAME, e.ENGINEERING_MAJOR, e.TEAM_MAJOR, e.RESPONSIBLE_X_NO "
+                        + "FROM CSICZB_ZB.SYS_USER u "
+                        + "LEFT JOIN LX.T_SYS_USER_EXT e ON e.USER_ID = u.USER_ID AND (e.DEL_FLAG = 0 OR e.DEL_FLAG IS NULL) "
+                        + "WHERE u.USER_ID = ? AND (u.DEL_FLAG = '0' OR u.DEL_FLAG IS NULL)",
+                new Object[]{userId}, (rs, rowNum) -> {
+                    Map<String, Object> row = new LinkedHashMap<>();
+                    row.put("userId", String.valueOf(userId));
+                    row.put("userName", rs.getString("USER_NAME"));
+                    row.put("nickName", rs.getString("NICK_NAME"));
+                    row.put("engineeringMajor", rs.getString("ENGINEERING_MAJOR"));
+                    row.put("teamMajor", rs.getString("TEAM_MAJOR"));
+                    row.put("responsibleXNo", rs.getString("RESPONSIBLE_X_NO"));
+                    boolean configured = StringUtils.hasText(rs.getString("ENGINEERING_MAJOR"))
+                            || StringUtils.hasText(rs.getString("TEAM_MAJOR"))
+                            || StringUtils.hasText(rs.getString("RESPONSIBLE_X_NO"));
+                    row.put("configured", configured);
+                    return row;
+                });
+        if (rows.isEmpty()) {
+            throw new BusinessException("鐢ㄦ埛涓嶅瓨鍦�");
+        }
+        return rows.get(0);
+    }
+
+    @Transactional
+    public void saveUserExt(Long userId, String engineeringMajor, String teamMajor, String responsibleXNo) {
+        assertExternalUser(userId);
+        String engineering = normalizeExtValue(engineeringMajor);
+        String team = normalizeExtValue(teamMajor);
+        String xNo = normalizeExtValue(responsibleXNo);
+        assertKnownEngineeringMajor(engineering);
+        assertKnownTeamMajor(team);
+        assertKnownXNo(xNo);
+        boolean configured = engineering != null || team != null || xNo != null;
+        String username = CurrentUserContext.get();
+        LocalDateTime now = LocalDateTime.now();
+        int updated = jdbcTemplate.update(
+                "UPDATE LX.T_SYS_USER_EXT SET ENGINEERING_MAJOR = ?, TEAM_MAJOR = ?, RESPONSIBLE_X_NO = ?, DEL_FLAG = ?, UPDATE_BY = ?, UPDATE_TIME = ? WHERE USER_ID = ?",
+                engineering, team, xNo, configured ? 0 : 1, username, now, userId);
+        if (updated == 0) {
+            jdbcTemplate.update(
+                    "INSERT INTO LX.T_SYS_USER_EXT (USER_ID, ENGINEERING_MAJOR, TEAM_MAJOR, RESPONSIBLE_X_NO, CREATE_BY, UPDATE_BY, DEL_FLAG, CREATE_TIME, UPDATE_TIME) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
+                    userId, engineering, team, xNo, username, username, configured ? 0 : 1, now, now);
+        }
+    }
+
+    private void assertExternalDept(Long deptId) {
+        if (deptId == null) {
+            throw new BusinessException("璇烽�夋嫨閮ㄩ棬");
+        }
+        Integer count = jdbcTemplate.queryForObject(
+                "SELECT COUNT(*) FROM CSICZB_ZB.SYS_DEPT WHERE DEPT_ID = ? AND (DEL_FLAG = '0' OR DEL_FLAG IS NULL)",
+                Integer.class, deptId);
+        if (count == null || count == 0) {
+            throw new BusinessException("閮ㄩ棬涓嶅瓨鍦�");
+        }
+    }
+
+    private void assertExternalUser(Long userId) {
+        if (userId == null) {
+            throw new BusinessException("璇烽�夋嫨鐢ㄦ埛");
+        }
+        Integer count = jdbcTemplate.queryForObject(
+                "SELECT COUNT(*) FROM CSICZB_ZB.SYS_USER WHERE USER_ID = ? AND (DEL_FLAG = '0' OR DEL_FLAG IS NULL)",
+                Integer.class, userId);
+        if (count == null || count == 0) {
+            throw new BusinessException("鐢ㄦ埛涓嶅瓨鍦�");
+        }
+    }
+
+    private void assertKnownXNo(String xNo) {
+        if (xNo != null && !productXNumbers().contains(xNo)) {
+            throw new BusinessException("rk_X鍙峰繀椤讳粠浜у搧缁撴瀯鏍戠殑side鑺傜偣涓�夋嫨");
+        }
+    }
+
+    private void assertKnownEngineeringMajor(String value) {
+        if (value != null && listEngineeringMajors().stream().map(EngineeringMajor::getName)
+                .filter(StringUtils::hasText).noneMatch(item -> value.equals(item.trim()))) {
+            throw new BusinessException("宸ョ▼涓撲笟涓嶅湪鍙�夎寖鍥村唴");
+        }
+    }
+
+    private void assertKnownTeamMajor(String value) {
+        if (value != null && listTeamMajors().stream().map(TeamMajor::getName)
+                .filter(StringUtils::hasText).noneMatch(item -> value.equals(item.trim()))) {
+            throw new BusinessException("rk_T闃熶笓涓氫笉鍦ㄥ彲閫夎寖鍥村唴");
+        }
+    }
+
+    private String normalizeExtValue(String value) {
+        return StringUtils.hasText(value) ? value.trim() : null;
     }
 
     public List<String> userRoleIds(Long userId) {
@@ -887,9 +1036,10 @@
             }
         }
         final Set<Long> allowedIds = allowed;
+        Set<Long> visibleIds = new LinkedHashSet<>(expandWithAncestors(new ArrayList<>(allowed)));
         List<SysMenu> navigable = listMenus().stream()
                 .filter(menu -> menu.getMenuType() == null || menu.getMenuType() != 2)
-                .filter(menu -> allowedIds.contains(menu.getId()))
+                .filter(menu -> visibleIds.contains(menu.getId()))
                 .collect(Collectors.toList());
         return buildTree(navigable);
     }
@@ -1038,6 +1188,47 @@
         return operationLogMapper.selectPage(page, wrapper);
     }
 
+    public Page<SysErrorLog> errorLogPage(int current, int size, String keyword, String module, String exceptionType, String startDate, String endDate) {
+        Page<SysErrorLog> page = new Page<>(current <= 0 ? 1 : current, size <= 0 ? 20 : size);
+        LambdaQueryWrapper<SysErrorLog> wrapper = Wrappers.lambdaQuery();
+        wrapper.select(SysErrorLog::getId, SysErrorLog::getUserId, SysErrorLog::getUserName,
+                SysErrorLog::getNickName, SysErrorLog::getModule, SysErrorLog::getRequestMethod,
+                SysErrorLog::getRequestUri, SysErrorLog::getExceptionType, SysErrorLog::getMessage,
+                SysErrorLog::getErrorTime);
+        if (StringUtils.hasText(keyword)) {
+            String value = keyword.trim();
+            wrapper.and(item -> item.like(SysErrorLog::getUserName, value)
+                    .or().like(SysErrorLog::getNickName, value)
+                    .or().like(SysErrorLog::getRequestUri, value)
+                    .or().like(SysErrorLog::getExceptionType, value)
+                    .or().like(SysErrorLog::getMessage, value));
+        }
+        if (StringUtils.hasText(module)) {
+            wrapper.eq(SysErrorLog::getModule, module.trim());
+        }
+        if (StringUtils.hasText(exceptionType)) {
+            wrapper.like(SysErrorLog::getExceptionType, exceptionType.trim());
+        }
+        LocalDateTime start = parseStart(startDate);
+        LocalDateTime end = parseEnd(endDate);
+        if (start != null) {
+            wrapper.ge(SysErrorLog::getErrorTime, start);
+        }
+        if (end != null) {
+            wrapper.le(SysErrorLog::getErrorTime, end);
+        }
+        wrapper.orderByDesc(SysErrorLog::getErrorTime);
+        return errorLogMapper.selectPage(page, wrapper);
+    }
+
+    public SysErrorLog errorLog(Long id) {
+        SysErrorLog log = errorLogMapper.selectById(id);
+        if (log == null) {
+            throw new BusinessException("閿欒鏃ュ織涓嶅瓨鍦�");
+        }
+        return log;
+    }
+
     public Long resolveUserId(String username) {
         if (!StringUtils.hasText(username)) {
             return null;

--
Gitblit v1.9.1