Bläddra i källkod

Merge branch 'mp-server-test' into 'master'

Mp server test

See merge request quanshu/mp-server!952
zhong chunping 2 år sedan
förälder
incheckning
a47c228eef
46 ändrade filer med 1193 tillägg och 52 borttagningar
  1. 136 0
      mp-admin/src/main/java/com/qs/mp/web/controller/api/admin/GoodsMgrController.java
  2. 16 1
      mp-admin/src/main/java/com/qs/mp/web/controller/api/admin/UserDeliverOrderMgrController.java
  3. 51 5
      mp-admin/src/main/java/com/qs/mp/web/controller/api/user/UserDeliverOrderController.java
  4. 4 0
      mp-admin/src/main/java/com/qs/mp/web/controller/api/user/UserPrizeRecoveryController.java
  5. 1 1
      mp-admin/src/main/java/com/qs/mp/web/controller/api/user/UserTicketController.java
  6. 75 1
      mp-admin/src/test/java/com/qs/mp/task/MyTest.java
  7. 2 1
      mp-common/src/main/java/com/qs/mp/common/enums/AsyncTaskTypeEnum.java
  8. 35 0
      mp-common/src/main/java/com/qs/mp/common/enums/DeliverOrderTypeEnum.java
  9. 39 0
      mp-common/src/main/java/com/qs/mp/common/enums/GoodsTypeEnum.java
  10. 48 0
      mp-common/src/main/java/com/qs/mp/common/enums/TicketBoxGoodsTypeEnum.java
  11. 1 0
      mp-framework/src/main/java/com/qs/mp/framework/monitor/SendErrorMsgAppender.java
  12. 6 0
      mp-quartz/src/main/java/com/qs/mp/quartz/task/AsyncConsumeTask.java
  13. 2 2
      mp-quartz/src/main/java/com/qs/mp/quartz/task/TicketOrderTask.java
  14. 8 0
      mp-service/src/main/java/com/qs/mp/admin/domain/Goods.java
  15. 85 0
      mp-service/src/main/java/com/qs/mp/admin/domain/GoodsCard.java
  16. 13 0
      mp-service/src/main/java/com/qs/mp/admin/domain/TicketBoxGoods.java
  17. 24 0
      mp-service/src/main/java/com/qs/mp/admin/domain/excel/GoodsCardImportExcel.java
  18. 20 0
      mp-service/src/main/java/com/qs/mp/admin/domain/param/GoodsCardQueryParam.java
  19. 6 0
      mp-service/src/main/java/com/qs/mp/admin/domain/param/GoodsParam.java
  20. 3 0
      mp-service/src/main/java/com/qs/mp/admin/domain/param/GoodsQueryParam.java
  21. 6 0
      mp-service/src/main/java/com/qs/mp/admin/domain/vo/GoodsVO.java
  22. 8 0
      mp-service/src/main/java/com/qs/mp/admin/domain/vo/TicketBoxGoodsListVO.java
  23. 23 0
      mp-service/src/main/java/com/qs/mp/admin/mapper/GoodsCardMapper.java
  24. 30 0
      mp-service/src/main/java/com/qs/mp/admin/service/IGoodsCardService.java
  25. 38 0
      mp-service/src/main/java/com/qs/mp/admin/service/impl/GoodsCardServiceImpl.java
  26. 72 15
      mp-service/src/main/java/com/qs/mp/admin/service/impl/TicketBoxServiceImpl.java
  27. 2 0
      mp-service/src/main/java/com/qs/mp/framework/redis/RedisLockKey.java
  28. 6 0
      mp-service/src/main/java/com/qs/mp/framework/service/IAsyncTaskService.java
  29. 107 0
      mp-service/src/main/java/com/qs/mp/framework/service/impl/AsyncTaskServiceImpl.java
  30. 4 0
      mp-service/src/main/java/com/qs/mp/user/domain/UserDeliverOrder.java
  31. 71 0
      mp-service/src/main/java/com/qs/mp/user/domain/UserDeliverOrderItemCard.java
  32. 3 0
      mp-service/src/main/java/com/qs/mp/user/domain/vo/DeliverOrderSettleVO.java
  33. 10 0
      mp-service/src/main/java/com/qs/mp/user/domain/vo/UserDeliverOrderItemVO.java
  34. 7 0
      mp-service/src/main/java/com/qs/mp/user/domain/vo/UserPrizeStorageVO.java
  35. 13 0
      mp-service/src/main/java/com/qs/mp/user/mapper/UserDeliverOrderItemCardMapper.java
  36. 16 0
      mp-service/src/main/java/com/qs/mp/user/service/IUserDeliverOrderItemCardService.java
  37. 20 0
      mp-service/src/main/java/com/qs/mp/user/service/impl/UserDeliverOrderItemCardServiceImpl.java
  38. 30 1
      mp-service/src/main/java/com/qs/mp/user/service/impl/UserDeliverOrderServiceImpl.java
  39. 22 10
      mp-service/src/main/java/com/qs/mp/user/service/impl/UserTicketOrderServiceImpl.java
  40. 28 0
      mp-service/src/main/resources/mapper/admin/GoodsCardMapper.xml
  41. 28 5
      mp-service/src/main/resources/mapper/admin/TicketAwardsPrizeMapper.xml
  42. 31 6
      mp-service/src/main/resources/mapper/admin/TicketBoxGoodsMapper.xml
  43. 20 3
      mp-service/src/main/resources/mapper/admin/TicketBoxMapper.xml
  44. 20 0
      mp-service/src/main/resources/mapper/user/UserDeliverOrderItemCardMapper.xml
  45. 1 1
      mp-service/src/main/resources/mapper/user/UserDeliverOrderItemMapper.xml
  46. 2 0
      mp-service/src/main/resources/mapper/user/UserPrizeStorageMapper.xml

+ 136 - 0
mp-admin/src/main/java/com/qs/mp/web/controller/api/admin/GoodsMgrController.java

@@ -7,25 +7,32 @@ import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.github.xiaoymin.knife4j.annotations.DynamicParameter;
 import com.github.xiaoymin.knife4j.annotations.DynamicParameters;
 import com.qs.mp.admin.domain.Goods;
+import com.qs.mp.admin.domain.GoodsCard;
 import com.qs.mp.admin.domain.GoodsSku;
 import com.qs.mp.admin.domain.GoodsTagRel;
+import com.qs.mp.admin.domain.excel.GoodsCardImportExcel;
 import com.qs.mp.admin.domain.excel.GoodsExcel;
 import com.qs.mp.admin.domain.excel.UserTicketOrderItemExcel;
+import com.qs.mp.admin.domain.param.GoodsCardQueryParam;
 import com.qs.mp.admin.domain.param.GoodsParam;
 import com.qs.mp.admin.domain.param.GoodsQueryParam;
 import com.qs.mp.admin.domain.param.GoodsUpdateSortWeightParam;
 import com.qs.mp.admin.domain.vo.GoodsExportVO;
 import com.qs.mp.admin.domain.vo.GoodsVO;
+import com.qs.mp.admin.service.IGoodsCardService;
 import com.qs.mp.admin.service.IGoodsService;
 import com.qs.mp.admin.service.IGoodsSkuService;
 import com.qs.mp.admin.service.IGoodsTagRelService;
 import com.qs.mp.common.annotation.Log;
 import com.qs.mp.common.core.domain.AjaxResult;
 import com.qs.mp.common.core.page.TableDataInfo;
+import com.qs.mp.common.core.redis.DistributedLocker;
 import com.qs.mp.common.enums.BusinessType;
 import com.qs.mp.common.enums.ErrorCodeEnum;
 import com.qs.mp.common.enums.GoodsStatusEnum;
+import com.qs.mp.common.enums.GoodsTypeEnum;
 import com.qs.mp.common.utils.LogUtil;
+import com.qs.mp.framework.redis.RedisLockKey;
 import com.qs.mp.utils.ExcelUtil;
 import com.qs.mp.web.controller.common.BaseApiController;
 import io.swagger.annotations.*;
@@ -37,6 +44,7 @@ import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
 import ma.glasnost.orika.MapperFacade;
