Andrew Wallo 1 anno fa
parent
commit
24e54b0057

+ 16
- 0
app/DTO/ReportDatesDTO.php Vedi File

@@ -0,0 +1,16 @@
1
+<?php
2
+
3
+namespace App\DTO;
4
+
5
+use Illuminate\Support\Carbon;
6
+
7
+class ReportDatesDTO
8
+{
9
+    public function __construct(
10
+        public Carbon $fiscalYearStartDate,
11
+        public Carbon $fiscalYearEndDate,
12
+        public string $defaultDateRange,
13
+        public Carbon $defaultStartDate,
14
+        public Carbon $defaultEndDate,
15
+    ) {}
16
+}

+ 28
- 0
app/Factories/ReportDateFactory.php Vedi File

@@ -0,0 +1,28 @@
1
+<?php
2
+
3
+namespace App\Factories;
4
+
5
+use App\DTO\ReportDatesDTO;
6
+use App\Models\Company;
7
+use Illuminate\Support\Carbon;
8
+
9
+class ReportDateFactory
10
+{
11
+    public static function create(Company $company): ReportDatesDTO
12
+    {
13
+        $fiscalYearStartDate = Carbon::parse($company->locale->fiscalYearStartDate())->startOfDay();
14
+        $fiscalYearEndDate = Carbon::parse($company->locale->fiscalYearEndDate())->endOfDay();
15
+        $defaultDateRange = 'FY-' . now()->year;
16
+        $defaultStartDate = $fiscalYearStartDate->startOfDay();
17
+        $defaultEndDate = $fiscalYearEndDate->isFuture() ? now()->endOfDay() : $fiscalYearEndDate->endOfDay();
18
+
19
+        // Return a new DTO with the calculated values
20
+        return new ReportDatesDTO(
21
+            $fiscalYearStartDate,
22
+            $fiscalYearEndDate,
23
+            $defaultDateRange,
24
+            $defaultStartDate,
25
+            $defaultEndDate
26
+        );
27
+    }
28
+}

+ 8
- 122
app/Observers/TransactionObserver.php Vedi File

@@ -2,16 +2,15 @@
2 2
 
3 3
 namespace App\Observers;
4 4
 
5
-use App\Enums\Accounting\JournalEntryType;
6
-use App\Models\Accounting\Account;
7
-use App\Models\Accounting\JournalEntry;
8 5
 use App\Models\Accounting\Transaction;
9
-use App\Utilities\Currency\CurrencyAccessor;
10
-use App\Utilities\Currency\CurrencyConverter;
11
-use Illuminate\Support\Facades\DB;
6
+use App\Services\TransactionService;
12 7
 
13 8
 class TransactionObserver
14 9
 {
10
+    public function __construct(
11
+        protected TransactionService $transactionService,
12
+    ) {}
13
+
15 14
     /**
16 15
      * Handle the Transaction "saving" event.
17 16
      */
@@ -27,18 +26,7 @@ class TransactionObserver
27 26
      */
28 27
     public function created(Transaction $transaction): void
29 28
     {
30
-        // Additional check to avoid duplication during replication
31
-        if ($transaction->journalEntries()->exists() || $transaction->type->isJournal() || str_starts_with($transaction->description, '(Copy of)')) {
32
-            return;
33
-        }
34
-
35
-        [$debitAccount, $creditAccount] = $this->determineAccounts($transaction);
36
-
37
-        if ($debitAccount === null || $creditAccount === null) {
38
-            return;
39
-        }
40
-
41
-        $this->createJournalEntries($transaction, $debitAccount, $creditAccount);
29
+        $this->transactionService->createJournalEntries($transaction);
42 30
     }
43 31
 
44 32
     /**
@@ -48,29 +36,7 @@ class TransactionObserver
48 36
     {
49 37
         $transaction->refresh(); // DO NOT REMOVE
50 38
 
51
-        if ($transaction->type->isJournal() || $this->hasRelevantChanges($transaction) === false) {
52
-            return;
53
-        }
54
-
55
-        $journalEntries = $transaction->journalEntries;
56
-
57
-        $debitEntry = $journalEntries->where('type', JournalEntryType::Debit)->first();
58
-        $creditEntry = $journalEntries->where('type', JournalEntryType::Credit)->first();
59
-
60
-        if ($debitEntry === null || $creditEntry === null) {
61
-            return;
62
-        }
63
-
64
-        [$debitAccount, $creditAccount] = $this->determineAccounts($transaction);
65
-
66
-        if ($debitAccount === null || $creditAccount === null) {
67
-            return;
68
-        }
69
-
70
-        $convertedTransactionAmount = $this->getConvertedTransactionAmount($transaction);
71
-
72
-        $this->updateJournalEntriesForTransaction($debitEntry, $debitAccount, $convertedTransactionAmount);
73
-        $this->updateJournalEntriesForTransaction($creditEntry, $creditAccount, $convertedTransactionAmount);
39
+        $this->transactionService->updateJournalEntries($transaction);
74 40
     }
75 41
 
76 42
     /**
@@ -78,86 +44,6 @@ class TransactionObserver
78 44
      */
79 45
     public function deleting(Transaction $transaction): void
80 46
     {
81
-        DB::transaction(static function () use ($transaction) {
82
-            $transaction->journalEntries()->each(fn (JournalEntry $entry) => $entry->delete());
83
-        });
84
-    }
85
-
86
-    private function determineAccounts(Transaction $transaction): array
87
-    {
88
-        $chartAccount = $transaction->account;
89
-        $bankAccount = $transaction->bankAccount?->account;
90
-
91
-        if ($transaction->type->isTransfer()) {
92
-            // Essentially a withdrawal from the bank account and a deposit to the chart account (which is a bank account)
93
-            // Credit: bankAccount (source of funds, money is being withdrawn)
94
-            // Debit: chartAccount (destination of funds, money is being deposited)
95
-            return [$chartAccount, $bankAccount];
96
-        }
97
-
98
-        $debitAccount = $transaction->type->isWithdrawal() ? $chartAccount : $bankAccount;
99
-        $creditAccount = $transaction->type->isWithdrawal() ? $bankAccount : $chartAccount;
100
-
101
-        return [$debitAccount, $creditAccount];
102
-    }
103
-
104
-    private function createJournalEntries(Transaction $transaction, Account $debitAccount, Account $creditAccount): void
105
-    {
106
-        $convertedTransactionAmount = $this->getConvertedTransactionAmount($transaction);
107
-
108
-        $debitAccount->journalEntries()->create([
109
-            'company_id' => $transaction->company_id,
110
-            'transaction_id' => $transaction->id,
111
-            'type' => JournalEntryType::Debit,
112
-            'amount' => $convertedTransactionAmount,
113
-            'description' => $transaction->description,
114
-        ]);
115
-
116
-        $creditAccount->journalEntries()->create([
117
-            'company_id' => $transaction->company_id,
118
-            'transaction_id' => $transaction->id,
119
-            'type' => JournalEntryType::Credit,
120
-            'amount' => $convertedTransactionAmount,
121
-            'description' => $transaction->description,
122
-        ]);
123
-    }
124
-
125
-    private function getConvertedTransactionAmount(Transaction $transaction): string
126
-    {
127
-        $defaultCurrency = CurrencyAccessor::getDefaultCurrency();
128
-        $bankAccountCurrency = $transaction->bankAccount->account->currency_code;
129
-        $chartAccountCurrency = $transaction->account->currency_code;
130
-
131
-        if ($bankAccountCurrency !== $defaultCurrency) {
132
-            return $this->convertToDefaultCurrency($transaction->amount, $bankAccountCurrency, $defaultCurrency);
133
-        } elseif ($chartAccountCurrency !== $defaultCurrency) {
134
-            return $this->convertToDefaultCurrency($transaction->amount, $chartAccountCurrency, $defaultCurrency);
135
-        }
136
-
137
-        return $transaction->amount;
138
-    }
139
-
140
-    private function convertToDefaultCurrency(string $amount, string $fromCurrency, string $toCurrency): string
141
-    {
142
-        $amountInCents = CurrencyConverter::prepareForAccessor($amount, $fromCurrency);
143
-
144
-        $convertedAmountInCents = CurrencyConverter::convertBalance($amountInCents, $fromCurrency, $toCurrency);
145
-
146
-        return CurrencyConverter::prepareForMutator($convertedAmountInCents, $toCurrency);
147
-    }
148
-
149
-    private function hasRelevantChanges(Transaction $transaction): bool
150
-    {
151
-        return $transaction->wasChanged(['amount', 'account_id', 'bank_account_id', 'type']);
152
-    }
153
-
154
-    private function updateJournalEntriesForTransaction(JournalEntry $journalEntry, Account $account, string $convertedTransactionAmount): void
155
-    {
156
-        DB::transaction(static function () use ($journalEntry, $account, $convertedTransactionAmount) {
157
-            $journalEntry->update([
158
-                'account_id' => $account->id,
159
-                'amount' => $convertedTransactionAmount,
160
-            ]);
161
-        });
47
+        $this->transactionService->deleteJournalEntries($transaction);
162 48
     }
163 49
 }

