Переглянути джерело

Merge branch 'sit' into sit-OrderFood

lutong 2 місяців тому
батько
коміт
ce0d1fc8fb
16 змінених файлів з 341 додано та 123 видалено
  1. 2 2
      alien-entity/src/main/java/shop/alien/entity/store/CommonComment.java
  2. 1 1
      alien-entity/src/main/java/shop/alien/mapper/LifeBrowseRecordMapper.java
  3. 9 0
      alien-entity/src/main/java/shop/alien/mapper/StoreUserMapper.java
  4. 10 0
      alien-entity/src/main/resources/mapper/StoreUserMapper.xml
  5. 1 1
      alien-store/src/main/java/shop/alien/store/controller/CommonCommentController.java
  6. 2 1
      alien-store/src/main/java/shop/alien/store/controller/OperationalActivityController.java
  7. 68 4
      alien-store/src/main/java/shop/alien/store/controller/StoreInfoController.java
  8. 17 2
      alien-store/src/main/java/shop/alien/store/controller/StoreUserController.java
  9. 2 2
      alien-store/src/main/java/shop/alien/store/service/LifeCommentService.java
  10. 2 0
      alien-store/src/main/java/shop/alien/store/service/impl/CommonCommentServiceImpl.java
  11. 7 3
      alien-store/src/main/java/shop/alien/store/service/impl/StoreClockInServiceImpl.java
  12. 8 6
      alien-store/src/main/java/shop/alien/store/service/impl/StoreInfoServiceImpl.java
  13. 164 88
      alien-store/src/main/java/shop/alien/store/service/impl/StoreUserServiceImpl.java
  14. 45 12
      alien-store/src/main/java/shop/alien/store/service/impl/TrackEventServiceImpl.java
  15. 1 0
      alien-store/src/main/java/shop/alien/store/util/CommonConstant.java
  16. 2 1
      alien-util/src/main/java/shop/alien/util/common/constant/CommentSourceTypeEnum.java

+ 2 - 2
alien-entity/src/main/java/shop/alien/entity/store/CommonComment.java

