Pārlūkot izejas kodu

Merge branch 'master' of http://1.14.104.71:10880/root/ylrz_his_scrm_java

caoliqin 3 nedēļas atpakaļ
vecāks
revīzija
571a60f0b3
66 mainītis faili ar 1814 papildinājumiem un 96 dzēšanām
  1. 164 8
      fs-admin/src/main/java/com/fs/course/controller/FsUserCourseCommentController.java
  2. 103 0
      fs-admin/src/main/java/com/fs/his/controller/FsSubMerchantController.java
  3. 72 0
      fs-admin/src/main/java/com/fs/hisStore/task/MallStoreTask.java
  4. 102 0
      fs-company-app/src/main/java/com/fs/app/controller/StoreProductController.java
  5. 1 1
      fs-company/src/main/java/com/fs/FsCompanyApplication.java
  6. 26 28
      fs-company/src/main/java/com/fs/company/controller/company/CompanyVoiceRoboticCallLogAddwxController.java
  7. 34 0
      fs-ipad-task/src/main/java/com/fs/app/service/IpadSendServer.java
  8. 28 4
      fs-qw-task/src/main/java/com/fs/app/controller/CommonController.java
  9. 8 8
      fs-service/src/main/java/com/fs/course/domain/FsUserCourseComment.java
  10. 3 0
      fs-service/src/main/java/com/fs/course/domain/FsUserCourseVideo.java
  11. 3 1
      fs-service/src/main/java/com/fs/course/enums/MiniProgramAgreementEnum.java
  12. 17 4
      fs-service/src/main/java/com/fs/course/mapper/FsUserCourseCommentMapper.java
  13. 1 1
      fs-service/src/main/java/com/fs/course/mapper/FsUserCourseMapper.java
  14. 3 1
      fs-service/src/main/java/com/fs/course/service/IFsUserCourseCommentService.java
  15. 65 0
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java
  16. 8 2
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseCommentServiceImpl.java
  17. 4 0
      fs-service/src/main/java/com/fs/course/vo/CourseStatisticsUserDetailVO.java
  18. 4 0
      fs-service/src/main/java/com/fs/course/vo/FsUserCourseVideoH5VO.java
  19. 2 0
      fs-service/src/main/java/com/fs/course/vo/FsUserCourseVideoQVO.java
  20. 5 0
      fs-service/src/main/java/com/fs/erp/dto/ShopOrderDTO.java
  21. 56 0
      fs-service/src/main/java/com/fs/erp/service/impl/JSTErpOrderServiceImpl.java
  22. 62 1
      fs-service/src/main/java/com/fs/fastGpt/service/impl/AiHookServiceImpl.java
  23. 2 0
      fs-service/src/main/java/com/fs/his/config/AgreementConfig.java
  24. 35 0
      fs-service/src/main/java/com/fs/his/domain/FsSubMerchant.java
  25. 2 0
      fs-service/src/main/java/com/fs/his/enums/FsUserIntegralLogTypeEnum.java
  26. 61 0
      fs-service/src/main/java/com/fs/his/mapper/FsSubMerchantMapper.java
  27. 8 0
      fs-service/src/main/java/com/fs/his/mapper/FsUserIntegralLogsMapper.java
  28. 61 0
      fs-service/src/main/java/com/fs/his/service/IFsSubMerchantService.java
  29. 10 3
      fs-service/src/main/java/com/fs/his/service/impl/FsIntegralGoodsServiceImpl.java
  30. 94 0
      fs-service/src/main/java/com/fs/his/service/impl/FsSubMerchantServiceImpl.java
  31. 5 0
      fs-service/src/main/java/com/fs/hisStore/config/StoreConfig.java
  32. 3 0
      fs-service/src/main/java/com/fs/hisStore/domain/FsStoreOrderScrm.java
  33. 8 0
      fs-service/src/main/java/com/fs/hisStore/domain/FsStoreProductScrm.java
  34. 1 0
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreOrderItemScrmMapper.java
  35. 5 0
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreOrderScrmMapper.java
  36. 2 0
      fs-service/src/main/java/com/fs/hisStore/param/FsStoreProductAddEditParam.java
  37. 3 0
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsExpressScrmServiceImpl.java
  38. 44 0
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreAfterSalesScrmServiceImpl.java
  39. 27 1
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderScrmServiceImpl.java
  40. 3 0
      fs-service/src/main/java/com/fs/hisStore/vo/FsStoreOrderItemExportVO.java
  41. 3 0
      fs-service/src/main/java/com/fs/hisStore/vo/FsStoreOrderItemExportZMVO.java
  42. 1 1
      fs-service/src/main/java/com/fs/hisStore/vo/FsStoreOrderVO.java
  43. 3 0
      fs-service/src/main/java/com/fs/hisStore/vo/FsStoreProductListVO.java
  44. 2 2
      fs-service/src/main/java/com/fs/qw/mapper/QwExternalContactMapper.java
  45. 47 5
      fs-service/src/main/java/com/fs/qw/service/AsyncQwAiChatSopService.java
  46. 371 0
      fs-service/src/main/java/com/fs/qw/service/AsyncQwAiChatSopServiceCopy.java
  47. 8 1
      fs-service/src/main/java/com/fs/qw/service/impl/QwExternalContactServiceImpl.java
  48. 1 0
      fs-service/src/main/java/com/fs/qw/vo/QwSopCourseFinishTempSetting.java
  49. 7 0
      fs-service/src/main/java/com/fs/qw/vo/QwSopRuleTimeVO.java
  50. 3 1
      fs-service/src/main/java/com/fs/qw/vo/QwSopTempSetting.java
  51. 3 0
      fs-service/src/main/java/com/fs/sop/domain/QwSop.java
  52. 6 0
      fs-service/src/main/java/com/fs/sop/domain/QwSopTempContent.java
  53. 3 1
      fs-service/src/main/java/com/fs/sop/mapper/QwSopTempContentMapper.java
  54. 1 1
      fs-service/src/main/resources/mapper/company/CompanyWithdrawDetailMapper.xml
  55. 4 0
      fs-service/src/main/resources/mapper/course/FsUserCourseCategoryMapper.xml
  56. 20 1
      fs-service/src/main/resources/mapper/course/FsUserCourseCommentMapper.xml
  57. 5 0
      fs-service/src/main/resources/mapper/course/FsUserCourseVideoMapper.xml
  58. 76 0
      fs-service/src/main/resources/mapper/his/FsSubMerchantMapper.xml
  59. 12 0
      fs-service/src/main/resources/mapper/hisStore/FsStoreOrderScrmMapper.xml
  60. 11 2
      fs-service/src/main/resources/mapper/hisStore/FsStoreProductScrmMapper.xml
  61. 5 0
      fs-service/src/main/resources/mapper/sop/QwSopMapper.xml
  62. 23 6
      fs-user-app/src/main/java/com/fs/app/controller/CourseCommentController.java
  63. 19 6
      fs-user-app/src/main/java/com/fs/app/controller/CourseController.java
  64. 6 0
      fs-user-app/src/main/java/com/fs/app/controller/H5Controller.java
  65. 23 6
      fs-user-app/src/main/java/com/fs/app/controller/store/CourseCommentScrmController.java
  66. 6 0
      fs-user-app/src/main/java/com/fs/app/controller/store/IndexScrmController.java

+ 164 - 8
fs-admin/src/main/java/com/fs/course/controller/FsUserCourseCommentController.java

@@ -2,21 +2,20 @@ package com.fs.course.controller;
 
 import java.util.List;
 
+import cn.hutool.core.util.IdUtil;
+import com.fs.common.utils.DateUtils;
+import com.fs.config.cloud.CloudHostProper;
 import com.fs.course.param.FsUserCourseCommentParam;
 import com.fs.course.param.FsCourseWatchCommentPageParam;
 import com.fs.course.vo.FsUserCourseCommentListVO;
 import com.fs.course.vo.FsUserCourseCommentVO;
 import com.fs.course.vo.FsCourseWatchCommentListVO;
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
 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 org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
 import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
@@ -27,6 +26,12 @@ import com.fs.course.service.IFsCourseWatchCommentService;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.common.core.page.TableDataInfo;
 
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.URLEncoder;
+import java.util.Date;
+
 /**
  * 课堂评论Controller
  *
@@ -150,4 +155,155 @@ public class FsUserCourseCommentController extends BaseController
     {
         return toAjax(fsUserCourseCommentService.deleteFsUserCourseCommentByCommentIds(commentIds));
     }
+
+    /**
+     * 下载精选留言Excel导入模板
+     * 模板字段:昵称、评论内容、图片URL、视频URL
+     */
+    @PreAuthorize("@ss.hasPermi('course:userCourseComment:add')")
+    @GetMapping("/importTemplate")
+    public void importTemplate(HttpServletResponse response) throws IOException {
+        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+        response.setCharacterEncoding("utf-8");
+        String fileName = URLEncoder.encode("精选留言导入模板", "UTF-8");
+        response.setHeader("Content-Disposition", "attachment;filename=" + fileName + ".xlsx");
+
+        try (Workbook workbook = new XSSFWorkbook(); OutputStream out = response.getOutputStream()) {
+            Sheet sheet = workbook.createSheet("精选留言");
+
+            // 表头样式
+            CellStyle headerStyle = workbook.createCellStyle();
+            Font headerFont = workbook.createFont();
+            headerFont.setBold(true);
+            headerStyle.setFont(headerFont);
+
+            // 表头行
+            Row headerRow = sheet.createRow(0);
+            String[] headers = {"昵称", "评论内容", "图片URL", "视频URL"};
+            for (int i = 0; i < headers.length; i++) {
+                Cell cell = headerRow.createCell(i);
+                cell.setCellValue(headers[i]);
+                cell.setCellStyle(headerStyle);
+            }
+
+            // 示例行
+            Row exampleRow = sheet.createRow(1);
+            String[] examples = {"张三", "课程内容非常棒!", "", ""};
+            for (int i = 0; i < examples.length; i++) {
+                exampleRow.createCell(i).setCellValue(examples[i]);
+            }
+
+            // 自适应列宽
+            for (int i = 0; i < headers.length; i++) {
+                sheet.autoSizeColumn(i);
+            }
+
+            workbook.write(out);
+        }
+    }
+
+    /**
+     * 导入精选留言(Excel解析)
+     * 解析Excel表格数据,按 type=3 保存为精选留言
+     */
+    @PreAuthorize("@ss.hasPermi('course:userCourseComment:add')")
+    @Log(title = "精选留言导入", businessType = BusinessType.IMPORT)
+    @PostMapping("/importComments/{courseId}")
+    public AjaxResult importComments(@PathVariable Long courseId, @RequestParam("file") MultipartFile file) throws IOException {
+        if (file.isEmpty()) {
+            return AjaxResult.error("请选择要导入的文件");
+        }
+        String originalFilename = file.getOriginalFilename();
+        if (originalFilename == null || (!originalFilename.endsWith(".xlsx") && !originalFilename.endsWith(".xls"))) {
+            return AjaxResult.error("仅支持Excel文件(.xls/.xlsx)格式");
+        }
+
+        try (Workbook workbook = WorkbookFactory.create(file.getInputStream())) {
+            Sheet sheet = workbook.getSheetAt(0);
+            if (sheet == null) {
+                return AjaxResult.error("Excel文件中未找到数据");
+            }
+
+            int count = 0;
+            // 从第2行开始读取(第1行是表头)
+            for (int i = 1; i <= sheet.getLastRowNum(); i++) {
+                Row row = sheet.getRow(i);
+                if (row == null) continue;
+
+                String nickName = getCellStringValue(row, 0);
+                String content = getCellStringValue(row, 1);
+                String imgUrl = getCellStringValue(row, 2);
+                String videoUrl = getCellStringValue(row, 3);
+
+                // 昵称和内容至少有一个非空才导入
+                if ((nickName == null || nickName.trim().isEmpty()) && (content == null || content.trim().isEmpty())) {
+                    continue;
+                }
+
+                // 拼接内容:文字 + 图片URL + 视频URL
+                StringBuilder contentBuilder = new StringBuilder();
+                if (content != null && !content.trim().isEmpty()) {
+                    contentBuilder.append(content.trim());
+                }
+                if (imgUrl != null && !imgUrl.trim().isEmpty()) {
+                    contentBuilder.append("\n").append(imgUrl.trim());
+                }
+                if (videoUrl != null && !videoUrl.trim().isEmpty()) {
+                    contentBuilder.append("\n").append(videoUrl.trim());
+                }
+
+                FsUserCourseComment comment = new FsUserCourseComment();
+                comment.setCourseId(courseId);
+                comment.setType(3L); // 精选留言
+                comment.setParentId(0L);
+                comment.setNickName(nickName != null ? nickName.trim() : "");
+                comment.setContent(contentBuilder.toString());
+                comment.setReplyCount(0L);
+                comment.setLikes(0L);
+                comment.setIsDel(0);
+                comment.setCreateTime(new Date());
+                fsUserCourseCommentService.insertFsUserCourseComment(comment);
+                count++;
+            }
+            return AjaxResult.success("成功导入" + count + "条精选留言");
+        }
+    }
+
+    /**
+     * 查询精选留言历史列表(type=3)
+     */
+    @PreAuthorize("@ss.hasPermi('course:userCourseComment:list')")
+    @GetMapping("/featuredComments/{courseId}")
+    public TableDataInfo featuredComments(@PathVariable Long courseId) {
+        startPage();
+        FsUserCourseComment param = new FsUserCourseComment();
+        param.setCourseId(courseId);
+        param.setType(3L);
+        param.setIsDel(0);
+        List<FsUserCourseComment> list = fsUserCourseCommentService.selectFsUserCourseCommentList(param);
+        return getDataTable(list);
+    }
+
+    /**
+     * 提取Excel单元格文本
+     */
+    private String getCellStringValue(Row row, int colIndex) {
+        if (colIndex >= row.getLastCellNum()) {
+            return "";
+        }
+        Cell cell = row.getCell(colIndex);
+        if (cell == null) {
+            return "";
+        }
+        switch (cell.getCellType()) {
+            case STRING:
+                return cell.getStringCellValue();
+            case NUMERIC:
+                return String.valueOf((long) cell.getNumericCellValue());
+            case BOOLEAN:
+                return String.valueOf(cell.getBooleanCellValue());
+            default:
+                return "";
+        }
+    }
 }

+ 103 - 0
fs-admin/src/main/java/com/fs/his/controller/FsSubMerchantController.java

@@ -0,0 +1,103 @@
+package com.fs.his.controller;
+
+import java.util.List;
+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.his.domain.FsSubMerchant;
+import com.fs.his.service.IFsSubMerchantService;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.common.core.page.TableDataInfo;
+
+/**
+ * 汇付-子商户信息Controller
+ * 
+ * @author fs
+ * @date 2026-05-11
+ */
+@RestController
+@RequestMapping("/his/merchant")
+public class FsSubMerchantController extends BaseController
+{
+    @Autowired
+    private IFsSubMerchantService fsSubMerchantService;
+
+    /**
+     * 查询汇付-子商户信息列表
+     */
+    @PreAuthorize("@ss.hasPermi('his:merchant:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsSubMerchant fsSubMerchant)
+    {
+        startPage();
+        List<FsSubMerchant> list = fsSubMerchantService.selectFsSubMerchantList(fsSubMerchant);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出汇付-子商户信息列表
+     */
+    @PreAuthorize("@ss.hasPermi('his:merchant:export')")
+    @Log(title = "汇付-子商户信息", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsSubMerchant fsSubMerchant)
+    {
+        List<FsSubMerchant> list = fsSubMerchantService.selectFsSubMerchantList(fsSubMerchant);
+        ExcelUtil<FsSubMerchant> util = new ExcelUtil<FsSubMerchant>(FsSubMerchant.class);
+        return util.exportExcel(list, "汇付-子商户信息数据");
+    }
+
+    /**
+     * 获取汇付-子商户信息详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('his:merchant:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(fsSubMerchantService.selectFsSubMerchantById(id));
+    }
+
+    /**
+     * 新增汇付-子商户信息
+     */
+    @PreAuthorize("@ss.hasPermi('his:merchant:add')")
+    @Log(title = "汇付-子商户信息", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsSubMerchant fsSubMerchant)
+    {
+        return toAjax(fsSubMerchantService.insertFsSubMerchant(fsSubMerchant));
+    }
+
+    /**
+     * 修改汇付-子商户信息
+     */
+    @PreAuthorize("@ss.hasPermi('his:merchant:edit')")
+    @Log(title = "汇付-子商户信息", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsSubMerchant fsSubMerchant)
+    {
+        return toAjax(fsSubMerchantService.updateFsSubMerchant(fsSubMerchant));
+    }
+
+    /**
+     * 删除汇付-子商户信息
+     */
+    @PreAuthorize("@ss.hasPermi('his:merchant:remove')")
+    @Log(title = "汇付-子商户信息", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(fsSubMerchantService.deleteFsSubMerchantByIds(ids));
+    }
+}

+ 72 - 0
fs-admin/src/main/java/com/fs/hisStore/task/MallStoreTask.java

@@ -28,10 +28,13 @@ import com.fs.his.utils.ConfigUtil;
 import com.fs.hisStore.domain.*;
 import com.fs.hisStore.dto.DateComparisonConfigDTO;
 import com.fs.hisStore.enums.ShipperCodeEnum;
+import com.fs.his.enums.FsUserIntegralLogTypeEnum;
 import com.fs.hisStore.mapper.FsStoreOrderItemScrmMapper;
 import com.fs.hisStore.mapper.FsStoreOrderScrmMapper;
 import com.fs.hisStore.mapper.FsStorePaymentScrmMapper;
 import com.fs.hisStore.mapper.FsStoreProductAttrValueScrmMapper;
+import com.fs.hisStore.mapper.FsUserScrmMapper;
+import com.fs.hisStore.mapper.FsUserIntegralLogsScrmMapper;
 import com.fs.hisStore.param.*;
 import com.fs.hisStore.service.*;
 import com.fs.huifuPay.domain.HuiFuQueryOrderResult;
@@ -43,6 +46,7 @@ import com.fs.pay.pay.dto.OrderQueryDTO;
 import com.fs.pay.service.IPayService;
 import com.fs.store.config.StoreConfig;
 import com.fs.system.service.ISysConfigService;
+import cn.hutool.json.JSONUtil;
 import com.fs.ybPay.domain.OrderResult;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
@@ -154,6 +158,12 @@ public class MallStoreTask
     @Autowired
     private ConfigUtil configUtil;
 
+    @Autowired
+    private FsUserScrmMapper fsUserScrmMapper;
+
+    @Autowired
+    private FsUserIntegralLogsScrmMapper fsUserIntegralLogsScrmMapper;
+
     @Autowired
     private FsCourseRedPacketLogMapper fsCourseRedPacketLogMapper;
 
@@ -748,4 +758,66 @@ public class MallStoreTask
         }
     }
 
