浏览代码

v2: Filament v3 support

3.x
wallo 2 年前
父节点
当前提交
320f587ab9
共有 100 个文件被更改,包括 2883 次插入3534 次删除
  1. 1
    3
      .env.example
  2. 0
    80
      app/Abstracts/Forms/EditFormRecord.php
  3. 5
    7
      app/Actions/FilamentCompanies/CreateNewUser.php
  4. 2
    4
      app/Actions/FilamentCompanies/CreateUserFromProvider.php
  5. 2
    2
      app/Actions/FilamentCompanies/ResolveSocialiteUser.php
  6. 1
    2
      app/Actions/FilamentCompanies/SetUserPassword.php
  7. 3
    5
      app/Actions/FilamentCompanies/UpdateUserPassword.php
  8. 2
    2
      app/Actions/FilamentCompanies/UpdateUserProfileInformation.php
  9. 0
    18
      app/Actions/Fortify/PasswordValidationRules.php
  10. 0
    29
      app/Actions/Fortify/ResetUserPassword.php
  11. 7
    6
      app/Actions/OptionAction/CreateCurrency.php
  12. 35
    0
      app/Casts/MoneyCast.php
  13. 24
    0
      app/Casts/RateCast.php
  14. 14
    0
      app/Casts/TrimLeadingZeroCast.php
  15. 18
    0
      app/Enums/CategoryType.php
  16. 32
    0
      app/Enums/Concerns/Utilities.php
  17. 16
    0
      app/Enums/DiscountComputation.php
  18. 16
    0
      app/Enums/DiscountScope.php
  19. 37
    0
      app/Enums/DiscountType.php
  20. 19
    0
      app/Enums/DocumentAmountColumn.php
  21. 23
    0
      app/Enums/DocumentItemColumn.php
  22. 19
    0
      app/Enums/DocumentPriceColumn.php
  23. 27
    0
      app/Enums/DocumentType.php
  24. 19
    0
      app/Enums/DocumentUnitColumn.php
  25. 30
    0
      app/Enums/EntityType.php
  26. 26
    0
      app/Enums/Font.php
  27. 33
    0
      app/Enums/MaxContentWidth.php
  28. 41
    0
      app/Enums/ModalWidth.php
  29. 44
    0
      app/Enums/PaymentTerms.php
  30. 83
    0
      app/Enums/PrimaryColor.php
  31. 29
    0
      app/Enums/RecordsPerPage.php
  32. 18
    0
      app/Enums/TableSortDirection.php
  33. 17
    0
      app/Enums/TaxComputation.php
  34. 16
    0
      app/Enums/TaxScope.php
  35. 37
    0
      app/Enums/TaxType.php
  36. 19
    0
      app/Enums/Template.php
  37. 23
    0
      app/Events/CompanyDefaultEvent.php
  38. 29
    0
      app/Events/CompanyDefaultUpdated.php
  39. 8
    0
      app/Events/UpdateCompanyDefault.php
  40. 77
    0
      app/Filament/Company/Pages/CreateCompany.php
  41. 282
    0
      app/Filament/Company/Pages/Setting/Appearance.php
  42. 251
    0
      app/Filament/Company/Pages/Setting/CompanyDefault.php
  43. 284
    0
      app/Filament/Company/Pages/Setting/CompanyProfile.php
  44. 334
    0
      app/Filament/Company/Pages/Setting/Invoice.php
  45. 57
    103
      app/Filament/Company/Resources/Banking/AccountResource.php
  46. 4
    3
      app/Filament/Company/Resources/Banking/AccountResource/Pages/CreateAccount.php
  47. 7
    9
      app/Filament/Company/Resources/Banking/AccountResource/Pages/EditAccount.php
  48. 4
    4
      app/Filament/Company/Resources/Banking/AccountResource/Pages/ListAccounts.php
  49. 155
    0
      app/Filament/Company/Resources/Setting/CategoryResource.php
  50. 4
    5
      app/Filament/Company/Resources/Setting/CategoryResource/Pages/CreateCategory.php
  51. 6
    7
      app/Filament/Company/Resources/Setting/CategoryResource/Pages/EditCategory.php
  52. 19
    0
      app/Filament/Company/Resources/Setting/CategoryResource/Pages/ListCategories.php
  53. 189
    0
      app/Filament/Company/Resources/Setting/CurrencyResource.php
  54. 4
    5
      app/Filament/Company/Resources/Setting/CurrencyResource/Pages/CreateCurrency.php
  55. 6
    7
      app/Filament/Company/Resources/Setting/CurrencyResource/Pages/EditCurrency.php
  56. 4
    4
      app/Filament/Company/Resources/Setting/CurrencyResource/Pages/ListCurrencies.php
  57. 197
    0
      app/Filament/Company/Resources/Setting/DiscountResource.php
  58. 6
    6
      app/Filament/Company/Resources/Setting/DiscountResource/Pages/CreateDiscount.php
  59. 6
    7
      app/Filament/Company/Resources/Setting/DiscountResource/Pages/EditDiscount.php
  60. 4
    4
      app/Filament/Company/Resources/Setting/DiscountResource/Pages/ListDiscounts.php
  61. 191
    0
      app/Filament/Company/Resources/Setting/TaxResource.php
  62. 5
    5
      app/Filament/Company/Resources/Setting/TaxResource/Pages/CreateTax.php
  63. 7
    8
      app/Filament/Company/Resources/Setting/TaxResource/Pages/EditTax.php
  64. 4
    4
      app/Filament/Company/Resources/Setting/TaxResource/Pages/ListTaxes.php
  65. 0
    39
      app/Filament/Pages/Companies.php
  66. 0
    51
      app/Filament/Pages/CompanyDetails.php
  67. 0
    10
      app/Filament/Pages/Dashboard.php
  68. 0
    51
      app/Filament/Pages/DefaultSetting.php
  69. 0
    38
      app/Filament/Pages/Employees.php
  70. 0
    51
      app/Filament/Pages/Invoice.php
  71. 0
    36
      app/Filament/Pages/Users.php
  72. 0
    148
      app/Filament/Pages/Widgets/Companies/Charts/CompanyStatsOverview.php
  73. 0
    154
      app/Filament/Pages/Widgets/Companies/Charts/CumulativeGrowth.php
  74. 0
    143
      app/Filament/Pages/Widgets/Companies/Charts/CumulativeTotal.php
  75. 0
    72
      app/Filament/Pages/Widgets/Companies/Tables/Companies.php
  76. 0
    153
      app/Filament/Pages/Widgets/Employees/Charts/CumulativeGrowth.php
  77. 0
    163
      app/Filament/Pages/Widgets/Employees/Charts/CumulativeRoles.php
  78. 0
    73
      app/Filament/Pages/Widgets/Employees/Tables/Employees.php
  79. 0
    45
      app/Filament/Pages/Widgets/Users/Tables/Users.php
  80. 0
    155
      app/Filament/Resources/CategoryResource.php
  81. 0
    23
      app/Filament/Resources/CategoryResource/Pages/ListCategories.php
  82. 0
    251
      app/Filament/Resources/CurrencyResource.php
  83. 0
    253
      app/Filament/Resources/CustomerResource.php
  84. 0
    26
      app/Filament/Resources/CustomerResource/Pages/CreateCustomer.php
  85. 0
    24
      app/Filament/Resources/CustomerResource/Pages/EditCustomer.php
  86. 0
    19
      app/Filament/Resources/CustomerResource/Pages/ListCustomers.php
  87. 0
    202
      app/Filament/Resources/DiscountResource.php
  88. 0
    152
      app/Filament/Resources/InvoiceResource.php
  89. 0
    27
      app/Filament/Resources/InvoiceResource/Pages/CreateInvoice.php
  90. 0
    24
      app/Filament/Resources/InvoiceResource/Pages/EditInvoice.php
  91. 0
    19
      app/Filament/Resources/InvoiceResource/Pages/ListInvoices.php
  92. 0
    49
      app/Filament/Resources/InvoiceResource/RelationManagers/DocumentItemsRelationManager.php
  93. 0
    208
      app/Filament/Resources/TaxResource.php
  94. 0
    26
      app/Forms/Components/Invoice.php
  95. 1
    0
      app/Http/Kernel.php
  96. 0
    13
      app/Http/Livewire/Bill.php
  97. 0
    89
      app/Http/Livewire/CompanyDetails.php
  98. 0
    161
      app/Http/Livewire/DefaultSetting.php
  99. 0
    247
      app/Http/Livewire/Invoice.php
  100. 0
    0
      app/Http/Middleware/ApplyCurrentCompanyScope.php

+ 1
- 3
.env.example 查看文件

@@ -51,11 +51,9 @@ PUSHER_PORT=443
51 51
 PUSHER_SCHEME=https
52 52
 PUSHER_APP_CLUSTER=mt1
53 53
 
54
+VITE_APP_NAME="${APP_NAME}"
54 55
 VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
55 56
 VITE_PUSHER_HOST="${PUSHER_HOST}"
56 57
 VITE_PUSHER_PORT="${PUSHER_PORT}"
57 58
 VITE_PUSHER_SCHEME="${PUSHER_SCHEME}"
58 59
 VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
59
-
60
-GITHUB_CLIENT_ID=
61
-GITHUB_CLIENT_SECRET=

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

@@ -1,80 +0,0 @@
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
-}

app/Actions/Fortify/CreateNewUser.php → app/Actions/FilamentCompanies/CreateNewUser.php 查看文件

@@ -1,19 +1,17 @@
1 1
 <?php
2 2
 
3
-namespace App\Actions\Fortify;
3
+namespace App\Actions\FilamentCompanies;
4 4
 
5 5
 use App\Models\Company;
6 6
 use App\Models\User;
7 7
 use Illuminate\Support\Facades\DB;
8 8
 use Illuminate\Support\Facades\Hash;
9 9
 use Illuminate\Support\Facades\Validator;
10
-use Laravel\Fortify\Contracts\CreatesNewUsers;
11
-use Wallo\FilamentCompanies\FilamentCompanies;
10
+use Wallo\FilamentCompanies\Contracts\CreatesNewUsers;
11
+use Wallo\FilamentCompanies\Features;
12 12
 
13 13
 class CreateNewUser implements CreatesNewUsers
14 14
 {
15
-    use PasswordValidationRules;
16
-
17 15
     /**
18 16
      * Create a newly registered user.
19 17
      *
@@ -24,8 +22,8 @@ class CreateNewUser implements CreatesNewUsers
24 22
         Validator::make($input, [
25 23
             'name' => ['required', 'string', 'max:255'],
26 24
             'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
27
-            'password' => $this->passwordRules(),
28
-            'terms' => FilamentCompanies::hasTermsAndPrivacyPolicyFeature() ? ['accepted', 'required'] : '',
25
+            'password' => ['required', 'string', 'min:8', 'confirmed'],
26
+            'terms' => Features::hasTermsAndPrivacyPolicyFeature() ? ['accepted', 'required'] : '',
29 27
         ])->validate();
30 28
 
31 29
         return DB::transaction(function () use ($input) {

+ 2
- 4
app/Actions/FilamentCompanies/CreateUserFromProvider.php 查看文件

@@ -9,7 +9,6 @@ use Laravel\Socialite\Contracts\User as ProviderUserContract;
9 9
 use Wallo\FilamentCompanies\Contracts\CreatesConnectedAccounts;
10 10
 use Wallo\FilamentCompanies\Contracts\CreatesUserFromProvider;
11 11
 use Wallo\FilamentCompanies\Features;
12
-use Wallo\FilamentCompanies\FilamentCompanies;
13 12
 use Wallo\FilamentCompanies\Socialite;
14 13
 
15 14
 class CreateUserFromProvider implements CreatesUserFromProvider
@@ -54,9 +53,8 @@ class CreateUserFromProvider implements CreatesUserFromProvider
54 53
 
55 54
     private function shouldSetProfilePhoto(ProviderUserContract $providerUser): bool
56 55
     {
57
-        return Features::profilePhotos() &&
58
-            Socialite::hasProviderAvatarsFeature() &&
59
-            FilamentCompanies::managesProfilePhotos() &&
56
+        return Socialite::hasProviderAvatarsFeature() &&
57
+            Features::managesProfilePhotos() &&
60 58
             $providerUser->getAvatar();
61 59
     }
62 60
 

+ 2
- 2
app/Actions/FilamentCompanies/ResolveSocialiteUser.php 查看文件

@@ -5,7 +5,7 @@ namespace App\Actions\FilamentCompanies;
5 5
 use Laravel\Socialite\Contracts\User;
6 6
 use Laravel\Socialite\Facades\Socialite;
7 7
 use Wallo\FilamentCompanies\Contracts\ResolvesSocialiteUsers;
8
-use Wallo\FilamentCompanies\Features;
8
+use Wallo\FilamentCompanies\Socialite as FilamentCompaniesSocialite;
9 9
 
10 10
 class ResolveSocialiteUser implements ResolvesSocialiteUsers
11 11
 {
@@ -16,7 +16,7 @@ class ResolveSocialiteUser implements ResolvesSocialiteUsers
16 16
     {
17 17
         $user = Socialite::driver($provider)->user();
18 18
 
19
-        if (Features::generatesMissingEmails()) {
19
+        if (FilamentCompaniesSocialite::generatesMissingEmails()) {
20 20
             $user->email = $user->getEmail() ?? ("{$user->id}@{$provider}".config('app.domain'));
21 21
         }
22 22
 

+ 1
- 2
app/Actions/FilamentCompanies/SetUserPassword.php 查看文件

@@ -5,7 +5,6 @@ namespace App\Actions\FilamentCompanies;
5 5
 use App\Models\User;
6 6
 use Illuminate\Support\Facades\Hash;
7 7
 use Illuminate\Support\Facades\Validator;
8
-use Laravel\Fortify\Rules\Password;
9 8
 use Wallo\FilamentCompanies\Contracts\SetsUserPasswords;
10 9
 
11 10
 class SetUserPassword implements SetsUserPasswords
@@ -16,7 +15,7 @@ class SetUserPassword implements SetsUserPasswords
16 15
     public function set(User $user, array $input): void
17 16
     {
18 17
         Validator::make($input, [
19
-            'password' => ['required', 'string', new Password, 'confirmed'],
18
+            'password' => ['required', 'string', 'min:8', 'confirmed'],
20 19
         ])->validateWithBag('setPassword');
21 20
 
22 21
         $user->forceFill([

app/Actions/Fortify/UpdateUserPassword.php → app/Actions/FilamentCompanies/UpdateUserPassword.php 查看文件

@@ -1,16 +1,14 @@
1 1
 <?php
2 2
 
3
-namespace App\Actions\Fortify;
3
+namespace App\Actions\FilamentCompanies;
4 4
 
5 5
 use App\Models\User;
6 6
 use Illuminate\Support\Facades\Hash;
7 7
 use Illuminate\Support\Facades\Validator;
8
-use Laravel\Fortify\Contracts\UpdatesUserPasswords;
8
+use Wallo\FilamentCompanies\Contracts\UpdatesUserPasswords;
9 9
 
10 10
 class UpdateUserPassword implements UpdatesUserPasswords
11 11
 {
12
-    use PasswordValidationRules;
13
-
14 12
     /**
15 13
      * Validate and update the user's password.
16 14
      *
@@ -20,7 +18,7 @@ class UpdateUserPassword implements UpdatesUserPasswords
20 18
     {
21 19
         Validator::make($input, [
22 20
             'current_password' => ['required', 'string', 'current_password:web'],
23
-            'password' => $this->passwordRules(),
21
+            'password' => ['required', 'string', 'min:8', 'confirmed'],
24 22
         ], [
25 23
             'current_password.current_password' => __('filament-companies::default.errors.password_does_not_match'),
26 24
         ])->validateWithBag('updatePassword');

app/Actions/Fortify/UpdateUserProfileInformation.php → app/Actions/FilamentCompanies/UpdateUserProfileInformation.php 查看文件

@@ -1,12 +1,12 @@
1 1
 <?php
2 2
 
3
-namespace App\Actions\Fortify;
3
+namespace App\Actions\FilamentCompanies;
4 4
 
5 5
 use App\Models\User;
6 6
 use Illuminate\Contracts\Auth\MustVerifyEmail;
7 7
 use Illuminate\Support\Facades\Validator;
8 8
 use Illuminate\Validation\Rule;
9
-use Laravel\Fortify\Contracts\UpdatesUserProfileInformation;
9
+use Wallo\FilamentCompanies\Contracts\UpdatesUserProfileInformation;
10 10
 
11 11
 class UpdateUserProfileInformation implements UpdatesUserProfileInformation
12 12
 {

+ 0
- 18
app/Actions/Fortify/PasswordValidationRules.php 查看文件

@@ -1,18 +0,0 @@
1
-<?php
2
-
3
-namespace App\Actions\Fortify;
4
-
5
-use Laravel\Fortify\Rules\Password;
6
-
7
-trait PasswordValidationRules
8
-{
9
-    /**
10
-     * Get the validation rules used to validate passwords.
11
-     *
12
-     * @return array<int, \Illuminate\Contracts\Validation\Rule|array|string>
13
-     */
14
-    protected function passwordRules(): array
15
-    {
16
-        return ['required', 'string', new Password, 'confirmed'];
17
-    }
18
-}

+ 0
- 29
app/Actions/Fortify/ResetUserPassword.php 查看文件

@@ -1,29 +0,0 @@
1
-<?php
2
-
3
-namespace App\Actions\Fortify;
4
-
5
-use App\Models\User;
6
-use Illuminate\Support\Facades\Hash;
7
-use Illuminate\Support\Facades\Validator;
8
-use Laravel\Fortify\Contracts\ResetsUserPasswords;
9
-
10
-class ResetUserPassword implements ResetsUserPasswords
11
-{
12
-    use PasswordValidationRules;
13
-
14
-    /**
15
-     * Validate and reset the user's forgotten password.
16
-     *
17
-     * @param  array<string, string>  $input
18
-     */
19
-    public function reset(User $user, array $input): void
20
-    {
21
-        Validator::make($input, [
22
-            'password' => $this->passwordRules(),
23
-        ])->validate();
24
-
25
-        $user->forceFill([
26
-            'password' => Hash::make($input['password']),
27
-        ])->save();
28
-    }
29
-}

+ 7
- 6
app/Actions/OptionAction/CreateCurrency.php 查看文件

