Browse Source

Merge branch 'feature/0712_vip' into develop-new

yuanliang 1 năm trước cách đây
mục cha
commit
89dba1f7a3
18 tập tin đã thay đổi với 559 bổ sung68 xóa
  1. 27 6
      cooleshow-app/src/main/java/com/yonge/cooleshow/admin/controller/VipCardRecordController.java
  2. 1 0
      cooleshow-app/src/main/java/com/yonge/cooleshow/student/controller/MemberPriceSettingsController.java
  3. 1 0
      cooleshow-app/src/main/java/com/yonge/cooleshow/teacher/controller/MemberPriceSettingsController.java
  4. 7 13
      cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/dto/search/MemberPriceSettingsSearch.java
  5. 16 0
      cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/entity/StudentTime.java
  6. 33 0
      cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/enums/EUserVipType.java
  7. 1 0
      cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/enums/EVipType.java
  8. 1 0
      cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/enums/MessageTypeEnum.java
  9. 2 0
      cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/service/VipCardRecordService.java
  10. 3 1
      cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/service/impl/StudentServiceImpl.java
  11. 1 1
      cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/service/impl/TeacherServiceImpl.java
  12. 43 43
      cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/service/impl/UserTenantAlbumRecordServiceImpl.java
  13. 352 4
      cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/service/impl/VipCardRecordServiceImpl.java
  14. 4 0
      cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/vo/StudentVo.java
  15. 4 0
      cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/vo/TeacherVo.java
  16. 53 0
      cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/wrapper/VipCardRecordWrapper.java
  17. 8 0
      cooleshow-user/user-biz/src/main/resources/config/mybatis/MemberPriceSettingsMapper.xml
  18. 2 0
      cooleshow-user/user-biz/src/main/resources/config/mybatis/StudentTimeMapper.xml

+ 27 - 6
cooleshow-app/src/main/java/com/yonge/cooleshow/admin/controller/VipCardRecordController.java

@@ -1,9 +1,12 @@
 package com.yonge.cooleshow.admin.controller;
 
 import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.yonge.cooleshow.auth.api.client.SysUserFeignService;
+import com.yonge.cooleshow.auth.api.entity.SysUser;
 import com.yonge.cooleshow.biz.dal.dto.search.VipCardRecordSearch;
 import com.yonge.cooleshow.biz.dal.service.VipCardRecordService;
 import com.yonge.cooleshow.biz.dal.vo.VipCardRecordVo;
+import com.yonge.cooleshow.biz.dal.wrapper.VipCardRecordWrapper;
 import com.yonge.cooleshow.common.controller.BaseController;
 import com.yonge.cooleshow.common.entity.HttpResponseResult;
 import com.yonge.toolset.base.page.PageInfo;
@@ -11,6 +14,8 @@ import com.yonge.toolset.mybatis.support.PageUtil;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.PostMapping;
@@ -18,6 +23,8 @@ import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
+import javax.annotation.Resource;
+
 @RestController
 @RequestMapping("${app-config.url.admin:}/vipCardRecord")
 @Api(value = "购买会员卡记录表", tags = "购买会员卡记录表")
@@ -26,22 +33,36 @@ public class VipCardRecordController extends BaseController {
     @Autowired
     private VipCardRecordService vipCardRecordService;
 
-	/**
+    @Resource
+    private SysUserFeignService sysUserFeignService;
+
+    /**
      * 查询单条
      */
     @GetMapping("/detail/{orderDetilId}")
     @ApiOperation(value = "详情", notes = "传入订单详情id")
     public HttpResponseResult<VipCardRecordVo> detail(@PathVariable("orderDetilId") Long orderDetilId) {
-    	return succeed(vipCardRecordService.detail(orderDetilId));
-	}
-    
+        return succeed(vipCardRecordService.detail(orderDetilId));
+    }
+
     /**
      * 查询分页
      */
     @PostMapping("/page")
     @ApiOperation(value = "查询分页", notes = "传入vipCardRecordSearch")
     public HttpResponseResult<PageInfo<VipCardRecordVo>> page(@RequestBody VipCardRecordSearch query) {
-		IPage<VipCardRecordVo> pages = vipCardRecordService.selectPage(PageUtil.getPage(query), query);
+        IPage<VipCardRecordVo> pages = vipCardRecordService.selectPage(PageUtil.getPage(query), query);
         return succeed(PageUtil.pageInfo(pages));
-	}
+    }
+
+
+    @ApiOperation("添加/扣减会员")
+    @PostMapping("/add")
+    @PreAuthorize("@pcs.hasPermissions('vipCardRecord/add')")
+    public HttpResponseResult<Void> add(@RequestBody @Validated VipCardRecordWrapper.AddVipCardRecord addVipCardRecord) {
+        SysUser sysUser = sysUserFeignService.queryUserInfo();
+        addVipCardRecord.setCreateBy(sysUser.getId());
+        vipCardRecordService.add(addVipCardRecord);
+        return succeed();
+    }
 }

+ 1 - 0
cooleshow-app/src/main/java/com/yonge/cooleshow/student/controller/MemberPriceSettingsController.java

