Prechádzať zdrojové kódy

Merge branch 'refs/heads/master_feat_coupon_20250308'

# Conflicts:
#	fs-service-system/src/main/java/com/fs/store/service/impl/FsStoreOrderServiceImpl.java
#	fs-service-system/src/main/java/com/fs/store/service/impl/FsStorePaymentServiceImpl.java
xdd 1 mesiac pred
rodič
commit
146dc51bc1
42 zmenil súbory, kde vykonal 2305 pridanie a 164 odobranie
  1. 5 2
      fs-admin/pom.xml
  2. 104 0
      fs-admin/src/main/java/com/fs/store/controller/FsCouponScheduleController.java
  3. 3 0
      fs-admin/src/main/java/com/fs/store/controller/FsStoreCouponController.java
  4. 14 1
      fs-admin/src/main/java/com/fs/store/controller/FsStoreCouponIssueController.java
  5. 1 1
      fs-admin/src/main/java/com/fs/store/controller/FsStorePaymentController.java
  6. 28 0
      fs-admin/src/main/java/com/fs/task/CouponTask.java
  7. 106 0
      fs-admin/src/main/java/com/fs/task/MiniProgramSubTask.java
  8. 103 0
      fs-admin/src/test/java/com/fs/store/controller/FsStorePaymentControllerTest.java
  9. 134 0
      fs-service-system/src/main/java/com/fs/store/domain/FsCouponSchedule.java
  10. 94 0
      fs-service-system/src/main/java/com/fs/store/domain/FsCouponScheduleLog.java
  11. 89 0
      fs-service-system/src/main/java/com/fs/store/domain/FsMiniprogramSubNotifyTask.java
  12. 64 144
      fs-service-system/src/main/java/com/fs/store/domain/FsStoreProductPackage.java
  13. 43 0
      fs-service-system/src/main/java/com/fs/store/dto/ClientCredGrantReqDTO.java
  14. 52 0
      fs-service-system/src/main/java/com/fs/store/dto/MiniGramSubsMsgResultDTO.java
  15. 67 0
      fs-service-system/src/main/java/com/fs/store/dto/TemplateMessageSendRequestDTO.java
  16. 36 0
      fs-service-system/src/main/java/com/fs/store/dto/WeXinAccessTokenDTO.java
  17. 33 0
      fs-service-system/src/main/java/com/fs/store/enums/IcgProcessStatusEnum.java
  18. 30 0
      fs-service-system/src/main/java/com/fs/store/enums/IcgScheduleOperationTypeEnum.java
  19. 44 0
      fs-service-system/src/main/java/com/fs/store/enums/MiniAppNotifyTaskStatusEnum.java
  20. 60 0
      fs-service-system/src/main/java/com/fs/store/mapper/FsCouponScheduleLogMapper.java
  21. 83 0
      fs-service-system/src/main/java/com/fs/store/mapper/FsCouponScheduleMapper.java
  22. 81 0
      fs-service-system/src/main/java/com/fs/store/mapper/FsMiniprogramSubNotifyTaskMapper.java
  23. 17 0
      fs-service-system/src/main/java/com/fs/store/mapper/FsStoreCouponIssueMapper.java
  24. 11 0
      fs-service-system/src/main/java/com/fs/store/mapper/FsStoreCouponMapper.java
  25. 82 0
      fs-service-system/src/main/java/com/fs/store/service/IFsCouponScheduleService.java
  26. 17 8
      fs-service-system/src/main/java/com/fs/store/service/IFsStoreCouponIssueService.java
  27. 1 0
      fs-service-system/src/main/java/com/fs/store/service/IFsStoreCouponService.java
  28. 28 0
      fs-service-system/src/main/java/com/fs/store/service/IWechatMiniProgrService.java
  29. 316 0
      fs-service-system/src/main/java/com/fs/store/service/impl/FsCouponScheduleServiceImpl.java
  30. 10 0
      fs-service-system/src/main/java/com/fs/store/service/impl/FsStoreCouponIssueServiceImpl.java
  31. 27 0
      fs-service-system/src/main/java/com/fs/store/service/impl/FsStoreOrderServiceImpl.java
  32. 9 0
      fs-service-system/src/main/java/com/fs/store/service/impl/FsStorePaymentServiceImpl.java
  33. 29 0
      fs-service-system/src/main/java/com/fs/store/service/impl/IWechatMiniProgrServiceImpl.java
  34. 96 0
      fs-service-system/src/main/java/com/fs/store/utils/MiniProgramHttp.java
  35. 36 0
      fs-service-system/src/main/java/com/fs/store/vo/FsStoreProductPacketVO.java
  36. 20 0
      fs-service-system/src/main/resources/mapper/store/FsCouponScheduleLogMapper.xml
  37. 181 0
      fs-service-system/src/main/resources/mapper/store/FsCouponScheduleMapper.xml
  38. 66 0
      fs-service-system/src/main/resources/mapper/store/FsMiniprogramSubNotifyTaskMapper.xml
  39. 53 7
      fs-service-system/src/main/resources/mapper/store/FsStoreCouponIssueMapper.xml
  40. 5 0
      fs-service-system/src/main/resources/mapper/store/FsStoreCouponMapper.xml
  41. 26 1
      fs-service-system/src/main/resources/mapper/store/FsStoreProductPackageMapper.xml
  42. 1 0
      fs-user-app/src/main/java/com/fs/app/controller/CouponController.java

+ 5 - 2
fs-admin/pom.xml

@@ -138,8 +138,11 @@
             <version>1.7.30</version>
             <scope>provided</scope>
         </dependency>
-
-
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
 
     </dependencies>
 

+ 104 - 0
fs-admin/src/main/java/com/fs/store/controller/FsCouponScheduleController.java

