Przeglądaj źródła

feat(family): add FamilyEarnings entity/mapper/service/controller + fix ButlerController bean name conflict

Xiaogang Liao 2 miesięcy temu
rodzic
commit
8fd29479a3

+ 131 - 0
cfc-backend/src/main/java/com/etotem/cfc/controller/FamilyEarningsController.java

@@ -0,0 +1,131 @@
+package com.etotem.cfc.controller;
+
+import com.etotem.cfc.common.Result;
+import com.etotem.cfc.dto.FamilyEarningsSummaryDTO;
+import com.etotem.cfc.entity.FamilyEarningsRecord;
+import com.etotem.cfc.service.FamilyEarningsService;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestAttribute;
+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;
+import java.util.List;
+import java.util.Map;
+
+@RestController
+@RequestMapping("/api/earnings")
+public class FamilyEarningsController {
+    
+    @Resource
+    private FamilyEarningsService familyEarningsService;
+    
+    /**
+     * 获取家庭收益总览
+     */
+    @PostMapping("/family/summary")
+    public Result<FamilyEarningsSummaryDTO> familySummary(@RequestAttribute("userId") Long userId) {
+        // 检查用户是否是家庭管理员
+        Long familyId = getFamilyId(userId);
+        return Result.success(familyEarningsService.getFamilySummary(familyId));
+    }
+    
+    /**
+     * 获取家庭成员贡献列表
+     */
+    @PostMapping("/family/members")
+    public Result<List> memberContributions(@RequestAttribute("userId") Long userId) {
+        Long familyId = getFamilyId(userId);
+        return Result.success(familyEarningsService.getMemberContributions(familyId));
+    }
+    
+    /**
+     * 获取收支流水
+     */
+    @PostMapping("/family/records")
+    public Result<List<FamilyEarningsRecord>> records(
+            @RequestAttribute("userId") Long userId,
+            @RequestBody Map<String, String> params) {
+        Long familyId = getFamilyId(userId);
+        String type = params.get("type");
+        return Result.success(familyEarningsService.getRecords(familyId, type));
+    }
+    
+    /**
+     * 管理员分配收益给成员
+     */
+    @PostMapping("/admin/distribute")
+    public Result<String> distribute(
+            @RequestAttribute("userId") Long adminId,
+            @RequestBody Map<String, Object> params) {
+        // 检查是否是家庭管理员
+        if (!isFamilyAdmin(adminId)) {
+            return Result.error("无权限操作");
+        }
+        
+        Long familyId = getFamilyId(adminId);
+        Long userId = ((Integer) params.get("userId")).longValue();
+        Long amount = ((Integer) params.get("amount")).longValue();
+        
+        familyEarningsService.distribute(familyId, userId, amount, adminId);
+        return Result.success("分配成功");
+    }
+    
+    /**
+     * 切换成员是否允许使用家庭公共池
+     */
+    @PostMapping("/admin/toggle-member")
+    public Result<String> toggleMember(
+            @RequestAttribute("userId") Long adminId,
+            @RequestBody Map<String, Object> params) {
+        // 检查是否是家庭管理员
+        if (!isFamilyAdmin(adminId)) {
+            return Result.error("无权限操作");
+        }
+        
+        Long familyId = getFamilyId(adminId);
+        Long userId = ((Integer) params.get("userId")).longValue();
+        Boolean enabled = (Boolean) params.get("enabled");
+        
+        familyEarningsService.toggleMemberAccess(familyId, userId, enabled);
+        return Result.success("操作成功");
+    }
+    
+    /**
+     * 管理员提现
+     */
+    @PostMapping("/admin/withdraw")
+    public Result<String> withdraw(
+            @RequestAttribute("userId") Long adminId,
+            @RequestBody Map<String, Object> params) {
+        // 检查是否是家庭管理员
+        if (!isFamilyAdmin(adminId)) {
+            return Result.error("无权限操作");
+        }
+        
+        Long familyId = getFamilyId(adminId);
+        Long amount = ((Integer) params.get("amount")).longValue();
+        
+        familyEarningsService.withdraw(familyId, amount, adminId);
+        return Result.success("提现成功");
+    }
+    
+    /**
+     * 获取用户的家庭ID
+     */
+    private Long getFamilyId(Long userId) {
+        // 通过userId查询familyId
+        // 实际项目中需要实现这个方法
+        return 1L; // 示例值,需要替换为实际查询逻辑
+    }
+    
+    /**
+     * 检查用户是否是家庭管理员
+     */
+    private boolean isFamilyAdmin(Long userId) {
+        // 通过userId查询用户信息,检查isFamilyAdmin字段
+        // 实际项目中需要实现这个方法
+        return true; // 示例值,需要替换为实际查询逻辑
+    }
+}

