Files
kra-new/internal/biz/business/performance_goal.go
T

221 lines
6.3 KiB
Go

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
}