@@ -49,6 +57,7 @@ import org.springframework.stereotype.Component;
 import org.springframework.util.CollectionUtils;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
 
 /**
  * @auther liugl
@@ -73,6 +82,111 @@ public class GoodsMgrController extends BaseApiController {
     @Autowired
     private MapperFacade mapperFacade;
 
+    @Autowired
+    private IGoodsCardService goodsCardService;
+
+    @Autowired
+    private DistributedLocker distributedLocker;
+
+
+    @PostMapping("/card/list")
+    @PreAuthorize("@ss.hasPermi('business:goods:query')")
+    @ApiOperation("商品卡密列表")
+    @ApiResponses(
+        @ApiResponse(code = 200, message = "OK", response = GoodsCard.class)
+    )
+    public TableDataInfo goodsCardList(@RequestBody GoodsCardQueryParam param) {
+        startPage();
+        if (param.getGoodsId() == null || param.getGoodsId() == 0) {
+            return getErrorDataTable("商品id不正确");
+        }
+
+        List<GoodsCard> list = goodsCardService.list(new LambdaQueryWrapper<GoodsCard>()
+            .eq(GoodsCard::getGoodsId, param.getGoodsId())
+            .eq(GoodsCard::getIsUse, 0));
+
+        return getDataTable(list);
+    }
+
+
+
+    @PostMapping("/card/template/download")
+    @ApiOperation("下载卡密导入模板")
+    public AjaxResult cardTemplateDownload(){
+        ExcelUtil<GoodsCardImportExcel> excelUtil = new ExcelUtil<>(GoodsCardImportExcel.class);
+        return excelUtil.exportExcel(null, "卡密导入模板", false);
+    }
+
+
+    @Log(title = "导入卡密", businessType = BusinessType.INSERT)
+    @PostMapping("/card/import/{goodsId}")
+    @ApiOperation("导入卡密")
+    @PreAuthorize("@ss.hasPermi('business:goods:edit')")
+    public AjaxResult cardImport(@PathVariable("goodsId") Long goodsId, @RequestParam("file") MultipartFile file){
+        Goods goods = goodsService.getById(goodsId);
+        if (goods == null) {
+            return AjaxResult.error("商品不存在");
+        }
+        if (file == null) {
+            return AjaxResult.error("文件不能为空");
+        }
+        if (!GoodsTypeEnum.CARD_GOODS.getValue().equals(goods.getType())) {
+            return AjaxResult.error("非卡密商品不支持导入");
+        }
+        String lockKey = RedisLockKey.build(RedisLockKey.GOODS_CARD_LOCK_KEY, goodsId);
+        LogUtil.info(logger,"卡密商品导入分布式锁,lockKey:{0}", lockKey);
+        // 加锁,自动续期,防止重复导入
+        if (!distributedLocker.tryLock(lockKey,0,-1, TimeUnit.SECONDS)) {
+            return AjaxResult.error("频繁操作,请稍后再试");
+        }
+        try {
+            // 解析excel中的数据
+            ExcelUtil<GoodsCardImportExcel> excelUtil = new ExcelUtil<>(GoodsCardImportExcel.class);
+            List<GoodsCardImportExcel> cardList = excelUtil.importExcel(file.getInputStream());
+            if (CollectionUtils.isEmpty(cardList)) {
+                return AjaxResult.error("导入数据为空");
+            }
+
+            // 校验excel中的卡密是否重复和空值
+            List<String> cardPwdList = new ArrayList<>();
+            for (GoodsCardImportExcel goodsCardImportExcel : cardList) {
+                if (StringUtils.isBlank(goodsCardImportExcel.getCardPwd())) {
+                    return AjaxResult.error("导入卡密存在空值");
+                }
+                cardPwdList.add(goodsCardImportExcel.getCardPwd());
+            }
+            long count = cardPwdList.stream().distinct().count();
+            if (cardPwdList.size() != count) {
+                return AjaxResult.error("导入卡密存在重复值");
+            }
+
+            // 库中是否存在卡密
+            int goodsCardCount = goodsCardService.count(new LambdaQueryWrapper<GoodsCard>()
+                .eq(GoodsCard::getGoodsId, goodsId)
+                .in(GoodsCard::getCardPwd, cardPwdList));
+            if (goodsCardCount > 0) {
+                return AjaxResult.error("库中已存在导入的部分卡密");
+            }
+
+            // 导入卡密
+            List<GoodsCard> goodsCardList = cardList.stream().map(goodsCardImportExcel -> {
+                GoodsCard goodsCard = new GoodsCard();
+                BeanUtils.copyProperties(goodsCardImportExcel, goodsCard);
+                goodsCard.setGoodsId(goodsId);
+                return goodsCard;
+            }).collect(Collectors.toList());
+
+            goodsCardService.importCard(goodsCardList);
+
+        } catch (Exception e) {
+            LogUtil.error(logger, e, "导入卡密异常,goodsId:{0}", goodsId);
+            return AjaxResult.error("导入卡密异常");
+        } finally {
+            distributedLocker.unlock(lockKey);
+        }
+        return AjaxResult.success();
+    }
+
 
     /**
      * 查询商品列表, 支持翻页
@@ -126,6 +240,9 @@ public class GoodsMgrController extends BaseApiController {
         queryWrapper.lambda()
                 .le(null != queryParam.getMaxExchange(), Goods::getExchangePrice, queryParam.getMaxExchange());
 
+        queryWrapper.lambda()
+                .eq(null != queryParam.getType(), Goods::getType, queryParam.getType());
+
         // 状态
         queryWrapper.lambda()
                 .eq(null != queryParam.getStatus(), Goods::getStatus, queryParam.getStatus());
@@ -142,6 +259,13 @@ public class GoodsMgrController extends BaseApiController {
         queryWrapper.orderByAsc("FIELD(`status`, 'init', 'on', 'off')");
         queryWrapper.lambda().orderByDesc(Goods::getGoodsId);
         List<Goods> goodsList = goodsService.list(queryWrapper);
+        for (Goods goods : goodsList) {
+            if (GoodsTypeEnum.CARD_GOODS.getValue().equals(goods.getType())) {
+                goods.setQuantity(goodsCardService.count(new LambdaQueryWrapper<GoodsCard>()
+                    .eq(GoodsCard::getGoodsId, goods.getGoodsId())
+                    .eq(GoodsCard::getIsUse, 0)));
+            }
+        }
         TableDataInfo res = getDataTable(goodsList);
         res.setRows(goodsList);
         return res;
@@ -252,6 +376,12 @@ public class GoodsMgrController extends BaseApiController {
         if (null == goods.getDiscountRate()) {
             goods.setDiscountRate(goodsParam.getDiscountRate());
         }
+
+        if (goodsParam.getType() != null && GoodsTypeEnum.CARD_GOODS.getValue().equals(goodsParam.getType())) {
+            // 卡密商品不控制库存,设置10万
+            goods.setQuantity(100000);
+        }
+
         // 3.插入数据
         try {
             goods.setSkuProp(getSkuProp(skuList));
@@ -318,6 +448,12 @@ public class GoodsMgrController extends BaseApiController {
                 goods.setQuantity(totalQuantity);
             }
         }
+
+        if (goodsParam.getType() != null && GoodsTypeEnum.CARD_GOODS.getValue().equals(goodsParam.getType())) {
+            // 卡密商品不控制库存,设置10万
+            goods.setQuantity(100000);
+        }
+
         // 3.插入数据
         try {
             goods.setSkuProp(getSkuProp(skuList));

+ 16 - 1
mp-admin/src/main/java/com/qs/mp/web/controller/api/admin/UserDeliverOrderMgrController.java

@@ -13,6 +13,7 @@ import com.qs.mp.common.core.domain.AjaxResult;
 import com.qs.mp.common.core.page.TableDataInfo;
 import com.qs.mp.common.enums.BusinessType;
 import com.qs.mp.common.enums.DeliverOrderStatusEnum;
+import com.qs.mp.common.enums.DeliverOrderTypeEnum;
 import com.qs.mp.common.enums.DeliverTypeEnum;
 import com.qs.mp.common.enums.ErrorCodeEnum;
 import com.qs.mp.common.enums.PayTypeEnum;
@@ -170,6 +171,15 @@ public class UserDeliverOrderMgrController extends BaseApiController {
 			return error(ErrorCodeEnum.ERROR_CODE_1001);
 		}
 
+		UserDeliverOrder userDeliverOrder = userDeliverOrderService.getById(shipParam.getOrderId());
+		if (userDeliverOrder == null) {
+			return AjaxResult.error("订单不存在");
+		}
+
+		if (DeliverOrderTypeEnum.CARD.getValue().equals(userDeliverOrder.getType())) {
+			return AjaxResult.error("卡密订单不支持手动发货");
+		}
+
 		Integer deliveryType = shipParam.getDeliveryType();
 		if (null == deliveryType) {
 			deliveryType = 1;
@@ -231,6 +241,11 @@ public class UserDeliverOrderMgrController extends BaseApiController {
 			return error(ErrorCodeEnum.ERROR_CODE_1001);
 		}
 
+		UserDeliverOrder userDeliverOrder = userDeliverOrderService.getById(shipParam.getOrderId());
+		if (DeliverOrderTypeEnum.CARD.getValue().equals(userDeliverOrder.getType())) {
+			return AjaxResult.error("卡密订单不支持修改");
+		}
+
 		Integer deliveryType = shipParam.getDeliveryType();
 		if (null == deliveryType) {
 			shipParam.setDeliveryType(1);
@@ -250,7 +265,7 @@ public class UserDeliverOrderMgrController extends BaseApiController {
 		boolean res = userDeliverOrderService.updateShipInfo(shipParam);
 
 
-		return res ? AjaxResult.success() : AjaxResult.error("息修改失败");
+		return res ? AjaxResult.success() : AjaxResult.error("息修改失败");
 	}
 
 

+ 51 - 5
mp-admin/src/main/java/com/qs/mp/web/controller/api/user/UserDeliverOrderController.java

@@ -4,9 +4,12 @@ import cn.hutool.core.date.LocalDateTimeUtil;
 import cn.hutool.core.util.NumberUtil;
 import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.qs.mp.admin.domain.Goods;
+import com.qs.mp.admin.domain.GoodsCard;
 import com.qs.mp.admin.domain.GoodsSku;
 import com.qs.mp.admin.domain.vo.ShippingTemplateCalculateVO;
+import com.qs.mp.admin.service.IGoodsCardService;
 import com.qs.mp.admin.service.IGoodsService;
 import com.qs.mp.admin.service.IGoodsSkuService;
 import com.qs.mp.channel.domain.ChannelOrder;
@@ -31,6 +34,7 @@ import com.qs.mp.user.domain.param.DeliverOrderParam;
 import com.qs.mp.user.domain.param.UserDeliverOrderCancelParam;
 import com.qs.mp.user.domain.vo.DeliverOrderSettleVO;
 import com.qs.mp.user.domain.vo.DeliverOrderVO;
+import com.qs.mp.user.domain.vo.UserDeliverOrderItemVO;
 import com.qs.mp.user.service.IUserAddrService;
 import com.qs.mp.user.service.IUserDeliverOrderItemService;
 import com.qs.mp.user.service.IUserDeliverOrderService;
@@ -45,6 +49,7 @@ import java.time.Duration;
 import java.time.LocalDateTime;
 import java.util.*;
 import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
 import javax.validation.Valid;
 
 import io.swagger.annotations.ApiResponse;
@@ -101,9 +106,14 @@ public class UserDeliverOrderController extends BaseApiController {
     @Autowired
     private IGoodsSkuService goodsSkuService;
 
+
+    @Autowired
+    private IGoodsCardService goodsCardService;
+
     @Value("${shipping.user}")
     public Long shippingTemplateId;
 
+
     /**
      * 订单列表
      */
