瀏覽代碼

wip: Added many new functionalities

3.x
wallo 2 年之前
父節點
當前提交
130432a531
共有 51 個檔案被更改,包括 1974 行新增696 行删除
  1. 80
    0
      app/Abstracts/Forms/EditFormRecord.php
  2. 51
    0
      app/Filament/Pages/CompanyDetails.php
  3. 33
    2
      app/Filament/Pages/DefaultSetting.php
  4. 33
    2
      app/Filament/Pages/Invoice.php
  5. 20
    6
      app/Filament/Pages/Widgets/Companies/Charts/CompanyStatsOverview.php
  6. 2
    2
      app/Filament/Pages/Widgets/Companies/Charts/CumulativeGrowth.php
  7. 1
    1
      app/Filament/Pages/Widgets/Employees/Charts/CumulativeGrowth.php
  8. 67
    1
      app/Filament/Resources/AccountResource.php
  9. 17
    2
      app/Filament/Resources/AccountResource/Pages/EditAccount.php
  10. 15
    2
      app/Filament/Resources/CategoryResource.php
  11. 4
    0
      app/Filament/Resources/CategoryResource/Pages/ListCategories.php
  12. 15
    2
      app/Filament/Resources/CurrencyResource.php
  13. 16
    2
      app/Filament/Resources/CustomerResource.php
  14. 15
    2
      app/Filament/Resources/DiscountResource.php
  15. 16
    2
      app/Filament/Resources/InvoiceResource.php
  16. 15
    2
      app/Filament/Resources/TaxResource.php
  17. 26
    0
      app/Forms/Components/Invoice.php
  18. 89
    0
      app/Http/Livewire/CompanyDetails.php
  19. 10
    13
      app/Http/Livewire/DefaultSetting.php
  20. 173
    88
      app/Http/Livewire/Invoice.php
  21. 0
    6
      app/Models/Banking/Account.php
  22. 10
    0
      app/Models/Company.php
  23. 0
    5
      app/Models/Contact.php
  24. 0
    6
      app/Models/Setting/Category.php
  25. 15
    5
      app/Models/Setting/Currency.php
  26. 0
    5
      app/Models/Setting/DefaultSetting.php
  27. 0
    5
      app/Models/Setting/Discount.php
  28. 72
    29
      app/Models/Setting/DocumentDefault.php
  29. 0
    5
      app/Models/Setting/Tax.php
  30. 2
    1
      app/Providers/SquireServiceProvider.php
  31. 14
    9
      app/Traits/CompanyOwned.php
  32. 9
    11
      app/Traits/HandlesDefaultSettingRecordUpdate.php
  33. 173
    0
      app/View/Models/InvoiceViewModel.php
  34. 1
    0
      composer.json
  35. 408
    280
      composer.lock
  36. 1
    1
      config/filament.php
  37. 1
    1
      config/services.php
  38. 9
    4
      database/factories/DocumentDefaultFactory.php
  39. 10
    5
      database/migrations/2023_05_22_000100_create_document_defaults_table.php
  40. 44
    0
      database/migrations/2023_08_07_144726_add_company_details_to_companies_table.php
  41. 161
    161
      package-lock.json
  42. 21
    0
      resources/css/filament.css
  43. 129
    0
      resources/views/components/invoice-layouts/default.blade.php
  44. 164
    0
      resources/views/components/invoice-layouts/modern.blade.php
  45. 3
    0
      resources/views/filament/pages/company-details.blade.php
  46. 1
    1
      resources/views/filament/pages/default-setting.blade.php
  47. 1
    1
      resources/views/filament/pages/invoice.blade.php
  48. 9
    0
      resources/views/livewire/company-details.blade.php
  49. 8
    12
      resources/views/livewire/default-setting.blade.php
  50. 8
    12
      resources/views/livewire/invoice.blade.php
  51. 2
    2
      tailwind.config.js

+ 80
- 0
app/Abstracts/Forms/EditFormRecord.php 查看文件

1
+<?php
2
+
3
+namespace App\Abstracts\Forms;
4
+
5
+use Filament\Forms\ComponentContainer;
6
+use Filament\Forms\Concerns\InteractsWithForms;
7
+use Filament\Forms\Contracts\HasForms;
8
+use Filament\Notifications\Notification;
9
+use Illuminate\Database\Eloquent\Model;
10
+use Livewire\Component;
11
+
12
+/**
13
+ * @property ComponentContainer $form
14
+ */
15
+abstract class EditFormRecord extends Component implements HasForms
16
+{
17
+    use InteractsWithForms;
18
+
19
+    public ?array $data = [];
20
+
21
+    abstract protected function getFormModel(): Model|string|null;
22
+
23
+    public function mount(): void
24
+    {
25
+        $this->fillForm();
26
+    }
27
+
28
+    public function fillForm(): void
29
+    {
30
+        $data = $this->getFormModel()->attributesToArray();
31
+
32
+        $data = $this->mutateFormDataBeforeFill($data);
33
+
34
+        $this->form->fill($data);
35
+    }
36
+
37
+    protected function mutateFormDataBeforeFill(array $data): array
38
+    {
39
+        return $data;
40
+    }
41
+
42
+    public function save(): void
43
+    {
44
+        $data = $this->form->getState();
45
+
46
+        $data = $this->mutateFormDataBeforeSave($data);
47
+
48
+        $this->handleRecordUpdate($this->getFormModel(), $data);
49
+
50
+        $this->getSavedNotification()?->send();
51
+    }
52
+
53
+    protected function mutateFormDataBeforeSave(array $data): array
54
+    {
55
+        return $data;
56
+    }
57
+
58
+    protected function handleRecordUpdate(Model $record, array $data): Model
59
+    {
60
+        $record->update($data);
61
+
62
+        return $record;
63
+    }
64
+
65
+    protected function getSavedNotification(): ?Notification
66
+    {
67
+        $title = $this->getSavedNotificationTitle();
68
+        if (blank($title)) {
69
+            return null;
70
+        }
71
+        return Notification::make()
72
+            ->success()
73
+            ->title($title);
74
+    }
75
+
76
+    protected function getSavedNotificationTitle(): ?string
77
+    {
78
+        return __('filament::resources/pages/edit-record.messages.saved');
79
+    }
80
+}

+ 51
- 0
app/Filament/Pages/CompanyDetails.php 查看文件

1
+<?php
2
+
3
+namespace App\Filament\Pages;
4
+
5
+use Filament\Pages\Page;
6
+use Illuminate\Support\Facades\Auth;
7
+use Illuminate\Support\Facades\Gate;
8
+use Wallo\FilamentCompanies\FilamentCompanies;
9
+
10
+class CompanyDetails extends Page
11
+{
12
+    public mixed $company;
13
+
14
+    protected static ?string $navigationIcon = 'heroicon-o-document-text';
15
+
16
+    protected static ?string $navigationLabel = 'Company';
17
+
18
+    protected static ?string $navigationGroup = 'Settings';
19
+
20
+    protected static ?string $title = 'Company';
21
+
22
+    protected static string $view = 'filament.pages.company-details';
23
+
24
+    public function mount($company): void
25
+    {
26
+        $this->company = FilamentCompanies::newCompanyModel()->findOrFail($company);
27
+        $this->authorizeAccess();
28
+    }
29
+
30
+    protected function authorizeAccess(): void
31
+    {
32
+        Gate::authorize('view', $this->company);
33
+    }
34
+
35
+    public static function getSlug(): string
36
+    {
37
+        return '{company}/settings/company';
38
+    }
39
+
40
+    public static function getUrl(array $parameters = [], bool $isAbsolute = true): string
41
+    {
42
+        return route(static::getRouteName(), ['company' => Auth::user()->currentCompany], $isAbsolute);
43
+    }
44
+
45
+    protected function getBreadcrumbs(): array
46
+    {
47
+        return [
48
+            'company' => 'Company',
49
+        ];
50
+    }
51
+}

+ 33
- 2
app/Filament/Pages/DefaultSetting.php 查看文件

3
 namespace App\Filament\Pages;
3
 namespace App\Filament\Pages;
4
 
4
 
5
 use Filament\Pages\Page;
5
 use Filament\Pages\Page;
6
+use Illuminate\Support\Facades\Auth;
7
+use Illuminate\Support\Facades\Gate;
8
+use Wallo\FilamentCompanies\FilamentCompanies;
6
 
9
 
7
 class DefaultSetting extends Page
10
 class DefaultSetting extends Page
8
 {
11
 {
12
+    public mixed $company;
13
+
9
     protected static ?string $navigationIcon = 'heroicon-o-adjustments';
14
     protected static ?string $navigationIcon = 'heroicon-o-adjustments';
10
 
15
 
11
     protected static ?string $navigationLabel = 'Default';
16
     protected static ?string $navigationLabel = 'Default';
14
 
19
 
15
     protected static ?string $title = 'Default';
20
     protected static ?string $title = 'Default';
16
 
21
 
17
-    protected static ?string $slug = 'default';
18
-
19
     protected static string $view = 'filament.pages.default-setting';
22
     protected static string $view = 'filament.pages.default-setting';
23
+
24
+    public function mount($company): void
25
+    {
26
+        $this->company = FilamentCompanies::newCompanyModel()->findOrFail($company);
27
+        $this->authorizeAccess();
28
+    }
29
+
30
+    protected function authorizeAccess(): void
31
+    {
32
+        Gate::authorize('view', $this->company);
33
+    }
34
+
35
+    public static function getSlug(): string
36
+    {
37
+        return '{company}/settings/default';
38
+    }
39
+
40
+    public static function getUrl(array $parameters = [], bool $isAbsolute = true): string
41
+    {
42
+        return route(static::getRouteName(), ['company' => Auth::user()->currentCompany], $isAbsolute);
43
+    }
44
+
45
+    protected function getBreadcrumbs(): array
46
+    {
47
+        return [
48
+            'default' => 'Default',
49
+        ];
50
+    }
20
 }
51
 }

+ 33
- 2
app/Filament/Pages/Invoice.php 查看文件

3
 namespace App\Filament\Pages;
3
 namespace App\Filament\Pages;
4
 
4
 
5
 use Filament\Pages\Page;
5
 use Filament\Pages\Page;
6
+use Illuminate\Support\Facades\Auth;
7
+use Illuminate\Support\Facades\Gate;
8
+use Wallo\FilamentCompanies\FilamentCompanies;
6
 
9
 
7
 class Invoice extends Page
10
 class Invoice extends Page
8
 {
11
 {
12
+    public mixed $company;
13
+
9
     protected static ?string $navigationIcon = 'heroicon-o-document-text';
14
     protected static ?string $navigationIcon = 'heroicon-o-document-text';
10
 
15
 
11
     protected static ?string $navigationLabel = 'Invoice';
16
     protected static ?string $navigationLabel = 'Invoice';
14
 
19
 
15
     protected static ?string $title = 'Invoice';
20
     protected static ?string $title = 'Invoice';
16
 
21
 
17
-    protected static ?string $slug = 'invoice';
18
-
19
     protected static string $view = 'filament.pages.invoice';
22
     protected static string $view = 'filament.pages.invoice';
23
+
24
+    public function mount($company): void
25
+    {
26
+        $this->company = FilamentCompanies::newCompanyModel()->findOrFail($company);
27
+        $this->authorizeAccess();
28
+    }
29
+
30
+    protected function authorizeAccess(): void
31
+    {
32
+        Gate::authorize('view', $this->company);
33
+    }
34
+
35
+    public static function getSlug(): string
36
+    {
37
+        return '{company}/settings/invoice';
38
+    }
39
+
40
+    public static function getUrl(array $parameters = [], bool $isAbsolute = true): string
41
+    {
42
+        return route(static::getRouteName(), ['company' => Auth::user()->currentCompany], $isAbsolute);
43
+    }
44
+
45
+    protected function getBreadcrumbs(): array
46
+    {
47
+        return [
48
+            'invoice' => 'Invoice',
49
+        ];
50
+    }
20
 }
51
 }

+ 20
- 6
app/Filament/Pages/Widgets/Companies/Charts/CompanyStatsOverview.php 查看文件

3
 namespace App\Filament\Pages\Widgets\Companies\Charts;
3
 namespace App\Filament\Pages\Widgets\Companies\Charts;
4
 
4
 
5
 use App\Models\Company;
5
 use App\Models\Company;
6
+use Exception;
6
 use Filament\Widgets\StatsOverviewWidget;
7
 use Filament\Widgets\StatsOverviewWidget;
8
+use InvalidArgumentException;
7
 
9
 
8
 class CompanyStatsOverview extends StatsOverviewWidget
10
 class CompanyStatsOverview extends StatsOverviewWidget
9
 {
11
 {
11
 
13
 
12
     /**
14
     /**
13
      * Holt's Linear Trend Method
15
      * Holt's Linear Trend Method
16
+     * @throws Exception
14
      */
17
      */
15
     protected function holtLinearTrend($data, $alpha, $beta): array
18
     protected function holtLinearTrend($data, $alpha, $beta): array
16
     {
19
     {
20
+        if (count($data) < 2 || array_filter($data, 'is_numeric') !== $data) {
21
+            throw new InvalidArgumentException('Insufficient or invalid data for Holt\'s Linear Trend calculation', 400);
22
+        }
23
+
17
         $level = $data[0];
24
         $level = $data[0];
18
         $trend = $data[1] - $data[0];
25
         $trend = $data[1] - $data[0];
19
 
26
 
30
 
37
 
31
     /**
38
     /**
32
      * Adjusts the alpha and beta parameters based on the model's performance
39
      * Adjusts the alpha and beta parameters based on the model's performance
40
+     * @throws Exception
33
      */
41
      */
34
     protected function adjustTrendParameters($data, $alpha, $beta): array
42
     protected function adjustTrendParameters($data, $alpha, $beta): array
35
     {
43
     {
68
 
76
 
69
     /**
77
     /**
70
      * Chart Options
78
      * Chart Options
79
+     * @throws Exception
71
      */
80
      */
72
     protected function getCards(): array
81
     protected function getCards(): array
73
     {
82
     {
106
         // Calculate new companies and percentage change per week
115
         // Calculate new companies and percentage change per week
107
         $newCompanies = [0];
116
         $newCompanies = [0];
108
         $weeklyPercentageChange = [0];
117
         $weeklyPercentageChange = [0];
118
+
109
         for ($i = 1, $iMax = count($totalCompanies); $i < $iMax; $i++) {
119
         for ($i = 1, $iMax = count($totalCompanies); $i < $iMax; $i++) {
110
             $newCompanies[] = $totalCompanies[$i] - $totalCompanies[$i - 1];
120
             $newCompanies[] = $totalCompanies[$i] - $totalCompanies[$i - 1];
111
-            $weeklyPercentageChange[] = ($newCompanies[$i] / $totalCompanies[$i - 1]) * 100;
121
+            $weeklyPercentageChange[] = $totalCompanies[$i - 1] !== 0 ? ($newCompanies[$i] / $totalCompanies[$i - 1]) * 100 : 0;
112
         }
122
         }
113
 
123
 
114
-        // Calculate average weekly growth rate
115
-        $totalWeeks = $startOfYear->diffInWeeks($today);
116
-        $averageWeeklyGrowthRate = round(array_sum($weeklyPercentageChange) / $totalWeeks, 2);
117
-
124
+        // Ensure $weeklyDataArray contains at least two values and all values are numeric
118
         $weeklyDataArray = $weeklyData->values()->toArray();
125
         $weeklyDataArray = $weeklyData->values()->toArray();
126
+        if (count($weeklyDataArray) < 2 || array_filter($weeklyDataArray, 'is_numeric') !== $weeklyDataArray) {
127
+            throw new InvalidArgumentException('Insufficient or invalid data for Holt\'s Linear Trend calculation', 400);
128
+        }
119
 
129
 
120
         // Adjust alpha and beta parameters
130
         // Adjust alpha and beta parameters
121
         [$alpha, $beta] = $this->adjustTrendParameters($weeklyDataArray, $alpha, $beta);
131
         [$alpha, $beta] = $this->adjustTrendParameters($weeklyDataArray, $alpha, $beta);
124
         $holtForecast = $this->holtLinearTrend($weeklyDataArray, $alpha, $beta);
134
         $holtForecast = $this->holtLinearTrend($weeklyDataArray, $alpha, $beta);
125
         $expectedNewCompanies = round(end($holtForecast));
135
         $expectedNewCompanies = round(end($holtForecast));
126
 
136
 
137
+        // Calculate average weekly growth rate
138
+        $totalWeeks = $startOfYear->diffInWeeks($today);
139
+        $averageWeeklyGrowthRate = round(array_sum($weeklyPercentageChange) / $totalWeeks, 2);
140
+
127
         // Company Stats Overview Cards
141
         // Company Stats Overview Cards
128
         return [
142
         return [
129
             StatsOverviewWidget\Card::make("New Companies Forecast (Holt's Linear Trend)", $expectedNewCompanies),
143
             StatsOverviewWidget\Card::make("New Companies Forecast (Holt's Linear Trend)", $expectedNewCompanies),
131
             StatsOverviewWidget\Card::make('Personal Companies', Company::sum('personal_company')),
145
             StatsOverviewWidget\Card::make('Personal Companies', Company::sum('personal_company')),
132
         ];
146
         ];
133
     }
147
     }
134
-}
148
+}

+ 2
- 2
app/Filament/Pages/Widgets/Companies/Charts/CumulativeGrowth.php 查看文件

52
 
52
 
53
         for ($i = 1, $iMax = count($totalCompanies); $i < $iMax; $i++) {
53
         for ($i = 1, $iMax = count($totalCompanies); $i < $iMax; $i++) {
54
             $newCompanies[] = $totalCompanies[$i] - $totalCompanies[$i - 1];
54
             $newCompanies[] = $totalCompanies[$i] - $totalCompanies[$i - 1];
55
-            $monthlyPercentageChange[] = ($newCompanies[$i] / $totalCompanies[$i - 1]) * 100;
55
+            $monthlyPercentageChange[] = $totalCompanies[$i - 1] !== 0 ? ($newCompanies[$i] / $totalCompanies[$i - 1]) * 100 : 0;
56
         }
56
         }
57
 
57
 
58
         $labels = collect($months)->keys()->map(static function ($month) {
58
         $labels = collect($months)->keys()->map(static function ($month) {
151
             ],
151
             ],
152
         ];
152
         ];
153
     }
153
     }
154
-}
154
+}

