Преглед изворни кода

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

caoliqin пре 1 недеља
родитељ
комит
76c5c56427
42 измењених фајлова са 992 додато и 353 уклоњено
  1. 8 0
      fs-admin/src/main/java/com/fs/course/controller/FsCourseWatchLogController.java
  2. 2 14
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreHealthOrderScrmController.java
  3. 12 0
      fs-company/src/main/java/com/fs/company/controller/company/CompanyProfileController.java
  4. 9 0
      fs-company/src/main/java/com/fs/company/controller/course/FsCourseWatchLogController.java
  5. 7 0
      fs-service/src/main/java/com/fs/company/domain/CompanyUser.java
  6. 4 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyUserMapper.java
  7. 11 0
      fs-service/src/main/java/com/fs/company/service/ICompanyUserService.java
  8. 18 0
      fs-service/src/main/java/com/fs/company/service/impl/CompanyUserServiceImpl.java
  9. 11 0
      fs-service/src/main/java/com/fs/company/vo/CompanyUserQwListVO.java
  10. 4 0
      fs-service/src/main/java/com/fs/course/config/CourseConfig.java
  11. 3 3
      fs-service/src/main/java/com/fs/course/mapper/FsCourseWatchLogMapper.java
  12. 5 0
      fs-service/src/main/java/com/fs/course/param/CourseStatisticsUserDetailParam.java
  13. 69 53
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseQuestionBankServiceImpl.java
  14. 25 4
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java
  15. 13 7
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseServiceImpl.java
  16. 55 0
      fs-service/src/main/java/com/fs/course/util/CourseConfigUserAnswerExpose.java
  17. 57 0
      fs-service/src/main/java/com/fs/course/util/CourseRatingAnswerExtract.java
  18. 6 0
      fs-service/src/main/java/com/fs/course/vo/CourseStatisticsUserDetailVO.java
  19. 4 0
      fs-service/src/main/java/com/fs/erp/service/impl/FsJstAftersalePushScrmServiceImpl.java
  20. 93 4
      fs-service/src/main/java/com/fs/erp/service/impl/JSTErpOrderServiceImpl.java
  21. 4 0
      fs-service/src/main/java/com/fs/his/domain/FsUser.java
  22. 2 0
      fs-service/src/main/java/com/fs/his/service/impl/FsPackageOrderServiceImpl.java
  23. 3 0
      fs-service/src/main/java/com/fs/hisStore/domain/FsStoreOrderScrm.java
  24. 4 0
      fs-service/src/main/java/com/fs/hisStore/domain/FsStoreProductScrm.java
  25. 176 104
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreOrderItemScrmMapper.java
  26. 28 7
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreOrderScrmMapper.java
  27. 2 0
      fs-service/src/main/java/com/fs/hisStore/param/FsStoreProductAddEditParam.java
  28. 36 0
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreCartScrmServiceImpl.java
  29. 80 3
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderScrmServiceImpl.java
  30. 15 5
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreProductScrmServiceImpl.java
  31. 3 0
      fs-service/src/main/java/com/fs/hisStore/vo/FsStoreProductQueryVO.java
  32. 14 0
      fs-service/src/main/java/com/fs/live/service/impl/LiveOrderServiceImpl.java
  33. 1 0
      fs-service/src/main/java/com/fs/live/service/impl/LiveServiceImpl.java
  34. 1 1
      fs-service/src/main/resources/application-common.yml
  35. 70 4
      fs-service/src/main/resources/mapper/company/CompanyUserMapper.xml
  36. 93 32
      fs-service/src/main/resources/mapper/course/FsCourseWatchLogMapper.xml
  37. 2 1
      fs-service/src/main/resources/mapper/hisStore/FsStoreOrderScrmMapper.xml
  38. 6 2
      fs-service/src/main/resources/mapper/hisStore/FsStoreProductScrmMapper.xml
  39. 1 0
      fs-service/src/main/resources/mapper/hisStore/FsStoreScrmMapper.xml
  40. 1 1
      fs-service/src/main/resources/mapper/live/LiveMapper.xml
  41. 34 3
      fs-user-app/src/main/java/com/fs/app/controller/UserController.java
  42. 0 105
      fs-user-app/src/test/java/com/fs/test/StockDeductTest.java

+ 8 - 0
fs-admin/src/main/java/com/fs/course/controller/FsCourseWatchLogController.java

@@ -10,6 +10,7 @@ import com.fs.course.param.FsCourseWatchLogListParam;
 import com.fs.course.param.FsCourseWatchLogStatisticsListParam;
 import com.fs.course.param.FsCourseWatchLogStatisticsListParam;
 import com.fs.course.service.IFsUserCoursePeriodDaysService;
 import com.fs.course.service.IFsUserCoursePeriodDaysService;
 import com.fs.course.service.IFsUserCoursePeriodService;
 import com.fs.course.service.IFsUserCoursePeriodService;
+import com.fs.course.util.CourseConfigUserAnswerExpose;
 import com.fs.course.vo.*;
 import com.fs.course.vo.*;
 import com.fs.qw.param.QwWatchLogStatisticsListParam;
 import com.fs.qw.param.QwWatchLogStatisticsListParam;
 import com.fs.qw.service.IQwWatchLogService;
 import com.fs.qw.service.IQwWatchLogService;
@@ -34,6 +35,7 @@ import com.fs.course.domain.FsCourseWatchLog;
 import com.fs.course.service.IFsCourseWatchLogService;
 import com.fs.course.service.IFsCourseWatchLogService;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.core.page.TableDataInfo;
+import com.fs.system.service.ISysConfigService;
 
 
 /**
 /**
  * 短链课程看课记录Controller
  * 短链课程看课记录Controller
@@ -54,6 +56,8 @@ public class FsCourseWatchLogController extends BaseController
     private IFsUserCoursePeriodDaysService userCoursePeriodDaysService;
     private IFsUserCoursePeriodDaysService userCoursePeriodDaysService;
     @Autowired
     @Autowired
     private IFsUserCoursePeriodService userCoursePeriodService;
     private IFsUserCoursePeriodService userCoursePeriodService;
+    @Autowired
+    private ISysConfigService configService;
     /**
     /**
      * 查询短链课程看课记录列表
      * 查询短链课程看课记录列表
      */
      */
@@ -281,6 +285,8 @@ public class FsCourseWatchLogController extends BaseController
         param.setPeriodId(periodId);
         param.setPeriodId(periodId);
         param.setPageNum(pageNum);
         param.setPageNum(pageNum);
         param.setPageSize(pageSize);
         param.setPageSize(pageSize);
+        String courseCfg = configService.selectConfigByKey("course.config");
+        param.setIncludeCourseRating(CourseConfigUserAnswerExpose.includeUserAnswerContentInStatistics(courseCfg));
         PageHelper.startPage(pageNum, pageSize);
         PageHelper.startPage(pageNum, pageSize);
         return R.ok().put("data", new PageInfo<>(fsCourseWatchLogService.getCourseStatisticsUserDetailList(param)));
         return R.ok().put("data", new PageInfo<>(fsCourseWatchLogService.getCourseStatisticsUserDetailList(param)));
     }
     }
@@ -300,6 +306,8 @@ public class FsCourseWatchLogController extends BaseController
         com.fs.course.param.CourseStatisticsUserDetailParam param = new com.fs.course.param.CourseStatisticsUserDetailParam();
         com.fs.course.param.CourseStatisticsUserDetailParam param = new com.fs.course.param.CourseStatisticsUserDetailParam();
         param.setVideoId(videoId);
         param.setVideoId(videoId);
         param.setPeriodId(periodId);
         param.setPeriodId(periodId);
+        String courseCfg = configService.selectConfigByKey("course.config");
+        param.setIncludeCourseRating(CourseConfigUserAnswerExpose.includeUserAnswerContentInStatistics(courseCfg));
         List<com.fs.course.vo.CourseStatisticsUserDetailVO> list = fsCourseWatchLogService.getCourseStatisticsUserDetailExportList(param);
         List<com.fs.course.vo.CourseStatisticsUserDetailVO> list = fsCourseWatchLogService.getCourseStatisticsUserDetailExportList(param);
         ExcelUtil<com.fs.course.vo.CourseStatisticsUserDetailVO> util = new ExcelUtil<>(com.fs.course.vo.CourseStatisticsUserDetailVO.class);
         ExcelUtil<com.fs.course.vo.CourseStatisticsUserDetailVO> util = new ExcelUtil<>(com.fs.course.vo.CourseStatisticsUserDetailVO.class);
         return util.exportExcel(list, "用户看课数据");
         return util.exportExcel(list, "用户看课数据");

+ 2 - 14
fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreHealthOrderScrmController.java

@@ -171,16 +171,10 @@ public class FsStoreHealthOrderScrmController extends BaseController {
     @Log(title = "健康商城订单", businessType = BusinessType.EXPORT)
     @Log(title = "健康商城订单", businessType = BusinessType.EXPORT)
     @PostMapping("/healthExport")
     @PostMapping("/healthExport")
     public AjaxResult export1(@RequestBody FsStoreOrderParam param) {
     public AjaxResult export1(@RequestBody FsStoreOrderParam param) {
-        if ("".equals(param.getBeginTime()) && "".equals(param.getEndTime())){
-            param.setBeginTime(null);
-            param.setEndTime(null);
-        }
+        normalizeExportParam(param);
         if (fsStoreOrderService.isEntityNull(param)){
         if (fsStoreOrderService.isEntityNull(param)){
             return AjaxResult.error("请筛选数据导出");
             return AjaxResult.error("请筛选数据导出");
         }
         }
-        if(!StringUtils.isEmpty(param.getCreateTimeRange())){
-            param.setCreateTimeList(param.getCreateTimeRange().split("--"));
-        }
         if(!StringUtils.isEmpty(param.getPayTimeRange())){
         if(!StringUtils.isEmpty(param.getPayTimeRange())){
             param.setPayTimeList(param.getPayTimeRange().split("--"));
             param.setPayTimeList(param.getPayTimeRange().split("--"));
         }
         }
@@ -231,16 +225,10 @@ public class FsStoreHealthOrderScrmController extends BaseController {
     @Log(title = "健康商城订单", businessType = BusinessType.EXPORT)
     @Log(title = "健康商城订单", businessType = BusinessType.EXPORT)
     @PostMapping("/healthExportDetails")
     @PostMapping("/healthExportDetails")
     public AjaxResult healthExportDetails(@RequestBody FsStoreOrderParam param) {
     public AjaxResult healthExportDetails(@RequestBody FsStoreOrderParam param) {
-        if ("".equals(param.getBeginTime()) && "".equals(param.getEndTime())){
-            param.setBeginTime(null);
-            param.setEndTime(null);
-        }
+        normalizeExportParam(param);
         if (fsStoreOrderService.isEntityNull(param)){
         if (fsStoreOrderService.isEntityNull(param)){
             return AjaxResult.error("请筛选数据导出");
             return AjaxResult.error("请筛选数据导出");
         }
         }
-        if(!StringUtils.isEmpty(param.getCreateTimeRange())){
-            param.setCreateTimeList(param.getCreateTimeRange().split("--"));
-        }
         if(!StringUtils.isEmpty(param.getPayTimeRange())){
         if(!StringUtils.isEmpty(param.getPayTimeRange())){
             param.setPayTimeList(param.getPayTimeRange().split("--"));
             param.setPayTimeList(param.getPayTimeRange().split("--"));
         }
         }

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

@@ -10,8 +10,10 @@ import com.fs.common.enums.ImTypeEnum;
 import com.fs.common.exception.file.OssException;
 import com.fs.common.exception.file.OssException;
 import com.fs.common.utils.PatternUtils;
 import com.fs.common.utils.PatternUtils;
 import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.ServletUtils;
+import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.file.FileUploadUtils;
 import com.fs.common.utils.file.FileUploadUtils;
 import com.fs.company.domain.CompanyUser;
 import com.fs.company.domain.CompanyUser;
+import com.fs.company.mapper.CompanyFsUserMapper;
 import com.fs.company.param.CompanyUserEditParam;
 import com.fs.company.param.CompanyUserEditParam;
 import com.fs.company.service.ICompanyUserService;
 import com.fs.company.service.ICompanyUserService;
 import com.fs.framework.security.LoginUser;
 import com.fs.framework.security.LoginUser;
@@ -47,6 +49,10 @@ public class CompanyProfileController extends BaseController
 
 
     @Autowired
     @Autowired
     private OpenIMService openIMService;
     private OpenIMService openIMService;
+
+    @Autowired
+    private CompanyFsUserMapper companyFsUserMapper;
+
     /**
     /**
      * 个人信息
      * 个人信息
      */
      */
@@ -55,6 +61,12 @@ public class CompanyProfileController extends BaseController
     {
     {
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         CompanyUser user = loginUser.getUser();
         CompanyUser user = loginUser.getUser();
+
+        String fsUser = companyFsUserMapper.selectUserListBySalesId(user.getUserId());
+        if(StringUtils.isNotEmpty(fsUser)){
+            user.setFsUserId(fsUser);
+        }
+
         AjaxResult ajax = AjaxResult.success(user);
         AjaxResult ajax = AjaxResult.success(user);
         ajax.put("roleGroup", userService.selectUserRoleGroup(loginUser.getUsername()));
         ajax.put("roleGroup", userService.selectUserRoleGroup(loginUser.getUsername()));
         ajax.put("postGroup", userService.selectUserPostGroup(loginUser.getUsername()));
         ajax.put("postGroup", userService.selectUserPostGroup(loginUser.getUsername()));

+ 9 - 0
fs-company/src/main/java/com/fs/company/controller/course/FsCourseWatchLogController.java

@@ -16,6 +16,7 @@ import com.fs.course.param.*;
 import com.fs.course.service.IFsCourseWatchLogService;
 import com.fs.course.service.IFsCourseWatchLogService;
 import com.fs.course.service.IFsUserCoursePeriodDaysService;
 import com.fs.course.service.IFsUserCoursePeriodDaysService;
 import com.fs.course.service.IFsUserCoursePeriodService;
 import com.fs.course.service.IFsUserCoursePeriodService;
+import com.fs.course.util.CourseConfigUserAnswerExpose;
 import com.fs.course.vo.FsCourseOverVO;
 import com.fs.course.vo.FsCourseOverVO;
 import com.fs.course.vo.FsCourseUserStatisticsListVO;
 import com.fs.course.vo.FsCourseUserStatisticsListVO;
 import com.fs.course.vo.FsCourseWatchLogListVO;
 import com.fs.course.vo.FsCourseWatchLogListVO;
@@ -29,6 +30,7 @@ import com.fs.qw.service.IQwWatchLogService;
 import com.fs.qw.vo.QwWatchLogAllStatisticsListVO;
 import com.fs.qw.vo.QwWatchLogAllStatisticsListVO;
 import com.fs.qw.vo.QwWatchLogStatisticsListVO;
 import com.fs.qw.vo.QwWatchLogStatisticsListVO;
 import com.fs.sop.mapper.SopUserLogsMapper;
 import com.fs.sop.mapper.SopUserLogsMapper;
+import com.fs.system.service.ISysConfigService;
 import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageInfo;
 import com.github.pagehelper.PageInfo;
 import com.google.gson.Gson;
 import com.google.gson.Gson;
@@ -72,6 +74,9 @@ public class FsCourseWatchLogController extends BaseController
     @Autowired
     @Autowired
     private IQwTagService iQwTagService;
     private IQwTagService iQwTagService;
 
 
+    @Autowired
+    private ISysConfigService configService;
+
     /**
     /**
      * 查询短链课程看课记录列表
      * 查询短链课程看课记录列表
      */
      */
@@ -447,6 +452,8 @@ public class FsCourseWatchLogController extends BaseController
         if(!"00".equals(loginUser.getUser().getUserType())){
         if(!"00".equals(loginUser.getUser().getUserType())){
             param.setCompanyUserId(loginUser.getUser().getUserId());
             param.setCompanyUserId(loginUser.getUser().getUserId());
         }
         }
+        String courseCfg = configService.selectConfigByKey("course.config");
+        param.setIncludeCourseRating(CourseConfigUserAnswerExpose.includeUserAnswerContentInStatistics(courseCfg));
         PageHelper.startPage(pageNum, pageSize);
         PageHelper.startPage(pageNum, pageSize);
         return R.ok().put("data", new PageInfo<>(fsCourseWatchLogService.getCourseStatisticsUserDetailList(param)));
         return R.ok().put("data", new PageInfo<>(fsCourseWatchLogService.getCourseStatisticsUserDetailList(param)));
     }
     }
@@ -474,6 +481,8 @@ public class FsCourseWatchLogController extends BaseController
         if(!"00".equals(loginUser.getUser().getUserType())){
         if(!"00".equals(loginUser.getUser().getUserType())){
             param.setCompanyUserId(loginUser.getUser().getUserId());
             param.setCompanyUserId(loginUser.getUser().getUserId());
         }
         }
+        String courseCfg = configService.selectConfigByKey("course.config");
+        param.setIncludeCourseRating(CourseConfigUserAnswerExpose.includeUserAnswerContentInStatistics(courseCfg));
         List<com.fs.course.vo.CourseStatisticsUserDetailVO> list = fsCourseWatchLogService.getCourseStatisticsUserDetailExportList(param);
         List<com.fs.course.vo.CourseStatisticsUserDetailVO> list = fsCourseWatchLogService.getCourseStatisticsUserDetailExportList(param);
         ExcelUtil<com.fs.course.vo.CourseStatisticsUserDetailVO> util = new ExcelUtil<>(com.fs.course.vo.CourseStatisticsUserDetailVO.class);
         ExcelUtil<com.fs.course.vo.CourseStatisticsUserDetailVO> util = new ExcelUtil<>(com.fs.course.vo.CourseStatisticsUserDetailVO.class);
         return util.exportExcel(list, "用户看课数据");
         return util.exportExcel(list, "用户看课数据");

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

@@ -192,6 +192,13 @@ public class CompanyUser extends BaseEntity
     /** 绑定的公司用户ID */
     /** 绑定的公司用户ID */
     private Long bindCompanyUserId;
     private Long bindCompanyUserId;
 
 
+    /** 邀请码 */
+    private String invitationCode;
+
+    /** 邀请人名称 */
+    @TableField(exist = false)
+    private String invitationName;
+
     @TableField(exist = false)
     @TableField(exist = false)
     private List<Long> deptList;
     private List<Long> deptList;
 
 

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

@@ -37,6 +37,10 @@ public interface CompanyUserMapper
      */
      */
     public CompanyUser selectCompanyUserById(Long userId);
     public CompanyUser selectCompanyUserById(Long userId);
 
 
+
+    @Select("select * from company_user where user_id=#{invitedSalesId} and del_flag=0")
+    CompanyUser getInviteCodeByCompanyUserIdAndUserId(@Param("invitedSalesId") Long invitedSalesId);
+
     /**
     /**
      * 查询物业公司管理员信息列表
      * 查询物业公司管理员信息列表
      *
      *

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

@@ -281,6 +281,12 @@ public interface ICompanyUserService {
 
 
 
 
 
 
+    /**
+     * 根据销售绑定关系获取销售邀请码
+     */
+    CompanyUser getInviteCodeByCompanyUserIdAndUserId( Long userId);
+
+
     /**
     /**
      * 绑定销售和fs_user 的关系(该销售绑定fs_user)
      * 绑定销售和fs_user 的关系(该销售绑定fs_user)
      */
      */
@@ -299,4 +305,9 @@ public interface ICompanyUserService {
      * 获取销售绑定的fs_user
      * 获取销售绑定的fs_user
      */
      */
     List<CompanyUserBindUserVO> getFsUserByCompanyUserId(FsUser fsUser);
     List<CompanyUserBindUserVO> getFsUserByCompanyUserId(FsUser fsUser);
+
+    /**
+     * 获取销售绑定的fs_user
+     */
+    int countCompanyUserByUserId(Long userId);
 }
 }

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

@@ -1135,6 +1135,15 @@ public class CompanyUserServiceImpl implements ICompanyUserService
 
 
     }
     }
 
 
+    @Override
+    public CompanyUser getInviteCodeByCompanyUserIdAndUserId(Long userId) {
+        CompanyUser companyUser = companyUserMapper.getInviteCodeByCompanyUserIdAndUserId(userId);
+        if (companyUser != null) {
+            return companyUser;
+        }
+        return null;
+    }
+
 
 
     @Override
     @Override
     @Transactional(rollbackFor = Exception.class)
     @Transactional(rollbackFor = Exception.class)
@@ -1200,4 +1209,13 @@ public class CompanyUserServiceImpl implements ICompanyUserService
         return fsUsers;
         return fsUsers;
     }
     }
 
 
+    @Override
+    public int countCompanyUserByUserId(Long userId) {
+        CompanyFsUser existingBinding = companyFsUserMapper.selectByFsUserId(userId);
+        if(existingBinding!=null){
+            return 1;
+        }
+        return 0;
+    }
+
 }
 }

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

