Andrew Wallo před 9 měsíci
rodič
revize
5177c4b306

+ 39
- 0
app/Console/Commands/TriggerRecurringInvoiceGeneration.php Zobrazit soubor

@@ -0,0 +1,39 @@
1
+<?php
2
+
3
+namespace App\Console\Commands;
4
+
5
+use App\Jobs\GenerateRecurringInvoices;
6
+use Illuminate\Console\Command;
7
+
8
+class TriggerRecurringInvoiceGeneration extends Command
9
+{
10
+    /**
11
+     * The name and signature of the console command.
12
+     *
13
+     * @var string
14
+     */
15
+    protected $signature = 'invoices:generate-recurring {--queue : Whether the job should be queued}';
16
+
17
+    /**
18
+     * The console command description.
19
+     *
20
+     * @var string
21
+     */
22
+    protected $description = 'Generate invoices for active recurring schedules';
23
+
24
+    /**
25
+     * Execute the console command.
26
+     */
27
+    public function handle(): void
28
+    {
29
+        if ($this->option('queue')) {
30
+            GenerateRecurringInvoices::dispatch();
31
+
32
+            $this->info('Recurring invoice generation has been queued.');
33
+        } else {
34
+            GenerateRecurringInvoices::dispatchSync();
35
+
36
+            $this->info('Recurring invoices have been generated.');
37
+        }
38
+    }
39
+}

+ 21
- 0
app/Enums/Accounting/DayOfMonth.php Zobrazit soubor

@@ -3,7 +3,9 @@
3 3
 namespace App\Enums\Accounting;
4 4
 
5 5
 use App\Enums\Concerns\ParsesEnum;
6
+use Carbon\CarbonImmutable;
6 7
 use Filament\Support\Contracts\HasLabel;
8
+use Illuminate\Support\Carbon;
7 9
 
8 10
 enum DayOfMonth: int implements HasLabel
9 11
 {
@@ -79,4 +81,23 @@ enum DayOfMonth: int implements HasLabel
79 81
             self::ThirtyFirst => '31st',
80 82
         };
81 83
     }
84
+
85
+    public function isFirst(): bool
86
+    {
87
+        return $this === self::First;
88
+    }
89
+
90
+    public function isLast(): bool
91
+    {
92
+        return $this === self::Last;
93
+    }
94
+
95
+    public function resolveDate(Carbon | CarbonImmutable $date): Carbon | CarbonImmutable
96
+    {
97
+        if ($this->isLast()) {
98
+            return $date->endOfMonth();
99
+        }
100
+
101
+        return $date->day(min($this->value, $date->daysInMonth));
102
+    }
82 103
 }

+ 18
- 2
app/Filament/Company/Resources/Sales/RecurringInvoiceResource/Pages/ViewRecurringInvoice.php Zobrazit soubor

@@ -4,6 +4,7 @@ namespace App\Filament\Company\Resources\Sales\RecurringInvoiceResource\Pages;
4 4
 
5 5
 use App\Enums\Accounting\DocumentType;
6 6
 use App\Filament\Company\Resources\Sales\ClientResource;
7
+use App\Filament\Company\Resources\Sales\InvoiceResource;
7 8
 use App\Filament\Company\Resources\Sales\RecurringInvoiceResource;
8 9
 use App\Filament\Infolists\Components\DocumentPreview;
9 10
 use App\Models\Accounting\RecurringInvoice;
@@ -19,6 +20,7 @@ use Filament\Support\Enums\FontWeight;
19 20
 use Filament\Support\Enums\IconPosition;
20 21
 use Filament\Support\Enums\IconSize;
21 22
 use Filament\Support\Enums\MaxWidth;
23
+use Illuminate\Support\Str;
22 24
 
23 25
 class ViewRecurringInvoice extends ViewRecord
24 26
 {
@@ -62,6 +64,16 @@ class ViewRecurringInvoice extends ViewRecord
62 64
                         RecurringInvoice::getUpdateScheduleAction(Action::class)
63 65
                             ->outlined(),
64 66
                     ]),
67
+                SimpleAlert::make('readyToApprove')
68
+                    ->info()
69
+                    ->title('Ready to Approve')
70
+                    ->description('This recurring invoice is ready for approval. Review the details, and approve it when you’re ready to start generating invoices.')
71
+                    ->visible(fn (RecurringInvoice $record) => $record->isDraft() && $record->hasSchedule())
72
+                    ->columnSpanFull()
73
+                    ->actions([
74
+                        RecurringInvoice::getApproveDraftAction(Action::class)
75
+                            ->outlined(),
76
+                    ]),
65 77
                 Section::make('Invoice Details')
66 78
                     ->columns(4)
