Browse Source

Merge pull request #62 from andrewdwallo/development-3.x

Development 3.x
3.x
Andrew Wallo 1 year ago
parent
commit
a7ed093557
No account linked to committer's email address
26 changed files with 856 additions and 613 deletions
  1. 1
    25
      app/DTO/AccountBalanceDTO.php
  2. 1
    19
      app/DTO/AccountCategoryDTO.php
  3. 2
    21
      app/DTO/AccountDTO.php
  4. 5
    0
      app/DTO/AccountTransactionDTO.php
  5. 1
    21
      app/DTO/ReportDTO.php
  6. 1
    1
      app/Filament/Company/Clusters/Settings/Resources/CurrencyResource/Pages/CreateCurrency.php
  7. 1
    1
      app/Filament/Company/Clusters/Settings/Resources/DiscountResource/Pages/CreateDiscount.php
  8. 1
    1
      app/Filament/Company/Clusters/Settings/Resources/TaxResource/Pages/CreateTax.php
  9. 2
    0
      app/Filament/Company/Pages/Reports/AccountTransactions.php
  10. 0
    2
      app/Filament/Company/Pages/Reports/TrialBalance.php
  11. 59
    12
      app/Services/AccountService.php
  12. 59
    25
      app/Services/ReportService.php
  13. 4
    1
      app/Transformers/AccountBalanceReportTransformer.php
  14. 5
    1
      app/Transformers/AccountTransactionReportTransformer.php
  15. 1
    16
      app/Transformers/BaseReportTransformer.php
  16. 4
    1
      app/Transformers/TrialBalanceReportTransformer.php
  17. 205
    205
      composer.lock
  18. 2
    2
      database/migrations/2024_01_01_234943_create_transactions_table.php
  19. 2
    2
      database/migrations/2024_01_11_062614_create_journal_entries_table.php
  20. 321
    126
      package-lock.json
  21. 9
    14
      resources/css/filament/user/theme.css
  22. 8
    8
      resources/views/components/company/reports/account-transactions-report-pdf.blade.php
  23. 14
    2
      resources/views/components/company/reports/report-pdf.blade.php
  24. 77
    54
      resources/views/components/company/tables/reports/account-transactions.blade.php
  25. 62
    45
      resources/views/components/company/tables/reports/detailed-report.blade.php
  26. 9
    8
      resources/views/filament/company/pages/reports/detailed-report.blade.php

+ 1
- 25
app/DTO/AccountBalanceDTO.php View File

@@ -2,9 +2,7 @@
2 2
 
3 3
 namespace App\DTO;
4 4
 
5
-use Livewire\Wireable;
6
-
7
-class AccountBalanceDTO implements Wireable
5
+class AccountBalanceDTO
8 6
 {
9 7
     public function __construct(
10 8
         public ?string $startingBalance,
@@ -13,26 +11,4 @@ class AccountBalanceDTO implements Wireable
13 11
         public ?string $netMovement,
14 12
         public ?string $endingBalance,
15 13
     ) {}
16
-
17
-    public function toLivewire(): array
18
-    {
19
-        return [
20
-            'startingBalance' => $this->startingBalance,
21
-            'debitBalance' => $this->debitBalance,
22
-            'creditBalance' => $this->creditBalance,
23
-            'netMovement' => $this->netMovement,
24
-            'endingBalance' => $this->endingBalance,
25
-        ];
26
-    }
27
-
28
-    public static function fromLivewire($value): static
29
-    {
30
-        return new static(
31
-            $value['startingBalance'],
32
-            $value['debitBalance'],
33
-            $value['creditBalance'],
34
-            $value['netMovement'],
35
-            $value['endingBalance'],
36
-        );
37
-    }
38 14
 }

+ 1
- 19
app/DTO/AccountCategoryDTO.php View File

@@ -2,9 +2,7 @@
2 2
 
3 3
 namespace App\DTO;
4 4
 
