implemented event package addons with filament resource, event-admin purchase path and notifications, showing up in purchase history
This commit is contained in:
@@ -5,6 +5,7 @@ namespace App\Models;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class EventPackage extends Model
|
||||
{
|
||||
@@ -20,6 +21,10 @@ class EventPackage extends Model
|
||||
'used_photos',
|
||||
'used_guests',
|
||||
'gallery_expires_at',
|
||||
'limits_snapshot',
|
||||
'extra_photos',
|
||||
'extra_guests',
|
||||
'extra_gallery_days',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
@@ -30,6 +35,10 @@ class EventPackage extends Model
|
||||
'gallery_expired_notified_at' => 'datetime',
|
||||
'used_photos' => 'integer',
|
||||
'used_guests' => 'integer',
|
||||
'extra_photos' => 'integer',
|
||||
'extra_guests' => 'integer',
|
||||
'extra_gallery_days' => 'integer',
|
||||
'limits_snapshot' => 'array',
|
||||
];
|
||||
|
||||
public function event(): BelongsTo
|
||||
@@ -42,6 +51,11 @@ class EventPackage extends Model
|
||||
return $this->belongsTo(Package::class)->withTrashed();
|
||||
}
|
||||
|
||||
public function addons(): HasMany
|
||||
{
|
||||
return $this->hasMany(EventPackageAddon::class);
|
||||
}
|
||||
|
||||
public function isActive(): bool
|
||||
{
|
||||
return $this->gallery_expires_at && $this->gallery_expires_at->isFuture();
|
||||
@@ -53,7 +67,11 @@ class EventPackage extends Model
|
||||
return false;
|
||||
}
|
||||
|
||||
$maxPhotos = $this->package->max_photos ?? 0;
|
||||
$maxPhotos = $this->effectiveLimits()['max_photos'];
|
||||
|
||||
if ($maxPhotos === null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this->used_photos < $maxPhotos;
|
||||
}
|
||||
@@ -64,23 +82,84 @@ class EventPackage extends Model
|
||||
return false;
|
||||
}
|
||||
|
||||
$maxGuests = $this->package->max_guests ?? 0;
|
||||
$maxGuests = $this->effectiveLimits()['max_guests'];
|
||||
|
||||
if ($maxGuests === null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this->used_guests < $maxGuests;
|
||||
}
|
||||
|
||||
public function getRemainingPhotosAttribute(): int
|
||||
{
|
||||
$max = $this->package->max_photos ?? 0;
|
||||
$limit = $this->effectiveLimits()['max_photos'] ?? 0;
|
||||
|
||||
return max(0, $max - $this->used_photos);
|
||||
return max(0, (int) $limit - $this->used_photos);
|
||||
}
|
||||
|
||||
public function getRemainingGuestsAttribute(): int
|
||||
{
|
||||
$max = $this->package->max_guests ?? 0;
|
||||
$limit = $this->effectiveLimits()['max_guests'] ?? 0;
|
||||
|
||||
return max(0, $max - $this->used_guests);
|
||||
return max(0, (int) $limit - $this->used_guests);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{max_photos: ?int, max_guests: ?int, gallery_days: ?int, max_tasks: ?int, max_events_per_year: ?int}
|
||||
*/
|
||||
public function effectiveLimits(): array
|
||||
{
|
||||
$snapshot = is_array($this->limits_snapshot) ? $this->limits_snapshot : [];
|
||||
|
||||
$base = [
|
||||
'max_photos' => array_key_exists('max_photos', $snapshot)
|
||||
? $snapshot['max_photos']
|
||||
: ($this->package->max_photos ?? null),
|
||||
'max_guests' => array_key_exists('max_guests', $snapshot)
|
||||
? $snapshot['max_guests']
|
||||
: ($this->package->max_guests ?? null),
|
||||
'gallery_days' => array_key_exists('gallery_days', $snapshot)
|
||||
? $snapshot['gallery_days']
|
||||
: ($this->package->gallery_days ?? null),
|
||||
'max_tasks' => array_key_exists('max_tasks', $snapshot)
|
||||
? $snapshot['max_tasks']
|
||||
: ($this->package->max_tasks ?? null),
|
||||
'max_events_per_year' => array_key_exists('max_events_per_year', $snapshot)
|
||||
? $snapshot['max_events_per_year']
|
||||
: ($this->package->max_events_per_year ?? null),
|
||||
];
|
||||
|
||||
$applyExtra = static function (?int $limit, int $extra): ?int {
|
||||
if ($limit === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$safeExtra = max(0, $extra);
|
||||
|
||||
return max(0, $limit + $safeExtra);
|
||||
};
|
||||
|
||||
$maxPhotos = $applyExtra($this->normalizeLimit($base['max_photos']), (int) ($this->extra_photos ?? 0));
|
||||
$maxGuests = $applyExtra($this->normalizeLimit($base['max_guests']), (int) ($this->extra_guests ?? 0));
|
||||
|
||||
return [
|
||||
'max_photos' => $maxPhotos,
|
||||
'max_guests' => $maxGuests,
|
||||
'gallery_days' => $this->normalizeLimit($base['gallery_days']),
|
||||
'max_tasks' => $this->normalizeLimit($base['max_tasks']),
|
||||
'max_events_per_year' => $this->normalizeLimit($base['max_events_per_year']),
|
||||
];
|
||||
}
|
||||
|
||||
public function effectivePhotoLimit(): ?int
|
||||
{
|
||||
return $this->effectiveLimits()['max_photos'];
|
||||
}
|
||||
|
||||
public function effectiveGuestLimit(): ?int
|
||||
{
|
||||
return $this->effectiveLimits()['max_guests'];
|
||||
}
|
||||
|
||||
protected static function boot()
|
||||
@@ -95,6 +174,31 @@ class EventPackage extends Model
|
||||
$days = $eventPackage->package->gallery_days ?? 30;
|
||||
$eventPackage->gallery_expires_at = now()->addDays($days);
|
||||
}
|
||||
|
||||
if (! $eventPackage->limits_snapshot) {
|
||||
$package = $eventPackage->relationLoaded('package')
|
||||
? $eventPackage->package
|
||||
: Package::query()->find($eventPackage->package_id);
|
||||
|
||||
if ($package) {
|
||||
$eventPackage->limits_snapshot = array_filter([
|
||||
'max_photos' => $package->max_photos,
|
||||
'max_guests' => $package->max_guests,
|
||||
'gallery_days' => $package->gallery_days,
|
||||
'max_tasks' => $package->max_tasks,
|
||||
'max_events_per_year' => $package->max_events_per_year,
|
||||
], static fn ($value) => $value !== null);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private function normalizeLimit($value): ?int
|
||||
{
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return is_numeric($value) ? (int) $value : null;
|
||||
}
|
||||
}
|
||||
|
||||
66
app/Models/EventPackageAddon.php
Normal file
66
app/Models/EventPackageAddon.php
Normal file
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class EventPackageAddon extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'event_package_id',
|
||||
'event_id',
|
||||
'tenant_id',
|
||||
'addon_key',
|
||||
'quantity',
|
||||
'extra_photos',
|
||||
'extra_guests',
|
||||
'extra_gallery_days',
|
||||
'price_id',
|
||||
'checkout_id',
|
||||
'transaction_id',
|
||||
'status',
|
||||
'amount',
|
||||
'currency',
|
||||
'metadata',
|
||||
'receipt_payload',
|
||||
'error',
|
||||
'purchased_at',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'metadata' => 'array',
|
||||
'receipt_payload' => 'array',
|
||||
'purchased_at' => 'datetime',
|
||||
];
|
||||
|
||||
protected function increments(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn () => [
|
||||
'extra_photos' => (int) ($this->extra_photos ?? 0),
|
||||
'extra_guests' => (int) ($this->extra_guests ?? 0),
|
||||
'extra_gallery_days' => (int) ($this->extra_gallery_days ?? 0),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
public function eventPackage(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(EventPackage::class);
|
||||
}
|
||||
|
||||
public function event(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Event::class);
|
||||
}
|
||||
|
||||
public function tenant(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Tenant::class);
|
||||
}
|
||||
}
|
||||
44
app/Models/PackageAddon.php
Normal file
44
app/Models/PackageAddon.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class PackageAddon extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'key',
|
||||
'label',
|
||||
'price_id',
|
||||
'extra_photos',
|
||||
'extra_guests',
|
||||
'extra_gallery_days',
|
||||
'active',
|
||||
'sort',
|
||||
'metadata',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'active' => 'boolean',
|
||||
'metadata' => 'array',
|
||||
'extra_photos' => 'integer',
|
||||
'extra_guests' => 'integer',
|
||||
'extra_gallery_days' => 'integer',
|
||||
'sort' => 'integer',
|
||||
];
|
||||
|
||||
protected function increments(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn () => [
|
||||
'extra_photos' => (int) ($this->extra_photos ?? 0),
|
||||
'extra_guests' => (int) ($this->extra_guests ?? 0),
|
||||
'extra_gallery_days' => (int) ($this->extra_gallery_days ?? 0),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user