Andrew Wallo 9 months ago
parent
commit
963c53245b

+ 93
- 245
app/Models/Accounting/RecurringInvoice.php View File

@@ -21,7 +21,9 @@ use App\Filament\Forms\Components\CustomSection;
21 21
 use App\Models\Common\Client;
22 22
 use App\Models\Setting\CompanyProfile;
23 23
 use App\Observers\RecurringInvoiceObserver;
24
+use App\Support\ScheduleHandler;
24 25
 use App\Utilities\Localization\Timezone;
26
+use CodeWithDennis\SimpleAlert\Components\Forms\SimpleAlert;
25 27
 use Filament\Actions\Action;
26 28
 use Filament\Actions\MountableAction;
27 29
 use Filament\Forms;
@@ -293,34 +295,17 @@ class RecurringInvoice extends Document
293 295
 
294 296
     public function calculateNextWeeklyDate(Carbon $lastDate): ?Carbon
295 297
     {
296
-        return $lastDate->copy()->next($this->day_of_week->value);
298
+        return $lastDate->copy()->next($this->day_of_week->name);
297 299
     }
298 300
 
299 301
     public function calculateNextMonthlyDate(Carbon $lastDate): ?Carbon
300 302
     {
301
-        return match (true) {
302
-            $lastDate->equalTo($this->start_date) => $lastDate->copy()->day(
303
-                min($this->day_of_month->value, $lastDate->daysInMonth)
304
-            ),
305
-
306
-            default => $lastDate->copy()->addMonth()->day(
307
-                min($this->day_of_month->value, $lastDate->copy()->addMonth()->daysInMonth)
308
-            ),
309
-        };
303
+        return $this->day_of_month->resolveDate($lastDate->copy()->addMonth());
310 304
     }
311 305
 
312 306
     public function calculateNextYearlyDate(Carbon $lastDate): ?Carbon
313 307
     {
314
-        return match (true) {
315
-            $lastDate->equalTo($this->start_date) => $lastDate->copy()
316
-                ->month($this->month->value)
317
-                ->day(min($this->day_of_month->value, $lastDate->daysInMonth)),
318
-
319
-            default => $lastDate->copy()
320
-                ->addYear()
321
-                ->month($this->month->value)
322
-                ->day(min($this->day_of_month->value, $lastDate->copy()->addYear()->month($this->month->value)->daysInMonth))
323
-        };
308
+        return $this->day_of_month->resolveDate($lastDate->copy()->addYear()->month($this->month->value));
324 309
     }
325 310
 
326 311
     protected function calculateCustomNextDate(Carbon $lastDate): ?Carbon
@@ -330,34 +315,11 @@ class RecurringInvoice extends Document
330 315
         return match ($this->interval_type) {
331 316
             IntervalType::Day => $lastDate->copy()->addDays($interval),
332 317
 
333
-            IntervalType::Week => match (true) {
334
-                $lastDate->equalTo($this->start_date) => $lastDate->copy()->next($this->day_of_week->value),
335
-
336
-                $lastDate->dayOfWeek === $this->day_of_week->value => $lastDate->copy()->addWeeks($interval),
337
-
338
-                default => $lastDate->copy()->next($this->day_of_week->value),
339
-            },
340
-
341
-            IntervalType::Month => match (true) {
342
-                $lastDate->equalTo($this->start_date) => $lastDate->copy()->day(
343
-                    min($this->day_of_month->value, $lastDate->daysInMonth)
344
-                ),
318
+            IntervalType::Week => $lastDate->copy()->addWeeks($interval),
345 319
 
346
-                default => $lastDate->copy()->addMonths($interval)->day(
347
-                    min($this->day_of_month->value, $lastDate->copy()->addMonths($interval)->daysInMonth)
348
-                ),
349
-            },
320
+            IntervalType::Month => $this->day_of_month->resolveDate($lastDate->copy()->addMonths($interval)),
350 321
 
351
-            IntervalType::Year => match (true) {
352
-                $lastDate->equalTo($this->start_date) => $lastDate->copy()
353
-                    ->month($this->month->value)
354
-                    ->day(min($this->day_of_month->value, $lastDate->daysInMonth)),
355
-
356
-                default => $lastDate->copy()
357
-                    ->addYears($interval)
358
-                    ->month($this->month->value)
359
-                    ->day(min($this->day_of_month->value, $lastDate->copy()->addYears($interval)->month($this->month->value)->daysInMonth))
360
-            },
322
+            IntervalType::Year => $this->day_of_month->resolveDate($lastDate->copy()->addYears($interval)->month($this->month->value)),
361 323
 
362 324
             default => null
363 325
         };
