Andrew Wallo před 8 měsíci
rodič
revize
267aaba680
34 změnil soubory, kde provedl 588 přidání a 318 odebrání
  1. 1
    1
      app/Concerns/ManagesLineItems.php
  2. 24
    2
      app/DTO/ClientDTO.php
  3. 32
    7
      app/DTO/CompanyDTO.php
  4. 5
    0
      app/Enums/Accounting/DayOfMonth.php
  5. 2
    4
      app/Filament/Company/Clusters/Settings/Pages/CompanyProfile.php
  6. 1
    1
      app/Filament/Company/Clusters/Settings/Pages/Localization.php
  7. 2
    2
      app/Filament/Company/Resources/Sales/ClientResource/Pages/CreateClient.php
  8. 2
    2
      app/Filament/Company/Resources/Sales/ClientResource/Pages/EditClient.php
  9. 4
    1
      app/Filament/Company/Resources/Sales/InvoiceResource.php
  10. 10
    3
      app/Filament/Company/Resources/Sales/InvoiceResource/Pages/CreateInvoice.php
  11. 3
    5
      app/Filament/Company/Resources/Sales/RecurringInvoiceResource/Pages/ViewRecurringInvoice.php
  12. 1
    1
      app/Filament/Forms/Components/AddressFields.php
  13. 47
    0
      app/Filament/Forms/Components/Banner.php
  14. 4
    1
      app/Filament/Forms/Components/StateSelect.php
  15. 47
    0
      app/Filament/Infolists/Components/BannerEntry.php
  16. 22
    17
      app/Models/Accounting/RecurringInvoice.php
  17. 3
    3
      app/Models/Common/Address.php
  18. 7
    5
      app/Models/Locale/Country.php
  19. 35
    5
      app/Models/Locale/State.php
  20. 13
    2
      app/Providers/MacroServiceProvider.php
  21. 8
    2
      app/Utilities/Currency/CurrencyConverter.php
  22. 23
    23
      composer.lock
  23. 5
    5
      database/factories/Accounting/RecurringInvoiceFactory.php
  24. 1
    1
      database/factories/Common/AddressFactory.php
  25. 1
    1
      database/factories/CompanyFactory.php
  26. 1
    1
      database/migrations/2024_11_19_225812_create_addresses_table.php
  27. 79
    79
      package-lock.json
  28. 4
    17
      resources/views/filament/infolists/components/document-preview.blade.php
  29. 0
    0
      resources/views/vendor/filament-simple-alert/.gitkeep
  30. 0
    15
      resources/views/vendor/filament-simple-alert/components/simple-alert-entry.blade.php
  31. 0
    15
      resources/views/vendor/filament-simple-alert/components/simple-alert-field.blade.php
  32. 0
    84
      resources/views/vendor/filament-simple-alert/components/simple-alert.blade.php
  33. 187
    13
      tests/Feature/Accounting/RecurringInvoiceTest.php
  34. 14
    0
      tests/TestCase.php

+ 1
- 1
app/Concerns/ManagesLineItems.php Zobrazit soubor

@@ -81,7 +81,7 @@ trait ManagesLineItems
81 81
 
82 82
     protected function updateDocumentTotals(Model $record, array $data): array
83 83
     {
84
-        $currencyCode = $data['currency_code'] ?? CurrencyAccessor::getDefaultCurrency();
84
+        $currencyCode = $data['currency_code'] ?? $record->currency_code ?? CurrencyAccessor::getDefaultCurrency();
85 85
         $subtotalCents = $record->lineItems()->sum('subtotal');
86 86
         $taxTotalCents = $record->lineItems()->sum('tax_total');
87 87
         $discountTotalCents = $this->calculateDiscountTotal(

+ 24
- 2
app/DTO/ClientDTO.php Zobrazit soubor

@@ -25,9 +25,31 @@ readonly class ClientDTO
25 25
             addressLine1: $address?->address_line_1 ?? '',
26 26
             addressLine2: $address?->address_line_2 ?? '',
27 27
             city: $address?->city ?? '',
28
-            state: $address?->state ?? '',
28
+            state: $address?->state?->name ?? '',
29 29
             postalCode: $address?->postal_code ?? '',
30
-            country: $address?->country ?? '',
30
+            country: $address?->country?->name ?? '',
31 31
         );
32 32
     }
33
+
34
+    public function getFormattedAddressHtml(): ?string
35
+    {
36
+        if (empty($this->addressLine1)) {
37
+            return null;
38
+        }
39
+
40
+        $lines = array_filter([
41
+            $this->addressLine1,
42
+            $this->addressLine2,
43
+            implode(', ', array_filter([
44
+                $this->city,
45
+                $this->state,
46
+                $this->postalCode,
47
+            ])),
48
+            $this->country,
49
+        ]);
50
+
51
+        return collect($lines)
52
+            ->map(static fn ($line) => "<p>{$line}</p>")
53
+            ->join('');
54
+    }
33 55
 }

+ 32
- 7
app/DTO/CompanyDTO.php Zobrazit soubor

@@ -8,24 +8,49 @@ readonly class CompanyDTO
8 8
 {
9 9
     public function __construct(
10 10
         public string $name,
11
-        public string $address,
11
+        public string $addressLine1,
12
+        public string $addressLine2,
12 13
         public string $city,
13 14
         public string $state,
14
-        public string $zipCode,
15
+        public string $postalCode,
15 16
         public string $country,
16 17
     ) {}
17 18
 
18 19
     public static function fromModel(Company $company): self
19 20
     {
20 21
         $profile = $company->profile;
22
+        $address = $profile->address ?? null;
21 23
 
22 24
         return new self(
23 25
             name: $company->name,
24
-            address: $profile->address ?? '',
25
-            city: $profile->city?->name ?? '',
26
-            state: $profile->state?->name ?? '',
27
-            zipCode: $profile->zip_code ?? '',
28
-            country: $profile->state?->country->name ?? '',
26
+            addressLine1: $address?->address_line_1 ?? '',
27
+            addressLine2: $address?->address_line_2 ?? '',
28
+            city: $address?->city ?? '',
29
+            state: $address?->state?->name ?? '',
30
+            postalCode: $address?->postal_code ?? '',
31
+            country: $address?->country?->name ?? '',
29 32
         );
30 33
     }
34
+
35
+    public function getFormattedAddressHtml(): ?string
36
+    {
37
+        if (empty($this->addressLine1)) {
38
+            return null;
39
+        }
40
+
41
+        $lines = array_filter([
42
+            $this->addressLine1,
43
+            $this->addressLine2,
44
+            implode(', ', array_filter([
45
+                $this->city,
46
+                $this->state,
47
+                $this->postalCode,
48
+            ])),
49
+            $this->country,
50
+        ]);
51
+
52
+        return collect($lines)
53
+            ->map(static fn ($line) => "<p>{$line}</p>")
54
+            ->join('');
55
+    }
31 56
 }

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

@@ -100,4 +100,9 @@ enum DayOfMonth: int implements HasLabel
100 100
 
101 101
         return $date->day(min($this->value, $date->daysInMonth));
102 102
     }
103
+
104
+    public function mayExceedMonthLength(): bool
105
+    {
106
+        return $this->value > 28;
107
+    }
103 108
 }

+ 2
- 4
app/Filament/Company/Clusters/Settings/Pages/CompanyProfile.php Zobrazit soubor

@@ -5,9 +5,9 @@ namespace App\Filament\Company\Clusters\Settings\Pages;
5 5
 use App\Enums\Setting\EntityType;
6 6
 use App\Filament\Company\Clusters\Settings;
7 7
 use App\Filament\Forms\Components\AddressFields;
8
+use App\Filament\Forms\Components\Banner;
8 9
 use App\Models\Setting\CompanyProfile as CompanyProfileModel;
9 10
 use App\Utilities\Localization\Timezone;
10
-use CodeWithDennis\SimpleAlert\Components\Forms\SimpleAlert;
11 11
 use Filament\Actions\Action;
12 12
 use Filament\Actions\ActionGroup;
13 13
 use Filament\Forms\Components\Component;
@@ -185,10 +185,8 @@ class CompanyProfile extends Page
185 185
 
186 186
     protected function getNeedsAddressCompletionAlert(): Component
187 187
     {
188
-        return SimpleAlert::make('needsAddressCompletion')
188
+        return Banner::make('needsAddressCompletion')
189 189
             ->warning()
190
-            ->border()
191
-            ->icon('heroicon-o-exclamation-triangle')
192 190
             ->title('Address Information Incomplete')
193 191
             ->description('Please complete the required address information for proper business operations.')
194 192
             ->visible(fn (CompanyProfileModel $record) => $record->address->isIncomplete())

+ 1
- 1
app/Filament/Company/Clusters/Settings/Pages/Localization.php Zobrazit soubor

@@ -130,7 +130,7 @@ class Localization extends Page
130 130
                 Select::make('timezone')
131 131
                     ->softRequired()
132 132
                     ->localizeLabel()
133
-                    ->options(Timezone::getTimezoneOptions(CompanyProfileModel::first()->address->country))
133
+                    ->options(Timezone::getTimezoneOptions(CompanyProfileModel::first()->address->country_code))
134 134
                     ->searchable(),
135 135
             ])->columns();
136 136
     }

+ 2
- 2
app/Filament/Company/Resources/Sales/ClientResource/Pages/CreateClient.php Zobrazit soubor

@@ -48,7 +48,7 @@ class CreateClient extends CreateRecord
48 48
                 'parent_address_id' => $billingAddress->id,
49 49
                 'address_line_1' => $billingAddress->address_line_1,
50 50
                 'address_line_2' => $billingAddress->address_line_2,
51
-                'country' => $billingAddress->country,
51
+                'country_code' => $billingAddress->country_code,
52 52
                 'state_id' => $billingAddress->state_id,
