Andrew Wallo 4 months ago
parent
commit
c6c467b036

+ 2
- 3
app/Concerns/HasTransactionAction.php View File

114
                     ->required(),
114
                     ->required(),
115
                 Forms\Components\Select::make('account_id')
115
                 Forms\Components\Select::make('account_id')
116
                     ->label('Category')
116
                     ->label('Category')
117
-                    ->options(fn (Forms\Get $get, ?Transaction $transaction) => Transaction::getChartAccountOptions(type: TransactionType::parse($get('type')), nominalAccountsOnly: true, currentAccountId: $transaction?->account_id))
117
+                    ->options(fn (Forms\Get $get, ?Transaction $transaction) => Transaction::getTransactionAccountOptions(type: TransactionType::parse($get('type')), currentAccountId: $transaction?->account_id))
118
                     ->searchable()
118
                     ->searchable()
119
-                    ->preload()
120
                     ->required(),
119
                     ->required(),
121
                 Forms\Components\Textarea::make('notes')
120
                 Forms\Components\Textarea::make('notes')
122
                     ->label('Notes')
121
                     ->label('Notes')
341
                 ->label('Description'),
340
                 ->label('Description'),
342
             Forms\Components\Select::make('account_id')
341
             Forms\Components\Select::make('account_id')
343
                 ->label('Account')
342
                 ->label('Account')
344
-                ->options(fn (?JournalEntry $journalEntry): array => Transaction::getChartAccountOptions(currentAccountId: $journalEntry?->account_id))
343
+                ->options(fn (?JournalEntry $journalEntry): array => Transaction::getJournalAccountOptions(currentAccountId: $journalEntry?->account_id))
345
                 ->softRequired()
344
                 ->softRequired()
346
                 ->searchable(),
345
                 ->searchable(),
347
             Forms\Components\TextInput::make('amount')
346
             Forms\Components\TextInput::make('amount')

+ 6
- 0
app/Filament/Actions/CreateTransactionAction.php View File

