feat:优化离线订单

This commit is contained in:
dzc committed 2026-10-26 14:34:11 +08:00
1 parent 2caa7dbff4
commit 42574f0412
4 files changed
+82 -1

No files matched your search

@@ -4180,6 +4180,11 @@ public class HardOrderServiceImpl extends ServiceImpl<HardOrderMapper, HardOrder
return updateObj.getId();
}
/**
* 按订单支付方式执行快速扣款或核销并更新成功状态;离线扣款携带设备发生时间及专用流水备注。
*
* @param order 待支付订单;离线订单创建时间已在同步保存时恢复,审批后扣款从数据库读取
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void fastReduceBalance(HardOrderEntity order) {
@@ -4200,6 +4205,11 @@ public class HardOrderServiceImpl extends ServiceImpl<HardOrderMapper, HardOrder
// 快速支付同样使用订单实际商户,禁止退化为钱包所有人的默认食堂。
reduceBuild.setSourceMchId(order.getMerchantId());
reduceBuild.setSourceScene("ORDER");
if (ObjectUtil.equals(order.getIsOfflineOrder(), YesNoEnum.YES.getCode())) {
// 离线订单按设备实际消费时间归账,审批后延迟扣款也使用同一时间。
reduceBuild.setCreateTime(Optional.ofNullable(order.getCreateTime()).orElse(order.getPayTime()));
reduceBuild.setRemark("离线订单扣除");
}
// 按订单类型+系统类型渲染消费场景系统备注(如"在食堂线下消费10元")
reduceBuild.setSysRemark(HardOrderSysRemarkHelper.renderConsume(order.getOrderType(), order.getSysType(), order.getPayPrice()));
R<WalletTransactionEntity> reduceRes = walletTransactionService.reduceWalletBalance(reduceBuild);
@@ -4,6 +4,7 @@ import cn.hutool.core.util.ObjectUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.bsd.pjcloud.admin.api.dto.UserInfo;
import com.bsd.pjcloud.admin.api.feign.RemoteUserService;
import com.bsd.pjcloud.common.core.constant.enums.YesNoEnum;
@@ -144,9 +145,19 @@ public class HardOrderSyncServiceImpl implements HardOrderSyncService {
orderService.updateById(updateObj);
}
// 同步安卓机单个离线订单 TODO 允许把金额扣为负数
/**
* 保存单个终端离线订单及购物车,保留设备发生时间;无需审批时立即扣款,允许钱包透支。
*
* @param order 已解析的离线订单,创建时间为空时沿用审计填充的同步时间
* @param parentCar 待创建的父购物车
* @param list 待创建的购物车明细
* @param offlineOrder 终端原始订单
* @param offLineOrderApplyEnable 是否等待后台审批后扣款
* @return 订单保存及本次需要的扣款均成功时返回 true
*/
@Transactional(rollbackFor = Exception.class)
public Boolean createOfflineOrder(HardOrderEntity order, HardParentCarEntity parentCar, List<HardCarEntity> list, OfflineOrder offlineOrder, boolean offLineOrderApplyEnable) {
boolean restoringOrderTime = false;
try {
// 创建父订单
hardParentCarService.save(parentCar);
@@ -155,7 +166,21 @@ public class HardOrderSyncServiceImpl implements HardOrderSyncService {
hardCarService.saveBatch(list);
// 创建订单
order.setParentCarId(parentCar.getId());
LocalDateTime orderTime = order.getCreateTime();
orderService.save(order);
// 公共插入审计会覆盖 createTime;仅在离线同步事务内恢复设备时间,供统计和后续扣款共用。
if (orderTime != null && !orderTime.equals(order.getCreateTime())) {
restoringOrderTime = true;
boolean updated = orderService.update(new LambdaUpdateWrapper<HardOrderEntity>()
.eq(HardOrderEntity::getId, order.getId())
.set(HardOrderEntity::getCreateTime, orderTime));
// 数据库时间精度可能使恢复成为同值更新,仅在订单也不存在时判定失败。
if (!updated && orderService.getById(order.getId()) == null) {
throw new IllegalStateException("保存离线订单发生时间失败");
}
order.setCreateTime(orderTime);
restoringOrderTime = false;
}
// 支付
if (!offLineOrderApplyEnable) {
orderService.fastReduceBalance(order);
@@ -167,6 +192,10 @@ public class HardOrderSyncServiceImpl implements HardOrderSyncService {
}
return true;
} catch (Exception e) {
if (restoringOrderTime) {
// 时间恢复失败必须穿出事务边界并停止批次,避免错误时间订单提交或回滚后继续远程扣款。
throw new IllegalStateException("保存离线订单发生时间失败", e);
}
if (!offLineOrderApplyEnable) {
HardOrderEntity updateObj = HardOrderEntity.builder().id(order.getId()).offlinePaySuccess(YesNoEnum.NO.getCode()).build();
orderService.updateById(updateObj);
@@ -177,6 +206,16 @@ public class HardOrderSyncServiceImpl implements HardOrderSyncService {
return false;
}
/**
* 将终端离线订单转换为云端订单,创建时间和预约使用日期均优先取设备下单时间,其次取支付时间。
*
* @param order 终端上报的订单;两个时间均缺失时由保存流程使用同步时间
* @param hardwareId 同步订单的设备 ID
* @param mchDetail 订单所属商户资料
* @param merchantDeptId 订单归属部门 ID
* @param userInfo 下单用户及其当前部门资料
* @return 尚未持久化的离线订单
*/
public HardOrderEntity parseOrder(OfflineOrder order, Long hardwareId, MchManageVo mchDetail, Long merchantDeptId, UserInfo userInfo) {
HardOrderEntity res = new HardOrderEntity();
res.setAndroidOrderCode(order.getUniqueCode());
@@ -200,6 +239,7 @@ public class HardOrderSyncServiceImpl implements HardOrderSyncService {
res.setProductDesc(order.getProductDesc());
res.setHourType(order.getHourType());
LocalDateTime orderTime = Optional.ofNullable(order.getCreateTime()).orElse(order.getPayTime());
res.setCreateTime(orderTime);
// 离线订单优先保留设备原始下单日期,旧终端未传时才使用同步当天。
res.setApptUseDate(orderTime == null ? LocalDate.now() : orderTime.toLocalDate());
return res;
@@ -9,6 +9,7 @@ import lombok.Builder;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 钱包余额变动参数;订单调用方可附带实际商户快照,账户级操作不传时由钱包服务按用户当前食堂解析。
@@ -51,6 +52,17 @@ public class WalletBalanceTransactionDto {
*/
private String sysRemark;
/**
* 离线订单实际发生时间,优先取设备下单时间,其次取设备支付时间;仅新建支付流水使用,
* 未传时保留钱包服务当前时间,退款不继承该时间。
*/
private LocalDateTime createTime;
/**
* 离线订单支付流水的业务备注,由订单服务传入“离线订单扣除”;未传时不覆盖流水原备注。
*/
private String remark;
/**
* 订单设备类型快照编码(HardTypeEnum.type);订单支付/退款调用传订单 sys_type,
* 随流水固化为 hard_type 供设备类型筛选,非订单调用不传。
@@ -220,6 +220,7 @@ public class WalletTransactionServiceImpl extends ServiceImpl<WalletTransactionM
/**
* 保存新钱包流水并在同一本地事务内追加业务消息Outbox;幂等查询旧流水不会进入本方法。
* 调用方未显式传系统备注时,按业务类型+金额方向自动生成默认场景文案(模板见 WalletSysRemarkTemplate)。
* 离线订单支付从调用上下文保留设备发生时间及业务备注,恢复时间后再生成消息快照。
*
* @param entity 待持久化钱包流水
* @return 流水和Outbox均写入成功时返回true
@@ -232,8 +233,26 @@ public class WalletTransactionServiceImpl extends ServiceImpl<WalletTransactionM
entity.setSysRemark(WalletSysRemarkTemplate.defaultRemark(entity.getBizType(), entity.getPrice()));
}
fillHardTypeFromContext(entity);
WalletBalanceTransactionDto context = WalletMessageContextHolder.find(entity.getWalletId(), entity.getBizId(), entity.getBizType());
// 补偿退款会复用同一调用上下文,必须同时限定上下文和流水为支付,避免回写退款的时间与备注。
boolean isOrderPayment = context != null && context.getBizType() == WalletBizEnum.PAYMENT
&& WalletBizEnum.PAYMENT.getCode().equals(entity.getBizType());
if (isOrderPayment && StrUtil.isNotBlank(context.getRemark())) {
entity.setRemark(context.getRemark());
}
boolean saved = baseMapper.insert(entity) > 0;
if (saved) {
if (isOrderPayment && context.getCreateTime() != null && !context.getCreateTime().equals(entity.getCreateTime())) {
// 公共插入审计强制使用当前时间,仅为显式携带设备时间的离线支付定向恢复,不改变其他流水审计规则。
int updated = baseMapper.update(null, Wrappers.<WalletTransactionEntity>lambdaUpdate()
.eq(WalletTransactionEntity::getId, entity.getId())
.set(WalletTransactionEntity::getCreateTime, context.getCreateTime()));
// 数据库时间精度可能使恢复成为同值更新,仅在流水也不存在时判定失败。
if (updated == 0 && baseMapper.selectById(entity.getId()) == null) {
throw new IllegalStateException("保存离线订单流水发生时间失败");
}
entity.setCreateTime(context.getCreateTime());
}
createMessageOutbox(entity);
}
return saved;