package data import ( "context" "errors" "fmt" "strings" "time" "kra/internal/biz" "gorm.io/gorm" ) type dictionaryPO struct { ID uint `gorm:"primaryKey"` CreatedAt time.Time UpdatedAt time.Time DeletedAt gorm.DeletedAt `gorm:"index"` Name string Type string Status bool Desc string ParentID *uint } func (dictionaryPO) TableName() string { return "sys_dictionaries" } type dictionaryDetailPO struct { ID uint `gorm:"primaryKey"` CreatedAt time.Time UpdatedAt time.Time DeletedAt gorm.DeletedAt `gorm:"index"` Label string Value string Extend string Status bool Sort int DictionaryID uint `gorm:"column:sys_dictionary_id;index"` ParentID *uint Level int Path string } func (dictionaryDetailPO) TableName() string { return "sys_dictionary_details" } func dictionaryFromPO(po dictionaryPO) *biz.Dictionary { return &biz.Dictionary{ID: po.ID, CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, Name: po.Name, Type: po.Type, Status: po.Status, Desc: po.Desc, ParentID: po.ParentID} } func detailFromPO(po dictionaryDetailPO) *biz.DictionaryDetail { return &biz.DictionaryDetail{ID: po.ID, CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, Label: po.Label, Value: po.Value, Extend: po.Extend, Status: po.Status, Sort: po.Sort, DictionaryID: po.DictionaryID, ParentID: po.ParentID, Level: po.Level, Path: po.Path} } func parameterFromPO(po parameterPO) *biz.SystemParameter { return &biz.SystemParameter{ID: po.ID, CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, Name: po.Name, Key: po.Key, Value: po.Value, Desc: po.Desc} } func (r *settingsRepo) CreateDictionary(ctx context.Context, v *biz.Dictionary) error { po := dictionaryPO{Name: v.Name, Type: v.Type, Status: v.Status, Desc: v.Desc, ParentID: v.ParentID} if err := r.data.gormDB.WithContext(ctx).Create(&po).Error; err != nil { return err } v.ID = po.ID return nil } func (r *settingsRepo) ImportDictionary(ctx context.Context, dictionary *biz.Dictionary, details []*biz.DictionaryDetail) error { return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error { po := dictionaryPO{Name: dictionary.Name, Type: dictionary.Type, Status: dictionary.Status, Desc: dictionary.Desc, ParentID: dictionary.ParentID} if err := tx.Create(&po).Error; err != nil { return err } dictionary.ID = po.ID idMap := make(map[uint]uint, len(details)) created := make(map[uint]uint, len(details)) for index, item := range details { // Incomplete detail rows are ignored to preserve import behavior. if item.Label == "" || item.Value == "" { continue } oldID := item.ID detail := dictionaryDetailPO{Label: item.Label, Value: item.Value, Extend: item.Extend, Status: item.Status, Sort: item.Sort, DictionaryID: po.ID, Level: item.Level, Path: item.Path} if err := tx.Create(&detail).Error; err != nil { return err } created[uint(index)] = detail.ID if oldID > 0 { idMap[oldID] = detail.ID } } for index, item := range details { if item.ID == 0 || item.ParentID == nil || *item.ParentID == 0 { continue } newID, exists := created[uint(index)] newParentID, parentExists := idMap[*item.ParentID] if !exists || !parentExists { continue } if err := tx.Model(&dictionaryDetailPO{}).Where("id = ?", newID).Update("parent_id", newParentID).Error; err != nil { return err } } return nil }) } func (r *settingsRepo) UpdateDictionary(ctx context.Context, v *biz.Dictionary) error { return r.data.gormDB.WithContext(ctx).Model(&dictionaryPO{}).Where("id = ?", v.ID).Updates(map[string]any{"name": v.Name, "type": v.Type, "status": v.Status, "desc": v.Desc, "parent_id": v.ParentID}).Error } func (r *settingsRepo) DeleteDictionary(ctx context.Context, id uint) error { return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error { var count int64 if err := tx.Model(&dictionaryPO{}).Where("parent_id = ?", id).Count(&count).Error; err != nil { return err } if count > 0 { return errors.New("存在子字典不可删除") } if err := tx.Where("sys_dictionary_id = ?", id).Delete(&dictionaryDetailPO{}).Error; err != nil { return err } return tx.Delete(&dictionaryPO{}, id).Error }) } func (r *settingsRepo) FindDictionary(ctx context.Context, id uint, typ string, status *bool, details bool) (*biz.Dictionary, error) { var po dictionaryPO db := r.data.gormDB.WithContext(ctx) active := true if status != nil { active = *status } err := db.Where("(type = ? OR id = ?) AND status = ?", typ, id, active).First(&po).Error if err != nil { return nil, err } out := dictionaryFromPO(po) if details { out.Details, _, err = r.ListDictionaryDetails(ctx, 0, 0, biz.DictionaryDetailFilter{DictionaryID: out.ID}) if err == nil { filtered := out.Details[:0] for _, item := range out.Details { if item.Status { filtered = append(filtered, item) } } out.Details = filtered } } return out, err } func (r *settingsRepo) ExportDictionary(ctx context.Context, id uint) (*biz.Dictionary, error) { var po dictionaryPO if err := r.data.gormDB.WithContext(ctx).First(&po, id).Error; err != nil { return nil, err } value := dictionaryFromPO(po) details, _, err := r.ListDictionaryDetails(ctx, 0, 0, biz.DictionaryDetailFilter{DictionaryID: id}) if err != nil { return nil, err } value.Details = details return value, nil } func (r *settingsRepo) ListDictionaries(ctx context.Context, page, size int, name, typ string, details bool) ([]*biz.Dictionary, int64, error) { db := r.data.gormDB.WithContext(ctx).Model(&dictionaryPO{}) if name != "" { like := "%" + name + "%" db = db.Where("name LIKE ? OR type LIKE ?", like, like) } var total int64 if err := db.Count(&total).Error; err != nil { return nil, 0, err } var pos []dictionaryPO if err := applyPagination(db.Order("id"), page, size, 0).Find(&pos).Error; err != nil { return nil, 0, err } parentIDs := make([]uint, 0, len(pos)) dictionaryIDs := make([]uint, 0, len(pos)) for _, po := range pos { parentIDs = append(parentIDs, po.ID) dictionaryIDs = append(dictionaryIDs, po.ID) } childrenByParent := make(map[uint][]dictionaryPO) if len(parentIDs) > 0 { var children []dictionaryPO if err := r.data.gormDB.WithContext(ctx).Where("parent_id IN ?", parentIDs).Order("id").Find(&children).Error; err != nil { return nil, 0, err } for _, child := range children { if child.ParentID != nil { childrenByParent[*child.ParentID] = append(childrenByParent[*child.ParentID], child) } } } detailsByDictionary := make(map[uint][]dictionaryDetailPO) if details && len(dictionaryIDs) > 0 { var values []dictionaryDetailPO if err := r.data.gormDB.WithContext(ctx).Where("sys_dictionary_id IN ?", dictionaryIDs).Order("sort").Find(&values).Error; err != nil { return nil, 0, err } for _, value := range values { detailsByDictionary[value.DictionaryID] = append(detailsByDictionary[value.DictionaryID], value) } } out := make([]*biz.Dictionary, 0, len(pos)) for _, po := range pos { v := dictionaryFromPO(po) for _, child := range childrenByParent[po.ID] { v.Children = append(v.Children, dictionaryFromPO(child)) } if details { for _, detail := range detailsByDictionary[v.ID] { v.Details = append(v.Details, detailFromPO(detail)) } } out = append(out, v) } return out, total, nil } func (r *settingsRepo) CreateDictionaryDetail(ctx context.Context, v *biz.DictionaryDetail) error { po := dictionaryDetailPO{Label: v.Label, Value: v.Value, Extend: v.Extend, Status: v.Status, Sort: v.Sort, DictionaryID: v.DictionaryID, ParentID: v.ParentID} po.Level = 0 po.Path = "" if v.ParentID != nil && *v.ParentID != 0 { var parent dictionaryDetailPO if err := r.data.gormDB.WithContext(ctx).First(&parent, *v.ParentID).Error; err != nil { return err } po.Level = parent.Level + 1 po.Path = strings.Trim(strings.Join([]string{parent.Path, fmt.Sprint(parent.ID)}, ","), ",") } if err := r.data.gormDB.WithContext(ctx).Create(&po).Error; err != nil { return err } v.ID = po.ID v.Level = po.Level v.Path = po.Path return nil } func (r *settingsRepo) UpdateDictionaryDetail(ctx context.Context, v *biz.DictionaryDetail) error { var po dictionaryDetailPO if err := r.data.gormDB.WithContext(ctx).First(&po, v.ID).Error; err != nil { return err } level, path := 0, "" if v.ParentID != nil && *v.ParentID != 0 { if *v.ParentID == v.ID { return errors.New("不能将自身设为父级") } var parent dictionaryDetailPO if err := r.data.gormDB.WithContext(ctx).First(&parent, *v.ParentID).Error; err != nil { return err } if strings.Contains(","+parent.Path+",", fmt.Sprintf(",%d,", v.ID)) { return errors.New("不能移动到自己的子级") } level = parent.Level + 1 path = strings.Trim(strings.Join([]string{parent.Path, fmt.Sprint(parent.ID)}, ","), ",") } oldPrefix := strings.Trim(strings.Join([]string{po.Path, fmt.Sprint(po.ID)}, ","), ",") newPrefix := strings.Trim(strings.Join([]string{path, fmt.Sprint(po.ID)}, ","), ",") return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error { if err := tx.Model(&dictionaryDetailPO{}).Where("id = ?", v.ID).Updates(map[string]any{"label": v.Label, "value": v.Value, "extend": v.Extend, "status": v.Status, "sort": v.Sort, "sys_dictionary_id": v.DictionaryID, "parent_id": v.ParentID, "level": level, "path": path}).Error; err != nil { return err } return tx.Model(&dictionaryDetailPO{}).Where("path = ? OR path LIKE ?", oldPrefix, oldPrefix+",%").Update("path", gorm.Expr("REPLACE(path, ?, ?)", oldPrefix, newPrefix)).Error }) } func (r *settingsRepo) DeleteDictionaryDetail(ctx context.Context, id uint) error { var count int64 if err := r.data.gormDB.WithContext(ctx).Model(&dictionaryDetailPO{}).Where("parent_id = ?", id).Count(&count).Error; err != nil { return err } if count > 0 { return errors.New("存在子级不可删除") } return r.data.gormDB.WithContext(ctx).Delete(&dictionaryDetailPO{}, id).Error } func (r *settingsRepo) FindDictionaryDetail(ctx context.Context, id uint) (*biz.DictionaryDetail, error) { var po dictionaryDetailPO if err := r.data.gormDB.WithContext(ctx).First(&po, id).Error; err != nil { return nil, err } return detailFromPO(po), nil } func (r *settingsRepo) ListDictionaryDetails(ctx context.Context, page, size int, filter biz.DictionaryDetailFilter) ([]*biz.DictionaryDetail, int64, error) { db := r.data.gormDB.WithContext(ctx).Model(&dictionaryDetailPO{}) if filter.DictionaryID != 0 { db = db.Where("sys_dictionary_id = ?", filter.DictionaryID) } if filter.Label != "" { db = db.Where("label LIKE ?", "%"+filter.Label+"%") } if filter.Value != "" { db = db.Where("value = ?", filter.Value) } if filter.Status != nil { db = db.Where("status = ?", *filter.Status) } if filter.ParentID != nil { db = db.Where("parent_id = ?", *filter.ParentID) } if filter.Level != nil { db = db.Where("level = ?", *filter.Level) } var total int64 if err := db.Count(&total).Error; err != nil { return nil, 0, err } var pos []dictionaryDetailPO if err := applyPagination(db.Order("sort,id"), page, size, 100).Find(&pos).Error; err != nil { return nil, 0, err } out := make([]*biz.DictionaryDetail, 0, len(pos)) for _, po := range pos { out = append(out, detailFromPO(po)) } return out, total, nil } func (r *settingsRepo) DictionaryDetailTree(ctx context.Context, dictionaryID uint, typ string) ([]*biz.DictionaryDetail, error) { if dictionaryID == 0 { active := true dictionary, err := r.FindDictionary(ctx, 0, typ, &active, false) if err != nil { return nil, err } dictionaryID = dictionary.ID } items, _, err := r.ListDictionaryDetails(ctx, 0, 0, biz.DictionaryDetailFilter{DictionaryID: dictionaryID}) if err != nil { return nil, err } byID := map[uint]*biz.DictionaryDetail{} for _, item := range items { item.Children = []*biz.DictionaryDetail{} byID[item.ID] = item } roots := []*biz.DictionaryDetail{} for _, item := range items { if item.ParentID != nil && byID[*item.ParentID] != nil { byID[*item.ParentID].Children = append(byID[*item.ParentID].Children, item) } else { roots = append(roots, item) } } return roots, nil }