feat(declare): 同一申报项目进行中只允许一条申报

- 新增 DeclareGuardService:按已通过/未处理驳回/待审核数判重,申报项目行加锁防连点
- 9 张申报表补 declare_id / declare_status / declare_remark,提交时写入并随审核结果回写
- 新增撤回、作废(强制放行)接口;挑战杯团队成员按手机号判重
- 申报项目支持配置允许重复提交与最多同时条数
- 迁移脚本 sql/20261102_add_declare_uniqueness.sql(含存量回填与重复清单)
This commit is contained in:
2026-11-02 19:28:56 +08:00
parent 76c9edd4be
commit 0e4986330a
28 changed files with 1089 additions and 15 deletions
+117
View File
@@ -0,0 +1,117 @@
-- 同一申报项目「进行中」只允许一条申报(见 docs/adr/0005-declare-uniqueness.md)
-- 1) 申报项目上配置是否允许重复提交、最多同时几条
ALTER TABLE gxmu_declare
ADD COLUMN allow_repeat TINYINT NOT NULL DEFAULT 0 COMMENT '是否允许重复提交 0否 1是',
ADD COLUMN max_pending INT NOT NULL DEFAULT 1 COMMENT '最多同时进行中的申报条数';
-- 2) 9 张申报表补充申报项目归属与状态
-- declare_status: 0草稿 1待审核 2通过 3驳回 4作废
ALTER TABLE gxmu_sjqn ADD COLUMN declare_id INT NULL, ADD COLUMN declare_status TINYINT NOT NULL DEFAULT 1, ADD COLUMN declare_remark VARCHAR(500) NULL;
ALTER TABLE gxmu_sjtbzbsj ADD COLUMN declare_id INT NULL, ADD COLUMN declare_status TINYINT NOT NULL DEFAULT 1, ADD COLUMN declare_remark VARCHAR(500) NULL;
ALTER TABLE gxmu_wshqtw ADD COLUMN declare_id INT NULL, ADD COLUMN declare_status TINYINT NOT NULL DEFAULT 1, ADD COLUMN declare_remark VARCHAR(500) NULL;
ALTER TABLE gxmu_wshqtzb ADD COLUMN declare_id INT NULL, ADD COLUMN declare_status TINYINT NOT NULL DEFAULT 1, ADD COLUMN declare_remark VARCHAR(500) NULL;
ALTER TABLE gxmu_yxgqtdgb ADD COLUMN declare_id INT NULL, ADD COLUMN declare_status TINYINT NOT NULL DEFAULT 1, ADD COLUMN declare_remark VARCHAR(500) NULL;
ALTER TABLE gxmu_yxgqty ADD COLUMN declare_id INT NULL, ADD COLUMN declare_status TINYINT NOT NULL DEFAULT 1, ADD COLUMN declare_remark VARCHAR(500) NULL;
ALTER TABLE gxmu_tzbcy_form ADD COLUMN declare_id INT NULL, ADD COLUMN declare_status TINYINT NOT NULL DEFAULT 1, ADD COLUMN declare_remark VARCHAR(500) NULL;
ALTER TABLE gxmu_qmgc_form ADD COLUMN declare_id INT NULL, ADD COLUMN declare_status TINYINT NOT NULL DEFAULT 1, ADD COLUMN declare_remark VARCHAR(500) NULL;
ALTER TABLE gxmu_wxxzx_project ADD COLUMN declare_id INT NULL, ADD COLUMN declare_status TINYINT NOT NULL DEFAULT 1, ADD COLUMN declare_remark VARCHAR(500) NULL;
ALTER TABLE gxmu_sjqn ADD KEY idx_declare_user_status (declare_id, user_id, declare_status);
ALTER TABLE gxmu_sjtbzbsj ADD KEY idx_declare_user_status (declare_id, user_id, declare_status);
ALTER TABLE gxmu_wshqtw ADD KEY idx_declare_user_status (declare_id, user_id, declare_status);
ALTER TABLE gxmu_wshqtzb ADD KEY idx_declare_user_status (declare_id, user_id, declare_status);
ALTER TABLE gxmu_yxgqtdgb ADD KEY idx_declare_user_status (declare_id, user_id, declare_status);
ALTER TABLE gxmu_yxgqty ADD KEY idx_declare_user_status (declare_id, user_id, declare_status);
ALTER TABLE gxmu_tzbcy_form ADD KEY idx_declare_user_status (declare_id, user_id, declare_status);
ALTER TABLE gxmu_qmgc_form ADD KEY idx_declare_user_status (declare_id, user_id, declare_status);
ALTER TABLE gxmu_wxxzx_project ADD KEY idx_declare_user_status (declare_id, user_id, declare_status);
-- 3) 存量回填 declare_id:只在「模块 + 年度」能唯一对应到一条申报项目时回填,其余留空
-- (留空的记录不参与判重,另见 docs/adr/0005-declare-uniqueness.md 的存量处置)
UPDATE gxmu_sjqn t JOIN (
SELECT module, year, MIN(id) AS id FROM gxmu_declare WHERE deleted = 0 GROUP BY module, year HAVING COUNT(*) = 1
) d ON d.module = 'gxmu_sjqn' AND d.year = t.year
SET t.declare_id = d.id WHERE t.declare_id IS NULL;
UPDATE gxmu_sjtbzbsj t JOIN (
SELECT module, year, MIN(id) AS id FROM gxmu_declare WHERE deleted = 0 GROUP BY module, year HAVING COUNT(*) = 1
) d ON d.module = 'gxmu_sjtbzbsj' AND d.year = t.year
SET t.declare_id = d.id WHERE t.declare_id IS NULL;
UPDATE gxmu_wshqtw t JOIN (
SELECT module, year, MIN(id) AS id FROM gxmu_declare WHERE deleted = 0 GROUP BY module, year HAVING COUNT(*) = 1
) d ON d.module = 'gxmu_wshqtw' AND d.year = t.year
SET t.declare_id = d.id WHERE t.declare_id IS NULL;
UPDATE gxmu_wshqtzb t JOIN (
SELECT module, year, MIN(id) AS id FROM gxmu_declare WHERE deleted = 0 GROUP BY module, year HAVING COUNT(*) = 1
) d ON d.module = 'gxmu_wshqtzb' AND d.year = t.year
SET t.declare_id = d.id WHERE t.declare_id IS NULL;
UPDATE gxmu_yxgqtdgb t JOIN (
SELECT module, year, MIN(id) AS id FROM gxmu_declare WHERE deleted = 0 GROUP BY module, year HAVING COUNT(*) = 1
) d ON d.module = 'gxmu_yxgqtdgb' AND d.year = t.year
SET t.declare_id = d.id WHERE t.declare_id IS NULL;
UPDATE gxmu_yxgqty t JOIN (
SELECT module, year, MIN(id) AS id FROM gxmu_declare WHERE deleted = 0 GROUP BY module, year HAVING COUNT(*) = 1
) d ON d.module = 'gxmu_yxgqty' AND d.year = t.year
SET t.declare_id = d.id WHERE t.declare_id IS NULL;
UPDATE gxmu_tzbcy_form t JOIN (
SELECT module, year, MIN(id) AS id FROM gxmu_declare WHERE deleted = 0 GROUP BY module, year HAVING COUNT(*) = 1
) d ON d.module = 'gxmu_tzbcy_form' AND d.year = t.year
SET t.declare_id = d.id WHERE t.declare_id IS NULL;
UPDATE gxmu_qmgc_form t JOIN (
SELECT module, year, MIN(id) AS id FROM gxmu_declare WHERE deleted = 0 GROUP BY module, year HAVING COUNT(*) = 1
) d ON d.module = 'gxmu_qmgc_form' AND d.year = t.year
SET t.declare_id = d.id WHERE t.declare_id IS NULL;
UPDATE gxmu_wxxzx_project t JOIN (
SELECT module, year, MIN(id) AS id FROM gxmu_declare WHERE deleted = 0 GROUP BY module, year HAVING COUNT(*) = 1
) d ON d.module = 'gxmu_wxxzx_project' AND d.year = t.year
SET t.declare_id = d.id WHERE t.declare_id IS NULL;
-- 4) 存量回填 declare_status:按最新一条审核节点推断(0待审核 1通过 2不通过 -> 1待审核 2通过 3驳回)
UPDATE gxmu_sjqn t JOIN (
SELECT pk, SUBSTRING_INDEX(GROUP_CONCAT(status ORDER BY id DESC), ',', 1) AS latest
FROM gxmu_review_list WHERE module = 'gxmu_sjqn' AND deleted = 0 GROUP BY pk
) r ON r.pk = t.id
SET t.declare_status = CASE WHEN r.latest = '2' THEN 3 WHEN r.latest = '1' THEN 2 ELSE 1 END;
UPDATE gxmu_sjtbzbsj t JOIN (
SELECT pk, SUBSTRING_INDEX(GROUP_CONCAT(status ORDER BY id DESC), ',', 1) AS latest
FROM gxmu_review_list WHERE module = 'gxmu_sjtbzbsj' AND deleted = 0 GROUP BY pk
) r ON r.pk = t.id
SET t.declare_status = CASE WHEN r.latest = '2' THEN 3 WHEN r.latest = '1' THEN 2 ELSE 1 END;
UPDATE gxmu_wshqtw t JOIN (
SELECT pk, SUBSTRING_INDEX(GROUP_CONCAT(status ORDER BY id DESC), ',', 1) AS latest
FROM gxmu_review_list WHERE module = 'gxmu_wshqtw' AND deleted = 0 GROUP BY pk
) r ON r.pk = t.id
SET t.declare_status = CASE WHEN r.latest = '2' THEN 3 WHEN r.latest = '1' THEN 2 ELSE 1 END;
UPDATE gxmu_wshqtzb t JOIN (
SELECT pk, SUBSTRING_INDEX(GROUP_CONCAT(status ORDER BY id DESC), ',', 1) AS latest
FROM gxmu_review_list WHERE module = 'gxmu_wshqtzb' AND deleted = 0 GROUP BY pk
) r ON r.pk = t.id
SET t.declare_status = CASE WHEN r.latest = '2' THEN 3 WHEN r.latest = '1' THEN 2 ELSE 1 END;
UPDATE gxmu_yxgqtdgb t JOIN (
SELECT pk, SUBSTRING_INDEX(GROUP_CONCAT(status ORDER BY id DESC), ',', 1) AS latest
FROM gxmu_review_list WHERE module = 'gxmu_yxgqtdgb' AND deleted = 0 GROUP BY pk
) r ON r.pk = t.id
SET t.declare_status = CASE WHEN r.latest = '2' THEN 3 WHEN r.latest = '1' THEN 2 ELSE 1 END;
UPDATE gxmu_yxgqty t JOIN (
SELECT pk, SUBSTRING_INDEX(GROUP_CONCAT(status ORDER BY id DESC), ',', 1) AS latest
FROM gxmu_review_list WHERE module = 'gxmu_yxgqty' AND deleted = 0 GROUP BY pk
) r ON r.pk = t.id
SET t.declare_status = CASE WHEN r.latest = '2' THEN 3 WHEN r.latest = '1' THEN 2 ELSE 1 END;
UPDATE gxmu_tzbcy_form t JOIN (
SELECT pk, SUBSTRING_INDEX(GROUP_CONCAT(status ORDER BY id DESC), ',', 1) AS latest
FROM gxmu_review_list WHERE module = 'gxmu_tzbcy_form' AND deleted = 0 GROUP BY pk
) r ON r.pk = t.id
SET t.declare_status = CASE WHEN r.latest = '2' THEN 3 WHEN r.latest = '1' THEN 2 ELSE 1 END;
UPDATE gxmu_qmgc_form t JOIN (
SELECT pk, SUBSTRING_INDEX(GROUP_CONCAT(status ORDER BY id DESC), ',', 1) AS latest
FROM gxmu_review_list WHERE module = 'gxmu_qmgc_form' AND deleted = 0 GROUP BY pk
) r ON r.pk = t.id
SET t.declare_status = CASE WHEN r.latest = '2' THEN 3 WHEN r.latest = '1' THEN 2 ELSE 1 END;
UPDATE gxmu_wxxzx_project t JOIN (
SELECT pk, SUBSTRING_INDEX(GROUP_CONCAT(status ORDER BY id DESC), ',', 1) AS latest
FROM gxmu_review_list WHERE module = 'gxmu_wxxzx_project' AND deleted = 0 GROUP BY pk
) r ON r.pk = t.id
SET t.declare_status = CASE WHEN r.latest = '2' THEN 3 WHEN r.latest = '1' THEN 2 ELSE 1 END;
-- 5) 存量重复清单(人工清理用,不自动处理)
-- SELECT 'gxmu_tzbcy_form' AS module, declare_id, user_id, COUNT(*) AS cnt
-- FROM gxmu_tzbcy_form WHERE declare_status = 1 AND deleted = 0 GROUP BY declare_id, user_id HAVING cnt > 1;
@@ -1,6 +1,7 @@
package com.gxwebsoft.gxmu.controller;
import com.gxwebsoft.common.core.web.BaseController;
import com.gxwebsoft.gxmu.service.DeclareGuardService;
import com.gxwebsoft.gxmu.service.DeclareService;
import com.gxwebsoft.gxmu.entity.Declare;
import com.gxwebsoft.gxmu.param.DeclareParam;
@@ -19,7 +20,9 @@ import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 申报管理控制器
@@ -36,12 +39,16 @@ public class DeclareController extends BaseController {
@Resource
private DeclareService declareService;
@Resource
private DeclareGuardService declareGuardService;
@ApiOperation("分页查询申报管理")
@GetMapping("/page")
public ApiResult<PageResult<Declare>> page(DeclareParam param) {
// 使用关联查询
return success(declareService.pageRel(param));
PageResult<Declare> result = declareService.pageRel(param);
fillUserDeclareStatus(result.getList());
return success(result);
}
@ApiOperation("分页查询当前用户申报管理")
@@ -49,21 +56,31 @@ public class DeclareController extends BaseController {
public ApiResult<PageResult<Declare>> userPage(DeclareParam param) {
Integer loginUserId = getLoginUserId();
param.setUserId(loginUserId == null ? null : loginUserId.longValue());
return success(declareService.pageRel(param));
PageResult<Declare> result = declareService.pageRel(param);
fillUserDeclareStatus(result.getList());
return success(result);
}
@ApiOperation("查询全部申报管理")
@GetMapping()
public ApiResult<List<Declare>> list(DeclareParam param) {
// 使用关联查询
return success(declareService.listRel(param));
List<Declare> list = declareService.listRel(param);
fillUserDeclareStatus(list);
return success(list);
}
@ApiOperation("根据id查询申报管理")
@GetMapping("/{id}")
public ApiResult<Declare> get(@PathVariable("id") Integer id) {
// 使用关联查询
return success(declareService.getByIdRel(id));
Declare declare = declareService.getByIdRel(id);
if (declare != null) {
List<Declare> single = new java.util.ArrayList<>();
single.add(declare);
fillUserDeclareStatus(single);
}
return success(declare);
}
@ApiOperation("添加申报管理")
@@ -78,6 +95,7 @@ public class DeclareController extends BaseController {
if (validateMessage != null) {
return fail(validateMessage);
}
normalizeRepeatConfig(declare, true);
// 显式写北京时间,避免容器 JVM/库会话仍为 UTC 时 DEFAULT CURRENT_TIMESTAMP 偏 8 小时
LocalDateTime now = LocalDateTime.now(BEIJING);
declare.setCreateTime(now);
@@ -95,6 +113,7 @@ public class DeclareController extends BaseController {
if (validateMessage != null) {
return fail(validateMessage);
}
normalizeRepeatConfig(declare, false);
declare.setUpdateTime(LocalDateTime.now(BEIJING));
if (declareService.updateById(declare)) {
return success("修改成功");
@@ -180,4 +199,68 @@ public class DeclareController extends BaseController {
return null;
}
}
/** 是否允许重复提交 / 最多同时几条:默认不允许、上限 1 */
private void normalizeRepeatConfig(Declare declare, boolean creating) {
if (declare.getAllowRepeat() == null && creating) {
declare.setAllowRepeat(0);
}
if (declare.getMaxPending() == null && creating) {
declare.setMaxPending(1);
}
if (declare.getMaxPending() != null && declare.getMaxPending() < 1) {
declare.setMaxPending(1);
}
}
/** 回填当前用户在各申报项目下的最新记录状态,供小程序列表展示与禁用 */
private void fillUserDeclareStatus(List<Declare> list) {
Integer userId = getLoginUserId();
if (userId == null || list == null || list.isEmpty()) {
return;
}
Map<String, Map<Integer, Map<String, Object>>> cache = new HashMap<>();
for (Declare declare : list) {
String module = declare.getModule();
if (declare.getId() == null || module == null || !declareGuardService.supports(module)) {
continue;
}
Map<Integer, Map<String, Object>> mine = cache.get(module);
if (mine == null) {
mine = declareGuardService.myDeclareStatus(module, userId);
cache.put(module, mine);
}
Map<String, Object> row = mine.get(declare.getId());
if (row == null) {
continue;
}
declare.setMyStatus(asInteger(row.get("declareStatus")));
declare.setMyRecordId(asInteger(row.get("id")));
declare.setMySubmitTime(formatSubmitTime(row.get("createTime")));
declare.setMyStatusRemark(row.get("declareRemark") == null ? null : String.valueOf(row.get("declareRemark")));
}
}
private Integer asInteger(Object value) {
if (value == null) {
return null;
}
if (value instanceof Number) {
return ((Number) value).intValue();
}
try {
return Integer.valueOf(String.valueOf(value));
} catch (NumberFormatException ignored) {
return null;
}
}
private String formatSubmitTime(Object value) {
if (value == null) {
return null;
}
String text = String.valueOf(value);
return text.length() >= 10 ? text.substring(0, 10) : text;
}
}
@@ -0,0 +1,66 @@
package com.gxwebsoft.gxmu.controller;
import com.gxwebsoft.common.core.annotation.OperationLog;
import com.gxwebsoft.common.core.web.ApiResult;
import com.gxwebsoft.common.core.web.BaseController;
import com.gxwebsoft.gxmu.param.DeclareRecordActionParam;
import com.gxwebsoft.gxmu.service.DeclareGuardService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.Map;
/**
* 申报记录状态操作:撤回、作废(强制放行)。
*
* <p>规则见 docs/adr/0005-declare-uniqueness.md。</p>
*/
@Api(tags = "申报记录状态管理")
@RestController
@RequestMapping("/api/gxmu/declare-record")
public class DeclareRecordController extends BaseController {
@Resource
private DeclareGuardService declareGuardService;
@OperationLog
@ApiOperation("撤回申报:待审核 -> 草稿")
@PostMapping("/withdraw")
public ApiResult<?> withdraw(@RequestBody DeclareRecordActionParam param) {
if (param == null || param.getModule() == null || param.getId() == null) {
return fail("参数不正确");
}
boolean admin = !hasOnlyStudentRole();
String error = declareGuardService.withdraw(param.getModule(), param.getId(), getLoginUserId(), admin);
return error == null ? success("已撤回,可在草稿中继续修改后重新提交") : fail(error);
}
@OperationLog
@ApiOperation("作废申报(管理员,作废后本人可重新申报)")
@PostMapping("/void")
public ApiResult<?> voidRecord(@RequestBody DeclareRecordActionParam param) {
if (param == null || param.getModule() == null || param.getId() == null) {
return fail("参数不正确");
}
if (hasOnlyStudentRole()) {
return fail("没有作废申报的权限");
}
String error = declareGuardService.voidRecord(param.getModule(), param.getId(), param.getReason());
return error == null ? success("已作废,该申报人可重新申报") : fail(error);
}
@ApiOperation("当前用户在某申报项目下的最新申报记录")
@GetMapping("/mine")
public ApiResult<?> mine(@RequestParam(value = "module", required = false) String module,
@RequestParam(value = "declareId", required = false) Integer declareId) {
Integer userId = getLoginUserId();
if (module == null || declareId == null || userId == null) {
Map<String, Object> empty = null;
return success(empty);
}
Map<Integer, Map<String, Object>> all = declareGuardService.myDeclareStatus(module, userId);
return success(all.get(declareId));
}
}
@@ -14,6 +14,9 @@ import com.gxwebsoft.gxmu.entity.QmgcForm;
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.DeclareGuardService;
import org.springframework.transaction.annotation.Transactional;
import com.gxwebsoft.gxmu.util.GxmuQueryHelper;
import com.gxwebsoft.gxmu.util.QmgcDocxExportUtil;
import io.swagger.annotations.Api;
@@ -41,6 +44,8 @@ public class QmgcFormController extends BaseController {
@Resource
private ReviewFlowStarterService reviewFlowStarterService;
@Resource
private DeclareGuardService declareGuardService;
@Resource
private FileRecordService fileRecordService;
@Value("${config.upload-path}")
@@ -134,11 +139,21 @@ public class QmgcFormController extends BaseController {
@OperationLog
@ApiOperation("添加青马工程培训班学员登记表")
@PostMapping()
@Transactional(rollbackFor = Exception.class)
public ApiResult<?> save(@RequestBody QmgcForm qmgcForm) {
User loginUser = getLoginUser();
if (loginUser != null) {
qmgcForm.setUserId(loginUser.getUserId());
}
DeclareCheckResult declareCheck = declareGuardService.checkBeforeCreate("gxmu_qmgc_form",
qmgcForm.getDeclareId(), qmgcForm.getYear(), qmgcForm.getUserId());
if (declareCheck.isRejected()) {
return fail(declareCheck.getError());
}
if (declareCheck.getDeclareId() != null) {
qmgcForm.setDeclareId(declareCheck.getDeclareId());
}
qmgcForm.setDeclareStatus(1);
if (qmgcFormService.save(qmgcForm)) {
reviewFlowStarterService.startReview("gxmu_qmgc_form", qmgcForm.getId());
return success("添加成功");
@@ -187,4 +202,4 @@ public class QmgcFormController extends BaseController {
}
return String.valueOf(java.time.LocalDate.now().getYear());
}
}
}
@@ -5,6 +5,7 @@ import com.gxwebsoft.gxmu.entity.ReviewFlow;
import com.gxwebsoft.gxmu.entity.ReviewFlowConfig;
import com.gxwebsoft.gxmu.service.ReviewFlowConfigService;
import com.gxwebsoft.gxmu.service.ReviewFlowService;
import com.gxwebsoft.gxmu.service.DeclareGuardService;
import com.gxwebsoft.gxmu.service.ReviewListService;
import com.gxwebsoft.gxmu.entity.ReviewList;
import com.gxwebsoft.gxmu.param.ReviewListParam;
@@ -38,6 +39,8 @@ public class ReviewListController extends BaseController {
private ReviewFlowConfigService reviewFlowConfigService;
@Resource
private ReviewFlowService reviewFlowService;
@Resource
private DeclareGuardService declareGuardService;
@ApiOperation("分页查询审核列表")
@GetMapping("/page")
@@ -125,6 +128,7 @@ public class ReviewListController extends BaseController {
}
}
}
declareGuardService.syncReviewStatus(reviewList.getModule(), reviewList.getPk());
return success("修改成功");
}
return fail("修改失败");
@@ -152,11 +156,25 @@ public class ReviewListController extends BaseController {
@PutMapping("/batch")
public ApiResult<?> updateBatch(@RequestBody BatchParam<ReviewList> batchParam) {
if (batchParam.update(reviewListService, "id")) {
syncBatchReviewStatus(batchParam);
return success("修改成功");
}
return fail("修改失败");
}
/** 批量审核后同样要回写申报记录状态,否则重复申报限制会读到旧状态 */
private void syncBatchReviewStatus(BatchParam<ReviewList> batchParam) {
if (batchParam == null || batchParam.getIds() == null) {
return;
}
for (java.io.Serializable id : batchParam.getIds()) {
ReviewList saved = reviewListService.getById(id);
if (saved != null) {
declareGuardService.syncReviewStatus(saved.getModule(), saved.getPk());
}
}
}
@ApiOperation("批量删除审核列表")
@DeleteMapping("/batch")
public ApiResult<?> removeBatch(@RequestBody List<Integer> ids) {
@@ -196,4 +214,4 @@ public class ReviewListController extends BaseController {
}
}
}
}
@@ -15,6 +15,9 @@ import com.gxwebsoft.common.system.service.OrganizationService;
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.DeclareGuardService;
import org.springframework.transaction.annotation.Transactional;
import com.gxwebsoft.gxmu.service.SjqnFormService;
import com.gxwebsoft.gxmu.util.GxmuQueryHelper;
import com.gxwebsoft.gxmu.util.SjqnDocxExportUtil;
@@ -45,6 +48,8 @@ public class SjqnFormController extends BaseController {
@Resource
private ReviewFlowStarterService reviewFlowStarterService;
@Resource
private DeclareGuardService declareGuardService;
@Resource
private FileRecordService fileRecordService;
@Resource
private OrganizationService organizationService;
@@ -151,11 +156,21 @@ public class SjqnFormController extends BaseController {
@OperationLog
@ApiOperation("添加十佳青年申报表")
@PostMapping()
@Transactional(rollbackFor = Exception.class)
public ApiResult<?> save(@RequestBody SjqnForm sjqnForm) {
User loginUser = getLoginUser();
if (loginUser != null) {
sjqnForm.setUserId(loginUser.getUserId());
}
DeclareCheckResult declareCheck = declareGuardService.checkBeforeCreate("gxmu_sjqn",
sjqnForm.getDeclareId(), sjqnForm.getYear(), sjqnForm.getUserId());
if (declareCheck.isRejected()) {
return fail(declareCheck.getError());
}
if (declareCheck.getDeclareId() != null) {
sjqnForm.setDeclareId(declareCheck.getDeclareId());
}
sjqnForm.setDeclareStatus(1);
if (sjqnFormService.save(sjqnForm)) {
reviewFlowStarterService.startReview("gxmu_sjqn", sjqnForm.getId());
return success("添加成功");
@@ -230,4 +245,4 @@ public class SjqnFormController extends BaseController {
private String valueOf(String value) {
return value == null ? "" : value.trim();
}
}
}
@@ -15,6 +15,9 @@ import com.gxwebsoft.common.system.service.OrganizationService;
import com.gxwebsoft.gxmu.entity.SjtbzbsjForm;
import com.gxwebsoft.gxmu.param.SjtbzbsjFormParam;
import com.gxwebsoft.gxmu.service.ReviewFlowStarterService;
import com.gxwebsoft.gxmu.model.DeclareCheckResult;
import com.gxwebsoft.gxmu.service.DeclareGuardService;
import org.springframework.transaction.annotation.Transactional;
import com.gxwebsoft.gxmu.service.SjtbzbsjFormService;
import com.gxwebsoft.gxmu.util.GxmuQueryHelper;
import com.gxwebsoft.gxmu.util.SjtbzbsjDocxExportUtil;
@@ -45,6 +48,8 @@ public class SjtbzbsjFormController extends BaseController {
@Resource
private ReviewFlowStarterService reviewFlowStarterService;
@Resource
private DeclareGuardService declareGuardService;
@Resource
private FileRecordService fileRecordService;
@Resource
private OrganizationService organizationService;
@@ -151,11 +156,21 @@ public class SjtbzbsjFormController extends BaseController {
@OperationLog
@ApiOperation("添加十佳团支部书记申报表")
@PostMapping()
@Transactional(rollbackFor = Exception.class)
public ApiResult<?> save(@RequestBody SjtbzbsjForm sjtbzbsjForm) {
User loginUser = getLoginUser();
if (loginUser != null) {
sjtbzbsjForm.setUserId(loginUser.getUserId());
}
DeclareCheckResult declareCheck = declareGuardService.checkBeforeCreate("gxmu_sjtbzbsj",
sjtbzbsjForm.getDeclareId(), sjtbzbsjForm.getYear(), sjtbzbsjForm.getUserId());
if (declareCheck.isRejected()) {
return fail(declareCheck.getError());
}
if (declareCheck.getDeclareId() != null) {
sjtbzbsjForm.setDeclareId(declareCheck.getDeclareId());
}
sjtbzbsjForm.setDeclareStatus(1);
if (sjtbzbsjFormService.save(sjtbzbsjForm)) {
reviewFlowStarterService.startReview("gxmu_sjtbzbsj", sjtbzbsjForm.getId());
return success("添加成功");
@@ -230,4 +245,4 @@ public class SjtbzbsjFormController extends BaseController {
private String valueOf(String value) {
return value == null ? "" : value.trim();
}
}
}
@@ -10,7 +10,10 @@ import com.gxwebsoft.common.system.entity.User;
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.DeclareGuardService;
import com.gxwebsoft.gxmu.service.ReviewFlowStarterService;
import org.springframework.transaction.annotation.Transactional;
import com.gxwebsoft.gxmu.service.TzbcyFormService;
import com.gxwebsoft.gxmu.util.GxmuQueryHelper;
import com.gxwebsoft.gxmu.util.TzbcyFormItemNormalizer;
@@ -36,6 +39,8 @@ public class TzbcyFormController extends BaseController {
private TzbcyFormService tzbcyFormService;
@Resource
private ReviewFlowStarterService reviewFlowStarterService;
@Resource
private DeclareGuardService declareGuardService;
@ApiOperation("分页查询挑战杯创业计划竞赛申报表")
@GetMapping("/page")
@@ -87,12 +92,26 @@ public class TzbcyFormController extends BaseController {
@OperationLog
@ApiOperation("添加挑战杯创业计划竞赛申报表")
@PostMapping()
@Transactional(rollbackFor = Exception.class)
public ApiResult<?> save(@RequestBody TzbcyForm tzbcyForm) {
normalizeForm(tzbcyForm);
User loginUser = getLoginUser();
if (loginUser != null) {
tzbcyForm.setUserId(loginUser.getUserId());
}
DeclareCheckResult declareCheck = declareGuardService.checkBeforeCreate("gxmu_tzbcy_form",
tzbcyForm.getDeclareId(), tzbcyForm.getYear(), tzbcyForm.getUserId());
if (declareCheck.isRejected()) {
return fail(declareCheck.getError());
}
if (declareCheck.getDeclareId() != null) {
tzbcyForm.setDeclareId(declareCheck.getDeclareId());
}
String teamError = checkTeamRepeat(tzbcyForm, null);
if (teamError != null) {
return fail(teamError);
}
tzbcyForm.setDeclareStatus(1);
if (tzbcyFormService.save(tzbcyForm)) {
reviewFlowStarterService.startReview("gxmu_tzbcy_form", tzbcyForm.getId());
return success("添加成功");
@@ -105,6 +124,16 @@ public class TzbcyFormController extends BaseController {
@PutMapping()
public ApiResult<?> update(@RequestBody TzbcyForm tzbcyForm) {
normalizeForm(tzbcyForm);
if (tzbcyForm.getDeclareId() == null && tzbcyForm.getId() != null) {
TzbcyForm existing = tzbcyFormService.getById(tzbcyForm.getId());
if (existing != null) {
tzbcyForm.setDeclareId(existing.getDeclareId());
}
}
String teamError = checkTeamRepeat(tzbcyForm, tzbcyForm.getId());
if (teamError != null) {
return fail(teamError);
}
if (tzbcyFormService.updateById(tzbcyForm)) {
reviewFlowStarterService.restartReviewWhenLastRejected("gxmu_tzbcy_form", tzbcyForm.getId());
return success("修改成功");
@@ -170,4 +199,16 @@ public class TzbcyFormController extends BaseController {
private boolean hasText(String value) {
return value != null && !value.trim().isEmpty();
}
}
/** 团队成员手机号在本项目内不得重复(见 ADR 0005) */
private String checkTeamRepeat(TzbcyForm form, Integer excludeRecordId) {
List<String> memberPhones = form.getTeamMembers() == null ? null
: form.getTeamMembers().stream()
.filter(java.util.Objects::nonNull)
.map(TzbcyForm.TeamMember::getPhone)
.collect(Collectors.toList());
return declareGuardService.checkTeamPhones("gxmu_tzbcy_form", form.getDeclareId(),
excludeRecordId, form.getPhone(), memberPhones);
}
}
@@ -15,6 +15,9 @@ import com.gxwebsoft.common.system.service.OrganizationService;
import com.gxwebsoft.gxmu.entity.WshqtwForm;
import com.gxwebsoft.gxmu.param.WshqtwFormParam;
import com.gxwebsoft.gxmu.service.ReviewFlowStarterService;
import com.gxwebsoft.gxmu.model.DeclareCheckResult;
import com.gxwebsoft.gxmu.service.DeclareGuardService;
import org.springframework.transaction.annotation.Transactional;
import com.gxwebsoft.gxmu.service.WshqtwFormService;
import com.gxwebsoft.gxmu.util.GxmuQueryHelper;
import com.gxwebsoft.gxmu.util.WshqtwDocxExportUtil;
@@ -45,6 +48,8 @@ public class WshqtwFormController extends BaseController {
@Resource
private ReviewFlowStarterService reviewFlowStarterService;
@Resource
private DeclareGuardService declareGuardService;
@Resource
private FileRecordService fileRecordService;
@Resource
private OrganizationService organizationService;
@@ -146,11 +151,21 @@ public class WshqtwFormController extends BaseController {
@OperationLog
@ApiOperation("添加五四红旗团委申报表")
@PostMapping()
@Transactional(rollbackFor = Exception.class)
public ApiResult<?> save(@RequestBody WshqtwForm wshqtwForm) {
User loginUser = getLoginUser();
if (loginUser != null) {
wshqtwForm.setUserId(loginUser.getUserId());
}
DeclareCheckResult declareCheck = declareGuardService.checkBeforeCreate("gxmu_wshqtw",
wshqtwForm.getDeclareId(), wshqtwForm.getYear(), wshqtwForm.getUserId());
if (declareCheck.isRejected()) {
return fail(declareCheck.getError());
}
if (declareCheck.getDeclareId() != null) {
wshqtwForm.setDeclareId(declareCheck.getDeclareId());
}
wshqtwForm.setDeclareStatus(1);
if (wshqtwFormService.save(wshqtwForm)) {
reviewFlowStarterService.startReview("gxmu_wshqtw", wshqtwForm.getId());
return success("添加成功");
@@ -225,4 +240,4 @@ public class WshqtwFormController extends BaseController {
private String valueOf(String value) {
return value == null ? "" : value.trim();
}
}
}
@@ -15,6 +15,9 @@ import com.gxwebsoft.common.system.service.OrganizationService;
import com.gxwebsoft.gxmu.entity.WshqtzbForm;
import com.gxwebsoft.gxmu.param.WshqtzbFormParam;
import com.gxwebsoft.gxmu.service.ReviewFlowStarterService;
import com.gxwebsoft.gxmu.model.DeclareCheckResult;
import com.gxwebsoft.gxmu.service.DeclareGuardService;
import org.springframework.transaction.annotation.Transactional;
import com.gxwebsoft.gxmu.service.WshqtzbFormService;
import com.gxwebsoft.gxmu.util.GxmuQueryHelper;
import com.gxwebsoft.gxmu.util.WshqtzbDocxExportUtil;
@@ -43,6 +46,8 @@ public class WshqtzbFormController extends BaseController {
@Resource
private ReviewFlowStarterService reviewFlowStarterService;
@Resource
private DeclareGuardService declareGuardService;
@Resource
private FileRecordService fileRecordService;
@Resource
private OrganizationService organizationService;
@@ -144,11 +149,21 @@ public class WshqtzbFormController extends BaseController {
@OperationLog
@ApiOperation("添加五四红旗团支部申报表")
@PostMapping()
@Transactional(rollbackFor = Exception.class)
public ApiResult<?> save(@RequestBody WshqtzbForm wshqtzbForm) {
User loginUser = getLoginUser();
if (loginUser != null) {
wshqtzbForm.setUserId(loginUser.getUserId());
}
DeclareCheckResult declareCheck = declareGuardService.checkBeforeCreate("gxmu_wshqtzb",
wshqtzbForm.getDeclareId(), wshqtzbForm.getYear(), wshqtzbForm.getUserId());
if (declareCheck.isRejected()) {
return fail(declareCheck.getError());
}
if (declareCheck.getDeclareId() != null) {
wshqtzbForm.setDeclareId(declareCheck.getDeclareId());
}
wshqtzbForm.setDeclareStatus(1);
if (wshqtzbFormService.save(wshqtzbForm)) {
reviewFlowStarterService.startReview("gxmu_wshqtzb", wshqtzbForm.getId());
return success("添加成功");
@@ -223,4 +238,4 @@ public class WshqtzbFormController extends BaseController {
private String valueOf(String value) {
return value == null ? "" : value.trim();
}
}
}
@@ -26,6 +26,9 @@ import com.gxwebsoft.gxmu.param.WxxzxMaterialUpdateParam;
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.DeclareGuardService;
import org.springframework.transaction.annotation.Transactional;
import com.gxwebsoft.gxmu.service.SjqnFormService;
import com.gxwebsoft.gxmu.service.SjtbzbsjFormService;
import com.gxwebsoft.gxmu.service.TzbcyFormService;
@@ -115,6 +118,8 @@ public class WxxzxFormController extends BaseController {
@Resource
private ReviewFlowStarterService reviewFlowStarterService;
@Resource
private DeclareGuardService declareGuardService;
@Resource
private FileRecordService fileRecordService;
@Value("${config.upload-path}")
@@ -243,11 +248,21 @@ public class WxxzxFormController extends BaseController {
@OperationLog
@ApiOperation("添加未来学术之星课题申报表")
@PostMapping()
@Transactional(rollbackFor = Exception.class)
public ApiResult<?> save(@RequestBody WxxzxForm wxxzxForm) {
User loginUser = getLoginUser();
if (loginUser != null) {
wxxzxForm.setUserId(loginUser.getUserId());
}
DeclareCheckResult declareCheck = declareGuardService.checkBeforeCreate("gxmu_wxxzx_project",
wxxzxForm.getDeclareId(), wxxzxForm.getYear(), wxxzxForm.getUserId());
if (declareCheck.isRejected()) {
return fail(declareCheck.getError());
}
if (declareCheck.getDeclareId() != null) {
wxxzxForm.setDeclareId(declareCheck.getDeclareId());
}
wxxzxForm.setDeclareStatus(1);
if (wxxzxFormService.save(wxxzxForm)) {
reviewFlowStarterService.startReview("gxmu_wxxzx_project", wxxzxForm.getId());
return success("添加成功");
@@ -889,4 +904,4 @@ public class WxxzxFormController extends BaseController {
return suggestion;
}
}
}
}
@@ -15,6 +15,9 @@ import com.gxwebsoft.common.system.service.OrganizationService;
import com.gxwebsoft.gxmu.entity.YxgqtdgbForm;
import com.gxwebsoft.gxmu.param.YxgqtdgbFormParam;
import com.gxwebsoft.gxmu.service.ReviewFlowStarterService;
import com.gxwebsoft.gxmu.model.DeclareCheckResult;
import com.gxwebsoft.gxmu.service.DeclareGuardService;
import org.springframework.transaction.annotation.Transactional;
import com.gxwebsoft.gxmu.service.YxgqtdgbFormService;
import com.gxwebsoft.gxmu.util.GxmuQueryHelper;
import com.gxwebsoft.gxmu.util.YxgqtdgbDocxExportUtil;
@@ -43,6 +46,8 @@ public class YxgqtdgbFormController extends BaseController {
@Resource
private ReviewFlowStarterService reviewFlowStarterService;
@Resource
private DeclareGuardService declareGuardService;
@Resource
private FileRecordService fileRecordService;
@Resource
private OrganizationService organizationService;
@@ -144,11 +149,21 @@ public class YxgqtdgbFormController extends BaseController {
@OperationLog
@ApiOperation("添加优秀共青团干部申报表")
@PostMapping()
@Transactional(rollbackFor = Exception.class)
public ApiResult<?> save(@RequestBody YxgqtdgbForm yxgqtdgbForm) {
User loginUser = getLoginUser();
if (loginUser != null) {
yxgqtdgbForm.setUserId(loginUser.getUserId());
}
DeclareCheckResult declareCheck = declareGuardService.checkBeforeCreate("gxmu_yxgqtdgb",
yxgqtdgbForm.getDeclareId(), yxgqtdgbForm.getYear(), yxgqtdgbForm.getUserId());
if (declareCheck.isRejected()) {
return fail(declareCheck.getError());
}
if (declareCheck.getDeclareId() != null) {
yxgqtdgbForm.setDeclareId(declareCheck.getDeclareId());
}
yxgqtdgbForm.setDeclareStatus(1);
if (yxgqtdgbFormService.save(yxgqtdgbForm)) {
reviewFlowStarterService.startReview("gxmu_yxgqtdgb", yxgqtdgbForm.getId());
return success("添加成功");
@@ -223,4 +238,4 @@ public class YxgqtdgbFormController extends BaseController {
private String valueOf(String value) {
return value == null ? "" : value.trim();
}
}
}
@@ -15,6 +15,9 @@ import com.gxwebsoft.common.system.service.OrganizationService;
import com.gxwebsoft.gxmu.entity.YxgqtyForm;
import com.gxwebsoft.gxmu.param.YxgqtyFormParam;
import com.gxwebsoft.gxmu.service.ReviewFlowStarterService;
import com.gxwebsoft.gxmu.model.DeclareCheckResult;
import com.gxwebsoft.gxmu.service.DeclareGuardService;
import org.springframework.transaction.annotation.Transactional;
import com.gxwebsoft.gxmu.service.YxgqtyFormService;
import com.gxwebsoft.gxmu.util.GxmuQueryHelper;
import com.gxwebsoft.gxmu.util.YxgqtyDocxExportUtil;
@@ -43,6 +46,8 @@ public class YxgqtyFormController extends BaseController {
@Resource
private ReviewFlowStarterService reviewFlowStarterService;
@Resource
private DeclareGuardService declareGuardService;
@Resource
private FileRecordService fileRecordService;
@Resource
private OrganizationService organizationService;
@@ -144,11 +149,21 @@ public class YxgqtyFormController extends BaseController {
@OperationLog
@ApiOperation("添加优秀共青团员申报表")
@PostMapping()
@Transactional(rollbackFor = Exception.class)
public ApiResult<?> save(@RequestBody YxgqtyForm yxgqtyForm) {
User loginUser = getLoginUser();
if (loginUser != null) {
yxgqtyForm.setUserId(loginUser.getUserId());
}
DeclareCheckResult declareCheck = declareGuardService.checkBeforeCreate("gxmu_yxgqty",
yxgqtyForm.getDeclareId(), yxgqtyForm.getYear(), yxgqtyForm.getUserId());
if (declareCheck.isRejected()) {
return fail(declareCheck.getError());
}
if (declareCheck.getDeclareId() != null) {
yxgqtyForm.setDeclareId(declareCheck.getDeclareId());
}
yxgqtyForm.setDeclareStatus(1);
if (yxgqtyFormService.save(yxgqtyForm)) {
reviewFlowStarterService.startReview("gxmu_yxgqty", yxgqtyForm.getId());
return success("添加成功");
@@ -223,4 +238,4 @@ public class YxgqtyFormController extends BaseController {
private String valueOf(String value) {
return value == null ? "" : value.trim();
}
}
}
@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableLogic;
import java.io.Serializable;
import io.swagger.annotations.ApiModel;
@@ -55,6 +56,12 @@ public class Declare implements Serializable {
private String endTime;
@ApiModelProperty(value = "是否允许重复提交:0否 1是")
private Integer allowRepeat;
@ApiModelProperty(value = "最多同时进行中的申报条数")
private Integer maxPending;
@ApiModelProperty(value = "用户ID")
private Integer userId;
@@ -71,4 +78,20 @@ public class Declare implements Serializable {
@TableLogic
private Integer deleted;
@ApiModelProperty(value = "当前用户在该项目下的最新申报状态:0草稿 1待审核 2通过 3驳回 4作废")
@TableField(exist = false)
private Integer myStatus;
@ApiModelProperty(value = "当前用户在该项目下的最新申报记录id")
@TableField(exist = false)
private Integer myRecordId;
@ApiModelProperty(value = "当前用户在该项目下的最新申报记录提交时间")
@TableField(exist = false)
private String mySubmitTime;
@ApiModelProperty(value = "当前用户在该项目下的最新申报记录状态备注")
@TableField(exist = false)
private String myStatusRemark;
}
@@ -24,6 +24,15 @@ import java.util.List;
public class QmgcForm implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("申报项目ID")
private Integer declareId;
@ApiModelProperty("申报记录状态:0草稿 1待审核 2通过 3驳回 4作废")
private Integer declareStatus;
@ApiModelProperty("状态备注(作废原因等)")
private String declareRemark;
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
@@ -24,6 +24,15 @@ import java.util.List;
public class SjqnForm implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("申报项目ID")
private Integer declareId;
@ApiModelProperty("申报记录状态:0草稿 1待审核 2通过 3驳回 4作废")
private Integer declareStatus;
@ApiModelProperty("状态备注(作废原因等)")
private String declareRemark;
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
@@ -24,6 +24,15 @@ import java.util.List;
public class SjtbzbsjForm implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("申报项目ID")
private Integer declareId;
@ApiModelProperty("申报记录状态:0草稿 1待审核 2通过 3驳回 4作废")
private Integer declareStatus;
@ApiModelProperty("状态备注(作废原因等)")
private String declareRemark;
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
@@ -25,6 +25,15 @@ import java.util.List;
public class TzbcyForm implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("申报项目ID")
private Integer declareId;
@ApiModelProperty("申报记录状态:0草稿 1待审核 2通过 3驳回 4作废")
private Integer declareStatus;
@ApiModelProperty("状态备注(作废原因等)")
private String declareRemark;
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
@@ -24,6 +24,15 @@ import java.util.List;
public class WshqtwForm implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("申报项目ID")
private Integer declareId;
@ApiModelProperty("申报记录状态:0草稿 1待审核 2通过 3驳回 4作废")
private Integer declareStatus;
@ApiModelProperty("状态备注(作废原因等)")
private String declareRemark;
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
@@ -20,6 +20,15 @@ import java.util.List;
public class WshqtzbForm implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("申报项目ID")
private Integer declareId;
@ApiModelProperty("申报记录状态:0草稿 1待审核 2通过 3驳回 4作废")
private Integer declareStatus;
@ApiModelProperty("状态备注(作废原因等)")
private String declareRemark;
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
@@ -24,6 +24,15 @@ import java.util.List;
public class WxxzxForm implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("申报项目ID")
private Integer declareId;
@ApiModelProperty("申报记录状态:0草稿 1待审核 2通过 3驳回 4作废")
private Integer declareStatus;
@ApiModelProperty("状态备注(作废原因等)")
private String declareRemark;
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
@@ -24,6 +24,15 @@ import java.util.List;
public class YxgqtdgbForm implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("申报项目ID")
private Integer declareId;
@ApiModelProperty("申报记录状态:0草稿 1待审核 2通过 3驳回 4作废")
private Integer declareStatus;
@ApiModelProperty("状态备注(作废原因等)")
private String declareRemark;
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
@@ -24,6 +24,15 @@ import java.util.List;
public class YxgqtyForm implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("申报项目ID")
private Integer declareId;
@ApiModelProperty("申报记录状态:0草稿 1待审核 2通过 3驳回 4作废")
private Integer declareStatus;
@ApiModelProperty("状态备注(作废原因等)")
private String declareRemark;
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
@@ -0,0 +1,65 @@
package com.gxwebsoft.gxmu.mapper;
import com.gxwebsoft.gxmu.entity.Declare;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import java.util.List;
import java.util.Map;
/**
* 申报记录跨模块查询(同一申报项目的重复申报限制)
*
* <p>table 只接受 {@link com.gxwebsoft.gxmu.service.DeclareGuardService} 白名单里的模块名,
* 调用方不得把用户输入直接传进来。</p>
*/
@Mapper
public interface DeclareRecordMapper {
/** 锁定申报项目行,串行化同一项目的并发提交 */
@Select("SELECT * FROM gxmu_declare WHERE id = #{id} AND deleted = 0 FOR UPDATE")
Declare lockDeclare(@Param("id") Integer id);
/** 某用户在某申报项目下、处于某状态的记录数 */
@Select("SELECT COUNT(*) FROM ${table} WHERE declare_id = #{declareId} AND user_id = #{userId} "
+ "AND declare_status = #{status} AND deleted = 0")
int countByStatus(@Param("table") String table,
@Param("declareId") Integer declareId,
@Param("userId") Integer userId,
@Param("status") Integer status);
/** 某用户在某申报项目下最近的一条记录 */
@Select("SELECT id AS id, declare_status AS declareStatus, create_time AS createTime, declare_remark AS declareRemark "
+ "FROM ${table} WHERE declare_id = #{declareId} AND user_id = #{userId} AND deleted = 0 "
+ "ORDER BY id DESC LIMIT 1")
Map<String, Object> selectLatestUserRecord(@Param("table") String table,
@Param("declareId") Integer declareId,
@Param("userId") Integer userId);
/** 某用户在某申报项目下的全部记录(申报项目列表回显本人状态用) */
@Select("SELECT id AS id, declare_id AS declareId, declare_status AS declareStatus, create_time AS createTime, "
+ "declare_remark AS declareRemark FROM ${table} WHERE user_id = #{userId} AND deleted = 0 ORDER BY id DESC")
List<Map<String, Object>> selectUserRecords(@Param("table") String table, @Param("userId") Integer userId);
/** 单条记录(撤回 / 作废时校验归属) */
@Select("SELECT id AS id, user_id AS userId, declare_id AS declareId, declare_status AS declareStatus "
+ "FROM ${table} WHERE id = #{id} AND deleted = 0")
Map<String, Object> selectRecord(@Param("table") String table, @Param("id") Integer id);
/** 同项目下其他有效申报的负责人手机号与团队成员,用于团队重复校验 */
@Select("SELECT id AS id, phone AS phone, team_members AS teamMembers FROM gxmu_tzbcy_form "
+ "WHERE declare_id = #{declareId} AND declare_status IN (1, 2, 3) AND deleted = 0 "
+ "AND id <> #{excludeId}")
List<Map<String, Object>> selectTzbcyTeamPhones(@Param("declareId") Integer declareId,
@Param("excludeId") Integer excludeId);
/** 更新申报记录状态(草稿 / 待审核 / 通过 / 驳回 / 作废) */
@Update("UPDATE ${table} SET declare_status = #{status}, declare_remark = #{remark}, update_time = NOW() "
+ "WHERE id = #{id} AND deleted = 0")
int updateStatus(@Param("table") String table,
@Param("id") Integer id,
@Param("status") Integer status,
@Param("remark") String remark);
}
@@ -0,0 +1,46 @@
package com.gxwebsoft.gxmu.model;
/**
* 申报提交前的校验结果。
*
* <p>三种形态:不适用(模块不在申报规则内)、被拒绝(带用户可读原因)、通过(带解析出的申报项目 id)。</p>
*/
public class DeclareCheckResult {
private final boolean applicable;
private final String error;
private final Integer declareId;
private DeclareCheckResult(boolean applicable, String error, Integer declareId) {
this.applicable = applicable;
this.error = error;
this.declareId = declareId;
}
public static DeclareCheckResult skip() {
return new DeclareCheckResult(false, null, null);
}
public static DeclareCheckResult reject(String error) {
return new DeclareCheckResult(true, error, null);
}
public static DeclareCheckResult pass(Integer declareId) {
return new DeclareCheckResult(true, null, declareId);
}
public boolean isApplicable() {
return applicable;
}
public boolean isRejected() {
return error != null;
}
public String getError() {
return error;
}
public Integer getDeclareId() {
return declareId;
}
}
@@ -0,0 +1,22 @@
package com.gxwebsoft.gxmu.param;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* 申报记录状态操作参数(撤回 / 作废)
*/
@Data
@ApiModel(value = "DeclareRecordActionParam", description = "申报记录状态操作参数")
public class DeclareRecordActionParam {
@ApiModelProperty("申报模块,同时也是表名")
private String module;
@ApiModelProperty("申报记录id")
private Integer id;
@ApiModelProperty("作废原因")
private String reason;
}
@@ -0,0 +1,52 @@
package com.gxwebsoft.gxmu.service;
import com.gxwebsoft.gxmu.model.DeclareCheckResult;
import java.util.List;
import java.util.Map;
/**
* 申报项目重复申报限制(见 docs/adr/0005-declare-uniqueness.md)。
*
* <p>规则:同一用户对同一申报项目,处于「待审核」的记录数不得超过项目配置的上限(默认 1);
* 「通过」后该项目对本人关闭;「驳回」后必须修改原记录重新提交。</p>
*/
public interface DeclareGuardService {
/** 该模块是否走申报项目判重(9 张申报表) */
boolean supports(String module);
/**
* 提交前校验。
*
* @param module 申报模块(同时也是表名)
* @param declareId 前端透传的申报项目 id,可为空
* @param year 申报年度,declareId 为空时按「模块 + 年度」唯一解析
* @param userId 提交人
* @return 不适用 / 拒绝(带原因)/ 通过(带解析出的 declareId)
*/
DeclareCheckResult checkBeforeCreate(String module, Integer declareId, Integer year, Integer userId);
/**
* 挑战杯团队成员重复校验:同一手机号不得出现在同一申报项目的两份申报里。
*
* @return 拒绝原因,null 表示通过
*/
String checkTeamPhones(String module, Integer declareId, Integer excludeRecordId,
String leaderPhone, List<String> memberPhones);
/** 按审核节点的最新状态回写申报记录状态 */
void syncReviewStatus(String module, Integer recordId);
/** 撤回:待审核 -> 草稿;任意一级审核通过后不再允许 */
String withdraw(String module, Integer recordId, Integer userId, boolean admin);
/** 作废:置为终态,释放名额,记录仍对申报人可见 */
String voidRecord(String module, Integer recordId, String reason);
/** 当前用户在各申报项目下的最新记录状态,key = declareId */
Map<Integer, Map<String, Object>> myDeclareStatus(String module, Integer userId);
/** 记录当前状态值,找不到返回 null */
Integer statusOf(String module, Integer recordId);
}
@@ -0,0 +1,340 @@
package com.gxwebsoft.gxmu.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.gxwebsoft.gxmu.entity.Declare;
import com.gxwebsoft.gxmu.entity.ReviewList;
import com.gxwebsoft.gxmu.mapper.DeclareRecordMapper;
import com.gxwebsoft.gxmu.model.DeclareCheckResult;
import com.gxwebsoft.gxmu.service.DeclareGuardService;
import com.gxwebsoft.gxmu.service.DeclareService;
import com.gxwebsoft.gxmu.service.ReviewListService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
/**
* 申报项目重复申报限制实现,见 docs/adr/0005-declare-uniqueness.md
*/
@Service
public class DeclareGuardServiceImpl implements DeclareGuardService {
/** 申报记录状态:0 草稿 / 1 待审核 / 2 通过 / 3 驳回 / 4 作废 */
public static final int STATUS_DRAFT = 0;
public static final int STATUS_PENDING = 1;
public static final int STATUS_PASSED = 2;
public static final int STATUS_REJECTED = 3;
public static final int STATUS_VOID = 4;
/** 走申报项目判重的 9 个模块,模块名同时是表名 */
private static final Set<String> MODULES = Collections.unmodifiableSet(new LinkedHashSet<>(Arrays.asList(
"gxmu_sjqn", "gxmu_sjtbzbsj", "gxmu_wshqtw", "gxmu_wshqtzb",
"gxmu_yxgqtdgb", "gxmu_yxgqty", "gxmu_tzbcy_form", "gxmu_qmgc_form",
"gxmu_wxxzx_project")));
private static final String TZBCY_MODULE = "gxmu_tzbcy_form";
private static final DateTimeFormatter DATE = DateTimeFormatter.ofPattern("yyyy-MM-dd");
private final ObjectMapper objectMapper = new ObjectMapper();
@Resource
private DeclareRecordMapper declareRecordMapper;
@Resource
private DeclareService declareService;
@Resource
private ReviewListService reviewListService;
@Override
public boolean supports(String module) {
return module != null && MODULES.contains(module);
}
@Override
@Transactional(rollbackFor = Exception.class)
public DeclareCheckResult checkBeforeCreate(String module, Integer declareId, Integer year, Integer userId) {
if (!supports(module) || userId == null) {
return DeclareCheckResult.skip();
}
Declare declare = null;
if (declareId != null) {
declare = declareRecordMapper.lockDeclare(declareId);
if (declare == null) {
return DeclareCheckResult.reject("申报项目不存在或已下架");
}
if (!module.equals(declare.getModule())) {
return DeclareCheckResult.reject("申报项目与当前申报表不匹配");
}
} else if (year != null) {
List<Declare> matched = declareService.list(new LambdaQueryWrapper<Declare>()
.eq(Declare::getModule, module)
.eq(Declare::getYear, year));
if (matched.size() > 1) {
return DeclareCheckResult.reject("存在多条同年度申报项目,请从申报项目列表进入");
}
if (matched.size() == 1) {
declare = declareRecordMapper.lockDeclare(matched.get(0).getId());
}
}
if (declare == null) {
// 该模块还没配置对应的申报项目:不套用重复申报限制
return DeclareCheckResult.skip();
}
Integer declareIdValue = declare.getId();
if (declareRecordMapper.countByStatus(module, declareIdValue, userId, STATUS_PASSED) > 0) {
return DeclareCheckResult.reject("该项目你已通过,不能重复申报");
}
if (declareRecordMapper.countByStatus(module, declareIdValue, userId, STATUS_REJECTED) > 0) {
return DeclareCheckResult.reject("该项目有一份被驳回的申报,请修改原申报后重新提交");
}
int maxPending = effectiveMaxPending(declare);
int pending = declareRecordMapper.countByStatus(module, declareIdValue, userId, STATUS_PENDING);
if (pending >= maxPending) {
Map<String, Object> latest =
declareRecordMapper.selectLatestUserRecord(module, declareIdValue, userId);
if (pending == 1) {
return DeclareCheckResult.reject("该项目你已申报过,当前状态:审核中(提交于 "
+ formatDate(latest == null ? null : latest.get("createTime")) + ")");
}
return DeclareCheckResult.reject("该项目已有 " + pending + " 条审核中的申报,最多允许 "
+ maxPending + " 条");
}
return DeclareCheckResult.pass(declareIdValue);
}
@Override
public String checkTeamPhones(String module, Integer declareId, Integer excludeRecordId,
String leaderPhone, List<String> memberPhones) {
if (!TZBCY_MODULE.equals(module) || declareId == null) {
return null;
}
Set<String> incoming = new LinkedHashSet<>();
addPhone(incoming, leaderPhone);
if (memberPhones != null) {
for (String phone : memberPhones) {
addPhone(incoming, phone);
}
}
if (incoming.isEmpty()) {
return null;
}
List<Map<String, Object>> rows =
declareRecordMapper.selectTzbcyTeamPhones(declareId, excludeRecordId == null ? 0 : excludeRecordId);
for (Map<String, Object> row : rows) {
Set<String> existing = new HashSet<>();
addPhone(existing, asString(row.get("phone")));
for (String phone : parseTeamPhones(asString(row.get("teamMembers")))) {
addPhone(existing, phone);
}
for (String phone : incoming) {
if (existing.contains(phone)) {
return "手机号 " + phone + " 已出现在该项目的另一份申报中,团队成员不能重复申报";
}
}
}
return null;
}
@Override
public void syncReviewStatus(String module, Integer recordId) {
if (!supports(module) || recordId == null) {
return;
}
List<ReviewList> nodes = reviewListService.listByModuleNPK(module, recordId);
if (nodes == null || nodes.isEmpty()) {
return;
}
boolean pending = nodes.stream()
.anyMatch(node -> node.getStatus() != null && node.getStatus() == 0);
if (pending) {
declareRecordMapper.updateStatus(module, recordId, STATUS_PENDING, null);
return;
}
ReviewList last = nodes.stream()
.max((left, right) -> {
int sortLeft = left.getSortNumber() == null ? 0 : left.getSortNumber();
int sortRight = right.getSortNumber() == null ? 0 : right.getSortNumber();
if (sortLeft != sortRight) {
return Integer.compare(sortLeft, sortRight);
}
int idLeft = left.getId() == null ? 0 : left.getId();
int idRight = right.getId() == null ? 0 : right.getId();
return Integer.compare(idLeft, idRight);
})
.orElse(null);
if (last == null || last.getStatus() == null) {
return;
}
declareRecordMapper.updateStatus(module, recordId,
last.getStatus() == 2 ? STATUS_REJECTED : STATUS_PASSED, null);
}
@Override
public String withdraw(String module, Integer recordId, Integer userId, boolean admin) {
if (!supports(module)) {
return "当前模块不支持撤回";
}
Map<String, Object> record = declareRecordMapper.selectRecord(module, recordId);
if (record == null) {
return "申报记录不存在";
}
if (!admin && !Objects.equals(userId, asInteger(record.get("userId")))) {
return "只能撤回自己的申报";
}
if (asInteger(record.get("declareStatus")) != STATUS_PENDING) {
return "只有待审核的申报可以撤回";
}
boolean approved = reviewListService.listByModuleNPK(module, recordId).stream()
.anyMatch(node -> node.getStatus() != null && node.getStatus() == 1);
if (approved) {
return "已有审核通过的记录不能撤回,请联系管理员处理";
}
removeReviewNodes(module, recordId);
declareRecordMapper.updateStatus(module, recordId, STATUS_DRAFT, null);
return null;
}
@Override
public String voidRecord(String module, Integer recordId, String reason) {
if (!supports(module)) {
return "当前模块不支持作废";
}
Map<String, Object> record = declareRecordMapper.selectRecord(module, recordId);
if (record == null) {
return "申报记录不存在";
}
if (asInteger(record.get("declareStatus")) == STATUS_VOID) {
return "该申报已作废";
}
removeReviewNodes(module, recordId);
declareRecordMapper.updateStatus(module, recordId, STATUS_VOID, reason);
return null;
}
@Override
public Map<Integer, Map<String, Object>> myDeclareStatus(String module, Integer userId) {
Map<Integer, Map<String, Object>> result = new LinkedHashMap<>();
if (!supports(module) || userId == null) {
return result;
}
for (Map<String, Object> row : declareRecordMapper.selectUserRecords(module, userId)) {
Integer declareId = asInteger(row.get("declareId"));
if (declareId == null || result.containsKey(declareId)) {
continue;
}
result.put(declareId, row);
}
return result;
}
@Override
public Integer statusOf(String module, Integer recordId) {
if (!supports(module) || recordId == null) {
return null;
}
Map<String, Object> record = declareRecordMapper.selectRecord(module, recordId);
return record == null ? null : asInteger(record.get("declareStatus"));
}
private void removeReviewNodes(String module, Integer recordId) {
reviewListService.remove(new LambdaQueryWrapper<ReviewList>()
.eq(ReviewList::getModule, module)
.eq(ReviewList::getPk, recordId));
}
private int effectiveMaxPending(Declare declare) {
if (declare.getAllowRepeat() != null && declare.getAllowRepeat() == 1) {
Integer configured = declare.getMaxPending();
if (configured != null && configured > 0) {
return configured;
}
}
return 1;
}
private List<String> parseTeamPhones(String json) {
if (json == null || json.trim().isEmpty()) {
return Collections.emptyList();
}
try {
List<Map<String, Object>> members = objectMapper.readValue(json,
objectMapper.getTypeFactory().constructCollectionType(List.class, Map.class));
List<String> phones = new ArrayList<>();
for (Map<String, Object> member : members) {
Object phone = member == null ? null : member.get("phone");
if (phone != null) {
phones.add(String.valueOf(phone));
}
}
return phones;
} catch (Exception ignored) {
return Collections.emptyList();
}
}
private void addPhone(Set<String> target, String raw) {
String normalized = normalizePhone(raw);
if (normalized != null) {
target.add(normalized);
}
}
private String normalizePhone(String raw) {
if (raw == null) {
return null;
}
String digits = raw.replaceAll("[^0-9]", "");
return digits.length() >= 7 ? digits : null;
}
private String asString(Object value) {
return value == null ? null : String.valueOf(value);
}
private Integer asInteger(Object value) {
if (value == null) {
return null;
}
if (value instanceof Number) {
return ((Number) value).intValue();
}
try {
return Integer.valueOf(String.valueOf(value));
} catch (NumberFormatException ignored) {
return null;
}
}
private String formatDate(Object value) {
if (value == null) {
return "-";
}
if (value instanceof LocalDateTime) {
return ((LocalDateTime) value).format(DATE);
}
if (value instanceof java.sql.Timestamp) {
return ((java.sql.Timestamp) value).toLocalDateTime().format(DATE);
}
if (value instanceof java.sql.Date) {
return ((java.sql.Date) value).toLocalDate().format(DATE);
}
if (value instanceof LocalDate) {
return ((LocalDate) value).format(DATE);
}
String text = String.valueOf(value);
return text.length() >= 10 ? text.substring(0, 10) : text;
}
}