+ 1
- 1
app/Filament/Pages/Widgets/Employees/Charts/CumulativeGrowth.php 查看文件

50
 
50
 
51
         for ($i = 1, $iMax = count($totalEmployees); $i < $iMax; $i++) {
51
         for ($i = 1, $iMax = count($totalEmployees); $i < $iMax; $i++) {
52
             $newEmployees[] = $totalEmployees[$i] - $totalEmployees[$i - 1];
52
             $newEmployees[] = $totalEmployees[$i] - $totalEmployees[$i - 1];
53
-            $monthlyPercentageChange[] = ($newEmployees[$i] / $totalEmployees[$i - 1]) * 100;
53
+            $monthlyPercentageChange[] = $totalEmployees[$i - 1] !== 0 ? ($newEmployees[$i] / $totalEmployees[$i - 1]) * 100 : 0;
54
         }
54
         }
55
 
55
 
56
         $labels = collect($months)->keys()->map(static function ($month) {
56
         $labels = collect($months)->keys()->map(static function ($month) {

+ 67
- 1
app/Filament/Resources/AccountResource.php 查看文件

4
 
4
 
5
 use App\Actions\OptionAction\CreateCurrency;
5
 use App\Actions\OptionAction\CreateCurrency;
6
 use App\Filament\Resources\AccountResource\Pages;
6
 use App\Filament\Resources\AccountResource\Pages;
7
-use App\Filament\Resources\AccountResource\RelationManagers;
8
 use App\Models\Banking\Account;
7
 use App\Models\Banking\Account;
9
 use App\Models\Setting\Currency;
8
 use App\Models\Setting\Currency;
10
 use App\Services\CurrencyService;
9
 use App\Services\CurrencyService;
77
                                     ->searchable()
76
                                     ->searchable()
78
                                     ->reactive()
77
                                     ->reactive()
79
                                     ->required()
78
                                     ->required()
79
+                                    ->saveRelationshipsUsing(null)
80
                                     ->createOptionForm([
80
                                     ->createOptionForm([
81
                                         Forms\Components\Select::make('currency.code')
81
                                         Forms\Components\Select::make('currency.code')
82
                                             ->label('Code')
82
                                             ->label('Code')
258
                 //
258
                 //
259
             ])
259
             ])
260
             ->actions([
260
             ->actions([
261
+                Tables\Actions\Action::make('update_balance')
262
+                    ->hidden(static fn (Account $record) => $record->currency_code === Currency::getDefaultCurrency())
263
+                    ->label('Update Balance')
264
+                    ->icon('heroicon-o-currency-dollar')
265
+                    ->requiresConfirmation()
266
+                    ->modalSubheading('Are you sure you want to update the balance with the latest exchange rate?')
267
+                    ->before(static function (Tables\Actions\Action $action, Account $record) {
268
+                        if ($record->currency_code !== Currency::getDefaultCurrency()) {
269
+                            $currencyService = app(CurrencyService::class);
270
+                            $defaultCurrency = Currency::getDefaultCurrency();
271
+                            $cachedExchangeRate = $currencyService->getCachedExchangeRate($defaultCurrency, $record->currency_code);
272
+                            $oldExchangeRate = $record->currency->rate;
273
+
274
+                            if ($cachedExchangeRate === $oldExchangeRate) {
275
+                                Notification::make()
276
+                                    ->warning()
277
+                                    ->title('Balance Already Up to Date')
278
+                                    ->body(__('The :name account balance is already up to date.', ['name' => $record->name]))
279
+                                    ->persistent()
280
+                                    ->send();
281
+
282
+                                $action->cancel();
283
+                            }
284
+                        }
285
+                    })
286
+                    ->action(static function (Account $record) {
287
+                        if ($record->currency_code !== Currency::getDefaultCurrency()) {
288
+                            $currencyService = app(CurrencyService::class);
289
+                            $defaultCurrency = Currency::getDefaultCurrency();
290
+                            $cachedExchangeRate = $currencyService->getCachedExchangeRate($defaultCurrency, $record->currency_code);
291
+                            $oldExchangeRate = $record->currency->rate;
292
+
293
+                            $originalBalanceInOriginalCurrency = $record->opening_balance / $oldExchangeRate;
294
+                            $currencyPrecision = $record->currency->precision;
295
+
296
+                            if ($cachedExchangeRate !== $oldExchangeRate) {
297
+                                $record->opening_balance = round($originalBalanceInOriginalCurrency * $cachedExchangeRate, $currencyPrecision);
298
+                                $record->currency->rate = $cachedExchangeRate;
299
+                                $record->currency->save();
300
+                                $record->save();
301
+                            }
302
+
303
+                            Notification::make()
304
+                                ->success()
305
+                                ->title('Balance Updated Successfully')
306
+                                ->body(__('The :name account balance has been updated to reflect the current exchange rate.', ['name' => $record->name]))
307
+                                ->send();
308
+                        }
309
+                    }),
261
                 Tables\Actions\EditAction::make(),
310
                 Tables\Actions\EditAction::make(),
262
                 Tables\Actions\DeleteAction::make()
311
                 Tables\Actions\DeleteAction::make()
312
+                    ->modalHeading('Delete Account')
313
+                    ->requiresConfirmation()
263
                     ->before(static function (Tables\Actions\DeleteAction $action, Account $record) {
314
                     ->before(static function (Tables\Actions\DeleteAction $action, Account $record) {
264
                         if ($record->enabled) {
315
                         if ($record->enabled) {
265
                             Notification::make()
316
                             Notification::make()
292
             ]);
343
             ]);
293
     }
344
     }
294
 
345
 
346
+    public static function getSlug(): string
347
+    {
348
+        return '{company}/banking/accounts';
349
+    }
350
+
351
+    public static function getUrl($name = 'index', $params = [], $isAbsolute = true): string
352
+    {
353
+        $routeBaseName = static::getRouteBaseName();
354
+
355
+        return route("{$routeBaseName}.{$name}", [
356
+            'company' => Auth::user()->currentCompany,
357
+            'record' => $params['record'] ?? null,
358
+        ], $isAbsolute);
359
+    }
360
+
295
     public static function getRelations(): array
361
     public static function getRelations(): array
296
     {
362
     {
297
         return [
363
         return [

+ 17
- 2
app/Filament/Resources/AccountResource/Pages/EditAccount.php 查看文件

3
 namespace App\Filament\Resources\AccountResource\Pages;
3
 namespace App\Filament\Resources\AccountResource\Pages;
4
 
4
 
5
 use App\Filament\Resources\AccountResource;
5
 use App\Filament\Resources\AccountResource;
6
+use App\Models\Banking\Account;
7
+use App\Models\Setting\Currency;
6
 use App\Traits\HandlesResourceRecordUpdate;
8
 use App\Traits\HandlesResourceRecordUpdate;
7
 use Filament\Pages\Actions;
9
 use Filament\Pages\Actions;
8
 use Filament\Resources\Pages\EditRecord;
10
 use Filament\Resources\Pages\EditRecord;
38
     /**
40
     /**
39
      * @throws Halt
41
      * @throws Halt
40
      */
42
      */
41
-    protected function handleRecordUpdate(Model $record, array $data): Model
43
+    protected function handleRecordUpdate(Model|Account $record, array $data): Model|Account
42
     {
44
     {
43
         $user = Auth::user();
45
         $user = Auth::user();
44
 
46
 
46
             throw new Halt('No authenticated user found.');
48
             throw new Halt('No authenticated user found.');
47
         }
49
         }
48
 
50
 
49
-        return $this->handleRecordUpdateWithUniqueField($record, $data, $user);
51
+        $oldCurrency = $record->currency_code;
52
+        $newCurrency = $data['currency_code'];
53
+
54
+        if ($oldCurrency !== $newCurrency) {
55
+            $data['opening_balance'] = Currency::convertBalance(
56
+                $data['opening_balance'],
57
+                $oldCurrency,
58
+                $newCurrency
59
+            );
60
+        }
61
+
62
+        $this->handleRecordUpdateWithUniqueField($record, $data, $user);
63
+
64
+        return parent::handleRecordUpdate($record, $data);
50
     }
65
     }
51
 }
66
 }

+ 15
- 2
app/Filament/Resources/CategoryResource.php 查看文件

3
 namespace App\Filament\Resources;
3
 namespace App\Filament\Resources;
4
 
4
 
5
 use App\Filament\Resources\CategoryResource\Pages;
5
 use App\Filament\Resources\CategoryResource\Pages;
6
-use App\Filament\Resources\CategoryResource\RelationManagers;
7
-use Illuminate\Database\Eloquent\Builder;
8
 use Illuminate\Support\Facades\Auth;
6
 use Illuminate\Support\Facades\Auth;
9
 use Wallo\FilamentSelectify\Components\ToggleButton;
7
 use Wallo\FilamentSelectify\Components\ToggleButton;
10
 use App\Models\Setting\Category;
8
 use App\Models\Setting\Category;
124
             ]);
122
             ]);
125
     }
123
     }
126
 
124
 
125
+    public static function getSlug(): string
126
+    {
127
+        return '{company}/settings/categories';
128
+    }
129
+
130
+    public static function getUrl($name = 'index', $params = [], $isAbsolute = true): string
131
+    {
132
+        $routeBaseName = static::getRouteBaseName();
133
+
134
+        return route("{$routeBaseName}.{$name}", [
135
+                'company' => Auth::user()->currentCompany,
136
+                'record' => $params['record'] ?? null,
137
+        ], $isAbsolute);
138
+    }
139
+
127
     public static function getRelations(): array
140
     public static function getRelations(): array
128
     {
141
     {
129
         return [
142
         return [

+ 4
- 0
app/Filament/Resources/CategoryResource/Pages/ListCategories.php 查看文件

3
 namespace App\Filament\Resources\CategoryResource\Pages;
3
 namespace App\Filament\Resources\CategoryResource\Pages;
4
 
4
 
5
 use App\Filament\Resources\CategoryResource;
5
 use App\Filament\Resources\CategoryResource;
6
+use Closure;
6
 use Filament\Pages\Actions;
7
 use Filament\Pages\Actions;
7
 use Filament\Resources\Pages\ListRecords;
8
 use Filament\Resources\Pages\ListRecords;
9
+use Illuminate\Support\Facades\Auth;
10
+use Illuminate\Support\Facades\Gate;
11
+use Wallo\FilamentCompanies\FilamentCompanies;
8
 
12
 
9
 class ListCategories extends ListRecords
13
 class ListCategories extends ListRecords
10
 {
14
 {

+ 15
- 2
app/Filament/Resources/CurrencyResource.php 查看文件

3
 namespace App\Filament\Resources;
3
 namespace App\Filament\Resources;
4
 
4
 
5
 use App\Filament\Resources\CurrencyResource\Pages;
5
 use App\Filament\Resources\CurrencyResource\Pages;
6
-use App\Filament\Resources\CurrencyResource\RelationManagers;
7
 use App\Models\Banking\Account;
6
 use App\Models\Banking\Account;
8
 use App\Models\Setting\Currency;
7
 use App\Models\Setting\Currency;
9
 use App\Services\CurrencyService;
8
 use App\Services\CurrencyService;
15
 use Filament\Resources\Resource;
14
 use Filament\Resources\Resource;
16
 use Filament\Resources\Table;
15
 use Filament\Resources\Table;
17
 use Filament\Tables;
16
 use Filament\Tables;
18
-use Illuminate\Database\Eloquent\Builder;
19
 use Illuminate\Support\Collection;
17
 use Illuminate\Support\Collection;
20
 use Illuminate\Support\Facades\Auth;
18
 use Illuminate\Support\Facades\Auth;
21
 use Wallo\FilamentSelectify\Components\ToggleButton;
19
 use Wallo\FilamentSelectify\Components\ToggleButton;
220
             ]);
218
             ]);
221
     }
219
     }
222
 
220
 
221
+    public static function getSlug(): string
222
+    {
223
+        return '{company}/settings/currencies';
224
+    }
225
+
226
+    public static function getUrl($name = 'index', $params = [], $isAbsolute = true): string
227
+    {
228
+        $routeBaseName = static::getRouteBaseName();
229
+
230
+        return route("{$routeBaseName}.{$name}", [
231
+            'company' => Auth::user()->currentCompany,
232
+            'record' => $params['record'] ?? null,
233
+        ], $isAbsolute);
234
+    }
235
+
223
     public static function getRelations(): array
236
     public static function getRelations(): array
224
     {
237
     {
225
         return [
238
         return [

+ 16
- 2
app/Filament/Resources/CustomerResource.php 查看文件

4
 
4
 
5
 use App\Actions\OptionAction\CreateCurrency;
5
 use App\Actions\OptionAction\CreateCurrency;
6
 use App\Filament\Resources\CustomerResource\Pages;
6
 use App\Filament\Resources\CustomerResource\Pages;
7
-use App\Filament\Resources\CustomerResource\RelationManagers;
8
 use App\Models\Setting\Currency;
7
 use App\Models\Setting\Currency;
9
 use App\Services\CurrencyService;
8
 use App\Services\CurrencyService;
9
+use Illuminate\Support\Facades\Auth;
10
 use Wallo\FilamentSelectify\Components\ButtonGroup;
10
 use Wallo\FilamentSelectify\Components\ButtonGroup;
11
 use App\Models\Contact;
11
 use App\Models\Contact;
12
 use Filament\Forms;
12
 use Filament\Forms;
15
 use Filament\Resources\Table;
15
 use Filament\Resources\Table;
16
 use Filament\Tables;
16
 use Filament\Tables;
17
 use Illuminate\Database\Eloquent\Builder;
17
 use Illuminate\Database\Eloquent\Builder;
18
-use Illuminate\Support\Facades\Auth;
19
 use Illuminate\Support\Facades\DB;
18
 use Illuminate\Support\Facades\DB;
20
 
19
 
21
 class CustomerResource extends Resource
20
 class CustomerResource extends Resource
221
             ]);
220
             ]);
222
     }
221
     }
223
 
222
 
223
+    public static function getSlug(): string
224
+    {
225
+        return '{company}/sales/customers';
226
+    }
227
+
228
+    public static function getUrl($name = 'index', $params = [], $isAbsolute = true): string
229
+    {
230
+        $routeBaseName = static::getRouteBaseName();
231
+
232
+        return route("{$routeBaseName}.{$name}", [
233
+            'company' => Auth::user()->currentCompany,
234
+            'record' => $params['record'] ?? null,
235
+        ], $isAbsolute);
236
+    }
237
+
224
     public static function getRelations(): array
238
     public static function getRelations(): array
225
     {
239
     {
226
         return [
240
         return [

+ 15
- 2
app/Filament/Resources/DiscountResource.php 查看文件

3
 namespace App\Filament\Resources;
3
 namespace App\Filament\Resources;
4
 
4
 
5
 use App\Filament\Resources\DiscountResource\Pages;
5
 use App\Filament\Resources\DiscountResource\Pages;
6
-use App\Filament\Resources\DiscountResource\RelationManagers;
7
 use App\Models\Setting\Discount;
6
 use App\Models\Setting\Discount;
8
 use Filament\Forms;
7
 use Filament\Forms;
9
 use Filament\Forms\Components\TextInput\Mask;
8
 use Filament\Forms\Components\TextInput\Mask;
11
 use Filament\Resources\Resource;
10
 use Filament\Resources\Resource;
12
 use Filament\Resources\Table;
11
 use Filament\Resources\Table;
13
 use Filament\Tables;
12
 use Filament\Tables;
14
-use Illuminate\Database\Eloquent\Builder;
15
 use Illuminate\Support\Facades\Auth;
13
 use Illuminate\Support\Facades\Auth;
16
 use Wallo\FilamentSelectify\Components\ToggleButton;
14
 use Wallo\FilamentSelectify\Components\ToggleButton;
17
 
15
 
171
             ]);
169
             ]);
172
     }
170
     }
173
 
171
 
172
+    public static function getSlug(): string
173
+    {
174
+        return '{company}/settings/discounts';
175
+    }
176
+
177
+    public static function getUrl($name = 'index', $params = [], $isAbsolute = true): string
178
+    {
179
+        $routeBaseName = static::getRouteBaseName();
180
+
181
+        return route("{$routeBaseName}.{$name}", [
182
+            'company' => Auth::user()->currentCompany,
183
+            'record' => $params['record'] ?? null,
184
+        ], $isAbsolute);
185
+    }
186
+
174
     public static function getRelations(): array
187
     public static function getRelations(): array
175
     {
188
     {
176
         return [
189
         return [

+ 16
- 2
app/Filament/Resources/InvoiceResource.php 查看文件

5
 use App\Filament\Resources\InvoiceResource\Pages;
5
 use App\Filament\Resources\InvoiceResource\Pages;
6
 use App\Filament\Resources\InvoiceResource\RelationManagers;
6
 use App\Filament\Resources\InvoiceResource\RelationManagers;
7
 use App\Models\Setting\Currency;
7
 use App\Models\Setting\Currency;
8
+use Illuminate\Support\Facades\Auth;
8
 use Wallo\FilamentSelectify\Components\ButtonGroup;
9
 use Wallo\FilamentSelectify\Components\ButtonGroup;
9
 use App\Models\Document\Document;
10
 use App\Models\Document\Document;
10
 use Filament\Forms;
11
 use Filament\Forms;
13
 use Filament\Resources\Table;
14
 use Filament\Resources\Table;
14
 use Filament\Tables;
15
 use Filament\Tables;
15
 use Illuminate\Database\Eloquent\Builder;
16
 use Illuminate\Database\Eloquent\Builder;
16
-use Illuminate\Database\Eloquent\SoftDeletingScope;
17
-use Illuminate\Support\Facades\Auth;
18
 
17
 
19
 class InvoiceResource extends Resource
18
 class InvoiceResource extends Resource
20
 {
19
 {
120
             ]);
119
             ]);
121
     }
120
     }