@@ -43,6 +43,7 @@ public class MemberPriceSettingsController extends BaseController {
 	@PostMapping("/list")
 	@ApiOperation(value = "查询列表")
 	public HttpResponseResult<MemberPriceVo> list(@RequestBody MemberPriceSettingsSearch query) {
+		query.setStatus(true);
 		MemberPriceVo memberPriceVo = memberPriceSettingsService.getVipShare(query);
 		return succeed(memberPriceVo);
 	}

+ 1 - 0
cooleshow-app/src/main/java/com/yonge/cooleshow/teacher/controller/MemberPriceSettingsController.java

@@ -43,6 +43,7 @@ public class MemberPriceSettingsController extends BaseController {
 	@PostMapping("/list")
 	@ApiOperation(value = "查询列表")
 	public HttpResponseResult<MemberPriceVo> list(@RequestBody MemberPriceSettingsSearch query) {
+		query.setStatus(true);
 		MemberPriceVo memberPriceVo = memberPriceSettingsService.getVipShare(query);
 		return succeed(memberPriceVo);
 	}

+ 7 - 13
cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/dto/search/MemberPriceSettingsSearch.java

@@ -1,13 +1,16 @@
 package com.yonge.cooleshow.biz.dal.dto.search;
 
+import com.yonge.cooleshow.biz.dal.enums.EVipType;
 import com.yonge.toolset.base.page.QueryInfo;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
 
 /**
  * @Author: liweifan
  * @Data: 2022-04-25 14:34:49
  */
+@Data
 @ApiModel(value = "MemberPriceSettingsSearch对象", description = "查询对象")
 public class MemberPriceSettingsSearch extends QueryInfo{
 	private static final long serialVersionUID = 1L;
@@ -18,19 +21,10 @@ public class MemberPriceSettingsSearch extends QueryInfo{
 	@ApiModelProperty("活动id")
 	private Long  activityId;
 
-	public Long getActivityId() {
-		return activityId;
-	}
+	@ApiModelProperty("true:启用,false:停用")
+	private Boolean status;
 
-	public void setActivityId(Long activityId) {
-		this.activityId = activityId;
-	}
+	@ApiModelProperty("VIP,SVIP")
+	private EVipType vipType;
 
-	public Long getUserId() {
-		return userId;
-	}
-
-	public void setUserId(Long userId) {
-		this.userId = userId;
-	}
 }

+ 16 - 0
cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/entity/StudentTime.java

@@ -25,6 +25,13 @@ public class StudentTime implements Serializable {
     @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone="GMT+8")
     private Date firstVipTime;
+
+
+    @ApiModelProperty("第一次购买svip时间 ")
+    @TableField(value = "first_svip_time_")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone="GMT+8")
+    private Date firstSvipTime;
     @ApiModelProperty("第一次购买陪练课时间 ")
     @TableField(value = "first_practice_time_")
     @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@@ -65,6 +72,15 @@ public class StudentTime implements Serializable {
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone="GMT+8")
     private Date firstActivityTime;
 
+
+    public Date getFirstSvipTime() {
+        return firstSvipTime;
+    }
+
+    public void setFirstSvipTime(Date firstSvipTime) {
+        this.firstSvipTime = firstSvipTime;
+    }
+
     public Date getFirstActivityTime() {
         return firstActivityTime;
     }

+ 33 - 0
cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/enums/EUserVipType.java

@@ -0,0 +1,33 @@
+package com.yonge.cooleshow.biz.dal.enums;
+
+import com.baomidou.mybatisplus.annotation.EnumValue;
+import com.yonge.toolset.base.enums.BaseEnum;
+
+/**
+ * 用户会员类型
+ */
+public enum EUserVipType implements BaseEnum<String, EUserVipType> {
+
+    NORMAL("普通用户"),
+    VIP("会员"),
+    SVIP("SVIP"),
+
+    ;
+    @EnumValue
+    private String code;
+    private String name;
+
+    EUserVipType(String name) {
+        this.code = this.name();
+        this.name = name;
+    }
+
+    @Override
+    public String getCode() {
+        return this.code;
+    }
+
+    public String getName() {
+        return name;
+    }
+}

+ 1 - 0
cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/enums/EVipType.java

@@ -23,6 +23,7 @@ public enum EVipType implements BaseEnum<String, EVipType> {
 
     // 不是vip
     NOT_VIP("不是vip"),
+    ALL_VIP("所有VIP"),
 
     ;
     @EnumValue

+ 1 - 0
cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/enums/MessageTypeEnum.java

@@ -124,6 +124,7 @@ public enum MessageTypeEnum implements BaseEnum<String, MessageTypeEnum> {
     TENANT_VIP_BUY("开通平台会员"),
     TENANT_VIP_EXPIRE("平台会员到期"),
     TENANT_PLATFORM_ADD_VIP("后台添加平台会员"),
+    TENANT_PLATFORM_DUDECT_VIP("后台添加扣减平台会员"),
     TENANT_ALBUM_BUY("购买训练教程"),
     TENANT_ALBUM_EXPIRE("训练教程到期"),
     TENANT_SEND_CODE("发放激活码"),

+ 2 - 0
cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/service/VipCardRecordService.java

@@ -104,4 +104,6 @@ public interface VipCardRecordService extends IService<VipCardRecord> {
      * @return
      */
     VipCardRecordWrapper.UserVip UserVipInfo(Long userId, ClientEnum clientEnum);
+
+    void add(VipCardRecordWrapper.AddVipCardRecord addVipCardRecord);
 }

+ 3 - 1
cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/service/impl/StudentServiceImpl.java

@@ -135,7 +135,9 @@ public class StudentServiceImpl extends ServiceImpl<StudentDao, Student> impleme
     private TenantGroupAlbumMapper tenantGroupAlbumMapper;
     @Override
     public StudentVo detail(Long userId) {
-        return baseMapper.detail(userId);
+        StudentVo detail = baseMapper.detail(userId);
+        detail.setUserVip(vipCardRecordService.UserVipInfo(userId, ClientEnum.STUDENT));
+        return detail;
     }
 
     @Override

+ 1 - 1
cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/service/impl/TeacherServiceImpl.java

@@ -1040,7 +1040,7 @@ public class TeacherServiceImpl extends ServiceImpl<TeacherDao, Teacher> impleme
 
             teacherInfo.setStudentNums(studentNumsMap.getOrDefault(teacherId, 0));
         }
-
+        teacherInfo.setUserVip(vipCardRecordService.UserVipInfo(teacherId, ClientEnum.TEACHER));
         return teacherInfo;
     }
 

+ 43 - 43
cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/service/impl/UserTenantAlbumRecordServiceImpl.java

@@ -169,49 +169,49 @@ public class UserTenantAlbumRecordServiceImpl extends ServiceImpl<UserTenantAlbu
                     vo.setTenantImg(tenantInfo.getLogo());
                 }
                 //查询机构专辑曲目表
-                List<TenantAlbumMusic> tenantAlbumMusics = tenantAlbumMusicService.lambdaQuery()
-                        .eq(TenantAlbumMusic::getTenantAlbumId, i.getId())
-                        .eq(TenantAlbumMusic::getDelFlag, false)
-                        .list();
-
-                Map<SubjectTypeEnum, List<TenantAlbumMusic>> groupByType =
-                        tenantAlbumMusics.stream().collect(Collectors.groupingBy(TenantAlbumMusic::getSubjectType));
-
-                List<Long> musicSheetIdlist = tenantAlbumMusics.stream().map(next -> next.getMusicSheetId()).distinct().collect(Collectors.toList());
-
-                StudentMusicSheetSearch search = new StudentMusicSheetSearch();
-                search.setMusicSheetIdlist(musicSheetIdlist);
-                search.setPage(1);
-                search.setRows(9999);
-
-                IPage<MusicSheetVo> records = musicSheetService.selectStudentPage(PageUtil.getPage(search), search, ClientEnum.TENANT_STUDENT);
-
-                Map<Long, MusicSheetVo> idMsMap = records.getRecords().stream()
-                        .collect(Collectors.toMap(MusicSheet::getId, Function.identity()));
-
-                List<TenantAlbumWrapper.MusicSheetData> musicSheetData = vo.getMusicSheetData();
-
-                groupByType.forEach((key, value) -> {
-                    value.sort(Comparator.comparing(TenantAlbumMusic::getSortNumber));
-                    TenantAlbumWrapper.MusicSheetData sheetData = new TenantAlbumWrapper.MusicSheetData();
-                    sheetData.setSubjectType(key);
-                    List<TenantAlbumWrapper.TenantAlbumSheet> tenantAlbumSheets = value.stream().map(next -> {
-
-                        TenantAlbumWrapper.TenantAlbumSheet tenantAlbumSheet = new TenantAlbumWrapper.TenantAlbumSheet();
-                        BeanUtils.copyProperties(next, tenantAlbumSheet);
-                        Long musicSheetId = tenantAlbumSheet.getMusicSheetId();
-                        MusicSheetVo musicSheet = idMsMap.getOrDefault(musicSheetId, new MusicSheetVo());
-                        tenantAlbumSheet.setMusicSheetName(musicSheet.getMusicSheetName());
-                        tenantAlbumSheet.setMusicTag(musicSheet.getMusicTag());
-                        tenantAlbumSheet.setComposer(musicSheet.getComposer());
-                        return tenantAlbumSheet;
-                    }).collect(Collectors.toList());
-
-
-                    sheetData.setTenantAlbumSheetList(tenantAlbumSheets);
-                    musicSheetData.add(sheetData);
-                    vo.setMusicSheetData(musicSheetData);
-                });
+//                List<TenantAlbumMusic> tenantAlbumMusics = tenantAlbumMusicService.lambdaQuery()
+//                        .eq(TenantAlbumMusic::getTenantAlbumId, i.getId())
+//                        .eq(TenantAlbumMusic::getDelFlag, false)
+//                        .list();
+//
+//                Map<SubjectTypeEnum, List<TenantAlbumMusic>> groupByType =
+//                        tenantAlbumMusics.stream().collect(Collectors.groupingBy(TenantAlbumMusic::getSubjectType));
+//
+//                List<Long> musicSheetIdlist = tenantAlbumMusics.stream().map(next -> next.getMusicSheetId()).distinct().collect(Collectors.toList());
+
+//                StudentMusicSheetSearch search = new StudentMusicSheetSearch();
+//                search.setMusicSheetIdlist(musicSheetIdlist);
+//                search.setPage(1);
+//                search.setRows(9999);
+//
+//                IPage<MusicSheetVo> records = musicSheetService.selectStudentPage(PageUtil.getPage(search), search, ClientEnum.TENANT_STUDENT);
+//
+//                Map<Long, MusicSheetVo> idMsMap = records.getRecords().stream()
+//                        .collect(Collectors.toMap(MusicSheet::getId, Function.identity()));
+//
+//                List<TenantAlbumWrapper.MusicSheetData> musicSheetData = vo.getMusicSheetData();
+//
+//                groupByType.forEach((key, value) -> {
+//                    value.sort(Comparator.comparing(TenantAlbumMusic::getSortNumber));
+//                    TenantAlbumWrapper.MusicSheetData sheetData = new TenantAlbumWrapper.MusicSheetData();
+//                    sheetData.setSubjectType(key);
+//                    List<TenantAlbumWrapper.TenantAlbumSheet> tenantAlbumSheets = value.stream().map(next -> {
+//
+//                        TenantAlbumWrapper.TenantAlbumSheet tenantAlbumSheet = new TenantAlbumWrapper.TenantAlbumSheet();
+//                        BeanUtils.copyProperties(next, tenantAlbumSheet);
+//                        Long musicSheetId = tenantAlbumSheet.getMusicSheetId();
+//                        MusicSheetVo musicSheet = idMsMap.getOrDefault(musicSheetId, new MusicSheetVo());
+//                        tenantAlbumSheet.setMusicSheetName(musicSheet.getMusicSheetName());
+//                        tenantAlbumSheet.setMusicTag(musicSheet.getMusicTag());
+//                        tenantAlbumSheet.setComposer(musicSheet.getComposer());
+//                        return tenantAlbumSheet;
+//                    }).collect(Collectors.toList());
+//
+//
+//                    sheetData.setTenantAlbumSheetList(tenantAlbumSheets);
+//                    musicSheetData.add(sheetData);
+//                    vo.setMusicSheetData(musicSheetData);
+//                });
                 list.add(vo);
             });
         }

