Просмотр исходного кода

Merge branch 'refs/heads/master' into master-clq-temp

# Conflicts:
#	fs-service/src/main/java/com/fs/company/service/ICompanyUserService.java
#	fs-service/src/main/java/com/fs/company/service/impl/CompanyUserServiceImpl.java
#	fs-service/src/main/java/com/fs/his/domain/FsPackage.java
#	fs-service/src/main/java/com/fs/his/dto/PayloadDTO.java
caoliqin 5 дней назад
Родитель
Сommit
b94d112fc4
100 измененных файлов с 3057 добавлено и 313 удалено
  1. 31 0
      fs-admin/src/main/java/com/fs/company/controller/CompanyController.java
  2. 8 0
      fs-admin/src/main/java/com/fs/company/controller/CompanyUserController.java
  3. 15 3
      fs-admin/src/main/java/com/fs/course/controller/FsUserCourseController.java
  4. 3 3
      fs-admin/src/main/java/com/fs/fastGpt/FastgptEventLogTotalController.java
  5. 14 0
      fs-admin/src/main/java/com/fs/his/controller/FsIntegralGoodsController.java
  6. 2 2
      fs-admin/src/main/java/com/fs/his/controller/FsIntegralOrderController.java
  7. 8 0
      fs-admin/src/main/java/com/fs/his/controller/FsSubMerchantController.java
  8. 223 27
      fs-admin/src/main/java/com/fs/his/task/Task.java
  9. 10 0
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreAfterSalesScrmController.java
  10. 252 0
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreDeliveryAbnormalOrderController.java
  11. 3 0
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreHealthOrderScrmController.java
  12. 11 0
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreProductScrmController.java
  13. 189 20
      fs-admin/src/main/java/com/fs/hisStore/task/MallStoreTask.java
  14. 60 12
      fs-admin/src/main/java/com/fs/qw/controller/CorporateWeChatSpaceController.java
  15. 12 2
      fs-admin/src/main/java/com/fs/qw/controller/QwUserController.java
  16. 105 3
      fs-admin/src/main/java/com/fs/qw/qwTask/qwTask.java
  17. 208 23
      fs-company-app/src/main/java/com/fs/app/controller/CompanyUserController.java
  18. 12 0
      fs-company/src/main/java/com/fs/company/controller/company/CompanyUserController.java
  19. 29 0
      fs-company/src/main/java/com/fs/company/controller/course/FsCourseCouponUserController.java
  20. 1 1
      fs-company/src/main/java/com/fs/company/controller/fastGpt/FastgptEventLogTotalController.java
  21. 14 2
      fs-company/src/main/java/com/fs/company/controller/live/LiveController.java
  22. 47 49
      fs-company/src/main/java/com/fs/company/controller/qw/QwAcquisitionAssistantController.java
  23. 449 0
      fs-company/src/main/java/com/fs/company/controller/qw/QwAcquisitionLinkInfoController.java
  24. 0 2
      fs-company/src/main/java/com/fs/company/controller/qw/QwUserController.java
  25. 12 3
      fs-company/src/main/java/com/fs/company/controller/store/FsStoreOrderController.java
  26. 69 22
      fs-company/src/main/java/com/fs/hisStore/controller/FsIntegralOrderController.java
  27. 14 5
      fs-company/src/main/java/com/fs/hisStore/controller/FsStoreOrderScrmController.java
  28. 4 0
      fs-ipad-task/src/main/java/com/fs/app/service/IpadSendServer.java
  29. 16 0
      fs-ipad-task/src/main/java/com/fs/app/task/SendMsg.java
  30. 8 5
      fs-live-app/src/main/java/com/fs/live/task/Task.java
  31. 128 24
      fs-live-app/src/main/java/com/fs/live/websocket/service/WebSocketServer.java
  32. 244 35
      fs-qw-task/src/main/java/com/fs/app/controller/CommonController.java
  33. 15 1
      fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopLogsTaskServiceImpl.java
  34. 11 0
      fs-service/pom.xml
  35. 2 1
      fs-service/src/main/java/com/fs/common/service/impl/SmsServiceImpl.java
  36. 5 0
      fs-service/src/main/java/com/fs/company/domain/Company.java
  37. 2 0
      fs-service/src/main/java/com/fs/company/domain/CompanyUser.java
  38. 14 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyConfigMapper.java
  39. 6 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyFsUserMapper.java
  40. 2 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyMapper.java
  41. 4 2
      fs-service/src/main/java/com/fs/company/mapper/CompanyUserMapper.java
  42. 1 1
      fs-service/src/main/java/com/fs/company/mapper/CompanyVoiceRoboticCalleesMapper.java
  43. 9 0
      fs-service/src/main/java/com/fs/company/param/SidebarCompanyImageParam.java
  44. 1 0
      fs-service/src/main/java/com/fs/company/service/ICompanyConfigService.java
  45. 10 0
      fs-service/src/main/java/com/fs/company/service/ICompanyService.java
  46. 8 0
      fs-service/src/main/java/com/fs/company/service/ICompanyUserService.java
  47. 5 0
      fs-service/src/main/java/com/fs/company/service/impl/CompanyConfigServiceImpl.java
  48. 27 0
      fs-service/src/main/java/com/fs/company/service/impl/CompanyServiceImpl.java
  49. 18 0
      fs-service/src/main/java/com/fs/company/service/impl/CompanyUserServiceImpl.java
  50. 7 5
      fs-service/src/main/java/com/fs/company/service/impl/CompanyVoiceRoboticServiceImpl.java
  51. 96 18
      fs-service/src/main/java/com/fs/company/service/impl/CompanyWorkflowEngineImpl.java
  52. 8 0
      fs-service/src/main/java/com/fs/company/vo/AiCallConfigVO.java
  53. 12 0
      fs-service/src/main/java/com/fs/company/vo/CompanyUserQwListVO.java
  54. 12 0
      fs-service/src/main/java/com/fs/company/vo/SimpleCompanyVo.java
  55. 6 0
      fs-service/src/main/java/com/fs/company/vo/easycall/EasyCallCreateTaskParam.java
  56. 34 0
      fs-service/src/main/java/com/fs/company/vo/easycall/ExtensionVO.java
  57. 5 0
      fs-service/src/main/java/com/fs/course/domain/FsCoursePlaySourceConfig.java
  58. 4 0
      fs-service/src/main/java/com/fs/course/domain/FsUserCourseCategory.java
  59. 3 0
      fs-service/src/main/java/com/fs/course/mapper/FsCoursePlaySourceConfigMapper.java
  60. 6 1
      fs-service/src/main/java/com/fs/course/mapper/FsCourseWatchLogMapper.java
  61. 27 0
      fs-service/src/main/java/com/fs/course/mapper/FsUserCourseStudyMapper.java
  62. 5 0
      fs-service/src/main/java/com/fs/course/param/FsCoursePlaySourceConfigCreateParam.java
  63. 5 0
      fs-service/src/main/java/com/fs/course/param/FsCoursePlaySourceConfigEditParam.java
  64. 3 3
      fs-service/src/main/java/com/fs/course/param/FsUserCourseCategoryAppQueryParam.java
  65. 3 1
      fs-service/src/main/java/com/fs/course/param/FsUserCourseListUParam.java
  66. 3 3
      fs-service/src/main/java/com/fs/course/param/FsUserCoursePublicAppQueryParam.java
  67. 1 0
      fs-service/src/main/java/com/fs/course/service/IFsCoursePlaySourceConfigService.java
  68. 5 0
      fs-service/src/main/java/com/fs/course/service/impl/FsCoursePlaySourceConfigServiceImpl.java
  69. 1 1
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseServiceImpl.java
  70. 28 3
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  71. 5 0
      fs-service/src/main/java/com/fs/course/vo/FsCoursePlaySourceConfigVO.java
  72. 11 0
      fs-service/src/main/java/com/fs/erp/service/IErpOrderService.java
  73. 105 3
      fs-service/src/main/java/com/fs/erp/service/impl/JSTErpOrderServiceImpl.java
  74. 2 0
      fs-service/src/main/java/com/fs/erp/utils/WeizouApiClient.java
  75. 3 0
      fs-service/src/main/java/com/fs/fastGpt/mapper/FastGptChatSessionMapper.java
  76. 48 21
      fs-service/src/main/java/com/fs/fastGpt/service/impl/AiHookServiceImpl.java
  77. 30 0
      fs-service/src/main/java/com/fs/gtPush/service/impl/uniPush2ServiceImpl.java
  78. 2 0
      fs-service/src/main/java/com/fs/gtPush/service/uniPush2Service.java
  79. 8 0
      fs-service/src/main/java/com/fs/his/config/AppRedPacketConfig.java
  80. 11 2
      fs-service/src/main/java/com/fs/his/domain/FsCourseCouponUser.java
  81. 4 1
      fs-service/src/main/java/com/fs/his/domain/FsIntegralOrder.java
  82. 1 1
      fs-service/src/main/java/com/fs/his/domain/FsIntegralOrderLogs.java
  83. 5 0
      fs-service/src/main/java/com/fs/his/domain/FsPackage.java
  84. 40 0
      fs-service/src/main/java/com/fs/his/domain/FsShareAmountDetail.java
  85. 20 0
      fs-service/src/main/java/com/fs/his/domain/FsStorePayment.java
  86. 8 0
      fs-service/src/main/java/com/fs/his/domain/FsUser.java
  87. 3 0
      fs-service/src/main/java/com/fs/his/dto/PayloadDTO.java
  88. 2 1
      fs-service/src/main/java/com/fs/his/enums/FsUserOperationEnum.java
  89. 2 1
      fs-service/src/main/java/com/fs/his/enums/ShipperCodeEnum.java
  90. 15 0
      fs-service/src/main/java/com/fs/his/mapper/FsCityMapper.java
  91. 11 0
      fs-service/src/main/java/com/fs/his/mapper/FsCourseCouponUserMapper.java
  92. 65 0
      fs-service/src/main/java/com/fs/his/mapper/FsShareAmountDetailMapper.java
  93. 6 0
      fs-service/src/main/java/com/fs/his/mapper/FsStorePaymentMapper.java
  94. 4 0
      fs-service/src/main/java/com/fs/his/mapper/FsSubMerchantMapper.java
  95. 3 0
      fs-service/src/main/java/com/fs/his/mapper/FsUserMapper.java
  96. 15 0
      fs-service/src/main/java/com/fs/his/param/FsShareAmountDetailParam.java
  97. 1 0
      fs-service/src/main/java/com/fs/his/param/FsUserIntegralLogsParam.java
  98. 4 0
      fs-service/src/main/java/com/fs/his/service/IFsCityService.java
  99. 7 0
      fs-service/src/main/java/com/fs/his/service/IFsCourseCouponUserService.java
  100. 1 0
      fs-service/src/main/java/com/fs/his/service/IFsIntegralGoodsService.java

+ 31 - 0
fs-admin/src/main/java/com/fs/company/controller/CompanyController.java

@@ -22,6 +22,7 @@ import com.fs.company.service.*;
 import com.fs.company.vo.CompanyCrmVO;
 import com.fs.company.vo.CompanyVO;
 import com.fs.company.vo.CompanyVoiceCallerListVO;
+import com.fs.company.vo.SimpleCompanyVo;
 import com.fs.core.utils.OrderCodeUtils;
 import com.fs.course.config.CourseConfig;
 import com.fs.framework.web.service.TokenService;
@@ -33,6 +34,7 @@ import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.bind.annotation.*;
 
 import java.text.ParseException;
+import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -305,4 +307,33 @@ public class CompanyController extends BaseController
         List<OptionsVO> list = companyService.selectAllCompanyList(deptId);
         return getDataTable(list);
     }
+
+    //获取侧边栏图片地址
+    @GetMapping("/getSidebarCompanyImage/{companyId}")
+    public R getSidebarCompanyImage(@PathVariable Long companyId)
+    {
+        return companyService.getSidebarCompanyImage(companyId);
+    }
+
+    //获取侧边栏图片地址
+    @PostMapping("/saveSidebarCompanyImage")
+    public R saveSidebarCompanyImage(@RequestBody SidebarCompanyImageParam param)
+    {
+        return companyService.saveSidebarCompanyImage(param);
+    }
+
+    //根据companyIds查询companyList
+    @PostMapping("/queryCompanyListByCompanyIds")
+    public R queryCompanyListByCompanyIds(@RequestBody List<Long> companyIds) {
+        List<Company> companyList = companyService.queryCompanyListByCompanyIds(companyIds);
+        //只返回 companyId和companyName
+        List<SimpleCompanyVo> simpleCompanyList = new ArrayList<>();
+        companyList.forEach(company -> {
+            SimpleCompanyVo simpleCompany = new SimpleCompanyVo();
+            simpleCompany.setCompanyId(company.getCompanyId());
+            simpleCompany.setCompanyName(company.getCompanyName());
+            simpleCompanyList.add(simpleCompany);
+        });
+        return R.ok().put("companyList", simpleCompanyList);
+    }
 }

+ 8 - 0
fs-admin/src/main/java/com/fs/company/controller/CompanyUserController.java

@@ -118,6 +118,14 @@ public class CompanyUserController extends BaseController
         List<CompanyUser> list = companyUserService.selectCompanyUserList(map);
         return R.ok().put("data",list);
     }
+
+    @GetMapping("/getAllCompanyUserListNoParams")
+    public R getAllCompanyUserListNoParams()
+    {
+        List<CompanyUser> list = companyUserService.getAllCompanyUserListNoParams();
+        return R.ok().put("data",list);
+    }
+
     @GetMapping("/getAllUserListLimit")
     public R getAllUserListLimit(@RequestParam(required = false) Long companyId,
                                  @RequestParam(required = false) String keywords){

+ 15 - 3
fs-admin/src/main/java/com/fs/course/controller/FsUserCourseController.java

@@ -186,7 +186,7 @@ public class FsUserCourseController extends BaseController {
         if (fsUserCourse.getIsPrivate() != null && fsUserCourse.getIsPrivate() == 0) {
             redisCacheUtil.delSpringCacheAllByName(PublicCourseAppCacheNames.COURSE_PUBLIC_APP_LIST);
         }
-        
+
         return toAjax(1);
     }
 
@@ -277,7 +277,7 @@ public class FsUserCourseController extends BaseController {
         fsUserCourseService.deleteFsUserCourseByCourseIds(courseIds);
         redisCacheUtil.delRedisKey("getCourseList");
         redisCacheUtil.delSpringCacheAllByName(PublicCourseAppCacheNames.COURSE_PUBLIC_APP_LIST);
-        
+
         return toAjax(1);
     }
 
@@ -370,7 +370,7 @@ public class FsUserCourseController extends BaseController {
 
         redisCacheUtil.delSpringCacheAllByName(PublicCourseAppCacheNames.COURSE_PUBLIC_APP_LIST);
 
-        
+
         return toAjax(1);
     }
 
@@ -404,6 +404,18 @@ public class FsUserCourseController extends BaseController {
         return toAjax(1);
     }
 
+    /**
+     * 同步课程模板
+     */
+    @PreAuthorize("@ss.hasPermi('course:userCourseVideo:sync')")
+    @Log(title = "同步课程模板", businessType = BusinessType.UPDATE)
+    @RepeatSubmit
+    @PostMapping("/syncTemplateByCompanyId/{courseId}/{companyId}")
+    public AjaxResult syncTemplateByCompanyId(@PathVariable Long courseId,@PathVariable Long companyId) {
+        sopTempService.syncTemplateByCompanyId(courseId,companyId);
+        return toAjax(1);
+    }
+
     /**
      * 课程下拉列表
      */

+ 3 - 3
fs-admin/src/main/java/com/fs/fastGpt/FastgptEventLogTotalController.java

@@ -21,7 +21,7 @@ import java.util.List;
 
 /**
  * ai事件埋点统计Controller
- * 
+ *
  * @author fs
  * @date 2025-06-26
  */
@@ -86,8 +86,8 @@ public class FastgptEventLogTotalController extends BaseController
      */
     @PreAuthorize("@ss.hasPermi('fastGpt:fastgptEventLogTotal:export')")
     @Log(title = "ai事件埋点统计", businessType = BusinessType.EXPORT)
-    @GetMapping("/export")
-    public AjaxResult export(FastgptEventLogTotal fastgptEventLogTotal)
+    @PostMapping("/export")
+    public AjaxResult export(@RequestBody FastgptEventLogTotal fastgptEventLogTotal)
     {
         List<FastgptEventLogTotalVo> list = fastgptEventLogTotalService.selectFastgptEventLogTotalExport(fastgptEventLogTotal);
         ExcelUtil<FastgptEventLogTotalVo> util = new ExcelUtil<FastgptEventLogTotalVo>(FastgptEventLogTotalVo.class);

+ 14 - 0
fs-admin/src/main/java/com/fs/his/controller/FsIntegralGoodsController.java

@@ -8,10 +8,12 @@ import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.his.domain.FsIntegralGoods;
+import com.fs.his.param.FsIntegralGoodsListUParam;
 import com.fs.his.param.FsIntegralGoodsParam;
 import com.fs.his.service.IFsIntegralGoodsService;
 import com.fs.his.utils.RedisCacheUtil;
 import com.fs.his.vo.FsIntegralGoodsChooseVO;
+import com.fs.his.vo.FsIntegralGoodsListUVO;
 import com.fs.his.vo.FsIntegralGoodsListVO;
 import com.fs.his.vo.FsStoreProductExcelVO;
 import com.github.pagehelper.PageHelper;
@@ -51,6 +53,18 @@ public class FsIntegralGoodsController extends BaseController
         return getDataTable(list);
     }
 
+    /**
+     * 查询积分商品列表 正常的
+     */
+    @PreAuthorize("@ss.hasPermi('his:integralGoods:list')")
+    @GetMapping("/norList")
+    public TableDataInfo norList(FsIntegralGoodsListUParam fsIntegralGoods)
+    {
+        List<FsIntegralGoodsListUVO> list=fsIntegralGoodsService.selectFsIntegralGoodsListUVO(fsIntegralGoods);
+        return getDataTable(list);
+    }
+
+
     /**
      * 导出积分商品列表
      */

+ 2 - 2
fs-admin/src/main/java/com/fs/his/controller/FsIntegralOrderController.java

@@ -502,7 +502,7 @@ public class FsIntegralOrderController extends BaseController
             }
             // 添加积分订单操作记录
             FsIntegralOrderLogs logs = new FsIntegralOrderLogs();
-            logs.setLogsId(IdUtils.fastUUID());
+//            logs.setLogsId(IdUtils.fastUUID());
             logs.setOrderId(item.getOrderId());
             logs.setChangeType("set_erp_account");
             logs.setChangeMessage(nickName+"设置ERP账户为:" + loginAccount);
@@ -553,7 +553,7 @@ public class FsIntegralOrderController extends BaseController
 
                 // 添加积分订单操作记录
                 FsIntegralOrderLogs logs = new FsIntegralOrderLogs();
-                logs.setLogsId(IdUtils.fastUUID());
+//                logs.setLogsId(IdUtils.fastUUID());
                 logs.setOrderId(order.getOrderId());
                 logs.setChangeType("create_erp_order");
                 logs.setChangeMessage(getLoginUser().getUser().getNickName()+"创建ERP订单,账户:" + loginAccount);

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

@@ -100,4 +100,12 @@ public class FsSubMerchantController extends BaseController
     {
         return toAjax(fsSubMerchantService.deleteFsSubMerchantByIds(ids));
     }
+
+    /**
+     * 子商户列表选项
+     */
+    @GetMapping("/merchantOptions")
+    public AjaxResult getMerchantOptions(){
+        return AjaxResult.success(fsSubMerchantService.selectFsSubMerchantListOptions());
+    }
 }

+ 223 - 27
fs-admin/src/main/java/com/fs/his/task/Task.java

@@ -7,12 +7,14 @@ import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
-import com.fs.common.core.domain.R;
+import com.fs.common.core.domain.entity.SysDictData;
 import com.fs.common.core.redis.RedisCache;
+import com.fs.common.exception.CustomException;
 import com.fs.common.service.impl.SmsServiceImpl;
 import com.fs.common.utils.CloudHostUtils;
 import com.fs.common.utils.DateUtils;
 import com.fs.common.utils.SecurityUtils;
+import com.fs.company.domain.Company;
 import com.fs.company.domain.CompanyMoneyLogs;
 import com.fs.company.domain.CompanyUser;
 import com.fs.company.domain.CompanyVoiceCaller;
@@ -21,6 +23,7 @@ import com.fs.company.service.ICompanyService;
 import com.fs.company.service.ICompanyUserService;
 import com.fs.company.vo.QwIpadTotalVo;
 import com.fs.company.vo.RedPacketMoneyVO;
+import com.fs.core.utils.OrderCodeUtils;
 import com.fs.course.dto.BatchSendCourseAllDTO;
 import com.fs.course.mapper.FsCourseRedPacketLogMapper;
 import com.fs.course.mapper.FsUserCompanyUserMapper;
@@ -58,7 +61,10 @@ import com.fs.hisStore.mapper.FsStorePaymentScrmMapper;
 import com.fs.hisStore.service.IFsStoreOrderScrmService;
 import com.fs.hisStore.service.IFsStorePaymentScrmService;
 import com.fs.huifuPay.domain.HuiFuQueryOrderResult;
+import com.fs.huifuPay.domain.HuifuOrderConfirm;
+import com.fs.huifuPay.domain.HuifuOrderConfirmResult;
 import com.fs.huifuPay.sdk.opps.core.request.V2TradePaymentScanpayQueryRequest;
+import com.fs.huifuPay.sdk.opps.core.utils.DateTools;
 import com.fs.huifuPay.service.HuiFuService;
 import com.fs.im.dto.*;
 import com.fs.im.service.IImService;
@@ -68,12 +74,14 @@ import com.fs.qw.domain.QwUser;
 import com.fs.qw.mapper.QwRestrictionPushRecordMapper;
 import com.fs.qw.mapper.QwUserMapper;
 import com.fs.qw.service.*;
+import com.fs.qw.service.impl.ConversationSyncService;
 import com.fs.qwApi.service.QwApiService;
 import com.fs.sop.domain.QwSopTempVoice;
 import com.fs.sop.service.IQwSopTempVoiceService;
 import com.fs.system.domain.SysConfig;
 import com.fs.system.mapper.SysConfigMapper;
 import com.fs.system.service.ISysConfigService;
+import com.fs.system.service.ISysDictTypeService;
 import com.fs.utils.OrderContextHolder;
 import com.google.gson.Gson;
 import lombok.extern.slf4j.Slf4j;
@@ -86,6 +94,7 @@ import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 import org.springframework.stereotype.Component;
 