122
 
121
 
122
+    public static function getSlug(): string
123
+    {
124
+        return '{company}/sales/invoices';
125
+    }
126
+
127
+    public static function getUrl($name = 'index', $params = [], $isAbsolute = true): string
128
+    {
129
+        $routeBaseName = static::getRouteBaseName();
130
+
131
+        return route("{$routeBaseName}.{$name}", [
132
+            'company' => Auth::user()->currentCompany,
133
+            'record' => $params['record'] ?? null,
134
+        ], $isAbsolute);
135
+    }
136
+
123
     public static function getRelations(): array
137
     public static function getRelations(): array
124
     {
138
     {
125
         return [
139
         return [

+ 15
- 2
app/Filament/Resources/TaxResource.php 查看文件

3
 namespace App\Filament\Resources;
3
 namespace App\Filament\Resources;
4
 
4
 
5
 use App\Filament\Resources\TaxResource\Pages;
5
 use App\Filament\Resources\TaxResource\Pages;
6
-use App\Filament\Resources\TaxResource\RelationManagers;
7
 use App\Models\Setting\Tax;
6
 use App\Models\Setting\Tax;
8
 use Exception;
7
 use Exception;
9
 use Filament\Forms;
8
 use Filament\Forms;
13
 use Filament\Resources\Resource;
12
 use Filament\Resources\Resource;
14
 use Filament\Resources\Table;
13
 use Filament\Resources\Table;
15
 use Filament\Tables;
14
 use Filament\Tables;
16
-use Illuminate\Database\Eloquent\Builder;
17
 use Illuminate\Support\Collection;
15
 use Illuminate\Support\Collection;
18
 use Illuminate\Support\Facades\Auth;
16
 use Illuminate\Support\Facades\Auth;
19
 use Wallo\FilamentSelectify\Components\ToggleButton;
17
 use Wallo\FilamentSelectify\Components\ToggleButton;
177
             ]);
175
             ]);
178
     }
176
     }
179
 
177
 
178
+    public static function getSlug(): string
179
+    {
180
+        return '{company}/settings/taxes';
181
+    }
182
+
183
+    public static function getUrl($name = 'index', $params = [], $isAbsolute = true): string
184
+    {
185
+        $routeBaseName = static::getRouteBaseName();
186
+
187
+        return route("{$routeBaseName}.{$name}", [
188
+            'company' => Auth::user()->currentCompany,
189
+            'record' => $params['record'] ?? null,
190
+        ], $isAbsolute);
191
+    }
192
+
180
     public static function getRelations(): array
193
     public static function getRelations(): array
