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.

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