add项目售后信息工作流

dev
Yangk 2 weeks ago
parent fada5ccbdb
commit 387967167d

@ -113,4 +113,15 @@ public class ErpAfterSalesController extends BaseController {
return R.ok(list);
}
/**
*
*/
@SaCheckPermission("oa/erp:afterSales:add")
@Log(title = "项目售后信息", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping("/submitAndFlowStart")
public R<ErpAfterSalesVo> submitAndFlowStart(@Validated(AddGroup.class) @RequestBody ErpAfterSalesBo bo) {
return R.ok(erpAfterSalesService.submitAndFlowStart(bo));
}
}

@ -13,10 +13,12 @@ import java.math.BigDecimal;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.util.List; // 1. 别忘了导包 List
import java.util.Map;
// 2. 导入两个子表的 BO 对象,因为前端传的是子表的业务数据
import org.dromara.oa.erp.domain.bo.ErpAfterSalesLaborCostsBo;
import org.dromara.oa.erp.domain.bo.ErpAfterSalesMaterialCostsBo;
import org.dromara.workflow.api.domain.RemoteFlowInstanceBizExt;
/**
* erp_after_sales
@ -180,5 +182,20 @@ public class ErpAfterSalesBo extends BaseEntity {
*/
private List<ErpAfterSalesMaterialCostsBo> materialCostsList;
/**
*
*/
private String flowCode;
/**
* {'entity': {}}
*/
private Map<String, Object> variables;
/**
*
*/
private RemoteFlowInstanceBizExt bizExt;
}

@ -66,4 +66,12 @@ public interface IErpAfterSalesService {
* @return
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
/**
*
* @param bo
* @return
*/
ErpAfterSalesVo submitAndFlowStart(ErpAfterSalesBo bo);
}

