File: /var/www/api-talleres.bradford/app/Models/ClubOrder.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class ClubOrder extends Model
{
use HasFactory;
protected $table = 'club_orders';
protected $primaryKey = 'id';
protected $fillable = [
'code',
'parent_id',
'period_id',
'status',
'items_count',
'subtotal',
'discount_percent',
'discount_amount',
'total',
'reservation_minutes',
'expires_at',
'paid_at',
'confirmed_at',
'failed_at',
'payment_method',
'payment_reference',
'checkout_url',
'payment_response',
'log_id',
'ip_address',
'user_agent',
'notes',
'deleted',
'deleted_at',
'user_created',
'user_updated',
'user_deleted',
];
protected $hidden = [
'deleted',
'deleted_at',
'user_deleted',
'payment_response',
'user_agent',
];
protected $casts = [
'items_count' => 'integer',
'subtotal' => 'decimal:0',
'discount_percent' => 'decimal:2',
'discount_amount' => 'decimal:0',
'total' => 'decimal:0',
'reservation_minutes' => 'integer',
'expires_at' => 'datetime',
'paid_at' => 'datetime',
'confirmed_at' => 'datetime',
'failed_at' => 'datetime',
];
// =========================================================================
// RELACIONES
// =========================================================================
/**
* Apoderado que realiza el pedido.
*/
public function parent()
{
return $this->belongsTo(Parents::class, 'parent_id');
}
/**
* Período académico del pedido.
*/
public function period()
{
return $this->belongsTo(Period::class, 'period_id');
}
/**
* Items del pedido (inscripciones).
*/
public function items()
{
return $this->hasMany(ClubOrderItem::class, 'club_order_id');
}
/**
* Estado del pedido (desde catálogo).
*/
public function statusInfo()
{
return $this->belongsTo(ClubOrderStatus::class, 'status', 'code');
}
/**
* Webhook log asociado al pago de este pedido.
*/
public function webhookLog()
{
return $this->belongsTo(WebhookLog::class, 'log_id');
}
/**
* Transacción estructurada (a través del webhook log).
*/
public function getTransactionAttribute()
{
return $this->webhookLog ? $this->webhookLog->transaction : null;
}
public function createdBy()
{
return $this->belongsTo(User::class, 'user_created');
}
public function updatedBy()
{
return $this->belongsTo(User::class, 'user_updated');
}
public function deletedBy()
{
return $this->belongsTo(User::class, 'user_deleted');
}
// =========================================================================
// HELPERS
// =========================================================================
/**
* Verifica si el pedido está expirado.
*/
public function isExpired(): bool
{
return $this->status === 'expired' ||
($this->expires_at && now()->greaterThan($this->expires_at));
}
/**
* Verifica si el pedido está activo (reserva en curso).
*/
public function isActive(): bool
{
return $this->status === 'active' && !$this->isExpired();
}
/**
* Verifica si el pedido está confirmado.
*/
public function isConfirmed(): bool
{
return $this->status === 'confirmed';
}
/**
* Verifica si el pedido está pagado.
*/
public function isPaid(): bool
{
return in_array($this->status, ['paid', 'confirmed']);
}
/**
* Verifica si el pedido falló.
*/
public function isFailed(): bool
{
return $this->status === 'failed';
}
/**
* Verifica si el pedido es gratuito (total = 0).
*/
public function isFree(): bool
{
return $this->total == 0;
}
/**
* Obtiene los segundos restantes de la reserva.
*/
public function getRemainingSeconds(): int
{
if (!$this->expires_at || $this->isExpired()) {
return 0;
}
return max(0, now()->diffInSeconds($this->expires_at, false));
}
/**
* Recalcula los totales del pedido.
*/
public function recalculateTotals(): void
{
$items = $this->items()->whereNull('cancelled_at')->get();
$this->items_count = $items->count();
$this->subtotal = $items->sum('price_snapshot');
$this->discount_percent = null;
$this->discount_amount = 0;
$this->total = $this->subtotal;
}
// =========================================================================
// SCOPES
// =========================================================================
/**
* Pedidos activos (con reserva en curso).
*/
public function scopeActive($query)
{
return $query->where('status', 'active')
->where('expires_at', '>', now());
}
/**
* Pedidos expirados pendientes de procesar.
*/
public function scopeExpiredPending($query)
{
return $query->where('status', 'active')
->where('expires_at', '<=', now());
}
/**
* Pedidos de un apoderado.
*/
public function scopeForParent($query, int $parentId)
{
return $query->where('parent_id', $parentId);
}
/**
* Pedidos de un período.
*/
public function scopeForPeriod($query, int $periodId)
{
return $query->where('period_id', $periodId);
}
/**
* Pedidos no eliminados.
*/
public function scopeNotDeleted($query)
{
return $query->where('deleted', 0);
}
}