+import java.math.BigDecimal;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.time.LocalDateTime;
@@ -219,6 +228,9 @@ public class Task {
     @Autowired
     private ISysConfigService sysConfigService;
 
+    @Autowired
+    private ISysDictTypeService dictTypeService;
+
     @Autowired
     private ThreadPoolTaskExecutor threadPoolTaskExecutor;
     @Autowired
@@ -242,6 +254,17 @@ public class Task {
     private FsUserCompanyUserMapper fsUserCompanyUserMapper;
     @Autowired
     private FsStoreOrderScrmMapper fsStoreOrderScrmMapper;
+    @Autowired
+    private FsStorePaymentMapper fsStorePaymentMapper;
+
+    @Autowired
+    private FsSubMerchantMapper fsSubMerchantMapper;
+
+    @Autowired
+    private IFsShareAmountDetailService shareAmountDetailService;
+
+    @Autowired
+    private ConversationSyncService syncService;
 
     /**
      * 定时任务,处理ai禁止回复之后的消息
@@ -716,8 +739,36 @@ public class Task {
     }
 
     public void isArtificial() throws Exception {
-        fastGptChatSessionMapper.updateFastGptChatSessionByIsReply();
+        Integer artificialTime = resolveIsArtificialTimeMinutes();
+        if (artificialTime != null && artificialTime > 0) {
+            fastGptChatSessionMapper.updateFastGptChatSessionByIsReplyWithTime(artificialTime);
+        } else {
+            fastGptChatSessionMapper.updateFastGptChatSessionByIsReply();
+        }
+    }
 
+    /**
+     * 查询字典 is_artificial_time,返回大于 0 的有效分钟数(取第一条有效配置)
+     */
+    private Integer resolveIsArtificialTimeMinutes() {
+        List<SysDictData> dictList = dictTypeService.selectDictDataByType("is_artificial_time");
+        if (CollectionUtil.isEmpty(dictList)) {
+            return null;
+        }
+        for (SysDictData dictData : dictList) {
+            if (dictData == null || StringUtils.isBlank(dictData.getDictValue())) {
+                continue;
+            }
+            try {
+                int minutes = Integer.parseInt(dictData.getDictValue().trim());
+                if (minutes > 0) {
+                    return minutes;
+                }
+            } catch (NumberFormatException e) {
+                log.warn("is_artificial_time 字典值无效:{}", dictData.getDictValue());
+            }
+        }
+        return null;
     }
 
     public void expirationQwAppCountWay() {
@@ -926,35 +977,40 @@ public class Task {
     }
 
     public void CreateWeizouErpPush() {
-        List<Long> omsList = fsStoreOrderMapper.selectFsStoreOrderNoCreateOms();
-        List<Long> integrals = fsIntegralOrderMapper.selectFsStoreOrderNoCreateOms();
-        List<Long> scrms = fsStoreOrderScrmMapper.selectFsStoreOrderNoCreateOms();
-        logger.info("推送订单id====>{}", omsList);
-        logger.info("推送积分订单id====>{}", integrals);
-        logger.info("推送SCRM订单id====>{}", scrms);
-
-        for (Long l : omsList) {
-            try {
-                fsStoreOrderService.weizouPush(l);
-            } catch (Exception e) {
-                logger.error("推送订单异常:", e);
+        try {
+            List<Long> omsList = fsStoreOrderMapper.selectFsStoreOrderNoCreateOms();
+            List<Long> integrals = fsIntegralOrderMapper.selectFsStoreOrderNoCreateOms();
+            List<Long> scrms = fsStoreOrderScrmMapper.selectFsStoreOrderNoCreateOms();
+            logger.info("推送订单id====>{}", omsList);
+            logger.info("推送积分订单id====>{}", integrals);
+            logger.info("推送SCRM订单id====>{}", scrms);
+
+            for (Long l : omsList) {
+                try {
+                    fsStoreOrderService.weizouPush(l);
+                } catch (Exception e) {
+                    logger.error("推送订单异常:", e);
+                }
             }
-        }
-        for (Long l : integrals) {
-            try {
-                fsStoreOrderService.weizouPushIntergral(l);
-            } catch (Exception e) {
-                logger.error("推送积分订单异常:", e);
+            for (Long l : integrals) {
+                try {
+                    fsStoreOrderService.weizouPushIntergral(l);
+                } catch (Exception e) {
+                    logger.error("推送积分订单异常:", e);
+                }
             }
-        }
-        for (Long l : scrms) {
-            try {
-                fsStoreOrderService.weizouPushScrm(l);
-            } catch (Exception e) {
-                logger.error("推送SCRM订单异常:", e);
+            for (Long l : scrms) {
+                try {
+                    fsStoreOrderService.weizouPushScrm(l);
+                } catch (Exception e) {
+                    logger.error("推送SCRM订单异常:", e);
+                }
             }
+        }catch (Exception e){
+            logger.error("推送订单异常:", e);
         }
-    }
+        }
+
     public void CreateOmsAndHis() {
         List<Long> omsList = fsStoreOrderMapper.selectFsStoreOrderNoCreateOms();
         logger.info("推送订单id====>{}", omsList);
@@ -1996,5 +2052,145 @@ public class Task {
         allFutures.join(); // 等待所有任务完成
     }
 
+    // 同步企业会话记录
+    public void syncAllCorps() {
+        List<QwCompany> companies = qwCompanyService.selectQwCompanyList(new QwCompany()); // 获取所有企业
+        for (QwCompany company : companies) {
+            try {
+                syncService.syncConversationsForCorp(company.getCorpId());
+            } catch (Exception e) {
+                log.error("同步企业 {} 会话失败", company.getCorpName());
+                log.error(e.getMessage());
+            }
+        }
+    }
+
+
 
+    public void shareAmount(){
+        List<FsStorePayment> payments = fsStorePaymentMapper.selectSharePaymentList();
+        if (CollectionUtil.isEmpty(payments)) return;
+        logger.info("开始处理分享金额");
+        for (FsStorePayment storePayment : payments) {
+            try {
+                Thread.sleep(1000);
+                //查询订单确认金额
+                V2TradePaymentScanpayQueryRequest request = new V2TradePaymentScanpayQueryRequest();
+                request.setOrgReqDate(DateTools.formatYYYYMMDD(storePayment.getPayTime()));
+                request.setOrgReqSeqId(storePayment.getShareCode()+"-"+storePayment.getPayCode());
+                request.setAppId(storePayment.getAppId());
+                HuiFuQueryOrderResult queryOrderResult = huiFuService.queryOrder(request);
+                logger.info("汇付订单查询返回:{}" , queryOrderResult);
+                if (queryOrderResult != null && "00000000".equals(queryOrderResult.getResp_code())) {
+                    //未开启延迟支付的跳出当前循环 执行下次循环
+                    if (queryOrderResult.getDelay_acct_flag().equals("N")) {
+                        FsStorePayment mapPayment = new FsStorePayment();
+                        mapPayment.setPaymentId(storePayment.getPaymentId());
+                        mapPayment.setIsShare(0);
+                        fsStorePaymentService.updateFsStorePayment(mapPayment);
+                        continue;
+                    }
+                    logger.info("分账订单号:{}",storePayment.getBusinessCode());
+
+                    //待确认金额
+                    BigDecimal unConfirmAmt = new BigDecimal(queryOrderResult.getUnconfirm_amt());
+                    //分账金额
+                    BigDecimal divAmount = null;
+                    //默认全部进入收款主账户
+                    BigDecimal mainAmount = unConfirmAmt;
+
+
+                    FsSubMerchant fsSubMerchant = null;
+                    FsPackageOrder order = null;
+
+                    //是否分账到合作人
+                    boolean isDiv = false;
+                    HuifuOrderConfirm confirm = new HuifuOrderConfirm();
+                    confirm.setAppId(storePayment.getAppId());
+
+                    //支付金额大于等于0.1的进行分账 最低分账金额为0.1
+                    if (storePayment.getPayMoney().compareTo(new BigDecimal("0.1")) > -1) {
+                        //分账订单
+                        order = fsPackageOrderMapper.selectFsPackageOrderByOrderSn(storePayment.getBusinessCode());
+                        if (order != null && order.getPackageId() != null && order.getCompanyId() != null) {
+                            //获取套餐包信息
+                            FsPackage fsPackage = fsPackageMapper.selectFsPackageByPackageId(order.getPackageId());
+                            //公司信息
+                            Company company = companyMapper.selectCompanyById(order.getCompanyId());
+                            //分账比列大于0且公司有分账商户
+                            if (fsPackage != null && (fsPackage.getShareRate() != null && fsPackage.getShareRate() > 0)
+                                    && company != null && company.getSubMerchantId() != null) {
+                                //分账商户
+                                fsSubMerchant = fsSubMerchantMapper.selectFsSubMerchantById(company.getSubMerchantId());
+                                if (fsSubMerchant != null && fsSubMerchant.getMerchantNo() != null) {
+                                    //金额全部进入分账商户 分账比列为100
+                                    if (fsPackage.getShareRate() == 100) {
+                                        divAmount = unConfirmAmt;
+                                        mainAmount = BigDecimal.ZERO;
+                                    } else {
+                                        //金额进行分账
+                                        divAmount = unConfirmAmt.multiply(BigDecimal.valueOf(fsPackage.getShareRate())).divide(new BigDecimal(100));
+                                        mainAmount = unConfirmAmt.subtract(divAmount);
+                                    }
+                                    isDiv = true;
+                                }
+                            }
+                        }
+                    }
+                    logger.info("开始确认订单分账..........");
+                    String orderCode = OrderCodeUtils.getOrderSn();
+                    if (StringUtils.isEmpty(orderCode)) {
+                        throw new CustomException("订单生成失败,请重试");
+                    }
+                    confirm.setReqSeqId(orderCode);
+                    if (isDiv) {
+                        //合作人分账金额和商户号
+                        confirm.setDivAmt(divAmount);
+                        confirm.setDivHfId(fsSubMerchant.getMerchantNo());
+                        //分账明细数据
+                        FsShareAmountDetail shareAmountDetail = new FsShareAmountDetail();
+                        shareAmountDetail.setShareAmount(divAmount);
+                        shareAmountDetail.setMerchantId(fsSubMerchant.getId());
+                        shareAmountDetail.setOrderCode(order.getOrderSn());
+                        shareAmountDetail.setOrderId(order.getOrderId());
+                        //添加分账明细
+                        shareAmountDetailService.insertFsShareAmountDetail(shareAmountDetail);
+                    }
+                    confirm.setMainDivAmt(mainAmount);
+                    confirm.setOrgReqDate(queryOrderResult.getOrg_req_date());
+                    confirm.setOrgReqSeqId(queryOrderResult.getOrg_req_seq_id());
+                    confirm.setMainHfId(queryOrderResult.getHuifu_id());
+                    confirm.setTotalAmount(storePayment.getPayMoney());
+
+                    //执行分账
+                    HuifuOrderConfirmResult result = huiFuService.confirmOrder(confirm);
+                    if (result != null && result.getResp_code().equals("00000000") && (result.getTrans_stat().equals("S") || result.getTrans_stat().equals("P"))) {
+                        logger.info("分账成功,订单号:{}",storePayment.getBusinessCode());
+                        FsStorePayment mapPayment = new FsStorePayment();
+                        //分账标识
+                        mapPayment.setPaymentId(storePayment.getPaymentId());
+                        mapPayment.setShareStatus(1);
+                        mapPayment.setShareDate(result.getReq_date());
+                        mapPayment.setShareCode(result.getReq_seq_id());
+                        logger.info("更新支付记录");
+                        fsStorePaymentMapper.updateFsStorePayment(mapPayment);
+                    } else {
+                        throw new CustomException("分账失败");
+                    }
+                } else {
+                    if (queryOrderResult != null) {
+                        throw new CustomException(queryOrderResult.getResp_desc());
+                    }
+                }
+            } catch (Exception e) {
+                FsStorePayment mapPayment = new FsStorePayment();
+                //分账标识
+                mapPayment.setPaymentId(storePayment.getPaymentId());
+                mapPayment.setShareStatus(1);
+                mapPayment.setShareCode(e.getMessage());
+                fsStorePaymentMapper.updateFsStorePayment(mapPayment);
+                logger.info("分账失败,订单号:{},原因:{}", storePayment.getBusinessCode(),e.getMessage());
+            }
+        }
+    }
 }

+ 10 - 0
fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreAfterSalesScrmController.java

@@ -20,6 +20,7 @@ import com.fs.hisStore.domain.*;
 import com.fs.hisStore.param.FsStoreAfterSalesAudit1Param;
 import com.fs.hisStore.param.FsStoreAfterSalesAudit2Param;
 import com.fs.hisStore.param.FsStoreAfterSalesCancelParam;
+import com.fs.hisStore.param.FsStoreAfterSalesErpStatusParam;
 import com.fs.hisStore.param.FsStoreAfterSalesRefundParam;
 import com.fs.hisStore.service.IFsStoreAfterSalesItemScrmService;
 import com.fs.hisStore.service.IFsStoreAfterSalesScrmService;
@@ -225,6 +226,15 @@ public class FsStoreAfterSalesScrmController extends BaseController
     }
 
 
+    @PreAuthorize("@ss.hasPermi('store:storeAfterSales:edit')")
+    @PostMapping("/confirmErpStatus")
+    public R confirmErpStatus(@RequestBody FsStoreAfterSalesErpStatusParam param)
+    {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        param.setOperator(loginUser.getUser().getNickName());
+        return fsStoreAfterSalesService.updateErpExceptionStatus(param);
+    }
+
     @PreAuthorize("@ss.hasPermi('store:storeAfterSales:audit1')")
     @PostMapping("/audit1")
     //平台审核

+ 252 - 0
fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreDeliveryAbnormalOrderController.java

@@ -0,0 +1,252 @@
+package com.fs.hisStore.controller;
+
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.R;
+import com.fs.common.core.domain.model.LoginUser;
+import com.fs.common.utils.CloudHostUtils;
+import com.fs.common.utils.ParseUtils;
+import com.fs.common.utils.ServletUtils;
+import com.fs.common.utils.StringUtils;
+import com.fs.company.domain.Company;
+import com.fs.company.domain.CompanyUser;
+import com.fs.company.service.ICompanyService;
+import com.fs.company.service.ICompanyUserService;
+import com.fs.company.service.ICompanyMoneyLogsService;
+import com.fs.company.vo.CompanyStoreOrderMoneyLogsVO;
+import com.fs.company.param.CompanyStoreOrderMoneyLogsListParam;
+import com.fs.framework.web.service.TokenService;
+import com.fs.his.domain.FsUser;
+import com.fs.his.service.IFsUserService;
+import com.fs.his.vo.FsStoreOrderListAndStatisticsVo;
+import com.fs.hisStore.domain.FsStoreAfterSalesScrm;
+import com.fs.hisStore.domain.FsStoreOrderItemScrm;
+import com.fs.hisStore.domain.FsStoreOrderScrm;
+import com.fs.hisStore.domain.FsStoreOrderStatusScrm;
+import com.fs.hisStore.domain.FsStorePaymentScrm;
+import com.fs.hisStore.param.FsStoreOrderDeliveryExceptionParam;
+import com.fs.hisStore.param.FsStoreOrderParam;
+import com.fs.hisStore.service.IFsStoreAfterSalesScrmService;
+import com.fs.hisStore.service.IFsStoreOrderAuditLogScrmService;
+import com.fs.hisStore.service.IFsStoreOrderItemScrmService;
+import com.fs.hisStore.service.IFsStoreOrderScrmService;
+import com.fs.hisStore.service.IFsStoreOrderStatusScrmService;
+import com.fs.hisStore.service.IFsStorePaymentScrmService;
+import com.fs.hisStore.vo.FsStoreOrderAuditLogVO;
+import com.fs.hisStore.vo.FsStoreOrderVO;
+import com.github.pagehelper.PageHelper;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 物流异常订单 Controller
+ */
+@RestController
+@RequestMapping("/store/store/storeDeliveryAbnormalOrder")
+public class FsStoreDeliveryAbnormalOrderController extends BaseController {
+
+    @Autowired
+    private IFsStoreOrderScrmService fsStoreOrderService;
+
+    @Autowired
+    private IFsStoreOrderItemScrmService orderItemService;
+
+    @Autowired
+    private IFsStoreOrderStatusScrmService orderStatusService;
+
+    @Autowired
+    private IFsStorePaymentScrmService paymentService;
+
+    @Autowired
+    private IFsUserService userService;
+
+    @Autowired
+    private ICompanyUserService companyUserService;
+
+    @Autowired
+    private ICompanyService companyService;
+
+    @Autowired
+    private ICompanyMoneyLogsService moneyLogsService;
+
+    @Autowired
+    private IFsStoreOrderAuditLogScrmService orderAuditLogService;
+
+    @Autowired
+    private IFsStoreAfterSalesScrmService fsStoreAfterSalesService;
+
+    @Autowired
+    private TokenService tokenService;
+
+    @Value("${cloud_host.company_name}")
+    private String signProjectName;
+
+    /**
+     * 查询物流异常订单列表(默认 delivery_status = 4 问题件)
+     */
+    @PreAuthorize("@ss.hasPermi('store:storeDeliveryAbnormalOrder:list')")
+    @PostMapping("/list")
+    public FsStoreOrderListAndStatisticsVo list(@RequestBody FsStoreOrderParam param) {
+        PageHelper.startPage(param.getPageNum(), param.getPageSize());
+        normalizeTimeRange(param);
+        param.setDeliveryStatus(4);
+//        if ("北京卓美".equals(signProjectName)) {
+//            param.setIsHealth("1");
+//        }
+        List<FsStoreOrderVO> list = fsStoreOrderService.selectFsStoreOrderListVO(param);
+        com.fs.common.core.page.TableDataInfo dataTable = getDataTable(list);
+        if (CloudHostUtils.hasCloudHostName("康年堂")) {
+            dataTable.setMsg("knt");
+        }
+        maskSensitiveFields(list);
+        FsStoreOrderListAndStatisticsVo vo = new FsStoreOrderListAndStatisticsVo();
+        BeanUtils.copyProperties(dataTable, vo);
+        if (dataTable.getTotal() > 0) {
+            Map<String, BigDecimal> statistics = fsStoreOrderService.selectFsStoreOrderStatistics(param);
+            if (statistics != null && statistics.size() >= 3) {
+                vo.setPayPriceTotal(statistics.get("pay_price").toString());
+                vo.setPayMoneyTotal(statistics.get("pay_money").toString());
+                vo.setPayRemainTotal(statistics.get("pay_remain").toString());
+            } else {
+                vo.setPayPriceTotal("0");
+                vo.setPayMoneyTotal("0");
+                vo.setPayRemainTotal("0");
+            }
+            String productStatistics = fsStoreOrderService.selectFsStoreOrderProductStatistics(param);
+            vo.setProductInfo(StringUtils.isNotBlank(productStatistics) ? productStatistics : "");
+        }
+        return vo;
+    }
+
+    /**
+     * 获取物流异常订单详情
+     */
+    @PreAuthorize("@ss.hasPermi('store:storeDeliveryAbnormalOrder:query')")
+    @GetMapping("/{id}")
+    public R getInfo(@PathVariable("id") Long id) {
+        FsStoreOrderScrm order = fsStoreOrderService.selectFsStoreOrderById(id);
+        if (order == null) {
+            return R.error("订单不存在");
+        }
+        if (order.getDeliveryStatus() == null || order.getDeliveryStatus() != 4) {
+            return R.error("该订单不是物流异常订单");
+        }
+        order.setUserPhone(ParseUtils.parsePhone(order.getUserPhone()));
+        order.setUserAddress(ParseUtils.parseAddress(order.getUserAddress()));
+        FsUser user = userService.selectFsUserById(order.getUserId());
+        if (user != null) {
+            user.setPhone(ParseUtils.parsePhone(user.getPhone()));
+        }
+        fillCompanyInfo(order);
+
+        FsStoreOrderItemScrm itemMap = new FsStoreOrderItemScrm();
+        itemMap.setOrderId(order.getId());
+        List<FsStoreOrderItemScrm> items = orderItemService.selectFsStoreOrderItemList(itemMap);
+        FsStoreOrderStatusScrm statusMap = new FsStoreOrderStatusScrm();
+        statusMap.setOrderId(order.getId());
+        List<FsStoreOrderStatusScrm> logs = orderStatusService.selectFsStoreOrderStatusList(statusMap);
+        List<FsStorePaymentScrm> payments = paymentService.selectFsStorePaymentByOrderId(order.getId());
+        List<CompanyStoreOrderMoneyLogsVO> tuiMoneyLogs = new ArrayList<>();
+        if (order.getCompanyId() != null) {
+            CompanyStoreOrderMoneyLogsListParam moneyLogsMap = new CompanyStoreOrderMoneyLogsListParam();
+            moneyLogsMap.setCompanyId(order.getCompanyId());
+            moneyLogsMap.setBusinessId(order.getId().toString());
+            tuiMoneyLogs = moneyLogsService.selectCompanyStoreOrderMoneyLogsList(moneyLogsMap);
+        }
+        List<FsStoreOrderAuditLogVO> auditLogs = orderAuditLogService.selectStoreOrderAuditLogVOByOrderId(order.getId());
+        FsStoreAfterSalesScrm afterSales = null;
+        if (order.getStatus() != null && (order.getStatus() == -1 || order.getStatus() == -2)) {
+            afterSales = fsStoreAfterSalesService.selectFsStoreAfterSalesByOrderCode(order.getOrderCode());
+        }
+        return R.ok().put("order", order).put("items", items).put("logs", logs).put("user", user)
+                .put("payments", payments).put("tuiMoneyLogs", tuiMoneyLogs)
+                .put("auditLogs", auditLogs).put("afterSales", afterSales);
+    }
+
+    /**
+     * 处理物流异常(标记为已处理并填写备注)
+     */
+    @PreAuthorize("@ss.hasPermi('store:storeDeliveryAbnormalOrder:handle')")
+    @PostMapping("/handle")
+    public R handle(@RequestBody FsStoreOrderDeliveryExceptionParam param) {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        param.setOperator(loginUser.getUser().getNickName());
+        if (param.getDeliveryExceptionStatus() == null) {
+            param.setDeliveryExceptionStatus(2);
+        }
+        return fsStoreOrderService.updateDeliveryExceptionStatus(param);
+    }
+
+    private void normalizeTimeRange(FsStoreOrderParam param) {
+        if (!StringUtils.isEmpty(param.getCreateTimeRange())) {
+            param.setCreateTimeList(param.getCreateTimeRange().split("--"));
+        }
+        if (!StringUtils.isEmpty(param.getPayTimeRange())) {
+            param.setPayTimeList(param.getPayTimeRange().split("--"));
+        }
+        if (!StringUtils.isEmpty(param.getDeliveryImportTimeRange())) {
+            param.setDeliveryImportTimeList(param.getDeliveryImportTimeRange().split("--"));
+        }
+        if (!StringUtils.isEmpty(param.getDeliverySendTimeRange())) {
+            param.setDeliverySendTimeList(param.getDeliverySendTimeRange().split("--"));
+        }
+    }
+
+    private void maskSensitiveFields(List<FsStoreOrderVO> list) {
+        if (list == null) {
+            return;
+        }
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        boolean showFinance = loginUser.getPermissions().contains("his:storeAfterSales:finance")
+                || loginUser.getPermissions().contains("*:*:*");
+        for (FsStoreOrderVO vo : list) {
+            if (StringUtils.isNotEmpty(vo.getPhone())) {
+                vo.setPhone(vo.getPhone().replaceAll("(\\d{3})\\d*(\\d{4})", "$1****$2"));
+            }
+            if (StringUtils.isNotEmpty(vo.getUserPhone())) {
+                vo.setUserPhone(vo.getUserPhone().replaceAll("(\\d{3})\\d*(\\d{4})", "$1****$2"));
+            }
+            if (!showFinance) {
+                vo.setPayPostage(BigDecimal.ZERO);
+                vo.setCost(BigDecimal.ZERO);
+                vo.setFPrice(BigDecimal.ZERO);
+                vo.setPayDelivery(BigDecimal.ZERO);
+                vo.setBarCode("");
+                vo.setCateName("");
+                vo.setBankTransactionId("");
+            } else if (vo.getCost() != null && vo.getTotalNum() != null) {
+                vo.setFPrice(vo.getCost().multiply(BigDecimal.valueOf(vo.getTotalNum())));
+            }
+        }
+    }
+
+    private void fillCompanyInfo(FsStoreOrderScrm order) {
+        if (order.getCompanyUserId() != null) {
+            CompanyUser companyUser = companyUserService.selectCompanyUserByUserId(order.getCompanyUserId());
+            if (companyUser != null) {
+                Company company = companyService.selectCompanyById(companyUser.getCompanyId());
+                order.setCompanyUserName(companyUser.getNickName());
+                if (company != null) {
+                    order.setCompanyName(company.getCompanyName());
+                }
+            }
+        } else if (order.getCompanyId() != null) {
+            Company company = companyService.selectCompanyById(order.getCompanyId());
+            if (company != null) {
+                order.setCompanyName(company.getCompanyName());
+            }
+        }
+    }
+}

+ 3 - 0
fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreHealthOrderScrmController.java

@@ -100,6 +100,9 @@ public class FsStoreHealthOrderScrmController extends BaseController {
             param.setDeliverySendTimeList(param.getDeliverySendTimeRange().split("--"));
         }
 //        param.setIsHealth("1");
+        if ("北京卓美".equals(signProjectName)) {
+          param.setIsHealth("1");
+        }
         List<FsStoreOrderVO> list = fsStoreOrderService.selectFsStoreOrderListVO(param);
         //金牛需求 区别其他项目 status = 6 (金牛代服管家) ,其他项目请避免使用订单状态status = 6
         TableDataInfo dataTable = getDataTable(list);

+ 11 - 0
fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreProductScrmController.java

@@ -70,6 +70,17 @@ public class FsStoreProductScrmController extends BaseController
         return R.ok();
     }
 
+    /**
+     * 批量修改商品上下架、商城展示、所属公司
+     */
+    @PreAuthorize("@ss.hasPermi('store:storeProduct:edit')")
+    @PostMapping("/bulkUpdate")
+    @Log(title = "商品管理", businessType = BusinessType.UPDATE, isStoreLog = true, logParam = {"商品", "批量修改商品信息"})
+    public R bulkUpdate(@RequestBody ModifyMoreDTO modifyMoreDTO) {
+        fsStoreProductService.batchModify(modifyMoreDTO);
+        return R.ok();
+    }
+
     @PreAuthorize("@ss.hasPermi('store:storeProduct:list')")
     @PostMapping("/batchAudit")
     @Log(title = "商品审核", businessType = BusinessType.AUDIT,isStoreLog = true,logParam = {"商品","批量审核商品信息"},

+ 189 - 20
fs-admin/src/main/java/com/fs/hisStore/task/MallStoreTask.java

@@ -62,9 +62,11 @@ import java.text.SimpleDateFormat;
 import java.time.LocalTime;
 import java.util.ArrayList;
 import java.util.Date;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.CompletableFuture;
+import java.util.stream.Collectors;
 
 import static com.fs.hisStore.constants.StoreConstants.DELIVERY;
 
@@ -270,36 +272,90 @@ public class MallStoreTask
     }
 
     //每5分钟执行一次
-    public void deliveryOp()
+    public void deliveryOp() throws InterruptedException
     {
         List<FsStoreOrderScrm> list = fsStoreOrderMapper.selectUpdateExpress();
-        Date nowDate = DateUtils.getNowDate();
-        for (FsStoreOrderScrm order : list){
-            order.setUpdateTime(new Date());
-            orderService.updateFsStoreOrderDb(order);
+        if (list == null || list.isEmpty()) {
+            return;
+        }
+        IErpOrderService erpOrderService = getErpOrderService();
+        if (erpOrderService == null) {
+            return;
+        }
+
+        // 聚水潭:批量查询物流并同步发货
+        if (erpOrderService == jSTOrderService) {
+            jstErpOrderDeliveryScrm(list);
+            return;
+        }
+        // 其他 ERP:逐单查询
+        if (erpOrderService == dfOrderService) {
+            return;
+        }
+        for (FsStoreOrderScrm order : list) {
+            if (StringUtils.isEmpty(order.getExtendOrderId())) {
+                continue;
+            }
             ErpOrderQueryRequert request = new ErpOrderQueryRequert();
             request.setCode(order.getExtendOrderId());
-            IErpOrderService erpOrderService = getErpOrderService();
             ErpOrderQueryResponse response = erpOrderService.getScrmOrder(request);
-            if (erpOrderService != dfOrderService) {
-                if(response.getOrders()!=null && !response.getOrders().isEmpty()){
-                    for(ErpOrderQuery orderQuery : response.getOrders()){
-                        if (orderQuery.getDeliverys() != null && !orderQuery.getDeliverys().isEmpty()) {
-                            for (ErpDeliverys delivery : orderQuery.getDeliverys()) {
-                                if (delivery.getDelivery() && StringUtils.isNotEmpty(delivery.getMail_no())) {
-                                    //更新商订单状态 删除REDIS
-                                    orderService.deliveryOrder(order.getOrderCode(), delivery.getMail_no(), delivery.getExpress_code(), delivery.getExpress_name());
-                                    redisCache.deleteObject(DELIVERY + ":" + order.getExtendOrderId());
-                                }
-                            }
+            syncScrmDeliveryFromErpResponse(response, buildOrderCodeMap(list));
+        }
+    }
 
-                        }
-                    }
+    /**
+     * 聚水潭批量查询发货状态(参考 ffhx StoreTask#jstErpOrderDelivery)
+     */
+    private void jstErpOrderDeliveryScrm(List<FsStoreOrderScrm> list) throws InterruptedException {
+        List<FsStoreOrderScrm> pendingList = list.stream()
+                .filter(order -> StringUtils.isNotEmpty(order.getExtendOrderId()))
+                .collect(Collectors.toList());
+        if (pendingList.isEmpty()) {
+            return;
+        }
+        Map<String, FsStoreOrderScrm> orderCodeMap = buildOrderCodeMap(pendingList);
+        int batchSize = 30;
+        for (int i = 0; i < pendingList.size(); i += batchSize) {
+            if (i > 0) {
+                Thread.sleep(300);
+            }
+            int end = Math.min(i + batchSize, pendingList.size());
+            List<FsStoreOrderScrm> batchOrders = pendingList.subList(i, end);
+            ErpOrderQueryResponse response = jSTOrderService.batchGetScrmOrder(batchOrders);
+            syncScrmDeliveryFromErpResponse(response, orderCodeMap);
+        }
+    }
 
-                }
+    private Map<String, FsStoreOrderScrm> buildOrderCodeMap(List<FsStoreOrderScrm> list) {
+        Map<String, FsStoreOrderScrm> orderCodeMap = new HashMap<>();
+        for (FsStoreOrderScrm order : list) {
+            if (StringUtils.isNotEmpty(order.getOrderCode())) {
+                orderCodeMap.put(order.getOrderCode(), order);
             }
         }
+        return orderCodeMap;
+    }
 
+    private void syncScrmDeliveryFromErpResponse(ErpOrderQueryResponse response, Map<String, FsStoreOrderScrm> orderCodeMap) {
+        if (response == null || response.getOrders() == null || response.getOrders().isEmpty()) {
+            return;
+        }
+        for (ErpOrderQuery orderQuery : response.getOrders()) {
+            if (orderQuery.getDeliverys() == null || orderQuery.getDeliverys().isEmpty()) {
+                continue;
+            }
+            FsStoreOrderScrm order = orderCodeMap.get(orderQuery.getCode());
+            if (order == null) {
+                continue;
+            }
+            for (ErpDeliverys delivery : orderQuery.getDeliverys()) {
+                if (delivery.getDelivery() && StringUtils.isNotEmpty(delivery.getMail_no())) {
+                    orderService.deliveryOrder(order.getOrderCode(), delivery.getMail_no(),
+                            delivery.getExpress_code(), delivery.getExpress_name());
+                    redisCache.deleteObject(DELIVERY + ":" + order.getExtendOrderId());
+                }
+            }
+        }
     }
 
 
@@ -417,6 +473,15 @@ public class MallStoreTask
             log.info("[cancelUnpayOrder] 发现{}个超时未支付订单,开始取消", orderList.size());
             for (FsStoreOrderScrm order : orderList) {
                 try {
+                    StringBuilder bankStatusDesc = new StringBuilder();
+                    if (!isBankOrderPendingPay(order, bankStatusDesc)) {
+                        String remark = String.format("[超时取消跳过 %s] 银行订单非待支付(%s),禁止自动取消",
+                                DateUtils.getTime(), bankStatusDesc);
+                        appendOrderRemark(order, remark);
+                        log.warn("[cancelUnpayOrder] 跳过取消,orderId={}, orderCode={}, bankStatus={}",
+                                order.getId(), order.getOrderCode(), bankStatusDesc);
+                        continue;
+                    }
                     // cancelOrder内部已处理活动库存回滚(refundStock -> refundActivityStock)
                     orderService.cancelOrder(order.getId());
                     log.info("[cancelUnpayOrder] 超时订单取消成功,orderId={}, orderType={}, associatedId={}",
@@ -430,6 +495,110 @@ public class MallStoreTask
         }
     }
 
+    /**
+     * 取消前校验银行侧是否仍为待支付。
+     * 汇付:trans_stat=S/P 视为非待支付;易宝:status=100 且 state=0 视为已支付。
+     */
+    private boolean isBankOrderPendingPay(FsStoreOrderScrm order, StringBuilder bankStatusDesc) {
+        List<FsStorePaymentScrm> payments = fsStorePaymentMapper.selectNoPayByOrderId(order.getId());
+        if (payments == null || payments.isEmpty()) {
+            return true;
+        }
+        boolean hasBankTrade = false;
+        for (FsStorePaymentScrm payment : payments) {
+            if (StringUtils.isEmpty(payment.getTradeNo())) {
+                continue;
+            }
+            hasBankTrade = true;
+            if (StringUtils.isNotEmpty(payment.getAppId())) {
+                if (!checkHuiFuPendingPay(payment, bankStatusDesc)) {
+                    return false;
+                }
+            } else {
+                if (!checkYbPayPendingPay(payment, bankStatusDesc)) {
+                    return false;
+                }
+            }
+        }
+        return !hasBankTrade || bankStatusDesc.length() == 0;
+    }
+
+    private boolean checkHuiFuPendingPay(FsStorePaymentScrm payment, StringBuilder bankStatusDesc) {
+        V2TradePaymentScanpayQueryRequest request = new V2TradePaymentScanpayQueryRequest();
+        request.setOrgReqDate(new SimpleDateFormat("yyyyMMdd").format(payment.getCreateTime()));
+        request.setOrgHfSeqId(payment.getTradeNo());
+        request.setAppId(payment.getAppId());
+        try {
+            HuiFuQueryOrderResult result = huiFuService.queryOrder(request);
+            if (result == null) {
+                bankStatusDesc.append("汇付无返回");
+                return true;
+            }
+            if (!"00000000".equals(result.getResp_code())) {
+                return true;
+            }
+            if ("S".equals(result.getTrans_stat())) {
+                bankStatusDesc.append("汇付已成功(S)");
+                return false;
+            }
+            if ("P".equals(result.getTrans_stat())) {
+                bankStatusDesc.append("汇付处理中(P)");
+                return false;
+            }
+            return true;
+        } catch (Exception e) {
+            log.error("[cancelUnpayOrder] 汇付查询失败 paymentId={}", payment.getPaymentId(), e);
+            bankStatusDesc.append("汇付查询异常");
+            return false;
+        }
+    }
+
+    private boolean checkYbPayPendingPay(FsStorePaymentScrm payment, StringBuilder bankStatusDesc) {
+        try {
+            OrderQueryDTO query = new OrderQueryDTO();
+            query.setUpOrderId(payment.getTradeNo());
+            OrderResult orderResult = ybPayService.getOrder(query);
+            if (orderResult == null) {
+                if (StringUtils.isNotEmpty(payment.getPayCode())) {
+                    query = new OrderQueryDTO();
+                    query.setLowOrderId("store-" + payment.getPayCode());
+                    orderResult = ybPayService.getOrder(query);
+                }
+            }
+            if (orderResult == null) {
+                return true;
+            }
+            if ("100".equals(orderResult.getStatus()) && "0".equals(orderResult.getState())) {
+                bankStatusDesc.append("易宝已支付");
+                return false;
+            }
+            return true;
+        } catch (Exception e) {
+            log.error("[cancelUnpayOrder] 易宝查询失败 paymentId={}", payment.getPaymentId(), e);
+            bankStatusDesc.append("易宝查询异常");
+            return false;
+        }
+    }
+
+    private void appendOrderRemark(FsStoreOrderScrm order, String appendRemark) {
+        if (order == null || StringUtils.isEmpty(appendRemark)) {
+            return;
+        }
+        String oldRemark = StringUtils.defaultString(order.getRemark());
+        if (oldRemark.contains(appendRemark)) {
+            return;
+        }
+        String newRemark = StringUtils.isEmpty(oldRemark) ? appendRemark : oldRemark + ";" + appendRemark;
+        if (newRemark.length() > 500) {
+            newRemark = newRemark.substring(newRemark.length() - 500);
+        }
+        FsStoreOrderScrm update = new FsStoreOrderScrm();
+        update.setId(order.getId());
+        update.setRemark(newRemark);
+        fsStoreOrderMapper.updateFsStoreOrder(update);
+        order.setRemark(newRemark);
+    }
+
 
     //每天执行一次
     public void userMoneyOp()

+ 60 - 12
fs-admin/src/main/java/com/fs/qw/controller/CorporateWeChatSpaceController.java

@@ -4,53 +4,101 @@ import com.alibaba.fastjson.JSONObject;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.exception.CustomException;
+import com.fs.qw.dto.SearchMsgRequest;
 import com.fs.qw.service.ICorporateWeChatSpaceService;
+import com.fs.qw.vo.QwSessionConfigVo;
+import com.fs.qw.vo.SearchResultVO;
 import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
 import org.springframework.web.bind.annotation.*;
 
 /**
  * 企业微信专区-统一前端 API 接口
  * */
+@Slf4j
+@RequiredArgsConstructor
 @RestController
 @RequestMapping("/weChatSpace")
-@RequiredArgsConstructor
 public class CorporateWeChatSpaceController extends BaseController {
 
     private final ICorporateWeChatSpaceService weChatSpaceService;
 
-    // 企业微信会话专区中转接口
+    // 会话信息列表
     @GetMapping("/conversations")
     public JSONObject getConversations(
-            @RequestParam(defaultValue = "0") long seq,
             @RequestParam(defaultValue = "100") long limit,
-            @RequestParam(defaultValue = "0") long proxy,
             @RequestParam(defaultValue = "30") long timeout,
+            @RequestParam(required = false) String cursor,
             @RequestParam(required = false) String customerId,
-            @RequestParam(required = false) String staffUserId) throws Exception {
+            @RequestParam(required = false) String staffUserId,
+            @RequestParam(required = false) String corpid) {
         if (customerId == null|| customerId.isEmpty()) {
             throw new CustomException("客户id不能为空");
         }else if (staffUserId == null|| staffUserId.isEmpty()) {
             throw new CustomException("员工id不能为空");
         }
-        return weChatSpaceService.fetchConversations(seq, limit, proxy, timeout, customerId,staffUserId);
+        return weChatSpaceService.fetchConversations(limit, timeout, cursor, customerId,staffUserId,corpid);
+    }
+
+    /**
+     * 关键词搜索会话消息
+     */
+    @PostMapping("/searchMsg")
+    public AjaxResult searchMsg(@RequestBody SearchMsgRequest request) {
+        // 参数校验
+        if (StringUtils.isBlank(request.getCorpId())) {
+            return AjaxResult.error("企业ID不能为空");
+        }
+        if (StringUtils.isBlank(request.getQueryWord()) || request.getQueryWord().length() < 2) {
+            return AjaxResult.error("关键词至少2个字符");
+        }
+        // 若指定了单聊,则员工ID和客户ID不能为空
+        if (request.getChatType() != null && request.getChatType() == 1) {
+            if (StringUtils.isBlank(request.getStaffUserId()) || StringUtils.isBlank(request.getCustomerId())) {
+                return AjaxResult.error("单聊搜索必须提供员工ID和客户ID");
+            }
+        }
+        // 若指定了群聊,则群聊ID不能为空
+        if (request.getChatType() != null && request.getChatType() == 2) {
+            if (StringUtils.isBlank(request.getChatId())) {
+                return AjaxResult.error("群聊搜索必须提供群聊ID");
+            }
+        }
+
+        try {
+            SearchResultVO result = weChatSpaceService.searchMsg(request);
+            return AjaxResult.success(result);
+        } catch (CustomException e) {
+            return AjaxResult.error(e.getMessage());
+        } catch (Exception e) {
+            log.error("搜索异常", e);
+            return AjaxResult.error("搜索失败:" + e.getMessage());
+        }
     }
 
 
     // agentConfig 签名
     @GetMapping("/getAgentConfigSignature")
-    public JSONObject getAgentConfigSignature(@RequestParam("url") String url) {
-        return weChatSpaceService.getAgentConfigSignature(url);
+    public JSONObject getAgentConfigSignature(@RequestParam("url") String url, @RequestParam("corpid") String corpid) {
+        return weChatSpaceService.getAgentConfigSignature(url,corpid);
     }
 
     // Web 登录
     @PostMapping("/login")
     public JSONObject login(@RequestBody JSONObject param) {
-        return weChatSpaceService.login(param.getString("code"));
+        return weChatSpaceService.login(param.getString("code"), param.getString("corpid"));
     }
 
     //获取企业微信专区会话配置
-    @GetMapping("/getQwSessionConfig")
-    public AjaxResult getQwSessionConfig() {
-        return AjaxResult.success(weChatSpaceService.getQwSessionConfig());
+    @GetMapping("/getQwSessionConfig/{corpid}")
+    public AjaxResult getQwSessionConfig(@PathVariable String corpid) {
+        QwSessionConfigVo qwSessionConfig = weChatSpaceService.getQwSessionConfigByCorpid(corpid);
+        //敏感信息设置为null
+        qwSessionConfig.setPrivateKey(null);
+        qwSessionConfig.setAgentSecret(null);
+        qwSessionConfig.setProgramId(null);
+        qwSessionConfig.setAbilityIds(null);
+        return AjaxResult.success(qwSessionConfig);
     }
 }

+ 12 - 2
fs-admin/src/main/java/com/fs/qw/controller/QwUserController.java

@@ -25,6 +25,7 @@ import com.fs.framework.manager.AsyncManager;
 import com.fs.framework.manager.factory.AsyncFactory;
 import com.fs.qw.domain.QwExternalContact;
 import com.fs.qw.domain.QwUser;
+import com.fs.qw.dto.QwUserQueryDto;
 import com.fs.qw.mapper.QwCompanyMapper;
 import com.fs.qw.mapper.QwExternalContactMapper;
 import com.fs.qw.param.*;
@@ -32,6 +33,7 @@ import com.fs.qw.service.IQwDeptService;
 import com.fs.qw.service.IQwExternalContactTransferCompanyAuditService;
 import com.fs.qw.service.IQwUserService;
 import com.fs.qw.vo.QwOptionsVO;
+import com.fs.qw.vo.QwUserListVo;
 import com.fs.qw.vo.QwUserVO;
 import com.fs.qw.vo.UpdateSendTypeVo;
 import com.fs.qwApi.domain.QwExternalContactAllListResult;
@@ -309,7 +311,6 @@ public class QwUserController extends BaseController {
     /**
      * 直接授权key
      */
-    @PreAuthorize("@ss.hasPermi('qw:user:authAppKey')")
     @PostMapping("/authAppKey")
     public R authAppKey(@RequestBody QwUser param){
         return qwUserService.authAppKey(param);
@@ -318,7 +319,6 @@ public class QwUserController extends BaseController {
     /**
      * 输入授权key
      */
-    @PreAuthorize("@ss.hasPermi('qw:user:authAppKey')")
     @PostMapping("/handleInputAuthAppKey")
     public R handleInputAuthAppKey(@RequestBody QwUser param){
         return qwUserService.handleInputAuthAppKey(param);
@@ -1002,4 +1002,14 @@ public class QwUserController extends BaseController {
     {
         return qwUserService.updateQwUserFastGptRoleStatusById(id);
     }
+
+    /**
+     * 获取企微用户列表
+     */
+    @PostMapping("/selectQwUserListByCondition")
+    public R selectQwUserListByCondition(@RequestBody QwUserQueryDto query) {
+        PageHelper.startPage(query.getPageNum(), query.getPageSize());
+        List<QwUserListVo> qwUserList = qwUserService.selectQwUserListByCondition(query);
+        return R.ok().put("data", new PageInfo<>(qwUserList));
+    }
 }

+ 105 - 3
fs-admin/src/main/java/com/fs/qw/qwTask/qwTask.java

@@ -1,18 +1,29 @@
 package com.fs.qw.qwTask;
 
+import com.fs.aiSoundReplication.param.TtsRequest;
+import com.fs.aiSoundReplication.service.impl.TtsServiceImpl;
+import com.fs.common.core.redis.RedisCache;
+import com.fs.company.mapper.CompanyUserMapper;
+import com.fs.company.param.VcCompanyUser;
+import com.fs.company.service.ICompanyUserService;
 import com.fs.course.service.IFinishCourseStatisticsSyncService;
 import com.fs.course.service.IFsUserCourseService;
+import com.fs.fastgptApi.vo.AudioVO;
 import com.fs.qw.domain.QwIpadServerLog;
 import com.fs.qw.domain.QwUser;
 import com.fs.qw.mapper.QwUserMapper;
 import com.fs.qw.param.QwMandatoryRegistrParam;
 import com.fs.qw.service.*;
+import com.fs.sop.domain.QwSopTempVoice;
+import com.fs.sop.service.IQwSopTempContentService;
+import com.fs.sop.service.IQwSopTempVoiceService;
 import com.fs.sop.service.impl.QwSopLogsServiceImpl;
 import com.fs.sop.service.impl.QwSopServiceImpl;
 import com.fs.sop.service.ISopUserLogsService;
 import com.fs.statis.IFsStatisQwWatchService;
 import com.fs.statis.service.FsStatisSalerWatchService;
 import com.fs.wxwork.dto.WxWorkGetQrCodeDTO;
+import com.fs.wxwork.dto.WxwSilkVoceDTO;
 import com.fs.wxwork.service.WxWorkService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
@@ -22,9 +33,9 @@ import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.ZoneId;
 import java.time.format.DateTimeFormatter;
-import java.util.Date;
-import java.util.List;
-import java.util.Optional;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
 
 @Component("qwTask")
 public class qwTask {
@@ -49,6 +60,11 @@ public class qwTask {
     @Autowired
     private IQwUserService qwUserService;
 
+    @Autowired
+    private IQwAcquisitionAssistantService qwAcquisitionAssistantService;
+
+    @Autowired
+    private IQwContactWayService contactWayService;
 
     @Autowired
     private FsStatisSalerWatchService fsStatisSalerWatchService;
@@ -85,6 +101,18 @@ public class qwTask {
     @Autowired
     private IFinishCourseStatisticsSyncService finishCourseStatisticsSyncService;
 
+    @Autowired
+    private IQwSopTempVoiceService iQwSopTempVoiceService;
+
+    @Autowired
+    private ICompanyUserService iCompanyUserService;
+
+    @Autowired
+    private TtsServiceImpl ttsServiceImpl;
+
+    @Autowired
+    private RedisCache redisCache;
+
 
     //正在使用
     public void qwExternalContact()
@@ -380,4 +408,78 @@ public class qwTask {
     public  void  syncMultiDimensionStatistics(){
         finishCourseStatisticsSyncService.syncMultiDimensionStatistics();
     }
+
+    /**
+     * 每天重置 获客链接里的 员工账号可添加次数
+     */
+    public void resetAcquisitionLinkUserLimit() {
+        qwAcquisitionAssistantService.resetAcquisitionLinkUserLimit();
+
+    }
+
+    /**
+     * 每天重置 渠道活码里的 员工账号可添加次数
+     */
+    public void resetQwContactWayUserLimit() {
+        contactWayService.resetQwContactWayUserLimit();
+
+    }
+
+    /**
+    * 定时 将当天 生成的语音文本 转为 声音
+    */
+    public void convertSopVoiceEveryDay() {
+
+        try {
+            // 查询待生成声音的文本
+            List<QwSopTempVoice> qwSopTempVoices = iQwSopTempVoiceService.convertSopVoiceEveryDay();
+
+            // 过滤 出销售id
+            List<Long> userIds = qwSopTempVoices.stream()
+                    .map(QwSopTempVoice::getCompanyUserId)
+                    .filter(Objects::nonNull)
+                    .distinct()
+                    .collect(Collectors.toList());
+            // 查询 销售 是否绑定了且录制了音色
+            List<VcCompanyUser> vcCompanyUsers = iCompanyUserService.selectVcCompanyUserList(userIds);
+
+            // 做键值 方便匹配
+            Map<Long, VcCompanyUser> vcUserMap = vcCompanyUsers.stream()
+                    .collect(Collectors.toMap(VcCompanyUser::getCompanyUserId, v -> v, (a, b) -> a));
+            List<QwSopTempVoice> filteredVoices = qwSopTempVoices.stream()
+                    .filter(v -> v.getCompanyUserId() != null && vcUserMap.containsKey(v.getCompanyUserId()))
+                    .filter(v -> {
+                        String redisKey = "sop:voice:processing:" + v.getId();
+                        return redisCache.setIfAbsent(redisKey, "1",12, TimeUnit.HOURS);
+                    })
+                    .collect(Collectors.toList());
+
+            filteredVoices.forEach(item -> {
+                VcCompanyUser vcCompanyUser = vcUserMap.get(item.getCompanyUserId());
+                AudioVO audioVO = ttsServiceImpl.textToSpeech(new TtsRequest(null, null, vcCompanyUser.getSpeakerId(), item.getVoiceTxt()));
+                item.setVoiceUrl(audioVO.getUrl());
+                item.setDuration(audioVO.getDuration());
+                item.setUserVoiceUrl(audioVO.getWavUrl());
+                QwUser user = qwUserMapper.selectQwUserconvertSopVoice(item.getCompanyUserId());
+                ttsServiceImpl.ttsChargeByCount(vcCompanyUser,audioVO,user);
+
+            });
+            // 批量更新
+            int batchSize = 200;
+            for (int i = 0; i < filteredVoices.size(); i += batchSize) {
+                List<QwSopTempVoice> batch = filteredVoices.subList(i, Math.min(i + batchSize, filteredVoices.size()));
+                iQwSopTempVoiceService.updateBatchWithDataSource(batch);
+            }
+            // 更新完成后清除Redis标记
+            filteredVoices.forEach(item -> {
+                String redisKey = "sop:voice:processing:" + item.getId();
+                redisCache.deleteObject(redisKey);
+            });
+        }catch (Exception e){
+
+        }
+
+
+    }
+
 }

+ 208 - 23
fs-company-app/src/main/java/com/fs/app/controller/CompanyUserController.java

@@ -5,12 +5,17 @@ import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.Wrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.fs.aiSoundReplication.VoiceCloneController;
+import com.fs.aiSoundReplication.param.TtsRequest;
+import com.fs.aiSoundReplication.service.impl.TtsServiceImpl;
+import com.fs.aiSoundReplication.util.FileToMultipartConverterUtil;
 import com.fs.app.annotation.Login;
 import com.fs.app.param.*;
 import com.fs.app.service.IAppService;
 import com.fs.app.vo.CompanySubUserVO;
 import com.fs.common.annotation.RepeatSubmit;
 import com.fs.common.constant.UserConstants;
+import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.core.redis.RedisCache;
@@ -20,6 +25,7 @@ import com.fs.common.utils.bean.BeanUtils;
 import com.fs.company.domain.*;
 import com.fs.company.mapper.CompanyRoleMapper;
 import com.fs.company.mapper.CompanyUserMapper;
+import com.fs.company.param.VcCompanyUser;
 import com.fs.company.param.companyUserAddPrintParam;
 import com.fs.company.service.*;
 import com.fs.company.vo.CompanyTagUserVO;
@@ -33,7 +39,11 @@ import com.fs.fastGpt.domain.FastgptChatVoiceHomo;
 import com.fs.fastGpt.mapper.FastgptChatVoiceHomoMapper;
 import com.fs.fastgptApi.util.AudioUtils;
 import com.fs.fastgptApi.vo.AudioVO;
+import com.fs.his.utils.ConfigUtil;
+import com.fs.hisStore.enums.SysConfigEnum;
+import com.fs.qw.domain.QwUser;
 import com.fs.qw.dto.UserProjectDTO;
+import com.fs.qw.mapper.QwUserMapper;
 import com.fs.sop.domain.QwSopTempVoice;
 import com.fs.sop.service.IQwSopTempVoiceService;
 import com.fs.system.oss.CloudStorageService;
@@ -57,14 +67,20 @@ import org.apache.http.util.EntityUtils;
 import org.apache.ibatis.annotations.Param;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
 
+import javax.sound.sampled.AudioFormat;
+import javax.sound.sampled.AudioInputStream;
+import javax.sound.sampled.AudioSystem;
 import javax.validation.Valid;
-import java.io.File;
-import java.io.FileInputStream;
+import java.io.*;
 import java.math.BigDecimal;
 import java.math.RoundingMode;
+import java.net.HttpURLConnection;
+import java.net.URL;
 import java.time.LocalDate;
 import java.util.*;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
 @Slf4j
@@ -91,6 +107,13 @@ public class CompanyUserController extends AppBaseController {
     private final ICompanyTagUserService companyTagUserService;
     private final FastgptChatVoiceHomoMapper fastgptChatVoiceHomoMapper;
     public static final String SOP_TEMP_VOICE_KEY = "sop:tempVoice";
+    private final ConfigUtil configUtil;
+    private final VoiceCloneController voiceCloneController;
+
+    private static final String VOICE_CLONE_CACHE_KEY = "voice:clone:company:user:id";
+    private static final Integer CACHE_TTL = 3600;
+    private final TtsServiceImpl ttsServiceImpl;
+    private final QwUserMapper qwUserMapper;
 
     @Login
     @ApiOperation("查询用户列表")
@@ -492,35 +515,177 @@ public class CompanyUserController extends AppBaseController {
         //更新销售员工声纹
         companyUser.setVoicePrintUrl(wavUrl);
         companyUserMapper.updateCompanyUser(companyUser);
+        JSONObject vcConfig = configUtil.generateConfigByKey(SysConfigEnum.VS_CONFIG.getKey());
+        if (vcConfig != null && !vcConfig.isEmpty() &&
+//                !vcConfig.equals(new JSONObject()) &&
+                "2".equals(vcConfig.getString("type"))) {
+            /*走豆包直接不走原逻辑*/
+            AjaxResult ajaxResult = uploadVoice(userId, wavUrl);
+            if (!ajaxResult.get("code").equals(200)) {
+                return R.error((String) ajaxResult.get("msg"));
+            }
+            voiceService.insertQwSopTempVoiceModel(userId);
+            return R.ok(ajaxResult.get("msg").toString());
+        } else {
+            try {
+                CloseableHttpClient httpClient = HttpClients.createDefault();
+                HttpPost httpPost = new HttpPost(aiHostProper.getCommonApi()+"/app/common/addCompanyAudio");
+                String json = "{\"url\":\""+wavUrl+"\",\"id\":\""+userId+"\"}";
+                StringEntity entity = new StringEntity(json);
+                httpPost.setEntity(entity);
+                httpPost.setHeader("Content-type", "application/json");
+                HttpResponse response = httpClient.execute(httpPost);
+
+                if (response.getStatusLine().getStatusCode() == 200) {
+                    String responseBody = EntityUtils.toString(response.getEntity());
+                    JSONObject jsonObject = JSON.parseObject(responseBody);
+                    Integer code = (Integer)jsonObject.get("code");
+                    if (code==200){
+                        voiceService.insertQwSopTempVoiceModel(userId);
+                        return R.ok();
+                    }
+                } else {
+                    return R.error();
+                }
+
+                httpClient.close();
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+
+        return R.error();
+
+    }
+
+    /**
+     * 上传音频豆包
+     *
+     * @param voicePrintUrl
+     * @return
+     * @throws Exception
+     */
+    @PostMapping("/uploadVoice")
+    public AjaxResult uploadVoice(
+            Long userId,
+            String voicePrintUrl) throws Exception {
+        if (userId == null) userId = 123L;
+        VcCompanyUser vcCompanyUser = companyUserMapper.selectVcCompanyUserByCompanyUserId(userId);
+        if (vcCompanyUser == null) {
+            return AjaxResult.error("用户没有声纹槽位,请联系管理员");
+        }
+        if (vcCompanyUser.getTimes() != null && vcCompanyUser.getTimes() >= 5)
+            return AjaxResult.error("用户已上传声纹达到上限");
+        vcCompanyUser.setUploadUrl(voicePrintUrl);
+        File file = downloadFileFromUrl(voicePrintUrl);
+        /*获取文件时长*/
+        AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(file);
+        AudioFormat format = audioInputStream.getFormat();
+        long frames = audioInputStream.getFrameLength();
+        // 计算时长(秒)= 总帧数 / 帧率
+        Double duration = (double) frames / format.getFrameRate();
+        audioInputStream.close();
+        MultipartFile convert = FileToMultipartConverterUtil.convert(file);
+        R r = voiceCloneController.uploadVoice(vcCompanyUser.getSpeakerId(), convert, vcCompanyUser.getVersionId(), 0);
+        if (!r.get("code").equals(200))
+            return AjaxResult.error("上传声纹失败", r.get("msg"));
+        vcCompanyUser.incrementTimes();
+        vcCompanyUser.setUploadTime(duration);
+        companyUserMapper.updateVcCompanyUser(vcCompanyUser);
+        return AjaxResult.success(String.format("您已上传%d次声纹,总共有5次录入次数", vcCompanyUser.getTimes()));
+    }
+
+    /**
+     * 获取文件扩展名
+     *
+     * @param fileUrl 文件路径
+     * @return 文件扩展名
+     */
+    private static String getFileExtension(String fileUrl) {
+        return fileUrl.substring(fileUrl.lastIndexOf("."));
+    }
 
+
+    private File downloadFileFromUrl(String fileUrl) throws IOException {
+        InputStream inputStream = null;
+        FileOutputStream outputStream = null;
         try {
-            CloseableHttpClient httpClient = HttpClients.createDefault();
-            HttpPost httpPost = new HttpPost(aiHostProper.getCommonApi()+"/app/common/addCompanyAudio");
-            String json = "{\"url\":\""+wavUrl+"\",\"id\":\""+userId+"\"}";
-            StringEntity entity = new StringEntity(json);
-            httpPost.setEntity(entity);
-            httpPost.setHeader("Content-type", "application/json");
-            HttpResponse response = httpClient.execute(httpPost);
-
-            if (response.getStatusLine().getStatusCode() == 200) {
-                String responseBody = EntityUtils.toString(response.getEntity());
-                JSONObject jsonObject = JSON.parseObject(responseBody);
-                Integer code = (Integer)jsonObject.get("code");
-                if (code==200){
-                    voiceService.insertQwSopTempVoiceModel(userId);
-                    return R.ok();
+            // 创建 HTTP 连接
+            URL url = new URL(fileUrl);
+            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+            // 设置Referer请求头
+//            connection.setRequestProperty("Referer", "cos.his.cdwjyyh.com");
+            connection.setRequestMethod("GET");
+            connection.connect();
+
+            // 检查是否成功连接
+            if (connection.getResponseCode() != 200) {
+                throw new ServiceException("无法下载音频文件,HTTP 响应码:" + connection.getResponseCode());
+            }
+
+            // 获取输入流
+            inputStream = connection.getInputStream();
+
+            // 创建临时文件,并指定存放地址
+            String tempFileName = "temp_" + UUID.randomUUID() + "_" + getFileExtension(fileUrl);
+            File destinationDirectory = new File("c:\\hook\\");
+
+            // 参照 transferAudioSilk 方法,同步确保目录创建的线程安全
+            synchronized (AudioUtils.class) {
+                if (!destinationDirectory.exists()) {
+                    destinationDirectory.mkdirs();
                 }
-            } else {
-                return R.error();
             }
 
-            httpClient.close();
+            // 将文件保存到指定路径
+            File tempFile = new File(destinationDirectory, tempFileName);
+
+            // 写入文件
+            outputStream = new FileOutputStream(tempFile);
+            byte[] buffer = new byte[8192];
+            int bytesRead;
+            while ((bytesRead = inputStream.read(buffer)) != -1) {
+                outputStream.write(buffer, 0, bytesRead);
+            }
+
+            return tempFile;
         } catch (Exception e) {
             e.printStackTrace();
+        } finally {
+            try {
+                if (inputStream != null) inputStream.close();
+                if (outputStream != null) outputStream.close();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
         }
+        return null;
+    }
 
-        return R.error();
-
+    public VcCompanyUser getVcCompanyUser(Long companyUserId) {
+        String cacheKey = VOICE_CLONE_CACHE_KEY + companyUserId;
+        VcCompanyUser vcCompanyUser = redisCache.getCacheObject(cacheKey);
+        if (vcCompanyUser != null) {
+            return vcCompanyUser;
+        }
+        // 使用双重检查锁,防止缓存击穿
+        synchronized (this) {
+            // 再次检查缓存,防止在等待锁的过程中已经被其他线程加载
+            vcCompanyUser = redisCache.getCacheObject(cacheKey);
+            if (vcCompanyUser != null) {
+                return vcCompanyUser;
+            }
+            // 查询数据库
+            vcCompanyUser = companyUserMapper.selectVcCompanyUserByCompanyUserId(companyUserId);
+            if (vcCompanyUser == null) {
+                // 缓存空对象,防止缓存穿透
+                redisCache.setCacheObject(cacheKey, new VcCompanyUser(), CACHE_TTL, TimeUnit.SECONDS);
+                throw new RuntimeException("用户不存在");
+            }
+            // 设置缓存
+            redisCache.setCacheObject(cacheKey, vcCompanyUser, CACHE_TTL,TimeUnit.SECONDS);
+            return vcCompanyUser;
+        }
     }
 
     /**
@@ -544,7 +709,27 @@ public class CompanyUserController extends AppBaseController {
 
         QwSopTempVoice qwSopTempVoice = voiceService.selectQwSopTempVoiceByIdAndUserVoiceUrl(id);
         if(qwSopTempVoice != null && qwSopTempVoice.getId() != null){
-            audioVO = AudioUtils.createVoiceUrl(qwSopTempVoice.getCompanyUserId(), userVoiceUrl);
+
+            JSONObject vcConfig = configUtil.generateConfigByKey(SysConfigEnum.VS_CONFIG.getKey());
+            if (vcConfig != null && !vcConfig.isEmpty() &&
+                    "2".equals(vcConfig.getString("type"))) {
+                VcCompanyUser vcCompanyUser = getVcCompanyUser(companyUserId);
+//                VcCompanyUser vcCompanyUser = companyUserMapper.selectVcCompanyUserByCompanyUserId(companyUserId);
+//                if (vcCompanyUser == null) throw new RuntimeException("用户不存在");
+                audioVO = ttsServiceImpl.textToSpeech(new TtsRequest(null, null, vcCompanyUser.getSpeakerId(), qwSopTempVoice.getVoiceTxt().replace(" ", "")));
+                QwUser qwUser = new QwUser();
+                qwUser.setCompanyUserId(companyUserId);
+                List<QwUser> qwUsers = qwUserMapper.selectQwUserList(qwUser);
+                if (qwUsers != null && !qwUsers.isEmpty()){
+                    List<QwUser> collect = qwUsers.stream().filter(o -> o.getFastGptRoleId() != null).collect(Collectors.toList());
+                    if (!collect.isEmpty()){
+                        qwUsers = collect;
+                    }
+                }else qwUsers= Collections.singletonList(new QwUser());
+                ttsServiceImpl.ttsChargeByCount(vcCompanyUser, audioVO, qwUsers.get(0));
+            } else {
+                audioVO = AudioUtils.createVoiceUrl(qwSopTempVoice.getCompanyUserId(), userVoiceUrl);
+            }
             if(audioVO != null && audioVO.getUrl() != null){
                 qwSopTempVoice.setVoiceUrl(audioVO.getUrl());
                 qwSopTempVoice.setUserVoiceUrl(userVoiceUrl);

+ 12 - 0
fs-company/src/main/java/com/fs/company/controller/company/CompanyUserController.java

@@ -17,9 +17,11 @@ import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.company.domain.*;
+import com.fs.company.mapper.CompanyFsUserMapper;
 import com.fs.company.param.CompanyUserAreaParam;
 import com.fs.company.param.CompanyUserCodeParam;
 import com.fs.company.param.CompanyUserQwParam;
+import com.fs.company.param.VcCompanyUser;
 import com.fs.company.service.*;
 import com.fs.company.service.impl.CompanyDeptServiceImpl;
 import com.fs.company.utils.DomainUtil;
@@ -76,6 +78,10 @@ import java.util.stream.Collectors;
 @RequestMapping("/company/user")
 public class CompanyUserController extends BaseController {
 
+
+    @Autowired
+    private CompanyFsUserMapper companyFsUserMapper;
+
     @Autowired
     private ICompanyRoleService roleService;
 
@@ -197,6 +203,12 @@ public class CompanyUserController extends BaseController {
             for (CompanyUserQwListVO companyUserQwListVO : list) {
                 CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
                     CompanyUserDelayTime companyUserDelayTime = companyUserDelayTimeService.selectCompanyUserDelayTimeByCompanyUser(companyUserQwListVO.getCompanyId(), companyUserQwListVO.getUserId());
+                    VcCompanyUser vcCompanyUser = companyFsUserMapper.selectVcCompanyUser(String.valueOf(companyUserQwListVO.getUserId()));
+                    if (vcCompanyUser!=null){
+                        companyUserQwListVO.setIsBindVoiceSeat(1);
+                        companyUserQwListVO.setIsRecordVoiceSeat(!StringUtil.strIsNullOrEmpty(vcCompanyUser.getUploadUrl()) ? 1 : 2);
+                    }
+
                     if (ObjectUtil.isEmpty(companyUserDelayTime)) {
                         companyUserQwListVO.setSendDelayTime(sendDelayTime);
                     } else {

+ 29 - 0
fs-company/src/main/java/com/fs/company/controller/course/FsCourseCouponUserController.java

@@ -0,0 +1,29 @@
+package com.fs.company.controller.course;
+
+
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.his.domain.FsCourseCouponUser;
+import com.fs.his.service.IFsCourseCouponUserService;
+import com.fs.his.vo.FsCourseCouponUserRecordVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/course/courseCouponUser")
+public class FsCourseCouponUserController extends BaseController {
+
+    @Autowired
+    private IFsCourseCouponUserService courseCouponUserService;
+
+    @GetMapping("/getRecordList")
+    public TableDataInfo getRecordList(FsCourseCouponUser courseCouponUser) {
+        startPage();
+        List<FsCourseCouponUserRecordVO> vos = courseCouponUserService.selectCourseCouponUserRecordList(courseCouponUser);
+        return getDataTable(vos);
+    }
+}

+ 1 - 1
fs-company/src/main/java/com/fs/company/controller/fastGpt/FastgptEventLogTotalController.java

@@ -24,7 +24,7 @@ import java.util.List;
 
 /**
  * ai事件埋点统计Controller
- * 
+ *
  * @author fs
  * @date 2025-06-26
  */

+ 14 - 2
fs-company/src/main/java/com/fs/company/controller/live/LiveController.java

@@ -14,6 +14,8 @@ import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.http.HttpUtils;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.company.domain.CompanyUser;
+import com.fs.company.service.ICompanyConfigService;
+import com.fs.company.vo.CompanyMiniAppVO;
 import com.fs.framework.security.SecurityUtils;
 import com.fs.framework.service.TokenService;
 import com.fs.his.domain.FsPayConfig;
@@ -62,6 +64,9 @@ public class LiveController extends BaseController
     @Autowired
     private ILiveCompanyCodeService liveCompanyCodeService;
 
+    @Autowired
+    private ICompanyConfigService companyConfigService;
+
     /**
      * 查询未结束直播间
      */
@@ -418,9 +423,16 @@ public class LiveController extends BaseController
     @ApiOperation("创建App跳转通用链接")
     @GetMapping("/createAppLink")
     @PreAuthorize("@ss.hasPermi('live:live:createAppLink')")
-    public R createAppLink(@RequestParam("liveId") Long liveId,@RequestParam("corpId")String corpId) {
+    public R createAppLink(@RequestParam("liveId") Long liveId,
+                           @RequestParam("corpId")String corpId,
+                           @RequestParam("appName")String appName) {
         CompanyUser user = SecurityUtils.getLoginUser().getUser();
-        return liveService.createAppLink(user,liveId,corpId);
+        return liveService.createAppLink(user,liveId,corpId,appName);
+    }
+
+    @GetMapping("/getAppAllList")
+    public R getAppAllList(){
+        return companyConfigService.getCompanyMiniAppAllList();
     }
 
 }

+ 47 - 49
fs-company/src/main/java/com/fs/company/controller/qw/QwAcquisitionAssistantController.java

@@ -1,27 +1,28 @@
 package com.fs.company.controller.qw;
 
-import com.fs.common.core.controller.BaseController;
-import com.fs.common.core.domain.AjaxResult;
-import com.fs.common.core.page.TableDataInfo;
+import java.util.Collections;
+import java.util.List;
+
 import com.fs.common.exception.CustomException;
 import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.StringUtils;
 import com.fs.framework.security.LoginUser;
 import com.fs.framework.service.TokenService;
-import com.fs.qw.domain.QwAcquisitionAssistant;
+import com.fs.his.dto.SendResultDetailDTO;
+import com.fs.qw.bo.SendMsgLogBo;
 import com.fs.qw.domain.QwCompany;
 import com.fs.qw.domain.QwUser;
-import com.fs.qw.dto.acquisition.AcquisitionListResponse;
-import com.fs.qw.service.IQwAcquisitionAssistantService;
 import com.fs.qw.service.IQwCompanyService;
 import com.fs.qw.service.IQwUserService;
 import com.fs.qw.vo.AcquisitionAssistantDetailVO;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
-
-import java.util.Collections;
-import java.util.List;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.qw.domain.QwAcquisitionAssistant;
+import com.fs.qw.service.IQwAcquisitionAssistantService;
 
 /**
  * 企微-获客链接管理Controller
@@ -56,48 +57,30 @@ public class QwAcquisitionAssistantController extends BaseController {
     }
 
     /**
-     * 从企微同步获客链接列表(全量)
-     * 手动点击同步按钮时调用
-     */
-    @PostMapping("/syncList")
-    public AjaxResult syncList(@RequestParam String corpId) {
-        try {
-
-            QwCompany qwCompany = getQwCompany(corpId);
-
-            // 调用同步服务
-            String result = qwAcquisitionAssistantService.syncListFromQw(qwCompany.getCorpId(), qwCompany.getOpenSecret());
-
-            return AjaxResult.success(result);
-        } catch (CustomException e) {
-            return AjaxResult.error(e.getMessage());
-        } catch (Exception e) {
-            return AjaxResult.error("系统异常:" + e.getMessage());
-        }
-    }
-
-    /**
-     * 分页获取企微列表(直接调用企微接口)
-     * 用于查看企微原始数据
+     * 发送获客链接短信
      */
-    @GetMapping("/qwList")
-    public AjaxResult getQwList(@RequestParam(required = false) Integer limit,
-                                @RequestParam(required = false) String cursor,
-                                @RequestParam String corpId) {
+    @GetMapping("/sendAcquisitionMessage/{id}/{phone}")
+    public AjaxResult sendAcquisitionMessage(@PathVariable Long id,@PathVariable String phone) {
         try {
-            QwCompany qwCompany = getQwCompany(corpId);
-            // 调用企微列表接口
-            AcquisitionListResponse response = qwAcquisitionAssistantService.getQwList(
-                    qwCompany.getCorpId(), qwCompany.getOpenSecret(), limit, cursor);
-
-            return AjaxResult.success(response);
-        } catch (CustomException e) {
-            return AjaxResult.error(e.getMessage());
+            LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+            if (loginUser==null||loginUser.getCompany()==null){
+                throw new CustomException("请登录");
+            }
+            SendMsgLogBo sendMsgLogBo=new SendMsgLogBo();
+            sendMsgLogBo.setCompanyId(loginUser.getCompany().getCompanyId());
+            sendMsgLogBo.setCompanyUserId(loginUser.getCompany().getUserId());
+            validatePhone(phone);
+            SendResultDetailDTO sendResultDetailDTO = qwAcquisitionAssistantService.sendMessageAcquisition(phone, id,sendMsgLogBo);
+            if (sendResultDetailDTO.isSuccess()){
+                return AjaxResult.success("发送成功");
+            }else {
+                return AjaxResult.error(sendResultDetailDTO.getFailReason());
+            }
         } catch (Exception e) {
-            return AjaxResult.error("系统异常:" + e.getMessage());
+            log.error("发送失败:" + e.getMessage());
+            return AjaxResult.error("网络异常,请稍后再试");
         }
     }
-
     /**
      * 根据linkId直接获取详情
      *
@@ -124,8 +107,9 @@ public class QwAcquisitionAssistantController extends BaseController {
         try {
             LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
             qwAcquisitionAssistant.setCreateBy(String.valueOf(loginUser.getUser().getUserId()));
+            Long companyId = loginUser.getUser().getCompanyId();
             QwCompany qwCompany = getQwCompany(qwAcquisitionAssistant.getCorpId());
-            QwAcquisitionAssistant result = qwAcquisitionAssistantService.createWithQw(qwCompany.getCorpId(), qwCompany.getOpenSecret(), qwAcquisitionAssistant);
+            QwAcquisitionAssistant result = qwAcquisitionAssistantService.createWithQw(qwCompany.getCorpId(), qwCompany.getOpenSecret(), qwAcquisitionAssistant,companyId);
             return AjaxResult.success("创建成功", result);
         } catch (CustomException e) {
             return AjaxResult.error(e.getMessage());
@@ -200,7 +184,7 @@ public class QwAcquisitionAssistantController extends BaseController {
             qwAcquisitionAssistant.setUpdateBy(String.valueOf(loginUser.getUser().getUserId()));
             QwCompany qwCompany = getQwCompany(qwAcquisitionAssistant.getCorpId());
             QwAcquisitionAssistant result = qwAcquisitionAssistantService.updateWithQw(
-                    qwCompany.getCorpId(), qwCompany.getOpenSecret(), qwAcquisitionAssistant);
+                    qwCompany.getCorpId(), qwCompany.getOpenSecret(), qwAcquisitionAssistant,loginUser.getUser().getCompanyId());
 
             return AjaxResult.success("修改成功", result);
         } catch (CustomException e) {
@@ -247,4 +231,18 @@ public class QwAcquisitionAssistantController extends BaseController {
         }
         return qwCompany;
     }
-}
+
+    /**
+     * 校验电话
+     */
+    private void validatePhone(String phone) {
+        if (StringUtils.isEmpty(phone)) {
+            throw new CustomException("发送短信的电话号码不能为空", 400);
+        }
+
+        // 支持手机号或固话
+        if (!phone.matches("^1[3-9]\\d{9}$") && !phone.matches("^\\d{3,4}-?\\d{7,8}$")) {
+            throw new CustomException("电话号码格式不正确,请输入正确的手机号或固定电话", 400);
+        }
+    }
+}

+ 449 - 0
fs-company/src/main/java/com/fs/company/controller/qw/QwAcquisitionLinkInfoController.java

@@ -0,0 +1,449 @@
+package com.fs.company.controller.qw;
+
+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.domain.R;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.exception.CustomException;
+import com.fs.common.utils.ServletUtils;
+import com.fs.common.utils.StringUtils;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.company.mapper.CompanyUserRoleMapper;
+import com.fs.framework.security.LoginUser;
+import com.fs.framework.service.TokenService;
+import com.fs.his.dto.SendResultDetailDTO;
+import com.fs.qw.bo.SendMsgLogBo;
+import com.fs.qw.domain.QwAcquisitionLinkInfo;
+import com.fs.qw.dto.BatchAddAcquisitionLinkDTO;
+import com.fs.qw.dto.IpadBlindAddDto;
+import com.fs.qw.service.IQwAcquisitionLinkInfoService;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.poi.hssf.usermodel.HSSFCell;
+import org.apache.poi.hssf.usermodel.HSSFRow;
+import org.apache.poi.hssf.usermodel.HSSFSheet;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.servlet.http.HttpServletResponse;
+import javax.validation.Valid;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+/**
+ * 获客链接-号码链接生成记录Controller
+ *
+ * @author fs
+ * @date 2026-03-27
+ */
+@Slf4j
+@RestController
+@RequestMapping("/qw/linkInfo")
+public class QwAcquisitionLinkInfoController extends BaseController
+{
+    @Autowired
+    private TokenService tokenService;
+
+    @Autowired
+    private CompanyUserRoleMapper roleMapper;
+
+    @Autowired
+    private IQwAcquisitionLinkInfoService qwAcquisitionLinkInfoService;
+
+    // 定义手机号正则表达式
+    private static final Pattern PHONE_PATTERN = Pattern.compile("^1[3-9]\\d{9}$");
+    /**
+     * 查询获客链接-号码链接生成记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('qw:linkInfo:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(QwAcquisitionLinkInfo qwAcquisitionLinkInfo)
+    {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        qwAcquisitionLinkInfo.setCreateBy(loginUser.getUser().getUserId());
+        //管理员查看所有数据
+        Long isAdmin = roleMapper.companyUserIsAdmin(loginUser.getUser().getUserId());
+        if (isAdmin != null) {
+            qwAcquisitionLinkInfo.setCreateBy(null);
+        }
+        startPage();
+        List<QwAcquisitionLinkInfo> list = qwAcquisitionLinkInfoService.selectQwAcquisitionLinkInfoList(qwAcquisitionLinkInfo);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出获客链接-号码链接生成记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('qw:linkInfo:export')")
+    @Log(title = "获客链接-号码链接生成记录", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, QwAcquisitionLinkInfo qwAcquisitionLinkInfo) throws IOException {
+        List<QwAcquisitionLinkInfo> list = qwAcquisitionLinkInfoService.selectQwAcquisitionLinkInfoList(qwAcquisitionLinkInfo);
+        ExcelUtil<QwAcquisitionLinkInfo> util = new ExcelUtil<QwAcquisitionLinkInfo>(QwAcquisitionLinkInfo.class);
+        util.exportExcel(response, list, "获客链接-号码链接生成记录数据");
+    }
+
+    /**
+     * 获取获客链接-号码链接生成记录详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('qw:linkInfo:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(qwAcquisitionLinkInfoService.selectQwAcquisitionLinkInfoById(id));
+    }
+
+    /**
+     * 新增获客链接-号码链接生成记录
+     */
+    @PreAuthorize("@ss.hasPermi('qw:linkInfo:add')")
+    @Log(title = "获客链接-号码链接生成记录", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody QwAcquisitionLinkInfo qwAcquisitionLinkInfo)
+    {
+        return toAjax(qwAcquisitionLinkInfoService.insertQwAcquisitionLinkInfo(qwAcquisitionLinkInfo));
+    }
+
+    /**
+     * 修改获客链接-号码链接生成记录
+     */
+    @PreAuthorize("@ss.hasPermi('qw:linkInfo:edit')")
+    @Log(title = "获客链接-号码链接生成记录", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody QwAcquisitionLinkInfo qwAcquisitionLinkInfo)
+    {
+        return toAjax(qwAcquisitionLinkInfoService.updateQwAcquisitionLinkInfo(qwAcquisitionLinkInfo));
+    }
+
+    /**
+     * 删除获客链接-号码链接生成记录
+     */
+    @PreAuthorize("@ss.hasPermi('qw:linkInfo:remove')")
+    @Log(title = "获客链接-号码链接生成记录", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(qwAcquisitionLinkInfoService.deleteQwAcquisitionLinkInfoByIds(ids));
+    }
+
+    /**
+     * 发送获客链接短信
+     */
+    @GetMapping("/sendMessageLink/{id}/{phone}")
+    public AjaxResult sendMessageLink(@PathVariable Long id,@PathVariable String phone) {
+        try {
+            LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+            if (loginUser==null||loginUser.getCompany()==null){
+                throw new CustomException("请登录");
+            }
+            SendMsgLogBo sendMsgLogBo=new SendMsgLogBo();
+            sendMsgLogBo.setCompanyId(loginUser.getCompany().getCompanyId());
+            sendMsgLogBo.setCompanyUserId(loginUser.getCompany().getUserId());
+            validatePhone(phone);
+            SendResultDetailDTO sendResultDetailDTO = qwAcquisitionLinkInfoService.sendMessageLink(phone, id,sendMsgLogBo);
+            if (sendResultDetailDTO.isSuccess()){
+                return AjaxResult.success("发送成功");
+            }else {
+                return AjaxResult.error(sendResultDetailDTO.getFailReason());
+            }
+        } catch (Exception e) {
+            log.error("发送失败:" + e.getMessage());
+            return AjaxResult.error("网络异常,请稍后再试");
+        }
+    }
+
+    /**
+     * 提取链接 - 根据手机号生成短链接文本
+     *
+     * @param params 包含 id, phone, url 的参数
+     * @return 生成的文本内容
+     */
+    @PostMapping("/extractLink")
+    public AjaxResult extractLink(@RequestBody Map<String, Object> params) {
+        try {
+            // 参数校验
+            Long qwAcquisitionAssistantId = null;
+            String phone = null;
+            String url = null;
+
+            if (params.get("id") != null) {
+                qwAcquisitionAssistantId = Long.valueOf(params.get("id").toString());
+            }
+            if (params.get("phone") != null) {
+                phone = params.get("phone").toString();
+            }
+            if (params.get("url") != null) {
+                url = params.get("url").toString();
+            }
+
+            if (qwAcquisitionAssistantId == null) {
+                return AjaxResult.error("获客链接ID不能为空");
+            }
+            if (StringUtils.isEmpty(phone)) {
+                return AjaxResult.error("手机号码不能为空");
+            }
+            if (StringUtils.isEmpty(url)) {
+                return AjaxResult.error("获客链接URL不能为空");
+            }
+
+            // 验证手机号格式
+            if (!phone.matches("^1[3-9]\\d{9}$")) {
+                return AjaxResult.error("请输入正确的11位手机号码");
+            }
+
+            // 获取当前登录用户信息
+            LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+            if (loginUser == null || loginUser.getCompany() == null) {
+                throw new CustomException("请登录");
+            }
+            // 调用服务层方法生成短链接文本
+            String resultText = qwAcquisitionLinkInfoService.extractLink(qwAcquisitionAssistantId, phone, url,loginUser.getCompany().getCompanyId());
+
+            return AjaxResult.success(resultText);
+
+        } catch (CustomException e) {
+            log.error("提取链接失败: {}", e.getMessage());
+            return AjaxResult.error(e.getMessage());
+        } catch (Exception e) {
+            log.error("提取链接异常", e);
+            return AjaxResult.error("服务器内部错误: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 提取链接 -正常的获客
+     *
+     * @param params 包含 id, url 的参数
+     * @return 生成的文本内容
+     */
+    @PostMapping("/extractLinkNol")
+    public R extractLinkNol(@RequestBody Map<String, Object> params) {
+        try {
+            // 参数校验
+            Long qwAcquisitionAssistantId = null;
+            String phone = null;
+            String url = null;
+
+            if (params.get("id") != null) {
+                qwAcquisitionAssistantId = Long.valueOf(params.get("id").toString());
+            }
+
+            if (params.get("url") != null) {
+                url = params.get("url").toString();
+            }
+
+            if (qwAcquisitionAssistantId == null) {
+                return R.error("获客链接ID不能为空");
+            }
+            if (StringUtils.isEmpty(url)) {
+                return R.error("获客链接URL不能为空");
+            }
+
+            // 获取当前登录用户信息
+            LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+            if (loginUser == null || loginUser.getCompany() == null) {
+                throw new CustomException("请登录");
+            }
+
+            // 调用服务层方法生成短链接文本
+            return qwAcquisitionLinkInfoService.extractLinkNol(qwAcquisitionAssistantId, url,loginUser.getCompany().getCompanyId());
+
+        } catch (CustomException e) {
+            log.error("提取链接失败: {}", e.getMessage());
+            return R.error(e.getMessage());
+        } catch (Exception e) {
+            log.error("提取链接异常", e);
+            return R.error("服务器内部错误: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 批量生成多手机号短链
+     * */
+    @PostMapping("/batchCreateMessageLink")
+    public AjaxResult batchCreateMessageLink(@RequestParam("file") MultipartFile file,
+                                             @RequestParam("qwAcquisitionAssistantId") Long qwAcquisitionAssistantId,
+                                             @RequestParam("qwAcquisitionAssistantUrl") String qwAcquisitionAssistantUrl) {
+
+        // 获取当前登录用户信息
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        if (loginUser == null || loginUser.getCompany() == null) {
+            throw new CustomException("请登录");
+        }
+        // 1. 参数校验
+        if (file == null || file.isEmpty()) {
+            return AjaxResult.error("上传的文件不能为空");
+        }
+        if (qwAcquisitionAssistantId == null) {
+            return AjaxResult.error("获客链接ID不能为空");
+        }
+        if (qwAcquisitionAssistantUrl == null || qwAcquisitionAssistantUrl.trim().isEmpty()) {
+            return AjaxResult.error("获客链接URL不能为空");
+        }
+
+        // 2. 检查文件类型
+        String originalFilename = file.getOriginalFilename();
+        if (originalFilename == null || !originalFilename.toLowerCase().endsWith(".xls")) {
+            return AjaxResult.error("仅支持上传 .xls 格式的文件");
+        }
+
+        try {
+            // 3. 读取Excel文件并解析出电话号码列表
+            List<String> phoneList = readPhonesFromXls(file.getInputStream());
+            if (CollectionUtils.isEmpty(phoneList)) {
+                return AjaxResult.error("上传的Excel文件中未找到有效的电话号码数据");
+            }
+            if (phoneList.size()>500) {
+                return AjaxResult.error("单次上传的号码不能超过500个");
+            }
+            // 4. 构建DTO对象
+            BatchAddAcquisitionLinkDTO batchAddAcquisitionLinkDTO = new BatchAddAcquisitionLinkDTO();
+            batchAddAcquisitionLinkDTO.setQwAcquisitionAssistantId(qwAcquisitionAssistantId);
+            batchAddAcquisitionLinkDTO.setQwAcquisitionAssistantUrl(qwAcquisitionAssistantUrl);
+            batchAddAcquisitionLinkDTO.setPhoneList(phoneList);
+            batchAddAcquisitionLinkDTO.setCreateBy(loginUser.getCompany().getUserId());
+            batchAddAcquisitionLinkDTO.setCompanyId(loginUser.getCompany().getCompanyId());
+            // 5. 调用服务层方法处理
+            int count = qwAcquisitionLinkInfoService.batchCreateMessageLink(batchAddAcquisitionLinkDTO);
+
+            return AjaxResult.success("成功处理 " + count + " 条记录");
+
+        } catch (Exception e) {
+            log.error("上传Excel并生成短链失败", e);
+            return AjaxResult.error("服务器内部错误: " + e.getMessage());
+        }
+    }
+
+    /**
+     * iPad获客链接加好友
+     *
+     * @param dto 请求参数
+     * @return 操作结果
+     */
+    @PostMapping("/ipadBlindAdd")
+    public AjaxResult ipadBlindAdd(@Valid @RequestBody IpadBlindAddDto dto) {
+        try {
+            LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+            if (loginUser == null || loginUser.getCompany() == null) {
+                throw new CustomException("请登录");
+            }
+            SendMsgLogBo sendMsgLogBo = new SendMsgLogBo();
+            sendMsgLogBo.setCompanyId(loginUser.getCompany().getCompanyId());
+            sendMsgLogBo.setCompanyUserId(loginUser.getCompany().getUserId());
+            validatePhone(dto.getPhone());
+            // 调用业务逻辑
+            qwAcquisitionLinkInfoService.ipadBlindAdd(dto, sendMsgLogBo);
+            return AjaxResult.success("添加成功");
+
+        } catch (Exception e) {
+            log.error("iPad盲加失败", e);
+            return AjaxResult.error(e.getMessage());
+        }
+    }
+
+    /**
+     * 校验电话
+     */
+    private void validatePhone(String phone) {
+        if (StringUtils.isEmpty(phone)) {
+            throw new CustomException("发送短信的电话号码不能为空", 400);
+        }
+
+        // 支持手机号或固话
+        if (!phone.matches("^1[3-9]\\d{9}$") && !phone.matches("^\\d{3,4}-?\\d{7,8}$")) {
+            throw new CustomException("电话号码格式不正确,请输入正确的手机号或固定电话", 400);
+        }
+    }
+    /**
+     * 从.xls文件流中读取电话号码列表
+     * 假设第一行为表头,第一列开始为电话号码
+     * @param inputStream 文件输入流
+     * @return 电话号码列表
+     * @throws IOException
+     */
+    public static List<String> readPhonesFromXls(InputStream inputStream) throws IOException {
+        List<String> phoneList = new ArrayList<>();
+        HSSFWorkbook workbook = null;
+
+        try {
+            workbook = new HSSFWorkbook(inputStream);
+            HSSFSheet sheet = workbook.getSheetAt(0); // 获取第一个Sheet
+
+            int lastRowNum = sheet.getLastRowNum();
+
+            // 从第二行开始遍历 (i=1),第一行是表头
+            for (int i = 1; i <= lastRowNum; i++) {
+                HSSFRow row = sheet.getRow(i);
+                if (row != null) {
+                    // 修改:获取第一列 (index=0) 的单元格
+                    HSSFCell cell = row.getCell(0);
+                    if (cell != null) {
+                        // 获取单元格内容并转为字符串
+                        String phoneValue = getCellValueAsString(cell);
+                        if (phoneValue != null && !phoneValue.trim().isEmpty()) {
+                            // 进行格式校验
+                            if (PHONE_PATTERN.matcher(phoneValue.trim()).matches()) {
+                                phoneList.add(phoneValue.trim());
+                            } else {
+                                log.warn("发现格式不正确的电话号码,已跳过: {}", phoneValue.trim());
+                            }
+                        }
+                    }
+                }
+            }
+        } finally {
+            if (workbook != null) {
+                try {
+                    workbook.close();
+                } catch (IOException e) {
+                    log.error("关闭Excel工作簿时发生错误", e);
+                }
+            }
+        }
+
+        return phoneList;
+    }
+
+    /**
+     * 将HSSFCell的值转为String
+     * @param cell 单元格
+     * @return 单元格的字符串值
+     */
+    private static String getCellValueAsString(HSSFCell cell) {
+        if (cell == null) {
+            return null;
+        }
+        switch (cell.getCellType()) {
+            case STRING:
+                return cell.getStringCellValue();
+            case NUMERIC:
+                // 如果是数字,通常电话号码是字符串,这里转为长整型再转为字符串,避免科学计数法
+                if (org.apache.poi.ss.usermodel.DateUtil.isCellDateFormatted(cell)) {
+                    return String.valueOf(cell.getDateCellValue());
+                } else {
+                    Double numericValue = cell.getNumericCellValue();
+                    // 尝试转为Long,适用于大部分电话号码
+                    return String.valueOf(numericValue.longValue());
+                }
+            case BOOLEAN:
+                return String.valueOf(cell.getBooleanCellValue());
+            case FORMULA:
+                return cell.getCellFormula();
+            case BLANK:
+                return "";
+            case ERROR:
+                return "ERROR";
+            default:
+                return "";
+        }
+    }
+}

+ 0 - 2
fs-company/src/main/java/com/fs/company/controller/qw/QwUserController.java

@@ -296,7 +296,6 @@ public class QwUserController extends BaseController
     /**
     * 直接授权key
     */
-    @PreAuthorize("@ss.hasPermi('qw:user:authAppKey')")
     @PostMapping("/authAppKey")
     public R authAppKey(@RequestBody QwUser param){
         return qwUserService.authAppKey(param);
@@ -305,7 +304,6 @@ public class QwUserController extends BaseController
     /**
      * 输入授权key
      */
-    @PreAuthorize("@ss.hasPermi('qw:user:authAppKey')")
     @PostMapping("/handleInputAuthAppKey")
     public R handleInputAuthAppKey(@RequestBody QwUser param){
         return qwUserService.handleInputAuthAppKey(param);

+ 12 - 3
fs-company/src/main/java/com/fs/company/controller/store/FsStoreOrderController.java

@@ -19,6 +19,7 @@ import com.fs.erp.dto.ErpOrderQueryResponse;
 import com.fs.erp.service.IErpOrderService;
 import com.fs.framework.security.LoginUser;
 import com.fs.framework.security.SecurityUtils;
+import com.fs.company.domain.CompanyRole;
 import com.fs.framework.service.TokenService;
 import com.fs.his.config.FsSysConfig;
 import com.fs.his.domain.*;
@@ -93,7 +94,7 @@ public class FsStoreOrderController extends BaseController
     /**
      * 查询直播/点播订单列表(fs_store_order_scrm 中 order_type=2 直播订单,order_type=3 点播订单)
      * 如果前端传了 orderType,则按指定类型查询;如果没传(null),则查询所有直播和点播订单(orderType IN (2,3))
-     * 分公司负责人(userType=00)可查公司下所有订单,否则仅能查自己的订单
+     * 分公司负责人(userType=00)或 finance_order_goods 角色可查公司下所有订单,否则仅能查自己的订单
      */
     @PostMapping("/healthLiveList")
     public FsStoreOrderListAndStatisticsVo healthLiveList(@RequestBody com.fs.hisStore.param.FsStoreOrderParam param) {
@@ -103,7 +104,7 @@ public class FsStoreOrderController extends BaseController
         if (param.getOrderType() == null) {
             param.setOrderType(-1); // 特殊值,表示查询所有直播和点播订单
         }
-        if (!"00".equals(loginUser.getUser().getUserType())) {
+        if (!"00".equals(loginUser.getUser().getUserType()) && !hasRoleKey(loginUser, "finance_order_goods")) {
             param.setCompanyUserId(loginUser.getUser().getUserId());
         } else {
             param.setCompanyUserId(null);
@@ -167,7 +168,7 @@ public class FsStoreOrderController extends BaseController
         if (param.getOrderType() == null) {
             param.setOrderType(-1); // 特殊值,表示查询所有直播和点播订单
         }
-        if (!"00".equals(loginUser.getUser().getUserType())) {
+        if (!"00".equals(loginUser.getUser().getUserType()) && !hasRoleKey(loginUser, "finance_order_goods")) {
             param.setCompanyUserId(loginUser.getUser().getUserId());
         } else {
             param.setCompanyUserId(null);
@@ -1006,4 +1007,12 @@ public class FsStoreOrderController extends BaseController
         return null;
     }
 
+    private boolean hasRoleKey(LoginUser loginUser, String roleKey) {
+        List<CompanyRole> roles = loginUser.getUser().getRoles();
+        if (roles == null || roles.isEmpty()) {
+            return false;
+        }
+        return roles.stream().anyMatch(role -> roleKey.equals(role.getRoleKey()));
+    }
+
 }

+ 69 - 22
fs-company/src/main/java/com/fs/hisStore/controller/FsIntegralOrderController.java

@@ -1,28 +1,26 @@
 package com.fs.hisStore.controller;
 
-import cn.hutool.core.lang.TypeReference;
-import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.StrUtil;
-import cn.hutool.json.JSONUtil;
-import com.alibaba.fastjson.JSONObject;
 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.domain.R;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
+import com.fs.common.exception.ServiceException;
+import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.poi.ExcelUtil;
-import com.fs.his.domain.FsIntegralGoods;
+import com.fs.company.domain.CompanyRole;
+import com.fs.framework.security.LoginUser;
+import com.fs.framework.service.TokenService;
 import com.fs.his.domain.FsIntegralOrder;
 import com.fs.his.dto.ExpressInfoDTO;
 import com.fs.his.enums.ShipperCodeEnum;
-import com.fs.his.mapper.FsIntegralGoodsMapper;
 import com.fs.his.param.FsIntegralOrderCreateParam;
 import com.fs.his.param.FsIntegralOrderParam;
 import com.fs.his.service.IFsExpressService;
 import com.fs.his.service.IFsIntegralOrderService;
-import com.fs.his.utils.PhoneUtil;
 import com.fs.his.vo.FsIntegralOrderListVO;
 import com.fs.his.vo.FsIntegralOrderPVO;
 import com.fs.his.vo.FsStoreProductDeliverExcelVO;
@@ -31,7 +29,7 @@ import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
 
-import java.util.*;
+import java.util.List;
 
 import static com.fs.his.utils.PhoneUtil.decryptAutoPhoneMk;
 import static com.fs.his.utils.PhoneUtil.decryptPhone;
@@ -50,16 +48,18 @@ public class FsIntegralOrderController extends BaseController
     private IFsIntegralOrderService fsIntegralOrderService;
     @Autowired
     private IFsExpressService expressService;
-
     @Autowired
-    private FsIntegralGoodsMapper fsIntegralGoodsMapper;
+    private TokenService tokenService;
+
     /**
      * 查询积分商品订单列表
+     * 销售公司只能查本公司数据;非管理员且非 finance_order_goods 角色只能查本人订单
      */
     @PreAuthorize("@ss.hasPermi('his:integralOrder:list')")
     @GetMapping("/list")
     public TableDataInfo list(FsIntegralOrderParam fsIntegralOrder)
     {
+        applyCompanyDataScope(fsIntegralOrder);
         startPage();
         List<FsIntegralOrderListVO> list = fsIntegralOrderService.selectFsIntegralOrderListVO(fsIntegralOrder);
         for (FsIntegralOrderListVO vo : list) {
@@ -75,15 +75,22 @@ public class FsIntegralOrderController extends BaseController
     @Log(title = "积分商品订单", businessType = BusinessType.EXPORT)
     @GetMapping("/export")
     public AjaxResult export(FsIntegralOrder fsIntegralOrder) {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        fsIntegralOrder.setCompanyId(loginUser.getCompany().getCompanyId());
+        if (!"00".equals(loginUser.getUser().getUserType()) && !hasRoleKey(loginUser, "finance_order_goods")) {
+            fsIntegralOrder.setCompanyUserId(loginUser.getUser().getUserId());
+        }
         return fsIntegralOrderService.export(fsIntegralOrder);
     }
+
     /**
      * 发货
      */
-//    @PreAuthorize("@ss.hasPermi('his:integralOrder:sendGoods')")
+    @PreAuthorize("@ss.hasPermi('his:integralOrder:sendGoods')")
     @PutMapping("/sendGoods")
     public AjaxResult sendGoods(@RequestBody FsIntegralOrder fsIntegralOrder)
     {
+        checkIntegralOrderAccess(fsIntegralOrderService.selectFsIntegralOrderByOrderId(fsIntegralOrder.getOrderId()));
         return toAjax(fsIntegralOrderService.sendGoods(fsIntegralOrder));
     }
 
@@ -93,6 +100,7 @@ public class FsIntegralOrderController extends BaseController
         ExcelUtil<FsStoreProductDeliverExcelVO> util = new ExcelUtil<>(FsStoreProductDeliverExcelVO.class);
         return util.importTemplateExcel("导入运单号");
     }
+
     @Log(title = "导入运单号", businessType = BusinessType.IMPORT)
     @PostMapping("/importData")
     public AjaxResult importData(MultipartFile file, boolean updateSupport) throws Exception
@@ -102,11 +110,13 @@ public class FsIntegralOrderController extends BaseController
         String message = fsIntegralOrderService.importProductDeliver(list);
         return AjaxResult.success(message);
     }
-//    @PreAuthorize("@ss.hasPermi('his:integralOrder:express')")
+
+    @PreAuthorize("@ss.hasPermi('his:integralOrder:express')")
     @GetMapping(value = "/getExpress/{id}")
     public R getExpress(@PathVariable("id") Long id)
     {
         FsIntegralOrder fsIntegralOrder = fsIntegralOrderService.selectFsIntegralOrderByOrderId(id);
+        checkIntegralOrderAccess(fsIntegralOrder);
         ExpressInfoDTO expressInfoDTO=null;
         if(StringUtils.isNotEmpty(fsIntegralOrder.getDeliverySn())){
             String lastFourNumber = "";
@@ -121,17 +131,19 @@ public class FsIntegralOrderController extends BaseController
         }
         return R.ok().put("data",expressInfoDTO);
     }
+
     /**
      * 获取积分商品订单详细信息
      */
-//    @PreAuthorize("@ss.hasPermi('his:integralOrder:query')")
+    @PreAuthorize("@ss.hasPermi('his:integralOrder:query')")
     @GetMapping(value = "/{orderId}")
     public AjaxResult getInfo(@PathVariable("orderId") Long orderId)
     {
-        FsIntegralOrderPVO order = fsIntegralOrderService.selectFsIntegralOrderPVO(orderId);
-
-        order.setUserPhone(decryptAutoPhoneMk(order.getUserPhone()));
-        return AjaxResult.success(order);
+        FsIntegralOrder order = fsIntegralOrderService.selectFsIntegralOrderByOrderId(orderId);
+        checkIntegralOrderAccess(order);
+        FsIntegralOrderPVO pvo = fsIntegralOrderService.selectFsIntegralOrderPVO(orderId);
+        pvo.setUserPhone(decryptAutoPhoneMk(pvo.getUserPhone()));
+        return AjaxResult.success(pvo);
     }
 
     @GetMapping(value = "/queryPhone/{orderId}")
@@ -139,8 +151,10 @@ public class FsIntegralOrderController extends BaseController
     @PreAuthorize("@ss.hasPermi('his:integralOrder:queryPhone')")
     public R getPhone(@PathVariable("orderId") Long orderId)
     {
-        FsIntegralOrderPVO order = fsIntegralOrderService.selectFsIntegralOrderPVO(orderId);
-        String userPhone = order.getUserPhone();
+        FsIntegralOrder order = fsIntegralOrderService.selectFsIntegralOrderByOrderId(orderId);
+        checkIntegralOrderAccess(order);
+        FsIntegralOrderPVO pvo = fsIntegralOrderService.selectFsIntegralOrderPVO(orderId);
+        String userPhone = pvo.getUserPhone();
         if (userPhone.length()>11){
             userPhone = decryptPhone(userPhone);
         }
@@ -150,7 +164,6 @@ public class FsIntegralOrderController extends BaseController
     /**
      * 新增积分商品订单
      */
-//    @PreAuthorize("@ss.hasPermi('his:integralOrder:add')")
     @Log(title = "积分商品订单", businessType = BusinessType.INSERT)
     @PostMapping
     public R add(@RequestBody FsIntegralOrderCreateParam param)
@@ -161,22 +174,56 @@ public class FsIntegralOrderController extends BaseController
     /**
      * 修改积分商品订单
      */
-//    @PreAuthorize("@ss.hasPermi('his:integralOrder:edit')")
+    @PreAuthorize("@ss.hasPermi('his:integralOrder:edit')")
     @Log(title = "积分商品订单", businessType = BusinessType.UPDATE)
     @PutMapping
     public AjaxResult edit(@RequestBody FsIntegralOrder fsIntegralOrder)
     {
+        checkIntegralOrderAccess(fsIntegralOrderService.selectFsIntegralOrderByOrderId(fsIntegralOrder.getOrderId()));
         return toAjax(fsIntegralOrderService.updateFsIntegralOrder(fsIntegralOrder));
     }
 
     /**
      * 删除积分商品订单
      */
-//    @PreAuthorize("@ss.hasPermi('his:integralOrder:remove')")
     @Log(title = "积分商品订单", businessType = BusinessType.DELETE)
 	@DeleteMapping("/{orderIds}")
     public AjaxResult remove(@PathVariable Long[] orderIds)
     {
+        for (Long orderId : orderIds) {
+            checkIntegralOrderAccess(fsIntegralOrderService.selectFsIntegralOrderByOrderId(orderId));
+        }
         return toAjax(fsIntegralOrderService.deleteFsIntegralOrderByOrderIds(orderIds));
     }
+
+    private void applyCompanyDataScope(FsIntegralOrderParam param) {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        param.setCompanyId(loginUser.getCompany().getCompanyId());
+        if (!"00".equals(loginUser.getUser().getUserType()) && !hasRoleKey(loginUser, "finance_order_goods")) {
+            param.setCompanyUserId(loginUser.getUser().getUserId());
+        }
+    }
+
+    private void checkIntegralOrderAccess(FsIntegralOrder order) {
+        if (order == null) {
+            throw new ServiceException("订单不存在");
+        }
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        if (!loginUser.getCompany().getCompanyId().equals(order.getCompanyId())) {
+            throw new ServiceException("非法操作");
+        }
+        if (!"00".equals(loginUser.getUser().getUserType()) && !hasRoleKey(loginUser, "finance_order_goods")) {
+            if (order.getCompanyUserId() == null || !loginUser.getUser().getUserId().equals(order.getCompanyUserId())) {
+                throw new ServiceException("非法操作");
+            }
+        }
+    }
+
+    private boolean hasRoleKey(LoginUser loginUser, String roleKey) {
+        List<CompanyRole> roles = loginUser.getUser().getRoles();
+        if (roles == null || roles.isEmpty()) {
+            return false;
+        }
+        return roles.stream().anyMatch(role -> roleKey.equals(role.getRoleKey()));
+    }
 }

+ 14 - 5
fs-company/src/main/java/com/fs/hisStore/controller/FsStoreOrderScrmController.java

@@ -20,6 +20,7 @@ import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.company.domain.Company;
+import com.fs.company.domain.CompanyRole;
 import com.fs.company.domain.CompanyUser;
 import com.fs.company.service.ICompanyService;
 import com.fs.company.service.ICompanyUserService;
@@ -136,13 +137,13 @@ public class FsStoreOrderScrmController extends BaseController
         String configJson = configService.selectConfigByKey("his.store");
         StoreConfig storeConfig = JSONUtil.toBean(configJson, StoreConfig.class);
         if(storeConfig != null && Boolean.TRUE.equals(storeConfig.getEnableCompanyOrderMode())){
-            if(!"00".equals(loginUser.getUser().getUserType())){//非管理员看见自己数据
+            if(!"00".equals(loginUser.getUser().getUserType()) && !hasRoleKey(loginUser, "finance_order_goods")){//非管理员看见自己数据
                 param.setCompanyUserId(loginUser.getUser().getUserId());
             }
             param.setIsCompanyOrder(1);//是否销售订单
         }
-        
-        
+
+
         if(!StringUtils.isEmpty(param.getCreateTimeRange())){
             param.setCreateTimeList(param.getCreateTimeRange().split("--"));
         }
@@ -223,7 +224,7 @@ public class FsStoreOrderScrmController extends BaseController
         String configJson = configService.selectConfigByKey("his.store");
         StoreConfig storeConfig = JSONUtil.toBean(configJson, StoreConfig.class);
         if(storeConfig != null && Boolean.TRUE.equals(storeConfig.getEnableCompanyOrderMode())){
-            if(!"00".equals(loginUser.getUser().getUserType())){//非管理员看见自己数据
+            if(!"00".equals(loginUser.getUser().getUserType()) && !hasRoleKey(loginUser, "finance_order_goods")){//非管理员看见自己数据
                 param.setCompanyUserId(loginUser.getUser().getUserId());
             }
             param.setIsCompanyOrder(1);//是否销售订单
@@ -551,7 +552,7 @@ public class FsStoreOrderScrmController extends BaseController
         String configJson = configService.selectConfigByKey("his.store");
         StoreConfig storeConfig = JSONUtil.toBean(configJson, StoreConfig.class);
         if(storeConfig != null && Boolean.TRUE.equals(storeConfig.getEnableCompanyOrderMode())){
-            if(!"00".equals(loginUser.getUser().getUserType())){//非管理员看见自己数据
+            if(!"00".equals(loginUser.getUser().getUserType()) && !hasRoleKey(loginUser, "finance_order_goods")){//非管理员看见自己数据
                 param.setCompanyUserId(loginUser.getUser().getUserId());
             }
             param.setIsCompanyOrder(1);//是否销售订单
@@ -691,4 +692,12 @@ public class FsStoreOrderScrmController extends BaseController
         BeanUtils.copyProperties(fsStoreOrderPayDeliveryDTO, fsStoreOrderScrm);
         return toAjax(fsStoreOrderService.updateFsStoreOrder(fsStoreOrderScrm));
     }
+
+    private boolean hasRoleKey(LoginUser loginUser, String roleKey) {
+        List<CompanyRole> roles = loginUser.getUser().getRoles();
+        if (roles == null || roles.isEmpty()) {
+            return false;
+        }
+        return roles.stream().anyMatch(role -> roleKey.equals(role.getRoleKey()));
+    }
 }

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

@@ -777,6 +777,10 @@ public class IpadSendServer {
                     // 发送直播短链
                     sendLiveShortLink(vo, content, miniMap);
                     break;
+                case "20":
+                    content.setSendStatus(0);
+                    content.setSendRemarks("APP直播待发送");
+                    break;
                 case "21":
                     content.setSendStatus(0);
                     content.setSendRemarks("短信待发送");

+ 16 - 0
fs-ipad-task/src/main/java/com/fs/app/task/SendMsg.java

@@ -367,6 +367,10 @@ public class SendMsg {
                             if (!settings.isEmpty()) {
                                 asyncSopTestService.asyncSendMsgBySopAppMP3NormalIM(settings, qwSopLogs.getCorpId(), qwUser.getCompanyUserId(), qwSopLogs.getFsUserId());
                             }
+                            List<QwSopCourseFinishTempSetting.Setting> liveSettings = JSON.parseArray(JSON.toJSONString(setting.getSetting()), QwSopCourseFinishTempSetting.Setting.class).stream().filter(e -> "20".equals(e.getContentType())).collect(Collectors.toList());
+                            if (!liveSettings.isEmpty()) {
+                                asyncSopTestService.asyncSendMsgBySopAppLiveIM(liveSettings, qwSopLogs.getCorpId(), qwUser.getCompanyUserId(), qwSopLogs.getFsUserId(), qwSopLogs.getId(), qwUser.getCompanyId());
+                            }
                         } catch (Exception e) {
                             log.error("推送APP失败", e);
                         }
@@ -405,6 +409,18 @@ public class SendMsg {
                             successCount++;
                         }
                     }
+
+                    // app直播卡片
+                    settings = setting.getSetting().stream().filter(e -> "20".equals(e.getContentType())).collect(Collectors.toList());
+                    if (!settings.isEmpty()) {
+                        actualCount++;
+                        hasAppSend = true;
+                        boolean sendFlag = asyncSopTestService.asyncSendMsgBySopAppLiveIM(
+                                settings, qwSopLogs.getCorpId(), qwUser.getCompanyUserId(), qwSopLogs.getFsUserId(), qwSopLogs.getId(), qwUser.getCompanyId());
+                        if (sendFlag) {
+                            successCount++;
+                        }
+                    }
                 }
             }
             qwSopLogs.setSend(true);

+ 8 - 5
fs-live-app/src/main/java/com/fs/live/task/Task.java

@@ -654,7 +654,7 @@ public class Task {
             }
 
             long currentTimeMillis = System.currentTimeMillis();
-            LocalDateTime now = LocalDateTime.now();
+
             List<Long> processedLiveIds = new ArrayList<>();
             Date nowDate = new Date();
             for (String key : keys) {
@@ -672,7 +672,7 @@ public class Task {
                     Long videoDuration = tagMarkInfo.getLong("videoDuration");
 
                     if (liveId == null || startTimeMillis == null || videoDuration == null || videoDuration <= 0) {
-                        log.warn("直播间打标签缓存信息不完整: key={}, liveId={}, startTime={}, videoDuration={}",
+                        log.info("直播间打标签缓存信息不完整: key={}, liveId={}, startTime={}, videoDuration={}",
                                 key, liveId, startTimeMillis, videoDuration);
                         continue;
                     }
@@ -680,12 +680,13 @@ public class Task {
                     // 查询直播间信息
                     Live live = liveService.selectLiveDbByLiveId(liveId);
                     if (live == null || live.getStartTime() == null) {
+                        log.info("没查到直播间-{}",liveId);
                         continue;
                     }
                     // 计算结束时间:开始时间 + 视频时长(秒转毫秒)
                     long endTimeMillis = startTimeMillis + (videoDuration * 1000);
 
-                    // 如果当前时间已经超过了结束时间,执行打标签操作
+                    // 如果当前时间已经超过了结束时间,执行打标签操作(约等于直播完成)
                     if (currentTimeMillis >= endTimeMillis) {
                         // 查询当前直播间的在线用户(liveFlag = 1, replayFlag = 0)
                         LiveWatchUser queryUser = new LiveWatchUser();
@@ -736,12 +737,14 @@ public class Task {
                                 }
                                 long totalOnlineSeconds = historyOnlineSeconds + currentWatchDuration;
 
-                                // 更新直播用户的在线时长
+                                log.info("更新直播用户的在线时长 用户直播离线 录播在线");
+                                // 更新直播用户的在线时长 用户直播离线 录播在线
                                 liveUser.setOnlineSeconds(totalOnlineSeconds);
                                 liveUser.setUpdateTime(nowDate);
                                 liveUser.setOnline(1);
                                 updateLiveUsers.add(liveUser);
 
+                                log.info(" 生成回放用户数据(liveFlag = 0, replayFlag = 1),在线时长从0开始");
                                 // 2. 生成回放用户数据(liveFlag = 0, replayFlag = 1),在线时长从0开始
                                 LiveWatchUser replayUser = new LiveWatchUser();
                                 replayUser.setLiveId(liveUser.getLiveId());
@@ -757,7 +760,7 @@ public class Task {
                                 replayUser.setCreateTime(nowDate);
                                 replayUser.setUpdateTime(nowDate);
                                 replayUsers.add(replayUser);
-                                redisCache.setCacheObject(entryTimeKey,now);
+                                redisCache.setCacheObject(entryTimeKey,currentTimeMillis);
                             }
 
                             // 批量更新直播用户的在线时长

+ 128 - 24
fs-live-app/src/main/java/com/fs/live/websocket/service/WebSocketServer.java

@@ -65,6 +65,8 @@ public class WebSocketServer {
 
     // 上次点赞数缓存
     private final ConcurrentHashMap<Long, Integer> lastLikeCountCache = new ConcurrentHashMap<>();
+    /** 直播间在线人数峰值(广播只增不减) */
+    private final ConcurrentHashMap<Long, Integer> maxUserCountCache = new ConcurrentHashMap<>();
     // 直播间用户session
     private final static ConcurrentHashMap<Long, ConcurrentHashMap<Long, Session>> rooms = new ConcurrentHashMap<>();
     // 管理端连接
@@ -135,7 +137,11 @@ public class WebSocketServer {
 
         Live live = liveService.selectLiveByLiveId(liveId);
         if (live == null) {
-            throw new BaseException("未找到直播间");
+            log.warn("WebSocket连接拒绝: 直播间不存在, liveId={}, userId={}", liveId, userId);
+            heartbeatCache.remove(session.getId());
+            sessionLocks.remove(session.getId());
+            closeSession(session, 4004, "未找到直播间");
+            return;
         }
         long companyId = -1L;
         long companyUserId = -1L;
@@ -168,7 +174,11 @@ public class WebSocketServer {
                 }
             }
             if (Objects.isNull(fsUser)) {
-                throw new BaseException("用户信息错误");
+                log.warn("WebSocket连接拒绝: 用户信息错误, liveId={}, userId={}", liveId, userId);
+                heartbeatCache.remove(session.getId());
+                sessionLocks.remove(session.getId());
+                closeSession(session, 4003, "用户信息错误");
+                return;
             }
 
             LiveWatchUser liveWatchUserVO = liveWatchUserService.join(fsUser,liveId, userId, location);
@@ -334,7 +344,9 @@ public class WebSocketServer {
     //关闭连接时调用
     @OnClose
     public void onClose(Session session) {
+
         Map<String, Object> userProperties = session.getUserProperties();
+
         // 获取公司ID和销售ID
         long companyId = -1L;
         long companyUserId = -1L;
@@ -349,10 +361,20 @@ public class WebSocketServer {
         long userId = (long) userProperties.get("userId");
         long userType = (long) userProperties.get("userType");
 
-        ConcurrentHashMap<Long, Session> room = getRoom(liveId);
-        List<Session> adminRoom = getAdminRoom(liveId);
+
+        log.info("关闭连接: sessionId={}, uri={}, liveId={}, userId={}, userType={}, companyId={}, companyUserId={}",
+                session.getId(), session.getRequestURI(), liveId, userId, userType, companyId, companyUserId);
+
+        ConcurrentHashMap<Long, Session> room = rooms.get(liveId);
+        List<Session> adminRoom = adminRooms.get(liveId);
         if (userType == 0) {
-            // 缓存用户信息,过期时间4小时
+            if (room == null || !room.containsKey(userId)) {
+                log.info("连接未成功建立,跳过清理: liveId={}, userId={}", liveId, userId);
+                heartbeatCache.remove(session.getId());
+                sessionLocks.remove(session.getId());
+                cleanupEmptyRoom(liveId);
+                return;
+            }
             String userCacheKey = "fs:user:" + userId;
             FsUserScrm fsUser = redisCache.getCacheObject(userCacheKey);
             if (fsUser == null) {
@@ -362,7 +384,12 @@ public class WebSocketServer {
                 }
             }
             if (Objects.isNull(fsUser)) {
-                throw new BaseException("用户信息错误");
+                log.error("用户信息错误: userId={}", userId);
+                room.remove(userId);
+                if (room.isEmpty()) {
+                    rooms.remove(liveId);
+                }
+                return;
             }
             // 计算并更新用户在线时长
             room.remove(userId);
@@ -377,11 +404,9 @@ public class WebSocketServer {
             String onlineUsersSetKey = ONLINE_USERS_SET_KEY + liveId;
             redisCache.redisTemplate.opsForSet().remove(onlineUsersSetKey, String.valueOf(userId));
 
-            LiveWatchUser liveWatchUserVO = liveWatchUserService.close(fsUser,liveId, userId);
-
+            LiveWatchUser liveWatchUserVO = liveWatchUserService.close(fsUser, liveId, userId);
 
-            // 下线消息采样10%进入队列
-            if (random.nextDouble() < ENTRY_EXIT_SAMPLE_RATE) {
+            if (random.nextDouble() < ENTRY_EXIT_SAMPLE_RATE && liveWatchUserVO != null) {
                 SendMsgVo sendMsgVo = new SendMsgVo();
                 sendMsgVo.setLiveId(liveId);
                 sendMsgVo.setUserId(userId);
@@ -396,14 +421,15 @@ public class WebSocketServer {
             }
 
         } else {
-            adminRoom.remove(session);
-            // 如果admin房间为空,关闭并清理执行器
-            if (adminRoom.isEmpty()) {
-                ExecutorService executor = adminExecutors.remove(liveId);
-                if (executor != null) {
-                    executor.shutdown();
+            if (adminRoom != null) {
+                adminRoom.remove(session);
+                if (adminRoom.isEmpty()) {
+                    ExecutorService executor = adminExecutors.remove(liveId);
+                    if (executor != null) {
+                        executor.shutdown();
+                    }
+                    adminRooms.remove(liveId);
                 }
-                adminRooms.remove(liveId);
             }
         }
 
@@ -616,6 +642,11 @@ public class WebSocketServer {
                 case "deleteMsg":
                     deleteMsg(liveId,msg);
                     break;
+                case "replyUser":
+                    if (userType == 1) {
+                        sendReplyToUser(liveId, msg, session);
+                    }
+                    break;
                 case "red":
                     processRed(liveId, msg);
                     break;
@@ -809,10 +840,30 @@ public class WebSocketServer {
     @OnError
     public void onError(Session session, Throwable throwable) {
 
+        log.error("WebSocket发生错误: sessionId={}, error={}",
+                session != null ? session.getId() : "null",
+                throwable != null ? throwable.getMessage() : "null", throwable);
+
+//        try {
+//            this.onClose(session);
+//        } catch (Exception e) {
+//            log.error("webSocket 错误处理失败", e.getMessage());
+//        }
+    }
+
+    private void closeSession(Session session, int code, String reason) {
         try {
-            this.onClose(session);
+            if (session != null && session.isOpen()) {
+                CloseReason.CloseCode closeCode;
+                try {
+                    closeCode = CloseReason.CloseCodes.getCloseCode(code);
+                } catch (Exception e) {
+                    closeCode = CloseReason.CloseCodes.NORMAL_CLOSURE;
+                }
+                session.close(new CloseReason(closeCode, reason != null ? reason : ""));
+            }
         } catch (Exception e) {
-            log.error("webSocket 错误处理失败", e);
+            log.error("关闭WebSocket会话失败: sessionId={}, error={}", session != null ? session.getId() : "null", e.getMessage());
         }
     }
 
@@ -922,6 +973,54 @@ public class WebSocketServer {
         }
     }
 
+    /**
+     * 管理端单独回复指定用户:从在线 map 定位 session,立即推送(不走广播队列,优先级最高)
+     */
+    private void sendReplyToUser(long liveId, SendMsgVo msg, Session adminSession) {
+        if (msg.getUserId() == null || StringUtils.isEmpty(msg.getMsg())) {
+            return;
+        }
+        msg.setMsg(productionWordFilter.filter(msg.getMsg()).getFilteredText());
+        if (StringUtils.isEmpty(msg.getMsg())) {
+            return;
+        }
+
+        Long targetUserId = msg.getUserId();
+        ConcurrentHashMap<Long, Session> room = getRoom(liveId);
+        Session targetSession = room.get(targetUserId);
+        if (targetSession == null || !targetSession.isOpen()) {
+            try {
+                sendMessage(adminSession, JSONObject.toJSONString(R.error("用户不在线,无法回复")));
+            } catch (IOException e) {
+                log.error("回复用户失败-通知管理员: liveId={}, targetUserId={}", liveId, targetUserId, e);
+            }
+            return;
+        }
+
+        SendMsgVo replyVo = new SendMsgVo();
+        replyVo.setLiveId(liveId);
+        replyVo.setUserId(targetUserId);
+        replyVo.setUserType(1L);
+        replyVo.setCmd("replyUser");
+        replyVo.setMsg(msg.getMsg());
+        replyVo.setNickName(msg.getNickName());
+        replyVo.setAvatar(msg.getAvatar());
+        replyVo.setOn(true);
+
+        String payload = JSONObject.toJSONString(R.ok().put("data", replyVo));
+        try {
+            sendMessage(targetSession, payload);
+            sendMessage(adminSession, JSONObject.toJSONString(R.ok().put("msg", "回复已发送")));
+        } catch (IOException e) {
+            log.error("发送单独回复失败: liveId={}, targetUserId={}, error={}", liveId, targetUserId, e.getMessage(), e);
+            try {
+                sendMessage(adminSession, JSONObject.toJSONString(R.error("回复发送失败")));
+            } catch (IOException ex) {
+                log.error("通知管理员回复失败: liveId={}, targetUserId={}", liveId, targetUserId, ex);
+            }
+        }
+    }
+
     /**
      * 广播消息
      * @param liveId   直播间ID
@@ -989,6 +1088,7 @@ public class WebSocketServer {
 
     public void removeLikeCountCache(Long liveId) {
         lastLikeCountCache.remove(liveId);
+        maxUserCountCache.remove(liveId);
     }
     @Scheduled(fixedRate = 300)// 每分钟执行一次
     public void broadcastLikeMessage() {
@@ -1022,23 +1122,27 @@ public class WebSocketServer {
 
     @Scheduled(fixedRate = 2000)// 每2秒执行一次
     public void broadcastUserNumMessage() {
+        Set<Long> activeLiveIds = new HashSet<>();
         // 遍历每个直播间
         for (Map.Entry<Long, ConcurrentHashMap<Long, Session>> entry : rooms.entrySet()) {
             Long liveId = entry.getKey();
+            activeLiveIds.add(liveId);
             ConcurrentHashMap<Long, Session> room = entry.getValue();
 
-            // 统计当前直播间的在线人数
-            int onlineCount = room.size();
+            // 当前在线人数与历史峰值取较大值,保证广播数值只增不减
+            int currentOnline = room.size();
+            int displayCount = maxUserCountCache.merge(liveId, currentOnline, Math::max);
 
             // 构造消息
             SendMsgVo sendMsgVo = new SendMsgVo();
             sendMsgVo.setLiveId(liveId);
             sendMsgVo.setCmd("userCount");
-            sendMsgVo.setData(String.valueOf(onlineCount));
+            sendMsgVo.setData(String.valueOf(displayCount));
 
-            // 广播当前直播间的在线人数
+            // 广播当前直播间的在线人数(峰值)
             broadcastMessage(liveId, JSONObject.toJSONString(R.ok().put("data", sendMsgVo)));
         }
+        maxUserCountCache.keySet().removeIf(liveId -> !activeLiveIds.contains(liveId));
     }
 
 
@@ -1709,7 +1813,7 @@ public class WebSocketServer {
                 return priorityCompare;
             }
             // 相同优先级按序列号排序(FIFO)
-            return Long.compare(this.sequence, other.sequence);
+            return java.lang.Long.compare(this.sequence, other.sequence);
         }
     }
 

+ 244 - 35
fs-qw-task/src/main/java/com/fs/app/controller/CommonController.java

@@ -3,38 +3,42 @@ package com.fs.app.controller;
 
 import cn.hutool.core.date.DateUtil;
 import com.alibaba.fastjson.JSON;
+import com.fs.aiSoundReplication.param.TtsRequest;
+import com.fs.aiSoundReplication.service.impl.TtsServiceImpl;
 import com.fs.app.task.qwTask;
 import com.fs.app.taskService.*;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.ResponseResult;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.utils.StringUtils;
+import com.fs.company.mapper.CompanyMapper;
+import com.fs.company.param.VcCompanyUser;
 import com.fs.company.service.ICompanyService;
+import com.fs.company.service.ICompanyUserService;
 import com.fs.company.vo.RedPacketMoneyVO;
 import com.fs.course.config.CourseConfig;
 import com.fs.course.mapper.FsCourseRedPacketLogMapper;
 import com.fs.course.mapper.FsCourseWatchLogMapper;
-import com.fs.course.param.FsCourseLinkMiniParam;
 import com.fs.course.param.newfs.FsUserCourseAddCompanyUserParam;
 import com.fs.course.service.*;
 import com.fs.course.vo.FsUserCourseVideoQVO;
-import com.fs.fastGpt.domain.FastGptPushTokenTotal;
+import com.fs.fastgptApi.vo.AudioVO;
 import com.fs.his.domain.FsIntegralCount;
 import com.fs.his.domain.FsUser;
 import com.fs.his.service.IFsInquiryOrderService;
 import com.fs.his.service.IFsIntegralCountService;
+import com.fs.his.service.IFsIntegralGoodsService;
 import com.fs.his.service.IFsUserIntegralLogsService;
 import com.fs.his.utils.qrcode.QRCodeUtils;
-import com.fs.qw.domain.QwCompany;
-import com.fs.qw.domain.QwExternalContact;
-import com.fs.qw.domain.QwIpadServerLog;
-import com.fs.qw.domain.QwUser;
+import com.fs.qw.domain.*;
+import com.fs.qw.mapper.QwContactWayMapper;
 import com.fs.qw.mapper.QwExternalContactMapper;
-import com.fs.qw.mapper.QwRestrictionPushRecordMapper;
 import com.fs.qw.mapper.QwUserMapper;
 import com.fs.qw.service.*;
-import com.fs.qwApi.domain.QwExternalContactResult;
+import com.fs.qw.service.impl.AsyncQwContactWayService;
+import com.fs.qwApi.domain.inner.ExternalContact;
 import com.fs.qwApi.service.QwApiService;
+import com.fs.sop.domain.QwSopTempVoice;
 import com.fs.sop.mapper.QwSopLogsMapper;
 import com.fs.sop.mapper.QwSopMapper;
 import com.fs.sop.mapper.SopUserLogsMapper;
@@ -50,15 +54,14 @@ import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import lombok.extern.slf4j.Slf4j;
 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.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
-import com.fs.app.task.qwTask;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.web.bind.annotation.*;
 
 import java.time.*;
 import java.time.format.DateTimeFormatter;
 import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
 
 @Api("公共接口")
 @RestController
@@ -67,12 +70,10 @@ import java.util.*;
 public class CommonController {
 
     @Autowired
-    private SopLogsTaskService service;
+    private SopLogsTaskService taskService;
     @Autowired
     private IFsUserCourseVideoService courseVideoService;
     @Autowired
-    private SopLogsTaskService sopLogsTaskService;
-    @Autowired
     private SopWxLogsService sopWxLogsService;
     @Autowired
     private IQwExternalContactService qwExternalContactService;
@@ -174,6 +175,233 @@ public class CommonController {
 
     @Autowired
     private IFsIntegralCountService integralCountService;
+    @Autowired
+    private QwContactWayMapper qwContactWayMapper;
+
+    @Autowired
+    RedisTemplate<String, String> redisTemplate;
+
+    @Autowired
+    private AsyncQwContactWayService asyncQwContactWayService;
+
+    @Autowired
+    private IQwContactWayService contactWayService;
+
+
+    @Autowired
+    private IQwSopTempVoiceService iQwSopTempVoiceService;
+
+    @Autowired
+    private ICompanyUserService iCompanyUserService;
+
+    @Autowired
+    private TtsServiceImpl ttsServiceImpl;
+
+    @Autowired
+    private CompanyMapper companyMapper;
+
+    @Autowired
+    private IFsIntegralGoodsService goodsService;
+
+    @Autowired
+    private IFsCoursePlaySourceConfigService fsCoursePlaySourceConfigService;
+
+    @GetMapping("/test")
+    public R test(){
+
+        Long userId = 10568613L;
+        String AppId = "";
+
+        if (AppId == null || AppId.isEmpty()) {
+            return goodsService.getCourseIntegralGoods(userId);
+        }
+
+        String integralGoods = fsCoursePlaySourceConfigService.selectCoursePlaySourceConfigByAppIdGoods(AppId);
+        if (!StringUtil.strIsNullOrEmpty(integralGoods)) {
+            return goodsService.getCourseIntegralGoodsByMiniApp(AppId, userId, integralGoods);
+        }
+
+        return goodsService.getCourseIntegralGoods(userId);
+    }
+
+    /**
+     * 定时 将当天 生成的语音文本 转为 声音
+     */
+    @GetMapping("/resetQwContactWayUserLimit")
+    public void convertSopVoiceEveryDay() {
+
+        try {
+            // 查询待生成声音的文本
+            List<QwSopTempVoice> qwSopTempVoices = iQwSopTempVoiceService.convertSopVoiceEveryDay();
+
+            // 过滤 出销售id
+            List<Long> userIds = qwSopTempVoices.stream()
+                    .map(QwSopTempVoice::getCompanyUserId)
+                    .filter(Objects::nonNull)
+                    .distinct()
+                    .collect(Collectors.toList());
+            // 查询 销售 是否绑定了且录制了音色
+            List<VcCompanyUser> vcCompanyUsers = iCompanyUserService.selectVcCompanyUserList(userIds);
+
+            // 做键值 方便匹配
+            Map<Long, VcCompanyUser> vcUserMap = vcCompanyUsers.stream()
+                    .collect(Collectors.toMap(VcCompanyUser::getCompanyUserId, v -> v, (a, b) -> a));
+            List<QwSopTempVoice> filteredVoices = qwSopTempVoices.stream()
+                    .filter(v -> v.getCompanyUserId() != null && vcUserMap.containsKey(v.getCompanyUserId()))
+                    .filter(v -> {
+                        String redisKey = "sop:voice:processing:" + v.getId();
+                        return redisCache.setIfAbsent(redisKey, "1",12, TimeUnit.HOURS);
+                    })
+                    .collect(Collectors.toList());
+
+            filteredVoices.forEach(item -> {
+                VcCompanyUser vcCompanyUser = vcUserMap.get(item.getCompanyUserId());
+                AudioVO audioVO = ttsServiceImpl.textToSpeech(new TtsRequest(null, null, vcCompanyUser.getSpeakerId(), item.getVoiceTxt()));
+                item.setVoiceUrl(audioVO.getUrl());
+                item.setDuration(audioVO.getDuration());
+                item.setUserVoiceUrl(audioVO.getWavUrl());
+                QwUser user = qwUserMapper.selectQwUserconvertSopVoice(item.getCompanyUserId());
+                ttsServiceImpl.ttsChargeByCount(vcCompanyUser,audioVO,user);
+
+            });
+            // 批量更新
+            int batchSize = 200;
+            for (int i = 0; i < filteredVoices.size(); i += batchSize) {
+                List<QwSopTempVoice> batch = filteredVoices.subList(i, Math.min(i + batchSize, filteredVoices.size()));
+                iQwSopTempVoiceService.updateBatchWithDataSource(batch);
+            }
+            // 更新完成后清除Redis标记
+            filteredVoices.forEach(item -> {
+                String redisKey = "sop:voice:processing:" + item.getId();
+                redisCache.deleteObject(redisKey);
+            });
+        }catch (Exception e){
+
+        }
+
+
+    }
+    @GetMapping("/qwContactWay")
+    public void qwContactWay(String userID) throws Exception {
+        String state="way:ww6d9a09888a2e9c4d:11";
+        String corpId="ww6d9a09888a2e9c4d";
+        boolean isSend = true;
+        boolean isWay = false;
+        QwContactWay wayId = null;
+        String welcomeCode="15454";
+
+        QwUser qwUser = qwUserMapper.selectQwUserByCorpIdAndUserId(corpId, userID);
+
+        if (state != null && state != "") {
+            String s = "way:" + corpId + ":";
+            if (state.contains(s)) {
+                String substring = state.substring(state.indexOf(s) + s.length());
+                QwContactWay qwContactWay = qwContactWayMapper.selectQwContactWayById(Long.parseLong(substring));
+                if (qwContactWay != null) {
+
+                    if (welcomeCode != null && welcomeCode != "") {
+                        isWay = true;
+                        wayId = qwContactWay;
+                        if (qwContactWay.getIsWelcome() != null && qwContactWay.getIsWelcome() == 1) {
+                            boolean isClose = true;
+                            if (wayId.getIsSpanWelcome() == 1) {
+                                ExternalContact externalContact = new ExternalContact();
+                                String name = externalContact.getName();
+                                String closeWelcomeWord = wayId.getCloseWelcomeWord();
+                                if (closeWelcomeWord != null && closeWelcomeWord.length() > 0) {
+                                    List<String> strings = JSON.parseArray(closeWelcomeWord, String.class);
+                                    for (String string : strings) {
+                                        if (name.contains(string)) {
+                                            isClose = false;
+                                            break;
+                                        }
+                                    }
+                                }
+                            }
+                            if (qwContactWay.getIsWelcome() == 1 && isClose) {
+                                isSend = contactWayService.sendWelcomeMsg(qwContactWay, corpId, welcomeCode, qwUser, 1546L);
+                            }
+                        }
+
+                    }
+                    if (qwContactWay.getUserType() == 1 && qwContactWay.getIsUserLimit() == 1) {
+
+                        String userLimitJson = qwContactWay.getUserLimitJson();
+                        List<QwContactWayUser> qwContactWayUsers = JSON.parseArray(userLimitJson, QwContactWayUser.class);
+
+                        String wayLimit = "qwContactWayLimit:" + corpId  + ":" + userID;
+
+                        QwContactWayUser matchedUser = qwContactWayUsers.stream()
+                                .filter(u -> userID.equals(u.getUserId()))
+                                .findFirst()
+                                .orElse(null);
+
+                        if (matchedUser != null) {
+                            Long currentCount = redisTemplate.opsForValue().increment(wayLimit, 1);
+                            if (currentCount == null) {
+                                currentCount = 1L;
+                            }
+
+                            if(currentCount == 1L){
+                                long expireSeconds = getSecondsUntilMidnight();
+                                redisTemplate.expire(wayLimit, expireSeconds, java.util.concurrent.TimeUnit.SECONDS);
+                            }
+                            if(currentCount >= matchedUser.getLimitCount()){
+                                log.error("用户{}已达到每日添加上限{}/{}", userID, currentCount, matchedUser.getLimitCount());
+
+                                boolean needUpdate = false;
+
+                                List<String> userIdList = JSON.parseArray(qwContactWay.getUserIds(), String.class);
+
+                                if (userIdList != null && !userIdList.isEmpty() && userIdList.contains(userID) && !userIdList.contains(qwContactWay.getSpareUserIds())) {
+                                    //移除渠道活码里面的人员-保留 备选人员
+                                    userIdList.remove(userID);
+                                    //移除渠道活码里面的人员-保留 备选人员
+                                    if (userIdList.isEmpty()){
+                                        List<String> newUserIdList =new ArrayList<>();
+                                        newUserIdList.add(JSON.parseObject(qwContactWay.getSpareUserIds(), String.class));
+                                        qwContactWay.setUserIds(JSON.toJSONString(newUserIdList));
+                                    }else {
+                                        qwContactWay.setUserIds(JSON.toJSONString(userIdList));
+                                    }
+                                    needUpdate = true;
+                                }
+
+                                if (needUpdate) {
+                                    asyncQwContactWayService.executeUpdateContactWay(qwContactWay,userID);
+                                }
+                            }
+                        }
+
+                    }
+                }
+
+            }
+        }
+    }
+
+    private long getSecondsUntilMidnight() {
+        LocalDateTime now = LocalDateTime.now();
+        LocalDateTime midnight = now.toLocalDate().plusDays(1).atStartOfDay();
+        return java.time.Duration.between(now, midnight).getSeconds();
+    }
+
+
+    @GetMapping("/testTime")
+    public R testTime(String time, String sopId) throws Exception {
+        log.info("进入sop任务");
+        LocalDateTime now = LocalDateTime.now();
+
+        List<String> sopIds = new ArrayList<>();
+        if (StringUtils.isNotEmpty(sopId)) {
+            sopIds = Arrays.asList(sopId.split(","));
+        }
+
+        taskService.selectSopUserLogsListByTime(DateUtil.parseLocalDateTime(time), sopIds);
+        LocalDateTime now1 = LocalDateTime.now();
+        return R.ok().put("start", now).put("end", now1);
+    }
+
 
     @GetMapping("/UserIntegralCount")
     public void UserIntegralCount(){
@@ -461,25 +689,6 @@ public class CommonController {
             }
         }
     }
-
-    @GetMapping("/test")
-    public R test(String time, String sopId) throws Exception {
-        log.info("进入sop任务");
-//        LocalDateTime currentTime = DateUtil.parseLocalDateTime(time);
-//        // 计算下一个整点时间
-//        LocalDateTime nextHourTime = currentTime.withMinute(0).withSecond(0).withNano(0).plusHours(1);
-//
-//        // 打印日志,确认时间
-//        log.info("任务实际执行时间: {}", currentTime);
-//        log.info("传递给任务的时间参数: {}", nextHourTime);
-        List<String> sopidList = new ArrayList<>();
-        if(StringUtils.isNotEmpty(sopId)){
-            sopidList = Arrays.asList(sopId.split(","));
-        }
-//        sopLogsTaskService.selectSopUserLogsListByTime(DateUtil.parseLocalDateTime(time), sopidList);
-        sopWxLogsTaskService.selectSopUserLogsListByTime(DateUtil.parseLocalDateTime(time), sopidList);
-        return R.ok();
-    }
     @GetMapping("/testWx")
     public R testWx(String time) throws Exception {
         sopWxLogsService.wxSopLogsByTime(DateUtil.parseLocalDateTime(time));

+ 15 - 1
fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopLogsTaskServiceImpl.java

@@ -799,7 +799,6 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
         if (StringUtils.isNotEmpty(logVo.getChatId())) {
             QwGroupChat groupChat = groupChatMap.get(logVo.getChatId());
             if (groupChat.getChatUserList() != null && !groupChat.getChatUserList().isEmpty()) {
-                QwSopLogs sopLogs = createBaseLog(formattedSendTime, logVo, ruleTimeVO, groupChat.getChatId(), groupChat.getName(), null, isOfficial, null, null);
                 boolean hasGroupNotice = content.getSetting() != null && content.getSetting().stream()
                         .anyMatch(st -> "11".equals(st.getContentType()));
                 if(hasGroupNotice){
@@ -809,6 +808,9 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                     ruleTimeVO.setSendType(6);
                 }
                 ruleTimeVO.setType(2);
+
+                QwSopLogs sopLogs = createBaseLog(formattedSendTime, logVo, ruleTimeVO, groupChat.getChatId(), groupChat.getName(), null, isOfficial, null, null);
+
                 handleLogBasedOnType(sopLogs, content, logVo, sendTime, courseId, videoId,
                         type, qwUserId, companyUserId, companyId, groupChat.getChatId(), welcomeText, qwUserName,
                         null, true, miniAppId, groupChat, config, miniMap, null, sendMsgType,
@@ -1629,6 +1631,12 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
 
                     setting.setMiniprogramPage(shortH5Link);
                     break;
+                // app发送直播IM消息
+                case "20":
+                    json = configService.selectConfigByKey("his.config");
+                    sysConfig= JSON.parseObject(json,FSSysConfig.class);
+                    createLiveWatchLogAndEnQueue(companyId,companyUserId,externalId, setting.getLiveId(),sysConfig.getAppId(),1,qwUserId,logVo.getCorpId());
+                    break;
                 default:
                     break;
             }
@@ -1956,6 +1964,12 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                         log.error("浏览器看课模板解析失败:" + e);
                     }
 
+                    break;
+                // app发送直播IM消息
+                case "20":
+                    json = configService.selectConfigByKey("his.config");
+                    sysConfig= JSON.parseObject(json,FSSysConfig.class);
+                    createLiveWatchLogAndEnQueue(companyId,companyUserId,externalId, setting.getLiveId(),sysConfig.getAppId(),1,qwUserId,logVo.getCorpId());
                     break;
                 case "21"://短信看课
                     if (sopLogs.getFsUserId() != null && !Long.valueOf(0L).equals(sopLogs.getFsUserId())) {

+ 11 - 0
fs-service/pom.xml

@@ -321,6 +321,17 @@
             <version>4.38.0.ALL</version>
         </dependency>
 
+        <dependency>
+            <groupId>org.bouncycastle</groupId>
+            <artifactId>bcpkix-jdk15on</artifactId>
+            <version>1.70</version>
+        </dependency>
+        <dependency>
+            <groupId>org.bouncycastle</groupId>
+            <artifactId>bcprov-jdk15on</artifactId>
+            <version>1.70</version>
+        </dependency>
+
 
     </dependencies>
 

+ 2 - 1
fs-service/src/main/java/com/fs/common/service/impl/SmsServiceImpl.java

@@ -1043,7 +1043,7 @@ public class SmsServiceImpl implements ISmsService
      * @param temp
      * @param param
      */
-//    @Async
+    @Async
     public void batchSmsOp4AiSend(CompanySmsTemp temp, SmsSendBatchParam param){
         CompanyUser companyUser=companyUserService.selectCompanyUserById(param.getCompanyUserId());
         CompanyVoiceRoboticCallBlacklistCheckParam companyVoiceRoboticCallBlacklistCheckParam = new CompanyVoiceRoboticCallBlacklistCheckParam();
@@ -1106,6 +1106,7 @@ public class SmsServiceImpl implements ISmsService
                 } catch (UnsupportedEncodingException e) {
                     e.printStackTrace();
                 }
+
                 String post = HttpRequest.get(urls)
 //                            .body(String.valueOf(jsonObject))
                         .execute().body();

+ 5 - 0
fs-service/src/main/java/com/fs/company/domain/Company.java

@@ -146,4 +146,9 @@ public class Company extends BaseEntity
 
     @TableField(exist = false)
     private List<Long> showGatewayIds;
+
+    /**
+     * 分账子商户id
+     */
+    private Long subMerchantId;
 }

+ 2 - 0
fs-service/src/main/java/com/fs/company/domain/CompanyUser.java

@@ -214,6 +214,8 @@ public class CompanyUser extends BaseEntity
      */
     private Long cidServerId;
 
+    private Long isCustomerVoice;
+
     public String getFsUserId() {
         return fsUserId;
     }

+ 14 - 0
fs-service/src/main/java/com/fs/company/mapper/CompanyConfigMapper.java

@@ -91,4 +91,18 @@ public interface CompanyConfigMapper
     List<CompanyMiniAppVO> getCompanyMiniAppList(@Param("companyId") Long companyId);
 
     List<CompanyConfig> selectListByKey(String configKey);
+
+
+    @Select("select \n" +
+            "id,\n" +
+            "name,\n" +
+            "appid\n" +
+            "from\n" +
+            "fs_course_play_source_config\n" +
+            "where \n" +
+            "is_del = 0 \n" +
+            "and status = 0" +
+            "and name like '%app%' ")
+    List<CompanyMiniAppVO> getCompanyMiniAppAllList();
+
 }

+ 6 - 0
fs-service/src/main/java/com/fs/company/mapper/CompanyFsUserMapper.java

@@ -1,6 +1,7 @@
 package com.fs.company.mapper;
 
 import com.fs.company.domain.CompanyFsUser;
+import com.fs.company.param.VcCompanyUser;
 import com.fs.his.domain.FsUser;
 import com.fs.his.vo.CompanyUserBindUserVO;
 import org.apache.ibatis.annotations.Delete;
@@ -50,4 +51,9 @@ public interface CompanyFsUserMapper {
 
     @Delete("delete from company_fs_user WHERE id=#{id}")
     int deleteById(Long id);
+
+    List<VcCompanyUser> selectVcCompanyUserList(@Param("list") List<Long> userIds);
+
+    @Select("select id,speaker_id,company_user_id,upload_url from vc_company_user where company_user_id=#{companyUserId}")
+    VcCompanyUser selectVcCompanyUser(@Param("companyUserId") String companyUserId);
 }

+ 2 - 0
fs-service/src/main/java/com/fs/company/mapper/CompanyMapper.java

@@ -256,4 +256,6 @@ public interface CompanyMapper
     List<Company> getCompanyList(@Param("corpId") String corpId);
 
     String getGateWayList(@Param("companyId") Long companyId);
+
+    List<Company> queryCompanyListByCompanyIds(@Param("companyIds") List<Long> companyIds);
 }

+ 4 - 2
fs-service/src/main/java/com/fs/company/mapper/CompanyUserMapper.java

@@ -92,8 +92,10 @@ public interface CompanyUserMapper
 
 
     CompanyUser selectUserByUserName(@Param("userName")String userName);
+
     @Select("select count(1) from company_user where user_name=#{userName}")
     int checkUserName(String userName);
+
     @Update("update company_user set password=#{password} where user_id=#{userId}")
     int resetUserPwdByUserId( @Param("userId")Long userId, @Param("password") String encryptPassword);
 
@@ -174,10 +176,10 @@ public interface CompanyUserMapper
             "    OR FIND_IN_SET(#{maps.deptId}, cd.ancestors))")
     List<CompanyUserQwVO> getUserListByDeptIdToQwUserid(@Param("maps") CompanyUser user);
 
-    @Select("select * from  qw_user  where corp_id=#{corpId} and company_id=#{companyId}")
+    @Select("select * from  qw_user  where corp_id=#{corpId} and company_id=#{companyId} and company_user_id is not null and is_del=0 ")
     List<QwUserVO> selectCompanyQwUserList(@Param("corpId") String corpId,@Param("companyId")Long companyId);
 
-    @Select("select * from  qw_user  where corp_id=#{corpId} and company_id=#{companyId} and company_user_id=#{userId}")
+    @Select("select * from  qw_user  where corp_id=#{corpId} and company_id=#{companyId} and company_user_id=#{userId} and is_del=0 ")
     List<QwUserVO> selectCompanyQwUserListByMy(@Param("corpId") String corpId,@Param("companyId")Long companyId,@Param("userId")Long userId);
 
     @Select("<script>" +

+ 1 - 1
fs-service/src/main/java/com/fs/company/mapper/CompanyVoiceRoboticCalleesMapper.java

@@ -34,7 +34,7 @@ public interface CompanyVoiceRoboticCalleesMapper extends BaseMapper<CompanyVoic
     public List<CompanyVoiceRoboticCallees> selectCompanyVoiceRoboticCalleesList(CompanyVoiceRoboticCallees companyVoiceRoboticCallees);
 
     @Select("select cv.*,cw.is_add,cw.customer_id from company_voice_robotic_callees  cv " +
-            "left join  company_wx_client cw on cv.robotic_id = cw.robotic_id " +
+            "inner join  company_wx_client cw on cv.robotic_id = cw.robotic_id and cv.user_id=cw.customer_id " +
             "where cv.robotic_id = #{roboticId}")
     public List<CompanyVoiceRoboticCallees> selectCompanyVoiceRoboticCalleesListByRoboticId(@Param("roboticId") Long id);
 

+ 9 - 0
fs-service/src/main/java/com/fs/company/param/SidebarCompanyImageParam.java

@@ -0,0 +1,9 @@
+package com.fs.company.param;
+
+import lombok.Data;
+
+@Data
+public class SidebarCompanyImageParam {
+    private Integer companyId;
+    private String imageUrl;
+}

+ 1 - 0
fs-service/src/main/java/com/fs/company/service/ICompanyConfigService.java

@@ -80,6 +80,7 @@ public interface ICompanyConfigService
      * @return
      */
     List<CompanyMiniAppVO> getCompanyMiniAppList(Long companyId);
+    R getCompanyMiniAppAllList();
 
     SaveCompanyMiniAppParam getCurrentCompanyMiniApp(Long companyId);
     /**

+ 10 - 0
fs-service/src/main/java/com/fs/company/service/ICompanyService.java

@@ -10,6 +10,7 @@ import com.fs.company.domain.Company;
 import com.fs.company.domain.CompanyUser;
 import com.fs.company.param.CompanyLiveShowParam;
 import com.fs.company.param.CompanyParam;
+import com.fs.company.param.SidebarCompanyImageParam;
 import com.fs.company.vo.CompanyCrmVO;
 import com.fs.company.vo.CompanyNameVO;
 import com.fs.company.vo.CompanyVO;
@@ -202,4 +203,13 @@ public interface ICompanyService
     String checkMchTransferStatus(String outBatchNo,Long companyId);
 
     R checkMchTransferStatusByBatchID(String batchId, Long companyId);
+
+    R getSidebarCompanyImage(Long companyId);
+    R saveSidebarCompanyImage(SidebarCompanyImageParam param);
+
+
+    /**
+     * 根据公司id集合查询多个公司
+     * */
+    List<Company> queryCompanyListByCompanyIds(List<Long> companyIds);
 }

+ 8 - 0
fs-service/src/main/java/com/fs/company/service/ICompanyUserService.java

@@ -6,6 +6,7 @@ import com.fs.company.domain.CompanyUser;
 import com.fs.company.param.CompanyUserAreaParam;
 import com.fs.company.param.CompanyUserCodeParam;
 import com.fs.company.param.CompanyUserQwParam;
+import com.fs.company.param.VcCompanyUser;
 import com.fs.company.vo.*;
 import com.fs.his.domain.FsUser;
 import com.fs.his.vo.CitysAreaVO;
@@ -313,4 +314,11 @@ public interface ICompanyUserService {
     int countCompanyUserByUserId(Long userId);
 
     List<OptionsVO> findCompanyUserOptions(String keyword, Long selectedId, Integer pageNumber,  Integer pageSize);
+
+    List<VcCompanyUser> selectVcCompanyUserList(List<Long> userIds);
+
+    /**
+     * 获取所有公司员工列表
+     * */
+    List<CompanyUser> getAllCompanyUserListNoParams();
 }

+ 5 - 0
fs-service/src/main/java/com/fs/company/service/impl/CompanyConfigServiceImpl.java

@@ -216,6 +216,11 @@ public class CompanyConfigServiceImpl implements ICompanyConfigService
         return companyConfigMapper.getCompanyMiniAppList(companyId);
     }
 
+    @Override
+    public R getCompanyMiniAppAllList() {
+        return  R.ok().put("data",companyConfigMapper.getCompanyMiniAppAllList());
+    }
+
     //主要小程序
     Integer type_main = 0;
     //备用小程序

+ 27 - 0
fs-service/src/main/java/com/fs/company/service/impl/CompanyServiceImpl.java

@@ -20,6 +20,7 @@ import com.fs.company.domain.*;
 import com.fs.company.mapper.*;
 import com.fs.company.param.CompanyLiveShowParam;
 import com.fs.company.param.CompanyParam;
+import com.fs.company.param.SidebarCompanyImageParam;
 import com.fs.company.service.*;
 import com.fs.company.util.CompanyTuiMoneyCalc;
 import com.fs.company.vo.*;
@@ -2013,4 +2014,30 @@ public class CompanyServiceImpl implements ICompanyService
         }));
     }
 
+
+    @Override
+    public R getSidebarCompanyImage(Long companyId) {
+        String url = redisCache.getCacheObject("SidebarCompany:" + companyId) != null
+                ? redisCache.getCacheObject("SidebarCompany:" + companyId)
+                : "";
+        return R.ok().put("imageUrl", url);
+    }
+
+    @Override
+    public R saveSidebarCompanyImage(SidebarCompanyImageParam param) {
+
+        redisCache.setCacheObject("SidebarCompany:"+param.getCompanyId(),param.getImageUrl());
+
+        return R.ok();
+    }
+
+    @Override
+    public List<Company> queryCompanyListByCompanyIds(List<Long> companyIds) {
+        List<Company> companyList =companyMapper.queryCompanyListByCompanyIds(companyIds);
+        if (CollectionUtils.isEmpty(companyList)){
+            return Collections.emptyList();
+        }
+        return companyList;
+    }
+
 }

+ 18 - 0
fs-service/src/main/java/com/fs/company/service/impl/CompanyUserServiceImpl.java

@@ -20,6 +20,7 @@ import com.fs.company.mapper.*;
 import com.fs.company.param.CompanyUserAreaParam;
 import com.fs.company.param.CompanyUserCodeParam;
 import com.fs.company.param.CompanyUserQwParam;
+import com.fs.company.param.VcCompanyUser;
 import com.fs.company.service.*;
 import com.fs.company.vo.*;
 import com.fs.config.cloud.CloudHostProper;
@@ -54,6 +55,7 @@ import com.fs.system.service.ISysUserService;
 import com.fs.voice.utils.StringUtil;
 import com.fs.wxUser.domain.CompanyWxUser;
 import com.github.pagehelper.PageHelper;
+import org.apache.commons.collections4.CollectionUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -1243,4 +1245,20 @@ public class CompanyUserServiceImpl implements ICompanyUserService
                 .collect(Collectors.toList());
     }
 
+    @Override
+    public List<VcCompanyUser> selectVcCompanyUserList(List<Long> userIds) {
+        return companyFsUserMapper.selectVcCompanyUserList(userIds);
+    }
+
+    @Override
+    public List<CompanyUser> getAllCompanyUserListNoParams() {
+        CompanyUser companyUser=new CompanyUser();
+        companyUser.setStatus("0");
+        List<CompanyUser> companyUserList = companyUserMapper.selectCompanyUserList(companyUser);
+        if (CollectionUtils.isEmpty(companyUserList)){
+            return Collections.emptyList();
+        }
+        return companyUserList;
+    }
+
 }

+ 7 - 5
fs-service/src/main/java/com/fs/company/service/impl/CompanyVoiceRoboticServiceImpl.java

@@ -1319,6 +1319,8 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
         final Long workflowId = robotic.getCompanyAiWorkflowId();
         final Long roboticId = robotic.getId();
 
+        companyWorkflowEngine.createSipTask(roboticId, workflowId);
+
         // 先初始化所有工作流实例
         List<ExecutionResult> initResults = new ArrayList<>();
         for (CompanyVoiceRoboticBusiness business : roboticBusinesseList) {
@@ -1667,11 +1669,11 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
                 .filter(Objects::nonNull)
                 .collect(Collectors.toList());
 
-        //拿取业务id
-        List<Long> businessIds = records.stream()
-                .map(WorkflowExecRecordVo::getBusinessId)
-                .filter(Objects::nonNull)
-                .collect(Collectors.toList());
+//        //拿取业务id
+//        List<Long> businessIds = records.stream()
+//                .map(WorkflowExecRecordVo::getBusinessId)
+//                .filter(Objects::nonNull)
+//                .collect(Collectors.toList());
 
 //        if (!businessIds.isEmpty()) {
 //            List<CompanyVoiceRoboticBusiness> businesses = companyVoiceRoboticBusinessMapper.selectList(new LambdaQueryWrapper<CompanyVoiceRoboticBusiness>()

+ 96 - 18
fs-service/src/main/java/com/fs/company/service/impl/CompanyWorkflowEngineImpl.java

@@ -4,6 +4,7 @@ import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fs.common.core.redis.RedisCache;
 import com.fs.common.exception.CustomException;
 import com.fs.common.utils.StringUtils;
 import com.fs.company.domain.*;
@@ -29,6 +30,7 @@ import org.springframework.transaction.annotation.Transactional;
 import java.time.LocalDateTime;
 import java.time.LocalTime;
 import java.util.*;
+import java.util.concurrent.TimeUnit;
 
 /**
  * @author MixLiu
@@ -78,6 +80,9 @@ public class CompanyWorkflowEngineImpl implements CompanyWorkflowEngine {
     @Autowired
     private CloudHostProper cloudHostProper;
 
+    @Autowired
+    private RedisCache redisCache;
+
     /**
      * 初始化工作流
      * 创建工作流实例并保存初始状态
@@ -102,8 +107,6 @@ public class CompanyWorkflowEngineImpl implements CompanyWorkflowEngine {
                     definition.getStartNodeKey(), context, definition);
 
             log.info("工作流初始化成功: {} -> {}", workflowInstanceId, workflowDefinitionId);
-            //为任务创建sip任务并存入表数据
-            createSipTask(Long.parseLong(inputVariables.get("roboticId").toString()),workflowDefinitionId);
             return ExecutionResult.success()
                     .nextNodeKey(definition.getStartNodeKey())
                     .workflowInstanceId(workflowInstanceId).build();
@@ -570,37 +573,59 @@ public class CompanyWorkflowEngineImpl implements CompanyWorkflowEngine {
      * @param workFlowId
      */
     public Long createSipTask(Long roboticId, Long workFlowId) {
-        try {
-            List<String> nodeTypes = Arrays.asList(NodeTypeEnum.AI_CALL_TASK.getCode());
-            CompanyVoiceRobotic robotic = companyVoiceRoboticMapper.selectCompanyVoiceRoboticById(roboticId);
-            List<CompanyWorkflowNode> companyWorkflowNodes = companyWorkflowNodeMapper.selectNodesByWorkflowIdAndTypes(workFlowId, nodeTypes);
-            //为所有外呼节点创建任务的对应sip外呼任务
-            for (CompanyWorkflowNode callNode : companyWorkflowNodes) {
+
+        List<String> nodeTypes = Arrays.asList(NodeTypeEnum.AI_CALL_TASK.getCode());
+        CompanyVoiceRobotic robotic = companyVoiceRoboticMapper.selectCompanyVoiceRoboticById(roboticId);
+        List<CompanyWorkflowNode> companyWorkflowNodes = companyWorkflowNodeMapper.selectNodesByWorkflowIdAndTypes(workFlowId, nodeTypes);
+        for (CompanyWorkflowNode callNode : companyWorkflowNodes) {
+            String lockKey = "sipTask:lock:" + roboticId + ":" + callNode.getNodeKey();
+            boolean locked = redisCache.setIfAbsent(lockKey, "1", 30, TimeUnit.SECONDS);
+            if (!locked) {
+                log.info("createSipTask: 其他线程正在创建SIP任务,等待后重试 - roboticId: {}, nodeKey: {}", roboticId, callNode.getNodeKey());
+                for (int i = 0; i < 20; i++) {
+                    try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
+                    CompanySiptaskInfo existing = companySiptaskInfoMapper.selectSipTaskInfoByTaskIdAndNodeKey(roboticId, callNode.getNodeKey());
+                    if (existing != null && existing.getBatchId() != null) {
+                        return existing.getBatchId();
+                    }
+                }
+                log.warn("createSipTask: 等待超时,尝试直接创建 - roboticId: {}, nodeKey: {}", roboticId, callNode.getNodeKey());
+            }
+            try {
+                CompanySiptaskInfo existingSipTask = companySiptaskInfoMapper.selectSipTaskInfoByTaskIdAndNodeKey(roboticId, callNode.getNodeKey());
+                if (existingSipTask != null && existingSipTask.getBatchId() != null) {
+                    log.info("createSipTask: SIP任务已存在,跳过创建 - roboticId: {}, nodeKey: {}, batchId: {}", roboticId, callNode.getNodeKey(), existingSipTask.getBatchId());
+                    return existingSipTask.getBatchId();
+                }
                 String nodeConfig = callNode.getNodeConfig();
                 AiCallConfigVO callConfigVo = JSONObject.parseObject(nodeConfig, AiCallConfigVO.class);
                 EasyCallCreateTaskParam createParam = new EasyCallCreateTaskParam();
-                // 任务名称:使用任务名称_工作流id_节点key
                 createParam.setBatchName(cloudHostProper.getCompanyName()+"-"+robotic.getName() + "_" + workFlowId + "_" + callNode.getNodeKey());
                 if (null != callConfigVo.getMaxConcurrency()) {
                     createParam.setThreadNum(Long.valueOf(callConfigVo.getMaxConcurrency()));
                 } else {
                     createParam.setThreadNum(3L);
                 }
-                // AI 外呼模式
                 createParam.setTaskType(1);
-                // 外呼线路(网关)
                 createParam.setGatewayId(callConfigVo.getGatewayId());
-                // 大模型底座
                 createParam.setLlmAccountId(callConfigVo.getLlmAccountId());
-                // 音色编号
                 createParam.setVoiceCode(callConfigVo.getVoiceCode());
-                // 音色来源(如未配置默认留空,由 EasyCallCenter365 使用默认值)
                 createParam.setVoiceSource(callConfigVo.getVoiceSource());
-                // 技能组(转人工客服分组,可选)
                 createParam.setGroupId(callConfigVo.getBusiGroupId());
-                // 模型参数
                 createParam.setTtsModels(callConfigVo.getTtsModels());
 
+                if (callConfigVo.getExtensionList() != null && !callConfigVo.getExtensionList().isEmpty()) {
+                    createParam.setAiTransferType("extension");
+                    StringBuilder aiTransferExtNumber = new StringBuilder();
+                    callConfigVo.getExtensionList().forEach(extension -> {
+                        aiTransferExtNumber.append(extension.getExtensionNum()).append(" ");
+                    });
+                    if (null != aiTransferExtNumber && aiTransferExtNumber.length() > 0) {
+                        aiTransferExtNumber.deleteCharAt(aiTransferExtNumber.length() - 1);
+                    }
+                    createParam.setAiTransferExtNumber(aiTransferExtNumber.toString());
+                }
+
                 EasyCallTaskVO task = easyCallService.createTask(createParam, null);
                 if (task == null || task.getBatchId() == null) {
                     log.error("createSipTask: 创建 EasyCall 任务失败 - workflowInstanceId: {}", workFlowId);
@@ -614,11 +639,64 @@ public class CompanyWorkflowEngineImpl implements CompanyWorkflowEngine {
                 sipTaskInfo.setTaskJson(JSONObject.toJSONString(task));
                 companySiptaskInfoMapper.insertCompanySiptaskInfo(sipTaskInfo);
                 return task.getBatchId();
+            } catch (Exception ex) {
+                log.error("创建SIP任务失败:{}", ex);
+                return null;
+            } finally {
+                redisCache.deleteObject(lockKey);
             }
-        } catch (Exception ex) {
-            log.error("创建SIP任务失败:{}", ex);
         }
         return null;
+
+//        try {
+//            List<String> nodeTypes = Arrays.asList(NodeTypeEnum.AI_CALL_TASK.getCode());
+//            CompanyVoiceRobotic robotic = companyVoiceRoboticMapper.selectCompanyVoiceRoboticById(roboticId);
+//            List<CompanyWorkflowNode> companyWorkflowNodes = companyWorkflowNodeMapper.selectNodesByWorkflowIdAndTypes(workFlowId, nodeTypes);
+//            //为所有外呼节点创建任务的对应sip外呼任务
+//            for (CompanyWorkflowNode callNode : companyWorkflowNodes) {
+//                String nodeConfig = callNode.getNodeConfig();
+//                AiCallConfigVO callConfigVo = JSONObject.parseObject(nodeConfig, AiCallConfigVO.class);
+//                EasyCallCreateTaskParam createParam = new EasyCallCreateTaskParam();
+//                // 任务名称:使用任务名称_工作流id_节点key
+//                createParam.setBatchName(cloudHostProper.getCompanyName()+"-"+robotic.getName() + "_" + workFlowId + "_" + callNode.getNodeKey());
+//                if (null != callConfigVo.getMaxConcurrency()) {
+//                    createParam.setThreadNum(Long.valueOf(callConfigVo.getMaxConcurrency()));
+//                } else {
+//                    createParam.setThreadNum(3L);
+//                }
+//                // AI 外呼模式
+//                createParam.setTaskType(1);
+//                // 外呼线路(网关)
+//                createParam.setGatewayId(callConfigVo.getGatewayId());
+//                // 大模型底座
+//                createParam.setLlmAccountId(callConfigVo.getLlmAccountId());
+//                // 音色编号
+//                createParam.setVoiceCode(callConfigVo.getVoiceCode());
+//                // 音色来源(如未配置默认留空,由 EasyCallCenter365 使用默认值)
+//                createParam.setVoiceSource(callConfigVo.getVoiceSource());
+//                // 技能组(转人工客服分组,可选)
+//                createParam.setGroupId(callConfigVo.getBusiGroupId());
+//                // 模型参数
+//                createParam.setTtsModels(callConfigVo.getTtsModels());
+//
+//                EasyCallTaskVO task = easyCallService.createTask(createParam, null);
+//                if (task == null || task.getBatchId() == null) {
+//                    log.error("createSipTask: 创建 EasyCall 任务失败 - workflowInstanceId: {}", workFlowId);
+//                    throw new RuntimeException("EasyCallCenter365 创建任务失败");
+//                }
+//                CompanySiptaskInfo sipTaskInfo = new CompanySiptaskInfo();
+//                sipTaskInfo.setTaskId(roboticId);
+//                sipTaskInfo.setWorkflowId(workFlowId);
+//                sipTaskInfo.setNodeKey(callNode.getNodeKey());
+//                sipTaskInfo.setBatchId(task.getBatchId());
+//                sipTaskInfo.setTaskJson(JSONObject.toJSONString(task));
+//                companySiptaskInfoMapper.insertCompanySiptaskInfo(sipTaskInfo);
+//                return task.getBatchId();
+//            }
+//        } catch (Exception ex) {
+//            log.error("创建SIP任务失败:{}", ex);
+//        }
+//        return null;
     }
 
     /**

+ 8 - 0
fs-service/src/main/java/com/fs/company/vo/AiCallConfigVO.java

@@ -1,7 +1,10 @@
 package com.fs.company.vo;
 
+import com.fs.company.vo.easycall.ExtensionVO;
 import lombok.Data;
 
+import java.util.List;
+
 /**
  * @author MixLiu
  * @date 2026/2/3 10:13
@@ -69,4 +72,9 @@ public class AiCallConfigVO {
 
     /** 最大并发数 */
     private Integer maxConcurrency;
+
+    /**
+     * 分机选择列表
+     */
+    private List<ExtensionVO> extensionList;
 }

+ 12 - 0
fs-service/src/main/java/com/fs/company/vo/CompanyUserQwListVO.java

@@ -160,5 +160,17 @@ public class CompanyUserQwListVO extends BaseEntity {
      */
     private Long accountId;
 
+    private Integer isCustomerVoice;
+
+    /**
+    * 是否绑定语音席位
+    */
+    private Integer isBindVoiceSeat;
+
+    /**
+    * 是否录制音色
+    */
+    private Integer isRecordVoiceSeat;
+
 
 }

+ 12 - 0
fs-service/src/main/java/com/fs/company/vo/SimpleCompanyVo.java

@@ -0,0 +1,12 @@
+package com.fs.company.vo;
+
+import lombok.Data;
+
+@Data
+public class SimpleCompanyVo {
+    /** ID */
+    private Long companyId;
+
+    /** 企业名 */
+    private String companyName;
+}

+ 6 - 0
fs-service/src/main/java/com/fs/company/vo/easycall/EasyCallCreateTaskParam.java

@@ -35,4 +35,10 @@ public class EasyCallCreateTaskParam {
     private Double avgCallEndProcessTimeLen;
 
     private String ttsModels;
+
+    /** aiTransferType */
+    private String aiTransferType;
+
+    /** aiTransferExtNumber */
+    private String aiTransferExtNumber;
 }

+ 34 - 0
fs-service/src/main/java/com/fs/company/vo/easycall/ExtensionVO.java

@@ -0,0 +1,34 @@
+package com.fs.company.vo.easycall;
+
+import lombok.Data;
+
+/**
+ * @author MixLiu
+ * @date 2026/5/20 15:16
+ * @description
+ */
+@Data
+public class ExtensionVO {
+    /**
+     * 分机号码
+     */
+    private String extensionNum;
+
+    /**
+     * 公司id
+     */
+    private Long companyId;
+    /**
+     * 销售id
+     */
+    private Long companyUserId;
+    /**
+     * 销售名称
+     */
+    private String companyUserName;
+    /**
+     * 分机id EasyCall 的分机ID
+     */
+    private Integer extId;
+
+}

+ 5 - 0
fs-service/src/main/java/com/fs/course/domain/FsCoursePlaySourceConfig.java

@@ -117,4 +117,9 @@ public class FsCoursePlaySourceConfig {
     * 备案号
     */
     private String recordNumber;
+
+    /**
+     *  积分商品配置
+     */
+    private String integralGoods;
 }

+ 4 - 0
fs-service/src/main/java/com/fs/course/domain/FsUserCourseCategory.java

@@ -44,5 +44,9 @@ public class FsUserCourseCategory extends BaseEntity
     @Excel(name = "分类类型")
     private Integer cateType;
 
+    /** 小程序首页频道一级标记:1=频道Tab 0=默认长视频等非频道 */
+    @Excel(name = "小程序频道")
+    private Integer appChannelFlag;
+
     private Long userId;
 }

+ 3 - 0
fs-service/src/main/java/com/fs/course/mapper/FsCoursePlaySourceConfigMapper.java

@@ -19,4 +19,7 @@ public interface FsCoursePlaySourceConfigMapper extends BaseMapper<FsCoursePlayS
 
     @DataSource(DataSourceType.SLAVE)
     FsCoursePlaySourceConfig selectCoursePlaySourceConfigByAppId(String appId);
+
+    @DataSource(DataSourceType.SLAVE)
+    String selectCoursePlaySourceConfigByAppIdGoods(@Param("appId") String appId);
 }

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

@@ -769,7 +769,12 @@ public interface FsCourseWatchLogMapper extends BaseMapper<FsCourseWatchLog> {
 
     List<FsSopMyCourseH5LinkVO> getSopCourseH5StudyListByQwExId(@Param("qwExternalId") Long qwExternalId);
 
-    @Select("select * from fs_course_watch_log where user_id=#{param.fsUserId} and company_user_id=#{param.companyUserId} and course_id=#{param.courseId} and video_id=#{param.videoId} and project=#{param.projectId} and period_id=#{param.periodId} limit 1")
+    @Select("<script>" +
+            "select * from fs_course_watch_log where user_id=#{param.fsUserId} and company_user_id=#{param.companyUserId} and course_id=#{param.courseId} and video_id=#{param.videoId} " +
+            "<if test='param.projectId != null'>and project=#{param.projectId} </if>" +
+            "<if test='param.periodId != null'>and period_id=#{param.periodId} </if>" +
+            "limit 1" +
+            "</script>")
     FsCourseWatchLog selectFsCourseWatchLogWithUCCV(@Param("param") FsUserCourseVideoRemainTimeParam param);
 
     /**

+ 27 - 0
fs-service/src/main/java/com/fs/course/mapper/FsUserCourseStudyMapper.java

@@ -80,6 +80,33 @@ public interface FsUserCourseStudyMapper
             " order by s.study_id  "+
             "</script>"})
     List<FsUserCourseStudyListUVO> selectFsUserCourseStudyListUVO(@Param("maps")FsUserCourseListUParam param);
+        /* 备用更新
+                "select s.*,c.img_url,c.description as courseDesc from fs_user_course_study s " +
+            "left join fs_user_course c on c.course_id = s.course_id " +
+            "where c.is_del = 0 and ifnull(c.is_private, 0) = 0 " +
+            "<if test='maps.userId != null'> " +
+            "and s.user_id = #{maps.userId} " +
+            "</if>" +
+            "<if test='maps.courseType != null and maps.courseType != 0'> " +
+            "and c.cate_id = #{maps.courseType} " +
+            "and exists ( " +
+            "  select 1 from fs_user_course_category ch " +
+            "  where ch.cate_id = #{maps.courseType} and ch.pid = 0 and ch.is_del = 0 " +
+            "  and ifnull(ch.app_channel_flag, 0) = 1 " +
+            ") " +
+            "</if>" +
+            "<if test='maps.courseType == null or maps.courseType == 0'> " +
+            "and exists ( " +
+            "  select 1 from fs_user_course_category ch " +
+            "  where ch.cate_id = c.cate_id and ch.pid = 0 and ch.is_del = 0 " +
+            "  and ifnull(ch.app_channel_flag, 0) = 0 " +
+            ") " +
+            "</if>" +
+            "order by s.study_id " +
+            "</script>"})
+
+
+    * */
 
     @Select("select * from fs_user_course_study where user_id = #{userId} and course_id = #{courseId} limit 1")
     FsUserCourseStudy selectFsUserCourseStudyByAddStudy(@Param("userId")Long userId,@Param("courseId")Long courseId);

+ 5 - 0
fs-service/src/main/java/com/fs/course/param/FsCoursePlaySourceConfigCreateParam.java

@@ -65,4 +65,9 @@ public class FsCoursePlaySourceConfigCreateParam {
     * 备案号
     */
     private String recordNumber;
+
+    /**
+    *  积分商品配置
+    */
+    private String integralGoods;
 }

+ 5 - 0
fs-service/src/main/java/com/fs/course/param/FsCoursePlaySourceConfigEditParam.java

@@ -66,4 +66,9 @@ public class FsCoursePlaySourceConfigEditParam {
     * 备案号
     */
     private String recordNumber;
+
+    /**
+     *  积分商品配置
+     */
+    private String integralGoods;
 }

+ 3 - 3
fs-service/src/main/java/com/fs/course/param/FsUserCourseCategoryAppQueryParam.java

@@ -36,8 +36,8 @@ public class FsUserCourseCategoryAppQueryParam implements Serializable {
     @ApiModelProperty(value = "分类类型:1=公域课程分类,0=普通;不传时接口默认按1(公域)查询")
     private Integer cateType;
 
-    @ApiModelProperty(value = "原乡行标签:不传或0=只统计/展示下挂课程中无「原乡行」标签的;1=只统计/展示含「原乡行」标签的课程", example = "0")
-    private Integer yxxTag;
+    @ApiModelProperty(value = "频道一级分类ID:0或不传=全部公域;>0 查对应频道", example = "0")
+    private Long yxxTag;
 
     @ApiModelProperty(value = "首页排序 1 是首页,后端添加标签排序,0不是首页", example = "0")
     private Integer homePage;
@@ -50,7 +50,7 @@ public class FsUserCourseCategoryAppQueryParam implements Serializable {
         int pn = pageNum != null ? pageNum : 1;
         int ps = pageSize != null ? pageSize : 10;
         int ct = cateType != null ? cateType : 1;
-        int yxx = yxxTag != null ? yxxTag : 0;
+        String yxx = yxxTag != null ? String.valueOf(yxxTag) : "null";
         int home = homePage != null ? homePage : 0;
         return pn + "|" + ps + "|" + n(cateName) + "|" + n(pid) + "|" + n(isShow) + "|" + ct + "|" + yxx  + "|" + home;
     }

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

@@ -22,7 +22,9 @@ public class FsUserCourseListUParam  implements Serializable {
 
     Integer isBest;
 
-    Integer courseType;
+    /** 频道一级 cateId:0 或不传=非频道(app_channel_flag=0)公域课学习记录;>0=对应频道 */
+    @ApiModelProperty(value = "频道一级分类ID:0或不传=默认公域;>0=频道cateId", example = "0")
+    Long courseType;
 
     @ApiModelProperty(value = "页码,默认为1")
     private Integer pageNum =1;

+ 3 - 3
fs-service/src/main/java/com/fs/course/param/FsUserCoursePublicAppQueryParam.java

@@ -36,14 +36,14 @@ public class FsUserCoursePublicAppQueryParam implements Serializable {
     @ApiModelProperty(value = "推荐位:1首页顶部 2商城首页 3首页长视频瀑布流;不传则不限")
     private Integer recommendSlot;
 
-    @ApiModelProperty(value = "原乡行标签:不传或0=只查无「原乡行」标签的公域课;1=只查带「原乡行」标签的", example = "0")
-    private Integer yxxTag;
+    @ApiModelProperty(value = "频道一级分类ID:0或不传=全部公域;>0 查对应频道", example = "0")
+    private Long yxxTag;
 
     /** 公域课列表缓存 key,与业务默认分页一致 */
     public String appListCacheKey() {
         int pn = pageNum != null ? pageNum : 1;
         int ps = pageSize != null ? pageSize : 10;
-        int yxx = yxxTag != null ? yxxTag : 0;
+        String yxx = yxxTag != null ? String.valueOf(yxxTag) : "null";
         return pn + "|" + ps + "|" + n(cateId) + "|" + n(subCateId) + "|" + n(keyword) + "|" + n(recommendSlot) + "|" + yxx;
     }
 

+ 1 - 0
fs-service/src/main/java/com/fs/course/service/IFsCoursePlaySourceConfigService.java

@@ -22,4 +22,5 @@ public interface IFsCoursePlaySourceConfigService extends IService<FsCoursePlayS
      * @return
      */
     FsCoursePlaySourceConfig selectCoursePlaySourceConfigByAppId(String appId);
+    String selectCoursePlaySourceConfigByAppIdGoods(String appId);
 }

+ 5 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsCoursePlaySourceConfigServiceImpl.java

@@ -35,4 +35,9 @@ public class FsCoursePlaySourceConfigServiceImpl extends ServiceImpl<FsCoursePla
     public FsCoursePlaySourceConfig selectCoursePlaySourceConfigByAppId(String appId) {
         return baseMapper.selectCoursePlaySourceConfigByAppId(appId);
     }
+
+    @Override
+    public String selectCoursePlaySourceConfigByAppIdGoods(String appId) {
+        return baseMapper.selectCoursePlaySourceConfigByAppIdGoods(appId);
+    }
 }

+ 1 - 1
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseServiceImpl.java

@@ -872,7 +872,7 @@ public class FsUserCourseServiceImpl implements IFsUserCourseService
 //            String sortLink = domainName + link.getRealLink().replace("/#","");
 //            return R.ok().put("url", sortLink).put("link", random).put("linkId", link.getLinkId());
             //没人用我先注释了,手动发课 直接用 链接带参数
-            if (CloudHostUtils.hasCloudHostName("中康","蒙牛", "鸿森堂")){
+            if (CloudHostUtils.hasCloudHostName("中康","蒙牛", "鸿森堂","北京卓美")){
                 String domainName = getDomainName(param.getCompanyUserId(), config);
                 String encodedCourseJson = URLEncoder.encode(courseJson, "UTF-8");
                 String sortLink = domainName +appRealLink+ encodedCourseJson;

+ 28 - 3
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java

@@ -3305,6 +3305,14 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
             return R.error().put("msg", "企业不存在,请联系管理员");
         }
 
+        String  miniprogramPicUrl="";
+        String sidebarUrl = redisCache.getCacheObject("SidebarCompany:" + qwUser.getCompanyId());
+        if (!StringUtil.strIsNullOrEmpty(sidebarUrl)){
+            miniprogramPicUrl=sidebarUrl;
+        }else {
+            miniprogramPicUrl=config.getSidebarImageUrl();
+        }
+
         //看课记录
         addWatchLogIfNeeded(param.getVideoId(), param.getCourseId(), param.getFsUserId(), qwUser, param.getExternalUserId(),2);
 
@@ -3314,7 +3322,7 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
         JSONObject news = new JSONObject(true);
         news.put("miniprogramAppid", qwCompany.getMiniAppId());
         news.put("miniprogramTitle", param.getTitle());
-        news.put("miniprogramPicUrl", config.getSidebarImageUrl());
+        news.put("miniprogramPicUrl", miniprogramPicUrl);
         news.put("miniprogramPage", linkByMiniApp);
 
         return R.ok().put("data", news);
@@ -3343,6 +3351,15 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
             return R.error().put("msg", "课程默认配置为空,请联系管理员");
         }
 
+
+        String  miniprogramPicUrl="";
+        String sidebarUrl = redisCache.getCacheObject("SidebarCompany:" + qwUser.getCompanyId());
+        if (!StringUtil.strIsNullOrEmpty(sidebarUrl)){
+            miniprogramPicUrl=sidebarUrl;
+        }else {
+            miniprogramPicUrl=config.getSidebarImageUrl();
+        }
+
         //域名
         String domainName = companyUserMapper.selectDomainByUserId(qwUser.getCompanyUserId());
         if (StringUtils.isEmpty(domainName)) {
@@ -3358,7 +3375,7 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
         JSONObject news = new JSONObject(true); // true 表示保持字段顺序
         news.put("linkTitle", param.getTitle());
         news.put("linkDescribe", param.getTitle());
-        news.put("linkImageUrl", config.getSidebarImageUrl());
+        news.put("linkImageUrl", miniprogramPicUrl);
         news.put("linkUrl", linkByCartLink);
 
         return R.ok().put("data", news);
@@ -3600,13 +3617,21 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
             return R.error().put("msg", "企业不存在,请联系管理员");
         }
 
+        String  miniprogramPicUrl="";
+        String sidebarUrl = redisCache.getCacheObject("SidebarCompany:" + qwUser.getCompanyId());
+        if (!StringUtil.strIsNullOrEmpty(sidebarUrl)){
+            miniprogramPicUrl=sidebarUrl;
+        }else {
+            miniprogramPicUrl=config.getSidebarImageUrl();
+        }
+
         //生成小程序链接
         String linkByMiniApp = createLinkByMiniApp(new Date(), param.getCourseId(), param.getVideoId(), qwUser, param.getExternalUserId(), 2, null, 1);
 
         JSONObject news = new JSONObject(true);
         news.put("miniprogramAppid", qwCompany.getMiniAppId());
         news.put("miniprogramTitle", param.getTitle());
-        news.put("miniprogramPicUrl", config.getSidebarImageUrl());
+        news.put("miniprogramPicUrl", miniprogramPicUrl);
         news.put("miniprogramPage", linkByMiniApp);
 
         return R.ok().put("data", news);

+ 5 - 0
fs-service/src/main/java/com/fs/course/vo/FsCoursePlaySourceConfigVO.java

@@ -76,4 +76,9 @@ public class FsCoursePlaySourceConfigVO {
      * 备案号
      */
     private String recordNumber;
+
+    /**
+     *  积分商品配置
+     */
+    private String integralGoods;
 }

+ 11 - 0
fs-service/src/main/java/com/fs/erp/service/IErpOrderService.java

@@ -7,6 +7,7 @@ import com.fs.his.domain.FsStoreOrder;
 import com.fs.hisStore.domain.FsStoreOrderScrm;
 import com.fs.live.domain.LiveOrder;
 
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
@@ -45,6 +46,16 @@ public interface IErpOrderService
      */
     ErpOrderQueryResponse batchGetLiveOrder(List<LiveOrder> orderList);
 
+    /**
+     * 批量查询商城订单(聚水潭等)
+     */
+    default ErpOrderQueryResponse batchGetScrmOrder(List<FsStoreOrderScrm> orderList) {
+        ErpOrderQueryResponse response = new ErpOrderQueryResponse();
+        response.setOrders(Collections.emptyList());
+        response.setSuccess(true);
+        return response;
+    }
+
     //  出库查询接口 目前只实现了 旺店通
     default Map<String, Object> stockOutOrderQueryTrade(ErpOrderQueryRequert param)
     {

+ 105 - 3
fs-service/src/main/java/com/fs/erp/service/impl/JSTErpOrderServiceImpl.java

@@ -1006,10 +1006,13 @@ public class JSTErpOrderServiceImpl implements IErpOrderService {
     }
 
     private ErpOrderQuery convertToErpOrderQueryScrm(OrderQueryResponseDTO.Order order) {
-        ErpOrderQuery erpOrder = new ErpOrderQuery();
-
         FsStoreOrderScrm fsStoreOrder = fsStoreOrderService.selectFsStoreOrderScrmByOrderCode(order.getSoId());
-        Asserts.notNull(fsStoreOrder,"该订单号没有找到!");
+        Asserts.notNull(fsStoreOrder, "该订单号没有找到!");
+        return convertToErpOrderQueryScrm(order, fsStoreOrder);
+    }
+
+    private ErpOrderQuery convertToErpOrderQueryScrm(OrderQueryResponseDTO.Order order, FsStoreOrderScrm fsStoreOrder) {
+        ErpOrderQuery erpOrder = new ErpOrderQuery();
 
         // 设置基本订单信息
         erpOrder.setCode(order.getSoId());
@@ -1281,6 +1284,105 @@ public class JSTErpOrderServiceImpl implements IErpOrderService {
 
     }
 
+    /**
+     * 批量查询商城订单(聚水潭)
+     */
+    @Override
+    public ErpOrderQueryResponse batchGetScrmOrder(List<FsStoreOrderScrm> orderList) {
+        ErpOrderQueryResponse response = new ErpOrderQueryResponse();
+        if (CollectionUtils.isEmpty(orderList)) {
+            response.setOrders(Collections.emptyList());
+            response.setSuccess(true);
+            return response;
+        }
+
+        Map<String, FsStoreOrderScrm> orderByExtendId = orderList.stream()
+                .filter(order -> StringUtils.isNotEmpty(order.getExtendOrderId()))
+                .collect(Collectors.toMap(FsStoreOrderScrm::getExtendOrderId, order -> order, (a, b) -> a));
+        Map<String, FsStoreOrderScrm> orderByOrderCode = orderList.stream()
+                .filter(order -> StringUtils.isNotEmpty(order.getOrderCode()))
+                .collect(Collectors.toMap(FsStoreOrderScrm::getOrderCode, order -> order, (a, b) -> a));
+
+        List<Long> extendOrderIds = orderByExtendId.keySet().stream()
+                .map(Long::valueOf)
+                .collect(Collectors.toList());
+        if (CollectionUtils.isEmpty(extendOrderIds)) {
+            response.setOrders(Collections.emptyList());
+            response.setSuccess(true);
+            return response;
+        }
+
+        List<ErpOrderQuery> erpOrders = new ArrayList<>();
+        int batchSize = 100;
+        for (int i = 0; i < extendOrderIds.size(); i += batchSize) {
+            List<Long> batch = extendOrderIds.subList(i, Math.min(i + batchSize, extendOrderIds.size()));
+            try {
+                OrderQueryRequestDTO requestDTO = new OrderQueryRequestDTO();
+                requestDTO.setOIds(batch);
+                OrderQueryResponseDTO query = jstErpHttpService.query(requestDTO);
+                if (query.getOrders() == null || query.getOrders().isEmpty()) {
+                    continue;
+                }
+                for (OrderQueryResponseDTO.Order jstOrder : query.getOrders()) {
+                    appendScrmErpOrderFromJst(jstOrder, orderByExtendId, orderByOrderCode, erpOrders);
+                }
+            } catch (Exception e) {
+                log.error("聚水潭批量查询商城订单失败,batchSize={}", batch.size(), e);
+            }
+        }
+
+        response.setOrders(erpOrders);
+        response.setSuccess(true);
+        return response;
+    }
+
+    private void appendScrmErpOrderFromJst(OrderQueryResponseDTO.Order jstOrder,
+                                           Map<String, FsStoreOrderScrm> orderByExtendId,
+                                           Map<String, FsStoreOrderScrm> orderByOrderCode,
+                                           List<ErpOrderQuery> erpOrders) {
+        if (jstOrder == null) {
+            return;
+        }
+        if (ErpQueryOrderStatusEnum.MERGED.getCode().equals(jstOrder.getStatus())
+                && StringUtils.isNotEmpty(jstOrder.getLinkOId())) {
+            OrderQueryResponseDTO mergedWrapper = new OrderQueryResponseDTO();
+            mergedWrapper.setOrders(Collections.singletonList(jstOrder));
+            OrderQueryResponseDTO mergeQuery = followMergedJstQueryResponse(mergedWrapper, 1);
+            if (mergeQuery != null && !CollectionUtils.isEmpty(mergeQuery.getOrders())) {
+                for (OrderQueryResponseDTO.Order mergedOrder : mergeQuery.getOrders()) {
+                    FsStoreOrderScrm localOrder = resolveScrmOrder(mergedOrder, orderByExtendId, orderByOrderCode);
+                    if (localOrder != null) {
+                        erpOrders.add(convertToErpOrderQueryScrm(mergedOrder, localOrder));
+                    }
+                }
+                persistOuterOiIdFromJstMergedOrder(jstOrder);
+            }
+            return;
+        }
+        FsStoreOrderScrm localOrder = resolveScrmOrder(jstOrder, orderByExtendId, orderByOrderCode);
+        if (localOrder != null) {
+            erpOrders.add(convertToErpOrderQueryScrm(jstOrder, localOrder));
+        }
+    }
+
+    private FsStoreOrderScrm resolveScrmOrder(OrderQueryResponseDTO.Order jstOrder,
+                                              Map<String, FsStoreOrderScrm> orderByExtendId,
+                                              Map<String, FsStoreOrderScrm> orderByOrderCode) {
+        if (jstOrder == null) {
+            return null;
+        }
+        if (StringUtils.isNotEmpty(jstOrder.getSoId())) {
+            FsStoreOrderScrm byCode = orderByOrderCode.get(jstOrder.getSoId());
+            if (byCode != null) {
+                return byCode;
+            }
+        }
+        if (jstOrder.getOId() != null) {
+            return orderByExtendId.get(String.valueOf(jstOrder.getOId()));
+        }
+        return null;
+    }
+
     /**
      * 批量查询直播订单
      * @param orderList 订单列表

+ 2 - 0
fs-service/src/main/java/com/fs/erp/utils/WeizouApiClient.java

@@ -171,6 +171,8 @@ public class WeizouApiClient {
                 .addHeader("token", token) // 假设使用Bearer Token
                 // TODO: 根据文档添加其他必要的Headers
                 .build();
+//        log.info("订单推送请求参数打印:"+request);
+//        log.info("订单推送请求体打印:"+orderData);
 
         try (Response response = httpClient.newCall(request).execute()) {
             if (!response.isSuccessful()) {

+ 3 - 0
fs-service/src/main/java/com/fs/fastGpt/mapper/FastGptChatSessionMapper.java

@@ -125,4 +125,7 @@ public interface FastGptChatSessionMapper
     @Update("UPDATE fastgpt_chat_session  SET is_artificial = 0 WHERE is_artificial = 1  AND TIMESTAMPDIFF(MINUTE, last_time, NOW()) > 10;")
     void updateFastGptChatSessionByIsReply();
 
+    @Update("UPDATE fastgpt_chat_session SET is_artificial = 0 WHERE is_artificial = 1 AND TIMESTAMPDIFF(MINUTE, last_time, NOW()) > #{time}")
+    void updateFastGptChatSessionByIsReplyWithTime(@Param("time") Integer time);
+
 }

+ 48 - 21
fs-service/src/main/java/com/fs/fastGpt/service/impl/AiHookServiceImpl.java

@@ -75,6 +75,7 @@ import org.apache.commons.lang3.StringUtils;
 import org.jetbrains.annotations.Nullable;
 import org.json.JSONObject;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
@@ -191,6 +192,9 @@ public class AiHookServiceImpl implements AiHookService {
     @Autowired
     private FastgptChatQuestionCollectServiceImpl fastgptChatQuestionCollectServiceImpl;
 
+    @Value("${cloud_host.company_name}")
+    private String signProjectName;
+
 
     /** Ai半小时未回复提醒 **/
     /**
@@ -399,7 +403,8 @@ public class AiHookServiceImpl implements AiHookService {
             return R.ok();
         }
         log.info("数据:{}", qwUserId);
-        QwUser user = qwUserMapper.selectQwUserById(qwUserId);
+//        QwUser user = qwUserMapper.selectQwUserById(qwUserId);
+        QwUser user = qwUserMapper.selectQwUserByIdCompanyUser(qwUserId);
         //查询接收人
         if(user==null){
             log.error("查询接收人为空");
@@ -665,40 +670,62 @@ public class AiHookServiceImpl implements AiHookService {
                     addUserInfo(contentKh, qwExternalContacts.getId(),fastGptChatSession);
 
                     if(isNewVersion){
-                        //发送图片消息
-                        sendImgMsg(contentKh,sender,uid,serverId);
-                        sendAiMsg(content,sender,uid,serverId);
-                    } else {
-                        if (type==16){
-                            sendAiVoiceMsg(content,sender,uid,serverId,user);
-                        }else {
+                        if ("北京卓美".equals(signProjectName) && type == 16) {
+                            sendAiVoiceMsg(content, sender, uid, serverId, user);
+                        } else {
+                            //发送图片消息
+                            sendImgMsg(contentKh,sender,uid,serverId);
                             sendAiMsg(content,sender,uid,serverId);
                         }
+                    } else {
+
+                        if (type == 16) {
+                            if (user.getIsCustomerVoice() != null && user.getIsCustomerVoice() == 1) {
+                                sendAiVoiceMsg(content, sender, uid, serverId, user);
+                            } else {
+                                sendAiMsg(content,sender,uid,serverId);
+                            }
+                        } else {
+                            sendAiMsg(content, sender, uid, serverId);
+                        }
                     }
 
                 }else {
-                    String sa = contentKh.replaceAll("】\n", "】").replaceAll("\n【", "【");
-                    String nr = replace(sa);
-                    String[] split = nr.split("\n");
-                    if (split.length>6){
-                        log.info("回复长度异常:"+role.getRoleId()+":"+qwExternalContacts.getName());
-                        notifyArtificial(fastGptChatSession.getSessionId(),user,qwExternalContacts.getName(),"回复长度异常",qwExternalContacts.getId(),sender);
-                        return R.ok();
+                    if(!isNewVersion){
+                        String sa = contentKh.replaceAll("】\n", "】").replaceAll("\n【", "【");
+                        String nr = replace(sa);
+                        String[] split = nr.split("\n");
+                        if (split.length>6){
+                            log.info("回复长度异常:"+role.getRoleId()+":"+qwExternalContacts.getName());
+                            notifyArtificial(fastGptChatSession.getSessionId(),user,qwExternalContacts.getName(),"回复长度异常",qwExternalContacts.getId(),sender);
+                            return R.ok();
+                        }
                     }
+
                     List<String> countList = countString(content);
                     //新增用户信息
                     addUserInfo(contentKh, qwExternalContacts.getId(),fastGptChatSession);
                     for (String msg : countList) {
                         if(isNewVersion){
-                            //发送图片消息
-                            sendImgMsg(contentKh,sender,uid,serverId);
-                            sendAiMsg(msg,sender,uid,serverId);
-                        } else {
-                            if (type==16){
+                            if ("北京卓美".equals(signProjectName) && type == 16) {
                                 sendAiVoiceMsg(msg,sender,uid,serverId,user);
-                            }else {
+                            } else {
+                                //发送图片消息
+                                sendImgMsg(contentKh,sender,uid,serverId);
                                 sendAiMsg(msg,sender,uid,serverId);
                             }
+                        } else {
+
+                            if (type == 16) {
+                                if (user.getIsCustomerVoice() != null && user.getIsCustomerVoice() == 1) {
+                                    sendAiVoiceMsg(msg, sender, uid, serverId, user);
+                                } else {
+                                    sendAiMsg(msg,sender,uid,serverId);
+                                }
+                            } else {
+                                sendAiMsg(msg, sender, uid, serverId);
+                            }
+
                         }
                         try {
                             Thread.sleep(10000);

+ 30 - 0
fs-service/src/main/java/com/fs/gtPush/service/impl/uniPush2ServiceImpl.java

@@ -277,6 +277,36 @@ public class uniPush2ServiceImpl implements uniPush2Service {
         return openIMService.sendCourse(fsUserId, companyUserId, appLinkUrl, linkDescribe, linkImageUrl, cropId);
     }
 
+    @Override
+    public OpenImResponseDTO pushSopAppLinkMsgByLiveIM(String cropId, String linkTile, String linkDescribe, Long liveId, String link, Long companyUserId, Long fsUserId, Long companyId) throws JsonProcessingException {
+        if (companyUserId == null || fsUserId == null || fsUserId == 0) {
+            OpenImResponseDTO errorResponse = new OpenImResponseDTO();
+            errorResponse.setErrCode(-1);
+            errorResponse.setErrMsg("参数错误:用户未绑定销售");
+            errorResponse.setErrDlt("缺少必要参数");
+            return errorResponse;
+        }
+
+        FsUser fsUser = fsUserMapper.selectFsUserByUserId(fsUserId);
+        if (fsUser == null) {
+            OpenImResponseDTO errorResponse = new OpenImResponseDTO();
+            errorResponse.setErrCode(-2);
+            errorResponse.setErrMsg("未找到对应的用户信息");
+            errorResponse.setErrDlt("用户ID: " + fsUserId);
+            return errorResponse;
+        }
+
+        if (StringUtils.isEmpty(fsUser.getHistoryApp())) {
+            OpenImResponseDTO errorResponse = new OpenImResponseDTO();
+            errorResponse.setErrCode(-3);
+            errorResponse.setErrMsg("用户未绑定APP");
+            errorResponse.setErrDlt("用户历史APP信息为空");
+            return errorResponse;
+        }
+
+        return openIMService.sendLive(fsUserId, companyUserId, link, linkTile, liveId, cropId, companyId);
+    }
+
     /**
      * 专用于 pushIm 的参数构建方法
      */

+ 2 - 0
fs-service/src/main/java/com/fs/gtPush/service/uniPush2Service.java

@@ -16,4 +16,6 @@ public interface uniPush2Service {
     void pushIm(Long userId, Long businessId, String purl, String title, String content, Float type, Integer desType,String imJsonString);
 
     OpenImResponseDTO pushSopAppLinkMsgByExternalIMV2(String cropId, String linkTitle, String linkDescribe, String linkImageUrl, String appLinkUrl, Long companyUserId, Long fsUserId) throws JsonProcessingException;
+
+    OpenImResponseDTO pushSopAppLinkMsgByLiveIM(String cropId, String linkTile, String linkDescribe, Long liveId, String link, Long companyUserId, Long fsUserId, Long companyId) throws JsonProcessingException;
 }

+ 8 - 0
fs-service/src/main/java/com/fs/his/config/AppRedPacketConfig.java

@@ -45,5 +45,13 @@ public class AppRedPacketConfig {
      */
     private String publicKeyPath;
 
+    /**
+    * 普通的回调地址
+    */
     private String notifyUrl;
+
+    /**
+    * 直播的回调地址
+    */
+    private String LiveNotifyUrl;
 }

+ 11 - 2
fs-service/src/main/java/com/fs/his/domain/FsCourseCouponUser.java

@@ -1,6 +1,9 @@
 package com.fs.his.domain;
 
 import java.util.Date;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.TableField;
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.fs.common.annotation.Excel;
@@ -15,8 +18,7 @@ import lombok.EqualsAndHashCode;
  * @date 2026-05-13
  */
 @Data
-@EqualsAndHashCode(callSuper = true)
-public class FsCourseCouponUser extends BaseEntity{
+public class FsCourseCouponUser {
 
     /** $column.columnComment */
     private Long id;
@@ -48,5 +50,12 @@ public class FsCourseCouponUser extends BaseEntity{
      */
     private Long logId;
 
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @TableField(fill = FieldFill.INSERT)
+    private Date createTime;
 
+    /** 更新时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private Date updateTime;
 }

+ 4 - 1
fs-service/src/main/java/com/fs/his/domain/FsIntegralOrder.java

@@ -72,7 +72,8 @@ public class FsIntegralOrder
     private Integer isPay;
 
     /** 支付时间 */
-    @Excel(name = "支付时间")
+    @Excel(name = "支付时间",width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private LocalDateTime payTime;
 
     /** 支付类型 1积分 2现金 3积分+现金 */
@@ -124,7 +125,9 @@ public class FsIntegralOrder
 
     private String remark;
 
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private Date createTime;
+
     @TableField(exist = false)
     private Date updateTime;
     @TableField(exist = false)

+ 1 - 1
fs-service/src/main/java/com/fs/his/domain/FsIntegralOrderLogs.java

@@ -22,7 +22,7 @@ import java.time.LocalDateTime;
 public class FsIntegralOrderLogs{
 
     /** $column.columnComment */
-    private String logsId;
+    private Long logsId;
 
     /** 订单id */
     @Excel(name = "订单id")

+ 5 - 0
fs-service/src/main/java/com/fs/his/domain/FsPackage.java

@@ -112,4 +112,9 @@ public class FsPackage extends BaseEntity
 
     @Excel(name = "AI回复名称")
     private String showName;
+
+    /**
+     * 分成比例(0-100)
+     */
+    private Integer shareRate;
 }

+ 40 - 0
fs-service/src/main/java/com/fs/his/domain/FsShareAmountDetail.java

@@ -0,0 +1,40 @@
+package com.fs.his.domain;
+
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.math.BigDecimal;
+
+/**
+ * 分账明细对象 fs_share_amount_detail
+ *
+ * @author fs
+ * @date 2025-12-02
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class FsShareAmountDetail extends BaseEntity{
+
+    /** $column.columnComment */
+    private Long id;
+
+    /** 订单id */
+    @Excel(name = "订单id")
+    private Long orderId;
+
+    /** 订单号 */
+    @Excel(name = "订单号")
+    private String orderCode;
+
+    /** 商户id */
+    @Excel(name = "商户id")
+    private Long merchantId;
+
+    /** 分账金额 */
+    @Excel(name = "分账金额")
+    private BigDecimal shareAmount;
+
+
+}

+ 20 - 0
fs-service/src/main/java/com/fs/his/domain/FsStorePayment.java

@@ -102,6 +102,26 @@ public class FsStorePayment extends BaseEntity
 
     private Long merConfigId;
 
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    private Date shareTime;
+
+    private String shareDate;
+
+    public Date getShareTime() {
+        return shareTime;
+    }
+    public void setShareTime(Date shareTime) {
+        this.shareTime = shareTime;
+    }
+
+    public String getShareDate() {
+        return shareDate;
+    }
+
+    public void setShareDate(String shareDate) {
+        this.shareDate = shareDate;
+    }
+
     public Long getMerConfigId() {
         return merConfigId;
     }

+ 8 - 0
fs-service/src/main/java/com/fs/his/domain/FsUser.java

@@ -273,4 +273,12 @@ public class FsUser extends BaseEntity
     public void setNickname(String nickname) {
         this.nickname = nickname;
     }
+
+    public Integer getIsOfficialAccountAuth() {
+        return isOfficialAccountAuth;
+    }
+
+    public void setIsOfficialAccountAuth(Integer isOfficialAccountAuth) {
+        this.isOfficialAccountAuth = isOfficialAccountAuth;
+    }
 }

+ 3 - 0
fs-service/src/main/java/com/fs/his/dto/PayloadDTO.java

@@ -1,6 +1,7 @@
 package com.fs.his.dto;
 
 import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fs.live.domain.Live;
 import lombok.Data;
 
 import java.io.Serializable;
@@ -44,6 +45,8 @@ public class PayloadDTO implements Serializable {
 
         private String id;
 
+        private Long liveId;
+        private Live live;
     }
 
 }

+ 2 - 1
fs-service/src/main/java/com/fs/his/enums/FsUserOperationEnum.java

@@ -10,7 +10,8 @@ public enum FsUserOperationEnum {
     STUDY("学习课程",5),
     ANSWER("答题",6),
     SENDREWARD("发送奖励",7),
-    USERIP("获取用户IP",7);
+    USERIP("获取用户IP",8),
+    SENDLIVEREWARD("发送直播红包奖励",9);
 
     private final String label;
     private final Integer value;

+ 2 - 1
fs-service/src/main/java/com/fs/his/enums/ShipperCodeEnum.java

@@ -6,7 +6,8 @@ import lombok.Getter;
 @Getter
 @AllArgsConstructor
 public enum ShipperCodeEnum {
-    SF("SF","顺丰速运");
+    SF("SF","顺丰速运"),
+    ZTO("ZTO","中通");
 
     private String value;
     private String desc;

+ 15 - 0
fs-service/src/main/java/com/fs/his/mapper/FsCityMapper.java

@@ -4,6 +4,7 @@ import java.util.List;
 import com.fs.his.domain.FsCity;
 import com.fs.his.vo.CitysAreaVO;
 import com.github.binarywang.wxpay.bean.result.WxPayRefundV3Result;
+import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 
 /**
@@ -75,4 +76,18 @@ public interface FsCityMapper
 
     @Select("select * from fs_city where is_show=1")
     List<FsCity> selectFsCitys();
+
+    @Select({"<script> " +
+            "select city_id from fs_city"+
+            "<where>  \n" +
+            "            <if test=\"cityName != null  and cityName != ''\"> and city_name like concat('%', #{cityName}, '%')</if>\n" +
+            "            <if test=\"citySname != null  and citySname != ''\"> and city_sname like concat('%', #{citySname}, '%')</if>\n" +
+            "            <if test=\"level != null \"> and 'level' = #{citySname}</if>\n" +
+            "        </where>"+
+            " limit 1"+
+            "</script>"})
+    String selectByFsCity(FsCity city);
+
+    @Select("SELECT city_id FROM fs_city WHERE city_sname LIKE CONCAT('%', #{city}, '%', #{district}, '%')")
+    String likeByCityDistrict(@Param("city") String city, @Param("district") String district);
 }

+ 11 - 0
fs-service/src/main/java/com/fs/his/mapper/FsCourseCouponUserMapper.java

@@ -3,6 +3,8 @@ package com.fs.his.mapper;
 import java.util.List;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.fs.his.domain.FsCourseCouponUser;
+import com.fs.his.vo.CourseCouponUserListUVO;
+import com.fs.his.vo.FsCourseCouponUserRecordVO;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 
@@ -66,4 +68,13 @@ public interface FsCourseCouponUserMapper extends BaseMapper<FsCourseCouponUser>
 
     @Select("SELECT * FROM fs_course_coupon_user WHERE log_id = #{logId}")
     FsCourseCouponUser selectByLogId(@Param("logId") Long logId);
+    @Select("SELECT cu.*,c.title couponName \n" +
+            "FROM `fs_course_coupon_user` cu \n" +
+            "LEFT JOIN fs_course_coupon c ON cu.coupon_id = c.id \n" +
+            "WHERE cu.user_id = #{param.userId} \n" +
+            "AND cu.`status` = #{param.status}")
+    List<CourseCouponUserListUVO> selectCourseCouponUserList(@Param("param") FsCourseCouponUser fsCourseCouponUser);
+
+    @Select("SELECT cu.*,c.title couponName FROM `fs_course_coupon_user` cu LEFT JOIN fs_course_coupon c ON cu.coupon_id = c.id WHERE cu.log_id = #{param.logId}")
+    List<FsCourseCouponUserRecordVO> selectCourseCouponUserRecordList(@Param("param")FsCourseCouponUser courseCouponUser);
 }

+ 65 - 0
fs-service/src/main/java/com/fs/his/mapper/FsShareAmountDetailMapper.java

@@ -0,0 +1,65 @@
+package com.fs.his.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.his.domain.FsShareAmountDetail;
+import com.fs.his.param.FsShareAmountDetailParam;
+import com.fs.his.vo.FsShareAmountDetailVO;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * 分账明细Mapper接口
+ * 
+ * @author fs
+ * @date 2025-12-02
+ */
+public interface FsShareAmountDetailMapper extends BaseMapper<FsShareAmountDetail>{
+    /**
+     * 查询分账明细
+     * 
+     * @param id 分账明细主键
+     * @return 分账明细
+     */
+    FsShareAmountDetail selectFsShareAmountDetailById(Long id);
+
+    /**
+     * 查询分账明细列表
+     * 
+     * @param fsShareAmountDetail 分账明细
+     * @return 分账明细集合
+     */
+    List<FsShareAmountDetailVO> selectFsShareAmountDetailList(@Param("maps") FsShareAmountDetailParam fsShareAmountDetail);
+
+    /**
+     * 新增分账明细
+     * 
+     * @param fsShareAmountDetail 分账明细
+     * @return 结果
+     */
+    int insertFsShareAmountDetail(FsShareAmountDetail fsShareAmountDetail);
+
+    /**
+     * 修改分账明细
+     * 
+     * @param fsShareAmountDetail 分账明细
+     * @return 结果
+     */
+    int updateFsShareAmountDetail(FsShareAmountDetail fsShareAmountDetail);
+
+    /**
+     * 删除分账明细
+     * 
+     * @param id 分账明细主键
+     * @return 结果
+     */
+    int deleteFsShareAmountDetailById(Long id);
+
+    /**
+     * 批量删除分账明细
+     * 
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteFsShareAmountDetailByIds(Long[] ids);
+}

+ 6 - 0
fs-service/src/main/java/com/fs/his/mapper/FsStorePaymentMapper.java

@@ -340,4 +340,10 @@ public interface FsStorePaymentMapper
 
     @Select(" select  * from fs_store_payment where status = 0 ")
     List<FsStorePayment> selectAllPayment();
+
+    /**
+     * 今日分账
+     */
+    @Select("SELECT * FROM fs_store_payment WHERE `status` = 1 AND DATE(share_time) = CURDATE() AND is_share = 1 AND share_status = 0")
+    List<FsStorePayment> selectSharePaymentList();
 }

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

@@ -3,6 +3,7 @@ package com.fs.his.mapper;
 import java.util.List;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.fs.his.domain.FsSubMerchant;
+import org.apache.ibatis.annotations.Select;
 
 /**
  * 汇付-子商户信息Mapper接口
@@ -58,4 +59,7 @@ public interface FsSubMerchantMapper extends BaseMapper<FsSubMerchant>{
      * @return 结果
      */
     int deleteFsSubMerchantByIds(Long[] ids);
+
+    @Select("SELECT id,CONCAT_WS('-',merchant_name,merchant_no) merchant_name FROM fs_sub_merchant")
+    List<FsSubMerchant> selectFsSubMerchantListOptions();
 }

+ 3 - 0
fs-service/src/main/java/com/fs/his/mapper/FsUserMapper.java

@@ -510,4 +510,7 @@ public interface FsUserMapper
     List<AppSalesCourseStatisticsVO> selectAppSalesNewUserCountVO(FsCourseWatchLogStatisticsListParam param);
 
     int updateMpOpenIdByUserId(@Param("userId") Long userId, @Param("openId") String openId);
+
+    @Update("update fs_user set password = #{password} where user_id = #{userId}")
+    void updatePasswordByUserId(FsUser user);
 }

+ 15 - 0
fs-service/src/main/java/com/fs/his/param/FsShareAmountDetailParam.java

@@ -0,0 +1,15 @@
+package com.fs.his.param;
+
+import lombok.Data;
+
+import java.util.Date;
+
+@Data
+public class FsShareAmountDetailParam {
+    private String orderCode;
+    private Integer type;
+    private String merchantName;
+    private String merchantAccount;
+    private Date startTime;
+    private Date endTime;
+}

+ 1 - 0
fs-service/src/main/java/com/fs/his/param/FsUserIntegralLogsParam.java

@@ -26,6 +26,7 @@ public class FsUserIntegralLogsParam {
     /** 积分余额 */
     @Excel(name = "积分余额")
     private Long balance;
+
     private String nickName;
 
     private String phone;

+ 4 - 0
fs-service/src/main/java/com/fs/his/service/IFsCityService.java

@@ -68,4 +68,8 @@ public interface IFsCityService
     List<CitysAreaVO> getCitysArea();
 
     List<FsCity> selectFsCitys();
+
+    String selectByFsCity(FsCity fsCity);
+
+    String likeByCityDistrict(String city, String district);
 }

+ 7 - 0
fs-service/src/main/java/com/fs/his/service/IFsCourseCouponUserService.java

@@ -4,6 +4,8 @@ import java.util.List;
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.fs.common.core.domain.R;
 import com.fs.his.domain.FsCourseCouponUser;
+import com.fs.his.vo.CourseCouponUserListUVO;
+import com.fs.his.vo.FsCourseCouponUserRecordVO;
 
 /**
  * 用户看课优惠券Service接口
@@ -61,4 +63,9 @@ public interface IFsCourseCouponUserService extends IService<FsCourseCouponUser>
     int deleteFsCourseCouponUserById(Long id);
 
     R useCoupon(Long userId,Long couponUserId);
+
+    List<CourseCouponUserListUVO> selectCourseCouponUserUVOList(FsCourseCouponUser courseCouponUser);
+
+
+    List<FsCourseCouponUserRecordVO> selectCourseCouponUserRecordList(FsCourseCouponUser courseCouponUser);
 }

+ 1 - 0
fs-service/src/main/java/com/fs/his/service/IFsIntegralGoodsService.java

@@ -74,6 +74,7 @@ public interface IFsIntegralGoodsService
     String importIntegralGoodsService(List<FsIntegralGoods> list);
 
     R getCourseIntegralGoods(Long userId);
+    R getCourseIntegralGoodsByMiniApp(String appId,Long userId, String integralGoods);
 
     /**
      * 获取选择积分商品列表

Некоторые файлы не были показаны из-за большого количества измененных файлов