|
|
|
|
@ -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);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|