diff --git a/issues/2026-01-23_17-04-15-whitelist-order-status-complete.csv b/issues/2026-01-23_17-04-15-whitelist-order-status-complete.csv index 2e5c86853..1ceefc0bb 100644 --- a/issues/2026-01-23_17-04-15-whitelist-order-status-complete.csv +++ b/issues/2026-01-23_17-04-15-whitelist-order-status-complete.csv @@ -1,7 +1,7 @@ "id","priority","phase","area","title","description","acceptance_criteria","test_mcp","review_initial_requirements","review_regression_requirements","dev_state","review_initial_state","review_regression_state","git_state","owner","refs","notes" "WLOS-010","P0","1","backend","澄清白名单修正规则与验收口径","明确白名单支付订单的判定字段与状态修正边界,形成可实现/可测试的规则清单。","明确并在PR/变更说明中写清:1) 白名单判定字段(order_basic_info.pay_mode 是否足够,是否需校验 order_pay_record.pay_mode);2) 仅当 payMode=3 且订单状态为 ABNORMAL(4) 或 STAY_SETTLEMENT(2) 才允许修正;3) 修正为 ORDER_COMPLETE(6) 时需要同步的字段列表(settlement_time/金额字段/pay_status 等)与缺失数据的默认策略。","AUTOSERVER","Review过程中持续检查:规则是否与枚举一致;字段同步是否会影响分账/退款/通知/解锁等后续链路;是否需要审计/日志记录原状态。","完成后复核:规则与验收口径在代码/测试/接口行为三者一致;对非白名单/非目标状态无副作用。","未开始","未开始","未开始","未提交","","plan/2026-01-23_16-45-56-whitelist-order-status-complete.md:16; plan/2026-01-23_16-45-56-whitelist-order-status-complete.md:17; plan/2026-01-23_16-45-56-whitelist-order-status-complete.md:18; jsowell-common/src/main/java/com/jsowell/common/enums/ykc/OrderPayModeEnum.java:11; jsowell-common/src/main/java/com/jsowell/common/enums/ykc/OrderStatusEnum.java:12; jsowell-common/src/main/java/com/jsowell/common/enums/ykc/OrderStatusEnum.java:14","" "WLOS-020","P0","2","backend","定位异常/待结算状态写入点并梳理影响面","确认订单进入 ABNORMAL/待结算/完成 的关键写入路径,决定修正应放在写入点还是补偿点,并列出受影响链路。","输出可审计的影响面清单(写在PR描述或变更说明中):至少包含白名单批量入口、订单标记异常位置、结算后落库完成位置;明确最终选择的修正插入点(例如统一补偿方法 + 入口调用)。","AUTOSERVER","检查是否存在并发/重复调用场景;是否有事务边界;是否会触发远程停机/拉取交易记录等外部依赖;异常原因字段/审计日志是否保留。","回归时覆盖:修正路径不会破坏正常结算完成流程;不会将真实异常订单误判为完成。","未开始","未开始","未开始","未提交","","plan/2026-01-23_16-45-56-whitelist-order-status-complete.md:20; plan/2026-01-23_16-45-56-whitelist-order-status-complete.md:22; jsowell-admin/src/main/java/com/jsowell/service/TempService.java:1504; jsowell-admin/src/main/java/com/jsowell/api/uniapp/customer/TempController.java:1043; jsowell-admin/src/main/java/com/jsowell/service/OrderService.java:528; jsowell-pile/src/main/java/com/jsowell/pile/service/programlogic/AbstractProgramLogic.java:247","" -"WLOS-030","P0","3","backend","设计并落地统一的白名单订单完成补偿方法","在订单领域服务中新增可复用且幂等的补偿方法,专门用于白名单订单从异常/待结算修正为完成,并补齐必要字段。","新增补偿方法(如 completeWhitelistOrderIfNeeded(orderCode) 或等价实现):1) 判定 payMode=3 且状态在 {ABNORMAL, STAY_SETTLEMENT};2) 幂等(重复调用不产生额外副作用);3) 更新为 ORDER_COMPLETE 并处理 settlement_time(为空则置当前时间)及约定的金额/状态字段默认值;4) 记录可追溯日志(至少包含订单号、原状态、新状态)。","AUTOSERVER","检查金额字段默认策略是否会影响对账;时间字段是否符合既有语义;方法是否避免不必要的外部调用(如远程停机/拉交易)。","回归验证:在真实/模拟数据上重复调用、并发调用无异常;下游依赖(通知/解锁/MQ/分账退款)要么保持一致,要么明确不触发并有说明。","进行中","进行中","未开始","未提交","","plan/2026-01-23_16-45-56-whitelist-order-status-complete.md:27; plan/2026-01-23_16-45-56-whitelist-order-status-complete.md:30; plan/2026-01-23_16-45-56-whitelist-order-status-complete.md:54; jsowell-common/src/main/java/com/jsowell/common/enums/ykc/OrderStatusEnum.java:14; jsowell-common/src/main/java/com/jsowell/common/enums/ykc/OrderPayModeEnum.java:11","picked_reason:unblocks core capability for WLOS-040/WLOS-050" +"WLOS-030","P0","3","backend","设计并落地统一的白名单订单完成补偿方法","在订单领域服务中新增可复用且幂等的补偿方法,专门用于白名单订单从异常/待结算修正为完成,并补齐必要字段。","新增补偿方法(如 completeWhitelistOrderIfNeeded(orderCode) 或等价实现):1) 判定 payMode=3 且状态在 {ABNORMAL, STAY_SETTLEMENT};2) 幂等(重复调用不产生额外副作用);3) 更新为 ORDER_COMPLETE 并处理 settlement_time(为空则置当前时间)及约定的金额/状态字段默认值;4) 记录可追溯日志(至少包含订单号、原状态、新状态)。","AUTOSERVER","检查金额字段默认策略是否会影响对账;时间字段是否符合既有语义;方法是否避免不必要的外部调用(如远程停机/拉交易)。","回归验证:在真实/模拟数据上重复调用、并发调用无异常;下游依赖(通知/解锁/MQ/分账退款)要么保持一致,要么明确不触发并有说明。","已完成","已完成","已完成","已提交","","plan/2026-01-23_16-45-56-whitelist-order-status-complete.md:27; plan/2026-01-23_16-45-56-whitelist-order-status-complete.md:30; plan/2026-01-23_16-45-56-whitelist-order-status-complete.md:54; jsowell-common/src/main/java/com/jsowell/common/enums/ykc/OrderStatusEnum.java:14; jsowell-common/src/main/java/com/jsowell/common/enums/ykc/OrderPayModeEnum.java:11; jsowell-admin/src/main/java/com/jsowell/service/OrderService.java:1629; jsowell-admin/src/main/java/com/jsowell/service/WhitelistOrderCompletionDefaults.java:17; jsowell-admin/src/test/java/com/jsowell/service/WhitelistOrderCompletionDefaultsTest.java:1","picked_reason:unblocks core capability for WLOS-040/WLOS-050; done_at:2026-01-28; evidence:added completeWhitelistOrderIfNeeded + defaults helper + unit test; validation_limited:jsowell-admin module compilation fails due to missing types (e.g., CarNumberBindDTO, com.jsowell.pile.jcpp.*), tests not runnable until fixed; manual_test:mvn -pl jsowell-admin test -Dtest=WhitelistOrderCompletionDefaultsTest; risk:low additive method not yet wired into flows" "WLOS-040","P0","4","backend","改造白名单结算入口以支持异常/待结算并复用补偿逻辑","调整 TempService.whiteListSettlement 及对应接口,使其支持 ABNORMAL/待结算两种状态并调用统一补偿方法,避免硬编码与不必要外部依赖。","1) TempService.whiteListSettlement 支持状态=ABNORMAL 或 STAY_SETTLEMENT;2) payMode 判断改为使用 OrderPayModeEnum.PAYMENT_OF_WHITELIST.getValue();3) 默认走统一补偿方法完成状态修正;4) 若保留 manualSettlementOrder 调用,需明确触发条件与降级策略(避免依赖外部设备/交易服务导致批量失败)。","AUTOSERVER","Review时重点:权限/入参校验是否合理;批量处理时的错误隔离与日志;是否需要对调用方预期做兼容(原先只允许异常)。","回归时验证:接口对非白名单/非目标状态不改动;批量输入中单个失败不影响其他(如有此需求则实现并说明)。","未开始","未开始","未开始","未提交","","plan/2026-01-23_16-45-56-whitelist-order-status-complete.md:35; plan/2026-01-23_16-45-56-whitelist-order-status-complete.md:39; jsowell-admin/src/main/java/com/jsowell/service/TempService.java:1504; jsowell-admin/src/main/java/com/jsowell/api/uniapp/customer/TempController.java:1043; jsowell-admin/src/main/java/com/jsowell/service/OrderService.java:749; jsowell-common/src/main/java/com/jsowell/common/enums/ykc/OrderPayModeEnum.java:11","" "WLOS-050","P0","5","backend","补齐白名单状态修正的单测/集成测试与回归场景","为白名单订单状态修正逻辑新增测试用例,确保异常/待结算均可修正且非白名单不会被误改。","新增/更新测试并通过:1) 白名单+异常(4) -> 完成(6);2) 白名单+待结算(2) -> 完成(6);3) 非白名单订单不变;4) 关键字段(如 settlement_time)按约定填充;执行 `mvn -pl jsowell-admin test` 通过。","AUTOSERVER","Review时关注:测试数据构造是否贴近真实(金额/时间/状态);是否覆盖幂等与边界输入(空字段、重复调用)。","完成后回归:全量后端测试通过;若有接口层变化,补充接口测试或Mock验证。","未开始","未开始","未开始","未提交","","plan/2026-01-23_16-45-56-whitelist-order-status-complete.md:41; plan/2026-01-23_16-45-56-whitelist-order-status-complete.md:46; jsowell-admin/src/main/java/com/jsowell/service/TempService.java:1504","" "WLOS-060","P0","6","backend","测试环境演练与上线前数据校验/回滚预案","在测试环境按订单号列表演练修正接口,校验DB字段变化,并准备可执行的回滚/审计方案。","1) 在测试环境对选定订单号列表调用 /whiteListSettlement(或等价入口)成功;2) DB 校验:order_basic_info.order_status=6 且 settlement_time/金额字段符合约定;3) 形成可执行回滚步骤(SQL/脚本/手工流程)并记录在PR描述或运维文档中;4) 日志中可追溯到修正前后状态。","AUTOE2E","Review过程中确认:是否需要灰度/限流;批量处理失败的可观测性(日志/告警);是否保留原状态用于追溯。","上线前回归:随机抽样多笔订单复测;验证不会导致下游不一致(通知/解锁/MQ/分账退款)或已明确不会触发并可接受。","未开始","未开始","未开始","未提交","","plan/2026-01-23_16-45-56-whitelist-order-status-complete.md:48; plan/2026-01-23_16-45-56-whitelist-order-status-complete.md:50; plan/2026-01-23_16-45-56-whitelist-order-status-complete.md:52; jsowell-admin/src/main/java/com/jsowell/api/uniapp/customer/TempController.java:1043","" diff --git a/jsowell-admin/src/main/java/com/jsowell/service/OrderService.java b/jsowell-admin/src/main/java/com/jsowell/service/OrderService.java index e7db09e12..0cfffad00 100644 --- a/jsowell-admin/src/main/java/com/jsowell/service/OrderService.java +++ b/jsowell-admin/src/main/java/com/jsowell/service/OrderService.java @@ -1617,6 +1617,64 @@ public class OrderService { personalChargingRecordService.insertOrUpdateSelective(record); } + /** + * 白名单订单完成补偿:仅在白名单支付(payMode=3)且订单状态为异常/待结算时,修正为订单完成。 + *
+ * 该方法为幂等:重复调用不会产生额外副作用(已完成或不符合条件将直接返回)。 + * + * @param orderCode 订单号 + * @return 是否发生了落库更新 + */ + @Transactional(rollbackFor = Exception.class) + public boolean completeWhitelistOrderIfNeeded(String orderCode) { + if (StringUtils.isBlank(orderCode)) { + log.warn("completeWhitelistOrderIfNeeded ignored, orderCode is blank"); + return false; + } + + OrderBasicInfo orderBasicInfo = orderBasicInfoService.getOrderInfoByOrderCode(orderCode); + if (orderBasicInfo == null) { + log.warn("completeWhitelistOrderIfNeeded ignored, order not found, orderCode:{}", orderCode); + return false; + } + + if (!StringUtils.equals(OrderPayModeEnum.PAYMENT_OF_WHITELIST.getValue(), orderBasicInfo.getPayMode())) { + log.info("completeWhitelistOrderIfNeeded skipped, not whitelist payMode, orderCode:{}, payMode:{}", + orderCode, orderBasicInfo.getPayMode()); + return false; + } + + String beforeStatus = orderBasicInfo.getOrderStatus(); + if (StringUtils.equals(OrderStatusEnum.ORDER_COMPLETE.getValue(), beforeStatus)) { + log.info("completeWhitelistOrderIfNeeded skipped, already complete, orderCode:{}", orderCode); + return false; + } + + boolean eligibleStatus = StringUtils.equals(OrderStatusEnum.ABNORMAL.getValue(), beforeStatus) + || StringUtils.equals(OrderStatusEnum.STAY_SETTLEMENT.getValue(), beforeStatus); + if (!eligibleStatus) { + log.info("completeWhitelistOrderIfNeeded skipped, not eligible status, orderCode:{}, status:{}", + orderCode, beforeStatus); + return false; + } + + if (orderBasicInfo.getSettlementTime() == null) { + orderBasicInfo.setSettlementTime(DateUtils.getNowDate()); + } + WhitelistOrderCompletionDefaults.apply(orderBasicInfo, orderBasicInfo.getSettlementTime()); + + int updated = orderBasicInfoService.updateOrderBasicInfo(orderBasicInfo); + try { + orderBasicInfoService.cleanCacheByOrderCode(orderCode, orderBasicInfo.getTransactionCode()); + } catch (Exception e) { + log.warn("completeWhitelistOrderIfNeeded clean cache failed, orderCode:{}", orderCode, e); + } + + log.info("completeWhitelistOrderIfNeeded updated orderCode:{}, {} -> {}", + orderCode, beforeStatus, orderBasicInfo.getOrderStatus()); + return updated > 0; + } + public static void main(String[] args) { } diff --git a/jsowell-admin/src/main/java/com/jsowell/service/WhitelistOrderCompletionDefaults.java b/jsowell-admin/src/main/java/com/jsowell/service/WhitelistOrderCompletionDefaults.java new file mode 100644 index 000000000..587dfda00 --- /dev/null +++ b/jsowell-admin/src/main/java/com/jsowell/service/WhitelistOrderCompletionDefaults.java @@ -0,0 +1,51 @@ +package com.jsowell.service; + +import com.jsowell.common.enums.ykc.OrderPayStatusEnum; +import com.jsowell.common.enums.ykc.OrderStatusEnum; +import com.jsowell.common.util.StringUtils; +import com.jsowell.pile.domain.OrderBasicInfo; + +import java.math.BigDecimal; +import java.util.Date; +import java.util.Objects; + +final class WhitelistOrderCompletionDefaults { + + private WhitelistOrderCompletionDefaults() { + } + + static void apply(OrderBasicInfo orderBasicInfo, Date now) { + Objects.requireNonNull(orderBasicInfo, "orderBasicInfo"); + + BigDecimal payAmount = orderBasicInfo.getPayAmount() != null ? orderBasicInfo.getPayAmount() : BigDecimal.ZERO; + BigDecimal orderAmount = orderBasicInfo.getOrderAmount() != null ? orderBasicInfo.getOrderAmount() : payAmount; + + if (orderAmount.compareTo(payAmount) > 0) { + orderAmount = payAmount; + } + + orderBasicInfo.setOrderAmount(orderAmount); + orderBasicInfo.setVirtualAmount(orderAmount); + orderBasicInfo.setSettleAmount(BigDecimal.ZERO); + orderBasicInfo.setActualReceivedAmount(BigDecimal.ZERO); + + if (orderBasicInfo.getRefundAmount() == null) { + BigDecimal refundAmount = payAmount.subtract(orderAmount); + if (refundAmount.compareTo(BigDecimal.ZERO) < 0) { + refundAmount = BigDecimal.ZERO; + } + orderBasicInfo.setRefundAmount(refundAmount); + } + + if (StringUtils.isBlank(orderBasicInfo.getPayStatus())) { + orderBasicInfo.setPayStatus(OrderPayStatusEnum.pay_nothing.getValue()); + } + + if (orderBasicInfo.getSettlementTime() == null && now != null) { + orderBasicInfo.setSettlementTime(now); + } + + orderBasicInfo.setOrderStatus(OrderStatusEnum.ORDER_COMPLETE.getValue()); + } +} + diff --git a/jsowell-admin/src/test/java/com/jsowell/service/WhitelistOrderCompletionDefaultsTest.java b/jsowell-admin/src/test/java/com/jsowell/service/WhitelistOrderCompletionDefaultsTest.java new file mode 100644 index 000000000..2645c5271 --- /dev/null +++ b/jsowell-admin/src/test/java/com/jsowell/service/WhitelistOrderCompletionDefaultsTest.java @@ -0,0 +1,53 @@ +package com.jsowell.service; + +import com.jsowell.common.enums.ykc.OrderPayStatusEnum; +import com.jsowell.common.enums.ykc.OrderStatusEnum; +import com.jsowell.pile.domain.OrderBasicInfo; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; +import java.util.Date; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class WhitelistOrderCompletionDefaultsTest { + + @Test + void apply_shouldFillDefaults_whenAmountsAndStatusMissing() { + OrderBasicInfo orderBasicInfo = new OrderBasicInfo(); + Date now = new Date(); + + WhitelistOrderCompletionDefaults.apply(orderBasicInfo, now); + + assertEquals(OrderStatusEnum.ORDER_COMPLETE.getValue(), orderBasicInfo.getOrderStatus()); + assertEquals(BigDecimal.ZERO, orderBasicInfo.getOrderAmount()); + assertEquals(BigDecimal.ZERO, orderBasicInfo.getVirtualAmount()); + assertEquals(BigDecimal.ZERO, orderBasicInfo.getSettleAmount()); + assertEquals(BigDecimal.ZERO, orderBasicInfo.getActualReceivedAmount()); + assertEquals(BigDecimal.ZERO, orderBasicInfo.getRefundAmount()); + assertEquals(OrderPayStatusEnum.pay_nothing.getValue(), orderBasicInfo.getPayStatus()); + assertEquals(now, orderBasicInfo.getSettlementTime()); + } + + @Test + void apply_shouldNotOverrideExistingSettlementTimeOrPayStatus() { + Date settlementTime = new Date(123456789L); + OrderBasicInfo orderBasicInfo = new OrderBasicInfo(); + orderBasicInfo.setSettlementTime(settlementTime); + orderBasicInfo.setPayStatus(OrderPayStatusEnum.paid.getValue()); + orderBasicInfo.setPayAmount(new BigDecimal("10")); + orderBasicInfo.setOrderAmount(new BigDecimal("5")); + + WhitelistOrderCompletionDefaults.apply(orderBasicInfo, new Date()); + + assertEquals(settlementTime, orderBasicInfo.getSettlementTime()); + assertEquals(OrderPayStatusEnum.paid.getValue(), orderBasicInfo.getPayStatus()); + assertEquals(new BigDecimal("5"), orderBasicInfo.getOrderAmount()); + assertEquals(new BigDecimal("5"), orderBasicInfo.getVirtualAmount()); + assertEquals(BigDecimal.ZERO, orderBasicInfo.getSettleAmount()); + assertEquals(BigDecimal.ZERO, orderBasicInfo.getActualReceivedAmount()); + assertEquals(new BigDecimal("5"), orderBasicInfo.getRefundAmount()); + assertEquals(OrderStatusEnum.ORDER_COMPLETE.getValue(), orderBasicInfo.getOrderStatus()); + } +} +