@@ -28,11 +28,11 @@ public class CommonComment implements Serializable {
     @TableId(value = "id", type = IdType.AUTO)
     private Long id;
 
-    @ApiModelProperty(value = "评论来源类型:1-评价的评论 2-动态评论")
+    @ApiModelProperty(value = "评论来源类型:1-评价的评论 2-动态评论 3-打卡评论")
     @TableField("source_type")
     private Integer sourceType;
 
-    @ApiModelProperty(value = "来源关联ID:source_type=1时=rating.id,=2时=动态ID")
+    @ApiModelProperty(value = "来源关联ID:source_type=1时=rating.id,=2时=动态ID,=3打卡id")
     @TableField("source_id")
     private Long sourceId;
 

+ 1 - 1
alien-entity/src/main/java/shop/alien/mapper/LifeBrowseRecordMapper.java

@@ -12,7 +12,7 @@ import java.util.Map;
 public interface LifeBrowseRecordMapper extends BaseMapper<LifeBrowseRecord> {
 
     @Select("SELECT a.id,a.liulan_date liulanDate, b.store_type storeType, b.store_position storePosition, a.liulan_time liulanTime, " +
-            "c.img_url image, b.store_name storeName, b.store_address storeDetailAddress, b.id storeId, a.created_time, d.img_url entranceImage, dict.dict_detail storeTypeStr,ROUND(b.score_avg, 2) scoreAvg,b.business_types_name businessTypesName " +
+            "c.img_url image, b.store_name storeName, b.store_address storeDetailAddress, b.id storeId, a.created_time, d.img_url entranceImage, dict.dict_detail storeTypeStr,ROUND(b.score_avg, 2) scoreAvg,b.business_types_name businessTypesName,b.business_section businessSection " +
             "FROM life_browse_record a " +
             "LEFT JOIN store_info b ON a.store_id = b.id " +
             "LEFT JOIN store_img c on b.id = c.store_id and c.img_type = 11 and c.delete_flag = 0 " +

+ 9 - 0
alien-entity/src/main/java/shop/alien/mapper/StoreUserMapper.java

@@ -36,4 +36,13 @@ public interface StoreUserMapper extends BaseMapper<StoreUser> {
     StoreUser getRemoveUser(@Param("id") String id);
 
     StoreUser getUserIncludeDeleted(@Param("id") Integer id);
+
+    /**
+     * 通过 store_user 与 store_platform_user_role 联表查询主账号下的子账号 id 列表(store_user.id)。
+     * 用于删除/禁用时判断主账号是否有关联子账号、主账号下子账号数量等。
+     *
+     * @param mainAccountId 主账号 id(store_user.id)
+     * @return 子账号的 store_user.id 列表
+     */
+    List<Integer> selectSubAccountUserIdsByMainAccountIdWithRole(@Param("mainAccountId") Integer mainAccountId);
 }

+ 10 - 0
alien-entity/src/main/resources/mapper/StoreUserMapper.xml

@@ -16,4 +16,14 @@
         FROM store_user
         where id = #{id}
     </select>
+
+    <!-- 通过 store_user 与 store_platform_user_role 联表查主账号下的子账号 id(store_user.id),用于删除/禁用时用子账号 id 判断 -->
+    <select id="selectSubAccountUserIdsByMainAccountIdWithRole" resultType="java.lang.Integer">
+        SELECT DISTINCT u.id
+        FROM store_user u
+        INNER JOIN store_platform_user_role r ON u.id = r.user_id AND r.delete_flag = 0
+        WHERE u.sub_account_id = #{mainAccountId}
+          AND u.account_type = 2
+          AND u.delete_flag = 0
+    </select>
 </mapper>

+ 1 - 1
alien-store/src/main/java/shop/alien/store/controller/CommonCommentController.java

@@ -140,7 +140,7 @@ public class CommonCommentController {
     public R getCommitCount(@RequestParam Integer sourceId,
                             @RequestParam Integer sourceType,
                             @RequestParam String userId,
-                            @RequestParam String userType){
+                            @RequestParam(required = false) String userType){
         return R.data(commonCommentService.getCommitCount(sourceId, sourceType, userId, userType));
     }
 }

+ 2 - 1
alien-store/src/main/java/shop/alien/store/controller/OperationalActivityController.java

@@ -60,10 +60,11 @@ public class OperationalActivityController {
             @RequestParam(value = "pageNum", required = false) Integer pageNum,
             @RequestParam(value = "pageSize", required = false) Integer pageSize,
             @RequestParam(value = "status", required = false) Integer status,
+            @RequestParam(value = "activityType", required = false) String activityType,
             @RequestParam(value = "activityName", required = false) String activityName) {
         log.info("OperationalActivityController.pageActivityDetail storeId={}, storeName={}, pageNum={}, pageSize={}, status={}, activityName={}", storeId, storeName, pageNum, pageSize, status, activityName);
         try {
-            IPage<StoreOperationalActivityVO> result = activityService.pageActivityDetail(storeId, storeName, pageNum, pageSize, status, activityName, null);
+            IPage<StoreOperationalActivityVO> result = activityService.pageActivityDetail(storeId, storeName, pageNum, pageSize, status, activityName, activityType);
             return R.data(result);
         } catch (IllegalArgumentException e) {
             return R.fail(e.getMessage());

+ 68 - 4
alien-store/src/main/java/shop/alien/store/controller/StoreInfoController.java

@@ -12,6 +12,7 @@ import lombok.EqualsAndHashCode;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.BeanUtils;
 import org.springframework.http.ResponseEntity;
 import org.springframework.transaction.annotation.Transactional;
@@ -72,6 +73,7 @@ public class StoreInfoController {
     private final StoreInfoMapper storeInfoMapper;
     private final LifeBlacklistMapper lifeBlacklistMapper;
     private final StoreUserMapper storeUserMapper;
+    private final LifeFansMapper lifeFansMapper;
 
     @ApiOperation("获取所有门店")
     @ApiOperationSupport(order = 1)
@@ -1591,7 +1593,7 @@ public class StoreInfoController {
             // 执行分页查询
             IPage<StoreInfo> result = storeInfoMapper.selectPage(pageObj, queryWrapper);
             
-            // 批量查询商户头像
+            // 批量查询商户头像和手机号
             List<Integer> storeIds = new ArrayList<>();
             for (StoreInfo store : result.getRecords()) {
                 if (store.getId() != null) {
@@ -1600,20 +1602,31 @@ public class StoreInfoController {
             }
             
             Map<Integer, String> headImgMap = new HashMap<>();
+            Map<Integer, String> storePhoneMap = new HashMap<>(); // 店铺ID -> 门店手机号
             if (!storeIds.isEmpty()) {
+                // 批量查询店铺信息,获取门店手机号
+                List<StoreInfo> storeInfoList = storeInfoMapper.selectBatchIds(storeIds);
+                for (StoreInfo storeInfo : storeInfoList) {
+                    if (storeInfo.getId() != null && StringUtils.isNotEmpty(storeInfo.getStoreTel())) {
+                        storePhoneMap.put(storeInfo.getId(), storeInfo.getStoreTel());
+                    }
+                }
+                
                 LambdaQueryWrapper<StoreUser> userWrapper = new LambdaQueryWrapper<>();
                 userWrapper.in(StoreUser::getStoreId, storeIds)
                           .eq(StoreUser::getDeleteFlag, 0);
                 List<StoreUser> storeUsers = storeUserMapper.selectList(userWrapper);
                 for (StoreUser user : storeUsers) {
-                    if (user.getStoreId() != null && user.getHeadImg() != null) {
+                    if (user.getStoreId() != null) {
                         // 如果同一个店铺有多个用户,取第一个(通常是主账号)
-                        headImgMap.putIfAbsent(user.getStoreId(), user.getHeadImg());
+                        if (user.getHeadImg() != null) {
+                            headImgMap.putIfAbsent(user.getStoreId(), user.getHeadImg());
+                        }
                     }
                 }
             }
             
-            // 转换为包含头像的VO对象
+            // 转换为包含头像和关注状态的VO对象
             List<StoreInfoWithHeadImg> voList = new ArrayList<>();
             for (StoreInfo store : result.getRecords()) {
                 StoreInfoWithHeadImg vo = new StoreInfoWithHeadImg();
@@ -1621,6 +1634,53 @@ public class StoreInfoController {
                 // 获取商户头像,如果没有头像则设置为null
                 String headImg = headImgMap.get(store.getId());
                 vo.setHeadImg(headImg != null && !headImg.trim().isEmpty() ? headImg : null);
+                
+                // 当前登录用户是否关注该店铺
+                Integer isFollowed = 0; // 默认未关注
+                if (userLoginInfo != null && userLoginInfo.getUserId() > 0 && "user".equals(userLoginInfo.getType())) {
+                    try {
+                        // userId是用户ID(数字),查询用户信息获取手机号
+                        Integer userId = userLoginInfo.getUserId();
+                        LifeUser lifeUser = lifeUserMapper.selectById(userId);
+                        
+                        // 获取门店手机号
+                        String storePhone = storePhoneMap.get(store.getId());
+                        
+                        if (lifeUser != null && StringUtils.isNotEmpty(lifeUser.getUserPhone()) 
+                                && StringUtils.isNotEmpty(storePhone)) {
+                            // 构造关注关系:followed_id = "store_" + 门店手机号,fans_id = "user_" + 用户手机号
+                            String followedId = "store_" + storePhone;
+                            String fansId = "user_" + lifeUser.getUserPhone();
+                            
+                            // 查询关注关系
+                            LambdaQueryWrapper<LifeFans> fansWrapper = new LambdaQueryWrapper<>();
+                            fansWrapper.eq(LifeFans::getFollowedId, followedId)
+                                    .eq(LifeFans::getFansId, fansId)
+                                    .eq(LifeFans::getDeleteFlag, 0);
+                            LifeFans lifeFans = lifeFansMapper.selectOne(fansWrapper);
+                            
+                            if (lifeFans != null) {
+                                isFollowed = 1; // 已关注
+                            } else {
+                                isFollowed = 0; // 未关注
+                            }
+                        } else {
+                            isFollowed = 0; // 无法获取用户手机号或店铺手机号,默认未关注
+                        }
+                    } catch (NumberFormatException e) {
+                        log.error("用户ID格式错误 - userId: {}, storeId: {}, error: {}", 
+                                userLoginInfo.getUserId(), store.getId(), e.getMessage());
+                        isFollowed = 0; // 用户ID格式错误,默认未关注
+                    } catch (Exception e) {
+                        log.error("查询用户关注状态失败 - userId: {}, storeId: {}, error: {}", 
+                                userLoginInfo.getUserId(), store.getId(), e.getMessage(), e);
+                        isFollowed = 0; // 查询失败,默认未关注
+                    }
+                } else {
+                    isFollowed = 0; // 用户未登录或不是普通用户,默认未关注
+                }
+                
+                vo.setIsFollowed(isFollowed);
                 voList.add(vo);
             }
             
@@ -1647,6 +1707,10 @@ public class StoreInfoController {
         @ApiModelProperty(value = "商户头像(来自store_user表的head_img字段)")
         @TableField(exist = false)
         private String headImg;
+        
+        @ApiModelProperty(value = "是否已关注(1-已关注,0-未关注)")
+        @TableField(exist = false)
+        private Integer isFollowed;
     }
 
 

+ 17 - 2
alien-store/src/main/java/shop/alien/store/controller/StoreUserController.java

@@ -288,6 +288,13 @@ public class StoreUserController {
      */
     @ApiOperation("web端切换商家端用户状态(主账号有子账号时禁止禁用;返回更新后状态供前端及时展示)")
     @ApiOperationSupport(order = 7)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "subAccountId", value = "子账号用户ID(store_user.id),子账号启用/禁用时必传", dataType = "int", paramType = "body"),
+            @ApiImplicitParam(name = "id", value = "主账号时为主账号ID;子账号时可传主账号id(与 parentAccountId 一致)", dataType = "int", paramType = "body"),
+            @ApiImplicitParam(name = "parentAccountId", value = "主账号ID(子账号启用/禁用时必传,与分页列表一致)", dataType = "int", paramType = "body"),
+            @ApiImplicitParam(name = "accountType", value = "账号类型:1-主账号,2-子账号", dataType = "int", paramType = "body"),
+            @ApiImplicitParam(name = "status", value = "当前状态:0-启用,1-禁用(用于切换,点击禁用传0,点击启用传1)", dataType = "int", paramType = "body")
+    })
     @PutMapping("/switchingStates")
     public R<StoreUserVo> switchingStates(@RequestBody StoreUserVo storeUser) {
         log.info("StoreUserController.switchingStates?storeUser={}", storeUser);
@@ -415,10 +422,18 @@ public class StoreUserController {
     }
 
     /**
-     * 手动删除商家账号及店铺
-     *  进行校验
+     * 手动删除商家账号及店铺。
+     * 主账号:有关联店铺或有关联子账号禁止删除。
+     * 子账号:按 userId(subAccountId)+storeId 逻辑删除角色;若主账号下仅 1 个子账号且非主账号则同时逻辑删除 store_user。
      */
     @ApiOperation("手动删除商家账号及店铺")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "subAccountId", value = "子账号用户ID(store_user.id)", dataType = "int", paramType = "body"),
+            @ApiImplicitParam(name = "id", value = "主账号时为主账号ID;子账号时可作主账号id(parentAccountId)时传 parentAccountId", dataType = "int", paramType = "body"),
+            @ApiImplicitParam(name = "parentAccountId", value = "主账号ID(子账号删除时必传,与分页列表一致)", dataType = "int", paramType = "body"),
+            @ApiImplicitParam(name = "accountType", value = "账号类型:1-主账号,2-子账号", dataType = "int", paramType = "body"),
+            @ApiImplicitParam(name = "phone", value = "子账号联系电话(可选,与 subAccountId 二选一)", dataType = "String", paramType = "body")
+    })
     @PostMapping("/deleteStoreAccountInfo")
     public R<String> deleteStoreAccountInfo(@RequestBody StoreUserVo storeUserVo) {
         log.info("StoreUserController.deleteStoreAccountInfo?phone={}, id={}", storeUserVo.getPhone(), storeUserVo.getSubAccountId());

+ 2 - 2
alien-store/src/main/java/shop/alien/store/service/LifeCommentService.java

@@ -196,7 +196,7 @@ public class LifeCommentService {
                 return commonRatingMapper.update(null, new UpdateWrapper<CommonRating>()
                         .setSql("like_count = like_count + 1")
                         .eq("id", huifuId));
-            } else if (CommonConstant.COMMENT_LIKE.equals(type) || CommonConstant.DYNAMIC_LIKE.equals(type)) {
+            } else if (CommonConstant.COMMENT_LIKE.equals(type) || CommonConstant.DYNAMIC_LIKE.equals(type) || CommonConstant.CLOCK_IN_LIKE.equals(type)) {
                 // 12-评论点赞:更新评论表点赞数
                 return commonCommentMapper.update(null, new UpdateWrapper<CommonComment>()
                         .setSql("like_count = like_count + 1")
@@ -364,7 +364,7 @@ public class LifeCommentService {
                 return commonRatingMapper.update(null, new UpdateWrapper<CommonRating>()
                         .setSql("like_count = like_count - 1")
                         .eq("id", huifuId));
-            } else if (CommonConstant.COMMENT_LIKE.equals(type) || CommonConstant.DYNAMIC_LIKE.equals(type)) {
+            } else if (CommonConstant.COMMENT_LIKE.equals(type) || CommonConstant.DYNAMIC_LIKE.equals(type) || CommonConstant.CLOCK_IN_LIKE.equals(type)) {
                 // 12-评论点赞:更新评论表点赞数
                 return commonCommentMapper.update(null, new UpdateWrapper<CommonComment>()
                         .setSql("like_count = like_count - 1")

+ 2 - 0
alien-store/src/main/java/shop/alien/store/service/impl/CommonCommentServiceImpl.java

@@ -86,6 +86,8 @@ public class CommonCommentServiceImpl extends ServiceImpl<CommonCommentMapper, C
             likeType = CommonConstant.COMMENT_LIKE;
         } else if (sourceType == CommentSourceTypeEnum.DYNAMIC_COMMENT.getType()) {
             likeType = CommonConstant.DYNAMIC_LIKE;
+        } else if (sourceType == CommentSourceTypeEnum.CLOCK_IN_COMMENT.getType()){
+            likeType = CommonConstant.CLOCK_IN_LIKE;
         }
         List<CommonCommentVo> firstLevelComment = getFirstLevelComment(sourceType, sourceId, pageNum, pageSize, userId,likeType);
         CommonRatingVo commonRatingVo = new CommonRatingVo();

+ 7 - 3
alien-store/src/main/java/shop/alien/store/service/impl/StoreClockInServiceImpl.java

@@ -1,6 +1,7 @@
 package shop.alien.store.service.impl;
 
 import cn.hutool.core.bean.BeanUtil;
+import com.alibaba.fastjson2.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
@@ -12,16 +13,16 @@ import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
-import com.alibaba.fastjson2.JSONObject;
 import shop.alien.entity.store.*;
 import shop.alien.entity.store.vo.StoreClockInVo;
 import shop.alien.entity.store.vo.WebSocketVo;
 import shop.alien.mapper.*;
 import shop.alien.store.config.WebSocketProcess;
+import shop.alien.store.service.CommonCommentService;
 import shop.alien.store.service.StoreClockInService;
 import shop.alien.store.service.StoreCommentService;
 import shop.alien.store.util.ai.AiContentModerationUtil;
-import shop.alien.store.util.ai.AiVideoModerationUtil;
+import shop.alien.util.common.constant.CommentSourceTypeEnum;
 
 import javax.annotation.PostConstruct;
 import javax.annotation.PreDestroy;
@@ -66,6 +67,8 @@ public class StoreClockInServiceImpl extends ServiceImpl<StoreClockInMapper, Sto
 
     private final LifeNoticeMapper lifeNoticeMapper;
 
+    private final CommonCommentService commonCommentService;
+
     private final WebSocketProcess webSocketProcess;
 
     // 初始化线程池
@@ -253,7 +256,8 @@ public class StoreClockInServiceImpl extends ServiceImpl<StoreClockInMapper, Sto
 //            vo.setClockInStoreNum(clockInStoreNumMap.get(vo.getStoreId()));
 //            // 打卡次数
 //            vo.setClockInNum(clockInNum);
-            vo.setCommentCount(Integer.parseInt(storeCommentService.getCommitCountAndScore(vo.getId(), 4, vo.getStoreId(),null,null).get("commitCount").toString()));
+            Map<String, Object> commitCount = commonCommentService.getCommitCount(vo.getId(), CommentSourceTypeEnum.CLOCK_IN_COMMENT.getType(), userId.toString(), null);
+            vo.setCommentCount(Integer.parseInt(commitCount.get("commentCount").toString()));
             if(Arrays.asList("酒吧", "KTV", "洗浴汗蒸", "按摩足疗").contains(vo.getBusinessSectionName())){
                 vo.setStoreTypeNew(1);
             } else if (Arrays.asList("丽人美发", "运动健身").contains(vo.getBusinessSectionName())){

+ 8 - 6
alien-store/src/main/java/shop/alien/store/service/impl/StoreInfoServiceImpl.java

@@ -5696,6 +5696,8 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
             result.setStorePhone(storeUser.getPhone());
             result.setStoreUserName(storeUser.getName());
             result.setIdCard(storeUser.getIdCard());
+            result.setImgUrl(storeUser.getHeadImg());
+
         }
 //        //存入执照图片地址
 //        List<StoreImg> storeImgs = storeImgMapper.selectList(new LambdaQueryWrapper<StoreImg>().eq(StoreImg::getStoreId, storeId).eq(StoreImg::getImgType, 14));
@@ -5740,12 +5742,12 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
             result.setEntranceImage("null");
         }
         // 存放商家头像
-        List<StoreImg> storeImgs1 = storeImgMapper.selectList(new LambdaQueryWrapper<StoreImg>().eq(StoreImg::getStoreId, storeId).eq(StoreImg::getImgType, 10));
-        if (!storeImgs1.isEmpty()) {
-            result.setImgUrl(storeImgs1.get(0).getImgUrl());
-        } else {
-            result.setImgUrl("null");
-        }
+//        List<StoreImg> storeImgs1 = storeImgMapper.selectList(new LambdaQueryWrapper<StoreImg>().eq(StoreImg::getStoreId, storeId).eq(StoreImg::getImgType, 10));
+//        if (!storeImgs1.isEmpty()) {
+//            result.setImgUrl(storeImgs1.get(0).getImgUrl());
+//        } else {
+//            result.setImgUrl("null");
+//        }
 
         // 获取店铺相册
         List<StoreImg> storeAlbumList = new ArrayList<>();

+ 164 - 88
alien-store/src/main/java/shop/alien/store/service/impl/StoreUserServiceImpl.java

@@ -537,7 +537,7 @@ public class StoreUserServiceImpl extends ServiceImpl<StoreUserMapper, StoreUser
                 StoreUser mainAccount = role.getStoreId() == null ? null : mainAccountMap.get(role.getStoreId());
                 StoreUserVo storeUserVo = new StoreUserVo();
                 BeanUtils.copyProperties(subAccount, storeUserVo);
-                // 分页列表:账号ID、parentAccountId 均为主账号 id(与主账号联系电话对应),子账号 id 放入 subAccountId
+                // 分页:返回主账号id(id/parentAccountId)及子账号id(subAccountId),禁用/删除时用 subAccountId;状态以 store_platform_user_role.status 为准,启用/禁用后分页再查即展示最新状态
                 Integer mainId = mainAccount != null ? mainAccount.getId() : subAccount.getSubAccountId();
                 storeUserVo.setId(mainId != null ? mainId : subAccount.getId());
                 storeUserVo.setSubAccountId(subAccount.getId());
@@ -631,72 +631,111 @@ public class StoreUserServiceImpl extends ServiceImpl<StoreUserMapper, StoreUser
 
 
     /**
-     * web端切换商家端用户状态(主账号底下有子账号时禁止禁用;子账号无限制)。
-     * 若同一账号既是主账号又是子账号:禁用时只禁用子账号角色,主账号保持启用。
+     * 启用/禁用(参考 deleteStoreAccountInfo 数据格式)。
+     * 要求1:子账号禁用时,若该账号既是主账号也是子账号,只禁用子账号角色,不禁用主账号;分页查询时页面展示最新状态(以 store_platform_user_role.status 为准)。
+     * 要求2:子账号可以禁用。
+     * 要求3:主账号底下有关联子账号时不能禁用。
+     * 状态:0 启用/正常,1 禁用;接口返回 newStatus 便于前端及分页展示最新状态。
      */
     @Override
     public R<StoreUserVo> switchingStates(StoreUserVo storeUserParam) {
-        // 默认传 0 表示执行禁用(状态改为 1),分页列表展示为禁用;传 1 表示执行启用(状态改为 0),分页列表展示为启用
+        if (storeUserParam == null) {
+            throw new RuntimeException("参数不能为空");
+        }
+        // 当前状态 0=启用 1=禁用;点击禁用则 newStatus=1,点击启用则 newStatus=0
         int currentStatus = storeUserParam.getStatus() != null ? storeUserParam.getStatus() : 0;
         int newStatus = (currentStatus == 0) ? 1 : 0;
         boolean isMainAccount = storeUserParam.getAccountType() != null && storeUserParam.getAccountType() == 1;
 
-        if(storeUserParam.getAccountType()==2){
-            StoreUser storeUser = storeUserMapper.selectById(storeUserParam.getSubAccountId());
+        // ---------- 子账号:要求2 子账号可以禁用 ----------
+        if (storeUserParam.getAccountType() != null && storeUserParam.getAccountType() == 2) {
+            Integer subAccountId = storeUserParam.getSubAccountId();
+            if (subAccountId == null) {
+                throw new RuntimeException("子账号 id 不能为空");
+            }
+            StoreUser storeUser = storeUserMapper.selectById(subAccountId);
             if (storeUser == null) {
                 throw new RuntimeException("用户不存在");
             }
-            // 统计该用户在 platform 中未删除的角色数,用于判断是否「既是主账号也是子账号」
-            long roleCount = storePlatformUserRoleMapper.selectCount(
-                    new LambdaQueryWrapper<StorePlatformUserRole>()
-                            .eq(StorePlatformUserRole::getUserId, storeUser.getId())
-                            .eq(StorePlatformUserRole::getDeleteFlag, 0));
-            log.info("员工状态的status roleCount={}",roleCount);
-            boolean isBothMainAndSub = isMainAccount && roleCount > 0;
-            // 1 既是主账号也是子账号且本次为禁用:只禁用子账号角色,不更新 store_user,主账号保持启用;子账号在列表上按 role.status 展示为禁用
+            Integer mainAccountId = storeUserParam.getParentAccountId() != null ? storeUserParam.getParentAccountId() : storeUser.getSubAccountId();
+            Integer storeId = null;
+            if (mainAccountId != null) {
+                StoreUser mainAccount = storeUserMapper.selectById(mainAccountId);
+                if (mainAccount != null) storeId = mainAccount.getStoreId();
+            }
+
+            // 要求1:若该账号既是主账号也是子账号,只禁用子账号角色,不禁用主账号(同一手机号存在主账号则只更新 role)
+            boolean isBothMainAndSub = false;
+            if (storeUser.getPhone() != null && !storeUser.getPhone().isEmpty()) {
+                long mainSamePhone = storeUserMapper.selectCount(
+                        new LambdaQueryWrapper<StoreUser>()
+                                .eq(StoreUser::getPhone, storeUser.getPhone())
+                                .eq(StoreUser::getAccountType, 1)
+                                .eq(StoreUser::getDeleteFlag, 0));
+                isBothMainAndSub = mainSamePhone > 0;
+            }
+
+            LambdaUpdateWrapper<StorePlatformUserRole> roleUpdateWrapper = new LambdaUpdateWrapper<>();
+            if (storeId != null) {
+                roleUpdateWrapper.eq(StorePlatformUserRole::getStoreId, storeId);
+            }
+            roleUpdateWrapper.eq(StorePlatformUserRole::getUserId, storeUser.getId())
+                    .eq(StorePlatformUserRole::getDeleteFlag, 0)
+                    .set(StorePlatformUserRole::getStatus, newStatus);
+            storePlatformUserRoleMapper.update(null, roleUpdateWrapper);
+
             if (isBothMainAndSub) {
-                LambdaUpdateWrapper<StorePlatformUserRole> roleUpdateWrapper = new LambdaUpdateWrapper<>();
-                roleUpdateWrapper.eq(StorePlatformUserRole::getUserId, storeUser.getId())
-                        .eq(StorePlatformUserRole::getDeleteFlag, 0)
-                        .set(StorePlatformUserRole::getStatus, currentStatus);
-                storePlatformUserRoleMapper.update(null, roleUpdateWrapper);
+                // 既是主账号也是子账号:只改了 role,不更新 store_user,主账号保持启用;分页列表以 role.status 展示为最新状态
                 StoreUserVo vo = new StoreUserVo();
-                vo.setId(storeUser.getId());
+                vo.setId(mainAccountId != null ? mainAccountId : storeUser.getId());
+                vo.setSubAccountId(storeUser.getId());
+                vo.setParentAccountId(mainAccountId);
                 vo.setStatus(newStatus);
                 vo.setSwitchStatus(newStatus == 0);
                 return R.data(vo);
             }
-            LambdaUpdateWrapper<StorePlatformUserRole> roleUpdateWrapper = new LambdaUpdateWrapper<>();
-            roleUpdateWrapper.eq(StorePlatformUserRole::getUserId, storeUser.getId())
-                    .eq(StorePlatformUserRole::getDeleteFlag, 0)
-                    .set(StorePlatformUserRole::getStatus, currentStatus);
-            storePlatformUserRoleMapper.update(null, roleUpdateWrapper);
+
             baseRedisService.delete("store_" + storeUser.getPhone());
             LambdaUpdateWrapper<StoreUser> userUpdateWrapper = new LambdaUpdateWrapper<>();
-            userUpdateWrapper.eq(StoreUser::getId, storeUser.getId()).set(StoreUser::getStatus, currentStatus);
+            userUpdateWrapper.eq(StoreUser::getId, storeUser.getId()).set(StoreUser::getStatus, newStatus);
             storeUserMapper.update(null, userUpdateWrapper);
+            long enabledRoleCount = storePlatformUserRoleMapper.selectCount(
+                    new LambdaQueryWrapper<StorePlatformUserRole>()
+                            .eq(StorePlatformUserRole::getUserId, storeUser.getId())
+                            .eq(StorePlatformUserRole::getDeleteFlag, 0)
+                            .eq(StorePlatformUserRole::getStatus, 0));
+            if (enabledRoleCount == 0) {
+                baseRedisService.delete("store_" + storeUser.getPhone());
+                userUpdateWrapper.eq(StoreUser::getId, storeUser.getId()).set(StoreUser::getStatus, newStatus);
+                storeUserMapper.update(null, userUpdateWrapper);
+            }
             StoreUserVo vo = new StoreUserVo();
-            vo.setId(storeUser.getId());
+            vo.setId(mainAccountId != null ? mainAccountId : storeUser.getId());
+            vo.setSubAccountId(storeUser.getId());
+            vo.setParentAccountId(mainAccountId);
             vo.setStatus(newStatus);
             vo.setSwitchStatus(newStatus == 0);
             return R.data(vo);
-
         }
 
-        // 本次为禁用
-        if (newStatus == 0) {
-            // 2 对于主账号:有关联子账号则禁止禁用,请先删除主账号下的子账号
-            if (isMainAccount) {
-                List<StoreUser> subAccounts = getSubAccountsByMainAccountId(storeUserParam.getId());
-                if (subAccounts != null && !subAccounts.isEmpty()) {
-                    throw new RuntimeException("请先删除主账号下的子账号后再禁用");
-                }
+        // ---------- 主账号:要求3 底下有关联子账号不能禁用 ----------
+        if (newStatus == 1 && isMainAccount) {
+            List<Integer> subAccountIds = storeUserMapper.selectSubAccountUserIdsByMainAccountIdWithRole(storeUserParam.getId());
+            if (subAccountIds != null && !subAccountIds.isEmpty()) {
+                throw new RuntimeException("请先删除主账号下的子账号后再禁用");
             }
         }
-        // 启用/禁用:更新 store_user 的 status(禁用 0→1,启用 1→0),列表即时展示用返回的 vo
+
+        // 主账号启用/禁用:更新 store_user.status,禁用时删 token;返回 newStatus 便于分页展示最新状态
         LambdaUpdateWrapper<StoreUser> userUpdateWrapper = new LambdaUpdateWrapper<>();
         userUpdateWrapper.eq(StoreUser::getId, storeUserParam.getId()).set(StoreUser::getStatus, newStatus);
         storeUserMapper.update(null, userUpdateWrapper);
+        if (newStatus == 1) {
+            StoreUser u = storeUserMapper.selectById(storeUserParam.getId());
+            if (u != null && u.getPhone() != null) {
+                baseRedisService.delete("store_" + u.getPhone());
+            }
+        }
         StoreUserVo vo = new StoreUserVo();
         vo.setId(storeUserParam.getId());
         vo.setStatus(newStatus);
@@ -1074,90 +1113,127 @@ public class StoreUserServiceImpl extends ServiceImpl<StoreUserMapper, StoreUser
     }
 
     /**
-     * 删除门店
-     *
-     * @param storeUserVo
+     * 删除商家账号。
+     * 主账号:有关联店铺禁止删除,有关联子账号禁止删除。
+     * 子账号:参照 removeRole 逻辑,按 userId/storeId 逻辑删除 store_platform_user_role;若该主账号下仅 1 个子账号且非主账号则同时逻辑删除 store_user 并清 token。
      */
-
     @Override
     public String deleteStoreAccountInfo(StoreUserVo storeUserVo) {
-        // 解析用户:优先用子账号联系方式 phone 查;未传 phone 时用 id/subAccountId 查 store_user,后端从库中拿到子账号联系方式(phone)
+        if (storeUserVo == null) {
+            log.warn("deleteStoreAccountInfo 参数不能为空");
+            return "删除失败";
+        }
+        // 1. 解析用户:子账号 id(userId) 优先用 subAccountId,其次 id;或通过 phone 查
+        Integer userId = null;
         StoreUser storeUser = null;
-        if ((storeUserVo.getId() != null || storeUserVo.getSubAccountId() != null)) {
+        if (storeUserVo.getSubAccountId() != null || storeUserVo.getId() != null) {
             Integer idParam = storeUserVo.getSubAccountId() != null ? storeUserVo.getSubAccountId() : storeUserVo.getId();
             storeUser = storeUserMapper.selectOne(new LambdaQueryWrapper<StoreUser>()
                     .eq(StoreUser::getId, idParam).eq(StoreUser::getDeleteFlag, 0));
             if (storeUser != null) {
-                log.info("deleteStoreAccountInfo 通过 id 查到用户,后端拿到子账号联系方式: userId={}, phone={}", storeUser.getId(), storeUser.getPhone());
+                userId = storeUser.getId();
+                log.info("deleteStoreAccountInfo 通过子账号 id 查到用户: userId={}, phone={}", userId, storeUser.getPhone());
             }
         }
-        if (storeUserVo == null) {
-            log.warn("deleteStoreAccountInfo 用户不存在或已删除,请传子账号联系电话(phone)或用户id");
-            return "删除失败";
-        }
-        // 通过 phone 再尝试解析用户(前端可能只传 phone)
         if (storeUser == null && storeUserVo.getPhone() != null) {
             storeUser = storeUserMapper.selectOne(new LambdaQueryWrapper<StoreUser>()
                     .eq(StoreUser::getPhone, storeUserVo.getPhone()).eq(StoreUser::getDeleteFlag, 0));
+            if (storeUser != null) userId = storeUser.getId();
         }
-        if (storeUser == null) {
-            log.warn("deleteStoreAccountInfo 用户不存在或已删除,请传子账号联系电话(phone)或用户id");
+        if (userId == null || storeUser == null) {
+            log.warn("deleteStoreAccountInfo 用户不存在或已删除,请传 userId(subAccountId/id)或 phone");
             return "删除失败";
         }
-        Integer userId = storeUser.getId();
 
-        // 删除主账号校验:该主账号下有店铺禁止删除、该主账号下有子账号禁止删除(主账号列表返回的 id 即主账号 id)
+        // 2. 主账号删除校验:有关联店铺禁止删除,有关联子账号禁止删除
         if (storeUserVo.getAccountType() != null && storeUserVo.getAccountType() == 1) {
-            if (storeUser.getStoreId() != null) {
-                log.error("该主账号下存在店铺,禁止删除");
+            Integer mainIdForCheck = storeUserVo.getId();
+            if (mainIdForCheck == null) {
+                log.warn("deleteStoreAccountInfo 删除主账号时 id 不能为空");
+                return "删除失败";
+            }
+            StoreUser mainUser = storeUserMapper.selectById(mainIdForCheck);
+            if (mainUser != null && mainUser.getStoreId() != null && mainUser.getStoreId() > 0) {
+                log.error("该主账号下存在关联店铺,禁止删除");
                 return "当前账号下存在店铺 禁止删除";
             }
-            Integer mainIdForCheck = storeUserVo.getId();
-            if (mainIdForCheck != null) {
-                List<StoreUser> subAccounts = getSubAccountsByMainAccountId(mainIdForCheck);
-                if (subAccounts != null && !subAccounts.isEmpty()) {
-                    log.error("该主账号下存在关联子账号,禁止删除");
-                    return "当前账号下存在子账号禁止删除";
-                }
+            List<Integer> subAccountIds = storeUserMapper.selectSubAccountUserIdsByMainAccountIdWithRole(mainIdForCheck);
+            if (subAccountIds != null && !subAccountIds.isEmpty()) {
+                log.error("该主账号下存在关联子账号,禁止删除");
+                return "当前账号下存在子账号禁止删除";
             }
+            return null;
         }
 
-        // 主账号 id:优先用子账号 store_user.sub_account_id;无则用前端列表返回的 parentAccountId(与分页接口 records 字段一致)
+        // 3. 子账号删除:入参等价于 userId、主账号 id(mainAccountId)、主账号店铺 storeId;先查子账号 id 列表再判断
         Integer mainAccountId = storeUser.getSubAccountId() != null ? storeUser.getSubAccountId() : storeUserVo.getParentAccountId();
         if (mainAccountId == null) {
-            log.warn("deleteStoreAccountInfo 无法确定主账号,请传 parentAccountId 或保证子账号 store_user.sub_account_id 有值");
+            log.warn("deleteStoreAccountInfo 无法确定主账号,请传 parentAccountId 或保证子账号 sub_account_id 有值");
             return "删除失败";
         }
-        // 查询主账号下的子账号数量(按 store_user 统计:account_type=2 且 sub_account_id=主账号id)
-        List<StoreUser> subAccountsUnderMain = getSubAccountsByMainAccountId(mainAccountId);
-        int subAccountCount = subAccountsUnderMain != null ? subAccountsUnderMain.size() : 0;
-        log.info("主账号下子账号数量: mainAccountId={}, subAccountCount={}", mainAccountId, subAccountCount);
-
-        // 仅删除该主账号对应店铺下的角色:取主账号的 store_id,按 userId + storeId 逻辑删除 store_platform_user_role
-        StoreUser mainAccount = storeUserMapper.selectById(mainAccountId);
-        Integer storeId = mainAccount != null ? mainAccount.getStoreId() : null;
+
+        // 3.1 查询主账号下的子账号 id 列表,根据子账号 id 判断数量及是否包含当前 userId
+        List<Integer> subAccountIds = storeUserMapper.selectSubAccountUserIdsByMainAccountIdWithRole(mainAccountId);
+        if (subAccountIds == null) subAccountIds = Collections.emptyList();
+        int subAccountCount = subAccountIds.size();
+        log.info("deleteStoreAccountInfo 主账号下子账号数量: userId={}, mainAccountId={}, storeId={}, subAccountCount={}", userId, mainAccountId, subAccountCount);
+        if (!subAccountIds.contains(userId)) {
+            log.warn("deleteStoreAccountInfo 当前用户不在主账号子账号列表中: userId={}, mainAccountId={}", userId, mainAccountId);
+            return "删除失败";
+        }
+
+        // 3.2 逻辑删除 store_platform_user_role(按 userId + storeId,delete_flag=1)
         LambdaUpdateWrapper<StorePlatformUserRole> roleUpdateWrapper = new LambdaUpdateWrapper<>();
-        roleUpdateWrapper.eq(StorePlatformUserRole::getUserId, userId).eq(StorePlatformUserRole::getDeleteFlag, 0);
-        if (storeId != null) {
-            roleUpdateWrapper.eq(StorePlatformUserRole::getStoreId, storeId);
+        roleUpdateWrapper.eq(StorePlatformUserRole::getUserId, userId)
+                .eq(StorePlatformUserRole::getDeleteFlag, 0)
+                .set(StorePlatformUserRole::getDeleteFlag, 1);
+        int roleUpdateResult = storePlatformUserRoleMapper.update(null, roleUpdateWrapper);
+        if (roleUpdateResult <= 0) {
+            log.error("deleteStoreAccountInfo 逻辑删除 store_platform_user_role 失败: userId={}", userId);
+            return "删除失败";
         }
-        roleUpdateWrapper.set(StorePlatformUserRole::getDeleteFlag, 1);
-        storePlatformUserRoleMapper.update(null, roleUpdateWrapper);
 
-        // 主账号下子账号唯一:逻辑删除 store_platform_user_role 和 store_user;不唯一:只逻辑删除 store_platform_user_role
+        // 3.3 根据子账号 id 查询用户信息及联系方式,删除 Redis token(key: store_手机号),参照 removeRole
+        StoreUser subUser = storeUserMapper.selectById(userId);
+        if (subUser != null && subUser.getPhone() != null) {
+            String tokenKey = "store_" + subUser.getPhone();
+            try {
+                String existingToken = baseRedisService.getString(tokenKey);
+                if (existingToken != null) {
+                    baseRedisService.delete(tokenKey);
+                    log.info("deleteStoreAccountInfo 清除用户 token 成功: userId={}, phone={}, tokenKey={}", userId, subUser.getPhone(), tokenKey);
+                } else {
+                    log.warn("deleteStoreAccountInfo 用户 token 不存在或已过期: userId={}, phone={}", userId, subUser.getPhone());
+                }
+            } catch (Exception e) {
+                log.warn("deleteStoreAccountInfo 清除 token 异常: userId={}, tokenKey={}", userId, tokenKey, e);
+            }
+        }
+
+        // 3.4 若该主账号下仅 1 个子账号,则进一步判断是否为主账号,再决定是否同时逻辑删除 store_user(参照 removeRole)
         if (subAccountCount == 1) {
-            LambdaUpdateWrapper<StoreUser> userUpdateWrapper = new LambdaUpdateWrapper<>();
-            userUpdateWrapper.eq(StoreUser::getId, userId)
-                    .eq(StoreUser::getDeleteFlag, 0)
-                    .set(StoreUser::getDeleteFlag, 1);
-            storeUserMapper.update(null, userUpdateWrapper);
-            log.info("主账号下子账号唯一,已逻辑删除 store_platform_user_role 和 store_user: userId={}", userId);
-            String tokenKey = "store_" + storeUser.getPhone();
-            baseRedisService.delete(tokenKey);
+            if (subUser != null) {
+                if (subUser.getStoreId() != null && subUser.getStoreId() > 0) {
+                    log.info("deleteStoreAccountInfo 用户是主账号,不删除 store_user 记录: userId={}, storeId={}", userId, subUser.getStoreId());
+                } else {
+                    LambdaUpdateWrapper<StoreUser> userUpdateWrapper = new LambdaUpdateWrapper<>();
+                    userUpdateWrapper.eq(StoreUser::getId, userId)
+                            .eq(StoreUser::getDeleteFlag, 0)
+                            .set(StoreUser::getDeleteFlag, 1);
+                    int userUpdateResult = storeUserMapper.update(null, userUpdateWrapper);
+                    if (userUpdateResult > 0) {
+                        log.info("deleteStoreAccountInfo 用户只有一个子账号且不是主账号,已同时逻辑删除 store_user 记录: userId={}", userId);
+                    } else {
+                        log.warn("deleteStoreAccountInfo 逻辑删除 store_user 记录失败或记录不存在: userId={}", userId);
+                    }
+                }
+            } else {
+                log.warn("deleteStoreAccountInfo 未找到用户信息: userId={}", userId);
+            }
         } else {
-            log.info("主账号下子账号数不为1,仅逻辑删除 store_platform_user_role,不删 store_user: mainAccountId={}, subAccountCount={}", mainAccountId, subAccountCount);
+            log.info("deleteStoreAccountInfo 用户有多个子账号(count={}),仅删除 store_platform_user_role 记录,不删除 store_user 记录: userId={}", subAccountCount, userId);
         }
-        return "删除失败";
+        return null;
     }
 
     @Override

+ 45 - 12
alien-store/src/main/java/shop/alien/store/service/impl/TrackEventServiceImpl.java

@@ -443,22 +443,28 @@ public class TrackEventServiceImpl extends ServiceImpl<StoreTrackEventMapper, St
         Map<String, Object> result = new HashMap<>();
         
         try {
-            // 赠送好友相关数据(累计数据,从life_discount_coupon_user表统计)
-            String storeIdStr = String.valueOf(storeId);
-            List<LifeDiscountCouponUser> couponUsers = queryCouponUsersByStore(storeIdStr, endDate);
-            
-            // 赠送好友数量
-            long giveToFriendCount = couponUsers.size();
+            // 赠送好友数量:从 LifeDiscountCoupon 查店铺优惠券 id,再按优惠券 id 查 LifeDiscountCouponStoreFriend 记录数
+
+            List<LifeDiscountCouponStoreFriend> giveToFriendRecords = queryGiveToFriendRecordsByStore(storeId, endDate);
+            long giveToFriendCount = giveToFriendRecords.size();
             result.put("giveToFriendCount", giveToFriendCount);
-            
-            // 赠送好友金额合计(优惠券面值的总和)
-            BigDecimal giveToFriendAmount = couponUsers.stream()
-                    .map(cu -> {
-                        LifeDiscountCoupon coupon = lifeDiscountCouponMapper.selectById(cu.getCouponId());
-                        return coupon != null && coupon.getNominalValue() != null ? coupon.getNominalValue() : BigDecimal.ZERO;
+
+            // 赠送好友金额合计:用 LifeDiscountCouponStoreFriend 的券 id 和数量(singleQty),从 LifeDiscountCoupon 取面值,计算面值金额合计
+
+            BigDecimal giveToFriendAmount = giveToFriendRecords.stream()
+                    .map(record -> {
+                        LifeDiscountCoupon coupon = lifeDiscountCouponMapper.selectById(record.getCouponId());
+                        BigDecimal faceValue = coupon != null && coupon.getNominalValue() != null ? coupon.getNominalValue() : BigDecimal.ZERO;
+                        int qty = record.getSingleQty() != null && record.getSingleQty() > 0 ? record.getSingleQty() : 0;
+                        return faceValue.multiply(BigDecimal.valueOf(qty));
                     })
                     .reduce(BigDecimal.ZERO, BigDecimal::add);
+
             result.put("giveToFriendAmount", giveToFriendAmount);
+
+            // 赠送好友使用数/使用金额:从 life_discount_coupon_user 统计
+            String storeIdStr = String.valueOf(storeId);
+            List<LifeDiscountCouponUser> couponUsers = queryCouponUsersByStore(storeIdStr, endDate);
             
             // 赠送好友使用数量(status=1已使用的数量)
             long giveToFriendUseCount = couponUsers.stream()
@@ -538,6 +544,33 @@ public class TrackEventServiceImpl extends ServiceImpl<StoreTrackEventMapper, St
     }
     
     /**
+     * 根据店铺优惠券 id 查询 LifeDiscountCouponStoreFriend 赠送好友记录(累计数据,截至 endDate)
+     * 从 LifeDiscountCoupon 查店铺优惠券 id,再按优惠券 id 查 LifeDiscountCouponStoreFriend
+     */
+    private List<LifeDiscountCouponStoreFriend> queryGiveToFriendRecordsByStore(Integer storeId, Date endDate) {
+        try {
+            LambdaQueryWrapper<LifeDiscountCoupon> couponWrapper = new LambdaQueryWrapper<>();
+            couponWrapper.eq(LifeDiscountCoupon::getStoreId, String.valueOf(storeId))
+                    .eq(LifeDiscountCoupon::getType, 1)
+                    .eq(LifeDiscountCoupon::getDeleteFlag, 0);
+            List<LifeDiscountCoupon> coupons = lifeDiscountCouponMapper.selectList(couponWrapper);
+            if (coupons == null || coupons.isEmpty()) {
+                return Collections.emptyList();
+            }
+            List<Integer> couponIds = coupons.stream()
+                    .map(LifeDiscountCoupon::getId)
+                    .collect(Collectors.toList());
+            LambdaQueryWrapper<LifeDiscountCouponStoreFriend> wrapper = new LambdaQueryWrapper<>();
+            wrapper.in(LifeDiscountCouponStoreFriend::getCouponId, couponIds)
+                    .lt(LifeDiscountCouponStoreFriend::getCreatedTime, endDate);
+            return lifeDiscountCouponStoreFriendMapper.selectList(wrapper);
+        } catch (Exception e) {
+            log.error("查询店铺赠送好友记录失败: storeId={}", storeId, e);
+            return Collections.emptyList();
+        }
+    }
+
+    /**
      * 查询店铺关联的优惠券用户数据(累计数据,截至到endDate)
      */
     private List<LifeDiscountCouponUser> queryCouponUsersByStore(String storeId, Date endDate) {

+ 1 - 0
alien-store/src/main/java/shop/alien/store/util/CommonConstant.java

@@ -112,6 +112,7 @@ public class CommonConstant {
     public static final String RATING_LIKE = "11";
     public static final String COMMENT_LIKE = "12";
     public static final String DYNAMIC_LIKE = "13";
+    public static final String CLOCK_IN_LIKE = "14";
 
 
     /**

+ 2 - 1
alien-util/src/main/java/shop/alien/util/common/constant/CommentSourceTypeEnum.java

@@ -3,7 +3,8 @@ package shop.alien.util.common.constant;
 
 public enum CommentSourceTypeEnum {
     STORE_COMMENT(1, "店铺评价"),
-    DYNAMIC_COMMENT(2, "动态评论");
+    DYNAMIC_COMMENT(2, "动态评论"),
+    CLOCK_IN_COMMENT(3, "打卡评论");
     private final Integer type;
     private final String info;