67 79
                     ->schema([
@@ -91,8 +103,12 @@ class ViewRecurringInvoice extends ViewRecord
91 103
                                         return $record->getTimelineDescription();
92 104
                                     }),
93 105
                                 TextEntry::make('occurrences_count')
94
-                                    ->label('Invoices Created')
95
-                                    ->visible(fn (RecurringInvoice $record) => $record->occurrences_count > 0),
106
+                                    ->label('Created to Date')
107
+                                    ->visible(static fn (RecurringInvoice $record) => $record->occurrences_count > 0)
108
+                                    ->color('primary')
109
+                                    ->weight(FontWeight::SemiBold)
110
+                                    ->suffix(fn (RecurringInvoice $record) => Str::of(' invoice')->plural($record->occurrences_count))
111
+                                    ->url(static fn (RecurringInvoice $record) => InvoiceResource::getUrl()),
96 112
                                 TextEntry::make('end_date')
97 113
                                     ->label('Ends On')
98 114
                                     ->date()

+ 33
- 0
app/Jobs/GenerateRecurringInvoices.php Zobrazit soubor

@@ -0,0 +1,33 @@
1
+<?php
2
+
3
+namespace App\Jobs;
4
+
5
+use App\Enums\Accounting\RecurringInvoiceStatus;
6
+use App\Models\Accounting\RecurringInvoice;
7
+use Illuminate\Contracts\Queue\ShouldQueue;
8
+use Illuminate\Foundation\Bus\Dispatchable;
9
+use Illuminate\Foundation\Queue\Queueable;
10
+use Illuminate\Queue\InteractsWithQueue;
11
+use Illuminate\Queue\SerializesModels;
12
+
13
+class GenerateRecurringInvoices implements ShouldQueue
14
+{
15
+    use Dispatchable;
16
+    use InteractsWithQueue;
17
+    use Queueable;
18
+    use SerializesModels;
19
+
20
+    /**
21
+     * Execute the job.
22
+     */
23
+    public function handle(): void
24
+    {
25
+        RecurringInvoice::query()
26
+            ->where('status', RecurringInvoiceStatus::Active)
27
+            ->chunk(100, function ($recurringInvoices) {
28
+                foreach ($recurringInvoices as $recurringInvoice) {
29
+                    $recurringInvoice->generateDueInvoices();
30
+                }
31
+            });
32
+    }
33
+}

+ 269
- 43
app/Models/Accounting/RecurringInvoice.php Zobrazit soubor

@@ -13,6 +13,7 @@ use App\Enums\Accounting\DocumentType;
13 13
 use App\Enums\Accounting\EndType;
14 14
 use App\Enums\Accounting\Frequency;
15 15
 use App\Enums\Accounting\IntervalType;
16
+use App\Enums\Accounting\InvoiceStatus;
16 17
 use App\Enums\Accounting\Month;
17 18
 use App\Enums\Accounting\RecurringInvoiceStatus;
18 19
 use App\Enums\Setting\PaymentTerms;
@@ -28,6 +29,7 @@ use Filament\Forms\Form;
28 29
 use Guava\FilamentClusters\Forms\Cluster;
29 30
 use Illuminate\Database\Eloquent\Attributes\CollectedBy;
30 31
 use Illuminate\Database\Eloquent\Attributes\ObservedBy;
32
+use Illuminate\Database\Eloquent\Model;
31 33
 use Illuminate\Database\Eloquent\Relations\BelongsTo;
32 34
 use Illuminate\Database\Eloquent\Relations\HasMany;
33 35
 use Illuminate\Support\Carbon;
@@ -91,6 +93,7 @@ class RecurringInvoice extends Document
91 93
         'payment_terms' => PaymentTerms::class,
92 94
         'frequency' => Frequency::class,
93 95
         'interval_type' => IntervalType::class,
96
+        'interval_value' => 'integer',
94 97
         'month' => Month::class,
95 98
         'day_of_month' => DayOfMonth::class,
96 99
         'day_of_week' => DayOfWeek::class,
@@ -223,9 +226,6 @@ class RecurringInvoice extends Document
223 226
         return "Repeat every {$interval}{$dayDescription}";
224 227
     }
225 228
 
226
-    /**
227
-     * Get a human-readable description of when the schedule ends.
228
-     */
229 229
     public function getEndDescription(): string
230 230
     {
231 231
         if (! $this->end_type) {
@@ -243,9 +243,6 @@ class RecurringInvoice extends Document
243 243
         };
244 244
     }
245 245
 
246
-    /**
247
-     * Get the schedule timeline description.
248
-     */
249 246
     public function getTimelineDescription(): string