@@ -411,196 +373,97 @@ class RecurringInvoice extends Document
411 373
             ->form([
412 374
                 CustomSection::make('Frequency')
413 375
                     ->contained(false)
414
-                    ->schema([
415
-                        Forms\Components\Select::make('frequency')
416
-                            ->label('Repeats')
417
-                            ->options(Frequency::class)
418
-                            ->softRequired()
419
-                            ->live()
420
-                            ->afterStateUpdated(function (Forms\Set $set, $state) {
421
-                                $frequency = Frequency::parse($state);
422
-
423
-                                if ($frequency->isDaily()) {
424
-                                    $set('interval_value', null);
425
-                                    $set('interval_type', null);
426
-                                }
427
-
428
-                                if ($frequency->isWeekly()) {
429
-                                    $currentDayOfWeek = now()->dayOfWeek;
430
-                                    $currentDayOfWeek = DayOfWeek::parse($currentDayOfWeek);
431
-                                    $set('day_of_week', $currentDayOfWeek);
432
-                                    $set('interval_value', null);
433
-                                    $set('interval_type', null);
434
-                                }
435
-
436
-                                if ($frequency->isMonthly()) {
437
-                                    $set('day_of_month', DayOfMonth::First);
438
-                                    $set('interval_value', null);
439
-                                    $set('interval_type', null);
440
-                                }
441
-
442
-                                if ($frequency->isYearly()) {
443
-                                    $currentMonth = now()->month;
444
-                                    $currentMonth = Month::parse($currentMonth);
445
-                                    $set('month', $currentMonth);
446
-
447
-                                    $currentDay = now()->dayOfMonth;
448
-                                    $currentDay = DayOfMonth::parse($currentDay);
449
-                                    $set('day_of_month', $currentDay);
450
-
451
-                                    $set('interval_value', null);
452
-                                    $set('interval_type', null);
453
-                                }
454
-
455
-                                if ($frequency->isCustom()) {
456
-                                    $set('interval_value', 1);
457
-                                    $set('interval_type', IntervalType::Month);
458
-
459
-                                    $currentDay = now()->dayOfMonth;
460
-                                    $currentDay = DayOfMonth::parse($currentDay);
461
-                                    $set('day_of_month', $currentDay);
462
-                                }
463
-                            }),
464
-
465
-                        // Custom frequency fields in a nested grid
466
-                        Cluster::make([
467
-                            Forms\Components\TextInput::make('interval_value')
376
+                    ->schema(function (Forms\Get $get) {
377
+                        $frequency = Frequency::parse($get('frequency'));
378
+                        $intervalType = IntervalType::parse($get('interval_type'));
379
+                        $month = Month::parse($get('month'));
380
+                        $dayOfMonth = DayOfMonth::parse($get('day_of_month'));
381
+
382
+                        return [
383
+                            Forms\Components\Select::make('frequency')
384
+                                ->label('Repeats')
385
+                                ->options(Frequency::class)
468 386
                                 ->softRequired()
469
-                                ->numeric()
470
-                                ->default(1),
471
-                            Forms\Components\Select::make('interval_type')
472
-                                ->options(IntervalType::class)
473
-                                ->softRequired()
474
-                                ->default(IntervalType::Month)
475 387
                                 ->live()
476 388
                                 ->afterStateUpdated(function (Forms\Set $set, $state) {
477
-                                    $intervalType = IntervalType::parse($state);
389
+                                    $handler = new ScheduleHandler($set);
390
+                                    $handler->handleFrequencyChange($state);
391
+                                }),
478 392
 
479
-                                    if ($intervalType->isWeek()) {
480
-                                        $currentDayOfWeek = now()->dayOfWeek;
481
-                                        $currentDayOfWeek = DayOfWeek::parse($currentDayOfWeek);
482
-                                        $set('day_of_week', $currentDayOfWeek);
483
-                                    }
393
+                            // Custom frequency fields in a nested grid
394
+                            Cluster::make([
395
+                                Forms\Components\TextInput::make('interval_value')
396
+                                    ->softRequired()
397
+                                    ->numeric()
398
+                                    ->default(1),
399
+                                Forms\Components\Select::make('interval_type')
400
+                                    ->options(IntervalType::class)
401
+                                    ->softRequired()
402
+                                    ->default(IntervalType::Month)
403
+                                    ->live()
404
+                                    ->afterStateUpdated(function (Forms\Set $set, $state) {
405
+                                        $handler = new ScheduleHandler($set);
406
+                                        $handler->handleIntervalTypeChange($state);
407
+                                    }),
408
+                            ])
409
+                                ->live()
410
+                                ->label('Every')
411
+                                ->required()
412
+                                ->markAsRequired(false)
413
+                                ->visible($frequency->isCustom()),
414
+
415
+                            // Specific schedule details
416
+                            Forms\Components\Select::make('month')
417
+                                ->label('Month')
418
+                                ->options(Month::class)
419
+                                ->softRequired()
420
+                                ->visible($frequency->isYearly() || $intervalType?->isYear())
421
+                                ->live()
422
+                                ->afterStateUpdated(function (Forms\Set $set, Forms\Get $get, $state) {
423
+                                    $handler = new ScheduleHandler($set, $get);
424
+                                    $handler->handleDateChange('month', $state);
425
+                                }),
484 426
 
485
-                                    if ($intervalType->isMonth()) {
486
-                                        $currentDay = now()->dayOfMonth;
487
-                                        $currentDay = DayOfMonth::parse($currentDay);
488
-                                        $set('day_of_month', $currentDay);
427
+                            Forms\Components\Select::make('day_of_month')
428
+                                ->label('Day of Month')
429
+                                ->options(function () use ($month) {
430
+                                    if (! $month) {
431
+                                        return DayOfMonth::class;
489 432
                                     }
490 433
 
491
-                                    if ($intervalType->isYear()) {
492
-                                        $currentMonth = now()->month;
493
-                                        $currentMonth = Month::parse($currentMonth);
494
-                                        $set('month', $currentMonth);
434
+                                    $daysInMonth = Carbon::createFromDate(null, $month->value)->daysInMonth;
495 435
 
496
-                                        $currentDay = now()->dayOfMonth;
497
-                                        $currentDay = DayOfMonth::parse($currentDay);
498
-                                        $set('day_of_month', $currentDay);
499
-                                    }
436
+                                    return collect(DayOfMonth::cases())
437
+                                        ->filter(static fn (DayOfMonth $dayOfMonth) => $dayOfMonth->value <= $daysInMonth || $dayOfMonth->isLast())
438
+                                        ->mapWithKeys(fn (DayOfMonth $dayOfMonth) => [$dayOfMonth->value => $dayOfMonth->getLabel()]);
439
+                                })
440
+                                ->softRequired()
441
+                                ->visible(in_array($frequency, [Frequency::Monthly, Frequency::Yearly]) || in_array($intervalType, [IntervalType::Month, IntervalType::Year]))
442
+                                ->live()
443
+                                ->afterStateUpdated(function (Forms\Set $set, Forms\Get $get, $state) {
444
+                                    $handler = new ScheduleHandler($set, $get);
445
+                                    $handler->handleDateChange('day_of_month', $state);
500 446
                                 }),
501
-                        ])
502
-                            ->live()
503
-                            ->label('Every')
504
-                            ->required()
505
-                            ->markAsRequired(false)
506
-                            ->visible(fn (Forms\Get $get) => Frequency::parse($get('frequency'))?->isCustom()),
507
-
508
-                        // Specific schedule details
509
-                        Forms\Components\Select::make('month')
510
-                            ->label('Month')
511
-                            ->options(Month::class)
512
-                            ->softRequired()
513
-                            ->visible(
514
-                                fn (Forms\Get $get) => Frequency::parse($get('frequency'))->isYearly() ||
515
-                                IntervalType::parse($get('interval_type'))?->isYear()
516
-                            )
517
-                            ->live()
518
-                            ->afterStateUpdated(function (Forms\Set $set, Forms\Get $get, $state) {
519
-                                $dayOfMonth = DayOfMonth::parse($get('day_of_month'));
520
-                                $frequency = Frequency::parse($get('frequency'));
521
-                                $intervalType = IntervalType::parse($get('interval_type'));
522
-                                $month = Month::parse($state);
523
-
524
-                                if (($frequency->isYearly() || $intervalType?->isYear()) && $month && $dayOfMonth) {
525
-                                    $date = $dayOfMonth->resolveDate(today()->month($month->value))->toImmutable();
526
-
527
-                                    $adjustedStartDate = $date->lt(today())
528
-                                        ? $dayOfMonth->resolveDate($date->addYear()->month($month->value))
529
-                                        : $dayOfMonth->resolveDate($date->month($month->value));
530
-
531
-                                    $adjustedDay = min($dayOfMonth->value, $adjustedStartDate->daysInMonth);
532 447
 
533
-                                    $set('day_of_month', $adjustedDay);
448
+                            SimpleAlert::make('dayOfMonthNotice')
449
+                                ->title(function () use ($dayOfMonth) {
450
+                                    return "The invoice will be created on the {$dayOfMonth->getLabel()} day of each month, or on the last day for months ending earlier.";
451
+                                })
452
+                                ->columnSpanFull()
453
+                                ->visible($dayOfMonth?->value > 28),
534 454
 
535
-                                    $set('start_date', $adjustedStartDate);
536
-                                }
537
-                            }),
538
-
539
-                        Forms\Components\Select::make('day_of_month')
540
-                            ->label('Day of Month')
541
-                            ->options(function (Forms\Get $get) {
542
-                                $month = Month::parse($get('month')) ?? Month::January;
543
-
544
-                                $daysInMonth = Carbon::createFromDate(null, $month->value)->daysInMonth;
545
-
546
-                                return collect(DayOfMonth::cases())
547
-                                    ->filter(static fn (DayOfMonth $dayOfMonth) => $dayOfMonth->value <= $daysInMonth || $dayOfMonth->isLast())
548
-                                    ->mapWithKeys(fn (DayOfMonth $dayOfMonth) => [$dayOfMonth->value => $dayOfMonth->getLabel()]);
549
-                            })
550
-                            ->softRequired()
551
-                            ->visible(
552
-                                fn (Forms\Get $get) => Frequency::parse($get('frequency'))?->isMonthly() ||
553
-                                Frequency::parse($get('frequency'))?->isYearly() ||
554
-                                IntervalType::parse($get('interval_type'))?->isMonth() ||
555
-                                IntervalType::parse($get('interval_type'))?->isYear()
556
-                            )
557
-                            ->live()
558
-                            ->afterStateUpdated(function (Forms\Set $set, Forms\Get $get, $state) {
559
-                                $dayOfMonth = DayOfMonth::parse($state);
560
-                                $frequency = Frequency::parse($get('frequency'));
561
-                                $intervalType = IntervalType::parse($get('interval_type'));
562
-                                $month = Month::parse($get('month'));
563
-
564
-                                if (($frequency->isMonthly() || $intervalType?->isMonth()) && $dayOfMonth) {
565
-                                    $date = $dayOfMonth->resolveDate(today())->toImmutable();
566
-
567
-                                    $adjustedStartDate = $date->lt(today())
568
-                                        ? $dayOfMonth->resolveDate($date->addMonth())
569
-                                        : $dayOfMonth->resolveDate($date);
570
-
571
-                                    $set('start_date', $adjustedStartDate);
572
-                                }
573
-
574
-                                if (($frequency->isYearly() || $intervalType?->isYear()) && $month && $dayOfMonth) {
575
-                                    $date = $dayOfMonth->resolveDate(today()->month($month->value))->toImmutable();
576
-
577
-                                    $adjustedStartDate = $date->lt(today())
578
-                                        ? $dayOfMonth->resolveDate($date->addYear()->month($month->value))
579
-                                        : $dayOfMonth->resolveDate($date->month($month->value));
580
-
581
-                                    $set('start_date', $adjustedStartDate);
582
-                                }
583
-                            }),
584
-
585
-                        Forms\Components\Select::make('day_of_week')
586
-                            ->label('Day of Week')
587
-                            ->options(DayOfWeek::class)
588
-                            ->softRequired()
589
-                            ->visible(
590
-                                fn (Forms\Get $get) => Frequency::parse($get('frequency'))?->isWeekly() ||
591
-                                IntervalType::parse($get('interval_type'))?->isWeek()
592
-                            )
593
-                            ->live()
594
-                            ->afterStateUpdated(function (Forms\Set $set, $state) {
595
-                                $dayOfWeek = DayOfWeek::parse($state);
596
-
597
-                                $adjustedStartDate = today()->is($dayOfWeek->name)
598
-                                    ? today()
599
-                                    : today()->next($dayOfWeek->name);
600
-
601
-                                $set('start_date', $adjustedStartDate);
602
-                            }),
603
-                    ])->columns(2),
455
+                            Forms\Components\Select::make('day_of_week')
456
+                                ->label('Day of Week')
457
+                                ->options(DayOfWeek::class)
458
+                                ->softRequired()
459
+                                ->visible($frequency->isWeekly() || $intervalType?->isWeek())
460
+                                ->live()
461
+                                ->afterStateUpdated(function (Forms\Set $set, $state) {
462
+                                    $handler = new ScheduleHandler($set);
463
+                                    $handler->handleDateChange('day_of_week', $state);
464
+                                }),
465
+                        ];
466
+                    })->columns(2),
604 467
 
605 468
                 CustomSection::make('Dates & Time')
606 469
                     ->contained(false)
@@ -611,12 +474,9 @@ class RecurringInvoice extends Document
611 474
                             ->live()
612 475
                             ->minDate(today())
613 476
                             ->closeOnDateSelection()
614
-                            ->afterStateUpdated(function (Forms\Set $set, $state) {
615
-                                $startDate = Carbon::parse($state);
616
-
617
-                                $dayOfWeek = DayOfWeek::parse($startDate->dayOfWeek);
618
-
619
-                                $set('day_of_week', $dayOfWeek);
477
+                            ->afterStateUpdated(function (Forms\Set $set, Forms\Get $get, $state) {
478
+                                $handler = new ScheduleHandler($set, $get);
479
+                                $handler->handleDateChange('start_date', $state);
620 480
                             }),
621 481
 
622 482
                         Forms\Components\Group::make(function (Forms\Get $get) {
@@ -630,20 +490,8 @@ class RecurringInvoice extends Document
630 490
                                 ->afterStateUpdated(function (Forms\Set $set, $state) {
631 491
                                     $endType = EndType::parse($state);
632 492
 
633
-                                    if ($endType?->isNever()) {
634
-                                        $set('max_occurrences', null);
635
-                                        $set('end_date', null);
636
-                                    }
637
-
638
-                                    if ($endType?->isAfter()) {
639
-                                        $set('max_occurrences', 1);
640
-                                        $set('end_date', null);
641
-                                    }
642
-
643
-                                    if ($endType?->isOn()) {
644
-                                        $set('max_occurrences', null);
645
-                                        $set('end_date', now()->addMonth()->startOfMonth());
646
-                                    }
493
+                                    $set('max_occurrences', $endType?->isAfter() ? 1 : null);
494
+                                    $set('end_date', $endType?->isOn() ? now()->addMonth()->startOfMonth() : null);
647 495
                                 });
648 496
 
649 497
                             $endType = EndType::parse($get('end_type'));

+ 273
- 0
app/Support/ScheduleHandler.php View File

@@ -0,0 +1,273 @@
1
+<?php
2
+
3
+namespace App\Support;
4
+
5
+use App\Enums\Accounting\DayOfMonth;
6
+use App\Enums\Accounting\DayOfWeek;
7
+use App\Enums\Accounting\Frequency;
8
+use App\Enums\Accounting\IntervalType;
9
+use App\Enums\Accounting\Month;
10
+use Carbon\CarbonImmutable;
11
+use Filament\Forms\Get;
12
+use Filament\Forms\Set;
13
+use Illuminate\Support\Carbon;
14
+
15
+class ScheduleHandler
16
+{
17
+    protected CarbonImmutable $today;
18
+
19
+    protected Set $set;
20
+
21
+    protected ?Get $get;
22
+
23
+    public function __construct(Set $set, ?Get $get = null)
24
+    {
25
+        $this->today = today()->toImmutable();
26
+        $this->set = $set;
27
+        $this->get = $get;
28
+    }
29
+
30
+    protected function setMany(Set $set, array $values): void
31
+    {
32
+        foreach ($values as $key => $value) {
33
+            $set($key, $value);
34
+        }
35
+    }
36
+
37
+    public function handleFrequencyChange(mixed $state): void
38
+    {
39
+        $frequency = Frequency::parse($state);
40
+
41
+        match (true) {
42
+            $frequency->isDaily() => $this->handleDaily(),
43
+            $frequency->isWeekly() => $this->handleWeekly(),
44
+            $frequency->isMonthly() => $this->handleMonthly(),
45
+            $frequency->isYearly() => $this->handleYearly(),
46
+            $frequency->isCustom() => $this->handleCustom(),
47
+            default => null,
48
+        };
49
+    }
50
+
51
+    public function handleIntervalTypeChange(mixed $state): void
52
+    {
53
+        $intervalType = IntervalType::parse($state);
54
+
55
+        match (true) {
56
+            $intervalType->isWeek() => $this->handleWeeklyInterval(),
57
+            $intervalType->isMonth() => $this->handleMonthlyInterval(),
58
+            $intervalType->isYear() => $this->handleYearlyInterval(),
59
+            default => null,
60
+        };
61
+    }
62
+
63
+    public function handleDateChange(?string $component, mixed $state): void
64
+    {
65
+        match ($component) {
66
+            'start_date' => $this->syncComponentsToStartDate(Carbon::parse($state)),
67
+            'month' => $this->handleMonthChange($state),
68
+            'day_of_month' => $this->handleDayOfMonthChange($state),
69
+            'day_of_week' => $this->handleDayOfWeekChange($state),
70
+            default => null,
71
+        };
72
+    }
73
+
74
+    protected function handleDaily(): void
75
+    {
76
+        $this->setMany($this->set, [
77
+            'interval_value' => null,
78
+            'interval_type' => null,
79
+            'day_of_month' => null,
80
+            'start_date' => $this->today,
81
+        ]);
82
+    }
83
+
84
+    protected function handleWeekly(): void
85
+    {
86
+        $currentDayOfWeek = DayOfWeek::parse($this->today->dayOfWeek);
87
+
88
+        $this->setMany($this->set, [
89
+            'day_of_week' => $currentDayOfWeek,
90
+            'start_date' => $this->today,
91
+            'interval_value' => null,
92
+            'interval_type' => null,
93
+            'day_of_month' => null,
94
+        ]);
95
+    }
96
+
97
+    protected function handleMonthly(): void
98
+    {
99
+        $dayOfMonth = DayOfMonth::First;
100
+        $date = $dayOfMonth->resolveDate($this->today);
101
+
102
+        $adjustedStartDate = $date->lt($this->today)
103
+            ? $dayOfMonth->resolveDate($date->addMonth())
104
+            : $dayOfMonth->resolveDate($date);
105
+
106
+        $this->setMany($this->set, [
107
+            'month' => null,
108
+            'day_of_month' => $dayOfMonth,
109
+            'start_date' => $adjustedStartDate,
110
+            'interval_value' => null,
111
+            'interval_type' => null,
112
+        ]);
113
+    }
114
+
115
+    protected function handleYearly(): void
116
+    {
117
+        $currentMonth = Month::parse($this->today->month);
118
+        $currentDayOfMonth = DayOfMonth::parse($this->today->day);
119
+
120
+        $this->setMany($this->set, [
121
+            'month' => $currentMonth,
122
+            'day_of_month' => $currentDayOfMonth,
123
+            'start_date' => $this->today,
124
+            'interval_value' => null,
125
+            'interval_type' => null,
126
+        ]);
127
+    }
128
+
129
+    protected function handleCustom(): void
130
+    {
131
+        $dayOfMonth = DayOfMonth::First;
132
+        $date = $dayOfMonth->resolveDate($this->today);
133
+
134
+        $adjustedStartDate = $date->lt($this->today)
135
+            ? $dayOfMonth->resolveDate($date->addMonth())
136
+            : $dayOfMonth->resolveDate($date);
137
+
138
+        $this->setMany($this->set, [
139
+            'interval_value' => 1,
140
+            'interval_type' => IntervalType::Month,
141
+            'month' => null,
142
+            'day_of_month' => $dayOfMonth,
143
+            'start_date' => $adjustedStartDate,
144
+        ]);
145
+    }
146
+
147
+    protected function handleWeeklyInterval(): void
148
+    {
149
+        $currentDayOfWeek = DayOfWeek::parse($this->today->dayOfWeek);
150
+
151
+        $this->setMany($this->set, [
152
+            'day_of_week' => $currentDayOfWeek,
153
+            'start_date' => $this->today,
154
+        ]);
155
+    }
156
+
157
+    protected function handleMonthlyInterval(): void
158
+    {
159
+        $dayOfMonth = DayOfMonth::First;
160
+        $date = $dayOfMonth->resolveDate($this->today);
161
+
162
+        $adjustedStartDate = $date->lt($this->today)
163
+            ? $dayOfMonth->resolveDate($date->addMonth())
164
+            : $dayOfMonth->resolveDate($date);
165
+
166
+        $this->setMany($this->set, [
167
+            'month' => null,
168
+            'day_of_month' => $dayOfMonth,
169
+            'start_date' => $adjustedStartDate,
170
+        ]);
171
+    }
172
+
173
+    protected function handleYearlyInterval(): void
174
+    {
175
+        $currentMonth = Month::parse($this->today->month);
176
+        $currentDayOfMonth = DayOfMonth::parse($this->today->day);
177
+
178
+        $this->setMany($this->set, [
179
+            'month' => $currentMonth,
180
+            'day_of_month' => $currentDayOfMonth,
181
+            'start_date' => $this->today,
182
+        ]);
183
+    }
184
+
185
+    protected function syncComponentsToStartDate(Carbon $startDate): void
186
+    {
187
+        $frequency = Frequency::parse(($this->get)('frequency'));
188
+        $intervalType = IntervalType::parse(($this->get)('interval_type'));
189
+
190
+        if ($frequency->isWeekly() || $intervalType?->isWeek()) {
191
+            ($this->set)('day_of_week', DayOfWeek::parse($startDate->dayOfWeek));
192
+        }
193
+
194
+        if ($frequency->isMonthly() || $intervalType?->isMonth() ||
195
+            $frequency->isYearly() || $intervalType?->isYear()) {
196
+            ($this->set)('day_of_month', $startDate->day);
197
+        }
198
+
199
+        if ($frequency->isYearly() || $intervalType?->isYear()) {
200
+            ($this->set)('month', Month::parse($startDate->month));
201
+        }
202
+    }
203
+
204
+    protected function handleMonthChange(mixed $state): void
205
+    {
206
+        if (! $this->get) {
207
+            return;
208
+        }
209
+
210
+        $dayOfMonth = DayOfMonth::parse(($this->get)('day_of_month'));
211
+        $frequency = Frequency::parse(($this->get)('frequency'));
212
+        $intervalType = IntervalType::parse(($this->get)('interval_type'));
213
+        $month = Month::parse($state);
214
+
215
+        if (($frequency->isYearly() || $intervalType?->isYear()) && $month && $dayOfMonth) {
216
+            $date = $dayOfMonth->resolveDate($this->today->month($month->value));
217
+
218
+            $adjustedStartDate = $date->lt($this->today)
219
+                ? $dayOfMonth->resolveDate($date->addYear()->month($month->value))
220
+                : $dayOfMonth->resolveDate($date->month($month->value));
221
+
222
+            $adjustedDay = min($dayOfMonth->value, $adjustedStartDate->daysInMonth);
223
+
224
+            $this->setMany($this->set, [
225
+                'day_of_month' => $adjustedDay,
226
+                'start_date' => $adjustedStartDate,
227
+            ]);
228
+        }
229
+    }
230
+
231
+    protected function handleDayOfMonthChange(mixed $state): void
232
+    {
233
+        if (! $this->get) {
234
+            return;
235
+        }
236
+
237
+        $dayOfMonth = DayOfMonth::parse($state);
238
+        $frequency = Frequency::parse(($this->get)('frequency'));
239
+        $intervalType = IntervalType::parse(($this->get)('interval_type'));
240
+        $month = Month::parse(($this->get)('month'));
241
+
242
+        if (($frequency->isMonthly() || $intervalType?->isMonth()) && $dayOfMonth) {
243
+            $date = $dayOfMonth->resolveDate($this->today);
244
+
245
+            $adjustedStartDate = $date->lt($this->today)
246
+                ? $dayOfMonth->resolveDate($date->addMonth())
247
+                : $dayOfMonth->resolveDate($date);
248
+
249
+            ($this->set)('start_date', $adjustedStartDate);
250
+        }
251
+
252
+        if (($frequency->isYearly() || $intervalType?->isYear()) && $month && $dayOfMonth) {
253
+            $date = $dayOfMonth->resolveDate($this->today->month($month->value));
254
+
255
+            $adjustedStartDate = $date->lt($this->today)
256
+                ? $dayOfMonth->resolveDate($date->addYear()->month($month->value))
257
+                : $dayOfMonth->resolveDate($date->month($month->value));
258
+
259
+            ($this->set)('start_date', $adjustedStartDate);
260
+        }
261
+    }
262
+
263
+    protected function handleDayOfWeekChange(mixed $state): void
264
+    {
265
+        $dayOfWeek = DayOfWeek::parse($state);
266
+
267
+        $adjustedStartDate = $this->today->is($dayOfWeek->name)
268
+            ? $this->today
269
+            : $this->today->next($dayOfWeek->name);
270
+
271
+        ($this->set)('start_date', $adjustedStartDate);
272
+    }
273
+}

+ 21
- 18
composer.lock View File

@@ -497,16 +497,16 @@
497 497
         },
498 498
         {
499 499
             "name": "aws/aws-sdk-php",
500
-            "version": "3.336.8",
500
+            "version": "3.336.9",
501 501
             "source": {
502 502
                 "type": "git",
503 503
                 "url": "https://github.com/aws/aws-sdk-php.git",
504
-                "reference": "933da0d1b9b1ac9b37d5e32e127d4581b1aabaf6"
504
+                "reference": "bbc76138ed66f593dc2ae529c95fe1f794e6d77f"
505 505
             },
506 506
             "dist": {
507 507
                 "type": "zip",
508
-                "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/933da0d1b9b1ac9b37d5e32e127d4581b1aabaf6",
509
-                "reference": "933da0d1b9b1ac9b37d5e32e127d4581b1aabaf6",
508
+                "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/bbc76138ed66f593dc2ae529c95fe1f794e6d77f",
509
+                "reference": "bbc76138ed66f593dc2ae529c95fe1f794e6d77f",
510 510
                 "shasum": ""
511 511
             },
512 512
             "require": {
@@ -589,9 +589,9 @@
589 589
             "support": {
590 590
                 "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
591 591
                 "issues": "https://github.com/aws/aws-sdk-php/issues",
592
-                "source": "https://github.com/aws/aws-sdk-php/tree/3.336.8"
592
+                "source": "https://github.com/aws/aws-sdk-php/tree/3.336.9"
593 593
             },
594
-            "time": "2025-01-03T19:06:11+00:00"
594
+            "time": "2025-01-06T19:06:42+00:00"
595 595
         },
596 596
         {
597 597
             "name": "aws/aws-sdk-php-laravel",
@@ -2986,16 +2986,16 @@
2986 2986
         },
2987 2987
         {
2988 2988
             "name": "knplabs/knp-snappy",
2989
-            "version": "v1.5.0",
2989
+            "version": "v1.5.1",
2990 2990
             "source": {
2991 2991
                 "type": "git",
2992 2992
                 "url": "https://github.com/KnpLabs/snappy.git",
2993
-                "reference": "98468898b50c09f26d56d905b79b0f52a2215da6"
2993
+                "reference": "3dd138e9e47de91cd2e056c5e6e1a0dd72547ee7"
2994 2994
             },
2995 2995
             "dist": {
2996 2996
                 "type": "zip",
2997
-                "url": "https://api.github.com/repos/KnpLabs/snappy/zipball/98468898b50c09f26d56d905b79b0f52a2215da6",
2998
-                "reference": "98468898b50c09f26d56d905b79b0f52a2215da6",
2997
+                "url": "https://api.github.com/repos/KnpLabs/snappy/zipball/3dd138e9e47de91cd2e056c5e6e1a0dd72547ee7",
2998
+                "reference": "3dd138e9e47de91cd2e056c5e6e1a0dd72547ee7",
2999 2999
                 "shasum": ""
3000 3000
             },
3001 3001
             "require": {
@@ -3047,9 +3047,9 @@
3047 3047
             ],
3048 3048
             "support": {
3049 3049
                 "issues": "https://github.com/KnpLabs/snappy/issues",
3050
-                "source": "https://github.com/KnpLabs/snappy/tree/v1.5.0"
3050
+                "source": "https://github.com/KnpLabs/snappy/tree/v1.5.1"
3051 3051
             },
3052
-            "time": "2023-12-18T09:12:11+00:00"
3052
+            "time": "2025-01-06T16:53:26+00:00"
3053 3053
         },
3054 3054
         {
3055 3055
             "name": "laravel/framework",
@@ -11528,16 +11528,16 @@
11528 11528
         },
11529 11529
         {
11530 11530
             "name": "sebastian/comparator",
11531
-            "version": "6.2.1",
11531
+            "version": "6.3.0",
11532 11532
             "source": {
11533 11533
                 "type": "git",
11534 11534
                 "url": "https://github.com/sebastianbergmann/comparator.git",
11535
-                "reference": "43d129d6a0f81c78bee378b46688293eb7ea3739"
11535
+                "reference": "d4e47a769525c4dd38cea90e5dcd435ddbbc7115"
11536 11536
             },
11537 11537
             "dist": {
11538 11538
                 "type": "zip",
11539
-                "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/43d129d6a0f81c78bee378b46688293eb7ea3739",
11540
-                "reference": "43d129d6a0f81c78bee378b46688293eb7ea3739",
11539
+                "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/d4e47a769525c4dd38cea90e5dcd435ddbbc7115",
11540
+                "reference": "d4e47a769525c4dd38cea90e5dcd435ddbbc7115",
11541 11541
                 "shasum": ""
11542 11542
             },
11543 11543
             "require": {
@@ -11550,6 +11550,9 @@
11550 11550
             "require-dev": {
11551 11551
                 "phpunit/phpunit": "^11.4"
11552 11552
             },
11553
+            "suggest": {
11554
+                "ext-bcmath": "For comparing BcMath\\Number objects"
11555
+            },
11553 11556
             "type": "library",
11554 11557
             "extra": {
11555 11558
                 "branch-alias": {
@@ -11593,7 +11596,7 @@
11593 11596
             "support": {
11594 11597
                 "issues": "https://github.com/sebastianbergmann/comparator/issues",
11595 11598
                 "security": "https://github.com/sebastianbergmann/comparator/security/policy",
11596
-                "source": "https://github.com/sebastianbergmann/comparator/tree/6.2.1"
11599
+                "source": "https://github.com/sebastianbergmann/comparator/tree/6.3.0"
11597 11600
             },
11598 11601
             "funding": [
11599 11602
                 {
@@ -11601,7 +11604,7 @@
11601 11604
                     "type": "github"
11602 11605
                 }
11603 11606
             ],
11604
-            "time": "2024-10-31T05:30:08+00:00"
11607
+            "time": "2025-01-06T10:28:19+00:00"
11605 11608
         },
11606 11609
         {
11607 11610
             "name": "sebastian/complexity",

+ 82
- 82
package-lock.json View File

@@ -575,9 +575,9 @@
575 575
             }
576 576
         },
577 577
         "node_modules/@rollup/rollup-android-arm-eabi": {
578
-            "version": "4.29.2",
579
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.29.2.tgz",
580
-            "integrity": "sha512-s/8RiF4bdmGnc/J0N7lHAr5ZFJj+NdJqJ/Hj29K+c4lEdoVlukzvWXB9XpWZCdakVT0YAw8iyIqUP2iFRz5/jA==",
578
+            "version": "4.30.0",
579
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.30.0.tgz",
580
+            "integrity": "sha512-qFcFto9figFLz2g25DxJ1WWL9+c91fTxnGuwhToCl8BaqDsDYMl/kOnBXAyAqkkzAWimYMSWNPWEjt+ADAHuoQ==",
581 581
             "cpu": [
582 582
                 "arm"
583 583
             ],
@@ -589,9 +589,9 @@
589 589
             ]
590 590
         },
591 591
         "node_modules/@rollup/rollup-android-arm64": {
592
-            "version": "4.29.2",
593
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.29.2.tgz",
594
-            "integrity": "sha512-mKRlVj1KsKWyEOwR6nwpmzakq6SgZXW4NUHNWlYSiyncJpuXk7wdLzuKdWsRoR1WLbWsZBKvsUCdCTIAqRn9cA==",
592
+            "version": "4.30.0",
593
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.30.0.tgz",
594
+            "integrity": "sha512-vqrQdusvVl7dthqNjWCL043qelBK+gv9v3ZiqdxgaJvmZyIAAXMjeGVSqZynKq69T7062T5VrVTuikKSAAVP6A==",
595 595
             "cpu": [
596 596
                 "arm64"
597 597
             ],
@@ -603,9 +603,9 @@
603 603
             ]
604 604
         },
605 605
         "node_modules/@rollup/rollup-darwin-arm64": {
606
-            "version": "4.29.2",
607
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.29.2.tgz",
608
-            "integrity": "sha512-vJX+vennGwygmutk7N333lvQ/yKVAHnGoBS2xMRQgXWW8tvn46YWuTDOpKroSPR9BEW0Gqdga2DHqz8Pwk6X5w==",
606
+            "version": "4.30.0",
607
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.30.0.tgz",
608
+            "integrity": "sha512-617pd92LhdA9+wpixnzsyhVft3szYiN16aNUMzVkf2N+yAk8UXY226Bfp36LvxYTUt7MO/ycqGFjQgJ0wlMaWQ==",
609 609
             "cpu": [
610 610
                 "arm64"
611 611
             ],
@@ -617,9 +617,9 @@
617 617
             ]
618 618
         },
619 619
         "node_modules/@rollup/rollup-darwin-x64": {
620
-            "version": "4.29.2",
621
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.29.2.tgz",
622
-            "integrity": "sha512-e2rW9ng5O6+Mt3ht8fH0ljfjgSCC6ffmOipiLUgAnlK86CHIaiCdHCzHzmTkMj6vEkqAiRJ7ss6Ibn56B+RE5w==",
620
+            "version": "4.30.0",
621
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.30.0.tgz",
622
+            "integrity": "sha512-Y3b4oDoaEhCypg8ajPqigKDcpi5ZZovemQl9Edpem0uNv6UUjXv7iySBpGIUTSs2ovWOzYpfw9EbFJXF/fJHWw==",
623 623
             "cpu": [
624 624
                 "x64"
625 625
             ],
@@ -631,9 +631,9 @@
631 631
             ]
632 632
         },
633 633
         "node_modules/@rollup/rollup-freebsd-arm64": {
634
-            "version": "4.29.2",
635
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.29.2.tgz",
636
-            "integrity": "sha512-/xdNwZe+KesG6XJCK043EjEDZTacCtL4yurMZRLESIgHQdvtNyul3iz2Ab03ZJG0pQKbFTu681i+4ETMF9uE/Q==",
634
+            "version": "4.30.0",
635
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.30.0.tgz",
636
+            "integrity": "sha512-3REQJ4f90sFIBfa0BUokiCdrV/E4uIjhkWe1bMgCkhFXbf4D8YN6C4zwJL881GM818qVYE9BO3dGwjKhpo2ABA==",
637 637
             "cpu": [
638 638
                 "arm64"
639 639
             ],
@@ -645,9 +645,9 @@
645 645
             ]
646 646
         },
647 647
         "node_modules/@rollup/rollup-freebsd-x64": {
648
-            "version": "4.29.2",
649
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.29.2.tgz",
650
-            "integrity": "sha512-eXKvpThGzREuAbc6qxnArHh8l8W4AyTcL8IfEnmx+bcnmaSGgjyAHbzZvHZI2csJ+e0MYddl7DX0X7g3sAuXDQ==",
648
+            "version": "4.30.0",
649
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.30.0.tgz",
650
+            "integrity": "sha512-ZtY3Y8icbe3Cc+uQicsXG5L+CRGUfLZjW6j2gn5ikpltt3Whqjfo5mkyZ86UiuHF9Q3ZsaQeW7YswlHnN+lAcg==",
651 651
             "cpu": [
652 652
                 "x64"
653 653
             ],
@@ -659,9 +659,9 @@
659 659
             ]
660 660
         },
661 661
         "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
662
-            "version": "4.29.2",
663
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.29.2.tgz",
664
-            "integrity": "sha512-h4VgxxmzmtXLLYNDaUcQevCmPYX6zSj4SwKuzY7SR5YlnCBYsmvfYORXgiU8axhkFCDtQF3RW5LIXT8B14Qykg==",
662
+            "version": "4.30.0",
663
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.30.0.tgz",
664
+            "integrity": "sha512-bsPGGzfiHXMhQGuFGpmo2PyTwcrh2otL6ycSZAFTESviUoBOuxF7iBbAL5IJXc/69peXl5rAtbewBFeASZ9O0g==",
665 665
             "cpu": [
666 666
                 "arm"
667 667
             ],
@@ -673,9 +673,9 @@
673 673
             ]
674 674
         },
675 675
         "node_modules/@rollup/rollup-linux-arm-musleabihf": {
676
-            "version": "4.29.2",
677
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.29.2.tgz",
678
-            "integrity": "sha512-EObwZ45eMmWZQ1w4N7qy4+G1lKHm6mcOwDa+P2+61qxWu1PtQJ/lz2CNJ7W3CkfgN0FQ7cBUy2tk6D5yR4KeXw==",
676
+            "version": "4.30.0",
677
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.30.0.tgz",
678
+            "integrity": "sha512-kvyIECEhs2DrrdfQf++maCWJIQ974EI4txlz1nNSBaCdtf7i5Xf1AQCEJWOC5rEBisdaMFFnOWNLYt7KpFqy5A==",
679 679
             "cpu": [
680 680
                 "arm"
681 681
             ],
@@ -687,9 +687,9 @@
687 687
             ]
688 688
         },