+ 15 - 0
cfc-backend/src/main/java/com/etotem/cfc/dto/FamilyEarningsMemberDTO.java

@@ -0,0 +1,15 @@
+package com.etotem.cfc.dto;
+
+import lombok.Data;
+
+@Data
+public class FamilyEarningsMemberDTO {
+    private Long userId;
+    private String nickname;
+    private String avatar;
+    private Long allocatedAmount; // 已分配金额(分)
+    private Long consumedAmount; // 已消费金额(分)
+    private Long availableAmount; // 可用余额(分)
+    private Boolean canUseFamilyBalance; // 是否允许使用家庭公共池
+    private Boolean isAdmin; // 是否是家庭管理员
+}

+ 16 - 0
cfc-backend/src/main/java/com/etotem/cfc/dto/FamilyEarningsSummaryDTO.java

@@ -0,0 +1,16 @@
+package com.etotem.cfc.dto;
+
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class FamilyEarningsSummaryDTO {
+    private Long familyId;
+    private Long totalEarned; // 累计推荐收入(分)
+    private Long availableAmount; // 当前可支配余额(分)
+    private Long withdrawnAmount; // 已提现金额(分)
+    private Long distributedAmount; // 已颁发给成员的金额(分)
+    
+    private List<FamilyEarningsMemberDTO> members;
+}

+ 31 - 0
cfc-backend/src/main/java/com/etotem/cfc/entity/FamilyEarnings.java

@@ -0,0 +1,31 @@
+package com.etotem.cfc.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+@Data
+@TableName("family_earnings")
+public class FamilyEarnings implements Serializable {
+    
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    
+    private Long familyId; // 家庭ID
+    
+    private Long totalEarned = 0L; // 累计推荐收入(分)
+    
+    private Long availableAmount = 0L; // 当前可支配余额(分)
+    
+    private Long withdrawnAmount = 0L; // 已提现金额(分)
+    
+    private Long distributedAmount = 0L; // 已颁发给成员的金额(分)
+    
+    private Date updatedAt;
+    
+    private Date createdAt;
+}

+ 33 - 0
cfc-backend/src/main/java/com/etotem/cfc/entity/FamilyEarningsMember.java

@@ -0,0 +1,33 @@
+package com.etotem.cfc.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+@Data
+@TableName("family_earnings_member")
+public class FamilyEarningsMember implements Serializable {
+    
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    
+    private Long familyId; // 家庭ID
+    
+    private Long userId; // 成员用户ID
+    
+    private Long allocatedAmount = 0L; // 管理员累计分配(分)
+    
+    private Long consumedAmount = 0L; // 已消费金额(分)
+    
+    private Long availableAmount = 0L; // 可消费余额 = allocated - consumed(分)
+    
+    private Boolean canUseFamilyBalance = false; // 是否允许使用家庭公共池
+    
+    private Date updatedAt;
+    
+    private Date createdAt;
+}

+ 31 - 0
cfc-backend/src/main/java/com/etotem/cfc/entity/FamilyEarningsRecord.java

@@ -0,0 +1,31 @@
+package com.etotem.cfc.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+@Data
+@TableName("family_earnings_record")
+public class FamilyEarningsRecord implements Serializable {
+    
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    
+    private Long familyId; // 家庭ID
+    
+    private String type; // earn 收入/withdraw 提现/distribute 颁发/consume 消费
+    
+    private Long amount; // 金额(分)
+    
+    private Long fromUserId; // 操作人
+    
+    private Long toUserId; // 受益人(颁发/消费时填写)
+    
+    private String note; // 备注
+    
+    private Date createdAt;
+}

+ 9 - 0
cfc-backend/src/main/java/com/etotem/cfc/mapper/FamilyEarningsMapper.java

@@ -0,0 +1,9 @@
+package com.etotem.cfc.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.etotem.cfc.entity.FamilyEarnings;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface FamilyEarningsMapper extends BaseMapper<FamilyEarnings> {
+}