+ 352 - 4
cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/service/impl/VipCardRecordServiceImpl.java

@@ -1,8 +1,11 @@
 package com.yonge.cooleshow.biz.dal.service.impl;
 
+import com.alibaba.fastjson.JSON;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.microsvc.toolkit.common.webportal.exception.BizException;
+import com.yonge.cooleshow.auth.api.entity.SysUser;
 import com.yonge.cooleshow.biz.dal.dao.MemberPriceSettingsDao;
 import com.yonge.cooleshow.biz.dal.dto.search.VipRecordSearch;
 import com.yonge.cooleshow.biz.dal.entity.ActivityReward;
@@ -24,7 +27,10 @@ import org.springframework.stereotype.Service;
 import com.yonge.cooleshow.biz.dal.entity.VipCardRecord;
 import com.yonge.cooleshow.biz.dal.dto.search.VipCardRecordSearch;
 import com.yonge.cooleshow.biz.dal.dao.VipCardRecordDao;
+import org.springframework.transaction.annotation.Transactional;
 
+import java.time.LocalDate;
+import java.time.ZoneId;
 import java.util.*;
 import java.util.concurrent.CompletableFuture;
 import java.util.stream.Collectors;
@@ -50,6 +56,10 @@ public class VipCardRecordServiceImpl extends ServiceImpl<VipCardRecordDao, VipC
     @Autowired
     private UserOrderDetailService orderDetailService;
 