@@ -0,0 +1,104 @@
+package com.fs.store.controller;
+
+import java.util.List;
+
+import com.fs.store.domain.FsCouponSchedule;
+import com.fs.store.service.IFsCouponScheduleService;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.common.core.page.TableDataInfo;
+
+/**
+ * 定时发放优惠券队列Controller
+ *
+ * @author fs
+ * @date 2025-03-08
+ */
+@RestController
+@RequestMapping("/system/schedule")
+public class FsCouponScheduleController extends BaseController
+{
+    @Autowired
+    private IFsCouponScheduleService fsCouponScheduleService;
+
+    /**
+     * 查询定时发放优惠券队列列表
+     */
+    @PreAuthorize("@ss.hasPermi('system:schedule:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsCouponSchedule fsCouponSchedule)
+    {
+        startPage();
+        List<FsCouponSchedule> list = fsCouponScheduleService.selectFsCouponScheduleList(fsCouponSchedule);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出定时发放优惠券队列列表
+     */
+    @PreAuthorize("@ss.hasPermi('system:schedule:export')")
+    @Log(title = "定时发放优惠券队列", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsCouponSchedule fsCouponSchedule)
+    {
+        List<FsCouponSchedule> list = fsCouponScheduleService.selectFsCouponScheduleList(fsCouponSchedule);
+        ExcelUtil<FsCouponSchedule> util = new ExcelUtil<FsCouponSchedule>(FsCouponSchedule.class);
+        return util.exportExcel(list, "schedule");
+    }
+
+    /**
+     * 获取定时发放优惠券队列详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('system:schedule:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(fsCouponScheduleService.selectFsCouponScheduleById(id));
+    }
+
+    /**
+     * 新增定时发放优惠券队列
+     */
+    @PreAuthorize("@ss.hasPermi('system:schedule:add')")
+    @Log(title = "定时发放优惠券队列", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsCouponSchedule fsCouponSchedule)
+    {
+        return toAjax(fsCouponScheduleService.insertFsCouponSchedule(fsCouponSchedule));
+    }
+
+    /**
+     * 修改定时发放优惠券队列
+     */
+    @PreAuthorize("@ss.hasPermi('system:schedule:edit')")
+    @Log(title = "定时发放优惠券队列", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsCouponSchedule fsCouponSchedule)
+    {
+        return toAjax(fsCouponScheduleService.updateFsCouponSchedule(fsCouponSchedule));
+    }
+
+    /**
+     * 删除定时发放优惠券队列
+     */
+    @PreAuthorize("@ss.hasPermi('system:schedule:remove')")
+    @Log(title = "定时发放优惠券队列", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(fsCouponScheduleService.deleteFsCouponScheduleByIds(ids));
+    }
+}

+ 3 - 0
fs-admin/src/main/java/com/fs/store/controller/FsStoreCouponController.java

@@ -7,6 +7,7 @@ import com.fs.common.core.domain.R;
 import com.fs.store.domain.FsStoreCouponIssue;
 import com.fs.store.param.FsStoreCouponPublishParam;
 import com.fs.store.service.IFsStoreCouponIssueService;
+import com.fs.store.vo.FsStoreCouponIssueVO;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.GetMapping;
@@ -40,6 +41,8 @@ public class FsStoreCouponController extends BaseController
     private IFsStoreCouponService fsStoreCouponService;
     @Autowired
     private IFsStoreCouponIssueService fsStoreCouponIssueService;
+
+
     /**
      * 查询优惠券列表
      */

+ 14 - 1
fs-admin/src/main/java/com/fs/store/controller/FsStoreCouponIssueController.java

@@ -2,6 +2,7 @@ package com.fs.store.controller;
 
 import java.util.List;
 
+import com.fs.store.domain.FsStoreCoupon;
 import com.fs.store.vo.FsStoreCouponIssueVO;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -24,7 +25,7 @@ import com.fs.common.core.page.TableDataInfo;
 
 /**
  * 优惠券领取Controller
- * 
+ *
  * @author fs
  * @date 2022-03-15
  */
@@ -35,6 +36,18 @@ public class FsStoreCouponIssueController extends BaseController
     @Autowired
     private IFsStoreCouponIssueService fsStoreCouponIssueService;
 
+
+    /**
+     * 获取所有可用套餐券
+     * @return 套餐券列表
+     */
+    @GetMapping("/listAllAvailable")
+    @PreAuthorize("@ss.hasPermi('store:storeCoupon:listAllAvailable')")
+    public AjaxResult listAllAvailable(){
+        List<FsStoreCouponIssueVO> fsStoreCouponIssueVOS = fsStoreCouponIssueService.listAllAvailable();
+        return AjaxResult.success(fsStoreCouponIssueVOS);
+    }
+
     /**
      * 查询优惠券领取列表
      */

+ 1 - 1
fs-admin/src/main/java/com/fs/store/controller/FsStorePaymentController.java

@@ -129,7 +129,7 @@ public class FsStorePaymentController extends BaseController
 
 
     @ApiOperation("支付手动通知")
-    @PreAuthorize("@ss.hasPermi('store:storePayment:payNotify')")
+//    @PreAuthorize("@ss.hasPermi('store:storePayment:payNotify')")
     @PostMapping("/returnPayStatus")
     public R returnPayStatus(@RequestBody FsStorePayment fsStorePayment) throws Exception
     {

+ 28 - 0
fs-admin/src/main/java/com/fs/task/CouponTask.java

@@ -0,0 +1,28 @@
+package com.fs.task;
+
+import com.fs.store.service.IFsCouponScheduleService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+/**
+ * 优惠券定时任务
+ */
+@RequiredArgsConstructor
+@Service("couponTask")
+@Slf4j
+public class CouponTask {
+
+    private final IFsCouponScheduleService fsCouponScheduleService;
+    /**
+     * 发放优惠券
+     */
+    public void issueCoupon(){
+        log.info("开始执行发放优惠券定时任务");
+        long startTime = System.currentTimeMillis();
+        fsCouponScheduleService.issueCoupon();
+        long endTime = System.currentTimeMillis();
+        log.info("发放优惠券定时任务执行完成,耗时:{}毫秒", endTime - startTime);
+    }
+
+}

+ 106 - 0
fs-admin/src/main/java/com/fs/task/MiniProgramSubTask.java

@@ -0,0 +1,106 @@
+package com.fs.task;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson.TypeReference;
+import com.fs.store.domain.FsMiniprogramSubNotifyTask;
+import com.fs.store.dto.ClientCredGrantReqDTO;
+import com.fs.store.dto.MiniGramSubsMsgResultDTO;
+import com.fs.store.dto.TemplateMessageSendRequestDTO;
+import com.fs.store.dto.WeXinAccessTokenDTO;
+import com.fs.store.enums.MiniAppNotifyTaskStatusEnum;
+import com.fs.store.mapper.FsMiniprogramSubNotifyTaskMapper;
+import com.fs.store.service.IWechatMiniProgrService;
+import com.fs.wx.miniapp.config.WxMaProperties;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang.exception.ExceptionUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 小程序订阅通知定时任务
+ */
+@Service("miniProgramSubTask")
+@Slf4j
+@RequiredArgsConstructor
+public class MiniProgramSubTask {
+    private final IWechatMiniProgrService wechatMiniProgrService;
+
+    private final FsMiniprogramSubNotifyTaskMapper notifyTaskMapper;
+
+    private WxMaProperties.Config config = null;
+
+    @Autowired
+    public void setConfig(WxMaProperties properties) {
+        if(ObjectUtil.isNotNull(properties)){
+            this.config = properties.getConfigs().get(0);
+        }
+    }
+
+    /**
+     * 小程序订阅通知
+     */
+    public void notifyMiniAppSub(){
+        log.info("小程序订阅通知定时任务");
+        // 先获取所有可用待处理任务
+        List<FsMiniprogramSubNotifyTask> pendingData = notifyTaskMapper.selectPendingData();
+        if(CollectionUtils.isEmpty(pendingData)){
+            log.info("小程序订阅通知定时任务, 无待处理数据");
+            return;
+        }
+        for (FsMiniprogramSubNotifyTask pendingDatum : pendingData) {
+            pendingDatum.setUpdateTime(LocalDateTime.now());
+
+            ClientCredGrantReqDTO clientCredGrantReqDTO = new ClientCredGrantReqDTO();
+            clientCredGrantReqDTO.setAppid(config.getAppid());
+            clientCredGrantReqDTO.setSecret(config.getSecret());
+            clientCredGrantReqDTO.setGrant_type("client_credential");
+
+           try{
+               // 获取accessToken
+               WeXinAccessTokenDTO stableToken = wechatMiniProgrService
+                       .getStableToken(clientCredGrantReqDTO);
+
+               String accessToken = stableToken.getAccessToken();
+
+               // 调用微信小程序订阅通知
+               TemplateMessageSendRequestDTO sendRequestDTO = new TemplateMessageSendRequestDTO();
+               sendRequestDTO.setTemplate_id(pendingDatum.getTemplateId());
+               sendRequestDTO.setTouser(pendingDatum.getTouser());
+               sendRequestDTO.setPage(pendingDatum.getPage());
+               TypeReference<Map<String, TemplateMessageSendRequestDTO.TemplateDataValue>> typeReference = new TypeReference<Map<String, TemplateMessageSendRequestDTO.TemplateDataValue>>() {};
+               sendRequestDTO.setData(JSONObject.parseObject(pendingDatum.getData(),typeReference));
+               MiniGramSubsMsgResultDTO miniGramSubsMsgResultDTO = wechatMiniProgrService.sendSubscribeMsg(accessToken, sendRequestDTO);
+               pendingDatum.setRequestBody(JSONObject.toJSONString(sendRequestDTO));
+               pendingDatum.setResponseBody(JSONObject.toJSONString(miniGramSubsMsgResultDTO));
+
+               // 如果推送消息成功
+               if(miniGramSubsMsgResultDTO.getErrcode() == 0){
+                   pendingDatum.setStatus(MiniAppNotifyTaskStatusEnum.SUCCESS.getValue());
+               } else {
+                   // 更新任务状态为执行失败
+                   pendingDatum.setStatus(MiniAppNotifyTaskStatusEnum.FAILED.getValue());
+                   pendingDatum.setErrorMessage(JSONObject.toJSONString(miniGramSubsMsgResultDTO));
+                   pendingDatum.setRetryCount(pendingDatum.getRetryCount() +1);
+               }
+           }catch (Throwable e){
+               // 更新任务状态为执行失败
+               pendingDatum.setStatus(MiniAppNotifyTaskStatusEnum.FAILED.getValue());
+               pendingDatum.setErrorMessage(ExceptionUtils.getStackTrace(e));
+               pendingDatum.setRetryCount(pendingDatum.getRetryCount() +1);
+               log.error("小程序订阅通知定时任务异常: {}", ExceptionUtils.getStackTrace(e));
+           }
+        }
+
+        if(CollectionUtils.isNotEmpty(pendingData)){
+            notifyTaskMapper.updateBatchById(pendingData);
+        }
+
+    }
+}

+ 103 - 0
fs-admin/src/test/java/com/fs/store/controller/FsStorePaymentControllerTest.java

@@ -0,0 +1,103 @@
+package com.fs.store.controller;
+
+import com.fs.FSAdminApplication;
+import com.fs.common.core.domain.R;
+import com.fs.store.domain.FsCouponSchedule;
+import com.fs.store.domain.FsMiniprogramSubNotifyTask;
+import com.fs.store.domain.FsStorePayment;
+import com.fs.store.enums.IcgProcessStatusEnum;
+import com.fs.store.mapper.FsMiniprogramSubNotifyTaskMapper;
+import com.fs.store.service.IFsCouponScheduleService;
+import com.fs.task.MiniProgramSubTask;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.Collections;
+import java.util.Date;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = FSAdminApplication.class)
+@RequiredArgsConstructor
+@Slf4j
+public class FsStorePaymentControllerTest {
+
+    @Autowired
+    private FsStorePaymentController fsStorePaymentController;
+
+    @Autowired
+    private IFsCouponScheduleService fsCouponScheduleService;
+
+    @Autowired
+    private MiniProgramSubTask miniProgramSubTask;
+    @Test
+    public void returnPayStatus() throws Exception {
+
+        Authentication authentication = new UsernamePasswordAuthenticationToken(
+                "admin",
+                "sadmin#258",
+                Collections.singletonList(new SimpleGrantedAuthority("ADMIN"))
+        );
+
+        // Set the Authentication in the SecurityContext
+        SecurityContextHolder.getContext().setAuthentication(authentication);
+
+
+        FsStorePayment fsStorePayment = new FsStorePayment();
+        fsStorePayment.setPaymentId(01L);
+        fsStorePayment.setStatus(1);
+        fsStorePayment.setOrderId(1899279950347763712L);
+        fsStorePayment.setPayTypeCode("weixin");
+        fsStorePayment.setPayMoney(new BigDecimal("0.01"));
+        fsStorePayment.setPayTime(new Date());
+        fsStorePayment.setTradeNo("002900TOP3B250311101453P779ac1369bc00000");
+        fsStorePayment.setUserId(520391L);
+        fsStorePayment.setOpenId("obmfC6y5ZOuvq9b4UlL7GUayuYQE");
+        fsStorePayment.setCompanyId(12L);
+        fsStorePayment.setCompanyUserId(42L);
+        fsStorePayment.setDeptId(144L);
+        fsStorePayment.setBankSerialNo("1899279950347763712L");
+        fsStorePayment.setPayMode("hf");
+        R r = fsStorePaymentController.returnPayStatus(fsStorePayment);
+    }
+
+    @Test
+    public void test() throws Exception {
+        FsCouponSchedule fsCouponSchedule = new FsCouponSchedule();
+        fsCouponSchedule.setOrderId(1899279950347763712L);
+        fsCouponSchedule.setSetmealId(1800L);
+        fsCouponSchedule.setSetmealTitle("甘草干姜汤(北京同仁堂)1盒");
+        fsCouponSchedule.setMonth(4L);
+        fsCouponSchedule.setCount(0L);
+        fsCouponSchedule.setStatus(IcgProcessStatusEnum.PENDING.getCode());
+        fsCouponSchedule.setOrderTime(LocalDateTime.now());
+        fsCouponSchedule.setUserId(520391L);
+        // 如果上次发送时间为空,那么预计发送时间等于=下单时间+1月
+        fsCouponSchedule.setSendTime(fsCouponSchedule.getOrderTime().plusMonths(1L));
+        fsCouponSchedule.setRetryCount(0);
+
+        fsCouponScheduleService.insertFsCouponSchedule(fsCouponSchedule);
+    }
+
+    @Test
+    public void issueCoupon() throws Exception {
+        fsCouponScheduleService.issueCoupon();
+    }
+
+    @Test
+    public void notifyMiniAppSub(){
+        miniProgramSubTask.notifyMiniAppSub();
+    }
+
+
+}

+ 134 - 0
fs-service-system/src/main/java/com/fs/store/domain/FsCouponSchedule.java

@@ -0,0 +1,134 @@
+package com.fs.store.domain;
+
+import java.time.LocalDateTime;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import com.hc.openapi.tool.util.StringUtils;
+import lombok.Data;
+import org.apache.commons.collections4.CollectionUtils;
+
+/**
+ * 定时发放优惠券队列对象 fs_coupon_schedule
+ *
+ * @author fs
+ * @date 2025-03-08
+ */
+@Data
+public class FsCouponSchedule extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 主键ID */
+    private Long id;
+
+    /** 用户ID */
+    @Excel(name = "用户ID")
+    private Long userId;
+
+    /**
+     * 用户名称
+     */
+    @Excel(name = "用户名称")
+    private String userName;
+    /** 订单ID */
+    @Excel(name = "订单ID")
+    private Long orderId;
+
+    /** 套餐ID */
+    @Excel(name = "套餐ID")
+    private Long setmealId;
+    /**
+     * 套餐名称
+     */
+    @Excel(name = "套餐名称")
+    private String setmealTitle;
+
+    /** 总月数 */
+    @Excel(name = "总月数")
+    private Long month;
+
+    /** 当前次数 */
+    @Excel(name = "当前次数")
+    private Long count;
+
+    /**
+     * 小程序跳转页面
+     */
+    @Excel(name = "小程序跳转页面")
+    private String page;
+
+    /**
+     * 优惠券备注信息
+     */
+    @Excel(name = "备注")
+    private String remark;
+
+    /** 0待处理, 1正在处理, 2成功, -1失败, 3用户拒签或者退货 */
+    @Excel(name = "0待处理, 1正在处理, 2成功, -1失败, 3用户拒签或者退货")
+    private Integer status;
+
+    /** 下单时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "下单时间", width = 30, dateFormat = "yyyy-MM-dd")
+    private LocalDateTime orderTime;
+
+    /** 优惠券ID */
+    @Excel(name = "优惠券ID")
+    private Set<Long> couponId;
+    /**
+     * 优惠券id列表
+     */
+    private String couponIds;
+
+    public Set<Long> getCouponId() {
+        if(StringUtils.isNotBlank(couponIds)){
+            String[] split = couponIds.split(",");
+            HashSet<Long> longs = new HashSet<>();
+            for (String e : split) {
+                longs.add(Long.valueOf(e.trim()));
+            }
+            couponId=longs;
+        }
+        return couponId;
+    }
+
+    public String getCouponIds() {
+        if(CollectionUtils.isNotEmpty(couponId)){
+            couponIds = couponId.stream()
+                    .map(String::valueOf)
+                    .collect(Collectors.joining(","));
+        }
+
+        return couponIds;
+    }
+
+    /** 预计发送时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "预计发送时间", width = 30, dateFormat = "yyyy-MM-dd")
+    private LocalDateTime sendTime;
+
+    /** 实际发送时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "实际发送时间", width = 30, dateFormat = "yyyy-MM-dd")
+    private LocalDateTime actualSendTime;
+
+    /** 错误信息 */
+    @Excel(name = "错误信息")
+    private String errorMessage;
+
+    /** 重试次数 */
+    @Excel(name = "重试次数")
+    private Integer retryCount;
+
+    /** 最大重试次数 */
+    @Excel(name = "最大重试次数")
+    private Integer maxRetries = 3;
+
+}

+ 94 - 0
fs-service-system/src/main/java/com/fs/store/domain/FsCouponScheduleLog.java

@@ -0,0 +1,94 @@
+package com.fs.store.domain;
+
+import lombok.Data;
+
+import java.time.LocalDateTime;
+import java.util.Date;
+
+/**
+ * 优惠券发放计划日志表实体类
+ *
+ * @author example
+ */
+@Data
+public class FsCouponScheduleLog {
+    /**
+     * 日志ID
+     */
+    private Long logId;
+
+    /**
+     * fs_coupon_schedule 表的主键ID
+     */
+    private Long scheduleId;
+
+    /**
+     * 用户ID
+     */
+    private Long userId;
+
+    /**
+     * 订单ID
+     */
+    private Long orderId;
+
+    /**
+     * 操作前状态
+     */
+    private Integer statusBefore;
+
+    /**
+     * 操作后状态
+     */
+    private Integer statusAfter;
+
+    /**
+     * 操作前预计发送时间
+     */
+    private LocalDateTime sendTimeBefore;
+
+    /**
+     * 操作后预计发送时间
+     */
+    private LocalDateTime sendTimeAfter;
+
+    /**
+     * 实际发送时间
+     */
+    private LocalDateTime actualSendTime;
+
+    /**
+     * 错误信息
+     */
+    private String errorMessage;
+
+    /**
+     * 操作前重试次数
+     */
+    private Integer retryCountBefore;
+
+    /**
+     * 操作后重试次数
+     */
+    private Integer retryCountAfter;
+
+    /**
+     * 操作类型,如:INSERT, ERROR, RETRY
+     */
+    private String operationType;
+
+    /**
+     * 操作时间
+     */
+    private LocalDateTime operationTime;
+
+    /**
+     * 操作人
+     */
+    private String operator;
+
+    /**
+     * 备注
+     */
+    private String remark;
+}

+ 89 - 0
fs-service-system/src/main/java/com/fs/store/domain/FsMiniprogramSubNotifyTask.java

@@ -0,0 +1,89 @@
+package com.fs.store.domain;
+
+import lombok.Data;
+import com.alibaba.fastjson.JSONObject; //如果data字段是fastjson类型
+import java.time.LocalDateTime;
+
+/**
+ * 小程序订阅通知定时任务表
+ * @author
+ */
+@Data
+public class FsMiniprogramSubNotifyTask {
+
+    /**
+     * 任务ID,唯一标识
+     */
+    private Long id;
+
+    /**
+     * 任务名称,用于描述任务
+     */
+    private String taskName;
+
+    /**
+     * 微信小程序订阅消息模板ID
+     */
+    private String templateId;
+
+    /**
+     * 要发送的用户openid
+     */
+    private String touser;
+
+    /**
+     * 点击消息跳转的页面路径(可选)
+     */
+    private String page;
+
+    /**
+     * 消息内容,JSON格式。每个键值对对应模板中的一个变量
+     */
+    private String data;
+
+    /**
+     * 任务状态:0=待执行, 1=执行中, 2=执行成功, 3=执行失败, 4=已取消
+     */
+    private Integer status;
+
+    /**
+     * 当前重试次数
+     */
+    private Integer retryCount;
+
+    /**
+     * 最大重试次数
+     */
+    private Integer maxRetries;
+
+    /**
+     * 请求参数
+     */
+    private String requestParams;
+
+
+    /**
+     * 完整的请求体 (JSON格式)
+     */
+    private String requestBody;
+
+    /**
+     * API 响应结果 (JSON格式)
+     */
+    private String responseBody;
+
+    /**
+     * 错误信息 (如果执行失败)
+     */
+    private String errorMessage;
+
+    /**
+     * 任务创建时间
+     */
+    private LocalDateTime createTime;
+
+    /**
+     * 最后更新时间
+     */
+    private LocalDateTime updateTime;
+}

+ 64 - 144
fs-service-system/src/main/java/com/fs/store/domain/FsStoreProductPackage.java

@@ -2,17 +2,22 @@ package com.fs.store.domain;
 
 import java.math.BigDecimal;
 import java.util.List;
+import java.util.StringJoiner;
 
+import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.fs.common.annotation.Excel;
 import com.fs.common.core.domain.BaseEntity;
 import com.fs.store.dto.StoreOrderProductDTO;
+import lombok.Data;
+import org.springframework.util.CollectionUtils;
 
 /**
  * 商品组合套餐对象 fs_store_product_package
- * 
+ *
  * @author fs
  * @date 2022-07-14
  */
+@Data
 public class FsStoreProductPackage extends BaseEntity
 {
     private static final long serialVersionUID = 1L;
@@ -66,149 +71,64 @@ public class FsStoreProductPackage extends BaseEntity
 
     private Integer status;
 
-    List<StoreOrderProductDTO> productList;
-
-    public Integer getStatus() {
-        return status;
-    }
-
-    public void setStatus(Integer status) {
-        this.status = status;
-    }
-
-    public Integer getSort() {
-        return sort;
-    }
-
-    public void setSort(Integer sort) {
-        this.sort = sort;
-    }
-
-    public Integer getCateId() {
-        return cateId;
-    }
-
-    public void setCateId(Integer cateId) {
-        this.cateId = cateId;
-    }
-
-    public String getImages() {
-        return images;
-    }
-
-    public void setImages(String images) {
-        this.images = images;
-    }
-
-
-
-    public List<StoreOrderProductDTO> getProductList() {
-        return productList;
-    }
-
-    public void setProductList(List<StoreOrderProductDTO> productList) {
-        this.productList = productList;
-    }
-
-    public Integer getLimitCount() {
-        return limitCount;
-    }
-
-    public void setLimitCount(Integer limitCount) {
-        this.limitCount = limitCount;
-    }
-
-    public Integer getPayType() {
-        return payType;
-    }
-
-    public void setPayType(Integer payType) {
-        this.payType = payType;
-    }
-
-
-
-    public static long getSerialVersionUID() {
-        return serialVersionUID;
-    }
-
-    public Long getPackageId() {
-        return packageId;
-    }
+    /**
+     * 分期赠送优惠券-是否开启 0未开启 1开启
+     */
+    private Integer icgEnable;
+
+    /**
+     * 分期赠送优惠券-月数
+     */
+    private Integer icgMonth;
+
+
+    /**
+     * 分期赠送优惠券-优惠券列表(数据库存储)
+     */
+    @JsonIgnore
+    private String icgCoupons;
+    /**
+     * 分期赠送优惠券-优惠券列表
+     */
+    private Integer[] icgCouponsId;
+
+    public String getIcgCoupons() {
+        if(icgCouponsId != null && icgCouponsId.length > 0){
+            StringJoiner stringJoiner = new StringJoiner(",");
+            for (Integer id : icgCouponsId) {
+                stringJoiner.add(id.toString());
+            }
+            icgCoupons = stringJoiner.toString();
+        }
+        return icgCoupons;
+    }
+
+    /**
+     * 获取优惠券列表ID
+     * @return 优惠券列表id
+     */
+    public Integer[] getIcgCouponsId() {
+        if (icgCouponsId == null && icgCoupons != null) {
+            String[] split = icgCoupons.split(",");
+            icgCouponsId = new Integer[split.length];
+            for (int i = 0; i < split.length; i++) {
+                icgCouponsId[i] = Integer.parseInt(split[i].trim());
+            }
+        }
+        return icgCouponsId;
+    }
+
+
+    /**
+     * 分期赠送优惠券-url配置
+     */
+    private String icgUrl;
+
+    /**
+     * 分期赠送优惠券-备注
+     */
+    private String icgMark;
 
-    public void setPackageId(Long packageId) {
-        this.packageId = packageId;
-    }
-
-    public String getTitle() {
-        return title;
-    }
-
-    public void setTitle(String title) {
-        this.title = title;
-    }
-
-    public String getDescs() {
-        return descs;
-    }
-
-    public void setDescs(String descs) {
-        this.descs = descs;
-    }
-
-    public String getContent() {
-        return content;
-    }
-
-    public void setContent(String content) {
-        this.content = content;
-    }
-
-    public String getImgUrl() {
-        return imgUrl;
-    }
-
-    public void setImgUrl(String imgUrl) {
-        this.imgUrl = imgUrl;
-    }
-
-    public BigDecimal getMoney() {
-        return money;
-    }
-
-    public void setMoney(BigDecimal money) {
-        this.money = money;
-    }
-
-    public BigDecimal getPayMoney() {
-        return payMoney;
-    }
-
-    public void setPayMoney(BigDecimal payMoney) {
-        this.payMoney = payMoney;
-    }
-
-    public Long getCompanyId() {
-        return companyId;
-    }
-
-    public void setCompanyId(Long companyId) {
-        this.companyId = companyId;
-    }
-
-    public Long getDeptId() {
-        return deptId;
-    }
-
-    public void setDeptId(Long deptId) {
-        this.deptId = deptId;
-    }
-
-    public String getProducts() {
-        return products;
-    }
+    List<StoreOrderProductDTO> productList;
 
-    public void setProducts(String products) {
-        this.products = products;
-    }
 }

+ 43 - 0
fs-service-system/src/main/java/com/fs/store/dto/ClientCredGrantReqDTO.java

@@ -0,0 +1,43 @@
+package com.fs.store.dto;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 客户端凭证授权请求DTO
+ * <p>
+ * 用于构建客户端凭证授权模式下的请求参数。
+ * </p>
+ *
+ * @author xdd
+ * @version 1.0
+ * @since 2025-02-27
+ */
+@Data
+public class ClientCredGrantReqDTO implements Serializable {
+
+    /**
+     * 授权类型
+     * <p>
+     * 固定值 "client_credential",表示客户端凭证授权模式。
+     * </p>
+     */
+    private String grant_type;
+
+    /**
+     * 应用ID
+     * <p>
+     * 应用程序的唯一标识符。
+     * </p>
+     */
+    private String appid;
+
+    /**
+     * 应用密钥
+     * <p>
+     * 应用程序的密钥,用于验证请求的合法性。  <b>注意:应妥善保管,避免泄露。</b>
+     * </p>
+     */
+    private String secret;
+}

+ 52 - 0
fs-service-system/src/main/java/com/fs/store/dto/MiniGramSubsMsgResultDTO.java

@@ -0,0 +1,52 @@
+package com.fs.store.dto;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 消息发送结果DTO
+ * <p>
+ * 用于封装消息发送接口的响应结果。
+ * </p>
+ *
+ * @author xdd
+ * @version 1.0
+ * @since 2025-02-27
+ */
+@Data
+public class MiniGramSubsMsgResultDTO implements Serializable {
+
+    /**
+     * 错误码
+     * <p>
+     * 返回码,0表示成功,其他值表示失败。
+     * </p>
+     */
+    private Integer errcode;
+
+    /**
+     * 错误信息
+     * <p>
+     * 返回码的文本描述,成功时为 "ok",失败时包含具体的错误信息。
+     * </p>
+     */
+    private String errmsg;
+
+    /**
+     * 消息ID
+     * <p>
+     * 消息的唯一标识符,成功发送时返回。
+     * </p>
+     *  <p>
+     *     注意:这个字段可能为null,发送失败时,此字段可能为null
+     *  </p>
+     */
+    private Long msgid;
+
+    /**
+     * rid  请求的唯一标识
+     * 仅在发生错误时出现
+     */
+    private String rid;
+}

+ 67 - 0
fs-service-system/src/main/java/com/fs/store/dto/TemplateMessageSendRequestDTO.java

@@ -0,0 +1,67 @@
+package com.fs.store.dto;
+
+import lombok.Data;
+import java.util.Map;
+
+/**
+ * 模板消息发送请求DTO
+ * <p>
+ * 用于构建发送模板消息的请求体。
+ * </p>
+ *
+ * @author xdd
+ * @version 1.0
+ * @since 2025-02-27
+ */
+@Data
+public class TemplateMessageSendRequestDTO {
+
+    /**
+     * 接收者openid
+     * <p>
+     * 用户的唯一标识符。
+     * </p>
+     */
+    private String touser;
+
+    /**
+     * 模板ID
+     * <p>
+     * 所需下发的模板消息的id。
+     * </p>
+     */
+    private String template_id;
+
+    /**
+     * 跳转页面
+     * <p>
+     * 点击模板消息后跳转的页面,可以为空。
+     * </p>
+     */
+    private String page;
+
+    /**
+     * 模板数据
+     * <p>
+     * 模板内容,键值对形式,键名为模板中的变量名,值为要替换的内容。
+     * </p>
+     */
+    private Map<String, TemplateDataValue> data;
+
+    /**
+     * 模板数据值对象
+     * <p>
+     * 内部类,用于表示模板数据中的单个值。
+     * </p>
+     */
+    @Data
+    public static class TemplateDataValue {
+        /**
+         * 模板变量值
+         * <p>
+         * 要替换模板变量的具体内容。
+         * </p>
+         */
+        private String value;
+    }
+}

+ 36 - 0
fs-service-system/src/main/java/com/fs/store/dto/WeXinAccessTokenDTO.java

@@ -0,0 +1,36 @@
+package com.fs.store.dto;
+
+import lombok.Data;
+
+/**
+ * 访问令牌DTO
+ * <p>
+ * 用于存储从认证服务器获取的访问令牌及其相关信息。
+ * </p>
+ *
+ * @author xdd
+ * @version 1.0
+ * @since 2025-02-27
+ */
+@Data
+public class WeXinAccessTokenDTO {
+
+    /**
+     * 访问令牌
+     * <p>
+     * 用于访问受保护资源的令牌。
+     * </p>
+     */
+    private String accessToken;
+
+    /**
+     * 过期时间(秒)
+     * <p>
+     * 访问令牌的有效时间,单位为秒。
+     * </p>
+     */
+    private Integer expiresIn;
+
+    private Long errcode;
+    private String errmsg;
+}

+ 33 - 0
fs-service-system/src/main/java/com/fs/store/enums/IcgProcessStatusEnum.java

@@ -0,0 +1,33 @@
+package com.fs.store.enums;
+
+import lombok.Getter;
+
+/**
+ * 定时发放优惠券处理枚举类
+ */
+@Getter
+public enum IcgProcessStatusEnum {
+    PENDING(0, "待处理"),
+    PROCESSING(1, "正在处理"),
+    SUCCESS(2, "成功"),
+    FAILED(-1, "失败"),
+    REJECTED_OR_RETURNED(3, "用户拒签或者退货"),
+    FINISHED(4, "已完成");
+
+    private final Integer code;
+    private final String description;
+
+    IcgProcessStatusEnum(Integer code, String description) {
+        this.code = code;
+        this.description = description;
+    }
+
+    public static IcgProcessStatusEnum fromCode(Integer code) {
+        for (IcgProcessStatusEnum status : IcgProcessStatusEnum.values()) {
+            if (status.getCode().equals(code)) {
+                return status;
+            }
+        }
+        return null;
+    }
+}

+ 30 - 0
fs-service-system/src/main/java/com/fs/store/enums/IcgScheduleOperationTypeEnum.java

@@ -0,0 +1,30 @@
+package com.fs.store.enums;
+
+import lombok.Getter;
+
+@Getter
+public enum IcgScheduleOperationTypeEnum {
+    NORMAL("NORMAL"),
+    ERROR("ERROR"),
+    RETRY("RETRY");
+    private final String code;
+
+    IcgScheduleOperationTypeEnum(String code) {
+        this.code = code;
+    }
+
+
+    public static IcgScheduleOperationTypeEnum fromCode(String code) {
+        for (IcgScheduleOperationTypeEnum type : IcgScheduleOperationTypeEnum.values()) {
+            if (type.getCode().equalsIgnoreCase(code)) {
+                return type;
+            }
+        }
+        return null;
+    }
+
+     @Override
+    public String toString() {
+        return code;
+    }
+}

+ 44 - 0
fs-service-system/src/main/java/com/fs/store/enums/MiniAppNotifyTaskStatusEnum.java

@@ -0,0 +1,44 @@
+package com.fs.store.enums;
+
+
+import lombok.Getter;
+
+@Getter
+public enum MiniAppNotifyTaskStatusEnum {
+    /**
+     * 待执行
+     */
+    WAITING(0),
+    /**
+     * 执行中
+     */
+    RUNNING(1),
+    /**
+     * 执行成功
+     */
+    SUCCESS(2),
+    /**
+     * 执行失败
+     */
+    FAILED(3),
+    /**
+     * 已取消
+     */
+    CANCELED(4);
+
+    private final int value;
+
+    MiniAppNotifyTaskStatusEnum(int value) {
+        this.value = value;
+    }
+
+
+    public static MiniAppNotifyTaskStatusEnum fromValue(int value) {
+        for (MiniAppNotifyTaskStatusEnum status : values()) {
+            if (status.getValue() == value) {
+                return status;
+            }
+        }
+        throw new IllegalArgumentException("Invalid value: " + value);
+    }
+}

+ 60 - 0
fs-service-system/src/main/java/com/fs/store/mapper/FsCouponScheduleLogMapper.java

@@ -0,0 +1,60 @@
+package com.fs.store.mapper;
+
+import com.fs.store.domain.FsCouponScheduleLog;
+import org.apache.ibatis.annotations.*;
+
+import java.util.List;
+
+/**
+ * 优惠券发放计划日志表Mapper接口
+ *
+ * @author example
+ */
+@Mapper
+public interface FsCouponScheduleLogMapper {
+
+    /**
+     * 根据日志ID查询日志信息
+     *
+     * @param logId 日志ID
+     * @return 日志信息
+     */
+    @Select("SELECT * FROM fs_coupon_schedule_log WHERE log_id = #{logId}")
+    FsCouponScheduleLog selectByLogId(@Param("logId") Integer logId);
+
+    /**
+     * 插入一条日志记录
+     *
+     * @param log 日志信息
+     * @return 插入的行数
+     */
+    @Insert("INSERT INTO fs_coupon_schedule_log (schedule_id, user_id, order_id, status_before, status_after, send_time_before, send_time_after, actual_send_time, error_message, retry_count_before, retry_count_after, operation_type, operation_time, operator, remark) " +
+            "VALUES (#{scheduleId}, #{userId}, #{orderId}, #{statusBefore}, #{statusAfter}, #{sendTimeBefore}, #{sendTimeAfter}, #{actualSendTime}, #{errorMessage}, #{retryCountBefore}, #{retryCountAfter}, #{operationType}, #{operationTime}, #{operator}, #{remark})")
+    int insert(FsCouponScheduleLog log);
+
+    /**
+     * 更新日志信息
+     *
+     * @param log 日志信息
+     * @return 更新的行数
+     */
+    @Update("UPDATE fs_coupon_schedule_log SET schedule_id = #{scheduleId}, user_id = #{userId}, order_id = #{orderId}, status_before = #{statusBefore}, status_after = #{statusAfter}, send_time_before = #{sendTimeBefore}, send_time_after = #{sendTimeAfter}, actual_send_time = #{actualSendTime}, error_message = #{errorMessage}, retry_count_before = #{retryCountBefore}, retry_count_after = #{retryCountAfter}, operation_type = #{operationType}, operation_time = #{operationTime}, operator = #{operator}, remark = #{remark} WHERE log_id = #{logId}")
+    int update(FsCouponScheduleLog log);
+
+    /**
+     * 根据schedule_id查询日志列表
+     *
+     * @param scheduleId fs_coupon_schedule 表的主键ID
+     * @return 日志列表
+     */
+    @Select("SELECT * FROM fs_coupon_schedule_log WHERE schedule_id = #{scheduleId}")
+    List<FsCouponScheduleLog> selectByScheduleId(@Param("scheduleId") Integer scheduleId);
+
+    /**
+     * 批量插入日志记录
+     *
+     * @param logs 日志信息列表
+     * @return 插入的行数
+     */
+    int batchInsert(@Param("logs") List<FsCouponScheduleLog> logs);
+}

+ 83 - 0
fs-service-system/src/main/java/com/fs/store/mapper/FsCouponScheduleMapper.java

@@ -0,0 +1,83 @@
+package com.fs.store.mapper;
+
+import com.fs.store.domain.FsCouponSchedule;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * 定时发放优惠券队列Mapper接口
+ *
+ * @author fs
+ * @date 2025-03-08
+ */
+@Mapper
+public interface FsCouponScheduleMapper
+{
+    /**
+     * 查询定时发放优惠券队列
+     *
+     * @param id 定时发放优惠券队列ID
+     * @return 定时发放优惠券队列
+     */
+    public FsCouponSchedule selectFsCouponScheduleById(Long id);
+
+    /**
+     * 查询定时发放优惠券队列列表
+     *
+     * @param fsCouponSchedule 定时发放优惠券队列
+     * @return 定时发放优惠券队列集合
+     */
+    public List<FsCouponSchedule> selectFsCouponScheduleList(FsCouponSchedule fsCouponSchedule);
+
+    /**
+     * 新增定时发放优惠券队列
+     *
+     * @param fsCouponSchedule 定时发放优惠券队列
+     * @return 结果
+     */
+    public int insertFsCouponSchedule(FsCouponSchedule fsCouponSchedule);
+
+    /**
+     * 修改定时发放优惠券队列
+     *
+     * @param fsCouponSchedule 定时发放优惠券队列
+     * @return 结果
+     */
+    public int updateFsCouponSchedule(FsCouponSchedule fsCouponSchedule);
+    /**
+     * 批量修改定时发放优惠券队列
+     *
+     * @param fsCouponSchedule 定时发放优惠券队列
+     * @return 结果
+     */
+    public int updateBatchFsCouponSchedule(@Param("list") List<FsCouponSchedule> fsCouponSchedule);
+    /**
+     * 删除定时发放优惠券队列
+     *
+     * @param id 定时发放优惠券队列ID
+     * @return 结果
+     */
+    public int deleteFsCouponScheduleById(Long id);
+
+    /**
+     * 批量删除定时发放优惠券队列
+     *
+     * @param ids 需要删除的数据ID
+     * @return 结果
+     */
+    public int deleteFsCouponScheduleByIds(Long[] ids);
+
+    /**
+     * 查询待发送优惠券
+     */
+    List<FsCouponSchedule> selectPendingCouponList();
+
+    /**
+     * 查询待发送优惠券通过订单id
+     * @param orderId
+     * @return
+     */
+    FsCouponSchedule selectFsCouponScheduleByOrderId(@Param("orderId") String orderId);
+}

+ 81 - 0
fs-service-system/src/main/java/com/fs/store/mapper/FsMiniprogramSubNotifyTaskMapper.java

@@ -0,0 +1,81 @@
+package com.fs.store.mapper;
+
+import com.fs.store.domain.FsMiniprogramSubNotifyTask;
+import org.apache.ibatis.annotations.*;
+import com.alibaba.fastjson.JSONObject;
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * FsMiniprogramSubNotifyTaskMapper接口,用于定义微信小程序子通知任务的数据访问方法。
+ *
+ * @author xdd
+ * @version 1.0.0
+ * @since 2025-03-10
+ */
+@Mapper
+public interface FsMiniprogramSubNotifyTaskMapper {
+
+    /**
+     * 根据ID查询任务
+     * @param id 任务ID
+     * @return 任务实体
+     */
+    @Select("SELECT * FROM fs_miniprogram_sub_notify_task WHERE id = #{id}")
+    FsMiniprogramSubNotifyTask findById(Long id);
+
+    /**
+     * 插入新任务
+     * @param task 任务实体
+     * @return
+     */
+    @Insert("INSERT INTO fs_miniprogram_sub_notify_task (task_name, template_id, touser, page, `data`, status, " +
+            "retry_count, max_retries, request_params,request_body, response_body, error_message, create_time, update_time) " +
+            "VALUES (#{taskName}, #{templateId}, #{touser}, #{page}, #{data}, #{status}, #{retryCount}, " +
+            "#{maxRetries}, #{requestParams},#{requestBody}, #{responseBody}, #{errorMessage}, #{createTime}, #{updateTime})")
+    @Options(useGeneratedKeys = true, keyProperty = "id")
+    int insert(FsMiniprogramSubNotifyTask task);
+    /**
+     * 更新任务状态
+     *
+     * @param id     任务ID
+     * @param status 任务状态
+     * @param retryCount     重试次数
+     * @param responseBody     响应体
+     * @param errorMessage      错误信息
+     * @param updateTime     更新时间
+     * @return 受影响的行数
+     */
+    @Update("<script>" +
+            "UPDATE fs_miniprogram_sub_notify_task " +
+            "SET update_time = #{updateTime} " +
+            "<if test='status != null'>, status = #{status}</if>" +
+            "<if test='retryCount != null'>, retry_count = #{retryCount}</if>" +
+            "<if test='responseBody != null'>, response_body = #{responseBody}</if>" +
+            "<if test='errorMessage != null'>, error_message = #{errorMessage}</if>" +
+            "WHERE id = #{id}" +
+            "</script>")
+    int updateStatus(@Param("id") Long id, @Param("status") Integer status,
+                     @Param("retryCount") Integer retryCount,
+                     @Param("responseBody") String responseBody, @Param("errorMessage")String errorMessage,
+                     @Param("updateTime") LocalDateTime updateTime);
+
+    /**
+     * 查询所有待处理数据
+     * @return
+     */
+    @Select("SELECT * FROM fs_miniprogram_sub_notify_task WHERE retry_count<3 and status in (0,3)")
+    List<FsMiniprogramSubNotifyTask> selectPendingData();
+
+    /**
+     * 批量更新数据
+     * @param pendingData 更新的数据
+     */
+    void updateBatchById(@Param("list") List<FsMiniprogramSubNotifyTask> pendingData);
+
+    /**
+     * 批量插入数据
+     * @param subNotifyTasks 数据
+     */
+    void insertBatch(List<FsMiniprogramSubNotifyTask> subNotifyTasks);
+}

+ 17 - 0
fs-service-system/src/main/java/com/fs/store/mapper/FsStoreCouponIssueMapper.java

@@ -1,6 +1,8 @@
 package com.fs.store.mapper;
 
 import java.util.List;
+
+import com.fs.store.domain.FsStoreCoupon;
 import com.fs.store.domain.FsStoreCouponIssue;
 import com.fs.store.param.FsCouponIssueParam;
 import com.fs.store.vo.FsStoreCouponIssueVO;
@@ -130,4 +132,19 @@ public interface FsStoreCouponIssueMapper
             " order by c.coupon_price "+
             "</script>"})
     List<FsStoreCouponIssueVO> selectFsStoreCouponIssueListVO(@Param("maps")FsStoreCouponIssue fsStoreCouponIssue);
