yxk-20260724

feat(改善提案): 支持直接发起审批流程及通用流程办理

- 新增改善提案专用流程发起接口,直接以 improvement_proposal.id 作为业务ID发起 dev_improvement_proposal_001,不再创建 task_task
- 发起时加行锁并校验草稿状态及流程实例,防止重复发起;成功后回写 process_instance_id
- 收紧已发起提案的编辑、单删和批量删除限制,避免流程中的业务数据被篡改
- 前端列表接入业务发起接口,仅草稿且未绑定流程实例时显示发起、编辑、删除入口
- 新增 ImprovementProposalBPMForm,只读展示提案信息并复用通用任务办理能力
- 兼容发起接口从 POST 请求体或 URL 参数中接收 id,修复前端参数未绑定导致的缺少 id 异常
- 明确流程变量使用业务表物理列名:proposal_dept_id,用于按提案部门匹配精益专员初审人员
This commit is contained in:
ye1023
2026-07-24 16:45:26 +08:00
parent beabbc3519
commit 91c85b6a6c
4 changed files with 115 additions and 13 deletions
@@ -118,14 +118,36 @@ public class ImprovementProposalController {
@RequiresPermissions("improvementproposal:improvement_proposal:edit") @RequiresPermissions("improvementproposal:improvement_proposal:edit")
@RequestMapping(value = "/edit", method = {RequestMethod.PUT,RequestMethod.POST}) @RequestMapping(value = "/edit", method = {RequestMethod.PUT,RequestMethod.POST})
public Result<String> edit(@RequestBody ImprovementProposalPage improvementProposalPage) { public Result<String> edit(@RequestBody ImprovementProposalPage improvementProposalPage) {
try {
ImprovementProposal improvementProposal = new ImprovementProposal(); ImprovementProposal improvementProposal = new ImprovementProposal();
BeanUtils.copyProperties(improvementProposalPage, improvementProposal); BeanUtils.copyProperties(improvementProposalPage, improvementProposal);
ImprovementProposal improvementProposalEntity = improvementProposalService.getById(improvementProposal.getId());
if(improvementProposalEntity==null) {
return Result.error("未找到对应数据");
}
improvementProposalService.updateMain(improvementProposal, improvementProposalPage.getImprovementDeptScoreList(),improvementProposalPage.getImprovementInstituteVoteList()); improvementProposalService.updateMain(improvementProposal, improvementProposalPage.getImprovementDeptScoreList(),improvementProposalPage.getImprovementInstituteVoteList());
return Result.OK("编辑成功!"); return Result.OK("编辑成功!");
} catch (IllegalStateException e) {
return Result.error(e.getMessage());
}
}
/**
* 使用改善提案主表作为流程业务数据发起审批,不复用督办任务表。
*/
@PostMapping(value = "/startProcess")
public Result<String> startProcess(@RequestBody(required = false) Map<String, String> requestBody,
@RequestParam(name = "id", required = false) String id) {
// defHttp 的 POST 参数默认在请求体中;同时兼容外部调用将 id 放在 URL 查询串的场景。
if (oConvertUtils.isEmpty(id) && requestBody != null) {
id = requestBody.get("id");
}
if (oConvertUtils.isEmpty(id)) {
return Result.error("提案ID不能为空");
}
LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
try {
String processInstanceId = improvementProposalService.startProcess(id, sysUser.getUsername());
return Result.OK("流程发起成功!", processInstanceId);
} catch (IllegalStateException e) {
return Result.error(e.getMessage());
}
} }
/** /**
@@ -139,8 +161,12 @@ public class ImprovementProposalController {
@RequiresPermissions("improvementproposal:improvement_proposal:delete") @RequiresPermissions("improvementproposal:improvement_proposal:delete")
@DeleteMapping(value = "/delete") @DeleteMapping(value = "/delete")
public Result<String> delete(@RequestParam(name="id",required=true) String id) { public Result<String> delete(@RequestParam(name="id",required=true) String id) {
try {
improvementProposalService.delMain(id); improvementProposalService.delMain(id);
return Result.OK("删除成功!"); return Result.OK("删除成功!");
} catch (IllegalStateException e) {
return Result.error(e.getMessage());
}
} }
/** /**
@@ -154,8 +180,12 @@ public class ImprovementProposalController {
@RequiresPermissions("improvementproposal:improvement_proposal:deleteBatch") @RequiresPermissions("improvementproposal:improvement_proposal:deleteBatch")
@DeleteMapping(value = "/deleteBatch") @DeleteMapping(value = "/deleteBatch")
public Result<String> deleteBatch(@RequestParam(name="ids",required=true) String ids) { public Result<String> deleteBatch(@RequestParam(name="ids",required=true) String ids) {
try {
this.improvementProposalService.delBatchMain(Arrays.asList(ids.split(","))); this.improvementProposalService.delBatchMain(Arrays.asList(ids.split(",")));
return Result.OK("批量删除成功!"); return Result.OK("批量删除成功!");
} catch (IllegalStateException e) {
return Result.error(e.getMessage());
}
} }
/** /**
@@ -1,8 +1,7 @@
package org.jeecg.modules.improvementproposal.mapper; package org.jeecg.modules.improvementproposal.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.jeecg.modules.improvementproposal.entity.ImprovementProposal; import org.jeecg.modules.improvementproposal.entity.ImprovementProposal;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;
@@ -14,4 +13,10 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
*/ */
public interface ImprovementProposalMapper extends BaseMapper<ImprovementProposal> { public interface ImprovementProposalMapper extends BaseMapper<ImprovementProposal> {
/**
* 锁定提案主表,串行化流程发起、编辑和删除操作,避免同一提案生成多个流程实例。
*/
@Select("SELECT * FROM improvement_proposal WHERE id = #{id} AND del_flag = '0' FOR UPDATE")
ImprovementProposal selectByIdForUpdate(@Param("id") String id);
} }
@@ -16,6 +16,15 @@ import java.util.List;
*/ */
public interface IImprovementProposalService extends IService<ImprovementProposal> { public interface IImprovementProposalService extends IService<ImprovementProposal> {
/**
* 发起改善提案审批流程。
*
* @param id 提案主表 ID
* @param username 当前发起人账号
* @return 流程实例 ID
*/
String startProcess(String id, String username);
/** /**
* 添加一对多 * 添加一对多
* *
@@ -7,6 +7,9 @@ import org.jeecg.modules.improvementproposal.mapper.ImprovementDeptScoreMapper;
import org.jeecg.modules.improvementproposal.mapper.ImprovementInstituteVoteMapper; import org.jeecg.modules.improvementproposal.mapper.ImprovementInstituteVoteMapper;
import org.jeecg.modules.improvementproposal.mapper.ImprovementProposalMapper; import org.jeecg.modules.improvementproposal.mapper.ImprovementProposalMapper;
import org.jeecg.modules.improvementproposal.service.IImprovementProposalService; import org.jeecg.modules.improvementproposal.service.IImprovementProposalService;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.extbpm.process.service.impl.BpmBaseExtApiImpl;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@@ -24,6 +27,9 @@ import java.util.Collection;
@Service @Service
public class ImprovementProposalServiceImpl extends ServiceImpl<ImprovementProposalMapper, ImprovementProposal> implements IImprovementProposalService { public class ImprovementProposalServiceImpl extends ServiceImpl<ImprovementProposalMapper, ImprovementProposal> implements IImprovementProposalService {
private static final String NORMAL_DEL_FLAG = "0"; private static final String NORMAL_DEL_FLAG = "0";
private static final String BPM_STATUS_DRAFT = "1";
private static final String FLOW_CODE = "dev_improvement_proposal_001";
private static final String BPM_FORM_URL = "gh/improvementProposal/components/ImprovementProposalBPMForm";
@Autowired @Autowired
private ImprovementProposalMapper improvementProposalMapper; private ImprovementProposalMapper improvementProposalMapper;
@@ -31,6 +37,34 @@ public class ImprovementProposalServiceImpl extends ServiceImpl<ImprovementPropo
private ImprovementDeptScoreMapper improvementDeptScoreMapper; private ImprovementDeptScoreMapper improvementDeptScoreMapper;
@Autowired @Autowired
private ImprovementInstituteVoteMapper improvementInstituteVoteMapper; private ImprovementInstituteVoteMapper improvementInstituteVoteMapper;
@Autowired
private BpmBaseExtApiImpl bpmBaseExtApi;
@Override
@Transactional(rollbackFor = Exception.class)
public String startProcess(String id, String username) {
ImprovementProposal proposal = getDraftProposalForUpdate(id);
try {
// 改善提案以自身主表 ID 作为流程业务键,不引入 task_task 的重复督办中间表。
Result<String> result = bpmBaseExtApi.startMutilProcess(FLOW_CODE, proposal.getId(), BPM_FORM_URL, BPM_FORM_URL, username, null);
if (result == null || !result.isSuccess() || oConvertUtils.isEmpty(result.getResult())) {
throw new IllegalStateException(result == null || oConvertUtils.isEmpty(result.getMessage()) ? "发起流程失败" : result.getMessage());
}
// 流程引擎会按 BPM 配置将 bpm_status 回写为处理中;此处使用局部对象更新,避免旧实体的草稿状态将其覆盖。
ImprovementProposal processUpdate = new ImprovementProposal();
processUpdate.setId(proposal.getId());
processUpdate.setProcessInstanceId(result.getResult());
if (improvementProposalMapper.updateById(processUpdate) != 1) {
throw new IllegalStateException("流程已启动,但流程实例关联保存失败");
}
return result.getResult();
} catch (IllegalStateException e) {
throw e;
} catch (Exception e) {
throw new IllegalStateException("发起流程失败:" + e.getMessage(), e);
}
}
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
@@ -65,6 +99,10 @@ public class ImprovementProposalServiceImpl extends ServiceImpl<ImprovementPropo
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public void updateMain(ImprovementProposal improvementProposal,List<ImprovementDeptScore> improvementDeptScoreList,List<ImprovementInstituteVote> improvementInstituteVoteList) { public void updateMain(ImprovementProposal improvementProposal,List<ImprovementDeptScore> improvementDeptScoreList,List<ImprovementInstituteVote> improvementInstituteVoteList) {
ImprovementProposal existingProposal = getDraftProposalForUpdate(improvementProposal.getId());
// 草稿编辑不能伪造流程状态或覆盖已关联的流程实例。
improvementProposal.setBpmStatus(existingProposal.getBpmStatus());
improvementProposal.setProcessInstanceId(existingProposal.getProcessInstanceId());
improvementProposalMapper.updateById(improvementProposal); improvementProposalMapper.updateById(improvementProposal);
//1.先删除子表数据 //1.先删除子表数据
@@ -97,6 +135,7 @@ public class ImprovementProposalServiceImpl extends ServiceImpl<ImprovementPropo
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public void delMain(String id) { public void delMain(String id) {
getDraftProposalForUpdate(id);
improvementDeptScoreMapper.deleteByMainId(id); improvementDeptScoreMapper.deleteByMainId(id);
improvementInstituteVoteMapper.deleteByMainId(id); improvementInstituteVoteMapper.deleteByMainId(id);
improvementProposalMapper.deleteById(id); improvementProposalMapper.deleteById(id);
@@ -106,10 +145,29 @@ public class ImprovementProposalServiceImpl extends ServiceImpl<ImprovementPropo
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public void delBatchMain(Collection<? extends Serializable> idList) { public void delBatchMain(Collection<? extends Serializable> idList) {
for(Serializable id:idList) { for(Serializable id:idList) {
getDraftProposalForUpdate(id.toString());
improvementDeptScoreMapper.deleteByMainId(id.toString()); improvementDeptScoreMapper.deleteByMainId(id.toString());
improvementInstituteVoteMapper.deleteByMainId(id.toString()); improvementInstituteVoteMapper.deleteByMainId(id.toString());
improvementProposalMapper.deleteById(id); improvementProposalMapper.deleteById(id);
} }
} }
/**
* 任何会改变主表生命周期的操作都必须先锁定并确认仍是未发起草稿,
* 这样并发的“发起流程”和“编辑/删除”不会让流程与业务数据脱节。
*/
private ImprovementProposal getDraftProposalForUpdate(String id) {
if (oConvertUtils.isEmpty(id)) {
throw new IllegalStateException("提案ID不能为空");
}
ImprovementProposal proposal = improvementProposalMapper.selectByIdForUpdate(id);
if (proposal == null) {
throw new IllegalStateException("未找到对应提案");
}
if (!BPM_STATUS_DRAFT.equals(proposal.getBpmStatus()) || oConvertUtils.isNotEmpty(proposal.getProcessInstanceId())) {
throw new IllegalStateException("提案已进入流程,不能编辑、删除或重复发起");
}
return proposal;
}
} }