Loggest thine Stuff
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

291 lines
7.8 KiB

package projects
import (
"git.aiterp.net/stufflog3/stufflog3/entities"
"git.aiterp.net/stufflog3/stufflog3/models"
"git.aiterp.net/stufflog3/stufflog3/usecases/items"
"git.aiterp.net/stufflog3/stufflog3/usecases/scopes"
"math"
"sort"
"time"
)
type Entry struct {
ID int `json:"id"`
OwnerID string `json:"ownerId"`
CreatedTime time.Time `json:"createdTime"`
Name string `json:"name"`
Status models.Status `json:"status"`
StatusName string `json:"statusName"`
OwnerName string `json:"ownerName,omitempty"`
Tags []string `json:"tags"`
}
func generateEntry(project entities.Project, scope scopes.Result) Entry {
return Entry{
ID: project.ID,
OwnerID: project.OwnerID,
CreatedTime: project.CreatedTime,
Name: project.Name,
Status: project.Status,
Tags: project.Tags,
StatusName: scope.StatusName(project.Status),
OwnerName: scope.MemberName(project.OwnerID),
}
}
type Result struct {
entities.Project
OwnerName string `json:"ownerName"`
StatusName string `json:"statusName"`
TotalAcquired float64 `json:"totalAcquired"`
TotalRequired float64 `json:"totalRequired"`
TotalPlanned float64 `json:"totalPlanned"`
Requirements []RequirementResult `json:"requirements"`
}
func (r *Result) Requirement(id int) *RequirementResult {
for _, requirement := range r.Requirements {
if id == requirement.ID {
return &requirement
}
}
return nil
}
type RequirementResult struct {
ID int `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Status models.Status `json:"status"`
StatusName string `json:"statusName"`
TotalAcquired float64 `json:"totalAcquired"`
TotalRequired float64 `json:"totalRequired"`
TotalPlanned float64 `json:"totalPlanned"`
IsCoarse bool `json:"isCoarse"`
AggregateRequired int `json:"aggregateRequired"`
Stats []RequirementResultStat `json:"stats"`
Items []items.Result `json:"items,omitempty"`
Tags []string `json:"tags"`
Requirement entities.Requirement `json:"-"`
}
func (r *RequirementResult) Stat(id int) *RequirementResultStat {
for _, stat := range r.Stats {
if stat.ID == id {
return &stat
}
}
return nil
}
func (r *RequirementResult) refresh(scope scopes.Result) {
r.Name = r.Requirement.Name
r.Description = r.Requirement.Description
r.Status = r.Requirement.Status
r.StatusName = scope.StatusName(r.Requirement.Status)
}
func (r *RequirementResult) updateStat(stat entities.RequirementStat) bool {
if stat.Required < 0 {
for i, stat2 := range r.Stats {
if stat2.ID == stat.StatID {
r.Stats = append(r.Stats[:i], r.Stats[i+1:]...)
return true
}
}
} else {
for i, stat2 := range r.Stats {
if stat2.ID == stat.StatID {
r.Stats[i].Required = stat.Required
return true
}
}
}
return false
}
type RequirementResultStat struct {
ID int `json:"id"`
Name string `json:"name"`
Weight float64 `json:"weight"`
Acquired int `json:"acquired"`
Required int `json:"required"`
Planned int `json:"planned"`
}
func generateResult(
project entities.Project,
scope scopes.Result,
requirement []entities.Requirement,
requirementStats []entities.RequirementStat,
projectItems []items.Result,
) *Result {
res := Result{
Project: project,
OwnerName: scope.MemberName(project.OwnerID),
StatusName: scope.StatusName(project.Status),
Requirements: make([]RequirementResult, 0, 8),
}
for _, req := range requirement {
if req.ProjectID != project.ID {
continue
}
resReq := generateRequirementResult(req, scope, requirementStats, projectItems)
res.TotalRequired += resReq.TotalRequired
res.TotalPlanned += resReq.TotalPlanned
if req.Status.Ended() {
res.TotalAcquired += resReq.TotalRequired
} else {
res.TotalAcquired += resReq.TotalAcquired
}
res.Requirements = append(res.Requirements, resReq)
}
sort.Slice(res.Requirements, func(i, j int) bool {
ri := res.Requirements[i]
rj := res.Requirements[j]
if ri.Status != rj.Status {
return ri.Status.Less(rj.Status)
}
return ri.ID < rj.ID
})
return &res
}
func generateRequirementResult(req entities.Requirement, scope scopes.Result, requirementStats []entities.RequirementStat, projectItems []items.Result) RequirementResult {
resReq := RequirementResult{
ID: req.ID,
Name: req.Name,
Description: req.Description,
Status: req.Status,
StatusName: scope.StatusName(req.Status),
IsCoarse: req.IsCoarse,
AggregateRequired: req.AggregateRequired,
Stats: make([]RequirementResultStat, 0, 8),
Items: make([]items.Result, 0, 8),
Tags: req.Tags,
Requirement: req,
}
resStats := make(map[int]*RequirementResultStat)
for _, reqStat := range requirementStats {
if reqStat.RequirementID != req.ID {
continue
}
resStat := RequirementResultStat{
ID: reqStat.StatID,
Required: reqStat.Required,
}
for _, stat := range scope.Stats {
if stat.ID == resStat.ID {
resStat.Name = stat.Name
resStat.Weight = stat.Weight
break
}
}
resStats[resStat.ID] = &resStat
}
for _, item := range projectItems {
if item.RequirementID == nil || *item.RequirementID != req.ID {
continue
}
for _, stat := range item.Stats {
if resStats[stat.ID] != nil {
if item.AcquiredTime != nil {
resStats[stat.ID].Acquired += stat.Acquired
}
resStats[stat.ID].Planned += stat.Required
}
}
item.Project = nil
item.Requirement = nil
resReq.Items = append(resReq.Items, item)
}
// Sort items so that they're in order of created, with acquired items being in order of acquired below.
sort.Slice(resReq.Items, func(i, j int) bool {
ii := resReq.Items[i]
ij := resReq.Items[j]
if ii.AcquiredTime != nil && ij.AcquiredTime != nil {
return ii.AcquiredTime.Before(*ij.AcquiredTime)
} else if ii.AcquiredTime != nil && ij.AcquiredTime == nil {
return false
} else if ii.AcquiredTime == nil && ij.AcquiredTime != nil {
return true
}
return ii.CreatedTime.Before(ij.CreatedTime)
})
for _, stat := range scope.Stats {
if rs := resStats[stat.ID]; rs != nil && rs.Required > 0 {
resReq.Stats = append(resReq.Stats, *rs)
}
}
for _, stat := range scope.Stats {
if rs := resStats[stat.ID]; rs != nil && rs.Required == 0 {
resReq.Stats = append(resReq.Stats, *rs)
}
}
hasRequiredStats := false
for _, stat := range resReq.Stats {
if stat.Required > 0 {
hasRequiredStats = true
break
}
}
if hasRequiredStats {
totalAcquired := 0.0
totalRequired := 0.0
totalPlanned := 0.0
for _, stat := range resReq.Stats {
if stat.Required > 0 {
totalAcquired += math.Min(float64(stat.Acquired), float64(stat.Required)) * stat.Weight
totalRequired += float64(stat.Required) * stat.Weight
totalPlanned += math.Min(float64(stat.Planned), float64(stat.Required)) * stat.Weight
}
}
resReq.TotalRequired += totalRequired
resReq.TotalAcquired += math.Min(totalAcquired, totalRequired)
resReq.TotalPlanned += math.Min(totalPlanned, totalRequired)
} else {
for _, item := range resReq.Items {
if item.AcquiredTime != nil {
resReq.TotalAcquired += item.WeightedRequired
}
resReq.TotalRequired += item.WeightedRequired
}
resReq.TotalPlanned = resReq.TotalRequired
if req.AggregateRequired > 0 {
resReq.TotalRequired = float64(req.AggregateRequired)
}
}
if projectItems == nil {
resReq.Items = nil
}
return resReq
}