Преглед на файлове

feat: Laravel 11 and Transactions

3.x
wallo преди 1 година
родител
ревизия
c6065d0f2a
променени са 92 файла, в които са добавени 3022 реда и са изтрити 2174 реда
  1. 33
    20
      .env.example
  2. 2
    2
      app/Actions/FilamentCompanies/CreateConnectedAccount.php
  3. 2
    2
      app/Actions/FilamentCompanies/CreateNewUser.php
  4. 4
    4
      app/Actions/FilamentCompanies/CreateUserFromProvider.php
  5. 2
    2
      app/Actions/FilamentCompanies/ResolveSocialiteUser.php
  6. 0
    27
      app/Console/Kernel.php
  7. 8
    0
      app/Enums/Accounting/AccountType.php
  8. 29
    0
      app/Enums/Accounting/JournalEntryType.php
  9. 20
    0
      app/Enums/Accounting/TransactionType.php
  10. 15
    0
      app/Enums/Concerns/ParsesEnum.php
  11. 0
    16
      app/Enums/Concerns/Utilities.php
  12. 1
    19
      app/Filament/Company/Pages/Accounting/AccountChart.php
  13. 729
    0
      app/Filament/Company/Pages/Accounting/Transactions.php
  14. 0
    194
      app/Filament/Company/Resources/Accounting/TransactionResource.php
  15. 0
    62
      app/Filament/Company/Resources/Accounting/TransactionResource/Pages/ManageTransaction.php
  16. 0
    1
      app/Filament/Company/Resources/Banking/AccountResource.php
  17. 35
    0
      app/Forms/Components/JournalEntryRepeater.php
  18. 8
    0
      app/Http/Controllers/Controller.php
  19. 0
    68
      app/Http/Kernel.php
  20. 0
    17
      app/Http/Middleware/EncryptCookies.php
  21. 0
    17
      app/Http/Middleware/PreventRequestsDuringMaintenance.php
  22. 0
    30
      app/Http/Middleware/RedirectIfAuthenticated.php
  23. 0
    19
      app/Http/Middleware/TrimStrings.php
  24. 0
    20
      app/Http/Middleware/TrustHosts.php
  25. 0
    28
      app/Http/Middleware/TrustProxies.php
  26. 0
    22
      app/Http/Middleware/ValidateSignature.php
  27. 0
    17
      app/Http/Middleware/VerifyCsrfToken.php
  28. 2
    2
      app/Listeners/ConfigureCompanyNavigation.php
  29. 2
    0
      app/Livewire/UpdatePassword.php
  30. 3
    3
      app/Livewire/UpdateProfileInformation.php
  31. 22
    0
      app/Models/Accounting/Account.php
  32. 19
    11
      app/Models/Accounting/JournalEntry.php
  33. 7
    0
      app/Models/Accounting/Transaction.php
  34. 7
    0
      app/Models/Banking/BankAccount.php
  35. 5
    5
      app/Observers/AccountObserver.php
  36. 0
    1
      app/Observers/JournalEntryObserver.php
  37. 9
    4
      app/Observers/TransactionObserver.php
  38. 0
    19
      app/Providers/BroadcastServiceProvider.php
  39. 39
    12
      app/Providers/FilamentCompaniesServiceProvider.php
  40. 0
    40
      app/Providers/RouteServiceProvider.php
  41. 1
    1
      app/Scopes/CurrentCompanyScope.php
  42. 17
    0
      app/Services/AccountService.php
  43. 11
    8
      app/Services/TransactionService.php
  44. 111
    0
      app/Traits/HasJournalEntryActions.php
  45. 1
    0
      app/ValueObjects/BalanceValue.php
  46. 6
    44
      artisan
  47. 17
    53
      bootstrap/app.php
  48. 15
    0
      bootstrap/providers.php
  49. 19
    17
      composer.json
  50. 671
    523
      composer.lock
  51. 23
    93
      config/app.php
  52. 17
    17
      config/auth.php
  53. 0
    71
      config/broadcasting.php
  54. 13
    17
      config/cache.php
  55. 0
    34
      config/cors.php
  56. 46
    27
      config/database.php
  57. 0
    1
      config/dompdf.php
  58. 4
    4
      config/filesystems.php
  59. 0
    52
      config/hashing.php
  60. 15
    14
      config/logging.php
  61. 17
    39
      config/mail.php
  62. 22
    19
      config/queue.php
  63. 7
    6
      config/sanctum.php
  64. 7
    7
      config/services.php
  65. 47
    30
      config/session.php
  66. 0
    36
      config/view.php
  67. 23
    17
      database/factories/UserFactory.php
  68. 20
    3
      database/migrations/0001_01_01_000000_create_users_table.php
  69. 35
    0
      database/migrations/0001_01_01_000001_create_cache_table.php
  70. 22
    0
      database/migrations/0001_01_01_000002_create_jobs_table.php
  71. 0
    28
      database/migrations/2014_10_12_100000_create_password_reset_tokens_table.php
  72. 0
    32
      database/migrations/2019_08_19_000000_create_failed_jobs_table.php
  73. 0
    31
      database/migrations/2023_09_03_023609_create_sessions_table.php
  74. 1
    1
      database/migrations/2023_09_03_100000_create_accounting_tables.php
  75. 0
    32
      database/migrations/2023_10_14_201948_create_jobs_table.php
  76. 1
    1
      database/migrations/2024_01_01_234943_create_transactions_table.php
  77. 478
    154
      package-lock.json
  78. 8
    7
      package.json
  79. 5
    3
      phpunit.xml
  80. 1
    1
      postcss.config.js
  81. 1
    0
      resources/css/filament/company/tailwind.config.js
  82. 59
    0
      resources/css/filament/company/theme.css
  83. 3
    1
      resources/data/lang/en.json
  84. 29
    0
      resources/views/filament/company/components/actions/journal-entry-footer.blade.php
  85. 13
    11
      resources/views/filament/company/pages/accounting/chart.blade.php
  86. 4
    0
      resources/views/filament/company/pages/accounting/transactions.blade.php
  87. 225
    0
      resources/views/forms/components/journal-entry-repeater.blade.php
  88. 2
    13
      routes/api.php
  89. 0
    18
      routes/channels.php
  90. 1
    12
      routes/console.php
  91. 0
    11
      routes/web.php
  92. 1
    1
      tests/TestCase.php

+ 33
- 20
.env.example Целия файл

@@ -2,9 +2,20 @@ APP_NAME=ERPSAAS
2 2
 APP_ENV=local
3 3
 APP_KEY=
4 4
 APP_DEBUG=true
5
+APP_TIMEZONE=UTC
5 6
 APP_URL=http://localhost
6 7
 
8
+APP_LOCALE=en
9
+APP_FALLBACK_LOCALE=en
10
+APP_FAKER_LOCALE=en_US
11
+
12
+APP_MAINTENANCE_DRIVER=file
13
+APP_MAINTENANCE_STORE=database
14
+
15
+BCRYPT_ROUNDS=12
16
+
7 17
 LOG_CHANNEL=stack
18
+LOG_STACK=single
8 19
 LOG_DEPRECATIONS_CHANNEL=null
9 20
 LOG_LEVEL=debug
10 21
 
@@ -15,22 +26,29 @@ DB_DATABASE=erpsaas
15 26
 DB_USERNAME=root
16 27
 DB_PASSWORD=
17 28
 
18
-BROADCAST_DRIVER=log
19
-CACHE_DRIVER=file
20
-FILESYSTEM_DISK=local
21
-QUEUE_CONNECTION=database
22 29
 SESSION_DRIVER=database
23 30
 SESSION_LIFETIME=120
31
+SESSION_ENCRYPT=false
32
+SESSION_PATH=/
33
+SESSION_DOMAIN=null
34
+
35
+BROADCAST_CONNECTION=log
36
+FILESYSTEM_DISK=local
37
+QUEUE_CONNECTION=database
38
+
39
+CACHE_STORE=database
40
+CACHE_PREFIX=
24 41
 
25 42
 MEMCACHED_HOST=127.0.0.1
26 43
 
44
+REDIS_CLIENT=phpredis
27 45
 REDIS_HOST=127.0.0.1
28 46
 REDIS_PASSWORD=null
29 47
 REDIS_PORT=6379
30 48
 
31
-MAIL_MAILER=smtp
32
-MAIL_HOST=mailpit
33
-MAIL_PORT=1025
49
+MAIL_MAILER=log
50
+MAIL_HOST=127.0.0.1
51
+MAIL_PORT=2525
34 52
 MAIL_USERNAME=null
35 53
 MAIL_PASSWORD=null
36 54
 MAIL_ENCRYPTION=null
@@ -43,20 +61,15 @@ AWS_DEFAULT_REGION=us-east-1
43 61
 AWS_BUCKET=
44 62
 AWS_USE_PATH_STYLE_ENDPOINT=false
45 63
 
46
-PUSHER_APP_ID=
47
-PUSHER_APP_KEY=
48
-PUSHER_APP_SECRET=
49
-PUSHER_HOST=
50
-PUSHER_PORT=443
51
-PUSHER_SCHEME=https
52
-PUSHER_APP_CLUSTER=mt1
53
-
54 64
 VITE_APP_NAME="${APP_NAME}"
55
-VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
56
-VITE_PUSHER_HOST="${PUSHER_HOST}"
57
-VITE_PUSHER_PORT="${PUSHER_PORT}"
58
-VITE_PUSHER_SCHEME="${PUSHER_SCHEME}"
59
-VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
65
+
66
+GITHUB_CLIENT_ID=
67
+GITHUB_CLIENT_SECRET=
60 68
 
61 69
 CURRENCY_API_KEY=
70
+
62 71
 TRANSMATIC_STORAGE=file
72
+
73
+PLAID_CLIENT_ID=
74
+PLAID_CLIENT_SECRET=
75
+PLAID_ENVIRONMENT=sandbox

+ 2
- 2
app/Actions/FilamentCompanies/CreateConnectedAccount.php Целия файл

@@ -6,7 +6,7 @@ use Illuminate\Contracts\Auth\Authenticatable;
6 6
 use Laravel\Socialite\Contracts\User as ProviderUser;
7 7
 use Wallo\FilamentCompanies\ConnectedAccount;
8 8
 use Wallo\FilamentCompanies\Contracts\CreatesConnectedAccounts;
9
-use Wallo\FilamentCompanies\Socialite;
9
+use Wallo\FilamentCompanies\FilamentCompanies;
10 10
 
11 11
 class CreateConnectedAccount implements CreatesConnectedAccounts