181
     {
194
     {
182
         return [
195
         return [

+ 26
- 0
app/Forms/Components/Invoice.php 查看文件

1
+<?php
2
+
3
+namespace App\Forms\Components;
4
+
5
+use Filament\Forms\Components\Field;
6
+
7
+class Invoice extends Field
8
+{
9
+    protected string $view = 'forms.components.invoice';
10
+
11
+    protected ?string $companyName = null;
12
+
13
+    protected ?string $companyAddress = null;
14
+
15
+    protected ?string $companyCity = null;
16
+
17
+    protected ?string $companyState = null;
18
+
19
+    protected ?string $companyZip = null;
20
+
21
+    protected ?string $companyCountry = null;
22
+
23
+    protected ?string $documentNumberPrefix = null;
24
+
25
+    protected ?string $documentNumberDigits = null;
26
+}

+ 89
- 0
app/Http/Livewire/CompanyDetails.php 查看文件

1
+<?php
2
+
3
+namespace App\Http\Livewire;
4
+
5
+use App\Abstracts\Forms\EditFormRecord;
6
+use App\Models\Company;
7
+use Filament\Forms\ComponentContainer;
8
+use Filament\Forms\Components\FileUpload;
9
+use Filament\Forms\Components\Group;
10
+use Filament\Forms\Components\Section;
11
+use Filament\Forms\Components\TextInput;
12
+use Illuminate\Contracts\View\View;
13
+use Illuminate\Database\Eloquent\Model;
14
+
15
+/**
16
+ * @property ComponentContainer $form
17
+ */
18
+class CompanyDetails extends EditFormRecord
19
+{
20
+    public Company $company;
21
+
22
+    protected function getFormModel(): Model|string|null
23
+    {
24
+        return $this->company;
25
+    }
26
+
27
+    protected function getFormSchema(): array
28
+    {
29
+        return [
30
+            Section::make('General')
31
+                ->schema([
32
+                    Group::make()
33
+                        ->schema([
34
+                            TextInput::make('email')
35
+                                ->label('Email')
36
+                                ->email()
37
+                                ->nullable(),
38
+                            TextInput::make('phone')
39
+                                ->label('Phone')
40
+                                ->tel()
41
+                                ->maxLength(20),
42
+                        ])->columns(1),
43
+                    Group::make()
44
+                        ->schema([
45
+                            FileUpload::make('logo')
46
+                                ->label('Logo')
47
+                                ->disk('public')
48
+                                ->directory('logos/company')
49
+                                ->imageResizeMode('cover')
50
+                                ->imagePreviewHeight('150')
51
+                                ->imageCropAspectRatio('2:1')
52
+                                ->panelAspectRatio('2:1')
53
+                                ->reactive()
54
+                                ->enableOpen()
55
+                                ->preserveFilenames()
56
+                                ->visibility('public')
57
+                                ->image(),
58
+                        ])->columns(1),
59
+                ])->columns(),
60
+            Section::make('Address')
61
+                ->schema([
62
+                    TextInput::make('address')
63
+                        ->label('Address')
64
+                        ->maxLength(100)
65
+                        ->columnSpanFull()
66
+                        ->nullable(),
67
+                    TextInput::make('country')
68
+                        ->label('Country')
69
+                        ->nullable(),
70
+                    TextInput::make('state')
71
+                        ->label('Province/State')
72
+                        ->nullable(),
73
+                    TextInput::make('city')
74
+                        ->label('Town/City')
75
+                        ->maxLength(100)
76
+                        ->nullable(),
77
+                    TextInput::make('zip_code')
78
+                        ->label('Postal/Zip Code')
79
+                        ->maxLength(100)
80
+                        ->nullable(),
81
+                ])->columns(),
82
+        ];
83
+    }
84
+
85
+    public function render(): View
86
+    {
87
+        return view('livewire.company-details');
88
+    }
89
+}

+ 10
- 13
app/Http/Livewire/DefaultSetting.php 查看文件

15
 use Filament\Forms\Contracts\HasForms;
15
 use Filament\Forms\Contracts\HasForms;
16
 use Filament\Notifications\Notification;
16
 use Filament\Notifications\Notification;
17
 use Illuminate\Contracts\View\View;
17
 use Illuminate\Contracts\View\View;
18
+use Illuminate\Database\Eloquent\Model;
18
 use Livewire\Component;
19
 use Livewire\Component;
19
 
20
 
20
 /**
21
 /**
24
 {
25
 {
25
     use InteractsWithForms, HandlesDefaultSettingRecordUpdate;
26
     use InteractsWithForms, HandlesDefaultSettingRecordUpdate;
26
 
27
 
27
-    public Defaults $defaultSetting;
28
-
29
     public $data;
28
     public $data;
30
 
29
 
31
-    public $record;
30
+    public Defaults $record;
32
 
31
 
33
     public function mount():void
32
     public function mount():void
34
     {
33
     {
35
-        $this->defaultSetting = Defaults::firstOrNew();
34
+        $this->record = Defaults::firstOrNew();
36
 
35
 
37
         $this->form->fill([
36
         $this->form->fill([
38
             'account_id' => Defaults::getDefaultAccount(),
37
             'account_id' => Defaults::getDefaultAccount(),
113
     {
112
     {
114
         $data = $this->form->getState();
113
         $data = $this->form->getState();
115
 
114
 
116
-        $this->record = $this->handleRecordUpdate($data);
117
-
118
-        $this->form->model($this->record)->saveRelationships();
115
+        $this->handleRecordUpdate($this->getFormModel(), $data);
119
 
116
 
120
         $this->getSavedNotification()?->send();
117
         $this->getSavedNotification()?->send();
121
     }
118
     }
122
 
119
 
120
+    protected function getFormModel(): Model
121
+    {
122
+        return $this->record;
123
+    }
124
+
123
     protected function getRelatedEntities(): array
125
     protected function getRelatedEntities(): array
124
     {
126
     {
125
         return [
127
         return [
134
         ];
136
         ];
135
     }
137
     }
136
 
138
 
137
-    protected function getFormModel(): string
138
-    {
139
-        return Defaults::class;
140
-    }
141
-
142
-    protected function getSavedNotification():?Notification
139
+    protected function getSavedNotification(): ?Notification
143
     {
140
     {
144
         $title = $this->getSavedNotificationTitle();
141
         $title = $this->getSavedNotificationTitle();
145
 
142
 

+ 173
- 88
app/Http/Livewire/Invoice.php 查看文件

2
 
2
 
3
 namespace App\Http\Livewire;
3
 namespace App\Http\Livewire;
4
 
4
 
5
+use App\Abstracts\Forms\EditFormRecord;
5
 use App\Models\Setting\DocumentDefault;
6
 use App\Models\Setting\DocumentDefault;
6
 use Filament\Forms\ComponentContainer;
7
 use Filament\Forms\ComponentContainer;
8
+use Filament\Forms\Components\ColorPicker;
9
+use Filament\Forms\Components\FileUpload;
10
+use Filament\Forms\Components\Group;
11
+use Filament\Forms\Components\Radio;
7
 use Filament\Forms\Components\Section;
12
 use Filament\Forms\Components\Section;
8
 use Filament\Forms\Components\Select;
13
 use Filament\Forms\Components\Select;
9
 use Filament\Forms\Components\Textarea;
14
 use Filament\Forms\Components\Textarea;
10
 use Filament\Forms\Components\TextInput;
15
 use Filament\Forms\Components\TextInput;
11
-use Filament\Forms\Concerns\InteractsWithForms;
12
-use Filament\Notifications\Notification;
16
+use Filament\Forms\Components\ViewField;
13
 use Illuminate\Contracts\View\View;
17
 use Illuminate\Contracts\View\View;
14
-use Illuminate\Support\Facades\Auth;
15
-use Livewire\Component;
16
-use Filament\Forms\Contracts\HasForms;
18
+use Illuminate\Database\Eloquent\Model;
17
 
19
 
18
 /**
20
 /**
19
  * @property ComponentContainer $form
21
  * @property ComponentContainer $form
20
  */
22
  */
21
-class Invoice extends Component implements HasForms
23
+class Invoice extends EditFormRecord
22
 {
24
 {
23
-    use InteractsWithForms;
24
-
25
     public DocumentDefault $invoice;
25
     public DocumentDefault $invoice;
26
 
26
 
27
-    public $data;
27
+    protected function getFormModel(): Model|string|null
28
+    {
29
+        $this->invoice = DocumentDefault::where('type', 'invoice')->firstOrNew();
28
 
30
 
29
-    public $record;
31
+        return $this->invoice;
32
+    }
30
 
33
 
31
     public function mount(): void
34
     public function mount(): void
32
     {
35
     {
33
-        $this->invoice = DocumentDefault::where('type', 'invoice')->firstOrNew();
36
+        $this->fillForm();
37
+    }
34
 
38
 
35
-        $this->form->fill([
36
-            'document_number_prefix' => $this->invoice->document_number_prefix,
37
-            'document_number_digits' => $this->invoice->document_number_digits,
38
-            'document_number_next' => $this->invoice->document_number_next,
39
-            'payment_terms' => $this->invoice->payment_terms,
40
-            'template' => $this->invoice->template,
41
-            'title' => $this->invoice->title,
42
-            'subheading' => $this->invoice->subheading,
43
-            'notes' => $this->invoice->notes,
44
-            'footer' => $this->invoice->footer,
45
-            'terms' => $this->invoice->terms,
46
-        ]);
39
+    public function fillForm(): void
40
+    {
41
+        $data = $this->getFormModel()->attributesToArray();
42
+
43
+        unset($data['id']);
44
+
45
+        $data = $this->mutateFormDataBeforeFill($data);
46
+
47
+        $this->form->fill($data);
47
     }
48
     }
48
 
49
 
49
     protected function getFormSchema(): array
50
     protected function getFormSchema(): array
51
         return [
52
         return [
52
             Section::make('General')
53
             Section::make('General')
53
                 ->schema([
54
                 ->schema([
54
-                    TextInput::make('document_number_prefix')
55
+                    TextInput::make('number_prefix')
55
                         ->label('Number Prefix')
56
                         ->label('Number Prefix')
56
                         ->default('INV-')
57
                         ->default('INV-')
58
+                        ->reactive()
57
                         ->required(),
59
                         ->required(),
58
-                    Select::make('document_number_digits')
60
+                    Select::make('number_digits')
59
                         ->label('Number Digits')
61
                         ->label('Number Digits')
60
-                        ->options(DocumentDefault::getDocumentNumberDigits())
61
-                        ->default(DocumentDefault::getDefaultDocumentNumberDigits())
62
+                        ->options($this->invoice->getAvailableNumberDigits())
63
+                        ->default($this->invoice->getDefaultNumberDigits())
62
                         ->reactive()
64
                         ->reactive()
63
-                        ->afterStateUpdated(static function (callable $set, $state) {
65
+                        ->afterStateUpdated(function (callable $set, $state) {
64
                             $numDigits = $state;
66
                             $numDigits = $state;
65
-                            $nextNumber = DocumentDefault::getDefaultDocumentNumberNext($numDigits);
67
+                            $nextNumber = $this->invoice->getNextDocumentNumber($numDigits);
66
 
68
 
67
-                            return $set('document_number_next', $nextNumber);
69
+                            return $set('number_next', $nextNumber);
68
                         })
70
                         })
69
-                        ->searchable()
70
-                        ->required(),
71
-                    TextInput::make('document_number_next')
71
+                        ->required()
72
+                        ->searchable(),
73
+                    TextInput::make('number_next')
72
                         ->label('Next Number')
74
                         ->label('Next Number')
73
-                        ->default(DocumentDefault::getDefaultDocumentNumberNext(DocumentDefault::getDefaultDocumentNumberDigits()))
74
-                        ->required(),
75
+                        ->reactive()
76
+                        ->required()
77
+                        ->default($this->invoice->getNextDocumentNumber($this->invoice->getDefaultNumberDigits())),
75
                     Select::make('payment_terms')
78
                     Select::make('payment_terms')
76
                         ->label('Payment Terms')
79
                         ->label('Payment Terms')
77
-                        ->options(DocumentDefault::getPaymentTerms())
78
-                        ->default(DocumentDefault::getDefaultPaymentTerms())
79
-                        ->searchable()
80
-                        ->required(),
81
-                ])->columns(),
82
-            Section::make('Template')
83
-                ->schema([
84
-                    Select::make('template')
85
-                        ->label('Template')
86
-                        ->options([
87
-                            'default' => 'Default',
88
-                            'simple' => 'Simple',
89
-                            'modern' => 'Modern',
90
-                        ])
91
-                        ->default('default')
80
+                        ->options($this->invoice->getPaymentTerms())
81
+                        ->default($this->invoice->getDefaultPaymentTerms())
92
                         ->searchable()
82
                         ->searchable()
83
+                        ->reactive()
93
                         ->required(),
84
                         ->required(),
94
                 ])->columns(),
85
                 ])->columns(),
95
             Section::make('Content')
86
             Section::make('Content')
96
                 ->schema([
87
                 ->schema([
97
                     TextInput::make('title')
88
                     TextInput::make('title')
98
                         ->label('Title')
89
                         ->label('Title')
90
+                        ->reactive()
99
                         ->default('Invoice')
91
                         ->default('Invoice')
100
                         ->nullable(),
92
                         ->nullable(),
101
                     TextInput::make('subheading')
93
                     TextInput::make('subheading')
102
                         ->label('Subheading')
94
                         ->label('Subheading')
103
-                        ->nullable(),
104
-                    Textarea::make('notes')
105
-                        ->label('Notes')
95
+                        ->reactive()
106
                         ->nullable(),
96
                         ->nullable(),
107
                     Textarea::make('footer')
97
                     Textarea::make('footer')
108
                         ->label('Footer')
98
                         ->label('Footer')
99
+                        ->reactive()
109
                         ->nullable(),
100
                         ->nullable(),
110
                     Textarea::make('terms')
101
                     Textarea::make('terms')
111
-                        ->label('Terms')
102
+                        ->label('Notes / Terms')
112
                         ->nullable()
103
                         ->nullable()
113
-                        ->columnSpanFull(),
104
+                        ->reactive(),
114
                 ])->columns(),
105
                 ])->columns(),
106
+            Section::make('Template Settings')
107
+                ->description('Choose the template and edit the titles of the columns on your invoices.')
108
+                ->schema([
109
+                    Group::make()
110
+                        ->schema([
111
+                            FileUpload::make('document_logo')
112
+                                ->label('Logo')
113
+                                ->disk('public')
114
+                                ->directory('logos/documents')
115
+                                ->imageResizeMode('contain')
116
+                                ->imagePreviewHeight('250')
117
+                                ->imageCropAspectRatio('2:1')
118
+                                ->reactive()
119
+                                ->enableOpen()
120
+                                ->preserveFilenames()
121
+                                ->visibility('public')
122
+                                ->image(),
123
+                            ColorPicker::make('accent_color')
124
+                                ->label('Accent Color')
125
+                                ->reactive()
126
+                                ->default('#d9d9d9'),
127
+                            Select::make('template')
128
+                                ->label('Template')
129
+                                ->options([
130
+                                    'default' => 'Default',
131
+                                    'modern' => 'Modern',
132
+                                ])
133
+                                ->reactive()
134
+                                ->default('modern')
135
+                                ->required(),
136
+                            Radio::make('item_column')
137
+                                ->label('Items')
138
+                                ->options($this->invoice->getItemColumns())
139
+                                ->dehydrateStateUsing(static function (callable $get, $state) {
140
+                                    return $state === 'other' ? $get('custom_item_column') : $state;
141
+                                })
142
+                                ->afterStateHydrated(function (callable $set, callable $get, $state, Radio $component) {
143
+                                    if (isset($this->invoice->getItemColumns()[$state])) {
144
+                                        $component->state($state);
145
+                                    } else {
146
+                                        $component->state('other');
147
+                                        $set('custom_item_column', $state);
148
+                                    }
149
+                                })
150
+                                ->default($this->invoice->getDefaultItemColumn())
151
+                                ->reactive(),
152
+                            TextInput::make('custom_item_column')
153
+                                ->reactive()
154
+                                ->disableLabel()
155
+                                ->disabled(static fn (callable $get) => $get('item_column') !== 'other')
156
+                                ->nullable(),
157
+                            Radio::make('unit_column')
158
+                                ->label('Units')
159
+                                ->options(DocumentDefault::getUnitColumns())
160
+                                ->dehydrateStateUsing(static function (callable $get, $state) {
161
+                                    return $state === 'other' ? $get('custom_unit_column') : $state;
162
+                                })
163
+                                ->afterStateHydrated(function (callable $set, callable $get, $state, Radio $component) {
164
+                                    if (isset($this->invoice->getUnitColumns()[$state])) {
165
+                                        $component->state($state);
166
+                                    } else {
167
+                                        $component->state('other');
168
+                                        $set('custom_unit_column', $state);
169
+                                    }
170
+                                })
171
+                                ->default($this->invoice->getDefaultUnitColumn())
172
+                                ->reactive(),
173
+                            TextInput::make('custom_unit_column')
174
+                                ->reactive()
175
+                                ->disableLabel()
176
+                                ->disabled(static fn (callable $get) => $get('unit_column') !== 'other')
177
+                                ->nullable(),
178
+                            Radio::make('price_column')
179
+                                ->label('Price')
180
+                                ->options($this->invoice->getPriceColumns())
181
+                                ->dehydrateStateUsing(static function (callable $get, $state) {
182
+                                    return $state === 'other' ? $get('custom_price_column') : $state;
183
+                                })
184
+                                ->afterStateHydrated(function (callable $set, callable $get, $state, Radio $component) {
185
+                                    if (isset($this->invoice->getPriceColumns()[$state])) {
186
+                                        $component->state($state);
187
+                                    } else {
188
+                                        $component->state('other');
189
+                                        $set('custom_price_column', $state);
190
+                                    }
191
+                                })
192
+                                ->default($this->invoice->getDefaultPriceColumn())
193
+                                ->reactive(),
194
+                            TextInput::make('custom_price_column')
195
+                                ->reactive()
196
+                                ->disableLabel()
197
+                                ->disabled(static fn (callable $get) => $get('price_column') !== 'other')
198
+                                ->nullable(),
199
+                            Radio::make('amount_column')
200
+                                ->label('Amount')
201
+                                ->options($this->invoice->getAmountColumns())
202
+                                ->dehydrateStateUsing(static function (callable $get, $state) {
203
+                                    return $state === 'other' ? $get('custom_amount_column') : $state;
204
+                                })
205
+                                ->afterStateHydrated(function (callable $set, callable $get, $state, Radio $component) {
206
+                                    if (isset($this->invoice->getAmountColumns()[$state])) {
207
+                                        $component->state($state);
208
+                                    } else {
209
+                                        $component->state('other');
210
+                                        $set('custom_amount_column', $state);
211
+                                    }
212
+                                })
213
+                                ->default($this->invoice->getDefaultAmountColumn())
214
+                                ->reactive(),
215
+                            TextInput::make('custom_amount_column')
216
+                                ->reactive()
217
+                                ->disableLabel()
218
+                                ->disabled(static fn (callable $get) => $get('amount_column') !== 'other')
219
+                                ->nullable(),
220
+                        ])->columns(1),
221
+                    Group::make()
222
+                        ->schema([
223
+                            ViewField::make('preview.default')
224
+                                ->label('Preview')
225
+                                ->visible(static fn (callable $get) => $get('template') === 'default')
226
+                                ->view('components.invoice-layouts.default'),
227
+                            ViewField::make('preview.modern')
228
+                                ->label('Preview')
229
+                                ->visible(static fn (callable $get) => $get('template') === 'modern')
230
+                                ->view('components.invoice-layouts.modern'),
231
+                        ])->columnSpan(2),
232
+                ])->columns(3),
115
         ];
233
         ];
116
     }
234
     }
117
 
235
 
118
-    public function save(): void
119
-    {
120
-        $data = $this->form->getState();
121
-
122
-        $data = $this->mutateFormDataBeforeSave($data);
123
-
124
-        $this->record = $this->invoice->update($data);
125
-
126
-        $this->form->model($this->record)->saveRelationships();
127
-
128
-        $this->getSavedNotification()?->send();
129
-    }
130
-
131
     protected function mutateFormDataBeforeSave(array $data): array
236
     protected function mutateFormDataBeforeSave(array $data): array
132
     {
237
     {
133
-        $data['company_id'] = Auth::user()->currentCompany->id;
134
         $data['type'] = 'invoice';
238
         $data['type'] = 'invoice';
135
 
239
 
136
         return $data;
240
         return $data;
137
     }
241
     }
138
 
242
 
139
-    protected function getSavedNotification():?Notification
140
-    {
141
-        $title = $this->getSavedNotificationTitle();
142
-
143
-        if (blank($title)) {
144
-            return null;
145
-        }
146
-
147
-        return Notification::make()
148
-            ->success()
149
-            ->title($title);
150
-    }
151
-
152
-    protected function getSavedNotificationTitle(): ?string
153
-    {
154
-        return __('filament::resources/pages/edit-record.messages.saved');
155
-    }
156
-
157
-
158
     public function render(): View
243
     public function render(): View
159
     {
244
     {
160
         return view('livewire.invoice');
245
         return view('livewire.invoice');

+ 0
- 6
app/Models/Banking/Account.php 查看文件

2
 
2
 
3
 namespace App\Models\Banking;
3
 namespace App\Models\Banking;
4
 
4
 
5
-use App\Scopes\CurrentCompanyScope;
6
 use App\Models\Setting\Currency;
5
 use App\Models\Setting\Currency;
7
 use App\Models\Setting\DefaultSetting;
6
 use App\Models\Setting\DefaultSetting;
8
 use App\Traits\Blamable;
7
 use App\Traits\Blamable;
50
         'enabled' => 'boolean',
49
         'enabled' => 'boolean',
51
     ];
50
     ];
52
 
51
 
53
-    protected static function booted(): void
54
-    {
55
-        static::addGlobalScope(new CurrentCompanyScope);
56
-    }
57
-
58
     public function company(): BelongsTo
52
     public function company(): BelongsTo
59
     {
53
     {
60
         return $this->belongsTo(FilamentCompanies::companyModel(), 'company_id');
54
         return $this->belongsTo(FilamentCompanies::companyModel(), 'company_id');

+ 10
- 0
app/Models/Company.php 查看文件

14
 use Illuminate\Database\Eloquent\Factories\HasFactory;
14
 use Illuminate\Database\Eloquent\Factories\HasFactory;
15
 use Illuminate\Database\Eloquent\Relations\HasMany;
15
 use Illuminate\Database\Eloquent\Relations\HasMany;
16
 use Illuminate\Database\Eloquent\Relations\HasOne;
16
 use Illuminate\Database\Eloquent\Relations\HasOne;
17
+use Squire\Models\Country;
18
+use Squire\Models\Region;
17
 use Wallo\FilamentCompanies\Company as FilamentCompaniesCompany;
19
 use Wallo\FilamentCompanies\Company as FilamentCompaniesCompany;
18
 use Wallo\FilamentCompanies\Events\CompanyCreated;
20
 use Wallo\FilamentCompanies\Events\CompanyCreated;
19
 use Wallo\FilamentCompanies\Events\CompanyDeleted;
21
 use Wallo\FilamentCompanies\Events\CompanyDeleted;
40
     protected $fillable = [
42
     protected $fillable = [
41
         'name',
43
         'name',
42
         'personal_company',
44
         'personal_company',
45
+        'logo',
46
+        'address',
47
+        'city',
48
+        'zip_code',
49
+        'state',
50
+        'country',
51
+        'phone',
52
+        'email',
43
     ];
53
     ];
44
 
54
 
45
     /**
55
     /**

+ 0
- 5
app/Models/Contact.php 查看文件

41
         'updated_by',
41
         'updated_by',
42
     ];
42
     ];
43
 
43
 
44
-    protected static function booted(): void
45
-    {
46
-        static::addGlobalScope(new CurrentCompanyScope);
47
-    }
48
-
49
     public function company(): BelongsTo
44
     public function company(): BelongsTo
50
     {
45
     {
51
         return $this->belongsTo(FilamentCompanies::companyModel(), 'company_id');
46
         return $this->belongsTo(FilamentCompanies::companyModel(), 'company_id');

+ 0
- 6
app/Models/Setting/Category.php 查看文件

4
 
4
 
5
 use App\Models\Document\Document;
5
 use App\Models\Document\Document;
6
 use App\Models\Item;
6
 use App\Models\Item;
7
-use App\Scopes\CurrentCompanyScope;
8
 use App\Traits\Blamable;
7
 use App\Traits\Blamable;
9
 use App\Traits\CompanyOwned;
8
 use App\Traits\CompanyOwned;
10
 use Database\Factories\CategoryFactory;
9
 use Database\Factories\CategoryFactory;
36
         'enabled' => 'boolean',
35
         'enabled' => 'boolean',
37
     ];
36
     ];
38
 
37
 
39
-    protected static function booted(): void
40
-    {
41
-        static::addGlobalScope(new CurrentCompanyScope);
42
-    }
43
-
44
     public static function getCategoryTypes(): array
38
     public static function getCategoryTypes(): array
45
     {
39
     {
46
         return [
40
         return [

+ 15
- 5
app/Models/Setting/Currency.php 查看文件

42
         'symbol_first' => 'boolean',
42
         'symbol_first' => 'boolean',
43
     ];
43
     ];
44
 
44
 
45
-    protected static function booted(): void
46
-    {
47
-        static::addGlobalScope(new CurrentCompanyScope);
48
-    }
49
-
50
     public function company(): BelongsTo
45
     public function company(): BelongsTo
51
     {
46
     {
52
         return $this->belongsTo(FilamentCompanies::companyModel(), 'company_id');
47
         return $this->belongsTo(FilamentCompanies::companyModel(), 'company_id');
93
         return $defaultCurrency->code ?? null;
88
         return $defaultCurrency->code ?? null;
94
     }
89
     }
95
 
90
 
91
+    public static function convertBalance($balance, $oldCurrency, $newCurrency): float|int
92
+    {
93
+        $currencies = self::whereIn('code', [$oldCurrency, $newCurrency])->get();
94
+        $oldCurrency = $currencies->firstWhere('code', $oldCurrency);
95
+        $newCurrency = $currencies->firstWhere('code', $newCurrency);
96
+
97
+        $oldRate = $oldCurrency?->rate;
98
+        $newRate = $newCurrency?->rate;
99
+        $precision = $newCurrency?->precision;
100
+
101
+        $baseBalance = $balance / $oldRate;
102
+
103
+        return round($baseBalance * $newRate, $precision);
104
+    }
105
+
96
     protected static function newFactory(): Factory
106
     protected static function newFactory(): Factory
97
     {
107
     {
98
         return CurrencyFactory::new();
108
         return CurrencyFactory::new();

+ 0
- 5
app/Models/Setting/DefaultSetting.php 查看文件

31
         'updated_by',
31
         'updated_by',
32
     ];
32
     ];
33
 
33
 
34
-    protected static function booted(): void
35
-    {
36
-        static::addGlobalScope(new CurrentCompanyScope);
37
-    }
38
-
39
     public function company(): BelongsTo
34
     public function company(): BelongsTo
40
     {
35
     {
41
         return $this->belongsTo(FilamentCompanies::companyModel(), 'company_id');
36
         return $this->belongsTo(FilamentCompanies::companyModel(), 'company_id');

+ 0
- 5
app/Models/Setting/Discount.php 查看文件

43
         'end_date' => 'datetime',
43
         'end_date' => 'datetime',
44
     ];
44
     ];
45
 
45
 
46
-    protected static function booted(): void
47
-    {
48
-        static::addGlobalScope(new CurrentCompanyScope);
49
-    }
50
-
51
     public function company(): BelongsTo
46
     public function company(): BelongsTo
52
     {
47
     {
53
         return $this->belongsTo(FilamentCompanies::companyModel(), 'company_id');
48
         return $this->belongsTo(FilamentCompanies::companyModel(), 'company_id');

+ 72
- 29
app/Models/Setting/DocumentDefault.php 查看文件

21
     protected $table = 'document_defaults';
21
     protected $table = 'document_defaults';
22
 
22
 
23
     protected $fillable = [
23
     protected $fillable = [
24
-        'company_id',
25
         'type',
24
         'type',
26
-        'document_number_prefix',
27
-        'document_number_digits',
28
-        'document_number_next',
25
+        'document_logo',
26
+        'number_prefix',
27
+        'number_digits',
28
+        'number_next',
29
         'payment_terms',
29
         'payment_terms',
30
-        'template',
31
         'title',
30
         'title',
32
         'subheading',
31
         'subheading',
33
-        'notes',
34
         'terms',
32
         'terms',
35
         'footer',
33
         'footer',
34
+        'accent_color',
35
+        'template',
36
+        'item_column',
37
+        'unit_column',
38
+        'price_column',
39
+        'amount_column',
36
         'created_by',
40
         'created_by',
37
         'updated_by',
41
         'updated_by',
38
     ];
42
     ];
39
 
43
 
40
-    protected static function booted(): void
41
-    {
42
-        static::addGlobalScope(new CurrentCompanyScope);
43
-    }
44
-
45
     public function company(): BelongsTo
44
     public function company(): BelongsTo
46
     {
45
     {
47
         return $this->belongsTo(FilamentCompanies::companyModel(), 'company_id');
46
         return $this->belongsTo(FilamentCompanies::companyModel(), 'company_id');
62
         return $this->belongsTo(FilamentCompanies::userModel(), 'updated_by');
61
         return $this->belongsTo(FilamentCompanies::userModel(), 'updated_by');
63
     }
62
     }
64
 
63
 
65
-    public static function getDocumentNumberDigits(): array
64
+    public static function getAvailableNumberDigits(): array
66
     {
65
     {
67
         return array_combine(range(1, 20), range(1, 20));
66
         return array_combine(range(1, 20), range(1, 20));
68
     }
67
     }
69
 
68
 
70
-    public static function getDefaultDocumentNumberDigits(string $type = 'invoice'): int
69
+    public static function getDefaultNumberDigits(string $type = 'invoice'): int
71
     {
70
     {
72
-        $documentNumberDigits = self::where('type', $type)->pluck('document_number_digits', 'id')->toArray();
73
-
74
-        return array_key_first($documentNumberDigits) ?? 5;
71
+        return static::where('type', $type)->value('number_digits') ?? 5;
75
     }
72
     }
76
 
73
 
77
-    public static function getDefaultDocumentNumberNext(int|null $numDigits = null, string $type = 'invoice'): string
74
+    public static function getNextDocumentNumber(int|null $numDigits = null, string $type = 'invoice'): string
78
     {
75
     {
79
-        // Fetch the latest document
80
         $latestDocument = Document::where('type', $type)->orderBy('id', 'desc')->first();
76
         $latestDocument = Document::where('type', $type)->orderBy('id', 'desc')->first();
81
-
82
-        // If there are no documents yet, start from 1
83
-        if (!$latestDocument) {
84
-            $nextNumber = 1;
85
-        } else {
86
-            // Otherwise, increment the latest document's number
87
-            $nextNumber = (int)$latestDocument->document_number + 1;
88
-        }
89
-
77
+        $nextNumber = $latestDocument ? ((int)$latestDocument->number + 1) : 1;
90
         return str_pad($nextNumber, $numDigits, '0', STR_PAD_LEFT);
78
         return str_pad($nextNumber, $numDigits, '0', STR_PAD_LEFT);
91
     }
79
     }
92
 
80
 
105
 
93
 
106
     public static function getDefaultPaymentTerms(string $type = 'invoice'): int
94
     public static function getDefaultPaymentTerms(string $type = 'invoice'): int
107
     {
95
     {
108
-        $paymentTerms = self::where('type', $type)->pluck('payment_terms', 'id')->toArray();
96
+        return static::where('type', $type)->value('payment_terms') ?? 30;
97
+    }
98
+
99
+    public static function getItemColumns(): array
100
+    {
101
+        return [
102
+            'items' => 'Items',
103
+            'products' => 'Products',
104
+            'services' => 'Services',
105
+            'other' => 'Other',
106
+        ];
107
+    }
108
+
109
+    public static function getDefaultItemColumn(string $type = 'invoice'): string
110
+    {
111
+        return static::where('type', $type)->value('item_column') ?? 'items';
112
+    }
109
 
113
 
110
-        return array_key_first($paymentTerms) ?? 30;
114
+    public static function getUnitColumns(): array
115
+    {
116
+        return [
117
+            'quantity' => 'Quantity',
118
+            'hours' => 'Hours',
119
+            'other' => 'Other',
120
+        ];
121
+    }
122
+
123
+    public static function getDefaultUnitColumn(string $type = 'invoice'): string
124
+    {
125
+        return static::where('type', $type)->value('unit_column') ?? 'quantity';
126
+    }
127
+
128
+    public static function getPriceColumns(): array
129
+    {
130
+        return [
131
+            'price' => 'Price',
132
+            'rate' => 'Rate',
133
+            'other' => 'Other',
134
+        ];
135
+    }
136
+
137
+    public static function getDefaultPriceColumn(string $type = 'invoice'): string
138
+    {
139
+        return static::where('type', $type)->value('price_column') ?? 'price';
140
+    }
141
+
142
+    public static function getAmountColumns(): array
143
+    {
144
+        return [
145
+            'amount' => 'Amount',
146
+            'total' => 'Total',
147
+            'other' => 'Other',
148
+        ];
149
+    }
150
+
151
+    public static function getDefaultAmountColumn(string $type = 'invoice'): string
152
+    {
153
+        return static::where('type', $type)->value('amount_column') ?? 'amount';
111
     }
154
     }
112
 
155
 
113
     public function getDocumentNumberAttribute(): string
156
     public function getDocumentNumberAttribute(): string

+ 0
- 5
app/Models/Setting/Tax.php 查看文件

40
         'enabled' => 'boolean',
40
         'enabled' => 'boolean',
41
     ];
41
     ];
42
 
42
 
43
-    protected static function booted(): void
44
-    {
45
-        static::addGlobalScope(new CurrentCompanyScope);
46
-    }
47
-
48
     public function company(): BelongsTo
43
     public function company(): BelongsTo
49
     {
44
     {
50
         return $this->belongsTo(Company::class, 'company_id');
45
         return $this->belongsTo(Company::class, 'company_id');

+ 2
- 1
app/Providers/SquireServiceProvider.php 查看文件

2
 
2
 
3
 namespace App\Providers;
3
 namespace App\Providers;
4
 
4
 
5
+use App\Models\Company;
5
 use App\Models\Contact;
6
 use App\Models\Contact;
6
 use Squire\Repository;
7
 use Squire\Repository;
7
 use Illuminate\Support\ServiceProvider;
8
 use Illuminate\Support\ServiceProvider;
24
         Repository::registerSource(Contact::class, 'en', base_path('vendor/squirephp/regions-en/resources/data.csv'));
25
         Repository::registerSource(Contact::class, 'en', base_path('vendor/squirephp/regions-en/resources/data.csv'));
25
         Repository::registerSource(Contact::class, 'en', base_path('vendor/squirephp/countries-en/resources/data.csv'));
26
         Repository::registerSource(Contact::class, 'en', base_path('vendor/squirephp/countries-en/resources/data.csv'));
26
     }
27
     }
27
-}
28
+}

+ 14
- 9
app/Traits/CompanyOwned.php 查看文件

2
 
2
 
3
 namespace App\Traits;
3
 namespace App\Traits;
4
 
4
 
5
+use App\Scopes\CurrentCompanyScope;
5
 use Filament\Notifications\Notification;
6
 use Filament\Notifications\Notification;
6
 use Illuminate\Support\Facades\Auth;
7
 use Illuminate\Support\Facades\Auth;
7
 
8
 
10
     public static function bootCompanyOwned(): void
11
     public static function bootCompanyOwned(): void
11
     {
12
     {
12
         static::creating(static function ($model) {
13
         static::creating(static function ($model) {
13
-            if (Auth::check() && Auth::user()->currentCompany) {
14
-                $model->company_id = Auth::user()->currentCompany->id;
15
-            } else {
16
-                Notification::make()
17
-                    ->danger()
18
-                    ->title('Oops! Unable to Assign Company')
19
-                    ->body('We encountered an issue while creating the record. Please ensure you are logged in and have a valid company associated with your account.')
20
-                    ->persistent()
21
-                    ->send();
14
+            if (empty($model->company_id)) {
15
+                if (Auth::check() && Auth::user()->currentCompany) {
16
+                    $model->company_id = Auth::user()->currentCompany->id;
17
+                } else {
18
+                    Notification::make()
19
+                        ->danger()
20
+                        ->title('Oops! Unable to Assign Company')
21
+                        ->body('We encountered an issue while creating the record. Please ensure you are logged in and have a valid company associated with your account.')
22
+                        ->persistent()
23
+                        ->send();
24
+                }
22
             }
25
             }
23
         });
26
         });
27
+
28
+        static::addGlobalScope(new CurrentCompanyScope);
24
     }
29
     }
25
 }
30
 }

+ 9
- 11
app/Traits/HandlesDefaultSettingRecordUpdate.php 查看文件

8
 trait HandlesDefaultSettingRecordUpdate
8
 trait HandlesDefaultSettingRecordUpdate
9
 {
9
 {
10
     abstract protected function getRelatedEntities(): array;
10
     abstract protected function getRelatedEntities(): array;
11
-    abstract protected function getFormModel(): string;
12
 
11
 
13
-    protected function handleRecordUpdate(array $data): Model
12
+    protected function handleRecordUpdate(Model $record, array $data): Model
14
     {
13
     {
15
         $relatedEntities = $this->getRelatedEntities();
14
         $relatedEntities = $this->getRelatedEntities();
16
-        $model = $this->getFormModel();
17
 
15
 
18
-        $existingRecord = $model::where('company_id', Auth::user()->currentCompany->id)
16
+        $existingRecord = $record::query()->where('company_id', Auth::user()->currentCompany->id)
19
             ->latest()
17
             ->latest()
20
             ->first();
18
             ->first();
21
 
19
 
22
         foreach ($relatedEntities as $field => $params) {
20
         foreach ($relatedEntities as $field => $params) {
23
-            [$class, $key, $type] = array_pad($params, 3, null);
21
+            [$modelClass, $key, $type] = array_pad($params, 3, null);
24
 
22
 
25
             if ($existingRecord === null || !isset($existingRecord->{$field})) {
23
             if ($existingRecord === null || !isset($existingRecord->{$field})) {
26
                 continue;
24
                 continue;
27
             }
25
             }
28
 
26
 
29
             if (isset($data[$field]) && $data[$field] !== $existingRecord->{$field}) {
27
             if (isset($data[$field]) && $data[$field] !== $existingRecord->{$field}) {
30
-                $this->updateEnabledRecord($class, $key, $existingRecord->{$field}, $type, false);
31
-                $this->updateEnabledRecord($class, $key, $data[$field], $type, true);
28
+                $this->updateEnabledRecord(new $modelClass, $key, $existingRecord->{$field}, $type, false);
29
+                $this->updateEnabledRecord(new $modelClass, $key, $data[$field], $type);
32
             }
30
             }
33
         }
31
         }
34
 
32
 
35
-        $defaults = $model::where('company_id', Auth::user()->currentCompany->id)->first();
33
+        $defaults = $record::query()->where('company_id', Auth::user()->currentCompany->id)->first();
36
 
34
 
37
         if ($defaults === null) {
35
         if ($defaults === null) {
38
-            $defaults = $model::create($data);
36
+            $defaults = $record::query()->create($data);
39
         } else {
37
         } else {
40
             $defaults->update($data);
38
             $defaults->update($data);
41
         }
39
         }
43
         return $defaults;
41
         return $defaults;
44
     }
42
     }
45
 
43
 
46
-    protected function updateEnabledRecord($class, $key, $value, $type = null, $enabled = true): void
44
+    protected function updateEnabledRecord(Model $record, string $key, mixed $value, ?string $type = null, bool $enabled = true): void
47
     {
45
     {
48
-        $query = $class::where('company_id', Auth::user()->currentCompany->id)
46
+        $query = $record::query()->where('company_id', Auth::user()->currentCompany->id)
49
             ->where('enabled', !$enabled);
47
             ->where('enabled', !$enabled);
50
 
48
 
51
         if ($type !== null) {
49
         if ($type !== null) {

+ 173
- 0
app/View/Models/InvoiceViewModel.php 查看文件

1
+<?php
2
+
3
+namespace App\View\Models;
4
+
5
+use App\Models\Setting\DocumentDefault;
6
+use Spatie\ViewModels\ViewModel;
7
+
8
+class InvoiceViewModel extends ViewModel
9
+{
10
+    public DocumentDefault $invoice;
11
+
12
+    public ?array $data = [];
13
+
14
+    public function __construct(DocumentDefault $invoice, ?array $data = null)
15
+    {
16
+        $this->invoice = $invoice;
17
+        $this->data = $data;
18
+    }
19
+
20
+    public function document_logo(): ?string
21
+    {
22
+        return $this->data['document_logo'] ?? $this->invoice->document_logo ?? $this->invoice->company->logo ?? null;
23
+    }
24
+
25
+    // Company related methods
26
+    public function company_name(): string
27
+    {
28
+        return $this->invoice->company->name;
29
+    }
30
+
31
+    public function company_address(): ?string
32
+    {
33
+        return $this->invoice->company->address ?? null;
34
+    }
35
+
36
+    public function company_phone(): ?string
37
+    {
38
+        return $this->invoice->company->phone ?? null;
39
+    }
40
+
41
+    public function company_city(): ?string
42
+    {
43
+        return $this->invoice->company->city ?? null;
44
+    }
45
+
46
+    public function company_state(): ?string
47
+    {
48
+        return $this->invoice->company->state ?? null;
49
+    }
50
+
51
+    public function company_zip(): ?string
52
+    {
53
+        return $this->invoice->company->zip_code ?? null;
54
+    }
55
+
56
+    // Invoice numbering related methods
57
+    public function number_prefix(): string
58
+    {
59
+        return $this->data['number_prefix'] ?? $this->invoice->number_prefix ?? 'INV-';
60
+    }
61
+
62
+    public function number_digits(): int
63
+    {
64
+        return $this->data['number_digits'] ?? $this->invoice->number_digits ?? $this->invoice->getDefaultNumberDigits();
65
+    }
66
+
67
+    public function number_next(): int
68
+    {
69
+        return $this->data['number_next'] ?? $this->invoice->number_next ?? $this->invoice->getNextDocumentNumber($this->number_digits());
70
+    }
71
+
72
+    public function invoice_number(): string
73
+    {
74
+        return $this->number_prefix() . str_pad($this->number_next(), $this->number_digits(), "0", STR_PAD_LEFT);
75
+    }
76
+
77
+    // Invoice date related methods
78
+    public function invoice_date(): string
79
+    {
80
+        return now()->format('M d, Y');
81
+    }
82
+
83
+    public function payment_terms(): string
84
+    {
85
+        return $this->data['payment_terms'] ?? $this->invoice->payment_terms ?? $this->invoice->getDefaultPaymentTerms();
86
+    }
87
+
88
+    public function invoice_due_date(): string
89
+    {
90
+        return now()->addDays($this->payment_terms())->format('M d, Y');
91
+    }
92
+
93
+    // Invoice header related methods
94
+    public function title(): string
95
+    {
96
+        return $this->data['title'] ?? $this->invoice->title ?? 'Invoice';
97
+    }
98
+
99
+    public function subheading(): ?string
100
+    {
101
+        return $this->data['subheading'] ?? $this->invoice->subheading ?? null;
102
+    }
103
+
104
+    // Invoice styling
105
+    public function accent_color(): string
106
+    {
107
+        return $this->data['accent_color'] ?? $this->invoice->accent_color ?? '#6366F1';
108
+    }
109
+
110
+    public function footer(): string
111
+    {
112
+        return $this->data['footer'] ?? $this->invoice->footer ?? 'Thank you for your business!';
113
+    }
114
+
115
+    public function terms(): string
116
+    {
117
+        return $this->data['terms'] ?? $this->invoice->terms ?? 'Payment is due within thirty (30) days from the date of invoice. Any discrepancies should be reported within fourteen (14) days of receipt.';
118
+    }
119
+
120
+    // Invoice column related methods
121
+    public function item_column(): string
122
+    {
123
+        $item_column = $this->data['item_column'] ?? $this->invoice->item_column ?? $this->invoice->getDefaultItemColumn();
124
+        return isset($this->invoice->getItemColumns()[$item_column]) ? ucfirst($item_column) : $item_column;
125
+    }
126
+
127
+    public function unit_column(): string
128
+    {
129
+        $unit_column = $this->data['unit_column'] ?? $this->invoice->unit_column ?? $this->invoice->getDefaultUnitColumn();
130
+        return isset($this->invoice->getUnitColumns()[$unit_column]) ? ucfirst($unit_column) : $unit_column;
131
+    }
132
+
133
+    public function price_column(): string
134
+    {
135
+        $price_column = $this->data['price_column'] ?? $this->invoice->price_column ?? $this->invoice->getDefaultPriceColumn();
136
+        return isset($this->invoice->getPriceColumns()[$price_column]) ? ucfirst($price_column) : $price_column;
137
+    }
138
+
139
+    public function amount_column(): string
140
+    {
141
+        $amount_column = $this->data['amount_column'] ?? $this->invoice->amount_column ?? $this->invoice->getDefaultAmountColumn();
142
+        return isset($this->invoice->getAmountColumns()[$amount_column]) ? ucfirst($amount_column) : $amount_column;
143
+    }
144
+
145
+    public function buildViewData(): array
146
+    {
147
+        return [
148
+            'document_logo' => $this->document_logo(),
149
+            'company_name' => $this->company_name(),
150
+            'company_address' => $this->company_address(),
151
+            'company_phone' => $this->company_phone(),
152
+            'company_city' => $this->company_city(),
153
+            'company_state' => $this->company_state(),
154
+            'company_zip' => $this->company_zip(),
155
+            'number_prefix' => $this->number_prefix(),
156
+            'number_digits' => $this->number_digits(),
157
+            'number_next' => $this->number_next(),
158
+            'invoice_number' => $this->invoice_number(),
159
+            'invoice_date' => $this->invoice_date(),
160
+            'payment_terms' => $this->payment_terms(),
161
+            'invoice_due_date' => $this->invoice_due_date(),
162
+            'title' => $this->title(),
163
+            'subheading' => $this->subheading(),
164
+            'accent_color' => $this->accent_color(),
165
+            'footer' => $this->footer(),
166
+            'terms' => $this->terms(),
167
+            'item_column' => $this->item_column(),
168
+            'unit_column' => $this->unit_column(),
169
+            'price_column' => $this->price_column(),
170
+            'amount_column' => $this->amount_column(),
171
+        ];
172
+    }
173
+}

+ 1
- 0
composer.json 查看文件

16
         "laravel/sanctum": "^3.2",
16
         "laravel/sanctum": "^3.2",
17
         "laravel/tinker": "^2.8",
17
         "laravel/tinker": "^2.8",
18
         "leandrocfe/filament-apex-charts": "^1.0",
18
         "leandrocfe/filament-apex-charts": "^1.0",
19
+        "spatie/laravel-view-models": "^1.5",
19
         "squirephp/countries-en": "^3.4",
20
         "squirephp/countries-en": "^3.4",
20
         "squirephp/regions-en": "^3.4",
21
         "squirephp/regions-en": "^3.4",
21
         "squirephp/repository": "^3.4"
22
         "squirephp/repository": "^3.4"

+ 408
- 280
composer.lock
文件差異過大導致無法顯示
查看文件


+ 1
- 1
config/filament.php 查看文件

25
     |
25
     |
26
     */
26
     */
27
 
27
 
28
-    'path' => env('FILAMENT_PATH', 'admin'),
28
+    'path' => env('FILAMENT_PATH', 'company'),
29
 
29
 
30
     /*
30
     /*
31
     |--------------------------------------------------------------------------
31
     |--------------------------------------------------------------------------

+ 1
- 1
config/services.php 查看文件

34
     'github' => [
34
     'github' => [
35
         'client_id' => env('GITHUB_CLIENT_ID'),
35
         'client_id' => env('GITHUB_CLIENT_ID'),
36
         'client_secret' => env('GITHUB_CLIENT_SECRET'),
36
         'client_secret' => env('GITHUB_CLIENT_SECRET'),
37
-        'redirect' => 'https://erpsaas.test/oauth/github/callback',
37
+        'redirect' => 'http://erpsaas.test/oauth/github/callback',
38
     ],
38
     ],
39
 
39
 
40
 ];
40
 ];

+ 9
- 4
database/factories/DocumentDefaultFactory.php 查看文件

25
     public function definition(): array
25
     public function definition(): array
26
     {
26
     {
27
         return [
27
         return [
28
-            'document_number_digits' => '5',
29
-            'document_number_next' => '1',
28
+            'number_digits' => '5',
29
+            'number_next' => '1',
30
             'payment_terms' => '30',
30
             'payment_terms' => '30',
31
+            'accent_color' => '#007BFF',
31
             'template' => 'default',
32
             'template' => 'default',
33
+            'item_column' => 'items',
34
+            'unit_column' => 'quantity',
35
+            'price_column' => 'price',
36
+            'amount_column' => 'amount',
32
         ];
37
         ];
33
     }
38
     }
34
 
39
 
40
         return $this->state(function (array $attributes) {
45
         return $this->state(function (array $attributes) {
41
             return [
46
             return [
42
                 'type' => 'invoice',
47
                 'type' => 'invoice',
43
-                'document_number_prefix' => 'INV-',
48
+                'number_prefix' => 'INV-',
44
             ];
49
             ];
45
         });
50
         });
46
     }
51
     }
53
         return $this->state(function (array $attributes) {
58
         return $this->state(function (array $attributes) {
54
             return [
59
             return [
55
                 'type' => 'bill',
60
                 'type' => 'bill',
56
-                'document_number_prefix' => 'BILL-',
61
+                'number_prefix' => 'BILL-',
57
             ];
62
             ];
58
         });
63
         });
59
     }
64
     }

+ 10
- 5
database/migrations/2023_05_22_000100_create_document_defaults_table.php 查看文件

15
             $table->id();
15
             $table->id();
16
             $table->foreignId('company_id')->constrained()->cascadeOnDelete();
16
             $table->foreignId('company_id')->constrained()->cascadeOnDelete();
17
             $table->string('type');
17
             $table->string('type');
18
-            $table->string('document_number_prefix')->nullable();
19
-            $table->string('document_number_digits')->default(5);
20
-            $table->string('document_number_next')->default(1);
18
+            $table->string('document_logo')->nullable();
19
+            $table->string('number_prefix')->nullable();
20
+            $table->unsignedTinyInteger('number_digits')->default(5); // Adjusted this
21
+            $table->unsignedBigInteger('number_next')->default(1);   // Adjusted this to allow larger invoice numbers
21
             $table->string('payment_terms')->nullable();
22
             $table->string('payment_terms')->nullable();
22
-            $table->string('template')->default('default');
23
             $table->string('title')->nullable();
23
             $table->string('title')->nullable();
24
             $table->string('subheading')->nullable();
24
             $table->string('subheading')->nullable();
25
-            $table->text('notes')->nullable();
26
             $table->text('terms')->nullable();
25
             $table->text('terms')->nullable();
27
             $table->string('footer')->nullable();
26
             $table->string('footer')->nullable();
27
+            $table->string('accent_color')->nullable();
28
+            $table->string('template')->default('default');
29
+            $table->string('item_column')->nullable();
30
+            $table->string('unit_column')->nullable();
31
+            $table->string('price_column')->nullable();
32
+            $table->string('amount_column')->nullable();
28
             $table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
33
             $table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
29
             $table->foreignId('updated_by')->nullable()->constrained('users')->nullOnDelete();
34
             $table->foreignId('updated_by')->nullable()->constrained('users')->nullOnDelete();
30
             $table->timestamps();
35
             $table->timestamps();

+ 44
- 0
database/migrations/2023_08_07_144726_add_company_details_to_companies_table.php 查看文件

1
+<?php
2
+
3
+use Illuminate\Database\Migrations\Migration;
4
+use Illuminate\Database\Schema\Blueprint;
5
+use Illuminate\Support\Facades\Schema;
6
+
7
+return new class extends Migration
8
+{
9
+    /**
10
+     * Run the migrations.
11
+     */
12
+    public function up(): void
13
+    {
14
+        Schema::table('companies', function (Blueprint $table) {
15
+            $table->string('logo')->nullable();
16
+            $table->text('address')->nullable();
17
+            $table->string('city')->nullable();
18
+            $table->string('zip_code')->nullable();
19
+            $table->string('state')->nullable();
20
+            $table->string('country')->nullable();
21
+            $table->string('phone')->nullable();
22
+            $table->string('email')->nullable();
23
+        });
24
+    }
25
+
26
+    /**
27
+     * Reverse the migrations.
28
+     */
29
+    public function down(): void
30
+    {
31
+        Schema::table('companies', function (Blueprint $table) {
32
+            $table->dropColumn([
33
+                'logo',
34
+                'address',
35
+                'city',
36
+                'zip_code',
37
+                'state',
38
+                'country',
39
+                'phone',
40
+                'email',
41
+            ]);
42
+        });
43
+    }
44
+};

+ 161
- 161
package-lock.json 查看文件

32
             }
32
             }
33
         },
33
         },
34
         "node_modules/@alpinejs/focus": {
34
         "node_modules/@alpinejs/focus": {
35
-            "version": "3.12.2",
36
-            "resolved": "https://registry.npmjs.org/@alpinejs/focus/-/focus-3.12.2.tgz",
37
-            "integrity": "sha512-Uhu/0uSwgYSyzAeNT/kzrxiVKJQezwhUM1VxYwCpfOMT0OvKJ80Wl+M4R+5JJhsf+15B65rQnsE7no+TxpV5bg==",
35
+            "version": "3.12.3",
36
+            "resolved": "https://registry.npmjs.org/@alpinejs/focus/-/focus-3.12.3.tgz",
37
+            "integrity": "sha512-kIQwvvUPCfCO2REpceQ3uHNdN3oqDLvvvQNaHVgHoYqk+RrL3EcR6uOHyvHJUgOhaIjN5Uc3b7BaRNrKZbDGew==",
38
             "dev": true,
38
             "dev": true,
39
             "dependencies": {
39
             "dependencies": {
40
                 "focus-trap": "^6.6.1"
40
                 "focus-trap": "^6.6.1"
47
             "dev": true
47
             "dev": true
48
         },
48
         },
49
         "node_modules/@esbuild/android-arm": {
49
         "node_modules/@esbuild/android-arm": {
50
-            "version": "0.17.19",
51
-            "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz",
52
-            "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==",
50
+            "version": "0.18.20",
51
+            "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz",
52
+            "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==",
53
             "cpu": [
53
             "cpu": [
54
                 "arm"
54
                 "arm"
55
             ],
55
             ],
63
             }
63
             }
64
         },
64
         },
65
         "node_modules/@esbuild/android-arm64": {
65
         "node_modules/@esbuild/android-arm64": {
66
-            "version": "0.17.19",
67
-            "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz",
68
-            "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==",
66
+            "version": "0.18.20",
67
+            "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz",
68
+            "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==",
69
             "cpu": [
69
             "cpu": [
70
                 "arm64"
70
                 "arm64"
71
             ],
71
             ],
79
             }
79
             }
80
         },
