feat(mall): 新增节点规则功能
- 添加节点规则相关实体、Mapper、Service和Controller
- 实现节点规则的新增、编辑和删除功能
- 添加节点奖励计算和发放逻辑
- 更新会员升级节点的条件和时间
5 files added
15 files modified
| | |
| | | @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"), |
| | |
| | | public enum RunVipMoneyFlowTypeEnum { |
| | | |
| | | /** |
| | | * 节点助力 |
| | | */ |
| | | NODE_BALANCE(10,"节点助力碳币{}","节点助力"), |
| | | |
| | | /** |
| | | * 获得碳积分 |
| | | */ |
| | | GET_SCORE(9,"本次获得碳积分{}","获得碳积分"), |
| | |
| | | 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; |
| | |
| | | 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) { |
| | |
| | | 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); |
| | |
| | | |
| | | 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; |
| | |
| | | public class ViewRunVipController{ |
| | | |
| | | private final IAdminRunVipService iAdminRunVipService; |
| | | private final RunNodeSetMapper runNodeSetMapper; |
| | | @GetMapping(value = "/sellVipList") |
| | | @RequiresPermissions("sellVipList:view") |
| | | public String sellVipList() { |
| | |
| | | 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); |
| | |
| | | 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"); |
| | | } |
| | | |
| | | } |
| | |
| | | private String levelName; |
| | | |
| | | /** |
| | | * 董事 |
| | | * 是否是节点 1是 0否 |
| | | */ |
| | | private Integer director; |
| | | //升级为节点的时间 |
| | | @DateTimeFormat(pattern = "yyyy-MM-dd") |
| | | @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8") |
| | | private Date directorTime; |
| | | |
| | | /** |
| | | * 店长 |
| | |
| | | |
| | | private Date lastLoginTime; |
| | | |
| | | //升级为会员的时间 |
| | | @DateTimeFormat(pattern = "yyyy-MM-dd") |
| | | @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8") |
| | | private Date vipLevelTime; |
New file |
| | |
| | | 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;//奖励碳币比例 |
| | | } |
New file |
| | |
| | | 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> { |
| | | } |
| | |
| | | @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小时执行一次 |
| | |
| | | public void updateRunScore() { |
| | | memberProfitService.updateRunScore(); |
| | | } |
| | | |
| | | /** |
| | | * 每个月一号 |
| | | * 节点奖励分发 |
| | | */ |
| | | @Scheduled(cron = "0 0 1 * *") |
| | | public void updateNodeScore() { |
| | | memberProfitService.updateNodeScore(); |
| | | } |
| | | } |
| | |
| | | |
| | | @Async(FebsConstant.ASYNC_POOL) |
| | | void releaseScore(BigDecimal amount,Long memberId); |
| | | |
| | | @Async(FebsConstant.ASYNC_POOL) |
| | | void releaseNodeCoin(BigDecimal amount,BigDecimal percent,BigDecimal balanceToCoin,Long memberId); |
| | | } |
| | |
| | | |
| | | 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; |
| | | |
| | |
| | | public interface IAdminRunVipService extends IService<RunVip> { |
| | | |
| | | IPage<RunVip> runVipListInPage(RunVip runVip,QueryRequest request); |
| | | |
| | | IPage<RunNodeSet> nodeListInPage(RunNodeSet runNodeSet, QueryRequest request); |
| | | |
| | | void addVip(RunVip config); |
| | | |
| | |
| | | IPage<MallMemberWithdraw> sellList(MallMemberWithdraw mallMemberWithdraw, QueryRequest request); |
| | | |
| | | List<MallMoneyFlow> allMoneyType(); |
| | | |
| | | void addRunNodeSet(RunNodeSet config); |
| | | |
| | | void editRunNodeSet(RunNodeSet config); |
| | | |
| | | void deleteNodeById(Long id); |
| | | } |
| | |
| | | void updateRunScore(); |
| | | |
| | | void updateMemberScore(); |
| | | |
| | | void updateNodeScore(); |
| | | } |
| | |
| | | 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; |
| | |
| | | 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 |
| | |
| | | |
| | | 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); |
| | | } |
| | | } |
| | |
| | | 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; |
| | |
| | | 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; |
| | |
| | | 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 |
| | |
| | | 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); |
| | | } |
| | | } |
| | |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.math.RoundingMode; |
| | | |
| | | @Slf4j |
| | | @Service |
| | |
| | | ); |
| | | |
| | | } |
| | | |
| | | @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() |
| | | ); |
| | | |
| | | } |
| | | } |
| | |
| | | private final MallMemberCouponMapper mallMemberCouponMapper; |
| | | private final RunVipMapper runVipMapper; |
| | | private final MallMemberWalletMapper mallMemberWalletMapper; |
| | | private final RunNodeSetMapper runNodeSetMapper; |
| | | private final MallChargeMapper mallChargeMapper; |
| | | |
| | | private final AsyncService asyncService; |
| | | |
| | |
| | | } |
| | | } |
| | | } |
| | | |
| | | @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; |
| | | } |
| | | } |
New file |
| | |
| | | <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> |
New file |
| | |
| | | <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> |
New file |
| | |
| | | <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> |
| | |
| | | |
| | | <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> |
| | |
| | | |
| | | <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> |