1.下单增加商品判断是否水票优化、默认订单类型功能
2.订单增加生成核销码功能 3.自提订单下单不校验电子围栏;配送订单如在电子围栏内默认自配送,在电子围栏外默认发快递 4.商品增加:配送方式、水票标识业务;商品订单增加:订单类型、水票标识、核销码业务
This commit is contained in:
@@ -262,6 +262,9 @@ public class User implements UserDetails {
|
||||
@Schema(description = "微信unionid")
|
||||
private String unionid;
|
||||
|
||||
@Schema(description = "核销权限是否开启 0-未开启 1-已开启")
|
||||
private Integer verifyFlag;
|
||||
|
||||
@Schema(description = "关联用户ID")
|
||||
@TableField(exist = false)
|
||||
private Integer sysUserId;
|
||||
|
||||
@@ -131,7 +131,7 @@ public class GltTicketIssueService {
|
||||
}
|
||||
|
||||
@Async
|
||||
// @Scheduled(cron = "0/1 * 4-22 * * ?") 没秒钟执行一次
|
||||
// @Scheduled(cron = "0/1 * 4-22 * * ?") 每秒钟执行一次
|
||||
public void paySuccessExecute(String orderNo, Integer tenantId){
|
||||
suerTicketRelease(orderNo, tenantId);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import com.gxwebsoft.common.core.utils.WechatPayConfigValidator;
|
||||
import com.gxwebsoft.common.core.web.BaseController;
|
||||
import com.gxwebsoft.common.system.entity.Payment;
|
||||
import com.gxwebsoft.glt.service.GltTicketIssueService;
|
||||
import com.gxwebsoft.shop.dto.VerifyShopOrderDto;
|
||||
import com.gxwebsoft.shop.entity.ShopOrderDelivery;
|
||||
import com.gxwebsoft.shop.entity.ShopUserAddress;
|
||||
import com.gxwebsoft.shop.service.*;
|
||||
@@ -136,14 +137,13 @@ public class ShopOrderController extends BaseController {
|
||||
return success(shopOrderService.getByIdRel(id));
|
||||
}
|
||||
|
||||
@Operation(summary = "添加订单")
|
||||
@Operation(summary = "添加订单【单商品添加】")
|
||||
@PostMapping()
|
||||
public ApiResult<?> save(@RequestBody OrderCreateRequest request) {
|
||||
User loginUser = getLoginUser();
|
||||
if (loginUser == null) {
|
||||
return fail("用户未登录");
|
||||
}
|
||||
|
||||
try {
|
||||
Map<String, String> wxOrderInfo = orderBusinessService.createOrder(request, loginUser);
|
||||
return success("下单成功", wxOrderInfo);
|
||||
@@ -961,6 +961,12 @@ public class ShopOrderController extends BaseController {
|
||||
return "fail";
|
||||
}
|
||||
|
||||
@Operation(summary = "核销订单", description = "核销员扫码核销用户订单")
|
||||
@PutMapping("/verifyOrder")
|
||||
public ApiResult<Boolean> verifyOrder(@RequestBody VerifyShopOrderDto verifyDto){
|
||||
return success(shopOrderService.verifyOrder(verifyDto));
|
||||
}
|
||||
|
||||
@Operation(summary = "更新订单支付状态", description = "用户支付成功后主动同步订单状态")
|
||||
@PutMapping("/payment-status")
|
||||
public ApiResult<?> updateOrderPaymentStatus(@RequestBody UpdatePaymentStatusRequest request) {
|
||||
|
||||
@@ -27,6 +27,9 @@ public class OrderCreateRequest {
|
||||
@Max(value = 2, message = "订单类型值无效")
|
||||
private Integer type;
|
||||
|
||||
@Schema(description = "订单类型 1-及时自配送 2-自提 3-预约自配送 4-发快递 5-配送【系统自动识别电子围栏内转及时配送,电子围栏外发快递】")
|
||||
private Integer orderType;
|
||||
|
||||
@Size(max = 60, message = "备注长度不能超过60个字符")
|
||||
@Schema(description = "订单标题")
|
||||
private String title;
|
||||
|
||||
25
src/main/java/com/gxwebsoft/shop/dto/VerifyShopOrderDto.java
Normal file
25
src/main/java/com/gxwebsoft/shop/dto/VerifyShopOrderDto.java
Normal file
@@ -0,0 +1,25 @@
|
||||
package com.gxwebsoft.shop.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
|
||||
/**
|
||||
* 更新订单支付状态请求DTO
|
||||
*
|
||||
* @author xm
|
||||
* @since 2026-05-04
|
||||
*/
|
||||
@Data
|
||||
@Schema(name = "VerifyShopOrderDto", description = "更新订单支付状态请求")
|
||||
public class VerifyShopOrderDto {
|
||||
|
||||
@Schema(description = "核销码")
|
||||
@NotBlank(message = "核销码不能为空")
|
||||
private String verifyCode;
|
||||
|
||||
@Schema(description = "核销码类型 1-普通核销【无推广佣金】 2-推广结算【有订单佣金】")
|
||||
private Integer verifyType;
|
||||
|
||||
}
|
||||
@@ -5,6 +5,8 @@ import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.annotation.JsonAlias;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import java.io.Serializable;
|
||||
@@ -21,6 +23,7 @@ import lombok.EqualsAndHashCode;
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@Schema(name = "ShopGoods对象", description = "商品")
|
||||
@TableName("shop_goods")
|
||||
public class ShopGoods implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@@ -171,6 +174,15 @@ public class ShopGoods implements Serializable {
|
||||
@Schema(description = "租户id")
|
||||
private Integer tenantId;
|
||||
|
||||
@Schema(description = "配送方式:1-自配送 2-自提 4-发快递 多属性用','隔开")
|
||||
private String deliveryType;
|
||||
|
||||
@Schema(description = "水票标识 0-否 1-是")
|
||||
private Integer waterTicketFlag;
|
||||
|
||||
@Schema(description = "水票ID")
|
||||
private Integer waterTickerId;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@@ -33,6 +33,9 @@ public class ShopOrder implements Serializable {
|
||||
@TableId(value = "order_id", type = IdType.AUTO)
|
||||
private Integer orderId;
|
||||
|
||||
@Schema(description = "订单类型 1-及时自配送 2-自提 3-预约自配送 4-发快递")
|
||||
private Integer orderType;
|
||||
|
||||
@Schema(description = "订单编号")
|
||||
private String orderNo;
|
||||
|
||||
@@ -310,6 +313,24 @@ public class ShopOrder implements Serializable {
|
||||
@Schema(description = "秒杀活动ID")
|
||||
private Integer activityId;
|
||||
|
||||
@Schema(description = "水票订单标识 0-否 1-是")
|
||||
private Integer waterTicketFlag;
|
||||
|
||||
@Schema(description = "核销码")
|
||||
private String verifyCode;
|
||||
|
||||
@Schema(description = "核销状态 0-未核销 1-已核销")
|
||||
private Integer verifyStatus;
|
||||
|
||||
@Schema(description = "推广佣金结算核销过期时间")
|
||||
private LocalDateTime verifyExpTime;
|
||||
|
||||
@Schema(description = "核销时间")
|
||||
private LocalDateTime verifyTime;
|
||||
|
||||
@Schema(description = "核销人")
|
||||
private Integer verifyUser;
|
||||
|
||||
@Schema(description = "修改时间")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
package com.gxwebsoft.shop.service;
|
||||
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.gxwebsoft.common.core.exception.BusinessException;
|
||||
import com.gxwebsoft.common.core.exception.enums.GlobalErrorCodeConstants;
|
||||
import com.gxwebsoft.common.system.entity.User;
|
||||
import com.gxwebsoft.shop.config.OrderConfigProperties;
|
||||
import com.gxwebsoft.shop.dto.OrderCreateRequest;
|
||||
import com.gxwebsoft.shop.entity.*;
|
||||
import com.gxwebsoft.shop.mapper.ShopFlashSaleActivityMapper;
|
||||
import com.gxwebsoft.shop.service.ShopStoreFenceService;
|
||||
import com.gxwebsoft.shop.mapper.ShopOrderMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -19,6 +19,7 @@ import org.springframework.util.CollectionUtils;
|
||||
import javax.annotation.Resource;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.security.SecureRandom;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -37,9 +38,16 @@ public class OrderBusinessService {
|
||||
private static final int DEDUCT_STOCK_TYPE_ORDER = 10; // 下单减库存
|
||||
private static final int DEDUCT_STOCK_TYPE_PAY = 20; // 付款减库存
|
||||
|
||||
// 去除易混淆字符:0 O 1 I L 8 B Z
|
||||
private static final String CHAR_POOL = "2345679ACDEFGHJKMNPQRSTUVWXY";
|
||||
private static final SecureRandom RANDOM = new SecureRandom();
|
||||
|
||||
@Resource
|
||||
private ShopOrderService shopOrderService;
|
||||
|
||||
@Resource
|
||||
private ShopOrderMapper shopOrderMapper;
|
||||
|
||||
@Resource
|
||||
private ShopOrderGoodsService shopOrderGoodsService;
|
||||
|
||||
@@ -291,6 +299,10 @@ public class OrderBusinessService {
|
||||
}
|
||||
shopOrder.setExpirationTime(shopOrder.getCreateTime().plusMinutes(10));
|
||||
|
||||
//设置核销码及过期时间
|
||||
shopOrder.setVerifyCode(getVerifyCode());
|
||||
shopOrder.setVerifyExpTime(shopOrder.getCreateTime().plusMinutes(10));
|
||||
|
||||
// 确保租户ID正确设置(关键字段,影响微信支付证书路径)
|
||||
shopOrder.setTenantId(loginUser.getTenantId());
|
||||
|
||||
@@ -356,9 +368,76 @@ public class OrderBusinessService {
|
||||
processCoupon(shopOrder, loginUser);
|
||||
}
|
||||
|
||||
//订单类型、水票判断 TODO 目前没有购物车业务,每笔订单只下一个商品,后期加购物车多商品同时下单业务需调整
|
||||
Integer orderType = request.getOrderType();
|
||||
List<OrderCreateRequest.OrderGoodsItem> goodsItems = request.getGoodsItems();
|
||||
if(!CollectionUtils.isEmpty(goodsItems)){
|
||||
OrderCreateRequest.OrderGoodsItem goodsItem = goodsItems.get(0);
|
||||
ShopGoods shopGoods = shopGoodsService.getById(goodsItem.getGoodsId());
|
||||
if(shopGoods != null){
|
||||
Integer waterTicketFlag = shopGoods.getWaterTicketFlag();
|
||||
if(waterTicketFlag != null && waterTicketFlag == 1){
|
||||
shopOrder.setWaterTicketFlag(1);
|
||||
}else {
|
||||
shopOrder.setWaterTicketFlag(0);
|
||||
}
|
||||
|
||||
//单一配送方式商品如用户没选择配送方式按商品默认配送属性设定订单类型
|
||||
String deliveryType = shopGoods.getDeliveryType();
|
||||
if(orderType == null && StrUtil.isNotBlank(deliveryType) && !deliveryType.contains(",")){
|
||||
switch (deliveryType){
|
||||
case "1":
|
||||
case "3":{
|
||||
orderType = 1;
|
||||
break;
|
||||
}
|
||||
case "2":{
|
||||
orderType = 2;
|
||||
break;
|
||||
}
|
||||
case "4":{
|
||||
orderType = 4;
|
||||
break;
|
||||
}
|
||||
default:{
|
||||
orderType = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
shopOrder.setOrderType(orderType);
|
||||
|
||||
return shopOrder;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成8位字母数字混合核销码(推荐)
|
||||
*/
|
||||
public static String generate6Mix() {
|
||||
StringBuilder sb = new StringBuilder(6);
|
||||
int len = CHAR_POOL.length();
|
||||
for (int i = 0; i < 6; i++) {
|
||||
int idx = RANDOM.nextInt(len);
|
||||
sb.append(CHAR_POOL.charAt(idx));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取不重复的核销码
|
||||
* @return
|
||||
*/
|
||||
public String getVerifyCode(){
|
||||
String verifyCode = generate6Mix();
|
||||
LambdaQueryWrapper<ShopOrder> orderLambdaQueryWrapper = new LambdaQueryWrapper<ShopOrder>().select(ShopOrder::getOrderId).eq(ShopOrder::getVerifyCode, verifyCode);
|
||||
List<ShopOrder> shopOrders = shopOrderMapper.selectList(orderLambdaQueryWrapper);
|
||||
if(!CollectionUtils.isEmpty(shopOrders)){
|
||||
getVerifyCode();
|
||||
}
|
||||
return verifyCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理优惠券
|
||||
*/
|
||||
@@ -556,6 +635,12 @@ public class OrderBusinessService {
|
||||
return;
|
||||
}
|
||||
|
||||
//自提订单类型不用校验电子围栏
|
||||
Integer orderType = shopOrder.getOrderType();
|
||||
if(orderType != null && orderType == 2){
|
||||
return;
|
||||
}
|
||||
|
||||
// 若已配置围栏,则必须有 addressId 才能从地址表取坐标进行校验
|
||||
if (shopOrder.getAddressId() == null) {
|
||||
if (shopStoreFenceService.hasEnabledFences(shopOrder.getTenantId())) {
|
||||
@@ -586,9 +671,18 @@ public class OrderBusinessService {
|
||||
throw new BusinessException("收货地址坐标异常,请重新选择收货地址");
|
||||
}
|
||||
|
||||
// 只做校验;具体围栏列表/points 异常处理由围栏服务统一处理
|
||||
//订单类型:5-配送【系统自动识别电子围栏内转及时配送,电子围栏外发快递】
|
||||
if(orderType != null && orderType == 5){
|
||||
Boolean exit = shopStoreFenceService.validatePointInEnabled(shopOrder.getTenantId(), lng, lat);
|
||||
if(exit){
|
||||
shopOrder.setOrderType(1);
|
||||
}else {
|
||||
shopOrder.setOrderType(4);
|
||||
}
|
||||
}else {
|
||||
shopStoreFenceService.validatePointInEnabledFences(shopOrder.getTenantId(), lng, lat);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用业务规则
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.gxwebsoft.shop.service;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.gxwebsoft.common.core.web.PageResult;
|
||||
import com.gxwebsoft.shop.dto.UserOrderStats;
|
||||
import com.gxwebsoft.shop.dto.VerifyShopOrderDto;
|
||||
import com.gxwebsoft.shop.entity.ShopOrder;
|
||||
import com.gxwebsoft.shop.param.ShopOrderParam;
|
||||
|
||||
@@ -86,4 +87,11 @@ public interface ShopOrderService extends IService<ShopOrder> {
|
||||
* @param type 订单类型(可为空)
|
||||
*/
|
||||
UserOrderStats getUserOrderStats(Integer userId, Integer tenantId, Integer type);
|
||||
|
||||
/**
|
||||
* 核销订单
|
||||
* @param verifyDto 核销码
|
||||
* @return
|
||||
*/
|
||||
Boolean verifyOrder(VerifyShopOrderDto verifyDto);
|
||||
}
|
||||
|
||||
@@ -59,4 +59,19 @@ public interface ShopStoreFenceService extends IService<ShopStoreFence> {
|
||||
*/
|
||||
void validatePointInEnabledFences(Integer tenantId, double lng, double lat);
|
||||
|
||||
/**
|
||||
* 校验坐标是否落在任一启用围栏内。
|
||||
* <p>
|
||||
* 约定:
|
||||
* - 围栏按 tenantId + status=0 过滤;
|
||||
* - 支持多个围栏:命中任意一个即通过;
|
||||
* - 无围栏配置:直接放行;
|
||||
* - 围栏 points 异常:抛出异常(避免误送)。
|
||||
*
|
||||
* @param tenantId 租户ID
|
||||
* @param lng 经度
|
||||
* @param lat 纬度
|
||||
*/
|
||||
Boolean validatePointInEnabled(Integer tenantId, double lng, double lat);
|
||||
|
||||
}
|
||||
|
||||
@@ -106,4 +106,49 @@ public class ShopStoreFenceServiceImpl extends ServiceImpl<ShopStoreFenceMapper,
|
||||
throw new BusinessException("收货地址不在配送范围内");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean validatePointInEnabled(Integer tenantId, double lng, double lat) {
|
||||
if (tenantId == null) {
|
||||
// tenantId 缺失时不做围栏校验,避免误伤;上层应保证 tenantId 正确传入
|
||||
return true;
|
||||
}
|
||||
|
||||
List<ShopStoreFence> fences = this.list(new LambdaQueryWrapper<ShopStoreFence>()
|
||||
.eq(ShopStoreFence::getTenantId, tenantId)
|
||||
.eq(ShopStoreFence::getStatus, 0)
|
||||
.orderByAsc(ShopStoreFence::getSortNumber)
|
||||
.orderByDesc(ShopStoreFence::getCreateTime));
|
||||
|
||||
// 无围栏配置:默认放行
|
||||
if (fences == null || fences.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (ShopStoreFence fence : fences) {
|
||||
if (fence == null) {
|
||||
continue;
|
||||
}
|
||||
List<GeoFenceUtil.Point> polygon;
|
||||
try {
|
||||
polygon = GeoFenceUtil.parsePolygonPoints(fence.getPoints());
|
||||
} catch (Exception e) {
|
||||
// points 异常:直接拒单并记录日志,避免误送
|
||||
log.error("围栏 points 解析失败,tenantId={}, fenceId={}, points={}",
|
||||
tenantId, fence.getId(), fence.getPoints(), e);
|
||||
throw new BusinessException("配送范围配置异常,请联系商家");
|
||||
}
|
||||
|
||||
if (polygon == null || polygon.size() < 3) {
|
||||
log.error("围栏 points 点数不足,tenantId={}, fenceId={}, points={}",
|
||||
tenantId, fence.getId(), fence.getPoints());
|
||||
throw new BusinessException("配送范围配置异常,请联系商家");
|
||||
}
|
||||
|
||||
if (GeoFenceUtil.containsInclusive(polygon, lng, lat)) {
|
||||
return true; // 命中任一围栏即通过
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user