147 lines
4.0 KiB
PHP
147 lines
4.0 KiB
PHP
<?php
|
|
|
|
namespace Modules\ProjectManagement\Models;
|
|
use Modules\MasterData\Models\Material;
|
|
use Modules\MasterData\Models\MaterialGroup;
|
|
|
|
use App\Models\User;
|
|
use App\Traits\HasPublicIdentifier;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
|
use Modules\ProjectManagement\Enums\DelayReason;
|
|
use Modules\ProjectManagement\Enums\TaskStatus;
|
|
|
|
class Task extends Model
|
|
{
|
|
use HasPublicIdentifier;
|
|
|
|
protected $fillable = [
|
|
'project_id',
|
|
'name',
|
|
'description',
|
|
'area',
|
|
'status',
|
|
'sort_order',
|
|
'labor_cost',
|
|
'estimated_hours',
|
|
'actual_hours',
|
|
'start_date',
|
|
'end_date',
|
|
'actual_start_date',
|
|
'actual_end_date',
|
|
'completion_percentage',
|
|
'milestone_id',
|
|
];
|
|
|
|
protected function casts(): array
|
|
{
|
|
return [
|
|
'status' => TaskStatus::class,
|
|
'labor_cost' => 'decimal:2',
|
|
'estimated_hours' => 'decimal:2',
|
|
'actual_hours' => 'decimal:2',
|
|
'completion_percentage' => 'decimal:2',
|
|
'start_date' => 'date',
|
|
'end_date' => 'date',
|
|
'actual_start_date' => 'date',
|
|
'actual_end_date' => 'date',
|
|
];
|
|
}
|
|
|
|
public function project(): BelongsTo
|
|
{
|
|
return $this->belongsTo(Project::class);
|
|
}
|
|
|
|
public function milestone(): BelongsTo
|
|
{
|
|
return $this->belongsTo(ProjectMilestone::class, 'milestone_id');
|
|
}
|
|
|
|
public function users(): BelongsToMany
|
|
{
|
|
return $this->belongsToMany(User::class);
|
|
}
|
|
|
|
public function taskMaterials(): HasMany
|
|
{
|
|
return $this->hasMany(TaskMaterial::class);
|
|
}
|
|
|
|
public function activities(): HasMany
|
|
{
|
|
return $this->hasMany(TaskActivity::class)->orderBy('created_at', 'desc');
|
|
}
|
|
|
|
public function delays(): HasMany
|
|
{
|
|
return $this->hasMany(TaskDelay::class)->orderByDesc('delay_date');
|
|
}
|
|
|
|
public function getTotalDelayDaysAttribute(): float
|
|
{
|
|
return round((float) $this->delays->sum('lost_hours') / 8, 1);
|
|
}
|
|
|
|
public function getWeatherDelayCountAttribute(): int
|
|
{
|
|
return $this->delays->where('reason_type', DelayReason::Weather)->count();
|
|
}
|
|
|
|
public function getMaterialCostAttribute(): float
|
|
{
|
|
return $this->taskMaterials->sum(fn (TaskMaterial $tm) => $tm->effective_cost);
|
|
}
|
|
|
|
public function getTotalCostAttribute(): float
|
|
{
|
|
return (float) $this->labor_cost + $this->material_cost;
|
|
}
|
|
|
|
public function transitionTo(TaskStatus $newStatus): void
|
|
{
|
|
$currentStatus = $this->status;
|
|
|
|
if (!in_array($newStatus, $currentStatus->allowedTransitions())) {
|
|
throw new \InvalidArgumentException(
|
|
"Cannot transition task from {$currentStatus->label()} to {$newStatus->label()}"
|
|
);
|
|
}
|
|
|
|
$updates = ['status' => $newStatus];
|
|
|
|
if ($newStatus === TaskStatus::InProgress) {
|
|
if (!$this->actual_start_date) {
|
|
$updates['actual_start_date'] = now();
|
|
}
|
|
if ($currentStatus === TaskStatus::Completed) {
|
|
$updates['actual_end_date'] = null;
|
|
$updates['completion_percentage'] = 0;
|
|
}
|
|
}
|
|
|
|
if ($newStatus === TaskStatus::Pending) {
|
|
$updates['actual_start_date'] = null;
|
|
$updates['actual_end_date'] = null;
|
|
$updates['completion_percentage'] = 0;
|
|
}
|
|
|
|
if ($newStatus === TaskStatus::Completed || $newStatus === TaskStatus::Closed) {
|
|
if (!$this->actual_end_date) {
|
|
$updates['actual_end_date'] = now();
|
|
}
|
|
$updates['completion_percentage'] = 100;
|
|
}
|
|
|
|
$this->update($updates);
|
|
}
|
|
|
|
public function scopeStatus($query, TaskStatus $status)
|
|
{
|
|
return $query->where('status', $status);
|
|
}
|
|
}
|
|
|