+
+
+    /**
+     * 返回所有可用套餐优惠券
+     * @return List<FsStoreCouponIssueVO>
+     */
+    List<FsStoreCouponIssueVO> listAllAvailable();
+
+    /**
+     * 根据优惠券id查询已发布的优惠券
+     * @param couponId
+     * @return
+     */
+    @Select("select * from fs_store_coupon_issue where coupon_id=${couponId} and status=1 and is_del=0 and limit_time > now() limit 1")
+    FsStoreCouponIssue selectFsStoreCouponIssueByCouponId(@Param("couponId") Long couponId);
 }

+ 11 - 0
fs-service-system/src/main/java/com/fs/store/mapper/FsStoreCouponMapper.java

@@ -1,7 +1,10 @@
 package com.fs.store.mapper;
 
 import java.util.List;
+import java.util.Set;
+
 import com.fs.store.domain.FsStoreCoupon;
+import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 
 /**
@@ -63,4 +66,12 @@ public interface FsStoreCouponMapper
 
     @Select("select * from fs_store_coupon where find_in_set(coupon_id,#{ids})")
     List<FsStoreCoupon> selectFsStoreCouponByIds(String ids);
+
+    /**
+     * 查询优惠券名称,通过ids
+     * @param couponId id
+     * @return List<String>
+     */
+    String selectFsStoreCouponNameById(@Param("couponId") Long couponId);
+
 }

