feat(food): add endpoint to update historical stock and separate current and historical food variants
This commit is contained in:
@@ -122,7 +122,11 @@ class EventDate extends Model
|
||||
|
||||
public function endsAt(): CarbonInterface
|
||||
{
|
||||
return Carbon::parse($this->date->format('Y-m-d').' '.$this->time_end);
|
||||
$endsAt = Carbon::parse($this->date->format('Y-m-d').' '.$this->time_end);
|
||||
|
||||
return $endsAt->lessThanOrEqualTo($this->startsAt())
|
||||
? $endsAt->addDay()
|
||||
: $endsAt;
|
||||
}
|
||||
|
||||
public function getStatusAttribute(): EventDateStatus
|
||||
@@ -169,10 +173,6 @@ class EventDate extends Model
|
||||
$startsAt = $this->startsAt();
|
||||
$expiresAt = $this->endsAt();
|
||||
|
||||
if ($expiresAt->lessThanOrEqualTo($startsAt)) {
|
||||
$expiresAt = $expiresAt->addDay();
|
||||
}
|
||||
|
||||
$attributes = [
|
||||
'type' => ValidityTimeType::FixedWindow,
|
||||
'start_time' => null,
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Domains\FiestaFutbolInfantil\Controllers;
|
||||
|
||||
use App\Domains\FiestaFutbolInfantil\Requests\UpsertFoodVariantsRequest;
|
||||
use App\Domains\FiestaFutbolInfantil\Requests\UpdateHistoricalFoodStockRequest;
|
||||
use App\Domains\FiestaFutbolInfantil\Resources\FoodResource;
|
||||
use App\Domains\FiestaFutbolInfantil\Services\FoodService;
|
||||
use App\Http\Controllers\Controller;
|
||||
@@ -32,6 +33,16 @@ class FoodController extends Controller
|
||||
);
|
||||
}
|
||||
|
||||
public function updateHistoricalStock(UpdateHistoricalFoodStockRequest $request): FoodResource
|
||||
{
|
||||
return FoodResource::make(
|
||||
$this->foodService->updateHistoricalStock(
|
||||
$request->user()->tenant()->firstOrFail(),
|
||||
$request->validated('variants'),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function destroy(Request $request, int $food): Response
|
||||
{
|
||||
$this->foodService->delete(
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\FiestaFutbolInfantil\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UpdateHistoricalFoodStockRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @return array<string, mixed> */
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'variants' => ['required', 'array', 'min:1', 'max:500'],
|
||||
'variants.*' => ['required', 'array:id,stock'],
|
||||
'variants.*.id' => ['required', 'integer', 'distinct'],
|
||||
'variants.*.stock' => ['required', 'integer', 'min:0'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,14 @@
|
||||
namespace App\Domains\FiestaFutbolInfantil\Resources;
|
||||
|
||||
use App\Domains\Catalog\Models\CatalogItem;
|
||||
use App\Domains\Catalog\Models\Variant;
|
||||
use App\Domains\Event\Enums\EventDateChangeType;
|
||||
use App\Domains\Event\Enums\EventDateStatus;
|
||||
use App\Domains\Event\Models\EventDate;
|
||||
use App\Domains\Event\Models\EventDateChange;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/** @mixin CatalogItem */
|
||||
class FoodResource extends JsonResource
|
||||
@@ -17,27 +23,134 @@ class FoodResource extends JsonResource
|
||||
'id' => null,
|
||||
'name' => 'Comida',
|
||||
'variants' => [],
|
||||
'history' => [],
|
||||
];
|
||||
}
|
||||
|
||||
$currentVariants = $this->variants
|
||||
->filter(fn (Variant $variant): bool => $this->isCurrent($variant));
|
||||
$historicalVariants = $this->variants
|
||||
->filter(fn (Variant $variant): bool => $this->isHistorical($variant));
|
||||
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'name' => $this->nombre,
|
||||
'variants' => $this->variants->map(function ($variant): array {
|
||||
$values = $variant->selectionValues();
|
||||
$eventDate = $variant->selectedEventDates()->first();
|
||||
|
||||
return [
|
||||
'id' => $variant->id,
|
||||
'event_date_id' => $eventDate?->id,
|
||||
'event_date' => $eventDate?->date?->format('Y-m-d'),
|
||||
'schedule' => $values->get('horario'),
|
||||
'service' => $values->get('servicio'),
|
||||
'description' => $variant->descripcion,
|
||||
'stock' => $variant->inventory->real_stock,
|
||||
'price' => number_format($variant->getPrice(), 2, '.', ''),
|
||||
];
|
||||
})->values(),
|
||||
'variants' => $currentVariants->map($this->variantData(...))->values(),
|
||||
'history' => $this->historyData($historicalVariants),
|
||||
];
|
||||
}
|
||||
|
||||
private function isCurrent(Variant $variant): bool
|
||||
{
|
||||
if ($variant->sales_disabled_at !== null || $variant->replaced_by_variant_id !== null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$status = $variant->selectedEventDates()->first()?->status;
|
||||
|
||||
return $status === null || in_array(
|
||||
$status,
|
||||
[EventDateStatus::Scheduled, EventDateStatus::InProgress],
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
private function isHistorical(Variant $variant): bool
|
||||
{
|
||||
return in_array(
|
||||
$variant->selectedEventDates()->first()?->status,
|
||||
[
|
||||
EventDateStatus::Rescheduled,
|
||||
EventDateStatus::Suspended,
|
||||
EventDateStatus::Completed,
|
||||
],
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
/** @return array<string, mixed> */
|
||||
private function variantData(Variant $variant): array
|
||||
{
|
||||
$values = $variant->selectionValues();
|
||||
$eventDate = $variant->selectedEventDates()->first();
|
||||
|
||||
return [
|
||||
'id' => $variant->id,
|
||||
'event_date_id' => $eventDate?->id,
|
||||
'event_date' => $eventDate?->date?->format('Y-m-d'),
|
||||
'schedule' => $values->get('horario'),
|
||||
'service' => $values->get('servicio'),
|
||||
'description' => $variant->descripcion,
|
||||
'stock' => $variant->inventory->real_stock,
|
||||
'price' => number_format($variant->getPrice(), 2, '.', ''),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Collection<int, Variant> $variants
|
||||
* @return Collection<int, array<string, mixed>>
|
||||
*/
|
||||
private function historyData(Collection $variants): Collection
|
||||
{
|
||||
return $variants
|
||||
->filter(fn (Variant $variant): bool => $variant->selectedEventDates()->first() !== null)
|
||||
->groupBy(fn (Variant $variant): int => (int) $variant->selectedEventDates()->first()->id)
|
||||
->map(function (Collection $dateVariants): array {
|
||||
/** @var EventDate $eventDate */
|
||||
$eventDate = $dateVariants->first()->selectedEventDates()->first();
|
||||
$status = $this->historicalStatus($eventDate);
|
||||
$change = $this->changeForStatus($eventDate, $status);
|
||||
|
||||
return [
|
||||
'id' => $eventDate->id,
|
||||
'change_id' => $change?->id,
|
||||
'status' => $status->value,
|
||||
'status_text' => match ($status) {
|
||||
EventDateStatus::Rescheduled => 'REPROGRAMADA',
|
||||
EventDateStatus::Suspended => 'CANCELADA',
|
||||
EventDateStatus::Completed => 'FINALIZADA',
|
||||
default => '',
|
||||
},
|
||||
'event_date_id' => $eventDate->id,
|
||||
'event_date' => $eventDate->date->format('Y-m-d'),
|
||||
'replacement_event_date_id' => $status === EventDateStatus::Rescheduled
|
||||
? ($change?->destination_event_date_id
|
||||
?? $eventDate->rescheduled_to_event_date_id)
|
||||
: null,
|
||||
'replacement_event_date' => $status === EventDateStatus::Rescheduled
|
||||
? ($change?->new_date?->format('Y-m-d')
|
||||
?? $eventDate->rescheduledTo?->date?->format('Y-m-d'))
|
||||
: null,
|
||||
'occurred_at' => ($change?->created_at ?? $eventDate->endsAt())->toISOString(),
|
||||
'variants' => $dateVariants->map($this->variantData(...))->values(),
|
||||
];
|
||||
})
|
||||
->sortByDesc('occurred_at')
|
||||
->values();
|
||||
}
|
||||
|
||||
private function historicalStatus(EventDate $eventDate): EventDateStatus
|
||||
{
|
||||
return match ($eventDate->status) {
|
||||
EventDateStatus::Rescheduled => EventDateStatus::Rescheduled,
|
||||
EventDateStatus::Suspended => EventDateStatus::Suspended,
|
||||
default => EventDateStatus::Completed,
|
||||
};
|
||||
}
|
||||
|
||||
private function changeForStatus(
|
||||
EventDate $eventDate,
|
||||
EventDateStatus $status,
|
||||
): ?EventDateChange {
|
||||
$changeType = match ($status) {
|
||||
EventDateStatus::Rescheduled => EventDateChangeType::Rescheduled,
|
||||
EventDateStatus::Suspended => EventDateChangeType::Suspended,
|
||||
default => null,
|
||||
};
|
||||
|
||||
return $changeType === null
|
||||
? null
|
||||
: $eventDate->changeHistory
|
||||
->first(fn (EventDateChange $change): bool => $change->change_type === $changeType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ use App\Domains\Catalog\Models\Inventory;
|
||||
use App\Domains\Catalog\Models\ItemAttribute;
|
||||
use App\Domains\Catalog\Models\Variant;
|
||||
use App\Domains\Catalog\Services\CatalogService;
|
||||
use App\Domains\Event\Enums\EventDateStatus;
|
||||
use App\Domains\Event\Models\EventDate;
|
||||
use App\Domains\Tenant\Models\Tenant;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
@@ -36,8 +38,10 @@ class FoodService
|
||||
->with([
|
||||
'variants.catalogItem',
|
||||
'variants.inventory',
|
||||
'variants.eventDate',
|
||||
'variants.eventDates',
|
||||
'variants.eventDate.rescheduledTo',
|
||||
'variants.eventDate.changeHistory.destinationEventDate',
|
||||
'variants.eventDates.rescheduledTo',
|
||||
'variants.eventDates.changeHistory.destinationEventDate',
|
||||
'variants.definitions.itemAttribute.attribute',
|
||||
])
|
||||
->first();
|
||||
@@ -55,11 +59,16 @@ class FoodService
|
||||
|
||||
$food->variants()->whereNull('precio')->update(['precio' => $food->precio]);
|
||||
$existingVariants = $food->variants()
|
||||
->whereNull('sales_disabled_at')
|
||||
->whereNull('replaced_by_variant_id')
|
||||
->with(['inventory', 'eventDate', 'eventDates', 'definitions.itemAttribute.attribute'])
|
||||
->lockForUpdate()
|
||||
->get();
|
||||
->get()
|
||||
->filter(fn (Variant $variant): bool => $this->hasCurrentDate($variant))
|
||||
->values();
|
||||
$resolvedVariants = $this->resolveVariants($variants, $attributes);
|
||||
|
||||
$this->validateCurrentEventDates($tenant, $resolvedVariants);
|
||||
$this->validateCombinations($resolvedVariants, $existingVariants);
|
||||
|
||||
foreach ($resolvedVariants as $index => $data) {
|
||||
@@ -80,7 +89,13 @@ class FoodService
|
||||
}
|
||||
}
|
||||
|
||||
$minimumPrice = $food->variants()->min('precio');
|
||||
$minimumPrice = $food->variants()
|
||||
->whereNull('sales_disabled_at')
|
||||
->whereNull('replaced_by_variant_id')
|
||||
->with(['eventDate', 'eventDates'])
|
||||
->get()
|
||||
->filter(fn (Variant $variant): bool => $this->hasCurrentDate($variant))
|
||||
->min('precio');
|
||||
if ($minimumPrice !== null) {
|
||||
$food->update(['precio' => $minimumPrice]);
|
||||
}
|
||||
@@ -88,22 +103,81 @@ class FoodService
|
||||
return $food->fresh()->load([
|
||||
'variants.catalogItem',
|
||||
'variants.inventory',
|
||||
'variants.eventDate',
|
||||
'variants.eventDates',
|
||||
'variants.eventDate.rescheduledTo',
|
||||
'variants.eventDate.changeHistory.destinationEventDate',
|
||||
'variants.eventDates.rescheduledTo',
|
||||
'variants.eventDates.changeHistory.destinationEventDate',
|
||||
'variants.definitions.itemAttribute.attribute',
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, array{id: int, stock: int}> $variants
|
||||
*/
|
||||
public function updateHistoricalStock(Tenant $tenant, array $variants): CatalogItem
|
||||
{
|
||||
return DB::transaction(function () use ($tenant, $variants): CatalogItem {
|
||||
$food = CatalogItem::query()
|
||||
->where('tenant_code', $tenant->codigo)
|
||||
->where('slug', 'comida')
|
||||
->lockForUpdate()
|
||||
->firstOrFail();
|
||||
$variantIds = collect($variants)->pluck('id')->map(fn ($id): int => (int) $id);
|
||||
$historicalVariants = $food->variants()
|
||||
->whereIn('id', $variantIds)
|
||||
->whereNull('sales_disabled_at')
|
||||
->whereNull('replaced_by_variant_id')
|
||||
->with(['inventory', 'eventDate', 'eventDates'])
|
||||
->lockForUpdate()
|
||||
->get()
|
||||
->filter(fn (Variant $variant): bool => $this->hasHistoricalDate($variant))
|
||||
->keyBy('id');
|
||||
|
||||
foreach ($variants as $index => $data) {
|
||||
$variant = $historicalVariants->get((int) $data['id']);
|
||||
if ($variant === null) {
|
||||
throw ValidationException::withMessages([
|
||||
"variants.{$index}.id" => [
|
||||
'La variante no pertenece al historial de Comida.',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
$stock = (int) $data['stock'];
|
||||
$inventory = Inventory::query()
|
||||
->whereKey($variant->inventory_id)
|
||||
->lockForUpdate()
|
||||
->firstOrFail();
|
||||
if ($stock < $inventory->reserved_stock) {
|
||||
throw ValidationException::withMessages([
|
||||
"variants.{$index}.stock" => [
|
||||
'El stock no puede ser menor que la cantidad actualmente reservada.',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
$inventory->update(['real_stock' => $stock]);
|
||||
}
|
||||
|
||||
return $this->current($tenant) ?? $food;
|
||||
});
|
||||
}
|
||||
|
||||
public function delete(Tenant $tenant, int $foodId): void
|
||||
{
|
||||
$variant = Variant::query()
|
||||
->whereKey($foodId)
|
||||
->whereNull('sales_disabled_at')
|
||||
->whereNull('replaced_by_variant_id')
|
||||
->with(['eventDate', 'eventDates'])
|
||||
->whereHas('catalogItem', fn ($query) => $query
|
||||
->where('tenant_code', $tenant->codigo)
|
||||
->where('slug', 'comida'))
|
||||
->firstOrFail();
|
||||
|
||||
abort_unless($this->hasCurrentDate($variant), 404);
|
||||
|
||||
$this->catalogService->deleteVariant($variant);
|
||||
}
|
||||
|
||||
@@ -225,6 +299,30 @@ class FoodService
|
||||
return $option;
|
||||
}
|
||||
|
||||
/** @param array<int, array<string, mixed>> $variants */
|
||||
private function validateCurrentEventDates(Tenant $tenant, array $variants): void
|
||||
{
|
||||
$eventDates = EventDate::query()
|
||||
->where('tenant_code', $tenant->codigo)
|
||||
->whereIn('id', collect($variants)->pluck('event_date_id')->unique())
|
||||
->get()
|
||||
->keyBy('id');
|
||||
|
||||
foreach ($variants as $index => $variant) {
|
||||
$eventDate = $eventDates->get($variant['event_date_id']);
|
||||
|
||||
if ($eventDate !== null && $this->isCurrentStatus($eventDate->status)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
throw ValidationException::withMessages([
|
||||
"variants.{$index}.event_date_id" => [
|
||||
'La fecha seleccionada ya no está disponible.',
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, array<string, mixed>> $incoming
|
||||
* @param Collection<int, Variant> $existing
|
||||
@@ -329,4 +427,25 @@ class FoodService
|
||||
mb_strtolower(trim($service)),
|
||||
]);
|
||||
}
|
||||
|
||||
private function hasCurrentDate(Variant $variant): bool
|
||||
{
|
||||
$status = $variant->selectedEventDates()->first()?->status;
|
||||
|
||||
return $status === null || $this->isCurrentStatus($status);
|
||||
}
|
||||
|
||||
private function hasHistoricalDate(Variant $variant): bool
|
||||
{
|
||||
return in_array(
|
||||
$variant->selectedEventDates()->first()?->status,
|
||||
[EventDateStatus::Rescheduled, EventDateStatus::Suspended, EventDateStatus::Completed],
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
private function isCurrentStatus(EventDateStatus $status): bool
|
||||
{
|
||||
return in_array($status, [EventDateStatus::Scheduled, EventDateStatus::InProgress], true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,9 @@ Route::prefix('v1/adminapp/tenant')
|
||||
Route::post('foods', [FoodController::class, 'store'])
|
||||
->middleware('tenant.menu:adminapp.fiesta-futbol-infantil.comida')
|
||||
->name('adminapp.fiesta-futbol-infantil.foods.store');
|
||||
Route::patch('foods/history-stock', [FoodController::class, 'updateHistoricalStock'])
|
||||
->middleware('tenant.menu:adminapp.fiesta-futbol-infantil.comida')
|
||||
->name('adminapp.fiesta-futbol-infantil.foods.history-stock.update');
|
||||
Route::delete('foods/{food}', [FoodController::class, 'destroy'])
|
||||
->middleware('tenant.menu:adminapp.fiesta-futbol-infantil.comida')
|
||||
->name('adminapp.fiesta-futbol-infantil.foods.destroy');
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Domains\Forms\Services;
|
||||
|
||||
use App\Domains\Catalog\Models\Attribute;
|
||||
use App\Domains\Catalog\Models\AttributeOption;
|
||||
use App\Domains\Event\Enums\EventDateStatus;
|
||||
use App\Domains\Event\Models\EventDate;
|
||||
use App\Domains\Tenant\Models\Tenant;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
@@ -31,7 +32,13 @@ class FoodFormService
|
||||
->whereNull('rescheduled_to_event_date_id')
|
||||
->whereNull('suspended_at')
|
||||
->with('validityTime')
|
||||
->get(),
|
||||
->get()
|
||||
->filter(fn (EventDate $eventDate): bool => in_array(
|
||||
$eventDate->status,
|
||||
[EventDateStatus::Scheduled, EventDateStatus::InProgress],
|
||||
true,
|
||||
))
|
||||
->values(),
|
||||
'schedules' => $attributes->get('horario')?->options ?? new Collection,
|
||||
'services' => $attributes->get('servicio')?->options ?? new Collection,
|
||||
];
|
||||
|
||||
@@ -8,12 +8,14 @@ use App\Domains\Catalog\Models\Attribute;
|
||||
use App\Domains\Catalog\Models\CatalogItem;
|
||||
use App\Domains\Catalog\Models\Category;
|
||||
use App\Domains\Catalog\Models\Variant;
|
||||
use App\Domains\Event\Services\EventService;
|
||||
use App\Domains\Menu\Models\Menu;
|
||||
use App\Domains\Shared\Enums\FieldType;
|
||||
use App\Domains\Tenant\Models\Tenant;
|
||||
use App\Domains\Tenant\Models\WebsiteType;
|
||||
use Database\Seeders\AuthorizationSeeder;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Laravel\Sanctum\Sanctum;
|
||||
use Tests\TestCase;
|
||||
|
||||
@@ -32,6 +34,13 @@ class FoodControllerTest extends TestCase
|
||||
]);
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
Carbon::setTestNow();
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
public function test_authentication_is_required(): void
|
||||
{
|
||||
$this->postJson('/api/v1/adminapp/tenant/foods', ['variants' => []])
|
||||
@@ -142,6 +151,101 @@ class FoodControllerTest extends TestCase
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_it_separates_current_and_historical_food_variants(): void
|
||||
{
|
||||
Carbon::setTestNow('2026-09-01 12:00:00');
|
||||
[$tenant, $rescheduledDate, $suspendedDate] = $this->configuredTenant();
|
||||
$completedDate = $tenant->eventDates()->create([
|
||||
'date' => '2026-10-11',
|
||||
'time_start' => '00:00',
|
||||
'time_end' => '23:59',
|
||||
]);
|
||||
$activeDate = $tenant->eventDates()->create([
|
||||
'date' => '2026-10-21',
|
||||
'time_start' => '00:00',
|
||||
'time_end' => '23:59',
|
||||
]);
|
||||
$admin = $this->createAdminAppUser($tenant);
|
||||
Sanctum::actingAs($admin);
|
||||
|
||||
$this->postJson('/api/v1/adminapp/tenant/foods', [
|
||||
'variants' => [
|
||||
$this->variantPayload($rescheduledDate->id, 'Almuerzo', 'Comedor', 100, 10000),
|
||||
$this->variantPayload($suspendedDate->id, 'Cena', 'Vianda', 90, 9000),
|
||||
$this->variantPayload($completedDate->id, 'Cena', 'Comedor', 80, 8000),
|
||||
$this->variantPayload($activeDate->id, 'Almuerzo', 'Vianda', 70, 7000),
|
||||
],
|
||||
])->assertOk();
|
||||
|
||||
$eventService = app(EventService::class);
|
||||
$eventService->rescheduleDateForTenant(
|
||||
$tenant,
|
||||
$rescheduledDate,
|
||||
['date' => '2026-10-20'],
|
||||
$admin,
|
||||
);
|
||||
$eventService->suspendDateForTenant($tenant, $suspendedDate, $admin);
|
||||
|
||||
Carbon::setTestNow('2026-10-15 12:00:00');
|
||||
$response = $this->getJson('/api/v1/adminapp/tenant/foods')->assertOk();
|
||||
|
||||
$response->assertJsonCount(2, 'data.variants')->assertJsonCount(3, 'data.history');
|
||||
$history = collect($response->json('data.history'));
|
||||
|
||||
$rescheduled = $history->firstWhere('status', 'rescheduled');
|
||||
$this->assertSame('REPROGRAMADA', $rescheduled['status_text']);
|
||||
$this->assertSame('2026-10-09', $rescheduled['event_date']);
|
||||
$this->assertSame('2026-10-20', $rescheduled['replacement_event_date']);
|
||||
$this->assertCount(1, $rescheduled['variants']);
|
||||
|
||||
$suspended = $history->firstWhere('status', 'suspended');
|
||||
$this->assertSame('CANCELADA', $suspended['status_text']);
|
||||
$this->assertNull($suspended['replacement_event_date']);
|
||||
|
||||
$completed = $history->firstWhere('status', 'completed');
|
||||
$this->assertSame('FINALIZADA', $completed['status_text']);
|
||||
$this->assertSame('2026-10-11', $completed['event_date']);
|
||||
$this->assertNull($completed['replacement_event_date']);
|
||||
|
||||
}
|
||||
|
||||
public function test_it_updates_only_the_stock_of_historical_food_variants(): void
|
||||
{
|
||||
[$tenant, $historicalDate, $activeDate] = $this->configuredTenant();
|
||||
$admin = $this->createAdminAppUser($tenant);
|
||||
Sanctum::actingAs($admin);
|
||||
|
||||
$created = $this->postJson('/api/v1/adminapp/tenant/foods', [
|
||||
'variants' => [
|
||||
$this->variantPayload($historicalDate->id, 'Almuerzo', 'Comedor', 100, 10000),
|
||||
$this->variantPayload($activeDate->id, 'Cena', 'Vianda', 80, 8000),
|
||||
],
|
||||
])->assertOk();
|
||||
$historicalVariantId = $created->json('data.variants.0.id');
|
||||
$activeVariantId = $created->json('data.variants.1.id');
|
||||
|
||||
app(EventService::class)->suspendDateForTenant($tenant, $historicalDate, $admin);
|
||||
|
||||
$this->patchJson('/api/v1/adminapp/tenant/foods/history-stock', [
|
||||
'variants' => [['id' => $historicalVariantId, 'stock' => 45]],
|
||||
])
|
||||
->assertOk()
|
||||
->assertJsonPath('data.history.0.variants.0.id', $historicalVariantId)
|
||||
->assertJsonPath('data.history.0.variants.0.stock', 45);
|
||||
|
||||
$historicalInventoryId = Variant::query()->findOrFail($historicalVariantId)->inventory_id;
|
||||
$this->assertDatabaseHas('inventories', [
|
||||
'id' => $historicalInventoryId,
|
||||
'real_stock' => 45,
|
||||
]);
|
||||
|
||||
$this->patchJson('/api/v1/adminapp/tenant/foods/history-stock', [
|
||||
'variants' => [['id' => $activeVariantId, 'stock' => 20]],
|
||||
])
|
||||
->assertUnprocessable()
|
||||
->assertJsonValidationErrors(['variants.0.id']);
|
||||
}
|
||||
|
||||
public function test_it_deletes_food_records_and_removes_the_empty_product(): void
|
||||
{
|
||||
[$tenant, $firstDate, $secondDate] = $this->configuredTenant();
|
||||
|
||||
@@ -65,6 +65,24 @@ class EventModelsTest extends TestCase
|
||||
}
|
||||
}
|
||||
|
||||
public function test_an_overnight_event_finishes_on_the_following_day(): void
|
||||
{
|
||||
Carbon::setTestNow('2026-10-10 01:00:00');
|
||||
|
||||
try {
|
||||
$eventDate = new EventDate([
|
||||
'date' => '2026-10-09',
|
||||
'time_start' => '20:00:00',
|
||||
'time_end' => '02:00:00',
|
||||
]);
|
||||
|
||||
$this->assertSame('2026-10-10 02:00:00', $eventDate->endsAt()->format('Y-m-d H:i:s'));
|
||||
$this->assertSame(EventDateStatus::InProgress, $eventDate->status);
|
||||
} finally {
|
||||
Carbon::setTestNow();
|
||||
}
|
||||
}
|
||||
|
||||
public function test_tenant_has_many_event_dates(): void
|
||||
{
|
||||
$tenant = new Tenant;
|
||||
|
||||
Reference in New Issue
Block a user