From 68f2cb381bdbeb65032ed0b6aaa942565bce9336 Mon Sep 17 00:00:00 2001 From: yinq Date: Sat, 9 May 2026 14:35:00 +0800 Subject: [PATCH] =?UTF-8?q?1.1.30=20=E5=88=86=E6=AC=BE=E6=98=8E=E7=BB=86?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E3=80=81=E4=BF=AE=E6=94=B9=E3=80=81=E5=88=A0?= =?UTF-8?q?=E9=99=A4=E6=97=B6=E5=90=8C=E6=AD=A5=E8=BF=9B=E8=A1=8C=E5=90=88?= =?UTF-8?q?=E5=90=8C=E5=9B=9E=E6=AC=BE=E7=A1=AE=E8=AE=A4=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ErpFinAccountInstallmentDetail.java | 18 +++ .../bo/ErpFinAccountInstallmentDetailBo.java | 15 ++ .../vo/CollectionStageSyncTargetVo.java | 22 +++ .../vo/ErpFinAccountInstallmentDetailVo.java | 15 ++ .../ErpFinAccountInstallmentDetailMapper.java | 9 ++ .../oa/erp/mapper/ErpProjectPlanMapper.java | 8 ++ .../erp/service/IErpProjectPlanService.java | 15 ++ ...inAccountInstallmentDetailServiceImpl.java | 129 ++++++++++++++---- .../impl/ErpProjectPlanServiceImpl.java | 68 +++++++-- .../ErpFinAccountInstallmentDetailMapper.xml | 30 +++- .../mapper/oa/erp/ErpProjectPlanMapper.xml | 29 ++++ 11 files changed, 316 insertions(+), 42 deletions(-) create mode 100644 ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/domain/vo/CollectionStageSyncTargetVo.java diff --git a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/domain/ErpFinAccountInstallmentDetail.java b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/domain/ErpFinAccountInstallmentDetail.java index de887ac6..01705d06 100644 --- a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/domain/ErpFinAccountInstallmentDetail.java +++ b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/domain/ErpFinAccountInstallmentDetail.java @@ -77,4 +77,22 @@ public class ErpFinAccountInstallmentDetail extends BaseEntity { @TableField(exist = false) private String stageName; + + /** + * 合同总价(连表 erp_contract_info) + */ + @TableField(exist = false) + private BigDecimal contractTotalPrice; + + /** + * 当前节点支付比例 %(连表 erp_contract_payment_method:合同ID+付款节点ID) + */ + @TableField(exist = false) + private BigDecimal contractPaymentPercentage; + + /** + * 当前节点合同约定支付金额(连表 erp_contract_payment_method.payment_amount) + */ + @TableField(exist = false) + private BigDecimal contractPaymentAmount; } diff --git a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/domain/bo/ErpFinAccountInstallmentDetailBo.java b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/domain/bo/ErpFinAccountInstallmentDetailBo.java index 313ee3e2..1a64a5f8 100644 --- a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/domain/bo/ErpFinAccountInstallmentDetailBo.java +++ b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/domain/bo/ErpFinAccountInstallmentDetailBo.java @@ -75,4 +75,19 @@ public class ErpFinAccountInstallmentDetailBo extends BaseEntity { */ private String installmentStatus; + /** + * 合同总价(连表 erp_contract_info) + */ + private BigDecimal contractTotalPrice; + + /** + * 当前节点支付比例 %(连表 erp_contract_payment_method:合同ID+付款节点ID) + */ + private BigDecimal contractPaymentPercentage; + + /** + * 当前节点合同约定支付金额(连表 erp_contract_payment_method.payment_amount) + */ + private BigDecimal contractPaymentAmount; + } diff --git a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/domain/vo/CollectionStageSyncTargetVo.java b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/domain/vo/CollectionStageSyncTargetVo.java new file mode 100644 index 00000000..0fb34a80 --- /dev/null +++ b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/domain/vo/CollectionStageSyncTargetVo.java @@ -0,0 +1,22 @@ +package org.dromara.oa.erp.domain.vo; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 分款明细同步「合同回款阶段」时解析出的计划阶段行(与 selectContractCollectionStageDetail 连表口径一致) + */ +@Data +public class CollectionStageSyncTargetVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** 合同订单项目ID(erp_project_info.project_id) */ + private Long projectId; + + private Long projectPlanId; + private Long planStageId; +} diff --git a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/domain/vo/ErpFinAccountInstallmentDetailVo.java b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/domain/vo/ErpFinAccountInstallmentDetailVo.java index dcaef4d9..7fcc414f 100644 --- a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/domain/vo/ErpFinAccountInstallmentDetailVo.java +++ b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/domain/vo/ErpFinAccountInstallmentDetailVo.java @@ -90,4 +90,19 @@ public class ErpFinAccountInstallmentDetailVo implements Serializable { private String contractName; private String stageName; + + /** + * 合同总价(连表 erp_contract_info) + */ + private BigDecimal contractTotalPrice; + + /** + * 当前节点支付比例 %(连表 erp_contract_payment_method:合同ID+付款节点ID) + */ + private BigDecimal contractPaymentPercentage; + + /** + * 当前节点合同约定支付金额(连表 erp_contract_payment_method.payment_amount) + */ + private BigDecimal contractPaymentAmount; } diff --git a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/mapper/ErpFinAccountInstallmentDetailMapper.java b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/mapper/ErpFinAccountInstallmentDetailMapper.java index a39eacf0..0b22fdd6 100644 --- a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/mapper/ErpFinAccountInstallmentDetailMapper.java +++ b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/mapper/ErpFinAccountInstallmentDetailMapper.java @@ -6,6 +6,8 @@ import com.baomidou.mybatisplus.annotation.InterceptorIgnore; import com.github.yulichang.wrapper.MPJLambdaWrapper; import com.baomidou.mybatisplus.core.toolkit.Constants; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import java.math.BigDecimal; + import org.apache.ibatis.annotations.Param; import org.dromara.oa.erp.domain.ErpFinAccountInstallmentDetail; import org.dromara.oa.erp.domain.vo.ErpFinAccountInstallmentDetailVo; @@ -37,4 +39,11 @@ public interface ErpFinAccountInstallmentDetailMapper extends BaseMapperPlus selectCustomErpFinAccountInstallmentDetailVoList(@Param(Constants.WRAPPER) MPJLambdaWrapper queryWrapper); + /** + * 按合同+项目+付款节点汇总未删除分款明细金额(用于同步合同回款阶段确认) + */ + BigDecimal sumDetailAmountByContractProjectStage(@Param("contractId") Long contractId, + @Param("projectId") Long projectId, + @Param("paymentStageId") Long paymentStageId); + } diff --git a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/mapper/ErpProjectPlanMapper.java b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/mapper/ErpProjectPlanMapper.java index 68b5af92..32dc2b61 100644 --- a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/mapper/ErpProjectPlanMapper.java +++ b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/mapper/ErpProjectPlanMapper.java @@ -11,6 +11,7 @@ import org.dromara.common.mybatis.annotation.DataColumn; import org.dromara.common.mybatis.annotation.DataPermission; import org.dromara.oa.erp.domain.ErpProjectPlan; import org.dromara.oa.erp.domain.bo.ErpProjectPlanBo; +import org.dromara.oa.erp.domain.vo.CollectionStageSyncTargetVo; import org.dromara.oa.erp.domain.vo.ContractCollectionPageVo; import org.dromara.oa.erp.domain.vo.ContractCollectionStageDetailVo; import org.dromara.oa.erp.domain.vo.ErpProjectPlanVo; @@ -146,4 +147,11 @@ public interface ErpProjectPlanMapper extends BaseMapperPlus selectContractCollectionStageDetail(@Param("projectPlanId") Long projectPlanId); + /** + * 按合同ID查项目表下全部合同订单,再经项目计划关联付款节点与计划阶段(口径同合同回款阶段明细) + */ + List selectCollectionStageSyncTargetsByContractProjectStage( + @Param("contractId") Long contractId, + @Param("paymentStageId") Long paymentStageId); + } 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 ca8bdd89..ba67c7de 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 @@ -11,6 +11,7 @@ import org.dromara.common.mybatis.core.page.PageQuery; import org.dromara.oa.erp.domain.vo.ErpProjectPlanStageVo; import java.util.Collection; +import java.util.Date; import java.util.List; /** @@ -97,4 +98,18 @@ public interface IErpProjectPlanService { * 合同回款页面阶段详情(合同订单阶段) */ List queryContractCollectionStageDetail(Long projectPlanId); + + /** + * 分款明细变更后:按合同ID解析该合同下全部合同订单的计划阶段,按「合同+各项目+付款节点」汇总分款金额, + * 为每个目标阶段组装 {@link ErpProjectPlanStageBo} 并调用 {@link #confirmCollectionStage}, + * 与合同回款页「阶段确认」同一套落库逻辑(含订单回款比例刷新)。 + * {@code projectId} 仅用于与 {@code receivableDate} 配合:仅本明细所属项目对应阶段更新应收款日期,多订单合同时避免串单。 + * + * @param contractId 合同ID + * @param projectId 分款明细所属项目ID;与 {@code receivableDate} 同时用于仅更新该项目对应阶段上的回款日期(多订单合同时避免串单) + * @param paymentStageId 付款节点ID + * @param receivableDate 实际回款日期,一般取回款主表 payment_date;为 null 时不改阶段上的 receivable_date(分款同步不写入确认备注) + */ + void syncCollectionStageFromInstallmentDetail(Long contractId, Long projectId, Long paymentStageId, + Date receivableDate); } diff --git a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/service/impl/ErpFinAccountInstallmentDetailServiceImpl.java b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/service/impl/ErpFinAccountInstallmentDetailServiceImpl.java index 1a2f813b..2f25263a 100644 --- a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/service/impl/ErpFinAccountInstallmentDetailServiceImpl.java +++ b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/service/impl/ErpFinAccountInstallmentDetailServiceImpl.java @@ -11,6 +11,7 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers; import lombok.RequiredArgsConstructor; import org.dromara.oa.base.domain.BasePaymentStage; import org.dromara.oa.erp.domain.ErpContractInfo; +import org.dromara.oa.erp.domain.ErpContractPaymentMethod; import org.dromara.oa.erp.domain.ErpFinAccountInstallment; import org.dromara.oa.erp.mapper.ErpFinAccountInstallmentMapper; import org.springframework.stereotype.Service; @@ -19,11 +20,15 @@ import org.dromara.oa.erp.domain.vo.ErpFinAccountInstallmentDetailVo; import org.dromara.oa.erp.domain.ErpFinAccountInstallmentDetail; import org.dromara.oa.erp.mapper.ErpFinAccountInstallmentDetailMapper; import org.dromara.oa.erp.service.IErpFinAccountInstallmentDetailService; +import org.dromara.oa.erp.service.IErpProjectPlanService; import org.springframework.transaction.annotation.Transactional; +import java.util.Collection; +import java.util.Date; +import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Collection; +import java.util.Set; /** * 分款明细信息Service业务层处理 @@ -39,6 +44,32 @@ public class ErpFinAccountInstallmentDetailServiceImpl implements IErpFinAccount private final ErpFinAccountInstallmentMapper finAccountInstallmentMapper; + private final IErpProjectPlanService erpProjectPlanService; + + /** + * 删除等场景:不覆盖阶段上的回款日期 + */ + private void syncContractCollectionStage(Long contractId, Long projectId, Long paymentStageId) { + erpProjectPlanService.syncCollectionStageFromInstallmentDetail(contractId, projectId, paymentStageId, null); + } + + /** + * 新增/修改:汇总后由项目计划服务组装 ErpProjectPlanStageBo 并调用 + * {@link IErpProjectPlanService#confirmCollectionStage};回款日期取回款主表 payment_date(仅回写本明细项目对应阶段)。 + */ + private void syncContractCollectionStage(Long contractId, Long projectId, Long paymentStageId, + Long accountInstallmentId) { + Date receivableDate = null; + if (accountInstallmentId != null) { + ErpFinAccountInstallment inst = finAccountInstallmentMapper.selectById(accountInstallmentId); + if (inst != null) { + receivableDate = inst.getPaymentDate(); + } + } + erpProjectPlanService.syncCollectionStageFromInstallmentDetail( + contractId, projectId, paymentStageId, receivableDate); + } + /** * 查询分款明细信息 * @@ -79,14 +110,14 @@ public class ErpFinAccountInstallmentDetailServiceImpl implements IErpFinAccount private MPJLambdaWrapper buildQueryWrapper(ErpFinAccountInstallmentDetailBo bo) { Map params = bo.getParams(); MPJLambdaWrapper lqw = JoinWrappers.lambda(ErpFinAccountInstallmentDetail.class) - .selectAll(ErpFinAccountInstallmentDetail.class) - .eq(bo.getAccountInstallmentId() != null, ErpFinAccountInstallmentDetail::getAccountInstallmentId, bo.getAccountInstallmentId()) - .eq(bo.getProjectId() != null, ErpFinAccountInstallmentDetail::getProjectId, bo.getProjectId()) - .eq(StringUtils.isNotBlank(bo.getProjectCode()), ErpFinAccountInstallmentDetail::getProjectCode, bo.getProjectCode()) - .like(StringUtils.isNotBlank(bo.getProjectName()), ErpFinAccountInstallmentDetail::getProjectName, bo.getProjectName()) - .eq(bo.getContractId() != null, ErpFinAccountInstallmentDetail::getContractId, bo.getContractId()) - .eq(bo.getPaymentStageId() != null, ErpFinAccountInstallmentDetail::getPaymentStageId, bo.getPaymentStageId()) - .eq(bo.getDetailAmount() != null, ErpFinAccountInstallmentDetail::getDetailAmount, bo.getDetailAmount()); + .selectAll(ErpFinAccountInstallmentDetail.class) + .eq(bo.getAccountInstallmentId() != null, ErpFinAccountInstallmentDetail::getAccountInstallmentId, bo.getAccountInstallmentId()) + .eq(bo.getProjectId() != null, ErpFinAccountInstallmentDetail::getProjectId, bo.getProjectId()) + .eq(StringUtils.isNotBlank(bo.getProjectCode()), ErpFinAccountInstallmentDetail::getProjectCode, bo.getProjectCode()) + .like(StringUtils.isNotBlank(bo.getProjectName()), ErpFinAccountInstallmentDetail::getProjectName, bo.getProjectName()) + .eq(bo.getContractId() != null, ErpFinAccountInstallmentDetail::getContractId, bo.getContractId()) + .eq(bo.getPaymentStageId() != null, ErpFinAccountInstallmentDetail::getPaymentStageId, bo.getPaymentStageId()) + .eq(bo.getDetailAmount() != null, ErpFinAccountInstallmentDetail::getDetailAmount, bo.getDetailAmount()); return lqw; } @@ -106,19 +137,26 @@ public class ErpFinAccountInstallmentDetailServiceImpl implements IErpFinAccount private MPJLambdaWrapper buildJoinQueryWrapper(ErpFinAccountInstallmentDetailBo bo) { Map params = bo.getParams(); MPJLambdaWrapper lqw = JoinWrappers.lambda(ErpFinAccountInstallmentDetail.class) - .selectAll(ErpFinAccountInstallmentDetail.class) - .select(ErpContractInfo::getContractCode) - .select(ErpContractInfo::getContractName) - .select(BasePaymentStage::getStageName) - .leftJoin(ErpContractInfo.class, ErpContractInfo::getContractId, ErpFinAccountInstallmentDetail::getContractId) - .leftJoin(BasePaymentStage.class, BasePaymentStage::getPaymentStageId, ErpFinAccountInstallmentDetail::getPaymentStageId) - .eq(bo.getAccountInstallmentId() != null, ErpFinAccountInstallmentDetail::getAccountInstallmentId, bo.getAccountInstallmentId()) - .eq(bo.getProjectId() != null, ErpFinAccountInstallmentDetail::getProjectId, bo.getProjectId()) - .eq(StringUtils.isNotBlank(bo.getProjectCode()), ErpFinAccountInstallmentDetail::getProjectCode, bo.getProjectCode()) - .like(StringUtils.isNotBlank(bo.getProjectName()), ErpFinAccountInstallmentDetail::getProjectName, bo.getProjectName()) - .eq(bo.getContractId() != null, ErpFinAccountInstallmentDetail::getContractId, bo.getContractId()) - .eq(bo.getPaymentStageId() != null, ErpFinAccountInstallmentDetail::getPaymentStageId, bo.getPaymentStageId()) - .eq(bo.getDetailAmount() != null, ErpFinAccountInstallmentDetail::getDetailAmount, bo.getDetailAmount()); + .selectAll(ErpFinAccountInstallmentDetail.class) + .select(ErpContractInfo::getContractCode) + .select(ErpContractInfo::getContractName) + .selectAs(ErpContractInfo::getTotalPrice, ErpFinAccountInstallmentDetailVo::getContractTotalPrice) + .select(BasePaymentStage::getStageName) + .selectAs(ErpContractPaymentMethod::getPaymentPercentage, ErpFinAccountInstallmentDetailVo::getContractPaymentPercentage) + .selectAs(ErpContractPaymentMethod::getPaymentAmount, ErpFinAccountInstallmentDetailVo::getContractPaymentAmount) + .leftJoin(ErpContractInfo.class, ErpContractInfo::getContractId, ErpFinAccountInstallmentDetail::getContractId) + .leftJoin(BasePaymentStage.class, BasePaymentStage::getPaymentStageId, ErpFinAccountInstallmentDetail::getPaymentStageId) + .leftJoin(ErpContractPaymentMethod.class, on -> on + .eq(ErpContractPaymentMethod::getContractId, ErpFinAccountInstallmentDetail::getContractId) + .eq(ErpContractPaymentMethod::getPaymentStageId, ErpFinAccountInstallmentDetail::getPaymentStageId) + .eq(ErpContractPaymentMethod::getDelFlag, "0")) + .eq(bo.getAccountInstallmentId() != null, ErpFinAccountInstallmentDetail::getAccountInstallmentId, bo.getAccountInstallmentId()) + .eq(bo.getProjectId() != null, ErpFinAccountInstallmentDetail::getProjectId, bo.getProjectId()) + .eq(StringUtils.isNotBlank(bo.getProjectCode()), ErpFinAccountInstallmentDetail::getProjectCode, bo.getProjectCode()) + .like(StringUtils.isNotBlank(bo.getProjectName()), ErpFinAccountInstallmentDetail::getProjectName, bo.getProjectName()) + .eq(bo.getContractId() != null, ErpFinAccountInstallmentDetail::getContractId, bo.getContractId()) + .eq(bo.getPaymentStageId() != null, ErpFinAccountInstallmentDetail::getPaymentStageId, bo.getPaymentStageId()) + .eq(bo.getDetailAmount() != null, ErpFinAccountInstallmentDetail::getDetailAmount, bo.getDetailAmount()); return lqw; } @@ -140,7 +178,10 @@ public class ErpFinAccountInstallmentDetailServiceImpl implements IErpFinAccount ErpFinAccountInstallment erpFinAccountInstallment = finAccountInstallmentMapper.selectById(bo.getAccountInstallmentId()); erpFinAccountInstallment.setInstallmentStatus(bo.getInstallmentStatus()); finAccountInstallmentMapper.updateById(erpFinAccountInstallment); - + if (flag) { + syncContractCollectionStage(bo.getContractId(), bo.getProjectId(), bo.getPaymentStageId(), + bo.getAccountInstallmentId()); + } return flag; } @@ -153,12 +194,22 @@ public class ErpFinAccountInstallmentDetailServiceImpl implements IErpFinAccount @Override @Transactional(rollbackFor = Exception.class) public Boolean updateByBo(ErpFinAccountInstallmentDetailBo bo) { + ErpFinAccountInstallmentDetail old = baseMapper.selectById(bo.getInstallmentDetailId()); ErpFinAccountInstallmentDetail update = MapstructUtils.convert(bo, ErpFinAccountInstallmentDetail.class); validEntityBeforeSave(update); ErpFinAccountInstallment erpFinAccountInstallment = finAccountInstallmentMapper.selectById(bo.getAccountInstallmentId()); erpFinAccountInstallment.setInstallmentStatus(bo.getInstallmentStatus()); finAccountInstallmentMapper.updateById(erpFinAccountInstallment); - return baseMapper.updateById(update) > 0; + boolean ok = baseMapper.updateById(update) > 0; + if (ok) { + if (old != null) { + syncContractCollectionStage(old.getContractId(), old.getProjectId(), old.getPaymentStageId(), + bo.getAccountInstallmentId()); + } + syncContractCollectionStage(bo.getContractId(), bo.getProjectId(), bo.getPaymentStageId(), + bo.getAccountInstallmentId()); + } + return ok; } /** @@ -180,23 +231,47 @@ public class ErpFinAccountInstallmentDetailServiceImpl implements IErpFinAccount if (isValid) { //TODO 做一些业务上的校验,判断是否需要校验 } - return baseMapper.deleteByIds(ids) > 0; + if (ids == null || ids.isEmpty()) { + return true; + } + List rows = baseMapper.selectList( + Wrappers.lambdaQuery(ErpFinAccountInstallmentDetail.class) + .in(ErpFinAccountInstallmentDetail::getInstallmentDetailId, ids)); + boolean ok = baseMapper.deleteByIds(ids) > 0; + if (ok && !rows.isEmpty()) { + Set seen = new HashSet<>(); + for (ErpFinAccountInstallmentDetail r : rows) { + if (r.getContractId() == null || r.getProjectId() == null || r.getPaymentStageId() == null) { + continue; + } + String key = r.getContractId() + "_" + r.getProjectId() + "_" + r.getPaymentStageId(); + if (seen.add(key)) { + syncContractCollectionStage(r.getContractId(), r.getProjectId(), r.getPaymentStageId()); + } + } + } + return ok; } /** * 删除分款信息 + * * @param bo * @return 是否删除成功 */ @Override public Boolean deleteAccountInstallmentDetail(ErpFinAccountInstallmentDetailBo bo) { + ErpFinAccountInstallmentDetail row = bo.getInstallmentDetailId() == null ? null : baseMapper.selectById(bo.getInstallmentDetailId()); ErpFinAccountInstallment erpFinAccountInstallment = new ErpFinAccountInstallment(); erpFinAccountInstallment.setAccountInstallmentId(bo.getAccountInstallmentId()); erpFinAccountInstallment.setInstallmentStatus(bo.getInstallmentStatus()); finAccountInstallmentMapper.updateById(erpFinAccountInstallment); - return baseMapper.deleteById(bo.getInstallmentDetailId()) > 0; - + boolean ok = baseMapper.deleteById(bo.getInstallmentDetailId()) > 0; + if (ok && row != null) { + syncContractCollectionStage(row.getContractId(), row.getProjectId(), row.getPaymentStageId()); + } + return ok; } } 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 661f44ec..ee2656fd 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 @@ -27,10 +27,12 @@ import org.dromara.oa.erp.domain.ErpProjectPlan; import org.dromara.oa.erp.domain.ErpProjectPlanStage; import org.dromara.oa.erp.domain.bo.ErpProjectPlanBo; import org.dromara.oa.erp.domain.bo.ErpProjectPlanStageBo; +import org.dromara.oa.erp.domain.vo.CollectionStageSyncTargetVo; import org.dromara.oa.erp.domain.vo.ContractCollectionPageVo; import org.dromara.oa.erp.domain.vo.ContractCollectionStageDetailVo; import org.dromara.oa.erp.domain.vo.ErpProjectPlanStageVo; import org.dromara.oa.erp.domain.vo.ErpProjectPlanVo; +import org.dromara.oa.erp.mapper.ErpFinAccountInstallmentDetailMapper; import org.dromara.oa.erp.mapper.ErpProjectInfoMapper; import org.dromara.oa.erp.mapper.ErpProjectPlanMapper; import org.dromara.oa.erp.mapper.ErpProjectPlanStageMapper; @@ -67,6 +69,8 @@ public class ErpProjectPlanServiceImpl implements IErpProjectPlanService { private final ErpProjectInfoMapper projectInfoMapper; + private final ErpFinAccountInstallmentDetailMapper finAccountInstallmentDetailMapper; + @DubboReference(timeout = 30000) private RemoteWorkflowService remoteWorkflowService; @@ -393,25 +397,16 @@ public class ErpProjectPlanServiceImpl implements IErpProjectPlanService { if (stage == null) { throw new ServiceException("项目计划阶段不存在"); } - stage.setReceivableDate(bo.getReceivableDate()); stage.setActualRepaymentAmount(bo.getActualRepaymentAmount()); BigDecimal contractOrderAmount = resolveContractOrderAmount(stage.getProjectPlanId(), stage.getProjectId()); stage.setActualRepaymentRate(calcActualRepaymentRate(bo.getActualRepaymentAmount(), contractOrderAmount)); stage.setCollectionConfirmRemark(bo.getCollectionConfirmRemark()); stage.setCollectionConfirmUserId(LoginHelper.getUserId()); - stage.setCollectionConfirmTime(new Date()); + stage.setCollectionConfirmTime(bo.getCollectionConfirmTime()); - String confirmStatus = "0"; BigDecimal expected = stage.getRepaymentAmount() == null ? BigDecimal.ZERO : stage.getRepaymentAmount(); BigDecimal actual = bo.getActualRepaymentAmount() == null ? BigDecimal.ZERO : bo.getActualRepaymentAmount(); - if (actual.compareTo(BigDecimal.ZERO) <= 0) { - confirmStatus = "0"; - } else if (expected.compareTo(BigDecimal.ZERO) > 0 && actual.compareTo(expected) >= 0) { - confirmStatus = "2"; - } else { - confirmStatus = "1"; - } - stage.setCollectionConfirmStatus(confirmStatus); + stage.setCollectionConfirmStatus(resolveCollectionConfirmStatus(actual, expected)); if (bo.getDelayDay() != null) { stage.setDelayDay(bo.getDelayDay()); @@ -508,6 +503,57 @@ public class ErpProjectPlanServiceImpl implements IErpProjectPlanService { return baseMapper.selectContractCollectionStageDetail(projectPlanId); } + @Override + public void syncCollectionStageFromInstallmentDetail(Long contractId, Long projectId, Long paymentStageId, + Date receivableDate) { + if (contractId == null || paymentStageId == null) { + return; + } + + // 合同ID → 项目表(该合同下全部合同订单) → 项目计划 → 计划阶段 + List targets = + baseMapper.selectCollectionStageSyncTargetsByContractProjectStage(contractId, paymentStageId); + if (targets == null || targets.isEmpty()) { + return; + } + + for (CollectionStageSyncTargetVo row : targets) { + if (row.getPlanStageId() == null || row.getProjectPlanId() == null || row.getProjectId() == null) { + continue; + } + ErpProjectPlanStage stage = planStageMapper.selectById(row.getPlanStageId()); + if (stage == null) { + continue; + } + BigDecimal sum = finAccountInstallmentDetailMapper.sumDetailAmountByContractProjectStage( + contractId, projectId, paymentStageId); + if (sum == null) { + sum = BigDecimal.ZERO; + } + ErpProjectPlanStageBo bo = new ErpProjectPlanStageBo(); + bo.setPlanStageId(row.getPlanStageId()); + bo.setActualRepaymentAmount(sum); + bo.setReceivableDate(receivableDate); + bo.setCollectionConfirmRemark(stage.getCollectionConfirmRemark()); + this.confirmCollectionStage(bo); + } + } + + /** + * 与 {@link #confirmCollectionStage} 一致:0 未确认 / 1 部分 / 2 已确认(按本阶段预计回款金额 repayment_amount) + */ + private static String resolveCollectionConfirmStatus(BigDecimal actualAmount, BigDecimal expectedAmount) { + BigDecimal actual = actualAmount == null ? BigDecimal.ZERO : actualAmount; + BigDecimal expected = expectedAmount == null ? BigDecimal.ZERO : expectedAmount; + if (actual.compareTo(BigDecimal.ZERO) <= 0) { + return "0"; + } + if (expected.compareTo(BigDecimal.ZERO) > 0 && actual.compareTo(expected) >= 0) { + return "2"; + } + return "1"; + } + /** * 总体流程监听(例如: 草稿,撤销,退回,作废,终止,已完成等) * diff --git a/ruoyi-modules/ruoyi-oa/src/main/resources/mapper/oa/erp/ErpFinAccountInstallmentDetailMapper.xml b/ruoyi-modules/ruoyi-oa/src/main/resources/mapper/oa/erp/ErpFinAccountInstallmentDetailMapper.xml index 9cdaa62b..91fdf4cf 100644 --- a/ruoyi-modules/ruoyi-oa/src/main/resources/mapper/oa/erp/ErpFinAccountInstallmentDetailMapper.xml +++ b/ruoyi-modules/ruoyi-oa/src/main/resources/mapper/oa/erp/ErpFinAccountInstallmentDetailMapper.xml @@ -1,14 +1,36 @@ + PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> - + + diff --git a/ruoyi-modules/ruoyi-oa/src/main/resources/mapper/oa/erp/ErpProjectPlanMapper.xml b/ruoyi-modules/ruoyi-oa/src/main/resources/mapper/oa/erp/ErpProjectPlanMapper.xml index 5db0dbe4..343cf1a8 100644 --- a/ruoyi-modules/ruoyi-oa/src/main/resources/mapper/oa/erp/ErpProjectPlanMapper.xml +++ b/ruoyi-modules/ruoyi-oa/src/main/resources/mapper/oa/erp/ErpProjectPlanMapper.xml @@ -448,4 +448,33 @@ order by pm.sort_order asc + + +