+    @Autowired
+    private SysUserService sysUserService;
+
+
     @Override
     public VipCardRecordVo detail(Long orderDetilId) {
         return baseMapper.detail(orderDetilId, null);
@@ -257,12 +267,32 @@ public class VipCardRecordServiceImpl extends ServiceImpl<VipCardRecordDao, VipC
      * @return
      */
     @Override
-    public VipCardRecordWrapper.UserVip UserVipInfo (Long userId, ClientEnum clientEnum) {
+    public VipCardRecordWrapper.UserVip UserVipInfo(Long userId, ClientEnum clientEnum) {
         // 获取生效中的会员记录
         VipCardRecordWrapper.UserVip userVip = new VipCardRecordWrapper.UserVip();
         List<VipCardRecord> vipCardRecords = this.getEfficientVipRecord(userId,clientEnum);
         if (CollectionUtils.isEmpty(vipCardRecords)) {
             userVip.setVipType(EVipType.NOT_VIP);
+            // 判断有没有过期的会员类型
+            Integer vipCount = this.lambdaQuery()
+                .eq(VipCardRecord::getUserId, userId)
+                .eq(VipCardRecord::getEfficientFlag, true)
+                .eq(VipCardRecord::getVipType, EVipType.VIP)
+                .eq(VipCardRecord::getClientType, clientEnum.name())
+                .count();
+            Integer svipCount = this.lambdaQuery()
+                .eq(VipCardRecord::getUserId, userId)
+                .eq(VipCardRecord::getEfficientFlag, true)
+                .eq(VipCardRecord::getVipType, EVipType.SVIP)
+                .eq(VipCardRecord::getClientType, clientEnum.name())
+                .count();
+            if (vipCount>0&&svipCount>0) {
+                userVip.setExpireVipType(EVipType.ALL_VIP);
+            } else if (vipCount>0) {
+                userVip.setExpireVipType(EVipType.VIP);
+            } else if (svipCount>0) {
+                userVip.setExpireVipType(EVipType.SVIP);
+            }
         } else {
             // 存在没有结束时间的SVIP记录 永久SVIP
             List<VipCardRecord> svipList = vipCardRecords.stream().filter(o -> o.getVipType() == EVipType.SVIP).collect(Collectors.toList());
@@ -275,6 +305,16 @@ public class VipCardRecordServiceImpl extends ServiceImpl<VipCardRecordDao, VipC
                 } else {
                     Optional<VipCardRecord> max = svipList.stream().max(Comparator.comparing(VipCardRecord::getEndTime));
                     max.ifPresent(vipCardRecord -> userVip.setSvipEndDate(vipCardRecord.getEndTime()));
+                }
+            } else {
+                Integer svipCount = this.lambdaQuery()
+                    .eq(VipCardRecord::getUserId, userId)
+                    .eq(VipCardRecord::getEfficientFlag, true)
+                    .eq(VipCardRecord::getVipType, EVipType.SVIP)
+                    .eq(VipCardRecord::getClientType, clientEnum.name())
+                    .count();
+                if (svipCount>0) {
+                    userVip.setExpireVipType(EVipType.SVIP);
 
                 }
             }
@@ -286,22 +326,312 @@ public class VipCardRecordServiceImpl extends ServiceImpl<VipCardRecordDao, VipC
                 }
                 Optional<VipCardRecord> max = vipList.stream().max(Comparator.comparing(VipCardRecord::getEndTime));
                 max.ifPresent(vipCardRecord -> userVip.setVipEndDate(vipCardRecord.getEndTime()));
+            } else {
+                Integer vipCount = this.lambdaQuery()
+                    .eq(VipCardRecord::getUserId, userId)
+                    .eq(VipCardRecord::getEfficientFlag, true)
+                    .eq(VipCardRecord::getVipType, EVipType.VIP)
+                    .eq(VipCardRecord::getClientType, clientEnum.name())
+                    .count();
+                if (vipCount>0) {
+                    userVip.setExpireVipType(EVipType.VIP);
+                }
             }
         }
 
         // 设置剩余天数
         if (userVip.getVipEndDate() != null) {
-
-            int num = DateUtil.daysBetween(new Date(), userVip.getVipEndDate());
+            int num = DateUtil.daysBetween(new Date(), userVip.getVipEndDate()) +1;
             userVip.setVipEndDays(Math.max(num, 0));
         }
         if (userVip.getSvipEndDate() != null) {
-            int num = DateUtil.daysBetween(new Date(), userVip.getSvipEndDate());
+            int num = DateUtil.daysBetween(new Date(), userVip.getSvipEndDate()) +1;
             userVip.setSvipEndDays(Math.max(num, 0));
         }
         return userVip;
     }
 
