feat: add superadmin member review and coupon ops

This commit is contained in:
2026-01-15 11:53:52 +08:00
parent 56082bad4f
commit 8419ddede7
11 changed files with 1254 additions and 96 deletions

View File

@@ -768,6 +768,273 @@ func (s *super) ListTenantUsers(ctx context.Context, tenantID int64, filter *sup
}, nil
}
func (s *super) ListTenantJoinRequests(ctx context.Context, filter *super_dto.SuperTenantJoinRequestListFilter) (*requests.Pager, error) {
if filter == nil {
filter = &super_dto.SuperTenantJoinRequestListFilter{}
}
tbl, q := models.TenantJoinRequestQuery.QueryContext(ctx)
if filter.TenantID != nil && *filter.TenantID > 0 {
q = q.Where(tbl.TenantID.Eq(*filter.TenantID))
}
if filter.UserID != nil && *filter.UserID > 0 {
q = q.Where(tbl.UserID.Eq(*filter.UserID))
}
if filter.Status != nil && *filter.Status != "" {
q = q.Where(tbl.Status.Eq(string(*filter.Status)))
}
tenantIDs, tenantFilter, err := s.lookupTenantIDs(ctx, filter.TenantCode, filter.TenantName)
if err != nil {
return nil, err
}
if tenantFilter {
if len(tenantIDs) == 0 {
q = q.Where(tbl.ID.Eq(-1))
} else {
q = q.Where(tbl.TenantID.In(tenantIDs...))
}
}
userIDs, userFilter, err := s.lookupUserIDs(ctx, filter.Username)
if err != nil {
return nil, err
}
if userFilter {
if len(userIDs) == 0 {
q = q.Where(tbl.ID.Eq(-1))
} else {
q = q.Where(tbl.UserID.In(userIDs...))
}
}
if filter.CreatedAtFrom != nil {
from, err := s.parseFilterTime(filter.CreatedAtFrom)
if err != nil {
return nil, err
}
if from != nil {
q = q.Where(tbl.CreatedAt.Gte(*from))
}
}
if filter.CreatedAtTo != nil {
to, err := s.parseFilterTime(filter.CreatedAtTo)
if err != nil {
return nil, err
}
if to != nil {
q = q.Where(tbl.CreatedAt.Lte(*to))
}
}
filter.Pagination.Format()
total, err := q.Count()
if err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
list, err := q.Order(tbl.CreatedAt.Desc()).
Offset(int(filter.Pagination.Offset())).
Limit(int(filter.Pagination.Limit)).
Find()
if err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
// 补齐租户与用户信息,便于前端展示。
tenantMap := make(map[int64]*models.Tenant)
userMap := make(map[int64]*models.User)
if len(list) > 0 {
tenantIDSet := make(map[int64]struct{})
userIDSet := make(map[int64]struct{})
for _, req := range list {
if req.TenantID > 0 {
tenantIDSet[req.TenantID] = struct{}{}
}
if req.UserID > 0 {
userIDSet[req.UserID] = struct{}{}
}
}
tenantIDs := make([]int64, 0, len(tenantIDSet))
for id := range tenantIDSet {
tenantIDs = append(tenantIDs, id)
}
if len(tenantIDs) > 0 {
tenantTbl, tenantQuery := models.TenantQuery.QueryContext(ctx)
tenants, err := tenantQuery.Where(tenantTbl.ID.In(tenantIDs...)).Find()
if err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
for _, tenant := range tenants {
tenantMap[tenant.ID] = tenant
}
}
userIDs := make([]int64, 0, len(userIDSet))
for id := range userIDSet {
userIDs = append(userIDs, id)
}
if len(userIDs) > 0 {
userTbl, userQuery := models.UserQuery.QueryContext(ctx)
users, err := userQuery.Where(userTbl.ID.In(userIDs...)).Find()
if err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
for _, user := range users {
userMap[user.ID] = user
}
}
}
items := make([]super_dto.SuperTenantJoinRequestItem, 0, len(list))
for _, req := range list {
tenant := tenantMap[req.TenantID]
user := userMap[req.UserID]
status := consts.TenantJoinRequestStatus(req.Status)
statusDescription := status.Description()
if statusDescription == "" {
statusDescription = req.Status
}
tenantCode := ""
tenantName := ""
if tenant != nil {
tenantCode = tenant.Code
tenantName = tenant.Name
}
username := ""
if user != nil {
username = user.Username
}
if username == "" && req.UserID > 0 {
username = "ID:" + strconv.FormatInt(req.UserID, 10)
}
items = append(items, super_dto.SuperTenantJoinRequestItem{
ID: req.ID,
TenantID: req.TenantID,
TenantCode: tenantCode,
TenantName: tenantName,
UserID: req.UserID,
Username: username,
Status: req.Status,
StatusDescription: statusDescription,
Reason: req.Reason,
DecidedAt: s.formatTime(req.DecidedAt),
DecidedOperatorUserID: req.DecidedOperatorUserID,
DecidedReason: req.DecidedReason,
CreatedAt: s.formatTime(req.CreatedAt),
UpdatedAt: s.formatTime(req.UpdatedAt),
})
}
return &requests.Pager{
Pagination: filter.Pagination,
Total: total,
Items: items,
}, nil
}
func (s *super) ReviewTenantJoinRequest(ctx context.Context, operatorID, requestID int64, form *v1_dto.TenantJoinReviewForm) error {
if operatorID == 0 {
return errorx.ErrUnauthorized.WithMsg("缺少操作者信息")
}
if form == nil {
return errorx.ErrBadRequest.WithMsg("审核参数不能为空")
}
action := strings.ToLower(strings.TrimSpace(form.Action))
if action != "approve" && action != "reject" {
return errorx.ErrBadRequest.WithMsg("审核动作无效")
}
tblReq, qReq := models.TenantJoinRequestQuery.QueryContext(ctx)
req, err := qReq.Where(tblReq.ID.Eq(requestID)).First()
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return errorx.ErrRecordNotFound.WithMsg("申请不存在")
}
return errorx.ErrDatabaseError.WithCause(err)
}
if req.Status != string(consts.TenantJoinRequestStatusPending) {
return errorx.ErrBadRequest.WithMsg("申请已处理")
}
reason := strings.TrimSpace(form.Reason)
now := time.Now()
if action == "reject" {
_, err = qReq.Where(
tblReq.ID.Eq(req.ID),
tblReq.Status.Eq(string(consts.TenantJoinRequestStatusPending)),
).UpdateSimple(
tblReq.Status.Value(string(consts.TenantJoinRequestStatusRejected)),
tblReq.DecidedAt.Value(now),
tblReq.DecidedOperatorUserID.Value(operatorID),
tblReq.DecidedReason.Value(reason),
)
if err != nil {
return errorx.ErrDatabaseError.WithCause(err)
}
return nil
}
// 审核通过需在事务内写入成员并更新申请状态。
return models.Q.Transaction(func(tx *models.Query) error {
tblMember, qMember := tx.TenantUser.QueryContext(ctx)
exists, err := qMember.Where(
tblMember.TenantID.Eq(req.TenantID),
tblMember.UserID.Eq(req.UserID),
).Exists()
if err != nil {
return errorx.ErrDatabaseError.WithCause(err)
}
if exists {
return errorx.ErrBadRequest.WithMsg("用户已是成员")
}
member := &models.TenantUser{
TenantID: req.TenantID,
UserID: req.UserID,
Role: types.Array[consts.TenantUserRole]{consts.TenantUserRoleMember},
Status: consts.UserStatusVerified,
}
if err := qMember.Create(member); err != nil {
return errorx.ErrDatabaseError.WithCause(err)
}
tblReqTx, qReqTx := tx.TenantJoinRequest.QueryContext(ctx)
_, err = qReqTx.Where(
tblReqTx.ID.Eq(req.ID),
tblReqTx.Status.Eq(string(consts.TenantJoinRequestStatusPending)),
).UpdateSimple(
tblReqTx.Status.Value(string(consts.TenantJoinRequestStatusApproved)),
tblReqTx.DecidedAt.Value(now),
tblReqTx.DecidedOperatorUserID.Value(operatorID),
tblReqTx.DecidedReason.Value(reason),
)
if err != nil {
return errorx.ErrDatabaseError.WithCause(err)
}
return nil
})
}
func (s *super) CreateTenantInvite(ctx context.Context, tenantID int64, form *v1_dto.TenantInviteCreateForm) (*v1_dto.TenantInviteItem, error) {
if tenantID == 0 {
return nil, errorx.ErrRecordNotFound.WithMsg("租户不存在")
}
// 使用租户主账号执行创建邀请码逻辑,复用既有校验流程。
tbl, q := models.TenantQuery.QueryContext(ctx)
tenant, err := q.Where(tbl.ID.Eq(tenantID)).First()
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errorx.ErrRecordNotFound.WithMsg("租户不存在")
}
return nil, errorx.ErrDatabaseError.WithCause(err)
}
return Tenant.CreateInvite(ctx, tenantID, tenant.UserID, form)
}
func (s *super) ListUserTenants(ctx context.Context, userID int64, filter *super_dto.SuperUserTenantListFilter) (*requests.Pager, error) {
tbl, q := models.TenantUserQuery.QueryContext(ctx)
q = q.Where(tbl.UserID.Eq(userID))