From 0e4986330af6fd63ce2fffc1f241b186a178e2fc Mon Sep 17 00:00:00 2001 From: weicw <594098497@qq.com> Date: Mon, 2 Nov 2026 19:28:56 +0800 Subject: [PATCH] =?UTF-8?q?feat(declare):=20=E5=90=8C=E4=B8=80=E7=94=B3?= =?UTF-8?q?=E6=8A=A5=E9=A1=B9=E7=9B=AE=E8=BF=9B=E8=A1=8C=E4=B8=AD=E5=8F=AA?= =?UTF-8?q?=E5=85=81=E8=AE=B8=E4=B8=80=E6=9D=A1=E7=94=B3=E6=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 DeclareGuardService:按已通过/未处理驳回/待审核数判重,申报项目行加锁防连点 - 9 张申报表补 declare_id / declare_status / declare_remark,提交时写入并随审核结果回写 - 新增撤回、作废(强制放行)接口;挑战杯团队成员按手机号判重 - 申报项目支持配置允许重复提交与最多同时条数 - 迁移脚本 sql/20261102_add_declare_uniqueness.sql(含存量回填与重复清单) --- sql/20261102_add_declare_uniqueness.sql | 117 ++++++ .../gxmu/controller/DeclareController.java | 93 ++++- .../controller/DeclareRecordController.java | 66 ++++ .../gxmu/controller/QmgcFormController.java | 17 +- .../gxmu/controller/ReviewListController.java | 20 +- .../gxmu/controller/SjqnFormController.java | 17 +- .../controller/SjtbzbsjFormController.java | 17 +- .../gxmu/controller/TzbcyFormController.java | 43 ++- .../gxmu/controller/WshqtwFormController.java | 17 +- .../controller/WshqtzbFormController.java | 17 +- .../gxmu/controller/WxxzxFormController.java | 17 +- .../controller/YxgqtdgbFormController.java | 17 +- .../gxmu/controller/YxgqtyFormController.java | 17 +- .../com/gxwebsoft/gxmu/entity/Declare.java | 23 ++ .../com/gxwebsoft/gxmu/entity/QmgcForm.java | 9 + .../com/gxwebsoft/gxmu/entity/SjqnForm.java | 9 + .../gxwebsoft/gxmu/entity/SjtbzbsjForm.java | 9 + .../com/gxwebsoft/gxmu/entity/TzbcyForm.java | 9 + .../com/gxwebsoft/gxmu/entity/WshqtwForm.java | 9 + .../gxwebsoft/gxmu/entity/WshqtzbForm.java | 9 + .../com/gxwebsoft/gxmu/entity/WxxzxForm.java | 9 + .../gxwebsoft/gxmu/entity/YxgqtdgbForm.java | 9 + .../com/gxwebsoft/gxmu/entity/YxgqtyForm.java | 9 + .../gxmu/mapper/DeclareRecordMapper.java | 65 ++++ .../gxmu/model/DeclareCheckResult.java | 46 +++ .../gxmu/param/DeclareRecordActionParam.java | 22 ++ .../gxmu/service/DeclareGuardService.java | 52 +++ .../service/impl/DeclareGuardServiceImpl.java | 340 ++++++++++++++++++ 28 files changed, 1089 insertions(+), 15 deletions(-) create mode 100644 sql/20261102_add_declare_uniqueness.sql create mode 100644 src/main/java/com/gxwebsoft/gxmu/controller/DeclareRecordController.java create mode 100644 src/main/java/com/gxwebsoft/gxmu/mapper/DeclareRecordMapper.java create mode 100644 src/main/java/com/gxwebsoft/gxmu/model/DeclareCheckResult.java create mode 100644 src/main/java/com/gxwebsoft/gxmu/param/DeclareRecordActionParam.java create mode 100644 src/main/java/com/gxwebsoft/gxmu/service/DeclareGuardService.java create mode 100644 src/main/java/com/gxwebsoft/gxmu/service/impl/DeclareGuardServiceImpl.java diff --git a/sql/20261102_add_declare_uniqueness.sql b/sql/20261102_add_declare_uniqueness.sql new file mode 100644 index 0000000..d4c289b --- /dev/null +++ b/sql/20261102_add_declare_uniqueness.sql @@ -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; diff --git a/src/main/java/com/gxwebsoft/gxmu/controller/DeclareController.java b/src/main/java/com/gxwebsoft/gxmu/controller/DeclareController.java index de9bfae..4038df9 100644 --- a/src/main/java/com/gxwebsoft/gxmu/controller/DeclareController.java +++ b/src/main/java/com/gxwebsoft/gxmu/controller/DeclareController.java @@ -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> page(DeclareParam param) { // 使用关联查询 - return success(declareService.pageRel(param)); + PageResult result = declareService.pageRel(param); + fillUserDeclareStatus(result.getList()); + return success(result); } @ApiOperation("分页查询当前用户申报管理") @@ -49,21 +56,31 @@ public class DeclareController extends BaseController { public ApiResult> userPage(DeclareParam param) { Integer loginUserId = getLoginUserId(); param.setUserId(loginUserId == null ? null : loginUserId.longValue()); - return success(declareService.pageRel(param)); + PageResult result = declareService.pageRel(param); + fillUserDeclareStatus(result.getList()); + return success(result); } @ApiOperation("查询全部申报管理") @GetMapping() public ApiResult> list(DeclareParam param) { // 使用关联查询 - return success(declareService.listRel(param)); + List list = declareService.listRel(param); + fillUserDeclareStatus(list); + return success(list); } @ApiOperation("根据id查询申报管理") @GetMapping("/{id}") public ApiResult get(@PathVariable("id") Integer id) { // 使用关联查询 - return success(declareService.getByIdRel(id)); + Declare declare = declareService.getByIdRel(id); + if (declare != null) { + List 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 list) { + Integer userId = getLoginUserId(); + if (userId == null || list == null || list.isEmpty()) { + return; + } + Map>> cache = new HashMap<>(); + for (Declare declare : list) { + String module = declare.getModule(); + if (declare.getId() == null || module == null || !declareGuardService.supports(module)) { + continue; + } + Map> mine = cache.get(module); + if (mine == null) { + mine = declareGuardService.myDeclareStatus(module, userId); + cache.put(module, mine); + } + Map 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; + } + +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/gxmu/controller/DeclareRecordController.java b/src/main/java/com/gxwebsoft/gxmu/controller/DeclareRecordController.java new file mode 100644 index 0000000..0abd4c5 --- /dev/null +++ b/src/main/java/com/gxwebsoft/gxmu/controller/DeclareRecordController.java @@ -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; + +/** + * 申报记录状态操作:撤回、作废(强制放行)。 + * + *

规则见 docs/adr/0005-declare-uniqueness.md。

+ */ +@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 empty = null; + return success(empty); + } + Map> all = declareGuardService.myDeclareStatus(module, userId); + return success(all.get(declareId)); + } +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/gxmu/controller/QmgcFormController.java b/src/main/java/com/gxwebsoft/gxmu/controller/QmgcFormController.java index abf4291..82c06ef 100644 --- a/src/main/java/com/gxwebsoft/gxmu/controller/QmgcFormController.java +++ b/src/main/java/com/gxwebsoft/gxmu/controller/QmgcFormController.java @@ -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()); } -} +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/gxmu/controller/ReviewListController.java b/src/main/java/com/gxwebsoft/gxmu/controller/ReviewListController.java index 5817d1f..4d12d7f 100644 --- a/src/main/java/com/gxwebsoft/gxmu/controller/ReviewListController.java +++ b/src/main/java/com/gxwebsoft/gxmu/controller/ReviewListController.java @@ -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 batchParam) { if (batchParam.update(reviewListService, "id")) { + syncBatchReviewStatus(batchParam); return success("修改成功"); } return fail("修改失败"); } + /** 批量审核后同样要回写申报记录状态,否则重复申报限制会读到旧状态 */ + private void syncBatchReviewStatus(BatchParam 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 ids) { @@ -196,4 +214,4 @@ public class ReviewListController extends BaseController { } } -} +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/gxmu/controller/SjqnFormController.java b/src/main/java/com/gxwebsoft/gxmu/controller/SjqnFormController.java index 665b8c2..38d9bf9 100644 --- a/src/main/java/com/gxwebsoft/gxmu/controller/SjqnFormController.java +++ b/src/main/java/com/gxwebsoft/gxmu/controller/SjqnFormController.java @@ -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(); } -} +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/gxmu/controller/SjtbzbsjFormController.java b/src/main/java/com/gxwebsoft/gxmu/controller/SjtbzbsjFormController.java index 3693d33..25b002b 100644 --- a/src/main/java/com/gxwebsoft/gxmu/controller/SjtbzbsjFormController.java +++ b/src/main/java/com/gxwebsoft/gxmu/controller/SjtbzbsjFormController.java @@ -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(); } -} +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/gxmu/controller/TzbcyFormController.java b/src/main/java/com/gxwebsoft/gxmu/controller/TzbcyFormController.java index 7523340..b935226 100644 --- a/src/main/java/com/gxwebsoft/gxmu/controller/TzbcyFormController.java +++ b/src/main/java/com/gxwebsoft/gxmu/controller/TzbcyFormController.java @@ -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 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); + } + +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/gxmu/controller/WshqtwFormController.java b/src/main/java/com/gxwebsoft/gxmu/controller/WshqtwFormController.java index fbbf769..508885d 100644 --- a/src/main/java/com/gxwebsoft/gxmu/controller/WshqtwFormController.java +++ b/src/main/java/com/gxwebsoft/gxmu/controller/WshqtwFormController.java @@ -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(); } -} +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/gxmu/controller/WshqtzbFormController.java b/src/main/java/com/gxwebsoft/gxmu/controller/WshqtzbFormController.java index 2685846..2e3289b 100644 --- a/src/main/java/com/gxwebsoft/gxmu/controller/WshqtzbFormController.java +++ b/src/main/java/com/gxwebsoft/gxmu/controller/WshqtzbFormController.java @@ -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(); } -} +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/gxmu/controller/WxxzxFormController.java b/src/main/java/com/gxwebsoft/gxmu/controller/WxxzxFormController.java index 3c1fa2b..46c7812 100644 --- a/src/main/java/com/gxwebsoft/gxmu/controller/WxxzxFormController.java +++ b/src/main/java/com/gxwebsoft/gxmu/controller/WxxzxFormController.java @@ -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; } } -} +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/gxmu/controller/YxgqtdgbFormController.java b/src/main/java/com/gxwebsoft/gxmu/controller/YxgqtdgbFormController.java index e1740fd..01bd16f 100644 --- a/src/main/java/com/gxwebsoft/gxmu/controller/YxgqtdgbFormController.java +++ b/src/main/java/com/gxwebsoft/gxmu/controller/YxgqtdgbFormController.java @@ -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(); } -} +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/gxmu/controller/YxgqtyFormController.java b/src/main/java/com/gxwebsoft/gxmu/controller/YxgqtyFormController.java index 4462974..38d1a0c 100644 --- a/src/main/java/com/gxwebsoft/gxmu/controller/YxgqtyFormController.java +++ b/src/main/java/com/gxwebsoft/gxmu/controller/YxgqtyFormController.java @@ -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(); } -} +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/gxmu/entity/Declare.java b/src/main/java/com/gxwebsoft/gxmu/entity/Declare.java index b866b65..af73310 100644 --- a/src/main/java/com/gxwebsoft/gxmu/entity/Declare.java +++ b/src/main/java/com/gxwebsoft/gxmu/entity/Declare.java @@ -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; + } diff --git a/src/main/java/com/gxwebsoft/gxmu/entity/QmgcForm.java b/src/main/java/com/gxwebsoft/gxmu/entity/QmgcForm.java index 199ffbb..ff60c4a 100644 --- a/src/main/java/com/gxwebsoft/gxmu/entity/QmgcForm.java +++ b/src/main/java/com/gxwebsoft/gxmu/entity/QmgcForm.java @@ -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; diff --git a/src/main/java/com/gxwebsoft/gxmu/entity/SjqnForm.java b/src/main/java/com/gxwebsoft/gxmu/entity/SjqnForm.java index 7ab46af..a9d3a0d 100644 --- a/src/main/java/com/gxwebsoft/gxmu/entity/SjqnForm.java +++ b/src/main/java/com/gxwebsoft/gxmu/entity/SjqnForm.java @@ -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; diff --git a/src/main/java/com/gxwebsoft/gxmu/entity/SjtbzbsjForm.java b/src/main/java/com/gxwebsoft/gxmu/entity/SjtbzbsjForm.java index 349faa4..b5d6500 100644 --- a/src/main/java/com/gxwebsoft/gxmu/entity/SjtbzbsjForm.java +++ b/src/main/java/com/gxwebsoft/gxmu/entity/SjtbzbsjForm.java @@ -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; diff --git a/src/main/java/com/gxwebsoft/gxmu/entity/TzbcyForm.java b/src/main/java/com/gxwebsoft/gxmu/entity/TzbcyForm.java index d465146..8ac524b 100644 --- a/src/main/java/com/gxwebsoft/gxmu/entity/TzbcyForm.java +++ b/src/main/java/com/gxwebsoft/gxmu/entity/TzbcyForm.java @@ -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; diff --git a/src/main/java/com/gxwebsoft/gxmu/entity/WshqtwForm.java b/src/main/java/com/gxwebsoft/gxmu/entity/WshqtwForm.java index 0317232..d4f95d5 100644 --- a/src/main/java/com/gxwebsoft/gxmu/entity/WshqtwForm.java +++ b/src/main/java/com/gxwebsoft/gxmu/entity/WshqtwForm.java @@ -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; diff --git a/src/main/java/com/gxwebsoft/gxmu/entity/WshqtzbForm.java b/src/main/java/com/gxwebsoft/gxmu/entity/WshqtzbForm.java index 9982065..1ad6513 100644 --- a/src/main/java/com/gxwebsoft/gxmu/entity/WshqtzbForm.java +++ b/src/main/java/com/gxwebsoft/gxmu/entity/WshqtzbForm.java @@ -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; diff --git a/src/main/java/com/gxwebsoft/gxmu/entity/WxxzxForm.java b/src/main/java/com/gxwebsoft/gxmu/entity/WxxzxForm.java index 0316d07..3031b0c 100644 --- a/src/main/java/com/gxwebsoft/gxmu/entity/WxxzxForm.java +++ b/src/main/java/com/gxwebsoft/gxmu/entity/WxxzxForm.java @@ -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; diff --git a/src/main/java/com/gxwebsoft/gxmu/entity/YxgqtdgbForm.java b/src/main/java/com/gxwebsoft/gxmu/entity/YxgqtdgbForm.java index 031622b..d465868 100644 --- a/src/main/java/com/gxwebsoft/gxmu/entity/YxgqtdgbForm.java +++ b/src/main/java/com/gxwebsoft/gxmu/entity/YxgqtdgbForm.java @@ -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; diff --git a/src/main/java/com/gxwebsoft/gxmu/entity/YxgqtyForm.java b/src/main/java/com/gxwebsoft/gxmu/entity/YxgqtyForm.java index efe395b..beb452a 100644 --- a/src/main/java/com/gxwebsoft/gxmu/entity/YxgqtyForm.java +++ b/src/main/java/com/gxwebsoft/gxmu/entity/YxgqtyForm.java @@ -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; diff --git a/src/main/java/com/gxwebsoft/gxmu/mapper/DeclareRecordMapper.java b/src/main/java/com/gxwebsoft/gxmu/mapper/DeclareRecordMapper.java new file mode 100644 index 0000000..13aca9b --- /dev/null +++ b/src/main/java/com/gxwebsoft/gxmu/mapper/DeclareRecordMapper.java @@ -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; + +/** + * 申报记录跨模块查询(同一申报项目的重复申报限制) + * + *

table 只接受 {@link com.gxwebsoft.gxmu.service.DeclareGuardService} 白名单里的模块名, + * 调用方不得把用户输入直接传进来。

+ */ +@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 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> 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 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> 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); +} diff --git a/src/main/java/com/gxwebsoft/gxmu/model/DeclareCheckResult.java b/src/main/java/com/gxwebsoft/gxmu/model/DeclareCheckResult.java new file mode 100644 index 0000000..adca681 --- /dev/null +++ b/src/main/java/com/gxwebsoft/gxmu/model/DeclareCheckResult.java @@ -0,0 +1,46 @@ +package com.gxwebsoft.gxmu.model; + +/** + * 申报提交前的校验结果。 + * + *

三种形态:不适用(模块不在申报规则内)、被拒绝(带用户可读原因)、通过(带解析出的申报项目 id)。

+ */ +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; + } +} diff --git a/src/main/java/com/gxwebsoft/gxmu/param/DeclareRecordActionParam.java b/src/main/java/com/gxwebsoft/gxmu/param/DeclareRecordActionParam.java new file mode 100644 index 0000000..55cb585 --- /dev/null +++ b/src/main/java/com/gxwebsoft/gxmu/param/DeclareRecordActionParam.java @@ -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; +} diff --git a/src/main/java/com/gxwebsoft/gxmu/service/DeclareGuardService.java b/src/main/java/com/gxwebsoft/gxmu/service/DeclareGuardService.java new file mode 100644 index 0000000..6b7d607 --- /dev/null +++ b/src/main/java/com/gxwebsoft/gxmu/service/DeclareGuardService.java @@ -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)。 + * + *