+    @Transactional(rollbackFor = Exception.class)
+    @Override
+    public void add(VipCardRecordWrapper.AddVipCardRecord addVipCardRecord) {
+        ClientEnum clientType = addVipCardRecord.getClientType();
+        if (!ClientEnum.STUDENT.equals(clientType) && !ClientEnum.TEACHER.equals(clientType)) {
+            throw new BizException("暂不支持非老师学生会员的增加或扣减");
+        }
+        if (EVipRecordStatus.DEDUCTION.equals(addVipCardRecord.getStatus())) {
+            deductVip(addVipCardRecord);
+        } else if (EVipRecordStatus.ADD.equals(addVipCardRecord.getStatus())) {
+            addVip(addVipCardRecord);
+        }
+
+        if (Boolean.TRUE.equals(addVipCardRecord.getSendMsg())) {
+            sendAddVipMsg(addVipCardRecord);
+        }
+    }
+
+    // 会员添加
+    private void addVip(VipCardRecordWrapper.AddVipCardRecord addVipCardRecord) {
+        List<VipCardRecord> vipCardRecordList = this.lambdaQuery()
+                .eq(VipCardRecord::getClientType, addVipCardRecord.getClientType())
+                .eq(VipCardRecord::getUserId, addVipCardRecord.getUserId())
+                .ge(VipCardRecord::getEndTime, new Date())
+                .eq(VipCardRecord::getEfficientFlag, true)
+                .list();
+
+        EVipType addVipType = addVipCardRecord.getVipType();
+        List<VipCardRecord> perpetualRecords = vipCardRecordList.stream()
+                .filter(n -> n.getVipType().equals(addVipType) && PeriodEnum.PERPETUAL.equals(n.getType()))
+                .collect(Collectors.toList());
+        if (!perpetualRecords.isEmpty()) {
+            throw new BizException("已经是永久会员");
+        }
+
+        Date startTime = new Date();
+        // 没有会员信息
+        if (vipCardRecordList.isEmpty()) {
+            PeriodEnum period = addVipCardRecord.getType();
+            LocalDate startLocalDate = startTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
+            Date endDate = plusDate(startLocalDate, period, Long.valueOf(addVipCardRecord.getTimes()));
+
+            VipCardRecord addRecord = JSON.parseObject(JSON.toJSONString(addVipCardRecord), VipCardRecord.class);
+            addRecord.setSourceType(SourceTypeEnum.BACKEND_GIVE);
+            addRecord.setStatus(EVipRecordStatus.ADD);
+            addRecord.setDisplayFlag(true);
+            addRecord.setEfficientFlag(true);
+            addRecord.setStartTime(startTime);
+            addRecord.setEndTime(endDate);
+            save(addRecord);
+            return;
+        }
+
+        // 存在会员信息
+        int index = 0;
+        for (int i = 0; i < vipCardRecordList.size(); i++) {
+            VipCardRecord vipCardRecord = vipCardRecordList.get(i);
+            if (vipCardRecord.getVipType().equals(addVipType)) {
+                index = i;
+                startTime = vipCardRecord.getEndTime();
+            }
+        }
+
+        LocalDate startLocalDate = startTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
+        Date endDate = plusDate(startLocalDate, addVipCardRecord.getType(), Long.valueOf(addVipCardRecord.getTimes()));
+        // 平移时间
+        long plusMills = endDate == null ? 0L : (endDate.getTime() - startTime.getTime());
+        for (int i = 0; i < vipCardRecordList.size(); i++) {
+            if (index == i) {
+                VipCardRecord addRecord = JSON.parseObject(JSON.toJSONString(addVipCardRecord), VipCardRecord.class);
+                addRecord.setSourceType(SourceTypeEnum.BACKEND_GIVE);
+                addRecord.setStatus(EVipRecordStatus.ADD);
+                addRecord.setStartTime(startTime);
+                addRecord.setEndTime(endDate);
+                addRecord.setDisplayFlag(true);
+                addRecord.setEfficientFlag(true);
+                save(addRecord);
+            }
+
+            if (i > index) {
+                VipCardRecord vipCardRecord = vipCardRecordList.get(i);
+                Long refId = null;
+                if (plusMills > 0L) {
+                    VipCardRecord addRecord = JSON.parseObject(JSON.toJSONString(vipCardRecord), VipCardRecord.class);
+                    addRecord.setStatus(EVipRecordStatus.UPDATE);
+                    addRecord.setStartTime(new Date(vipCardRecord.getStartTime().getTime() - plusMills));
+                    addRecord.setEndTime(new Date(vipCardRecord.getEndTime().getTime() - plusMills));
+                    addRecord.setDisplayFlag(false);
+                    addRecord.setEfficientFlag(true);
+                    save(addRecord);
+                    refId = addRecord.getId();
+                }
+
+                VipCardRecord updateRecord = JSON.parseObject(JSON.toJSONString(vipCardRecord), VipCardRecord.class);
+                updateRecord.setEfficientFlag(false);
+                updateRecord.setRefId(refId);
+                updateById(updateRecord);
+            }
+        }
+    }
+
+
+    // 会员扣减
+    private void deductVip(VipCardRecordWrapper.AddVipCardRecord addVipCardRecord) {
+
+        // 所有的会员重新生成
+        List<VipCardRecord> vipCardRecordList = this.lambdaQuery()
+                .eq(VipCardRecord::getClientType, addVipCardRecord.getClientType())
+                .eq(VipCardRecord::getUserId, addVipCardRecord.getUserId())
+                .ge(VipCardRecord::getEndTime, new Date())
+//                .eq(VipCardRecord::getDisplayFlag, false)
+                .eq(VipCardRecord::getEfficientFlag, true)
+                .list();
+
+        // 当前类型的VIP
+        List<VipCardRecord> collect = vipCardRecordList.stream().filter(n -> n.getVipType().equals(addVipCardRecord.getVipType())).collect(Collectors.toList());
+        if (collect.isEmpty()) {
+            throw new BizException("剩余扣减数量不足");
+        }
+//        List<VipCardRecord> perpetualRecords = vipCardRecordList.stream()
+//                .filter(n -> n.getVipType().equals(addVipCardRecord.getVipType()) && PeriodEnum.PERPETUAL.equals(n.getType()))
+//                .collect(Collectors.toList());
+//        if (!perpetualRecords.isEmpty()) {
+//            throw new BizException("永久会员不支持扣减");
+//        }
+
+
+        LocalDate maxEndTime = collect.stream().map(VipCardRecord::getEndTime).max(Comparator.naturalOrder()).get().toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
+        PeriodEnum period = addVipCardRecord.getType();
+        // 扣减后的开始时间
+        LocalDate deductedStartTime;
+        switch (period) {
+            case DAY:
+                deductedStartTime = maxEndTime.minusDays(addVipCardRecord.getTimes());
+                break;
+            case MONTH:
+                deductedStartTime = maxEndTime.minusMonths(addVipCardRecord.getTimes());
+                break;
+            case QUARTERLY:
+                deductedStartTime = maxEndTime.minusMonths(addVipCardRecord.getTimes() * 3);
+                break;
+            case YEAR_HALF:
+                deductedStartTime = maxEndTime.minusMonths(addVipCardRecord.getTimes() * 6);
+                break;
+            case YEAR:
+                deductedStartTime = maxEndTime.minusYears(addVipCardRecord.getTimes());
+                break;
+            case PERPETUAL:
+                // 永久扣减
+                deductedPerpetual(addVipCardRecord, vipCardRecordList);
+                return;
+            default:
+                throw new BizException("不支持的扣减类型");
+        }
+        Date deductedStartDate = Date.from(deductedStartTime.atStartOfDay(ZoneId.systemDefault()).toInstant());
+
+        Date minStartTime = collect.stream().map(VipCardRecord::getStartTime).min(Comparator.naturalOrder()).get();
+        // 如果扣减的数量超过1天,则提示扣减数量不足
+        if (deductedStartDate.before(minStartTime)) {
+            double day = (minStartTime.getTime() - deductedStartDate.getTime()) * 1.0D / (24 * 60 * 60);
+            if (day > 1.0D) {
+                throw new BizException("剩余扣减数量不足");
+            }
+        }
+
+        // 重新计算会员时间,每一条记录置换成一条新的记录,时间区间重新计算
+        List<VipCardRecord> updateRecords = new ArrayList<>();
+        Long deductMills = null;
+
+        for (VipCardRecord vipCardRecord : vipCardRecordList) {
+            Date startTime = vipCardRecord.getStartTime();
+            Date endTime = vipCardRecord.getEndTime();
+            if (endTime.before(deductedStartDate)) {
+                continue;
+            }
+
+            Long addId = null;
+            // 扣减到当前时间区间
+            if (deductedStartDate.after(startTime) && deductedStartDate.before(endTime)) {
+                VipCardRecord addRecord = JSON.parseObject(JSON.toJSONString(vipCardRecord), VipCardRecord.class);
+                addRecord.setId(null);
+                addRecord.setDisplayFlag(false);
+                addRecord.setEfficientFlag(true);
+                addRecord.setEndTime(deductedStartDate);
+                save(addRecord);
+                addId = addRecord.getId();
+
+                deductMills = endTime.getTime() - deductedStartDate.getTime();
+            } else {
+                // 有扣减,整体时间前移
+                if (deductMills != null) {
+                    VipCardRecord newRecord = JSON.parseObject(JSON.toJSONString(vipCardRecord), VipCardRecord.class);
+                    newRecord.setId(null);
+                    newRecord.setEfficientFlag(true);
+                    newRecord.setStartTime(new Date(newRecord.getStartTime().getTime() - deductMills));
+                    newRecord.setEndTime(new Date(newRecord.getEndTime().getTime() - deductMills));
+                    newRecord.setStatus(EVipRecordStatus.UPDATE);
+                    save(newRecord);
+                    addId = newRecord.getId();
+                }
+            }
+
+            VipCardRecord updateRecord = JSON.parseObject(JSON.toJSONString(vipCardRecord), VipCardRecord.class);
+            updateRecord.setEfficientFlag(false);
+            updateRecord.setRefId(addId);
+            updateRecords.add(updateRecord);
+        }
+
+        this.updateBatchById(updateRecords, 100);
+
+        VipCardRecord vipCardRecord = JSON.parseObject(JSON.toJSONString(addVipCardRecord), VipCardRecord.class);
+        vipCardRecord.setDisplayFlag(true);
+        vipCardRecord.setEfficientFlag(false);
+        vipCardRecord.setStatus(EVipRecordStatus.DEDUCTION);
+        this.save(vipCardRecord);
+    }
+
+    // 扣减永久会员
+    private void deductedPerpetual(VipCardRecordWrapper.AddVipCardRecord addVipCardRecord, List<VipCardRecord> vipCardRecordList) {
+
+        EVipType vipType = addVipCardRecord.getVipType();
+        long deductMills = 0L;
+        Date now = new Date();
+        for (VipCardRecord vipCardRecord : vipCardRecordList) {
+            Date startTime = vipCardRecord.getStartTime();
+            Date endTime = vipCardRecord.getEndTime();
+
+            if (vipCardRecord.getVipType().equals(vipType)) {
+                deductMills += endTime.getTime() - Math.max(startTime.getTime(), now.getTime());
+                VipCardRecord updateRecord = JSON.parseObject(JSON.toJSONString(vipCardRecord), VipCardRecord.class);
+                updateRecord.setEfficientFlag(false);
+                updateById(updateRecord);
+            } else if (deductMills > 0L) {
+                    VipCardRecord newRecord = JSON.parseObject(JSON.toJSONString(vipCardRecord), VipCardRecord.class);
+                    newRecord.setId(null);
+                    newRecord.setDisplayFlag(false);
+                    newRecord.setEfficientFlag(true);
+                    newRecord.setStartTime(new Date(newRecord.getStartTime().getTime() - deductMills));
+                    newRecord.setEndTime(new Date(newRecord.getEndTime().getTime() - deductMills));
+                    save(newRecord);
+                    Long refId = newRecord.getId();
+
+                    VipCardRecord updateRecord = JSON.parseObject(JSON.toJSONString(vipCardRecord), VipCardRecord.class);
+                    updateRecord.setEfficientFlag(false);
+                    updateRecord.setRefId(refId);
+                    updateById(updateRecord);
+                }
+            }
+        VipCardRecord vipCardRecord = JSON.parseObject(JSON.toJSONString(addVipCardRecord), VipCardRecord.class);
+        vipCardRecord.setDisplayFlag(true);
+        vipCardRecord.setEfficientFlag(false);
+        vipCardRecord.setStatus(EVipRecordStatus.DEDUCTION);
+        this.save(vipCardRecord);
+    }
+
+    private Date plusDate(LocalDate start, PeriodEnum period, long times) {
+        LocalDate end = null;
+        switch (period) {
+            case DAY:
+                end = start.minusDays(times);
+                break;
+            case MONTH:
+                end = start.minusMonths(times);
+                break;
+            case QUARTERLY:
+                end = start.minusMonths(times * 3);
+                break;
+            case YEAR_HALF:
+                end = start.minusMonths(times * 6);
+                break;
+            case YEAR:
+                end = start.minusYears(times);
+                break;
+            case PERPETUAL:
+                return null;
+            default:
+                throw new BizException("不支持的扣减类型");
+        }
+        return Date.from(end.atStartOfDay(ZoneId.systemDefault()).toInstant());
+    }
+
     // 发送会员到期3天消息推送
     private void temporary3DaysSend(Long userId, String phone, ClientEnum clientType) {
         Map<Long, String> receivers = new HashMap<>();
@@ -357,4 +687,22 @@ public class VipCardRecordServiceImpl extends ServiceImpl<VipCardRecordDao, VipC
             }
         }
     }