+ 82 - 0
fs-service-system/src/main/java/com/fs/store/service/IFsCouponScheduleService.java

@@ -0,0 +1,82 @@
+package com.fs.store.service;
+
+
+import com.fs.store.domain.FsCouponSchedule;
+
+import java.util.List;
+
+/**
+ * 定时发放优惠券队列Service接口
+ *
+ * @author fs
+ * @date 2025-03-08
+ */
+public interface IFsCouponScheduleService
+{
+    /**
+     * 查询定时发放优惠券队列
+     *
+     * @param id 定时发放优惠券队列ID
+     * @return 定时发放优惠券队列
+     */
+    public FsCouponSchedule selectFsCouponScheduleById(Long id);
+
+    /**
+     * 查询定时发放优惠券队列通过订单id
+     * @param orderId 订单id
+     * @return FsCouponSchedule
+     */
+    FsCouponSchedule selectFsCouponScheduleByOrderId(String orderId);
+
+    /**
+     * 退款将定时发放优惠券的状态设置为已退款
+     * @param orderCode 订单号码
+     */
+    void setScheduleCouponRefund(String orderCode);
+    /**
+     * 查询定时发放优惠券队列列表
+     *
+     * @param fsCouponSchedule 定时发放优惠券队列
+     * @return 定时发放优惠券队列集合
+     */
+    public List<FsCouponSchedule> selectFsCouponScheduleList(FsCouponSchedule fsCouponSchedule);
+
+    /**
+     * 新增定时发放优惠券队列
+     *
+     * @param fsCouponSchedule 定时发放优惠券队列
+     * @return 结果
+     */
+    public int insertFsCouponSchedule(FsCouponSchedule fsCouponSchedule);
+
+    /**
+     * 修改定时发放优惠券队列
+     *
+     * @param fsCouponSchedule 定时发放优惠券队列
+     * @return 结果
+     */
+    public int updateFsCouponSchedule(FsCouponSchedule fsCouponSchedule);
+
+    /**
+     * 批量删除定时发放优惠券队列
+     *
+     * @param ids 需要删除的定时发放优惠券队列ID
+     * @return 结果
+     */
+    public int deleteFsCouponScheduleByIds(Long[] ids);
+
+    /**
+     * 删除定时发放优惠券队列信息
+     *
+     * @param id 定时发放优惠券队列ID
+     * @return 结果
+     */
+    public int deleteFsCouponScheduleById(Long id);
+
+    /**
+     * 发放优惠券
+     */
+    void issueCoupon();
+
+
+}