+ 137
- 0
app/Services/TransactionService.php Vedi File

@@ -4,12 +4,17 @@ namespace App\Services;
4 4
 
5 5
 use App\Enums\Accounting\AccountCategory;
6 6
 use App\Enums\Accounting\AccountType;
7
+use App\Enums\Accounting\JournalEntryType;
7 8
 use App\Enums\Accounting\TransactionType;
8 9
 use App\Models\Accounting\Account;
10
+use App\Models\Accounting\JournalEntry;
9 11
 use App\Models\Accounting\Transaction;
10 12
 use App\Models\Banking\BankAccount;
11 13
 use App\Models\Company;
14
+use App\Utilities\Currency\CurrencyAccessor;
15
+use App\Utilities\Currency\CurrencyConverter;
12 16
 use Illuminate\Support\Carbon;
17
+use Illuminate\Support\Facades\DB;
13 18
 
14 19
 class TransactionService
15 20
 {
@@ -142,4 +147,136 @@ class TransactionService
142 147
             ->where('name', $name)
143 148
             ->firstOrFail();
144 149
     }
150
+
151
+    public function createJournalEntries(Transaction $transaction): void
152
+    {
153
+        // Additional check to avoid duplication during replication
154
+        if ($transaction->journalEntries()->exists() || $transaction->type->isJournal() || str_starts_with($transaction->description, '(Copy of)')) {
155
+            return;
156
+        }
157
+
158
+        [$debitAccount, $creditAccount] = $this->determineAccounts($transaction);
159
+
160
+        if ($debitAccount === null || $creditAccount === null) {
161
+            return;
162
+        }
163
+
164
+        $this->createJournalEntriesForTransaction($transaction, $debitAccount, $creditAccount);
165
+    }
166
+
167
+    public function updateJournalEntries(Transaction $transaction): void
168
+    {
169
+        if ($transaction->type->isJournal() || $this->hasRelevantChanges($transaction) === false) {
170
+            return;
171
+        }
172
+
173
+        $journalEntries = $transaction->journalEntries;
174
+
175
+        $debitEntry = $journalEntries->where('type', JournalEntryType::Debit)->first();
176
+        $creditEntry = $journalEntries->where('type', JournalEntryType::Credit)->first();
177
+
178
+        if ($debitEntry === null || $creditEntry === null) {
179
+            return;
180
+        }
181
+
182
+        [$debitAccount, $creditAccount] = $this->determineAccounts($transaction);
183
+
184
+        if ($debitAccount === null || $creditAccount === null) {
185
+            return;
186
+        }
187
+
188
+        $convertedTransactionAmount = $this->getConvertedTransactionAmount($transaction);
189
+
190
+        DB::transaction(function () use ($debitEntry, $debitAccount, $convertedTransactionAmount, $creditEntry, $creditAccount) {
191
+            $this->updateJournalEntryForTransaction($debitEntry, $debitAccount, $convertedTransactionAmount);
192
+            $this->updateJournalEntryForTransaction($creditEntry, $creditAccount, $convertedTransactionAmount);
193
+        });
194
+    }
195
+
196
+    public function deleteJournalEntries(Transaction $transaction): void
197
+    {
198
+        DB::transaction(static function () use ($transaction) {
199
+            $transaction->journalEntries()->each(fn (JournalEntry $entry) => $entry->delete());
200
+        });
201
+    }
202
+
203
+    private function determineAccounts(Transaction $transaction): array
204
+    {
205
+        $chartAccount = $transaction->account;
206
+        $bankAccount = $transaction->bankAccount?->account;
207
+
208
+        if ($transaction->type->isTransfer()) {
209
+            // Essentially a withdrawal from the bank account and a deposit to the chart account (which is a bank account)
210
+            // Credit: bankAccount (source of funds, money is being withdrawn)
211
+            // Debit: chartAccount (destination of funds, money is being deposited)
212
+            return [$chartAccount, $bankAccount];
213
+        }
214
+
215
+        $debitAccount = $transaction->type->isWithdrawal() ? $chartAccount : $bankAccount;
216
+        $creditAccount = $transaction->type->isWithdrawal() ? $bankAccount : $chartAccount;
217
+
218
+        return [$debitAccount, $creditAccount];
219
+    }
220
+
221
+    private function createJournalEntriesForTransaction(Transaction $transaction, Account $debitAccount, Account $creditAccount): void
222
+    {
223
+        $convertedTransactionAmount = $this->getConvertedTransactionAmount($transaction);
224
+
225
+        DB::transaction(function () use ($debitAccount, $transaction, $convertedTransactionAmount, $creditAccount) {
226
+            $debitAccount->journalEntries()->create([
227
+                'company_id' => $transaction->company_id,
228
+                'transaction_id' => $transaction->id,
229
+                'type' => JournalEntryType::Debit,
230
+                'amount' => $convertedTransactionAmount,
231
+                'description' => $transaction->description,
232
+            ]);
233
+
234
+            $creditAccount->journalEntries()->create([
235
+                'company_id' => $transaction->company_id,
236
+                'transaction_id' => $transaction->id,
237
+                'type' => JournalEntryType::Credit,
238
+                'amount' => $convertedTransactionAmount,
239
+                'description' => $transaction->description,
240
+            ]);
241
+        });
242
+    }
243
+
244
+    private function getConvertedTransactionAmount(Transaction $transaction): string
245
+    {
246
+        $defaultCurrency = CurrencyAccessor::getDefaultCurrency();
247
+        $bankAccountCurrency = $transaction->bankAccount->account->currency_code;
248
+        $chartAccountCurrency = $transaction->account->currency_code;
249
+
250
+        if ($bankAccountCurrency !== $defaultCurrency) {
251
+            return $this->convertToDefaultCurrency($transaction->amount, $bankAccountCurrency, $defaultCurrency);
252
+        } elseif ($chartAccountCurrency !== $defaultCurrency) {
253
+            return $this->convertToDefaultCurrency($transaction->amount, $chartAccountCurrency, $defaultCurrency);
254
+        }
255
+
256
+        return $transaction->amount;
257
+    }
258
+
259
+    private function convertToDefaultCurrency(string $amount, string $fromCurrency, string $toCurrency): string
260
+    {
261
+        $amountInCents = CurrencyConverter::prepareForAccessor($amount, $fromCurrency);
262
+
263
+        $convertedAmountInCents = CurrencyConverter::convertBalance($amountInCents, $fromCurrency, $toCurrency);
264
+
265
+        return CurrencyConverter::prepareForMutator($convertedAmountInCents, $toCurrency);
266
+    }
267
+
268
+    private function hasRelevantChanges(Transaction $transaction): bool
269
+    {
270
+        return $transaction->wasChanged(['amount', 'account_id', 'bank_account_id', 'type']);
271
+    }
272
+
273
+    private function updateJournalEntryForTransaction(JournalEntry $journalEntry, Account $account, string $convertedTransactionAmount): void
274
+    {
275
+        DB::transaction(static function () use ($journalEntry, $account, $convertedTransactionAmount) {
276
+            $journalEntry->update([
277
+                'account_id' => $account->id,
278
+                'amount' => $convertedTransactionAmount,
279
+            ]);
280
+        });
281
+    }
145 282
 }