@@ -8,19 +8,20 @@ class CreateCurrency
8 8
 {
9 9
     public function create(string $code, string $name, string $rate): Currency
10 10
     {
11
-        $defaultCurrency = Currency::getDefaultCurrency();
11
+        $defaultCurrency = Currency::getDefaultCurrencyCode();
12 12
 
13 13
         $hasDefaultCurrency = $defaultCurrency !== null;
14
+        $currency_code = currency($code);
14 15
 
15 16
         return Currency::create([
16 17
             'name' => $name,
17 18
             'code' => $code,
18 19
             'rate' => $rate,
19
-            'precision' => config("money.{$code}.precision"),
20
-            'symbol' => config("money.{$code}.symbol"),
21
-            'symbol_first' => config("money.{$code}.symbol_first"),
22
-            'decimal_mark' => config("money.{$code}.decimal_mark"),
23
-            'thousands_separator' => config("money.{$code}.thousands_separator"),
20
+            'precision' => $currency_code->getPrecision(),
21
+            'symbol' => $currency_code->getSymbol(),
22
+            'symbol_first' => $currency_code->isSymbolFirst(),
23
+            'decimal_mark' => $currency_code->getDecimalMark(),
24
+            'thousands_separator' => $currency_code->getThousandsSeparator(),
24 25
             'enabled' => !$hasDefaultCurrency,
25 26
         ]);
26 27
     }

+ 35
- 0
app/Casts/MoneyCast.php 查看文件

@@ -0,0 +1,35 @@
1
+<?php
2
+
3
+namespace App\Casts;
4
+
5
+use App\Models\Setting\Currency;
6
+use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
7
+use UnexpectedValueException;
8
+
9
+class MoneyCast implements CastsAttributes
10
+{
11
+    public function get($model, string $key, $value, array $attributes): string
12
+    {
13
+        $currency_code = $model->currency_code;
14
+
15
+        return money($value, $currency_code)->formatSimple();
16
+    }
17
+
18
+    /**
19
+     * @throws UnexpectedValueException
20
+     */
21
+    public function set($model, string $key, $value, array $attributes): int
22
+    {
23
+        if (is_int($value)) {
24
+            return $value;
25
+        }
26
+
27
+        $currency_code = $model->currency_code ?? Currency::getDefaultCurrencyCode();
28
+
29
+        if (!$currency_code) {
30
+            throw new UnexpectedValueException('Currency code is not set');
31
+        }
32
+
33
+        return money($value, $currency_code, true)->getAmount();
34
+    }
35
+}

+ 24
- 0
app/Casts/RateCast.php 查看文件

@@ -0,0 +1,24 @@
1
+<?php
2
+
3
+namespace App\Casts;
4
+
5
+use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
6
+
7
+class RateCast implements CastsAttributes
8
+{
9
+    private const SCALE = 8;
10
+
11
+    public function get($model, string $key, $value, array $attributes): float
12
+    {
13
+        $floatValue = $value / (10 ** self::SCALE);
14
+
15
+        $strValue = rtrim(rtrim(number_format($floatValue, self::SCALE, '.', ''), '0'), '.');
16
+
17
+        return (float) $strValue;
18
+    }
19
+
20
+    public function set($model, string $key, $value, array $attributes): int
21
+    {
22
+        return (int) round($value * (10 ** self::SCALE));
23
+    }
24
+}

+ 14
- 0
app/Casts/TrimLeadingZeroCast.php 查看文件

@@ -0,0 +1,14 @@
1
+<?php
2
+
3
+namespace App\Casts;
4
+
5
+use Illuminate\Contracts\Database\Eloquent\CastsInboundAttributes;
6
+use Illuminate\Database\Eloquent\Model;
7
+
8
+class TrimLeadingZeroCast implements CastsInboundAttributes
9
+{
10
+    public function set(Model $model, string $key, mixed $value, array $attributes): int
11
+    {
12
+        return (int) ltrim($value, '0');
13
+    }
14
+}

+ 18
- 0
app/Enums/CategoryType.php 查看文件

@@ -0,0 +1,18 @@
1
+<?php
2
+
3
+namespace App\Enums;
4
+
5
+use Filament\Support\Contracts\HasLabel;
6
+
7
+enum CategoryType: string implements HasLabel
8
+{
9
+    case Expense = 'expense';
10
+    case Income = 'income';
11
+    case Item = 'item';
12
+    case Other = 'other';
13
+
14
+    public function getLabel(): ?string
15
+    {
16
+        return $this->name;
17
+    }
18
+}

+ 32
- 0
app/Enums/Concerns/Utilities.php 查看文件

@@ -0,0 +1,32 @@
1
+<?php
2
+
3
+namespace App\Enums\Concerns;
4
+
5
+trait Utilities
6
+{
7
+    public static function caseValues(): array
8
+    {
9
+        return array_column(static::cases(), 'value');
10
+    }
11
+
12
+    public static function caseNames(): array
13
+    {
14
+        return array_column(static::cases(), 'name');
15
+    }
16
+
17
+    public static function constantNames(): array
18
+    {
19
+        $allConstants = array_keys((new \ReflectionClass(static::class))->getConstants());
20
+        $caseNames = static::caseNames();
21
+
22
+        return array_values(array_diff($allConstants, $caseNames));
23
+    }
24
+
25
+    public static function constantValues(): array
26
+    {
27
+        $allConstants = array_values((new \ReflectionClass(static::class))->getConstants());
28
+        $caseValues = static::caseValues();
29
+
30
+        return array_values(array_diff_key($allConstants, $caseValues));
31
+    }
32
+}

+ 16
- 0
app/Enums/DiscountComputation.php 查看文件

@@ -0,0 +1,16 @@
1
+<?php
2
+
3
+namespace App\Enums;
4
+
5
+use Filament\Support\Contracts\HasLabel;
6
+
7
+enum DiscountComputation: string implements HasLabel
8
+{
9
+    case Percentage = 'percentage';
10
+    case Fixed = 'fixed';
11
+
12
+    public function getLabel(): ?string
13
+    {
14
+        return $this->name;
15
+    }
16
+}

+ 16
- 0
app/Enums/DiscountScope.php 查看文件

@@ -0,0 +1,16 @@
1
+<?php
2
+
3
+namespace App\Enums;
4
+
5
+use Filament\Support\Contracts\HasLabel;
6
+
7
+enum DiscountScope: string implements HasLabel
8
+{
9
+    case Product = 'product';
10
+    case Service = 'service';
11
+
12
+    public function getLabel(): ?string
13
+    {
14
+        return $this->name;
15
+    }
16
+}

+ 37
- 0
app/Enums/DiscountType.php 查看文件

@@ -0,0 +1,37 @@
1
+<?php
2
+
3
+namespace App\Enums;
4
+
5
+use Filament\Support\Contracts\HasColor;
6
+use Filament\Support\Contracts\HasIcon;
7
+use Filament\Support\Contracts\HasLabel;
8
+
9
+enum DiscountType: string implements HasLabel, HasColor, HasIcon
10
+{
11
+    case Sales = 'sales';
12
+    case Purchase = 'purchase';
13
+    case None = 'none';
14
+
15
+    public function getLabel(): ?string
16
+    {
17
+        return $this->name;
18
+    }
19
+
20
+    public function getColor(): string|array|null
21
+    {
22
+        return match ($this) {
23
+            self::Sales => 'success',
24
+            self::Purchase => 'warning',
25
+            self::None => 'gray',
26
+        };
27
+    }
28
+
29
+    public function getIcon(): ?string
30
+    {
31
+        return match ($this) {
32
+            self::Sales => 'heroicon-o-currency-dollar',
33
+            self::Purchase => 'heroicon-o-shopping-bag',
34
+            self::None => 'heroicon-o-x-circle',
35
+        };
36
+    }
37
+}

+ 19
- 0
app/Enums/DocumentAmountColumn.php 查看文件

@@ -0,0 +1,19 @@
1
+<?php
2
+
3
+namespace App\Enums;
4
+
5
+use Filament\Support\Contracts\HasLabel;
6
+
7
+enum DocumentAmountColumn: string implements HasLabel
8
+{
9
+    case Amount = 'amount';
10
+    case Total = 'total';
11
+    case Other = 'other';
12
+
13
+    public const DEFAULT = self::Amount->value;
14
+
15
+    public function getLabel(): ?string
16
+    {
17
+        return $this->name;
18
+    }
19
+}

+ 23
- 0
app/Enums/DocumentItemColumn.php 查看文件

@@ -0,0 +1,23 @@
1
+<?php
2
+
3
+namespace App\Enums;
4
+
5
+use App\Enums\Concerns\Utilities;
6
+use Filament\Support\Contracts\HasLabel;
7
+
8
+enum DocumentItemColumn: string implements HasLabel
9
+{
10
+    use Utilities;
11
+
12
+    case Items = 'items';
13
+    case Products = 'products';
14
+    case Services = 'services';
15
+    case Other = 'other';
16
+
17
+    public const DEFAULT = self::Items->value;
18
+
19
+    public function getLabel(): ?string
20
+    {
21
+        return $this->name;
22
+    }
23
+}

+ 19
- 0
app/Enums/DocumentPriceColumn.php 查看文件

@@ -0,0 +1,19 @@
1
+<?php
2
+
3
+namespace App\Enums;
4
+
5
+use Filament\Support\Contracts\HasLabel;
6
+
7
+enum DocumentPriceColumn: string implements HasLabel
8
+{
9
+    case Price = 'price';
10
+    case Rate = 'rate';
11
+    case Other = 'other';
12
+
13
+    public const DEFAULT = self::Price->value;
14
+
15
+    public function getLabel(): ?string
16
+    {
17
+        return $this->name;
18
+    }
19
+}

+ 27
- 0
app/Enums/DocumentType.php 查看文件

@@ -0,0 +1,27 @@
1
+<?php
2
+
3
+namespace App\Enums;
4
+
5
+use Filament\Support\Contracts\HasIcon;
6
+use Filament\Support\Contracts\HasLabel;
7
+
8
+enum DocumentType: string implements HasLabel, HasIcon
9
+{
10
+    case Invoice = 'invoice';
11
+    case Bill = 'bill';
12
+
13
+    public const DEFAULT = self::Invoice->value;
14
+
15
+    public function getLabel(): ?string
16
+    {
17
+        return $this->name;
18
+    }
19
+
20
+    public function getIcon(): ?string
21
+    {
22
+        return match ($this->value) {
23
+            self::Invoice->value => 'heroicon-o-document-duplicate',
24
+            self::Bill->value => 'heroicon-o-clipboard-document-list',
25
+        };
26
+    }
27
+}

+ 19
- 0
app/Enums/DocumentUnitColumn.php 查看文件

@@ -0,0 +1,19 @@
1
+<?php
2
+
3
+namespace App\Enums;
4
+
5
+use Filament\Support\Contracts\HasLabel;
6
+
7
+enum DocumentUnitColumn: string implements HasLabel
8
+{
9
+    case Quantity = 'quantity';
10
+    case Hours = 'hours';
11
+    case Other = 'other';
12
+
13
+    public const DEFAULT = self::Quantity->value;
14
+
15
+    public function getLabel(): ?string
16
+    {
17
+        return $this->name;
18
+    }
19
+}

+ 30
- 0
app/Enums/EntityType.php 查看文件

@@ -0,0 +1,30 @@
1
+<?php
2
+
3
+namespace App\Enums;
4
+
5
+use Filament\Support\Contracts\HasLabel;
6
+
7
+enum EntityType: string implements HasLabel
8
+{
9
+    case SoleProprietorship = 'sole_proprietorship';
10
+    case GeneralPartnership = 'general_partnership';
11
+    case LimitedPartnership = 'limited_partnership';
12
+    case LimitedLiabilityPartnership = 'limited_liability_partnership';
13
+    case LimitedLiabilityCompany = 'limited_liability_company';
14
+    case Corporation = 'corporation';
15
+    case Nonprofit = 'nonprofit';
16
+
17
+    public function getLabel(): ?string
18
+    {
19
+        return match ($this) {
20
+            self::SoleProprietorship => 'Sole Proprietorship',
21
+            self::GeneralPartnership => 'General Partnership',
22
+            self::LimitedPartnership => 'Limited Partnership (LP)',
23
+            self::LimitedLiabilityPartnership => 'Limited Liability Partnership (LLP)',
24
+            self::LimitedLiabilityCompany => 'Limited Liability Company (LLC)',
25
+            self::Corporation => 'Corporation',
26
+            self::Nonprofit => 'Nonprofit',
27
+        };
28
+    }
29
+
30
+}

+ 26
- 0
app/Enums/Font.php 查看文件

@@ -0,0 +1,26 @@
1
+<?php
2
+
3
+namespace App\Enums;
4
+
5
+use Filament\Support\Contracts\HasLabel;
6
+
7
+enum Font: string implements HasLabel
8
+{
9
+    case Inter = 'inter';
10
+    case Roboto = 'roboto';
11
+    case OpenSans = 'open_sans';
12
+    case Poppins = 'poppins';
13
+    case NotoSans = 'noto_sans';
14
+    case DMSans = 'dm_sans';
15
+    case Arial = 'arial';
16
+    case Helvetica = 'helvetica';
17
+    case Verdana = 'verdana';
18
+    case Rubik = 'rubik';
19
+
20
+    public const DEFAULT = self::Inter->value;
21
+
22
+    public function getLabel(): ?string
23
+    {
24
+        return ucwords(str_replace('_', ' ', $this->value));
25
+    }
26
+}

+ 33
- 0
app/Enums/MaxContentWidth.php 查看文件

@@ -0,0 +1,33 @@
1
+<?php
2
+
3
+namespace App\Enums;
4
+
5
+use Filament\Support\Contracts\HasLabel;
6
+
7
+enum MaxContentWidth: string implements HasLabel
8
+{
9
+    case FOUR_XL = '4xl';
10
+    case FIVE_XL = '5xl';
11
+    case SIX_XL = '6xl';
12
+    case SEVEN_XL = '7xl';
13
+    case SCREEN_LG = 'screen-lg';
14
+    case SCREEN_XL = 'screen-xl';
15
+    case SCREEN_2XL = 'screen-2xl';
16
+    case FULL = 'full';
17
+
18
+    public const DEFAULT = self::SEVEN_XL->value;
19
+
20
+    public function getLabel(): ?string
21
+    {
22
+        return match ($this) {
23
+            self::FOUR_XL => '4X Large',
24
+            self::FIVE_XL => '5X Large',
25
+            self::SIX_XL => '6X Large',
26
+            self::SEVEN_XL => '7X Large',
27
+            self::SCREEN_LG => 'Screen Large',
28
+            self::SCREEN_XL => 'Screen Extra Large',
29
+            self::SCREEN_2XL => 'Screen 2X Large',
30
+            self::FULL => 'Full',
31
+        };
32
+    }
33
+}

+ 41
- 0
app/Enums/ModalWidth.php 查看文件

@@ -0,0 +1,41 @@
1
+<?php
2
+
3
+namespace App\Enums;
4
+
5
+use Filament\Support\Contracts\HasLabel;
6
+
7
+enum ModalWidth: string implements HasLabel
8
+{
9
+    case XS = 'xs';
10
+    case SM = 'sm';
11
+    case MD = 'md';
12
+    case LG = 'lg';
13
+    case XL = 'xl';
14
+    case TWO_XL = '2xl';
15
+    case THREE_XL = '3xl';
16
+    case FOUR_XL = '4xl';
17
+    case FIVE_XL = '5xl';
18
+    case SIX_XL = '6xl';
19
+    case SEVEN_XL = '7xl';
20
+    case SCREEN = 'screen';
21
+
22
+    public const DEFAULT = self::MD->value;
23
+
24
+    public function getLabel(): ?string
25
+    {
26
+        return match ($this) {
27
+            self::XS => 'Extra Small',
28
+            self::SM => 'Small',
29
+            self::MD => 'Medium',
30
+            self::LG => 'Large',
31
+            self::XL => 'Extra Large',
32
+            self::TWO_XL => '2X Large',
33
+            self::THREE_XL => '3X Large',
34
+            self::FOUR_XL => '4X Large',
35
+            self::FIVE_XL => '5X Large',
36
+            self::SIX_XL => '6X Large',
37
+            self::SEVEN_XL => '7X Large',
38
+            self::SCREEN => 'Screen',
39
+        };
40
+    }
41
+}

+ 44
- 0
app/Enums/PaymentTerms.php 查看文件

@@ -0,0 +1,44 @@
1
+<?php
2
+
3
+namespace App\Enums;
4
+
5
+use Filament\Support\Contracts\HasLabel;
6
+
7
+enum PaymentTerms: string implements HasLabel
8
+{
9
+    case DueOnReceipt = 'due_on_receipt';
10
+    case Net7 = 'net_7';
11
+    case Net10 = 'net_10';
12
+    case Net15 = 'net_15';
13
+    case Net30 = 'net_30';
14
+    case Net60 = 'net_60';
15
+    case Net90 = 'net_90';
16
+
17
+    public const DEFAULT = self::DueOnReceipt->value;
18
+
19
+    public function getLabel(): ?string
20
+    {
21
+        return match ($this) {
22
+            self::DueOnReceipt => 'Due on Receipt',
23
+            self::Net7 => 'Net 7',
24
+            self::Net10 => 'Net 10',
25
+            self::Net15 => 'Net 15',
26
+            self::Net30 => 'Net 30',
27
+            self::Net60 => 'Net 60',
28
+            self::Net90 => 'Net 90',
29
+        };
30
+    }
31
+
32
+    public function getDays(): int
33
+    {
34
+        return match ($this) {
35
+            self::DueOnReceipt => 0,
36
+            self::Net7 => 7,
37
+            self::Net10 => 10,
38
+            self::Net15 => 15,
39
+            self::Net30 => 30,
40
+            self::Net60 => 60,
41
+            self::Net90 => 90,
42
+        };
43
+    }
44
+}

+ 83
- 0
app/Enums/PrimaryColor.php 查看文件

@@ -0,0 +1,83 @@
1
+<?php
2
+
3
+namespace App\Enums;
4
+
5
+use App\Enums\Concerns\Utilities;
6
+use Filament\Support\Colors\Color;
7
+use Filament\Support\Contracts\HasColor;
8
+use Spatie\Color\Rgb;
9
+use UnexpectedValueException;
10
+
11
+enum PrimaryColor: string implements HasColor
12
+{
13
+    use Utilities;
14
+
15
+    case Slate = 'slate';
16
+    case Gray = 'gray';
17
+    case Zinc = 'zinc';
18
+    case Neutral = 'neutral';
19
+    case Stone = 'stone';
20
+    case Red = 'red';
21
+    case Orange = 'orange';
22
+    case Amber = 'amber';
23
+    case Yellow = 'yellow';
24
+    case Lime = 'lime';
25
+    case Green = 'green';
26
+    case Emerald = 'emerald';
27
+    case Teal = 'teal';
28
+    case Cyan = 'cyan';
29
+    case Sky = 'sky';
30
+    case Blue = 'blue';
31
+    case Indigo = 'indigo';
32
+    case Violet = 'violet';
33
+    case Purple = 'purple';
34
+    case Fuchsia = 'fuchsia';
35
+    case Pink = 'pink';
36
+    case Rose = 'rose';
37
+
38
+    public const DEFAULT = self::Indigo->value;
39
+
40
+    public function getColor(): string|array|null
41
+    {
42
+        return match ($this) {
43
+            self::Slate => Color::Slate,
44
+            self::Gray => Color::Gray,
45
+            self::Zinc => Color::Zinc,
46
+            self::Neutral => Color::Neutral,
47
+            self::Stone => Color::Stone,
48
+            self::Red => Color::Red,
49
+            self::Orange => Color::Orange,
50
+            self::Amber => Color::Amber,
51
+            self::Yellow => Color::Yellow,
52
+            self::Lime => Color::Lime,
53
+            self::Green => Color::Green,
54
+            self::Emerald => Color::Emerald,
55
+            self::Teal => Color::Teal,
56
+            self::Cyan => Color::Cyan,
57
+            self::Sky => Color::Sky,
58
+            self::Blue => Color::Blue,
59
+            self::Indigo => Color::Indigo,
60
+            self::Violet => Color::Violet,
61
+            self::Purple => Color::Purple,
62
+            self::Fuchsia => Color::Fuchsia,
63
+            self::Pink => Color::Pink,
64
+            self::Rose => Color::Rose,
65
+        };
66
+    }
67
+
68
+    /**
69
+     * @throws UnexpectedValueException
70
+     */
71
+    public function getHexCode(): string
72
+    {
73
+        $colorArray = $this->getColor();
74
+
75
+        if ($colorArray !== null && isset($colorArray[600])) {
76
+            $rgbToString = $colorArray[600];
77
+
78
+            return Rgb::fromString("rgb({$rgbToString})")->toHex();
79
+        }
80
+
81
+        throw new UnexpectedValueException("The color {$this->value} does not have a hex code.");
82
+    }
83
+}

+ 29
- 0
app/Enums/RecordsPerPage.php 查看文件

@@ -0,0 +1,29 @@
1
+<?php
2
+
3
+namespace App\Enums;
4
+
5
+use App\Enums\Concerns\Utilities;
6
+use Filament\Support\Contracts\HasLabel;
7
+
8
+enum RecordsPerPage: int implements HasLabel
9
+{
10
+    use Utilities;
11
+    case Five = 5;
12
+    case Ten = 10;
13
+    case TwentyFive = 25;
14
+    case Fifty = 50;
15
+    case OneHundred = 100;
16
+
17
+    public const DEFAULT = self::Ten->value;
18
+
19
+    public const FIVE = self::Five->value;
20
+    public const TEN = self::Ten->value;
21
+    public const TWENTY_FIVE = self::TwentyFive->value;
22
+    public const FIFTY = self::Fifty->value;
23
+    public const ONE_HUNDRED = self::OneHundred->value;
24
+
25
+    public function getLabel(): ?string
26
+    {
27
+        return (string)$this->value;
28
+    }
29
+}

+ 18
- 0
app/Enums/TableSortDirection.php 查看文件

@@ -0,0 +1,18 @@
1
+<?php
2
+
3
+namespace App\Enums;
4
+
5
+use Filament\Support\Contracts\HasLabel;
6
+
7
+enum TableSortDirection: string implements HasLabel
8
+{
9
+    case Ascending = 'asc';
10
+    case Descending = 'desc';
11
+
12
+    public const DEFAULT = self::Ascending->value;
13
+
14
+    public function getLabel(): ?string
15
+    {
16
+        return $this->name;
17
+    }
18
+}

+ 17
- 0
app/Enums/TaxComputation.php 查看文件

@@ -0,0 +1,17 @@
1
+<?php
2
+
3
+namespace App\Enums;
4
+
5
+use Filament\Support\Contracts\HasLabel;
6
+
7
+enum TaxComputation: string implements HasLabel
8
+{
9
+    case Fixed = 'fixed';
10
+    case Percentage = 'percentage';
11
+    case Compound = 'compound';
12
+
13
+    public function getLabel(): ?string
14
+    {
15
+        return $this->name;
16
+    }
17
+}

+ 16
- 0
app/Enums/TaxScope.php 查看文件

@@ -0,0 +1,16 @@
1
+<?php
2
+
3
+namespace App\Enums;
4
+
5
+use Filament\Support\Contracts\HasLabel;
6
+
7
+enum TaxScope: string implements HasLabel
8
+{
9
+    case Product = 'product';
10
+    case Service = 'service';
11
+
12
+    public function getLabel(): ?string
13
+    {
14
+        return $this->name;
15
+    }
16
+}

+ 37
- 0
app/Enums/TaxType.php 查看文件

@@ -0,0 +1,37 @@
1
+<?php
2
+
3
+namespace App\Enums;
4
+
5
+use Filament\Support\Contracts\HasColor;
6
+use Filament\Support\Contracts\HasIcon;
7
+use Filament\Support\Contracts\HasLabel;
8
+
9
+enum TaxType: string implements HasLabel, HasColor, HasIcon
10
+{
11
+    case Sales = 'sales';
12
+    case Purchase = 'purchase';
13
+    case None = 'none';
14
+
15
+    public function getLabel(): ?string
16
+    {
17
+        return$this->name;
18
+    }
19
+
20
+    public function getColor(): string|array|null
21
+    {
22
+        return match ($this) {
23
+            self::Sales => 'success',
24
+            self::Purchase => 'warning',
25
+            self::None => 'gray',
26
+        };
27
+    }
28
+
29
+    public function getIcon(): ?string
30
+    {
31
+        return match ($this) {
32
+            self::Sales => 'heroicon-o-currency-dollar',
33
+            self::Purchase => 'heroicon-o-shopping-bag',
34
+            self::None => 'heroicon-o-x-circle',
35
+        };
36
+    }
37
+}

+ 19
- 0
app/Enums/Template.php 查看文件

@@ -0,0 +1,19 @@
1
+<?php
2
+
3
+namespace App\Enums;
4
+
5
+use Filament\Support\Contracts\HasLabel;
6
+
7
+enum Template: string implements HasLabel
8
+{
9
+    case Default = 'default';
10
+    case Modern = 'modern';
11
+    case Classic = 'classic';
12
+
13
+    public const DEFAULT = self::Default->value;
14
+
15
+    public function getLabel(): ?string
16
+    {
17
+        return $this->name;
18
+    }
19
+}

+ 23
- 0
app/Events/CompanyDefaultEvent.php 查看文件

@@ -0,0 +1,23 @@
1
+<?php
2
+
3
+namespace App\Events;
4
+
5
+use Illuminate\Broadcasting\InteractsWithSockets;
6
+use Illuminate\Database\Eloquent\Model;
7
+use Illuminate\Foundation\Events\Dispatchable;
8
+use Illuminate\Queue\SerializesModels;
9
+
10
+class CompanyDefaultEvent
11
+{
12
+    use Dispatchable, InteractsWithSockets, SerializesModels;
13
+
14
+    public Model $model;
15
+
16
+    /**
17
+     * Create a new event instance.
18
+     */
19
+    public function __construct(Model $model)
20
+    {
21
+        $this->model = $model;
22
+    }
23
+}

+ 29
- 0
app/Events/CompanyDefaultUpdated.php 查看文件

@@ -0,0 +1,29 @@
1
+<?php
2
+
3
+namespace App\Events;
4
+
5
+use Illuminate\Broadcasting\Channel;
6
+use Illuminate\Broadcasting\InteractsWithSockets;
7
+use Illuminate\Broadcasting\PresenceChannel;
8
+use Illuminate\Broadcasting\PrivateChannel;
9
+use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
10
+use Illuminate\Database\Eloquent\Model;
11
+use Illuminate\Foundation\Events\Dispatchable;
12
+use Illuminate\Queue\SerializesModels;
13
+
14
+class CompanyDefaultUpdated
15
+{
16
+    use Dispatchable, InteractsWithSockets, SerializesModels;
17
+
18
+    public Model $record;
19
+    public array $data;
20
+
21
+    /**
22
+     * Create a new event instance.
23
+     */
24
+    public function __construct(Model $record, array $data)
25
+    {
26
+        $this->record = $record;
27
+        $this->data = $data;
28
+    }
29
+}

+ 8
- 0
app/Events/UpdateCompanyDefault.php 查看文件

@@ -0,0 +1,8 @@
1
+<?php
2
+
3
+namespace App\Events;
4
+
5
+class UpdateCompanyDefault
6
+{
7
+
8
+}

+ 77
- 0
app/Filament/Company/Pages/CreateCompany.php 查看文件

@@ -0,0 +1,77 @@
1
+<?php
2
+
3
+namespace App\Filament\Company\Pages;
4
+
5
+use App\Enums\EntityType;
6
+use App\Models\Setting\CompanyProfile;
7
+use Filament\Forms\Components\Select;
8
+use Filament\Forms\Components\TextInput;
9
+use Filament\Forms\Form;
10
+use Illuminate\Database\Eloquent\Model;
11
+use Illuminate\Support\Facades\Auth;
12
+use Illuminate\Support\Facades\Gate;
13
+use Wallo\FilamentCompanies\Events\AddingCompany;
14
+use Wallo\FilamentCompanies\FilamentCompanies;
15
+use Wallo\FilamentCompanies\Pages\Company\CreateCompany as FilamentCreateCompany;
16
+
17
+class CreateCompany extends FilamentCreateCompany
18
+{
19
+    public function form(Form $form): Form
20
+    {
21
+        return $form
22
+            ->schema([
23
+                TextInput::make('name')
24
+                    ->label(__('filament-companies::default.labels.company_name'))
25
+                    ->autofocus()
26
+                    ->maxLength(255)
27
+                    ->required(),
28
+                TextInput::make('profile.email')
29
+                    ->label('Company Email')
30
+                    ->email()
31
+                    ->required(),
32
+                Select::make('profile.entity_type')
33
+                    ->label('Entity Type')
34
+                    ->native(false)
35
+                    ->options(EntityType::class)
36
+                    ->required(),
37
+                Select::make('profile.country')
38
+                    ->label('Country')
39
+                    ->native(false)
40
+                    ->searchable()
41
+                    ->options(CompanyProfile::getAvailableCountryOptions())
42
+                    ->required(),
43
+            ])
44
+            ->model(FilamentCompanies::companyModel())
45
+            ->statePath('data');
46
+    }
47
+
48
+    protected function handleRegistration(array $data): Model
49
+    {
50
+        $user = Auth::user();
51
+
52
+        Gate::forUser($user)->authorize('create', FilamentCompanies::newCompanyModel());
53
+
54
+        AddingCompany::dispatch($user);
55
+
56
+        $personalCompany = $user?->personalCompany() === null;
57
+
58
+        $company = $user?->ownedCompanies()->create([
59
+            'name' => $data['name'],
60
+            'personal_company' => $personalCompany,
61
+        ]);
62
+
63
+        $company->profile()->create([
64
+            'email' => $data['profile']['email'],
65
+            'entity_type' => $data['profile']['entity_type'],
66
+            'country' => $data['profile']['country'],
67
+        ]);
68
+
69
+        $user?->switchCompany($company);
70
+
71
+        $name = $data['name'];
72
+
73
+        $this->companyCreated($name);
74
+
75
+        return $company;
76
+    }
77
+}

+ 282
- 0
app/Filament/Company/Pages/Setting/Appearance.php 查看文件

@@ -0,0 +1,282 @@
1
+<?php
2
+
3
+namespace App\Filament\Company\Pages\Setting;
4
+
5
+use App\Enums\Font;
6
+use App\Enums\MaxContentWidth;
7
+use App\Enums\ModalWidth;
8
+use App\Enums\PrimaryColor;
9
+use App\Enums\RecordsPerPage;
10
+use App\Enums\TableSortDirection;
11
+use App\Models\Setting\Appearance as AppearanceModel;
12
+use Filament\Actions\Action;
13
+use Filament\Actions\ActionGroup;
14
+use Filament\Forms\Components\Component;
15
+use Filament\Forms\Components\Section;
16
+use Filament\Forms\Components\Select;
17
+use Filament\Forms\Form;
18
+use Filament\Notifications\Notification;
19
+use Filament\Pages\Concerns\InteractsWithFormActions;
20
+use Filament\Pages\Page;
21
+use Filament\Support\Exceptions\Halt;
22
+use Illuminate\Auth\Access\AuthorizationException;
23
+use Illuminate\Database\Eloquent\Model;
24
+use Livewire\Attributes\Locked;
25
+use Wallo\FilamentSelectify\Components\ButtonGroup;
26
+use Wallo\FilamentSelectify\Components\ToggleButton;
27
+use function Filament\authorize;
28
+
29
+/**
30
+ * @property Form $form
31
+ */
32
+class Appearance extends Page
33
+{
34
+    use InteractsWithFormActions;
35
+
36
+    protected static ?string $navigationIcon = 'heroicon-o-paint-brush';
37
+
38
+    protected static ?string $navigationLabel = 'Appearance';
39
+
40
+    protected static ?string $navigationGroup = 'Settings';
41
+
42
+    protected static ?string $slug = 'settings/appearance';
43
+
44
+    protected ?string $heading = 'Appearance';
45
+
46
+    protected static string $view = 'filament.company.pages.setting.appearance';
47
+
48
+    public ?array $data = [];
49
+
50
+    #[Locked]
51
+    public ?AppearanceModel $record = null;
52
+
53
+    public function mount(): void
54
+    {
55
+        $this->record = AppearanceModel::firstOrNew([
56
+            'company_id' => auth()->user()->currentCompany->id,
57
+        ]);
58
+
59
+        abort_unless(static::canView($this->record), 404);
60
+
61
+        $this->fillForm();
62
+    }
63
+
64
+    public function fillForm(): void
65
+    {
66
+        $data = $this->record->attributesToArray();
67
+
68
+        $data = $this->mutateFormDataBeforeFill($data);
69
+
70
+        $this->form->fill($data);
71
+    }
72
+
73
+    protected function mutateFormDataBeforeFill(array $data): array
74
+    {
75
+        return $data;
76
+    }
77
+
78
+    protected function mutateFormDataBeforeSave(array $data): array
79
+    {
80
+        return $data;
81
+    }
82
+
83
+    public function save(): void
84
+    {
85
+        try {
86
+            $data = $this->form->getState();
87
+
88
+            $data = $this->mutateFormDataBeforeSave($data);
89
+
90
+            $this->handleRecordUpdate($this->record, $data);
91
+
92
+        } catch (Halt $exception) {
93
+            return;
94
+        }
95
+
96
+        $this->getSavedNotification()?->send();
97
+
98
+        if ($redirectUrl = $this->getRedirectUrl()) {
99
+            $this->redirect($redirectUrl);
100
+        }
101
+    }
102
+
103
+    protected function getSavedNotification(): ?Notification
104
+    {
105
+        $title = $this->getSavedNotificationTitle();
106
+
107
+        if (blank($title)) {
108
+            return null;
109
+        }
110
+
111
+        return Notification::make()
112
+            ->success()
113
+            ->title($this->getSavedNotificationTitle());
114
+    }
115
+
116
+    protected function getSavedNotificationTitle(): ?string
117
+    {
118
+        return __('filament-panels::pages/tenancy/edit-tenant-profile.notifications.saved.title');
119
+    }
120
+
121
+    protected function getRedirectUrl(): ?string
122
+    {
123
+        return null;
124
+    }
125
+
126
+    public function form(Form $form): Form
127
+    {
128
+        return $form
129
+            ->schema([
130
+                $this->getGeneralSection(),
131
+                $this->getLayoutSection(),
132
+                $this->getDataPresentationSection(),
133
+            ])
134
+            ->model($this->record)
135
+            ->statePath('data')
136
+            ->operation('edit');
137
+    }
138
+
139
+    protected function getGeneralSection(): Component
140
+    {
141
+        return Section::make('General')
142
+            ->schema([
143
+                Select::make('primary_color')
144
+                    ->label('Primary Color')
145
+                    ->native(false)
146
+                    ->allowHtml()
147
+                    ->selectablePlaceholder(false)
148
+                    ->rule('required')
149
+                    ->options(collect(PrimaryColor::cases())
150
+                        ->mapWithKeys(static fn ($case) => [
151
+                            $case->value => "<span class='flex items-center gap-x-4'>
152
+                                <span class='rounded-full w-4 h-4' style='background:rgb(" . $case->getColor()[600] . ")'></span>
153
+                                <span>" . str($case->value)->title() . "</span>
154
+                                </span>"
155
+                        ]),
156
+                    ),
157
+                Select::make('font')
158
+                    ->label('Font')
159
+                    ->native(false)
160
+                    ->selectablePlaceholder(false)
161
+                    ->rule('required')
162
+                    ->allowHtml()
163
+                    ->options(collect(Font::cases())
164
+                        ->mapWithKeys(static fn ($case) => [
165
+                            $case->value => "<span style='font-family:{$case->getLabel()}'>{$case->getLabel()}</span>"
166
+                        ]),
167
+                    ),
168
+            ])->columns();
169
+    }
170
+
171
+    protected function getLayoutSection(): Component
172
+    {
173
+        return Section::make('Layout')
174
+            ->schema([
175
+                Select::make('max_content_width')
176
+                    ->label('Max Content Width')
177
+                    ->native(false)
178
+                    ->selectablePlaceholder(false)
179
+                    ->rule('required')
180
+                    ->options(MaxContentWidth::class),
181
+                Select::make('modal_width')
182
+                    ->label('Modal Width')
183
+                    ->native(false)
184
+                    ->selectablePlaceholder(false)
185
+                    ->rule('required')
186
+                    ->options(ModalWidth::class),
187
+                ButtonGroup::make('has_top_navigation')
188
+                    ->label('Navigation Layout')
189
+                    ->boolean('Top Navigation', 'Side Navigation')
190
+                    ->rule('required'),
191
+                ToggleButton::make('is_table_striped')
192
+                    ->label('Striped Tables')
193
+                    ->onLabel('Enabled')
194
+                    ->offLabel('Disabled')
195
+                    ->rule('required'),
196
+            ])->columns();
197
+    }
198
+
199
+    protected function getDataPresentationSection(): Component
200
+    {
201
+        return Section::make('Data Presentation')
202
+            ->schema([
203
+                Select::make('table_sort_direction')
204
+                    ->label('Table Sort Direction')
205
+                    ->native(false)
206
+                    ->selectablePlaceholder(false)
207
+                    ->rule('required')
208
+                    ->options(TableSortDirection::class),
209
+                Select::make('records_per_page')
210
+                    ->label('Records Per Page')
211
+                    ->native(false)
212
+                    ->selectablePlaceholder(false)
213
+                    ->rule('required')
214
+                    ->options(RecordsPerPage::class),
215
+            ])->columns();
216
+    }
217
+
218
+    protected function handleRecordUpdate(AppearanceModel $record, array $data): AppearanceModel
219
+    {
220
+        $record_array = array_map('strval', $record->toArray());
221
+        $data_array = array_map('strval', $data);
222
+        $diff = array_diff_assoc($data_array, $record_array);
223
+
224
+        $keysToWatch = [
225
+            'primary_color',
226
+            'max_content_width',
227
+            'has_top_navigation',
228
+            'font',
229
+        ];
230
+
231
+        foreach ($diff as $key => $value) {
232
+            if (in_array($key, $keysToWatch, true)) {
233
+                $this->dispatch('appearanceUpdated');
234
+            }
235
+        }
236
+
237
+        // If the primary color or font has changed, we need to update the associated models accent_color column.
238
+        if (array_key_exists('primary_color', $diff) || array_key_exists('font', $diff)) {
239
+            $primaryColorToHex = PrimaryColor::from($data['primary_color'])->getHexCode();
240
+            $font = Font::from($data['font'])->value;
241
+            $this->record->company->defaultBill()->update([
242
+                'accent_color' => $primaryColorToHex,
243
+                'font' => $font,
244
+            ]);
245
+            $this->record->company->defaultInvoice()->update([
246
+                'accent_color' => $primaryColorToHex,
247
+                'font' => $font,
248
+            ]);
249
+        }
250
+
251
+        $record->update($data);
252
+
253
+        return $record;
254
+    }
255
+
256
+    /**
257
+     * @return array<Action | ActionGroup>
258
+     */
259
+    protected function getFormActions(): array
260
+    {
261
+        return [
262
+            $this->getSaveFormAction(),
263
+        ];
264
+    }
265
+
266
+    protected function getSaveFormAction(): Action
267
+    {
268
+        return Action::make('save')
269
+            ->label(__('filament-panels::pages/tenancy/edit-tenant-profile.form.actions.save.label'))
270
+            ->submit('save')
271
+            ->keyBindings(['mod+s']);
272
+    }
273
+
274
+    public static function canView(Model $record): bool
275
+    {
276
+        try {
277
+            return authorize('update', $record)->allowed();
278
+        } catch (AuthorizationException $exception) {
279
+            return $exception->toResponse()->allowed();
280
+        }
281
+    }
282
+}

+ 251
- 0
app/Filament/Company/Pages/Setting/CompanyDefault.php 查看文件

@@ -0,0 +1,251 @@
1
+<?php
2
+
3
+namespace App\Filament\Company\Pages\Setting;
4
+
5
+use App\Events\CompanyDefaultUpdated;
6
+use App\Models\Setting\CompanyDefault as CompanyDefaultModel;
7
+use Filament\Actions\Action;
8
+use Filament\Actions\ActionGroup;
9
+use Filament\Forms\Components\Component;
10
+use Filament\Forms\Components\Section;
11
+use Filament\Forms\Components\Select;
12
+use Filament\Forms\Form;
13
+use Filament\Notifications\Notification;
14
+use Filament\Pages\Concerns\InteractsWithFormActions;
15
+use Filament\Pages\Page;
16
+use Filament\Support\Exceptions\Halt;
17
+use Illuminate\Auth\Access\AuthorizationException;
18
+use Illuminate\Database\Eloquent\Model;
19
+use Livewire\Attributes\Locked;
20
+use function Filament\authorize;
21
+
22
+/**
23
+ * @property Form $form
24
+ */
25
+class CompanyDefault extends Page
26
+{
27
+    use InteractsWithFormActions;
28
+
29
+    protected static ?string $navigationIcon = 'heroicon-o-adjustments-vertical';
30
+
31
+    protected static ?string $navigationLabel = 'Default';
32
+
33
+    protected static ?string $navigationGroup = 'Settings';
34
+
35
+    protected static ?string $slug = 'settings/default';
36
+
37
+    protected ?string $heading = 'Default';
38
+
39
+    protected static string $view = 'filament.company.pages.setting.company-default';
40
+
41
+    public ?array $data = [];
42
+
43
+    #[Locked]
44
+    public ?CompanyDefaultModel $record = null;
45
+
46
+    public function mount(): void
47
+    {
48
+        $this->record = CompanyDefaultModel::firstOrNew([
49
+            'company_id' => auth()->user()->currentCompany->id,
50
+        ]);
51
+
52
+        abort_unless(static::canView($this->record), 404);
53
+
54
+        $this->fillForm();
55
+    }
56
+
57
+    public function fillForm(): void
58
+    {
59
+        $data = $this->record->attributesToArray();
60
+
61
+        $data = $this->mutateFormDataBeforeFill($data);
62
+
63
+        $this->form->fill($data);
64
+    }
65
+
66
+    protected function mutateFormDataBeforeFill(array $data): array
67
+    {
68
+        return $data;
69
+    }
70
+
71
+    protected function mutateFormDataBeforeSave(array $data): array
72
+    {
73
+        return $data;
74
+    }
75
+
76
+    public function save(): void
77
+    {
78
+        try {
79
+            $data = $this->form->getState();
80
+
81
+            $data = $this->mutateFormDataBeforeSave($data);
82
+
83
+            $this->handleRecordUpdate($this->record, $data);
84
+
85
+        } catch (Halt $exception) {
86
+            return;
87
+        }
88
+
89
+        $this->getSavedNotification()?->send();
90
+
91
+        if ($redirectUrl = $this->getRedirectUrl()) {
92
+            $this->redirect($redirectUrl);
93
+        }
94
+    }
95
+
96
+    protected function getSavedNotification(): ?Notification
97
+    {
98
+        $title = $this->getSavedNotificationTitle();
99
+
100
+        if (blank($title)) {
101
+            return null;
102
+        }
103
+
104
+        return Notification::make()
105
+            ->success()
106
+            ->title($this->getSavedNotificationTitle());
107
+    }
108
+
109
+    protected function getSavedNotificationTitle(): ?string
110
+    {
111
+        return __('filament-panels::pages/tenancy/edit-tenant-profile.notifications.saved.title');
112
+    }
113
+
114
+    protected function getRedirectUrl(): ?string
115
+    {
116
+        return null;
117
+    }
118
+
119
+    public function form(Form $form): Form
120
+    {
121
+        return $form
122
+            ->schema([
123
+                $this->getGeneralSection(),
124
+                $this->getModifiersSection(),
125
+                $this->getCategoriesSection(),
126
+            ])
127
+            ->model($this->record)
128
+            ->statePath('data')
129
+            ->operation('edit');
130
+    }
131
+
132
+    protected function getGeneralSection(): Component
133
+    {
134
+        return Section::make('General')
135
+            ->schema([
136
+                Select::make('account_id')
137
+                    ->label('Account')
138
+                    ->relationship('account', 'name')
139
+                    ->saveRelationshipsUsing(null)
140
+                    ->selectablePlaceholder(false)
141
+                    ->searchable()
142
+                    ->preload(),
143
+                Select::make('currency_code')
144
+                    ->label('Currency')
145
+                    ->relationship('currency', 'code')
146
+                    ->saveRelationshipsUsing(null)
147
+                    ->selectablePlaceholder(false)
148
+                    ->rule('required')
149
+                    ->searchable()
150
+                    ->preload(),
151
+            ])->columns();
152
+    }
153
+
154
+    protected function getModifiersSection(): Component
155
+    {
156
+        return Section::make('Taxes & Discounts')
157
+            ->schema([
158
+                Select::make('sales_tax_id')
159
+                    ->label('Sales Tax')
160
+                    ->relationship('salesTax', 'name')
161
+                    ->saveRelationshipsUsing(null)
162
+                    ->selectablePlaceholder(false)
163
+                    ->rule('required')
164
+                    ->searchable()
165
+                    ->preload(),
166
+                Select::make('purchase_tax_id')
167
+                    ->label('Purchase Tax')
168
+                    ->relationship('purchaseTax', 'name')
169
+                    ->saveRelationshipsUsing(null)
170
+                    ->selectablePlaceholder(false)
171
+                    ->rule('required')
172
+                    ->searchable()
173
+                    ->preload(),
174
+                Select::make('sales_discount_id')
175
+                    ->label('Sales Discount')
176
+                    ->relationship('salesDiscount', 'name')
177
+                    ->saveRelationshipsUsing(null)
178
+                    ->selectablePlaceholder(false)
179
+                    ->rule('required')
180
+                    ->searchable()
181
+                    ->preload(),
182
+                Select::make('purchase_discount_id')
183
+                    ->label('Purchase Discount')
184
+                    ->relationship('purchaseDiscount', 'name')
185
+                    ->saveRelationshipsUsing(null)
186
+                    ->selectablePlaceholder(false)
187
+                    ->rule('required')
188
+                    ->searchable()
189
+                    ->preload(),
190
+            ])->columns();
191
+    }
192
+
193
+    protected function getCategoriesSection(): Component
194
+    {
195
+        return Section::make('Categories')
196
+            ->schema([
197
+                Select::make('income_category_id')
198
+                    ->label('Income Category')
199
+                    ->relationship('incomeCategory', 'name')
200
+                    ->saveRelationshipsUsing(null)
201
+                    ->selectablePlaceholder(false)
202
+                    ->rule('required')
203
+                    ->searchable()
204
+                    ->preload(),
205
+                Select::make('expense_category_id')
206
+                    ->label('Expense Category')
207
+                    ->relationship('expenseCategory', 'name')
208
+                    ->saveRelationshipsUsing(null)
209
+                    ->selectablePlaceholder(false)
210
+                    ->rule('required')
211
+                    ->searchable()
212
+                    ->preload(),
213
+            ])->columns();
214
+    }
215
+
216
+    protected function handleRecordUpdate(CompanyDefaultModel $record, array $data): CompanyDefaultModel
217
+    {
218
+        CompanyDefaultUpdated::dispatch($record, $data);
219
+
220
+        $record->update($data);
221
+
222
+        return $record;
223
+    }
224
+
225
+    /**
226
+     * @return array<Action | ActionGroup>
227
+     */
228
+    protected function getFormActions(): array
229
+    {
230
+        return [
231
+            $this->getSaveFormAction(),
232
+        ];
233
+    }
234
+
235
+    protected function getSaveFormAction(): Action
236
+    {
237
+        return Action::make('save')
238
+            ->label(__('filament-panels::pages/tenancy/edit-tenant-profile.form.actions.save.label'))
239
+            ->submit('save')
240
+            ->keyBindings(['mod+s']);
241
+    }
242
+
243
+    public static function canView(Model $record): bool
244
+    {
245
+        try {
246
+            return authorize('update', $record)->allowed();
247
+        } catch (AuthorizationException $exception) {
248
+            return $exception->toResponse()->allowed();
249
+        }
250
+    }
251
+}

+ 284
- 0
app/Filament/Company/Pages/Setting/CompanyProfile.php 查看文件

@@ -0,0 +1,284 @@
1
+<?php
2
+
3
+namespace App\Filament\Company\Pages\Setting;
4
+
5
+use App\Enums\EntityType;
6
+use App\Events\CompanyDefaultUpdated;
7
+use App\Models\Setting\CompanyDefault as CompanyDefaultModel;
8
+use Filament\Actions\Action;
9
+use Filament\Actions\ActionGroup;
10
+use Filament\Forms\Components\Component;
11
+use Filament\Forms\Components\DatePicker;
12
+use Filament\Forms\Components\FileUpload;
13
+use Filament\Forms\Components\Group;
14
+use Filament\Forms\Components\Section;
15
+use Filament\Forms\Components\Select;
16
+use Filament\Forms\Components\TextInput;
17
+use Filament\Forms\Form;
18
+use Filament\Forms\Get;
19
+use Filament\Notifications\Notification;
20
+use Filament\Pages\Concerns\InteractsWithFormActions;
21
+use Filament\Pages\Page;
22
+use App\Models\Setting\CompanyProfile as CompanyProfileModel;
23
+use Filament\Support\Exceptions\Halt;
24
+use Illuminate\Auth\Access\AuthorizationException;
25
+use Illuminate\Database\Eloquent\Model;
26
+use Illuminate\Support\Facades\Auth;
27
+use Livewire\Attributes\Locked;
28
+use Livewire\Features\SupportFileUploads\TemporaryUploadedFile;
29
+use function Filament\authorize;
30
+
31
+/**
32
+ * @property Form $form
33
+ */
34
+class CompanyProfile extends Page
35
+{
36
+    use InteractsWithFormActions;
37
+
38
+    protected static ?string $navigationIcon = 'heroicon-o-building-office-2';
39
+
40
+    protected static ?string $navigationLabel = 'Company Profile';
41
+
42
+    protected static ?string $navigationGroup = 'Settings';
43
+
44
+    protected static ?string $slug = 'settings/company-profile';
45
+
46
+    protected ?string $heading = 'Company Profile';
47
+
48
+    protected static string $view = 'filament.company.pages.setting.company-profile';
49
+
50
+    public ?array $data = [];
51
+
52
+    #[Locked]
53
+    public ?CompanyProfileModel $record = null;
54
+
55
+    public function mount(): void
56
+    {
57
+        $this->record = CompanyProfileModel::firstOrNew([
58
+            'company_id' => auth()->user()->currentCompany->id,
59
+        ]);
60
+
61
+        abort_unless(static::canView($this->record), 404);
62
+
63
+        $this->fillForm();
64
+    }
65
+
66
+    public function fillForm(): void
67
+    {
68
+        $data = $this->record->attributesToArray();
69
+
70
+        $data = $this->mutateFormDataBeforeFill($data);
71
+
72
+        $data['fiscal_year_start'] = now()->startOfYear()->toDateString();
73
+        $data['fiscal_year_end'] = now()->endOfYear()->toDateString();
74
+
75
+        $this->form->fill($data);
76
+    }
77
+
78
+    protected function mutateFormDataBeforeFill(array $data): array
79
+    {
80
+        return $data;
81
+    }
82
+
83
+    protected function mutateFormDataBeforeSave(array $data): array
84
+    {
85
+        return $data;
86
+    }
87
+
88
+    public function save(): void
89
+    {
90
+        try {
91
+            $data = $this->form->getState();
92
+
93
+            $data = $this->mutateFormDataBeforeSave($data);
94
+
95
+            $this->handleRecordUpdate($this->record, $data);
96
+
97
+        } catch (Halt $exception) {
98
+            return;
99
+        }
100
+
101
+        $this->getSavedNotification()?->send();
102
+
103
+        if ($redirectUrl = $this->getRedirectUrl()) {
104
+            $this->redirect($redirectUrl);
105
+        }
106
+    }
107
+
108
+    protected function getSavedNotification(): ?Notification
109
+    {
110
+        $title = $this->getSavedNotificationTitle();
111
+
112
+        if (blank($title)) {
113
+            return null;
114
+        }
115
+
116
+        return Notification::make()
117
+            ->success()
118
+            ->title($this->getSavedNotificationTitle());
119
+    }
120
+
121
+    protected function getSavedNotificationTitle(): ?string
122
+    {
123
+        return __('filament-panels::pages/tenancy/edit-tenant-profile.notifications.saved.title');
124
+    }
125
+
126
+    protected function getRedirectUrl(): ?string
127
+    {
128
+        return null;
129
+    }
130
+
131
+    public function form(Form $form): Form
132
+    {
133
+        return $form
134
+            ->schema([
135
+                $this->getIdentificationSection(),
136
+                $this->getLocationDetailsSection(),
137
+                $this->getLegalAndComplianceSection(),
138
+                $this->getFiscalYearSection(),
139
+            ])
140
+            ->model($this->record)
141
+            ->statePath('data')
142
+            ->operation('edit');
143
+    }
144
+
145
+    protected function getIdentificationSection(): Component
146
+    {
147
+        return Section::make('Identification')
148
+            ->schema([
149
+                FileUpload::make('logo')
150
+                    ->label('Logo')
151
+                    ->disk('public')
152
+                    ->directory('logos/company')
153
+                    ->imageResizeMode('contain')
154
+                    ->imagePreviewHeight('250')
155
+                    ->imageCropAspectRatio('2:1')
156
+                    ->getUploadedFileNameForStorageUsing(
157
+                        static fn (TemporaryUploadedFile $file): string => (string) str($file->getClientOriginalName())
158
+                            ->prepend(Auth::user()->currentCompany->id . '_'),
159
+                    )
160
+                    ->openable()
161
+                    ->maxSize(2048)
162
+                    ->image()
163
+                    ->visibility('public')
164
+                    ->acceptedFileTypes(['image/png', 'image/jpeg']),
165
+                Group::make()
166
+                    ->schema([
167
+                        TextInput::make('email')
168
+                            ->label('Email')
169
+                            ->email()
170
+                            ->maxLength(255)
171
+                            ->required(),
172
+                        TextInput::make('phone_number')
173
+                            ->label('Phone Number')
174
+                            ->tel()
175
+                            ->nullable(),
176
+                    ])->columns(1)
177
+            ])->columns();
178
+    }
179
+
180
+    protected function getLocationDetailsSection(): Component
181
+    {
182
+        return Section::make('Location Details')
183
+            ->schema([
184
+                Select::make('country')
185
+                    ->label('Country')
186
+                    ->native(false)
187
+                    ->live()
188
+                    ->searchable()
189
+                    ->options(CompanyProfileModel::getAvailableCountryOptions())
190
+                    ->required(),
191
+                Select::make('state')
192
+                    ->label('State / Province')
193
+                    ->searchable()
194
+                    ->native(false)
195
+                    ->options(static fn (Get $get) => CompanyProfileModel::getStateOptions($get('country')))
196
+                    ->nullable(),
197
+                Select::make('timezone')
198
+                    ->label('Timezone')
199
+                    ->native(false)
200
+                    ->options(static fn (Get $get) => CompanyProfileModel::getTimezoneOptions($get('country')))
201
+                    ->nullable(),
202
+                TextInput::make('address')
203
+                    ->label('Street Address')
204
+                    ->maxLength(255)
205
+                    ->nullable(),
206
+                TextInput::make('city')
207
+                    ->label('City / Town')
208
+                    ->maxLength(255)
209
+                    ->nullable(),
210
+                TextInput::make('zip_code')
211
+                    ->label('Zip Code')
212
+                    ->maxLength(20)
213
+                    ->nullable(),
214
+            ])->columns();
215
+    }
216
+
217
+    protected function getLegalAndComplianceSection(): Component
218
+    {
219
+        return Section::make('Legal & Compliance')
220
+            ->schema([
221
+                Select::make('entity_type')
222
+                    ->label('Entity Type')
223
+                    ->native(false)
224
+                    ->options(EntityType::class)
225
+                    ->required(),
226
+                TextInput::make('tax_id')
227
+                    ->label('Tax ID')
228
+                    ->maxLength(50)
229
+                    ->nullable(),
230
+            ])->columns();
231
+    }
232
+
233
+    protected function getFiscalYearSection(): Component
234
+    {
235
+        return Section::make('Fiscal Year')
236
+            ->schema([
237
+                DatePicker::make('fiscal_year_start')
238
+                    ->label('Start')
239
+                    ->native(false)
240
+                    ->seconds(false)
241
+                    ->rule('required'),
242
+                DatePicker::make('fiscal_year_end')
243
+                    ->label('End')
244
+                    ->minDate(static fn (Get $get) => $get('fiscal_year_start'))
245
+                    ->native(false)
246
+                    ->seconds(false)
247
+                    ->rule('required'),
248
+            ])->columns();
249
+    }
250
+
251
+    protected function handleRecordUpdate(CompanyProfileModel $record, array $data): CompanyProfileModel
252
+    {
253
+        $record->update($data);
254
+
255
+        return $record;
256
+    }
257
+
258
+    /**
259
+     * @return array<Action | ActionGroup>
260
+     */
261
+    protected function getFormActions(): array
262
+    {
263
+        return [
264
+            $this->getSaveFormAction(),
265
+        ];
266
+    }
267
+
268
+    protected function getSaveFormAction(): Action
269
+    {
270
+        return Action::make('save')
271
+            ->label(__('filament-panels::pages/tenancy/edit-tenant-profile.form.actions.save.label'))
272
+            ->submit('save')
273
+            ->keyBindings(['mod+s']);
274
+    }
275
+
276
+    public static function canView(Model $record): bool
277
+    {
278
+        try {
279
+            return authorize('update', $record)->allowed();
280
+        } catch (AuthorizationException $exception) {
281
+            return $exception->toResponse()->allowed();
282
+        }
283
+    }
284
+}

+ 334
- 0
app/Filament/Company/Pages/Setting/Invoice.php 查看文件

@@ -0,0 +1,334 @@
1
+<?php
2
+
3
+namespace App\Filament\Company\Pages\Setting;
4
+
5
+use App\Enums\DocumentAmountColumn;
6
+use App\Enums\DocumentItemColumn;
7
+use App\Enums\DocumentPriceColumn;
8
+use App\Enums\DocumentType;
9
+use App\Enums\DocumentUnitColumn;
10
+use App\Enums\Font;
11
+use App\Enums\PaymentTerms;
12
+use App\Enums\Template;
13
+use App\Models\Setting\DocumentDefault as InvoiceModel;
14
+use Filament\Actions\Action;
15
+use Filament\Actions\ActionGroup;
16
+use Filament\Forms\Components\Checkbox;
17
+use Filament\Forms\Components\ColorPicker;
18
+use Filament\Forms\Components\Component;
19
+use Filament\Forms\Components\FileUpload;
20
+use Filament\Forms\Components\Group;
21
+use Filament\Forms\Components\Radio;
22
+use Filament\Forms\Components\Section;
23
+use Filament\Forms\Components\Select;
24
+use Filament\Forms\Components\Textarea;
25
+use Filament\Forms\Components\TextInput;
26
+use Filament\Forms\Components\ViewField;
27
+use Filament\Forms\Form;
28
+use Filament\Forms\Get;
29
+use Filament\Forms\Set;
30
+use Filament\Notifications\Notification;
31
+use Filament\Pages\Concerns\InteractsWithFormActions;
32
+use Filament\Pages\Page;
33
+use Filament\Support\Exceptions\Halt;
34
+use Illuminate\Auth\Access\AuthorizationException;
35
+use Illuminate\Database\Eloquent\Model;
36
+use Livewire\Attributes\Locked;
37
+use function Filament\authorize;
38
+
39
+/**
40
+ * @property Form $form
41
+ */
42
+class Invoice extends Page
43
+{
44
+    use InteractsWithFormActions;
45
+
46
+    protected static ?string $navigationIcon = 'heroicon-o-document-duplicate';
47
+
48
+    protected static ?string $navigationLabel = 'Invoice';
49
+
50
+    protected static ?string $navigationGroup = 'Settings';
51
+
52
+    protected static ?string $slug = 'settings/invoice';
53
+
54
+    protected ?string $heading = 'Invoice';
55
+
56
+    protected static string $view = 'filament.company.pages.setting.invoice';
57
+
58
+    public ?array $data = [];
59
+
60
+    public ?InvoiceModel $record = null;
61
+
62
+    public function mount(): void
63
+    {
64
+        $this->record = InvoiceModel::invoice()
65
+            ->firstOrNew([
66
+                'company_id' => auth()->user()->currentCompany->id,
67
+                'type' => DocumentType::Invoice->value,
68
+            ]);
69
+
70
+        abort_unless(static::canView($this->record), 404);
71
+
72
+        $this->fillForm();
73
+    }
74
+
75
+    public function fillForm(): void
76
+    {
77
+        $data = $this->record->attributesToArray();
78
+
79
+        $data = $this->mutateFormDataBeforeFill($data);
80
+
81
+        $this->form->fill($data);
82
+    }
83
+
84
+    protected function mutateFormDataBeforeFill(array $data): array
85
+    {
86
+        return $data;
87
+    }
88
+
89
+    protected function mutateFormDataBeforeSave(array $data): array
90
+    {
91
+        return $data;
92
+    }
93
+
94
+    public function save(): void
95
+    {
96
+        try {
97
+            $data = $this->form->getState();
98
+
99
+            $data = $this->mutateFormDataBeforeSave($data);
100
+
101
+            $this->handleRecordUpdate($this->record, $data);
102
+
103
+        } catch (Halt $exception) {
104
+            return;
105
+        }
106
+
107
+        $this->getSavedNotification()?->send();
108
+
109
+        if ($redirectUrl = $this->getRedirectUrl()) {
110
+            $this->redirect($redirectUrl);
111
+        }
112
+    }
113
+
114
+    protected function getSavedNotification(): ?Notification
115
+    {
116
+        $title = $this->getSavedNotificationTitle();
117
+
118
+        if (blank($title)) {
119
+            return null;
120
+        }
121
+
122
+        return Notification::make()
123
+            ->success()
124
+            ->title($this->getSavedNotificationTitle());
125
+    }
126
+
127
+    protected function getSavedNotificationTitle(): ?string
128
+    {
129
+        return __('filament-panels::pages/tenancy/edit-tenant-profile.notifications.saved.title');
130
+    }
131
+
132
+    protected function getRedirectUrl(): ?string
133
+    {
134
+        return null;
135
+    }
136
+
137
+    public function form(Form $form): Form
138
+    {
139
+        return $form
140
+            ->schema([
141
+                $this->getGeneralSection(),
142
+                $this->getContentSection(),
143
+                $this->getTemplateSection(),
144
+            ])
145
+            ->model($this->record)
146
+            ->statePath('data')
147
+            ->operation('edit');
148
+    }
149
+
150
+    protected function getGeneralSection(): Component
151
+    {
152
+        return Section::make('General')
153
+            ->schema([
154
+                TextInput::make('number_prefix')
155
+                    ->label('Number Prefix')
156
+                    ->live()
157
+                    ->required(),
158
+                Select::make('number_digits')
159
+                    ->label('Number Digits')
160
+                    ->options(InvoiceModel::availableNumberDigits())
161
+                    ->native(false)
162
+                    ->live()
163
+                    ->required(),
164
+                TextInput::make('number_next')
165
+                    ->label('Next Number')
166
+                    ->live()
167
+                    ->maxLength(static fn (Get $get) => $get('number_digits'))
168
+                    ->suffix(static function (Get $get, $state) {
169
+                        $number_prefix = $get('number_prefix');
170
+                        $number_digits = $get('number_digits');
171
+                        $number_next = $state;
172
+
173
+                        return InvoiceModel::getNumberNext(true, true, $number_prefix, $number_digits, $number_next);
174
+                    })
175
+                    ->required(),
176
+                Select::make('payment_terms')
177
+                    ->label('Payment Terms')
178
+                    ->options(PaymentTerms::class)
179
+                    ->native(false)
180
+                    ->live()
181
+                    ->required(),
182
+            ])->columns();
183
+    }
184
+
185
+    protected function getContentSection(): Component
186
+    {
187
+        return Section::make('Content')
188
+            ->schema([
189
+                TextInput::make('header')
190
+                    ->label('Header')
191
+                    ->live()
192
+                    ->required(),
193
+                TextInput::make('subheader')
194
+                    ->label('Subheader')
195
+                    ->live()
196
+                    ->nullable(),
197
+                Textarea::make('terms')
198
+                    ->label('Terms')
199
+                    ->live()
200
+                    ->nullable(),
201
+                Textarea::make('footer')
202
+                    ->label('Footer / Notes')
203
+                    ->live()
204
+                    ->nullable(),
205
+            ])->columns();
206
+    }
207
+
208
+    protected function getTemplateSection(): Component
209
+    {
210
+        return Section::make('Template')
211
+            ->description('Choose the template and edit the column names.')
212
+            ->schema([
213
+                Group::make()
214
+                    ->live()
215
+                    ->schema([
216
+                        FileUpload::make('logo')
217
+                            ->label('Logo')
218
+                            ->disk('public')
219
+                            ->directory('logos/documents')
220
+                            ->imageResizeMode('contain')
221
+                            ->imagePreviewHeight('250')
222
+                            ->imageCropAspectRatio('2:1')
223
+                            ->openable()
224
+                            ->preserveFilenames()
225
+                            ->visibility('public')
226
+                            ->image(),
227
+                        Checkbox::make('show_logo')
228
+                            ->label('Show Logo'),
229
+                        ColorPicker::make('accent_color')
230
+                            ->label('Accent Color'),
231
+                        Select::make('font')
232
+                            ->label('Font')
233
+                            ->native(false)
234
+                            ->selectablePlaceholder(false)
235
+                            ->rule('required')
236
+                            ->allowHtml()
237
+                            ->options(collect(Font::cases())
238
+                                ->mapWithKeys(static fn ($case) => [
239
+                                    $case->value => "<span style='font-family:{$case->getLabel()}'>{$case->getLabel()}</span>"
240
+                                ]),
241
+                            ),
242
+                        Select::make('template')
243
+                            ->label('Template')
244
+                            ->options(Template::class)
245
+                            ->required(),
246
+                        Select::make('item_name.option')
247
+                            ->label('Item Name')
248
+                            ->native(false)
249
+                            ->required()
250
+                            ->options(InvoiceModel::getAvailableItemNameOptions()),
251
+                        TextInput::make('item_name.custom')
252
+                            ->hiddenLabel()
253
+                            ->disabled(static fn (callable $get) => $get('item_name.option') !== 'other')
254
+                            ->nullable(),
255
+                        Select::make('unit_name.option')
256
+                            ->label('Unit Name')
257
+                            ->native(false)
258
+                            ->required()
259
+                            ->options(InvoiceModel::getAvailableUnitNameOptions()),
260
+                        TextInput::make('unit_name.custom')
261
+                            ->hiddenLabel()
262
+                            ->disabled(static fn (callable $get) => $get('unit_name.option') !== 'other')
263
+                            ->nullable(),
264
+                        Select::make('price_name.option')
265
+                            ->label('Price Name')
266
+                            ->native(false)
267
+                            ->required()
268
+                            ->options(InvoiceModel::getAvailablePriceNameOptions()),
269
+                        TextInput::make('price_name.custom')
270
+                            ->hiddenLabel()
271
+                            ->disabled(static fn (callable $get) => $get('price_name.option') !== 'other')
272
+                            ->nullable(),
273
+                        Select::make('amount_name.option')
274
+                            ->label('Amount Name')
275
+                            ->native(false)
276
+                            ->required()
277
+                            ->options(InvoiceModel::getAvailableAmountNameOptions()),
278
+                        TextInput::make('amount_name.custom')
279
+                            ->hiddenLabel()
280
+                            ->disabled(static fn (callable $get) => $get('amount_name.option') !== 'other')
281
+                            ->nullable(),
282
+                    ])->columns(1),
283
+                Group::make()
284
+                    ->schema([
285
+                        ViewField::make('preview.default')
286
+                            ->label('Preview')
287
+                            ->visible(static fn (callable $get) => $get('template') === 'default')
288
+                            ->view('components.invoice-layouts.default'),
289
+                        ViewField::make('preview.modern')
290
+                            ->label('Preview')
291
+                            ->visible(static fn (callable $get) => $get('template') === 'modern')
292
+                            ->view('components.invoice-layouts.modern'),
293
+                        ViewField::make('preview.classic')
294
+                            ->label('Preview')
295
+                            ->visible(static fn (callable $get) => $get('template') === 'classic')
296
+                            ->view('components.invoice-layouts.classic'),
297
+                    ])->columnSpan(2),
298
+            ])->columns(3);
299
+    }
300
+
301
+    protected function handleRecordUpdate(InvoiceModel $record, array $data): InvoiceModel
302
+    {
303
+        $record->update($data);
304
+
305
+        return $record;
306
+    }
307
+
308
+    /**
309
+     * @return array<Action | ActionGroup>
310
+     */
311
+    protected function getFormActions(): array
312
+    {
313
+        return [
314
+            $this->getSaveFormAction(),
315
+        ];
316
+    }
317
+
318
+    protected function getSaveFormAction(): Action
319
+    {
320
+        return Action::make('save')
321
+            ->label(__('filament-panels::pages/tenancy/edit-tenant-profile.form.actions.save.label'))
322
+            ->submit('save')
323
+            ->keyBindings(['mod+s']);
324
+    }
325
+
326
+    public static function canView(Model $record): bool
327
+    {
328
+        try {
329
+            return authorize('update', $record)->allowed();
330
+        } catch (AuthorizationException $exception) {
331
+            return $exception->toResponse()->allowed();
332
+        }
333
+    }
334
+}

app/Filament/Resources/AccountResource.php → app/Filament/Company/Resources/Banking/AccountResource.php 查看文件

@@ -1,22 +1,21 @@
1 1
 <?php
2 2
 
3
-namespace App\Filament\Resources;
3
+namespace App\Filament\Company\Resources\Banking;
4 4
 
5 5
 use App\Actions\OptionAction\CreateCurrency;
6
-use App\Filament\Resources\AccountResource\Pages;
6
+use App\Filament\Company\Resources\Banking\AccountResource\Pages;
7
+use App\Filament\Company\Resources\Banking\AccountResource\RelationManagers;
7 8
 use App\Models\Banking\Account;
8 9
 use App\Models\Setting\Currency;
9 10
 use App\Services\CurrencyService;
10
-use Closure;
11
-use Exception;
11
+use App\Utilities\CurrencyConverter;
12 12
 use Filament\Forms;
13
-use Filament\Forms\Components\TextInput\Mask;
13
+use Filament\Forms\Form;
14 14
 use Filament\Notifications\Notification;
15
-use Filament\Resources\Form;
16 15
 use Filament\Resources\Resource;
17
-use Filament\Resources\Table;
18 16
 use Filament\Tables;
19
-use Illuminate\Support\Collection;
17
+use Filament\Tables\Table;
18
+use Illuminate\Database\Eloquent\SoftDeletingScope;
20 19
 use Illuminate\Support\Facades\Auth;
21 20
 use Illuminate\Support\Facades\DB;
22 21
 use Illuminate\Validation\Rules\Unique;
@@ -43,8 +42,7 @@ class AccountResource extends Resource
43 42
                                     ->options(Account::getAccountTypes())
44 43
                                     ->searchable()
45 44
                                     ->default('checking')
46
-                                    ->reactive()
47
-                                    ->disablePlaceholderSelection()
45
+                                    ->live()
48 46
                                     ->required(),
49 47
                                 Forms\Components\TextInput::make('name')
50 48
                                     ->label('Name')
@@ -52,17 +50,17 @@ class AccountResource extends Resource
52 50
                                     ->required(),
53 51
                                 Forms\Components\TextInput::make('number')
54 52
                                     ->label('Account Number')
55
-                                    ->unique(callback: static function (Unique $rule, $state) {
53
+                                    ->unique(ignoreRecord: true, modifyRuleUsing: static function (Unique $rule, $state) {
56 54
                                         $companyId = Auth::user()->currentCompany->id;
57 55
 
58 56
                                         return $rule->where('company_id', $companyId)->where('number', $state);
59
-                                    }, ignoreRecord: true)
57
+                                    })
60 58
                                     ->maxLength(20)
61 59
                                     ->validationAttribute('account number')
62 60
                                     ->required(),
63 61
                                 ToggleButton::make('enabled')
64 62
                                     ->label('Default Account')
65
-                                    ->hidden(static fn (Closure $get) => $get('type') === 'credit_card')
63
+                                    ->hidden(static fn (Forms\Get $get) => $get('type') === 'credit_card')
66 64
                                     ->offColor('danger')
67 65
                                     ->onColor('primary'),
68 66
                             ])->columns(),
