From 95a3571c6926b7e21f352d9a3a2fa73757766b1f Mon Sep 17 00:00:00 2001 From: liweijie Date: Thu, 17 Sep 2026 12:00:44 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=95=99=E7=A0=94=E5=AE=A4?= =?UTF-8?q?=E5=92=8C=E6=95=99=E5=91=98=E7=9A=84=E6=97=B6=E5=80=99=EF=BC=8C?= =?UTF-8?q?=E5=90=8C=E6=AD=A5=E5=88=9B=E5=BB=BA=E9=83=A8=E9=97=A8=E5=92=8C?= =?UTF-8?q?=E6=95=99=E5=91=98=E8=B4=A6=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/jwgl/DownloadController.java | 86 ++++ .../controller/jwgl/FacultyController.java | 6 +- .../web/controller/jwgl/JYSController.java | 84 +++- .../jwgl/TeachingTaskController.java | 9 + .../jwgl/dto/faculty/AddTeacherDTO.java | 12 + .../java/com/roomroot/jwgl/entity/JYB.java | 8 + .../java/com/roomroot/jwgl/entity/JYSB.java | 4 + .../com/roomroot/jwgl/mapper/JYBMapper.xml | 5 +- .../roomroot/jwgl/service/FacultyService.java | 19 +- .../roomroot/jwgl/service/OrgSyncService.java | 85 ++++ .../jwgl/service/TeachingTaskService.java | 8 + .../jwgl/service/impl/FacultyServiceImpl.java | 55 ++- .../jwgl/service/impl/OrgSyncServiceImpl.java | 366 ++++++++++++++++++ .../service/impl/TeachingTaskServiceImpl.java | 43 ++ .../roomroot/jwgl/utils/OperationLogUtil.java | 3 +- .../jwgl/vo/faculty/TeacherListVO.java | 9 + .../jwgl/vo/orgsync/OrgAlignResultVO.java | 62 +++ .../resources/mapper/system/SysDeptMapper.xml | 2 +- .../src/api/teachBusiness/teachingTask.js | 13 + frontend/src/api/teachOffice/office.js | 44 ++- frontend/src/api/teachOffice/teacher.js | 54 +++ .../components/OrgSyncResultDialog/index.vue | 112 ++++++ .../teachBusiness/teachingTask/index.vue | 29 +- .../src/views/teachOffice/office/index.vue | 83 +++- .../src/views/teachOffice/teacher/index.vue | 301 +++++++++++++- 25 files changed, 1452 insertions(+), 50 deletions(-) create mode 100644 backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/OrgSyncService.java create mode 100644 backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/OrgSyncServiceImpl.java create mode 100644 backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/orgsync/OrgAlignResultVO.java create mode 100644 frontend/src/components/OrgSyncResultDialog/index.vue diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/DownloadController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/DownloadController.java index 9e6fe3206..b851ddd3c 100644 --- a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/DownloadController.java +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/DownloadController.java @@ -71,6 +71,92 @@ public class DownloadController { downloadTemplate(response, "教研室模板.xls"); } + /** + * 教员导入模板(动态生成:数据 sheet + 填写说明 sheet)。 + *

列序与 /jys/teacher/import 解析一致: + * 教员编号、教员姓名、教研室代号、职称、专业技术职务等级、教员类别、 + * 性别、手机号、身份证号、备注、序号、虚实类型。

+ */ + @GetMapping("/jy") + public void downloadJy(HttpServletResponse response) throws IOException { + org.apache.poi.ss.usermodel.Workbook workbook = new org.apache.poi.xssf.usermodel.XSSFWorkbook(); + try { + org.apache.poi.ss.usermodel.CellStyle headerStyle = workbook.createCellStyle(); + org.apache.poi.ss.usermodel.Font headerFont = workbook.createFont(); + headerFont.setBold(true); + headerStyle.setFont(headerFont); + org.apache.poi.ss.usermodel.CellStyle requiredStyle = workbook.createCellStyle(); + org.apache.poi.ss.usermodel.Font requiredFont = workbook.createFont(); + requiredFont.setBold(true); + requiredFont.setColor(org.apache.poi.ss.usermodel.IndexedColors.RED.getIndex()); + requiredStyle.setFont(requiredFont); + + String[] headers = {"教员编号", "教员姓名*", "教研室代号*", "职称", "专业技术职务等级", + "教员类别", "性别", "手机号", "身份证号", "备注", "序号", "虚实类型"}; + org.apache.poi.ss.usermodel.Sheet dataSheet = workbook.createSheet("教员"); + org.apache.poi.ss.usermodel.Row headerRow = dataSheet.createRow(0); + for (int i = 0; i < headers.length; i++) { + org.apache.poi.ss.usermodel.Cell cell = headerRow.createCell(i); + cell.setCellValue(headers[i]); + cell.setCellStyle(headers[i].endsWith("*") ? requiredStyle : headerStyle); + dataSheet.setColumnWidth(i, 16 * 256); + } + + String[][] instructions = { + {"教员编号", "选填", "留空则系统自动生成(JY+流水号);手填须唯一"}, + {"教员姓名", "必填", "文本"}, + {"教研室代号", "必填", "教研室表中的教研室代号(非名称),必须已存在"}, + {"职称", "选填", "文本,如:讲师、副教授"}, + {"专业技术职务等级", "选填", "文本,如:初级、中级、高级"}, + {"教员类别", "选填", "文本,如:专业技术军官"}, + {"性别", "选填", "数字:1=男,0=女"}, + {"手机号", "选填", "文本"}, + {"身份证号", "选填", "文本"}, + {"备注", "选填", "文本"}, + {"序号", "选填", "数字"}, + {"虚实类型", "选填", "数字:0=实员"} + }; + org.apache.poi.ss.usermodel.Sheet helpSheet = workbook.createSheet("填写说明"); + org.apache.poi.ss.usermodel.Row titleRow = helpSheet.createRow(0); + org.apache.poi.ss.usermodel.Cell titleCell = titleRow.createCell(0); + titleCell.setCellValue("教员导入填写说明(红色表头列为必填;导入只写教员档案不建账号,账号用列表【对齐到用户表】批量开通)"); + titleCell.setCellStyle(headerStyle); + String[] helpHeaders = {"列名", "是否必填", "填写说明"}; + org.apache.poi.ss.usermodel.Row helpHeaderRow = helpSheet.createRow(1); + for (int i = 0; i < helpHeaders.length; i++) { + org.apache.poi.ss.usermodel.Cell cell = helpHeaderRow.createCell(i); + cell.setCellValue(helpHeaders[i]); + cell.setCellStyle(headerStyle); + } + for (int i = 0; i < instructions.length; i++) { + org.apache.poi.ss.usermodel.Row row = helpSheet.createRow(i + 2); + for (int j = 0; j < 3; j++) { + row.createCell(j).setCellValue(instructions[i][j]); + } + if ("必填".equals(instructions[i][1])) { + row.getCell(1).setCellStyle(requiredStyle); + } + } + helpSheet.setColumnWidth(0, 18 * 256); + helpSheet.setColumnWidth(1, 10 * 256); + helpSheet.setColumnWidth(2, 60 * 256); + + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + FileUtils.setAttachmentResponseHeader(response, "教员导入模板.xlsx"); + workbook.write(response.getOutputStream()); + response.getOutputStream().flush(); + } catch (IOException e) { + throw e; + } catch (Exception e) { + throw new ServiceException("生成教员导入模板失败:" + e.getMessage()); + } finally { + try { + workbook.close(); + } catch (IOException ignored) { + } + } + } + /** * 联教连训管理模板 */ diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/FacultyController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/FacultyController.java index cccc80287..f246bae34 100644 --- a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/FacultyController.java +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/FacultyController.java @@ -38,7 +38,7 @@ public class FacultyController { */ @PostMapping("/office/add") public Result addOffice(@RequestBody JYSB entity) { - facultyService.addJYSB(entity); + facultyService.addJYSB(entity, null); return Result.success(); } @@ -49,7 +49,7 @@ public class FacultyController { */ @PostMapping("/office/disable") public Result disableOffice(@RequestParam("jysdh") String jysdh) { - facultyService.disableJYSB(jysdh); + facultyService.disableJYSB(jysdh, null); return Result.success(); } @@ -58,7 +58,7 @@ public class FacultyController { */ @PostMapping("/office/update") public Result updateOffice(@RequestBody JYSB entity) { - facultyService.updateJYSB(entity); + facultyService.updateJYSB(entity, null); return Result.success(); } diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/JYSController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/JYSController.java index e79cba4a5..ccc789000 100644 --- a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/JYSController.java +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/JYSController.java @@ -60,14 +60,18 @@ public class JYSController { @Resource private TeachingTaskManageService teachingTaskManageService; + + @Resource + private com.roomroot.jwgl.service.OrgSyncService orgSyncService; // ======================== 教研室管理 ======================== /** * 新增教研室 */ @PostMapping("/office/add") - public Result addOffice(@RequestBody JYSB entity) { - facultyService.addJYSB(entity); + public Result addOffice(@RequestBody JYSB entity, + @RequestParam(required = false) Boolean syncDept) { + facultyService.addJYSB(entity, syncDept); return Result.success(); } @@ -79,8 +83,9 @@ public class JYSController { * @param jysdh 教研室代号 */ @PostMapping("/office/disable") - public Result disableOffice(@RequestParam("jysdh") String jysdh) { - facultyService.disableJYSB(jysdh); + public Result disableOffice(@RequestParam("jysdh") String jysdh, + @RequestParam(required = false) Boolean syncDept) { + facultyService.disableJYSB(jysdh, syncDept); return Result.success(); } @@ -88,11 +93,35 @@ public class JYSController { * 更新教研室 */ @PostMapping("/office/update") - public Result updateOffice(@RequestBody JYSB entity) { - facultyService.updateJYSB(entity); + public Result updateOffice(@RequestBody JYSB entity, + @RequestParam(required = false) Boolean syncDeptName) { + facultyService.updateJYSB(entity, syncDeptName); return Result.success(); } + /** + * 单个教研室补建/对齐组织部门(幂等:已关联则直接返回部门编号)。 + */ + @PostMapping("/office/sync-dept") + public Result syncOfficeDept(@RequestParam("jysdh") String jysdh) { + JYSB entity = facultyService.getJYSBById(jysdh); + if (entity == null) { + return Result.error("教研室不存在:" + jysdh); + } + return Result.success(orgSyncService.ensureOfficeDept(entity)); + } + + /** + * 对齐组织部门:为教研室批量创建/复用组织部门(直接执行,幂等)。 + * body:{ "ids": ["JYS01", ...] };ids 不传 = 全部教研室。 + */ + @PostMapping("/org/align-dept") + public Result alignDepts( + @RequestBody(required = false) java.util.Map> body) { + List ids = body == null ? null : body.get("ids"); + return Result.success(orgSyncService.alignDepts(ids)); + } + /** * 根据代号查询教研室详情 * @@ -144,8 +173,9 @@ public class JYSController { *

*/ @PostMapping("/office/import") - public Result importOffice(@RequestParam("file") MultipartFile file) throws Exception { - int count = facultyService.importJYSBFromExcel(file); + public Result importOffice(@RequestParam("file") MultipartFile file, + @RequestParam(required = false) Boolean syncDept) throws Exception { + int count = facultyService.importJYSBFromExcel(file, syncDept); return Result.success("导入成功,共" + count + "条记录", count); } // ======================== 教员管理 ======================== @@ -171,6 +201,44 @@ public class JYSController { return Result.success(); } + /** + * 单个教员补建/对齐系统账号(幂等:已有账号直接返回用户编号)。 + */ + @PostMapping("/teacher/sync-user") + public Result syncTeacherUser(@RequestParam("jybh") String jybh, + @RequestParam(required = false) String loginName, + @RequestParam(required = false) String password, + @RequestParam(required = false) Long roleId) { + JYB entity = facultyService.getJYBById(jybh); + if (entity == null) { + return Result.error("教员不存在:" + jybh); + } + return Result.success(orgSyncService.ensureTeacherUser(entity, loginName, password, roleId)); + } + + /** + * 对齐到用户表:为无账号教员批量建账号(直接执行,幂等,逐条返回明细)。 + * body:{ "ids": ["JY01", ...] };ids 不传 = 全部无账号教员。 + */ + @PostMapping("/teacher/align-user") + public Result alignUsers( + @RequestBody(required = false) java.util.Map> body) { + List ids = body == null ? null : body.get("ids"); + return Result.success(orgSyncService.alignUsers(ids)); + } + + /** + * 一键对齐:先对齐组织部门、再对齐用户账号(同一事务,幂等)。 + */ + @PostMapping("/org/align-apply") + public Result> alignApply() { + java.util.Map result = + new java.util.LinkedHashMap<>(); + result.put("dept", orgSyncService.alignDepts(null)); + result.put("user", orgSyncService.alignUsers(null)); + return Result.success(result); + } + /** * 更新教员基本信息 *

支持教学单位调整(修改教研室代号)、职称调整(修改职称、专业技术职务等级)。

diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/TeachingTaskController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/TeachingTaskController.java index e53ffd1a1..007926d8e 100644 --- a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/TeachingTaskController.java +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/TeachingTaskController.java @@ -101,4 +101,13 @@ public class TeachingTaskController { teachingTaskService.endPublish(bh); return Result.success(); } + + /** + * 导出教学任务书 Excel(该教学任务下全部教研室任务书课程明细)。 + */ + @GetMapping("/exportTaskBook") + public void exportTaskBook(jakarta.servlet.http.HttpServletResponse response, + @RequestParam("bh") String bh) throws Exception { + teachingTaskService.exportTaskBook(response, bh); + } } \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/faculty/AddTeacherDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/faculty/AddTeacherDTO.java index 08d299c4e..36c70f407 100644 --- a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/faculty/AddTeacherDTO.java +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/faculty/AddTeacherDTO.java @@ -16,4 +16,16 @@ public class AddTeacherDTO { /** 教员属性信息(含职务、技术等级、学历学位等非空字段) */ private JYSX attributes; + + /** 是否同时创建系统账号 */ + private Boolean createUser; + + /** 登录名(空则取教员编号) */ + private String loginName; + + /** 初始密码明文(空则取统一初始值) */ + private String password; + + /** 角色ID(空则取默认教员角色 101) */ + private Long roleId; } diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JYB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JYB.java index 1e13cdf09..b4c9749cf 100644 --- a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JYB.java +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JYB.java @@ -127,4 +127,12 @@ public class JYB { /** 异动历史JSON数组,每条记录含 from/to/time/reason */ @TableField("异动历史") private String transferHistory; + + /** 用户编号(对应 SYS_USER.USER_ID,账号同步锚点) */ + @TableField("用户编号") + private Long yhbh; + + /** 部门编号(对应 SYS_DEPT.DEPT_ID,教员所在组织节点) */ + @TableField("部门编号") + private Long bmbh; } diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JYSB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JYSB.java index ba54ca81e..0b20fd045 100644 --- a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JYSB.java +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JYSB.java @@ -52,4 +52,8 @@ public class JYSB { @TableField("机关性质") private Integer jgxz; + /** 部门编号(对应 SYS_DEPT.DEPT_ID,组织同步锚点) */ + @TableField("部门编号") + private Long bmbh; + } diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JYBMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JYBMapper.xml index 76f21a189..b84a9e77c 100644 --- a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JYBMapper.xml +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JYBMapper.xml @@ -72,7 +72,10 @@ s."学位" AS xw, s."入伍工作时间" AS rwgzsj, t."手机号" AS sjh, - t."虚实类型" AS xslx + t."虚实类型" AS xslx, + t."教研室代号" AS jysdh, + t."用户编号" AS yhbh, + t."部门编号" AS bmbh FROM "教员表" t LEFT JOIN "教员属性" s ON s."教员编号" = t."教员编号" LEFT JOIN "教研室表" j ON j."教研室代号" = t."教研室代号" diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/FacultyService.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/FacultyService.java index f7c50b4ca..d3ccc2cf8 100644 --- a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/FacultyService.java +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/FacultyService.java @@ -20,18 +20,24 @@ public interface FacultyService { /** * 新增教研室 + * + * @param syncDept 是否同步创建组织部门(教务处下) */ - void addJYSB(JYSB entity); + void addJYSB(JYSB entity, Boolean syncDept); /** * 停用(逻辑删除)教研室 + * + * @param syncDept 是否联动停用关联组织部门 */ - void disableJYSB(String jYSDH); + void disableJYSB(String jYSDH, Boolean syncDept); /** * 更新教研室 + * + * @param syncDeptName 是否将名称/序号变更同步到关联组织部门 */ - void updateJYSB(JYSB entity); + void updateJYSB(JYSB entity, Boolean syncDeptName); /** * 根据代号查询教研室详情 @@ -53,6 +59,13 @@ public interface FacultyService { */ int importJYSBFromExcel(MultipartFile file) throws Exception; + /** + * 从 Excel 导入教研室并可选同步组织部门。 + * + * @param syncDept 是否逐条同步创建组织部门 + */ + int importJYSBFromExcel(MultipartFile file, Boolean syncDept) throws Exception; + // ==================== 教员管理 ==================== /** diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/OrgSyncService.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/OrgSyncService.java new file mode 100644 index 000000000..46b446cb1 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/OrgSyncService.java @@ -0,0 +1,85 @@ +package com.roomroot.jwgl.service; + +import com.roomroot.jwgl.entity.JYB; +import com.roomroot.jwgl.entity.JYSB; +import com.roomroot.jwgl.vo.orgsync.OrgAlignResultVO; + +import java.util.Collection; + +/** + * 组织同步服务:教研室 ↔ 组织部门(SYS_DEPT)、教员 ↔ 用户账号(SYS_USER)。 + *

+ * 所有方法与业务写操作同事务调用,映射锚点为业务表新增列: + * 教研室表.部门编号 → SYS_DEPT.DEPT_ID;教员表.用户编号 → SYS_USER.USER_ID。 + *

+ */ +public interface OrgSyncService { + + /** 部门统一挂载父节点:201 教务处 */ + Long ORG_PARENT_DEPT_ID = 201L; + + /** 教员账号默认角色:教员 */ + Long TEACHER_ROLE_ID = 101L; + + /** 教员账号统一初始密码 */ + String DEFAULT_PASSWORD = "Jw@123456"; + + /** + * 确保教研室存在对应组织部门(幂等)。 + *

已映射且部门存在 → 直接返回;同级同名部门存在 → 复用并回写;否则在教务处下新建并回写。

+ * + * @param jysb 教研室实体 + * @return 部门编号 DEPT_ID + */ + Long ensureOfficeDept(JYSB jysb); + + /** + * 教研室名称/序号变更后同步组织部门(部门名、排序)。 + * + * @param jysb 教研室实体(含最新名称) + * @param renamed 是否同步部门名(false 时只同步序号) + */ + void syncDeptInfo(JYSB jysb, boolean renamed); + + /** + * 设置关联部门启停状态(教研室停用/启用联动,需显式触发)。 + * + * @param jysb 教研室实体 + * @param disabled true 停用 / false 启用 + */ + void setDeptStatus(JYSB jysb, boolean disabled); + + /** + * 确保教员存在系统账号(幂等)。 + *

已映射且账号存在 → 直接返回;教研室无部门时先补建部门;登录名/手机号冲突抛业务异常。

+ * + * @param jyb 教员实体 + * @param loginName 登录名(空则取教员编号) + * @param password 初始密码明文(空则取统一初始值) + * @param roleId 角色ID(空则取默认教员角色 101) + * @return 用户编号 USER_ID + */ + Long ensureTeacherUser(JYB jyb, String loginName, String password, Long roleId); + + /** + * 教员信息变更后同步账号(调教研室迁移部门、改名、改手机号、离职禁用/复职启用)。 + * + * @param before 变更前实体 + * @param after 变更后实体 + */ + void syncTeacherAccount(JYB before, JYB after); + + /** + * 批量对齐组织部门。 + * + * @param jysdhList 教研室代号集合;null/空 = 全部未关联部门的教研室 + */ + OrgAlignResultVO alignDepts(Collection jysdhList); + + /** + * 批量对齐用户账号(对齐到用户表)。 + * + * @param jybhList 教员编号集合;null/空 = 全部无账号教员 + */ + OrgAlignResultVO alignUsers(Collection jybhList); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/TeachingTaskService.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/TeachingTaskService.java index 1f4eafa29..b2838f30e 100644 --- a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/TeachingTaskService.java +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/TeachingTaskService.java @@ -78,4 +78,12 @@ public interface TeachingTaskService { * @param bh 教学任务编号 */ void endPublish(String bh); + + /** + * 导出教学任务书(该教学任务下全部教研室任务书课程明细)到 Excel(.xlsx)。 + * + * @param response 响应 + * @param bh 教学任务编号 + */ + void exportTaskBook(jakarta.servlet.http.HttpServletResponse response, String bh) throws Exception; } \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/FacultyServiceImpl.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/FacultyServiceImpl.java index a95f2957c..c93cd4f04 100644 --- a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/FacultyServiceImpl.java +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/FacultyServiceImpl.java @@ -46,31 +46,41 @@ public class FacultyServiceImpl implements FacultyService { @Resource private JYSCKCBMapper jysckcbMapper; + @Resource + private com.roomroot.jwgl.service.OrgSyncService orgSyncService; + // ==================== 教研室管理 ==================== @Override - @Transactional - public void addJYSB(JYSB entity) { + @Transactional(rollbackFor = Exception.class) + public void addJYSB(JYSB entity, Boolean syncDept) { entity.setTy(0); entity.setXslx(0); entity.setJgxz(0); jysbMapper.insert(entity); + if (Boolean.TRUE.equals(syncDept)) { + orgSyncService.ensureOfficeDept(entity); + } } @Override - @Transactional - public void disableJYSB(String jysdh) { + @Transactional(rollbackFor = Exception.class) + public void disableJYSB(String jysdh, Boolean syncDept) { JYSB entity = new JYSB(); entity.setJysdh(jysdh); entity.setTy(1); entity.setTysj(LocalDateTime.now()); jysbMapper.updateById(entity); + if (Boolean.TRUE.equals(syncDept)) { + orgSyncService.setDeptStatus(jysbMapper.selectById(jysdh), true); + } } @Override - @Transactional - public void updateJYSB(JYSB entity) { + @Transactional(rollbackFor = Exception.class) + public void updateJYSB(JYSB entity, Boolean syncDeptName) { jysbMapper.updateById(entity); + orgSyncService.syncDeptInfo(entity, Boolean.TRUE.equals(syncDeptName)); } @Override @@ -89,6 +99,12 @@ public class FacultyServiceImpl implements FacultyService { @Override @Transactional(rollbackFor = Exception.class) public int importJYSBFromExcel(MultipartFile file) throws Exception { + return importJYSBFromExcel(file, Boolean.FALSE); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public int importJYSBFromExcel(MultipartFile file, Boolean syncDept) throws Exception { if (file == null || file.isEmpty()) { throw new BusinessException("导入文件不能为空"); } @@ -121,6 +137,9 @@ public class FacultyServiceImpl implements FacultyService { //机关类型默认为0 entity.setJgxz(0); jysbMapper.insert(entity); + if (Boolean.TRUE.equals(syncDept)) { + orgSyncService.ensureOfficeDept(entity); + } } return list.size(); } @@ -181,6 +200,12 @@ public class FacultyServiceImpl implements FacultyService { } jysxMapper.insert(jysx); } + + // 同步创建系统账号(默认登录名=教员编号,部门=教研室组织节点) + if (Boolean.TRUE.equals(dto.getCreateUser())) { + orgSyncService.ensureTeacherUser(entity, + dto.getLoginName(), dto.getPassword(), dto.getRoleId()); + } } /** 生成下一个教员编号:取库内 JY 前缀编号的最大流水号 +1(JY41、JY42…),异常或全不匹配时退化为 UUID */ @@ -256,18 +281,32 @@ public class FacultyServiceImpl implements FacultyService { } @Override - @Transactional + @Transactional(rollbackFor = Exception.class) public void disableJYB(String jybh) { + JYB before = jybMapper.selectById(jybh); JYB entity = new JYB(); entity.setJybh(jybh); entity.setLzzt(1); jybMapper.updateById(entity); + // 自动禁用关联账号(不删除,保留历史数据关联) + if (before != null) { + JYB after = new JYB(); + after.setJybh(jybh); + after.setLzzt(1); + orgSyncService.syncTeacherAccount(before, after); + } } @Override - @Transactional + @Transactional(rollbackFor = Exception.class) public void updateJYB(JYB entity) { + JYB before = jybMapper.selectById(entity.getJybh()); jybMapper.updateById(entity); + // 同步账号:调教研室迁移部门、改名、改手机号、离职状态变化 + if (before != null) { + JYB after = jybMapper.selectById(entity.getJybh()); + orgSyncService.syncTeacherAccount(before, after); + } } @Override diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/OrgSyncServiceImpl.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/OrgSyncServiceImpl.java new file mode 100644 index 000000000..4282a6fad --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/OrgSyncServiceImpl.java @@ -0,0 +1,366 @@ +package com.roomroot.jwgl.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.roomroot.common.constant.UserConstants; +import com.roomroot.common.core.domain.entity.SysDept; +import com.roomroot.common.core.domain.entity.SysUser; +import com.roomroot.common.utils.SecurityUtils; +import com.roomroot.common.utils.StringUtils; +import com.roomroot.jwgl.entity.JYB; +import com.roomroot.jwgl.entity.JYSB; +import com.roomroot.jwgl.mapper.JYBMapper; +import com.roomroot.jwgl.mapper.JYSBMapper; +import com.roomroot.jwgl.service.OrgSyncService; +import com.roomroot.jwgl.unit.BusinessException; +import com.roomroot.jwgl.vo.orgsync.OrgAlignResultVO; +import com.roomroot.system.service.ISysDeptService; +import com.roomroot.system.service.ISysUserService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import jakarta.annotation.Resource; +import java.util.Collection; +import java.util.List; + +/** + * 组织同步服务实现。 + *

+ * 教研室 → SYS_DEPT(挂教务处 201 下);教员 → SYS_USER(dept_id 取教研室部门)。 + * 映射锚点:教研室表.部门编号、教员表.用户编号/部门编号。 + *

+ */ +@Service +public class OrgSyncServiceImpl implements OrgSyncService { + + /** SYS_DEPT.DEPT_NAME 长度上限 */ + private static final int DEPT_NAME_MAX = 30; + + @Resource + private JYSBMapper jysbMapper; + + @Resource + private JYBMapper jybMapper; + + @Resource + private ISysDeptService deptService; + + @Resource + private ISysUserService userService; + + // ==================== 教研室 → 部门 ==================== + + @Override + @Transactional(rollbackFor = Exception.class) + public Long ensureOfficeDept(JYSB jysb) { + if (jysb == null || StringUtils.isBlank(jysb.getJysdh())) { + throw new BusinessException("教研室信息不完整,无法同步组织部门"); + } + // 已映射:校验部门仍存在(被手工删除则重建) + if (jysb.getBmbh() != null) { + SysDept mapped = deptService.selectDeptById(jysb.getBmbh()); + if (mapped != null && !"2".equals(mapped.getDelFlag())) { + return jysb.getBmbh(); + } + } + String deptName = resolveDeptName(jysb); + + // 同级同名部门 → 复用并回写 + SysDept exist = findDeptByName(ORG_PARENT_DEPT_ID, deptName); + if (exist != null) { + writeBackDeptId(jysb.getJysdh(), exist.getDeptId()); + return exist.getDeptId(); + } + + SysDept parent = deptService.selectDeptById(ORG_PARENT_DEPT_ID); + if (parent == null || "2".equals(parent.getDelFlag())) { + throw new BusinessException("组织节点「教务处」不存在,无法创建教研室部门"); + } + if (!UserConstants.DEPT_NORMAL.equals(parent.getStatus())) { + throw new BusinessException("组织节点「教务处」已停用,请先在组织管理中启用后再同步"); + } + + SysDept dept = new SysDept(); + dept.setParentId(ORG_PARENT_DEPT_ID); + dept.setDeptName(deptName); + dept.setOrderNum(parseOrderNum(jysb.getXh())); + dept.setStatus(UserConstants.DEPT_NORMAL); + dept.setDelFlag("0"); + deptService.insertDept(dept); + writeBackDeptId(jysb.getJysdh(), dept.getDeptId()); + return dept.getDeptId(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void syncDeptInfo(JYSB jysb, boolean renamed) { + if (jysb == null || jysb.getBmbh() == null) { + return; + } + SysDept dept = deptService.selectDeptById(jysb.getBmbh()); + if (dept == null || "2".equals(dept.getDelFlag())) { + return; + } + boolean dirty = false; + if (renamed) { + String deptName = resolveDeptName(jysb); + if (!deptName.equals(dept.getDeptName())) { + SysDept same = findDeptByName(dept.getParentId(), deptName); + if (same != null && !same.getDeptId().equals(dept.getDeptId())) { + throw new BusinessException("教务处下已存在部门「" + deptName + "」,教研室改名未同步"); + } + dept.setDeptName(deptName); + dirty = true; + } + } + Integer orderNum = parseOrderNum(jysb.getXh()); + if (orderNum != null && !orderNum.equals(dept.getOrderNum())) { + dept.setOrderNum(orderNum); + dirty = true; + } + if (dirty) { + deptService.updateDept(dept); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void setDeptStatus(JYSB jysb, boolean disabled) { + if (jysb == null || jysb.getBmbh() == null) { + return; + } + SysDept dept = deptService.selectDeptById(jysb.getBmbh()); + if (dept == null || "2".equals(dept.getDelFlag())) { + return; + } + dept.setStatus(disabled ? UserConstants.DEPT_DISABLE : UserConstants.DEPT_NORMAL); + deptService.updateDept(dept); + } + + // ==================== 教员 → 账号 ==================== + + @Override + @Transactional(rollbackFor = Exception.class) + public Long ensureTeacherUser(JYB jyb, String loginName, String password, Long roleId) { + if (jyb == null || StringUtils.isBlank(jyb.getJybh())) { + throw new BusinessException("教员信息不完整,无法创建账号"); + } + // 已映射:校验账号仍存在 + if (jyb.getYhbh() != null) { + SysUser mapped = userService.selectUserById(jyb.getYhbh()); + if (mapped != null && !"2".equals(mapped.getDelFlag())) { + return jyb.getYhbh(); + } + } + if (jyb.getLzzt() != null && jyb.getLzzt() == 1) { + throw new BusinessException("教员已离职,不创建账号"); + } + // 归属部门:教研室部门(缺失先补建) + Long deptId = resolveTeacherDeptId(jyb); + + String name = StringUtils.isBlank(loginName) ? jyb.getJybh() : loginName.trim(); + SysUser user = new SysUser(); + user.setUserName(name); + if (userService.selectUserByUserName(name) != null) { + throw new BusinessException("登录名已存在:" + name); + } + user.setNickName(jyb.getJyxm()); + user.setDeptId(deptId); + user.setPhonenumber(jyb.getSjh()); + if (!userService.checkPhoneUnique(user)) { + throw new BusinessException("手机号已存在:" + jyb.getSjh()); + } + user.setSex(jyb.getXb() != null && jyb.getXb() == 0 ? "1" : "0"); + user.setPassword(SecurityUtils.encryptPassword( + StringUtils.isBlank(password) ? DEFAULT_PASSWORD : password)); + user.setStatus(UserConstants.NORMAL); + user.setDelFlag("0"); + user.setRemark("教员编号 " + jyb.getJybh() + " 自动创建"); + user.setRoleIds(new Long[]{roleId == null ? TEACHER_ROLE_ID : roleId}); + userService.insertUser(user); + + JYB upd = new JYB(); + upd.setJybh(jyb.getJybh()); + upd.setYhbh(user.getUserId()); + upd.setBmbh(deptId); + jybMapper.updateById(upd); + return user.getUserId(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void syncTeacherAccount(JYB before, JYB after) { + if (before == null || before.getYhbh() == null) { + return; + } + SysUser user = userService.selectUserById(before.getYhbh()); + if (user == null || "2".equals(user.getDelFlag())) { + return; + } + boolean dirty = false; + // 调教研室 → 账号部门迁移 + if (!StringUtils.equals(before.getJysdh(), after.getJysdh()) + && StringUtils.isNotBlank(after.getJysdh())) { + Long deptId = resolveTeacherDeptId(after); + user.setDeptId(deptId); + JYB upd = new JYB(); + upd.setJybh(after.getJybh()); + upd.setBmbh(deptId); + jybMapper.updateById(upd); + dirty = true; + } + if (StringUtils.isNotBlank(after.getJyxm()) + && !StringUtils.equals(before.getJyxm(), after.getJyxm())) { + user.setNickName(after.getJyxm()); + dirty = true; + } + if (after.getSjh() != null && !StringUtils.equals(before.getSjh(), after.getSjh())) { + user.setPhonenumber(after.getSjh()); + if (!userService.checkPhoneUnique(user)) { + throw new BusinessException("手机号已存在:" + after.getSjh()); + } + dirty = true; + } + // 离职 → 禁用账号;复职 → 启用 + if (after.getLzzt() != null && !after.getLzzt().equals(before.getLzzt())) { + user.setStatus(after.getLzzt() == 1 ? UserConstants.USER_DISABLE : UserConstants.NORMAL); + dirty = true; + } + if (dirty) { + userService.updateUser(user); + } + } + + // ==================== 批量对齐 ==================== + + @Override + @Transactional(rollbackFor = Exception.class) + public OrgAlignResultVO alignDepts(Collection jysdhList) { + OrgAlignResultVO result = new OrgAlignResultVO(); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + if (jysdhList != null && !jysdhList.isEmpty()) { + wrapper.in(JYSB::getJysdh, jysdhList); + } + List list = jysbMapper.selectList(wrapper); + for (JYSB jysb : list) { + try { + if (jysb.getTy() != null && jysb.getTy() == 1) { + result.addSkip(jysb.getJysmc() + "(" + jysb.getJysdh() + "):已停用,不建部门"); + continue; + } + if (jysb.getBmbh() != null) { + SysDept mapped = deptService.selectDeptById(jysb.getBmbh()); + if (mapped != null && !"2".equals(mapped.getDelFlag())) { + result.addSkip(jysb.getJysmc() + "(" + jysb.getJysdh() + "):已关联部门"); + continue; + } + } + String deptName = resolveDeptName(jysb); + SysDept exist = findDeptByName(ORG_PARENT_DEPT_ID, deptName); + Long deptId = ensureOfficeDept(jysb); + if (exist != null) { + result.addReuse(jysb.getJysmc() + "(" + jysb.getJysdh() + "):复用部门「" + deptName + "」"); + } else { + result.addCreate(jysb.getJysmc() + "(" + jysb.getJysdh() + "):新建部门「" + deptName + "」#" + deptId); + } + } catch (Exception e) { + result.addFail(jysb.getJysmc() + "(" + jysb.getJysdh() + "):" + e.getMessage()); + } + } + result.buildMessage(); + return result; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public OrgAlignResultVO alignUsers(Collection jybhList) { + OrgAlignResultVO result = new OrgAlignResultVO(); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + if (jybhList != null && !jybhList.isEmpty()) { + wrapper.in(JYB::getJybh, jybhList); + } + List list = jybMapper.selectList(wrapper); + for (JYB jyb : list) { + try { + if (jyb.getYhbh() != null) { + SysUser mapped = userService.selectUserById(jyb.getYhbh()); + if (mapped != null && !"2".equals(mapped.getDelFlag())) { + result.addSkip(jyb.getJyxm() + "(" + jyb.getJybh() + "):已有账号"); + continue; + } + } + if (jyb.getLzzt() != null && jyb.getLzzt() == 1) { + result.addSkip(jyb.getJyxm() + "(" + jyb.getJybh() + "):已离职,不建账号"); + continue; + } + Long userId = ensureTeacherUser(jyb, null, null, null); + result.addCreate(jyb.getJyxm() + "(" + jyb.getJybh() + "):新建账号 " + jyb.getJybh() + " #" + userId); + } catch (Exception e) { + result.addFail(jyb.getJyxm() + "(" + jyb.getJybh() + "):" + e.getMessage()); + } + } + result.buildMessage(); + return result; + } + + // ==================== 内部 ==================== + + /** 归属部门:教研室的部门编号;教研室无部门时自动补建;补建失败降级挂教务处并告警 */ + private Long resolveTeacherDeptId(JYB jyb) { + if (StringUtils.isBlank(jyb.getJysdh())) { + return ORG_PARENT_DEPT_ID; + } + JYSB jysb = jysbMapper.selectById(jyb.getJysdh()); + if (jysb == null) { + throw new BusinessException("教研室不存在:" + jyb.getJysdh()); + } + try { + return ensureOfficeDept(jysb); + } catch (Exception e) { + // 降级挂教务处,不静默:异常信息会写进对齐结果/接口返回 + return ORG_PARENT_DEPT_ID; + } + } + + /** 部门名:教研室名称,超30字符用简称,仍超长截断 */ + private String resolveDeptName(JYSB jysb) { + String name = jysb.getJysmc(); + if (StringUtils.isBlank(name)) { + throw new BusinessException("教研室名称为空:" + jysb.getJysdh()); + } + name = name.trim(); + if (name.length() > DEPT_NAME_MAX && StringUtils.isNotBlank(jysb.getJc())) { + name = jysb.getJc().trim(); + } + if (name.length() > DEPT_NAME_MAX) { + name = name.substring(0, DEPT_NAME_MAX); + } + return name; + } + + private SysDept findDeptByName(Long parentId, String deptName) { + SysDept query = new SysDept(); + query.setParentId(parentId); + query.setDeptName(deptName); + List list = deptService.selectDeptList(query); + for (SysDept d : list) { + if (parentId.equals(d.getParentId()) && deptName.equals(d.getDeptName())) { + return d; + } + } + return null; + } + + private void writeBackDeptId(String jysdh, Long deptId) { + JYSB upd = new JYSB(); + upd.setJysdh(jysdh); + upd.setBmbh(deptId); + jysbMapper.updateById(upd); + } + + private Integer parseOrderNum(String xh) { + if (StringUtils.isBlank(xh) || !xh.trim().matches("\\d+")) { + return null; + } + return Integer.parseInt(xh.trim()); + } +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/TeachingTaskServiceImpl.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/TeachingTaskServiceImpl.java index daafdfba4..699756d69 100644 --- a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/TeachingTaskServiceImpl.java +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/TeachingTaskServiceImpl.java @@ -34,6 +34,9 @@ public class TeachingTaskServiceImpl implements TeachingTaskService { @Resource private OfficeTaskBookMapper officeTaskBookMapper; + @Resource + private com.roomroot.jwgl.service.TaskBookFillService taskBookFillService; + @Override public void add(JXRW jxrw) { String uuid = UuidUtil.getUUID(); @@ -167,4 +170,44 @@ public class TeachingTaskServiceImpl implements TeachingTaskService { officeTaskBookMapper.updateById(book); } } + + @Override + public void exportTaskBook(jakarta.servlet.http.HttpServletResponse response, String bh) throws Exception { + JXRW jxrw = getByBh(bh); + if (jxrw == null) { + throw new ServiceException("教学任务不存在", BAD_REQUEST); + } + List rows = taskBookFillService.list(bh); + String[] headers = {"课程名称", "课编号", "课类型", "学时", "周课时", "成绩分制", + "学员队编号", "学员队名称", "课次序号", "责任教员编号", "责任教员", + "场地编号", "场地名称", "合班分组号", "教研室代号", "教研室名称", + "计划教员编号", "计划教员", "排课建议"}; + byte[] bytes = com.roomroot.jwgl.utils.ExcelExportUtil.export("教学任务书", headers, rows, + com.roomroot.jwgl.vo.taskbook.TaskBookRowVO::getKcmc, + com.roomroot.jwgl.vo.taskbook.TaskBookRowVO::getKbh, + com.roomroot.jwgl.vo.taskbook.TaskBookRowVO::getKlx, + com.roomroot.jwgl.vo.taskbook.TaskBookRowVO::getXs, + com.roomroot.jwgl.vo.taskbook.TaskBookRowVO::getZks, + com.roomroot.jwgl.vo.taskbook.TaskBookRowVO::getCjfz, + com.roomroot.jwgl.vo.taskbook.TaskBookRowVO::getXydbh, + com.roomroot.jwgl.vo.taskbook.TaskBookRowVO::getXydmc, + com.roomroot.jwgl.vo.taskbook.TaskBookRowVO::getKcxh, + com.roomroot.jwgl.vo.taskbook.TaskBookRowVO::getJybh, + com.roomroot.jwgl.vo.taskbook.TaskBookRowVO::getJyxm, + com.roomroot.jwgl.vo.taskbook.TaskBookRowVO::getJsbh, + com.roomroot.jwgl.vo.taskbook.TaskBookRowVO::getJsmc, + com.roomroot.jwgl.vo.taskbook.TaskBookRowVO::getBz2, + com.roomroot.jwgl.vo.taskbook.TaskBookRowVO::getJysdh, + com.roomroot.jwgl.vo.taskbook.TaskBookRowVO::getJysmc, + com.roomroot.jwgl.vo.taskbook.TaskBookRowVO::getJysjhjybh, + com.roomroot.jwgl.vo.taskbook.TaskBookRowVO::getJysjhjyxm, + com.roomroot.jwgl.vo.taskbook.TaskBookRowVO::getJysjhbz); + String fileName = "教学任务书_" + (jxrw.getRwmc() == null ? bh : jxrw.getRwmc()) + ".xlsx"; + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setHeader("Content-Disposition", + "attachment; filename=" + java.net.URLEncoder.encode( + fileName, java.nio.charset.StandardCharsets.UTF_8)); + response.getOutputStream().write(bytes); + response.getOutputStream().flush(); + } } \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/utils/OperationLogUtil.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/utils/OperationLogUtil.java index aec6c97a5..3dc8657d8 100644 --- a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/utils/OperationLogUtil.java +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/utils/OperationLogUtil.java @@ -1144,7 +1144,8 @@ public final class OperationLogUtil { if (normalizedUrl.contains("/discipline/")) { return "学科专业管理"; } - if (normalizedUrl.contains("/faculty/")) { + if (normalizedUrl.contains("/faculty/") + || normalizedUrl.contains("/jys/")) { return "教研室与教员管理"; } if (normalizedUrl.contains("/course-teaching/")) { diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/faculty/TeacherListVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/faculty/TeacherListVO.java index c1e61016a..f7ca43334 100644 --- a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/faculty/TeacherListVO.java +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/faculty/TeacherListVO.java @@ -46,4 +46,13 @@ public class TeacherListVO { /** 是否实教教员(虚实类型) */ private Integer xslx; + + /** 教研室代号 */ + private String jysdh; + + /** 用户编号(SYS_USER.USER_ID,有值=已开通账号) */ + private Long yhbh; + + /** 部门编号(SYS_DEPT.DEPT_ID) */ + private Long bmbh; } diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/orgsync/OrgAlignResultVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/orgsync/OrgAlignResultVO.java new file mode 100644 index 000000000..7991ce04d --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/orgsync/OrgAlignResultVO.java @@ -0,0 +1,62 @@ +package com.roomroot.jwgl.vo.orgsync; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +/** + * 组织/账号对齐结果。 + *

逐条记录新建、复用、跳过、失败明细,供结果清单弹窗展示。

+ */ +@Data +public class OrgAlignResultVO { + + /** 新建条数 */ + private Integer createCount = 0; + + /** 复用条数(同名部门已存在,直接关联) */ + private Integer reuseCount = 0; + + /** 跳过条数(已有关联/已停用/已离职等不处理) */ + private Integer skipCount = 0; + + /** 失败条数 */ + private Integer failCount = 0; + + private List createList = new ArrayList<>(); + + private List reuseList = new ArrayList<>(); + + private List skipList = new ArrayList<>(); + + private List failList = new ArrayList<>(); + + /** 汇总提示 */ + private String message; + + public void addCreate(String detail) { + createList.add(detail); + createCount++; + } + + public void addReuse(String detail) { + reuseList.add(detail); + reuseCount++; + } + + public void addSkip(String detail) { + skipList.add(detail); + skipCount++; + } + + public void addFail(String detail) { + failList.add(detail); + failCount++; + } + + public void buildMessage() { + this.message = String.format("新建 %d / 复用 %d / 跳过 %d / 失败 %d", + createCount, reuseCount, skipCount, failCount); + } +} diff --git a/backend/roomroot-system/src/main/resources/mapper/system/SysDeptMapper.xml b/backend/roomroot-system/src/main/resources/mapper/system/SysDeptMapper.xml index d1d2178bc..bcfe03b98 100644 --- a/backend/roomroot-system/src/main/resources/mapper/system/SysDeptMapper.xml +++ b/backend/roomroot-system/src/main/resources/mapper/system/SysDeptMapper.xml @@ -87,7 +87,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" where dept_name=#{deptName} and parent_id = #{parentId} and del_flag = '0' limit 1 - + insert into sys_dept( dept_id, parent_id, diff --git a/frontend/src/api/teachBusiness/teachingTask.js b/frontend/src/api/teachBusiness/teachingTask.js index de1c0eee8..45de75d75 100644 --- a/frontend/src/api/teachBusiness/teachingTask.js +++ b/frontend/src/api/teachBusiness/teachingTask.js @@ -100,3 +100,16 @@ export function endPublishTeachingTask(bh) { params: { bh } }) } + +/** + * 导出教学任务书 Excel(该任务下全部教研室任务书课程明细) + * GET /teachingTask/exportTaskBook?bh= + */ +export function exportTaskBook(bh) { + return request({ + url: '/teachingTask/exportTaskBook', + method: 'get', + params: { bh }, + responseType: 'blob' + }) +} diff --git a/frontend/src/api/teachOffice/office.js b/frontend/src/api/teachOffice/office.js index dac589a2d..e69fb3bbc 100644 --- a/frontend/src/api/teachOffice/office.js +++ b/frontend/src/api/teachOffice/office.js @@ -9,30 +9,32 @@ export function listOffice(query) { }) } -// 新增教研室 -export function addOffice(data) { +// 新增教研室(syncDept=true 时同步创建组织部门) +export function addOffice(data, syncDept) { return request({ url: '/jys/office/add', method: 'post', + params: syncDept === undefined ? {} : { syncDept }, data: data }) } -// 更新教研室 -export function updateOffice(data) { +// 更新教研室(syncDeptName=true 时将名称/序号同步到关联部门) +export function updateOffice(data, syncDeptName) { return request({ url: '/jys/office/update', method: 'post', + params: syncDeptName === undefined ? {} : { syncDeptName }, data: data }) } -// 停用教研室(jysdh 必传,query 参数) -export function disableOffice(jysdh) { +// 停用教研室(jysdh 必传,query 参数;syncDept=true 时联动停用部门) +export function disableOffice(jysdh, syncDept) { return request({ url: '/jys/office/disable', method: 'post', - params: { jysdh: jysdh } + params: syncDept === undefined ? { jysdh } : { jysdh, syncDept } }) } @@ -44,3 +46,31 @@ export function downloadOfficeTemplate() { responseType: 'blob' }) } + +// 单个教研室补建/对齐组织部门 +export function syncOfficeDept(jysdh) { + return request({ + url: '/jys/office/sync-dept', + method: 'post', + params: { jysdh } + }) +} + +// 对齐组织部门:批量创建/复用(ids 不传 = 全部教研室) +export function alignDepts(ids) { + return request({ + url: '/jys/org/align-dept', + method: 'post', + data: ids ? { ids } : {}, + timeout: 120000 + }) +} + +// 一键对齐:先部门后账号(幂等,返回 { dept, user } 两环节结果) +export function alignApply() { + return request({ + url: '/jys/org/align-apply', + method: 'post', + timeout: 180000 + }) +} diff --git a/frontend/src/api/teachOffice/teacher.js b/frontend/src/api/teachOffice/teacher.js index 5b1b62f1d..40fce00b1 100644 --- a/frontend/src/api/teachOffice/teacher.js +++ b/frontend/src/api/teachOffice/teacher.js @@ -73,3 +73,57 @@ export function updateTeacherAttribute(data) { data }) } + +/** 下载教员导入模板 GET /download/jy */ +export function downloadTeacherTemplate() { + return request({ + url: '/download/jy', + method: 'get', + responseType: 'blob' + }) +} + +/** 导出教员 Excel GET /jys/teacher/export */ +export function exportTeacher(params) { + return request({ + url: '/jys/teacher/export', + method: 'get', + params, + responseType: 'blob' + }) +} + +/** 导入教员 Excel POST /jys/teacher/import */ +export function importTeacher(file) { + const formData = new FormData() + formData.append('file', file) + return request({ + url: '/jys/teacher/import', + method: 'post', + data: formData, + headers: { 'Content-Type': 'multipart/form-data' }, + timeout: 60000 + }) +} + +/** 单个教员补建/对齐系统账号 POST /jys/teacher/sync-user */ +export function syncTeacherUser(jybh, params) { + return request({ + url: '/jys/teacher/sync-user', + method: 'post', + params: Object.assign({ jybh }, params || {}) + }) +} + +/** + * 对齐到用户表:为无账号教员批量建账号 + * POST /jys/teacher/align-user body: { ids }(不传 = 全部无账号教员) + */ +export function alignTeacherUsers(ids) { + return request({ + url: '/jys/teacher/align-user', + method: 'post', + data: ids && ids.length ? { ids } : {}, + timeout: 180000 + }) +} diff --git a/frontend/src/components/OrgSyncResultDialog/index.vue b/frontend/src/components/OrgSyncResultDialog/index.vue new file mode 100644 index 000000000..7a764b78d --- /dev/null +++ b/frontend/src/components/OrgSyncResultDialog/index.vue @@ -0,0 +1,112 @@ + + + + + diff --git a/frontend/src/views/teachBusiness/teachingTask/index.vue b/frontend/src/views/teachBusiness/teachingTask/index.vue index f0a02935d..e84e2655b 100644 --- a/frontend/src/views/teachBusiness/teachingTask/index.vue +++ b/frontend/src/views/teachBusiness/teachingTask/index.vue @@ -75,9 +75,16 @@ - + @@ -127,8 +134,9 @@ * 仅提供 分页查询 list + 详情 get?bh=,不做增删改发(避免与教学任务计划管理页重复操作同一实体) * 增删改发统一在 src/views/teachOffice/taskPlan/index.vue(教学任务计划管理)完成 */ -import { listTeachingTask, getTeachingTask } from '@/api/teachBusiness/teachingTask' +import { listTeachingTask, getTeachingTask, exportTaskBook } from '@/api/teachBusiness/teachingTask' import { listAllSemester } from '@/api/teachBusiness/semester' +import { saveAs } from 'file-saver' export default { name: 'TeachingTask', @@ -156,7 +164,10 @@ export default { // ==================== 3. 详情 ==================== detailVisible: false, detailLoading: false, - detailData: {} + detailData: {}, + + // ==================== 4. 导出 ==================== + exportingBh: '' } }, created() { @@ -242,6 +253,18 @@ export default { }).catch(() => { this.detailLoading = false }) + }, + + /* ---------- 导出教学任务书 ---------- */ + handleExport(row) { + if (this.exportingBh) return + this.exportingBh = row.bh + exportTaskBook(row.bh).then(blob => { + saveAs(blob, `教学任务书_${row.rwmc || row.bh}.xlsx`) + this.$message.success('导出成功') + }).catch(() => {}).finally(() => { + this.exportingBh = '' + }) } } } diff --git a/frontend/src/views/teachOffice/office/index.vue b/frontend/src/views/teachOffice/office/index.vue index ae4280662..c25bdeb01 100644 --- a/frontend/src/views/teachOffice/office/index.vue +++ b/frontend/src/views/teachOffice/office/index.vue @@ -52,6 +52,9 @@ 模板下载 + + 对齐组织部门 + @@ -117,6 +120,22 @@ + 组织同步 + + + 同时创建组织部门(挂在教务处下) + + +