80
         },
81
         "node_modules/@esbuild/android-x64": {
81
         "node_modules/@esbuild/android-x64": {
82
-            "version": "0.17.19",
83
-            "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz",
84
-            "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==",
82
+            "version": "0.18.20",
83
+            "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz",
84
+            "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==",
85
             "cpu": [
85
             "cpu": [
86
                 "x64"
86
                 "x64"
87
             ],
87
             ],
95
             }
95
             }
96
         },
96
         },
97
         "node_modules/@esbuild/darwin-arm64": {
97
         "node_modules/@esbuild/darwin-arm64": {
98
-            "version": "0.17.19",
99
-            "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz",
100
-            "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==",
98
+            "version": "0.18.20",
99
+            "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz",
100
+            "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==",
101
             "cpu": [
101
             "cpu": [
102
                 "arm64"
102
                 "arm64"
103
             ],
103
             ],
111
             }
111
             }
112
         },
112
         },
113
         "node_modules/@esbuild/darwin-x64": {
113
         "node_modules/@esbuild/darwin-x64": {
114
-            "version": "0.17.19",
115
-            "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz",
116
-            "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==",
114
+            "version": "0.18.20",
115
+            "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz",
116
+            "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==",
117
             "cpu": [
117
             "cpu": [
118
                 "x64"
118
                 "x64"
119
             ],
119
             ],
127
             }
127
             }
