fix(gxmu): 新申报按学籍补齐 college_id,不再只有存量回填
4 张在跑表(gxmu_sjqn / gxmu_tzbcy_form / gxmu_qmgc_form / gxmu_wxxzx_project) 的实体补 collegeId,save 路径在为空时按「登录用户 → gxmu_student.college_id」 补齐;解析不到(无学籍、名册未同步、学院不唯一)一律留空不报错。 更新路径不调用解析器,collegeId 为 null 时 MyBatis-Plus 忽略该字段,库里已有值不变。 解析逻辑收在一个共享组件 CollegeResolver 里,4 个 controller 只各调一行; 口径与一次性回填脚本一致(优先在校那条学籍,学院不唯一就留空)。 单测:有学籍时写入、无学籍时留空、学院多义时留空、更新不覆盖已有值。 refs #58
This commit is contained in:
@@ -15,6 +15,7 @@ import com.gxwebsoft.gxmu.param.QmgcFormParam;
|
||||
import com.gxwebsoft.gxmu.service.QmgcFormService;
|
||||
import com.gxwebsoft.gxmu.service.ReviewFlowStarterService;
|
||||
import com.gxwebsoft.gxmu.model.DeclareCheckResult;
|
||||
import com.gxwebsoft.gxmu.service.CollegeResolver;
|
||||
import com.gxwebsoft.gxmu.service.DeclareGuardService;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import com.gxwebsoft.gxmu.util.GxmuQueryHelper;
|
||||
@@ -42,6 +43,8 @@ public class QmgcFormController extends BaseController {
|
||||
@Resource
|
||||
private QmgcFormService qmgcFormService;
|
||||
@Resource
|
||||
private CollegeResolver collegeResolver;
|
||||
@Resource
|
||||
private ReviewFlowStarterService reviewFlowStarterService;
|
||||
@Resource
|
||||
private DeclareGuardService declareGuardService;
|
||||
@@ -145,6 +148,9 @@ public class QmgcFormController extends BaseController {
|
||||
if (loginUser != null) {
|
||||
qmgcForm.setUserId(loginUser.getUserId());
|
||||
}
|
||||
// 新申报没有学院标识时按学籍补齐(没有学籍 / 学院不唯一就留空,不报错):
|
||||
// 审批链的 COLLEGE_ROLE 规则要靠它定位学院,一次性回填只解决存量
|
||||
qmgcForm.setCollegeId(collegeResolver.fillIfAbsent(qmgcForm.getCollegeId(), qmgcForm.getUserId()));
|
||||
DeclareCheckResult declareCheck = declareGuardService.checkBeforeCreate("gxmu_qmgc_form",
|
||||
qmgcForm.getDeclareId(), qmgcForm.getYear(), qmgcForm.getUserId());
|
||||
if (declareCheck.isRejected()) {
|
||||
|
||||
@@ -16,6 +16,7 @@ import com.gxwebsoft.gxmu.entity.SjqnForm;
|
||||
import com.gxwebsoft.gxmu.param.SjqnFormParam;
|
||||
import com.gxwebsoft.gxmu.service.ReviewFlowStarterService;
|
||||
import com.gxwebsoft.gxmu.model.DeclareCheckResult;
|
||||
import com.gxwebsoft.gxmu.service.CollegeResolver;
|
||||
import com.gxwebsoft.gxmu.service.DeclareGuardService;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import com.gxwebsoft.gxmu.service.SjqnFormService;
|
||||
@@ -46,6 +47,8 @@ public class SjqnFormController extends BaseController {
|
||||
@Resource
|
||||
private SjqnFormService sjqnFormService;
|
||||
@Resource
|
||||
private CollegeResolver collegeResolver;
|
||||
@Resource
|
||||
private ReviewFlowStarterService reviewFlowStarterService;
|
||||
@Resource
|
||||
private DeclareGuardService declareGuardService;
|
||||
@@ -162,6 +165,9 @@ public class SjqnFormController extends BaseController {
|
||||
if (loginUser != null) {
|
||||
sjqnForm.setUserId(loginUser.getUserId());
|
||||
}
|
||||
// 新申报没有学院标识时按学籍补齐(没有学籍 / 学院不唯一就留空,不报错):
|
||||
// 审批链的 COLLEGE_ROLE 规则要靠它定位学院,一次性回填只解决存量
|
||||
sjqnForm.setCollegeId(collegeResolver.fillIfAbsent(sjqnForm.getCollegeId(), sjqnForm.getUserId()));
|
||||
DeclareCheckResult declareCheck = declareGuardService.checkBeforeCreate("gxmu_sjqn",
|
||||
sjqnForm.getDeclareId(), sjqnForm.getYear(), sjqnForm.getUserId());
|
||||
if (declareCheck.isRejected()) {
|
||||
|
||||
@@ -11,6 +11,7 @@ import com.gxwebsoft.gxmu.entity.TzbcyForm;
|
||||
import com.gxwebsoft.gxmu.model.TzbcyStatisticsResult;
|
||||
import com.gxwebsoft.gxmu.param.TzbcyFormParam;
|
||||
import com.gxwebsoft.gxmu.model.DeclareCheckResult;
|
||||
import com.gxwebsoft.gxmu.service.CollegeResolver;
|
||||
import com.gxwebsoft.gxmu.service.DeclareGuardService;
|
||||
import com.gxwebsoft.gxmu.service.ReviewFlowStarterService;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@@ -38,6 +39,8 @@ public class TzbcyFormController extends BaseController {
|
||||
@Resource
|
||||
private TzbcyFormService tzbcyFormService;
|
||||
@Resource
|
||||
private CollegeResolver collegeResolver;
|
||||
@Resource
|
||||
private ReviewFlowStarterService reviewFlowStarterService;
|
||||
@Resource
|
||||
private DeclareGuardService declareGuardService;
|
||||
@@ -99,6 +102,9 @@ public class TzbcyFormController extends BaseController {
|
||||
if (loginUser != null) {
|
||||
tzbcyForm.setUserId(loginUser.getUserId());
|
||||
}
|
||||
// 新申报没有学院标识时按学籍补齐(没有学籍 / 学院不唯一就留空,不报错):
|
||||
// 审批链的 COLLEGE_ROLE 规则要靠它定位学院,一次性回填只解决存量
|
||||
tzbcyForm.setCollegeId(collegeResolver.fillIfAbsent(tzbcyForm.getCollegeId(), tzbcyForm.getUserId()));
|
||||
DeclareCheckResult declareCheck = declareGuardService.checkBeforeCreate("gxmu_tzbcy_form",
|
||||
tzbcyForm.getDeclareId(), tzbcyForm.getYear(), tzbcyForm.getUserId());
|
||||
if (declareCheck.isRejected()) {
|
||||
|
||||
@@ -27,6 +27,7 @@ import com.gxwebsoft.gxmu.param.WxxzxFormParam;
|
||||
import com.gxwebsoft.gxmu.service.QmgcFormService;
|
||||
import com.gxwebsoft.gxmu.service.ReviewFlowStarterService;
|
||||
import com.gxwebsoft.gxmu.model.DeclareCheckResult;
|
||||
import com.gxwebsoft.gxmu.service.CollegeResolver;
|
||||
import com.gxwebsoft.gxmu.service.DeclareGuardService;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import com.gxwebsoft.gxmu.service.SjqnFormService;
|
||||
@@ -100,6 +101,8 @@ public class WxxzxFormController extends BaseController {
|
||||
@Resource
|
||||
private WxxzxFormService wxxzxFormService;
|
||||
@Resource
|
||||
private CollegeResolver collegeResolver;
|
||||
@Resource
|
||||
private SjqnFormService sjqnFormService;
|
||||
@Resource
|
||||
private SjtbzbsjFormService sjtbzbsjFormService;
|
||||
@@ -254,6 +257,9 @@ public class WxxzxFormController extends BaseController {
|
||||
if (loginUser != null) {
|
||||
wxxzxForm.setUserId(loginUser.getUserId());
|
||||
}
|
||||
// 新申报没有学院标识时按学籍补齐(没有学籍 / 学院不唯一就留空,不报错):
|
||||
// 审批链的 COLLEGE_ROLE 规则要靠它定位学院,一次性回填只解决存量
|
||||
wxxzxForm.setCollegeId(collegeResolver.fillIfAbsent(wxxzxForm.getCollegeId(), wxxzxForm.getUserId()));
|
||||
DeclareCheckResult declareCheck = declareGuardService.checkBeforeCreate("gxmu_wxxzx_project",
|
||||
wxxzxForm.getDeclareId(), wxxzxForm.getYear(), wxxzxForm.getUserId());
|
||||
if (declareCheck.isRejected()) {
|
||||
|
||||
@@ -117,6 +117,9 @@ public class QmgcForm implements Serializable {
|
||||
@ApiModelProperty("用户ID")
|
||||
private Integer userId;
|
||||
|
||||
@ApiModelProperty("学院标识(sys_organization.organization_id),新建申报时按学籍补齐,允许为空")
|
||||
private Integer collegeId;
|
||||
|
||||
@ApiModelProperty("是否删除")
|
||||
@TableLogic
|
||||
private Integer deleted;
|
||||
|
||||
@@ -92,6 +92,9 @@ public class SjqnForm implements Serializable {
|
||||
@ApiModelProperty("用户ID")
|
||||
private Integer userId;
|
||||
|
||||
@ApiModelProperty("学院标识(sys_organization.organization_id),新建申报时按学籍补齐,允许为空")
|
||||
private Integer collegeId;
|
||||
|
||||
@ApiModelProperty("是否删除")
|
||||
@TableLogic
|
||||
private Integer deleted;
|
||||
|
||||
@@ -114,6 +114,9 @@ public class TzbcyForm implements Serializable {
|
||||
@ApiModelProperty("用户ID")
|
||||
private Integer userId;
|
||||
|
||||
@ApiModelProperty("学院标识(sys_organization.organization_id),新建申报时按学籍补齐,允许为空")
|
||||
private Integer collegeId;
|
||||
|
||||
@ApiModelProperty("是否删除")
|
||||
@TableLogic
|
||||
private Integer deleted;
|
||||
|
||||
@@ -143,6 +143,9 @@ public class WxxzxForm implements Serializable {
|
||||
@ApiModelProperty("用户ID")
|
||||
private Integer userId;
|
||||
|
||||
@ApiModelProperty("学院标识(sys_organization.organization_id),新建申报时按学籍补齐,允许为空")
|
||||
private Integer collegeId;
|
||||
|
||||
@ApiModelProperty("是否删除")
|
||||
@TableLogic
|
||||
private Integer deleted;
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.gxwebsoft.gxmu.service;
|
||||
|
||||
/**
|
||||
* 学院标识解析:把登录用户映射到申报记录的 college_id(spec #58 用户故事 2)。
|
||||
*
|
||||
* <p>申报表上的 college_id 指向 sys_organization.organization_id,与学生名册
|
||||
* gxmu_student.college_id 是同一套 ID(见 ADR 0006「学院标识」)。新建申报时按
|
||||
* 「登录用户 → gxmu_student.college_id」补齐,审批链里的 COLLEGE_ROLE 规则才有字段可用;
|
||||
* 解析不到(没有学籍、名册未同步、学院不唯一)一律返回 null,不报错、不猜。
|
||||
*
|
||||
* @author Codex
|
||||
* @since 2026-11-11
|
||||
*/
|
||||
public interface CollegeResolver {
|
||||
|
||||
/**
|
||||
* 按登录用户查学院标识。
|
||||
*
|
||||
* <p>同一用户有多条学籍时以「在校」那条为准;学院不唯一说明无法判断,返回 null。
|
||||
*
|
||||
* @param userId 登录用户 id(sys_user.user_id),可为空
|
||||
* @return 学院标识,解析不到返回 null
|
||||
*/
|
||||
Integer resolve(Integer userId);
|
||||
|
||||
/**
|
||||
* 只在申报记录还没有学院标识时补齐;已有值一律保留(更新路径不覆盖已有值)。
|
||||
*
|
||||
* @param currentCollegeId 申报记录当前的学院标识
|
||||
* @param userId 登录用户 id
|
||||
* @return 最终写入的学院标识
|
||||
*/
|
||||
Integer fillIfAbsent(Integer currentCollegeId, Integer userId);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.gxwebsoft.gxmu.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.gxwebsoft.gxmu.openplat.entity.GxmuStudent;
|
||||
import com.gxwebsoft.gxmu.openplat.mapper.GxmuStudentMapper;
|
||||
import com.gxwebsoft.gxmu.service.CollegeResolver;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 学院标识解析实现:gxmu_student.college_id 是学院标识的唯一权威来源。
|
||||
*
|
||||
* <p>口径与一次性回填脚本 sql/20261110_backfill_college_id.sql 保持一致:
|
||||
* 优先取「在校(status = 1)」那条学籍的学院;学院不唯一就留空交给人工,
|
||||
* 不做层级推导、不猜。
|
||||
*
|
||||
* @author Codex
|
||||
* @since 2026-11-11
|
||||
*/
|
||||
@Service
|
||||
public class CollegeResolverImpl implements CollegeResolver {
|
||||
|
||||
@Resource
|
||||
private GxmuStudentMapper gxmuStudentMapper;
|
||||
|
||||
@Override
|
||||
public Integer resolve(Integer userId) {
|
||||
if (userId == null) {
|
||||
return null;
|
||||
}
|
||||
List<GxmuStudent> students = gxmuStudentMapper.selectList(new LambdaQueryWrapper<GxmuStudent>()
|
||||
.select(GxmuStudent::getCollegeId, GxmuStudent::getStatus)
|
||||
.eq(GxmuStudent::getUserId, userId)
|
||||
.isNotNull(GxmuStudent::getCollegeId)
|
||||
.orderByDesc(GxmuStudent::getStatus)
|
||||
.orderByDesc(GxmuStudent::getId));
|
||||
if (students == null || students.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
Integer collegeId = null;
|
||||
for (GxmuStudent student : students) {
|
||||
Integer candidate = student.getCollegeId();
|
||||
if (candidate == null) {
|
||||
continue;
|
||||
}
|
||||
if (collegeId == null) {
|
||||
collegeId = candidate;
|
||||
} else if (!collegeId.equals(candidate)) {
|
||||
// 同一用户的本科学院与研究生学院不同且没有可判定的在校行:留空,不猜
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return collegeId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer fillIfAbsent(Integer currentCollegeId, Integer userId) {
|
||||
return currentCollegeId != null ? currentCollegeId : resolve(userId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
package com.gxwebsoft.gxmu.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.MybatisConfiguration;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
||||
import com.gxwebsoft.gxmu.openplat.entity.GxmuStudent;
|
||||
import com.gxwebsoft.gxmu.openplat.mapper.GxmuStudentMapper;
|
||||
import org.apache.ibatis.builder.MapperBuilderAssistant;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.mockito.junit.jupiter.MockitoSettings;
|
||||
import org.mockito.quality.Strictness;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* 学院标识解析(spec #58 用户故事 2:一条链 27 个学院共用)。
|
||||
*
|
||||
* <p>钉三件事:有学籍时新申报写入学院、没有学籍时留空不报错、更新路径不覆盖已有值。
|
||||
* 不启 Spring、不连库。
|
||||
*
|
||||
* @author Codex
|
||||
* @since 2026-11-11
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||
class CollegeResolverImplTest {
|
||||
|
||||
private static final Integer USER_ID = 12;
|
||||
private static final Integer COLLEGE_ID = 100;
|
||||
|
||||
@Mock
|
||||
private GxmuStudentMapper gxmuStudentMapper;
|
||||
|
||||
@InjectMocks
|
||||
private CollegeResolverImpl collegeResolver;
|
||||
|
||||
/** MyBatis-Plus 的 lambda 列名解析需要 TableInfo,纯单元测试里手工初始化一次 */
|
||||
@BeforeAll
|
||||
static void initLambdaCache() {
|
||||
MapperBuilderAssistant assistant = new MapperBuilderAssistant(new MybatisConfiguration(), "");
|
||||
TableInfoHelper.initTableInfo(assistant, GxmuStudent.class);
|
||||
}
|
||||
|
||||
/** 有学籍:按 gxmu_student.college_id 解析出学院。 */
|
||||
@Test
|
||||
void resolveReturnsCollegeFromStudentRecord() {
|
||||
when(gxmuStudentMapper.selectList(any()))
|
||||
.thenReturn(Collections.singletonList(student(COLLEGE_ID, 1)));
|
||||
|
||||
assertEquals(COLLEGE_ID, collegeResolver.resolve(USER_ID));
|
||||
}
|
||||
|
||||
/** 没有学籍(或学籍没学院):留空,不报错——申报不能因为名册没同步就提交不了。 */
|
||||
@Test
|
||||
void resolveReturnsNullWithoutStudentRecord() {
|
||||
when(gxmuStudentMapper.selectList(any())).thenReturn(Collections.emptyList());
|
||||
|
||||
assertNull(collegeResolver.resolve(USER_ID));
|
||||
assertNull(collegeResolver.resolve(null));
|
||||
}
|
||||
|
||||
/** 同一用户本科学院与研究生学院不同且没有在校行:留空,不猜。 */
|
||||
@Test
|
||||
void resolveReturnsNullWhenCollegesAreAmbiguous() {
|
||||
when(gxmuStudentMapper.selectList(any())).thenReturn(Arrays.asList(
|
||||
student(COLLEGE_ID, 0),
|
||||
student(200, 0)));
|
||||
|
||||
assertNull(collegeResolver.resolve(USER_ID));
|
||||
}
|
||||
|
||||
/** 新建申报:没有学院标识时写入解析结果。 */
|
||||
@Test
|
||||
void fillIfAbsentWritesResolvedCollegeForNewRecord() {
|
||||
when(gxmuStudentMapper.selectList(any()))
|
||||
.thenReturn(Collections.singletonList(student(COLLEGE_ID, 1)));
|
||||
|
||||
assertEquals(COLLEGE_ID, collegeResolver.fillIfAbsent(null, USER_ID));
|
||||
}
|
||||
|
||||
/** 更新路径:已有学院标识一律保留,不被登录用户的学籍覆盖。 */
|
||||
@Test
|
||||
void fillIfAbsentKeepsExistingCollegeOnUpdate() {
|
||||
assertEquals(Integer.valueOf(999), collegeResolver.fillIfAbsent(999, USER_ID));
|
||||
// 已有值时不查库
|
||||
assertEquals(Integer.valueOf(999), collegeResolver.fillIfAbsent(999, null));
|
||||
}
|
||||
|
||||
private GxmuStudent student(Integer collegeId, Integer status) {
|
||||
GxmuStudent student = new GxmuStudent();
|
||||
student.setUserId(USER_ID);
|
||||
student.setCollegeId(collegeId);
|
||||
student.setStatus(status);
|
||||
return student;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user