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.

242 lines
10 KiB

  1. <?php
  2. namespace App\Http\Controllers;
  3. use Carbon\Carbon;
  4. use App\Models\Task;
  5. use App\Models\Work;
  6. use Illuminate\Http\Request;
  7. use Illuminate\Http\Response;
  8. use Spatie\QueryBuilder\QueryBuilder;
  9. use Spatie\QueryBuilder\AllowedFilter;
  10. class WorkController extends Controller
  11. {
  12. public function index($business, Request $request)
  13. {
  14. permit('businessAccess');
  15. $per_page = $request->limit > 100 ? 10 : $request->limit;
  16. $workQ = $this->indexFiltering($business)
  17. ->when($request->filled('group'), function ($q) use ($request) {
  18. return $request->group == 'user' ? $q->report() : $q->reportByDate();
  19. });
  20. return $request->filled('group') ? $workQ->get() :
  21. $workQ->defaultSort('-id')
  22. ->allowedSorts('id', 'started_at')->paginate($per_page);
  23. }
  24. public function indexFiltering($business)
  25. {
  26. $query = Work::where('works.business_id', $business);
  27. $workQ = queryBuilder::for($query)
  28. ->join('tasks', 'tasks.id', 'works.task_id')
  29. ->select('works.*', 'tasks.title', 'tasks.sprint_id', 'tasks.system_id')
  30. ->allowedFilters([
  31. AllowedFilter::exact('project_id'),
  32. AllowedFilter::exact('tasks.sprint_id', null, false),
  33. AllowedFilter::exact('tasks.system_id', null, false),
  34. AllowedFilter::exact('user_id'),
  35. AllowedFilter::scope('started_at_in'),
  36. AllowedFilter::scope('started_at'),
  37. AllowedFilter::scope('spent_time_from'),
  38. AllowedFilter::scope('spent_time_to'),
  39. ]);
  40. if (\request('_business_info')['info']['users'][\auth()->id()]['level'] != enum('levels.owner.id')) {
  41. $requested_projects = isset(\request('filter')['project_id']) ?
  42. array_unique(explode(',',\request('filter')['project_id'] ?? null )) :
  43. null;
  44. $requested_projects = collect($requested_projects)->keyBy(null)->toArray();
  45. $project_ids = $this->myStateProjects($requested_projects);
  46. $workQ->where(function ($q) use ($project_ids) {
  47. $q->whereIn('project_id', $project_ids['non_guest_ids'])
  48. ->orWhere(function ($q) use ($project_ids) {
  49. $q->whereIn('project_id', $project_ids['guest_ids'])
  50. ->where('assignee_id', auth()->id());
  51. });
  52. });
  53. }
  54. return $workQ;
  55. }
  56. public function myStateProjects($requested_projects)
  57. {
  58. $non_guest_ids = [];
  59. $guest_ids = [];
  60. $is_empty = empty($requested_projects);
  61. foreach (\request('_business_info')['info']['projects'] as $p_id => $p) {
  62. $level = \request('_business_info')['info']['projects'][$p_id]['members'][\auth()->id()]['level'];
  63. if (( $is_empty || isset($requested_projects[$p_id]))
  64. && $level > enum('levels.guest.id')) {
  65. array_push($non_guest_ids, $p_id);
  66. }
  67. if (( $is_empty || isset($requested_projects[$p_id]))
  68. && $level == enum('levels.guest.id')) {
  69. array_push($guest_ids, $p_id);
  70. }
  71. }
  72. return ['non_guest_ids' => $non_guest_ids, 'guest_ids' => $guest_ids];
  73. }
  74. public function show($business, $project, $task, $work)
  75. {
  76. permit('projectAccess', ['project_id' => $project]);
  77. $work = Work::where([['project_id', $project ], ['task_id', $task], ['id', $work]])->firstOrFail();
  78. if (can('isDefiniteGuestInProject', ['project_id' => $project])){ // is guest in project (only guest)
  79. return $work->user_id == \auth()->id() ? $work : abort(Response::HTTP_FORBIDDEN); // not allowed
  80. } else {
  81. return $work;
  82. }
  83. }
  84. /**
  85. * Rule's:
  86. * 1) only assignee_id can store work
  87. * 2) started_at after task created_at
  88. * 3) ended_at after started_at
  89. * 4) not any work before in this work
  90. */
  91. public function store($business, $project, $task, Request $request)
  92. {
  93. $taskModel = Task::findOrFail($task);
  94. if ($taskModel->assignee_id != auth()->id()) {
  95. abort(Response::HTTP_FORBIDDEN); // not allowed
  96. }
  97. $end = Carbon::createFromFormat('Y-m-d H:i', $request->ended_at);
  98. $start = Carbon::createFromFormat('Y-m-d H:i', $request->started_at);
  99. $diff_in_min = $end->diffInMinutes($start);
  100. $work = Work::create($request->merge([
  101. 'business_id' => $business,
  102. 'project_id' => $project,
  103. 'task_id' => $task,
  104. 'user_id' => auth()->id(),
  105. 'minute_sum' => $diff_in_min,
  106. 'task' => [
  107. 'spent_time' => $taskModel->spent_time + $diff_in_min
  108. ]
  109. ])->except('_business_info'));
  110. $taskModel->refresh();
  111. // $taskModel->update([
  112. // 'work_start' => Work::where('task_id', $taskModel->id)->orderBy('started_at')->first()->started_at ?? null,
  113. // 'spent_time' => $taskModel->spent_time + $diff_in_min
  114. // ]);
  115. return $taskModel->load(['tagTask'=> fn($q) => $q->select('id', 'tag_id', 'task_id'), 'works', 'comments']);
  116. }
  117. public function storeValidation($request, $taskModel)
  118. {
  119. $this->validate($request, [
  120. 'message' => 'nullable|string|min:3|max:225',
  121. 'started_at' => 'required|date_format:Y-m-d H:i|after:'.$taskModel->created_at,
  122. 'ended_at' => 'required|date_format:Y-m-d H:i|after:started_at',
  123. ]);
  124. $state = \request('_business_info')['workflows'][$taskModel->workflow_id]['statuses'][$taskModel->status_id]['state'] ?? null;
  125. if ($state == enum('status.states.close.id') || $state == enum('status.states.done.id')) {
  126. throw ValidationException::withMessages(['task' => 'The selected task is invalid.']);
  127. }
  128. $works = Work::where([
  129. ['ended_at', '>', $request->started_at],
  130. ['ended_at', '<', $request->ended_at],
  131. ])->orWhere([
  132. ['started_at', '>', $request->started_at],
  133. ['started_at', '<', $request->ended_at],
  134. ])->orWhere([
  135. ['started_at', '>=', $request->started_at],
  136. ['ended_at', '<=', $request->ended_at],
  137. ])->exists();
  138. if ($works) {
  139. throw ValidationException::withMessages(['work' => 'The selected work is invalid.']);
  140. }
  141. }
  142. /**
  143. * Rule's:
  144. * 1) only assignee_id can store work
  145. * 2) started_at after task created_at
  146. * 3) ended_at after started_at
  147. * 4) not any work before in this work
  148. */
  149. public function update($business, $project, $task, $work, Request $request)
  150. {
  151. $taskModel = Task::findOrFail($task);
  152. $workModel = Work::findOrFail($work);
  153. if ($taskModel->assignee_id != auth()->id()) {
  154. abort(Response::HTTP_FORBIDDEN); // not allowed
  155. }
  156. $end = Carbon::createFromFormat('Y-m-d H:i', $request->ended_at);
  157. $start = Carbon::createFromFormat('Y-m-d H:i', $request->started_at);
  158. $new_diff_in_min = $end->diffInMinutes($start);
  159. $old_diff_in_min = $workModel->minute_sum;
  160. $workModel->update($request->merge([
  161. 'business_id' => $business,
  162. 'project_id' => $project,
  163. 'task_id' => $task,
  164. 'user_id' => auth()->id(),
  165. 'minute_sum' => $new_diff_in_min,
  166. 'task' => [
  167. 'spent_time' => ($taskModel->spent_time - $old_diff_in_min) + $new_diff_in_min
  168. ]
  169. ])->except('_business_info'));
  170. $taskModel->refresh();
  171. // $taskModel->update([
  172. // 'work_start' => Work::where('task_id', $taskModel->id)->orderBy('started_at')->first()->started_at ?? null,
  173. // 'spent_time' => ($taskModel->spent_time - $old_diff_in_min) + $new_diff_in_min
  174. // ]);
  175. return $taskModel->load(['tagTask'=> fn($q) => $q->select('id', 'tag_id', 'task_id'), 'works', 'comments']);
  176. }
  177. public function updateValidation($request, $taskModel, $workModel)
  178. {
  179. $this->validate($request, [
  180. 'message' => 'nullable|string|min:3|max:225',
  181. 'started_at' => 'nullable|date_format:Y-m-d H:i|after:'.$taskModel->created_at,
  182. 'ended_at' => 'nullable|date_format:Y-m-d H:i|after:started_at',
  183. ]);
  184. //ToDo: is needed to check status is active or idea??
  185. $works = false;
  186. if ($request->filled('started_at') || $request->filled('ended_at')) {
  187. $started_at = $request->started_at ?? $workModel->started_at->format('Y-m-d H:i');
  188. $ended_at = $request->ended_at ?? $workModel->ended_at->format('Y-m-d H:i');
  189. if (strtotime($ended_at) <= strtotime($started_at)) {
  190. throw ValidationException::withMessages(['ended_at' => 'The ended at must be a date after started at.']);
  191. }
  192. $works = Work::where([
  193. ['ended_at', '>', $started_at],
  194. ['ended_at', '<', $ended_at],
  195. ])->orWhere([
  196. ['started_at', '>', $started_at],
  197. ['started_at', '<', $ended_at],
  198. ])->orWhere([
  199. ['started_at', '>=', $started_at],
  200. ['ended_at', '<=', $ended_at],
  201. ])->where('id', '!=', $workModel->id)->exists();
  202. $end = Carbon::createFromFormat('Y-m-d H:i', $ended_at);
  203. $start = Carbon::createFromFormat('Y-m-d H:i', $started_at);
  204. \request()->merge(['minute_sum' => $end->diffInMinutes($start)]);
  205. }
  206. if ($works) {
  207. throw ValidationException::withMessages(['work' => 'The selected work is invalid.']);
  208. }
  209. }
  210. public function destroy($business, $project, $task, $work)
  211. {
  212. $taskModel = Task::findOrFail($task);
  213. $workModel = Work::findOrFail($work);
  214. if ($taskModel->assignee_id != auth()->id()) {
  215. abort(Response::HTTP_FORBIDDEN); // not allowed
  216. }
  217. $diff_in_min = $workModel->minute_sum;
  218. $workModel->delete();
  219. $taskModel->update([
  220. 'work_start' => Work::where('task_id', $taskModel->id)->orderBy('started_at')->first()->started_at ?? null,
  221. 'spent_time' => ($taskModel->spent_time - $diff_in_min)
  222. ]);
  223. return $taskModel->load(['tagTask'=> fn($q) => $q->select('id', 'tag_id', 'task_id'), 'works','comments']);
  224. }
  225. }