18
     {
18
     {
19
         parent::setUp();
19
         parent::setUp();
20
 
20
 
21
+        $this->label(null);
22
+
23
+        $this->groupedIcon(null);
24
+
25
+        $this->slideOver();
26
+
21
         $this->modalWidth(function (): MaxWidth {
27
         $this->modalWidth(function (): MaxWidth {
22
             return match ($this->transactionType) {
28
             return match ($this->transactionType) {
23
                 TransactionType::Journal => MaxWidth::Screen,
29
                 TransactionType::Journal => MaxWidth::Screen,

+ 1
- 1
app/Filament/Company/Pages/Reports/BaseReportPage.php View File

235
                 ->outlined()
235
                 ->outlined()
236
                 ->dropdownWidth('max-w-[7rem]')
236
                 ->dropdownWidth('max-w-[7rem]')
237
                 ->dropdownPlacement('bottom-end')
237
                 ->dropdownPlacement('bottom-end')
238
-                ->icon('heroicon-c-chevron-down')
238
+                ->icon('heroicon-m-chevron-down')
239
                 ->iconSize(IconSize::Small)
239
                 ->iconSize(IconSize::Small)
240
                 ->iconPosition(IconPosition::After),
240
                 ->iconPosition(IconPosition::After),
241
         ];
241
         ];

+ 37
- 15
app/Filament/Company/Resources/Accounting/TransactionResource.php View File

9
 use App\Filament\Tables\Actions\ReplicateBulkAction;
9
 use App\Filament\Tables\Actions\ReplicateBulkAction;
10
 use App\Models\Accounting\JournalEntry;
10
 use App\Models\Accounting\JournalEntry;
11
 use App\Models\Accounting\Transaction;
11
 use App\Models\Accounting\Transaction;
12
+use App\Models\Common\Client;
13
+use App\Models\Common\Vendor;
12
 use Exception;
14
 use Exception;
13
 use Filament\Forms\Components\DatePicker;
15
 use Filament\Forms\Components\DatePicker;
14
 use Filament\Forms\Components\Grid;
16
 use Filament\Forms\Components\Grid;
29
 {
31
 {
30
     protected static ?string $model = Transaction::class;
32
     protected static ?string $model = Transaction::class;
31
 
33
 
32
-    protected static ?string $navigationGroup = 'Accounting';
33
-
34
-    protected static ?string $navigationIcon = 'heroicon-o-banknotes';
35
-
36
     protected static ?string $recordTitleAttribute = 'description';
34
     protected static ?string $recordTitleAttribute = 'description';
37
 
35
 
38
-    protected static bool $isGloballySearchable = false;
39
-
40
     public static function form(Form $form): Form
36
     public static function form(Form $form): Form
41
     {
37
     {
42
         return $form
38
         return $form
51
                     'account',
47
                     'account',
52
                     'bankAccount.account',
48
                     'bankAccount.account',
53
                     'journalEntries.account',
49
                     'journalEntries.account',
50
+                    'payeeable',
54
                 ])
51
                 ])
55
                     ->where(function (Builder $query) {
52
                     ->where(function (Builder $query) {
56
                         $query->whereNull('transactionable_id')
53
                         $query->whereNull('transactionable_id')
69
                 Tables\Columns\TextColumn::make('description')
66
                 Tables\Columns\TextColumn::make('description')
70
                     ->label('Description')
67
                     ->label('Description')
71
                     ->limit(50)
68
                     ->limit(50)
69
+                    ->searchable()
72
                     ->toggleable(),
70
                     ->toggleable(),
71
+                Tables\Columns\TextColumn::make('payeeable.name')
72
+                    ->label('Payee')
73
+                    ->searchable()
74
+                    ->toggleable(isToggledHiddenByDefault: true),
73
                 Tables\Columns\TextColumn::make('bankAccount.account.name')
75
                 Tables\Columns\TextColumn::make('bankAccount.account.name')
74
                     ->label('Account')
76
                     ->label('Account')
77
+                    ->searchable()
75
                     ->toggleable(),
78
                     ->toggleable(),
76
                 Tables\Columns\TextColumn::make('account.name')
79
                 Tables\Columns\TextColumn::make('account.name')
77
                     ->label('Category')
80
                     ->label('Category')
78
                     ->prefix(static fn (Transaction $transaction) => $transaction->type->isTransfer() ? 'Transfer to ' : null)
81
                     ->prefix(static fn (Transaction $transaction) => $transaction->type->isTransfer() ? 'Transfer to ' : null)
82
+                    ->searchable()
79
                     ->toggleable()
83
                     ->toggleable()
80
                     ->state(static fn (Transaction $transaction) => $transaction->account->name ?? 'Journal Entry'),
84
                     ->state(static fn (Transaction $transaction) => $transaction->account->name ?? 'Journal Entry'),
81
                 Tables\Columns\TextColumn::make('amount')
85
                 Tables\Columns\TextColumn::make('amount')
91
                     ->sortable()
95
                     ->sortable()
92
                     ->currency(static fn (Transaction $transaction) => $transaction->bankAccount?->account->currency_code),
96
                     ->currency(static fn (Transaction $transaction) => $transaction->bankAccount?->account->currency_code),
93
             ])
97
             ])
94
-            ->recordClasses(static fn (Transaction $transaction) => $transaction->reviewed ? 'bg-primary-300/10' : null)
95
             ->defaultSort('posted_at', 'desc')
98
             ->defaultSort('posted_at', 'desc')
96
             ->filters([
99
             ->filters([
97
                 Tables\Filters\SelectFilter::make('bank_account_id')
100
                 Tables\Filters\SelectFilter::make('bank_account_id')
98
                     ->label('Account')
101
                     ->label('Account')
99
                     ->searchable()
102
                     ->searchable()
100
-                    ->options(fn () => Transaction::getBankAccountOptions(false)),
103
+                    ->options(static fn () => Transaction::getBankAccountOptions(false)),
101
                 Tables\Filters\SelectFilter::make('account_id')
104
                 Tables\Filters\SelectFilter::make('account_id')
102
                     ->label('Category')
105
                     ->label('Category')
103
                     ->multiple()
106
                     ->multiple()
104
-                    ->options(fn () => Transaction::getChartAccountOptions(nominalAccountsOnly: false)),
107
+                    ->options(static fn () => Transaction::getChartAccountOptions()),
105
                 Tables\Filters\TernaryFilter::make('reviewed')
108
                 Tables\Filters\TernaryFilter::make('reviewed')
106
                     ->label('Status')
109
                     ->label('Status')
107
-                    ->native(false)
108
                     ->trueLabel('Reviewed')
110
                     ->trueLabel('Reviewed')
109
                     ->falseLabel('Not Reviewed'),
111
                     ->falseLabel('Not Reviewed'),
110
                 Tables\Filters\SelectFilter::make('type')
112
                 Tables\Filters\SelectFilter::make('type')
111
                     ->label('Type')
113
                     ->label('Type')
112
-                    ->native(false)
113
                     ->options(TransactionType::class),
114
                     ->options(TransactionType::class),
115
+                Tables\Filters\TernaryFilter::make('is_payment')
116
+                    ->label('Payment')
117
+                    ->default(false),
118
+                Tables\Filters\SelectFilter::make('payee')
119
+                    ->label('Payee')
120
+                    ->options(static fn () => Transaction::getPayeeOptions())
121
+                    ->searchable()
122
+                    ->query(function (Builder $query, array $data): Builder {
123
+                        if (empty($data['value'])) {
124
+                            return $query;
125
+                        }
126
+
127
+                        $id = (int) $data['value'];
128
+
129
+                        if ($id < 0) {
130
+                            return $query->where('payeeable_type', Vendor::class)
131
+                                ->where('payeeable_id', abs($id));
132
+                        } else {
133
+                            return $query->where('payeeable_type', Client::class)
134
+                                ->where('payeeable_id', $id);
135
+                        }
136
+                    }),
114
                 static::buildDateRangeFilter('posted_at', 'Posted', true),
137
                 static::buildDateRangeFilter('posted_at', 'Posted', true),
115
                 static::buildDateRangeFilter('updated_at', 'Last modified'),
138
                 static::buildDateRangeFilter('updated_at', 'Last modified'),
116
             ])
139
             ])
121
                         $filters['account_id'],
144
                         $filters['account_id'],
122
                         $filters['reviewed'],
145
                         $filters['reviewed'],
123
                         $filters['type'],
146
                         $filters['type'],
147
+                        $filters['is_payment'],
148
+                        $filters['payee'],
124
                     ])
149
                     ])
125
                     ->columnSpanFull()
150
                     ->columnSpanFull()
126
                     ->extraAttributes(['class' => 'border-b border-gray-200 dark:border-white/10 pb-8']),
151
                     ->extraAttributes(['class' => 'border-b border-gray-200 dark:border-white/10 pb-8']),
148
                 Tables\Actions\ActionGroup::make([
173
                 Tables\Actions\ActionGroup::make([
149
                     Tables\Actions\ActionGroup::make([
174
                     Tables\Actions\ActionGroup::make([
150
                         EditTransactionAction::make('editTransaction')
175
                         EditTransactionAction::make('editTransaction')
151
-                            ->label('Edit transaction')
152
                             ->visible(static fn (Transaction $transaction) => $transaction->type->isStandard() && ! $transaction->transactionable_id),
176
                             ->visible(static fn (Transaction $transaction) => $transaction->type->isStandard() && ! $transaction->transactionable_id),
153
                         EditTransactionAction::make('editTransfer')
177
                         EditTransactionAction::make('editTransfer')
154
-                            ->label('Edit transfer')
155
                             ->type(TransactionType::Transfer)
178
                             ->type(TransactionType::Transfer)
156
                             ->visible(static fn (Transaction $transaction) => $transaction->type->isTransfer()),
179
                             ->visible(static fn (Transaction $transaction) => $transaction->type->isTransfer()),
157
-                        EditTransactionAction::make('editJournalTransaction')
158
-                            ->label('Edit journal transaction')
180
+                        EditTransactionAction::make('editJournalEntry')
159
                             ->type(TransactionType::Journal)
181
                             ->type(TransactionType::Journal)
160
                             ->visible(static fn (Transaction $transaction) => $transaction->type->isJournal() && ! $transaction->transactionable_id),
182
                             ->visible(static fn (Transaction $transaction) => $transaction->type->isJournal() && ! $transaction->transactionable_id),
161
                         Tables\Actions\ReplicateAction::make()
183
                         Tables\Actions\ReplicateAction::make()

+ 16
- 17
app/Filament/Company/Resources/Accounting/TransactionResource/Pages/ListTransactions.php View File

11
 use Filament\Actions;
11
 use Filament\Actions;
12
 use Filament\Resources\Pages\ListRecords;
12
 use Filament\Resources\Pages\ListRecords;
13
 use Filament\Support\Enums\IconPosition;
13
 use Filament\Support\Enums\IconPosition;
14
-use Filament\Support\Enums\IconSize;
15
 use Filament\Support\Enums\MaxWidth;
14
 use Filament\Support\Enums\MaxWidth;
16
 
15
 
17
 class ListTransactions extends ListRecords
16
 class ListTransactions extends ListRecords
28
     protected function getHeaderActions(): array
27
     protected function getHeaderActions(): array
29
     {
28
     {
30
         return [
29
         return [
31
-            CreateTransactionAction::make('addIncome')
32
-                ->label('Add income')
33
-                ->type(TransactionType::Deposit),
34
-            CreateTransactionAction::make('addExpense')
35
-                ->label('Add expense')
36
-                ->type(TransactionType::Withdrawal),
37
-            CreateTransactionAction::make('addTransfer')
38
-                ->label('Add transfer')
39
-                ->type(TransactionType::Transfer),
40
             Actions\ActionGroup::make([
30
             Actions\ActionGroup::make([
41
-                CreateTransactionAction::make('addJournalTransaction')
42
-                    ->label('Add journal transaction')
43
-                    ->type(TransactionType::Journal)
44
-                    ->groupedIcon(null),
31
+                CreateTransactionAction::make('addDeposit')
32
+                    ->type(TransactionType::Deposit),
33
+                CreateTransactionAction::make('addWithdrawal')
34
+                    ->type(TransactionType::Withdrawal),
35
+                CreateTransactionAction::make('addTransfer')
36
+                    ->type(TransactionType::Transfer),
37
+                CreateTransactionAction::make('addJournalEntry')
38
+                    ->type(TransactionType::Journal),
39
+            ])
40
+                ->label('Add transaction')
41
+                ->button()
42
+                ->dropdownPlacement('bottom-end')
43
+                ->icon('heroicon-m-chevron-down')
44
+                ->iconPosition(IconPosition::After),
45
+            Actions\ActionGroup::make([
45
                 Actions\Action::make('connectBank')
46
                 Actions\Action::make('connectBank')
46
                     ->label('Connect your bank')
47
                     ->label('Connect your bank')
47
                     ->visible(app(PlaidService::class)->isEnabled())
48
                     ->visible(app(PlaidService::class)->isEnabled())
50
                 ->label('More')
51
                 ->label('More')
51
                 ->button()
52
                 ->button()
52
                 ->outlined()
53
                 ->outlined()
53
-                ->dropdownWidth('max-w-fit')
54
                 ->dropdownPlacement('bottom-end')
54
                 ->dropdownPlacement('bottom-end')
55
-                ->icon('heroicon-c-chevron-down')
56
-                ->iconSize(IconSize::Small)
55
+                ->icon('heroicon-m-chevron-down')
57
                 ->iconPosition(IconPosition::After),
56
                 ->iconPosition(IconPosition::After),
58
         ];
57
         ];
59
     }
58
     }

+ 1
- 25
app/Filament/Company/Resources/Accounting/TransactionResource/Pages/ViewTransaction.php View File

7
 use App\Models\Accounting\JournalEntry;
7
 use App\Models\Accounting\JournalEntry;
8
 use App\Models\Accounting\Transaction;
8
 use App\Models\Accounting\Transaction;
9
 use Filament\Actions;
9
 use Filament\Actions;
10
-use Filament\Infolists\Components\Grid;
11
-use Filament\Infolists\Components\RepeatableEntry;
12
 use Filament\Infolists\Components\Section;
10
 use Filament\Infolists\Components\Section;
13
 use Filament\Infolists\Components\TextEntry;
11
 use Filament\Infolists\Components\TextEntry;
14
 use Filament\Infolists\Infolist;
12
 use Filament\Infolists\Infolist;
57
                 ->button()
55
                 ->button()
58
                 ->outlined()
56
                 ->outlined()
59
                 ->dropdownPlacement('bottom-end')
57
                 ->dropdownPlacement('bottom-end')
60
-                ->icon('heroicon-c-chevron-down')
58
+                ->icon('heroicon-m-chevron-down')
61
                 ->iconSize(IconSize::Small)
59
                 ->iconSize(IconSize::Small)
62
                 ->iconPosition(IconPosition::After),
60
                 ->iconPosition(IconPosition::After),
63
         ];
61
         ];
103
                             ->label('Notes')
101
                             ->label('Notes')
104
                             ->columnSpanFull(),
102
                             ->columnSpanFull(),
105
                     ]),
103
                     ]),
106
-                Section::make('Journal Entries')
107
-                    ->visible(fn (Transaction $record) => $record->type->isJournal())
108
-                    ->schema([
109
-                        RepeatableEntry::make('journalEntries')
110
-                            ->hiddenLabel()
111
-                            ->schema([
112
-                                Grid::make(4)
113
-                                    ->schema([
114
-                                        TextEntry::make('type')
115
-                                            ->label('Type')
116
-                                            ->badge(),
117
-                                        TextEntry::make('description')
118
-                                            ->label('Description'),
119
-                                        TextEntry::make('account.name')
120
-                                            ->label('Account'),
121
-                                        TextEntry::make('amount')
122
-                                            ->label('Amount')
123
-                                            ->currency(fn (JournalEntry $record) => $record->transaction->bankAccount?->account->currency_code)
124
-                                            ->alignEnd(),
125
-                                    ]),
126
-                            ]),
127
-                    ]),
128
             ]);
104
             ]);
129
     }
105
     }
130
 
106
 

+ 1
- 1
app/Filament/Company/Resources/Purchases/BillResource/Pages/ViewBill.php View File

38
                 ->button()
38
                 ->button()
39
                 ->outlined()
39
                 ->outlined()
40
                 ->dropdownPlacement('bottom-end')
40
                 ->dropdownPlacement('bottom-end')
41
-                ->icon('heroicon-c-chevron-down')
41
+                ->icon('heroicon-m-chevron-down')
42
                 ->iconSize(IconSize::Small)
42
                 ->iconSize(IconSize::Small)
43
                 ->iconPosition(IconPosition::After),
43
                 ->iconPosition(IconPosition::After),
44
         ];
44
         ];

