feat(tickets): add source_purchase_id to tickets and update related logic

This commit is contained in:
2026-07-28 16:10:37 -03:00
parent 8ab94ec61f
commit 1c870d9cfa
8 changed files with 69 additions and 2 deletions

View File

@@ -55,7 +55,7 @@ class PurchaseController extends Controller
{
$compra = $this->resolveScopedPurchase($tenant, $request->user()->id, $compra);
$compra->loadMissing('items');
$compra->loadMissing('items')->loadCount('tickets');
$compra->items->load('imageAttachment');
return PurchaseResource::make($compra);

View File

@@ -6,6 +6,7 @@ use App\Domains\Auth\Models\User;
use App\Domains\Cart\Models\Cart;
use App\Domains\Purchase\Events\PurchasePaid;
use App\Domains\Tenant\Models\Tenant;
use App\Domains\Ticket\Models\Ticket;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
@@ -88,6 +89,14 @@ class Purchase extends Model
return $this->hasMany(PurchaseItem::class, 'compra_id');
}
/**
* @return HasMany<Ticket, $this>
*/
public function tickets(): HasMany
{
return $this->hasMany(Ticket::class, 'source_purchase_id');
}
/**
* @return HasOne<TelepagosQr, $this>
*/

View File

@@ -20,6 +20,9 @@ class PurchaseResource extends JsonResource
$items = $this->resource->relationLoaded('items')
? $this->resource->getRelation('items')
: collect();
$ticketsCount = array_key_exists('tickets_count', $this->resource->getAttributes())
? (int) $this->resource->getAttribute('tickets_count')
: null;
$subtotal = $items->isNotEmpty()
? $items->reduce(
@@ -51,6 +54,8 @@ class PurchaseResource extends JsonResource
'email' => $this->email,
'items_source' => $items->isNotEmpty() ? 'purchase' : null,
'items' => PurchaseItemResource::collection($items),
'tickets_count' => $this->when($ticketsCount !== null, $ticketsCount),
'has_generated_tickets' => $this->when($ticketsCount !== null, $ticketsCount > 0),
'subtotal' => $this->formatMoney($subtotal),
'total' => $this->formatMoney($total),
];

View File

@@ -45,6 +45,7 @@ class GenerateTicketsForPaidPurchase
$user,
$purchaseItem->cantidad,
$purchaseItem->source_variant_id,
$purchase->getKey(),
);
array_push($ticketIds, ...$generatedTickets->pluck('id')->all());

View File

@@ -3,6 +3,7 @@
namespace App\Domains\Ticket\Models;
use App\Domains\Auth\Models\User;
use App\Domains\Purchase\Models\Purchase;
use App\Domains\Tenant\Models\Tenant;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
@@ -14,6 +15,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
'ticket',
'name',
'description',
'source_purchase_id',
'source_catalog_item_id',
'source_variant_id',
'starts_at',
@@ -38,6 +40,7 @@ class Ticket extends Model
return [
'source_catalog_item_id' => 'integer',
'source_variant_id' => 'integer',
'source_purchase_id' => 'integer',
'starts_at' => 'datetime',
'expires_at' => 'datetime',
'used_at' => 'datetime',
@@ -57,6 +60,12 @@ class Ticket extends Model
return $this->belongsTo(User::class);
}
/** @return BelongsTo<Purchase, $this> */
public function sourcePurchase(): BelongsTo
{
return $this->belongsTo(Purchase::class, 'source_purchase_id');
}
public function isValid(): bool
{
$now = now();

View File

@@ -21,12 +21,13 @@ class TicketGeneratorService
User $user,
int $quantity = 1,
?int $sourceVariantId = null,
?int $sourcePurchaseId = null,
): Collection {
if ($quantity < 1) {
throw TicketGenerationException::invalidQuantity();
}
return DB::transaction(function () use ($catalogItem, $user, $quantity, $sourceVariantId): Collection {
return DB::transaction(function () use ($catalogItem, $user, $quantity, $sourceVariantId, $sourcePurchaseId): Collection {
$targets = $this->resolveTargets(
$catalogItem,
$quantity,
@@ -36,6 +37,7 @@ class TicketGeneratorService
return $targets->map(function (array $target) use (
$catalogItem,
$sourceVariantId,
$sourcePurchaseId,
$user,
): Ticket {
$item = $target['catalog_item'];
@@ -46,6 +48,7 @@ class TicketGeneratorService
'ticket' => (string) Str::uuid(),
'name' => $item->nombre,
'description' => (string) ($item->descripcion ?? ''),
'source_purchase_id' => $sourcePurchaseId,
'source_catalog_item_id' => $catalogItem->getKey(),
'source_variant_id' => $sourceVariantId,
'starts_at' => $selectedItem->getMinimumUseDate(),

View File

@@ -0,0 +1,27 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('tickets', function (Blueprint $table): void {
$table->foreignId('source_purchase_id')
->nullable()
->after('description')
->constrained('compras')
->cascadeOnUpdate()
->nullOnDelete();
});
}
public function down(): void
{
Schema::table('tickets', function (Blueprint $table): void {
$table->dropConstrainedForeignId('source_purchase_id');
});
}
};

View File

@@ -193,6 +193,12 @@ class TicketGeneratorServiceTest extends TestCase
$this->assertDatabaseCount('tickets', 2);
Event::assertDispatchedTimes(TicketsAvailable::class, 1);
$this->actingAs($this->user, 'sanctum')
->getJson("/api/tenants/{$this->tenant->codigo}/compras/{$purchase->id}")
->assertOk()
->assertJsonPath('data.tickets_count', 2)
->assertJsonPath('data.has_generated_tickets', true);
$purchase->markAsPaid();
$this->assertDatabaseCount('tickets', 2);
@@ -209,6 +215,7 @@ class TicketGeneratorServiceTest extends TestCase
$purchase->markAsPaid();
$this->assertDatabaseHas('tickets', [
'source_purchase_id' => $purchase->id,
'source_catalog_item_id' => $item->id,
'source_variant_id' => $variant->id,
]);
@@ -226,6 +233,12 @@ class TicketGeneratorServiceTest extends TestCase
$this->assertSame(Purchase::STATUS_PAID, $purchase->status);
$this->assertDatabaseCount('tickets', 0);
Event::assertNotDispatched(TicketsAvailable::class);
$this->actingAs($this->user, 'sanctum')
->getJson("/api/tenants/{$this->tenant->codigo}/compras/{$purchase->id}")
->assertOk()
->assertJsonPath('data.tickets_count', 0)
->assertJsonPath('data.has_generated_tickets', false);
}
public function test_paid_status_is_confirmed_when_ticket_maximum_use_date_was_reached(): void