feat(storefront): expose event date notices
This commit is contained in:
parent
0d54887602
commit
61314d5166
|
|
@ -32,6 +32,7 @@ class TenantBootstrapService
|
|||
return $this->tenantInformationService->load(
|
||||
$tenant,
|
||||
[
|
||||
'eventDateChanges',
|
||||
'menues' => fn ($query) => $query->whereHas(
|
||||
'roles',
|
||||
fn ($query) => $query->where('codigo', RoleCode::User->value)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace App\Domains\Event\Resources;
|
||||
|
||||
use App\Domains\Event\Models\EventDateChange;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
/** @mixin EventDateChange */
|
||||
class EventDateChangeResource extends JsonResource
|
||||
{
|
||||
/** @return array<string, mixed> */
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'type' => $this->change_type->value,
|
||||
'source_event_date_id' => $this->source_event_date_id,
|
||||
'destination_event_date_id' => $this->destination_event_date_id,
|
||||
'previous_date' => $this->previous_date->format('Y-m-d'),
|
||||
'new_date' => $this->new_date?->format('Y-m-d'),
|
||||
'occurred_at' => $this->created_at->toISOString(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
<?php
|
||||
|
||||
namespace App\Domains\Event\Services;
|
||||
|
||||
use App\Domains\Event\Enums\EventDateChangeType;
|
||||
use App\Domains\Event\Models\EventDateChange;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class EventDateNoticeFormatter
|
||||
{
|
||||
public function __construct(private readonly EventDateTextFormatter $dateTextFormatter) {}
|
||||
|
||||
/**
|
||||
* @param Collection<int, EventDateChange> $changes
|
||||
* @return list<array{
|
||||
* type: string,
|
||||
* title: string,
|
||||
* message: list<array{text: string, bold: bool}>
|
||||
* }>
|
||||
*/
|
||||
public function format(Collection $changes): array
|
||||
{
|
||||
return collect([
|
||||
$this->suspensionNotice(
|
||||
$changes->where('change_type', EventDateChangeType::Suspended)
|
||||
),
|
||||
$this->rescheduleNotice(
|
||||
$changes->where('change_type', EventDateChangeType::Rescheduled)
|
||||
),
|
||||
])->filter()->values()->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Collection<int, EventDateChange> $changes
|
||||
* @return array{type: string, title: string, message: list<array{text: string, bold: bool}>}|null
|
||||
*/
|
||||
private function suspensionNotice(Collection $changes): ?array
|
||||
{
|
||||
$dates = $this->formatDates($changes, 'previous_date');
|
||||
|
||||
if ($dates === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$plural = $changes->count() > 1;
|
||||
|
||||
return [
|
||||
'type' => EventDateChangeType::Suspended->value,
|
||||
'title' => $plural ? 'FECHAS CANCELADAS!' : 'FECHA CANCELADA!',
|
||||
'message' => [
|
||||
['text' => $plural ? 'Las fechas del ' : 'La fecha del ', 'bold' => false],
|
||||
['text' => $dates, 'bold' => true],
|
||||
['text' => $plural ? ' han sido canceladas.' : ' ha sido cancelada.', 'bold' => false],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Collection<int, EventDateChange> $changes
|
||||
* @return array{type: string, title: string, message: list<array{text: string, bold: bool}>}|null
|
||||
*/
|
||||
private function rescheduleNotice(Collection $changes): ?array
|
||||
{
|
||||
$changes = $changes->whereNotNull('new_date');
|
||||
$sourceDates = $this->formatDates($changes, 'previous_date');
|
||||
$destinationDates = $this->formatDates($changes, 'new_date');
|
||||
|
||||
if ($sourceDates === null || $destinationDates === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$plural = $changes->count() > 1;
|
||||
$message = [
|
||||
['text' => $plural ? 'Las fechas del ' : 'La fecha del ', 'bold' => false],
|
||||
['text' => $sourceDates, 'bold' => true],
|
||||
[
|
||||
'text' => $plural ? ' han sido reprogramadas para el ' : ' ha sido reprogramada para el ',
|
||||
'bold' => false,
|
||||
],
|
||||
['text' => $destinationDates, 'bold' => true],
|
||||
];
|
||||
|
||||
if ($plural) {
|
||||
$message[] = ['text' => ', ', 'bold' => false];
|
||||
$message[] = ['text' => 'respectivamente', 'bold' => true];
|
||||
}
|
||||
|
||||
$message[] = ['text' => '.', 'bold' => false];
|
||||
|
||||
return [
|
||||
'type' => EventDateChangeType::Rescheduled->value,
|
||||
'title' => $plural ? 'FECHAS REPROGRAMADAS!' : 'FECHA REPROGRAMADA!',
|
||||
'message' => $message,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Collection<int, EventDateChange> $changes
|
||||
*/
|
||||
private function formatDates(Collection $changes, string $attribute): ?string
|
||||
{
|
||||
return $this->dateTextFormatter->formatForSentence(
|
||||
$changes
|
||||
->pluck($attribute)
|
||||
->filter()
|
||||
->map(fn ($date): string => $date->format('Y-m-d'))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -25,6 +25,21 @@ class EventDateTextFormatter
|
|||
/** @param iterable<string> $dates */
|
||||
public function format(iterable $dates): ?string
|
||||
{
|
||||
return $this->formatWithOptions($dates, false, false);
|
||||
}
|
||||
|
||||
/** @param iterable<string> $dates */
|
||||
public function formatForSentence(iterable $dates): ?string
|
||||
{
|
||||
return $this->formatWithOptions($dates, true, true);
|
||||
}
|
||||
|
||||
/** @param iterable<string> $dates */
|
||||
private function formatWithOptions(
|
||||
iterable $dates,
|
||||
bool $padDays,
|
||||
bool $includeYearPreposition,
|
||||
): ?string {
|
||||
$normalizedDates = collect($dates)
|
||||
->map(fn (string $date): DateTimeImmutable => new DateTimeImmutable($date))
|
||||
->unique(fn (DateTimeImmutable $date): string => $date->format('Y-m-d'))
|
||||
|
|
@ -37,12 +52,14 @@ class EventDateTextFormatter
|
|||
|
||||
$years = $normalizedDates
|
||||
->groupBy(fn (DateTimeImmutable $date): string => $date->format('Y'))
|
||||
->map(function ($yearDates, string $year): string {
|
||||
->map(function ($yearDates, string $year) use ($padDays, $includeYearPreposition): string {
|
||||
$months = $yearDates
|
||||
->groupBy(fn (DateTimeImmutable $date): string => $date->format('n'))
|
||||
->map(function ($monthDates, string $month): string {
|
||||
->map(function ($monthDates, string $month) use ($padDays): string {
|
||||
$days = $monthDates
|
||||
->map(fn (DateTimeImmutable $date): string => (string) ((int) $date->format('j')))
|
||||
->map(fn (DateTimeImmutable $date): string => $padDays
|
||||
? $date->format('d')
|
||||
: (string) ((int) $date->format('j')))
|
||||
->values()
|
||||
->all();
|
||||
|
||||
|
|
@ -51,7 +68,7 @@ class EventDateTextFormatter
|
|||
->values()
|
||||
->all();
|
||||
|
||||
return $this->join($months).' '.$year;
|
||||
return $this->join($months).($includeYearPreposition ? ' de ' : ' ').$year;
|
||||
})
|
||||
->values()
|
||||
->all();
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ use App\Domains\Catalog\Models\CatalogItem;
|
|||
use App\Domains\Catalog\Models\Category;
|
||||
use App\Domains\Client\Models\Client;
|
||||
use App\Domains\Event\Models\EventDate;
|
||||
use App\Domains\Event\Models\EventDateChange;
|
||||
use App\Domains\Menu\Models\Menu;
|
||||
use App\Domains\Menu\Models\TenantMenu;
|
||||
use App\Domains\Tenant\Enums\CartEditingPolicy;
|
||||
|
|
@ -229,6 +230,14 @@ class Tenant extends Model
|
|||
->orderBy('time_start');
|
||||
}
|
||||
|
||||
/** @return HasMany<EventDateChange, $this> */
|
||||
public function eventDateChanges(): HasMany
|
||||
{
|
||||
return $this->hasMany(EventDateChange::class, 'tenant_code', 'codigo')
|
||||
->orderBy('created_at')
|
||||
->orderBy('id');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return HasMany<Category, $this>
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ use App\Domains\Attachable\Models\Attachment;
|
|||
use App\Domains\Attachable\Models\AttachmentCrop;
|
||||
use App\Domains\Catalog\Models\Category;
|
||||
use App\Domains\Event\Models\EventDate;
|
||||
use App\Domains\Event\Resources\EventDateChangeResource;
|
||||
use App\Domains\Event\Services\EventDateNoticeFormatter;
|
||||
use App\Domains\Menu\Models\Menu;
|
||||
use App\Domains\Tenant\Models\Tenant;
|
||||
use Illuminate\Http\Request;
|
||||
|
|
@ -61,6 +63,14 @@ class TenantResource extends JsonResource
|
|||
'time_start' => $eventDate->time_start,
|
||||
'time_end' => $eventDate->time_end,
|
||||
])->values(),
|
||||
'date_changes' => $this->whenLoaded(
|
||||
'eventDateChanges',
|
||||
fn () => EventDateChangeResource::collection($this->eventDateChanges)
|
||||
),
|
||||
'date_notices' => $this->whenLoaded(
|
||||
'eventDateChanges',
|
||||
fn () => app(EventDateNoticeFormatter::class)->format($this->eventDateChanges)
|
||||
),
|
||||
]),
|
||||
'extras' => $this->whenLoaded(
|
||||
'websiteExtras',
|
||||
|
|
|
|||
|
|
@ -309,6 +309,21 @@ class AdminAppEventControllerTest extends TestCase
|
|||
->assertOk()
|
||||
->assertJsonCount(1, 'data.event.dates')
|
||||
->assertJsonPath('data.event.dates.0.id', $destination->id)
|
||||
->assertJsonCount(1, 'data.event.date_changes')
|
||||
->assertJsonPath('data.event.date_changes.0.type', 'rescheduled')
|
||||
->assertJsonPath('data.event.date_changes.0.source_event_date_id', $original->id)
|
||||
->assertJsonPath('data.event.date_changes.0.destination_event_date_id', $destination->id)
|
||||
->assertJsonPath('data.event.date_changes.0.previous_date', '2027-10-09')
|
||||
->assertJsonPath('data.event.date_changes.0.new_date', '2027-10-20')
|
||||
->assertJsonPath('data.event.date_changes.0.occurred_at', fn ($value) => is_string($value))
|
||||
->assertJsonMissingPath('data.event.date_changes.0.created_by_user_id')
|
||||
->assertJsonCount(1, 'data.event.date_notices')
|
||||
->assertJsonPath('data.event.date_notices.0.type', 'rescheduled')
|
||||
->assertJsonPath('data.event.date_notices.0.title', 'FECHA REPROGRAMADA!')
|
||||
->assertJsonPath('data.event.date_notices.0.message.0.text', 'La fecha del ')
|
||||
->assertJsonPath('data.event.date_notices.0.message.1.text', '09 de Octubre de 2027')
|
||||
->assertJsonPath('data.event.date_notices.0.message.1.bold', true)
|
||||
->assertJsonPath('data.event.date_notices.0.message.3.text', '20 de Octubre de 2027')
|
||||
->assertJsonPath('data.event_date_text', '20 de Octubre 2027');
|
||||
$this->assertSame(
|
||||
'2027-10-20 11:00:00',
|
||||
|
|
@ -344,6 +359,15 @@ class AdminAppEventControllerTest extends TestCase
|
|||
->assertOk()
|
||||
->assertJsonCount(1, 'data.event.dates')
|
||||
->assertJsonPath('data.event.dates.0.date', '2027-10-25')
|
||||
->assertJsonCount(2, 'data.event.date_changes')
|
||||
->assertJsonPath('data.event.date_changes.1.type', 'rescheduled')
|
||||
->assertJsonPath('data.event.date_changes.1.previous_date', '2027-10-20')
|
||||
->assertJsonPath('data.event.date_changes.1.new_date', '2027-10-25')
|
||||
->assertJsonPath('data.event.date_notices.0.title', 'FECHAS REPROGRAMADAS!')
|
||||
->assertJsonPath('data.event.date_notices.0.message.1.text', '09 y 20 de Octubre de 2027')
|
||||
->assertJsonPath('data.event.date_notices.0.message.3.text', '20 y 25 de Octubre de 2027')
|
||||
->assertJsonPath('data.event.date_notices.0.message.5.text', 'respectivamente')
|
||||
->assertJsonPath('data.event.date_notices.0.message.5.bold', true)
|
||||
->assertJsonPath('data.event_date_text', '25 de Octubre 2027');
|
||||
$this->assertSame(
|
||||
'2027-10-25 11:00:00',
|
||||
|
|
@ -443,6 +467,18 @@ class AdminAppEventControllerTest extends TestCase
|
|||
->assertOk()
|
||||
->assertJsonCount(1, 'data.event.dates')
|
||||
->assertJsonPath('data.event.dates.0.id', $otherDate->id)
|
||||
->assertJsonCount(1, 'data.event.date_changes')
|
||||
->assertJsonPath('data.event.date_changes.0.type', 'suspended')
|
||||
->assertJsonPath('data.event.date_changes.0.source_event_date_id', $suspendedDate->id)
|
||||
->assertJsonPath('data.event.date_changes.0.destination_event_date_id', null)
|
||||
->assertJsonPath('data.event.date_changes.0.previous_date', '2027-10-09')
|
||||
->assertJsonPath('data.event.date_changes.0.new_date', null)
|
||||
->assertJsonMissingPath('data.event.date_changes.0.created_by_user_id')
|
||||
->assertJsonCount(1, 'data.event.date_notices')
|
||||
->assertJsonPath('data.event.date_notices.0.type', 'suspended')
|
||||
->assertJsonPath('data.event.date_notices.0.title', 'FECHA CANCELADA!')
|
||||
->assertJsonPath('data.event.date_notices.0.message.1.text', '09 de Octubre de 2027')
|
||||
->assertJsonPath('data.event.date_notices.0.message.1.bold', true)
|
||||
->assertJsonPath('data.event_date_text', '10 de Octubre 2027');
|
||||
$this->assertSame(
|
||||
'2027-10-10 09:00:00',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,97 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Unit\Event;
|
||||
|
||||
use App\Domains\Event\Enums\EventDateChangeType;
|
||||
use App\Domains\Event\Models\EventDateChange;
|
||||
use App\Domains\Event\Services\EventDateNoticeFormatter;
|
||||
use App\Domains\Event\Services\EventDateTextFormatter;
|
||||
use Tests\TestCase;
|
||||
|
||||
class EventDateNoticeFormatterTest extends TestCase
|
||||
{
|
||||
public function test_it_formats_singular_suspension_and_reschedule_notices(): void
|
||||
{
|
||||
$formatter = new EventDateNoticeFormatter(new EventDateTextFormatter);
|
||||
|
||||
$notices = $formatter->format(collect([
|
||||
$this->change(EventDateChangeType::Suspended, '2026-10-09'),
|
||||
$this->change(EventDateChangeType::Rescheduled, '2026-10-10', '2026-10-13'),
|
||||
]));
|
||||
|
||||
$this->assertSame([
|
||||
[
|
||||
'type' => 'suspended',
|
||||
'title' => 'FECHA CANCELADA!',
|
||||
'message' => [
|
||||
['text' => 'La fecha del ', 'bold' => false],
|
||||
['text' => '09 de Octubre de 2026', 'bold' => true],
|
||||
['text' => ' ha sido cancelada.', 'bold' => false],
|
||||
],
|
||||
],
|
||||
[
|
||||
'type' => 'rescheduled',
|
||||
'title' => 'FECHA REPROGRAMADA!',
|
||||
'message' => [
|
||||
['text' => 'La fecha del ', 'bold' => false],
|
||||
['text' => '10 de Octubre de 2026', 'bold' => true],
|
||||
['text' => ' ha sido reprogramada para el ', 'bold' => false],
|
||||
['text' => '13 de Octubre de 2026', 'bold' => true],
|
||||
['text' => '.', 'bold' => false],
|
||||
],
|
||||
],
|
||||
], $notices);
|
||||
}
|
||||
|
||||
public function test_it_formats_suspension_and_reschedule_notices_for_the_storefront(): void
|
||||
{
|
||||
$formatter = new EventDateNoticeFormatter(new EventDateTextFormatter);
|
||||
|
||||
$notices = $formatter->format(collect([
|
||||
$this->change(EventDateChangeType::Rescheduled, '2026-10-09', '2026-10-13'),
|
||||
$this->change(EventDateChangeType::Suspended, '2026-10-09'),
|
||||
$this->change(EventDateChangeType::Suspended, '2026-10-10'),
|
||||
$this->change(EventDateChangeType::Rescheduled, '2026-10-10', '2026-10-14'),
|
||||
]));
|
||||
|
||||
$this->assertSame([
|
||||
[
|
||||
'type' => 'suspended',
|
||||
'title' => 'FECHAS CANCELADAS!',
|
||||
'message' => [
|
||||
['text' => 'Las fechas del ', 'bold' => false],
|
||||
['text' => '09 y 10 de Octubre de 2026', 'bold' => true],
|
||||
['text' => ' han sido canceladas.', 'bold' => false],
|
||||
],
|
||||
],
|
||||
[
|
||||
'type' => 'rescheduled',
|
||||
'title' => 'FECHAS REPROGRAMADAS!',
|
||||
'message' => [
|
||||
['text' => 'Las fechas del ', 'bold' => false],
|
||||
['text' => '09 y 10 de Octubre de 2026', 'bold' => true],
|
||||
['text' => ' han sido reprogramadas para el ', 'bold' => false],
|
||||
['text' => '13 y 14 de Octubre de 2026', 'bold' => true],
|
||||
['text' => ', ', 'bold' => false],
|
||||
['text' => 'respectivamente', 'bold' => true],
|
||||
['text' => '.', 'bold' => false],
|
||||
],
|
||||
],
|
||||
], $notices);
|
||||
}
|
||||
|
||||
private function change(
|
||||
EventDateChangeType $type,
|
||||
string $previousDate,
|
||||
?string $newDate = null,
|
||||
): EventDateChange {
|
||||
$change = new EventDateChange;
|
||||
$change->setRawAttributes([
|
||||
'change_type' => $type->value,
|
||||
'previous_date' => $previousDate,
|
||||
'new_date' => $newDate,
|
||||
]);
|
||||
|
||||
return $change;
|
||||
}
|
||||
}
|
||||
|
|
@ -35,4 +35,12 @@ class EventDateTextFormatterTest extends TestCase
|
|||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function test_it_formats_dates_for_use_inside_sentences(): void
|
||||
{
|
||||
$this->assertSame(
|
||||
'09 y 10 de Octubre de 2026',
|
||||
(new EventDateTextFormatter)->formatForSentence(['2026-10-10', '2026-10-09'])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue