feat(event): add per-user date change notices
This commit is contained in:
@@ -5,6 +5,7 @@ namespace App\Domains\Auth\Models;
|
||||
use App\Domains\Authorization\Enums\RoleCode;
|
||||
use App\Domains\Authorization\Models\Role;
|
||||
use App\Domains\Catalog\Models\Category;
|
||||
use App\Domains\Event\Models\EventDateChangeView;
|
||||
use App\Domains\Tenant\Models\Tenant;
|
||||
use App\Domains\Ticket\Models\ScanAttempt;
|
||||
use Database\Factories\UserFactory;
|
||||
@@ -53,6 +54,12 @@ class User extends Authenticatable
|
||||
return $this->hasMany(ScanAttempt::class, 'scanner_user_id');
|
||||
}
|
||||
|
||||
/** @return HasMany<EventDateChangeView, $this> */
|
||||
public function eventDateChangeViews(): HasMany
|
||||
{
|
||||
return $this->hasMany(EventDateChangeView::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BelongsTo<Role, $this>
|
||||
*/
|
||||
|
||||
22
app/Domains/Event/Controllers/EventDateNoticeController.php
Normal file
22
app/Domains/Event/Controllers/EventDateNoticeController.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Controllers;
|
||||
|
||||
use App\Domains\Event\Resources\EventDateNoticeResource;
|
||||
use App\Domains\Event\Services\EventDateNoticeService;
|
||||
use App\Domains\Tenant\Models\Tenant;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
|
||||
|
||||
class EventDateNoticeController extends Controller
|
||||
{
|
||||
public function __construct(private readonly EventDateNoticeService $noticeService) {}
|
||||
|
||||
public function claim(Request $request, Tenant $tenant): AnonymousResourceCollection
|
||||
{
|
||||
return EventDateNoticeResource::collection(
|
||||
$this->noticeService->claimFor($request->user(), $tenant)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ use App\Domains\Tenant\Models\Tenant;
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
#[Fillable([
|
||||
'tenant_code',
|
||||
@@ -58,4 +59,10 @@ class EventDateChange extends Model
|
||||
{
|
||||
return $this->belongsTo(User::class, 'created_by_user_id')->withTrashed();
|
||||
}
|
||||
|
||||
/** @return HasMany<EventDateChangeView, $this> */
|
||||
public function views(): HasMany
|
||||
{
|
||||
return $this->hasMany(EventDateChangeView::class);
|
||||
}
|
||||
}
|
||||
|
||||
41
app/Domains/Event/Models/EventDateChangeView.php
Normal file
41
app/Domains/Event/Models/EventDateChangeView.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Models;
|
||||
|
||||
use App\Domains\Auth\Models\User;
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
#[Fillable([
|
||||
'user_id',
|
||||
'event_date_change_id',
|
||||
'display_count',
|
||||
'last_displayed_at',
|
||||
])]
|
||||
class EventDateChangeView extends Model
|
||||
{
|
||||
protected $table = 'user_event_date_change_views';
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'user_id' => 'integer',
|
||||
'event_date_change_id' => 'integer',
|
||||
'display_count' => 'integer',
|
||||
'last_displayed_at' => 'datetime',
|
||||
];
|
||||
}
|
||||
|
||||
/** @return BelongsTo<User, $this> */
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
/** @return BelongsTo<EventDateChange, $this> */
|
||||
public function eventDateChange(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(EventDateChange::class);
|
||||
}
|
||||
}
|
||||
20
app/Domains/Event/Resources/EventDateNoticeResource.php
Normal file
20
app/Domains/Event/Resources/EventDateNoticeResource.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Resources;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class EventDateNoticeResource extends JsonResource
|
||||
{
|
||||
/** @return array<string, mixed> */
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'type' => $this->resource['type'],
|
||||
'change_ids' => $this->resource['change_ids'],
|
||||
'title' => $this->resource['title'],
|
||||
'message' => $this->resource['message'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ class EventDateNoticeFormatter
|
||||
* @param Collection<int, EventDateChange> $changes
|
||||
* @return list<array{
|
||||
* type: string,
|
||||
* change_ids: list<int>,
|
||||
* title: string,
|
||||
* message: list<array{text: string, bold: bool}>
|
||||
* }>
|
||||
@@ -32,7 +33,7 @@ class EventDateNoticeFormatter
|
||||
|
||||
/**
|
||||
* @param Collection<int, EventDateChange> $changes
|
||||
* @return array{type: string, title: string, message: list<array{text: string, bold: bool}>}|null
|
||||
* @return array{type: string, change_ids: list<int>, title: string, message: list<array{text: string, bold: bool}>}|null
|
||||
*/
|
||||
private function suspensionNotice(Collection $changes): ?array
|
||||
{
|
||||
@@ -46,6 +47,7 @@ class EventDateNoticeFormatter
|
||||
|
||||
return [
|
||||
'type' => EventDateChangeType::Suspended->value,
|
||||
'change_ids' => $this->changeIds($changes),
|
||||
'title' => $plural ? 'FECHAS CANCELADAS!' : 'FECHA CANCELADA!',
|
||||
'message' => [
|
||||
['text' => $plural ? 'Las fechas del ' : 'La fecha del ', 'bold' => false],
|
||||
@@ -57,7 +59,7 @@ class EventDateNoticeFormatter
|
||||
|
||||
/**
|
||||
* @param Collection<int, EventDateChange> $changes
|
||||
* @return array{type: string, title: string, message: list<array{text: string, bold: bool}>}|null
|
||||
* @return array{type: string, change_ids: list<int>, title: string, message: list<array{text: string, bold: bool}>}|null
|
||||
*/
|
||||
private function rescheduleNotice(Collection $changes): ?array
|
||||
{
|
||||
@@ -89,6 +91,7 @@ class EventDateNoticeFormatter
|
||||
|
||||
return [
|
||||
'type' => EventDateChangeType::Rescheduled->value,
|
||||
'change_ids' => $this->changeIds($changes),
|
||||
'title' => $plural ? 'FECHAS REPROGRAMADAS!' : 'FECHA REPROGRAMADA!',
|
||||
'message' => $message,
|
||||
];
|
||||
@@ -106,4 +109,18 @@ class EventDateNoticeFormatter
|
||||
->map(fn ($date): string => $date->format('Y-m-d'))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Collection<int, EventDateChange> $changes
|
||||
* @return list<int>
|
||||
*/
|
||||
private function changeIds(Collection $changes): array
|
||||
{
|
||||
return $changes
|
||||
->pluck('id')
|
||||
->filter(fn ($id): bool => $id !== null)
|
||||
->map(fn ($id): int => (int) $id)
|
||||
->values()
|
||||
->all();
|
||||
}
|
||||
}
|
||||
|
||||
60
app/Domains/Event/Services/EventDateNoticeService.php
Normal file
60
app/Domains/Event/Services/EventDateNoticeService.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Services;
|
||||
|
||||
use App\Domains\Auth\Models\User;
|
||||
use App\Domains\Event\Models\EventDateChange;
|
||||
use App\Domains\Event\Models\EventDateChangeView;
|
||||
use App\Domains\Tenant\Models\Tenant;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class EventDateNoticeService
|
||||
{
|
||||
public const MAX_DISPLAYS = 3;
|
||||
|
||||
public function __construct(private readonly EventDateNoticeFormatter $formatter) {}
|
||||
|
||||
/**
|
||||
* Claims one display of every pending change and returns them grouped by type.
|
||||
*
|
||||
* @return list<array{
|
||||
* type: string,
|
||||
* change_ids: list<int>,
|
||||
* title: string,
|
||||
* message: list<array{text: string, bold: bool}>
|
||||
* }>
|
||||
*/
|
||||
public function claimFor(User $user, Tenant $tenant): array
|
||||
{
|
||||
return DB::transaction(function () use ($user, $tenant): array {
|
||||
$lockedUser = User::query()->whereKey($user->getKey())->lockForUpdate()->firstOrFail();
|
||||
|
||||
$changes = EventDateChange::query()
|
||||
->where('tenant_code', $tenant->codigo)
|
||||
->whereDoesntHave('views', fn ($query) => $query
|
||||
->where('user_id', $lockedUser->getKey())
|
||||
->where('display_count', '>=', self::MAX_DISPLAYS))
|
||||
->orderBy('created_at')
|
||||
->orderBy('id')
|
||||
->get();
|
||||
|
||||
$notices = $this->formatter->format($changes);
|
||||
$claimedChangeIds = collect($notices)->pluck('change_ids')->flatten()->unique();
|
||||
|
||||
foreach ($claimedChangeIds as $changeId) {
|
||||
$view = EventDateChangeView::query()->firstOrNew([
|
||||
'user_id' => $lockedUser->getKey(),
|
||||
'event_date_change_id' => $changeId,
|
||||
]);
|
||||
$view->display_count = min(
|
||||
self::MAX_DISPLAYS,
|
||||
((int) $view->display_count) + 1,
|
||||
);
|
||||
$view->last_displayed_at = now();
|
||||
$view->save();
|
||||
}
|
||||
|
||||
return $notices;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ Administra la configuración temporal de un tenant orientado a eventos y sus fec
|
||||
|
||||
- `Models/EventDate.php`: fecha del evento con inicio, fin, tenant y variantes asociadas.
|
||||
- `Services/EventService.php`: obtiene y actualiza la configuración de evento del tenant.
|
||||
- `Services/EventDateNoticeService.php`: reclama y agrupa los cambios pendientes de cada usuario.
|
||||
- `Controllers/AdminApp/EventController.php`: consulta y modificación desde AdminApp.
|
||||
- `UpdateEventRequest`: valida datos y reglas cruzadas de fechas.
|
||||
- `EventResource`: serializa la configuración de salida.
|
||||
@@ -19,10 +20,18 @@ Bajo `/v1/adminapp/tenant/event`, protegidos por `auth:sanctum` y `adminapp.tena
|
||||
- `GET`: obtiene la configuración.
|
||||
- `PUT`: actualiza la configuración.
|
||||
|
||||
Para el storefront autenticado:
|
||||
|
||||
- `POST /tenants/{tenant}/event-date-notices/claim`: devuelve hasta un aviso de suspensiones y otro de
|
||||
reprogramaciones. Cada cambio se muestra como máximo tres veces por usuario.
|
||||
|
||||
## Dependencias
|
||||
|
||||
Depende de `Tenant`. Las fechas se vinculan con variantes de `Catalog`, que a su vez pueden generar tickets.
|
||||
|
||||
## Consideraciones
|
||||
|
||||
El archivo `routes/api.php` no publica operaciones adicionales. Al modificar fechas debe mantenerse la validación de orden y coherencia temporal de `UpdateEventRequest`.
|
||||
Los avisos se construyen dinámicamente después de excluir los cambios que el usuario ya vio
|
||||
tres veces. Al reclamar los avisos se incrementa una vez cada cambio incluido, aunque varios
|
||||
cambios aparezcan agrupados en el mismo mensaje. El reclamo bloquea al usuario durante la
|
||||
transacción para impedir que pestañas concurrentes superen el máximo.
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
<?php
|
||||
|
||||
use App\Domains\Event\Controllers\EventDateNoticeController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
require __DIR__.'/adminapp.php';
|
||||
|
||||
Route::middleware('auth:sanctum')->post(
|
||||
'tenants/{tenant:codigo}/event-date-notices/claim',
|
||||
[EventDateNoticeController::class, 'claim'],
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user