257 lines
6.6 KiB
Go
257 lines
6.6 KiB
Go
package supportgroup
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
|
|
"hfb_sys/backend/internal/model"
|
|
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/clause"
|
|
)
|
|
|
|
type Repository struct {
|
|
db *gorm.DB
|
|
}
|
|
|
|
func NewRepository(db *gorm.DB) *Repository {
|
|
return &Repository{db: db}
|
|
}
|
|
|
|
func (r *Repository) List(ctx context.Context) ([]GroupDTO, error) {
|
|
var groups []model.ChatSupportGroup
|
|
if err := r.db.WithContext(ctx).Order("sort_order ASC, id ASC").Find(&groups).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
items := make([]GroupDTO, 0, len(groups))
|
|
for _, group := range groups {
|
|
dto, err := r.toDTO(ctx, group)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, dto)
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
func (r *Repository) FindByID(ctx context.Context, id uint64) (*GroupDTO, error) {
|
|
var group model.ChatSupportGroup
|
|
if err := r.db.WithContext(ctx).First(&group, id).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
dto, err := r.toDTO(ctx, group)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &dto, nil
|
|
}
|
|
|
|
func (r *Repository) Create(ctx context.Context, req CreateGroupRequest) (*GroupDTO, error) {
|
|
code := strings.TrimSpace(req.Code)
|
|
if code == "" {
|
|
code = makeCustomCode(req.Name)
|
|
}
|
|
status := normalizeStatus(req.Status)
|
|
group := model.ChatSupportGroup{
|
|
Code: code,
|
|
Name: strings.TrimSpace(req.Name),
|
|
Description: strings.TrimSpace(req.Description),
|
|
Status: status,
|
|
SortOrder: req.SortOrder,
|
|
}
|
|
err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
if err := tx.Create(&group).Error; err != nil {
|
|
return err
|
|
}
|
|
return replaceMembers(tx, group.ID, req.MemberIDs)
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return r.FindByID(ctx, group.ID)
|
|
}
|
|
|
|
func (r *Repository) Update(ctx context.Context, id uint64, req UpdateGroupRequest) (*GroupDTO, error) {
|
|
var group model.ChatSupportGroup
|
|
db := r.db.WithContext(ctx)
|
|
if err := db.First(&group, id).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
group.Name = strings.TrimSpace(req.Name)
|
|
group.Description = strings.TrimSpace(req.Description)
|
|
group.Status = normalizeStatus(req.Status)
|
|
group.SortOrder = req.SortOrder
|
|
err := db.Transaction(func(tx *gorm.DB) error {
|
|
if err := tx.Save(&group).Error; err != nil {
|
|
return err
|
|
}
|
|
return replaceMembers(tx, group.ID, req.MemberIDs)
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return r.FindByID(ctx, id)
|
|
}
|
|
|
|
func (r *Repository) Delete(ctx context.Context, id uint64) error {
|
|
var group model.ChatSupportGroup
|
|
db := r.db.WithContext(ctx)
|
|
if err := db.First(&group, id).Error; err != nil {
|
|
return err
|
|
}
|
|
if isProtectedCode(group.Code) {
|
|
return ErrProtectedGroup
|
|
}
|
|
return db.Transaction(func(tx *gorm.DB) error {
|
|
if err := tx.Where("group_id = ?", id).Delete(&model.ChatSupportGroupMember{}).Error; err != nil {
|
|
return err
|
|
}
|
|
return tx.Delete(&model.ChatSupportGroup{}, id).Error
|
|
})
|
|
}
|
|
|
|
func (r *Repository) AssignMembers(ctx context.Context, id uint64, memberIDs []uint64) error {
|
|
var count int64
|
|
if err := r.db.WithContext(ctx).Model(&model.ChatSupportGroup{}).Where("id = ?", id).Count(&count).Error; err != nil {
|
|
return err
|
|
}
|
|
if count == 0 {
|
|
return gorm.ErrRecordNotFound
|
|
}
|
|
return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
return replaceMembers(tx, id, memberIDs)
|
|
})
|
|
}
|
|
|
|
func (r *Repository) ListSupportAdmins(ctx context.Context) ([]MemberDTO, error) {
|
|
var rows []struct {
|
|
ID uint64
|
|
Username string
|
|
Nickname string
|
|
SupportStatus string
|
|
}
|
|
err := r.db.WithContext(ctx).Table("admin_users AS au").
|
|
Select("DISTINCT au.id, au.username, au.nickname, au.support_status").
|
|
Joins("JOIN admin_user_roles AS aur ON aur.admin_user_id = au.id").
|
|
Joins("JOIN roles AS r ON r.id = aur.role_id").
|
|
Where("au.status = ? AND r.code = ?", "active", "cs").
|
|
Order("CASE au.support_status WHEN 'online' THEN 0 WHEN 'busy' THEN 1 ELSE 2 END, au.id ASC").
|
|
Scan(&rows).Error
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
items := make([]MemberDTO, 0, len(rows))
|
|
for _, row := range rows {
|
|
items = append(items, MemberDTO{
|
|
ID: row.ID,
|
|
Username: row.Username,
|
|
Nickname: row.Nickname,
|
|
SupportStatus: row.SupportStatus,
|
|
})
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
func (r *Repository) toDTO(ctx context.Context, group model.ChatSupportGroup) (GroupDTO, error) {
|
|
members, err := r.members(ctx, group.ID)
|
|
if err != nil {
|
|
return GroupDTO{}, err
|
|
}
|
|
return GroupDTO{
|
|
ID: group.ID,
|
|
Code: group.Code,
|
|
Name: group.Name,
|
|
Description: group.Description,
|
|
Status: group.Status,
|
|
SortOrder: group.SortOrder,
|
|
Members: members,
|
|
CreatedAt: group.CreatedAt,
|
|
UpdatedAt: group.UpdatedAt,
|
|
}, nil
|
|
}
|
|
|
|
func (r *Repository) members(ctx context.Context, groupID uint64) ([]MemberDTO, error) {
|
|
var rows []struct {
|
|
ID uint64
|
|
Username string
|
|
Nickname string
|
|
SupportStatus string
|
|
}
|
|
err := r.db.WithContext(ctx).Table("chat_support_group_members AS gm").
|
|
Select("au.id, au.username, au.nickname, au.support_status").
|
|
Joins("JOIN admin_users AS au ON au.id = gm.admin_user_id").
|
|
Where("gm.group_id = ?", groupID).
|
|
Order("au.id ASC").
|
|
Scan(&rows).Error
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
items := make([]MemberDTO, 0, len(rows))
|
|
for _, row := range rows {
|
|
items = append(items, MemberDTO{
|
|
ID: row.ID,
|
|
Username: row.Username,
|
|
Nickname: row.Nickname,
|
|
SupportStatus: row.SupportStatus,
|
|
})
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
func replaceMembers(tx *gorm.DB, groupID uint64, memberIDs []uint64) error {
|
|
if err := tx.Where("group_id = ?", groupID).Delete(&model.ChatSupportGroupMember{}).Error; err != nil {
|
|
return err
|
|
}
|
|
for _, adminID := range uniqueIDs(memberIDs) {
|
|
if adminID == 0 {
|
|
continue
|
|
}
|
|
member := model.ChatSupportGroupMember{GroupID: groupID, AdminID: adminID}
|
|
if err := tx.Clauses(clause.OnConflict{DoNothing: true}).Create(&member).Error; err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func normalizeStatus(status string) string {
|
|
switch status {
|
|
case "disabled":
|
|
return "disabled"
|
|
default:
|
|
return "active"
|
|
}
|
|
}
|
|
|
|
func isProtectedCode(code string) bool {
|
|
return code == GroupCodeOwnerOnboarding || code == GroupCodeRenterHandoff
|
|
}
|
|
|
|
func makeCustomCode(name string) string {
|
|
base := strings.TrimSpace(strings.ToLower(name))
|
|
base = strings.ReplaceAll(base, " ", "_")
|
|
base = strings.ReplaceAll(base, "-", "_")
|
|
if base == "" {
|
|
base = "custom"
|
|
}
|
|
return "custom_" + base
|
|
}
|
|
|
|
func uniqueIDs(ids []uint64) []uint64 {
|
|
seen := make(map[uint64]struct{}, len(ids))
|
|
result := make([]uint64, 0, len(ids))
|
|
for _, id := range ids {
|
|
if _, ok := seen[id]; ok {
|
|
continue
|
|
}
|
|
seen[id] = struct{}{}
|
|
result = append(result, id)
|
|
}
|
|
return result
|
|
}
|
|
|
|
func IsNotFound(err error) bool {
|
|
return errors.Is(err, gorm.ErrRecordNotFound)
|
|
}
|