689 689
         "node_modules/@rollup/rollup-linux-arm64-gnu": {
690
-            "version": "4.29.2",
691
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.29.2.tgz",
692
-            "integrity": "sha512-Z7zXVHEXg1elbbYiP/29pPwlJtLeXzjrj4241/kCcECds8Zg9fDfURWbZHRIKrEriAPS8wnVtdl4ZJBvZr325w==",
690
+            "version": "4.30.0",
691
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.30.0.tgz",
692
+            "integrity": "sha512-CFE7zDNrokaotXu+shwIrmWrFxllg79vciH4E/zeK7NitVuWEaXRzS0mFfFvyhZfn8WfVOG/1E9u8/DFEgK7WQ==",
693 693
             "cpu": [
694 694
                 "arm64"
695 695
             ],
@@ -701,9 +701,9 @@
701 701
             ]
702 702
         },
703 703
         "node_modules/@rollup/rollup-linux-arm64-musl": {
704
-            "version": "4.29.2",
705
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.29.2.tgz",
706
-            "integrity": "sha512-TF4kxkPq+SudS/r4zGPf0G08Bl7+NZcFrUSR3484WwsHgGgJyPQRLCNrQ/R5J6VzxfEeQR9XRpc8m2t7lD6SEQ==",
704
+            "version": "4.30.0",
705
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.30.0.tgz",
706
+            "integrity": "sha512-MctNTBlvMcIBP0t8lV/NXiUwFg9oK5F79CxLU+a3xgrdJjfBLVIEHSAjQ9+ipofN2GKaMLnFFXLltg1HEEPaGQ==",
707 707
             "cpu": [
708 708
                 "arm64"
709 709
             ],
@@ -715,9 +715,9 @@
715 715
             ]
