feat(refunds): persist and apply refund configuration per event

This commit is contained in:
2026-09-30 15:31:54 -03:00
parent a770d435eb
commit 5a0ce08adb
7 changed files with 77 additions and 16 deletions

View File

@@ -15,14 +15,27 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
#[Fillable(['client_id', 'tenant_code', 'slug', 'event_category_id', 'title', 'subtitle', 'description', 'location', 'exact_location', 'date_text', 'published_at', 'attachment_id'])]
#[Fillable(['client_id', 'tenant_code', 'slug', 'event_category_id', 'title', 'subtitle', 'description', 'location', 'exact_location', 'date_text', 'published_at', 'attachment_id', 'allow_ticket_refund', 'allow_ticket_total_refund', 'allow_ticket_partial_refund', 'ticket_partial_refund_percentage'])]
class Event extends Model
{
use HasFactory;
protected function casts(): array
{
return ['client_id' => 'integer', 'published_at' => 'datetime', 'exact_location' => 'array'];
return ['allow_ticket_refund' => 'boolean', 'allow_ticket_total_refund' => 'boolean',
'allow_ticket_partial_refund' => 'boolean', 'ticket_partial_refund_percentage' => 'decimal:2', 'client_id' => 'integer', 'published_at' => 'datetime', 'exact_location' => 'array'];
}
public function allow_refund(): bool
{
return (bool) $this->allow_ticket_refund
&& ((bool) $this->allow_ticket_total_refund || $this->allow_partial_refund());
}
public function allow_partial_refund(): bool
{
return (bool) $this->allow_ticket_refund && (bool) $this->allow_ticket_partial_refund
&& (float) $this->ticket_partial_refund_percentage > 0;
}
/** @return BelongsTo<Tenant, $this> */

View File

@@ -26,10 +26,10 @@ class EventResource extends JsonResource
'date_text' => $this->date_text,
'published_at' => $this->published_at?->toIso8601String(),
'attachment_id' => $this->attachment_id,
'allow_ticket_refund' => $this->tenant->allow_ticket_refund,
'allow_ticket_total_refund' => $this->tenant->allow_ticket_total_refund,
'allow_ticket_partial_refund' => $this->tenant->allow_ticket_partial_refund,
'ticket_partial_refund_percentage' => $this->tenant->ticket_partial_refund_percentage,
'allow_ticket_refund' => $this->allow_ticket_refund,
'allow_ticket_total_refund' => $this->allow_ticket_total_refund,
'allow_ticket_partial_refund' => $this->allow_ticket_partial_refund,
'ticket_partial_refund_percentage' => $this->ticket_partial_refund_percentage,
'dates' => EventDateResource::collection(
app(EventDateGroupingService::class)->group($this->dates)
),

View File

@@ -49,7 +49,7 @@ class EventService
'location' => $data['location'],
...array_intersect_key($data, ['attachment_id' => true, 'exact_location' => true]),
]);
$tenant->update([
$event->update([
...array_intersect_key($data, array_flip([
'allow_ticket_refund',
'allow_ticket_total_refund',

View File

@@ -53,3 +53,7 @@ una lista de eventos ni existe todavía una pantalla para gestionarlos.
Las fechas se vinculan con variantes de `Catalog`, que a su vez pueden generar
tickets. Los avisos por suspensión y reprogramación se construyen dinámicamente
después de excluir los cambios que el usuario ya vio tres veces.
La configuración de devoluciones se persiste en `events`, se entrega en
`EventResource` y se aplica al evento de cada ticket. La migración inicial copia
los valores anteriores del tenant a sus eventos.

View File

@@ -148,7 +148,9 @@ class Ticket extends Model
public function allow_refund(): bool
{
return $this->tenant?->allow_refund() ?? false;
return $this->event_id === null
? ($this->tenant?->allow_refund() ?? false)
: ($this->event?->allow_refund() ?? false);
}
public function allowRefund(): bool

View File

@@ -9,6 +9,7 @@ use App\Domains\Commerce\Purchase\Models\PurchaseItem;
use App\Domains\Commerce\Purchase\Services\PurchaseRefundSummaryService;
use App\Domains\Core\Auth\Models\User;
use App\Domains\Core\Tenant\Models\Tenant;
use App\Domains\Ticketing\Event\Models\Event;
use App\Domains\Ticketing\Ticket\Models\Ticket;
use App\Domains\Ticketing\Ticket\Models\TicketRefund;
use Illuminate\Database\Eloquent\Builder;
@@ -23,6 +24,7 @@ class AdminAppTicketService
...TicketValidityResolver::RELATIONS,
...TicketPresentationResolver::RELATIONS,
'tenant',
'event',
'user',
'scannerUser',
'sourceCatalogItem.category',
@@ -145,19 +147,20 @@ class AdminAppTicketService
]);
}
$configuration = $ticket->event_id === null ? $tenant : $ticket->event;
$unitPrice = (float) $purchaseItem->precio_unitario;
$itemTotal = (float) $purchaseItem->total;
$itemRefundedAmount = $this->refundedAmountForPurchaseItem($purchaseItem);
$remainingItemAmount = max(0.0, round($itemTotal - $itemRefundedAmount, 2));
$total = null;
if ($tenant->allow_refund() && $tenant->allow_ticket_total_refund && $unitPrice <= $remainingItemAmount) {
if ($configuration->allow_refund() && $configuration->allow_ticket_total_refund && $unitPrice <= $remainingItemAmount) {
$total = number_format($unitPrice, 2, '.', '');
}
$partial = null;
if ($tenant->allow_refund() && $tenant->allow_partial_refund()) {
$partialAmount = $this->refundAmount($purchaseItem, $tenant, 'partial');
if ($configuration->allow_refund() && $configuration->allow_partial_refund()) {
$partialAmount = $this->refundAmount($purchaseItem, $configuration, 'partial');
if ($partialAmount <= $remainingItemAmount) {
$partial = number_format($partialAmount, 2, '.', '');
}
@@ -175,14 +178,15 @@ class AdminAppTicketService
string $refundType,
?User $createdBy = null,
): Ticket {
$this->ensureRefundIsAllowed($tenant, $refundType);
return DB::transaction(function () use ($tenant, $ticketId, $refundType, $createdBy): Ticket {
$ticket = Ticket::query()
->where('tenant_code', $tenant->codigo)
->lockForUpdate()
->findOrFail($ticketId);
$configuration = $ticket->event_id === null ? $tenant : $ticket->event;
$this->ensureRefundIsAllowed($configuration, $refundType);
if (! $ticket->can_refund()) {
if ($ticket->status !== Ticket::STATUS_ACTIVE) {
throw ValidationException::withMessages([
@@ -205,7 +209,7 @@ class AdminAppTicketService
]);
}
$refundAmount = $this->refundAmount($purchaseItem, $tenant, $refundType);
$refundAmount = $this->refundAmount($purchaseItem, $configuration, $refundType);
$refundedAmount = round(
$this->refundedAmountForPurchaseItem($purchaseItem) + $refundAmount,
2,
@@ -286,7 +290,7 @@ class AdminAppTicketService
->sum('amount'), 2);
}
private function ensureRefundIsAllowed(Tenant $tenant, string $refundType): void
private function ensureRefundIsAllowed(Tenant|Event $tenant, string $refundType): void
{
$isAllowed = match ($refundType) {
TicketRefund::TYPE_PARTIAL => $tenant->allow_refund() && $tenant->allow_partial_refund(),
@@ -300,7 +304,7 @@ class AdminAppTicketService
}
}
private function refundAmount(PurchaseItem $purchaseItem, Tenant $tenant, string $refundType): float
private function refundAmount(PurchaseItem $purchaseItem, Tenant|Event $tenant, string $refundType): float
{
$ticketAmount = (float) $purchaseItem->precio_unitario;

View File

@@ -0,0 +1,38 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('events', function (Blueprint $table): void {
$table->boolean('allow_ticket_refund')->default(false);
$table->boolean('allow_ticket_total_refund')->default(false);
$table->boolean('allow_ticket_partial_refund')->default(false);
$table->decimal('ticket_partial_refund_percentage', 5, 2)->default(0);
});
DB::table('tenants')->select([
'codigo', 'allow_ticket_refund', 'allow_ticket_total_refund',
'allow_ticket_partial_refund', 'ticket_partial_refund_percentage',
])->get()->each(function (object $tenant): void {
DB::table('events')->where('tenant_code', $tenant->codigo)->update([
'allow_ticket_refund' => $tenant->allow_ticket_refund,
'allow_ticket_total_refund' => $tenant->allow_ticket_total_refund,
'allow_ticket_partial_refund' => $tenant->allow_ticket_partial_refund,
'ticket_partial_refund_percentage' => $tenant->ticket_partial_refund_percentage ?? 0,
]);
});
}
public function down(): void
{
Schema::table('events', fn (Blueprint $table) => $table->dropColumn([
'allow_ticket_refund', 'allow_ticket_total_refund',
'allow_ticket_partial_refund', 'ticket_partial_refund_percentage',
]));
}
};