2026-07-03 22:42:48 +08:00

194 lines
6.5 KiB
Go

package repository
import (
"errors"
"regexp"
"strconv"
"strings"
"hyapp-admin-server/internal/appctx"
"hyapp-admin-server/internal/model"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
var databiPeriodMonthPattern = regexp.MustCompile(`^\d{4}-(0[1-9]|1[0-2])$`)
// ListMoneyScopeOperators 返回所有配置过财务/数据范围的后台用户及其范围;
// 社交 BI 的"运营人员"就是这批人,不再依赖固定 team_id 约定。
func (s *Store) ListMoneyScopeOperators() ([]model.User, map[uint][]model.UserMoneyScope, error) {
var scopes []model.UserMoneyScope
if err := s.db.Order("user_id ASC, app_code ASC, region_id ASC").Find(&scopes).Error; err != nil {
return nil, nil, err
}
scopesByUser := map[uint][]model.UserMoneyScope{}
userIDs := make([]uint, 0, len(scopes))
for _, scope := range scopes {
if _, ok := scopesByUser[scope.UserID]; !ok {
userIDs = append(userIDs, scope.UserID)
}
scopesByUser[scope.UserID] = append(scopesByUser[scope.UserID], scope)
}
if len(userIDs) == 0 {
return []model.User{}, scopesByUser, nil
}
var users []model.User
if err := s.db.Where("id IN ?", userIDs).Order("id ASC").Find(&users).Error; err != nil {
return nil, nil, err
}
return users, scopesByUser, nil
}
func (s *Store) ListDatabiKpiTargets(periodMonths []string, userIDs []uint) ([]model.DatabiKpiTarget, error) {
query := s.db.Model(&model.DatabiKpiTarget{})
months := normalizeDatabiPeriodMonths(periodMonths)
if len(months) > 0 {
query = query.Where("period_month IN ?", months)
}
if len(userIDs) > 0 {
query = query.Where("user_id IN ?", userIDs)
}
var targets []model.DatabiKpiTarget
err := query.Order("user_id ASC, app_code ASC, region_id ASC, period_month ASC").Find(&targets).Error
return targets, err
}
// UpsertDatabiKpiTargets 逐条 upsert 目标;月目标和日目标同时为 0 视为清除该条目标。
func (s *Store) UpsertDatabiKpiTargets(targets []model.DatabiKpiTarget) error {
normalized, removals, err := normalizeDatabiKpiTargets(targets)
if err != nil {
return err
}
return s.db.Transaction(func(tx *gorm.DB) error {
for _, target := range removals {
if err := tx.Where(
"user_id = ? AND app_code = ? AND region_id = ? AND period_month = ?",
target.UserID, target.AppCode, target.RegionID, target.PeriodMonth,
).Delete(&model.DatabiKpiTarget{}).Error; err != nil {
return err
}
}
if len(normalized) == 0 {
return nil
}
return tx.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "user_id"}, {Name: "app_code"}, {Name: "region_id"}, {Name: "period_month"}},
DoUpdates: clause.AssignmentColumns([]string{
"target_usd_minor",
"daily_target_usd_minor",
"updated_at_ms",
}),
}).Create(&normalized).Error
})
}
func (s *Store) ListDatabiAppKpiTargets(periodMonths []string) ([]model.DatabiAppKpiTarget, error) {
query := s.db.Model(&model.DatabiAppKpiTarget{})
months := normalizeDatabiPeriodMonths(periodMonths)
if len(months) > 0 {
query = query.Where("period_month IN ?", months)
}
var targets []model.DatabiAppKpiTarget
err := query.Order("app_code ASC, period_month ASC").Find(&targets).Error
return targets, err
}
// UpsertDatabiAppKpiTargets 逐条 upsert App 级目标;月目标和日目标同时为 0 视为清除。
func (s *Store) UpsertDatabiAppKpiTargets(targets []model.DatabiAppKpiTarget) error {
seen := map[string]struct{}{}
upserts := make([]model.DatabiAppKpiTarget, 0, len(targets))
removals := make([]model.DatabiAppKpiTarget, 0)
for _, target := range targets {
target.AppCode = appctx.Normalize(target.AppCode)
target.PeriodMonth = strings.TrimSpace(target.PeriodMonth)
if target.AppCode == "" {
return errors.New("app kpi target app code is required")
}
if !databiPeriodMonthPattern.MatchString(target.PeriodMonth) {
return errors.New("app kpi target period month must be formatted as YYYY-MM")
}
if target.TargetUSDMinor < 0 || target.DailyTargetUSDMinor < 0 {
return errors.New("app kpi target amounts must not be negative")
}
key := target.AppCode + ":" + target.PeriodMonth
if _, ok := seen[key]; ok {
continue
}
seen[key] = struct{}{}
if target.TargetUSDMinor == 0 && target.DailyTargetUSDMinor == 0 {
removals = append(removals, target)
continue
}
target.ID = 0
upserts = append(upserts, target)
}
return s.db.Transaction(func(tx *gorm.DB) error {
for _, target := range removals {
if err := tx.Where("app_code = ? AND period_month = ?", target.AppCode, target.PeriodMonth).
Delete(&model.DatabiAppKpiTarget{}).Error; err != nil {
return err
}
}
if len(upserts) == 0 {
return nil
}
return tx.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "app_code"}, {Name: "period_month"}},
DoUpdates: clause.AssignmentColumns([]string{
"target_usd_minor",
"daily_target_usd_minor",
"updated_at_ms",
}),
}).Create(&upserts).Error
})
}
func normalizeDatabiKpiTargets(targets []model.DatabiKpiTarget) ([]model.DatabiKpiTarget, []model.DatabiKpiTarget, error) {
seen := map[string]struct{}{}
upserts := make([]model.DatabiKpiTarget, 0, len(targets))
removals := make([]model.DatabiKpiTarget, 0)
for _, target := range targets {
target.AppCode = appctx.Normalize(target.AppCode)
target.PeriodMonth = strings.TrimSpace(target.PeriodMonth)
if target.UserID == 0 || target.AppCode == "" || target.RegionID < 0 {
return nil, nil, errors.New("kpi target user, app code and region id are required")
}
if !databiPeriodMonthPattern.MatchString(target.PeriodMonth) {
return nil, nil, errors.New("kpi target period month must be formatted as YYYY-MM")
}
if target.TargetUSDMinor < 0 || target.DailyTargetUSDMinor < 0 {
return nil, nil, errors.New("kpi target amounts must not be negative")
}
key := strconv.FormatUint(uint64(target.UserID), 10) + ":" + target.AppCode + ":" + strconv.FormatInt(target.RegionID, 10) + ":" + target.PeriodMonth
if _, ok := seen[key]; ok {
continue
}
seen[key] = struct{}{}
if target.TargetUSDMinor == 0 && target.DailyTargetUSDMinor == 0 {
removals = append(removals, target)
continue
}
target.ID = 0
upserts = append(upserts, target)
}
return upserts, removals, nil
}
func normalizeDatabiPeriodMonths(periodMonths []string) []string {
out := make([]string, 0, len(periodMonths))
seen := map[string]struct{}{}
for _, month := range periodMonths {
month = strings.TrimSpace(month)
if month == "" || !databiPeriodMonthPattern.MatchString(month) {
continue
}
if _, ok := seen[month]; ok {
continue
}
seen[month] = struct{}{}
out = append(out, month)
}
return out
}