5
-use Livewire\Wireable;
6
-
7
-class AccountCategoryDTO implements Wireable
5
+class AccountCategoryDTO
8 6
 {
9 7
     /**
10 8
      * @param  AccountDTO[]  $accounts
@@ -13,20 +11,4 @@ class AccountCategoryDTO implements Wireable
13 11
         public array $accounts,
14 12
         public AccountBalanceDTO $summary,
15 13
     ) {}
16
-
17
-    public function toLivewire(): array
18
-    {
19
-        return [
20
-            'accounts' => $this->accounts,
21
-            'summary' => $this->summary->toLivewire(),
22
-        ];
23
-    }
24
-
25
-    public static function fromLivewire($value): static
26
-    {
27
-        return new static(
28
-            $value['accounts'],
29
-            AccountBalanceDTO::fromLivewire($value['summary']),
30
-        );
31
-    }
32 14
 }

+ 2
- 21
app/DTO/AccountDTO.php View File

@@ -2,31 +2,12 @@
2 2
 
3 3
 namespace App\DTO;
4 4
 
5
-use Livewire\Wireable;
6
-
7
-class AccountDTO implements Wireable
5
+class AccountDTO
8 6
 {
9 7
     public function __construct(
10 8
         public string $accountName,
11 9
         public string $accountCode,
10
+        public ?int $accountId,
12 11
         public AccountBalanceDTO $balance,
13 12
     ) {}
14
-
15
-    public function toLivewire(): array
16
-    {
17
-        return [
18
-            'accountName' => $this->accountName,
19
-            'accountCode' => $this->accountCode,
20
-            'balance' => $this->balance->toLivewire(),
21
-        ];
22
-    }
23
-
24
-    public static function fromLivewire($value): static
25
-    {
26
-        return new static(
27
-            $value['accountName'],
28
-            $value['accountCode'],
29
-            AccountBalanceDTO::fromLivewire($value['balance']),
30
-        );
31
-    }
32 13
 }

+ 5
- 0
app/DTO/AccountTransactionDTO.php View File

@@ -2,13 +2,18 @@
2 2
 
3 3
 namespace App\DTO;
4 4
 
5
+use App\Enums\Accounting\TransactionType;
6
+
5 7
 class AccountTransactionDTO
6 8
 {
7 9
     public function __construct(
10
+        public ?int $id,
8 11
         public string $date,
9 12
         public string $description,
10 13
         public string $debit,
11 14
         public string $credit,
12 15
         public string $balance,
16
+        public ?TransactionType $type,
17
+        public ?string $tableAction,
13 18
     ) {}
14 19
 }

+ 1
- 21
app/DTO/ReportDTO.php View File

@@ -2,9 +2,7 @@
2 2
 
3 3
 namespace App\DTO;
4 4
 
5
-use Livewire\Wireable;
6
-
7
-class ReportDTO implements Wireable
5
+class ReportDTO
8 6
 {
9 7
     public function __construct(
10 8
         /**
@@ -14,22 +12,4 @@ class ReportDTO implements Wireable
14 12
         public ?AccountBalanceDTO $overallTotal = null,
15 13
         public array $fields = [],
16 14
     ) {}
17
-
18
-    public function toLivewire(): array
19
-    {
20
-        return [
21
-            'categories' => $this->categories,
22
-            'overallTotal' => $this->overallTotal?->toLivewire(),
23
-            'fields' => $this->fields,
24
-        ];
25
-    }
26
-
27
-    public static function fromLivewire($value): static
28
-    {
29
-        return new static(
30
-            $value['categories'],
31
-            isset($value['overallTotal']) ? AccountBalanceDTO::fromLivewire($value['overallTotal']) : null,
32
-            $value['fields'] ?? [],
33
-        );
34
-    }
35 15
 }

+ 1
- 1
app/Filament/Company/Clusters/Settings/Resources/CurrencyResource/Pages/CreateCurrency.php View File

@@ -39,6 +39,6 @@ class CreateCurrency extends CreateRecord
39 39
             throw new Halt('No authenticated user found');
40 40
         }
41 41
 
42
-        return $this->handleRecordCreationWithUniqueField($data, new Currency(), $user);
42
+        return $this->handleRecordCreationWithUniqueField($data, new Currency, $user);
43 43
     }
44 44
 }

+ 1
- 1
app/Filament/Company/Clusters/Settings/Resources/DiscountResource/Pages/CreateDiscount.php View File

@@ -41,6 +41,6 @@ class CreateDiscount extends CreateRecord
41 41
 
42 42
         $evaluatedTypes = [DiscountType::Sales, DiscountType::Purchase];
43 43
 
44
-        return $this->handleRecordCreationWithUniqueField($data, new Discount(), $user, 'type', $evaluatedTypes);
44
+        return $this->handleRecordCreationWithUniqueField($data, new Discount, $user, 'type', $evaluatedTypes);
45 45
     }
46 46
 }

+ 1
- 1
app/Filament/Company/Clusters/Settings/Resources/TaxResource/Pages/CreateTax.php View File

@@ -41,6 +41,6 @@ class CreateTax extends CreateRecord
41 41
 
42 42
         $evaluatedTypes = [TaxType::Sales, TaxType::Purchase];
43 43
 
44
-        return $this->handleRecordCreationWithUniqueField($data, new Tax(), $user, 'type', $evaluatedTypes);
44
+        return $this->handleRecordCreationWithUniqueField($data, new Tax, $user, 'type', $evaluatedTypes);
45 45
     }
46 46
 }

+ 2
- 0
app/Filament/Company/Pages/Reports/AccountTransactions.php View File

@@ -18,6 +18,7 @@ use Filament\Tables\Actions\Action;
18 18
 use Guava\FilamentClusters\Forms\Cluster;
19 19
 use Illuminate\Contracts\Support\Htmlable;
20 20
 use Illuminate\Support\Collection;
21
+use Livewire\Attributes\Url;
21 22
 use Symfony\Component\HttpFoundation\StreamedResponse;
22 23
 
23 24
 class AccountTransactions extends BaseReportPage
@@ -32,6 +33,7 @@ class AccountTransactions extends BaseReportPage
32 33
 
33 34
     protected ExportService $exportService;
34 35
 
36
+    #[Url]
35 37
     public ?string $account_id = 'all';
36 38
 
37 39
     public function boot(ReportService $reportService, ExportService $exportService): void

+ 0
- 2
app/Filament/Company/Pages/Reports/TrialBalance.php View File

@@ -43,11 +43,9 @@ class TrialBalance extends BaseReportPage
43 43
                 ->alignment(Alignment::Left),
44 44
             Column::make('debit_balance')
45 45
                 ->label('Debit')
46
-                ->toggleable()
47 46
                 ->alignment(Alignment::Right),
48 47
             Column::make('credit_balance')
49 48
                 ->label('Credit')
50
-                ->toggleable()
51 49
                 ->alignment(Alignment::Right),
52 50
         ];
53 51
     }

+ 59
- 12
app/Services/AccountService.php View File

@@ -5,11 +5,12 @@ namespace App\Services;
5 5
 use App\Contracts\AccountHandler;
6 6
 use App\Enums\Accounting\AccountCategory;
7 7
 use App\Models\Accounting\Account;
8
+use App\Models\Accounting\JournalEntry;
8 9
 use App\Models\Accounting\Transaction;
9
-use App\Models\Banking\BankAccount;
10 10
 use App\Repositories\Accounting\JournalEntryRepository;
11 11
 use App\Utilities\Currency\CurrencyAccessor;
12 12
 use App\ValueObjects\Money;
13
+use Illuminate\Support\Facades\DB;
13 14
 
14 15
 class AccountService implements AccountHandler
15 16
 {
@@ -65,6 +66,31 @@ class AccountService implements AccountHandler
65 66
         return new Money($endingBalance, $account->currency_code);
66 67
     }
67 68
 
69
+    public function getRetainedEarnings(string $startDate): Money
70
+    {
71
+        $revenue = JournalEntry::whereHas('account', static function ($query) {
72
+            $query->where('category', AccountCategory::Revenue);
73
+        })
74
+            ->where('type', 'credit')
75
+            ->whereHas('transaction', static function ($query) use ($startDate) {
76
+                $query->where('posted_at', '<=', $startDate);
77
+            })
78
+            ->sum('amount');
79
+
80
+        $expense = JournalEntry::whereHas('account', static function ($query) {
81
+            $query->where('category', AccountCategory::Expense);
82
+        })
83
+            ->where('type', 'debit')
84
+            ->whereHas('transaction', static function ($query) use ($startDate) {
85
+                $query->where('posted_at', '<=', $startDate);
86
+            })
87
+            ->sum('amount');
88
+
89
+        $retainedEarnings = $revenue - $expense;
90
+
91
+        return new Money($retainedEarnings, CurrencyAccessor::getDefaultCurrency());
92
+    }
93
+
68 94
     private function calculateNetMovementByCategory(AccountCategory $category, int $debitBalance, int $creditBalance): int
69 95
     {
70 96
         return match ($category) {
@@ -125,20 +151,41 @@ class AccountService implements AccountHandler
125 151
 
126 152
     public function getTotalBalanceForAllBankAccounts(string $startDate, string $endDate): Money
127 153
     {
128
-        $bankAccounts = BankAccount::with('account')
129
-            ->get();
154
+        $accountIds = Account::whereHas('bankAccount')
155
+            ->pluck('id')
156
+            ->toArray();
130 157
 
131
-        $totalBalance = 0;
132
-
133
-        foreach ($bankAccounts as $bankAccount) {
134
-            $account = $bankAccount->account;
135
-
136
-            if ($account) {
137
-                $endingBalance = $this->getEndingBalance($account, $startDate, $endDate)?->getAmount() ?? 0;
138
-                $totalBalance += $endingBalance;
139
-            }
158
+        if (empty($accountIds)) {
159
+            return new Money(0, CurrencyAccessor::getDefaultCurrency());
140 160
         }
141 161
 
162
+        $result = DB::table('journal_entries')
163
+            ->join('transactions', 'journal_entries.transaction_id', '=', 'transactions.id')
164
+            ->whereIn('journal_entries.account_id', $accountIds)
165
+            ->where('transactions.posted_at', '<=', $endDate)
166
+            ->selectRaw('
167
+            SUM(CASE
168
+                WHEN transactions.posted_at <= ? AND journal_entries.type = "debit" THEN journal_entries.amount
169
+                WHEN transactions.posted_at <= ? AND journal_entries.type = "credit" THEN -journal_entries.amount
170
+                ELSE 0
171
+            END) AS totalStartingBalance,
172
+            SUM(CASE
173
+                WHEN transactions.posted_at BETWEEN ? AND ? AND journal_entries.type = "debit" THEN journal_entries.amount
174
+                WHEN transactions.posted_at BETWEEN ? AND ? AND journal_entries.type = "credit" THEN -journal_entries.amount
175
+                ELSE 0
176
+            END) AS totalNetMovement
177
+        ', [
178
+                $startDate,
179
+                $startDate,
180
+                $startDate,
181
+                $endDate,
182
+                $startDate,
183
+                $endDate,
184
+            ])
185
+            ->first();
186
+
187
+        $totalBalance = $result->totalStartingBalance + $result->totalNetMovement;
188
+
142 189
         return new Money($totalBalance, CurrencyAccessor::getDefaultCurrency());
143 190
     }
144 191
 

+ 59
- 25
app/Services/ReportService.php View File

@@ -12,8 +12,8 @@ use App\Models\Accounting\Account;
12 12
 use App\Support\Column;
13 13
 use App\Utilities\Currency\CurrencyAccessor;
14 14
 use Illuminate\Database\Eloquent\Builder;
15
-use Illuminate\Database\Eloquent\Collection;
16 15
 use Illuminate\Database\Eloquent\Relations\Relation;
16
+use Illuminate\Support\Collection;
17 17
 
18 18
 class ReportService
19 19
 {
@@ -46,7 +46,7 @@ class ReportService
46 46
     private function getCategoryGroupedAccounts(array $allCategories): Collection
47 47
     {
48 48
         return Account::whereHas('journalEntries')
49
-            ->select('id', 'name', 'currency_code', 'category', 'code')
49
+            ->select(['id', 'name', 'currency_code', 'category', 'code'])
50 50
             ->get()
51 51
             ->groupBy(fn (Account $account) => $account->category->getPluralLabel())
52 52
             ->sortBy(static fn (Collection $groupedAccounts, string $key) => array_search($key, $allCategories, true));
@@ -74,25 +74,6 @@ class ReportService
74 74
         );
75 75
     }
76 76
 
77
-    public function buildTrialBalanceReport(string $startDate, string $endDate, array $columns = []): ReportDTO
78
-    {
79
-        $allCategories = $this->accountService->getAccountCategoryOrder();
80
-
81
-        $categoryGroupedAccounts = $this->getCategoryGroupedAccounts($allCategories);
82
-
83
-        $balanceFields = ['debit_balance', 'credit_balance'];
84
-
85
-        return $this->buildReport($allCategories, $categoryGroupedAccounts, function (Account $account) use ($startDate, $endDate) {
86
-            $endingBalance = $this->accountService->getEndingBalance($account, $startDate, $endDate)?->getAmount() ?? 0;
87
-
88
-            if ($endingBalance === 0) {
89
-                return [];
90
-            }
91
-
92
-            return $this->calculateTrialBalance($account->category, $endingBalance);
93
-        }, $balanceFields, $columns);
94
-    }
95
-
96 77
     public function buildAccountTransactionsReport(string $startDate, string $endDate, ?array $columns = null, ?string $accountId = 'all'): ReportDTO
97 78
     {
98 79
         $columns ??= [];
@@ -103,9 +84,7 @@ class ReportService
103 84
                 $query->whereHas('transaction', function (Builder $query) use ($startDate, $endDate) {
104 85
                     $query->whereBetween('posted_at', [$startDate, $endDate]);
105 86
                 })
106
-                    ->with(['transaction' => function (Relation $query) {
107
-                        $query->select(['id', 'description', 'posted_at']);
108
-                    }])
87
+                    ->with('transaction:id,type,description,posted_at')
109 88
                     ->select(['account_id', 'transaction_id'])
110 89
                     ->selectRaw('SUM(CASE WHEN type = "debit" THEN amount ELSE 0 END) AS total_debit')
111 90
                     ->selectRaw('SUM(CASE WHEN type = "credit" THEN amount ELSE 0 END) AS total_credit')
@@ -134,11 +113,14 @@ class ReportService
134 113
             $totalCredit = 0;
135 114
 
136 115
             $accountTransactions[] = new AccountTransactionDTO(
116
+                id: null,
137 117
                 date: 'Starting Balance',
138 118
                 description: '',
139 119
                 debit: '',
140 120
                 credit: '',
141 121
                 balance: $startingBalance?->formatInDefaultCurrency() ?? 0,
122
+                type: null,
123
+                tableAction: null,
142 124
             );
143 125
 
144 126
             foreach ($account->journalEntries as $journalEntry) {
@@ -150,30 +132,39 @@ class ReportService
150 132
                 $currentBalance -= $journalEntry->total_credit;
151 133
 
152 134
                 $accountTransactions[] = new AccountTransactionDTO(
135
+                    id: $transaction->id,
153 136
                     date: $transaction->posted_at->format('Y-m-d'),
154 137
                     description: $transaction->description ?? '',
155 138
                     debit: $journalEntry->total_debit ? money($journalEntry->total_debit, $defaultCurrency)->format() : '',
156 139
                     credit: $journalEntry->total_credit ? money($journalEntry->total_credit, $defaultCurrency)->format() : '',
157 140
                     balance: money($currentBalance, $defaultCurrency)->format(),
141
+                    type: $transaction->type,
142
+                    tableAction: $transaction->type->isJournal() ? 'updateJournalTransaction' : 'updateTransaction',
158 143
                 );
159 144
             }
160 145
 
161 146
             $balanceChange = $currentBalance - ($startingBalance?->getAmount() ?? 0);
162 147
 
163 148
             $accountTransactions[] = new AccountTransactionDTO(
149
+                id: null,
164 150
                 date: 'Totals and Ending Balance',
165 151
                 description: '',
166 152
                 debit: money($totalDebit, $defaultCurrency)->format(),
167 153
                 credit: money($totalCredit, $defaultCurrency)->format(),
168 154
                 balance: money($currentBalance, $defaultCurrency)->format(),
155
+                type: null,
156
+                tableAction: null,
169 157
             );
170 158
 
171 159
             $accountTransactions[] = new AccountTransactionDTO(
160
+                id: null,
172 161
                 date: 'Balance Change',
173 162
                 description: '',
174 163
                 debit: '',
175 164
                 credit: '',
176 165
                 balance: money($balanceChange, $defaultCurrency)->format(),
166
+                type: null,
167
+                tableAction: null,
177 168
             );
178 169
 
179 170
             $reportCategories[] = [
@@ -186,7 +177,7 @@ class ReportService
186 177
         return new ReportDTO(categories: $reportCategories, fields: $columns);
187 178
     }
188 179
 
189
-    private function buildReport(array $allCategories, Collection $categoryGroupedAccounts, callable $balanceCalculator, array $balanceFields, array $allFields, ?callable $initializeCategoryBalances = null): ReportDTO
180
+    private function buildReport(array $allCategories, Collection $categoryGroupedAccounts, callable $balanceCalculator, array $balanceFields, array $allFields, ?callable $initializeCategoryBalances = null, bool $includeRetainedEarnings = false, ?string $startDate = null): ReportDTO
190 181
     {
191 182
         $accountCategories = [];
192 183
         $reportTotalBalances = array_fill_keys($balanceFields, 0);
@@ -221,10 +212,34 @@ class ReportService
221 212
                 $categoryAccounts[] = new AccountDTO(
222 213
                     $account->name,
223 214
                     $account->code,
215
+                    $account->id,
224 216
                     $formattedAccountBalances,
225 217
                 );
226 218
             }
227 219
 
220
+            if ($includeRetainedEarnings && $categoryName === AccountCategory::Equity->getPluralLabel()) {
221
+                $retainedEarnings = $this->accountService->getRetainedEarnings($startDate);
222
+                $retainedEarningsAmount = $retainedEarnings->getAmount();
223
+
224
+                if ($retainedEarningsAmount >= 0) {
225
+                    $categorySummaryBalances['credit_balance'] += $retainedEarningsAmount;
226
+                    $categoryAccounts[] = new AccountDTO(
227
+                        'Retained Earnings',
228
+                        'RE',
229
+                        null,
230
+                        $this->formatBalances(['debit_balance' => 0, 'credit_balance' => $retainedEarningsAmount])
231
+                    );
232
+                } else {
233
+                    $categorySummaryBalances['debit_balance'] += abs($retainedEarningsAmount);
234
+                    $categoryAccounts[] = new AccountDTO(
235
+                        'Retained Earnings',
236
+                        'RE',
237
+                        null,
238
+                        $this->formatBalances(['debit_balance' => abs($retainedEarningsAmount), 'credit_balance' => 0])
239
+                    );
240
+                }
241
+            }
242
+
228 243
             foreach ($balanceFields as $field) {
229 244
                 if (array_key_exists($field, $categorySummaryBalances)) {
230 245
                     $reportTotalBalances[$field] += $categorySummaryBalances[$field];
@@ -252,6 +267,25 @@ class ReportService
252 267
         }
253 268
     }
254 269
 
270
+    public function buildTrialBalanceReport(string $startDate, string $endDate, array $columns = []): ReportDTO
271
+    {
272
+        $allCategories = $this->accountService->getAccountCategoryOrder();
273
+
274
+        $categoryGroupedAccounts = $this->getCategoryGroupedAccounts($allCategories);
275
+
276
+        $balanceFields = ['debit_balance', 'credit_balance'];
277
+
278
+        return $this->buildReport($allCategories, $categoryGroupedAccounts, function (Account $account) use ($startDate, $endDate) {
279
+            $endingBalance = $this->accountService->getEndingBalance($account, $startDate, $endDate)?->getAmount() ?? 0;
280
+
281
+            if ($endingBalance === 0) {
282
+                return [];
283
+            }
284
+
285
+            return $this->calculateTrialBalance($account->category, $endingBalance);
286
+        }, $balanceFields, $columns, null, true, $startDate);
287
+    }
288
+
255 289
     private function calculateTrialBalance(AccountCategory $category, int $endingBalance): array
256 290
     {
257 291
         if (in_array($category, [AccountCategory::Asset, AccountCategory::Expense], true)) {

+ 4
- 1
app/Transformers/AccountBalanceReportTransformer.php View File

@@ -43,7 +43,10 @@ class AccountBalanceReportTransformer extends BaseReportTransformer
43 43
                 foreach ($this->getColumns() as $column) {
44 44
                     $row[] = match ($column->getName()) {
45 45
                         'account_code' => $account->accountCode,
46
-                        'account_name' => $account->accountName,
46
+                        'account_name' => [
47
+                            'name' => $account->accountName,
48
+                            'id' => $account->accountId ?? null,
49
+                        ],
47 50
                         'starting_balance' => $account->balance->startingBalance ?? '',
48 51
                         'debit_balance' => $account->balance->debitBalance,
49 52
                         'credit_balance' => $account->balance->creditBalance,

+ 5
- 1
app/Transformers/AccountTransactionReportTransformer.php View File

@@ -52,7 +52,11 @@ class AccountTransactionReportTransformer extends BaseReportTransformer
52 52
                 foreach ($this->getColumns() as $column) {
53 53
                     $row[] = match ($column->getName()) {
54 54
                         'date' => $transaction->date,
55
-                        'description' => $transaction->description,
55
+                        'description' => [
56
+                            'id' => $transaction->id,
57
+                            'description' => $transaction->description,
58
+                            'tableAction' => $transaction->tableAction,
59
+                        ],
56 60
                         'debit' => $transaction->debit,
57 61
                         'credit' => $transaction->credit,
58 62
                         'balance' => $transaction->balance,

+ 1
- 16
app/Transformers/BaseReportTransformer.php View File

@@ -5,9 +5,8 @@ namespace App\Transformers;
5 5
 use App\Contracts\ExportableReport;
6 6
 use App\DTO\ReportDTO;
7 7
 use Filament\Support\Enums\Alignment;
8
-use Livewire\Wireable;
9 8
 
10
-abstract class BaseReportTransformer implements ExportableReport, Wireable
9
+abstract class BaseReportTransformer implements ExportableReport
11 10
 {
12 11
     protected ReportDTO $report;
13 12
 
@@ -40,18 +39,4 @@ abstract class BaseReportTransformer implements ExportableReport, Wireable
40 39
 
41 40
         return 'text-left';
42 41
     }
43
-
44
-    public function toLivewire(): array
45
-    {
46
-        return [
47
-            'report' => $this->report->toLivewire(),
48
-        ];
49
-    }
50
-
51
-    public static function fromLivewire($value): static
52
-    {
53
-        return new static(
54
-            ReportDTO::fromLivewire($value['report']),
55
-        );
56
-    }
57 42
 }

+ 4
- 1
app/Transformers/TrialBalanceReportTransformer.php View File

@@ -43,7 +43,10 @@ class TrialBalanceReportTransformer extends BaseReportTransformer
43 43
                 foreach ($this->getColumns() as $column) {
44 44
                     $row[] = match ($column->getName()) {
45 45
                         'account_code' => $account->accountCode,
46
-                        'account_name' => $account->accountName,
46
+                        'account_name' => [
47
+                            'name' => $account->accountName,
48
+                            'id' => $account->accountId ?? null,
49
+                        ],
47 50
                         'debit_balance' => $account->balance->debitBalance,
48 51
                         'credit_balance' => $account->balance->creditBalance,
49 52
                         default => '',

+ 205
- 205
composer.lock
File diff suppressed because it is too large
View File


+ 2
- 2
database/migrations/2024_01_01_234943_create_transactions_table.php View File

@@ -31,8 +31,8 @@ return new class extends Migration
31 31
             $table->foreignId('updated_by')->nullable()->constrained('users')->nullOnDelete();
32 32
             $table->timestamps();
33 33
 
34
-            $table->index(['company_id', 'account_id', 'posted_at']);
35
-            $table->index(['company_id', 'bank_account_id', 'posted_at']);
34
+            $table->index(['account_id', 'posted_at']);
35
+            $table->index(['bank_account_id', 'posted_at']);
36 36
         });
37 37
     }
38 38
 

+ 2
- 2
database/migrations/2024_01_11_062614_create_journal_entries_table.php View File

@@ -23,8 +23,8 @@ return new class extends Migration
23 23
             $table->foreignId('updated_by')->nullable()->constrained('users')->nullOnDelete();
24 24
             $table->timestamps();
25 25
 
26
-            $table->index(['company_id', 'account_id', 'type']);
27
-            $table->index(['company_id', 'account_id', 'transaction_id']);
26
+            $table->index(['account_id', 'type']);
27
+            $table->index(['account_id', 'transaction_id']);
28 28
         });
29 29
     }
30 30
 

+ 321
- 126
package-lock.json
File diff suppressed because it is too large
View File


+ 9
- 14
resources/css/filament/user/theme.css View File

@@ -8,24 +8,19 @@
8 8
     z-index: 1;
9 9
 }
10 10
 
11
-.fi-input-password-revealable::-ms-reveal {
12
-    display: none;
13
-}
14
-
15 11
 .fi-body::before {
16 12
     content: '';
17
-    position: absolute;
13
+    position: fixed;
18 14
     top: 0;
19 15
     left: 0;
20 16
     width: 100%;
21 17
     height: 100%;
22
-    background-image:
23
-        linear-gradient(99.6deg,
24
-            rgba(232, 233, 235, 1) 10.6%,
25
-            rgba(240, 241, 243, 1) 32.9%,
26
-            rgba(248, 249, 251, 0.7) 50%,
27
-            rgba(240, 241, 243, 1) 67.1%,
28
-            rgba(232, 233, 235, 1) 83.4%);
18
+    background-image: linear-gradient(99.6deg,
19
+    rgba(232, 233, 235, 1) 10.6%,
20
+    rgba(240, 241, 243, 1) 32.9%,
21
+    rgba(248, 249, 251, 0.7) 50%,
22
+    rgba(240, 241, 243, 1) 67.1%,
23
+    rgba(232, 233, 235, 1) 83.4%);
29 24
     pointer-events: none;
30 25
     z-index: -1;
31 26
 }
@@ -38,7 +33,7 @@
38 33
 
39 34
 :is(.dark .fi-body)::before {
40 35
     content: '';
41
-    position: absolute;
36
+    position: fixed;
42 37
     top: 0;
43 38
     right: 0;
44 39
     background-image: radial-gradient(
@@ -49,7 +44,7 @@
49 44
         rgba(var(--primary-900), 0.5) 45%,
50 45
         rgba(var(--primary-950), 0.3) 60%,
51 46
         rgba(var(--primary-950), 0.1) 75%,
52
-        rgba(3,7,18,0) 100%
47
+        rgba(3, 7, 18, 0) 100%
53 48
     );
54 49
     width: 100%;
55 50
     height: 100%;

+ 8
- 8
resources/views/components/company/reports/account-transactions-report-pdf.blade.php View File

@@ -35,6 +35,14 @@
35 35
             color: #374151;
36 36
         }
37 37
 
38
+        .whitespace-normal {
39
+            white-space: normal;
40
+        }
41
+
42
+        .whitespace-nowrap {
43
+            white-space: nowrap;
44
+        }
45
+
38 46
         .title {
39 47
             font-size: 1.5rem;
40 48
         }
@@ -61,14 +69,6 @@
61 69
             border-bottom: 1px solid #d1d5db; /* Gray border for all rows */
62 70
         }