+ 17 - 8
fs-service-system/src/main/java/com/fs/store/service/IFsStoreCouponIssueService.java

@@ -10,15 +10,15 @@ import com.fs.store.vo.FsStoreCouponIssueVO;
 
 /**
  * 优惠券领取Service接口
- * 
+ *
  * @author fs
  * @date 2022-03-15
  */
-public interface IFsStoreCouponIssueService 
+public interface IFsStoreCouponIssueService
 {
     /**
      * 查询优惠券领取
-     * 
+     *
      * @param id 优惠券领取ID
      * @return 优惠券领取
      */
@@ -26,7 +26,7 @@ public interface IFsStoreCouponIssueService
 
     /**
      * 查询优惠券领取列表
-     * 
+     *
      * @param fsStoreCouponIssue 优惠券领取
      * @return 优惠券领取集合
      */
@@ -34,7 +34,7 @@ public interface IFsStoreCouponIssueService
 
     /**
      * 新增优惠券领取
-     * 
+     *
      * @param fsStoreCouponIssue 优惠券领取
      * @return 结果
      */
@@ -42,7 +42,7 @@ public interface IFsStoreCouponIssueService
 
     /**
      * 修改优惠券领取
-     * 
+     *
      * @param fsStoreCouponIssue 优惠券领取
      * @return 结果
      */
@@ -50,7 +50,7 @@ public interface IFsStoreCouponIssueService
 
     /**
      * 批量删除优惠券领取
-     * 
+     *
      * @param ids 需要删除的优惠券领取ID
      * @return 结果
      */
@@ -58,7 +58,7 @@ public interface IFsStoreCouponIssueService
 
     /**
      * 删除优惠券领取信息
-     * 
+     *
      * @param id 优惠券领取ID
      * @return 结果
      */
@@ -73,4 +73,13 @@ public interface IFsStoreCouponIssueService
     FsStoreCouponIssueVO selectFsStoreCouponIssueVOById(Long id);
 
     List<FsStoreCouponIssueVO> selectFsStoreCouponIssueListVO(FsStoreCouponIssue fsStoreCouponIssue);
+
+    List<FsStoreCouponIssueVO> listAllAvailable();
+
+    /**
+     * 查询优惠券领取列表
+     * @param couponId
+     * @return FsStoreCouponIssue
+     */
+    FsStoreCouponIssue selectFsStoreCouponIssueByCouponId(Long couponId);
 }

+ 1 - 0
fs-service-system/src/main/java/com/fs/store/service/IFsStoreCouponService.java

@@ -61,4 +61,5 @@ public interface IFsStoreCouponService
     public int deleteFsStoreCouponById(Long couponId);
 
     List<FsStoreCoupon> selectFsStoreCouponByIds(String ids);
+
 }

+ 28 - 0
fs-service-system/src/main/java/com/fs/store/service/IWechatMiniProgrService.java

@@ -0,0 +1,28 @@
+package com.fs.store.service;
+
+import com.fs.store.dto.ClientCredGrantReqDTO;
+import com.fs.store.dto.MiniGramSubsMsgResultDTO;
+import com.fs.store.dto.TemplateMessageSendRequestDTO;
+import com.fs.store.dto.WeXinAccessTokenDTO;
+
+/**
+ * 小程序调用相关
+ */
+public interface IWechatMiniProgrService {
+
+    /**
+     * 获取稳定的token
+     *
+     * @param param 请求参数
+     * @return {@link WeXinAccessTokenDTO}
+     */
+    WeXinAccessTokenDTO getStableToken(ClientCredGrantReqDTO param);
+
+    /**
+     * 微信小程序发送订阅消息
+     *
+     * @param param 请求参数
+     * @return {@link MiniGramSubsMsgResultDTO}
+     */
+    MiniGramSubsMsgResultDTO sendSubscribeMsg(String accessToken,TemplateMessageSendRequestDTO param);
+}

+ 316 - 0
fs-service-system/src/main/java/com/fs/store/service/impl/FsCouponScheduleServiceImpl.java