+
+    // 添加、扣除会员发送推送
+    private void sendAddVipMsg(VipCardRecordWrapper.AddVipCardRecord addVipCardRecord) {
+        SysUser sysUser = sysUserService.getByUserId(addVipCardRecord.getUserId());
+        if (sysUser == null) {
+            return;
+        }
+        MessageTypeEnum messageTypeEnum = EVipRecordStatus.ADD.equals(addVipCardRecord.getStatus()) ? MessageTypeEnum.TENANT_PLATFORM_ADD_VIP : MessageTypeEnum.TENANT_PLATFORM_DUDECT_VIP;
+        Map<Long, String> receivers = new HashMap<>();
+        receivers.put(addVipCardRecord.getUserId(), sysUser.getPhone());
+
+        try {
+            sysMessageService.batchSendMessage(MessageSenderPluginContext.MessageSender.JIGUANG, messageTypeEnum,
+                    receivers, null, 0, null, addVipCardRecord.getClientType().getCode());
+        } catch (Exception e) {
+            log.error("会员赠送消息发送失败 : {}", e.getMessage());
+        }
+    }
 }

+ 4 - 0
cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/vo/StudentVo.java

@@ -1,6 +1,7 @@
 package com.yonge.cooleshow.biz.dal.vo;
 
 import com.alibaba.excel.annotation.ExcelProperty;