63 71
 
64
-        .whitespace-normal {
65
-            white-space: normal;
66
-        }
67
-
68
-        .whitespace-nowrap {
69
-            white-space: nowrap;
70
-        }
71
-
72 72
         .category-header-row > td {
73 73
             background-color: #f3f4f6; /* Gray background for category names */
74 74
             font-weight: 600;

+ 14
- 2
resources/views/components/company/reports/report-pdf.blade.php View File

@@ -35,6 +35,14 @@
35 35
             color: #374151;
36 36
         }
37 37
 
38
+        .whitespace-normal {
39
+            white-space: normal;
40
+        }
41
+
42
+        .whitespace-nowrap {
43
+            white-space: nowrap;
44
+        }
45
+
38 46
         .title {
39 47
             font-size: 1.5rem;
40 48
         }
@@ -109,8 +117,12 @@
109 117
         @foreach($category->data as $account)
110 118
             <tr>
111 119
                 @foreach($account as $index => $cell)
112
-                    <td class="{{ $report->getAlignmentClass($index) }}">
113
-                        {{ $cell }}
120
+                    <td class="{{ $report->getAlignmentClass($index) }} {{ $index === 1 ? 'whitespace-normal' : 'whitespace-nowrap' }}">
121
+                        @if(isset($cell['id']) && isset($cell['name']))
122
+                            {{ $cell['name'] }}
123
+                        @else
124
+                            {{ $cell }}
125
+                        @endif
114 126
                     </td>
