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

<?php
namespace App\Models;
use Carbon\Carbon;
use App\Models\Tag;
use App\Models\User;
use App\Models\Work;
use App\Models\Model;
use App\Models\Comment;
use App\Models\Project;
use App\Models\TagTask;
use App\Models\Business;
use Illuminate\Validation\Rule;
use Illuminate\Support\Facades\DB;
use App\HiLib\Models\ReportableRelation;
use Illuminate\Validation\Rules\RequiredIf;
use Illuminate\Database\Eloquent\Factories\HasFactory;
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');
}
}