From 74080946e360c6bdc638b1f5e1a5f0b86eef69b0 Mon Sep 17 00:00:00 2001 From: yinq Date: Wed, 3 Jun 2026 18:31:09 +0800 Subject: [PATCH] =?UTF-8?q?1.1.57=20=E8=AE=A2=E5=8D=95=E7=9A=84=E5=8F=91?= =?UTF-8?q?=E8=B4=A7=E3=80=81=E5=BC=80=E7=A5=A8=E3=80=81=E9=87=87=E8=B4=AD?= =?UTF-8?q?=E3=80=81=E5=9B=9E=E6=AC=BE=E7=8A=B6=E6=80=81=E5=AE=8C=E6=88=90?= =?UTF-8?q?=E6=97=B6=E6=9B=B4=E6=96=B0=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ruoyi-api/pom.xml | 1 + ruoyi-api/ruoyi-api-bom/pom.xml | 7 + ruoyi-api/ruoyi-api-oa/pom.xml | 27 ++ .../oa/api/RemoteErpContractOrderService.java | 19 ++ .../wms/api/RemoteWmsShippingBillService.java | 8 + ruoyi-modules/ruoyi-oa/pom.xml | 5 + .../RemoteErpContractOrderServiceImpl.java | 26 ++ .../erp/service/IErpContractOrderService.java | 17 ++ .../erp/service/IErpProjectPlanService.java | 14 + .../impl/ErpContractOrderServiceImpl.java | 274 ++++++++++++++++++ .../ErpFinAccountInstallmentServiceImpl.java | 27 ++ .../impl/ErpProjectPlanServiceImpl.java | 34 +++ .../impl/ErpProjectPurchaseServiceImpl.java | 15 + .../handler/OaProcessEventHandler.java | 26 ++ ruoyi-modules/ruoyi-wms/pom.xml | 5 + .../RemoteWmsShippingBillServiceImpl.java | 6 + .../wms/service/IWmsShippingBillService.java | 8 + .../impl/WmsShippingBillServiceImpl.java | 70 ++++- 18 files changed, 577 insertions(+), 12 deletions(-) create mode 100644 ruoyi-api/ruoyi-api-oa/pom.xml create mode 100644 ruoyi-api/ruoyi-api-oa/src/main/java/org/dromara/oa/api/RemoteErpContractOrderService.java create mode 100644 ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/dubbo/RemoteErpContractOrderServiceImpl.java 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 导出数据。 */