+ 1
- 1
app/Filament/Company/Resources/Purchases/VendorResource/Pages/ViewVendor.php View File

51
                 ->button()
51
                 ->button()
52
                 ->outlined()
52
                 ->outlined()
53
                 ->dropdownPlacement('bottom-end')
53
                 ->dropdownPlacement('bottom-end')
54
-                ->icon('heroicon-c-chevron-down')
54
+                ->icon('heroicon-m-chevron-down')
55
                 ->iconSize(IconSize::Small)
55
                 ->iconSize(IconSize::Small)
56
                 ->iconPosition(IconPosition::After),
56
                 ->iconPosition(IconPosition::After),
57
         ];
57
         ];

+ 1
- 1
app/Filament/Company/Resources/Sales/ClientResource/Pages/ViewClient.php View File

64
                 ->button()
64
                 ->button()
65
                 ->outlined()
65
                 ->outlined()
66
                 ->dropdownPlacement('bottom-end')
66
                 ->dropdownPlacement('bottom-end')
67
-                ->icon('heroicon-c-chevron-down')
67
+                ->icon('heroicon-m-chevron-down')
68
                 ->iconSize(IconSize::Small)
68
                 ->iconSize(IconSize::Small)
69
                 ->iconPosition(IconPosition::After),
69
                 ->iconPosition(IconPosition::After),
