|
|
@@ -136,6 +136,9 @@ import me.chanjar.weixin.common.error.WxErrorException;
|
|
|
import org.apache.commons.collections4.CollectionUtils;
|
|
|
import org.apache.commons.lang.ObjectUtils;
|
|
|
import org.apache.http.util.Asserts;
|
|
|
+import com.fs.framework.datasource.DynamicDataSourceContextHolder;
|
|
|
+import org.redisson.api.RLock;
|
|
|
+import org.redisson.api.RedissonClient;
|
|
|
import org.slf4j.Logger;
|
|
|
import org.slf4j.LoggerFactory;
|
|
|
import org.springframework.aop.framework.AopContext;
|
|
|
@@ -166,6 +169,8 @@ import java.time.format.DateTimeFormatter;
|
|
|
import java.time.format.DateTimeParseException;
|
|
|
import java.util.*;
|
|
|
import java.util.concurrent.TimeUnit;
|
|
|
+
|
|
|
+import static com.fs.hisStore.constants.UserAppsLockConstant.*;
|
|
|
import java.util.stream.Collectors;
|
|
|
|
|
|
import static com.fs.his.utils.PhoneUtil.decryptPhone;
|
|
|
@@ -410,6 +415,9 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
|
|
|
@Autowired
|
|
|
private FsUserCompanyPackageScrmMapper fsUserCompanyPackageScrmMapper;
|
|
|
|
|
|
+ @Autowired
|
|
|
+ private RedissonClient redissonClient;
|
|
|
+
|
|
|
/**
|
|
|
* 租户编码请求头名称。
|
|
|
* 前端需在每次请求中携带该头,才能启用多租户能力。
|
|
|
@@ -5660,6 +5668,252 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
|
|
|
return fsStoreOrderMapper.updateFsStoreOrder(order);
|
|
|
}
|
|
|
|
|
|
+ // ==================== 合并多店铺下单 ====================
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构建多租户隔离的分布式锁 Key
|
|
|
+ * 多租户: 1:order:multiStore:create:10001
|
|
|
+ * 单租户: order:multiStore:create:10001
|
|
|
+ */
|
|
|
+ private String buildTenantLockKey(String keyFormat, Object... args) {
|
|
|
+ Long tenantId = DynamicDataSourceContextHolder.getCurrentTenantId();
|
|
|
+ String prefix = tenantId != null ? tenantId + ":" : "";
|
|
|
+ return prefix + String.format(keyFormat, args);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public R createMultiStoreOrder(long userId, FsStoreMultiStoreOrderCreateParam param) {
|
|
|
+ RLock lock = redissonClient.getLock(buildTenantLockKey(LOCK_KEY_MULTI_STORE_CREATE, userId));
|
|
|
+ try {
|
|
|
+ boolean locked = lock.tryLock(5, 30, TimeUnit.SECONDS);
|
|
|
+ if (!locked) {
|
|
|
+ log.warn("合并多店铺下单失败: 用户{}正在创建订单中,获取锁失败", userId);
|
|
|
+ return R.error("您有订单正在处理中,请稍后再试");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 1. 收集所有 cartIds 并查询购物车信息(含店铺ID)
|
|
|
+ List<Long> allCartIds = new ArrayList<>();
|
|
|
+ for (FsStoreMultiStoreOrderCreateParam.StoreCartGroup group : param.getStoreCarts()) {
|
|
|
+ allCartIds.addAll(group.getCartIds());
|
|
|
+ }
|
|
|
+ if (allCartIds.isEmpty()) {
|
|
|
+ return R.error("购物车ID列表不能为空");
|
|
|
+ }
|
|
|
+
|
|
|
+ List<FsStoreCartStoreVO> allCarts = fsStoreOrderMapper.selectCartByCartIds(allCartIds);
|
|
|
+ if (allCarts == null || allCarts.isEmpty()) {
|
|
|
+ return R.error("购物车数据不存在或已过期");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 按店铺分组
|
|
|
+ Map<Long, List<FsStoreCartStoreVO>> storeCartMap = new HashMap<>();
|
|
|
+ for (FsStoreCartStoreVO cart : allCarts) {
|
|
|
+ storeCartMap.computeIfAbsent(cart.getStoreId(), k -> new ArrayList<>()).add(cart);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 获取用户地址
|
|
|
+ FsUserAddressScrm address = null;
|
|
|
+ if (param.getAddressId() != null) {
|
|
|
+ address = userAddressMapper.selectFsUserAddressById(param.getAddressId());
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 获取用户信息
|
|
|
+ FsUserScrm user = userService.selectFsUserById(userId);
|
|
|
+
|
|
|
+ // 5. 按店铺逐个创建订单
|
|
|
+ FsStoreMultiStoreOrderResultVO result = new FsStoreMultiStoreOrderResultVO();
|
|
|
+ List<FsStoreMultiStoreOrderResultVO.StoreOrderEntry> orderEntries = new ArrayList<>();
|
|
|
+ BigDecimal totalAmount = BigDecimal.ZERO;
|
|
|
+
|
|
|
+ for (Map.Entry<Long, List<FsStoreCartStoreVO>> entry : storeCartMap.entrySet()) {
|
|
|
+ Long storeId = entry.getKey();
|
|
|
+ List<FsStoreCartStoreVO> storeCarts = entry.getValue();
|
|
|
+ String storeName = storeCarts.get(0).getStoreName();
|
|
|
+
|
|
|
+ // 生成订单号
|
|
|
+ String orderSn = OrderCodeUtils.getOrderSn();
|
|
|
+
|
|
|
+ // 组装成 FsStoreCartQueryVO 以复用计价逻辑
|
|
|
+ List<FsStoreCartQueryVO> cartQueryVOs = new ArrayList<>();
|
|
|
+ StringBuilder cartIdSb = new StringBuilder();
|
|
|
+ for (FsStoreCartStoreVO cart : storeCarts) {
|
|
|
+ FsStoreCartQueryVO vo = new FsStoreCartQueryVO();
|
|
|
+ vo.setId(cart.getId());
|
|
|
+ vo.setProductId(cart.getProductId());
|
|
|
+ vo.setCartNum(cart.getCartNum());
|
|
|
+ vo.setPrice(cart.getPrice());
|
|
|
+ vo.setProductName(cart.getProductName());
|
|
|
+ vo.setProductImage(cart.getProductImage());
|
|
|
+ cartQueryVOs.add(vo);
|
|
|
+ if (cartIdSb.length() > 0) cartIdSb.append(",");
|
|
|
+ cartIdSb.append(cart.getId());
|
|
|
+ }
|
|
|
+
|
|
|
+ // 计算价格
|
|
|
+ BigDecimal payPrice = getOrderSumPrice(cartQueryVOs, "truePrice");
|
|
|
+ FsStoreOrderPriceDTO priceGroup = this.getOrderPriceGroup(cartQueryVOs, address);
|
|
|
+ BigDecimal payPostage = priceGroup.getStorePostage();
|
|
|
+ if (payPostage.compareTo(BigDecimal.valueOf(-1)) == 0) {
|
|
|
+ throw new ServiceException("店铺[" + storeName + "]偏远地区暂不可购买");
|
|
|
+ }
|
|
|
+ payPrice = NumberUtil.add(payPrice, payPostage);
|
|
|
+
|
|
|
+ // 构建订单实体
|
|
|
+ FsStoreOrderScrm storeOrder = new FsStoreOrderScrm();
|
|
|
+ storeOrder.setStoreId(storeId);
|
|
|
+ storeOrder.setStoreHouseCode("CK01");
|
|
|
+ storeOrder.setUserId(userId);
|
|
|
+ storeOrder.setOrderCode(orderSn);
|
|
|
+ storeOrder.setCartId(cartIdSb.toString());
|
|
|
+ storeOrder.setTotalNum((long) storeCarts.size());
|
|
|
+ storeOrder.setTotalPrice(payPrice);
|
|
|
+ storeOrder.setTotalPostage(payPostage);
|
|
|
+ storeOrder.setPayPostage(payPostage);
|
|
|
+ storeOrder.setDeductionPrice(priceGroup.getCouponPrice());
|
|
|
+ storeOrder.setPaid(0);
|
|
|
+ storeOrder.setPayType(param.getPayType());
|
|
|
+ storeOrder.setStatus(0);
|
|
|
+ storeOrder.setRefundStatus(0);
|
|
|
+ storeOrder.setBackIntegral(BigDecimal.ZERO);
|
|
|
+ storeOrder.setGainIntegral(BigDecimal.ZERO);
|
|
|
+ storeOrder.setMark(param.getMark());
|
|
|
+ storeOrder.setIsChannel(1);
|
|
|
+ storeOrder.setIsDel(0);
|
|
|
+ storeOrder.setIsSysDel(0);
|
|
|
+ storeOrder.setOrderType(param.getOrderType());
|
|
|
+ storeOrder.setOrderMedium(param.getOrderMedium());
|
|
|
+
|
|
|
+ // 成本价
|
|
|
+ BigDecimal costPrice = this.getOrderSumPrice(cartQueryVOs, "costPrice");
|
|
|
+ storeOrder.setCost(costPrice);
|
|
|
+
|
|
|
+ // 地址信息
|
|
|
+ if (address != null) {
|
|
|
+ storeOrder.setRealName(address.getRealName());
|
|
|
+ storeOrder.setUserPhone(address.getPhone());
|
|
|
+ storeOrder.setUserAddress(address.getProvince() + " " + address.getCity()
|
|
|
+ + " " + address.getDistrict() + " " + address.getDetail().trim());
|
|
|
+ }
|
|
|
+
|
|
|
+ // 插入订单
|
|
|
+ fsStoreOrderMapper.insertFsStoreOrder(storeOrder);
|
|
|
+ Long orderId = storeOrder.getId();
|
|
|
+
|
|
|
+ // 插入订单明细和扣减库存
|
|
|
+ for (FsStoreCartQueryVO cartVO : cartQueryVOs) {
|
|
|
+ FsStoreOrderItemScrm item = new FsStoreOrderItemScrm();
|
|
|
+ item.setOrderId(orderId);
|
|
|
+ item.setOrderCode(orderSn);
|
|
|
+ item.setProductId(cartVO.getProductId());
|
|
|
+ item.setCartId(cartVO.getId());
|
|
|
+ item.setNum(cartVO.getCartNum());
|
|
|
+ item.setStoreId(storeId);
|
|
|
+
|
|
|
+ JSONObject itemJson = new JSONObject();
|
|
|
+ itemJson.put("productName", cartVO.getProductName());
|
|
|
+ itemJson.put("productImage", cartVO.getProductImage());
|
|
|
+ itemJson.put("price", cartVO.getPrice());
|
|
|
+ itemJson.put("num", cartVO.getCartNum());
|
|
|
+ item.setJsonInfo(itemJson.toJSONString());
|
|
|
+ fsStoreOrderItemMapper.insertFsStoreOrderItem(item);
|
|
|
+
|
|
|
+ // 扣减库存
|
|
|
+ FsStoreProductScrm product = productService.selectFsStoreProductById(cartVO.getProductId());
|
|
|
+ if (product != null) {
|
|
|
+ if (product.getStock() >= cartVO.getCartNum()) {
|
|
|
+ product.setStock(product.getStock() - cartVO.getCartNum());
|
|
|
+ productService.updateFsStoreProduct(product);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 清理购物车
|
|
|
+ for (FsStoreCartQueryVO cartVO : cartQueryVOs) {
|
|
|
+ FsStoreCartScrm deleteCart = new FsStoreCartScrm();
|
|
|
+ deleteCart.setId(cartVO.getId());
|
|
|
+ cartMapper.deleteFsStoreCartByIds(new Long[]{cartVO.getId()});
|
|
|
+ }
|
|
|
+
|
|
|
+ // 组装返回结果
|
|
|
+ FsStoreMultiStoreOrderResultVO.StoreOrderEntry storeOrderEntry = new FsStoreMultiStoreOrderResultVO.StoreOrderEntry();
|
|
|
+ storeOrderEntry.setStoreId(storeId);
|
|
|
+ storeOrderEntry.setStoreName(storeName);
|
|
|
+ storeOrderEntry.setOrderId(orderId);
|
|
|
+ storeOrderEntry.setOrderCode(orderSn);
|
|
|
+ storeOrderEntry.setPayPrice(payPrice);
|
|
|
+ orderEntries.add(storeOrderEntry);
|
|
|
+
|
|
|
+ totalAmount = totalAmount.add(payPrice);
|
|
|
+ }
|
|
|
+
|
|
|
+ result.setOrderCount(orderEntries.size());
|
|
|
+ result.setTotalAmount(totalAmount);
|
|
|
+ result.setOrders(orderEntries);
|
|
|
+ return R.ok().put("map",result);
|
|
|
+
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ Thread.currentThread().interrupt();
|
|
|
+ log.error("合并多店铺下单被中断, userId: {}", userId, e);
|
|
|
+ return R.error("下单处理被中断,请稍后重试");
|
|
|
+ } catch (ServiceException e) {
|
|
|
+ // 运费模板等业务异常直接返回
|
|
|
+ return R.error(e.getMessage());
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("合并多店铺下单异常, userId: {}", userId, e);
|
|
|
+ return R.error("下单失败: " + e.getMessage());
|
|
|
+ } finally {
|
|
|
+ if (lock.isHeldByCurrentThread()) {
|
|
|
+ lock.unlock();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== 整单退款 ====================
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public R fullOrderRefund(Long orderId) {
|
|
|
+ RLock lock = redissonClient.getLock(buildTenantLockKey(LOCK_KEY_ORDER_REFUND, orderId));
|
|
|
+ try {
|
|
|
+ boolean locked = lock.tryLock(5, 30, TimeUnit.SECONDS);
|
|
|
+ if (!locked) {
|
|
|
+ log.warn("整单退款失败: 订单{}正在退款中,获取锁失败", orderId);
|
|
|
+ return R.error("订单正在退款处理中,请勿重复提交");
|
|
|
+ }
|
|
|
+
|
|
|
+ FsStoreOrderScrm order = fsStoreOrderMapper.selectFsStoreOrderById(orderId);
|
|
|
+ if (order == null) {
|
|
|
+ return R.error("订单不存在");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 已退款订单不可重复退款
|
|
|
+ if (order.getStatus() == -2) {
|
|
|
+ return R.error("该订单已退款");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 仅已支付或已发货的订单可退款
|
|
|
+ if (order.getStatus() != 1 && order.getStatus() != 2) {
|
|
|
+ return R.error("当前订单状态不支持退款");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 调用已有的退款逻辑
|
|
|
+ return refundOrderMoney(orderId);
|
|
|
+
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ Thread.currentThread().interrupt();
|
|
|
+ log.error("整单退款被中断, orderId: {}", orderId, e);
|
|
|
+ return R.error("退款处理被中断,请稍后重试");
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("整单退款异常, orderId: {}", orderId, e);
|
|
|
+ return R.error("退款失败: " + e.getMessage());
|
|
|
+ } finally {
|
|
|
+ if (lock.isHeldByCurrentThread()) {
|
|
|
+ lock.unlock();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
private static final DateTimeFormatter CST_FORMATTER = DateTimeFormatter
|
|
|
.ofPattern("EEE MMM dd HH:mm:ss zzz yyyy", Locale.US)
|
|
|
.withZone(ZoneId.of("Asia/Shanghai"));
|