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.

308 lines
12 KiB

  1. <?php
  2. namespace App\Models;
  3. use App\Models\ReportableRelation;
  4. use Carbon\Carbon;
  5. use App\Models\Tag;
  6. use App\Models\User;
  7. use App\Models\Work;
  8. use App\Models\Model;
  9. use App\Models\Comment;
  10. use App\Models\Project;
  11. use App\Models\TagTask;
  12. use App\Models\Business;
  13. use Illuminate\Validation\Rule;
  14. use Illuminate\Support\Facades\DB;
  15. use Illuminate\Validation\Rules\RequiredIf;
  16. use Illuminate\Database\Eloquent\Factories\HasFactory;
  17. class Task extends Model
  18. {
  19. use HasFactory;
  20. protected $fillable = [
  21. 'business_id', 'project_id', 'creator_id', 'workflow_id', 'assignee_id', 'system_id', 'sprint_id',
  22. 'status_id', 'approver_id', 'title', 'description', 'priority', 'on_time', 'estimated_time', 'due_date', 'completed_at',
  23. 'work_start', 'spent_time', 'work_finish', 'ready_to_test', 'watchers', 'tags'
  24. ];
  25. protected $reportable = [
  26. 'priority', 'title', 'description', 'priority', 'on_time', 'estimated_time', 'due_date', 'completed_at',
  27. 'work_finish', 'ready_to_test', 'assignee_id', 'approver_id', ['tags' => 'tag_task']
  28. ];
  29. protected $fillable_relations = [
  30. 'tags'
  31. ];
  32. protected $casts = [
  33. 'work_start' => 'datetime:Y-m-d H:i',
  34. 'work_finish' => 'datetime:Y-m-d H:i',
  35. 'watchers' => 'array',
  36. ];
  37. public function rules()
  38. {
  39. $validations = [
  40. 'assignee_id' => ['nullable', 'numeric',
  41. function ($attribute, $value, $fail) {
  42. //check assignee at least guest in project
  43. if (!can('isProjectGuest', ['project_id' => request()->route('project'), 'user_id' => $value])) {
  44. $fail('The selected '.$attribute.' is invalid.');
  45. }
  46. if (request()->method() === 'PUT' && ($this->assignee_id != $value && Work::where('task_id', $this->id)->exists())) {
  47. //when update execute
  48. //check if change assignee then should be on task not work set
  49. $fail('The selected '.$attribute.' is invalid.');
  50. }
  51. }],
  52. 'system_id' => ['nullable', 'numeric',
  53. Rule::in(\request('_business_info')['info']['projects'][request()->route('project')]['systems'])],
  54. 'sprint_id' => ['nullable', 'numeric',
  55. Rule::in(\request('_business_info')['info']['projects'][request()->route('project')]['sprints'])],
  56. 'workflow_id' => ['required', 'numeric',
  57. Rule::in(array_keys(\request('_business_info')['info']['workflows']))],
  58. 'status_id' => 'required|numeric',
  59. 'approver_id' => ['nullable', 'numeric',
  60. function ($attribute, $value, $fail) {
  61. //check approval at least colleague in project
  62. if (!can('isProjectColleague', ['project_id' => request()->route('project'), 'user_id' => $value])) {
  63. $fail('The selected '.$attribute.' is invalid.');
  64. }
  65. }],
  66. 'title' => 'required|string|min:3|max:254',
  67. 'description' => 'nullable|string|min:2|max:1000',
  68. 'priority' => 'nullable|numeric|between:1,10',
  69. 'estimated_time' => 'nullable|numeric|min:30',
  70. 'due_date' => 'bail|nullable|date|date_format:Y-m-d|after_or_equal:'.
  71. ((request()->method() === 'POST') ? date('yy-m-d') : $this->created_at->toDateString()),
  72. 'tags' => 'nullable|array',
  73. 'tags.*' => [new RequiredIf(isset($request->tags)), 'numeric',
  74. Rule::in(\request('_business_info')['info']['tags'])],
  75. ] ;
  76. $workflow_id = $this->workflow_id;
  77. if (request()->filled('workflow_id') && isset(request('_business_info')['info']['workflows'][request()->workflow_id])) {
  78. $workflow_id = request()->workflow_id;
  79. }
  80. if (isset($workflow_id)) {
  81. $validations['status_id'] = ['bail', 'required', 'numeric',
  82. //check status exists in status list
  83. Rule::in(\request('_business_info')['info']['workflows'][$workflow_id]['statuses']),
  84. function ($attribute, $value, $fail) use ($workflow_id) {
  85. //check not close or done
  86. $state = \request('_business_info')['workflows'][$workflow_id]['statuses'][$value]['state'];
  87. if (request()->method() == 'POST' && ($state == enum('status.states.close.id') || $state == enum('status.states.done.id'))) {
  88. $fail('The selected '.$attribute.' is invalid.');
  89. }
  90. if ((request()->method() == 'PUT') && !can('projectTasks', ['project_id' => request()->route('project')])
  91. && $state != enum('status.states.active.id')) {
  92. $fail('The selected '.$attribute.' is invalid.');
  93. }
  94. }];
  95. }
  96. return $validations;
  97. }
  98. public function updateRelations()
  99. {
  100. // tags relations
  101. $this->dirties['tags'] = $this->tags()->sync($this->filled_relations['tags']);
  102. //old code
  103. // if (!empty($this->filled_relations['tags'])) {
  104. // $this->dirties['tags'] = $this->tags()->sync($this->filled_relations['tags']);
  105. // }
  106. }
  107. public function getValueOf(?string $key)
  108. {
  109. $values = [
  110. 'business_id' => $this->business_id,
  111. 'project_id' => $this->project_id,
  112. 'sprint_id' => $this->sprint_id,
  113. 'workflow_id' => $this->workflow_id,
  114. 'status_id' => $this->status_id,
  115. 'system_id' => $this->system_id,
  116. 'task_id' => $this->id,
  117. 'subject_id' => $this->id,
  118. 'user_id' => $this->assignee_id,
  119. ];
  120. if ($key && isset($values, $key)) {
  121. return $values[$key];
  122. }
  123. return $values;
  124. }
  125. public function business()
  126. {
  127. return $this->belongsTo(Business::class, 'business_id', 'id', __FUNCTION__);
  128. }
  129. public function creator()
  130. {
  131. return $this->belongsTo(User::class, 'creator_id', 'id', __FUNCTION__);
  132. }
  133. public function user()
  134. {
  135. return $this->belongsTo(User::class, 'user_id', 'id', __FUNCTION__);
  136. }
  137. public function project()
  138. {
  139. return $this->belongsTo(Project::class, 'project_id', 'id', __FUNCTION__);
  140. }
  141. public function tags()
  142. {
  143. return $this->belongsToMany(
  144. Tag::class, 'tag_task', 'task_id', 'tag_id',
  145. 'id', 'id', __FUNCTION__
  146. )->using(ReportableRelation::class);
  147. }
  148. public function tagTask()
  149. {
  150. return $this->hasMany(TagTask::class, 'task_id', 'id');
  151. }
  152. public function works()
  153. {
  154. return $this->hasMany(Work::class, 'task_id', 'id');
  155. }
  156. public function comments()
  157. {
  158. return $this->hasMany(Comment::class, 'task_id', 'id');
  159. }
  160. public function scopePriorityMin($query, $min)
  161. {
  162. return $query->where('tasks.priority', '>=', $min);
  163. }
  164. public function scopePriorityMax($query, $max)
  165. {
  166. return $query->where('tasks.priority', '<=', $max);
  167. }
  168. public function scopeCreatesBefore($query, $date)
  169. {
  170. //ToDo: comment lines have better performance but should be test
  171. // return $query->whereDate('tasks.created_at', '<=', Carbon::parse($date.' 23:59:59'));
  172. // return $query->where('tasks.created_at', '<', (new DateTime('2014-07-10'))->modify('+1 day')->format('Y-m-d'));
  173. return $query->whereDate('tasks.created_at', '<=', Carbon::parse($date));
  174. }
  175. public function scopeCreatesAfter($query, $date)
  176. {
  177. return $query->whereDate('tasks.created_at', '>=', Carbon::parse($date));
  178. }
  179. public function scopeCreatesIn($query, $days)
  180. {
  181. return $query->whereDate('tasks.created_at', '>=', Carbon::now()->modify('-'.$days.' day')->toDate());
  182. }
  183. public function scopeUpdatesBefore($query, $date)
  184. {
  185. return $query->whereDate('tasks.updated_at', '<=', Carbon::parse($date));
  186. }
  187. public function scopeUpdatesAfter($query, $date)
  188. {
  189. return $query->whereDate('tasks.updated_at', '>=', Carbon::parse($date));
  190. }
  191. public function scopeUpdatesIn($query, $days)
  192. {
  193. return $query->whereDate('tasks.updated_at', '>=', Carbon::now()->modify('-'.$days.' day')->toDate());
  194. }
  195. public function scopeSpentFrom($query, $minute)
  196. {
  197. return $query->where('tasks.spent_time', '>=', $minute);
  198. }
  199. public function scopeSpentTo($query, $minute)
  200. {
  201. return $query->where('tasks.spent_time', '<=', $minute);
  202. }
  203. public function scopeEstimatedFrom($query, $minute)
  204. {
  205. return $query->where('tasks.estimated_time', '>=', $minute);
  206. }
  207. public function scopeEstimatedTo($query, $minute)
  208. {
  209. return $query->where('tasks.estimated_time', '<=', $minute);
  210. }
  211. public function scopeStartsBefore($query, $date)
  212. {
  213. return $query->whereDate('tasks.work_start', '<=', Carbon::parse($date));
  214. }
  215. public function scopeStartsAfter($query, $date)
  216. {
  217. return $query->whereDate('tasks.work_start', '>=', Carbon::parse($date));
  218. }
  219. public function scopeStartsIn($query, $days)
  220. {
  221. return $query->whereDate('tasks.work_start', '>=', Carbon::now()->modify('-'.$days.' day')->toDate());
  222. }
  223. public function scopeFinishBefore($query, $date)
  224. {
  225. return $query->whereDate('tasks.work_finish', '<=', Carbon::parse($date));
  226. }
  227. public function scopeFinishAfter($query, $date)
  228. {
  229. return $query->whereDate('tasks.work_finish', '>=', Carbon::parse($date));
  230. }
  231. public function scopeFinishIn($query, $days)
  232. {
  233. return $query->whereDate('tasks.work_finish', '>=', Carbon::now()->modify('-'.$days.' day')->toDate());
  234. }
  235. public function scopeCompletesBefore($query, $date)
  236. {
  237. return $query->where('tasks.completed_at', '<=', $date);
  238. }
  239. public function scopeCompletesAfter($query, $date)
  240. {
  241. return $query->where('tasks.completed_at', '>=', $date);
  242. }
  243. public function scopeCompletesIn($query, $days)
  244. {
  245. return $query->whereDate('tasks.completed_at', '>=', Carbon::now()->modify('-'.$days.' day')->toDate());
  246. }
  247. public function scopeDueDateBefore($query, $date)
  248. {
  249. return $query->where('tasks.due_date', '<=', $date);
  250. }
  251. public function scopeDueDateAfter($query, $date)
  252. {
  253. return $query->where('tasks.due_date', '>=', $date);
  254. }
  255. public function scopeDueDateIn($query, $days)
  256. {
  257. return $query->whereDate('tasks.due_date', '>=', Carbon::now()->modify('-'.$days.' day')->toDate());
  258. }
  259. public function scopeOverSpentFrom($query, $minute)
  260. {
  261. return $query->whereColumn('spent_time', '>', 'estimated_time')
  262. ->having('over_spent', '>=', $minute);
  263. }
  264. public function scopeOverSpentTo($query, $minute)
  265. {
  266. return $query->whereColumn('spent_time', '>', 'estimated_time')
  267. ->having('over_spent', '<=', $minute);
  268. }
  269. public function scopeMyWatching($query)
  270. {
  271. return $query->whereJsonContains('watchers', auth()->id());
  272. }
  273. public function scopeOverDue($query)
  274. {
  275. return $query->whereColumn('due_date', '<', 'completed_at');
  276. }
  277. public function scopeReport($query, $group_field)
  278. {
  279. return $query->select(DB::raw($group_field.' , status_id, COUNT(*) as total, SUM(spent_time) as spent_sum,
  280. SUM(estimated_time) as estimated_sum,SUM(ready_to_test) as test_sum,
  281. COUNT(completed_at) as completed_total, SUM(on_time) as on_time_total,
  282. SUM(Case When spent_time > estimated_time Then (spent_time - estimated_time) Else 0 End) as over_spent_sum,
  283. SUM(Case When (spent_time <= estimated_time) And (completed_at) Then (estimated_time - spent_time) Else 0 End)
  284. as under_spent_sum'))
  285. ->groupBy($group_field, 'status_id');
  286. }
  287. }