@@ -142,6 +152,9 @@ public class UserDeliverOrderController extends BaseApiController {
      */
     @PostMapping("/order/detail")
     @ApiOperation(value = "订单详情", notes = "在订单列表页面查看详情")
+    @ApiResponses(
+        @ApiResponse(code = 200, message = "success", response = DeliverOrderVO.class)
+    )
     public AjaxResult query(@RequestBody UserDeliverOrder order) {
         Long userId = SecurityUtils.getLoginUser().getUserId();
         UserDeliverOrder deliverOrder = userDeliverOrderService.getOne(new LambdaQueryWrapper<UserDeliverOrder>()
@@ -159,10 +172,23 @@ public class UserDeliverOrderController extends BaseApiController {
             deliverOrderVO.setAutoConfirmTime(day + "天" + hour + "小时");
         }
 
-        deliverOrderVO.setItems(
-            userDeliverOrderItemService.selectUserDeliverOrderItemVOList(deliverOrder.getOrderId()));
-        deliverOrderVO.setDeliverList(
-            userDeliverOrderItemService.getDeliverItemList(order.getOrderId(), deliverOrderVO.getItems()));
+        List<UserDeliverOrderItemVO> userDeliverOrderItemVOS = userDeliverOrderItemService.selectUserDeliverOrderItemVOList(
+            deliverOrder.getOrderId());
+
+        if (DeliverOrderTypeEnum.CARD.getValue().equals(deliverOrder.getType())) {
+            for (UserDeliverOrderItemVO userDeliverOrderItemVO : userDeliverOrderItemVOS) {
+                QueryWrapper<GoodsCard> queryWrapper = new QueryWrapper<>();
+                queryWrapper.eq("t1.item_id", userDeliverOrderItemVO.getItemId());
+                queryWrapper.eq("t1.is_deleted", 0);
+                userDeliverOrderItemVO.setCardList(goodsCardService.listDeliveryOrderItemCardByWrapper(queryWrapper));
+            }
+        }
+        deliverOrderVO.setItems(userDeliverOrderItemVOS);
+
+        if (!DeliverOrderTypeEnum.CARD.getValue().equals(deliverOrder.getType())) {
+            deliverOrderVO.setDeliverList(
+                userDeliverOrderItemService.getDeliverItemList(order.getOrderId(), deliverOrderVO.getItems()));
+        }
         return AjaxResult.success(deliverOrderVO);
     }
 
@@ -235,9 +261,29 @@ public class UserDeliverOrderController extends BaseApiController {
             orderSettleVO.setResource(DeliverOrderResourceEnum.DELIVER.getValue());
         }
 
+        List<Long> goodsIdList = prizeStorageList.stream().map(UserPrizeStorage::getGoodsId).collect(Collectors.toList());
+        List<Goods> goodsList = goodsService.list(new LambdaQueryWrapper<Goods>()
+            .in(Goods::getGoodsId, goodsIdList));
+
+        Integer goodsType = goodsList.get(0).getType();
+        // 设置订单类型
+        if (GoodsTypeEnum.CARD_GOODS.getValue().equals(goodsType)) {
+            orderSettleVO.setOrderType(DeliverOrderTypeEnum.CARD.getValue());
+        } else {
+            orderSettleVO.setOrderType(DeliverOrderTypeEnum.ORDINARY.getValue());
+        }
+
+        for (Goods goods : goodsList) {
+            if (!goodsType.equals(goods.getType())) {
+                return AjaxResult.error("实物商品和卡密商品不能混合提货");
+            }
+        }
+
+
         // 计算运费
         UserAddr userAddr = orderSettleVO.getAddr();
-        if (Objects.nonNull(userAddr)) {
+        // 地址不为空,并且是实物商品,计算运费
+        if (Objects.nonNull(userAddr) && GoodsTypeEnum.REAL_GOODS.getValue().equals(goodsType)) {
             // TODO: 4L上线需要调整,后期放开就是取的商品里配的运费模板id
             ShippingTemplateCalculateVO shippingTemplateCalculateVO = shippingTemplateRuleService.getShippingTemplateRuleByTemplateIdAndAreaInfo(
                 shippingTemplateId, userAddr.getProvinceId(), userAddr.getCityId());

+ 4 - 0
mp-admin/src/main/java/com/qs/mp/web/controller/api/user/UserPrizeRecoveryController.java

@@ -137,6 +137,10 @@ public class UserPrizeRecoveryController extends BaseApiController {
             exchangePrice = goods.getExchangePrice();
         }
 
+        if (BigDecimal.ZERO.compareTo(discountRate) == 0) {
+            return AjaxResult.error("该商品不支持回收");
+        }
+
         int returnNum = BigDecimal.valueOf((long) exchangePrice * param.getNum()).multiply(discountRate).divide(BigDecimal.valueOf(100),0, RoundingMode.HALF_UP).intValue();
 
         settleVO.setReturnCoin(returnNum);

+ 1 - 1
mp-admin/src/main/java/com/qs/mp/web/controller/api/user/UserTicketController.java

@@ -108,7 +108,7 @@ public class UserTicketController extends BaseApiController {
     @PostMapping("/mall/ticket/goods/list")
     @ApiOperation("盲票关联商品列表")
     @ApiResponses(
-        @ApiResponse(code = 200, message = "成功", response = TicketBoxGoodsVO.class)
+        @ApiResponse(code = 200, message = "成功", response = TicketBoxGoodsListVO.class)
     )
     public TableDataInfo ticketGoodsList(@RequestBody TicketBoxGoodsQueryParam param) {
         startPage();

+ 75 - 1
mp-admin/src/test/java/com/qs/mp/task/MyTest.java

@@ -10,7 +10,15 @@ import cn.hutool.crypto.symmetric.DES;
 import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
 import cn.hutool.crypto.symmetric.SymmetricCrypto;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.qs.mp.admin.domain.Coupon;
+import com.qs.mp.admin.domain.CouponTicket;
+import com.qs.mp.admin.domain.GoodsCard;
 import com.qs.mp.admin.domain.Marketing;
+import com.qs.mp.admin.service.ICouponChannelService;
+import com.qs.mp.admin.service.ICouponService;
+import com.qs.mp.admin.service.ICouponTicketService;
+import com.qs.mp.admin.service.IGoodsCardService;
 import com.qs.mp.common.core.redis.DistributedLocker;
 import com.qs.mp.common.core.redis.RedisCache;
 import com.qs.mp.common.enums.UserTypeEnum;
@@ -25,10 +33,12 @@ import com.qs.mp.framework.service.IWxSubscribeMessage;
 import com.qs.mp.pay.service.IWalletService;
 import com.qs.mp.quartz.task.DayStatTask;
 import com.qs.mp.quartz.task.MarketingTask;
+import com.qs.mp.quartz.task.TicketOrderTask;
 import com.qs.mp.system.service.id.BizIdGenerator;
 import com.qs.mp.user.domain.MarketingUserCode;
 import com.qs.mp.user.service.IMarketingUserCodeService;
 import com.qs.mp.utils.SecurityUtils;
+import java.util.concurrent.TimeUnit;
 import org.apache.commons.lang3.RandomStringUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.junit.jupiter.api.Test;
@@ -69,6 +79,24 @@ public class MyTest {
     @Autowired
     private DayStatTask dayStatTask;
 
+    @Autowired
+    private ICouponService couponService;
+
+    @Autowired
+    private ICouponChannelService couponChannelService;
+
+    @Autowired
+    private ICouponTicketService couponTicketService;
+
+
+    @Autowired
+    private IGoodsCardService goodsCardService;
+
+    @Autowired
+    private DistributedLocker distributedLocker;
+
+    @Autowired
+    private TicketOrderTask ticketOrderTask;
 
     public static void main(String[] args) {
         WebhookService.sendAlertDing("mayday,mayday,上上下下,左右左右,BABA");
@@ -78,14 +106,60 @@ public class MyTest {
 
     }
 
+    @Test
+    void test15() {
+        ticketOrderTask.cancel();
+
+    }
+
+    @Test
+    void test14() {
+        distributedLocker.tryLock("goods_card_lock_key_466",0,-1, TimeUnit.SECONDS);
+    }
+
+    @Test
+    void test13() {
+        List<GoodsCard> goodsCardList = goodsCardService.list(new QueryWrapper<GoodsCard>()
+            .eq("is_use", 1));
+        goodsCardList.forEach(System.out::println);
+    }
+
+    @Test
+    void test12() {
+        Coupon coupon = couponService.getById(56);
+        String description = coupon.getDescription();
+        String s = description.replaceAll("\\n", "<br>");
+        System.out.println("coupon.getDescription() = " + s);
+    }
+
+    @Test
+    void test11() {
+        boolean remove = couponTicketService.remove(
+            new LambdaQueryWrapper<CouponTicket>().eq(CouponTicket::getCouponId, 11));
+        System.out.println("remove = " + remove);
+    }
+
+
+    @Test
+    void test10(){
+        for (int i = 0; i < 1; i++) {
+            couponService.distributeByMarketing(188L,"56");
+        }
+
+    }
+
     @Test
     void test9() {
+        Marketing marketing = new Marketing();
+        marketing.setId(44L);
+        marketing.setTitle("测试测试");
+        wxSubscribeMessage.sendMarketingLottery(187L, marketing);
 
     }
 
     @Test
     void test8(){
-        dayStatTask.stat("20220617");
+        dayStatTask.stat("20220713");
     }
 
 

+ 2 - 1
mp-common/src/main/java/com/qs/mp/common/enums/AsyncTaskTypeEnum.java

@@ -13,7 +13,8 @@ import io.swagger.annotations.ApiModel;
 public enum AsyncTaskTypeEnum implements IEnum<Integer> {
     CHANNEL_CONFIRM_RECEIPT(1,"经销商确认收货任务"),
     TICKET_PAY(2,"盲票付款任务"),
-    TICKET_GENERATE(3, "盲票生成任务");
+    TICKET_GENERATE(3, "盲票生成任务"),
+    CARD_ORDER_DELIVER(4, "卡密订单自动发货");
 
     private Integer value;
     private String desc;

+ 35 - 0
mp-common/src/main/java/com/qs/mp/common/enums/DeliverOrderTypeEnum.java

@@ -0,0 +1,35 @@
+package com.qs.mp.common.enums;
+
+import com.baomidou.mybatisplus.annotation.IEnum;
+import io.swagger.annotations.ApiModel;
+
+/**
+ * 提货订单类型枚举
+ * @author Cup
+ * @date 2022/8/3
+ */
+@ApiModel("提货订单类型枚举")
+public enum DeliverOrderTypeEnum implements IEnum<Integer> {
+
+    ORDINARY(1, "普通订单"),
+
+    CARD(2,"卡密订单");
+
+    private Integer value;
+
+    private String description;
+
+    @Override
+    public Integer getValue() {
+        return value;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    DeliverOrderTypeEnum(Integer value, String description) {
+        this.value = value;
+        this.description = description;
+    }
+}

+ 39 - 0
mp-common/src/main/java/com/qs/mp/common/enums/GoodsTypeEnum.java

@@ -0,0 +1,39 @@
+package com.qs.mp.common.enums;
+
+import com.baomidou.mybatisplus.annotation.IEnum;
+import io.swagger.annotations.ApiModel;
+import lombok.Getter;
+
+/**
+ * @author Cup
+ * @date 2022/8/3
+ */
+@ApiModel("商品类型枚举")
+public enum GoodsTypeEnum implements IEnum<Integer> {
+
+
+    REAL_GOODS(1,"实物商品"),
+
+    CARD_GOODS(2,"卡密商品");
+
+    @Getter
+    private Integer value;
+
+    @Getter
+    private String description;
+
+
+    @Override
+    public Integer getValue() {
+        return value;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    GoodsTypeEnum(Integer value, String description) {
+        this.value = value;
+        this.description = description;
+    }
+}

+ 48 - 0
mp-common/src/main/java/com/qs/mp/common/enums/TicketBoxGoodsTypeEnum.java

@@ -0,0 +1,48 @@
+package com.qs.mp.common.enums;
+
+import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson.annotation.JSONType;
+import com.baomidou.mybatisplus.annotation.IEnum;
+import com.qs.mp.common.json.EnumValueDeserializer;
+import io.swagger.annotations.ApiModel;
+
+/**
+ * 盲票关联商品枚举
+ * @author Cup
+ * @date 2022/8/4
+ */
+@JSONType(deserializer = EnumValueDeserializer.class)
+public enum TicketBoxGoodsTypeEnum implements IEnum<String> {
+
+
+    GOODS("goods", "商品"),
+    COUPON("coupon", "优惠券"),
+    COUPON_PKG("coupon_pkg", "优惠券包"),
+    COIN("coin", "盲豆");
+
+    private final String value;
+    private final String desc;
+
+    TicketBoxGoodsTypeEnum(final String value, final String desc) {
+        this.value = value;
+        this.desc = desc;
+    }
+
+    @Override
+    public String getValue() {
+        return value;
+    }
+
+    /**
+     * 重写toString,单个转化成json
+     * @return
+     */
+    @Override
+    public String toString() {
+        JSONObject object = new JSONObject();
+        object.put("value",value);
+        object.put("desc", desc);
+        return object.toString();
+    }
+
+}

+ 1 - 0
mp-framework/src/main/java/com/qs/mp/framework/monitor/SendErrorMsgAppender.java

@@ -32,6 +32,7 @@ public class SendErrorMsgAppender extends UnsynchronizedAppenderBase<ILoggingEve
         || throwableMsg.startsWith("用户不存在/密码错误") || throwableMsg.startsWith("验证码已失效") || throwableMsg.startsWith("您已经助力过了")
         || throwableMsg.startsWith("您已经获取过抽奖码了") || throwableMsg.startsWith("奖品总数值不相同") || throwableMsg.startsWith("包含已兑奖记录的奖品不能从奖级中删除")
         || throwableMsg.startsWith("登录账号不存在") || throwableMsg.startsWith("已选择更高级的渠道,不支持渠道相互嵌套!") || throwableMsg.startsWith("内定人数不能超过100")
+        || throwableMsg.startsWith("卡密不足,无法发货")
         )) {
           return;
         }

+ 6 - 0
mp-quartz/src/main/java/com/qs/mp/quartz/task/AsyncConsumeTask.java

@@ -78,6 +78,12 @@ public class AsyncConsumeTask {
         } else if (AsyncTaskTypeEnum.TICKET_GENERATE.getValue().equals(asyncTask.getType())) {
             // 盲票创建后相关任务处理
             asyncTaskService.ticketGenerate(asyncTask);
+        } else if (AsyncTaskTypeEnum.CARD_ORDER_DELIVER.getValue().equals(asyncTask.getType())) {
+            // 卡密订单发货任务
+            asyncTaskService.cardOrderDeliver(asyncTask);
+        } else {
+            // 都没匹配到则忽略不处理任务
+            return;
         }
 
         // 更新异步任务状态

+ 2 - 2
mp-quartz/src/main/java/com/qs/mp/quartz/task/TicketOrderTask.java

@@ -43,7 +43,7 @@ public class TicketOrderTask {
               .lambda()
               .eq(UserTicketOrder::getStatus, UserTicketOrderStatusEnum.NOT_PAY)
               .lt(UserTicketOrder::getCreatedTime,
-                  DateUtils.addMinutes(DateUtils.getNowDate(), -30))
+                  DateUtils.addMinutes(DateUtils.getNowDate(), -1))
               .last("limit " + PAGE_SIZE));
       total = ticketOrderList.size();
       // 按票组分批次取消
@@ -56,7 +56,7 @@ public class TicketOrderTask {
                   .lambda().eq(UserTicketOrder::getBoxId, ticketOrder.getBoxId())
                   .eq(UserTicketOrder::getStatus, UserTicketOrderStatusEnum.NOT_PAY)
                   .lt(UserTicketOrder::getCreatedTime,
-                      DateUtils.addMinutes(DateUtils.getNowDate(), -30))
+                      DateUtils.addMinutes(DateUtils.getNowDate(), -1))
                   .last("limit " + 10));
           boxTotal = orderList.size();
           try {

+ 8 - 0
mp-service/src/main/java/com/qs/mp/admin/domain/Goods.java

@@ -69,6 +69,10 @@ public class Goods implements Serializable {
   @TableField("pic_url")
   private String picUrl;
 
+  @ApiModelProperty("商品类型:1实物,2卡密")
+  @TableField("type")
+  private Integer type;
+
   /**
    * 详情
    */
@@ -134,6 +138,10 @@ public class Goods implements Serializable {
   @TableField("merchant_info")
   private String merchantInfo;
 
+  @ApiModelProperty("使用链接")
+  @TableField("use_link")
+  private String useLink;
+
   @ApiModelProperty("供应商ID")
   @TableField("supplier_id")
   private Long supplierId;

+ 85 - 0
mp-service/src/main/java/com/qs/mp/admin/domain/GoodsCard.java

@@ -0,0 +1,85 @@
+package com.qs.mp.admin.domain;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.annotation.IdType;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import java.util.Date;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableField;
+import java.io.Serializable;
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+
+/**
+ * @describe 商品卡密关联表实体类
+ * @auther quanshu
+ * @create 2022-08-02 15:43:31
+ */
+@TableName("mp_goods_card")
+@Data
+@ApiModel("商品卡密关联表实体类")
+public class GoodsCard implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键
+     */
+    @ApiModelProperty("主键")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 商品id
+     */
+    @ApiModelProperty("商品id")
+    @TableField("goods_id")
+    private Long goodsId;
+
+    /**
+     * 卡号
+     */
+    @ApiModelProperty("卡号")
+    @TableField("card_no")
+    private String cardNo;
+
+    /**
+     * 卡密
+     */
+    @ApiModelProperty("卡密")
+    @TableField("card_pwd")
+    private String cardPwd;
+
+    /**
+     * 是否使用:0未使用,1已使用
+     */
+    @ApiModelProperty("是否使用:0未使用,1已使用")
+    @TableField("is_use")
+    private Integer isUse;
+
+    /**
+     * 逻辑删除标识
+     */
+    @ApiModelProperty("逻辑删除标识")
+    @TableField("is_deleted")
+    @TableLogic
+    private Integer isDeleted;
+
+    /**
+     * 创建时间
+     */
+    @ApiModelProperty("创建时间")
+    @TableField("created_time")
+    private Date createdTime;
+
+    /**
+     * 更新时间
+     */
+    @ApiModelProperty("更新时间")
+    @TableField("updated_time")
+    private Date updatedTime;
+
+
+}

+ 13 - 0
mp-service/src/main/java/com/qs/mp/admin/domain/TicketBoxGoods.java

@@ -1,7 +1,11 @@
 package com.qs.mp.admin.domain;
 
+import com.alibaba.fastjson.annotation.JSONField;
+import com.alibaba.fastjson.serializer.SerializerFeature;
 import com.baomidou.mybatisplus.annotation.TableName;
 import com.baomidou.mybatisplus.annotation.IdType;
+import com.qs.mp.common.enums.TicketBoxGoodsTypeEnum;
+import com.qs.mp.common.enums.TicketPrizeTypeEnum;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import java.util.Date;
@@ -52,6 +56,15 @@ public class TicketBoxGoods implements Serializable {
     @TableField("pic_url")
     private String picUrl;
 
+
+    /**
+     * 商品类型:goods实物商品 coupon优惠券 coin平台代币 coupon_pkg券包
+     */
+    @ApiModelProperty("商品类型:goods实物商品 coupon优惠券 coin平台代币 coupon_pkg券包")
+    @TableField("type")
+    @JSONField(serialzeFeatures = SerializerFeature.WriteEnumUsingToString)
+    private TicketBoxGoodsTypeEnum type;
+
     /**
      * 关联商品id
      */

+ 24 - 0
mp-service/src/main/java/com/qs/mp/admin/domain/excel/GoodsCardImportExcel.java

@@ -0,0 +1,24 @@
+package com.qs.mp.admin.domain.excel;
+
+import com.qs.mp.common.annotation.Excel;
+import io.swagger.annotations.ApiModel;
+import lombok.Data;
+
+/**
+ * 商品卡密导入类
+ * @author Cup
+ * @date 2022/8/2
+ */
+@ApiModel("商品卡密导入类")
+@Data
+public class GoodsCardImportExcel {
+
+
+    @Excel(name = "卡号")
+    private String cardNo;
+
+
+    @Excel(name = "密码(激活码)")
+    private String cardPwd;
+
+}

+ 20 - 0
mp-service/src/main/java/com/qs/mp/admin/domain/param/GoodsCardQueryParam.java

@@ -0,0 +1,20 @@
+package com.qs.mp.admin.domain.param;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * 商品关联卡密查询入参类
+ * @author Cup
+ * @date 2022/8/3
+ */
+@ApiModel("商品关联卡密查询入参类")
+@Data
+public class GoodsCardQueryParam {
+
+    @ApiModelProperty("商品id")
+    private Long goodsId;
+
+
+}

+ 6 - 0
mp-service/src/main/java/com/qs/mp/admin/domain/param/GoodsParam.java

@@ -131,4 +131,10 @@ public class GoodsParam {
 	private String shoppingLink;
 
 
+	@ApiModelProperty("商品类型:1实物,2卡密")
+	private Integer type;
+
+	@ApiModelProperty("使用链接")
+	private String useLink;
+
 }

+ 3 - 0
mp-service/src/main/java/com/qs/mp/admin/domain/param/GoodsQueryParam.java

@@ -43,6 +43,9 @@ public class GoodsQueryParam {
     private Long supplierId;
 
 
+    @ApiModelProperty("商品类型:1实物商品,2卡密商品")
+    private Integer type;
+
     /**
      * 最低成本
      */

+ 6 - 0
mp-service/src/main/java/com/qs/mp/admin/domain/vo/GoodsVO.java

@@ -46,6 +46,9 @@ public class GoodsVO {
 	@ApiModelProperty("商品编码")
 	private String goodsCode;
 
+	@ApiModelProperty("商品类型:1实物商品,2卡密商品")
+	private Integer type;
+
 	/**
 	 * 商品图片
 	 */
@@ -139,6 +142,9 @@ public class GoodsVO {
 	@ApiModelProperty("商家信息")
 	private String merchantInfo;
 
+	@ApiModelProperty("使用链接")
+	private String useLink;
+
 	@ApiModelProperty("供应商ID")
 	private Long supplierId;
 

+ 8 - 0
mp-service/src/main/java/com/qs/mp/admin/domain/vo/TicketBoxGoodsListVO.java

@@ -1,5 +1,9 @@
 package com.qs.mp.admin.domain.vo;
 
+import com.alibaba.fastjson.annotation.JSONField;
+import com.alibaba.fastjson.serializer.SerializerFeature;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.qs.mp.common.enums.TicketBoxGoodsTypeEnum;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import java.math.BigDecimal;
@@ -18,6 +22,10 @@ public class TicketBoxGoodsListVO {
     @ApiModelProperty("关联商品id")
     private String refId;
 
+    @ApiModelProperty("关联商品类型:goods实物商品 coupon优惠券 coin平台代币 coupon_pkg券包")
+    @JSONField(serialzeFeatures = SerializerFeature.WriteEnumUsingToString)
+    private TicketBoxGoodsTypeEnum refType;
+
     @ApiModelProperty("盲票组id")
     private String boxId;
     /**

+ 23 - 0
mp-service/src/main/java/com/qs/mp/admin/mapper/GoodsCardMapper.java

@@ -0,0 +1,23 @@
+package com.qs.mp.admin.mapper;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Constants;
+import com.qs.mp.admin.domain.GoodsCard;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import java.util.List;
+import org.apache.ibatis.annotations.Param;
+
+/**
+ * @auther quanshu
+ * @create 2022-08-02 15:43:31
+ * @describe 商品卡密关联表mapper类
+ */
+public interface GoodsCardMapper extends BaseMapper<GoodsCard> {
+
+    /**
+     * 查询提货订单明细的商品卡密信息
+     * @param queryWrapper
+     * @return
+     */
+    List<GoodsCard> listDeliveryOrderItemCardByWrapper(@Param(Constants.WRAPPER) QueryWrapper<GoodsCard> queryWrapper);
+}

+ 30 - 0
mp-service/src/main/java/com/qs/mp/admin/service/IGoodsCardService.java

@@ -0,0 +1,30 @@
+package com.qs.mp.admin.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.qs.mp.admin.domain.GoodsCard;
+import com.baomidou.mybatisplus.extension.service.IService;
+import java.util.List;
+
+/**
+ * <p>
+ * 商品卡密关联表 服务类
+ * </p>
+ *
+ * @author quanshu
+ * @since 2022-08-02
+ */
+public interface IGoodsCardService extends IService<GoodsCard> {
+
+    /**
+     * 查询提货订单明细的商品卡密信息
+     * @param queryWrapper
+     * @return
+     */
+    List<GoodsCard> listDeliveryOrderItemCardByWrapper(QueryWrapper<GoodsCard> queryWrapper);
+
+    /**
+     * 导入卡密
+     * @param goodsCardList
+     */
+    void importCard(List<GoodsCard> goodsCardList);
+}

+ 38 - 0
mp-service/src/main/java/com/qs/mp/admin/service/impl/GoodsCardServiceImpl.java

@@ -0,0 +1,38 @@
+package com.qs.mp.admin.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.qs.mp.admin.domain.GoodsCard;
+import com.qs.mp.admin.mapper.GoodsCardMapper;
+import com.qs.mp.admin.service.IGoodsCardService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.qs.mp.common.core.domain.AjaxResult;
+import com.qs.mp.common.exception.ServiceException;
+import java.util.List;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * <p>
+ * 商品卡密关联表 服务实现类
+ * </p>
+ *
+ * @author quanshu
+ * @since 2022-08-02
+ */
+@Service
+public class GoodsCardServiceImpl extends ServiceImpl<GoodsCardMapper, GoodsCard> implements IGoodsCardService {
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void importCard(List<GoodsCard> goodsCardList) {
+        boolean rtn = this.saveBatch(goodsCardList);
+        if (!rtn) {
+            throw new ServiceException("导入失败,请重试");
+        }
+    }
+
+    @Override
+    public List<GoodsCard> listDeliveryOrderItemCardByWrapper(QueryWrapper<GoodsCard> queryWrapper) {
+        return getBaseMapper().listDeliveryOrderItemCardByWrapper(queryWrapper);
+    }
+}

+ 72 - 15
mp-service/src/main/java/com/qs/mp/admin/service/impl/TicketBoxServiceImpl.java

@@ -37,6 +37,7 @@ import ma.glasnost.orika.MapperFacade;
 import org.apache.pulsar.client.api.PulsarClientException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -140,18 +141,46 @@ public class TicketBoxServiceImpl extends ServiceImpl<TicketBoxMapper, TicketBox
 
         // 关联商品
         if (CollectionUtils.isNotEmpty(param.getGoodsList())) {
+            List<TicketBoxGoods> ticketBoxGoodsList = new ArrayList<>();
             // 设置boxId和商品信息
             for (TicketBoxGoods ticketBoxGoods : param.getGoodsList()) {
-                ticketBoxGoods.setId(null);
-                ticketBoxGoods.setBoxId(ticketBox.getBoxId());
-                Goods goods = goodsService.getById(ticketBoxGoods.getRefId());
-                if (goods != null) {
-                    ticketBoxGoods.setTitle(goods.getTitle());
-                    ticketBoxGoods.setPicUrl(goods.getPicUrl());
-                    ticketBoxGoods.setValue(goods.getValue());
+                TicketBoxGoods boxGoods = new TicketBoxGoods();
+                BeanUtils.copyProperties(ticketBoxGoods, boxGoods);
+                boxGoods.setId(null);
+                boxGoods.setBoxId(ticketBox.getBoxId());
+                if (TicketBoxGoodsTypeEnum.COUPON.equals(ticketBoxGoods.getType())) {
+                    Coupon coupon = couponService.getById(boxGoods.getRefId());
+                    if (coupon == null) {
+                        LogUtil.error(logger,"关联优惠券信息不存在,boxId:{0}", ticketBox.getBoxId());
+                        throw new ServiceException("关联优惠券信息不存在");
+                    }
+                    boxGoods.setTitle(coupon.getTitle());
+                    boxGoods.setPicUrl(coupon.getPicUrl());
+                    boxGoods.setValue(coupon.getDiscount());
+                } else if (TicketBoxGoodsTypeEnum.COUPON_PKG.equals(ticketBoxGoods.getType())) {
+                    CouponPkg couponPkg = couponPkgService.getById(boxGoods.getRefId());
+                    if (couponPkg == null) {
+                        LogUtil.error(logger,"关联券包信息不存在,boxId:{0}", ticketBox.getBoxId());
+                        throw new ServiceException("关联券包信息不存在");
+                    }
+                    boxGoods.setTitle(couponPkg.getTitle());
+                    boxGoods.setPicUrl(couponPkg.getPicUrl());
+                    boxGoods.setValue(couponPkg.getFacePrice());
+                } else {
+                    Goods goods = goodsService.getById(boxGoods.getRefId());
+                    if (goods == null) {
+                        LogUtil.error(logger,"关联商品信息不存在,boxId:{0}", ticketBox.getBoxId());
+                        throw new ServiceException("关联商品信息不存在");
+                    }
+                    boxGoods.setTitle(goods.getTitle());
+                    boxGoods.setPicUrl(goods.getPicUrl());
+                    boxGoods.setValue(goods.getValue());
                 }
+                ticketBoxGoodsList.add(boxGoods);
+            }
+            if (CollectionUtils.isNotEmpty(ticketBoxGoodsList)) {
+                ticketBoxGoodsService.saveBatch(ticketBoxGoodsList);
             }
-            ticketBoxGoodsService.saveBatch(param.getGoodsList());
         }
 
         // 清除指定渠道内容
@@ -364,17 +393,45 @@ public class TicketBoxServiceImpl extends ServiceImpl<TicketBoxMapper, TicketBox
 
         // 关联商品
         if (CollectionUtils.isNotEmpty(param.getGoodsList())) {
+            List<TicketBoxGoods> ticketBoxGoodsList = new ArrayList<>();
             // 设置boxId和商品信息
             for (TicketBoxGoods ticketBoxGoods : param.getGoodsList()) {
-                ticketBoxGoods.setBoxId(ticketBox.getBoxId());
-                Goods goods = goodsService.getById(ticketBoxGoods.getRefId());
-                if (goods != null) {
-                    ticketBoxGoods.setTitle(goods.getTitle());
-                    ticketBoxGoods.setPicUrl(goods.getPicUrl());
-                    ticketBoxGoods.setValue(goods.getValue());
+                TicketBoxGoods boxGoods = new TicketBoxGoods();
+                BeanUtils.copyProperties(ticketBoxGoods, boxGoods);
+                boxGoods.setBoxId(ticketBox.getBoxId());
+                if (TicketBoxGoodsTypeEnum.COUPON.equals(ticketBoxGoods.getType())) {
+                    Coupon coupon = couponService.getById(boxGoods.getRefId());
+                    if (coupon == null) {
+                        LogUtil.error(logger,"关联优惠券信息不存在,boxId:{0}", ticketBox.getBoxId());
+                        throw new ServiceException("关联优惠券信息不存在");
+                    }
+                    boxGoods.setTitle(coupon.getTitle());
+                    boxGoods.setPicUrl(coupon.getPicUrl());
+                    boxGoods.setValue(coupon.getDiscount());
+                } else if (TicketBoxGoodsTypeEnum.COUPON_PKG.equals(ticketBoxGoods.getType())) {
+                    CouponPkg couponPkg = couponPkgService.getById(boxGoods.getRefId());
+                    if (couponPkg == null) {
+                        LogUtil.error(logger,"关联券包信息不存在,boxId:{0}", ticketBox.getBoxId());
+                        throw new ServiceException("关联券包信息不存在");
+                    }
+                    boxGoods.setTitle(couponPkg.getTitle());
+                    boxGoods.setPicUrl(couponPkg.getPicUrl());
+                    boxGoods.setValue(couponPkg.getFacePrice());
+                } else {
+                    Goods goods = goodsService.getById(boxGoods.getRefId());
+                    if (goods == null) {
+                        LogUtil.error(logger,"关联商品信息不存在,boxId:{0}", ticketBox.getBoxId());
+                        throw new ServiceException("关联商品信息不存在");
+                    }
+                    boxGoods.setTitle(goods.getTitle());
+                    boxGoods.setPicUrl(goods.getPicUrl());
+                    boxGoods.setValue(goods.getValue());
                 }
+                ticketBoxGoodsList.add(boxGoods);
+            }
+            if (CollectionUtils.isNotEmpty(ticketBoxGoodsList)) {
+                ticketBoxGoodsService.saveBatch(ticketBoxGoodsList);
             }
-            ticketBoxGoodsService.saveBatch(param.getGoodsList());
         }
 
         // 指定渠道

+ 2 - 0
mp-service/src/main/java/com/qs/mp/framework/redis/RedisLockKey.java

@@ -9,6 +9,8 @@ import com.qs.mp.common.utils.MessageHelper;
  *
  */
 public enum RedisLockKey {
+
+    GOODS_CARD_LOCK_KEY("goods_card_lock_key_{0}","商品卡密导入锁"),
     CREATED_ONLINE_TICKET_ORDER_KEY("created_online_ticket_order_key_{0}", "线上盲票创建锁"),
     ASYNC_TASK_KEY("async_task_key_{0}", "异步任务key"),
 

+ 6 - 0
mp-service/src/main/java/com/qs/mp/framework/service/IAsyncTaskService.java

@@ -39,4 +39,10 @@ public interface IAsyncTaskService extends IService<AsyncTask> {
      * @param asyncTask
      */
     void ticketGenerate(AsyncTask asyncTask);
+
+    /**
+     * 卡密订单发货任务
+     * @param asyncTask
+     */
+    void cardOrderDeliver(AsyncTask asyncTask);
 }

+ 107 - 0
mp-service/src/main/java/com/qs/mp/framework/service/impl/AsyncTaskServiceImpl.java

@@ -1,10 +1,14 @@
 package com.qs.mp.framework.service.impl;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
+import com.qs.mp.admin.domain.GoodsCard;
 import com.qs.mp.admin.domain.Ticket;
 import com.qs.mp.admin.domain.TicketBox;
 import com.qs.mp.admin.domain.TicketPackage;
+import com.qs.mp.admin.service.IGoodsCardService;
 import com.qs.mp.admin.service.ITicketBoxService;
 import com.qs.mp.admin.service.ITicketPackageService;
 import com.qs.mp.admin.service.ITicketService;
@@ -13,15 +17,25 @@ import com.qs.mp.channel.service.IChannelOrderService;
 import com.qs.mp.channel.service.IChannelUserRelService;
 import com.qs.mp.common.enums.*;
 import com.qs.mp.common.exception.ServiceException;
+import com.qs.mp.common.utils.DateUtils;
 import com.qs.mp.common.utils.LogUtil;
 import com.qs.mp.framework.domain.AsyncTask;
 import com.qs.mp.framework.mapper.AsyncTaskMapper;
 import com.qs.mp.framework.service.IAsyncTaskService;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.qs.mp.user.domain.UserDeliverOrder;
+import com.qs.mp.user.domain.UserDeliverOrderItem;
+import com.qs.mp.user.domain.UserDeliverOrderItemCard;
 import com.qs.mp.user.domain.UserTicketOrder;
 import com.qs.mp.user.domain.UserTicketOrderItem;
+import com.qs.mp.user.service.IUserDeliverOrderItemCardService;
+import com.qs.mp.user.service.IUserDeliverOrderItemService;
+import com.qs.mp.user.service.IUserDeliverOrderService;
 import com.qs.mp.user.service.IUserTicketOrderItemService;
 import com.qs.mp.user.service.IUserTicketOrderService;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.stream.Collectors;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -68,6 +82,19 @@ public class AsyncTaskServiceImpl extends ServiceImpl<AsyncTaskMapper, AsyncTask
     @Autowired
     private IChannelUserRelService channelUserRelService;
 
+
+    @Autowired
+    private IUserDeliverOrderService userDeliverOrderService;
+
+    @Autowired
+    private IUserDeliverOrderItemService userDeliverOrderItemService;
+
+    @Autowired
+    private IGoodsCardService goodsCardService;
+
+    @Autowired
+    private IUserDeliverOrderItemCardService userDeliverOrderItemCardService;
+
     @Override
     @Transactional(rollbackFor = Exception.class)
     public boolean insertAsyncTask(AsyncTaskTypeEnum taskType, String bizId) {
@@ -150,4 +177,84 @@ public class AsyncTaskServiceImpl extends ServiceImpl<AsyncTaskMapper, AsyncTask
         }
         ticketBoxService.generateTicket(boxId);
     }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void cardOrderDeliver(AsyncTask asyncTask) {
+        String orderId = asyncTask.getBizId();
+        LogUtil.error(logger, "收到卡密订单自动发货任务。orderId=" + orderId);
+        UserDeliverOrder userDeliverOrder = userDeliverOrderService.getById(orderId);
+        // 如果是普通订单或者订单不为待发货则不处理
+        if (DeliverOrderTypeEnum.ORDINARY.getValue().equals(userDeliverOrder.getType())
+            || !DeliverOrderStatusEnum.NOT_DELIVER.equals(userDeliverOrder.getStatus())) {
+            LogUtil.error(logger, "收到卡密订单自动发货任务,订单类型不为卡密订单或者订单状态不为待发货,忽略任务。orderId=" + orderId);
+            return;
+        }
+
+        // 获取订单详情
+        List<UserDeliverOrderItem> userDeliverOrderItemList = userDeliverOrderItemService.list(
+            new LambdaQueryWrapper<UserDeliverOrderItem>().eq(UserDeliverOrderItem::getOrderId, orderId));
+        if (CollectionUtils.isEmpty(userDeliverOrderItemList)) {
+            LogUtil.error(logger, "订单明细为空。orderId=" + orderId);
+            throw new ServiceException("订单明细为空");
+        }
+
+        // 分配卡密
+        List<UserDeliverOrderItemCard> userDeliverOrderItemCardList = new ArrayList<>();
+        for (UserDeliverOrderItem userDeliverOrderItem : userDeliverOrderItemList) {
+            List<GoodsCard> goodsCardList = goodsCardService.list(new QueryWrapper<GoodsCard>()
+                .eq("goods_id", userDeliverOrderItem.getGoodsId())
+                .eq("is_use", 0)
+                .orderByAsc("rand()")
+                .last("limit " + userDeliverOrderItem.getGoodsNum()));
+            if (CollectionUtils.isEmpty(goodsCardList) || goodsCardList.size() < userDeliverOrderItem.getGoodsNum()) {
+                LogUtil.error(logger,"卡密不足,无法发货。orderId:{0},itemId:{1},goodsId:{2}",userDeliverOrderItem.getOrderId(),
+                    userDeliverOrderItem.getItemId(), userDeliverOrderItem.getGoodsId());
+                throw new ServiceException("卡密不足,无法发货");
+            }
+
+            for (GoodsCard goodsCard : goodsCardList) {
+                boolean rtn = goodsCardService.update(new LambdaUpdateWrapper<GoodsCard>()
+                    .set(GoodsCard::getIsUse, 1)
+                    .eq(GoodsCard::getId, goodsCard.getId())
+                    .eq(GoodsCard::getIsUse, 0));
+                if (!rtn) {
+                    LogUtil.error(logger, "卡密分配失败。orderId={0},itemId={1},goodsId={2}",userDeliverOrderItem.getOrderId(),
+                        userDeliverOrderItem.getItemId(), userDeliverOrderItem.getGoodsId());
+                    throw new ServiceException("卡密分配并发失败,稍后重试,goodsId=" + userDeliverOrderItem.getGoodsId());
+                }
+                UserDeliverOrderItemCard userDeliverOrderItemCard = new UserDeliverOrderItemCard();
+                userDeliverOrderItemCard.setItemId(userDeliverOrderItem.getItemId());
+                userDeliverOrderItemCard.setCardId(goodsCard.getId());
+                userDeliverOrderItemCardList.add(userDeliverOrderItemCard);
+            }
+        }
+
+        boolean rtn = userDeliverOrderItemCardService.saveBatch(userDeliverOrderItemCardList);
+        if (!rtn) {
+            throw new ServiceException("分配卡密保存失败");
+        }
+
+        // 更新订单信息
+        boolean res = userDeliverOrderService.update(new LambdaUpdateWrapper<UserDeliverOrder>()
+            .set(UserDeliverOrder::getStatus, DeliverOrderStatusEnum.FINISHED)
+            .set(UserDeliverOrder::getDeliveryTime, new Date())
+            .set(UserDeliverOrder::getConfirmTime, new Date())
+            .eq(UserDeliverOrder::getOrderId, orderId)
+            .eq(UserDeliverOrder::getStatus, DeliverOrderStatusEnum.NOT_DELIVER)
+        );
+        if (!res) {
+            LogUtil.error(logger, "卡密商品发货更新订单信息失败,orderId: {0}", orderId);
+            throw new ServiceException("卡密商品发货更新订单信息失败");
+        }
+
+        // 更新订单明细的发货信息
+        boolean res1 = userDeliverOrderItemService.update(new LambdaUpdateWrapper<UserDeliverOrderItem>()
+            .set(UserDeliverOrderItem::getDeliveryTime, new Date())
+            .eq(UserDeliverOrderItem::getOrderId, orderId));
+        if(!res1) {
+            LogUtil.error(logger,"卡密商品发货更新订单明细发货时间失败,orderId: {0}", orderId);
+            throw new ServiceException("卡密商品发货更新订单明细发货时间失败");
+        }
+    }
 }

+ 4 - 0
mp-service/src/main/java/com/qs/mp/user/domain/UserDeliverOrder.java

@@ -112,6 +112,10 @@ public class UserDeliverOrder implements Serializable {
   @TableField("memo")
   private String memo;
 
+  @ApiModelProperty("订单类型:1普通订单,2卡密订单")
+  @TableField("type")
+  private Integer type;
+
 
   @ApiModelProperty("订单来源:1提货订单,2现金购买")
   @TableField("resource")

+ 71 - 0
mp-service/src/main/java/com/qs/mp/user/domain/UserDeliverOrderItemCard.java

@@ -0,0 +1,71 @@
+package com.qs.mp.user.domain;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.annotation.IdType;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import java.util.Date;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableField;
+import java.io.Serializable;
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+
+/**
+ * @describe 提货订单卡密关联表实体类
+ * @auther quanshu
+ * @create 2022-08-02 15:44:39
+ */
+@TableName("mp_user_deliver_order_item_card")
+@Data
+@ApiModel("提货订单卡密关联表实体类")
+public class UserDeliverOrderItemCard implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键
+     */
+    @ApiModelProperty("主键")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 提货订单明细id
+     */
+    @ApiModelProperty("提货订单明细id")
+    @TableField("item_id")
+    private String itemId;
+
+    /**
+     * 关联卡密id
+     */
+    @ApiModelProperty("关联卡密id")
+    @TableField("card_id")
+    private Long cardId;
+
+    /**
+     * 逻辑删除标识
+     */
+    @ApiModelProperty("逻辑删除标识")
+    @TableField("is_deleted")
+    @TableLogic
+    private Integer isDeleted;
+
+    /**
+     * 创建时间
+     */
+    @ApiModelProperty("创建时间")
+    @TableField("created_time")
+    private Date createdTime;
+
+    /**
+     * 更新时间
+     */
+    @ApiModelProperty("更新时间")
+    @TableField("updated_time")
+    private Date updatedTime;
+
+
+}

+ 3 - 0
mp-service/src/main/java/com/qs/mp/user/domain/vo/DeliverOrderSettleVO.java

@@ -58,4 +58,7 @@ public class DeliverOrderSettleVO {
 
     @ApiModelProperty("小程序Id")
     private String appId;
+
+    @ApiModelProperty("订单类型:1实物订单,2卡密订单")
+    private Integer orderType;
 }

+ 10 - 0
mp-service/src/main/java/com/qs/mp/user/domain/vo/UserDeliverOrderItemVO.java

@@ -1,11 +1,14 @@
 package com.qs.mp.user.domain.vo;
 
 import com.alibaba.fastjson.annotation.JSONField;
+import com.qs.mp.admin.domain.GoodsCard;
 import com.qs.mp.common.annotation.Excel;
 import com.qs.mp.user.domain.UserDeliverOrderItem;
 
+import com.qs.mp.user.domain.UserDeliverOrderItemCard;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
+import java.util.List;
 import lombok.Data;
 
 /**
@@ -57,6 +60,13 @@ public class UserDeliverOrderItemVO extends UserDeliverOrderItem {
     @ApiModelProperty("物流查询接口")
     private String queryUrl;
 
+
+    @ApiModelProperty("卡密使用链接")
+    private String useLink;
+
+    @ApiModelProperty("提货商品卡密信息")
+    private List<GoodsCard> cardList;
+
     public Integer getPrice() {
         if (this.skuValue == null) {
             return this.goodsValue;

+ 7 - 0
mp-service/src/main/java/com/qs/mp/user/domain/vo/UserPrizeStorageVO.java

@@ -20,6 +20,13 @@ import java.math.BigDecimal;
 @Data
 public class UserPrizeStorageVO extends UserPrizeStorage {
 
+    @ApiModelProperty("商品类型:1实物,2卡密")
+    private Integer goodsType;
+
+    @ApiModelProperty("回收折扣")
+    private BigDecimal discountRate;
+
+
     @ApiModelProperty("是否多sku: 0否,1是")
     private Integer isMoreSku;
 

+ 13 - 0
mp-service/src/main/java/com/qs/mp/user/mapper/UserDeliverOrderItemCardMapper.java

@@ -0,0 +1,13 @@
+package com.qs.mp.user.mapper;
+
+import com.qs.mp.user.domain.UserDeliverOrderItemCard;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+ * @auther quanshu
+ * @create 2022-08-02 15:44:39
+ * @describe 提货订单卡密关联表mapper类
+ */
+public interface UserDeliverOrderItemCardMapper extends BaseMapper<UserDeliverOrderItemCard> {
+
+}

+ 16 - 0
mp-service/src/main/java/com/qs/mp/user/service/IUserDeliverOrderItemCardService.java

@@ -0,0 +1,16 @@
+package com.qs.mp.user.service;
+
+import com.qs.mp.user.domain.UserDeliverOrderItemCard;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+ * <p>
+ * 提货订单卡密关联表 服务类
+ * </p>
+ *
+ * @author quanshu
+ * @since 2022-08-02
+ */
+public interface IUserDeliverOrderItemCardService extends IService<UserDeliverOrderItemCard> {
+
+}

+ 20 - 0
mp-service/src/main/java/com/qs/mp/user/service/impl/UserDeliverOrderItemCardServiceImpl.java

@@ -0,0 +1,20 @@
+package com.qs.mp.user.service.impl;
+
+import com.qs.mp.user.domain.UserDeliverOrderItemCard;
+import com.qs.mp.user.mapper.UserDeliverOrderItemCardMapper;
+import com.qs.mp.user.service.IUserDeliverOrderItemCardService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * 提货订单卡密关联表 服务实现类
+ * </p>
+ *
+ * @author quanshu
+ * @since 2022-08-02
+ */
+@Service
+public class UserDeliverOrderItemCardServiceImpl extends ServiceImpl<UserDeliverOrderItemCardMapper, UserDeliverOrderItemCard> implements IUserDeliverOrderItemCardService {
+
+}

+ 30 - 1
mp-service/src/main/java/com/qs/mp/user/service/impl/UserDeliverOrderServiceImpl.java

@@ -16,6 +16,8 @@ import com.qs.mp.common.service.IDeliveryCompanyService;
 import com.qs.mp.common.utils.DateUtils;
 import com.qs.mp.common.utils.LogUtil;
 import com.qs.mp.common.utils.StringUtils;
+import com.qs.mp.framework.domain.AsyncTask;
+import com.qs.mp.framework.service.IAsyncTaskService;
 import com.qs.mp.pay.domain.PayOrder;
 import com.qs.mp.system.service.id.BizIdGenerator;
 import com.qs.mp.user.domain.UserAddr;
@@ -80,6 +82,9 @@ public class UserDeliverOrderServiceImpl extends ServiceImpl<UserDeliverOrderMap
     @Autowired
     private IDeliveryCompanyService deliveryCompanyService;
 
+    @Autowired
+    private IAsyncTaskService asyncTaskService;
+
     @Override
     @Transactional
     public String submitOrder(Long userId, String memo, DeliverOrderSettleVO orderSettleVO) {
@@ -95,6 +100,7 @@ public class UserDeliverOrderServiceImpl extends ServiceImpl<UserDeliverOrderMap
         userDeliverOrder.setCity(userAddr.getCity());
         userDeliverOrder.setArea(userAddr.getArea());
         userDeliverOrder.setAddress(userAddr.getAddr());
+        userDeliverOrder.setType(orderSettleVO.getOrderType());
         userDeliverOrder.setOrderNum(
             orderSettleVO.getPrizeList().stream().mapToInt(UserPrizeStorage::getGoodsNum).sum());
         userDeliverOrder.setPayAmt(orderSettleVO.getPayAmt());
@@ -169,6 +175,15 @@ public class UserDeliverOrderServiceImpl extends ServiceImpl<UserDeliverOrderMap
 
         }
 
+        if (orderSettleVO.getPayAmt() == 0 && DeliverOrderTypeEnum.CARD.getValue().equals(orderSettleVO.getOrderType())) {
+            // 无需支付的卡密订单插入自动发货的异步任务
+            AsyncTask asyncTask = new AsyncTask();
+            asyncTask.setBizId(userDeliverOrder.getOrderId());
+            asyncTask.setType(AsyncTaskTypeEnum.CARD_ORDER_DELIVER.getValue());
+            boolean rtn = asyncTaskService.save(asyncTask);
+            Assert.isTrue(rtn, "提交卡密提货订单时,插入异步任务失败。userDeliverOrder:" + JSONObject.toJSONString(userDeliverOrder));
+        }
+
         return userDeliverOrder.getOrderId();
     }
 
@@ -233,7 +248,7 @@ public class UserDeliverOrderServiceImpl extends ServiceImpl<UserDeliverOrderMap
     }
 
     @Override
-    @Transactional
+    @Transactional(rollbackFor = Exception.class)
     public boolean paySuccess(PayOrder payOrder) {
         UserDeliverOrder deliverOrder = getById(payOrder.getBizId());
         if (null == deliverOrder || deliverOrder.getStatus() != DeliverOrderStatusEnum.NOT_PAY) {
@@ -246,6 +261,16 @@ public class UserDeliverOrderServiceImpl extends ServiceImpl<UserDeliverOrderMap
         deliverOrder.setPayType(payOrder.getPayResource());
         boolean rst = updateById(deliverOrder);
         Assert.isTrue(rst, "支付回调用户提货订单处理,更新订单状态失败,orderId:" + deliverOrder.getOrderId());
+
+        // 卡密订单插入自动发货任务
+        if (DeliverOrderTypeEnum.CARD.getValue().equals(deliverOrder.getType())) {
+            AsyncTask asyncTask = new AsyncTask();
+            asyncTask.setBizId(deliverOrder.getOrderId());
+            asyncTask.setType(AsyncTaskTypeEnum.CARD_ORDER_DELIVER.getValue());
+            boolean rtn = asyncTaskService.save(asyncTask);
+            Assert.isTrue(rtn, "支付回调用户提货订单处理,卡密订单插入发货任务失败,orderId:" + deliverOrder.getOrderId());
+        }
+
         return false;
     }
 
@@ -336,6 +361,10 @@ public class UserDeliverOrderServiceImpl extends ServiceImpl<UserDeliverOrderMap
                 if (Objects.isNull(userDeliverOrder)) {
                     flag = false;
                     failContent = "订单不存在";
+                } else if (DeliverOrderTypeEnum.CARD.getValue().equals(userDeliverOrder.getType())) {
+                    flag = false;
+                    failContent = "卡密订单不支持批量发货";
+
                 } else {
                     if (!DeliverOrderStatusEnum.NOT_DELIVER.equals(userDeliverOrder.getStatus())
                         && !DeliverOrderStatusEnum.PART_DELIVER.equals(userDeliverOrder.getStatus())) {

+ 22 - 10
mp-service/src/main/java/com/qs/mp/user/service/impl/UserTicketOrderServiceImpl.java

@@ -16,6 +16,8 @@ import com.qs.mp.admin.domain.param.IndexTicketBoxTopQueryParam;
 import com.qs.mp.admin.domain.param.IndexTicketSiteTopQueryParam;
 import com.qs.mp.admin.domain.param.UserTicketOrderQueryParam;
 import com.qs.mp.admin.domain.vo.*;
+import com.qs.mp.admin.service.ICouponPkgService;
+import com.qs.mp.admin.service.ICouponService;
 import com.qs.mp.admin.service.IGoodsService;
 import com.qs.mp.admin.service.ITicketBoxGoodsService;
 import com.qs.mp.admin.service.ITicketBoxService;
@@ -142,6 +144,12 @@ public class UserTicketOrderServiceImpl extends
     @Autowired
     private IGoodsService goodsService;
 
+    @Autowired
+    private ICouponService couponService;
+
+    @Autowired
+    private ICouponPkgService couponPkgService;
+
     @Override
     public String submitOrder(Long userId, TicketOrderSettleVO orderSettleVO,
         UserShareVO userShareVO) {
@@ -360,7 +368,7 @@ public class UserTicketOrderServiceImpl extends
     }
 
     @Override
-    @Transactional
+    @Transactional(rollbackFor = Exception.class)
     public boolean batchCancelOrder(String boxId, List<String> orderIds) {
         int sumTicket = 0;
         for (String orderId : orderIds) {
@@ -384,6 +392,7 @@ public class UserTicketOrderServiceImpl extends
                     ticketBox.getSaleQty() - qty)
                 .set(
                     ticketBox.getStatus() == TicketBoxStatusEnum.PUT_OFF
+                        && TicketTypeEnum.ONLINE.equals(ticketBox.getType())
                         && ticketBox.getManualOff() != 1,
                     TicketBox::getStatus, TicketBoxStatusEnum.PUT_ON)
                 .eq(TicketBox::getBoxId, ticketBox.getBoxId())
@@ -498,18 +507,21 @@ public class UserTicketOrderServiceImpl extends
             Assert.isTrue(rtn, "支付回调用户购票订单处理,更新盲票状态失败,ticketId:" + ticket.getTicketId());
 
 
-            // 如果为线上票则发送关联商品到用户仓库
-            TicketBox ticketBox = ticketBoxService.getById(ticketOrder.getBoxId());
-            if (TicketTypeEnum.ONLINE.equals(ticketBox.getType())) {
-                List<TicketBoxGoods> ticketBoxGoodsList = ticketBoxGoodsService.list(new LambdaQueryWrapper<TicketBoxGoods>()
-                    .eq(TicketBoxGoods::getBoxId, ticketBox.getBoxId()));
-                if (CollectionUtils.isNotEmpty(ticketBoxGoodsList)) {
-                    for (TicketBoxGoods ticketBoxGoods : ticketBoxGoodsList) {
-                        Goods goods = goodsService.getById(Long.valueOf(ticketBoxGoods.getRefId()));
+            // TODO: 线上和线下票如果有关联商品,都要发送关联商品到用户的仓库中
+            List<TicketBoxGoods> ticketBoxGoodsList = ticketBoxGoodsService.list(new LambdaQueryWrapper<TicketBoxGoods>()
+                .eq(TicketBoxGoods::getBoxId, ticketOrder.getBoxId()));
+            if (CollectionUtils.isNotEmpty(ticketBoxGoodsList)) {
+                // 发送关联商品
+                for (TicketBoxGoods ticketBoxGoods : ticketBoxGoodsList) {
 
+                    if (TicketBoxGoodsTypeEnum.GOODS.equals(ticketBoxGoods.getType())) {
+                        Goods goods = goodsService.getById(Long.valueOf(ticketBoxGoods.getRefId()));
                         userPrizeStorageService.takeInStorage(orderItem.getUserId(),goods.getTitle(),goods.getPicUrl(),
                             ticketBoxGoods.getRefId(),PrizeStorageInTypeEnum.TICKET_GOODS,orderItem.getItemId());
-
+                    } else if (TicketBoxGoodsTypeEnum.COUPON.equals(ticketBoxGoods.getType())) {
+                        couponService.distribute(ticket, orderItem.getUserId(), ticketBoxGoods.getRefId());
+                    } else if (TicketBoxGoodsTypeEnum.COUPON_PKG.equals(ticketBoxGoods.getType())) {
+                        couponPkgService.distribute(ticket, orderItem.getUserId(), ticketBoxGoods.getRefId());
                     }
                 }
             }

+ 28 - 0
mp-service/src/main/resources/mapper/admin/GoodsCardMapper.xml

@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.qs.mp.admin.mapper.GoodsCardMapper">
+
+    <!-- 通用查询映射结果 -->
+    <resultMap id="BaseResultMap" type="com.qs.mp.admin.domain.GoodsCard">
+        <id column="id" property="id" />
+        <result column="goods_id" property="goodsId" />
+        <result column="card_no" property="cardNo" />
+        <result column="card_pwd" property="cardPwd" />
+        <result column="is_use" property="isUse" />
+        <result column="is_deleted" property="isDeleted" />
+        <result column="created_time" property="createdTime" />
+        <result column="updated_time" property="updatedTime" />
+    </resultMap>
+
+    <!-- 通用查询结果列 -->
+    <sql id="Base_Column_List">
+        id, goods_id, card_no, card_pwd, is_use, is_deleted, created_time, updated_time
+    </sql>
+
+    <select id="listDeliveryOrderItemCardByWrapper" resultMap="BaseResultMap">
+        select t2.*
+        from mp_user_deliver_order_item_card t1
+         left join mp_goods_card t2 on t1.card_id = t2.id
+        ${ew.customSqlSegment}
+  </select>
+</mapper>

+ 28 - 5
mp-service/src/main/resources/mapper/admin/TicketAwardsPrizeMapper.xml

@@ -41,13 +41,36 @@
 
     <select id="listByQueryWrapper" resultType="com.qs.mp.admin.domain.TicketAwardsPrize">
         select
-            if(t1.prize_type = 'goods' AND t2.title is not null ,t2.title,t1.title) as title,
-            if(t1.prize_type = 'goods' AND t2.pic_url is not null,t2.pic_url,t1.pic_url) as pic_url,
-            if(t1.prize_type = 'goods' AND t2.value is not null,t2.value,t1.value) as value,
-            if(t1.prize_type = 'goods' AND t2.cost is not null,t2.cost,0) as cost,
+            case t1.prize_type
+                when 'goods' then if(t2.title is not null, t2.title, t1.title)
+                when 'coupon' then if(t3.title is not null, t3.title, t1.title)
+                when 'coupon_pkg' then if(t4.title is not null, t4.title, t1.title)
+                else t1.title
+            end as title,
+
+            case t1.prize_type
+                when 'goods' then if(t2.pic_url is not null, t2.pic_url, t1.pic_url)
+                when 'coupon' then if(t3.pic_url is not null, t3.pic_url, t1.pic_url)
+                when 'coupon_pkg' then if(t4.pic_url is not null, t4.pic_url, t1.pic_url)
+                else t1.pic_url
+            end as pic_url,
+
+            case t1.prize_type
+                when 'goods' then if(t2.value is not null, t2.value, t1.value)
+                when 'coupon' then if(t3.discount is not null, t3.discount, t1.value)
+                when 'coupon_pkg' then if(t4.face_price is not null, t4.face_price, t1.value)
+                else t1.value
+            end as value,
+
+            case t1.prize_type
+                when 'goods' then if(t2.cost is not null, t2.cost, 0)
+                else 0
+            end as cost,
             t1.*
         from mp_ticket_awards_prize t1
-         left join mp_goods t2 on t1.ref_id = t2.goods_id
+            left join mp_goods t2 on t1.ref_id = t2.goods_id
+            left join mp_coupon t3 on t1.ref_id = t3.coupon_id
+            left join mp_coupon_pkg t4 on t1.ref_id = t4.id
         ${ew.customSqlSegment}
     </select>
 </mapper>

+ 31 - 6
mp-service/src/main/resources/mapper/admin/TicketBoxGoodsMapper.xml

@@ -20,13 +20,38 @@
     </sql>
 
     <select id="listByQueryWrapper" resultType="com.qs.mp.admin.domain.vo.TicketBoxGoodsVO">
-        select if(t2.title is not null, t2.title, t1.title)       as title,
-               if(t2.pic_url is not null, t2.pic_url, t1.pic_url) as pic_url,
-               if(t2.value is not null, t2.value, t1.value)       as value,
-               if(t2.cost is not null, t2.cost, 0)                as cost,
-               t1.*
+        select
+            case t1.type
+                when 'goods' then if(t2.title is not null, t2.title, t1.title)
+                when 'coupon' then if(t3.title is not null, t3.title, t1.title)
+                when 'coupon_pkg' then if(t4.title is not null, t4.title, t1.title)
+                else t1.title
+            end as title,
+
+            case t1.type
+                when 'goods' then if(t2.pic_url is not null, t2.pic_url, t1.pic_url)
+                when 'coupon' then if(t3.pic_url is not null, t3.pic_url, t1.pic_url)
+                when 'coupon_pkg' then if(t4.pic_url is not null, t4.pic_url, t1.pic_url)
+                else t1.pic_url
+            end as pic_url,
+
+            case t1.type
+                when 'goods' then if(t2.value is not null, t2.value, t1.value)
+                when 'coupon' then if(t3.discount is not null, t3.discount, t1.value)
+                when 'coupon_pkg' then if(t4.face_price is not null, t4.face_price, t1.value)
+                else t1.value
+            end as value,
+
+            case t1.type
+                when 'goods' then if(t2.cost is not null, t2.cost, 0)
+                else 0
+            end as cost,
+
+           t1.*
         from mp_ticket_box_goods t1
         left join mp_goods t2 on t1.ref_id = t2.goods_id
-            ${ew.customSqlSegment}
+        left join mp_coupon t3 on t1.ref_id = t3.coupon_id
+        left join mp_coupon_pkg t4 on t1.ref_id = t4.id
+        ${ew.customSqlSegment}
     </select>
 </mapper>

+ 20 - 3
mp-service/src/main/resources/mapper/admin/TicketBoxMapper.xml

@@ -45,13 +45,30 @@
     </select>
 
     <select id="listTicketGoodsByQueryWrapper" resultType="com.qs.mp.admin.domain.vo.TicketBoxGoodsListVO">
-        select if(t3.title is not null, t3.title, t2.title)       as title,
-               if(t3.pic_url is not null, t3.pic_url, t2.pic_url) as pic_url,
+        select
+                case t2.type
+                when 'goods' then if(t3.title is not null, t3.title, t2.title)
+                when 'coupon' then if(t4.title is not null, t4.title, t2.title)
+                when 'coupon_pkg' then if(t5.title is not null, t5.title, t2.title)
+                else t2.title
+                end as title,
+
+                case t2.type
+                when 'goods' then if(t3.pic_url is not null, t3.pic_url, t2.pic_url)
+                when 'coupon' then if(t4.pic_url is not null, t4.pic_url, t2.pic_url)
+                when 'coupon_pkg' then if(t5.pic_url is not null, t5.pic_url, t2.pic_url)
+                else t2.pic_url
+                end as pic_url,
+
+                t2.type refType,
                 t2.ref_id,
                t1.*
         from mp_ticket_box t1
              left join mp_ticket_box_goods t2 on t1.box_id = t2.box_id
              left join mp_goods t3 on t2.ref_id = t3.goods_id
-        ${ew.customSqlSegment}
+             left join mp_coupon t4 on t2.ref_id = t4.coupon_id
+             left join mp_coupon_pkg t5 on t2.ref_id = t5.id
+
+      ${ew.customSqlSegment}
   </select>
 </mapper>

+ 20 - 0
mp-service/src/main/resources/mapper/user/UserDeliverOrderItemCardMapper.xml

@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.qs.mp.user.mapper.UserDeliverOrderItemCardMapper">
+
+    <!-- 通用查询映射结果 -->
+    <resultMap id="BaseResultMap" type="com.qs.mp.user.domain.UserDeliverOrderItemCard">
+        <id column="id" property="id" />
+        <result column="item_id" property="itemId" />
+        <result column="card_id" property="cardId" />
+        <result column="is_deleted" property="isDeleted" />
+        <result column="created_time" property="createdTime" />
+        <result column="updated_time" property="updatedTime" />
+    </resultMap>
+
+    <!-- 通用查询结果列 -->
+    <sql id="Base_Column_List">
+        id, item_id, card_id, is_deleted, created_time, updated_time
+    </sql>
+
+</mapper>

+ 1 - 1
mp-service/src/main/resources/mapper/user/UserDeliverOrderItemMapper.xml

@@ -36,7 +36,7 @@
 	
 	 <!-- 查询用户订单明细列表 -->
     <select id="selectUserDeliverOrderItemVOList" resultType="com.qs.mp.user.domain.vo.UserDeliverOrderItemVO">
-		select t1.*, t2.company_name ,t2.company_home_url ,t2.query_url,t3.goods_code,t3.value goodsValue,t3.cost goodsCost,t4.value skuValue,t4.sku_code,t5.name supplierName
+		select t1.*, t2.company_name ,t2.company_home_url ,t2.query_url,t3.goods_code,t3.value goodsValue,t3.cost goodsCost,t3.use_link,t4.value skuValue,t4.sku_code,t5.name supplierName
 		from mp_user_deliver_order_item t1
 		left join mp_delivery_company t2 on t1.delivery_id = t2.delivery_id
         left join mp_goods t3 on t1.goods_id = t3.goods_id

+ 2 - 0
mp-service/src/main/resources/mapper/user/UserPrizeStorageMapper.xml

@@ -28,6 +28,8 @@
         SELECT
         t1.*,
         t2.multi_sku isMoreSku ,
+        t2.type goodsType,
+        t2.discount_rate,
         t5.type
         FROM
         mp_user_prize_storage t1