716 716
         },
717 717
         "node_modules/@rollup/rollup-linux-loongarch64-gnu": {
718
-            "version": "4.29.2",
719
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.29.2.tgz",
720
-            "integrity": "sha512-kO9Fv5zZuyj2zB2af4KA29QF6t7YSxKrY7sxZXfw8koDQj9bx5Tk5RjH+kWKFKok0wLGTi4bG117h31N+TIBEg==",
718
+            "version": "4.30.0",
719
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.30.0.tgz",
720
+            "integrity": "sha512-fBpoYwLEPivL3q368+gwn4qnYnr7GVwM6NnMo8rJ4wb0p/Y5lg88vQRRP077gf+tc25akuqd+1Sxbn9meODhwA==",
721 721
             "cpu": [
722 722
                 "loong64"
723 723
             ],
@@ -729,9 +729,9 @@
729 729
             ]
730 730
         },
731 731
         "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
732
-            "version": "4.29.2",
733
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.29.2.tgz",
734
-            "integrity": "sha512-gIh776X7UCBaetVJGdjXPFurGsdWwHHinwRnC5JlLADU8Yk0EdS/Y+dMO264OjJFo7MXQ5PX4xVFbxrwK8zLqA==",
732
+            "version": "4.30.0",
733
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.30.0.tgz",
734
+            "integrity": "sha512-1hiHPV6dUaqIMXrIjN+vgJqtfkLpqHS1Xsg0oUfUVD98xGp1wX89PIXgDF2DWra1nxAd8dfE0Dk59MyeKaBVAw==",
735 735
             "cpu": [
736 736
                 "ppc64"
737 737
             ],