+ 61
- 0
app/Testing/TestsReport.php Vedi File

@@ -0,0 +1,61 @@
1
+<?php
2
+
3
+namespace App\Testing;
4
+
5
+use App\Contracts\ExportableReport;
6
+use Closure;
7
+use Livewire\Features\SupportTesting\Testable;
8
+
9
+/**
10
+ * @mixin Testable
11
+ */
12
+class TestsReport
13
+{
14
+    /**
15
+     * Asserts the report table data.
16
+     */
17
+    public function assertReportTableData(): Closure
18
+    {
19
+        return function (): static {
20
+            /** @var ExportableReport $report */
21
+            $report = $this->get('report');
22
+
23
+            // Assert headers
24
+            $this->assertSeeTextInOrder($report->getHeaders());
25
+
26
+            // Assert categories, headers, data, and summaries
27
+            $categories = $report->getCategories();
28
+            foreach ($categories as $category) {
29
+                $header = $category->header;
30
+                $data = $category->data;
31
+                $summary = $category->summary;
32
+
33
+                // Assert header
34
+                $this->assertSeeTextInOrder($header);
35
+
36
+                // Assert data rows
37
+                foreach ($data as $row) {
38
+                    $flatRow = [];
39
+
40
+                    foreach ($row as $value) {
41
+                        if (is_array($value)) {
42
+                            $flatRow[] = $value['name'];
43
+                        } else {
44
+                            $flatRow[] = $value;
45
+                        }
46
+                    }
47
+
48
+                    $this->assertSeeTextInOrder($flatRow);
49
+                }
50
+
51
+                // Assert summary
52
+                $this->assertSeeTextInOrder($summary);
53
+            }
54
+
55
+            // Assert overall totals
56
+            $this->assertSeeTextInOrder($report->getOverallTotals());
57
+
58
+            return $this;
59
+        };
60
+    }
61
+}

+ 62
- 62
composer.lock Vedi File

@@ -302,16 +302,16 @@
302 302
         },
303 303
         {
304 304
             "name": "anourvalar/eloquent-serialize",
305
-            "version": "1.2.24",
305
+            "version": "1.2.25",
306 306
             "source": {
307 307
                 "type": "git",
308 308
                 "url": "https://github.com/AnourValar/eloquent-serialize.git",
309
-                "reference": "77e3fc7da44fa96b6148a1613dd76fe954a5f279"
309
+                "reference": "6d7a868ae4218b9d7796334ff9a17e1539bad48a"
310 310
             },
311 311
             "dist": {
312 312
                 "type": "zip",
313
-                "url": "https://api.github.com/repos/AnourValar/eloquent-serialize/zipball/77e3fc7da44fa96b6148a1613dd76fe954a5f279",
314
-                "reference": "77e3fc7da44fa96b6148a1613dd76fe954a5f279",
313
+                "url": "https://api.github.com/repos/AnourValar/eloquent-serialize/zipball/6d7a868ae4218b9d7796334ff9a17e1539bad48a",
314
+                "reference": "6d7a868ae4218b9d7796334ff9a17e1539bad48a",
315 315
                 "shasum": ""
316 316
             },
317 317
             "require": {
@@ -362,9 +362,9 @@
362 362
             ],
363 363
             "support": {
364 364
                 "issues": "https://github.com/AnourValar/eloquent-serialize/issues",
365
-                "source": "https://github.com/AnourValar/eloquent-serialize/tree/1.2.24"
365
+                "source": "https://github.com/AnourValar/eloquent-serialize/tree/1.2.25"
366 366
             },
367
-            "time": "2024-09-08T15:57:08+00:00"
367
+            "time": "2024-09-16T12:59:37+00:00"
368 368
         },
369 369
         {
370 370
             "name": "awcodes/filament-table-repeater",
@@ -497,16 +497,16 @@
497 497
         },
