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.
295 lines
12 KiB
295 lines
12 KiB
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Http\Resources\TaskCollection;
|
|
use App\Http\Resources\TaskResource;
|
|
use App\Rules\maxBound;
|
|
use App\Models\TagTask;
|
|
use App\Models\Task;
|
|
use App\Models\Work;
|
|
use Carbon\Carbon;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Http\Response;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Spatie\QueryBuilder\AllowedFilter;
|
|
use Spatie\QueryBuilder\QueryBuilder;
|
|
|
|
class TaskController extends Controller
|
|
{
|
|
public function index($business, Request $request)
|
|
{
|
|
permit('businessAccess');
|
|
$per_page = $request->limit > 100 ? 10 : $request->limit;
|
|
$this->indexValidation($request);
|
|
$tasks = $this->indexFiltering($business)
|
|
->when($request->filled('group'), function ($q) use ($request) {
|
|
return $q->report($request->group);
|
|
});
|
|
|
|
return $request->filled('group') ?
|
|
$tasks->get()->groupBy($request->group)
|
|
->map(function ($q) { return $q->keyBy('status_id'); })
|
|
: /*response()->json(collect(['now' => Carbon::now()->toDateString()])->merge(*/new TaskCollection($tasks->paginate($per_page));//));
|
|
}
|
|
|
|
public function indexValidation($request)
|
|
{
|
|
$bound = 10;
|
|
$this->validate($request, [
|
|
'filter.project_id' => [new maxBound($bound)] ,
|
|
'filter.creator_id' => [new maxBound($bound)] ,
|
|
'filter.assignee_id' => [new maxBound($bound)] ,
|
|
'filter.system_id' => [new maxBound($bound)] ,
|
|
'filter.workflow_id' => [new maxBound($bound)] ,
|
|
'filter.status_id' => [new maxBound($bound)] ,
|
|
'filter.approver_id' => [new maxBound($bound)] ,
|
|
'filter.priority_min' => 'nullable|numeric|between:1,10' ,
|
|
'filter.priority_max' => 'nullable|numeric|between:1,10' ,
|
|
'filter.ready_to_test' => 'nullable|boolean' ,
|
|
'filter.on_time' => 'nullable|boolean' ,
|
|
]);
|
|
}
|
|
|
|
public function indexFiltering($business)
|
|
{
|
|
$query = Task::where('business_id', $business);
|
|
$taskQ = QueryBuilder::for($query)
|
|
->with('tagTask')
|
|
->select(DB::raw('tasks.* , (spent_time - estimated_time) as over_spent'))
|
|
->allowedFilters([
|
|
AllowedFilter::exact('project_id'),
|
|
AllowedFilter::exact('system_id'),
|
|
AllowedFilter::exact('creator_id'),
|
|
AllowedFilter::exact('assignee_id'),
|
|
AllowedFilter::exact('approver_id'),
|
|
AllowedFilter::exact('sprint_id'),
|
|
AllowedFilter::exact('workflow_id'),
|
|
AllowedFilter::exact('status_id'),
|
|
AllowedFilter::exact('on_time'),
|
|
AllowedFilter::exact('ready_to_test'),
|
|
AllowedFilter::exact('tagTask.tag_id'),
|
|
AllowedFilter::scope('priority_min'),
|
|
AllowedFilter::scope('priority_max'),
|
|
AllowedFilter::scope('creates_before'),
|
|
AllowedFilter::scope('creates_after'),
|
|
AllowedFilter::scope('creates_in'),
|
|
AllowedFilter::scope('updates_before'),
|
|
AllowedFilter::scope('updates_after'),
|
|
AllowedFilter::scope('updates_in'),
|
|
AllowedFilter::scope('spent_from'),
|
|
AllowedFilter::scope('spent_to'),
|
|
AllowedFilter::scope('estimated_from'),
|
|
AllowedFilter::scope('estimated_to'),
|
|
AllowedFilter::scope('starts_before'),
|
|
AllowedFilter::scope('starts_after'),
|
|
AllowedFilter::scope('starts_in'),
|
|
AllowedFilter::scope('finish_before'),
|
|
AllowedFilter::scope('finish_after'),
|
|
AllowedFilter::scope('finish_in'),
|
|
AllowedFilter::scope('completes_before'),
|
|
AllowedFilter::scope('completes_after'),
|
|
AllowedFilter::scope('completes_in'),
|
|
AllowedFilter::scope('over_spent_from'),
|
|
AllowedFilter::scope('over_spent_to'),
|
|
AllowedFilter::scope('due_date_before'),
|
|
AllowedFilter::scope('due_date_after'),
|
|
AllowedFilter::scope('due_date_in'),
|
|
AllowedFilter::scope('my_watching'),
|
|
AllowedFilter::scope('over_due'),
|
|
]);
|
|
if (\request('_business_info')['info']['users'][\auth()->id()]['level'] != enum('levels.owner.id')) {
|
|
$requested_projects = isset(\request('filter')['project_id']) ?
|
|
array_unique(explode(',',\request('filter')['project_id'] ?? null )) :
|
|
null;
|
|
$requested_projects = collect($requested_projects)->keyBy(null)->toArray();
|
|
$project_ids = $this->myStateProjects($requested_projects);
|
|
$taskQ->where(function ($q) use ($project_ids) {
|
|
$q->whereIn('project_id', $project_ids['non_guest_ids'])
|
|
->orWhere(function ($q) use ($project_ids) {
|
|
$q->whereIn('project_id', $project_ids['guest_ids'])
|
|
->where('assignee_id', auth()->id());
|
|
});
|
|
});
|
|
}
|
|
return $taskQ;
|
|
}
|
|
|
|
public function myStateProjects($requested_projects)
|
|
{
|
|
$non_guest_ids = [];
|
|
$guest_ids = [];
|
|
$is_empty = empty($requested_projects);
|
|
|
|
foreach (\request('_business_info')['info']['projects'] as $p_id => $p) {
|
|
|
|
$level = \request('_business_info')['info']['projects'][$p_id]['members'][\auth()->id()]['level'];
|
|
|
|
if (( $is_empty || isset($requested_projects[$p_id]))
|
|
&& $level > enum('levels.guest.id')) {
|
|
array_push($non_guest_ids, $p_id);
|
|
}
|
|
if (( $is_empty || isset($requested_projects[$p_id]))
|
|
&& $level == enum('levels.guest.id')) {
|
|
array_push($guest_ids, $p_id);
|
|
}
|
|
}
|
|
|
|
return ['non_guest_ids' => $non_guest_ids, 'guest_ids' => $guest_ids];
|
|
}
|
|
|
|
public function store($business, $project, Request $request)
|
|
{
|
|
permit('projectTasks', ['project_id' => $project]);
|
|
|
|
$task = Task::create($request->merge(
|
|
['business_id' => $business, 'project_id' => $project, 'creator_id' => \auth()->id()]
|
|
)->except(['_business_info', 'completed_at', 'on_time', 'ready_to_test']));
|
|
|
|
|
|
return new TaskResource($task);
|
|
}
|
|
|
|
public function storeTagTasks($tags, $task) {
|
|
$tagModels = [];
|
|
if (isset($tags)) {
|
|
foreach ($tags as $tag) {
|
|
array_push($tagModels,
|
|
new TagTask(['tag_id' => $tag, 'task_id' => $task->id])
|
|
);
|
|
}
|
|
$task->tags()->saveMany($tagModels);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Rule's:
|
|
* 1) guest's only can see self task
|
|
* 2) user is active in project
|
|
*/
|
|
public function show($business, $task, $project =null)
|
|
{
|
|
$task = Task::findOrFail($task);
|
|
$project = $task->project_id;
|
|
permit('projectAccess', ['project_id' => $project]);
|
|
if (can('isDefiniteGuestInProject', ['project_id' => $project])){ // is guest in project (only guest)
|
|
return $task->assignee_id == \auth()->id() ?
|
|
new TaskResource($task) :
|
|
abort(Response::HTTP_METHOD_NOT_ALLOWED); // not allowed
|
|
} else {
|
|
return new TaskResource($task);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Rule's:
|
|
* 1) update assignee_id when not exist work time and active user
|
|
* 2) update approver_id when atLeast colleague
|
|
* 3) update ready_to_test when assignee_id == auth xor assignee_id == null and isAdmin
|
|
* 4) update tags
|
|
* 5) update completed_at when status in [done, close]
|
|
* 6) due_date before sprint end_time
|
|
*/
|
|
public function update($business, $project, $task, Request $request)
|
|
{
|
|
permit('isProjectGuest', ['project_id' => $project]);
|
|
$taskModel = Task::where([['project_id', $project ], ['id', $task]])->firstOrFail();
|
|
|
|
if ($taskModel->assignee_id != \auth()->id() && can('isDefiniteGuestInProject', ['project_id' => $project])) {
|
|
permit('isDefiniteGuestInProject', ['project_id' => $project]);
|
|
}
|
|
|
|
if($request->filled('watchers')) {
|
|
$watchers = $taskModel->watchers ?? [];
|
|
if(!can('isDefiniteGuestInProject', ['project_id' => $project]) && !can('projectTasks', ['project_id' => $project])) {
|
|
if (array_search(auth()->id(), $watchers) !== false) {
|
|
// remove auth from watchers
|
|
$watchers = array_values(\array_diff($watchers, [auth()->id()]));
|
|
} else {
|
|
// add auth to watchers
|
|
$watchers = array_merge($watchers, [auth()->id()]);
|
|
}
|
|
}
|
|
if(can('projectTasks', ['project_id' => $project])) {
|
|
$watchers = array_intersect($watchers ?? [], $request->watchers);
|
|
}
|
|
$request->merge(['watchers' => $watchers]);
|
|
}
|
|
|
|
if (!can('projectTasks', ['project_id' => $project])) {
|
|
$request->replace($request->only(['_business_info', 'ready_to_test', 'status_id', 'watchers']));
|
|
}
|
|
if ($taskModel->assignee_id != \auth()->id()) {
|
|
$request->request->remove('ready_to_test');
|
|
}
|
|
|
|
|
|
if (isset(\request('_business_info')['workflows'][$request->workflow_id]['statuses'][$request->status_id]['state'])) {
|
|
$state = \request('_business_info')['workflows'][$request->workflow_id]['statuses'][$request->status_id]['state'];
|
|
if ($state == enum('status.states.close.id') || $state == enum('status.states.done.id')) {
|
|
//ToDo: is needed to check before state is done or close?
|
|
$request->merge([
|
|
'completed_at' => Carbon::now(),
|
|
'work_finish' => Work::where('task_id', $task)->latest()->first()->ended_at ?? null
|
|
]);
|
|
if (isset($taskModel->due_date) && $taskModel->due_date < date('yy-m-d')) {
|
|
//check if before set due date and miss, we change on time flag
|
|
$request->merge(['on_time' => false]);
|
|
}
|
|
}
|
|
}
|
|
if (!$request->has('tags')) {
|
|
$request->merge(['tags' => []]);
|
|
}
|
|
$taskModel->update($request->except('_business_info'));
|
|
return new TaskResource($taskModel);
|
|
}
|
|
|
|
public function updateReadyToTest($business, $project, $task)
|
|
{
|
|
permit('isProjectGuest', ['project_id' => $project]);
|
|
$task = Task::where([['project_id', $project ], ['id', $task]])->firstOrFail();
|
|
if ($task->assignee_id == \auth()->id()) {
|
|
$task->update([
|
|
'ready_to_test' => 1
|
|
]);
|
|
} else {
|
|
return abort(Response::HTTP_FORBIDDEN); // not allowed
|
|
}
|
|
return $task->load(['tagTask'=> fn($q) => $q->select('id', 'tag_id', 'task_id'), 'works', 'comments']);
|
|
}
|
|
|
|
/**
|
|
* Rule's:
|
|
* 1) delete only not work time (soft delete)
|
|
*/
|
|
public function destroy($task)
|
|
{
|
|
$taskModel = Task::findOrFail($task);
|
|
if (Work::where('task_id', $task)->exists()) {
|
|
return \response()->json(['task_id' => 'The task id cannot be deleted.'], Response::HTTP_UNPROCESSABLE_ENTITY);
|
|
}
|
|
$taskModel->delete();
|
|
return \response()->json(['message' => 'task deleted successfully.'], Response::HTTP_OK);
|
|
}
|
|
|
|
public function toggleWatcher($business, $task, $project =null)
|
|
{
|
|
permit('isProjectGuest', ['project_id' => $project]);
|
|
$taskModel = Task::findOrFail($task);
|
|
$watchers = $taskModel->watchers ?? [];
|
|
|
|
if (array_search(auth()->id(), $watchers) !== false) {
|
|
// remove auth from watchers
|
|
$new_watchers = array_values(\array_diff($watchers, [auth()->id()]));
|
|
} else {
|
|
// add auth to watchers
|
|
$new_watchers = array_merge($watchers, [auth()->id()]);
|
|
}
|
|
|
|
$taskModel->update([
|
|
'watchers' => $new_watchers
|
|
]);
|
|
return new TaskResource($taskModel);
|
|
}
|
|
}
|