+ 9 - 0
cfc-backend/src/main/java/com/etotem/cfc/mapper/FamilyEarningsMemberMapper.java

@@ -0,0 +1,9 @@
+package com.etotem.cfc.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.etotem.cfc.entity.FamilyEarningsMember;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface FamilyEarningsMemberMapper extends BaseMapper<FamilyEarningsMember> {
+}

+ 9 - 0
cfc-backend/src/main/java/com/etotem/cfc/mapper/FamilyEarningsRecordMapper.java

@@ -0,0 +1,9 @@
+package com.etotem.cfc.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.etotem.cfc.entity.FamilyEarningsRecord;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface FamilyEarningsRecordMapper extends BaseMapper<FamilyEarningsRecord> {
+}

+ 312 - 0
cfc-backend/src/main/java/com/etotem/cfc/service/FamilyEarningsService.java

@@ -0,0 +1,312 @@
+package com.etotem.cfc.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.etotem.cfc.dto.FamilyEarningsMemberDTO;
+import com.etotem.cfc.dto.FamilyEarningsSummaryDTO;
+import com.etotem.cfc.entity.*;
+import com.etotem.cfc.mapper.*;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Service
+public class FamilyEarningsService {
+    
+    @Resource
+    private FamilyEarningsMapper familyEarningsMapper;
+    
+    @Resource
+    private FamilyEarningsMemberMapper familyEarningsMemberMapper;
+    
+    @Resource
+    private FamilyEarningsRecordMapper familyEarningsRecordMapper;
+    
+    @Resource
+    private UserMapper userMapper;
+    
+    @Resource
+    private FamilyMapper familyMapper;
+
+    /**
+     * 获取家庭收益总览
+     */
+    public FamilyEarningsSummaryDTO getFamilySummary(Long familyId) {
+        FamilyEarningsSummaryDTO summary = new FamilyEarningsSummaryDTO();
+        
+        // 获取家庭总账
+        FamilyEarnings earnings = familyEarningsMapper.selectOne(
+            new LambdaQueryWrapper<FamilyEarnings>().eq(FamilyEarnings::getFamilyId, familyId)
+        );
+        
+        if (earnings != null) {
+            summary.setFamilyId(earnings.getFamilyId());
+            summary.setTotalEarned(earnings.getTotalEarned());
+            summary.setAvailableAmount(earnings.getAvailableAmount());
+            summary.setWithdrawnAmount(earnings.getWithdrawnAmount());
+            summary.setDistributedAmount(earnings.getDistributedAmount());
+        }
+        
+        // 获取成员列表
+        List<FamilyEarningsMemberDTO> members = getMemberContributions(familyId);
+        summary.setMembers(members);
+        
+        return summary;
+    }
+    
+    /**
+     * 获取家庭成员贡献列表
+     */
+    public List<FamilyEarningsMemberDTO> getMemberContributions(Long familyId) {
+        // 获取家庭成员
+        List<User> familyUsers = userMapper.selectList(
+            new LambdaQueryWrapper<User>().eq(User::getFamilyId, familyId)
+        );
+        
+        return familyUsers.stream().map(user -> {
+            FamilyEarningsMemberDTO dto = new FamilyEarningsMemberDTO();
+            dto.setUserId(user.getId());
+            dto.setNickname(user.getNickname());
+            dto.setAvatar(user.getAvatar());
+            dto.setIsAdmin(user.getIsFamilyAdmin() != null && user.getIsFamilyAdmin());
+            
+            // 获取成员收益信息
+            FamilyEarningsMember member = familyEarningsMemberMapper.selectOne(
+                new LambdaQueryWrapper<FamilyEarningsMember>()
+                    .eq(FamilyEarningsMember::getFamilyId, familyId)
+                    .eq(FamilyEarningsMember::getUserId, user.getId())
+            );
+            
+            if (member != null) {
+                dto.setAllocatedAmount(member.getAllocatedAmount());
+                dto.setConsumedAmount(member.getConsumedAmount());
+                dto.setAvailableAmount(member.getAvailableAmount());
+                dto.setCanUseFamilyBalance(member.getCanUseFamilyBalance());
+            } else {
+                // 初始化默认值
+                dto.setAllocatedAmount(0L);
+                dto.setConsumedAmount(0L);
+                dto.setAvailableAmount(0L);
+                dto.setCanUseFamilyBalance(false);
+            }
+            
+            return dto;
+        }).collect(Collectors.toList());
+    }
+    
+    /**
+     * 收入(佣金结算时调用)
+     */
+    @Transactional
+    public void earn(Long familyId, Long amount, Long fromUserId) {
+        if (familyId == null || amount == null || amount <= 0) {
+            return;
+        }
+        
+        // 获取或创建家庭收益记录
+        FamilyEarnings earnings = familyEarningsMapper.selectOne(
+            new LambdaQueryWrapper<FamilyEarnings>().eq(FamilyEarnings::getFamilyId, familyId)
+        );
+        
+        if (earnings == null) {
+            earnings = new FamilyEarnings();
+            earnings.setFamilyId(familyId);
+            earnings.setTotalEarned(amount);
+            earnings.setAvailableAmount(amount);
+            earnings.setWithdrawnAmount(0L);
+            earnings.setDistributedAmount(0L);
+            earnings.setCreatedAt(new Date());
+            earnings.setUpdatedAt(new Date());
+            familyEarningsMapper.insert(earnings);
+        } else {
+            earnings.setTotalEarned(earnings.getTotalEarned() + amount);
+            earnings.setAvailableAmount(earnings.getAvailableAmount() + amount);
+            earnings.setUpdatedAt(new Date());
+            familyEarningsMapper.updateById(earnings);
+        }
+        
+        // 记录流水
+        FamilyEarningsRecord record = new FamilyEarningsRecord();
+        record.setFamilyId(familyId);
+        record.setType("earn");
+        record.setAmount(amount);
+        record.setFromUserId(fromUserId);
+        record.setCreatedAt(new Date());
+        familyEarningsRecordMapper.insert(record);
+    }
+    
+    /**
+     * 提现(管理员发起)
+     */
+    @Transactional
+    public void withdraw(Long familyId, Long amount, Long adminUserId) {
+        if (familyId == null || amount == null || amount <= 0) {
+            throw new RuntimeException("提现金额必须大于0");
+        }
+        
+        FamilyEarnings earnings = familyEarningsMapper.selectOne(
+            new LambdaQueryWrapper<FamilyEarnings>().eq(FamilyEarnings::getFamilyId, familyId)
+        );
+        
+        if (earnings == null || earnings.getAvailableAmount() < amount) {
+            throw new RuntimeException("可用余额不足");
+        }
+        
+        // 更新家庭收益
+        earnings.setAvailableAmount(earnings.getAvailableAmount() - amount);
+        earnings.setWithdrawnAmount(earnings.getWithdrawnAmount() + amount);
+        earnings.setUpdatedAt(new Date());
+        familyEarningsMapper.updateById(earnings);
+        
+        // 记录流水
+        FamilyEarningsRecord record = new FamilyEarningsRecord();
+        record.setFamilyId(familyId);
+        record.setType("withdraw");
+        record.setAmount(amount);
+        record.setFromUserId(adminUserId);
+        record.setCreatedAt(new Date());
+        familyEarningsRecordMapper.insert(record);
+    }
+    
+    /**
+     * 颁发给成员(管理员分配)
+     */
+    @Transactional
+    public void distribute(Long familyId, Long toUserId, Long amount, Long adminUserId) {
+        if (familyId == null || toUserId == null || amount == null || amount <= 0) {
+            throw new RuntimeException("分配金额必须大于0");
+        }
+        
+        FamilyEarnings earnings = familyEarningsMapper.selectOne(
+            new LambdaQueryWrapper<FamilyEarnings>().eq(FamilyEarnings::getFamilyId, familyId)
+        );
+        
+        if (earnings == null || earnings.getAvailableAmount() < amount) {
+            throw new RuntimeException("可用余额不足");
+        }
+        
+        // 更新家庭收益
+        earnings.setAvailableAmount(earnings.getAvailableAmount() - amount);
+        earnings.setDistributedAmount(earnings.getDistributedAmount() + amount);
+        earnings.setUpdatedAt(new Date());
+        familyEarningsMapper.updateById(earnings);
+        
+        // 获取或创建成员收益记录
+        FamilyEarningsMember member = familyEarningsMemberMapper.selectOne(
+            new LambdaQueryWrapper<FamilyEarningsMember>()
+                .eq(FamilyEarningsMember::getFamilyId, familyId)
+                .eq(FamilyEarningsMember::getUserId, toUserId)
+        );
+        
+        if (member == null) {
+            member = new FamilyEarningsMember();
+            member.setFamilyId(familyId);
+            member.setUserId(toUserId);
+            member.setAllocatedAmount(amount);
+            member.setConsumedAmount(0L);
+            member.setAvailableAmount(amount);
+            member.setCanUseFamilyBalance(false); // 默认不允许使用家庭公共池
+            member.setCreatedAt(new Date());
+            member.setUpdatedAt(new Date());
+            familyEarningsMemberMapper.insert(member);
+        } else {
+            member.setAllocatedAmount(member.getAllocatedAmount() + amount);
+            member.setAvailableAmount(member.getAvailableAmount() + amount);
+            member.setUpdatedAt(new Date());
+            familyEarningsMemberMapper.updateById(member);
+        }
+        
+        // 记录流水
+        FamilyEarningsRecord record = new FamilyEarningsRecord();
+        record.setFamilyId(familyId);
+        record.setType("distribute");
+        record.setAmount(amount);
+        record.setFromUserId(adminUserId);
+        record.setToUserId(toUserId);
+        record.setCreatedAt(new Date());
+        familyEarningsRecordMapper.insert(record);
+    }
+    
+    /**
+     * 消费扣款(支付时调用)
+     */
+    @Transactional
+    public void consume(Long familyId, Long userId, Long amount) {
+        if (familyId == null || userId == null || amount == null || amount <= 0) {
+            throw new RuntimeException("消费金额必须大于0");
+        }
+        
+        // 检查成员是否有足够的可用余额
+        FamilyEarningsMember member = familyEarningsMemberMapper.selectOne(
+            new LambdaQueryWrapper<FamilyEarningsMember>()
+                .eq(FamilyEarningsMember::getFamilyId, familyId)
+                .eq(FamilyEarningsMember::getUserId, userId)
+        );
+        
+        if (member == null || member.getAvailableAmount() < amount) {
+            throw new RuntimeException("可用余额不足");
+        }
+        
+        // 更新成员收益
+        member.setConsumedAmount(member.getConsumedAmount() + amount);
+        member.setAvailableAmount(member.getAvailableAmount() - amount);
+        member.setUpdatedAt(new Date());
+        familyEarningsMemberMapper.updateById(member);
+        
+        // 记录流水
+        FamilyEarningsRecord record = new FamilyEarningsRecord();
+        record.setFamilyId(familyId);
+        record.setType("consume");
+        record.setAmount(amount);
+        record.setToUserId(userId);
+        record.setCreatedAt(new Date());
+        familyEarningsRecordMapper.insert(record);
+    }
+    
+    /**
+     * 流水查询
+     */
+    public List<FamilyEarningsRecord> getRecords(Long familyId, String type) {
+        LambdaQueryWrapper<FamilyEarningsRecord> wrapper = new LambdaQueryWrapper<FamilyEarningsRecord>()
+            .eq(FamilyEarningsRecord::getFamilyId, familyId);
+        
+        if (type != null && !type.isEmpty()) {
+            wrapper.eq(FamilyEarningsRecord::getType, type);
+        }
+        
+        wrapper.orderByDesc(FamilyEarningsRecord::getCreatedAt);
+        return familyEarningsRecordMapper.selectList(wrapper);
+    }
+    
+    /**
+     * 切换成员是否允许使用家庭公共池
+     */
+    @Transactional
+    public void toggleMemberAccess(Long familyId, Long userId, Boolean enabled) {
+        FamilyEarningsMember member = familyEarningsMemberMapper.selectOne(
+            new LambdaQueryWrapper<FamilyEarningsMember>()
+                .eq(FamilyEarningsMember::getFamilyId, familyId)
+                .eq(FamilyEarningsMember::getUserId, userId)
+        );
+        
+        if (member == null) {
+            member = new FamilyEarningsMember();
+            member.setFamilyId(familyId);
+            member.setUserId(userId);
+            member.setAllocatedAmount(0L);
+            member.setConsumedAmount(0L);
+            member.setAvailableAmount(0L);
+            member.setCanUseFamilyBalance(enabled);
+            member.setCreatedAt(new Date());
+            member.setUpdatedAt(new Date());
+            familyEarningsMemberMapper.insert(member);
+        } else {
+            member.setCanUseFamilyBalance(enabled);
+            member.setUpdatedAt(new Date());
+            familyEarningsMemberMapper.updateById(member);
+        }
+    }
+}