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.

307 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' => 'bail|nullable|numeric',
  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(Tag::class)->using(ReportableRelation::class);
  145. }
  146. // Todo: are we need this relation????
  147. public function tagTask()
  148. {
  149. return $this->hasMany(TagTask::class, 'task_id', 'id');
  150. }
  151. public function works()
  152. {
  153. return $this->hasMany(Work::class, 'task_id', 'id');
  154. }
  155. public function comments()
  156. {
  157. return $this->hasMany(Comment::class, 'task_id', 'id');
  158. }
  159. public function scopePriorityMin($query, $min)
  160. {
  161. return $query->where('tasks.priority', '>=', $min);
  162. }
  163. public function scopePriorityMax($query, $max)
  164. {
  165. return $query->where('tasks.priority', '<=', $max);
  166. }
  167. public function scopeCreatesBefore($query, $date)
  168. {
  169. //ToDo: comment lines have better performance but should be test
  170. // return $query->whereDate('tasks.created_at', '<=', Carbon::parse($date.' 23:59:59'));
  171. // return $query->where('tasks.created_at', '<', (new DateTime('2014-07-10'))->modify('+1 day')->format('Y-m-d'));
  172. return $query->whereDate('tasks.created_at', '<=', Carbon::parse($date));
  173. }
  174. public function scopeCreatesAfter($query, $date)
  175. {
  176. return $query->whereDate('tasks.created_at', '>=', Carbon::parse($date));
  177. }
  178. public function scopeCreatesIn($query, $days)
  179. {
  180. return $query->whereDate('tasks.created_at', '>=', Carbon::now()->modify('-'.$days.' day')->toDate());
  181. }
  182. public function scopeUpdatesBefore($query, $date)
  183. {
  184. return $query->whereDate('tasks.updated_at', '<=', Carbon::parse($date));
  185. }
  186. public function scopeUpdatesAfter($query, $date)
  187. {
  188. return $query->whereDate('tasks.updated_at', '>=', Carbon::parse($date));
  189. }
  190. public function scopeUpdatesIn($query, $days)
  191. {
  192. return $query->whereDate('tasks.updated_at', '>=', Carbon::now()->modify('-'.$days.' day')->toDate());
  193. }
  194. public function scopeSpentFrom($query, $minute)
  195. {
  196. return $query->where('tasks.spent_time', '>=', $minute);
  197. }
  198. public function scopeSpentTo($query, $minute)
  199. {
  200. return $query->where('tasks.spent_time', '<=', $minute);
  201. }
  202. public function scopeEstimatedFrom($query, $minute)
  203. {
  204. return $query->where('tasks.estimated_time', '>=', $minute);
  205. }
  206. public function scopeEstimatedTo($query, $minute)
  207. {
  208. return $query->where('tasks.estimated_time', '<=', $minute);
  209. }
  210. public function scopeStartsBefore($query, $date)
  211. {
  212. return $query->whereDate('tasks.work_start', '<=', Carbon::parse($date));
  213. }
  214. public function scopeStartsAfter($query, $date)
  215. {
  216. return $query->whereDate('tasks.work_start', '>=', Carbon::parse($date));
  217. }
  218. public function scopeStartsIn($query, $days)
  219. {
  220. return $query->whereDate('tasks.work_start', '>=', Carbon::now()->modify('-'.$days.' day')->toDate());
  221. }
  222. public function scopeFinishBefore($query, $date)
  223. {
  224. return $query->whereDate('tasks.work_finish', '<=', Carbon::parse($date));
  225. }
  226. public function scopeFinishAfter($query, $date)
  227. {
  228. return $query->whereDate('tasks.work_finish', '>=', Carbon::parse($date));
  229. }
  230. public function scopeFinishIn($query, $days)
  231. {
  232. return $query->whereDate('tasks.work_finish', '>=', Carbon::now()->modify('-'.$days.' day')->toDate());
  233. }
  234. public function scopeCompletesBefore($query, $date)
  235. {
  236. return $query->where('tasks.completed_at', '<=', $date);
  237. }
  238. public function scopeCompletesAfter($query, $date)
  239. {
  240. return $query->where('tasks.completed_at', '>=', $date);
  241. }
  242. public function scopeCompletesIn($query, $days)
  243. {
  244. return $query->whereDate('tasks.completed_at', '>=', Carbon::now()->modify('-'.$days.' day')->toDate());
  245. }
  246. public function scopeDueDateBefore($query, $date)
  247. {
  248. return $query->where('tasks.due_date', '<=', $date);
  249. }
  250. public function scopeDueDateAfter($query, $date)
  251. {
  252. return $query->where('tasks.due_date', '>=', $date);
  253. }
  254. public function scopeDueDateIn($query, $days)
  255. {
  256. return $query->whereDate('tasks.due_date', '>=', Carbon::now()->modify('-'.$days.' day')->toDate());
  257. }
  258. public function scopeOverSpentFrom($query, $minute)
  259. {
  260. return $query->whereColumn('spent_time', '>', 'estimated_time')
  261. ->having('over_spent', '>=', $minute);
  262. }
  263. public function scopeOverSpentTo($query, $minute)
  264. {
  265. return $query->whereColumn('spent_time', '>', 'estimated_time')
  266. ->having('over_spent', '<=', $minute);
  267. }
  268. public function scopeMyWatching($query)
  269. {
  270. return $query->whereJsonContains('watchers', auth()->id());
  271. }
  272. public function scopeOverDue($query)
  273. {
  274. return $query->whereColumn('due_date', '<', 'completed_at');
  275. }
  276. public function scopeReport($query, $group_field)
  277. {
  278. return $query->select(DB::raw($group_field.' , status_id, COUNT(*) as total, SUM(spent_time) as spent_sum,
  279. SUM(estimated_time) as estimated_sum,SUM(ready_to_test) as test_sum,
  280. COUNT(completed_at) as completed_total, SUM(on_time) as on_time_total,
  281. SUM(Case When spent_time > estimated_time Then (spent_time - estimated_time) Else 0 End) as over_spent_sum,
  282. SUM(Case When (spent_time <= estimated_time) And (completed_at) Then (estimated_time - spent_time) Else 0 End)
  283. as under_spent_sum'))
  284. ->groupBy($group_field, 'status_id');
  285. }
  286. }