115 127
                 @endforeach
116 128
             </tr>

+ 77
- 54
resources/views/components/company/tables/reports/account-transactions.blade.php View File

@@ -1,68 +1,91 @@
1 1
 <table class="w-full table-auto divide-y divide-gray-200 dark:divide-white/5">
2 2
     <thead class="divide-y divide-gray-200 dark:divide-white/5">
3
-        <tr class="bg-gray-50 dark:bg-white/5">
4
-            @foreach($report->getHeaders() as $index => $header)
5
-                <th wire:key="header-{{ $index }}" class="px-3 py-3.5 sm:first-of-type:ps-6 sm:last-of-type:pe-6 {{ $report->getAlignmentClass($index) }}">
3
+    <tr class="bg-gray-50 dark:bg-white/5">
4
+        @foreach($report->getHeaders() as $index => $header)
5
+            <th wire:key="header-{{ $index }}"
6
+                class="px-3 py-3.5 sm:first-of-type:ps-6 sm:last-of-type:pe-6 {{ $report->getAlignmentClass($index) }}">
6 7
                     <span class="text-sm font-semibold text-gray-950 dark:text-white">
7 8
                         {{ $header }}
8 9
                     </span>
9
-                </th>
10
-            @endforeach
11
-        </tr>
10
+            </th>
11
+        @endforeach
12
+    </tr>
12 13
     </thead>
