|
|
@@ -10,13 +10,16 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|
|
import lombok.RequiredArgsConstructor;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
import org.apache.dubbo.config.annotation.DubboReference;
|
|
|
+import org.dromara.common.core.enums.OrderPayType;
|
|
|
import org.dromara.common.core.enums.OrderStatus;
|
|
|
import org.dromara.common.core.utils.MapstructUtils;
|
|
|
import org.dromara.common.core.utils.StringUtils;
|
|
|
import org.dromara.common.mybatis.core.page.PageQuery;
|
|
|
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
|
|
import org.dromara.common.redis.utils.SequenceUtils;
|
|
|
+import org.dromara.customer.api.RemoteCustomerSalesService;
|
|
|
import org.dromara.customer.api.RemoteCustomerService;
|
|
|
+import org.dromara.customer.api.domain.vo.RemoteCustomerSalesVo;
|
|
|
import org.dromara.order.domain.OrderMain;
|
|
|
import org.dromara.order.domain.OrderProduct;
|
|
|
import org.dromara.order.domain.bo.OrderMainBo;
|
|
|
@@ -35,7 +38,9 @@ import org.dromara.system.api.RemoteUserService;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
|
|
|
+import java.math.BigDecimal;
|
|
|
import java.util.*;
|
|
|
+import java.util.function.Function;
|
|
|
import java.util.stream.Collectors;
|
|
|
|
|
|
/**
|
|
|
@@ -58,6 +63,9 @@ public class OrderMainServiceImpl extends ServiceImpl<OrderMainMapper, OrderMain
|
|
|
@DubboReference
|
|
|
private RemoteCustomerService remoteCustomerService;
|
|
|
|
|
|
+ @DubboReference
|
|
|
+ private RemoteCustomerSalesService remoteCustomerSalesService;
|
|
|
+
|
|
|
private final OrderMainMapper baseMapper;
|
|
|
|
|
|
private final OrderProductMapper orderProductMapper;
|
|
|
@@ -239,7 +247,7 @@ public class OrderMainServiceImpl extends ServiceImpl<OrderMainMapper, OrderMain
|
|
|
*/
|
|
|
@Transactional(rollbackFor = Exception.class)
|
|
|
@Override
|
|
|
- public Boolean insertByBo(OrderMainBo bo) {
|
|
|
+ public Long insertByBo(OrderMainBo bo) {
|
|
|
try {
|
|
|
// 1. 校验商品列表
|
|
|
List<OrderProductBo> orderProductBos = bo.getOrderProductBos();
|
|
|
@@ -254,26 +262,40 @@ public class OrderMainServiceImpl extends ServiceImpl<OrderMainMapper, OrderMain
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ BigDecimal totalAmount = orderProductBos.stream().map(OrderProductBo::getSubtotal).reduce(BigDecimal::add).orElse(BigDecimal.ZERO);
|
|
|
+
|
|
|
+ // 应付总额 = 商品总价 + 运费
|
|
|
+ BigDecimal payableAmount = totalAmount.add(bo.getShippingFee());
|
|
|
+
|
|
|
// 2. 生成订单号并转换主单
|
|
|
String orderNo = SequenceUtils.generateOrderCode("RS");
|
|
|
bo.setOrderNo(orderNo);
|
|
|
- Date originalDate = bo.getOrderTime(); // 假设是 2025-01-06 00:00:00
|
|
|
- if (originalDate != null) {
|
|
|
- Calendar calOriginal = Calendar.getInstance();
|
|
|
- calOriginal.setTime(originalDate);
|
|
|
|
|
|
- Calendar calNow = Calendar.getInstance(); // 获取当前时分秒
|
|
|
+ bo.setTotalAmount(totalAmount);
|
|
|
+
|
|
|
+ bo.setPayableAmount(payableAmount);
|
|
|
+
|
|
|
+ // 处理下单时间:前台不传则用当前时间,后台传了则按规则调整
|
|
|
+ Date orderTimeToUse;
|
|
|
+ if (bo.getOrderTime() != null) {
|
|
|
+ // 后台传了时间:保留日期,更新时分秒为当前
|
|
|
+ Calendar cal = Calendar.getInstance();
|
|
|
+ cal.setTime(bo.getOrderTime());
|
|
|
+ Calendar now = Calendar.getInstance();
|
|
|
+ cal.set(Calendar.HOUR_OF_DAY, now.get(Calendar.HOUR_OF_DAY));
|
|
|
+ cal.set(Calendar.MINUTE, now.get(Calendar.MINUTE));
|
|
|
+ cal.set(Calendar.SECOND, now.get(Calendar.SECOND));
|
|
|
+ cal.set(Calendar.MILLISECOND, now.get(Calendar.MILLISECOND));
|
|
|
+ orderTimeToUse = cal.getTime();
|
|
|
+ } else {
|
|
|
+ // 前台未传:直接使用当前时间
|
|
|
+ orderTimeToUse = new Date();
|
|
|
+ }
|
|
|
+ bo.setOrderTime(orderTimeToUse);
|
|
|
|
|
|
- // 保留原日期的年月日,替换时分秒为当前时间
|
|
|
- calOriginal.set(Calendar.HOUR_OF_DAY, calNow.get(Calendar.HOUR_OF_DAY));
|
|
|
- calOriginal.set(Calendar.MINUTE, calNow.get(Calendar.MINUTE));
|
|
|
- calOriginal.set(Calendar.SECOND, calNow.get(Calendar.SECOND));
|
|
|
- calOriginal.set(Calendar.MILLISECOND, calNow.get(Calendar.MILLISECOND));
|
|
|
|
|
|
- bo.setOrderTime(calOriginal.getTime());
|
|
|
- }
|
|
|
+ // 转换主订单
|
|
|
OrderMain orderMain = MapstructUtils.convert(bo, OrderMain.class);
|
|
|
-
|
|
|
validEntityBeforeSave(orderMain);
|
|
|
|
|
|
// 3. 插入主订单
|
|
|
@@ -282,22 +304,39 @@ public class OrderMainServiceImpl extends ServiceImpl<OrderMainMapper, OrderMain
|
|
|
throw new RuntimeException("主订单保存失败");
|
|
|
}
|
|
|
|
|
|
- // 4. 转换并填充子订单商品
|
|
|
Long orderId = orderMain.getId();
|
|
|
+
|
|
|
+ // 4. 转换并填充子订单商品
|
|
|
List<OrderProduct> orderProducts = orderProductBos.stream()
|
|
|
.map(productBo -> {
|
|
|
+ BigDecimal unitPrice = productBo.getOrderPrice();
|
|
|
+ Long quantity = productBo.getOrderQuantity();
|
|
|
+
|
|
|
+ // 防御性校验
|
|
|
+ if (unitPrice == null) {
|
|
|
+ throw new IllegalArgumentException("商品单价不能为空");
|
|
|
+ }
|
|
|
+ if (quantity == null || quantity <= 0) {
|
|
|
+ throw new IllegalArgumentException("商品数量必须大于0");
|
|
|
+ }
|
|
|
+
|
|
|
OrderProduct product = MapstructUtils.convert(productBo, OrderProduct.class);
|
|
|
- product.setOrderId(orderId); // 关联主单ID
|
|
|
- product.setOrderNo(orderNo); // 关联订单号
|
|
|
+ product.setOrderId(orderId);
|
|
|
+ product.setOrderNo(orderNo);
|
|
|
+
|
|
|
+ // 小计 = 单价 × 数量
|
|
|
+ BigDecimal subtotal = unitPrice.multiply(BigDecimal.valueOf(quantity));
|
|
|
+ product.setSubtotal(subtotal);
|
|
|
+
|
|
|
return product;
|
|
|
})
|
|
|
.collect(Collectors.toList());
|
|
|
|
|
|
// 5. 批量插入订单商品
|
|
|
- boolean productsSaved = orderProductMapper.insertBatch(orderProducts);
|
|
|
+ orderProductMapper.insertBatch(orderProducts);
|
|
|
|
|
|
log.info("成功新增订单,ID: {}", orderId);
|
|
|
- return productsSaved;
|
|
|
+ return orderId;
|
|
|
} catch (RuntimeException e) {
|
|
|
log.error("新增订单失败: {}", e.getMessage(), e);
|
|
|
throw new RuntimeException("新增订单失败: " + e.getMessage());
|
|
|
@@ -324,6 +363,187 @@ public class OrderMainServiceImpl extends ServiceImpl<OrderMainMapper, OrderMain
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 订单确认
|
|
|
+ *
|
|
|
+ * @param
|
|
|
+ * @return 是否确认成功
|
|
|
+ */
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ @Override
|
|
|
+ public void confirmOrder(Long orderId, List<OrderProductBo> updatedProducts) {
|
|
|
+ if (orderId == null) {
|
|
|
+ throw new IllegalArgumentException("订单ID不能为空");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 1. 查询现有主订单(加锁防止并发修改)
|
|
|
+ OrderMain existingOrder = baseMapper.selectOne(new LambdaQueryWrapper<OrderMain>()
|
|
|
+ .eq(OrderMain::getId, orderId)
|
|
|
+ .last("FOR UPDATE"));
|
|
|
+ if (existingOrder == null) {
|
|
|
+ throw new IllegalArgumentException("订单不存在");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 检查当前状态:只允许从“待确认”(比如状态0或1)变为“待发货”(2)
|
|
|
+ if (!OrderStatus.PENDING_PAYMENT.getCode().equals(existingOrder.getOrderStatus()) && !OrderStatus.PENDING_CONFIRMATION.getCode().equals(existingOrder.getOrderStatus())) {
|
|
|
+ throw new IllegalStateException("订单状态不可确认,当前状态:" + existingOrder.getOrderStatus());
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 查询现有子订单商品(按 orderId)
|
|
|
+ List<OrderProduct> existingProducts = orderProductMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<OrderProduct>().eq(OrderProduct::getOrderId, orderId)
|
|
|
+ );
|
|
|
+
|
|
|
+ if (CollUtil.isEmpty(existingProducts)) {
|
|
|
+ throw new IllegalStateException("订单商品不存在,无法确认");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 构建商品ID -> 现有商品的映射
|
|
|
+ Map<Long, OrderProduct> existingProductMap = existingProducts.stream()
|
|
|
+ .collect(Collectors.toMap(OrderProduct::getId, Function.identity()));
|
|
|
+
|
|
|
+ // 5. 校验传入的商品列表是否匹配(不能增删商品,只能改数量)
|
|
|
+ if (CollUtil.isEmpty(updatedProducts)) {
|
|
|
+ throw new IllegalArgumentException("确认订单时商品列表不能为空");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (updatedProducts.size() != existingProducts.size()) {
|
|
|
+ throw new IllegalArgumentException("商品数量不一致,不允许增删商品项");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 6. 遍历更新项,仅允许修改数量,并校验ID存在
|
|
|
+ List<OrderProduct> toUpdateProducts = new ArrayList<>();
|
|
|
+ BigDecimal newTotalAmount = BigDecimal.ZERO;
|
|
|
+
|
|
|
+ for (OrderProductBo bo : updatedProducts) {
|
|
|
+ if (bo.getId() == null) {
|
|
|
+ throw new IllegalArgumentException("商品项必须包含ID(用于匹配原订单商品)");
|
|
|
+ }
|
|
|
+
|
|
|
+ OrderProduct existing = existingProductMap.get(bo.getId());
|
|
|
+ if (existing == null) {
|
|
|
+ throw new IllegalArgumentException("商品ID " + bo.getId() + " 不属于该订单");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 仅允许修改数量(可选),其他字段如 price、skuCode 等必须保持一致
|
|
|
+ Long newQty = bo.getOrderQuantity();
|
|
|
+ if (newQty == null || newQty <= 0) {
|
|
|
+ throw new IllegalArgumentException("商品数量必须大于0");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 重新计算小计:注意使用原单价 * 新数量(防止前端篡改价格)
|
|
|
+ BigDecimal unitPrice = existing.getOrderPrice(); // 使用数据库中的原始单价
|
|
|
+ BigDecimal newSubtotal = unitPrice.multiply(BigDecimal.valueOf(newQty));
|
|
|
+
|
|
|
+ // 更新对象(仅数量和小计)
|
|
|
+ existing.setOrderQuantity(newQty);
|
|
|
+ existing.setSubtotal(newSubtotal);
|
|
|
+
|
|
|
+ toUpdateProducts.add(existing);
|
|
|
+ newTotalAmount = newTotalAmount.add(newSubtotal);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 7. 重新计算应付金额(运费不变)
|
|
|
+ BigDecimal newPayableAmount = newTotalAmount.add(existingOrder.getShippingFee());
|
|
|
+
|
|
|
+ // 8. 更新主订单:仅更新 totalAmount、payableAmount 和 orderStatus
|
|
|
+ existingOrder.setTotalAmount(newTotalAmount);
|
|
|
+ existingOrder.setPayableAmount(newPayableAmount);
|
|
|
+ existingOrder.setOrderStatus(OrderStatus.PENDING_SHIPMENT.getCode()); // 待发货
|
|
|
+
|
|
|
+ // 9. 执行更新
|
|
|
+ int mainUpdated = baseMapper.updateById(existingOrder);
|
|
|
+ if (mainUpdated <= 0) {
|
|
|
+ throw new RuntimeException("主订单确认失败");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 10. 批量更新子订单商品
|
|
|
+ orderProductMapper.updateBatchById(toUpdateProducts);
|
|
|
+
|
|
|
+ log.info("订单 {} 已确认,状态更新为待发货", orderId);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public Boolean orderPay(Long customerId, Long orderId, String payType) {
|
|
|
+ // 参数校验
|
|
|
+ if (orderId == null) {
|
|
|
+ throw new IllegalArgumentException("订单ID不能为空");
|
|
|
+ }
|
|
|
+ if (customerId == null) {
|
|
|
+ throw new IllegalArgumentException("客户ID不能为空");
|
|
|
+ }
|
|
|
+ if (payType == null || !OrderPayType.CREDIT_PAY.getCode().equals(payType)) {
|
|
|
+ throw new IllegalArgumentException("仅支持信用支付方式");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 查询订单
|
|
|
+ OrderMainVo orderMainVo = baseMapper.selectVoById(orderId);
|
|
|
+ if (orderMainVo == null) {
|
|
|
+ throw new IllegalArgumentException("订单不存在");
|
|
|
+ }
|
|
|
+ if (!Objects.equals(customerId, orderMainVo.getCustomerId())) {
|
|
|
+ throw new IllegalArgumentException("订单不属于当前客户");
|
|
|
+ }
|
|
|
+
|
|
|
+ BigDecimal totalAmount = orderMainVo.getTotalAmount();
|
|
|
+ if (totalAmount == null || totalAmount.compareTo(BigDecimal.ZERO) <= 0) {
|
|
|
+ throw new IllegalArgumentException("订单金额无效");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 查询客户信用销售信息
|
|
|
+ RemoteCustomerSalesVo salesInfo = remoteCustomerSalesService.selectCustomerSalesInfoByCustomerId(customerId);
|
|
|
+ if (salesInfo == null) {
|
|
|
+ throw new IllegalStateException("客户信用信息未配置,无法使用信用支付");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取可用额度:剩余额度 + 临时额度
|
|
|
+ BigDecimal remainingQuota = Optional.ofNullable(salesInfo.getRemainingQuota()).orElse(BigDecimal.ZERO);
|
|
|
+ BigDecimal temporaryQuota = Optional.ofNullable(salesInfo.getTemporaryQuota()).orElse(BigDecimal.ZERO);
|
|
|
+
|
|
|
+
|
|
|
+ // 临时额度可用于支付
|
|
|
+ BigDecimal availableCredit = remainingQuota.add(temporaryQuota);
|
|
|
+
|
|
|
+ // 判断额度是否足够
|
|
|
+ if (availableCredit.compareTo(totalAmount) < 0) {
|
|
|
+ throw new IllegalStateException("信用额度不足,无法完成支付");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 扣减逻辑:优先扣减临时额度,再扣剩余额度
|
|
|
+ BigDecimal newRemainingQuota = remainingQuota;
|
|
|
+ BigDecimal newTemporaryQuota = temporaryQuota;
|
|
|
+
|
|
|
+ if (temporaryQuota.compareTo(totalAmount) >= 0) {
|
|
|
+ // 临时额度足够,只扣临时额度
|
|
|
+ newTemporaryQuota = temporaryQuota.subtract(totalAmount);
|
|
|
+ } else {
|
|
|
+ // 临时额度不足,先清空临时额度,再扣剩余额度
|
|
|
+ BigDecimal remainingToDeduct = totalAmount.subtract(temporaryQuota);
|
|
|
+ newTemporaryQuota = BigDecimal.ZERO;
|
|
|
+ newRemainingQuota = remainingQuota.subtract(remainingToDeduct);
|
|
|
+
|
|
|
+ // 理论上不会为负(前面已校验总额度足够),但防御性检查
|
|
|
+ if (newRemainingQuota.compareTo(BigDecimal.ZERO) < 0) {
|
|
|
+ throw new IllegalStateException("信用额度计算异常,请联系管理员");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新对象
|
|
|
+ salesInfo.setRemainingQuota(newRemainingQuota);
|
|
|
+ salesInfo.setTemporaryQuota(newTemporaryQuota);
|
|
|
+
|
|
|
+ // 执行更新
|
|
|
+ boolean updated = remoteCustomerSalesService.updateCustomerSalesInfo(salesInfo);
|
|
|
+ if (!updated) {
|
|
|
+ throw new RuntimeException("信用额度更新失败,请重试");
|
|
|
+ }
|
|
|
+
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 修改状态
|
|
|
*
|