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
308 lines
12 KiB
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use App\Models\Business;
|
|
use App\Models\Comment;
|
|
use App\HiLib\Models\ReportableRelation;
|
|
use App\Models\Project;
|
|
use App\Models\Tag;
|
|
use App\Models\TagTask;
|
|
use App\Models\User;
|
|
use App\Models\Work;
|
|
use Carbon\Carbon;
|
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Validation\Rule;
|
|
use Illuminate\Validation\Rules\RequiredIf;
|
|
|
|
class Task extends Model
|
|
{
|
|
use HasFactory;
|
|
protected $fillable = [
|
|
'business_id', 'project_id', 'creator_id', 'workflow_id', 'assignee_id', 'system_id', 'sprint_id',
|
|
'status_id', 'approver_id', 'title', 'description', 'priority', 'on_time', 'estimated_time', 'due_date', 'completed_at',
|
|
'work_start', 'spent_time', 'work_finish', 'ready_to_test', 'watchers', 'tags'
|
|
];
|
|
|
|
protected $reportable = [
|
|
'priority', 'title', 'description', 'priority', 'on_time', 'estimated_time', 'due_date', 'completed_at',
|
|
'work_finish', 'ready_to_test', 'assignee_id', 'approver_id', ['tags' => 'tag_task']
|
|
];
|
|
protected $fillable_relations = [
|
|
'tags'
|
|
];
|
|
|
|
protected $casts = [
|
|
'work_start' => 'datetime:Y-m-d H:i',
|
|
'work_finish' => 'datetime:Y-m-d H:i',
|
|
'watchers' => 'array',
|
|
];
|
|
|
|
public function rules()
|
|
{
|
|
$validations = [
|
|
'assignee_id' => ['nullable', 'numeric',
|
|
function ($attribute, $value, $fail) {
|
|
//check assignee at least guest in project
|
|
if (!can('isProjectGuest', ['project_id' => request()->route('project'), 'user_id' => $value])) {
|
|
$fail('The selected '.$attribute.' is invalid.');
|
|
}
|
|
if (request()->method() === 'PUT' && ($this->assignee_id != $value && Work::where('task_id', $this->id)->exists())) {
|
|
//when update execute
|
|
//check if change assignee then should be on task not work set
|
|
$fail('The selected '.$attribute.' is invalid.');
|
|
}
|
|
}],
|
|
'system_id' => ['nullable', 'numeric',
|
|
Rule::in(\request('_business_info')['info']['projects'][request()->route('project')]['systems'])],
|
|
'sprint_id' => ['nullable', 'numeric',
|
|
Rule::in(\request('_business_info')['info']['projects'][request()->route('project')]['sprints'])],
|
|
'workflow_id' => ['required', 'numeric',
|
|
Rule::in(array_keys(\request('_business_info')['info']['workflows']))],
|
|
'status_id' => 'required|numeric',
|
|
'approver_id' => ['nullable', 'numeric',
|
|
function ($attribute, $value, $fail) {
|
|
//check approval at least colleague in project
|
|
if (!can('isProjectColleague', ['project_id' => request()->route('project'), 'user_id' => $value])) {
|
|
$fail('The selected '.$attribute.' is invalid.');
|
|
}
|
|
}],
|
|
'title' => 'required|string|min:3|max:254',
|
|
'description' => 'nullable|string|min:2|max:1000',
|
|
'priority' => 'nullable|numeric|between:1,10',
|
|
'estimated_time' => 'nullable|numeric|min:30',
|
|
'due_date' => 'bail|nullable|date|date_format:Y-m-d|after_or_equal:'.
|
|
((request()->method() === 'POST') ? date('yy-m-d') : $this->created_at->toDateString()),
|
|
'tags' => 'nullable|array',
|
|
'tags.*' => [new RequiredIf(isset($request->tags)), 'numeric',
|
|
Rule::in(\request('_business_info')['info']['tags'])],
|
|
] ;
|
|
$workflow_id = $this->workflow_id;
|
|
if (request()->filled('workflow_id') && isset(request('_business_info')['info']['workflows'][request()->workflow_id])) {
|
|
$workflow_id = request()->workflow_id;
|
|
}
|
|
if (isset($workflow_id)) {
|
|
$validations['status_id'] = ['bail', 'required', 'numeric',
|
|
//check status exists in status list
|
|
Rule::in(\request('_business_info')['info']['workflows'][$workflow_id]['statuses']),
|
|
function ($attribute, $value, $fail) use ($workflow_id) {
|
|
//check not close or done
|
|
$state = \request('_business_info')['workflows'][$workflow_id]['statuses'][$value]['state'];
|
|
if (request()->method() == 'POST' && ($state == enum('status.states.close.id') || $state == enum('status.states.done.id'))) {
|
|
$fail('The selected '.$attribute.' is invalid.');
|
|
}
|
|
if ((request()->method() == 'PUT') && !can('projectTasks', ['project_id' => request()->route('project')])
|
|
&& $state != enum('status.states.active.id')) {
|
|
$fail('The selected '.$attribute.' is invalid.');
|
|
}
|
|
}];
|
|
}
|
|
|
|
return $validations;
|
|
}
|
|
public function updateRelations()
|
|
{
|
|
// tags relations
|
|
$this->dirties['tags'] = $this->tags()->sync($this->filled_relations['tags']);
|
|
|
|
//old code
|
|
// if (!empty($this->filled_relations['tags'])) {
|
|
// $this->dirties['tags'] = $this->tags()->sync($this->filled_relations['tags']);
|
|
// }
|
|
|
|
}
|
|
|
|
public function getValueOf(?string $key)
|
|
{
|
|
$values = [
|
|
'business_id' => $this->business_id,
|
|
'project_id' => $this->project_id,
|
|
'sprint_id' => $this->sprint_id,
|
|
'workflow_id' => $this->workflow_id,
|
|
'status_id' => $this->status_id,
|
|
'system_id' => $this->system_id,
|
|
'task_id' => $this->id,
|
|
'subject_id' => $this->id,
|
|
'user_id' => $this->assignee_id,
|
|
];
|
|
|
|
if ($key && isset($values, $key)) {
|
|
return $values[$key];
|
|
}
|
|
|
|
return $values;
|
|
}
|
|
|
|
public function business()
|
|
{
|
|
return $this->belongsTo(Business::class, 'business_id', 'id', __FUNCTION__);
|
|
}
|
|
|
|
public function creator()
|
|
{
|
|
return $this->belongsTo(User::class, 'creator_id', 'id', __FUNCTION__);
|
|
}
|
|
|
|
public function user()
|
|
{
|
|
return $this->belongsTo(User::class, 'user_id', 'id', __FUNCTION__);
|
|
}
|
|
|
|
public function project()
|
|
{
|
|
return $this->belongsTo(Project::class, 'project_id', 'id', __FUNCTION__);
|
|
}
|
|
|
|
public function tags()
|
|
{
|
|
return $this->belongsToMany(
|
|
Tag::class, 'tag_task', 'task_id', 'tag_id',
|
|
'id', 'id', __FUNCTION__
|
|
)->using(ReportableRelation::class);
|
|
}
|
|
|
|
public function tagTask()
|
|
{
|
|
return $this->hasMany(TagTask::class, 'task_id', 'id');
|
|
}
|
|
|
|
public function works()
|
|
{
|
|
return $this->hasMany(Work::class, 'task_id', 'id');
|
|
}
|
|
|
|
public function comments()
|
|
{
|
|
return $this->hasMany(Comment::class, 'task_id', 'id');
|
|
}
|
|
|
|
public function scopePriorityMin($query, $min)
|
|
{
|
|
return $query->where('tasks.priority', '>=', $min);
|
|
}
|
|
public function scopePriorityMax($query, $max)
|
|
{
|
|
return $query->where('tasks.priority', '<=', $max);
|
|
}
|
|
public function scopeCreatesBefore($query, $date)
|
|
{
|
|
//ToDo: comment lines have better performance but should be test
|
|
// return $query->whereDate('tasks.created_at', '<=', Carbon::parse($date.' 23:59:59'));
|
|
// return $query->where('tasks.created_at', '<', (new DateTime('2014-07-10'))->modify('+1 day')->format('Y-m-d'));
|
|
return $query->whereDate('tasks.created_at', '<=', Carbon::parse($date));
|
|
}
|
|
public function scopeCreatesAfter($query, $date)
|
|
{
|
|
return $query->whereDate('tasks.created_at', '>=', Carbon::parse($date));
|
|
}
|
|
public function scopeCreatesIn($query, $days)
|
|
{
|
|
return $query->whereDate('tasks.created_at', '>=', Carbon::now()->modify('-'.$days.' day')->toDate());
|
|
}
|
|
public function scopeUpdatesBefore($query, $date)
|
|
{
|
|
return $query->whereDate('tasks.updated_at', '<=', Carbon::parse($date));
|
|
}
|
|
public function scopeUpdatesAfter($query, $date)
|
|
{
|
|
return $query->whereDate('tasks.updated_at', '>=', Carbon::parse($date));
|
|
}
|
|
public function scopeUpdatesIn($query, $days)
|
|
{
|
|
return $query->whereDate('tasks.updated_at', '>=', Carbon::now()->modify('-'.$days.' day')->toDate());
|
|
}
|
|
public function scopeSpentFrom($query, $minute)
|
|
{
|
|
return $query->where('tasks.spent_time', '>=', $minute);
|
|
}
|
|
public function scopeSpentTo($query, $minute)
|
|
{
|
|
return $query->where('tasks.spent_time', '<=', $minute);
|
|
}
|
|
public function scopeEstimatedFrom($query, $minute)
|
|
{
|
|
return $query->where('tasks.estimated_time', '>=', $minute);
|
|
}
|
|
public function scopeEstimatedTo($query, $minute)
|
|
{
|
|
return $query->where('tasks.estimated_time', '<=', $minute);
|
|
}
|
|
public function scopeStartsBefore($query, $date)
|
|
{
|
|
return $query->whereDate('tasks.work_start', '<=', Carbon::parse($date));
|
|
}
|
|
public function scopeStartsAfter($query, $date)
|
|
{
|
|
return $query->whereDate('tasks.work_start', '>=', Carbon::parse($date));
|
|
}
|
|
public function scopeStartsIn($query, $days)
|
|
{
|
|
return $query->whereDate('tasks.work_start', '>=', Carbon::now()->modify('-'.$days.' day')->toDate());
|
|
}
|
|
public function scopeFinishBefore($query, $date)
|
|
{
|
|
return $query->whereDate('tasks.work_finish', '<=', Carbon::parse($date));
|
|
}
|
|
public function scopeFinishAfter($query, $date)
|
|
{
|
|
return $query->whereDate('tasks.work_finish', '>=', Carbon::parse($date));
|
|
}
|
|
public function scopeFinishIn($query, $days)
|
|
{
|
|
return $query->whereDate('tasks.work_finish', '>=', Carbon::now()->modify('-'.$days.' day')->toDate());
|
|
}
|
|
public function scopeCompletesBefore($query, $date)
|
|
{
|
|
return $query->where('tasks.completed_at', '<=', $date);
|
|
}
|
|
public function scopeCompletesAfter($query, $date)
|
|
{
|
|
return $query->where('tasks.completed_at', '>=', $date);
|
|
}
|
|
public function scopeCompletesIn($query, $days)
|
|
{
|
|
return $query->whereDate('tasks.completed_at', '>=', Carbon::now()->modify('-'.$days.' day')->toDate());
|
|
}
|
|
public function scopeDueDateBefore($query, $date)
|
|
{
|
|
return $query->where('tasks.due_date', '<=', $date);
|
|
}
|
|
public function scopeDueDateAfter($query, $date)
|
|
{
|
|
return $query->where('tasks.due_date', '>=', $date);
|
|
}
|
|
public function scopeDueDateIn($query, $days)
|
|
{
|
|
return $query->whereDate('tasks.due_date', '>=', Carbon::now()->modify('-'.$days.' day')->toDate());
|
|
}
|
|
public function scopeOverSpentFrom($query, $minute)
|
|
{
|
|
return $query->whereColumn('spent_time', '>', 'estimated_time')
|
|
->having('over_spent', '>=', $minute);
|
|
}
|
|
public function scopeOverSpentTo($query, $minute)
|
|
{
|
|
return $query->whereColumn('spent_time', '>', 'estimated_time')
|
|
->having('over_spent', '<=', $minute);
|
|
}
|
|
public function scopeMyWatching($query)
|
|
{
|
|
return $query->whereJsonContains('watchers', auth()->id());
|
|
}
|
|
public function scopeOverDue($query)
|
|
{
|
|
return $query->whereColumn('due_date', '<', 'completed_at');
|
|
}
|
|
public function scopeReport($query, $group_field)
|
|
{
|
|
return $query->select(DB::raw($group_field.' , status_id, COUNT(*) as total, SUM(spent_time) as spent_sum,
|
|
SUM(estimated_time) as estimated_sum,SUM(ready_to_test) as test_sum,
|
|
COUNT(completed_at) as completed_total, SUM(on_time) as on_time_total,
|
|
SUM(Case When spent_time > estimated_time Then (spent_time - estimated_time) Else 0 End) as over_spent_sum,
|
|
SUM(Case When (spent_time <= estimated_time) And (completed_at) Then (estimated_time - spent_time) Else 0 End)
|
|
as under_spent_sum'))
|
|
->groupBy($group_field, 'status_id');
|
|
}
|
|
}
|