package com.ruoyi.user.domain; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Date; import java.util.List; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonProperty; import com.ruoyi.common.core.annotation.Excel; import com.ruoyi.common.core.annotation.Excel.ColumnType; import com.ruoyi.common.core.web.domain.BaseEntity; /** * C端会员用户表 account_user * * @author ruoyi */ public class AccountUser extends BaseEntity { private static final long serialVersionUID = 1L; /** 会员ID */ @Excel(name = "会员ID", cellType = ColumnType.NUMERIC) private Long userId; /** * 用户名 * * 注意:属性名刻意用驼峰 userName,与上游 SysUser.userName 一致。网关的 AuthFilter 会 * 给每个转发请求加上请求头 username=<登录名>(SecurityConstants.DETAILS_USERNAME), * 而 Spring 6+ 的 @ModelAttribute 会把请求头一并绑定到命令对象——若此处叫 username, * 列表/导出接口在未显式传参时会被注入当前登录名,导致查询结果被静默过滤(实测列表恒为空)。 */ @Excel(name = "用户名") private String userName; /** 手机号 */ @Excel(name = "手机号") private String phone; /** 密码(BCrypt 存储)。只入不出,避免密码随响应回传 */ @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) private String password; /** 昵称 */ @Excel(name = "昵称") private String nickname; /** 头像地址 */ private String avatar; /** 性别(0男 1女 2未知,对齐 sys_user_sex 字典) */ @Excel(name = "性别", readConverterExp = "0=男,1=女,2=未知") private String gender; /** 生日 */ @JsonFormat(pattern = "yyyy-MM-dd") @Excel(name = "生日", width = 30, dateFormat = "yyyy-MM-dd") private Date birthday; /** 账户状态:正常 */ public static final String STATUS_NORMAL = "0"; /** 账户状态:禁用 */ public static final String STATUS_DISABLED = "1"; /** 账户状态:注销中(会员已提交注销申请,待审核) */ public static final String STATUS_CANCELING = "2"; /** 账户状态:已注销(注销申请审核通过) */ public static final String STATUS_CANCELED = "3"; /** 状态(0正常 1禁用 2注销中 3已注销) */ @Excel(name = "状态", readConverterExp = "0=正常,1=禁用,2=注销中,3=已注销") private String status; /** * 系统等级取值,从低到高——等级顺序的唯一定义。 * * 比较一律走 levelIndex / isHigherLevel / levelsAbove,别在别处再写一遍顺序 * (前端 @/utils/member 的 LEVELS 是同一口径的另一份拷贝,改动需同步)。 */ private static final String[] LEVELS = { "C", "L1", "L2", "L3", "L4" }; /** 最高等级(即 LEVELS 末位) */ public static final String LEVEL_L4 = LEVELS[LEVELS.length - 1]; /** 系统等级(C < L1 < L2 < L3 < L4,从低到高) */ @Excel(name = "系统等级") private String systemLevel; /** 主体类型(1个人 2个体工商户 3企业) */ @Excel(name = "主体类型", readConverterExp = "1=个人,2=个体工商户,3=企业") private String subjectType; /** 认证状态(0未认证 1审核中 2已认证 3认证失败) */ @Excel(name = "认证状态", readConverterExp = "0=未认证,1=审核中,2=已认证,3=认证失败") private String authStatus; /** * 企业/个体认证状态(0未认证 1审核中 2已认证 3认证失败),与个人 authStatus 相互独立。 * * 刻意不加 @Excel:避免会员列表导出与个人认证状态混淆。 * 也刻意不进 insertAccountUser / updateAccountUser 的动态列——那两个是共享入口, * 挂上去等于请求体里带个 companyAuthStatus 就能自造认证状态,绕过审核。 * 该列只能由 updateUserCompanyAuthStatus 改写,唯一调用方是企业认证审核流转。 */ private String companyAuthStatus; /** 所属城市 */ @Excel(name = "所属城市") private String city; /** 推荐人会员ID(自连接 account_user.user_id,不冗余存手机号) */ private Long referrerId; /** 推荐关系绑定时间 */ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") @Excel(name = "推荐绑定时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss") private Date referrerBindTime; /** 供货人会员ID(自连接 account_user.user_id,不冗余存手机号) */ private Long supplierId; /** 供货关系绑定时间 */ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") @Excel(name = "供货绑定时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss") private Date supplierBindTime; /* * 下列 9 个关联业务统计字段由订单/库存模块回填,当前恒为 0,展示时需注明"待回填"。 * 金额一律 BigDecimal(对齐 decimal(16,2)),不用 double 以免精度丢失。 */ /** 购买订单数 */ @Excel(name = "购买订单数", cellType = ColumnType.NUMERIC) private Integer buyOrderCount; /** 购买金额合计 */ @Excel(name = "购买金额", cellType = ColumnType.NUMERIC, scale = 2) private BigDecimal buyAmountTotal; /** 销售订单数 */ @Excel(name = "销售订单数", cellType = ColumnType.NUMERIC) private Integer sellOrderCount; /** 销售金额合计 */ @Excel(name = "销售金额", cellType = ColumnType.NUMERIC, scale = 2) private BigDecimal sellAmountTotal; /** 采购订单数 */ @Excel(name = "采购订单数", cellType = ColumnType.NUMERIC) private Integer purchaseOrderCount; /** 采购金额合计 */ @Excel(name = "采购金额", cellType = ColumnType.NUMERIC, scale = 2) private BigDecimal purchaseAmountTotal; /** 销售利润合计(可为负) */ @Excel(name = "销售利润", cellType = ColumnType.NUMERIC, scale = 2) private BigDecimal sellProfitTotal; /** 虚拟库存数量 */ @Excel(name = "虚拟库存数量", cellType = ColumnType.NUMERIC) private Long virtualStockQty; /** 虚拟库存货值 */ @Excel(name = "虚拟库存货值", cellType = ColumnType.NUMERIC, scale = 2) private BigDecimal virtualStockValue; /** 删除标志(0存在 2删除) */ private String delFlag; /** * 服务商标识(0非服务商 1服务商),见 account_provider * * 刻意不加 @Excel:加了会员列表的导出会平白多出一列服务商标识。 * 也刻意不进 insertAccountUser / updateAccountUser 的动态列——那两个是共享入口, * 挂上去等于 POST/PUT /account/user 的请求体里带个 providerFlag=1 就能把自己标成服务商。 * 该列只能由 updateUserProviderFlag 改写,当前唯一调用方是服务商模块。 */ private String providerFlag; /** 本人推荐码(注册时生成,全局唯一) */ private String myReferralCode; public Long getUserId() { return userId; } public void setUserId(Long userId) { this.userId = userId; } @NotBlank(message = "用户名不能为空") @Size(min = 0, max = 64, message = "用户名长度不能超过64个字符") public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } @Size(min = 0, max = 20, message = "手机号长度不能超过20个字符") public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } @Size(min = 0, max = 20, message = "密码长度必须在5到20个字符之间") public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } @Size(min = 0, max = 64, message = "昵称长度不能超过64个字符") public String getNickname() { return nickname; } public void setNickname(String nickname) { this.nickname = nickname; } public String getAvatar() { return avatar; } public void setAvatar(String avatar) { this.avatar = avatar; } public String getGender() { return gender; } public void setGender(String gender) { this.gender = gender; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } public String getSubjectType() { return subjectType; } public void setSubjectType(String subjectType) { this.subjectType = subjectType; } public String getAuthStatus() { return authStatus; } public void setAuthStatus(String authStatus) { this.authStatus = authStatus; } public String getCompanyAuthStatus() { return companyAuthStatus; } public void setCompanyAuthStatus(String companyAuthStatus) { this.companyAuthStatus = companyAuthStatus; } public String getCity() { return city; } public void setCity(String city) { this.city = city; } public Date getReferrerBindTime() { return referrerBindTime; } public void setReferrerBindTime(Date referrerBindTime) { this.referrerBindTime = referrerBindTime; } public Long getSupplierId() { return supplierId; } public void setSupplierId(Long supplierId) { this.supplierId = supplierId; } public Date getSupplierBindTime() { return supplierBindTime; } public void setSupplierBindTime(Date supplierBindTime) { this.supplierBindTime = supplierBindTime; } public Integer getBuyOrderCount() { return buyOrderCount; } public void setBuyOrderCount(Integer buyOrderCount) { this.buyOrderCount = buyOrderCount; } public BigDecimal getBuyAmountTotal() { return buyAmountTotal; } public void setBuyAmountTotal(BigDecimal buyAmountTotal) { this.buyAmountTotal = buyAmountTotal; } public Integer getSellOrderCount() { return sellOrderCount; } public void setSellOrderCount(Integer sellOrderCount) { this.sellOrderCount = sellOrderCount; } public BigDecimal getSellAmountTotal() { return sellAmountTotal; } public void setSellAmountTotal(BigDecimal sellAmountTotal) { this.sellAmountTotal = sellAmountTotal; } public Integer getPurchaseOrderCount() { return purchaseOrderCount; } public void setPurchaseOrderCount(Integer purchaseOrderCount) { this.purchaseOrderCount = purchaseOrderCount; } public BigDecimal getPurchaseAmountTotal() { return purchaseAmountTotal; } public void setPurchaseAmountTotal(BigDecimal purchaseAmountTotal) { this.purchaseAmountTotal = purchaseAmountTotal; } public BigDecimal getSellProfitTotal() { return sellProfitTotal; } public void setSellProfitTotal(BigDecimal sellProfitTotal) { this.sellProfitTotal = sellProfitTotal; } public Long getVirtualStockQty() { return virtualStockQty; } public void setVirtualStockQty(Long virtualStockQty) { this.virtualStockQty = virtualStockQty; } public BigDecimal getVirtualStockValue() { return virtualStockValue; } public void setVirtualStockValue(BigDecimal virtualStockValue) { this.virtualStockValue = virtualStockValue; } public String getDelFlag() { return delFlag; } public void setDelFlag(String delFlag) { this.delFlag = delFlag; } public String getProviderFlag() { return providerFlag; } public void setProviderFlag(String providerFlag) { this.providerFlag = providerFlag; } /** * 等级在 LEVELS 里的下标;未知或为空返回 -1(比任何合法等级都低)。 * * 未知等级按「最低」处理是刻意选的方向:等级写错的会员因此当不了任何人的供货人 * (isHigherLevel 恒为假),而不是被当成高等级放行。 */ public static int levelIndex(String level) { if (level == null) { return -1; } for (int i = 0; i < LEVELS.length; i++) { if (LEVELS[i].equals(level)) { return i; } } return -1; } /** * candidate 的等级是否严格高于 self。 * * 「严格」是要点:供货人不能是同级或更低等级(含 C),这正是业务上 * 「不能 L4 指向 C」的由来——向下指天然被排除。 */ public static boolean isHigherLevel(String candidate, String self) { return levelIndex(candidate) > levelIndex(self); } /** * 严格高于 self 的所有等级,按从低到高排列;self 已是最高等级时返回空集合。 * * 调用方必须自己判断集合非空:这批等级会拼进 SQL 的 in (...),空集合是语法错误。 */ public static List levelsAbove(String self) { List levels = new ArrayList<>(); for (int i = levelIndex(self) + 1; i < LEVELS.length; i++) { levels.add(LEVELS[i]); } return levels; } public String getMyReferralCode() { return myReferralCode; } public void setMyReferralCode(String myReferralCode) { this.myReferralCode = myReferralCode; } public Long getReferrerId() { return referrerId; } public void setReferrerId(Long referrerId) { this.referrerId = referrerId; } public String getSystemLevel() { return systemLevel; } public void setSystemLevel(String systemLevel) { this.systemLevel = systemLevel; } @Override public String toString() { return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) .append("userId", getUserId()) .append("userName", getUserName()) .append("phone", getPhone()) .append("nickname", getNickname()) .append("avatar", getAvatar()) .append("gender", getGender()) .append("birthday", getBirthday()) .append("status", getStatus()) .append("systemLevel", getSystemLevel()) .append("subjectType", getSubjectType()) .append("authStatus", getAuthStatus()) .append("companyAuthStatus", getCompanyAuthStatus()) .append("city", getCity()) .append("referrerId", getReferrerId()) .append("referrerBindTime", getReferrerBindTime()) .append("supplierId", getSupplierId()) .append("supplierBindTime", getSupplierBindTime()) .append("buyOrderCount", getBuyOrderCount()) .append("buyAmountTotal", getBuyAmountTotal()) .append("sellOrderCount", getSellOrderCount()) .append("sellAmountTotal", getSellAmountTotal()) .append("purchaseOrderCount", getPurchaseOrderCount()) .append("purchaseAmountTotal", getPurchaseAmountTotal()) .append("sellProfitTotal", getSellProfitTotal()) .append("virtualStockQty", getVirtualStockQty()) .append("virtualStockValue", getVirtualStockValue()) .append("delFlag", getDelFlag()) .append("providerFlag", getProviderFlag()) .append("myReferralCode", getMyReferralCode()) .append("referrerId", getReferrerId()) .append("systemLevel", getSystemLevel()) .append("createBy", getCreateBy()) .append("createTime", getCreateTime()) .append("updateBy", getUpdateBy()) .append("updateTime", getUpdateTime()) .append("remark", getRemark()) .toString(); } }