@@ -743,9 +743,9 @@
743 743
             ]
744 744
         },
745 745
         "node_modules/@rollup/rollup-linux-riscv64-gnu": {
746
-            "version": "4.29.2",
747
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.29.2.tgz",
748
-            "integrity": "sha512-YgikssQ5UNq1GoFKZydMEkhKbjlUq7G3h8j6yWXLBF24KyoA5BcMtaOUAXq5sydPmOPEqB6kCyJpyifSpCfQ0w==",
746
+            "version": "4.30.0",
747
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.30.0.tgz",
748
+            "integrity": "sha512-U0xcC80SMpEbvvLw92emHrNjlS3OXjAM0aVzlWfar6PR0ODWCTQtKeeB+tlAPGfZQXicv1SpWwRz9Hyzq3Jx3g==",
749 749
             "cpu": [
750 750
                 "riscv64"
751 751
             ],
@@ -757,9 +757,9 @@
757 757
             ]
758 758
         },
759 759
         "node_modules/@rollup/rollup-linux-s390x-gnu": {
760
-            "version": "4.29.2",
761
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.29.2.tgz",
762
-            "integrity": "sha512-9ouIR2vFWCyL0Z50dfnon5nOrpDdkTG9lNDs7MRaienQKlTyHcDxplmk3IbhFlutpifBSBr2H4rVILwmMLcaMA==",
760
+            "version": "4.30.0",
761
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.30.0.tgz",
762
+            "integrity": "sha512-VU/P/IODrNPasgZDLIFJmMiLGez+BN11DQWfTVlViJVabyF3JaeaJkP6teI8760f18BMGCQOW9gOmuzFaI1pUw==",
763 763
             "cpu": [
764 764
                 "s390x"
765 765
             ],
@@ -771,9 +771,9 @@
771 771
             ]