498 498
         {
499 499
             "name": "aws/aws-sdk-php",
500
-            "version": "3.321.11",
500
+            "version": "3.321.13",
501 501
             "source": {
502 502
                 "type": "git",
503 503
                 "url": "https://github.com/aws/aws-sdk-php.git",
504
-                "reference": "bbd357d246350ffcd0dd8df30951d2d46c5ddadb"
504
+                "reference": "6330c3367f303ef4430fca605df9e411abe34d48"
505 505
             },
506 506
             "dist": {
507 507
                 "type": "zip",
508
-                "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/bbd357d246350ffcd0dd8df30951d2d46c5ddadb",
509
-                "reference": "bbd357d246350ffcd0dd8df30951d2d46c5ddadb",
508
+                "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/6330c3367f303ef4430fca605df9e411abe34d48",
509
+                "reference": "6330c3367f303ef4430fca605df9e411abe34d48",
510 510
                 "shasum": ""
511 511
             },
512 512
             "require": {
@@ -589,9 +589,9 @@
589 589
             "support": {
590 590
                 "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
591 591
                 "issues": "https://github.com/aws/aws-sdk-php/issues",
592
-                "source": "https://github.com/aws/aws-sdk-php/tree/3.321.11"
592
+                "source": "https://github.com/aws/aws-sdk-php/tree/3.321.13"
593 593
             },
594
-            "time": "2024-09-13T18:05:10+00:00"
594
+            "time": "2024-09-17T18:08:34+00:00"
595 595
         },
596 596
         {
597 597
             "name": "aws/aws-sdk-php-laravel",
@@ -1738,16 +1738,16 @@
1738 1738
         },
1739 1739
         {
1740 1740
             "name": "filament/actions",
1741
-            "version": "v3.2.112",
1741
+            "version": "v3.2.113",
1742 1742
             "source": {
1743 1743
                 "type": "git",
1744 1744
                 "url": "https://github.com/filamentphp/actions.git",
1745
-                "reference": "df3310607b49dad302b03516c558c93cb82c5164"
1745
+                "reference": "4cf93bf9ff04a76a9256ce6df88216583aeccb15"
1746 1746
             },
1747 1747
             "dist": {
1748 1748
                 "type": "zip",
1749
-                "url": "https://api.github.com/repos/filamentphp/actions/zipball/df3310607b49dad302b03516c558c93cb82c5164",
1750
-                "reference": "df3310607b49dad302b03516c558c93cb82c5164",
1749
+                "url": "https://api.github.com/repos/filamentphp/actions/zipball/4cf93bf9ff04a76a9256ce6df88216583aeccb15",
1750
+                "reference": "4cf93bf9ff04a76a9256ce6df88216583aeccb15",
1751 1751
                 "shasum": ""
1752 1752
             },
1753 1753
             "require": {
@@ -1787,20 +1787,20 @@
1787 1787
                 "issues": "https://github.com/filamentphp/filament/issues",
1788 1788
                 "source": "https://github.com/filamentphp/filament"
1789 1789
             },
1790
-            "time": "2024-09-11T08:25:31+00:00"
1790
+            "time": "2024-09-17T08:30:20+00:00"
1791 1791
         },
1792 1792
         {
1793 1793
             "name": "filament/filament",
1794
-            "version": "v3.2.112",
1794
+            "version": "v3.2.113",
1795 1795
             "source": {
1796 1796
                 "type": "git",
1797 1797
                 "url": "https://github.com/filamentphp/panels.git",
1798
-                "reference": "86aa182deceedce5970560c60ceae30c2c40632d"
1798
+                "reference": "cea015f11b3d1b41bbf826e6f724e444e5fda3cb"
1799 1799
             },
1800 1800
             "dist": {
1801 1801
                 "type": "zip",
1802
-                "url": "https://api.github.com/repos/filamentphp/panels/zipball/86aa182deceedce5970560c60ceae30c2c40632d",
1803
-                "reference": "86aa182deceedce5970560c60ceae30c2c40632d",
1802
+                "url": "https://api.github.com/repos/filamentphp/panels/zipball/cea015f11b3d1b41bbf826e6f724e444e5fda3cb",
1803
+                "reference": "cea015f11b3d1b41bbf826e6f724e444e5fda3cb",
1804 1804
                 "shasum": ""
1805 1805
             },
1806 1806
             "require": {
@@ -1852,20 +1852,20 @@
1852 1852
                 "issues": "https://github.com/filamentphp/filament/issues",
1853 1853
                 "source": "https://github.com/filamentphp/filament"
1854 1854
             },
1855
-            "time": "2024-09-11T08:25:51+00:00"
1855
+            "time": "2024-09-17T08:30:28+00:00"
1856 1856
         },
1857 1857
         {
1858 1858
             "name": "filament/forms",
1859
-            "version": "v3.2.112",
1859
+            "version": "v3.2.113",
1860 1860
             "source": {
1861 1861
                 "type": "git",
1862 1862
                 "url": "https://github.com/filamentphp/forms.git",
1863
-                "reference": "99d72777f1e6dc5d42d936e7deb53148e4233ec3"
1863
+                "reference": "46a42dbc18f9273a3a59c54e94222fa62855c702"
1864 1864
             },
1865 1865
             "dist": {
1866 1866
                 "type": "zip",
1867
-                "url": "https://api.github.com/repos/filamentphp/forms/zipball/99d72777f1e6dc5d42d936e7deb53148e4233ec3",
1868
-                "reference": "99d72777f1e6dc5d42d936e7deb53148e4233ec3",
1867
+                "url": "https://api.github.com/repos/filamentphp/forms/zipball/46a42dbc18f9273a3a59c54e94222fa62855c702",
1868
+                "reference": "46a42dbc18f9273a3a59c54e94222fa62855c702",
1869 1869
                 "shasum": ""
1870 1870
             },
1871 1871
             "require": {
@@ -1908,20 +1908,20 @@
1908 1908
                 "issues": "https://github.com/filamentphp/filament/issues",
1909 1909
                 "source": "https://github.com/filamentphp/filament"
1910 1910
             },
1911
-            "time": "2024-09-12T12:27:13+00:00"
1911
+            "time": "2024-09-17T08:30:15+00:00"
1912 1912
         },
1913 1913
         {
1914 1914
             "name": "filament/infolists",
1915
-            "version": "v3.2.112",
1915
+            "version": "v3.2.113",
1916 1916
             "source": {
1917 1917
                 "type": "git",
1918 1918
                 "url": "https://github.com/filamentphp/infolists.git",
1919
-                "reference": "e50bd9a5fc623320bd79508e3bfb72ff9e309edf"
1919
+                "reference": "dd6e2319aea92c5444c52792c750edfeb057f62a"
1920 1920
             },
1921 1921
             "dist": {
1922 1922
                 "type": "zip",
1923
-                "url": "https://api.github.com/repos/filamentphp/infolists/zipball/e50bd9a5fc623320bd79508e3bfb72ff9e309edf",
1924
-                "reference": "e50bd9a5fc623320bd79508e3bfb72ff9e309edf",
1923
+                "url": "https://api.github.com/repos/filamentphp/infolists/zipball/dd6e2319aea92c5444c52792c750edfeb057f62a",
1924
+                "reference": "dd6e2319aea92c5444c52792c750edfeb057f62a",
1925 1925
                 "shasum": ""
1926 1926
             },
1927 1927
             "require": {
@@ -1959,11 +1959,11 @@
1959 1959
                 "issues": "https://github.com/filamentphp/filament/issues",
1960 1960
                 "source": "https://github.com/filamentphp/filament"
1961 1961
             },
1962
-            "time": "2024-09-11T08:25:25+00:00"
1962
+            "time": "2024-09-17T08:30:15+00:00"
1963 1963
         },
1964 1964
         {
1965 1965
             "name": "filament/notifications",
1966
-            "version": "v3.2.112",
1966
+            "version": "v3.2.113",
1967 1967
             "source": {
1968 1968
                 "type": "git",
1969 1969
                 "url": "https://github.com/filamentphp/notifications.git",
@@ -2015,16 +2015,16 @@
2015 2015
         },
2016 2016
         {
2017 2017
             "name": "filament/support",
2018
-            "version": "v3.2.112",
2018
+            "version": "v3.2.113",
2019 2019
             "source": {
2020 2020
                 "type": "git",
2021 2021
                 "url": "https://github.com/filamentphp/support.git",
2022
-                "reference": "d07086506d39f318398c13a0b8d689f75cbc14d6"
2022
+                "reference": "d2825e1c116e50d440bb3e7ac56ebcb8d1ddf184"
2023 2023
             },
2024 2024
             "dist": {
2025 2025
                 "type": "zip",
2026
-                "url": "https://api.github.com/repos/filamentphp/support/zipball/d07086506d39f318398c13a0b8d689f75cbc14d6",
2027
-                "reference": "d07086506d39f318398c13a0b8d689f75cbc14d6",
2026
+                "url": "https://api.github.com/repos/filamentphp/support/zipball/d2825e1c116e50d440bb3e7ac56ebcb8d1ddf184",
2027
+                "reference": "d2825e1c116e50d440bb3e7ac56ebcb8d1ddf184",
2028 2028
                 "shasum": ""
2029 2029
             },
2030 2030
             "require": {
@@ -2070,20 +2070,20 @@
2070 2070
                 "issues": "https://github.com/filamentphp/filament/issues",
2071 2071
                 "source": "https://github.com/filamentphp/filament"
2072 2072
             },
2073
-            "time": "2024-09-11T08:25:46+00:00"
2073
+            "time": "2024-09-17T08:30:37+00:00"
2074 2074
         },
2075 2075
         {
2076 2076
             "name": "filament/tables",
2077
-            "version": "v3.2.112",
2077
+            "version": "v3.2.113",
2078 2078
             "source": {
2079 2079
                 "type": "git",
2080 2080
                 "url": "https://github.com/filamentphp/tables.git",
2081
-                "reference": "4285a031dd36250a86710631a5b1fea1372097a1"
2081
+                "reference": "75acf6f38a8ccfded57dc62bc3af0dd0bb04069d"
2082 2082
             },
2083 2083
             "dist": {
2084 2084
                 "type": "zip",
2085
-                "url": "https://api.github.com/repos/filamentphp/tables/zipball/4285a031dd36250a86710631a5b1fea1372097a1",
2086
-                "reference": "4285a031dd36250a86710631a5b1fea1372097a1",
2085
+                "url": "https://api.github.com/repos/filamentphp/tables/zipball/75acf6f38a8ccfded57dc62bc3af0dd0bb04069d",
2086
+                "reference": "75acf6f38a8ccfded57dc62bc3af0dd0bb04069d",
2087 2087
                 "shasum": ""
2088 2088
             },
2089 2089
             "require": {
@@ -2122,20 +2122,20 @@
2122 2122
                 "issues": "https://github.com/filamentphp/filament/issues",
2123 2123
                 "source": "https://github.com/filamentphp/filament"
2124 2124
             },
2125
-            "time": "2024-09-11T08:25:43+00:00"
2125
+            "time": "2024-09-17T08:30:46+00:00"
2126 2126
         },
2127 2127
         {
2128 2128
             "name": "filament/widgets",
2129
-            "version": "v3.2.112",
2129
+            "version": "v3.2.113",
2130 2130
             "source": {
2131 2131
                 "type": "git",
2132 2132
                 "url": "https://github.com/filamentphp/widgets.git",
2133
-                "reference": "909fc82bae2cf41d70b3cd7dda8982245b2ea723"
2133
+                "reference": "d168a97f0861b7964437652e64fa5af83212d1f4"
2134 2134
             },
2135 2135
             "dist": {
2136 2136
                 "type": "zip",
2137
-                "url": "https://api.github.com/repos/filamentphp/widgets/zipball/909fc82bae2cf41d70b3cd7dda8982245b2ea723",
2138
-                "reference": "909fc82bae2cf41d70b3cd7dda8982245b2ea723",
2137
+                "url": "https://api.github.com/repos/filamentphp/widgets/zipball/d168a97f0861b7964437652e64fa5af83212d1f4",
2138
+                "reference": "d168a97f0861b7964437652e64fa5af83212d1f4",
2139 2139
                 "shasum": ""
2140 2140
             },
2141 2141
             "require": {
@@ -2166,7 +2166,7 @@
2166 2166
                 "issues": "https://github.com/filamentphp/filament/issues",
2167 2167
                 "source": "https://github.com/filamentphp/filament"
2168 2168
             },
2169
-            "time": "2024-07-31T11:53:30+00:00"
2169
+            "time": "2024-09-17T08:30:51+00:00"
2170 2170
         },
2171 2171
         {
2172 2172
             "name": "firebase/php-jwt",
@@ -5342,16 +5342,16 @@
5342 5342
         },
5343 5343
         {
5344 5344
             "name": "phpseclib/phpseclib",
5345
-            "version": "3.0.41",
5345
+            "version": "3.0.42",
5346 5346
             "source": {
5347 5347
                 "type": "git",
5348 5348
                 "url": "https://github.com/phpseclib/phpseclib.git",
5349
-                "reference": "621c73f7dcb310b61de34d1da4c4204e8ace6ceb"
5349
+                "reference": "db92f1b1987b12b13f248fe76c3a52cadb67bb98"
5350 5350
             },
5351 5351
             "dist": {
5352 5352
                 "type": "zip",
5353
-                "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/621c73f7dcb310b61de34d1da4c4204e8ace6ceb",
5354
-                "reference": "621c73f7dcb310b61de34d1da4c4204e8ace6ceb",
5353
+                "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/db92f1b1987b12b13f248fe76c3a52cadb67bb98",
5354
+                "reference": "db92f1b1987b12b13f248fe76c3a52cadb67bb98",
5355 5355
                 "shasum": ""
5356 5356
             },
5357 5357
             "require": {
@@ -5432,7 +5432,7 @@
5432 5432
             ],
5433 5433
             "support": {
5434 5434
                 "issues": "https://github.com/phpseclib/phpseclib/issues",
5435
-                "source": "https://github.com/phpseclib/phpseclib/tree/3.0.41"
5435
+                "source": "https://github.com/phpseclib/phpseclib/tree/3.0.42"
5436 5436
             },
5437 5437
             "funding": [
5438 5438
                 {
@@ -5448,7 +5448,7 @@
5448 5448
                     "type": "tidelift"
5449 5449
                 }
5450 5450
             ],
5451
-            "time": "2024-08-12T00:13:54+00:00"
5451
+            "time": "2024-09-16T03:06:04+00:00"
5452 5452
         },
5453 5453
         {
5454 5454
             "name": "psr/cache",
@@ -12208,28 +12208,28 @@
12208 12208
         },
12209 12209
         {
12210 12210
             "name": "sebastian/type",
12211
-            "version": "5.0.1",
12211
+            "version": "5.1.0",
12212 12212
             "source": {
12213 12213
                 "type": "git",
12214 12214
                 "url": "https://github.com/sebastianbergmann/type.git",
12215
-                "reference": "fb6a6566f9589e86661291d13eba708cce5eb4aa"
12215
+                "reference": "461b9c5da241511a2a0e8f240814fb23ce5c0aac"
12216 12216
             },
12217 12217
             "dist": {
12218 12218
                 "type": "zip",
12219
-                "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/fb6a6566f9589e86661291d13eba708cce5eb4aa",
12220
-                "reference": "fb6a6566f9589e86661291d13eba708cce5eb4aa",
12219
+                "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/461b9c5da241511a2a0e8f240814fb23ce5c0aac",
12220
+                "reference": "461b9c5da241511a2a0e8f240814fb23ce5c0aac",
12221 12221
                 "shasum": ""
12222 12222
             },
12223 12223
             "require": {
12224 12224
                 "php": ">=8.2"
12225 12225
             },
12226 12226
             "require-dev": {
12227
-                "phpunit/phpunit": "^11.0"
12227
+                "phpunit/phpunit": "^11.3"
12228 12228
             },
12229 12229
             "type": "library",
12230 12230
             "extra": {
12231 12231
                 "branch-alias": {
12232
-                    "dev-main": "5.0-dev"
12232
+                    "dev-main": "5.1-dev"
12233 12233
                 }
12234 12234
             },
12235 12235
             "autoload": {
@@ -12253,7 +12253,7 @@
12253 12253
             "support": {
12254 12254
                 "issues": "https://github.com/sebastianbergmann/type/issues",
12255 12255
                 "security": "https://github.com/sebastianbergmann/type/security/policy",
12256
-                "source": "https://github.com/sebastianbergmann/type/tree/5.0.1"
12256
+                "source": "https://github.com/sebastianbergmann/type/tree/5.1.0"
12257 12257
             },
12258 12258
             "funding": [
12259 12259
                 {
@@ -12261,7 +12261,7 @@
12261 12261
                     "type": "github"
12262 12262
                 }
12263 12263
             ],
12264
-            "time": "2024-07-03T05:11:49+00:00"
12264
+            "time": "2024-09-17T13:12:04+00:00"
12265 12265
         },
12266 12266
         {
12267 12267
             "name": "sebastian/version",

+ 59
- 29
database/factories/Accounting/TransactionFactory.php Vedi File

@@ -2,13 +2,16 @@
2 2
 
3 3
 namespace Database\Factories\Accounting;
4 4
 
5
-use App\Enums\Accounting\JournalEntryType;
5
+use App\Enums\Accounting\AccountType;
6 6
 use App\Enums\Accounting\TransactionType;
7 7
 use App\Models\Accounting\Account;
8 8
 use App\Models\Accounting\Transaction;
9 9
 use App\Models\Banking\BankAccount;
10 10
 use App\Models\Company;
11
+use App\Models\Setting\CompanyDefault;
12
+use App\Services\TransactionService;
11 13
 use Illuminate\Database\Eloquent\Factories\Factory;
14
+use Illuminate\Support\Facades\DB;
12 15
 
13 16
 /**
14 17
  * @extends Factory<Transaction>
@@ -45,35 +48,9 @@ class TransactionFactory extends Factory
45 48
     public function configure(): static
46 49
     {
47 50
         return $this->afterCreating(function (Transaction $transaction) {
48
-            $chartAccount = $transaction->account;
49
-            $bankAccount = $transaction->bankAccount->account;
50
-
51
-            $debitAccount = $transaction->type->isWithdrawal() ? $chartAccount : $bankAccount;
52
-            $creditAccount = $transaction->type->isWithdrawal() ? $bankAccount : $chartAccount;
53
-
54
-            if ($debitAccount === null || $creditAccount === null) {
55
-                return;
51
+            if (DB::getDefaultConnection() === 'sqlite') {
52
+                app(TransactionService::class)->createJournalEntries($transaction);
56 53
             }
57
-
58
-            $debitAccount->journalEntries()->create([
59
-                'company_id' => $transaction->company_id,
60
-                'transaction_id' => $transaction->id,
61
-                'type' => JournalEntryType::Debit,
62
-                'amount' => $transaction->amount,
63
-                'description' => $transaction->description,
64
-                'created_by' => $transaction->created_by,
65
-                'updated_by' => $transaction->updated_by,
66
-            ]);
67
-
68
-            $creditAccount->journalEntries()->create([
69
-                'company_id' => $transaction->company_id,
70
-                'transaction_id' => $transaction->id,
71
-                'type' => JournalEntryType::Credit,
72
-                'amount' => $transaction->amount,
73
-                'description' => $transaction->description,
74
-                'created_by' => $transaction->created_by,
75
-                'updated_by' => $transaction->updated_by,
76
-            ]);
77 54
         });
78 55
     }
79 56
 
@@ -111,4 +88,57 @@ class TransactionFactory extends Factory
111 88
             ];
112 89
         });
113 90
     }
91
+
92
+    public function forDefaultBankAccount(): static
93
+    {
94
+        return $this->state(function (array $attributes) {
95
+            $defaultBankAccount = CompanyDefault::first()->bankAccount;
96
+
97
+            return [
98
+                'bank_account_id' => $defaultBankAccount->id,
99
+            ];
100
+        });
101
+    }
102
+
103
+    public function forUncategorizedRevenue(): static
104
+    {
105
+        return $this->state(function (array $attributes) {
106
+            $account = Account::where('type', AccountType::UncategorizedRevenue)->firstOrFail();
107
+
108
+            return [
109
+                'account_id' => $account->id,
110
+            ];
111
+        });
112
+    }
113
+
114
+    public function forUncategorizedExpense(): static
115
+    {
116
+        return $this->state(function (array $attributes) {
117
+            $account = Account::where('type', AccountType::UncategorizedExpense)->firstOrFail();
118
+
119
+            return [
120
+                'account_id' => $account->id,
121
+            ];
122
+        });
123
+    }
124
+
125
+    public function asDeposit(int $amount): static
126
+    {
127
+        return $this->state(function () use ($amount) {
128
+            return [
129
+                'type' => TransactionType::Deposit,
130
+                'amount' => $amount,
131
+            ];
132
+        });
133
+    }
134
+
135
+    public function asWithdrawal(int $amount): static
136
+    {
137
+        return $this->state(function () use ($amount) {
138
+            return [
139
+                'type' => TransactionType::Withdrawal,
140
+                'amount' => $amount,
141
+            ];
142
+        });
143
+    }
114 144
 }

+ 9
- 9
package-lock.json Vedi File

@@ -1159,9 +1159,9 @@
1159 1159
             "license": "MIT"
1160 1160
         },
1161 1161
         "node_modules/electron-to-chromium": {
1162
-            "version": "1.5.23",
1163
-            "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.23.tgz",
1164
-            "integrity": "sha512-mBhODedOXg4v5QWwl21DjM5amzjmI1zw9EPrPK/5Wx7C8jt33bpZNrC7OhHUG3pxRtbLpr3W2dXT+Ph1SsfRZA==",
1162
+            "version": "1.5.25",
1163
+            "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.25.tgz",
1164
+            "integrity": "sha512-kMb204zvK3PsSlgvvwzI3wBIcAw15tRkYk+NQdsjdDtcQWTp2RABbMQ9rUBy8KNEOM+/E6ep+XC3AykiWZld4g==",
1165 1165
             "dev": true,
1166 1166
             "license": "ISC"
1167 1167
         },
@@ -2417,9 +2417,9 @@
2417 2417
             }
2418 2418
         },
2419 2419
         "node_modules/tailwindcss": {
2420
-            "version": "3.4.11",
2421
-            "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.11.tgz",
2422
-            "integrity": "sha512-qhEuBcLemjSJk5ajccN9xJFtM/h0AVCPaA6C92jNP+M2J8kX+eMJHI7R2HFKUvvAsMpcfLILMCFYSeDwpMmlUg==",
2420
+            "version": "3.4.12",
2421
+            "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.12.tgz",
2422
+            "integrity": "sha512-Htf/gHj2+soPb9UayUNci/Ja3d8pTmu9ONTfh4QY8r3MATTZOzmv6UYWF7ZwikEIC8okpfqmGqrmDehua8mF8w==",
2423 2423
             "dev": true,
2424 2424
             "license": "MIT",
2425 2425
             "dependencies": {
@@ -2550,9 +2550,9 @@
2550 2550
             "license": "MIT"
2551 2551
         },
2552 2552
         "node_modules/vite": {
2553
-            "version": "5.4.5",
2554
-            "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.5.tgz",
2555
-            "integrity": "sha512-pXqR0qtb2bTwLkev4SE3r4abCNioP3GkjvIDLlzziPpXtHgiJIjuKl+1GN6ESOT3wMjG3JTeARopj2SwYaHTOA==",
2553
+            "version": "5.4.6",
2554
+            "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.6.tgz",
2555
+            "integrity": "sha512-IeL5f8OO5nylsgzd9tq4qD2QqI0k2CQLGrWD0rCN0EQJZpBK5vJAx0I+GDkMOXxQX/OfFHMuLIx6ddAxGX/k+Q==",
2556 2556
             "dev": true,
2557 2557
             "license": "MIT",
2558 2558
             "dependencies": {

+ 1
- 1
resources/css/filament/company/theme.css Vedi File

@@ -4,7 +4,7 @@
4 4
 
5 5
 @config './tailwind.config.js';
6 6
 
7
-.fi-fo-field-wrp.report-hidden-label div.flex.items-center {
7
+.fi-fo-field-wrp.report-hidden-label > div.grid.gap-y-2 > div.flex.items-center {
8 8
     @apply hidden;
9 9
 }
10 10
 

+ 47
- 175
tests/Feature/Reports/TrialBalanceReportTest.php Vedi File

@@ -1,237 +1,109 @@
1 1
 <?php
2 2
 
3
-use App\Contracts\ExportableReport;
4
-use App\Enums\Accounting\AccountCategory;
3
+use App\Factories\ReportDateFactory;
5 4
 use App\Filament\Company\Pages\Reports\TrialBalance;
6 5
 use App\Models\Accounting\Transaction;
7
-use App\Services\AccountService;
8
-use App\Services\ReportService;
9
-use Filament\Facades\Filament;
10
-use Illuminate\Support\Carbon;
6
+use App\Utilities\Currency\CurrencyAccessor;
11 7
 
12 8
 use function Pest\Livewire\livewire;
13 9
 
14 10
 it('correctly builds a standard trial balance report', function () {
15
-    $testUser = $this->testUser;
16 11
     $testCompany = $this->testCompany;
17
-    $defaultBankAccount = $testCompany->default->bankAccount;
18 12
 
19
-    $fiscalYearStartDate = $testCompany->locale->fiscalYearStartDate();
20
-    $fiscalYearEndDate = $testCompany->locale->fiscalYearEndDate();
21
-    $defaultEndDate = Carbon::parse($fiscalYearEndDate);
22
-    $defaultAsOfDate = $defaultEndDate->isFuture() ? now()->endOfDay() : $defaultEndDate->endOfDay();
13
+    $reportDatesDTO = ReportDateFactory::create($testCompany);
14
+    $defaultDateRange = $reportDatesDTO->defaultDateRange;
15
+    $defaultEndDate = $reportDatesDTO->defaultEndDate;
23 16
 
24
-    $defaultDateRange = 'FY-' . now()->year;
25 17
     $defaultReportType = 'standard';
26 18
 
27 19
     // Create transactions for the company
28 20
     Transaction::factory()
29
-        ->forCompanyAndBankAccount($testCompany, $defaultBankAccount)
21
+        ->forDefaultBankAccount()
22
+        ->forUncategorizedRevenue()
23
+        ->asDeposit(1000)
30 24
         ->count(10)
31 25
         ->create();
32 26
 
33
-    // Instantiate services
34
-    $accountService = app(AccountService::class);
35
-    $reportService = app(ReportService::class);
27
+    $expectedBankAccountDebit = 10000;
28
+    $expectedBankAccountCredit = 0;
36 29
 
37
-    // Calculate balances
38
-    $asOfStartDate = $accountService->getEarliestTransactionDate();
39
-    $netMovement = $accountService->getNetMovement($defaultBankAccount->account, $asOfStartDate, $defaultAsOfDate);
40
-    $netMovementAmount = $netMovement->getAmount();
41
-
42
-    // Verify trial balance calculations
43
-    $trialBalance = $reportService->calculateTrialBalance($defaultBankAccount->account->category, $netMovementAmount);
44
-    expect($trialBalance)->toMatchArray([
45
-        'debit_balance' => max($netMovementAmount, 0),
46
-        'credit_balance' => $netMovementAmount < 0 ? abs($netMovementAmount) : 0,
47
-    ]);
48
-
49
-    $formattedBalances = $reportService->formatBalances($trialBalance);
50
-
51
-    $accountCategoryPluralLabels = array_map(fn ($category) => $category->getPluralLabel(), AccountCategory::getOrderedCategories());
52
-
53
-    Filament::setTenant($testCompany);
54
-
55
-    $component = livewire(TrialBalance::class)
30
+    livewire(TrialBalance::class)
56 31
         ->assertFormSet([
57 32
             'deferredFilters.reportType' => $defaultReportType,
58 33
             'deferredFilters.dateRange' => $defaultDateRange,
59
-            'deferredFilters.asOfDate' => $defaultAsOfDate->toDateTimeString(),
34
+            'deferredFilters.asOfDate' => $defaultEndDate->toDateTimeString(),
60 35
         ])
61 36
         ->assertSet('filters', [
62 37
             'reportType' => $defaultReportType,
63 38
             'dateRange' => $defaultDateRange,
64
-            'asOfDate' => $defaultAsOfDate->toDateString(),
39
+            'asOfDate' => $defaultEndDate->toDateString(),
65 40
         ])
66 41
         ->call('applyFilters')
67
-        ->assertSeeTextInOrder($accountCategoryPluralLabels)
68 42
         ->assertDontSeeText('Retained Earnings')
69 43
         ->assertSeeTextInOrder([
70
-            $formattedBalances->debitBalance,
71
-            $formattedBalances->creditBalance,
72
-        ]);
73
-
74
-    /** @var ExportableReport $report */
75
-    $report = $component->report;
76
-
77
-    $columnLabels = array_map(static fn ($column) => $column->getLabel(), $report->getColumns());
78
-
79
-    $component->assertSeeTextInOrder($columnLabels);
80
-
81
-    $categories = $report->getCategories();
82
-
83
-    foreach ($categories as $category) {
84
-        $header = $category->header;
85
-        $data = $category->data;
86
-        $summary = $category->summary;
87
-
88
-        $component->assertSeeTextInOrder($header);
89
-
90
-        foreach ($data as $row) {
91
-            $flatRow = [];
92
-
93
-            foreach ($row as $value) {
94
-                if (is_array($value)) {
95
-                    $flatRow[] = $value['name'];
96
-                } else {
97
-                    $flatRow[] = $value;
98
-                }
99
-            }
100
-
101
-            $component->assertSeeTextInOrder($flatRow);
102
-        }
103
-
104
-        $component->assertSeeTextInOrder($summary);
105
-    }
106
-
107
-    $overallTotals = $report->getOverallTotals();
108
-    $component->assertSeeTextInOrder($overallTotals);
44
+            'Cash on Hand',
45
+            money($expectedBankAccountDebit, CurrencyAccessor::getDefaultCurrency(), true),
46
+            money($expectedBankAccountCredit, CurrencyAccessor::getDefaultCurrency(), true),
47
+        ])
48
+        ->assertReportTableData();
109 49
 });