250 247
     {
251 248
         $parts = [];
@@ -261,12 +258,14 @@ class RecurringInvoice extends Document
261 258
         return implode(', ', $parts);
262 259
     }
263 260
 
264
-    /**
265
-     * Get next occurrence date based on the schedule.
266
-     */
267
-    public function calculateNextDate(): ?Carbon
261
+    public function calculateNextDate(?Carbon $lastDate = null): ?Carbon
268 262
     {
269
-        $lastDate = $this->last_date ?? $this->start_date;
263
+        $lastDate ??= $this->last_date;
264
+
265
+        if (! $lastDate && $this->start_date) {
266
+            return $this->start_date;
267
+        }
268
+
270 269
         if (! $lastDate) {
271 270
             return null;
272 271
         }
@@ -274,59 +273,109 @@ class RecurringInvoice extends Document
274 273
         $nextDate = match (true) {
275 274
             $this->frequency->isDaily() => $lastDate->addDay(),
276 275
 
277
-            $this->frequency->isWeekly() => $lastDate->addWeek(),
276
+            $this->frequency->isWeekly() => $this->calculateNextWeeklyDate($lastDate),
278 277
 
279
-            $this->frequency->isMonthly() => $lastDate->addMonth(),
278
+            $this->frequency->isMonthly() => $this->calculateNextMonthlyDate($lastDate),
280 279
 
281
-            $this->frequency->isYearly() => $lastDate->addYear(),
280
+            $this->frequency->isYearly() => $this->calculateNextYearlyDate($lastDate),
282 281
 
283 282
             $this->frequency->isCustom() => $this->calculateCustomNextDate($lastDate),
284 283
 
285 284
             default => null
286 285
         };
287 286
 
288
-        // Check if we've reached the end
289
-        if ($this->hasReachedEnd($nextDate)) {
287
+        if (! $nextDate || $this->hasReachedEnd($nextDate)) {
290 288
             return null;
291 289
         }
292 290
 
293 291
         return $nextDate;
294 292
     }
295 293
 
296
-    public function calculateNextDueDate(): ?Carbon
294
+    public function calculateNextWeeklyDate(Carbon $lastDate): ?Carbon
297 295
     {
298
-        $nextDate = $this->calculateNextDate();
299
-        if (! $nextDate) {
300
-            return null;
301
-        }
296
+        return $lastDate->copy()->next($this->day_of_week->value);
297
+    }
302 298
 
303
-        $terms = $this->payment_terms;
304
-        if (! $terms) {
305
-            return $nextDate;
306
-        }
299
+    public function calculateNextMonthlyDate(Carbon $lastDate): ?Carbon
300
+    {
301
+        return match (true) {
302
+            $lastDate->equalTo($this->start_date) => $lastDate->copy()->day(
303
+                min($this->day_of_month->value, $lastDate->daysInMonth)
304
+            ),
307 305
 
308
-        return $nextDate->addDays($terms->getDays());
306
+            default => $lastDate->copy()->addMonth()->day(
307
+                min($this->day_of_month->value, $lastDate->copy()->addMonth()->daysInMonth)
308
+            ),
309
+        };
309 310
     }
310 311
 
311
-    /**
312
-     * Calculate next date for custom intervals
313
-     */
314
-    protected function calculateCustomNextDate(Carbon $lastDate): ?\Carbon\Carbon
312
+    public function calculateNextYearlyDate(Carbon $lastDate): ?Carbon
315 313
     {
316
-        $value = $this->interval_value ?? 1;
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
+        };
324
+    }
325
+
326
+    protected function calculateCustomNextDate(Carbon $lastDate): ?Carbon
327
+    {
328
+        $interval = $this->interval_value ?? 1;
317 329
 
318 330
         return match ($this->interval_type) {
319
-            IntervalType::Day => $lastDate->addDays($value),
320
-            IntervalType::Week => $lastDate->addWeeks($value),
321
-            IntervalType::Month => $lastDate->addMonths($value),
322
-            IntervalType::Year => $lastDate->addYears($value),
331
+            IntervalType::Day => $lastDate->copy()->addDays($interval),
332
+
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
+                ),
345
+
346
+                default => $lastDate->copy()->addMonths($interval)->day(
347
+                    min($this->day_of_month->value, $lastDate->copy()->addMonths($interval)->daysInMonth)
348
+                ),
349
+            },
350
+
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
+            },
361
+
323 362
             default => null
324 363
         };
325 364
     }
326 365
 