772 772
         },
773 773
         "node_modules/@rollup/rollup-linux-x64-gnu": {
774
-            "version": "4.29.2",
775
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.29.2.tgz",
776
-            "integrity": "sha512-ckBBNRN/F+NoSUDENDIJ2U9UWmIODgwDB/vEXCPOMcsco1niTkxTXa6D2Y/pvCnpzaidvY2qVxGzLilNs9BSzw==",
774
+            "version": "4.30.0",
775
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.30.0.tgz",
776
+            "integrity": "sha512-laQVRvdbKmjXuFA3ZiZj7+U24FcmoPlXEi2OyLfbpY2MW1oxLt9Au8q9eHd0x6Pw/Kw4oe9gwVXWwIf2PVqblg==",
777 777
             "cpu": [
778 778
                 "x64"
779 779
             ],
@@ -785,9 +785,9 @@
785 785
             ]
786 786
         },
787 787
         "node_modules/@rollup/rollup-linux-x64-musl": {
788
-            "version": "4.29.2",
789
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.29.2.tgz",
790
-            "integrity": "sha512-jycl1wL4AgM2aBFJFlpll/kGvAjhK8GSbEmFT5v3KC3rP/b5xZ1KQmv0vQQ8Bzb2ieFQ0kZFPRMbre/l3Bu9JA==",
788
+            "version": "4.30.0",
789
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.30.0.tgz",
790
+            "integrity": "sha512-3wzKzduS7jzxqcOvy/ocU/gMR3/QrHEFLge5CD7Si9fyHuoXcidyYZ6jyx8OPYmCcGm3uKTUl+9jUSAY74Ln5A==",
791 791
             "cpu": [
792 792
                 "x64"
793 793
             ],
@@ -799,9 +799,9 @@
799 799
             ]
