|
@@ -1,12 +1,26 @@
|
|
|
package com.mdframe.forge.business.core.restaurant.service.impl;
|
|
package com.mdframe.forge.business.core.restaurant.service.impl;
|
|
|
|
|
|
|
|
|
|
+import com.alibaba.fastjson2.JSON;
|
|
|
|
|
+import com.alibaba.fastjson2.JSONObject;
|
|
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
|
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
|
import com.mdframe.forge.business.core.restaurant.domain.CreditCustomer;
|
|
import com.mdframe.forge.business.core.restaurant.domain.CreditCustomer;
|
|
|
import com.mdframe.forge.business.core.restaurant.dto.CreditCustomerDTO;
|
|
import com.mdframe.forge.business.core.restaurant.dto.CreditCustomerDTO;
|
|
|
|
|
+import com.mdframe.forge.business.core.restaurant.dto.CreditCustomerSubmitDTO;
|
|
|
|
|
+import com.mdframe.forge.business.core.restaurant.dto.CreditCustomerTaskSaveDTO;
|
|
|
import com.mdframe.forge.business.core.restaurant.mapper.CreditCustomerMapper;
|
|
import com.mdframe.forge.business.core.restaurant.mapper.CreditCustomerMapper;
|
|
|
import com.mdframe.forge.business.core.restaurant.service.CreditCustomerService;
|
|
import com.mdframe.forge.business.core.restaurant.service.CreditCustomerService;
|
|
|
|
|
+import com.mdframe.forge.business.core.restaurant.support.CreditCustomerFlowBpmn;
|
|
|
|
|
+import com.mdframe.forge.business.core.restaurant.support.CreditCustomerFlowDefinition;
|
|
|
|
|
+import com.mdframe.forge.business.core.restaurant.vo.CreditCustomerFlowInitVO;
|
|
|
import com.mdframe.forge.business.core.restaurant.vo.CreditCustomerVO;
|
|
import com.mdframe.forge.business.core.restaurant.vo.CreditCustomerVO;
|
|
|
|
|
+import com.mdframe.forge.flow.client.FlowClient;
|
|
|
|
|
+import com.mdframe.forge.flow.client.FlowResult;
|
|
|
|
|
+import com.mdframe.forge.flow.client.annotation.FlowBind;
|
|
|
|
|
+import com.mdframe.forge.flow.client.annotation.FlowCallback;
|
|
|
|
|
+import com.mdframe.forge.flow.client.annotation.FlowEventContext;
|
|
|
|
|
+import com.mdframe.forge.plugin.generator.domain.entity.AiBusinessBinding;
|
|
|
|
|
+import com.mdframe.forge.plugin.generator.mapper.BusinessBindingMapper;
|
|
|
import com.mdframe.forge.starter.core.domain.PageQuery;
|
|
import com.mdframe.forge.starter.core.domain.PageQuery;
|
|
|
import com.mdframe.forge.starter.core.exception.BusinessException;
|
|
import com.mdframe.forge.starter.core.exception.BusinessException;
|
|
|
import com.mdframe.forge.starter.core.session.SessionHelper;
|
|
import com.mdframe.forge.starter.core.session.SessionHelper;
|
|
@@ -20,23 +34,48 @@ import org.springframework.util.StringUtils;
|
|
|
|
|
|
|
|
import java.math.BigDecimal;
|
|
import java.math.BigDecimal;
|
|
|
import java.time.LocalDate;
|
|
import java.time.LocalDate;
|
|
|
|
|
+import java.time.LocalDateTime;
|
|
|
|
|
+import java.time.format.DateTimeFormatter;
|
|
|
|
|
+import java.util.ArrayList;
|
|
|
|
|
+import java.util.Collection;
|
|
|
|
|
+import java.util.LinkedHashMap;
|
|
|
|
|
+import java.util.LinkedHashSet;
|
|
|
|
|
+import java.util.List;
|
|
|
|
|
+import java.util.Map;
|
|
|
import java.util.Objects;
|
|
import java.util.Objects;
|
|
|
|
|
+import java.util.Set;
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* 挂账客户服务实现。
|
|
* 挂账客户服务实现。
|
|
|
|
|
+ * <p>
|
|
|
|
|
+ * 集成 Flowable 审批流程(单步老板审核):
|
|
|
|
|
+ * 提交后写入 business_key/process_instance_id 并置为 IN_PROCESS,
|
|
|
|
|
+ * 回调维护 NEED_MODIFY/APPROVED/REJECTED/CANCELED 状态,
|
|
|
|
|
+ * 分页/详情查询按活动待办节点对账修复状态漂移。
|
|
|
*/
|
|
*/
|
|
|
@Slf4j
|
|
@Slf4j
|
|
|
@Service
|
|
@Service
|
|
|
|
|
+@FlowBind(modelKey = CreditCustomerService.MODEL_KEY, businessType = CreditCustomerService.BUSINESS_TYPE)
|
|
|
public class CreditCustomerServiceImpl implements CreditCustomerService {
|
|
public class CreditCustomerServiceImpl implements CreditCustomerService {
|
|
|
|
|
|
|
|
@Autowired
|
|
@Autowired
|
|
|
private CreditCustomerMapper creditCustomerMapper;
|
|
private CreditCustomerMapper creditCustomerMapper;
|
|
|
|
|
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private FlowClient flowClient;
|
|
|
|
|
+
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private BusinessBindingMapper businessBindingMapper;
|
|
|
|
|
+
|
|
|
@Override
|
|
@Override
|
|
|
- public IPage<CreditCustomerVO> creditPage(PageQuery pageQuery, String customerName, String mobile, String creditStatus) {
|
|
|
|
|
|
|
+ public IPage<CreditCustomerVO> creditPage(PageQuery pageQuery, String customerName, String mobile,
|
|
|
|
|
+ String creditStatus, String approvalStatus) {
|
|
|
Long tenantId = resolveTenantId();
|
|
Long tenantId = resolveTenantId();
|
|
|
Page<CreditCustomerVO> page = new Page<>(pageQuery.getPageNum(), pageQuery.getPageSize());
|
|
Page<CreditCustomerVO> page = new Page<>(pageQuery.getPageNum(), pageQuery.getPageSize());
|
|
|
- return creditCustomerMapper.selectPage(page, tenantId, customerName, mobile, creditStatus);
|
|
|
|
|
|
|
+ IPage<CreditCustomerVO> result = creditCustomerMapper.selectPage(page, tenantId, customerName, mobile,
|
|
|
|
|
+ creditStatus, approvalStatus);
|
|
|
|
|
+ reconcileStatusesWithActiveTasks(result.getRecords());
|
|
|
|
|
+ return result;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
@Override
|
|
@@ -49,10 +88,43 @@ public class CreditCustomerServiceImpl implements CreditCustomerService {
|
|
|
if (vo == null) {
|
|
if (vo == null) {
|
|
|
throw new BusinessException("挂账客户不存在");
|
|
throw new BusinessException("挂账客户不存在");
|
|
|
}
|
|
}
|
|
|
|
|
+ List<CreditCustomerVO> single = new ArrayList<>();
|
|
|
|
|
+ single.add(vo);
|
|
|
|
|
+ reconcileStatusesWithActiveTasks(single);
|
|
|
return vo;
|
|
return vo;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
@Override
|
|
|
|
|
+ public List<CreditCustomerVO> detailsByIds(Collection<Long> ids) {
|
|
|
|
|
+ List<Long> normalizedIds = new ArrayList<>();
|
|
|
|
|
+ if (ids != null) {
|
|
|
|
|
+ for (Long id : ids) {
|
|
|
|
|
+ if (id != null && !normalizedIds.contains(id)) {
|
|
|
|
|
+ normalizedIds.add(id);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if (normalizedIds.isEmpty()) {
|
|
|
|
|
+ return new ArrayList<>();
|
|
|
|
|
+ }
|
|
|
|
|
+ List<CreditCustomerVO> details = creditCustomerMapper.selectDetailsByIds(resolveTenantId(), normalizedIds);
|
|
|
|
|
+ reconcileStatusesWithActiveTasks(details);
|
|
|
|
|
+ return details == null ? new ArrayList<>() : details;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public CreditCustomerVO detailByBusinessKey(String businessKey) {
|
|
|
|
|
+ if (!StringUtils.hasText(businessKey)) {
|
|
|
|
|
+ throw new BusinessException("挂账客户业务Key不能为空");
|
|
|
|
|
+ }
|
|
|
|
|
+ CreditCustomer entity = creditCustomerMapper.selectByBusinessKey(resolveTenantId(), businessKey.trim());
|
|
|
|
|
+ if (entity == null) {
|
|
|
|
|
+ throw new BusinessException("挂账客户不存在");
|
|
|
|
|
+ }
|
|
|
|
|
+ return creditDetail(entity.getId());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
@Transactional(rollbackFor = Exception.class)
|
|
@Transactional(rollbackFor = Exception.class)
|
|
|
public Long creditSave(CreditCustomerDTO dto) {
|
|
public Long creditSave(CreditCustomerDTO dto) {
|
|
|
if (dto == null) {
|
|
if (dto == null) {
|
|
@@ -72,28 +144,39 @@ public class CreditCustomerServiceImpl implements CreditCustomerService {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if (dto.getId() != null) {
|
|
if (dto.getId() != null) {
|
|
|
- // 修改
|
|
|
|
|
- CreditCustomer entity = creditCustomerMapper.selectById(dto.getId());
|
|
|
|
|
- if (entity == null || !Objects.equals(entity.getTenantId(), tenantId)) {
|
|
|
|
|
- throw new BusinessException("挂账客户不存在");
|
|
|
|
|
|
|
+ // 修改:仅暂存/撤回/驳回状态允许编辑;审批中走待办任务表单,审批通过后资料锁定
|
|
|
|
|
+ CreditCustomer entity = requireEntity(dto.getId());
|
|
|
|
|
+ String originStatus = entity.getApprovalStatus();
|
|
|
|
|
+ if (!STATUS_DRAFT.equals(originStatus)
|
|
|
|
|
+ && !STATUS_CANCELED.equals(originStatus)
|
|
|
|
|
+ && !STATUS_REJECTED.equals(originStatus)) {
|
|
|
|
|
+ throw new BusinessException("只有暂存、撤回或驳回状态的挂账客户允许编辑");
|
|
|
|
|
+ }
|
|
|
|
|
+ BeanUtils.copyProperties(dto, entity, "id", "birthday", "firstCreditTime", "lastConsumeTime");
|
|
|
|
|
+ // 撤回/驳回记录编辑保存后回到暂存,允许重新送审
|
|
|
|
|
+ if (STATUS_CANCELED.equals(originStatus) || STATUS_REJECTED.equals(originStatus)) {
|
|
|
|
|
+ entity.setApprovalStatus(STATUS_DRAFT);
|
|
|
}
|
|
}
|
|
|
- BeanUtils.copyProperties(dto, entity, "id", "birthday");
|
|
|
|
|
// 处理生日字段(String → LocalDate)
|
|
// 处理生日字段(String → LocalDate)
|
|
|
if (StringUtils.hasText(dto.getBirthday())) {
|
|
if (StringUtils.hasText(dto.getBirthday())) {
|
|
|
entity.setBirthday(LocalDate.parse(dto.getBirthday()));
|
|
entity.setBirthday(LocalDate.parse(dto.getBirthday()));
|
|
|
}
|
|
}
|
|
|
|
|
+ entity.setFirstCreditTime(parseDateTime(dto.getFirstCreditTime()));
|
|
|
|
|
+ entity.setLastConsumeTime(parseDateTime(dto.getLastConsumeTime()));
|
|
|
creditCustomerMapper.updateById(entity);
|
|
creditCustomerMapper.updateById(entity);
|
|
|
log.info("修改挂账客户: {} ({})", entity.getCustomerName(), entity.getMobile());
|
|
log.info("修改挂账客户: {} ({})", entity.getCustomerName(), entity.getMobile());
|
|
|
} else {
|
|
} else {
|
|
|
- // 新增
|
|
|
|
|
|
|
+ // 新增:初始为草稿,等待提交老板审批
|
|
|
CreditCustomer entity = new CreditCustomer();
|
|
CreditCustomer entity = new CreditCustomer();
|
|
|
- BeanUtils.copyProperties(dto, entity, "birthday");
|
|
|
|
|
|
|
+ BeanUtils.copyProperties(dto, entity, "birthday", "firstCreditTime", "lastConsumeTime");
|
|
|
if (StringUtils.hasText(dto.getBirthday())) {
|
|
if (StringUtils.hasText(dto.getBirthday())) {
|
|
|
entity.setBirthday(LocalDate.parse(dto.getBirthday()));
|
|
entity.setBirthday(LocalDate.parse(dto.getBirthday()));
|
|
|
}
|
|
}
|
|
|
|
|
+ entity.setFirstCreditTime(parseDateTime(dto.getFirstCreditTime()));
|
|
|
|
|
+ entity.setLastConsumeTime(parseDateTime(dto.getLastConsumeTime()));
|
|
|
entity.setTenantId(tenantId);
|
|
entity.setTenantId(tenantId);
|
|
|
if (!StringUtils.hasText(entity.getCreditStatus())) {
|
|
if (!StringUtils.hasText(entity.getCreditStatus())) {
|
|
|
- entity.setCreditStatus("NORMAL");
|
|
|
|
|
|
|
+ entity.setCreditStatus("0");
|
|
|
}
|
|
}
|
|
|
if (entity.getTotalArrearsAmount() == null) {
|
|
if (entity.getTotalArrearsAmount() == null) {
|
|
|
entity.setTotalArrearsAmount(BigDecimal.valueOf(0L));
|
|
entity.setTotalArrearsAmount(BigDecimal.valueOf(0L));
|
|
@@ -101,6 +184,9 @@ public class CreditCustomerServiceImpl implements CreditCustomerService {
|
|
|
if (entity.getUnsettledBillCount() == null) {
|
|
if (entity.getUnsettledBillCount() == null) {
|
|
|
entity.setUnsettledBillCount(0);
|
|
entity.setUnsettledBillCount(0);
|
|
|
}
|
|
}
|
|
|
|
|
+ entity.setApprovalStatus(STATUS_DRAFT);
|
|
|
|
|
+ entity.setApplicantId(SessionHelper.getUserId());
|
|
|
|
|
+ entity.setApplicantName(resolveUsername());
|
|
|
creditCustomerMapper.insert(entity);
|
|
creditCustomerMapper.insert(entity);
|
|
|
log.info("新增挂账客户: {} ({})", entity.getCustomerName(), entity.getMobile());
|
|
log.info("新增挂账客户: {} ({})", entity.getCustomerName(), entity.getMobile());
|
|
|
return entity.getId();
|
|
return entity.getId();
|
|
@@ -108,16 +194,21 @@ public class CreditCustomerServiceImpl implements CreditCustomerService {
|
|
|
return dto.getId();
|
|
return dto.getId();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /** 解析时间字段(String yyyy-MM-dd HH:mm:ss → LocalDateTime),空值返回 null */
|
|
|
|
|
+ private LocalDateTime parseDateTime(String value) {
|
|
|
|
|
+ return StringUtils.hasText(value)
|
|
|
|
|
+ ? LocalDateTime.parse(value.trim(), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
|
|
|
|
|
+ : null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
@Override
|
|
@Override
|
|
|
@Transactional(rollbackFor = Exception.class)
|
|
@Transactional(rollbackFor = Exception.class)
|
|
|
public void creditDelete(Long id) {
|
|
public void creditDelete(Long id) {
|
|
|
- if (id == null) {
|
|
|
|
|
- throw new BusinessException("客户ID不能为空");
|
|
|
|
|
- }
|
|
|
|
|
- Long tenantId = resolveTenantId();
|
|
|
|
|
- CreditCustomer entity = creditCustomerMapper.selectById(id);
|
|
|
|
|
- if (entity == null || !Objects.equals(entity.getTenantId(), tenantId)) {
|
|
|
|
|
- throw new BusinessException("挂账客户不存在");
|
|
|
|
|
|
|
+ CreditCustomer entity = requireEntity(id);
|
|
|
|
|
+ if (!STATUS_DRAFT.equals(entity.getApprovalStatus())
|
|
|
|
|
+ && !STATUS_REJECTED.equals(entity.getApprovalStatus())
|
|
|
|
|
+ && !STATUS_CANCELED.equals(entity.getApprovalStatus())) {
|
|
|
|
|
+ throw new BusinessException("当前审批状态不允许删除,仅草稿/已拒绝/已取消可删除");
|
|
|
}
|
|
}
|
|
|
creditCustomerMapper.deleteById(id);
|
|
creditCustomerMapper.deleteById(id);
|
|
|
log.info("删除挂账客户: {} ({})", entity.getCustomerName(), entity.getMobile());
|
|
log.info("删除挂账客户: {} ({})", entity.getCustomerName(), entity.getMobile());
|
|
@@ -126,27 +217,23 @@ public class CreditCustomerServiceImpl implements CreditCustomerService {
|
|
|
@Override
|
|
@Override
|
|
|
@Transactional(rollbackFor = Exception.class)
|
|
@Transactional(rollbackFor = Exception.class)
|
|
|
public void creditToggleStatus(Long id) {
|
|
public void creditToggleStatus(Long id) {
|
|
|
- if (id == null) {
|
|
|
|
|
- throw new BusinessException("客户ID不能为空");
|
|
|
|
|
- }
|
|
|
|
|
- Long tenantId = resolveTenantId();
|
|
|
|
|
- CreditCustomer entity = creditCustomerMapper.selectById(id);
|
|
|
|
|
- if (entity == null || !Objects.equals(entity.getTenantId(), tenantId)) {
|
|
|
|
|
- throw new BusinessException("挂账客户不存在");
|
|
|
|
|
|
|
+ CreditCustomer entity = requireEntity(id);
|
|
|
|
|
+ if (!STATUS_APPROVED.equals(entity.getApprovalStatus())) {
|
|
|
|
|
+ throw new BusinessException("挂账客户审批通过后才能切换挂账状态");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 循环切换:NORMAL → SUSPENDED → BLACKLIST → NORMAL
|
|
|
|
|
|
|
+ // 循环切换:0(正常) → 1(暂停) → 2(黑名单) → 0(正常)
|
|
|
String current = entity.getCreditStatus();
|
|
String current = entity.getCreditStatus();
|
|
|
String next;
|
|
String next;
|
|
|
- switch (current != null ? current : "NORMAL") {
|
|
|
|
|
- case "NORMAL":
|
|
|
|
|
- next = "SUSPENDED";
|
|
|
|
|
|
|
+ switch (current != null ? current : "0") {
|
|
|
|
|
+ case "0":
|
|
|
|
|
+ next = "1";
|
|
|
break;
|
|
break;
|
|
|
- case "SUSPENDED":
|
|
|
|
|
- next = "BLACKLIST";
|
|
|
|
|
|
|
+ case "1":
|
|
|
|
|
+ next = "2";
|
|
|
break;
|
|
break;
|
|
|
default:
|
|
default:
|
|
|
- next = "NORMAL";
|
|
|
|
|
|
|
+ next = "0";
|
|
|
break;
|
|
break;
|
|
|
}
|
|
}
|
|
|
entity.setCreditStatus(next);
|
|
entity.setCreditStatus(next);
|
|
@@ -154,7 +241,595 @@ public class CreditCustomerServiceImpl implements CreditCustomerService {
|
|
|
log.info("切换挂账状态: {} -> {}", entity.getCustomerName(), next);
|
|
log.info("切换挂账状态: {} -> {}", entity.getCustomerName(), next);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // ==================== 审批流程 ====================
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
|
|
+ public String submitApproval(Long id, CreditCustomerSubmitDTO dto) {
|
|
|
|
|
+ CreditCustomer entity = requireEntity(id);
|
|
|
|
|
+ if (!STATUS_DRAFT.equals(entity.getApprovalStatus())) {
|
|
|
|
|
+ throw new BusinessException("只有草稿状态的挂账客户允许提交审批");
|
|
|
|
|
+ }
|
|
|
|
|
+ // 审批人由流程节点配置(候选角色/指定人)决定;仅当老板节点仍用 bossId 变量表达式时才强制选择
|
|
|
|
|
+ Long approverId = dto == null ? null : dto.getApproverId();
|
|
|
|
|
+ if (approverId == null && requiresApproverSelection()) {
|
|
|
|
|
+ throw new BusinessException("请选择审批人(老板)");
|
|
|
|
|
+ }
|
|
|
|
|
+ ensureFlowModel();
|
|
|
|
|
+
|
|
|
|
|
+ String businessKey = CreditCustomerFlowDefinition.businessKey(entity.getId());
|
|
|
|
|
+ String userId = SessionHelper.getUserId() == null ? null : String.valueOf(SessionHelper.getUserId());
|
|
|
|
|
+ String userName = resolveUsername();
|
|
|
|
|
+
|
|
|
|
|
+ Map<String, Object> variables = new LinkedHashMap<>();
|
|
|
|
|
+ variables.put(CreditCustomerFlowDefinition.VAR_BUSINESS_KEY, businessKey);
|
|
|
|
|
+ variables.put(CreditCustomerFlowDefinition.VAR_OBJECT_CODE, BUSINESS_TYPE);
|
|
|
|
|
+ variables.put(CreditCustomerFlowDefinition.VAR_RECORD_ID, String.valueOf(entity.getId()));
|
|
|
|
|
+ variables.put(CreditCustomerFlowDefinition.VAR_CREDIT_CUSTOMER_ID, String.valueOf(entity.getId()));
|
|
|
|
|
+ variables.put(CreditCustomerFlowDefinition.VAR_CUSTOMER_NAME, entity.getCustomerName());
|
|
|
|
|
+ variables.put(CreditCustomerFlowDefinition.VAR_MOBILE, entity.getMobile());
|
|
|
|
|
+ variables.put(CreditCustomerFlowDefinition.VAR_TITLE, "挂账客户审批-" + entity.getCustomerName());
|
|
|
|
|
+ variables.put(CreditCustomerFlowDefinition.VAR_INITIATOR, userId);
|
|
|
|
|
+ if (approverId != null) {
|
|
|
|
|
+ variables.put(CreditCustomerFlowDefinition.VAR_BOSS_ID, String.valueOf(approverId));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ String flowTitle = buildConfiguredFlowTitle(entity, businessKey, variables);
|
|
|
|
|
+ FlowResult<String> result = flowClient.startProcess(
|
|
|
|
|
+ MODEL_KEY,
|
|
|
|
|
+ businessKey,
|
|
|
|
|
+ BUSINESS_TYPE,
|
|
|
|
|
+ flowTitle,
|
|
|
|
|
+ variables,
|
|
|
|
|
+ userId,
|
|
|
|
|
+ userName,
|
|
|
|
|
+ SessionHelper.getMainOrgId() == null ? null : String.valueOf(SessionHelper.getMainOrgId()),
|
|
|
|
|
+ null);
|
|
|
|
|
+ if (result == null || !result.isSuccess() || !StringUtils.hasText(result.getData())) {
|
|
|
|
|
+ throw new BusinessException("流程发起失败: " + (result == null ? "无返回结果" : result.getMsg()));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ entity.setBusinessKey(businessKey);
|
|
|
|
|
+ entity.setProcessInstanceId(result.getData());
|
|
|
|
|
+ entity.setApplicantId(SessionHelper.getUserId());
|
|
|
|
|
+ entity.setApplicantName(userName);
|
|
|
|
|
+ if (approverId != null) {
|
|
|
|
|
+ entity.setApproverId(approverId);
|
|
|
|
|
+ }
|
|
|
|
|
+ entity.setApprovalStatus(STATUS_IN_PROCESS);
|
|
|
|
|
+ entity.setRejectReason(null);
|
|
|
|
|
+ creditCustomerMapper.updateById(entity);
|
|
|
|
|
+ log.info("挂账客户提交审批: {} ({}), 审批人ID={}, 流程实例={}",
|
|
|
|
|
+ entity.getCustomerName(), entity.getMobile(), approverId, result.getData());
|
|
|
|
|
+ return result.getData();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public boolean requiresApproverSelection() {
|
|
|
|
|
+ FlowResult<Map<String, Object>> current = flowClient.getModelByKey(MODEL_KEY);
|
|
|
|
|
+ Map<String, Object> model = current == null ? null : current.getData();
|
|
|
|
|
+ String bpmnXml = model == null ? null : Objects.toString(model.get("bpmnXml"), "");
|
|
|
|
|
+ if (!StringUtils.hasText(bpmnXml)) {
|
|
|
|
|
+ // 模型尚未创建:种子模板为候选角色,无需选人
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ String taskTag = findUserTaskTag(bpmnXml, CreditCustomerFlowDefinition.NODE_BOSS_APPROVE);
|
|
|
|
|
+ if (taskTag == null) {
|
|
|
|
|
+ // 节点不存在等同什么都没配置,提交端兜底选人
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ // 配置了候选角色/候选用户(角色、岗位、部门等在设计器中均落到候选组/候选人)→ 自动分配
|
|
|
|
|
+ if (hasNonEmptyAttribute(taskTag, "flowable:candidateGroups")
|
|
|
|
|
+ || hasNonEmptyAttribute(taskTag, "flowable:candidateUsers")) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ String assignee = readTagAttribute(taskTag, "flowable:assignee");
|
|
|
|
|
+ if (assignee == null || assignee.isBlank()) {
|
|
|
|
|
+ // 无任何办理人配置 → 需要提交端选人
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!assignee.contains("${")) {
|
|
|
|
|
+ // 固定办理人 → 自动分配
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ // 表达式:仅 bossId 变量依赖提交时选人;发起人/上级/负责人等表达式由引擎自行解析
|
|
|
|
|
+ return assignee.contains(CreditCustomerFlowDefinition.VAR_BOSS_ID);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** 判断节点开标签上指定属性是否存在且非空 */
|
|
|
|
|
+ private boolean hasNonEmptyAttribute(String tag, String name) {
|
|
|
|
|
+ String value = readTagAttribute(tag, name);
|
|
|
|
|
+ return value != null && !value.isBlank();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** 提取 BPMN XML 中指定 userTask 节点的开标签(找不到返回 null) */
|
|
|
|
|
+ private String findUserTaskTag(String xml, String nodeId) {
|
|
|
|
|
+ String marker = "<bpmn:userTask";
|
|
|
|
|
+ int index = xml.indexOf(marker);
|
|
|
|
|
+ while (index >= 0) {
|
|
|
|
|
+ int end = xml.indexOf('>', index);
|
|
|
|
|
+ if (end < 0) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ String tag = xml.substring(index, end);
|
|
|
|
|
+ if (tag.contains("id=\"" + nodeId + "\"")) {
|
|
|
|
|
+ return tag;
|
|
|
|
|
+ }
|
|
|
|
|
+ index = xml.indexOf(marker, end);
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private String readTagAttribute(String tag, String name) {
|
|
|
|
|
+ String prefix = name + "=\"";
|
|
|
|
|
+ int start = tag.indexOf(prefix);
|
|
|
|
|
+ if (start < 0) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ int valueStart = start + prefix.length();
|
|
|
|
|
+ int valueEnd = tag.indexOf('"', valueStart);
|
|
|
|
|
+ return valueEnd < 0 ? null : tag.substring(valueStart, valueEnd);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
|
|
+ public CreditCustomerVO saveTaskFields(CreditCustomerTaskSaveDTO dto) {
|
|
|
|
|
+ if (dto == null) {
|
|
|
|
|
+ throw new BusinessException("保存参数不能为空");
|
|
|
|
|
+ }
|
|
|
|
|
+ CreditCustomer entity = resolveTaskEntity(dto);
|
|
|
|
|
+ String taskDefKey = StringUtils.hasText(dto.getTaskDefKey()) ? dto.getTaskDefKey() : "";
|
|
|
|
|
+ if (CreditCustomerFlowDefinition.NODE_BOSS_APPROVE.equals(taskDefKey)) {
|
|
|
|
|
+ ensureApprovalState(entity, dto);
|
|
|
|
|
+ entity.setBossRemark(dto.getBossRemark());
|
|
|
|
|
+ } else if (CreditCustomerFlowDefinition.NODE_APPLICANT_MODIFY.equals(taskDefKey)) {
|
|
|
|
|
+ ensureApplicantModifyState(entity, dto);
|
|
|
|
|
+ applyModifyFields(entity, dto);
|
|
|
|
|
+ entity.setApplicantModifyRemark(dto.getApplicantModifyRemark());
|
|
|
|
|
+ } else {
|
|
|
|
|
+ throw new BusinessException("当前节点不允许保存挂账客户字段: " + taskDefKey);
|
|
|
|
|
+ }
|
|
|
|
|
+ creditCustomerMapper.updateById(entity);
|
|
|
|
|
+ return creditDetail(entity.getId());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public CreditCustomerFlowInitVO ensureFlowModel() {
|
|
|
|
|
+ String bpmnXml = CreditCustomerFlowBpmn.build();
|
|
|
|
|
+ FlowResult<Map<String, Object>> current = flowClient.getModelByKey(MODEL_KEY);
|
|
|
|
|
+ Map<String, Object> model = current == null ? null : current.getData();
|
|
|
|
|
+ boolean created = model == null || model.get("id") == null;
|
|
|
|
|
+ boolean shouldDeploy = created;
|
|
|
|
|
+ boolean preserved = false;
|
|
|
|
|
+
|
|
|
|
|
+ if (created) {
|
|
|
|
|
+ FlowResult<Map<String, Object>> createdResult = flowClient.createModel(buildFlowModelPayload(null, bpmnXml));
|
|
|
|
|
+ if (createdResult == null || !createdResult.isSuccess() || createdResult.getData() == null) {
|
|
|
|
|
+ throw new BusinessException("创建挂账客户审批流程失败: "
|
|
|
|
|
+ + (createdResult == null ? "无返回结果" : createdResult.getMsg()));
|
|
|
|
|
+ }
|
|
|
|
|
+ model = createdResult.getData();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ String existingBpmnXml = Objects.toString(model.get("bpmnXml"), "");
|
|
|
|
|
+ Integer status = toInteger(model.get("status"));
|
|
|
|
|
+ shouldDeploy = status == null || status != 1
|
|
|
|
|
+ || !StringUtils.hasText(Objects.toString(model.get("deploymentId"), ""));
|
|
|
|
|
+ if (!StringUtils.hasText(existingBpmnXml)) {
|
|
|
|
|
+ FlowResult<Map<String, Object>> updatedResult = flowClient.updateModel(
|
|
|
|
|
+ buildFlowModelPayload(model.get("id"), bpmnXml));
|
|
|
|
|
+ if (updatedResult == null || !updatedResult.isSuccess() || updatedResult.getData() == null) {
|
|
|
|
|
+ throw new BusinessException("更新挂账客户审批流程失败: "
|
|
|
|
|
+ + (updatedResult == null ? "无返回结果" : updatedResult.getMsg()));
|
|
|
|
|
+ }
|
|
|
|
|
+ model = updatedResult.getData();
|
|
|
|
|
+ shouldDeploy = true;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ preserved = true;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ String deploymentId = Objects.toString(model.get("deploymentId"), null);
|
|
|
|
|
+ if (shouldDeploy) {
|
|
|
|
|
+ FlowResult<String> deployedResult = flowClient.deployModel(Objects.toString(model.get("id"), ""));
|
|
|
|
|
+ if (deployedResult == null || !deployedResult.isSuccess()) {
|
|
|
|
|
+ throw new BusinessException("发布挂账客户审批流程失败: "
|
|
|
|
|
+ + (deployedResult == null ? "无返回结果" : deployedResult.getMsg()));
|
|
|
|
|
+ }
|
|
|
|
|
+ deploymentId = deployedResult.getData();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ CreditCustomerFlowInitVO vo = new CreditCustomerFlowInitVO();
|
|
|
|
|
+ vo.setModelKey(MODEL_KEY);
|
|
|
|
|
+ vo.setModelId(Objects.toString(model.get("id"), null));
|
|
|
|
|
+ vo.setDeploymentId(deploymentId);
|
|
|
|
|
+ vo.setStatus(shouldDeploy ? 1 : toInteger(model.get("status")));
|
|
|
|
|
+ vo.setMessage(shouldDeploy
|
|
|
|
|
+ ? (preserved ? "挂账客户审批流程已发布,已保留流程设计器中的节点配置" : "挂账客户审批流程已初始化并发布")
|
|
|
|
|
+ : (preserved ? "挂账客户审批流程已存在,已保留流程设计器中的节点配置" : "挂账客户审批流程已存在,无需重复发布"));
|
|
|
|
|
+ return vo;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 流程事件回调:维护业务审批状态(幂等)。
|
|
|
|
|
+ */
|
|
|
|
|
+ @FlowCallback(on = {
|
|
|
|
|
+ FlowCallback.ON_TASK_CREATED,
|
|
|
|
|
+ FlowCallback.ON_TASK_COMPLETED,
|
|
|
|
|
+ FlowCallback.ON_COMPLETED,
|
|
|
|
|
+ FlowCallback.ON_REJECTED,
|
|
|
|
|
+ FlowCallback.ON_CANCELED
|
|
|
|
|
+ })
|
|
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
|
|
+ public void handleFlowEvent(FlowEventContext context) {
|
|
|
|
|
+ if (context == null || !StringUtils.hasText(context.getBusinessKey())) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ CreditCustomer entity = creditCustomerMapper.selectByBusinessKey(
|
|
|
|
|
+ resolveTenantId(context.getTenantId()), context.getBusinessKey());
|
|
|
|
|
+ if (entity == null) {
|
|
|
|
|
+ log.warn("挂账客户流程回调未找到业务记录: event={}, businessKey={}",
|
|
|
|
|
+ context.getEvent(), context.getBusinessKey());
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (FlowCallback.ON_TASK_COMPLETED.equals(context.getEvent())) {
|
|
|
|
|
+ handleTaskCompleted(entity, context);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (FlowCallback.ON_TASK_CREATED.equals(context.getEvent())) {
|
|
|
|
|
+ handleTaskCreated(entity, context);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (FlowCallback.ON_COMPLETED.equals(context.getEvent())) {
|
|
|
|
|
+ markApproved(entity);
|
|
|
|
|
+ creditCustomerMapper.updateById(entity);
|
|
|
|
|
+ log.info("挂账客户审批通过: businessKey={}", entity.getBusinessKey());
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (FlowCallback.ON_REJECTED.equals(context.getEvent())) {
|
|
|
|
|
+ markRejected(entity, firstText(context.getLastComment(), context.getComment(), entity.getRejectReason()));
|
|
|
|
|
+ creditCustomerMapper.updateById(entity);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (FlowCallback.ON_CANCELED.equals(context.getEvent())) {
|
|
|
|
|
+ markCanceled(entity, firstText(context.getLastComment(), context.getComment(), entity.getRejectReason()));
|
|
|
|
|
+ creditCustomerMapper.updateById(entity);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void handleTaskCreated(CreditCustomer entity, FlowEventContext context) {
|
|
|
|
|
+ if (CreditCustomerFlowDefinition.isApplicantModifyNode(context.getTaskDefKey())) {
|
|
|
|
|
+ if (!STATUS_IN_PROCESS.equals(entity.getApprovalStatus())) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ markNeedModify(entity, firstText(context.getComment(), context.getLastComment(), entity.getRejectReason()));
|
|
|
|
|
+ creditCustomerMapper.updateById(entity);
|
|
|
|
|
+ log.info("挂账客户进入申请人修改节点,状态自动切换为待修改: businessKey={}, taskId={}",
|
|
|
|
|
+ entity.getBusinessKey(), context.getTaskId());
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (CreditCustomerFlowDefinition.isApprovalNode(context.getTaskDefKey())
|
|
|
|
|
+ && STATUS_NEED_MODIFY.equals(entity.getApprovalStatus())) {
|
|
|
|
|
+ markInProcess(entity);
|
|
|
|
|
+ creditCustomerMapper.updateById(entity);
|
|
|
|
|
+ log.info("挂账客户重新进入老板审核节点,状态自动切换为审批中: businessKey={}, taskId={}",
|
|
|
|
|
+ entity.getBusinessKey(), context.getTaskId());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void handleTaskCompleted(CreditCustomer entity, FlowEventContext context) {
|
|
|
|
|
+ copyFinishVariables(entity, context.getVariables());
|
|
|
|
|
+ if (isRejectAction(context)) {
|
|
|
|
|
+ if (CreditCustomerFlowDefinition.isApplicantModifyNode(context.getTaskDefKey())) {
|
|
|
|
|
+ markRejected(entity, firstText(context.getComment(), context.getLastComment(), entity.getRejectReason()));
|
|
|
|
|
+ } else {
|
|
|
|
|
+ markNeedModify(entity, firstText(context.getComment(), context.getLastComment(), entity.getRejectReason()));
|
|
|
|
|
+ }
|
|
|
|
|
+ creditCustomerMapper.updateById(entity);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (CreditCustomerFlowDefinition.isApplicantModifyNode(context.getTaskDefKey())) {
|
|
|
|
|
+ markInProcess(entity);
|
|
|
|
|
+ creditCustomerMapper.updateById(entity);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private boolean isRejectAction(FlowEventContext context) {
|
|
|
|
|
+ if (context == null || context.getVariables() == null || context.getVariables().isEmpty()) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ Map<String, Object> variables = context.getVariables();
|
|
|
|
|
+ String approvalResult = Objects.toString(
|
|
|
|
|
+ variables.get(CreditCustomerFlowDefinition.VAR_APPROVAL_RESULT), null);
|
|
|
|
|
+ if (CreditCustomerFlowDefinition.isRejectAction(approvalResult)) {
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ Object approved = variables.get("approved");
|
|
|
|
|
+ if (approved instanceof Boolean) {
|
|
|
|
|
+ return !(Boolean) approved;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (approved != null) {
|
|
|
|
|
+ return "false".equalsIgnoreCase(String.valueOf(approved)) || "0".equals(String.valueOf(approved));
|
|
|
|
|
+ }
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void copyFinishVariables(CreditCustomer entity, Map<String, Object> variables) {
|
|
|
|
|
+ if (variables == null || variables.isEmpty()) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ Object bossRemark = variables.get(CreditCustomerFlowDefinition.FIELD_BOSS_REMARK);
|
|
|
|
|
+ if (bossRemark != null && StringUtils.hasText(String.valueOf(bossRemark))) {
|
|
|
|
|
+ entity.setBossRemark(String.valueOf(bossRemark));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ==================== 状态机与对账修复 ====================
|
|
|
|
|
+
|
|
|
|
|
+ private void ensureApprovalState(CreditCustomer entity, CreditCustomerTaskSaveDTO dto) {
|
|
|
|
|
+ if (STATUS_NEED_MODIFY.equals(entity.getApprovalStatus())
|
|
|
|
|
+ && dto != null
|
|
|
|
|
+ && CreditCustomerFlowDefinition.isApprovalNode(dto.getTaskDefKey())) {
|
|
|
|
|
+ markInProcess(entity);
|
|
|
|
|
+ log.info("挂账客户审批节点保存前修复审批中状态: businessKey={}, taskId={}",
|
|
|
|
|
+ entity.getBusinessKey(), dto.getTaskId());
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!STATUS_IN_PROCESS.equals(entity.getApprovalStatus())) {
|
|
|
|
|
+ throw new BusinessException("当前挂账客户不是审批中状态,不能保存审批节点字段");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void ensureApplicantModifyState(CreditCustomer entity, CreditCustomerTaskSaveDTO dto) {
|
|
|
|
|
+ if (STATUS_IN_PROCESS.equals(entity.getApprovalStatus())
|
|
|
|
|
+ && dto != null
|
|
|
|
|
+ && CreditCustomerFlowDefinition.isApplicantModifyNode(dto.getTaskDefKey())) {
|
|
|
|
|
+ markNeedModify(entity, entity.getRejectReason());
|
|
|
|
|
+ log.info("挂账客户申请人修改节点保存前修复待修改状态: businessKey={}, taskId={}",
|
|
|
|
|
+ entity.getBusinessKey(), dto.getTaskId());
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!STATUS_NEED_MODIFY.equals(entity.getApprovalStatus())) {
|
|
|
|
|
+ throw new BusinessException("当前挂账客户不是待修改状态,不能执行申请人修改节点");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void reconcileStatusesWithActiveTasks(List<CreditCustomerVO> records) {
|
|
|
|
|
+ if (records == null || records.isEmpty()) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ Set<String> businessKeys = new LinkedHashSet<>();
|
|
|
|
|
+ for (CreditCustomerVO record : records) {
|
|
|
|
|
+ if (record == null || !isRunningStatus(record.getApprovalStatus())
|
|
|
|
|
+ || !StringUtils.hasText(record.getBusinessKey())) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ businessKeys.add(record.getBusinessKey());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (businessKeys.isEmpty()) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ List<Map<String, Object>> activeTasks =
|
|
|
|
|
+ creditCustomerMapper.selectActiveTaskDefKeysByBusinessKeys(resolveTenantId(), businessKeys);
|
|
|
|
|
+ if (activeTasks == null || activeTasks.isEmpty()) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ Map<String, String> activeTaskIndex = new LinkedHashMap<>();
|
|
|
|
|
+ for (Map<String, Object> task : activeTasks) {
|
|
|
|
|
+ String businessKey = Objects.toString(task.get("businessKey"), "");
|
|
|
|
|
+ String taskDefKey = Objects.toString(task.get("taskDefKey"), "");
|
|
|
|
|
+ if (StringUtils.hasText(businessKey) && StringUtils.hasText(taskDefKey)
|
|
|
|
|
+ && !activeTaskIndex.containsKey(businessKey)) {
|
|
|
|
|
+ activeTaskIndex.put(businessKey, taskDefKey);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ for (CreditCustomerVO record : records) {
|
|
|
|
|
+ if (record == null || !isRunningStatus(record.getApprovalStatus())) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ String taskDefKey = activeTaskIndex.get(record.getBusinessKey());
|
|
|
|
|
+ String expectedStatus = expectedStatusByActiveTask(taskDefKey);
|
|
|
|
|
+ if (!StringUtils.hasText(expectedStatus) || Objects.equals(expectedStatus, record.getApprovalStatus())) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ CreditCustomer update = new CreditCustomer();
|
|
|
|
|
+ update.setId(record.getId());
|
|
|
|
|
+ update.setApprovalStatus(expectedStatus);
|
|
|
|
|
+ creditCustomerMapper.updateById(update);
|
|
|
|
|
+ record.setApprovalStatus(expectedStatus);
|
|
|
|
|
+ log.info("挂账客户状态按当前待办节点对账修复: businessKey={}, taskDefKey={}, status={}",
|
|
|
|
|
+ record.getBusinessKey(), taskDefKey, expectedStatus);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private boolean isRunningStatus(String status) {
|
|
|
|
|
+ return STATUS_IN_PROCESS.equals(status) || STATUS_NEED_MODIFY.equals(status);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private String expectedStatusByActiveTask(String taskDefKey) {
|
|
|
|
|
+ if (CreditCustomerFlowDefinition.isApplicantModifyNode(taskDefKey)) {
|
|
|
|
|
+ return STATUS_NEED_MODIFY;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (CreditCustomerFlowDefinition.isApprovalNode(taskDefKey)) {
|
|
|
|
|
+ return STATUS_IN_PROCESS;
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void markInProcess(CreditCustomer entity) {
|
|
|
|
|
+ entity.setApprovalStatus(STATUS_IN_PROCESS);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void markNeedModify(CreditCustomer entity, String rejectReason) {
|
|
|
|
|
+ entity.setApprovalStatus(STATUS_NEED_MODIFY);
|
|
|
|
|
+ entity.setRejectReason(rejectReason);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void markApproved(CreditCustomer entity) {
|
|
|
|
|
+ entity.setApprovalStatus(STATUS_APPROVED);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void markRejected(CreditCustomer entity, String rejectReason) {
|
|
|
|
|
+ entity.setApprovalStatus(STATUS_REJECTED);
|
|
|
|
|
+ entity.setRejectReason(rejectReason);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void markCanceled(CreditCustomer entity, String rejectReason) {
|
|
|
|
|
+ entity.setApprovalStatus(STATUS_CANCELED);
|
|
|
|
|
+ entity.setRejectReason(rejectReason);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ==================== 私有辅助 ====================
|
|
|
|
|
+
|
|
|
|
|
+ private void applyModifyFields(CreditCustomer entity, CreditCustomerTaskSaveDTO dto) {
|
|
|
|
|
+ if (StringUtils.hasText(dto.getCustomerName())) {
|
|
|
|
|
+ entity.setCustomerName(dto.getCustomerName().trim());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (StringUtils.hasText(dto.getMobile()) && !dto.getMobile().equals(entity.getMobile())) {
|
|
|
|
|
+ int mobileCount = creditCustomerMapper.checkMobileExists(entity.getTenantId(), dto.getMobile(), entity.getId());
|
|
|
|
|
+ if (mobileCount > 0) {
|
|
|
|
|
+ throw new BusinessException("手机号已存在,请检查");
|
|
|
|
|
+ }
|
|
|
|
|
+ entity.setMobile(dto.getMobile().trim());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (StringUtils.hasText(dto.getBirthday())) {
|
|
|
|
|
+ entity.setBirthday(LocalDate.parse(dto.getBirthday()));
|
|
|
|
|
+ }
|
|
|
|
|
+ if (dto.getIdCard() != null) {
|
|
|
|
|
+ entity.setIdCard(dto.getIdCard());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (dto.getOccupation() != null) {
|
|
|
|
|
+ entity.setOccupation(dto.getOccupation());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (dto.getCompanyName() != null) {
|
|
|
|
|
+ entity.setCompanyName(dto.getCompanyName());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (dto.getAddress() != null) {
|
|
|
|
|
+ entity.setAddress(dto.getAddress());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (dto.getEmergencyContact() != null) {
|
|
|
|
|
+ entity.setEmergencyContact(dto.getEmergencyContact());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (dto.getEmergencyContactPhone() != null) {
|
|
|
|
|
+ entity.setEmergencyContactPhone(dto.getEmergencyContactPhone());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (dto.getInvoiceTitle() != null) {
|
|
|
|
|
+ entity.setInvoiceTitle(dto.getInvoiceTitle());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (dto.getTaxNo() != null) {
|
|
|
|
|
+ entity.setTaxNo(dto.getTaxNo());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (dto.getBankName() != null) {
|
|
|
|
|
+ entity.setBankName(dto.getBankName());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (dto.getBankAccount() != null) {
|
|
|
|
|
+ entity.setBankAccount(dto.getBankAccount());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (dto.getInvoiceAddressPhone() != null) {
|
|
|
|
|
+ entity.setInvoiceAddressPhone(dto.getInvoiceAddressPhone());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (dto.getRemark() != null) {
|
|
|
|
|
+ entity.setRemark(dto.getRemark());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private CreditCustomer resolveTaskEntity(CreditCustomerTaskSaveDTO dto) {
|
|
|
|
|
+ if (dto.getId() != null) {
|
|
|
|
|
+ return requireEntity(dto.getId());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (StringUtils.hasText(dto.getBusinessKey())) {
|
|
|
|
|
+ CreditCustomer entity = creditCustomerMapper.selectByBusinessKey(resolveTenantId(), dto.getBusinessKey());
|
|
|
|
|
+ if (entity != null) {
|
|
|
|
|
+ return entity;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ throw new BusinessException("未找到待办关联的挂账客户");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private CreditCustomer requireEntity(Long id) {
|
|
|
|
|
+ if (id == null) {
|
|
|
|
|
+ throw new BusinessException("客户ID不能为空");
|
|
|
|
|
+ }
|
|
|
|
|
+ CreditCustomer entity = creditCustomerMapper.selectById(id);
|
|
|
|
|
+ if (entity == null || !Objects.equals(entity.getTenantId(), resolveTenantId())) {
|
|
|
|
|
+ throw new BusinessException("挂账客户不存在");
|
|
|
|
|
+ }
|
|
|
|
|
+ return entity;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private Map<String, Object> buildFlowModelPayload(Object id, String bpmnXml) {
|
|
|
|
|
+ return CreditCustomerFlowDefinition.flowModelPayload(id, resolveTenantId(), bpmnXml);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private String buildConfiguredFlowTitle(CreditCustomer entity,
|
|
|
|
|
+ String businessKey,
|
|
|
|
|
+ Map<String, Object> variables) {
|
|
|
|
|
+ String fallback = "挂账客户审批-" + entity.getCustomerName();
|
|
|
|
|
+ JSONObject bindingConfig = loadFlowBindingConfig();
|
|
|
|
|
+ String template = trimToNull(bindingConfig.getString("titleTemplate"));
|
|
|
|
|
+ if (template == null) {
|
|
|
|
|
+ return fallback;
|
|
|
|
|
+ }
|
|
|
|
|
+ Map<String, Object> titleData = new LinkedHashMap<>();
|
|
|
|
|
+ if (variables != null) {
|
|
|
|
|
+ titleData.putAll(variables);
|
|
|
|
|
+ }
|
|
|
|
|
+ titleData.put("id", entity.getId());
|
|
|
|
|
+ titleData.put("businessKey", businessKey);
|
|
|
|
|
+ titleData.put("objectCode", BUSINESS_TYPE);
|
|
|
|
|
+ titleData.put("businessType", BUSINESS_TYPE);
|
|
|
|
|
+ titleData.put("modelKey", MODEL_KEY);
|
|
|
|
|
+ titleData.put(CreditCustomerFlowDefinition.FIELD_CUSTOMER_NAME, entity.getCustomerName());
|
|
|
|
|
+ titleData.put(CreditCustomerFlowDefinition.FIELD_MOBILE, entity.getMobile());
|
|
|
|
|
+ titleData.put(CreditCustomerFlowDefinition.FIELD_COMPANY_NAME, entity.getCompanyName());
|
|
|
|
|
+ titleData.put(CreditCustomerFlowDefinition.FIELD_APPLICANT_NAME, entity.getApplicantName());
|
|
|
|
|
+ String title = template;
|
|
|
|
|
+ for (Map.Entry<String, Object> entry : titleData.entrySet()) {
|
|
|
|
|
+ title = replaceTemplateValue(title, entry.getKey(), entry.getValue());
|
|
|
|
|
+ }
|
|
|
|
|
+ return StringUtils.hasText(title) ? title : fallback;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private JSONObject loadFlowBindingConfig() {
|
|
|
|
|
+ AiBusinessBinding binding = businessBindingMapper.selectBindingByTypeAndCode(
|
|
|
|
|
+ resolveTenantId(), "OBJECT", BUSINESS_TYPE, "FLOW");
|
|
|
|
|
+ if (binding == null || !StringUtils.hasText(binding.getBindingConfig())) {
|
|
|
|
|
+ return new JSONObject();
|
|
|
|
|
+ }
|
|
|
|
|
+ try {
|
|
|
|
|
+ return JSON.parseObject(binding.getBindingConfig());
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.warn("挂账客户流程标题模板配置解析失败: bindingId={}", binding.getId(), e);
|
|
|
|
|
+ return new JSONObject();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private String replaceTemplateValue(String template, String key, Object value) {
|
|
|
|
|
+ if (!StringUtils.hasText(template) || !StringUtils.hasText(key)) {
|
|
|
|
|
+ return template;
|
|
|
|
|
+ }
|
|
|
|
|
+ String text = value == null ? "" : String.valueOf(value);
|
|
|
|
|
+ return template.replace("${" + key + "}", text).replace("{" + key + "}", text);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private String trimToNull(String value) {
|
|
|
|
|
+ if (!StringUtils.hasText(value)) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ return value.trim();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private String resolveUsername() {
|
|
|
|
|
+ return StringUtils.hasText(SessionHelper.getUsername()) ? SessionHelper.getUsername() : "system";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
private Long resolveTenantId() {
|
|
private Long resolveTenantId() {
|
|
|
|
|
+ return resolveTenantId(null);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private Long resolveTenantId(Long eventTenantId) {
|
|
|
|
|
+ if (eventTenantId != null) {
|
|
|
|
|
+ return eventTenantId;
|
|
|
|
|
+ }
|
|
|
Long tenantId = TenantContextHolder.getTenantId();
|
|
Long tenantId = TenantContextHolder.getTenantId();
|
|
|
if (tenantId != null) {
|
|
if (tenantId != null) {
|
|
|
return tenantId;
|
|
return tenantId;
|
|
@@ -162,4 +837,30 @@ public class CreditCustomerServiceImpl implements CreditCustomerService {
|
|
|
tenantId = SessionHelper.getTenantId();
|
|
tenantId = SessionHelper.getTenantId();
|
|
|
return tenantId == null ? 1L : tenantId;
|
|
return tenantId == null ? 1L : tenantId;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ private Integer toInteger(Object value) {
|
|
|
|
|
+ if (value instanceof Number) {
|
|
|
|
|
+ return ((Number) value).intValue();
|
|
|
|
|
+ }
|
|
|
|
|
+ if (value == null) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ try {
|
|
|
|
|
+ return Integer.parseInt(String.valueOf(value));
|
|
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private String firstText(String... values) {
|
|
|
|
|
+ if (values == null) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ for (String value : values) {
|
|
|
|
|
+ if (StringUtils.hasText(value)) {
|
|
|
|
|
+ return value;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|