@@ -0,0 +1,316 @@
+package com.fs.store.service.impl;
+
+import java.time.LocalDateTime;
+import java.util.*;
+
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.alibaba.fastjson.JSON;
+import com.fs.common.core.domain.R;
+import com.fs.common.utils.DateUtils;
+import com.fs.store.domain.*;
+import com.fs.store.dto.TemplateMessageSendRequestDTO;
+import com.fs.store.enums.IcgProcessStatusEnum;
+import com.fs.store.enums.IcgScheduleOperationTypeEnum;
+import com.fs.store.enums.MiniAppNotifyTaskStatusEnum;
+import com.fs.store.mapper.*;
+import com.fs.store.param.FsStoreCouponReceiveParam;
+import com.fs.store.service.IFsCouponScheduleService;
+import com.fs.store.service.IFsStoreCouponIssueService;
+import com.github.pagehelper.util.StringUtil;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.CollectionUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Propagation;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * 定时发放优惠券队列Service业务层处理
+ *
+ * @author fs
+ * @date 2025-03-08
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class FsCouponScheduleServiceImpl implements IFsCouponScheduleService
+{
+    /**
+     * 优惠券定时任务
+     */
+    private final FsCouponScheduleMapper fsCouponScheduleMapper;
+    /**
+     * 优惠券领取Service接口
+     */
+    private final IFsStoreCouponIssueService fsStoreCouponIssueService;
+    /**
+     * 优惠券发放计划日志表Mapper接口
+     */
+    private final FsCouponScheduleLogMapper fsCouponScheduleLogMapper;
+    /**
+     * 优惠券表Mapper接口
+     */
+    private final FsStoreCouponMapper fsStoreCouponMapper;
+
+    /**
+     * 用户表Mapper接口
+     */
+    private final FsUserMapper fsUserMapper;
+
+    /**
+     * 优惠券发放计划日志表Mapper接口
+     */
+    private final FsMiniprogramSubNotifyTaskMapper fsMiniprogramSubNotifyTaskMapper;
+
+    /**
+     * 小程序消息通知任务名称
+     */
+    private final String WX_MINI_APP_NOTIFY_TASK_NAME = "优惠券发放";
+
+    /**
+     * 小程序消息通知模板ID
+     */
+    private final String WX_MINI_APP_NOTIFY_TEMPLATE_ID = "5ZSzz2nPmJo9EuenZa78mQPScoOMc84LnEfEpV0-i04";
+
+    /**
+     * 查询定时发放优惠券队列
+     *
+     * @param id 定时发放优惠券队列ID
+     * @return 定时发放优惠券队列
+     */
+    @Override
+    public FsCouponSchedule selectFsCouponScheduleById(Long id)
+    {
+        return fsCouponScheduleMapper.selectFsCouponScheduleById(id);
+    }
+
+    @Override
+    public FsCouponSchedule selectFsCouponScheduleByOrderId(String orderId) {
+        return fsCouponScheduleMapper.selectFsCouponScheduleByOrderId(orderId);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
+    public void setScheduleCouponRefund(String orderCode) {
+        FsCouponSchedule fsCouponSchedule = fsCouponScheduleMapper.selectFsCouponScheduleByOrderId(orderCode);
+        if(ObjectUtil.isNotNull(fsCouponSchedule)){
+            fsCouponSchedule.setStatus(IcgProcessStatusEnum.REJECTED_OR_RETURNED.getCode());
+            fsCouponScheduleMapper.updateFsCouponSchedule(fsCouponSchedule);
+        }
+    }
+
+    /**
+     * 查询定时发放优惠券队列列表
+     *
+     * @param fsCouponSchedule 定时发放优惠券队列
+     * @return 定时发放优惠券队列
+     */
+    @Override
+    public List<FsCouponSchedule> selectFsCouponScheduleList(FsCouponSchedule fsCouponSchedule)
+    {
+        return fsCouponScheduleMapper.selectFsCouponScheduleList(fsCouponSchedule);
+    }
+
+    /**
+     * 新增定时发放优惠券队列
+     *
+     * @param fsCouponSchedule 定时发放优惠券队列
+     * @return 结果
+     */
+    @Override
+    public int insertFsCouponSchedule(FsCouponSchedule fsCouponSchedule)
+    {
+        fsCouponSchedule.setCreateTime(DateUtils.getNowDate());
+        return fsCouponScheduleMapper.insertFsCouponSchedule(fsCouponSchedule);
+    }
+
+    /**
+     * 修改定时发放优惠券队列
+     *
+     * @param fsCouponSchedule 定时发放优惠券队列
+     * @return 结果
+     */
+    @Override
+    public int updateFsCouponSchedule(FsCouponSchedule fsCouponSchedule)
+    {
+        fsCouponSchedule.setUpdateTime(DateUtils.getNowDate());
+        return fsCouponScheduleMapper.updateFsCouponSchedule(fsCouponSchedule);
+    }
+
+    /**
+     * 批量删除定时发放优惠券队列
+     *
+     * @param ids 需要删除的定时发放优惠券队列ID
+     * @return 结果
+     */
+    @Override
+    public int deleteFsCouponScheduleByIds(Long[] ids)
+    {
+        return fsCouponScheduleMapper.deleteFsCouponScheduleByIds(ids);
+    }
+
+    /**
+     * 删除定时发放优惠券队列信息
+     *
+     * @param id 定时发放优惠券队列ID
+     * @return 结果
+     */
+    @Override
+    public int deleteFsCouponScheduleById(Long id)
+    {
+        return fsCouponScheduleMapper.deleteFsCouponScheduleById(id);
+    }
+
+    /**
+     * 发放优惠券
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
+    public void issueCoupon() {
+        List<FsCouponSchedule> fsCouponSchedules = fsCouponScheduleMapper.selectPendingCouponList();
+        List<FsCouponScheduleLog> logList = new ArrayList<>();
+        List<FsMiniprogramSubNotifyTask> subNotifyTasks = new ArrayList<>();
+
+        for (FsCouponSchedule fsCouponSchedule : fsCouponSchedules) {
+
+            FsCouponScheduleLog scheduleLog = new FsCouponScheduleLog();
+
+            scheduleLog.setScheduleId(fsCouponSchedule.getId());
+            scheduleLog.setOrderId(fsCouponSchedule.getOrderId());
+            scheduleLog.setUserId(fsCouponSchedule.getUserId());
+            scheduleLog.setStatusBefore(fsCouponSchedule.getStatus());
+            scheduleLog.setSendTimeBefore(fsCouponSchedule.getSendTime());
+            scheduleLog.setRetryCountBefore(fsCouponSchedule.getRetryCount());
+            scheduleLog.setOperator("SYSTEM");
+            scheduleLog.setOperationTime(LocalDateTime.now());
+            // 获取优惠券名称
+            Set<Long> couponIds = fsCouponSchedule.getCouponId();
+            if(CollectionUtils.isEmpty(couponIds)){
+                log.info("当前优惠券没有被选择!已经跳过");
+                continue;
+            }
+            // 优惠券id (有多张这里只取第一张)
+            Long couponId = couponIds.stream().findFirst().get();
+            // 找到该优惠券
+            FsStoreCouponIssue fsStoreCouponIssue = fsStoreCouponIssueService.selectFsStoreCouponIssueByCouponId(couponId);
+            Long userId = fsCouponSchedule.getUserId();
+            // 领取优惠券
+            FsStoreCouponReceiveParam param = new FsStoreCouponReceiveParam();
+            param.setId(fsStoreCouponIssue.getId());
+            R receive = fsStoreCouponIssueService.receive(String.valueOf(userId), param);
+
+            Set<String> operationTypeSet = new HashSet<>();
+            if(fsCouponSchedule.getRetryCount()>0) {
+                operationTypeSet.add(IcgScheduleOperationTypeEnum.RETRY.getCode());
+            }
+            // 如果不等于200表示失败了
+            if(ObjectUtil.notEqual(200,receive.get("code"))){
+                fsCouponSchedule.setErrorMessage(JSON.toJSONString(receive));
+                fsCouponSchedule.setStatus(IcgProcessStatusEnum.FAILED.getCode());
+                fsCouponSchedule.setRetryCount(fsCouponSchedule.getRetryCount()+1);
+
+                scheduleLog.setStatusAfter(IcgProcessStatusEnum.FAILED.getCode());
+                scheduleLog.setErrorMessage(fsCouponSchedule.getErrorMessage());
+
+                operationTypeSet.add(IcgScheduleOperationTypeEnum.ERROR.getCode());
+            } else {
+                if(fsCouponSchedule.getCount() >=  fsCouponSchedule.getMonth()){
+                    fsCouponSchedule.setStatus(IcgProcessStatusEnum.FINISHED.getCode());
+                } else {
+                    fsCouponSchedule.setStatus(IcgProcessStatusEnum.SUCCESS.getCode());
+                }
+                fsCouponSchedule.setActualSendTime(LocalDateTime.now());
+                fsCouponSchedule.setSendTime(fsCouponSchedule.getActualSendTime().plusMonths(1));
+                fsCouponSchedule.setCount(fsCouponSchedule.getCount()+1);
+                // 清空错误信息
+                fsCouponSchedule.setErrorMessage(null);
+                scheduleLog.setStatusAfter(IcgProcessStatusEnum.SUCCESS.getCode());
+                scheduleLog.setActualSendTime(fsCouponSchedule.getActualSendTime());
+                scheduleLog.setSendTimeAfter(fsCouponSchedule.getSendTime());
+                operationTypeSet.add(IcgScheduleOperationTypeEnum.NORMAL.getCode());
+
+
+                // 小程序消息通知
+                FsMiniprogramSubNotifyTask notifyTask = new FsMiniprogramSubNotifyTask();
+                notifyTask.setTaskName(WX_MINI_APP_NOTIFY_TASK_NAME);
+                notifyTask.setTemplateId(WX_MINI_APP_NOTIFY_TEMPLATE_ID);
+                FsUser fsUser = fsUserMapper.selectFsUserById(fsCouponSchedule.getUserId());
+                String maOpenId = fsUser.getMaOpenId();
+                notifyTask.setTouser(maOpenId);
+                notifyTask.setPage(fsCouponSchedule.getPage());
+
+                notifyTask.setCreateTime(LocalDateTime.now());
+                // 状态等待执行
+                notifyTask.setStatus(MiniAppNotifyTaskStatusEnum.WAITING.getValue());
+                notifyTask.setRetryCount(0);
+                notifyTask.setMaxRetries(3);
+                notifyTask.setUpdateTime(LocalDateTime.now());
+                Map<String, TemplateMessageSendRequestDTO.TemplateDataValue> data = new HashMap<>();
+
+
+                TemplateMessageSendRequestDTO.TemplateDataValue couponName = new TemplateMessageSendRequestDTO.TemplateDataValue();
+                FsStoreCoupon fsStoreCoupon = fsStoreCouponMapper.selectFsStoreCouponById(couponId);
+                if(ObjectUtil.isNotNull(fsStoreCoupon)){
+                    String couponNameById = fsStoreCoupon.getTitle();
+                    if(StringUtil.isNotEmpty(couponNameById)){
+                        couponName.setValue(couponNameById);
+                    }
+                }
+
+                // 获取优惠券时间范围
+                TemplateMessageSendRequestDTO.TemplateDataValue couponValidate = new TemplateMessageSendRequestDTO.TemplateDataValue();
+
+
+                if(ObjectUtil.isNotNull(fsStoreCouponIssue)){
+                    Date beginTime = fsStoreCouponIssue.getStartTime();
+                    Date endTime = fsStoreCouponIssue.getLimitTime();
+                    String beginTimeStr = DateUtil.format(beginTime, "yyyy-MM-dd");
+                    String endTimeStr = DateUtil.format(endTime, "yyyy-MM-dd");
+
+                    if(StringUtil.isNotEmpty(beginTimeStr) && StringUtil.isNotEmpty(endTimeStr)){
+                        couponValidate.setValue(beginTimeStr + " ~ " + endTimeStr);
+                    } else {
+                        couponValidate.setValue("#");
+                    }
+                }
+                // 备注消息
+                TemplateMessageSendRequestDTO.TemplateDataValue couponMark = new TemplateMessageSendRequestDTO.TemplateDataValue();
+                couponMark.setValue(fsCouponSchedule.getRemark());
+
+                // 面值
+                TemplateMessageSendRequestDTO.TemplateDataValue couponPrice = new TemplateMessageSendRequestDTO.TemplateDataValue();
+                if(ObjectUtil.isNotNull(fsStoreCoupon)){
+                    if(ObjectUtil.isNotNull(fsStoreCoupon.getCouponPrice())){
+                        couponPrice.setValue(fsStoreCoupon.getCouponPrice().toPlainString());
+                    }
+                }
+
+                data.put("thing1",couponName);
+                data.put("time2",couponValidate);
+                data.put("thing3",couponMark);
+                data.put("amount4",couponPrice);
+
+                notifyTask.setData(JSON.toJSONString(data));
+
+                subNotifyTasks.add(notifyTask);
+            }
+            fsCouponSchedule.setUpdateTime(new Date());
+
+            scheduleLog.setOperationType(String.join(",",operationTypeSet));
+            logList.add(scheduleLog);
+        }
+        // 批量更新发放优惠券队列表
+        if(CollectionUtils.isNotEmpty(fsCouponSchedules)){
+            fsCouponScheduleMapper.updateBatchFsCouponSchedule(fsCouponSchedules);
+        }
+        // 批量添加执行日志
+        if(CollectionUtils.isNotEmpty(logList)){
+            fsCouponScheduleLogMapper.batchInsert(logList);
+        }
+        // 批量进行微信公众号消息通知
+        if(CollectionUtils.isNotEmpty(subNotifyTasks)){
+            fsMiniprogramSubNotifyTaskMapper.insertBatch(subNotifyTasks);
+        }
+    }
+}

+ 10 - 0
fs-service-system/src/main/java/com/fs/store/service/impl/FsStoreCouponIssueServiceImpl.java

@@ -1,6 +1,7 @@
 package com.fs.store.service.impl;
 
 import java.util.Calendar;
+import java.util.Collections;
 import java.util.Date;
 import java.util.List;
 
@@ -181,5 +182,14 @@ public class FsStoreCouponIssueServiceImpl implements IFsStoreCouponIssueService
         return fsStoreCouponIssueMapper.selectFsStoreCouponIssueListVO(fsStoreCouponIssue);
     }
 
+    @Override
+    public List<FsStoreCouponIssueVO> listAllAvailable() {
+        return fsStoreCouponIssueMapper.listAllAvailable();
+    }
+
+    @Override
+    public FsStoreCouponIssue selectFsStoreCouponIssueByCouponId(Long couponId) {
+        return fsStoreCouponIssueMapper.selectFsStoreCouponIssueByCouponId(couponId);
+    }
 
 }

+ 27 - 0
fs-service-system/src/main/java/com/fs/store/service/impl/FsStoreOrderServiceImpl.java

@@ -6,6 +6,7 @@ import java.nio.charset.Charset;
 import java.sql.Timestamp;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
+import java.time.LocalDateTime;
 import java.util.*;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
@@ -184,6 +185,8 @@ public class FsStoreOrderServiceImpl implements IFsStoreOrderService
     @Autowired
     TzBankService tzBankService;
 
+    @Autowired
+    private IFsCouponScheduleService fsCouponScheduleService;
     /**
      * 查询订单
      *
@@ -1387,6 +1390,30 @@ public class FsStoreOrderServiceImpl implements IFsStoreOrderService
             storeOrder.setStatus(OrderInfoEnum.STATUS_1.getValue());
             storeOrder.setPayTime(new Date());
             fsStoreOrderMapper.updateFsStoreOrder(storeOrder);
+
+            // 如果是套餐类型的
+            if(ObjectUtil.equal(storeOrder.getIsPackage(),1)) {
+                // 如果该套餐有定时发放优惠券,就放置到定时发放优惠券队列
+                FsStoreProductPackage fsStoreProductPackage = productPackageService.selectFsStoreProductPackageById(storeOrder.getPackageId());
+                if(ObjectUtil.equal(1,fsStoreProductPackage.getIcgEnable())){
+                    FsCouponSchedule fsCouponSchedule = new FsCouponSchedule();
+                    fsCouponSchedule.setOrderId(order.getId());
+                    fsCouponSchedule.setSetmealId(fsStoreProductPackage.getPackageId());
+                    fsCouponSchedule.setSetmealTitle(fsStoreProductPackage.getTitle());
+                    fsCouponSchedule.setMonth(fsStoreProductPackage.getIcgMonth().longValue());
+                    fsCouponSchedule.setCount(0L);
+                    fsCouponSchedule.setPage(fsStoreProductPackage.getIcgUrl());
+                    fsCouponSchedule.setRemark(fsStoreProductPackage.getIcgMark());
+                    fsCouponSchedule.setStatus(IcgProcessStatusEnum.PENDING.getCode());
+                    fsCouponSchedule.setOrderTime(LocalDateTime.now());
+                    fsCouponSchedule.setUserId(order.getUserId());
+                    // 如果上次发送时间为空,那么预计发送时间等于=下单时间+1月
+                    fsCouponSchedule.setSendTime(fsCouponSchedule.getOrderTime().plusMonths(1L));
+                    fsCouponSchedule.setRetryCount(0);
+
+                    fsCouponScheduleService.insertFsCouponSchedule(fsCouponSchedule);
+                }
+            }
             //非处方直接提交OMS
             try {
                 if(order.getIsPrescribe().equals(0)){

+ 9 - 0
fs-service-system/src/main/java/com/fs/store/service/impl/FsStorePaymentServiceImpl.java

@@ -12,6 +12,7 @@ import cn.hutool.core.util.StrUtil;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
 import com.fs.common.annotation.DataScope;
+import com.fs.common.config.FSConfig;
 import com.fs.common.config.FSSysConfig;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.R;
@@ -33,6 +34,7 @@ import com.fs.pay.pay.service.PayService;
 import com.fs.store.domain.FsPayConfig;
 import com.fs.store.domain.TzConfigInfoDTO;
 import com.fs.store.param.FsStoreStatisticsParam;
+import com.fs.store.service.IFsCouponScheduleService;
 import com.fs.store.vo.FsStorePaymentStatisticsVO;
 import com.fs.system.service.ISysConfigService;
 import com.fs.wx.miniapp.config.WxMaProperties;
@@ -43,6 +45,9 @@ import com.fs.store.param.FsStorePaymentParam;
 import com.fs.store.service.IFsUserService;
 import com.fs.store.vo.FsStorePaymentVO;
 import com.fs.pay.service.IPayService;
+import com.fs.pay.service.dto.CreatePayDTO;
+import com.fs.pay.service.dto.PayDTO;
+import com.fs.pay.service.dto.TradeOrder;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -86,6 +91,8 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService
     @Autowired
     HuiFuService huiFuService;
     @Autowired
+    IFsCouponScheduleService fsCouponScheduleService;
+    @Autowired
     TzBankService tzBankService;
     /**
      * 查询支付明细
@@ -415,6 +422,8 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService
         if(payment.getCompanyId()!=null&&payment.getCompanyId()>0){
             companyService.subCompanyPaymentMoney(payment);
         }
+        // 退款将定时发放优惠券的状态设置为已退款
+        fsCouponScheduleService.setScheduleCouponRefund(orderCode);
         return "success";
     }
 

+ 29 - 0
fs-service-system/src/main/java/com/fs/store/service/impl/IWechatMiniProgrServiceImpl.java

@@ -0,0 +1,29 @@
+package com.fs.store.service.impl;
+
+import com.fs.store.dto.ClientCredGrantReqDTO;
+import com.fs.store.dto.MiniGramSubsMsgResultDTO;
+import com.fs.store.dto.TemplateMessageSendRequestDTO;
+import com.fs.store.dto.WeXinAccessTokenDTO;
+import com.fs.store.service.IWechatMiniProgrService;
+import com.fs.store.utils.MiniProgramHttp;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+@RequiredArgsConstructor
+@Service
+@Slf4j
+public class IWechatMiniProgrServiceImpl implements IWechatMiniProgrService {
+
+    private final MiniProgramHttp miniProgramHttp;
+
+    @Override
+    public WeXinAccessTokenDTO getStableToken(ClientCredGrantReqDTO param) {
+        return miniProgramHttp.getStableAccessToken(param);
+    }
+
+    @Override
+    public MiniGramSubsMsgResultDTO sendSubscribeMsg(String accessToken,TemplateMessageSendRequestDTO param) {
+        return miniProgramHttp.sendSubscribeMessage(accessToken,param);
+    }
+}

+ 96 - 0
fs-service-system/src/main/java/com/fs/store/utils/MiniProgramHttp.java

@@ -0,0 +1,96 @@
+package com.fs.store.utils;
+
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.http.HttpRequest;
+import cn.hutool.http.HttpUtil;
+import cn.hutool.json.JSONUtil;
+import com.alibaba.fastjson.JSONObject;
+import com.fs.store.dto.ClientCredGrantReqDTO;
+import com.fs.store.dto.MiniGramSubsMsgResultDTO;
+import com.fs.store.dto.TemplateMessageSendRequestDTO;
+import com.fs.store.dto.WeXinAccessTokenDTO;
+import com.hc.openapi.tool.fastjson.JSON;
+import com.hc.openapi.tool.util.StringUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+@Component
+@Slf4j
+public class MiniProgramHttp {
+
+    /**
+     * 微信小程序-发送订阅消息地址
+     */
+    private static final String BASE_URL = "https://api.weixin.qq.com/cgi-bin/message/subscribe/send";
+
+    /**
+     * 微信小程序-获取accessToken地址
+     */
+    private static final String TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/stable_token";
+
+
+    /**
+     * 发送微信订阅消息 (使用 Hutool)
+     * @param accessToken token
+     * @param param 请求数据
+     * @return String
+     */
+    public MiniGramSubsMsgResultDTO sendSubscribeMessage(String accessToken, TemplateMessageSendRequestDTO param) {
+        String url = BASE_URL + "?access_token=" + accessToken;
+
+        log.info("发送小程序订阅消息, 请求 URL: {}", url);
+
+        String requestBody = JSON.toJSONString(param);
+        log.info("发送小程序订阅消息, 请求参数: {}", requestBody);
+
+        try {
+            String response = HttpUtil.post(url, requestBody);
+            log.info("发送小程序订阅消息, HTTP 请求 URL: {}", url);
+            log.info("发送小程序订阅消息, HTTP 请求体: {}", requestBody);
+            log.info("发送小程序订阅消息, HTTP 响应: {}", response);
+
+            MiniGramSubsMsgResultDTO result = JSONObject.parseObject(response, MiniGramSubsMsgResultDTO.class);
+            if(ObjectUtil.notEqual(result.getErrcode(),0)){
+                throw new RuntimeException("发送小程序订阅消息失败, " + result.getErrmsg());
+            }
+            log.info("发送小程序订阅消息, 解析结果: {}", JSON.toJSONString(result));
+            return result;
+
+        } catch (Exception e) {
+            log.error("发送小程序订阅消息失败: {}", e.getMessage());
+            throw e;
+        }
+    }
+
+
+    /**
+     * 获取微信 Stable Access Token
+     * @return WeXinAccessTokenDTO
+     */
+    public WeXinAccessTokenDTO getStableAccessToken(ClientCredGrantReqDTO param) {
+        String requestBody = JSONObject.toJSONString(param);
+        log.info("获取微信 Stable Access Token, 请求参数: {}", requestBody); // 打印请求参数
+
+        try {
+            String responseJson =
+            HttpRequest.post(TOKEN_URL).contentType("application/json").body(requestBody).execute().body();
+            log.info("获取微信 Stable Access Token, HTTP 请求 URL: {}", TOKEN_URL);
+            log.info("获取微信 Stable Access Token, HTTP 请求体: {}", requestBody);
+            log.info("获取微信 Stable Access Token, HTTP 响应: {}", responseJson);
+
+            if(StringUtils.isBlank(responseJson)){
+                throw new RuntimeException("获取微信 Stable Access Token 失败,response为空");
+            }
+            WeXinAccessTokenDTO result = JSONObject.parseObject(responseJson, WeXinAccessTokenDTO.class);
+            log.info("获取微信 Stable Access Token, 解析结果: {}", JSONObject.toJSONString(result)); //记录解析结果
+            if(result == null || StringUtils.isBlank(result.getAccessToken())){
+                throw new RuntimeException("获取微信 Stable Access Token 失败, accessToken为空");
+            }
+            return result;
+
+        } catch (Exception e) {
+            log.error("获取微信 Stable Access Token 失败", e);
+            throw e;
+        }
+    }
+}

+ 36 - 0
fs-service-system/src/main/java/com/fs/store/vo/FsStoreProductPacketVO.java

@@ -1,5 +1,6 @@
 package com.fs.store.vo;
 
+import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.fs.common.annotation.Excel;
 import com.fs.store.dto.StoreOrderProductDTO;
 import lombok.Data;
@@ -7,6 +8,7 @@ import lombok.Data;
 import java.io.Serializable;
 import java.math.BigDecimal;
 import java.util.List;
+import java.util.StringJoiner;
 
 /**
  * 订单对象 fs_store_order
@@ -71,6 +73,40 @@ public class FsStoreProductPacketVO implements Serializable
     @Excel(name = "状态", dictType = "common_status")
     private Integer status;
 
+    /**
+     * 分期赠送优惠券-是否开启 0未开启 1开启
+     */
+    private Integer icgEnable;
+    /**
+     * 分期赠送优惠券-月数
+     */
+    private Integer icgMonth;
+
+    /**
+     * 分期赠送优惠券-优惠券列表(数据库存储)
+     */
+    @JsonIgnore
+    private String icgCoupons;
+    /**
+     * 分期赠送优惠券-优惠券列表
+     */
+    private Integer[] icgCouponsId;
+
+    /**
+     * 获取优惠券列表ID
+     * @return 优惠券列表id
+     */
+    public Integer[] getIcgCouponsId() {
+        if (icgCouponsId == null && icgCoupons != null) {
+            String[] split = icgCoupons.split(",");
+            icgCouponsId = new Integer[split.length];
+            for (int i = 0; i < split.length; i++) {
+                icgCouponsId[i] = Integer.parseInt(split[i].trim());
+            }
+        }
+        return icgCouponsId;
+    }
+
     List<StoreOrderProductDTO> productList;
 
 }