+    /**
+     * 购物积分发放定时任务
+     * 查询条件:订单状态为已收货(2)或交易完成(3),超过7天,创建时间在2026-05-1以后,
+     *           是否领取购物积分=0,已支付,商城订单(order_type=1)
+     * 积分规则:用户实际支付金额(pay_money) 1:1 向下取整折算积分(eg: 19.9元=19积分)
+     */
+    public void grantShoppingPoints() {
+        // 检查配置开关
+        String storeConfigJson = configService.selectConfigByKey("his.store");
+        if (StringUtils.isBlank(storeConfigJson)) {
+            return;
+        }
+        com.fs.hisStore.config.StoreConfig storeConfig = JSONUtil.toBean(storeConfigJson, com.fs.hisStore.config.StoreConfig.class);
+        if (storeConfig == null || !Boolean.TRUE.equals(storeConfig.getEnableShoppingPoints())) {
+            return;
+        }
+
+        // 查询符合条件的订单(SQL直接过滤)
+        String cutoffDate = "2026-05-01 00:00:00";
+        String sevenDaysAgo = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(DateUtils.addDays(new Date(), -7));
+        List<FsStoreOrderScrm> orders = fsStoreOrderMapper.selectShoppingPointsPendingOrders(cutoffDate, sevenDaysAgo);
+
+        int count = 0;
+        for (FsStoreOrderScrm order : orders) {
+            // 用户ID为空跳过
+            if (order.getUserId() == null) continue;
+
+            // 计算积分:pay_money 向下取整
+            long points = order.getPayMoney().setScale(0, BigDecimal.ROUND_DOWN).longValue();
+            if (points <= 0) continue;
+
+            try {
+                // 发放积分
+                fsUserScrmMapper.incrIntegral(order.getUserId(), BigDecimal.valueOf(points));
+
+                // 记录积分日志 logType=5 购物积分
+                FsUserIntegralLogsScrm logs = new FsUserIntegralLogsScrm();
+                logs.setUserId(order.getUserId());
+                logs.setIntegral(BigDecimal.valueOf(points));
+                logs.setLogType(FsUserIntegralLogTypeEnum.TYPE_30.getValue()); // 购物积分发放
+                logs.setStatus(1);
+                logs.setBusinessId(String.valueOf(order.getId()));
+                logs.setBusinessType(1); // 商城订单
+                logs.setCreateTime(new Date());
+                FsUserScrm user = fsUserScrmMapper.selectFsUserByUserId(order.getUserId());
+                logs.setBalance(user != null && user.getIntegral() != null ? BigDecimal.valueOf(user.getIntegral()) : BigDecimal.valueOf(points));
+                fsUserIntegralLogsScrmMapper.insertFsUserIntegralLogs(logs);
+
+                // 更新订单为已领取
+                FsStoreOrderScrm updateOrder = new FsStoreOrderScrm();
+                updateOrder.setId(order.getId());
+                updateOrder.setShoppingPointsClaimed(1);
+                fsStoreOrderMapper.updateFsStoreOrder(updateOrder);
+
+                count++;
+            } catch (Exception e) {
+                log.error("购物积分发放异常,订单ID={}", order.getId(), e);
+            }
+        }
+        log.info("购物积分发放完成,本次发放{}笔订单", count);
+    }
+
 }

+ 102 - 0
fs-company-app/src/main/java/com/fs/app/controller/StoreProductController.java

@@ -0,0 +1,102 @@
+package com.fs.app.controller;
+
+import com.fs.app.annotation.Login;
+import com.fs.common.core.domain.R;
+import com.fs.common.utils.StringUtils;
+import com.fs.hisStore.domain.FsStoreProductAttrValueScrm;
+import com.fs.hisStore.domain.FsStoreProductScrm;
+import com.fs.hisStore.service.IFsStoreProductAttrValueScrmService;
+import com.fs.hisStore.service.IFsStoreProductScrmService;
+import com.fs.hisStore.vo.FsStoreProductListVO;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+
+@Api("商城产品")
+@RestController
+@RequestMapping("/app/storeProduct")
+public class StoreProductController extends AppBaseController {
+
+    @Autowired
+    private IFsStoreProductScrmService fsStoreProductService;
+
+    @Autowired
+    private IFsStoreProductAttrValueScrmService attrValueService;
+
+    /**
+     * 查询本公司下的商城产品列表
+     */
+    @Login
+    @ApiOperation("查询本公司下的商城产品列表")
+    @GetMapping("/list")
+    public R list(Integer page, Integer pageSize, String productName) {
+        if (page == null || page < 1) {
+            page = 1;
+        }
+        if (pageSize == null || pageSize < 1) {
+            pageSize = 10;
+        }
+        PageHelper.startPage(page, pageSize);
+        FsStoreProductScrm query = new FsStoreProductScrm();
+        query.setIsDel(0);
+        query.setIsShow(1);
+        query.setIsAudit("1");
+        query.setCompanyIds(String.valueOf(getCompanyId()));
+        if (StringUtils.isNotEmpty(productName)) {
+            query.setProductName(productName);
+        }
+        List<FsStoreProductListVO> list = fsStoreProductService.selectFsStoreProductListVO(query);
+        PageInfo<FsStoreProductListVO> pageInfo = new PageInfo<>(list);
+        return R.ok().put("data", pageInfo);
+    }
+
+    /**
+     * 查看商品详情
+     */
+    @Login
+    @ApiOperation("查看商品详情")
+    @GetMapping("/{productId}")
+    public R getInfo(@PathVariable("productId") Long productId) {
+        FsStoreProductScrm product = fsStoreProductService.selectFsStoreProductById(productId);
+        if (product == null) {
+            return R.error("商品不存在");
+        }
+        // 校验该商品是否属于当前公司
+        String companyIds = product.getCompanyIds();
+        Long companyId = getCompanyId();
+        if (StringUtils.isEmpty(companyIds) || !containsCompanyId(companyIds, companyId)) {
+            return R.error("无权查看该商品");
+        }
+        // 查询商品规格属性值
+        FsStoreProductAttrValueScrm attrQuery = new FsStoreProductAttrValueScrm();
+        attrQuery.setProductId(productId);
+        List<FsStoreProductAttrValueScrm> values = attrValueService.selectFsStoreProductAttrValueList(attrQuery);
+        return R.ok().put("product", product).put("values", values);
+    }
+
+    /**
+     * 判断 companyIds 字符串中是否包含指定 companyId
+     * companyIds 格式可能是 "123" 或 "123,456" 等
+     */
+    private boolean containsCompanyId(String companyIds, Long companyId) {
+        if (StringUtils.isEmpty(companyIds)) {
+            return false;
+        }
+        String[] ids = companyIds.split(",");
+        for (String id : ids) {
+            if (id.trim().equals(String.valueOf(companyId))) {
+                return true;
+            }
+        }
+        return false;
+    }
+}

+ 1 - 1
fs-company/src/main/java/com/fs/FsCompanyApplication.java

@@ -11,7 +11,7 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
  */
 @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
 @EnableTransactionManagement
-//@EnableAsync
+@EnableAsync
 public class FsCompanyApplication
 {
     public static void main(String[] args)

+ 26 - 28
fs-company/src/main/java/com/fs/company/controller/company/CompanyVoiceRoboticCallLogAddwxController.java

@@ -1,42 +1,32 @@
 package com.fs.company.controller.company;
 
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-import com.fs.common.utils.StringUtils;
-import com.fs.company.domain.CompanyVoiceRoboticCallLogCallphone;
-import com.fs.company.domain.CompanyWxClient;
-import com.fs.company.vo.CompanyVoiceRoboticCallLogAddWxExportVO;
-import com.fs.company.vo.CompanyVoiceRoboticCallLogAddwxVO;
-import com.fs.company.vo.CompanyVoiceRoboticCallLogCallPhoneVO;
-import com.fs.company.vo.CompanyVoiceRoboticCallLogCount;
-import org.springframework.beans.BeanUtils;
-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.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.ServletUtils;
+import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.company.domain.CompanyVoiceRoboticCallLogAddwx;
+import com.fs.company.domain.CompanyWxClient;
 import com.fs.company.service.ICompanyVoiceRoboticCallLogAddwxService;
-import com.fs.common.utils.poi.ExcelUtil;
-import com.fs.common.core.page.TableDataInfo;
+import com.fs.company.service.impl.CompanyUserServiceImpl;
+import com.fs.company.vo.CompanyVoiceRoboticCallLogAddWxExportVO;
+import com.fs.company.vo.CompanyVoiceRoboticCallLogAddwxVO;
+import com.fs.company.vo.CompanyVoiceRoboticCallLogCount;
+import com.fs.framework.security.LoginUser;
+import com.fs.framework.service.TokenService;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.*;
+import java.util.stream.Collectors;
 
 /**
  * 调用日志_加微信Controller
- * 
+ *
  * @author fs
  * @date 2026-01-15
  */
@@ -47,6 +37,12 @@ public class CompanyVoiceRoboticCallLogAddwxController extends BaseController
     @Autowired
     private ICompanyVoiceRoboticCallLogAddwxService companyVoiceRoboticCallLogAddwxService;
 
+    @Autowired
+    private TokenService tokenService;
+
+    @Autowired
+    private CompanyUserServiceImpl companyUserService;
+
     /**
      * 查询调用日志_加微信列表
      */
@@ -88,6 +84,8 @@ public class CompanyVoiceRoboticCallLogAddwxController extends BaseController
     public TableDataInfo listAll(CompanyVoiceRoboticCallLogAddwx companyVoiceRoboticCallLogAddwx)
     {
         startPage();
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        companyVoiceRoboticCallLogAddwx.setCompanyId(loginUser.getCompany().getCompanyId());
         List<CompanyVoiceRoboticCallLogAddwxVO> list = companyVoiceRoboticCallLogAddwxService.listAll(companyVoiceRoboticCallLogAddwx);
         TableDataInfo dataTable = getDataTable(list);
         Map<String, Long> countMap = companyVoiceRoboticCallLogAddwxService.countListAll(companyVoiceRoboticCallLogAddwx);

+ 34 - 0
fs-ipad-task/src/main/java/com/fs/app/service/IpadSendServer.java

@@ -7,6 +7,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.exception.base.BaseException;
+import com.fs.common.utils.CloudHostUtils;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.date.DateUtil;
 import com.fs.common.utils.spring.SpringUtils;
@@ -470,6 +471,39 @@ public class IpadSendServer {
                 }
             }
         }
+
+        //新客对话
+        if (qwSopLogs.getSendType() == 4 && CloudHostUtils.hasCloudHostName("今正科技")) {
+            Long fsUserId = qwExternalContactMapper.selectQwExternalContactTimeById(qwSopLogs.getExternalId());
+
+            List<QwSopCourseFinishTempSetting.Setting> settingList = setting.getSetting();
+
+            // 检查是否存在符合条件的内容
+            boolean hasValidContent;
+
+            if (fsUserId == null) {
+                // 用户未注册:需要存在标记为 2(未注册) 或 3(通用) 的内容
+                hasValidContent = settingList.stream()
+                        .anyMatch(item -> "2".equals(item.getOnlyUnregistered()) || "3".equals(item.getOnlyUnregistered()));
+
+                if (!hasValidContent) {
+                    log.warn("SOP_LOG_ID:{}, 新客对话-用户未注册不发送已注册内容", qwSopLogs.getId());
+                    qwSopLogsService.updateQwSopLogsByWatchLogType(qwSopLogs.getId(), "新客对话-用户未注册不发送已注册内容");
+                    return false;
+                }
+            } else {
+                // 用户已注册:需要存在标记为 1(已注册) 或 3(通用) 的内容
+                hasValidContent = settingList.stream()
+                        .anyMatch(item -> "1".equals(item.getOnlyUnregistered()) || "3".equals(item.getOnlyUnregistered()));
+
+                if (!hasValidContent) {
+                    log.warn("SOP_LOG_ID:{}, 新客对话-用户已注册且不发送已注册内容", qwSopLogs.getId());
+                    qwSopLogsService.updateQwSopLogsByWatchLogType(qwSopLogs.getId(), "新客对话-用户已注册且不发送已注册内容");
+                    return false;
+                }
+            }
+        }
+
         return true;
     }
 

+ 28 - 4
fs-qw-task/src/main/java/com/fs/app/controller/CommonController.java

@@ -35,6 +35,7 @@ import com.fs.qwApi.service.QwApiService;
 import com.fs.sop.mapper.QwSopLogsMapper;
 import com.fs.sop.mapper.QwSopMapper;
 import com.fs.sop.mapper.SopUserLogsMapper;
+import com.fs.sop.params.QwSopAutoByTags;
 import com.fs.sop.service.*;
 import com.fs.sop.vo.QwSopLogsDoSendListTVO;
 import com.fs.store.service.IFsUserCourseCountService;
@@ -52,10 +53,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 import com.fs.app.task.qwTask;
 
-import java.time.Duration;
-import java.time.LocalDate;
-import java.time.LocalDateTime;
-import java.time.ZoneId;
+import java.time.*;
 import java.time.format.DateTimeFormatter;
 import java.util.*;
 