12 12
 {
@@ -15,7 +15,7 @@ class CreateConnectedAccount implements CreatesConnectedAccounts
15 15
      */
16 16
     public function create(Authenticatable $user, string $provider, ProviderUser $providerUser): ConnectedAccount
17 17
     {
18
-        return Socialite::connectedAccountModel()::forceCreate([
18
+        return FilamentCompanies::connectedAccountModel()::forceCreate([
19 19
             'user_id' => $user->getAuthIdentifier(),
20 20
             'provider' => strtolower($provider),
21 21
             'provider_id' => $providerUser->getId(),

+ 2
- 2
app/Actions/FilamentCompanies/CreateNewUser.php Целия файл

@@ -8,7 +8,7 @@ use Illuminate\Support\Facades\DB;
8 8
 use Illuminate\Support\Facades\Hash;
9 9
 use Illuminate\Support\Facades\Validator;
10 10
 use Wallo\FilamentCompanies\Contracts\CreatesNewUsers;
11
-use Wallo\FilamentCompanies\Features;
11
+use Wallo\FilamentCompanies\FilamentCompanies;
12 12
 
13 13
 class CreateNewUser implements CreatesNewUsers
14 14
 {
@@ -23,7 +23,7 @@ class CreateNewUser implements CreatesNewUsers
23 23
             'name' => ['required', 'string', 'max:255'],
24 24
             'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
25 25
             'password' => ['required', 'string', 'min:8', 'confirmed'],
26
-            'terms' => Features::hasTermsAndPrivacyPolicyFeature() ? ['accepted', 'required'] : '',
26
+            'terms' => FilamentCompanies::hasTermsAndPrivacyPolicyFeature() ? ['accepted', 'required'] : '',
27 27
         ])->validate();
28 28
 
29 29
         return DB::transaction(function () use ($input) {

+ 4
- 4
app/Actions/FilamentCompanies/CreateUserFromProvider.php Целия файл

@@ -8,8 +8,8 @@ use Illuminate\Support\Facades\DB;
8 8
 use Laravel\Socialite\Contracts\User as ProviderUserContract;
9 9
 use Wallo\FilamentCompanies\Contracts\CreatesConnectedAccounts;
10 10
 use Wallo\FilamentCompanies\Contracts\CreatesUserFromProvider;
11
-use Wallo\FilamentCompanies\Features;
12
-use Wallo\FilamentCompanies\Socialite;
11
+use Wallo\FilamentCompanies\Enums\Feature;
12
+use Wallo\FilamentCompanies\FilamentCompanies;
13 13
 
14 14
 class CreateUserFromProvider implements CreatesUserFromProvider
15 15
 {
@@ -53,8 +53,8 @@ class CreateUserFromProvider implements CreatesUserFromProvider
53 53
 
54 54
     private function shouldSetProfilePhoto(ProviderUserContract $providerUser): bool
55 55
     {
56
-        return Socialite::hasProviderAvatarsFeature() &&
57
-            Features::managesProfilePhotos() &&
56
+        return Feature::ProviderAvatars->isEnabled() &&
57
+            FilamentCompanies::managesProfilePhotos() &&
58 58
             $providerUser->getAvatar();
59 59
     }
60 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\Socialite as FilamentCompaniesSocialite;
8
+use Wallo\FilamentCompanies\Enums\Feature;
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 (FilamentCompaniesSocialite::generatesMissingEmails()) {
19
+        if (Feature::GenerateMissingEmails->isEnabled()) {
20 20
             $user->email = $user->getEmail() ?? ("{$user->id}@{$provider}" . config('app.domain'));
21 21
         }
22 22
 

+ 0
- 27
app/Console/Kernel.php Целия файл

@@ -1,27 +0,0 @@
1
-<?php
2
-
3
-namespace App\Console;
4
-
5
-use Illuminate\Console\Scheduling\Schedule;
6
-use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
7
-
8
-class Kernel extends ConsoleKernel
9
-{
10
-    /**
11
-     * Define the application's command schedule.
12
-     */
13
-    protected function schedule(Schedule $schedule): void
14
-    {
15
-        // $schedule->command('inspire')->hourly();
16
-    }
17
-
18
-    /**
19
-     * Register the commands for the application.
20
-     */
21
-    protected function commands(): void
22
-    {
23
-        $this->load(__DIR__ . '/Commands');
24
-
25
-        require base_path('routes/console.php');
26
-    }
27
-}

+ 8
- 0
app/Enums/Accounting/AccountType.php Целия файл

@@ -55,4 +55,12 @@ enum AccountType: string implements HasLabel
55 55
             self::OperatingExpense, self::NonOperatingExpense, self::ContraExpense, self::UncategorizedExpense => AccountCategory::Expense,
56 56
         };
57 57
     }
58
+
59
+    public function isUncategorized(): bool
60
+    {
61
+        return match ($this) {
62
+            self::UncategorizedRevenue, self::UncategorizedExpense => true,
63
+            default => false,
64
+        };
65
+    }
58 66
 }

+ 29
- 0
app/Enums/Accounting/JournalEntryType.php Целия файл

@@ -0,0 +1,29 @@
1
+<?php
2
+
3
+namespace App\Enums\Accounting;
4
+
5
+use App\Enums\Concerns\ParsesEnum;
6
+use Filament\Support\Contracts\HasLabel;
7
+
8
+enum JournalEntryType: string implements HasLabel
9
+{
10
+    use ParsesEnum;
11
+
12
+    case Debit = 'debit';
13
+    case Credit = 'credit';
14
+
15
+    public function getLabel(): ?string
16
+    {
17
+        return $this->name;
18
+    }
19
+
20
+    public function isDebit(): bool
21
+    {
22
+        return $this === self::Debit;
23
+    }
24
+
25
+    public function isCredit(): bool
26
+    {
27
+        return $this === self::Credit;
28
+    }
29
+}

+ 20
- 0
app/Enums/Accounting/TransactionType.php Целия файл

@@ -0,0 +1,20 @@
1
+<?php
2
+
3
+namespace App\Enums\Accounting;
4
+
5
+use App\Enums\Concerns\ParsesEnum;
6
+use Filament\Support\Contracts\HasLabel;
7
+
8
+enum TransactionType: string implements HasLabel
9
+{
10
+    use ParsesEnum;
11
+
12
+    case Deposit = 'deposit';
13
+    case Withdrawal = 'withdrawal';
14
+    case Journal = 'journal';
15
+
16
+    public function getLabel(): ?string
17
+    {
18
+        return $this->name;
19
+    }
20
+}

+ 15
- 0
app/Enums/Concerns/ParsesEnum.php Целия файл

@@ -0,0 +1,15 @@
1
+<?php
2
+
3
+namespace App\Enums\Concerns;
4
+
5
+trait ParsesEnum
6
+{
7
+    public static function parse(string | self $value): self
8
+    {
9
+        if ($value instanceof self) {
10
+            return $value;
11
+        }
12
+
13
+        return self::from($value);
14
+    }
15
+}

+ 0
- 16
app/Enums/Concerns/Utilities.php Целия файл

@@ -13,20 +13,4 @@ trait Utilities
13 13
     {
14 14
         return array_column(static::cases(), 'name');
15 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
 }

+ 1
- 19
app/Filament/Company/Pages/Accounting/AccountChart.php Целия файл

@@ -3,10 +3,8 @@
3 3
 namespace App\Filament\Company\Pages\Accounting;
4 4
 
5 5
 use App\Enums\Accounting\AccountCategory;
6
-use App\Models\Accounting\Account;
7 6
 use App\Models\Accounting\Account as ChartModel;
8 7
 use App\Models\Accounting\AccountSubtype;
9
-use App\Services\AccountService;
10 8
 use App\Utilities\Accounting\AccountCode;
11 9
 use App\Utilities\Currency\CurrencyAccessor;
12 10
 use Filament\Actions\Action;
@@ -37,27 +35,11 @@ class AccountChart extends Page
37 35
     #[Url]
38 36
     public ?string $activeTab = null;
39 37
 
40
-    protected AccountService $accountService;
41
-
42
-    public function boot(AccountService $accountService): void
43
-    {
44
-        $this->accountService = $accountService;
45
-    }
46
-
47 38
     public function mount(): void
48 39
     {
49 40
         $this->activeTab = $this->activeTab ?? AccountCategory::Asset->value;
50 41
     }
51 42
 
52
-    public function getAccountBalance(Account $account): ?string
53
-    {
54
-        $company = $account->company;
55
-        $startDate = $company->locale->fiscalYearStartDate();
56
-        $endDate = $company->locale->fiscalYearEndDate();
57
-
58
-        return $this->accountService->getEndingBalance($account, $startDate, $endDate)?->formatted();
59
-    }
60
-
61 43
     protected function configureAction(Action $action): void
62 44
     {
63 45
         $action
@@ -186,6 +168,6 @@ class AccountChart extends Page
186 168
 
187 169
     public function getCategoryLabel($categoryValue): string
188 170
     {
189
-        return AccountCategory::from($categoryValue)->getLabel();
171
+        return AccountCategory::from($categoryValue)->getPluralLabel();
190 172
     }
191 173
 }

+ 729
- 0
app/Filament/Company/Pages/Accounting/Transactions.php Целия файл

@@ -0,0 +1,729 @@
1
+<?php
2
+
3
+namespace App\Filament\Company\Pages\Accounting;
4
+
5
+use App\Enums\Accounting\AccountCategory;
6
+use App\Enums\Accounting\JournalEntryType;
7
+use App\Enums\Accounting\TransactionType;
8
+use App\Enums\DateFormat;
9
+use App\Forms\Components\JournalEntryRepeater;
10
+use App\Models\Accounting\Account;
11
+use App\Models\Accounting\Transaction;
12
+use App\Models\Banking\BankAccount;
13
+use App\Models\Setting\Localization;
14
+use App\Services\AccountService;
15
+use App\Traits\HasJournalEntryActions;
16
+use Awcodes\TableRepeater\Header;
17
+use Filament\Actions\ActionGroup;
18
+use Filament\Actions\CreateAction;
19
+use Filament\Actions\StaticAction;
20
+use Filament\Forms;
21
+use Filament\Forms\Components\Actions\Action;
22
+use Filament\Forms\Components\DatePicker;
23
+use Filament\Forms\Components\Grid;
24
+use Filament\Forms\Components\Select;
25
+use Filament\Forms\Components\Tabs;
26
+use Filament\Forms\Components\Tabs\Tab;
27
+use Filament\Forms\Components\Textarea;
28
+use Filament\Forms\Components\TextInput;
29
+use Filament\Forms\Form;
30
+use Filament\Forms\Get;
31
+use Filament\Forms\Set;
32
+use Filament\Pages\Page;
33
+use Filament\Support\Colors\Color;
34
+use Filament\Support\Enums\Alignment;
35
+use Filament\Support\Enums\FontWeight;
36
+use Filament\Support\Enums\IconPosition;
37
+use Filament\Support\Enums\IconSize;
38
+use Filament\Support\Enums\MaxWidth;
39
+use Filament\Support\RawJs;
40
+use Filament\Tables;
41
+use Filament\Tables\Concerns\InteractsWithTable;
42
+use Filament\Tables\Contracts\HasTable;
43
+use Filament\Tables\Table;
44
+use Illuminate\Contracts\View\View;
45
+use Illuminate\Database\Eloquent\Builder;
46
+use Illuminate\Support\Carbon;
47
+use Illuminate\Support\Collection;
48
+use Illuminate\Support\Str;
49
+
50
+/**
51
+ * @property Form $form
52
+ */
53
+class Transactions extends Page implements HasTable
54
+{
55
+    use HasJournalEntryActions;
56
+    use InteractsWithTable;
57
+
58
+    protected static ?string $navigationIcon = 'heroicon-o-document-text';
59
+
60
+    protected static string $view = 'filament.company.pages.accounting.transactions';
61
+
62
+    protected static ?string $model = Transaction::class;
63
+
64
+    public ?string $bankAccountIdFiltered = 'all';
65
+
66
+    protected AccountService $accountService;
67
+
68
+    public function boot(AccountService $accountService): void
69
+    {
70
+        $this->accountService = $accountService;
71
+    }
72
+
73
+    public static function getModel(): string
74
+    {
75
+        return static::$model;
76
+    }
77
+
78
+    public static function getEloquentQuery(): Builder
79
+    {
80
+        return static::getModel()::query();
81
+    }
82
+
83
+    protected function getHeaderActions(): array
84
+    {
85
+        return [
86
+            $this->buildTransactionAction('addIncome', 'Add Income', TransactionType::Deposit),
87
+            $this->buildTransactionAction('addExpense', 'Add Expense', TransactionType::Withdrawal),
88
+            ActionGroup::make([
89
+                CreateAction::make('addJournalTransaction')
90
+                    ->label('Add Journal Transaction')
91
+                    ->fillForm(fn (): array => $this->getFormDefaultsForType(TransactionType::Journal))
92
+                    ->modalWidth(MaxWidth::Screen)
93
+                    ->model(static::getModel())
94
+                    ->form(fn (Form $form) => $this->journalTransactionForm($form))
95
+                    ->modalSubmitAction(fn (StaticAction $action) => $action->disabled(! $this->isJournalEntryBalanced()))
96
+                    ->groupedIcon(null)
97
+                    ->modalHeading('Journal Entry')
98
+                    ->mutateFormDataUsing(static fn (array $data) => array_merge($data, ['type' => TransactionType::Journal]))
99
+                    ->afterFormFilled(fn () => $this->resetJournalEntryAmounts()),
100
+
101
+            ])
102
+                ->label('More')
103
+                ->button()
104
+                ->outlined()
105
+                ->dropdownWidth('max-w-fit')
106
+                ->dropdownPlacement('bottom-end')
107
+                ->icon('heroicon-c-chevron-down')
108
+                ->iconSize(IconSize::Small)
109
+                ->iconPosition(IconPosition::After),
110
+        ];
111
+    }
112
+
113
+    protected function getFormDefaultsForType(TransactionType $type): array
114
+    {
115
+        $commonDefaults = [
116
+            'posted_at' => now()->format('Y-m-d'),
117
+        ];
118
+
119
+        return match ($type) {
120
+            TransactionType::Deposit, TransactionType::Withdrawal => array_merge($commonDefaults, $this->transactionDefaults($type)),
121
+            TransactionType::Journal => array_merge($commonDefaults, $this->journalEntryDefaults()),
122
+        };
123
+    }
124
+
125
+    protected function journalEntryDefaults(): array
126
+    {
127
+        return [
128
+            'journalEntries' => [
129
+                $this->defaultEntry(JournalEntryType::Debit),
130
+                $this->defaultEntry(JournalEntryType::Credit),
131
+            ],
132
+        ];
133
+    }
134
+
135
+    protected function defaultEntry(JournalEntryType $journalEntryType): array
136
+    {
137
+        return [
138
+            'type' => $journalEntryType,
139
+            'account_id' => static::getUncategorizedAccountByType($journalEntryType->isDebit() ? TransactionType::Withdrawal : TransactionType::Deposit)?->id,
140
+            'amount' => '0.00',
141
+        ];
142
+    }
143
+
144
+    public function buildTransactionAction(string $name, string $label, TransactionType $type): CreateAction
145
+    {
146
+        return CreateAction::make($name)
147
+            ->label($label)
148
+            ->modalWidth(MaxWidth::ThreeExtraLarge)
149
+            ->model(static::getModel())
150
+            ->fillForm(fn (): array => $this->getFormDefaultsForType($type))
151
+            ->form(fn (Form $form) => $this->transactionForm($form))
152
+            ->button()
153
+            ->outlined();
154
+    }
155
+
156
+    protected function transactionDefaults(TransactionType $type): array
157
+    {
158
+        return [
159
+            'type' => $type,
160
+            'bank_account_id' => BankAccount::where('enabled', true)->first()?->id,
161
+            'amount' => '0.00',
162
+            'account_id' => static::getUncategorizedAccountByType($type)?->id,
163
+        ];
164
+    }
165
+
166
+    public static function getUncategorizedAccountByType(TransactionType $type): ?Account
167
+    {
168
+        [$category, $accountName] = match ($type) {
169
+            TransactionType::Deposit => [AccountCategory::Revenue, 'Uncategorized Income'],
170
+            TransactionType::Withdrawal => [AccountCategory::Expense, 'Uncategorized Expense'],
171
+            default => [null, null],
172
+        };
173
+
174
+        return Account::where('category', $category)
175
+            ->where('name', $accountName)
176
+            ->first();
177
+    }
178
+
179
+    public function transactionForm(Form $form): Form
180
+    {
181
+        return $form
182
+            ->schema([
183
+                Forms\Components\DatePicker::make('posted_at')
184
+                    ->label('Date')
185
+                    ->required()
186
+                    ->displayFormat('Y-m-d'),
187
+                Forms\Components\TextInput::make('description')
188
+                    ->label('Description'),
189
+                Forms\Components\Select::make('bank_account_id')
190
+                    ->label('Account')
191
+                    ->options(fn () => $this->getBankAccountOptions())
192
+                    ->live()
193
+                    ->searchable()
194
+                    ->required(),
195
+                Forms\Components\Select::make('type')
196
+                    ->label('Type')
197
+                    ->live()
198
+                    ->options([
199
+                        TransactionType::Deposit->value => TransactionType::Deposit->getLabel(),
200
+                        TransactionType::Withdrawal->value => TransactionType::Withdrawal->getLabel(),
201
+                    ])
202
+                    ->required()
203
+                    ->afterStateUpdated(static fn (Forms\Set $set, $state) => $set('account_id', static::getUncategorizedAccountByType(TransactionType::parse($state))?->id)),
204
+                Forms\Components\TextInput::make('amount')
205
+                    ->label('Amount')
206
+                    ->money(static fn (Forms\Get $get) => BankAccount::find($get('bank_account_id'))?->account?->currency_code ?? 'USD')
207
+                    ->required(),
208
+                Forms\Components\Select::make('account_id')
209
+                    ->label('Category')
210
+                    ->options(fn (Forms\Get $get) => $this->getChartAccountOptions(type: TransactionType::parse($get('type')), nominalAccountsOnly: true))
211
+                    ->searchable()
212
+                    ->preload()
213
+                    ->required(),
214
+                Forms\Components\Textarea::make('notes')
215
+                    ->label('Notes')
216
+                    ->autosize()
217
+                    ->rows(10)
218
+                    ->columnSpanFull(),
219
+            ])
220
+            ->columns();
221
+    }
222
+
223
+    public function journalTransactionForm(Form $form): Form
224
+    {
225
+        return $form
226
+            ->schema([
227
+                Tabs::make('Tabs')
228
+                    ->contained(false)
229
+                    ->tabs([
230
+                        $this->getJournalTransactionFormEditTab(),
231
+                        $this->getJournalTransactionFormNotesTab(),
232
+                    ]),
233
+            ])
234
+            ->columns(1);
235
+    }
236
+
237
+    protected function getJournalTransactionFormEditTab(): Tab
238
+    {
239
+        return Tab::make('Edit')
240
+            ->label('Edit')
241
+            ->icon('heroicon-o-pencil-square')
242
+            ->schema([
243
+                $this->getTransactionDetailsGrid(),
244
+                $this->getJournalEntriesTableRepeater(),
245
+            ]);
246
+    }
247
+
248
+    protected function getJournalTransactionFormNotesTab(): Tab
249
+    {
250
+        return Tab::make('Notes')
251
+            ->label('Notes')
252
+            ->icon('heroicon-o-clipboard')
253
+            ->id('notes')
254
+            ->schema([
255
+                $this->getTransactionDetailsGrid(),
256
+                Textarea::make('notes')
257
+                    ->label('Notes')
258
+                    ->rows(10)
259
+                    ->autosize(),
260
+            ]);
261
+    }
262
+
263
+    protected function getTransactionDetailsGrid(): Grid
264
+    {
265
+        return Grid::make(8)
266
+            ->schema([
267
+                DatePicker::make('posted_at')
268
+                    ->label('Date')
269
+                    ->softRequired()
270
+                    ->displayFormat('Y-m-d'),
271
+                TextInput::make('description')
272
+                    ->label('Description')
273
+                    ->columnSpan(2),
274
+            ]);
275
+    }
276
+
277
+    protected function getJournalEntriesTableRepeater(): JournalEntryRepeater
278
+    {
279
+        return JournalEntryRepeater::make('journalEntries')
280
+            ->relationship('journalEntries')
281
+            ->hiddenLabel()
282
+            ->columns(4)
283
+            ->headers($this->getJournalEntriesTableRepeaterHeaders())
284
+            ->schema($this->getJournalEntriesTableRepeaterSchema())
285
+            ->streamlined()
286
+            ->deletable(fn (JournalEntryRepeater $repeater) => $repeater->getItemsCount() > 2)
287
+            ->minItems(2)
288
+            ->defaultItems(2)
289
+            ->addable(false)
290
+            ->footerItem(fn (): View => $this->getJournalTransactionModalFooter())
291
+            ->extraActions([
292
+                $this->buildAddJournalEntryAction(JournalEntryType::Debit),
293
+                $this->buildAddJournalEntryAction(JournalEntryType::Credit),
294
+            ]);
295
+    }
296
+
297
+    protected function getJournalEntriesTableRepeaterHeaders(): array
298
+    {
299
+        return [
300
+            Header::make('type')
301
+                ->width('150px')
302
+                ->label('Type'),
303
+            Header::make('description')
304
+                ->width('320px')
305
+                ->label('Description'),
306
+            Header::make('account_id')
307
+                ->width('320px')
308
+                ->label('Account'),
309
+            Header::make('amount')
310
+                ->width('192px')
311
+                ->label('Amount'),
312
+        ];
313
+    }
314
+
315
+    protected function getJournalEntriesTableRepeaterSchema(): array
316
+    {
317
+        return [
318
+            Select::make('type')
319
+                ->label('Type')
320
+                ->options(JournalEntryType::class)
321
+                ->live()
322
+                ->afterStateUpdated(function (Get $get, Set $set, ?string $state, ?string $old) {
323
+                    $this->adjustJournalEntryAmountsForTypeChange(JournalEntryType::parse($state), JournalEntryType::parse($old), $get('amount'));
324
+                })
325
+                ->softRequired(),
326
+            TextInput::make('description')
327
+                ->label('Description'),
328
+            Select::make('account_id')
329
+                ->label('Account')
330
+                ->options(fn (): array => $this->getChartAccountOptions())
331
+                ->live()
332
+                ->softRequired()
333
+                ->searchable(),
334
+            TextInput::make('amount')
335
+                ->label('Amount')
336
+                ->live()
337
+                ->mask(RawJs::make('$money($input)'))
338
+                ->afterStateUpdated(function (Get $get, Set $set, ?string $state, ?string $old) {
339
+                    $this->updateJournalEntryAmount(JournalEntryType::parse($get('type')), $state, $old);
340
+                })
341
+                ->softRequired(),
342
+        ];
343
+    }
344
+
345
+    protected function buildAddJournalEntryAction(JournalEntryType $type): Action
346
+    {
347
+        $typeLabel = $type->getLabel();
348
+
349
+        return Action::make("add{$typeLabel}Entry")
350
+            ->label("Add {$typeLabel} Entry")
351
+            ->button()
352
+            ->outlined()
353
+            ->color($type->isDebit() ? 'primary' : 'gray')
354
+            ->iconSize(IconSize::Small)
355
+            ->iconPosition(IconPosition::Before)
356
+            ->action(function (JournalEntryRepeater $component) use ($type) {
357
+                $state = $component->getState();
358
+                $newUuid = (string) Str::uuid();
359
+                $state[$newUuid] = $this->defaultEntry($type);
360
+
361
+                $component->state($state);
362
+            });
363
+    }
364
+
365
+    public function getJournalTransactionModalFooter(): View
366
+    {
367
+        return view(
368
+            'filament.company.components.actions.journal-entry-footer',
369
+            [
370
+                'debitAmount' => $this->getFormattedDebitAmount(),
371
+                'creditAmount' => $this->getFormattedCreditAmount(),
372
+                'difference' => $this->getFormattedBalanceDifference(),
373
+                'isJournalBalanced' => $this->isJournalEntryBalanced(),
374
+            ],
375
+        );
376
+    }
377
+
378
+    public function form(Form $form): Form
379
+    {
380
+        return $form
381
+            ->schema([
382
+                Forms\Components\Select::make('bankAccountIdFiltered')
383
+                    ->label('Account')
384
+                    ->hiddenLabel()
385
+                    ->allowHtml()
386
+                    ->options($this->getBankAccountOptions(true, true))
387
+                    ->live()
388
+                    ->selectablePlaceholder(false)
389
+                    ->columnSpan(4),
390
+            ])
391
+            ->columns(14);
392
+    }
393
+
394
+    public function table(Table $table): Table
395
+    {
396
+        return $table
397
+            ->query(static::getEloquentQuery())
398
+            ->modifyQueryUsing(function (Builder $query) {
399
+                if ($this->bankAccountIdFiltered !== 'all') {
400
+                    $query->where('bank_account_id', $this->bankAccountIdFiltered);
401
+                }
402
+            })
403
+            ->columns([
404
+                Tables\Columns\TextColumn::make('posted_at')
405
+                    ->label('Date')
406
+                    ->sortable()
407
+                    ->formatStateUsing(static function ($state) {
408
+                        $dateFormat = Localization::firstOrFail()->date_format->value ?? DateFormat::DEFAULT;
409
+
410
+                        return Carbon::parse($state)->translatedFormat($dateFormat);
411
+                    }),
412
+                Tables\Columns\TextColumn::make('description')
413
+                    ->limit(30)
414
+                    ->label('Description'),
415
+                Tables\Columns\TextColumn::make('bankAccount.account.name')
416
+                    ->label('Account'),
417
+                Tables\Columns\TextColumn::make('account.name')
418
+                    ->label('Category')
419
+                    ->state(static fn (Transaction $record) => $record->account->name ?? 'Journal Entry'),
420
+                Tables\Columns\TextColumn::make('amount')
421
+                    ->label('Amount')
422
+                    ->weight(static fn (Transaction $record) => $record->reviewed ? null : FontWeight::SemiBold)
423
+                    ->color(
424
+                        static fn (Transaction $record) => match ($record->type) {
425
+                            TransactionType::Deposit => Color::rgb('rgb(' . Color::Green[700] . ')'),
426
+                            TransactionType::Journal => 'primary',
427
+                            default => null,
428
+                        }
429
+                    )
430
+                    ->currency(static fn (Transaction $record) => $record->bankAccount->account->currency_code ?? 'USD', true)
431
+                    ->state(fn (Transaction $record) => $record->type === TransactionType::Journal ? $record->journalEntries->first()->amount : $record->amount),
432
+            ])
433
+            ->recordClasses(static fn (Transaction $record) => $record->reviewed ? 'bg-primary-300/10' : null)
434
+            ->defaultSort('posted_at', 'desc')
435
+            ->filters([
436
+                Tables\Filters\Filter::make('filters')
437
+                    ->columnSpanFull()
438
+                    ->form([
439
+                        Grid::make()
440
+                            ->schema([
441
+                                Select::make('account_id')
442
+                                    ->label('Category')
443
+                                    ->options(fn () => $this->getChartAccountOptions(nominalAccountsOnly: true))
444
+                                    ->multiple()
445
+                                    ->searchable(),
446
+                                Select::make('reviewed')
447
+                                    ->label('Status')
448
+                                    ->native(false)
449
+                                    ->options([
450
+                                        '1' => 'Reviewed',
451
+                                        '0' => 'Not Reviewed',
452
+                                    ]),
453
+                                Select::make('type')
454
+                                    ->label('Type')
455
+                                    ->options(TransactionType::class)
456
+                                    ->multiple(),
457
+                            ])
458
+                            ->extraAttributes([
459
+                                'class' => 'border-b border-gray-200 dark:border-white/10 pb-8',
460
+                            ]),
461
+                        Grid::make()
462
+                            ->schema([
463
+                                Select::make('posted_at_date_range')
464
+                                    ->label('Posted Date')
465
+                                    ->placeholder('Select a date range')
466
+                                    ->options([
467
+                                        'all' => 'All Dates', // Handle this later
468
+                                        'custom' => 'Custom Date Range',
469
+                                    ]),
470
+                                DatePicker::make('posted_at_start_date')
471
+                                    ->label('Posted From')
472
+                                    ->displayFormat('Y-m-d')
473
+                                    ->columnStart(1),
474
+                                DatePicker::make('posted_at_end_date')
475
+                                    ->label('Posted To')
476
+                                    ->displayFormat('Y-m-d'),
477
+                                TextInput::make('posted_at_combined_dates')
478
+                                    ->hidden(),
479
+                            ])
480
+                            ->extraAttributes([
481
+                                'class' => 'border-b border-gray-200 dark:border-white/10 pb-8',
482
+                            ]),
483
+                        Grid::make()
484
+                            ->schema([
485
+                                Select::make('updated_at_date_range')
486
+                                    ->label('Last Modified Date')
487
+                                    ->placeholder('Select a date range')
488
+                                    ->options([
489
+                                        'all' => 'All Dates', // Handle this later
490
+                                        'custom' => 'Custom Date Range',
491
+                                    ]),
492
+                                DatePicker::make('updated_at_start_date')
493
+                                    ->label('Last Modified From')
494
+                                    ->displayFormat('Y-m-d')
495
+                                    ->columnStart(1),
496
+                                DatePicker::make('updated_at_end_date')
497
+                                    ->label('Last Modified To')
498
+                                    ->displayFormat('Y-m-d'),
499
+                                TextInput::make('updated_at_combined_dates')
500
+                                    ->label('Updated Date Range')
501
+                                    ->hidden(),
502
+                            ]),
503
+                    ])->query(function (Builder $query, array $data): Builder {
504
+                        if (filled($data['reviewed'])) {
505
+                            $reviewedStatus = $data['reviewed'] === '1';
506
+                            $query->where('reviewed', $reviewedStatus);
507
+                        }
508
+
509
+                        $query
510
+                            ->when($data['account_id'], fn (Builder $query, $accountIds) => $query->whereIn('account_id', $accountIds))
511
+                            ->when($data['type'], fn (Builder $query, $types) => $query->whereIn('type', $types))
512
+                            ->when($data['posted_at_start_date'], fn (Builder $query, $startDate) => $query->whereDate('posted_at', '>=', $startDate))
513
+                            ->when($data['posted_at_end_date'], fn (Builder $query, $endDate) => $query->whereDate('posted_at', '<=', $endDate))
514
+                            ->when($data['updated_at_start_date'], fn (Builder $query, $startDate) => $query->whereDate('updated_at', '>=', $startDate))
515
+                            ->when($data['updated_at_end_date'], fn (Builder $query, $endDate) => $query->whereDate('updated_at', '<=', $endDate));
516
+
517
+                        return $query;
518
+                    })
519
+                    ->indicateUsing(function (array $data): array {
520
+                        $indicators = [];
521
+
522
+                        $this->addIndicatorForSingleSelection($data, 'reviewed', $data['reviewed'] === '1' ? 'Reviewed' : 'Not Reviewed', $indicators);
523
+                        $this->addMultipleSelectionIndicator($data, 'account_id', fn ($accountId) => Account::find($accountId)->name, 'account_id', $indicators);
524
+                        $this->addMultipleSelectionIndicator($data, 'type', fn ($type) => TransactionType::parse($type)->getLabel(), 'type', $indicators);
525
+                        $this->addIndicatorForDateRange($data, 'posted_at_start_date', 'posted_at_end_date', 'Posted', 'posted_at_combined_dates', $indicators);
526
+                        $this->addIndicatorForDateRange($data, 'updated_at_start_date', 'updated_at_end_date', 'Last Modified', 'updated_at_combined_dates', $indicators);
527
+
528
+                        return $indicators;
529
+                    }),
530
+            ], layout: Tables\Enums\FiltersLayout::Modal)
531
+            ->deferFilters()
532
+            ->filtersFormColumns(2)
533
+            ->filtersTriggerAction(
534
+                fn (Tables\Actions\Action $action) => $action
535
+                    ->stickyModalHeader()
536
+                    ->stickyModalFooter()
537
+                    ->modalWidth(MaxWidth::ThreeExtraLarge)
538
+                    ->modalFooterActionsAlignment(Alignment::End)
539
+                    ->modalCancelAction(false)
540
+                    ->extraModalFooterActions(function (Table $table) use ($action) {
541
+                        return [
542
+                            $table->getFiltersApplyAction()
543
+                                ->close(),
544
+                            StaticAction::make('cancel')
545
+                                ->label($action->getModalCancelActionLabel())
546
+                                ->button()
547
+                                ->close()
548
+                                ->color('gray'),
549
+                            Tables\Actions\Action::make('resetFilters')
550
+                                ->label(__('Clear All'))
551
+                                ->color('primary')
552
+                                ->link()
553
+                                ->extraAttributes([
554
+                                    'class' => 'me-auto',
555
+                                ])
556
+                                ->action('resetTableFiltersForm'),
557
+                        ];
558
+                    })
559
+            )
560
+            ->actions([
561
+                Tables\Actions\Action::make('markAsReviewed')
562
+                    ->label('Mark as Reviewed')
563
+                    ->view('filament.company.components.tables.actions.mark-as-reviewed')
564
+                    ->icon(static fn (Transaction $record) => $record->reviewed ? 'heroicon-s-check-circle' : 'heroicon-o-check-circle')
565
+                    ->color(static fn (Transaction $record, Tables\Actions\Action $action) => match (static::determineTransactionState($record, $action)) {
566
+                        'reviewed' => 'primary',
567
+                        'unreviewed' => Color::rgb('rgb(' . Color::Gray[600] . ')'),
568
+                        'uncategorized' => 'gray',
569
+                    })
570
+                    ->tooltip(static fn (Transaction $record, Tables\Actions\Action $action) => match (static::determineTransactionState($record, $action)) {
571
+                        'reviewed' => 'Reviewed',
572
+                        'unreviewed' => 'Mark as Reviewed',
573
+                        'uncategorized' => 'Categorize first to mark as reviewed',
574
+                    })
575
+                    ->disabled(fn (Transaction $record): bool => $record->isUncategorized())
576
+                    ->action(fn (Transaction $record) => $record->update(['reviewed' => ! $record->reviewed])),
577
+                Tables\Actions\ActionGroup::make([
578
+                    Tables\Actions\EditAction::make('updateTransaction')
579
+                        ->label('Edit Transaction')
580
+                        ->modalHeading('Edit Transaction')
581
+                        ->modalWidth(MaxWidth::ThreeExtraLarge)
582
+                        ->form(fn (Form $form) => $this->transactionForm($form))
583
+                        ->hidden(static fn (Transaction $record) => $record->type === TransactionType::Journal),
584
+                    Tables\Actions\EditAction::make('updateJournalTransaction')
585
+                        ->label('Edit Journal Transaction')
586
+                        ->modalHeading('Journal Entry')
587
+                        ->modalWidth(MaxWidth::Screen)
588
+                        ->form(fn (Form $form) => $this->journalTransactionForm($form))
589
+                        ->afterFormFilled(function (Transaction $record) {
590
+                            $debitAmounts = $record->journalEntries->where('type', JournalEntryType::Debit)->sum('amount');
591
+                            $creditAmounts = $record->journalEntries->where('type', JournalEntryType::Credit)->sum('amount');
592
+
593
+                            $this->setDebitAmount($debitAmounts);
594
+                            $this->setCreditAmount($creditAmounts);
595
+                        })
596
+                        ->hidden(static fn (Transaction $record) => $record->type !== TransactionType::Journal),
597
+                    Tables\Actions\DeleteAction::make(),
598
+                    Tables\Actions\ReplicateAction::make()
599
+                        ->excludeAttributes(['created_by', 'updated_by', 'created_at', 'updated_at'])
600
+                        ->modal(false)
601
+                        ->beforeReplicaSaved(static function (Transaction $replica) {
602
+                            $replica->description = '(Copy of) ' . $replica->description;
603
+                        }),
604
+                ])
605
+                    ->dropdownPlacement('bottom-start')
606
+                    ->dropdownWidth('max-w-fit'),
607
+            ])
608
+            ->bulkActions([
609
+                Tables\Actions\BulkActionGroup::make([
610
+                    Tables\Actions\DeleteBulkAction::make(),
611
+                ]),
612
+            ]);
613
+    }
614
+
615
+    protected function addIndicatorForSingleSelection($data, $key, $label, &$indicators): void
616
+    {
617
+        if (filled($data[$key])) {
618
+            $indicators[] = Tables\Filters\Indicator::make($label)
619
+                ->removeField($key);
620
+        }
621
+    }
622
+
623
+    protected function addMultipleSelectionIndicator($data, $key, callable $labelRetriever, $field, &$indicators): void
624
+    {
625
+        if (filled($data[$key])) {
626
+            $labels = collect($data[$key])->map($labelRetriever);
627
+            $additionalCount = $labels->count() - 1;
628
+            $indicatorLabel = $additionalCount > 0 ? "{$labels->first()} + {$additionalCount}" : $labels->first();
629
+            $indicators[] = Tables\Filters\Indicator::make($indicatorLabel)
630
+                ->removeField($field);
631
+        }
632
+    }
633
+
634
+    protected function addIndicatorForDateRange($data, $startKey, $endKey, $labelPrefix, $combinedFieldKey, &$indicators): void
635
+    {
636
+        $formattedStartDate = filled($data[$startKey]) ? Carbon::parse($data[$startKey])->toFormattedDateString() : null;
637
+        $formattedEndDate = filled($data[$endKey]) ? Carbon::parse($data[$endKey])->toFormattedDateString() : null;
638
+        if ($formattedStartDate && $formattedEndDate) {
639
+            $indicators[] = Tables\Filters\Indicator::make("{$labelPrefix}: {$formattedStartDate} - {$formattedEndDate}")
640
+                ->removeField($combinedFieldKey); // Associate with the hidden combined_dates field for removal
641
+        } else {
642
+            if ($formattedStartDate) {
643
+                $indicators[] = Tables\Filters\Indicator::make("{$labelPrefix} After: {$formattedStartDate}")
644
+                    ->removeField($startKey);
645
+            }
646
+
647
+            if ($formattedEndDate) {
648
+                $indicators[] = Tables\Filters\Indicator::make("{$labelPrefix} Before: {$formattedEndDate}")
649
+                    ->removeField($endKey);
650
+            }
651
+        }
652
+    }
653
+
654
+    protected static function determineTransactionState(Transaction $record, Tables\Actions\Action $action): string
655
+    {
656
+        if ($record->reviewed) {
657
+            return 'reviewed';
658
+        }
659
+
660
+        if ($record->reviewed === false && $action->isEnabled()) {
661
+            return 'unreviewed';
662
+        }
663
+
664
+        return 'uncategorized';
665
+    }
666
+
667
+    protected function getChartAccountOptions(?TransactionType $type = null, bool $nominalAccountsOnly = false): array
668
+    {
669
+        $excludedCategory = match ($type) {
670
+            TransactionType::Deposit => AccountCategory::Expense,
671
+            TransactionType::Withdrawal => AccountCategory::Revenue,
672
+            default => null,
673
+        };
674
+
675
+        return Account::query()
676
+            ->when($nominalAccountsOnly, fn (Builder $query) => $query->whereNull('accountable_type'))
677
+            ->when($excludedCategory, fn (Builder $query) => $query->whereNot('category', $excludedCategory))
678
+            ->get()
679
+            ->groupBy(fn (Account $account) => $account->category->getPluralLabel())
680
+            ->map(fn (Collection $accounts, string $category) => $accounts->pluck('name', 'id'))
681
+            ->toArray();
682
+    }
683
+
684
+    protected function getBankAccountOptions(?bool $onlyWithTransactions = null, bool $isFilter = false): array
685
+    {
686
+        $onlyWithTransactions ??= false;
687
+
688
+        $options = $isFilter ? [
689
+            '' => ['all' => "All Accounts <span class='float-right'>{$this->getBalanceForAllAccounts()}</span>"],
690
+        ] : [];
691
+
692
+        $bankAccountOptions = BankAccount::with('account.subtype')
693
+            ->when($onlyWithTransactions, fn (Builder $query) => $query->has('transactions'))
694
+            ->get()
695
+            ->groupBy('account.subtype.name')
696
+            ->mapWithKeys(function (Collection $bankAccounts, string $subtype) use ($isFilter) {
697
+                return [$subtype => $bankAccounts->mapWithKeys(function (BankAccount $bankAccount) use ($isFilter) {
698
+                    $label = $bankAccount->account->name;
699
+                    if ($isFilter) {
700
+                        $balance = $this->getAccountBalance($bankAccount->account);
701
+                        $label .= "<span class='float-right'>{$balance}</span>";
702
+                    }
703
+
704
+                    return [$bankAccount->id => $label];
705
+                })];
706
+            })
707
+            ->toArray();
708
+
709
+        return array_merge($options, $bankAccountOptions);
710
+    }
711
+
712
+    public function getAccountBalance(Account $account): ?string
713
+    {
714
+        $company = $account->company;
715
+        $startDate = $company->locale->fiscalYearStartDate();
716
+        $endDate = $company->locale->fiscalYearEndDate();
717
+
718
+        return $this->accountService->getEndingBalance($account, $startDate, $endDate)?->formatted();
719
+    }
720
+
721
+    public function getBalanceForAllAccounts(): string
722
+    {
723
+        $company = auth()->user()->currentCompany;
724
+        $startDate = $company->locale->fiscalYearStartDate();
725
+        $endDate = $company->locale->fiscalYearEndDate();
726
+
727
+        return $this->accountService->getTotalBalanceForAllBankAccount($startDate, $endDate)->formatted();
728
+    }
729
+}

+ 0
- 194
app/Filament/Company/Resources/Accounting/TransactionResource.php Целия файл

@@ -1,194 +0,0 @@
1
-<?php
2
-
3
-namespace App\Filament\Company\Resources\Accounting;
4
-
5
-use App\Enums\Accounting\AccountCategory;
6
-use App\Enums\Accounting\AccountType;
7
-use App\Enums\DateFormat;
8
-use App\Filament\Company\Resources\Accounting\TransactionResource\Pages;
9
-use App\Models\Accounting\Account;
10
-use App\Models\Accounting\Transaction;
11
-use App\Models\Banking\BankAccount;
12
-use App\Models\Setting\Localization;
13
-use Filament\Forms;
14
-use Filament\Forms\Form;
15
-use Filament\Resources\Resource;
16
-use Filament\Support\Colors\Color;
17
-use Filament\Support\Enums\FontWeight;
18
-use Filament\Support\Enums\MaxWidth;
19
-use Filament\Tables;
20
-use Filament\Tables\Table;
21
-use Illuminate\Support\Carbon;
22
-use Illuminate\Support\Collection;
23
-
24
-class TransactionResource extends Resource
25
-{
26
-    protected static ?string $model = Transaction::class;
27
-
28
-    protected static ?string $modelLabel = 'Transaction';
29
-
30
-    public static function form(Form $form): Form
31
-    {
32
-        return $form
33
-            ->schema([
34
-                Forms\Components\DatePicker::make('posted_at')
35
-                    ->label('Date')
36
-                    ->required()
37
-                    ->displayFormat('Y-m-d'),
38
-                Forms\Components\TextInput::make('description')
39
-                    ->label('Description'),
40
-                Forms\Components\Select::make('bank_account_id')
41
-                    ->label('Account')
42
-                    ->options(static fn () => static::getBankAccountOptions())
43
-                    ->live()
44
-                    ->searchable()
45
-                    ->required(),
46
-                Forms\Components\Select::make('type')
47
-                    ->label('Type')
48
-                    ->live()
49
-                    ->options([
50
-                        'deposit' => 'Deposit',
51
-                        'withdrawal' => 'Withdrawal',
52
-                    ])
53
-                    ->required()
54
-                    ->afterStateUpdated(static fn (Forms\Set $set, $state) => $set('account_id', Pages\ManageTransaction::getUncategorizedAccountByType($state)?->id)),
55
-                Forms\Components\TextInput::make('amount')
56
-                    ->label('Amount')
57
-                    ->money(static fn (Forms\Get $get) => BankAccount::find($get('bank_account_id'))?->account?->currency_code ?? 'USD')
58
-                    ->required(),
59
-                Forms\Components\Select::make('account_id')
60
-                    ->label('Category')
61
-                    ->options(static fn (Forms\Get $get) => static::getAccountOptions($get('type')))
62
-                    ->searchable()
63
-                    ->preload()
64
-                    ->required(),
65
-                Forms\Components\Textarea::make('notes')
66
-                    ->label('Notes')
67
-                    ->autosize()
68
-                    ->rows(10)
69
-                    ->columnSpanFull(),
70
-            ]);
71
-    }
72
-
73
-    public static function table(Table $table): Table
74
-    {
75
-        return $table
76
-            ->columns([
77
-                Tables\Columns\TextColumn::make('posted_at')
78
-                    ->label('Date')
79
-                    ->sortable()
80
-                    ->formatStateUsing(static function ($state) {
81
-                        $dateFormat = Localization::firstOrFail()->date_format->value ?? DateFormat::DEFAULT;
82
-
83
-                        return Carbon::parse($state)->translatedFormat($dateFormat);
84
-                    }),
85
-                Tables\Columns\TextColumn::make('description')
86
-                    ->limit(30)
87
-                    ->label('Description'),
88
-                Tables\Columns\TextColumn::make('bankAccount.account.name')
89
-                    ->label('Account')
90
-                    ->sortable(),
91
-                Tables\Columns\TextColumn::make('account.name')
92
-                    ->label('Category'),
93
-                Tables\Columns\TextColumn::make('amount')
94
-                    ->label('Amount')
95
-                    ->sortable()
96
-                    ->weight(static fn (Transaction $record) => $record->reviewed ? null : FontWeight::SemiBold)
97
-                    ->color(static fn (Transaction $record) => $record->type === 'deposit' ? Color::rgb('rgb(' . Color::Green[700] . ')') : null)
98
-                    ->currency(static fn (Transaction $record) => $record->bankAccount->account->currency_code ?? 'USD', true),
99
-            ])
100
-            ->recordClasses(static fn (Transaction $record) => $record->reviewed ? 'bg-primary-300/10' : null)
101
-            ->defaultSort('posted_at', 'desc')
102
-            ->filters([
103
-                //
104
-            ])
105
-            ->actions([
106
-                Tables\Actions\Action::make('markAsReviewed')
107
-                    ->label('Mark as Reviewed')
108
-                    ->view('filament.company.components.tables.actions.mark-as-reviewed')
109
-                    ->icon(static fn (Transaction $record) => $record->reviewed ? 'heroicon-s-check-circle' : 'heroicon-o-check-circle')
110
-                    ->color(static fn (Transaction $record, Tables\Actions\Action $action) => match (static::determineTransactionState($record, $action)) {
111
-                        'reviewed' => 'primary',
112
-                        'unreviewed' => Color::rgb('rgb(' . Color::Gray[600] . ')'),
113
-                        'uncategorized' => 'gray',
114
-                    })
115
-                    ->tooltip(static fn (Transaction $record, Tables\Actions\Action $action) => match (static::determineTransactionState($record, $action)) {
116
-                        'reviewed' => 'Reviewed',
117
-                        'unreviewed' => 'Mark as Reviewed',
118
-                        'uncategorized' => 'Categorize first to mark as reviewed',
119
-                    })
120
-                    ->disabled(static fn (Transaction $record) => in_array($record->account->type, [AccountType::UncategorizedRevenue, AccountType::UncategorizedExpense], true))
121
-                    ->action(fn (Transaction $record) => $record->update(['reviewed' => ! $record->reviewed])),
122
-                Tables\Actions\ActionGroup::make([
123
-                    Tables\Actions\EditAction::make()
124
-                        ->modalWidth(MaxWidth::ThreeExtraLarge)
125
-                        ->stickyModalHeader()
126
-                        ->stickyModalFooter(),
127
-                    Tables\Actions\DeleteAction::make(),
128
-                    Tables\Actions\ReplicateAction::make()
129
-                        ->excludeAttributes(['created_by', 'updated_by', 'created_at', 'updated_at'])
130
-                        ->modal(false)
131
-                        ->beforeReplicaSaved(static function (Transaction $replica) {
132
-                            $replica->description = '(Copy of) ' . $replica->description;
133
-                        }),
134
-                ])
135
-                    ->dropdownPlacement('bottom-start')
136
-                    ->dropdownWidth('max-w-fit'),
137
-            ])
138
-            ->bulkActions([
139
-                Tables\Actions\BulkActionGroup::make([
140
-                    Tables\Actions\DeleteBulkAction::make(),
141
-                ]),
142
-            ]);
143
-    }
144
-
145
-    protected static function determineTransactionState(Transaction $record, Tables\Actions\Action $action): string
146
-    {
147
-        if ($record->reviewed) {
148
-            return 'reviewed';
149
-        }
150
-
151
-        if ($record->reviewed === false && $action->isEnabled()) {
152
-            return 'unreviewed';
153
-        }
154
-
155
-        return 'uncategorized';
156
-    }
157
-
158
-    public static function getRelations(): array
159
-    {
160
-        return [
161
-            //
162
-        ];
163
-    }
164
-
165
-    public static function getPages(): array
166
-    {
167
-        return [
168
-            'index' => Pages\ManageTransaction::route('/'),
169
-        ];
170
-    }
171
-
172
-    public static function getBankAccountOptions(): array
173
-    {
174
-        $bankAccounts = BankAccount::with('account.subtype')->get();
175
-
176
-        return $bankAccounts->groupBy('account.subtype.name')
177
-            ->map(fn (Collection $bankAccounts) => $bankAccounts->pluck('account.name', 'id'))
178
-            ->toArray();
179
-    }
180
-
181
-    public static function getAccountOptions(string $type)
182
-    {
183
-        $excludedCategory = match ($type) {
184
-            'deposit' => AccountCategory::Expense,
185
-            'withdrawal' => AccountCategory::Revenue,
186
-        };
187
-
188
-        $accounts = Account::whereNot('category', $excludedCategory)->get();
189
-
190
-        return $accounts->groupBy(fn (Account $account) => $account->category->getLabel())
191
-            ->map(fn (Collection $accounts, string $category) => $accounts->mapWithKeys(static fn (Account $account) => [$account->id => $account->name]))
192
-            ->toArray();
193
-    }
194
-}

+ 0
- 62
app/Filament/Company/Resources/Accounting/TransactionResource/Pages/ManageTransaction.php Целия файл

@@ -1,62 +0,0 @@
1
-<?php
2
-
3
-namespace App\Filament\Company\Resources\Accounting\TransactionResource\Pages;
4
-
5
-use App\Enums\Accounting\AccountCategory;
6
-use App\Filament\Company\Resources\Accounting\TransactionResource;
7
-use App\Models\Accounting\Account;
8
-use App\Models\Banking\BankAccount;
9
-use Filament\Actions;
10
-use Filament\Resources\Pages\ManageRecords;
11
-use Filament\Support\Enums\MaxWidth;
12
-
13
-class ManageTransaction extends ManageRecords
14
-{
15
-    public static string $resource = TransactionResource::class;
16
-
17
-    protected function getHeaderActions(): array
18
-    {
19
-        return [
20
-            $this->createAction(
21
-                name: 'addIncome',
22
-                label: 'Add Income',
23
-                type: 'deposit',
24
-            ),
25
-            $this->createAction(
26
-                name: 'addExpense',
27
-                label: 'Add Expense',
28
-                type: 'withdrawal',
29
-            ),
30
-        ];
31
-    }
32
-
33
-    protected function createAction(string $name, string $label, string $type): Actions\CreateAction
34
-    {
35
-        return Actions\CreateAction::make($name)
36
-            ->label($label)
37
-            ->modalWidth(MaxWidth::ThreeExtraLarge)
38
-            ->stickyModalHeader()
39
-            ->stickyModalFooter()
40
-            ->button()
41
-            ->outlined()
42
-            ->fillForm(static fn (): array => [
43
-                'type' => $type,
44
-                'posted_at' => now()->format('Y-m-d'),
45
-                'bank_account_id' => BankAccount::where('enabled', true)->first()->id ?? null,
46
-                'amount' => '0.00',
47
-                'account_id' => static::getUncategorizedAccountByType($type)?->id,
48
-            ]);
49
-    }
50
-
51
-    public static function getUncategorizedAccountByType(string $type): ?Account
52
-    {
53
-        [$category, $accountName] = match ($type) {
54
-            'deposit' => [AccountCategory::Revenue, 'Uncategorized Income'],
55
-            'withdrawal' => [AccountCategory::Expense, 'Uncategorized Expense'],
56
-        };
57
-
58
-        return Account::where('category', $category)
59
-            ->where('name', $accountName)
60
-            ->first();
61
-    }
62
-}

+ 0
- 1
app/Filament/Company/Resources/Banking/AccountResource.php Целия файл

@@ -11,7 +11,6 @@ use App\Models\Accounting\AccountSubtype;
11 11
 use App\Models\Banking\BankAccount;
12 12
 use App\Services\AccountService;
13 13
 use App\Utilities\Currency\CurrencyAccessor;
14
-use App\Utilities\Currency\CurrencyConverter;
15 14
 use BackedEnum;
16 15
 use Filament\Facades\Filament;
17 16
 use Filament\Forms;

+ 35
- 0
app/Forms/Components/JournalEntryRepeater.php Целия файл

@@ -0,0 +1,35 @@
1
+<?php
2
+
3
+namespace App\Forms\Components;
4
+
5
+use Awcodes\TableRepeater\Components\TableRepeater;
6
+use Closure;
7
+use Illuminate\Contracts\Support\Htmlable;
8
+use Illuminate\Contracts\View\View;
9
+
10
+class JournalEntryRepeater extends TableRepeater
11
+{
12
+    protected View | Htmlable | Closure | null $footerItem = null;
13
+
14
+    public function footerItem(View | Htmlable | Closure | null $footer = null): static
15
+    {
16
+        $this->footerItem = $footer;
17
+
18
+        return $this;
19
+    }
20
+
21
+    public function getFooterItem(): View | Htmlable | null
22
+    {
23
+        return $this->evaluate($this->footerItem);
24
+    }
25
+
26
+    public function hasFooterItem(): bool
27
+    {
28
+        return $this->footerItem !== null;
29
+    }
30
+
31
+    public function getView(): string
32
+    {
33
+        return 'forms.components.journal-entry-repeater';
34
+    }
35
+}

+ 8
- 0
app/Http/Controllers/Controller.php Целия файл

@@ -0,0 +1,8 @@
1
+<?php
2
+
3
+namespace App\Http\Controllers;
4
+
5
+abstract class Controller
6
+{
7
+    //
8
+}

+ 0
- 68
app/Http/Kernel.php Целия файл

@@ -1,68 +0,0 @@
1
-<?php
2
-
3
-namespace App\Http;
4
-
5
-use Illuminate\Foundation\Http\Kernel as HttpKernel;
6
-
7
-class Kernel extends HttpKernel
8
-{
9
-    /**
10
-     * The application's global HTTP middleware stack.
11
-     *
12
-     * These middleware are run during every request to your application.
13
-     *
14
-     * @var array<int, class-string|string>
15
-     */
16
-    protected $middleware = [
17
-        // \App\Http\Middleware\TrustHosts::class,
18
-        \App\Http\Middleware\TrustProxies::class,
19
-        \Illuminate\Http\Middleware\HandleCors::class,
20
-        \App\Http\Middleware\PreventRequestsDuringMaintenance::class,
21
-        \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
22
-        \App\Http\Middleware\TrimStrings::class,
23
-        \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
24
-    ];
25
-
26
-    /**
27
-     * The application's route middleware groups.
28
-     *
29
-     * @var array<string, array<int, class-string|string>>
30
-     */
31
-    protected $middlewareGroups = [
32
-        'web' => [
33
-            \App\Http\Middleware\EncryptCookies::class,
34
-            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
35
-            \Illuminate\Session\Middleware\StartSession::class,
36
-            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
37
-            \App\Http\Middleware\VerifyCsrfToken::class,
38
-            \Illuminate\Routing\Middleware\SubstituteBindings::class,
39
-        ],
40
-
41
-        'api' => [
42
-            // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
43
-            \Illuminate\Routing\Middleware\ThrottleRequests::class . ':api',
44
-            \Illuminate\Routing\Middleware\SubstituteBindings::class,
45
-        ],
46
-    ];
47
-
48
-    /**
49
-     * The application's middleware aliases.
50
-     *
51
-     * Aliases may be used instead of class names to conveniently assign middleware to routes and groups.
52
-     *
53
-     * @var array<string, class-string|string>
54
-     */
55
-    protected $middlewareAliases = [
56
-        'auth' => \App\Http\Middleware\Authenticate::class,
57
-        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
58
-        'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class,
59
-        'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
60
-        'can' => \Illuminate\Auth\Middleware\Authorize::class,
61
-        'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
62
-        'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
63
-        'precognitive' => \Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests::class,
64
-        'signed' => \App\Http\Middleware\ValidateSignature::class,
65
-        'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
66
-        'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
67
-    ];
68
-}

+ 0
- 17
app/Http/Middleware/EncryptCookies.php Целия файл

@@ -1,17 +0,0 @@
1
-<?php
2
-
3
-namespace App\Http\Middleware;
4
-
5
-use Illuminate\Cookie\Middleware\EncryptCookies as Middleware;
6
-
7
-class EncryptCookies extends Middleware
8
-{
9
-    /**
10
-     * The names of the cookies that should not be encrypted.
11
-     *
12
-     * @var array<int, string>
13
-     */
14
-    protected $except = [
15
-        //
16
-    ];
17
-}

+ 0
- 17
app/Http/Middleware/PreventRequestsDuringMaintenance.php Целия файл

@@ -1,17 +0,0 @@
1
-<?php
2
-
3
-namespace App\Http\Middleware;
4
-
5
-use Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance as Middleware;
6
-
7
-class PreventRequestsDuringMaintenance extends Middleware
8
-{
9
-    /**
10
-     * The URIs that should be reachable while maintenance mode is enabled.
11
-     *
12
-     * @var array<int, string>
13
-     */
14
-    protected $except = [
15
-        //
16
-    ];
17
-}

+ 0
- 30
app/Http/Middleware/RedirectIfAuthenticated.php Целия файл

@@ -1,30 +0,0 @@
1
-<?php
2
-
3
-namespace App\Http\Middleware;
4
-
5
-use App\Providers\RouteServiceProvider;
6
-use Closure;
7
-use Illuminate\Http\Request;
8
-use Illuminate\Support\Facades\Auth;
9
-use Symfony\Component\HttpFoundation\Response;
10
-
11
-class RedirectIfAuthenticated
12
-{
13
-    /**
14
-     * Handle an incoming request.
15
-     *
16
-     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
17
-     */
18
-    public function handle(Request $request, Closure $next, string ...$guards): Response
19
-    {
20
-        $guards = empty($guards) ? [null] : $guards;
21
-
22
-        foreach ($guards as $guard) {
23
-            if (Auth::guard($guard)->check()) {
24
-                return redirect(RouteServiceProvider::HOME);
25
-            }
26
-        }
27
-
28
-        return $next($request);
29
-    }
30
-}

+ 0
- 19
app/Http/Middleware/TrimStrings.php Целия файл

@@ -1,19 +0,0 @@
1
-<?php
2
-
3
-namespace App\Http\Middleware;
4
-
5
-use Illuminate\Foundation\Http\Middleware\TrimStrings as Middleware;
6
-
7
-class TrimStrings extends Middleware
8
-{
9
-    /**
10
-     * The names of the attributes that should not be trimmed.
11
-     *
12
-     * @var array<int, string>
13
-     */
14
-    protected $except = [
15
-        'current_password',
16
-        'password',
17
-        'password_confirmation',
18
-    ];
19
-}

+ 0
- 20
app/Http/Middleware/TrustHosts.php Целия файл

@@ -1,20 +0,0 @@
1
-<?php
2
-
3
-namespace App\Http\Middleware;
4
-
5
-use Illuminate\Http\Middleware\TrustHosts as Middleware;
6
-
7
-class TrustHosts extends Middleware
8
-{
9
-    /**
10
-     * Get the host patterns that should be trusted.
11
-     *
12
-     * @return array<int, string|null>
13
-     */
14
-    public function hosts(): array
15
-    {
16
-        return [
17
-            $this->allSubdomainsOfApplicationUrl(),
18
-        ];
19
-    }
20
-}

+ 0
- 28
app/Http/Middleware/TrustProxies.php Целия файл

@@ -1,28 +0,0 @@
1
-<?php
2
-
3
-namespace App\Http\Middleware;
4
-
5
-use Illuminate\Http\Middleware\TrustProxies as Middleware;
6
-use Illuminate\Http\Request;
7
-
8
-class TrustProxies extends Middleware
9
-{
10
-    /**
11
-     * The trusted proxies for this application.
12
-     *
13
-     * @var array<int, string>|string|null
14
-     */
15
-    protected $proxies;
16
-
17
-    /**
18
-     * The headers that should be used to detect proxies.
19
-     *
20
-     * @var int
21
-     */
22
-    protected $headers =
23
-        Request::HEADER_X_FORWARDED_FOR |
24
-        Request::HEADER_X_FORWARDED_HOST |
25
-        Request::HEADER_X_FORWARDED_PORT |
26
-        Request::HEADER_X_FORWARDED_PROTO |
27
-        Request::HEADER_X_FORWARDED_AWS_ELB;
28
-}

+ 0
- 22
app/Http/Middleware/ValidateSignature.php Целия файл

@@ -1,22 +0,0 @@
1
-<?php
2
-
3
-namespace App\Http\Middleware;
4
-
5
-use Illuminate\Routing\Middleware\ValidateSignature as Middleware;
6
-
7
-class ValidateSignature extends Middleware
8
-{
9
-    /**
10
-     * The names of the query string parameters that should be ignored.
11
-     *
12
-     * @var array<int, string>
13
-     */
14
-    protected $except = [
15
-        // 'fbclid',
16
-        // 'utm_campaign',
17
-        // 'utm_content',
18
-        // 'utm_medium',
19
-        // 'utm_source',
20
-        // 'utm_term',
21
-    ];
22
-}

+ 0
- 17
app/Http/Middleware/VerifyCsrfToken.php Целия файл

@@ -1,17 +0,0 @@
1
-<?php
2
-
3
-namespace App\Http\Middleware;
4
-
5
-use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
6
-
7
-class VerifyCsrfToken extends Middleware
8
-{
9
-    /**
10
-     * The URIs that should be excluded from CSRF verification.
11
-     *
12
-     * @var array<int, string>
13
-     */
14
-    protected $except = [
15
-        //
16
-    ];
17
-}

+ 2
- 2
app/Listeners/ConfigureCompanyNavigation.php Целия файл

@@ -4,6 +4,7 @@ namespace App\Listeners;
4 4
 
5 5
 use App\Events\CompanyConfigured;
6 6
 use App\Filament\Company\Pages\Accounting\AccountChart;
7
+use App\Filament\Company\Pages\Accounting\Transactions;
7 8
 use App\Filament\Company\Pages\Reports;
8 9
 use App\Filament\Company\Pages\Service\ConnectedAccount;
9 10
 use App\Filament\Company\Pages\Service\LiveCurrency;
@@ -12,7 +13,6 @@ use App\Filament\Company\Pages\Setting\CompanyDefault;
12 13
 use App\Filament\Company\Pages\Setting\CompanyProfile;
13 14
 use App\Filament\Company\Pages\Setting\Invoice;
14 15
 use App\Filament\Company\Pages\Setting\Localization;
15
-use App\Filament\Company\Resources\Accounting\TransactionResource;
16 16
 use App\Filament\Company\Resources\Banking\AccountResource;
17 17
 use App\Filament\Company\Resources\Core\DepartmentResource;
18 18
 use App\Filament\Company\Resources\Setting\CurrencyResource;
@@ -114,7 +114,7 @@ class ConfigureCompanyNavigation
114 114
                         ->extraSidebarAttributes(['class' => 'es-sidebar-group'])
115 115
                         ->items([
116 116
                             ...AccountChart::getNavigationItems(),
117
-                            ...TransactionResource::getNavigationItems(),
117
+                            ...Transactions::getNavigationItems(),
118 118
                         ]),
119 119
                     NavigationGroup::make('Banking')
120 120
                         ->icon('heroicon-o-building-library')

+ 2
- 0
app/Livewire/UpdatePassword.php Целия файл

@@ -146,6 +146,7 @@ class UpdatePassword extends Component implements HasForms
146 146
                 Forms\Components\TextInput::make('password')
147 147
                     ->label(__('filament-companies::default.labels.new_password'))
148 148
                     ->password()
149
+                    ->revealable()
149 150
                     ->autocomplete('new-password')
150 151
                     ->rule(Password::default())
151 152
                     ->required()
@@ -155,6 +156,7 @@ class UpdatePassword extends Component implements HasForms
155 156
                 Forms\Components\TextInput::make('password_confirmation')
156 157
                     ->label(__('filament-companies::default.labels.password_confirmation'))
157 158
                     ->password()
159
+                    ->revealable()
158 160
                     ->autocomplete('new-password')
159 161
                     ->required()
160 162
                     ->dehydrated(false),

+ 3
- 3
app/Livewire/UpdateProfileInformation.php Целия файл

@@ -18,7 +18,7 @@ use Illuminate\Support\Facades\Blade;
18 18
 use Illuminate\Support\HtmlString;
19 19
 use Livewire\Component;
20 20
 use RuntimeException;
21
-use Wallo\FilamentCompanies\Features;
21
+use Wallo\FilamentCompanies\FilamentCompanies;
22 22
 
23 23
 /**
24 24
  * @property Form $form
@@ -148,8 +148,8 @@ class UpdateProfileInformation extends Component implements HasForms
148 148
                             </div>
149 149
                         ');
150 150
                     })
151
-                    ->disk(Features::profilePhotoDisk())
152
-                    ->directory(Features::profilePhotoStoragePath())
151
+                    ->disk(FilamentCompanies::profilePhotoDisk())
152
+                    ->directory(FilamentCompanies::profilePhotoStoragePath())
153 153
                     ->saveUploadedFileUsing(function (User $record, UploadedFile $file) {
154 154
                         $record->updateProfilePhoto($file);
155 155
                     })

+ 22
- 0
app/Models/Accounting/Account.php Целия файл

@@ -16,6 +16,7 @@ use Illuminate\Database\Eloquent\Model;
16 16
 use Illuminate\Database\Eloquent\Relations\BelongsTo;
17 17
 use Illuminate\Database\Eloquent\Relations\HasMany;
18 18
 use Illuminate\Database\Eloquent\Relations\MorphTo;
19
+use Illuminate\Support\Carbon;
19 20
 use Wallo\FilamentCompanies\FilamentCompanies;
20 21
 
21 22
 #[ObservedBy(AccountObserver::class)]
@@ -93,6 +94,27 @@ class Account extends Model
93 94
         return $this->morphTo();
94 95
     }
95 96
 
97
+    public function getLastTransactionDateAttribute(): ?string
98
+    {
99
+        $lastJournalEntryTransaction = $this->journalEntries()
100
+            ->with('transaction')
101
+            ->latest('transactions.posted_at')
102
+            ->join('transactions', 'journal_entries.transaction_id', '=', 'transactions.id')
103
+            ->select('transactions.posted_at')
104
+            ->first();
105
+
106
+        if ($lastJournalEntryTransaction) {
107
+            return Carbon::parse($lastJournalEntryTransaction->posted_at)->format('F j, Y');
108
+        }
109
+
110
+        return null;
111
+    }
112
+
113
+    public function isUncategorized(): bool
114
+    {
115
+        return $this->type->isUncategorized();
116
+    }
117
+
96 118
     public function transactions(): HasMany
97 119
     {
98 120
         return $this->hasMany(Transaction::class, 'account_id');

+ 19
- 11
app/Models/Accounting/JournalEntry.php Целия файл

@@ -3,12 +3,14 @@
3 3
 namespace App\Models\Accounting;
4 4
 
5 5
 use App\Casts\MoneyCast;
6
+use App\Enums\Accounting\JournalEntryType;
6 7
 use App\Models\Banking\BankAccount;
7 8
 use App\Observers\JournalEntryObserver;
8 9
 use App\Traits\Blamable;
9 10
 use App\Traits\CompanyOwned;
10 11
 use Database\Factories\Accounting\JournalEntryFactory;
11 12
 use Illuminate\Database\Eloquent\Attributes\ObservedBy;
13
+use Illuminate\Database\Eloquent\Builder;
12 14
 use Illuminate\Database\Eloquent\Factories\Factory;
13 15
 use Illuminate\Database\Eloquent\Factories\HasFactory;
14 16
 use Illuminate\Database\Eloquent\Model;
@@ -26,7 +28,7 @@ class JournalEntry extends Model
26 28
         'company_id',
27 29
         'account_id',
28 30
         'transaction_id',
29
-        'type', // debit or credit
31
+        'type',
30 32
         'amount',
31 33
         'description',
32 34
         'created_by',
@@ -34,6 +36,7 @@ class JournalEntry extends Model
34 36
     ];
35 37
 
36 38
     protected $casts = [
39
+        'type' => JournalEntryType::class,
37 40
         'amount' => MoneyCast::class,
38 41
     ];
39 42
 
@@ -52,16 +55,6 @@ class JournalEntry extends Model
52 55
         return $this->belongsTo(Transaction::class, 'transaction_id');
53 56
     }
54 57
 
55
-    public function scopeDebit($query)
56
-    {
57
-        return $query->where('type', 'debit');
58
-    }
59
-
60
-    public function scopeCredit($query)
61
-    {
62
-        return $query->where('type', 'credit');
63
-    }
64
-
65 58
     public function bankAccount(): BelongsTo
66 59
     {
67 60
         return $this->account()->where('accountable_type', BankAccount::class);
@@ -77,6 +70,21 @@ class JournalEntry extends Model
77 70
         return $this->belongsTo(FilamentCompanies::userModel(), 'updated_by');
78 71
     }
79 72
 
73
+    public function isUncategorized(): bool
74
+    {
75
+        return $this->account->isUncategorized();
76
+    }
77
+
78
+    public function scopeDebit(Builder $query): Builder
79
+    {
80
+        return $query->where('type', JournalEntryType::Debit);
81
+    }
82
+
83
+    public function scopeCredit(Builder $query): Builder
84
+    {
85
+        return $query->where('type', JournalEntryType::Credit);
86
+    }
87
+
80 88
     protected static function newFactory(): Factory
81 89
     {
82 90
         return JournalEntryFactory::new();

+ 7
- 0
app/Models/Accounting/Transaction.php Целия файл

@@ -3,6 +3,7 @@
3 3
 namespace App\Models\Accounting;
4 4
 
5 5
 use App\Casts\MoneyCast;
6
+use App\Enums\Accounting\TransactionType;
6 7
 use App\Models\Banking\BankAccount;
7 8
 use App\Models\Common\Contact;
8 9
 use App\Observers\TransactionObserver;
@@ -43,6 +44,7 @@ class Transaction extends Model
43 44
     ];
44 45
 
45 46
     protected $casts = [
47
+        'type' => TransactionType::class,
46 48
         'amount' => MoneyCast::class,
47 49
         'pending' => 'boolean',
48 50
         'reviewed' => 'boolean',
@@ -74,6 +76,11 @@ class Transaction extends Model
74 76
         return $this->hasMany(JournalEntry::class, 'transaction_id');
75 77
     }
76 78
 
79
+    public function isUncategorized(): bool
80
+    {
81
+        return $this->journalEntries->contains(fn (JournalEntry $entry) => $entry->account->isUncategorized());
82
+    }
83
+
77 84
     public function createdBy(): BelongsTo
78 85
     {
79 86
         return $this->belongsTo(FilamentCompanies::userModel(), 'created_by');

+ 7
- 0
app/Models/Banking/BankAccount.php Целия файл

@@ -4,6 +4,7 @@ namespace App\Models\Banking;
4 4
 
5 5
 use App\Enums\BankAccountType;
6 6
 use App\Models\Accounting\Account;
7
+use App\Models\Accounting\Transaction;
7 8
 use App\Observers\BankAccountObserver;
8 9
 use App\Traits\Blamable;
9 10
 use App\Traits\CompanyOwned;
@@ -16,6 +17,7 @@ use Illuminate\Database\Eloquent\Factories\Factory;
16 17
 use Illuminate\Database\Eloquent\Factories\HasFactory;
17 18
 use Illuminate\Database\Eloquent\Model;
18 19
 use Illuminate\Database\Eloquent\Relations\BelongsTo;
20
+use Illuminate\Database\Eloquent\Relations\HasMany;
19 21
 use Illuminate\Database\Eloquent\Relations\HasOne;
20 22
 use Illuminate\Database\Eloquent\Relations\MorphOne;
21 23
 use Wallo\FilamentCompanies\FilamentCompanies;
@@ -70,6 +72,11 @@ class BankAccount extends Model
70 72
         return $this->belongsTo(Institution::class, 'institution_id');
71 73
     }
72 74
 
75
+    public function transactions(): HasMany
76
+    {
77
+        return $this->hasMany(Transaction::class, 'bank_account_id');
78
+    }
79
+
73 80
     public function createdBy(): BelongsTo
74 81
     {
75 82
         return $this->belongsTo(FilamentCompanies::userModel(), 'created_by');

+ 5
- 5
app/Observers/AccountObserver.php Целия файл

@@ -15,11 +15,6 @@ class AccountObserver
15 15
     public function creating(Account $account): void
16 16
     {
17 17
         $this->setCategoryAndType($account, true);
18
-
19
-        // $bankAccount = $account->accountable;
20
-        if (($account->accountable_type === BankAccount::class) && $account->code === null) {
21
-            $this->setFieldsForBankAccount($account);
22
-        }
23 18
     }
24 19
 
25 20
     public function updating(Account $account): void
@@ -54,6 +49,11 @@ class AccountObserver
54 49
      */
55 50
     public function created(Account $account): void
56 51
     {
52
+        // $bankAccount = $account->accountable;
53
+        if (($account->accountable_type === BankAccount::class) && $account->code === null) {
54
+            $this->setFieldsForBankAccount($account);
55
+        }
56
+
57 57
         //$account->histories()->create([
58 58
         // 'company_id' => $account->company_id,
59 59
         //  'account_id' => $account->id,

+ 0
- 1
app/Observers/JournalEntryObserver.php Целия файл

@@ -15,7 +15,6 @@ class JournalEntryObserver
15 15
         //
16 16
     }
17 17
 
18
-
19 18
     private function updateEndingBalance(Account $account): void
20 19
     {
21 20
         //

+ 9
- 4
app/Observers/TransactionObserver.php Целия файл

@@ -2,6 +2,7 @@
2 2
 
3 3
 namespace App\Observers;
4 4
 
5
+use App\Enums\Accounting\TransactionType;
5 6
 use App\Models\Accounting\Account;
6 7
 use App\Models\Accounting\JournalEntry;
7 8
 use App\Models\Accounting\Transaction;
@@ -14,11 +15,15 @@ class TransactionObserver
14 15
      */
15 16
     public function created(Transaction $transaction): void
16 17
     {
18
+        if ($transaction->type === TransactionType::Journal) {
19
+            return;
20
+        }
21
+
17 22
         $chartAccount = $transaction->account;
18 23
         $bankAccount = $transaction->bankAccount->account;
19 24
 
20
-        $debitAccount = $transaction->type === 'withdrawal' ? $chartAccount : $bankAccount;
21
-        $creditAccount = $transaction->type === 'withdrawal' ? $bankAccount : $chartAccount;
25
+        $debitAccount = $transaction->type === TransactionType::Withdrawal ? $chartAccount : $bankAccount;
26
+        $creditAccount = $transaction->type === TransactionType::Withdrawal ? $bankAccount : $chartAccount;
22 27
 
23 28
         $this->createJournalEntries($transaction, $debitAccount, $creditAccount);
24 29
     }
@@ -63,8 +68,8 @@ class TransactionObserver
63 68
         $debitEntry = $journalEntries->where('type', 'debit')->first();
64 69
         $creditEntry = $journalEntries->where('type', 'credit')->first();
65 70
 
66
-        $debitAccount = $transaction->type === 'withdrawal' ? $chartAccount : $bankAccount;
67
-        $creditAccount = $transaction->type === 'withdrawal' ? $bankAccount : $chartAccount;
71
+        $debitAccount = $transaction->type === TransactionType::Withdrawal ? $chartAccount : $bankAccount;
72
+        $creditAccount = $transaction->type === TransactionType::Withdrawal ? $bankAccount : $chartAccount;
68 73
 
69 74
         $debitEntry?->update([
70 75
             'account_id' => $debitAccount->id,

+ 0
- 19
app/Providers/BroadcastServiceProvider.php Целия файл

@@ -1,19 +0,0 @@
1
-<?php
2
-
3
-namespace App\Providers;
4
-
5
-use Illuminate\Support\Facades\Broadcast;
6
-use Illuminate\Support\ServiceProvider;
7
-
8
-class BroadcastServiceProvider extends ServiceProvider
9
-{
10
-    /**
11
-     * Bootstrap any application services.
12
-     */
13
-    public function boot(): void
14
-    {
15
-        Broadcast::routes();
16
-
17
-        require base_path('routes/channels.php');
18
-    }
19
-}

+ 39
- 12
app/Providers/FilamentCompaniesServiceProvider.php Целия файл

@@ -24,6 +24,7 @@ use App\Livewire\UpdateProfileInformation;
24 24
 use App\Models\Company;
25 25
 use Exception;
26 26
 use Filament\Actions\CreateAction;
27
+use Filament\Actions\EditAction;
27 28
 use Filament\Forms\Components\DatePicker;
28 29
 use Filament\Forms\Components\DateTimePicker;
29 30
 use Filament\Forms\Components\Select;
@@ -34,6 +35,7 @@ use Filament\Pages;
34 35
 use Filament\Panel;
35 36
 use Filament\PanelProvider;
36 37
 use Filament\Support\Colors\Color;
38
+use Filament\Support\Enums\Alignment;
37 39
 use Filament\Widgets;
38 40
 use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
39 41
 use Illuminate\Cookie\Middleware\EncryptCookies;
@@ -43,12 +45,12 @@ use Illuminate\Session\Middleware\AuthenticateSession;
43 45
 use Illuminate\Session\Middleware\StartSession;
44 46
 use Illuminate\View\Middleware\ShareErrorsFromSession;
45 47
 use Wallo\FilamentCompanies\Actions\GenerateRedirectForProvider;
48
+use Wallo\FilamentCompanies\Enums\Feature;
49
+use Wallo\FilamentCompanies\Enums\Provider;
46 50
 use Wallo\FilamentCompanies\FilamentCompanies;
47 51
 use Wallo\FilamentCompanies\Pages\Auth\Login;
48 52
 use Wallo\FilamentCompanies\Pages\Auth\Register;
49 53
 use Wallo\FilamentCompanies\Pages\Company\CompanySettings;
50
-use Wallo\FilamentCompanies\Providers;
51
-use Wallo\FilamentCompanies\Socialite;
52 54
 
53 55
 class FilamentCompaniesServiceProvider extends PanelProvider
54 56
 {
@@ -81,8 +83,8 @@ class FilamentCompaniesServiceProvider extends PanelProvider
81 83
                     ->notifications()
82 84
                     ->modals()
83 85
                     ->socialite(
84
-                        providers: [Providers::github()],
85
-                        features: [Socialite::rememberSession(), Socialite::providerAvatars()]
86
+                        providers: [Provider::Github],
87
+                        features: [Feature::RememberSession, Feature::ProviderAvatars],
86 88
                     ),
87 89
             )
88 90
             ->colors([
@@ -144,13 +146,13 @@ class FilamentCompaniesServiceProvider extends PanelProvider
144 146
         FilamentCompanies::deleteCompaniesUsing(DeleteCompany::class);
145 147
         FilamentCompanies::deleteUsersUsing(DeleteUser::class);
146 148
 
147
-        Socialite::resolvesSocialiteUsersUsing(ResolveSocialiteUser::class);
148
-        Socialite::createUsersFromProviderUsing(CreateUserFromProvider::class);
149
-        Socialite::createConnectedAccountsUsing(CreateConnectedAccount::class);
150
-        Socialite::updateConnectedAccountsUsing(UpdateConnectedAccount::class);
151
-        Socialite::setUserPasswordsUsing(SetUserPassword::class);
152
-        Socialite::handlesInvalidStateUsing(HandleInvalidState::class);
153
-        Socialite::generatesProvidersRedirectsUsing(GenerateRedirectForProvider::class);
149
+        FilamentCompanies::resolvesSocialiteUsersUsing(ResolveSocialiteUser::class);
150
+        FilamentCompanies::createUsersFromProviderUsing(CreateUserFromProvider::class);
151
+        FilamentCompanies::createConnectedAccountsUsing(CreateConnectedAccount::class);
152
+        FilamentCompanies::updateConnectedAccountsUsing(UpdateConnectedAccount::class);
153
+        FilamentCompanies::setUserPasswordsUsing(SetUserPassword::class);
154
+        FilamentCompanies::handlesInvalidStateUsing(HandleInvalidState::class);
155
+        FilamentCompanies::generatesProvidersRedirectsUsing(GenerateRedirectForProvider::class);
154 156
     }
155 157
 
156 158
     /**
@@ -182,7 +184,11 @@ class FilamentCompaniesServiceProvider extends PanelProvider
182 184
         $this->configureSelect();
183 185
 
184 186
         CreateAction::configureUsing(static function (CreateAction $action) {
185
-            $action->createAnother(false);
187
+            $action
188
+                ->createAnother(false)
189
+                ->stickyModalHeader()
190
+                ->stickyModalFooter()
191
+                ->modalFooterActionsAlignment(Alignment::End);
186 192
         });
187 193
 
188 194
         DatePicker::configureUsing(static function (DatePicker $component) {
@@ -192,6 +198,27 @@ class FilamentCompaniesServiceProvider extends PanelProvider
192 198
         DateTimePicker::configureUsing(static function (DateTimePicker $component) {
193 199
             $component->native(false);
194 200
         });
201
+
202
+        EditAction::configureUsing(static function (EditAction $action) {
203
+            $action
204
+                ->stickyModalHeader()
205
+                ->stickyModalFooter()
206
+                ->modalFooterActionsAlignment(Alignment::End);
207
+        });
208
+
209
+        \Filament\Tables\Actions\EditAction::configureUsing(static function (\Filament\Tables\Actions\EditAction $action) {
210
+            $action
211
+                ->stickyModalHeader()
212
+                ->stickyModalFooter()
213
+                ->modalFooterActionsAlignment(Alignment::End);
214
+        });
215
+
216
+        \Filament\Tables\Actions\CreateAction::configureUsing(static function (\Filament\Tables\Actions\CreateAction $action) {
217
+            $action
218
+                ->stickyModalHeader()
219
+                ->stickyModalFooter()
220
+                ->modalFooterActionsAlignment(Alignment::End);
221
+        });
195 222
     }
196 223
 
197 224
     /**

+ 0
- 40
app/Providers/RouteServiceProvider.php Целия файл

@@ -1,40 +0,0 @@
1
-<?php
2
-
3
-namespace App\Providers;
4
-
5
-use Illuminate\Cache\RateLimiting\Limit;
6
-use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
7
-use Illuminate\Http\Request;
8
-use Illuminate\Support\Facades\RateLimiter;
9
-use Illuminate\Support\Facades\Route;
10
-
11
-class RouteServiceProvider extends ServiceProvider
12
-{
13
-    /**
14
-     * The path to your application's "home" route.
15
-     *
16
-     * Typically, users are redirected here after authentication.
17
-     *
18
-     * @var string
19
-     */
20
-    public const HOME = '/home';
21
-
22
-    /**
23
-     * Define your route model bindings, pattern filters, and other route configuration.
24
-     */
25
-    public function boot(): void
26
-    {
27
-        RateLimiter::for('api', function (Request $request) {
28
-            return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
29
-        });
30
-
31
-        $this->routes(function () {
32
-            Route::middleware('api')
33
-                ->prefix('api')
34
-                ->group(base_path('routes/api.php'));
35
-
36
-            Route::middleware('web')
37
-                ->group(base_path('routes/web.php'));
38
-        });
39
-    }
40
-}

+ 1
- 1
app/Scopes/CurrentCompanyScope.php Целия файл

@@ -15,7 +15,7 @@ class CurrentCompanyScope implements Scope
15 15
     public function apply(Builder $builder, Model $model): void
16 16
     {
17 17
         if (Auth::check() && Auth::user()->currentCompany) {
18
-            $builder->where('company_id', Auth::user()->currentCompany->id);
18
+            $builder->where("{$model->getTable()}.company_id", Auth::user()->currentCompany->id);
19 19
         }
20 20
     }
21 21
 }

+ 17
- 0
app/Services/AccountService.php Целия файл

@@ -10,6 +10,7 @@ use App\DTO\AccountDTO;
10 10
 use App\Enums\Accounting\AccountCategory;
11 11
 use App\Models\Accounting\Account;
12 12
 use App\Models\Accounting\Transaction;
13
+use App\Models\Banking\BankAccount;
13 14
 use App\Repositories\Accounting\JournalEntryRepository;
14 15
 use App\ValueObjects\BalanceValue;
15 16
 use Illuminate\Database\Eloquent\Collection;
@@ -190,6 +191,22 @@ class AccountService
190 191
         return new AccountBalanceReportDTO($accountCategories, $formattedReportTotalBalances);
191 192
     }
192 193
 
194
+    public function getTotalBalanceForAllBankAccount(string $startDate, string $endDate): BalanceValue
195
+    {
196
+        $bankAccountsAccounts = Account::where('accountable_type', BankAccount::class)
197
+            ->get();
198
+
199
+        $totalBalance = 0;
200
+
201
+        // Get ending balance for each bank account
202
+        foreach ($bankAccountsAccounts as $account) {
203
+            $endingBalance = $this->getEndingBalance($account, $startDate, $endDate)?->getValue() ?? 0;
204
+            $totalBalance += $endingBalance;
205
+        }
206
+
207
+        return new BalanceValue($totalBalance, 'USD');
208
+    }
209
+
193 210
     public function getAccountCategoryOrder(): array
194 211
     {
195 212
         return [

+ 11
- 8
app/Services/TransactionService.php Целия файл

@@ -4,6 +4,7 @@ namespace App\Services;
4 4
 
5 5
 use App\Enums\Accounting\AccountCategory;
6 6
 use App\Enums\Accounting\AccountType;
7
+use App\Enums\Accounting\TransactionType;
7 8
 use App\Models\Accounting\Account;
8 9
 use App\Models\Accounting\Transaction;
9 10
 use App\Models\Banking\BankAccount;
@@ -39,7 +40,7 @@ class TransactionService
39 40
 
40 41
     public function createStartingBalanceTransaction(Company $company, Account $account, BankAccount $bankAccount, float $startingBalance, string $startDate): void
41 42
     {
42
-        $transactionType = $startingBalance >= 0 ? 'deposit' : 'withdrawal';
43
+        $transactionType = $startingBalance >= 0 ? TransactionType::Deposit : TransactionType::Withdrawal;
43 44
         $chartAccount = $account->where('category', AccountCategory::Equity)->where('name', 'Owner\'s Equity')->first();
44 45
         $postedAt = Carbon::parse($startDate)->subDay()->toDateTimeString();
45 46
 
@@ -59,7 +60,7 @@ class TransactionService
59 60
 
60 61
     public function storeTransaction(Company $company, BankAccount $bankAccount, object $transaction): void
61 62
     {
62
-        $transactionType = $transaction->amount < 0 ? 'deposit' : 'withdrawal';
63
+        $transactionType = $transaction->amount < 0 ? TransactionType::Deposit : TransactionType::Withdrawal;
63 64
         $paymentChannel = $transaction->payment_channel;
64 65
         $chartAccount = $this->getAccountFromTransaction($company, $transaction, $transactionType);
65 66
         $postedAt = $transaction->datetime ?? Carbon::parse($transaction->date)->toDateTimeString();
@@ -79,11 +80,12 @@ class TransactionService
79 80
         ]);
80 81
     }
81 82
 
82
-    public function getAccountFromTransaction(Company $company, object $transaction, string $transactionType): Account
83
+    public function getAccountFromTransaction(Company $company, object $transaction, TransactionType $transactionType): Account
83 84
     {
84 85
         $accountCategory = match ($transactionType) {
85
-            'deposit' => AccountCategory::Revenue,
86
-            'withdrawal' => AccountCategory::Expense,
86
+            TransactionType::Deposit => AccountCategory::Revenue,
87
+            TransactionType::Withdrawal => AccountCategory::Expense,
88
+            default => null,
87 89
         };
88 90
 
89 91
         $accounts = $company->accounts()
@@ -127,11 +129,12 @@ class TransactionService
127 129
         return $bestMatchName;
128 130
     }
129 131
 
130
-    public function getUncategorizedAccount(Company $company, string $transactionType): Account
132
+    public function getUncategorizedAccount(Company $company, TransactionType $transactionType): Account
131 133
     {
132 134
         [$type, $name] = match ($transactionType) {
133
-            'deposit' => [AccountType::UncategorizedRevenue, 'Uncategorized Income'],
134
-            'withdrawal' => [AccountType::UncategorizedExpense, 'Uncategorized Expense'],
135
+            TransactionType::Deposit => [AccountType::UncategorizedRevenue, 'Uncategorized Income'],
136
+            TransactionType::Withdrawal => [AccountType::UncategorizedExpense, 'Uncategorized Expense'],
137
+            default => [null, null],
135 138
         };
136 139
 
137 140
         return $company->accounts()

+ 111
- 0
app/Traits/HasJournalEntryActions.php Целия файл

@@ -0,0 +1,111 @@
1
+<?php
2
+
3
+namespace App\Traits;
4
+
5
+use App\Enums\Accounting\JournalEntryType;
6
+
7
+trait HasJournalEntryActions
8
+{
9
+    public string $debitAmount = '0.00';
10
+
11
+    public string $creditAmount = '0.00';
12
+
13
+    public function setDebitAmount(string $amount): void
14
+    {
15
+        $this->debitAmount = $amount;
16
+    }
17
+
18
+    public function getDebitAmount(): string
19
+    {
20
+        return $this->debitAmount;
21
+    }
22
+
23
+    public function getFormattedDebitAmount(): string
24
+    {
25
+        return money($this->getDebitAmount(), 'USD', true)->format();
26
+    }
27
+
28
+    public function setCreditAmount(string $amount): void
29
+    {
30
+        $this->creditAmount = $amount;
31
+    }
32
+
33
+    public function getCreditAmount(): string
34
+    {
35
+        return $this->creditAmount;
36
+    }
37
+
38
+    public function getFormattedCreditAmount(): string
39
+    {
40
+        return money($this->getCreditAmount(), 'USD', true)->format();
41
+    }
42
+
43
+    public function getBalanceDifference(): string
44
+    {
45
+        return bcsub($this->getDebitAmount(), $this->getCreditAmount(), 2);
46
+    }
47
+
48
+    public function getFormattedBalanceDifference(): string
49
+    {
50
+        $difference = $this->getBalanceDifference();
51
+        $absoluteDifference = abs((float) $difference);
52
+
53
+        return money($absoluteDifference, 'USD', true)->format();
54
+    }
55
+
56
+    public function isJournalEntryBalanced(): bool
57
+    {
58
+        return bccomp($this->getDebitAmount(), $this->getCreditAmount(), 2) === 0;
59
+    }
60
+
61
+    public function resetJournalEntryAmounts(): void
62
+    {
63
+        $this->reset(['debitAmount', 'creditAmount']);
64
+    }
65
+
66
+    public function adjustJournalEntryAmountsForTypeChange(JournalEntryType $newType, JournalEntryType $oldType, ?string $amount): void
67
+    {
68
+        if ($newType !== $oldType) {
69
+            $normalizedAmount = $amount === null ? '0.00' : rtrim($amount, '.');
70
+            $normalizedAmount = $this->sanitizeAndFormatAmount($normalizedAmount);
71
+
72
+            if (bccomp($normalizedAmount, '0.00', 2) === 0) {
73
+                return;
74
+            }
75
+
76
+            if ($oldType->isDebit() && $newType->isCredit()) {
77
+                $this->setDebitAmount(bcsub($this->getDebitAmount(), $normalizedAmount, 2));
78
+                $this->setCreditAmount(bcadd($this->getCreditAmount(), $normalizedAmount, 2));
79
+            } elseif ($oldType->isCredit() && $newType->isDebit()) {
80
+                $this->setDebitAmount(bcadd($this->getDebitAmount(), $normalizedAmount, 2));
81
+                $this->setCreditAmount(bcsub($this->getCreditAmount(), $normalizedAmount, 2));
82
+            }
83
+        }
84
+    }
85
+
86
+    public function updateJournalEntryAmount(JournalEntryType $journalEntryType, ?string $newAmount, ?string $oldAmount): void
87
+    {
88
+        if ($newAmount === $oldAmount) {
89
+            return;
90
+        }
91
+
92
+        $normalizedNewAmount = $newAmount === null ? '0.00' : rtrim($newAmount, '.');
93
+        $normalizedOldAmount = $oldAmount === null ? '0.00' : rtrim($oldAmount, '.');
94
+
95
+        $formattedNewAmount = $this->sanitizeAndFormatAmount($normalizedNewAmount);
96
+        $formattedOldAmount = $this->sanitizeAndFormatAmount($normalizedOldAmount);
97
+
98
+        $difference = bcsub($formattedNewAmount, $formattedOldAmount, 2);
99
+
100
+        if ($journalEntryType->isDebit()) {
101
+            $this->setDebitAmount(bcadd($this->getDebitAmount(), $difference, 2));
102
+        } else {
103
+            $this->setCreditAmount(bcadd($this->getCreditAmount(), $difference, 2));
104
+        }
105
+    }
106
+
107
+    protected function sanitizeAndFormatAmount(string $amount): string
108
+    {
109
+        return (string) money($amount, 'USD')->getAmount();
110
+    }
111
+}

+ 1
- 0
app/ValueObjects/BalanceValue.php Целия файл

@@ -5,6 +5,7 @@ namespace App\ValueObjects;
5 5
 class BalanceValue
6 6
 {
7 7
     private int $value;
8
+
8 9
     private string $currency;
9 10
 
10 11
     public function __construct(int $value, string $currency = 'USD')

+ 6
- 44
artisan Целия файл

@@ -1,53 +1,15 @@
1 1
 #!/usr/bin/env php
2 2
 <?php
3 3
 
4
-define('LARAVEL_START', microtime(true));
4
+use Symfony\Component\Console\Input\ArgvInput;
5 5
 
6
-/*
7
-|--------------------------------------------------------------------------
8
-| Register The Auto Loader
9
-|--------------------------------------------------------------------------
10
-|
11
-| Composer provides a convenient, automatically generated class loader
12
-| for our application. We just need to utilize it! We'll require it
13
-| into the script here so that we do not have to worry about the
14
-| loading of any of our classes manually. It's great to relax.
15
-|
16
-*/
6
+define('LARAVEL_START', microtime(true));
17 7
 
8
+// Register the Composer autoloader...
18 9
 require __DIR__.'/vendor/autoload.php';
19 10
 
20
-$app = require_once __DIR__.'/bootstrap/app.php';
21
-
22
-/*
23
-|--------------------------------------------------------------------------
24
-| Run The Artisan Application
25
-|--------------------------------------------------------------------------
26
-|
27
-| When we run the console application, the current CLI command will be
28
-| executed in this console and the response sent back to a terminal
29
-| or another output device for the developers. Here goes nothing!
30
-|
31
-*/
32
-
33
-$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class);
34
-
35
-$status = $kernel->handle(
36
-    $input = new Symfony\Component\Console\Input\ArgvInput,
37
-    new Symfony\Component\Console\Output\ConsoleOutput
38
-);
39
-
40
-/*
41
-|--------------------------------------------------------------------------
42
-| Shutdown The Application
43
-|--------------------------------------------------------------------------
44
-|
45
-| Once Artisan has finished running, we will fire off the shutdown events
46
-| so that any final work may be done by the application before we shut
47
-| down the process. This is the last thing to happen to the request.
48
-|
49
-*/
50
-
51
-$kernel->terminate($input, $status);
11
+// Bootstrap Laravel and handle the command...
12
+$status = (require_once __DIR__.'/bootstrap/app.php')
13
+    ->handleCommand(new ArgvInput);
52 14
 
53 15
 exit($status);

+ 17
- 53
bootstrap/app.php Целия файл

@@ -1,55 +1,19 @@
1 1
 <?php
2 2
 
3
-/*
4
-|--------------------------------------------------------------------------
5
-| Create The Application
6
-|--------------------------------------------------------------------------
7
-|
8
-| The first thing we will do is create a new Laravel application instance
9
-| which serves as the "glue" for all the components of Laravel, and is
10
-| the IoC container for the system binding all of the various parts.
11
-|
12
-*/
13
-
14
-$app = new Illuminate\Foundation\Application(
15
-    $_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
16
-);
17
-
18
-/*
19
-|--------------------------------------------------------------------------
20
-| Bind Important Interfaces
21
-|--------------------------------------------------------------------------
22
-|
23
-| Next, we need to bind some important interfaces into the container so
24
-| we will be able to resolve them when needed. The kernels serve the
25
-| incoming requests to this application from both the web and CLI.
26
-|
27
-*/
28
-
29
-$app->singleton(
30
-    Illuminate\Contracts\Http\Kernel::class,
31
-    App\Http\Kernel::class
32
-);
33
-
34
-$app->singleton(
35
-    Illuminate\Contracts\Console\Kernel::class,
36
-    App\Console\Kernel::class
37
-);
38
-
39
-$app->singleton(
40
-    Illuminate\Contracts\Debug\ExceptionHandler::class,
41
-    App\Exceptions\Handler::class
42
-);
43
-
44
-/*
45
-|--------------------------------------------------------------------------
46
-| Return The Application
47
-|--------------------------------------------------------------------------
48
-|
49
-| This script returns the application instance. The instance is given to
50
-| the calling script so we can separate the building of the instances
51
-| from the actual running of the application and sending responses.
52
-|
53
-*/
54
-
55
-return $app;
3
+use Illuminate\Foundation\Application;
4
+use Illuminate\Foundation\Configuration\Exceptions;
5
+use Illuminate\Foundation\Configuration\Middleware;
6
+
7
+return Application::configure(basePath: dirname(__DIR__))
8
+    ->withRouting(
9
+        web: __DIR__ . '/../routes/web.php',
10
+        api: __DIR__ . '/../routes/api.php',
11
+        commands: __DIR__ . '/../routes/console.php',
12
+        health: '/up',
13
+    )
14
+    ->withMiddleware(function (Middleware $middleware) {
15
+        //
16
+    })
17
+    ->withExceptions(function (Exceptions $exceptions) {
18
+        //
19
+    })->create();

+ 15
- 0
bootstrap/providers.php Целия файл

@@ -0,0 +1,15 @@
1
+<?php
2
+
3
+return [
4
+    App\Providers\AppServiceProvider::class,
5
+    App\Providers\AuthServiceProvider::class,
6
+    App\Providers\EventServiceProvider::class,
7
+    App\Providers\Filament\AdminPanelProvider::class,
8
+    App\Providers\FilamentCompaniesServiceProvider::class,
9
+    App\Providers\Filament\UserPanelProvider::class,
10
+    App\Providers\Faker\FakerServiceProvider::class,
11
+    App\Providers\MacroServiceProvider::class,
12
+    App\Providers\SquireServiceProvider::class,
13
+    App\Providers\TranslationServiceProvider::class,
14
+    App\Providers\CurrencyServiceProvider::class,
15
+];

+ 19
- 17
composer.json Целия файл

@@ -11,31 +11,32 @@
11 11
         "php": "^8.2",
12 12
         "ext-bcmath": "*",
13 13
         "ext-intl": "*",
14
-        "akaunting/laravel-money": "^5.1",
15
-        "andrewdwallo/filament-companies": "^3.0",
14
+        "akaunting/laravel-money": "^5.2",
15
+        "andrewdwallo/filament-companies": "^4.0",
16 16
         "andrewdwallo/filament-selectify": "^2.0",
17
-        "andrewdwallo/transmatic": "^1.0",
18
-        "barryvdh/laravel-dompdf": "^2.0",
17
+        "andrewdwallo/transmatic": "^1.1",
18
+        "awcodes/filament-table-repeater": "^3.0",
19
+        "barryvdh/laravel-dompdf": "^2.1",
19 20
         "bezhansalleh/filament-panel-switch": "^1.0",
20
-        "filament/filament": "^3.0",
21
+        "filament/filament": "^3.2.29",
21 22
         "filament/spatie-laravel-tags-plugin": "^3.0-stable",
22 23
         "guava/filament-clusters": "^1.1",
23
-        "guzzlehttp/guzzle": "^7.2",
24
-        "laravel/framework": "^10.10",
25
-        "laravel/sanctum": "^3.2",
26
-        "laravel/tinker": "^2.8",
24
+        "guzzlehttp/guzzle": "^7.8",
25
+        "laravel/framework": "^11.0",
26
+        "laravel/sanctum": "^4.0",
27
+        "laravel/tinker": "^2.9",
27 28
         "squirephp/model": "^3.4",
28 29
         "squirephp/repository": "^3.4",
29 30
         "symfony/intl": "^6.3"
30 31
     },
31 32
     "require-dev": {
32
-        "fakerphp/faker": "^1.9.1",
33
-        "laravel/pint": "^1.0",
34
-        "laravel/sail": "^1.18",
35
-        "mockery/mockery": "^1.4.4",
36
-        "nunomaduro/collision": "^7.0",
37
-        "phpunit/phpunit": "^10.1",
38
-        "spatie/laravel-ignition": "^2.0"
33
+        "fakerphp/faker": "^1.23",
34
+        "laravel/pint": "^1.13",
35
+        "laravel/sail": "^1.26",
36
+        "mockery/mockery": "^1.6",
37
+        "nunomaduro/collision": "^8.0",
38
+        "phpunit/phpunit": "^10.5",
39
+        "spatie/laravel-ignition": "^2.4"
39 40
     },
40 41
     "autoload": {
41 42
         "psr-4": {
@@ -59,7 +60,8 @@
59 60
             "@php artisan filament:upgrade"
60 61
         ],
61 62
         "post-update-cmd": [
62
-            "@php artisan vendor:publish --tag=laravel-assets --ansi --force"
63
+            "@php artisan vendor:publish --tag=laravel-assets --ansi --force",
64
+            "npm run build || echo \"Skipping npm run build (npm not available)\""
63 65
         ],
64 66
         "post-root-package-install": [
65 67
             "@php -r \"file_exists('.env') || copy('.env.example', '.env');\""

+ 671
- 523
composer.lock
Файловите разлики са ограничени, защото са твърде много
Целия файл


+ 23
- 93
config/app.php Целия файл

@@ -1,8 +1,5 @@
1 1
 <?php
2 2
 
3
-use Illuminate\Support\Facades\Facade;
4
-use Illuminate\Support\ServiceProvider;
5
-
6 3
 return [
7 4
 
8 5
     /*
@@ -10,9 +7,9 @@ return [
10 7
     | Application Name
11 8
     |--------------------------------------------------------------------------
12 9
     |
13
-    | This value is the name of your application. This value is used when the
10
+    | This value is the name of your application, which will be used when the
14 11
     | framework needs to place the application's name in a notification or
15
-    | any other location as required by the application or its packages.
12
+    | other UI elements where an application name needs to be displayed.
16 13
     |
17 14
     */
18 15
 
@@ -51,26 +48,24 @@ return [
51 48
     |
52 49
     | This URL is used by the console to properly generate URLs when using
53 50
     | the Artisan command line tool. You should set this to the root of
54
-    | your application so that it is used when running Artisan tasks.
51
+    | the application so that it's available within Artisan commands.
55 52
     |
56 53
     */
57 54
 
58 55
     'url' => env('APP_URL', 'http://localhost'),
59 56
 
60
-    'asset_url' => env('ASSET_URL'),
61
-
62 57
     /*
63 58
     |--------------------------------------------------------------------------
64 59
     | Application Timezone
65 60
     |--------------------------------------------------------------------------
66 61
     |
67 62
     | Here you may specify the default timezone for your application, which
68
-    | will be used by the PHP date and date-time functions. We have gone
69
-    | ahead and set this to a sensible default for you out of the box.
63
+    | will be used by the PHP date and date-time functions. The timezone
64
+    | is set to "UTC" by default as it is suitable for most use cases.
70 65
     |
71 66
     */
72 67
 
73
-    'timezone' => 'UTC',
68
+    'timezone' => env('APP_TIMEZONE', 'UTC'),
74 69
 
75 70
     /*
76 71
     |--------------------------------------------------------------------------
@@ -78,53 +73,37 @@ return [
78 73
     |--------------------------------------------------------------------------
79 74
     |
80 75
     | The application locale determines the default locale that will be used
81
-    | by the translation service provider. You are free to set this value
82
-    | to any of the locales which will be supported by the application.
83
-    |
84
-    */
85
-
86
-    'locale' => 'en',
87
-
88
-    /*
89
-    |--------------------------------------------------------------------------
90
-    | Application Fallback Locale
91
-    |--------------------------------------------------------------------------
92
-    |
93
-    | The fallback locale determines the locale to use when the current one
94
-    | is not available. You may change the value to correspond to any of
95
-    | the language folders that are provided through your application.
76
+    | by Laravel's translation / localization methods. This option can be
77
+    | set to any locale for which you plan to have translation strings.
96 78
     |
97 79
     */
98 80
 
99
-    'fallback_locale' => 'en',
81
+    'locale' => env('APP_LOCALE', 'en'),
100 82
 
101
-    /*
102
-    |--------------------------------------------------------------------------
103
-    | Faker Locale
104
-    |--------------------------------------------------------------------------
105
-    |
106
-    | This locale will be used by the Faker PHP library when generating fake
107
-    | data for your database seeds. For example, this will be used to get
108
-    | localized telephone numbers, street address information and more.
109
-    |
110
-    */
83
+    'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'),
111 84
 
112
-    'faker_locale' => 'en_US',
85
+    'faker_locale' => env('APP_FAKER_LOCALE', 'en_US'),
113 86
 
114 87
     /*
115 88
     |--------------------------------------------------------------------------
116 89
     | Encryption Key
117 90
     |--------------------------------------------------------------------------
118 91
     |
119
-    | This key is used by the Illuminate encrypter service and should be set
120
-    | to a random, 32 character string, otherwise these encrypted strings
121
-    | will not be safe. Please do this before deploying an application!
92
+    | This key is utilized by Laravel's encryption services and should be set
93
+    | to a random, 32 character string to ensure that all encrypted values
94
+    | are secure. You should do this prior to deploying the application.
122 95
     |
123 96
     */
124 97
 
98
+    'cipher' => 'AES-256-CBC',
99
+
125 100
     'key' => env('APP_KEY'),
126 101
 
127
-    'cipher' => 'AES-256-CBC',
102
+    'previous_keys' => [
103
+        ...array_filter(
104
+            explode(',', env('APP_PREVIOUS_KEYS', ''))
105
+        ),
106
+    ],
128 107
 
129 108
     /*
130 109
     |--------------------------------------------------------------------------
@@ -140,57 +119,8 @@ return [
140 119
     */
141 120
 
142 121
     'maintenance' => [
143
-        'driver' => 'file',
144
-        // 'store'  => 'redis',
122
+        'driver' => env('APP_MAINTENANCE_DRIVER', 'file'),
123
+        'store' => env('APP_MAINTENANCE_STORE', 'database'),
145 124
     ],
146 125
 
147
-    /*
148
-    |--------------------------------------------------------------------------
149
-    | Autoloaded Service Providers
150
-    |--------------------------------------------------------------------------
151
-    |
152
-    | The service providers listed here will be automatically loaded on the
153
-    | request to your application. Feel free to add your own services to
154
-    | this array to grant expanded functionality to your applications.
155
-    |
156
-    */
157
-
158
-    'providers' => ServiceProvider::defaultProviders()->merge([
159
-        /*
160
-         * Package Service Providers...
161
-         */
162
-
163
-        /*
164
-         * Application Service Providers...
165
-         */
166
-        App\Providers\AppServiceProvider::class,
167
-        App\Providers\AuthServiceProvider::class,
168
-        // App\Providers\BroadcastServiceProvider::class,
169
-        App\Providers\EventServiceProvider::class,
170
-        App\Providers\Faker\FakerServiceProvider::class,
171
-        App\Providers\Filament\AdminPanelProvider::class,
172
-        App\Providers\Filament\UserPanelProvider::class,
173
-        App\Providers\MacroServiceProvider::class,
174
-        App\Providers\RouteServiceProvider::class,
175
-        App\Providers\FilamentCompaniesServiceProvider::class,
176
-        App\Providers\SquireServiceProvider::class,
177
-        App\Providers\TranslationServiceProvider::class,
178
-        App\Providers\CurrencyServiceProvider::class,
179
-    ])->toArray(),
180
-
181
-    /*
182
-    |--------------------------------------------------------------------------
183
-    | Class Aliases
184
-    |--------------------------------------------------------------------------
185
-    |
186
-    | This array of class aliases will be registered when this application
187
-    | is started. However, feel free to register as many as you wish as
188
-    | the aliases are "lazy" loaded so they don't hinder performance.
189
-    |
190
-    */
191
-
192
-    'aliases' => Facade::defaultAliases()->merge([
193
-        // 'Example' => App\Facades\Example::class,
194
-    ])->toArray(),
195
-
196 126
 ];

+ 17
- 17
config/auth.php Целия файл

@@ -7,15 +7,15 @@ return [
7 7
     | Authentication Defaults
8 8
     |--------------------------------------------------------------------------
9 9
     |
10
-    | This option controls the default authentication "guard" and password
11
-    | reset options for your application. You may change these defaults
10
+    | This option defines the default authentication "guard" and password
11
+    | reset "broker" for your application. You may change these values
12 12
     | as required, but they're a perfect start for most applications.
13 13
     |
14 14
     */
15 15
 
16 16
     'defaults' => [
17
-        'guard' => 'web',
18
-        'passwords' => 'users',
17
+        'guard' => env('AUTH_GUARD', 'web'),
18
+        'passwords' => env('AUTH_PASSWORD_BROKER', 'users'),
19 19
     ],
20 20
 
21 21
     /*
@@ -25,11 +25,11 @@ return [
25 25
     |
26 26
     | Next, you may define every authentication guard for your application.
27 27
     | Of course, a great default configuration has been defined for you
28
-    | here which uses session storage and the Eloquent user provider.
28
+    | which utilizes session storage plus the Eloquent user provider.
29 29
     |
30
-    | All authentication drivers have a user provider. This defines how the
30
+    | All authentication guards have a user provider, which defines how the
31 31
     | users are actually retrieved out of your database or other storage
32
-    | mechanisms used by this application to persist your user's data.
32
+    | system used by the application. Typically, Eloquent is utilized.
33 33
     |
34 34
     | Supported: "session"
35 35
     |
@@ -47,12 +47,12 @@ return [
47 47
     | User Providers
48 48
     |--------------------------------------------------------------------------
49 49
     |
50
-    | All authentication drivers have a user provider. This defines how the
50
+    | All authentication guards have a user provider, which defines how the
51 51
     | users are actually retrieved out of your database or other storage
52
-    | mechanisms used by this application to persist your user's data.
52
+    | system used by the application. Typically, Eloquent is utilized.
53 53
     |
54 54
     | If you have multiple user tables or models you may configure multiple
55
-    | sources which represent each model / table. These sources may then
55
+    | providers to represent the model / table. These providers may then
56 56
     | be assigned to any extra authentication guards you have defined.
57 57
     |
58 58
     | Supported: "database", "eloquent"
@@ -62,7 +62,7 @@ return [
62 62
     'providers' => [
63 63
         'users' => [
64 64
             'driver' => 'eloquent',
65
-            'model' => App\Models\User::class,
65
+            'model' => env('AUTH_MODEL', App\Models\User::class),
66 66
         ],
67 67
 
68 68
         // 'users' => [
@@ -76,9 +76,9 @@ return [
76 76
     | Resetting Passwords
77 77
     |--------------------------------------------------------------------------
78 78
     |
79
-    | You may specify multiple password reset configurations if you have more
80
-    | than one user table or model in the application and you want to have
81
-    | separate password reset settings based on the specific user types.
79
+    | These configuration options specify the behavior of Laravel's password
80
+    | reset functionality, including the table utilized for token storage
81
+    | and the user provider that is invoked to actually retrieve users.
82 82
     |
83 83
     | The expiry time is the number of minutes that each reset token will be
84 84
     | considered valid. This security feature keeps tokens short-lived so
@@ -93,7 +93,7 @@ return [
93 93
     'passwords' => [
94 94
         'users' => [
95 95
             'provider' => 'users',
96
-            'table' => 'password_reset_tokens',
96
+            'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'),
97 97
             'expire' => 60,
98 98
             'throttle' => 60,
99 99
         ],
@@ -105,11 +105,11 @@ return [
105 105
     |--------------------------------------------------------------------------
106 106
     |
107 107
     | Here you may define the amount of seconds before a password confirmation
108
-    | times out and the user is prompted to re-enter their password via the
108
+    | window expires and users are asked to re-enter their password via the
109 109
     | confirmation screen. By default, the timeout lasts for three hours.
110 110
     |
111 111
     */
112 112
 
113
-    'password_timeout' => 10800,
113
+    'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800),
114 114
 
115 115
 ];

+ 0
- 71
config/broadcasting.php Целия файл

@@ -1,71 +0,0 @@
1
-<?php
2
-
3
-return [
4
-
5
-    /*
6
-    |--------------------------------------------------------------------------
7
-    | Default Broadcaster
8
-    |--------------------------------------------------------------------------
9
-    |
10
-    | This option controls the default broadcaster that will be used by the
11
-    | framework when an event needs to be broadcast. You may set this to
12
-    | any of the connections defined in the "connections" array below.
13
-    |
14
-    | Supported: "pusher", "ably", "redis", "log", "null"
15
-    |
16
-    */
17
-
18
-    'default' => env('BROADCAST_DRIVER', 'null'),
19
-
20
-    /*
21
-    |--------------------------------------------------------------------------
22
-    | Broadcast Connections
23
-    |--------------------------------------------------------------------------
24
-    |
25
-    | Here you may define all of the broadcast connections that will be used
26
-    | to broadcast events to other systems or over websockets. Samples of
27
-    | each available type of connection are provided inside this array.
28
-    |
29
-    */
30
-
31
-    'connections' => [
32
-
33
-        'pusher' => [
34
-            'driver' => 'pusher',
35
-            'key' => env('PUSHER_APP_KEY'),
36
-            'secret' => env('PUSHER_APP_SECRET'),
37
-            'app_id' => env('PUSHER_APP_ID'),
38
-            'options' => [
39
-                'cluster' => env('PUSHER_APP_CLUSTER'),
40
-                'host' => env('PUSHER_HOST') ?: 'api-' . env('PUSHER_APP_CLUSTER', 'mt1') . '.pusher.com',
41
-                'port' => env('PUSHER_PORT', 443),
42
-                'scheme' => env('PUSHER_SCHEME', 'https'),
43
-                'encrypted' => true,
44
-                'useTLS' => env('PUSHER_SCHEME', 'https') === 'https',
45
-            ],
46
-            'client_options' => [
47
-                // Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html
48
-            ],
49
-        ],
50
-
51
-        'ably' => [
52
-            'driver' => 'ably',
53
-            'key' => env('ABLY_KEY'),
54
-        ],
55
-
56
-        'redis' => [
57
-            'driver' => 'redis',
58
-            'connection' => 'default',
59
-        ],
60
-
61
-        'log' => [
62
-            'driver' => 'log',
63
-        ],
64
-
65
-        'null' => [
66
-            'driver' => 'null',
67
-        ],
68
-
69
-    ],
70
-
71
-];

+ 13
- 17
config/cache.php Целия файл

@@ -9,13 +9,13 @@ return [
9 9
     | Default Cache Store
10 10
     |--------------------------------------------------------------------------
11 11
     |
12
-    | This option controls the default cache connection that gets used while
13
-    | using this caching library. This connection is used when another is
14
-    | not explicitly specified when executing a given caching function.
12
+    | This option controls the default cache store that will be used by the
13
+    | framework. This connection is utilized if another isn't explicitly
14
+    | specified when running a cache operation inside the application.
15 15
     |
16 16
     */
17 17
 
18
-    'default' => env('CACHE_DRIVER', 'file'),
18
+    'default' => env('CACHE_STORE', 'database'),
19 19
 
20 20
     /*
21 21
     |--------------------------------------------------------------------------
@@ -26,17 +26,13 @@ return [
26 26
     | well as their drivers. You may even define multiple stores for the
27 27
     | same cache driver to group types of items stored in your caches.
28 28
     |
29
-    | Supported drivers: "apc", "array", "database", "file",
30
-    |         "memcached", "redis", "dynamodb", "octane", "null"
29
+    | Supported drivers: "apc", "array", "database", "file", "memcached",
30
+    |                    "redis", "dynamodb", "octane", "null"
31 31
     |
32 32
     */
33 33
 
34 34
     'stores' => [
35 35
 
36
-        'apc' => [
37
-            'driver' => 'apc',
38
-        ],
39
-
40 36
         'array' => [
41 37
             'driver' => 'array',
42 38
             'serialize' => false,
@@ -44,9 +40,9 @@ return [
44 40
 
45 41
         'database' => [
46 42
             'driver' => 'database',
47
-            'table' => 'cache',
48
-            'connection' => null,
49
-            'lock_connection' => null,
43
+            'table' => env('DB_CACHE_TABLE', 'cache'),
44
+            'connection' => env('DB_CACHE_CONNECTION', null),
45
+            'lock_connection' => env('DB_CACHE_LOCK_CONNECTION', null),
50 46
         ],
51 47
 
52 48
         'file' => [
@@ -76,8 +72,8 @@ return [
76 72
 
77 73
         'redis' => [
78 74
             'driver' => 'redis',
79
-            'connection' => 'cache',
80
-            'lock_connection' => 'default',
75
+            'connection' => env('REDIS_CACHE_CONNECTION', 'cache'),
76
+            'lock_connection' => env('REDIS_CACHE_LOCK_CONNECTION', 'default'),
81 77
         ],
82 78
 
83 79
         'dynamodb' => [
@@ -100,8 +96,8 @@ return [
100 96
     | Cache Key Prefix
101 97
     |--------------------------------------------------------------------------
102 98
     |
103
-    | When utilizing the APC, database, memcached, Redis, or DynamoDB cache
104
-    | stores there might be other applications using the same cache. For
99
+    | When utilizing the APC, database, memcached, Redis, and DynamoDB cache
100
+    | stores, there might be other applications using the same cache. For
105 101
     | that reason, you may prefix every cache key to avoid collisions.
106 102
     |
107 103
     */

+ 0
- 34
config/cors.php Целия файл

@@ -1,34 +0,0 @@
1
-<?php
2
-
3
-return [
4
-
5
-    /*
6
-    |--------------------------------------------------------------------------
7
-    | Cross-Origin Resource Sharing (CORS) Configuration
8
-    |--------------------------------------------------------------------------
9
-    |
10
-    | Here you may configure your settings for cross-origin resource sharing
11
-    | or "CORS". This determines what cross-origin operations may execute
12
-    | in web browsers. You are free to adjust these settings as needed.
13
-    |
14
-    | To learn more: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
15
-    |
16
-    */
17
-
18
-    'paths' => ['api/*', 'sanctum/csrf-cookie'],
19
-
20
-    'allowed_methods' => ['*'],
21
-
22
-    'allowed_origins' => ['*'],
23
-
24
-    'allowed_origins_patterns' => [],
25
-
26
-    'allowed_headers' => ['*'],
27
-
28
-    'exposed_headers' => [],
29
-
30
-    'max_age' => 0,
31
-
32
-    'supports_credentials' => false,
33
-
34
-];

+ 46
- 27
config/database.php Целия файл

@@ -10,8 +10,9 @@ return [
10 10
     |--------------------------------------------------------------------------
11 11
     |
12 12
     | Here you may specify which of the database connections below you wish
13
-    | to use as your default connection for all database work. Of course
14
-    | you may use many connections at once using the Database library.
13
+    | to use as your default connection for database operations. This is
14
+    | the connection which will be utilized unless another connection
15
+    | is explicitly specified when you execute a query / statement.
15 16
     |
16 17
     */
17 18
 
@@ -22,14 +23,9 @@ return [
22 23
     | Database Connections
23 24
     |--------------------------------------------------------------------------
24 25
     |
25
-    | Here are each of the database connections setup for your application.
26
-    | Of course, examples of configuring each database platform that is
27
-    | supported by Laravel is shown below to make development simple.
28
-    |
29
-    |
30
-    | All database work in Laravel is done through the PHP PDO facilities
31
-    | so make sure you have the driver for your particular database of
32
-    | choice installed on your machine before you begin development.
26
+    | Below are all of the database connections defined for your application.
27
+    | An example configuration is provided for each database system which
28
+    | is supported by Laravel. You're free to add / remove connections.
33 29
     |
34 30
     */
35 31
 
@@ -37,7 +33,7 @@ return [
37 33
 
38 34
         'sqlite' => [
39 35
             'driver' => 'sqlite',
40
-            'url' => env('DATABASE_URL'),
36
+            'url' => env('DB_URL'),
41 37
             'database' => env('DB_DATABASE', database_path('database.sqlite')),
42 38
             'prefix' => '',
43 39
             'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
@@ -45,15 +41,35 @@ return [
45 41
 
46 42
         'mysql' => [
47 43
             'driver' => 'mysql',
48
-            'url' => env('DATABASE_URL'),
44
+            'url' => env('DB_URL'),
45
+            'host' => env('DB_HOST', '127.0.0.1'),
46
+            'port' => env('DB_PORT', '3306'),
47
+            'database' => env('DB_DATABASE', 'laravel'),
48
+            'username' => env('DB_USERNAME', 'root'),
49
+            'password' => env('DB_PASSWORD', ''),
50
+            'unix_socket' => env('DB_SOCKET', ''),
51
+            'charset' => env('DB_CHARSET', 'utf8mb4'),
52
+            'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
53
+            'prefix' => '',
54
+            'prefix_indexes' => true,
55
+            'strict' => true,
56
+            'engine' => null,
57
+            'options' => extension_loaded('pdo_mysql') ? array_filter([
58
+                PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
59
+            ]) : [],
60
+        ],
61
+
62
+        'mariadb' => [
63
+            'driver' => 'mariadb',
64
+            'url' => env('DB_URL'),
49 65
             'host' => env('DB_HOST', '127.0.0.1'),
50 66
             'port' => env('DB_PORT', '3306'),
51
-            'database' => env('DB_DATABASE', 'forge'),
52
-            'username' => env('DB_USERNAME', 'forge'),
67
+            'database' => env('DB_DATABASE', 'laravel'),
68
+            'username' => env('DB_USERNAME', 'root'),
53 69
             'password' => env('DB_PASSWORD', ''),
54 70
             'unix_socket' => env('DB_SOCKET', ''),
55
-            'charset' => 'utf8mb4',
56
-            'collation' => 'utf8mb4_unicode_ci',
71
+            'charset' => env('DB_CHARSET', 'utf8mb4'),
72
+            'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
57 73
             'prefix' => '',
58 74
             'prefix_indexes' => true,
59 75
             'strict' => true,
@@ -65,13 +81,13 @@ return [
65 81
 
66 82
         'pgsql' => [
67 83
             'driver' => 'pgsql',
68
-            'url' => env('DATABASE_URL'),
84
+            'url' => env('DB_URL'),
69 85
             'host' => env('DB_HOST', '127.0.0.1'),
70 86
             'port' => env('DB_PORT', '5432'),
71
-            'database' => env('DB_DATABASE', 'forge'),
72
-            'username' => env('DB_USERNAME', 'forge'),
87
+            'database' => env('DB_DATABASE', 'laravel'),
88
+            'username' => env('DB_USERNAME', 'root'),
73 89
             'password' => env('DB_PASSWORD', ''),
74
-            'charset' => 'utf8',
90
+            'charset' => env('DB_CHARSET', 'utf8'),
75 91
             'prefix' => '',
76 92
             'prefix_indexes' => true,
77 93
             'search_path' => 'public',
@@ -80,13 +96,13 @@ return [
80 96
 
81 97
         'sqlsrv' => [
82 98
             'driver' => 'sqlsrv',
83
-            'url' => env('DATABASE_URL'),
99
+            'url' => env('DB_URL'),
84 100
             'host' => env('DB_HOST', 'localhost'),
85 101
             'port' => env('DB_PORT', '1433'),
86
-            'database' => env('DB_DATABASE', 'forge'),
87
-            'username' => env('DB_USERNAME', 'forge'),
102
+            'database' => env('DB_DATABASE', 'laravel'),
103
+            'username' => env('DB_USERNAME', 'root'),
88 104
             'password' => env('DB_PASSWORD', ''),
89
-            'charset' => 'utf8',
105
+            'charset' => env('DB_CHARSET', 'utf8'),
90 106
             'prefix' => '',
91 107
             'prefix_indexes' => true,
92 108
             // 'encrypt' => env('DB_ENCRYPT', 'yes'),
@@ -102,11 +118,14 @@ return [
102 118
     |
103 119
     | This table keeps track of all the migrations that have already run for
104 120
     | your application. Using this information, we can determine which of
105
-    | the migrations on disk haven't actually been run in the database.
121
+    | the migrations on disk haven't actually been run on the database.
106 122
     |
107 123
     */
108 124
 
109
-    'migrations' => 'migrations',
125
+    'migrations' => [
126
+        'table' => 'migrations',
127
+        'update_date_on_publish' => true,
128
+    ],
110 129
 
111 130
     /*
112 131
     |--------------------------------------------------------------------------
@@ -115,7 +134,7 @@ return [
115 134
     |
116 135
     | Redis is an open source, fast, and advanced key-value store that also
117 136
     | provides a richer body of commands than a typical key-value system
118
-    | such as APC or Memcached. Laravel makes it easy to dig right in.
137
+    | such as Memcached. You may define your connection settings here.
119 138
     |
120 139
     */
121 140
 

+ 0
- 1
config/dompdf.php Целия файл

@@ -96,7 +96,6 @@ return [
96 96
             'https://' => ['rules' => []],
97 97
         ],
98 98
 
99
-
100 99
         'log_output_file' => null,
101 100
 
102 101
         /**

+ 4
- 4
config/filesystems.php Целия файл

@@ -9,7 +9,7 @@ return [
9 9
     |
10 10
     | Here you may specify the default filesystem disk that should be used
11 11
     | by the framework. The "local" disk, as well as a variety of cloud
12
-    | based disks are available to your application. Just store away!
12
+    | based disks are available to your application for file storage.
13 13
     |
14 14
     */
15 15
 
@@ -20,9 +20,9 @@ return [
20 20
     | Filesystem Disks
21 21
     |--------------------------------------------------------------------------
22 22
     |
23
-    | Here you may configure as many filesystem "disks" as you wish, and you
24
-    | may even configure multiple disks of the same driver. Defaults have
25
-    | been set up for each driver as an example of the required values.
23
+    | Below you may configure as many filesystem disks as necessary, and you
24
+    | may even configure multiple disks for the same driver. Examples for
25
+    | most supported storage drivers are configured here for reference.
26 26
     |
27 27
     | Supported Drivers: "local", "ftp", "sftp", "s3"
28 28
     |

+ 0
- 52
config/hashing.php Целия файл

@@ -1,52 +0,0 @@
1
-<?php
2
-
3
-return [
4
-
5
-    /*
6
-    |--------------------------------------------------------------------------
7
-    | Default Hash Driver
8
-    |--------------------------------------------------------------------------
9
-    |
10
-    | This option controls the default hash driver that will be used to hash
11
-    | passwords for your application. By default, the bcrypt algorithm is
12
-    | used; however, you remain free to modify this option if you wish.
13
-    |
14
-    | Supported: "bcrypt", "argon", "argon2id"
15
-    |
16
-    */
17
-
18
-    'driver' => 'bcrypt',
19
-
20
-    /*
21
-    |--------------------------------------------------------------------------
22
-    | Bcrypt Options
23
-    |--------------------------------------------------------------------------
24
-    |
25
-    | Here you may specify the configuration options that should be used when
26
-    | passwords are hashed using the Bcrypt algorithm. This will allow you
27
-    | to control the amount of time it takes to hash the given password.
28
-    |
29
-    */
30
-
31
-    'bcrypt' => [
32
-        'rounds' => env('BCRYPT_ROUNDS', 10),
33
-    ],
34
-
35
-    /*
36
-    |--------------------------------------------------------------------------
37
-    | Argon Options
38
-    |--------------------------------------------------------------------------
39
-    |
40
-    | Here you may specify the configuration options that should be used when
41
-    | passwords are hashed using the Argon algorithm. These will allow you
42
-    | to control the amount of time it takes to hash the given password.
43
-    |
44
-    */
45
-
46
-    'argon' => [
47
-        'memory' => 65536,
48
-        'threads' => 1,
49
-        'time' => 4,
50
-    ],
51
-
52
-];

+ 15
- 14
config/logging.php Целия файл

@@ -12,9 +12,9 @@ return [
12 12
     | Default Log Channel
13 13
     |--------------------------------------------------------------------------
14 14
     |
15
-    | This option defines the default log channel that gets used when writing
16
-    | messages to the logs. The name specified in this option should match
17
-    | one of the channels defined in the "channels" configuration array.
15
+    | This option defines the default log channel that is utilized to write
16
+    | messages to your logs. The value provided here should match one of
17
+    | the channels present in the list of "channels" configured below.
18 18
     |
19 19
     */
20 20
 
@@ -33,7 +33,7 @@ return [
33 33
 
34 34
     'deprecations' => [
35 35
         'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'),
36
-        'trace' => false,
36
+        'trace' => env('LOG_DEPRECATIONS_TRACE', false),
37 37
     ],
38 38
 
39 39
     /*
@@ -41,20 +41,20 @@ return [
41 41
     | Log Channels
42 42
     |--------------------------------------------------------------------------
43 43
     |
44
-    | Here you may configure the log channels for your application. Out of
45
-    | the box, Laravel uses the Monolog PHP logging library. This gives
46
-    | you a variety of powerful log handlers / formatters to utilize.
44
+    | Here you may configure the log channels for your application. Laravel
45
+    | utilizes the Monolog PHP logging library, which includes a variety
46
+    | of powerful log handlers and formatters that you're free to use.
47 47
     |
48 48
     | Available Drivers: "single", "daily", "slack", "syslog",
49
-    |                    "errorlog", "monolog",
50
-    |                    "custom", "stack"
49
+    |                    "errorlog", "monolog", "custom", "stack"
51 50
     |
52 51
     */
53 52
 
54 53
     'channels' => [
54
+
55 55
         'stack' => [
56 56
             'driver' => 'stack',
57
-            'channels' => ['single'],
57
+            'channels' => explode(',', env('LOG_STACK', 'single')),
58 58
             'ignore_exceptions' => false,
59 59
         ],
60 60
 
@@ -69,15 +69,15 @@ return [
69 69
             'driver' => 'daily',
70 70
             'path' => storage_path('logs/laravel.log'),
71 71
             'level' => env('LOG_LEVEL', 'debug'),
72
-            'days' => 14,
72
+            'days' => env('LOG_DAILY_DAYS', 14),
73 73
             'replace_placeholders' => true,
74 74
         ],
75 75
 
76 76
         'slack' => [
77 77
             'driver' => 'slack',
78 78
             'url' => env('LOG_SLACK_WEBHOOK_URL'),
79
-            'username' => 'Laravel Log',
80
-            'emoji' => ':boom:',
79
+            'username' => env('LOG_SLACK_USERNAME', 'Laravel Log'),
80
+            'emoji' => env('LOG_SLACK_EMOJI', ':boom:'),
81 81
             'level' => env('LOG_LEVEL', 'critical'),
82 82
             'replace_placeholders' => true,
83 83
         ],
@@ -108,7 +108,7 @@ return [
108 108
         'syslog' => [
109 109
             'driver' => 'syslog',
110 110
             'level' => env('LOG_LEVEL', 'debug'),
111
-            'facility' => LOG_USER,
111
+            'facility' => env('LOG_SYSLOG_FACILITY', LOG_USER),
112 112
             'replace_placeholders' => true,
113 113
         ],
114 114
 
@@ -126,6 +126,7 @@ return [
126 126
         'emergency' => [
127 127
             'path' => storage_path('logs/laravel.log'),
128 128
         ],
129
+
129 130
     ],
130 131
 
131 132
 ];

+ 17
- 39
config/mail.php Целия файл

@@ -7,13 +7,14 @@ return [
7 7
     | Default Mailer
8 8
     |--------------------------------------------------------------------------
9 9
     |
10
-    | This option controls the default mailer that is used to send any email
11
-    | messages sent by your application. Alternative mailers may be setup
12
-    | and used as needed; however, this mailer will be used by default.
10
+    | This option controls the default mailer that is used to send all email
11
+    | messages unless another mailer is explicitly specified when sending
12
+    | the message. All additional mailers can be configured within the
13
+    | "mailers" array. Examples of each type of mailer are provided.
13 14
     |
14 15
     */
15 16
 
16
-    'default' => env('MAIL_MAILER', 'smtp'),
17
+    'default' => env('MAIL_MAILER', 'log'),
17 18
 
18 19
     /*
19 20
     |--------------------------------------------------------------------------
@@ -24,21 +25,22 @@ return [
24 25
     | their respective settings. Several examples have been configured for
25 26
     | you and you are free to add your own as your application requires.
26 27
     |
27
-    | Laravel supports a variety of mail "transport" drivers to be used while
28
-    | sending an e-mail. You will specify which one you are using for your
29
-    | mailers below. You are free to add additional mailers as required.
28
+    | Laravel supports a variety of mail "transport" drivers that can be used
29
+    | when delivering an email. You may specify which one you're using for
30
+    | your mailers below. You may also add additional mailers if needed.
30 31
     |
31 32
     | Supported: "smtp", "sendmail", "mailgun", "ses", "ses-v2",
32
-    |            "postmark", "log", "array", "failover"
33
+    |            "postmark", "log", "array", "failover", "roundrobin"
33 34
     |
34 35
     */
35 36
 
36 37
     'mailers' => [
38
+
37 39
         'smtp' => [
38 40
             'transport' => 'smtp',
39 41
             'url' => env('MAIL_URL'),
40
-            'host' => env('MAIL_HOST', 'smtp.mailgun.org'),
41
-            'port' => env('MAIL_PORT', 587),
42
+            'host' => env('MAIL_HOST', '127.0.0.1'),
43
+            'port' => env('MAIL_PORT', 2525),
42 44
             'encryption' => env('MAIL_ENCRYPTION', 'tls'),
43 45
             'username' => env('MAIL_USERNAME'),
44 46
             'password' => env('MAIL_PASSWORD'),
@@ -50,15 +52,9 @@ return [
50 52
             'transport' => 'ses',
51 53
         ],
52 54
 
53
-        'mailgun' => [
54
-            'transport' => 'mailgun',
55
-            // 'client' => [
56
-            //     'timeout' => 5,
57
-            // ],
58
-        ],
59
-
60 55
         'postmark' => [
61 56
             'transport' => 'postmark',
57
+            // 'message_stream_id' => env('POSTMARK_MESSAGE_STREAM_ID'),
62 58
             // 'client' => [
63 59
             //     'timeout' => 5,
64 60
             // ],
@@ -85,6 +81,7 @@ return [
85 81
                 'log',
86 82
             ],
87 83
         ],
84
+
88 85
     ],
89 86
 
90 87
     /*
@@ -92,9 +89,9 @@ return [
92 89
     | Global "From" Address
93 90
     |--------------------------------------------------------------------------
94 91
     |
95
-    | You may wish for all e-mails sent by your application to be sent from
96
-    | the same address. Here, you may specify a name and address that is
97
-    | used globally for all e-mails that are sent by your application.
92
+    | You may wish for all emails sent by your application to be sent from
93
+    | the same address. Here you may specify a name and address that is
94
+    | used globally for all emails that are sent by your application.
98 95
     |
99 96
     */
100 97
 
@@ -103,23 +100,4 @@ return [
103 100
         'name' => env('MAIL_FROM_NAME', 'Example'),
104 101
     ],
105 102
 
106
-    /*
107
-    |--------------------------------------------------------------------------
108
-    | Markdown Mail Settings
109
-    |--------------------------------------------------------------------------
110
-    |
111
-    | If you are using Markdown based email rendering, you may configure your
112
-    | theme and component paths here, allowing you to customize the design
113
-    | of the emails. Or, you may simply stick with the Laravel defaults!
114
-    |
115
-    */
116
-
117
-    'markdown' => [
118
-        'theme' => 'default',
119
-
120
-        'paths' => [
121
-            resource_path('views/vendor/mail'),
122
-        ],
123
-    ],
124
-
125 103
 ];

+ 22
- 19
config/queue.php Целия файл

@@ -7,22 +7,22 @@ return [
7 7
     | Default Queue Connection Name
8 8
     |--------------------------------------------------------------------------
9 9
     |
10
-    | Laravel's queue API supports an assortment of back-ends via a single
11
-    | API, giving you convenient access to each back-end using the same
12
-    | syntax for every one. Here you may define a default connection.
10
+    | Laravel's queue supports a variety of backends via a single, unified
11
+    | API, giving you convenient access to each backend using identical
12
+    | syntax for each. The default queue connection is defined below.
13 13
     |
14 14
     */
15 15
 
16
-    'default' => env('QUEUE_CONNECTION', 'sync'),
16
+    'default' => env('QUEUE_CONNECTION', 'database'),
17 17
 
18 18
     /*
19 19
     |--------------------------------------------------------------------------
20 20
     | Queue Connections
21 21
     |--------------------------------------------------------------------------
22 22
     |
23
-    | Here you may configure the connection information for each server that
24
-    | is used by your application. A default configuration has been added
25
-    | for each back-end shipped with Laravel. You are free to add more.
23
+    | Here you may configure the connection options for every queue backend
24
+    | used by your application. An example configuration is provided for
25
+    | each backend supported by Laravel. You're also free to add more.
26 26
     |
27 27
     | Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null"
28 28
     |
@@ -36,17 +36,18 @@ return [
36 36
 
37 37
         'database' => [
38 38
             'driver' => 'database',
39
-            'table' => 'jobs',
40
-            'queue' => 'translations',
41
-            'retry_after' => 90,
39
+            'connection' => env('DB_QUEUE_CONNECTION', null),
40
+            'table' => env('DB_QUEUE_TABLE', 'jobs'),
41
+            'queue' => env('DB_QUEUE', 'translations'),
42
+            'retry_after' => env('DB_QUEUE_RETRY_AFTER', 90),
42 43
             'after_commit' => false,
43 44
         ],
44 45
 
45 46
         'beanstalkd' => [
46 47
             'driver' => 'beanstalkd',
47
-            'host' => 'localhost',
48
-            'queue' => 'default',
49
-            'retry_after' => 90,
48
+            'host' => env('BEANSTALKD_QUEUE_HOST', 'localhost'),
49
+            'queue' => env('BEANSTALKD_QUEUE', 'default'),
50
+            'retry_after' => env('BEANSTALKD_QUEUE_RETRY_AFTER', 90),
50 51
             'block_for' => 0,
51 52
             'after_commit' => false,
52 53
         ],
@@ -64,9 +65,9 @@ return [
64 65
 
65 66
         'redis' => [
66 67
             'driver' => 'redis',
67
-            'connection' => 'default',
68
+            'connection' => env('REDIS_QUEUE_CONNECTION', 'default'),
68 69
             'queue' => env('REDIS_QUEUE', 'default'),
69
-            'retry_after' => 90,
70
+            'retry_after' => env('REDIS_QUEUE_RETRY_AFTER', 90),
70 71
             'block_for' => null,
71 72
             'after_commit' => false,
72 73
         ],
@@ -85,7 +86,7 @@ return [
85 86
     */
86 87
 
87 88
     'batching' => [
88
-        'database' => env('DB_CONNECTION', 'mysql'),
89
+        'database' => env('DB_CONNECTION', 'sqlite'),
89 90
         'table' => 'job_batches',
90 91
     ],
91 92
 
@@ -95,14 +96,16 @@ return [
95 96
     |--------------------------------------------------------------------------
96 97
     |
97 98
     | These options configure the behavior of failed queue job logging so you
98
-    | can control which database and table are used to store the jobs that
99
-    | have failed. You may change them to any database / table you wish.
99
+    | can control how and where failed jobs are stored. Laravel ships with
100
+    | support for storing failed jobs in a simple file or in a database.
101
+    |
102
+    | Supported drivers: "database-uuids", "dynamodb", "file", "null"
100 103
     |
101 104
     */
102 105
 
103 106
     'failed' => [
104 107
         'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'),
105
-        'database' => env('DB_CONNECTION', 'mysql'),
108
+        'database' => env('DB_CONNECTION', 'sqlite'),
106 109
         'table' => 'failed_jobs',
107 110
     ],
108 111
 

+ 7
- 6
config/sanctum.php Целия файл

@@ -53,15 +53,15 @@ return [
53 53
     | Token Prefix
54 54
     |--------------------------------------------------------------------------
55 55
     |
56
-    | Sanctum can prefix new tokens in order to take advantage of various
57
-    | security scanning initiaives maintained by open source platforms
58
-    | that alert developers if they commit tokens into repositories.
56
+    | Sanctum can prefix new tokens in order to take advantage of numerous
57
+    | security scanning initiatives maintained by open source platforms
58
+    | that notify developers if they commit tokens into repositories.
59 59
     |
60 60
     | See: https://docs.github.com/en/code-security/secret-scanning/about-secret-scanning
61 61
     |
62 62
     */
63 63
 
64
-    'token_prefix' => env('SANCTUM_TOKEN_PREFIX', 'laravel_sanctum_'),
64
+    'token_prefix' => env('SANCTUM_TOKEN_PREFIX', ''),
65 65
 
66 66
     /*
67 67
     |--------------------------------------------------------------------------
@@ -75,8 +75,9 @@ return [
75 75
     */
76 76
 
77 77
     'middleware' => [
78
-        'verify_csrf_token' => App\Http\Middleware\VerifyCsrfToken::class,
79
-        'encrypt_cookies' => App\Http\Middleware\EncryptCookies::class,
78
+        'authenticate_session' => Laravel\Sanctum\Http\Middleware\AuthenticateSession::class,
79
+        'encrypt_cookies' => Illuminate\Cookie\Middleware\EncryptCookies::class,
80
+        'validate_csrf_token' => Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class,
80 81
     ],
81 82
 
82 83
 ];

+ 7
- 7
config/services.php Целия файл

@@ -14,13 +14,6 @@ return [
14 14
     |
15 15
     */
16 16
 
17
-    'mailgun' => [
18
-        'domain' => env('MAILGUN_DOMAIN'),
19
-        'secret' => env('MAILGUN_SECRET'),
20
-        'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'),
21
-        'scheme' => 'https',
22
-    ],
23
-
24 17
     'postmark' => [
25 18
         'token' => env('POSTMARK_TOKEN'),
26 19
     ],
@@ -31,6 +24,13 @@ return [
31 24
         'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
32 25
     ],
33 26
 
27
+    'slack' => [
28
+        'notifications' => [
29
+            'bot_user_oauth_token' => env('SLACK_BOT_USER_OAUTH_TOKEN'),
30
+            'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'),
31
+        ],
32
+    ],
33
+
34 34
     'github' => [
35 35
         'client_id' => env('GITHUB_CLIENT_ID'),
36 36
         'client_secret' => env('GITHUB_CLIENT_SECRET'),

+ 47
- 30
config/session.php Целия файл

@@ -9,9 +9,9 @@ return [
9 9
     | Default Session Driver
10 10
     |--------------------------------------------------------------------------
11 11
     |
12
-    | This option controls the default session "driver" that will be used on
13
-    | requests. By default, we will use the lightweight native driver but
14
-    | you may specify any of the other wonderful drivers provided here.
12
+    | This option determines the default session driver that is utilized for
13
+    | incoming requests. Laravel supports a variety of storage options to
14
+    | persist session data. Database storage is a great default choice.
15 15
     |
16 16
     | Supported: "file", "cookie", "database", "apc",
17 17
     |            "memcached", "redis", "dynamodb", "array"
@@ -27,13 +27,14 @@ return [
27 27
     |
28 28
     | Here you may specify the number of minutes that you wish the session
29 29
     | to be allowed to remain idle before it expires. If you want them
30
-    | to immediately expire on the browser closing, set that option.
30
+    | to expire immediately when the browser is closed then you may
31
+    | indicate that via the expire_on_close configuration option.
31 32
     |
32 33
     */
33 34
 
34 35
     'lifetime' => env('SESSION_LIFETIME', 120),
35 36
 
36
-    'expire_on_close' => false,
37
+    'expire_on_close' => env('SESSION_EXPIRE_ON_CLOSE', false),
37 38
 
38 39
     /*
39 40
     |--------------------------------------------------------------------------
@@ -41,21 +42,21 @@ return [
41 42
     |--------------------------------------------------------------------------
42 43
     |
43 44
     | This option allows you to easily specify that all of your session data
44
-    | should be encrypted before it is stored. All encryption will be run
45
-    | automatically by Laravel and you can use the Session like normal.
45
+    | should be encrypted before it's stored. All encryption is performed
46
+    | automatically by Laravel and you may use the session like normal.
46 47
     |
47 48
     */
48 49
 
49
-    'encrypt' => false,
50
+    'encrypt' => env('SESSION_ENCRYPT', false),
50 51
 
51 52
     /*
52 53
     |--------------------------------------------------------------------------
53 54
     | Session File Location
54 55
     |--------------------------------------------------------------------------
55 56
     |
56
-    | When using the native session driver, we need a location where session
57
-    | files may be stored. A default has been set for you but a different
58
-    | location may be specified. This is only needed for file sessions.
57
+    | When utilizing the "file" session driver, the session files are placed
58
+    | on disk. The default storage location is defined here; however, you
59
+    | are free to provide another location where they should be stored.
59 60
     |
60 61
     */
61 62
 
@@ -79,22 +80,22 @@ return [
79 80
     | Session Database Table
80 81
     |--------------------------------------------------------------------------
81 82
     |
82
-    | When using the "database" session driver, you may specify the table we
83
-    | should use to manage the sessions. Of course, a sensible default is
84
-    | provided for you; however, you are free to change this as needed.
83
+    | When using the "database" session driver, you may specify the table to
84
+    | be used to store sessions. Of course, a sensible default is defined
85
+    | for you; however, you're welcome to change this to another table.
85 86
     |
86 87
     */
87 88
 
88
-    'table' => 'sessions',
89
+    'table' => env('SESSION_TABLE', 'sessions'),
89 90
 
90 91
     /*
91 92
     |--------------------------------------------------------------------------
92 93
     | Session Cache Store
93 94
     |--------------------------------------------------------------------------
94 95
     |
95
-    | While using one of the framework's cache driven session backends you may
96
-    | list a cache store that should be used for these sessions. This value
97
-    | must match with one of the application's configured cache "stores".
96
+    | When using one of the framework's cache driven session backends, you may
97
+    | define the cache store which should be used to store the session data
98
+    | between requests. This must match one of your defined cache stores.
98 99
     |
99 100
     | Affects: "apc", "dynamodb", "memcached", "redis"
100 101
     |
@@ -120,9 +121,10 @@ return [
120 121
     | Session Cookie Name
121 122
     |--------------------------------------------------------------------------
122 123
     |
123
-    | Here you may change the name of the cookie used to identify a session
124
-    | instance by ID. The name specified here will get used every time a
125
-    | new session cookie is created by the framework for every driver.
124
+    | Here you may change the name of the session cookie that is created by
125
+    | the framework. Typically, you should not need to change this value
126
+    | since doing so does not grant a meaningful security improvement.
127
+    |
126 128
     |
127 129
     */
128 130
 
@@ -138,20 +140,20 @@ return [
138 140
     |
139 141
     | The session cookie path determines the path for which the cookie will
140 142
     | be regarded as available. Typically, this will be the root path of
141
-    | your application but you are free to change this when necessary.
143
+    | your application, but you're free to change this when necessary.
142 144
     |
143 145
     */
144 146
 
145
-    'path' => '/',
147
+    'path' => env('SESSION_PATH', '/'),
146 148
 
147 149
     /*
148 150
     |--------------------------------------------------------------------------
149 151
     | Session Cookie Domain
150 152
     |--------------------------------------------------------------------------
151 153
     |
152
-    | Here you may change the domain of the cookie used to identify a session
153
-    | in your application. This will determine which domains the cookie is
154
-    | available to in your application. A sensible default has been set.
154
+    | This value determines the domain and subdomains the session cookie is
155
+    | available to. By default, the cookie will be available to the root
156
+    | domain and all subdomains. Typically, this shouldn't be changed.
155 157
     |
156 158
     */
157 159
 
@@ -177,11 +179,11 @@ return [
177 179
     |
178 180
     | Setting this value to true will prevent JavaScript from accessing the
179 181
     | value of the cookie and the cookie will only be accessible through
180
-    | the HTTP protocol. You are free to modify this option if needed.
182
+    | the HTTP protocol. It's unlikely you should disable this option.
181 183
     |
182 184
     */
183 185
 
184
-    'http_only' => true,
186
+    'http_only' => env('SESSION_HTTP_ONLY', true),
185 187
 
186 188
     /*
187 189
     |--------------------------------------------------------------------------
@@ -190,12 +192,27 @@ return [
190 192
     |
191 193
     | This option determines how your cookies behave when cross-site requests
192 194
     | take place, and can be used to mitigate CSRF attacks. By default, we
193
-    | will set this value to "lax" since this is a secure default value.
195
+    | will set this value to "lax" to permit secure cross-site requests.
196
+    |
197
+    | See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value
194 198
     |
195 199
     | Supported: "lax", "strict", "none", null
196 200
     |
197 201
     */
198 202
 
199
-    'same_site' => 'lax',
203
+    'same_site' => env('SESSION_SAME_SITE', 'lax'),
204
+
205
+    /*
206
+    |--------------------------------------------------------------------------
207
+    | Partitioned Cookies
208
+    |--------------------------------------------------------------------------
209
+    |
210
+    | Setting this value to true will tie the cookie to the top-level site for
211
+    | a cross-site context. Partitioned cookies are accepted by the browser
212
+    | when flagged "secure" and the Same-Site attribute is set to "none".
213
+    |
214
+    */
215
+
216
+    'partitioned' => env('SESSION_PARTITIONED_COOKIE', false),
200 217
 
201 218
 ];

+ 0
- 36
config/view.php Целия файл

@@ -1,36 +0,0 @@
1
-<?php
2
-
3
-return [
4
-
5
-    /*
6
-    |--------------------------------------------------------------------------
7
-    | View Storage Paths
8
-    |--------------------------------------------------------------------------
9
-    |
10
-    | Most templating systems load templates from disk. Here you may specify
11
-    | an array of paths that should be checked for your views. Of course
12
-    | the usual Laravel view path has already been registered for you.
13
-    |
14
-    */
15
-
16
-    'paths' => [
17
-        resource_path('views'),
18
-    ],
19
-
20
-    /*
21
-    |--------------------------------------------------------------------------
22
-    | Compiled View Path
23
-    |--------------------------------------------------------------------------
24
-    |
25
-    | This option determines where all the compiled Blade templates will be
26
-    | stored for your application. Typically, this is within the storage
27
-    | directory. However, as usual, you are free to change this value.
28
-    |
29
-    */
30
-
31
-    'compiled' => env(
32
-        'VIEW_COMPILED_PATH',
33
-        realpath(storage_path('framework/views'))
34
-    ),
35
-
36
-];

+ 23
- 17
database/factories/UserFactory.php Целия файл

@@ -7,8 +7,9 @@ use App\Models\Setting\CompanyDefault;
7 7
 use App\Models\Setting\CompanyProfile;
8 8
 use App\Models\User;
9 9
 use Illuminate\Database\Eloquent\Factories\Factory;
10
+use Illuminate\Support\Facades\Hash;
10 11
 use Illuminate\Support\Str;
11
-use Wallo\FilamentCompanies\Features;
12
+use Wallo\FilamentCompanies\FilamentCompanies;
12 13
 
13 14
 class UserFactory extends Factory
14 15
 {
@@ -19,6 +20,11 @@ class UserFactory extends Factory
19 20
      */
20 21
     protected $model = User::class;
21 22
 
23
+    /**
24
+     * The current password being used by the factory.
25
+     */
26
+    protected static ?string $password = null;
27
+
22 28
     /**
23 29
      * Define the model's default state.
24 30
      *
@@ -27,10 +33,10 @@ class UserFactory extends Factory
27 33
     public function definition(): array
28 34
     {
29 35
         return [
30
-            'name' => $this->faker->name(),
31
-            'email' => $this->faker->unique()->safeEmail(),
36
+            'name' => fake()->name(),
37
+            'email' => fake()->unique()->safeEmail(),
32 38
             'email_verified_at' => now(),
33
-            'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
39
+            'password' => static::$password ??= Hash::make('password'),
34 40
             'remember_token' => Str::random(10),
35 41
             'profile_photo_path' => null,
36 42
             'current_company_id' => null,
@@ -42,35 +48,35 @@ class UserFactory extends Factory
42 48
      */
43 49
     public function unverified(): static
44 50
     {
45
-        return $this->state(function (array $attributes) {
46
-            return [
47
-                'email_verified_at' => null,
48
-            ];
49
-        });
51
+        return $this->state(static fn (array $attributes) => [
52
+            'email_verified_at' => null,
53
+        ]);
50 54
     }
51 55
 
52 56
     /**
53 57
      * Indicate that the user should have a personal company.
54 58
      */
55
-    public function withPersonalCompany(): static
59
+    public function withPersonalCompany(?callable $callback = null): static
56 60
     {
57
-        if (! Features::hasCompanyFeatures()) {
61
+        if (! FilamentCompanies::hasCompanyFeatures()) {
58 62
             return $this->state([]);
59 63
         }
60 64
 
61 65
         $countryCode = $this->faker->countryCode;
62 66
 
63
-        return $this->afterCreating(function (User $user) use ($countryCode) {
67
+        return $this->afterCreating(function (User $user) use ($countryCode, $callback) {
64 68
             Company::factory()
69
+                ->state(static fn (array $attributes, User $user) => [
70
+                    'name' => $user->name . '\'s Company',
71
+                    'user_id' => $user->id,
72
+                    'personal_company' => true,
73
+                ])
65 74
                 ->has(CompanyProfile::factory()->withCountry($countryCode), 'profile')
66 75
                 ->afterCreating(function (Company $company) use ($user, $countryCode) {
67 76
                     CompanyDefault::factory()->withDefault($user, $company, $countryCode)->create();
68 77
                 })
69
-                ->create([
70
-                    'name' => $user->name . '\'s Company',
71
-                    'user_id' => $user->id,
72
-                    'personal_company' => true,
73
-                ]);
78
+                ->when(is_callable($callback), $callback)
79
+                ->create();
74 80
         });
75 81
     }
76 82
 }

database/migrations/2014_10_12_000000_create_users_table.php → database/migrations/0001_01_01_000000_create_users_table.php Целия файл

@@ -3,7 +3,7 @@
3 3
 use Illuminate\Database\Migrations\Migration;
4 4
 use Illuminate\Database\Schema\Blueprint;
5 5
 use Illuminate\Support\Facades\Schema;
6
-use Wallo\FilamentCompanies\Socialite;
6
+use Wallo\FilamentCompanies\FilamentCompanies;
7 7
 
8 8
 return new class extends Migration
9 9
 {
@@ -18,14 +18,29 @@ return new class extends Migration
18 18
             $table->string('email')->unique();
19 19
             $table->timestamp('email_verified_at')->nullable();
20 20
             $table->string('password')->nullable(
21
-                Socialite::hasSocialiteFeatures()
21
+                FilamentCompanies::hasSocialiteFeatures()
22 22
             );
23 23
             $table->rememberToken();
24 24
             $table->foreignId('current_company_id')->nullable();
25 25
             $table->foreignId('current_connected_account_id')->nullable();
26
-            $table->string('profile_photo_path')->nullable();
26
+            $table->string('profile_photo_path', 2048)->nullable();
27 27
             $table->timestamps();
28 28
         });
29
+
30
+        Schema::create('password_reset_tokens', function (Blueprint $table) {
31
+            $table->string('email')->primary();
32
+            $table->string('token');
33
+            $table->timestamp('created_at')->nullable();
34
+        });
35
+
36
+        Schema::create('sessions', function (Blueprint $table) {
37
+            $table->string('id')->primary();
38
+            $table->foreignId('user_id')->nullable()->index();
39
+            $table->string('ip_address', 45)->nullable();
40
+            $table->text('user_agent')->nullable();
41
+            $table->longText('payload');
42
+            $table->integer('last_activity')->index();
43
+        });
29 44
     }
30 45
 
31 46
     /**
@@ -34,5 +49,7 @@ return new class extends Migration
34 49
     public function down(): void
35 50
     {
36 51
         Schema::dropIfExists('users');
52
+        Schema::dropIfExists('password_reset_tokens');
53
+        Schema::dropIfExists('sessions');
37 54
     }
38 55
 };

+ 35
- 0
database/migrations/0001_01_01_000001_create_cache_table.php Целия файл

@@ -0,0 +1,35 @@
1
+<?php
2
+
3
+use Illuminate\Database\Migrations\Migration;
4
+use Illuminate\Database\Schema\Blueprint;
5
+use Illuminate\Support\Facades\Schema;
6
+
7
+return new class extends Migration
8
+{
9
+    /**
10
+     * Run the migrations.
11
+     */
12
+    public function up(): void
13
+    {
14
+        Schema::create('cache', function (Blueprint $table) {
15
+            $table->string('key')->primary();
16
+            $table->mediumText('value');
17
+            $table->integer('expiration');
18
+        });
19
+
20
+        Schema::create('cache_locks', function (Blueprint $table) {
21
+            $table->string('key')->primary();
22
+            $table->string('owner');
23
+            $table->integer('expiration');
24
+        });
25
+    }
26
+
27
+    /**
28
+     * Reverse the migrations.
29
+     */
30
+    public function down(): void
31
+    {
32
+        Schema::dropIfExists('cache');
33
+        Schema::dropIfExists('cache_locks');
34
+    }
35
+};

database/migrations/2023_10_25_050002_create_job_batches_table.php → database/migrations/0001_01_01_000002_create_jobs_table.php Целия файл

@@ -11,6 +11,16 @@ return new class extends Migration
11 11
      */
12 12
     public function up(): void
13 13
     {
14
+        Schema::create('jobs', function (Blueprint $table) {
15
+            $table->id();
16
+            $table->string('queue')->index();
17
+            $table->longText('payload');
18
+            $table->unsignedTinyInteger('attempts');
19
+            $table->unsignedInteger('reserved_at')->nullable();
20
+            $table->unsignedInteger('available_at');
21
+            $table->unsignedInteger('created_at');
22
+        });
23
+
14 24
         Schema::create('job_batches', function (Blueprint $table) {
15 25
             $table->string('id')->primary();
16 26
             $table->string('name');
@@ -23,6 +33,16 @@ return new class extends Migration
23 33
             $table->integer('created_at');
24 34
             $table->integer('finished_at')->nullable();
25 35
         });
36
+
37
+        Schema::create('failed_jobs', function (Blueprint $table) {
38
+            $table->id();
39
+            $table->string('uuid')->unique();
40
+            $table->text('connection');
41
+            $table->text('queue');
42
+            $table->longText('payload');
43
+            $table->longText('exception');
44
+            $table->timestamp('failed_at')->useCurrent();
45
+        });
26 46
     }
27 47
 
28 48
     /**
@@ -30,6 +50,8 @@ return new class extends Migration
30 50
      */
31 51
     public function down(): void
32 52
     {
53
+        Schema::dropIfExists('jobs');
33 54
         Schema::dropIfExists('job_batches');
55
+        Schema::dropIfExists('failed_jobs');
34 56
     }
35 57
 };

+ 0
- 28
database/migrations/2014_10_12_100000_create_password_reset_tokens_table.php Целия файл

@@ -1,28 +0,0 @@
1
-<?php
2
-
3
-use Illuminate\Database\Migrations\Migration;
4
-use Illuminate\Database\Schema\Blueprint;
5
-use Illuminate\Support\Facades\Schema;
6
-
7
-return new class extends Migration
8
-{
9
-    /**
10
-     * Run the migrations.
11
-     */
12
-    public function up(): void
13
-    {
14
-        Schema::create('password_reset_tokens', function (Blueprint $table) {
15
-            $table->string('email')->primary();
16
-            $table->string('token');
17
-            $table->timestamp('created_at')->nullable();
18
-        });
19
-    }
20
-
21
-    /**
22
-     * Reverse the migrations.
23
-     */
24
-    public function down(): void
25
-    {
26
-        Schema::dropIfExists('password_reset_tokens');
27
-    }
28
-};

+ 0
- 32
database/migrations/2019_08_19_000000_create_failed_jobs_table.php Целия файл

@@ -1,32 +0,0 @@
1
-<?php
2
-
3
-use Illuminate\Database\Migrations\Migration;
4
-use Illuminate\Database\Schema\Blueprint;
5
-use Illuminate\Support\Facades\Schema;
6
-
7
-return new class extends Migration
8
-{
9
-    /**
10
-     * Run the migrations.
11
-     */
12
-    public function up(): void
13
-    {
14
-        Schema::create('failed_jobs', function (Blueprint $table) {
15
-            $table->id();
16
-            $table->string('uuid')->unique();
17
-            $table->text('connection');
18
-            $table->text('queue');
19
-            $table->longText('payload');
20
-            $table->longText('exception');
21
-            $table->timestamp('failed_at')->useCurrent();
22
-        });
23
-    }
24
-
25
-    /**
26
-     * Reverse the migrations.
27
-     */
28
-    public function down(): void
29
-    {
30
-        Schema::dropIfExists('failed_jobs');
31
-    }
32
-};

+ 0
- 31
database/migrations/2023_09_03_023609_create_sessions_table.php Целия файл

@@ -1,31 +0,0 @@
1
-<?php
2
-
3
-use Illuminate\Database\Migrations\Migration;
4
-use Illuminate\Database\Schema\Blueprint;
5
-use Illuminate\Support\Facades\Schema;
6
-
7
-return new class extends Migration
8
-{
9
-    /**
10
-     * Run the migrations.
11
-     */
12
-    public function up(): void
13
-    {
14
-        Schema::create('sessions', function (Blueprint $table) {
15
-            $table->string('id')->primary();
16
-            $table->foreignId('user_id')->nullable()->index();
17
-            $table->string('ip_address', 45)->nullable();
18
-            $table->text('user_agent')->nullable();
19
-            $table->longText('payload');
20
-            $table->integer('last_activity')->index();
21
-        });
22
-    }
23
-
24
-    /**
25
-     * Reverse the migrations.
26
-     */
27
-    public function down(): void
28
-    {
29
-        Schema::dropIfExists('sessions');
30
-    }
31
-};

+ 1
- 1
database/migrations/2023_09_03_100000_create_accounting_tables.php Целия файл

@@ -63,7 +63,7 @@ return new class extends Migration
63 63
             $table->foreignId('company_id')->constrained()->cascadeOnDelete();
64 64
             $table->foreignId('institution_id')->nullable()->constrained('institutions')->nullOnDelete();
65 65
             $table->string('type')->default(BankAccountType::DEFAULT);
66
-            $table->string('number', 20);
66
+            $table->string('number', 20)->nullable();
67 67
             $table->boolean('enabled')->default(true);
68 68
             $table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
69 69
             $table->foreignId('updated_by')->nullable()->constrained('users')->nullOnDelete();

+ 0
- 32
database/migrations/2023_10_14_201948_create_jobs_table.php Целия файл

@@ -1,32 +0,0 @@
1
-<?php
2
-
3
-use Illuminate\Database\Migrations\Migration;
4
-use Illuminate\Database\Schema\Blueprint;
5
-use Illuminate\Support\Facades\Schema;
6
-
7
-return new class extends Migration
8
-{
9
-    /**
10
-     * Run the migrations.
11
-     */
12
-    public function up(): void
13
-    {
14
-        Schema::create('jobs', function (Blueprint $table) {
15
-            $table->bigIncrements('id');
16
-            $table->string('queue')->index();
17
-            $table->longText('payload');
18
-            $table->unsignedTinyInteger('attempts');
19
-            $table->unsignedInteger('reserved_at')->nullable();
20
-            $table->unsignedInteger('available_at');
21
-            $table->unsignedInteger('created_at');
22
-        });
23
-    }
24
-
25
-    /**
26
-     * Reverse the migrations.
27
-     */
28
-    public function down(): void
29
-    {
30
-        Schema::dropIfExists('jobs');
31
-    }
32
-};

+ 1
- 1
database/migrations/2024_01_01_234943_create_transactions_table.php Целия файл

@@ -17,7 +17,7 @@ return new class extends Migration
17 17
             $table->foreignId('account_id')->nullable()->constrained()->nullOnDelete();
18 18
             $table->foreignId('bank_account_id')->nullable()->constrained()->nullOnDelete();
19 19
             $table->foreignId('contact_id')->nullable()->constrained()->nullOnDelete();
20
-            $table->string('type'); // deposit, withdrawal, journal entry
20
+            $table->string('type'); // deposit, withdrawal, journal
21 21
             $table->string('payment_channel')->nullable(); // online, in store, other
22 22
             $table->string('description')->nullable();
23 23
             $table->text('notes')->nullable();

+ 478
- 154
package-lock.json Целия файл

@@ -5,14 +5,15 @@
5 5
     "packages": {
6 6
         "": {
7 7
             "devDependencies": {
8
-                "@tailwindcss/forms": "^0.5.6",
8
+                "@tailwindcss/forms": "^0.5.7",
9 9
                 "@tailwindcss/typography": "^0.5.10",
10
-                "autoprefixer": "^10.4.16",
11
-                "axios": "^1.1.2",
12
-                "laravel-vite-plugin": "^0.8.0",
13
-                "postcss": "^8.4.31",
14
-                "tailwindcss": "^3.3.3",
15
-                "vite": "^4.0.0"
10
+                "autoprefixer": "^10.4.18",
11
+                "axios": "^1.6.4",
12
+                "laravel-vite-plugin": "^1.0",
13
+                "postcss": "^8.4.35",
14
+                "postcss-nesting": "^12.1.0",
15
+                "tailwindcss": "^3.4.1",
16
+                "vite": "^5.0"
16 17
             }
17 18
         },
18 19
         "node_modules/@alloc/quick-lru": {
@@ -27,10 +28,26 @@
27 28
                 "url": "https://github.com/sponsors/sindresorhus"
28 29
             }
29 30
         },
31
+        "node_modules/@esbuild/aix-ppc64": {
32
+            "version": "0.20.2",
33
+            "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz",
34
+            "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==",
35
+            "cpu": [
36
+                "ppc64"
37
+            ],
38
+            "dev": true,
39
+            "optional": true,
40
+            "os": [
41
+                "aix"
42
+            ],
43
+            "engines": {
44
+                "node": ">=12"
45
+            }
46
+        },
30 47
         "node_modules/@esbuild/android-arm": {
31
-            "version": "0.18.20",
32
-            "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz",
33
-            "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==",
48
+            "version": "0.20.2",
49
+            "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz",
50
+            "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==",
34 51
             "cpu": [
35 52
                 "arm"
36 53
             ],
@@ -44,9 +61,9 @@
44 61
             }
45 62
         },
46 63
         "node_modules/@esbuild/android-arm64": {
47
-            "version": "0.18.20",
48
-            "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz",
49
-            "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==",
64
+            "version": "0.20.2",
65
+            "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz",
66
+            "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==",
50 67
             "cpu": [
51 68
                 "arm64"
52 69
             ],
@@ -60,9 +77,9 @@
60 77
             }
61 78
         },
62 79
         "node_modules/@esbuild/android-x64": {
63
-            "version": "0.18.20",
64
-            "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz",
65
-            "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==",
80
+            "version": "0.20.2",
81
+            "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz",
82
+            "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==",
66 83
             "cpu": [
67 84
                 "x64"
68 85
             ],
@@ -76,9 +93,9 @@
76 93
             }
77 94
         },
78 95
         "node_modules/@esbuild/darwin-arm64": {
79
-            "version": "0.18.20",
80
-            "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz",
81
-            "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==",
96
+            "version": "0.20.2",
97
+            "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz",
98
+            "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==",
82 99
             "cpu": [
83 100
                 "arm64"
84 101
             ],
@@ -92,9 +109,9 @@
92 109
             }
93 110
         },
94 111
         "node_modules/@esbuild/darwin-x64": {
95
-            "version": "0.18.20",
96
-            "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz",
97
-            "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==",
112
+            "version": "0.20.2",
113
+            "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz",
114
+            "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==",
98 115
             "cpu": [
99 116
                 "x64"
100 117
             ],
@@ -108,9 +125,9 @@
108 125
             }
109 126
         },
110 127
         "node_modules/@esbuild/freebsd-arm64": {
111
-            "version": "0.18.20",
112
-            "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz",
113
-            "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==",
128
+            "version": "0.20.2",
129
+            "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz",
130
+            "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==",
114 131
             "cpu": [
115 132
                 "arm64"
116 133
             ],
@@ -124,9 +141,9 @@
124 141
             }
125 142
         },
126 143
         "node_modules/@esbuild/freebsd-x64": {
127
-            "version": "0.18.20",
128
-            "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz",
129
-            "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==",
144
+            "version": "0.20.2",
145
+            "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz",
146
+            "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==",
130 147
             "cpu": [
131 148
                 "x64"
132 149
             ],
@@ -140,9 +157,9 @@
140 157
             }
141 158
         },
142 159
         "node_modules/@esbuild/linux-arm": {
143
-            "version": "0.18.20",
144
-            "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz",
145
-            "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==",
160
+            "version": "0.20.2",
161
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz",
162
+            "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==",
146 163
             "cpu": [
147 164
                 "arm"
148 165
             ],
@@ -156,9 +173,9 @@
156 173
             }
157 174
         },
158 175
         "node_modules/@esbuild/linux-arm64": {
159
-            "version": "0.18.20",
160
-            "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz",
161
-            "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==",
176
+            "version": "0.20.2",
177
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz",
178
+            "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==",
162 179
             "cpu": [
163 180
                 "arm64"
164 181
             ],
@@ -172,9 +189,9 @@
172 189
             }
173 190
         },
174 191
         "node_modules/@esbuild/linux-ia32": {
175
-            "version": "0.18.20",
176
-            "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz",
177
-            "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==",
192
+            "version": "0.20.2",
193
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz",
194
+            "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==",
178 195
             "cpu": [
179 196
                 "ia32"
180 197
             ],
@@ -188,9 +205,9 @@
188 205
             }
189 206
         },
190 207
         "node_modules/@esbuild/linux-loong64": {
191
-            "version": "0.18.20",
192
-            "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz",
193
-            "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==",
208
+            "version": "0.20.2",
209
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz",
210
+            "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==",
194 211
             "cpu": [
195 212
                 "loong64"
196 213
             ],
@@ -204,9 +221,9 @@
204 221
             }
205 222
         },
206 223
         "node_modules/@esbuild/linux-mips64el": {
207
-            "version": "0.18.20",
208
-            "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz",
209
-            "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==",
224
+            "version": "0.20.2",
225
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz",
226
+            "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==",
210 227
             "cpu": [
211 228
                 "mips64el"
212 229
             ],
@@ -220,9 +237,9 @@
220 237
             }
221 238
         },
222 239
         "node_modules/@esbuild/linux-ppc64": {
223
-            "version": "0.18.20",
224
-            "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz",
225
-            "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==",
240
+            "version": "0.20.2",
241
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz",
242
+            "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==",
226 243
             "cpu": [
227 244
                 "ppc64"
228 245
             ],
@@ -236,9 +253,9 @@
236 253
             }
237 254
         },
238 255
         "node_modules/@esbuild/linux-riscv64": {
239
-            "version": "0.18.20",
240
-            "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz",
241
-            "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==",
256
+            "version": "0.20.2",
257
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz",
258
+            "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==",
242 259
             "cpu": [
243 260
                 "riscv64"
244 261
             ],
@@ -252,9 +269,9 @@
252 269
             }
253 270
         },
254 271
         "node_modules/@esbuild/linux-s390x": {
255
-            "version": "0.18.20",
256
-            "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz",
257
-            "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==",
272
+            "version": "0.20.2",
273
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz",
274
+            "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==",
258 275
             "cpu": [
259 276
                 "s390x"
260 277
             ],
@@ -268,9 +285,9 @@
268 285
             }
269 286
         },
270 287
         "node_modules/@esbuild/linux-x64": {
271
-            "version": "0.18.20",
272
-            "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz",
273
-            "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==",
288
+            "version": "0.20.2",
289
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz",
290
+            "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==",
274 291
             "cpu": [
275 292
                 "x64"
276 293
             ],
@@ -284,9 +301,9 @@
284 301
             }
285 302
         },
286 303
         "node_modules/@esbuild/netbsd-x64": {
287
-            "version": "0.18.20",
288
-            "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz",
289
-            "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==",
304
+            "version": "0.20.2",
305
+            "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz",
306
+            "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==",
290 307
             "cpu": [
291 308
                 "x64"
292 309
             ],
@@ -300,9 +317,9 @@
300 317
             }
301 318
         },
302 319
         "node_modules/@esbuild/openbsd-x64": {
303
-            "version": "0.18.20",
304
-            "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz",
305
-            "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==",
320
+            "version": "0.20.2",
321
+            "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz",
322
+            "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==",
306 323
             "cpu": [
307 324
                 "x64"
308 325
             ],
@@ -316,9 +333,9 @@
316 333
             }
317 334
         },
318 335
         "node_modules/@esbuild/sunos-x64": {
319
-            "version": "0.18.20",
320
-            "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz",
321
-            "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==",
336
+            "version": "0.20.2",
337
+            "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz",
338
+            "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==",
322 339
             "cpu": [
323 340
                 "x64"
324 341
             ],
@@ -332,9 +349,9 @@
332 349
             }
333 350
         },
334 351
         "node_modules/@esbuild/win32-arm64": {
335
-            "version": "0.18.20",
336
-            "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz",
337
-            "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==",
352
+            "version": "0.20.2",
353
+            "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz",
354
+            "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==",
338 355
             "cpu": [
339 356
                 "arm64"
340 357
             ],
@@ -348,9 +365,9 @@
348 365
             }
349 366
         },
350 367
         "node_modules/@esbuild/win32-ia32": {
351
-            "version": "0.18.20",
352
-            "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz",
353
-            "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==",
368
+            "version": "0.20.2",
369
+            "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz",
370
+            "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==",
354 371
             "cpu": [
355 372
                 "ia32"
356 373
             ],
@@ -364,9 +381,9 @@
364 381
             }
365 382
         },
366 383
         "node_modules/@esbuild/win32-x64": {
367
-            "version": "0.18.20",
368
-            "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz",
369
-            "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==",
384
+            "version": "0.20.2",
385
+            "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz",
386
+            "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==",
370 387
             "cpu": [
371 388
                 "x64"
372 389
             ],
@@ -489,6 +506,201 @@
489 506
                 "node": ">=14"
490 507
             }
491 508
         },
509
+        "node_modules/@rollup/rollup-android-arm-eabi": {
510
+            "version": "4.13.2",
511
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.13.2.tgz",
512
+            "integrity": "sha512-3XFIDKWMFZrMnao1mJhnOT1h2g0169Os848NhhmGweEcfJ4rCi+3yMCOLG4zA61rbJdkcrM/DjVZm9Hg5p5w7g==",
513
+            "cpu": [
514
+                "arm"
515
+            ],
516
+            "dev": true,
517
+            "optional": true,
518
+            "os": [
519
+                "android"
520
+            ]
521
+        },
522
+        "node_modules/@rollup/rollup-android-arm64": {
523
+            "version": "4.13.2",
524
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.13.2.tgz",
525
+            "integrity": "sha512-GdxxXbAuM7Y/YQM9/TwwP+L0omeE/lJAR1J+olu36c3LqqZEBdsIWeQ91KBe6nxwOnb06Xh7JS2U5ooWU5/LgQ==",
526
+            "cpu": [
527
+                "arm64"
528
+            ],
529
+            "dev": true,
530
+            "optional": true,
531
+            "os": [
532
+                "android"
533
+            ]
534
+        },
535
+        "node_modules/@rollup/rollup-darwin-arm64": {
536
+            "version": "4.13.2",
537
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.13.2.tgz",
538
+            "integrity": "sha512-mCMlpzlBgOTdaFs83I4XRr8wNPveJiJX1RLfv4hggyIVhfB5mJfN4P8Z6yKh+oE4Luz+qq1P3kVdWrCKcMYrrA==",
539
+            "cpu": [
540
+                "arm64"
541
+            ],
542
+            "dev": true,
543
+            "optional": true,
544
+            "os": [
545
+                "darwin"
546
+            ]
547
+        },
548
+        "node_modules/@rollup/rollup-darwin-x64": {
549
+            "version": "4.13.2",
550
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.13.2.tgz",
551
+            "integrity": "sha512-yUoEvnH0FBef/NbB1u6d3HNGyruAKnN74LrPAfDQL3O32e3k3OSfLrPgSJmgb3PJrBZWfPyt6m4ZhAFa2nZp2A==",
552
+            "cpu": [
553
+                "x64"
554
+            ],
555
+            "dev": true,
556
+            "optional": true,
557
+            "os": [
558
+                "darwin"
559
+            ]
560
+        },
561
+        "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
562
+            "version": "4.13.2",
563
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.13.2.tgz",
564
+            "integrity": "sha512-GYbLs5ErswU/Xs7aGXqzc3RrdEjKdmoCrgzhJWyFL0r5fL3qd1NPcDKDowDnmcoSiGJeU68/Vy+OMUluRxPiLQ==",
565
+            "cpu": [
566
+                "arm"
567
+            ],
568
+            "dev": true,
569
+            "optional": true,
570
+            "os": [
571
+                "linux"
572
+            ]
573
+        },
574
+        "node_modules/@rollup/rollup-linux-arm64-gnu": {
575
+            "version": "4.13.2",
576
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.13.2.tgz",
577
+            "integrity": "sha512-L1+D8/wqGnKQIlh4Zre9i4R4b4noxzH5DDciyahX4oOz62CphY7WDWqJoQ66zNR4oScLNOqQJfNSIAe/6TPUmQ==",
578
+            "cpu": [
579
+                "arm64"
580
+            ],
581
+            "dev": true,
582
+            "optional": true,
583
+            "os": [
584
+                "linux"
585
+            ]
586
+        },
587
+        "node_modules/@rollup/rollup-linux-arm64-musl": {
588
+            "version": "4.13.2",
589
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.13.2.tgz",
590
+            "integrity": "sha512-tK5eoKFkXdz6vjfkSTCupUzCo40xueTOiOO6PeEIadlNBkadH1wNOH8ILCPIl8by/Gmb5AGAeQOFeLev7iZDOA==",
591
+            "cpu": [
592
+                "arm64"
593
+            ],
594
+            "dev": true,
595
+            "optional": true,
596
+            "os": [
597
+                "linux"
598
+            ]
599
+        },
600
+        "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
601
+            "version": "4.13.2",
602
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.13.2.tgz",
603
+            "integrity": "sha512-zvXvAUGGEYi6tYhcDmb9wlOckVbuD+7z3mzInCSTACJ4DQrdSLPNUeDIcAQW39M3q6PDquqLWu7pnO39uSMRzQ==",
604
+            "cpu": [
605
+                "ppc64le"
606
+            ],
607
+            "dev": true,
608
+            "optional": true,
609
+            "os": [
610
+                "linux"
611
+            ]
612
+        },
613
+        "node_modules/@rollup/rollup-linux-riscv64-gnu": {
614
+            "version": "4.13.2",
615
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.13.2.tgz",
616
+            "integrity": "sha512-C3GSKvMtdudHCN5HdmAMSRYR2kkhgdOfye4w0xzyii7lebVr4riCgmM6lRiSCnJn2w1Xz7ZZzHKuLrjx5620kw==",
617
+            "cpu": [
618
+                "riscv64"
619
+            ],
620
+            "dev": true,
621
+            "optional": true,
622
+            "os": [
623
+                "linux"
624
+            ]
625
+        },
626
+        "node_modules/@rollup/rollup-linux-s390x-gnu": {
627
+            "version": "4.13.2",
628
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.13.2.tgz",
629
+            "integrity": "sha512-l4U0KDFwzD36j7HdfJ5/TveEQ1fUTjFFQP5qIt9gBqBgu1G8/kCaq5Ok05kd5TG9F8Lltf3MoYsUMw3rNlJ0Yg==",
630
+            "cpu": [
631
+                "s390x"
632
+            ],
633
+            "dev": true,
634
+            "optional": true,
635
+            "os": [
636
+                "linux"
637
+            ]
638
+        },
639
+        "node_modules/@rollup/rollup-linux-x64-gnu": {
640
+            "version": "4.13.2",
641
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.13.2.tgz",
642
+            "integrity": "sha512-xXMLUAMzrtsvh3cZ448vbXqlUa7ZL8z0MwHp63K2IIID2+DeP5iWIT6g1SN7hg1VxPzqx0xZdiDM9l4n9LRU1A==",
643
+            "cpu": [
644
+                "x64"
645
+            ],
646
+            "dev": true,
647
+            "optional": true,
648
+            "os": [
649
+                "linux"
650
+            ]
651
+        },
652
+        "node_modules/@rollup/rollup-linux-x64-musl": {
653
+            "version": "4.13.2",
654
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.13.2.tgz",
655
+            "integrity": "sha512-M/JYAWickafUijWPai4ehrjzVPKRCyDb1SLuO+ZyPfoXgeCEAlgPkNXewFZx0zcnoIe3ay4UjXIMdXQXOZXWqA==",
656
+            "cpu": [
657
+                "x64"
658
+            ],
659
+            "dev": true,
660
+            "optional": true,
661
+            "os": [
662
+                "linux"
663
+            ]
664
+        },
665
+        "node_modules/@rollup/rollup-win32-arm64-msvc": {
666
+            "version": "4.13.2",
667
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.13.2.tgz",
668
+            "integrity": "sha512-2YWwoVg9KRkIKaXSh0mz3NmfurpmYoBBTAXA9qt7VXk0Xy12PoOP40EFuau+ajgALbbhi4uTj3tSG3tVseCjuA==",
669
+            "cpu": [
670
+                "arm64"
671
+            ],
672
+            "dev": true,
673
+            "optional": true,
674
+            "os": [
675
+                "win32"
676
+            ]
677
+        },
678
+        "node_modules/@rollup/rollup-win32-ia32-msvc": {
679
+            "version": "4.13.2",
680
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.13.2.tgz",
681
+            "integrity": "sha512-2FSsE9aQ6OWD20E498NYKEQLneShWes0NGMPQwxWOdws35qQXH+FplabOSP5zEe1pVjurSDOGEVCE2agFwSEsw==",
682
+            "cpu": [
683
+                "ia32"
684
+            ],
685
+            "dev": true,
686
+            "optional": true,
687
+            "os": [
688
+                "win32"
689
+            ]
690
+        },
691
+        "node_modules/@rollup/rollup-win32-x64-msvc": {
692
+            "version": "4.13.2",
693
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.13.2.tgz",
694
+            "integrity": "sha512-7h7J2nokcdPePdKykd8wtc8QqqkqxIrUz7MHj6aNr8waBRU//NLDVnNjQnqQO6fqtjrtCdftpbTuOKAyrAQETQ==",
695
+            "cpu": [
696
+                "x64"
697
+            ],
698
+            "dev": true,
699
+            "optional": true,
700
+            "os": [
701
+                "win32"
702
+            ]
703
+        },
492 704
         "node_modules/@tailwindcss/forms": {
493 705
             "version": "0.5.7",
494 706
             "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.7.tgz",
@@ -502,9 +714,9 @@
502 714
             }
503 715
         },
504 716
         "node_modules/@tailwindcss/typography": {
505
-            "version": "0.5.10",
506
-            "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.10.tgz",
507
-            "integrity": "sha512-Pe8BuPJQJd3FfRnm6H0ulKIGoMEQS+Vq01R6M5aCrFB/ccR/shT+0kXLjouGC1gFLm9hopTFN+DMP0pfwRWzPw==",
717
+            "version": "0.5.12",
718
+            "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.12.tgz",
719
+            "integrity": "sha512-CNwpBpconcP7ppxmuq3qvaCxiRWnbhANpY/ruH4L5qs2GCiVDJXde/pjj2HWPV1+Q4G9+V/etrwUYopdcjAlyg==",
508 720
             "dev": true,
509 721
             "dependencies": {
510 722
                 "lodash.castarray": "^4.4.0",
@@ -516,6 +728,12 @@
516 728
                 "tailwindcss": ">=3.0.0 || insiders"
517 729
             }
518 730
         },
731
+        "node_modules/@types/estree": {
732
+            "version": "1.0.5",
733
+            "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
734
+            "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
735
+            "dev": true
736
+        },
519 737
         "node_modules/ansi-regex": {
520 738
             "version": "6.0.1",
521 739
             "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
@@ -572,9 +790,9 @@
572 790
             "dev": true
573 791
         },
574 792
         "node_modules/autoprefixer": {
575
-            "version": "10.4.18",
576
-            "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.18.tgz",
577
-            "integrity": "sha512-1DKbDfsr6KUElM6wg+0zRNkB/Q7WcKYAaK+pzXn+Xqmszm/5Xa9coeNdtP88Vi+dPzZnMjhge8GIV49ZQkDa+g==",
793
+            "version": "10.4.19",
794
+            "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz",
795
+            "integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==",
578 796
             "dev": true,
579 797
             "funding": [
580 798
                 {
@@ -592,7 +810,7 @@
592 810
             ],
593 811
             "dependencies": {
594 812
                 "browserslist": "^4.23.0",
595
-                "caniuse-lite": "^1.0.30001591",
813
+                "caniuse-lite": "^1.0.30001599",
596 814
                 "fraction.js": "^4.3.7",
597 815
                 "normalize-range": "^0.1.2",
598 816
                 "picocolors": "^1.0.0",
@@ -700,9 +918,9 @@
700 918
             }
701 919
         },
702 920
         "node_modules/caniuse-lite": {
703
-            "version": "1.0.30001597",
704
-            "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001597.tgz",
705
-            "integrity": "sha512-7LjJvmQU6Sj7bL0j5b5WY/3n7utXUJvAe1lxhsHDbLmwX9mdL86Yjtr+5SRCyf8qME4M7pU2hswj0FpyBVCv9w==",
921
+            "version": "1.0.30001600",
922
+            "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001600.tgz",
923
+            "integrity": "sha512-+2S9/2JFhYmYaDpZvo0lKkfvuKIglrx68MwOBqMGHhQsNkLjB5xtc/TGoEPs+MxjSyN/72qer2g97nzR641mOQ==",
706 924
             "dev": true,
707 925
             "funding": [
708 926
                 {
@@ -848,9 +1066,9 @@
848 1066
             "dev": true
849 1067
         },
850 1068
         "node_modules/electron-to-chromium": {
851
-            "version": "1.4.708",
852
-            "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.708.tgz",
853
-            "integrity": "sha512-iWgEEvREL4GTXXHKohhh33+6Y8XkPI5eHihDmm8zUk5Zo7HICEW+wI/j5kJ2tbuNUCXJ/sNXa03ajW635DiJXA==",
1069
+            "version": "1.4.719",
1070
+            "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.719.tgz",
1071
+            "integrity": "sha512-FbWy2Q2YgdFzkFUW/W5jBjE9dj+804+98E4Pup78JBPnbdb3pv6IneY2JCPKdeKLh3AOKHQeYf+KwLr7mxGh6Q==",
854 1072
             "dev": true
855 1073
         },
856 1074
         "node_modules/emoji-regex": {
@@ -860,9 +1078,9 @@
860 1078
             "dev": true
861 1079
         },
862 1080
         "node_modules/esbuild": {
863
-            "version": "0.18.20",
864
-            "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz",
865
-            "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==",
1081
+            "version": "0.20.2",
1082
+            "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz",
1083
+            "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==",
866 1084
             "dev": true,
867 1085
             "hasInstallScript": true,
868 1086
             "bin": {
@@ -872,28 +1090,29 @@
872 1090
                 "node": ">=12"
873 1091
             },
874 1092
             "optionalDependencies": {
875
-                "@esbuild/android-arm": "0.18.20",
876
-                "@esbuild/android-arm64": "0.18.20",
877
-                "@esbuild/android-x64": "0.18.20",
878
-                "@esbuild/darwin-arm64": "0.18.20",
879
-                "@esbuild/darwin-x64": "0.18.20",
880
-                "@esbuild/freebsd-arm64": "0.18.20",
881
-                "@esbuild/freebsd-x64": "0.18.20",
882
-                "@esbuild/linux-arm": "0.18.20",
883
-                "@esbuild/linux-arm64": "0.18.20",
884
-                "@esbuild/linux-ia32": "0.18.20",
885
-                "@esbuild/linux-loong64": "0.18.20",
886
-                "@esbuild/linux-mips64el": "0.18.20",
887
-                "@esbuild/linux-ppc64": "0.18.20",
888
-                "@esbuild/linux-riscv64": "0.18.20",
889
-                "@esbuild/linux-s390x": "0.18.20",
890
-                "@esbuild/linux-x64": "0.18.20",
891
-                "@esbuild/netbsd-x64": "0.18.20",
892
-                "@esbuild/openbsd-x64": "0.18.20",
893
-                "@esbuild/sunos-x64": "0.18.20",
894
-                "@esbuild/win32-arm64": "0.18.20",
895
-                "@esbuild/win32-ia32": "0.18.20",
896
-                "@esbuild/win32-x64": "0.18.20"
1093
+                "@esbuild/aix-ppc64": "0.20.2",
1094
+                "@esbuild/android-arm": "0.20.2",
1095
+                "@esbuild/android-arm64": "0.20.2",
1096
+                "@esbuild/android-x64": "0.20.2",
1097
+                "@esbuild/darwin-arm64": "0.20.2",
1098
+                "@esbuild/darwin-x64": "0.20.2",
1099
+                "@esbuild/freebsd-arm64": "0.20.2",
1100
+                "@esbuild/freebsd-x64": "0.20.2",
1101
+                "@esbuild/linux-arm": "0.20.2",
1102
+                "@esbuild/linux-arm64": "0.20.2",
1103
+                "@esbuild/linux-ia32": "0.20.2",
1104
+                "@esbuild/linux-loong64": "0.20.2",
1105
+                "@esbuild/linux-mips64el": "0.20.2",
1106
+                "@esbuild/linux-ppc64": "0.20.2",
1107
+                "@esbuild/linux-riscv64": "0.20.2",
1108
+                "@esbuild/linux-s390x": "0.20.2",
1109
+                "@esbuild/linux-x64": "0.20.2",
1110
+                "@esbuild/netbsd-x64": "0.20.2",
1111
+                "@esbuild/openbsd-x64": "0.20.2",
1112
+                "@esbuild/sunos-x64": "0.20.2",
1113
+                "@esbuild/win32-arm64": "0.20.2",
1114
+                "@esbuild/win32-ia32": "0.20.2",
1115
+                "@esbuild/win32-x64": "0.20.2"
897 1116
             }
898 1117
         },
899 1118
         "node_modules/escalade": {
@@ -1041,16 +1260,16 @@
1041 1260
             }
1042 1261
         },
1043 1262
         "node_modules/glob": {
1044
-            "version": "10.3.10",
1045
-            "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz",
1046
-            "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==",
1263
+            "version": "10.3.12",
1264
+            "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz",
1265
+            "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==",
1047 1266
             "dev": true,
1048 1267
             "dependencies": {
1049 1268
                 "foreground-child": "^3.1.0",
1050
-                "jackspeak": "^2.3.5",
1269
+                "jackspeak": "^2.3.6",
1051 1270
                 "minimatch": "^9.0.1",
1052
-                "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0",
1053
-                "path-scurry": "^1.10.1"
1271
+                "minipass": "^7.0.4",
1272
+                "path-scurry": "^1.10.2"
1054 1273
             },
1055 1274
             "bin": {
1056 1275
                 "glob": "dist/esm/bin.mjs"
@@ -1183,19 +1402,22 @@
1183 1402
             }
1184 1403
         },
1185 1404
         "node_modules/laravel-vite-plugin": {
1186
-            "version": "0.8.1",
1187
-            "resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-0.8.1.tgz",
1188
-            "integrity": "sha512-fxzUDjOA37kOsYq8dP+3oPIlw8/kJVXwu0hOXLun82R1LpV02shGeWGYKx2lbpKffL5I0sfPPjfqbYxuqBluAA==",
1405
+            "version": "1.0.2",
1406
+            "resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-1.0.2.tgz",
1407
+            "integrity": "sha512-Mcclml10khYzBVxDwJro8wnVDwD4i7XOSEMACQNnarvTnHjrjXLLL+B/Snif2wYAyElsOqagJZ7VAinb/2vF5g==",
1189 1408
             "dev": true,
1190 1409
             "dependencies": {
1191 1410
                 "picocolors": "^1.0.0",
1192
-                "vite-plugin-full-reload": "^1.0.5"
1411
+                "vite-plugin-full-reload": "^1.1.0"
1412
+            },
1413
+            "bin": {
1414
+                "clean-orphaned-assets": "bin/clean.js"
1193 1415
             },
1194 1416
             "engines": {
1195
-                "node": ">=14"
1417
+                "node": "^18.0.0 || >=20.0.0"
1196 1418
             },
1197 1419
             "peerDependencies": {
1198
-                "vite": "^3.0.0 || ^4.0.0"
1420
+                "vite": "^5.0.0"
1199 1421
             }
1200 1422
         },
1201 1423
         "node_modules/lilconfig": {
@@ -1293,9 +1515,9 @@
1293 1515
             }
1294 1516
         },
1295 1517
         "node_modules/minimatch": {
1296
-            "version": "9.0.3",
1297
-            "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
1298
-            "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
1518
+            "version": "9.0.4",
1519
+            "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz",
1520
+            "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==",
1299 1521
             "dev": true,
1300 1522
             "dependencies": {
1301 1523
                 "brace-expansion": "^2.0.1"
@@ -1403,12 +1625,12 @@
1403 1625
             "dev": true
1404 1626
         },
1405 1627
         "node_modules/path-scurry": {
1406
-            "version": "1.10.1",
1407
-            "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz",
1408
-            "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==",
1628
+            "version": "1.10.2",
1629
+            "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz",
1630
+            "integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==",
1409 1631
             "dev": true,
1410 1632
             "dependencies": {
1411
-                "lru-cache": "^9.1.1 || ^10.0.0",
1633
+                "lru-cache": "^10.2.0",
1412 1634
                 "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
1413 1635
             },
1414 1636
             "engines": {
@@ -1455,9 +1677,9 @@
1455 1677
             }
1456 1678
         },
1457 1679
         "node_modules/postcss": {
1458
-            "version": "8.4.35",
1459
-            "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz",
1460
-            "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==",
1680
+            "version": "8.4.38",
1681
+            "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
1682
+            "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==",
1461 1683
             "dev": true,
1462 1684
             "funding": [
1463 1685
                 {
@@ -1476,7 +1698,7 @@
1476 1698
             "dependencies": {
1477 1699
                 "nanoid": "^3.3.7",
1478 1700
                 "picocolors": "^1.0.0",
1479
-                "source-map-js": "^1.0.2"
1701
+                "source-map-js": "^1.2.0"
1480 1702
             },
1481 1703
             "engines": {
1482 1704
                 "node": "^10 || ^12 || >=14"
@@ -1597,6 +1819,90 @@
1597 1819
                 "node": ">=4"
1598 1820
             }
1599 1821
         },
1822
+        "node_modules/postcss-nesting": {
1823
+            "version": "12.1.0",
1824
+            "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-12.1.0.tgz",
1825
+            "integrity": "sha512-QOYnosaZ+mlP6plQrAxFw09UUp2Sgtxj1BVHN+rSVbtV0Yx48zRt9/9F/ZOoxOKBBEsaJk2MYhhVRjeRRw5yuw==",
1826
+            "dev": true,
1827
+            "funding": [
1828
+                {
1829
+                    "type": "github",
1830
+                    "url": "https://github.com/sponsors/csstools"
1831
+                },
1832
+                {
1833
+                    "type": "opencollective",
1834
+                    "url": "https://opencollective.com/csstools"
1835
+                }
1836
+            ],
1837
+            "dependencies": {
1838
+                "@csstools/selector-resolve-nested": "^1.1.0",
1839
+                "@csstools/selector-specificity": "^3.0.2",
1840
+                "postcss-selector-parser": "^6.0.13"
1841
+            },
1842
+            "engines": {
1843
+                "node": "^14 || ^16 || >=18"
1844
+            },
1845
+            "peerDependencies": {
1846
+                "postcss": "^8.4"
1847
+            }
1848
+        },
1849
+        "node_modules/postcss-nesting/node_modules/@csstools/selector-resolve-nested": {
1850
+            "version": "1.1.0",
1851
+            "resolved": "https://registry.npmjs.org/@csstools/selector-resolve-nested/-/selector-resolve-nested-1.1.0.tgz",
1852
+            "integrity": "sha512-uWvSaeRcHyeNenKg8tp17EVDRkpflmdyvbE0DHo6D/GdBb6PDnCYYU6gRpXhtICMGMcahQmj2zGxwFM/WC8hCg==",
1853
+            "dev": true,
1854
+            "funding": [
1855
+                {
1856
+                    "type": "github",
1857
+                    "url": "https://github.com/sponsors/csstools"
1858
+                },
1859
+                {
1860
+                    "type": "opencollective",
1861
+                    "url": "https://opencollective.com/csstools"
1862
+                }
1863
+            ],
1864
+            "engines": {
1865
+                "node": "^14 || ^16 || >=18"
1866
+            },
1867
+            "peerDependencies": {
1868
+                "postcss-selector-parser": "^6.0.13"
1869
+            }
1870
+        },
1871
+        "node_modules/postcss-nesting/node_modules/@csstools/selector-specificity": {
1872
+            "version": "3.0.2",
1873
+            "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-3.0.2.tgz",
1874
+            "integrity": "sha512-RpHaZ1h9LE7aALeQXmXrJkRG84ZxIsctEN2biEUmFyKpzFM3zZ35eUMcIzZFsw/2olQE6v69+esEqU2f1MKycg==",
1875
+            "dev": true,
1876
+            "funding": [
1877
+                {
1878
+                    "type": "github",
1879
+                    "url": "https://github.com/sponsors/csstools"
1880
+                },
1881
+                {
1882
+                    "type": "opencollective",
1883
+                    "url": "https://opencollective.com/csstools"
1884
+                }
1885
+            ],
1886
+            "engines": {
1887
+                "node": "^14 || ^16 || >=18"
1888
+            },
1889
+            "peerDependencies": {
1890
+                "postcss-selector-parser": "^6.0.13"
1891
+            }
1892
+        },
1893
+        "node_modules/postcss-nesting/node_modules/postcss-selector-parser": {
1894
+            "version": "6.0.16",
1895
+            "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz",
1896
+            "integrity": "sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==",
1897
+            "dev": true,
1898
+            "dependencies": {
1899
+                "cssesc": "^3.0.0",
1900
+                "util-deprecate": "^1.0.2"
1901
+            },
1902
+            "engines": {
1903
+                "node": ">=4"
1904
+            }
1905
+        },
1600 1906
         "node_modules/postcss-selector-parser": {
1601 1907
             "version": "6.0.10",
1602 1908
             "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
@@ -1691,18 +1997,36 @@
1691 1997
             }
1692 1998
         },
1693 1999
         "node_modules/rollup": {
1694
-            "version": "3.29.4",
1695
-            "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz",
1696
-            "integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==",
2000
+            "version": "4.13.2",
2001
+            "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.13.2.tgz",
2002
+            "integrity": "sha512-MIlLgsdMprDBXC+4hsPgzWUasLO9CE4zOkj/u6j+Z6j5A4zRY+CtiXAdJyPtgCsc42g658Aeh1DlrdVEJhsL2g==",
1697 2003
             "dev": true,
2004
+            "dependencies": {
2005
+                "@types/estree": "1.0.5"
2006
+            },
1698 2007
             "bin": {
1699 2008
                 "rollup": "dist/bin/rollup"
1700 2009
             },
1701 2010
             "engines": {
1702
-                "node": ">=14.18.0",
2011
+                "node": ">=18.0.0",
1703 2012
                 "npm": ">=8.0.0"
1704 2013
             },
1705 2014
             "optionalDependencies": {
2015
+                "@rollup/rollup-android-arm-eabi": "4.13.2",
2016
+                "@rollup/rollup-android-arm64": "4.13.2",
2017
+                "@rollup/rollup-darwin-arm64": "4.13.2",
2018
+                "@rollup/rollup-darwin-x64": "4.13.2",
2019
+                "@rollup/rollup-linux-arm-gnueabihf": "4.13.2",
2020
+                "@rollup/rollup-linux-arm64-gnu": "4.13.2",
2021
+                "@rollup/rollup-linux-arm64-musl": "4.13.2",
2022
+                "@rollup/rollup-linux-powerpc64le-gnu": "4.13.2",
2023
+                "@rollup/rollup-linux-riscv64-gnu": "4.13.2",
2024
+                "@rollup/rollup-linux-s390x-gnu": "4.13.2",
2025
+                "@rollup/rollup-linux-x64-gnu": "4.13.2",
2026
+                "@rollup/rollup-linux-x64-musl": "4.13.2",
2027
+                "@rollup/rollup-win32-arm64-msvc": "4.13.2",
2028
+                "@rollup/rollup-win32-ia32-msvc": "4.13.2",
2029
+                "@rollup/rollup-win32-x64-msvc": "4.13.2",
1706 2030
                 "fsevents": "~2.3.2"
1707 2031
             }
1708 2032
         },
@@ -1763,9 +2087,9 @@
1763 2087
             }
1764 2088
         },
1765 2089
         "node_modules/source-map-js": {
1766
-            "version": "1.0.2",
1767
-            "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
1768
-            "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
2090
+            "version": "1.2.0",
2091
+            "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
2092
+            "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
1769 2093
             "dev": true,
1770 2094
             "engines": {
1771 2095
                 "node": ">=0.10.0"
@@ -1902,9 +2226,9 @@
1902 2226
             }
1903 2227
         },
1904 2228
         "node_modules/tailwindcss": {
1905
-            "version": "3.4.1",
1906
-            "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz",
1907
-            "integrity": "sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA==",
2229
+            "version": "3.4.3",
2230
+            "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.3.tgz",
2231
+            "integrity": "sha512-U7sxQk/n397Bmx4JHbJx/iSOOv5G+II3f1kpLpY2QeUv5DcPdcTsYLlusZfq1NthHS1c1cZoyFmmkex1rzke0A==",
1908 2232
             "dev": true,
1909 2233
             "dependencies": {
1910 2234
                 "@alloc/quick-lru": "^5.2.0",
@@ -1915,7 +2239,7 @@
1915 2239
                 "fast-glob": "^3.3.0",
1916 2240
                 "glob-parent": "^6.0.2",
1917 2241
                 "is-glob": "^4.0.3",
1918
-                "jiti": "^1.19.1",
2242
+                "jiti": "^1.21.0",
1919 2243
                 "lilconfig": "^2.1.0",
1920 2244
                 "micromatch": "^4.0.5",
1921 2245
                 "normalize-path": "^3.0.0",
@@ -2027,29 +2351,29 @@
2027 2351
             "dev": true
2028 2352
         },
2029 2353
         "node_modules/vite": {
2030
-            "version": "4.5.2",
2031
-            "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.2.tgz",
2032
-            "integrity": "sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==",
2354
+            "version": "5.2.6",
2355
+            "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.6.tgz",
2356
+            "integrity": "sha512-FPtnxFlSIKYjZ2eosBQamz4CbyrTizbZ3hnGJlh/wMtCrlp1Hah6AzBLjGI5I2urTfNnpovpHdrL6YRuBOPnCA==",
2033 2357
             "dev": true,
2034 2358
             "dependencies": {
2035
-                "esbuild": "^0.18.10",
2036
-                "postcss": "^8.4.27",
2037
-                "rollup": "^3.27.1"
2359
+                "esbuild": "^0.20.1",
2360
+                "postcss": "^8.4.36",
2361
+                "rollup": "^4.13.0"
2038 2362
             },
2039 2363
             "bin": {
2040 2364
                 "vite": "bin/vite.js"
2041 2365
             },
2042 2366
             "engines": {
2043
-                "node": "^14.18.0 || >=16.0.0"
2367
+                "node": "^18.0.0 || >=20.0.0"
2044 2368
             },
2045 2369
             "funding": {
2046 2370
                 "url": "https://github.com/vitejs/vite?sponsor=1"
2047 2371
             },
2048 2372
             "optionalDependencies": {
2049
-                "fsevents": "~2.3.2"
2373
+                "fsevents": "~2.3.3"
2050 2374
             },
2051 2375
             "peerDependencies": {
2052
-                "@types/node": ">= 14",
2376
+                "@types/node": "^18.0.0 || >=20.0.0",
2053 2377
                 "less": "*",
2054 2378
                 "lightningcss": "^1.21.0",
2055 2379
                 "sass": "*",

+ 8
- 7
package.json Целия файл

@@ -6,13 +6,14 @@
6 6
         "build": "vite build"
7 7
     },
8 8
     "devDependencies": {
9
-        "@tailwindcss/forms": "^0.5.6",
9
+        "@tailwindcss/forms": "^0.5.7",
10 10
         "@tailwindcss/typography": "^0.5.10",
11
-        "autoprefixer": "^10.4.16",
12
-        "axios": "^1.1.2",
13
-        "laravel-vite-plugin": "^0.8.0",
14
-        "postcss": "^8.4.31",
15
-        "tailwindcss": "^3.3.3",
16
-        "vite": "^4.0.0"
11
+        "autoprefixer": "^10.4.18",
12
+        "axios": "^1.6.4",
13
+        "laravel-vite-plugin": "^1.0",
14
+        "postcss": "^8.4.35",
15
+        "postcss-nesting": "^12.1.0",
16
+        "tailwindcss": "^3.4.1",
17
+        "vite": "^5.0"
17 18
     }
18 19
 }

+ 5
- 3
phpunit.xml Целия файл

@@ -19,11 +19,13 @@
19 19
     </source>
20 20
     <php>
21 21
         <env name="APP_ENV" value="testing"/>
22
+        <env name="APP_MAINTENANCE_DRIVER" value="file"/>
22 23
         <env name="BCRYPT_ROUNDS" value="4"/>
23
-        <env name="CACHE_DRIVER" value="array"/>
24
-        <env name="DB_CONNECTION" value="sqlite"/>
25
-        <env name="DB_DATABASE" value=":memory:"/>
24
+        <env name="CACHE_STORE" value="array"/>
25
+        <!-- <env name="DB_CONNECTION" value="sqlite"/> -->
26
+        <!-- <env name="DB_DATABASE" value=":memory:"/> -->
26 27
         <env name="MAIL_MAILER" value="array"/>
28
+        <env name="PULSE_ENABLED" value="false"/>
27 29
         <env name="QUEUE_CONNECTION" value="sync"/>
28 30
         <env name="SESSION_DRIVER" value="array"/>
29 31
         <env name="TELESCOPE_ENABLED" value="false"/>

+ 1
- 1
postcss.config.js Целия файл

@@ -1,6 +1,6 @@
1 1
 export default {
2 2
     plugins: {
3
-        'tailwindcss/nesting': {},
3
+        'tailwindcss/nesting': 'postcss-nesting',
4 4
         tailwindcss: {},
5 5
         autoprefixer: {},
6 6
     },

+ 1
- 0
resources/css/filament/company/tailwind.config.js Целия файл

@@ -12,6 +12,7 @@ export default {
12 12
         './vendor/andrewdwallo/filament-selectify/resources/views/**/*.blade.php',
13 13
         './resources/views/vendor/**/*.blade.php',
14 14
         './vendor/bezhansalleh/filament-panel-switch/resources/views/panel-switch-menu.blade.php',
15
+        './vendor/awcodes/filament-table-repeater/resources/**/*.blade.php',
15 16
     ],
16 17
     theme: {
17 18
         extend: {

+ 59
- 0
resources/css/filament/company/theme.css Целия файл

@@ -1,8 +1,49 @@
1 1
 @import '../../../../vendor/filament/filament/resources/css/theme.css';
2 2
 @import 'tooltip.css';
3
+@import '../../../../vendor/awcodes/filament-table-repeater/resources/css/plugin.css';
3 4
 
4 5
 @config './tailwind.config.js';
5 6
 
7
+.choices__list.choices__list--single {
8
+    @apply w-full;
9
+}
10
+
11
+.table-repeater-container {
12
+    @apply rounded-none ring-0;
13
+}
14
+
15
+.table-repeater-component {
16
+    @apply space-y-10;
17
+}
18
+
19
+.table-repeater-component ul {
20
+    @apply justify-start;
21
+}
22
+
23
+.table-repeater-row {
24
+    @apply divide-x-0 !important;
25
+}
26
+
27
+.table-repeater-column {
28
+    @apply py-2 !important;
29
+}
30
+
31
+.table-repeater-header {
32
+    @apply rounded-t-none !important;
33
+}
34
+
35
+.table-repeater-rows-wrapper {
36
+    @apply divide-gray-300 last:border-b last:border-gray-300 dark:divide-white/20 dark:last:border-white/20;
37
+}
38
+
39
+.table-repeater-header tr {
40
+    @apply divide-x-0 text-base sm:text-sm sm:leading-6 !important;
41
+}
42
+
43
+.table-repeater-header-column {
44
+    @apply ps-3 pe-3 font-semibold bg-gray-200 dark:bg-gray-800 rounded-none !important;
45
+}
46
+
6 47
 .es-report-card {
7 48
     @apply md:!grid-cols-2;
8 49
 
@@ -19,6 +60,24 @@
19 60
     }
20 61
 }
21 62
 
63
+.fi-modal.fi-width-screen {
64
+    .fi-modal-header {
65
+        @apply xl:px-80;
66
+
67
+        .absolute.end-4.top-4 {
68
+            @apply xl:end-80;
69
+        }
70
+    }
71
+
72
+    .fi-modal-content {
73
+        @apply xl:px-80;
74
+    }
75
+
76
+    .fi-modal-footer {
77
+        @apply xl:px-80;
78
+    }
79
+}
80
+
22 81
 .choices:focus-visible {
23 82
     outline: none;
24 83
 }

+ 3
- 1
resources/data/lang/en.json Целия файл

@@ -246,5 +246,7 @@
246 246
     "Starting Balance": "Starting Balance",
247 247
     "Account Identification": "Account Identification",
248 248
     "Account Settings": "Account Settings",
249
-    "Financial Details": "Financial Details"
249
+    "Financial Details": "Financial Details",
250
+    "Edit": "Edit",
251
+    "Notes": "Notes"
250 252
 }

+ 29
- 0
resources/views/filament/company/components/actions/journal-entry-footer.blade.php Целия файл

@@ -0,0 +1,29 @@
1
+<div class="bg-primary-300/10 border border-primary-200 p-4 rounded-lg">
2
+    <div class="grid gap-3 items-end text-center">
3
+        <div class="text-sm">
4
+            <div class="text-gray-600 mb-2">Total Debits</div>
5
+            <strong class="text-lg">{{ $debitAmount }}</strong>
6
+        </div>
7
+        <div class="flex items-center justify-center px-2">
8
+            <strong class="text-lg">{!! $isJournalBalanced ? '=' : '&ne;' !!}</strong>
9
+        </div>
10
+        <div class="text-sm">
11
+            <div class="mb-2 text-gray-600">Total Credits</div>
12
+            <strong class="text-lg">{{ $creditAmount }}</strong>
13
+        </div>
14
+        <div class="col-span-3 text-sm text-gray-600">
15
+            <div class="flex justify-center items-center space-x-2">
16
+                <span>Difference:</span>
17
+                <span
18
+                    @class([
19
+                        'text-lg',
20
+                        'text-success-600' => $isJournalBalanced,
21
+                        'text-warning-600' => ! $isJournalBalanced,
22
+                    ])
23
+                >
24
+                    {{ $difference }}
25
+                </span>
26
+            </div>
27
+        </div>
28
+    </div>
29
+</div>

+ 13
- 11
resources/views/filament/company/pages/accounting/chart.blade.php Целия файл

@@ -47,18 +47,20 @@
47 47
 
48 48
                                 <!-- Chart Rows -->
49 49
                                 @forelse($subtype->accounts as $account)
50
-                                    @php
51
-                                        $accountBalance = $this->getAccountBalance($account);
52
-                                    @endphp
53 50
                                     <tr class="es-table__row">
54 51
                                         <td colspan="1" class="es-table__cell px-4 py-4">{{ $account->code }}</td>
55
-                                        <td colspan="1" class="es-table__cell px-4 py-4">{{ $account->name }}</td>
56
-                                        <td colspan="{{ $accountBalance === null ? '2' : '1' }}" class="es-table__cell px-4 py-4">{{ $account->description }}</td>
57
-                                        @if($accountBalance !== null)
58
-                                            <td colspan="1" class="es-table__cell px-4 py-4">
59
-                                                {{ $accountBalance }}
60
-                                            </td>
61
-                                        @endif
52
+                                        <td colspan="1" class="es-table__cell px-4 py-4">
53
+                                            {{ $account->name }}
54
+                                            <br>
55
+                                            <small class="text-gray-500 dark:text-gray-400">
56
+                                                @if($account->last_transaction_date)
57
+                                                    Last transaction on {{ $account->last_transaction_date }}
58
+                                                @else
59
+                                                    No transactions for this account
60
+                                                @endif
61
+                                            </small>
62
+                                        </td>
63
+                                        <td colspan="2" class="es-table__cell px-4 py-4">{{ $account->description }}</td>
62 64
                                         <td colspan="1" class="es-table__cell px-4 py-4">
63 65
                                             <div>
64 66
                                                 @if($account->default === false)
@@ -70,7 +72,7 @@
70 72
                                 @empty
71 73
                                     <!-- No Accounts Available Row -->
72 74
                                     <tr class="es-table__row">
73
-                                        <td colspan="5" class="es-table__cell px-4 py-4 italic">
75
+                                        <td colspan="5" class="es-table__cell px-4 py-4 italic text-xs text-gray-500 dark:text-gray-400">
74 76
                                             {{ __("You haven't added any {$subtype->name} accounts yet.") }}
75 77
                                         </td>
76 78
                                     </tr>

+ 4
- 0
resources/views/filament/company/pages/accounting/transactions.blade.php Целия файл

@@ -0,0 +1,4 @@
1
+<x-filament-panels::page>
2
+    {{ $this->form }}
3
+    {{ $this->table }}
4
+</x-filament-panels::page>

+ 225
- 0
resources/views/forms/components/journal-entry-repeater.blade.php Целия файл

@@ -0,0 +1,225 @@
1
+@php
2
+    use Filament\Forms\Components\Actions\Action;
3
+    use Filament\Support\Enums\Alignment;
4
+    use Filament\Support\Enums\MaxWidth;
5
+
6
+    $containers = $getChildComponentContainers();
7
+
8
+    $addAction = $getAction($getAddActionName());
9
+    $cloneAction = $getAction($getCloneActionName());
10
+    $deleteAction = $getAction($getDeleteActionName());
11
+    $moveDownAction = $getAction($getMoveDownActionName());
12
+    $moveUpAction = $getAction($getMoveUpActionName());
13
+    $reorderAction = $getAction($getReorderActionName());
14
+    $isReorderableWithButtons = $isReorderableWithButtons();
15
+    $extraItemActions = $getExtraItemActions();
16
+    $extraActions = $getExtraActions();
17
+    $visibleExtraItemActions = [];
18
+    $visibleExtraActions = [];
19
+
20
+    $headers = $getHeaders();
21
+    $renderHeader = $shouldRenderHeader();
22
+    $stackAt = $getStackAt();
23
+    $hasContainers = count($containers) > 0;
24
+    $emptyLabel = $getEmptyLabel();
25
+    $streamlined = $isStreamlined();
26
+
27
+    $statePath = $getStatePath();
28
+
29
+    foreach ($extraActions as $extraAction) {
30
+        $visibleExtraActions = array_filter(
31
+            $extraActions,
32
+            fn (Action $action): bool => $action->isVisible(),
33
+        );
34
+    }
35
+
36
+    $hasActions = $reorderAction->isVisible()
37
+        || $cloneAction->isVisible()
38
+        || $deleteAction->isVisible()
39
+        || $moveUpAction->isVisible()
40
+        || $moveDownAction->isVisible()
41
+        || filled($visibleExtraItemActions);
42
+@endphp
43
+
44
+<x-dynamic-component :component="$getFieldWrapperView()" :field="$field">
45
+    <div
46
+        x-data="{}"
47
+        {{ $attributes->merge($getExtraAttributes())->class([
48
+            'table-repeater-component space-y-6 relative',
49
+            'streamlined' => $streamlined,
50
+            match ($stackAt) {
51
+                'sm', MaxWidth::Small => 'break-point-sm',
52
+                'lg', MaxWidth::Large => 'break-point-lg',
53
+                'xl', MaxWidth::ExtraLarge => 'break-point-xl',
54
+                '2xl', MaxWidth::TwoExtraLarge => 'break-point-2xl',
55
+                default => 'break-point-md',
56
+            }
57
+        ]) }}
58
+    >
59
+        @if (count($containers) || $emptyLabel !== false)
60
+            <div class="table-repeater-container rounded-xl relative ring-1 ring-gray-950/5 dark:ring-white/20">
61
+                <table class="w-full">
62
+                    <thead @class([
63
+                        'table-repeater-header-hidden sr-only' => ! $renderHeader,
64
+                        'table-repeater-header rounded-t-xl overflow-hidden border-b border-gray-950/5 dark:border-white/20' => $renderHeader,
65
+                    ])>
66
+                    <tr class="text-xs md:divide-x md:divide-gray-950/5 dark:md:divide-white/20">
67
+                        @foreach ($headers as $key => $header)
68
+                            <th
69
+                                @class([
70
+                                    'table-repeater-header-column p-2 font-medium first:rounded-tl-xl last:rounded-tr-xl bg-gray-100 dark:text-gray-300 dark:bg-gray-900/60',
71
+                                    match($header->getAlignment()) {
72
+                                      'center', Alignment::Center => 'text-center',
73
+                                      'right', 'end', Alignment::Right, Alignment::End => 'text-end',
74
+                                      default => 'text-start'
75
+                                    }
76
+                                ])
77
+                                style="width: {{ $header->getWidth() }}"
78
+                            >
79
+                                {{ $header->getLabel() }}
80
+                                @if ($header->isRequired())
81
+                                    <span class="whitespace-nowrap">
82
+                                        <sup class="font-medium text-danger-700 dark:text-danger-400">*</sup>
83
+                                    </span>
84
+                                @endif
85
+                            </th>
86
+                        @endforeach
87
+                        @if (count($containers))
88
+                            <th class="table-repeater-header-column w-px last:rounded-tr-xl p-2 bg-gray-100 dark:bg-gray-900/60">
89
+                                <span class="sr-only">
90
+                                    {{ trans('table-repeater::components.repeater.row_actions.label') }}
91
+                                </span>
92
+                            </th>
93
+                        @endif
94
+                    </tr>
95
+                    </thead>
96
+                    <tbody
97
+                        x-sortable
98
+                        wire:end.stop="{{ 'mountFormComponentAction(\'' . $statePath . '\', \'reorder\', { items: $event.target.sortable.toArray() })' }}"
99
+                        class="table-repeater-rows-wrapper divide-y divide-gray-950/5 dark:divide-white/20"
100
+                    >
101
+                    @if (count($containers))
102
+                        @foreach ($containers as $uuid => $row)
103
+                            @php
104
+                                $visibleExtraItemActions = array_filter(
105
+                                    $extraItemActions,
106
+                                    fn (Action $action): bool => $action(['item' => $uuid])->isVisible(),
107
+                                );
108
+                            @endphp
109
+                            <tr
110
+                                wire:key="{{ $this->getId() }}.{{ $row->getStatePath() }}.{{ $field::class }}.item"
111
+                                x-sortable-item="{{ $uuid }}"
112
+                                class="table-repeater-row"
113
+                            >
114
+                                @php($counter = 0)
115
+                                @foreach($row->getComponents() as $cell)
116
+                                    @if($cell instanceof \Filament\Forms\Components\Hidden || $cell->isHidden())
117
+                                        {{ $cell }}
118
+                                    @else
119
+                                        <td
120
+                                            @class([
121
+                                                'table-repeater-column',
122
+                                                'p-2' => ! $streamlined,
123
+                                                'has-hidden-label' => $cell->isLabelHidden(),
124
+                                                match($headers[$counter++]->getAlignment()) {
125
+                                                  'center', Alignment::Center => 'text-center',
126
+                                                  'right', 'end', Alignment::Right, Alignment::End => 'text-end',
127
+                                                  default => 'text-start'
128
+                                                }
129
+                                            ])
130
+                                            style="width: {{ $cell->getMaxWidth() ?? 'auto' }}"
131
+                                        >
132
+                                            {{ $cell }}
133
+                                        </td>
134
+                                    @endif
135
+                                @endforeach
136
+
137
+
138
+                                <td class="table-repeater-column p-2 w-px">
139
+                                    <ul class="flex items-center table-repeater-row-actions gap-x-3 px-2">
140
+                                        @foreach ($visibleExtraItemActions as $extraItemAction)
141
+                                            <li>
142
+                                                {{ $extraItemAction(['item' => $uuid]) }}
143
+                                            </li>
144
+                                        @endforeach
145
+
146
+                                        @if ($reorderAction->isVisible())
147
+                                            <li x-sortable-handle class="shrink-0">
148
+                                                {{ $reorderAction }}
149
+                                            </li>
150
+                                        @endif
151
+
152
+                                        @if ($isReorderableWithButtons)
153
+                                            @if (! $loop->first)
154
+                                                <li>
155
+                                                    {{ $moveUpAction(['item' => $uuid]) }}
156
+                                                </li>
157
+                                            @endif
158
+
159
+                                            @if (! $loop->last)
160
+                                                <li>
161
+                                                    {{ $moveDownAction(['item' => $uuid]) }}
162
+                                                </li>
163
+                                            @endif
164
+                                        @endif
165
+
166
+                                        @if ($cloneAction->isVisible())
167
+                                            <li>
168
+                                                {{ $cloneAction(['item' => $uuid]) }}
169
+                                            </li>
170
+                                        @endif
171
+
172
+                                        <li
173
+                                            @class([
174
+                                                'invisible' => ! $deleteAction->isVisible(),
175
+                                            ])
176
+                                        >
177
+                                            {{ $deleteAction(['item' => $uuid]) }}
178
+                                        </li>
179
+                                    </ul>
180
+                                </td>
181
+                            </tr>
182
+                        @endforeach
183
+                    @else
184
+                        <tr class="table-repeater-row table-repeater-empty-row">
185
+                            <td colspan="{{ count($headers) + intval($hasActions) }}"
186
+                                class="table-repeater-column table-repeater-empty-column p-4 w-px text-center italic">
187
+                                {{ $emptyLabel ?: trans('table-repeater::components.repeater.empty.label') }}
188
+                            </td>
189
+                        </tr>
190
+                    @endif
191
+                    </tbody>
192
+                </table>
193
+            </div>
194
+        @endif
195
+
196
+        @if ($addAction->isVisible() || filled($visibleExtraActions) || $hasFooterItem())
197
+            <div class="flex justify-between items-start">
198
+                <!-- Actions grouped to the left -->
199
+                @if ($addAction->isVisible() || filled($visibleExtraActions))
200
+                    <ul class="flex gap-4">
201
+                        @if ($addAction->isVisible())
202
+                            <li>
203
+                                {{ $addAction }}
204
+                            </li>
205
+                        @endif
206
+                        @if (filled($visibleExtraActions))
207
+                            @foreach ($visibleExtraActions as $extraAction)
208
+                                <li>
209
+                                    {{ $extraAction }}
210
+                                </li>
211
+                            @endforeach
212
+                        @endif
213
+                    </ul>
214
+                @endif
215
+
216
+                <!-- Container for Footer Item to the right -->
217
+                @if($hasFooterItem())
218
+                    <div>
219
+                        {{ $getFooterItem() }}
220
+                    </div>
221
+                @endif
222
+            </div>
223
+        @endif
224
+    </div>
225
+</x-dynamic-component>

+ 2
- 13
routes/api.php Целия файл

@@ -3,17 +3,6 @@
3 3
 use Illuminate\Http\Request;
4 4
 use Illuminate\Support\Facades\Route;
5 5
 
6
-/*
7
-|--------------------------------------------------------------------------
8
-| API Routes
9
-|--------------------------------------------------------------------------
10
-|
11
-| Here is where you can register API routes for your application. These
12
-| routes are loaded by the RouteServiceProvider and all of them will
13
-| be assigned to the "api" middleware group. Make something great!
14
-|
15
-*/
16
-
17
-Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
6
+Route::get('/user', function (Request $request) {
18 7
     return $request->user();
19
-});
8
+})->middleware('auth:sanctum');

+ 0
- 18
routes/channels.php Целия файл

@@ -1,18 +0,0 @@
1
-<?php
2
-
3
-use Illuminate\Support\Facades\Broadcast;
4
-
5
-/*
6
-|--------------------------------------------------------------------------
7
-| Broadcast Channels
8
-|--------------------------------------------------------------------------
9
-|
10
-| Here you may register all of the event broadcasting channels that your
11
-| application supports. The given channel authorization callbacks are
12
-| used to check if an authenticated user can listen to the channel.
13
-|
14
-*/
15
-
16
-Broadcast::channel('App.Models.User.{id}', function ($user, $id) {
17
-    return (int) $user->id === (int) $id;
18
-});

+ 1
- 12
routes/console.php Целия файл

@@ -3,17 +3,6 @@
3 3
 use Illuminate\Foundation\Inspiring;
4 4
 use Illuminate\Support\Facades\Artisan;
5 5
 
6
-/*
7
-|--------------------------------------------------------------------------
8
-| Console Routes
9
-|--------------------------------------------------------------------------
10
-|
11
-| This file is where you may define all of your Closure based console
12
-| commands. Each Closure is bound to a command instance allowing a
13
-| simple approach to interacting with each command's IO methods.
14
-|
15
-*/
16
-
17 6
 Artisan::command('inspire', function () {
18 7
     $this->comment(Inspiring::quote());
19
-})->purpose('Display an inspiring quote');
8
+})->purpose('Display an inspiring quote')->hourly();

+ 0
- 11
routes/web.php Целия файл

@@ -2,17 +2,6 @@
2 2
 
3 3
 use Illuminate\Support\Facades\Route;
4 4
 
5
-/*
6
-|--------------------------------------------------------------------------
7
-| Web Routes
8
-|--------------------------------------------------------------------------
9
-|
10
-| Here is where you can register web routes for your application. These
11
-| routes are loaded by the RouteServiceProvider and all of them will
12
-| be assigned to the "web" middleware group. Make something great!
13
-|
14
-*/
15
-
16 5
 Route::get('/', function () {
17 6
     return view('welcome');
18 7
 });

+ 1
- 1
tests/TestCase.php Целия файл

@@ -6,5 +6,5 @@ use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
6 6
 
7 7
 abstract class TestCase extends BaseTestCase
8 8
 {
9
-    use CreatesApplication;
9
+    //
10 10
 }

Loading…
Отказ
Запис