+ 20 - 0
fs-service-system/src/main/resources/mapper/store/FsCouponScheduleLogMapper.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.fs.store.mapper.FsCouponScheduleLogMapper">
+
+    <!-- 其他映射配置... -->
+
+    <insert id="batchInsert" parameterType="java.util.List">
+        INSERT INTO fs_coupon_schedule_log (schedule_id, user_id, order_id, status_before, status_after,
+        send_time_before, send_time_after, actual_send_time, error_message, retry_count_before,
+        retry_count_after, operation_type, operation_time, operator, remark)
+        VALUES
+        <foreach collection="logs" item="log" separator=",">
+            (#{log.scheduleId}, #{log.userId}, #{log.orderId}, #{log.statusBefore}, #{log.statusAfter},
+            #{log.sendTimeBefore}, #{log.sendTimeAfter}, #{log.actualSendTime}, #{log.errorMessage},
+            #{log.retryCountBefore}, #{log.retryCountAfter}, #{log.operationType}, #{log.operationTime},
+            #{log.operator}, #{log.remark})
+        </foreach>
+    </insert>
+
+</mapper>

+ 181 - 0
fs-service-system/src/main/resources/mapper/store/FsCouponScheduleMapper.xml

@@ -0,0 +1,181 @@
+<?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.fs.store.mapper.FsCouponScheduleMapper">
+
+    <resultMap type="FsCouponSchedule" id="FsCouponScheduleResult">
+        <result property="id"    column="id"    />
+        <result property="userId"    column="user_id"    />
+        <result property="orderId"    column="order_id"    />
+        <result property="setmealId"    column="setmeal_id"    />
+        <result property="month"    column="month"    />
+        <result property="count"    column="count"    />
+        <result property="status"    column="status"    />
+        <result property="createTime"    column="create_time"    />
+        <result property="createBy"    column="create_by"    />
+        <result property="updateTime"    column="update_time"    />
+        <result property="updateBy"    column="update_by"    />
+        <result property="orderTime"    column="order_time"    />
+        <result property="couponIds"    column="coupon_id"    />
+        <result property="sendTime"    column="send_time"    />
+        <result property="actualSendTime"    column="actual_send_time"    />
+        <result property="errorMessage"    column="error_message"    />
+        <result property="retryCount"    column="retry_count"    />
+        <result property="maxRetries"    column="max_retries"    />
+        <result property="remark"    column="remark"    />
+        <result property="page" column="page"/>
+    </resultMap>
+
+    <sql id="selectFsCouponScheduleVo">
+        select id, user_id, order_id, setmeal_id, month, count, status, create_time, create_by, update_time, update_by, order_time, coupon_id as coupon_ids, send_time, actual_send_time, error_message, retry_count, max_retries,  remark,page from fs_coupon_schedule
+    </sql>
+
+    <select id="selectFsCouponScheduleList" parameterType="FsCouponSchedule" resultMap="FsCouponScheduleResult">
+        <include refid="selectFsCouponScheduleVo"/>
+        <where>
+            <if test="userId != null "> and user_id = #{userId}</if>
+            <if test="orderId != null  and orderId != ''"> and order_id = #{orderId}</if>
+            <if test="setmealId != null "> and setmeal_id = #{setmealId}</if>
+            <if test="month != null "> and month = #{month}</if>
+            <if test="count != null "> and count = #{count}</if>
+            <if test="status != null "> and status = #{status}</if>
+            <if test="orderTime != null "> and order_time = #{orderTime}</if>
+            <if test="couponIds != null "> and coupon_id = #{couponIds}</if>
+            <if test="sendTime != null "> and send_time = #{sendTime}</if>
+            <if test="actualSendTime != null "> and actual_send_time = #{actualSendTime}</if>
+            <if test="errorMessage != null  and errorMessage != ''"> and error_message = #{errorMessage}</if>
+            <if test="retryCount != null "> and retry_count = #{retryCount}</if>
+            <if test="maxRetries != null "> and max_retries = #{maxRetries}</if>
+            <if test="remark != null "> and remark = #{remark}</if>
+            <if test="page != null "> and page = #{page}</if>
+        </where>
+    </select>
+
+    <select id="selectFsCouponScheduleById" parameterType="Long" resultMap="FsCouponScheduleResult">
+        <include refid="selectFsCouponScheduleVo"/>
+        where id = #{id}
+    </select>
+
+    <select id="selectPendingCouponList" resultType="com.fs.store.domain.FsCouponSchedule">
+        <include refid="selectFsCouponScheduleVo"/>
+        where status IN (0,-1,2) AND retry_count &lt; 3 AND count &lt; month
+        AND now()>=send_time
+        limit 500
+    </select>
+    <select id="selectFsCouponScheduleByOrderId" resultType="com.fs.store.domain.FsCouponSchedule">
+        <include refid="selectFsCouponScheduleVo"/>
+        where order_id = #{orderId} limit 1
+    </select>
+
+    <insert id="insertFsCouponSchedule" parameterType="FsCouponSchedule" useGeneratedKeys="true" keyProperty="id">
+        insert into fs_coupon_schedule
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="userId != null">user_id,</if>
+            <if test="orderId != null and orderId != ''">order_id,</if>
+            <if test="setmealId != null">setmeal_id,</if>
+            <if test="month != null">month,</if>
+            <if test="count != null">count,</if>
+            <if test="status != null">status,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="createBy != null">create_by,</if>
+            <if test="updateTime != null">update_time,</if>
+            <if test="updateBy != null">update_by,</if>
+            <if test="orderTime != null">order_time,</if>
+            <if test="couponIds != null">coupon_id,</if>
+            <if test="sendTime != null">send_time,</if>
+            <if test="actualSendTime != null">actual_send_time,</if>
+            <if test="errorMessage != null">error_message,</if>
+            <if test="retryCount != null">retry_count,</if>
+            <if test="maxRetries != null">max_retries,</if>
+            <if test="remark != null">remark,</if>
+            <if test="page != null">page,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="userId != null">#{userId},</if>
+            <if test="orderId != null and orderId != ''">#{orderId},</if>
+            <if test="setmealId != null">#{setmealId},</if>
+            <if test="month != null">#{month},</if>
+            <if test="count != null">#{count},</if>
+            <if test="status != null">#{status},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="createBy != null">#{createBy},</if>
+            <if test="updateTime != null">#{updateTime},</if>
+            <if test="updateBy != null">#{updateBy},</if>
+            <if test="orderTime != null">#{orderTime},</if>
+            <if test="couponIds != null">#{couponIds},</if>
+            <if test="sendTime != null">#{sendTime},</if>
+            <if test="actualSendTime != null">#{actualSendTime},</if>
+            <if test="errorMessage != null">#{errorMessage},</if>
+            <if test="retryCount != null">#{retryCount},</if>
+            <if test="maxRetries != null">#{maxRetries},</if>
+            <if test="remark != null">#{remark},</if>
+            <if test="page != null">#{page},</if>
+         </trim>
+    </insert>
+
+    <update id="updateFsCouponSchedule" parameterType="FsCouponSchedule">
+        update fs_coupon_schedule
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="userId != null">user_id = #{userId},</if>
+            <if test="orderId != null and orderId != ''">order_id = #{orderId},</if>
+            <if test="setmealId != null">setmeal_id = #{setmealId},</if>
+            <if test="month != null">month = #{month},</if>
+            <if test="count != null">count = #{count},</if>
+            <if test="status != null">status = #{status},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+            <if test="createBy != null">create_by = #{createBy},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+            <if test="updateBy != null">update_by = #{updateBy},</if>
+            <if test="orderTime != null">order_time = #{orderTime},</if>
+            <if test="couponIds != null">coupon_id = #{couponIds},</if>
+            <if test="sendTime != null">send_time = #{sendTime},</if>
+            <if test="actualSendTime != null">actual_send_time = #{actualSendTime},</if>
+            <if test="errorMessage != null">error_message = #{errorMessage},</if>
+            <if test="retryCount != null">retry_count = #{retryCount},</if>
+            <if test="maxRetries != null">max_retries = #{maxRetries},</if>
+            <if test="remark != null">remark = #{remark},</if>
+            <if test="page != null">page = #{page},</if>
+        </trim>
+        where id = #{id}
+    </update>
+    <update id="updateBatchFsCouponSchedule">
+        <foreach collection="list" separator=";" item="item">
+            update fs_coupon_schedule
+            <trim prefix="SET" suffixOverrides=",">
+                <if test="item.userId != null">user_id = #{item.userId},</if>
+                <if test="item.orderId != null and item.orderId != ''">order_id = #{item.orderId},</if>
+                <if test="item.setmealId != null">setmeal_id = #{item.setmealId},</if>
+                <if test="item.month != null">month = #{item.month},</if>
+                <if test="item.count != null">count = #{item.count},</if>
+                <if test="item.status != null">status = #{item.status},</if>
+                <if test="item.createTime != null">create_time = #{item.createTime},</if>
+                <if test="item.createBy != null">create_by = #{item.createBy},</if>
+                <if test="item.updateTime != null">update_time = #{item.updateTime},</if>
+                <if test="item.updateBy != null">update_by = #{item.updateBy},</if>
+                <if test="item.orderTime != null">order_time = #{item.orderTime},</if>
+                <if test="item.couponIds != null">coupon_id = #{item.couponIds},</if>
+                <if test="item.sendTime != null">send_time = #{item.sendTime},</if>
+                <if test="item.actualSendTime != null">actual_send_time = #{item.actualSendTime},</if>
+                <if test="item.errorMessage != null">error_message = #{item.errorMessage},</if>
+                <if test="item.retryCount != null">retry_count = #{item.retryCount},</if>
+                <if test="item.maxRetries != null">max_retries = #{item.maxRetries},</if>
+                <if test="item.remark != null">remark = #{item.remark},</if>
+                <if test="item.page != null">page = #{item.page},</if>
+            </trim>
+            where id = #{item.id}
+        </foreach>
+    </update>
+
+    <delete id="deleteFsCouponScheduleById" parameterType="Long">
+        delete from fs_coupon_schedule where id = #{id}
+    </delete>
+
+    <delete id="deleteFsCouponScheduleByIds" parameterType="String">
+        delete from fs_coupon_schedule where id in
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+
+</mapper>

+ 66 - 0
fs-service-system/src/main/resources/mapper/store/FsMiniprogramSubNotifyTaskMapper.xml

@@ -0,0 +1,66 @@
+<?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.fs.store.mapper.FsMiniprogramSubNotifyTaskMapper">
+    <insert id="insertBatch">
+        INSERT INTO fs_miniprogram_sub_notify_task (
+        id,
+        task_name,
+        template_id,
+        touser,
+        page,
+        data,
+        status,
+        retry_count,
+        max_retries,
+        request_params,
+        request_body,
+        response_body,
+        error_message,
+        create_time,
+        update_time
+        ) VALUES
+        <foreach collection="list" item="item" separator=",">
+            (
+            #{item.id},
+            #{item.taskName},
+            #{item.templateId},
+            #{item.touser},
+            #{item.page},
+            #{item.data},
+            #{item.status},
+            #{item.retryCount},
+            #{item.maxRetries},
+            #{item.requestParams},
+            #{item.requestBody},
+            #{item.responseBody},
+            #{item.errorMessage},
+            #{item.createTime},
+            #{item.updateTime}
+            )
+        </foreach>
+    </insert>
+
+    <update id="updateBatchById" parameterType="java.util.List">
+        <foreach collection="list" item="item" separator=";">
+            UPDATE fs_miniprogram_sub_notify_task
+            SET
+            task_name = #{item.taskName},
+            template_id = #{item.templateId},
+            touser = #{item.touser},
+            page = #{item.page},
+            data = #{item.data},
+            status = #{item.status},
+            retry_count = #{item.retryCount},
+            max_retries = #{item.maxRetries},
+            request_params = #{item.requestParams},
+            request_body = #{item.requestBody},
+            response_body = #{item.responseBody},
+            error_message = #{item.errorMessage},
+            update_time = #{item.updateTime}
+            WHERE id = #{item.id}
+        </foreach>
+    </update>
+
+</mapper>

+ 53 - 7
fs-service-system/src/main/resources/mapper/store/FsStoreCouponIssueMapper.xml

@@ -3,7 +3,7 @@
 PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 <mapper namespace="com.fs.store.mapper.FsStoreCouponIssueMapper">
-    
+
     <resultMap type="FsStoreCouponIssue" id="FsStoreCouponIssueResult">
         <result property="id"    column="id"    />
         <result property="couponName"    column="coupon_name"    />
@@ -26,7 +26,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 
     <select id="selectFsStoreCouponIssueList" parameterType="FsStoreCouponIssue" resultMap="FsStoreCouponIssueResult">
         <include refid="selectFsStoreCouponIssueVo"/>
-        <where>  
+        <where>
             <if test="couponName != null  and couponName != ''"> and coupon_name like concat('%', #{couponName}, '%')</if>
             <if test="couponId != null "> and coupon_id = #{couponId}</if>
             <if test="couponType != null "> and coupon_type = #{couponType}</if>
@@ -40,12 +40,58 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         </where>
         order by id desc
     </select>
-    
+
     <select id="selectFsStoreCouponIssueById" parameterType="Long" resultMap="FsStoreCouponIssueResult">
         <include refid="selectFsStoreCouponIssueVo"/>
         where id = #{id}
     </select>
-        
+    <select id="listAllAvailable" resultMap="FsStoreCouponIssueResult">
+        SELECT
+            id,
+            coupon_name,
+            coupon_id,
+            coupon_type,
+            start_time,
+            limit_time,
+            total_count,
+            remain_count,
+            is_permanent,
+            STATUS,
+            is_del,
+            create_time,
+            update_time
+        FROM
+            fs_store_coupon_issue
+        WHERE
+            coupon_type = 1
+          AND  remain_count> 0
+          AND ifnull(is_del,0)= 0
+          AND `status`=1
+
+        UNION
+
+        SELECT
+            id,
+            coupon_name,
+            coupon_id,
+            coupon_type,
+            start_time,
+            limit_time,
+            total_count,
+            remain_count,
+            is_permanent,
+            STATUS,
+            is_del,
+            create_time,
+            update_time
+        FROM
+            fs_store_coupon_issue
+        WHERE
+            coupon_type = 1
+          AND is_permanent= 1
+          AND ifnull(is_del,0)= 0
+          AND `status`=1
+    </select>
     <insert id="insertFsStoreCouponIssue" parameterType="FsStoreCouponIssue" useGeneratedKeys="true" keyProperty="id">
         insert into fs_store_coupon_issue
         <trim prefix="(" suffix=")" suffixOverrides=",">
@@ -102,10 +148,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     </delete>
 
     <delete id="deleteFsStoreCouponIssueByIds" parameterType="String">
-        delete from fs_store_coupon_issue where id in 
+        delete from fs_store_coupon_issue where id in
         <foreach item="id" collection="array" open="(" separator="," close=")">
             #{id}
         </foreach>
     </delete>
-    
-</mapper>
+
+</mapper>

+ 5 - 0
fs-service-system/src/main/resources/mapper/store/FsStoreCouponMapper.xml

@@ -51,6 +51,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <include refid="selectFsStoreCouponVo"/>
         where coupon_id = #{couponId}
     </select>
+    <select id="selectFsStoreCouponNameById" resultType="java.lang.String">
+        select title from fs_store_coupon where id = ${couponId}
+
+    </select>
+
 
     <insert id="insertFsStoreCoupon" parameterType="FsStoreCoupon" useGeneratedKeys="true" keyProperty="couponId">
         insert into fs_store_coupon

+ 26 - 1
fs-service-system/src/main/resources/mapper/store/FsStoreProductPackageMapper.xml

@@ -20,10 +20,15 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="cateId"    column="cate_id"    />
         <result property="sort"    column="sort"    />
         <result property="status"    column="status"    />
+        <result property="icgEnable"    column="icg_enable"    />
+        <result property="icgMonth"    column="icg_month"    />
+        <result property="icgCoupons"    column="icg_coupons"    />
+        <result property="icgUrl"    column="icg_url"    />
+        <result property="icgMark"    column="icg_mark"    />
     </resultMap>
 
     <sql id="selectFsStoreProductPackageVo">
-        select package_id, title, descs, content, img_url,images, products, money, pay_money, company_id, dept_id,pay_type,limit_count,cate_id,sort,status from fs_store_product_package
+        select package_id, title, descs, content, img_url,images, products, money, pay_money, company_id, dept_id,pay_type,limit_count,cate_id,sort,status,icg_enable,icg_month,icg_coupons,icg_url,icg_mark from fs_store_product_package
     </sql>
 
     <select id="selectFsStoreProductPackageList" parameterType="FsStoreProductPackage" resultMap="FsStoreProductPackageResult">
@@ -40,6 +45,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="deptId != null "> and dept_id = #{deptId}</if>
             <if test="cateId != null "> and cate_id = #{cateId}</if>
             <if test="status != null "> and status = #{status}</if>
+            <if test="icgEnable != null "> and icg_enable = #{icgEnable}</if>
+            <if test="icgMonth != null "> and icg_month = #{icgMonth}</if>
+            <if test="icgCoupons != null "> and icg_coupons = #{icgCoupons}</if>
+            <if test="icgUrl != null "> and icg_url = #{icgUrl}</if>
+            <if test="icgMark != null "> and icg_mark = #{icgMark}</if>
         </where>
     </select>
 
@@ -69,6 +79,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="cateId != null">cate_id,</if>
             <if test="sort != null">sort,</if>
             <if test="status != null">status,</if>
+            <if test="icgEnable != null">icg_enable,</if>
+            <if test="icgMonth != null">icg_month,</if>
+            <if test="icgCoupons != null">icg_coupons,</if>
+            <if test="icgUrl != null">icg_url,</if>
+            <if test="icgMark != null">icg_mark,</if>
          </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="packageId != null">#{packageId},</if>
@@ -87,6 +102,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="cateId != null">#{cateId},</if>
             <if test="sort != null">#{sort},</if>
             <if test="status != null">#{status},</if>
+            <if test="icgEnable != null">#{icgEnable},</if>
+            <if test="icgMonth != null">#{icgMonth},</if>
+            <if test="icgCoupons != null">#{icgCoupons},</if>
+            <if test="icgUrl != null">#{icgUrl},</if>
+            <if test="icgMark != null">#{IcgMark},</if>
          </trim>
     </insert>
 
@@ -108,6 +128,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="cateId != null">cate_id = #{cateId},</if>
             <if test="sort != null">sort = #{sort},</if>
             <if test="status != null">status = #{status},</if>
+            <if test="icgEnable != null">icg_enable = #{icgEnable},</if>
+            <if test="icgMonth != null">icg_month = #{icgMonth},</if>
+            <if test="icgCoupons != null">icg_coupons = #{icgCoupons},</if>
+            <if test="icgUrl != null">icg_url = #{icgUrl},</if>
+            <if test="icgMark != null">icg_mark = #{icgMark},</if>
         </trim>
         where package_id = #{packageId}
     </update>

+ 1 - 0
fs-user-app/src/main/java/com/fs/app/controller/CouponController.java

@@ -104,6 +104,7 @@ public class CouponController extends  AppBaseController {
         FsStoreCouponIssueVO couponIssue=fsStoreCouponIssueService.selectFsStoreCouponIssueVOById(id);
         return R.ok().put("data",couponIssue).put("code",code);
     }
+
     @Login
     @ApiOperation("领取")
     @PostMapping("/companyReceive")