@@ -165,7 +163,33 @@ public class CommonController {
     @Autowired
     private SopWxLogsTaskService sopWxLogsTaskService;
 
+    @Autowired
+    private AsyncQwAiChatSopService asyncQwAiChatSopService;
+
+    @GetMapping("/newRoomLinkAllow")
+    public void newRoomLinkAllow() {
+
+
+        Set<String> combinedTagsSet = new HashSet<>();
+        combinedTagsSet.add("et_-lxDwAAlzHGQGKcgw0fNRGnGN-blw");
+        List<String> combinedTagsList = new ArrayList<>(combinedTagsSet);
+
+        //分时段里符合的标签-再用来创建自动SOP
+        QwSopAutoByTags qwSopAutoByTags = new QwSopAutoByTags();
+        qwSopAutoByTags.setQwUserId("32189");
+        qwSopAutoByTags.setCorpId("wwce259dbd92b6f0e7");
+        qwSopAutoByTags.setTagsIdsSelectList(combinedTagsList);
+        qwSopAutoByTags.setSendType(2);
 
+        QwUser qwUser = qwUserMapper.selectQwUserById(32189L);
+        LocalDate currentDate = LocalDate.now();
+//        LocalDate currentDate = LocalDate.now().plusDays(3);
+        LocalTime localTime = LocalTime.now();
+
+        asyncQwAiChatSopService.executeQwAiChatSop(qwSopAutoByTags, "ShiGuoWei", qwUser, "wm6JoFEQAAtVLeG1vCBPYu_S6wRaAb_g"
+                , "西红柿", 16292365L, null, currentDate, localTime);
+
+    }
 
     @GetMapping("/roomLinkAllow")
     public R roomLinkAllow() {

+ 8 - 8
fs-service/src/main/java/com/fs/course/domain/FsUserCourseComment.java

@@ -7,6 +7,11 @@ import lombok.Data;
 /**
  * 课堂评论对象 fs_user_course_comment
  *
+ * type 说明:
+ *   1:评论
+ *   2:回复
+ *   3:精选留言(通过Word文档批量导入的用户留言)
+ *
  * @author fs
  * @date 2024-05-15
  */
@@ -26,7 +31,7 @@ public class FsUserCourseComment extends BaseEntity
     @Excel(name = "课堂id")
     private Long courseId;
 
-    /** 评论类型 1:评论,2:回复 */
+    /** 评论类型 1:评论,2:回复,3:精选留言(Word批量导入) */
     @Excel(name = "评论类型")
     private Long type;
 
@@ -50,12 +55,7 @@ public class FsUserCourseComment extends BaseEntity
 
     private Integer isDel;
 
-    /** 图片URL列表(JSON数组) */
-    @Excel(name = "图片")
-    private String images;
-
-    /** 视频URL列表(JSON数组) */
-    @Excel(name = "视频")
-    private String videos;
+    /** 昵称(精选留言导入时使用) */
+    private String nickName;
 
 }

+ 3 - 0
fs-service/src/main/java/com/fs/course/domain/FsUserCourseVideo.java

@@ -139,6 +139,9 @@ public class FsUserCourseVideo extends BaseEntity
     /** 看课奖励类型(展示文案,可配置) */
     private String rewardType;
 
+    /** 课程介绍图片URL */
+    private String courseIntroImg;
+
     @TableField(exist = false)
     private Integer showProduct; //1不展示疗法,0展示疗法
 

+ 3 - 1
fs-service/src/main/java/com/fs/course/enums/MiniProgramAgreementEnum.java

@@ -9,7 +9,9 @@ public enum MiniProgramAgreementEnum {
     USER_HEALTH("userHealth", "健康客服协议"),
     VIP_SERVICE("vipService", "会员服务协议"),
     VIP_AUTOMATIC_SERVICE("vipAutomaticService", "会员自动续费协议"),
-    USER_REMOVE_SERVICE("userRemoveService", "用户注销协议");
+    USER_REMOVE_SERVICE("userRemoveService", "用户注销协议"),
+    APP_USER_AGREEMENT("appUserAgreement", "APP用户协议"),
+    APP_PRIVACY_AGREEMENT("appPrivacyAgreement", "APP隐私协议");
     private String code;
     private String message;
     MiniProgramAgreementEnum(String code, String message) {

+ 17 - 4
fs-service/src/main/java/com/fs/course/mapper/FsUserCourseCommentMapper.java

@@ -11,6 +11,7 @@ import com.fs.course.vo.FsUserCourseCommentVO;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 import org.apache.ibatis.annotations.Update;
+import org.checkerframework.checker.units.qual.A;
 
 /**
  * 课堂评论Mapper接口
@@ -97,8 +98,13 @@ public interface FsUserCourseCommentMapper
             "<if test=\"sortType != null and sortType == 2\"> order by c.comment_id desc </if>" +
             "</script>"})
     List<FsUserCourseCommentListUVO> selectFsUserCourseCommentListUVO(FsUserCourseCommentUParam param);
-    @Select("select c.*,u.nick_name,u.avatar,tu.nick_name to_nick_name from fs_user_course_comment c LEFT JOIN fs_user u ON c.user_id=u.user_id LEFT  JOIN fs_user tu ON tu.user_id =c.to_user_id where c.user_id=#{userId}")
-    List<FsUserCourseCommentListUVO> selectFsUserCourseCommentMyListUVO(Long userId);
+    @Select({"<script> " +
+            "select c.*,u.nick_name,u.avatar,tu.nick_name to_nick_name from fs_user_course_comment c LEFT JOIN fs_user u ON c.user_id=u.user_id LEFT  JOIN fs_user tu ON tu.user_id =c.to_user_id " +
+            "where c.user_id=#{userId} " +
+            "<if test='courseId != null'> and c.course_id = #{courseId}</if>" +
+            " order by c.create_time desc" +
+            "</script>"})
+    List<FsUserCourseCommentListUVO> selectFsUserCourseCommentMyListUVO(FsUserCourseCommentUParam param);
 
     @Update("update fs_user_course_comment set likes=likes+1 where comment_id=#{commentId}")
     int addLikes(Long commentId);
@@ -119,9 +125,9 @@ public interface FsUserCourseCommentMapper
     List<FsUserCourseCommentListUVO> selectFsUserCourseCommentListVOByCourseId(Long courseId);
 
     @Select({"<script> " +
-            "SELECT c.*, u.nick_name, u.avatar, tu.nick_name to_nick_name, " +
+            "SELECT c.*, u.nick_name, u.avatar, tu.nick_name to_nick_name " +
             "<if test='userId != null and userId != \"\"'>" +
-            "   IFNULL(l.id, 0) AS is_like " +
+            "   ,IFNULL(l.id, 0) AS is_like " +
             "</if>" +
             "FROM fs_user_course_comment c " +
             "LEFT JOIN fs_user u ON c.user_id = u.user_id " +
@@ -144,4 +150,11 @@ public interface FsUserCourseCommentMapper
             "<if test=\"sortType != null and sortType == 2\"> order by c.comment_id desc </if>" +
             "</script>"})
     List<FsUserCourseCommentListUVO> selectFsUserCourseCommentListUVOAll(FsUserCourseCommentUParam param);
+
+    List<FsUserCourseComment> selectFsUserCourseCommentListByTypeAndCourseId(@Param("type") int type,@Param("courseId") Long courseId);
+
+    /**
+     * 按课程ID和用户ID列表批量查询评论(type=1,未删除)
+     */
+    List<FsUserCourseComment> selectCommentsByCourseIdAndUserIds(@Param("courseId") Long courseId, @Param("userIds") List<Long> userIds);
 }

+ 1 - 1
fs-service/src/main/java/com/fs/course/mapper/FsUserCourseMapper.java

@@ -204,7 +204,7 @@ public interface FsUserCourseMapper
     List<FsUserCourseListUVO> selectFsUserCourseCommentListUVO(@Param("maps") FsUserCourseListUParam param);
 
 
-    @Select("select v.video_id,v.title,v.course_id,v.video_url,v.question_bank_id,v.is_del,v.is_on_put," +
+    @Select("select v.video_id,v.title,v.course_id,v.video_url,v.question_bank_id,v.is_del,v.is_on_put,v.course_intro_img," +
             "SEC_TO_TIME(c.duration) as total_duration,c.views,c.course_name,c.description,c.img_url,c.config_json  " +
             " from fs_user_course_video v " +
             "left join fs_user_course c on v.course_id = c.course_id " +

+ 3 - 1
fs-service/src/main/java/com/fs/course/service/IFsUserCourseCommentService.java

@@ -75,10 +75,12 @@ public interface IFsUserCourseCommentService
 
     List<FsUserCourseCommentReplyListUVO> selectFsUserCourseCommentReplyListUVO(Long commentId, String userId);
 
-    List<FsUserCourseCommentListUVO> selectFsUserCourseCommentMyListUVO(Long userId);
+    List<FsUserCourseCommentListUVO> selectFsUserCourseCommentMyListUVO(FsUserCourseCommentUParam param);
 
     int updateLikes(Long commentId, Integer type);
 
     int addComment(Long parentId);
     int minusComment(Long parentId);
+
+    List<FsUserCourseComment> selectFsUserCourseCommentListByTypeAndCourseId(int i, Long courseId);
 }

+ 65 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java

@@ -189,6 +189,9 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
     @Autowired
     private FsCourseAnswerLogsMapper fsCourseAnswerLogsMapper;
 
+    @Autowired
+    private FsUserCourseCommentMapper fsUserCourseCommentMapper;
+
     @Autowired
     private FsUserMapper userMapper;
 
@@ -1915,6 +1918,8 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
         }
         List<CourseStatisticsUserDetailVO> list = fsCourseWatchLogMapper.selectCourseStatisticsUserDetailExportList(param);
         applyCourseRatingAnswerDisplay(list);
+        // 查询用户留言并填充
+        applyUserComments(list, param.getVideoId());
         return list;
     }
 
@@ -1934,6 +1939,66 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
         }
     }
 