53 53
                 'city' => $billingAddress->city,
54 54
                 'postal_code' => $billingAddress->postal_code,
@@ -58,7 +58,7 @@ class CreateClient extends CreateRecord
58 58
                 ...$shippingAddress,
59 59
                 'address_line_1' => $shippingData['address_line_1'],
60 60
                 'address_line_2' => $shippingData['address_line_2'],
61
-                'country' => $shippingData['country'],
61
+                'country_code' => $shippingData['country_code'],
62 62
                 'state_id' => $shippingData['state_id'],
63 63
                 'city' => $shippingData['city'],
64 64
                 'postal_code' => $shippingData['postal_code'],

+ 2
- 2
app/Filament/Company/Resources/Sales/ClientResource/Pages/EditClient.php Zobrazit soubor

@@ -53,7 +53,7 @@ class EditClient extends EditRecord
53 53
                 'parent_address_id' => $billingAddress->id,
54 54
                 'address_line_1' => $billingAddress->address_line_1,
55 55
                 'address_line_2' => $billingAddress->address_line_2,
56
-                'country' => $billingAddress->country,
56
+                'country_code' => $billingAddress->country_code,
57 57
                 'state_id' => $billingAddress->state_id,
58 58
                 'city' => $billingAddress->city,
59 59
                 'postal_code' => $billingAddress->postal_code,
@@ -64,7 +64,7 @@ class EditClient extends EditRecord
64 64
                 'parent_address_id' => null,
65 65
                 'address_line_1' => $shippingData['address_line_1'],
66 66
                 'address_line_2' => $shippingData['address_line_2'],
67
-                'country' => $shippingData['country'],
67
+                'country_code' => $shippingData['country_code'],
68 68
                 'state_id' => $shippingData['state_id'],
69 69
                 'city' => $shippingData['city'],
70 70
                 'postal_code' => $shippingData['postal_code'],

+ 4
- 1
app/Filament/Company/Resources/Sales/InvoiceResource.php Zobrazit soubor

@@ -115,7 +115,10 @@ class InvoiceResource extends Resource
115 115
                                             $set('currency_code', $currencyCode);
116 116
                                         }
117 117
                                     }),
118
-                                CreateCurrencySelect::make('currency_code'),
118
+                                CreateCurrencySelect::make('currency_code')
119
+                                    ->disabled(function (?Invoice $record) {
120
+                                        return $record?->hasPayments();
121
+                                    }),
119 122
                             ]),
120 123
                             Forms\Components\Group::make([
121 124
                                 Forms\Components\TextInput::make('invoice_number')

+ 10
- 3
app/Filament/Company/Resources/Sales/InvoiceResource/Pages/CreateInvoice.php Zobrazit soubor

@@ -6,9 +6,11 @@ use App\Concerns\ManagesLineItems;
6 6
 use App\Concerns\RedirectToListPage;
7 7
 use App\Filament\Company\Resources\Sales\InvoiceResource;
8 8
 use App\Models\Accounting\Invoice;
9
+use App\Models\Common\Client;
9 10
 use Filament\Resources\Pages\CreateRecord;
10 11
 use Filament\Support\Enums\MaxWidth;
11 12
 use Illuminate\Database\Eloquent\Model;
13
+use Livewire\Attributes\Url;
12 14
 
13 15
 class CreateInvoice extends CreateRecord
14 16
 {
@@ -17,14 +19,19 @@ class CreateInvoice extends CreateRecord
17 19
 
18 20
     protected static string $resource = InvoiceResource::class;
19 21
 
22
+    #[Url(as: 'client')]
23
+    public ?int $clientId = null;
24
+
20 25
     public function mount(): void
21 26
     {
22 27
         parent::mount();
23 28
 
24
-        $clientId = request()->query('client');
29
+        if ($this->clientId) {
30
+            $this->data['client_id'] = $this->clientId;
25 31
 
26
-        if ($clientId) {
27
-            $this->data['client_id'] = $clientId;
32
+            if ($currencyCode = Client::find($this->clientId)?->currency_code) {
33
+                $this->data['currency_code'] = $currencyCode;
34
+            }
28 35
         }
29 36
     }
30 37
 

+ 3
- 5
app/Filament/Company/Resources/Sales/RecurringInvoiceResource/Pages/ViewRecurringInvoice.php Zobrazit soubor

@@ -6,9 +6,9 @@ use App\Enums\Accounting\DocumentType;
6 6
 use App\Filament\Company\Resources\Sales\ClientResource;
7 7
 use App\Filament\Company\Resources\Sales\InvoiceResource\Pages\ListInvoices;
8 8
 use App\Filament\Company\Resources\Sales\RecurringInvoiceResource;
9
+use App\Filament\Infolists\Components\BannerEntry;
9 10
 use App\Filament\Infolists\Components\DocumentPreview;
10 11
 use App\Models\Accounting\RecurringInvoice;
11
-use CodeWithDennis\SimpleAlert\Components\Infolists\SimpleAlert;
12 12
 use Filament\Actions;
13 13
 use Filament\Infolists\Components\Actions\Action;
14 14
 use Filament\Infolists\Components\Grid;
@@ -54,10 +54,8 @@ class ViewRecurringInvoice extends ViewRecord
54 54
     {
55 55
         return $infolist
56 56
             ->schema([
57
-                SimpleAlert::make('scheduleIsNotSet')
57
+                BannerEntry::make('scheduleIsNotSet')
58 58
                     ->info()
59
-                    ->border()
60
-                    ->icon('heroicon-o-information-circle')
61 59
                     ->title('Schedule Not Set')
62 60
                     ->description('The schedule for this recurring invoice has not been set. You must set a schedule before you can approve this draft and start creating invoices.')
63 61
                     ->visible(fn (RecurringInvoice $record) => ! $record->hasValidStartDate())
@@ -66,7 +64,7 @@ class ViewRecurringInvoice extends ViewRecord
66 64
                         RecurringInvoice::getUpdateScheduleAction(Action::class)
67 65
                             ->outlined(),
68 66
                     ]),
69
-                SimpleAlert::make('readyToApprove')
67
+                BannerEntry::make('readyToApprove')
70 68
                     ->info()
71 69
                     ->title('Ready to Approve')
72 70
                     ->description('This recurring invoice is ready for approval. Review the details, and approve it when you’re ready to start generating invoices.')

+ 1
- 1
app/Filament/Forms/Components/AddressFields.php Zobrazit soubor

@@ -22,7 +22,7 @@ class AddressFields extends Grid
22 22
             TextInput::make('address_line_2')
23 23
                 ->label('Address Line 2')
24 24
                 ->maxLength(255),
25
-            CountrySelect::make('country')
25
+            CountrySelect::make('country_code')
26 26
                 ->clearStateField()
27 27
                 ->required(),
28 28
             StateSelect::make('state_id'),

+ 47
- 0
app/Filament/Forms/Components/Banner.php Zobrazit soubor

@@ -0,0 +1,47 @@
1
+<?php
2
+
3
+namespace App\Filament\Forms\Components;
4
+
5
+use CodeWithDennis\SimpleAlert\Components\Forms\SimpleAlert;
6
+
7
+class Banner extends SimpleAlert
8
+{
9
+    protected function setUp(): void
10
+    {
11
+        parent::setUp();
12
+
13
+        $this->border();
14
+    }
15
+
16
+    public function danger(): static
17
+    {
18
+        $this->color = 'danger';
19
+        $this->icon = 'heroicon-o-x-circle';
20
+
21
+        return $this;
22
+    }
23
+
24
+    public function info(): static
25
+    {
26
+        $this->color = 'info';
27
+        $this->icon = 'heroicon-o-information-circle';
28
+
29
+        return $this;
30
+    }
31
+
32
+    public function success(): static
33
+    {
34
+        $this->color = 'success';
35
+        $this->icon = 'heroicon-o-check-circle';
36
+
37
+        return $this;
38
+    }
39
+
40
+    public function warning(): static
41
+    {
42
+        $this->color = 'warning';
43
+        $this->icon = 'heroicon-o-exclamation-triangle';
44
+
45
+        return $this;
46
+    }
47
+}

+ 4
- 1
app/Filament/Forms/Components/StateSelect.php Zobrazit soubor

@@ -15,6 +15,9 @@ class StateSelect extends Select
15 15
         $this
16 16
             ->localizeLabel('State / Province')
17 17
             ->searchable()
18
-            ->options(static fn (Get $get) => State::getStateOptions($get('country')));
18
+            ->options(static fn (Get $get) => State::getStateOptions($get('country_code')))
19
+            ->getSearchResultsUsing(static function (string $search, Get $get): array {
20
+                return State::getSearchResultsUsing($search, $get('country_code'));
21
+            });
19 22
     }
20 23
 }

+ 47
- 0
app/Filament/Infolists/Components/BannerEntry.php Zobrazit soubor

@@ -0,0 +1,47 @@
1
+<?php
2
+
3
+namespace App\Filament\Infolists\Components;
4
+
5
+use CodeWithDennis\SimpleAlert\Components\Infolists\SimpleAlert;
6
+
7
+class BannerEntry extends SimpleAlert
8
+{
9
+    protected function setUp(): void
10
+    {
11
+        parent::setUp();
12
+
13
+        $this->border();
14
+    }
15
+
16
+    public function danger(): static
17
+    {
18
+        $this->color = 'danger';
19
+        $this->icon = 'heroicon-o-x-circle';
20
+
21
+        return $this;
22
+    }
23
+
24
+    public function info(): static
25
+    {
26
+        $this->color = 'info';
27
+        $this->icon = 'heroicon-o-information-circle';
28
+
29
+        return $this;
30
+    }
31
+
32
+    public function success(): static
33
+    {
34
+        $this->color = 'success';
35
+        $this->icon = 'heroicon-o-check-circle';
36
+
37
+        return $this;
38
+    }
39
+
40
+    public function warning(): static
41
+    {
42
+        $this->color = 'warning';
43
+        $this->icon = 'heroicon-o-exclamation-triangle';
44
+
45
+        return $this;
46
+    }
47
+}

+ 22
- 17
app/Models/Accounting/RecurringInvoice.php Zobrazit soubor

@@ -17,13 +17,13 @@ use App\Enums\Accounting\InvoiceStatus;
17 17
 use App\Enums\Accounting\Month;
18 18
 use App\Enums\Accounting\RecurringInvoiceStatus;
19 19
 use App\Enums\Setting\PaymentTerms;
20
+use App\Filament\Forms\Components\Banner;
20 21
 use App\Filament\Forms\Components\CustomSection;
21 22
 use App\Models\Common\Client;
22 23
 use App\Models\Setting\CompanyProfile;
23 24
 use App\Observers\RecurringInvoiceObserver;
24 25
 use App\Support\ScheduleHandler;
25 26
 use App\Utilities\Localization\Timezone;
26
-use CodeWithDennis\SimpleAlert\Components\Forms\SimpleAlert;
27 27
 use Filament\Actions\Action;
28 28
 use Filament\Actions\MountableAction;
29 29
 use Filament\Forms;
@@ -311,19 +311,25 @@ class RecurringInvoice extends Document
311 311
         return $nextDate;
312 312
     }
313 313
 
314
-    public function calculateNextWeeklyDate(Carbon $lastDate): ?Carbon
314
+    public function calculateNextWeeklyDate(Carbon $lastDate, int $interval = 1): ?Carbon
315 315
     {
316
-        return $lastDate->copy()->next($this->day_of_week->name);
316
+        return $lastDate->copy()
317
+            ->addWeeks($interval - 1)
318
+            ->next($this->day_of_week->value);
317 319
     }
318 320
 
319
-    public function calculateNextMonthlyDate(Carbon $lastDate): ?Carbon
321
+    public function calculateNextMonthlyDate(Carbon $lastDate, int $interval = 1): ?Carbon
320 322
     {
321
-        return $this->day_of_month->resolveDate($lastDate->copy()->addMonth());
323
+        return $this->day_of_month->resolveDate(
324
+            $lastDate->copy()->addMonthsNoOverflow($interval)
325
+        );
322 326
     }
323 327
 
324
-    public function calculateNextYearlyDate(Carbon $lastDate): ?Carbon
328
+    public function calculateNextYearlyDate(Carbon $lastDate, int $interval = 1): ?Carbon
325 329
     {
326
-        return $this->day_of_month->resolveDate($lastDate->copy()->addYear()->month($this->month->value));
330
+        return $this->day_of_month->resolveDate(
331
+            $lastDate->copy()->addYears($interval)->month($this->month->value)
332
+        );
327 333
     }
328 334
 
329 335
     protected function calculateCustomNextDate(Carbon $lastDate): ?Carbon
@@ -333,11 +339,11 @@ class RecurringInvoice extends Document
333 339
         return match ($this->interval_type) {
334 340
             IntervalType::Day => $lastDate->copy()->addDays($interval),
335 341
 
336
-            IntervalType::Week => $lastDate->copy()->addWeeks($interval),
342
+            IntervalType::Week => $this->calculateNextWeeklyDate($lastDate, $interval),
337 343
 
338
-            IntervalType::Month => $this->day_of_month->resolveDate($lastDate->copy()->addMonths($interval)),
344
+            IntervalType::Month => $this->calculateNextMonthlyDate($lastDate, $interval),
339 345
 
340
-            IntervalType::Year => $this->day_of_month->resolveDate($lastDate->copy()->addYears($interval)->month($this->month->value)),
346
+            IntervalType::Year => $this->calculateNextYearlyDate($lastDate, $interval),
341 347
 
342 348
             default => null
343 349
         };
@@ -408,7 +414,6 @@ class RecurringInvoice extends Document
408 414
                                     $handler->handleFrequencyChange($state);
409 415
                                 }),