110 50
 
111 51
 it('correctly builds a post-closing trial balance report', function () {
112
-    $testUser = $this->testUser;
113 52
     $testCompany = $this->testCompany;
114
-    $defaultBankAccount = $testCompany->default->bankAccount;
115 53
 
116
-    $fiscalYearStartDate = $testCompany->locale->fiscalYearStartDate();
117
-    $fiscalYearEndDate = $testCompany->locale->fiscalYearEndDate();
118
-    $defaultEndDate = Carbon::parse($fiscalYearEndDate);
119
-    $defaultAsOfDate = $defaultEndDate->isFuture() ? now()->endOfDay() : $defaultEndDate->endOfDay();
54
+    $reportDatesDTO = ReportDateFactory::create($testCompany);
55
+    $defaultDateRange = $reportDatesDTO->defaultDateRange;
56
+    $defaultEndDate = $reportDatesDTO->defaultEndDate;
120 57
 
121
-    $defaultDateRange = 'FY-' . now()->year;
122 58
     $defaultReportType = 'postClosing';
123 59
 
124 60
     // Create transactions for the company
125
-    Transaction::factory()
126
-        ->forCompanyAndBankAccount($testCompany, $defaultBankAccount)
127
-        ->count(10)
61
+    $transaction1 = Transaction::factory()
62
+        ->forDefaultBankAccount()
63
+        ->forUncategorizedRevenue()
64
+        ->asDeposit(1000)
128 65
         ->create();
129 66
 
130
-    // Instantiate services
131
-    $accountService = app(AccountService::class);
132
-    $reportService = app(ReportService::class);
133
-
134
-    // Calculate balances
135
-    $asOfStartDate = $accountService->getEarliestTransactionDate();
136
-    $netMovement = $accountService->getNetMovement($defaultBankAccount->account, $asOfStartDate, $defaultAsOfDate);
137
-    $netMovementAmount = $netMovement->getAmount();
138
-
139
-    // Verify trial balance calculations
140
-    $trialBalance = $reportService->calculateTrialBalance($defaultBankAccount->account->category, $netMovementAmount);
141
-    expect($trialBalance)->toMatchArray([
142
-        'debit_balance' => max($netMovementAmount, 0),
143
-        'credit_balance' => $netMovementAmount < 0 ? abs($netMovementAmount) : 0,
144
-    ]);
145
-
146
-    $formattedBalances = $reportService->formatBalances($trialBalance);
147
-
148
-    $accountCategoryPluralLabels = array_map(fn ($category) => $category->getPluralLabel(), AccountCategory::getOrderedCategories());
149
-
150
-    $retainedEarningsAmount = $reportService->calculateRetainedEarnings($asOfStartDate, $defaultAsOfDate)->getAmount();
151
-
152
-    $isCredit = $retainedEarningsAmount >= 0;
153
-
154
-    $retainedEarningsDebitAmount = $isCredit ? 0 : abs($retainedEarningsAmount);
155
-    $retainedEarningsCreditAmount = $isCredit ? $retainedEarningsAmount : 0;
156
-
157
-    $formattedRetainedEarnings = $reportService->formatBalances([
158
-        'debit_balance' => $retainedEarningsDebitAmount,
159
-        'credit_balance' => $retainedEarningsCreditAmount,
160
-    ]);
67
+    $transaction2 = Transaction::factory()
68
+        ->forDefaultBankAccount()
69
+        ->forUncategorizedExpense()
70
+        ->asWithdrawal(500)
71
+        ->create();
161 72
 
162
-    $retainedEarningsRow = [
163
-        'RE',
164
-        'Retained Earnings',
165
-        $formattedRetainedEarnings->debitBalance,
166
-        $formattedRetainedEarnings->creditBalance,
167
-    ];
73
+    $expectedRetainedEarningsDebit = 0;
74
+    $expectedRetainedEarningsCredit = 500;
168 75
 
169
-    Filament::setTenant($testCompany);
76
+    $defaultCurrencyCode = CurrencyAccessor::getDefaultCurrency();
170 77
 
171
-    $component = livewire(TrialBalance::class)
78
+    livewire(TrialBalance::class)
172 79
         ->set('deferredFilters.reportType', $defaultReportType)
173 80
         ->call('applyFilters')
174 81
         ->assertFormSet([
175 82
             'deferredFilters.reportType' => $defaultReportType,
176 83
             'deferredFilters.dateRange' => $defaultDateRange,
177
-            'deferredFilters.asOfDate' => $defaultAsOfDate->toDateTimeString(),
84
+            'deferredFilters.asOfDate' => $defaultEndDate->toDateTimeString(),
178 85
         ])
179 86
         ->assertSet('filters', [
180 87
             'reportType' => $defaultReportType,
181 88
             'dateRange' => $defaultDateRange,
182
-            'asOfDate' => $defaultAsOfDate->toDateString(),
89
+            'asOfDate' => $defaultEndDate->toDateString(),
183 90
         ])
184 91
         ->call('applyFilters')
185
-        ->assertSeeTextInOrder($retainedEarningsRow)
92
+        ->assertSeeTextInOrder([
93
+            'RE',
94
+            'Retained Earnings',
95
+            money($expectedRetainedEarningsDebit, $defaultCurrencyCode, true),
96
+            money($expectedRetainedEarningsCredit, $defaultCurrencyCode, true),
97
+        ])
186 98
         ->assertSeeTextInOrder([
187 99
             'Total Revenue',
188
-            '$0.00',
189
-            '$0.00',
100
+            money(0, $defaultCurrencyCode, true),
101
+            money(0, $defaultCurrencyCode, true),
190 102
         ])
191 103
         ->assertSeeTextInOrder([
192 104
             'Total Expenses',
193
-            '$0.00',
194
-            '$0.00',
105
+            money(0, $defaultCurrencyCode, true),
106
+            money(0, $defaultCurrencyCode, true),
195 107
         ])
196
-        ->assertSeeTextInOrder($accountCategoryPluralLabels)
197
-        ->assertSeeTextInOrder([
198
-            $formattedBalances->debitBalance,
199
-            $formattedBalances->creditBalance,
200
-        ]);
201
-
202
-    /** @var ExportableReport $report */
203
-    $report = $component->report;
204
-
205
-    $columnLabels = array_map(static fn ($column) => $column->getLabel(), $report->getColumns());
206
-
207
-    $component->assertSeeTextInOrder($columnLabels);
208
-
209
-    $categories = $report->getCategories();
210
-
211
-    foreach ($categories as $category) {
212
-        $header = $category->header;
213
-        $data = $category->data;
214
-        $summary = $category->summary;
215
-
216
-        $component->assertSeeTextInOrder($header);
217
-
218
-        foreach ($data as $row) {
219
-            $flatRow = [];
220
-
221
-            foreach ($row as $value) {
222
-                if (is_array($value)) {
223
-                    $flatRow[] = $value['name'];
224
-                } else {
225
-                    $flatRow[] = $value;
226
-                }
227
-            }
228
-
229
-            $component->assertSeeTextInOrder($flatRow);
230
-        }
231
-
232
-        $component->assertSeeTextInOrder($summary);
233
-    }
234
-
235
-    $overallTotals = $report->getOverallTotals();
236
-    $component->assertSeeTextInOrder($overallTotals);
108
+        ->assertReportTableData();
237 109
 });

