'decimal:2', 'actual_qty' => 'decimal:2', 'unit_cost' => 'decimal:2', ]; } public function task(): BelongsTo { return $this->belongsTo(Task::class); } public function material(): BelongsTo { return $this->belongsTo(Material::class); } protected static function booted(): void { static::created(function (TaskMaterial $tm) { if (auth()->check() && $tm->task) { $tm->task->activities()->create([ 'user_id' => auth()->id(), 'description' => "added material '{$tm->material?->name}' (Planned Qty: {$tm->planned_qty}).", 'type' => 'material_add', ]); } }); static::updated(function (TaskMaterial $tm) { if (auth()->check() && $tm->task) { $dirty = $tm->getDirty(); $changes = []; if (array_key_exists('actual_qty', $dirty)) { $changes[] = "updated actual quantity for '{$tm->material?->name}' to {$tm->actual_qty}"; } if (array_key_exists('planned_qty', $dirty)) { $changes[] = "updated planned quantity for '{$tm->material?->name}' to {$tm->planned_qty}"; } if (!empty($changes)) { $tm->task->activities()->create([ 'user_id' => auth()->id(), 'description' => implode(', ', $changes) . '.', 'type' => 'material_update', ]); } } }); static::deleted(function (TaskMaterial $tm) { if (auth()->check() && $tm->task) { $tm->task->activities()->create([ 'user_id' => auth()->id(), 'description' => "removed material '{$tm->material?->name}'.", 'type' => 'material_remove', ]); } }); } public function getPlannedCostAttribute(): float { return (float) $this->planned_qty * (float) $this->unit_cost; } public function getActualCostAttribute(): float { return (float) $this->actual_qty * (float) $this->unit_cost; } public function getEffectiveCostAttribute(): float { return $this->actual_qty > 0 ? $this->actual_cost : $this->planned_cost; } public function getVarianceAttribute(): float { return $this->planned_cost - $this->actual_cost; } }