|
|
@@ -0,0 +1,785 @@
|
|
|
+package org.dromara.xg.service.impl;
|
|
|
+
|
|
|
+import cn.hutool.core.collection.CollUtil;
|
|
|
+import cn.hutool.core.util.IdUtil;
|
|
|
+import cn.hutool.json.JSONUtil;
|
|
|
+import lombok.RequiredArgsConstructor;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.dromara.common.core.exception.ServiceException;
|
|
|
+import org.dromara.common.core.utils.StringUtils;
|
|
|
+import org.dromara.common.satoken.utils.LoginHelper;
|
|
|
+import org.dromara.system.domain.SysDept;
|
|
|
+import org.dromara.system.mapper.SysDeptMapper;
|
|
|
+import org.dromara.xg.domain.XgMajorMergeBatch;
|
|
|
+import org.dromara.xg.domain.XgMajorMergeRowSnapshot;
|
|
|
+import org.dromara.xg.domain.bo.XgMajorMergeBo;
|
|
|
+import org.dromara.xg.domain.vo.XgMajorMergePreviewVo;
|
|
|
+import org.dromara.xg.domain.vo.XgMajorMergeResultVo;
|
|
|
+import org.dromara.xg.mapper.XgMajorMergeBatchMapper;
|
|
|
+import org.dromara.xg.mapper.XgMajorMergeRowSnapshotMapper;
|
|
|
+import org.dromara.xg.service.IXgMajorMergeService;
|
|
|
+import org.springframework.jdbc.core.JdbcTemplate;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.transaction.annotation.Transactional;
|
|
|
+
|
|
|
+import java.util.*;
|
|
|
+import java.util.stream.Collectors;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 专业合并服务实现
|
|
|
+ * <p>
|
|
|
+ * 规则:同学院多选合并;班级改挂目标专业(班级ID不变);major_id 与 major_name 同改;
|
|
|
+ * 源专业停用;改前写入行级快照。
|
|
|
+ */
|
|
|
+@Slf4j
|
|
|
+@Service
|
|
|
+@RequiredArgsConstructor
|
|
|
+public class XgMajorMergeServiceImpl implements IXgMajorMergeService {
|
|
|
+
|
|
|
+ private final SysDeptMapper sysDeptMapper;
|
|
|
+ private final XgMajorMergeBatchMapper batchMapper;
|
|
|
+ private final XgMajorMergeRowSnapshotMapper snapshotMapper;
|
|
|
+ private final JdbcTemplate jdbcTemplate;
|
|
|
+
|
|
|
+ /** 白名单:按 major_id(+name/+code) 更新的业务表 */
|
|
|
+ private static final List<MajorFieldSpec> MAJOR_FIELD_SPECS = List.of(
|
|
|
+ new MajorFieldSpec("xg_class_msg", "id", "major_id", "major_name", null, "deleted = 0", false),
|
|
|
+ new MajorFieldSpec("xg_student_msg", "id", "major_id", "major_name", null, "deleted = 0", true),
|
|
|
+ new MajorFieldSpec("xg_student_msg_batch", "id", "major_id", "major_name", null, "deleted = 0", true),
|
|
|
+ new MajorFieldSpec("xg_student_leave", "id", "major_id", "major_name", null, "deleted = 0", false),
|
|
|
+ new MajorFieldSpec("xg_student_cancel_leave", "id", "major_id", "major_name", null, "deleted = 0", false),
|
|
|
+ new MajorFieldSpec("xg_dorm_application", "id", "major_id", "major_name", null, "deleted = 0", false),
|
|
|
+ new MajorFieldSpec("xg_dorm_bed_student", "id", "major_id", null, null, "deleted = 0", false),
|
|
|
+ new MajorFieldSpec("xg_dorm_hygiene_student_score", "id", "major_id", "major_name", null, "deleted = 0", false),
|
|
|
+ new MajorFieldSpec("xg_dorm_roll_call_detail", "id", "major_id", "major_name", null, null, false),
|
|
|
+ new MajorFieldSpec("xg_dorm_violation", "id", "major_id", "major_name", null, "deleted = 0", false),
|
|
|
+ new MajorFieldSpec("xg_holiday_stay", "id", "major_id", "major_name", null, "deleted = 0", false),
|
|
|
+ new MajorFieldSpec("xg_stu_col_batch_class", "id", "major_id", "major_name", null, "deleted = 0", false),
|
|
|
+ new MajorFieldSpec("xljk_evaluation", "id", "major_id", "major_name", null, "deleted = 0", false),
|
|
|
+ new MajorFieldSpec("xskh_assessment_application", "id", "major_id", "major_name", null, "deleted = 0", false),
|
|
|
+ new MajorFieldSpec("xskh_assessment_application_approval", "id", "major_id", "major_name", null, "deleted = 0", false),
|
|
|
+ new MajorFieldSpec("xskh_discipline_record", "id", "major_id", "major_name", null, "deleted = 0", false),
|
|
|
+ new MajorFieldSpec("xskh_reward_record", "id", "major_id", "major_name", null, "deleted = 0", false),
|
|
|
+ new MajorFieldSpec("jcbt_poverty_application", "id", "major_id", "major_name", "major_code", "deleted = 0", false),
|
|
|
+ new MajorFieldSpec("jcbt_scholarship_application", "id", "major_id", null, null, "deleted = 0", false),
|
|
|
+ new MajorFieldSpec("zsjy_graduate_info", "id", "major_id", "major_name", null, "deleted = 0", false),
|
|
|
+ new MajorFieldSpec("zsjy_internship_info", "id", "major_id", null, null, "deleted = 0", false)
|
|
|
+ // zsjy_enrollment_major 有 uk_major_dept 唯一约束,单独处理(冲突时逻辑删除源配置)
|
|
|
+ );
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public XgMajorMergePreviewVo preview(XgMajorMergeBo bo) {
|
|
|
+ MergeContext ctx = validateAndBuildContext(bo);
|
|
|
+ XgMajorMergePreviewVo vo = new XgMajorMergePreviewVo();
|
|
|
+ vo.setTargetMajorId(ctx.target.getDeptId());
|
|
|
+ vo.setTargetMajorName(ctx.target.getDeptName());
|
|
|
+ vo.setTargetMajorCategory(ctx.target.getDeptCategory());
|
|
|
+ vo.setCollegeId(ctx.target.getParentId());
|
|
|
+ SysDept college = sysDeptMapper.selectById(ctx.target.getParentId());
|
|
|
+ vo.setCollegeName(college != null ? college.getDeptName() : null);
|
|
|
+
|
|
|
+ int totalClass = 0;
|
|
|
+ int totalStudent = 0;
|
|
|
+ for (SysDept source : ctx.sources) {
|
|
|
+ XgMajorMergePreviewVo.SourceMajorImpact impact = new XgMajorMergePreviewVo.SourceMajorImpact();
|
|
|
+ impact.setMajorId(source.getDeptId());
|
|
|
+ impact.setMajorName(source.getDeptName());
|
|
|
+ impact.setMajorCategory(source.getDeptCategory());
|
|
|
+ int classCount = countClassUnderMajor(source.getDeptId());
|
|
|
+ int studentCount = countStudentUnderMajor(source.getDeptId());
|
|
|
+ impact.setClassCount(classCount);
|
|
|
+ impact.setStudentCount(studentCount);
|
|
|
+ totalClass += classCount;
|
|
|
+ totalStudent += studentCount;
|
|
|
+ vo.getSources().add(impact);
|
|
|
+ }
|
|
|
+ vo.setTotalClassCount(totalClass);
|
|
|
+ vo.setTotalStudentCount(totalStudent);
|
|
|
+
|
|
|
+ List<Long> sourceIds = ctx.sourceIds;
|
|
|
+ List<String> sourceNames = ctx.sources.stream().map(SysDept::getDeptName).toList();
|
|
|
+ for (MajorFieldSpec spec : MAJOR_FIELD_SPECS) {
|
|
|
+ if (!tableExists(spec.tableName)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ long rows = countByMajorSpec(spec, sourceIds);
|
|
|
+ if (rows > 0) {
|
|
|
+ XgMajorMergePreviewVo.TableHit hit = new XgMajorMergePreviewVo.TableHit();
|
|
|
+ hit.setTable(spec.tableName);
|
|
|
+ hit.setStrategy("BY_ID");
|
|
|
+ hit.setRows(rows);
|
|
|
+ vo.getTableHits().add(hit);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 勤工助学:仅名称
|
|
|
+ if (tableExists("jcbt_work_study_application")) {
|
|
|
+ long rows = countByMajorNames("jcbt_work_study_application", "major_name", sourceNames, "deleted = 0");
|
|
|
+ if (rows > 0) {
|
|
|
+ XgMajorMergePreviewVo.TableHit hit = new XgMajorMergePreviewVo.TableHit();
|
|
|
+ hit.setTable("jcbt_work_study_application");
|
|
|
+ hit.setStrategy("BY_NAME_ONLY");
|
|
|
+ hit.setRows(rows);
|
|
|
+ vo.getTableHits().add(hit);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (tableExists("xg_student_manager_source")) {
|
|
|
+ long rows = countSimpleIn("xg_student_manager_source", "auth_source", sourceIds, "deleted = '0'");
|
|
|
+ if (rows > 0) {
|
|
|
+ XgMajorMergePreviewVo.TableHit hit = new XgMajorMergePreviewVo.TableHit();
|
|
|
+ hit.setTable("xg_student_manager_source");
|
|
|
+ hit.setStrategy("AUTH_SOURCE");
|
|
|
+ hit.setRows(rows);
|
|
|
+ vo.getTableHits().add(hit);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (tableExists("jcbt_scholarship_quota")) {
|
|
|
+ long rows = countSimpleIn("jcbt_scholarship_quota", "target_dept_id", sourceIds, "deleted = 0");
|
|
|
+ if (rows > 0) {
|
|
|
+ XgMajorMergePreviewVo.TableHit hit = new XgMajorMergePreviewVo.TableHit();
|
|
|
+ hit.setTable("jcbt_scholarship_quota");
|
|
|
+ hit.setStrategy("TARGET_DEPT");
|
|
|
+ hit.setRows(rows);
|
|
|
+ vo.getTableHits().add(hit);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (tableExists("sys_role_dept")) {
|
|
|
+ long rows = countSimpleIn("sys_role_dept", "dept_id", sourceIds, null);
|
|
|
+ if (rows > 0) {
|
|
|
+ XgMajorMergePreviewVo.TableHit hit = new XgMajorMergePreviewVo.TableHit();
|
|
|
+ hit.setTable("sys_role_dept");
|
|
|
+ hit.setStrategy("DEPT_ID");
|
|
|
+ hit.setRows(rows);
|
|
|
+ vo.getTableHits().add(hit);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (tableExists("zsjy_enrollment_major")) {
|
|
|
+ long rows = countSimpleIn("zsjy_enrollment_major", "major_dept_id", sourceIds, "deleted = 0");
|
|
|
+ if (rows > 0) {
|
|
|
+ XgMajorMergePreviewVo.TableHit hit = new XgMajorMergePreviewVo.TableHit();
|
|
|
+ hit.setTable("zsjy_enrollment_major");
|
|
|
+ hit.setStrategy("ENROLLMENT_DEDUP");
|
|
|
+ hit.setRows(rows);
|
|
|
+ vo.getTableHits().add(hit);
|
|
|
+ Integer targetExists = jdbcTemplate.queryForObject(
|
|
|
+ "SELECT COUNT(1) FROM zsjy_enrollment_major WHERE major_dept_id = ? AND deleted = 0",
|
|
|
+ Integer.class, ctx.target.getDeptId());
|
|
|
+ if (targetExists != null && targetExists > 0) {
|
|
|
+ vo.getWarnings().add("招生专业配置中目标专业已存在,合并时将逻辑删除源专业配置行(避免唯一键冲突)");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ long userOnMajor = countSimpleIn("sys_user", "dept_id", sourceIds, "del_flag = '0'");
|
|
|
+ if (userOnMajor > 0) {
|
|
|
+ XgMajorMergePreviewVo.TableHit hit = new XgMajorMergePreviewVo.TableHit();
|
|
|
+ hit.setTable("sys_user");
|
|
|
+ hit.setStrategy("DEPT_ID");
|
|
|
+ hit.setRows(userOnMajor);
|
|
|
+ vo.getTableHits().add(hit);
|
|
|
+ vo.getWarnings().add("存在 " + userOnMajor + " 个用户直接挂在源专业下,合并时将改挂到目标专业");
|
|
|
+ }
|
|
|
+
|
|
|
+ vo.getWarnings().add("同学院校验通过");
|
|
|
+ vo.getWarnings().add("班级仅改归属,同名班级不做合并");
|
|
|
+ vo.getWarnings().add("源专业合并后将停用;改前写入行级快照");
|
|
|
+ return vo;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public XgMajorMergeResultVo merge(XgMajorMergeBo bo) {
|
|
|
+ MergeContext ctx = validateAndBuildContext(bo);
|
|
|
+ Long batchId = IdUtil.getSnowflakeNextId();
|
|
|
+ Long userId = safeUserId();
|
|
|
+ Long deptId = safeDeptId();
|
|
|
+ Date now = new Date();
|
|
|
+
|
|
|
+ XgMajorMergeBatch batch = new XgMajorMergeBatch();
|
|
|
+ batch.setId(batchId);
|
|
|
+ batch.setCollegeId(ctx.target.getParentId());
|
|
|
+ batch.setTargetMajorId(ctx.target.getDeptId());
|
|
|
+ batch.setTargetMajorName(ctx.target.getDeptName());
|
|
|
+ batch.setSourceMajorIds(JSONUtil.toJsonStr(ctx.sourceIds));
|
|
|
+ batch.setSourceMajorNames(JSONUtil.toJsonStr(ctx.sources.stream().map(SysDept::getDeptName).toList()));
|
|
|
+ batch.setStatus("RUNNING");
|
|
|
+ batch.setSnapshotRowCount(0);
|
|
|
+ batch.setCreateBy(userId);
|
|
|
+ batch.setCreateDept(deptId);
|
|
|
+ batch.setCreateTime(now);
|
|
|
+ batch.setUpdateBy(userId);
|
|
|
+ batch.setUpdateTime(now);
|
|
|
+ batch.setTenantId(LoginHelper.getTenantId());
|
|
|
+ batchMapper.insert(batch);
|
|
|
+
|
|
|
+ List<XgMajorMergeResultVo.TableUpdateSummary> summaries = new ArrayList<>();
|
|
|
+ int snapshotCount = 0;
|
|
|
+ String targetName = ctx.target.getDeptName();
|
|
|
+ String targetCategory = ctx.target.getDeptCategory();
|
|
|
+ Long targetId = ctx.target.getDeptId();
|
|
|
+ List<String> sourceNames = ctx.sources.stream().map(SysDept::getDeptName).toList();
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 1) 业务表白名单:快照 + 更新
|
|
|
+ for (MajorFieldSpec spec : MAJOR_FIELD_SPECS) {
|
|
|
+ if (!tableExists(spec.tableName)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ int snapped = snapshotByMajorSpec(batchId, spec, ctx.sourceIds, userId, deptId, now);
|
|
|
+ snapshotCount += snapped;
|
|
|
+ int updated = updateByMajorSpec(spec, ctx.sourceIds, targetId, targetName, targetCategory);
|
|
|
+ if (updated > 0 || snapped > 0) {
|
|
|
+ summaries.add(new XgMajorMergeResultVo.TableUpdateSummary(spec.tableName, updated));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 勤工助学:仅名称
|
|
|
+ if (tableExists("jcbt_work_study_application")) {
|
|
|
+ int snapped = snapshotByNames(batchId, "jcbt_work_study_application", "id", "major_name",
|
|
|
+ sourceNames, "deleted = 0", userId, deptId, now);
|
|
|
+ snapshotCount += snapped;
|
|
|
+ int updated = updateByNames("jcbt_work_study_application", "major_name", sourceNames, targetName, "deleted = 0");
|
|
|
+ if (updated > 0 || snapped > 0) {
|
|
|
+ summaries.add(new XgMajorMergeResultVo.TableUpdateSummary("jcbt_work_study_application", updated));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 招生开放专业配置:唯一键 uk_major_dept,冲突时逻辑删除源行
|
|
|
+ if (tableExists("zsjy_enrollment_major")) {
|
|
|
+ int snapped = snapshotSimple(batchId, "zsjy_enrollment_major", "id", "major_dept_id",
|
|
|
+ ctx.sourceIds, "deleted = 0", userId, deptId, now);
|
|
|
+ snapshotCount += snapped;
|
|
|
+ int updated = remountEnrollmentMajor(ctx.sourceIds, targetId, targetName, targetCategory);
|
|
|
+ if (updated > 0 || snapped > 0) {
|
|
|
+ summaries.add(new XgMajorMergeResultVo.TableUpdateSummary("zsjy_enrollment_major", updated));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 管理员授权
|
|
|
+ if (tableExists("xg_student_manager_source")) {
|
|
|
+ int snapped = snapshotSimple(batchId, "xg_student_manager_source", "id", "auth_source",
|
|
|
+ ctx.sourceIds, "deleted = '0'", userId, deptId, now);
|
|
|
+ snapshotCount += snapped;
|
|
|
+ int updated = updateSimpleId("xg_student_manager_source", "auth_source", ctx.sourceIds, targetId, "deleted = '0'");
|
|
|
+ if (updated > 0 || snapped > 0) {
|
|
|
+ summaries.add(new XgMajorMergeResultVo.TableUpdateSummary("xg_student_manager_source", updated));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 奖学金名额
|
|
|
+ if (tableExists("jcbt_scholarship_quota")) {
|
|
|
+ assertNoQuotaConflict(ctx.sourceIds, targetId);
|
|
|
+ int snapped = snapshotSimple(batchId, "jcbt_scholarship_quota", "id", "target_dept_id",
|
|
|
+ ctx.sourceIds, "deleted = 0", userId, deptId, now);
|
|
|
+ snapshotCount += snapped;
|
|
|
+ int updated = updateSimpleId("jcbt_scholarship_quota", "target_dept_id", ctx.sourceIds, targetId, "deleted = 0");
|
|
|
+ if (updated > 0 || snapped > 0) {
|
|
|
+ summaries.add(new XgMajorMergeResultVo.TableUpdateSummary("jcbt_scholarship_quota", updated));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 角色部门
|
|
|
+ if (tableExists("sys_role_dept")) {
|
|
|
+ int snapped = snapshotRoleDept(batchId, ctx.sourceIds, userId, deptId, now);
|
|
|
+ snapshotCount += snapped;
|
|
|
+ int updated = remountRoleDept(ctx.sourceIds, targetId);
|
|
|
+ if (updated > 0 || snapped > 0) {
|
|
|
+ summaries.add(new XgMajorMergeResultVo.TableUpdateSummary("sys_role_dept", updated));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 用户直接挂在专业上
|
|
|
+ int userSnapped = snapshotSimple(batchId, "sys_user", "user_id", "dept_id",
|
|
|
+ ctx.sourceIds, "del_flag = '0'", userId, deptId, now);
|
|
|
+ snapshotCount += userSnapped;
|
|
|
+ int userUpdated = updateSimpleId("sys_user", "dept_id", ctx.sourceIds, targetId, "del_flag = '0'");
|
|
|
+ if (userUpdated > 0 || userSnapped > 0) {
|
|
|
+ summaries.add(new XgMajorMergeResultVo.TableUpdateSummary("sys_user", userUpdated));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2) 组织树:班级改挂
|
|
|
+ int movedClass = remountClasses(batchId, ctx, userId, deptId, now);
|
|
|
+ snapshotCount += movedClass; // remountClasses 内部已快照,这里 movedClass 是班级数;快照数在方法内累加需返回
|
|
|
+ // remountClasses returns class count and adds snapshots itself - fix: return snapshot added
|
|
|
+ summaries.add(new XgMajorMergeResultVo.TableUpdateSummary("sys_dept(class)", movedClass));
|
|
|
+
|
|
|
+ // 3) 停用源专业
|
|
|
+ int disabled = disableSourceMajors(batchId, ctx, userId, deptId, now);
|
|
|
+ snapshotCount += disabled;
|
|
|
+ summaries.add(new XgMajorMergeResultVo.TableUpdateSummary("sys_dept(major-disable)", disabled));
|
|
|
+
|
|
|
+ // 4) 复查残留
|
|
|
+ assertNoResidue(ctx.sourceIds);
|
|
|
+
|
|
|
+ int studentUpdated = summaries.stream()
|
|
|
+ .filter(s -> "xg_student_msg".equals(s.getTable()))
|
|
|
+ .mapToInt(XgMajorMergeResultVo.TableUpdateSummary::getUpdated)
|
|
|
+ .findFirst().orElse(0);
|
|
|
+
|
|
|
+ batch.setStatus("SUCCESS");
|
|
|
+ batch.setSnapshotRowCount(snapshotCount);
|
|
|
+ batch.setSummaryJson(JSONUtil.toJsonStr(summaries));
|
|
|
+ batch.setUpdateBy(userId);
|
|
|
+ batch.setUpdateTime(new Date());
|
|
|
+ batchMapper.updateById(batch);
|
|
|
+
|
|
|
+ XgMajorMergeResultVo result = new XgMajorMergeResultVo();
|
|
|
+ result.setSuccess(true);
|
|
|
+ result.setMergeBatchId(batchId);
|
|
|
+ result.setTargetMajorId(targetId);
|
|
|
+ result.setTargetMajorName(targetName);
|
|
|
+ result.setMovedClassCount(movedClass);
|
|
|
+ result.setUpdatedStudentCount(studentUpdated);
|
|
|
+ result.setDisabledMajorCount(disabled);
|
|
|
+ result.setSnapshotRowCount(snapshotCount);
|
|
|
+ result.setTableUpdateSummary(summaries);
|
|
|
+ return result;
|
|
|
+ } catch (RuntimeException ex) {
|
|
|
+ log.error("专业合并失败 batchId={}", batchId, ex);
|
|
|
+ throw ex;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // -------------------- 校验与上下文 --------------------
|
|
|
+
|
|
|
+ private MergeContext validateAndBuildContext(XgMajorMergeBo bo) {
|
|
|
+ if (bo.getSelectedMajorIds() == null || bo.getSelectedMajorIds().size() < 2) {
|
|
|
+ throw new ServiceException("请至少选择两个专业进行合并");
|
|
|
+ }
|
|
|
+ if (bo.getTargetMajorId() == null) {
|
|
|
+ throw new ServiceException("请选择目标专业");
|
|
|
+ }
|
|
|
+ if (!bo.getSelectedMajorIds().contains(bo.getTargetMajorId())) {
|
|
|
+ throw new ServiceException("目标专业必须在已选专业列表中");
|
|
|
+ }
|
|
|
+
|
|
|
+ Set<Long> distinct = new LinkedHashSet<>(bo.getSelectedMajorIds());
|
|
|
+ if (distinct.size() < 2) {
|
|
|
+ throw new ServiceException("请至少选择两个不同的专业");
|
|
|
+ }
|
|
|
+
|
|
|
+ SysDept target = sysDeptMapper.selectById(bo.getTargetMajorId());
|
|
|
+ if (target == null || !"0".equals(String.valueOf(target.getDelFlag()))) {
|
|
|
+ throw new ServiceException("目标专业不存在");
|
|
|
+ }
|
|
|
+ if (!StringUtils.startsWith(target.getDeptCategory(), "zy-")) {
|
|
|
+ throw new ServiceException("目标必须是专业节点(zy-)");
|
|
|
+ }
|
|
|
+ if (!"0".equals(target.getStatus())) {
|
|
|
+ throw new ServiceException("目标专业已停用,请选择正常状态的专业");
|
|
|
+ }
|
|
|
+
|
|
|
+ List<SysDept> sources = new ArrayList<>();
|
|
|
+ for (Long id : distinct) {
|
|
|
+ if (Objects.equals(id, bo.getTargetMajorId())) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ SysDept source = sysDeptMapper.selectById(id);
|
|
|
+ if (source == null || !"0".equals(String.valueOf(source.getDelFlag()))) {
|
|
|
+ throw new ServiceException("源专业不存在:" + id);
|
|
|
+ }
|
|
|
+ if (!StringUtils.startsWith(source.getDeptCategory(), "zy-")) {
|
|
|
+ throw new ServiceException("只能合并专业节点:" + source.getDeptName());
|
|
|
+ }
|
|
|
+ if (!Objects.equals(source.getParentId(), target.getParentId())) {
|
|
|
+ throw new ServiceException("仅允许同一学院下的专业合并:" + source.getDeptName());
|
|
|
+ }
|
|
|
+ sources.add(source);
|
|
|
+ }
|
|
|
+ if (sources.isEmpty()) {
|
|
|
+ throw new ServiceException("没有可合并的源专业");
|
|
|
+ }
|
|
|
+
|
|
|
+ MergeContext ctx = new MergeContext();
|
|
|
+ ctx.target = target;
|
|
|
+ ctx.sources = sources;
|
|
|
+ ctx.sourceIds = sources.stream().map(SysDept::getDeptId).toList();
|
|
|
+ return ctx;
|
|
|
+ }
|
|
|
+
|
|
|
+ // -------------------- 组织树 --------------------
|
|
|
+
|
|
|
+ private int remountClasses(Long batchId, MergeContext ctx, Long userId, Long createDept, Date now) {
|
|
|
+ String inSql = placeholders(ctx.sourceIds.size());
|
|
|
+ List<Object> args = new ArrayList<>(ctx.sourceIds);
|
|
|
+ List<Map<String, Object>> classes = jdbcTemplate.queryForList(
|
|
|
+ "SELECT * FROM sys_dept WHERE parent_id IN (" + inSql + ") AND del_flag = '0'",
|
|
|
+ args.toArray());
|
|
|
+ if (classes.isEmpty()) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ String newAncestors = ctx.target.getAncestors() + "," + ctx.target.getDeptId();
|
|
|
+ for (Map<String, Object> row : classes) {
|
|
|
+ saveSnapshotRow(batchId, "sys_dept", String.valueOf(row.get("dept_id")), row, userId, createDept, now);
|
|
|
+ jdbcTemplate.update(
|
|
|
+ "UPDATE sys_dept SET parent_id = ?, ancestors = ?, update_by = ?, update_time = NOW() WHERE dept_id = ?",
|
|
|
+ ctx.target.getDeptId(), newAncestors, userId, row.get("dept_id"));
|
|
|
+ }
|
|
|
+ return classes.size();
|
|
|
+ }
|
|
|
+
|
|
|
+ private int disableSourceMajors(Long batchId, MergeContext ctx, Long userId, Long createDept, Date now) {
|
|
|
+ int count = 0;
|
|
|
+ for (SysDept source : ctx.sources) {
|
|
|
+ Map<String, Object> row = jdbcTemplate.queryForMap(
|
|
|
+ "SELECT * FROM sys_dept WHERE dept_id = ?", source.getDeptId());
|
|
|
+ saveSnapshotRow(batchId, "sys_dept", String.valueOf(source.getDeptId()), row, userId, createDept, now);
|
|
|
+ jdbcTemplate.update(
|
|
|
+ "UPDATE sys_dept SET status = '1', update_by = ?, update_time = NOW() WHERE dept_id = ?",
|
|
|
+ userId, source.getDeptId());
|
|
|
+ count++;
|
|
|
+ }
|
|
|
+ return count;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void assertNoResidue(List<Long> sourceIds) {
|
|
|
+ // 班级不应再挂在源专业下
|
|
|
+ String inSql = placeholders(sourceIds.size());
|
|
|
+ Integer classLeft = jdbcTemplate.queryForObject(
|
|
|
+ "SELECT COUNT(1) FROM sys_dept WHERE parent_id IN (" + inSql + ") AND del_flag = '0'",
|
|
|
+ Integer.class, sourceIds.toArray());
|
|
|
+ if (classLeft != null && classLeft > 0) {
|
|
|
+ throw new ServiceException("合并复查失败:仍有班级挂在源专业下");
|
|
|
+ }
|
|
|
+ for (MajorFieldSpec spec : MAJOR_FIELD_SPECS) {
|
|
|
+ if (!tableExists(spec.tableName)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ long left = countByMajorSpec(spec, sourceIds);
|
|
|
+ if (left > 0) {
|
|
|
+ throw new ServiceException("合并复查失败:表 " + spec.tableName + " 仍有 " + left + " 行指向源专业");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // -------------------- 白名单更新 / 快照 --------------------
|
|
|
+
|
|
|
+ private int snapshotByMajorSpec(Long batchId, MajorFieldSpec spec, List<Long> sourceIds,
|
|
|
+ Long userId, Long createDept, Date now) {
|
|
|
+ if (sourceIds.isEmpty()) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ String where = buildMajorIdWhere(spec, sourceIds.size());
|
|
|
+ String sql = "SELECT * FROM " + spec.tableName + " WHERE " + where;
|
|
|
+ if (StringUtils.isNotBlank(spec.extraWhere)) {
|
|
|
+ sql += " AND (" + spec.extraWhere + ")";
|
|
|
+ }
|
|
|
+ List<Object> args = buildMajorIdArgs(spec, sourceIds);
|
|
|
+ List<Map<String, Object>> rows = jdbcTemplate.queryForList(sql, args.toArray());
|
|
|
+ for (Map<String, Object> row : rows) {
|
|
|
+ Object pk = row.get(spec.pkColumn);
|
|
|
+ saveSnapshotRow(batchId, spec.tableName, String.valueOf(pk), row, userId, createDept, now);
|
|
|
+ }
|
|
|
+ return rows.size();
|
|
|
+ }
|
|
|
+
|
|
|
+ private int updateByMajorSpec(MajorFieldSpec spec, List<Long> sourceIds,
|
|
|
+ Long targetId, String targetName, String targetCategory) {
|
|
|
+ if (sourceIds.isEmpty()) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ StringBuilder set = new StringBuilder();
|
|
|
+ List<Object> args = new ArrayList<>();
|
|
|
+ if (spec.idAsString) {
|
|
|
+ set.append(spec.idColumn).append(" = ?");
|
|
|
+ args.add(String.valueOf(targetId));
|
|
|
+ } else {
|
|
|
+ set.append(spec.idColumn).append(" = ?");
|
|
|
+ args.add(targetId);
|
|
|
+ }
|
|
|
+ if (StringUtils.isNotBlank(spec.nameColumn)) {
|
|
|
+ set.append(", ").append(spec.nameColumn).append(" = ?");
|
|
|
+ args.add(targetName);
|
|
|
+ }
|
|
|
+ if (StringUtils.isNotBlank(spec.codeColumn)) {
|
|
|
+ set.append(", ").append(spec.codeColumn).append(" = ?");
|
|
|
+ args.add(targetCategory);
|
|
|
+ }
|
|
|
+ String where = buildMajorIdWhere(spec, sourceIds.size());
|
|
|
+ String sql = "UPDATE " + spec.tableName + " SET " + set + " WHERE " + where;
|
|
|
+ if (StringUtils.isNotBlank(spec.extraWhere)) {
|
|
|
+ sql += " AND (" + spec.extraWhere + ")";
|
|
|
+ }
|
|
|
+ args.addAll(buildMajorIdArgs(spec, sourceIds));
|
|
|
+ return jdbcTemplate.update(sql, args.toArray());
|
|
|
+ }
|
|
|
+
|
|
|
+ private String buildMajorIdWhere(MajorFieldSpec spec, int size) {
|
|
|
+ if (spec.idAsString) {
|
|
|
+ // varchar major_id:同时兼容字符串与数字比较
|
|
|
+ return "(" + spec.idColumn + " IN (" + placeholders(size) + ")"
|
|
|
+ + " OR CAST(" + spec.idColumn + " AS CHAR) IN (" + placeholders(size) + "))";
|
|
|
+ }
|
|
|
+ return spec.idColumn + " IN (" + placeholders(size) + ")";
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<Object> buildMajorIdArgs(MajorFieldSpec spec, List<Long> sourceIds) {
|
|
|
+ List<Object> args = new ArrayList<>();
|
|
|
+ if (spec.idAsString) {
|
|
|
+ for (Long id : sourceIds) {
|
|
|
+ args.add(String.valueOf(id));
|
|
|
+ }
|
|
|
+ for (Long id : sourceIds) {
|
|
|
+ args.add(String.valueOf(id));
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ args.addAll(sourceIds);
|
|
|
+ }
|
|
|
+ return args;
|
|
|
+ }
|
|
|
+
|
|
|
+ private long countByMajorSpec(MajorFieldSpec spec, List<Long> sourceIds) {
|
|
|
+ String where = buildMajorIdWhere(spec, sourceIds.size());
|
|
|
+ String sql = "SELECT COUNT(1) FROM " + spec.tableName + " WHERE " + where;
|
|
|
+ if (StringUtils.isNotBlank(spec.extraWhere)) {
|
|
|
+ sql += " AND (" + spec.extraWhere + ")";
|
|
|
+ }
|
|
|
+ List<Object> args = buildMajorIdArgs(spec, sourceIds);
|
|
|
+ Long count = jdbcTemplate.queryForObject(sql, Long.class, args.toArray());
|
|
|
+ return count == null ? 0 : count;
|
|
|
+ }
|
|
|
+
|
|
|
+ private int snapshotByNames(Long batchId, String table, String pk, String nameCol,
|
|
|
+ List<String> names, String extraWhere,
|
|
|
+ Long userId, Long createDept, Date now) {
|
|
|
+ if (CollUtil.isEmpty(names)) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ String sql = "SELECT * FROM " + table + " WHERE " + nameCol + " IN (" + placeholders(names.size()) + ")";
|
|
|
+ if (StringUtils.isNotBlank(extraWhere)) {
|
|
|
+ sql += " AND (" + extraWhere + ")";
|
|
|
+ }
|
|
|
+ List<Map<String, Object>> rows = jdbcTemplate.queryForList(sql, names.toArray());
|
|
|
+ for (Map<String, Object> row : rows) {
|
|
|
+ saveSnapshotRow(batchId, table, String.valueOf(row.get(pk)), row, userId, createDept, now);
|
|
|
+ }
|
|
|
+ return rows.size();
|
|
|
+ }
|
|
|
+
|
|
|
+ private int updateByNames(String table, String nameCol, List<String> names, String targetName, String extraWhere) {
|
|
|
+ if (CollUtil.isEmpty(names)) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ String sql = "UPDATE " + table + " SET " + nameCol + " = ? WHERE " + nameCol + " IN (" + placeholders(names.size()) + ")";
|
|
|
+ if (StringUtils.isNotBlank(extraWhere)) {
|
|
|
+ sql += " AND (" + extraWhere + ")";
|
|
|
+ }
|
|
|
+ List<Object> args = new ArrayList<>();
|
|
|
+ args.add(targetName);
|
|
|
+ args.addAll(names);
|
|
|
+ return jdbcTemplate.update(sql, args.toArray());
|
|
|
+ }
|
|
|
+
|
|
|
+ private long countByMajorNames(String table, String nameCol, List<String> names, String extraWhere) {
|
|
|
+ if (CollUtil.isEmpty(names)) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ String sql = "SELECT COUNT(1) FROM " + table + " WHERE " + nameCol + " IN (" + placeholders(names.size()) + ")";
|
|
|
+ if (StringUtils.isNotBlank(extraWhere)) {
|
|
|
+ sql += " AND (" + extraWhere + ")";
|
|
|
+ }
|
|
|
+ Long count = jdbcTemplate.queryForObject(sql, Long.class, names.toArray());
|
|
|
+ return count == null ? 0 : count;
|
|
|
+ }
|
|
|
+
|
|
|
+ private int snapshotSimple(Long batchId, String table, String pk, String idCol,
|
|
|
+ List<Long> ids, String extraWhere,
|
|
|
+ Long userId, Long createDept, Date now) {
|
|
|
+ String sql = "SELECT * FROM " + table + " WHERE " + idCol + " IN (" + placeholders(ids.size()) + ")";
|
|
|
+ if (StringUtils.isNotBlank(extraWhere)) {
|
|
|
+ sql += " AND (" + extraWhere + ")";
|
|
|
+ }
|
|
|
+ List<Map<String, Object>> rows = jdbcTemplate.queryForList(sql, ids.toArray());
|
|
|
+ for (Map<String, Object> row : rows) {
|
|
|
+ saveSnapshotRow(batchId, table, String.valueOf(row.get(pk)), row, userId, createDept, now);
|
|
|
+ }
|
|
|
+ return rows.size();
|
|
|
+ }
|
|
|
+
|
|
|
+ private int updateSimpleId(String table, String idCol, List<Long> sourceIds, Long targetId, String extraWhere) {
|
|
|
+ String sql = "UPDATE " + table + " SET " + idCol + " = ? WHERE " + idCol + " IN (" + placeholders(sourceIds.size()) + ")";
|
|
|
+ if (StringUtils.isNotBlank(extraWhere)) {
|
|
|
+ sql += " AND (" + extraWhere + ")";
|
|
|
+ }
|
|
|
+ List<Object> args = new ArrayList<>();
|
|
|
+ args.add(targetId);
|
|
|
+ args.addAll(sourceIds);
|
|
|
+ return jdbcTemplate.update(sql, args.toArray());
|
|
|
+ }
|
|
|
+
|
|
|
+ private long countSimpleIn(String table, String idCol, List<Long> ids, String extraWhere) {
|
|
|
+ if (!tableExists(table)) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ String sql = "SELECT COUNT(1) FROM " + table + " WHERE " + idCol + " IN (" + placeholders(ids.size()) + ")";
|
|
|
+ if (StringUtils.isNotBlank(extraWhere)) {
|
|
|
+ sql += " AND (" + extraWhere + ")";
|
|
|
+ }
|
|
|
+ Long count = jdbcTemplate.queryForObject(sql, Long.class, ids.toArray());
|
|
|
+ return count == null ? 0 : count;
|
|
|
+ }
|
|
|
+
|
|
|
+ private int snapshotRoleDept(Long batchId, List<Long> sourceIds, Long userId, Long createDept, Date now) {
|
|
|
+ String sql = "SELECT * FROM sys_role_dept WHERE dept_id IN (" + placeholders(sourceIds.size()) + ")";
|
|
|
+ List<Map<String, Object>> rows = jdbcTemplate.queryForList(sql, sourceIds.toArray());
|
|
|
+ for (Map<String, Object> row : rows) {
|
|
|
+ String pk = row.get("role_id") + "_" + row.get("dept_id");
|
|
|
+ saveSnapshotRow(batchId, "sys_role_dept", pk, row, userId, createDept, now);
|
|
|
+ }
|
|
|
+ return rows.size();
|
|
|
+ }
|
|
|
+
|
|
|
+ private int remountRoleDept(List<Long> sourceIds, Long targetId) {
|
|
|
+ String sql = "SELECT role_id, dept_id FROM sys_role_dept WHERE dept_id IN (" + placeholders(sourceIds.size()) + ")";
|
|
|
+ List<Map<String, Object>> rows = jdbcTemplate.queryForList(sql, sourceIds.toArray());
|
|
|
+ int affected = 0;
|
|
|
+ for (Map<String, Object> row : rows) {
|
|
|
+ Long roleId = ((Number) row.get("role_id")).longValue();
|
|
|
+ Long oldDeptId = ((Number) row.get("dept_id")).longValue();
|
|
|
+ Integer exists = jdbcTemplate.queryForObject(
|
|
|
+ "SELECT COUNT(1) FROM sys_role_dept WHERE role_id = ? AND dept_id = ?",
|
|
|
+ Integer.class, roleId, targetId);
|
|
|
+ if (exists != null && exists > 0) {
|
|
|
+ affected += jdbcTemplate.update(
|
|
|
+ "DELETE FROM sys_role_dept WHERE role_id = ? AND dept_id = ?", roleId, oldDeptId);
|
|
|
+ } else {
|
|
|
+ affected += jdbcTemplate.update(
|
|
|
+ "UPDATE sys_role_dept SET dept_id = ? WHERE role_id = ? AND dept_id = ?",
|
|
|
+ targetId, roleId, oldDeptId);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return affected;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 招生开放专业配置:uk_major_dept(major_dept_id, tenant_id, deleted) 唯一。
|
|
|
+ * 目标已存在则逻辑删除源行;否则改挂到目标专业。
|
|
|
+ */
|
|
|
+ private int remountEnrollmentMajor(List<Long> sourceIds, Long targetId,
|
|
|
+ String targetName, String targetCategory) {
|
|
|
+ String sql = "SELECT id, major_dept_id FROM zsjy_enrollment_major WHERE deleted = 0 AND major_dept_id IN ("
|
|
|
+ + placeholders(sourceIds.size()) + ")";
|
|
|
+ List<Map<String, Object>> rows = jdbcTemplate.queryForList(sql, sourceIds.toArray());
|
|
|
+ if (rows.isEmpty()) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ Integer targetExists = jdbcTemplate.queryForObject(
|
|
|
+ "SELECT COUNT(1) FROM zsjy_enrollment_major WHERE major_dept_id = ? AND deleted = 0",
|
|
|
+ Integer.class, targetId);
|
|
|
+ boolean hasTarget = targetExists != null && targetExists > 0;
|
|
|
+ int affected = 0;
|
|
|
+ for (Map<String, Object> row : rows) {
|
|
|
+ Long id = ((Number) row.get("id")).longValue();
|
|
|
+ if (hasTarget) {
|
|
|
+ affected += jdbcTemplate.update(
|
|
|
+ "UPDATE zsjy_enrollment_major SET deleted = 1, update_time = NOW() WHERE id = ? AND deleted = 0",
|
|
|
+ id);
|
|
|
+ } else {
|
|
|
+ affected += jdbcTemplate.update(
|
|
|
+ "UPDATE zsjy_enrollment_major SET major_dept_id = ?, major_name = ?, major_code = ?, update_time = NOW() WHERE id = ? AND deleted = 0",
|
|
|
+ targetId, targetName, targetCategory, id);
|
|
|
+ // 第一条改挂成功后,后续源行若再改会撞唯一键,改为逻辑删除
|
|
|
+ hasTarget = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return affected;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void assertNoQuotaConflict(List<Long> sourceIds, Long targetId) {
|
|
|
+ String sql = """
|
|
|
+ SELECT COUNT(1) FROM jcbt_scholarship_quota s
|
|
|
+ WHERE s.deleted = 0 AND s.target_dept_id IN (%s)
|
|
|
+ AND EXISTS (
|
|
|
+ SELECT 1 FROM jcbt_scholarship_quota t
|
|
|
+ WHERE t.deleted = 0 AND t.target_dept_id = ?
|
|
|
+ AND t.batch_id = s.batch_id AND t.scholarship_type_id = s.scholarship_type_id
|
|
|
+ )
|
|
|
+ """.formatted(placeholders(sourceIds.size()));
|
|
|
+ List<Object> args = new ArrayList<>(sourceIds);
|
|
|
+ args.add(targetId);
|
|
|
+ Long conflict = jdbcTemplate.queryForObject(sql, Long.class, args.toArray());
|
|
|
+ if (conflict != null && conflict > 0) {
|
|
|
+ throw new ServiceException("奖学金名额表存在目标专业冲突(同批次同类型已有目标专业名额),请先手工处理后再合并");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void saveSnapshotRow(Long batchId, String tableName, String rowPk,
|
|
|
+ Map<String, Object> row, Long userId, Long createDept, Date now) {
|
|
|
+ XgMajorMergeRowSnapshot snap = new XgMajorMergeRowSnapshot();
|
|
|
+ snap.setId(IdUtil.getSnowflakeNextId());
|
|
|
+ snap.setMergeBatchId(batchId);
|
|
|
+ snap.setTableName(tableName);
|
|
|
+ snap.setRowPk(rowPk);
|
|
|
+ snap.setRowJson(JSONUtil.toJsonStr(row));
|
|
|
+ snap.setCreateBy(userId);
|
|
|
+ snap.setCreateDept(createDept);
|
|
|
+ snap.setCreateTime(now);
|
|
|
+ snap.setUpdateBy(userId);
|
|
|
+ snap.setUpdateTime(now);
|
|
|
+ snap.setTenantId(LoginHelper.getTenantId());
|
|
|
+ snapshotMapper.insert(snap);
|
|
|
+ }
|
|
|
+
|
|
|
+ // -------------------- 统计辅助 --------------------
|
|
|
+
|
|
|
+ private int countClassUnderMajor(Long majorId) {
|
|
|
+ Integer c = jdbcTemplate.queryForObject(
|
|
|
+ "SELECT COUNT(1) FROM sys_dept WHERE parent_id = ? AND del_flag = '0'",
|
|
|
+ Integer.class, majorId);
|
|
|
+ return c == null ? 0 : c;
|
|
|
+ }
|
|
|
+
|
|
|
+ private int countStudentUnderMajor(Long majorId) {
|
|
|
+ if (!tableExists("xg_student_msg")) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ Integer c = jdbcTemplate.queryForObject(
|
|
|
+ "SELECT COUNT(1) FROM xg_student_msg WHERE deleted = 0 AND (major_id = ? OR major_id = ?)",
|
|
|
+ Integer.class, String.valueOf(majorId), majorId);
|
|
|
+ return c == null ? 0 : c;
|
|
|
+ }
|
|
|
+
|
|
|
+ private boolean tableExists(String tableName) {
|
|
|
+ Integer count = jdbcTemplate.queryForObject(
|
|
|
+ "SELECT COUNT(1) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = ?",
|
|
|
+ Integer.class, tableName);
|
|
|
+ return count != null && count > 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static String placeholders(int n) {
|
|
|
+ return String.join(",", Collections.nCopies(n, "?"));
|
|
|
+ }
|
|
|
+
|
|
|
+ private Long safeUserId() {
|
|
|
+ try {
|
|
|
+ return LoginHelper.getUserId();
|
|
|
+ } catch (Exception e) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private Long safeDeptId() {
|
|
|
+ try {
|
|
|
+ return LoginHelper.getDeptId();
|
|
|
+ } catch (Exception e) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // -------------------- 内部类型 --------------------
|
|
|
+
|
|
|
+ private static class MergeContext {
|
|
|
+ private SysDept target;
|
|
|
+ private List<SysDept> sources;
|
|
|
+ private List<Long> sourceIds;
|
|
|
+ }
|
|
|
+
|
|
|
+ private record MajorFieldSpec(
|
|
|
+ String tableName,
|
|
|
+ String pkColumn,
|
|
|
+ String idColumn,
|
|
|
+ String nameColumn,
|
|
|
+ String codeColumn,
|
|
|
+ String extraWhere,
|
|
|
+ boolean idAsString
|
|
|
+ ) {
|
|
|
+ }
|
|
|
+}
|