327
-    /**
328
-     * Check if the schedule has reached its end
329
-     */
366
+    public function calculateNextDueDate(): ?Carbon
367
+    {
368
+        if (! $nextDate = $this->calculateNextDate()) {
369
+            return null;
370
+        }
371
+
372
+        if (! $terms = $this->payment_terms) {
373
+            return $nextDate;
374
+        }
375
+
376
+        return $nextDate->copy()->addDays($terms->getDays());
377
+    }
378
+
330 379
     public function hasReachedEnd(?Carbon $nextDate = null): bool
331 380
     {
332 381
         if (! $this->end_type) {
@@ -464,18 +513,74 @@ class RecurringInvoice extends Document
464 513
                             ->visible(
465 514
                                 fn (Forms\Get $get) => Frequency::parse($get('frequency'))->isYearly() ||
466 515
                                 IntervalType::parse($get('interval_type'))?->isYear()
467
-                            ),
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
+
533
+                                    $set('day_of_month', $adjustedDay);
534
+
535
+                                    $set('start_date', $adjustedStartDate);
536
+                                }
537
+                            }),
468 538
 
469 539
                         Forms\Components\Select::make('day_of_month')
470 540
                             ->label('Day of Month')
471
-                            ->options(DayOfMonth::class)
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
+                            })
472 550
                             ->softRequired()
473 551
                             ->visible(
474 552
                                 fn (Forms\Get $get) => Frequency::parse($get('frequency'))?->isMonthly() ||
475 553
                                 Frequency::parse($get('frequency'))?->isYearly() ||
476 554
                                 IntervalType::parse($get('interval_type'))?->isMonth() ||
477 555
                                 IntervalType::parse($get('interval_type'))?->isYear()
478
-                            ),
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
+                            }),
479 584
 
480 585
                         Forms\Components\Select::make('day_of_week')
481 586
                             ->label('Day of Week')
@@ -484,7 +589,17 @@ class RecurringInvoice extends Document
484 589
                             ->visible(
485 590
                                 fn (Forms\Get $get) => Frequency::parse($get('frequency'))?->isWeekly() ||
486 591
                                 IntervalType::parse($get('interval_type'))?->isWeek()
487
-                            ),
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
+                            }),
488 603
                     ])->columns(2),
489 604
 
490 605
                 CustomSection::make('Dates & Time')
@@ -492,7 +607,17 @@ class RecurringInvoice extends Document
492 607
                     ->schema([
493 608
                         Forms\Components\DatePicker::make('start_date')
494 609
                             ->label('First Invoice Date')
495
-                            ->softRequired(),
610
+                            ->softRequired()
611
+                            ->live()
612
+                            ->minDate(today())
613
+                            ->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);
620
+                            }),
496 621
 
497 622
                         Forms\Components\Group::make(function (Forms\Get $get) {
498 623
                             $components = [];
@@ -587,4 +712,105 @@ class RecurringInvoice extends Document
587 712
             'status' => RecurringInvoiceStatus::Active,
588 713
         ]);
589 714
     }
715
+
716
+    public function generateInvoice(): ?Invoice
717
+    {
718
+        if (! $this->shouldGenerateInvoice()) {
719
+            return null;
720
+        }
721
+
722
+        $nextDate = $this->next_date ?? $this->calculateNextDate();
723
+
724
+        if (! $nextDate) {
725
+            return null;
726
+        }
727
+
728
+        $dueDate = $this->calculateNextDueDate();
729
+
730
+        $invoice = $this->invoices()->create([
731
+            'company_id' => $this->company_id,
732
+            'client_id' => $this->client_id,
733
+            'logo' => $this->logo,
734
+            'header' => $this->header,
735
+            'subheader' => $this->subheader,
736
+            'invoice_number' => Invoice::getNextDocumentNumber($this->company),
737
+            'date' => $nextDate,
738
+            'due_date' => $dueDate,
739
+            'status' => InvoiceStatus::Draft,
740
+            'currency_code' => $this->currency_code,
741
+            'discount_method' => $this->discount_method,
742
+            'discount_computation' => $this->discount_computation,
743
+            'discount_rate' => $this->discount_rate,
744
+            'subtotal' => $this->subtotal,
745
+            'tax_total' => $this->tax_total,
746
+            'discount_total' => $this->discount_total,
747
+            'total' => $this->total,
748
+            'terms' => $this->terms,
749
+            'footer' => $this->footer,
750
+            'created_by' => auth()->id(),
751
+            'updated_by' => auth()->id(),
752
+        ]);
753
+
754
+        $this->replicateLineItems($invoice);
755
+
756
+        $this->update([
757
+            'last_date' => $nextDate,
758
+            'next_date' => $this->calculateNextDate($nextDate),
759
+            'occurrences_count' => ($this->occurrences_count ?? 0) + 1,
760
+        ]);
761
+
762
+        return $invoice;
763
+    }
764
+
765
+    public function replicateLineItems(Model $target): void
766
+    {
767
+        $this->lineItems->each(function (DocumentLineItem $lineItem) use ($target) {
768
+            $replica = $lineItem->replicate([
769
+                'documentable_id',
770
+                'documentable_type',
771
+                'subtotal',
772
+                'total',
773
+                'created_by',
774
+                'updated_by',
775
+                'created_at',
776
+                'updated_at',
777
+            ]);
778
+
779
+            $replica->documentable_id = $target->id;
780
+            $replica->documentable_type = $target->getMorphClass();
781
+            $replica->save();
782
+
783
+            $replica->adjustments()->sync($lineItem->adjustments->pluck('id'));
784
+        });
785
+    }
786
+
787
+    public function shouldGenerateInvoice(): bool
788
+    {
789
+        if (! $this->isActive() || $this->hasReachedEnd()) {
790
+            return false;
791
+        }
792
+
793
+        $nextDate = $this->calculateNextDate();
794
+
795
+        if (! $nextDate || $nextDate->startOfDay()->isFuture()) {
796
+            return false;
797
+        }
798
+
799
+        return true;
800
+    }
801
+
802
+    public function generateDueInvoices(): void
803
+    {
804
+        $maxIterations = 100;
805
+
806
+        for ($i = 0; $i < $maxIterations; $i++) {
807
+            $result = $this->generateInvoice();
808
+
809
+            if (! $result) {
810
+                break;
811
+            }
812
+
813
+            $this->refresh();
814
+        }
815
+    }
590 816
 }