70
         ];
70
         ];

+ 1
- 1
app/Filament/Company/Resources/Sales/EstimateResource/Pages/ViewEstimate.php View File

49
                 ->button()
49
                 ->button()
50
                 ->outlined()
50
                 ->outlined()
51
                 ->dropdownPlacement('bottom-end')
51
                 ->dropdownPlacement('bottom-end')
52
-                ->icon('heroicon-c-chevron-down')
52
+                ->icon('heroicon-m-chevron-down')
53
                 ->iconSize(IconSize::Small)
53
                 ->iconSize(IconSize::Small)
54
                 ->iconPosition(IconPosition::After),
54
                 ->iconPosition(IconPosition::After),
55
         ];
55
         ];

+ 1
- 2
app/Filament/Company/Resources/Sales/InvoiceResource/Pages/ViewInvoice.php View File

17
 use Filament\Support\Enums\FontWeight;
17
 use Filament\Support\Enums\FontWeight;
18
 use Filament\Support\Enums\IconPosition;
18
 use Filament\Support\Enums\IconPosition;
19
 use Filament\Support\Enums\IconSize;
19
 use Filament\Support\Enums\IconSize;
20
-use Filament\Support\Enums\MaxWidth;
21
 use Illuminate\Support\HtmlString;
20
 use Illuminate\Support\HtmlString;
