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 @@
+
+
+
+ 新建 {{ result.createCount || 0 }}
+ 复用 {{ result.reuseCount || 0 }}
+ 跳过 {{ result.skipCount || 0 }}
+ 失败 {{ result.failCount || 0 }}
+
+
+
+
+
{{ sec.label }}({{ sec.list.length }})
+
+
+
+
+
+
+ 关 闭
+
+
+
+
+
+
+
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 @@
+ 组织同步
+
+
+ 在「学员综合管理 › 教务处」下创建同名组织部门
+ 部门名称跟随教研室名称(超30字用简称);创建后可在组织管理中调整负责人/排序
+
+
+
+
+ 已关联(教务处下)
+ 未关联,保存后可用「对齐组织部门」补建
+
+
+ 保存时将教研室名称/序号同步到组织部门
+
+
+ 同时创建组织部门(挂在教务处下)
+
+
+