diff --git a/.workbuddy/expert-history.json b/.workbuddy/expert-history.json new file mode 100644 index 0000000..21dc95b --- /dev/null +++ b/.workbuddy/expert-history.json @@ -0,0 +1,17 @@ +{ + "version": 2, + "sessions": { + "8b8cdf8cb3404efeac18615f9f419cb1": [ + { + "expertId": "SeniorDeveloper", + "name": "吴八哥", + "profession": "高级开发工程师", + "avatarUrl": "https://acc-1258344699.cos.accelerate.myqcloud.com/workbuddy/experts/avatars/02-Engineering/SeniorDeveloper/SeniorDeveloper.png", + "promptUrl": "https://acc-1258344699.cos.accelerate.myqcloud.com/workbuddy/experts/experts/02-Engineering/SeniorDeveloper/SeniorDeveloper_zh.md", + "usedAt": 1776323986694, + "industryId": "02-Engineering" + } + ] + }, + "lastUpdated": 1776324781452 +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/app/recommendation/controller/LeadReferralAdminController.java b/src/main/java/com/gxwebsoft/app/recommendation/controller/LeadReferralAdminController.java new file mode 100644 index 0000000..f70c070 --- /dev/null +++ b/src/main/java/com/gxwebsoft/app/recommendation/controller/LeadReferralAdminController.java @@ -0,0 +1,104 @@ +package com.gxwebsoft.app.recommendation.controller; + +import com.gxwebsoft.app.recommendation.param.LeadReferralParam; +import com.gxwebsoft.app.recommendation.service.LeadReferralService; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.core.annotation.OperationLog; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import java.math.BigDecimal; +import java.util.List; +import java.util.Map; + +/** + * 推荐记录管理控制器(后台管理端) + * + * @author 科技小王子 + * @since 2026-04-16 + */ +@Slf4j +@RestController +@RequestMapping("/app/lead/referral/admin") +@Tag(name = "推荐客户管理", description = "后台管理端推荐记录相关接口") +public class LeadReferralAdminController extends BaseController { + + @Autowired + private LeadReferralService leadReferralService; + + @Operation(summary = "分页查询推荐记录") + @GetMapping("/page") + @PreAuthorize("hasAuthority('app:referral:page') or hasAuthority('app:referral:view')") + public ApiResult> getReferralPage(LeadReferralParam param) { + return success(leadReferralService.getReferralPage(param)); + } + + @Operation(summary = "获取推荐统计汇总") + @GetMapping("/statistics") + @PreAuthorize("hasAuthority('app:referral:statistics')") + public ApiResult> getStatistics(LeadReferralParam param) { + return success(leadReferralService.getStatistics(param)); + } + + @Operation(summary = "确认推荐有效") + @PutMapping("/confirm/{id}") + @PreAuthorize("hasAuthority('app:referral:confirm')") + @OperationLog + public ApiResult confirmReferral( + @Parameter(description = "推荐ID") @PathVariable Long id) { + if (leadReferralService.confirmReferral(id)) { + return success("确认成功"); + } + return fail("确认失败,状态已变更"); + } + + @Operation(summary = "作废推荐") + @PutMapping("/invalid/{id}") + @PreAuthorize("hasAuthority('app:referral:invalid')") + @OperationLog + public ApiResult invalidateReferral( + @Parameter(description = "推荐ID") @PathVariable Long id, + @Parameter(description = "作废原因") @RequestParam(required = false) String reason) { + if (leadReferralService.invalidateReferral(id, reason)) { + return success("已作废"); + } + return fail("操作失败"); + } + + @Operation(summary = "结算推荐费") + @PutMapping("/settle/{id}") + @PreAuthorize("hasAuthority('app:referral:settle')") + @OperationLog + public ApiResult settleReferral( + @Parameter(description = "推荐ID") @PathVariable Long id, + @Parameter(description = "结算金额(为空则用默认推荐费)") @RequestParam(required = false) BigDecimal amount, + @Parameter(description = "备注") @RequestParam(required = false) String remarks) { + if (leadReferralService.settleReferral(id, amount, remarks)) { + return success("结算成功"); + } + return fail("结算失败,请确认状态为「有效」"); + } + + @Operation(summary = "批量结算推荐费") + @PutMapping("/settle/batch") + @PreAuthorize("hasAuthority('app:referral:settle')") + @OperationLog + public ApiResult batchSettleReferrals( + @Parameter(description = "推荐ID列表") @RequestBody List ids) { + int count = leadReferralService.batchSettleReferrals(ids); + return success("成功结算 " + count + " 条"); + } + + @Operation(summary = "导出推荐数据") + @GetMapping("/export") + @PreAuthorize("hasAuthority('app:referral:export')") + public ApiResult>> exportReferrals(LeadReferralParam param) { + return success(leadReferralService.exportReferrals(param)); + } +} diff --git a/src/main/java/com/gxwebsoft/app/recommendation/controller/LeadReferralController.java b/src/main/java/com/gxwebsoft/app/recommendation/controller/LeadReferralController.java new file mode 100644 index 0000000..973bb1a --- /dev/null +++ b/src/main/java/com/gxwebsoft/app/recommendation/controller/LeadReferralController.java @@ -0,0 +1,64 @@ +package com.gxwebsoft.app.recommendation.controller; + +import com.gxwebsoft.app.recommendation.param.LeadReferralParam; +import com.gxwebsoft.app.recommendation.service.LeadReferralService; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.BaseController; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.math.BigDecimal; +import java.util.List; +import java.util.Map; + +/** + * 推荐记录控制器(小程序端) + * + * @author 科技小王子 + * @since 2026-04-16 + */ +@Slf4j +@RestController +@RequestMapping("/app/lead/referral") +@Tag(name = "推荐客户", description = "小程序端推荐报备相关接口") +public class LeadReferralController extends BaseController { + + @Autowired + private LeadReferralService leadReferralService; + + @Operation(summary = "报备客户") + @PostMapping("/add") + public ApiResult addReferral(@RequestBody LeadReferralParam param) { + return success(leadReferralService.addReferral(param)); + } + + @Operation(summary = "获取我的推荐码(无则自动生成)") + @GetMapping("/my/code") + public ApiResult getMyReferralCode() { + String code = leadReferralService.getMyReferralCode(); + return success(code != null ? code : ""); + } + + @Operation(summary = "获取我的推荐记录(分页)") + @GetMapping("/my") + public ApiResult> getMyReferrals(LeadReferralParam param) { + return success(leadReferralService.getMyReferrals(param)); + } + + @Operation(summary = "获取我的推荐统计") + @GetMapping("/my/stats") + public ApiResult> getMyStats() { + return success(leadReferralService.getMyStats()); + } + + @Operation(summary = "根据推荐码获取推荐人信息") + @GetMapping("/referrer/{code}") + public ApiResult> getReferrerByCode( + @Parameter(description = "推荐码") @PathVariable String code) { + return success(leadReferralService.getReferrerByCode(code)); + } +} diff --git a/src/main/java/com/gxwebsoft/app/recommendation/entity/LeadReferral.java b/src/main/java/com/gxwebsoft/app/recommendation/entity/LeadReferral.java new file mode 100644 index 0000000..6f4f002 --- /dev/null +++ b/src/main/java/com/gxwebsoft/app/recommendation/entity/LeadReferral.java @@ -0,0 +1,103 @@ +package com.gxwebsoft.app.recommendation.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableLogic; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * 推荐记录表 + * + * @author 科技小王子 + * @since 2026-04-16 + */ +@Data +@TableName("app_lead_referral") +@Schema(name = "LeadReferral", description = "推荐记录") +public class LeadReferral implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "主键ID") + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @Schema(description = "推荐码(推荐人分享给客户的码)") + private String referralCode; + + @Schema(description = "推荐人用户ID(NULL=匿名推荐)") + private Integer referrerId; + + @Schema(description = "推荐人姓名") + private String referrerName; + + @Schema(description = "推荐人手机号") + private String referrerPhone; + + @Schema(description = "客户姓名") + private String customerName; + + @Schema(description = "客户手机号") + private String customerPhone; + + @Schema(description = "客户公司名称") + private String customerCompany; + + @Schema(description = "客户需求描述") + private String requirement; + + @Schema(description = "预约联系时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm") + private LocalDateTime appointmentTime; + + @Schema(description = "推荐人备注") + private String remarks; + + @Schema(description = "推荐费(元)") + private BigDecimal referralFee; + + @Schema(description = "推荐状态: 0=待确认, 1=有效, 2=无效, 3=已结算") + private Integer referralStatus; + + @Schema(description = "作废原因") + private String invalidReason; + + @Schema(description = "作废时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime invalidTime; + + @Schema(description = "确认有效时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime confirmedTime; + + @Schema(description = "结算时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime settledTime; + + @Schema(description = "租户ID") + private Integer tenantId; + + @Schema(description = "创建人ID") + private Integer createBy; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "更新人ID") + private Integer updateBy; + + @Schema(description = "更新时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + + @Schema(description = "删除标记: 0=未删, 1=已删") + @TableLogic + private Integer deleted; +} diff --git a/src/main/java/com/gxwebsoft/app/recommendation/entity/ReferralSettlement.java b/src/main/java/com/gxwebsoft/app/recommendation/entity/ReferralSettlement.java new file mode 100644 index 0000000..8118b67 --- /dev/null +++ b/src/main/java/com/gxwebsoft/app/recommendation/entity/ReferralSettlement.java @@ -0,0 +1,59 @@ +package com.gxwebsoft.app.recommendation.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableLogic; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * 推荐费结算记录表 + * + * @author 科技小王子 + * @since 2026-04-16 + */ +@Data +@TableName("app_referral_settlement") +@Schema(name = "ReferralSettlement", description = "推荐费结算记录") +public class ReferralSettlement implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "主键ID") + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @Schema(description = "关联推荐记录ID") + private Long referralId; + + @Schema(description = "推荐人用户ID") + private Integer referrerId; + + @Schema(description = "结算金额(元)") + private BigDecimal amount; + + @Schema(description = "结算单号") + private String settlementNo; + + @Schema(description = "结算备注") + private String remarks; + + @Schema(description = "租户ID") + private Integer tenantId; + + @Schema(description = "结算操作人ID") + private Integer createBy; + + @Schema(description = "结算时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "删除标记: 0=未删, 1=已删") + @TableLogic + private Integer deleted; +} diff --git a/src/main/java/com/gxwebsoft/app/recommendation/entity/ReferrerInfo.java b/src/main/java/com/gxwebsoft/app/recommendation/entity/ReferrerInfo.java new file mode 100644 index 0000000..3797b98 --- /dev/null +++ b/src/main/java/com/gxwebsoft/app/recommendation/entity/ReferrerInfo.java @@ -0,0 +1,72 @@ +package com.gxwebsoft.app.recommendation.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableLogic; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * 推荐人信息表 + * + * @author 科技小王子 + * @since 2026-04-16 + */ +@Data +@TableName("app_referrer_info") +@Schema(name = "ReferrerInfo", description = "推荐人信息") +public class ReferrerInfo implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "主键ID") + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "推荐码(唯一)") + private String referralCode; + + @Schema(description = "累计推荐人数") + private Integer totalReferrals; + + @Schema(description = "有效推荐数") + private Integer validReferrals; + + @Schema(description = "已结算数") + private Integer settledCount; + + @Schema(description = "累计获得推荐费(元)") + private BigDecimal totalEarned; + + @Schema(description = "待结算金额(元)") + private BigDecimal pendingAmount; + + @Schema(description = "租户ID") + private Integer tenantId; + + @Schema(description = "创建人ID") + private Integer createBy; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "更新人ID") + private Integer updateBy; + + @Schema(description = "更新时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + + @Schema(description = "删除标记: 0=未删, 1=已删") + @TableLogic + private Integer deleted; +} diff --git a/src/main/java/com/gxwebsoft/app/recommendation/mapper/LeadReferralMapper.java b/src/main/java/com/gxwebsoft/app/recommendation/mapper/LeadReferralMapper.java new file mode 100644 index 0000000..996fd48 --- /dev/null +++ b/src/main/java/com/gxwebsoft/app/recommendation/mapper/LeadReferralMapper.java @@ -0,0 +1,27 @@ +package com.gxwebsoft.app.recommendation.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.gxwebsoft.app.recommendation.entity.LeadReferral; +import com.gxwebsoft.app.recommendation.param.LeadReferralParam; +import org.apache.ibatis.annotations.Param; + +/** + * 推荐记录 Mapper + * + * @author 科技小王子 + * @since 2026-04-16 + */ +public interface LeadReferralMapper extends BaseMapper { + + /** + * 分页查询推荐记录(关联推荐人信息) + */ + IPage selectReferralPage(Page page, @Param("param") LeadReferralParam param); + + /** + * 查询用户的所有推荐记录 + */ + IPage selectByReferrerId(Page page, @Param("referrerId") Integer referrerId); +} diff --git a/src/main/java/com/gxwebsoft/app/recommendation/mapper/ReferralSettlementMapper.java b/src/main/java/com/gxwebsoft/app/recommendation/mapper/ReferralSettlementMapper.java new file mode 100644 index 0000000..6a30853 --- /dev/null +++ b/src/main/java/com/gxwebsoft/app/recommendation/mapper/ReferralSettlementMapper.java @@ -0,0 +1,13 @@ +package com.gxwebsoft.app.recommendation.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.gxwebsoft.app.recommendation.entity.ReferralSettlement; + +/** + * 推荐费结算记录 Mapper + * + * @author 科技小王子 + * @since 2026-04-16 + */ +public interface ReferralSettlementMapper extends BaseMapper { +} diff --git a/src/main/java/com/gxwebsoft/app/recommendation/mapper/ReferrerInfoMapper.java b/src/main/java/com/gxwebsoft/app/recommendation/mapper/ReferrerInfoMapper.java new file mode 100644 index 0000000..decc228 --- /dev/null +++ b/src/main/java/com/gxwebsoft/app/recommendation/mapper/ReferrerInfoMapper.java @@ -0,0 +1,27 @@ +package com.gxwebsoft.app.recommendation.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.gxwebsoft.app.recommendation.entity.ReferrerInfo; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +/** + * 推荐人信息 Mapper + * + * @author 科技小王子 + * @since 2026-04-16 + */ +public interface ReferrerInfoMapper extends BaseMapper { + + /** + * 根据用户ID查询推荐人信息 + */ + @Select("SELECT * FROM app_referrer_info WHERE user_id = #{userId} AND deleted = 0 LIMIT 1") + ReferrerInfo selectByUserId(@Param("userId") Integer userId); + + /** + * 根据推荐码查询推荐人信息 + */ + @Select("SELECT * FROM app_referrer_info WHERE referral_code = #{referralCode} AND deleted = 0 LIMIT 1") + ReferrerInfo selectByReferralCode(@Param("referralCode") String referralCode); +} diff --git a/src/main/java/com/gxwebsoft/app/recommendation/param/LeadReferralParam.java b/src/main/java/com/gxwebsoft/app/recommendation/param/LeadReferralParam.java new file mode 100644 index 0000000..7f31e9e --- /dev/null +++ b/src/main/java/com/gxwebsoft/app/recommendation/param/LeadReferralParam.java @@ -0,0 +1,53 @@ +package com.gxwebsoft.app.recommendation.param; + +import com.gxwebsoft.common.core.web.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 推荐记录查询参数 + * + * @author 科技小王子 + * @since 2026-04-16 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@Schema(name = "LeadReferralParam", description = "推荐记录查询参数") +public class LeadReferralParam extends PageParam { + + @Schema(description = "推荐状态: 0=待确认, 1=有效, 2=无效, 3=已结算") + private Integer referralStatus; + + @Schema(description = "推荐人用户ID") + private Integer referrerId; + + @Schema(description = "客户姓名(模糊)") + private String customerName; + + @Schema(description = "客户手机号") + private String customerPhone; + + @Schema(description = "客户公司") + private String customerCompany; + + @Schema(description = "推荐需求") + private String requirement; + + @Schema(description = "预约时间") + private LocalDateTime appointmentTime; + + @Schema(description = "备注") + private String remarks; + + @Schema(description = "推荐码") + private String referralCode; + + @Schema(description = "开始日期") + private String startDate; + + @Schema(description = "结束日期") + private String endDate; +} diff --git a/src/main/java/com/gxwebsoft/app/recommendation/service/LeadReferralService.java b/src/main/java/com/gxwebsoft/app/recommendation/service/LeadReferralService.java new file mode 100644 index 0000000..c1440a2 --- /dev/null +++ b/src/main/java/com/gxwebsoft/app/recommendation/service/LeadReferralService.java @@ -0,0 +1,83 @@ +package com.gxwebsoft.app.recommendation.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.app.recommendation.entity.LeadReferral; +import com.gxwebsoft.app.recommendation.entity.ReferralSettlement; +import com.gxwebsoft.app.recommendation.param.LeadReferralParam; + +import java.math.BigDecimal; +import java.util.List; +import java.util.Map; + +/** + * 推荐记录 Service + * + * @author 科技小王子 + * @since 2026-04-16 + */ +public interface LeadReferralService extends IService { + + // ========== 小程序端 API ========== + + /** + * 用户报备客户 + */ + LeadReferral addReferral(LeadReferralParam param); + + /** + * 获取我的推荐码(无则生成) + */ + String getMyReferralCode(); + + /** + * 获取我的推荐记录(分页) + */ + Map getMyReferrals(LeadReferralParam param); + + /** + * 获取我的推荐统计 + */ + Map getMyStats(); + + // ========== 后台管理 API ========== + + /** + * 分页查询推荐记录 + */ + Map getReferralPage(LeadReferralParam param); + + /** + * 确认推荐有效 + */ + boolean confirmReferral(Long id); + + /** + * 作废推荐 + */ + boolean invalidateReferral(Long id, String reason); + + /** + * 结算推荐费 + */ + boolean settleReferral(Long id, BigDecimal amount, String remarks); + + /** + * 批量结算推荐费 + */ + int batchSettleReferrals(List ids); + + /** + * 根据推荐码获取推荐人信息 + */ + Map getReferrerByCode(String code); + + /** + * 获取推荐统计汇总 + */ + Map getStatistics(LeadReferralParam param); + + /** + * 导出推荐数据 + */ + List> exportReferrals(LeadReferralParam param); +} diff --git a/src/main/java/com/gxwebsoft/app/recommendation/service/impl/LeadReferralServiceImpl.java b/src/main/java/com/gxwebsoft/app/recommendation/service/impl/LeadReferralServiceImpl.java new file mode 100644 index 0000000..ff7ced4 --- /dev/null +++ b/src/main/java/com/gxwebsoft/app/recommendation/service/impl/LeadReferralServiceImpl.java @@ -0,0 +1,423 @@ +package com.gxwebsoft.app.recommendation.service.impl; + +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.app.recommendation.mapper.ReferrerInfoMapper; +import com.gxwebsoft.app.recommendation.entity.LeadReferral; +import com.gxwebsoft.app.recommendation.entity.ReferralSettlement; +import com.gxwebsoft.app.recommendation.entity.ReferrerInfo; +import com.gxwebsoft.app.recommendation.mapper.LeadReferralMapper; +import com.gxwebsoft.app.recommendation.mapper.ReferralSettlementMapper; +import com.gxwebsoft.app.recommendation.param.LeadReferralParam; +import com.gxwebsoft.app.recommendation.service.LeadReferralService; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.common.system.service.UserService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.*; +import java.util.stream.Collectors; + +/** + * 推荐记录 Service 实现 + * + * @author 科技小王子 + * @since 2026-04-16 + */ +@Slf4j +@Service +public class LeadReferralServiceImpl extends ServiceImpl + implements LeadReferralService { + + @Autowired + private LeadReferralMapper leadReferralMapper; + + @Autowired + private ReferrerInfoMapper referrerInfoMapper; + + @Autowired + private ReferralSettlementMapper settlementMapper; + + @Autowired + private UserService userService; + + /** 默认推荐费(元),可配置化 */ + private static final BigDecimal DEFAULT_REFERRAL_FEE = new BigDecimal("100.00"); + + // ========== 私有方法 ========== + + private User getLoginUser() { + Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + if (principal instanceof User) { + return (User) principal; + } + return null; + } + + private Integer getTenantId() { + User loginUser = getLoginUser(); + if (loginUser != null) { + return loginUser.getTenantId(); + } + return null; + } + + private String generateReferralCode() { + return "RC" + IdUtil.fastSimpleUUID().substring(0, 8).toUpperCase(); + } + + private String getOrCreateReferralCode(Integer userId, String userName, String phone, Integer tenantId) { + ReferrerInfo info = referrerInfoMapper.selectByUserId(userId); + if (info != null && StrUtil.isNotBlank(info.getReferralCode())) { + return info.getReferralCode(); + } + // 创建新记录 + String code; + do { + code = generateReferralCode(); + } while (referrerInfoMapper.selectByReferralCode(code) != null); + + info = new ReferrerInfo(); + info.setUserId(userId); + info.setReferralCode(code); + info.setTotalReferrals(0); + info.setValidReferrals(0); + info.setSettledCount(0); + info.setTotalEarned(BigDecimal.ZERO); + info.setPendingAmount(BigDecimal.ZERO); + info.setTenantId(tenantId); + referrerInfoMapper.insert(info); + return code; + } + + private Map buildPageResult(IPage page) { + Map result = new HashMap<>(); + result.put("list", page.getRecords()); + result.put("total", page.getTotal()); + result.put("pageNum", page.getCurrent()); + result.put("pageSize", page.getSize()); + result.put("pages", page.getPages()); + return result; + } + + // ========== 小程序端 API ========== + + @Override + @Transactional(rollbackFor = Exception.class) + public LeadReferral addReferral(LeadReferralParam param) { + User loginUser = getLoginUser(); + Integer tenantId = getTenantId(); + + LeadReferral referral = new LeadReferral(); + referral.setCustomerName(param.getCustomerName()); + referral.setCustomerPhone(param.getCustomerPhone()); + referral.setCustomerCompany(param.getCustomerCompany()); + referral.setRequirement(param.getRequirement()); + referral.setAppointmentTime(param.getAppointmentTime()); + referral.setRemarks(param.getRemarks()); + referral.setReferralFee(DEFAULT_REFERRAL_FEE); + referral.setReferralStatus(0); // 待确认 + referral.setTenantId(tenantId); + referral.setCreateBy(loginUser != null ? loginUser.getUserId() : null); + referral.setCreateTime(LocalDateTime.now()); + + if (loginUser != null) { + referral.setReferrerId(loginUser.getUserId()); + referral.setReferrerName(loginUser.getUsername()); + referral.setReferrerPhone(loginUser.getPhone()); + // 生成推荐码 + String code = getOrCreateReferralCode( + loginUser.getUserId(), + loginUser.getUsername(), + loginUser.getPhone(), + tenantId + ); + referral.setReferralCode(code); + } + + baseMapper.insert(referral); + + // 更新推荐人统计 + if (referral.getReferrerId() != null) { + ReferrerInfo info = referrerInfoMapper.selectByUserId(referral.getReferrerId()); + if (info != null) { + info.setTotalReferrals(info.getTotalReferrals() + 1); + referrerInfoMapper.updateById(info); + } + } + + return referral; + } + + @Override + public String getMyReferralCode() { + User loginUser = getLoginUser(); + if (loginUser == null) { + return null; + } + Integer tenantId = getTenantId(); + return getOrCreateReferralCode( + loginUser.getUserId(), + loginUser.getUsername(), + loginUser.getPhone(), + tenantId + ); + } + + @Override + public Map getMyReferrals(LeadReferralParam param) { + User loginUser = getLoginUser(); + if (loginUser == null) { + return Collections.emptyMap(); + } +Page page = new Page<>( + param.getCurrent() != null ? param.getCurrent() : 1, + param.getSize() != null ? param.getSize() : 10 + ); + I + return buildPageResult(result); + } + + @Override + public Map getMyStats() { + User loginUser = getLoginUser(); + Map stats = new HashMap<>(); + + if (loginUser == null) { + stats.put("totalCount", 0); + stats.put("pendingCount", 0); + stats.put("validCount", 0); + stats.put("settledCount", 0); + stats.put("pendingAmount", "0.00"); + return stats; + } + + ReferrerInfo info = referrerInfoMapper.selectByUserId(loginUser.getUserId()); + if (info == null) { + stats.put("totalCount", 0); + stats.put("pendingCount", 0); + stats.put("validCount", 0); + stats.put("settledCount", 0); + stats.put("pendingAmount", "0.00"); + } else { + stats.put("totalCount", info.getTotalReferrals()); + stats.put("validCount", info.getValidReferrals()); + stats.put("settledCount", info.getSettledCount()); + stats.put("pendingAmount", info.getPendingAmount().toString()); + // 待确认 = 总数 - 有效 - 已结算 - 无效 + int invalidCount = info.getTotalReferrals() - info.getValidReferrals() + - info.getSettledCount(); + LambdaQueryWrapper qw = new LambdaQueryWrapper<>(); + qw.eq(LeadReferral::getReferrerId, loginUser.getUserId()) + .eq(LeadReferral::getReferralStatus, 0); + stats.put("pendingCount", baseMapper.selectCount(qw)); + } + + // 加上推荐码 + stats.put("referralCode", getMyReferralCode()); + return stats; + } + + // ========== 后台管理 API ========== + + @Override + public Map getReferralPage(LeadReferralParam param) { + Page page = new Page<>( + param.getPageNum() != null ? param.getPageNum() : 1, + param.getPageSize() != null ? param.getPageSize() : 10 + ); + IPage result = leadReferralMapper.selectReferralPage(page, param); + return buildPageResult(result); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public boolean confirmReferral(Long id) { + LeadReferral referral = baseMapper.selectById(id); + if (referral == null || referral.getReferralStatus() != 0) { + return false; + } + referral.setReferralStatus(1); // 有效 + referral.setConfirmedTime(LocalDateTime.now()); + referral.setUpdateTime(LocalDateTime.now()); + baseMapper.updateById(referral); + + // 更新推荐人统计 + if (referral.getReferrerId() != null) { + ReferrerInfo info = referrerInfoMapper.selectByUserId(referral.getReferrerId()); + if (info != null) { + info.setValidReferrals(info.getValidReferrals() + 1); + info.setPendingAmount(info.getPendingAmount().add(referral.getReferralFee())); + referrerInfoMapper.updateById(info); + } + } + return true; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public boolean invalidateReferral(Long id, String reason) { + LeadReferral referral = baseMapper.selectById(id); + if (referral == null || referral.getReferralStatus() == 2 + || referral.getReferralStatus() == 3) { + return false; + } + referral.setReferralStatus(2); // 无效 + referral.setInvalidReason(reason); + referral.setInvalidTime(LocalDateTime.now()); + referral.setUpdateTime(LocalDateTime.now()); + baseMapper.updateById(referral); + + // 若之前是有效状态,需回退推荐人统计 + if (referral.getReferralStatus() == 1 && referral.getReferrerId() != null) { + ReferrerInfo info = referrerInfoMapper.selectByUserId(referral.getReferrerId()); + if (info != null) { + info.setValidReferrals(Math.max(0, info.getValidReferrals() - 1)); + info.setPendingAmount(info.getPendingAmount().subtract(referral.getReferralFee())); + referrerInfoMapper.updateById(info); + } + } + return true; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public boolean settleReferral(Long id, BigDecimal amount, String remarks) { + LeadReferral referral = baseMapper.selectById(id); + if (referral == null || referral.getReferralStatus() != 1) { + return false; + } + + BigDecimal settleAmount = amount != null ? amount : referral.getReferralFee(); + + referral.setReferralStatus(3); // 已结算 + referral.setSettledTime(LocalDateTime.now()); + referral.setUpdateTime(LocalDateTime.now()); + baseMapper.updateById(referral); + + // 写入结算记录 + ReferralSettlement settlement = new ReferralSettlement(); + settlement.setReferralId(id); + settlement.setReferrerId(referral.getReferrerId()); + settlement.setAmount(settleAmount); + settlement.setSettlementNo("ST" + System.currentTimeMillis()); + settlement.setRemarks(remarks); + settlement.setTenantId(referral.getTenantId()); + settlement.setCreateBy(getLoginUser() != null ? getLoginUser().getUserId() : null); + settlement.setCreateTime(LocalDateTime.now()); + settlementMapper.insert(settlement); + + // 更新推荐人统计 + if (referral.getReferrerId() != null) { + ReferrerInfo info = referrerInfoMapper.selectByUserId(referral.getReferrerId()); + if (info != null) { + info.setSettledCount(info.getSettledCount() + 1); + info.setPendingAmount(info.getPendingAmount().subtract(settleAmount)); + info.setTotalEarned(info.getTotalEarned().add(settleAmount)); + referrerInfoMapper.updateById(info); + } + } + return true; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public int batchSettleReferrals(List ids) { + int count = 0; + for (Long id : ids) { + if (settleReferral(id, null, null)) { + count++; + } + } + return count; + } + + @Override + public Map getReferrerByCode(String code) { + ReferrerInfo info = referrerInfoMapper.selectByReferralCode(code); + if (info == null) { + return Collections.emptyMap(); + } + Map result = new HashMap<>(); + result.put("referrerId", info.getUserId()); + result.put("referrerName", ""); + result.put("referralCode", info.getReferralCode()); + if (info.getUserId() != null) { + User user = userService.getByIdRel(info.getUserId()); + if (user != null) { + result.put("referrerName", user.getUsername()); + } + } + return result; + } + + @Override + public Map getStatistics(LeadReferralParam param) { + Map stats = new HashMap<>(); + + LambdaQueryWrapper qw = new LambdaQueryWrapper<>(); + if (param.getStartDate() != null) { + qw.ge(LeadReferral::getCreateTime, + LocalDateTime.parse(param.getStartDate() + " 00:00:00", + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); + } + if (param.getEndDate() != null) { + qw.le(LeadReferral::getCreateTime, + LocalDateTime.parse(param.getEndDate() + " 23:59:59", + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); + } + + stats.put("totalCount", baseMapper.selectCount(qw.clone())); + + qw.eq(LeadReferral::getReferralStatus, 0); + stats.put("pendingCount", baseMapper.selectCount(qw.clone())); + + qw.eq(LeadReferral::getReferralStatus, 1); + stats.put("validCount", baseMapper.selectCount(qw.clone())); + + qw.eq(LeadReferral::getReferralStatus, 3); + stats.put("settledCount", baseMapper.selectCount(qw)); + + return stats; + } + + @Override + public List> exportReferrals(LeadReferralParam param) { + Page page = new Page<>(1, 10000); + IPage result = leadReferralMapper.selectReferralPage(page, param); + return result.getRecords().stream().map(r -> { + Map map = new LinkedHashMap<>(); + map.put("推荐码", r.getReferralCode()); + map.put("推荐人", r.getReferrerName()); + map.put("推荐人手机", r.getReferrerPhone()); + map.put("客户姓名", r.getCustomerName()); + map.put("客户电话", r.getCustomerPhone()); + map.put("客户公司", r.getCustomerCompany()); + map.put("推荐费", r.getReferralFee()); + map.put("状态", getStatusText(r.getReferralStatus())); + map.put("推荐时间", r.getCreateTime()); + return map; + }).collect(Collectors.toList()); + } + + private String getStatusText(Integer status) { + if (status == null) return ""; + switch (status) { + case 0: return "待确认"; + case 1: return "有效"; + case 2: return "无效"; + case 3: return "已结算"; + default: return ""; + } + } +} diff --git a/src/main/resources/db/sql/app_lead_referral.sql b/src/main/resources/db/sql/app_lead_referral.sql new file mode 100644 index 0000000..746429c --- /dev/null +++ b/src/main/resources/db/sql/app_lead_referral.sql @@ -0,0 +1,81 @@ +-- ============================================================ +-- 推荐客户模块 SQL +-- 包: app +-- 创建时间: 2026-04-16 +-- ============================================================ + +-- 1. 推荐记录表 +CREATE TABLE `app_lead_referral` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `referral_code` VARCHAR(32) DEFAULT NULL COMMENT '推荐码(推荐人分享给客户的码)', + `referrer_id` INT DEFAULT NULL COMMENT '推荐人用户ID(NULL=匿名推荐)', + `referrer_name` VARCHAR(64) DEFAULT NULL COMMENT '推荐人姓名', + `referrer_phone` VARCHAR(20) DEFAULT NULL COMMENT '推荐人手机号', + + `customer_name` VARCHAR(64) NOT NULL COMMENT '客户姓名', + `customer_phone` VARCHAR(20) NOT NULL COMMENT '客户手机号', + `customer_company` VARCHAR(255) DEFAULT NULL COMMENT '客户公司名称', + `requirement` TEXT DEFAULT NULL COMMENT '客户需求描述', + `appointment_time` DATETIME DEFAULT NULL COMMENT '预约联系时间', + `remarks` VARCHAR(500) DEFAULT NULL COMMENT '推荐人备注', + + `referral_fee` DECIMAL(10,2) DEFAULT '0.00' COMMENT '推荐费(元)', + `referral_status` TINYINT NOT NULL DEFAULT '0' COMMENT '推荐状态: 0=待确认, 1=有效, 2=无效/作废, 3=已结算', + `invalid_reason` VARCHAR(255) DEFAULT NULL COMMENT '作废原因', + `invalid_time` DATETIME DEFAULT NULL COMMENT '作废时间', + `confirmed_time` DATETIME DEFAULT NULL COMMENT '确认有效时间', + `settled_time` DATETIME DEFAULT NULL COMMENT '结算时间', + + `tenant_id` INT DEFAULT NULL COMMENT '租户ID', + `create_by` INT DEFAULT NULL COMMENT '创建人ID', + `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` INT DEFAULT NULL COMMENT '更新人ID', + `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` TINYINT NOT NULL DEFAULT '0' COMMENT '删除标记: 0=未删, 1=已删', + PRIMARY KEY (`id`), + KEY `idx_referrer_id` (`referrer_id`), + KEY `idx_customer_phone` (`customer_phone`), + KEY `idx_referral_status` (`referral_status`), + KEY `idx_create_time` (`create_time`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='推荐记录表'; + +-- 2. 推荐人信息表 +CREATE TABLE `app_referrer_info` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `user_id` INT NOT NULL COMMENT '用户ID', + `referral_code` VARCHAR(32) NOT NULL COMMENT '推荐码(唯一)', + `total_referrals` INT NOT NULL DEFAULT '0' COMMENT '累计推荐人数', + `valid_referrals` INT NOT NULL DEFAULT '0' COMMENT '有效推荐数', + `settled_count` INT NOT NULL DEFAULT '0' COMMENT '已结算数', + `total_earned` DECIMAL(10,2) NOT NULL DEFAULT '0.00' COMMENT '累计获得推荐费(元)', + `pending_amount` DECIMAL(10,2) NOT NULL DEFAULT '0.00' COMMENT '待结算金额(元)', + + `tenant_id` INT DEFAULT NULL COMMENT '租户ID', + `create_by` INT DEFAULT NULL COMMENT '创建人ID', + `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` INT DEFAULT NULL COMMENT '更新人ID', + `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` TINYINT NOT NULL DEFAULT '0' COMMENT '删除标记: 0=未删, 1=已删', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_user_id` (`user_id`), + UNIQUE KEY `uk_referral_code` (`referral_code`), + KEY `idx_create_time` (`create_time`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='推荐人信息表'; + +-- 3. 推荐费结算记录表 +CREATE TABLE `app_referral_settlement` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `referral_id` BIGINT NOT NULL COMMENT '关联推荐记录ID', + `referrer_id` INT NOT NULL COMMENT '推荐人用户ID', + `amount` DECIMAL(10,2) NOT NULL COMMENT '结算金额(元)', + `settlement_no` VARCHAR(32) DEFAULT NULL COMMENT '结算单号', + `remarks` VARCHAR(500) DEFAULT NULL COMMENT '结算备注', + + `tenant_id` INT DEFAULT NULL COMMENT '租户ID', + `create_by` INT DEFAULT NULL COMMENT '结算操作人ID', + `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '结算时间', + `deleted` TINYINT NOT NULL DEFAULT '0' COMMENT '删除标记: 0=未删, 1=已删', + PRIMARY KEY (`id`), + KEY `idx_referral_id` (`referral_id`), + KEY `idx_referrer_id` (`referrer_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='推荐费结算记录表';