| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329 | 
							- <?php
 - 
 - namespace App\Models\Accounting;
 - 
 - use App\Concerns\Blamable;
 - use App\Concerns\CompanyOwned;
 - use App\Enums\Accounting\BudgetIntervalType;
 - use App\Enums\Accounting\BudgetSourceType;
 - use App\Enums\Accounting\BudgetStatus;
 - use App\Filament\Company\Resources\Accounting\BudgetResource;
 - use App\Models\User;
 - use Filament\Actions\Action;
 - use Filament\Actions\MountableAction;
 - use Filament\Actions\ReplicateAction;
 - use Illuminate\Database\Eloquent\Builder;
 - use Illuminate\Database\Eloquent\Casts\Attribute;
 - use Illuminate\Database\Eloquent\Factories\HasFactory;
 - use Illuminate\Database\Eloquent\Model;
 - use Illuminate\Database\Eloquent\Relations\BelongsTo;
 - use Illuminate\Database\Eloquent\Relations\HasMany;
 - use Illuminate\Database\Eloquent\Relations\HasManyThrough;
 - use Illuminate\Support\Carbon;
 - use Illuminate\Support\Collection;
 - 
 - class Budget extends Model
 - {
 -     use Blamable;
 -     use CompanyOwned;
 -     use HasFactory;
 - 
 -     protected $fillable = [
 -         'company_id',
 -         'source_budget_id',
 -         'source_fiscal_year',
 -         'source_type',
 -         'name',
 -         'start_date',
 -         'end_date',
 -         'status', // draft, active, closed
 -         'interval_type', // day, week, month, quarter, year
 -         'notes',
 -         'approved_at',
 -         'approved_by_id',
 -         'closed_at',
 -         'created_by',
 -         'updated_by',
 -     ];
 - 
 -     protected $casts = [
 -         'source_fiscal_year' => 'integer',
 -         'source_type' => BudgetSourceType::class,
 -         'start_date' => 'date',
 -         'end_date' => 'date',
 -         'status' => BudgetStatus::class,
 -         'interval_type' => BudgetIntervalType::class,
 -         'approved_at' => 'datetime',
 -         'closed_at' => 'datetime',
 -     ];
 - 
 -     public function sourceBudget(): BelongsTo
 -     {
 -         return $this->belongsTo(self::class, 'source_budget_id');
 -     }
 - 
 -     public function derivedBudgets(): HasMany
 -     {
 -         return $this->hasMany(self::class, 'source_budget_id');
 -     }
 - 
 -     public function approvedBy(): BelongsTo
 -     {
 -         return $this->belongsTo(User::class, 'approved_by_id');
 -     }
 - 
 -     public function budgetItems(): HasMany
 -     {
 -         return $this->hasMany(BudgetItem::class);
 -     }
 - 
 -     public function allocations(): HasManyThrough
 -     {
 -         return $this->hasManyThrough(BudgetAllocation::class, BudgetItem::class);
 -     }
 - 
 -     public function getPeriods(): Collection
 -     {
 -         return $this->allocations()
 -             ->select(['period', 'start_date'])
 -             ->distinct()
 -             ->orderBy('start_date')
 -             ->get();
 -     }
 - 
 -     public function isDraft(): bool
 -     {
 -         return $this->status === BudgetStatus::Draft;
 -     }
 - 
 -     public function isActive(): bool
 -     {
 -         return $this->status === BudgetStatus::Active;
 -     }
 - 
 -     public function isClosed(): bool
 -     {
 -         return $this->status === BudgetStatus::Closed;
 -     }
 - 
 -     public function wasApproved(): bool
 -     {
 -         return $this->approved_at !== null;
 -     }
 - 
 -     public function wasClosed(): bool
 -     {
 -         return $this->closed_at !== null;
 -     }
 - 
 -     public function canBeApproved(): bool
 -     {
 -         return $this->isDraft() && ! $this->wasApproved();
 -     }
 - 
 -     public function canBeClosed(): bool
 -     {
 -         return $this->isActive() && ! $this->wasClosed();
 -     }
 - 
 -     public function hasItems(): bool
 -     {
 -         return $this->budgetItems()->exists();
 -     }
 - 
 -     public function hasAllocations(): bool
 -     {
 -         return $this->allocations()->exists();
 -     }
 - 
 -     public function scopeDraft(Builder $query): Builder
 -     {
 -         return $query->where('status', BudgetStatus::Draft);
 -     }
 - 
 -     public function scopeActive(Builder $query): Builder
 -     {
 -         return $query->where('status', BudgetStatus::Active);
 -     }
 - 
 -     public function scopeClosed(Builder $query): Builder
 -     {
 -         return $query->where('status', BudgetStatus::Closed);
 -     }
 - 
 -     public function scopeCurrentlyActive(Builder $query): Builder
 -     {
 -         return $query->active()
 -             ->where('start_date', '<=', now())
 -             ->where('end_date', '>=', now());
 -     }
 - 
 -     protected function isCurrentlyInPeriod(): Attribute
 -     {
 -         return Attribute::get(function () {
 -             return now()->between($this->start_date, $this->end_date);
 -         });
 -     }
 - 
 -     /**
 -      * Approve a draft budget
 -      */
 -     public function approveDraft(?Carbon $approvedAt = null): void
 -     {
 -         if (! $this->canBeApproved()) {
 -             throw new \RuntimeException('Budget cannot be approved.');
 -         }
 - 
 -         $approvedAt ??= now();
 - 
 -         $this->update([
 -             'status' => BudgetStatus::Active,
 -             'approved_at' => $approvedAt,
 -         ]);
 -     }
 - 
 -     /**
 -      * Close an active budget
 -      */
 -     public function close(?Carbon $closedAt = null): void
 -     {
 -         if (! $this->canBeClosed()) {
 -             throw new \RuntimeException('Budget cannot be closed.');
 -         }
 - 
 -         $closedAt ??= now();
 - 
 -         $this->update([
 -             'status' => BudgetStatus::Closed,
 -             'closed_at' => $closedAt,
 -         ]);
 -     }
 - 
 -     /**
 -      * Reopen a closed budget
 -      */
 -     public function reopen(): void
 -     {
 -         if (! $this->isClosed()) {
 -             throw new \RuntimeException('Only closed budgets can be reopened.');
 -         }
 - 
 -         $this->update([
 -             'status' => BudgetStatus::Active,
 -             'closed_at' => null,
 -         ]);
 -     }
 - 
 -     /**
 -      * Get Action for approving a draft budget
 -      */
 -     public static function getApproveDraftAction(string $action = Action::class): MountableAction
 -     {
 -         return $action::make('approveDraft')
 -             ->label('Approve')
 -             ->icon('heroicon-m-check-circle')
 -             ->visible(function (self $record) {
 -                 return $record->canBeApproved();
 -             })
 -             ->databaseTransaction()
 -             ->successNotificationTitle('Budget approved')
 -             ->action(function (self $record, MountableAction $action) {
 -                 $record->approveDraft();
 -                 $action->success();
 -             });
 -     }
 - 
 -     /**
 -      * Get Action for closing an active budget
 -      */
 -     public static function getCloseAction(string $action = Action::class): MountableAction
 -     {
 -         return $action::make('close')
 -             ->label('Close')
 -             ->icon('heroicon-m-lock-closed')
 -             ->color('warning')
 -             ->visible(function (self $record) {
 -                 return $record->canBeClosed();
 -             })
 -             ->requiresConfirmation()
 -             ->databaseTransaction()
 -             ->successNotificationTitle('Budget closed')
 -             ->action(function (self $record, MountableAction $action) {
 -                 $record->close();
 -                 $action->success();
 -             });
 -     }
 - 
 -     /**
 -      * Get Action for reopening a closed budget
 -      */
 -     public static function getReopenAction(string $action = Action::class): MountableAction
 -     {
 -         return $action::make('reopen')
 -             ->label('Reopen')
 -             ->icon('heroicon-m-lock-open')
 -             ->visible(function (self $record) {
 -                 return $record->isClosed();
 -             })
 -             ->requiresConfirmation()
 -             ->databaseTransaction()
 -             ->successNotificationTitle('Budget reopened')
 -             ->action(function (self $record, MountableAction $action) {
 -                 $record->reopen();
 -                 $action->success();
 -             });
 -     }
 - 
 -     /**
 -      * Get Action for duplicating a budget
 -      */
 -     public static function getReplicateAction(string $action = ReplicateAction::class): MountableAction
 -     {
 -         return $action::make()
 -             ->excludeAttributes([
 -                 'status',
 -                 'approved_at',
 -                 'closed_at',
 -                 'created_by',
 -                 'updated_by',
 -                 'created_at',
 -                 'updated_at',
 -             ])
 -             ->modal(false)
 -             ->beforeReplicaSaved(function (self $original, self $replica) {
 -                 $replica->status = BudgetStatus::Draft;
 -                 $replica->name = $replica->name . ' (Copy)';
 -             })
 -             ->databaseTransaction()
 -             ->after(function (self $original, self $replica) {
 -                 // Clone budget items and their allocations
 -                 $original->budgetItems->each(function (BudgetItem $item) use ($replica) {
 -                     $newItem = $item->replicate([
 -                         'budget_id',
 -                         'created_by',
 -                         'updated_by',
 -                         'created_at',
 -                         'updated_at',
 -                     ]);
 - 
 -                     $newItem->budget_id = $replica->id;
 -                     $newItem->save();
 - 
 -                     // Clone the allocations for this budget item
 -                     $item->allocations->each(function (BudgetAllocation $allocation) use ($newItem) {
 -                         $newAllocation = $allocation->replicate([
 -                             'budget_item_id',
 -                             'created_at',
 -                             'updated_at',
 -                         ]);
 - 
 -                         $newAllocation->budget_item_id = $newItem->id;
 -                         $newAllocation->save();
 -                     });
 -                 });
 -             })
 -             ->successRedirectUrl(static function (self $replica) {
 -                 return BudgetResource::getUrl('edit', ['record' => $replica]);
 -             });
 -     }
 - }
 
 
  |