@@ -144,4 +144,15 @@ public class CompanyUserQwListVO extends BaseEntity {
      */
      */
     private Long aiSipCallUserId;
     private Long aiSipCallUserId;
 
 
+
+    /**
+     * 绑定会员昵称
+     */
+    private  String  bindUser;
+
+    /**
+     * 绑定会员状态
+     */
+    private  Integer bindStatus;
+
 }
 }

+ 4 - 0
fs-service/src/main/java/com/fs/course/config/CourseConfig.java

@@ -12,6 +12,10 @@ import java.util.List;
 public class CourseConfig implements Serializable {
 public class CourseConfig implements Serializable {
     private Integer answerRate; //可答题百分比
     private Integer answerRate; //可答题百分比
     private Integer answerErrorCount; //允许错误次数
     private Integer answerErrorCount; //允许错误次数
+    /**
+     * 看课是否校验答案:1-是(默认) 0-否,与后台点播配置 JSON 字段 validateAnswerWhenWatch 一致
+     */
+        private String validateAnswerWhenWatch;
     private Integer videoLinkExpireDate; //课程小节短链过期时间
     private Integer videoLinkExpireDate; //课程小节短链过期时间
     private Integer maxBufferLength;//最大缓冲时长
     private Integer maxBufferLength;//最大缓冲时长
     private Integer videoIntegral;//每十分钟获取多少积分
     private Integer videoIntegral;//每十分钟获取多少积分

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

@@ -773,10 +773,10 @@ public interface FsCourseWatchLogMapper extends BaseMapper<FsCourseWatchLog> {
     Long selectVideoDurationByVideoId(@Param("videoId") Long videoId);
     Long selectVideoDurationByVideoId(@Param("videoId") Long videoId);
 
 
     /**
     /**
-     * 统计累计观看人数(对userId去重
+     * 统计累计到课人数(对 userId 去重,存在本视频本营期且 duration&gt;0 的看课记录
      * @param videoId 视频ID
      * @param videoId 视频ID
      * @param periodId 营期ID
      * @param periodId 营期ID
-     * @return 累计观看人数
+     * @return 累计到课人数
      */
      */
     @Select("<script>" +
     @Select("<script>" +
             "SELECT COUNT(DISTINCT user_id) FROM fs_course_watch_log " +
             "SELECT COUNT(DISTINCT user_id) FROM fs_course_watch_log " +
@@ -790,7 +790,7 @@ public interface FsCourseWatchLogMapper extends BaseMapper<FsCourseWatchLog> {
                                  @Param("companyUserId") Long companyUserId);
                                  @Param("companyUserId") Long companyUserId);
 
 
     /**
     /**
-     * 统计累计完课人数(duration >= 1200秒,即20分钟,对userId去重
+     * 统计累计完课人数(存在完课记录 log_type=2 且 duration&gt;0,对 user_id 去重;与「实际看课数据」口径一致
      * @param videoId 视频ID
      * @param videoId 视频ID
      * @param periodId 营期ID
      * @param periodId 营期ID
      * @return 累计完课人数
      * @return 累计完课人数

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

@@ -27,4 +27,9 @@ public class CourseStatisticsUserDetailParam implements Serializable {
 
 
     // 公司ID
     // 公司ID
     private Long companyUserId;
     private Long companyUserId;
+
+    /**
+     * 为 true 时 SQL 联表查询答题日志 question_json,填充 VO.courseRating(课程评分-用户作答内容)
+     */
+    private Boolean includeCourseRating;
 }
 }

+ 69 - 53
fs-service/src/main/java/com/fs/course/service/impl/FsCourseQuestionBankServiceImpl.java

@@ -15,6 +15,7 @@ import com.fs.course.dto.ImportResultDTO;
 import com.fs.course.mapper.*;
 import com.fs.course.mapper.*;
 import com.fs.course.param.FsCourseQuestionAnswerUParam;
 import com.fs.course.param.FsCourseQuestionAnswerUParam;
 import com.fs.course.service.IFsCourseQuestionBankService;
 import com.fs.course.service.IFsCourseQuestionBankService;
+import com.fs.course.util.CourseConfigUserAnswerExpose;
 import com.fs.his.domain.FsUser;
 import com.fs.his.domain.FsUser;
 import com.fs.his.mapper.FsUserMapper;
 import com.fs.his.mapper.FsUserMapper;
 import com.fs.his.service.IFsStorePaymentService;
 import com.fs.his.service.IFsStorePaymentService;
@@ -155,6 +156,8 @@ public class FsCourseQuestionBankServiceImpl implements IFsCourseQuestionBankSer
         new FsCourseAnswerLogs();
         new FsCourseAnswerLogs();
         FsCourseAnswerLogs rightLog;
         FsCourseAnswerLogs rightLog;
         //判断短链类型
         //判断短链类型
+        boolean skipAnswerValidation = CourseConfigUserAnswerExpose.skipValidateAnswerOnSubmit(json);
+
 
 
         if ("泽林文化".equals(signProjectName)){
         if ("泽林文化".equals(signProjectName)){
             FsCourseWatchLog log = courseWatchLogMapper.getWatchLogByFsUserAndPeriodId(param.getVideoId(), param.getUserId(), param.getCompanyUserId(),param.getPeriodId());
             FsCourseWatchLog log = courseWatchLogMapper.getWatchLogByFsUserAndPeriodId(param.getVideoId(), param.getUserId(), param.getCompanyUserId(),param.getPeriodId());
@@ -218,31 +221,35 @@ public class FsCourseQuestionBankServiceImpl implements IFsCourseQuestionBankSer
         // 一次性获取所有问题的正确答案
         // 一次性获取所有问题的正确答案
         List<FsCourseQuestionBank> questions = param.getQuestions();
         List<FsCourseQuestionBank> questions = param.getQuestions();
         if (questions != null && !questions.isEmpty()) {
         if (questions != null && !questions.isEmpty()) {
-            Map<Long, FsCourseQuestionBank> correctAnswersMap = fsCourseQuestionBankMapper.selectFsCourseQuestionBankByIds(
-                    questions.stream().map(FsCourseQuestionBank::getId).collect(Collectors.toList())
-            ).stream().collect(Collectors.toMap(FsCourseQuestionBank::getId, question -> question));
-
-            for (FsCourseQuestionBank questionBank : questions) {
-                FsCourseQuestionBank correctAnswer = correctAnswersMap.get(questionBank.getId());
-                if (correctAnswer.getType() == 1) {
-                    if (questionBank.getAnswer().equals(correctAnswer.getAnswer())) {
-                        thisRightCount++;
-                    } else {
-                        correctAnswer.setAnswer(null);
-                        incorrectQuestions.add(correctAnswer);
-                    }
-                } else if (correctAnswer.getType() == 2) {
-                    String[] userAnswers = convertStringToArray(questionBank.getAnswer());
-                    String[] correctAnswers = convertStringToArray(correctAnswer.getAnswer());
-
-                    Arrays.sort(userAnswers);
-                    Arrays.sort(correctAnswers);
-
-                    if (Arrays.equals(userAnswers, correctAnswers)) {
-                        thisRightCount++;
-                    } else {
-                        correctAnswer.setAnswer(null);
-                        incorrectQuestions.add(correctAnswer);
+            if (skipAnswerValidation) {
+                thisRightCount = questions.size();
+            } else {
+                Map<Long, FsCourseQuestionBank> correctAnswersMap = fsCourseQuestionBankMapper.selectFsCourseQuestionBankByIds(
+                        questions.stream().map(FsCourseQuestionBank::getId).collect(Collectors.toList())
+                ).stream().collect(Collectors.toMap(FsCourseQuestionBank::getId, question -> question));
+
+                for (FsCourseQuestionBank questionBank : questions) {
+                    FsCourseQuestionBank correctAnswer = correctAnswersMap.get(questionBank.getId());
+                    if (correctAnswer.getType() == 1) {
+                        if (questionBank.getAnswer().equals(correctAnswer.getAnswer())) {
+                            thisRightCount++;
+                        } else {
+                            correctAnswer.setAnswer(null);
+                            incorrectQuestions.add(correctAnswer);
+                        }
+                    } else if (correctAnswer.getType() == 2) {
+                        String[] userAnswers = convertStringToArray(questionBank.getAnswer());
+                        String[] correctAnswers = convertStringToArray(correctAnswer.getAnswer());
+
+                        Arrays.sort(userAnswers);
+                        Arrays.sort(correctAnswers);
+
+                        if (Arrays.equals(userAnswers, correctAnswers)) {
+                            thisRightCount++;
+                        } else {
+                            correctAnswer.setAnswer(null);
+                            incorrectQuestions.add(correctAnswer);
+                        }
                     }
                     }
                 }
                 }
             }
             }
@@ -259,7 +266,8 @@ public class FsCourseQuestionBankServiceImpl implements IFsCourseQuestionBankSer
         logs.setCreateTime(new Date());
         logs.setCreateTime(new Date());
         logs.setPeriodId(param.getPeriodId());
         logs.setPeriodId(param.getPeriodId());
 
 
-        if (thisRightCount == questions.size()) {
+        int questionCount = questions == null ? 0 : questions.size();
+        if (thisRightCount == questionCount) {
             logs.setIsRight(1);
             logs.setIsRight(1);
             courseAnswerLogsMapper.insertFsCourseAnswerLogs(logs);
             courseAnswerLogsMapper.insertFsCourseAnswerLogs(logs);
             return R.ok("答题成功");
             return R.ok("答题成功");
@@ -437,32 +445,39 @@ public class FsCourseQuestionBankServiceImpl implements IFsCourseQuestionBankSer
         }
         }
         int remainCount = config.getAnswerErrorCount()-errorCount-1;
         int remainCount = config.getAnswerErrorCount()-errorCount-1;
 
 
-        // 一次性获取所有问题的正确答案
-        Map<Long, FsCourseQuestionBank> correctAnswersMap = fsCourseQuestionBankMapper.selectFsCourseQuestionBankByIds(
-                param.getQuestions().stream().map(FsCourseQuestionBank::getId).collect(Collectors.toList())
-        ).stream().collect(Collectors.toMap(FsCourseQuestionBank::getId, question -> question));
-
-        for (FsCourseQuestionBank questionBank : param.getQuestions()) {
-            FsCourseQuestionBank correctAnswer = correctAnswersMap.get(questionBank.getId());
-            if (correctAnswer.getType() == 1) {
-                if (questionBank.getAnswer().equals(correctAnswer.getAnswer())) {
-                    thisRightCount++;
-                } else {
-                    correctAnswer.setAnswer(null);
-                    incorrectQuestions.add(correctAnswer);
-                }
-            } else if (correctAnswer.getType() == 2) {
-                String[] userAnswers = convertStringToArray(questionBank.getAnswer());
-                String[] correctAnswers = convertStringToArray(correctAnswer.getAnswer());
-
-                Arrays.sort(userAnswers);
-                Arrays.sort(correctAnswers);
-
-                if (Arrays.equals(userAnswers, correctAnswers)) {
-                    thisRightCount++;
-                } else {
-                    correctAnswer.setAnswer(null);
-                    incorrectQuestions.add(correctAnswer);
+        boolean skipAnswerValidation = CourseConfigUserAnswerExpose.skipValidateAnswerOnSubmit(json);
+        List<FsCourseQuestionBank> questions = param.getQuestions();
+        if (questions != null && !questions.isEmpty()) {
+            if (skipAnswerValidation) {
+                thisRightCount = questions.size();
+            } else {
+                Map<Long, FsCourseQuestionBank> correctAnswersMap = fsCourseQuestionBankMapper.selectFsCourseQuestionBankByIds(
+                        questions.stream().map(FsCourseQuestionBank::getId).collect(Collectors.toList())
+                ).stream().collect(Collectors.toMap(FsCourseQuestionBank::getId, question -> question));
+
+                for (FsCourseQuestionBank questionBank : questions) {
+                    FsCourseQuestionBank correctAnswer = correctAnswersMap.get(questionBank.getId());
+                    if (correctAnswer.getType() == 1) {
+                        if (questionBank.getAnswer().equals(correctAnswer.getAnswer())) {
+                            thisRightCount++;
+                        } else {
+                            correctAnswer.setAnswer(null);
+                            incorrectQuestions.add(correctAnswer);
+                        }
+                    } else if (correctAnswer.getType() == 2) {
+                        String[] userAnswers = convertStringToArray(questionBank.getAnswer());
+                        String[] correctAnswers = convertStringToArray(correctAnswer.getAnswer());
+
+                        Arrays.sort(userAnswers);
+                        Arrays.sort(correctAnswers);
+
+                        if (Arrays.equals(userAnswers, correctAnswers)) {
+                            thisRightCount++;
+                        } else {
+                            correctAnswer.setAnswer(null);
+                            incorrectQuestions.add(correctAnswer);
+                        }
+                    }
                 }
                 }
             }
             }
         }
         }
@@ -479,7 +494,8 @@ public class FsCourseQuestionBankServiceImpl implements IFsCourseQuestionBankSer
         logs.setCreateTime(new Date());
         logs.setCreateTime(new Date());
         logs.setPeriodId(param.getPeriodId());
         logs.setPeriodId(param.getPeriodId());
 
 
-        if (thisRightCount == param.getQuestions().size()) {
+        int questionCount = questions == null ? 0 : questions.size();
+        if (thisRightCount == questionCount) {
             logs.setIsRight(1);
             logs.setIsRight(1);
             courseAnswerLogsMapper.insertFsCourseAnswerLogs(logs);
             courseAnswerLogsMapper.insertFsCourseAnswerLogs(logs);
             return R.ok("答题成功");
             return R.ok("答题成功");

+ 25 - 4
fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java

@@ -41,6 +41,7 @@ import com.fs.course.service.IFsCourseWatchLogService;
 import com.fs.course.service.IFsUserCoursePeriodDaysService;
 import com.fs.course.service.IFsUserCoursePeriodDaysService;
 import com.fs.course.service.IFsUserCoursePeriodService;
 import com.fs.course.service.IFsUserCoursePeriodService;
 import com.fs.course.service.cache.IFsUserCourseVideoCacheService;
 import com.fs.course.service.cache.IFsUserCourseVideoCacheService;
+import com.fs.course.util.CourseRatingAnswerExtract;
 import com.fs.course.vo.*;
 import com.fs.course.vo.*;
 import com.fs.his.config.FsSysConfig;
 import com.fs.his.config.FsSysConfig;
 import com.fs.his.domain.FsUser;
 import com.fs.his.domain.FsUser;
@@ -1784,11 +1785,11 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
 //        FsUserCoursePeriod fsUserCoursePeriod = fsUserCoursePeriodMapper.selectFsUserCoursePeriodById(periodId);
 //        FsUserCoursePeriod fsUserCoursePeriod = fsUserCoursePeriodMapper.selectFsUserCoursePeriodById(periodId);
 
 
 
 
-        // 2. 统计累计观看人数(对userId去重
+        // 2. 统计累计到课人数(对 userId 去重,需存在 duration>0 的看课记录,与「实际看课」i.1 一致
         Long totalWatchCount = fsCourseWatchLogMapper.countDistinctWatchUsers(videoId, periodId,companyId,companyUserId);
         Long totalWatchCount = fsCourseWatchLogMapper.countDistinctWatchUsers(videoId, periodId,companyId,companyUserId);
         vo.setTotalWatchCount(totalWatchCount != null ? totalWatchCount : 0L);
         vo.setTotalWatchCount(totalWatchCount != null ? totalWatchCount : 0L);
 
 
-        // 3. 统计累计完课人数(duration >= 1200秒,即20分钟,对userId去重)
+        // 3. 统计累计完课人数(log_type=2 且 duration>0,对 userId 去重,与「实际看课」i.2 一致
         Long totalCompleteCount = fsCourseWatchLogMapper.countDistinctCompleteUsers(videoId, periodId,companyId,companyUserId);
         Long totalCompleteCount = fsCourseWatchLogMapper.countDistinctCompleteUsers(videoId, periodId,companyId,companyUserId);
         vo.setTotalCompleteCount(totalCompleteCount != null ? totalCompleteCount : 0L);
         vo.setTotalCompleteCount(totalCompleteCount != null ? totalCompleteCount : 0L);
 
 
@@ -1926,7 +1927,9 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
         if (param == null || param.getVideoId() == null || param.getPeriodId() == null) {
         if (param == null || param.getVideoId() == null || param.getPeriodId() == null) {
             return Collections.emptyList();
             return Collections.emptyList();
         }
         }
-        return fsCourseWatchLogMapper.selectCourseStatisticsUserDetailList(param);
+        List<CourseStatisticsUserDetailVO> list = fsCourseWatchLogMapper.selectCourseStatisticsUserDetailList(param);
+        applyCourseRatingAnswerDisplay(list);
+        return list;
     }
     }
 
 
     @Override
     @Override
@@ -1934,7 +1937,25 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
         if (param == null || param.getVideoId() == null || param.getPeriodId() == null) {
         if (param == null || param.getVideoId() == null || param.getPeriodId() == null) {
             return Collections.emptyList();
             return Collections.emptyList();
         }
         }
-        return fsCourseWatchLogMapper.selectCourseStatisticsUserDetailExportList(param);
+        List<CourseStatisticsUserDetailVO> list = fsCourseWatchLogMapper.selectCourseStatisticsUserDetailExportList(param);
+        applyCourseRatingAnswerDisplay(list);
+        return list;
+    }
+
+    private void applyCourseRatingAnswerDisplay(List<CourseStatisticsUserDetailVO> list) {
+        if (CollectionUtils.isEmpty(list)) {
+            return;
+        }
+        for (CourseStatisticsUserDetailVO vo : list) {
+            if (vo == null) {
+                continue;
+            }
+            String raw = vo.getCourseRating();
+            if (raw == null || raw.isEmpty()) {
+                continue;
+            }
+            vo.setCourseRating(CourseRatingAnswerExtract.toDisplayCourseRating(raw));
+        }
     }
     }
 
 
     @Override
     @Override

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

@@ -139,8 +139,8 @@ public class FsUserCourseServiceImpl implements IFsUserCourseService
 
 
     private static final String userRealLink = "/pages/user/users/becomeVIP?";
     private static final String userRealLink = "/pages/user/users/becomeVIP?";
 
 
-    private static final String appRealLink = "/#/pages_course/videovip?course=";
-    public static final String appShortLink = "/#/pages_course/videovip?s=";
+    private static final String appRealLink = "/courseH5/pages_course/videovip?course=";
+    public static final String appShortLink = "/courseH5/pages_course/videovip?s=";
 
 
     /**
     /**
      * 查询课程
      * 查询课程
@@ -593,7 +593,7 @@ public class FsUserCourseServiceImpl implements IFsUserCourseService
         BeanUtils.copyProperties(link, courseMap);
         BeanUtils.copyProperties(link, courseMap);
         courseMap.setProjectId(param.getProjectId());
         courseMap.setProjectId(param.getProjectId());
         String courseJson = JSON.toJSONString(courseMap);
         String courseJson = JSON.toJSONString(courseMap);
-        link.setRealLink(realLink + courseJson);
+        link.setRealLink(("1".equals(param.getType()) ?appRealLink:realLink) + courseJson);
 
 
         link.setCreateTime(new Date());
         link.setCreateTime(new Date());
 
 
@@ -602,10 +602,16 @@ public class FsUserCourseServiceImpl implements IFsUserCourseService
         link.setUpdateTime(calendar.getTime());
         link.setUpdateTime(calendar.getTime());
         int i = fsCourseLinkMapper.insertFsCourseLink(link);
         int i = fsCourseLinkMapper.insertFsCourseLink(link);
         if (i > 0){
         if (i > 0){
-            String domainName = getDomainName(param.getCompanyUserId(), config);
-            String sortLink = domainName + shortLink + link.getLink();
-//            return R.ok().put("url", sortLink).put("link", random);
-            return R.ok().put("url", sortLink).put("link", random).put("linkId", link.getLinkId());
+            if ("1".equals(param.getType())){
+                String domainName = getDomainName(param.getCompanyUserId(), config);
+                String sortLink = domainName+ link.getRealLink().replace("/#","");
+                sortLink = sortLink.replaceAll("\\\\", "");
+                return R.ok().put("url", sortLink).put("link", random).put("linkId", link.getLinkId());
+            }else {
+                String domainName = getDomainName(param.getCompanyUserId(), config);
+                String sortLink = domainName + shortLink + link.getLink();
+                return R.ok().put("url", sortLink).put("link", random).put("linkId", link.getLinkId());
+            }
         }
         }
         return R.error("生成链接失败!");
         return R.error("生成链接失败!");
     }
     }

+ 55 - 0
fs-service/src/main/java/com/fs/course/util/CourseConfigUserAnswerExpose.java

@@ -0,0 +1,55 @@
+package com.fs.course.util;
+
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+
+/**
+ * 点播配置 course.config(参数键 course.config):与 JSON 字段 validateAnswerWhenWatch 相关的判定。
+ */
+public final class CourseConfigUserAnswerExpose {
+
+    private CourseConfigUserAnswerExpose() {
+    }
+
+    /**
+     * 当 JSON 中存在 validateAnswerWhenWatch 且非 null,并表示「不校验答案」(false / 0 / "0" / "false")时返回 true。
+     */
+    public static boolean isValidateAnswerWhenWatchDisabled(String courseConfigJson) {
+        if (StrUtil.isBlank(courseConfigJson)) {
+            return false;
+        }
+        try {
+            JSONObject obj = JSONUtil.parseObj(courseConfigJson);
+            if (!obj.containsKey("validateAnswerWhenWatch")) {
+                return false;
+            }
+            Object v = obj.get("validateAnswerWhenWatch");
+            if (v == null) {
+                return false;
+            }
+            if (Boolean.FALSE.equals(v)) {
+                return true;
+            }
+            String s = String.valueOf(v).trim();
+            return "0".equals(s) || "false".equalsIgnoreCase(s);
+        } catch (Exception e) {
+            return false;
+        }
+    }
+
+    /**
+     * 当 JSON 中存在 validateAnswerWhenWatch 且表示「不校验答案」(false / 0 / "0" / "false")时返回 true,
+     * 此时课程小结用户详情/导出可展示「课程评分」字段(答题内容)。
+     */
+    public static boolean includeUserAnswerContentInStatistics(String courseConfigJson) {
+        return isValidateAnswerWhenWatchDisabled(courseConfigJson);
+    }
+
+    /**
+     * 会员/ H5 提交答题时是否跳过对错校验:关闭校验时任意作答视为全对,写入正确答题日志,可走后续积分/红包领取流程。
+     */
+    public static boolean skipValidateAnswerOnSubmit(String courseConfigJson) {
+        return isValidateAnswerWhenWatchDisabled(courseConfigJson);
+    }
+}

+ 57 - 0
fs-service/src/main/java/com/fs/course/util/CourseRatingAnswerExtract.java

@@ -0,0 +1,57 @@
+package com.fs.course.util;
+
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONArray;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+
+/**
+ * 从答题日志 question_json(JSON 数组)中提取各题 {@code answer},作为统计/导出中的课程评分展示值。
+ */
+public final class CourseRatingAnswerExtract {
+
+    private CourseRatingAnswerExtract() {
+    }
+
+    /**
+     * 解析数组元素中的 {@code answer};多条时用中文分号拼接。非 JSON 数组或解析失败时返回原文。
+     */
+    public static String toDisplayCourseRating(String questionJson) {
+        if (StrUtil.isBlank(questionJson)) {
+            return questionJson;
+        }
+        String trimmed = questionJson.trim();
+        if (!trimmed.startsWith("[")) {
+            return questionJson;
+        }
+        try {
+            JSONArray arr = JSONUtil.parseArray(questionJson);
+            if (arr == null || arr.isEmpty()) {
+                return questionJson;
+            }
+            StringBuilder sb = new StringBuilder();
+            for (int i = 0; i < arr.size(); i++) {
+                JSONObject o = arr.getJSONObject(i);
+                if (o == null || !o.containsKey("answer")) {
+                    continue;
+                }
+                Object a = o.get("answer");
+                if (a == null) {
+                    continue;
+                }
+                String s = String.valueOf(a).trim();
+                if (s.isEmpty()) {
+                    continue;
+                }
+                if (sb.length() > 0) {
+                    sb.append(';');
+                }
+                sb.append(s);
+                break;
+            }
+            return sb.length() > 0 ? sb.toString() : questionJson;
+        } catch (Exception e) {
+            return questionJson;
+        }
+    }
+}

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

@@ -31,4 +31,10 @@ public class CourseStatisticsUserDetailVO implements Serializable {
     private String companyName;
     private String companyName;
     @Excel(name = "销售名称")
     @Excel(name = "销售名称")
     private String salesName;
     private String salesName;
+
+    /**
+     * 看课不校验答案时导出/列表展示:用户最近一次提交答题的 question_json 原文(多题为 JSON 字符串)
+     */
+    @Excel(name = "课程评分", width = 60)
+    private String courseRating;
 }
 }

+ 4 - 0
fs-service/src/main/java/com/fs/erp/service/impl/FsJstAftersalePushScrmServiceImpl.java

@@ -197,6 +197,10 @@ public class FsJstAftersalePushScrmServiceImpl implements FsJstAftersalePushScrm
             itemDTO.setQty(cartDTO.getNum());
             itemDTO.setQty(cartDTO.getNum());
             itemDTO.setAmount(cartDTO.getPrice());
             itemDTO.setAmount(cartDTO.getPrice());
             itemDTO.setType("退货");
             itemDTO.setType("退货");
+            if (StringUtils.isNotEmpty(fsStoreOrder.getOuterOiId())) {
+                itemDTO.setOuterOiId(fsStoreOrder.getOuterOiId());
+            }
+
             refundItemDTOS.add(itemDTO);
             refundItemDTOS.add(itemDTO);
         }
         }
         dto.setItems(refundItemDTOS);
         dto.setItems(refundItemDTOS);

+ 93 - 4
fs-service/src/main/java/com/fs/erp/service/impl/JSTErpOrderServiceImpl.java

@@ -29,6 +29,7 @@ import com.fs.his.service.IFsStoreProductService;
 import com.fs.hisStore.domain.FsStoreOrderItemScrm;
 import com.fs.hisStore.domain.FsStoreOrderItemScrm;
 import com.fs.hisStore.domain.FsStoreOrderScrm;
 import com.fs.hisStore.domain.FsStoreOrderScrm;
 import com.fs.hisStore.domain.FsStoreProductScrm;
 import com.fs.hisStore.domain.FsStoreProductScrm;
+import com.fs.hisStore.mapper.FsStoreOrderScrmMapper;
 import com.fs.hisStore.service.IFsStoreOrderItemScrmService;
 import com.fs.hisStore.service.IFsStoreOrderItemScrmService;
 import com.fs.hisStore.service.IFsStoreOrderScrmService;
 import com.fs.hisStore.service.IFsStoreOrderScrmService;
 import com.fs.hisStore.service.IFsStoreProductScrmService;
 import com.fs.hisStore.service.IFsStoreProductScrmService;
@@ -54,6 +55,10 @@ import java.util.stream.Collectors;
 @Slf4j
 @Slf4j
 @Service
 @Service
 public class JSTErpOrderServiceImpl implements IErpOrderService {
 public class JSTErpOrderServiceImpl implements IErpOrderService {
+
+    /** 合并订单:按 linkOId 跟进拉主单的最大轮数 */
+    private static final int JST_MERGED_FOLLOW_MAX_ROUNDS = 3;
+
     @Autowired
     @Autowired
     private JstErpHttpService jstErpHttpService;
     private JstErpHttpService jstErpHttpService;
 
 
@@ -63,6 +68,9 @@ public class JSTErpOrderServiceImpl implements IErpOrderService {
     @Autowired
     @Autowired
     private IFsStoreOrderScrmService fsStoreOrderScrmService;
     private IFsStoreOrderScrmService fsStoreOrderScrmService;
 
 
+    @Autowired
+    private FsStoreOrderScrmMapper fsStoreOrderScrmMapper;
+
     @Autowired
     @Autowired
     private IFsStoreOrderItemService fsStoreOrderItemService;
     private IFsStoreOrderItemService fsStoreOrderItemService;
     @Autowired
     @Autowired
@@ -535,10 +543,26 @@ public class JSTErpOrderServiceImpl implements IErpOrderService {
 
 
         // 5. 转换订单数据
         // 5. 转换订单数据
         if (query.getOrders() != null && !query.getOrders().isEmpty()) {
         if (query.getOrders() != null && !query.getOrders().isEmpty()) {
-            List<ErpOrderQuery> erpOrders = query.getOrders().stream()
-                    .map(this::convertToErpOrderQueryScrm)
-                    .collect(Collectors.toList());
-            response.setOrders(erpOrders);
+            OrderQueryResponseDTO.Order firstOrder = query.getOrders().get(0);
+
+            if (ErpQueryOrderStatusEnum.MERGED.getCode().equals(firstOrder.getStatus())
+                    && StringUtils.isNotEmpty(firstOrder.getLinkOId())) {
+                OrderQueryResponseDTO mergeQuery = followMergedJstQueryResponse(query, 1);
+                if (mergeQuery != null && mergeQuery.getOrders() != null && !mergeQuery.getOrders().isEmpty()) {
+                    List<ErpOrderQuery> erpOrders = mergeQuery.getOrders().stream()
+                            .map(this::convertToErpOrderQueryScrm)
+                            .collect(Collectors.toList());
+                    response.setOrders(erpOrders);
+                    persistOuterOiIdFromJstMergedOrder(firstOrder);
+                } else {
+                    response.setOrders(Collections.emptyList());
+                }
+            } else {
+                List<ErpOrderQuery> erpOrders = query.getOrders().stream()
+                        .map(this::convertToErpOrderQueryScrm)
+                        .collect(Collectors.toList());
+                response.setOrders(erpOrders);
+            }
         } else {
         } else {
             response.setOrders(Collections.emptyList());
             response.setOrders(Collections.emptyList());
         }
         }
@@ -858,7 +882,72 @@ public class JSTErpOrderServiceImpl implements IErpOrderService {
         return erpOrder;
         return erpOrder;
     }
     }
 
 
+    /**
+     * 合并单跟进成功后:取「原合并子单」firstOrder 明细首行的 outerOiId,按 soId=order_code 仅更新商城订单 outer_oi_id。
+     */
+    private void persistOuterOiIdFromJstMergedOrder(OrderQueryResponseDTO.Order firstOrder) {
+        if (firstOrder == null || StringUtils.isEmpty(firstOrder.getSoId())) {
+            return;
+        }
+        List<OrderQueryResponseDTO.OrderItem> items = firstOrder.getItems();
+        if (CollectionUtils.isEmpty(items)) {
+            return;
+        }
+        OrderQueryResponseDTO.OrderItem line = items.get(0);
+        if (line == null || StringUtils.isEmpty(line.getOuterOiId())) {
+            return;
+        }
+        int rows = fsStoreOrderScrmMapper.updateOuterOiIdByOrderCode(firstOrder.getSoId(), line.getOuterOiId());
+        if (rows > 0) {
+            log.info("合并订单:已回写 outerOiId,orderCode={} outerOiId={}", firstOrder.getSoId(), line.getOuterOiId());
+        }
+    }
+
+    private static String firstOrderSoId(OrderQueryResponseDTO query) {
+        if (query == null || CollectionUtils.isEmpty(query.getOrders())) {
+            return null;
+        }
+        return query.getOrders().get(0).getSoId();
+    }
 
 
+    /**
+     * 首单若为「被合并」且带 linkOId,则按 oId 整单重查明细,最多 maxRounds 轮。
+     */
+    private OrderQueryResponseDTO followMergedJstQueryResponse(OrderQueryResponseDTO query, int maxRounds) {
+        if (query == null || CollectionUtils.isEmpty(query.getOrders())) {
+            return query;
+        }
+        OrderQueryResponseDTO current = query;
+        for (int round = 0; round < maxRounds; round++) {
+            if (current.getOrders() == null || current.getOrders().isEmpty()) {
+                break;
+            }
+            OrderQueryResponseDTO.Order first = current.getOrders().get(0);
+            if (!ErpQueryOrderStatusEnum.MERGED.getCode().equals(first.getStatus())
+                    || StringUtils.isEmpty(first.getLinkOId())) {
+                break;
+            }
+            long linkOid;
+            try {
+                linkOid = Long.parseLong(first.getLinkOId().trim());
+            } catch (NumberFormatException e) {
+                log.warn("聚水潭合单 linkOId 非数字: {}, soId={}", first.getLinkOId(), first.getSoId());
+                break;
+            }
+            OrderQueryRequestDTO requestDTO = new OrderQueryRequestDTO();
+            requestDTO.setOIds(Collections.singletonList(linkOid));
+            OrderQueryResponseDTO next = jstErpHttpService.query(requestDTO);
+            if (next == null || CollectionUtils.isEmpty(next.getOrders())) {
+                log.warn("聚水潭按 linkOId={} 重新查询无结果, 子单 soId={}", linkOid, first.getSoId());
+                break;
+            }
+            log.info("合单重新查询聚水潭: round={}/{}, linkOId={}, 子单 oId/soId={}/{}, 新单首条 oId/soId={}/{}",
+                    round + 1, maxRounds, linkOid, first.getOId(), first.getSoId(),
+                    next.getOrders().get(0).getOId(), next.getOrders().get(0).getSoId());
+            current = next;
+        }
+        return current;
+    }
 
 
     private ErpOrderQuery convertToErpOrderQueryScrm(OrderQueryResponseDTO.Order order) {
     private ErpOrderQuery convertToErpOrderQueryScrm(OrderQueryResponseDTO.Order order) {
         ErpOrderQuery erpOrder = new ErpOrderQuery();
         ErpOrderQuery erpOrder = new ErpOrderQuery();

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

@@ -160,6 +160,8 @@ public class FsUser extends BaseEntity
     private Integer isAddQw;//是否添加企微客服
     private Integer isAddQw;//是否添加企微客服
     private Integer isShow;//是否展示购买以及订单状态
     private Integer isShow;//是否展示购买以及订单状态
 
 
+    private Boolean isCompanyUser;// 是否是销售
+
     private Long parentId; //邀请人id
     private Long parentId; //邀请人id
 
 
     private String courseMaOpenId; //看课小程序openid
     private String courseMaOpenId; //看课小程序openid
@@ -190,6 +192,8 @@ public class FsUser extends BaseEntity
     /** app登录后不为null(表示是否下载app) */
     /** app登录后不为null(表示是否下载app) */
     private String historyApp;
     private String historyApp;
 
 
+    private  Long invitedBySalesId;//邀请人销售id
+
     private String appOpenId;
     private String appOpenId;
 
 
     private String appleKey; // 苹果key登陆验证
     private String appleKey; // 苹果key登陆验证

+ 2 - 0
fs-service/src/main/java/com/fs/his/service/impl/FsPackageOrderServiceImpl.java

@@ -573,6 +573,7 @@ public class FsPackageOrderServiceImpl implements IFsPackageOrderService
 
 
         FsPackageOrder  order=new FsPackageOrder();
         FsPackageOrder  order=new FsPackageOrder();
         String orderSn =  OrderCodeUtils.getOrderSn();
         String orderSn =  OrderCodeUtils.getOrderSn();
+//        String orderSn =  IdUtil.getSnowflake(0, 0).nextIdStr();
         if(StringUtils.isEmpty(orderSn)){
         if(StringUtils.isEmpty(orderSn)){
             return R.error("订单生成失败,请重试");
             return R.error("订单生成失败,请重试");
         }
         }
@@ -1231,6 +1232,7 @@ public class FsPackageOrderServiceImpl implements IFsPackageOrderService
         if(user!=null){
         if(user!=null){
             if(fsPackageOrder.getPayMoney().compareTo(new BigDecimal(0))==1){
             if(fsPackageOrder.getPayMoney().compareTo(new BigDecimal(0))==1){
                 String payCode =  OrderCodeUtils.getOrderSn();
                 String payCode =  OrderCodeUtils.getOrderSn();
+//                String payCode =  IdUtil.getSnowflake(0, 0).nextIdStr();
                 if(StringUtils.isEmpty(payCode)){
                 if(StringUtils.isEmpty(payCode)){
                     return R.error("订单生成失败,请重试");
                     return R.error("订单生成失败,请重试");
                 }
                 }

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

@@ -27,6 +27,9 @@ public class FsStoreOrderScrm extends BaseEntity
     @Excel(name = "订单号")
     @Excel(name = "订单号")
     private String orderCode;
     private String orderCode;
 
 
+    /** 聚水潭外部订单明细ID(outerOiId,合并单等场景取明细首行) */
+    private String outerOiId;
+
     /** 额外订单号 */
     /** 额外订单号 */
     private String extendOrderId;
     private String extendOrderId;
 
 

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

@@ -350,6 +350,10 @@ public class FsStoreProductScrm extends BaseEntity
     @Excel(name = "限购数量")
     @Excel(name = "限购数量")
     private Integer purchaseLimit;
     private Integer purchaseLimit;
 
 
+    /** 单次购买数量上限(0 表示不限制) */
+    @Excel(name = "单次购买数量")
+    private Integer singlePurchaseLimit;
+
     @TableField(exist = false)
     @TableField(exist = false)
     private String onShelfTime;
     private String onShelfTime;
 
 

+ 176 - 104
fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreOrderItemScrmMapper.java

@@ -88,16 +88,17 @@ public interface FsStoreOrderItemScrmMapper
             " left join company_tcm_schedule cts on cts.id = o.schedule_id " +
             " left join company_tcm_schedule cts on cts.id = o.schedule_id " +
             " left join fs_store_product_scrm psps on i.product_id=psps.product_id " +
             " left join fs_store_product_scrm psps on i.product_id=psps.product_id " +
             " left join fs_store_product_category_scrm fspcs on fspcs.cate_id=psps.cate_id " +
             " left join fs_store_product_category_scrm fspcs on fspcs.cate_id=psps.cate_id " +
-            "            LEFT JOIN (\n" +
-            "            SELECT\n" +
-            "            sp.*,\n" +
-            "            ROW_NUMBER() OVER (PARTITION BY sp.business_code ORDER BY sp.create_time DESC) as rn\n" +
-            "            FROM fs_store_payment_scrm sp\n" +
-            "            WHERE sp.business_code IS NOT NULL\n" +
-            "            ) sp_latest ON sp_latest.business_code = o.order_code AND sp_latest.rn = 1\n" +
+            " LEFT JOIN (" +
+            " SELECT sp.*, ROW_NUMBER() OVER (PARTITION BY sp.business_code ORDER BY sp.create_time DESC) as rn" +
+            " FROM fs_store_payment_scrm sp WHERE sp.business_code IS NOT NULL" +
+            " ) sp_latest ON sp_latest.order_id = o.id AND sp_latest.rn = 1" +
+            " LEFT JOIN fs_course_play_source_config csc ON csc.appid = sp_latest.app_id" +
+            " <if test=\"maps.erpAccount != null and maps.erpAccount != ''\">" +
+            " LEFT JOIN fs_store_order_df df on df.order_id=o.id " +
+            " </if>" +
             " where 1=1 " +
             " where 1=1 " +
-            "<if test=\"maps.bankTransactionId !=null and maps.bankTransactionId!=''\">" +
-            " and sp_latest.bank_transaction_id = #{maps.bankTransactionId} " +
+            "<if test=\"maps.appId != null and maps.appId != ''\">" +
+            " and csc.appid = #{maps.appId} " +
             "</if>" +
             "</if>" +
             "<if test=\"maps.orderCodes != null  and maps.orderCodes.size > 0\">" +
             "<if test=\"maps.orderCodes != null  and maps.orderCodes.size > 0\">" +
             " and o.order_code in" +
             " and o.order_code in" +
@@ -108,12 +109,24 @@ public interface FsStoreOrderItemScrmMapper
             "<if test = 'maps.orderCode != null and  maps.orderCode !=\"\"    '> " +
             "<if test = 'maps.orderCode != null and  maps.orderCode !=\"\"    '> " +
             "and o.order_code like CONCAT('%',#{maps.orderCode},'%') " +
             "and o.order_code like CONCAT('%',#{maps.orderCode},'%') " +
             "</if>" +
             "</if>" +
-            "<if test = 'maps.deliveryId != null    '> " +
-            "and o.delivery_id =#{maps.deliveryId} " +
+            "<if test=\"maps.bankTransactionId != null and  maps.bankTransactionId !=''\">" +
+            " and sp_latest.bank_transaction_id like CONCAT('%',#{maps.bankTransactionId},'%') " +
+            "</if>" +
+            "<if test=\"maps.isPayRemain != null\">" +
+            " and o.is_pay_remain =#{maps.isPayRemain} " +
+            "</if>" +
+            "<if test=\"maps.userId != null\">" +
+            " and o.user_id =#{maps.userId} " +
+            "</if>" +
+            "<if test=\"maps.deliveryId != null and  maps.deliveryId !=''\">" +
+            " and o.delivery_id =#{maps.deliveryId} " +
             "</if>" +
             "</if>" +
             "<if test = 'maps.nickname != null and  maps.nickname !=\"\"     '> " +
             "<if test = 'maps.nickname != null and  maps.nickname !=\"\"     '> " +
             "and u.nickname like CONCAT('%',#{maps.nickname},'%') " +
             "and u.nickname like CONCAT('%',#{maps.nickname},'%') " +
             "</if>" +
             "</if>" +
+            "<if test=\"maps.realName != null and  maps.realName !=''\">" +
+            " and o.real_name like CONCAT('%',#{maps.realName},'%') " +
+            "</if>" +
             "<if test = 'maps.phone != null and  maps.phone !=\"\"     '> " +
             "<if test = 'maps.phone != null and  maps.phone !=\"\"     '> " +
             "and u.phone like CONCAT('%',#{maps.phone},'%') " +
             "and u.phone like CONCAT('%',#{maps.phone},'%') " +
             "</if>" +
             "</if>" +
@@ -126,19 +139,26 @@ public interface FsStoreOrderItemScrmMapper
             "<if test = 'maps.status != null and maps.status == 6    '> " +
             "<if test = 'maps.status != null and maps.status == 6    '> " +
             "and o.`status`= 1 and (o.extend_order_id is null or o.extend_order_id like '') " +
             "and o.`status`= 1 and (o.extend_order_id is null or o.extend_order_id like '') " +
             "</if>" +
             "</if>" +
+            "<if test = 'maps.isUpload != null and maps.isUpload == 0    '> " +
+            "and o.certificates is null  " +
+            "</if>" +
+            "<if test = 'maps.isUpload != null and maps.isUpload == 1    '> " +
+            "and o.certificates is not null " +
+            "</if>" +
+            "<if test=\"maps.deliveryStatus != null     \">" +
+            " and o.delivery_status =#{maps.deliveryStatus}" +
+            "</if>" +
+            "<if test=\"maps.deliveryPayStatus != null  \">" +
+            " and o.delivery_pay_status =#{maps.deliveryPayStatus}" +
+            "</if>" +
             "<if test = 'maps.companyId != null    '> " +
             "<if test = 'maps.companyId != null    '> " +
             "and o.company_id =#{maps.companyId} " +
             "and o.company_id =#{maps.companyId} " +
             "</if>" +
             "</if>" +
-            "            <if test=\"maps.realName != null and  maps.realName !=''\">\n" +
-            "                and o.real_name like CONCAT('%',#{maps.realName},'%')\n" +
-            "            </if>"+
             "<if test = 'maps.isHealth != null and maps.isHealth !=  \"\"  '> " +
             "<if test = 'maps.isHealth != null and maps.isHealth !=  \"\"  '> " +
-//            "and (o.company_id is null or o.order_type = 2 ) " +
-            "              and (o.company_id is null\n" +
-            "                or o.order_type = 2 or o.order_type = 3)"+
+            " and (o.company_id is null or o.order_type = 2 or o.order_type = 3)" +
             "</if>" +
             "</if>" +
-            "<if test = 'maps.notHealth != null and maps.notHealth !=  \"\"  '> " +
-            "and o.company_id is not null " +
+            "<if test = 'maps.notHealth != null  '> " +
+            "and o.company_id is not null and o.order_type = 0 " +
             "</if>" +
             "</if>" +
             "<if test = 'maps.companyUserId != null    '> " +
             "<if test = 'maps.companyUserId != null    '> " +
             "and o.company_user_id =#{maps.companyUserId} " +
             "and o.company_user_id =#{maps.companyUserId} " +
@@ -146,52 +166,70 @@ public interface FsStoreOrderItemScrmMapper
             "<if test = 'maps.companyUserNickName != null and  maps.companyUserNickName !=  \"\" '> " +
             "<if test = 'maps.companyUserNickName != null and  maps.companyUserNickName !=  \"\" '> " +
             "and cu.nick_name like concat('%', #{maps.companyUserNickName}, '%') " +
             "and cu.nick_name like concat('%', #{maps.companyUserNickName}, '%') " +
             "</if>" +
             "</if>" +
+            "<if test=\"maps.payCode != null and maps.payCode != ''\">" +
+            " and sp_latest.pay_code like CONCAT('%', #{maps.payCode}, '%') " +
+            "</if>" +
+            "<if test=\"maps.productName != null and  maps.productName !=  '' \">" +
+            " and psps.product_name like concat('%', #{maps.productName}, '%') " +
+            "</if>" +
             "<if test = 'maps.orderType != null and maps.orderType != -1    '> " +
             "<if test = 'maps.orderType != null and maps.orderType != -1    '> " +
             "and o.order_type =#{maps.orderType} " +
             "and o.order_type =#{maps.orderType} " +
             "</if>" +
             "</if>" +
             "<if test = 'maps.orderType != null and maps.orderType == -1    '> " +
             "<if test = 'maps.orderType != null and maps.orderType == -1    '> " +
             "and o.order_type in (2, 3) " +
             "and o.order_type in (2, 3) " +
             "</if>" +
             "</if>" +
+            "<if test = 'maps.payType != null    '> " +
+            "and o.pay_type =#{maps.payType} " +
+            "</if>" +
+            "<if test = 'maps.scheduleId != null    '> " +
+            "and o.schedule_id =#{maps.scheduleId} " +
+            "</if>" +
             "<if test = 'maps.createTimeList != null    '> " +
             "<if test = 'maps.createTimeList != null    '> " +
             " AND date_format(o.create_time,'%y%m%d') &gt;= date_format(#{maps.createTimeList[0]},'%y%m%d') " +
             " AND date_format(o.create_time,'%y%m%d') &gt;= date_format(#{maps.createTimeList[0]},'%y%m%d') " +
             " AND date_format(o.create_time,'%y%m%d') &lt;= date_format(#{maps.createTimeList[1]},'%y%m%d') " +
             " AND date_format(o.create_time,'%y%m%d') &lt;= date_format(#{maps.createTimeList[1]},'%y%m%d') " +
             "</if>" +
             "</if>" +
-            "<if test = 'maps.payTimeList != null    '> " +
-            " AND date_format(o.pay_time,'%y%m%d') &gt;= date_format(#{maps.payTimeList[0]},'%y%m%d') " +
-            " AND date_format(o.pay_time,'%y%m%d') &lt;= date_format(#{maps.payTimeList[1]},'%y%m%d') " +
-            "</if>" +
             "<if test = 'maps.deliverySendTimeList != null    '> " +
             "<if test = 'maps.deliverySendTimeList != null    '> " +
             " AND date_format(o.delivery_send_time,'%y%m%d') &gt;= date_format(#{maps.deliverySendTimeList[0]},'%y%m%d') " +
             " AND date_format(o.delivery_send_time,'%y%m%d') &gt;= date_format(#{maps.deliverySendTimeList[0]},'%y%m%d') " +
             " AND date_format(o.delivery_send_time,'%y%m%d') &lt;= date_format(#{maps.deliverySendTimeList[1]},'%y%m%d') " +
             " AND date_format(o.delivery_send_time,'%y%m%d') &lt;= date_format(#{maps.deliverySendTimeList[1]},'%y%m%d') " +
             "</if>" +
             "</if>" +
+            "<if test = 'maps.paidStatus != null    '> " +
+            "and o.paid =#{maps.paidStatus} " +
+            "</if>" +
+            "<if test = 'maps.payTimeList != null    '> " +
+            " AND date_format(o.pay_time,'%y%m%d') &gt;= date_format(#{maps.payTimeList[0]},'%y%m%d') " +
+            " AND date_format(o.pay_time,'%y%m%d') &lt;= date_format(#{maps.payTimeList[1]},'%y%m%d') " +
+            "</if>" +
             "<if test = 'maps.deliveryImportTimeList != null    '> " +
             "<if test = 'maps.deliveryImportTimeList != null    '> " +
             " AND date_format(o.delivery_import_time,'%y%m%d') &gt;= date_format(#{maps.deliveryImportTimeList[0]},'%y%m%d') " +
             " AND date_format(o.delivery_import_time,'%y%m%d') &gt;= date_format(#{maps.deliveryImportTimeList[0]},'%y%m%d') " +
             " AND date_format(o.delivery_import_time,'%y%m%d') &lt;= date_format(#{maps.deliveryImportTimeList[1]},'%y%m%d') " +
             " AND date_format(o.delivery_import_time,'%y%m%d') &lt;= date_format(#{maps.deliveryImportTimeList[1]},'%y%m%d') " +
             "</if>" +
             "</if>" +
-            "<if test = 'maps.isUpload != null and maps.isUpload == 0    '> " +
-            "and o.certificates is null  " +
+            "<if test=\"maps.deptId != null\">" +
+            " AND (cu.dept_id = #{maps.deptId} OR cu.dept_id IN (" +
+            " SELECT t.dept_id FROM company_dept t WHERE find_in_set(#{maps.deptId}, ancestors)" +
+            " ))" +
             "</if>" +
             "</if>" +
-            "<if test = 'maps.isUpload != null and maps.isUpload == 1    '> " +
-            "and o.certificates is not null " +
+            "<if test=\"maps.erpPhoneNumber != null and maps.erpPhoneNumber != ''\">" +
+            " and o.erp_phone like concat(#{maps.erpPhoneNumber},'%') " +
             "</if>" +
             "</if>" +
-            "<if test = 'maps.scheduleId != null    '> " +
-            "and o.schedule_id =#{maps.scheduleId} " +
+            "<if test=\"maps.erpAccount != null and maps.erpAccount != '未分拣' and maps.erpAccount != ''\">" +
+            " and df.login_account like #{maps.erpAccount} " +
+            "</if>" +
+            "<if test=\"maps.erpAccount == '未分拣'\">" +
+            " and ( df.login_account is null or df.login_account like '') " +
             "</if>" +
             "</if>" +
-            "            <if test=\"maps.deliveryPayStatus != null  \">\n" +
-            "                and o.delivery_pay_status =#{maps.deliveryPayStatus}\n" +
-            "            </if>"+
-            "            <if test=\"maps.deliveryStatus != null     \">\n" +
-            "                and o.delivery_status =#{maps.deliveryStatus}\n" +
-            "            </if>"+
-            "           <if test=\"maps.productName != null and  maps.productName !=  '' \">\n" +
-            "                and psps.product_name like concat('%', #{maps.productName}, '%')\n" +
-            "            </if>"+
             "<if test = 'maps.isAudit != null'> " +
             "<if test = 'maps.isAudit != null'> " +
-            "and o.is_audit = #{maps.isAudit} \n" +
+            "and o.is_audit = #{maps.isAudit} " +
+            "</if>" +
+            "<if test=\"maps.isCompanyOrder != null and maps.isCompanyOrder > 0\">" +
+            " and o.order_type != 3 and o.order_type != 2" +
             "</if>" +
             "</if>" +
-            "            <if test=\"maps.isCompanyOrder != null and maps.isCompanyOrder > 0\">\n" +
-            "                and o.order_type != 3 and o.order_type != 2\n" +
-            "            </if>"+
+            "<if test=\"maps.companyUserIds != null  and maps.companyUserIds.size > 0\">" +
+            " and o.company_user_id in" +
+            " <foreach collection=\"maps.companyUserIds\" item=\"companyUserId\" open=\"(\" close=\")\" separator=\",\">" +
+            " #{companyUserId}" +
+            " </foreach>" +
+            "</if>" +
+            " ${maps.params.dataScope} " +
             "GROUP BY  o.id order by o.id desc limit 50000"+
             "GROUP BY  o.id order by o.id desc limit 50000"+
             "</script>"})
             "</script>"})
     List<FsStoreOrderItemExportVO> selectFsStoreOrderItemListExportVO(@Param("maps")FsStoreOrderParam fsStoreOrder);
     List<FsStoreOrderItemExportVO> selectFsStoreOrderItemListExportVO(@Param("maps")FsStoreOrderParam fsStoreOrder);
@@ -204,67 +242,78 @@ public interface FsStoreOrderItemScrmMapper
             "left join company c on c.company_id=o.company_id " +
             "left join company c on c.company_id=o.company_id " +
             "left join company_user cu on cu.user_id=o.company_user_id " +
             "left join company_user cu on cu.user_id=o.company_user_id " +
             "left join company_tcm_schedule cts on cts.id = o.schedule_id " +
             "left join company_tcm_schedule cts on cts.id = o.schedule_id " +
-            "LEFT JOIN fs_store_order_df df on df.order_id=o.id\n" +
-            "        <if test=\"maps.bankTransactionId !=null and maps.bankTransactionId!=''\">\n" +
-            "            LEFT JOIN (\n" +
-            "            SELECT\n" +
-            "            sp.*,\n" +
-            "            ROW_NUMBER() OVER (PARTITION BY sp.business_code ORDER BY sp.create_time DESC) as rn\n" +
-            "            FROM fs_store_payment_scrm sp\n" +
-            "            WHERE sp.business_code IS NOT NULL\n" +
-            "            ) sp_latest ON sp_latest.business_code = o.order_code AND sp_latest.rn = 1\n" +
-                        "<if test=\"maps.appId != null and maps.appId != ''\">" +
-            "            LEFT JOIN fs_course_play_source_config csc ON csc.appid = sp_latest.app_id\n" +
-                        "</if>" +
-            "        </if>" +
+            " left join fs_store_product_scrm psps on i.product_id=psps.product_id " +
+            " LEFT JOIN (" +
+            " SELECT sp.*, ROW_NUMBER() OVER (PARTITION BY sp.business_code ORDER BY sp.create_time DESC) as rn" +
+            " FROM fs_store_payment_scrm sp WHERE sp.business_code IS NOT NULL" +
+            " ) sp_latest ON sp_latest.order_id = o.id AND sp_latest.rn = 1" +
+            " LEFT JOIN fs_course_play_source_config csc ON csc.appid = sp_latest.app_id" +
+            " <if test=\"maps.erpAccount != null and maps.erpAccount != ''\">" +
+            " LEFT JOIN fs_store_order_df df on df.order_id=o.id " +
+            " </if>" +
             "where 1=1 " +
             "where 1=1 " +
-            "<if test=\"maps.bankTransactionId !=null and maps.bankTransactionId!=''\">" +
-            "and sp_latest.bank_transaction_id = #{maps.bankTransactionId}\n" +
-            "</if>" +
-            "<if test=\"maps.appId != null and maps.appId != ''\">\n" +
-            "   and csc.appid = #{maps.appId}\n" +
-            " </if>\n" +
-            "            <if test=\"maps.orderCodes != null  and maps.orderCodes.size > 0\">\n" +
-            "                and o.order_code in\n" +
-            "                <foreach collection=\"maps.orderCodes\" item=\"orderCode\" open=\"(\" close=\")\" separator=\",\">\n" +
-            "                    #{orderCode}\n" +
-            "                </foreach>\n" +
-            "            </if>" +
+            "<if test=\"maps.appId != null and maps.appId != ''\">" +
+            " and csc.appid = #{maps.appId} " +
+            "</if>" +
+            "<if test=\"maps.orderCodes != null  and maps.orderCodes.size > 0\">" +
+            " and o.order_code in" +
+            " <foreach collection=\"maps.orderCodes\" item=\"orderCode\" open=\"(\" close=\")\" separator=\",\">" +
+            "     #{orderCode}" +
+            " </foreach>" +
+            "</if>" +
             "<if test = 'maps.orderCode != null and  maps.orderCode !=\"\"    '> " +
             "<if test = 'maps.orderCode != null and  maps.orderCode !=\"\"    '> " +
             "and o.order_code like CONCAT('%',#{maps.orderCode},'%') " +
             "and o.order_code like CONCAT('%',#{maps.orderCode},'%') " +
             "</if>" +
             "</if>" +
-            "<if test = 'maps.deliveryId != null    '> " +
-            "and o.delivery_id =#{maps.deliveryId} " +
+            "<if test=\"maps.bankTransactionId != null and  maps.bankTransactionId !=''\">" +
+            " and sp_latest.bank_transaction_id like CONCAT('%',#{maps.bankTransactionId},'%') " +
+            "</if>" +
+            "<if test=\"maps.isPayRemain != null\">" +
+            " and o.is_pay_remain =#{maps.isPayRemain} " +
+            "</if>" +
+            "<if test=\"maps.userId != null\">" +
+            " and o.user_id =#{maps.userId} " +
+            "</if>" +
+            "<if test=\"maps.deliveryId != null and  maps.deliveryId !=''\">" +
+            " and o.delivery_id =#{maps.deliveryId} " +
             "</if>" +
             "</if>" +
             "<if test = 'maps.nickname != null and  maps.nickname !=\"\"     '> " +
             "<if test = 'maps.nickname != null and  maps.nickname !=\"\"     '> " +
             "and u.nickname like CONCAT('%',#{maps.nickname},'%') " +
             "and u.nickname like CONCAT('%',#{maps.nickname},'%') " +
             "</if>" +
             "</if>" +
+            "<if test=\"maps.realName != null and  maps.realName !=''\">" +
+            " and o.real_name like CONCAT('%',#{maps.realName},'%') " +
+            "</if>" +
             "<if test = 'maps.phone != null and  maps.phone !=\"\"     '> " +
             "<if test = 'maps.phone != null and  maps.phone !=\"\"     '> " +
             "and u.phone like CONCAT('%',#{maps.phone},'%') " +
             "and u.phone like CONCAT('%',#{maps.phone},'%') " +
             "</if>" +
             "</if>" +
-            "<if test = 'maps.realName != null and  maps.realName !=\"\"     '> " +
-            "and o.real_name like CONCAT('%',#{maps.realName},'%') " +
-            "</if>" +
             "<if test = 'maps.userPhone != null and  maps.userPhone !=\"\"     '> " +
             "<if test = 'maps.userPhone != null and  maps.userPhone !=\"\"     '> " +
             "and o.user_phone like CONCAT('%',#{maps.userPhone},'%') " +
             "and o.user_phone like CONCAT('%',#{maps.userPhone},'%') " +
             "</if>" +
             "</if>" +
-            "<if test=\"maps.status != null and maps.status != 6\">\n" +
-            "                and o.status = #{maps.status}\n" +
-            "            </if>\n" +
-            "            <if test=\"maps.status == 6\">\n" +
-            "                and o.`status`= 1\n" +
-            "\n" +
-            "                and  (o.extend_order_id is null or  o.extend_order_id like '')\n" +
-            "            </if>" +
-
+            "<if test = 'maps.status != null and maps.status != 6    '> " +
+            "and o.status =#{maps.status} " +
+            "</if>" +
+            "<if test = 'maps.status != null and maps.status == 6    '> " +
+            "and o.`status`= 1 and (o.extend_order_id is null or o.extend_order_id like '') " +
+            "</if>" +
+            "<if test = 'maps.isUpload != null and maps.isUpload == 0    '> " +
+            "and o.certificates is null  " +
+            "</if>" +
+            "<if test = 'maps.isUpload != null and maps.isUpload == 1    '> " +
+            "and o.certificates is not null " +
+            "</if>" +
+            "<if test=\"maps.deliveryStatus != null     \">" +
+            " and o.delivery_status =#{maps.deliveryStatus}" +
+            "</if>" +
+            "<if test=\"maps.deliveryPayStatus != null  \">" +
+            " and o.delivery_pay_status =#{maps.deliveryPayStatus}" +
+            "</if>" +
             "<if test = 'maps.companyId != null    '> " +
             "<if test = 'maps.companyId != null    '> " +
             "and o.company_id =#{maps.companyId} " +
             "and o.company_id =#{maps.companyId} " +
             "</if>" +
             "</if>" +
             "<if test = 'maps.isHealth != null and maps.isHealth !=  \"\"  '> " +
             "<if test = 'maps.isHealth != null and maps.isHealth !=  \"\"  '> " +
-            "and o.company_id is null " +
+            " and (o.company_id is null or o.order_type = 2 or o.order_type = 3)" +
             "</if>" +
             "</if>" +
-            "<if test = 'maps.notHealth != null and maps.notHealth !=  \"\"  '> " +
-            "and o.company_id is not null " +
+            "<if test = 'maps.notHealth != null  '> " +
+            "and o.company_id is not null and o.order_type = 0 " +
             "</if>" +
             "</if>" +
             "<if test = 'maps.companyUserId != null    '> " +
             "<if test = 'maps.companyUserId != null    '> " +
             "and o.company_user_id =#{maps.companyUserId} " +
             "and o.company_user_id =#{maps.companyUserId} " +
@@ -272,47 +321,70 @@ public interface FsStoreOrderItemScrmMapper
             "<if test = 'maps.companyUserNickName != null and  maps.companyUserNickName !=  \"\" '> " +
             "<if test = 'maps.companyUserNickName != null and  maps.companyUserNickName !=  \"\" '> " +
             "and cu.nick_name like concat('%', #{maps.companyUserNickName}, '%') " +
             "and cu.nick_name like concat('%', #{maps.companyUserNickName}, '%') " +
             "</if>" +
             "</if>" +
+            "<if test=\"maps.payCode != null and maps.payCode != ''\">" +
+            " and sp_latest.pay_code like CONCAT('%', #{maps.payCode}, '%') " +
+            "</if>" +
+            "<if test=\"maps.productName != null and  maps.productName !=  '' \">" +
+            " and psps.product_name like concat('%', #{maps.productName}, '%') " +
+            "</if>" +
             "<if test = 'maps.orderType != null and maps.orderType != -1    '> " +
             "<if test = 'maps.orderType != null and maps.orderType != -1    '> " +
             "and o.order_type =#{maps.orderType} " +
             "and o.order_type =#{maps.orderType} " +
             "</if>" +
             "</if>" +
             "<if test = 'maps.orderType != null and maps.orderType == -1    '> " +
             "<if test = 'maps.orderType != null and maps.orderType == -1    '> " +
             "and o.order_type in (2, 3) " +
             "and o.order_type in (2, 3) " +
             "</if>" +
             "</if>" +
+            "<if test = 'maps.payType != null    '> " +
+            "and o.pay_type =#{maps.payType} " +
+            "</if>" +
+            "<if test = 'maps.scheduleId != null    '> " +
+            "and o.schedule_id =#{maps.scheduleId} " +
+            "</if>" +
             "<if test = 'maps.createTimeList != null    '> " +
             "<if test = 'maps.createTimeList != null    '> " +
             " AND date_format(o.create_time,'%y%m%d') &gt;= date_format(#{maps.createTimeList[0]},'%y%m%d') " +
             " AND date_format(o.create_time,'%y%m%d') &gt;= date_format(#{maps.createTimeList[0]},'%y%m%d') " +
             " AND date_format(o.create_time,'%y%m%d') &lt;= date_format(#{maps.createTimeList[1]},'%y%m%d') " +
             " AND date_format(o.create_time,'%y%m%d') &lt;= date_format(#{maps.createTimeList[1]},'%y%m%d') " +
             "</if>" +
             "</if>" +
-            "<if test = 'maps.payTimeList != null    '> " +
-            " AND date_format(o.pay_time,'%y%m%d') &gt;= date_format(#{maps.payTimeList[0]},'%y%m%d') " +
-            " AND date_format(o.pay_time,'%y%m%d') &lt;= date_format(#{maps.payTimeList[1]},'%y%m%d') " +
-            "</if>" +
             "<if test = 'maps.deliverySendTimeList != null    '> " +
             "<if test = 'maps.deliverySendTimeList != null    '> " +
             " AND date_format(o.delivery_send_time,'%y%m%d') &gt;= date_format(#{maps.deliverySendTimeList[0]},'%y%m%d') " +
             " AND date_format(o.delivery_send_time,'%y%m%d') &gt;= date_format(#{maps.deliverySendTimeList[0]},'%y%m%d') " +
             " AND date_format(o.delivery_send_time,'%y%m%d') &lt;= date_format(#{maps.deliverySendTimeList[1]},'%y%m%d') " +
             " AND date_format(o.delivery_send_time,'%y%m%d') &lt;= date_format(#{maps.deliverySendTimeList[1]},'%y%m%d') " +
             "</if>" +
             "</if>" +
+            "<if test = 'maps.paidStatus != null    '> " +
+            "and o.paid =#{maps.paidStatus} " +
+            "</if>" +
+            "<if test = 'maps.payTimeList != null    '> " +
+            " AND date_format(o.pay_time,'%y%m%d') &gt;= date_format(#{maps.payTimeList[0]},'%y%m%d') " +
+            " AND date_format(o.pay_time,'%y%m%d') &lt;= date_format(#{maps.payTimeList[1]},'%y%m%d') " +
+            "</if>" +
             "<if test = 'maps.deliveryImportTimeList != null    '> " +
             "<if test = 'maps.deliveryImportTimeList != null    '> " +
             " AND date_format(o.delivery_import_time,'%y%m%d') &gt;= date_format(#{maps.deliveryImportTimeList[0]},'%y%m%d') " +
             " AND date_format(o.delivery_import_time,'%y%m%d') &gt;= date_format(#{maps.deliveryImportTimeList[0]},'%y%m%d') " +
             " AND date_format(o.delivery_import_time,'%y%m%d') &lt;= date_format(#{maps.deliveryImportTimeList[1]},'%y%m%d') " +
             " AND date_format(o.delivery_import_time,'%y%m%d') &lt;= date_format(#{maps.deliveryImportTimeList[1]},'%y%m%d') " +
             "</if>" +
             "</if>" +
-            "<if test = 'maps.isUpload != null and maps.isUpload == 0    '> " +
-            "and o.certificates is null  " +
+            "<if test=\"maps.deptId != null\">" +
+            " AND (cu.dept_id = #{maps.deptId} OR cu.dept_id IN (" +
+            " SELECT t.dept_id FROM company_dept t WHERE find_in_set(#{maps.deptId}, ancestors)" +
+            " ))" +
             "</if>" +
             "</if>" +
-            "<if test = 'maps.isUpload != null and maps.isUpload == 1    '> " +
-            "and o.certificates is not null " +
+            "<if test=\"maps.erpPhoneNumber != null and maps.erpPhoneNumber != ''\">" +
+            " and o.erp_phone like concat(#{maps.erpPhoneNumber},'%') " +
             "</if>" +
             "</if>" +
-            "<if test = 'maps.scheduleId != null    '> " +
-            "and o.schedule_id =#{maps.scheduleId} " +
+            "<if test=\"maps.erpAccount != null and maps.erpAccount != '未分拣' and maps.erpAccount != ''\">" +
+            " and df.login_account like #{maps.erpAccount} " +
+            "</if>" +
+            "<if test=\"maps.erpAccount == '未分拣'\">" +
+            " and ( df.login_account is null or df.login_account like '') " +
+            "</if>" +
+            "<if test = 'maps.isAudit != null'> " +
+            "and o.is_audit = #{maps.isAudit} " +
+            "</if>" +
+            "<if test=\"maps.isCompanyOrder != null and maps.isCompanyOrder > 0\">" +
+            " and o.order_type != 3 and o.order_type != 2" +
+            "</if>" +
+            "<if test=\"maps.companyUserIds != null  and maps.companyUserIds.size > 0\">" +
+            " and o.company_user_id in" +
+            " <foreach collection=\"maps.companyUserIds\" item=\"companyUserId\" open=\"(\" close=\")\" separator=\",\">" +
+            " #{companyUserId}" +
+            " </foreach>" +
             "</if>" +
             "</if>" +
-            "<if test=\"maps.erpPhoneNumber != null and maps.erpPhoneNumber != ''\">\n" +
-            "                and o.erp_phone like concat(#{maps.erpPhoneNumber},'%')\n" +
-            "            </if>\n" +
-            "            <if test=\"maps.erpAccount != null and maps.erpAccount != '未分拣' and maps.erpAccount != ''\">\n" +
-            "                and df.login_account like #{maps.erpAccount}\n" +
-            "            </if>\n" +
-            "            <if test=\"maps.erpAccount == '未分拣'\">\n" +
-            "                and ( df.login_account is null or df.login_account like '')\n" +
-            "            </if>" +
-            " order by o.id desc "+
+            " ${maps.params.dataScope} " +
             "</script>"})
             "</script>"})
     Long itemsCount(@Param("maps")FsStoreOrderParam fsStoreOrder);
     Long itemsCount(@Param("maps")FsStoreOrderParam fsStoreOrder);
 
 

+ 28 - 7
fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreOrderScrmMapper.java

@@ -493,6 +493,12 @@ public interface FsStoreOrderScrmMapper
     List<FsMyStoreOrderListQueryVO> selectFsMyStoreOrderListVO(@Param("maps")FsMyStoreOrderQueryParam param);
     List<FsMyStoreOrderListQueryVO> selectFsMyStoreOrderListVO(@Param("maps")FsMyStoreOrderQueryParam param);
     @Select("select * from fs_store_order_scrm where order_code=#{orderCode}")
     @Select("select * from fs_store_order_scrm where order_code=#{orderCode}")
     FsStoreOrderScrm selectFsStoreOrderByOrderCode(String orderCode);
     FsStoreOrderScrm selectFsStoreOrderByOrderCode(String orderCode);
+
+    /**
+     * 仅更新聚水潭外部订单明细ID
+     */
+    @Update("UPDATE fs_store_order_scrm SET outer_oi_id = #{outerOiId} WHERE order_code = #{orderCode}")
+    int updateOuterOiIdByOrderCode(@Param("orderCode") String orderCode, @Param("outerOiId") String outerOiId);
     @Update("update fs_store_order_scrm set status=-3 where id=#{orderId}")
     @Update("update fs_store_order_scrm set status=-3 where id=#{orderId}")
     int cancelOrder(Long orderId);
     int cancelOrder(Long orderId);
     @Select({"<script> " +
     @Select({"<script> " +
@@ -677,6 +683,12 @@ public interface FsStoreOrderScrmMapper
             "<if test=\"maps.bankTransactionId !=null and maps.bankTransactionId!=''\">" +
             "<if test=\"maps.bankTransactionId !=null and maps.bankTransactionId!=''\">" +
             " and sp_latest.bank_transaction_id = #{maps.bankTransactionId} " +
             " and sp_latest.bank_transaction_id = #{maps.bankTransactionId} " +
             "</if>" +
             "</if>" +
+            "<if test=\"maps.orderCodes != null  and maps.orderCodes.size > 0\">" +
+            " and o.order_code in" +
+            " <foreach collection=\"maps.orderCodes\" item=\"orderCode\" open=\"(\" close=\")\" separator=\",\">" +
+            "     #{orderCode}" +
+            " </foreach>" +
+            "</if>" +
             "<if test = 'maps.orderCode != null and  maps.orderCode !=\"\"    '> " +
             "<if test = 'maps.orderCode != null and  maps.orderCode !=\"\"    '> " +
             "and o.order_code like CONCAT('%',#{maps.orderCode},'%') " +
             "and o.order_code like CONCAT('%',#{maps.orderCode},'%') " +
             "</if>" +
             "</if>" +
@@ -698,9 +710,12 @@ public interface FsStoreOrderScrmMapper
             "<if test = 'maps.userPhone != null and  maps.userPhone !=\"\"     '> " +
             "<if test = 'maps.userPhone != null and  maps.userPhone !=\"\"     '> " +
             "and o.user_phone like CONCAT('%',#{maps.userPhone},'%') " +
             "and o.user_phone like CONCAT('%',#{maps.userPhone},'%') " +
             "</if>" +
             "</if>" +
-            "<if test = 'maps.status != null    '> " +
+            "<if test = 'maps.status != null and maps.status != 6    '> " +
             "and o.status =#{maps.status} " +
             "and o.status =#{maps.status} " +
             "</if>" +
             "</if>" +
+            "<if test = 'maps.status != null and maps.status == 6    '> " +
+            "and o.`status`= 1 and (o.extend_order_id is null or o.extend_order_id like '') " +
+            "</if>" +
             "<if test = 'maps.deliveryStatus != null    '> " +
             "<if test = 'maps.deliveryStatus != null    '> " +
             "and o.delivery_status =#{maps.deliveryStatus} " +
             "and o.delivery_status =#{maps.deliveryStatus} " +
             "</if>" +
             "</if>" +
@@ -717,10 +732,10 @@ public interface FsStoreOrderScrmMapper
             "and sp_latest.pay_code like CONCAT('%', #{maps.payCode}, '%') " +
             "and sp_latest.pay_code like CONCAT('%', #{maps.payCode}, '%') " +
             "</if>" +
             "</if>" +
             "<if test = 'maps.isHealth != null and maps.isHealth !=  \"\"  '> " +
             "<if test = 'maps.isHealth != null and maps.isHealth !=  \"\"  '> " +
-            "and o.company_id is null " +
+            "and (o.company_id is null or o.order_type = 2 or o.order_type = 3) " +
             "</if>" +
             "</if>" +
             "<if test = 'maps.notHealth != null and maps.notHealth !=  \"\"  '> " +
             "<if test = 'maps.notHealth != null and maps.notHealth !=  \"\"  '> " +
-            "and o.company_id is not null " +
+            "and o.company_id is not null and o.order_type = 0 " +
             "</if>" +
             "</if>" +
             "<if test = 'maps.companyUserId != null    '> " +
             "<if test = 'maps.companyUserId != null    '> " +
             "and o.company_user_id =#{maps.companyUserId} " +
             "and o.company_user_id =#{maps.companyUserId} " +
@@ -731,9 +746,12 @@ public interface FsStoreOrderScrmMapper
             "<if test = 'maps.productName != null and maps.productName != \"\" '> " +
             "<if test = 'maps.productName != null and maps.productName != \"\" '> " +
             "and EXISTS (select 1 from fs_store_order_item_scrm oi2 join fs_store_product_scrm fsp2 on oi2.product_id = fsp2.product_id where oi2.order_id = o.id and fsp2.product_name like CONCAT('%', #{maps.productName}, '%')) " +
             "and EXISTS (select 1 from fs_store_order_item_scrm oi2 join fs_store_product_scrm fsp2 on oi2.product_id = fsp2.product_id where oi2.order_id = o.id and fsp2.product_name like CONCAT('%', #{maps.productName}, '%')) " +
             "</if>" +
             "</if>" +
-            "<if test = 'maps.orderType != null    '> " +
+            "<if test = 'maps.orderType != null and maps.orderType != -1    '> " +
             "and o.order_type =#{maps.orderType} " +
             "and o.order_type =#{maps.orderType} " +
             "</if>" +
             "</if>" +
+            "<if test = 'maps.orderType != null and maps.orderType == -1    '> " +
+            "and o.order_type in (2, 3) " +
+            "</if>" +
             "<if test = 'maps.payType != null    '> " +
             "<if test = 'maps.payType != null    '> " +
             "and o.pay_type =#{maps.payType} " +
             "and o.pay_type =#{maps.payType} " +
             "</if>" +
             "</if>" +
@@ -1361,10 +1379,10 @@ public interface FsStoreOrderScrmMapper
             "and sp_latest.pay_code like CONCAT('%', #{maps.payCode}, '%') " +
             "and sp_latest.pay_code like CONCAT('%', #{maps.payCode}, '%') " +
             "</if>" +
             "</if>" +
             "<if test = 'maps.isHealth != null and maps.isHealth !=  \"\"  '> " +
             "<if test = 'maps.isHealth != null and maps.isHealth !=  \"\"  '> " +
-            "and o.company_id is null " +
+            "and (o.company_id is null or o.order_type = 2 or o.order_type = 3) " +
             "</if>" +
             "</if>" +
             "<if test = 'maps.notHealth != null and maps.notHealth !=  \"\"  '> " +
             "<if test = 'maps.notHealth != null and maps.notHealth !=  \"\"  '> " +
-            "and o.company_id is not null " +
+            "and o.company_id is not null and o.order_type = 0 " +
             "</if>" +
             "</if>" +
             "<if test = 'maps.companyUserId != null    '> " +
             "<if test = 'maps.companyUserId != null    '> " +
             "and o.company_user_id =#{maps.companyUserId} " +
             "and o.company_user_id =#{maps.companyUserId} " +
@@ -1375,9 +1393,12 @@ public interface FsStoreOrderScrmMapper
             "<if test = 'maps.productName != null and maps.productName != \"\" '> " +
             "<if test = 'maps.productName != null and maps.productName != \"\" '> " +
             "and EXISTS (select 1 from fs_store_order_item_scrm oi2 join fs_store_product_scrm fsp2 on oi2.product_id = fsp2.product_id where oi2.order_id = o.id and fsp2.product_name like CONCAT('%', #{maps.productName}, '%')) " +
             "and EXISTS (select 1 from fs_store_order_item_scrm oi2 join fs_store_product_scrm fsp2 on oi2.product_id = fsp2.product_id where oi2.order_id = o.id and fsp2.product_name like CONCAT('%', #{maps.productName}, '%')) " +
             "</if>" +
             "</if>" +
-            "<if test = 'maps.orderType != null    '> " +
+            "<if test = 'maps.orderType != null and maps.orderType != -1    '> " +
             "and o.order_type =#{maps.orderType} " +
             "and o.order_type =#{maps.orderType} " +
             "</if>" +
             "</if>" +
+            "<if test = 'maps.orderType != null and maps.orderType == -1    '> " +
+            "and o.order_type in (2, 3) " +
+            "</if>" +
             "<if test = 'maps.payType != null    '> " +
             "<if test = 'maps.payType != null    '> " +
             "and o.pay_type =#{maps.payType} " +
             "and o.pay_type =#{maps.payType} " +
             "</if>" +
             "</if>" +

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

@@ -281,6 +281,8 @@ public class FsStoreProductAddEditParam implements Serializable
     /** 限购数量 */
     /** 限购数量 */
     private Integer purchaseLimit;
     private Integer purchaseLimit;
 
 
+    /** 单次购买数量上限(0 表示不限制) */
+    private Integer singlePurchaseLimit;
 
 
     /** 原产地 */
     /** 原产地 */
     @Excel(name = "原产地")
     @Excel(name = "原产地")

+ 36 - 0
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreCartScrmServiceImpl.java

@@ -163,6 +163,7 @@ public class FsStoreCartScrmServiceImpl implements IFsStoreCartScrmService
     public R addCart(long uid, FsStoreCartParam cartParam) {
     public R addCart(long uid, FsStoreCartParam cartParam) {
         // 检查并调整限购数量
         // 检查并调整限购数量
         Integer adjustedNum = adjustPurchaseLimit(uid, cartParam.getProductId(), cartParam.getCartNum());
         Integer adjustedNum = adjustPurchaseLimit(uid, cartParam.getProductId(), cartParam.getCartNum());
+        adjustedNum = capBySinglePurchaseLimit(cartParam.getProductId(), adjustedNum);
         cartParam.setCartNum(adjustedNum);
         cartParam.setCartNum(adjustedNum);
         
         
         //如果是直接购买,直接写入记录
         //如果是直接购买,直接写入记录
@@ -213,6 +214,7 @@ public class FsStoreCartScrmServiceImpl implements IFsStoreCartScrmService
                 int newCartNum = cartParam.getCartNum() + cart.get(0).getCartNum();
                 int newCartNum = cartParam.getCartNum() + cart.get(0).getCartNum();
                 // 检查并调整限购数量(需要检查新的总数量)
                 // 检查并调整限购数量(需要检查新的总数量)
                 Integer adjustedNewNum = adjustPurchaseLimit(uid, cartParam.getProductId(), newCartNum);
                 Integer adjustedNewNum = adjustPurchaseLimit(uid, cartParam.getProductId(), newCartNum);
+                adjustedNewNum = capBySinglePurchaseLimit(cartParam.getProductId(), adjustedNewNum);
                 storeCart.setCartNum(adjustedNewNum);
                 storeCart.setCartNum(adjustedNewNum);
                 storeCart.setUpdateTime(new Date());
                 storeCart.setUpdateTime(new Date());
                 checkProductStock(cartParam.getProductId(),storeCart.getProductAttrValueId());
                 checkProductStock(cartParam.getProductId(),storeCart.getProductAttrValueId());
@@ -239,6 +241,8 @@ public class FsStoreCartScrmServiceImpl implements IFsStoreCartScrmService
         FsStoreCartScrm cart=fsStoreCartMapper.selectFsStoreCartById(cartParam.getId());
         FsStoreCartScrm cart=fsStoreCartMapper.selectFsStoreCartById(cartParam.getId());
         // 检查限购
         // 检查限购
         checkPurchaseLimit(userId, cart.getProductId(), cartParam.getNumber());
         checkPurchaseLimit(userId, cart.getProductId(), cartParam.getNumber());
+        FsStoreProductScrm productForSingle = productService.selectFsStoreProductById(cart.getProductId());
+        checkSinglePurchaseLimit(productForSingle, cartParam.getNumber());
         checkProductStock(cart.getProductId(),cart.getProductAttrValueId());
         checkProductStock(cart.getProductId(),cart.getProductAttrValueId());
         cart.setCartNum(cartParam.getNumber());
         cart.setCartNum(cartParam.getNumber());
         cart.setUpdateTime(new Date());
         cart.setUpdateTime(new Date());
@@ -252,6 +256,36 @@ public class FsStoreCartScrmServiceImpl implements IFsStoreCartScrmService
      * @param productId 商品ID
      * @param productId 商品ID
      * @param num 要购买的数量
      * @param num 要购买的数量
      */
      */
+    /**
+     * 单次购买数量上限:购物车单行数量不得超过该上限(0 或空表示不限制)
+     */
+    private void checkSinglePurchaseLimit(FsStoreProductScrm product, Integer num) {
+        if (product == null || num == null) {
+            return;
+        }
+        if (product.getSinglePurchaseLimit() != null && product.getSinglePurchaseLimit() > 0
+                && num > product.getSinglePurchaseLimit()) {
+            throw new CustomException("该商品单次最多购买" + product.getSinglePurchaseLimit() + "件");
+        }
+    }
+
+    /**
+     * 将数量限制在单次购买上限内(用于加购时自动 cap,避免超过上限仍入库)
+     */
+    private Integer capBySinglePurchaseLimit(Long productId, Integer num) {
+        if (num == null) {
+            return null;
+        }
+        FsStoreProductScrm product = productService.selectFsStoreProductById(productId);
+        if (product == null) {
+            return num;
+        }
+        if (product.getSinglePurchaseLimit() == null || product.getSinglePurchaseLimit() <= 0) {
+            return num;
+        }
+        return Math.min(num, product.getSinglePurchaseLimit());
+    }
+
     private void checkPurchaseLimit(Long userId, Long productId, Integer num) {
     private void checkPurchaseLimit(Long userId, Long productId, Integer num) {
         // 查询商品信息
         // 查询商品信息
         FsStoreProductScrm product = productService.selectFsStoreProductById(productId);
         FsStoreProductScrm product = productService.selectFsStoreProductById(productId);
@@ -432,6 +466,7 @@ public class FsStoreCartScrmServiceImpl implements IFsStoreCartScrmService
     public R addCartBySidebar(Long uid, FsStoreCartParam cartParam) {
     public R addCartBySidebar(Long uid, FsStoreCartParam cartParam) {
         // 检查并调整限购数量
         // 检查并调整限购数量
         Integer adjustedNum = adjustPurchaseLimit(uid, cartParam.getProductId(), cartParam.getCartNum());
         Integer adjustedNum = adjustPurchaseLimit(uid, cartParam.getProductId(), cartParam.getCartNum());
+        adjustedNum = capBySinglePurchaseLimit(cartParam.getProductId(), adjustedNum);
         cartParam.setCartNum(adjustedNum);
         cartParam.setCartNum(adjustedNum);
 
 
         //如果是直接购买,直接写入记录
         //如果是直接购买,直接写入记录
@@ -482,6 +517,7 @@ public class FsStoreCartScrmServiceImpl implements IFsStoreCartScrmService
                 int newCartNum = cartParam.getCartNum() + cart.get(0).getCartNum();
                 int newCartNum = cartParam.getCartNum() + cart.get(0).getCartNum();
                 // 检查并调整限购数量(需要检查新的总数量)
                 // 检查并调整限购数量(需要检查新的总数量)
                 Integer adjustedNewNum = adjustPurchaseLimit(uid, cartParam.getProductId(), newCartNum);
                 Integer adjustedNewNum = adjustPurchaseLimit(uid, cartParam.getProductId(), newCartNum);
+                adjustedNewNum = capBySinglePurchaseLimit(cartParam.getProductId(), adjustedNewNum);
                 storeCart.setCartNum(adjustedNewNum);
                 storeCart.setCartNum(adjustedNewNum);
                 storeCart.setUpdateTime(new Date());
                 storeCart.setUpdateTime(new Date());
                 checkProductStock(cartParam.getProductId(),storeCart.getProductAttrValueId());
                 checkProductStock(cartParam.getProductId(),storeCart.getProductAttrValueId());

+ 80 - 3
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderScrmServiceImpl.java

@@ -796,6 +796,7 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
             throw new CustomException("订单已过期", 501);
             throw new CustomException("订单已过期", 501);
         }
         }
         List<FsStoreCartQueryVO> carts = redisCache.getCacheObject("orderCarts:" + param.getOrderKey());
         List<FsStoreCartQueryVO> carts = redisCache.getCacheObject("orderCarts:" + param.getOrderKey());
+        validateSinglePurchaseLimitForCarts(carts);
         BigDecimal payPrice = getOrderSumPrice(carts, "truePrice");
         BigDecimal payPrice = getOrderSumPrice(carts, "truePrice");
         if (StringUtils.isNotEmpty(param.getCreateOrderKey())) {
         if (StringUtils.isNotEmpty(param.getCreateOrderKey())) {
             Integer payType = redisCache.getCacheObject("createOrderPayType:" + param.getCreateOrderKey());
             Integer payType = redisCache.getCacheObject("createOrderPayType:" + param.getCreateOrderKey());
@@ -968,7 +969,7 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
                     if(param.getCompanyUserId()!=null){
                     if(param.getCompanyUserId()!=null){
                         if (ObjectUtil.isNotEmpty(fsuser.getCompanyUserId())&&!fsuser.getCompanyUserId().equals(param.getCompanyUserId())){
                         if (ObjectUtil.isNotEmpty(fsuser.getCompanyUserId())&&!fsuser.getCompanyUserId().equals(param.getCompanyUserId())){
                             CompanyUser companyUser=companyUserService.selectCompanyUserById(fsuser.getCompanyUserId());
                             CompanyUser companyUser=companyUserService.selectCompanyUserById(fsuser.getCompanyUserId());
-                            return R.error(String.format("请联系%s销售进行购买商品!",companyUser.getNickName()));
+                            return R.error(String.format("请联系【%s】销售进行购买商品!",companyUser.getUserName()));
                         }else {
                         }else {
                             fsuser.setCompanyUserId(param.getCompanyUserId());
                             fsuser.setCompanyUserId(param.getCompanyUserId());
                             userService.updateFsUser(fsuser);
                             userService.updateFsUser(fsuser);
@@ -1478,11 +1479,59 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
         }
         }
     }
     }
 
 
+    /**
+     * OMS 侧常见承运商编码(与快递 100 等标准码一致)。按长度降序,避免 YD 抢占 YZPY 等长码前缀。
+     * 如 ZTO1、ZTO1.1 归一为 ZTO 再查库;库中 oms_code 须为标准码(如 ZTO),不能仅 ZTO1。
+     */
+    private static final String[] OMS_EXPRESS_KNOWN_CODES_LONGEST_FIRST = {
+            "HTKY", "YZPY", "JTSD", "ZTO", "STO", "YTO", "EMS", "DBL", "ZYE", "ZJS",
+            "SF", "YD", "JD", "UC"
+    };
+
+    /**
+     * 先用传入的 deliverCode(含 trim、大写)精确查 OMS;若无记录,再按 {@link #OMS_EXPRESS_KNOWN_CODES_LONGEST_FIRST}
+     * 做前缀/等于模糊匹配(如 ZTO1.1 → ZTO),用标准码查库后拷贝一条 express,{@code omsCode} 保留为传入的原始编码,{@code code} 等为库中标准数据。
+     */
+    private FsExpressScrm selectExpressByOmsDeliverCode(String deliverCode) {
+        if (StringUtils.isEmpty(deliverCode)) {
+            return null;
+        }
+        String raw = deliverCode.trim();
+        FsExpressScrm express = expressService.selectFsExpressByOmsCode(raw);
+        if (express != null) {
+            return express;
+        }
+        String upper = raw.toUpperCase(Locale.ROOT);
+        if (!upper.equals(raw)) {
+            express = expressService.selectFsExpressByOmsCode(upper);
+            if (express != null) {
+                return express;
+            }
+        }
+        for (String standardOms : OMS_EXPRESS_KNOWN_CODES_LONGEST_FIRST) {
+            if (upper.equals(standardOms) || upper.startsWith(standardOms)) {
+                FsExpressScrm base = expressService.selectFsExpressByCode(standardOms);
+                if (base != null) {
+                    FsExpressScrm assembled = new FsExpressScrm();
+                    BeanUtils.copyProperties(base, assembled);
+                    assembled.setOmsCode(raw);
+                    return assembled;
+                }
+            }
+        }
+        return null;
+    }
+
     @Override
     @Override
     public void deliveryOrder(String orderCode, String deliveryId, String deliverCode, String deliverName) {
     public void deliveryOrder(String orderCode, String deliveryId, String deliverCode, String deliverName) {
         FsStoreOrderScrm order = fsStoreOrderMapper.selectFsStoreOrderByOrderCode(orderCode);
         FsStoreOrderScrm order = fsStoreOrderMapper.selectFsStoreOrderByOrderCode(orderCode);
         if (order != null && order.getStatus() == OrderInfoEnum.STATUS_1.getValue()) {
         if (order != null && order.getStatus() == OrderInfoEnum.STATUS_1.getValue()) {
-            FsExpressScrm express = expressService.selectFsExpressByOmsCode(deliverCode);
+            FsExpressScrm express = selectExpressByOmsDeliverCode(deliverCode);
+            if (express == null) {
+                // 这里输出订单号 还有相关的物流信息,
+                log.error("发货失败:未找到快递公司,订单号:{},deliveryId:{},deliverCode:{},deliverName:{}", orderCode, deliveryId, deliverCode, deliverName);
+                return;
+            }
             if (express != null) {
             if (express != null) {
                 order.setDeliveryName(deliverName);
                 order.setDeliveryName(deliverName);
                 order.setDeliverySn(express.getCode());
                 order.setDeliverySn(express.getCode());
@@ -1537,7 +1586,7 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
     public void updateDeliveryOrder(Long id, String deliveryId, String deliverCode, String deliverName) {
     public void updateDeliveryOrder(Long id, String deliveryId, String deliverCode, String deliverName) {
         FsStoreOrderScrm order = fsStoreOrderMapper.selectFsStoreOrderById(id);
         FsStoreOrderScrm order = fsStoreOrderMapper.selectFsStoreOrderById(id);
         if (order != null) {
         if (order != null) {
-            FsExpressScrm express = expressService.selectFsExpressByOmsCode(deliverCode);
+            FsExpressScrm express = selectExpressByOmsDeliverCode(deliverCode);
             if (express != null) {
             if (express != null) {
                 order.setDeliveryName(deliverName);
                 order.setDeliveryName(deliverName);
                 order.setDeliverySn(express.getCode());
                 order.setDeliverySn(express.getCode());
@@ -3325,6 +3374,29 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
     /**
     /**
      * 退回库存
      * 退回库存
      */
      */
+    /**
+     * 订单计算/提交:校验每个购物车行的数量不超过商品「单次购买上限」
+     */
+    private void validateSinglePurchaseLimitForCarts(List<FsStoreCartQueryVO> carts) {
+        if (carts == null || carts.isEmpty()) {
+            return;
+        }
+        for (FsStoreCartQueryVO c : carts) {
+            if (c.getProductId() == null || c.getCartNum() == null) {
+                continue;
+            }
+            FsStoreProductScrm product = productService.selectFsStoreProductById(c.getProductId());
+            if (product == null) {
+                continue;
+            }
+            if (product.getSinglePurchaseLimit() != null && product.getSinglePurchaseLimit() > 0
+                    && c.getCartNum() > product.getSinglePurchaseLimit()) {
+                throw new CustomException("商品「" + product.getProductName() + "」单次最多购买"
+                        + product.getSinglePurchaseLimit() + "件");
+            }
+        }
+    }
+
     /**
     /**
      * 检查并记录限购
      * 检查并记录限购
      * @param userId 用户ID
      * @param userId 用户ID
@@ -3338,6 +3410,11 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
             return;
             return;
         }
         }
 
 
+        if (product.getSinglePurchaseLimit() != null && product.getSinglePurchaseLimit() > 0
+                && num != null && num > product.getSinglePurchaseLimit()) {
+            throw new CustomException("该商品单次最多购买" + product.getSinglePurchaseLimit() + "件");
+        }
+
         // 如果商品没有设置限购,直接返回
         // 如果商品没有设置限购,直接返回
         if (product.getPurchaseLimit() == null || product.getPurchaseLimit() <= 0) {
         if (product.getPurchaseLimit() == null || product.getPurchaseLimit() <= 0) {
             return;
             return;

+ 15 - 5
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreProductScrmServiceImpl.java

@@ -102,7 +102,7 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
 
 
     @Autowired
     @Autowired
     private ConfigUtil configUtil;
     private ConfigUtil configUtil;
-    
+
     @Autowired
     @Autowired
     private com.fs.system.service.ISysConfigService configService;
     private com.fs.system.service.ISysConfigService configService;
 
 
@@ -699,6 +699,12 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
         } else {
         } else {
             product.setPurchaseLimit(0);
             product.setPurchaseLimit(0);
         }
         }
+        // 单次购买上限:与限购字段含义一致,0 表示不限制
+        if (param.getSinglePurchaseLimit() != null && param.getSinglePurchaseLimit() > 0) {
+            product.setSinglePurchaseLimit(param.getSinglePurchaseLimit());
+        } else {
+            product.setSinglePurchaseLimit(0);
+        }
         //校验店铺资质信息
         //校验店铺资质信息
         if (!CompanyEnum.contains(cloudHostProper.getCompanyName())) {
         if (!CompanyEnum.contains(cloudHostProper.getCompanyName())) {
             //获取店铺
             //获取店铺
@@ -738,7 +744,11 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
         if(param.getProductId() != null && param.getProductId() > 0){
         if(param.getProductId() != null && param.getProductId() > 0){
             //对已上架的商品进行修改需要重新审核(益寿缘商城商品无需审核)
             //对已上架的商品进行修改需要重新审核(益寿缘商城商品无需审核)
             if(!("益善缘".equals(cloudHostProper.getCompanyName()))){
             if(!("益善缘".equals(cloudHostProper.getCompanyName()))){
-                if(1 == product.getIsShow() && "1".equals(product.getIsAudit())){
+//                if(1 == product.getIsShow() && "1".equals(product.getIsAudit())){
+//                    product.setIsAudit("0");
+//                }
+
+                if(1 == product.getIsShow()){
                     product.setIsAudit("0");
                     product.setIsAudit("0");
                 }
                 }
             } else{
             } else{
@@ -1117,7 +1127,7 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
             String productName = product != null ? product.getProductName() : "商品";
             String productName = product != null ? product.getProductName() : "商品";
             throw new CustomException(productName + "库存不足,当前库存:" + (productStock != null ? productStock : 0) + ",需要数量:" + cartNum);
             throw new CustomException(productName + "库存不足,当前库存:" + (productStock != null ? productStock : 0) + ",需要数量:" + cartNum);
         }
         }
-        
+
         // 如果有规格,检查规格库存
         // 如果有规格,检查规格库存
         if (productAttrValueId != null) {
         if (productAttrValueId != null) {
             Integer attrStock = fsStoreProductAttrValueMapper.selectProductAttrStockForUpdate(productAttrValueId);
             Integer attrStock = fsStoreProductAttrValueMapper.selectProductAttrStockForUpdate(productAttrValueId);
@@ -1128,7 +1138,7 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
             }
             }
         }
         }
     }
     }
-    
+
     @Override
     @Override
     public void decProductStock(Long productId, Long productAttrValueId, Integer cartNum) {
     public void decProductStock(Long productId, Long productAttrValueId, Integer cartNum) {
         // 检查配置是否开启库存检查
         // 检查配置是否开启库存检查
@@ -1144,7 +1154,7 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
         } catch (Exception e) {
         } catch (Exception e) {
             log.error("检查库存配置失败", e);
             log.error("检查库存配置失败", e);
         }
         }
-        
+
         //处理属性sku
         //处理属性sku
         fsStoreProductAttrValueMapper.decProductAttrStock(productAttrValueId,cartNum);
         fsStoreProductAttrValueMapper.decProductAttrStock(productAttrValueId,cartNum);
         fsStoreProductMapper.decProductAttrStock(productId,cartNum);
         fsStoreProductMapper.decProductAttrStock(productId,cartNum);

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

@@ -142,4 +142,7 @@ public class FsStoreProductQueryVO implements Serializable
     /** 限购数量 */
     /** 限购数量 */
     private Integer purchaseLimit;
     private Integer purchaseLimit;
 
 
+    /** 单次购买数量上限 */
+    private Integer singlePurchaseLimit;
+
 }
 }

+ 14 - 0
fs-service/src/main/java/com/fs/live/service/impl/LiveOrderServiceImpl.java

@@ -2285,6 +2285,15 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
             log.error("商品不存在");
             log.error("商品不存在");
             return null;
             return null;
         }
         }
+        if (StringUtils.isEmpty(param.getTotalNum())) {
+            log.error("商品数量不能为空");
+            return null;
+        }
+        int purchaseNum = Integer.parseInt(param.getTotalNum());
+        if (fsStoreProduct.getSinglePurchaseLimit() != null && fsStoreProduct.getSinglePurchaseLimit() > 0
+                && purchaseNum > fsStoreProduct.getSinglePurchaseLimit()) {
+            throw new CustomException("该商品单次最多购买" + fsStoreProduct.getSinglePurchaseLimit() + "件");
+        }
         FsStoreProductAttrValueScrm fsStoreProductAttrValue = null;
         FsStoreProductAttrValueScrm fsStoreProductAttrValue = null;
         if (!Objects.isNull(param.getAttrValueId())) {
         if (!Objects.isNull(param.getAttrValueId())) {
             fsStoreProductAttrValue = attrValueScrmMapper.selectFsStoreProductAttrValueById(param.getAttrValueId());
             fsStoreProductAttrValue = attrValueScrmMapper.selectFsStoreProductAttrValueById(param.getAttrValueId());
@@ -4791,6 +4800,11 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
             return;
             return;
         }
         }
 
 
+        if (product.getSinglePurchaseLimit() != null && product.getSinglePurchaseLimit() > 0
+                && num != null && num > product.getSinglePurchaseLimit()) {
+            throw new CustomException("该商品单次最多购买" + product.getSinglePurchaseLimit() + "件");
+        }
+
         // 如果商品没有设置限购,直接返回
         // 如果商品没有设置限购,直接返回
         if (product.getPurchaseLimit() == null || product.getPurchaseLimit() <= 0) {
         if (product.getPurchaseLimit() == null || product.getPurchaseLimit() <= 0) {
             return;
             return;

+ 1 - 0
fs-service/src/main/java/com/fs/live/service/impl/LiveServiceImpl.java

@@ -1607,6 +1607,7 @@ public class LiveServiceImpl implements ILiveService
         try {
         try {
             String cacheKey = String.format(LiveKeysConstant.LIVE_DATA_CACHE, liveId);
             String cacheKey = String.format(LiveKeysConstant.LIVE_DATA_CACHE, liveId);
             redisCache.deleteObject(cacheKey);
             redisCache.deleteObject(cacheKey);
+            redisCache.hashDelete(LiveKeysConstant.LIVE_HOME_PAGE_DETAIL, String.valueOf(liveId));
             log.debug("清除直播间缓存: liveId={}", liveId);
             log.debug("清除直播间缓存: liveId={}", liveId);
             return R.ok("缓存清理成功");
             return R.ok("缓存清理成功");
         } catch (Exception e) {
         } catch (Exception e) {

+ 1 - 1
fs-service/src/main/resources/application-common.yml

@@ -36,7 +36,7 @@ server:
 # 日志配置
 # 日志配置
 logging:
 logging:
   level:
   level:
-    com.fs: DEBUG
+    com.fs: info
     org.springframework: warn
     org.springframework: warn
 
 
 express:
 express:

+ 70 - 4
fs-service/src/main/resources/mapper/company/CompanyUserMapper.xml

@@ -91,6 +91,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="dataScope"     column="data_scope"    />
         <result property="dataScope"     column="data_scope"    />
         <result property="status"       column="role_status"    />
         <result property="status"       column="role_status"    />
     </resultMap>
     </resultMap>
+
     <select id="selectCompanyUserQwListVO" resultMap="CompanyUserQwListVOResult">
     <select id="selectCompanyUserQwListVO" resultMap="CompanyUserQwListVOResult">
         select
         select
         u.user_id, u.user_name, u.nick_name, u.company_id, u.status,
         u.user_id, u.user_name, u.nick_name, u.company_id, u.status,
@@ -100,15 +101,17 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         u.avatar,u.address_id,
         u.avatar,u.address_id,
         u.qw_user_id,
         u.qw_user_id,
         u.doctor_id,
         u.doctor_id,
-        u.cid_server_id,
         d.dept_name,
         d.dept_name,
         d.leader,
         d.leader,
-        u.ai_sip_call_user_id
+        GROUP_CONCAT(fu.nick_name) bindUser,
+        cfu.`status` bindStatus
         from
         from
         company_user u
         company_user u
         left join
         left join
         company_dept d on u.dept_id = d.dept_id
         company_dept d on u.dept_id = d.dept_id
-
+        LEFT JOIN
+        company_fs_user cfu on u.user_id=cfu.company_user_id
+        LEFT JOIN fs_user fu on cfu.fs_user_id=fu.user_id
         where
         where
         u.del_flag = '0'
         u.del_flag = '0'
         <if test="userId != null ">
         <if test="userId != null ">
@@ -149,11 +152,74 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <if test="deptId != null and deptId != 0">
         <if test="deptId != null and deptId != 0">
             AND (u.dept_id = #{deptId} OR u.dept_id IN ( SELECT t.dept_id FROM company_dept t WHERE find_in_set(#{deptId}, ancestors) ))
             AND (u.dept_id = #{deptId} OR u.dept_id IN ( SELECT t.dept_id FROM company_dept t WHERE find_in_set(#{deptId}, ancestors) ))
         </if>
         </if>
-
+        GROUP BY u.user_id
         <!-- 数据范围过滤 -->
         <!-- 数据范围过滤 -->
         ${params.dataScope}
         ${params.dataScope}
     </select>
     </select>
 
 
+<!--    <select id="selectCompanyUserQwListVO" resultMap="CompanyUserQwListVOResult">-->
+<!--        select-->
+<!--        u.user_id, u.user_name, u.nick_name, u.company_id, u.status,-->
+<!--        u.qw_status, u.phonenumber, u.create_time, u.dept_id,-->
+<!--        u.qr_code_weixin, u.user_type, u.qr_code_wecom, u.jpush_id,-->
+<!--        u.is_need_register_member,u.is_allowed_all_register,-->
+<!--        u.avatar,u.address_id,-->
+<!--        u.qw_user_id,-->
+<!--        u.doctor_id,-->
+<!--        u.cid_server_id,-->
+<!--        d.dept_name,-->
+<!--        d.leader,-->
+<!--        u.ai_sip_call_user_id-->
+<!--        from-->
+<!--        company_user u-->
+<!--        left join-->
+<!--        company_dept d on u.dept_id = d.dept_id-->
+
+<!--        where-->
+<!--        u.del_flag = '0'-->
+<!--        <if test="userId != null ">-->
+<!--            AND u.user_id = #{userId}-->
+<!--        </if>-->
+<!--        <if test="userName != null and userName != ''">-->
+<!--            AND u.user_name like concat('%', #{userName}, '%')-->
+<!--        </if>-->
+
+<!--        <if test="nickName != null and nickName != ''">-->
+<!--            AND u.nick_name like concat( #{nickName}, '%')-->
+<!--        </if>-->
+
+<!--        <if test="companyId != null and companyId != ''">-->
+<!--            AND u.company_id = #{companyId}-->
+<!--        </if>-->
+
+<!--        <if test="status != null and status != ''">-->
+<!--            AND u.status = #{status}-->
+<!--        </if>-->
+
+<!--        <if test="qwStatus != null">-->
+<!--            AND u.qw_status = #{qwStatus}-->
+<!--        </if>-->
+
+<!--        <if test="phonenumber != null and phonenumber != ''">-->
+<!--            AND u.phonenumber like concat('%', #{phonenumber}, '%')-->
+<!--        </if>-->
+
+<!--        <if test="beginTime != null and beginTime != ''">-->
+<!--            AND date_format(u.create_time,'%y%m%d') &gt;= date_format(#{beginTime},'%y%m%d')-->
+<!--        </if>-->
+
+<!--        <if test="endTime != null and endTime != ''">-->
+<!--            AND date_format(u.create_time,'%y%m%d') &lt;= date_format(#{endTime},'%y%m%d')-->
+<!--        </if>-->
+
+<!--        <if test="deptId != null and deptId != 0">-->
+<!--            AND (u.dept_id = #{deptId} OR u.dept_id IN ( SELECT t.dept_id FROM company_dept t WHERE find_in_set(#{deptId}, ancestors) ))-->
+<!--        </if>-->
+
+<!--        &lt;!&ndash; 数据范围过滤 &ndash;&gt;-->
+<!--        ${params.dataScope}-->
+<!--    </select>-->
+
     <!-- 角色子查询 -->
     <!-- 角色子查询 -->
     <select id="selectUserRoles" resultType="java.lang.String">
     <select id="selectUserRoles" resultType="java.lang.String">
         SELECT cr.role_name
         SELECT cr.role_name

+ 93 - 32
fs-service/src/main/resources/mapper/course/FsCourseWatchLogMapper.xml

@@ -1453,6 +1453,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             COALESCE(ord.order_amount, 0) AS orderAmount,
             COALESCE(ord.order_amount, 0) AS orderAmount,
             c.company_name AS companyName,
             c.company_name AS companyName,
             cu.nick_name AS salesName
             cu.nick_name AS salesName
+            <if test="param.includeCourseRating != null and param.includeCourseRating">
+            , ans.course_rating AS courseRating
+            </if>
         FROM (
         FROM (
             SELECT
             SELECT
                 l.user_id,
                 l.user_id,
@@ -1495,6 +1498,18 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         ) ord ON ord.user_id = ua.user_id
         ) ord ON ord.user_id = ua.user_id
         LEFT JOIN company c ON c.company_id = ua.company_id
         LEFT JOIN company c ON c.company_id = ua.company_id
         LEFT JOIN company_user cu ON cu.user_id = ua.company_user_id
         LEFT JOIN company_user cu ON cu.user_id = ua.company_user_id
+        <if test="param.includeCourseRating != null and param.includeCourseRating">
+        LEFT JOIN (
+            SELECT a.user_id, a.question_json AS course_rating
+            FROM fs_course_answer_logs a
+            INNER JOIN (
+                SELECT user_id, MAX(log_id) AS max_log_id
+                FROM fs_course_answer_logs
+                WHERE video_id = #{param.videoId} AND period_id = #{param.periodId}
+                GROUP BY user_id
+            ) latest ON latest.user_id = a.user_id AND latest.max_log_id = a.log_id
+        ) ans ON ans.user_id = ua.user_id
+        </if>
         ORDER BY ua.max_create_time DESC
         ORDER BY ua.max_create_time DESC
     </select>
     </select>
 
 
@@ -1509,6 +1524,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             COALESCE(ord.order_amount, 0) AS orderAmount,
             COALESCE(ord.order_amount, 0) AS orderAmount,
             c.company_name AS companyName,
             c.company_name AS companyName,
             cu.nick_name AS salesName
             cu.nick_name AS salesName
+            <if test="param.includeCourseRating != null and param.includeCourseRating">
+            , ans.course_rating AS courseRating
+            </if>
         FROM (
         FROM (
             SELECT
             SELECT
                 l.user_id,
                 l.user_id,
@@ -1551,44 +1569,87 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         ) ord ON ord.user_id = ua.user_id
         ) ord ON ord.user_id = ua.user_id
         LEFT JOIN company c ON c.company_id = ua.company_id
         LEFT JOIN company c ON c.company_id = ua.company_id
         LEFT JOIN company_user cu ON cu.user_id = ua.company_user_id
         LEFT JOIN company_user cu ON cu.user_id = ua.company_user_id
+        <if test="param.includeCourseRating != null and param.includeCourseRating">
+        LEFT JOIN (
+            SELECT a.user_id, a.question_json AS course_rating
+            FROM fs_course_answer_logs a
+            INNER JOIN (
+                SELECT user_id, MAX(log_id) AS max_log_id
+                FROM fs_course_answer_logs
+                WHERE video_id = #{param.videoId} AND period_id = #{param.periodId}
+                GROUP BY user_id
+            ) latest ON latest.user_id = a.user_id AND latest.max_log_id = a.log_id
+        ) ans ON ans.user_id = ua.user_id
+        </if>
         ORDER BY ua.max_create_time DESC
         ORDER BY ua.max_create_time DESC
         LIMIT 50000
         LIMIT 50000
     </select>
     </select>
 
 
+    <!--
+        实际看课数据(按看课记录口径,与 countDistinctWatchUsers / countDistinctCompleteUsers 一致):
+        仅统计 duration > 0 的记录参与时长汇总;到课/时长均以正时长行为准。
+        i.1 实际到课人数 = 去重 user_id,且存在至少一条本视频本营期 duration>0 的看课记录(进入点播产生有效观看)
+        i.2 实际完课人数 = 去重 user_id,且存在至少一条 log_type=2 且 duration>0 的看课记录(完课记录)
+        ii.3 实际完课率 = i.2 / i.1(百分比)
+        iv.4 人均看课时长(分钟) = 到课用户各自的「仅 duration>0 记录时长之和」汇总结秒 / 到课人数
+        v.5 人均完课时长(分钟) = 完课用户各自的同上累计秒数之和 / 完课人数
+        vi.6 人均完课完播率(%) = (v.5 对应的人均秒数) / 素材时长(秒) * 100,即 (完课用户总秒数/完课人数) / video.duration * 100
+    -->
     <select id="selectActualCompletionList" resultType="com.fs.course.vo.FSActualCompletionVO">
     <select id="selectActualCompletionList" resultType="com.fs.course.vo.FSActualCompletionVO">
         SELECT
         SELECT
-        a.totalStudents,
-        a.completedCount,
-        ROUND((a.completedCount / a.totalStudents) * 100, 2) AS actualCompletionRate,
-        ROUND((a.totalViewingDuration / a.totalStudents), 2) AS avgWatchDurationMinutes,
-        ROUND((a.totalDurationOfCompleters/a.completedCount),2) AS avgCompletedDuration,
-        ROUND(((a.totalDurationOfCompleters/a.completedCount)/a.duration) * 100,2) AS avgCompletionPlaybackRate
-        FROM
-        (
-        SELECT
-        COUNT(*) AS totalStudents,
-        SUM(CASE WHEN wl.log_type = 2 THEN 1 ELSE 0 END) AS completedCount,
-        SUM(wl.duration) AS totalViewingDuration,
-        SUM(CASE WHEN wl.log_type = 2 THEN wl.duration ELSE 0 END) AS totalDurationOfCompleters,
-        cv.duration
-        FROM
-        fs_user_course_period_days pd
-        INNER JOIN fs_course_watch_log wl ON pd.period_id = wl.period_id
-        INNER JOIN fs_user_course_video cv ON pd.video_id = cv.video_id
-        AND pd.video_id = wl.video_id
-        <where>
-            pd.period_id = #{periodId}
-            AND pd.video_id = #{videoId}
-            <if test="companyId != null">
-                AND wl.company_id = #{companyId}
-            </if>
-            <if test="companyUserId != null">
-                AND wl.company_user_id = #{companyUserId}
-            </if>
-        </where>
-        GROUP BY
-        pd.period_id
-        ) a
+            COALESCE(agg.total_students, 0) AS totalStudents,
+            COALESCE(agg.completed_count, 0) AS completedCount,
+            CASE
+                WHEN COALESCE(agg.total_students, 0) > 0 THEN
+                    ROUND((agg.completed_count * 100.0 / agg.total_students), 2)
+                ELSE 0
+            END AS actualCompletionRate,
+            CASE
+                WHEN COALESCE(agg.total_students, 0) > 0 THEN
+                    ROUND((agg.total_viewing_seconds / agg.total_students) , 2)
+                ELSE 0
+            END AS avgWatchDurationMinutes,
+            CASE
+                WHEN COALESCE(agg.completed_count, 0) > 0 THEN
+                    ROUND((agg.total_duration_completers_seconds / agg.completed_count) , 2)
+                ELSE 0
+            END AS avgCompletedDuration,
+            CASE
+                WHEN COALESCE(agg.completed_count, 0) > 0
+                     AND COALESCE(vid.video_duration_sec, 0) > 0 THEN
+                    ROUND(((agg.total_duration_completers_seconds / agg.completed_count) / vid.video_duration_sec) * 100, 2)
+                ELSE 0
+            END AS avgCompletionPlaybackRate
+        FROM (
+            SELECT
+                COUNT(*) AS total_students,
+                COALESCE(SUM(CASE WHEN u.has_complete_log = 1 THEN 1 ELSE 0 END), 0) AS completed_count,
+                COALESCE(SUM(u.sum_pos_duration_sec), 0) AS total_viewing_seconds,
+                COALESCE(SUM(CASE WHEN u.has_complete_log = 1 THEN u.sum_pos_duration_sec ELSE 0 END), 0) AS total_duration_completers_seconds
+            FROM (
+                SELECT
+                    wl.user_id,
+                    SUM(CASE WHEN COALESCE(wl.duration, 0) > 0 THEN COALESCE(wl.duration, 0) ELSE 0 END) AS sum_pos_duration_sec,
+                    MAX(CASE WHEN wl.log_type = 2 AND COALESCE(wl.duration, 0) > 0 THEN 1 ELSE 0 END) AS has_complete_log
+                FROM fs_course_watch_log wl
+                WHERE wl.video_id = #{videoId}
+                  AND wl.period_id = #{periodId}
+                  AND wl.user_id IS NOT NULL
+                <if test="companyId != null">
+                    AND wl.company_id = #{companyId}
+                </if>
+                <if test="companyUserId != null">
+                    AND wl.company_user_id = #{companyUserId}
+                </if>
+                GROUP BY wl.user_id
+                HAVING SUM(CASE WHEN COALESCE(wl.duration, 0) > 0 THEN COALESCE(wl.duration, 0) ELSE 0 END) > 0
+            ) u
+        ) agg
+        CROSS JOIN (
+            SELECT COALESCE(MAX(v.duration), 0) AS video_duration_sec
+            FROM fs_user_course_video v
+            WHERE v.video_id = #{videoId} AND v.is_del = 0
+        ) vid
     </select>
     </select>
 
 
     <!-- 记录类型 1看课中 2完课 3待看课 4看课中断   -->
     <!-- 记录类型 1看课中 2完课 3待看课 4看课中断   -->

+ 2 - 1
fs-service/src/main/resources/mapper/hisStore/FsStoreOrderScrmMapper.xml

@@ -7,6 +7,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     <resultMap type="FsStoreOrderScrm" id="FsStoreOrderResult">
     <resultMap type="FsStoreOrderScrm" id="FsStoreOrderResult">
         <result property="id"    column="id"    />
         <result property="id"    column="id"    />
         <result property="orderCode"    column="order_code"    />
         <result property="orderCode"    column="order_code"    />
+        <result property="outerOiId"    column="outer_oi_id"    />
         <result property="extendOrderId"    column="extend_order_id"    />
         <result property="extendOrderId"    column="extend_order_id"    />
         <result property="payOrderId"    column="pay_order_id"    />
         <result property="payOrderId"    column="pay_order_id"    />
         <result property="bankOrderId"    column="bank_order_id"    />
         <result property="bankOrderId"    column="bank_order_id"    />
@@ -94,7 +95,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     </resultMap>
     </resultMap>
 
 
     <sql id="selectFsStoreOrderVo">
     <sql id="selectFsStoreOrderVo">
-        select id, order_code,service_fee, extend_order_id,pay_order_id,bank_order_id, user_id,order_visit, real_name, user_phone, user_address, cart_id, freight_price, total_num, total_price, total_postage, pay_price, pay_postage,pay_delivery,pay_money, deduction_price, coupon_id, coupon_price, paid, pay_time, pay_type, create_time, update_time, status, refund_status, refund_reason_wap_img, refund_reason_wap_explain, refund_reason_time, refund_reason_wap, refund_reason, refund_price, delivery_sn, delivery_name, delivery_type, delivery_id, gain_integral, use_integral, pay_integral, back_integral, mark, is_del, remark, cost, verify_code, store_id, shipping_type, is_channel, is_remind, is_sys_del,is_prescribe,prescribe_id ,company_id,company_user_id,is_package,package_json,item_json,order_type,package_id,finish_time,delivery_status,delivery_pay_status,delivery_time,delivery_pay_time,delivery_pay_money,tui_money,tui_money_status,delivery_import_time,tui_user_id,tui_user_money_status,order_create_type,store_house_code,dept_id,is_edit_money,customer_id,is_pay_remain,delivery_send_time,certificates,schedule_id,backend_edit_product_type,video_id,course_id,project_id,period_id,virtual_phone from fs_store_order_scrm
+        select id, order_code,outer_oi_id,service_fee, extend_order_id,pay_order_id,bank_order_id, user_id,order_visit, real_name, user_phone, user_address, cart_id, freight_price, total_num, total_price, total_postage, pay_price, pay_postage,pay_delivery,pay_money, deduction_price, coupon_id, coupon_price, paid, pay_time, pay_type, create_time, update_time, status, refund_status, refund_reason_wap_img, refund_reason_wap_explain, refund_reason_time, refund_reason_wap, refund_reason, refund_price, delivery_sn, delivery_name, delivery_type, delivery_id, gain_integral, use_integral, pay_integral, back_integral, mark, is_del, remark, cost, verify_code, store_id, shipping_type, is_channel, is_remind, is_sys_del,is_prescribe,prescribe_id ,company_id,company_user_id,is_package,package_json,item_json,order_type,package_id,finish_time,delivery_status,delivery_pay_status,delivery_time,delivery_pay_time,delivery_pay_money,tui_money,tui_money_status,delivery_import_time,tui_user_id,tui_user_money_status,order_create_type,store_house_code,dept_id,is_edit_money,customer_id,is_pay_remain,delivery_send_time,certificates,schedule_id,backend_edit_product_type,video_id,course_id,project_id,period_id,virtual_phone from fs_store_order_scrm
     </sql>
     </sql>
 
 
     <select id="selectFsStoreOrderList" parameterType="FsStoreOrderScrm" resultMap="FsStoreOrderResult">
     <select id="selectFsStoreOrderList" parameterType="FsStoreOrderScrm" resultMap="FsStoreOrderResult">

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

@@ -77,6 +77,7 @@
         <result property="domesticImported"    column="domestic_imported"    />
         <result property="domesticImported"    column="domestic_imported"    />
         <result property="appIds"    column="app_ids"    />
         <result property="appIds"    column="app_ids"    />
         <result property="purchaseLimit"    column="purchase_limit"    />
         <result property="purchaseLimit"    column="purchase_limit"    />
+        <result property="singlePurchaseLimit"    column="single_purchase_limit"    />
     </resultMap>
     </resultMap>
 
 
     <sql id="selectFsStoreProductVo">
     <sql id="selectFsStoreProductVo">
@@ -88,7 +89,7 @@
                is_display,tui_cate_id,company_ids,is_drug,drug_image,drug_reg_cert_no,common_name,dosage_form,
                is_display,tui_cate_id,company_ids,is_drug,drug_image,drug_reg_cert_no,common_name,dosage_form,
                unit_price,batch_number,mah,mah_address,manufacturer,manufacturer_address,indications,dosage,
                unit_price,batch_number,mah,mah_address,manufacturer,manufacturer_address,indications,dosage,
                adverse_reactions,contraindications,precautions,is_audit,store_id,return_address,brand,food_production_license_code,
                adverse_reactions,contraindications,precautions,is_audit,store_id,return_address,brand,food_production_license_code,
-               origin_place,net_content,shelf_life,domestic_imported,app_ids,purchase_limit
+               origin_place,net_content,shelf_life,domestic_imported,app_ids,purchase_limit,single_purchase_limit
         from fs_store_product_scrm
         from fs_store_product_scrm
     </sql>
     </sql>
 
 
@@ -101,7 +102,7 @@
                p.is_display,p.tui_cate_id,p.company_ids,p.is_drug,p.drug_image,p.drug_reg_cert_no,p.common_name,p.dosage_form,
                p.is_display,p.tui_cate_id,p.company_ids,p.is_drug,p.drug_image,p.drug_reg_cert_no,p.common_name,p.dosage_form,
                p.unit_price,p.batch_number,p.mah,p.mah_address,p.manufacturer,p.manufacturer_address,p.indications,p.dosage,
                p.unit_price,p.batch_number,p.mah,p.mah_address,p.manufacturer,p.manufacturer_address,p.indications,p.dosage,
                p.adverse_reactions,p.contraindications,p.precautions,p.is_audit,p.store_id,p.return_address,p.brand,p.food_production_license_code,
                p.adverse_reactions,p.contraindications,p.precautions,p.is_audit,p.store_id,p.return_address,p.brand,p.food_production_license_code,
-               p.origin_place,p.net_content,p.shelf_life,p.domestic_imported,app_ids,p.purchase_limit
+               p.origin_place,p.net_content,p.shelf_life,p.domestic_imported,app_ids,p.purchase_limit,p.single_purchase_limit
         from fs_store_product_scrm p
         from fs_store_product_scrm p
     </sql>
     </sql>
 
 
@@ -280,6 +281,7 @@
             <if test="domesticImported != null and domesticImported != ''">domestic_imported,</if>
             <if test="domesticImported != null and domesticImported != ''">domestic_imported,</if>
             <if test="appIds != null and appIds != ''">app_ids, </if>
             <if test="appIds != null and appIds != ''">app_ids, </if>
             <if test="purchaseLimit != null">purchase_limit,</if>
             <if test="purchaseLimit != null">purchase_limit,</if>
+            <if test="singlePurchaseLimit != null">single_purchase_limit,</if>
         </trim>
         </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="image != null and image != ''">#{image},</if>
             <if test="image != null and image != ''">#{image},</if>
@@ -353,6 +355,7 @@
             <if test="domesticImported != null and domesticImported != ''">#{domesticImported},</if>
             <if test="domesticImported != null and domesticImported != ''">#{domesticImported},</if>
             <if test="appIds != null and appIds != ''">#{appIds}, </if>
             <if test="appIds != null and appIds != ''">#{appIds}, </if>
             <if test="purchaseLimit != null">#{purchaseLimit},</if>
             <if test="purchaseLimit != null">#{purchaseLimit},</if>
+            <if test="singlePurchaseLimit != null">#{singlePurchaseLimit},</if>
         </trim>
         </trim>
     </insert>
     </insert>
 
 
@@ -430,6 +433,7 @@
             <if test="domesticImported != null">domestic_imported = #{domesticImported},</if>
             <if test="domesticImported != null">domestic_imported = #{domesticImported},</if>
             <if test="appIds != null and appIds != ''">app_ids = #{appIds}, </if>
             <if test="appIds != null and appIds != ''">app_ids = #{appIds}, </if>
             <if test="purchaseLimit != null">purchase_limit = #{purchaseLimit},</if>
             <if test="purchaseLimit != null">purchase_limit = #{purchaseLimit},</if>
+            <if test="singlePurchaseLimit != null">single_purchase_limit = #{singlePurchaseLimit},</if>
         </trim>
         </trim>
         where product_id = #{productId}
         where product_id = #{productId}
     </update>
     </update>

+ 1 - 0
fs-service/src/main/resources/mapper/hisStore/FsStoreScrmMapper.xml

@@ -77,6 +77,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     <select id="selectFsStoreList" parameterType="FsStoreScrm" resultMap="FsStoreResult">
     <select id="selectFsStoreList" parameterType="FsStoreScrm" resultMap="FsStoreResult">
         <include refid="selectFsStoreVo"/>
         <include refid="selectFsStoreVo"/>
         <where>
         <where>
+            <if test="storeId != null  and storeId != ''"> and store_id = #{storeId}</if>
             <if test="storeName != null  and storeName != ''"> and store_name like concat('%', #{storeName}, '%')</if>
             <if test="storeName != null  and storeName != ''"> and store_name like concat('%', #{storeName}, '%')</if>
             <if test="address != null  and address != ''"> and address like concat('%', #{address}, '%')</if>
             <if test="address != null  and address != ''"> and address like concat('%', #{address}, '%')</if>
             <if test="phone != null  and phone != ''"> and phone = #{phone}</if>
             <if test="phone != null  and phone != ''"> and phone = #{phone}</if>

+ 1 - 1
fs-service/src/main/resources/mapper/live/LiveMapper.xml

@@ -112,7 +112,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 
 
     <select id="selectLiveByLiveId" parameterType="Long" resultMap="LiveResult">
     <select id="selectLiveByLiveId" parameterType="Long" resultMap="LiveResult">
         <include refid="selectLiveVo"/>
         <include refid="selectLiveVo"/>
-        where live_id = #{liveId}
+        where live_id = #{liveId} and is_del = 0
     </select>
     </select>
 
 
     <select id="selectLiveByLiveIdAndCompanyIdAndCompanyUserId" resultMap="LiveResult">
     <select id="selectLiveByLiveIdAndCompanyIdAndCompanyUserId" resultMap="LiveResult">

+ 34 - 3
fs-user-app/src/main/java/com/fs/app/controller/UserController.java

@@ -16,6 +16,8 @@ import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.exception.CustomException;
 import com.fs.common.exception.CustomException;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.sign.Md5Utils;
 import com.fs.common.utils.sign.Md5Utils;
+import com.fs.company.domain.CompanyUser;
+import com.fs.company.service.ICompanyUserService;
 import com.fs.core.config.WxMaConfiguration;
 import com.fs.core.config.WxMaConfiguration;
 import com.fs.course.domain.FsCourseWatchLog;
 import com.fs.course.domain.FsCourseWatchLog;
 import com.fs.course.service.IFsCourseWatchLogService;
 import com.fs.course.service.IFsCourseWatchLogService;
@@ -34,6 +36,8 @@ import com.fs.his.utils.PhoneUtil;
 import com.fs.his.vo.FsUserCouponCountUVO;
 import com.fs.his.vo.FsUserCouponCountUVO;
 import com.fs.his.vo.FsUserCouponListUVO;
 import com.fs.his.vo.FsUserCouponListUVO;
 import com.fs.his.vo.UserVo;
 import com.fs.his.vo.UserVo;
+import com.fs.hisStore.domain.FsStoreOrderScrm;
+import com.fs.hisStore.service.IFsStoreOrderScrmService;
 import com.fs.qw.domain.QwExternalContact;
 import com.fs.qw.domain.QwExternalContact;
 import com.fs.qw.service.IQwAppContactWayService;
 import com.fs.qw.service.IQwAppContactWayService;
 import com.fs.qw.service.IQwExternalContactService;
 import com.fs.qw.service.IQwExternalContactService;
@@ -97,6 +101,12 @@ public class UserController extends  AppBaseController {
     @Autowired
     @Autowired
     private IFsUserService fsUserService;
     private IFsUserService fsUserService;
 
 
+    @Autowired
+    IFsStoreOrderScrmService orderService;
+
+    @Autowired
+    private ICompanyUserService companyUserService;
+
     @Autowired
     @Autowired
     private IQwExternalContactService qwExternalContactService;
     private IQwExternalContactService qwExternalContactService;
     @Autowired
     @Autowired
@@ -141,9 +151,30 @@ public class UserController extends  AppBaseController {
             if (user.getPhone()!=null&&user.getPhone().length()>11&&!user.getPhone().matches("\\d+")){
             if (user.getPhone()!=null&&user.getPhone().length()>11&&!user.getPhone().matches("\\d+")){
                 user.setPhone(decryptPhoneMk(user.getPhone()));
                 user.setPhone(decryptPhoneMk(user.getPhone()));
             }
             }
-            Map<String,Object> map=new HashMap<>();
-            map.put("user",user);
-            return R.ok(map);
+            if (user.getIsShow() !=null && user.getIsShow() == 0){
+                FsStoreOrderScrm order = orderService.selectOrderByUserIdLimit1(user.getUserId());
+                if (order != null) {
+                    user.setIsShow(1);
+                }
+            }
+
+            user.setIsCompanyUser(false);
+            //判断该用户是否是销售
+            int i = companyUserService.countCompanyUserByUserId(user.getUserId());
+            if(i>0){
+                //是销售
+                user.setIsCompanyUser(true);
+            }
+
+            CompanyUser companyUser =new CompanyUser();
+            if(user.getInvitedBySalesId()!=null){
+                companyUser = companyUserService.getInviteCodeByCompanyUserIdAndUserId(user.getInvitedBySalesId());
+                if(companyUser!=null){
+                    companyUser.setInvitationName(StringUtils.isNotEmpty(companyUser.getNickName())?companyUser.getNickName():"");
+                }
+            }
+
+            return R.ok().put("user",user).put("inviteInfo",companyUser);
         } catch (Exception e){
         } catch (Exception e){
             return R.error("操作异常");
             return R.error("操作异常");
         }
         }

+ 0 - 105
fs-user-app/src/test/java/com/fs/test/StockDeductTest.java

@@ -1,105 +0,0 @@
-package com.fs.test;
-
-import com.fs.FsUserAppApplication;
-import com.fs.common.constant.RedisConstant;
-import com.fs.common.core.redis.service.StockDeductService;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.test.context.junit4.SpringRunner;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.*;
-
-/**
- * 50万高并发库存扣减测试
- */
-@RunWith(SpringRunner.class)
-@SpringBootTest(classes = FsUserAppApplication.class)
-@RequiredArgsConstructor
-@Slf4j
-public class StockDeductTest {
-
-    @Autowired
-    private StockDeductService stockDeductService;
-
-    // 商品ID
-    private static final Long PRODUCT_ID = 1001L;
-    // 初始库存(模拟5万库存,应对50万并发扣减)
-    private static final Integer INIT_STOCK = 5000;
-    // 总请求数(50万)
-    private static final int TOTAL_REQUESTS = 50000;
-
-    /**
-     * 模拟50万高并发库存扣减
-     */
-    @Test
-    public void testHighConcurrencyDeduct() throws InterruptedException, ExecutionException {
-        stockDeductService.initStock(PRODUCT_ID, 1L, INIT_STOCK);
-        // Java 8 ExecutorService 线程池(固定线程池,适配高并发)
-        ExecutorService executorService = createHighConcurrencyPool();
-
-        // 存储所有异步任务结果
-        List<CompletableFuture<Boolean>> futureList = new ArrayList<>();
-
-        // 提交50万请求
-        for (int i = 0; i < TOTAL_REQUESTS; i++) {
-//            futureList.add(stockDeductService.deductStockAsync(PRODUCT_ID, 1L, (long) i));
-        }
-
-        // 等待所有任务完成(Java 8 CompletableFuture 批量处理)
-        CompletableFuture<Void> allFutures = CompletableFuture.allOf(
-                futureList.toArray(new CompletableFuture[0])
-        );
-        allFutures.get();
-
-        // 统计结果
-        long successCount = futureList.stream()
-                .map(future -> {
-                    try {
-                        return future.get();
-                    } catch (Exception e) {
-                        return false;
-                    }
-                })
-                .filter(Boolean::booleanValue)
-                .count();
-
-        // 打印结果
-        System.out.println("======================================");
-        System.out.println("50万高并发库存扣减测试完成");
-        System.out.println("成功扣减次数:" + successCount);
-        System.out.println("失败扣减次数:" + (TOTAL_REQUESTS - successCount));
-        System.out.println("最终剩余库存:" + stockDeductService.redisTemplate.opsForValue().get(RedisConstant.STOCK_KEY_PREFIX + PRODUCT_ID));
-        System.out.println("======================================");
-
-        // 关闭线程池
-        executorService.shutdown();
-        executorService.awaitTermination(1, TimeUnit.MINUTES);
-    }
-
-
-
-    private static ExecutorService createHighConcurrencyPool() {
-        int corePoolSize = Runtime.getRuntime().availableProcessors() * 2; // CPU核心数*2
-        int maximumPoolSize = 200; // 最大线程数,根据服务器配置调整
-        long keepAliveTime = 60L;
-        // 用SynchronousQueue,直接提交任务,避免队列积压
-        BlockingQueue<Runnable> workQueue = new SynchronousQueue<>();
-        // 拒绝策略:丢弃最老的任务,避免OOM
-        RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardOldestPolicy();
-
-        return new ThreadPoolExecutor(
-                corePoolSize,
-                maximumPoolSize,
-                keepAliveTime,
-                TimeUnit.SECONDS,
-                workQueue,
-                new ThreadPoolExecutor.CallerRunsPolicy() // 兜底:主线程执行,避免任务丢失
-        );
-    }
-}