Pārlūkot izejas kodu

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

Development 3.x
3.x
Andrew Wallo 11 mēnešus atpakaļ
vecāks
revīzija
9902962a66
Revīzijas autora e-pasta adrese nav piesaistīta nevienam kontam
41 mainītis faili ar 2488 papildinājumiem un 562 dzēšanām
  1. 19
    9
      app/Collections/Accounting/JournalEntryCollection.php
  2. 4
    0
      app/Contracts/ExportableReport.php
  3. 10
    0
      app/DTO/CashFlowOverviewDTO.php
  4. 5
    0
      app/DTO/ReportDTO.php
  5. 55
    0
      app/Enums/Accounting/AccountType.php
  6. 2
    1
      app/Filament/Company/Pages/Reports.php
  7. 9
    9
      app/Filament/Company/Pages/Reports/AccountBalances.php
  8. 26
    10
      app/Filament/Company/Pages/Reports/AccountTransactions.php
  9. 5
    5
      app/Filament/Company/Pages/Reports/BalanceSheet.php
  10. 23
    0
      app/Filament/Company/Pages/Reports/BaseReportPage.php
  11. 87
    0
      app/Filament/Company/Pages/Reports/CashFlowStatement.php
  12. 5
    5
      app/Filament/Company/Pages/Reports/IncomeStatement.php
  13. 6
    6
      app/Filament/Company/Pages/Reports/TrialBalance.php
  14. 2
    0
      app/Models/Accounting/AccountSubtype.php
  15. 105
    1
      app/Services/AccountService.php
  16. 1
    0
      app/Services/ChartOfAccountsService.php
  17. 44
    0
      app/Services/ExportService.php
  18. 262
    16
      app/Services/ReportService.php
  19. 18
    15
      app/Transformers/BalanceSheetReportTransformer.php
  20. 10
    0
      app/Transformers/BaseReportTransformer.php
  21. 338
    0
      app/Transformers/CashFlowStatementReportTransformer.php
  22. 22
    8
      app/Utilities/Currency/CurrencyConverter.php
  23. 245
    245
      composer.lock
  24. 44
    0
      config/chart-of-accounts.php
  25. 17
    3
      database/factories/Accounting/TransactionFactory.php
  26. 28
    0
      database/migrations/2024_11_02_182328_add_inverse_cash_flow_to_account_subtypes_table.php
  27. 130
    100
      package-lock.json
  28. 53
    48
      resources/views/components/company/reports/account-transactions-report-pdf.blade.php
  29. 334
    0
      resources/views/components/company/reports/cash-flow-statement-pdf.blade.php
  30. 92
    61
      resources/views/components/company/reports/report-pdf.blade.php
  31. 5
    1
      resources/views/components/company/tables/cell.blade.php
  32. 1
    1
      resources/views/components/company/tables/footer.blade.php
  33. 7
    2
      resources/views/components/company/tables/reports/balance-sheet-summary.blade.php
  34. 15
    6
      resources/views/components/company/tables/reports/balance-sheet.blade.php
  35. 71
    0
      resources/views/components/company/tables/reports/cash-flow-statement-summary.blade.php
  36. 213
    0
      resources/views/components/company/tables/reports/cash-flow-statement.blade.php
  37. 10
    7
      resources/views/components/company/tables/reports/income-statement-summary.blade.php
  38. 77
    0
      resources/views/components/company/tables/reports/income-statement.blade.php
  39. 87
    0
      resources/views/filament/company/pages/reports/cash-flow-statement.blade.php
  40. 1
    1
      resources/views/filament/company/pages/reports/income-statement.blade.php
  41. 0
    2
      tests/Feature/Reports/TrialBalanceReportTest.php

+ 19
- 9
app/Collections/Accounting/JournalEntryCollection.php Parādīt failu

@@ -2,9 +2,9 @@
2 2
 
3 3
 namespace App\Collections\Accounting;
4 4
 
5
-use App\Enums\Accounting\JournalEntryType;
6 5
 use App\Models\Accounting\JournalEntry;
7 6
 use App\Utilities\Currency\CurrencyAccessor;
7
+use App\Utilities\Currency\CurrencyConverter;
8 8
 use App\ValueObjects\Money;
9 9
 use Illuminate\Database\Eloquent\Collection;
10 10
 
@@ -12,20 +12,30 @@ class JournalEntryCollection extends Collection
12 12
 {
13 13
     public function sumDebits(): Money
14 14
     {
15
-        $total = $this->where('type', JournalEntryType::Debit)
16
-            ->sum(static function (JournalEntry $item) {
17
-                return $item->rawValue('amount');
18
-            });
15
+        $total = $this->reduce(static function ($carry, JournalEntry $item) {
16
+            if ($item->type->isDebit()) {
17
+                $amountAsInteger = CurrencyConverter::convertToCents($item->amount);
18
+
19
+                return bcadd($carry, $amountAsInteger, 0);
20
+            }
21
+
22
+            return $carry;
23
+        }, 0);
19 24
 
20 25
         return new Money($total, CurrencyAccessor::getDefaultCurrency());
21 26
     }
22 27
 
23 28
     public function sumCredits(): Money
24 29
     {
25
-        $total = $this->where('type', JournalEntryType::Credit)
26
-            ->sum(static function (JournalEntry $item) {
27
-                return $item->rawValue('amount');
28
-            });
30
+        $total = $this->reduce(static function ($carry, JournalEntry $item) {
31
+            if ($item->type->isCredit()) {
32
+                $amountAsInteger = CurrencyConverter::convertToCents($item->amount);
33
+
34
+                return bcadd($carry, $amountAsInteger, 0);
35
+            }
36
+
37
+            return $carry;
38
+        }, 0);
29 39
 
30 40
         return new Money($total, CurrencyAccessor::getDefaultCurrency());
31 41
     }

+ 4
- 0
app/Contracts/ExportableReport.php Parādīt failu

@@ -26,4 +26,8 @@ interface ExportableReport
26 26
     public function getPdfView(): string;
27 27
 
28 28
     public function getAlignmentClass(string $columnName): string;
29
+
30
+    public function getStartDate(): ?string;
31
+
32
+    public function getEndDate(): ?string;
29 33
 }

+ 10
- 0
app/DTO/CashFlowOverviewDTO.php Parādīt failu

@@ -0,0 +1,10 @@
1
+<?php
2
+
3
+namespace App\DTO;
4
+
5
+class CashFlowOverviewDTO
6
+{
7
+    public function __construct(
8
+        public array $categories,
9
+    ) {}
10
+}

+ 5
- 0
app/DTO/ReportDTO.php Parādīt failu

@@ -2,6 +2,8 @@
2 2
 
3 3
 namespace App\DTO;
4 4
 
5
+use Illuminate\Support\Carbon;
6
+
5 7
 class ReportDTO
6 8
 {
7 9
     public function __construct(
@@ -12,5 +14,8 @@ class ReportDTO
12 14
         public ?AccountBalanceDTO $overallTotal = null,
13 15
         public array $fields = [],
14 16
         public ?string $reportType = null,
17
+        public ?CashFlowOverviewDTO $overview = null,
18
+        public ?Carbon $startDate = null,
19
+        public ?Carbon $endDate = null,
15 20
     ) {}
16 21
 }

+ 55
- 0
app/Enums/Accounting/AccountType.php Parādīt failu

@@ -85,4 +85,59 @@ enum AccountType: string implements HasLabel
85 85
             default => false,
86 86
         };
87 87
     }
88
+
89
+    public function isNormalDebitBalance(): bool
90
+    {
91
+        return in_array($this, [
92
+            self::CurrentAsset,
93
+            self::NonCurrentAsset,
94
+            self::ContraLiability,
95
+            self::ContraEquity,
96
+            self::ContraRevenue,
97
+            self::OperatingExpense,
98
+            self::NonOperatingExpense,
99
+            self::UncategorizedExpense,
100
+        ], true);
101
+    }
102
+
103
+    public function isNormalCreditBalance(): bool
104
+    {
105
+        return ! $this->isNormalDebitBalance();
106
+    }
107
+
108
+    /**
109
+     * Determines if the account is a nominal account.
110
+     *
111
+     * In accounting, nominal accounts are temporary accounts that are closed at the end of each accounting period,
112
+     * with their net balances transferred to Retained Earnings (a real account).
113
+     */
114
+    public function isNominal(): bool
115
+    {
116
+        return in_array($this->getCategory(), [
117
+            AccountCategory::Revenue,
118
+            AccountCategory::Expense,
119
+        ], true);
120
+    }
121
+
122
+    /**
123
+     * Determines if the account is a real account.
124
+     *
125
+     * In accounting, assets, liabilities, and equity are real accounts which are permanent accounts that retain their balances across accounting periods.
126
+     * They are not closed at the end of each accounting period.
127
+     */
128
+    public function isReal(): bool
129
+    {
130
+        return ! $this->isNominal();
131
+    }
132
+
133
+    public function isContra(): bool
134
+    {
135
+        return in_array($this, [
136
+            self::ContraAsset,
137
+            self::ContraLiability,
138
+            self::ContraEquity,
139
+            self::ContraRevenue,
140
+            self::ContraExpense,
141
+        ], true);
142
+    }
88 143
 }

+ 2
- 1
app/Filament/Company/Pages/Reports.php Parādīt failu

@@ -5,6 +5,7 @@ namespace App\Filament\Company\Pages;
5 5
 use App\Filament\Company\Pages\Reports\AccountBalances;
6 6
 use App\Filament\Company\Pages\Reports\AccountTransactions;
7 7
 use App\Filament\Company\Pages\Reports\BalanceSheet;
8
+use App\Filament\Company\Pages\Reports\CashFlowStatement;
8 9
 use App\Filament\Company\Pages\Reports\IncomeStatement;
9 10
 use App\Filament\Company\Pages\Reports\TrialBalance;
10 11
 use App\Infolists\Components\ReportEntry;
@@ -49,7 +50,7 @@ class Reports extends Page
49 50
                             ->description('Shows cash inflows and outflows over a specific period of time.')
50 51
                             ->icon('heroicon-o-document-currency-dollar')
51 52
                             ->iconColor(Color::Cyan)
52
-                            ->url('#'),
53
+                            ->url(CashFlowStatement::getUrl()),
53 54
                     ]),
54 55
                 Section::make('Detailed Reports')
55 56
                     ->aside()

+ 9
- 9
app/Filament/Company/Pages/Reports/AccountBalances.php Parādīt failu

@@ -38,30 +38,30 @@ class AccountBalances extends BaseReportPage
38 38
     {
39 39
         return [
40 40
             Column::make('account_code')
41
-                ->label('Account Code')
42
-                ->toggleable()
43
-                ->alignment(Alignment::Center),
41
+                ->label('ACCOUNT CODE')
42
+                ->toggleable(isToggledHiddenByDefault: true)
43
+                ->alignment(Alignment::Left),
44 44
             Column::make('account_name')
45
-                ->label('Account')
45
+                ->label('ACCOUNT')
46 46
                 ->alignment(Alignment::Left),
47 47
             Column::make('starting_balance')
48
-                ->label('Starting Balance')
48
+                ->label('STARTING BALANCE')
49 49
                 ->toggleable()
50 50
                 ->alignment(Alignment::Right),
51 51
             Column::make('debit_balance')
52
-                ->label('Debit')
52
+                ->label('DEBIT')
53 53
                 ->toggleable()
54 54
                 ->alignment(Alignment::Right),
55 55
             Column::make('credit_balance')
56
-                ->label('Credit')
56
+                ->label('CREDIT')
57 57
                 ->toggleable()
58 58
                 ->alignment(Alignment::Right),
59 59
             Column::make('net_movement')
60
-                ->label('Net Movement')
60
+                ->label('NET MOVEMENT')
61 61
                 ->toggleable()
62 62
                 ->alignment(Alignment::Right),
63 63
             Column::make('ending_balance')
64
-                ->label('Ending Balance')
64
+                ->label('ENDING BALANCE')
65 65
                 ->toggleable()
66 66
                 ->alignment(Alignment::Right),
67 67
         ];

+ 26
- 10
app/Filament/Company/Pages/Reports/AccountTransactions.php Parādīt failu

@@ -6,6 +6,7 @@ use App\Contracts\ExportableReport;
6 6
 use App\DTO\ReportDTO;
7 7
 use App\Filament\Company\Pages\Accounting\Transactions;
8 8
 use App\Models\Accounting\Account;
9
+use App\Models\Accounting\JournalEntry;
9 10
 use App\Services\ExportService;
10 11
 use App\Services\ReportService;
11 12
 use App\Support\Column;
@@ -18,6 +19,7 @@ use Filament\Support\Enums\MaxWidth;
18 19
 use Filament\Tables\Actions\Action;
19 20
 use Guava\FilamentClusters\Forms\Cluster;
20 21
 use Illuminate\Contracts\Support\Htmlable;
22
+use Illuminate\Database\Eloquent\Builder;
21 23
 use Illuminate\Support\Collection;
22 24
 use Symfony\Component\HttpFoundation\StreamedResponse;
23 25
 
@@ -58,20 +60,20 @@ class AccountTransactions extends BaseReportPage
58 60
     {
59 61
         return [
60 62
             Column::make('date')
61
-                ->label('Date')
63
+                ->label('DATE')
62 64
                 ->markAsDate()
63 65
                 ->alignment(Alignment::Left),
64 66
             Column::make('description')
65
-                ->label('Description')
67
+                ->label('DESCRIPTION')
66 68
                 ->alignment(Alignment::Left),
67 69
             Column::make('debit')
68
-                ->label('Debit')
70
+                ->label('DEBIT')
69 71
                 ->alignment(Alignment::Right),
70 72
             Column::make('credit')
71
-                ->label('Credit')
73
+                ->label('CREDIT')
72 74
                 ->alignment(Alignment::Right),
73 75
             Column::make('balance')
74
-                ->label('Running Balance')
76
+                ->label('RUNNING BALANCE')
75 77
                 ->alignment(Alignment::Right),
76 78
         ];
77 79
     }
@@ -162,12 +164,26 @@ class AccountTransactions extends BaseReportPage
162 164
         ];
163 165
     }
164 166
 
165
-    public function tableHasEmptyState(): bool
167
+    public function hasNoTransactionsForSelectedAccount(): bool
166 168
     {
167
-        if ($this->report) {
168
-            return empty($this->report->getCategories());
169
-        } else {
170
-            return true;
169
+        $query = JournalEntry::query();
170
+        $selectedAccountId = $this->getFilterState('selectedAccount');
171
+
172
+        if ($selectedAccountId !== 'all') {
173
+            $query->where('account_id', $selectedAccountId);
171 174
         }
175
+
176
+        if ($this->getFilterState('startDate') && $this->getFilterState('endDate')) {
177
+            $query->whereHas('transaction', function (Builder $query) {
178
+                $query->whereBetween('posted_at', [$this->getFormattedStartDate(), $this->getFormattedEndDate()]);
179
+            });
180
+        }
181
+
182
+        return $query->doesntExist();
183
+    }
184
+
185
+    public function tableHasEmptyState(): bool
186
+    {
187
+        return $this->hasNoTransactionsForSelectedAccount();
172 188
     }
173 189
 }

+ 5
- 5
app/Filament/Company/Pages/Reports/BalanceSheet.php Parādīt failu

@@ -37,14 +37,14 @@ class BalanceSheet extends BaseReportPage
37 37
     {
38 38
         return [
39 39
             Column::make('account_code')
40
-                ->label('Account Code')
41
-                ->toggleable()
42
-                ->alignment(Alignment::Center),
40
+                ->label('ACCOUNT CODE')
41
+                ->toggleable(isToggledHiddenByDefault: true)
42
+                ->alignment(Alignment::Left),
43 43
             Column::make('account_name')
44
-                ->label('Account')
44
+                ->label('ACCOUNTS')
45 45
                 ->alignment(Alignment::Left),
46 46
             Column::make('ending_balance')
47
-                ->label('Amount')
47
+                ->label($this->getDisplayAsOfDate())
48 48
                 ->alignment(Alignment::Right),
49 49
         ];
50 50
     }

+ 23
- 0
app/Filament/Company/Pages/Reports/BaseReportPage.php Parādīt failu

@@ -169,6 +169,29 @@ abstract class BaseReportPage extends Page
169 169
         return Carbon::parse($this->getFilterState('asOfDate'))->endOfDay()->toDateTimeString();
170 170
     }
171 171
 
172
+    public function getDisplayAsOfDate(): string
173
+    {
174
+        return Carbon::parse($this->getFilterState('asOfDate'))->toDefaultDateFormat();
175
+    }
176
+
177
+    public function getDisplayStartDate(): string
178
+    {
179
+        return Carbon::parse($this->getFilterState('startDate'))->toDefaultDateFormat();
180
+    }
181
+
182
+    public function getDisplayEndDate(): string
183
+    {
184
+        return Carbon::parse($this->getFilterState('endDate'))->toDefaultDateFormat();
185
+    }
186
+
187
+    public function getDisplayDateRange(): string
188
+    {
189
+        $startDate = Carbon::parse($this->getFilterState('startDate'));
190
+        $endDate = Carbon::parse($this->getFilterState('endDate'));
191
+
192
+        return $startDate->toDefaultDateFormat() . ' - ' . $endDate->toDefaultDateFormat();
193
+    }
194
+
172 195
     protected function getHeaderActions(): array