13 14
     @foreach($report->getCategories() as $categoryIndex => $category)
14
-        <tbody wire:key="category-{{ $categoryIndex }}" class="divide-y divide-gray-200 whitespace-nowrap dark:divide-white/5">
15
-            <!-- Category Header -->
16
-            <tr class="bg-gray-50 dark:bg-white/5">
17
-                <x-filament-tables::cell colspan="{{ count($report->getHeaders()) }}" class="text-left">
18
-                    <div class="px-3 py-2">
19
-                        @foreach ($category->header as $headerRow)
20
-                            <div class="text-sm {{ $loop->first ? 'font-semibold text-gray-950 dark:text-white' : 'text-gray-500 dark:text-white/50' }}">
21
-                                @foreach ($headerRow as $headerValue)
22
-                                    @if (!empty($headerValue))
23
-                                        {{ $headerValue }}
24
-                                    @endif
25
-                                @endforeach
26
-                            </div>
27
-                        @endforeach
28
-                    </div>
29
-                </x-filament-tables::cell>
30
-            </tr>
31
-            <!-- Transactions Data -->
32
-            @foreach($category->data as $dataIndex => $transaction)
33
-                <tr wire:key="category-{{ $categoryIndex }}-data-{{ $dataIndex }}"
34
-                    @class([
35
-                        'bg-gray-50 dark:bg-white/5' => $loop->first || $loop->last || $loop->remaining === 1,
36
-                    ])
37
-                >
38
-                    @foreach($transaction as $cellIndex => $cell)
39
-                        <x-filament-tables::cell
40
-                            wire:key="category-{{ $categoryIndex }}-data-{{ $dataIndex }}-cell-{{ $cellIndex }}"
41
-                             @class([
42
-                                $report->getAlignmentClass($cellIndex),
43
-                                'whitespace-normal' => $cellIndex === 1,
15
+        <tbody wire:key="category-{{ $categoryIndex }}"
16
+               class="divide-y divide-gray-200 whitespace-nowrap dark:divide-white/5">
17
+        <!-- Category Header -->
18
+        <tr class="bg-gray-50 dark:bg-white/5">
19
+            <x-filament-tables::cell colspan="{{ count($report->getHeaders()) }}" class="text-left">
20
+                <div class="px-3 py-2">
21
+                    @foreach ($category->header as $headerRow)
22
+                        <div
23
+                            class="text-sm {{ $loop->first ? 'font-semibold text-gray-950 dark:text-white' : 'text-gray-500 dark:text-white/50' }}">
24
+                            @foreach ($headerRow as $headerValue)
25
+                                @if (!empty($headerValue))
26
+                                    {{ $headerValue }}
27
+                                @endif
28
+                            @endforeach
29
+                        </div>
30
+                    @endforeach
31
+                </div>
32
+            </x-filament-tables::cell>
33
+        </tr>
34
+        <!-- Transactions Data -->
35
+        @foreach($category->data as $dataIndex => $transaction)
36
+            <tr wire:key="category-{{ $categoryIndex }}-data-{{ $dataIndex }}"
37
+                @class([
38
+                    'bg-gray-50 dark:bg-white/5' => $loop->first || $loop->last || $loop->remaining === 1,
39
+                ])
40
+            >
41
+                @foreach($transaction as $cellIndex => $cell)
42
+                    <x-filament-tables::cell
43
+                        wire:key="category-{{ $categoryIndex }}-data-{{ $dataIndex }}-cell-{{ $cellIndex }}"
44
+                        @class([
45
+                           $report->getAlignmentClass($cellIndex),
46
+                           'whitespace-normal' => $cellIndex === 1,
47
+                       ])
48
+                    >
49
+                        <div
50
+                            @class([
51
+                                'px-3 py-4 text-sm leading-6 text-gray-950 dark:text-white',
52
+                                'font-semibold' => $loop->parent->first || $loop->parent->last || $loop->parent->remaining === 1,
44 53
                             ])
45 54
                         >
46
-                            <div
47
-                                @class([
48
-                                    'px-3 py-4 text-sm leading-6 text-gray-950 dark:text-white',
49
-                                    'font-semibold' => $loop->parent->first || $loop->parent->last || $loop->parent->remaining === 1,
50
-                                ])
51
-                            >
55
+                            @if(is_array($cell) && isset($cell['description']))
56
+                                @if(isset($cell['id']) && $cell['tableAction'])
57
+                                    <x-filament::link
58
+                                        :href="\App\Filament\Company\Pages\Accounting\Transactions::getUrl(parameters: [
59
+                                            'tableAction' => $cell['tableAction'],
60
+                                            'tableActionRecord' => $cell['id'],
61
+                                        ])"
62
+                                        target="_blank"
63
+                                        color="primary"
64
+                                        icon="heroicon-o-arrow-top-right-on-square"
65
+                                        :icon-position="\Filament\Support\Enums\IconPosition::After"
66
+                                        icon-size="w-4 h-4 min-w-4 min-h-4"
67
+                                    >
68
+                                        {{ $cell['description'] }}
69
+                                    </x-filament::link>
70
+                                @else
71
+                                    {{ $cell['description'] }}
72
+                                @endif
73
+                            @else
52 74
                                 {{ $cell }}
