预订单时间槽列表逻辑调整,分账、提现 报文字段 保存

This commit is contained in:
Jack 2025-10-30 01:01:40 +08:00
parent 7810340cde
commit 0248a38268
11 changed files with 243 additions and 329 deletions

View File

@ -155,6 +155,9 @@ public class LklOrderDraw {
@ApiModelProperty(value = "异步通知地址", example = "https://api.example.com/notify")
private String notify_url;
@ApiModelProperty(value = "接口请求报文")
private String lkl_req;
/**
* 异步通知返回的JSON数据
*/

View File

@ -94,6 +94,9 @@ public class LklOrderSeparate {
@ApiModelProperty(value = "处理状态ACCEPTED-已受理, PROCESSING-处理中, FAIL-失败, SUCCESS-成功")
private String final_status;
@ApiModelProperty(value = "接口请求报文")
private String lkl_req;
@ApiModelProperty(value = "异步通知数据")
private String notify_resp;

View File

@ -10,8 +10,7 @@ import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoField;
import java.util.Calendar;
import java.util.Date;
import java.util.*;
@Slf4j
public class DateTimeUtils {
@ -363,6 +362,76 @@ public class DateTimeUtils {
return count;
}
/**
* 计算多个时间段之间的交集不跨天
* <p>
* 算法逻辑
* 1. 遍历所有时间段找到最晚的开始时间和最早的结束时间
* 2. 如果最晚开始时间小于等于最早结束时间则存在交集
* 3. 如果最晚开始时间大于最早结束时间则不存在交集
*
* @param timeList 时间段列表每个时间段是一个Map包含开始时间startTimeStr和结束时间endTimeStr
* startTimeStr 开始时间字符串支持格式如 HH:mm, HH:mm:ss, HH:mm:ss.SSS
* endTimeStr 结束时间字符串支持格式如 HH:mm, HH:mm:ss, HH:mm:ss.SSS
* @return 返回一个Map包含交集的时间段(startTimeStr和endTimeStr)如果无交集则返回空Map
*/
public static Map<String, String> findTimeInterSection(List<Map<String, String>> timeList) {
// 参数校验
if (timeList == null || timeList.isEmpty()) {
log.warn("时间段列表为空或null无法计算交集");
return new HashMap<>();
}
try {
LocalTime latestStartTime = null;
LocalTime earliestEndTime = null;
// 遍历所有时间段
for (Map<String, String> timeMap : timeList) {
if (timeMap == null || !timeMap.containsKey("startTimeStr") || !timeMap.containsKey("endTimeStr")) {
log.warn("时间段数据不完整或格式不正确: {}", timeMap);
continue;
}
String startTimeStr = timeMap.get("startTimeStr");
String endTimeStr = timeMap.get("endTimeStr");
if (startTimeStr == null || endTimeStr == null) {
log.warn("时间段的开始或结束时间为空: startTime={}, endTime={}", startTimeStr, endTimeStr);
continue;
}
LocalTime startTime = parseTime(startTimeStr);
LocalTime endTime = parseTime(endTimeStr);
// 更新最晚开始时间和最早结束时间
if (latestStartTime == null || startTime.isAfter(latestStartTime)) {
latestStartTime = startTime;
}
if (earliestEndTime == null || endTime.isBefore(earliestEndTime)) {
earliestEndTime = endTime;
}
}
// 检查是否存在交集
if (latestStartTime != null && earliestEndTime != null && !latestStartTime.isAfter(earliestEndTime)) {
Map<String, String> result = new HashMap<>();
result.put("startTimeStr", latestStartTime.toString());
result.put("endTimeStr", earliestEndTime.toString());
return result;
} else {
// 无交集情况
log.debug("给定的时间段列表无交集");
return new HashMap<>();
}
} catch (Exception e) {
log.error("计算时间段交集时发生异常", e);
return new HashMap<>();
}
}
/**
* 判断指定时间是否在两个时间点之间包含边界
*
@ -537,14 +606,52 @@ public class DateTimeUtils {
// System.out.println(formatLocalDate(LocalDate.now(), "yyyy-MM-dd"));
// 判断当前时间是否在工作时间9:00-18:00
boolean isWorkTime = isCurrentTimeInRange("09:00", "22:36");
// boolean isWorkTime = isCurrentTimeInRange("09:00", "22:36");
// 判断特定时间是否在夜间时间22:00-06:00
// LocalDateTime testTime = LocalDateTime.of(2025, 1, 1, 23, 30);
Date testTime = Date.from(LocalDateTime.of(2025, 10, 23, 21, 30).atZone(ZoneId.systemDefault()).toInstant());
boolean isNight = isTimeInRange("08:30", "22:20", testTime);
// Date testTime = Date.from(LocalDateTime.of(2025, 10, 23, 21, 30).atZone(ZoneId.systemDefault()).toInstant());
// boolean isNight = isTimeInRange("08:30", "22:20", testTime);
// System.out.println("当前时间是否在工作时间内:" + isWorkTime);
// System.out.println("多个时间段的交集结果:" + isNight);
System.out.println("=== 测试 findTimeIntersection ===");
// 测试正常交集情况
List<Map<String, String>> timeList1 = new ArrayList<>();
Map<String, String> range1 = new HashMap<>();
range1.put("startTimeStr", "06:00");
range1.put("endTimeStr", "17:00");
timeList1.add(range1);
Map<String, String> range2 = new HashMap<>();
range2.put("startTimeStr", "06:00");
range2.put("endTimeStr", "17:00");
timeList1.add(range2);
Map<String, String> intersection1 = findTimeInterSection(timeList1);
System.out.println("交集结果1: " + intersection1); // 应该是 10:00 - 17:00
// 测试无交集情况
List<Map<String, String>> timeList2 = new ArrayList<>();
Map<String, String> range3 = new HashMap<>();
range3.put("startTimeStr", "09:00");
range3.put("endTimeStr", "12:00");
timeList2.add(range3);
Map<String, String> range4 = new HashMap<>();
range4.put("startTimeStr", "13:00");
range4.put("endTimeStr", "17:00");
timeList2.add(range4);
Map<String, String> intersection2 = findTimeInterSection(timeList2);
System.out.println("交集结果2: " + intersection2); // 应该是空Map
// 测试空列表
Map<String, String> intersection3 = findTimeInterSection(null);
System.out.println("交集结果3 (null输入): " + intersection3); // 应该是空Map
System.out.println("当前时间是否在工作时间内:" + isWorkTime);
System.out.println("特定时间是否在夜间时间段内:" + isNight);
}
}

View File

@ -13,6 +13,7 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.suisung.mall.common.api.ResultCode;
import com.suisung.mall.common.api.StateCode;
import com.suisung.mall.common.constant.CommonConstant;
import com.suisung.mall.common.domain.UserDto;
import com.suisung.mall.common.exception.ApiException;
import com.suisung.mall.common.exception.ApiUserException;
@ -648,8 +649,10 @@ public class ShopActivityGroupbookingServiceImpl extends BaseServiceImpl<ShopAct
for (ShopActivityGroupbookingHistory history : historyList) {
// 已支付
ShopOrderInfo order_info_data = shopOrderInfoService.get(history.getOrder_id());
if (history.getGbh_flag() == 1) {
boolean used = shopUserVoucherService.useNeedNotPin(history.getUser_id(), history.getOrder_id());
if (history.getGbh_flag() == 1) { // 已支付
// 是否使用了免拼券
boolean used = shopUserVoucherService.tryUseFreeGroupVoucher(history.getUser_id(), history.getOrder_id());
if (used) {
if (order_info_data.getOrder_is_sync() == 2) {
@ -664,30 +667,33 @@ public class ShopActivityGroupbookingServiceImpl extends BaseServiceImpl<ShopAct
// 如果使用免拼券成功则改变参团人员拼单状态为成功.不往下走
ShopActivityGroupbookingHistory groupbookingHistory = new ShopActivityGroupbookingHistory();
groupbookingHistory.setGbh_id(history.getGbh_id());
groupbookingHistory.setGb_enable(1);
groupbookingHistory.setGb_enable(CommonConstant.Enable);
if (!shopActivityGroupbookingHistoryService.edit(groupbookingHistory)) {
throw new ApiException(I18nUtil._("修正订单为可同步状态失败!"));
}
free_fight_num++;
// TODO 平团成功需要发同城配送
continue;
}
}
// 取消订单
List<String> order_ids = Convert.toList(String.class, history.getOrder_id());
//只有一条记录
List<ShopOrderInfo> shopOrderInfos = shopOrderInfoService.gets(order_ids);
List<Map> rows = Convert.toList(Map.class, shopOrderInfos);
// 取消订单
flag = shopOrderBaseService.cancel(order_ids, rows, false);
//已经调用cancelActivity, 重复
if (flag) {
// 改变参团人员拼单状态
ShopActivityGroupbookingHistory groupbookingHistory = new ShopActivityGroupbookingHistory();
groupbookingHistory.setGbh_id(history.getGbh_id());
groupbookingHistory.setGb_enable(0);
groupbookingHistory.setGb_enable(CommonConstant.Disable);
if (!shopActivityGroupbookingHistoryService.edit(groupbookingHistory)) {
throw new ApiException(I18nUtil._("改变参团人员拼单状态失败!"));
}
@ -727,12 +733,10 @@ public class ShopActivityGroupbookingServiceImpl extends BaseServiceImpl<ShopAct
orderReturn.setReturn_tel("");
orderReturn.setReturn_store_user_id(buyer_store_id);
orderReturn.setReturn_telephone("");
orderReturn.setSubsite_id(order_info_data.getSubsite_id());
ShopOrderReturnItem orderReturnItem = new ShopOrderReturnItem();
orderReturnItem.setOrder_item_id(order_item_row.getOrder_item_id()); // 退货商品编号(DOT):0为退款
orderReturnItem.setOrder_id(history.getOrder_id()); // 订单编号
orderReturnItem.setReturn_item_num(order_item_row.getOrder_item_quantity()); // 退货商品编号(DOT):0为退款
@ -760,7 +764,7 @@ public class ShopActivityGroupbookingServiceImpl extends BaseServiceImpl<ShopAct
}
}
//如果免拼数等于成团数量则此团完成
//如果使用免拼券人数等于成团数量则此团完成
if (gb_quantity <= free_fight_num) {
groupbooking.setGb_enable(1);
shopActivityGroupbookingService.saveOrUpdate(groupbooking);

View File

@ -176,19 +176,7 @@ public interface LakalaApiService {
* @return
*/
CommonResult getBankCardBin(String bankCardNo);
/**
* 拉卡拉订单分账用户下单成功之后进行分账
* 说明分账指令是异步处理模式响应报文成功时指令状态是status: PROCESSING需要等待分账结果通知或者主动发起查询建议主动发起查询与分账指令动作之间间隔15秒以上
* 参考https://o.lakala.com/#/home/document/detail?id=389
*
* @param orderId 平台订单号,必填参数
* @param storeId 商户门店编号,非必填参数
* @return
*/
Pair<Boolean, String> innerDoOrderSeparate(String orderId, String storeId);
/**
* 根据商户号交易号和收货流水号执行订单分账操作
* <p>

View File

@ -65,7 +65,6 @@ import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
@Slf4j
@ -1838,7 +1837,7 @@ public class LakalaApiServiceImpl implements LakalaApiService {
// 10. 检查商户绑定状态是否完成 更改总的审核状态
shopMchEntryService.checkMerchEntryFinished(mchId);
// 11. 日志记录并返回成功响应
log.info("商家绑定分账接收方异步通知处理完成mchId:{} merCupNo{}", mchId, merCupNo);
return JSONUtil.createObj().set("code", "SUCCESS").set("message", "分账接收方绑定成功");
@ -2265,270 +2264,6 @@ public class LakalaApiServiceImpl implements LakalaApiService {
}
/**
* 执行拉卡拉订单分账操作
* <p>
* 用户确认收货成功之后大约15秒后进行分账操作
* 分账指令是异步处理模式响应报文成功时指令状态是"status": "PROCESSING"
* 需要等待分账结果通知或者主动发起查询
* 建议主动发起查询与分账指令动作之间间隔15秒以上
* </p>
* <p>
* 参考文档https://o.lakala.com/#/home/document/detail?id=389
* </p>
*
* @param orderId 平台订单Id
* @param storeId 店铺Id可为空
* @return Pair<Boolean, String> 处理结果对first为是否成功second为结果描述信息
*/
@Override
public Pair<Boolean, String> innerDoOrderSeparate(String orderId, String storeId) {
// 1. 输入参数校验
if (StrUtil.isBlank(orderId)) {
log.warn("[分账操作] 参数校验失败:订单号为空");
return Pair.of(false, "订单号不能为空");
}
try {
// TODO 检查可分账余额是否足够
// 2. 查询订单信息
log.info("[分账操作] 开始执行订单[{}]分账操作", orderId);
List<ShopOrderLkl> shopOrderLklList = shopOrderLklService.selectByOrderId(orderId, "", storeId);
if (CollectionUtil.isEmpty(shopOrderLklList)) {
log.warn("[分账操作] 失败:订单[{}]不存在", orderId);
return Pair.of(false, "订单不存在");
}
int totalCount = shopOrderLklList.size();
int successCount = 0;
StringBuilder errorMessages = new StringBuilder();
// 3. 初始化拉卡拉SDK
initLKLSDK();
// 4. 遍历处理每个店铺订单的分账
log.info("[分账操作] 订单[{}]包含{}个子订单,开始逐一处理", orderId, totalCount);
for (ShopOrderLkl shopOrderLkl : shopOrderLklList) {
log.debug("[分账操作] 处理子订单storeId={}, subLogNo={}, receive_log_no={}", shopOrderLkl.getStore_id(), shopOrderLkl.getLkl_sub_log_no(), shopOrderLkl.getLkl_receive_log_no());
if (!CommonConstant.Enable.equals(shopOrderLkl.getReceive_status()) || StrUtil.isBlank(shopOrderLkl.getLkl_receive_log_no())) {
log.warn("[分账操作] 订单[{}]对账流水号[{}]未被确认收货,跳过处理", orderId, shopOrderLkl.getLkl_receive_log_no());
continue;
}
// 5. 检查分账状态避免重复处理
LklOrderSeparate existingSeparateRecord = lklOrderSeparateService.getByLogNoAndOutTradeNo(shopOrderLkl.getLkl_sub_log_no(), orderId);
if (existingSeparateRecord != null) {
String status = existingSeparateRecord.getStatus();
if ("SUCCESS".equals(status)) {
log.info("[分账操作] 订单[{}]交易对账流水号[{}]已完成分账,跳过处理", orderId, shopOrderLkl.getLkl_sub_log_no());
successCount++;
continue;
}
if ("PROCESSING".equals(status) || "ACCEPTED".equals(status)) {
log.info("[分账操作] 订单[{}]交易对账流水号[{}]分账处理中或已受理,跳过处理", orderId, shopOrderLkl.getLkl_sub_log_no());
successCount++;
continue;
}
}
// 6. 获取订单分账相关参数
String merchantNo = shopOrderLkl.getLkl_merchant_no();
// 分账金额 = 应付总金额-运费支付时已计算好
Integer splitAmount = shopOrderLkl.getSplit_amt();
splitAmount = CheckUtil.isEmpty(splitAmount) ? 0 : splitAmount;
// 7. 分账金额校验
if (splitAmount < 1) {
String errorMsg = String.format("[分账操作] 店铺[%s]订单[%s]分账金额[%d]低于1分钱跳过分账",
shopOrderLkl.getStore_id(), orderId, splitAmount);
log.error(errorMsg);
errorMessages.append(errorMsg).append("; ");
if (existingSeparateRecord != null) {
lklOrderSeparateService.updateRemark(existingSeparateRecord.getId(), errorMsg);
}
continue;
}
// 获取分账平台接收方信息
LklLedgerMerReceiverBind platformReceiver = lklLedgerMerReceiverBindService.getPlatformByMerCupNo(merchantNo);
if (platformReceiver == null) {
String errorMsg = String.format("[分账操作] 店铺[%s]未绑定平台方接收账户,跳过分账", shopOrderLkl.getStore_id());
log.error(errorMsg);
errorMessages.append(errorMsg).append("; ");
continue;
}
// 8. 构建分账接收方列表
List<V3SacsSeparateRecvDatas> recvDatas = new ArrayList<>();
// 9. 获取商家分账比例并校验
BigDecimal merchantSplitRatioRaw = shopOrderLkl.getSplit_ratio(); // 94 代表94%
// 判断商家分账比例是否有效必须在(0, 100]范围内
boolean canSplitForMerchant = merchantSplitRatioRaw != null
&& merchantSplitRatioRaw.compareTo(BigDecimal.ZERO) > 0
&& merchantSplitRatioRaw.compareTo(new BigDecimal(100)) <= 0;
if (!canSplitForMerchant) {
String errorMsg = String.format("[分账操作] 店铺[%s]商家分账比例[%s]不在(0-100]范围内,无法分账",
shopOrderLkl.getStore_id(), merchantSplitRatioRaw);
log.error(errorMsg);
errorMessages.append(errorMsg).append("; ");
continue;
}
// 商家分账
BigDecimal merchantSplitRatio = merchantSplitRatioRaw.divide(new BigDecimal(100)); // 比如94%
BigDecimal distributorSplitRatio = BigDecimal.ZERO;
BigDecimal platformSplitRatio = BigDecimal.ONE;
// 分账代理商接收方信息
List<LklLedgerMerReceiverBind> distributorReceivers = lklLedgerMerReceiverBindService.selectAgentByMerCupNo(merchantNo);
if (distributorReceivers != null && !distributorReceivers.isEmpty()) {
distributorSplitRatio = new BigDecimal("0.8");
platformSplitRatio = new BigDecimal("0.2");
}
// 记录关键分账参数便于问题排查
log.info("[分账操作] 参数信息:订单={}, 商户={}, 总金额={}分, 商家比例={}, 平台比例={}, 代理商比例={}, 是否有代理商={}",
orderId, merchantNo, splitAmount, merchantSplitRatio, platformSplitRatio, distributorSplitRatio,
(distributorReceivers != null && !distributorReceivers.isEmpty()));
// 返回值如下{platformAmount=6, merchantAmount=94, agentAmount=0}
Map<String, Integer> splitAmountMap = CommonUtil.calculateProfitSharing(splitAmount, merchantSplitRatio, platformSplitRatio, distributorSplitRatio);
Integer merchantAmount = splitAmountMap.get("merchantAmount");
Integer platformAmount = splitAmountMap.get("platformAmount");
Integer agentAmount = splitAmountMap.get("agentAmount");
// 记录分账结果便于问题排查
log.info("[分账操作] 金额计算结果:订单={}, 商户={}, 总金额={}分, 商家分得={}分, 平台分得={}分, 代理商分得={}分",
orderId, merchantNo, splitAmount, merchantAmount, platformAmount, agentAmount);
if (merchantAmount > 0) {
V3SacsSeparateRecvDatas receiver = new V3SacsSeparateRecvDatas();
receiver.setRecvMerchantNo(merchantNo);
receiver.setSeparateValue(merchantAmount.toString());
recvDatas.add(receiver);
}
if (platformAmount > 0) {
V3SacsSeparateRecvDatas receiver = new V3SacsSeparateRecvDatas();
receiver.setRecvNo(platformReceiver.getReceiver_no());
receiver.setSeparateValue(platformAmount.toString());
recvDatas.add(receiver);
}
if (agentAmount > 0 && distributorReceivers != null && !distributorReceivers.isEmpty()) {
V3SacsSeparateRecvDatas receiver = new V3SacsSeparateRecvDatas();
receiver.setRecvNo(distributorReceivers.get(0).getReceiver_no());
receiver.setSeparateValue(agentAmount.toString());
recvDatas.add(receiver);
}
// 14. 构建分账请求对象
V3SacsSeparateRequest separateRequest = new V3SacsSeparateRequest();
separateRequest.setMerchantNo(merchantNo);
separateRequest.setLogNo(shopOrderLkl.getLkl_receive_log_no()); // 合单和非合单的流水号保存在此字段
separateRequest.setLogDate(shopOrderLkl.getLkl_log_date());
separateRequest.setOutSeparateNo(shopOrderLkl.getOut_separate_no());
separateRequest.setTotalAmt(splitAmount.toString());
separateRequest.setLklOrgNo(orgCode);
separateRequest.setCalType("0"); // 0- 按照指定金额1- 按照指定比例默认 0
separateRequest.setNotifyUrl(projectDomain + "/api/mobile/shop/lakala/sacs/separateNotify");
// 15. 设置分账接收方列表
separateRequest.setRecvDatas(recvDatas);
log.info("[分账操作] 请求参数: 订单={}, 商户={}, 金额={}分, 分账接收方数量={}",
orderId, merchantNo, splitAmount, recvDatas.size());
log.debug("[分账操作] 请求详细参数: {}", JSONUtil.toJsonStr(separateRequest));
// 16. 发送分账请求
log.info("[分账操作] 向拉卡拉发送分账请求:订单={}, 商户={}, 分账流水号={}",
orderId, merchantNo, shopOrderLkl.getLkl_sub_log_no());
String response = LKLSDK.httpPost(separateRequest);
if (StrUtil.isBlank(response)) {
String errorMsg = String.format("[分账操作] 拉卡拉无响应,订单=%s商户=%s分账流水号=%s",
orderId, merchantNo, shopOrderLkl.getLkl_sub_log_no());
log.error(errorMsg);
errorMessages.append(errorMsg).append("; ");
continue;
}
log.debug("[分账操作] 响应结果: {}", response);
// 17. 解析响应结果
JSONObject respJson = JSONUtil.parseObj(response);
if (respJson == null || !lklSacsSuccessCode.equals(respJson.getStr("code")) || respJson.getJSONObject("resp_data") == null) {
String errorMsg = String.format("[分账操作] 拉卡拉返回格式异常,订单=%s商户=%s分账流水号=%s响应=%srespJson=%s",
orderId, merchantNo, shopOrderLkl.getLkl_sub_log_no(), response, respJson);
log.error(errorMsg);
errorMessages.append(errorMsg).append("; ");
continue;
}
// 18. 保存分账记录
JSONObject respData = respJson.getJSONObject("resp_data");
LklOrderSeparate separateRecord = new LklOrderSeparate();
separateRecord.setSeparate_no(respData.getStr("separate_no"));
separateRecord.setOut_separate_no(separateRequest.getOutSeparateNo());
separateRecord.setMerchant_no(merchantNo);
separateRecord.setLog_no(separateRequest.getLogNo()); // 发货完成交易流水号后14位 分账商户用该流水号发起分账
separateRecord.setLog_date(separateRequest.getLogDate());
separateRecord.setOrder_id(shopOrderLkl.getOrder_id());
separateRecord.setNotify_url(separateRequest.getNotifyUrl());
separateRecord.setLkl_org_no(separateRequest.getLklOrgNo());
separateRecord.setRecv_datas(JSONUtil.toJsonStr(separateRequest.getRecvDatas()));
separateRecord.setStatus(respData.getStr("status"));
separateRecord.setTotal_amt(separateRequest.getTotalAmt());
separateRecord.setActual_separate_amt(Convert.toStr(shopOrderLkl.getSplit_amt()));
try {
if (lklOrderSeparateService.addOrUpdateByReceiverNo(separateRecord)) {
log.info("[分账操作] 记录保存成功:订单={}, 分账单号={}, 状态={}, 分账流水号={}",
orderId, separateRecord.getSeparate_no(), separateRecord.getStatus(), separateRecord.getLog_no());
successCount++;
} else {
String errorMsg = String.format("[分账操作] 保存分账记录失败,订单=%s分账单号=%s分账流水号=%s",
orderId, separateRecord.getSeparate_no(), separateRecord.getLog_no());
log.error(errorMsg);
lklOrderSeparateService.updateRemark(separateRecord.getLog_no(), separateRecord.getSeparate_no(), errorMsg);
errorMessages.append(errorMsg).append("; ");
}
} catch (Exception e) {
String errorMsg = String.format("[分账操作] 保存分账记录异常,订单=%s分账单号=%s流水号=%s错误=%s",
orderId,
separateRecord.getSeparate_no(),
separateRecord.getLog_no(),
e.getMessage());
log.error(errorMsg, e);
errorMessages.append(errorMsg).append("; ");
}
}
// 19. 返回最终处理结果
log.info("[分账操作] 处理完成:总订单数={},成功处理数={}", orderId, totalCount, successCount);
if (successCount == 0) {
String result = "分账全部失败: " + errorMessages;
log.warn("[分账操作] 结果:订单[{}] {}", orderId, result);
return Pair.of(false, result);
} else if (successCount < totalCount) {
String result = "部分分账成功,处理中: " + errorMessages;
log.info("[分账操作] 结果:订单[{}] {}", orderId, result);
return Pair.of(true, result);
} else {
String result = "全部订单分账已提交处理";
log.info("[分账操作] 结果:订单[{}] {}", orderId, result);
return Pair.of(true, result);
}
} catch (Exception e) {
String errorMsg = String.format("[分账操作] 系统异常,订单=%s错误=%s", orderId, e.getMessage());
log.error(errorMsg, e);
return Pair.of(false, "系统异常,请稍后重试");
}
}
/**
* 根据商户号交易号和收货流水号执行订单分账操作
* <p>
@ -2838,6 +2573,9 @@ public class LakalaApiServiceImpl implements LakalaApiService {
separateRecord.setTotal_amt(separateRequest.getTotalAmt());
separateRecord.setActual_separate_amt(Convert.toStr(refCanSeparateAmt));
separateRecord.setTotal_fee_amt(Convert.toStr(lklSeparateDTO.getLklAmount()));
if (separateRequest != null) {
separateRecord.setLkl_req(JSONUtil.toJsonStr(separateRequest));
}
if (lklOrderSeparateService.addOrUpdateByReceiverNo(separateRecord)) {
log.info("[分账操作] 分账记录保存成功, orderId={}, separateNo={}, status={}, logNo={}",
@ -3798,6 +3536,9 @@ public class LakalaApiServiceImpl implements LakalaApiService {
lklOrderDraw.setBatch_auto_settle(payType);
lklOrderDraw.setNotify_url(request.getNotifyUrl());
lklOrderDraw.setNotify_resp(responseStr);
if (request != null) {
lklOrderDraw.setLkl_req(JSONUtil.toJsonStr(request));
}
lklOrderDraw.setRemark(remark);
if (StrUtil.isNotBlank(summary)) {
lklOrderDraw.setSummary(summary);

View File

@ -324,7 +324,7 @@ public class UserOrderController extends BaseControllerImpl {
@ApiOperation(value = "可预约订单的时间槽", notes = "可预约订单的时间槽")
@RequestMapping(value = "/booking_time_args", method = RequestMethod.GET)
public CommonResult listInvoice(@RequestParam(name = "store_id", defaultValue = "0") Integer store_id) {
public CommonResult listInvoice(@RequestParam(name = "store_id", defaultValue = "0") String store_id) {
List<BookingArgDTO> list = shopOrderInfoService.genBookingOrderArgList(store_id);
return CommonResult.success(list);
}

View File

@ -139,8 +139,8 @@ public interface ShopOrderInfoService extends IBaseService<ShopOrderInfo> {
/**
* 根据店铺的营业时间范围生成可预约下单的参数
*
* @param storeId 店铺ID
* @param storeId 店铺ID多个店铺使用英文半角逗号隔开57,58,59
* @return
*/
List<BookingArgDTO> genBookingOrderArgList(Integer storeId);
List<BookingArgDTO> genBookingOrderArgList(String storeId);
}

View File

@ -1,5 +1,6 @@
package com.suisung.mall.shop.order.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.NumberUtil;
@ -1017,36 +1018,20 @@ public class ShopOrderInfoServiceImpl extends BaseServiceImpl<ShopOrderInfoMappe
/**
* 根据店铺的营业时间范围生成可预约下单的参数
*
* @param storeId 店铺ID
* @param storeIds 店铺ID多个店铺使用英文半角逗号隔开57,58,59
* @return 预约参数列表
*/
@Override
public List<BookingArgDTO> genBookingOrderArgList(Integer storeId) {
public List<BookingArgDTO> genBookingOrderArgList(String storeIds) {
// 初始化默认营业时间
String openingHours = "09:00";
String closeHours = "18:00";
Map<String, String> timesMap = new HashMap<>();
// 如果storeId不为空则尝试获取店铺信息
if (storeId != null) {
try {
ShopStoreInfo shopStoreInfo = shopStoreInfoService.getShopStoreInfoByStoreId(storeId);
if (shopStoreInfo != null) {
// 检查并使用店铺设置的营业时间
if (StrUtil.isNotBlank(shopStoreInfo.getStore_opening_hours())) {
openingHours = shopStoreInfo.getStore_opening_hours();
}
if (StrUtil.isNotBlank(shopStoreInfo.getStore_close_hours())) {
closeHours = shopStoreInfo.getStore_close_hours();
}
logger.debug("[生成预约参数] 使用店铺营业时间storeId: {}, opening: {}, close: {}", storeId, openingHours, closeHours);
} else {
logger.warn("[生成预约参数] 未找到店铺信息使用默认营业时间storeId: {}", storeId);
}
} catch (Exception e) {
logger.error("[生成预约参数] 获取店铺信息异常使用默认营业时间storeId: {}", storeId, e);
if (StrUtil.isNotBlank(storeIds)) {
List<Map<String, String>> timesMapList = selStoreBizTimeMapList(storeIds);
if (!CollUtil.isEmpty(timesMapList)) {
timesMap = DateTimeUtils.findTimeInterSection(timesMapList);
}
} else {
logger.warn("[生成预约参数] 店铺ID为空使用默认营业时间");
}
List<BookingArgDTO> result = new ArrayList<>();
@ -1082,18 +1067,98 @@ public class ShopOrderInfoServiceImpl extends BaseServiceImpl<ShopOrderInfoMappe
bookingArgDTO.setDate(dateStr);
// 生成时间项
List<BookingArgDTO.BookingArgItem> items = generateTimeSlots(dateStr, openingHours, closeHours, i == 0);
bookingArgDTO.setItems(items != null ? items : new ArrayList<>());
List<BookingArgDTO.BookingArgItem> items = new ArrayList<>();
// 如果是今天始终添加"立即送出"选项
if (i == 0) {
BookingArgDTO.BookingArgItem immediateItem = new BookingArgDTO.BookingArgItem();
immediateItem.setTime_title("立即送出");
immediateItem.setBooking_at(0L);
immediateItem.setBooking_state(1);
immediateItem.setBooking_begin_time("");
immediateItem.setBooking_end_time("");
items.add(immediateItem);
}
// 只有当timesMap不为空时才生成其他时间槽
if (!ObjectUtil.isEmpty(timesMap) && StrUtil.isNotBlank(timesMap.get("startTimeStr")) && StrUtil.isNotBlank(timesMap.get("endTimeStr"))) {
List<BookingArgDTO.BookingArgItem> timeSlots = generateTimeSlots(dateStr, timesMap.get("startTimeStr"), timesMap.get("endTimeStr"), i == 0);
if (i == 0) {
// 对于今天移除除"立即送出"外的所有时间槽
items.addAll(timeSlots.stream().filter(item -> item.getBooking_state() != 1).collect(Collectors.toList()));
} else {
// 对于其他日期添加所有时间槽
items.addAll(timeSlots);
}
} else if (i == 0) {
// 如果timesMap为空今天只保留"立即送出"选项
logger.debug("[生成预约参数] timesMap为空今天只生成立即送出选项");
} else {
// 如果timesMap为空其他日期不生成任何时间槽
logger.debug("[生成预约参数] timesMap为空不生成{}的预约时间槽", dateStr);
continue; // 跳过当前日期
}
bookingArgDTO.setItems(items);
result.add(bookingArgDTO);
}
logger.debug("[生成预约参数] 成功生成预约参数storeId: {}, opening: {}, close: {}, 参数数量: {}",
storeId, openingHours, closeHours, result.size());
logger.debug("[生成预约参数] 成功生成预约参数storeId: {}, timesMap: {}, 参数数量: {}",
storeIds, timesMap, result.size());
return result;
}
/**
* 根据 storeIds一个或多个 storeid 34,23,43,23,先对id去重再获取多个店铺的营业时间 List<map{startTimeStr, endTimeStr}> 列表list
*
* @param storeIds 以逗号分隔的店铺ID字符串
* @return 包含店铺营业时间信息的列表每个元素为包含opening_hours和close_hours的Map
*/
private List<Map<String, String>> selStoreBizTimeMapList(String storeIds) {
// 参数校验
if (StrUtil.isBlank(storeIds)) {
return Collections.emptyList();
}
try {
// 1. 解析并去重店铺ID
List<String> uniqueStoreIds = Arrays.stream(storeIds.split(","))
.map(String::trim)
.filter(StrUtil::isNotBlank)
.distinct()
.collect(Collectors.toList());
// 2. 如果没有有效的店铺ID返回空列表
if (uniqueStoreIds.isEmpty()) {
return Collections.emptyList();
}
// 3. 批量获取店铺信息
QueryWrapper<ShopStoreInfo> queryWrapper = new QueryWrapper<>();
queryWrapper.select("store_opening_hours", "store_close_hours"); // 只查询必要字段
queryWrapper.in("store_id", storeIds);
List<ShopStoreInfo> shopStoreInfos = shopStoreInfoService.find(queryWrapper);
// 4. 转换为营业时间映射列表
return shopStoreInfos.stream()
.filter(Objects::nonNull)
.map(storeInfo -> {
Map<String, String> timeSlot = new HashMap<>();
timeSlot.put("startTimeStr", storeInfo.getStore_opening_hours());
timeSlot.put("endTimeStr", storeInfo.getStore_close_hours());
return timeSlot;
})
.collect(Collectors.toList());
} catch (Exception e) {
logger.error("[获取店铺营业时间] 处理店铺营业时间异常storeIds: {}", storeIds, e);
return Collections.emptyList();
}
}
/**
* 生成时间槽列表
*

View File

@ -39,7 +39,7 @@ public interface ShopUserVoucherService extends IBaseService<ShopUserVoucher> {
Map getLists(QueryWrapper<ShopUserVoucher> voucherQueryWrapper, int page, int rows);
boolean useNeedNotPin(Integer user_id, String order_id);
boolean tryUseFreeGroupVoucher(Integer user_id, String order_id);
// 线下优惠券核销
boolean exitWriteoffUserVoucher(ShopUserVoucher shopUserVoucher);

View File

@ -65,7 +65,7 @@ public class ShopUserVoucherServiceImpl extends BaseServiceImpl<ShopUserVoucherM
@Lazy
@Autowired
private ShopStoreActivityBaseService storeActivityBaseService;
@Lazy
@Autowired
private ShopStoreBaseService shopStoreBaseService;
@ -331,17 +331,20 @@ public class ShopUserVoucherServiceImpl extends BaseServiceImpl<ShopUserVoucherM
}
/**
* 用户使用免拼券
* 用户是否使用免拼券
*
* @param user_id
* @param order_id
* @return
*/
@Override
public boolean useNeedNotPin(Integer user_id, String order_id) {
public boolean tryUseFreeGroupVoucher(Integer user_id, String order_id) {
Date date = new Date();
QueryWrapper<ShopUserVoucher> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("voucher_type", 1).eq("user_id", user_id).eq("voucher_state_id", StateCode.VOUCHER_STATE_UNUSED).ge("voucher_end_date", date);
queryWrapper.eq("voucher_type", 1)
.eq("user_id", user_id)
.eq("voucher_state_id", StateCode.VOUCHER_STATE_UNUSED)
.ge("voucher_end_date", date);
ShopUserVoucher user_voucher_row = findOne(queryWrapper);
if (user_voucher_row != null) {