22
 
21
 
23
 class ViewInvoice extends ViewRecord
22
 class ViewInvoice extends ViewRecord
47
                 ->button()
46
                 ->button()
48
                 ->outlined()
47
                 ->outlined()
49
                 ->dropdownPlacement('bottom-end')
48
                 ->dropdownPlacement('bottom-end')
50
-                ->icon('heroicon-c-chevron-down')
49
+                ->icon('heroicon-m-chevron-down')
51
                 ->iconSize(IconSize::Small)
50
                 ->iconSize(IconSize::Small)
52
                 ->iconPosition(IconPosition::After),
51
                 ->iconPosition(IconPosition::After),
53
         ];
52
         ];

+ 1
- 1
app/Filament/Company/Resources/Sales/RecurringInvoiceResource/Pages/ViewRecurringInvoice.php View File

44
                 ->button()
44
                 ->button()
45
                 ->outlined()
45
                 ->outlined()
46
                 ->dropdownPlacement('bottom-end')
46
                 ->dropdownPlacement('bottom-end')
47
-                ->icon('heroicon-c-chevron-down')
47
+                ->icon('heroicon-m-chevron-down')
48
                 ->iconSize(IconSize::Small)
48
                 ->iconSize(IconSize::Small)
49
                 ->iconPosition(IconPosition::After),
49
                 ->iconPosition(IconPosition::After),
50
         ];
50
         ];

+ 4
- 0
app/Filament/Tables/Actions/EditTransactionAction.php View File

