FIX EV3-466 hétvégi szállítási napok engedélyezése a heti sablonból
A DeliveryCalendarService 4. prioritása a WorkCalendarService::isWorkDay()-t hívta, ami a naptárban nem szereplő napoknál a hétvégére esett vissza. Így egy hétvégét is tartalmazó sablon szombat/vasárnap mezőit sosem érte el a kiértékelés (5. prioritás), és csak egyedi felülbírálással lehetett hétvégi szállítást engedni. - WorkCalendarService: új isHoliday(), ami csak a work_calendars ünnepnap rekordjaira ad true-t, a naptári hétvégére nem. Az isWorkDay() változatlan. - DeliveryCalendarService: a 4. prioritás mostantól isHoliday()-t használ, így a hétvégéről a heti sablon dönt. Ünnepnap továbbra is tilt. - NextDeliveryDateCalculatorService: a szállítási nap keresése kikerült az isWorkDay() blokkból, különben a hétvége a következő szállítási nap számításánál továbbra is kimaradt volna. Az átfutási időbe az új countsTowardLeadTime() szerint a sablon hétvégi napjai is beleszámítanak, a H-P sablonok viselkedése változatlan. - DeliveryCalendarWidget: a szürke "inaktív" festés csak a nem szállítási hétvégékre megy rá. Mellékesen a szállítási napok ciklusa $date->addDay()-jel mutálta a kollekció Carbon objektumait, ez copy()-ra javítva. 7 új teszt, köztük a H-P sablon regresszió-őre. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
3cfaa6c9d5
commit
d26ba5e5e6
@ -139,12 +139,17 @@ public function fetchEvents(array $info): array
|
|||||||
$assignment
|
$assignment
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// A szállítási napok halmaza a hétvége-festéshez (4. pont) is kell.
|
||||||
|
$deliveryDayLookup = array_flip(
|
||||||
|
$deliveryDates->map(fn (Carbon $date) => $date->format('Y-m-d'))->all()
|
||||||
|
);
|
||||||
|
|
||||||
foreach ($deliveryDates as $date) {
|
foreach ($deliveryDates as $date) {
|
||||||
$events[] = [
|
$events[] = [
|
||||||
'id' => 'delivery-' . $date->format('Y-m-d'),
|
'id' => 'delivery-' . $date->format('Y-m-d'),
|
||||||
'title' => '',
|
'title' => '',
|
||||||
'start' => $date->format('Y-m-d'),
|
'start' => $date->format('Y-m-d'),
|
||||||
'end' => $date->addDay()->format('Y-m-d'),
|
'end' => $date->copy()->addDay()->format('Y-m-d'),
|
||||||
'allDay' => true,
|
'allDay' => true,
|
||||||
'display' => 'background',
|
'display' => 'background',
|
||||||
'backgroundColor' => '#4CAF50',
|
'backgroundColor' => '#4CAF50',
|
||||||
@ -182,9 +187,11 @@ public function fetchEvents(array $info): array
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 4. Hétvégék – szürke háttér (inaktív napok)
|
// 4. Hétvégék – szürke háttér (inaktív napok)
|
||||||
|
// Csak azok a hétvégi napok, amelyek nem szállítási napok: a heti sablon
|
||||||
|
// tartalmazhatja a szombatot/vasárnapot is (EV3-466).
|
||||||
$current = $start->copy();
|
$current = $start->copy();
|
||||||
while ($current->lte($end)) {
|
while ($current->lte($end)) {
|
||||||
if ($current->isWeekend()) {
|
if ($current->isWeekend() && ! isset($deliveryDayLookup[$current->format('Y-m-d')])) {
|
||||||
$events[] = [
|
$events[] = [
|
||||||
'id' => 'weekend-' . $current->format('Y-m-d'),
|
'id' => 'weekend-' . $current->format('Y-m-d'),
|
||||||
'title' => '',
|
'title' => '',
|
||||||
|
|||||||
@ -72,9 +72,11 @@ public function isDeliveryDay(ProfitCenter $pc, Supplier $supplier, Carbon $date
|
|||||||
return $override->is_delivery_day;
|
return $override->is_delivery_day;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Prioritás: Hivatalos ünnepnap (WorkCalendar)
|
// 4. Prioritás: Hivatalos munkaszüneti nap (WorkCalendar)
|
||||||
// Ha aznap nem hivatalos munkanap van, akkor nincs szállítás (kivéve ha az 1-3. pont felülbírálta).
|
// Csak a naptárban ünnepnapként rögzített dátum tilt. A naptári hétvége önmagában
|
||||||
if (! $this->workCalendarService->isWorkDay($date)) {
|
// NEM: azt az 5. pont heti sablonja dönti el, különben egy hétvégét is tartalmazó
|
||||||
|
// sablon napjai elérhetetlenek maradnának (EV3-466).
|
||||||
|
if ($this->workCalendarService->isHoliday($date)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -62,36 +62,65 @@ public function calculate(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->findNextDeliveryDay($startDay, $supplier, $pc, $leadDays);
|
return $this->findNextDeliveryDay($startDay, $supplier, $pc, $leadDays, $assignment);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Munkanapokban számol előre ($leadDays darabot), majd az első olyan napot adja vissza,
|
* Előreszámol $leadDays darab átfutási napot, majd az első olyan napot adja vissza,
|
||||||
* amelyik szállítási nap is.
|
* amelyik szállítási nap is.
|
||||||
|
*
|
||||||
|
* A szállítási nap keresése NINCS munkanaphoz kötve: hétvégi szállítást a heti sablon
|
||||||
|
* engedhet, ezt az isDeliveryDay() dönti el (EV3-466).
|
||||||
*/
|
*/
|
||||||
private function findNextDeliveryDay(
|
private function findNextDeliveryDay(
|
||||||
Carbon $candidate,
|
Carbon $candidate,
|
||||||
Supplier $supplier,
|
Supplier $supplier,
|
||||||
ProfitCenter $pc,
|
ProfitCenter $pc,
|
||||||
int $leadDays,
|
int $leadDays,
|
||||||
|
ProfitCenterSupplierSchedule $assignment,
|
||||||
): ?Carbon {
|
): ?Carbon {
|
||||||
$workingDaysCounted = 0;
|
$leadDaysCounted = 0;
|
||||||
|
|
||||||
// Max. 365 nap iteráció (végtelen ciklus-védelem)
|
// Max. 365 nap iteráció (végtelen ciklus-védelem)
|
||||||
for ($i = 0; $i < 365; $i++) {
|
for ($i = 0; $i < 365; $i++) {
|
||||||
if ($this->workCalendarService->isWorkDay($candidate)) {
|
if ($leadDaysCounted >= $leadDays) {
|
||||||
if ($workingDaysCounted >= $leadDays) {
|
|
||||||
// Átfutási idő letelt – az első szállítási napot keressük
|
// Átfutási idő letelt – az első szállítási napot keressük
|
||||||
if ($this->deliveryCalendarService->isDeliveryDay($pc, $supplier, $candidate)) {
|
if ($this->deliveryCalendarService->isDeliveryDay($pc, $supplier, $candidate, $assignment)) {
|
||||||
return $candidate->copy();
|
return $candidate->copy();
|
||||||
}
|
}
|
||||||
} else {
|
} elseif ($this->countsTowardLeadTime($candidate, $assignment)) {
|
||||||
$workingDaysCounted++;
|
$leadDaysCounted++;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
$candidate->addDay();
|
$candidate->addDay();
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Eldönti, hogy az adott nap beleszámít-e az átfutási idő visszaszámolásába.
|
||||||
|
*
|
||||||
|
* A hivatalos munkanapokon túl beleszámítanak azok a hétvégi napok is, amelyeket a
|
||||||
|
* PC-hez rendelt heti sablon szállítási napként jelöl – egy 7 napos sablonnál tehát
|
||||||
|
* minden nap telik, egy H–P sablonnál viszont a hétvége változatlanul nem (EV3-466).
|
||||||
|
* Munkaszüneti napon sosem telik az átfutás.
|
||||||
|
*/
|
||||||
|
private function countsTowardLeadTime(Carbon $date, ProfitCenterSupplierSchedule $assignment): bool
|
||||||
|
{
|
||||||
|
if ($this->workCalendarService->isHoliday($date)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->workCalendarService->isWorkDay($date)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$schedule = $assignment->deliverySchedule;
|
||||||
|
|
||||||
|
if (! $schedule || ! $schedule->is_active) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (bool) $schedule->{strtolower($date->format('l'))};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -110,6 +110,21 @@ public function isWorkDay(Carbon $date): bool
|
|||||||
return !$date->isWeekend();
|
return !$date->isWeekend();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Megállapítja, hogy az adott nap hivatalos munkaszüneti nap-e.
|
||||||
|
*
|
||||||
|
* Az isWorkDay()-jel ellentétben a puszta naptári hétvégét NEM tekinti annak:
|
||||||
|
* csak a work_calendars táblában ünnepnapként rögzített dátumra ad true-t.
|
||||||
|
* A szállítási naptár ezt használja, hogy a heti sablon engedhessen hétvégi
|
||||||
|
* szállítást is (EV3-466).
|
||||||
|
*/
|
||||||
|
public function isHoliday(Carbon $date): bool
|
||||||
|
{
|
||||||
|
$record = WorkCalendar::where('date', $date->toDateString())->first();
|
||||||
|
|
||||||
|
return $record?->type === WorkDayType::Holiday;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Kiszámítja a következő érvényes munkanapot.
|
* Kiszámítja a következő érvényes munkanapot.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -264,3 +264,78 @@
|
|||||||
expect($dateStrings)->toContain('2026-04-15');
|
expect($dateStrings)->toContain('2026-04-15');
|
||||||
expect($dateStrings)->toContain('2026-04-17');
|
expect($dateStrings)->toContain('2026-04-17');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('allows weekend delivery when the schedule includes it', function () {
|
||||||
|
$pc = ProfitCenter::factory()->create();
|
||||||
|
$supplier = Supplier::factory()->create();
|
||||||
|
$schedule = DeliverySchedule::factory()->create(['saturday' => true]);
|
||||||
|
ProfitCenterSupplierSchedule::factory()->create([
|
||||||
|
'profit_center_id' => $pc->id,
|
||||||
|
'supplier_id' => $supplier->id,
|
||||||
|
'delivery_schedule_id' => $schedule->id,
|
||||||
|
]);
|
||||||
|
$service = app(DeliveryCalendarService::class);
|
||||||
|
$saturday = Carbon::parse('2026-04-18'); // Saturday
|
||||||
|
expect($service->isDeliveryDay($pc, $supplier, $saturday))->toBeTrue();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('still blocks weekends the schedule does not include', function () {
|
||||||
|
$pc = ProfitCenter::factory()->create();
|
||||||
|
$supplier = Supplier::factory()->create();
|
||||||
|
$schedule = DeliverySchedule::factory()->create(['monday' => true, 'saturday' => false]);
|
||||||
|
ProfitCenterSupplierSchedule::factory()->create([
|
||||||
|
'profit_center_id' => $pc->id,
|
||||||
|
'supplier_id' => $supplier->id,
|
||||||
|
'delivery_schedule_id' => $schedule->id,
|
||||||
|
]);
|
||||||
|
$service = app(DeliveryCalendarService::class);
|
||||||
|
$saturday = Carbon::parse('2026-04-18'); // Saturday
|
||||||
|
expect($service->isDeliveryDay($pc, $supplier, $saturday))->toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('blocks official holidays even on a scheduled weekend day', function () {
|
||||||
|
$pc = ProfitCenter::factory()->create();
|
||||||
|
$supplier = Supplier::factory()->create();
|
||||||
|
$schedule = DeliverySchedule::factory()->create(['saturday' => true]);
|
||||||
|
ProfitCenterSupplierSchedule::factory()->create([
|
||||||
|
'profit_center_id' => $pc->id,
|
||||||
|
'supplier_id' => $supplier->id,
|
||||||
|
'delivery_schedule_id' => $schedule->id,
|
||||||
|
]);
|
||||||
|
WorkCalendar::create([
|
||||||
|
'date' => '2026-04-18',
|
||||||
|
'type' => WorkDayType::Holiday,
|
||||||
|
'data_source' => DataSource::Manual,
|
||||||
|
]);
|
||||||
|
$service = app(DeliveryCalendarService::class);
|
||||||
|
$saturday = Carbon::parse('2026-04-18'); // Saturday
|
||||||
|
expect($service->isDeliveryDay($pc, $supplier, $saturday))->toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('includes scheduled weekend days in an available date range', function () {
|
||||||
|
$pc = ProfitCenter::factory()->create();
|
||||||
|
$supplier = Supplier::factory()->create();
|
||||||
|
$schedule = DeliverySchedule::factory()->create([
|
||||||
|
'monday' => true,
|
||||||
|
'tuesday' => true,
|
||||||
|
'wednesday' => true,
|
||||||
|
'thursday' => true,
|
||||||
|
'friday' => true,
|
||||||
|
'saturday' => true,
|
||||||
|
'sunday' => true,
|
||||||
|
]);
|
||||||
|
ProfitCenterSupplierSchedule::factory()->create([
|
||||||
|
'profit_center_id' => $pc->id,
|
||||||
|
'supplier_id' => $supplier->id,
|
||||||
|
'delivery_schedule_id' => $schedule->id,
|
||||||
|
]);
|
||||||
|
$service = app(DeliveryCalendarService::class);
|
||||||
|
$dates = $service->getAvailableDeliveryDates(
|
||||||
|
$pc,
|
||||||
|
$supplier,
|
||||||
|
Carbon::parse('2026-04-13'), // Monday
|
||||||
|
Carbon::parse('2026-04-19'), // Sunday
|
||||||
|
);
|
||||||
|
|
||||||
|
expect($dates)->toHaveCount(7);
|
||||||
|
});
|
||||||
|
|||||||
81
tests/Feature/NextDeliveryDateCalculatorServiceTest.php
Normal file
81
tests/Feature/NextDeliveryDateCalculatorServiceTest.php
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\DeliverySchedule;
|
||||||
|
use App\Models\ProfitCenter;
|
||||||
|
use App\Models\ProfitCenterSupplierSchedule;
|
||||||
|
use App\Models\Supplier;
|
||||||
|
use App\Services\NextDeliveryDateCalculatorService;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
uses(TestCase::class, RefreshDatabase::class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, bool> $days
|
||||||
|
*/
|
||||||
|
function makeAssignment(array $days, bool $hasConstraint = true): array
|
||||||
|
{
|
||||||
|
$pc = ProfitCenter::factory()->create();
|
||||||
|
$supplier = Supplier::factory()->create([
|
||||||
|
'hasDeliveryConstraint' => $hasConstraint,
|
||||||
|
'orderCutOffTime' => 12,
|
||||||
|
'deliveryLeadTime' => 48, // 2 nap
|
||||||
|
]);
|
||||||
|
$schedule = DeliverySchedule::factory()->create($days);
|
||||||
|
ProfitCenterSupplierSchedule::factory()->create([
|
||||||
|
'profit_center_id' => $pc->id,
|
||||||
|
'supplier_id' => $supplier->id,
|
||||||
|
'delivery_schedule_id' => $schedule->id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return [$pc, $supplier];
|
||||||
|
}
|
||||||
|
|
||||||
|
const EVERY_DAY = [
|
||||||
|
'monday' => true,
|
||||||
|
'tuesday' => true,
|
||||||
|
'wednesday' => true,
|
||||||
|
'thursday' => true,
|
||||||
|
'friday' => true,
|
||||||
|
'saturday' => true,
|
||||||
|
'sunday' => true,
|
||||||
|
];
|
||||||
|
|
||||||
|
const WEEKDAYS_ONLY = [
|
||||||
|
'monday' => true,
|
||||||
|
'tuesday' => true,
|
||||||
|
'wednesday' => true,
|
||||||
|
'thursday' => true,
|
||||||
|
'friday' => true,
|
||||||
|
];
|
||||||
|
|
||||||
|
it('counts scheduled weekend days towards the lead time', function () {
|
||||||
|
[$pc, $supplier] = makeAssignment(EVERY_DAY);
|
||||||
|
|
||||||
|
$result = app(NextDeliveryDateCalculatorService::class)
|
||||||
|
->calculate($supplier->id, $pc->id, Carbon::parse('2026-04-17 08:00')); // Friday, cut-off előtt
|
||||||
|
|
||||||
|
// Péntek (1.) + szombat (2.) telik el az átfutásból, így vasárnap már szállítható.
|
||||||
|
expect($result?->toDateString())->toBe('2026-04-19'); // Sunday
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not count weekends for a weekday-only schedule', function () {
|
||||||
|
[$pc, $supplier] = makeAssignment(WEEKDAYS_ONLY);
|
||||||
|
|
||||||
|
$result = app(NextDeliveryDateCalculatorService::class)
|
||||||
|
->calculate($supplier->id, $pc->id, Carbon::parse('2026-04-17 08:00')); // Friday, cut-off előtt
|
||||||
|
|
||||||
|
// Péntek (1.) + hétfő (2.) telik el – a hétvége kimarad –, így kedd az első szállítási nap.
|
||||||
|
expect($result?->toDateString())->toBe('2026-04-21'); // Tuesday
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns a weekend day when there is no delivery constraint', function () {
|
||||||
|
[$pc, $supplier] = makeAssignment(EVERY_DAY, hasConstraint: false);
|
||||||
|
|
||||||
|
$result = app(NextDeliveryDateCalculatorService::class)
|
||||||
|
->calculate($supplier->id, $pc->id, Carbon::parse('2026-04-18 08:00')); // Saturday
|
||||||
|
|
||||||
|
// Átfutási idő nélkül a szombat maga az első szállítási nap.
|
||||||
|
expect($result?->toDateString())->toBe('2026-04-18'); // Saturday
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue
Block a user