feat(ticket): implement validity time management for tickets and catalog items

- Added ValidityTime model and migration to manage ticket validity periods.
- Updated TicketGeneratorService to resolve and assign validity times to tickets.
- Refactored ticket generation logic to remove legacy date fields and use validity time.
- Introduced timezone support for tenants to handle service dates correctly.
- Updated migrations to remove deprecated columns and add foreign keys for validity times.
- Modified seeders and tests to accommodate new validity time structure.
- Enhanced tests to validate ticket generation and validity time behavior.
This commit is contained in:
2026-08-06 15:52:19 -03:00
parent 7f01adab73
commit 448ffb4102
30 changed files with 556 additions and 381 deletions

View File

@@ -0,0 +1,22 @@
<?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('tenants', function (Blueprint $table): void {
$table->string('timezone')->default('UTC')->after('dominio');
});
}
public function down(): void
{
Schema::table('tenants', function (Blueprint $table): void {
$table->dropColumn('timezone');
});
}
};

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::create('validity_times', function (Blueprint $table): void {
$table->id();
$table->string('type');
$table->time('start_time')->nullable();
$table->time('end_time')->nullable();
$table->dateTime('fixed_starts_at')->nullable();
$table->dateTime('fixed_expires_at')->nullable();
$table->boolean('active')->default(true);
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('validity_times');
}
};

View File

@@ -0,0 +1,40 @@
<?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('catalog_items', function (Blueprint $table): void {
$table->foreignId('validity_time_id')
->nullable()
->after('has_tickets')
->constrained('validity_times')
->cascadeOnUpdate()
->nullOnDelete();
});
Schema::table('attribute_options', function (Blueprint $table): void {
$table->foreignId('validity_time_id')
->nullable()
->after('attribute_id')
->constrained('validity_times')
->cascadeOnUpdate()
->nullOnDelete();
});
}
public function down(): void
{
Schema::table('attribute_options', function (Blueprint $table): void {
$table->dropConstrainedForeignId('validity_time_id');
});
Schema::table('catalog_items', function (Blueprint $table): void {
$table->dropConstrainedForeignId('validity_time_id');
});
}
};

View File

@@ -0,0 +1,29 @@
<?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('validity_time_id')
->nullable()
->after('source_variant_id')
->constrained('validity_times')
->cascadeOnUpdate()
->restrictOnDelete();
$table->date('service_date')->nullable()->after('validity_time_id');
});
}
public function down(): void
{
Schema::table('tickets', function (Blueprint $table): void {
$table->dropConstrainedForeignId('validity_time_id');
$table->dropColumn('service_date');
});
}
};

View File

@@ -0,0 +1,93 @@
<?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
{
$this->preserveFixedWindows(
'catalog_items',
'minimum_use_date',
'maximum_use_date',
);
$this->preserveFixedWindows('tickets', 'starts_at', 'expires_at');
Schema::table('tickets', function (Blueprint $table): void {
$table->dropColumn(['starts_at', 'expires_at']);
});
Schema::table('catalog_items', function (Blueprint $table): void {
$table->dropColumn(['minimum_use_date', 'maximum_use_date']);
});
Schema::table('variantes', function (Blueprint $table): void {
$table->dropColumn(['minimum_use_date', 'maximum_use_date']);
});
}
public function down(): void
{
Schema::table('variantes', function (Blueprint $table): void {
$table->dateTime('minimum_use_date')->nullable();
$table->dateTime('maximum_use_date')->nullable();
});
Schema::table('catalog_items', function (Blueprint $table): void {
$table->dateTime('minimum_use_date')->nullable();
$table->dateTime('maximum_use_date')->nullable();
});
Schema::table('tickets', function (Blueprint $table): void {
$table->dateTime('starts_at')->nullable();
$table->dateTime('expires_at')->nullable();
});
}
private function preserveFixedWindows(
string $table,
string $startsAtColumn,
string $expiresAtColumn,
): void {
DB::table($table)
->whereNull('validity_time_id')
->where(function ($query) use ($startsAtColumn, $expiresAtColumn): void {
$query->whereNotNull($startsAtColumn)
->orWhereNotNull($expiresAtColumn);
})
->select([$startsAtColumn, $expiresAtColumn])
->distinct()
->get()
->each(function ($window) use ($table, $startsAtColumn, $expiresAtColumn): void {
$startsAt = $window->{$startsAtColumn};
$expiresAt = $window->{$expiresAtColumn};
$validityTimeId = DB::table('validity_times')->insertGetId([
'type' => 'fixed_window',
'start_time' => null,
'end_time' => null,
'fixed_starts_at' => $startsAt,
'fixed_expires_at' => $expiresAt,
'active' => true,
'created_at' => now(),
'updated_at' => now(),
]);
DB::table($table)
->whereNull('validity_time_id')
->when(
$startsAt === null,
fn ($query) => $query->whereNull($startsAtColumn),
fn ($query) => $query->where($startsAtColumn, $startsAt),
)
->when(
$expiresAt === null,
fn ($query) => $query->whereNull($expiresAtColumn),
fn ($query) => $query->where($expiresAtColumn, $expiresAt),
)
->update(['validity_time_id' => $validityTimeId]);
});
}
};