+    /**
+     * 查询用户留言并填充到导出VO中
+     * 1. 通过videoId获取courseId
+     * 2. 收集所有userId,批量查询评论
+     * 3. 按userId分组,拼接评论内容填充到对应VO
+     */
+    private void applyUserComments(List<CourseStatisticsUserDetailVO> list, Long videoId) {
+        if (CollectionUtils.isEmpty(list)) {
+            return;
+        }
+        // 通过videoId获取courseId
+        FsUserCourseVideo video = fsUserCourseVideoMapper.selectFsUserCourseVideoByVideoId(videoId);
+        if (video == null || video.getCourseId() == null) {
+            return;
+        }
+        Long courseId = video.getCourseId();
+
+        // 收集所有userId
+        List<Long> userIds = list.stream()
+                .map(CourseStatisticsUserDetailVO::getUserId)
+                .filter(Objects::nonNull)
+                .distinct()
+                .collect(Collectors.toList());
+        if (userIds.isEmpty()) {
+            return;
+        }
+
+        // 批量查询评论(type=1 评论,is_del=0 未删除)
+        List<FsUserCourseComment> comments = fsUserCourseCommentMapper.selectCommentsByCourseIdAndUserIds(courseId, userIds);
+        if (CollectionUtils.isEmpty(comments)) {
+            return;
+        }
+
+        // 按userId分组,拼接评论内容
+        Map<Long, List<FsUserCourseComment>> commentMap = comments.stream()
+                .collect(Collectors.groupingBy(FsUserCourseComment::getUserId));
+
+        for (CourseStatisticsUserDetailVO vo : list) {
+            if (vo == null || vo.getUserId() == null) {
+                continue;
+            }
+            List<FsUserCourseComment> userComments = commentMap.get(vo.getUserId());
+            if (userComments != null && !userComments.isEmpty()) {
+                SimpleDateFormat commentFmt = new SimpleDateFormat("yyyy-MM-dd HH:mm");
+                String joined = userComments.stream()
+                        .map(c -> {
+                            String content = c.getContent();
+                            if (content == null || content.trim().isEmpty()) {
+                                return "";
+                            }
+                            String timePrefix = c.getCreateTime() != null ? "[" + commentFmt.format(c.getCreateTime()) + "] " : "";
+                            return timePrefix + content.trim();
+                        })
+                        .filter(s -> !s.isEmpty())
+                        .collect(Collectors.joining(";"));
+                vo.setUserComment(joined);
+            }
+        }
+    }
+
     @Override
     public List<AppWatchLogReportVO> selectUserAppWatchLogReportVO(FsCourseWatchLogStatisticsListParam param) {
         if (StringUtils.isNotEmpty(param.getUserPhone())) {

+ 8 - 2
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseCommentServiceImpl.java

@@ -1,5 +1,6 @@
 package com.fs.course.service.impl;
 
+import java.util.Collections;
 import java.util.List;
 import com.fs.common.utils.DateUtils;
 import com.fs.course.param.FsUserCourseCommentParam;
@@ -127,8 +128,8 @@ public class FsUserCourseCommentServiceImpl implements IFsUserCourseCommentServi
     }
 
     @Override
-    public List<FsUserCourseCommentListUVO> selectFsUserCourseCommentMyListUVO(Long userId) {
-        return fsUserCourseCommentMapper.selectFsUserCourseCommentMyListUVO(userId);
+    public List<FsUserCourseCommentListUVO> selectFsUserCourseCommentMyListUVO(FsUserCourseCommentUParam param) {
+        return fsUserCourseCommentMapper.selectFsUserCourseCommentMyListUVO(param);
     }
 
     @Override
@@ -149,6 +150,11 @@ public class FsUserCourseCommentServiceImpl implements IFsUserCourseCommentServi
         return fsUserCourseCommentMapper.minusComment(parentId);
     }
 
+    @Override
+    public List<FsUserCourseComment> selectFsUserCourseCommentListByTypeAndCourseId(int type, Long courseId) {
+        return fsUserCourseCommentMapper.selectFsUserCourseCommentListByTypeAndCourseId(type,courseId);
+    }
+
     @Override
     public List<FsUserCourseCommentReplyListUVO> selectFsUserCourseCommentReplyListUVO(Long commentId, String userId) {
         return fsUserCourseCommentMapper.selectFsUserCourseCommentReplyListUVO(commentId, userId);

+ 4 - 0
fs-service/src/main/java/com/fs/course/vo/CourseStatisticsUserDetailVO.java

@@ -37,4 +37,8 @@ public class CourseStatisticsUserDetailVO implements Serializable {
      */
     @Excel(name = "课程评分", width = 60)
     private String courseRating;
+
+    /** 用户留言(该课程下该用户的所有评论拼接) */
+    @Excel(name = "用户留言", width = 80)
+    private String userComment;
 }

+ 4 - 0
fs-service/src/main/java/com/fs/course/vo/FsUserCourseVideoH5VO.java

@@ -43,4 +43,8 @@ public class FsUserCourseVideoH5VO extends BaseEntity
     /** 是否上下架 **/
     private Integer isOnPut;
 
+    /** 课程介绍图片URL */
+    private String courseIntroImg;
+
+
 }

+ 2 - 0
fs-service/src/main/java/com/fs/course/vo/FsUserCourseVideoQVO.java

@@ -72,6 +72,8 @@ public class FsUserCourseVideoQVO extends BaseEntity {
 
     private String lineTwo; //线路二 电信 前缀ctev
     private String lineThree; //线路三 华为云obs
+    /** 课程介绍图片URL */
+    private String courseIntroImg;
     private Integer uploadType;
     private String redPacketMoney;
 

+ 5 - 0
fs-service/src/main/java/com/fs/erp/dto/ShopOrderDTO.java

@@ -26,6 +26,11 @@ public class ShopOrderDTO implements Serializable {
      */
     private String soId;
 
+    /**
+     * 最晚发货时间;发货前可更新
+     */
+    private String planDeliveryDate;
+
     /**
      * 订单日期
      */

+ 56 - 0
fs-service/src/main/java/com/fs/erp/service/impl/JSTErpOrderServiceImpl.java

@@ -271,6 +271,7 @@ public class JSTErpOrderServiceImpl implements IErpOrderService {
 
         // 订单商品项列表
         List<OrderItemDTO> itemDTOList = new ArrayList<>();
+        String presaleShippingTime = null; // 预售最晚发货时间
 
         List<FsStoreOrderItemVO> fsStoreOrderItemVOS = fsStoreOrderItemScrmService.selectFsStoreOrderItemListByOrderId(fsStoreOrder.getId());
         log.info("fsStoreOrderItemVOS==========>{}",fsStoreOrderItemVOS);
@@ -296,9 +297,36 @@ public class JSTErpOrderServiceImpl implements IErpOrderService {
             orderItemDTO.setQty(item.getNum().intValue());
             orderItemDTO.setOuterOiId(String.format("%s%s",fsStoreOrder.getOrderCode(),item.getItemId()));
             itemDTOList.add(orderItemDTO);
+
+            // 检查商品是否有presale标签,收集最晚发货时间
+            try {
+                if (StringUtils.isNotBlank(fsStoreProduct.getTagInfo())) {
+                    JSONObject tagObj = JSON.parseObject(fsStoreProduct.getTagInfo());
+                    Object typeObj = tagObj.get("type");
+                    boolean isPresale = false;
+                    if (typeObj instanceof com.alibaba.fastjson.JSONArray) {
+                        isPresale = ((com.alibaba.fastjson.JSONArray) typeObj).contains("presale");
+                    } else if (typeObj instanceof String) {
+                        isPresale = "presale".equals(typeObj);
+                    }
+                    if (isPresale && tagObj.getString("shippingTime") != null) {
+                        String shippingTime = tagObj.getString("shippingTime");
+                        if (presaleShippingTime == null || shippingTime.compareTo(presaleShippingTime) > 0) {
+                            presaleShippingTime = shippingTime;
+                        }
+                    }
+                }
+            } catch (Exception e) {
+                log.error("检查商品presale标签失败:{}", e.getMessage());
+            }
         }
         shopOrderDTO.setItems(itemDTOList);
 
+        // 如果有预售发货时间,设置到planDeliveryDate
+        if (presaleShippingTime != null) {
+            shopOrderDTO.setPlanDeliveryDate(presaleShippingTime);
+        }
+
         // 实际支付金额
         PaymentDTO paymentDTO = new PaymentDTO();
         paymentDTO.setAmount(fsStoreOrder.getPayMoney().doubleValue());
@@ -397,6 +425,7 @@ public class JSTErpOrderServiceImpl implements IErpOrderService {
 
         // 订单商品项列表
         List<OrderItemDTO> itemDTOList = new ArrayList<>();
+        String presaleShippingTime = null; // 预售最晚发货时间
 
         List<LiveOrderItem> liveOrderItems = liveOrderItemMapper.selectLiveOrderItemByOrderId(liveOrder.getOrderId());
         log.info("liveOrderItems==========>{}",liveOrderItems);
@@ -422,9 +451,36 @@ public class JSTErpOrderServiceImpl implements IErpOrderService {
             orderItemDTO.setQty(item.getNum().intValue());
             orderItemDTO.setOuterOiId(String.format("%s%s",liveOrder.getOrderCode(),item.getItemId()));
             itemDTOList.add(orderItemDTO);
+
+            // 检查商品是否有presale标签,收集最晚发货时间
+            try {
+                if (StringUtils.isNotBlank(fsStoreProduct.getTagInfo())) {
+                    JSONObject tagObj = JSON.parseObject(fsStoreProduct.getTagInfo());
+                    Object typeObj = tagObj.get("type");
+                    boolean isPresale = false;
+                    if (typeObj instanceof com.alibaba.fastjson.JSONArray) {
+                        isPresale = ((com.alibaba.fastjson.JSONArray) typeObj).contains("presale");
+                    } else if (typeObj instanceof String) {
+                        isPresale = "presale".equals(typeObj);
+                    }
+                    if (isPresale && tagObj.getString("shippingTime") != null) {
+                        String shippingTime = tagObj.getString("shippingTime");
+                        if (presaleShippingTime == null || shippingTime.compareTo(presaleShippingTime) > 0) {
+                            presaleShippingTime = shippingTime;
+                        }
+                    }
+                }
+            } catch (Exception e) {
+                log.error("检查商品presale标签失败:{}", e.getMessage());
+            }
         }
         shopOrderDTO.setItems(itemDTOList);
 
+        // 如果有预售发货时间,设置到planDeliveryDate
+        if (presaleShippingTime != null) {
+            shopOrderDTO.setPlanDeliveryDate(presaleShippingTime);
+        }
+
         // 实际支付金额
         PaymentDTO paymentDTO = new PaymentDTO();
         paymentDTO.setAmount(liveOrder.getPayMoney().doubleValue());

+ 62 - 1
fs-service/src/main/java/com/fs/fastGpt/service/impl/AiHookServiceImpl.java

@@ -7,6 +7,7 @@ import com.alibaba.fastjson.JSONArray;
 import com.fs.common.annotation.Excel;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.redis.RedisCache;
+import com.fs.common.utils.CloudHostUtils;
 import com.fs.company.domain.CompanyConfig;
 import com.fs.company.domain.CompanyUser;
 import com.fs.company.mapper.CompanyConfigMapper;
@@ -51,6 +52,7 @@ import com.fs.qw.domain.*;
 import com.fs.qw.mapper.*;
 import com.fs.qw.param.QwAutoTagsRulesTags;
 import com.fs.qw.service.*;
+import com.fs.qw.vo.QwSopCourseFinishTempSetting;
 import com.fs.qwApi.domain.QwResult;
 import com.fs.qwApi.param.QwEditUserTagParam;
 import com.fs.qwApi.param.QwSendMsgParam;
@@ -489,7 +491,12 @@ public class AiHookServiceImpl implements AiHookService {
                 if(!contentEmj.isEmpty()){
                     addSaveAiMsg(1,1,contentEmj,user,fastGptChatSession.getSessionId(),role.getRoleId(),qwExternalContacts,fastGptChatSession.getUserId(),null,null,null);
                     //通过用户发送的对话去查询用户是否为新客,是就删除sop,否就不做处理
-                    cleanNewUserDialogue(user, qwExternalContacts);
+                    if (CloudHostUtils.hasCloudHostName("今正科技")){
+                        cleanNewUserDialogueSXJZ(user, qwExternalContacts);
+                    }else {
+                        cleanNewUserDialogue(user, qwExternalContacts);
+                    }
+
                     //用户是未回复状态
                     if(qwExternalContacts.getIsReply() == 0){
                         qwExternalContactMapper.updateQwExternalContactIsRePlyById(qwExternalContacts.getId());
@@ -827,6 +834,60 @@ public class AiHookServiceImpl implements AiHookService {
         }
     }
 
+    private void cleanNewUserDialogueSXJZ(QwUser user, QwExternalContact qwExternalContacts) {
+        String redisKey = "qwNewChat:" + user.getQwUserId() + ":" + user.getCorpId() + ":" + qwExternalContacts.getExternalUserId();
+        String key  = (String) redisCache.getCacheObject(redisKey);
+        if(!StringUtil.strIsNullOrEmpty(key)){
+            try {
+                QwSopLogs qwSopLogs = new QwSopLogs();
+                qwSopLogs.setQwUserid(user.getQwUserId());
+                qwSopLogs.setCorpId(user.getCorpId());
+                qwSopLogs.setExternalUserId(qwExternalContacts.getExternalUserId());
+                qwSopLogs.setSendStatus(3L);
+                qwSopLogs.setSendType(4);
+                List<QwSopLogs> qwSopLogsList = qwSopLogsMapper.selectQwSopLogsList(qwSopLogs);
+
+                if(qwSopLogsList != null && !qwSopLogsList.isEmpty()){
+                    List<QwSopLogs> logsToUpdate = new ArrayList<>();
+
+                    for (QwSopLogs sopLogs : qwSopLogsList) {
+                        if (sopLogs.getContentJson() != null) {
+                            try {
+                                QwSopCourseFinishTempSetting setting = JSON.parseObject(sopLogs.getContentJson(), QwSopCourseFinishTempSetting.class);
+
+                                if (setting != null && setting.getSetting() != null) {
+                                    List<QwSopCourseFinishTempSetting.Setting> settingList = setting.getSetting();
+
+                                    // 检查是否存在 onlyUnregistered == 3 的内容
+                                    boolean hasUniversalContent = settingList.stream()
+                                            .anyMatch(item -> "3".equals(item.getOnlyUnregistered()));
+
+                                    if (hasUniversalContent) {
+                                        logsToUpdate.add(sopLogs);
+                                    }
+                                }
+                            } catch (Exception e) {
+                                sopLogs.setRemark("内容解析失败作废");
+                                logsToUpdate.add(sopLogs);
+                                log.error("解析SOP日志contentJson失败,logId:{}", sopLogs.getId(), e);
+                            }
+                        }
+                    }
+
+                    // 批量作废包含通用内容(onlyUnregistered=3)的SOP日志
+                    if (!logsToUpdate.isEmpty()) {
+                        qwSopLogsMapper.batchUpdateQwSopLogsNewUserById(logsToUpdate);
+                        log.info("批量作废包含通用内容的新客对话SOP,数量:{}", logsToUpdate.size());
+                    }
+                }
+            } catch (Exception e) {
+                log.error("停用新客对话sop失败:" + redisKey + "原因:" + e);
+            }finally {
+                redisCache.deleteObject(redisKey);
+            }
+        }
+    }
+
 
 
     /**

+ 2 - 0
fs-service/src/main/java/com/fs/his/config/AgreementConfig.java

@@ -12,6 +12,8 @@ public class AgreementConfig implements Serializable {
     String vipService;
     String vipAutomaticService;
     String userRemoveService;
+    String appUserAgreement;
+    String appPrivacyAgreement;
     String doctorRegister;
     String doctorFiling;
 

+ 35 - 0
fs-service/src/main/java/com/fs/his/domain/FsSubMerchant.java

@@ -0,0 +1,35 @@
+package com.fs.his.domain;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 汇付-子商户信息对象 fs_sub_merchant
+ *
+ * @author fs
+ * @date 2026-05-11
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class FsSubMerchant extends BaseEntity{
+
+    /** $column.columnComment */
+    private Long id;
+
+    /** 商户名称 */
+    @Excel(name = "商户名称")
+    private String merchantName;
+
+    /** 商户号 */
+    @Excel(name = "商户号")
+    private String merchantNo;
+
+    /** 主商户号 */
+    @Excel(name = "主商户号")
+    private String masterMerchantNo;
+
+
+}

+ 2 - 0
fs-service/src/main/java/com/fs/his/enums/FsUserIntegralLogTypeEnum.java

@@ -37,6 +37,8 @@ public enum FsUserIntegralLogTypeEnum {
     TYPE_27(27, "积分订单取消退回积分"),
     TYPE_28(28, "首次下载APP获取积分"),
     TYPE_29( 29,"玩游戏获取积分"),
+    TYPE_30(30, "购物积分发放"),
+    TYPE_31(31, "购物积分退款扣除"),
     ;
 
 

+ 61 - 0
fs-service/src/main/java/com/fs/his/mapper/FsSubMerchantMapper.java

@@ -0,0 +1,61 @@
+package com.fs.his.mapper;
+
+import java.util.List;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.his.domain.FsSubMerchant;
+
+/**
+ * 汇付-子商户信息Mapper接口
+ * 
+ * @author fs
+ * @date 2026-05-11
+ */
+public interface FsSubMerchantMapper extends BaseMapper<FsSubMerchant>{
+    /**
+     * 查询汇付-子商户信息
+     * 
+     * @param id 汇付-子商户信息主键
+     * @return 汇付-子商户信息
+     */
+    FsSubMerchant selectFsSubMerchantById(Long id);
+
+    /**
+     * 查询汇付-子商户信息列表
+     * 
+     * @param fsSubMerchant 汇付-子商户信息
+     * @return 汇付-子商户信息集合
+     */
+    List<FsSubMerchant> selectFsSubMerchantList(FsSubMerchant fsSubMerchant);
+
+    /**
+     * 新增汇付-子商户信息
+     * 
+     * @param fsSubMerchant 汇付-子商户信息
+     * @return 结果
+     */
+    int insertFsSubMerchant(FsSubMerchant fsSubMerchant);
+
+    /**
+     * 修改汇付-子商户信息
+     * 
+     * @param fsSubMerchant 汇付-子商户信息
+     * @return 结果
+     */
+    int updateFsSubMerchant(FsSubMerchant fsSubMerchant);
+
+    /**
+     * 删除汇付-子商户信息
+     * 
+     * @param id 汇付-子商户信息主键
+     * @return 结果
+     */
+    int deleteFsSubMerchantById(Long id);
+
+    /**
+     * 批量删除汇付-子商户信息
+     * 
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteFsSubMerchantByIds(Long[] ids);
+}

+ 8 - 0
fs-service/src/main/java/com/fs/his/mapper/FsUserIntegralLogsMapper.java

@@ -1,5 +1,7 @@
 package com.fs.his.mapper;
 
+import com.fs.common.annotation.DataSource;
+import com.fs.common.enums.DataSourceType;
 import com.fs.his.domain.FsUserIntegralLogs;
 import com.fs.his.param.FsUserIntegralLogsListUParam;
 import com.fs.his.param.FsUserIntegralLogsParam;
@@ -154,4 +156,10 @@ public interface FsUserIntegralLogsMapper
      * 查询用户最新的积分记录
      */
     FsUserIntegralLogs selectLatestIntegralLogByUserId(@Param("userId") Long userId);
+
+    /**
+     * 获取用户指定类型最后一条记录
+     */
+    @Select("select * from fs_user_integral_logs where user_id = #{userId} and log_type = #{logType} order by create_time desc limit 1")
+    FsUserIntegralLogs getLast1LogByUserIdAndLogType(@Param("userId") Long userId, @Param("logType") Integer logType);
 }

+ 61 - 0
fs-service/src/main/java/com/fs/his/service/IFsSubMerchantService.java

@@ -0,0 +1,61 @@
+package com.fs.his.service;
+
+import java.util.List;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.his.domain.FsSubMerchant;
+
+/**
+ * 汇付-子商户信息Service接口
+ * 
+ * @author fs
+ * @date 2026-05-11
+ */
+public interface IFsSubMerchantService extends IService<FsSubMerchant>{
+    /**
+     * 查询汇付-子商户信息
+     * 
+     * @param id 汇付-子商户信息主键
+     * @return 汇付-子商户信息
+     */
+    FsSubMerchant selectFsSubMerchantById(Long id);
+
+    /**
+     * 查询汇付-子商户信息列表
+     * 
+     * @param fsSubMerchant 汇付-子商户信息
+     * @return 汇付-子商户信息集合
+     */
+    List<FsSubMerchant> selectFsSubMerchantList(FsSubMerchant fsSubMerchant);
+
+    /**
+     * 新增汇付-子商户信息
+     * 
+     * @param fsSubMerchant 汇付-子商户信息
+     * @return 结果
+     */
+    int insertFsSubMerchant(FsSubMerchant fsSubMerchant);
+
+    /**
+     * 修改汇付-子商户信息
+     * 
+     * @param fsSubMerchant 汇付-子商户信息
+     * @return 结果
+     */
+    int updateFsSubMerchant(FsSubMerchant fsSubMerchant);
+
+    /**
+     * 批量删除汇付-子商户信息
+     * 
+     * @param ids 需要删除的汇付-子商户信息主键集合
+     * @return 结果
+     */
+    int deleteFsSubMerchantByIds(Long[] ids);
+
+    /**
+     * 删除汇付-子商户信息信息
+     * 
+     * @param id 汇付-子商户信息主键
+     * @return 结果
+     */
+    int deleteFsSubMerchantById(Long id);
+}

+ 10 - 3
fs-service/src/main/java/com/fs/his/service/impl/FsIntegralGoodsServiceImpl.java

@@ -9,8 +9,10 @@ import com.fs.course.domain.FsCourseAnswerReward;
 import com.fs.his.domain.FsChineseMedicine;
 import com.fs.his.domain.FsIntegralGoods;
 import com.fs.his.domain.FsUser;
+import com.fs.his.domain.FsUserIntegralLogs;
 import com.fs.his.mapper.FsIntegralCartMapper;
 import com.fs.his.mapper.FsIntegralGoodsMapper;
+import com.fs.his.mapper.FsUserIntegralLogsMapper;
 import com.fs.his.mapper.FsUserMapper;
 import com.fs.his.param.FsIntegralGoodsListUParam;
 import com.fs.his.service.IFsIntegralGoodsService;
@@ -44,6 +46,9 @@ public class FsIntegralGoodsServiceImpl implements IFsIntegralGoodsService
     @Autowired
     private ISysConfigService configService;
 
+    @Autowired
+    private FsUserIntegralLogsMapper userIntegralLogsMapper;
+
     /**
      * 查询积分商品
      *
@@ -187,9 +192,10 @@ public class FsIntegralGoodsServiceImpl implements IFsIntegralGoodsService
     @Override
     public R getCourseIntegralGoods(Long userId) {
         log.info("获取答题奖励信息, userId:{}", userId);
-        String json = configService.selectConfigByKey("course.config");
-        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+//        String json = configService.selectConfigByKey("course.config");
+//        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
         FsUser user = fsUserMapper.selectFsUserByUserId(userId);
+        FsUserIntegralLogs integralLog = userIntegralLogsMapper.getLast1LogByUserIdAndLogType(userId, 17);
 
         // 1. 创建返回对象
         FsCourseAnswerReward reward = new FsCourseAnswerReward();
@@ -238,7 +244,8 @@ public class FsIntegralGoodsServiceImpl implements IFsIntegralGoodsService
         int exchangeProgress = (int) ((currentPoints * 100.0) / minRequiredPoints);
         // 进度最大显示100%
         reward.setExchangeProgress(Math.min(exchangeProgress, 100));
-        reward.setAvailableCoins(config.getAnswerIntegral());
+        reward.setAvailableCoins(Objects.nonNull(integralLog) ? integralLog.getIntegral().intValue() : 0);
+//        reward.setAvailableCoins(config.getAnswerIntegral());
         log.info("答题奖励信息: {}", reward);
         return R.ok().put("data", reward);
     }

+ 94 - 0
fs-service/src/main/java/com/fs/his/service/impl/FsSubMerchantServiceImpl.java

@@ -0,0 +1,94 @@
+package com.fs.his.service.impl;
+
+import java.util.List;
+import com.fs.common.utils.DateUtils;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.fs.his.mapper.FsSubMerchantMapper;
+import com.fs.his.domain.FsSubMerchant;
+import com.fs.his.service.IFsSubMerchantService;
+
+/**
+ * 汇付-子商户信息Service业务层处理
+ * 
+ * @author fs
+ * @date 2026-05-11
+ */
+@Service
+public class FsSubMerchantServiceImpl extends ServiceImpl<FsSubMerchantMapper, FsSubMerchant> implements IFsSubMerchantService {
+
+    /**
+     * 查询汇付-子商户信息
+     * 
+     * @param id 汇付-子商户信息主键
+     * @return 汇付-子商户信息
+     */
+    @Override
+    public FsSubMerchant selectFsSubMerchantById(Long id)
+    {
+        return baseMapper.selectFsSubMerchantById(id);
+    }
+
+    /**
+     * 查询汇付-子商户信息列表
+     * 
+     * @param fsSubMerchant 汇付-子商户信息
+     * @return 汇付-子商户信息
+     */
+    @Override
+    public List<FsSubMerchant> selectFsSubMerchantList(FsSubMerchant fsSubMerchant)
+    {
+        return baseMapper.selectFsSubMerchantList(fsSubMerchant);
+    }
+
+    /**
+     * 新增汇付-子商户信息
+     * 
+     * @param fsSubMerchant 汇付-子商户信息
+     * @return 结果
+     */
+    @Override
+    public int insertFsSubMerchant(FsSubMerchant fsSubMerchant)
+    {
+        fsSubMerchant.setCreateTime(DateUtils.getNowDate());
+        return baseMapper.insertFsSubMerchant(fsSubMerchant);
+    }
+
+    /**
+     * 修改汇付-子商户信息
+     * 
+     * @param fsSubMerchant 汇付-子商户信息
+     * @return 结果
+     */
+    @Override
+    public int updateFsSubMerchant(FsSubMerchant fsSubMerchant)
+    {
+        fsSubMerchant.setUpdateTime(DateUtils.getNowDate());
+        return baseMapper.updateFsSubMerchant(fsSubMerchant);
+    }
+
+    /**
+     * 批量删除汇付-子商户信息
+     * 
+     * @param ids 需要删除的汇付-子商户信息主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsSubMerchantByIds(Long[] ids)
+    {
+        return baseMapper.deleteFsSubMerchantByIds(ids);
+    }
+
+    /**
+     * 删除汇付-子商户信息信息
+     * 
+     * @param id 汇付-子商户信息主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsSubMerchantById(Long id)
+    {
+        return baseMapper.deleteFsSubMerchantById(id);
+    }
+}

+ 5 - 0
fs-service/src/main/java/com/fs/hisStore/config/StoreConfig.java

@@ -59,4 +59,9 @@ public class StoreConfig implements Serializable {
      * 是否开启积分订单手机号解密
      */
     private Boolean enableIntegralOrderPhoneDecrypt;
+
+    /**
+     * 是否开启购物积分(用户实际支付金额1:1向下取整折算积分)
+     */
+    private Boolean enableShoppingPoints;
 }

+ 3 - 0
fs-service/src/main/java/com/fs/hisStore/domain/FsStoreOrderScrm.java

@@ -413,4 +413,7 @@ public class FsStoreOrderScrm extends BaseEntity
 
     @TableField(exist = false)
     private String auditReasonName;
+
+    /** 是否领取购物积分 0-未领取 1-已领取 */
+    private Integer shoppingPointsClaimed;
 }

+ 8 - 0
fs-service/src/main/java/com/fs/hisStore/domain/FsStoreProductScrm.java

@@ -2,6 +2,7 @@ package com.fs.hisStore.domain;
 
 import java.math.BigDecimal;
 import java.util.Date;
+import java.util.List;
 
 import com.baomidou.mybatisplus.annotation.FieldStrategy;
 import com.baomidou.mybatisplus.annotation.TableField;
@@ -380,4 +381,11 @@ public class FsStoreProductScrm extends BaseEntity
 
     /** 过滤商品id */
     private Long[] excludeProductIds;
+
+    /** 标签信息(JSON格式,存储标签类型及预售发货时间,如 {"type":["no_commission","presale"],"shippingTime":"2026-05-17 00:00:00"} */
+//    @Excel(name = "标签信息")
+    private String tagInfo;
+
+    /** 商品类型多选查询(用于IN查询) */
+    private List<Integer> productTypes;
 }

+ 1 - 0
fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreOrderItemScrmMapper.java

@@ -80,6 +80,7 @@ public interface FsStoreOrderItemScrmMapper
             " o.order_code, o.pay_price, o.pay_money, o.deduction_price,o.pay_delivery, o.order_type,psps.price " +
             ", CASE o.is_audit WHEN 1 THEN '是' ELSE '否' END AS isAudit " +
             ", sas.audit_remark as auditRemark, sas.reason_level1_text as reasonValue1, sas.reason_level2_text as reasonValue2 " +
+            ", o.delivery_type" +
             " from fs_store_order_item_scrm i " +
             " left join fs_store_order_scrm o on o.id=i.order_id" +
             " left join fs_user u on o.user_id=u.user_id  " +

+ 5 - 0
fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreOrderScrmMapper.java

@@ -53,6 +53,11 @@ public interface FsStoreOrderScrmMapper
      * @return 订单集合
      */
     public List<FsStoreOrderScrm> selectFsStoreOrderList(FsStoreOrderScrm fsStoreOrder);
+    
+        /**
+         * 查询待发放购物积分的订单列表
+         */
+        List<FsStoreOrderScrm> selectShoppingPointsPendingOrders(@Param("cutoffDate") String cutoffDate, @Param("sevenDaysAgo") String sevenDaysAgo);
 
     /**
      * 新增订单

+ 2 - 0
fs-service/src/main/java/com/fs/hisStore/param/FsStoreProductAddEditParam.java

@@ -309,4 +309,6 @@ public class FsStoreProductAddEditParam implements Serializable
 
     /** 商品标签ID列表(最多3个) */
     private List<Long> tagIds;
+
+    private String tagInfo;
 }

+ 3 - 0
fs-service/src/main/java/com/fs/hisStore/service/impl/FsExpressScrmServiceImpl.java

@@ -157,6 +157,9 @@ public class FsExpressScrmServiceImpl implements IFsExpressScrmService
 
             //根据公司业务处理返回的信息......
             ExpressInfoDTO dto=JSONUtil.toBean(result,ExpressInfoDTO.class);
+            if (!dto.isSuccess()) {
+                logger.error("查询快递信息失败:{}:{}", result, requestData);
+            }
             return dto;
         } catch (Exception e) {
             throw  new CustomException(e.getMessage());

+ 44 - 0
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreAfterSalesScrmServiceImpl.java

@@ -36,6 +36,7 @@ import com.fs.his.domain.*;
 import com.fs.his.enums.FsStoreAfterSalesStatusEnum;
 import com.fs.his.enums.FsStoreOrderLogEnum;
 import com.fs.his.enums.FsStoreOrderStatusEnum;
+import com.fs.his.enums.FsUserIntegralLogTypeEnum;
 import com.fs.his.mapper.*;
 import com.fs.his.service.IFsStoreOrderLogsService;
 import com.fs.his.utils.ConfigUtil;
@@ -157,6 +158,12 @@ public class FsStoreAfterSalesScrmServiceImpl implements IFsStoreAfterSalesScrmS
     @Autowired
     private ISysConfigService configService;
 
+    @Autowired
+    private FsUserScrmMapper fsUserScrmMapper;
+
+    @Autowired
+    private FsUserIntegralLogsScrmMapper fsUserIntegralLogsScrmMapper;
+
     @Autowired
     private RedisCache redisCache;
 
@@ -928,6 +935,43 @@ public class FsStoreAfterSalesScrmServiceImpl implements IFsStoreAfterSalesScrmS
         order.setRefundStatus(OrderInfoEnum.REFUND_STATUS_2.getValue());
         orderService.updateFsStoreOrder(order);
 
+        // 购物积分扣除逻辑:如果该订单已领取购物积分,退款时需扣除
+        if (order.getShoppingPointsClaimed() != null && order.getShoppingPointsClaimed() == 1
+                && order.getPayMoney() != null && order.getPayMoney().compareTo(BigDecimal.ZERO) > 0) {
+            try {
+                // 计算应扣除的购物积分(与发放时一致:pay_money向下取整)
+                long pointsToDeduct = order.getPayMoney().setScale(0, BigDecimal.ROUND_DOWN).longValue();
+                if (pointsToDeduct > 0) {
+                    // 查询用户当前积分,积分不足则扣除全部积分
+                    FsUserScrm user = fsUserScrmMapper.selectFsUserByUserId(order.getUserId());
+                    long currentIntegral = (user != null && user.getIntegral() != null) ? user.getIntegral() : 0L;
+                    long actualDeduct = Math.min(pointsToDeduct, currentIntegral);
+
+                    if (actualDeduct > 0) {
+                        // 扣除用户积分
+                        fsUserScrmMapper.decIntegral(order.getUserId(), actualDeduct);
+
+                        // 记录积分日志 logType=6 购物积分退款扣除
+                        FsUserIntegralLogsScrm logs = new FsUserIntegralLogsScrm();
+                        logs.setUserId(order.getUserId());
+                        logs.setIntegral(BigDecimal.valueOf(-actualDeduct));
+                        logs.setLogType(FsUserIntegralLogTypeEnum.TYPE_31.getValue()); // 购物积分退款扣除
+                        logs.setStatus(1);
+                        logs.setBusinessId(String.valueOf(order.getId()));
+                        logs.setBusinessType(1);
+                        logs.setCreateTime(new Date());
+                        long newBalance = Math.max(currentIntegral - actualDeduct, 0);
+                        logs.setBalance(BigDecimal.valueOf(newBalance));
+                        fsUserIntegralLogsScrmMapper.insertFsUserIntegralLogs(logs);
+
+                        logger.info("购物积分退款扣除,订单ID={},应扣={},实扣={}", order.getId(), pointsToDeduct, actualDeduct);
+                    }
+                }
+            } catch (Exception e) {
+                logger.error("购物积分退款扣除异常,订单ID={}", order.getId(), e);
+            }
+        }
+
         BigDecimal refundAmount = param.getRefundAmount();
         if (Objects.isNull(refundAmount)) {
             throw new CustomException("退款金额不能为空");

+ 27 - 1
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderScrmServiceImpl.java

@@ -2644,7 +2644,33 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
             //写入公司余额 条件是只有全款订单才分,非全款后台导入
             if (order.getCompanyId() != null && order.getCompanyId() > 0 && order.getPayDelivery().compareTo(new BigDecimal(0)) == 0) {
                 if (order.getTuiMoneyStatus() == null || order.getTuiMoneyStatus() != 1) {
-                    companyService.addCompanyMoney(order);
+                    // 检查商品是否有"不分润"标签,有则不增加公司余额
+                    boolean noCommission = false;
+                    try {
+                        if (StringUtils.isNotBlank(order.getItemJson())) {
+                            com.alibaba.fastjson.JSONArray itemArray = JSON.parseArray(order.getItemJson());
+                            if (itemArray != null && !itemArray.isEmpty()) {
+                                Long productId = itemArray.getJSONObject(0).getLong("productId");
+                                if (productId != null) {
+                                    FsStoreProductScrm product = productService.selectFsStoreProductById(productId);
+                                    if (product != null && StringUtils.isNotBlank(product.getTagInfo())) {
+                                        JSONObject tagObj = JSON.parseObject(product.getTagInfo());
+                                        Object typeObj = tagObj.get("type");
+                                        if (typeObj instanceof com.alibaba.fastjson.JSONArray) {
+                                            noCommission = ((com.alibaba.fastjson.JSONArray) typeObj).contains("no_commission");
+                                        } else if (typeObj instanceof String) {
+                                            noCommission = "no_commission".equals(typeObj);
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    } catch (Exception e) {
+                        log.error("检查商品不分润标签失败:{},订单号:{}", e.getMessage(), order.getOrderCode());
+                    }
+                    if (!noCommission) {
+                        companyService.addCompanyMoney(order);
+                    }
                 }
             }
             //套餐包赠送积分

+ 3 - 0
fs-service/src/main/java/com/fs/hisStore/vo/FsStoreOrderItemExportVO.java

@@ -123,6 +123,9 @@ public class FsStoreOrderItemExportVO implements Serializable
     @Excel(name = "是否审核")
     private String isAudit;
 
+    @Excel(name = "物流跟踪状态" , dictType = "store_order_delivery_type")
+    private String deliveryType;
+
 
     private BigDecimal payPrice;// 应付金额
     private BigDecimal deductionPrice;// 应付金额

+ 3 - 0
fs-service/src/main/java/com/fs/hisStore/vo/FsStoreOrderItemExportZMVO.java

@@ -104,6 +104,9 @@ public class FsStoreOrderItemExportZMVO implements Serializable {
     @Excel(name = "银行交易流水号")
     private String bankTransactionId;
 
+    @Excel(name = "物流跟踪状态" , dictType = "store_order_delivery_type")
+    private String deliveryType;
+
 
     /** 商品分类(与 MergedOrderVO 一致,不导出) */
 

+ 1 - 1
fs-service/src/main/java/com/fs/hisStore/vo/FsStoreOrderVO.java

@@ -172,7 +172,7 @@ public class FsStoreOrderVO implements Serializable
     private String deliveryName;
 
     /** 发货类型 */
-    @Excel(name = "发货类型")
+    @Excel(name = "物流跟踪状态", dictType = "store_order_delivery_type")
     private String deliveryType;
 
     /** 快递单号/手机号 */

+ 3 - 0
fs-service/src/main/java/com/fs/hisStore/vo/FsStoreProductListVO.java

@@ -128,4 +128,7 @@ public class FsStoreProductListVO  implements Serializable
 
     /** 活动结束时间 */
     private Date activityEndTime;
+
+    /** 标签信息(JSON格式,存储标签类型及预售发货时间) */
+    private String tagInfo;
 }

+ 2 - 2
fs-service/src/main/java/com/fs/qw/mapper/QwExternalContactMapper.java

@@ -478,8 +478,8 @@ public interface QwExternalContactMapper extends BaseMapper<QwExternalContact> {
     @Select("select id,external_user_id,tag_ids from qw_external_contact where  qw_user_id=#{id} ")
     List<QwExternalContact> selectExternalUserIdsByQwUserId(Long id);
 
-    @Select("select id,first_time,qw_user_id,create_time from qw_external_contact where  id=#{id} ")
-    QwExternalContact selectQwExternalContactTimeById(Long id);
+    @Select("select fs_user_id from qw_external_contact where  id=#{id} ")
+    Long selectQwExternalContactTimeById(@Param("id") Long id);
 
     @Select("select id,user_id,corp_id,external_user_id,name,remark,status from qw_external_contact where  id=#{id} ")
     QwExternalContact selectQwExternalContactByRemark(Long id);

+ 47 - 5
fs-service/src/main/java/com/fs/qw/service/AsyncQwAiChatSopService.java

@@ -43,6 +43,7 @@ import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.LocalTime;
 import java.time.ZoneId;
+import java.time.temporal.ChronoUnit;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Date;
@@ -98,7 +99,6 @@ public class AsyncQwAiChatSopService {
                                    QwUser qwUser, String externalUserID, String externalContactName,
                                    Long externalId, Long fsUserId, LocalDate currentDate, LocalTime localTime) {
 
-
         QwExternalContact contact;
         if (externalId != null) {
             contact = qwExternalContactMapper.selectById(externalId);
@@ -130,9 +130,33 @@ public class AsyncQwAiChatSopService {
 
             qwSopAiRuleTimeVOS.forEach(item -> {
 
+                LocalDateTime deadLineTime = item.getDeadLineTime();
+
+                // 计算剩余天数
+                Long daysRemaining;
+                if (deadLineTime != null) {
+                    LocalDate deadLineDate = deadLineTime.toLocalDate();
+                    daysRemaining = ChronoUnit.DAYS.between(currentDate, deadLineDate) + 1; // 同一天为1天
+
+                    if (daysRemaining < 1) {
+                        log.warn("截止时间已过期,跳过SOP任务: sopId={}", item.getId());
+                        return;
+                    }
+                } else {
+                    daysRemaining = null;
+                }
+
                 List<QwSopTempContent> tempContentList = qwSopTempContentMapper.selectQwSopTempContentByTempIdAndRules(item.getTempId());
 
                 tempContentList.forEach(content -> {
+
+                    // 解析dayNum,如果有截止时间且dayNum大于剩余天数则跳过
+                    Integer dayNum = content.getDayNum();
+                    if (daysRemaining != null && dayNum != null && dayNum > daysRemaining) {
+                        log.debug("dayNum={} 大于剩余天数={},跳过此内容", dayNum, daysRemaining);
+                        return;
+                    }
+
                     QwSopLogs sopLogs = new QwSopLogs();
                     sopLogs.setQwUserKey(qwUser.getId());
                     sopLogs.setQwUserid(userID);
@@ -152,12 +176,30 @@ public class AsyncQwAiChatSopService {
                     List<QwSopTempSetting.Content.Setting> settingList = new ArrayList<>();
                     QwSopTempSetting.Content.Setting setting = JSON.parseObject(content.getContent(), QwSopTempSetting.Content.Setting.class);
 
-                    LocalDateTime dateTime = LocalDateTime.of(currentDate, localTime);
-                    LocalDateTime expiryDateTime = dateTime.plusMinutes(setting.getIntervalTime());
-                    String sendTime = DateUtil.formatLocalDateTime(expiryDateTime);
+
+                    LocalDateTime dateTime;
+                    // 如果dayNum > 1,需要根据setting中的time计算新的发送时间
+                    if (dayNum != null && dayNum > 1 && setting.getTime() != null && !setting.getTime().isEmpty()) {
+                        // 解析time字段,格式为"09:00"
+                        String[] timeParts = setting.getTime().split(":");
+                        int hour = Integer.parseInt(timeParts[0]);
+                        int minute = Integer.parseInt(timeParts[1]);
+
+                        // 计算目标日期:当前日期 + (dayNum - 1)天
+                        LocalDate targetDate = currentDate.plusDays(dayNum - 1);
+                        dateTime = LocalDateTime.of(targetDate, LocalTime.of(hour, minute));
+                    } else {
+                        // dayNum为1或没有时间配置,使用原有逻辑
+                        dateTime = LocalDateTime.of(currentDate, localTime);
+                        LocalDateTime expiryDateTime = dateTime.plusMinutes(setting.getIntervalTime());
+                        dateTime = expiryDateTime;
+                    }
+
+                    String sendTime = DateUtil.formatLocalDateTime(dateTime);
                     sopLogs.setSendTime(sendTime);
 
-                    Date expirySendTime = Date.from(expiryDateTime.atZone(ZoneId.of("Asia/Shanghai")).toInstant());
+                    Date expirySendTime = Date.from(dateTime.atZone(ZoneId.of("Asia/Shanghai")).toInstant());
+
                     //过滤违禁词
                     if ("1".equals(setting.getContentType())) {
                         sopUserLogsInfoService.replaceContent(setting.getContentType(), setting.getValue(), setting::setValue, words); // 替换 value

+ 371 - 0
fs-service/src/main/java/com/fs/qw/service/AsyncQwAiChatSopServiceCopy.java

@@ -0,0 +1,371 @@
+//package com.fs.qw.service;
+//
+//import com.alibaba.fastjson.JSON;
+//import com.fs.common.utils.CloudHostUtils;
+//import com.fs.common.utils.StringUtils;
+//import com.fs.common.utils.date.DateUtil;
+//import com.fs.company.service.ICompanyMiniappService;
+//import com.fs.course.config.CourseConfig;
+//import com.fs.course.domain.FsCourseLink;
+//import com.fs.course.domain.FsCourseRealLink;
+//import com.fs.course.domain.FsCourseWatchLog;
+//import com.fs.course.mapper.FsCourseLinkMapper;
+//import com.fs.course.mapper.FsCourseWatchLogMapper;
+//import com.fs.fastGpt.domain.FastGptChatReplaceWords;
+//import com.fs.fastGpt.mapper.FastGptChatReplaceWordsMapper;
+//import com.fs.qw.domain.QwCompany;
+//import com.fs.qw.domain.QwExternalContact;
+//import com.fs.qw.domain.QwExternalContactInfo;
+//import com.fs.qw.domain.QwUser;
+//import com.fs.qw.mapper.QwExternalContactInfoMapper;
+//import com.fs.qw.mapper.QwExternalContactMapper;
+//import com.fs.qw.vo.QwSopRuleTimeVO;
+//import com.fs.qw.vo.QwSopTempSetting;
+//import com.fs.sop.domain.QwSopLogs;
+//import com.fs.sop.domain.QwSopTempContent;
+//import com.fs.sop.domain.QwSopTempVoice;
+//import com.fs.sop.mapper.QwSopLogsMapper;
+//import com.fs.sop.mapper.QwSopMapper;
+//import com.fs.sop.mapper.QwSopTempContentMapper;
+//import com.fs.sop.params.QwSopAutoByTags;
+//import com.fs.sop.service.IQwSopTempVoiceService;
+//import com.fs.sop.service.impl.SopUserLogsInfoServiceImpl;
+//import com.fs.system.service.ISysConfigService;
+//import com.fs.voice.utils.StringUtil;
+//import lombok.AllArgsConstructor;
+//import lombok.extern.slf4j.Slf4j;
+//import org.joda.time.DateTime;
+//import org.springframework.beans.BeanUtils;
+//import org.springframework.beans.factory.annotation.Autowired;
+//import org.springframework.scheduling.annotation.Async;
+//import org.springframework.stereotype.Service;
+//
+//import java.time.LocalDate;
+//import java.time.LocalDateTime;
+//import java.time.LocalTime;
+//import java.time.ZoneId;
+//import java.time.temporal.ChronoUnit;
+//import java.util.ArrayList;
+//import java.util.Collections;
+//import java.util.Date;
+//import java.util.List;
+//
+//@Slf4j
+//@Service
+//@AllArgsConstructor
+//public class AsyncQwAiChatSopServiceCopy {
+//
+//    private static final String miniappRealLink = "/pages_course/video.html?course=";
+//
+//    @Autowired
+//    private QwSopMapper qwSopMapper;
+//
+//    @Autowired
+//    private QwSopTempContentMapper qwSopTempContentMapper;
+//
+//    @Autowired
+//    private ISysConfigService configService;
+//
+//    @Autowired
+//    private SopUserLogsInfoServiceImpl sopUserLogsInfoService;
+//    @Autowired
+//    private FastGptChatReplaceWordsMapper fastGptChatReplaceWordsMapper;
+//
+//    @Autowired
+//    private FsCourseWatchLogMapper fsCourseWatchLogMapper;
+//    @Autowired
+//    private QwExternalContactMapper qwExternalContactMapper;
+//
+//    @Autowired
+//    private QwSopLogsMapper qwSopLogsMapper;
+//
+//    @Autowired
+//    private FsCourseLinkMapper fsCourseLinkMapper;
+//
+//    @Autowired
+//    private IQwCompanyService iQwCompanyService;
+//
+//    @Autowired
+//    private ICompanyMiniappService companyMiniappService;
+//
+//
+//    @Autowired
+//    private IQwSopTempVoiceService sopTempVoiceService;
+//
+//    @Autowired
+//    private QwExternalContactInfoMapper qwExternalContactInfoMapper;
+//
+////    @Async("threadPoolTaskExecutor")
+//    public void executeQwAiChatSop(QwSopAutoByTags qwSopAutoByTags, String userID,
+//                                   QwUser qwUser, String externalUserID, String externalContactName,
+//                                   Long externalId, Long fsUserId, LocalDate currentDate, LocalTime localTime) {
+//
+//        QwExternalContact contact;
+//        if (externalId != null) {
+//            contact = qwExternalContactMapper.selectById(externalId);
+//        } else {
+//            contact = null;
+//        }
+//        //新客对话任务
+//        List<QwSopRuleTimeVO> qwSopAiRuleTimeVOS = qwSopMapper.selectQwAiSopAutoByTagsByForeach(qwSopAutoByTags);
+//        List<FastGptChatReplaceWords> words = fastGptChatReplaceWordsMapper.selectAllFastGptChatReplaceWords();
+//        List<QwSopLogs> sopLogsList = new ArrayList<>(Collections.emptyList());
+//
+//
+//        String json = configService.selectConfigByKey("course.config");
+//        CourseConfig config = JSON.parseObject(json, CourseConfig.class);
+//
+//        if (config == null) {
+//            log.error("配置为空-新客对话创建失败");
+//            return;
+//        }
+//
+//        QwCompany qwCompany = iQwCompanyService.getQwCompanyByRedis(qwUser.getCorpId());
+//
+//        if (qwCompany == null) {
+//            log.error("企业微信主体未配置默认小程序-新客对话创建失败");
+//            return;
+//        }
+//
+//        if (qwSopAiRuleTimeVOS != null && !qwSopAiRuleTimeVOS.isEmpty()) {
+//
+//            qwSopAiRuleTimeVOS.forEach(item -> {
+//
+//
+//                List<QwSopTempContent> tempContentList = qwSopTempContentMapper.selectQwSopTempContentByTempIdAndRules(item.getTempId());
+//
+//                tempContentList.forEach(content -> {
+//
+//                    QwSopLogs sopLogs = new QwSopLogs();
+//                    sopLogs.setQwUserKey(qwUser.getId());
+//                    sopLogs.setQwUserid(userID);
+//                    sopLogs.setExternalUserId(externalUserID);
+//                    sopLogs.setExternalId(externalId);
+//                    sopLogs.setLogType(2);
+//                    sopLogs.setSendStatus(3L);
+//                    sopLogs.setCompanyId(qwUser.getCompanyId());
+//                    sopLogs.setReceivingStatus(0L);
+//                    sopLogs.setSopId(item.getId());
+//                    sopLogs.setCorpId(qwUser.getCorpId());
+//                    sopLogs.setFsUserId(fsUserId);
+//                    sopLogs.setSort(99999999);
+//                    sopLogs.setSendType(4);
+//                    sopLogs.setExternalUserName(externalContactName);
+//
+//                    List<QwSopTempSetting.Content.Setting> settingList = new ArrayList<>();
+//                    QwSopTempSetting.Content.Setting setting = JSON.parseObject(content.getContent(), QwSopTempSetting.Content.Setting.class);
+//
+//                    LocalDateTime dateTime = LocalDateTime.of(currentDate, localTime);
+//                    LocalDateTime expiryDateTime = dateTime.plusMinutes(setting.getIntervalTime());
+//                    String sendTime = DateUtil.formatLocalDateTime(expiryDateTime);
+//                    sopLogs.setSendTime(sendTime);
+//
+//                    Date expirySendTime = Date.from(expiryDateTime.atZone(ZoneId.of("Asia/Shanghai")).toInstant());
+//                    //过滤违禁词
+//                    if ("1".equals(setting.getContentType())) {
+//                        sopUserLogsInfoService.replaceContent(setting.getContentType(), setting.getValue(), setting::setValue, words); // 替换 value
+//                    }
+//                    //过滤违禁词
+//                    if ("3".equals(setting.getContentType())) {
+//                        sopUserLogsInfoService.replaceContent(setting.getContentType(), setting.getLinkTitle(), setting::setLinkTitle, words); // 替换 linkTitle
+//                        sopUserLogsInfoService.replaceContent(setting.getContentType(), setting.getLinkDescribe(), setting::setLinkDescribe, words); // 替换 linkTitle
+//                    }
+//                    switch (setting.getContentType()) {
+//                        //文字和短链一起
+//                        case "1":
+//                        case "3":
+//
+//                            if ("1".equals(setting.getContentType())) {
+//                                String defaultName = "同学";
+//                                if (contact != null && StringUtils.isNotEmpty(contact.getName()) && !"待同步客户".equals(contact.getName())) {
+//                                    defaultName = contact.getName();
+//                                }
+//                                setting.setValue(setting.getValue()
+//                                        .replaceAll("#销售称呼#", StringUtil.strIsNullOrEmpty(qwUser.getWelcomeText()) ? "" : qwUser.getWelcomeText())
+//                                        .replaceAll("#客户称呼#", contact == null || StringUtil.strIsNullOrEmpty(contact.getStageStatus()) || "0".equals(contact.getStageStatus()) ? defaultName : contact.getStageStatus()));
+//                            }
+//
+//
+//                            break;
+//                        //小程序单独
+//                        case "4":
+//                            addWatchLogIfNeededByNewChat(item.getId(), content.getVideoId(), content.getCourseId(), fsUserId, qwUser.getId(), qwUser.getCompanyUserId(), qwUser.getCompanyId(),
+//                                    externalId, sendTime, expirySendTime, 2);
+//
+//                            String linkByMiniApp = createLinkByMiniAppByNewChat(setting.getExpiresDays(), qwUser.getCorpId(), expirySendTime, content.getCourseId(), content.getVideoId(),
+//                                    qwUser.getId(), String.valueOf(qwUser.getCompanyUserId()), String.valueOf(qwUser.getCompanyId()), externalId, config);
+//
+//
+//                            setting.setMiniprogramAppid(qwCompany.getMiniAppId());
+//
+//                            String miniprogramTitle = setting.getMiniprogramTitle();
+//                            int maxLength = 17;
+//                            setting.setMiniprogramTitle(miniprogramTitle.length() > maxLength ? miniprogramTitle.substring(0, maxLength) + "..." : miniprogramTitle);
+//                            setting.setMiniprogramPage(linkByMiniApp);
+//                            break;
+//                        case "7":
+//
+//                            createVoiceUrlByNewChat(setting, qwUser.getCompanyUserId());
+//                            break;
+//                        default:
+//                            break;
+//
+//                    }
+//
+//                    settingList.add(setting);
+//
+//                    QwSopTempSetting.Content clonedContent = new QwSopTempSetting.Content();
+//                    clonedContent.setContentType(setting.getContentType());
+//                    clonedContent.setCourseId(Long.valueOf(content.getCourseId()));
+//                    clonedContent.setVideoId(Long.valueOf(content.getVideoId()));
+//                    clonedContent.setSetting(settingList);
+//                    clonedContent.setType(content.getType());
+//                    clonedContent.setCourseType(0);
+//                    sopLogs.setContentJson(JSON.toJSONString(clonedContent));
+//                    sopLogsList.add(sopLogs);
+//                });
+//
+//
+//            });
+//
+//            //批量插入 发送记录
+//            if (!sopLogsList.isEmpty()) {
+//                processAndInsertQwSopLogsBySendMsg(sopLogsList);
+//            }
+//        }
+//
+//    }
+//
+//    private void processAndInsertQwSopLogsBySendMsg(List<QwSopLogs> sopLogsList) {
+//        // 定义批量插入的大小
+//        int batchSize = 500;
+//
+//        // 循环处理外部用户 ID,每次处理批量大小的子集
+//        for (int i = 0; i < sopLogsList.size(); i += batchSize) {
+//
+//            int endIndex = Math.min(i + batchSize, sopLogsList.size());
+//            List<QwSopLogs> batchList = sopLogsList.subList(i, endIndex);  // 获取当前批次的子集
+//
+//            // 直接使用批次数据进行批量更新,不需要额外的 List
+//            try {
+//                qwSopLogsMapper.batchInsertQwSopLogsOneTouch(batchList);
+//            } catch (Exception e) {
+//                // 记录异常日志,方便后续排查问题
+//                log.error("批量执行一键群发时发生异常,处理的批次起始索引为: " + i, e);
+//            }
+//        }
+//    }
+//
+//    //插入观看记录
+//    private Long addWatchLogIfNeededByNewChat(String sopId, Integer videoId, Integer courseId,
+//                                              Long fsUserId, Long qwUserId, Long companyUserId,
+//                                              Long companyId, Long externalId, String startTime, Date createTime, Integer watchType) {
+//
+//        try {
+//            FsCourseWatchLog watchLog = new FsCourseWatchLog();
+//            watchLog.setVideoId(Long.valueOf(videoId));
+//            watchLog.setQwExternalContactId(externalId);
+//            watchLog.setSendType(2);
+//            watchLog.setQwUserId(qwUserId);
+//            watchLog.setSopId(sopId);
+//            watchLog.setDuration(0L);
+//            watchLog.setCourseId(Long.valueOf(courseId));
+//            watchLog.setCompanyUserId(companyUserId);
+//            watchLog.setCompanyId(companyId);
+//            watchLog.setCreateTime(createTime);
+//            watchLog.setUpdateTime(createTime);
+//            watchLog.setLogType(3);
+//            watchLog.setUserId(fsUserId);
+//            if (!CloudHostUtils.hasCloudHostName("木易华康")) {
+//                watchLog.setWatchType(watchType);
+//            }
+//            watchLog.setCampPeriodTime(sopUserLogsInfoService.convertStringToDate(startTime, "yyyy-MM-dd HH:mm:ss"));
+//
+//            //存看课记录
+//            fsCourseWatchLogMapper.insertOrUpdateFsCourseWatchLog(watchLog);
+//            return watchLog.getLogId();
+//        } catch (Exception e) {
+//            log.error("一键群发失败-插入观看记录失败:" + e.getMessage());
+//            return null;
+//        }
+//    }
+//
+//    private String createLinkByMiniAppByNewChat(Integer expiresDays, String corpId, Date sendTime,
+//                                                Integer courseId, Integer videoId, Long qwUserId,
+//                                                String companyUserId, String companyId, Long externalId, CourseConfig config) {
+//
+//        try {
+//            FsCourseLink link = sopUserLogsInfoService.createFsCourseLink(corpId, sendTime, courseId, videoId, qwUserId,
+//                    companyUserId, companyId, externalId, 3, null);
+//
+//            FsCourseRealLink courseMap = new FsCourseRealLink();
+//            BeanUtils.copyProperties(link, courseMap);
+//
+//            String courseJson = JSON.toJSONString(courseMap);
+//            String realLinkFull = miniappRealLink + courseJson;
+//            link.setRealLink(realLinkFull);
+//
+//            Date updateTime = createUpdateTimeByNewChat(expiresDays, sendTime, config);
+//
+//            link.setUpdateTime(updateTime);
+//            //存短链-
+//            fsCourseLinkMapper.insertFsCourseLink(link);
+//            return link.getRealLink();
+//        } catch (Exception e) {
+//            log.error("创建新客对话短链失败:{}|{}|{}|{}|{}", corpId, sendTime, courseId, videoId, qwUserId);
+//            log.error("e", e);
+//        }
+//        return null;
+//    }
+//
+//    public Date createUpdateTimeByNewChat(Integer expiresDays, Date sendTime, CourseConfig config) {
+//
+//
+//        Integer expDays = (expiresDays == null || expiresDays == 0)
+//                ? config.getVideoLinkExpireDate()
+//                : expiresDays;
+//
+////         使用 Java 8 时间 API 计算过期时间
+//        LocalDateTime sendDateTime = sendTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
+//        LocalDateTime expireDateTime = sendDateTime.plusDays(expDays - 1);
+//        expireDateTime = expireDateTime.toLocalDate().atTime(23, 59, 59);
+//        Date updateTime = Date.from(expireDateTime.atZone(ZoneId.systemDefault()).toInstant());
+//
+//        return updateTime;
+//    }
+//
+//    private void createVoiceUrlByNewChat(QwSopTempSetting.Content.Setting setting, Long companyUserId) {
+//        QwSopTempVoice qwSopTempVoice = sopTempVoiceService.selectQwSopTempVoiceByCompanyUserIdAndVoiceTxt(companyUserId, setting.getValue());
+//        if (qwSopTempVoice != null && qwSopTempVoice.getVoiceUrl() != null && qwSopTempVoice.getRecordType() == 1) {
+//            setting.setVoiceUrl(qwSopTempVoice.getVoiceUrl());
+//            setting.setVoiceDuration(String.valueOf(qwSopTempVoice.getDuration()));
+//        } else if (qwSopTempVoice == null) {
+//            if (companyUserId != null && setting.getValue() != null) {
+//                qwSopTempVoice = new QwSopTempVoice();
+//                qwSopTempVoice.setCompanyUserId(companyUserId);
+//                qwSopTempVoice.setVoiceTxt(setting.getValue());
+//                qwSopTempVoice.setRecordType(0);
+//                sopTempVoiceService.insertQwSopTempVoice(qwSopTempVoice);
+//            }
+//        }
+//    }
+//
+//    /**
+//     * 在职转接 -AI用户信息 一起转
+//     */
+//    @Async("threadPoolTaskExecutor")
+//    public void executeQwSopJobTransfer(Long externalContactId, Long externalId) {
+//        try {
+//            QwExternalContactInfo contactInfo = qwExternalContactInfoMapper.selectQwExternalContactInfoByExternalContactId(externalContactId);
+//            if (contactInfo != null) {
+//                contactInfo.setExternalContactId(externalId);
+//                qwExternalContactInfoMapper.insertQwExternalContactInfo(contactInfo);
+//            }
+//        } catch (Exception e) {
+//            log.error("在职转接-转接客户AI信息失败:{},{}", externalContactId, externalId, e);
+//        }
+//
+//    }
+//
+//
+//}

+ 8 - 1
fs-service/src/main/java/com/fs/qw/service/impl/QwExternalContactServiceImpl.java

@@ -6063,12 +6063,19 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
 
     public Boolean getSopAiChatByRedis(String qwUserId, String corpId, String externalUserId) {
 
+
         try {
             String key = (String) redisCache.getCacheObject("qwNewChat:" + qwUserId + ":" + corpId + ":" + externalUserId);
             if (!StringUtil.strIsNullOrEmpty(key)) {
                 return true;
             } else {
-                redisCache.setCacheObject("qwNewChat:" + qwUserId + ":" + corpId + ":" + externalUserId, "1", 1, TimeUnit.DAYS);
+                // 今正的新客对话,缓存7天
+                if (CloudHostUtils.hasCloudHostName("今正科技")){
+                    redisCache.setCacheObject("qwNewChat:" + qwUserId + ":" + corpId + ":" + externalUserId, "1", 7, TimeUnit.DAYS);
+                }else {
+                    redisCache.setCacheObject("qwNewChat:" + qwUserId + ":" + corpId + ":" + externalUserId, "1", 1, TimeUnit.DAYS);
+                }
+
                 return false;
             }
         } catch (Exception e) {

+ 1 - 0
fs-service/src/main/java/com/fs/qw/vo/QwSopCourseFinishTempSetting.java

@@ -63,6 +63,7 @@ public class QwSopCourseFinishTempSetting implements Serializable,Cloneable{
         private String externalUserId;
         //文本-图片-链接-小程序-文件-视频-语音-视频号-app
         private String contentType;
+        private String onlyUnregistered;
         //文本
         private String value;
         //图片

+ 7 - 0
fs-service/src/main/java/com/fs/qw/vo/QwSopRuleTimeVO.java

@@ -5,6 +5,7 @@ import com.fs.common.annotation.Excel;
 import com.fs.common.core.domain.BaseEntity;
 import lombok.Data;
 
+import java.time.LocalDateTime;
 import java.util.Date;
 
 //查出要执行的SOP
@@ -55,6 +56,12 @@ public class QwSopRuleTimeVO extends BaseEntity {
     */
     @JsonFormat(pattern = "yyyy-MM-dd")
     private Date startTime;
+
+    /**
+    * 截至时间
+    */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime deadLineTime;
     /**
      *  是否开启新客户添加自动创建sop 1 否 2 是
      */

+ 3 - 1
fs-service/src/main/java/com/fs/qw/vo/QwSopTempSetting.java

@@ -44,7 +44,7 @@ public class QwSopTempSetting implements Serializable{
         private String addTag;
 
         private String delTag;
-        
+
         private Integer isAtAll;
 
         private Long liveId;
@@ -76,6 +76,8 @@ public class QwSopTempSetting implements Serializable{
             private Long id;
             private Long sopLogId;
             private Integer intervalTime;
+            private String time;
+            private String onlyUnregistered;
             private String talkType;
             /**
              * 员工的id(用于发送)

+ 3 - 0
fs-service/src/main/java/com/fs/sop/domain/QwSop.java

@@ -57,6 +57,9 @@ public class QwSop implements Serializable
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private String startTime;
 
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private String deadLineTime;
+
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private String createTime;
 

+ 6 - 0
fs-service/src/main/java/com/fs/sop/domain/QwSopTempContent.java

@@ -58,4 +58,10 @@ public class QwSopTempContent{
      */
     @TableField(exist = false)
     private Integer videoId;
+
+    /**
+    * 第几天
+    */
+    @TableField(exist = false)
+    private Integer dayNum;
 }

+ 3 - 1
fs-service/src/main/java/com/fs/sop/mapper/QwSopTempContentMapper.java

@@ -96,10 +96,12 @@ public interface QwSopTempContentMapper extends BaseMapper<QwSopTempContent>{
             "  tc.content,\n" +
             "  tr.course_id,\n" +
             "  tr.video_id," +
-            "  tr.content_type as type " +
+            "  tr.content_type as type," +
+            "  td.day_num  " +
             "FROM\n" +
             "  qw_sop_temp_content tc " +
             "left join qw_sop_temp_rules tr on  tc.rules_id=tr.id " +
+            "LEFT JOIN qw_sop_temp_day  td on tc.day_id=td.id " +
             "where tc.temp_id=#{tempId}")
     List<QwSopTempContent> selectQwSopTempContentByTempIdAndRules(@Param("tempId") String tempId);
 

+ 1 - 1
fs-service/src/main/resources/mapper/company/CompanyWithdrawDetailMapper.xml

@@ -98,7 +98,7 @@
     </sql>
 
     <sql id="withdrawDetailBaseWhere">
-        WHERE l.create_time &gt;= '2026-03-27 00:00:00'
+        WHERE l.create_time &gt;= '2026-04-01 00:00:00'
           AND l.logs_type IN (1, 2, 3, 4, 5, 6, 7, 8)
           AND NOT (l.logs_type = 5 AND IFNULL(l.remark, '') = '订单佣金冻结')
     </sql>

+ 4 - 0
fs-service/src/main/resources/mapper/course/FsUserCourseCategoryMapper.xml

@@ -55,6 +55,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             c.is_del = 0
             AND p.is_del = 0
             AND p.pid = 0
+            <if test="homePage != null and homePage == 1">
+                and d.is_del = 0
+                and d.is_show = 1
+            </if>
                 <if test="yxxTag != null and yxxTag == 1 and homePage != null and homePage == 1">
                     and d.rec_home_course_top_enabled = 1
                 </if>

+ 20 - 1
fs-service/src/main/resources/mapper/course/FsUserCourseCommentMapper.xml

@@ -17,10 +17,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="likes"    column="likes"    />
         <result property="toUserId"    column="to_user_id"    />
         <result property="isDel"    column="is_del"    />
+        <result property="nickName"    column="nick_name"    />
     </resultMap>
 
     <sql id="selectFsUserCourseCommentVo">
-        select comment_id,to_user_id, user_id,is_del, course_id, type, parent_id, content, reply_count, create_time, update_time, likes from fs_user_course_comment
+        select comment_id,to_user_id, user_id,is_del, course_id, type, parent_id, content, reply_count, create_time, update_time, likes, nick_name from fs_user_course_comment
     </sql>
 
     <select id="selectFsUserCourseCommentList" parameterType="FsUserCourseComment" resultMap="FsUserCourseCommentResult">
@@ -33,6 +34,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="content != null  and content != ''"> and content = #{content}</if>
             <if test="replyCount != null "> and reply_count = #{replyCount}</if>
             <if test="likes != null "> and likes = #{likes}</if>
+            <if test="isDel != null "> and is_del = #{isDel}</if>
         </where>
     </select>
 
@@ -55,6 +57,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="likes != null">likes,</if>
             <if test="toUserId != null">to_user_id,</if>
             <if test="isDel != null">is_del,</if>
+            <if test="nickName != null">nick_name,</if>
          </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="userId != null">#{userId},</if>
@@ -68,6 +71,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="likes != null">#{likes},</if>
             <if test="toUserId != null">#{toUserId},</if>
             <if test="isDel != null">#{isDel},</if>
+            <if test="nickName != null">#{nickName},</if>
          </trim>
     </insert>
 
@@ -85,6 +89,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="likes != null">likes = #{likes},</if>
             <if test="toUserId != null">to_user_id = #{toUserId},</if>
             <if test="isDel != null">is_del = #{isDel},</if>
+            <if test="nickName != null">nick_name = #{nickName},</if>
         </trim>
         where comment_id = #{commentId}
     </update>
@@ -99,4 +104,18 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             #{commentId}
         </foreach>
     </update>
+
+    <select id="selectFsUserCourseCommentListByTypeAndCourseId" resultType="com.fs.course.domain.FsUserCourseComment">
+        select * from fs_user_course_comment where type = #{type} and course_id = #{courseId} and is_del = 0 order by create_time desc
+    </select>
+
+    <select id="selectCommentsByCourseIdAndUserIds" resultMap="FsUserCourseCommentResult">
+        <include refid="selectFsUserCourseCommentVo"/>
+        WHERE course_id = #{courseId} AND type = 1 AND is_del = 0
+        AND user_id IN
+        <foreach item="userId" collection="userIds" open="(" separator="," close=")">
+            #{userId}
+        </foreach>
+        ORDER BY user_id, create_time ASC
+    </select>
 </mapper>

+ 5 - 0
fs-service/src/main/resources/mapper/course/FsUserCourseVideoMapper.xml

@@ -39,6 +39,7 @@
         <result property="lastJoinTime"    column="last_join_time"    />
         <result property="projectId"    column="project_id"    />
         <result property="isFirst"    column="is_first"    />
+        <result property="courseIntroImg"    column="course_intro_img"    />
     </resultMap>
 
     <sql id="selectFsUserCourseVideoVo">
@@ -116,6 +117,7 @@
             <if test="isSpeed != null">is_speed,</if>
             <if test="jobId != null">job_id,</if>
             <if test="vid != null">vid,</if>
+            <if test="courseIntroImg != null">course_intro_img,</if>
         </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="fileId != null">#{fileId},</if>
@@ -160,6 +162,7 @@
             <if test="isSpeed != null">#{isSpeed},</if>
             <if test="jobId != null">#{jobId},</if>
             <if test="vid != null">#{vid},</if>
+            <if test="courseIntroImg != null">#{courseIntroImg},</if>
         </trim>
     </insert>
     <insert id="insertBatchFsUserCourseVideo" parameterType="FsUserCourseVideo" useGeneratedKeys="true" keyProperty="videoId">
@@ -254,6 +257,8 @@
             <if test="isOnPut != null">is_on_put = #{isOnPut},</if>
             <if test="jobId != null">job_id = #{jobId},</if>
             <if test="vid != null">vid = #{vid},</if>
+            <if test="courseIntroImg != null">course_intro_img = #{courseIntroImg},</if>
+            <if test="courseIntroImg == null">course_intro_img = null,</if>
         </trim>
         where video_id = #{videoId}
     </update>

+ 76 - 0
fs-service/src/main/resources/mapper/his/FsSubMerchantMapper.xml

@@ -0,0 +1,76 @@
+<?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.his.mapper.FsSubMerchantMapper">
+    
+    <resultMap type="FsSubMerchant" id="FsSubMerchantResult">
+        <result property="id"    column="id"    />
+        <result property="merchantName"    column="merchant_name"    />
+        <result property="merchantNo"    column="merchant_no"    />
+        <result property="createTime"    column="create_time"    />
+        <result property="updateTime"    column="update_time"    />
+        <result property="masterMerchantNo"    column="master_merchant_no"    />
+    </resultMap>
+
+    <sql id="selectFsSubMerchantVo">
+        select id, merchant_name, merchant_no, create_time, update_time, master_merchant_no from fs_sub_merchant
+    </sql>
+
+    <select id="selectFsSubMerchantList" parameterType="FsSubMerchant" resultMap="FsSubMerchantResult">
+        <include refid="selectFsSubMerchantVo"/>
+        <where>  
+            <if test="merchantName != null  and merchantName != ''"> and merchant_name like concat('%', #{merchantName}, '%')</if>
+            <if test="merchantNo != null  and merchantNo != ''"> and merchant_no = #{merchantNo}</if>
+            <if test="masterMerchantNo != null  and masterMerchantNo != ''"> and master_merchant_no = #{masterMerchantNo}</if>
+        </where>
+    </select>
+    
+    <select id="selectFsSubMerchantById" parameterType="Long" resultMap="FsSubMerchantResult">
+        <include refid="selectFsSubMerchantVo"/>
+        where id = #{id}
+    </select>
+        
+    <insert id="insertFsSubMerchant" parameterType="FsSubMerchant">
+        insert into fs_sub_merchant
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="id != null">id,</if>
+            <if test="merchantName != null">merchant_name,</if>
+            <if test="merchantNo != null">merchant_no,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="updateTime != null">update_time,</if>
+            <if test="masterMerchantNo != null">master_merchant_no,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="id != null">#{id},</if>
+            <if test="merchantName != null">#{merchantName},</if>
+            <if test="merchantNo != null">#{merchantNo},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="updateTime != null">#{updateTime},</if>
+            <if test="masterMerchantNo != null">#{masterMerchantNo},</if>
+         </trim>
+    </insert>
+
+    <update id="updateFsSubMerchant" parameterType="FsSubMerchant">
+        update fs_sub_merchant
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="merchantName != null">merchant_name = #{merchantName},</if>
+            <if test="merchantNo != null">merchant_no = #{merchantNo},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+            <if test="masterMerchantNo != null">master_merchant_no = #{masterMerchantNo},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteFsSubMerchantById" parameterType="Long">
+        delete from fs_sub_merchant where id = #{id}
+    </delete>
+
+    <delete id="deleteFsSubMerchantByIds" parameterType="String">
+        delete from fs_sub_merchant where id in 
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+</mapper>

+ 12 - 0
fs-service/src/main/resources/mapper/hisStore/FsStoreOrderScrmMapper.xml

@@ -93,6 +93,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="backendEditProductType"    column="backend_edit_product_type"    />
         <result property="virtualPhone"    column="virtual_phone"    />
         <result property="groupBuyId"    column="group_buy_id"    />
+        <result property="shoppingPointsClaimed"    column="shopping_points_claimed"    />
     </resultMap>
 
     <sql id="selectFsStoreOrderVo">
@@ -164,6 +165,16 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         </where>
     </select>
 
+    <select id="selectShoppingPointsPendingOrders" resultMap="FsStoreOrderResult">
+        <include refid="selectFsStoreOrderVo"/>
+        WHERE  paid = 1
+          AND status = 4
+          AND (shopping_points_claimed IS NULL OR shopping_points_claimed = 0)
+          AND create_time &gt;= #{cutoffDate}
+          AND create_time &lt;= #{sevenDaysAgo}
+          AND pay_money &gt; 0
+    </select>
+
     <select id="selectFsStoreOrderById" parameterType="Long" resultMap="FsStoreOrderResult">
         <include refid="selectFsStoreOrderVo"/>
         where id = #{id}
@@ -469,6 +480,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="courseId != null">course_id = #{courseId},</if>
             <if test="virtualPhone != null">virtual_phone = #{virtualPhone},</if>
             <if test="groupBuyId != null">group_buy_id = #{groupBuyId},</if>
+            <if test="shoppingPointsClaimed != null">shopping_points_claimed = #{shoppingPointsClaimed},</if>
         </trim>
         where id = #{id}
     </update>

+ 11 - 2
fs-service/src/main/resources/mapper/hisStore/FsStoreProductScrmMapper.xml

@@ -81,6 +81,7 @@
         <result property="activityType"    column="activity_type"    />
         <result property="activityStartTime"    column="activity_start_time"    />
         <result property="activityEndTime"    column="activity_end_time"    />
+        <result property="tagInfo"    column="tag_info"    />
     </resultMap>
 
     <sql id="selectFsStoreProductVo">
@@ -92,7 +93,7 @@
                is_display,tui_cate_id,company_ids,is_drug,drug_image,drug_reg_cert_no,common_name,dosage_form,
                unit_price,batch_number,mah,mah_address,manufacturer,manufacturer_address,indications,dosage,
                adverse_reactions,contraindications,precautions,is_audit,store_id,return_address,brand,food_production_license_code,
-               origin_place,net_content,shelf_life,domestic_imported,app_ids,purchase_limit,single_purchase_limit,activity_type,activity_start_time,activity_end_time
+               origin_place,net_content,shelf_life,domestic_imported,app_ids,purchase_limit,single_purchase_limit,activity_type,activity_start_time,activity_end_time,tag_info
         from fs_store_product_scrm
     </sql>
 
@@ -105,7 +106,7 @@
                p.is_display,p.tui_cate_id,p.company_ids,p.is_drug,p.drug_image,p.drug_reg_cert_no,p.common_name,p.dosage_form,
                p.unit_price,p.batch_number,p.mah,p.mah_address,p.manufacturer,p.manufacturer_address,p.indications,p.dosage,
                p.adverse_reactions,p.contraindications,p.precautions,p.is_audit,p.store_id,p.return_address,p.brand,p.food_production_license_code,
-               p.origin_place,p.net_content,p.shelf_life,p.domestic_imported,app_ids,p.purchase_limit,p.single_purchase_limit,p.activity_type
+               p.origin_place,p.net_content,p.shelf_life,p.domestic_imported,app_ids,p.purchase_limit,p.single_purchase_limit,p.activity_type,p.tag_info
         from fs_store_product_scrm p
     </sql>
 
@@ -151,6 +152,11 @@
             <if test="isIntegral != null "> and is_integral = #{isIntegral}</if>
             <if test="integral != null "> and integral = #{integral}</if>
             <if test="productType != null "> and product_type = #{productType}</if>
+            <if test="productTypes != null and productTypes.size() > 0"> and product_type IN
+                <foreach item="pt" collection="productTypes" open="(" separator="," close=")">
+                    #{pt}
+                </foreach>
+            </if>
             <if test="prescribeCode != null  and prescribeCode != ''"> and prescribe_code = #{prescribeCode}</if>
             <if test="prescribeSpec != null  and prescribeSpec != ''"> and prescribe_spec = #{prescribeSpec}</if>
             <if test="prescribeFactory != null  and prescribeFactory != ''"> and prescribe_factory = #{prescribeFactory}</if>
@@ -286,6 +292,7 @@
             <if test="purchaseLimit != null">purchase_limit,</if>
             <if test="singlePurchaseLimit != null">single_purchase_limit,</if>
             <if test="activityType != null">activity_type,</if>
+            <if test="tagInfo != null and tagInfo != ''">tag_info,</if>
         </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="image != null and image != ''">#{image},</if>
@@ -361,6 +368,7 @@
             <if test="purchaseLimit != null">#{purchaseLimit},</if>
             <if test="singlePurchaseLimit != null">#{singlePurchaseLimit},</if>
             <if test="activityType != null">#{activityType},</if>
+            <if test="tagInfo != null and tagInfo != ''">#{tagInfo},</if>
         </trim>
     </insert>
 
@@ -440,6 +448,7 @@
             <if test="purchaseLimit != null">purchase_limit = #{purchaseLimit},</if>
             <if test="singlePurchaseLimit != null">single_purchase_limit = #{singlePurchaseLimit},</if>
             <if test="activityType != null">activity_type = #{activityType},</if>
+            <if test="tagInfo != null and tagInfo != ''">tag_info = #{tagInfo},</if>
         </trim>
         where product_id = #{productId}
     </update>

+ 5 - 0
fs-service/src/main/resources/mapper/sop/QwSopMapper.xml

@@ -41,6 +41,7 @@
         <result property="autoGroup"    column="auto_group"    />
         <result property="autoGroupLevel"    column="auto_group_level"    />
         <result property="groupName"    column="group_name"    />
+        <result property="deadLineTime"    column="dead_line_time"    />
     </resultMap>
 
     <sql id="selectQwSopVo">
@@ -134,6 +135,7 @@
     <select id="selectQwAiSopAutoByTagsByForeach"  resultType="com.fs.qw.vo.QwSopRuleTimeVO">
         SELECT
         qs.*,
+        qs.dead_line_time AS deadLineTime,
         qst.name AS temp_name,
         qst.setting AS temp_setting,
         qst.status AS temp_status,
@@ -324,6 +326,7 @@
             <if test="data.autoGroupLevel != null">auto_group_level,</if>
             <if test="data.groupName != null">group_name,</if>
             <if test="data.isFixed != null">is_fixed,</if>
+            <if test="data.deadLineTime != null">dead_line_time,</if>
         </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="data.name != null">#{data.name},</if>
@@ -357,6 +360,7 @@
             <if test="data.autoGroupLevel != null">#{data.autoGroupLevel},</if>
             <if test="data.groupName != null">#{data.groupName},</if>
             <if test="data.isFixed != null">#{data.isFixed},</if>
+            <if test="data.deadLineTime != null">#{data.deadLineTime},</if>
         </trim>
     </insert>
 
@@ -537,6 +541,7 @@
             <if test="data.autoGroupLevel != null">auto_group_level = #{data.autoGroupLevel},</if>
             <if test="data.groupName != null">group_name = #{data.groupName},</if>
             <if test="data.isFixed != null">is_fixed = #{data.isFixed},</if>
+            <if test="data.deadLineTime != null">dead_line_time = #{data.deadLineTime},</if>
         </trim>
         where id = #{data.id}
     </update>

+ 23 - 6
fs-user-app/src/main/java/com/fs/app/controller/CourseCommentController.java

@@ -55,6 +55,7 @@ import com.fs.system.oss.CloudStorageService;
 import com.fs.system.oss.OSSFactory;
 import org.springframework.web.multipart.MultipartFile;
 
+import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
 import java.util.UUID;
@@ -107,8 +108,16 @@ public class CourseCommentController extends AppBaseController
             List<FsUserCourseCommentReplyListUVO> replies = fsUserCourseCommentService.selectFsUserCourseCommentReplyListUVO(comment.getCommentId(), getUserId());
             comment.setReplyList(replies);
         });
+        List<FsUserCourseComment> fsUserCourseComments = new ArrayList<>();
+        PageInfo<FsUserCourseComment> defaultPageInfo= new PageInfo<>(fsUserCourseComments);
+        try {
+            PageHelper.startPage(param.getPageNum(), param.getPageSize());
+            fsUserCourseComments = fsUserCourseCommentService.selectFsUserCourseCommentListByTypeAndCourseId(3, param.getCourseId());
+            defaultPageInfo = new PageInfo<>(fsUserCourseComments);
+        } catch (Exception ignore) {}
+
         PageInfo<FsUserCourseCommentListUVO> listPageInfo=new PageInfo<>(list);
-        return R.ok().put("data",listPageInfo);
+        return R.ok().put("data",listPageInfo).put("defaultPageInfo",defaultPageInfo);
     }
 
     /**
@@ -120,9 +129,16 @@ public class CourseCommentController extends AppBaseController
     {
         param.setUserId(Long.parseLong(getUserId()));
         PageHelper.startPage(param.getPageNum(), param.getPageSize());
-        List<FsUserCourseCommentListUVO> list = fsUserCourseCommentService.selectFsUserCourseCommentMyListUVO(Long.parseLong(getUserId()));
+        List<FsUserCourseCommentListUVO> list = fsUserCourseCommentService.selectFsUserCourseCommentMyListUVO(param);
         PageInfo<FsUserCourseCommentListUVO> listPageInfo=new PageInfo<>(list);
-        return R.ok().put("data",listPageInfo);
+        List<FsUserCourseComment> fsUserCourseComments = new ArrayList<>();
+        PageInfo<FsUserCourseComment> defaultPageInfo= new PageInfo<>(fsUserCourseComments);
+        try {
+            PageHelper.startPage(param.getPageNum(), param.getPageSize());
+            fsUserCourseComments = fsUserCourseCommentService.selectFsUserCourseCommentListByTypeAndCourseId(3, param.getCourseId());
+            defaultPageInfo = new PageInfo<>(fsUserCourseComments);
+        } catch (Exception ignore) {}
+        return R.ok().put("data",listPageInfo).put("defaultPageInfo",defaultPageInfo);
     }
     /**
      * 新增课堂评论
@@ -240,6 +256,9 @@ public class CourseCommentController extends AppBaseController
                     String path = "comment/video/" + datePath + "/" + uuid + suffix;
                     CloudStorageService storage = OSSFactory.build();
                     String url = storage.upload(videoFile.getBytes(), path);
+                    if (StringUtils.isNotEmpty(cloudHostProper.getCommentContentReplaceFrom())) {
+                        url = url.replace(cloudHostProper.getCommentContentReplaceFrom(), cloudHostProper.getCommentContentReplaceTo());
+                    }
                     content = (content == null ? "" : content) + "\n" + url;
                 }
             }
@@ -247,9 +266,7 @@ public class CourseCommentController extends AppBaseController
             FsUserCourseComment fsUserCourseComment = new FsUserCourseComment();
             fsUserCourseComment.setUserId(Long.parseLong(getUserId()));
             fsUserCourseComment.setCourseId(courseId);
-            if (StringUtils.isNotEmpty(cloudHostProper.getCommentContentReplaceFrom())) {
-                content = content.replace(cloudHostProper.getCommentContentReplaceFrom(), cloudHostProper.getCommentContentReplaceTo());
-            }
+
             fsUserCourseComment.setContent(content);
             fsUserCourseComment.setType(type);
             if (type == 1) {

+ 19 - 6
fs-user-app/src/main/java/com/fs/app/controller/CourseController.java

@@ -14,6 +14,7 @@ import com.fs.course.param.*;
 import com.fs.course.service.*;
 import com.fs.course.vo.*;
 import com.fs.course.vo.newfs.FsUserCourseVideoDetailsVO;
+import com.fs.his.service.IFsIntegralGoodsService;
 import com.fs.his.vo.OptionsVO;
 import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageInfo;
@@ -66,6 +67,9 @@ public class CourseController extends  AppBaseController{
     @Autowired
     private IFsCourseQuestionBankService fsCourseQuestionBankService;
 
+    @Autowired
+    private IFsIntegralGoodsService goodsService;
+
 //    @Cacheable(value="getCourseCate" )
     @ApiOperation("获取分类")
     @GetMapping("/getCourseCate")
@@ -136,10 +140,15 @@ public class CourseController extends  AppBaseController{
         PageInfo<FsUserCourseCategory> pageInfo = courseCategoryService.selectFsUserCourseCategoryAppPage(param);
         if (StringUtils.isNotEmpty(param.getCateName())) {
             int relatedCourseCount = (int) pageInfo.getTotal();
-            Long userId = StringUtils.isNotEmpty(getUserId()) ? Long.parseLong(getUserId()) : null;
-            if (userId != null) {
-                publicCourseSearchKeywordStatService.recordKeywordSearch(param.getKeyword(), relatedCourseCount, userId);
+            try {
+                Long userId = StringUtils.isNotEmpty(getUserId()) ? Long.parseLong(getUserId()) : null;
+                if (userId != null) {
+                    publicCourseSearchKeywordStatService.recordKeywordSearch(param.getKeyword(), relatedCourseCount, userId);
+                }
+            } catch (Exception ignored){
+
             }
+
         }
         return R.ok().put("data", pageInfo);
     }
@@ -159,9 +168,13 @@ public class CourseController extends  AppBaseController{
         PageInfo<FsUserCoursePublicAppVO> pageInfo = courseService.selectFsUserCoursePublicAppPage(param);
         if (StringUtils.isNotEmpty(param.getKeyword())) {
             int relatedCourseCount = (int) pageInfo.getTotal();
-            Long userId = StringUtils.isNotEmpty(getUserId()) ? Long.parseLong(getUserId()) : null;
-            if (userId != null) {
-                publicCourseSearchKeywordStatService.recordKeywordSearch(param.getKeyword(), relatedCourseCount, userId);
+            try {
+                Long userId = StringUtils.isNotEmpty(getUserId()) ? Long.parseLong(getUserId()) : null;
+                if (userId != null) {
+                    publicCourseSearchKeywordStatService.recordKeywordSearch(param.getKeyword(), relatedCourseCount, userId);
+                }
+            } catch (Exception ignored){
+
             }
         }
         return R.ok().put("data", pageInfo);

+ 6 - 0
fs-user-app/src/main/java/com/fs/app/controller/H5Controller.java

@@ -163,6 +163,12 @@ public class H5Controller
                 case DOCTOR_FILING:
                     data = config.getDoctorFiling();
                     break;
+                case APP_USER_AGREEMENT:
+                    data = config.getAppUserAgreement();
+                    break;
+                case APP_PRIVACY_AGREEMENT:
+                    data = config.getAppPrivacyAgreement();
+                    break;
                 default:
                     agreementType="notFound";
                     data="<div class=\"error-message\"><h3>暂未找到该协议内容</h3><p>请确认协议类型是否正确,或联系管理员配置相关协议内容。</p></div>";

+ 23 - 6
fs-user-app/src/main/java/com/fs/app/controller/store/CourseCommentScrmController.java

@@ -35,6 +35,7 @@ import com.fs.common.utils.DateUtils;
 import com.fs.system.oss.CloudStorageService;
 import com.fs.system.oss.OSSFactory;
 
+import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
 import java.util.UUID;
@@ -86,8 +87,16 @@ public class CourseCommentScrmController extends AppBaseController
             List<FsUserCourseCommentReplyListUVO> replies = fsUserCourseCommentService.selectFsUserCourseCommentReplyListUVO(comment.getCommentId(), getUserId());
             comment.setReplyList(replies);
         });
+        List<FsUserCourseComment> fsUserCourseComments = new ArrayList<>();
+        PageInfo<FsUserCourseComment> defaultPageInfo= new PageInfo<>(fsUserCourseComments);
+        try {
+            PageHelper.startPage(param.getPageNum(), param.getPageSize());
+            fsUserCourseComments = fsUserCourseCommentService.selectFsUserCourseCommentListByTypeAndCourseId(3, param.getCourseId());
+            defaultPageInfo = new PageInfo<>(fsUserCourseComments);
+        } catch (Exception ignore) {}
+
         PageInfo<FsUserCourseCommentListUVO> listPageInfo=new PageInfo<>(list);
-        return R.ok().put("data",listPageInfo);
+        return R.ok().put("data",listPageInfo).put("defaultPageInfo",defaultPageInfo);
     }
 
     /**
@@ -99,9 +108,16 @@ public class CourseCommentScrmController extends AppBaseController
     {
         param.setUserId(Long.parseLong(getUserId()));
         PageHelper.startPage(param.getPageNum(), param.getPageSize());
-        List<FsUserCourseCommentListUVO> list = fsUserCourseCommentService.selectFsUserCourseCommentMyListUVO(Long.parseLong(getUserId()));
+        List<FsUserCourseCommentListUVO> list = fsUserCourseCommentService.selectFsUserCourseCommentMyListUVO(param);
         PageInfo<FsUserCourseCommentListUVO> listPageInfo=new PageInfo<>(list);
-        return R.ok().put("data",listPageInfo);
+        List<FsUserCourseComment> fsUserCourseComments = new ArrayList<>();
+        PageInfo<FsUserCourseComment> defaultPageInfo= new PageInfo<>(fsUserCourseComments);
+        try {
+            PageHelper.startPage(param.getPageNum(), param.getPageSize());
+            fsUserCourseComments = fsUserCourseCommentService.selectFsUserCourseCommentListByTypeAndCourseId(3, param.getCourseId());
+            defaultPageInfo = new PageInfo<>(fsUserCourseComments);
+        } catch (Exception ignore) {}
+        return R.ok().put("data",listPageInfo).put("defaultPageInfo",defaultPageInfo);
     }
     /**
      * 新增课堂评论
@@ -219,6 +235,9 @@ public class CourseCommentScrmController extends AppBaseController
                     String path = "comment/video/" + datePath + "/" + uuid + suffix;
                     CloudStorageService storage = OSSFactory.build();
                     String url = storage.upload(videoFile.getBytes(), path);
+                    if (StringUtils.isNotEmpty(cloudHostProper.getCommentContentReplaceFrom())) {
+                        url = url.replace(cloudHostProper.getCommentContentReplaceFrom(), cloudHostProper.getCommentContentReplaceTo());
+                    }
                     content = (content == null ? "" : content) + "\n" + url;
                 }
             }
@@ -226,9 +245,7 @@ public class CourseCommentScrmController extends AppBaseController
             FsUserCourseComment fsUserCourseComment = new FsUserCourseComment();
             fsUserCourseComment.setUserId(Long.parseLong(getUserId()));
             fsUserCourseComment.setCourseId(courseId);
-            if (StringUtils.isNotEmpty(cloudHostProper.getCommentContentReplaceFrom())) {
-                content = content.replace(cloudHostProper.getCommentContentReplaceFrom(), cloudHostProper.getCommentContentReplaceTo());
-            }
+
             fsUserCourseComment.setContent(content);
             fsUserCourseComment.setType(type);
             if (type == 1) {

+ 6 - 0
fs-user-app/src/main/java/com/fs/app/controller/store/IndexScrmController.java

@@ -377,6 +377,12 @@ public class IndexScrmController extends AppBaseController {
 			case "userRemoveService":
 				data = config.getUserRemoveService();
 				break;
+			case "appUserAgreement":
+				data = config.getAppUserAgreement();
+				break;
+			case "appPrivacyAgreement":
+				data = config.getAppPrivacyAgreement();
+				break;
 			default:
 				break;
 		}