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:
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Domains\Catalog\Models;
|
||||
|
||||
use App\Domains\Ticket\Models\ValidityTime;
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
@@ -9,6 +10,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
#[Fillable([
|
||||
'attribute_id',
|
||||
'validity_time_id',
|
||||
'value',
|
||||
'label',
|
||||
'sort_order',
|
||||
@@ -26,6 +28,7 @@ class AttributeOption extends Model
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'validity_time_id' => 'integer',
|
||||
'sort_order' => 'integer',
|
||||
'metadata' => 'array',
|
||||
];
|
||||
@@ -38,4 +41,10 @@ class AttributeOption extends Model
|
||||
{
|
||||
return $this->belongsTo(Attribute::class, 'attribute_id');
|
||||
}
|
||||
|
||||
/** @return BelongsTo<ValidityTime, $this> */
|
||||
public function validityTime(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(ValidityTime::class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ use App\Domains\Catalog\Services\CatalogInventoryService;
|
||||
use App\Domains\Event\Models\Event;
|
||||
use App\Domains\Tenant\Models\Tenant;
|
||||
use App\Domains\Ticket\Models\Ticket;
|
||||
use Carbon\CarbonInterface;
|
||||
use App\Domains\Ticket\Models\ValidityTime;
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
@@ -32,8 +32,7 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
'precio',
|
||||
'inventory_policy',
|
||||
'has_tickets',
|
||||
'maximum_use_date',
|
||||
'minimum_use_date',
|
||||
'validity_time_id',
|
||||
])]
|
||||
class CatalogItem extends Model
|
||||
{
|
||||
@@ -61,8 +60,7 @@ class CatalogItem extends Model
|
||||
'precio' => 'decimal:2',
|
||||
'inventory_policy' => InventoryPolicy::class,
|
||||
'has_tickets' => 'boolean',
|
||||
'maximum_use_date' => 'datetime',
|
||||
'minimum_use_date' => 'datetime',
|
||||
'validity_time_id' => 'integer',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -120,6 +118,12 @@ class CatalogItem extends Model
|
||||
return $this->hasMany(Ticket::class, 'source_catalog_item_id');
|
||||
}
|
||||
|
||||
/** @return BelongsTo<ValidityTime, $this> */
|
||||
public function validityTime(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(ValidityTime::class);
|
||||
}
|
||||
|
||||
/** @return BelongsToMany<Attribute, $this> */
|
||||
public function attributes(): BelongsToMany
|
||||
{
|
||||
@@ -183,16 +187,6 @@ class CatalogItem extends Model
|
||||
return $this->nombre;
|
||||
}
|
||||
|
||||
public function getMinimumUseDate(): ?CarbonInterface
|
||||
{
|
||||
return $this->minimum_use_date;
|
||||
}
|
||||
|
||||
public function getMaximumUseDate(): ?CarbonInterface
|
||||
{
|
||||
return $this->maximum_use_date;
|
||||
}
|
||||
|
||||
public function isBundle(): bool
|
||||
{
|
||||
return $this->type === CatalogItemType::Bundle;
|
||||
|
||||
@@ -5,7 +5,6 @@ namespace App\Domains\Catalog\Models;
|
||||
use App\Domains\Attachable\Models\Attachment;
|
||||
use App\Domains\Event\Models\EventDate;
|
||||
use App\Domains\Ticket\Models\Ticket;
|
||||
use Carbon\CarbonInterface;
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
@@ -17,8 +16,6 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
'catalog_item_id',
|
||||
'event_date_id',
|
||||
'inventory_id',
|
||||
'minimum_use_date',
|
||||
'maximum_use_date',
|
||||
])]
|
||||
class Variant extends Model
|
||||
{
|
||||
@@ -34,8 +31,6 @@ class Variant extends Model
|
||||
'catalog_item_id' => 'integer',
|
||||
'event_date_id' => 'integer',
|
||||
'inventory_id' => 'integer',
|
||||
'minimum_use_date' => 'datetime',
|
||||
'maximum_use_date' => 'datetime',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -97,18 +92,4 @@ class Variant extends Model
|
||||
{
|
||||
return $this->catalogItem->nombre;
|
||||
}
|
||||
|
||||
public function getMinimumUseDate(): ?CarbonInterface
|
||||
{
|
||||
return $this->eventDate?->startsAt()
|
||||
?? $this->minimum_use_date
|
||||
?? $this->catalogItem->getMinimumUseDate();
|
||||
}
|
||||
|
||||
public function getMaximumUseDate(): ?CarbonInterface
|
||||
{
|
||||
return $this->eventDate?->endsAt()
|
||||
?? $this->maximum_use_date
|
||||
?? $this->catalogItem->getMaximumUseDate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,8 +69,7 @@ class StoreCatalogItemRequest extends FormRequest
|
||||
'precio' => ['required', 'numeric', 'min:0'],
|
||||
'inventory_policy' => [Rule::prohibitedIf($isBundle), 'sometimes', Rule::enum(InventoryPolicy::class)],
|
||||
'has_tickets' => [Rule::prohibitedIf($isBundle), 'sometimes', 'boolean'],
|
||||
'minimum_use_date' => [Rule::prohibitedIf($isBundle), 'sometimes', 'nullable', 'date'],
|
||||
'maximum_use_date' => [Rule::prohibitedIf($isBundle), 'sometimes', 'nullable', 'date', 'after_or_equal:minimum_use_date'],
|
||||
'validity_time_id' => [Rule::prohibitedIf($isBundle), 'sometimes', 'nullable', 'integer', Rule::exists('validity_times', 'id')],
|
||||
'real_stock' => [Rule::prohibitedIf($isBundle), 'sometimes', 'integer', 'min:0'],
|
||||
'inventory_id' => ['prohibited'],
|
||||
'reserved_stock' => ['prohibited'],
|
||||
@@ -99,13 +98,6 @@ class StoreCatalogItemRequest extends FormRequest
|
||||
'variants.*.inventory_id' => ['prohibited'],
|
||||
'variants.*.reserved_stock' => ['prohibited'],
|
||||
'variants.*.sold_units' => ['prohibited'],
|
||||
'variants.*.minimum_use_date' => ['sometimes', 'nullable', 'date'],
|
||||
'variants.*.maximum_use_date' => [
|
||||
'sometimes',
|
||||
'nullable',
|
||||
'date',
|
||||
'after_or_equal:variants.*.minimum_use_date',
|
||||
],
|
||||
'variants.*.values' => ['sometimes', 'array'],
|
||||
'variants.*.values.*' => ['nullable', 'string'],
|
||||
'variants.*.images' => ['sometimes', 'array'],
|
||||
|
||||
@@ -34,8 +34,7 @@ class CatalogItemDetailResource extends JsonResource
|
||||
'brand' => $this->brand?->nombre,
|
||||
'inventory_policy' => $this->inventory_policy?->value,
|
||||
'has_tickets' => $this->has_tickets,
|
||||
'minimum_use_date' => $this->minimum_use_date,
|
||||
'maximum_use_date' => $this->maximum_use_date,
|
||||
'validity_time_id' => $this->validity_time_id,
|
||||
'attributes' => $this->itemAttributes
|
||||
->map(fn (ItemAttribute $itemAttribute): array => $this->attributeData($itemAttribute))
|
||||
->values(),
|
||||
@@ -101,6 +100,7 @@ class CatalogItemDetailResource extends JsonResource
|
||||
'value' => $option->value,
|
||||
'label' => $option->label,
|
||||
'sort_order' => $option->sort_order,
|
||||
'validity_time_id' => $option->validity_time_id,
|
||||
'metadata' => $option->metadata,
|
||||
])
|
||||
->values(),
|
||||
@@ -115,14 +115,6 @@ class CatalogItemDetailResource extends JsonResource
|
||||
'event_date_id' => $variant->event_date_id,
|
||||
'event_date' => $variant->eventDate?->date?->format('Y-m-d'),
|
||||
'stock_tecnico' => $this->variantStock($variant),
|
||||
'minimum_use_date' => $variant->minimum_use_date,
|
||||
'maximum_use_date' => $variant->maximum_use_date,
|
||||
'effective_minimum_use_date' => $variant->eventDate?->startsAt()
|
||||
?? $variant->minimum_use_date
|
||||
?? $this->minimum_use_date,
|
||||
'effective_maximum_use_date' => $variant->eventDate?->endsAt()
|
||||
?? $variant->maximum_use_date
|
||||
?? $this->maximum_use_date,
|
||||
'values' => $variant->definitions
|
||||
->mapWithKeys(fn ($definition) => [
|
||||
$definition->itemAttribute?->attribute?->codigo => $definition->value,
|
||||
|
||||
@@ -25,8 +25,7 @@ class CatalogItemResource extends JsonResource
|
||||
'precio' => $this->precio,
|
||||
'inventory_policy' => $this->inventory_policy?->value,
|
||||
'has_tickets' => $this->has_tickets,
|
||||
'minimum_use_date' => $this->minimum_use_date,
|
||||
'maximum_use_date' => $this->maximum_use_date,
|
||||
'validity_time_id' => $this->validity_time_id,
|
||||
'real_stock' => $this->whenLoaded('inventory', fn () => $this->inventory?->real_stock),
|
||||
'images' => $this->whenLoaded('attachments', fn () => $this->attachments
|
||||
->map(fn ($attachment) => $attachment->getTemporaryUrl(1440))
|
||||
@@ -37,14 +36,6 @@ class CatalogItemResource extends JsonResource
|
||||
'event_date_id' => $variant->event_date_id,
|
||||
'event_date' => $variant->eventDate?->date?->format('Y-m-d'),
|
||||
'real_stock' => $variant->inventory?->real_stock,
|
||||
'minimum_use_date' => $variant->minimum_use_date,
|
||||
'maximum_use_date' => $variant->maximum_use_date,
|
||||
'effective_minimum_use_date' => $variant->eventDate?->startsAt()
|
||||
?? $variant->minimum_use_date
|
||||
?? $this->minimum_use_date,
|
||||
'effective_maximum_use_date' => $variant->eventDate?->endsAt()
|
||||
?? $variant->maximum_use_date
|
||||
?? $this->maximum_use_date,
|
||||
'values' => $variant->definitions
|
||||
->mapWithKeys(fn ($definition) => [
|
||||
$definition->itemAttribute?->attribute?->codigo => $definition->value,
|
||||
|
||||
@@ -83,8 +83,6 @@ class CatalogService
|
||||
$data['inventory_id'] = null;
|
||||
$data['inventory_policy'] = null;
|
||||
$data['has_tickets'] = false;
|
||||
$data['minimum_use_date'] = null;
|
||||
$data['maximum_use_date'] = null;
|
||||
} elseif ($hasVariants) {
|
||||
$data['inventory_id'] = null;
|
||||
} else {
|
||||
@@ -381,8 +379,7 @@ class CatalogService
|
||||
'attribute_codes',
|
||||
'variants',
|
||||
'has_tickets',
|
||||
'minimum_use_date',
|
||||
'maximum_use_date',
|
||||
'validity_time_id',
|
||||
] as $field) {
|
||||
if (array_key_exists($field, $data)) {
|
||||
throw ValidationException::withMessages([
|
||||
@@ -508,13 +505,9 @@ class CatalogService
|
||||
$variant = $catalogItem->variants()->create([
|
||||
'inventory_id' => $inventory->id,
|
||||
'event_date_id' => $eventDateId,
|
||||
'minimum_use_date' => $data['minimum_use_date'] ?? null,
|
||||
'maximum_use_date' => $data['maximum_use_date'] ?? null,
|
||||
]);
|
||||
$variant->setRelation('catalogItem', $catalogItem);
|
||||
|
||||
$this->validateVariantUseDates($variant, $index);
|
||||
|
||||
foreach ($data['values'] ?? [] as $attributeCode => $value) {
|
||||
$itemAttribute = $itemAttributes[$attributeCode] ?? null;
|
||||
|
||||
@@ -567,24 +560,6 @@ class CatalogService
|
||||
}
|
||||
}
|
||||
|
||||
private function validateVariantUseDates(Variant $variant, int $index): void
|
||||
{
|
||||
$minimumUseDate = $variant->getMinimumUseDate();
|
||||
$maximumUseDate = $variant->getMaximumUseDate();
|
||||
|
||||
if (
|
||||
$minimumUseDate !== null
|
||||
&& $maximumUseDate !== null
|
||||
&& $maximumUseDate->lessThan($minimumUseDate)
|
||||
) {
|
||||
throw ValidationException::withMessages([
|
||||
"variants.{$index}.maximum_use_date" => [
|
||||
__('api.catalog.invalid_effective_date_range'),
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
* @param array<int, array<string, mixed>> $variants
|
||||
|
||||
Reference in New Issue
Block a user