53
-                            </div>
54
-                        </x-filament-tables::cell>
55
-                    @endforeach
56
-                </tr>
57
-            @endforeach
58
-            <!-- Spacer Row -->
59
-            @unless($loop->last)
60
-                <tr wire:key="category-{{ $categoryIndex }}-spacer">
61
-                    <x-filament-tables::cell colspan="{{ count($report->getHeaders()) }}">
62
-                        <div class="px-3 py-2 leading-6 invisible">Hidden Text</div>
75
+                            @endif
76
+                        </div>
63 77
                     </x-filament-tables::cell>
64
-                </tr>
65
-            @endunless
78
+                @endforeach
79
+            </tr>
80
+        @endforeach
81
+        <!-- Spacer Row -->
82
+        @unless($loop->last)
83
+            <tr wire:key="category-{{ $categoryIndex }}-spacer">
84
+                <x-filament-tables::cell colspan="{{ count($report->getHeaders()) }}">
85
+                    <div class="px-3 py-2 leading-6 invisible">Hidden Text</div>
86
+                </x-filament-tables::cell>
87
+            </tr>
88
+        @endunless
66 89
         </tbody>
67 90
     @endforeach
68 91
 </table>

+ 62
- 45
resources/views/components/company/tables/reports/detailed-report.blade.php View File