410 416
 
411
-                            // Custom frequency fields in a nested grid
412 417
                             Cluster::make([
413 418
                                 Forms\Components\TextInput::make('interval_value')
414 419
                                     ->softRequired()
@@ -430,7 +435,6 @@ class RecurringInvoice extends Document
430 435
                                 ->markAsRequired(false)
431 436
                                 ->visible($frequency->isCustom()),
432 437
 
433
-                            // Specific schedule details
434 438
                             Forms\Components\Select::make('month')
435 439
                                 ->label('Month')
436 440
                                 ->options(Month::class)
@@ -463,18 +467,19 @@ class RecurringInvoice extends Document
463 467
                                     $handler->handleDateChange('day_of_month', $state);
464 468
                                 }),
465 469
 
466
-                            SimpleAlert::make('dayOfMonthNotice')
467
-                                ->title(function () use ($dayOfMonth) {
468
-                                    return "The invoice will be created on the {$dayOfMonth->getLabel()} day of each month, or on the last day for months ending earlier.";
470
+                            Banner::make('dayOfMonthNotice')
471
+                                ->info()
472
+                                ->title(static function () use ($dayOfMonth) {
473
+                                    return "For months with fewer than {$dayOfMonth->value} days, the last day of the month will be used.";
469 474
                                 })
470 475
                                 ->columnSpanFull()
471
-                                ->visible($dayOfMonth?->value > 28),
476
+                                ->visible($dayOfMonth?->mayExceedMonthLength() && ($frequency->isMonthly() || $intervalType?->isMonth())),
472 477
 
473 478
                             Forms\Components\Select::make('day_of_week')
474 479
                                 ->label('Day of Week')
475 480
                                 ->options(DayOfWeek::class)
476 481
                                 ->softRequired()
477
-                                ->visible($frequency->isWeekly() || $intervalType?->isWeek())
482
+                                ->visible(($frequency->isWeekly() || $intervalType?->isWeek()) ?? false)
478 483
                                 ->live()
479 484
                                 ->afterStateUpdated(function (Forms\Set $set, $state) {
480 485
                                     $handler = new ScheduleHandler($set);

+ 3
- 3
app/Models/Common/Address.php Zobrazit soubor

@@ -33,7 +33,7 @@ class Address extends Model
33 33
         'city',
34 34
         'state_id',
35 35
         'postal_code',
36
-        'country',
36
+        'country_code',
37 37
         'notes',
38 38
         'created_by',
39 39
         'updated_by',
@@ -60,7 +60,7 @@ class Address extends Model
60 60
 
61 61
     public function country(): BelongsTo
62 62
     {
63
-        return $this->belongsTo(Country::class, 'country', 'id');
63
+        return $this->belongsTo(Country::class, 'country_code', 'id');
64 64
     }
65 65
 
66 66
     public function state(): BelongsTo
@@ -80,7 +80,7 @@ class Address extends Model
80 80
                 implode(', ', $street), // Street 1 & 2 on same line if both exist
81 81
                 implode(', ', array_filter([
82 82
                     $this->city,
83
-                    $this->state->state_code,
83
+                    $this->state->name,
84 84
                     $this->postal_code,
85 85
                 ])),
86 86
             ]);

+ 7
- 5
app/Models/Locale/Country.php Zobrazit soubor

@@ -51,7 +51,7 @@ class Country extends Model
51 51
 
52 52
     public function addresses(): HasMany
53 53
     {
54
-        return $this->hasMany(Address::class, 'country', 'id');
54
+        return $this->hasMany(Address::class, 'country_code', 'id');
55 55
     }
56 56
 
57 57
     public function states(): HasMany
@@ -101,8 +101,10 @@ class Country extends Model
101 101
     {
102 102
         return self::query()
103 103
             ->select(['id', 'name', 'flag'])
104
-            ->whereLike('name', "%{$search}%")
105
-            ->orWhereLike('id', "%{$search}%")
104
+            ->where(static function ($query) use ($search) {
105
+                $query->whereLike('name', "%{$search}%")
106
+                    ->orWhereLike('id', "%{$search}%");
107
+            })
106 108
             ->orderByRaw('
107 109
                 CASE
108 110
                     WHEN id = ? THEN 1
@@ -113,7 +115,7 @@ class Country extends Model
113 115
             ', [$search, $search . '%', $search . '%'])
114 116
             ->limit(50)
115 117
             ->get()
116
-            ->mapWithKeys(static fn ($country) => [
118
+            ->mapWithKeys(static fn (self $country) => [
117 119
                 $country->id => $country->name . ' ' . $country->flag,
118 120
             ])
119 121
             ->toArray();
@@ -121,7 +123,7 @@ class Country extends Model
121 123
 
122 124
     public static function getLanguagesByCountryCode(?string $code = null): array
123 125
     {
124
-        if ($code === null) {
126
+        if (! $code) {
125 127
             return Locales::getNames();
126 128
         }
127 129
 

+ 35
- 5
app/Models/Locale/State.php Zobrazit soubor

@@ -4,7 +4,6 @@ namespace App\Models\Locale;
4 4
 
5 5
 use Illuminate\Database\Eloquent\Relations\BelongsTo;
6 6
 use Illuminate\Database\Eloquent\Relations\HasMany;
7
-use Illuminate\Support\Collection;
8 7
 use Squire\Model;
9 8
 
10 9
 /**
@@ -28,13 +27,44 @@ class State extends Model
28 27
         'longitude' => 'float',
29 28
     ];
30 29
 
31
-    public static function getStateOptions(?string $code = null): Collection
30
+    public static function getStateOptions(?string $code = null): array
32 31
     {
33
-        if ($code === null) {
34
-            return collect();
32
+        if (! $code) {
33
+            return [];
35 34
         }
36 35
 
37
-        return self::where('country_id', $code)->get()->pluck('name', 'id');
36
+        return self::query()
37
+            ->where('country_id', $code)
38
+            ->orderBy('name')
39
+            ->get()
40
+            ->pluck('name', 'id')
41
+            ->toArray();
42
+    }
43
+
44
+    public static function getSearchResultsUsing(string $search, ?string $countryCode = null): array
45
+    {
46
+        if (! $countryCode) {
47
+            return [];
48
+        }
49
+
50
+        return self::query()
51
+            ->where('country_id', $countryCode)
52
+            ->where(static function ($query) use ($search) {
53
+                $query->whereLike('name', "%{$search}%")
54
+                    ->orWhereLike('state_code', "%{$search}%");
55
+            })
56
+            ->orderByRaw('
57
+                CASE
58
+                    WHEN state_code = ? THEN 1
59
+                    WHEN state_code LIKE ? THEN 2
60
+                    WHEN name LIKE ? THEN 3
61
+                    ELSE 4
62
+                END
63
+            ', [$search, $search . '%', $search . '%'])
64
+            ->limit(50)
65
+            ->get()
66
+            ->pluck('name', 'id')
67
+            ->toArray();
38 68
     }
39 69
 
40 70
     public function country(): BelongsTo

+ 13
- 2
app/Providers/MacroServiceProvider.php Zobrazit soubor

@@ -211,12 +211,19 @@ class MacroServiceProvider extends ServiceProvider
211 211
                 }
212 212
 
213 213
                 $currency = $column->evaluate($currency);
214
+                $showCurrency = $currency !== CurrencyAccessor::getDefaultCurrency();
214 215
 
215 216
                 if ($convertFromCents) {
216
-                    return CurrencyConverter::formatCentsToMoney($state, $currency);
217
+                    $balanceInCents = $state;
218
+                } else {
219
+                    $balanceInCents = CurrencyConverter::convertToCents($state, $currency);
217 220
                 }
218 221
 
219
-                return CurrencyConverter::formatToMoney($state, $currency);
222
+                if ($balanceInCents < 0) {
223
+                    return '(' . CurrencyConverter::formatCentsToMoney(abs($balanceInCents), $currency, $showCurrency) . ')';
224
+                }
225
+
226
+                return CurrencyConverter::formatCentsToMoney($balanceInCents, $currency, $showCurrency);
220 227
             });
221 228
 
222 229
             $this->description(static function (TextColumn $column, $state) use ($currency, $convertFromCents): ?string {
@@ -239,6 +246,10 @@ class MacroServiceProvider extends ServiceProvider
239 246
 
240 247
                 $convertedBalanceInCents = CurrencyConverter::convertBalance($balanceInCents, $oldCurrency, $newCurrency);
241 248
 
249
+                if ($convertedBalanceInCents < 0) {
250
+                    return '(' . CurrencyConverter::formatCentsToMoney(abs($convertedBalanceInCents), $newCurrency, true) . ')';
251
+                }
252
+
242 253
                 return CurrencyConverter::formatCentsToMoney($convertedBalanceInCents, $newCurrency, true);
243 254
             });
244 255
 

+ 8
- 2
app/Utilities/Currency/CurrencyConverter.php Zobrazit soubor

@@ -62,11 +62,17 @@ class CurrencyConverter
62 62
         return $money->format();
63 63
     }
64 64
 
65
-    public static function formatToMoney(string | float $amount, ?string $currency = null): string
65
+    public static function formatToMoney(string | float $amount, ?string $currency = null, bool $withCode = false): string
66 66
     {
67 67
         $currency ??= CurrencyAccessor::getDefaultCurrency();
68 68
 
69
-        return money($amount, $currency, true)->format();
69
+        $money = money($amount, $currency, true);
70
+
71
+        if ($withCode) {
72
+            return $money->formatWithCode();
73
+        }
74
+
75
+        return $money->format();
70 76
     }
71 77
 
72 78
     public static function convertCentsToFloat(int $amount, ?string $currency = null): float

+ 23
- 23
composer.lock Zobrazit soubor

@@ -1029,16 +1029,16 @@
1029 1029
         },
1030 1030
         {
1031 1031
             "name": "codewithdennis/filament-simple-alert",
1032
-            "version": "v3.0.15",
1032
+            "version": "v3.0.16",
1033 1033
             "source": {
1034 1034
                 "type": "git",
1035 1035
                 "url": "https://github.com/CodeWithDennis/filament-simple-alert.git",
1036
-                "reference": "8fd7dfa48bb98061bcc3f5fbaacb82dce7a09c7b"
1036
+                "reference": "f29677d3a0d2b6fd9b1c3627152cd0107d2db337"
1037 1037
             },
1038 1038
             "dist": {
1039 1039
                 "type": "zip",
1040
-                "url": "https://api.github.com/repos/CodeWithDennis/filament-simple-alert/zipball/8fd7dfa48bb98061bcc3f5fbaacb82dce7a09c7b",
1041
-                "reference": "8fd7dfa48bb98061bcc3f5fbaacb82dce7a09c7b",
1040
+                "url": "https://api.github.com/repos/CodeWithDennis/filament-simple-alert/zipball/f29677d3a0d2b6fd9b1c3627152cd0107d2db337",
1041
+                "reference": "f29677d3a0d2b6fd9b1c3627152cd0107d2db337",
1042 1042
                 "shasum": ""
1043 1043
             },
1044 1044
             "require": {
@@ -1098,7 +1098,7 @@
1098 1098
                     "type": "github"
1099 1099
                 }
1100 1100
             ],
1101
-            "time": "2024-12-03T16:17:47+00:00"
1101
+            "time": "2025-01-19T17:37:34+00:00"
1102 1102
         },
1103 1103
         {
1104 1104
             "name": "danharrin/date-format-converter",
@@ -10149,16 +10149,16 @@
10149 10149
         },
10150 10150
         {
10151 10151
             "name": "pestphp/pest",
10152
-            "version": "v3.7.1",
10152
+            "version": "v3.7.2",
10153 10153
             "source": {
10154 10154
                 "type": "git",
10155 10155
                 "url": "https://github.com/pestphp/pest.git",
10156
-                "reference": "bf3178473dcaa53b0458f21dfdb271306ea62512"
10156
+                "reference": "709ecb1ba2641fc0c4653ebe1fd8a402bbf4d18b"
10157 10157
             },
10158 10158
             "dist": {
10159 10159
                 "type": "zip",
10160
-                "url": "https://api.github.com/repos/pestphp/pest/zipball/bf3178473dcaa53b0458f21dfdb271306ea62512",
10161
-                "reference": "bf3178473dcaa53b0458f21dfdb271306ea62512",
10160
+                "url": "https://api.github.com/repos/pestphp/pest/zipball/709ecb1ba2641fc0c4653ebe1fd8a402bbf4d18b",
10161
+                "reference": "709ecb1ba2641fc0c4653ebe1fd8a402bbf4d18b",
10162 10162
                 "shasum": ""
10163 10163
             },
10164 10164
             "require": {
@@ -10169,17 +10169,17 @@
10169 10169
                 "pestphp/pest-plugin-arch": "^3.0.0",
10170 10170
                 "pestphp/pest-plugin-mutate": "^3.0.5",
10171 10171
                 "php": "^8.2.0",
10172
-                "phpunit/phpunit": "^11.5.1"
10172
+                "phpunit/phpunit": "^11.5.3"
10173 10173
             },
10174 10174
             "conflict": {
10175 10175
                 "filp/whoops": "<2.16.0",
10176
-                "phpunit/phpunit": ">11.5.1",
10176
+                "phpunit/phpunit": ">11.5.3",
10177 10177
                 "sebastian/exporter": "<6.0.0",
10178 10178
                 "webmozart/assert": "<1.11.0"
10179 10179
             },
10180 10180
             "require-dev": {
10181 10181
                 "pestphp/pest-dev-tools": "^3.3.0",
10182
-                "pestphp/pest-plugin-type-coverage": "^3.2.0",
10182
+                "pestphp/pest-plugin-type-coverage": "^3.2.3",
10183 10183
                 "symfony/process": "^7.2.0"
10184 10184
             },
10185 10185
             "bin": [
@@ -10245,7 +10245,7 @@
10245 10245
             ],
10246 10246
             "support": {
10247 10247
                 "issues": "https://github.com/pestphp/pest/issues",
10248
-                "source": "https://github.com/pestphp/pest/tree/v3.7.1"
10248
+                "source": "https://github.com/pestphp/pest/tree/v3.7.2"
10249 10249
             },
10250 10250
             "funding": [
10251 10251
                 {
@@ -10257,7 +10257,7 @@
10257 10257
                     "type": "github"
10258 10258
                 }
10259 10259
             ],
10260
-            "time": "2024-12-12T11:52:01+00:00"
10260
+            "time": "2025-01-19T17:35:09+00:00"
10261 10261
         },
10262 10262
         {
10263 10263
             "name": "pestphp/pest-plugin",
@@ -11202,16 +11202,16 @@
11202 11202
         },
11203 11203
         {
11204 11204
             "name": "phpunit/phpunit",
11205
-            "version": "11.5.1",
11205
+            "version": "11.5.3",
11206 11206
             "source": {
11207 11207
                 "type": "git",
11208 11208
                 "url": "https://github.com/sebastianbergmann/phpunit.git",
11209
-                "reference": "2b94d4f2450b9869fa64a46fd8a6a41997aef56a"
11209
+                "reference": "30e319e578a7b5da3543073e30002bf82042f701"
11210 11210
             },
11211 11211
             "dist": {
11212 11212
                 "type": "zip",
11213
-                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/2b94d4f2450b9869fa64a46fd8a6a41997aef56a",
11214
-                "reference": "2b94d4f2450b9869fa64a46fd8a6a41997aef56a",
11213
+                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/30e319e578a7b5da3543073e30002bf82042f701",
11214
+                "reference": "30e319e578a7b5da3543073e30002bf82042f701",
11215 11215
                 "shasum": ""
11216 11216
             },
11217 11217
             "require": {
@@ -11225,14 +11225,14 @@
11225 11225
                 "phar-io/manifest": "^2.0.4",
11226 11226
                 "phar-io/version": "^3.2.1",
11227 11227
                 "php": ">=8.2",
11228
-                "phpunit/php-code-coverage": "^11.0.7",
11228
+                "phpunit/php-code-coverage": "^11.0.8",
11229 11229
                 "phpunit/php-file-iterator": "^5.1.0",
11230 11230
                 "phpunit/php-invoker": "^5.0.1",
11231 11231
                 "phpunit/php-text-template": "^4.0.1",
11232 11232
                 "phpunit/php-timer": "^7.0.1",
11233 11233
                 "sebastian/cli-parser": "^3.0.2",
11234
-                "sebastian/code-unit": "^3.0.1",
11235
-                "sebastian/comparator": "^6.2.1",
11234
+                "sebastian/code-unit": "^3.0.2",
11235
+                "sebastian/comparator": "^6.3.0",
11236 11236
                 "sebastian/diff": "^6.0.2",
11237 11237
                 "sebastian/environment": "^7.2.0",
11238 11238
                 "sebastian/exporter": "^6.3.0",
@@ -11283,7 +11283,7 @@
11283 11283
             "support": {
11284 11284
                 "issues": "https://github.com/sebastianbergmann/phpunit/issues",
11285 11285
                 "security": "https://github.com/sebastianbergmann/phpunit/security/policy",
11286
-                "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.1"
11286
+                "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.3"
11287 11287
             },
11288 11288
             "funding": [
11289 11289
                 {
@@ -11299,7 +11299,7 @@
11299 11299
                     "type": "tidelift"
11300 11300
                 }
11301 11301
             ],
11302
-            "time": "2024-12-11T10:52:48+00:00"
11302
+            "time": "2025-01-13T09:36:00+00:00"
11303 11303
         },
11304 11304
         {
11305 11305
             "name": "pimple/pimple",

+ 5
- 5
database/factories/Accounting/RecurringInvoiceFactory.php Zobrazit soubor

@@ -90,7 +90,7 @@ class RecurringInvoiceFactory extends Factory
90 90
         });
91 91
     }
92 92
 
93
-    protected function withDailySchedule(Carbon $startDate, EndType $endType): static
93
+    public function withDailySchedule(Carbon $startDate, EndType $endType): static
94 94
     {
95 95
         return $this->afterCreating(function (RecurringInvoice $recurringInvoice) use ($startDate, $endType) {
96 96
             $this->ensureLineItems($recurringInvoice);
@@ -103,7 +103,7 @@ class RecurringInvoiceFactory extends Factory
103 103
         });
104 104
     }
105 105
 
106
-    protected function withWeeklySchedule(Carbon $startDate, EndType $endType): static
106
+    public function withWeeklySchedule(Carbon $startDate, EndType $endType): static
107 107
     {
108 108
         return $this->afterCreating(function (RecurringInvoice $recurringInvoice) use ($startDate, $endType) {
109 109
             $this->ensureLineItems($recurringInvoice);
@@ -117,7 +117,7 @@ class RecurringInvoiceFactory extends Factory
117 117
         });
118 118
     }
119 119
 
120
-    protected function withMonthlySchedule(Carbon $startDate, EndType $endType): static
120
+    public function withMonthlySchedule(Carbon $startDate, EndType $endType): static
121 121
     {
122 122
         return $this->afterCreating(function (RecurringInvoice $recurringInvoice) use ($startDate, $endType) {
123 123
             $this->ensureLineItems($recurringInvoice);
@@ -131,7 +131,7 @@ class RecurringInvoiceFactory extends Factory
131 131
         });
132 132
     }
133 133
 
134
-    protected function withYearlySchedule(Carbon $startDate, EndType $endType): static
134
+    public function withYearlySchedule(Carbon $startDate, EndType $endType): static
135 135
     {
136 136
         return $this->afterCreating(function (RecurringInvoice $recurringInvoice) use ($startDate, $endType) {
137 137
             $this->ensureLineItems($recurringInvoice);
@@ -146,7 +146,7 @@ class RecurringInvoiceFactory extends Factory
146 146
         });
147 147
     }
148 148
 
149
-    protected function withCustomSchedule(
149
+    public function withCustomSchedule(
150 150
         Carbon $startDate,
151 151
         EndType $endType,
152 152
         ?IntervalType $intervalType = null,

+ 1
- 1
database/factories/Common/AddressFactory.php Zobrazit soubor

@@ -33,7 +33,7 @@ class AddressFactory extends Factory
33 33
             'city' => $this->faker->city,
34 34
             'state_id' => $this->faker->state('US'),
35 35
             'postal_code' => $this->faker->postcode,
36
-            'country' => 'US',
36
+            'country_code' => 'US',
37 37
             'notes' => $this->faker->sentence,
38 38
             'created_by' => 1,
39 39
             'updated_by' => 1,

+ 1
- 1
database/factories/CompanyFactory.php Zobrazit soubor

@@ -52,7 +52,7 @@ class CompanyFactory extends Factory
52 52
     public function withCompanyDefaults(): self
53 53
     {
54 54
         return $this->afterCreating(function (Company $company) {
55
-            $countryCode = $company->profile->address->country;
55
+            $countryCode = $company->profile->address->country_code;
56 56
             $companyDefaultService = app(CompanyDefaultService::class);
57 57
             $companyDefaultService->createCompanyDefaults($company, $company->owner, 'USD', $countryCode, 'en');
58 58
         });

+ 1
- 1
database/migrations/2024_11_19_225812_create_addresses_table.php Zobrazit soubor

@@ -24,7 +24,7 @@ return new class extends Migration
24 24
             $table->string('city')->nullable();
25 25
             $table->smallInteger('state_id')->nullable();
26 26
             $table->string('postal_code')->nullable();
27
-            $table->string('country');
27
+            $table->string('country_code');
28 28
             $table->text('notes')->nullable();
29 29
             $table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
30 30
             $table->foreignId('updated_by')->nullable()->constrained('users')->nullOnDelete();

+ 79
- 79
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.30.1",
579
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.30.1.tgz",
580
-            "integrity": "sha512-pSWY+EVt3rJ9fQ3IqlrEUtXh3cGqGtPDH1FQlNZehO2yYxCHEX1SPsz1M//NXwYfbTlcKr9WObLnJX9FsS9K1Q==",
578
+            "version": "4.31.0",
579
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.31.0.tgz",
580
+            "integrity": "sha512-9NrR4033uCbUBRgvLcBrJofa2KY9DzxL2UKZ1/4xA/mnTNyhZCWBuD8X3tPm1n4KxcgaraOYgrFKSgwjASfmlA==",
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.30.1",
593
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.30.1.tgz",
594
-            "integrity": "sha512-/NA2qXxE3D/BRjOJM8wQblmArQq1YoBVJjrjoTSBS09jgUisq7bqxNHJ8kjCHeV21W/9WDGwJEWSN0KQ2mtD/w==",
592
+            "version": "4.31.0",
593
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.31.0.tgz",
594
+            "integrity": "sha512-iBbODqT86YBFHajxxF8ebj2hwKm1k8PTBQSojSt3d1FFt1gN+xf4CowE47iN0vOSdnd+5ierMHBbu/rHc7nq5g==",
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.30.1",
607
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.30.1.tgz",
608
-            "integrity": "sha512-r7FQIXD7gB0WJ5mokTUgUWPl0eYIH0wnxqeSAhuIwvnnpjdVB8cRRClyKLQr7lgzjctkbp5KmswWszlwYln03Q==",
606
+            "version": "4.31.0",
607
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.31.0.tgz",
608
+            "integrity": "sha512-WHIZfXgVBX30SWuTMhlHPXTyN20AXrLH4TEeH/D0Bolvx9PjgZnn4H677PlSGvU6MKNsjCQJYczkpvBbrBnG6g==",
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.30.1",
621
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.30.1.tgz",
622
-            "integrity": "sha512-x78BavIwSH6sqfP2xeI1hd1GpHL8J4W2BXcVM/5KYKoAD3nNsfitQhvWSw+TFtQTLZ9OmlF+FEInEHyubut2OA==",
620
+            "version": "4.31.0",
621
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.31.0.tgz",
622
+            "integrity": "sha512-hrWL7uQacTEF8gdrQAqcDy9xllQ0w0zuL1wk1HV8wKGSGbKPVjVUv/DEwT2+Asabf8Dh/As+IvfdU+H8hhzrQQ==",
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.30.1",
635
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.30.1.tgz",
636
-            "integrity": "sha512-HYTlUAjbO1z8ywxsDFWADfTRfTIIy/oUlfIDmlHYmjUP2QRDTzBuWXc9O4CXM+bo9qfiCclmHk1x4ogBjOUpUQ==",
634
+            "version": "4.31.0",
635
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.31.0.tgz",
636
+            "integrity": "sha512-S2oCsZ4hJviG1QjPY1h6sVJLBI6ekBeAEssYKad1soRFv3SocsQCzX6cwnk6fID6UQQACTjeIMB+hyYrFacRew==",
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.30.1",
649
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.30.1.tgz",
650
-            "integrity": "sha512-1MEdGqogQLccphhX5myCJqeGNYTNcmTyaic9S7CG3JhwuIByJ7J05vGbZxsizQthP1xpVx7kd3o31eOogfEirw==",
648
+            "version": "4.31.0",
649
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.31.0.tgz",
650
+            "integrity": "sha512-pCANqpynRS4Jirn4IKZH4tnm2+2CqCNLKD7gAdEjzdLGbH1iO0zouHz4mxqg0uEMpO030ejJ0aA6e1PJo2xrPA==",
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.30.1",
663
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.30.1.tgz",
664
-            "integrity": "sha512-PaMRNBSqCx7K3Wc9QZkFx5+CX27WFpAMxJNiYGAXfmMIKC7jstlr32UhTgK6T07OtqR+wYlWm9IxzennjnvdJg==",
662
+            "version": "4.31.0",
663
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.31.0.tgz",
664
+            "integrity": "sha512-0O8ViX+QcBd3ZmGlcFTnYXZKGbFu09EhgD27tgTdGnkcYXLat4KIsBBQeKLR2xZDCXdIBAlWLkiXE1+rJpCxFw==",
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.30.1",
677
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.30.1.tgz",
678
-            "integrity": "sha512-B8Rcyj9AV7ZlEFqvB5BubG5iO6ANDsRKlhIxySXcF1axXYUyqwBok+XZPgIYGBgs7LDXfWfifxhw0Ik57T0Yug==",
676
+            "version": "4.31.0",
677
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.31.0.tgz",
678
+            "integrity": "sha512-w5IzG0wTVv7B0/SwDnMYmbr2uERQp999q8FMkKG1I+j8hpPX2BYFjWe69xbhbP6J9h2gId/7ogesl9hwblFwwg==",
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.30.1",
691
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.30.1.tgz",
692
-            "integrity": "sha512-hqVyueGxAj3cBKrAI4aFHLV+h0Lv5VgWZs9CUGqr1z0fZtlADVV1YPOij6AhcK5An33EXaxnDLmJdQikcn5NEw==",
690
+            "version": "4.31.0",
691
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.31.0.tgz",
692
+            "integrity": "sha512-JyFFshbN5xwy6fulZ8B/8qOqENRmDdEkcIMF0Zz+RsfamEW+Zabl5jAb0IozP/8UKnJ7g2FtZZPEUIAlUSX8cA==",
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.30.1",
705
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.30.1.tgz",
706
-            "integrity": "sha512-i4Ab2vnvS1AE1PyOIGp2kXni69gU2DAUVt6FSXeIqUCPIR3ZlheMW3oP2JkukDfu3PsexYRbOiJrY+yVNSk9oA==",
704
+            "version": "4.31.0",
705
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.31.0.tgz",
706
+            "integrity": "sha512-kpQXQ0UPFeMPmPYksiBL9WS/BDiQEjRGMfklVIsA0Sng347H8W2iexch+IEwaR7OVSKtr2ZFxggt11zVIlZ25g==",
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.30.1",
719
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.30.1.tgz",
720
-            "integrity": "sha512-fARcF5g296snX0oLGkVxPmysetwUk2zmHcca+e9ObOovBR++9ZPOhqFUM61UUZ2EYpXVPN1redgqVoBB34nTpQ==",
718
+            "version": "4.31.0",
719
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.31.0.tgz",
720
+            "integrity": "sha512-pMlxLjt60iQTzt9iBb3jZphFIl55a70wexvo8p+vVFK+7ifTRookdoXX3bOsRdmfD+OKnMozKO6XM4zR0sHRrQ==",
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.30.1",
733
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.30.1.tgz",
734
-            "integrity": "sha512-GLrZraoO3wVT4uFXh67ElpwQY0DIygxdv0BNW9Hkm3X34wu+BkqrDrkcsIapAY+N2ATEbvak0XQ9gxZtCIA5Rw==",
732
+            "version": "4.31.0",
733
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.31.0.tgz",
734
+            "integrity": "sha512-D7TXT7I/uKEuWiRkEFbed1UUYZwcJDU4vZQdPTcepK7ecPhzKOYk4Er2YR4uHKme4qDeIh6N3XrLfpuM7vzRWQ==",
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.30.1",
747
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.30.1.tgz",
748
-            "integrity": "sha512-0WKLaAUUHKBtll0wvOmh6yh3S0wSU9+yas923JIChfxOaaBarmb/lBKPF0w/+jTVozFnOXJeRGZ8NvOxvk/jcw==",
746
+            "version": "4.31.0",
747
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.31.0.tgz",
748
+            "integrity": "sha512-wal2Tc8O5lMBtoePLBYRKj2CImUCJ4UNGJlLwspx7QApYny7K1cUYlzQ/4IGQBLmm+y0RS7dwc3TDO/pmcneTw==",
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.30.1",
761
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.30.1.tgz",
762
-            "integrity": "sha512-GWFs97Ruxo5Bt+cvVTQkOJ6TIx0xJDD/bMAOXWJg8TCSTEK8RnFeOeiFTxKniTc4vMIaWvCplMAFBt9miGxgkA==",
760
+            "version": "4.31.0",
761
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.31.0.tgz",
762
+            "integrity": "sha512-O1o5EUI0+RRMkK9wiTVpk2tyzXdXefHtRTIjBbmFREmNMy7pFeYXCFGbhKFwISA3UOExlo5GGUuuj3oMKdK6JQ==",
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.30.1",
775
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.30.1.tgz",
776
-            "integrity": "sha512-UtgGb7QGgXDIO+tqqJ5oZRGHsDLO8SlpE4MhqpY9Llpzi5rJMvrK6ZGhsRCST2abZdBqIBeXW6WPD5fGK5SDwg==",
774
+            "version": "4.31.0",
775
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.31.0.tgz",
776
+            "integrity": "sha512-zSoHl356vKnNxwOWnLd60ixHNPRBglxpv2g7q0Cd3Pmr561gf0HiAcUBRL3S1vPqRC17Zo2CX/9cPkqTIiai1g==",
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.30.1",
789
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.30.1.tgz",
790
-            "integrity": "sha512-V9U8Ey2UqmQsBT+xTOeMzPzwDzyXmnAoO4edZhL7INkwQcaW1Ckv3WJX3qrrp/VHaDkEWIBWhRwP47r8cdrOow==",
788
+            "version": "4.31.0",
789
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.31.0.tgz",
790
+            "integrity": "sha512-ypB/HMtcSGhKUQNiFwqgdclWNRrAYDH8iMYH4etw/ZlGwiTVxBz2tDrGRrPlfZu6QjXwtd+C3Zib5pFqID97ZA==",
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.30.1",
803
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.30.1.tgz",
804
-            "integrity": "sha512-WabtHWiPaFF47W3PkHnjbmWawnX/aE57K47ZDT1BXTS5GgrBUEpvOzq0FI0V/UYzQJgdb8XlhVNH8/fwV8xDjw==",
802
+            "version": "4.31.0",
803
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.31.0.tgz",
804
+            "integrity": "sha512-JuhN2xdI/m8Hr+aVO3vspO7OQfUFO6bKLIRTAy0U15vmWjnZDLrEgCZ2s6+scAYaQVpYSh9tZtRijApw9IXyMw==",
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.30.1",
817
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.30.1.tgz",
818
-            "integrity": "sha512-pxHAU+Zv39hLUTdQQHUVHf4P+0C47y/ZloorHpzs2SXMRqeAWmGghzAhfOlzFHHwjvgokdFAhC4V+6kC1lRRfw==",
816
+            "version": "4.31.0",
817
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.31.0.tgz",
818
+            "integrity": "sha512-U1xZZXYkvdf5MIWmftU8wrM5PPXzyaY1nGCI4KI4BFfoZxHamsIe+BtnPLIvvPykvQWlVbqUXdLa4aJUuilwLQ==",
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.30.1",
831
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.30.1.tgz",
832
-            "integrity": "sha512-D6qjsXGcvhTjv0kI4fU8tUuBDF/Ueee4SVX79VfNDXZa64TfCW1Slkb6Z7O1p7vflqZjcmOVdZlqf8gvJxc6og==",
830
+            "version": "4.31.0",
831
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.31.0.tgz",
832
+            "integrity": "sha512-ul8rnCsUumNln5YWwz0ted2ZHFhzhRRnkpBZ+YRuHoRAlUji9KChpOUOndY7uykrPEPXVbHLlsdo6v5yXo/TXw==",
833 833
             "cpu": [
834 834
                 "x64"
835 835
             ],
@@ -2242,9 +2242,9 @@
2242 2242
             }
2243 2243
         },
2244 2244
         "node_modules/rollup": {
2245
-            "version": "4.30.1",
2246
-            "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.30.1.tgz",
2247
-            "integrity": "sha512-mlJ4glW020fPuLi7DkM/lN97mYEZGWeqBnrljzN0gs7GLctqX3lNWxKQ7Gl712UAX+6fog/L3jh4gb7R6aVi3w==",
2245
+            "version": "4.31.0",
2246
+            "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.31.0.tgz",
2247
+            "integrity": "sha512-9cCE8P4rZLx9+PjoyqHLs31V9a9Vpvfo4qNcs6JCiGWYhw2gijSetFbH6SSy1whnkgcefnUwr8sad7tgqsGvnw==",
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.30.1",
2262
-                "@rollup/rollup-android-arm64": "4.30.1",
2263
-                "@rollup/rollup-darwin-arm64": "4.30.1",
2264
-                "@rollup/rollup-darwin-x64": "4.30.1",
2265
-                "@rollup/rollup-freebsd-arm64": "4.30.1",
2266
-                "@rollup/rollup-freebsd-x64": "4.30.1",
2267
-                "@rollup/rollup-linux-arm-gnueabihf": "4.30.1",
2268
-                "@rollup/rollup-linux-arm-musleabihf": "4.30.1",
2269
-                "@rollup/rollup-linux-arm64-gnu": "4.30.1",
2270
-                "@rollup/rollup-linux-arm64-musl": "4.30.1",
2271
-                "@rollup/rollup-linux-loongarch64-gnu": "4.30.1",
2272
-                "@rollup/rollup-linux-powerpc64le-gnu": "4.30.1",
2273
-                "@rollup/rollup-linux-riscv64-gnu": "4.30.1",
2274
-                "@rollup/rollup-linux-s390x-gnu": "4.30.1",
2275
-                "@rollup/rollup-linux-x64-gnu": "4.30.1",
2276
-                "@rollup/rollup-linux-x64-musl": "4.30.1",
2277
-                "@rollup/rollup-win32-arm64-msvc": "4.30.1",
2278
-                "@rollup/rollup-win32-ia32-msvc": "4.30.1",
2279
-                "@rollup/rollup-win32-x64-msvc": "4.30.1",
2261
+                "@rollup/rollup-android-arm-eabi": "4.31.0",
2262
+                "@rollup/rollup-android-arm64": "4.31.0",
2263
+                "@rollup/rollup-darwin-arm64": "4.31.0",
2264
+                "@rollup/rollup-darwin-x64": "4.31.0",
2265
+                "@rollup/rollup-freebsd-arm64": "4.31.0",
2266
+                "@rollup/rollup-freebsd-x64": "4.31.0",
2267
+                "@rollup/rollup-linux-arm-gnueabihf": "4.31.0",
2268
+                "@rollup/rollup-linux-arm-musleabihf": "4.31.0",
2269
+                "@rollup/rollup-linux-arm64-gnu": "4.31.0",
2270
+                "@rollup/rollup-linux-arm64-musl": "4.31.0",
2271
+                "@rollup/rollup-linux-loongarch64-gnu": "4.31.0",
2272
+                "@rollup/rollup-linux-powerpc64le-gnu": "4.31.0",
2273
+                "@rollup/rollup-linux-riscv64-gnu": "4.31.0",
2274
+                "@rollup/rollup-linux-s390x-gnu": "4.31.0",
2275
+                "@rollup/rollup-linux-x64-gnu": "4.31.0",
2276
+                "@rollup/rollup-linux-x64-musl": "4.31.0",
2277
+                "@rollup/rollup-win32-arm64-msvc": "4.31.0",
2278
+                "@rollup/rollup-win32-ia32-msvc": "4.31.0",
2279
+                "@rollup/rollup-win32-x64-msvc": "4.31.0",
2280 2280
                 "fsevents": "~2.3.2"
2281 2281
             }
2282 2282
         },

+ 4
- 17
resources/views/filament/infolists/components/document-preview.blade.php Zobrazit soubor

@@ -26,10 +26,8 @@
26 26
         <x-company.invoice.metadata class="modern-template-metadata space-y-8">
27 27
             <div class="text-sm">
28 28
                 <h2 class="text-lg font-semibold">{{ $document->company->name }}</h2>
29
-                @if($document->company->address && $document->company->city && $document->company->state && $document->company->zipCode)
30
-                    <p>{{ $document->company->address }}</p>
31
-                    <p>{{ $document->company->city }}, {{ $document->company->state }} {{ $document->company->zipCode }}</p>
32
-                    <p>{{ $document->company->country }}</p>
29
+                @if($formattedAddress = $document->company->getFormattedAddressHtml())
30
+                    {!! $formattedAddress !!}
33 31
                 @endif
34 32
             </div>
35 33
 
@@ -40,19 +38,8 @@
40 38
                     <p class="text-base font-bold"
41 39
                        style="color: {{ $document->accentColor }}">{{ $document->client->name }}</p>
42 40
 
43
-                    @if($document->client->addressLine1)
44
-                        <p>{{ $document->client->addressLine1 }}</p>
45
-
46
-                        @if($document->client->addressLine2)
47
-                            <p>{{ $document->client->addressLine2 }}</p>
48
-                        @endif
49
-                        <p>
50
-                            {{ $document->client->city }}{{ $document->client->state ? ', ' . $document->client->state: '' }}
51
-                            {{ $document->client->postalCode }}
52
-                        </p>
53
-                        @if($document->client->country)
54
-                            <p>{{ $document->client->country }}</p>
55
-                        @endif
41
+                    @if($formattedAddress = $document->client->getFormattedAddressHtml())
42
+                        {!! $formattedAddress !!}
56 43
                     @endif
57 44
                 </div>
58 45
 

+ 0
- 0
resources/views/vendor/filament-simple-alert/.gitkeep Zobrazit soubor


+ 0
- 15
resources/views/vendor/filament-simple-alert/components/simple-alert-entry.blade.php Zobrazit soubor

@@ -1,15 +0,0 @@
1
-<x-dynamic-component :component="$getEntryWrapperView()" :entry="$entry">
2
-    <x-filament-simple-alert::simple-alert
3
-            :icon="$getIcon()"
4
-            :icon-vertical-alignment="$getIconVerticalAlignment()"
5
-            :color="$getColor()"
6
-            :title="$getTitle()"
7
-            :description="$getDescription()"
8
-            :link="$getLink()"
9
-            :link-label="$getLinkLabel()"
10
-            :link-blank="$getLinkBlank()"
11
-            :actions-vertical-alignment="$getActionsVerticalAlignment()"
12
-            :actions="$getActions()"
13
-            :border="$getBorder()"
14
-    />
15
-</x-dynamic-component>

+ 0
- 15
resources/views/vendor/filament-simple-alert/components/simple-alert-field.blade.php Zobrazit soubor

@@ -1,15 +0,0 @@
1
-<x-dynamic-component :component="$getFieldWrapperView()" :field="$field">
2
-    <x-filament-simple-alert::simple-alert
3
-            :icon="$getIcon()"
4
-            :icon-vertical-alignment="$getIconVerticalAlignment()"
5
-            :color="$getColor()"
6
-            :title="$getTitle()"
7
-            :description="$getDescription()"
8
-            :link="$getLink()"
9
-            :link-label="$getLinkLabel()"
10
-            :link-blank="$getLinkBlank()"
11
-            :actions-vertical-alignment="$getActionsVerticalAlignment()"
12
-            :actions="$getActions()"
13
-            :border="$getBorder()"
14
-    />
15
-</x-dynamic-component>

+ 0
- 84
resources/views/vendor/filament-simple-alert/components/simple-alert.blade.php Zobrazit soubor

@@ -1,84 +0,0 @@
1
-@props([
2
-    'actions' => null,
3
-    'actionsVerticalAlignment' => 'center',
4
-    'border' => false,
5
-    'color' => null,
6
-    'description' => null,
7
-    'icon' => null,
8
-    'iconVerticalAlignment' => 'center',
9
-    'link' => null,
10
-    'linkBlank' => false,
11
-    'linkLabel' => null,
12
-    'title' => null,
13
-])
14
-
15
-@php
16
-    use function Filament\Support\get_color_css_variables;
17
-
18
-    $colors = \Illuminate\Support\Arr::toCssStyles([
19
-           get_color_css_variables($color, shades: [50, 100, 400, 500, 700, 800]),
20
-   ]);
21
-@endphp
22
-
23
-<div x-data="{}"
24
-     @class([
25
-       'filament-simple-alert rounded-md bg-custom-50 p-4 dark:bg-custom-400/10',
26
-       'ring-1 ring-custom-100 dark:ring-custom-500/70' => $border,
27
-     ])
28
-     style="{{ $colors }}">
29
-    <div class="flex gap-3">
30
-        @if($icon)
31
-            <div @class([
32
-                'flex-shrink-0',
33
-                $iconVerticalAlignment === 'start' ? 'self-start' : 'self-center',
34
-            ])>
35
-                <x-filament::icon
36
-                        :icon="$icon"
37
-                        class="h-5 w-5 text-custom-400"
38
-                />
39
-            </div>
40
-        @endif
41
-        <div class="items-center flex-1 md:flex md:justify-between space-y-3 md:space-y-0 md:gap-3">
42
-            @if($title || $description)
43
-                <div class="space-y-0.5">
44
-                    @if($title)
45
-                        <p class="text-sm font-medium text-custom-800 dark:text-white">
46
-                            {{ $title }}
47
-                        </p>
48
-                    @endif
49
-                    @if($description)
50
-                        <p class="text-sm text-custom-700 dark:text-white">
51
-                            {{ $description }}
52
-                        </p>
53
-                    @endif
54
-                </div>
55
-            @endif
56
-            @if($link || $actions)
57
-                <div @class([
58
-                  'flex items-center gap-3',
59
-                    $actionsVerticalAlignment === 'start' ? 'self-start' : 'self-center',
60
-                ])>
61
-                    <div class="flex items-center whitespace-nowrap gap-3">
62
-                        @if($link)
63
-                            <p class="text-sm md:mt-0 self-center">
64
-                                <a href="{{ $link }}" {{ $linkBlank ? 'target="_blank"' : '' }} class="whitespace-nowrap font-medium text-custom-400 hover:text-custom-500">
65
-                                    {{ $linkLabel }}
66
-                                    <span aria-hidden="true"> &rarr;</span>
67
-                                </a>
68
-                            </p>
69
-                        @endif
70
-                        @if($actions)
71
-                            <div class="gap-3 flex items-center justify-start">
72
-                                @foreach ($actions as $action)
73
-                                    @if ($action->isVisible())
74
-                                        {{ $action }}
75
-                                    @endif
76
-                                @endforeach
77
-                            </div>
78
-                        @endif
79
-                    </div>
80
-                </div>
81
-            @endif
82
-        </div>
83
-    </div>
84
-</div>

+ 187
- 13
tests/Feature/Accounting/RecurringInvoiceTest.php Zobrazit soubor

@@ -1,29 +1,203 @@
1 1
 <?php
2 2
 
3
+use App\Enums\Accounting\EndType;
4
+use App\Enums\Accounting\Frequency;
3 5
 use App\Enums\Accounting\IntervalType;
4 6
 use App\Models\Accounting\RecurringInvoice;
7
+use Illuminate\Support\Carbon;
5 8
 
6
-test('example', function () {
9
+beforeEach(function () {
10
+    $this->withOfferings();
11
+});
12
+
13
+test('recurring invoice properly handles months with fewer days for monthly frequency', function () {
14
+    // Start from January 31st
15
+    Carbon::setTestNow('2024-01-31');
16
+
17
+    RecurringInvoice::unsetEventDispatcher();
18
+
19
+    // Create a recurring invoice set for the 31st of each month
7 20
     $recurringInvoice = RecurringInvoice::factory()
8
-        ->custom(IntervalType::Week, 2)
9
-        ->create([
10
-            'start_date' => today(),
11
-            'day_of_week' => today()->dayOfWeek,
12
-        ]);
21
+        ->withLineItems()
22
+        ->withSchedule(
23
+            frequency: Frequency::Monthly,
24
+            startDate: Carbon::now(),
25
+        )
26
+        ->approved()
27
+        ->create();
28
+
29
+    // First invoice should be the start date
30
+    expect($recurringInvoice->calculateNextDate())
31
+        ->toBeInstanceOf(Carbon::class)
32
+        ->toDateString()->toBe('2024-01-31');
33
+
34
+    // Now set last_date to simulate first invoice being generated
35
+    $recurringInvoice->update(['last_date' => '2024-01-31']);
36
+    $recurringInvoice->refresh();
37
+    expect($recurringInvoice->calculateNextDate())
38
+        ->toBeInstanceOf(Carbon::class)
39
+        ->toDateString()->toBe('2024-02-29');
40
+
41
+    // Update last_date to Feb 29 and check next date (should be March 31)
42
+    $recurringInvoice->update(['last_date' => '2024-02-29']);
43
+    $recurringInvoice->refresh();
44
+    expect($recurringInvoice->calculateNextDate())
45
+        ->toBeInstanceOf(Carbon::class)
46
+        ->toDateString()->toBe('2024-03-31');
47
+
48
+    // Update last_date to March 31 and check next date (should be April 30)
49
+    $recurringInvoice->update(['last_date' => '2024-03-31']);
50
+    $recurringInvoice->refresh();
51
+    expect($recurringInvoice->calculateNextDate())
52
+        ->toBeInstanceOf(Carbon::class)
53
+        ->toDateString()->toBe('2024-04-30');
54
+
55
+    // Update last_date to April 30 and check next date (should be May 31)
56
+    $recurringInvoice->update(['last_date' => '2024-04-30']);
57
+    $recurringInvoice->refresh();
58
+    expect($recurringInvoice->calculateNextDate())
59
+        ->toBeInstanceOf(Carbon::class)
60
+        ->toDateString()->toBe('2024-05-31');
61
+});
62
+
63
+test('recurring invoice properly handles months with fewer days for yearly frequency', function () {
64
+    // Start from January 31st
65
+    Carbon::setTestNow('2024-02-29');
66
+
67
+    RecurringInvoice::unsetEventDispatcher();
68
+
69
+    // Create a recurring invoice set for the 31st of each month
70
+    $recurringInvoice = RecurringInvoice::factory()
71
+        ->withLineItems()
72
+        ->withSchedule(
73
+            frequency: Frequency::Yearly,
74
+            startDate: Carbon::now(),
75
+        )
76
+        ->approved()
77
+        ->create();
78
+
79
+    // First invoice should be the start date
80
+    expect($recurringInvoice->calculateNextDate())
81
+        ->toBeInstanceOf(Carbon::class)
82
+        ->toDateString()->toBe('2024-02-29');
83
+
84
+    // Next date should be Feb 28, 2025 (non-leap year)
85
+    $recurringInvoice->update(['last_date' => '2024-02-29']);
86
+    $recurringInvoice->refresh();
87
+    expect($recurringInvoice->calculateNextDate())
88
+        ->toBeInstanceOf(Carbon::class)
89
+        ->toDateString()->toBe('2025-02-28');
13 90
 
91
+    // Next date should be Feb 29, 2026 (leap year)
92
+    $recurringInvoice->update(['last_date' => '2025-02-28']);
14 93
     $recurringInvoice->refresh();
94
+    expect($recurringInvoice->calculateNextDate())
95
+        ->toBeInstanceOf(Carbon::class)
96
+        ->toDateString()->toBe('2026-02-28');
97
+});
98
+
99
+test('recurring invoice properly handles weekly frequency and custom weekly intervals', function () {
100
+    Carbon::setTestNow('2024-01-31'); // Wednesday
15 101
 
16
-    $nextInvoiceDate = $recurringInvoice->calculateNextDate();
102
+    RecurringInvoice::unsetEventDispatcher();
17 103
 
18
-    expect($nextInvoiceDate)->toEqual(today());
104
+    // Test regular weekly frequency
105
+    $recurringInvoice = RecurringInvoice::factory()
106
+        ->withLineItems()
107
+        ->withSchedule(
108
+            frequency: Frequency::Weekly,
109
+            startDate: Carbon::now(),
110
+        )
111
+        ->approved()
112
+        ->create();
19 113
 
20
-    $recurringInvoice->update([
21
-        'last_date' => $nextInvoiceDate,
22
-    ]);
114
+    // First invoice should be the start date
115
+    expect($recurringInvoice->calculateNextDate())
116
+        ->toBeInstanceOf(Carbon::class)
117
+        ->toDateString()->toBe('2024-01-31');
23 118
 
119
+    // Next date should be that Friday
120
+    $recurringInvoice->update(['last_date' => '2024-01-31']);
24 121
     $recurringInvoice->refresh();
122
+    expect($recurringInvoice->calculateNextDate())
123
+        ->toBeInstanceOf(Carbon::class)
124
+        ->toDateString()->toBe('2024-02-07');
125
+
126
+    // Test custom weekly frequency (every 2 weeks)
127
+    $recurringInvoice = RecurringInvoice::factory()
128
+        ->withLineItems()
129
+        ->withCustomSchedule(
130
+            startDate: Carbon::now(), // Wednesday
131
+            endType: EndType::Never,
132
+            intervalType: IntervalType::Week,
133
+            intervalValue: 2,
134
+        )
135
+        ->approved()
136
+        ->create();
137
+
138
+    // First invoice should be the start date
139
+    expect($recurringInvoice->calculateNextDate())
140
+        ->toBeInstanceOf(Carbon::class)
141
+        ->toDateString()->toBe('2024-01-31');
142
+
143
+    // Next date should be two weeks from start, on Friday
144
+    $recurringInvoice->update(['last_date' => '2024-01-31']);
145
+    $recurringInvoice->refresh();
146
+    expect($recurringInvoice->calculateNextDate())
147
+        ->toBeInstanceOf(Carbon::class)
148
+        ->toDateString()->toBe('2024-02-14');
149
+});
150
+
151
+test('recurring invoice generates correct sequence of invoices across different month lengths', function () {
152
+    Carbon::setTestNow('2024-01-31');
153
+
154
+    $recurringInvoice = RecurringInvoice::factory()
155
+        ->withLineItems()
156
+        ->withSchedule(
157
+            frequency: Frequency::Monthly,
158
+            startDate: Carbon::now(),
159
+        )
160
+        ->approved()
161
+        ->create();
162
+
163
+    // Generate first invoice
164
+    $recurringInvoice->generateDueInvoices();
165
+
166
+    $invoices = $recurringInvoice->invoices()
167
+        ->orderBy('date')
168
+        ->get();
169
+
170
+    expect($invoices)->toHaveCount(1)
171
+        ->and($invoices->pluck('date')->map->toDateString()->toArray())->toBe([
172
+            '2024-01-31',
173
+        ]);
174
+
175
+    // Move time forward to February (leap year)
176
+    Carbon::setTestNow('2024-02-29');
177
+    $recurringInvoice->generateDueInvoices();
25 178
 
26
-    $nextInvoiceDate = $recurringInvoice->calculateNextDate();
179
+    $invoices = $recurringInvoice->invoices()
180
+        ->orderBy('date')
181
+        ->get();
27 182
 
28
-    expect($nextInvoiceDate)->toEqual(today()->addWeeks(2));
183
+    expect($invoices)->toHaveCount(2)
184
+        ->and($invoices->pluck('date')->map->toDateString()->toArray())->toBe([
185
+            '2024-01-31',
186
+            '2024-02-29',
187
+        ]);
188
+
189
+    // Move time forward to March
190
+    Carbon::setTestNow('2024-03-31');
191
+    $recurringInvoice->generateDueInvoices();
192
+
193
+    $invoices = $recurringInvoice->invoices()
194
+        ->orderBy('date')
195
+        ->get();
196
+
197
+    expect($invoices)->toHaveCount(3)
198
+        ->and($invoices->pluck('date')->map->toDateString()->toArray())->toBe([
199
+            '2024-01-31',
200
+            '2024-02-29',
201
+            '2024-03-31',
202
+        ]);
29 203
 });

+ 14
- 0
tests/TestCase.php Zobrazit soubor

@@ -2,6 +2,7 @@
2 2
 
3 3
 namespace Tests;
4 4
 
5
+use App\Models\Common\Offering;
5 6
 use App\Models\Company;
6 7
 use App\Models\User;
7 8
 use App\Testing\TestsReport;
@@ -45,4 +46,17 @@ abstract class TestCase extends BaseTestCase
45 46
 
46 47
         Filament::setTenant($this->testCompany);
47 48
     }
49
+
50
+    public function withOfferings(): static
51
+    {
52
+        Offering::factory()
53
+            ->for($this->testCompany)
54
+            ->sellable()
55
+            ->withSalesAdjustments()
56
+            ->purchasable()
57
+            ->withPurchaseAdjustments()
58
+            ->create();
59
+
60
+        return $this;
61
+    }
48 62
 }

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