800 800
         },
801 801
         "node_modules/@rollup/rollup-win32-arm64-msvc": {
802
-            "version": "4.29.2",
803
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.29.2.tgz",
804
-            "integrity": "sha512-S2V0LlcOiYkNGlRAWZwwUdNgdZBfvsDHW0wYosYFV3c7aKgEVcbonetZXsHv7jRTTX+oY5nDYT4W6B1oUpMNOg==",
802
+            "version": "4.30.0",
803
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.30.0.tgz",
804
+            "integrity": "sha512-jROwnI1+wPyuv696rAFHp5+6RFhXGGwgmgSfzE8e4xfit6oLRg7GyMArVUoM3ChS045OwWr9aTnU+2c1UdBMyw==",
805 805
             "cpu": [
806 806
                 "arm64"
807 807
             ],
@@ -813,9 +813,9 @@
813 813
             ]
814 814
         },
815 815
         "node_modules/@rollup/rollup-win32-ia32-msvc": {
816
-            "version": "4.29.2",
817
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.29.2.tgz",
818
-            "integrity": "sha512-pW8kioj9H5f/UujdoX2atFlXNQ9aCfAxFRaa+mhczwcsusm6gGrSo4z0SLvqLF5LwFqFTjiLCCzGkNK/LE0utQ==",
816
+            "version": "4.30.0",
817
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.30.0.tgz",
818
+            "integrity": "sha512-duzweyup5WELhcXx5H1jokpr13i3BV9b48FMiikYAwk/MT1LrMYYk2TzenBd0jj4ivQIt58JWSxc19y4SvLP4g==",
819 819
             "cpu": [
820 820
                 "ia32"
821 821
             ],
@@ -827,9 +827,9 @@
827 827
             ]