+ 16
- 0
tests/Helpers/helpers.php Vedi File

@@ -1,8 +1,10 @@
1 1
 <?php
2 2
 
3
+use App\DTO\AccountBalanceDTO;
3 4
 use App\Enums\Setting\EntityType;
4 5
 use App\Filament\Company\Pages\CreateCompany;
5 6
 use App\Models\Company;
7
+use App\Services\ReportService;
6 8
 
7 9
 use function Pest\Livewire\livewire;
8 10
 
@@ -22,3 +24,17 @@ function createCompany(string $name): Company
22 24
 
23 25
     return auth()->user()->currentCompany;
24 26
 }
27
+
28
+function calculateRetainedEarningsBalances(ReportService $reportService, $startDate, $endDate): AccountBalanceDTO
29
+{
30
+    $retainedEarningsAmount = $reportService->calculateRetainedEarnings($startDate, $endDate)->getAmount();
31
+
32
+    $isCredit = $retainedEarningsAmount >= 0;
33
+    $retainedEarningsDebitAmount = $isCredit ? 0 : abs($retainedEarningsAmount);
34
+    $retainedEarningsCreditAmount = $isCredit ? $retainedEarningsAmount : 0;
35
+
36
+    return $reportService->formatBalances([
37
+        'debit_balance' => $retainedEarningsDebitAmount,
38
+        'credit_balance' => $retainedEarningsCreditAmount,
39
+    ]);
40
+}

