yxk-20260730

改善提案
普通新增页面不再预生成流水号。提案编号在点击保存时由后端生成,前端仅展示“保存后自动生成”,避免并发重复。
Implementation Changes
新增页面初始化时不再调用年度流水号接口,也不再拼接提案编号。
提案编号输入框继续只读;新增状态显示“保存后自动生成”占位提示。
后端新增专用创建方法,在保存事务内:原子递增年度序列表。
按 提案部门名称-YYYY年-四位流水号 生成编号。
覆盖前端传入的 proposalNo 后保存主表。

新增接口返回 { id, proposalNo };前端附件仍使用返回的 id 关联。
新增年度序列表及历史编号初始化 SQL,保证首次上线从当前最大流水号继续递增。
This commit is contained in:
ye1023
2026-07-30 15:51:15 +08:00
parent abff0b286e
commit 915581eba1
5 changed files with 64 additions and 4 deletions
@@ -105,11 +105,12 @@ public class ImprovementProposalController {
ImprovementProposal improvementProposal = new ImprovementProposal();
BeanUtils.copyProperties(improvementProposalPage, improvementProposal);
improvementProposal.setBpmStatus("1");
// 新增申请阶段只创建主表,评分和投票子表由后续流程节点维护
improvementProposalService.saveMain(improvementProposal, null, null);
// 附件需要使用主表 ID 关联,不能让前端从成功提示文本中猜测新增记录的 ID
Map<String, String> result = new HashMap<>(1);
// 新增申请在保存事务内分配年度流水号,避免前端预取号产生并发重复
improvementProposalService.createNewProposal(improvementProposal);
// 附件必须使用已落库的主表 ID 关联,不能使用响应成功文案
Map<String, String> result = new HashMap<>(2);
result.put("id", improvementProposal.getId());
result.put("proposalNo", improvementProposal.getProposalNo());
return Result.OK("添加成功!", result);
}
@@ -16,6 +16,13 @@ import java.util.List;
public interface ImprovementProposalMapper extends BaseMapper<ImprovementProposal> {
String selectLatestProposalNoByYear(@Param("year") Integer year);
/** 原子递增指定年度的提案流水号,首次使用该年度时从 1 开始。 */
int incrementProposalYearSequence(@Param("year") Integer year);
/** 在当前事务内锁定并读取指定年度的最新提案流水号。 */
Integer selectProposalYearSequenceForUpdate(@Param("year") Integer year);
/**
* 锁定提案主表,串行化流程发起、编辑和删除操作,避免同一提案生成多个流程实例。
*/
@@ -10,6 +10,20 @@
LIMIT 1
</select>
<!-- INSERT ... ON DUPLICATE KEY UPDATE 以年度主键为粒度串行递增流水号。 -->
<update id="incrementProposalYearSequence">
INSERT INTO improvement_proposal_year_sequence (sequence_year, current_sequence)
VALUES (#{year}, 1)
ON DUPLICATE KEY UPDATE current_sequence = current_sequence + 1
</update>
<select id="selectProposalYearSequenceForUpdate" resultType="java.lang.Integer">
SELECT current_sequence
FROM improvement_proposal_year_sequence
WHERE sequence_year = #{year}
FOR UPDATE
</select>
<!-- 角色名单由服务端查询,前端只能从该受控集合中选择。 -->
<select id="selectInstituteVoteCandidates" resultType="org.jeecg.modules.improvementproposal.dto.ImprovementInstituteVoteCandidateDTO">
SELECT DISTINCT u.id, u.username, u.realname
@@ -39,6 +39,13 @@ public interface IImprovementProposalService extends IService<ImprovementProposa
*/
public void saveMain(ImprovementProposal improvementProposal,List<ImprovementDeptScore> improvementDeptScoreList,List<ImprovementInstituteVote> improvementInstituteVoteList) ;
/**
* 创建新的改善提案,并在保存事务内生成不可重复的年度提案编号。
*
* @param improvementProposal 提案主表数据
*/
void createNewProposal(ImprovementProposal improvementProposal);
/**
* 修改一对多
*
@@ -25,6 +25,7 @@ import java.math.RoundingMode;
import java.util.HashMap;
import java.util.List;
import java.util.Collection;
import java.util.Calendar;
import java.util.Date;
import java.util.Map;
import org.flowable.engine.RuntimeService;
@@ -64,6 +65,7 @@ public class ImprovementProposalServiceImpl extends ServiceImpl<ImprovementPropo
private static final String AWARD_SILVER = "1";
private static final String AWARD_BRONZE = "2";
private static final String AWARD_PARTICIPATION = "3";
private static final int PROPOSAL_NO_SEQUENCE_LENGTH = 4;
private static final String INSTITUTE_VOTE_RESULT_PASSED = "通过";
private static final String INSTITUTE_VOTE_RESULT_FAILED = "不通过";
@@ -122,6 +124,35 @@ public class ImprovementProposalServiceImpl extends ServiceImpl<ImprovementPropo
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void createNewProposal(ImprovementProposal improvementProposal) {
if (oConvertUtils.isEmpty(improvementProposal.getProposalDeptName())) {
throw new IllegalStateException("提案部门不能为空,无法生成提案编号");
}
int proposalYear = getProposalYear(improvementProposal.getProposalDate());
// 年度控制表通过主键锁串行递增,整个创建事务回滚时流水号也会随之回滚。
improvementProposalMapper.incrementProposalYearSequence(proposalYear);
Integer sequence = improvementProposalMapper.selectProposalYearSequenceForUpdate(proposalYear);
if (sequence == null) {
throw new IllegalStateException("生成提案编号失败,请稍后重试");
}
improvementProposal.setProposalNo(formatProposalNo(improvementProposal.getProposalDeptName(), proposalYear, sequence));
saveMain(improvementProposal, null, null);
}
private int getProposalYear(Date proposalDate) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(proposalDate == null ? new Date() : proposalDate);
return calendar.get(Calendar.YEAR);
}
private String formatProposalNo(String proposalDeptName, int proposalYear, int sequence) {
return String.format("%s-%d年-%0" + PROPOSAL_NO_SEQUENCE_LENGTH + "d", proposalDeptName.trim(), proposalYear, sequence);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void saveMain(ImprovementProposal improvementProposal, List<ImprovementDeptScore> improvementDeptScoreList,List<ImprovementInstituteVote> improvementInstituteVoteList) {