287 lines
10 KiB
Go
287 lines
10 KiB
Go
package business
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"gorm.io/gorm"
|
|
"kra/internal/biz/business"
|
|
)
|
|
|
|
// accountPO 是 sys_users 的商务投影,表结构仍由 system 模块维护。
|
|
type accountPO struct {
|
|
ID uint `gorm:"primaryKey"`
|
|
CreatedAt, UpdatedAt time.Time
|
|
DeletedAt gorm.DeletedAt
|
|
UUID, Username, Password, NickName, RealName, Phone, Email string
|
|
QQNumber string `gorm:"column:qq_number"`
|
|
Level int
|
|
ParentID uint
|
|
IDPath string `gorm:"column:id_path"`
|
|
BusinessStatus int
|
|
RejectReason string
|
|
AuthorityID uint
|
|
Enable int
|
|
OriginSetting string
|
|
PasswordUpdatedAt *time.Time
|
|
ParentUserName string `gorm:"->;-:migration"`
|
|
}
|
|
|
|
func (accountPO) TableName() string { return "sys_users" }
|
|
|
|
func newAccount(a *business.Account) accountPO {
|
|
now := time.Now()
|
|
enable := 0
|
|
if a.Status == 0 {
|
|
enable = 1
|
|
}
|
|
return accountPO{ID: a.ID, UUID: uuid.NewString(), Username: a.Username, NickName: a.NickName, Password: a.Password,
|
|
RealName: a.RealName, Phone: a.Phone, Email: a.Email, QQNumber: a.QQNumber, Level: a.Level, ParentID: a.ParentID,
|
|
IDPath: a.IDPath, BusinessStatus: a.Status, RejectReason: a.RejectReason, AuthorityID: a.AuthorityID, Enable: enable,
|
|
OriginSetting: "{}", PasswordUpdatedAt: &now}
|
|
}
|
|
func toBizAccount(po accountPO) *business.Account {
|
|
return &business.Account{ID: po.ID, Username: po.Username, NickName: po.NickName, RealName: po.RealName, Phone: po.Phone, Email: po.Email,
|
|
QQNumber: po.QQNumber, Level: po.Level, ParentID: po.ParentID, IDPath: po.IDPath, Status: po.BusinessStatus, RejectReason: po.RejectReason,
|
|
AuthorityID: po.AuthorityID, CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, ParentUserName: po.ParentUserName}
|
|
}
|
|
|
|
type accountRepo struct{ data Provider }
|
|
|
|
func NewAccountRepo(data Provider) business.AccountRepo { return &accountRepo{data: data} }
|
|
|
|
func (r *accountRepo) Find(ctx context.Context, id uint) (*business.Account, error) {
|
|
var po accountPO
|
|
if err := r.data.DB().WithContext(ctx).First(&po, id).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, business.ErrAccountNotFound
|
|
}
|
|
return nil, business.ErrStorage
|
|
}
|
|
return toBizAccount(po), nil
|
|
}
|
|
|
|
func applyUserScope(db *gorm.DB, scope business.UserScope, columnPrefix string) *gorm.DB {
|
|
if scope.All {
|
|
return db
|
|
}
|
|
if scope.Empty || scope.SelfID == 0 {
|
|
return db.Where("1=0")
|
|
}
|
|
id, path, parent := columnPrefix+"id", columnPrefix+"id_path", columnPrefix+"parent_id"
|
|
if scope.OnlySelf {
|
|
return db.Where(id+" = ?", scope.SelfID)
|
|
}
|
|
query := "(" + path + " = ? OR " + path + " LIKE ? OR " + parent + " = ?)"
|
|
args := []any{scope.DescendantPath, scope.DescendantPath + ",%", scope.SelfID}
|
|
if scope.IncludeSelf {
|
|
query = "(" + query + " OR " + id + " = ?)"
|
|
args = append(args, scope.SelfID)
|
|
} else {
|
|
db = db.Where(id+" <> ?", scope.SelfID)
|
|
}
|
|
return db.Where(query, args...)
|
|
}
|
|
|
|
func (r *accountRepo) List(ctx context.Context, scope business.UserScope, options ...business.ListOption) ([]*business.Account, int64, error) {
|
|
o := business.NewListOptions(options...)
|
|
filter := o.Filter
|
|
db := r.data.DB().WithContext(ctx).Model(&accountPO{}).Table("sys_users AS u").Where("u.deleted_at IS NULL")
|
|
db = applyUserScope(db, scope, "u.")
|
|
if filter.UserID != 0 {
|
|
db = db.Where("u.id = ?", filter.UserID)
|
|
}
|
|
for _, item := range []struct{ column, value string }{{"username", filter.Username}, {"nick_name", filter.NickName}, {"real_name", filter.RealName}, {"phone", filter.Phone}} {
|
|
if item.value != "" {
|
|
db = db.Where("u."+item.column+" LIKE ?", "%"+item.value+"%")
|
|
}
|
|
}
|
|
if filter.Level != nil {
|
|
db = db.Where("u.level = ?", *filter.Level)
|
|
}
|
|
if filter.Status != nil {
|
|
db = db.Where("u.business_status = ?", *filter.Status)
|
|
}
|
|
if filter.BeginTime != nil && filter.EndTime != nil {
|
|
db = db.Where("u.created_at BETWEEN ? AND ?", filter.BeginTime, filter.EndTime)
|
|
}
|
|
var total int64
|
|
if err := db.Count(&total).Error; err != nil {
|
|
return nil, 0, business.ErrStorage
|
|
}
|
|
columns := map[string]string{"userId": "id", "userName": "username", "realName": "real_name", "level": "level", "status": "business_status", "createTime": "created_at"}
|
|
column := columns[o.OrderBy]
|
|
if column == "" {
|
|
column = "id"
|
|
}
|
|
order := "u." + column
|
|
if o.Desc {
|
|
order += " DESC"
|
|
} else {
|
|
order += " ASC"
|
|
}
|
|
var pos []accountPO
|
|
if err := db.Select("u.*, p.username AS parent_user_name").Joins("LEFT JOIN sys_users p ON p.id=u.parent_id AND p.deleted_at IS NULL").Order(order).Order("u.id ASC").Offset(o.Offset).Limit(o.Limit).Find(&pos).Error; err != nil {
|
|
return nil, 0, business.ErrStorage
|
|
}
|
|
rows := make([]*business.Account, 0, len(pos))
|
|
for _, po := range pos {
|
|
rows = append(rows, toBizAccount(po))
|
|
}
|
|
return rows, total, nil
|
|
}
|
|
|
|
func checkAccountUnique(tx *gorm.DB, a *business.Account) error {
|
|
for _, item := range []struct {
|
|
column, value string
|
|
err error
|
|
}{{"username", a.Username, business.ErrAccountExists}, {"phone", a.Phone, business.ErrPhoneExists}, {"email", a.Email, business.ErrEmailExists}} {
|
|
if item.value == "" {
|
|
continue
|
|
}
|
|
var count int64
|
|
if err := tx.Model(&accountPO{}).Where(item.column+" = ? AND id <> ?", item.value, a.ID).Count(&count).Error; err != nil {
|
|
return business.ErrStorage
|
|
}
|
|
if count > 0 {
|
|
return item.err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *accountRepo) Create(ctx context.Context, a *business.Account) error {
|
|
err := r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
if err := checkAccountUnique(tx, a); err != nil {
|
|
return err
|
|
}
|
|
po := newAccount(a)
|
|
if err := tx.Create(&po).Error; err != nil {
|
|
return business.ErrStorage
|
|
}
|
|
if a.HireDate != "" {
|
|
if err := tx.Create(&businessMemberProfilePO{UserID: po.ID, HireDate: a.HireDate, Employment: "employed"}).Error; err != nil {
|
|
return business.ErrStorage
|
|
}
|
|
}
|
|
if err := tx.Table("sys_user_authority").Create(map[string]any{"sys_user_id": po.ID, "sys_authority_authority_id": po.AuthorityID}).Error; err != nil {
|
|
return business.ErrStorage
|
|
}
|
|
on := 1
|
|
if err := tx.Create(&preferencePO{UserID: po.ID, IncludeSubPromote: &on, IDCardDedup: &on, UpdateTime: time.Now()}).Error; err != nil {
|
|
return business.ErrStorage
|
|
}
|
|
a.ID, a.CreatedAt, a.UpdatedAt = po.ID, po.CreatedAt, po.UpdatedAt
|
|
return nil
|
|
})
|
|
return storageError(err)
|
|
}
|
|
|
|
func (r *accountRepo) Update(ctx context.Context, a *business.Account) error {
|
|
err := r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
if err := checkAccountUnique(tx, a); err != nil {
|
|
return err
|
|
}
|
|
enable := 0
|
|
if a.Status == 0 {
|
|
enable = 1
|
|
}
|
|
result := tx.Model(&accountPO{}).Where("id = ?", a.ID).Updates(map[string]any{
|
|
"username": a.Username, "nick_name": a.NickName, "real_name": a.RealName, "phone": a.Phone, "email": a.Email, "qq_number": a.QQNumber,
|
|
"business_status": a.Status, "enable": enable, "reject_reason": a.RejectReason,
|
|
})
|
|
if result.Error != nil {
|
|
return business.ErrStorage
|
|
}
|
|
if result.RowsAffected == 0 {
|
|
return business.ErrAccountNotFound
|
|
}
|
|
return nil
|
|
})
|
|
return storageError(err)
|
|
}
|
|
|
|
func (r *accountRepo) AuthorityForLevel(ctx context.Context, level int) (uint, error) {
|
|
// 原角色 ID 仅用于查迁移映射,不作为 Kra 角色 ID 使用。
|
|
legacy := map[int]string{1: "2083497078545207297", 2: "2087435436573081601", 3: "2087361690139152385"}
|
|
oldID, ok := legacy[level]
|
|
if !ok {
|
|
return 0, business.ErrBusinessRole
|
|
}
|
|
db := r.data.DB().WithContext(ctx)
|
|
var id uint
|
|
if db.Migrator().HasTable("sw_kra_id_map") {
|
|
if err := db.Table("sw_kra_id_map m").Select("m.new_id").Joins("JOIN sys_authorities a ON a.authority_id=m.new_id AND a.deleted_at IS NULL").Where("m.entity = ? AND m.old_id = ?", "role", oldID).Scan(&id).Error; err != nil {
|
|
return 0, business.ErrStorage
|
|
}
|
|
if id != 0 && id != 888 {
|
|
return id, nil
|
|
}
|
|
}
|
|
// 新安装可使用对应中文角色;匹配不唯一时停止创建,避免错授权限。
|
|
names := map[int]string{1: "一级商务", 2: "二级商务", 3: "三级商务"}
|
|
var ids []uint
|
|
if err := db.Table("sys_authorities").Where("authority_name = ? AND deleted_at IS NULL AND authority_id <> ?", names[level], 888).Pluck("authority_id", &ids).Error; err != nil {
|
|
return 0, business.ErrStorage
|
|
}
|
|
if len(ids) != 1 {
|
|
return 0, business.ErrBusinessRole
|
|
}
|
|
return ids[0], nil
|
|
}
|
|
|
|
func (r *accountRepo) DirectCounts(ctx context.Context, ids []uint) (map[uint]business.AccountCounts, error) {
|
|
result := map[uint]business.AccountCounts{}
|
|
if len(ids) == 0 {
|
|
return result, nil
|
|
}
|
|
db := r.data.DB().WithContext(ctx)
|
|
for _, id := range ids {
|
|
zero := int64(0)
|
|
result[id] = business.AccountCounts{PromoteCount: &zero}
|
|
}
|
|
var binds []struct {
|
|
UserID uint
|
|
Total int64
|
|
}
|
|
if err := db.Table("sys_user_bind_platform").Select("user_id, COUNT(*) AS total").Where("user_id IN ? AND status = ?", ids, "0").Group("user_id").Scan(&binds).Error; err != nil {
|
|
return nil, business.ErrStorage
|
|
}
|
|
if len(binds) == 0 {
|
|
return result, nil
|
|
}
|
|
for _, b := range binds {
|
|
zero := int64(0)
|
|
result[b.UserID] = business.AccountCounts{PromoteCount: &zero, BindCount: b.Total}
|
|
}
|
|
// 系统数据演练库尚未包含渠道表时,基础资料与已通过绑定数仍可使用。
|
|
available, err := businessTableAvailable(db, "tab_promote")
|
|
if err != nil {
|
|
return nil, business.ErrStorage
|
|
}
|
|
if !available {
|
|
for _, b := range binds {
|
|
value := result[b.UserID]
|
|
value.PromoteCount = nil
|
|
result[b.UserID] = value
|
|
}
|
|
return result, nil
|
|
}
|
|
var counts []struct {
|
|
UserID uint
|
|
Total int64
|
|
}
|
|
if err := db.Table("sys_user_bind_platform b").Select("b.user_id, COUNT(*) AS total").Joins("JOIN tab_promote p ON p.source_db=b.source_db AND p.busier_id=b.platform_busier_id AND p.promote_level=1").Where("b.user_id IN ? AND b.status = ?", ids, "0").Group("b.user_id").Scan(&counts).Error; err != nil {
|
|
return nil, business.ErrStorage
|
|
}
|
|
for _, c := range counts {
|
|
value := result[c.UserID]
|
|
count := c.Total
|
|
value.PromoteCount = &count
|
|
result[c.UserID] = value
|
|
}
|
|
return result, nil
|
|
}
|