@@ -1,62 +1,79 @@
1
-<table class="w-full table-auto divide-y divide-gray-200 dark:divide-white/5">
1
+<table class="w-full table-auto divide-y divide-gray-200 dark:divide-white/5" x-data>
2 2
     <thead class="divide-y divide-gray-200 dark:divide-white/5">
3
+    <tr class="bg-gray-50 dark:bg-white/5">
4
+        @foreach($report->getHeaders() as $index => $header)
5
+            <th class="px-3 py-3.5 sm:first-of-type:ps-6 sm:last-of-type:pe-6 {{ $report->getAlignmentClass($index) }}">
6
+                <span class="text-sm font-semibold text-gray-950 dark:text-white">
7
+                    {{ $header }}
8
+                </span>
9
+            </th>
10
+        @endforeach
11
+    </tr>
12
+    </thead>
13
+    @foreach($report->getCategories() as $categoryIndex => $category)
14
+        <tbody class="divide-y divide-gray-200 whitespace-nowrap dark:divide-white/5">
3 15
         <tr class="bg-gray-50 dark:bg-white/5">
4
-            @foreach($report->getHeaders() as $index => $header)
5
-                <th wire:key="header-{{ $index }}" class="px-3 py-3.5 sm:first-of-type:ps-6 sm:last-of-type:pe-6 {{ $report->getAlignmentClass($index) }}">
6
-                    <span class="text-sm font-semibold text-gray-950 dark:text-white">
16
+            @foreach($category->header as $headerIndex => $header)
17
+                <x-filament-tables::cell class="{{ $report->getAlignmentClass($headerIndex) }}">
18
+                    <div class="px-3 py-2 text-sm font-semibold text-gray-950 dark:text-white">
7 19
                         {{ $header }}
8
-                    </span>
9
-                </th>
20
+                    </div>
21
+                </x-filament-tables::cell>
10 22
             @endforeach
11 23
         </tr>