@ -1,6 +1,12 @@
package org.dromara.oa.erp.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.seata.spring.annotation.GlobalTransactional;
import org.dromara.common.core.enums.BusinessStatusEnum;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.TableDataInfo;
@ -10,6 +16,7 @@ import com.github.yulichang.toolkit.JoinWrappers;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.oa.erp.domain.ErpAfterSalesLaborCosts;
import org.dromara.oa.erp.domain.ErpAfterSalesMaterialCosts;
import org.dromara.oa.erp.domain.bo.ErpAfterSalesLaborCostsBo;
@ -18,6 +25,8 @@ import org.dromara.oa.erp.domain.vo.ErpAfterSalesLaborCostsVo;
import org.dromara.oa.erp.domain.vo.ErpAfterSalesMaterialCostsVo;
import org.dromara.oa.erp.service.IErpAfterSalesLaborCostsService;
import org.dromara.oa.erp.service.IErpAfterSalesMaterialCostsService;
import org.dromara.workflow.api.RemoteWorkflowService;
import org.dromara.workflow.api.domain.RemoteStartProcess;
import org.springframework.stereotype.Service;
import org.dromara.oa.erp.domain.bo.ErpAfterSalesBo;
import org.dromara.oa.erp.domain.vo.ErpAfterSalesVo;
@ -26,7 +35,12 @@ import org.dromara.oa.erp.mapper.ErpAfterSalesMapper;
import org.dromara.oa.erp.service.IErpAfterSalesService;
import org.springframework.transaction.annotation.Transactional;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.springframework.context.event.EventListener;
import org.dromara.workflow.api.event.ProcessEvent;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Collection;
@ -39,12 +53,14 @@ import java.util.Collection;
*/
@RequiredArgsConstructor
@Service
@Slf4j
public class ErpAfterSalesServiceImpl implements IErpAfterSalesService {
private final ErpAfterSalesMapper baseMapper;
// 【修改点 1】注入两个子表的 Service
// 注意:这里必须加 final配合 @RequiredArgsConstructor 使用
@DubboReference(timeout = 30000)
private RemoteWorkflowService remoteWorkflowService;
private final IErpAfterSalesLaborCostsService laborCostsService;
private final IErpAfterSalesMaterialCostsService materialCostsService;
@ -56,31 +72,21 @@ public class ErpAfterSalesServiceImpl implements IErpAfterSalesService {
*/
@Override
public ErpAfterSalesVo queryById(Long afterSalesId){
// 1. 查询主表 VO
ErpAfterSalesVo vo = baseMapper.selectVoById(afterSalesId);
// 2. 如果主表存在,继续查询子表
if (vo != null) {
// ============ 处理人工费 ============
// 2.1 使用 list 方法查询出 Entity 列表
List<ErpAfterSalesLaborCosts> laborEntities = laborCostsService.list(
new LambdaQueryWrapper<ErpAfterSalesLaborCosts>()
.eq(ErpAfterSalesLaborCosts::getAfterSalesId, afterSalesId)
);
// 2.2 将 Entity 列表转换为 VO 列表 (核心修复点)
List<ErpAfterSalesLaborCostsVo> laborVos = MapstructUtils.convert(laborEntities, ErpAfterSalesLaborCostsVo.class);
// 2.3 塞入主表对象
vo.setLaborCostsList(laborVos);
// ============ 处理材料费 ============
// 3.1 使用 list 方法查询出 Entity 列表
List<ErpAfterSalesMaterialCosts> materialEntities = materialCostsService.list(
new LambdaQueryWrapper<ErpAfterSalesMaterialCosts>()
.eq(ErpAfterSalesMaterialCosts::getAfterSalesId, afterSalesId)
);
// 3.2 将 Entity 列表转换为 VO 列表
List<ErpAfterSalesMaterialCostsVo> materialVos = MapstructUtils.convert(materialEntities, ErpAfterSalesMaterialCostsVo.class);
// 3.3 塞入主表对象
vo.setMaterialCostsList(materialVos);
}
return vo;
@ -155,35 +161,23 @@ public class ErpAfterSalesServiceImpl implements IErpAfterSalesService {
@Override
@Transactional(rollbackFor = Exception.class) // 【重要】开启事务!如果子表保存失败,主表也要回滚,防止数据不一致
public Boolean insertByBo(ErpAfterSalesBo bo) {
// 1. 将 BO (业务对象) 转换为 Entity (数据库实体)
// MapstructUtils 是 Plus 封装的高性能 Bean 拷贝工具
ErpAfterSales add = MapstructUtils.convert(bo, ErpAfterSales.class);
// 2. 校验数据(这是生成的代码自带的)
validEntityBeforeSave(add);
// 3. 保存主表
// baseMapper.insert(add) 返回受影响行数大于0表示成功
// 成功后MyBatis 会自动把生成的 ID 回填到 add 对象中
boolean flag = baseMapper.insert(add) > 0;
bo.setAfterSalesId(add.getAfterSalesId()); // 将生成的 ID 回填给 BO
// 4. 如果主表保存成功,开始处理子表
if (flag) {
Long afterSalesId = add.getAfterSalesId(); // 拿到主表刚才生成的 ID
Long afterSalesId = add.getAfterSalesId();
// --- 处理子表 A人员费用 ---
List<ErpAfterSalesLaborCostsBo> laborBoList = bo.getLaborCostsList();
// 使用 CollUtil.isNotEmpty 判断列表不为空
if (CollUtil.isNotEmpty(laborBoList)) {
// 将 BO 列表转为 Entity 列表
List<ErpAfterSalesLaborCosts> laborList = MapstructUtils.convert(laborBoList, ErpAfterSalesLaborCosts.class);
// 遍历每个子对象,把主表 ID 填进去,建立外键关联
laborList.forEach(item -> item.setAfterSalesId(afterSalesId));
// 批量保存
laborCostsService.saveBatch(laborList);
}
// --- 处理子表 B材料费用 ---
List<ErpAfterSalesMaterialCostsBo> materialBoList = bo.getMaterialCostsList();
if (CollUtil.isNotEmpty(materialBoList)) {
List<ErpAfterSalesMaterialCosts> materialList = MapstructUtils.convert(materialBoList, ErpAfterSalesMaterialCosts.class);
@ -203,17 +197,14 @@ public class ErpAfterSalesServiceImpl implements IErpAfterSalesService {
@Override
@Transactional(rollbackFor = Exception.class) // 【重要】开启事务
public Boolean updateByBo(ErpAfterSalesBo bo) {
// 1. 更新主表数据
ErpAfterSales update = MapstructUtils.convert(bo, ErpAfterSales.class);
validEntityBeforeSave(update);
// updateById 会根据主键 ID 更新其他字段
int row = baseMapper.updateById(update);
// 2. 如果主表更新成功,开始处理子表
if (row > 0) {
Long afterSalesId = bo.getAfterSalesId();
// ================== 1. 处理人员费用 ==================
// ================== 处理人员费用 ==================
// 先删除旧的
laborCostsService.remove(new LambdaQueryWrapper<ErpAfterSalesLaborCosts>()
.eq(ErpAfterSalesLaborCosts::getAfterSalesId, afterSalesId));
@ -223,7 +214,7 @@ public class ErpAfterSalesServiceImpl implements IErpAfterSalesService {
if (CollUtil.isNotEmpty(laborBoList)) {
List<ErpAfterSalesLaborCosts> laborList = MapstructUtils.convert(laborBoList, ErpAfterSalesLaborCosts.class);
// 【核心修改点 👇】遍历列表,清空 ID
// 遍历列表,清空 ID
laborList.forEach(item -> {
item.setLaborCostsId(null); // 关键清空ID让MP重新生成雪花ID避免和逻辑删除的数据冲突
item.setAfterSalesId(afterSalesId);
@ -232,17 +223,14 @@ public class ErpAfterSalesServiceImpl implements IErpAfterSalesService {
laborCostsService.saveBatch(laborList);
}
// ================== 2. 处理材料费用 ==================
// 先删除旧的
// ================== 处理材料费用 ==================
materialCostsService.remove(new LambdaQueryWrapper<ErpAfterSalesMaterialCosts>()
.eq(ErpAfterSalesMaterialCosts::getAfterSalesId, afterSalesId));
// 后新增新的
List<ErpAfterSalesMaterialCostsBo> materialBoList = bo.getMaterialCostsList();
if (CollUtil.isNotEmpty(materialBoList)) {
List<ErpAfterSalesMaterialCosts> materialList = MapstructUtils.convert(materialBoList, ErpAfterSalesMaterialCosts.class);
// 【核心修改点 👇】遍历列表,清空 ID
materialList.forEach(item -> {
item.setMaterialCostsId(null); // 关键清空ID
item.setAfterSalesId(afterSalesId);
@ -275,4 +263,104 @@ public class ErpAfterSalesServiceImpl implements IErpAfterSalesService {
}
return baseMapper.deleteByIds(ids) > 0;
}
/**
*
*
* @param bo
* @return
*/
@Override
@GlobalTransactional(rollbackFor = Exception.class) // 开启全局事务
public ErpAfterSalesVo submitAndFlowStart(ErpAfterSalesBo bo) {
ErpAfterSales add = MapstructUtils.convert(bo, ErpAfterSales.class);
validEntityBeforeSave(add);
if (bo.getAfterSalesId() == null) {
this.insertByBo(bo);
} else {
this.updateByBo(bo);
}
// 准备启动流程参数
// 后端发起需要忽略权限
if (bo.getVariables() == null) {
bo.setVariables(new HashMap<>());
}
bo.getVariables().put("ignore", true);
RemoteStartProcess startProcess = new RemoteStartProcess();
startProcess.setBusinessId(bo.getAfterSalesId().toString());
startProcess.setFlowCode(bo.getFlowCode());
startProcess.setVariables(bo.getVariables());
startProcess.setBizExt(bo.getBizExt());
// 确保 BizExt 里也有 BusinessId
if (bo.getBizExt() != null) {
bo.getBizExt().setBusinessId(startProcess.getBusinessId());
}
// 调用远程服务启动流程
// startCompleteTask 表示“启动并自动完成第一个发起节点”,直接流转到下一个审批人
boolean flagOne = remoteWorkflowService.startCompleteTask(startProcess);
if (!flagOne) {
throw new ServiceException("流程发起异常");
}
return MapstructUtils.convert(add, ErpAfterSalesVo.class);
}
/**
* (: 稿退)
*
* @param processEvent
*/
@EventListener(condition = "#processEvent.flowCode == 'OAAS'")
public void processHandler(ProcessEvent processEvent) {
// 多租户上下文切换(防止异步线程找不到租户)
TenantHelper.dynamic(processEvent.getTenantId(), () -> {
log.info("【售后流程监听】开始处理: key={}, status={}", processEvent.getFlowCode(), processEvent.getStatus());
// 根据 BusinessId 查询出当前的售后单
Long afterSalesId = Convert.toLong(processEvent.getBusinessId());
ErpAfterSales afterSales = baseMapper.selectById(afterSalesId);
if (afterSales == null) {
log.error("未找到对应的售后单据id={}", afterSalesId);
return;
}
// 同步流程状态 (flow_status)
afterSales.setFlowStatus(processEvent.getStatus());
// 根据流程状态,更新业务状态 (after_sales_status)
String status = processEvent.getStatus();
// A. 审批中
if (BusinessStatusEnum.WAITING.getStatus().equals(status)) {
afterSales.setAfterSalesStatus("1");
}
// B. 流程结束/审批通过 (Finish)
else if (BusinessStatusEnum.FINISH.getStatus().equals(status)) {
afterSales.setAfterSalesStatus("2");
}
// C. 驳回 (Back) 或 撤销 (Cancel)
else if (BusinessStatusEnum.BACK.getStatus().equals(status)
|| BusinessStatusEnum.CANCEL.getStatus().equals(status)) {
afterSales.setAfterSalesStatus("0");
}
// D. 作废 (Invalid) 或 终止 (Termination)
else if (BusinessStatusEnum.INVALID.getStatus().equals(status)
|| BusinessStatusEnum.TERMINATION.getStatus().equals(status)) {
afterSales.setAfterSalesStatus("0");
}
// 4. 更新数据库
baseMapper.updateById(afterSales);
});
}
}

Loading…
Cancel
Save