规则:同一用户对同一申报项目,处于「待审核」的记录数不得超过项目配置的上限(默认 1); + * 「通过」后该项目对本人关闭;「驳回」后必须修改原记录重新提交。

+ */ +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 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> myDeclareStatus(String module, Integer userId); + + /** 记录当前状态值,找不到返回 null */ + Integer statusOf(String module, Integer recordId); +} diff --git a/src/main/java/com/gxwebsoft/gxmu/service/impl/DeclareGuardServiceImpl.java b/src/main/java/com/gxwebsoft/gxmu/service/impl/DeclareGuardServiceImpl.java new file mode 100644 index 0000000..25985d5 --- /dev/null +++ b/src/main/java/com/gxwebsoft/gxmu/service/impl/DeclareGuardServiceImpl.java @@ -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 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 matched = declareService.list(new LambdaQueryWrapper() + .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 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 memberPhones) { + if (!TZBCY_MODULE.equals(module) || declareId == null) { + return null; + } + Set incoming = new LinkedHashSet<>(); + addPhone(incoming, leaderPhone); + if (memberPhones != null) { + for (String phone : memberPhones) { + addPhone(incoming, phone); + } + } + if (incoming.isEmpty()) { + return null; + } + List> rows = + declareRecordMapper.selectTzbcyTeamPhones(declareId, excludeRecordId == null ? 0 : excludeRecordId); + for (Map row : rows) { + Set 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 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 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 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> myDeclareStatus(String module, Integer userId) { + Map> result = new LinkedHashMap<>(); + if (!supports(module) || userId == null) { + return result; + } + for (Map 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 record = declareRecordMapper.selectRecord(module, recordId); + return record == null ? null : asInteger(record.get("declareStatus")); + } + + private void removeReviewNodes(String module, Integer recordId) { + reviewListService.remove(new LambdaQueryWrapper() + .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 parseTeamPhones(String json) { + if (json == null || json.trim().isEmpty()) { + return Collections.emptyList(); + } + try { + List> members = objectMapper.readValue(json, + objectMapper.getTypeFactory().constructCollectionType(List.class, Map.class)); + List phones = new ArrayList<>(); + for (Map 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 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; + } +}