+import com.yonge.cooleshow.biz.dal.wrapper.VipCardRecordWrapper;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 
@@ -82,6 +83,9 @@ public class StudentVo extends Student {
 
     private String imUserId;
 
+    @ApiModelProperty(value = "会员信息")
+    private VipCardRecordWrapper.UserVip userVip;
+
     public YesOrNoEnum getDelFlag() {
         return delFlag;
     }

+ 4 - 0
cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/vo/TeacherVo.java

@@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonFormat;
 import com.yonge.cooleshow.biz.dal.entity.Teacher;
 import com.yonge.cooleshow.biz.dal.entity.TeacherStyleVideo;
 import com.yonge.cooleshow.biz.dal.enums.GenderEnum;
+import com.yonge.cooleshow.biz.dal.wrapper.VipCardRecordWrapper;
 import com.yonge.cooleshow.common.enums.ESettlementFrom;
 import com.yonge.cooleshow.common.enums.UserLockFlag;
 import com.yonge.cooleshow.common.enums.UserStatusEnum;
@@ -102,6 +103,9 @@ public class TeacherVo extends Teacher {
     @ApiModelProperty("机构小组名称")
     private String tenantGroupName;
 
+    @ApiModelProperty(value = "会员信息")
+    private VipCardRecordWrapper.UserVip userVip;
+
 
     public YesOrNoEnum getDelFlag() {
         return delFlag;

+ 53 - 0
cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/wrapper/VipCardRecordWrapper.java

@@ -1,9 +1,16 @@
 package com.yonge.cooleshow.biz.dal.wrapper;
 
+import com.yonge.cooleshow.biz.dal.enums.ClientEnum;
+import com.yonge.cooleshow.biz.dal.enums.EVipRecordStatus;
 import com.yonge.cooleshow.biz.dal.enums.EVipType;
+import com.yonge.cooleshow.biz.dal.enums.PeriodEnum;
+import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 
+import javax.validation.constraints.Max;
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotNull;
 import java.util.Date;
 
 public class VipCardRecordWrapper {
@@ -15,6 +22,11 @@ public class VipCardRecordWrapper {
         @ApiModelProperty(value = "vip类型 VIP:会员 SVIP:SVIP,PERMANENT_SVIP:永久SVIP,NOT_VIP:不是vip")
         private EVipType vipType;
 
+
+        @ApiModelProperty(value = "vip类型 VIP:会员 SVIP:SVIP,ALL_VIP:全vip")
+        private EVipType expireVipType;
+
+
         @ApiModelProperty(value = "vip结束时间")
         private Date vipEndDate;
 
@@ -29,4 +41,45 @@ public class VipCardRecordWrapper {
         @ApiModelProperty(value = "svip剩余天数")
         private Integer svipEndDays;
     }
+
+    @ApiModel("添加/扣减会员")
+    @Data
+    public static class AddVipCardRecord {
+
+        @ApiModelProperty("用户ID")
+        @NotNull
+        private Long userId;
+
+        @ApiModelProperty("用户类型")
+        @NotNull
+        private ClientEnum clientType;
+
+        @ApiModelProperty("状态 ADD:新增,DEDUCTION:扣减,UPDATE:变更")
+        @NotNull
+        private EVipRecordStatus status;
+
+        @ApiModelProperty("会员类型,VIP,SVIP")
+        @NotNull
+        private EVipType vipType;
+
+        @ApiModelProperty("时间类型 DAY:天 MONTH:月 ,QUARTERLY : 季度 YEAR_HALF : 半年 YEAR:年,永久:PERMANENT")
+        @NotNull
+        private PeriodEnum type;
+
+        @ApiModelProperty("数量")
+        @Min(value = 1, message = "最小数量为1")
+        @Max(value = 999, message = "最大数量为999")
+        private Integer times;
+
+        @ApiModelProperty("扣减原因")
+        @NotNull
+        private String reason;
+
+        @ApiModelProperty("是否发送推送,是:true,否:false")
+        @NotNull
+        private Boolean sendMsg;
+
+        @ApiModelProperty(value = "创建人",hidden = true)
+        private Long createBy;
+    }
 }

+ 8 - 0
cooleshow-user/user-biz/src/main/resources/config/mybatis/MemberPriceSettingsMapper.xml

@@ -45,6 +45,14 @@
 		ifnull(u.real_name_,u.username_) as modifierName
 		FROM member_price_settings t
 		LEFT JOIN sys_user u on t.update_by_ = u.id_
+		<where>
+			<if test="param.status != null">
+				and t.status = #{param.status}
+			</if>
+			<if test="param.vipType != null">
+				and t.vip_type_ = #{param.vipType}
+			</if>
+		</where>
 	</sql>
 
 	<select id="selectPage" resultType="com.yonge.cooleshow.biz.dal.vo.MemberPriceSettingsVo">

+ 2 - 0
cooleshow-user/user-biz/src/main/resources/config/mybatis/StudentTimeMapper.xml

@@ -4,6 +4,7 @@
     <resultMap id="BaseResultMap" type="com.yonge.cooleshow.biz.dal.entity.StudentTime">
         <result column="user_id_" property="userId" />
         <result column="first_vip_time_" property="firstVipTime" />
+        <result column="first_svip_time_" property="firstSvipTime" />
         <result column="first_practice_time_" property="firstPracticeTime" />
         <result column="first_video_time_" property="firstVideoTime" />
         <result column="first_live_time_" property="firstLiveTime" />
@@ -17,6 +18,7 @@
     <sql id="baseColumns">
          t.user_id_ as userId
         , t.first_vip_time_ as firstVipTime
+        , t.first_svip_time_ as firstSvipTime
         , t.first_practice_time_ as firstPracticeTime
         , t.first_video_time_ as firstVideoTime
         , t.first_live_time_ as firstLiveTime