@@ -71,36 +69,41 @@ class AccountResource extends Resource
71 69
                                 Forms\Components\Select::make('currency_code')
72 70
                                     ->label('Currency')
73 71
                                     ->relationship('currency', 'name')
72
+                                    ->default(Currency::getDefaultCurrencyCode())
73
+                                    ->saveRelationshipsUsing(null)
74 74
                                     ->preload()
75
-                                    ->default(Currency::getDefaultCurrency())
76 75
                                     ->searchable()
77
-                                    ->reactive()
76
+                                    ->live()
77
+                                    ->afterStateUpdated(static function (Forms\Set $set, $state, $old, Forms\Get $get) {
78
+                                        $opening_balance = CurrencyConverter::convertAndSet($state, $old, $get('opening_balance'));
79
+
80
+                                        if ($opening_balance !== null) {
81
+                                            $set('opening_balance', $opening_balance);
82
+                                        }
83
+                                    })
78 84
                                     ->required()
79
-                                    ->saveRelationshipsUsing(null)
80 85
                                     ->createOptionForm([
81 86
                                         Forms\Components\Select::make('currency.code')
82 87
                                             ->label('Code')
83 88
                                             ->searchable()
84
-                                            ->options(Currency::getCurrencyCodes())
85
-                                            ->reactive()
89
+                                            ->options(Currency::getAvailableCurrencyCodes())
90
+                                            ->live()
86 91
                                             ->afterStateUpdated(static function (callable $set, $state) {
87 92
                                                 if ($state === null) {
88 93
                                                     return;
89 94
                                                 }
90 95
 
91
-                                                $code = $state;
92
-                                                $currencyConfig = config("money.{$code}", []);
96
+                                                $currency_code = currency($state);
93 97
                                                 $currencyService = app(CurrencyService::class);
94 98
 
95
-                                                $defaultCurrency = Currency::getDefaultCurrency();
96
-
99
+                                                $defaultCurrencyCode = Currency::getDefaultCurrencyCode();
97 100
                                                 $rate = 1;
98 101
 
99
-                                                if ($defaultCurrency !== null) {
100
-                                                    $rate = $currencyService->getCachedExchangeRate($defaultCurrency, $code);
102
+                                                if ($defaultCurrencyCode !== null) {
103
+                                                    $rate = $currencyService->getCachedExchangeRate($defaultCurrencyCode, $state);
101 104
                                                 }
102 105
 
103
-                                                $set('currency.name', $currencyConfig['name'] ?? '');
106
+                                                $set('currency.name', $currency_code->getName() ?? '');
104 107
                                                 $set('currency.rate', $rate);
105 108
                                             })
106 109
                                             ->required(),
@@ -116,7 +119,8 @@ class AccountResource extends Resource
116 119
                                         return $action
117 120
                                             ->label('Add Currency')
118 121
                                             ->modalHeading('Add Currency')
119
-                                            ->modalButton('Add')
122
+                                            ->modalSubmitActionLabel('Add')
123
+                                            ->slideOver()
120 124
                                             ->action(static function (array $data) {
121 125
                                                 return DB::transaction(static function () use ($data) {
122 126
                                                     $code = $data['currency']['code'];
@@ -130,21 +134,7 @@ class AccountResource extends Resource
130 134
                                 Forms\Components\TextInput::make('opening_balance')
131 135
                                     ->label('Opening Balance')
132 136
                                     ->required()
133
-                                    ->default('0')
134
-                                    ->numeric()
135
-                                    ->mask(static fn (Forms\Components\TextInput\Mask $mask, Closure $get) => $mask
136
-                                        ->patternBlocks([
137
-                                            'money' => static fn (Mask $mask) => $mask
138
-                                                ->numeric()
139
-                                                ->decimalPlaces(config('money.' . $get('currency_code') . '.precision'))
140
-                                                ->decimalSeparator(config('money.' . $get('currency_code') . '.decimal_mark'))
141
-                                                ->thousandsSeparator(config('money.' . $get('currency_code') . '.thousands_separator'))
142
-                                                ->signed()
143
-                                                ->padFractionalZeros()
144
-                                                ->normalizeZeros(),
145
-                                    ])
146
-                                    ->pattern(config('money.' . $get('currency_code') . '.symbol_first') ? config('money.' . $get('currency_code') . '.symbol') . 'money' : 'money' . config('money.' . $get('currency_code') . '.symbol'))
147
-                                    ->lazyPlaceholder(false)),
137
+                                    ->currency(static fn (Forms\Get $get) => $get('currency_code'))
148 138
                             ])->columns(),
149 139
                         Forms\Components\Tabs::make('Account Specifications')
150 140
                             ->tabs([
@@ -210,9 +200,6 @@ class AccountResource extends Resource
210 200
             ])->columns(3);
211 201
     }
212 202
 
213
-    /**
214
-     * @throws Exception
215
-     */
216 203
     public static function table(Table $table): Table
217 204
     {
218 205
         return $table
@@ -232,7 +219,8 @@ class AccountResource extends Resource
232 219
                     ->description(static fn (Account $record) => $record->bank_phone ?: 'N/A')
233 220
                     ->searchable()
234 221
                     ->sortable(),
235
-                Tables\Columns\BadgeColumn::make('status')
222
+                Tables\Columns\TextColumn::make('status')
223
+                    ->badge()
236 224
                     ->label('Status')
237 225
                     ->colors([
238 226
                         'primary' => 'open',
@@ -242,7 +230,7 @@ class AccountResource extends Resource
242 230
                         'danger' => 'closed',
243 231
                     ])
244 232
                     ->icons([
245
-                        'heroicon-o-cash' => 'open',
233
+                        'heroicon-o-currency-dollar' => 'open',
246 234
                         'heroicon-o-clock' => 'active',
247 235
                         'heroicon-o-status-offline' => 'dormant',
248 236
                         'heroicon-o-exclamation' => 'restricted',
@@ -252,22 +240,23 @@ class AccountResource extends Resource
252 240
                 Tables\Columns\TextColumn::make('opening_balance')
253 241
                     ->label('Current Balance')
254 242
                     ->sortable()
255
-                    ->money(static fn ($record) => $record->currency_code, true),
243
+                    ->currency(static fn (Account $record) => $record->currency_code, true),
256 244
             ])
257 245
             ->filters([
258 246
                 //
259 247
             ])
260 248
             ->actions([
249
+                Tables\Actions\EditAction::make(),
261 250
                 Tables\Actions\Action::make('update_balance')
262
-                    ->hidden(static fn (Account $record) => $record->currency_code === Currency::getDefaultCurrency())
251
+                    ->hidden(static fn (Account $record) => $record->currency_code === Currency::getDefaultCurrencyCode())
263 252
                     ->label('Update Balance')
264 253
                     ->icon('heroicon-o-currency-dollar')
265 254
                     ->requiresConfirmation()
266
-                    ->modalSubheading('Are you sure you want to update the balance with the latest exchange rate?')
255
+                    ->modalDescription('Are you sure you want to update the balance with the latest exchange rate?')
267 256
                     ->before(static function (Tables\Actions\Action $action, Account $record) {
268
-                        if ($record->currency_code !== Currency::getDefaultCurrency()) {
257
+                        if ($record->currency_code !== Currency::getDefaultCurrencyCode()) {
269 258
                             $currencyService = app(CurrencyService::class);
270
-                            $defaultCurrency = Currency::getDefaultCurrency();
259
+                            $defaultCurrency = Currency::getDefaultCurrencyCode();
271 260
                             $cachedExchangeRate = $currencyService->getCachedExchangeRate($defaultCurrency, $record->currency_code);
272 261
                             $oldExchangeRate = $record->currency->rate;
273 262
 
@@ -284,18 +273,23 @@ class AccountResource extends Resource
284 273
                         }
285 274
                     })
286 275
                     ->action(static function (Account $record) {
287
-                        if ($record->currency_code !== Currency::getDefaultCurrency()) {
276
+                        if ($record->currency_code !== Currency::getDefaultCurrencyCode()) {
288 277
                             $currencyService = app(CurrencyService::class);
289
-                            $defaultCurrency = Currency::getDefaultCurrency();
278
+                            $defaultCurrency = Currency::getDefaultCurrencyCode();
290 279
                             $cachedExchangeRate = $currencyService->getCachedExchangeRate($defaultCurrency, $record->currency_code);
291 280
                             $oldExchangeRate = $record->currency->rate;
292 281
 
293
-                            $originalBalanceInOriginalCurrency = $record->opening_balance / $oldExchangeRate;
294
-                            $currencyPrecision = $record->currency->precision;
295
-
296 282
                             if ($cachedExchangeRate !== $oldExchangeRate) {
297
-                                $record->opening_balance = round($originalBalanceInOriginalCurrency * $cachedExchangeRate, $currencyPrecision);
283
+
284
+                                $scale = 10 ** $record->currency->precision;
285
+                                $cleanedBalance = (int)filter_var($record->opening_balance, FILTER_SANITIZE_NUMBER_INT);
286
+
287
+                                $newBalance = ($cachedExchangeRate / $oldExchangeRate) * $cleanedBalance;
288
+                                $newBalanceInt = (int)round($newBalance, $scale);
289
+
290
+                                $record->opening_balance = money($newBalanceInt, $record->currency_code)->getValue();
298 291
                                 $record->currency->rate = $cachedExchangeRate;
292
+
299 293
                                 $record->currency->save();
300 294
                                 $record->save();
301 295
                             }
@@ -307,57 +301,17 @@ class AccountResource extends Resource
307 301
                                 ->send();
308 302
                         }
309 303
                     }),
310
-                Tables\Actions\EditAction::make(),
311
-                Tables\Actions\DeleteAction::make()
312
-                    ->modalHeading('Delete Account')
313
-                    ->requiresConfirmation()
314
-                    ->before(static function (Tables\Actions\DeleteAction $action, Account $record) {
315
-                        if ($record->enabled) {
316
-                            Notification::make()
317
-                                ->danger()
318
-                                ->title('Action Denied')
319
-                                ->body(__('The :name account is currently set as your default account and cannot be deleted. Please set a different account as your default before attempting to delete this one.', ['name' => $record->name]))
320
-                                ->persistent()
321
-                                ->send();
322
-
323
-                            $action->cancel();
324
-                        }
325
-                    }),
326 304
             ])
327 305
             ->bulkActions([
328
-                Tables\Actions\DeleteBulkAction::make()
329
-                    ->before(static function (Tables\Actions\DeleteBulkAction $action, Collection $records) {
330
-                        foreach ($records as $record) {
331
-                            if ($record->enabled) {
332
-                                Notification::make()
333
-                                    ->danger()
334
-                                    ->title('Action Denied')
335
-                                    ->body(__('The :name account is currently set as your default account and cannot be deleted. Please set a different account as your default before attempting to delete this one.', ['name' => $record->name]))
336
-                                    ->persistent()
337
-                                    ->send();
338
-
339
-                                $action->cancel();
340
-                            }
341
-                        }
342
-                    }),
306
+                Tables\Actions\BulkActionGroup::make([
307
+                    Tables\Actions\DeleteBulkAction::make(),
308
+                ]),
309
+            ])
310
+            ->emptyStateActions([
311
+                Tables\Actions\CreateAction::make(),
343 312
             ]);
344 313
     }
345 314
 
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
-
361 315
     public static function getRelations(): array
362 316
     {
363 317
         return [

app/Filament/Resources/AccountResource/Pages/CreateAccount.php → app/Filament/Company/Resources/Banking/AccountResource/Pages/CreateAccount.php 查看文件

@@ -1,10 +1,11 @@
1 1
 <?php
2 2
 
3
-namespace App\Filament\Resources\AccountResource\Pages;
3
+namespace App\Filament\Company\Resources\Banking\AccountResource\Pages;
4 4
 
5
-use App\Filament\Resources\AccountResource;
5
+use App\Filament\Company\Resources\Banking\AccountResource;
6 6
 use App\Models\Banking\Account;
7 7
 use App\Traits\HandlesResourceRecordCreation;
8
+use Filament\Actions;
8 9
 use Filament\Resources\Pages\CreateRecord;
9 10
 use Filament\Support\Exceptions\Halt;
10 11
 use Illuminate\Database\Eloquent\Model;
@@ -12,7 +13,7 @@ use Illuminate\Support\Facades\Auth;
12 13
 
13 14
 class CreateAccount extends CreateRecord
14 15
 {
15
-    use  HandlesResourceRecordCreation;
16
+    use HandlesResourceRecordCreation;
16 17
 
17 18
     protected static string $resource = AccountResource::class;
18 19
 

app/Filament/Resources/AccountResource/Pages/EditAccount.php → app/Filament/Company/Resources/Banking/AccountResource/Pages/EditAccount.php 查看文件

@@ -1,12 +1,12 @@
1 1
 <?php
2 2
 
3
-namespace App\Filament\Resources\AccountResource\Pages;
3
+namespace App\Filament\Company\Resources\Banking\AccountResource\Pages;
4 4
 
5
-use App\Filament\Resources\AccountResource;
5
+use App\Filament\Company\Resources\Banking\AccountResource;
6 6
 use App\Models\Banking\Account;
7 7
 use App\Models\Setting\Currency;
8 8
 use App\Traits\HandlesResourceRecordUpdate;
9
-use Filament\Pages\Actions;
9
+use Filament\Actions;
10 10
 use Filament\Resources\Pages\EditRecord;
11 11
 use Filament\Support\Exceptions\Halt;
12 12
 use Illuminate\Database\Eloquent\Model;
@@ -18,14 +18,14 @@ class EditAccount extends EditRecord
18 18
 
19 19
     protected static string $resource = AccountResource::class;
20 20
 
21
-    protected function getActions(): array
21
+    protected function getHeaderActions(): array
22 22
     {
23 23
         return [
24 24
             Actions\DeleteAction::make(),
25 25
         ];
26 26
     }
27 27
 
28
-    protected function getRedirectUrl(): string
28
+    protected function getRedirectUrl(): ?string
29 29
     {
30 30
         return $this->previousUrl;
31 31
     }
@@ -40,7 +40,7 @@ class EditAccount extends EditRecord
40 40
     /**
41 41
      * @throws Halt
42 42
      */
43
-    protected function handleRecordUpdate(Model|Account $record, array $data): Model|Account
43
+    protected function handleRecordUpdate(Account|Model $record, array $data): Model|Account
44 44
     {
45 45
         $user = Auth::user();
46 46
 
@@ -59,8 +59,6 @@ class EditAccount extends EditRecord
59 59
             );
60 60
         }
61 61
 
62
-        $this->handleRecordUpdateWithUniqueField($record, $data, $user);
63
-
64
-        return parent::handleRecordUpdate($record, $data);
62
+        return $this->handleRecordUpdateWithUniqueField($record, $data, $user);
65 63
     }
66 64
 }

app/Filament/Resources/AccountResource/Pages/ListAccounts.php → app/Filament/Company/Resources/Banking/AccountResource/Pages/ListAccounts.php 查看文件

@@ -1,16 +1,16 @@
1 1
 <?php
2 2
 
3
-namespace App\Filament\Resources\AccountResource\Pages;
3
+namespace App\Filament\Company\Resources\Banking\AccountResource\Pages;
4 4
 
5
-use App\Filament\Resources\AccountResource;
6
-use Filament\Pages\Actions;
5
+use App\Filament\Company\Resources\Banking\AccountResource;
6
+use Filament\Actions;
7 7
 use Filament\Resources\Pages\ListRecords;
8 8
 
9 9
 class ListAccounts extends ListRecords
10 10
 {
11 11
     protected static string $resource = AccountResource::class;
12 12
 
13
-    protected function getActions(): array
13
+    protected function getHeaderActions(): array
14 14
     {
15 15
         return [
16 16
             Actions\CreateAction::make(),

+ 155
- 0
app/Filament/Company/Resources/Setting/CategoryResource.php 查看文件

@@ -0,0 +1,155 @@
1
+<?php
2
+
3
+namespace App\Filament\Company\Resources\Setting;
4
+
5
+use App\Enums\CategoryType;
6
+use App\Filament\Company\Resources\Setting\CategoryResource\Pages;
7
+use App\Models\Setting\Category;
8
+use Closure;
9
+use Exception;
10
+use Filament\Forms;
11
+use Filament\Forms\Form;
12
+use Filament\Notifications\Notification;
13
+use Filament\Resources\Resource;
14
+use Filament\Tables;
15
+use Filament\Tables\Table;
16
+use Illuminate\Database\Eloquent\Collection;
17
+use Wallo\FilamentSelectify\Components\ToggleButton;
18
+
19
+class CategoryResource extends Resource
20
+{
21
+    protected static ?string $model = Category::class;
22
+
23
+    protected static ?string $navigationIcon = 'heroicon-o-folder';
24
+
25
+    protected static ?string $navigationGroup = 'Settings';
26
+
27
+    protected static ?string $slug = 'settings/categories';
28
+
29
+    public static function form(Form $form): Form
30
+    {
31
+        return $form
32
+            ->schema([
33
+                Forms\Components\Section::make('General')
34
+                    ->schema([
35
+                        Forms\Components\TextInput::make('name')
36
+                            ->label('Name')
37
+                            ->autofocus()
38
+                            ->required()
39
+                            ->maxLength(255)
40
+                            ->rule(static function (Forms\Get $get, Forms\Components\Component $component): Closure {
41
+                                return static function (string $attribute, $value, Closure $fail) use ($get, $component) {
42
+                                    $existingCategory = Category::where('company_id', auth()->user()->currentCompany->id)
43
+                                                                ->where('name', $value)
44
+                                                                ->where('type', $get('type'))
45
+                                                                ->first();
46
+
47
+                                    if ($existingCategory && $existingCategory->getKey() !== $component->getRecord()?->getKey()) {
48
+                                        $type = ucwords($get('type'));
49
+                                        $fail("The {$type} category \"{$value}\" already exists.");
50
+                                    }
51
+                                };
52
+                            }),
53
+                        Forms\Components\Select::make('type')
54
+                            ->options(CategoryType::class)
55
+                            ->required()
56
+                            ->native(false)
57
+                            ->label('Type'),
58
+                        Forms\Components\ColorPicker::make('color')
59
+                            ->required()
60
+                            ->label('Color'),
61
+                        ToggleButton::make('enabled')
62
+                            ->label('Default'),
63
+                    ])->columns(),
64
+            ]);
65
+    }
66
+
67
+    /**
68
+     * @throws Exception
69
+     */
70
+    public static function table(Table $table): Table
71
+    {
72
+        return $table
73
+            ->columns([
74
+                Tables\Columns\TextColumn::make('name')
75
+                    ->label('Name')
76
+                    ->weight('semibold')
77
+                    ->icon(static fn (Category $record) => $record->enabled ? 'heroicon-o-lock-closed' : null)
78
+                    ->tooltip(static fn (Category $record) => $record->enabled ? "Default {$record->type->getLabel()} Category" : null)
79
+                    ->iconPosition('after')
80
+                    ->searchable()
81
+                    ->sortable(),
82
+                Tables\Columns\TextColumn::make('type')
83
+                    ->label('Type')
84
+                    ->sortable()
85
+                    ->searchable(),
86
+                Tables\Columns\ColorColumn::make('color')
87
+                    ->label('Color')
88
+                    ->copyable()
89
+                    ->copyMessage('Color code copied'),
90
+            ])
91
+            ->filters([
92
+                Tables\Filters\SelectFilter::make('type')
93
+                    ->label('Type')
94
+                    ->multiple()
95
+                    ->options(CategoryType::class),
96
+            ])
97
+            ->actions([
98
+                Tables\Actions\EditAction::make(),
99
+                Tables\Actions\DeleteAction::make()
100
+                    ->before(static function (Category $record, Tables\Actions\DeleteAction $action) {
101
+                        if ($record->enabled) {
102
+                            Notification::make()
103
+                                ->danger()
104
+                                ->title('Action Denied')
105
+                                ->body(__('The :name category is currently set as your default :type category and cannot be deleted. Please set a different category as your default before attempting to delete this one.', ['name' => $record->name, 'type' => $record->type->getLabel()]))
106
+                                ->persistent()
107
+                                ->send();
108
+
109
+                            $action->cancel();
110
+                        }
111
+                    }),
112
+            ])
113
+            ->bulkActions([
114
+                Tables\Actions\BulkActionGroup::make([
115
+                    Tables\Actions\DeleteBulkAction::make()
116
+                        ->before(static function (Collection $records, Tables\Actions\DeleteBulkAction $action) {
117
+                            $defaultCategories = $records->filter(static function (Category $record) {
118
+                                return $record->enabled;
119
+                            });
120
+
121
+                            if ($defaultCategories->isNotEmpty()) {
122
+                                $defaultCategoryNames = $defaultCategories->pluck('name')->toArray();
123
+
124
+                                Notification::make()
125
+                                    ->danger()
126
+                                    ->title('Action Denied')
127
+                                    ->body(static function () use ($defaultCategoryNames) {
128
+                                        $message = __('The following categories are currently set as your default and cannot be deleted. Please set a different category as your default before attempting to delete these ones.') . "<br><br>";
129
+                                        $message .= implode("<br>", array_map(static function ($name) {
130
+                                            return "&bull; " . $name;
131
+                                        }, $defaultCategoryNames));
132
+                                        return $message;
133
+                                    })
134
+                                    ->persistent()
135
+                                    ->send();
136
+
137
+                                $action->cancel();
138
+                            }
139
+                        }),
140
+                ]),
141
+            ])
142
+            ->emptyStateActions([
143
+                Tables\Actions\CreateAction::make(),
144
+            ]);
145
+    }
146
+
147
+    public static function getPages(): array
148
+    {
149
+        return [
150
+            'index' => Pages\ListCategories::route('/'),
151
+            'create' => Pages\CreateCategory::route('/create'),
152
+            'edit' => Pages\EditCategory::route('/{record}/edit'),
153
+        ];
154
+    }
155
+}

app/Filament/Resources/CategoryResource/Pages/CreateCategory.php → app/Filament/Company/Resources/Setting/CategoryResource/Pages/CreateCategory.php 查看文件

@@ -1,14 +1,13 @@
1 1
 <?php
2 2
 
3
-namespace App\Filament\Resources\CategoryResource\Pages;
3
+namespace App\Filament\Company\Resources\Setting\CategoryResource\Pages;
4 4
 
5
-use App\Filament\Resources\CategoryResource;
5
+use App\Filament\Company\Resources\Setting\CategoryResource;
6 6
 use App\Models\Setting\Category;
7 7
 use App\Traits\HandlesResourceRecordCreation;
8 8
 use Filament\Resources\Pages\CreateRecord;
9 9
 use Filament\Support\Exceptions\Halt;
10 10
 use Illuminate\Database\Eloquent\Model;
11
-use Illuminate\Support\Facades\Auth;
12 11
 
13 12
 class CreateCategory extends CreateRecord
14 13
 {
@@ -33,10 +32,10 @@ class CreateCategory extends CreateRecord
33 32
      */
34 33
     protected function handleRecordCreation(array $data): Model
35 34
     {
36
-        $user = Auth::user();
35
+        $user = auth()->user();
37 36
 
38 37
         if (!$user) {
39
-            throw new Halt('No authenticated user found.');
38
+            throw new Halt('No authenticated user found');
40 39
         }
41 40
 
42 41
         return $this->handleRecordCreationWithUniqueField($data, new Category(), $user, 'type');

app/Filament/Resources/CategoryResource/Pages/EditCategory.php → app/Filament/Company/Resources/Setting/CategoryResource/Pages/EditCategory.php 查看文件

@@ -1,14 +1,13 @@
1 1
 <?php
2 2
 
3
-namespace App\Filament\Resources\CategoryResource\Pages;
3
+namespace App\Filament\Company\Resources\Setting\CategoryResource\Pages;
4 4
 
5
-use App\Filament\Resources\CategoryResource;
5
+use App\Filament\Company\Resources\Setting\CategoryResource;
6 6
 use App\Traits\HandlesResourceRecordUpdate;
7
-use Filament\Pages\Actions;
7
+use Filament\Actions;
8 8
 use Filament\Resources\Pages\EditRecord;
9 9
 use Filament\Support\Exceptions\Halt;
10 10
 use Illuminate\Database\Eloquent\Model;
11
-use Illuminate\Support\Facades\Auth;
12 11
 
13 12
 class EditCategory extends EditRecord
14 13
 {
@@ -16,7 +15,7 @@ class EditCategory extends EditRecord
16 15
 
17 16
     protected static string $resource = CategoryResource::class;
18 17
 
19
-    protected function getActions(): array
18
+    protected function getHeaderActions(): array
20 19
     {
21 20
         return [
22 21
             Actions\DeleteAction::make(),
@@ -40,10 +39,10 @@ class EditCategory extends EditRecord
40 39
      */
41 40
     protected function handleRecordUpdate(Model $record, array $data): Model
42 41
     {
43
-        $user = Auth::user();
42
+        $user = auth()->user();
44 43
 
45 44
         if (!$user) {
46
-            throw new Halt('No authenticated user found.');
45
+            throw new Halt('No authenticated user found');
47 46
         }
48 47
 
49 48
         return $this->handleRecordUpdateWithUniqueField($record, $data, $user, 'type');

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

@@ -0,0 +1,19 @@
1
+<?php
2
+
3
+namespace App\Filament\Company\Resources\Setting\CategoryResource\Pages;
4
+
5
+use App\Filament\Company\Resources\Setting\CategoryResource;
6
+use Filament\Actions;
7
+use Filament\Resources\Pages\ListRecords;
8
+
9
+class ListCategories extends ListRecords
10
+{
11
+    protected static string $resource = CategoryResource::class;
12
+
13
+    protected function getHeaderActions(): array
14
+    {
15
+        return [
16
+            Actions\CreateAction::make(),
17
+        ];
18
+    }
19
+}

+ 189
- 0
app/Filament/Company/Resources/Setting/CurrencyResource.php 查看文件

@@ -0,0 +1,189 @@
1
+<?php
2
+
3
+namespace App\Filament\Company\Resources\Setting;
4
+
5
+use App\Filament\Company\Resources\Setting\CurrencyResource\Pages;
6
+use App\Filament\Company\Resources\Setting\CurrencyResource\RelationManagers;
7
+use App\Models\Setting\Currency;
8
+use App\Services\CurrencyService;
9
+use Filament\Forms;
10
+use Filament\Forms\Form;
11
+use Filament\Resources\Resource;
12
+use Filament\Support\Colors\Color;
13
+use Filament\Tables;
14
+use Filament\Tables\Table;
15
+use Illuminate\Database\Eloquent\Builder;
16
+use Illuminate\Database\Eloquent\SoftDeletingScope;
17
+use Wallo\FilamentSelectify\Components\ButtonGroup;
18
+use Wallo\FilamentSelectify\Components\ToggleButton;
19
+
20
+class CurrencyResource extends Resource
21
+{
22
+    protected static ?string $model = Currency::class;
23
+
24
+    protected static ?string $navigationIcon = 'heroicon-o-currency-dollar';
25
+
26
+    protected static ?string $navigationGroup = 'Settings';
27
+
28
+    protected static ?string $slug = 'settings/currencies';
29
+
30
+    public static function form(Form $form): Form
31
+    {
32
+        return $form
33
+            ->schema([
34
+                Forms\Components\Section::make('General')
35
+                    ->schema([
36
+                        Forms\Components\Select::make('code')
37
+                        ->label('Code')
38
+                        ->options(Currency::getAvailableCurrencyCodes())
39
+                        ->searchable()
40
+                        ->placeholder('Select a currency code...')
41
+                        ->live()
42
+                        ->required()
43
+                        ->hidden(static fn (Forms\Get $get): bool => $get('enabled'))
44
+                        ->afterStateUpdated(static function (Forms\Set $set, $state) {
45
+                            if ($state === null) {
46
+                                return;
47
+                            }
48
+
49
+                            $code = $state;
50
+
51
+                            $allCurrencies = Currency::getAllCurrencies();
52
+
53
+                            $selectedCurrencyCode = $allCurrencies[$code] ?? [];
54
+
55
+                            $currencyService = app(CurrencyService::class);
56
+                            $defaultCurrencyCode = Currency::getDefaultCurrencyCode();
57
+                            $rate = 1;
58
+
59
+                            if ($defaultCurrencyCode !== null) {
60
+                                $rate = $currencyService->getCachedExchangeRate($defaultCurrencyCode, $code);
61
+                            }
62
+
63
+                            $set('name', $selectedCurrencyCode['name'] ?? '');
64
+                            $set('rate', $rate);
65
+                            $set('precision', $selectedCurrencyCode['precision'] ?? '');
66
+                            $set('symbol', $selectedCurrencyCode['symbol'] ?? '');
67
+                            $set('symbol_first', $selectedCurrencyCode['symbol_first'] ?? '');
68
+                            $set('decimal_mark', $selectedCurrencyCode['decimal_mark'] ?? '');
69
+                            $set('thousands_separator', $selectedCurrencyCode['thousands_separator'] ?? '');
70
+                        }),
71
+                        Forms\Components\TextInput::make('code')
72
+                            ->label('Code')
73
+                            ->hidden(static fn (Forms\Get $get): bool => !$get('enabled'))
74
+                            ->disabled(static fn (Forms\Get $get): bool => $get('enabled'))
75
+                            ->required(),
76
+                        Forms\Components\TextInput::make('name')
77
+                            ->label('Name')
78
+                            ->maxLength(50)
79
+                            ->required(),
80
+                        Forms\Components\TextInput::make('rate')
81
+                            ->label('Rate')
82
+                            ->dehydrateStateUsing(static fn (Forms\Get $get, $state): float => $get('enabled') ? '1.0' : (float) $state)
83
+                            ->numeric()
84
+                            ->live()
85
+                            ->disabled(static fn (Forms\Get $get): bool => $get('enabled'))
86
+                            ->required(),
87
+                        Forms\Components\Select::make('precision')
88
+                            ->label('Precision')
89
+                            ->searchable()
90
+                            ->placeholder('Select a precision...')
91
+                            ->options(['0', '1', '2', '3', '4'])
92
+                            ->required(),
93
+                        Forms\Components\TextInput::make('symbol')
94
+                            ->label('Symbol')
95
+                            ->maxLength(5)
96
+                            ->required(),
97
+                        Forms\Components\Select::make('symbol_first')
98
+                            ->label('Symbol Position')
99
+                            ->searchable()
100
+                            ->boolean('Before Amount', 'After Amount', 'Select the currency symbol position...')
101
+                            ->required(),
102
+                        Forms\Components\TextInput::make('decimal_mark')
103
+                            ->label('Decimal Separator')
104
+                            ->maxLength(1)
105
+                            ->required(),
106
+                        Forms\Components\TextInput::make('thousands_separator')
107
+                            ->label('Thousands Separator')
108
+                            ->maxLength(1)
109
+                            ->required(),
110
+                        ToggleButton::make('enabled')
111
+                            ->label('Default Currency')
112
+                            ->live()
113
+                            ->offColor(Color::Red)
114
+                            ->onColor(Color::Indigo)
115
+                            ->afterStateUpdated(static function (Forms\Set $set, Forms\Get $get, $state) {
116
+                                $enabled = $state;
117
+                                $code = $get('code');
118
+                                $currencyService = app(CurrencyService::class);
119
+
120
+                                if ($enabled) {
121
+                                    $rate = 1;
122
+                                } else {
123
+                                    $defaultCurrencyCode = Currency::getDefaultCurrencyCode();
124
+                                    $rate = $defaultCurrencyCode ? $currencyService->getCachedExchangeRate($defaultCurrencyCode, $code) : 1;
125
+                                }
126
+
127
+                                $set('rate', $rate);
128
+                            }),
129
+                    ])->columns(),
130
+            ]);
131
+    }
132
+
133
+    public static function table(Table $table): Table
134
+    {
135
+        return $table
136
+            ->columns([
137
+                Tables\Columns\TextColumn::make('name')
138
+                    ->label('Name')
139
+                    ->weight('semibold')
140
+                    ->icon(static fn (Currency $record) => $record->enabled ? 'heroicon-o-lock-closed' : null)
141
+                    ->tooltip(static fn (Currency $record) => $record->enabled ? 'Default Currency' : null)
142
+                    ->iconPosition('after')
143
+                    ->searchable()
144
+                    ->sortable(),
145
+                Tables\Columns\TextColumn::make('code')
146
+                    ->label('Code')
147
+                    ->searchable()
148
+                    ->sortable(),
149
+                Tables\Columns\TextColumn::make('symbol')
150
+                    ->label('Symbol')
151
+                    ->searchable()
152
+                    ->sortable(),
153
+                Tables\Columns\TextColumn::make('rate')
154
+                    ->label('Rate')
155
+                    ->searchable()
156
+                    ->sortable(),
157
+            ])
158
+            ->filters([
159
+                //
160
+            ])
161
+            ->actions([
162
+                Tables\Actions\EditAction::make(),
163
+            ])
164
+            ->bulkActions([
165
+                Tables\Actions\BulkActionGroup::make([
166
+                    Tables\Actions\DeleteBulkAction::make(),
167
+                ]),
168
+            ])
169
+            ->emptyStateActions([
170
+                Tables\Actions\CreateAction::make(),
171
+            ]);
172
+    }
173
+
174
+    public static function getRelations(): array
175
+    {
176
+        return [
177
+            //
178
+        ];
179
+    }
180
+
181
+    public static function getPages(): array
182
+    {
183
+        return [
184
+            'index' => Pages\ListCurrencies::route('/'),
185
+            'create' => Pages\CreateCurrency::route('/create'),
186
+            'edit' => Pages\EditCurrency::route('/{record}/edit'),
187
+        ];
188
+    }
189
+}

app/Filament/Resources/CurrencyResource/Pages/CreateCurrency.php → app/Filament/Company/Resources/Setting/CurrencyResource/Pages/CreateCurrency.php 查看文件

@@ -1,16 +1,15 @@
1 1
 <?php
2 2
 
3
-namespace App\Filament\Resources\CurrencyResource\Pages;
3
+namespace App\Filament\Company\Resources\Setting\CurrencyResource\Pages;
4 4
 
5
-use App\Filament\Resources\CurrencyResource;
5
+use App\Filament\Company\Resources\Setting\CurrencyResource;
6 6
 use App\Models\Setting\Currency;
7 7
 use App\Traits\HandlesResourceRecordCreation;
8
-use Filament\Pages\Actions;
8
+use Filament\Actions;
9 9
 use Filament\Resources\Pages\CreateRecord;
10 10
 use Filament\Support\Exceptions\Halt;
11 11
 use Illuminate\Database\Eloquent\Model;
12 12
 use Illuminate\Support\Facades\Auth;
13
-use Illuminate\Support\Facades\DB;
14 13
 
15 14
 class CreateCurrency extends CreateRecord
16 15
 {
@@ -38,7 +37,7 @@ class CreateCurrency extends CreateRecord
38 37
         $user = Auth::user();
39 38
 
40 39
         if (!$user) {
41
-            throw new Halt('No authenticated user found.');
40
+            throw new Halt('No authenticated user found');
42 41
         }
43 42
 
44 43
         return $this->handleRecordCreationWithUniqueField($data, new Currency(), $user);

app/Filament/Resources/CurrencyResource/Pages/EditCurrency.php → app/Filament/Company/Resources/Setting/CurrencyResource/Pages/EditCurrency.php 查看文件

@@ -1,10 +1,10 @@
1 1
 <?php
2 2
 
3
-namespace App\Filament\Resources\CurrencyResource\Pages;
3
+namespace App\Filament\Company\Resources\Setting\CurrencyResource\Pages;
4 4
 
5
-use App\Filament\Resources\CurrencyResource;
5
+use App\Filament\Company\Resources\Setting\CurrencyResource;
6 6
 use App\Traits\HandlesResourceRecordUpdate;
7
-use Filament\Pages\Actions;
7
+use Filament\Actions;
8 8
 use Filament\Resources\Pages\EditRecord;
9 9
 use Filament\Support\Exceptions\Halt;
10 10
 use Illuminate\Database\Eloquent\Model;
@@ -16,14 +16,14 @@ class EditCurrency extends EditRecord
16 16
 
17 17
     protected static string $resource = CurrencyResource::class;
18 18
 
19
-    protected function getActions(): array
19
+    protected function getHeaderActions(): array
20 20
     {
21 21
         return [
22 22
             Actions\DeleteAction::make(),
23 23
         ];
24 24
     }
25 25
 
26
-    protected function getRedirectUrl(): string
26
+    protected function getRedirectUrl(): ?string
27 27
     {
28 28
         return $this->previousUrl;
29 29
     }
@@ -43,10 +43,9 @@ class EditCurrency extends EditRecord
43 43
         $user = Auth::user();
44 44
 
45 45
         if (!$user) {
46
-            throw new Halt('No authenticated user found.');
46
+            throw new Halt('No authenticated user found');
47 47
         }
48 48
 
49 49
         return $this->handleRecordUpdateWithUniqueField($record, $data, $user);
50 50
     }
51
-
52 51
 }

app/Filament/Resources/CurrencyResource/Pages/ListCurrencies.php → app/Filament/Company/Resources/Setting/CurrencyResource/Pages/ListCurrencies.php 查看文件

@@ -1,16 +1,16 @@
1 1
 <?php
2 2
 
3
-namespace App\Filament\Resources\CurrencyResource\Pages;
3
+namespace App\Filament\Company\Resources\Setting\CurrencyResource\Pages;
4 4
 
5
-use App\Filament\Resources\CurrencyResource;
6
-use Filament\Pages\Actions;
5
+use App\Filament\Company\Resources\Setting\CurrencyResource;
6
+use Filament\Actions;
7 7
 use Filament\Resources\Pages\ListRecords;
8 8
 
9 9
 class ListCurrencies extends ListRecords
10 10
 {
11 11
     protected static string $resource = CurrencyResource::class;
12 12
 
13
-    protected function getActions(): array
13
+    protected function getHeaderActions(): array
14 14
     {
15 15
         return [
16 16
             Actions\CreateAction::make(),

+ 197
- 0
app/Filament/Company/Resources/Setting/DiscountResource.php 查看文件

@@ -0,0 +1,197 @@
1
+<?php
2
+
3
+namespace App\Filament\Company\Resources\Setting;
4
+
5
+use App\Enums\DiscountComputation;
6
+use App\Enums\DiscountScope;
7
+use App\Enums\DiscountType;
8
+use App\Filament\Company\Resources\Setting\DiscountResource\Pages;
9
+use App\Filament\Company\Resources\Setting\DiscountResource\RelationManagers;
10
+use App\Models\Setting\Category;
11
+use App\Models\Setting\Discount;
12
+use Closure;
13
+use Filament\Forms;
14
+use Filament\Forms\Form;
15
+use Filament\Resources\Resource;
16
+use Filament\Tables;
17
+use Filament\Tables\Table;
18
+use Illuminate\Database\Eloquent\Builder;
19
+use Illuminate\Database\Eloquent\SoftDeletingScope;
20
+use Wallo\FilamentSelectify\Components\ToggleButton;
21
+
22
+class DiscountResource extends Resource
23
+{
24
+    protected static ?string $model = Discount::class;
25
+
26
+    protected static ?string $navigationIcon = 'heroicon-o-tag';
27
+
28
+    protected static ?string $navigationGroup = 'Settings';
29
+
30
+    protected static ?string $slug = 'settings/discounts';
31
+
32
+    public static function form(Form $form): Form
33
+    {
34
+        return $form
35
+            ->schema([
36
+                Forms\Components\Section::make('General')
37
+                    ->schema([
38
+                        Forms\Components\TextInput::make('name')
39
+                            ->label('Name')
40
+                            ->autofocus()
41
+                            ->required()
42
+                            ->maxLength(255)
43
+                            ->rule(static function (Forms\Get $get, Forms\Components\Component $component): Closure {
44
+                                return static function (string $attribute, $value, Closure $fail) use ($get, $component) {
45
+                                    $existingCategory = Discount::where('company_id', auth()->user()->currentCompany->id)
46
+                                                                ->where('name', $value)
47
+                                                                ->where('type', $get('type'))
48
+                                                                ->first();
49
+
50
+                                    if ($existingCategory && $existingCategory->getKey() !== $component->getRecord()?->getKey()) {
51
+                                        $type = $get('type')->getLabel();
52
+                                        $fail("The {$type} discount \"{$value}\" already exists.");
53
+                                    }
54
+                                };
55
+                            }),
56
+                        Forms\Components\TextInput::make('description')
57
+                            ->label('Description'),
58
+                        Forms\Components\Select::make('computation')
59
+                            ->label('Computation')
60
+                            ->options(DiscountComputation::class)
61
+                            ->default(DiscountComputation::Percentage)
62
+                            ->live()
63
+                            ->native(false)
64
+                            ->required(),
65
+                        Forms\Components\TextInput::make('rate')
66
+                            ->label('Rate')
67
+                            ->numeric()
68
+                            ->suffix(static function (Forms\Get $get) {
69
+                                $computation = $get('computation');
70
+
71
+                                if ($computation === DiscountComputation::Percentage) {
72
+                                    return '%';
73
+                                }
74
+
75
+                                return null;
76
+                            })
77
+                            ->required(),
78
+                        Forms\Components\Select::make('type')
79
+                            ->label('Type')
80
+                            ->options(DiscountType::class)
81
+                            ->default(DiscountType::Sales)
82
+                            ->native(false)
83
+                            ->required(),
84
+                        Forms\Components\Select::make('scope')
85
+                            ->label('Scope')
86
+                            ->options(DiscountScope::class)
87
+                            ->native(false),
88
+                        Forms\Components\DateTimePicker::make('start_date')
89
+                            ->label('Start Date')
90
+                            ->native(false)
91
+                            ->minDate(static function ($context, Discount|null $record = null) {
92
+                                if ($context === 'create') {
93
+                                    return today()->addDay();
94
+                                }
95
+
96
+                                return $record?->start_date?->isFuture() ? today()->addDay() : $record?->start_date;
97
+                            })
98
+                            ->maxDate(static function (callable $get, Discount|null $record = null) {
99
+                                $end_date = $get('end_date') ?? $record?->end_date;
100
+
101
+                                return $end_date ?: today()->addYear();
102
+                            })
103
+                            ->format('Y-m-d H:i:s')
104
+                            ->displayFormat('F d, Y H:i')
105
+                            ->seconds(false)
106
+                            ->live()
107
+                            ->disabled(static fn ($context, Discount|null $record = null) => $context === 'edit' && $record?->start_date?->isPast() ?? false)
108
+                            ->helperText(static fn (Forms\Components\DateTimePicker $component) => $component->isDisabled() ? 'Start date cannot be changed after the discount has begun.' : null),
109
+                        Forms\Components\DateTimePicker::make('end_date')
110
+                            ->label('End Date')
111
+                            ->native(false)
112
+                            ->live()
113
+                            ->minDate(static function (callable $get, Discount|null $record = null) {
114
+                                $start_date = $get('start_date') ?? $record?->start_date;
115
+
116
+                                return $start_date ?: today()->addDay();
117
+                            })
118
+                            ->maxDate(today()->addYear())
119
+                            ->format('Y-m-d H:i:s')
120
+                            ->displayFormat('F d, Y H:i')
121
+                            ->seconds(false),
122
+                        ToggleButton::make('enabled')
123
+                            ->label('Default'),
124
+                ])->columns(),
125
+            ]);
126
+    }
127
+
128
+    public static function table(Table $table): Table
129
+    {
130
+        return $table
131
+            ->columns([
132
+                Tables\Columns\TextColumn::make('name')
133
+                    ->label('Name')
134
+                    ->weight('semibold')
135
+                    ->icon(static fn (Discount $record) => $record->enabled ? 'heroicon-o-lock-closed' : null)
136
+                    ->tooltip(static fn (Discount $record) => $record->enabled ? "Default {$record->type->getLabel()} Discount" : null)
137
+                    ->iconPosition('after')
138
+                    ->searchable()
139
+                    ->sortable(),
140
+                Tables\Columns\TextColumn::make('computation')
141
+                    ->label('Computation')
142
+                    ->searchable()
143
+                    ->sortable(),
144
+                Tables\Columns\TextColumn::make('rate')
145
+                    ->label('Rate')
146
+                    ->formatStateUsing(static fn (Discount $record) => $record->rate . ($record->computation === DiscountComputation::Percentage ? '%' : null))
147
+                    ->searchable()
148
+                    ->sortable(),
149
+                Tables\Columns\TextColumn::make('type')
150
+                    ->label('Type')
151
+                    ->badge()
152
+                    ->searchable()
153
+                    ->sortable(),
154
+                Tables\Columns\TextColumn::make('start_date')
155
+                    ->label('Start Date')
156
+                    ->formatStateUsing(static fn (Discount $record) => $record->start_date ? $record->start_date->format('F d, Y H:i') : 'N/A')
157
+                    ->searchable()
158
+                    ->sortable(),
159
+                Tables\Columns\TextColumn::make('end_date')
160
+                    ->label('End Date')
161
+                    ->formatStateUsing(static fn (Discount $record) => $record->end_date ? $record->end_date->format('F d, Y H:i') : 'N/A')
162
+                    ->color(static fn(Discount $record) => $record->end_date?->isPast() ? 'danger' : null)
163
+                    ->searchable()
164
+                    ->sortable(),
165
+            ])
166
+            ->filters([
167
+                //
168
+            ])
169
+            ->actions([
170
+                Tables\Actions\EditAction::make(),
171
+            ])
172
+            ->bulkActions([
173
+                Tables\Actions\BulkActionGroup::make([
174
+                    Tables\Actions\DeleteBulkAction::make(),
175
+                ]),
176
+            ])
177
+            ->emptyStateActions([
178
+                Tables\Actions\CreateAction::make(),
179
+            ]);
180
+    }
181
+
182
+    public static function getRelations(): array
183
+    {
184
+        return [
185
+            //
186
+        ];
187
+    }
188
+
189
+    public static function getPages(): array
190
+    {
191
+        return [
192
+            'index' => Pages\ListDiscounts::route('/'),
193
+            'create' => Pages\CreateDiscount::route('/create'),
194
+            'edit' => Pages\EditDiscount::route('/{record}/edit'),
195
+        ];
196
+    }
197
+}

app/Filament/Resources/DiscountResource/Pages/CreateDiscount.php → app/Filament/Company/Resources/Setting/DiscountResource/Pages/CreateDiscount.php 查看文件

@@ -1,14 +1,14 @@
1 1
 <?php
2 2
 
3
-namespace App\Filament\Resources\DiscountResource\Pages;
3
+namespace App\Filament\Company\Resources\Setting\DiscountResource\Pages;
4 4
 
5
-use App\Filament\Resources\DiscountResource;
5
+use App\Filament\Company\Resources\Setting\DiscountResource;
6 6
 use App\Models\Setting\Discount;
7 7
 use App\Traits\HandlesResourceRecordCreation;
8
+use Filament\Actions;
8 9
 use Filament\Resources\Pages\CreateRecord;
9 10
 use Filament\Support\Exceptions\Halt;
10 11
 use Illuminate\Database\Eloquent\Model;
11
-use Illuminate\Support\Facades\Auth;
12 12
 
13 13
 class CreateDiscount extends CreateRecord
14 14
 {
@@ -23,7 +23,7 @@ class CreateDiscount extends CreateRecord
23 23
 
24 24
     protected function mutateFormDataBeforeCreate(array $data): array
25 25
     {
26
-        $data['enabled'] = (bool)($data['enabled']);
26
+        $data['enabled'] = (bool)$data['enabled'];
27 27
 
28 28
         return $data;
29 29
     }
@@ -33,10 +33,10 @@ class CreateDiscount extends CreateRecord
33 33
      */
34 34
     protected function handleRecordCreation(array $data): Model
35 35
     {
36
-        $user = Auth::user();
36
+        $user = auth()->user();
37 37
 
38 38
         if (!$user) {
39
-            throw new Halt('No authenticated user found.');
39
+            throw new Halt('No authenticated user found');
40 40
         }
41 41
 
42 42
         return $this->handleRecordCreationWithUniqueField($data, new Discount(), $user, 'type');

app/Filament/Resources/DiscountResource/Pages/EditDiscount.php → app/Filament/Company/Resources/Setting/DiscountResource/Pages/EditDiscount.php 查看文件

@@ -1,14 +1,13 @@
1 1
 <?php
2 2
 
3
-namespace App\Filament\Resources\DiscountResource\Pages;
3
+namespace App\Filament\Company\Resources\Setting\DiscountResource\Pages;
4 4
 
5
-use App\Filament\Resources\DiscountResource;
5
+use App\Filament\Company\Resources\Setting\DiscountResource;
6 6
 use App\Traits\HandlesResourceRecordUpdate;
7
-use Filament\Pages\Actions;
7
+use Filament\Actions;
8 8
 use Filament\Resources\Pages\EditRecord;
9 9
 use Filament\Support\Exceptions\Halt;
10 10
 use Illuminate\Database\Eloquent\Model;
11
-use Illuminate\Support\Facades\Auth;
12 11
 
13 12
 class EditDiscount extends EditRecord
14 13
 {
@@ -16,7 +15,7 @@ class EditDiscount extends EditRecord
16 15
 
17 16
     protected static string $resource = DiscountResource::class;
18 17
 
19
-    protected function getActions(): array
18
+    protected function getHeaderActions(): array
20 19
     {
21 20
         return [
22 21
             Actions\DeleteAction::make(),
@@ -40,10 +39,10 @@ class EditDiscount extends EditRecord
40 39
      */
41 40
     protected function handleRecordUpdate(Model $record, array $data): Model
42 41
     {
43
-        $user = Auth::user();
42
+        $user = auth()->user();
44 43
 
45 44
         if (!$user) {
46
-            throw new Halt('No authenticated user found.');
45
+            throw new Halt('No authenticated user found');
47 46
         }
48 47
 
49 48
         return $this->handleRecordUpdateWithUniqueField($record, $data, $user, 'type');

app/Filament/Resources/DiscountResource/Pages/ListDiscounts.php → app/Filament/Company/Resources/Setting/DiscountResource/Pages/ListDiscounts.php 查看文件

@@ -1,16 +1,16 @@
1 1
 <?php
2 2
 
3
-namespace App\Filament\Resources\DiscountResource\Pages;
3
+namespace App\Filament\Company\Resources\Setting\DiscountResource\Pages;
4 4
 
5
-use App\Filament\Resources\DiscountResource;
6
-use Filament\Pages\Actions;
5
+use App\Filament\Company\Resources\Setting\DiscountResource;
6
+use Filament\Actions;
7 7
 use Filament\Resources\Pages\ListRecords;
8 8
 
9 9
 class ListDiscounts extends ListRecords
10 10
 {
11 11
     protected static string $resource = DiscountResource::class;
12 12
 
13
-    protected function getActions(): array
13
+    protected function getHeaderActions(): array
14 14
     {
15 15
         return [
16 16
             Actions\CreateAction::make(),

+ 191
- 0
app/Filament/Company/Resources/Setting/TaxResource.php 查看文件

@@ -0,0 +1,191 @@
1
+<?php
2
+
3
+namespace App\Filament\Company\Resources\Setting;
4
+
5
+use App\Enums\TaxComputation;
6
+use App\Enums\TaxScope;
7
+use App\Enums\TaxType;
8
+use App\Filament\Company\Resources\Setting\TaxResource\Pages;
9
+use App\Filament\Company\Resources\Setting\TaxResource\RelationManagers;
10
+use App\Models\Setting\Category;
11
+use App\Models\Setting\Tax;
12
+use Closure;
13
+use Filament\Forms;
14
+use Filament\Forms\Form;
15
+use Filament\Notifications\Notification;
16
+use Filament\Resources\Resource;
17
+use Filament\Tables;
18
+use Filament\Tables\Table;
19
+use Illuminate\Database\Eloquent\Builder;
20
+use Illuminate\Database\Eloquent\Collection;
21
+use Illuminate\Database\Eloquent\SoftDeletingScope;
22
+use Wallo\FilamentSelectify\Components\ToggleButton;
23
+
24
+class TaxResource extends Resource
25
+{
26
+    protected static ?string $model = Tax::class;
27
+
28
+    protected static ?string $navigationIcon = 'heroicon-o-receipt-percent';
29
+
30
+    protected static ?string $navigationGroup = 'Settings';
31
+
32
+    protected static ?string $slug = 'settings/taxes';
33
+
34
+    public static function form(Form $form): Form
35
+    {
36
+        return $form
37
+            ->schema([
38
+                Forms\Components\Section::make('General')
39
+                    ->schema([
40
+                        Forms\Components\TextInput::make('name')
41
+                            ->label('Name')
42
+                            ->autofocus()
43
+                            ->required()
44
+                            ->maxLength(255)
45
+                            ->rule(static function (Forms\Get $get, Forms\Components\Component $component): Closure {
46
+                                return static function (string $attribute, $value, Closure $fail) use ($get, $component) {
47
+                                    $existingCategory = Tax::where('company_id', auth()->user()->currentCompany->id)
48
+                                                                ->where('name', $value)
49
+                                                                ->where('type', $get('type'))
50
+                                                                ->first();
51
+
52
+                                    if ($existingCategory && $existingCategory->getKey() !== $component->getRecord()?->getKey()) {
53
+                                        $type = $get('type')->getLabel();
54
+                                        $fail("The {$type} tax \"{$value}\" already exists.");
55
+                                    }
56
+                                };
57
+                            }),
58
+                        Forms\Components\TextInput::make('description')
59
+                            ->label('Description'),
60
+                        Forms\Components\Select::make('computation')
61
+                            ->label('Computation')
62
+                            ->options(TaxComputation::class)
63
+                            ->default(TaxComputation::Percentage)
64
+                            ->live()
65
+                            ->native(false)
66
+                            ->required(),
67
+                        Forms\Components\TextInput::make('rate')
68
+                            ->label('Rate')
69
+                            ->numeric()
70
+                            ->suffix(static function (Forms\Get $get) {
71
+                                $computation = $get('computation');
72
+
73
+                                if ($computation === TaxComputation::Percentage) {
74
+                                    return '%';
75
+                                }
76
+
77
+                                return null;
78
+                            })
79
+                            ->required(),
80
+                        Forms\Components\Select::make('type')
81
+                            ->label('Type')
82
+                            ->options(TaxType::class)
83
+                            ->default(TaxType::Sales)
84
+                            ->native(false)
85
+                            ->required(),
86
+                        Forms\Components\Select::make('scope')
87
+                            ->label('Scope')
88
+                            ->options(TaxScope::class)
89
+                            ->native(false),
90
+                        ToggleButton::make('enabled')
91
+                            ->label('Enabled'),
92
+                    ])->columns(),
93
+            ]);
94
+    }
95
+
96
+    public static function table(Table $table): Table
97
+    {
98
+        return $table
99
+            ->columns([
100
+                Tables\Columns\TextColumn::make('name')
101
+                    ->label('Name')
102
+                    ->weight('semibold')
103
+                    ->icon(static fn (Tax $record) => $record->enabled ? 'heroicon-o-lock-closed' : null)
104
+                    ->tooltip(static fn (Tax $record) => $record->enabled ? "Default {$record->type->getLabel()} Tax" : null)
105
+                    ->iconPosition('after')
106
+                    ->searchable()
107
+                    ->sortable(),
108
+                Tables\Columns\TextColumn::make('computation')
109
+                    ->label('Computation')
110
+                    ->searchable()
111
+                    ->sortable(),
112
+                Tables\Columns\TextColumn::make('rate')
113
+                    ->label('Rate')
114
+                    ->formatStateUsing(static fn (Tax $record) => $record->rate . ($record->computation === TaxComputation::Percentage ? '%' : null))
115
+                    ->searchable()
116
+                    ->sortable(),
117
+                Tables\Columns\TextColumn::make('type')
118
+                    ->label('Type')
119
+                    ->badge()
120
+                    ->searchable()
121
+                    ->sortable(),
122
+            ])
123
+            ->filters([
124
+                //
125
+            ])
126
+            ->actions([
127
+                Tables\Actions\EditAction::make(),
128
+                Tables\Actions\DeleteAction::make()
129
+                    ->before(static function (Tables\Actions\DeleteAction $action, Tax $record) {
130
+                        if ($record->enabled) {
131
+                            Notification::make()
132
+                                ->danger()
133
+                                ->title('Action Denied')
134
+                                ->body(__('The :name tax is currently set as your default :type tax and cannot be deleted. Please set a different tax as your default before attempting to delete this one.', ['name' => $record->name, 'type' => $record->type->getLabel()]))
135
+                                ->persistent()
136
+                                ->send();
137
+
138
+                            $action->cancel();
139
+                        }
140
+                    }),
141
+            ])
142
+            ->bulkActions([
143
+                Tables\Actions\BulkActionGroup::make([
144
+                    Tables\Actions\DeleteBulkAction::make()
145
+                        ->before(static function (Collection $records, Tables\Actions\DeleteBulkAction $action) {
146
+                            $defaultTaxes = $records->filter(static function (Tax $record) {
147
+                                return $record->enabled;
148
+                            });
149
+
150
+                            if ($defaultTaxes->isNotEmpty()) {
151
+                                $defaultTaxNames = $defaultTaxes->pluck('name')->toArray();
152
+
153
+                                Notification::make()
154
+                                    ->danger()
155
+                                    ->title('Action Denied')
156
+                                    ->body(static function () use ($defaultTaxNames) {
157
+                                        $message = __('The following taxes are currently set as your default and cannot be deleted. Please set a different tax as your default before attempting to delete these ones.') . "<br><br>";
158
+                                        $message .= implode("<br>", array_map(static function ($name) {
159
+                                            return "&bull; " . $name;
160
+                                        }, $defaultTaxNames));
161
+                                        return $message;
162
+                                    })
163
+                                    ->persistent()
164
+                                    ->send();
165
+
166
+                                $action->cancel();
167
+                            }
168
+                        }),
169
+                ]),
170
+            ])
171
+            ->emptyStateActions([
172
+                Tables\Actions\CreateAction::make(),
173
+            ]);
174
+    }
175
+
176
+    public static function getRelations(): array
177
+    {
178
+        return [
179
+            //
180
+        ];
181
+    }
182
+
183
+    public static function getPages(): array
184
+    {
185
+        return [
186
+            'index' => Pages\ListTaxes::route('/'),
187
+            'create' => Pages\CreateTax::route('/create'),
188
+            'edit' => Pages\EditTax::route('/{record}/edit'),
189
+        ];
190
+    }
191
+}

app/Filament/Resources/TaxResource/Pages/CreateTax.php → app/Filament/Company/Resources/Setting/TaxResource/Pages/CreateTax.php 查看文件

@@ -1,14 +1,14 @@
1 1
 <?php
2 2
 
3
-namespace App\Filament\Resources\TaxResource\Pages;
3
+namespace App\Filament\Company\Resources\Setting\TaxResource\Pages;
4 4
 
5
-use App\Filament\Resources\TaxResource;
5
+use App\Filament\Company\Resources\Setting\TaxResource;
6 6
 use App\Models\Setting\Tax;
7 7
 use App\Traits\HandlesResourceRecordCreation;
8
+use Filament\Actions;
8 9
 use Filament\Resources\Pages\CreateRecord;
9 10
 use Filament\Support\Exceptions\Halt;
10 11
 use Illuminate\Database\Eloquent\Model;
11
-use Illuminate\Support\Facades\Auth;
12 12
 
13 13
 class CreateTax extends CreateRecord
14 14
 {
@@ -33,10 +33,10 @@ class CreateTax extends CreateRecord
33 33
      */
34 34
     protected function handleRecordCreation(array $data): Model
35 35
     {
36
-        $user = Auth::user();
36
+        $user = auth()->user();
37 37
 
38 38
         if (!$user) {
39
-            throw new Halt('No authenticated user found.');
39
+            throw new Halt('No authenticated user found');
40 40
         }
41 41
 
42 42
         return $this->handleRecordCreationWithUniqueField($data, new Tax(), $user, 'type');

app/Filament/Resources/TaxResource/Pages/EditTax.php → app/Filament/Company/Resources/Setting/TaxResource/Pages/EditTax.php 查看文件

@@ -1,14 +1,13 @@
1 1
 <?php
2 2
 
3
-namespace App\Filament\Resources\TaxResource\Pages;
3
+namespace App\Filament\Company\Resources\Setting\TaxResource\Pages;
4 4
 
5
-use App\Filament\Resources\TaxResource;
5
+use App\Filament\Company\Resources\Setting\TaxResource;
6 6
 use App\Traits\HandlesResourceRecordUpdate;
7
-use Filament\Pages\Actions;
7
+use Filament\Actions;
8 8
 use Filament\Resources\Pages\EditRecord;
9 9
 use Filament\Support\Exceptions\Halt;
10 10
 use Illuminate\Database\Eloquent\Model;
11
-use Illuminate\Support\Facades\Auth;
12 11
 
13 12
 class EditTax extends EditRecord
14 13
 {
@@ -16,7 +15,7 @@ class EditTax extends EditRecord
16 15
 
17 16
     protected static string $resource = TaxResource::class;
18 17
 
19
-    protected function getActions(): array
18
+    protected function getHeaderActions(): array
20 19
     {
21 20
         return [
22 21
             Actions\DeleteAction::make(),
@@ -28,7 +27,7 @@ class EditTax extends EditRecord
28 27
         return $this->previousUrl;
29 28
     }
30 29
 
31
-    protected function mutateFormDataBeforeUpdate(array $data): array
30
+    protected function mutateFormDataBeforeSave(array $data): array
32 31
     {
33 32
         $data['enabled'] = (bool)$data['enabled'];
34 33
 
@@ -40,10 +39,10 @@ class EditTax extends EditRecord
40 39
      */
41 40
     protected function handleRecordUpdate(Model $record, array $data): Model
42 41
     {
43
-        $user = Auth::user();
42
+        $user = auth()->user();
44 43
 
45 44
         if (!$user) {
46
-            throw new Halt('No authenticated user found.');
45
+            throw new Halt('No authenticated user found');
47 46
         }
48 47
 
49 48
         return $this->handleRecordUpdateWithUniqueField($record, $data, $user, 'type');

app/Filament/Resources/TaxResource/Pages/ListTaxes.php → app/Filament/Company/Resources/Setting/TaxResource/Pages/ListTaxes.php 查看文件

@@ -1,16 +1,16 @@
1 1
 <?php
2 2
 
3
-namespace App\Filament\Resources\TaxResource\Pages;
3
+namespace App\Filament\Company\Resources\Setting\TaxResource\Pages;
4 4
 
5
-use App\Filament\Resources\TaxResource;
6
-use Filament\Pages\Actions;
5
+use App\Filament\Company\Resources\Setting\TaxResource;
6
+use Filament\Actions;
7 7
 use Filament\Resources\Pages\ListRecords;
8 8
 
9 9
 class ListTaxes extends ListRecords
10 10
 {
11 11
     protected static string $resource = TaxResource::class;
12 12
 
13
-    protected function getActions(): array
13
+    protected function getHeaderActions(): array
14 14
     {
15 15
         return [
16 16
             Actions\CreateAction::make(),

+ 0
- 39
app/Filament/Pages/Companies.php 查看文件

@@ -1,39 +0,0 @@
1
-<?php
2
-
3
-namespace App\Filament\Pages;
4
-
5
-use Filament\Pages\Page;
6
-use Illuminate\Support\Facades\Auth;
7
-use Wallo\FilamentCompanies\FilamentCompanies;
8
-
9
-class Companies extends Page
10
-{
11
-    protected static ?string $navigationIcon = 'heroicon-o-office-building';
12
-
13
-    protected static string $view = 'filament.pages.companies';
14
-
15
-    protected static function shouldRegisterNavigation(): bool
16
-    {
17
-        return Auth::user()->currentCompany->id === 1;
18
-    }
19
-
20
-    public function mount(): void
21
-    {
22
-        abort_unless(Auth::user()->currentCompany->id === 1, 403);
23
-    }
24
-
25
-    protected function getHeaderWidgets(): array
26
-    {
27
-        return [
28
-            Widgets\Companies\Charts\CompanyStatsOverview::class,
29
-            Widgets\Companies\Charts\CumulativeGrowth::class,
30
-            Widgets\Companies\Charts\CumulativeTotal::class,
31
-            Widgets\Companies\Tables\Companies::class,
32
-        ];
33
-    }
34
-
35
-    protected static function getNavigationBadge(): ?string
36
-    {
37
-        return FilamentCompanies::companyModel()::count();
38
-    }
39
-}

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

@@ -1,51 +0,0 @@
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
-}

+ 0
- 10
app/Filament/Pages/Dashboard.php 查看文件

@@ -1,10 +0,0 @@
1
-<?php
2
-
3
-namespace App\Filament\Pages;
4
-
5
-use Filament\Pages\Dashboard as BasePage;
6
-
7
-class Dashboard extends BasePage
8
-{
9
-    //
10
-}

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

@@ -1,51 +0,0 @@
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 DefaultSetting extends Page
11
-{
12
-    public mixed $company;
13
-
14
-    protected static ?string $navigationIcon = 'heroicon-o-adjustments';
15
-
16
-    protected static ?string $navigationLabel = 'Default';
17
-
18
-    protected static ?string $navigationGroup = 'Settings';
19
-
20
-    protected static ?string $title = 'Default';
21
-
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
-    }
51
-}

+ 0
- 38
app/Filament/Pages/Employees.php 查看文件

@@ -1,38 +0,0 @@
1
-<?php
2
-
3
-namespace App\Filament\Pages;
4
-
5
-use Filament\Pages\Page;
6
-use Illuminate\Support\Facades\Auth;
7
-use Wallo\FilamentCompanies\FilamentCompanies;
8
-
9
-class Employees extends Page
10
-{
11
-    protected static ?string $navigationIcon = 'heroicon-o-users';
12
-
13
-    protected static string $view = 'filament.pages.employees';
14
-
15
-    protected static function shouldRegisterNavigation(): bool
16
-    {
17
-        return Auth::user()->currentCompany->id === 1;
18
-    }
19
-
20
-    public function mount(): void
21
-    {
22
-        abort_unless(Auth::user()->currentCompany->id === 1, 403);
23
-    }
24
-
25
-    protected function getHeaderWidgets(): array
26
-    {
27
-        return [
28
-            Widgets\Employees\Charts\CumulativeRoles::class,
29
-            Widgets\Employees\Charts\CumulativeGrowth::class,
30
-            Widgets\Employees\Tables\Employees::class,
31
-        ];
32
-    }
33
-
34
-    protected static function getNavigationBadge(): ?string
35
-    {
36
-        return FilamentCompanies::employeeshipModel()::count();
37
-    }
38
-}

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

@@ -1,51 +0,0 @@
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 Invoice extends Page
11
-{
12
-    public mixed $company;
13
-
14
-    protected static ?string $navigationIcon = 'heroicon-o-document-text';
15
-
16
-    protected static ?string $navigationLabel = 'Invoice';
17
-
18
-    protected static ?string $navigationGroup = 'Settings';
19
-
20
-    protected static ?string $title = 'Invoice';
21
-
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
-    }
51
-}

+ 0
- 36
app/Filament/Pages/Users.php 查看文件

@@ -1,36 +0,0 @@
1
-<?php
2
-
3
-namespace App\Filament\Pages;
4
-
5
-use Filament\Pages\Page;
6
-use Illuminate\Support\Facades\Auth;
7
-use Wallo\FilamentCompanies\FilamentCompanies;
8
-
9
-class Users extends Page
10
-{
11
-    protected static ?string $navigationIcon = 'heroicon-o-user-group';
12
-
13
-    protected static string $view = 'filament.pages.users';
14
-
15
-    protected static function shouldRegisterNavigation(): bool
16
-    {
17
-        return Auth::user()->currentCompany->id === 1;
18
-    }
19
-
20
-    public function mount(): void
21
-    {
22
-        abort_unless(Auth::user()->currentCompany->id === 1, 403);
23
-    }
24
-
25
-    protected function getHeaderWidgets(): array
26
-    {
27
-        return [
28
-            Widgets\Users\Tables\Users::class,
29
-        ];
30
-    }
31
-
32
-    protected static function getNavigationBadge(): ?string
33
-    {
34
-        return FilamentCompanies::userModel()::count();
35
-    }
36
-}

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

@@ -1,148 +0,0 @@
1
-<?php
2
-
3
-namespace App\Filament\Pages\Widgets\Companies\Charts;
4
-
5
-use App\Models\Company;
6
-use Exception;
7
-use Filament\Widgets\StatsOverviewWidget;
8
-use InvalidArgumentException;
9
-
10
-class CompanyStatsOverview extends StatsOverviewWidget
11
-{
12
-    protected static ?int $sort = 0;
13
-
14
-    /**
15
-     * Holt's Linear Trend Method
16
-     * @throws Exception
17
-     */
18
-    protected function holtLinearTrend($data, $alpha, $beta): array
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
-
24
-        $level = $data[0];
25
-        $trend = $data[1] - $data[0];
26
-
27
-        $forecast = [];
28
-        foreach ($data as $iValue) {
29
-            $prev_level = $level;
30
-            $level = $alpha * $iValue + (1 - $alpha) * ($prev_level + $trend);
31
-            $trend = $beta * ($level - $prev_level) + (1 - $beta) * $trend;
32
-            $forecast[] = $level + $trend;
33
-        }
34
-
35
-        return $forecast;
36
-    }
37
-
38
-    /**
39
-     * Adjusts the alpha and beta parameters based on the model's performance
40
-     * @throws Exception
41
-     */
42
-    protected function adjustTrendParameters($data, $alpha, $beta): array
43
-    {
44
-        $minError = PHP_INT_MAX;
45
-        $bestAlpha = $alpha;
46
-        $bestBeta = $beta;
47
-
48
-        // try different alpha and beta values within a reasonable range
49
-        for ($testAlpha = 0.1; $testAlpha <= 1; $testAlpha += 0.1) {
50
-            for ($testBeta = 0.1; $testBeta <= 1; $testBeta += 0.1) {
51
-                $forecast = $this->holtLinearTrend($data, $testAlpha, $testBeta);
52
-                $error = $this->calculateError($data, $forecast);
53
-                if ($error < $minError) {
54
-                    $minError = $error;
55
-                    $bestAlpha = $testAlpha;
56
-                    $bestBeta = $testBeta;
57
-                }
58
-            }
59
-        }
60
-
61
-        return [$bestAlpha, $bestBeta];
62
-    }
63
-
64
-    /**
65
-     * Calculates the sum of squared errors between the actual data and the forecast
66
-     */
67
-    protected function calculateError($data, $forecast): float
68
-    {
69
-        $error = 0;
70
-        for ($i = 0, $iMax = count($data); $i < $iMax; $i++) {
71
-            $error += ($data[$i] - $forecast[$i]) ** 2;
72
-        }
73
-
74
-        return $error;
75
-    }
76
-
77
-    /**
78
-     * Chart Options
79
-     * @throws Exception
80
-     */
81
-    protected function getCards(): array
82
-    {
83
-        // Define constants
84
-        $alpha = 0.8;
85
-        $beta = 0.2;
86
-
87
-        // Define time variables
88
-        $startOfYear = today()->startOfYear();
89
-        $today = today();
90
-
91
-        // Get Company Data
92
-        $companyData = Company::selectRaw("COUNT(*) as aggregate, YEARWEEK(created_at, 3) as week")
93
-            ->whereBetween('created_at', [$startOfYear, $today])
94
-            ->groupByRaw('week')
95
-            ->get();
96
-
97
-        // Initialize weeks
98
-        $weeks = [];
99
-        for ($week = $startOfYear->copy(); $week->lte($today); $week->addWeek()) {
100
-            $weeks[$week->format('oW')] = 0;
101
-        }
102
-
103
-        // Get Weekly Data for Company Data
104
-        $weeklyData = collect($weeks)->mapWithKeys(static function ($value, $week) use ($companyData) {
105
-            $matchingData = $companyData->firstWhere('week', $week);
106
-            return [$week => $matchingData->aggregate ?? 0];
107
-        });
108
-
109
-        // Calculate total companies per week
110
-        $totalCompanies = $weeklyData->reduce(static function ($carry, $value) {
111
-            $carry[] = ($carry ? end($carry) : 0) + $value;
112
-            return $carry;
113
-        }, []);
114
-
115
-        // Calculate new companies and percentage change per week
116
-        $newCompanies = [0];
117
-        $weeklyPercentageChange = [0];
118
-
119
-        for ($i = 1, $iMax = count($totalCompanies); $i < $iMax; $i++) {
120
-            $newCompanies[] = $totalCompanies[$i] - $totalCompanies[$i - 1];
121
-            $weeklyPercentageChange[] = $totalCompanies[$i - 1] !== 0 ? ($newCompanies[$i] / $totalCompanies[$i - 1]) * 100 : 0;
122
-        }
123
-
124
-        // Ensure $weeklyDataArray contains at least two values and all values are numeric
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
-        }
129
-
130
-        // Adjust alpha and beta parameters
131
-        [$alpha, $beta] = $this->adjustTrendParameters($weeklyDataArray, $alpha, $beta);
132
-
133
-        // Calculate Holt's Linear Trend Forecast for next week
134
-        $holtForecast = $this->holtLinearTrend($weeklyDataArray, $alpha, $beta);
135
-        $expectedNewCompanies = round(end($holtForecast));
136
-
137
-        // Calculate average weekly growth rate
138
-        $totalWeeks = $startOfYear->diffInWeeks($today);
139
-        $averageWeeklyGrowthRate = round(array_sum($weeklyPercentageChange) / $totalWeeks, 2);
140
-
141
-        // Company Stats Overview Cards
142
-        return [
143
-            StatsOverviewWidget\Card::make("New Companies Forecast (Holt's Linear Trend)", $expectedNewCompanies),
144
-            StatsOverviewWidget\Card::make('Average Weekly Growth Rate', $averageWeeklyGrowthRate . '%'),
145
-            StatsOverviewWidget\Card::make('Personal Companies', Company::sum('personal_company')),
146
-        ];
147
-    }
148
-}

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

@@ -1,154 +0,0 @@
1
-<?php
2
-
3
-namespace App\Filament\Pages\Widgets\Companies\Charts;
4
-
5
-use App\Models\Company;
6
-use Illuminate\Contracts\View\View;
7
-use Leandrocfe\FilamentApexCharts\Widgets\ApexChartWidget;
8
-
9
-class CumulativeGrowth extends ApexChartWidget
10
-{
11
-    protected static ?int $sort = 1;
12
-
13
-    /**
14
-     * Chart Id
15
-     *
16
-     * @var string
17
-     */
18
-    protected static string $chartId = 'cumulative-growth';
19
-
20
-    protected static ?string $pollingInterval = null;
21
-
22
-
23
-    protected function getOptions(): array
24
-    {
25
-        $startOfYear = today()->startOfYear();
26
-        $today = today();
27
-
28
-        // Company data
29
-        $companyData = Company::selectRaw("COUNT(*) as aggregate, DATE_FORMAT(created_at, '%Y%m') as month")
30
-            ->whereBetween('created_at', [$startOfYear, $today])
31
-            ->groupByRaw('month')
32
-            ->get();
33
-
34
-        $months = [];
35
-        for ($month = $startOfYear->copy(); $month->lte($today); $month->addMonth()) {
36
-            $months[$month->format('Ym')] = 0;
37
-        }
38
-
39
-        $monthlyData = collect($months)->mapWithKeys(static function ($value, $month) use ($companyData) {
40
-            $matchingData = $companyData->firstWhere('month', $month);
41
-            return [$month => $matchingData->aggregate ?? 0];
42
-        });
43
-
44
-        $totalCompanies = $monthlyData->reduce(static function ($carry, $value) {
45
-            $carry[] = ($carry ? end($carry) : 0) + $value;
46
-            return $carry;
47
-        }, []);
48
-
49
-        // Calculate percentage increase and increase in companies per month
50
-        $newCompanies = [0];
51
-        $monthlyPercentageChange = [0];
52
-
53
-        for ($i = 1, $iMax = count($totalCompanies); $i < $iMax; $i++) {
54
-            $newCompanies[] = $totalCompanies[$i] - $totalCompanies[$i - 1];
55
-            $monthlyPercentageChange[] = $totalCompanies[$i - 1] !== 0 ? ($newCompanies[$i] / $totalCompanies[$i - 1]) * 100 : 0;
56
-        }
57
-
58
-        $labels = collect($months)->keys()->map(static function ($month) {
59
-            $year = substr($month, 0, 4);
60
-            $monthNumber = substr($month, 4);
61
-
62
-            return today()->startOfYear()->setDate($year, $monthNumber, 1)->format('M');
63
-        });
64
-
65
-        return [
66
-            'chart' => [
67
-                'type' => 'area',
68
-                'height' => 350,
69
-                'fontFamily' => 'inherit',
70
-                'toolbar' => [
71
-                    'show' => false,
72
-                ],
73
-            ],
74
-            'title' => [
75
-                'text' => 'Cumulative Growth',
76
-                'align' => 'left',
77
-                'margin' => 20,
78
-                'style' => [
79
-                    'fontSize' => '20px',
80
-                ],
81
-            ],
82
-            'subtitle' => [
83
-                'text' => 'Monthly',
84
-                'align' => 'left',
85
-                'margin' => 20,
86
-                'style' => [
87
-                    'fontSize' => '14px',
88
-                ],
89
-            ],
90
-            'series' => [
91
-                [
92
-                    'name' => 'Growth Rate',
93
-                    'data' => $monthlyPercentageChange,
94
-                ],
95
-                [
96
-                    'name' => 'New Companies',
97
-                    'data' => $newCompanies,
98
-                ],
99
-            ],
100
-            'xaxis' => [
101
-                'categories' => $labels,
102
-                'position' => 'bottom',
103
-                'labels' => [
104
-                    'show' => true,
105
-                    'style' => [
106
-                        'colors' => '#9ca3af',
107
-                    ],
108
-                ],
109
-            ],
110
-            'yaxis' => [
111
-                'decimalsInFloat' => 2,
112
-                'labels' => [
113
-                    'style' => [
114
-                        'colors' => '#9ca3af',
115
-                    ],
116
-                ],
117
-            ],
118
-            'dataLabels' => [
119
-                'enabled' => false,
120
-            ],
121
-            'legend' => [
122
-                'show' => true,
123
-                'position' => 'bottom',
124
-                'horizontalAlign' => 'center',
125
-                'floating' => false,
126
-                'labels' => [
127
-                    'useSeriesColors' => true,
128
-                ],
129
-                'markers' => [
130
-                    'width' => 30,
131
-                    'height' => 8,
132
-                    'radius' => 0,
133
-                ],
134
-            ],
135
-            'colors' => ['#454DC8', '#22d3ee'],
136
-            'fill' => [
137
-                'type' => 'gradient',
138
-                'gradient' => [
139
-                    'opacityFrom' => 0.6,
140
-                    'opacityTo' => 0.8,
141
-                ],
142
-            ],
143
-            'markers' => [
144
-                'size' => 4,
145
-                'hover' => [
146
-                    'size' => 7,
147
-                ],
148
-            ],
149
-            'stroke' => [
150
-                'curve' => 'smooth',
151
-            ],
152
-        ];
153
-    }
154
-}

+ 0
- 143
app/Filament/Pages/Widgets/Companies/Charts/CumulativeTotal.php 查看文件

@@ -1,143 +0,0 @@
1
-<?php
2
-
3
-namespace App\Filament\Pages\Widgets\Companies\Charts;
4
-
5
-use App\Models\Company;
6
-use Illuminate\Contracts\View\View;
7
-use Leandrocfe\FilamentApexCharts\Widgets\ApexChartWidget;
8
-
9
-class CumulativeTotal extends ApexChartWidget
10
-{
11
-    protected static ?int $sort = 2;
12
-
13
-    /**
14
-     * Chart Id
15
-     *
16
-     * @var string
17
-     */
18
-    protected static string $chartId = 'cumulative-total';
19
-
20
-    protected static ?string $pollingInterval = null;
21
-
22
-
23
-    protected function getOptions(): array
24
-    {
25
-        $startOfYear = today()->startOfYear();
26
-        $today = today();
27
-
28
-        // Company data
29
-        $companyData = Company::selectRaw("COUNT(*) as aggregate, DATE_FORMAT(created_at, '%Y%m') as month")
30
-            ->whereBetween('created_at', [$startOfYear, $today])
31
-            ->groupByRaw('month')
32
-            ->get();
33
-
34
-        $months = [];
35
-        for ($month = $startOfYear->copy(); $month->lte($today); $month->addMonth()) {
36
-            $months[$month->format('Ym')] = 0;
37
-        }
38
-
39
-        $monthlyData = collect($months)->mapWithKeys(static function ($value, $month) use ($companyData) {
40
-            $matchingData = $companyData->firstWhere('month', $month);
41
-            return [$month => $matchingData->aggregate ?? 0];
42
-        });
43
-
44
-        $totalCompanies = $monthlyData->reduce(static function ($carry, $value) {
45
-            $carry[] = ($carry ? end($carry) : 0) + $value;
46
-            return $carry;
47
-        }, []);
48
-
49
-        // Calculate exponential smoothing for total companies
50
-        $alpha = 0.3; // Smoothing factor, between 0 and 1
51
-        $smoothedTotalCompanies = [];
52
-
53
-        $smoothedTotalCompanies[0] = $totalCompanies[0]; // Initialize the first smoothed value
54
-        for ($i = 1, $iMax = count($totalCompanies); $i < $iMax; $i++) {
55
-            $smoothedTotalCompanies[$i] = $alpha * $totalCompanies[$i] + (1 - $alpha) * $smoothedTotalCompanies[$i - 1];
56
-        }
57
-
58
-        $labels = collect($months)->keys()->map(static function ($month) {
59
-            $year = substr($month, 0, 4);
60
-            $monthNumber = substr($month, 4);
61
-
62
-            return today()->startOfYear()->setDate($year, $monthNumber, 1)->format('M');
63
-        });
64
-
65
-        return [
66
-            'chart' => [
67
-                'type' => 'line',
68
-                'height' => 350,
69
-                'fontFamily' => 'inherit',
70
-                'toolbar' => [
71
-                    'show' => false,
72
-                ],
73
-            ],
74
-            'title' => [
75
-                'text' => 'Cumulative Total',
76
-                'align' => 'left',
77
-                'margin' => 20,
78
-                'style' => [
79
-                    'fontSize' => '20px',
80
-                ],
81
-            ],
82
-            'subtitle' => [
83
-                'text' => 'Monthly',
84
-                'align' => 'left',
85
-                'margin' => 20,
86
-                'style' => [
87
-                    'fontSize' => '14px',
88
-                ],
89
-            ],
90
-            'series' => [
91
-                [
92
-                    'name' => 'Total Companies',
93
-                    'data' => $totalCompanies,
94
-                ],
95
-                [
96
-                    'name' => 'Smoothed Total Companies',
97
-                    'data' => $smoothedTotalCompanies,
98
-                ],
99
-            ],
100
-            'xaxis' => [
101
-                'type' => 'category',
102
-                'categories' => $labels,
103
-                'position' => 'bottom',
104
-                'labels' => [
105
-                    'show' => true,
106
-                ],
107
-            ],
108
-            'yaxis' => [
109
-                'decimalsInFloat' => 0,
110
-                'labels' => [
111
-                    'show' => true,
112
-                ],
113
-            ],
114
-            'dataLabels' => [
115
-                'enabled' => false,
116
-            ],
117
-            'legend' => [
118
-                'show' => true,
119
-                'position' => 'bottom', // Placing the legend at the right side of the chart.
120
-                'horizontalAlign' => 'center', // Centering the legend items horizontally.
121
-                'floating' => false,
122
-                'labels' => [
123
-                    'useSeriesColors' => true,
124
-                ],
125
-                'markers' => [
126
-                    'width' => 30,
127
-                    'height' => 4,
128
-                    'radius' => 4,
129
-                ],
130
-            ],
131
-            'colors' => ['#454DC8', '#22d3ee'],
132
-            'markers' => [
133
-                'size' => 4,
134
-                'hover' => [
135
-                    'size' => 7,
136
-                ],
137
-            ],
138
-            'stroke' => [
139
-                'curve' => 'smooth',
140
-            ],
141
-        ];
142
-    }
143
-}

+ 0
- 72
app/Filament/Pages/Widgets/Companies/Tables/Companies.php 查看文件

@@ -1,72 +0,0 @@
1
-<?php
2
-
3
-namespace App\Filament\Pages\Widgets\Companies\Tables;
4
-
5
-use App\Models\Company;
6
-use Closure;
7
-use Exception;
8
-use Filament\Tables;
9
-use Filament\Widgets\TableWidget as PageWidget;
10
-use Illuminate\Contracts\Support\Htmlable;
11
-use Illuminate\Database\Eloquent\Builder;
12
-use Illuminate\Database\Eloquent\Relations\Relation;
13
-
14
-class Companies extends PageWidget
15
-{
16
-    protected int | string | array $columnSpan = 'full';
17
-
18
-    protected static ?int $sort = 3;
19
-
20
-    protected function getTableQuery(): Builder|Relation
21
-    {
22
-        return Company::query();
23
-    }
24
-
25
-    protected function getTableHeading(): string|Htmlable|Closure|null
26
-    {
27
-        return null;
28
-    }
29
-
30
-    /**
31
-     * @throws Exception
32
-     */
33
-    protected function getTableFilters(): array
34
-    {
35
-        return [
36
-            Tables\Filters\SelectFilter::make('name')
37
-                ->label('Owner')
38
-                ->searchable()
39
-                ->relationship('owner', 'name'),
40
-            Tables\Filters\TernaryFilter::make('personal_company')
41
-                ->label('Personal Company')
42
-        ];
43
-    }
44
-
45
-    protected function getTableColumns(): array
46
-    {
47
-        return [
48
-            Tables\Columns\ViewColumn::make('owner.name')
49
-                ->view('filament.components.companies.avatar-column')
50
-                ->label('Owner')
51
-                ->sortable()
52
-                ->searchable()
53
-                ->grow(false),
54
-            Tables\Columns\TextColumn::make('name')
55
-                ->label('Company')
56
-                ->sortable()
57
-                ->searchable(),
58
-            Tables\Columns\TextColumn::make('users_count')
59
-                ->label('Employees')
60
-                ->counts('users')
61
-                ->sortable(),
62
-            Tables\Columns\IconColumn::make('personal_company')
63
-                ->label('Personal Company')
64
-                ->boolean()
65
-                ->sortable()
66
-                ->trueIcon('heroicon-o-badge-check')
67
-                ->falseIcon('heroicon-o-x-circle')
68
-                ->trueColor('primary')
69
-                ->falseColor('secondary')
70
-        ];
71
-    }
72
-}

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

@@ -1,153 +0,0 @@
1
-<?php
2
-
3
-namespace App\Filament\Pages\Widgets\Employees\Charts;
4
-
5
-use App\Models\Employeeship;
6
-use Leandrocfe\FilamentApexCharts\Widgets\ApexChartWidget;
7
-
8
-class CumulativeGrowth extends ApexChartWidget
9
-{
10
-    protected static ?int $sort = 1;
11
-
12
-    /**
13
-     * Chart Id
14
-     *
15
-     * @var string
16
-     */
17
-    protected static string $chartId = 'cumulative-growth';
18
-
19
-    protected static ?string $pollingInterval = null;
20
-
21
-    protected function getOptions(): array
22
-    {
23
-        $startOfYear = today()->startOfYear();
24
-        $today = today();
25
-
26
-        // Company data
27
-        $employeeData = Employeeship::selectRaw("COUNT(*) as aggregate, DATE_FORMAT(created_at, '%Y%m') as month")
28
-            ->whereBetween('created_at', [$startOfYear, $today])
29
-            ->groupByRaw('month')
30
-            ->get();
31
-
32
-        $months = [];
33
-        for ($month = $startOfYear->copy(); $month->lte($today); $month->addMonth()) {
34
-            $months[$month->format('Ym')] = 0;
35
-        }
36
-
37
-        $monthlyData = collect($months)->mapWithKeys(static function ($value, $month) use ($employeeData) {
38
-            $matchingData = $employeeData->firstWhere('month', $month);
39
-            return [$month => $matchingData->aggregate ?? 0];
40
-        });
41
-
42
-        $totalEmployees = $monthlyData->reduce(static function ($carry, $value) {
43
-            $carry[] = ($carry ? end($carry) : 0) + $value;
44
-            return $carry;
45
-        }, []);
46
-
47
-        // Calculate percentage increase and increase in companies per month
48
-        $newEmployees = [0];
49
-        $monthlyPercentageChange = [0];
50
-
51
-        for ($i = 1, $iMax = count($totalEmployees); $i < $iMax; $i++) {
52
-            $newEmployees[] = $totalEmployees[$i] - $totalEmployees[$i - 1];
53
-            $monthlyPercentageChange[] = $totalEmployees[$i - 1] !== 0 ? ($newEmployees[$i] / $totalEmployees[$i - 1]) * 100 : 0;
54
-        }
55
-
56
-        $labels = collect($months)->keys()->map(static function ($month) {
57
-            $year = substr($month, 0, 4);
58
-            $monthNumber = substr($month, 4);
59
-
60
-            return today()->startOfYear()->setDate($year, $monthNumber, 1)->format('M');
61
-        });
62
-
63
-
64
-        return [
65
-            'chart' => [
66
-                'type' => 'area',
67
-                'height' => 350,
68
-                'fontFamily' => 'inherit',
69
-                'toolbar' => [
70
-                    'show' => false,
71
-                ],
72
-            ],
73
-            'title' => [
74
-                'text' => 'Cumulative Growth',
75
-                'align' => 'left',
76
-                'margin' => 20,
77
-                'style' => [
78
-                    'fontSize' => '20px',
79
-                ],
80
-            ],
81
-            'subtitle' => [
82
-                'text' => 'Monthly',
83
-                'align' => 'left',
84
-                'margin' => 20,
85
-                'style' => [
86
-                    'fontSize' => '14px',
87
-                ],
88
-            ],
89
-            'series' => [
90
-                [
91
-                    'name' => 'Growth Rate',
92
-                    'data' => $monthlyPercentageChange,
93
-                ],
94
-                [
95
-                    'name' => 'New Employees',
96
-                    'data' => $newEmployees,
97
-                ],
98
-            ],
99
-            'xaxis' => [
100
-                'categories' => $labels,
101
-                'position' => 'bottom',
102
-                'labels' => [
103
-                    'show' => true,
104
-                    'style' => [
105
-                        'colors' => '#9ca3af',
106
-                    ],
107
-                ],
108
-            ],
109
-            'yaxis' => [
110
-                'decimalsInFloat' => 2,
111
-                'labels' => [
112
-                    'style' => [
113
-                        'colors' => '#9ca3af',
114
-                    ],
115
-                ],
116
-            ],
117
-            'dataLabels' => [
118
-                'enabled' => false,
119
-            ],
120
-            'legend' => [
121
-                'show' => true,
122
-                'position' => 'bottom',
123
-                'horizontalAlign' => 'center',
124
-                'floating' => false,
125
-                'labels' => [
126
-                    'useSeriesColors' => true,
127
-                ],
128
-                'markers' => [
129
-                    'width' => 30,
130
-                    'height' => 8,
131
-                    'radius' => 0,
132
-                ],
133
-            ],
134
-            'colors' => ['#454DC8', '#22d3ee'],
135
-            'fill' => [
136
-                'type' => 'gradient',
137
-                'gradient' => [
138
-                    'opacityFrom' => 0.6,
139
-                    'opacityTo' => 0.8,
140
-                ],
141
-            ],
142
-            'markers' => [
143
-                'size' => 4,
144
-                'hover' => [
145
-                    'size' => 7,
146
-                ],
147
-            ],
148
-            'stroke' => [
149
-                'curve' => 'smooth',
150
-            ],
151
-        ];
152
-    }
153
-}

+ 0
- 163
app/Filament/Pages/Widgets/Employees/Charts/CumulativeRoles.php 查看文件

@@ -1,163 +0,0 @@
1
-<?php
2
-
3
-namespace App\Filament\Pages\Widgets\Employees\Charts;
4
-
5
-use App\Models\Employeeship;
6
-use Leandrocfe\FilamentApexCharts\Widgets\ApexChartWidget;
7
-
8
-class CumulativeRoles extends ApexChartWidget
9
-{
10
-    protected static ?int $sort = 0;
11
-
12
-    /**
13
-     * Chart Id
14
-     *
15
-     * @var string
16
-     */
17
-    protected static string $chartId = 'cumulative-roles';
18
-
19
-    protected static ?string $pollingInterval = null;
20
-
21
-    protected function getOptions(): array
22
-    {
23
-        $startOfYear = today()->startOfYear();
24
-        $today = today();
25
-
26
-        // Company data
27
-        $employeeData = Employeeship::selectRaw("COUNT(*) as aggregate, role, DATE_FORMAT(created_at, '%Y%m') as month")
28
-            ->whereBetween('created_at', [$startOfYear, $today])
29
-            ->groupByRaw('month, role')
30
-            ->get();
31
-
32
-        $months = [];
33
-        for ($month = $startOfYear->copy(); $month->lte($today); $month->addMonth()) {
34
-            $months[$month->format('Ym')] = 0;
35
-        }
36
-
37
-        $monthlyRoleData = collect($months)->mapWithKeys(static function ($value, $month) use ($employeeData) {
38
-            $editors = $employeeData->where('role', 'editor')->where('month', $month)->first();
39
-            $admins = $employeeData->where('role', 'admin')->where('month', $month)->first();
40
-
41
-            return [
42
-                $month => [
43
-                    'editors' => $editors->aggregate ?? 0,
44
-                    'admins' => $admins->aggregate ?? 0,
45
-                ]
46
-            ];
47
-        });
48
-
49
-        $cumulativeEditors = $monthlyRoleData->reduce(static function ($carry, $value) {
50
-            $carry[] = ($carry ? end($carry) : 0) + $value['editors'];
51
-            return $carry;
52
-        }, []);
53
-
54
-        $cumulativeAdmins = $monthlyRoleData->reduce(static function ($carry, $value) {
55
-            $carry[] = ($carry ? end($carry) : 0) + $value['admins'];
56
-            return $carry;
57
-        }, []);
58
-
59
-        $labels = collect($months)->keys()->map(static function ($month) {
60
-            $year = substr($month, 0, 4);
61
-            $monthNumber = substr($month, 4);
62
-
63
-            return today()->startOfYear()->setDate($year, $monthNumber, 1)->format('M');
64
-        });
65
-
66
-        return [
67
-            'chart' => [
68
-                'type' => 'bar',
69
-                'height' => 350,
70
-                'fontFamily' => 'inherit',
71
-                'toolbar' => [
72
-                    'show' => false,
73
-                ],
74
-            ],
75
-            'title' => [
76
-                'text' => 'Cumulative Roles',
77
-                'align' => 'left',
78
-                'margin' => 20,
79
-                'style' => [
80
-                    'fontSize' => '20px',
81
-                ],
82
-            ],
83
-            'subtitle' => [
84
-                'text' => 'Monthly',
85
-                'align' => 'left',
86
-                'margin' => 20,
87
-                'style' => [
88
-                    'fontSize' => '14px',
89
-                ],
90
-            ],
91
-            'series' => [
92
-                [
93
-                    'name' => 'Editors',
94
-                    'data' => $cumulativeEditors,
95
-                ],
96
-                [
97
-                    'name' => 'Admins',
98
-                    'data' => $cumulativeAdmins,
99
-                ],
100
-            ],
101
-            'xaxis' => [
102
-                'categories' => $labels,
103
-                'position' => 'bottom',
104
-                'labels' => [
105
-                    'show' => true,
106
-                    'style' => [
107
-                        'colors' => '#9ca3af',
108
-                    ],
109
-                ],
110
-            ],
111
-            'yaxis' => [
112
-                'decimalsInFloat' => 2,
113
-                'labels' => [
114
-                    'style' => [
115
-                        'colors' => '#9ca3af',
116
-                    ],
117
-                ],
118
-            ],
119
-            'dataLabels' => [
120
-                'enabled' => false,
121
-            ],
122
-            'legend' => [
123
-                'show' => true,
124
-                'position' => 'bottom',
125
-                'horizontalAlign' => 'center',
126
-                'floating' => false,
127
-                'labels' => [
128
-                    'useSeriesColors' => true,
129
-                ],
130
-                'markers' => [
131
-                    'width' => 12,
132
-                    'height' => 12,
133
-                    'radius' => 0,
134
-                ],
135
-            ],
136
-            'tooltip' => [
137
-                'enabled' => true,
138
-                'shared' => true,
139
-                'intersect' => false,
140
-                'x' => [
141
-                    'show' => true,
142
-                ],
143
-            ],
144
-            'colors' => ['#454DC8', '#22d3ee'],
145
-            'plotOptions' => [
146
-                'bar' => [
147
-                    'horizontal' => false,
148
-                    'endingShape' => 'rounded',
149
-                    'columnWidth' => '55%',
150
-                ],
151
-            ],
152
-            'markers' => [
153
-                'size' => 4,
154
-                'hover' => [
155
-                    'size' => 7,
156
-                ],
157
-            ],
158
-            'stroke' => [
159
-                'curve' => 'smooth',
160
-            ],
161
-        ];
162
-    }
163
-}

+ 0
- 73
app/Filament/Pages/Widgets/Employees/Tables/Employees.php 查看文件

@@ -1,73 +0,0 @@
1
-<?php
2
-
3
-namespace App\Filament\Pages\Widgets\Employees\Tables;
4
-
5
-use App\Models\User;
6
-use Closure;
7
-use Exception;
8
-use Filament\Tables;
9
-use Filament\Widgets\TableWidget as PageWidget;
10
-use Illuminate\Contracts\Support\Htmlable;
11
-use Illuminate\Database\Eloquent\Builder;
12
-use Illuminate\Database\Eloquent\Relations\Relation;
13
-
14
-class Employees extends PageWidget
15
-{
16
-    protected int | string | array $columnSpan = 'full';
17
-
18
-    protected static ?int $sort = 2;
19
-
20
-    protected function getTableQuery(): Builder|Relation
21
-    {
22
-        return User::whereHas('employeeships');
23
-    }
24
-
25
-    protected function getTableHeading(): string|Htmlable|Closure|null
26
-    {
27
-        return null;
28
-    }
29
-
30
-    /**
31
-     * @throws Exception
32
-     */
33
-    protected function getTableFilters(): array
34
-    {
35
-        return [
36
-            Tables\Filters\SelectFilter::make('name')
37
-                ->label('Company')
38
-                ->searchable()
39
-                ->relationship('companies', 'name', static fn (Builder $query) => $query->whereHas('users')),
40
-        ];
41
-    }
42
-
43
-    protected function getTableColumns(): array
44
-    {
45
-        return [
46
-            Tables\Columns\ViewColumn::make('name')
47
-                ->view('filament.components.users.avatar-column')
48
-                ->label('Name')
49
-                ->sortable()
50
-                ->searchable()
51
-                ->grow(false),
52
-            Tables\Columns\TextColumn::make('companies.name')
53
-                ->label('Company')
54
-                ->sortable()
55
-                ->searchable(),
56
-            Tables\Columns\BadgeColumn::make('employeeships.role')
57
-                ->label('Role')
58
-                ->enum([
59
-                    'admin' => 'Administrator',
60
-                    'editor' => 'Editor',
61
-                ])
62
-                ->icons([
63
-                    'heroicon-o-shield-check' => 'admin',
64
-                    'heroicon-o-pencil' => 'editor',
65
-                ])
66
-                ->colors([
67
-                    'primary' => 'admin',
68
-                    'warning' => 'editor',
69
-                ])
70
-                ->sortable(),
71
-        ];
72
-    }
73
-}

+ 0
- 45
app/Filament/Pages/Widgets/Users/Tables/Users.php 查看文件

@@ -1,45 +0,0 @@
1
-<?php
2
-
3
-namespace App\Filament\Pages\Widgets\Users\Tables;
4
-
5
-use App\Models\User;
6
-use Closure;
7
-use Filament\Tables;
8
-use Filament\Widgets\TableWidget as PageWidget;
9
-use Illuminate\Contracts\Support\Htmlable;
10
-use Illuminate\Database\Eloquent\Builder;
11
-use Illuminate\Database\Eloquent\Relations\Relation;
12
-
13
-class Users extends PageWidget
14
-{
15
-    protected int|string|array $columnSpan = [
16
-        'md' => 2,
17
-        'xl' => 3,
18
-    ];
19
-
20
-    protected function getTableQuery(): Builder|Relation
21
-    {
22
-        return User::query();
23
-    }
24
-
25
-    protected function getTableHeading(): string|Htmlable|Closure|null
26
-    {
27
-        return null;
28
-    }
29
-
30
-    protected function getTableColumns(): array
31
-    {
32
-        return [
33
-            Tables\Columns\ViewColumn::make('name')
34
-                ->view('filament.components.users.avatar-column')
35
-                ->label('Name')
36
-                ->sortable()
37
-                ->searchable()
38
-                ->grow(false),
39
-            Tables\Columns\TextColumn::make('owned_companies_count')
40
-                ->counts('ownedCompanies')
41
-                ->label('Companies')
42
-                ->sortable(),
43
-        ];
44
-    }
45
-}

+ 0
- 155
app/Filament/Resources/CategoryResource.php 查看文件

@@ -1,155 +0,0 @@
1
-<?php
2
-
3
-namespace App\Filament\Resources;
4
-
5
-use App\Filament\Resources\CategoryResource\Pages;
6
-use Illuminate\Support\Facades\Auth;
7
-use Wallo\FilamentSelectify\Components\ToggleButton;
8
-use App\Models\Setting\Category;
9
-use Exception;
10
-use Filament\Forms;
11
-use Filament\Notifications\Notification;
12
-use Filament\Resources\Form;
13
-use Filament\Resources\Resource;
14
-use Filament\Resources\Table;
15
-use Filament\Tables;
16
-use Illuminate\Support\Collection;
17
-
18
-class CategoryResource extends Resource
19
-{
20
-    protected static ?string $model = Category::class;
21
-
22
-    protected static ?string $navigationIcon = 'heroicon-o-folder';
23
-
24
-    protected static ?string $navigationGroup = 'Settings';
25
-
26
-    public static function form(Form $form): Form
27
-    {
28
-        return $form
29
-            ->schema([
30
-                Forms\Components\Section::make('General')
31
-                    ->schema([
32
-                        Forms\Components\TextInput::make('name')
33
-                            ->label('Name')
34
-                            ->required(),
35
-                        Forms\Components\ColorPicker::make('color')
36
-                            ->label('Color')
37
-                            ->default('#4f46e5')
38
-                            ->required(),
39
-                        Forms\Components\Select::make('type')
40
-                            ->label('Type')
41
-                            ->options(Category::getCategoryTypes())
42
-                            ->searchable()
43
-                            ->required(),
44
-                        ToggleButton::make('enabled')
45
-                            ->label('Default')
46
-                            ->offColor('danger')
47
-                            ->onColor('primary'),
48
-                    ])->columns(),
49
-            ]);
50
-    }
51
-
52
-    /**
53
-     * @throws Exception
54
-     */
55
-    public static function table(Table $table): Table
56
-    {
57
-        return $table
58
-            ->columns([
59
-                Tables\Columns\TextColumn::make('name')
60
-                    ->label('Name')
61
-                    ->weight('semibold')
62
-                    ->icon(static fn (Category $record) => $record->enabled ? 'heroicon-o-lock-closed' : null)
63
-                    ->tooltip(static fn (Category $record) => $record->enabled ? "Default " .ucwords($record->type) . " Category" : null)
64
-                    ->iconPosition('after')
65
-                    ->searchable()
66
-                    ->sortable(),
67
-                Tables\Columns\TextColumn::make('type')
68
-                    ->label('Type')
69
-                    ->formatStateUsing(static fn (Category $record): string => ucwords($record->type))
70
-                    ->searchable()
71
-                    ->sortable(),
72
-                Tables\Columns\ColorColumn::make('color')
73
-                    ->label('Color')
74
-                    ->copyable()
75
-                    ->copyMessage('Color copied to clipboard.'),
76
-            ])
77
-            ->filters([
78
-                //
79
-            ])
80
-            ->actions([
81
-                Tables\Actions\EditAction::make(),
82
-                Tables\Actions\DeleteAction::make()
83
-                    ->before(static function (Category $record, Tables\Actions\DeleteAction $action) {
84
-                        if ($record->enabled) {
85
-                            Notification::make()
86
-                                ->danger()
87
-                                ->title('Action Denied')
88
-                                ->body(__('The :name category is currently set as your default :Type category and cannot be deleted. Please set a different category as your default before attempting to delete this one.', ['name' => $record->name, 'Type' => ucwords($record->type)]))
89
-                                ->persistent()
90
-                                ->send();
91
-
92
-                            $action->cancel();
93
-                        }
94
-                    }),
95
-            ])
96
-            ->bulkActions([
97
-                Tables\Actions\DeleteBulkAction::make()
98
-                    ->before(static function (Collection $records, Tables\Actions\DeleteBulkAction $action) {
99
-                        $defaultCategories = $records->filter(static function (Category $record) {
100
-                            return $record->enabled;
101
-                        });
102
-
103
-                        if ($defaultCategories->isNotEmpty()) {
104
-                            $defaultCategoryNames = $defaultCategories->pluck('name')->toArray();
105
-
106
-                            Notification::make()
107
-                                ->danger()
108
-                                ->title('Action Denied')
109
-                                ->body(static function () use ($defaultCategoryNames) {
110
-                                    $message = __('The following categories are currently set as your default and cannot be deleted. Please set a different category as your default before attempting to delete these ones.') . "<br><br>";
111
-                                    $message .= implode("<br>", array_map(static function ($name) {
112
-                                        return "&bull; " . $name;
113
-                                    }, $defaultCategoryNames));
114
-                                    return $message;
115
-                                })
116
-                                ->persistent()
117
-                                ->send();
118
-
119
-                            $action->cancel();
120
-                        }
121
-                    }),
122
-            ]);
123
-    }
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
-
140
-    public static function getRelations(): array
141
-    {
142
-        return [
143
-            //
144
-        ];
145
-    }
146
-
147
-    public static function getPages(): array
148
-    {
149
-        return [
150
-            'index' => Pages\ListCategories::route('/'),
151
-            'create' => Pages\CreateCategory::route('/create'),
152
-            'edit' => Pages\EditCategory::route('/{record}/edit'),
153
-        ];
154
-    }
155
-}

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

@@ -1,23 +0,0 @@
1
-<?php
2
-
3
-namespace App\Filament\Resources\CategoryResource\Pages;
4
-
5
-use App\Filament\Resources\CategoryResource;
6
-use Closure;
7
-use Filament\Pages\Actions;
8
-use Filament\Resources\Pages\ListRecords;
9
-use Illuminate\Support\Facades\Auth;
10
-use Illuminate\Support\Facades\Gate;
11
-use Wallo\FilamentCompanies\FilamentCompanies;
12
-
13
-class ListCategories extends ListRecords
14
-{
15
-    protected static string $resource = CategoryResource::class;
16
-
17
-    protected function getActions(): array
18
-    {
19
-        return [
20
-            Actions\CreateAction::make(),
21
-        ];
22
-    }
23
-}

+ 0
- 251
app/Filament/Resources/CurrencyResource.php 查看文件

@@ -1,251 +0,0 @@
1
-<?php
2
-
3
-namespace App\Filament\Resources;
4
-
5
-use App\Filament\Resources\CurrencyResource\Pages;
6
-use App\Models\Banking\Account;
7
-use App\Models\Setting\Currency;
8
-use App\Services\CurrencyService;
9
-use Closure;
10
-use Exception;
11
-use Filament\Forms;
12
-use Filament\Notifications\Notification;
13
-use Filament\Resources\Form;
14
-use Filament\Resources\Resource;
15
-use Filament\Resources\Table;
16
-use Filament\Tables;
17
-use Illuminate\Support\Collection;
18
-use Illuminate\Support\Facades\Auth;
19
-use Wallo\FilamentSelectify\Components\ToggleButton;
20
-
21
-class CurrencyResource extends Resource
22
-{
23
-    protected static ?string $model = Currency::class;
24
-
25
-    protected static ?string $navigationIcon = 'heroicon-o-currency-dollar';
26
-
27
-    protected static ?string $navigationGroup = 'Settings';
28
-
29
-    public static function form(Form $form): Form
30
-    {
31
-        return $form
32
-            ->schema([
33
-                Forms\Components\Section::make('General')
34
-                    ->description('Upon selecting a currency code, the corresponding values based on real-world currencies will auto-populate. The default currency is used for all transactions and reports and cannot be deleted. Currency precision determines the number of decimal places to display when formatting currency amounts. The currency rate is set to 1 for the default currency and is utilized as the basis for setting exchange rates for all other currencies. Alterations to default values are allowed but manage such changes wisely as any confusion or discrepancies are your responsibility.')
35
-                    ->schema([
36
-                        Forms\Components\Select::make('code')
37
-                            ->label('Code')
38
-                            ->options(Currency::getCurrencyCodes())
39
-                            ->searchable()
40
-                            ->placeholder('Select a currency code...')
41
-                            ->reactive()
42
-                            ->hidden(static fn (Closure $get): bool => $get('enabled'))
43
-                            ->afterStateUpdated(static function (Closure $set, $state) {
44
-                                if ($state === null) {
45
-                                    return;
46
-                                }
47
-
48
-                                $code = $state;
49
-                                $currencyConfig = config("money.{$code}", []);
50
-                                $currencyService = app(CurrencyService::class);
51
-
52
-                                $defaultCurrency = Currency::getDefaultCurrency();
53
-
54
-                                $rate = 1;
55
-
56
-                                if ($defaultCurrency !== null) {
57
-                                   $rate = $currencyService->getCachedExchangeRate($defaultCurrency, $code);
58
-                                }
59
-
60
-                                $set('name', $currencyConfig['name'] ?? '');
61
-                                $set('rate', $rate);
62
-                                $set('precision', $currencyConfig['precision'] ?? '');
63
-                                $set('symbol', $currencyConfig['symbol'] ?? '');
64
-                                $set('symbol_first', $currencyConfig['symbol_first'] ?? '');
65
-                                $set('decimal_mark', $currencyConfig['decimal_mark'] ?? '');
66
-                                $set('thousands_separator', $currencyConfig['thousands_separator'] ?? '');
67
-                            })
68
-                            ->required(),
69
-                        Forms\Components\TextInput::make('code')
70
-                            ->label('Code')
71
-                            ->hidden(static fn (Closure $get): bool => !$get('enabled'))
72
-                            ->disabled(static fn (Closure $get): bool => $get('enabled'))
73
-                            ->required(),
74
-                        Forms\Components\TextInput::make('name')
75
-                            ->translateLabel()
76
-                            ->maxLength(100)
77
-                            ->required(),
78
-                        Forms\Components\TextInput::make('rate')
79
-                            ->label('Rate')
80
-                            ->dehydrateStateUsing(static fn (Closure $get, $state) => $get('enabled') ? '1' : $state)
81
-                            ->numeric()
82
-                            ->reactive()
83
-                            ->disabled(static fn (Closure $get): bool => $get('enabled'))
84
-                            ->required(),
85
-                        Forms\Components\Select::make('precision')
86
-                            ->label('Precision')
87
-                            ->searchable()
88
-                            ->placeholder('Select the currency precision...')
89
-                            ->options(['0', '1', '2', '3', '4'])
90
-                            ->required(),
91
-                        Forms\Components\TextInput::make('symbol')
92
-                            ->label('Symbol')
93
-                            ->maxLength(5)
94
-                            ->required(),
95
-                        Forms\Components\Select::make('symbol_first')
96
-                            ->label('Symbol Position')
97
-                            ->searchable()
98
-                            ->boolean('Before Amount', 'After Amount', 'Select the currency symbol position...')
99
-                            ->required(),
100
-                        Forms\Components\TextInput::make('decimal_mark')
101
-                            ->label('Decimal Separator')
102
-                            ->maxLength(1)
103
-                            ->required(),
104
-                        Forms\Components\TextInput::make('thousands_separator')
105
-                            ->label('Thousands Separator')
106
-                            ->maxLength(1)
107
-                            ->required(),
108
-                        ToggleButton::make('enabled')
109
-                            ->label('Default Currency')
110
-                            ->reactive()
111
-                            ->offColor('danger')
112
-                            ->onColor('primary')
113
-                            ->afterStateUpdated(static function (Closure $set, Closure $get, $state) {
114
-                                $enabled = $state;
115
-                                $code = $get('code');
116
-                                $currencyService = app(CurrencyService::class);
117
-
118
-                                if ($enabled) {
119
-                                    $rate = 1;
120
-                                } else {
121
-                                    $defaultCurrency = Currency::getDefaultCurrency();
122
-                                    $rate = $defaultCurrency ? $currencyService->getCachedExchangeRate($defaultCurrency, $code) : 1;
123
-                                }
124
-
125
-                                $set('rate', $rate);
126
-                            }),
127
-                    ])->columns(),
128
-            ]);
129
-    }
130
-
131
-    /**
132
-     * @throws Exception
133
-     */
134
-    public static function table(Table $table): Table
135
-    {
136
-        return $table
137
-            ->columns([
138
-                Tables\Columns\TextColumn::make('name')
139
-                    ->label('Name')
140
-                    ->weight('semibold')
141
-                    ->icon(static fn (Currency $record) => $record->enabled ? 'heroicon-o-lock-closed' : null)
142
-                    ->tooltip(static fn (Currency $record) => $record->enabled ? 'Default Currency' : null)
143
-                    ->iconPosition('after')
144
-                    ->searchable()
145
-                    ->sortable(),
146
-                Tables\Columns\TextColumn::make('code')
147
-                    ->label('Code')
148
-                    ->searchable()
149
-                    ->sortable(),
150
-                Tables\Columns\TextColumn::make('symbol')
151
-                    ->label('Symbol')
152
-                    ->searchable()
153
-                    ->sortable(),
154
-                Tables\Columns\TextColumn::make('rate')
155
-                    ->label('Rate')
156
-                    ->searchable()
157
-                    ->sortable(),
158
-            ])
159
-            ->filters([
160
-                //
161
-            ])
162
-            ->actions([
163
-                Tables\Actions\EditAction::make(),
164
-                Tables\Actions\DeleteAction::make()
165
-                    ->before(static function (Tables\Actions\DeleteAction $action, Currency $record) {
166
-                        $defaultCurrency = $record->enabled;
167
-                        $accountUsesCurrency = Account::where('currency_code', $record->code)->exists();
168
-
169
-                        if ($defaultCurrency) {
170
-                            Notification::make()
171
-                                ->danger()
172
-                                ->title('Action Denied')
173
-                                ->body(__('The :name currency is currently set as the default currency and cannot be deleted. Please set a different currency as your default before attempting to delete this one.', ['name' => $record->name]))
174
-                                ->persistent()
175
-                                ->send();
176
-
177
-                            $action->cancel();
178
-                        } elseif ($accountUsesCurrency) {
179
-                            Notification::make()
180
-                                ->danger()
181
-                                ->title('Action Denied')
182
-                                ->body(__('The :name currency is currently in use by one or more accounts and cannot be deleted. Please remove this currency from all accounts before attempting to delete it.', ['name' => $record->name]))
183
-                                ->persistent()
184
-                                ->send();
185
-
186
-                            $action->cancel();
187
-                        }
188
-                    }),
189
-            ])
190
-            ->bulkActions([
191
-                Tables\Actions\DeleteBulkAction::make()
192
-                    ->before(static function (Tables\Actions\DeleteBulkAction $action, Collection $records) {
193
-                        foreach ($records as $record) {
194
-                            $defaultCurrency = $record->enabled;
195
-                            $accountUsesCurrency = Account::where('currency_code', $record->code)->exists();
196
-
197
-                            if ($defaultCurrency) {
198
-                                Notification::make()
199
-                                    ->danger()
200
-                                    ->title('Action Denied')
201
-                                    ->body(__('The :name currency is currently set as the default currency and cannot be deleted. Please set a different currency as your default before attempting to delete this one.', ['name' => $record->name]))
202
-                                    ->persistent()
203
-                                    ->send();
204
-
205
-                                $action->cancel();
206
-                            } elseif ($accountUsesCurrency) {
207
-                                Notification::make()
208
-                                    ->danger()
209
-                                    ->title('Action Denied')
210
-                                    ->body(__('The :name currency is currently in use by one or more accounts and cannot be deleted. Please remove this currency from all accounts before attempting to delete it.', ['name' => $record->name]))
211
-                                    ->persistent()
212
-                                    ->send();
213
-
214
-                                $action->cancel();
215
-                            }
216
-                        }
217
-                    }),
218
-            ]);
219
-    }
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
-
236
-    public static function getRelations(): array
237
-    {
238
-        return [
239
-            //
240
-        ];
241
-    }
242
-
243
-    public static function getPages(): array
244
-    {
245
-        return [
246
-            'index' => Pages\ListCurrencies::route('/'),
247
-            'create' => Pages\CreateCurrency::route('/create'),
248
-            'edit' => Pages\EditCurrency::route('/{record}/edit'),
249
-        ];
250
-    }
251
-}

+ 0
- 253
app/Filament/Resources/CustomerResource.php 查看文件

@@ -1,253 +0,0 @@
1
-<?php
2
-
3
-namespace App\Filament\Resources;
4
-
5
-use App\Actions\OptionAction\CreateCurrency;
6
-use App\Filament\Resources\CustomerResource\Pages;
7
-use App\Models\Setting\Currency;
8
-use App\Services\CurrencyService;
9
-use Illuminate\Support\Facades\Auth;
10
-use Wallo\FilamentSelectify\Components\ButtonGroup;
11
-use App\Models\Contact;
12
-use Filament\Forms;
13
-use Filament\Resources\Form;
14
-use Filament\Resources\Resource;
15
-use Filament\Resources\Table;
16
-use Filament\Tables;
17
-use Illuminate\Database\Eloquent\Builder;
18
-use Illuminate\Support\Facades\DB;
19
-
20
-class CustomerResource extends Resource
21
-{
22
-    protected static ?string $model = Contact::class;
23
-
24
-    protected static ?string $navigationIcon = 'heroicon-o-user-group';
25
-
26
-    protected static ?string $navigationGroup = 'Sales';
27
-
28
-    protected static ?string $navigationLabel = 'Customers';
29
-
30
-    protected static ?string $modelLabel = 'customer';
31
-
32
-    public static function getEloquentQuery(): Builder
33
-    {
34
-        return parent::getEloquentQuery()->customer();
35
-    }
36
-
37
-    public static function form(Form $form): Form
38
-    {
39
-        return $form
40
-            ->schema([
41
-                Forms\Components\Section::make('General')
42
-                    ->schema([
43
-                        Forms\Components\Grid::make(3)
44
-                            ->schema([
45
-                                ButtonGroup::make('entity')
46
-                                    ->label('Entity')
47
-                                    ->options([
48
-                                        'individual' => 'Individual',
49
-                                        'company' => 'Company',
50
-                                    ])
51
-                                    ->gridDirection('column')
52
-                                    ->default('individual')
53
-                                    ->columnSpan(1),
54
-                                Forms\Components\Grid::make()
55
-                                    ->schema([
56
-                                        Forms\Components\TextInput::make('name')
57
-                                            ->label('Name')
58
-                                            ->maxLength(100)
59
-                                            ->required(),
60
-                                        Forms\Components\TextInput::make('email')
61
-                                            ->label('Email')
62
-                                            ->email()
63
-                                            ->nullable(),
64
-                                        Forms\Components\TextInput::make('phone')
65
-                                            ->label('Phone')
66
-                                            ->tel()
67
-                                            ->maxLength(20),
68
-                                        Forms\Components\TextInput::make('website')
69
-                                            ->label('Website')
70
-                                            ->maxLength(100)
71
-                                            ->url()
72
-                                            ->nullable(),
73
-                                        Forms\Components\TextInput::make('reference')
74
-                                            ->label('Reference')
75
-                                            ->maxLength(100)
76
-                                            ->columnSpan(2)
77
-                                            ->nullable(),
78
-                                    ])->columnSpan(2),
79
-                            ]),
80
-                    ])->columns(),
81
-                Forms\Components\Section::make('Billing')
82
-                    ->schema([
83
-                        Forms\Components\TextInput::make('tax_number')
84
-                            ->label('Tax Number')
85
-                            ->maxLength(100)
86
-                            ->nullable(),
87
-                        Forms\Components\Select::make('currency_code')
88
-                            ->label('Currency')
89
-                            ->relationship('currency', 'name')
90
-                            ->preload()
91
-                            ->default(Currency::getDefaultCurrency())
92
-                            ->searchable()
93
-                            ->reactive()
94
-                            ->required()
95
-                            ->createOptionForm([
96
-                                Forms\Components\Select::make('currency.code')
97
-                                    ->label('Code')
98
-                                    ->searchable()
99
-                                    ->options(Currency::getCurrencyCodes())
100
-                                    ->reactive()
101
-                                    ->afterStateUpdated(static function (callable $set, $state) {
102
-                                        if ($state === null) {
103
-                                            return;
104
-                                        }
105
-
106
-                                        $code = $state;
107
-                                        $currencyConfig = config("money.{$code}", []);
108
-                                        $currencyService = app(CurrencyService::class);
109
-
110
-                                        $defaultCurrency = Currency::getDefaultCurrency();
111
-
112
-                                        $rate = 1;
113
-
114
-                                        if ($defaultCurrency !== null) {
115
-                                            $rate = $currencyService->getCachedExchangeRate($defaultCurrency, $code);
116
-                                        }
117
-
118
-                                        $set('currency.name', $currencyConfig['name'] ?? '');
119
-                                        $set('currency.rate', $rate);
120
-                                    })
121
-                                    ->required(),
122
-                                Forms\Components\TextInput::make('currency.name')
123
-                                    ->label('Name')
124
-                                    ->maxLength(100)
125
-                                    ->required(),
126
-                                Forms\Components\TextInput::make('currency.rate')
127
-                                    ->label('Rate')
128
-                                    ->numeric()
129
-                                    ->required(),
130
-                            ])->createOptionAction(static function (Forms\Components\Actions\Action $action) {
131
-                                return $action
132
-                                    ->label('Add Currency')
133
-                                    ->modalHeading('Add Currency')
134
-                                    ->modalButton('Add')
135
-                                    ->action(static function (array $data) {
136
-                                        return DB::transaction(static function () use ($data) {
137
-                                            $code = $data['currency']['code'];
138
-                                            $name = $data['currency']['name'];
139
-                                            $rate = $data['currency']['rate'];
140
-
141
-                                            return (new CreateCurrency())->create($code, $name, $rate);
142
-                                        });
143
-                                    });
144
-                            }),
145
-                    ])->columns(),
146
-                Forms\Components\Section::make('Address')
147
-                    ->schema([
148
-                        Forms\Components\TextInput::make('address')
149
-                            ->label('Address')
150
-                            ->maxLength(100)
151
-                            ->columnSpanFull()
152
-                            ->nullable(),
153
-                        Forms\Components\Select::make('country')
154
-                            ->label('Country')
155
-                            ->searchable()
156
-                            ->reactive()
157
-                            ->options(Contact::getCountryOptions())
158
-                            ->nullable(),
159
-                        Forms\Components\Select::make('doesnt_exist') // TODO: Remove this when we have a better way to handle the searchable select when disabled
160
-                            ->label('Province/State')
161
-                            ->disabled()
162
-                            ->hidden(static fn (callable $get) => $get('country') !== null),
163
-                        Forms\Components\Select::make('state')
164
-                            ->label('Province/State')
165
-                            ->hidden(static fn (callable $get) => $get('country') === null)
166
-                            ->options(static function (callable $get) {
167
-                                $country = $get('country');
168
-
169
-                                if (! $country) {
170
-                                    return [];
171
-                                }
172
-
173
-                                return Contact::getRegionOptions($country);
174
-                            })
175
-                            ->searchable()
176
-                            ->nullable(),
177
-                        Forms\Components\TextInput::make('city')
178
-                            ->label('Town/City')
179
-                            ->maxLength(100)
180
-                            ->nullable(),
181
-                        Forms\Components\TextInput::make('zip_code')
182
-                            ->label('Postal/Zip Code')
183
-                            ->maxLength(100)
184
-                            ->nullable(),
185
-                    ])->columns(),
186
-            ]);
187
-    }
188
-
189
-    public static function table(Table $table): Table
190
-    {
191
-        return $table
192
-            ->columns([
193
-                Tables\Columns\TextColumn::make('name')
194
-                    ->label('Name')
195
-                    ->weight('semibold')
196
-                    ->description(static fn (Contact $record) => $record->tax_number ?: 'N/A')
197
-                    ->searchable()
198
-                    ->sortable(),
199
-                Tables\Columns\TextColumn::make('email')
200
-                    ->label('Email')
201
-                    ->formatStateUsing(static fn (Contact $record) => $record->email ?: 'N/A')
202
-                    ->description(static fn (Contact $record) => $record->phone ?: 'N/A')
203
-                    ->searchable()
204
-                    ->sortable(),
205
-                Tables\Columns\TextColumn::make('country')
206
-                    ->label('Country')
207
-                    ->searchable()
208
-                    ->formatStateUsing(static fn (Contact $record) => $record->country ?: 'N/A')
209
-                    ->description(static fn (Contact $record) => $record->currency->name ?: 'N/A')
210
-                    ->sortable(),
211
-            ])
212
-            ->filters([
213
-                //
214
-            ])
215
-            ->actions([
216
-                Tables\Actions\EditAction::make(),
217
-            ])
218
-            ->bulkActions([
219
-                Tables\Actions\DeleteBulkAction::make(),
220
-            ]);
221
-    }
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
-
238
-    public static function getRelations(): array
239
-    {
240
-        return [
241
-            //
242
-        ];
243
-    }
244
-
245
-    public static function getPages(): array
246
-    {
247
-        return [
248
-            'index' => Pages\ListCustomers::route('/'),
249
-            'create' => Pages\CreateCustomer::route('/create'),
250
-            'edit' => Pages\EditCustomer::route('/{record}/edit'),
251
-        ];
252
-    }
253
-}

+ 0
- 26
app/Filament/Resources/CustomerResource/Pages/CreateCustomer.php 查看文件

@@ -1,26 +0,0 @@
1
-<?php
2
-
3
-namespace App\Filament\Resources\CustomerResource\Pages;
4
-
5
-use App\Filament\Resources\CustomerResource;
6
-use Filament\Pages\Actions;
7
-use Filament\Resources\Pages\CreateRecord;
8
-use Illuminate\Support\Facades\Auth;
9
-use Squire\Models\Region;
10
-
11
-class CreateCustomer extends CreateRecord
12
-{
13
-    protected static string $resource = CustomerResource::class;
14
-
15
-    protected function getRedirectUrl(): string
16
-    {
17
-        return $this->previousUrl;
18
-    }
19
-
20
-    protected function mutateFormDataBeforeCreate(array $data): array
21
-    {
22
-        $data['type'] = 'customer';
23
-
24
-        return $data;
25
-    }
26
-}

+ 0
- 24
app/Filament/Resources/CustomerResource/Pages/EditCustomer.php 查看文件

@@ -1,24 +0,0 @@
1
-<?php
2
-
3
-namespace App\Filament\Resources\CustomerResource\Pages;
4
-
5
-use App\Filament\Resources\CustomerResource;
6
-use Filament\Pages\Actions;
7
-use Filament\Resources\Pages\EditRecord;
8
-
9
-class EditCustomer extends EditRecord
10
-{
11
-    protected static string $resource = CustomerResource::class;
12
-
13
-    protected function getActions(): array
14
-    {
15
-        return [
16
-            Actions\DeleteAction::make(),
17
-        ];
18
-    }
19
-
20
-    protected function getRedirectUrl(): string
21
-    {
22
-        return $this->previousUrl;
23
-    }
24
-}

+ 0
- 19
app/Filament/Resources/CustomerResource/Pages/ListCustomers.php 查看文件

@@ -1,19 +0,0 @@
1
-<?php
2
-
3
-namespace App\Filament\Resources\CustomerResource\Pages;
4
-
5
-use App\Filament\Resources\CustomerResource;
6
-use Filament\Pages\Actions;
7
-use Filament\Resources\Pages\ListRecords;
8
-
9
-class ListCustomers extends ListRecords
10
-{
11
-    protected static string $resource = CustomerResource::class;
12
-
13
-    protected function getActions(): array
14
-    {
15
-        return [
16
-            Actions\CreateAction::make(),
17
-        ];
18
-    }
19
-}

+ 0
- 202
app/Filament/Resources/DiscountResource.php 查看文件

@@ -1,202 +0,0 @@
1
-<?php
2
-
3
-namespace App\Filament\Resources;
4
-
5
-use App\Filament\Resources\DiscountResource\Pages;
6
-use App\Models\Setting\Discount;
7
-use Filament\Forms;
8
-use Filament\Forms\Components\TextInput\Mask;
9
-use Filament\Resources\Form;
10
-use Filament\Resources\Resource;
11
-use Filament\Resources\Table;
12
-use Filament\Tables;
13
-use Illuminate\Support\Facades\Auth;
14
-use Wallo\FilamentSelectify\Components\ToggleButton;
15
-
16
-class DiscountResource extends Resource
17
-{
18
-    protected static ?string $model = Discount::class;
19
-
20
-    protected static ?string $navigationIcon = 'heroicon-o-tag';
21
-
22
-    protected static ?string $navigationGroup = 'Settings';
23
-
24
-    public static function form(Form $form): Form
25
-    {
26
-        return $form
27
-            ->schema([
28
-                Forms\Components\Section::make('General')
29
-                ->schema([
30
-                    Forms\Components\TextInput::make('name')
31
-                        ->label('Name')
32
-                        ->required(),
33
-                    Forms\Components\TextInput::make('description')
34
-                        ->label('Description'),
35
-                    Forms\Components\Select::make('computation')
36
-                        ->label('Computation')
37
-                        ->options(Discount::getComputationTypes())
38
-                        ->reactive()
39
-                        ->searchable()
40
-                        ->default('percentage')
41
-                        ->required(),
42
-                    Forms\Components\TextInput::make('rate')
43
-                        ->label('Rate')
44
-                        ->mask(static fn (Mask $mask) => $mask
45
-                            ->numeric()
46
-                            ->decimalPlaces(4)
47
-                            ->decimalSeparator('.')
48
-                            ->thousandsSeparator(',')
49
-                            ->minValue(0)
50
-                            ->normalizeZeros()
51
-                            ->padFractionalZeros()
52
-                        )
53
-                        ->suffix(static fn (callable $get) => $get('computation') === 'percentage' ? '%' : null)
54
-                        ->default(0.0000)
55
-                        ->required(),
56
-                    Forms\Components\Select::make('type')
57
-                        ->label('Type')
58
-                        ->options(Discount::getDiscountTypes())
59
-                        ->searchable()
60
-                        ->default('sales')
61
-                        ->required(),
62
-                    Forms\Components\Select::make('scope')
63
-                        ->label('Scope')
64
-                        ->options(Discount::getDiscountScopes())
65
-                        ->searchable(),
66
-                    Forms\Components\DateTimePicker::make('start_date')
67
-                        ->label('Start Date')
68
-                        ->minDate(static function ($context, Discount|null $record = null) {
69
-                            if ($context === 'create') {
70
-                                return today()->addDay();
71
-                            }
72
-
73
-                            return $record?->start_date?->isFuture() ? today()->addDay() : $record?->start_date;
74
-                        })
75
-                        ->maxDate(static function (callable $get, Discount|null $record = null) {
76
-                            $end_date = $get('end_date') ?? $record?->end_date;
77
-
78
-                            return $end_date ?: today()->addYear();
79
-                        })
80
-                        ->format('Y-m-d H:i:s')
81
-                        ->displayFormat('F d, Y H:i')
82
-                        ->withoutSeconds()
83
-                        ->reactive()
84
-                        ->disabled(static fn ($context, Discount|null $record = null) => $context === 'edit' && $record?->start_date?->isPast() ?? false)
85
-                        ->helperText(static fn (Forms\Components\DateTimePicker $component) => $component->isDisabled() ? 'Start date cannot be changed after the discount has begun.' : null),
86
-                    Forms\Components\DateTimePicker::make('end_date')
87
-                        ->label('End Date')
88
-                        ->reactive()
89
-                        ->minDate(static function (callable $get, Discount|null $record = null) {
90
-                            $start_date = $get('start_date') ?? $record?->start_date;
91
-
92
-                            return $start_date ?: today()->addDay();
93
-                        })
94
-                        ->maxDate(today()->addYear())
95
-                        ->format('Y-m-d H:i:s')
96
-                        ->displayFormat('F d, Y H:i')
97
-                        ->withoutSeconds(),
98
-                    ToggleButton::make('enabled')
99
-                        ->label('Default')
100
-                        ->offColor('danger')
101
-                        ->onColor('primary'),
102
-                ])->columns(),
103
-            ]);
104
-    }
105
-
106
-    public static function table(Table $table): Table
107
-    {
108
-        return $table
109
-            ->columns([
110
-                Tables\Columns\TextColumn::make('name')
111
-                    ->label('Name')
112
-                    ->weight('semibold')
113
-                    ->icon(static fn (Discount $record) => $record->enabled ? 'heroicon-o-lock-closed' : null)
114
-                    ->tooltip(static fn (Discount $record) => $record->enabled ? "Default ". ucwords($record->type) . " Discount" : null)
115
-                    ->iconPosition('after')
116
-                    ->searchable()
117
-                    ->sortable(),
118
-                Tables\Columns\TextColumn::make('computation')
119
-                    ->label('Computation')
120
-                    ->formatStateUsing(static fn (Discount $record) => ucwords($record->computation))
121
-                    ->searchable()
122
-                    ->sortable(),
123
-                Tables\Columns\TextColumn::make('rate')
124
-                    ->label('Rate')
125
-                    ->formatStateUsing(static function (Discount $record) {
126
-                        $rate = $record->rate;
127
-
128
-                        return $rate . ($record->computation === 'percentage' ? '%' : null);
129
-                    })
130
-                    ->searchable()
131
-                    ->sortable(),
132
-                Tables\Columns\BadgeColumn::make('type')
133
-                    ->label('Type')
134
-                    ->formatStateUsing(static fn (Discount $record) => ucwords($record->type))
135
-                    ->colors([
136
-                        'success' => 'sales',
137
-                        'warning' => 'purchase',
138
-                        'secondary' => 'none',
139
-                    ])
140
-                    ->icons([
141
-                        'heroicon-o-cash' => 'sales',
142
-                        'heroicon-o-shopping-bag' => 'purchase',
143
-                        'heroicon-o-x-circle' => 'none',
144
-                    ])
145
-                    ->searchable()
146
-                    ->sortable(),
147
-                Tables\Columns\TextColumn::make('start_date')
148
-                ->label('Start Date')
149
-                ->formatStateUsing(static fn (Discount $record) => $record->start_date ? $record->start_date->format('F d, Y H:i') : 'N/A')
150
-                ->searchable()
151
-                ->sortable(),
152
-                Tables\Columns\TextColumn::make('end_date')
153
-                ->label('End Date')
154
-                ->formatStateUsing(static fn (Discount $record) => $record->end_date ? $record->end_date->format('F d, Y H:i') : 'N/A')
155
-                ->color(static fn(Discount $record) => $record->end_date?->isPast() ? 'danger' : null)
156
-                ->searchable()
157
-                ->sortable(),
158
-            ])
159
-            ->filters([
160
-                //
161
-            ])
162
-            ->actions([
163
-                // Create a cron job to update recurring discounts once they have expired
164
-                Tables\Actions\EditAction::make(),
165
-                Tables\Actions\DeleteAction::make(),
166
-            ])
167
-            ->bulkActions([
168
-                Tables\Actions\DeleteBulkAction::make(),
169
-            ]);
170
-    }
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
-
187
-    public static function getRelations(): array
188
-    {
189
-        return [
190
-            //
191
-        ];
192
-    }
193
-
194
-    public static function getPages(): array
195
-    {
196
-        return [
197
-            'index' => Pages\ListDiscounts::route('/'),
198
-            'create' => Pages\CreateDiscount::route('/create'),
199
-            'edit' => Pages\EditDiscount::route('/{record}/edit'),
200
-        ];
201
-    }
202
-}

+ 0
- 152
app/Filament/Resources/InvoiceResource.php 查看文件

@@ -1,152 +0,0 @@
1
-<?php
2
-
3
-namespace App\Filament\Resources;
4
-
5
-use App\Filament\Resources\InvoiceResource\Pages;
6
-use App\Filament\Resources\InvoiceResource\RelationManagers;
7
-use App\Models\Setting\Currency;
8
-use Illuminate\Support\Facades\Auth;
9
-use Wallo\FilamentSelectify\Components\ButtonGroup;
10
-use App\Models\Document\Document;
11
-use Filament\Forms;
12
-use Filament\Resources\Form;
13
-use Filament\Resources\Resource;
14
-use Filament\Resources\Table;
15
-use Filament\Tables;
16
-use Illuminate\Database\Eloquent\Builder;
17
-
18
-class InvoiceResource extends Resource
19
-{
20
-    protected static ?string $model = Document::class;
21
-
22
-    protected static ?string $navigationIcon = 'heroicon-o-document-text';
23
-
24
-    protected static ?string $navigationGroup = 'Sales';
25
-
26
-    protected static ?string $navigationLabel = 'Invoices';
27
-
28
-    protected static ?string $modelLabel = 'invoice';
29
-
30
-    public static function getEloquentQuery(): Builder
31
-    {
32
-        return parent::getEloquentQuery()
33
-            ->where('type', 'invoice');
34
-    }
35
-
36
-    public static function form(Form $form): Form
37
-    {
38
-        return $form
39
-            ->schema([
40
-                Forms\Components\Section::make('Billing')
41
-                    ->schema([
42
-                        Forms\Components\Grid::make(3)
43
-                            ->schema([
44
-                                Forms\Components\Select::make('contact_id')
45
-                                    ->label('Customer')
46
-                                    ->preload()
47
-                                    ->placeholder('Select a customer')
48
-                                    ->relationship('contact', 'name', static fn (Builder $query) => $query->where('type', 'customer'))
49
-                                    ->searchable()
50
-                                    ->required()
51
-                                    ->createOptionForm([
52
-                                        ButtonGroup::make('contact.entity')
53
-                                            ->label('Entity')
54
-                                            ->options([
55
-                                                'company' => 'Company',
56
-                                                'individual' => 'Individual',
57
-                                            ])
58
-                                            ->default('company')
59
-                                            ->required(),
60
-                                        Forms\Components\TextInput::make('contact.name')
61
-                                            ->label('Name')
62
-                                            ->maxLength(100)
63
-                                            ->required(),
64
-                                        Forms\Components\TextInput::make('contact.email')
65
-                                            ->label('Email')
66
-                                            ->email()
67
-                                            ->nullable(),
68
-                                        Forms\Components\TextInput::make('contact.phone')
69
-                                            ->label('Phone')
70
-                                            ->tel()
71
-                                            ->maxLength(20),
72
-                                        Forms\Components\Select::make('contact.currency_code')
73
-                                            ->label('Currency')
74
-                                            ->relationship('currency', 'name')
75
-                                            ->preload()
76
-                                            ->default(Currency::getDefaultCurrency())
77
-                                            ->searchable()
78
-                                            ->reactive()
79
-                                            ->required(),
80
-                                    ])->columnSpan(1),
81
-                                Forms\Components\Grid::make(2)
82
-                                    ->schema([
83
-                                        Forms\Components\DatePicker::make('document_date')
84
-                                            ->label('Invoice Date')
85
-                                            ->default(now())
86
-                                            ->format('Y-m-d')
87
-                                            ->required(),
88
-                                        Forms\Components\DatePicker::make('due_date')
89
-                                            ->label('Due Date')
90
-                                            ->default(now())
91
-                                            ->format('Y-m-d')
92
-                                            ->required(),
93
-                                        Forms\Components\TextInput::make('document_number')
94
-                                            ->label('Invoice Number')
95
-                                            ->required(),
96
-                                        Forms\Components\TextInput::make('order_number')
97
-                                            ->label('Order Number')
98
-                                            ->nullable(),
99
-                                    ])->columnSpan(2),
100
-                            ])->columns(3),
101
-                    ])->columns(3),
102
-            ]);
103
-    }
104
-
105
-    public static function table(Table $table): Table
106
-    {
107
-        return $table
108
-            ->columns([
109
-                //
110
-            ])
111
-            ->filters([
112
-                //
113
-            ])
114
-            ->actions([
115
-                Tables\Actions\EditAction::make(),
116
-            ])
117
-            ->bulkActions([
118
-                Tables\Actions\DeleteBulkAction::make(),
119
-            ]);
120
-    }
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
-
137
-    public static function getRelations(): array
138
-    {
139
-        return [
140
-            RelationManagers\DocumentItemsRelationManager::class,
141
-        ];
142
-    }
143
-
144
-    public static function getPages(): array
145
-    {
146
-        return [
147
-            'index' => Pages\ListInvoices::route('/'),
148
-            'create' => Pages\CreateInvoice::route('/create'),
149
-            'edit' => Pages\EditInvoice::route('/{record}/edit'),
150
-        ];
151
-    }
152
-}

+ 0
- 27
app/Filament/Resources/InvoiceResource/Pages/CreateInvoice.php 查看文件

@@ -1,27 +0,0 @@
1
-<?php
2
-
3
-namespace App\Filament\Resources\InvoiceResource\Pages;
4
-
5
-use App\Filament\Resources\InvoiceResource;
6
-use Filament\Pages\Actions;
7
-use Filament\Resources\Pages\CreateRecord;
8
-use Illuminate\Support\Facades\Auth;
9
-
10
-class CreateInvoice extends CreateRecord
11
-{
12
-    protected static string $resource = InvoiceResource::class;
13
-
14
-    protected function getRedirectUrl(): string
15
-    {
16
-        return $this->previousUrl;
17
-    }
18
-
19
-    protected function mutateFormDataBeforeCreate(array $data): array
20
-    {
21
-        $data['type'] = 'invoice';
22
-        $data['status'] = 'draft';
23
-        $data['amount'] = 0;
24
-
25
-        return $data;
26
-    }
27
-}

+ 0
- 24
app/Filament/Resources/InvoiceResource/Pages/EditInvoice.php 查看文件

@@ -1,24 +0,0 @@
1
-<?php
2
-
3
-namespace App\Filament\Resources\InvoiceResource\Pages;
4
-
5
-use App\Filament\Resources\InvoiceResource;
6
-use Filament\Pages\Actions;
7
-use Filament\Resources\Pages\EditRecord;
8
-
9
-class EditInvoice extends EditRecord
10
-{
11
-    protected static string $resource = InvoiceResource::class;
12
-
13
-    protected function getActions(): array
14
-    {
15
-        return [
16
-            Actions\DeleteAction::make(),
17
-        ];
18
-    }
19
-
20
-    protected function getRedirectUrl(): string
21
-    {
22
-        return $this->previousUrl;
23
-    }
24
-}

+ 0
- 19
app/Filament/Resources/InvoiceResource/Pages/ListInvoices.php 查看文件

@@ -1,19 +0,0 @@
1
-<?php
2
-
3
-namespace App\Filament\Resources\InvoiceResource\Pages;
4
-
5
-use App\Filament\Resources\InvoiceResource;
6
-use Filament\Pages\Actions;
7
-use Filament\Resources\Pages\ListRecords;
8
-
9
-class ListInvoices extends ListRecords
10
-{
11
-    protected static string $resource = InvoiceResource::class;
12
-
13
-    protected function getActions(): array
14
-    {
15
-        return [
16
-            Actions\CreateAction::make(),
17
-        ];
18
-    }
19
-}

+ 0
- 49
app/Filament/Resources/InvoiceResource/RelationManagers/DocumentItemsRelationManager.php 查看文件

@@ -1,49 +0,0 @@
1
-<?php
2
-
3
-namespace App\Filament\Resources\InvoiceResource\RelationManagers;
4
-
5
-use Filament\Forms;
6
-use Filament\Resources\Form;
7
-use Filament\Resources\RelationManagers\RelationManager;
8
-use Filament\Resources\Table;
9
-use Filament\Tables;
10
-use Illuminate\Database\Eloquent\Builder;
11
-use Illuminate\Database\Eloquent\SoftDeletingScope;
12
-
13
-class DocumentItemsRelationManager extends RelationManager
14
-{
15
-    protected static string $relationship = 'items';
16
-
17
-    protected static ?string $recordTitleAttribute = 'invoice';
18
-
19
-    public static function form(Form $form): Form
20
-    {
21
-        return $form
22
-            ->schema([
23
-                Forms\Components\TextInput::make('item')
24
-                    ->required()
25
-                    ->maxLength(255),
26
-            ]);
27
-    }
28
-
29
-    public static function table(Table $table): Table
30
-    {
31
-        return $table
32
-            ->columns([
33
-                Tables\Columns\TextColumn::make('item'),
34
-            ])
35
-            ->filters([
36
-                //
37
-            ])
38
-            ->headerActions([
39
-                Tables\Actions\CreateAction::make(),
40
-            ])
41
-            ->actions([
42
-                Tables\Actions\EditAction::make(),
43
-                Tables\Actions\DeleteAction::make(),
44
-            ])
45
-            ->bulkActions([
46
-                Tables\Actions\DeleteBulkAction::make(),
47
-            ]);
48
-    }    
49
-}

+ 0
- 208
app/Filament/Resources/TaxResource.php 查看文件

@@ -1,208 +0,0 @@
1
-<?php
2
-
3
-namespace App\Filament\Resources;
4
-
5
-use App\Filament\Resources\TaxResource\Pages;
6
-use App\Models\Setting\Tax;
7
-use Exception;
8
-use Filament\Forms;
9
-use Filament\Forms\Components\TextInput\Mask;
10
-use Filament\Notifications\Notification;
11
-use Filament\Resources\Form;
12
-use Filament\Resources\Resource;
13
-use Filament\Resources\Table;
14
-use Filament\Tables;
15
-use Illuminate\Support\Collection;
16
-use Illuminate\Support\Facades\Auth;
17
-use Wallo\FilamentSelectify\Components\ToggleButton;
18
-
19
-class TaxResource extends Resource
20
-{
21
-    protected static ?string $model = Tax::class;
22
-
23
-    protected static ?string $navigationIcon = 'heroicon-o-receipt-tax';
24
-
25
-    protected static ?string $navigationGroup = 'Settings';
26
-
27
-    public static function form(Form $form): Form
28
-    {
29
-        return $form
30
-            ->schema([
31
-                Forms\Components\Section::make('General')
32
-                    ->schema([
33
-                        Forms\Components\TextInput::make('name')
34
-                            ->label('Name')
35
-                            ->required(),
36
-                        Forms\Components\TextInput::make('description')
37
-                            ->label('Description'),
38
-                        Forms\Components\Select::make('computation')
39
-                            ->label('Computation')
40
-                            ->options(Tax::getComputationTypes())
41
-                            ->reactive()
42
-                            ->searchable()
43
-                            ->default('percentage')
44
-                            ->required(),
45
-                        Forms\Components\TextInput::make('rate')
46
-                            ->label('Rate')
47
-                            ->mask(static fn (Mask $mask) => $mask
48
-                                ->numeric()
49
-                                ->decimalPlaces(4)
50
-                                ->decimalSeparator('.')
51
-                                ->thousandsSeparator(',')
52
-                                ->minValue(0)
53
-                                ->normalizeZeros()
54
-                                ->padFractionalZeros()
55
-                            )
56
-                            ->suffix(static function (callable $get) {
57
-                                $computation = $get('computation');
58
-
59
-                                if ($computation === 'percentage' || $computation === 'compound') {
60
-                                    return '%';
61
-                                }
62
-
63
-                                return null;
64
-                            })
65
-                            ->default(0.0000)
66
-                            ->required(),
67
-                        Forms\Components\Select::make('type')
68
-                            ->label('Type')
69
-                            ->options(Tax::getTaxTypes())
70
-                            ->searchable()
71
-                            ->default('sales')
72
-                            ->required(),
73
-                        Forms\Components\Select::make('scope')
74
-                            ->label('Scope')
75
-                            ->options(Tax::getTaxScopes())
76
-                            ->searchable(),
77
-                        ToggleButton::make('enabled')
78
-                            ->label('Default')
79
-                            ->offColor('danger')
80
-                            ->onColor('primary'),
81
-                    ])->columns(),
82
-            ]);
83
-    }
84
-
85
-    /**
86
-     * @throws Exception
87
-     */
88
-    public static function table(Table $table): Table
89
-    {
90
-        return $table
91
-            ->columns([
92
-                Tables\Columns\TextColumn::make('name')
93
-                    ->label('Name')
94
-                    ->weight('semibold')
95
-                    ->icon(static fn (Tax $record) => $record->enabled ? 'heroicon-o-lock-closed' : null)
96
-                    ->tooltip(static fn (Tax $record) => $record->enabled ? "Default " .ucwords($record->type) . " Tax" : null)
97
-                    ->iconPosition('after')
98
-                    ->searchable()
99
-                    ->sortable(),
100
-                Tables\Columns\TextColumn::make('computation')
101
-                    ->label('Computation')
102
-                    ->formatStateUsing(static fn (Tax $record) => ucwords($record->computation))
103
-                    ->searchable()
104
-                    ->sortable(),
105
-                Tables\Columns\TextColumn::make('rate')
106
-                    ->label('Rate')
107
-                    ->formatStateUsing(static function (Tax $record) {
108
-                        $rate = $record->rate;
109
-
110
-                        return $rate . ($record->computation === 'percentage' || $record->computation === 'compound' ? '%' : null);
111
-                    })
112
-                    ->searchable()
113
-                    ->sortable(),
114
-                Tables\Columns\BadgeColumn::make('type')
115
-                    ->label('Type')
116
-                    ->formatStateUsing(static fn (Tax $record) => ucwords($record->type))
117
-                    ->colors([
118
-                        'success' => 'sales',
119
-                        'warning' => 'purchase',
120
-                        'secondary' => 'none',
121
-                    ])
122
-                    ->icons([
123
-                        'heroicon-o-cash' => 'sales',
124
-                        'heroicon-o-shopping-bag' => 'purchase',
125
-                        'heroicon-o-x-circle' => 'none',
126
-                    ])
127
-                    ->searchable()
128
-                    ->sortable(),
129
-            ])
130
-            ->filters([
131
-                //
132
-            ])
133
-            ->actions([
134
-                Tables\Actions\EditAction::make(),
135
-                Tables\Actions\DeleteAction::make()
136
-                    ->before(static function (Tables\Actions\DeleteAction $action, Tax $record) {
137
-                        if ($record->enabled) {
138
-                            Notification::make()
139
-                                ->danger()
140
-                                ->title('Action Denied')
141
-                                ->body(__('The :name tax is currently set as your default :Type tax and cannot be deleted. Please set a different tax as your default before attempting to delete this one.', ['name' => $record->name, 'Type' => ucwords($record->type)]))
142
-                                ->persistent()
143
-                                ->send();
144
-
145
-                            $action->cancel();
146
-                        }
147
-                    }),
148
-            ])
149
-            ->bulkActions([
150
-                Tables\Actions\DeleteBulkAction::make()
151
-                    ->before(static function (Collection $records, Tables\Actions\DeleteBulkAction $action) {
152
-                        $defaultTaxes = $records->filter(static function (Tax $record) {
153
-                            return $record->enabled;
154
-                        });
155
-
156
-                        if ($defaultTaxes->isNotEmpty()) {
157
-                            $defaultTaxNames = $defaultTaxes->pluck('name')->toArray();
158
-
159
-                            Notification::make()
160
-                                ->danger()
161
-                                ->title('Action Denied')
162
-                                ->body(static function () use ($defaultTaxNames) {
163
-                                    $message = __('The following taxes are currently set as your default and cannot be deleted. Please set a different tax as your default before attempting to delete these ones.') . "<br><br>";
164
-                                    $message .= implode("<br>", array_map(static function ($name) {
165
-                                        return "&bull; " . $name;
166
-                                    }, $defaultTaxNames));
167
-                                    return $message;
168
-                                })
169
-                                ->persistent()
170
-                                ->send();
171
-
172
-                            $action->cancel();
173
-                        }
174
-                    }),
175
-            ]);
176
-    }
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
-
193
-    public static function getRelations(): array
194
-    {
195
-        return [
196
-            //
197
-        ];
198
-    }
199
-
200
-    public static function getPages(): array
201
-    {
202
-        return [
203
-            'index' => Pages\ListTaxes::route('/'),
204
-            'create' => Pages\CreateTax::route('/create'),
205
-            'edit' => Pages\EditTax::route('/{record}/edit'),
206
-        ];
207
-    }
208
-}

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

@@ -1,26 +0,0 @@
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
-}

+ 1
- 0
app/Http/Kernel.php 查看文件

@@ -60,6 +60,7 @@ class Kernel extends HttpKernel
60 60
         'can' => \Illuminate\Auth\Middleware\Authorize::class,
61 61
         'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
62 62
         'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
63
+        'precognitive' => \Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests::class,
63 64
         'signed' => \App\Http\Middleware\ValidateSignature::class,
64 65
         'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
65 66
         'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,

+ 0
- 13
app/Http/Livewire/Bill.php 查看文件

@@ -1,13 +0,0 @@
1
-<?php
2
-
3
-namespace App\Http\Livewire;
4
-
5
-use Livewire\Component;
6
-
7
-class Bill extends Component
8
-{
9
-    public function render()
10
-    {
11
-        return view('livewire.bill');
12
-    }
13
-}

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

@@ -1,89 +0,0 @@
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
-}

+ 0
- 161
app/Http/Livewire/DefaultSetting.php 查看文件

@@ -1,161 +0,0 @@
1
-<?php
2
-
3
-namespace App\Http\Livewire;
4
-
5
-use App\Models\Banking\Account;
6
-use App\Models\Setting\Category;
7
-use App\Models\Setting\Currency;
8
-use App\Models\Setting\DefaultSetting as Defaults;
9
-use App\Models\Setting\Tax;
10
-use App\Traits\HandlesDefaultSettingRecordUpdate;
11
-use Filament\Forms\ComponentContainer;
12
-use Filament\Forms\Components\Section;
13
-use Filament\Forms\Components\Select;
14
-use Filament\Forms\Concerns\InteractsWithForms;
15
-use Filament\Forms\Contracts\HasForms;
16
-use Filament\Notifications\Notification;
17
-use Illuminate\Contracts\View\View;
18
-use Illuminate\Database\Eloquent\Model;
19
-use Livewire\Component;
20
-
21
-/**
22
- * @property ComponentContainer $form
23
- */
24
-class DefaultSetting extends Component implements HasForms
25
-{
26
-    use InteractsWithForms, HandlesDefaultSettingRecordUpdate;
27
-
28
-    public $data;
29
-
30
-    public Defaults $record;
31
-
32
-    public function mount():void
33
-    {
34
-        $this->record = Defaults::firstOrNew();
35
-
36
-        $this->form->fill([
37
-            'account_id' => Defaults::getDefaultAccount(),
38
-            'currency_code' => Defaults::getDefaultCurrency(),
39
-            'sales_tax_id' => Defaults::getDefaultSalesTax(),
40
-            'purchase_tax_id' => Defaults::getDefaultPurchaseTax(),
41
-            'sales_discount_id' => Defaults::getDefaultSalesDiscount(),
42
-            'purchase_discount_id' => Defaults::getDefaultPurchaseDiscount(),
43
-            'income_category_id' => Defaults::getDefaultIncomeCategory(),
44
-            'expense_category_id' => Defaults::getDefaultExpenseCategory(),
45
-        ]);
46
-    }
47
-
48
-    protected function getFormSchema(): array
49
-    {
50
-        return [
51
-            Section::make('General')
52
-                ->schema([
53
-                    Select::make('account_id')
54
-                        ->label('Account')
55
-                        ->options(Defaults::getAccounts())
56
-                        ->searchable()
57
-                        ->validationAttribute('Account')
58
-                        ->nullable(),
59
-                    Select::make('currency_code')
60
-                        ->label('Currency')
61
-                        ->options(Defaults::getCurrencies())
62
-                        ->searchable()
63
-                        ->validationAttribute('Currency')
64
-                        ->nullable(),
65
-                ])->columns(),
66
-            Section::make('Taxes & Discounts')
67
-                ->schema([
68
-                    Select::make('sales_tax_id')
69
-                        ->label('Sales Tax')
70
-                        ->options(Defaults::getSalesTaxes())
71
-                        ->searchable()
72
-                        ->validationAttribute('Sales Tax')
73
-                        ->nullable(),
74
-                    Select::make('purchase_tax_id')
75
-                        ->label('Purchase Tax')
76
-                        ->options(Defaults::getPurchaseTaxes())
77
-                        ->searchable()
78
-                        ->validationAttribute('Purchase Tax')
79
-                        ->nullable(),
80
-                    Select::make('sales_discount_id')
81
-                        ->label('Sales Discount')
82
-                        ->options(Defaults::getSalesDiscounts())
83
-                        ->searchable()
84
-                        ->validationAttribute('Sales Discount')
85
-                        ->nullable(),
86
-                    Select::make('purchase_discount_id')
87
-                        ->label('Purchase Discount')
88
-                        ->options(Defaults::getPurchaseDiscounts())
89
-                        ->searchable()
90
-                        ->validationAttribute('Purchase Discount')
91
-                        ->nullable(),
92
-                ])->columns(),
93
-            Section::make('Categories')
94
-                ->schema([
95
-                    Select::make('income_category_id')
96
-                        ->label('Income Category')
97
-                        ->options(Defaults::getIncomeCategories())
98
-                        ->searchable()
99
-                        ->validationAttribute('Income Category')
100
-                        ->nullable(),
101
-                    Select::make('expense_category_id')
102
-                        ->label('Expense Category')
103
-                        ->options(Defaults::getExpenseCategories())
104
-                        ->searchable()
105
-                        ->validationAttribute('Expense Category')
106
-                        ->nullable(),
107
-                ])->columns(),
108
-        ];
109
-    }
110
-
111
-    public function save(): void
112
-    {
113
-        $data = $this->form->getState();
114
-
115
-        $this->handleRecordUpdate($this->getFormModel(), $data);
116
-
117
-        $this->getSavedNotification()?->send();
118
-    }
119
-
120
-    protected function getFormModel(): Model
121
-    {
122
-        return $this->record;
123
-    }
124
-
125
-    protected function getRelatedEntities(): array
126
-    {
127
-        return [
128
-            'account_id' => [Account::class, 'id'],
129
-            'currency_code' => [Currency::class, 'code'],
130
-            'sales_tax_id' => [Tax::class, 'id', 'sales'],
131
-            'purchase_tax_id' => [Tax::class, 'id', 'purchase'],
132
-            'sales_discount_id' => [Tax::class, 'id', 'sales'],
133
-            'purchase_discount_id' => [Tax::class, 'id', 'purchase'],
134
-            'income_category_id' => [Category::class, 'id', 'income'],
135
-            'expense_category_id' => [Category::class, 'id', 'expense'],
136
-        ];
137
-    }
138
-
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
-    public function render(): View
158
-    {
159
-        return view('livewire.default-setting');
160
-    }
161
-}

+ 0
- 247
app/Http/Livewire/Invoice.php 查看文件

@@ -1,247 +0,0 @@
1
-<?php
2
-
3
-namespace App\Http\Livewire;
4
-
5
-use App\Abstracts\Forms\EditFormRecord;
6
-use App\Models\Setting\DocumentDefault;
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;
12
-use Filament\Forms\Components\Section;
13
-use Filament\Forms\Components\Select;
14
-use Filament\Forms\Components\Textarea;
15
-use Filament\Forms\Components\TextInput;
16
-use Filament\Forms\Components\ViewField;
17
-use Illuminate\Contracts\View\View;
18
-use Illuminate\Database\Eloquent\Model;
19
-
20
-/**
21
- * @property ComponentContainer $form
22
- */
23
-class Invoice extends EditFormRecord
24
-{
25
-    public DocumentDefault $invoice;
26
-
27
-    protected function getFormModel(): Model|string|null
28
-    {
29
-        $this->invoice = DocumentDefault::where('type', 'invoice')->firstOrNew();
30
-
31
-        return $this->invoice;
32
-    }
33
-
34
-    public function mount(): void
35
-    {
36
-        $this->fillForm();
37
-    }
38
-
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);
48
-    }
49
-
50
-    protected function getFormSchema(): array
51
-    {
52
-        return [
53
-            Section::make('General')
54
-                ->schema([
55
-                    TextInput::make('number_prefix')
56
-                        ->label('Number Prefix')
57
-                        ->default('INV-')
58
-                        ->reactive()
59
-                        ->required(),
60
-                    Select::make('number_digits')
61
-                        ->label('Number Digits')
62
-                        ->options($this->invoice->getAvailableNumberDigits())
63
-                        ->default($this->invoice->getDefaultNumberDigits())
64
-                        ->reactive()
65
-                        ->afterStateUpdated(function (callable $set, $state) {
66
-                            $numDigits = $state;
67
-                            $nextNumber = $this->invoice->getNextDocumentNumber($numDigits);
68
-
69
-                            return $set('number_next', $nextNumber);
70
-                        })
71
-                        ->required()
72
-                        ->searchable(),
73
-                    TextInput::make('number_next')
74
-                        ->label('Next Number')
75
-                        ->reactive()
76
-                        ->required()
77
-                        ->default($this->invoice->getNextDocumentNumber($this->invoice->getDefaultNumberDigits())),
78
-                    Select::make('payment_terms')
79
-                        ->label('Payment Terms')
80
-                        ->options($this->invoice->getPaymentTerms())
81
-                        ->default($this->invoice->getDefaultPaymentTerms())
82
-                        ->searchable()
83
-                        ->reactive()
84
-                        ->required(),
85
-                ])->columns(),
86
-            Section::make('Content')
87
-                ->schema([
88
-                    TextInput::make('title')
89
-                        ->label('Title')
90
-                        ->reactive()
91
-                        ->default('Invoice')
92
-                        ->nullable(),
93
-                    TextInput::make('subheading')
94
-                        ->label('Subheading')
95
-                        ->reactive()
96
-                        ->nullable(),
97
-                    Textarea::make('footer')
98
-                        ->label('Footer')
99
-                        ->reactive()
100
-                        ->nullable(),
101
-                    Textarea::make('terms')
102
-                        ->label('Notes / Terms')
103
-                        ->nullable()
104
-                        ->reactive(),
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),
233
-        ];
234
-    }
235
-
236
-    protected function mutateFormDataBeforeSave(array $data): array
237
-    {
238
-        $data['type'] = 'invoice';
239
-
240
-        return $data;
241
-    }
242
-
243
-    public function render(): View
244
-    {
245
-        return view('livewire.invoice');
246
-    }
247
-}

+ 0
- 0
app/Http/Middleware/ApplyCurrentCompanyScope.php 查看文件


部分文件因为文件数量过多而无法显示

正在加载...
取消
保存