128
         },
128
         },
129
         "node_modules/@esbuild/freebsd-arm64": {
129
         "node_modules/@esbuild/freebsd-arm64": {
130
-            "version": "0.17.19",
131
-            "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz",
132
-            "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==",
130
+            "version": "0.18.20",
131
+            "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz",
132
+            "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==",
133
             "cpu": [
133
             "cpu": [
134
                 "arm64"
134
                 "arm64"
135
             ],
135
             ],
143
             }
143
             }
144
         },
144
         },
145
         "node_modules/@esbuild/freebsd-x64": {
145
         "node_modules/@esbuild/freebsd-x64": {
146
-            "version": "0.17.19",
147
-            "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz",
148
-            "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==",
146
+            "version": "0.18.20",
147
+            "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz",
148
+            "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==",
149
             "cpu": [
149
             "cpu": [
150
                 "x64"
150
                 "x64"
151
             ],
151
             ],
159
             }
159
             }
160
         },
160
         },
161
         "node_modules/@esbuild/linux-arm": {
161
         "node_modules/@esbuild/linux-arm": {
162
-            "version": "0.17.19",
163
-            "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz",
164
-            "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==",
162
+            "version": "0.18.20",
163
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz",
164
+            "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==",
165
             "cpu": [
165
             "cpu": [
166
                 "arm"
166
                 "arm"
167
             ],
167
             ],
175
             }
175
             }
176
         },
176
         },
177
         "node_modules/@esbuild/linux-arm64": {
177
         "node_modules/@esbuild/linux-arm64": {
178
-            "version": "0.17.19",
179
-            "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz",
180
-            "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==",
178
+            "version": "0.18.20",
179
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz",
180
+            "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==",
181
             "cpu": [
181
             "cpu": [
182
                 "arm64"
182
                 "arm64"
183
             ],
183
             ],
191
             }
191
             }
192
         },
192
         },
193
         "node_modules/@esbuild/linux-ia32": {
193
         "node_modules/@esbuild/linux-ia32": {
194
-            "version": "0.17.19",
195
-            "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz",
196
-            "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==",
194
+            "version": "0.18.20",
195
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz",
196
+            "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==",
197
             "cpu": [
197
             "cpu": [
198
                 "ia32"
198
                 "ia32"
199
             ],
199
             ],
207
             }
207
             }
208
         },
208
         },
209
         "node_modules/@esbuild/linux-loong64": {
209
         "node_modules/@esbuild/linux-loong64": {
210
-            "version": "0.17.19",
211
-            "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz",
212
-            "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==",
210
+            "version": "0.18.20",
211
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz",
212
+            "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==",
213
             "cpu": [
213
             "cpu": [
214
                 "loong64"
214
                 "loong64"
215
             ],
215
             ],
223
             }
223
             }
224
         },
224
         },
225
         "node_modules/@esbuild/linux-mips64el": {
225
         "node_modules/@esbuild/linux-mips64el": {
226
-            "version": "0.17.19",
227
-            "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz",
228
-            "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==",
226
+            "version": "0.18.20",
227
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz",
228
+            "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==",
229
             "cpu": [
229
             "cpu": [
230
                 "mips64el"
230
                 "mips64el"
231
             ],
231
             ],
239
             }
239
             }
240
         },
240
         },
241
         "node_modules/@esbuild/linux-ppc64": {
241
         "node_modules/@esbuild/linux-ppc64": {
242
-            "version": "0.17.19",
243
-            "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz",
244
-            "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==",
242
+            "version": "0.18.20",
243
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz",
244
+            "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==",
245
             "cpu": [
245
             "cpu": [
246
                 "ppc64"
246
                 "ppc64"
247
             ],
247
             ],
255
             }
255
             }
256
         },
256
         },
257
         "node_modules/@esbuild/linux-riscv64": {
257
         "node_modules/@esbuild/linux-riscv64": {
258
-            "version": "0.17.19",
259
-            "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz",
260
-            "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==",
258
+            "version": "0.18.20",
259
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz",
260
+            "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==",
261
             "cpu": [
261
             "cpu": [
262
                 "riscv64"
262
                 "riscv64"
263
             ],
263
             ],
271
             }
271
             }
272
         },
272
         },
273
         "node_modules/@esbuild/linux-s390x": {
273
         "node_modules/@esbuild/linux-s390x": {
274
-            "version": "0.17.19",
275
-            "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz",
276
-            "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==",
274
+            "version": "0.18.20",
275
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz",
276
+            "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==",
277
             "cpu": [
277
             "cpu": [
278
                 "s390x"
278
                 "s390x"
279
             ],
279
             ],
287
             }
287
             }
288
         },
288
         },
289
         "node_modules/@esbuild/linux-x64": {
289
         "node_modules/@esbuild/linux-x64": {
290
-            "version": "0.17.19",
291
-            "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz",
292
-            "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==",
290
+            "version": "0.18.20",
291
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz",
292
+            "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==",
293
             "cpu": [
293
             "cpu": [
294
                 "x64"
294
                 "x64"
295
             ],
295
             ],
303
             }
303
             }
304
         },
304
         },
305
         "node_modules/@esbuild/netbsd-x64": {
305
         "node_modules/@esbuild/netbsd-x64": {
306
-            "version": "0.17.19",
307
-            "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz",
308
-            "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==",
306
+            "version": "0.18.20",
307
+            "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz",
308
+            "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==",
309
             "cpu": [
309
             "cpu": [
310
                 "x64"
310
                 "x64"
311
             ],
311
             ],
319
             }
319
             }
320
         },
320
         },
321
         "node_modules/@esbuild/openbsd-x64": {
321
         "node_modules/@esbuild/openbsd-x64": {
322
-            "version": "0.17.19",
323
-            "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz",
324
-            "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==",
322
+            "version": "0.18.20",
323
+            "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz",
324
+            "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==",
325
             "cpu": [
325
             "cpu": [
326
                 "x64"
326
                 "x64"
327
             ],
327
             ],
335
             }
335
             }
336
         },
336
         },
337
         "node_modules/@esbuild/sunos-x64": {
337
         "node_modules/@esbuild/sunos-x64": {
338
-            "version": "0.17.19",
339
-            "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz",
340
-            "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==",
338
+            "version": "0.18.20",
339
+            "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz",
340
+            "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==",
341
             "cpu": [
341
             "cpu": [
342
                 "x64"
342
                 "x64"
343
             ],
343
             ],
351
             }
351
             }
352
         },
352
         },
353
         "node_modules/@esbuild/win32-arm64": {
353
         "node_modules/@esbuild/win32-arm64": {
354
-            "version": "0.17.19",
355
-            "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz",
356
-            "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==",
354
+            "version": "0.18.20",
355
+            "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz",
356
+            "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==",
357
             "cpu": [
357
             "cpu": [
358
                 "arm64"
358
                 "arm64"
359
             ],
359
             ],
367
             }
367
             }
368
         },
368
         },
369
         "node_modules/@esbuild/win32-ia32": {
369
         "node_modules/@esbuild/win32-ia32": {
370
-            "version": "0.17.19",
371
-            "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz",
372
-            "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==",
370
+            "version": "0.18.20",
371
+            "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz",
372
+            "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==",
373
             "cpu": [
373
             "cpu": [
374
                 "ia32"
374
                 "ia32"
375
             ],
375
             ],
383
             }
383
             }
384
         },
384
         },
385
         "node_modules/@esbuild/win32-x64": {
385
         "node_modules/@esbuild/win32-x64": {
386
-            "version": "0.17.19",
387
-            "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz",
388
-            "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==",
386
+            "version": "0.18.20",
387
+            "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz",
388
+            "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==",
389
             "cpu": [
389
             "cpu": [
390
                 "x64"
390
                 "x64"
391
             ],
391
             ],
413
             }
413
             }
414
         },
414
         },
415
         "node_modules/@jridgewell/resolve-uri": {
415
         "node_modules/@jridgewell/resolve-uri": {
416
-            "version": "3.1.0",
417
-            "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
418
-            "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==",
416
+            "version": "3.1.1",
417
+            "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz",
418
+            "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==",
419
             "dev": true,
419
             "dev": true,
420
             "engines": {
420
             "engines": {
421
                 "node": ">=6.0.0"
421
                 "node": ">=6.0.0"
437
             "dev": true
437
             "dev": true
438
         },
438
         },
439
         "node_modules/@jridgewell/trace-mapping": {
439
         "node_modules/@jridgewell/trace-mapping": {
440
-            "version": "0.3.18",
441
-            "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz",
442
-            "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==",
440
+            "version": "0.3.19",
441
+            "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz",
442
+            "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==",
443
             "dev": true,
443
             "dev": true,
444
             "dependencies": {
444
             "dependencies": {
445
-                "@jridgewell/resolve-uri": "3.1.0",
446
-                "@jridgewell/sourcemap-codec": "1.4.14"
445
+                "@jridgewell/resolve-uri": "^3.1.0",
446
+                "@jridgewell/sourcemap-codec": "^1.4.14"
447
             }
447
             }
448
         },
448
         },
449
-        "node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/sourcemap-codec": {
450
-            "version": "1.4.14",
451
-            "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
452
-            "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
453
-            "dev": true
454
-        },
455
         "node_modules/@nodelib/fs.scandir": {
449
         "node_modules/@nodelib/fs.scandir": {
456
             "version": "2.1.5",
450
             "version": "2.1.5",
457
             "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
451
             "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
498
             }
492
             }
499
         },
493
         },