173 196
     {
174 197
         return [

+ 87
- 0
app/Filament/Company/Pages/Reports/CashFlowStatement.php Parādīt failu

@@ -0,0 +1,87 @@
1
+<?php
2
+
3
+namespace App\Filament\Company\Pages\Reports;
4
+
5
+use App\Contracts\ExportableReport;
6
+use App\DTO\ReportDTO;
7
+use App\Services\ExportService;
8
+use App\Services\ReportService;
9
+use App\Support\Column;
10
+use App\Transformers\CashFlowStatementReportTransformer;
11
+use Filament\Forms\Form;
12
+use Filament\Support\Enums\Alignment;
13
+use Guava\FilamentClusters\Forms\Cluster;
14
+use Livewire\Attributes\Url;
15
+use Symfony\Component\HttpFoundation\StreamedResponse;
16
+
17
+class CashFlowStatement extends BaseReportPage
18
+{
19
+    protected static string $view = 'filament.company.pages.reports.cash-flow-statement';
20
+
21
+    protected static ?string $slug = 'reports/cash-flow-statement';
22
+
23
+    protected static bool $shouldRegisterNavigation = false;
24
+
25
+    protected ReportService $reportService;
26
+
27
+    protected ExportService $exportService;
28
+
29
+    #[Url]
30
+    public ?string $activeTab = 'summary';
31
+
32
+    public function boot(ReportService $reportService, ExportService $exportService): void
33
+    {
34
+        $this->reportService = $reportService;
35
+        $this->exportService = $exportService;
36
+    }
37
+
38
+    public function getTable(): array
39
+    {
40
+        return [
41
+            Column::make('account_code')
42
+                ->label('ACCOUNT CODE')
43
+                ->toggleable(isToggledHiddenByDefault: true)
44
+                ->alignment(Alignment::Left),
45
+            Column::make('account_name')
46
+                ->label('CASH INFLOWS AND OUTFLOWS')
47
+                ->alignment(Alignment::Left),
48
+            Column::make('net_movement')
49
+                ->label($this->getDisplayDateRange())
50
+                ->alignment(Alignment::Right),
51
+        ];
52
+    }
53
+
54
+    public function filtersForm(Form $form): Form
55
+    {
56
+        return $form
57
+            ->inlineLabel()
58
+            ->columns()
59
+            ->schema([
60
+                $this->getDateRangeFormComponent(),
61
+                Cluster::make([
62
+                    $this->getStartDateFormComponent(),
63
+                    $this->getEndDateFormComponent(),
64
+                ])->hiddenLabel(),
65
+            ]);
66
+    }
67
+
68
+    protected function buildReport(array $columns): ReportDTO
69
+    {
70
+        return $this->reportService->buildCashFlowStatementReport($this->getFormattedStartDate(), $this->getFormattedEndDate(), $columns);
71
+    }
72
+
73
+    protected function getTransformer(ReportDTO $reportDTO): ExportableReport
74
+    {
75
+        return new CashFlowStatementReportTransformer($reportDTO);
76
+    }
77
+
78
+    public function exportCSV(): StreamedResponse
79
+    {
80
+        return $this->exportService->exportToCsv($this->company, $this->report, $this->getFilterState('startDate'), $this->getFilterState('endDate'));
81
+    }
82
+
83
+    public function exportPDF(): StreamedResponse
84
+    {
85
+        return $this->exportService->exportToPdf($this->company, $this->report, $this->getFilterState('startDate'), $this->getFilterState('endDate'));
86
+    }
87
+}

+ 5
- 5
app/Filament/Company/Pages/Reports/IncomeStatement.php Parādīt failu

@@ -39,14 +39,14 @@ class IncomeStatement extends BaseReportPage
39 39
     {
40 40
         return [
41 41
             Column::make('account_code')
42
-                ->label('Account Code')
43
-                ->toggleable()
44
-                ->alignment(Alignment::Center),
42
+                ->label('ACCOUNT CODE')
43
+                ->toggleable(isToggledHiddenByDefault: true)
44
+                ->alignment(Alignment::Left),
45 45
             Column::make('account_name')
46
-                ->label('Account')
46
+                ->label('ACCOUNTS')
47 47
                 ->alignment(Alignment::Left),
48 48
             Column::make('net_movement')
49
-                ->label('Amount')
49
+                ->label($this->getDisplayDateRange())
50 50
                 ->alignment(Alignment::Right),
51 51
         ];
52 52
     }

+ 6
- 6
app/Filament/Company/Pages/Reports/TrialBalance.php Parādīt failu

@@ -43,17 +43,17 @@ class TrialBalance extends BaseReportPage
43 43
     {
44 44
         return [
45 45
             Column::make('account_code')
46
-                ->label('Account Code')
47
-                ->toggleable()
48
-                ->alignment(Alignment::Center),
46
+                ->label('ACCOUNT CODE')
47
+                ->toggleable(isToggledHiddenByDefault: true)
48
+                ->alignment(Alignment::Left),
49 49
             Column::make('account_name')
50
-                ->label('Account')
50
+                ->label('ACCOUNTS')
51 51
                 ->alignment(Alignment::Left),
52 52
             Column::make('debit_balance')
53
-                ->label('Debit')
53
+                ->label('DEBIT')
54 54
                 ->alignment(Alignment::Right),
55 55
             Column::make('credit_balance')
56
-                ->label('Credit')
56
+                ->label('CREDIT')
57 57
                 ->alignment(Alignment::Right),
58 58
         ];
59 59
     }

+ 2
- 0
app/Models/Accounting/AccountSubtype.php Parādīt failu

@@ -21,6 +21,7 @@ class AccountSubtype extends Model
21 21
     protected $fillable = [
22 22
         'company_id',
23 23
         'multi_currency',
24
+        'inverse_cash_flow',
24 25
         'category',
25 26
         'type',
26 27
         'name',
@@ -29,6 +30,7 @@ class AccountSubtype extends Model
29 30
 
30 31
     protected $casts = [
31 32
         'multi_currency' => 'boolean',
33
+        'inverse_cash_flow' => 'boolean',
32 34
         'category' => AccountCategory::class,
33 35
         'type' => AccountType::class,
34 36
     ];

+ 105
- 1
app/Services/AccountService.php Parādīt failu

@@ -171,7 +171,67 @@ class AccountService
171 171
                     ->where('transactions.posted_at', '<=', $endDate);
172 172
             })
173 173
             ->groupBy('accounts.id')
174
-            ->with(['subtype:id,name']);
174
+            ->with(['subtype:id,name,inverse_cash_flow']);
175
+
176
+        if (! empty($accountIds)) {
177
+            $query->whereIn('accounts.id', $accountIds);
178
+        }
179
+
180
+        $query->addBinding([$startDate, $startDate, $startDate, $startDate, $startDate, $endDate, $startDate, $endDate], 'select');
181
+
182
+        return $query;
183
+    }
184
+
185
+    public function getCashFlowAccountBalances(string $startDate, string $endDate, array $accountIds = []): Builder
186
+    {
187
+        $accountIds = array_map('intval', $accountIds);
188
+
189
+        $query = Account::query()
190
+            ->select([
191
+                'accounts.id',
192
+                'accounts.name',
193
+                'accounts.category',
194
+                'accounts.type',
195
+                'accounts.subtype_id',
196
+                'accounts.currency_code',
197
+                'accounts.code',
198
+            ])
199
+            ->addSelect([
200
+                DB::raw("
201
+                    COALESCE(
202
+                        IF(accounts.category IN ('asset', 'expense'),
203
+                            SUM(IF(journal_entries.type = 'debit' AND transactions.posted_at < ?, journal_entries.amount, 0)) -
204
+                            SUM(IF(journal_entries.type = 'credit' AND transactions.posted_at < ?, journal_entries.amount, 0)),
205
+                            SUM(IF(journal_entries.type = 'credit' AND transactions.posted_at < ?, journal_entries.amount, 0)) -
206
+                            SUM(IF(journal_entries.type = 'debit' AND transactions.posted_at < ?, journal_entries.amount, 0))
207
+                        ), 0
208
+                    ) AS starting_balance
209
+                "),
210
+                DB::raw("
211
+                    COALESCE(SUM(
212
+                        IF(journal_entries.type = 'debit' AND transactions.posted_at BETWEEN ? AND ?, journal_entries.amount, 0)
213
+                    ), 0) AS total_debit
214
+                "),
215
+                DB::raw("
216
+                    COALESCE(SUM(
217
+                        IF(journal_entries.type = 'credit' AND transactions.posted_at BETWEEN ? AND ?, journal_entries.amount, 0)
218
+                    ), 0) AS total_credit
219
+                "),
220
+            ])
221
+            ->join('journal_entries', 'journal_entries.account_id', '=', 'accounts.id')
222
+            ->join('transactions', function (JoinClause $join) use ($endDate) {
223
+                $join->on('transactions.id', '=', 'journal_entries.transaction_id')
224
+                    ->where('transactions.posted_at', '<=', $endDate);
225
+            })
226
+            ->whereExists(function (\Illuminate\Database\Query\Builder $subQuery) {
227
+                $subQuery->select(DB::raw(1))
228
+                    ->from('journal_entries as je')
229
+                    ->join('accounts as bank_accounts', 'bank_accounts.id', '=', 'je.account_id')
230
+                    ->whereNotNull('bank_accounts.bank_account_id')
231
+                    ->whereColumn('je.transaction_id', 'journal_entries.transaction_id');
232
+            })
233
+            ->groupBy('accounts.id')
234
+            ->with(['subtype:id,name,inverse_cash_flow']);
175 235
 
176 236
         if (! empty($accountIds)) {
177 237
             $query->whereIn('accounts.id', $accountIds);
@@ -224,6 +284,50 @@ class AccountService
224 284
         return new Money($totalBalance, CurrencyAccessor::getDefaultCurrency());
225 285
     }
226 286
 
287
+    public function getStartingBalanceForAllBankAccounts(string $startDate): Money
288
+    {
289
+        $accountIds = Account::whereHas('bankAccount')
290
+            ->pluck('id')
291
+            ->toArray();
292
+
293
+        if (empty($accountIds)) {
294
+            return new Money(0, CurrencyAccessor::getDefaultCurrency());
295
+        }
296
+
297
+        $result = DB::table('journal_entries')
298
+            ->join('transactions', function (JoinClause $join) use ($startDate) {
299
+                $join->on('transactions.id', '=', 'journal_entries.transaction_id')
300
+                    ->where('transactions.posted_at', '<', $startDate);
301
+            })
302
+            ->whereIn('journal_entries.account_id', $accountIds)
303
+            ->selectRaw('
304
+            SUM(CASE
305
+                WHEN transactions.posted_at < ? AND journal_entries.type = "debit" THEN journal_entries.amount
306
+                WHEN transactions.posted_at < ? AND journal_entries.type = "credit" THEN -journal_entries.amount
307
+                ELSE 0
308
+            END) AS totalStartingBalance
309
+        ', [
310
+                $startDate,
311
+                $startDate,
312
+            ])
313
+            ->first();
314
+
315
+        return new Money($result->totalStartingBalance ?? 0, CurrencyAccessor::getDefaultCurrency());
316
+    }
317
+
318
+    public function getBankAccountBalances(string $startDate, string $endDate): Builder | array
319
+    {
320
+        $accountIds = Account::whereHas('bankAccount')
321
+            ->pluck('id')
322
+            ->toArray();
323
+
324
+        if (empty($accountIds)) {
325
+            return [];
326
+        }
327
+
328
+        return $this->getAccountBalances($startDate, $endDate, $accountIds);
329
+    }
330
+
227 331
     public function getEarliestTransactionDate(): string
228 332
     {
229 333
         $earliestDate = Transaction::min('posted_at');

+ 1
- 0
app/Services/ChartOfAccountsService.php Parādīt failu

@@ -21,6 +21,7 @@ class ChartOfAccountsService
21 21
                 $subtype = $company->accountSubtypes()
22 22
                     ->createQuietly([
23 23
                         'multi_currency' => $subtypeConfig['multi_currency'] ?? false,
24
+                        'inverse_cash_flow' => $subtypeConfig['inverse_cash_flow'] ?? false,
24 25
                         'category' => AccountType::from($type)->getCategory()->value,
25 26
                         'type' => $type,
26 27
                         'name' => $subtypeName,

+ 44
- 0
app/Services/ExportService.php Parādīt failu

@@ -4,6 +4,7 @@ namespace App\Services;
4 4
 
5 5
 use App\Contracts\ExportableReport;
6 6
 use App\Models\Company;
7
+use App\Transformers\CashFlowStatementReportTransformer;
7 8
 use Barryvdh\Snappy\Facades\SnappyPdf;
8 9
 use Carbon\Exceptions\InvalidFormatException;
9 10
 use Illuminate\Support\Carbon;
@@ -72,6 +73,10 @@ class ExportService
72 73
                 $csv->insertOne([]);
73 74
             }
74 75
 
76
+            if ($report->getTitle() === 'Cash Flow Statement') {
77
+                $this->writeOverviewTableToCsv($csv, $report);
78
+            }
79
+
75 80
             if (filled($report->getOverallTotals())) {
76 81
                 $csv->insertOne($report->getOverallTotals());
77 82
             }
@@ -80,6 +85,45 @@ class ExportService
80 85
         return response()->streamDownload($callback, $filename, $headers);
81 86
     }
82 87
 
88
+    /**
89
+     * @throws CannotInsertRecord
90
+     * @throws Exception
91
+     */
92
+    protected function writeOverviewTableToCsv(Writer $csv, ExportableReport $report): void
93
+    {
94
+        /** @var CashFlowStatementReportTransformer $report */
95
+        $headers = $report->getOverviewHeaders();
96
+
97
+        if (filled($headers)) {
98
+            $csv->insertOne($headers);
99
+        }
100
+
101
+        foreach ($report->getOverview() as $overviewCategory) {
102
+            if (filled($overviewCategory->header)) {
103
+                $this->writeDataRowsToCsv($csv, $overviewCategory->header, $overviewCategory->data, $report->getColumns());
104
+            }
105
+
106
+            if (filled($overviewCategory->summary)) {
107
+                $csv->insertOne($overviewCategory->summary);
108
+            }
109
+
110
+            if ($overviewCategory->header['account_name'] === 'Starting Balance') {
111
+                foreach ($report->getOverviewAlignedWithColumns() as $summaryRow) {
112
+                    $row = [];
113
+
114
+                    foreach ($report->getColumns() as $column) {
115
+                        $columnName = $column->getName();
116
+                        $row[] = $summaryRow[$columnName] ?? '';
117
+                    }
118
+
119
+                    if (array_filter($row)) {
120
+                        $csv->insertOne($row);
121
+                    }
122
+                }
123
+            }
124
+        }
125
+    }
126
+
83 127
     public function exportToPdf(Company $company, ExportableReport $report, ?string $startDate = null, ?string $endDate = null): StreamedResponse
84 128
     {
85 129
         if ($startDate && $endDate) {

+ 262
- 16
app/Services/ReportService.php Parādīt failu

@@ -7,12 +7,14 @@ use App\DTO\AccountCategoryDTO;
7 7
 use App\DTO\AccountDTO;
8 8
 use App\DTO\AccountTransactionDTO;
9 9
 use App\DTO\AccountTypeDTO;
10
+use App\DTO\CashFlowOverviewDTO;
10 11
 use App\DTO\ReportDTO;
11 12
 use App\Enums\Accounting\AccountCategory;
12 13
 use App\Enums\Accounting\AccountType;
13 14
 use App\Models\Accounting\Account;
14 15
 use App\Support\Column;
15 16
 use App\Utilities\Currency\CurrencyAccessor;
17
+use App\Utilities\Currency\CurrencyConverter;
16 18
 use App\ValueObjects\Money;
17 19
 use Illuminate\Database\Eloquent\Builder;
18 20
 use Illuminate\Support\Carbon;
@@ -44,7 +46,9 @@ class ReportService
44 46
     {
45 47
         $orderedCategories = AccountCategory::getOrderedCategories();
46 48
 
47
-        $accounts = $this->accountService->getAccountBalances($startDate, $endDate)->get();
49
+        $accounts = $this->accountService->getAccountBalances($startDate, $endDate)
50
+            ->orderByRaw('LENGTH(code), code')
51
+            ->get();
48 52
 
49 53
         $columnNameKeys = array_map(fn (Column $column) => $column->getName(), $columns);
50 54
 
@@ -52,8 +56,7 @@ class ReportService
52 56
         $reportTotalBalances = [];
53 57
 
54 58
         foreach ($orderedCategories as $category) {
55
-            $accountsInCategory = $accounts->where('category', $category)
56
-                ->sortBy('code', SORT_NATURAL);
59
+            $accountsInCategory = $accounts->where('category', $category);
57 60
 
58 61
             $relevantFields = array_intersect($category->getRelevantBalanceFields(), $columnNameKeys);
59 62
 
@@ -63,7 +66,7 @@ class ReportService
63 66
 
64 67
             /** @var Account $account */
65 68
             foreach ($accountsInCategory as $account) {
66
-                $accountBalances = $this->calculateAccountBalances($account, $category);
69
+                $accountBalances = $this->calculateAccountBalances($account);
67 70
 
68 71
                 foreach ($relevantFields as $field) {
69 72
                     $categorySummaryBalances[$field] += $accountBalances[$field];
@@ -95,11 +98,16 @@ class ReportService
95 98
 
96 99
         $formattedReportTotalBalances = $this->formatBalances($reportTotalBalances);
97 100
 
98
-        return new ReportDTO($accountCategories, $formattedReportTotalBalances, $columns);
101
+        return new ReportDTO(
102
+            categories: $accountCategories,
103
+            overallTotal: $formattedReportTotalBalances,
104
+            fields: $columns,
105
+        );
99 106
     }
100 107
 
101
-    public function calculateAccountBalances(Account $account, AccountCategory $category): array
108
+    public function calculateAccountBalances(Account $account): array
102 109
     {
110
+        $category = $account->category;
103 111
         $balances = [
104 112
             'debit_balance' => $account->total_debit ?? 0,
105 113
             'credit_balance' => $account->total_credit ?? 0,
@@ -130,12 +138,12 @@ class ReportService
130 138
         $expenseTotal = 0;
131 139
 
132 140
         foreach ($revenueAccounts as $account) {
133
-            $revenueBalances = $this->calculateAccountBalances($account, AccountCategory::Revenue);
141
+            $revenueBalances = $this->calculateAccountBalances($account);
134 142
             $revenueTotal += $revenueBalances['net_movement'];
135 143
         }
136 144
 
137 145
         foreach ($expenseAccounts as $account) {
138
-            $expenseBalances = $this->calculateAccountBalances($account, AccountCategory::Expense);
146
+            $expenseBalances = $this->calculateAccountBalances($account);
139 147
             $expenseTotal += $expenseBalances['net_movement'];
140 148
         }
141 149
 
@@ -151,7 +159,8 @@ class ReportService
151 159
 
152 160
         $accountIds = $accountId !== 'all' ? [$accountId] : [];
153 161
 
154
-        $query = $this->accountService->getAccountBalances($startDate, $endDate, $accountIds);
162
+        $query = $this->accountService->getAccountBalances($startDate, $endDate, $accountIds)
163
+            ->orderByRaw('LENGTH(code), code');
155 164
 
156 165
         $accounts = $query->with(['journalEntries' => $this->accountService->getTransactionDetailsSubquery($startDate, $endDate)])->get();
157 166
 
@@ -242,6 +251,7 @@ class ReportService
242 251
 
243 252
         $accounts = $this->accountService->getAccountBalances($startDateCarbon->toDateTimeString(), $asOfDateCarbon->toDateTimeString())
244 253
             ->when($isPostClosingTrialBalance, fn (Builder $query) => $query->whereNotIn('category', [AccountCategory::Revenue, AccountCategory::Expense]))
254
+            ->orderByRaw('LENGTH(code), code')
245 255
             ->get();
246 256
 
247 257
         $balanceFields = ['debit_balance', 'credit_balance'];
@@ -250,8 +260,7 @@ class ReportService
250 260
         $reportTotalBalances = array_fill_keys($balanceFields, 0);
251 261
 
252 262
         foreach ($orderedCategories as $category) {
253
-            $accountsInCategory = $accounts->where('category', $category)
254
-                ->sortBy('code', SORT_NATURAL);
263
+            $accountsInCategory = $accounts->where('category', $category);
255 264
 
256 265
             $categorySummaryBalances = array_fill_keys($balanceFields, 0);
257 266
 
@@ -259,7 +268,7 @@ class ReportService
259 268
 
260 269
             /** @var Account $account */
261 270
             foreach ($accountsInCategory as $account) {
262
-                $accountBalances = $this->calculateAccountBalances($account, $category);
271
+                $accountBalances = $this->calculateAccountBalances($account);
263 272
 
264 273
                 $endingBalance = $accountBalances['ending_balance'] ?? $accountBalances['net_movement'];
265 274
 
@@ -402,7 +411,7 @@ class ReportService
402 411
                 };
403 412
 
404 413
                 if ($category !== null) {
405
-                    $accountBalances = $this->calculateAccountBalances($account, $category);
414
+                    $accountBalances = $this->calculateAccountBalances($account);
406 415
                     $movement = $accountBalances['net_movement'];
407 416
                     $netMovement += $movement;
408 417
                     $group['total'] += $movement;
@@ -429,7 +438,238 @@ class ReportService
429 438
         $netProfit = $grossProfit - $totalExpenses;
430 439
         $formattedReportTotalBalances = $this->formatBalances(['net_movement' => $netProfit]);
431 440
 
432
-        return new ReportDTO($accountCategories, $formattedReportTotalBalances, $columns);
441
+        return new ReportDTO(
442
+            categories: $accountCategories,
443
+            overallTotal: $formattedReportTotalBalances,
444
+            fields: $columns,
445
+            startDate: Carbon::parse($startDate),
446
+            endDate: Carbon::parse($endDate),
447
+        );
448
+    }
449
+
450
+    public function buildCashFlowStatementReport(string $startDate, string $endDate, array $columns = []): ReportDTO
451
+    {
452
+        $sections = [
453
+            'Operating Activities' => $this->buildOperatingActivities($startDate, $endDate),
454
+            'Investing Activities' => $this->buildInvestingActivities($startDate, $endDate),
455
+            'Financing Activities' => $this->buildFinancingActivities($startDate, $endDate),
456
+        ];
457
+
458
+        $totalCashFlows = $this->calculateTotalCashFlows($sections, $startDate);
459
+
460
+        $overview = $this->buildCashFlowOverview($startDate, $endDate);
461
+
462
+        return new ReportDTO(
463
+            categories: $sections,
464
+            overallTotal: $totalCashFlows,
465
+            fields: $columns,
466
+            overview: $overview,
467
+            startDate: Carbon::parse($startDate),
468
+            endDate: Carbon::parse($endDate),
469
+        );
470
+    }
471
+
472
+    private function calculateTotalCashFlows(array $sections, string $startDate): AccountBalanceDTO
473
+    {
474
+        $totalInflow = 0;
475
+        $totalOutflow = 0;
476
+        $startingBalance = $this->accountService->getStartingBalanceForAllBankAccounts($startDate)->getAmount();
477
+
478
+        foreach ($sections as $section) {
479
+            $netMovement = $section->summary->netMovement ?? 0;
480
+
481
+            $numericNetMovement = CurrencyConverter::convertToCents($netMovement);
482
+
483
+            if ($numericNetMovement > 0) {
484
+                $totalInflow += $numericNetMovement;
485
+            } else {
486
+                $totalOutflow += $numericNetMovement;
487
+            }
488
+        }
489
+
490
+        $netCashChange = $totalInflow + $totalOutflow;
491
+        $endingBalance = $startingBalance + $netCashChange;
492
+
493
+        return $this->formatBalances([
494
+            'starting_balance' => $startingBalance,
495
+            'debit_balance' => $totalInflow,
496
+            'credit_balance' => abs($totalOutflow),
497
+            'net_movement' => $netCashChange,
498
+            'ending_balance' => $endingBalance,
499
+        ]);
500
+    }
501
+
502
+    private function buildCashFlowOverview(string $startDate, string $endDate): CashFlowOverviewDTO
503
+    {
504
+        $accounts = $this->accountService->getBankAccountBalances($startDate, $endDate)->get();
505
+
506
+        $startingBalanceAccounts = [];
507
+        $endingBalanceAccounts = [];
508
+
509
+        $startingBalanceTotal = 0;
510
+        $endingBalanceTotal = 0;
511
+
512
+        foreach ($accounts as $account) {
513
+            $accountBalances = $this->calculateAccountBalances($account);
514
+
515
+            $startingBalanceTotal += $accountBalances['starting_balance'];
516
+            $endingBalanceTotal += $accountBalances['ending_balance'];
517
+
518
+            $startingBalanceAccounts[] = new AccountDTO(
519
+                accountName: $account->name,
520
+                accountCode: $account->code,
521
+                accountId: $account->id,
522
+                balance: $this->formatBalances(['starting_balance' => $accountBalances['starting_balance']]),
523
+                startDate: $startDate,
524
+                endDate: $endDate,
525
+            );
526
+
527
+            $endingBalanceAccounts[] = new AccountDTO(
528
+                accountName: $account->name,
529
+                accountCode: $account->code,
530
+                accountId: $account->id,
531
+                balance: $this->formatBalances(['ending_balance' => $accountBalances['ending_balance']]),
532
+                startDate: $startDate,
533
+                endDate: $endDate,
534
+            );
535
+        }
536
+
537
+        $startingBalanceSummary = $this->formatBalances(['starting_balance' => $startingBalanceTotal]);
538
+        $endingBalanceSummary = $this->formatBalances(['ending_balance' => $endingBalanceTotal]);
539
+
540
+        $overviewCategories = [
541
+            'Starting Balance' => new AccountCategoryDTO(
542
+                accounts: $startingBalanceAccounts,
543
+                summary: $startingBalanceSummary,
544
+            ),
545
+            'Ending Balance' => new AccountCategoryDTO(
546
+                accounts: $endingBalanceAccounts,
547
+                summary: $endingBalanceSummary,
548
+            ),
549
+        ];
550
+
551
+        return new CashFlowOverviewDTO($overviewCategories);
552
+    }
553
+
554
+    private function buildOperatingActivities(string $startDate, string $endDate): AccountCategoryDTO
555
+    {
556
+        $accounts = $this->accountService->getCashFlowAccountBalances($startDate, $endDate)
557
+            ->whereIn('accounts.type', [
558
+                AccountType::OperatingRevenue,
559
+                AccountType::UncategorizedRevenue,
560
+                AccountType::ContraRevenue,
561
+                AccountType::OperatingExpense,
562
+                AccountType::NonOperatingExpense,
563
+                AccountType::UncategorizedExpense,
564
+                AccountType::ContraExpense,
565
+                AccountType::CurrentAsset,
566
+            ])
567
+            ->whereRelation('subtype', 'name', '!=', 'Cash and Cash Equivalents')
568
+            ->orderByRaw('LENGTH(code), code')
569
+            ->get();
570
+
571
+        $adjustments = $this->accountService->getCashFlowAccountBalances($startDate, $endDate)
572
+            ->whereIn('accounts.type', [
573
+                AccountType::ContraAsset,
574
+                AccountType::CurrentLiability,
575
+            ])
576
+            ->whereRelation('subtype', 'name', '!=', 'Short-Term Borrowings')
577
+            ->orderByRaw('LENGTH(code), code')
578
+            ->get();
579
+
580
+        return $this->formatSectionAccounts($accounts, $adjustments, $startDate, $endDate);
581
+    }
582
+
583
+    private function buildInvestingActivities(string $startDate, string $endDate): AccountCategoryDTO
584
+    {
585
+        $accounts = $this->accountService->getCashFlowAccountBalances($startDate, $endDate)
586
+            ->whereIn('accounts.type', [AccountType::NonCurrentAsset])
587
+            ->orderByRaw('LENGTH(code), code')
588
+            ->get();
589
+
590
+        $adjustments = $this->accountService->getCashFlowAccountBalances($startDate, $endDate)
591
+            ->whereIn('accounts.type', [AccountType::NonOperatingRevenue])
592
+            ->orderByRaw('LENGTH(code), code')
593
+            ->get();
594
+
595
+        return $this->formatSectionAccounts($accounts, $adjustments, $startDate, $endDate);
596
+    }
597
+
598
+    private function buildFinancingActivities(string $startDate, string $endDate): AccountCategoryDTO
599
+    {
600
+        $accounts = $this->accountService->getCashFlowAccountBalances($startDate, $endDate)
601
+            ->where(function (Builder $query) {
602
+                $query->whereIn('accounts.type', [
603
+                    AccountType::Equity,
604
+                    AccountType::NonCurrentLiability,
605
+                ])
606
+                    ->orWhere(function (Builder $subQuery) {
607
+                        $subQuery->where('accounts.type', AccountType::CurrentLiability)
608
+                            ->whereRelation('subtype', 'name', 'Short-Term Borrowings');
609
+                    });
610
+            })
611
+            ->orderByRaw('LENGTH(code), code')
612
+            ->get();
613
+
614
+        return $this->formatSectionAccounts($accounts, [], $startDate, $endDate);
615
+    }
616
+
617
+    private function formatSectionAccounts($accounts, $adjustments, string $startDate, string $endDate): AccountCategoryDTO
618
+    {
619
+        $categoryAccountsByType = [];
620
+        $sectionTotal = 0;
621
+        $subCategoryTotals = [];
622
+
623
+        // Process accounts and adjustments
624
+        /** @var Account[] $entries */
625
+        foreach ([$accounts, $adjustments] as $entries) {
626
+            foreach ($entries as $entry) {
627
+                $accountCategory = $entry->type->getCategory();
628
+                $accountBalances = $this->calculateAccountBalances($entry);
629
+                $netCashFlow = $accountBalances['net_movement'] ?? 0;
630
+
631
+                if ($entry->subtype->inverse_cash_flow) {
632
+                    $netCashFlow *= -1;
633
+                }
634
+
635
+                // Accumulate totals
636
+                $sectionTotal += $netCashFlow;
637
+                $accountTypeName = $entry->subtype->name;
638
+                $subCategoryTotals[$accountTypeName] = ($subCategoryTotals[$accountTypeName] ?? 0) + $netCashFlow;
639
+
640
+                // Create AccountDTO and group by account type
641
+                $accountDTO = new AccountDTO(
642
+                    $entry->name,
643
+                    $entry->code,
644
+                    $entry->id,
645
+                    $this->formatBalances(['net_movement' => $netCashFlow]),
646
+                    $startDate,
647
+                    $endDate
648
+                );
649
+
650
+                $categoryAccountsByType[$accountTypeName][] = $accountDTO;
651
+            }
652
+        }
653
+
654
+        // Prepare AccountTypeDTO for each account type with the accumulated totals
655
+        $subCategories = [];
656
+        foreach ($categoryAccountsByType as $typeName => $accountsInType) {
657
+            $typeTotal = $subCategoryTotals[$typeName] ?? 0;
658
+            $formattedTypeTotal = $this->formatBalances(['net_movement' => $typeTotal]);
659
+            $subCategories[$typeName] = new AccountTypeDTO(
660
+                accounts: $accountsInType,
661
+                summary: $formattedTypeTotal
662
+            );
663
+        }
664
+
665
+        // Format the overall section total as the section summary
666
+        $formattedSectionTotal = $this->formatBalances(['net_movement' => $sectionTotal]);
667
+
668
+        return new AccountCategoryDTO(
669
+            accounts: [], // No direct accounts at the section level
670
+            types: $subCategories, // Grouped by AccountTypeDTO
671
+            summary: $formattedSectionTotal,
672
+        );
433 673
     }
434 674
 
435 675
     public function buildBalanceSheetReport(string $asOfDate, array $columns = []): ReportDTO
@@ -461,7 +701,7 @@ class ReportService
461 701
             /** @var Account $account */
462 702
             foreach ($accounts as $account) {
463 703
                 if ($account->type->getCategory() === $category) {
464
-                    $accountBalances = $this->calculateAccountBalances($account, $category);
704
+                    $accountBalances = $this->calculateAccountBalances($account);
465 705
                     $endingBalance = $accountBalances['ending_balance'] ?? $accountBalances['net_movement'];
466 706
 
467 707
                     $categorySummaryBalances['ending_balance'] += $endingBalance;
@@ -533,6 +773,12 @@ class ReportService
533 773
 
534 774
         $formattedReportTotalBalances = $this->formatBalances(['ending_balance' => $netAssets]);
535 775
 
536
-        return new ReportDTO($accountCategories, $formattedReportTotalBalances, $columns);
776
+        return new ReportDTO(
777
+            categories: $accountCategories,
778
+            overallTotal: $formattedReportTotalBalances,
779
+            fields: $columns,
780
+            startDate: $startDateCarbon,
781
+            endDate: $asOfDateCarbon,
782
+        );
537 783
     }
538 784
 }

+ 18
- 15
app/Transformers/BalanceSheetReportTransformer.php Parādīt failu

@@ -200,23 +200,26 @@ class BalanceSheetReportTransformer extends SummaryReportTransformer
200 200
                 $totalEquitySummary = $accountCategory->summary->endingBalance ?? 0;
201 201
                 $totalEquitySummary = money($totalEquitySummary, CurrencyAccessor::getDefaultCurrency())->getAmount();
202 202
                 $totalOtherEquity = $totalEquitySummary - $totalTypeSummaries;
203
-                $totalOtherEquity = money($totalOtherEquity, CurrencyAccessor::getDefaultCurrency(), true)->format();
204 203
 
205
-                // Add "Total Other Equity" as a new "type"
206
-                $otherEquitySummary = [];
207
-                foreach ($columns as $column) {
208
-                    $otherEquitySummary[$column->getName()] = match ($column->getName()) {
209
-                        'account_name' => 'Total Other Equity',
210
-                        'ending_balance' => $totalOtherEquity,
211
-                        default => '',
212
-                    };
213
-                }
204
+                if ($totalOtherEquity != 0) {
205
+                    $totalOtherEquityFormatted = money($totalOtherEquity, CurrencyAccessor::getDefaultCurrency(), true)->format();
214 206
 
215
-                $types['Total Other Equity'] = new ReportTypeDTO(
216
-                    header: [],
217
-                    data: [],
218
-                    summary: $otherEquitySummary,
219
-                );
207
+                    // Add "Total Other Equity" as a new "type"
208
+                    $otherEquitySummary = [];
209
+                    foreach ($columns as $column) {
210
+                        $otherEquitySummary[$column->getName()] = match ($column->getName()) {
211
+                            'account_name' => 'Total Other Equity',
212
+                            'ending_balance' => $totalOtherEquityFormatted,
213
+                            default => '',
214
+                        };
215
+                    }
216
+
217
+                    $types['Total Other Equity'] = new ReportTypeDTO(
218
+                        header: [],
219
+                        data: [],
220
+                        summary: $otherEquitySummary,
221
+                    );
222
+                }
220 223
             }
221 224
 
222 225
             // Add the category with its types and summary to the final array

+ 10
- 0
app/Transformers/BaseReportTransformer.php Parādīt failu

@@ -76,4 +76,14 @@ abstract class BaseReportTransformer implements ExportableReport
76 76
             return 'text-left';
77 77
         });
78 78
     }
79
+
80
+    public function getStartDate(): ?string
81
+    {
82
+        return $this->report->startDate?->toDefaultDateFormat();
83
+    }
84
+
85
+    public function getEndDate(): ?string
86
+    {
87
+        return $this->report->endDate?->toDefaultDateFormat();
88
+    }
79 89
 }

+ 338
- 0
app/Transformers/CashFlowStatementReportTransformer.php Parādīt failu

@@ -0,0 +1,338 @@
1
+<?php
2
+
3
+namespace App\Transformers;
4
+
5
+use App\DTO\AccountDTO;
6
+use App\DTO\ReportCategoryDTO;
7
+use App\DTO\ReportTypeDTO;
8
+
9
+class CashFlowStatementReportTransformer extends SummaryReportTransformer
10
+{
11
+    public function getPdfView(): string
12
+    {
13
+        return 'components.company.reports.cash-flow-statement-pdf';
14
+    }
15
+
16
+    public function getTitle(): string
17
+    {
18
+        return 'Cash Flow Statement';
19
+    }
20
+
21
+    public function getCategories(): array
22
+    {
23
+        $categories = [];
24
+
25
+        foreach ($this->report->categories as $accountCategoryName => $accountCategory) {
26
+            // Header for the main category
27
+            $header = [];
28
+
29
+            foreach ($this->getColumns() as $column) {
30
+                $header[$column->getName()] = $column->getName() === 'account_name' ? $accountCategoryName : '';
31
+            }
32
+
33
+            // Category-level summary
34
+            $categorySummary = [];
35
+            foreach ($this->getColumns() as $column) {
36
+                $categorySummary[$column->getName()] = match ($column->getName()) {
37
+                    'account_name' => 'Total ' . $accountCategoryName,
38
+                    'net_movement' => $accountCategory->summary->netMovement ?? '',
39
+                    default => '',
40
+                };
41
+            }
42
+
43
+            // Accounts directly under the main category
44
+            $data = array_map(function (AccountDTO $account) {
45
+                $row = [];
46
+
47
+                foreach ($this->getColumns() as $column) {
48
+                    $row[$column->getName()] = match ($column->getName()) {
49
+                        'account_code' => $account->accountCode,
50
+                        'account_name' => [
51
+                            'name' => $account->accountName,
52
+                            'id' => $account->accountId ?? null,
53
+                            'start_date' => $account->startDate,
54
+                            'end_date' => $account->endDate,
55
+                        ],
56
+                        'net_movement' => $account->balance->netMovement ?? '',
57
+                        default => '',
58
+                    };
59
+                }
60
+
61
+                return $row;
62
+            }, $accountCategory->accounts ?? []);
63
+
64
+            // Subcategories (types) under the main category
65
+            $types = [];
66
+            foreach ($accountCategory->types as $typeName => $type) {
67
+                // Header for subcategory (type)
68
+                $typeHeader = [];
69
+                foreach ($this->getColumns() as $column) {
70
+                    $typeHeader[$column->getName()] = $column->getName() === 'account_name' ? $typeName : '';
71
+                }
72
+
73
+                // Account data for the subcategory
74
+                $typeData = array_map(function (AccountDTO $account) {
75
+                    $row = [];
76
+
77
+                    foreach ($this->getColumns() as $column) {
78
+                        $row[$column->getName()] = match ($column->getName()) {
79
+                            'account_code' => $account->accountCode,
80
+                            'account_name' => [
81
+                                'name' => $account->accountName,
82
+                                'id' => $account->accountId ?? null,
83
+                                'start_date' => $account->startDate,
84
+                                'end_date' => $account->endDate,
85
+                            ],
86
+                            'net_movement' => $account->balance->netMovement ?? '',
87
+                            default => '',
88
+                        };
89
+                    }
90
+
91
+                    return $row;
92
+                }, $type->accounts ?? []);
93
+
94
+                // Subcategory (type) summary
95
+                $typeSummary = [];
96
+                foreach ($this->getColumns() as $column) {
97
+                    $typeSummary[$column->getName()] = match ($column->getName()) {
98
+                        'account_name' => 'Total ' . $typeName,
99
+                        'net_movement' => $type->summary->netMovement ?? '',
100
+                        default => '',
101
+                    };
102
+                }
103
+
104
+                // Add subcategory (type) to the list
105
+                $types[$typeName] = new ReportTypeDTO(
106
+                    header: $typeHeader,
107
+                    data: $typeData,
108
+                    summary: $typeSummary,
109
+                );
110
+            }
111
+
112
+            // Add the category to the final array with its direct accounts and subcategories (types)
113
+            $categories[$accountCategoryName] = new ReportCategoryDTO(
114
+                header: $header,
115
+                data: $data, // Direct accounts under the category
116
+                summary: $categorySummary,
117
+                types: $types, // Subcategories (types) under the category
118
+            );
119
+        }
120
+
121
+        return $categories;
122
+    }
123
+
124
+    public function getSummaryCategories(): array
125
+    {
126
+        $summaryCategories = [];
127
+
128
+        $columns = $this->getSummaryColumns();
129
+
130
+        foreach ($this->report->categories as $accountCategoryName => $accountCategory) {
131
+            $categoryHeader = [];
132
+
133
+            foreach ($columns as $column) {
134
+                $categoryHeader[$column->getName()] = $column->getName() === 'account_name' ? $accountCategoryName : '';
135
+            }
136
+
137
+            $categorySummary = [];
138
+            foreach ($columns as $column) {
139
+                $categorySummary[$column->getName()] = match ($column->getName()) {
140
+                    'account_name' => 'Total ' . $accountCategoryName,
141
+                    'net_movement' => $accountCategory->summary->netMovement ?? '',
142
+                    default => '',
143
+                };
144
+            }
145
+
146
+            $types = [];
147
+
148
+            // Iterate through each account type and calculate type summaries
149
+            foreach ($accountCategory->types as $typeName => $type) {
150
+                $typeSummary = [];
151
+
152
+                foreach ($columns as $column) {
153
+                    $typeSummary[$column->getName()] = match ($column->getName()) {
154
+                        'account_name' => $typeName,
155
+                        'net_movement' => $type->summary->netMovement ?? '',
156
+                        default => '',
157
+                    };
158
+                }
159
+
160
+                $types[$typeName] = new ReportTypeDTO(
161
+                    header: [],
162
+                    data: [],
163
+                    summary: $typeSummary,
164
+                );
165
+            }
166
+
167
+            // Add the category with its types and summary to the final array
168
+            $summaryCategories[$accountCategoryName] = new ReportCategoryDTO(
169
+                header: $categoryHeader,
170
+                data: [],
171
+                summary: $categorySummary,
172
+                types: $types,
173
+            );
174
+        }
175
+
176
+        return $summaryCategories;
177
+    }
178
+
179
+    public function getOverallTotals(): array
180
+    {
181
+        return [];
182
+    }
183
+
184
+    public function getSummaryOverallTotals(): array
185
+    {
186
+        return [];
187
+    }
188
+
189
+    public function getSummary(): array
190
+    {
191
+        return [
192
+            [
193
+                'label' => 'Total Cash Inflows',
194
+                'value' => $this->report->overallTotal->debitBalance ?? '',
195
+            ],
196
+            [
197
+                'label' => 'Total Cash Outflows',
198
+                'value' => $this->report->overallTotal->creditBalance ?? '',
199
+            ],
200
+            [
201
+                'label' => 'Net Cash Flow',
202
+                'value' => $this->report->overallTotal->netMovement ?? '',
203
+            ],
204
+        ];
205
+    }
206
+
207
+    public function getOverviewAlignedWithColumns(): array
208
+    {
209
+        $summary = [];
210
+
211
+        foreach ($this->getSummary() as $summaryItem) {
212
+            $row = [];
213
+
214
+            foreach ($this->getColumns() as $column) {
215
+                $row[$column->getName()] = match ($column->getName()) {
216
+                    'account_name' => $summaryItem['label'] ?? '',
217
+                    'net_movement' => $summaryItem['value'] ?? '',
218
+                    default => '',
219
+                };
220
+            }
221
+
222
+            $summary[] = $row;
223
+        }
224
+
225
+        return $summary;
226
+    }
227
+
228
+    public function getSummaryOverviewAlignedWithColumns(): array
229
+    {
230
+        return array_map(static function ($row) {
231
+            unset($row['account_code']);
232
+
233
+            return $row;
234
+        }, $this->getOverviewAlignedWithColumns());
235
+    }
236
+
237
+    public function getOverviewHeaders(): array
238
+    {
239
+        return once(function (): array {
240
+            $headers = [];
241
+
242
+            foreach ($this->getColumns() as $column) {
243
+                $headers[$column->getName()] = $column->getName() === 'account_name' ? 'OVERVIEW' : '';
244
+            }
245
+
246
+            return $headers;
247
+        });
248
+    }
249
+
250
+    public function getSummaryOverviewHeaders(): array
251
+    {
252
+        return once(function (): array {
253
+            $headers = $this->getOverviewHeaders();
254
+
255
+            unset($headers['account_code']);
256
+
257
+            return $headers;
258
+        });
259
+    }
260
+
261
+    public function getOverview(): array
262
+    {
263
+        $categories = [];
264
+
265
+        foreach ($this->report->overview->categories as $categoryName => $category) {
266
+            $header = [];
267
+
268
+            foreach ($this->getColumns() as $column) {
269
+                $header[$column->getName()] = $column->getName() === 'account_name' ? $categoryName : '';
270
+            }
271
+
272
+            $data = array_map(function (AccountDTO $account) {
273
+                $row = [];
274
+
275
+                foreach ($this->getColumns() as $column) {
276
+                    $row[$column->getName()] = match ($column->getName()) {
277
+                        'account_code' => $account->accountCode,
278
+                        'account_name' => [
279
+                            'name' => $account->accountName,
280
+                            'id' => $account->accountId ?? null,
281
+                            'start_date' => $account->startDate,
282
+                            'end_date' => $account->endDate,
283
+                        ],
284
+                        'net_movement' => $account->balance->startingBalance ?? $account->balance->endingBalance ?? '',
285
+                        default => '',
286
+                    };
287
+                }
288
+
289
+                return $row;
290
+            }, $category->accounts);
291
+
292
+            $summary = [];
293
+
294
+            foreach ($this->getColumns() as $column) {
295
+                $summary[$column->getName()] = match ($column->getName()) {
296
+                    'account_name' => 'Total ' . $categoryName,
297
+                    'net_movement' => $category->summary->startingBalance ?? $category->summary->endingBalance ?? '',
298
+                    default => '',
299
+                };
300
+            }
301
+
302
+            $categories[] = new ReportCategoryDTO(
303
+                header: $header,
304
+                data: $data,
305
+                summary: $summary,
306
+            );
307
+        }
308
+
309
+        return $categories;
310
+    }
311
+
312
+    public function getSummaryOverview(): array
313
+    {
314
+        $summaryCategories = [];
315
+
316
+        $columns = $this->getSummaryColumns();
317
+
318
+        foreach ($this->report->overview->categories as $categoryName => $category) {
319
+            $categorySummary = [];
320
+
321
+            foreach ($columns as $column) {
322
+                $categorySummary[$column->getName()] = match ($column->getName()) {
323
+                    'account_name' => $categoryName,
324
+                    'net_movement' => $category->summary->startingBalance ?? $category->summary->endingBalance ?? '',
325
+                    default => '',
326
+                };
327
+            }
328
+
329
+            $summaryCategories[] = new ReportCategoryDTO(
330
+                header: [],
331
+                data: [],
332
+                summary: $categorySummary,
333
+            );
334
+        }
335
+
336
+        return $summaryCategories;
337
+    }
338
+}

+ 22
- 8
app/Utilities/Currency/CurrencyConverter.php Parādīt failu

@@ -15,24 +15,38 @@ class CurrencyConverter
15 15
 
16 16
         $old_attr = currency($oldCurrency);
17 17
         $new_attr = currency($newCurrency);
18
-        $temp_balance = str_replace([$old_attr->getThousandsSeparator(), $old_attr->getDecimalMark()], ['', '.'], $amount);
18
+        $temp_amount = str_replace([$old_attr->getThousandsSeparator(), $old_attr->getDecimalMark()], ['', '.'], $amount);
19 19
 
20
-        return number_format((float) $temp_balance, $new_attr->getPrecision(), $new_attr->getDecimalMark(), $new_attr->getThousandsSeparator());
20
+        return number_format((float) $temp_amount, $new_attr->getPrecision(), $new_attr->getDecimalMark(), $new_attr->getThousandsSeparator());
21 21
     }
22 22
 
23
-    public static function convertBalance(int $balance, string $oldCurrency, string $newCurrency): int
23
+    public static function convertBalance(int $amount, string $oldCurrency, string $newCurrency): int
24 24
     {
25
-        return money($balance, $oldCurrency)->swapAmountFor($newCurrency);
25
+        return money($amount, $oldCurrency)->swapAmountFor($newCurrency);
26 26
     }
27 27
 
28
-    public static function prepareForMutator(int $balance, string $currency): string
28
+    public static function prepareForMutator(int $amount, string $currency): string
29 29
     {
30
-        return money($balance, $currency)->formatSimple();
30
+        return money($amount, $currency)->formatSimple();
31 31
     }
32 32
 
33
-    public static function prepareForAccessor(string $balance, string $currency): int
33
+    public static function prepareForAccessor(string $amount, string $currency): int
34 34
     {
35
-        return money($balance, $currency, true)->getAmount();
35
+        return money($amount, $currency, true)->getAmount();
36
+    }
37
+
38
+    public static function convertToCents(string | float $amount, ?string $currency = null): int
39
+    {
40
+        $currency ??= CurrencyAccessor::getDefaultCurrency();
41
+
42
+        return money($amount, $currency, true)->getAmount();
43
+    }
44
+
45
+    public static function formatCentsToMoney(int $amount, ?string $currency = null): string
46
+    {
47
+        $currency ??= CurrencyAccessor::getDefaultCurrency();
48
+
49
+        return money($amount, $currency)->format();
36 50
     }
37 51
 
38 52
     public static function handleCurrencyChange(Set $set, $state): void

+ 245
- 245
composer.lock
Failā izmaiņas netiks attēlotas, jo tās ir par lielu
Parādīt failu


+ 44
- 0
config/chart-of-accounts.php Parādīt failu

@@ -8,6 +8,7 @@ return [
8 8
                 'multi_currency' => true,
9 9
                 'base_code' => '1000',
10 10
                 'bank_account_type' => 'depository',
11
+                'inverse_cash_flow' => false,
11 12
                 'accounts' => [
12 13
                     'Cash on Hand' => [
13 14
                         'description' => 'The amount of money held by the company in the form of cash.',
@@ -18,6 +19,7 @@ return [
18 19
                 'description' => 'Amounts owed to the company for goods sold or services rendered, including accounts receivable, notes receivable, and other receivables.',
19 20
                 'multi_currency' => false,
20 21
                 'base_code' => '1100',
22
+                'inverse_cash_flow' => true,
21 23
                 'accounts' => [
22 24
                     'Accounts Receivable' => [
23 25
                         'description' => 'The amount of money owed to the company by customers who have not yet paid for goods or services received.',
@@ -28,16 +30,19 @@ return [
28 30
                 'description' => 'The raw materials, work-in-progress goods and completely finished goods that are considered to be the portion of a business\'s assets that are ready or will be ready for sale.',
29 31
                 'multi_currency' => true,
30 32
                 'base_code' => '1200',
33
+                'inverse_cash_flow' => true,
31 34
             ],
32 35
             'Prepaid and Deferred Charges' => [
33 36
                 'description' => 'Payments made in advance for future goods or services, such as insurance premiums, rent, and prepaid taxes.',
34 37
                 'multi_currency' => true,
35 38
                 'base_code' => '1300',
39
+                'inverse_cash_flow' => true,
36 40
             ],
37 41
             'Other Current Assets' => [
38 42
                 'description' => 'Other assets that are expected to be converted to cash, sold, or consumed within one year or the business\'s operating cycle.',
39 43
                 'multi_currency' => true,
40 44
                 'base_code' => '1400',
45
+                'inverse_cash_flow' => true,
41 46
             ],
42 47
         ],
43 48
         'non_current_asset' => [
@@ -45,21 +50,25 @@ return [
45 50
                 'description' => 'Investments in securities like bonds and stocks, investments in other companies, or real estate held for more than one year, aiming for long-term benefits.',
46 51
                 'multi_currency' => true,
47 52
                 'base_code' => '1500',
53
+                'inverse_cash_flow' => true,
48 54
             ],
49 55
             'Fixed Assets' => [
50 56
                 'description' => 'Physical, tangible assets used in the business\'s operations with a useful life exceeding one year, such as buildings, machinery, and vehicles. These assets are subject to depreciation.',
51 57
                 'multi_currency' => true,
52 58
                 'base_code' => '1600',
59
+                'inverse_cash_flow' => true,
53 60
             ],
54 61
             'Intangible Assets' => [
55 62
                 'description' => 'Assets lacking physical substance but offering value to the business, like patents, copyrights, trademarks, software, and goodwill.',
56 63
                 'multi_currency' => true,
57 64
                 'base_code' => '1700',
65
+                'inverse_cash_flow' => true,
58 66
             ],
59 67
             'Other Non-Current Assets' => [
60 68
                 'description' => 'Includes long-term assets not classified in the above categories, such as long-term prepaid expenses, deferred tax assets, and loans made to other entities that are not expected to be settled within the next year.',
61 69
                 'multi_currency' => true,
62 70
                 'base_code' => '1800',
71
+                'inverse_cash_flow' => true,
63 72
             ],
64 73
         ],
65 74
         'contra_asset' => [
@@ -67,6 +76,7 @@ return [
67 76
                 'description' => 'Accounts that accumulate depreciation of tangible assets and amortization of intangible assets, reflecting the reduction in value over time.',
68 77
                 'multi_currency' => false,
69 78
                 'base_code' => '1900',
79
+                'inverse_cash_flow' => false,
70 80
                 'accounts' => [
71 81
                     'Accumulated Depreciation' => [
72 82
                         'description' => 'Used to account for the depreciation of fixed assets over time, offsetting assets like equipment or property.',
@@ -77,6 +87,7 @@ return [
77 87
                 'description' => 'Accounts representing estimated uncollected receivables, used to adjust the value of gross receivables to a realistic collectible amount.',
78 88
                 'multi_currency' => false,
79 89
                 'base_code' => '1940',
90
+                'inverse_cash_flow' => false,
80 91
                 'accounts' => [
81 92
                     'Allowance for Doubtful Accounts' => [
82 93
                         'description' => 'Used to account for potential bad debts that may not be collectable, offsetting receivables.',
@@ -87,6 +98,7 @@ return [
87 98
                 'description' => 'Accounts used to record adjustments in asset values due to impairments, market changes, or other factors affecting their recoverable amount.',
88 99
                 'multi_currency' => false,
89 100
                 'base_code' => '1950',
101
+                'inverse_cash_flow' => false,
90 102
             ],
91 103
         ],
92 104
         'current_liability' => [
@@ -94,6 +106,7 @@ return [
94 106
                 'description' => 'Liabilities arising from purchases of goods or services from suppliers, not yet paid for. This can include individual accounts payable and trade credits.',
95 107
                 'multi_currency' => true,
96 108
                 'base_code' => '2000',
109
+                'inverse_cash_flow' => false,
97 110
                 'accounts' => [
98 111
                     'Accounts Payable' => [
99 112
                         'description' => 'The amount of money owed by the company to suppliers for goods or services received.',
@@ -104,6 +117,7 @@ return [
104 117
                 'description' => 'Expenses that have been incurred but not yet paid, including wages, utilities, interest, and taxes. This category can house various accrued expense accounts.',
105 118
                 'multi_currency' => false,
106 119
                 'base_code' => '2100',
120
+                'inverse_cash_flow' => false,
107 121
                 'accounts' => [
108 122
                     'Sales Tax Payable' => [
109 123
                         'description' => 'The amount of money owed to the government for sales tax collected from customers.',
@@ -114,16 +128,19 @@ return [
114 128
                 'description' => 'Debt obligations due within the next year, such as bank loans, lines of credit, and short-term notes. This category can cover multiple short-term debt accounts.',
115 129
                 'multi_currency' => true,
116 130
                 'base_code' => '2200',
131
+                'inverse_cash_flow' => false,
117 132
             ],
118 133
             'Customer Deposits and Advances' => [
119 134
                 'description' => 'Funds received in advance for goods or services to be provided in the future, including customer deposits and prepayments.',
120 135
                 'multi_currency' => true,
121 136
                 'base_code' => '2300',
137
+                'inverse_cash_flow' => false,
122 138
             ],
123 139
             'Other Current Liabilities' => [
124 140
                 'description' => 'A grouping for miscellaneous short-term liabilities not covered in other categories, like the current portion of long-term debts, short-term provisions, and other similar obligations.',
125 141
                 'multi_currency' => true,
126 142
                 'base_code' => '2400',
143
+                'inverse_cash_flow' => false,
127 144
             ],
128 145
         ],
129 146
         'non_current_liability' => [
@@ -131,16 +148,19 @@ return [
131 148
                 'description' => 'Obligations such as bonds, mortgages, and loans with a maturity of more than one year, covering various types of long-term debt instruments.',
132 149
                 'multi_currency' => true,
133 150
                 'base_code' => '2500',
151
+                'inverse_cash_flow' => false,
134 152
             ],
135 153
             'Deferred Tax Liabilities' => [
136 154
                 'description' => 'Taxes incurred in the current period but payable in a future period, typically due to differences in accounting methods between tax reporting and financial reporting.',
137 155
                 'multi_currency' => false,
138 156
                 'base_code' => '2600',
157
+                'inverse_cash_flow' => false,
139 158
             ],
140 159
             'Other Long-Term Liabilities' => [
141 160
                 'description' => 'Liabilities not due within the next year and not classified as long-term debt or deferred taxes, including pension liabilities, lease obligations, and long-term provisions.',
142 161
                 'multi_currency' => true,
143 162
                 'base_code' => '2700',
163
+                'inverse_cash_flow' => false,
144 164
             ],
145 165
         ],
146 166
         'contra_liability' => [
@@ -148,11 +168,13 @@ return [
148 168
                 'description' => 'Accumulated amount representing the reduction of bond or loan liabilities, reflecting the difference between the face value and the discounted issuance price over time.',
149 169
                 'multi_currency' => false,
150 170
                 'base_code' => '2900',
171
+                'inverse_cash_flow' => false,
151 172
             ],
152 173
             'Valuation Adjustments for Liabilities' => [
153 174
                 'description' => 'Adjustments made to the recorded value of liabilities, such as changes in fair value of derivative liabilities or adjustments for hedging activities.',
154 175
                 'multi_currency' => false,
155 176
                 'base_code' => '2950',
177
+                'inverse_cash_flow' => false,
156 178
             ],
157 179
         ],
158 180
         'equity' => [
@@ -160,6 +182,7 @@ return [
160 182
                 'description' => 'Funds provided by owners or shareholders for starting the business and subsequent capital injections. Reflects the financial commitment of the owner(s) or shareholders to the business.',
161 183
                 'multi_currency' => true,
162 184
                 'base_code' => '3000',
185
+                'inverse_cash_flow' => false,
163 186
                 'accounts' => [
164 187
                     'Owner\'s Investment' => [
165 188
                         'description' => 'The amount of money invested by the owner(s) or shareholders to start or expand the business.',
@@ -172,6 +195,7 @@ return [
172 195
                 'description' => 'Equity that is deducted from gross equity to arrive at net equity. This includes treasury stock, which is stock that has been repurchased by the company.',
173 196
                 'multi_currency' => false,
174 197
                 'base_code' => '3900',
198
+                'inverse_cash_flow' => true,
175 199
                 'accounts' => [
176 200
                     'Owner\'s Drawings' => [
177 201
                         'description' => 'The amount of money withdrawn by the owner(s) or shareholders from the business for personal use, reducing equity.',
@@ -184,6 +208,7 @@ return [
184 208
                 'description' => 'Income from selling physical or digital products. Includes revenue from all product lines or categories.',
185 209
                 'multi_currency' => false,
186 210
                 'base_code' => '4000',
211
+                'inverse_cash_flow' => false,
187 212
                 'accounts' => [
188 213
                     'Product Sales' => [
189 214
                         'description' => 'The amount of money earned from selling physical or digital products.',
@@ -194,11 +219,13 @@ return [
194 219
                 'description' => 'Income earned from providing services, encompassing activities like consulting, maintenance, and repair services.',
195 220
                 'multi_currency' => false,
196 221
                 'base_code' => '4100',
222
+                'inverse_cash_flow' => false,
197 223
             ],
198 224
             'Other Operating Revenue' => [
199 225
                 'description' => 'Income from other business operations not classified as product sales or services, such as rental income, royalties, or income from licensing agreements.',
200 226
                 'multi_currency' => false,
201 227
                 'base_code' => '4200',
228
+                'inverse_cash_flow' => false,
202 229
             ],
203 230
         ],
204 231
         'non_operating_revenue' => [
@@ -206,6 +233,7 @@ return [
206 233
                 'description' => 'Earnings from investments, including dividends, interest from securities, and profits from real estate investments.',
207 234
                 'multi_currency' => false,
208 235
                 'base_code' => '4500',
236
+                'inverse_cash_flow' => false,
209 237
                 'accounts' => [
210 238
                     'Dividends' => [
211 239
                         'description' => 'The amount of money received from investments in shares of other companies.',
@@ -219,11 +247,13 @@ return [
219 247
                 'description' => 'Profits from selling assets like property, equipment, or investments, excluding regular sales of inventory.',
220 248
                 'multi_currency' => false,
221 249
                 'base_code' => '4600',
250
+                'inverse_cash_flow' => false,
222 251
             ],
223 252
             'Other Non-Operating Revenue' => [
224 253
                 'description' => 'Income from sources not related to the main business activities, such as legal settlements, insurance recoveries, or gains from foreign exchange transactions.',
225 254
                 'multi_currency' => false,
226 255
                 'base_code' => '4700',
256
+                'inverse_cash_flow' => false,
227 257
                 'accounts' => [
228 258
                     'Gain on Foreign Exchange' => [
229 259
                         'description' => 'The amount of money earned from foreign exchange transactions due to favorable exchange rate changes.',
@@ -236,6 +266,7 @@ return [
236 266
                 'description' => 'Revenue that is deducted from gross revenue to arrive at net revenue. This includes sales discounts, returns, and allowances.',
237 267
                 'multi_currency' => false,
238 268
                 'base_code' => '4900',
269
+                'inverse_cash_flow' => false,
239 270
                 'accounts' => [
240 271
                     'Sales Returns and Allowances' => [
241 272
                         'description' => 'The amount of money returned to customers or deducted from sales due to returned goods or allowances granted.',
@@ -251,6 +282,7 @@ return [
251 282
                 'description' => 'Revenue that has not been categorized into other revenue categories.',
252 283
                 'multi_currency' => false,
253 284
                 'base_code' => '4950',
285
+                'inverse_cash_flow' => false,
254 286
                 'accounts' => [
255 287
                     'Uncategorized Income' => [
256 288
                         'description' => 'Revenue from other business operations that don\'t fall under regular sales or services. This account is used as the default for all new transactions.',
@@ -263,11 +295,13 @@ return [
263 295
                 'description' => 'Direct costs attributable to the production of goods sold by a company. This includes material costs and direct labor.',
264 296
                 'multi_currency' => false,
265 297
                 'base_code' => '5000',
298
+                'inverse_cash_flow' => true,
266 299
             ],
267 300
             'Payroll and Employee Benefits' => [
268 301
                 'description' => 'Expenses related to employee compensation, including salaries, wages, bonuses, commissions, and payroll taxes.',
269 302
                 'multi_currency' => false,
270 303
                 'base_code' => '5050',
304
+                'inverse_cash_flow' => true,
271 305
                 'accounts' => [
272 306
                     'Salaries and Wages' => [
273 307
                         'description' => 'The amount of money paid to employees for their work, including regular salaries and hourly wages.',
@@ -287,6 +321,7 @@ return [
287 321
                 'description' => 'Costs incurred for business premises, including rent or lease payments, property taxes, utilities, and building maintenance.',
288 322
                 'multi_currency' => false,
289 323
                 'base_code' => '5100',
324
+                'inverse_cash_flow' => true,
290 325
                 'accounts' => [
291 326
                     'Rent or Lease Payments' => [
292 327
                         'description' => 'The amount of money paid for renting or leasing business premises.',
@@ -309,6 +344,7 @@ return [
309 344
                 'description' => 'Expenses related to general business operations, such as office supplies, insurance, and professional fees.',
310 345
                 'multi_currency' => false,
311 346
                 'base_code' => '5150',
347
+                'inverse_cash_flow' => true,
312 348
                 'accounts' => [
313 349
                     'Food and Drink' => [
314 350
                         'description' => 'The amount of money spent on food and drink for business purposes, such as office snacks, meals, and catering.',
@@ -340,6 +376,7 @@ return [
340 376
                 'description' => 'Expenses related to marketing and advertising activities, such as advertising campaigns, promotional events, and marketing materials.',
341 377
                 'multi_currency' => false,
342 378
                 'base_code' => '5200',
379
+                'inverse_cash_flow' => true,
343 380
                 'accounts' => [
344 381
                     'Advertising' => [
345 382
                         'description' => 'The amount of money spent on advertising campaigns, including print, digital, and outdoor advertising.',
@@ -353,11 +390,13 @@ return [
353 390
                 'description' => 'Expenses incurred in the process of researching and developing new products or services.',
354 391
                 'multi_currency' => false,
355 392
                 'base_code' => '5250',
393
+                'inverse_cash_flow' => true,
356 394
             ],
357 395
             'Other Operating Expenses' => [
358 396
                 'description' => 'Miscellaneous expenses not categorized elsewhere, such as research and development costs, legal fees, and other irregular expenses.',
359 397
                 'multi_currency' => false,
360 398
                 'base_code' => '5300',
399
+                'inverse_cash_flow' => true,
361 400
             ],
362 401
         ],
363 402
         'non_operating_expense' => [
@@ -365,16 +404,19 @@ return [
365 404
                 'description' => 'Expenses related to borrowing and financing, such as interest payments on loans, bonds, and credit lines.',
366 405
                 'multi_currency' => false,
367 406
                 'base_code' => '5500',
407
+                'inverse_cash_flow' => true,
368 408
             ],
369 409
             'Tax Expenses' => [
370 410
                 'description' => 'Various taxes incurred by the business, including income tax, sales tax, property tax, and payroll tax.',
371 411
                 'multi_currency' => false,
372 412
                 'base_code' => '5600',
413
+                'inverse_cash_flow' => true,
373 414
             ],
374 415
             'Other Non-Operating Expense' => [
375 416
                 'description' => 'Expenses not related to primary business activities, like losses from asset disposals, legal settlements, restructuring costs, or foreign exchange losses.',
376 417
                 'multi_currency' => false,
377 418
                 'base_code' => '5700',
419
+                'inverse_cash_flow' => true,
378 420
                 'accounts' => [
379 421
                     'Loss on Foreign Exchange' => [
380 422
                         'description' => 'The amount of money lost from foreign exchange transactions due to unfavorable exchange rate changes.',
@@ -387,6 +429,7 @@ return [
387 429
                 'description' => 'Expenses that are deducted from gross expenses to arrive at net expenses. This includes purchase discounts, returns, and allowances.',
388 430
                 'multi_currency' => false,
389 431
                 'base_code' => '5900',
432
+                'inverse_cash_flow' => true,
390 433
                 'accounts' => [
391 434
                     'Purchase Returns and Allowances' => [
392 435
                         'description' => 'The amount of money returned to suppliers or deducted from purchases due to returned goods or allowances granted.',
@@ -402,6 +445,7 @@ return [
402 445
                 'description' => 'Expenses that have not been categorized into other expense categories.',
403 446
                 'multi_currency' => false,
404 447
                 'base_code' => '5950',
448
+                'inverse_cash_flow' => true,
405 449
                 'accounts' => [
406 450
                     'Uncategorized Expense' => [
407 451
                         'description' => 'Expenses not classified into regular expense categories. This account is used as the default for all new transactions.',

+ 17
- 3
database/factories/Accounting/TransactionFactory.php Parādīt failu

@@ -49,13 +49,27 @@ class TransactionFactory extends Factory
49 49
             $type = $this->faker->randomElement([TransactionType::Deposit, TransactionType::Withdrawal]);
50 50
 
51 51
             $associatedAccountTypes = match ($type) {
52
-                TransactionType::Deposit => ['asset', 'liability', 'equity', 'revenue'],
53
-                TransactionType::Withdrawal => ['asset', 'liability', 'equity', 'expense'],
52
+                TransactionType::Deposit => [
53
+                    AccountType::CurrentLiability,
54
+                    AccountType::NonCurrentLiability,
55
+                    AccountType::Equity,
56
+                    AccountType::OperatingRevenue,
57
+                    AccountType::NonOperatingRevenue,
58
+                    AccountType::ContraExpense,
59
+                ],
60
+                TransactionType::Withdrawal => [
61
+                    AccountType::OperatingExpense,
62
+                    AccountType::NonOperatingExpense,
63
+                    AccountType::CurrentLiability,
64
+                    AccountType::NonCurrentLiability,
65
+                    AccountType::Equity,
66
+                    AccountType::ContraRevenue,
67
+                ],
54 68
             };
55 69
 
56 70
             $accountIdForBankAccount = $bankAccount->account->id;
57 71
 
58
-            $account = Account::where('category', $this->faker->randomElement($associatedAccountTypes))
72
+            $account = Account::whereIn('type', $associatedAccountTypes)
59 73
                 ->where('company_id', $company->id)
60 74
                 ->whereKeyNot($accountIdForBankAccount)
61 75
                 ->inRandomOrder()

+ 28
- 0
database/migrations/2024_11_02_182328_add_inverse_cash_flow_to_account_subtypes_table.php Parādīt failu

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

+ 130
- 100
package-lock.json Parādīt failu

@@ -541,9 +541,9 @@
541 541
             }
542 542
         },
543 543
         "node_modules/@rollup/rollup-android-arm-eabi": {
544
-            "version": "4.24.0",
545
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.0.tgz",
546
-            "integrity": "sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==",
544
+            "version": "4.25.0",
545
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.25.0.tgz",
546
+            "integrity": "sha512-CC/ZqFZwlAIbU1wUPisHyV/XRc5RydFrNLtgl3dGYskdwPZdt4HERtKm50a/+DtTlKeCq9IXFEWR+P6blwjqBA==",
547 547
             "cpu": [
548 548
                 "arm"
549 549
             ],
@@ -555,9 +555,9 @@
555 555
             ]
556 556
         },
557 557
         "node_modules/@rollup/rollup-android-arm64": {
558
-            "version": "4.24.0",
559
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.0.tgz",
560
-            "integrity": "sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==",
558
+            "version": "4.25.0",
559
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.25.0.tgz",
560
+            "integrity": "sha512-/Y76tmLGUJqVBXXCfVS8Q8FJqYGhgH4wl4qTA24E9v/IJM0XvJCGQVSW1QZ4J+VURO9h8YCa28sTFacZXwK7Rg==",
561 561
             "cpu": [
562 562
                 "arm64"
563 563
             ],
@@ -569,9 +569,9 @@
569 569
             ]
570 570
         },
571 571
         "node_modules/@rollup/rollup-darwin-arm64": {
572
-            "version": "4.24.0",
573
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.0.tgz",
574
-            "integrity": "sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==",
572
+            "version": "4.25.0",
573
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.25.0.tgz",
574
+            "integrity": "sha512-YVT6L3UrKTlC0FpCZd0MGA7NVdp7YNaEqkENbWQ7AOVOqd/7VzyHpgIpc1mIaxRAo1ZsJRH45fq8j4N63I/vvg==",
575 575
             "cpu": [
576 576
                 "arm64"
577 577
             ],
@@ -583,9 +583,9 @@
583 583
             ]
584 584
         },
585 585
         "node_modules/@rollup/rollup-darwin-x64": {
586
-            "version": "4.24.0",
587
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.0.tgz",
588
-            "integrity": "sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==",
586
+            "version": "4.25.0",
587
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.25.0.tgz",
588
+            "integrity": "sha512-ZRL+gexs3+ZmmWmGKEU43Bdn67kWnMeWXLFhcVv5Un8FQcx38yulHBA7XR2+KQdYIOtD0yZDWBCudmfj6lQJoA==",
589 589
             "cpu": [
590 590
                 "x64"
591 591
             ],
@@ -596,10 +596,38 @@
596 596
                 "darwin"
597 597
             ]
598 598
         },
599
+        "node_modules/@rollup/rollup-freebsd-arm64": {
600
+            "version": "4.25.0",
601
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.25.0.tgz",
602
+            "integrity": "sha512-xpEIXhiP27EAylEpreCozozsxWQ2TJbOLSivGfXhU4G1TBVEYtUPi2pOZBnvGXHyOdLAUUhPnJzH3ah5cqF01g==",
603
+            "cpu": [
604
+                "arm64"
605
+            ],
606
+            "dev": true,
607
+            "license": "MIT",
608
+            "optional": true,
609
+            "os": [
610
+                "freebsd"
611
+            ]
612
+        },
613
+        "node_modules/@rollup/rollup-freebsd-x64": {
614
+            "version": "4.25.0",
615
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.25.0.tgz",
616
+            "integrity": "sha512-sC5FsmZGlJv5dOcURrsnIK7ngc3Kirnx3as2XU9uER+zjfyqIjdcMVgzy4cOawhsssqzoAX19qmxgJ8a14Qrqw==",
617
+            "cpu": [
618
+                "x64"
619
+            ],
620
+            "dev": true,
621
+            "license": "MIT",
622
+            "optional": true,
623
+            "os": [
624
+                "freebsd"
625
+            ]
626
+        },
599 627
         "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
600
-            "version": "4.24.0",
601
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.0.tgz",
602
-            "integrity": "sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==",
628
+            "version": "4.25.0",
629
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.25.0.tgz",
630
+            "integrity": "sha512-uD/dbLSs1BEPzg564TpRAQ/YvTnCds2XxyOndAO8nJhaQcqQGFgv/DAVko/ZHap3boCvxnzYMa3mTkV/B/3SWA==",
603 631
             "cpu": [
604 632
                 "arm"
605 633
             ],
@@ -611,9 +639,9 @@
611 639
             ]
612 640
         },
613 641
         "node_modules/@rollup/rollup-linux-arm-musleabihf": {
614
-            "version": "4.24.0",
615
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.0.tgz",
616
-            "integrity": "sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==",
642
+            "version": "4.25.0",
643
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.25.0.tgz",
644
+            "integrity": "sha512-ZVt/XkrDlQWegDWrwyC3l0OfAF7yeJUF4fq5RMS07YM72BlSfn2fQQ6lPyBNjt+YbczMguPiJoCfaQC2dnflpQ==",
617 645
             "cpu": [
618 646
                 "arm"
619 647
             ],
@@ -625,9 +653,9 @@
625 653
             ]
626 654
         },
627 655
         "node_modules/@rollup/rollup-linux-arm64-gnu": {
628
-            "version": "4.24.0",
629
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.0.tgz",
630
-            "integrity": "sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==",
656
+            "version": "4.25.0",
657
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.25.0.tgz",
658
+            "integrity": "sha512-qboZ+T0gHAW2kkSDPHxu7quaFaaBlynODXpBVnPxUgvWYaE84xgCKAPEYE+fSMd3Zv5PyFZR+L0tCdYCMAtG0A==",
631 659
             "cpu": [
632 660
                 "arm64"
633 661
             ],
@@ -639,9 +667,9 @@
639 667
             ]
640 668
         },
641 669
         "node_modules/@rollup/rollup-linux-arm64-musl": {
642
-            "version": "4.24.0",
643
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.0.tgz",
644
-            "integrity": "sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==",
670
+            "version": "4.25.0",
671
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.25.0.tgz",
672
+            "integrity": "sha512-ndWTSEmAaKr88dBuogGH2NZaxe7u2rDoArsejNslugHZ+r44NfWiwjzizVS1nUOHo+n1Z6qV3X60rqE/HlISgw==",
645 673
             "cpu": [
646 674
                 "arm64"
647 675
             ],
@@ -653,9 +681,9 @@
653 681
             ]
654 682
         },
655 683
         "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
656
-            "version": "4.24.0",
657
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.0.tgz",
658
-            "integrity": "sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==",
684
+            "version": "4.25.0",
685
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.25.0.tgz",
686
+            "integrity": "sha512-BVSQvVa2v5hKwJSy6X7W1fjDex6yZnNKy3Kx1JGimccHft6HV0THTwNtC2zawtNXKUu+S5CjXslilYdKBAadzA==",
659 687
             "cpu": [
660 688
                 "ppc64"
661 689
             ],
@@ -667,9 +695,9 @@
667 695
             ]
668 696
         },
669 697
         "node_modules/@rollup/rollup-linux-riscv64-gnu": {
670
-            "version": "4.24.0",
671
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.0.tgz",
672
-            "integrity": "sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==",
698
+            "version": "4.25.0",
699
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.25.0.tgz",
700
+            "integrity": "sha512-G4hTREQrIdeV0PE2JruzI+vXdRnaK1pg64hemHq2v5fhv8C7WjVaeXc9P5i4Q5UC06d/L+zA0mszYIKl+wY8oA==",
673 701
             "cpu": [
674 702
                 "riscv64"
675 703
             ],
@@ -681,9 +709,9 @@
681 709
             ]
682 710
         },
683 711
         "node_modules/@rollup/rollup-linux-s390x-gnu": {
684
-            "version": "4.24.0",
685
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.0.tgz",
686
-            "integrity": "sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==",
712
+            "version": "4.25.0",
713
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.25.0.tgz",
714
+            "integrity": "sha512-9T/w0kQ+upxdkFL9zPVB6zy9vWW1deA3g8IauJxojN4bnz5FwSsUAD034KpXIVX5j5p/rn6XqumBMxfRkcHapQ==",
687 715
             "cpu": [
688 716
                 "s390x"
689 717
             ],
@@ -695,9 +723,9 @@
695 723
             ]
696 724
         },
697 725
         "node_modules/@rollup/rollup-linux-x64-gnu": {
698
-            "version": "4.24.0",
699
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.0.tgz",
700
-            "integrity": "sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==",
726
+            "version": "4.25.0",
727
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.25.0.tgz",
728
+            "integrity": "sha512-ThcnU0EcMDn+J4B9LD++OgBYxZusuA7iemIIiz5yzEcFg04VZFzdFjuwPdlURmYPZw+fgVrFzj4CA64jSTG4Ig==",
701 729
             "cpu": [
702 730
                 "x64"
703 731
             ],
@@ -709,9 +737,9 @@
709 737
             ]
710 738
         },
711 739
         "node_modules/@rollup/rollup-linux-x64-musl": {
712
-            "version": "4.24.0",
713
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.0.tgz",
714
-            "integrity": "sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==",
740
+            "version": "4.25.0",
741
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.25.0.tgz",
742
+            "integrity": "sha512-zx71aY2oQxGxAT1JShfhNG79PnjYhMC6voAjzpu/xmMjDnKNf6Nl/xv7YaB/9SIa9jDYf8RBPWEnjcdlhlv1rQ==",
715 743
             "cpu": [
716 744
                 "x64"
717 745
             ],
@@ -723,9 +751,9 @@
723 751
             ]
724 752
         },
725 753
         "node_modules/@rollup/rollup-win32-arm64-msvc": {
726
-            "version": "4.24.0",
727
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.0.tgz",
728
-            "integrity": "sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==",
754
+            "version": "4.25.0",
755
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.25.0.tgz",
756
+            "integrity": "sha512-JT8tcjNocMs4CylWY/CxVLnv8e1lE7ff1fi6kbGocWwxDq9pj30IJ28Peb+Y8yiPNSF28oad42ApJB8oUkwGww==",
729 757
             "cpu": [
730 758
                 "arm64"
731 759
             ],
@@ -737,9 +765,9 @@
737 765
             ]
738 766
         },
739 767
         "node_modules/@rollup/rollup-win32-ia32-msvc": {
740
-            "version": "4.24.0",
741
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.0.tgz",
742
-            "integrity": "sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==",
768
+            "version": "4.25.0",
769
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.25.0.tgz",
770
+            "integrity": "sha512-dRLjLsO3dNOfSN6tjyVlG+Msm4IiZnGkuZ7G5NmpzwF9oOc582FZG05+UdfTbz5Jd4buK/wMb6UeHFhG18+OEg==",
743 771
             "cpu": [
744 772
                 "ia32"
745 773
             ],
@@ -751,9 +779,9 @@
751 779
             ]
752 780
         },
753 781
         "node_modules/@rollup/rollup-win32-x64-msvc": {
754
-            "version": "4.24.0",
755
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.0.tgz",
756
-            "integrity": "sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==",
782
+            "version": "4.25.0",
783
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.25.0.tgz",
784
+            "integrity": "sha512-/RqrIFtLB926frMhZD0a5oDa4eFIbyNEwLLloMTEjmqfwZWXywwVVOVmwTsuyhC9HKkVEZcOOi+KV4U9wmOdlg==",
757 785
             "cpu": [
758 786
                 "x64"
759 787
             ],
@@ -998,9 +1026,9 @@
998 1026
             }
999 1027
         },
1000 1028
         "node_modules/caniuse-lite": {
1001
-            "version": "1.0.30001669",
1002
-            "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001669.tgz",
1003
-            "integrity": "sha512-DlWzFDJqstqtIVx1zeSpIMLjunf5SmwOw0N2Ck/QSQdS8PLS4+9HrLaYei4w8BIAL7IB/UEDu889d8vhCTPA0w==",
1029
+            "version": "1.0.30001680",
1030
+            "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001680.tgz",
1031
+            "integrity": "sha512-rPQy70G6AGUMnbwS1z6Xg+RkHYPAi18ihs47GH0jcxIG7wArmPgY3XbS2sRdBbxJljp3thdT8BIqv9ccCypiPA==",
1004 1032
             "dev": true,
1005 1033
             "funding": [
1006 1034
                 {
@@ -1100,9 +1128,9 @@
1100 1128
             }
1101 1129
         },
1102 1130
         "node_modules/cross-spawn": {
1103
-            "version": "7.0.3",
1104
-            "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
1105
-            "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
1131
+            "version": "7.0.5",
1132
+            "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.5.tgz",
1133
+            "integrity": "sha512-ZVJrKKYunU38/76t0RMOulHOnUcbU9GbpWKAOZ0mhjr7CX6FVrH+4FrAapSOekrgFQ3f/8gwMEuIft0aKq6Hug==",
1106 1134
             "dev": true,
1107 1135
             "license": "MIT",
1108 1136
             "dependencies": {
@@ -1159,9 +1187,9 @@
1159 1187
             "license": "MIT"
1160 1188
         },
1161 1189
         "node_modules/electron-to-chromium": {
1162
-            "version": "1.5.41",
1163
-            "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.41.tgz",
1164
-            "integrity": "sha512-dfdv/2xNjX0P8Vzme4cfzHqnPm5xsZXwsolTYr0eyW18IUmNyG08vL+fttvinTfhKfIKdRoqkDIC9e9iWQCNYQ==",
1190
+            "version": "1.5.56",
1191
+            "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.56.tgz",
1192
+            "integrity": "sha512-7lXb9dAvimCFdvUMTyucD4mnIndt/xhRKFAlky0CyFogdnNmdPQNoHI23msF/2V4mpTxMzgMdjK4+YRlFlRQZw==",
1165 1193
             "dev": true,
1166 1194
             "license": "ISC"
1167 1195
         },
@@ -1826,9 +1854,9 @@
1826 1854
             }
1827 1855
         },
1828 1856
         "node_modules/postcss": {
1829
-            "version": "8.4.47",
1830
-            "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz",
1831
-            "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==",
1857
+            "version": "8.4.49",
1858
+            "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz",
1859
+            "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==",
1832 1860
             "dev": true,
1833 1861
             "funding": [
1834 1862
                 {
@@ -1847,7 +1875,7 @@
1847 1875
             "license": "MIT",
1848 1876
             "dependencies": {
1849 1877
                 "nanoid": "^3.3.7",
1850
-                "picocolors": "^1.1.0",
1878
+                "picocolors": "^1.1.1",
1851 1879
                 "source-map-js": "^1.2.1"
1852 1880
             },
1853 1881
             "engines": {
@@ -1982,9 +2010,9 @@
1982 2010
             }
1983 2011
         },
1984 2012
         "node_modules/postcss-nesting": {
1985
-            "version": "13.0.0",
1986
-            "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-13.0.0.tgz",
1987
-            "integrity": "sha512-TCGQOizyqvEkdeTPM+t6NYwJ3EJszYE/8t8ILxw/YoeUvz2rz7aM8XTAmBWh9/DJjfaaabL88fWrsVHSPF2zgA==",
2013
+            "version": "13.0.1",
2014
+            "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-13.0.1.tgz",
2015
+            "integrity": "sha512-VbqqHkOBOt4Uu3G8Dm8n6lU5+9cJFxiuty9+4rcoyRPO9zZS1JIs6td49VIoix3qYqELHlJIn46Oih9SAKo+yQ==",
1988 2016
             "dev": true,
1989 2017
             "funding": [
1990 2018
                 {
@@ -1998,9 +2026,9 @@
1998 2026
             ],
1999 2027
             "license": "MIT-0",
2000 2028
             "dependencies": {
2001
-                "@csstools/selector-resolve-nested": "^2.0.0",
2002
-                "@csstools/selector-specificity": "^4.0.0",
2003
-                "postcss-selector-parser": "^6.1.0"
2029
+                "@csstools/selector-resolve-nested": "^3.0.0",
2030
+                "@csstools/selector-specificity": "^5.0.0",
2031
+                "postcss-selector-parser": "^7.0.0"
2004 2032
             },
2005 2033
             "engines": {
2006 2034
                 "node": ">=18"
@@ -2010,9 +2038,9 @@
2010 2038
             }
2011 2039
         },
2012 2040
         "node_modules/postcss-nesting/node_modules/@csstools/selector-resolve-nested": {
2013
-            "version": "2.0.0",
2014
-            "resolved": "https://registry.npmjs.org/@csstools/selector-resolve-nested/-/selector-resolve-nested-2.0.0.tgz",
2015
-            "integrity": "sha512-oklSrRvOxNeeOW1yARd4WNCs/D09cQjunGZUgSq6vM8GpzFswN+8rBZyJA29YFZhOTQ6GFzxgLDNtVbt9wPZMA==",
2041
+            "version": "3.0.0",
2042
+            "resolved": "https://registry.npmjs.org/@csstools/selector-resolve-nested/-/selector-resolve-nested-3.0.0.tgz",
2043
+            "integrity": "sha512-ZoK24Yku6VJU1gS79a5PFmC8yn3wIapiKmPgun0hZgEI5AOqgH2kiPRsPz1qkGv4HL+wuDLH83yQyk6inMYrJQ==",
2016 2044
             "dev": true,
2017 2045
             "funding": [
2018 2046
                 {
@@ -2029,13 +2057,13 @@
2029 2057
                 "node": ">=18"
2030 2058
             },
2031 2059
             "peerDependencies": {
2032
-                "postcss-selector-parser": "^6.1.0"
2060
+                "postcss-selector-parser": "^7.0.0"
2033 2061
             }
2034 2062
         },
2035 2063
         "node_modules/postcss-nesting/node_modules/@csstools/selector-specificity": {
2036
-            "version": "4.0.0",
2037
-            "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-4.0.0.tgz",
2038
-            "integrity": "sha512-189nelqtPd8++phaHNwYovKZI0FOzH1vQEE3QhHHkNIGrg5fSs9CbYP3RvfEH5geztnIA9Jwq91wyOIwAW5JIQ==",
2064
+            "version": "5.0.0",
2065
+            "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz",
2066
+            "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==",
2039 2067
             "dev": true,
2040 2068
             "funding": [
2041 2069
                 {
@@ -2052,13 +2080,13 @@
2052 2080
                 "node": ">=18"
2053 2081
             },
2054 2082
             "peerDependencies": {
2055
-                "postcss-selector-parser": "^6.1.0"
2083
+                "postcss-selector-parser": "^7.0.0"
2056 2084
             }
2057 2085
         },
2058 2086
         "node_modules/postcss-nesting/node_modules/postcss-selector-parser": {
2059
-            "version": "6.1.2",
2060
-            "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
2061
-            "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
2087
+            "version": "7.0.0",
2088
+            "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz",
2089
+            "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==",
2062 2090
             "dev": true,
2063 2091
             "license": "MIT",
2064 2092
             "dependencies": {
@@ -2171,9 +2199,9 @@
2171 2199
             }
2172 2200
         },
2173 2201
         "node_modules/rollup": {
2174
-            "version": "4.24.0",
2175
-            "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.0.tgz",
2176
-            "integrity": "sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==",
2202
+            "version": "4.25.0",
2203
+            "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.25.0.tgz",
2204
+            "integrity": "sha512-uVbClXmR6wvx5R1M3Od4utyLUxrmOcEm3pAtMphn73Apq19PDtHpgZoEvqH2YnnaNUuvKmg2DgRd2Sqv+odyqg==",
2177 2205
             "dev": true,
2178 2206
             "license": "MIT",
2179 2207
             "dependencies": {
@@ -2187,22 +2215,24 @@
2187 2215
                 "npm": ">=8.0.0"
2188 2216
             },
2189 2217
             "optionalDependencies": {
2190
-                "@rollup/rollup-android-arm-eabi": "4.24.0",
2191
-                "@rollup/rollup-android-arm64": "4.24.0",
2192
-                "@rollup/rollup-darwin-arm64": "4.24.0",
2193
-                "@rollup/rollup-darwin-x64": "4.24.0",
2194
-                "@rollup/rollup-linux-arm-gnueabihf": "4.24.0",
2195
-                "@rollup/rollup-linux-arm-musleabihf": "4.24.0",
2196
-                "@rollup/rollup-linux-arm64-gnu": "4.24.0",
2197
-                "@rollup/rollup-linux-arm64-musl": "4.24.0",
2198
-                "@rollup/rollup-linux-powerpc64le-gnu": "4.24.0",
2199
-                "@rollup/rollup-linux-riscv64-gnu": "4.24.0",
2200
-                "@rollup/rollup-linux-s390x-gnu": "4.24.0",
2201
-                "@rollup/rollup-linux-x64-gnu": "4.24.0",
2202
-                "@rollup/rollup-linux-x64-musl": "4.24.0",
2203
-                "@rollup/rollup-win32-arm64-msvc": "4.24.0",
2204
-                "@rollup/rollup-win32-ia32-msvc": "4.24.0",
2205
-                "@rollup/rollup-win32-x64-msvc": "4.24.0",
2218
+                "@rollup/rollup-android-arm-eabi": "4.25.0",
2219
+                "@rollup/rollup-android-arm64": "4.25.0",
2220
+                "@rollup/rollup-darwin-arm64": "4.25.0",
2221
+                "@rollup/rollup-darwin-x64": "4.25.0",
2222
+                "@rollup/rollup-freebsd-arm64": "4.25.0",
2223
+                "@rollup/rollup-freebsd-x64": "4.25.0",
2224
+                "@rollup/rollup-linux-arm-gnueabihf": "4.25.0",
2225
+                "@rollup/rollup-linux-arm-musleabihf": "4.25.0",
2226
+                "@rollup/rollup-linux-arm64-gnu": "4.25.0",
2227
+                "@rollup/rollup-linux-arm64-musl": "4.25.0",
2228
+                "@rollup/rollup-linux-powerpc64le-gnu": "4.25.0",
2229
+                "@rollup/rollup-linux-riscv64-gnu": "4.25.0",
2230
+                "@rollup/rollup-linux-s390x-gnu": "4.25.0",
2231
+                "@rollup/rollup-linux-x64-gnu": "4.25.0",
2232
+                "@rollup/rollup-linux-x64-musl": "4.25.0",
2233
+                "@rollup/rollup-win32-arm64-msvc": "4.25.0",
2234
+                "@rollup/rollup-win32-ia32-msvc": "4.25.0",
2235
+                "@rollup/rollup-win32-x64-msvc": "4.25.0",
2206 2236
                 "fsevents": "~2.3.2"
2207 2237
             }
2208 2238
         },
@@ -2550,9 +2580,9 @@
2550 2580
             "license": "MIT"
2551 2581
         },
2552 2582
         "node_modules/vite": {
2553
-            "version": "5.4.9",
2554
-            "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.9.tgz",
2555
-            "integrity": "sha512-20OVpJHh0PAM0oSOELa5GaZNWeDjcAvQjGXy2Uyr+Tp+/D2/Hdz6NLgpJLsarPTA2QJ6v8mX2P1ZfbsSKvdMkg==",
2583
+            "version": "5.4.11",
2584
+            "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz",
2585
+            "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==",
2556 2586
             "dev": true,
2557 2587
             "license": "MIT",
2558 2588
             "dependencies": {

+ 53
- 48
resources/views/components/company/reports/account-transactions-report-pdf.blade.php Parādīt failu

@@ -5,87 +5,87 @@
5 5
     <meta name="viewport" content="width=device-width, initial-scale=1">
6 6
     <title>{{ $report->getTitle() }}</title>
7 7
     <style>
8
-        .header {
9
-            color: #374151;
10
-            margin-bottom: 1rem;
8
+        .category-header-row > td {
9
+            background-color: #f3f4f6;
10
+            font-weight: bold;
11 11
         }
12 12
 
13
-        .header > * + * {
14
-            margin-top: 0.5rem;
13
+        .category-summary-row > td,
14
+        .table-footer-row > td {
15
+            background-color: #ffffff;
16
+            font-weight: bold;
15 17
         }
16 18
 
17
-        .table-head {
18
-            display: table-row-group;
19
+        .company-name {
20
+            font-size: 1.125rem;
21
+            font-weight: bold;
19 22
         }
20 23
 
21
-        .text-left {
22
-            text-align: left;
24
+        .date-range {
25
+            font-size: 0.875rem;
23 26
         }
24 27
 
25
-        .text-right {
26
-            text-align: right;
28
+        .header {
29
+            color: #374151;
30
+            margin-bottom: 1rem;
27 31
         }
28 32
 
29
-        .text-center {
30
-            text-align: center;
33
+        .header div + div {
34
+            margin-top: 0.5rem;
31 35
         }
32 36
 
33
-        .table-class th,
34
-        .table-class td {
35
-            color: #374151;
37
+        .spacer-row > td {
38
+            height: 0.75rem;
36 39
         }
37 40
 
38
-        .whitespace-normal {
39
-            white-space: normal;
41
+        .table-body tr {
42
+            background-color: #ffffff;
40 43
         }
41 44
 
42
-        .whitespace-nowrap {
43
-            white-space: nowrap;
45
+        .table-class {
46
+            border-collapse: collapse;
47
+            width: 100%;
44 48
         }
45 49
 
46
-        .title {
47
-            font-size: 1.5rem;
50
+        .table-class td,
51
+        .table-class th {
52
+            border-bottom: 1px solid #d1d5db;
53
+            color: #374151;
54
+            font-size: 0.75rem;
55
+            line-height: 1rem;
56
+            padding: 0.75rem;
48 57
         }
49 58
 
50
-        .company-name {
51
-            font-size: 1.125rem;
52
-            font-weight: bold;
59
+        .table-head {
60
+            display: table-row-group;
53 61
         }
54 62
 
55
-        .date-range {
56
-            font-size: 0.875rem;
63
+        .text-center {
64
+            text-align: center;
57 65
         }
58 66
 
59
-        .table-class {
60
-            width: 100%;
61
-            border-collapse: collapse;
67
+        .text-left {
68
+            text-align: left;
62 69
         }
63 70
 
64
-        .table-class th,
65
-        .table-class td {
66
-            padding: 0.75rem;
67
-            font-size: 0.75rem;
68
-            line-height: 1rem;
69
-            border-bottom: 1px solid #d1d5db; /* Gray border for all rows */
71
+        .text-right {
72
+            text-align: right;
70 73
         }
71 74
 
72
-        .category-header-row > td {
73
-            background-color: #f3f4f6; /* Gray background for category names */
74
-            font-weight: bold;
75
+        .title {
76
+            font-size: 1.5rem;
75 77
         }
76 78
 
77
-        .table-body tr {
78
-            background-color: #ffffff; /* White background for other rows */
79
+        .whitespace-normal {
80
+            white-space: normal;
79 81
         }
80 82
 
81
-        .spacer-row > td {
82
-            height: 0.75rem;
83
+        .whitespace-nowrap {
84
+            white-space: nowrap;
83 85
         }
84 86
 
85
-        .category-summary-row > td,
86
-        .table-footer-row > td {
87
-            font-weight: bold;
88
-            background-color: #ffffff; /* White background for footer */
87
+        table tfoot {
88
+            display: table-row-group;
89 89
         }
90 90
     </style>
91 91
 </head>
@@ -128,7 +128,12 @@
128 128
                     'category-header-row' => $loop->first || $loop->last || $loop->remaining === 1,
129 129
                 ])>
130 130
                 @foreach($transaction as $cellIndex => $cell)
131
-                    <td class="{{ $report->getAlignmentClass($cellIndex) }} {{ $cellIndex === 1 ? 'whitespace-normal' : 'whitespace-nowrap' }}">
131
+                    <td @class([
132
+                            $report->getAlignmentClass($cellIndex),
133
+                            'whitespace-normal' => $cellIndex === 'description',
134
+                            'whitespace-nowrap' => $cellIndex !== 'description',
135
+                        ])
136
+                    >
132 137
                         @if(is_array($cell) && isset($cell['description']))
133 138
                             {{ $cell['description'] }}
134 139
                         @else

+ 334
- 0
resources/views/components/company/reports/cash-flow-statement-pdf.blade.php Parādīt failu

@@ -0,0 +1,334 @@
1
+<!DOCTYPE html>
2
+<html lang="en">
3
+<head>
4
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
5
+    <meta name="viewport" content="width=device-width, initial-scale=1">
6
+    <title>{{ $report->getTitle() }}</title>
7
+    <style>
8
+        .font-bold {
9
+            font-weight: bold;
10
+        }
11
+
12
+        .category-header-row > td,
13
+        .type-header-row > td {
14
+            background-color: #f3f4f6;
15
+            font-weight: bold;
16
+        }
17
+
18
+        .category-summary-row > td,
19
+        .table-footer-row > td,
20
+        .type-summary-row > td {
21
+            background-color: #ffffff;
22
+            font-weight: bold;
23
+        }
24
+
25
+        .cell {
26
+            padding-bottom: 5px;
27
+            position: relative;
28
+        }
29
+
30
+        .company-name {
31
+            font-size: 1.125rem;
32
+            font-weight: bold;
33
+        }
34
+
35
+        .date-range {
36
+            font-size: 0.875rem;
37
+        }
38
+
39
+        .header {
40
+            color: #374151;
41
+            margin-bottom: 1rem;
42
+        }
43
+
44
+        .header div + div {
45
+            margin-top: 0.5rem;
46
+        }
47
+
48
+        .spacer-row > td {
49
+            height: 0.75rem;
50
+        }
51
+
52
+        .table-body tr {
53
+            background-color: #ffffff;
54
+        }
55
+
56
+        .table-class {
57
+            border-collapse: collapse;
58
+            table-layout: fixed;
59
+            width: 100%;
60
+        }
61
+
62
+        .table-class .type-row-indent {
63
+            padding-left: 1.5rem;
64
+        }
65
+
66
+        .table-class td,
67
+        .table-class th {
68
+            border-bottom: 1px solid #d1d5db;
69
+            color: #374151;
70
+            font-size: 0.75rem;
71
+            line-height: 1rem;
72
+            padding: 0.75rem;
73
+        }
74
+
75
+        .table-head {
76
+            display: table-row-group;
77
+        }
78
+
79
+        .text-center {
80
+            text-align: center;
81
+        }
82
+
83
+        .text-left {
84
+            text-align: left;
85
+        }
86
+
87
+        .text-right {
88
+            text-align: right;
89
+        }
90
+
91
+        .title {
92
+            font-size: 1.5rem;
93
+        }
94
+
95
+        .table-class .underline-bold {
96
+            border-bottom: 2px solid #374151;
97
+        }
98
+
99
+        .table-class .underline-thin {
100
+            border-bottom: 1px solid #374151;
101
+        }
102
+
103
+        .whitespace-normal {
104
+            white-space: normal;
105
+        }
106
+
107
+        .whitespace-nowrap {
108
+            white-space: nowrap;
109
+        }
110
+
111
+        table tfoot {
112
+            display: table-row-group;
113
+        }
114
+    </style>
115
+</head>
116
+<body>
117
+<div class="header">
118
+    <div class="title">{{ $report->getTitle() }}</div>
119
+    <div class="company-name">{{ $company->name }}</div>
120
+    @if($startDate && $endDate)
121
+        <div class="date-range">Date Range: {{ $startDate }} to {{ $endDate }}</div>
122
+    @else
123
+        <div class="date-range">As of {{ $endDate }}</div>
124
+    @endif
125
+</div>
126
+<table class="table-class">
127
+    <colgroup>
128
+        @if(array_key_exists('account_code', $report->getHeaders()))
129
+            <col span="1" style="width: 20%;">
130
+            <col span="1" style="width: 55%;">
131
+            <col span="1" style="width: 25%;">
132
+        @else
133
+            <col span="1" style="width: 65%;">
134
+            <col span="1" style="width: 35%;">
135
+        @endif
136
+    </colgroup>
137
+    <thead class="table-head">
138
+    <tr>
139
+        @foreach($report->getHeaders() as $index => $header)
140
+            <th class="{{ $report->getAlignmentClass($index) }}">
141
+                {{ $header }}
142
+            </th>
143
+        @endforeach
144
+    </tr>
145
+    </thead>
146
+    @foreach($report->getCategories() as $category)
147
+        <tbody>
148
+        <tr class="category-header-row">
149
+            @foreach($category->header as $index => $header)
150
+                <td class="{{ $report->getAlignmentClass($index) }}">
151
+                    {{ $header }}
152
+                </td>
153
+            @endforeach
154
+        </tr>
155
+        @foreach($category->data as $account)
156
+            <tr>
157
+                @foreach($account as $index => $cell)
158
+                    <td @class([
159
+                            $report->getAlignmentClass($index),
160
+                            'whitespace-normal' => $index === 'account_name',
161
+                            'whitespace-nowrap' => $index !== 'account_name',
162
+                        ])
163
+                    >
164
+                        @if(is_array($cell) && isset($cell['name']))
165
+                            {{ $cell['name'] }}
166
+                        @else
167
+                            {{ $cell }}
168
+                        @endif
169
+                    </td>
170
+                @endforeach
171
+            </tr>
172
+        @endforeach
173
+
174
+        <!-- Category Types -->
175
+        @foreach($category->types ?? [] as $type)
176
+            <!-- Type Header -->
177
+            <tr class="type-header-row">
178
+                @foreach($type->header as $index => $header)
179
+                    <td @class([
180
+                            $report->getAlignmentClass($index),
181
+                            'type-row-indent' => $index === 'account_name',
182
+                        ])
183
+                    >
184
+                        {{ $header }}
185
+                    </td>
186
+                @endforeach
187
+            </tr>
188
+
189
+            <!-- Type Data -->
190
+            @foreach($type->data as $typeRow)
191
+                <tr class="type-data-row">
192
+                    @foreach($typeRow as $index => $cell)
193
+                        <td @class([
194
+                                $report->getAlignmentClass($index),
195
+                                'whitespace-normal type-row-indent' => $index === 'account_name',
196
+                                'whitespace-nowrap' => $index !== 'account_name',
197
+                            ])
198
+                        >
199
+                            @if(is_array($cell) && isset($cell['name']))
200
+                                {{ $cell['name'] }}
201
+                            @else
202
+                                {{ $cell }}
203
+                            @endif
204
+                        </td>
205
+                    @endforeach
206
+                </tr>
207
+            @endforeach
208
+
209
+            <!-- Type Summary -->
210
+            <tr class="type-summary-row">
211
+                @foreach($type->summary as $index => $cell)
212
+                    <td @class([
213
+                            $report->getAlignmentClass($index),
214
+                            'type-row-indent' => $index === 'account_name',
215
+                        ])
216
+                    >
217
+                        {{ $cell }}
218
+                    </td>
219
+                @endforeach
220
+            </tr>
221
+        @endforeach
222
+
223
+        <tr class="category-summary-row">
224
+            @foreach($category->summary as $index => $cell)
225
+                <td @class([
226
+                        'cell',
227
+                        $report->getAlignmentClass($index),
228
+                        'underline-bold' => $loop->last,
229
+                    ])
230
+                >
231
+                    {{ $cell }}
232
+                </td>
233
+            @endforeach
234
+        </tr>
235
+
236
+        @unless($loop->last && empty($report->getOverallTotals()))
237
+            <tr class="spacer-row">
238
+                <td colspan="{{ count($report->getHeaders()) }}"></td>
239
+            </tr>
240
+        @endunless
241
+        </tbody>
242
+    @endforeach
243
+    <tfoot>
244
+    <tr class="table-footer-row">
245
+        @foreach ($report->getOverallTotals() as $index => $total)
246
+            <td class="{{ $report->getAlignmentClass($index) }}">
247
+                {{ $total }}
248
+            </td>
249
+        @endforeach
250
+    </tr>
251
+    </tfoot>
252
+</table>
253
+
254
+<!-- Second Overview Table -->
255
+<table class="table-class">
256
+    <colgroup>
257
+        @if(array_key_exists('account_code', $report->getHeaders()))
258
+            <col span="1" style="width: 20%;">
259
+            <col span="1" style="width: 55%;">
260
+            <col span="1" style="width: 25%;">
261
+        @else
262
+            <col span="1" style="width: 65%;">
263
+            <col span="1" style="width: 35%;">
264
+        @endif
265
+    </colgroup>
266
+    <thead class="table-head">
267
+    <tr>
268
+        @foreach($report->getOverviewHeaders() as $index => $header)
269
+            <th class="{{ $report->getAlignmentClass($index) }}">
270
+                {{ $header }}
271
+            </th>
272
+        @endforeach
273
+    </tr>
274
+    </thead>
275
+    <!-- Overview Content -->
276
+    @foreach($report->getOverview() as $overviewCategory)
277
+        <tbody>
278
+        <tr class="category-header-row">
279
+            @foreach($overviewCategory->header as $index => $header)
280
+                <td class="{{ $report->getAlignmentClass($index) }}">
281
+                    {{ $header }}
282
+                </td>
283
+            @endforeach
284
+        </tr>
285
+        @foreach($overviewCategory->data as $overviewAccount)
286
+            <tr>
287
+                @foreach($overviewAccount as $index => $cell)
288
+                    <td @class([
289
+                            $report->getAlignmentClass($index),
290
+                            'whitespace-normal' => $index === 'account_name',
291
+                            'whitespace-nowrap' => $index !== 'account_name',
292
+                        ])
293
+                    >
294
+                        @if(is_array($cell) && isset($cell['name']))
295
+                            {{ $cell['name'] }}
296
+                        @else
297
+                            {{ $cell }}
298
+                        @endif
299
+                    </td>
300
+                @endforeach
301
+            </tr>
302
+        @endforeach
303
+        <!-- Summary Row -->
304
+        <tr class="category-summary-row">
305
+            @foreach($overviewCategory->summary as $index => $summaryCell)
306
+                <td class="{{ $report->getAlignmentClass($index) }}">
307
+                    {{ $summaryCell }}
308
+                </td>
309
+            @endforeach
310
+        </tr>
311
+
312
+        @if($overviewCategory->header['account_name'] === 'Starting Balance')
313
+            @foreach($report->getOverviewAlignedWithColumns() as $summaryRow)
314
+                <tr>
315
+                    @foreach($summaryRow as $index => $summaryCell)
316
+                        <td @class([
317
+                                'cell',
318
+                                $report->getAlignmentClass($index),
319
+                                'font-bold' => $loop->parent->last,
320
+                                'underline-thin' => $loop->parent->remaining === 1 && $index === 'net_movement',
321
+                                'underline-bold' => $loop->parent->last && $index === 'net_movement',
322
+                            ])
323
+                        >
324
+                            {{ $summaryCell }}
325
+                        </td>
326
+                    @endforeach
327
+                </tr>
328
+            @endforeach
329
+        @endif
330
+        </tbody>
331
+    @endforeach
332
+</table>
333
+</body>
334
+</html>

+ 92
- 61
resources/views/components/company/reports/report-pdf.blade.php Parādīt failu

@@ -5,95 +5,105 @@
5 5
     <meta name="viewport" content="width=device-width, initial-scale=1">
6 6
     <title>{{ $report->getTitle() }}</title>
7 7
     <style>
8
+        .font-bold {
9
+            font-weight: bold;
10
+        }
11
+
12
+        .category-header-row > td,
13
+        .type-header-row > td {
14
+            background-color: #f3f4f6;
15
+            font-weight: bold;
16
+        }
17
+
18
+        .category-summary-row > td,
19
+        .table-footer-row > td,
20
+        .type-summary-row > td {
21
+            background-color: #ffffff;
22
+            font-weight: bold;
23
+        }
24
+
25
+        .company-name {
26
+            font-size: 1.125rem;
27
+            font-weight: bold;
28
+        }
29
+
30
+        .date-range {
31
+            font-size: 0.875rem;
32
+        }
33
+
8 34
         .header {
9 35
             color: #374151;
10 36
             margin-bottom: 1rem;
11 37
         }
12 38
 
13
-        .header > * + * {
39
+        .header div + div {
14 40
             margin-top: 0.5rem;
15 41
         }
16 42
 
17
-        .table-head {
18
-            display: table-row-group;
43
+        .spacer-row > td {
44
+            height: 0.75rem;
19 45
         }
20 46
 
21
-        .text-left {
22
-            text-align: left;
47
+        .table-body tr {
48
+            background-color: #ffffff;
23 49
         }
24 50
 
25
-        .text-right {
26
-            text-align: right;
51
+        .table-class {
52
+            border-collapse: collapse;
53
+            width: 100%;
27 54
         }
28 55
 
29
-        .text-center {
30
-            text-align: center;
56
+        .table-class .type-row-indent {
57
+            padding-left: 1.5rem;
31 58
         }
32 59
 
33
-        .table-class th,
34
-        .table-class td {
60
+        .table-class td,
61
+        .table-class th {
62
+            border-bottom: 1px solid #d1d5db;
35 63
             color: #374151;
64
+            font-size: 0.75rem;
65
+            line-height: 1rem;
66
+            padding: 0.75rem;
36 67
         }
37 68
 
38
-        .whitespace-normal {
39
-            white-space: normal;
40
-        }
41
-
42
-        .whitespace-nowrap {
43
-            white-space: nowrap;
44
-        }
45
-
46
-        .title {
47
-            font-size: 1.5rem;
69
+        .table-head {
70
+            display: table-row-group;
48 71
         }
49 72
 
50
-        .company-name {
51
-            font-size: 1.125rem;
52
-            font-weight: bold;
73
+        .text-center {
74
+            text-align: center;
53 75
         }
54 76
 
55
-        .date-range {
56
-            font-size: 0.875rem;
77
+        .text-left {
78
+            text-align: left;
57 79
         }
58 80
 
59
-        .table-class {
60
-            width: 100%;
61
-            border-collapse: collapse;
81
+        .text-right {
82
+            text-align: right;
62 83
         }
63 84
 
64
-        .table-class th,
65
-        .table-class td {
66
-            padding: 0.75rem;
67
-            font-size: 0.75rem;
68
-            line-height: 1rem;
69
-            border-bottom: 1px solid #d1d5db; /* Gray border for all rows */
85
+        .title {
86
+            font-size: 1.5rem;
70 87
         }
71 88
 
72
-        .category-header-row > td,
73
-        .type-header-row > td {
74
-            background-color: #f3f4f6; /* Gray background for category names */
75
-            font-weight: bold;
89
+        .table-class .underline-bold {
90
+            border-bottom: 2px solid #374151;
76 91
         }
77 92
 
78
-        .type-header-row > td,
79
-        .type-data-row > td,
80
-        .type-summary-row > td {
81
-            padding-left: 1.5rem; /* Indentation for type rows */
93
+        .table-class .underline-thin {
94
+            border-bottom: 1px solid #374151;
82 95
         }
83 96
 
84
-        .table-body tr {
85
-            background-color: #ffffff; /* White background for other rows */
97
+        .whitespace-normal {
98
+            white-space: normal;
86 99
         }
87 100
 
88
-        .spacer-row > td {
89
-            height: 0.75rem;
101
+        .whitespace-nowrap {
102
+            white-space: nowrap;
90 103
         }
91 104
 
92
-        .category-summary-row > td,
93
-        .type-summary-row > td,
94
-        .table-footer-row > td {
95
-            font-weight: bold;
96
-            background-color: #ffffff; /* White background for footer */
105
+        table tfoot {
106
+            display: table-row-group;
97 107
         }
98 108
     </style>
99 109
 </head>
@@ -129,7 +139,12 @@
129 139
         @foreach($category->data as $account)
130 140
             <tr>
131 141
                 @foreach($account as $index => $cell)
132
-                    <td class="{{ $report->getAlignmentClass($index) }} {{ $index === 1 ? 'whitespace-normal' : 'whitespace-nowrap' }}">
142
+                    <td @class([
143
+                            $report->getAlignmentClass($index),
144
+                            'whitespace-normal' => $index === 'account_name',
145
+                            'whitespace-nowrap' => $index !== 'account_name',
146
+                        ])
147
+                    >
133 148
                         @if(is_array($cell) && isset($cell['name']))
134 149
                             {{ $cell['name'] }}
135 150
                         @else
@@ -145,7 +160,11 @@
145 160
             <!-- Type Header -->
146 161
             <tr class="type-header-row">
147 162
                 @foreach($type->header as $index => $header)
148
-                    <td class="{{ $report->getAlignmentClass($index) }}">
163
+                    <td @class([
164
+                            $report->getAlignmentClass($index),
165
+                            'type-row-indent' => $index === 'account_name',
166
+                        ])
167
+                    >
149 168
                         {{ $header }}
150 169
                     </td>
151 170
                 @endforeach
@@ -155,7 +174,12 @@
155 174
             @foreach($type->data as $typeRow)
156 175
                 <tr class="type-data-row">
157 176
                     @foreach($typeRow as $index => $cell)
158
-                        <td class="{{ $report->getAlignmentClass($index) }} {{ $index === 'account_name' ? 'whitespace-normal' : 'whitespace-nowrap' }}">
177
+                        <td @class([
178
+                                $report->getAlignmentClass($index),
179
+                                'whitespace-normal type-row-indent' => $index === 'account_name',
180
+                                'whitespace-nowrap' => $index !== 'account_name',
181
+                            ])
182
+                        >
159 183
                             @if(is_array($cell) && isset($cell['name']))
160 184
                                 {{ $cell['name'] }}
161 185
                             @else
@@ -169,7 +193,11 @@
169 193
             <!-- Type Summary -->
170 194
             <tr class="type-summary-row">
171 195
                 @foreach($type->summary as $index => $cell)
172
-                    <td class="{{ $report->getAlignmentClass($index) }}">
196
+                    <td @class([
197
+                            $report->getAlignmentClass($index),
198
+                            'type-row-indent' => $index === 'account_name',
199
+                        ])
200
+                    >
173 201
                         {{ $cell }}
174 202
                     </td>
175 203
                 @endforeach
@@ -183,12 +211,15 @@
183 211
                 </td>
184 212
             @endforeach
185 213
         </tr>
186
-        <tr class="spacer-row">
187
-            <td colspan="{{ count($report->getHeaders()) }}"></td>
188
-        </tr>
214
+
215
+        @unless($loop->last && empty($report->getOverallTotals()))
216
+            <tr class="spacer-row">
217
+                <td colspan="{{ count($report->getHeaders()) }}"></td>
218
+            </tr>
219
+        @endunless
189 220
         </tbody>
190 221
     @endforeach
191
-    <tbody>
222
+    <tfoot>
192 223
     <tr class="table-footer-row">
193 224
         @foreach ($report->getOverallTotals() as $index => $total)
194 225
             <td class="{{ $report->getAlignmentClass($index) }}">
@@ -196,7 +227,7 @@
196 227
             </td>
197 228
         @endforeach
198 229
     </tr>
199
-    </tbody>
230
+    </tfoot>
200 231
 </table>
201 232
 </body>
202 233
 </html>

+ 5
- 1
resources/views/components/company/tables/cell.blade.php Parādīt failu

@@ -2,13 +2,15 @@
2 2
     'alignmentClass',
3 3
     'indent' => false,
4 4
     'bold' => false,
5
+    'underlineThin' => false,
6
+    'underlineBold' => false,
5 7
 ])
6 8
 
7 9
 <td
8 10
     @class([
9 11
         $alignmentClass,
10 12
         'last-of-type:pe-1 sm:last-of-type:pe-3',
11
-        'ps-3 sm:ps-6' => $indent,
13
+        'ps-4 sm:first-of-type:ps-7' => $indent,
12 14
         'p-0 first-of-type:ps-1 sm:first-of-type:ps-3' => ! $indent,
13 15
     ])
14 16
 >
@@ -16,6 +18,8 @@
16 18
         @class([
17 19
             'px-3 py-4 text-sm leading-6 text-gray-950 dark:text-white',
18 20
             'font-semibold' => $bold,
21
+            'border-b border-gray-700 dark:border-white/10' => $underlineThin,
22
+            'border-b-[1.5px] border-gray-800 dark:border-white/5' => $underlineBold,
19 23
         ])
20 24
     >
21 25
         {{ $slot }}

+ 1
- 1
resources/views/components/company/tables/footer.blade.php Parādīt failu

@@ -5,7 +5,7 @@
5 5
     <tr class="bg-gray-50 dark:bg-white/5">
6 6
         @foreach($totals as $totalIndex => $totalCell)
7 7
             <x-filament-tables::cell class="{{ $alignmentClass($totalIndex) }}">
8
-                <div class="px-3 py-3 text-sm leading-6 font-semibold text-gray-950 dark:text-white">
8
+                <div class="px-3 py-3.5 text-sm font-semibold leading-6 text-gray-950 dark:text-white">
9 9
                     {{ $totalCell }}
10 10
                 </div>
11 11
             </x-filament-tables::cell>

+ 7
- 2
resources/views/components/company/tables/reports/balance-sheet-summary.blade.php Parādīt failu

@@ -1,5 +1,10 @@
1
-<table class="w-full table-auto divide-y divide-gray-200 dark:divide-white/5">
2
-    <x-company.tables.header :headers="$report->getSummaryHeaders()" :alignment-class="[$report, 'getAlignmentClass']"/>
1
+<table class="w-full table-fixed divide-y divide-gray-200 dark:divide-white/5">
2
+    <colgroup>
3
+        <col span="1" style="width: 65%;">
4
+        <col span="1" style="width: 35%;">
5
+    </colgroup>
6
+    <x-company.tables.header :headers="$report->getSummaryHeaders()"
7
+                             :alignment-class="[$report, 'getAlignmentClass']"/>
3 8
     @foreach($report->getSummaryCategories() as $accountCategory)
4 9
         <tbody class="divide-y divide-gray-200 whitespace-nowrap dark:divide-white/5">
5 10
         <x-company.tables.category-header :category-headers="$accountCategory->header"

+ 15
- 6
resources/views/components/company/tables/reports/balance-sheet.blade.php Parādīt failu

@@ -1,4 +1,14 @@
1
-<table class="w-full table-auto divide-y divide-gray-200 dark:divide-white/5">
1
+<table class="w-full table-fixed divide-y divide-gray-200 dark:divide-white/5">
2
+    <colgroup>
3
+        @if(array_key_exists('account_code', $report->getHeaders()))
4
+            <col span="1" style="width: 20%;">
5
+            <col span="1" style="width: 55%;">
6
+            <col span="1" style="width: 25%;">
7
+        @else
8
+            <col span="1" style="width: 65%;">
9
+            <col span="1" style="width: 35%;">
10
+        @endif
11
+    </colgroup>
2 12
     <x-company.tables.header :headers="$report->getHeaders()" :alignment-class="[$report, 'getAlignmentClass']"/>
3 13
     @foreach($report->getCategories() as $accountCategory)
4 14
         <tbody class="divide-y divide-gray-200 whitespace-nowrap dark:divide-white/5">
@@ -7,8 +17,7 @@
7 17
         @foreach($accountCategory->data as $categoryAccount)
8 18
             <tr>
9 19
                 @foreach($categoryAccount as $accountIndex => $categoryAccountCell)
10
-                    <x-company.tables.cell :alignment-class="$report->getAlignmentClass($accountIndex)"
11
-                                           indent="true">
20
+                    <x-company.tables.cell :alignment-class="$report->getAlignmentClass($accountIndex)">
12 21
                         @if(is_array($categoryAccountCell) && isset($categoryAccountCell['name']))
13 22
                             @if($categoryAccountCell['name'] === 'Retained Earnings' && isset($categoryAccountCell['start_date']) && isset($categoryAccountCell['end_date']))
14 23
                                 <x-filament::link
@@ -53,7 +62,7 @@
53 62
             <tr class="bg-gray-50 dark:bg-white/5">
54 63
                 @foreach($accountType->header as $accountTypeHeaderIndex => $accountTypeHeaderCell)
55 64
                     <x-company.tables.cell :alignment-class="$report->getAlignmentClass($accountTypeHeaderIndex)"
56
-                                           indent="true" bold="true">
65
+                                           :indent="$accountTypeHeaderIndex === 'account_name'" bold="true">
57 66
                         {{ $accountTypeHeaderCell }}
58 67
                     </x-company.tables.cell>
59 68
                 @endforeach
@@ -62,7 +71,7 @@
62 71
                 <tr>
63 72
                     @foreach($typeAccount as $accountIndex => $typeAccountCell)
64 73
                         <x-company.tables.cell :alignment-class="$report->getAlignmentClass($accountIndex)"
65
-                                               indent="true">
74
+                                               :indent="$accountIndex === 'account_name'">
66 75
                             @if(is_array($typeAccountCell) && isset($typeAccountCell['name']))
67 76
                                 @if($typeAccountCell['name'] === 'Retained Earnings' && isset($typeAccountCell['start_date']) && isset($typeAccountCell['end_date']))
68 77
                                     <x-filament::link
@@ -106,7 +115,7 @@
106 115
             <tr>
107 116
                 @foreach($accountType->summary as $accountTypeSummaryIndex => $accountTypeSummaryCell)
108 117
                     <x-company.tables.cell :alignment-class="$report->getAlignmentClass($accountTypeSummaryIndex)"
109
-                                           indent="true" bold="true">
118
+                                           :indent="$accountTypeSummaryIndex === 'account_name'" bold="true">
110 119
                         {{ $accountTypeSummaryCell }}
111 120
                     </x-company.tables.cell>
112 121
                 @endforeach

+ 71
- 0
resources/views/components/company/tables/reports/cash-flow-statement-summary.blade.php Parādīt failu

@@ -0,0 +1,71 @@
1
+<table class="w-full table-fixed divide-y divide-gray-200 dark:divide-white/5">
2
+    <colgroup>
3
+        <col span="1" style="width: 65%;">
4
+        <col span="1" style="width: 35%;">
5
+    </colgroup>
6
+    <x-company.tables.header :headers="$report->getSummaryHeaders()"
7
+                             :alignment-class="[$report, 'getAlignmentClass']"/>
8
+    @foreach($report->getSummaryCategories() as $accountCategory)
9
+        <tbody class="divide-y divide-gray-200 whitespace-nowrap dark:divide-white/5">
10
+        <x-company.tables.category-header :category-headers="$accountCategory->header"
11
+                                          :alignment-class="[$report, 'getAlignmentClass']"/>
12
+        @foreach($accountCategory->types as $accountType)
13
+            <tr>
14
+                @foreach($accountType->summary as $accountTypeSummaryIndex => $accountTypeSummaryCell)
15
+                    <x-company.tables.cell :alignment-class="$report->getAlignmentClass($accountTypeSummaryIndex)">
16
+                        {{ $accountTypeSummaryCell }}
17
+                    </x-company.tables.cell>
18
+                @endforeach
19
+            </tr>
20
+        @endforeach
21
+        <tr>
22
+            @foreach($accountCategory->summary as $accountCategorySummaryIndex => $accountCategorySummaryCell)
23
+                <x-company.tables.cell :alignment-class="$report->getAlignmentClass($accountCategorySummaryIndex)"
24
+                                       bold="true" :underline-bold="$loop->last">
25
+                    {{ $accountCategorySummaryCell }}
26
+                </x-company.tables.cell>
27
+            @endforeach
28
+        </tr>
29
+        <tr>
30
+            <td colspan="{{ count($report->getSummaryHeaders()) }}">
31
+                <div class="min-h-12"></div>
32
+            </td>
33
+        </tr>
34
+        </tbody>
35
+    @endforeach
36
+</table>
37
+
38
+<table class="w-full table-fixed divide-y border-t divide-gray-200 dark:divide-white/5">
39
+    <colgroup>
40
+        <col span="1" style="width: 65%;">
41
+        <col span="1" style="width: 35%;">
42
+    </colgroup>
43
+    <x-company.tables.header :headers="$report->getSummaryOverviewHeaders()"
44
+                             :alignment-class="[$report, 'getAlignmentClass']"/>
45
+    @foreach($report->getSummaryOverview() as $overviewCategory)
46
+        <tbody class="divide-y divide-gray-200 whitespace-nowrap dark:divide-white/5">
47
+        <tr class="bg-gray-50 dark:bg-white/5">
48
+            @foreach($overviewCategory->summary as $overviewSummaryIndex => $overviewSummaryCell)
49
+                <x-company.tables.cell :alignment-class="$report->getAlignmentClass($overviewSummaryIndex)" bold="true">
50
+                    {{ $overviewSummaryCell }}
51
+                </x-company.tables.cell>
52
+            @endforeach
53
+        </tr>
54
+        @if($overviewCategory->summary['account_name'] === 'Starting Balance')
55
+            @foreach($report->getSummaryOverviewAlignedWithColumns() as $summaryRow)
56
+                <tr>
57
+                    @foreach($summaryRow as $summaryIndex => $summaryCell)
58
+                        <x-company.tables.cell :alignment-class="$report->getAlignmentClass($summaryIndex)"
59
+                                               :bold="$loop->parent->last"
60
+                                               :underline-bold="$loop->parent->last && $summaryIndex === 'net_movement'"
61
+                                               :underline-thin="$loop->parent->remaining === 1 && $summaryIndex === 'net_movement'"
62
+                        >
63
+                            {{ $summaryCell }}
64
+                        </x-company.tables.cell>
65
+                    @endforeach
66
+                </tr>
67
+            @endforeach
68
+        @endif
69
+        </tbody>
70
+    @endforeach
71
+</table>

+ 213
- 0
resources/views/components/company/tables/reports/cash-flow-statement.blade.php Parādīt failu

@@ -0,0 +1,213 @@
1
+<table class="w-full table-fixed divide-y divide-gray-200 dark:divide-white/5">
2
+    <colgroup>
3
+        @if(array_key_exists('account_code', $report->getHeaders()))
4
+            <col span="1" style="width: 20%;">
5
+            <col span="1" style="width: 55%;">
6
+            <col span="1" style="width: 25%;">
7
+        @else
8
+            <col span="1" style="width: 65%;">
9
+            <col span="1" style="width: 35%;">
10
+        @endif
11
+    </colgroup>
12
+    <x-company.tables.header :headers="$report->getHeaders()" :alignment-class="[$report, 'getAlignmentClass']"/>
13
+    @foreach($report->getCategories() as $accountCategory)
14
+        <tbody class="divide-y divide-gray-200 whitespace-nowrap dark:divide-white/5">
15
+        <x-company.tables.category-header :category-headers="$accountCategory->header"
16
+                                          :alignment-class="[$report, 'getAlignmentClass']"/>
17
+        @foreach($accountCategory->data as $categoryAccount)
18
+            <tr>
19
+                @foreach($categoryAccount as $accountIndex => $categoryAccountCell)
20
+                    <x-company.tables.cell :alignment-class="$report->getAlignmentClass($accountIndex)">
21
+                        @if(is_array($categoryAccountCell) && isset($categoryAccountCell['name']))
22
+                            @if($categoryAccountCell['name'] === 'Retained Earnings' && isset($categoryAccountCell['start_date']) && isset($categoryAccountCell['end_date']))
23
+                                <x-filament::link
24
+                                    color="primary"
25
+                                    target="_blank"
26
+                                    icon="heroicon-o-arrow-top-right-on-square"
27
+                                    :icon-position="\Filament\Support\Enums\IconPosition::After"
28
+                                    :icon-size="\Filament\Support\Enums\IconSize::Small"
29
+                                    href="{{ \App\Filament\Company\Pages\Reports\IncomeStatement::getUrl([
30
+                                            'startDate' => $categoryAccountCell['start_date'],
31
+                                            'endDate' => $categoryAccountCell['end_date']
32
+                                        ]) }}"
33
+                                >
34
+                                    {{ $categoryAccountCell['name'] }}
35
+                                </x-filament::link>
36
+                            @elseif(isset($categoryAccountCell['id']) && isset($categoryAccountCell['start_date']) && isset($categoryAccountCell['end_date']))
37
+                                <x-filament::link
38
+                                    color="primary"
39
+                                    target="_blank"
40
+                                    icon="heroicon-o-arrow-top-right-on-square"
41
+                                    :icon-position="\Filament\Support\Enums\IconPosition::After"
42
+                                    :icon-size="\Filament\Support\Enums\IconSize::Small"
43
+                                    href="{{ \App\Filament\Company\Pages\Reports\AccountTransactions::getUrl([
44
+                                            'startDate' => $categoryAccountCell['start_date'],
45
+                                            'endDate' => $categoryAccountCell['end_date'],
46
+                                            'selectedAccount' => $categoryAccountCell['id']
47
+                                        ]) }}"
48
+                                >
49
+                                    {{ $categoryAccountCell['name'] }}
50
+                                </x-filament::link>
51
+                            @else
52
+                                {{ $categoryAccountCell['name'] }}
53
+                            @endif
54
+                        @else
55
+                            {{ $categoryAccountCell }}
56
+                        @endif
57
+                    </x-company.tables.cell>
58
+                @endforeach
59
+            </tr>
60
+        @endforeach
61
+        @foreach($accountCategory->types as $accountType)
62
+            <tr class="bg-gray-50 dark:bg-white/5">
63
+                @foreach($accountType->header as $accountTypeHeaderIndex => $accountTypeHeaderCell)
64
+                    <x-company.tables.cell :alignment-class="$report->getAlignmentClass($accountTypeHeaderIndex)"
65
+                                           :indent="$accountTypeHeaderIndex === 'account_name'" bold="true">
66
+                        {{ $accountTypeHeaderCell }}
67
+                    </x-company.tables.cell>
68
+                @endforeach
69
+            </tr>
70
+            @foreach($accountType->data as $typeAccount)
71
+                <tr>
72
+                    @foreach($typeAccount as $accountIndex => $typeAccountCell)
73
+                        <x-company.tables.cell :alignment-class="$report->getAlignmentClass($accountIndex)"
74
+                                               :indent="$accountIndex === 'account_name'">
75
+                            @if(is_array($typeAccountCell) && isset($typeAccountCell['name']))
76
+                                @if($typeAccountCell['name'] === 'Retained Earnings' && isset($typeAccountCell['start_date']) && isset($typeAccountCell['end_date']))
77
+                                    <x-filament::link
78
+                                        color="primary"
79
+                                        target="_blank"
80
+                                        icon="heroicon-o-arrow-top-right-on-square"
81
+                                        :icon-position="\Filament\Support\Enums\IconPosition::After"
82
+                                        :icon-size="\Filament\Support\Enums\IconSize::Small"
83
+                                        href="{{ \App\Filament\Company\Pages\Reports\IncomeStatement::getUrl([
84
+                                            'startDate' => $typeAccountCell['start_date'],
85
+                                            'endDate' => $typeAccountCell['end_date']
86
+                                        ]) }}"
87
+                                    >
88
+                                        {{ $typeAccountCell['name'] }}
89
+                                    </x-filament::link>
90
+                                @elseif(isset($typeAccountCell['id']) && isset($typeAccountCell['start_date']) && isset($typeAccountCell['end_date']))
91
+                                    <x-filament::link
92
+                                        color="primary"
93
+                                        target="_blank"
94
+                                        icon="heroicon-o-arrow-top-right-on-square"
95
+                                        :icon-position="\Filament\Support\Enums\IconPosition::After"
96
+                                        :icon-size="\Filament\Support\Enums\IconSize::Small"
97
+                                        href="{{ \App\Filament\Company\Pages\Reports\AccountTransactions::getUrl([
98
+                                            'startDate' => $typeAccountCell['start_date'],
99
+                                            'endDate' => $typeAccountCell['end_date'],
100
+                                            'selectedAccount' => $typeAccountCell['id']
101
+                                        ]) }}"
102
+                                    >
103
+                                        {{ $typeAccountCell['name'] }}
104
+                                    </x-filament::link>
105
+                                @else
106
+                                    {{ $typeAccountCell['name'] }}
107
+                                @endif
108
+                            @else
109
+                                {{ $typeAccountCell }}
110
+                            @endif
111
+                        </x-company.tables.cell>
112
+                    @endforeach
113
+                </tr>
114
+            @endforeach
115
+            <tr>
116
+                @foreach($accountType->summary as $accountTypeSummaryIndex => $accountTypeSummaryCell)
117
+                    <x-company.tables.cell :alignment-class="$report->getAlignmentClass($accountTypeSummaryIndex)"
118
+                                           :indent="$accountTypeSummaryIndex === 'account_name'" bold="true">
119
+                        {{ $accountTypeSummaryCell }}
120
+                    </x-company.tables.cell>
121
+                @endforeach
122
+            </tr>
123
+        @endforeach
124
+        <tr>
125
+            @foreach($accountCategory->summary as $accountCategorySummaryIndex => $accountCategorySummaryCell)
126
+                <x-company.tables.cell :alignment-class="$report->getAlignmentClass($accountCategorySummaryIndex)"
127
+                                       bold="true" :underline-bold="$loop->last">
128
+                    {{ $accountCategorySummaryCell }}
129
+                </x-company.tables.cell>
130
+            @endforeach
131
+        </tr>
132
+        <tr>
133
+            <td colspan="{{ count($report->getHeaders()) }}">
134
+                <div class="min-h-12"></div>
135
+            </td>
136
+        </tr>
137
+        </tbody>
138
+    @endforeach
139
+    <x-company.tables.footer :totals="$report->getOverallTotals()" :alignment-class="[$report, 'getAlignmentClass']"/>
140
+</table>
141
+
142
+<table class="w-full table-fixed divide-y border-t divide-gray-200 dark:divide-white/5">
143
+    <colgroup>
144
+        @if(array_key_exists('account_code', $report->getHeaders()))
145
+            <col span="1" style="width: 20%;">
146
+            <col span="1" style="width: 55%;">
147
+            <col span="1" style="width: 25%;">
148
+        @else
149
+            <col span="1" style="width: 65%;">
150
+            <col span="1" style="width: 35%;">
151
+        @endif
152
+    </colgroup>
153
+    <x-company.tables.header :headers="$report->getOverviewHeaders()"
154
+                             :alignment-class="[$report, 'getAlignmentClass']"/>
155
+    @foreach($report->getOverview() as $overviewCategory)
156
+        <tbody class="divide-y divide-gray-200 whitespace-nowrap dark:divide-white/5">
157
+        <x-company.tables.category-header :category-headers="$overviewCategory->header"
158
+                                          :alignment-class="[$report, 'getAlignmentClass']"/>
159
+        @foreach($overviewCategory->data as $overviewAccount)
160
+            <tr>
161
+                @foreach($overviewAccount as $overviewIndex => $overviewCell)
162
+                    <x-company.tables.cell :alignment-class="$report->getAlignmentClass($overviewIndex)">
163
+                        @if(is_array($overviewCell) && isset($overviewCell['name']))
164
+                            @if(isset($overviewCell['id']) && isset($overviewCell['start_date']) && isset($overviewCell['end_date']))
165
+                                <x-filament::link
166
+                                    color="primary"
167
+                                    target="_blank"
168
+                                    icon="heroicon-o-arrow-top-right-on-square"
169
+                                    :icon-position="\Filament\Support\Enums\IconPosition::After"
170
+                                    :icon-size="\Filament\Support\Enums\IconSize::Small"
171
+                                    href="{{ \App\Filament\Company\Pages\Reports\AccountTransactions::getUrl([
172
+                                            'startDate' => $overviewCell['start_date'],
173
+                                            'endDate' => $overviewCell['end_date'],
174
+                                            'selectedAccount' => $overviewCell['id']
175
+                                        ]) }}"
176
+                                >
177
+                                    {{ $overviewCell['name'] }}
178
+                                </x-filament::link>
179
+                            @else
180
+                                {{ $overviewCell['name'] }}
181
+                            @endif
182
+                        @else
183
+                            {{ $overviewCell }}
184
+                        @endif
185
+                    </x-company.tables.cell>
186
+                @endforeach
187
+            </tr>
188
+        @endforeach
189
+        <tr>
190
+            @foreach($overviewCategory->summary as $overviewSummaryIndex => $overviewSummaryCell)
191
+                <x-company.tables.cell :alignment-class="$report->getAlignmentClass($overviewSummaryIndex)" bold="true">
192
+                    {{ $overviewSummaryCell }}
193
+                </x-company.tables.cell>
194
+            @endforeach
195
+        </tr>
196
+        @if($overviewCategory->header['account_name'] === 'Starting Balance')
197
+            @foreach($report->getOverviewAlignedWithColumns() as $summaryRow)
198
+                <tr>
199
+                    @foreach($summaryRow as $summaryIndex => $summaryCell)
200
+                        <x-company.tables.cell :alignment-class="$report->getAlignmentClass($summaryIndex)"
201
+                                               :bold="$loop->parent->last"
202
+                                               :underline-bold="$loop->parent->last && $summaryIndex === 'net_movement'"
203
+                                               :underline-thin="$loop->parent->remaining === 1 && $summaryIndex === 'net_movement'"
204
+                        >
205
+                            {{ $summaryCell }}
206
+                        </x-company.tables.cell>
207
+                    @endforeach
208
+                </tr>
209
+            @endforeach
210
+        @endif
211
+        </tbody>
212
+    @endforeach
213
+</table>

+ 10
- 7
resources/views/components/company/tables/reports/income-statement-summary.blade.php Parādīt failu

@@ -1,5 +1,10 @@
1
-<table class="w-full table-auto divide-y divide-gray-200 dark:divide-white/5">
2
-    <x-company.tables.header :headers="$report->getSummaryHeaders()" :alignment-class="[$report, 'getAlignmentClass']"/>
1
+<table class="w-full table-fixed divide-y divide-gray-200 dark:divide-white/5">
2
+    <colgroup>
3
+        <col span="1" style="width: 65%;">
4
+        <col span="1" style="width: 35%;">
5
+    </colgroup>
6
+    <x-company.tables.header :headers="$report->getSummaryHeaders()"
7
+                             :alignment-class="[$report, 'getAlignmentClass']"/>
3 8
     @foreach($report->getSummaryCategories() as $accountCategory)
4 9
         <tbody class="divide-y divide-gray-200 whitespace-nowrap dark:divide-white/5">
5 10
         <tr>
@@ -13,11 +18,9 @@
13 18
         @if($accountCategory->header['account_name'] === 'Cost of Goods Sold')
14 19
             <tr class="bg-gray-50 dark:bg-white/5">
15 20
                 @foreach($report->getGrossProfit() as $grossProfitIndex => $grossProfitCell)
16
-                    <x-filament-tables::cell class="{{ $report->getAlignmentClass($grossProfitIndex) }}">
17
-                        <div class="px-3 py-2 text-sm leading-6 font-semibold text-gray-950 dark:text-white">
18
-                            {{ $grossProfitCell }}
19
-                        </div>
20
-                    </x-filament-tables::cell>
21
+                    <x-company.tables.cell :alignment-class="$report->getAlignmentClass($grossProfitIndex)" bold="true">
22
+                        {{ $grossProfitCell }}
23
+                    </x-company.tables.cell>
21 24
                 @endforeach
22 25
             </tr>
23 26
         @endif

+ 77
- 0
resources/views/components/company/tables/reports/income-statement.blade.php Parādīt failu

@@ -0,0 +1,77 @@
1
+<table class="w-full table-fixed divide-y divide-gray-200 dark:divide-white/5">
2
+    <colgroup>
3
+        @if(array_key_exists('account_code', $report->getHeaders()))
4
+            <col span="1" style="width: 20%;">
5
+            <col span="1" style="width: 55%;">
6
+            <col span="1" style="width: 25%;">
7
+        @else
8
+            <col span="1" style="width: 65%;">
9
+            <col span="1" style="width: 35%;">
10
+        @endif
11
+    </colgroup>
12
+    <x-company.tables.header :headers="$report->getHeaders()" :alignment-class="[$report, 'getAlignmentClass']"/>
13
+    @foreach($report->getCategories() as $accountCategory)
14
+        <tbody class="divide-y divide-gray-200 whitespace-nowrap dark:divide-white/5">
15
+        <x-company.tables.category-header :category-headers="$accountCategory->header"
16
+                                          :alignment-class="[$report, 'getAlignmentClass']"/>
17
+        @foreach($accountCategory->data as $categoryAccount)
18
+            <tr>
19
+                @foreach($categoryAccount as $accountIndex => $categoryAccountCell)
20
+                    <x-company.tables.cell :alignment-class="$report->getAlignmentClass($accountIndex)">
21
+                        @if(is_array($categoryAccountCell) && isset($categoryAccountCell['name']))
22
+                            @if($categoryAccountCell['name'] === 'Retained Earnings' && isset($categoryAccountCell['start_date']) && isset($categoryAccountCell['end_date']))
23
+                                <x-filament::link
24
+                                    color="primary"
25
+                                    target="_blank"
26
+                                    icon="heroicon-o-arrow-top-right-on-square"
27
+                                    :icon-position="\Filament\Support\Enums\IconPosition::After"
28
+                                    :icon-size="\Filament\Support\Enums\IconSize::Small"
29
+                                    href="{{ \App\Filament\Company\Pages\Reports\IncomeStatement::getUrl([
30
+                                            'startDate' => $categoryAccountCell['start_date'],
31
+                                            'endDate' => $categoryAccountCell['end_date']
32
+                                        ]) }}"
33
+                                >
34
+                                    {{ $categoryAccountCell['name'] }}
35
+                                </x-filament::link>
36
+                            @elseif(isset($categoryAccountCell['id']) && isset($categoryAccountCell['start_date']) && isset($categoryAccountCell['end_date']))
37
+                                <x-filament::link
38
+                                    color="primary"
39
+                                    target="_blank"
40
+                                    icon="heroicon-o-arrow-top-right-on-square"
41
+                                    :icon-position="\Filament\Support\Enums\IconPosition::After"
42
+                                    :icon-size="\Filament\Support\Enums\IconSize::Small"
43
+                                    href="{{ \App\Filament\Company\Pages\Reports\AccountTransactions::getUrl([
44
+                                            'startDate' => $categoryAccountCell['start_date'],
45
+                                            'endDate' => $categoryAccountCell['end_date'],
46
+                                            'selectedAccount' => $categoryAccountCell['id']
47
+                                        ]) }}"
48
+                                >
49
+                                    {{ $categoryAccountCell['name'] }}
50
+                                </x-filament::link>
51
+                            @else
52
+                                {{ $categoryAccountCell['name'] }}
53
+                            @endif
54
+                        @else
55
+                            {{ $categoryAccountCell }}
56
+                        @endif
57
+                    </x-company.tables.cell>
58
+                @endforeach
59
+            </tr>
60
+        @endforeach
61
+        <tr>
62
+            @foreach($accountCategory->summary as $accountCategorySummaryIndex => $accountCategorySummaryCell)
63
+                <x-company.tables.cell :alignment-class="$report->getAlignmentClass($accountCategorySummaryIndex)"
64
+                                       bold="true">
65
+                    {{ $accountCategorySummaryCell }}
66
+                </x-company.tables.cell>
67
+            @endforeach
68
+        </tr>
69
+        <tr>
70
+            <td colspan="{{ count($report->getHeaders()) }}">
71
+                <div class="min-h-12"></div>
72
+            </td>
73
+        </tr>
74
+        </tbody>
75
+    @endforeach
76
+    <x-company.tables.footer :totals="$report->getOverallTotals()" :alignment-class="[$report, 'getAlignmentClass']"/>
77
+</table>

+ 87
- 0
resources/views/filament/company/pages/reports/cash-flow-statement.blade.php Parādīt failu

@@ -0,0 +1,87 @@
1
+<x-filament-panels::page>
2
+    <x-filament::section>
3
+        <div class="flex flex-col md:flex-row items-start md:items-center justify-between gap-4">
4
+            <!-- Form Container -->
5
+            @if(method_exists($this, 'filtersForm'))
6
+                {{ $this->filtersForm }}
7
+            @endif
8
+
9
+            <!-- Grouping Button and Column Toggle -->
10
+            @if($this->hasToggleableColumns())
11
+                <x-filament-tables::column-toggle.dropdown
12
+                    :form="$this->getTableColumnToggleForm()"
13
+                    :trigger-action="$this->getToggleColumnsTriggerAction()"
14
+                />
15
+            @endif
16
+
17
+            <div class="inline-flex items-center min-w-0 md:min-w-[9.5rem] justify-end">
18
+                {{ $this->applyFiltersAction }}
19
+            </div>
20
+        </div>
21
+    </x-filament::section>
22
+
23
+
24
+    <x-filament::section>
25
+        <!-- Summary Section -->
26
+        @if($this->reportLoaded)
27
+            <div
28
+                class="flex flex-col md:flex-row items-center md:items-end text-center justify-center gap-4 md:gap-8">
29
+                @foreach($this->report->getSummary() as $summary)
30
+                    <div class="text-sm">
31
+                        <div class="text-gray-600 font-medium mb-2">{{ $summary['label'] }}</div>
32
+
33
+                        @php
34
+                            $isNetCashFlow = $summary['label'] === 'Net Cash Flow';
35
+                            $isPositive = money($summary['value'], \App\Utilities\Currency\CurrencyAccessor::getDefaultCurrency())->isPositive();
36
+                        @endphp
37
+
38
+                        <strong
39
+                            @class([
40
+                                'text-lg',
41
+                                'text-green-700' => $isNetCashFlow && $isPositive,
42
+                                'text-danger-700' => $isNetCashFlow && ! $isPositive,
43
+                            ])
44
+                        >
45
+                            {{ $summary['value'] }}
46
+                        </strong>
47
+                    </div>
48
+
49
+                    @if(! $loop->last)
50
+                        <div class="flex items-center justify-center px-2">
51
+                            <strong class="text-lg">
52
+                                {{ $loop->remaining === 1 ? '=' : '-' }}
53
+                            </strong>
54
+                        </div>
55
+                    @endif
56
+                @endforeach
57
+            </div>
58
+        @endif
59
+    </x-filament::section>
60
+
61
+    <x-filament::tabs>
62
+        <x-filament::tabs.item
63
+            :active="$activeTab === 'summary'"
64
+            wire:click="$set('activeTab', 'summary')"
65
+        >
66
+            Summary
67
+        </x-filament::tabs.item>
68
+
69
+        <x-filament::tabs.item
70
+            :active="$activeTab === 'details'"
71
+            wire:click="$set('activeTab', 'details')"
72
+        >
73
+            Details
74
+        </x-filament::tabs.item>
75
+    </x-filament::tabs>
76
+
77
+    <x-company.tables.container :report-loaded="$this->reportLoaded">
78
+        @if($this->report)
79
+            @if($activeTab === 'summary')
80
+                <x-company.tables.reports.cash-flow-statement-summary :report="$this->report"/>
81
+            @elseif($activeTab === 'details')
82
+                <x-company.tables.reports.cash-flow-statement :report="$this->report"/>
83
+            @endif
84
+        @endif
85
+    </x-company.tables.container>
86
+</x-filament-panels::page>
87
+

+ 1
- 1
resources/views/filament/company/pages/reports/income-statement.blade.php Parādīt failu

@@ -79,7 +79,7 @@
79 79
             @if($activeTab === 'summary')
80 80
                 <x-company.tables.reports.income-statement-summary :report="$this->report"/>
81 81
             @elseif($activeTab === 'details')
82
-                <x-company.tables.reports.detailed-report :report="$this->report"/>
82
+                <x-company.tables.reports.income-statement :report="$this->report"/>
83 83
             @endif
84 84
         @endif
85 85
     </x-company.tables.container>

+ 0
- 2
tests/Feature/Reports/TrialBalanceReportTest.php Parādīt failu

@@ -70,7 +70,6 @@ it('correctly builds a standard trial balance report', function () {
70 70
         ->call('applyFilters')
71 71
         ->assertDontSeeText('Retained Earnings')
72 72
         ->assertSeeTextInOrder([
73
-            $defaultBankAccountAccount->code,
74 73
             $defaultBankAccountAccount->name,
75 74
             $formattedExpectedBalances->debitBalance,
76 75
             $formattedExpectedBalances->creditBalance,
@@ -143,7 +142,6 @@ it('correctly builds a post-closing trial balance report', function () {
143 142
             'asOfDate' => $defaultEndDate->toDateString(),
144 143
         ])
145 144
         ->assertSeeTextInOrder([
146
-            $defaultBankAccountAccount->code,
147 145
             $defaultBankAccountAccount->name,
148 146
             $formattedExpectedBalances->debitBalance,
149 147
             $formattedExpectedBalances->creditBalance,

Notiek ielāde…
Atcelt
Saglabāt