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

2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
  1. package projects
  2. import (
  3. "git.aiterp.net/stufflog3/stufflog3/entities"
  4. "git.aiterp.net/stufflog3/stufflog3/models"
  5. "git.aiterp.net/stufflog3/stufflog3/usecases/items"
  6. "git.aiterp.net/stufflog3/stufflog3/usecases/scopes"
  7. "math"
  8. "sort"
  9. "time"
  10. )
  11. type Entry struct {
  12. ID int `json:"id"`
  13. OwnerID string `json:"ownerId"`
  14. CreatedTime time.Time `json:"createdTime"`
  15. Name string `json:"name"`
  16. Status models.Status `json:"status"`
  17. StatusName string `json:"statusName"`
  18. OwnerName string `json:"ownerName,omitempty"`
  19. Tags []string `json:"tags"`
  20. }
  21. func generateEntry(project entities.Project, scope scopes.Result) Entry {
  22. return Entry{
  23. ID: project.ID,
  24. OwnerID: project.OwnerID,
  25. CreatedTime: project.CreatedTime,
  26. Name: project.Name,
  27. Status: project.Status,
  28. Tags: project.Tags,
  29. StatusName: scope.StatusName(project.Status),
  30. OwnerName: scope.MemberName(project.OwnerID),
  31. }
  32. }
  33. type Result struct {
  34. entities.Project
  35. OwnerName string `json:"ownerName"`
  36. StatusName string `json:"statusName"`
  37. TotalAcquired float64 `json:"totalAcquired"`
  38. TotalRequired float64 `json:"totalRequired"`
  39. TotalPlanned float64 `json:"totalPlanned"`
  40. Requirements []RequirementResult `json:"requirements"`
  41. }
  42. func (r *Result) Requirement(id int) *RequirementResult {
  43. for _, requirement := range r.Requirements {
  44. if id == requirement.ID {
  45. return &requirement
  46. }
  47. }
  48. return nil
  49. }
  50. type RequirementResult struct {
  51. ID int `json:"id"`
  52. Name string `json:"name"`
  53. Description string `json:"description"`
  54. Status models.Status `json:"status"`
  55. StatusName string `json:"statusName"`
  56. TotalAcquired float64 `json:"totalAcquired"`
  57. TotalRequired float64 `json:"totalRequired"`
  58. TotalPlanned float64 `json:"totalPlanned"`
  59. IsCoarse bool `json:"isCoarse"`
  60. AggregateRequired int `json:"aggregateRequired"`
  61. Stats []RequirementResultStat `json:"stats"`
  62. Items []items.Result `json:"items,omitempty"`
  63. Tags []string `json:"tags"`
  64. Requirement entities.Requirement `json:"-"`
  65. }
  66. func (r *RequirementResult) Stat(id int) *RequirementResultStat {
  67. for _, stat := range r.Stats {
  68. if stat.ID == id {
  69. return &stat
  70. }
  71. }
  72. return nil
  73. }
  74. func (r *RequirementResult) refresh(scope scopes.Result) {
  75. r.Name = r.Requirement.Name
  76. r.Description = r.Requirement.Description
  77. r.Status = r.Requirement.Status
  78. r.StatusName = scope.StatusName(r.Requirement.Status)
  79. }
  80. func (r *RequirementResult) updateStat(stat entities.RequirementStat) bool {
  81. if stat.Required < 0 {
  82. for i, stat2 := range r.Stats {
  83. if stat2.ID == stat.StatID {
  84. r.Stats = append(r.Stats[:i], r.Stats[i+1:]...)
  85. return true
  86. }
  87. }
  88. } else {
  89. for i, stat2 := range r.Stats {
  90. if stat2.ID == stat.StatID {
  91. r.Stats[i].Required = stat.Required
  92. return true
  93. }
  94. }
  95. }
  96. return false
  97. }
  98. type RequirementResultStat struct {
  99. ID int `json:"id"`
  100. Name string `json:"name"`
  101. Weight float64 `json:"weight"`
  102. Acquired int `json:"acquired"`
  103. Required int `json:"required"`
  104. Planned int `json:"planned"`
  105. }
  106. func generateResult(
  107. project entities.Project,
  108. scope scopes.Result,
  109. requirement []entities.Requirement,
  110. requirementStats []entities.RequirementStat,
  111. projectItems []items.Result,
  112. ) *Result {
  113. res := Result{
  114. Project: project,
  115. OwnerName: scope.MemberName(project.OwnerID),
  116. StatusName: scope.StatusName(project.Status),
  117. Requirements: make([]RequirementResult, 0, 8),
  118. }
  119. for _, req := range requirement {
  120. if req.ProjectID != project.ID {
  121. continue
  122. }
  123. resReq := generateRequirementResult(req, scope, requirementStats, projectItems)
  124. res.TotalRequired += resReq.TotalRequired
  125. res.TotalPlanned += resReq.TotalPlanned
  126. if req.Status.Ended() {
  127. res.TotalAcquired += resReq.TotalRequired
  128. } else {
  129. res.TotalAcquired += resReq.TotalAcquired
  130. }
  131. res.Requirements = append(res.Requirements, resReq)
  132. }
  133. sort.Slice(res.Requirements, func(i, j int) bool {
  134. ri := res.Requirements[i]
  135. rj := res.Requirements[j]
  136. if ri.Status != rj.Status {
  137. return ri.Status.Less(rj.Status)
  138. }
  139. return ri.ID < rj.ID
  140. })
  141. return &res
  142. }
  143. func generateRequirementResult(req entities.Requirement, scope scopes.Result, requirementStats []entities.RequirementStat, projectItems []items.Result) RequirementResult {
  144. resReq := RequirementResult{
  145. ID: req.ID,
  146. Name: req.Name,
  147. Description: req.Description,
  148. Status: req.Status,
  149. StatusName: scope.StatusName(req.Status),
  150. IsCoarse: req.IsCoarse,
  151. AggregateRequired: req.AggregateRequired,
  152. Stats: make([]RequirementResultStat, 0, 8),
  153. Items: make([]items.Result, 0, 8),
  154. Tags: req.Tags,
  155. Requirement: req,
  156. }
  157. resStats := make(map[int]*RequirementResultStat)
  158. for _, reqStat := range requirementStats {
  159. if reqStat.RequirementID != req.ID {
  160. continue
  161. }
  162. resStat := RequirementResultStat{
  163. ID: reqStat.StatID,
  164. Required: reqStat.Required,
  165. }
  166. for _, stat := range scope.Stats {
  167. if stat.ID == resStat.ID {
  168. resStat.Name = stat.Name
  169. resStat.Weight = stat.Weight
  170. break
  171. }
  172. }
  173. resStats[resStat.ID] = &resStat
  174. }
  175. for _, item := range projectItems {
  176. if item.RequirementID == nil || *item.RequirementID != req.ID {
  177. continue
  178. }
  179. for _, stat := range item.Stats {
  180. if resStats[stat.ID] != nil {
  181. if item.AcquiredTime != nil {
  182. resStats[stat.ID].Acquired += stat.Acquired
  183. }
  184. resStats[stat.ID].Planned += stat.Required
  185. }
  186. }
  187. item.Project = nil
  188. item.Requirement = nil
  189. resReq.Items = append(resReq.Items, item)
  190. }
  191. // Sort items so that they're in order of created, with acquired items being in order of acquired below.
  192. sort.Slice(resReq.Items, func(i, j int) bool {
  193. ii := resReq.Items[i]
  194. ij := resReq.Items[j]
  195. if ii.AcquiredTime != nil && ij.AcquiredTime != nil {
  196. return ii.AcquiredTime.Before(*ij.AcquiredTime)
  197. } else if ii.AcquiredTime != nil && ij.AcquiredTime == nil {
  198. return false
  199. } else if ii.AcquiredTime == nil && ij.AcquiredTime != nil {
  200. return true
  201. }
  202. return ii.CreatedTime.Before(ij.CreatedTime)
  203. })
  204. for _, stat := range scope.Stats {
  205. if rs := resStats[stat.ID]; rs != nil && rs.Required > 0 {
  206. resReq.Stats = append(resReq.Stats, *rs)
  207. }
  208. }
  209. for _, stat := range scope.Stats {
  210. if rs := resStats[stat.ID]; rs != nil && rs.Required == 0 {
  211. resReq.Stats = append(resReq.Stats, *rs)
  212. }
  213. }
  214. hasRequiredStats := false
  215. for _, stat := range resReq.Stats {
  216. if stat.Required > 0 {
  217. hasRequiredStats = true
  218. break
  219. }
  220. }
  221. if hasRequiredStats {
  222. totalAcquired := 0.0
  223. totalRequired := 0.0
  224. totalPlanned := 0.0
  225. for _, stat := range resReq.Stats {
  226. if stat.Required > 0 {
  227. totalAcquired += math.Min(float64(stat.Acquired), float64(stat.Required)) * stat.Weight
  228. totalRequired += float64(stat.Required) * stat.Weight
  229. totalPlanned += math.Min(float64(stat.Planned), float64(stat.Required)) * stat.Weight
  230. }
  231. }
  232. resReq.TotalRequired += totalRequired
  233. resReq.TotalAcquired += math.Min(totalAcquired, totalRequired)
  234. resReq.TotalPlanned += math.Min(totalPlanned, totalRequired)
  235. } else {
  236. for _, item := range resReq.Items {
  237. if item.AcquiredTime != nil {
  238. resReq.TotalAcquired += item.WeightedRequired
  239. }
  240. resReq.TotalRequired += item.WeightedRequired
  241. }
  242. resReq.TotalPlanned = resReq.TotalRequired
  243. if req.AggregateRequired > 0 {
  244. resReq.TotalRequired = float64(req.AggregateRequired)
  245. }
  246. }
  247. if projectItems == nil {
  248. resReq.Items = nil
  249. }
  250. return resReq
  251. }