diff --git a/ruoyi-api/pom.xml b/ruoyi-api/pom.xml
index e0e99eba..3b6f6c32 100644
--- a/ruoyi-api/pom.xml
+++ b/ruoyi-api/pom.xml
@@ -14,6 +14,7 @@
ruoyi-api-resource
ruoyi-api-workflow
ruoyi-api-wms
+ ruoyi-api-oa
ruoyi-api
diff --git a/ruoyi-api/ruoyi-api-bom/pom.xml b/ruoyi-api/ruoyi-api-bom/pom.xml
index a3283eb5..ec33893c 100644
--- a/ruoyi-api/ruoyi-api-bom/pom.xml
+++ b/ruoyi-api/ruoyi-api-bom/pom.xml
@@ -48,6 +48,13 @@
${revision}
+
+
+ org.dromara
+ ruoyi-api-oa
+ ${revision}
+
+
diff --git a/ruoyi-api/ruoyi-api-oa/pom.xml b/ruoyi-api/ruoyi-api-oa/pom.xml
new file mode 100644
index 00000000..3c2838ef
--- /dev/null
+++ b/ruoyi-api/ruoyi-api-oa/pom.xml
@@ -0,0 +1,27 @@
+
+
+
+ org.dromara
+ ruoyi-api
+ ${revision}
+
+ 4.0.0
+
+ ruoyi-api-oa
+
+
+ ruoyi-api-oa OA接口模块
+
+
+
+
+
+ org.dromara
+ ruoyi-common-core
+
+
+
+
+
diff --git a/ruoyi-api/ruoyi-api-oa/src/main/java/org/dromara/oa/api/RemoteErpContractOrderService.java b/ruoyi-api/ruoyi-api-oa/src/main/java/org/dromara/oa/api/RemoteErpContractOrderService.java
new file mode 100644
index 00000000..94fc5e7f
--- /dev/null
+++ b/ruoyi-api/ruoyi-api-oa/src/main/java/org/dromara/oa/api/RemoteErpContractOrderService.java
@@ -0,0 +1,19 @@
+package org.dromara.oa.api;
+
+/**
+ * 合同订单远程服务
+ *
+ * @author Codex
+ */
+public interface RemoteErpContractOrderService {
+
+ /**
+ * 刷新合同订单台账业务状态
+ *
+ * @param contractId 合同ID(采购/发货/开票/回款按合同汇总时必填)
+ * @param projectId 项目ID(回款等按单项目刷新时传入,可与 contractId 配合使用)
+ * @param statusType 状态类型:purchase / delivery / invoice / payment,见 {@link org.dromara.oa.api.enums.ContractOrderStatusTypeEnum}
+ */
+ void refreshContractOrderStatus(Long contractId, Long projectId, String statusType);
+
+}
diff --git a/ruoyi-api/ruoyi-api-wms/src/main/java/org/dromara/wms/api/RemoteWmsShippingBillService.java b/ruoyi-api/ruoyi-api-wms/src/main/java/org/dromara/wms/api/RemoteWmsShippingBillService.java
index a80b6343..3c39d6b1 100644
--- a/ruoyi-api/ruoyi-api-wms/src/main/java/org/dromara/wms/api/RemoteWmsShippingBillService.java
+++ b/ruoyi-api/ruoyi-api-wms/src/main/java/org/dromara/wms/api/RemoteWmsShippingBillService.java
@@ -17,4 +17,12 @@ public interface RemoteWmsShippingBillService {
*/
Long createDraftByProject(RemoteWmsShippingDraft draft);
+ /**
+ * 根据合同下已完成发货单的 is_all_receiving 计算合同订单发货状态
+ *
+ * @param contractId 合同ID
+ * @return 发货状态:1未发货 2部分发货 3已发货
+ */
+ String resolveOrderDeliveryStatusByContractId(Long contractId);
+
}
diff --git a/ruoyi-modules/ruoyi-oa/pom.xml b/ruoyi-modules/ruoyi-oa/pom.xml
index 33e7e93b..873ea955 100644
--- a/ruoyi-modules/ruoyi-oa/pom.xml
+++ b/ruoyi-modules/ruoyi-oa/pom.xml
@@ -110,6 +110,11 @@
ruoyi-api-wms
+
+ org.dromara
+ ruoyi-api-oa
+
+
org.dromara
ruoyi-common-bus
diff --git a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/dubbo/RemoteErpContractOrderServiceImpl.java b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/dubbo/RemoteErpContractOrderServiceImpl.java
new file mode 100644
index 00000000..129e6a9b
--- /dev/null
+++ b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/dubbo/RemoteErpContractOrderServiceImpl.java
@@ -0,0 +1,26 @@
+package org.dromara.oa.dubbo;
+
+import lombok.RequiredArgsConstructor;
+import org.apache.dubbo.config.annotation.DubboService;
+import org.dromara.oa.api.RemoteErpContractOrderService;
+import org.dromara.oa.erp.service.IErpContractOrderService;
+import org.springframework.stereotype.Service;
+
+/**
+ * 合同订单远程服务实现
+ *
+ * @author Codex
+ */
+@RequiredArgsConstructor
+@Service
+@DubboService
+public class RemoteErpContractOrderServiceImpl implements RemoteErpContractOrderService {
+
+ private final IErpContractOrderService erpContractOrderService;
+
+ @Override
+ public void refreshContractOrderStatus(Long contractId, Long projectId, String statusType) {
+ erpContractOrderService.refreshContractOrderStatus(contractId, projectId, statusType);
+ }
+
+}
diff --git a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/service/IErpContractOrderService.java b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/service/IErpContractOrderService.java
index adfbcc7c..95d77541 100644
--- a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/service/IErpContractOrderService.java
+++ b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/service/IErpContractOrderService.java
@@ -80,5 +80,22 @@ public interface IErpContractOrderService {
* @return 合同订单(项目信息)
*/
ErpProjectInfoVo submitContractOrderAndFlowStart(ErpProjectInfoBo bo);
+
+ /**
+ * 刷新合同订单台账业务状态(采购/发货/开票/回款)
+ *
+ * @param contractId 合同ID
+ * @param projectId 项目ID(回款按项目刷新时传入)
+ * @param statusType 状态类型:purchase / delivery / invoice / payment
+ */
+ void refreshContractOrderStatus(Long contractId, Long projectId, String statusType);
+
+ /**
+ * 根据项目ID解析关联合同ID
+ *
+ * @param projectId 实施项目或合同订单项目ID
+ * @return 合同ID
+ */
+ Long resolveContractIdByProjectId(Long projectId);
}
diff --git a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/service/IErpProjectPlanService.java b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/service/IErpProjectPlanService.java
index ba67c7de..051c9962 100644
--- a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/service/IErpProjectPlanService.java
+++ b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/service/IErpProjectPlanService.java
@@ -112,4 +112,18 @@ public interface IErpProjectPlanService {
*/
void syncCollectionStageFromInstallmentDetail(Long contractId, Long projectId, Long paymentStageId,
Date receivableDate);
+
+ /**
+ * 按合同刷新其下全部合同订单的回款比例(order_payment_rate)
+ *
+ * @param contractId 合同ID
+ */
+ void refreshContractOrderPaymentRateByContractId(Long contractId);
+
+ /**
+ * 按项目刷新合同订单回款比例
+ *
+ * @param projectId 合同订单项目ID
+ */
+ void refreshContractOrderPaymentRateByProjectId(Long projectId);
}
diff --git a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/service/impl/ErpContractOrderServiceImpl.java b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/service/impl/ErpContractOrderServiceImpl.java
index 2bf3542a..0701a5da 100644
--- a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/service/impl/ErpContractOrderServiceImpl.java
+++ b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/service/impl/ErpContractOrderServiceImpl.java
@@ -2,6 +2,7 @@ package org.dromara.oa.erp.service.impl;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.seata.spring.annotation.GlobalTransactional;
@@ -12,8 +13,13 @@ import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.tenant.helper.TenantHelper;
+import org.dromara.oa.api.constant.OrderInvoiceStatusConstant;
+import org.dromara.oa.api.constant.OrderPurchaseStatusConstant;
+import org.dromara.oa.api.enums.ContractOrderStatusTypeEnum;
import org.dromara.oa.erp.domain.ErpContractInfo;
+import org.dromara.oa.erp.domain.ErpFinInvoiceInfo;
import org.dromara.oa.erp.domain.ErpProjectInfo;
+import org.dromara.oa.erp.domain.ErpProjectPurchase;
import org.dromara.oa.erp.domain.ErpProjectContracts;
import org.dromara.oa.erp.domain.ErpProjectPlan;
import org.dromara.oa.erp.domain.ErpProjectPlanStage;
@@ -25,8 +31,10 @@ import org.dromara.oa.erp.domain.vo.ErpContractOrderPurchaseMaterialVo;
import org.dromara.oa.erp.domain.vo.ErpProjectInfoVo;
import org.dromara.oa.erp.mapper.ErpContractInfoMapper;
import org.dromara.oa.erp.mapper.ErpContractMaterialMapper;
+import org.dromara.oa.erp.mapper.ErpFinInvoiceInfoMapper;
import org.dromara.oa.erp.mapper.ErpProjectInfoMapper;
import org.dromara.oa.erp.mapper.ErpProjectContractsMapper;
+import org.dromara.oa.erp.mapper.ErpProjectPurchaseMapper;
import org.dromara.oa.erp.mapper.ErpProjectPlanMapper;
import org.dromara.oa.erp.mapper.ErpProjectPlanStageMapper;
import org.dromara.oa.crm.domain.vo.CrmCustomerInfoVo;
@@ -35,6 +43,7 @@ import org.dromara.oa.erp.service.IErpContractChangeService;
import org.dromara.oa.erp.service.IErpContractOrderService;
import org.dromara.oa.erp.service.IErpProjectContractsService;
import org.dromara.oa.erp.service.IErpProjectInfoService;
+import org.dromara.oa.erp.service.IErpProjectPlanService;
import org.dromara.oa.erp.service.IErpProjectTypeService;
import org.dromara.oa.erp.domain.vo.ErpProjectTypeVo;
import org.dromara.oa.erp.constant.ProjectCategoryConstant;
@@ -44,6 +53,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.github.yulichang.toolkit.JoinWrappers;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import org.dromara.system.api.RemoteCodeRuleService;
+import org.dromara.wms.api.RemoteWmsShippingBillService;
import org.dromara.workflow.api.RemoteWorkflowService;
import org.dromara.workflow.api.domain.RemoteStartProcess;
import org.dromara.workflow.api.domain.RemoteFlowInstanceBizExt;
@@ -52,6 +62,7 @@ import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import org.apache.dubbo.config.annotation.DubboReference;
+import java.math.BigDecimal;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -79,9 +90,12 @@ public class ErpContractOrderServiceImpl implements IErpContractOrderService {
private final ErpContractInfoMapper contractInfoMapper;
private final ErpContractMaterialMapper contractMaterialMapper;
private final ErpProjectContractsMapper projectContractsMapper;
+ private final ErpProjectPurchaseMapper projectPurchaseMapper;
+ private final ErpFinInvoiceInfoMapper finInvoiceInfoMapper;
private final IErpProjectContractsService projectContractsService;
private final IErpContractChangeService erpContractChangeService;
+ private final IErpProjectPlanService erpProjectPlanService;
private final IErpProjectInfoService projectInfoService;
private final IErpProjectTypeService projectTypeService;
private final ICrmCustomerInfoService crmCustomerInfoService;
@@ -97,6 +111,9 @@ public class ErpContractOrderServiceImpl implements IErpContractOrderService {
@DubboReference(timeout = 30000)
private RemoteCodeRuleService remoteCodeRuleService;
+ @DubboReference(timeout = 30000)
+ private RemoteWmsShippingBillService remoteWmsShippingBillService;
+
/**
* 查询合同订单(项目信息)
*
@@ -659,6 +676,263 @@ public class ErpContractOrderServiceImpl implements IErpContractOrderService {
}
}
+ /**
+ * 刷新合同订单台账业务状态(采购/发货/开票/回款)
+ */
+ @Override
+ public void refreshContractOrderStatus(Long contractId, Long projectId, String statusType) {
+ ContractOrderStatusTypeEnum type = ContractOrderStatusTypeEnum.getByCode(statusType);
+ if (type == null) {
+ log.warn("不支持的合同订单状态类型: {}", statusType);
+ return;
+ }
+ switch (type) {
+ case PAYMENT -> erpProjectPlanService.refreshContractOrderPaymentRateByContractId(contractId);
+ case DELIVERY -> refreshDeliveryStatus(contractId);
+ case PURCHASE -> refreshPurchaseStatus(contractId);
+ case INVOICE -> refreshInvoiceStatus(contractId);
+ default -> log.warn("未处理的状态类型: {}", statusType);
+ }
+ }
+
+ private void refreshDeliveryStatus(Long contractId) {
+ if (contractId == null) {
+ return;
+ }
+ String deliveryStatus = remoteWmsShippingBillService.resolveOrderDeliveryStatusByContractId(contractId);
+ updateContractOrdersField(contractId, deliveryStatus, ContractOrderStatusTypeEnum.DELIVERY);
+ }
+
+ private void refreshInvoiceStatus(Long contractId) {
+ if (contractId == null) {
+ return;
+ }
+ String invoiceStatus = resolveInvoiceStatus(contractId);
+ updateContractOrdersField(contractId, invoiceStatus, ContractOrderStatusTypeEnum.INVOICE);
+ }
+
+ private void refreshPurchaseStatus(Long contractId) {
+ if (contractId == null) {
+ return;
+ }
+ List contractOrders = listContractOrdersByContractId(contractId);
+ if (CollUtil.isEmpty(contractOrders)) {
+ log.warn("未找到合同订单,contractId={}", contractId);
+ return;
+ }
+ for (ErpProjectInfo contractOrder : contractOrders) {
+ String purchaseStatus = resolvePurchaseStatus(contractOrder);
+ if (Objects.equals(purchaseStatus, contractOrder.getOrderPurchaseStatus())) {
+ continue;
+ }
+ ErpProjectInfo update = new ErpProjectInfo();
+ update.setProjectId(contractOrder.getProjectId());
+ update.setOrderPurchaseStatus(purchaseStatus);
+ projectInfoMapper.updateById(update);
+ }
+ }
+
+ private void updateContractOrdersField(Long contractId, String statusValue, ContractOrderStatusTypeEnum type) {
+ List contractOrders = listContractOrdersByContractId(contractId);
+ if (CollUtil.isEmpty(contractOrders)) {
+ log.warn("未找到合同订单,contractId={}", contractId);
+ return;
+ }
+ for (ErpProjectInfo contractOrder : contractOrders) {
+ String current = switch (type) {
+ case DELIVERY -> contractOrder.getOrderDeliveryStatus();
+ case INVOICE -> contractOrder.getOrderInvoiceStatus();
+ default -> null;
+ };
+ if (Objects.equals(statusValue, current)) {
+ continue;
+ }
+ ErpProjectInfo update = new ErpProjectInfo();
+ update.setProjectId(contractOrder.getProjectId());
+ switch (type) {
+ case DELIVERY -> update.setOrderDeliveryStatus(statusValue);
+ case INVOICE -> update.setOrderInvoiceStatus(statusValue);
+ default -> {
+ }
+ }
+ projectInfoMapper.updateById(update);
+ }
+ }
+
+ private List listContractOrdersByContractId(Long contractId) {
+ return projectInfoMapper.selectList(Wrappers.lambdaQuery()
+ .eq(ErpProjectInfo::getContractId, contractId)
+ .eq(ErpProjectInfo::getProjectCategory, ProjectCategoryConstant.CONTRACT_ORDER)
+ .eq(ErpProjectInfo::getDelFlag, "0"));
+ }
+
+ /**
+ * 按合同下开票记录汇总计算开票状态。
+ */
+ private String resolveInvoiceStatus(Long contractId) {
+ List invoices = finInvoiceInfoMapper.selectList(Wrappers.lambdaQuery()
+ .eq(ErpFinInvoiceInfo::getContractId, contractId)
+ .eq(ErpFinInvoiceInfo::getDelFlag, "0"));
+ if (CollUtil.isEmpty(invoices)) {
+ return OrderInvoiceStatusConstant.NOT_INVOICED;
+ }
+ boolean hasCompleted = false;
+ boolean hasInProgress = false;
+ BigDecimal completedAmount = BigDecimal.ZERO;
+ for (ErpFinInvoiceInfo invoice : invoices) {
+ if (OAStatusEnum.APPROVING.getStatus().equals(invoice.getInvoiceStatus())
+ || BusinessStatusEnum.WAITING.getStatus().equals(invoice.getFlowStatus())) {
+ hasInProgress = true;
+ }
+ if (OAStatusEnum.COMPLETED.getStatus().equals(invoice.getInvoiceStatus())) {
+ hasCompleted = true;
+ if (invoice.getIssueAmount() != null) {
+ completedAmount = completedAmount.add(invoice.getIssueAmount());
+ }
+ }
+ }
+ if (!hasCompleted) {
+ return hasInProgress ? OrderInvoiceStatusConstant.PARTIAL_INVOICED : OrderInvoiceStatusConstant.NOT_INVOICED;
+ }
+ BigDecimal contractAmount = resolveContractAmount(contractId);
+ if (contractAmount.compareTo(BigDecimal.ZERO) <= 0 || completedAmount.compareTo(contractAmount) >= 0) {
+ return OrderInvoiceStatusConstant.INVOICED;
+ }
+ return OrderInvoiceStatusConstant.PARTIAL_INVOICED;
+ }
+
+ /**
+ * 按采购匹配清单与在途采购单计算采购状态。
+ */
+ private String resolvePurchaseStatus(ErpProjectInfo contractOrder) {
+ Long bizProjectId = resolvePurchaseBizProjectId(contractOrder);
+ if (bizProjectId == null) {
+ return OrderPurchaseStatusConstant.NOT_PURCHASED;
+ }
+ Long contractId = contractOrder.getContractId();
+ if (hasPurchasingInProgress(bizProjectId, contractId)) {
+ return OrderPurchaseStatusConstant.PURCHASING;
+ }
+ List materials = queryPurchaseMaterialList(bizProjectId);
+ if (CollUtil.isEmpty(materials)) {
+ return OrderPurchaseStatusConstant.NOT_PURCHASED;
+ }
+ boolean hasPurchased = false;
+ boolean allPurchased = true;
+ boolean hasRequiredMaterial = false;
+ for (ErpContractOrderPurchaseMaterialVo material : materials) {
+ if (contractId != null && material.getContractId() != null
+ && !Objects.equals(contractId, material.getContractId())) {
+ continue;
+ }
+ BigDecimal contractAmount = material.getContractAmount() == null ? BigDecimal.ZERO : material.getContractAmount();
+ if (contractAmount.compareTo(BigDecimal.ZERO) <= 0) {
+ continue;
+ }
+ hasRequiredMaterial = true;
+ BigDecimal purchasedAmount = material.getPurchasedAmount() == null ? BigDecimal.ZERO : material.getPurchasedAmount();
+ if (purchasedAmount.compareTo(BigDecimal.ZERO) > 0) {
+ hasPurchased = true;
+ }
+ BigDecimal unpurchased = material.getUnpurchasedAmount() == null ? BigDecimal.ZERO : material.getUnpurchasedAmount();
+ if (unpurchased.compareTo(BigDecimal.ZERO) > 0) {
+ allPurchased = false;
+ }
+ }
+ if (!hasRequiredMaterial || !hasPurchased) {
+ return OrderPurchaseStatusConstant.NOT_PURCHASED;
+ }
+ if (allPurchased) {
+ return OrderPurchaseStatusConstant.PURCHASED;
+ }
+ return OrderPurchaseStatusConstant.PARTIAL_PURCHASED;
+ }
+
+ private boolean hasPurchasingInProgress(Long bizProjectId, Long contractId) {
+ LambdaQueryWrapper lqw = Wrappers.lambdaQuery()
+ .eq(ErpProjectPurchase::getProjectId, bizProjectId)
+ .eq(ErpProjectPurchase::getDelFlag, "0")
+ .and(w -> w.eq(ErpProjectPurchase::getProjectPurchaseStatus, OAStatusEnum.APPROVING.getStatus())
+ .or()
+ .eq(ErpProjectPurchase::getFlowStatus, BusinessStatusEnum.WAITING.getStatus()));
+ if (contractId != null) {
+ lqw.eq(ErpProjectPurchase::getRelationId, contractId);
+ }
+ return projectPurchaseMapper.selectCount(lqw) > 0;
+ }
+
+ /**
+ * 解析采购查询用的实施项目ID(与订单台账前端口径一致)。
+ */
+ private Long resolvePurchaseBizProjectId(ErpProjectInfo contractOrder) {
+ if (contractOrder == null) {
+ return null;
+ }
+ if (!ProjectCategoryConstant.CONTRACT_ORDER.equals(contractOrder.getProjectCategory())) {
+ return contractOrder.getProjectId();
+ }
+ if (contractOrder.getParentProjectId() != null) {
+ return contractOrder.getParentProjectId();
+ }
+ Long contractId = contractOrder.getContractId();
+ if (contractId == null) {
+ return null;
+ }
+ List relations = projectContractsMapper.selectList(Wrappers.lambdaQuery()
+ .eq(ErpProjectContracts::getContractId, contractId)
+ .eq(ErpProjectContracts::getDelFlag, "0")
+ .orderByAsc(ErpProjectContracts::getSortOrder)
+ .orderByAsc(ErpProjectContracts::getProjectContractsId));
+ if (CollUtil.isEmpty(relations)) {
+ return null;
+ }
+ Set projectIds = relations.stream()
+ .map(ErpProjectContracts::getProjectId)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toSet());
+ if (CollUtil.isEmpty(projectIds)) {
+ return null;
+ }
+ List projects = projectInfoMapper.selectList(Wrappers.lambdaQuery()
+ .in(ErpProjectInfo::getProjectId, projectIds)
+ .ne(ErpProjectInfo::getProjectCategory, ProjectCategoryConstant.CONTRACT_ORDER)
+ .eq(ErpProjectInfo::getDelFlag, "0")
+ .last("limit 1"));
+ return CollUtil.isEmpty(projects) ? null : projects.get(0).getProjectId();
+ }
+
+ private BigDecimal resolveContractAmount(Long contractId) {
+ ErpContractInfo contractInfo = contractInfoMapper.selectById(contractId);
+ if (contractInfo != null && contractInfo.getTotalPrice() != null
+ && contractInfo.getTotalPrice().compareTo(BigDecimal.ZERO) > 0) {
+ return contractInfo.getTotalPrice();
+ }
+ List orders = listContractOrdersByContractId(contractId);
+ return orders.stream()
+ .map(ErpProjectInfo::getAmount)
+ .filter(Objects::nonNull)
+ .reduce(BigDecimal.ZERO, BigDecimal::add);
+ }
+
+ @Override
+ public Long resolveContractIdByProjectId(Long projectId) {
+ if (projectId == null) {
+ return null;
+ }
+ ErpProjectInfo project = projectInfoMapper.selectById(projectId);
+ if (project == null || !"0".equals(project.getDelFlag())) {
+ return null;
+ }
+ if (project.getContractId() != null) {
+ return project.getContractId();
+ }
+ ErpProjectContracts relation = projectContractsMapper.selectOne(Wrappers.lambdaQuery()
+ .eq(ErpProjectContracts::getProjectId, projectId)
+ .eq(ErpProjectContracts::getDelFlag, "0")
+ .last("limit 1"), false);
+ return relation == null ? null : relation.getContractId();
+ }
+
/**
* 总体流程监听(例如: 草稿,撤销,退回,作废,终止,已完成等)
*
diff --git a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/service/impl/ErpFinAccountInstallmentServiceImpl.java b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/service/impl/ErpFinAccountInstallmentServiceImpl.java
index aee9703f..87d44b17 100644
--- a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/service/impl/ErpFinAccountInstallmentServiceImpl.java
+++ b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/service/impl/ErpFinAccountInstallmentServiceImpl.java
@@ -25,6 +25,7 @@ import org.dromara.oa.erp.domain.vo.ErpFinAccountInstallmentVo;
import org.dromara.oa.erp.mapper.ErpFinAccountInstallmentDetailMapper;
import org.dromara.oa.erp.mapper.ErpFinAccountInstallmentMapper;
import org.dromara.oa.erp.service.IErpFinAccountInstallmentService;
+import org.dromara.oa.erp.service.IErpProjectPlanService;
import org.dromara.system.api.RemoteCodeRuleService;
import org.dromara.system.api.RemoteUserService;
import org.dromara.workflow.api.RemoteWorkflowService;
@@ -44,6 +45,7 @@ import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.stream.Collectors;
/**
@@ -68,6 +70,8 @@ public class ErpFinAccountInstallmentServiceImpl implements IErpFinAccountInstal
private final ErpFinAccountInstallmentMapper baseMapper;
private final ErpFinAccountInstallmentDetailMapper detailMapper;
+ private final IErpProjectPlanService erpProjectPlanService;
+
@DubboReference(timeout = 30000)
private RemoteCodeRuleService remoteCodeRuleService;
@@ -432,9 +436,32 @@ public class ErpFinAccountInstallmentServiceImpl implements IErpFinAccountInstal
update.setAccountInstallmentId(installmentId);
applyInstallmentStatusByFlowStatus(update, processEvent.getStatus());
baseMapper.updateById(update);
+ if (BusinessStatusEnum.FINISH.getStatus().equals(processEvent.getStatus())) {
+ refreshContractOrderPaymentStatus(installmentId);
+ }
});
}
+ private void refreshContractOrderPaymentStatus(Long installmentId) {
+ List details = detailMapper.selectList(Wrappers.lambdaQuery()
+ .eq(ErpFinAccountInstallmentDetail::getAccountInstallmentId, installmentId)
+ .eq(ErpFinAccountInstallmentDetail::getDelFlag, "0"));
+ if (CollUtil.isEmpty(details)) {
+ return;
+ }
+ details.stream()
+ .map(ErpFinAccountInstallmentDetail::getContractId)
+ .filter(Objects::nonNull)
+ .distinct()
+ .forEach(contractId -> {
+ try {
+ erpProjectPlanService.refreshContractOrderPaymentRateByContractId(contractId);
+ } catch (Exception e) {
+ log.error("刷新合同订单回款比例失败,contractId={}", contractId, e);
+ }
+ });
+ }
+
/**
* 按流程状态同步主表 flow_status、installment_status
*
diff --git a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/service/impl/ErpProjectPlanServiceImpl.java b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/service/impl/ErpProjectPlanServiceImpl.java
index ee2656fd..0f38ac02 100644
--- a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/service/impl/ErpProjectPlanServiceImpl.java
+++ b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/service/impl/ErpProjectPlanServiceImpl.java
@@ -1,5 +1,6 @@
package org.dromara.oa.erp.service.impl;
+import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.map.MapUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
@@ -421,6 +422,39 @@ public class ErpProjectPlanServiceImpl implements IErpProjectPlanService {
return updated;
}
+ @Override
+ public void refreshContractOrderPaymentRateByContractId(Long contractId) {
+ if (contractId == null) {
+ return;
+ }
+ List contractOrders = projectInfoMapper.selectList(Wrappers.lambdaQuery()
+ .eq(ErpProjectInfo::getContractId, contractId)
+ .eq(ErpProjectInfo::getProjectCategory, ProjectCategoryConstant.CONTRACT_ORDER)
+ .eq(ErpProjectInfo::getDelFlag, "0"));
+ if (CollUtil.isEmpty(contractOrders)) {
+ return;
+ }
+ for (ErpProjectInfo contractOrder : contractOrders) {
+ refreshContractOrderPaymentRateByProjectId(contractOrder.getProjectId());
+ }
+ }
+
+ @Override
+ public void refreshContractOrderPaymentRateByProjectId(Long projectId) {
+ if (projectId == null) {
+ return;
+ }
+ ErpProjectPlan plan = baseMapper.selectOne(Wrappers.lambdaQuery()
+ .eq(ErpProjectPlan::getProjectId, projectId)
+ .eq(ErpProjectPlan::getDelFlag, "0")
+ .orderByDesc(ErpProjectPlan::getCreateTime)
+ .last("limit 1"), false);
+ if (plan == null) {
+ return;
+ }
+ refreshContractOrderPaymentRate(plan.getProjectPlanId(), projectId);
+ }
+
/**
* 同步合同订单回款比例:本计划下各阶段 actual_repayment_rate(%) 相加,写入 erp_project_info.order_payment_rate
*/
diff --git a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/service/impl/ErpProjectPurchaseServiceImpl.java b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/service/impl/ErpProjectPurchaseServiceImpl.java
index c85f6aca..5c5c67b5 100644
--- a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/service/impl/ErpProjectPurchaseServiceImpl.java
+++ b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/service/impl/ErpProjectPurchaseServiceImpl.java
@@ -26,6 +26,8 @@ import org.dromara.oa.erp.domain.vo.ErpProjectPurchaseVo;
import org.dromara.oa.erp.domain.vo.ErpProjectPurchaseMaterialVo;
import org.dromara.oa.erp.mapper.ErpProjectPurchaseMapper;
import org.dromara.oa.erp.mapper.ErpProjectPurchaseMaterialMapper;
+import org.dromara.oa.api.enums.ContractOrderStatusTypeEnum;
+import org.dromara.oa.erp.service.IErpContractOrderService;
import org.dromara.oa.erp.service.IErpProjectPurchaseService;
import org.dromara.system.api.RemoteCodeRuleService;
import org.dromara.workflow.api.RemoteWorkflowService;
@@ -67,6 +69,8 @@ public class ErpProjectPurchaseServiceImpl implements IErpProjectPurchaseService
private final ErpProjectPurchaseMaterialMapper purchaseMaterialMapper;
+ private final IErpContractOrderService erpContractOrderService;
+
@DubboReference(timeout = 30000)
private RemoteWorkflowService remoteWorkflowService;
@@ -557,6 +561,17 @@ public class ErpProjectPurchaseServiceImpl implements IErpProjectPurchaseService
purchase.setProjectPurchaseStatus(OAStatusEnum.DRAFT.getStatus());
}
baseMapper.updateById(purchase);
+ if (Objects.equals(processEvent.getStatus(), BusinessStatusEnum.FINISH.getStatus())) {
+ Long contractId = erpContractOrderService.resolveContractIdByProjectId(purchase.getProjectId());
+ if (contractId != null) {
+ try {
+ erpContractOrderService.refreshContractOrderStatus(
+ contractId, null, ContractOrderStatusTypeEnum.PURCHASE.getCode());
+ } catch (Exception e) {
+ log.error("刷新合同订单采购状态失败,contractId={}", contractId, e);
+ }
+ }
+ }
});
}
}
diff --git a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/workflow/handler/OaProcessEventHandler.java b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/workflow/handler/OaProcessEventHandler.java
index 71eaea6b..865bf446 100644
--- a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/workflow/handler/OaProcessEventHandler.java
+++ b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/workflow/handler/OaProcessEventHandler.java
@@ -3,16 +3,23 @@ package org.dromara.oa.workflow.handler;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.core.enums.BusinessStatusEnum;
+import org.dromara.oa.api.enums.ContractOrderStatusTypeEnum;
+import org.dromara.oa.erp.domain.ErpFinInvoiceInfo;
+import org.dromara.oa.erp.mapper.ErpFinInvoiceInfoMapper;
import org.dromara.oa.erp.mapper.OaUniversalMapper;
+import org.dromara.oa.erp.service.IErpContractOrderService;
import org.dromara.workflow.api.event.ProcessEvent;
import org.dromara.workflow.enums.FlowConfigEnum;
import org.dromara.workflow.event.AbstractProcessEventHandler;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
+import cn.hutool.core.convert.Convert;
import java.util.HashMap;
import java.util.Map;
+import java.util.Objects;
/**
* @Author xins
@@ -24,6 +31,8 @@ import java.util.Map;
@RequiredArgsConstructor
public class OaProcessEventHandler extends AbstractProcessEventHandler {
private final OaUniversalMapper oaUniversalMapper;
+ private final ErpFinInvoiceInfoMapper finInvoiceInfoMapper;
+ private final IErpContractOrderService erpContractOrderService;
@EventListener(condition = "#processEvent.flowCode.startsWith('HWOA')")
@Transactional(rollbackFor = Exception.class)
@@ -42,7 +51,24 @@ public class OaProcessEventHandler extends AbstractProcessEventHandler {
int updateCount = handleProcessEvent(processEvent);
+ if (updateCount > 0
+ && Objects.equals(processEvent.getStatus(), BusinessStatusEnum.FINISH.getStatus())
+ && FlowConfigEnum.INVOICE.getFlowCode().equals(flowCode)) {
+ refreshContractOrderInvoiceStatus(processEvent);
+ }
+ }
+ private void refreshContractOrderInvoiceStatus(ProcessEvent processEvent) {
+ ErpFinInvoiceInfo invoice = finInvoiceInfoMapper.selectById(Convert.toLong(processEvent.getBusinessId()));
+ if (invoice == null || invoice.getContractId() == null) {
+ return;
+ }
+ try {
+ erpContractOrderService.refreshContractOrderStatus(
+ invoice.getContractId(), null, ContractOrderStatusTypeEnum.INVOICE.getCode());
+ } catch (Exception e) {
+ log.error("刷新合同订单开票状态失败,contractId={}", invoice.getContractId(), e);
+ }
}
@Override
diff --git a/ruoyi-modules/ruoyi-wms/pom.xml b/ruoyi-modules/ruoyi-wms/pom.xml
index 93416dbe..42487bad 100644
--- a/ruoyi-modules/ruoyi-wms/pom.xml
+++ b/ruoyi-modules/ruoyi-wms/pom.xml
@@ -109,6 +109,11 @@
org.dromara
ruoyi-api-wms
+
+
+ org.dromara
+ ruoyi-api-oa
+
org.dromara
ruoyi-common-bus
diff --git a/ruoyi-modules/ruoyi-wms/src/main/java/org/dromara/wms/dubbo/RemoteWmsShippingBillServiceImpl.java b/ruoyi-modules/ruoyi-wms/src/main/java/org/dromara/wms/dubbo/RemoteWmsShippingBillServiceImpl.java
index 7b167197..1b256fbf 100644
--- a/ruoyi-modules/ruoyi-wms/src/main/java/org/dromara/wms/dubbo/RemoteWmsShippingBillServiceImpl.java
+++ b/ruoyi-modules/ruoyi-wms/src/main/java/org/dromara/wms/dubbo/RemoteWmsShippingBillServiceImpl.java
@@ -7,6 +7,7 @@ import org.dromara.wms.api.domain.RemoteWmsShippingDraft;
import org.dromara.wms.service.IWmsShippingBillService;
import org.springframework.stereotype.Service;
+
/**
* 发货单远程服务实现
*
@@ -28,4 +29,9 @@ public class RemoteWmsShippingBillServiceImpl implements RemoteWmsShippingBillSe
return 1L;
}
+ @Override
+ public String resolveOrderDeliveryStatusByContractId(Long contractId) {
+ return wmsShippingBillService.resolveOrderDeliveryStatusByContractId(contractId);
+ }
+
}
diff --git a/ruoyi-modules/ruoyi-wms/src/main/java/org/dromara/wms/service/IWmsShippingBillService.java b/ruoyi-modules/ruoyi-wms/src/main/java/org/dromara/wms/service/IWmsShippingBillService.java
index 2c0a6830..dbdce028 100644
--- a/ruoyi-modules/ruoyi-wms/src/main/java/org/dromara/wms/service/IWmsShippingBillService.java
+++ b/ruoyi-modules/ruoyi-wms/src/main/java/org/dromara/wms/service/IWmsShippingBillService.java
@@ -84,6 +84,14 @@ public interface IWmsShippingBillService {
*/
Map buildWordExportData(Long shippingBillId);
+ /**
+ * 根据合同下已完成发货单的 is_all_receiving 计算合同订单发货状态
+ *
+ * @param contractId 合同ID
+ * @return 发货状态:1未发货 2部分发货 3已发货
+ */
+ String resolveOrderDeliveryStatusByContractId(Long contractId);
+
// /**
// * 根据项目快照创建发货草稿
diff --git a/ruoyi-modules/ruoyi-wms/src/main/java/org/dromara/wms/service/impl/WmsShippingBillServiceImpl.java b/ruoyi-modules/ruoyi-wms/src/main/java/org/dromara/wms/service/impl/WmsShippingBillServiceImpl.java
index 19881765..8cc9ae1b 100644
--- a/ruoyi-modules/ruoyi-wms/src/main/java/org/dromara/wms/service/impl/WmsShippingBillServiceImpl.java
+++ b/ruoyi-modules/ruoyi-wms/src/main/java/org/dromara/wms/service/impl/WmsShippingBillServiceImpl.java
@@ -13,6 +13,9 @@ import com.github.yulichang.wrapper.MPJLambdaWrapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
+import org.dromara.oa.api.RemoteErpContractOrderService;
+import org.dromara.oa.api.constant.OrderDeliveryStatusConstant;
+import org.dromara.oa.api.enums.ContractOrderStatusTypeEnum;
import org.dromara.common.core.enums.BusinessStatusEnum;
import org.dromara.common.core.enums.OAStatusEnum;
import org.dromara.common.core.exception.ServiceException;
@@ -65,6 +68,8 @@ public class WmsShippingBillServiceImpl implements IWmsShippingBillService {
private RemoteWorkflowService remoteWorkflowService;
@DubboReference(timeout = 30000)
private RemoteCodeRuleService remoteCodeRuleService;
+ @DubboReference(timeout = 30000)
+ private RemoteErpContractOrderService remoteErpContractOrderService;
/**
* 查询发货单(包含明细列表)
@@ -410,9 +415,7 @@ public class WmsShippingBillServiceImpl implements IWmsShippingBillService {
public WmsShippingBillVo shippingBillSubmitAndFlowStart(WmsShippingBillBo bo) {
// 根据发货类型决定是否需要到货确认:1=普通发货需确认,2=备件/3=物流不需确认
String shippingType = bo.getShippingType();
- boolean needConfirm = !"2".equals(shippingType) && !"3".equals(shippingType);
- bo.setNeedArrivalConfirm(needConfirm ? "1" : "0");
-
+ bo.setNeedArrivalConfirm("1");
WmsShippingBill add = MapstructUtils.convert(bo, WmsShippingBill.class);
validEntityBeforeSave(add);
if (StringUtils.isNull(bo.getShippingBillId())) {
@@ -426,14 +429,8 @@ public class WmsShippingBillServiceImpl implements IWmsShippingBillService {
variables.put("shippingType", StringUtils.defaultIfBlank(shippingType, "1"));
variables.put("shippingCode", bo.getShippingCode());
variables.put("applicantId", String.valueOf(LoginHelper.getUserId()));
- // 到货确认节点配置了 ${tManagerId} 抄送表达式,发起流程时必须保证变量有效
- if (needConfirm) {
- String tManagerId = StringUtils.trim(Convert.toStr(variables.get("tManagerId")));
-// if (StringUtils.isBlank(tManagerId) || "null".equalsIgnoreCase(tManagerId)) {
-// throw new ServiceException("到货确认节点抄送人员不能为空");
-// }
- variables.put("tManagerId", tManagerId);
- }
+ String tManagerId = StringUtils.trim(Convert.toStr(variables.get("tManagerId")));
+ variables.put("tManagerId", tManagerId);
// 后端发起需要忽略权限
variables.put("ignore", true);
if (StringUtils.isBlank(bo.getBizExt().getBusinessCode())) {
@@ -493,7 +490,7 @@ public class WmsShippingBillServiceImpl implements IWmsShippingBillService {
// 审批中
shippingBill.setOutStockBillStatus(OAStatusEnum.APPROVING.getStatus());
} else if (Objects.equals(processEvent.getStatus(), BusinessStatusEnum.FINISH.getStatus())) {
- // 审批完成 → 业务状态变为可用/待发货
+ // 审批完成 → 业务状态变为可用
shippingBill.setOutStockBillStatus(OAStatusEnum.COMPLETED.getStatus());
} else if (Objects.equals(processEvent.getStatus(), BusinessStatusEnum.INVALID.getStatus())
|| Objects.equals(processEvent.getStatus(), BusinessStatusEnum.TERMINATION.getStatus())) {
@@ -505,9 +502,58 @@ public class WmsShippingBillServiceImpl implements IWmsShippingBillService {
shippingBill.setOutStockBillStatus(OAStatusEnum.DRAFT.getStatus());
}
baseMapper.updateById(shippingBill);
+ if (Objects.equals(processEvent.getStatus(), BusinessStatusEnum.FINISH.getStatus())) {
+ // 发货单状态落库后再汇总,确保本次完成的发货单计入合同订单发货状态
+ refreshContractOrderDeliveryStatus(shippingBill.getContractId());
+ }
});
}
+ /**
+ * 流程完成后,按合同刷新合同订单发货状态。
+ */
+ private void refreshContractOrderDeliveryStatus(Long contractId) {
+ if (contractId == null) {
+ return;
+ }
+ try {
+ remoteErpContractOrderService.refreshContractOrderStatus(
+ contractId, null, ContractOrderStatusTypeEnum.DELIVERY.getCode());
+ } catch (Exception e) {
+ log.error("刷新合同订单发货状态失败,contractId={}", contractId, e);
+ }
+ }
+
+ /**
+ * 根据合同下已完成发货单的 is_all_receiving(0全部到货 1部分到货)计算合同订单发货状态。
+ */
+ @Override
+ public String resolveOrderDeliveryStatusByContractId(Long contractId) {
+ if (contractId == null) {
+ return OrderDeliveryStatusConstant.NOT_DELIVERED;
+ }
+ List completedBills = baseMapper.selectList(Wrappers.lambdaQuery()
+ .select(WmsShippingBill::getIsAllReceiving, WmsShippingBill::getNeedArrivalConfirm)
+ .eq(WmsShippingBill::getContractId, contractId)
+ .eq(WmsShippingBill::getOutStockBillStatus, OAStatusEnum.COMPLETED.getStatus())
+ .eq(WmsShippingBill::getDelFlag, "0"));
+ if (CollUtil.isEmpty(completedBills)) {
+ return OrderDeliveryStatusConstant.NOT_DELIVERED;
+ }
+ for (WmsShippingBill bill : completedBills) {
+ String isAllReceiving = StringUtils.trim(bill.getIsAllReceiving());
+ // 1=部分到货 → 合同订单为部分发货
+ if ("1".equals(isAllReceiving)) {
+ return OrderDeliveryStatusConstant.PARTIAL_DELIVERED;
+ }
+ // 需到货确认但未标记全部到货,按部分发货处理
+ if (!"0".equals(isAllReceiving)) {
+ return OrderDeliveryStatusConstant.PARTIAL_DELIVERED;
+ }
+ }
+ return OrderDeliveryStatusConstant.DELIVERED;
+ }
+
/**
* 组装发货单 Word 导出数据。
*/