+ 38
- 10
app/Observers/RecurringInvoiceObserver.php Zobrazit soubor

@@ -2,25 +2,53 @@
2 2
 
3 3
 namespace App\Observers;
4 4
 
5
+use App\Enums\Accounting\RecurringInvoiceStatus;
6
+use App\Models\Accounting\DocumentLineItem;
5 7
 use App\Models\Accounting\RecurringInvoice;
8
+use Illuminate\Support\Facades\DB;
6 9
 
7 10
 class RecurringInvoiceObserver
8 11
 {
9
-    /**
10
-     * Handle the RecurringInvoice "updated" event.
11
-     */
12
-    public function updated(RecurringInvoice $recurringInvoice): void
12
+    public function saving(RecurringInvoice $recurringInvoice): void
13 13
     {
14
-        $recurringInvoice->updateQuietly([
15
-            'next_date' => $recurringInvoice->calculateNextDate(),
14
+        if (($recurringInvoice->isDirty('start_date') && ! $recurringInvoice->last_date) || $this->otherScheduleDetailsChanged($recurringInvoice)) {
15
+            $recurringInvoice->next_date = $recurringInvoice->calculateNextDate();
16
+        }
17
+
18
+        if ($recurringInvoice->end_type?->isAfter() && $recurringInvoice->occurrences_count >= $recurringInvoice->max_occurrences) {
19
+            $recurringInvoice->status = RecurringInvoiceStatus::Ended;
20
+            $recurringInvoice->ended_at = now();
21
+        }
22
+    }
23
+
24
+    public function saved(RecurringInvoice $recurringInvoice): void
25
+    {
26
+        if ($recurringInvoice->wasChanged('status')) {
27
+            $recurringInvoice->generateDueInvoices();
28
+        }
29
+    }
30
+
31
+    protected function otherScheduleDetailsChanged(RecurringInvoice $recurringInvoice): bool
32
+    {
33
+        return $recurringInvoice->isDirty([
34
+            'frequency',
35
+            'interval_type',
36
+            'interval_value',
37
+            'month',
38
+            'day_of_month',
39
+            'day_of_week',
40
+            'end_type',
41
+            'max_occurrences',
42
+            'end_date',
16 43
         ]);
17 44
     }
18 45
 
19
-    /**
20
-     * Handle the RecurringInvoice "deleted" event.
21
-     */
22 46
     public function deleted(RecurringInvoice $recurringInvoice): void
23 47
     {
24
-        //
48
+        DB::transaction(function () use ($recurringInvoice) {
49
+            $recurringInvoice->lineItems()->each(function (DocumentLineItem $lineItem) {
50
+                $lineItem->delete();
51
+            });
52
+        });
25 53
     }
26 54
 }

+ 96
- 2
database/factories/Accounting/RecurringInvoiceFactory.php Zobrazit soubor

@@ -2,13 +2,29 @@
2 2
 
3 3
 namespace Database\Factories\Accounting;
4 4
 
5
+use App\Enums\Accounting\DayOfMonth;
6
+use App\Enums\Accounting\DayOfWeek;
7
+use App\Enums\Accounting\EndType;
8
+use App\Enums\Accounting\Frequency;
9
+use App\Enums\Accounting\IntervalType;
10
+use App\Enums\Accounting\Month;
11
+use App\Enums\Accounting\RecurringInvoiceStatus;
12
+use App\Enums\Setting\PaymentTerms;
13
+use App\Models\Accounting\RecurringInvoice;
14
+use App\Models\Common\Client;
5 15
 use Illuminate\Database\Eloquent\Factories\Factory;
16
+use Illuminate\Support\Carbon;
6 17
 
7 18
 /**
8
- * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Accounting\RecurringInvoice>
19
+ * @extends Factory<RecurringInvoice>
9 20
  */
10 21
 class RecurringInvoiceFactory extends Factory
11 22
 {
23
+    /**
24
+     * The name of the factory's corresponding model.
25
+     */
26
+    protected $model = RecurringInvoice::class;
27
+
12 28
     /**
13 29
      * Define the model's default state.
14 30
      *
@@ -17,7 +33,85 @@ class RecurringInvoiceFactory extends Factory
17 33
     public function definition(): array
18 34
     {
19 35
         return [
20
-            //
36
+            'company_id' => 1,
37
+            'client_id' => Client::inRandomOrder()->value('id'),
38
+            'status' => RecurringInvoiceStatus::Draft,
39
+
40
+            // Schedule configuration
41
+            'frequency' => Frequency::Monthly,
42
+            'day_of_month' => DayOfMonth::First,
43
+
44
+            // Date configuration
45
+            'start_date' => now()->addMonth()->startOfMonth(),
46
+            'end_type' => EndType::Never,
47
+
48
+            // Invoice configuration
49
+            'payment_terms' => PaymentTerms::DueUponReceipt,
50
+            'currency_code' => 'USD',
51
+
52
+            // Timestamps and user tracking
53
+            'terms' => $this->faker->sentence,
54
+            'footer' => $this->faker->sentence,
55
+            'created_by' => 1,
56
+            'updated_by' => 1,
21 57
         ];
22 58
     }
59
+
60
+    public function weekly(DayOfWeek $dayOfWeek = DayOfWeek::Monday): self
61
+    {
62
+        return $this->state([
63
+            'frequency' => Frequency::Weekly,
64
+            'day_of_week' => $dayOfWeek,
65
+        ]);
66
+    }
67
+
68
+    public function monthly(DayOfMonth $dayOfMonth = DayOfMonth::First): self
69
+    {
70
+        return $this->state([
71
+            'frequency' => Frequency::Monthly,
72
+            'day_of_month' => $dayOfMonth,
73
+        ]);
74
+    }
75
+
76
+    public function yearly(Month $month = Month::January, DayOfMonth $dayOfMonth = DayOfMonth::First): self
77
+    {
78
+        return $this->state([
79
+            'frequency' => Frequency::Yearly,
80
+            'month' => $month,
81
+            'day_of_month' => $dayOfMonth,
82
+        ]);
83
+    }
84
+
85
+    public function custom(IntervalType $intervalType, int $intervalValue = 1): self
86
+    {
87
+        return $this->state([
88
+            'frequency' => Frequency::Custom,
89
+            'interval_type' => $intervalType,
90
+            'interval_value' => $intervalValue,
91
+        ]);
92
+    }
93
+
94
+    public function withEndDate(Carbon $endDate): self
95
+    {
96
+        return $this->state([
97
+            'end_type' => EndType::On,
98
+            'end_date' => $endDate,
99
+        ]);
100
+    }
101
+
102
+    public function withMaxOccurrences(int $maxOccurrences): self
103
+    {
104
+        return $this->state([
105
+            'end_type' => EndType::After,
106
+            'max_occurrences' => $maxOccurrences,
107
+        ]);
108
+    }
109
+
110
+    public function autoSend(string $time = '09:00'): self
111
+    {
112
+        return $this->state([
113
+            'auto_send' => true,
114
+            'send_time' => $time,
115
+        ]);
116
+    }
23 117
 }

+ 83
- 83
package-lock.json Zobrazit soubor

@@ -575,9 +575,9 @@
575 575
             }
576 576
         },
577 577
         "node_modules/@rollup/rollup-android-arm-eabi": {
578
-            "version": "4.29.1",
579
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.29.1.tgz",
580
-            "integrity": "sha512-ssKhA8RNltTZLpG6/QNkCSge+7mBQGUqJRisZ2MDQcEGaK93QESEgWK2iOpIDZ7k9zPVkG5AS3ksvD5ZWxmItw==",
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==",
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.1",
593
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.29.1.tgz",
594
-            "integrity": "sha512-CaRfrV0cd+NIIcVVN/jx+hVLN+VRqnuzLRmfmlzpOzB87ajixsN/+9L5xNmkaUUvEbI5BmIKS+XTwXsHEb65Ew==",
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==",
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.1",
607
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.29.1.tgz",
608
-            "integrity": "sha512-2ORr7T31Y0Mnk6qNuwtyNmy14MunTAMx06VAPI6/Ju52W10zk1i7i5U3vlDRWjhOI5quBcrvhkCHyF76bI7kEw==",
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==",
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.1",
621
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.29.1.tgz",
622
-            "integrity": "sha512-j/Ej1oanzPjmN0tirRd5K2/nncAhS9W6ICzgxV+9Y5ZsP0hiGhHJXZ2JQ53iSSjj8m6cRY6oB1GMzNn2EUt6Ng==",
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==",
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.1",
635
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.29.1.tgz",
636
-            "integrity": "sha512-91C//G6Dm/cv724tpt7nTyP+JdN12iqeXGFM1SqnljCmi5yTXriH7B1r8AD9dAZByHpKAumqP1Qy2vVNIdLZqw==",
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==",
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.1",
649
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.29.1.tgz",
650
-            "integrity": "sha512-hEioiEQ9Dec2nIRoeHUP6hr1PSkXzQaCUyqBDQ9I9ik4gCXQZjJMIVzoNLBRGet+hIUb3CISMh9KXuCcWVW/8w==",
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==",
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.1",
663
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.29.1.tgz",
664
-            "integrity": "sha512-Py5vFd5HWYN9zxBv3WMrLAXY3yYJ6Q/aVERoeUFwiDGiMOWsMs7FokXihSOaT/PMWUty/Pj60XDQndK3eAfE6A==",
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==",
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.1",
677
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.29.1.tgz",
678
-            "integrity": "sha512-RiWpGgbayf7LUcuSNIbahr0ys2YnEERD4gYdISA06wa0i8RALrnzflh9Wxii7zQJEB2/Eh74dX4y/sHKLWp5uQ==",
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==",
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.1",
691
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.29.1.tgz",
692
-            "integrity": "sha512-Z80O+taYxTQITWMjm/YqNoe9d10OX6kDh8X5/rFCMuPqsKsSyDilvfg+vd3iXIqtfmp+cnfL1UrYirkaF8SBZA==",
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==",
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.1",
705
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.29.1.tgz",
706
-            "integrity": "sha512-fOHRtF9gahwJk3QVp01a/GqS4hBEZCV1oKglVVq13kcK3NeVlS4BwIFzOHDbmKzt3i0OuHG4zfRP0YoG5OF/rA==",
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==",
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.1",
719
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.29.1.tgz",
720
-            "integrity": "sha512-5a7q3tnlbcg0OodyxcAdrrCxFi0DgXJSoOuidFUzHZ2GixZXQs6Tc3CHmlvqKAmOs5eRde+JJxeIf9DonkmYkw==",
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==",
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.1",
733
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.29.1.tgz",
734
-            "integrity": "sha512-9b4Mg5Yfz6mRnlSPIdROcfw1BU22FQxmfjlp/CShWwO3LilKQuMISMTtAu/bxmmrE6A902W2cZJuzx8+gJ8e9w==",
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==",
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.1",
747
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.29.1.tgz",
748
-            "integrity": "sha512-G5pn0NChlbRM8OJWpJFMX4/i8OEU538uiSv0P6roZcbpe/WfhEO+AT8SHVKfp8qhDQzaz7Q+1/ixMy7hBRidnQ==",
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==",
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.1",
761
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.29.1.tgz",
762
-            "integrity": "sha512-WM9lIkNdkhVwiArmLxFXpWndFGuOka4oJOZh8EP3Vb8q5lzdSCBuhjavJsw68Q9AKDGeOOIHYzYm4ZFvmWez5g==",
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==",
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.1",
775
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.29.1.tgz",
776
-            "integrity": "sha512-87xYCwb0cPGZFoGiErT1eDcssByaLX4fc0z2nRM6eMtV9njAfEE6OW3UniAoDhX4Iq5xQVpE6qO9aJbCFumKYQ==",
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==",
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.1",
789
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.29.1.tgz",
790
-            "integrity": "sha512-xufkSNppNOdVRCEC4WKvlR1FBDyqCSCpQeMMgv9ZyXqqtKBfkw1yfGMTUTs9Qsl6WQbJnsGboWCp7pJGkeMhKA==",
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==",
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.1",
803
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.29.1.tgz",
804
-            "integrity": "sha512-F2OiJ42m77lSkizZQLuC+jiZ2cgueWQL5YC9tjo3AgaEw+KJmVxHGSyQfDUoYR9cci0lAywv2Clmckzulcq6ig==",
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==",
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.1",
817
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.29.1.tgz",
818
-            "integrity": "sha512-rYRe5S0FcjlOBZQHgbTKNrqxCBUmgDJem/VQTCcTnA2KCabYSWQDrytOzX7avb79cAAweNmMUb/Zw18RNd4mng==",
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==",
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.1",
831
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.29.1.tgz",
832
-            "integrity": "sha512-+10CMg9vt1MoHj6x1pxyjPSMjHTIlqs8/tBztXvPAx24SKs9jwVnKqHJumlH/IzhaPUaj3T6T6wfZr8okdXaIg==",
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==",
833 833
             "cpu": [
834 834
                 "x64"
835 835
             ],
@@ -1300,9 +1300,9 @@
1300 1300
             }
1301 1301
         },
1302 1302
         "node_modules/fast-glob": {
1303
-            "version": "3.3.2",
1304
-            "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
1305
-            "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
1303
+            "version": "3.3.3",
1304
+            "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
1305
+            "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
1306 1306
             "dev": true,
1307 1307
             "license": "MIT",
1308 1308
             "dependencies": {
@@ -1310,7 +1310,7 @@
1310 1310
                 "@nodelib/fs.walk": "^1.2.3",
1311 1311
                 "glob-parent": "^5.1.2",
1312 1312
                 "merge2": "^1.3.0",
1313
-                "micromatch": "^4.0.4"
1313
+                "micromatch": "^4.0.8"
1314 1314
             },
1315 1315
             "engines": {
1316 1316
                 "node": ">=8.6.0"
@@ -2242,9 +2242,9 @@
2242 2242
             }
2243 2243
         },
2244 2244
         "node_modules/rollup": {
2245
-            "version": "4.29.1",
2246
-            "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.29.1.tgz",
2247
-            "integrity": "sha512-RaJ45M/kmJUzSWDs1Nnd5DdV4eerC98idtUOVr6FfKcgxqvjwHmxc5upLF9qZU9EpsVzzhleFahrT3shLuJzIw==",
2245
+            "version": "4.29.2",
2246
+            "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.29.2.tgz",
2247
+            "integrity": "sha512-tJXpsEkzsEzyAKIaB3qv3IuvTVcTN7qBw1jL4SPPXM3vzDrJgiLGFY6+HodgFaUHAJ2RYJ94zV5MKRJCoQzQeA==",
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.1",
2262
-                "@rollup/rollup-android-arm64": "4.29.1",
2263
-                "@rollup/rollup-darwin-arm64": "4.29.1",
2264
-                "@rollup/rollup-darwin-x64": "4.29.1",
2265
-                "@rollup/rollup-freebsd-arm64": "4.29.1",
2266
-                "@rollup/rollup-freebsd-x64": "4.29.1",
2267
-                "@rollup/rollup-linux-arm-gnueabihf": "4.29.1",
2268
-                "@rollup/rollup-linux-arm-musleabihf": "4.29.1",
2269
-                "@rollup/rollup-linux-arm64-gnu": "4.29.1",
2270
-                "@rollup/rollup-linux-arm64-musl": "4.29.1",
2271
-                "@rollup/rollup-linux-loongarch64-gnu": "4.29.1",
2272
-                "@rollup/rollup-linux-powerpc64le-gnu": "4.29.1",
2273
-                "@rollup/rollup-linux-riscv64-gnu": "4.29.1",
2274
-                "@rollup/rollup-linux-s390x-gnu": "4.29.1",
2275
-                "@rollup/rollup-linux-x64-gnu": "4.29.1",
2276
-                "@rollup/rollup-linux-x64-musl": "4.29.1",
2277
-                "@rollup/rollup-win32-arm64-msvc": "4.29.1",
2278
-                "@rollup/rollup-win32-ia32-msvc": "4.29.1",
2279
-                "@rollup/rollup-win32-x64-msvc": "4.29.1",
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",
2280 2280
                 "fsevents": "~2.3.2"
2281 2281
             }
2282 2282
         },

+ 2
- 0
routes/console.php Zobrazit soubor

@@ -1,6 +1,8 @@
1 1
 <?php
2 2
 
3
+use App\Console\Commands\TriggerRecurringInvoiceGeneration;
3 4
 use App\Console\Commands\UpdateOverdueInvoices;
4 5
 use Illuminate\Support\Facades\Schedule;
5 6
 
6 7
 Schedule::command(UpdateOverdueInvoices::class)->everyFiveMinutes();
8
+Schedule::command(TriggerRecurringInvoiceGeneration::class, ['--queue'])->everyMinute();

Načítá se…
Zrušit
Uložit