828 828
         },
829 829
         "node_modules/@rollup/rollup-win32-x64-msvc": {
830
-            "version": "4.29.2",
831
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.29.2.tgz",
832
-            "integrity": "sha512-p6fTArexECPf6KnOHvJXRpAEq0ON1CBtzG/EY4zw08kCHk/kivBc5vUEtnCFNCHOpJZ2ne77fxwRLIKD4wuW2Q==",
830
+            "version": "4.30.0",
831
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.30.0.tgz",
832
+            "integrity": "sha512-DYvxS0M07PvgvavMIybCOBYheyrqlui6ZQBHJs6GqduVzHSZ06TPPvlfvnYstjODHQ8UUXFwt5YE+h0jFI8kwg==",
833 833
             "cpu": [
834 834
                 "x64"
835 835
             ],
@@ -1235,9 +1235,9 @@
1235 1235
             "license": "MIT"
1236 1236
         },
1237 1237
         "node_modules/electron-to-chromium": {
1238
-            "version": "1.5.76",
1239
-            "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.76.tgz",
1240
-            "integrity": "sha512-CjVQyG7n7Sr+eBXE86HIulnL5N8xZY1sgmOPGuq/F0Rr0FJq63lg0kEtOIDfZBk44FnDLf6FUJ+dsJcuiUDdDQ==",
1238
+            "version": "1.5.77",
1239
+            "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.77.tgz",
1240
+            "integrity": "sha512-AnJSrt5JpRVgY6dgd5yccguLc5A7oMSF0Kt3fcW+Hp5WTuFbl5upeSFZbMZYy2o7jhmIhU8Ekrd82GhyXUqUUg==",
1241 1241
             "dev": true,
1242 1242
             "license": "ISC"
1243 1243
         },
@@ -2242,9 +2242,9 @@
2242 2242
             }
2243 2243
         },
2244 2244
         "node_modules/rollup": {
2245
-            "version": "4.29.2",
2246
-            "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.29.2.tgz",
2247
-            "integrity": "sha512-tJXpsEkzsEzyAKIaB3qv3IuvTVcTN7qBw1jL4SPPXM3vzDrJgiLGFY6+HodgFaUHAJ2RYJ94zV5MKRJCoQzQeA==",
2245
+            "version": "4.30.0",
2246
+            "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.30.0.tgz",
2247
+            "integrity": "sha512-sDnr1pcjTgUT69qBksNF1N1anwfbyYG6TBQ22b03bII8EdiUQ7J0TlozVaTMjT/eEJAO49e1ndV7t+UZfL1+vA==",
2248 2248
             "dev": true,
2249 2249
             "license": "MIT",
2250 2250
             "dependencies": {
@@ -2258,25 +2258,25 @@
2258 2258
                 "npm": ">=8.0.0"
2259 2259
             },
2260 2260
             "optionalDependencies": {
2261
-                "@rollup/rollup-android-arm-eabi": "4.29.2",
2262
-                "@rollup/rollup-android-arm64": "4.29.2",
2263
-                "@rollup/rollup-darwin-arm64": "4.29.2",
2264
-                "@rollup/rollup-darwin-x64": "4.29.2",
2265
-                "@rollup/rollup-freebsd-arm64": "4.29.2",
2266
-                "@rollup/rollup-freebsd-x64": "4.29.2",
2267
-                "@rollup/rollup-linux-arm-gnueabihf": "4.29.2",
2268
-                "@rollup/rollup-linux-arm-musleabihf": "4.29.2",
2269
-                "@rollup/rollup-linux-arm64-gnu": "4.29.2",
2270
-                "@rollup/rollup-linux-arm64-musl": "4.29.2",
2271
-                "@rollup/rollup-linux-loongarch64-gnu": "4.29.2",
2272
-                "@rollup/rollup-linux-powerpc64le-gnu": "4.29.2",
2273
-                "@rollup/rollup-linux-riscv64-gnu": "4.29.2",
2274
-                "@rollup/rollup-linux-s390x-gnu": "4.29.2",
2275
-                "@rollup/rollup-linux-x64-gnu": "4.29.2",
2276
-                "@rollup/rollup-linux-x64-musl": "4.29.2",
2277
-                "@rollup/rollup-win32-arm64-msvc": "4.29.2",
2278
-                "@rollup/rollup-win32-ia32-msvc": "4.29.2",
2279
-                "@rollup/rollup-win32-x64-msvc": "4.29.2",
2261
+                "@rollup/rollup-android-arm-eabi": "4.30.0",
2262
+                "@rollup/rollup-android-arm64": "4.30.0",
2263
+                "@rollup/rollup-darwin-arm64": "4.30.0",
2264
+                "@rollup/rollup-darwin-x64": "4.30.0",
2265
+                "@rollup/rollup-freebsd-arm64": "4.30.0",
2266
+                "@rollup/rollup-freebsd-x64": "4.30.0",
2267
+                "@rollup/rollup-linux-arm-gnueabihf": "4.30.0",
2268
+                "@rollup/rollup-linux-arm-musleabihf": "4.30.0",
2269
+                "@rollup/rollup-linux-arm64-gnu": "4.30.0",
2270
+                "@rollup/rollup-linux-arm64-musl": "4.30.0",
2271
+                "@rollup/rollup-linux-loongarch64-gnu": "4.30.0",
2272
+                "@rollup/rollup-linux-powerpc64le-gnu": "4.30.0",
2273
+                "@rollup/rollup-linux-riscv64-gnu": "4.30.0",
2274
+                "@rollup/rollup-linux-s390x-gnu": "4.30.0",
2275
+                "@rollup/rollup-linux-x64-gnu": "4.30.0",
2276
+                "@rollup/rollup-linux-x64-musl": "4.30.0",
2277
+                "@rollup/rollup-win32-arm64-msvc": "4.30.0",
2278
+                "@rollup/rollup-win32-ia32-msvc": "4.30.0",
2279
+                "@rollup/rollup-win32-x64-msvc": "4.30.0",
2280 2280
                 "fsevents": "~2.3.2"
2281 2281
             }
2282 2282
         },

+ 29
- 0
tests/Feature/Accounting/RecurringInvoiceTest.php View File

@@ -0,0 +1,29 @@
1
+<?php
2
+
3
+use App\Enums\Accounting\IntervalType;
4
+use App\Models\Accounting\RecurringInvoice;
5
+
6
+test('example', function () {
7
+    $recurringInvoice = RecurringInvoice::factory()
8
+        ->custom(IntervalType::Week, 2)
9
+        ->create([
10
+            'start_date' => today(),
11
+            'day_of_week' => today()->dayOfWeek,
12
+        ]);
13
+
14
+    $recurringInvoice->refresh();
15
+
16
+    $nextInvoiceDate = $recurringInvoice->calculateNextDate();
17
+
18
+    expect($nextInvoiceDate)->toEqual(today());
19
+
20
+    $recurringInvoice->update([
21
+        'last_date' => $nextInvoiceDate,
22
+    ]);
23
+
24
+    $recurringInvoice->refresh();
25
+
26
+    $nextInvoiceDate = $recurringInvoice->calculateNextDate();
27
+
28
+    expect($nextInvoiceDate)->toEqual(today()->addWeeks(2));
29
+});

Loading…
Cancel
Save