12
-    </thead>
13
-    @foreach($report->getCategories() as $categoryIndex => $category)
14
-        <tbody wire:key="category-{{ $categoryIndex }}" class="divide-y divide-gray-200 whitespace-nowrap dark:divide-white/5">
15
-            <tr class="bg-gray-50 dark:bg-white/5">
16
-                @foreach($category->header as $headerIndex => $header)
17
-                    <x-filament-tables::cell wire:key="category-{{ $categoryIndex }}-header-{{ $headerIndex }}" class="{{ $report->getAlignmentClass($headerIndex) }}">
18
-                        <div class="px-3 py-2 text-sm font-semibold text-gray-950 dark:text-white">
19
-                            {{ $header }}
20
-                        </div>
21
-                    </x-filament-tables::cell>
22
-                @endforeach
23
-            </tr>
24
-            @foreach($category->data as $dataIndex => $account)
25
-                <tr wire:key="category-{{ $categoryIndex }}-data-{{ $dataIndex }}">
26
-                    @foreach($account as $cellIndex => $cell)
27
-                        <x-filament-tables::cell wire:key="category-{{ $categoryIndex }}-data-{{ $dataIndex }}-cell-{{ $cellIndex }}" class="{{ $report->getAlignmentClass($cellIndex) }}">
28
-                            <div class="px-3 py-4 text-sm leading-6 text-gray-950 dark:text-white">
24
+        @foreach($category->data as $dataIndex => $account)
25
+            <tr>
26
+                @foreach($account as $cellIndex => $cell)
27
+                    <x-filament-tables::cell class="{{ $report->getAlignmentClass($cellIndex) }}">
28
+                        <div class="px-3 py-4 text-sm leading-6 text-gray-950 dark:text-white">
29
+                            @if(is_array($cell) && isset($cell['name']))
30
+                                @if(isset($cell['id']))
31
+                                    <x-filament::link
32
+                                        color="primary"
33
+                                        target="_blank"
34
+                                        icon="heroicon-o-arrow-top-right-on-square"
35
+                                        :icon-position="\Filament\Support\Enums\IconPosition::After"
36
+                                        :icon-size="\Filament\Support\Enums\IconSize::Small"
37
+                                        href="{{ \App\Filament\Company\Pages\Reports\AccountTransactions::getUrl(['account_id' => $cell['id']]) }}"
38
+                                    >
39
+                                        {{ $cell['name'] }}
40
+                                    </x-filament::link>
41
+                                @else
42
+                                    {{ $cell['name'] }}
43
+                                @endif
44
+                            @else
29 45
                                 {{ $cell }}
30
-                            </div>
31
-                        </x-filament-tables::cell>
32
-                    @endforeach
33
-                </tr>
34
-            @endforeach
35
-            <tr wire:key="category-{{ $categoryIndex }}-summary">
36
-                @foreach($category->summary as $summaryIndex => $cell)
37
-                    <x-filament-tables::cell wire:key="category-{{ $categoryIndex }}-summary-{{ $summaryIndex }}" class="{{ $report->getAlignmentClass($summaryIndex) }}">
38
-                        <div class="px-3 py-2 text-sm leading-6 font-semibold text-gray-950 dark:text-white">
39
-                            {{ $cell }}
46
+                            @endif
40 47
                         </div>
41 48
                     </x-filament-tables::cell>
42 49
                 @endforeach
43 50
             </tr>
44
-            <tr wire:key="category-{{ $categoryIndex }}-spacer">
45
-                <x-filament-tables::cell colspan="{{ count($report->getHeaders()) }}">
46
-                    <div class="px-3 py-2 leading-6 invisible">Hidden Text</div>
47
-                </x-filament-tables::cell>
48
-            </tr>
49
-        </tbody>
50
-    @endforeach
51
-    <tfoot>
52
-        <tr class="bg-gray-50 dark:bg-white/5">
53
-            @foreach($report->getOverallTotals() as $index => $total)
54
-                <x-filament-tables::cell wire:key="footer-total-{{ $index }}" class="{{ $report->getAlignmentClass($index) }}">
51
+        @endforeach
52
+        <tr>
53
+            @foreach($category->summary as $summaryIndex => $cell)
54
+                <x-filament-tables::cell class="{{ $report->getAlignmentClass($summaryIndex) }}">
55 55
                     <div class="px-3 py-2 text-sm leading-6 font-semibold text-gray-950 dark:text-white">
56
-                        {{ $total }}
56
+                        {{ $cell }}
57 57
                     </div>
58 58
                 </x-filament-tables::cell>
59 59
             @endforeach
60 60
         </tr>
61
+        <tr>
62
+            <x-filament-tables::cell colspan="{{ count($report->getHeaders()) }}">
63
+                <div class="px-3 py-2 leading-6 invisible">Hidden Text</div>
64
+            </x-filament-tables::cell>
65
+        </tr>
66
+        </tbody>
67
+    @endforeach
68
+    <tfoot>
69
+    <tr class="bg-gray-50 dark:bg-white/5">
70
+        @foreach($report->getOverallTotals() as $index => $total)
71
+            <x-filament-tables::cell class="{{ $report->getAlignmentClass($index) }}">
72
+                <div class="px-3 py-2 text-sm leading-6 font-semibold text-gray-950 dark:text-white">
73
+                    {{ $total }}
74
+                </div>
75
+            </x-filament-tables::cell>
76
+        @endforeach
77
+    </tr>
61 78
     </tfoot>
62 79
 </table>

+ 9
- 8
resources/views/filament/company/pages/reports/detailed-report.blade.php View File

@@ -14,19 +14,20 @@
14 14
                 </x-filament::button>
15 15
             </div>
16 16
         </form>
17
-        <div class="relative divide-y divide-gray-200 overflow-x-auto dark:divide-white/10 dark:border-t-white/10 min-h-64">
18
-            <div wire:init="loadReportData" class="flex items-center justify-center w-full h-full absolute">
19
-                <div wire:loading wire:target="loadReportData">
20
-                    <x-filament::loading-indicator class="p-6 text-primary-700 dark:text-primary-300" />
21
-                </div>
22
-            </div>
23
-
17
+        <div wire:init="loadReportData"
18
+             class="relative divide-y divide-gray-200 overflow-x-auto dark:divide-white/10 dark:border-t-white/10 min-h-64">
24 19
             @if($this->reportLoaded)
25 20
                 <div wire:loading.remove wire:target="loadReportData">
26 21
                     @if($this->report)
27
-                        <x-company.tables.reports.detailed-report :report="$this->report" />
22
+                        <x-company.tables.reports.detailed-report :report="$this->report"/>
28 23
                     @endif
29 24
                 </div>
25
+            @else
26
+                <div class="absolute inset-0 flex items-center justify-center">
27
+                    <div wire:loading wire:target="loadReportData">
28
+                        <x-filament::loading-indicator class="p-6 text-primary-700 dark:text-primary-300"/>
29
+                    </div>
30
+                </div>
30 31
             @endif
31 32
         </div>
32 33
         <div class="es-table__footer-ctn border-t border-gray-200"></div>

Loading…
Cancel
Save