18
     {
18
     {
19
         parent::setUp();
19
         parent::setUp();
20
 
20
 
21
+        $this->label(null);
22
+
23
+        $this->slideOver();
24
+
21
         $this->modalWidth(function (): MaxWidth {
25
         $this->modalWidth(function (): MaxWidth {
22
             return match ($this->transactionType) {
26
             return match ($this->transactionType) {
23
                 TransactionType::Journal => MaxWidth::Screen,
27
                 TransactionType::Journal => MaxWidth::Screen,

+ 75
- 7
app/Models/Accounting/Transaction.php View File

6
 use App\Concerns\Blamable;
6
 use App\Concerns\Blamable;
7
 use App\Concerns\CompanyOwned;
7
 use App\Concerns\CompanyOwned;
8
 use App\Enums\Accounting\AccountCategory;
8
 use App\Enums\Accounting\AccountCategory;
9
+use App\Enums\Accounting\AccountType;
9
 use App\Enums\Accounting\PaymentMethod;
10
 use App\Enums\Accounting\PaymentMethod;
10
 use App\Enums\Accounting\TransactionType;
11
 use App\Enums\Accounting\TransactionType;
11
 use App\Models\Banking\BankAccount;
12
 use App\Models\Banking\BankAccount;
13
+use App\Models\Common\Client;
12
 use App\Models\Common\Contact;
14
 use App\Models\Common\Contact;
15
+use App\Models\Common\Vendor;
13
 use App\Observers\TransactionObserver;
16
 use App\Observers\TransactionObserver;
14
 use Database\Factories\Accounting\TransactionFactory;
17
 use Database\Factories\Accounting\TransactionFactory;
15
 use Illuminate\Database\Eloquent\Attributes\ObservedBy;
18
 use Illuminate\Database\Eloquent\Attributes\ObservedBy;
86
         return $this->morphTo();
89
         return $this->morphTo();
87
     }
90
     }
88
 
91
 
92
+    public function payeeable(): MorphTo
93
+    {
94
+        return $this->morphTo();
95
+    }
96
+
89
     public function isUncategorized(): bool
97
     public function isUncategorized(): bool
90
     {
98
     {
91
         return $this->journalEntries->contains(fn (JournalEntry $entry) => $entry->account->isUncategorized());
99
         return $this->journalEntries->contains(fn (JournalEntry $entry) => $entry->account->isUncategorized());
137
             ->toArray();
145
             ->toArray();
138
     }
146
     }
139
 
147
 
140
-    public static function getChartAccountOptions(?TransactionType $type = null, ?bool $nominalAccountsOnly = null, ?int $currentAccountId = null): array
148
+    public static function getChartAccountOptions(): array
141
     {
149
     {
142
-        $nominalAccountsOnly ??= false;
150
+        return Account::query()
151
+            ->select(['id', 'name', 'category'])
152
+            ->get()
153
+            ->groupBy(fn (Account $account) => $account->category->getPluralLabel())
154
+            ->map(fn (Collection $accounts, string $category) => $accounts->pluck('name', 'id'))
155
+            ->toArray();
156
+    }
143
 
157
 
144
-        $excludedCategory = match ($type) {
145
-            TransactionType::Deposit => AccountCategory::Expense,
146
-            TransactionType::Withdrawal => AccountCategory::Revenue,
158
+    public static function getTransactionAccountOptions(
159
+        TransactionType $type,
160
+        ?int $currentAccountId = null
161
+    ): array {
162
+        $associatedAccountTypes = match ($type) {
163
+            TransactionType::Deposit => [
164
+                AccountType::OperatingRevenue,     // Sales, service income
165
+                AccountType::NonOperatingRevenue,  // Interest, dividends received
166
+                AccountType::CurrentLiability,     // Loans received
167
+                AccountType::NonCurrentLiability,  // Long-term financing
168
+                AccountType::Equity,               // Owner contributions
169
+                AccountType::ContraExpense,        // Refunds of expenses
170
+                AccountType::UncategorizedRevenue,
171
+            ],
172
+            TransactionType::Withdrawal => [
173
+                AccountType::OperatingExpense,     // Regular business expenses
174
+                AccountType::NonOperatingExpense,  // Interest paid, etc.
175
+                AccountType::CurrentLiability,     // Loan payments
176
+                AccountType::NonCurrentLiability,  // Long-term debt payments
177
+                AccountType::Equity,               // Owner withdrawals
178
+                AccountType::ContraRevenue,        // Customer refunds, discounts
179
+                AccountType::UncategorizedExpense,
180
+            ],
147
             default => null,
181
             default => null,
148
         };
182
         };
149
 
183
 
150
         return Account::query()
184
         return Account::query()
151
-            ->when($nominalAccountsOnly, fn (Builder $query) => $query->doesntHave('bankAccount'))
152
-            ->when($excludedCategory, fn (Builder $query) => $query->whereNot('category', $excludedCategory))
185
+            ->doesntHave('adjustment')
186
+            ->doesntHave('bankAccount')
187
+            ->when($associatedAccountTypes, fn (Builder $query) => $query->whereIn('type', $associatedAccountTypes))
188
+            ->where(function (Builder $query) use ($currentAccountId) {
189
+                $query->where('archived', false)
190
+                    ->orWhere('id', $currentAccountId);
191
+            })
192
+            ->get()
193
+            ->groupBy(fn (Account $account) => $account->category->getPluralLabel())
194
+            ->map(fn (Collection $accounts, string $category) => $accounts->pluck('name', 'id'))
195
+            ->toArray();
196
+    }
197
+
198
+    public static function getJournalAccountOptions(
199
+        ?int $currentAccountId = null
200
+    ): array {
201
+        return Account::query()
153
             ->where(function (Builder $query) use ($currentAccountId) {
202
             ->where(function (Builder $query) use ($currentAccountId) {
154
                 $query->where('archived', false)
203
                 $query->where('archived', false)
155
                     ->orWhere('id', $currentAccountId);
204
                     ->orWhere('id', $currentAccountId);
173
             ->first();
222
             ->first();
174
     }
223
     }
175
 
224
 
225
+    public static function getPayeeOptions(): array
226
+    {
227
+        $clients = Client::query()
228
+            ->orderBy('name')
229
+            ->pluck('name', 'id')
230
+            ->toArray();
231
+
232
+        $vendors = Vendor::query()
233
+            ->orderBy('name')
234
+            ->pluck('name', 'id')
235
+            ->mapWithKeys(fn ($name, $id) => [-$id => $name])
236
+            ->toArray();
237
+
238
+        return [
239
+            'Clients' => $clients,
240
+            'Vendors' => $vendors,
241
+        ];
242
+    }
243
+
176
     protected static function newFactory(): Factory
244
     protected static function newFactory(): Factory
177
     {
245
     {
178
         return TransactionFactory::new();
246
         return TransactionFactory::new();

+ 6
- 0
app/Models/Common/Client.php View File

8
 use App\Models\Accounting\Estimate;
8
 use App\Models\Accounting\Estimate;
9
 use App\Models\Accounting\Invoice;
9
 use App\Models\Accounting\Invoice;
10
 use App\Models\Accounting\RecurringInvoice;
10
 use App\Models\Accounting\RecurringInvoice;
11
+use App\Models\Accounting\Transaction;
11
 use App\Models\Setting\Currency;
12
 use App\Models\Setting\Currency;
12
 use Illuminate\Database\Eloquent\Factories\HasFactory;
13
 use Illuminate\Database\Eloquent\Factories\HasFactory;
13
 use Illuminate\Database\Eloquent\Model;
14
 use Illuminate\Database\Eloquent\Model;
215
         return $this;
216
         return $this;
216
     }
217
     }
217
 
218
 
219
+    public function transactions(): MorphMany
220
+    {
221
+        return $this->morphMany(Transaction::class, 'payeeable');
222
+    }
223
+
218
     public function contacts(): MorphMany
224
     public function contacts(): MorphMany
219
     {
225
     {
220
         return $this->morphMany(Contact::class, 'contactable');
226
         return $this->morphMany(Contact::class, 'contactable');

+ 7
- 0
app/Models/Common/Vendor.php View File

7
 use App\Enums\Common\ContractorType;
7
 use App\Enums\Common\ContractorType;
8
 use App\Enums\Common\VendorType;
8
 use App\Enums\Common\VendorType;
9
 use App\Models\Accounting\Bill;
9
 use App\Models\Accounting\Bill;
10
+use App\Models\Accounting\Transaction;
10
 use App\Models\Setting\Currency;
11
 use App\Models\Setting\Currency;
11
 use Illuminate\Database\Eloquent\Factories\HasFactory;
12
 use Illuminate\Database\Eloquent\Factories\HasFactory;
12
 use Illuminate\Database\Eloquent\Model;
13
 use Illuminate\Database\Eloquent\Model;
13
 use Illuminate\Database\Eloquent\Relations\BelongsTo;
14
 use Illuminate\Database\Eloquent\Relations\BelongsTo;
14
 use Illuminate\Database\Eloquent\Relations\HasMany;
15
 use Illuminate\Database\Eloquent\Relations\HasMany;
16
+use Illuminate\Database\Eloquent\Relations\MorphMany;
15
 use Illuminate\Database\Eloquent\Relations\MorphOne;
17
 use Illuminate\Database\Eloquent\Relations\MorphOne;
16
 
18
 
17
 class Vendor extends Model
19
 class Vendor extends Model
112
         return $this->hasMany(Bill::class);
114
         return $this->hasMany(Bill::class);
113
     }
115
     }
114
 
116
 
117
+    public function transactions(): MorphMany
118
+    {
119
+        return $this->morphMany(Transaction::class, 'payeeable');
120
+    }
121
+
115
     public function currency(): BelongsTo
122
     public function currency(): BelongsTo
116
     {
123
     {
117
         return $this->belongsTo(Currency::class, 'currency_code', 'code');
124
         return $this->belongsTo(Currency::class, 'currency_code', 'code');

+ 14
- 0
app/Observers/TransactionObserver.php View File

7
 use App\Models\Accounting\Bill;
7
 use App\Models\Accounting\Bill;
8
 use App\Models\Accounting\Invoice;
8
 use App\Models\Accounting\Invoice;
9
 use App\Models\Accounting\Transaction;
9
 use App\Models\Accounting\Transaction;
10
+use App\Models\Common\Client;
11
+use App\Models\Common\Vendor;
10
 use App\Services\TransactionService;
12
 use App\Services\TransactionService;
11
 use App\Utilities\Currency\CurrencyConverter;
13
 use App\Utilities\Currency\CurrencyConverter;
12
 use Illuminate\Database\Eloquent\Builder;
14
 use Illuminate\Database\Eloquent\Builder;
26
         if ($transaction->type->isTransfer() && $transaction->description === null) {
28
         if ($transaction->type->isTransfer() && $transaction->description === null) {
27
             $transaction->description = 'Account Transfer';
29
             $transaction->description = 'Account Transfer';
28
         }
30
         }
31
+
32
+        if ($transaction->transactionable && ! $transaction->payeeable_id) {
33
+            $document = $transaction->transactionable;
34
+
35
+            if ($document instanceof Invoice) {
36
+                $transaction->payeeable_id = $document->client_id;
37
+                $transaction->payeeable_type = Client::class;
38
+            } elseif ($document instanceof Bill) {
39
+                $transaction->payeeable_id = $document->vendor_id;
40
+                $transaction->payeeable_type = Vendor::class;
41
+            }
42
+        }
29
     }
43
     }
30
 
44
 
31
     /**
45
     /**

+ 3
- 4
app/Providers/Filament/CompanyPanelProvider.php View File

92
                     ->passwordReset();
92
                     ->passwordReset();
93
             })
93
             })
94
             ->tenantMenu(false)
94
             ->tenantMenu(false)
95
-            ->plugin(
95
+            ->plugins([
96
                 FilamentCompanies::make()
96
                 FilamentCompanies::make()
97
                     ->userPanel('user')
97
                     ->userPanel('user')
98
                     ->switchCurrentCompany()
98
                     ->switchCurrentCompany()
114
                         providers: [Provider::Github],
114
                         providers: [Provider::Github],
115
                         features: [Feature::RememberSession, Feature::ProviderAvatars],
115
                         features: [Feature::RememberSession, Feature::ProviderAvatars],
116
                     ),
116
                     ),
117
-            )
118
-            ->plugin(
119
                 PanelShiftDropdown::make()
117
                 PanelShiftDropdown::make()
120
                     ->logoutItem()
118
                     ->logoutItem()
121
                     ->companySettings()
119
                     ->companySettings()
123
                         return $builder
121
                         return $builder
124
                             ->items(Account::getNavigationItems());
122
                             ->items(Account::getNavigationItems());
125
                     }),
123
                     }),
126
-            )
124
+            ])
127
             ->colors([
125
             ->colors([
128
                 'primary' => Color::Indigo,
126
                 'primary' => Color::Indigo,
129
             ])
127
             ])
173
                             ]),
171
                             ]),
174
                     ]);
172
                     ]);
175
             })
173
             })
174
+            ->globalSearch(false)
176
             ->sidebarCollapsibleOnDesktop()
175
             ->sidebarCollapsibleOnDesktop()
177
             ->viteTheme('resources/css/filament/company/theme.css')
176
             ->viteTheme('resources/css/filament/company/theme.css')
178
             ->brandLogo(static fn () => view('components.icons.logo'))
177
             ->brandLogo(static fn () => view('components.icons.logo'))

+ 1
- 1
app/Services/ReportService.php View File

282
             return [
282
             return [
283
                 'type' => 'transaction',
283
                 'type' => 'transaction',
284
                 'action' => match ($transaction->type) {
284
                 'action' => match ($transaction->type) {
285
-                    TransactionType::Journal => 'editJournalTransaction',
285
+                    TransactionType::Journal => 'editJournalEntry',
286
                     TransactionType::Transfer => 'editTransfer',
286
                     TransactionType::Transfer => 'editTransfer',
287
                     default => 'editTransaction',
287
                     default => 'editTransaction',
288
                 },
288
                 },

+ 30
- 0
database/migrations/2025_05_17_194711_add_payeeable_to_transactions_table.php View File

1
+<?php
2
+
3
+use Illuminate\Database\Migrations\Migration;
4
+use Illuminate\Database\Schema\Blueprint;
5
+use Illuminate\Support\Facades\Schema;
6
+
7
+return new class extends Migration
8
+{
9
+    /**
10
+     * Run the migrations.
11
+     */
12
+    public function up(): void
13
+    {
14
+        Schema::table('transactions', function (Blueprint $table) {
15
+            $table->after('transactionable_id', function (Blueprint $table) {
16
+                $table->nullableMorphs('payeeable');
17
+            });
18
+        });
19
+    }
20
+
21
+    /**
22
+     * Reverse the migrations.
23
+     */
24
+    public function down(): void
25
+    {
26
+        Schema::table('transactions', function (Blueprint $table) {
27
+            $table->dropMorphs('payeeable');
28
+        });
29
+    }
30
+};

+ 3
- 3
tests/Feature/Accounting/TransactionTest.php View File

297
     $undoRepeaterFake = JournalEntryRepeater::fake();
297
     $undoRepeaterFake = JournalEntryRepeater::fake();
298
 
298
 
299
     livewire(ListTransactions::class)
299
     livewire(ListTransactions::class)
300
-        ->mountAction('addJournalTransaction')
300
+        ->mountAction('addJournalEntry')
301
         ->assertActionDataSet([
301
         ->assertActionDataSet([
302
             'posted_at' => today(),
302
             'posted_at' => today(),
303
             'journalEntries' => [
303
             'journalEntries' => [
378
 
378
 
379
     livewire(ListTransactions::class)
379
     livewire(ListTransactions::class)
380
         ->assertTableActionHidden('editTransfer', $transaction)
380
         ->assertTableActionHidden('editTransfer', $transaction)
381
-        ->assertTableActionHidden('editJournalTransaction', $transaction);
381
+        ->assertTableActionHidden('editJournalEntry', $transaction);
382
 })->with([
382
 })->with([
383
     TransactionType::Deposit,
383
     TransactionType::Deposit,
384
     TransactionType::Withdrawal,
384
     TransactionType::Withdrawal,
422
 
422
 
423
     livewire(ListTransactions::class)
423
     livewire(ListTransactions::class)
424
         ->assertTableActionHidden('editTransaction', $transaction)
424
         ->assertTableActionHidden('editTransaction', $transaction)
425
-        ->assertTableActionHidden('editJournalTransaction', $transaction);
425
+        ->assertTableActionHidden('editJournalEntry', $transaction);
426
 });
426
 });
427
 
427
 
428
 it('replicates a transaction with correct journal entries', function () {
428
 it('replicates a transaction with correct journal entries', function () {

Loading…
Cancel
Save