diff --git a/bms-back/ruoyi-system/src/main/java/com/ruoyi/ar/enums/ArIncomeBillFeeStatus.java b/bms-back/ruoyi-system/src/main/java/com/ruoyi/ar/enums/ArIncomeBillFeeStatus.java new file mode 100644 index 00000000..45a09e37 --- /dev/null +++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/ar/enums/ArIncomeBillFeeStatus.java @@ -0,0 +1,20 @@ +package com.ruoyi.ar.enums; + +/** 应收费用明细是否已经生成正式应收账单。 */ +public enum ArIncomeBillFeeStatus +{ + UNBILLED("UNBILLED"), + BILLED("BILLED"); + + private final String code; + + ArIncomeBillFeeStatus(String code) + { + this.code = code; + } + + public String getCode() + { + return code; + } +} diff --git a/bms-back/ruoyi-system/src/main/java/com/ruoyi/ar/enums/ArOrderBillStatus.java b/bms-back/ruoyi-system/src/main/java/com/ruoyi/ar/enums/ArOrderBillStatus.java index e165236b..88552aa5 100644 --- a/bms-back/ruoyi-system/src/main/java/com/ruoyi/ar/enums/ArOrderBillStatus.java +++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/ar/enums/ArOrderBillStatus.java @@ -1,9 +1,10 @@ package com.ruoyi.ar.enums; -/** 计费订单的应收账单占用状态。 */ +/** 计费订单的应收账单状态。 */ public enum ArOrderBillStatus { UNBILLED("UNBILLED"), + PARTIAL("PARTIAL"), BILLED("BILLED"); private final String code; diff --git a/bms-back/ruoyi-system/src/main/java/com/ruoyi/ar/service/impl/ArBillServiceImpl.java b/bms-back/ruoyi-system/src/main/java/com/ruoyi/ar/service/impl/ArBillServiceImpl.java index ae8bb5ef..5e664083 100644 --- a/bms-back/ruoyi-system/src/main/java/com/ruoyi/ar/service/impl/ArBillServiceImpl.java +++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/ar/service/impl/ArBillServiceImpl.java @@ -13,6 +13,7 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.LinkedHashSet; @@ -51,6 +52,7 @@ import com.ruoyi.ar.enums.ArBillGenerationStatus; import com.ruoyi.ar.enums.ArBillSelectionMode; import com.ruoyi.ar.enums.ArBillStatus; import com.ruoyi.ar.enums.ArExportStatus; +import com.ruoyi.ar.enums.ArIncomeBillFeeStatus; import com.ruoyi.ar.enums.ArLogObjectType; import com.ruoyi.ar.enums.ArLogOperationSource; import com.ruoyi.ar.enums.ArLogOperationType; @@ -222,7 +224,7 @@ public class ArBillServiceImpl implements IArBillService { return sourceBillMapper.selectPrcOrderBillByCustomerPeriod(query.getCustomerId(), query.getCustomerCode(), StringUtils.trim(query.getOrderOrTrackingNo()), StringUtils.trim(query.getExcludeOrderOrTrackingNo()), query.getBillPeriodEnd(), query.getCurrency(), - ArOrderBillStatus.UNBILLED.getCode(), null); + eligibleOrderBillStatuses(), ArIncomeBillFeeStatus.UNBILLED.getCode(), null); } /** @@ -234,7 +236,7 @@ public class ArBillServiceImpl implements IArBillService { return nvl(sourceBillMapper.sumPrcOrderBillIncomeByCustomerPeriod(query.getCustomerId(), query.getCustomerCode(), StringUtils.trim(query.getOrderOrTrackingNo()), StringUtils.trim(query.getExcludeOrderOrTrackingNo()), query.getBillPeriodEnd(), query.getCurrency(), - ArOrderBillStatus.UNBILLED.getCode())); + eligibleOrderBillStatuses(), ArIncomeBillFeeStatus.UNBILLED.getCode())); } /** @@ -421,7 +423,8 @@ public class ArBillServiceImpl implements IArBillService { log.info("开始统计应收账单生成范围,taskId={}", taskId); int totalCount = sourceBillMapper.countPrcOrderBillByCustomerPeriod( request.getCustomerId(), request.getCustomerCode(), StringUtils.trim(request.getOrderOrTrackingNo()), - request.getBillPeriodEnd(), request.getCurrency(), ArOrderBillStatus.UNBILLED.getCode(), + request.getBillPeriodEnd(), request.getCurrency(), eligibleOrderBillStatuses(), + ArIncomeBillFeeStatus.UNBILLED.getCode(), task.getSnapshotCreateTime(), null, excludedSourceBillIds); if (totalCount <= 0) { throw new ServiceException("当前全选范围内没有可生成的订单"); @@ -545,7 +548,8 @@ public class ArBillServiceImpl implements IArBillService { List sources = sourceBillMapper.selectPrcOrderBillByCustomerPeriodCursor( request.getCustomerId(), request.getCustomerCode(), StringUtils.trim(request.getOrderOrTrackingNo()), request.getBillPeriodEnd(), StringUtils.isEmpty(request.getCurrency()) ? "CNY" : request.getCurrency(), - ArOrderBillStatus.UNBILLED.getCode(), task.getSnapshotCreateTime(), task.getLastSourceBillId(), + eligibleOrderBillStatuses(), ArIncomeBillFeeStatus.UNBILLED.getCode(), + task.getSnapshotCreateTime(), task.getLastSourceBillId(), explicitIds, selectionMode == ArBillSelectionMode.ALL_MATCHED ? excludedSourceBillIds : null, GENERATE_PAGE_SIZE); if (sources == null || sources.isEmpty()) { @@ -563,13 +567,36 @@ public class ArBillServiceImpl implements IArBillService { throw new ServiceException("截止日期内存在多个币种,不能合并为一张账单"); } } + + // 先抓取本批次的未出账费用快照,后续只更新和复制这些费用,避免把同订单已有 BILLED 费用重复带入新账单。 + List sourceFees = sourceFeeMapper.selectUnbilledByBillIds( + currentSourceBillIds, ArIncomeBillFeeStatus.UNBILLED.getCode()); + if (sourceFees == null || sourceFees.isEmpty()) { + throw new ServiceException("当前批次没有可生成的未出账费用"); + } + Map sourceFeeCounts = new HashMap<>(); + List sourceFeeIds = new ArrayList<>(sourceFees.size()); + for (PrcIncomeBillFee sourceFee : sourceFees) { + sourceFeeCounts.merge(sourceFee.getBillId(), 1, Integer::sum); + sourceFeeIds.add(sourceFee.getFeeId()); + } + for (String sourceBillId : currentSourceBillIds) { + if (!sourceFeeCounts.containsKey(sourceBillId)) { + throw new ServiceException("来源订单没有可生成的未出账费用:" + sourceBillId); + } + } + int claimedCount = sourceBillMapper.claimArBillOrders(currentSourceBillIds, task.getCreateBy(), - ArOrderBillStatus.UNBILLED.getCode(), ArOrderBillStatus.BILLED.getCode()); + eligibleOrderBillStatuses(), ArOrderBillStatus.BILLED.getCode()); if (claimedCount != currentSourceBillIds.size()) { throw new ServiceException("部分计费订单已被其他账单占用,请重新查询后生成"); } - sourceFeeMapper.updateArBillStatusByBillIds(currentSourceBillIds, - ArOrderBillStatus.UNBILLED.getCode(), ArOrderBillStatus.BILLED.getCode()); + int billedFeeCount = sourceFeeMapper.updateArBillStatusByFeeIds(sourceFeeIds, + ArIncomeBillFeeStatus.UNBILLED.getCode(), ArIncomeBillFeeStatus.BILLED.getCode()); + if (billedFeeCount != sourceFeeIds.size()) { + throw new ServiceException("部分费用状态已发生变化,请重新查询后生成"); + } + sourceBillMapper.refreshArBillStatusByBillIds(currentSourceBillIds); int detailNo = (task.getProcessedCount() == null ? 0 : task.getProcessedCount()) + 1; List details = new ArrayList<>(sources.size()); @@ -586,7 +613,6 @@ public class ArBillServiceImpl implements IArBillService { throw new ServiceException("账单明细写入数量不一致"); } - List sourceFees = sourceFeeMapper.selectByBillIds(currentSourceBillIds); List detailFees = new ArrayList<>(sourceFees == null ? 0 : sourceFees.size()); Map feeNumbers = new HashMap<>(); if (sourceFees != null) { @@ -625,18 +651,9 @@ public class ArBillServiceImpl implements IArBillService { */ private void cleanupGenerateTask(ArBillGenerateTask task, String message, boolean canceled, String operator, String requestId) { - List sourceBillIds = new ArrayList<>(); - for (ArBillDetail detail : detailMapper.selectByBillId(task.getArBillId())) { - if (!StringUtils.isEmpty(detail.getPrcOrderBillId())) { - sourceBillIds.add(detail.getPrcOrderBillId()); - } - } - sourceBillMapper.releaseArBillOrdersByArBillId(task.getArBillId(), operator, - ArOrderBillStatus.BILLED.getCode(), ArOrderBillStatus.UNBILLED.getCode()); - if (!sourceBillIds.isEmpty()) { - sourceFeeMapper.updateArBillStatusByBillIds(sourceBillIds, - ArOrderBillStatus.BILLED.getCode(), ArOrderBillStatus.UNBILLED.getCode()); - } + // 当前任务账单仍处于草稿状态,恢复时排除当前账单自身的关联,避免组装超大的来源 ID 集合。 + sourceFeeMapper.restoreUnbilledIfNoActiveArBillByArBillId(task.getArBillId()); + sourceBillMapper.refreshArBillStatusByArBillId(task.getArBillId()); detailFeeMapper.deleteByBillId(task.getArBillId()); detailMapper.deleteByBillId(task.getArBillId()); billMapper.updateArBillGenerationStatus(task.getArBillId(), ArBillGenerationStatus.FAILED, operator, message); @@ -781,28 +798,14 @@ public class ArBillServiceImpl implements IArBillService { bill.getStatus(), "当前状态不允许作废", operator, requestId); } - List sourceBillIds = new ArrayList<>(); - for (ArBillDetail detail : detailMapper.selectByBillId(arBillId)) { - if (!StringUtils.isEmpty(detail.getPrcOrderBillId())) { - sourceBillIds.add(detail.getPrcOrderBillId()); - } - } if (billMapper.updateArBillStatus(arBillId, ArBillStatus.VOID.getCode(), remark, operator, currentStatus.getCode()) != 1) { throw failure(arBillId, ArLogObjectType.BILL, String.valueOf(arBillId), ArLogOperationType.VOID, bill.getStatus(), "账单状态已发生变化,请刷新后重试", operator, requestId); } - if (!sourceBillIds.isEmpty()) { - int releasedCount = sourceBillMapper.releaseArBillOrders(sourceBillIds, operator, - ArOrderBillStatus.BILLED.getCode(), ArOrderBillStatus.UNBILLED.getCode()); - if (releasedCount != sourceBillIds.size()) { - throw failure(arBillId, ArLogObjectType.BILL, String.valueOf(arBillId), ArLogOperationType.VOID, - bill.getStatus(), - "部分来源订单状态异常,账单未作废,请刷新后重试", operator, requestId); - } - sourceFeeMapper.updateArBillStatusByBillIds(sourceBillIds, - ArOrderBillStatus.BILLED.getCode(), ArOrderBillStatus.UNBILLED.getCode()); - } + // 作废后保留历史明细,只释放本账单关联的费用;若费用仍被其他有效账单引用,则继续保持 BILLED。 + sourceFeeMapper.restoreUnbilledIfNoActiveArBillByArBillId(arBillId); + sourceBillMapper.refreshArBillStatusByArBillId(arBillId); recordSuccess(bill, ArLogObjectType.BILL, ArLogOperationType.VOID, "作废应收账单:" + remark, bill.getStatus(), ArBillStatus.VOID.getCode(), requestId, operator); return selectArBillById(arBillId); @@ -1667,6 +1670,11 @@ public class ArBillServiceImpl implements IArBillService { } } + /** 可参与本次应收生成的订单状态;PARTIAL 只生成其中仍为 UNBILLED 的费用。 */ + private List eligibleOrderBillStatuses() { + return Arrays.asList(ArOrderBillStatus.UNBILLED.getCode(), ArOrderBillStatus.PARTIAL.getCode()); + } + private Date earliestBizDate(List sources) { Date earliest = null; for (PrcOrderBill source : sources) { diff --git a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/domain/PrcIncomeBillFee.java b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/domain/PrcIncomeBillFee.java index fb6af3b4..7b71dfa3 100644 --- a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/domain/PrcIncomeBillFee.java +++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/domain/PrcIncomeBillFee.java @@ -23,7 +23,7 @@ public class PrcIncomeBillFee implements Serializable private String currency; private String roundMode; private String remark; - /** 是否生成账单状态:UNBILLED/BILLED */ + /** 是否生成账单状态:UNBILLED未生成/BILLED已生成 */ private String arBillStatus; /** 是否手动增加:0否 1是 */ private String manualFlag; diff --git a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/domain/PrcOrderBill.java b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/domain/PrcOrderBill.java index b21d631d..3dc97cd0 100644 --- a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/domain/PrcOrderBill.java +++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/domain/PrcOrderBill.java @@ -97,7 +97,7 @@ public class PrcOrderBill extends BaseEntity /** payable total */ private BigDecimal payTotal; private String currency; - /** 应收账单占用状态:UNBILLED/BILLED。 */ + /** 应收账单状态:UNBILLED未出账/PARTIAL部分出账/BILLED已出账。 */ private String arBillStatus; /** ACTIVE / MISSING / DISABLED */ private String customerActiveStatus; diff --git a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/mapper/PrcIncomeBillFeeMapper.java b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/mapper/PrcIncomeBillFeeMapper.java index 8ecb4344..80dd1e13 100644 --- a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/mapper/PrcIncomeBillFeeMapper.java +++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/mapper/PrcIncomeBillFeeMapper.java @@ -11,6 +11,10 @@ public interface PrcIncomeBillFeeMapper public List selectByBillIds(@Param("billIds") List billIds); + /** 查询指定计费订单中尚未生成应收账单的费用。 */ + public List selectUnbilledByBillIds(@Param("billIds") List billIds, + @Param("arBillStatus") String arBillStatus); + public PrcIncomeBillFee selectByFeeId(String feeId); /** Count fees of a bill under given ar_bill_status and fee_type (e.g. BILLED + BASIC_FREIGHT). */ @@ -26,6 +30,16 @@ public interface PrcIncomeBillFeeMapper public int updateArBillStatusByBillIds(@Param("billIds") List billIds, @Param("fromStatus") String fromStatus, @Param("toStatus") String toStatus); + /** 按费用明细 ID 精确更新状态,避免误更新同一订单的历史费用。 */ + public int updateArBillStatusByFeeIds(@Param("feeIds") List feeIds, + @Param("fromStatus") String fromStatus, @Param("toStatus") String toStatus); + + /** 作废或失败清理后,仅在不存在其他有效账单关联时恢复为未出账。 */ + public int restoreUnbilledIfNoActiveArBill(@Param("feeIds") List feeIds); + + /** 按应收账单批量恢复其关联费用,避免大账单组装超大的费用 ID 列表。 */ + public int restoreUnbilledIfNoActiveArBillByArBillId(@Param("arBillId") Long arBillId); + public int deleteByFeeId(String feeId); public int deleteByBillId(String billId); diff --git a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/mapper/PrcOrderBillMapper.java b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/mapper/PrcOrderBillMapper.java index 265d85a7..e7c9edc5 100644 --- a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/mapper/PrcOrderBillMapper.java +++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/mapper/PrcOrderBillMapper.java @@ -29,32 +29,35 @@ public interface PrcOrderBillMapper /** 转单号下拉搜索:按关键词模糊匹配,返回去重后的转单号(按最近创建排序,限量返回) */ public List selectTransferNoOptions(@Param("keyword") String keyword, @Param("limit") int limit); - /** 应收账单生成读取指定客户截止日期前的未出账订单计费快照。 */ + /** 应收账单生成读取指定客户截止日期前仍存在未出账费用的订单计费快照。 */ public List selectPrcOrderBillByCustomerPeriod(@Param("customerId") Long customerId, @Param("customerCode") String customerCode, @Param("orderOrTrackingNo") String orderOrTrackingNo, @Param("excludeOrderOrTrackingNo") String excludeOrderOrTrackingNo, @Param("cutoffDate") Date cutoffDate, @Param("currency") String currency, - @Param("arBillStatus") String arBillStatus, @Param("excludedBillIds") List excludedBillIds); + @Param("arBillStatuses") List arBillStatuses, @Param("feeArBillStatus") String feeArBillStatus, + @Param("excludedBillIds") List excludedBillIds); /** 统计应收账单预览结果金额,供全选模式展示。 */ BigDecimal sumPrcOrderBillIncomeByCustomerPeriod(@Param("customerId") Long customerId, @Param("customerCode") String customerCode, @Param("orderOrTrackingNo") String orderOrTrackingNo, @Param("excludeOrderOrTrackingNo") String excludeOrderOrTrackingNo, @Param("cutoffDate") Date cutoffDate, @Param("currency") String currency, - @Param("arBillStatus") String arBillStatus); + @Param("arBillStatuses") List arBillStatuses, @Param("feeArBillStatus") String feeArBillStatus); /** 异步生成账单按 bill_id 游标读取一批来源订单。 */ List selectPrcOrderBillByCustomerPeriodCursor(@Param("customerId") Long customerId, @Param("customerCode") String customerCode, @Param("orderOrTrackingNo") String orderOrTrackingNo, @Param("cutoffDate") Date cutoffDate, @Param("currency") String currency, - @Param("arBillStatus") String arBillStatus, @Param("snapshotCreateTime") Date snapshotCreateTime, + @Param("arBillStatuses") List arBillStatuses, @Param("feeArBillStatus") String feeArBillStatus, + @Param("snapshotCreateTime") Date snapshotCreateTime, @Param("lastSourceBillId") String lastSourceBillId, @Param("billIds") List billIds, @Param("excludedBillIds") List excludedBillIds, @Param("limit") int limit); int countPrcOrderBillByCustomerPeriod(@Param("customerId") Long customerId, @Param("customerCode") String customerCode, @Param("orderOrTrackingNo") String orderOrTrackingNo, @Param("cutoffDate") Date cutoffDate, @Param("currency") String currency, - @Param("arBillStatus") String arBillStatus, @Param("snapshotCreateTime") Date snapshotCreateTime, + @Param("arBillStatuses") List arBillStatuses, @Param("feeArBillStatus") String feeArBillStatus, + @Param("snapshotCreateTime") Date snapshotCreateTime, @Param("billIds") List billIds, @Param("excludedBillIds") List excludedBillIds); /** 按生成账单时用户勾选的订单读取,并再次校验账单筛选条件。 */ @@ -62,11 +65,11 @@ public interface PrcOrderBillMapper @Param("customerCode") String customerCode, @Param("orderOrTrackingNo") String orderOrTrackingNo, @Param("cutoffDate") Date cutoffDate, @Param("currency") String currency, @Param("billIds") List billIds, - @Param("arBillStatus") String arBillStatus); + @Param("arBillStatuses") List arBillStatuses, @Param("feeArBillStatus") String feeArBillStatus); /** 原子占用订单,防止同一订单被并发生成到多张应收账单。 */ public int claimArBillOrders(@Param("billIds") List billIds, @Param("operator") String operator, - @Param("fromStatus") String fromStatus, @Param("toStatus") String toStatus); + @Param("arBillStatuses") List arBillStatuses, @Param("toStatus") String toStatus); /** 作废账单后释放订单,允许后续重新生成新版本账单。 */ public int releaseArBillOrders(@Param("billIds") List billIds, @Param("operator") String operator, @@ -75,6 +78,12 @@ public interface PrcOrderBillMapper int releaseArBillOrdersByArBillId(@Param("arBillId") Long arBillId, @Param("operator") String operator, @Param("fromStatus") String fromStatus, @Param("toStatus") String toStatus); + /** 根据费用明细状态汇总订单的 UNBILLED/PARTIAL/BILLED 状态。 */ + int refreshArBillStatusByBillIds(@Param("billIds") List billIds); + + /** 按应收账单关联的来源订单批量汇总状态,适用于大账单清理和作废。 */ + int refreshArBillStatusByArBillId(@Param("arBillId") Long arBillId); + public int insertPrcOrderBill(PrcOrderBill bill); public int updatePrcOrderBill(PrcOrderBill bill); diff --git a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/service/impl/PrcOrderBillServiceImpl.java b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/service/impl/PrcOrderBillServiceImpl.java index f1f47297..67fa1392 100644 --- a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/service/impl/PrcOrderBillServiceImpl.java +++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/service/impl/PrcOrderBillServiceImpl.java @@ -16,6 +16,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; +import com.ruoyi.ar.enums.ArIncomeBillFeeStatus; import com.ruoyi.ar.enums.ArOrderBillStatus; import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.utils.MessageUtils; @@ -253,7 +254,7 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService fee.setCurrency(currency); fee.setRoundMode(StringUtils.isEmpty(surcharge.getRoundMode()) ? bill.getRoundMode() : surcharge.getRoundMode()); fee.setRemark(request.getRemark()); - fee.setArBillStatus(ArOrderBillStatus.UNBILLED.getCode()); + fee.setArBillStatus(ArIncomeBillFeeStatus.UNBILLED.getCode()); fee.setManualFlag("1"); incomeBillFeeMapper.insertPrcIncomeBillFee(fee); @@ -349,7 +350,7 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService { throw new ServiceException(MessageUtils.message("price.bill.fee.notManual")); } - if (ArOrderBillStatus.BILLED.getCode().equals(fee.getArBillStatus())) + if (ArIncomeBillFeeStatus.BILLED.getCode().equals(fee.getArBillStatus())) { throw new ServiceException(MessageUtils.message("price.bill.fee.alreadyBilled")); } @@ -400,6 +401,7 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService ? BillCustomerActiveStatus.ACTIVE.getCode() : bill.getCustomerActiveStatus()); bill.setCreditArrearsStatus(creditArrearsStatus); orderBillMapper.updatePrcOrderBill(bill); + orderBillMapper.refreshArBillStatusByBillIds(Arrays.asList(bill.getBillId())); return toResult(bill, fees, payBillFeeMapper.selectByBillId(bill.getBillId())); } @@ -639,6 +641,7 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService { incomeBillFeeMapper.insertBatch(fees); } + orderBillMapper.refreshArBillStatusByBillIds(Arrays.asList(bill.getBillId())); PrcPayBill payBill = persistPayBill(bill); List payFees = toPayFeeRows(bill.getBillId(), payBill == null ? null : payBill.getPayBillId(), result); @@ -810,7 +813,7 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService return; } int billedFreightCount = incomeBillFeeMapper.countByBillIdAndArBillStatusAndFeeType( - existed.getBillId(), ArOrderBillStatus.BILLED.getCode(), "BASIC_FREIGHT"); + existed.getBillId(), ArIncomeBillFeeStatus.BILLED.getCode(), "BASIC_FREIGHT"); if (billedFreightCount > 0) { throw new ServiceException(MessageUtils.message("price.bill.alreadyInArBill", existed.getOrderNo())); @@ -842,7 +845,7 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService fee.setCurrency(line.getCurrency()); fee.setRoundMode(line.getRoundMode()); fee.setRemark(line.getRemark()); - fee.setArBillStatus(ArOrderBillStatus.UNBILLED.getCode()); + fee.setArBillStatus(ArIncomeBillFeeStatus.UNBILLED.getCode()); fee.setManualFlag("0"); fees.add(fee); } diff --git a/bms-back/ruoyi-system/src/main/resources/mapper/price/PrcIncomeBillFeeMapper.xml b/bms-back/ruoyi-system/src/main/resources/mapper/price/PrcIncomeBillFeeMapper.xml index eb32a75b..afe80c2f 100644 --- a/bms-back/ruoyi-system/src/main/resources/mapper/price/PrcIncomeBillFeeMapper.xml +++ b/bms-back/ruoyi-system/src/main/resources/mapper/price/PrcIncomeBillFeeMapper.xml @@ -39,6 +39,18 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" order by bill_id, field(fee_type, 'BASIC_FREIGHT', 'REGISTRATION_FEE', 'SURCHARGE'), fee_id + + where order_no = #{orderNo} @@ -245,85 +268,106 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - where customer_id = #{customerId} + where p.customer_id = #{customerId} - and customer_code like concat('%', #{customerCode}, '%') + and p.customer_code like concat('%', #{customerCode}, '%') - and (order_no like concat('%', #{orderOrTrackingNo}, '%') - or tracking_no like concat('%', #{orderOrTrackingNo}, '%') - or transfer_no like concat('%', #{orderOrTrackingNo}, '%')) + and (p.order_no like concat('%', #{orderOrTrackingNo}, '%') + or p.tracking_no like concat('%', #{orderOrTrackingNo}, '%') + or p.transfer_no like concat('%', #{orderOrTrackingNo}, '%')) - and biz_date <= #{cutoffDate} - and ar_bill_status = #{arBillStatus} + and p.biz_date <= #{cutoffDate} + and p.ar_bill_status in + + #{status} + + and exists ( + select 1 from prc_income_bill_fee f + where f.bill_id = p.bill_id + and f.ar_bill_status = #{feeArBillStatus} + ) - and currency = #{currency} + and p.currency = #{currency} - and (create_time is null or create_time <= #{snapshotCreateTime}) + and (p.create_time is null or p.create_time <= #{snapshotCreateTime}) - and bill_id in + and p.bill_id in #{billId} - and bill_id not in + and p.bill_id not in #{excludedBillId} @@ -332,43 +376,51 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" @@ -479,11 +531,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" where bill_id = #{billId} - + update prc_order_bill set ar_bill_status = #{toStatus}, updated_by = #{operator}, update_time = sysdate() - where ar_bill_status = #{fromStatus} + where ar_bill_status in + + #{status} + and bill_id in #{billId} @@ -507,4 +562,63 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" where d.ar_bill_id = #{arBillId} and p.ar_bill_status = #{fromStatus} + + + + update prc_order_bill o + left join ( + select bill_id, + count(1) as fee_count, + sum(case when ar_bill_status = 'BILLED' then 1 else 0 end) as billed_count, + sum(case when ar_bill_status = 'UNBILLED' then 1 else 0 end) as unbilled_count + from prc_income_bill_fee + where bill_id in + + #{billId} + + group by bill_id + ) f on f.bill_id = o.bill_id + set o.ar_bill_status = case + when f.bill_id is null then 'UNBILLED' + when f.unbilled_count > 0 and f.billed_count > 0 then 'PARTIAL' + when f.unbilled_count > 0 then 'UNBILLED' + when f.billed_count = f.fee_count then 'BILLED' + else 'UNBILLED' + end, + o.update_time = sysdate() + where o.bill_id in + + #{billId} + + + + + update prc_order_bill o + inner join ( + select distinct d.prc_order_bill_id + from ar_bill_detail d + where d.ar_bill_id = #{arBillId} + ) source_order on source_order.prc_order_bill_id = o.bill_id + left join ( + select f.bill_id, + count(1) as fee_count, + sum(case when f.ar_bill_status = 'BILLED' then 1 else 0 end) as billed_count, + sum(case when f.ar_bill_status = 'UNBILLED' then 1 else 0 end) as unbilled_count + from prc_income_bill_fee f + inner join ( + select distinct d2.prc_order_bill_id + from ar_bill_detail d2 + where d2.ar_bill_id = #{arBillId} + ) source_order2 on source_order2.prc_order_bill_id = f.bill_id + group by f.bill_id + ) fee_summary on fee_summary.bill_id = o.bill_id + set o.ar_bill_status = case + when fee_summary.bill_id is null then 'UNBILLED' + when fee_summary.unbilled_count > 0 and fee_summary.billed_count > 0 then 'PARTIAL' + when fee_summary.unbilled_count > 0 then 'UNBILLED' + when fee_summary.billed_count = fee_summary.fee_count then 'BILLED' + else 'UNBILLED' + end, + o.update_time = sysdate() + diff --git a/bms-back/sql/ar/ar_order_fee_bill_status_migration.sql b/bms-back/sql/ar/ar_order_fee_bill_status_migration.sql new file mode 100644 index 00000000..93695b9d --- /dev/null +++ b/bms-back/sql/ar/ar_order_fee_bill_status_migration.sql @@ -0,0 +1,124 @@ +-- 应收账单来源费用状态迁移(统一脚本) +-- +-- 目标: +-- 1. prc_order_bill.ar_bill_status:UNBILLED 未出账、PARTIAL 部分出账、BILLED 已出账; +-- 2. prc_income_bill_fee.ar_bill_status:UNBILLED 未生成、BILLED 已生成; +-- 3. 按现有有效应收账单关联回填费用状态,再由费用状态汇总回填订单状态。 +-- +-- 脚本可重复执行。执行前请备份数据库,并确认 ar_bill_detail_fee.prc_income_bill_fee_id +-- 已经正确保存来源费用 ID。 +SET NAMES utf8mb4; +SET @db_name := DATABASE(); + +-- 1. 来源计费订单状态字段及索引 +SET @has_order_status := ( + SELECT COUNT(*) + FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = @db_name + AND TABLE_NAME = 'prc_order_bill' + AND COLUMN_NAME = 'ar_bill_status' +); +SET @sql := IF(@has_order_status = 0, + 'ALTER TABLE prc_order_bill ADD COLUMN ar_bill_status VARCHAR(20) NOT NULL DEFAULT ''UNBILLED'' COMMENT ''应收账单状态:UNBILLED未出账/PARTIAL部分出账/BILLED已出账'' AFTER currency', + 'SELECT 1'); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +ALTER TABLE prc_order_bill + MODIFY COLUMN ar_bill_status VARCHAR(20) NOT NULL DEFAULT 'UNBILLED' + COMMENT '应收账单状态:UNBILLED未出账/PARTIAL部分出账/BILLED已出账'; + +SET @has_order_status_index := ( + SELECT COUNT(*) + FROM information_schema.STATISTICS + WHERE TABLE_SCHEMA = @db_name + AND TABLE_NAME = 'prc_order_bill' + AND INDEX_NAME = 'idx_bill_ar_status' +); +SET @sql := IF(@has_order_status_index = 0, + 'ALTER TABLE prc_order_bill ADD KEY idx_bill_ar_status (customer_id, currency, ar_bill_status, biz_date)', + 'SELECT 1'); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +-- 2. 来源费用状态字段及索引(兼容同事已提前加列的数据库) +SET @has_fee_status := ( + SELECT COUNT(*) + FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = @db_name + AND TABLE_NAME = 'prc_income_bill_fee' + AND COLUMN_NAME = 'ar_bill_status' +); +SET @sql := IF(@has_fee_status = 0, + 'ALTER TABLE prc_income_bill_fee ADD COLUMN ar_bill_status VARCHAR(20) NOT NULL DEFAULT ''UNBILLED'' COMMENT ''是否生成账单状态:UNBILLED未生成/BILLED已生成'' AFTER remark', + 'SELECT 1'); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +ALTER TABLE prc_income_bill_fee + MODIFY COLUMN ar_bill_status VARCHAR(20) NOT NULL DEFAULT 'UNBILLED' + COMMENT '是否生成账单状态:UNBILLED未生成/BILLED已生成'; + +SET @has_fee_status_index := ( + SELECT COUNT(*) + FROM information_schema.STATISTICS + WHERE TABLE_SCHEMA = @db_name + AND TABLE_NAME = 'prc_income_bill_fee' + AND INDEX_NAME = 'idx_income_fee_ar_status' +); +SET @sql := IF(@has_fee_status_index = 0, + 'ALTER TABLE prc_income_bill_fee ADD KEY idx_income_fee_ar_status (ar_bill_status)', + 'SELECT 1'); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @has_fee_bill_status_index := ( + SELECT COUNT(*) + FROM information_schema.STATISTICS + WHERE TABLE_SCHEMA = @db_name + AND TABLE_NAME = 'prc_income_bill_fee' + AND INDEX_NAME = 'idx_income_fee_bill_ar_status' +); +SET @sql := IF(@has_fee_bill_status_index = 0, + 'ALTER TABLE prc_income_bill_fee ADD KEY idx_income_fee_bill_ar_status (bill_id, ar_bill_status)', + 'SELECT 1'); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +-- 3. 根据有效应收账单关联回填费用状态。 +-- 作废账单不再占用来源费用;同一费用只要仍关联一张有效账单,就保持 BILLED。 +UPDATE prc_income_bill_fee f +LEFT JOIN ( + SELECT DISTINCT df.prc_income_bill_fee_id AS fee_id + FROM ar_bill_detail_fee df + INNER JOIN ar_bill_detail d ON d.detail_id = df.detail_id + INNER JOIN ar_bill b ON b.ar_bill_id = d.ar_bill_id + WHERE b.status <> 'VOID' +) active_fee ON active_fee.fee_id = f.fee_id +SET f.ar_bill_status = CASE + WHEN active_fee.fee_id IS NOT NULL THEN 'BILLED' + ELSE 'UNBILLED' +END; + +-- 4. 根据费用状态回填订单状态。 +UPDATE prc_order_bill o +LEFT JOIN ( + SELECT bill_id, + COUNT(*) AS fee_count, + SUM(CASE WHEN ar_bill_status = 'BILLED' THEN 1 ELSE 0 END) AS billed_count, + SUM(CASE WHEN ar_bill_status = 'UNBILLED' THEN 1 ELSE 0 END) AS unbilled_count + FROM prc_income_bill_fee + GROUP BY bill_id +) fee_summary ON fee_summary.bill_id = o.bill_id +SET o.ar_bill_status = CASE + WHEN fee_summary.bill_id IS NULL THEN 'UNBILLED' + WHEN fee_summary.unbilled_count > 0 AND fee_summary.billed_count > 0 THEN 'PARTIAL' + WHEN fee_summary.unbilled_count > 0 THEN 'UNBILLED' + WHEN fee_summary.billed_count = fee_summary.fee_count THEN 'BILLED' + ELSE 'UNBILLED' +END;