+ 8
- 2
tests/TestCase.php Vedi File

@@ -4,9 +4,12 @@ namespace Tests;
4 4
 
5 5
 use App\Models\Company;
6 6
 use App\Models\User;
7
+use App\Testing\TestsReport;
7 8
 use Database\Seeders\TestDatabaseSeeder;
9
+use Filament\Facades\Filament;
8 10
 use Illuminate\Foundation\Testing\RefreshDatabase;
9 11
 use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
12
+use Livewire\Features\SupportTesting\Testable;
10 13
 
11 14
 abstract class TestCase extends BaseTestCase
12 15
 {
@@ -30,13 +33,16 @@ abstract class TestCase extends BaseTestCase
30 33
     {
31 34
         parent::setUp();
32 35
 
36
+        Testable::mixin(new TestsReport);
37
+
33 38
         $this->testUser = User::first();
34 39
 
35 40
         $this->testCompany = $this->testUser->ownedCompanies->first();
36 41
 
37 42
         $this->testUser->switchCompany($this->testCompany);
38 43
 
39
-        $this->actingAs($this->testUser)
40
-            ->withSession(['current_company_id' => $this->testCompany->id]);
44
+        $this->actingAs($this->testUser);
45
+
46
+        Filament::setTenant($this->testCompany);
41 47
     }
42 48
 }

Loading…
Annulla
Salva