500
         "node_modules/@tailwindcss/forms": {
494
         "node_modules/@tailwindcss/forms": {
501
-            "version": "0.5.3",
502
-            "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.3.tgz",
503
-            "integrity": "sha512-y5mb86JUoiUgBjY/o6FJSFZSEttfb3Q5gllE4xoKjAAD+vBrnIhE4dViwUuow3va8mpH4s9jyUbUbrRGoRdc2Q==",
495
+            "version": "0.5.4",
496
+            "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.4.tgz",
497
+            "integrity": "sha512-YAm12D3R7/9Mh4jFbYSMnsd6jG++8KxogWgqs7hbdo/86aWjjlIEvL7+QYdVELmAI0InXTpZqFIg5e7aDVWI2Q==",
504
             "dev": true,
498
             "dev": true,
505
             "dependencies": {
499
             "dependencies": {
506
                 "mini-svg-data-uri": "^1.2.3"
500
                 "mini-svg-data-uri": "^1.2.3"
540
             "dev": true
534
             "dev": true
541
         },
535
         },
542
         "node_modules/alpinejs": {
536
         "node_modules/alpinejs": {
543
-            "version": "3.12.2",
544
-            "resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.12.2.tgz",
545
-            "integrity": "sha512-wrUZULm9w6DYwWcUtB/anFLgRaF4riSuPgIJ3gcPUS8st9luAJnAxoIgro/Al97d2McSSz/JypWg/NlmKFIJJA==",
537
+            "version": "3.12.3",
538
+            "resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.12.3.tgz",
539
+            "integrity": "sha512-fLz2dfYQ3xCk7Ip8LiIpV2W+9brUyex2TAE7Z0BCvZdUDklJE+n+a8gCgLWzfZ0GzZNZu7HUP8Z0z6Xbm6fsSA==",
546
             "dev": true,
540
             "dev": true,
547
             "dependencies": {
541
             "dependencies": {
548
                 "@vue/reactivity": "~3.1.1"
542
                 "@vue/reactivity": "~3.1.1"
661
             }
655
             }
662
         },
656
         },
663
         "node_modules/browserslist": {
657
         "node_modules/browserslist": {
664
-            "version": "4.21.9",
665
-            "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.9.tgz",
666
-            "integrity": "sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg==",
658
+            "version": "4.21.10",
659
+            "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.10.tgz",
660
+            "integrity": "sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==",
667
             "dev": true,
661
             "dev": true,
668
             "funding": [
662
             "funding": [
669
                 {
663
                 {
680
                 }
674
                 }
681
             ],
675
             ],
682
             "dependencies": {
676
             "dependencies": {
683
-                "caniuse-lite": "^1.0.30001503",
684
-                "electron-to-chromium": "^1.4.431",
685
-                "node-releases": "^2.0.12",
677
+                "caniuse-lite": "^1.0.30001517",
678
+                "electron-to-chromium": "^1.4.477",
679
+                "node-releases": "^2.0.13",
686
                 "update-browserslist-db": "^1.0.11"
680
                 "update-browserslist-db": "^1.0.11"
687
             },
681
             },
688
             "bin": {
682
             "bin": {
702
             }
696
             }
703
         },
697
         },
704
         "node_modules/caniuse-lite": {
698
         "node_modules/caniuse-lite": {
705
-            "version": "1.0.30001509",
706
-            "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001509.tgz",
707
-            "integrity": "sha512-2uDDk+TRiTX5hMcUYT/7CSyzMZxjfGu0vAUjS2g0LSD8UoXOv0LtpH4LxGMemsiPq6LCVIUjNwVM0erkOkGCDA==",
699
+            "version": "1.0.30001519",
700
+            "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001519.tgz",
701
+            "integrity": "sha512-0QHgqR+Jv4bxHMp8kZ1Kn8CH55OikjKJ6JmKkZYP1F3D7w+lnFXF70nG5eNfsZS89jadi5Ywy5UCSKLAglIRkg==",
708
             "dev": true,
702
             "dev": true,
709
             "funding": [
703
             "funding": [
710
                 {
704
                 {
821
             "dev": true
815
             "dev": true
822
         },
816
         },
823
         "node_modules/electron-to-chromium": {
817
         "node_modules/electron-to-chromium": {
824
-            "version": "1.4.447",
825
-            "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.447.tgz",
826
-            "integrity": "sha512-sxX0LXh+uL41hSJsujAN86PjhrV/6c79XmpY0TvjZStV6VxIgarf8SRkUoUTuYmFcZQTemsoqo8qXOGw5npWfw==",
818
+            "version": "1.4.490",
819
+            "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.490.tgz",
820
+            "integrity": "sha512-6s7NVJz+sATdYnIwhdshx/N/9O6rvMxmhVoDSDFdj6iA45gHR8EQje70+RYsF4GeB+k0IeNSBnP7yG9ZXJFr7A==",
827
             "dev": true
821
             "dev": true
828
         },
822
         },
829
         "node_modules/esbuild": {
823
         "node_modules/esbuild": {
830
-            "version": "0.17.19",
831
-            "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz",
832
-            "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==",
824
+            "version": "0.18.20",
825
+            "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz",
826
+            "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==",
833
             "dev": true,
827
             "dev": true,
834
             "hasInstallScript": true,
828
             "hasInstallScript": true,
835
             "bin": {
829
             "bin": {
839
                 "node": ">=12"
833
                 "node": ">=12"
840
             },
834
             },
841
             "optionalDependencies": {
835
             "optionalDependencies": {
842
-                "@esbuild/android-arm": "0.17.19",
843
-                "@esbuild/android-arm64": "0.17.19",
844
-                "@esbuild/android-x64": "0.17.19",
845
-                "@esbuild/darwin-arm64": "0.17.19",
846
-                "@esbuild/darwin-x64": "0.17.19",
847
-                "@esbuild/freebsd-arm64": "0.17.19",
848
-                "@esbuild/freebsd-x64": "0.17.19",
849
-                "@esbuild/linux-arm": "0.17.19",
850
-                "@esbuild/linux-arm64": "0.17.19",
851
-                "@esbuild/linux-ia32": "0.17.19",
852
-                "@esbuild/linux-loong64": "0.17.19",
853
-                "@esbuild/linux-mips64el": "0.17.19",
854
-                "@esbuild/linux-ppc64": "0.17.19",
855
-                "@esbuild/linux-riscv64": "0.17.19",
856
-                "@esbuild/linux-s390x": "0.17.19",
857
-                "@esbuild/linux-x64": "0.17.19",
858
-                "@esbuild/netbsd-x64": "0.17.19",
859
-                "@esbuild/openbsd-x64": "0.17.19",
860
-                "@esbuild/sunos-x64": "0.17.19",
861
-                "@esbuild/win32-arm64": "0.17.19",
862
-                "@esbuild/win32-ia32": "0.17.19",
863
-                "@esbuild/win32-x64": "0.17.19"
836
+                "@esbuild/android-arm": "0.18.20",
837
+                "@esbuild/android-arm64": "0.18.20",
838
+                "@esbuild/android-x64": "0.18.20",
839
+                "@esbuild/darwin-arm64": "0.18.20",
840
+                "@esbuild/darwin-x64": "0.18.20",
841
+                "@esbuild/freebsd-arm64": "0.18.20",
842
+                "@esbuild/freebsd-x64": "0.18.20",
843
+                "@esbuild/linux-arm": "0.18.20",
844
+                "@esbuild/linux-arm64": "0.18.20",
845
+                "@esbuild/linux-ia32": "0.18.20",
846
+                "@esbuild/linux-loong64": "0.18.20",
847
+                "@esbuild/linux-mips64el": "0.18.20",
848
+                "@esbuild/linux-ppc64": "0.18.20",
849
+                "@esbuild/linux-riscv64": "0.18.20",
850
+                "@esbuild/linux-s390x": "0.18.20",
851
+                "@esbuild/linux-x64": "0.18.20",
852
+                "@esbuild/netbsd-x64": "0.18.20",
853
+                "@esbuild/openbsd-x64": "0.18.20",
854
+                "@esbuild/sunos-x64": "0.18.20",
855
+                "@esbuild/win32-arm64": "0.18.20",
856
+                "@esbuild/win32-ia32": "0.18.20",
857
+                "@esbuild/win32-x64": "0.18.20"
864
             }
858
             }
865
         },
859
         },
866
         "node_modules/escalade": {
860
         "node_modules/escalade": {
873
             }
867
             }
874
         },
868
         },
875
         "node_modules/fast-glob": {
869
         "node_modules/fast-glob": {
876
-            "version": "3.3.0",
877
-            "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.0.tgz",
878
-            "integrity": "sha512-ChDuvbOypPuNjO8yIDf36x7BlZX1smcUMTTcyoIjycexOxd6DFsKsg21qVBzEmr3G7fUKIRy2/psii+CIUt7FA==",
870
+            "version": "3.3.1",
871
+            "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz",
872
+            "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==",
879
             "dev": true,
873
             "dev": true,
880
             "dependencies": {
874
             "dependencies": {
881
                 "@nodelib/fs.stat": "^2.0.2",
875
                 "@nodelib/fs.stat": "^2.0.2",
1076
             }
1070
             }
1077
         },
1071
         },
1078
         "node_modules/is-core-module": {
1072
         "node_modules/is-core-module": {
1079
-            "version": "2.12.1",
1080
-            "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz",
1081
-            "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==",
1073
+            "version": "2.13.0",
1074
+            "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz",
1075
+            "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==",
1082
             "dev": true,
1076
             "dev": true,
1083
             "dependencies": {
1077
             "dependencies": {
1084
                 "has": "^1.0.3"
1078
                 "has": "^1.0.3"
1118
             }
1112
             }
1119
         },
1113
         },
1120
         "node_modules/jiti": {
1114
         "node_modules/jiti": {
1121
-            "version": "1.18.2",
1122
-            "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.18.2.tgz",
1123
-            "integrity": "sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==",
1115
+            "version": "1.19.1",
1116
+            "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.19.1.tgz",
1117
+            "integrity": "sha512-oVhqoRDaBXf7sjkll95LHVS6Myyyb1zaunVwk4Z0+WPSW4gjS0pl01zYKHScTuyEhQsFxV5L4DR5r+YqSyqyyg==",
1124
             "dev": true,
1118
             "dev": true,
1125
             "bin": {
1119
             "bin": {
1126
                 "jiti": "bin/jiti.js"
1120
                 "jiti": "bin/jiti.js"
1269
             }
1263
             }
1270
         },
1264
         },
1271
         "node_modules/node-releases": {
1265
         "node_modules/node-releases": {
1272
-            "version": "2.0.12",
1273
-            "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.12.tgz",
1274
-            "integrity": "sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ==",
1266
+            "version": "2.0.13",
1267
+            "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz",
1268
+            "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==",
1275
             "dev": true
1269
             "dev": true
1276
         },
1270
         },
1277
         "node_modules/normalize-path": {
1271
         "node_modules/normalize-path": {
1371
             }
1365
             }
1372
         },
1366
         },
1373
         "node_modules/postcss": {
1367
         "node_modules/postcss": {
1374
-            "version": "8.4.24",
1375
-            "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz",
1376
-            "integrity": "sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==",
1368
+            "version": "8.4.27",
1369
+            "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.27.tgz",
1370
+            "integrity": "sha512-gY/ACJtJPSmUFPDCHtX78+01fHa64FaU4zaaWfuh1MhGJISufJAH4cun6k/8fwsHYeK4UQmENQK+tRLCFJE8JQ==",
1377
             "dev": true,
1371
             "dev": true,
1378
             "funding": [
1372
             "funding": [
1379
                 {
1373
                 {
1562
             }
1556
             }
1563
         },
1557
         },
1564
         "node_modules/resolve": {
1558
         "node_modules/resolve": {
1565
-            "version": "1.22.2",
1566
-            "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz",
1567
-            "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==",
1559
+            "version": "1.22.4",
1560
+            "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.4.tgz",
1561
+            "integrity": "sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==",
1568
             "dev": true,
1562
             "dev": true,
1569
             "dependencies": {
1563
             "dependencies": {
1570
-                "is-core-module": "^2.11.0",
1564
+                "is-core-module": "^2.13.0",
1571
                 "path-parse": "^1.0.7",
1565
                 "path-parse": "^1.0.7",
1572
                 "supports-preserve-symlinks-flag": "^1.0.0"
1566
                 "supports-preserve-symlinks-flag": "^1.0.0"
1573
             },
1567
             },
1589
             }
1583
             }
1590
         },
1584
         },
1591
         "node_modules/rollup": {
1585
         "node_modules/rollup": {
1592
-            "version": "3.26.0",
1593
-            "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.26.0.tgz",
1594
-            "integrity": "sha512-YzJH0eunH2hr3knvF3i6IkLO/jTjAEwU4HoMUbQl4//Tnl3ou0e7P5SjxdDr8HQJdeUJShlbEHXrrnEHy1l7Yg==",
1586
+            "version": "3.28.0",
1587
+            "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.28.0.tgz",
1588
+            "integrity": "sha512-d7zhvo1OUY2SXSM6pfNjgD5+d0Nz87CUp4mt8l/GgVP3oBsPwzNvSzyu1me6BSG9JIgWNTVcafIXBIyM8yQ3yw==",
1595
             "dev": true,
1589
             "dev": true,
1596
             "bin": {
1590
             "bin": {
1597
                 "rollup": "dist/bin/rollup"
1591
                 "rollup": "dist/bin/rollup"
1637
             }
1631
             }
1638
         },
1632
         },
1639
         "node_modules/sucrase": {
1633
         "node_modules/sucrase": {
1640
-            "version": "3.32.0",
1641
-            "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.32.0.tgz",
1642
-            "integrity": "sha512-ydQOU34rpSyj2TGyz4D2p8rbktIOZ8QY9s+DGLvFU1i5pWJE8vkpruCjGCMHsdXwnD7JDcS+noSwM/a7zyNFDQ==",
1634
+            "version": "3.34.0",
1635
+            "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.34.0.tgz",
1636
+            "integrity": "sha512-70/LQEZ07TEcxiU2dz51FKaE6hCTWC6vr7FOk3Gr0U60C3shtAN+H+BFr9XlYe5xqf3RA8nrc+VIwzCfnxuXJw==",
1643
             "dev": true,
1637
             "dev": true,
1644
             "dependencies": {
1638
             "dependencies": {
1645
                 "@jridgewell/gen-mapping": "^0.3.2",
1639
                 "@jridgewell/gen-mapping": "^0.3.2",
1677
             "dev": true
1671
             "dev": true
1678
         },
1672
         },
1679
         "node_modules/tailwindcss": {
1673
         "node_modules/tailwindcss": {
1680
-            "version": "3.3.2",
1681
-            "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.2.tgz",
1682
-            "integrity": "sha512-9jPkMiIBXvPc2KywkraqsUfbfj+dHDb+JPWtSJa9MLFdrPyazI7q6WX2sUrm7R9eVR7qqv3Pas7EvQFzxKnI6w==",
1674
+            "version": "3.3.3",
1675
+            "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.3.tgz",
1676
+            "integrity": "sha512-A0KgSkef7eE4Mf+nKJ83i75TMyq8HqY3qmFIJSWy8bNt0v1lG7jUcpGpoTFxAwYcWOphcTBLPPJg+bDfhDf52w==",
1683
             "dev": true,
1677
             "dev": true,
1684
             "dependencies": {
1678
             "dependencies": {
1685
                 "@alloc/quick-lru": "^5.2.0",
1679
                 "@alloc/quick-lru": "^5.2.0",
1702
                 "postcss-load-config": "^4.0.1",
1696
                 "postcss-load-config": "^4.0.1",
1703
                 "postcss-nested": "^6.0.1",
1697
                 "postcss-nested": "^6.0.1",
1704
                 "postcss-selector-parser": "^6.0.11",
1698
                 "postcss-selector-parser": "^6.0.11",
1705
-                "postcss-value-parser": "^4.2.0",
1706
                 "resolve": "^1.22.2",
1699
                 "resolve": "^1.22.2",
1707
                 "sucrase": "^3.32.0"
1700
                 "sucrase": "^3.32.0"
1708
             },
1701
             },
1812
             "dev": true
1805
             "dev": true
1813
         },
1806
         },
1814
         "node_modules/vite": {
1807
         "node_modules/vite": {
1815
-            "version": "4.3.9",
1816
-            "resolved": "https://registry.npmjs.org/vite/-/vite-4.3.9.tgz",
1817
-            "integrity": "sha512-qsTNZjO9NoJNW7KnOrgYwczm0WctJ8m/yqYAMAK9Lxt4SoySUfS5S8ia9K7JHpa3KEeMfyF8LoJ3c5NeBJy6pg==",
1808
+            "version": "4.4.9",
1809
+            "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.9.tgz",
1810
+            "integrity": "sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==",
1818
             "dev": true,
1811
             "dev": true,
1819
             "dependencies": {
1812
             "dependencies": {
1820
-                "esbuild": "^0.17.5",
1821
-                "postcss": "^8.4.23",
1822
-                "rollup": "^3.21.0"
1813
+                "esbuild": "^0.18.10",
1814
+                "postcss": "^8.4.27",
1815
+                "rollup": "^3.27.1"
1823
             },
1816
             },
1824
             "bin": {
1817
             "bin": {
1825
                 "vite": "bin/vite.js"
1818
                 "vite": "bin/vite.js"
1827
             "engines": {
1820
             "engines": {
1828
                 "node": "^14.18.0 || >=16.0.0"
1821
                 "node": "^14.18.0 || >=16.0.0"
1829
             },
1822
             },
1823
+            "funding": {
1824
+                "url": "https://github.com/vitejs/vite?sponsor=1"
1825
+            },
1830
             "optionalDependencies": {
1826
             "optionalDependencies": {
1831
                 "fsevents": "~2.3.2"
1827
                 "fsevents": "~2.3.2"
1832
             },
1828
             },
1833
             "peerDependencies": {
1829
             "peerDependencies": {
1834
                 "@types/node": ">= 14",
1830
                 "@types/node": ">= 14",
1835
                 "less": "*",
1831
                 "less": "*",
1832
+                "lightningcss": "^1.21.0",
1836
                 "sass": "*",
1833
                 "sass": "*",
1837
                 "stylus": "*",
1834
                 "stylus": "*",
1838
                 "sugarss": "*",
1835
                 "sugarss": "*",
1845
                 "less": {
1842
                 "less": {
1846
                     "optional": true
1843
                     "optional": true
1847
                 },
1844
                 },
1845
+                "lightningcss": {
1846
+                    "optional": true
1847
+                },
1848
                 "sass": {
1848
                 "sass": {
1849
                     "optional": true
1849
                     "optional": true
1850
                 },
1850
                 },

+ 21
- 0
resources/css/filament.css 查看文件

30
     @apply !shadow-none bg-moonlight;
30
     @apply !shadow-none bg-moonlight;
31
 }
31
 }
32
 
32
 
33
+[type="text"]:disabled,
34
+[type="email"]:disabled,
35
+[type="url"]:disabled,
36
+[type="password"]:disabled,
37
+[type="number"]:disabled,
38
+[type="date"]:disabled,
39
+[type="datetime-local"]:disabled,
40
+[type="month"]:disabled,
41
+[type="search"]:disabled,
42
+[type="tel"]:disabled,
43
+[type="time"]:disabled,
44
+[type="week"]:disabled,
45
+[multiple]:disabled,
46
+textarea:disabled,
47
+select:disabled,
48
+[type="checkbox"]:disabled,
49
+[type="radio"]:disabled,
50
+.filament-forms-repeater-component *:disabled {
51
+    @apply bg-gray-100 dark:bg-gray-700 text-gray-400 border-gray-300 opacity-[0.7];
52
+}
53
+
33
 div.p-2.space-y-2.bg-white.rounded-xl {
54
 div.p-2.space-y-2.bg-white.rounded-xl {
34
     @apply bg-moonlight dark:!bg-gray-800;
55
     @apply bg-moonlight dark:!bg-gray-800;
35
 }
56
 }

+ 129
- 0
resources/views/components/invoice-layouts/default.blade.php 查看文件

1
+@php
2
+    $data = $this->form->getState();
3
+    $viewModel = new \App\View\Models\InvoiceViewModel($this->invoice, $data);
4
+    $viewSpecial = $viewModel->buildViewData();
5
+    extract($viewSpecial);
6
+@endphp
7
+
8
+<div class="print-template flex justify-center p-6">
9
+    <div class="paper bg-white dark:bg-gray-900 p-8 rounded-lg shadow-[0_0_10px_rgba(0,0,0,0.1)] w-[612px] h-[791px]">
10
+
11
+        <!-- Header: Logo on the left and Company details on the right -->
12
+        <div class="flex mb-4">
13
+            <div class="w-2/5">
14
+                @if($document_logo)
15
+                    <div class="text-left">
16
+                        <img src="{{ \Illuminate\Support\Facades\URL::asset($document_logo) }}" alt="logo" style="width: 120px; height: auto">
17
+                    </div>
18
+                @endif
19
+            </div>
20
+
21
+            <!-- Company Details -->
22
+            <div class="w-3/5">
23
+                <div class="text-xs text-gray-600 dark:text-gray-200 text-right space-y-1">
24
+                    <h2 class="text-xl font-bold text-gray-800 dark:text-white">{{ $company_name }}</h2>
25
+                    @if($company_address && $company_city && $company_state && $company_zip)
26
+                        <p>{{ $company_address }}</p>
27
+                        <p>{{ $company_city }}, {{ $company_state }} {{ $company_zip }}</p>
28
+                    @endif
29
+                </div>
30
+            </div>
31
+        </div>
32
+
33
+        <!-- Border Line -->
34
+        <div class="border-b-2 my-4" style="border-color: {{ $accent_color }}"></div>
35
+
36
+        <!-- Invoice Details -->
37
+        <div class="flex mb-4">
38
+            <div class="w-2/5">
39
+                <div class="text-left">
40
+                    <h1 class="text-3xl font-semibold text-gray-800 dark:text-white">{{ $title }}</h1>
41
+                    @if ($subheading)
42
+                        <p class="text-sm text-gray-600 dark:text-gray-100">{{ $subheading }}</p>
43
+                    @endif
44
+                </div>
45
+            </div>
46
+
47
+            <div class="w-3/5">
48
+                <div class="text-right">
49
+                    <p>
50
+                        <span class="text-xs font-semibold text-gray-500 dark:text-gray-100">No: </span>
51
+                        <span class="text-xs text-gray-700 dark:text-white">{{ $invoice_number }}</span>
52
+                    </p>
53
+                    <p>
54
+                        <span class="text-xs font-semibold text-gray-500 dark:text-gray-100">Date: </span>
55
+                        <span class="text-xs text-gray-500 dark:text-white">{{ $invoice_date }}</span>
56
+                    </p>
57
+                    <p>
58
+                        <span class="text-xs font-semibold text-gray-500 dark:text-gray-100">Due Date: </span>
59
+                        <span class="text-xs text-gray-500 dark:text-white">{{ $invoice_due_date }}</span>
60
+                    </p>
61
+                </div>
62
+            </div>
63
+        </div>
64
+
65
+        <!-- Billing Details -->
66
+        <div class="text-xs text-gray-600 dark:text-gray-200 mb-4">
67
+            <h3 class="text-base font-semibold text-gray-600 dark:text-gray-200 mb-2">BILL TO</h3>
68
+            <p class="text-sm text-gray-800 dark:text-white font-semibold">John Doe</p>
69
+            <p>123 Main Street</p>
70
+            <p>New York, NY 10001</p>
71
+        </div>
72
+
73
+        <!-- Line Items Table -->
74
+        <div class="mb-4">
75
+            <table class="w-full border-collapse text-sm">
76
+                <thead>
77
+                    <tr style="color: {{ $accent_color }}">
78
+                        <th class="text-left p-2 w-1/2">{{ $item_column }}</th>
79
+                        <th class="text-center p-2 w-1/6">{{ $unit_column }}</th>
80
+                        <th class="text-center p-2 w-1/6">{{ $price_column }}</th>
81
+                        <th class="text-center p-2 w-1/6">{{ $amount_column }}</th>
82
+                    </tr>
83
+                </thead>
84
+                <tbody>
85
+                    <tr class="bg-gray-200/75 dark:bg-gray-800">
86
+                        <td class="p-2">Item 1</td>
87
+                        <td class="p-2 text-center">2</td>
88
+                        <td class="p-2 text-center">$150.00</td>
89
+                        <td class="p-2 text-center">$300.00</td>
90
+                    </tr>
91
+                    <tr>
92
+                        <td class="p-2">Item 2</td>
93
+                        <td class="p-2 text-center">3</td>
94
+                        <td class="p-2 text-center">$200.00</td>
95
+                        <td class="p-2 text-center">$600.00</td>
96
+                    </tr>
97
+                    <tr class="bg-gray-200/75 dark:bg-gray-800">
98
+                        <td class="p-2">Item 3</td>
99
+                        <td class="p-2 text-center">1</td>
100
+                        <td class="p-2 text-center">$200.00</td>
101
+                        <td class="p-2 text-center">$200.00</td>
102
+                    </tr>
103
+                </tbody>
104
+            </table>
105
+        </div>
106
+
107
+        <!-- Total Amount -->
108
+        <div class="text-right mb-8">
109
+            <p class="text-sm text-gray-600 dark:text-gray-200">Subtotal: $1100.00</p>
110
+            <p class="text-sm text-gray-600 dark:text-gray-200">Tax: $110.00</p>
111
+            <p class="text-lg font-semibold text-gray-800 dark:text-white">Total: $1210.00</p>
112
+        </div>
113
+
114
+        <!-- Footer Notes -->
115
+        <div class="pt-6 text-gray-600 dark:text-gray-300 text-xs">
116
+            <p>{{ $footer }}</p>
117
+            <div class="mt-2 border-t-2 border-gray-300 py-2">
118
+                <h4 class="font-semibold text-gray-700 dark:text-gray-100 mb-2">Terms & Conditions:</h4>
119
+                <p>{{ $terms }}</p>
120
+            </div>
121
+        </div>
122
+    </div>
123
+</div>
124
+
125
+
126
+
127
+
128
+
129
+

+ 164
- 0
resources/views/components/invoice-layouts/modern.blade.php 查看文件

1
+@php
2
+    $data = $this->form->getState();
3
+    $viewModel = new \App\View\Models\InvoiceViewModel($this->invoice, $data);
4
+    $viewSpecial = $viewModel->buildViewData();
5
+    extract($viewSpecial);
6
+@endphp
7
+
8
+<div class="print-template flex justify-center p-6">
9
+    <div class="paper bg-white dark:bg-gray-900 p-8 rounded-lg shadow-[0_0_10px_rgba(0,0,0,0.1)] w-[612px] h-[791px]">
10
+
11
+        <!-- Colored Header with Logo -->
12
+        <div class="flex">
13
+            <div class="text-white py-3 flex items-start justify-start bg-gray-800" style="height: 80px; width: 85%;">
14
+                @if($document_logo)
15
+                    <div class="text-left">
16
+                        <img src="{{ \Illuminate\Support\Facades\URL::asset($document_logo) }}" alt="logo" style="width: 120px; height: auto">
17
+                    </div>
18
+                @endif
19
+            </div>
20
+
21
+            <!-- Ribbon Container -->
22
+            <div class="text-white flex flex-col justify-end p-2" style="background: {{ $accent_color }}; width: 30%; height: 120px; margin-left: -15%;">
23
+                @if($title)
24
+                    <div class="text-center align-bottom">
25
+                        <h1 class="text-3xl font-bold">{{ $title }}</h1>
26
+                    </div>
27
+                @endif
28
+            </div>
29
+        </div>
30
+
31
+        <!-- Company Details -->
32
+        <div class="flex justify-between">
33
+            <div class="text-xs text-gray-600 dark:text-gray-200 space-y-1">
34
+                <h2 class="text-xl font-bold text-gray-800 dark:text-white">{{ $company_name }}</h2>
35
+                @if($company_address && $company_city && $company_state && $company_zip)
36
+                    <p>{{ $company_address }}</p>
37
+                    <p>{{ $company_city }}, {{ $company_state }} {{ $company_zip }}</p>
38
+                @endif
39
+            </div>
40
+
41
+            <table class="mt-6" style="width: 35%; height: 100px">
42
+                <thead class="border-b-2" style="border-color: {{ $accent_color }}">
43
+                <tr class="p-1">
44
+                    <th class="text-xs text text-gray-500 text-right">Total Due</th>
45
+                    <th class="text-xs text-gray-500 text-left">:</th>
46
+                    <th class="text-sm font-semibold text-gray-800 dark:text-white text-right">USD $1100.00</th>
47
+                </tr>
48
+                </thead>
49
+                <tr>
50
+                    <td colspan="3" class="py-1"></td>
51
+                </tr>
52
+                <tbody class="text-xs text-gray-500 dark:text-white">
53
+                <tr class="p-1">
54
+                    <td class="text-right">Invoice No</td>
55
+                    <td class="text-left">:</td>
56
+                    <td class="text-right">{{ $invoice_number }}</td>
57
+                </tr>
58
+                <tr class="p-1">
59
+                    <td class="text-right">Invoice Date</td>
60
+                    <td class="text-left">:</td>
61
+                    <td class="text-right">{{ $invoice_date }}</td>
62
+                </tr>
63
+                <tr class="p-1">
64
+                    <td class="text-right">Invoice Due</td>
65
+                    <td class="text-left">:</td>
66
+                    <td class="text-right">{{ $invoice_due_date }}</td>
67
+                </tr>
68
+                </tbody>
69
+            </table>
70
+        </div>
71
+
72
+        <!-- Billing Details -->
73
+        <div class="text-xs text-gray-600 dark:text-gray-200 mb-4">
74
+            <h3 class="text-base font-semibold text-gray-600 dark:text-gray-200 tracking-tight mb-2">BILL TO</h3>
75
+            <p class="text-lg font-semibold" style="color: {{ $accent_color }}">John Doe</p>
76
+            <p>123 Main Street</p>
77
+            <p>New York, NY 10001</p>
78
+        </div>
79
+
80
+        <!-- Line Items Table -->
81
+        <div class="mb-8">
82
+            <table class="w-full border-collapse text-sm">
83
+                <thead style="background: {{ $accent_color }};">
84
+                <tr class="text-white">
85
+                    <th class="text-left p-2 w-1/12">No</th>
86
+                    <th class="text-left p-2 w-7/12">{{ $item_column }}</th>
87
+                    <th class="text-left p-2 w-1/6">{{ $unit_column }}</th>
88
+                    <th class="text-left p-2 w-1/6">{{ $price_column }}</th>
89
+                    <th class="text-left p-2 w-1/6">{{ $amount_column }}</th>
90
+                </tr>
91
+                </thead>
92
+                <tbody class="text-xs">
93
+                <tr class="border-b border-gray-300">
94
+                    <td class="p-2 bg-gray-100 dark:bg-gray-800">01</td>
95
+                    <td class="p-2">Item 1</td>
96
+                    <td class="p-2 bg-gray-100 dark:bg-gray-800">2</td>
97
+                    <td class="p-2">$150.00</td>
98
+                    <td class="p-2 bg-gray-100 dark:bg-gray-800">$300.00</td>
99
+                </tr>
100
+                <tr class="border-b border-gray-300">
101
+                    <td class="p-2 bg-gray-100 dark:bg-gray-800">02</td>
102
+                    <td class="p-2">Item 2</td>
103
+                    <td class="p-2 bg-gray-100 dark:bg-gray-800">3</td>
104
+                    <td class="p-2">$200.00</td>
105
+                    <td class="p-2 bg-gray-100 dark:bg-gray-800">$600.00</td>
106
+                </tr>
107
+                <tr class="border-b border-gray-300">
108
+                    <td class="p-2 bg-gray-100 dark:bg-gray-800">03</td>
109
+                    <td class="p-2">Item 3</td>
110
+                    <td class="p-2 bg-gray-100 dark:bg-gray-800">1</td>
111
+                    <td class="p-2">$180.00</td>
112
+                    <td class="p-2 bg-gray-100 dark:bg-gray-800">$180.00</td>
113
+                </tr>
114
+                <tr>
115
+                    <td class="p-2"></td>
116
+                    <td class="p-2"></td>
117
+                    <td class="p-2"></td>
118
+                    <td class="p-2">Subtotal:</td>
119
+                    <td class="p-2 bg-gray-100 dark:bg-gray-800">$1000.00</td>
120
+                </tr>
121
+                <tr>
122
+                    <td class="p-2"></td>
123
+                    <td class="p-2"></td>
124
+                    <td class="p-2"></td>
125
+                    <td class="p-2">Tax:</td>
126
+                    <td class="p-2 bg-gray-100 dark:bg-gray-800">$100.00</td>
127
+                </tr>
128
+                <tr>
129
+                    <td class="p-2"></td>
130
+                    <td class="p-2"></td>
131
+                    <td class="p-2"></td>
132
+                    <td class="p-2">Total:</td>
133
+                    <td class="p-2 bg-gray-100 dark:bg-gray-800">$1100.00</td>
134
+                </tr>
135
+                </tbody>
136
+            </table>
137
+        </div>
138
+
139
+        <!-- Footer Notes -->
140
+        <div class="text-gray-600 dark:text-gray-300 text-xs">
141
+            <h4 class="font-semibold text-gray-700 dark:text-gray-100 mb-2" style="color: {{ $accent_color }}">Terms & Conditions:</h4>
142
+            <div class="flex mt-2 justify-between py-2 border-t-2 border-gray-300">
143
+                <div class="w-1/2">
144
+                    <p>{{ $terms }}</p>
145
+                </div>
146
+                <div class="w-1/2 text-right">
147
+                    <p>{{ $footer }}</p>
148
+                </div>
149
+            </div>
150
+        </div>
151
+    </div>
152
+</div>
153
+
154
+
155
+
156
+
157
+
158
+
159
+
160
+
161
+
162
+
163
+
164
+

+ 3
- 0
resources/views/filament/pages/company-details.blade.php 查看文件

1
+<x-filament::page>
2
+    @livewire('company-details', compact('company'))
3
+</x-filament::page>

+ 1
- 1
resources/views/filament/pages/default-setting.blade.php 查看文件

1
 <x-filament::page>
1
 <x-filament::page>
2
-    @livewire('default-setting')
2
+    @livewire('default-setting', compact('company'))
3
 </x-filament::page>
3
 </x-filament::page>

+ 1
- 1
resources/views/filament/pages/invoice.blade.php 查看文件

1
 <x-filament::page>
1
 <x-filament::page>
2
-    @livewire('invoice')
2
+    @livewire('invoice', compact('company'))
3
 </x-filament::page>
3
 </x-filament::page>

+ 9
- 0
resources/views/livewire/company-details.blade.php 查看文件

1
+<x-filament::form wire:submit.prevent="save">
2
+    {{ $this->form }}
3
+
4
+    <div class="flex flex-wrap items-center gap-4 justify-start">
5
+        <x-filament::button type="submit" tag="button" wire:target="save">
6
+            {{ __('Save Changes') }}
7
+        </x-filament::button>
8
+    </div>
9
+</x-filament::form>

+ 8
- 12
resources/views/livewire/default-setting.blade.php 查看文件

1
-<div>
2
-    <form wire:submit.prevent="save">
3
-        {{ $this->form }}
1
+<x-filament::form wire:submit.prevent="save">
2
+    {{ $this->form }}
4
 
3
 
5
-        <div class="mt-6">
6
-            <div class="flex flex-wrap items-center gap-4 justify-start">
7
-                <x-filament::button type="submit">
8
-                    {{ __('Save Changes') }}
9
-                </x-filament::button>
10
-            </div>
11
-        </div>
12
-    </form>
13
-</div>
4
+    <div class="flex flex-wrap items-center gap-4 justify-start">
5
+        <x-filament::button type="submit" tag="button" wire:target="save">
6
+            {{ __('Save Changes') }}
7
+        </x-filament::button>
8
+    </div>
9
+</x-filament::form>

+ 8
- 12
resources/views/livewire/invoice.blade.php 查看文件

1
-<div>
2
-    <form wire:submit.prevent="save">
3
-        {{ $this->form }}
1
+<x-filament::form wire:submit.prevent="save">
2
+    {{ $this->form }}
4
 
3
 
5
-        <div class="mt-6">
6
-            <div class="flex flex-wrap items-center gap-4 justify-start">
7
-                <x-filament::button type="submit">
8
-                    {{ __('Save Changes') }}
9
-                </x-filament::button>
10
-            </div>
11
-        </div>
12
-    </form>
13
-</div>
4
+    <div class="flex flex-wrap items-center gap-4 justify-start">
5
+        <x-filament::button type="submit" tag="button" wire:target="save">
6
+            {{ __('Save Changes') }}
7
+        </x-filament::button>
8
+    </div>
9
+</x-filament::form>

+ 2
- 2
tailwind.config.js 查看文件

16
         primary: {
16
         primary: {
17
           50: '#F2F3FA',
17
           50: '#F2F3FA',
18
           100: '#D8DBF5',
18
           100: '#D8DBF5',
19
-          200: '#B4B9EB',
20
-          300: '#9297E1',
19
+          200: '#d3e8f0',
20
+          300: '#b8d2e6',
21
           400: '#7075D7',
21
           400: '#7075D7',
22
           500: '#454DC8',
22
           500: '#454DC8',
23
           600: '#27285C',
23
           600: '#27285C',

Loading…
取消
儲存