parfum/app/Models/Tier.php

146 lines
4.9 KiB
PHP

<?php
namespace App\Models;
use Dyrynda\Database\Support\CascadeSoftDeletes;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Veelasky\LaravelHashId\Eloquent\HashableId;
class Tier extends Model
{
use CascadeSoftDeletes, HasFactory, HashableId, SoftDeletes;
protected $guarded = ['id'];
protected $cascadeDeletes = ['memberships'];
protected function casts(): array
{
return [
'min_spending' => 'int',
'max_spending' => 'int',
];
}
public static function isOverlap(int $minSpending, ?int $maxSpending, ?int $ignoreId = null): bool
{
$maxSpendingCheck = $maxSpending ?? PHP_INT_MAX;
return self::when($ignoreId, fn ($query) => $query->where('id', '!=', $ignoreId))
->get()
->contains(function ($tier) use ($minSpending, $maxSpendingCheck) {
$tierMax = $tier->max_spending ?? PHP_INT_MAX;
return ($minSpending <= $tierMax) && ($maxSpendingCheck >= $tier->min_spending);
});
}
public static function getHighestTier()
{
return self::orderByDesc('min_spending')->first();
}
public static function updatePreviousTierMaxPoints(int $newMinSpending): void
{
$previousTier = self::whereNull('max_spending')
->orWhere('max_spending', '>=', $newMinSpending)
->orderByDesc('min_spending')
->first();
if ($previousTier && is_null($previousTier->max_spending)) {
$previousTier->update(['max_spending' => $newMinSpending - 1]);
}
}
public static function validateTierPoints(int $minSpending, ?int $maxSpending = null, ?int $ignoreId = null): array
{
$errors = [];
// Get the highest tier (excluding current if updating)
$highestTier = self::when($ignoreId, fn ($query) => $query->where('id', '!=', $ignoreId))
->orderByDesc('min_spending')
->first();
// If there's a highest tier with null max_spending, it should be updated first
if ($highestTier && is_null($highestTier->max_spending)) {
if ($minSpending <= $highestTier->min_spending) {
$errors[] = "Min. belanja harus lebih besar dari {$highestTier->min_spending} (tier {$highestTier->name})";
}
} elseif ($highestTier) {
$requiredMinSpending = ($highestTier->max_spending ?? $highestTier->min_spending) + 1;
if ($minSpending <= $highestTier->max_spending) {
$errors[] = "Min. belanja harus lebih besar dari {$highestTier->max_spending} (tier {$highestTier->name})";
}
}
// Check for overlap with existing tiers
if (self::isOverlap($minSpending, $maxSpending, $ignoreId)) {
$errors[] = 'Rentang belanja tier tumpang tindih dengan tier lain';
}
return $errors;
}
public static function reorderTierChain(): void
{
$tiers = self::orderBy('min_spending')->get();
foreach ($tiers as $index => $tier) {
if ($index < $tiers->count() - 1) {
// Set max_spending to min_spending of next tier - 1
$nextTier = $tiers[$index + 1];
$tier->update(['max_spending' => $nextTier->min_spending - 1]);
} else {
// Last tier should have null max_spending
$tier->update(['max_spending' => null]);
}
}
}
public static function canDeleteTier(int $tierId): array
{
$tier = self::find($tierId);
if (! $tier) {
return ['can_delete' => false, 'message' => 'Tier tidak ditemukan'];
}
$tiers = self::orderBy('min_spending')->get();
$tierIndex = $tiers->search(fn ($t) => $t->id === $tierId);
// Can always delete the lowest tier (first tier)
if ($tierIndex === 0) {
return ['can_delete' => true, 'message' => ''];
}
// Can always delete the highest tier (last tier with null max_spending)
if ($tierIndex === $tiers->count() - 1) {
return ['can_delete' => true, 'message' => ''];
}
// Cannot delete middle tiers as it would create gaps in the tier chain
return [
'can_delete' => false,
'message' => 'Tidak dapat menghapus tier tengah karena akan mengacaukan sistem belanja. Tier harus membentuk rantai yang kontinyu.',
];
}
public function memberships(): HasMany
{
return $this->hasMany(Membership::class);
}
public function rewards(): BelongsToMany
{
return $this->belongsToMany(Reward::class);
}
public function vouchers(): BelongsToMany
{
return $this->belongsToMany(Voucher::class);
}
}