123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218 |
- <?php
-
- namespace App\Models\Accounting;
-
- use App\Casts\RateCast;
- use App\Concerns\Blamable;
- use App\Concerns\CompanyOwned;
- use App\Enums\Accounting\AdjustmentCategory;
- use App\Enums\Accounting\AdjustmentComputation;
- use App\Enums\Accounting\AdjustmentScope;
- use App\Enums\Accounting\AdjustmentStatus;
- use App\Enums\Accounting\AdjustmentType;
- use App\Models\Common\Offering;
- use App\Observers\AdjustmentObserver;
- use Database\Factories\Accounting\AdjustmentFactory;
- use Illuminate\Database\Eloquent\Attributes\ObservedBy;
- use Illuminate\Database\Eloquent\Factories\Factory;
- use Illuminate\Database\Eloquent\Factories\HasFactory;
- use Illuminate\Database\Eloquent\Model;
- use Illuminate\Database\Eloquent\Relations\BelongsTo;
- use Illuminate\Database\Eloquent\Relations\MorphToMany;
-
- #[ObservedBy(AdjustmentObserver::class)]
- class Adjustment extends Model
- {
- use Blamable;
- use CompanyOwned;
- use HasFactory;
-
- protected $fillable = [
- 'company_id',
- 'account_id',
- 'name',
- 'status',
- 'status_reason',
- 'description',
- 'category',
- 'type',
- 'recoverable',
- 'rate',
- 'computation',
- 'scope',
- 'start_date',
- 'end_date',
- 'paused_at',
- 'paused_until',
- 'archived_at',
- 'created_by',
- 'updated_by',
- ];
-
- protected $casts = [
- 'status' => AdjustmentStatus::class,
- 'category' => AdjustmentCategory::class,
- 'type' => AdjustmentType::class,
- 'recoverable' => 'boolean',
- 'rate' => RateCast::class,
- 'computation' => AdjustmentComputation::class,
- 'scope' => AdjustmentScope::class,
- 'start_date' => 'datetime',
- 'end_date' => 'datetime',
- 'paused_at' => 'datetime',
- 'paused_until' => 'datetime',
- 'archived_at' => 'datetime',
- ];
-
- public function account(): BelongsTo
- {
- return $this->belongsTo(Account::class, 'account_id');
- }
-
- public function offerings(): MorphToMany
- {
- return $this->morphedByMany(Offering::class, 'adjustmentable', 'adjustmentables');
- }
-
- public function isSalesTax(): bool
- {
- return $this->category->isTax() && $this->type->isSales();
- }
-
- public function isNonRecoverablePurchaseTax(): bool
- {
- return $this->category->isTax() && $this->type->isPurchase() && $this->recoverable === false;
- }
-
- public function isRecoverablePurchaseTax(): bool
- {
- return $this->category->isTax() && $this->type->isPurchase() && $this->recoverable === true;
- }
-
- public function isSalesDiscount(): bool
- {
- return $this->category->isDiscount() && $this->type->isSales();
- }
-
- public function isPurchaseDiscount(): bool
- {
- return $this->category->isDiscount() && $this->type->isPurchase();
- }
-
- public function isActive(): bool
- {
- return $this->status === AdjustmentStatus::Active;
- }
-
- public function isInactive(): bool
- {
- return ! $this->isActive();
- }
-
- public function canBePaused(): bool
- {
- return $this->status === AdjustmentStatus::Active;
- }
-
- public function canBeResumed(): bool
- {
- return $this->status === AdjustmentStatus::Paused;
- }
-
- public function canBeArchived(): bool
- {
- return $this->status !== AdjustmentStatus::Archived;
- }
-
- public function calculateNaturalStatus(): AdjustmentStatus
- {
- if ($this->start_date?->isAfter(company_now())) {
- return AdjustmentStatus::Upcoming;
- }
-
- if ($this->end_date?->isBefore(company_now())) {
- return AdjustmentStatus::Expired;
- }
-
- return AdjustmentStatus::Active;
- }
-
- public function pause(?string $reason = null, ?\DateTime $untilDate = null): bool
- {
- if (! $this->canBePaused()) {
- return false;
- }
-
- return $this->update([
- 'paused_at' => company_now(),
- 'paused_until' => $untilDate,
- 'status' => AdjustmentStatus::Paused,
- 'status_reason' => $reason,
- ]);
- }
-
- public function resume(): bool
- {
- if (! $this->canBeResumed()) {
- return false;
- }
-
- return $this->update([
- 'paused_at' => null,
- 'paused_until' => null,
- 'status_reason' => null,
- 'status' => $this->calculateNaturalStatus(),
- ]);
- }
-
- public function archive(?string $reason = null): bool
- {
- if (! $this->canBeArchived()) {
- return false;
- }
-
- return $this->update([
- 'archived_at' => company_now(),
- 'status' => AdjustmentStatus::Archived,
- 'status_reason' => $reason,
- ]);
- }
-
- public function shouldAutoResume(): bool
- {
- return $this->status === AdjustmentStatus::Paused &&
- $this->paused_until !== null &&
- $this->paused_until->isBefore(company_now());
- }
-
- public function refreshStatus(): bool
- {
- // Don't automatically change archived or paused status
- if ($this->status === AdjustmentStatus::Archived ||
- ($this->status === AdjustmentStatus::Paused && ! $this->shouldAutoResume())) {
- return false;
- }
-
- // Check if a paused adjustment should be auto-resumed
- if ($this->shouldAutoResume()) {
- return $this->resume();
- }
-
- // Calculate natural status based on dates
- $naturalStatus = $this->calculateNaturalStatus();
-
- // Only update if the status would change
- if ($this->status !== $naturalStatus) {
- return $this->update([
- 'status' => $naturalStatus,
- ]);
- }
-
- return false;
- }
-
- protected static function newFactory(): Factory
- {
- return AdjustmentFactory::new();
- }
- }
|