refactor(backend): reorganize domains into Core, Commerce, Ticketing and Shared
This commit is contained in:
426
app/Domains/Ticketing/Ticket/Models/Ticket.php
Normal file
426
app/Domains/Ticketing/Ticket/Models/Ticket.php
Normal file
@@ -0,0 +1,426 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Ticket\Models;
|
||||
|
||||
use App\Domains\Auth\Models\User;
|
||||
use App\Domains\Catalog\Models\CatalogItem;
|
||||
use App\Domains\Catalog\Models\Variant;
|
||||
use App\Domains\Logging\Models\Concerns\LogsValueChanges;
|
||||
use App\Domains\Purchase\Models\PurchaseItem;
|
||||
use App\Domains\Tenant\Models\Tenant;
|
||||
use App\Domains\Ticket\Services\ResolvedTicketValidity;
|
||||
use App\Domains\Ticket\Services\ResolvedValidityGroup;
|
||||
use App\Domains\Ticket\Services\TicketPresentationResolver;
|
||||
use App\Domains\Ticket\Services\TicketValidityResolver;
|
||||
use Carbon\CarbonInterface;
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
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\HasOne;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
#[Fillable([
|
||||
'tenant_code',
|
||||
'ticket',
|
||||
'source_purchase_item_id',
|
||||
'source_catalog_item_id',
|
||||
'source_variant_id',
|
||||
'used_at',
|
||||
'disabled_at',
|
||||
'cancelled_at',
|
||||
'refunded_at',
|
||||
'scanner_user_id',
|
||||
'user_id',
|
||||
])]
|
||||
class Ticket extends Model
|
||||
{
|
||||
use HasFactory, LogsValueChanges;
|
||||
|
||||
private ?ResolvedTicketValidity $resolvedValidity = null;
|
||||
|
||||
public const STATUS_ACTIVE = 'active';
|
||||
|
||||
public const STATUS_EXPIRED = 'expired';
|
||||
|
||||
public const STATUS_USED = 'used';
|
||||
|
||||
public const STATUS_DISABLED = 'disabled';
|
||||
|
||||
public const STATUS_CANCELLED = 'cancelled';
|
||||
|
||||
public const STATUS_REFUNDED = 'refunded';
|
||||
|
||||
public $timestamps = false;
|
||||
|
||||
/** @var list<string> */
|
||||
protected array $loggedAttributes = [
|
||||
'used_at',
|
||||
'disabled_at',
|
||||
'cancelled_at',
|
||||
'refunded_at',
|
||||
];
|
||||
|
||||
protected $appends = [
|
||||
'name',
|
||||
'description',
|
||||
'is_valid',
|
||||
'is_expired',
|
||||
'is_used',
|
||||
'status',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'source_catalog_item_id' => 'integer',
|
||||
'source_variant_id' => 'integer',
|
||||
'source_purchase_item_id' => 'integer',
|
||||
'used_at' => 'datetime',
|
||||
'disabled_at' => 'datetime',
|
||||
'cancelled_at' => 'datetime',
|
||||
'refunded_at' => 'datetime',
|
||||
'scanner_user_id' => 'integer',
|
||||
'user_id' => 'integer',
|
||||
];
|
||||
}
|
||||
|
||||
/** @return list<string> */
|
||||
public static function statuses(): array
|
||||
{
|
||||
return array_keys(self::statusLabels());
|
||||
}
|
||||
|
||||
/** @return array<string, string> */
|
||||
public static function statusLabels(): array
|
||||
{
|
||||
return [
|
||||
self::STATUS_ACTIVE => 'Activo',
|
||||
self::STATUS_USED => 'Usado',
|
||||
self::STATUS_EXPIRED => 'Vencido',
|
||||
self::STATUS_DISABLED => 'Inhabilitado',
|
||||
self::STATUS_CANCELLED => 'Cancelado',
|
||||
self::STATUS_REFUNDED => 'Reembolsado',
|
||||
];
|
||||
}
|
||||
|
||||
/** @return list<array{value: string, label: string}> */
|
||||
public static function statusOptions(): array
|
||||
{
|
||||
return collect(self::statusLabels())
|
||||
->map(fn (string $label, string $status): array => [
|
||||
'value' => $status,
|
||||
'label' => $label,
|
||||
])
|
||||
->values()
|
||||
->all();
|
||||
}
|
||||
|
||||
public static function statusLabel(string $status): string
|
||||
{
|
||||
return self::statusLabels()[$status] ?? $status;
|
||||
}
|
||||
|
||||
protected static function booted(): void
|
||||
{
|
||||
static::saving(function (self $ticket): void {
|
||||
$ticket->ensureTerminalStatusTransitionIsAllowed();
|
||||
});
|
||||
}
|
||||
|
||||
/** @return BelongsTo<Tenant, $this> */
|
||||
public function tenant(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Tenant::class, 'tenant_code', 'codigo');
|
||||
}
|
||||
|
||||
public function allow_refund(): bool
|
||||
{
|
||||
return $this->tenant?->allow_refund() ?? false;
|
||||
}
|
||||
|
||||
public function allowRefund(): bool
|
||||
{
|
||||
return $this->allow_refund();
|
||||
}
|
||||
|
||||
public function getAllowRefundAttribute(): bool
|
||||
{
|
||||
return $this->allow_refund();
|
||||
}
|
||||
|
||||
public function is_active(): bool
|
||||
{
|
||||
return $this->status === self::STATUS_ACTIVE;
|
||||
}
|
||||
|
||||
public function isActive(): bool
|
||||
{
|
||||
return $this->is_active();
|
||||
}
|
||||
|
||||
public function getIsActiveAttribute(): bool
|
||||
{
|
||||
return $this->is_active();
|
||||
}
|
||||
|
||||
public function can_cancel(): bool
|
||||
{
|
||||
return $this->is_active();
|
||||
}
|
||||
|
||||
public function canCancel(): bool
|
||||
{
|
||||
return $this->can_cancel();
|
||||
}
|
||||
|
||||
public function getCanCancelAttribute(): bool
|
||||
{
|
||||
return $this->can_cancel();
|
||||
}
|
||||
|
||||
public function can_refund(): bool
|
||||
{
|
||||
return $this->is_active() && $this->allow_refund();
|
||||
}
|
||||
|
||||
public function canRefund(): bool
|
||||
{
|
||||
return $this->can_refund();
|
||||
}
|
||||
|
||||
public function getCanRefundAttribute(): bool
|
||||
{
|
||||
return $this->can_refund();
|
||||
}
|
||||
|
||||
/** @return BelongsTo<User, $this> */
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
/** @return BelongsTo<User, $this> */
|
||||
public function scannerUser(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'scanner_user_id')->withTrashed();
|
||||
}
|
||||
|
||||
/** @return HasMany<ScanAttempt, $this> */
|
||||
public function scanAttempts(): HasMany
|
||||
{
|
||||
return $this->hasMany(ScanAttempt::class);
|
||||
}
|
||||
|
||||
/** @return BelongsTo<PurchaseItem, $this> */
|
||||
public function sourcePurchaseItem(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(PurchaseItem::class, 'source_purchase_item_id');
|
||||
}
|
||||
|
||||
/** @return HasOne<TicketRefund, $this> */
|
||||
public function refund(): HasOne
|
||||
{
|
||||
return $this->hasOne(TicketRefund::class);
|
||||
}
|
||||
|
||||
/** @return BelongsTo<CatalogItem, $this> */
|
||||
public function sourceCatalogItem(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(CatalogItem::class, 'source_catalog_item_id')->withTrashed();
|
||||
}
|
||||
|
||||
/** @return BelongsTo<Variant, $this> */
|
||||
public function sourceVariant(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Variant::class, 'source_variant_id')->withTrashed();
|
||||
}
|
||||
|
||||
public function isValid(): bool
|
||||
{
|
||||
if ($this->hasTerminalStatus() || $this->used_at !== null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->resolvedValidity()->isValid();
|
||||
}
|
||||
|
||||
public function getIsValidAttribute(): bool
|
||||
{
|
||||
return $this->isValid();
|
||||
}
|
||||
|
||||
public function getIsExpiredAttribute(): bool
|
||||
{
|
||||
return ! $this->hasTerminalStatus()
|
||||
&& $this->used_at === null
|
||||
&& $this->resolvedValidity()->isExpired();
|
||||
}
|
||||
|
||||
public function getIsUsedAttribute(): bool
|
||||
{
|
||||
return $this->used_at !== null;
|
||||
}
|
||||
|
||||
public function getStatusAttribute(): string
|
||||
{
|
||||
if ($this->refunded_at !== null) {
|
||||
return self::STATUS_REFUNDED;
|
||||
}
|
||||
|
||||
if ($this->cancelled_at !== null) {
|
||||
return self::STATUS_CANCELLED;
|
||||
}
|
||||
|
||||
if ($this->disabled_at !== null) {
|
||||
return self::STATUS_DISABLED;
|
||||
}
|
||||
|
||||
if ($this->is_used) {
|
||||
return self::STATUS_USED;
|
||||
}
|
||||
|
||||
if ($this->is_expired) {
|
||||
return self::STATUS_EXPIRED;
|
||||
}
|
||||
|
||||
return self::STATUS_ACTIVE;
|
||||
}
|
||||
|
||||
public function getStatusLabelAttribute(): string
|
||||
{
|
||||
if ($this->status === self::STATUS_REFUNDED && $this->relationLoaded('refund')) {
|
||||
$refund = $this->getRelation('refund');
|
||||
|
||||
if ($refund instanceof TicketRefund) {
|
||||
return $refund->typeLabel();
|
||||
}
|
||||
}
|
||||
|
||||
return self::statusLabel($this->status);
|
||||
}
|
||||
|
||||
public function markAsDisabled(): void
|
||||
{
|
||||
$this->markAsTerminalStatus(self::STATUS_DISABLED);
|
||||
}
|
||||
|
||||
public function markAsCancelled(): void
|
||||
{
|
||||
$this->markAsTerminalStatus(self::STATUS_CANCELLED);
|
||||
}
|
||||
|
||||
public function markAsRefunded(): void
|
||||
{
|
||||
$this->markAsTerminalStatus(self::STATUS_REFUNDED);
|
||||
}
|
||||
|
||||
protected function valueChangeTenantCode(): string
|
||||
{
|
||||
return $this->tenant_code;
|
||||
}
|
||||
|
||||
private function hasTerminalStatus(): bool
|
||||
{
|
||||
return $this->terminalStatus() !== null;
|
||||
}
|
||||
|
||||
private function markAsTerminalStatus(string $status): void
|
||||
{
|
||||
$currentStatus = $this->terminalStatus();
|
||||
|
||||
if ($currentStatus === $status) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($currentStatus !== null) {
|
||||
$this->throwTerminalStatusTransitionException();
|
||||
}
|
||||
|
||||
$this->ensureTerminalStatusTransitionIsAllowed($status);
|
||||
|
||||
$this->{self::terminalStatusTimestampColumn($status)} = now();
|
||||
}
|
||||
|
||||
private function ensureTerminalStatusTransitionIsAllowed(?string $targetStatus = null): void
|
||||
{
|
||||
$currentStatus = $this->terminalStatusFromAttributes($this->getRawOriginal());
|
||||
$nextStatus = $targetStatus ?? $this->terminalStatus();
|
||||
|
||||
if ($currentStatus === null || $nextStatus === null || $currentStatus === $nextStatus) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->throwTerminalStatusTransitionException();
|
||||
}
|
||||
|
||||
private function throwTerminalStatusTransitionException(): never
|
||||
{
|
||||
throw ValidationException::withMessages([
|
||||
'status' => 'No se puede cambiar un ticket con estado terminal a otro estado terminal.',
|
||||
]);
|
||||
}
|
||||
|
||||
private function terminalStatus(): ?string
|
||||
{
|
||||
return $this->terminalStatusFromAttributes($this->getAttributes());
|
||||
}
|
||||
|
||||
/** @param array<string, mixed> $attributes */
|
||||
private function terminalStatusFromAttributes(array $attributes): ?string
|
||||
{
|
||||
foreach ([
|
||||
self::STATUS_REFUNDED,
|
||||
self::STATUS_CANCELLED,
|
||||
self::STATUS_DISABLED,
|
||||
] as $status) {
|
||||
if (($attributes[self::terminalStatusTimestampColumn($status)] ?? null) !== null) {
|
||||
return $status;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static function terminalStatusTimestampColumn(string $status): string
|
||||
{
|
||||
return match ($status) {
|
||||
self::STATUS_DISABLED => 'disabled_at',
|
||||
self::STATUS_CANCELLED => 'cancelled_at',
|
||||
self::STATUS_REFUNDED => 'refunded_at',
|
||||
};
|
||||
}
|
||||
|
||||
public function getNameAttribute(): string
|
||||
{
|
||||
return app(TicketPresentationResolver::class)->name($this);
|
||||
}
|
||||
|
||||
public function getDescriptionAttribute(): string
|
||||
{
|
||||
return app(TicketPresentationResolver::class)->description($this);
|
||||
}
|
||||
|
||||
public function getEffectiveStartsAt(): ?CarbonInterface
|
||||
{
|
||||
return $this->resolvedValidity()->effectiveStartsAt();
|
||||
}
|
||||
|
||||
public function getEffectiveExpiresAt(): ?CarbonInterface
|
||||
{
|
||||
return $this->resolvedValidity()->effectiveExpiresAt();
|
||||
}
|
||||
|
||||
/** @return Collection<int, ResolvedValidityGroup> */
|
||||
public function resolvedValidityGroups(): Collection
|
||||
{
|
||||
return $this->resolvedValidity()->groups;
|
||||
}
|
||||
|
||||
public function resolvedValidity(): ResolvedTicketValidity
|
||||
{
|
||||
return $this->resolvedValidity ??= app(TicketValidityResolver::class)->resolveTicket($this);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user