From 8d326b853cd3f2d2ca5f9a70b343389da63d9af6 Mon Sep 17 00:00:00 2001 From: KKSU <15274802129@163.com> Date: Mon, 23 Dec 2024 18:00:00 +0800 Subject: [PATCH] feat(mall): 新增节点规则功能 --- src/main/resources/templates/febs/views/modules/runVip/vipEdit.html | 4 src/main/java/cc/mrbird/febs/common/enumerates/RunVipMoneyFlowTypeEnum.java | 5 src/main/java/cc/mrbird/febs/mall/controller/AdminRunVipController.java | 27 + src/main/java/cc/mrbird/febs/mall/entity/RunNodeSet.java | 17 + src/main/resources/templates/febs/views/modules/runVip/nodeList.html | 146 +++++++++ src/main/java/cc/mrbird/febs/mall/service/IMemberProfitService.java | 2 src/main/java/cc/mrbird/febs/mall/controller/ViewRunVipController.java | 22 + src/main/java/cc/mrbird/febs/mall/service/IAdminRunVipService.java | 13 src/main/java/cc/mrbird/febs/mall/entity/MallMember.java | 7 src/main/java/cc/mrbird/febs/mall/mapper/RunNodeSetMapper.java | 7 src/main/java/cc/mrbird/febs/mall/service/AsyncService.java | 3 src/main/java/cc/mrbird/febs/mall/service/impl/AdminRunVipServiceImpl.java | 36 + src/main/resources/templates/febs/views/modules/runVip/vipAdd.html | 4 src/main/java/cc/mrbird/febs/common/enumerates/RunVipDataDictionaryEnum.java | 7 src/main/java/cc/mrbird/febs/mall/service/impl/AgentServiceImpl.java | 171 +++++++---- src/main/java/cc/mrbird/febs/mall/service/impl/MemberProfitServiceImpl.java | 95 ++++++ src/main/java/cc/mrbird/febs/mall/quartz/ProfitJob.java | 43 +- src/main/java/cc/mrbird/febs/mall/service/impl/AsyncServiceImpl.java | 27 + src/main/resources/templates/febs/views/modules/runVip/nodeEdit.html | 111 +++++++ src/main/resources/templates/febs/views/modules/runVip/nodeAdd.html | 105 +++++++ 20 files changed, 751 insertions(+), 101 deletions(-) diff --git a/src/main/java/cc/mrbird/febs/common/enumerates/RunVipDataDictionaryEnum.java b/src/main/java/cc/mrbird/febs/common/enumerates/RunVipDataDictionaryEnum.java index 6d44429..a853314 100644 --- a/src/main/java/cc/mrbird/febs/common/enumerates/RunVipDataDictionaryEnum.java +++ b/src/main/java/cc/mrbird/febs/common/enumerates/RunVipDataDictionaryEnum.java @@ -5,6 +5,13 @@ @Getter public enum RunVipDataDictionaryEnum { + //节点升级-直推人数 + NODE_DIRECT_CNT("CHARGE_TYPE", "NODE_DIRECT_CNT"), + //节点升级-团队人数 + NODE_TEAM_CNT("CHARGE_TYPE", "NODE_TEAM_CNT"), + //节点升级-业绩要求 + NODE_ACHIEVE_CNT("CHARGE_TYPE", "NODE_ACHIEVE_CNT"), + //释放碳积分的开始结束时间 RUN_START_TIME("CHARGE_TYPE", "RUN_START_TIME"), RUN_END_TIME("CHARGE_TYPE", "RUN_END_TIME"), diff --git a/src/main/java/cc/mrbird/febs/common/enumerates/RunVipMoneyFlowTypeEnum.java b/src/main/java/cc/mrbird/febs/common/enumerates/RunVipMoneyFlowTypeEnum.java index 845351c..6bf02bf 100644 --- a/src/main/java/cc/mrbird/febs/common/enumerates/RunVipMoneyFlowTypeEnum.java +++ b/src/main/java/cc/mrbird/febs/common/enumerates/RunVipMoneyFlowTypeEnum.java @@ -6,6 +6,11 @@ public enum RunVipMoneyFlowTypeEnum { /** + * 节点助力 + */ + NODE_BALANCE(10,"节点助力碳币{}","节点助力"), + + /** * 获得碳积分 */ GET_SCORE(9,"本次获得碳积分{}","获得碳积分"), diff --git a/src/main/java/cc/mrbird/febs/mall/controller/AdminRunVipController.java b/src/main/java/cc/mrbird/febs/mall/controller/AdminRunVipController.java index 52aefd3..2f7fa89 100644 --- a/src/main/java/cc/mrbird/febs/mall/controller/AdminRunVipController.java +++ b/src/main/java/cc/mrbird/febs/mall/controller/AdminRunVipController.java @@ -4,10 +4,7 @@ import cc.mrbird.febs.common.controller.BaseController; import cc.mrbird.febs.common.entity.FebsResponse; import cc.mrbird.febs.common.entity.QueryRequest; -import cc.mrbird.febs.mall.entity.MallCharge; -import cc.mrbird.febs.mall.entity.MallMember; -import cc.mrbird.febs.mall.entity.MallMemberWithdraw; -import cc.mrbird.febs.mall.entity.RunVip; +import cc.mrbird.febs.mall.entity.*; import cc.mrbird.febs.mall.mapper.MallMemberMapper; import cc.mrbird.febs.mall.service.IAdminRunVipService; import cc.mrbird.febs.mall.service.ISystemService; @@ -82,6 +79,12 @@ return new FebsResponse().success().data(data); } + @GetMapping("/nodeList") + public FebsResponse nodeList(RunNodeSet runNodeSet, QueryRequest request) { + Map<String, Object> data = getDataTable(iAdminRunVipService.nodeListInPage(runNodeSet,request)); + return new FebsResponse().success().data(data); + } + @PostMapping(value = "/addOrEdit") public FebsResponse addOrEdit(@RequestBody RunVip config) { if (config.getId() == null) { @@ -92,6 +95,22 @@ return new FebsResponse().success().message("操作成功"); } + @PostMapping(value = "/nodeAddOrEdit") + public FebsResponse nodeAddOrEdit(@RequestBody RunNodeSet config) { + if (config.getId() == null) { + iAdminRunVipService.addRunNodeSet(config); + } else { + iAdminRunVipService.editRunNodeSet(config); + } + return new FebsResponse().success().message("操作成功"); + } + + @GetMapping(value = "/nodeDel/{id}") + public FebsResponse nodeDel(@PathVariable("id") Long id) { + iAdminRunVipService.deleteNodeById(id); + return new FebsResponse().success().message("操作成功"); + } + @GetMapping(value = "/del/{id}") public FebsResponse del(@PathVariable("id") Long id) { iAdminRunVipService.getBaseMapper().deleteById(id); diff --git a/src/main/java/cc/mrbird/febs/mall/controller/ViewRunVipController.java b/src/main/java/cc/mrbird/febs/mall/controller/ViewRunVipController.java index 4dc0c67..c2e1134 100644 --- a/src/main/java/cc/mrbird/febs/mall/controller/ViewRunVipController.java +++ b/src/main/java/cc/mrbird/febs/mall/controller/ViewRunVipController.java @@ -2,7 +2,9 @@ import cc.mrbird.febs.common.entity.FebsConstant; import cc.mrbird.febs.common.utils.FebsUtil; +import cc.mrbird.febs.mall.entity.RunNodeSet; import cc.mrbird.febs.mall.entity.RunVip; +import cc.mrbird.febs.mall.mapper.RunNodeSetMapper; import cc.mrbird.febs.mall.service.IAdminRunVipService; import lombok.RequiredArgsConstructor; import org.apache.shiro.authz.annotation.RequiresPermissions; @@ -18,6 +20,7 @@ public class ViewRunVipController{ private final IAdminRunVipService iAdminRunVipService; + private final RunNodeSetMapper runNodeSetMapper; @GetMapping(value = "/sellVipList") @RequiresPermissions("sellVipList:view") public String sellVipList() { @@ -33,12 +36,10 @@ public String runVipList() { return FebsUtil.view("modules/runVip/runVipList"); } - @GetMapping(value = "/vipAdd") public String levelAdd() { return FebsUtil.view("modules/runVip/vipAdd"); } - @GetMapping("/vipEdit/{id}") public String vipEdit(@PathVariable("id") Long id, Model model) { RunVip runVip = iAdminRunVipService.getBaseMapper().selectById(id); @@ -52,4 +53,21 @@ return FebsUtil.view("modules/runVip/systemSetting"); } + + @GetMapping(value = "/nodeList") + @RequiresPermissions("nodeList:view") + public String nodeList() { + return FebsUtil.view("modules/runVip/nodeList"); + } + @GetMapping(value = "/nodeAdd") + public String nodeAdd() { + return FebsUtil.view("modules/runVip/nodeAdd"); + } + @GetMapping("/nodeEdit/{id}") + public String nodeEdit(@PathVariable("id") Long id, Model model) { + RunNodeSet runNodeSet = runNodeSetMapper.selectById(id); + model.addAttribute("runNodeSet", runNodeSet); + return FebsUtil.view("modules/runVip/nodeEdit"); + } + } diff --git a/src/main/java/cc/mrbird/febs/mall/entity/MallMember.java b/src/main/java/cc/mrbird/febs/mall/entity/MallMember.java index 145534b..f564d01 100644 --- a/src/main/java/cc/mrbird/febs/mall/entity/MallMember.java +++ b/src/main/java/cc/mrbird/febs/mall/entity/MallMember.java @@ -126,9 +126,13 @@ private String levelName; /** - * 董事 + * 是否是节点 1是 0否 */ private Integer director; + //升级为节点的时间 + @DateTimeFormat(pattern = "yyyy-MM-dd") + @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8") + private Date directorTime; /** * 店长 @@ -180,6 +184,7 @@ private Date lastLoginTime; + //升级为会员的时间 @DateTimeFormat(pattern = "yyyy-MM-dd") @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8") private Date vipLevelTime; diff --git a/src/main/java/cc/mrbird/febs/mall/entity/RunNodeSet.java b/src/main/java/cc/mrbird/febs/mall/entity/RunNodeSet.java new file mode 100644 index 0000000..10ebaa6 --- /dev/null +++ b/src/main/java/cc/mrbird/febs/mall/entity/RunNodeSet.java @@ -0,0 +1,17 @@ +package cc.mrbird.febs.mall.entity; + +import cc.mrbird.febs.common.entity.BaseEntity; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.math.BigDecimal; + +@Data +@TableName("run_node_set") +public class RunNodeSet extends BaseEntity { + + private Integer orderNumber; + private BigDecimal minAchieve; + private BigDecimal maxAchieve; + private String nodePercent;//奖励碳币比例 +} diff --git a/src/main/java/cc/mrbird/febs/mall/mapper/RunNodeSetMapper.java b/src/main/java/cc/mrbird/febs/mall/mapper/RunNodeSetMapper.java new file mode 100644 index 0000000..ec31b93 --- /dev/null +++ b/src/main/java/cc/mrbird/febs/mall/mapper/RunNodeSetMapper.java @@ -0,0 +1,7 @@ +package cc.mrbird.febs.mall.mapper; + +import cc.mrbird.febs.mall.entity.RunNodeSet; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +public interface RunNodeSetMapper extends BaseMapper<RunNodeSet> { +} diff --git a/src/main/java/cc/mrbird/febs/mall/quartz/ProfitJob.java b/src/main/java/cc/mrbird/febs/mall/quartz/ProfitJob.java index 704ddc6..1a795d5 100644 --- a/src/main/java/cc/mrbird/febs/mall/quartz/ProfitJob.java +++ b/src/main/java/cc/mrbird/febs/mall/quartz/ProfitJob.java @@ -15,23 +15,23 @@ @Autowired private IMemberProfitService memberProfitService; - /** - * 每12小时更新一次会员等级 - * 套餐过期后,更新用户为游客等级 - */ - @Scheduled(cron = "0 0 0/12 * * ?") - public void updateMemberLevel() { - memberProfitService.updateMemberLevel(); - } - - /** - * 每天凌晨 - * 清空用户的碳积分 - */ - @Scheduled(cron = "0 0 0 * * ?") - public void updateMemberScore() { - memberProfitService.updateMemberScore(); - } +// /** +// * 每12小时更新一次会员等级 +// * 套餐过期后,更新用户为游客等级 +// */ +// @Scheduled(cron = "0 0 0/12 * * ?") +// public void updateMemberLevel() { +// memberProfitService.updateMemberLevel(); +// } +// +// /** +// * 每天凌晨 +// * 清空用户的碳积分 +// */ +// @Scheduled(cron = "0 0 0 * * ?") +// public void updateMemberScore() { +// memberProfitService.updateMemberScore(); +// } /** * 每1小时执行一次 @@ -42,4 +42,13 @@ public void updateRunScore() { memberProfitService.updateRunScore(); } + + /** + * 每个月一号 + * 节点奖励分发 + */ + @Scheduled(cron = "0 0 1 * *") + public void updateNodeScore() { + memberProfitService.updateNodeScore(); + } } diff --git a/src/main/java/cc/mrbird/febs/mall/service/AsyncService.java b/src/main/java/cc/mrbird/febs/mall/service/AsyncService.java index b217db9..dbedd8a 100644 --- a/src/main/java/cc/mrbird/febs/mall/service/AsyncService.java +++ b/src/main/java/cc/mrbird/febs/mall/service/AsyncService.java @@ -11,4 +11,7 @@ @Async(FebsConstant.ASYNC_POOL) void releaseScore(BigDecimal amount,Long memberId); + + @Async(FebsConstant.ASYNC_POOL) + void releaseNodeCoin(BigDecimal amount,BigDecimal percent,BigDecimal balanceToCoin,Long memberId); } diff --git a/src/main/java/cc/mrbird/febs/mall/service/IAdminRunVipService.java b/src/main/java/cc/mrbird/febs/mall/service/IAdminRunVipService.java index 7d71dbe..8e09cca 100644 --- a/src/main/java/cc/mrbird/febs/mall/service/IAdminRunVipService.java +++ b/src/main/java/cc/mrbird/febs/mall/service/IAdminRunVipService.java @@ -2,10 +2,7 @@ import cc.mrbird.febs.common.entity.FebsResponse; import cc.mrbird.febs.common.entity.QueryRequest; -import cc.mrbird.febs.mall.entity.MallCharge; -import cc.mrbird.febs.mall.entity.MallMemberWithdraw; -import cc.mrbird.febs.mall.entity.MallMoneyFlow; -import cc.mrbird.febs.mall.entity.RunVip; +import cc.mrbird.febs.mall.entity.*; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.service.IService; @@ -14,6 +11,8 @@ public interface IAdminRunVipService extends IService<RunVip> { IPage<RunVip> runVipListInPage(RunVip runVip,QueryRequest request); + + IPage<RunNodeSet> nodeListInPage(RunNodeSet runNodeSet, QueryRequest request); void addVip(RunVip config); @@ -28,4 +27,10 @@ IPage<MallMemberWithdraw> sellList(MallMemberWithdraw mallMemberWithdraw, QueryRequest request); List<MallMoneyFlow> allMoneyType(); + + void addRunNodeSet(RunNodeSet config); + + void editRunNodeSet(RunNodeSet config); + + void deleteNodeById(Long id); } diff --git a/src/main/java/cc/mrbird/febs/mall/service/IMemberProfitService.java b/src/main/java/cc/mrbird/febs/mall/service/IMemberProfitService.java index 3c81cef..02600f6 100644 --- a/src/main/java/cc/mrbird/febs/mall/service/IMemberProfitService.java +++ b/src/main/java/cc/mrbird/febs/mall/service/IMemberProfitService.java @@ -47,4 +47,6 @@ void updateRunScore(); void updateMemberScore(); + + void updateNodeScore(); } diff --git a/src/main/java/cc/mrbird/febs/mall/service/impl/AdminRunVipServiceImpl.java b/src/main/java/cc/mrbird/febs/mall/service/impl/AdminRunVipServiceImpl.java index 7aa03f5..8cbd603 100644 --- a/src/main/java/cc/mrbird/febs/mall/service/impl/AdminRunVipServiceImpl.java +++ b/src/main/java/cc/mrbird/febs/mall/service/impl/AdminRunVipServiceImpl.java @@ -5,14 +5,8 @@ import cc.mrbird.febs.common.enumerates.RunVipMoneyFlowTypeEnum; import cc.mrbird.febs.common.enumerates.YesOrNoEnum; import cc.mrbird.febs.common.exception.FebsException; -import cc.mrbird.febs.mall.entity.MallCharge; -import cc.mrbird.febs.mall.entity.MallMemberWithdraw; -import cc.mrbird.febs.mall.entity.MallMoneyFlow; -import cc.mrbird.febs.mall.entity.RunVip; -import cc.mrbird.febs.mall.mapper.MallChargeMapper; -import cc.mrbird.febs.mall.mapper.MallMemberWithdrawMapper; -import cc.mrbird.febs.mall.mapper.MallMoneyFlowMapper; -import cc.mrbird.febs.mall.mapper.RunVipMapper; +import cc.mrbird.febs.mall.entity.*; +import cc.mrbird.febs.mall.mapper.*; import cc.mrbird.febs.mall.service.IAdminRunVipService; import cc.mrbird.febs.mall.service.IApiMallMemberWalletService; import cc.mrbird.febs.rabbit.producter.AgentProducer; @@ -40,10 +34,21 @@ private final MallMoneyFlowMapper mallMoneyFlowMapper; private final AgentProducer agentProducer; private final IApiMallMemberWalletService walletService; + private final RunNodeSetMapper runNodeSetMapper; @Override public IPage<RunVip> runVipListInPage(RunVip runVip,QueryRequest request) { Page<RunVip> page = new Page<>(request.getPageNum(), request.getPageSize()); return this.baseMapper.selectRunVipListInPage(page); + } + + @Override + public IPage<RunNodeSet> nodeListInPage(RunNodeSet runNodeSet, QueryRequest request) { + Page<RunNodeSet> page = new Page<>(request.getPageNum(), request.getPageSize()); + return runNodeSetMapper.selectPage( + page, + new LambdaQueryWrapper<RunNodeSet>() + .orderByAsc(RunNodeSet::getOrderNumber) + ); } @Override @@ -137,4 +142,19 @@ return mallMoneyFlows; } + + @Override + public void addRunNodeSet(RunNodeSet config) { + runNodeSetMapper.insert(config); + } + + @Override + public void editRunNodeSet(RunNodeSet config) { + runNodeSetMapper.updateById(config); + } + + @Override + public void deleteNodeById(Long id) { + runNodeSetMapper.deleteById(id); + } } diff --git a/src/main/java/cc/mrbird/febs/mall/service/impl/AgentServiceImpl.java b/src/main/java/cc/mrbird/febs/mall/service/impl/AgentServiceImpl.java index ee71133..9091b6b 100644 --- a/src/main/java/cc/mrbird/febs/mall/service/impl/AgentServiceImpl.java +++ b/src/main/java/cc/mrbird/febs/mall/service/impl/AgentServiceImpl.java @@ -1,9 +1,13 @@ package cc.mrbird.febs.mall.service.impl; -import cc.mrbird.febs.common.enumerates.*; -import cc.mrbird.febs.common.utils.MallUtils; +import cc.mrbird.febs.common.enumerates.AgentLevelEnum; +import cc.mrbird.febs.common.enumerates.RunVipDataDictionaryEnum; +import cc.mrbird.febs.common.enumerates.YesOrNoEnum; import cc.mrbird.febs.mall.dto.ApiMemberChargeFailDto; -import cc.mrbird.febs.mall.entity.*; +import cc.mrbird.febs.mall.entity.AgentInfo; +import cc.mrbird.febs.mall.entity.DataDictionaryCustom; +import cc.mrbird.febs.mall.entity.MallCharge; +import cc.mrbird.febs.mall.entity.MallMember; import cc.mrbird.febs.mall.mapper.DataDictionaryCustomMapper; import cc.mrbird.febs.mall.mapper.MallChargeMapper; import cc.mrbird.febs.mall.mapper.MallMemberMapper; @@ -12,7 +16,6 @@ import cc.mrbird.febs.mall.service.IApiMallMemberService; import cc.mrbird.febs.mall.service.IApiMallMemberWalletService; import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; @@ -24,8 +27,9 @@ import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; -import java.math.RoundingMode; import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; /** * @author wzy @@ -192,73 +196,118 @@ mallChargeMapper.updateById(mallCharge); } + /** + * 购买会员看你的上级和上上级是否满足升级节点的条件 + * 直推 10人 + * 团队 50人 + * 团队总业绩 10000 + * 满足以上三个条件 + * 更新升级节点的时间 + */ + private void NodeUp(Long memberId,Integer directCnt,Integer teamCnt,BigDecimal achieveCnt){ + try { + // 获取会员信息 + MallMember mallMember = mallMemberMapper.selectById(memberId); + if (mallMember == null) { + throw new IllegalArgumentException("会员不存在"); + } + + // 已经是节点则跳过 + if (YesOrNoEnum.YES.getValue() == mallMember.getDirector()) { + return; + } + + // 获取直推成员和团队成员 + List<MallMember> allMembers = mallMemberMapper.selectList( + new LambdaQueryWrapper<MallMember>() + .eq(MallMember::getReferrerId, mallMember.getInviteId()) + .or() + .in(MallMember::getReferrerId, mallMemberMapper.selectList( + new LambdaQueryWrapper<MallMember>() + .eq(MallMember::getReferrerId, mallMember.getInviteId()) + ).stream() + .map(MallMember::getInviteId) + .collect(Collectors.toSet())) + ); + + if (allMembers == null || allMembers.isEmpty()) { + return; + } + + // 检查直推人数 + long directCount = allMembers.stream() + .filter(member -> mallMember.getInviteId().equals(member.getReferrerId())) + .count(); + if (directCount < directCnt) { + return; + } + + // 检查团队人数 + if (allMembers.size() < teamCnt) { + return; + } + + // 获取团队业绩(不包含本人业绩) + Set<Long> memberIds = allMembers.stream() + .map(MallMember::getId) + .collect(Collectors.toSet()); + + List<MallCharge> mallCharges = mallChargeMapper.selectList( + new LambdaQueryWrapper<MallCharge>() + .in(MallCharge::getMemberId, memberIds) + .eq(MallCharge::getState, YesOrNoEnum.YES.getValue()) + ); + + BigDecimal totalCharge = mallCharges.stream() + .map(MallCharge::getAmount) + .reduce(BigDecimal.ZERO, BigDecimal::add); + + if (totalCharge.compareTo(achieveCnt) < 0) { + return; + } + + mallMember.setDirector(YesOrNoEnum.YES.getValue()); + mallMember.setDirectorTime(DateUtil.date()); + mallMemberMapper.updateById(mallMember); + } catch (Exception e) { + // 记录日志并处理异常 + log.error("会员升级节点失败: {}", memberId, e); + throw new RuntimeException("会员升级节点失败", e); + } + } @Override public void buyVipSuccessMsg(Long chargeId) { MallCharge mallCharge = mallChargeMapper.selectById(chargeId); - //更新会员的等级和过期时间 + //更新会员的等级和会员升级的时间 Long memberId = mallCharge.getMemberId(); String vipCode = mallCharge.getVipCode(); - DateTime dateTime = DateUtil.offsetMonth(DateUtil.date(), 1); - mallMemberMapper.updateVipLevelTimeAndLevel(memberId,dateTime,vipCode); + mallMemberMapper.updateVipLevelTimeAndLevel(memberId,DateUtil.date(),vipCode); + DataDictionaryCustom directCntDic = dataDictionaryCustomMapper.selectDicDataByTypeAndCode( + RunVipDataDictionaryEnum.NODE_DIRECT_CNT.getType(), + RunVipDataDictionaryEnum.NODE_DIRECT_CNT.getCode()); + Integer directCnt = Integer.parseInt(directCntDic.getValue()); + DataDictionaryCustom teamCntDic = dataDictionaryCustomMapper.selectDicDataByTypeAndCode( + RunVipDataDictionaryEnum.NODE_TEAM_CNT.getType(), + RunVipDataDictionaryEnum.NODE_TEAM_CNT.getCode()); + Integer teamCnt = Integer.parseInt(teamCntDic.getValue()); + DataDictionaryCustom achieveCntDic = dataDictionaryCustomMapper.selectDicDataByTypeAndCode( + RunVipDataDictionaryEnum.NODE_ACHIEVE_CNT.getType(), + RunVipDataDictionaryEnum.NODE_ACHIEVE_CNT.getCode()); + BigDecimal achieveCnt = new BigDecimal(achieveCntDic.getValue()); - //购买成功后,是否返利上级 MallMember mallMember = mallMemberMapper.selectById(memberId); - if(null == mallMember.getReferrerId()){ + if(StrUtil.isEmpty(mallMember.getReferrerId())){ return; } + //上级 + MallMember parentMember = mallMemberMapper.selectInfoByInviteId(mallMember.getReferrerId()); + NodeUp(parentMember.getId(),directCnt,teamCnt,achieveCnt); - RunVip runVip = runVipMapper.selectOne(new LambdaQueryWrapper<RunVip>().eq(RunVip::getVipCode, vipCode)); - BigDecimal rebatePercent = runVip.getRebatePercent(); - BigDecimal amount = mallCharge.getAmount(); - BigDecimal rebateAmount = amount.multiply(rebatePercent).setScale(2, RoundingMode.DOWN); - BigDecimal balanceToCoin = - new BigDecimal( - dataDictionaryCustomMapper.selectDicDataByTypeAndCode( - RunVipDataDictionaryEnum.RUN_VIP_BALANCE_TO_COIN.getType(), - RunVipDataDictionaryEnum.RUN_VIP_BALANCE_TO_COIN.getCode() - ).getValue() - ); - BigDecimal rebateCoin = rebateAmount.divide(balanceToCoin, 2, RoundingMode.DOWN); - if(BigDecimal.ZERO.compareTo(rebateCoin) >= 0){ + if(StrUtil.isEmpty(parentMember.getReferrerId())){ return; } - MallMember parent = mallMemberMapper.selectInfoByInviteId(mallMember.getReferrerId()); - if(null == parent){ - return; - } - String orderNo = MallUtils.getOrderNum("FL"); - if(null != parent.getReferrerId()){ - BigDecimal rebateCoinGrandpa = rebateCoin.multiply(rebatePercent).setScale(2, RoundingMode.DOWN); - MallMember grandpa = mallMemberMapper.selectInfoByInviteId(parent.getReferrerId()); - if(null != grandpa){ - if(BigDecimal.ZERO.compareTo(rebateCoinGrandpa) < 0){ - //返利给上上级 - walletService.addBalance(rebateCoinGrandpa,grandpa.getId()); - mallMemberService.runVipMoneyFlowAdd( - grandpa.getId(), - mallMember.getId(), - orderNo, - FlowTypeEnum.BALANCE.getValue(), - RunVipMoneyFlowTypeEnum.BUY_VIP_REBATE.getValue(), - rebateCoinGrandpa, - StrUtil.format(RunVipMoneyFlowTypeEnum.BUY_VIP_REBATE.getDescription(),mallMember.getPhone(),runVip.getVipName(),rebateCoinGrandpa), - YesOrNoEnum.YES.getValue() - ); - rebateCoin = rebateCoin.subtract(rebateCoinGrandpa).setScale(2, RoundingMode.DOWN); - } - } - } - //返利给上级 - walletService.addBalance(rebateCoin,parent.getId()); - mallMemberService.runVipMoneyFlowAdd( - parent.getId(), - mallMember.getId(), - orderNo, - FlowTypeEnum.BALANCE.getValue(), - RunVipMoneyFlowTypeEnum.BUY_VIP_REBATE.getValue(), - rebateCoin, - StrUtil.format(RunVipMoneyFlowTypeEnum.BUY_VIP_REBATE.getDescription(),mallMember.getPhone(),runVip.getVipName(),rebateCoin), - YesOrNoEnum.YES.getValue() - ); + //上上级 + MallMember graMember = mallMemberMapper.selectInfoByInviteId(parentMember.getReferrerId()); + NodeUp(graMember.getId(),directCnt,teamCnt,achieveCnt); } } diff --git a/src/main/java/cc/mrbird/febs/mall/service/impl/AsyncServiceImpl.java b/src/main/java/cc/mrbird/febs/mall/service/impl/AsyncServiceImpl.java index bef028b..d661692 100644 --- a/src/main/java/cc/mrbird/febs/mall/service/impl/AsyncServiceImpl.java +++ b/src/main/java/cc/mrbird/febs/mall/service/impl/AsyncServiceImpl.java @@ -16,6 +16,7 @@ import org.springframework.stereotype.Service; import java.math.BigDecimal; +import java.math.RoundingMode; @Slf4j @Service @@ -40,4 +41,30 @@ ); } + + @Override + public void releaseNodeCoin(BigDecimal amount, BigDecimal percent, BigDecimal balanceToCoin,Long memberId) { + //实际节点返利的金额 + BigDecimal multiply = amount.multiply(percent); + if(BigDecimal.ZERO.compareTo(multiply) >=0){ + return; + } + BigDecimal divide = multiply.divide(balanceToCoin, 2, RoundingMode.DOWN); + if(BigDecimal.ZERO.compareTo(divide) >=0){ + return; + } + walletService.addBalance(divide,memberId); + String orderNo = MallUtils.getOrderNum("JD"); + mallMoneyFlowService.runVipMoneyFlowAdd( + memberId, + memberId, + orderNo, + FlowTypeEnum.BALANCE.getValue(), + RunVipMoneyFlowTypeEnum.NODE_BALANCE.getValue(), + divide, + StrUtil.format(RunVipMoneyFlowTypeEnum.NODE_BALANCE.getDescription(),divide), + YesOrNoEnum.YES.getValue() + ); + + } } diff --git a/src/main/java/cc/mrbird/febs/mall/service/impl/MemberProfitServiceImpl.java b/src/main/java/cc/mrbird/febs/mall/service/impl/MemberProfitServiceImpl.java index ad065f7..c7e69f2 100644 --- a/src/main/java/cc/mrbird/febs/mall/service/impl/MemberProfitServiceImpl.java +++ b/src/main/java/cc/mrbird/febs/mall/service/impl/MemberProfitServiceImpl.java @@ -51,6 +51,8 @@ private final MallMemberCouponMapper mallMemberCouponMapper; private final RunVipMapper runVipMapper; private final MallMemberWalletMapper mallMemberWalletMapper; + private final RunNodeSetMapper runNodeSetMapper; + private final MallChargeMapper mallChargeMapper; private final AsyncService asyncService; @@ -688,4 +690,97 @@ } } } + + @Override + public void updateNodeScore() { + /** + * 获取全部的节点 + */ + List<MallMember> mallMembers = mallMemberMapper.selectList( + new LambdaQueryWrapper<MallMember>() + .eq(MallMember::getDirector, YesOrNoEnum.YES.getValue()) + ); + if(CollUtil.isEmpty(mallMembers)){ + return; + } + //获取节点设置 + List<RunNodeSet> runNodeSets = runNodeSetMapper.selectList(null); + BigDecimal balanceToCoin = new BigDecimal( + dataDictionaryCustomMapper.selectDicDataByTypeAndCode( + RunVipDataDictionaryEnum.RUN_VIP_BALANCE_TO_COIN.getType(), + RunVipDataDictionaryEnum.RUN_VIP_BALANCE_TO_COIN.getCode()).getValue() + ).setScale(2, BigDecimal.ROUND_DOWN); + for (MallMember item : mallMembers) { + //获取总业绩 + BigDecimal achieve = getDirectAchieve(item.getInviteId()); + BigDecimal nodePercent = getNodePercent(runNodeSets, achieve); + asyncService.releaseNodeCoin(achieve,nodePercent,balanceToCoin,item.getId()); + } + } + + private BigDecimal getDirectAchieve(String inviteId) { + + try { + // 获取直推成员和团队成员的ID集合 + Set<Long> memberIds = getTeamMemberIds(inviteId); + + if (CollUtil.isEmpty(memberIds)) { + return BigDecimal.ZERO; + } + + // 获取团队业绩(不包含本人业绩) + List<MallCharge> mallCharges = mallChargeMapper.selectList( + new LambdaQueryWrapper<MallCharge>() + .in(MallCharge::getMemberId, memberIds) + .eq(MallCharge::getState, YesOrNoEnum.YES.getValue()) + ); + + if (CollUtil.isEmpty(mallCharges)) { + return BigDecimal.ZERO; + } + + return mallCharges.stream() + .map(MallCharge::getAmount) + .reduce(BigDecimal.ZERO, BigDecimal::add); + } catch (Exception e) { + // 异常处理 + log.error("Error occurred while calculating direct achieve for member: {}", inviteId, e); + return BigDecimal.ZERO; + } + } + + private Set<Long> getTeamMemberIds(String inviteId) { + Set<Long> memberIds = new HashSet<>(); + // 获取直推成员 + List<MallMember> directMembers = mallMemberMapper.selectList( + new LambdaQueryWrapper<MallMember>() + .eq(MallMember::getReferrerId, inviteId) + ); + memberIds.addAll(directMembers.stream().map(MallMember::getId).collect(Collectors.toSet())); + + // 获取团队成员 + List<MallMember> teamMembers = mallMemberMapper.selectList( + new LambdaQueryWrapper<MallMember>() + .in(MallMember::getReferrerId, directMembers.stream().map(MallMember::getInviteId).collect(Collectors.toSet())) + ); + memberIds.addAll(teamMembers.stream().map(MallMember::getId).collect(Collectors.toSet())); + + return memberIds; + } + + + private BigDecimal getNodePercent(List<RunNodeSet> runNodeSets, BigDecimal directAchieve) { + BigDecimal percent = BigDecimal.ZERO; + //获取directAchieve在runNodeSets中哪一个位置 + for(RunNodeSet item : runNodeSets){ + BigDecimal minAchieve = item.getMinAchieve(); + BigDecimal maxAchieve = item.getMaxAchieve(); + BigDecimal nodePercent = new BigDecimal(item.getNodePercent()); + if(directAchieve.compareTo(minAchieve) >= 0 && directAchieve.compareTo(maxAchieve) < 0){ + percent = nodePercent; + break; + } + } + return percent; + } } diff --git a/src/main/resources/templates/febs/views/modules/runVip/nodeAdd.html b/src/main/resources/templates/febs/views/modules/runVip/nodeAdd.html new file mode 100644 index 0000000..40153a2 --- /dev/null +++ b/src/main/resources/templates/febs/views/modules/runVip/nodeAdd.html @@ -0,0 +1,105 @@ +<style> + #node-add { + padding: 20px 25px 25px 0; + } + + #node-add .layui-treeSelect .ztree li a, .ztree li span { + margin: 0 0 2px 3px !important; + } + #node-add #data-permission-tree-block { + border: 1px solid #eee; + border-radius: 2px; + padding: 3px 0; + } + #node-add .layui-treeSelect .ztree li span.button.switch { + top: 1px; + left: 3px; + } + #node-add img{ + max-width:100px + } + +</style> +<div class="layui-fluid" id="febs-node-add"> + <form class="layui-form" action="" lay-filter="node-add-form"> + <div class="layui-form-item"> + <div class="layui-col-lg6"> + <label class="layui-form-label febs-form-item-require">序号:</label> + <div class="layui-input-block"> + <input type="number" name="orderNumber" lay-verify="required" autocomplete="off" class="layui-input" > + </div> + </div> + + <div class="layui-col-lg6"> + <label class="layui-form-label febs-form-item-require">助力比例:</label> + <div class="layui-input-block"> + <input type="text" name="nodePercent" lay-verify="required" autocomplete="off" class="layui-input" > + </div> + </div> + </div> + <div class="layui-form-item"> + <div class="layui-col-lg6"> + <label class="layui-form-label febs-form-item-require">起始业绩:</label> + <div class="layui-input-block"> + <input type="number" name="minAchieve" lay-verify="required" autocomplete="off" class="layui-input" > + </div> + </div> + + <div class="layui-col-lg6"> + <label class="layui-form-label febs-form-item-require">结束业绩:</label> + <div class="layui-input-block"> + <input type="text" name="maxAchieve" lay-verify="required" autocomplete="off" class="layui-input" > + </div> + </div> + </div> + + + <div class="layui-form-item febs-hide"> + <button class="layui-btn" lay-submit="" lay-filter="node-add-form-submit" id="submit"></button> + </div> + </form> +</div> + +<script data-th-inline="javascript"> + layui.use(['febs', 'form', 'formSelects', 'validate', 'treeSelect', 'eleTree','layedit', 'laydate', 'upload', 'xmSelect'], function () { + var $ = layui.$, + febs = layui.febs, + layer = layui.layer, + upload = layui.upload, + formSelects = layui.formSelects, + form = layui.form, + laydate = layui.laydate, + layedit = layui.layedit, + $view = $('#node-add'), + validate = layui.validate; + + form.render(); + + formSelects.render(); + form.on('submit(node-add-form-submit)', function (data) { + $.ajax({ + 'url':ctx + 'admin/runVip/nodeAddOrEdit', + 'type':'post', + 'dataType':'json', + 'headers' : {'Content-Type' : 'application/json;charset=utf-8'}, //接口json格式 + 'traditional': true,//ajax传递数组必须添加属性 + 'data':JSON.stringify(data.field), + 'success':function (data) { + if(data.code==200){ + layer.closeAll(); + febs.alert.success(data.message); + $('#febs-run-node').find('#reset').click(); + }else{ + febs.alert.warn(data.message); + } + }, + 'error':function () { + febs.alert.warn('服务器繁忙'); + } + }) + return false; + return false; + }); + + }); +</script> \ No newline at end of file diff --git a/src/main/resources/templates/febs/views/modules/runVip/nodeEdit.html b/src/main/resources/templates/febs/views/modules/runVip/nodeEdit.html new file mode 100644 index 0000000..72fd712 --- /dev/null +++ b/src/main/resources/templates/febs/views/modules/runVip/nodeEdit.html @@ -0,0 +1,111 @@ +<style> + #node-edit { + padding: 20px 25px 25px 0; + } + + #node-edit .layui-treeSelect .ztree li a, .ztree li span { + margin: 0 0 2px 3px !important; + } + #node-edit #data-permission-tree-block { + border: 1px solid #eee; + border-radius: 2px; + padding: 3px 0; + } + #node-edit .layui-treeSelect .ztree li span.button.switch { + top: 1px; + left: 3px; + } + #node-edit img{ + max-width:100px + } + +</style> +<div class="layui-fluid" id="febs-node-edit"> + <form class="layui-form" action="" lay-filter="node-edit-form"> + <div class="layui-form-item"> + <input type="text" name="id" lay-verify="required" autocomplete="off" class="layui-input febs-hide" > + <div class="layui-col-lg6"> + <label class="layui-form-label febs-form-item-require">序号:</label> + <div class="layui-input-block"> + <input type="number" name="orderNumber" lay-verify="required" autocomplete="off" class="layui-input" > + </div> + </div> + + <div class="layui-col-lg6"> + <label class="layui-form-label febs-form-item-require">助力比例:</label> + <div class="layui-input-block"> + <input type="text" name="nodePercent" lay-verify="required" autocomplete="off" class="layui-input" > + </div> + </div> + </div> + <div class="layui-form-item"> + <div class="layui-col-lg6"> + <label class="layui-form-label febs-form-item-require">起始业绩:</label> + <div class="layui-input-block"> + <input type="number" name="minAchieve" lay-verify="required" autocomplete="off" class="layui-input" > + </div> + </div> + + <div class="layui-col-lg6"> + <label class="layui-form-label febs-form-item-require">结束业绩:</label> + <div class="layui-input-block"> + <input type="text" name="maxAchieve" lay-verify="required" autocomplete="off" class="layui-input" > + </div> + </div> + </div> + + <div class="layui-form-item febs-hide"> + <button class="layui-btn" lay-submit="" lay-filter="node-edit-form-submit" id="submit"></button> + </div> + </form> +</div> + +<script data-th-inline="javascript"> + layui.use(['febs', 'form', 'formSelects', 'validate', 'treeSelect', 'eleTree','layedit', 'laydate', 'upload', 'xmSelect'], function () { + var $ = layui.$, + febs = layui.febs, + layer = layui.layer, + form = layui.form, + $view = $('#node-edit'), + runNodeSet = [[${runNodeSet}]] + validate = layui.validate; + + form.render(); + initNodeValue(); + + function initNodeValue() { + form.val("node-edit-form", { + "id": runNodeSet.id, + "orderNumber": runNodeSet.orderNumber, + "minAchieve" : runNodeSet.minAchieve, + "maxAchieve" : runNodeSet.maxAchieve, + "nodePercent" : runNodeSet.nodePercent + }); + } + + form.on('submit(node-edit-form-submit)', function (data) { + $.ajax({ + 'url':ctx + 'admin/runVip/nodeAddOrEdit', + 'type':'post', + 'dataType':'json', + 'headers' : {'Content-Type' : 'application/json;charset=utf-8'}, //接口json格式 + 'traditional': true,//ajax传递数组必须添加属性 + 'data':JSON.stringify(data.field), + 'success':function (data) { + if(data.code==200){ + layer.closeAll(); + febs.alert.success(data.message); + $('#febs-run-node').find('#reset').click(); + }else{ + febs.alert.warn(data.message); + } + }, + 'error':function () { + febs.alert.warn('服务器繁忙'); + } + }) + return false; + return false; + }); + }); +</script> \ No newline at end of file diff --git a/src/main/resources/templates/febs/views/modules/runVip/nodeList.html b/src/main/resources/templates/febs/views/modules/runVip/nodeList.html new file mode 100644 index 0000000..d403a54 --- /dev/null +++ b/src/main/resources/templates/febs/views/modules/runVip/nodeList.html @@ -0,0 +1,146 @@ +<div class="layui-fluid layui-anim febs-anim" id="febs-run-node" lay-title="节点规则"> + <div class="layui-row febs-container"> + <div class="layui-col-md12"> + <div class="layui-card"> + <div class="layui-card-body febs-table-full"> + <form class="layui-form layui-table-form" lay-filter="run-node-level-table-form"> + <div class="layui-row"> + <div class="layui-col-md2 layui-col-sm12 layui-col-xs12 table-action-area"> + <div class="layui-btn layui-btn-sm layui-btn-primary febs-button-green-plain table-action" id="nodeAdd"> + 新增 + </div> + <div class="layui-btn layui-btn-sm layui-btn-primary febs-button-blue-plain table-action" id="query"> + <i class="layui-icon"></i> + </div> + <div class="layui-btn layui-btn-sm layui-btn-primary febs-button-green-plain table-action" id="reset"> + <i class="layui-icon"></i> + </div> + </div> + </div> + </form> + <table lay-filter="runNodeLevelTable" lay-data="{id: 'runNodeLevelTable'}"></table> + </div> + </div> + </div> + </div> +</div> +<style> + .layui-table-cell { + height: auto; + } + .layui-form-onswitch { + background-color: #5FB878 !important; + } +</style> +<!-- 表格操作栏 start --> +<script type="text/html" id="user-option"> + <span shiro:lacksPermission="runnode:view,runnode:update,runnode:delete"> + <span class="layui-badge-dot febs-bg-orange"></span> 无权限 + </span> + <a lay-event="edit" shiro:hasPermission="runnode:update"><i + class="layui-icon febs-edit-area febs-blue"></i></a> +</script> + +<!-- 表格操作栏 end --> +<script data-th-inline="none" type="text/javascript"> + // 引入组件并初始化 + layui.use([ 'jquery', 'form', 'table', 'febs'], function () { + var $ = layui.jquery, + febs = layui.febs, + form = layui.form, + table = layui.table, + $view = $('#febs-run-node'), + $query = $view.find('#query'), + $nodeAdd = $view.find('#nodeAdd'), + $reset = $view.find('#reset'), + $searchForm = $view.find('form'), + sortObject = {field: 'orderNumber', type: null}, + tableIns; + + form.render(); + + // 表格初始化 + initNodeListTable(); + + // 初始化表格操作栏各个按钮功能 + table.on('tool(runNodeLevelTable)', function (obj) { + var data = obj.data, + layEvent = obj.event; + if (layEvent === 'runNodeUpdate') { + febs.modal.open('编辑', 'modules/runVip/nodeEdit/' + data.id, { + btn: ['提交', '取消'], + yes: function (index, layero) { + $('#febs-node-edit').find('#submit').trigger('click'); + }, + btn2: function () { + layer.closeAll(); + } + }); + } + if (layEvent === 'delRunNode') { + febs.modal.confirm('删除', '确认删除?', function () { + delRunNode(data.id); + }); + } + }); + + function delRunNode(id) { + febs.get(ctx + 'admin/runVip/nodeDel/' + id, null, function () { + febs.alert.success('操作成功'); + $query.click(); + }); + } + + // 查询按钮 + $query.on('click', function () { + var params = $.extend(getQueryParams(), {field: sortObject.field, order: sortObject.type}); + tableIns.reload({where: params, page: {curr: 1}}); + }); + + // 刷新按钮 + $reset.on('click', function () { + $searchForm[0].reset(); + sortObject.type = 'null'; + tableIns.reload({where: getQueryParams(), page: {curr: 1}, initSort: sortObject}); + }); + + $nodeAdd.on('click', function () { + febs.modal.open('新增', 'modules/runVip/nodeAdd/', { + btn: ['提交', '取消'], + yes: function (index, layero) { + $('#febs-node-add').find('#submit').trigger('click'); + }, + btn2: function () { + layer.closeAll(); + } + }); + }); + + function initNodeListTable() { + tableIns = febs.table.init({ + elem: $view.find('table'), + id: 'runNodeLevelTable', + url: ctx + 'admin/runVip/nodeList', + cols: [[ + {field: 'orderNumber', title: '序号', minWidth: 120, align: 'center'}, + {field: 'minAchieve', title: '业绩范围', minWidth: 120, align: 'center'}, + {field: 'maxAchieve', title: '业绩范围', minWidth: 120, align: 'center'}, + {field: 'nodePercent', title: '助力比例', minWidth: 120, align: 'center'}, + { + title: '操作', + templet: function (d) { + return '<button class="layui-btn layui-btn-normal layui-btn-xs" lay-event="runNodeUpdate" shiro:hasPermission="user:update">编辑</button>' + + '<button class="layui-btn layui-btn-danger layui-btn-xs" lay-event="delRunNode" shiro:hasPermission="user:update">删除</button>' + }, minWidth: 300, align: 'center' + } + ]] + }); + } + + // 获取查询参数 + function getQueryParams() { + return { + }; + } + }) +</script> \ No newline at end of file diff --git a/src/main/resources/templates/febs/views/modules/runVip/vipAdd.html b/src/main/resources/templates/febs/views/modules/runVip/vipAdd.html index 839b9c5..304c5b0 100644 --- a/src/main/resources/templates/febs/views/modules/runVip/vipAdd.html +++ b/src/main/resources/templates/febs/views/modules/runVip/vipAdd.html @@ -104,10 +104,10 @@ <div class="layui-form-item"> <div class="layui-col-lg6"> - <label class="layui-form-label febs-form-item-require">返利比例:</label> + <label class="layui-form-label febs-form-item-require">助力系数:</label> <div class="layui-input-block"> <input type="text" name="rebatePercent" lay-verify="required" autocomplete="off" class="layui-input" > - <div class="layui-form-mid layui-word-aux">购买会员返利金额比例(实际支付金额 * rebatePercent = 上级返利金额)。</div> + <div class="layui-form-mid layui-word-aux">助力系数,每日获取的助力值乘以该比例给上级助力。</div> </div> </div> </div> diff --git a/src/main/resources/templates/febs/views/modules/runVip/vipEdit.html b/src/main/resources/templates/febs/views/modules/runVip/vipEdit.html index bbcceba..454e579 100644 --- a/src/main/resources/templates/febs/views/modules/runVip/vipEdit.html +++ b/src/main/resources/templates/febs/views/modules/runVip/vipEdit.html @@ -104,10 +104,10 @@ <div class="layui-form-item"> <div class="layui-col-lg6"> - <label class="layui-form-label febs-form-item-require">返利比例:</label> + <label class="layui-form-label febs-form-item-require">助力系数:</label> <div class="layui-input-block"> <input type="text" name="rebatePercent" lay-verify="required" autocomplete="off" class="layui-input" > - <div class="layui-form-mid layui-word-aux">购买会员返利金额比例(实际支付金额 * rebatePercent = 上级返利金额)。</div> + <div class="layui-form-mid layui-word-aux">助力系数,每日获取的助力值乘以该比例给上级助力。</div> </div> </div> </div> -- Gitblit v1.9.1