mirror of
https://codeup.aliyun.com/67c68d4e484ca2f0a13ac3c1/ydc/jsowell-charger-web.git
synced 2026-04-19 18:45:03 +08:00
Merge remote-tracking branch 'origin/dev' into dev
This commit is contained in:
48
AGENTS.md
Normal file
48
AGENTS.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# Repository Guidelines
|
||||
|
||||
This repository contains a Java 8, multi-module Spring Boot backend plus a separate Vue 2 (Vue CLI) admin UI.
|
||||
|
||||
## Project Structure & Module Organization
|
||||
|
||||
- Backend parent: `pom.xml` (aggregates the Maven modules).
|
||||
- Backend modules live under `jsowell-*/`.
|
||||
- Entry point/service: `jsowell-admin/` (Spring Boot app).
|
||||
- Shared/core: `jsowell-common/`, `jsowell-framework/`, `jsowell-system/`.
|
||||
- Business/integrations: `jsowell-pile/`, `jsowell-netty/`, `jsowell-thirdparty/`, plus schedulers/generator/settlement modules.
|
||||
- UI: `jsowell-ui/` (not part of the Maven build). Code is in `jsowell-ui/src/`, static assets in `jsowell-ui/public/`.
|
||||
- Reference material: `doc/`, `docs/`; database scripts: `sql/`; helper batch scripts: `bin/`.
|
||||
|
||||
## Build, Test, and Development Commands
|
||||
|
||||
Backend (from repo root):
|
||||
- `mvn clean compile`: compile all modules.
|
||||
- `mvn clean package -DskipTests` (or `bin\\package.bat`): build artifacts.
|
||||
- `mvn test` (or `mvn -pl jsowell-admin test`): run backend tests.
|
||||
- `mvn -pl jsowell-admin spring-boot:run -Dspring-boot.run.profiles=dev`: run the API locally.
|
||||
|
||||
UI (from `jsowell-ui/`):
|
||||
- `npm run dev`: start dev server (uses `jsowell-ui/.env.*` modes).
|
||||
- `npm run build:prd`: production build.
|
||||
- `npm run lint`: ESLint on `src/`.
|
||||
|
||||
## Coding Style & Naming Conventions
|
||||
|
||||
- Java: follow existing conventions (4 spaces; `PascalCase` types; `camelCase` methods; `UPPER_SNAKE_CASE` constants). Keep changes scoped to one module when possible.
|
||||
- UI: follow `jsowell-ui/.editorconfig` + `jsowell-ui/.eslintrc.js` (2 spaces, single quotes, no semicolons). Run `npm run lint` before PRs.
|
||||
|
||||
## Testing Guidelines
|
||||
|
||||
- Backend tests are primarily in `jsowell-admin/src/test/java` and use Spring Boot + JUnit.
|
||||
- Prefer `*Test.java` naming and avoid tests that depend on external services unless clearly marked/configured.
|
||||
|
||||
## Commit & Pull Request Guidelines
|
||||
|
||||
- Commit subjects commonly use short prefixes: `add ...`, `update ...`, `bugfix ...`. Keep them under ~72 chars; add module context when helpful (example: `jsowell-ui: update login flow`).
|
||||
- PRs should target `dev` unless agreed otherwise, include a clear description, and link the relevant issue/ticket.
|
||||
- Include screenshots for UI changes and migration notes for DB/API changes (`sql/`, request/response examples).
|
||||
|
||||
## Security & Configuration Notes
|
||||
|
||||
- Environment config lives in `jsowell-admin/src/main/resources/application-*.yml` and `jsowell-ui/.env.*`.
|
||||
- Do not commit real secrets/keys; use placeholders and local overrides.
|
||||
- See `CLAUDE.md` for deeper architecture and operational notes.
|
||||
@@ -0,0 +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; plan/2026-01-23_16-45-56-whitelist-order-status-complete.md:20; jsowell-common/src/main/java/com/jsowell/common/enums/ykc/OrderPayModeEnum.java:11; jsowell-common/src/main/java/com/jsowell/common/enums/ykc/OrderPayRecordEnum.java:8; 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; jsowell-pile/src/main/java/com/jsowell/pile/service/impl/OrderBasicInfoServiceImpl.java:1244; jsowell-pile/src/main/java/com/jsowell/pile/service/impl/OrderBasicInfoServiceImpl.java:5618; jsowell-pile/src/main/java/com/jsowell/pile/service/impl/OrderBasicInfoServiceImpl.java:5693","picked_reason:define concrete rule set before wiring behavior in WLOS-040; done_at:2026-01-28; evidence:documented executable rule set in plan/2026-01-23_16-45-56-whitelist-order-status-complete.md; risk:low doc-only"
|
||||
"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; plan/2026-01-23_16-45-56-whitelist-order-status-complete.md:42; plan/2026-01-23_16-45-56-whitelist-order-status-complete.md:51; 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-netty/src/main/java/com/jsowell/netty/strategy/ykc/RemoteStopChargingStrategy.java:85; jsowell-netty/src/main/java/com/jsowell/netty/strategy/ykc/TransactionRecordsStrategy.java:617; jsowell-netty/src/main/java/com/jsowell/netty/strategy/ykc/TransactionRecordsStrategy.java:642; jsowell-pile/src/main/java/com/jsowell/pile/service/programlogic/AbstractProgramLogic.java:247","picked_reason:map status write paths before changing TempService入口逻辑; done_at:2026-01-28; evidence:documented status write paths + insertion point decision in plan/2026-01-23_16-45-56-whitelist-order-status-complete.md; risk:low doc-only"
|
||||
"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/service/TempService.java:1516; jsowell-admin/src/main/java/com/jsowell/service/TempService.java:1525; jsowell-admin/src/main/java/com/jsowell/api/uniapp/customer/TempController.java:1043; jsowell-admin/src/main/java/com/jsowell/service/OrderService.java:1629; jsowell-common/src/main/java/com/jsowell/common/enums/ykc/OrderPayModeEnum.java:11","picked_reason:wire whitelist入口 to unified compensation method; done_at:2026-01-28; evidence:whiteListSettlement now allows ABNORMAL/STAY_SETTLEMENT + uses OrderPayModeEnum + delegates to completeWhitelistOrderIfNeeded; 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:POST /whiteListSettlement with whitelist orders in status 4/2 then verify order_basic_info.order_status=6 and settlement_time set when null; risk:medium endpoint behavior changed (no longer calls manualSettlementOrder/remote stop)"
|
||||
"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; jsowell-admin/src/main/java/com/jsowell/service/TempService.java:1525; jsowell-admin/src/main/java/com/jsowell/service/OrderService.java:1629; jsowell-admin/src/test/java/com/jsowell/service/OrderServiceWhitelistCompletionTest.java:1; jsowell-admin/src/test/java/com/jsowell/service/WhitelistOrderCompletionDefaultsTest.java:1","picked_reason:add tests for whitelist compensation + rerun mvn with -am; done_at:2026-01-28; evidence:added unit tests covering whitelist ABNORMAL/STAY_SETTLEMENT -> COMPLETE and non-whitelist no-op; validation_limited:mvn build fails in jsowell-common with javac NoSuchFieldError (JDK/toolchain mismatch), cannot run tests in this environment; manual_test:set JAVA_HOME to JDK8 then run mvn -pl jsowell-admin -am test; risk:low tests only"
|
||||
"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:77; plan/2026-01-23_16-45-56-whitelist-order-status-complete.md:88; jsowell-admin/src/main/java/com/jsowell/api/uniapp/customer/TempController.java:54; jsowell-admin/src/main/java/com/jsowell/api/uniapp/customer/TempController.java:1043; jsowell-admin/src/main/java/com/jsowell/service/OrderService.java:1673","picked_reason:write executable manual runbook + rollback plan for test/prod; done_at:2026-01-28; evidence:added runnable rehearsal + rollback runbook in plan/2026-01-23_16-45-56-whitelist-order-status-complete.md; validation_limited:no access to test environment/DB/logs from this session to execute rehearsal; manual_test:follow plan runbook (snapshot DB -> POST /temp/whiteListSettlement -> verify SQL/logs -> rollback if needed); risk:medium rehearsal not executed yet"
|
||||
|
@@ -4,6 +4,7 @@ import com.alibaba.fastjson2.JSON;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.jsowell.common.UserAgentUtils;
|
||||
import com.jsowell.common.annotation.Anonymous;
|
||||
import com.jsowell.common.constant.Constants;
|
||||
import com.jsowell.common.core.controller.BaseController;
|
||||
import com.jsowell.common.core.redis.RedisCache;
|
||||
import com.jsowell.common.enums.adapay.AdapayPayChannelEnum;
|
||||
@@ -275,8 +276,13 @@ public class PayController extends BaseController {
|
||||
throw new BusinessException(ReturnCodeEnum.CODE_TOKEN_ERROR);
|
||||
}
|
||||
dto.setMemberId(memberId);
|
||||
// 判断appid,看是否为万车充,如果为万车充小程序,直接返回,不进行充值余额
|
||||
String appId = request.getHeader("appId");
|
||||
if (StringUtils.equals(appId, Constants.DEFAULT_APP_ID)) {
|
||||
return new RestApiResponse<>(ReturnCodeEnum.CODE_FUNCTION_IS_UNAVAILABLE);
|
||||
}
|
||||
// 设置appId
|
||||
dto.setWechatAppId(request.getHeader("appId"));
|
||||
dto.setWechatAppId(appId);
|
||||
// 获取openId
|
||||
MemberBasicInfo memberBasicInfo = memberBasicInfoService.selectInfoByMemberId(memberId);
|
||||
if (memberBasicInfo == null) {
|
||||
|
||||
@@ -1617,6 +1617,64 @@ public class OrderService {
|
||||
personalChargingRecordService.insertOrUpdateSelective(record);
|
||||
}
|
||||
|
||||
/**
|
||||
* 白名单订单完成补偿:仅在白名单支付(payMode=3)且订单状态为异常/待结算时,修正为订单完成。
|
||||
* <p>
|
||||
* 该方法为幂等:重复调用不会产生额外副作用(已完成或不符合条件将直接返回)。
|
||||
*
|
||||
* @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) {
|
||||
|
||||
}
|
||||
|
||||
@@ -1513,21 +1513,16 @@ public class TempService {
|
||||
throw new RuntimeException("订单号不存在:" + orderCode);
|
||||
}
|
||||
|
||||
if (!Objects.equals(orderBasicInfo.getOrderStatus(), OrderStatusEnum.ABNORMAL.getValue())) {
|
||||
if (!Objects.equals(orderBasicInfo.getOrderStatus(), OrderStatusEnum.ABNORMAL.getValue())
|
||||
&& !Objects.equals(orderBasicInfo.getOrderStatus(), OrderStatusEnum.STAY_SETTLEMENT.getValue())) {
|
||||
throw new RuntimeException("订单不是异常订单:" + orderCode);
|
||||
}
|
||||
|
||||
if (!Objects.equals(orderBasicInfo.getPayMode(), "3")) {
|
||||
if (!Objects.equals(orderBasicInfo.getPayMode(), OrderPayModeEnum.PAYMENT_OF_WHITELIST.getValue())) {
|
||||
throw new RuntimeException("订单不是白名单支付方式:" + orderCode);
|
||||
}
|
||||
|
||||
ManualSettlementDTO build = ManualSettlementDTO.builder()
|
||||
.chargingDegree("0")
|
||||
.chargingAmount("0")
|
||||
.orderCode(orderCode)
|
||||
.build();
|
||||
|
||||
orderService.manualSettlementOrder(build);
|
||||
orderService.completeWhitelistOrderIfNeeded(orderCode);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
package com.jsowell.service;
|
||||
|
||||
import com.jsowell.common.enums.ykc.OrderPayModeEnum;
|
||||
import com.jsowell.common.enums.ykc.OrderStatusEnum;
|
||||
import com.jsowell.pile.domain.OrderBasicInfo;
|
||||
import com.jsowell.pile.service.OrderBasicInfoService;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
class OrderServiceWhitelistCompletionTest {
|
||||
|
||||
private static OrderService newOrderService(OrderBasicInfoService orderBasicInfoService) {
|
||||
OrderService service = new OrderService();
|
||||
setField(service, "orderBasicInfoService", orderBasicInfoService);
|
||||
return service;
|
||||
}
|
||||
|
||||
private static void setField(Object target, String fieldName, Object value) {
|
||||
try {
|
||||
Field field = target.getClass().getDeclaredField(fieldName);
|
||||
field.setAccessible(true);
|
||||
field.set(target, value);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void completeWhitelistOrderIfNeeded_shouldComplete_whenAbnormal() {
|
||||
OrderBasicInfoService orderBasicInfoService = Mockito.mock(OrderBasicInfoService.class);
|
||||
|
||||
OrderBasicInfo orderBasicInfo = new OrderBasicInfo();
|
||||
orderBasicInfo.setOrderCode("O1");
|
||||
orderBasicInfo.setPayMode(OrderPayModeEnum.PAYMENT_OF_WHITELIST.getValue());
|
||||
orderBasicInfo.setOrderStatus(OrderStatusEnum.ABNORMAL.getValue());
|
||||
orderBasicInfo.setPayAmount(new BigDecimal("10"));
|
||||
|
||||
when(orderBasicInfoService.getOrderInfoByOrderCode("O1")).thenReturn(orderBasicInfo);
|
||||
when(orderBasicInfoService.updateOrderBasicInfo(any(OrderBasicInfo.class))).thenReturn(1);
|
||||
|
||||
OrderService service = newOrderService(orderBasicInfoService);
|
||||
|
||||
boolean updated = service.completeWhitelistOrderIfNeeded("O1");
|
||||
|
||||
assertTrue(updated);
|
||||
assertEquals(OrderStatusEnum.ORDER_COMPLETE.getValue(), orderBasicInfo.getOrderStatus());
|
||||
assertNotNull(orderBasicInfo.getSettlementTime());
|
||||
assertEquals(new BigDecimal("10"), orderBasicInfo.getOrderAmount());
|
||||
assertEquals(new BigDecimal("10"), orderBasicInfo.getVirtualAmount());
|
||||
assertEquals(BigDecimal.ZERO, orderBasicInfo.getSettleAmount());
|
||||
assertEquals(BigDecimal.ZERO, orderBasicInfo.getActualReceivedAmount());
|
||||
|
||||
verify(orderBasicInfoService).updateOrderBasicInfo(orderBasicInfo);
|
||||
verify(orderBasicInfoService).cleanCacheByOrderCode("O1", null);
|
||||
}
|
||||
|
||||
@Test
|
||||
void completeWhitelistOrderIfNeeded_shouldComplete_whenStaySettlement() {
|
||||
OrderBasicInfoService orderBasicInfoService = Mockito.mock(OrderBasicInfoService.class);
|
||||
|
||||
OrderBasicInfo orderBasicInfo = new OrderBasicInfo();
|
||||
orderBasicInfo.setOrderCode("O2");
|
||||
orderBasicInfo.setPayMode(OrderPayModeEnum.PAYMENT_OF_WHITELIST.getValue());
|
||||
orderBasicInfo.setOrderStatus(OrderStatusEnum.STAY_SETTLEMENT.getValue());
|
||||
|
||||
when(orderBasicInfoService.getOrderInfoByOrderCode("O2")).thenReturn(orderBasicInfo);
|
||||
when(orderBasicInfoService.updateOrderBasicInfo(any(OrderBasicInfo.class))).thenReturn(1);
|
||||
|
||||
OrderService service = newOrderService(orderBasicInfoService);
|
||||
|
||||
boolean updated = service.completeWhitelistOrderIfNeeded("O2");
|
||||
|
||||
assertTrue(updated);
|
||||
assertEquals(OrderStatusEnum.ORDER_COMPLETE.getValue(), orderBasicInfo.getOrderStatus());
|
||||
assertNotNull(orderBasicInfo.getSettlementTime());
|
||||
|
||||
verify(orderBasicInfoService).updateOrderBasicInfo(orderBasicInfo);
|
||||
}
|
||||
|
||||
@Test
|
||||
void completeWhitelistOrderIfNeeded_shouldNoop_whenNotWhitelistPayMode() {
|
||||
OrderBasicInfoService orderBasicInfoService = Mockito.mock(OrderBasicInfoService.class);
|
||||
|
||||
OrderBasicInfo orderBasicInfo = new OrderBasicInfo();
|
||||
orderBasicInfo.setOrderCode("O3");
|
||||
orderBasicInfo.setPayMode(OrderPayModeEnum.PAYMENT_OF_WECHATPAY.getValue());
|
||||
orderBasicInfo.setOrderStatus(OrderStatusEnum.ABNORMAL.getValue());
|
||||
|
||||
when(orderBasicInfoService.getOrderInfoByOrderCode("O3")).thenReturn(orderBasicInfo);
|
||||
|
||||
OrderService service = newOrderService(orderBasicInfoService);
|
||||
|
||||
boolean updated = service.completeWhitelistOrderIfNeeded("O3");
|
||||
|
||||
assertFalse(updated);
|
||||
verify(orderBasicInfoService, never()).updateOrderBasicInfo(any(OrderBasicInfo.class));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,6 +147,8 @@ public enum ReturnCodeEnum {
|
||||
|
||||
CODE_PENDING_PAYMENT_ORDERS_EXIST_ERROR("00100067", "存在待支付订单"),
|
||||
|
||||
CODE_FUNCTION_IS_UNAVAILABLE("00100068", "功能暂不可用"),
|
||||
|
||||
/* 个人桩 start */
|
||||
|
||||
CODE_PILE_HAS_BEEN_BINDING_ERROR("00400001", "此桩已被绑定,请联系管理员!"),
|
||||
|
||||
@@ -56,6 +56,7 @@ import com.jsowell.pile.transaction.dto.OrderTransactionDTO;
|
||||
import com.jsowell.pile.transaction.service.TransactionService;
|
||||
import com.jsowell.pile.util.UserUtils;
|
||||
import com.jsowell.pile.vo.OrderInfoDetailVO;
|
||||
import com.jsowell.pile.vo.OrderPayRecordVO;
|
||||
import com.jsowell.pile.vo.SupStationStatsVO;
|
||||
import com.jsowell.pile.vo.base.*;
|
||||
import com.jsowell.pile.vo.base.PileInfoVO;
|
||||
@@ -5721,14 +5722,19 @@ public class OrderBasicInfoServiceImpl implements OrderBasicInfoService {
|
||||
public BusinessOrderDetailInfoVO getBusinessOrderDetail(String orderCode) {
|
||||
BusinessOrderDetailInfoVO vo = new BusinessOrderDetailInfoVO();
|
||||
OrderVO orderVO = getChargeOrderInfoByOrderCode(orderCode);
|
||||
// 查询各时段详细费用
|
||||
OrderDetail orderDetail = getOrderDetailByOrderCode(orderCode);
|
||||
List<OrderPeriodAmountVO> orderPeriodAmountVOS = transformPeriodAmountByOrderDetail(orderDetail);
|
||||
vo.setChargeDetails(orderPeriodAmountVOS);
|
||||
if (orderVO == null) {
|
||||
return vo;
|
||||
}
|
||||
// 查询该站点正在使用的计费模板
|
||||
List<BillingPriceVO> priceList = pileBillingTemplateService.queryBillingPrice(orderVO.getStationId());
|
||||
if (CollectionUtils.isNotEmpty(priceList)) {
|
||||
vo.setPriceList(priceList);
|
||||
}
|
||||
// // 查询该站点正在使用的计费模板
|
||||
// List<BillingPriceVO> priceList = pileBillingTemplateService.queryBillingPrice(orderVO.getStationId());
|
||||
// if (CollectionUtils.isNotEmpty(priceList)) {
|
||||
// vo.setPriceList(priceList);
|
||||
// }
|
||||
|
||||
vo.setOrderStatus(orderVO.getOrderStatus());
|
||||
vo.setCreateTime(orderVO.getCreateTime());
|
||||
vo.setStartChargeTime(orderVO.getStartTime());
|
||||
@@ -5800,6 +5806,20 @@ public class OrderBasicInfoServiceImpl implements OrderBasicInfoService {
|
||||
vo.setPileMonitorDataList(infoList);
|
||||
}
|
||||
|
||||
// 支付流水对应页面支付信息
|
||||
// List<OrderDetailInfoVO.PayRecord> payRecords = orderPayRecordService.selectOrderPayInfoList(orderCode);
|
||||
List<OrderPayRecordVO> payRecordList = orderPayRecordService.selectOrderPayRecordList(orderCode);
|
||||
|
||||
vo.setPayRecordList(payRecordList);
|
||||
|
||||
// 查询退款明细
|
||||
List<OrderDetailInfoVO.OrderRefundInfo> orderRefundInfoList = getOrderRefundInfoList(orderCode);
|
||||
vo.setRefundInfoList(orderRefundInfoList);
|
||||
|
||||
// 用户信息
|
||||
MemberVO memberVO = memberBasicInfoService.queryMemberInfoByMemberId(orderVO.getMemberId());
|
||||
vo.setMemberVO(memberVO);
|
||||
|
||||
return vo;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package com.jsowell.pile.vo.uniapp.business;
|
||||
|
||||
import com.jsowell.pile.vo.OrderPayRecordVO;
|
||||
import com.jsowell.pile.vo.base.OrderPeriodAmountVO;
|
||||
import com.jsowell.pile.vo.uniapp.customer.BillingPriceVO;
|
||||
import com.jsowell.pile.vo.uniapp.customer.MemberVO;
|
||||
import com.jsowell.pile.vo.web.OrderDetailInfoVO;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
@@ -233,17 +236,58 @@ public class BusinessOrderDetailInfoVO {
|
||||
*/
|
||||
private String vinCode;
|
||||
|
||||
/**
|
||||
* 订单金额
|
||||
*/
|
||||
private BigDecimal orderAmount;
|
||||
|
||||
/**
|
||||
* 折扣金额
|
||||
*/
|
||||
private BigDecimal discountAmount;
|
||||
|
||||
/**
|
||||
* 手机号码
|
||||
*/
|
||||
private String phoneNumber;
|
||||
|
||||
/**
|
||||
* 订单状态
|
||||
*/
|
||||
private String orderStatus;
|
||||
|
||||
/**
|
||||
* 充电卡号
|
||||
*/
|
||||
private String cardNumber;
|
||||
|
||||
/**
|
||||
* 计费模板
|
||||
*/
|
||||
private List<BillingPriceVO> priceList;
|
||||
|
||||
/**
|
||||
* 充电实时数据信息
|
||||
*/
|
||||
private List<OrderDetailInfoVO.PileMonitorData> pileMonitorDataList;
|
||||
|
||||
/**
|
||||
* 各时段充电计费详情
|
||||
*/
|
||||
private List<OrderPeriodAmountVO> chargeDetails;
|
||||
|
||||
/**
|
||||
* 会员信息
|
||||
*/
|
||||
private MemberVO memberVO;
|
||||
|
||||
/**
|
||||
* 支付记录信息
|
||||
*/
|
||||
private List<OrderPayRecordVO> payRecordList;
|
||||
|
||||
/**
|
||||
* 退款信息
|
||||
*/
|
||||
private List<OrderDetailInfoVO.OrderRefundInfo> refundInfoList;
|
||||
}
|
||||
|
||||
104
plan/2026-01-23_16-45-56-whitelist-order-status-complete.md
Normal file
104
plan/2026-01-23_16-45-56-whitelist-order-status-complete.md
Normal file
@@ -0,0 +1,104 @@
|
||||
---
|
||||
mode: plan
|
||||
cwd: D:\jsowell\IdeaProjects\jsowell-charger-web
|
||||
task: 白名单支付订单在异常/待结算时自动修正为订单完成
|
||||
complexity: medium
|
||||
planning_method: builtin
|
||||
created_at: 2026-01-23T16:45:56.6767838+08:00
|
||||
---
|
||||
|
||||
# Plan: 白名单支付订单状态修正(异常/待结算 -> 完成)
|
||||
|
||||
🎯 任务概述
|
||||
当前系统中存在白名单支付(payMode=3)的订单,在某些场景下可能停留在“异常(4)”或“待结算(2)”状态。目标是在满足白名单支付条件时,将这些订单状态修正为“订单完成(6)”,并确保必要的结算时间/金额字段与后续流程(通知、解锁、分账/退款)保持一致或可控。
|
||||
|
||||
📋 执行计划
|
||||
1. 需求澄清与验收口径
|
||||
- 明确“白名单支付”的判定字段:`order_basic_info.pay_mode` 是否恒为 `3`(参考 `OrderPayModeEnum.PAYMENT_OF_WHITELIST`),以及是否需要同时检查支付流水 `order_pay_record.pay_mode`。
|
||||
- 定义修正规则:仅对白名单支付订单,且当前状态 ∈ {`ABNORMAL(4)`, `STAY_SETTLEMENT(2)`} 时修正为 `ORDER_COMPLETE(6)`;确认是否需要同步设置 `settlement_time`、`order_amount`、`refund_amount`、`virtual_amount`、`settle_amount`、`pay_status` 等字段。
|
||||
|
||||
✅ 规则清单(可实现/可测试)
|
||||
- 白名单判定:以 `order_basic_info.pay_mode == OrderPayModeEnum.PAYMENT_OF_WHITELIST(3)` 为主(系统业务字段);若存在支付流水,可选做一致性校验 `order_pay_record.pay_mode == OrderPayRecordEnum.WHITELIST_PAYMENT(3)`,不一致时仅记录告警日志,不阻断补偿。
|
||||
- 修正边界:仅当订单状态 ∈ {`ABNORMAL(4)`, `STAY_SETTLEMENT(2)`} 才允许修正为 `ORDER_COMPLETE(6)`;其余状态必须保持不变。
|
||||
- 字段同步(补偿落库时):
|
||||
- `order_status`:置为 `ORDER_COMPLETE(6)`
|
||||
- `settlement_time`:为空则置当前时间;非空保持原值
|
||||
- 金额字段默认策略(尽量沿用既有结算逻辑的语义):
|
||||
- `order_amount`:优先保留已有值;为空则使用 `pay_amount`,仍为空则置 0;若 `order_amount > pay_amount` 则以 `pay_amount` 为准
|
||||
- `virtual_amount`:白名单支付场景置为 `order_amount`(不参与结算对账)
|
||||
- `settle_amount` / `actual_received_amount`:置为 `order_amount - virtual_amount`(白名单场景为 0)
|
||||
- `refund_amount`:为空则置为 `max(pay_amount - order_amount, 0)`
|
||||
- `pay_status`:优先保留原值;为空则置 `OrderPayStatusEnum.pay_nothing(2)`
|
||||
- 副作用约束:补偿方法为 DB/缓存层修正,不应触发远程停机、拉交易记录、MQ 推送、解锁等额外链路(如需触发,应另起规则与验收)。
|
||||
- 审计日志:至少记录 `orderCode`、原状态、目标状态(必要时补充关键字段快照)。
|
||||
|
||||
2. 代码定位与影响面梳理
|
||||
- 定位现有白名单异常订单批量处理入口:`jsowell-admin/src/main/java/com/jsowell/service/TempService.java` 的 `whiteListSettlement` 与 `jsowell-admin/src/main/java/com/jsowell/api/uniapp/customer/TempController.java` 的 `/whiteListSettlement`。
|
||||
- 查找订单状态被设置为异常/待结算的路径,确认修正应放在“状态写入点”还是“事后补偿点”。重点关注:
|
||||
- `jsowell-common/src/main/java/com/jsowell/common/enums/ykc/OrderStatusEnum.java`
|
||||
- `jsowell-admin/src/main/java/com/jsowell/service/OrderService.java`(存在将充电中订单标记为异常的逻辑)
|
||||
- `jsowell-pile/src/main/java/com/jsowell/pile/service/impl/OrderBasicInfoServiceImpl.java`、`jsowell-pile/src/main/java/com/jsowell/pile/service/programlogic/AbstractProgramLogic.java`(结算后会落库为完成)
|
||||
|
||||
📌 影响面清单(可审计)
|
||||
- 白名单批量入口(人工/管理接口):`TempController#/whiteListSettlement` → `TempService#whiteListSettlement`(当前仅允许异常订单、且硬编码 payMode=3,并调用 `OrderService#manualSettlementOrder`,会触发远程停机/拉交易记录等外部依赖)。
|
||||
- 订单标记异常(后管查询触发):`OrderService#getOrderDetail` 在“充电中”但实时数据缺失/超时场景会将订单置为 `ABNORMAL(4)` 并落库。
|
||||
- 待结算写入点(设备/交易链路):
|
||||
- `RemoteStopChargingStrategy`:停机成功→`STAY_SETTLEMENT(2)`;停机失败→`ABNORMAL(4)`。
|
||||
- `TransactionRecordsStrategy`:收到交易记录后,若订单为 `ABNORMAL(4)` 会先改为 `STAY_SETTLEMENT(2)`,随后进入 `ProgramLogicFactory#getProgramLogic(...).settleOrder(...)` 完成结算落库。
|
||||
- 其他协议/平台(同类写入点):`jsowell-netty` 的 yunkuaichong handlers、`jsowell-thirdparty` 的 Huawei 等也存在将订单置为 `STAY_SETTLEMENT(2)` 的路径(需避免在这些写入点引入“白名单直接完成”造成副作用扩散)。
|
||||
- 结算后落库完成(核心):`AbstractProgramLogic#returnUpdateOrderBasicInfo` 会设置 `ORDER_COMPLETE(6)` 并写入结算时间/金额字段(及可能补 SOC)。
|
||||
|
||||
🧭 修正插入点选择(结论)
|
||||
- 选择“事后补偿点”而不是在各类“状态写入点”分散修正:以统一补偿方法 + 白名单批量入口调用为主(后续可扩展定时任务/管理按钮复用该方法)。
|
||||
- 理由:避免侵入设备协议/交易记录链路(风险大、场景多);补偿方法可控、幂等、日志可追溯,且明确不触发远程停机/拉交易/MQ/解锁等外部副作用。
|
||||
|
||||
3. 设计修正策略(推荐“统一补偿方法”)
|
||||
- 在订单领域服务中新增一个可复用的方法(命名示例:`completeWhitelistOrderIfNeeded(orderCode)`),封装:
|
||||
- 判定:payMode=3 且状态为异常/待结算。
|
||||
- 修正:更新为 `ORDER_COMPLETE`,补齐 `settlement_time`(无则置当前时间),并根据现有数据填充金额字段(缺失时以 0 或合理默认值),确保幂等。
|
||||
- 选择调用点:
|
||||
- 扩展现有 `/whiteListSettlement`:允许处理 `ABNORMAL` 与 `STAY_SETTLEMENT` 两种状态;内部改为调用统一补偿方法。
|
||||
- 视业务需要补充一个定时任务/管理端按钮做批量修复(避免仅靠人工接口)。
|
||||
|
||||
4. 实现细节落地
|
||||
- 调整 `TempService.whiteListSettlement`:
|
||||
- 将“只允许异常订单”的校验扩展为“异常或待结算”。
|
||||
- 将硬编码 `payMode == "3"` 改为使用枚举 `OrderPayModeEnum.PAYMENT_OF_WHITELIST.getValue()`。
|
||||
- 明确是否继续调用 `orderService.manualSettlementOrder(...)`(它会尝试远程停机、拉取交易记录);若目标仅是状态修正,优先走新的补偿方法,避免不必要的外部依赖。
|
||||
|
||||
5. 测试与回归
|
||||
- 增加单元/集成测试覆盖:
|
||||
- 白名单订单 + 状态=异常 -> 修正为完成。
|
||||
- 白名单订单 + 状态=待结算 -> 修正为完成。
|
||||
- 非白名单订单不应被修正。
|
||||
- 运行验证命令:`mvn -pl jsowell-admin test`(必要时指定测试类)。
|
||||
|
||||
6. 数据校验与上线准备
|
||||
- 演练入口:`POST /temp/whiteListSettlement`
|
||||
- body(示例):`{"orderCodeList":["O1","O2"]}`
|
||||
- 演练前(务必留存快照,用于回滚/审计):
|
||||
- DB 快照(示例 SQL):
|
||||
- `SELECT order_code,pay_mode,order_status,pay_status,settlement_time,order_amount,virtual_amount,settle_amount,actual_received_amount,refund_amount,reason FROM order_basic_info WHERE order_code IN ('O1','O2');`
|
||||
- 将查询结果保存到工单/PR/运维记录(CSV/截图均可)。
|
||||
- 演练后(校验点):
|
||||
- `order_basic_info.order_status = 6`
|
||||
- `settlement_time`:原为空则已填充;原非空保持不变
|
||||
- 金额字段符合“规则清单”默认策略(白名单虚拟金额/结算金额等)
|
||||
- 日志可追溯:搜索 `completeWhitelistOrderIfNeeded updated orderCode:`,应包含订单号与状态变更前后值
|
||||
- 回滚预案(可执行):
|
||||
- 原则:以“演练前快照”为准逐单回退(避免盲目回退影响正常订单)。
|
||||
- 回滚 SQL(模板,按快照逐单生成):
|
||||
- `UPDATE order_basic_info SET order_status='<before_status>', settlement_time='<before_settlement_time>', order_amount=<before_order_amount>, virtual_amount=<before_virtual_amount>, settle_amount=<before_settle_amount>, actual_received_amount=<before_actual_received_amount>, refund_amount=<before_refund_amount>, pay_status='<before_pay_status>' WHERE order_code='<order_code>';`
|
||||
|
||||
⚠️ 风险与注意事项
|
||||
- 业务风险:将异常/待结算直接置为完成可能掩盖真实设备/交易异常;需确认是否仍需要保留异常原因字段或额外审计。
|
||||
- 技术风险:如果修正流程绕过了“解锁卡/VIN、推送MQ、实时数据落库”等步骤,可能导致下游状态不一致;应明确是否需要补做这些副作用。
|
||||
- 兼容性:`TempService.whiteListSettlement` 当前只允许异常订单,放宽后需确认调用方预期与权限控制。
|
||||
|
||||
📎 参考
|
||||
- `jsowell-admin/src/main/java/com/jsowell/service/TempService.java`
|
||||
- `jsowell-admin/src/main/java/com/jsowell/api/uniapp/customer/TempController.java`
|
||||
- `jsowell-common/src/main/java/com/jsowell/common/enums/ykc/OrderStatusEnum.java`
|
||||
- `jsowell-common/src/main/java/com/jsowell/common/enums/ykc/OrderPayModeEnum.java`
|
||||
- `jsowell-admin/src/main/java/com/jsowell/service/OrderService.java`
|
||||
- `jsowell-pile/src/main/java/com/jsowell/pile/service/programlogic/AbstractProgramLogic.java`
|
||||
Reference in New Issue
Block a user