package business import ( "context" "strconv" "time" "github.com/go-kratos/kratos/v3/errors" ) var ErrGoalInput = errors.BadRequest("SW_GOAL_INPUT", "目标须为非负整数或留空,可修改近6个月及下月目标") type GoalUsecase struct { repo GoalRepo performance *PerformanceUsecase now func() time.Time } func (uc *GoalUsecase) Mine(ctx context.Context) (*GoalMonth, error) { actor, _, err := uc.performance.teams.accounts.actor(ctx) if err != nil { return nil, err } now := uc.now().In(portraitLocation) month := now.Format("2006-01") goals, err := uc.repo.List(ctx, month, month) if err != nil { return nil, err } var goal *PerformanceGoal for _, g := range goals { if g.Kind == "person" && g.TargetID == uint64(actor.UserID) { goal = g break } } if goal == nil { return nil, nil } _, owners, _, _, err := uc.performance.Target(ctx, "person", uint64(actor.UserID)) if err != nil { return nil, err } data, err := uc.performance.query(ctx, PerformanceFilter{Year: now.Year(), BeginMonth: int(now.Month()), EndMonth: int(now.Month())}, owners) if err != nil { return nil, err } if !data.Available { return nil, ErrWorkbenchUnavailable } for _, row := range data.Rows { if row.Member.ID == actor.UserID { return &GoalMonth{Month: month, Goal: goal, Actual: row.Total, Available: true}, nil } } return nil, nil } func NewGoalUsecase(repo GoalRepo, performance *PerformanceUsecase) *GoalUsecase { return &GoalUsecase{repo: repo, performance: performance, now: time.Now} } func goalEditable(month string, now time.Time) bool { current := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, portraitLocation) return month >= current.AddDate(0, -5, 0).Format("2006-01") && month <= current.AddDate(0, 1, 0).Format("2006-01") } func (uc *GoalUsecase) Save(ctx context.Context, goal *PerformanceGoal) error { if goal == nil { return ErrGoalInput } _, _, _, editable, err := uc.performance.Target(ctx, goal.Kind, goal.TargetID) if err != nil { return err } if !editable { return ErrTeamScope } month, err := time.ParseInLocation("2006-01", goal.Month, portraitLocation) if err != nil || month.Format("2006-01") != goal.Month || !goalEditable(goal.Month, uc.now().In(portraitLocation)) { return ErrGoalInput } for _, value := range []*int64{goal.Open, goal.Effective, goal.Amount} { if value != nil && (*value < 0 || *value > MaxSettingsInteger) { return ErrGoalInput } } actor, _, err := uc.performance.teams.accounts.actor(ctx) if err != nil { return err } goal.UpdatedBy = actor.UserID return uc.repo.Save(ctx, goal) } func goalStatus(goal *PerformanceGoal, actual PerformanceMetrics, month, current string) (string, int) { if goal == nil || goal.Open == nil && goal.Effective == nil && goal.Amount == nil { return "unset", 0 } missing := 0 if goal.Open != nil && actual.Open < *goal.Open { missing++ } if goal.Effective != nil && actual.Effective < *goal.Effective { missing++ } if goal.Amount != nil { pay, _ := portraitNumber(actual.Pay) threshold, _ := portraitNumber(strconv.FormatInt(*goal.Amount, 10)) if pay.Cmp(threshold) < 0 { missing++ } } if month == current { return "ongoing", missing } if month > current { return "future", missing } if missing == 0 { return "achieved", 0 } return "missing", missing } func (uc *GoalUsecase) History(ctx context.Context, kind string, id uint64) (*GoalPage, error) { _, owners, name, editable, err := uc.performance.Target(ctx, kind, id) if err != nil { return nil, err } now := uc.now().In(portraitLocation) current := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, portraitLocation) begin := current.AddDate(0, -5, 0) end := current.AddDate(0, 1, 0) goals, err := uc.repo.List(ctx, begin.Format("2006-01"), end.Format("2006-01")) if err != nil { return nil, err } byMonth := map[string]*PerformanceGoal{} for _, g := range goals { if g.Kind == kind && g.TargetID == id { byMonth[g.Month] = g } } out := &GoalPage{Kind: kind, TargetID: id, Name: name, CanEdit: editable, Months: []GoalMonth{}, MemberGoals: map[string]PerformanceMetrics{}} if kind == "group" { view, err := uc.performance.teams.View(ctx) if err != nil { return nil, err } members := map[uint64]bool{} for _, group := range view.Groups { if group.ID == id { for _, member := range group.MemberIDs { members[uint64(member)] = true } } } for _, goal := range goals { if goal.Kind != "person" || !members[goal.TargetID] { continue } value := PerformanceMetrics{Pay: "0.00"} if goal.Open != nil { value.Open = *goal.Open } if goal.Effective != nil { value.Effective = *goal.Effective } if goal.Amount != nil { value.Pay = strconv.FormatInt(*goal.Amount, 10) } out.MemberGoals[goal.Month] = addPerformance(out.MemberGoals[goal.Month], value) } } // At most two year queries supply the complete history; no query per row. actuals := map[string]PerformanceMetrics{} available := true for year := begin.Year(); year <= end.Year(); year++ { from, to := 1, 12 if year == begin.Year() { from = int(begin.Month()) } if year == end.Year() { to = int(end.Month()) } data, err := uc.performance.query(ctx, PerformanceFilter{Year: year, BeginMonth: from, EndMonth: to}, owners) if err != nil { return nil, err } available = available && data.Available var values map[string]PerformanceMetrics if kind == "person" { for _, row := range data.Rows { if uint64(row.Member.ID) == id { values = row.Periods } } } else { for _, group := range data.Groups { if group.Group.ID == id { values = group.Periods } } } for key, value := range values { month := key[:7] actuals[month] = addPerformance(actuals[month], value) } } for month := end; !month.Before(begin); month = month.AddDate(0, -1, 0) { key := month.Format("2006-01") actual := actuals[key] if actual.Pay == "" { actual.Pay = "0.00" } status, missing := goalStatus(byMonth[key], actual, key, current.Format("2006-01")) if !available { status = "unavailable" } out.Months = append(out.Months, GoalMonth{Month: key, Goal: byMonth[key], Actual: actual, Status: status, Missing: missing, CanEdit: editable && goalEditable(key, now), Available: available}) } return out, nil }