feat(refunds): persist and apply refund configuration per event
This commit is contained in:
@@ -15,14 +15,27 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|||||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
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
|
class Event extends Model
|
||||||
{
|
{
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
|
|
||||||
protected function casts(): array
|
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> */
|
/** @return BelongsTo<Tenant, $this> */
|
||||||
|
|||||||
@@ -26,10 +26,10 @@ class EventResource extends JsonResource
|
|||||||
'date_text' => $this->date_text,
|
'date_text' => $this->date_text,
|
||||||
'published_at' => $this->published_at?->toIso8601String(),
|
'published_at' => $this->published_at?->toIso8601String(),
|
||||||
'attachment_id' => $this->attachment_id,
|
'attachment_id' => $this->attachment_id,
|
||||||
'allow_ticket_refund' => $this->tenant->allow_ticket_refund,
|
'allow_ticket_refund' => $this->allow_ticket_refund,
|
||||||
'allow_ticket_total_refund' => $this->tenant->allow_ticket_total_refund,
|
'allow_ticket_total_refund' => $this->allow_ticket_total_refund,
|
||||||
'allow_ticket_partial_refund' => $this->tenant->allow_ticket_partial_refund,
|
'allow_ticket_partial_refund' => $this->allow_ticket_partial_refund,
|
||||||
'ticket_partial_refund_percentage' => $this->tenant->ticket_partial_refund_percentage,
|
'ticket_partial_refund_percentage' => $this->ticket_partial_refund_percentage,
|
||||||
'dates' => EventDateResource::collection(
|
'dates' => EventDateResource::collection(
|
||||||
app(EventDateGroupingService::class)->group($this->dates)
|
app(EventDateGroupingService::class)->group($this->dates)
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ class EventService
|
|||||||
'location' => $data['location'],
|
'location' => $data['location'],
|
||||||
...array_intersect_key($data, ['attachment_id' => true, 'exact_location' => true]),
|
...array_intersect_key($data, ['attachment_id' => true, 'exact_location' => true]),
|
||||||
]);
|
]);
|
||||||
$tenant->update([
|
$event->update([
|
||||||
...array_intersect_key($data, array_flip([
|
...array_intersect_key($data, array_flip([
|
||||||
'allow_ticket_refund',
|
'allow_ticket_refund',
|
||||||
'allow_ticket_total_refund',
|
'allow_ticket_total_refund',
|
||||||
|
|||||||
@@ -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
|
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
|
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.
|
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.
|
||||||
|
|||||||
@@ -148,7 +148,9 @@ class Ticket extends Model
|
|||||||
|
|
||||||
public function allow_refund(): bool
|
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
|
public function allowRefund(): bool
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use App\Domains\Commerce\Purchase\Models\PurchaseItem;
|
|||||||
use App\Domains\Commerce\Purchase\Services\PurchaseRefundSummaryService;
|
use App\Domains\Commerce\Purchase\Services\PurchaseRefundSummaryService;
|
||||||
use App\Domains\Core\Auth\Models\User;
|
use App\Domains\Core\Auth\Models\User;
|
||||||
use App\Domains\Core\Tenant\Models\Tenant;
|
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\Ticket;
|
||||||
use App\Domains\Ticketing\Ticket\Models\TicketRefund;
|
use App\Domains\Ticketing\Ticket\Models\TicketRefund;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
@@ -23,6 +24,7 @@ class AdminAppTicketService
|
|||||||
...TicketValidityResolver::RELATIONS,
|
...TicketValidityResolver::RELATIONS,
|
||||||
...TicketPresentationResolver::RELATIONS,
|
...TicketPresentationResolver::RELATIONS,
|
||||||
'tenant',
|
'tenant',
|
||||||
|
'event',
|
||||||
'user',
|
'user',
|
||||||
'scannerUser',
|
'scannerUser',
|
||||||
'sourceCatalogItem.category',
|
'sourceCatalogItem.category',
|
||||||
@@ -145,19 +147,20 @@ class AdminAppTicketService
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$configuration = $ticket->event_id === null ? $tenant : $ticket->event;
|
||||||
$unitPrice = (float) $purchaseItem->precio_unitario;
|
$unitPrice = (float) $purchaseItem->precio_unitario;
|
||||||
$itemTotal = (float) $purchaseItem->total;
|
$itemTotal = (float) $purchaseItem->total;
|
||||||
$itemRefundedAmount = $this->refundedAmountForPurchaseItem($purchaseItem);
|
$itemRefundedAmount = $this->refundedAmountForPurchaseItem($purchaseItem);
|
||||||
$remainingItemAmount = max(0.0, round($itemTotal - $itemRefundedAmount, 2));
|
$remainingItemAmount = max(0.0, round($itemTotal - $itemRefundedAmount, 2));
|
||||||
|
|
||||||
$total = null;
|
$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, '.', '');
|
$total = number_format($unitPrice, 2, '.', '');
|
||||||
}
|
}
|
||||||
|
|
||||||
$partial = null;
|
$partial = null;
|
||||||
if ($tenant->allow_refund() && $tenant->allow_partial_refund()) {
|
if ($configuration->allow_refund() && $configuration->allow_partial_refund()) {
|
||||||
$partialAmount = $this->refundAmount($purchaseItem, $tenant, 'partial');
|
$partialAmount = $this->refundAmount($purchaseItem, $configuration, 'partial');
|
||||||
if ($partialAmount <= $remainingItemAmount) {
|
if ($partialAmount <= $remainingItemAmount) {
|
||||||
$partial = number_format($partialAmount, 2, '.', '');
|
$partial = number_format($partialAmount, 2, '.', '');
|
||||||
}
|
}
|
||||||
@@ -175,14 +178,15 @@ class AdminAppTicketService
|
|||||||
string $refundType,
|
string $refundType,
|
||||||
?User $createdBy = null,
|
?User $createdBy = null,
|
||||||
): Ticket {
|
): Ticket {
|
||||||
$this->ensureRefundIsAllowed($tenant, $refundType);
|
|
||||||
|
|
||||||
return DB::transaction(function () use ($tenant, $ticketId, $refundType, $createdBy): Ticket {
|
return DB::transaction(function () use ($tenant, $ticketId, $refundType, $createdBy): Ticket {
|
||||||
$ticket = Ticket::query()
|
$ticket = Ticket::query()
|
||||||
->where('tenant_code', $tenant->codigo)
|
->where('tenant_code', $tenant->codigo)
|
||||||
->lockForUpdate()
|
->lockForUpdate()
|
||||||
->findOrFail($ticketId);
|
->findOrFail($ticketId);
|
||||||
|
|
||||||
|
$configuration = $ticket->event_id === null ? $tenant : $ticket->event;
|
||||||
|
$this->ensureRefundIsAllowed($configuration, $refundType);
|
||||||
|
|
||||||
if (! $ticket->can_refund()) {
|
if (! $ticket->can_refund()) {
|
||||||
if ($ticket->status !== Ticket::STATUS_ACTIVE) {
|
if ($ticket->status !== Ticket::STATUS_ACTIVE) {
|
||||||
throw ValidationException::withMessages([
|
throw ValidationException::withMessages([
|
||||||
@@ -205,7 +209,7 @@ class AdminAppTicketService
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$refundAmount = $this->refundAmount($purchaseItem, $tenant, $refundType);
|
$refundAmount = $this->refundAmount($purchaseItem, $configuration, $refundType);
|
||||||
$refundedAmount = round(
|
$refundedAmount = round(
|
||||||
$this->refundedAmountForPurchaseItem($purchaseItem) + $refundAmount,
|
$this->refundedAmountForPurchaseItem($purchaseItem) + $refundAmount,
|
||||||
2,
|
2,
|
||||||
@@ -286,7 +290,7 @@ class AdminAppTicketService
|
|||||||
->sum('amount'), 2);
|
->sum('amount'), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function ensureRefundIsAllowed(Tenant $tenant, string $refundType): void
|
private function ensureRefundIsAllowed(Tenant|Event $tenant, string $refundType): void
|
||||||
{
|
{
|
||||||
$isAllowed = match ($refundType) {
|
$isAllowed = match ($refundType) {
|
||||||
TicketRefund::TYPE_PARTIAL => $tenant->allow_refund() && $tenant->allow_partial_refund(),
|
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;
|
$ticketAmount = (float) $purchaseItem->precio_unitario;
|
||||||
|
|
||||||
|
|||||||
@@ -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',
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user