Selaa lähdekoodia

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

Development 3.x
3.x
Andrew Wallo 11 kuukautta sitten
vanhempi
commit
61adbe2fc5
No account linked to committer's email address
55 muutettua tiedostoa jossa 1654 lisäystä ja 603 poistoa
  1. 6
    0
      app/Contracts/ExportableReport.php
  2. 23
    0
      app/Contracts/HasSummaryReport.php
  3. 5
    3
      app/DTO/AccountCategoryDTO.php
  4. 14
    0
      app/DTO/AccountTypeDTO.php
  5. 9
    3
      app/DTO/ReportCategoryDTO.php
  6. 12
    0
      app/DTO/ReportTypeDTO.php
  7. 22
    0
      app/Enums/Accounting/AccountType.php
  8. 3
    0
      app/Filament/Company/Clusters/Settings/Resources/CurrencyResource.php
  9. 1
    6
      app/Filament/Company/Pages/Accounting/AccountChart.php
  10. 1
    1
      app/Filament/Company/Pages/Accounting/Transactions.php
  11. 2
    1
      app/Filament/Company/Pages/Reports.php
  12. 1
    1
      app/Filament/Company/Pages/Reports/AccountTransactions.php
  13. 87
    0
      app/Filament/Company/Pages/Reports/BalanceSheet.php
  14. 4
    0
      app/Filament/Company/Pages/Reports/IncomeStatement.php
  15. 4
    1
      app/Filament/Company/Resources/Banking/AccountResource.php
  16. 2
    1
      app/Filament/Company/Resources/Banking/AccountResource/Pages/EditAccount.php
  17. 1
    1
      app/Listeners/UpdateAccountBalances.php
  18. 1
    1
      app/Models/Accounting/Transaction.php
  19. 2
    1
      app/Services/AccountService.php
  20. 59
    43
      app/Services/ExportService.php
  21. 112
    6
      app/Services/ReportService.php
  22. 5
    16
      app/Transformers/AccountBalanceReportTransformer.php
  23. 5
    16
      app/Transformers/AccountTransactionReportTransformer.php
  24. 261
    0
      app/Transformers/BalanceSheetReportTransformer.php
  25. 42
    5
      app/Transformers/BaseReportTransformer.php
  26. 92
    21
      app/Transformers/IncomeStatementReportTransformer.php
  27. 34
    0
      app/Transformers/SummaryReportTransformer.php
  28. 5
    15
      app/Transformers/TrialBalanceReportTransformer.php
  29. 211
    208
      composer.lock
  30. 15
    13
      config/chart-of-accounts.php
  31. 3
    4
      database/factories/Accounting/TransactionFactory.php
  32. 5
    0
      database/factories/Setting/CompanyDefaultFactory.php
  33. 2
    2
      database/factories/Setting/CurrencyFactory.php
  34. 28
    0
      database/migrations/2024_10_13_163049_update_posted_at_column_in_transactions_table.php
  35. 27
    27
      package-lock.json
  36. 1
    5
      resources/css/filament/company/theme.css
  37. 3
    3
      resources/views/components/company/reports/account-transactions-report-pdf.blade.php
  38. 48
    3
      resources/views/components/company/reports/report-pdf.blade.php
  39. 20
    0
      resources/views/components/company/tables/category-header.blade.php
  40. 23
    0
      resources/views/components/company/tables/cell.blade.php
  41. 24
    0
      resources/views/components/company/tables/container.blade.php
  42. 15
    0
      resources/views/components/company/tables/footer.blade.php
  43. 16
    0
      resources/views/components/company/tables/header.blade.php
  44. 10
    23
      resources/views/components/company/tables/reports/account-transactions.blade.php
  45. 31
    0
      resources/views/components/company/tables/reports/balance-sheet-summary.blade.php
  46. 131
    0
      resources/views/components/company/tables/reports/balance-sheet.blade.php
  47. 49
    79
      resources/views/components/company/tables/reports/detailed-report.blade.php
  48. 28
    0
      resources/views/components/company/tables/reports/income-statement-summary.blade.php
  49. 12
    6
      resources/views/filament/company/pages/accounting/chart.blade.php
  50. 12
    27
      resources/views/filament/company/pages/reports/account-transactions.blade.php
  51. 89
    0
      resources/views/filament/company/pages/reports/balance-sheet.blade.php
  52. 5
    20
      resources/views/filament/company/pages/reports/detailed-report.blade.php
  53. 23
    18
      resources/views/filament/company/pages/reports/income-statement.blade.php
  54. 5
    20
      resources/views/filament/company/pages/reports/trial-balance.blade.php
  55. 3
    3
      tests/Feature/Accounting/TransactionTest.php

+ 6
- 0
app/Contracts/ExportableReport.php Näytä tiedosto

@@ -3,6 +3,7 @@
3 3
 namespace App\Contracts;
4 4
 
5 5
 use App\DTO\ReportCategoryDTO;
6
+use App\Support\Column;
6 7
 
7 8
 interface ExportableReport
8 9
 {
@@ -17,7 +18,12 @@ interface ExportableReport
17 18
 
18 19
     public function getOverallTotals(): array;
19 20
 
21
+    /**
22
+     * @return Column[]
23
+     */
20 24
     public function getColumns(): array;
21 25
 
22 26
     public function getPdfView(): string;
27
+
28
+    public function getAlignmentClass(string $columnName): string;
23 29
 }

+ 23
- 0
app/Contracts/HasSummaryReport.php Näytä tiedosto

@@ -0,0 +1,23 @@
1
+<?php
2
+
3
+namespace App\Contracts;
4
+
5
+use App\DTO\ReportCategoryDTO;
6
+use App\Support\Column;
7
+
8
+interface HasSummaryReport
9
+{
10
+    /**
11
+     * @return Column[]
12
+     */
13
+    public function getSummaryColumns(): array;
14
+
15
+    public function getSummaryHeaders(): array;
16
+
17
+    /**
18
+     * @return ReportCategoryDTO[]
19
+     */
20
+    public function getSummaryCategories(): array;
21
+
22
+    public function getSummaryOverallTotals(): array;
23
+}

+ 5
- 3
app/DTO/AccountCategoryDTO.php Näytä tiedosto

@@ -5,10 +5,12 @@ namespace App\DTO;
5 5
 class AccountCategoryDTO
6 6
 {
7 7
     /**
8
-     * @param  AccountDTO[]  $accounts
8
+     * @param  AccountDTO[]|null  $accounts
9
+     * @param  AccountTypeDTO[]|null  $types
9 10
      */
10 11
     public function __construct(
11
-        public array $accounts,
12
-        public AccountBalanceDTO $summary,
12
+        public ?array $accounts = null,
13
+        public ?array $types = null,
14
+        public ?AccountBalanceDTO $summary = null,
13 15
     ) {}
14 16
 }

+ 14
- 0
app/DTO/AccountTypeDTO.php Näytä tiedosto

@@ -0,0 +1,14 @@
1
+<?php
2
+
3
+namespace App\DTO;
4
+
5
+class AccountTypeDTO
6
+{
7
+    /**
8
+     * @param  AccountDTO[]  $accounts
9
+     */
10
+    public function __construct(
11
+        public array $accounts,
12
+        public AccountBalanceDTO $summary,
13
+    ) {}
14
+}

+ 9
- 3
app/DTO/ReportCategoryDTO.php Näytä tiedosto

@@ -4,9 +4,15 @@ namespace App\DTO;
4 4
 
5 5
 class ReportCategoryDTO
6 6
 {
7
+    /**
8
+     * ReportCategoryDTO constructor.
9
+     *
10
+     * @param  ReportTypeDTO[]|null  $types
11
+     */
7 12
     public function __construct(
8
-        public array $header,
9
-        public array $data,
10
-        public array $summary = [],
13
+        public ?array $header = null,
14
+        public ?array $data = null,
15
+        public ?array $summary = null,
16
+        public ?array $types = null,
11 17
     ) {}
12 18
 }

+ 12
- 0
app/DTO/ReportTypeDTO.php Näytä tiedosto

@@ -0,0 +1,12 @@
1
+<?php
2
+
3
+namespace App\DTO;
4
+
5
+class ReportTypeDTO
6
+{
7
+    public function __construct(
8
+        public ?array $header = null,
9
+        public ?array $data = null,
10
+        public ?array $summary = null,
11
+    ) {}
12
+}

+ 22
- 0
app/Enums/Accounting/AccountType.php Näytä tiedosto

@@ -45,6 +45,28 @@ enum AccountType: string implements HasLabel
45 45
         };
46 46
     }
47 47
 
48
+    public function getPluralLabel(): ?string
49
+    {
50
+        return match ($this) {
51
+            self::CurrentAsset => 'Current Assets',
52
+            self::NonCurrentAsset => 'Non-Current Assets',
53
+            self::ContraAsset => 'Contra Assets',
54
+            self::CurrentLiability => 'Current Liabilities',
55
+            self::NonCurrentLiability => 'Non-Current Liabilities',
56
+            self::ContraLiability => 'Contra Liabilities',
57
+            self::Equity => 'Equity',
58
+            self::ContraEquity => 'Contra Equity',
59
+            self::OperatingRevenue => 'Operating Revenue',
60
+            self::NonOperatingRevenue => 'Non-Operating Revenue',
61
+            self::ContraRevenue => 'Contra Revenue',
62
+            self::UncategorizedRevenue => 'Uncategorized Revenue',
63
+            self::OperatingExpense => 'Operating Expenses',
64
+            self::NonOperatingExpense => 'Non-Operating Expenses',
65
+            self::ContraExpense => 'Contra Expenses',
66
+            self::UncategorizedExpense => 'Uncategorized Expenses',
67
+        };
68
+    }
69
+
48 70
     public function getCategory(): AccountCategory
49 71
     {
50 72
         return match ($this) {

+ 3
- 0
app/Filament/Company/Clusters/Settings/Resources/CurrencyResource.php Näytä tiedosto

@@ -185,6 +185,9 @@ class CurrencyResource extends Resource
185 185
                                     $action->cancel();
186 186
                                 }
187 187
                             }
188
+                        })
189
+                        ->hidden(function (Table $table) {
190
+                            return $table->getAllSelectableRecordsCount() === 0;
188 191
                         }),
189 192
                 ]),
190 193
             ])

+ 1
- 6
app/Filament/Company/Pages/Accounting/AccountChart.php Näytä tiedosto

@@ -33,12 +33,7 @@ class AccountChart extends Page
33 33
     protected static string $view = 'filament.company.pages.accounting.chart';
34 34
 
35 35
     #[Url]
36
-    public ?string $activeTab = null;
37
-
38
-    public function mount(): void
39
-    {
40
-        $this->activeTab = $this->activeTab ?? AccountCategory::Asset->value;
41
-    }
36
+    public ?string $activeTab = AccountCategory::Asset->value;
42 37
 
43 38
     protected function configureAction(Action $action): void
44 39
     {

+ 1
- 1
app/Filament/Company/Pages/Accounting/Transactions.php Näytä tiedosto

@@ -459,7 +459,7 @@ class Transactions extends Page implements HasTable
459 459
     protected function getFormDefaultsForType(TransactionType $type): array
460 460
     {
461 461
         $commonDefaults = [
462
-            'posted_at' => now()->format('Y-m-d'),
462
+            'posted_at' => today(),
463 463
         ];
464 464
 
465 465
         return match ($type) {

+ 2
- 1
app/Filament/Company/Pages/Reports.php Näytä tiedosto

@@ -4,6 +4,7 @@ namespace App\Filament\Company\Pages;
4 4
 
5 5
 use App\Filament\Company\Pages\Reports\AccountBalances;
6 6
 use App\Filament\Company\Pages\Reports\AccountTransactions;
7
+use App\Filament\Company\Pages\Reports\BalanceSheet;
7 8
 use App\Filament\Company\Pages\Reports\IncomeStatement;
8 9
 use App\Filament\Company\Pages\Reports\TrialBalance;
9 10
 use App\Infolists\Components\ReportEntry;
@@ -41,7 +42,7 @@ class Reports extends Page
41 42
                             ->description('Snapshot of assets, liabilities, and equity at a specific point in time.')
42 43
                             ->icon('heroicon-o-clipboard-document-list')
43 44
                             ->iconColor(Color::Emerald)
44
-                            ->url('#'),
45
+                            ->url(BalanceSheet::getUrl()),
45 46
                         ReportEntry::make('cash_flow_statement')
46 47
                             ->hiddenLabel()
47 48
                             ->heading('Cash Flow Statement')

+ 1
- 1
app/Filament/Company/Pages/Reports/AccountTransactions.php Näytä tiedosto

@@ -71,7 +71,7 @@ class AccountTransactions extends BaseReportPage
71 71
                 ->label('Credit')
72 72
                 ->alignment(Alignment::Right),
73 73
             Column::make('balance')
74
-                ->label('Balance')
74
+                ->label('Running Balance')
75 75
                 ->alignment(Alignment::Right),
76 76
         ];
77 77
     }

+ 87
- 0
app/Filament/Company/Pages/Reports/BalanceSheet.php Näytä tiedosto

@@ -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\Filament\Forms\Components\DateRangeSelect;
8
+use App\Services\ExportService;
9
+use App\Services\ReportService;
10
+use App\Support\Column;
11
+use App\Transformers\BalanceSheetReportTransformer;
12
+use Filament\Forms\Form;
13
+use Filament\Support\Enums\Alignment;
14
+use Livewire\Attributes\Url;
15
+use Symfony\Component\HttpFoundation\StreamedResponse;
16
+
17
+class BalanceSheet extends BaseReportPage
18
+{
19
+    protected static string $view = 'filament.company.pages.reports.balance-sheet';
20
+
21
+    protected static bool $shouldRegisterNavigation = false;
22
+
23
+    protected ReportService $reportService;
24
+
25
+    protected ExportService $exportService;
26
+
27
+    #[Url]
28
+    public ?string $activeTab = 'summary';
29
+
30
+    public function boot(ReportService $reportService, ExportService $exportService): void
31
+    {
32
+        $this->reportService = $reportService;
33
+        $this->exportService = $exportService;
34
+    }
35
+
36
+    public function getTable(): array
37
+    {
38
+        return [
39
+            Column::make('account_code')
40
+                ->label('Account Code')
41
+                ->toggleable()
42
+                ->alignment(Alignment::Center),
43
+            Column::make('account_name')
44
+                ->label('Account')
45
+                ->alignment(Alignment::Left),
46
+            Column::make('ending_balance')
47
+                ->label('Amount')
48
+                ->alignment(Alignment::Right),
49
+        ];
50
+    }
51
+
52
+    public function filtersForm(Form $form): Form
53
+    {
54
+        return $form
55
+            ->inlineLabel()
56
+            ->columns(3)
57
+            ->schema([
58
+                DateRangeSelect::make('dateRange')
59
+                    ->label('As of')
60
+                    ->selectablePlaceholder(false)
61
+                    ->endDateField('asOfDate'),
62
+                $this->getAsOfDateFormComponent()
63
+                    ->hiddenLabel()
64
+                    ->extraFieldWrapperAttributes([]),
65
+            ]);
66
+    }
67
+
68
+    protected function buildReport(array $columns): ReportDTO
69
+    {
70
+        return $this->reportService->buildBalanceSheetReport($this->getFormattedAsOfDate(), $columns);
71
+    }
72
+
73
+    protected function getTransformer(ReportDTO $reportDTO): ExportableReport
74
+    {
75
+        return new BalanceSheetReportTransformer($reportDTO);
76
+    }
77
+
78
+    public function exportCSV(): StreamedResponse
79
+    {
80
+        return $this->exportService->exportToCsv($this->company, $this->report, endDate: $this->getFilterState('asOfDate'));
81
+    }
82
+
83
+    public function exportPDF(): StreamedResponse
84
+    {
85
+        return $this->exportService->exportToPdf($this->company, $this->report, endDate: $this->getFilterState('asOfDate'));
86
+    }
87
+}

+ 4
- 0
app/Filament/Company/Pages/Reports/IncomeStatement.php Näytä tiedosto

@@ -11,6 +11,7 @@ use App\Transformers\IncomeStatementReportTransformer;
11 11
 use Filament\Forms\Form;
12 12
 use Filament\Support\Enums\Alignment;
13 13
 use Guava\FilamentClusters\Forms\Cluster;
14
+use Livewire\Attributes\Url;
14 15
 use Symfony\Component\HttpFoundation\StreamedResponse;
15 16
 
16 17
 class IncomeStatement extends BaseReportPage
@@ -25,6 +26,9 @@ class IncomeStatement extends BaseReportPage
25 26
 
26 27
     protected ExportService $exportService;
27 28
 
29
+    #[Url]
30
+    public ?string $activeTab = 'summary';
31
+
28 32
     public function boot(ReportService $reportService, ExportService $exportService): void
29 33
     {
30 34
         $this->reportService = $reportService;

+ 4
- 1
app/Filament/Company/Resources/Banking/AccountResource.php Näytä tiedosto

@@ -143,7 +143,10 @@ class AccountResource extends Resource
143 143
                 Tables\Actions\BulkActionGroup::make([
144 144
                     Tables\Actions\DeleteBulkAction::make()
145 145
                         ->requiresConfirmation()
146
-                        ->modalDescription('Are you sure you want to delete the selected accounts? All transactions associated with the accounts will be deleted as well.'),
146
+                        ->modalDescription('Are you sure you want to delete the selected accounts? All transactions associated with the accounts will be deleted as well.')
147
+                        ->hidden(function (Table $table) {
148
+                            return $table->getAllSelectableRecordsCount() === 0;
149
+                        }),
147 150
                 ]),
148 151
             ])
149 152
             ->checkIfRecordIsSelectableUsing(static function (BankAccount $record) {

+ 2
- 1
app/Filament/Company/Resources/Banking/AccountResource/Pages/EditAccount.php Näytä tiedosto

@@ -3,6 +3,7 @@
3 3
 namespace App\Filament\Company\Resources\Banking\AccountResource\Pages;
4 4
 
5 5
 use App\Filament\Company\Resources\Banking\AccountResource;
6
+use Filament\Actions;
6 7
 use Filament\Resources\Pages\EditRecord;
7 8
 
8 9
 class EditAccount extends EditRecord
@@ -12,7 +13,7 @@ class EditAccount extends EditRecord
12 13
     protected function getHeaderActions(): array
13 14
     {
14 15
         return [
15
-            //
16
+            Actions\DeleteAction::make(),
16 17
         ];
17 18
     }
18 19
 

+ 1
- 1
app/Listeners/UpdateAccountBalances.php Näytä tiedosto

@@ -58,7 +58,7 @@ class UpdateAccountBalances
58 58
                         'type' => $transactionType,
59 59
                         'amount' => $formattedSimpleDifference,
60 60
                         'payment_channel' => 'other',
61
-                        'posted_at' => now(),
61
+                        'posted_at' => today(),
62 62
                         'description' => $description,
63 63
                         'pending' => false,
64 64
                         'reviewed' => false,

+ 1
- 1
app/Models/Accounting/Transaction.php Näytä tiedosto

@@ -48,7 +48,7 @@ class Transaction extends Model
48 48
         'amount' => TransactionAmountCast::class,
49 49
         'pending' => 'boolean',
50 50
         'reviewed' => 'boolean',
51
-        'posted_at' => 'datetime',
51
+        'posted_at' => 'date',
52 52
     ];
53 53
 
54 54
     public function account(): BelongsTo

+ 2
- 1
app/Services/AccountService.php Näytä tiedosto

@@ -138,6 +138,7 @@ class AccountService
138 138
                 'accounts.id',
139 139
                 'accounts.name',
140 140
                 'accounts.category',
141
+                'accounts.type',
141 142
                 'accounts.subtype_id',
142 143
                 'accounts.currency_code',
143 144
                 'accounts.code',
@@ -227,6 +228,6 @@ class AccountService
227 228
     {
228 229
         $earliestDate = Transaction::min('posted_at');
229 230
 
230
-        return $earliestDate ?? now()->toDateTimeString();
231
+        return $earliestDate ?? today()->toDateTimeString();
231 232
     }
232 233
 }

+ 59
- 43
app/Services/ExportService.php Näytä tiedosto

@@ -4,10 +4,13 @@ namespace App\Services;
4 4
 
5 5
 use App\Contracts\ExportableReport;
6 6
 use App\Models\Company;
7
-use App\Support\Column;
8 7
 use Barryvdh\Snappy\Facades\SnappyPdf;
9 8
 use Carbon\Exceptions\InvalidFormatException;
10 9
 use Illuminate\Support\Carbon;
10
+use League\Csv\Bom;
11
+use League\Csv\CannotInsertRecord;
12
+use League\Csv\Exception;
13
+use League\Csv\Writer;
11 14
 use Symfony\Component\HttpFoundation\StreamedResponse;
12 15
 
13 16
 class ExportService
@@ -33,7 +36,8 @@ class ExportService
33 36
         ];
34 37
 
35 38
         $callback = function () use ($startDate, $endDate, $report, $company) {
36
-            $file = fopen('php://output', 'wb');
39
+            $csv = Writer::createFromStream(fopen('php://output', 'wb'));
40
+            $csv->setOutputBOM(Bom::Utf8);
37 41
 
38 42
             if ($startDate && $endDate) {
39 43
                 $defaultStartDateFormat = Carbon::parse($startDate)->toDefaultDateFormat();
@@ -43,61 +47,34 @@ class ExportService
43 47
                 $dateLabel = 'As of ' . Carbon::parse($endDate)->toDefaultDateFormat();
44 48
             }
45 49
 
46
-            fputcsv($file, [$report->getTitle()]);
47
-            fputcsv($file, [$company->name]);
48
-            fputcsv($file, [$dateLabel]);
49
-            fputcsv($file, []);
50
+            $csv->insertOne([$report->getTitle()]);
51
+            $csv->insertOne([$company->name]);
52
+            $csv->insertOne([$dateLabel]);
53
+            $csv->insertOne([]);
50 54
 
51
-            fputcsv($file, $report->getHeaders());
55
+            $csv->insertOne($report->getHeaders());
52 56
 
53 57
             foreach ($report->getCategories() as $category) {
54
-                if (isset($category->header[0]) && is_array($category->header[0])) {
55
-                    foreach ($category->header as $headerRow) {
56
-                        fputcsv($file, $headerRow);
57
-                    }
58
-                } else {
59
-                    fputcsv($file, $category->header);
60
-                }
58
+                $this->writeDataRowsToCsv($csv, $category->header, $category->data, $report->getColumns());
61 59
 
62
-                foreach ($category->data as $accountRow) {
63
-                    $row = [];
64
-                    $columns = $report->getColumns();
65
-
66
-                    /**
67
-                     * @var Column $column
68
-                     */
69
-                    foreach ($columns as $index => $column) {
70
-                        $cell = $accountRow[$index] ?? '';
71
-
72
-                        if ($column->isDate()) {
73
-                            try {
74
-                                $row[] = Carbon::parse($cell)->toDateString();
75
-                            } catch (InvalidFormatException) {
76
-                                $row[] = $cell;
77
-                            }
78
-                        } elseif (is_array($cell)) {
79
-                            // Handle array cells by extracting 'name' or 'description'
80
-                            $row[] = $cell['name'] ?? $cell['description'] ?? '';
81
-                        } else {
82
-                            $row[] = $cell;
83
-                        }
84
-                    }
60
+                foreach ($category->types ?? [] as $type) {
61
+                    $this->writeDataRowsToCsv($csv, $type->header, $type->data, $report->getColumns());
85 62
 
86
-                    fputcsv($file, $row);
63
+                    if (filled($type->summary)) {
64
+                        $csv->insertOne($type->summary);
65
+                    }
87 66
                 }
88 67
 
89 68
                 if (filled($category->summary)) {
90
-                    fputcsv($file, $category->summary);
69
+                    $csv->insertOne($category->summary);
91 70
                 }
92 71
 
93
-                fputcsv($file, []); // Empty row for spacing
72
+                $csv->insertOne([]);
94 73
             }
95 74
 
96 75
             if (filled($report->getOverallTotals())) {
97
-                fputcsv($file, $report->getOverallTotals());
76
+                $csv->insertOne($report->getOverallTotals());
98 77
             }
99
-
100
-            fclose($file);
101 78
         };
102 79
 
103 80
         return response()->streamDownload($callback, $filename, $headers);
@@ -129,4 +106,43 @@ class ExportService
129 106
             echo $pdf->inline();
130 107
         }, $filename);
131 108
     }
109
+
110
+    /**
111
+     * @throws CannotInsertRecord
112
+     * @throws Exception
113
+     */
114
+    protected function writeDataRowsToCsv(Writer $csv, array $header, array $data, array $columns): void
115
+    {
116
+        if (isset($header[0]) && is_array($header[0])) {
117
+            foreach ($header as $headerRow) {
118
+                $csv->insertOne($headerRow);
119
+            }
120
+        } else {
121
+            $csv->insertOne($header);
122
+        }
123
+
124
+        // Output data rows
125
+        foreach ($data as $rowData) {
126
+            $row = [];
127
+
128
+            foreach ($columns as $column) {
129
+                $columnName = $column->getName();
130
+                $cell = $rowData[$columnName] ?? '';
131
+
132
+                if ($column->isDate()) {
133
+                    try {
134
+                        $row[] = Carbon::parse($cell)->toDateString();
135
+                    } catch (InvalidFormatException) {
136
+                        $row[] = $cell;
137
+                    }
138
+                } elseif (is_array($cell)) {
139
+                    $row[] = $cell['name'] ?? $cell['description'] ?? '';
140
+                } else {
141
+                    $row[] = $cell;
142
+                }
143
+            }
144
+
145
+            $csv->insertOne($row);
146
+        }
147
+    }
132 148
 }

+ 112
- 6
app/Services/ReportService.php Näytä tiedosto

@@ -6,8 +6,10 @@ use App\DTO\AccountBalanceDTO;
6 6
 use App\DTO\AccountCategoryDTO;
7 7
 use App\DTO\AccountDTO;
8 8
 use App\DTO\AccountTransactionDTO;
9
+use App\DTO\AccountTypeDTO;
9 10
 use App\DTO\ReportDTO;
10 11
 use App\Enums\Accounting\AccountCategory;
12
+use App\Enums\Accounting\AccountType;
11 13
 use App\Models\Accounting\Account;
12 14
 use App\Support\Column;
13 15
 use App\Utilities\Currency\CurrencyAccessor;
@@ -86,8 +88,8 @@ class ReportService
86 88
             $formattedCategorySummaryBalances = $this->formatBalances($categorySummaryBalances);
87 89
 
88 90
             $accountCategories[$category->getPluralLabel()] = new AccountCategoryDTO(
89
-                $categoryAccounts,
90
-                $formattedCategorySummaryBalances,
91
+                accounts: $categoryAccounts,
92
+                summary: $formattedCategorySummaryBalances,
91 93
             );
92 94
         }
93 95
 
@@ -305,8 +307,8 @@ class ReportService
305 307
             $formattedCategorySummaryBalances = $this->formatBalances($categorySummaryBalances);
306 308
 
307 309
             $accountCategories[$category->getPluralLabel()] = new AccountCategoryDTO(
308
-                $categoryAccounts,
309
-                $formattedCategorySummaryBalances,
310
+                accounts: $categoryAccounts,
311
+                summary: $formattedCategorySummaryBalances,
310 312
             );
311 313
         }
312 314
 
@@ -417,8 +419,8 @@ class ReportService
417 419
             }
418 420
 
419 421
             $accountCategories[$label] = new AccountCategoryDTO(
420
-                $categoryAccounts,
421
-                $this->formatBalances(['net_movement' => $netMovement])
422
+                accounts: $categoryAccounts,
423
+                summary: $this->formatBalances(['net_movement' => $netMovement])
422 424
             );
423 425
         }
424 426
 
@@ -429,4 +431,108 @@ class ReportService
429 431
 
430 432
         return new ReportDTO($accountCategories, $formattedReportTotalBalances, $columns);
431 433
     }
434
+
435
+    public function buildBalanceSheetReport(string $asOfDate, array $columns = []): ReportDTO
436
+    {
437
+        $asOfDateCarbon = Carbon::parse($asOfDate);
438
+        $startDateCarbon = Carbon::parse($this->accountService->getEarliestTransactionDate());
439
+
440
+        $orderedCategories = array_filter(AccountCategory::getOrderedCategories(), fn (AccountCategory $category) => $category->isReal());
441
+
442
+        $accounts = $this->accountService->getAccountBalances($startDateCarbon->toDateTimeString(), $asOfDateCarbon->toDateTimeString())
443
+            ->whereIn('category', $orderedCategories)
444
+            ->orderByRaw('LENGTH(code), code')
445
+            ->get();
446
+
447
+        $accountCategories = [];
448
+        $reportTotalBalances = [
449
+            'assets' => 0,
450
+            'liabilities' => 0,
451
+            'equity' => 0,
452
+        ];
453
+
454
+        foreach ($orderedCategories as $category) {
455
+            $categorySummaryBalances = ['ending_balance' => 0];
456
+
457
+            $categoryAccountsByType = [];
458
+            $categoryAccounts = [];
459
+            $subCategoryTotals = [];
460
+
461
+            /** @var Account $account */
462
+            foreach ($accounts as $account) {
463
+                if ($account->type->getCategory() === $category) {
464
+                    $accountBalances = $this->calculateAccountBalances($account, $category);
465
+                    $endingBalance = $accountBalances['ending_balance'] ?? $accountBalances['net_movement'];
466
+
467
+                    $categorySummaryBalances['ending_balance'] += $endingBalance;
468
+
469
+                    $formattedAccountBalances = $this->formatBalances($accountBalances);
470
+
471
+                    $accountDTO = new AccountDTO(
472
+                        $account->name,
473
+                        $account->code,
474
+                        $account->id,
475
+                        $formattedAccountBalances,
476
+                        startDate: $startDateCarbon->toDateString(),
477
+                        endDate: $asOfDateCarbon->toDateString(),
478
+                    );
479
+
480
+                    if ($category === AccountCategory::Equity && $account->type === AccountType::Equity) {
481
+                        $categoryAccounts[] = $accountDTO;
482
+                    } else {
483
+                        $accountType = $account->type->getPluralLabel();
484
+                        $categoryAccountsByType[$accountType][] = $accountDTO;
485
+                        $subCategoryTotals[$accountType] = ($subCategoryTotals[$accountType] ?? 0) + $endingBalance;
486
+                    }
487
+                }
488
+            }
489
+
490
+            if ($category === AccountCategory::Equity) {
491
+                $retainedEarningsAmount = $this->calculateRetainedEarnings($startDateCarbon->toDateTimeString(), $asOfDateCarbon->toDateTimeString())->getAmount();
492
+
493
+                $categorySummaryBalances['ending_balance'] += $retainedEarningsAmount;
494
+
495
+                $retainedEarningsDTO = new AccountDTO(
496
+                    'Retained Earnings',
497
+                    'RE',
498
+                    null,
499
+                    $this->formatBalances(['ending_balance' => $retainedEarningsAmount]),
500
+                    startDate: $startDateCarbon->toDateString(),
501
+                    endDate: $asOfDateCarbon->toDateString(),
502
+                );
503
+
504
+                $categoryAccounts[] = $retainedEarningsDTO;
505
+            }
506
+
507
+            $subCategories = [];
508
+            foreach ($categoryAccountsByType as $accountType => $accountsInType) {
509
+                $subCategorySummary = $this->formatBalances([
510
+                    'ending_balance' => $subCategoryTotals[$accountType] ?? 0,
511
+                ]);
512
+
513
+                $subCategories[$accountType] = new AccountTypeDTO(
514
+                    accounts: $accountsInType,
515
+                    summary: $subCategorySummary
516
+                );
517
+            }
518
+
519
+            $reportTotalBalances[match ($category) {
520
+                AccountCategory::Asset => 'assets',
521
+                AccountCategory::Liability => 'liabilities',
522
+                AccountCategory::Equity => 'equity',
523
+            }] += $categorySummaryBalances['ending_balance'];
524
+
525
+            $accountCategories[$category->getPluralLabel()] = new AccountCategoryDTO(
526
+                accounts: $categoryAccounts,
527
+                types: $subCategories,
528
+                summary: $this->formatBalances($categorySummaryBalances),
529
+            );
530
+        }
531
+
532
+        $netAssets = $reportTotalBalances['assets'] - $reportTotalBalances['liabilities'];
533
+
534
+        $formattedReportTotalBalances = $this->formatBalances(['ending_balance' => $netAssets]);
535
+
536
+        return new ReportDTO($accountCategories, $formattedReportTotalBalances, $columns);
537
+    }
432 538
 }

+ 5
- 16
app/Transformers/AccountBalanceReportTransformer.php Näytä tiedosto

@@ -4,7 +4,6 @@ namespace App\Transformers;
4 4
 
5 5
 use App\DTO\AccountDTO;
6 6
 use App\DTO\ReportCategoryDTO;
7
-use App\Support\Column;
8 7
 
9 8
 class AccountBalanceReportTransformer extends BaseReportTransformer
10 9
 {
@@ -13,11 +12,6 @@ class AccountBalanceReportTransformer extends BaseReportTransformer
13 12
         return 'Account Balances';
14 13
     }
15 14
 
16
-    public function getHeaders(): array
17
-    {
18
-        return array_map(fn (Column $column) => $column->getLabel(), $this->getColumns());
19
-    }
20
-
21 15
     /**
22 16
      * @return ReportCategoryDTO[]
23 17
      */
@@ -26,22 +20,17 @@ class AccountBalanceReportTransformer extends BaseReportTransformer
26 20
         $categories = [];
27 21
 
28 22
         foreach ($this->report->categories as $accountCategoryName => $accountCategory) {
29
-            // Initialize header with empty strings
30 23
             $header = [];
31 24
 
32
-            foreach ($this->getColumns() as $index => $column) {
33
-                if ($column->getName() === 'account_name') {
34
-                    $header[$index] = $accountCategoryName;
35
-                } else {
36
-                    $header[$index] = '';
37
-                }
25
+            foreach ($this->getColumns() as $column) {
26
+                $header[$column->getName()] = $column->getName() === 'account_name' ? $accountCategoryName : '';
38 27
             }
39 28
 
40 29
             $data = array_map(function (AccountDTO $account) {
41 30
                 $row = [];
42 31
 
43 32
                 foreach ($this->getColumns() as $column) {
44
-                    $row[] = match ($column->getName()) {
33
+                    $row[$column->getName()] = match ($column->getName()) {
45 34
                         'account_code' => $account->accountCode,
46 35
                         'account_name' => [
47 36
                             'name' => $account->accountName,
@@ -64,7 +53,7 @@ class AccountBalanceReportTransformer extends BaseReportTransformer
64 53
             $summary = [];
65 54
 
66 55
             foreach ($this->getColumns() as $column) {
67
-                $summary[] = match ($column->getName()) {
56
+                $summary[$column->getName()] = match ($column->getName()) {
68 57
                     'account_name' => 'Total ' . $accountCategoryName,
69 58
                     'starting_balance' => $accountCategory->summary->startingBalance ?? '',
70 59
                     'debit_balance' => $accountCategory->summary->debitBalance,
@@ -90,7 +79,7 @@ class AccountBalanceReportTransformer extends BaseReportTransformer
90 79
         $totals = [];
91 80
 
92 81
         foreach ($this->getColumns() as $column) {
93
-            $totals[] = match ($column->getName()) {
82
+            $totals[$column->getName()] = match ($column->getName()) {
94 83
                 'account_name' => 'Total for all accounts',
95 84
                 'debit_balance' => $this->report->overallTotal->debitBalance,
96 85
                 'credit_balance' => $this->report->overallTotal->creditBalance,

+ 5
- 16
app/Transformers/AccountTransactionReportTransformer.php Näytä tiedosto

@@ -4,7 +4,6 @@ namespace App\Transformers;
4 4
 
5 5
 use App\DTO\AccountTransactionDTO;
6 6
 use App\DTO\ReportCategoryDTO;
7
-use App\Support\Column;
8 7
 
9 8
 class AccountTransactionReportTransformer extends BaseReportTransformer
10 9
 {
@@ -18,11 +17,6 @@ class AccountTransactionReportTransformer extends BaseReportTransformer
18 17
         return 'Account Transactions';
19 18
     }
20 19
 
21
-    public function getHeaders(): array
22
-    {
23
-        return array_map(fn (Column $column) => $column->getLabel(), $this->getColumns());
24
-    }
25
-
26 20
     /**
27 21
      * @return ReportCategoryDTO[]
28 22
      */
@@ -31,17 +25,12 @@ class AccountTransactionReportTransformer extends BaseReportTransformer
31 25
         $categories = [];
32 26
 
33 27
         foreach ($this->report->categories as $categoryData) {
34
-            // Initialize header with account and category information
35
-
36
-            $header = [
37
-                array_fill(0, count($this->getColumns()), ''),
38
-                array_fill(0, count($this->getColumns()), ''),
39
-            ];
28
+            $header = [];
40 29
 
41
-            foreach ($this->getColumns() as $index => $column) {
30
+            foreach ($this->getColumns() as $column) {
42 31
                 if ($column->getName() === 'date') {
43
-                    $header[0][$index] = $categoryData['category'];
44
-                    $header[1][$index] = $categoryData['under'];
32
+                    $header[0][$column->getName()] = $categoryData['category'];
33
+                    $header[1][$column->getName()] = $categoryData['under'];
45 34
                 }
46 35
             }
47 36
 
@@ -50,7 +39,7 @@ class AccountTransactionReportTransformer extends BaseReportTransformer
50 39
                 $row = [];
51 40
 
52 41
                 foreach ($this->getColumns() as $column) {
53
-                    $row[] = match ($column->getName()) {
42
+                    $row[$column->getName()] = match ($column->getName()) {
54 43
                         'date' => $transaction->date,
55 44
                         'description' => [
56 45
                             'id' => $transaction->id,

+ 261
- 0
app/Transformers/BalanceSheetReportTransformer.php Näytä tiedosto

@@ -0,0 +1,261 @@
1
+<?php
2
+
3
+namespace App\Transformers;
4
+
5
+use App\DTO\AccountDTO;
6
+use App\DTO\ReportCategoryDTO;
7
+use App\DTO\ReportDTO;
8
+use App\DTO\ReportTypeDTO;
9
+use App\Utilities\Currency\CurrencyAccessor;
10
+
11
+class BalanceSheetReportTransformer extends SummaryReportTransformer
12
+{
13
+    protected string $totalAssets;
14
+
15
+    protected string $totalLiabilities;
16
+
17
+    protected string $totalEquity;
18
+
19
+    public function __construct(ReportDTO $report)
20
+    {
21
+        parent::__construct($report);
22
+
23
+        $this->calculateTotals();
24
+    }
25
+
26
+    public function getTitle(): string
27
+    {
28
+        return 'Balance Sheet';
29
+    }
30
+
31
+    public function calculateTotals(): void
32
+    {
33
+        foreach ($this->report->categories as $accountCategoryName => $accountCategory) {
34
+            match ($accountCategoryName) {
35
+                'Assets' => $this->totalAssets = $accountCategory->summary->endingBalance ?? '',
36
+                'Liabilities' => $this->totalLiabilities = $accountCategory->summary->endingBalance ?? '',
37
+                'Equity' => $this->totalEquity = $accountCategory->summary->endingBalance ?? '',
38
+            };
39
+        }
40
+    }
41
+
42
+    public function getCategories(): array
43
+    {
44
+        $categories = [];
45
+
46
+        foreach ($this->report->categories as $accountCategoryName => $accountCategory) {
47
+            // Header for the main category
48
+            $header = [];
49
+
50
+            foreach ($this->getColumns() as $column) {
51
+                $header[$column->getName()] = $column->getName() === 'account_name' ? $accountCategoryName : '';
52
+            }
53
+
54
+            // Category-level summary
55
+            $categorySummary = [];
56
+            foreach ($this->getColumns() as $column) {
57
+                $categorySummary[$column->getName()] = match ($column->getName()) {
58
+                    'account_name' => 'Total ' . $accountCategoryName,
59
+                    'ending_balance' => $accountCategory->summary->endingBalance ?? '',
60
+                    default => '',
61
+                };
62
+            }
63
+
64
+            // Accounts directly under the main category
65
+            $data = array_map(function (AccountDTO $account) {
66
+                $row = [];
67
+
68
+                foreach ($this->getColumns() as $column) {
69
+                    $row[$column->getName()] = match ($column->getName()) {
70
+                        'account_code' => $account->accountCode,
71
+                        'account_name' => [
72
+                            'name' => $account->accountName,
73
+                            'id' => $account->accountId ?? null,
74
+                            'start_date' => $account->startDate,
75
+                            'end_date' => $account->endDate,
76
+                        ],
77
+                        'ending_balance' => $account->balance->endingBalance ?? '',
78
+                        default => '',
79
+                    };
80
+                }
81
+
82
+                return $row;
83
+            }, $accountCategory->accounts ?? []);
84
+
85
+            // Subcategories (types) under the main category
86
+            $types = [];
87
+            foreach ($accountCategory->types as $typeName => $type) {
88
+                // Header for subcategory (type)
89
+                $typeHeader = [];
90
+                foreach ($this->getColumns() as $column) {
91
+                    $typeHeader[$column->getName()] = $column->getName() === 'account_name' ? $typeName : '';
92
+                }
93
+
94
+                // Account data for the subcategory
95
+                $typeData = array_map(function (AccountDTO $account) {
96
+                    $row = [];
97
+
98
+                    foreach ($this->getColumns() as $column) {
99
+                        $row[$column->getName()] = match ($column->getName()) {
100
+                            'account_code' => $account->accountCode,
101
+                            'account_name' => [
102
+                                'name' => $account->accountName,
103
+                                'id' => $account->accountId ?? null,
104
+                                'start_date' => $account->startDate,
105
+                                'end_date' => $account->endDate,
106
+                            ],
107
+                            'ending_balance' => $account->balance->endingBalance ?? '',
108
+                            default => '',
109
+                        };
110
+                    }
111
+
112
+                    return $row;
113
+                }, $type->accounts);
114
+
115
+                // Subcategory (type) summary
116
+                $typeSummary = [];
117
+                foreach ($this->getColumns() as $column) {
118
+                    $typeSummary[$column->getName()] = match ($column->getName()) {
119
+                        'account_name' => 'Total ' . $typeName,
120
+                        'ending_balance' => $type->summary->endingBalance ?? '',
121
+                        default => '',
122
+                    };
123
+                }
124
+
125
+                // Add subcategory (type) to the list
126
+                $types[$typeName] = new ReportTypeDTO(
127
+                    header: $typeHeader,
128
+                    data: $typeData,
129
+                    summary: $typeSummary,
130
+                );
131
+            }
132
+
133
+            // Add the category to the final array with its direct accounts and subcategories (types)
134
+            $categories[$accountCategoryName] = new ReportCategoryDTO(
135
+                header: $header,
136
+                data: $data, // Direct accounts under the category
137
+                summary: $categorySummary,
138
+                types: $types, // Subcategories (types) under the category
139
+            );
140
+        }
141
+
142
+        return $categories;
143
+    }
144
+
145
+    public function getSummaryCategories(): array
146
+    {
147
+        $summaryCategories = [];
148
+
149
+        $columns = $this->getSummaryColumns();
150
+
151
+        foreach ($this->report->categories as $accountCategoryName => $accountCategory) {
152
+            $categoryHeader = [];
153
+
154
+            foreach ($columns as $column) {
155
+                $categoryHeader[$column->getName()] = $column->getName() === 'account_name' ? $accountCategoryName : '';
156
+            }
157
+
158
+            $categorySummary = [];
159
+            foreach ($columns as $column) {
160
+                $categorySummary[$column->getName()] = match ($column->getName()) {
161
+                    'account_name' => 'Total ' . $accountCategoryName,
162
+                    'ending_balance' => $accountCategory->summary->endingBalance ?? '',
163
+                    default => '',
164
+                };
165
+            }
166
+
167
+            $types = [];
168
+            $totalTypeSummaries = 0;
169
+
170
+            // Iterate through each account type and calculate type summaries
171
+            foreach ($accountCategory->types as $typeName => $type) {
172
+                $typeSummary = [];
173
+                $typeEndingBalance = 0;
174
+
175
+                foreach ($columns as $column) {
176
+                    $typeSummary[$column->getName()] = match ($column->getName()) {
177
+                        'account_name' => 'Total ' . $typeName,
178
+                        'ending_balance' => $type->summary->endingBalance ?? '',
179
+                        default => '',
180
+                    };
181
+
182
+                    if ($column->getName() === 'ending_balance') {
183
+                        $typeEndingBalance = $type->summary->endingBalance ?? 0;
184
+                    }
185
+                }
186
+
187
+                $typeEndingBalance = money($typeEndingBalance, CurrencyAccessor::getDefaultCurrency())->getAmount();
188
+
189
+                $totalTypeSummaries += $typeEndingBalance;
190
+
191
+                $types[$typeName] = new ReportTypeDTO(
192
+                    header: [],
193
+                    data: [],
194
+                    summary: $typeSummary,
195
+                );
196
+            }
197
+
198
+            // Only for the "Equity" category, calculate and add "Total Other Equity"
199
+            if ($accountCategoryName === 'Equity') {
200
+                $totalEquitySummary = $accountCategory->summary->endingBalance ?? 0;
201
+                $totalEquitySummary = money($totalEquitySummary, CurrencyAccessor::getDefaultCurrency())->getAmount();
202
+                $totalOtherEquity = $totalEquitySummary - $totalTypeSummaries;
203
+                $totalOtherEquity = money($totalOtherEquity, CurrencyAccessor::getDefaultCurrency(), true)->format();
204
+
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
+                }
214
+
215
+                $types['Total Other Equity'] = new ReportTypeDTO(
216
+                    header: [],
217
+                    data: [],
218
+                    summary: $otherEquitySummary,
219
+                );
220
+            }
221
+
222
+            // Add the category with its types and summary to the final array
223
+            $summaryCategories[$accountCategoryName] = new ReportCategoryDTO(
224
+                header: $categoryHeader,
225
+                data: [],
226
+                summary: $categorySummary,
227
+                types: $types,
228
+            );
229
+        }
230
+
231
+        return $summaryCategories;
232
+    }
233
+
234
+    public function getOverallTotals(): array
235
+    {
236
+        return [];
237
+    }
238
+
239
+    public function getSummaryOverallTotals(): array
240
+    {
241
+        return [];
242
+    }
243
+
244
+    public function getSummary(): array
245
+    {
246
+        return [
247
+            [
248
+                'label' => 'Total Assets',
249
+                'value' => $this->totalAssets,
250
+            ],
251
+            [
252
+                'label' => 'Total Liabilities',
253
+                'value' => $this->totalLiabilities,
254
+            ],
255
+            [
256
+                'label' => 'Net Assets',
257
+                'value' => $this->report->overallTotal->endingBalance ?? '',
258
+            ],
259
+        ];
260
+    }
261
+}

+ 42
- 5
app/Transformers/BaseReportTransformer.php Näytä tiedosto

@@ -4,6 +4,7 @@ namespace App\Transformers;
4 4
 
5 5
 use App\Contracts\ExportableReport;
6 6
 use App\DTO\ReportDTO;
7
+use App\Support\Column;
7 8
 use Filament\Support\Enums\Alignment;
8 9
 
9 10
 abstract class BaseReportTransformer implements ExportableReport
@@ -15,9 +16,27 @@ abstract class BaseReportTransformer implements ExportableReport
15 16
         $this->report = $report;
16 17
     }
17 18
 
19
+    /**
20
+     * @return Column[]
21
+     */
18 22
     public function getColumns(): array
19 23
     {
20
-        return $this->report->fields;
24
+        return once(function (): array {
25
+            return $this->report->fields;
26
+        });
27
+    }
28
+
29
+    public function getHeaders(): array
30
+    {
31
+        return once(function (): array {
32
+            $headers = [];
33
+
34
+            foreach ($this->getColumns() as $column) {
35
+                $headers[$column->getName()] = $column->getLabel();
36
+            }
37
+
38
+            return $headers;
39
+        });
21 40
     }
22 41
 
23 42
     public function getPdfView(): string
@@ -25,18 +44,36 @@ abstract class BaseReportTransformer implements ExportableReport
25 44
         return 'components.company.reports.report-pdf';
26 45
     }
27 46
 
28
-    public function getAlignmentClass(int $index): string
47
+    public function getAlignment(int $index): string
29 48
     {
30 49
         $column = $this->getColumns()[$index];
31 50
 
32 51
         if ($column->getAlignment() === Alignment::Right) {
33
-            return 'text-right';
52
+            return 'right';
34 53
         }
35 54
 
36 55
         if ($column->getAlignment() === Alignment::Center) {
37
-            return 'text-center';
56
+            return 'center';
38 57
         }
39 58
 
40
-        return 'text-left';
59
+        return 'left';
60
+    }
61
+
62
+    public function getAlignmentClass(string $columnName): string
63
+    {
64
+        return once(function () use ($columnName): string {
65
+            /** @var Column|null $column */
66
+            $column = collect($this->getColumns())->first(fn (Column $column) => $column->getName() === $columnName);
67
+
68
+            if ($column?->getAlignment() === Alignment::Right) {
69
+                return 'text-right';
70
+            }
71
+
72
+            if ($column?->getAlignment() === Alignment::Center) {
73
+                return 'text-center';
74
+            }
75
+
76
+            return 'text-left';
77
+        });
41 78
     }
42 79
 }

+ 92
- 21
app/Transformers/IncomeStatementReportTransformer.php Näytä tiedosto

@@ -4,24 +4,27 @@ namespace App\Transformers;
4 4
 
5 5
 use App\DTO\AccountDTO;
6 6
 use App\DTO\ReportCategoryDTO;
7
-use App\Support\Column;
7
+use App\DTO\ReportDTO;
8
+use App\Utilities\Currency\CurrencyAccessor;
8 9
 
9
-class IncomeStatementReportTransformer extends BaseReportTransformer
10
+class IncomeStatementReportTransformer extends SummaryReportTransformer
10 11
 {
11
-    protected string $totalRevenue = '$0.00';
12
+    protected string $totalRevenue;
12 13
 
13
-    protected string $totalCogs = '$0.00';
14
+    protected string $totalCogs;
14 15
 
15
-    protected string $totalExpenses = '$0.00';
16
+    protected string $totalExpenses;
16 17
 
17
-    public function getTitle(): string
18
+    public function __construct(ReportDTO $report)
18 19
     {
19
-        return 'Income Statement';
20
+        parent::__construct($report);
21
+
22
+        $this->calculateTotals();
20 23
     }
21 24
 
22
-    public function getHeaders(): array
25
+    public function getTitle(): string
23 26
     {
24
-        return array_map(fn (Column $column) => $column->getLabel(), $this->getColumns());
27
+        return 'Income Statement';
25 28
     }
26 29
 
27 30
     public function calculateTotals(): void
@@ -40,22 +43,17 @@ class IncomeStatementReportTransformer extends BaseReportTransformer
40 43
         $categories = [];
41 44
 
42 45
         foreach ($this->report->categories as $accountCategoryName => $accountCategory) {
43
-            // Initialize header with empty strings
44 46
             $header = [];
45 47
 
46
-            foreach ($this->getColumns() as $index => $column) {
47
-                if ($column->getName() === 'account_name') {
48
-                    $header[$index] = $accountCategoryName;
49
-                } else {
50
-                    $header[$index] = '';
51
-                }
48
+            foreach ($this->getColumns() as $column) {
49
+                $header[$column->getName()] = $column->getName() === 'account_name' ? $accountCategoryName : '';
52 50
             }
53 51
 
54 52
             $data = array_map(function (AccountDTO $account) {
55 53
                 $row = [];
56 54
 
57 55
                 foreach ($this->getColumns() as $column) {
58
-                    $row[] = match ($column->getName()) {
56
+                    $row[$column->getName()] = match ($column->getName()) {
59 57
                         'account_code' => $account->accountCode,
60 58
                         'account_name' => [
61 59
                             'name' => $account->accountName,
@@ -74,7 +72,7 @@ class IncomeStatementReportTransformer extends BaseReportTransformer
74 72
             $summary = [];
75 73
 
76 74
             foreach ($this->getColumns() as $column) {
77
-                $summary[] = match ($column->getName()) {
75
+                $summary[$column->getName()] = match ($column->getName()) {
78 76
                     'account_name' => 'Total ' . $accountCategoryName,
79 77
                     'net_movement' => $accountCategory->summary->netMovement ?? '',
80 78
                     default => '',
@@ -91,12 +89,71 @@ class IncomeStatementReportTransformer extends BaseReportTransformer
91 89
         return $categories;
92 90
     }
93 91
 
92
+    public function getSummaryCategories(): array
93
+    {
94
+        $summaryCategories = [];
95
+
96
+        $columns = $this->getSummaryColumns();
97
+
98
+        foreach ($this->report->categories as $accountCategoryName => $accountCategory) {
99
+            // Header for the main category
100
+            $categoryHeader = [];
101
+
102
+            foreach ($columns as $column) {
103
+                $categoryHeader[$column->getName()] = $column->getName() === 'account_name' ? $accountCategoryName : '';
104
+            }
105
+
106
+            // Category-level summary
107
+            $categorySummary = [];
108
+            foreach ($columns as $column) {
109
+                $categorySummary[$column->getName()] = match ($column->getName()) {
110
+                    'account_name' => $accountCategoryName,
111
+                    'net_movement' => $accountCategory->summary->netMovement ?? '',
112
+                    default => '',
113
+                };
114
+            }
115
+
116
+            // Add the category summary to the final array
117
+            $summaryCategories[$accountCategoryName] = new ReportCategoryDTO(
118
+                header: $categoryHeader,
119
+                data: [], // No direct accounts are needed here, only summaries
120
+                summary: $categorySummary,
121
+                types: [] // No types for the income statement
122
+            );
123
+        }
124
+
125
+        return $summaryCategories;
126
+    }
127
+
128
+    public function getGrossProfit(): array
129
+    {
130
+        $grossProfit = [];
131
+
132
+        $columns = $this->getSummaryColumns();
133
+
134
+        $revenue = money($this->totalRevenue, CurrencyAccessor::getDefaultCurrency())->getAmount();
135
+        $cogs = money($this->totalCogs, CurrencyAccessor::getDefaultCurrency())->getAmount();
136
+
137
+        $grossProfitAmount = $revenue - $cogs;
138
+        $grossProfitFormatted = money($grossProfitAmount, CurrencyAccessor::getDefaultCurrency(), true)->format();
139
+
140
+        foreach ($columns as $column) {
141
+            $grossProfit[$column->getName()] = match ($column->getName()) {
142
+                'account_name' => 'Gross Profit',
143
+                'net_movement' => $grossProfitFormatted,
144
+                default => '',
145
+            };
146
+        }
147
+
148
+        return $grossProfit;
149
+    }
150
+
94 151
     public function getOverallTotals(): array
95 152
     {
96 153
         $totals = [];
97 154
 
98 155
         foreach ($this->getColumns() as $column) {
99
-            $totals[] = match ($column->getName()) {
156
+            $totals[$column->getName()] = match ($column->getName()) {
100 157
                 'account_name' => 'Net Earnings',
101 158
                 'net_movement' => $this->report->overallTotal->netMovement ?? '',
102 159
                 default => '',
@@ -106,10 +163,24 @@ class IncomeStatementReportTransformer extends BaseReportTransformer
106 163
         return $totals;
107 164
     }
108 165
 
109
-    public function getSummary(): array
166
+    public function getSummaryOverallTotals(): array
110 167
     {
111
-        $this->calculateTotals();
168
+        $totals = [];
169
+        $columns = $this->getSummaryColumns();
170
+
171
+        foreach ($columns as $column) {
172
+            $totals[$column->getName()] = match ($column->getName()) {
173
+                'account_name' => 'Net Earnings',
174
+                'net_movement' => $this->report->overallTotal->netMovement ?? '',
175
+                default => '',
176
+            };
177
+        }
178
+
179
+        return $totals;
180
+    }
112 181
 
182
+    public function getSummary(): array
183
+    {
113 184
         return [
114 185
             [
115 186
                 'label' => 'Revenue',

+ 34
- 0
app/Transformers/SummaryReportTransformer.php Näytä tiedosto

@@ -0,0 +1,34 @@
1
+<?php
2
+
3
+namespace App\Transformers;
4
+
5
+use App\Contracts\HasSummaryReport;
6
+use App\Support\Column;
7
+
8
+abstract class SummaryReportTransformer extends BaseReportTransformer implements HasSummaryReport
9
+{
10
+    /**
11
+     * @return Column[]
12
+     */
13
+    public function getSummaryColumns(): array
14
+    {
15
+        return once(function (): array {
16
+            return collect($this->getColumns())
17
+                ->reject(fn (Column $column) => $column->getName() === 'account_code')
18
+                ->toArray();
19
+        });
20
+    }
21
+
22
+    public function getSummaryHeaders(): array
23
+    {
24
+        return once(function (): array {
25
+            $headers = [];
26
+
27
+            foreach ($this->getSummaryColumns() as $column) {
28
+                $headers[$column->getName()] = $column->getLabel();
29
+            }
30
+
31
+            return $headers;
32
+        });
33
+    }
34
+}

+ 5
- 15
app/Transformers/TrialBalanceReportTransformer.php Näytä tiedosto

@@ -4,7 +4,6 @@ namespace App\Transformers;
4 4
 
5 5
 use App\DTO\AccountDTO;
6 6
 use App\DTO\ReportCategoryDTO;
7
-use App\Support\Column;
8 7
 
9 8
 class TrialBalanceReportTransformer extends BaseReportTransformer
10 9
 {
@@ -16,11 +15,6 @@ class TrialBalanceReportTransformer extends BaseReportTransformer
16 15
         };
17 16
     }
18 17
 
19
-    public function getHeaders(): array
20
-    {
21
-        return array_map(fn (Column $column) => $column->getLabel(), $this->getColumns());
22
-    }
23
-
24 18
     /**
25 19
      * @return ReportCategoryDTO[]
26 20
      */
@@ -32,19 +26,15 @@ class TrialBalanceReportTransformer extends BaseReportTransformer
32 26
             // Initialize header with empty strings
33 27
             $header = [];
34 28
 
35
-            foreach ($this->getColumns() as $index => $column) {
36
-                if ($column->getName() === 'account_name') {
37
-                    $header[$index] = $accountCategoryName;
38
-                } else {
39
-                    $header[$index] = '';
40
-                }
29
+            foreach ($this->getColumns() as $column) {
30
+                $header[$column->getName()] = $column->getName() === 'account_name' ? $accountCategoryName : '';
41 31
             }
42 32
 
43 33
             $data = array_map(function (AccountDTO $account) {
44 34
                 $row = [];
45 35
 
46 36
                 foreach ($this->getColumns() as $column) {
47
-                    $row[] = match ($column->getName()) {
37
+                    $row[$column->getName()] = match ($column->getName()) {
48 38
                         'account_code' => $account->accountCode,
49 39
                         'account_name' => [
50 40
                             'name' => $account->accountName,
@@ -64,7 +54,7 @@ class TrialBalanceReportTransformer extends BaseReportTransformer
64 54
             $summary = [];
65 55
 
66 56
             foreach ($this->getColumns() as $column) {
67
-                $summary[] = match ($column->getName()) {
57
+                $summary[$column->getName()] = match ($column->getName()) {
68 58
                     'account_name' => 'Total ' . $accountCategoryName,
69 59
                     'debit_balance' => $accountCategory->summary->debitBalance,
70 60
                     'credit_balance' => $accountCategory->summary->creditBalance,
@@ -87,7 +77,7 @@ class TrialBalanceReportTransformer extends BaseReportTransformer
87 77
         $totals = [];
88 78
 
89 79
         foreach ($this->getColumns() as $column) {
90
-            $totals[] = match ($column->getName()) {
80
+            $totals[$column->getName()] = match ($column->getName()) {
91 81
                 'account_name' => 'Total for all accounts',
92 82
                 'debit_balance' => $this->report->overallTotal->debitBalance,
93 83
                 'credit_balance' => $this->report->overallTotal->creditBalance,

+ 211
- 208
composer.lock
File diff suppressed because it is too large
Näytä tiedosto


+ 15
- 13
config/chart-of-accounts.php Näytä tiedosto

@@ -67,11 +67,21 @@ return [
67 67
                 'description' => 'Accounts that accumulate depreciation of tangible assets and amortization of intangible assets, reflecting the reduction in value over time.',
68 68
                 'multi_currency' => false,
69 69
                 'base_code' => '1900',
70
+                'accounts' => [
71
+                    'Accumulated Depreciation' => [
72
+                        'description' => 'Used to account for the depreciation of fixed assets over time, offsetting assets like equipment or property.',
73
+                    ],
74
+                ],
70 75
             ],
71 76
             'Allowances for Receivables' => [
72 77
                 'description' => 'Accounts representing estimated uncollected receivables, used to adjust the value of gross receivables to a realistic collectible amount.',
73 78
                 'multi_currency' => false,
74 79
                 'base_code' => '1940',
80
+                'accounts' => [
81
+                    'Allowance for Doubtful Accounts' => [
82
+                        'description' => 'Used to account for potential bad debts that may not be collectable, offsetting receivables.',
83
+                    ],
84
+                ],
75 85
             ],
76 86
             'Valuation Adjustments' => [
77 87
                 'description' => 'Accounts used to record adjustments in asset values due to impairments, market changes, or other factors affecting their recoverable amount.',
@@ -154,19 +164,6 @@ return [
154 164
                     'Owner\'s Investment' => [
155 165
                         'description' => 'The amount of money invested by the owner(s) or shareholders to start or expand the business.',
156 166
                     ],
157
-                    'Owner\'s Drawings' => [
158
-                        'description' => 'The amount of money withdrawn by the owner(s) or shareholders from the business for personal use.',
159
-                    ],
160
-                ],
161
-            ],
162
-            'Retained Earnings: Profit' => [
163
-                'description' => 'Cumulative profits retained in the business and not distributed as dividends. Indicates the company\'s financial health and profit-generating ability.',
164
-                'multi_currency' => false,
165
-                'base_code' => '3100',
166
-                'accounts' => [
167
-                    'Owner\'s Equity' => [
168
-                        'description' => 'Owner\'s equity is what remains after you subtract business liabilities from business assets. In other words, it\'s what\'s left over for you if you sell all your assets and pay all your debts.',
169
-                    ],
170 167
                 ],
171 168
             ],
172 169
         ],
@@ -175,6 +172,11 @@ return [
175 172
                 '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.',
176 173
                 'multi_currency' => false,
177 174
                 'base_code' => '3900',
175
+                'accounts' => [
176
+                    'Owner\'s Drawings' => [
177
+                        'description' => 'The amount of money withdrawn by the owner(s) or shareholders from the business for personal use, reducing equity.',
178
+                    ],
179
+                ],
178 180
             ],
179 181
         ],
180 182
         'operating_revenue' => [

+ 3
- 4
database/factories/Accounting/TransactionFactory.php Näytä tiedosto

@@ -57,16 +57,15 @@ class TransactionFactory extends Factory
57 57
 
58 58
             $account = Account::where('category', $this->faker->randomElement($associatedAccountTypes))
59 59
                 ->where('company_id', $company->id)
60
-                ->where('id', '<>', $accountIdForBankAccount)
60
+                ->whereKeyNot($accountIdForBankAccount)
61 61
                 ->inRandomOrder()
62 62
                 ->first();
63 63
 
64
-            // If no matching account is found, use a fallback
65 64
             if (! $account) {
66 65
                 $account = Account::where('company_id', $company->id)
67
-                    ->where('id', '<>', $accountIdForBankAccount)
66
+                    ->whereKeyNot($accountIdForBankAccount)
68 67
                     ->inRandomOrder()
69
-                    ->firstOrFail(); // Ensure there is at least some account
68
+                    ->firstOrFail();
70 69
             }
71 70
 
72 71
             return [

+ 5
- 0
database/factories/Setting/CompanyDefaultFactory.php Näytä tiedosto

@@ -72,6 +72,7 @@ class CompanyDefaultFactory extends Factory
72 72
     {
73 73
         return Currency::factory()->forCurrency($currencyCode)->createQuietly([
74 74
             'company_id' => $company->id,
75
+            'enabled' => true,
75 76
             'created_by' => $user->id,
76 77
             'updated_by' => $user->id,
77 78
         ]);
@@ -81,6 +82,7 @@ class CompanyDefaultFactory extends Factory
81 82
     {
82 83
         return Tax::factory()->salesTax()->createQuietly([
83 84
             'company_id' => $company->id,
85
+            'enabled' => true,
84 86
             'created_by' => $user->id,
85 87
             'updated_by' => $user->id,
86 88
         ]);
@@ -90,6 +92,7 @@ class CompanyDefaultFactory extends Factory
90 92
     {
91 93
         return Tax::factory()->purchaseTax()->createQuietly([
92 94
             'company_id' => $company->id,
95
+            'enabled' => true,
93 96
             'created_by' => $user->id,
94 97
             'updated_by' => $user->id,
95 98
         ]);
@@ -99,6 +102,7 @@ class CompanyDefaultFactory extends Factory
99 102
     {
100 103
         return Discount::factory()->salesDiscount()->createQuietly([
101 104
             'company_id' => $company->id,
105
+            'enabled' => true,
102 106
             'created_by' => $user->id,
103 107
             'updated_by' => $user->id,
104 108
         ]);
@@ -108,6 +112,7 @@ class CompanyDefaultFactory extends Factory
108 112
     {
109 113
         return Discount::factory()->purchaseDiscount()->createQuietly([
110 114
             'company_id' => $company->id,
115
+            'enabled' => true,
111 116
             'created_by' => $user->id,
112 117
             'updated_by' => $user->id,
113 118
         ]);

+ 2
- 2
database/factories/Setting/CurrencyFactory.php Näytä tiedosto

@@ -33,7 +33,7 @@ class CurrencyFactory extends Factory
33 33
             'symbol_first' => $defaultCurrency->isSymbolFirst(),
34 34
             'decimal_mark' => $defaultCurrency->getDecimalMark(),
35 35
             'thousands_separator' => $defaultCurrency->getThousandsSeparator(),
36
-            'enabled' => true,
36
+            'enabled' => false,
37 37
         ];
38 38
     }
39 39
 
@@ -53,7 +53,7 @@ class CurrencyFactory extends Factory
53 53
             'symbol_first' => $currency->isSymbolFirst(),
54 54
             'decimal_mark' => $currency->getDecimalMark(),
55 55
             'thousands_separator' => $currency->getThousandsSeparator(),
56
-            'enabled' => true,
56
+            'enabled' => false,
57 57
         ]);
58 58
     }
59 59
 }

+ 28
- 0
database/migrations/2024_10_13_163049_update_posted_at_column_in_transactions_table.php Näytä tiedosto

@@ -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('transactions', function (Blueprint $table) {
15
+            $table->date('posted_at')->change();
16
+        });
17
+    }
18
+
19
+    /**
20
+     * Reverse the migrations.
21
+     */
22
+    public function down(): void
23
+    {
24
+        Schema::table('transactions', function (Blueprint $table) {
25
+            $table->dateTime('posted_at')->change();
26
+        });
27
+    }
28
+};

+ 27
- 27
package-lock.json Näytä tiedosto

@@ -955,9 +955,9 @@
955 955
             }
956 956
         },
957 957
         "node_modules/browserslist": {
958
-            "version": "4.24.0",
959
-            "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.0.tgz",
960
-            "integrity": "sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A==",
958
+            "version": "4.24.2",
959
+            "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz",
960
+            "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==",
961 961
             "dev": true,
962 962
             "funding": [
963 963
                 {
@@ -975,10 +975,10 @@
975 975
             ],
976 976
             "license": "MIT",
977 977
             "dependencies": {
978
-                "caniuse-lite": "^1.0.30001663",
979
-                "electron-to-chromium": "^1.5.28",
978
+                "caniuse-lite": "^1.0.30001669",
979
+                "electron-to-chromium": "^1.5.41",
980 980
                 "node-releases": "^2.0.18",
981
-                "update-browserslist-db": "^1.1.0"
981
+                "update-browserslist-db": "^1.1.1"
982 982
             },
983 983
             "bin": {
984 984
                 "browserslist": "cli.js"
@@ -998,9 +998,9 @@
998 998
             }
999 999
         },
1000 1000
         "node_modules/caniuse-lite": {
1001
-            "version": "1.0.30001667",
1002
-            "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001667.tgz",
1003
-            "integrity": "sha512-7LTwJjcRkzKFmtqGsibMeuXmvFDfZq/nzIjnmgCGzKKRVzjD72selLDK1oPF/Oxzmt4fNcPvTDvGqSDG4tCALw==",
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==",
1004 1004
             "dev": true,
1005 1005
             "funding": [
1006 1006
                 {
@@ -1159,9 +1159,9 @@
1159 1159
             "license": "MIT"
1160 1160
         },
1161 1161
         "node_modules/electron-to-chromium": {
1162
-            "version": "1.5.33",
1163
-            "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.33.tgz",
1164
-            "integrity": "sha512-+cYTcFB1QqD4j4LegwLfpCNxifb6dDFUAwk6RsLusCwIaZI6or2f+q8rs5tTB2YC53HhOlIbEaqHMAAC8IOIwA==",
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==",
1165 1165
             "dev": true,
1166 1166
             "license": "ISC"
1167 1167
         },
@@ -1313,9 +1313,9 @@
1313 1313
             }
1314 1314
         },
1315 1315
         "node_modules/form-data": {
1316
-            "version": "4.0.0",
1317
-            "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
1318
-            "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
1316
+            "version": "4.0.1",
1317
+            "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz",
1318
+            "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==",
1319 1319
             "dev": true,
1320 1320
             "license": "MIT",
1321 1321
             "dependencies": {
@@ -1786,9 +1786,9 @@
1786 1786
             }
1787 1787
         },
1788 1788
         "node_modules/picocolors": {
1789
-            "version": "1.1.0",
1790
-            "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz",
1791
-            "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==",
1789
+            "version": "1.1.1",
1790
+            "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
1791
+            "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
1792 1792
             "dev": true,
1793 1793
             "license": "ISC"
1794 1794
         },
@@ -2417,9 +2417,9 @@
2417 2417
             }
2418 2418
         },
2419 2419
         "node_modules/tailwindcss": {
2420
-            "version": "3.4.13",
2421
-            "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.13.tgz",
2422
-            "integrity": "sha512-KqjHOJKogOUt5Bs752ykCeiwvi0fKVkr5oqsFNt/8px/tA8scFPIlkygsf6jXrfCqGHz7VflA6+yytWuM+XhFw==",
2420
+            "version": "3.4.14",
2421
+            "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.14.tgz",
2422
+            "integrity": "sha512-IcSvOcTRcUtQQ7ILQL5quRDg7Xs93PdJEk1ZLbhhvJc7uj/OAhYOnruEiwnGgBvUtaUAJ8/mhSw1o8L2jCiENA==",
2423 2423
             "dev": true,
2424 2424
             "license": "MIT",
2425 2425
             "dependencies": {
@@ -2550,9 +2550,9 @@
2550 2550
             "license": "MIT"
2551 2551
         },
2552 2552
         "node_modules/vite": {
2553
-            "version": "5.4.8",
2554
-            "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.8.tgz",
2555
-            "integrity": "sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==",
2553
+            "version": "5.4.9",
2554
+            "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.9.tgz",
2555
+            "integrity": "sha512-20OVpJHh0PAM0oSOELa5GaZNWeDjcAvQjGXy2Uyr+Tp+/D2/Hdz6NLgpJLsarPTA2QJ6v8mX2P1ZfbsSKvdMkg==",
2556 2556
             "dev": true,
2557 2557
             "license": "MIT",
2558 2558
             "dependencies": {
@@ -2735,9 +2735,9 @@
2735 2735
             }
2736 2736
         },
2737 2737
         "node_modules/yaml": {
2738
-            "version": "2.5.1",
2739
-            "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.1.tgz",
2740
-            "integrity": "sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==",
2738
+            "version": "2.6.0",
2739
+            "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.0.tgz",
2740
+            "integrity": "sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==",
2741 2741
             "dev": true,
2742 2742
             "license": "ISC",
2743 2743
             "bin": {

+ 1
- 5
resources/css/filament/company/theme.css Näytä tiedosto

@@ -107,11 +107,7 @@
107 107
 }
108 108
 
109 109
 .es-table__header-ctn, .es-table__footer-ctn {
110
-    @apply divide-y divide-gray-200 dark:divide-white/10 h-12;
111
-}
112
-
113
-.es-table__row {
114
-    @apply [@media(hover:hover)]:transition [@media(hover:hover)]:duration-75 hover:bg-gray-50 dark:hover:bg-white/5;
110
+    @apply divide-y divide-gray-200 dark:divide-white/10 min-h-12;
115 111
 }
116 112
 
117 113
 .es-table .es-table__rowgroup td:first-child {

+ 3
- 3
resources/views/components/company/reports/account-transactions-report-pdf.blade.php Näytä tiedosto

@@ -49,7 +49,7 @@
49 49
 
50 50
         .company-name {
51 51
             font-size: 1.125rem;
52
-            font-weight: 600;
52
+            font-weight: bold;
53 53
         }
54 54
 
55 55
         .date-range {
@@ -71,7 +71,7 @@
71 71
 
72 72
         .category-header-row > td {
73 73
             background-color: #f3f4f6; /* Gray background for category names */
74
-            font-weight: 600;
74
+            font-weight: bold;
75 75
         }
76 76
 
77 77
         .table-body tr {
@@ -84,7 +84,7 @@
84 84
 
85 85
         .category-summary-row > td,
86 86
         .table-footer-row > td {
87
-            font-weight: 600;
87
+            font-weight: bold;
88 88
             background-color: #ffffff; /* White background for footer */
89 89
         }
90 90
     </style>

+ 48
- 3
resources/views/components/company/reports/report-pdf.blade.php Näytä tiedosto

@@ -49,7 +49,7 @@
49 49
 
50 50
         .company-name {
51 51
             font-size: 1.125rem;
52
-            font-weight: 600;
52
+            font-weight: bold;
53 53
         }
54 54
 
55 55
         .date-range {
@@ -69,9 +69,16 @@
69 69
             border-bottom: 1px solid #d1d5db; /* Gray border for all rows */
70 70
         }
71 71
 
72
-        .category-header-row > td {
72
+        .category-header-row > td,
73
+        .type-header-row > td {
73 74
             background-color: #f3f4f6; /* Gray background for category names */
74
-            font-weight: 600;
75
+            font-weight: bold;
76
+        }
77
+
78
+        .type-header-row > td,
79
+        .type-data-row > td,
80
+        .type-summary-row > td {
81
+            padding-left: 1.5rem; /* Indentation for type rows */
75 82
         }
76 83
 
77 84
         .table-body tr {
@@ -83,6 +90,7 @@
83 90
         }
84 91
 
85 92
         .category-summary-row > td,
93
+        .type-summary-row > td,
86 94
         .table-footer-row > td {
87 95
             font-weight: bold;
88 96
             background-color: #ffffff; /* White background for footer */
@@ -131,6 +139,43 @@
131 139
                 @endforeach
132 140
             </tr>
133 141
         @endforeach
142
+
143
+        <!-- Category Types -->
144
+        @foreach($category->types ?? [] as $type)
145
+            <!-- Type Header -->
146
+            <tr class="type-header-row">
147
+                @foreach($type->header as $index => $header)
148
+                    <td class="{{ $report->getAlignmentClass($index) }}">
149
+                        {{ $header }}
150
+                    </td>
151
+                @endforeach
152
+            </tr>
153
+
154
+            <!-- Type Data -->
155
+            @foreach($type->data as $typeRow)
156
+                <tr class="type-data-row">
157
+                    @foreach($typeRow as $index => $cell)
158
+                        <td class="{{ $report->getAlignmentClass($index) }} {{ $index === 'account_name' ? 'whitespace-normal' : 'whitespace-nowrap' }}">
159
+                            @if(is_array($cell) && isset($cell['name']))
160
+                                {{ $cell['name'] }}
161
+                            @else
162
+                                {{ $cell }}
163
+                            @endif
164
+                        </td>
165
+                    @endforeach
166
+                </tr>
167
+            @endforeach
168
+
169
+            <!-- Type Summary -->
170
+            <tr class="type-summary-row">
171
+                @foreach($type->summary as $index => $cell)
172
+                    <td class="{{ $report->getAlignmentClass($index) }}">
173
+                        {{ $cell }}
174
+                    </td>
175
+                @endforeach
176
+            </tr>
177
+        @endforeach
178
+
134 179
         <tr class="category-summary-row">
135 180
             @foreach($category->summary as $index => $cell)
136 181
                 <td class="{{ $report->getAlignmentClass($index) }}">

+ 20
- 0
resources/views/components/company/tables/category-header.blade.php Näytä tiedosto

@@ -0,0 +1,20 @@
1
+@props([
2
+    'categoryHeaders',
3
+    'alignmentClass' => null,
4
+])
5
+
6
+
7
+<tr class="bg-gray-50 dark:bg-white/5">
8
+    @foreach($categoryHeaders as $index => $header)
9
+        <th
10
+            @class([
11
+                'px-3 py-3.5 sm:first-of-type:ps-6 sm:last-of-type:pe-6',
12
+                $alignmentClass($index) => $alignmentClass,
13
+            ])
14
+        >
15
+            <span class="text-sm font-semibold leading-6 text-gray-950 dark:text-white">
16
+                {{ $header }}
17
+            </span>
18
+        </th>
19
+    @endforeach
20
+</tr>

+ 23
- 0
resources/views/components/company/tables/cell.blade.php Näytä tiedosto

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

+ 24
- 0
resources/views/components/company/tables/container.blade.php Näytä tiedosto

@@ -0,0 +1,24 @@
1
+@props([
2
+    'reportLoaded' => false,
3
+])
4
+
5
+<x-filament-tables::container>
6
+    <div class="es-table__header-ctn"></div>
7
+    <div
8
+        class="relative divide-y divide-gray-200 overflow-x-auto dark:divide-white/10 dark:border-t-white/10 min-h-64">
9
+        <div wire:init="applyFilters">
10
+            <div wire:loading.class="flex items-center justify-center w-full h-full absolute inset-0 z-10">
11
+                <div wire:loading wire:target="applyFilters">
12
+                    <x-filament::loading-indicator class="p-6 text-primary-700 dark:text-primary-300"/>
13
+                </div>
14
+            </div>
15
+
16
+            @if($reportLoaded)
17
+                <div wire:loading.remove wire:target="applyFilters">
18
+                    {{ $slot }}
19
+                </div>
20
+            @endif
21
+        </div>
22
+    </div>
23
+    <div class="es-table__footer-ctn border-t border-gray-200"></div>
24
+</x-filament-tables::container>

+ 15
- 0
resources/views/components/company/tables/footer.blade.php Näytä tiedosto

@@ -0,0 +1,15 @@
1
+@props(['totals', 'alignmentClass'])
2
+
3
+@if(!empty($totals))
4
+    <tfoot>
5
+    <tr class="bg-gray-50 dark:bg-white/5">
6
+        @foreach($totals as $totalIndex => $totalCell)
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">
9
+                    {{ $totalCell }}
10
+                </div>
11
+            </x-filament-tables::cell>
12
+        @endforeach
13
+    </tr>
14
+    </tfoot>
15
+@endif

+ 16
- 0
resources/views/components/company/tables/header.blade.php Näytä tiedosto

@@ -0,0 +1,16 @@
1
+@props([
2
+    'headers',
3
+    'alignmentClass',
4
+])
5
+
6
+<thead class="divide-y divide-gray-200 dark:divide-white/5">
7
+<tr class="bg-gray-50 dark:bg-white/5">
8
+    @foreach($headers as $headerIndex => $headerCell)
9
+        <th class="px-3 py-3.5 sm:first-of-type:ps-6 sm:last-of-type:pe-6 {{ $alignmentClass($headerIndex) }}">
10
+            <span class="text-sm font-semibold leading-6 text-gray-950 dark:text-white">
11
+                {{ $headerCell }}
12
+            </span>
13
+        </th>
14
+    @endforeach
15
+</tr>
16
+</thead>

+ 10
- 23
resources/views/components/company/tables/reports/account-transactions.blade.php Näytä tiedosto

@@ -1,26 +1,14 @@
1 1
 <table class="w-full table-auto divide-y divide-gray-200 dark:divide-white/5">
2
-    <thead class="divide-y divide-gray-200 dark:divide-white/5">
3
-    <tr class="bg-gray-50 dark:bg-white/5">
4
-        @foreach($report->getHeaders() as $index => $header)
5
-            <th wire:key="header-{{ $index }}"
6
-                class="px-3 py-3.5 sm:first-of-type:ps-6 sm:last-of-type:pe-6 {{ $report->getAlignmentClass($index) }}">
7
-                    <span class="text-sm font-semibold text-gray-950 dark:text-white">
8
-                        {{ $header }}
9
-                    </span>
10
-            </th>
11
-        @endforeach
12
-    </tr>
13
-    </thead>
2
+    <x-company.tables.header :headers="$report->getHeaders()" :alignmentClass="[$report, 'getAlignmentClass']"/>
14 3
     @foreach($report->getCategories() as $categoryIndex => $category)
15
-        <tbody wire:key="category-{{ $categoryIndex }}"
16
-               class="divide-y divide-gray-200 whitespace-nowrap dark:divide-white/5">
4
+        <tbody class="divide-y divide-gray-200 whitespace-nowrap dark:divide-white/5">
17 5
         <!-- Category Header -->
18 6
         <tr class="bg-gray-50 dark:bg-white/5">
19
-            <x-filament-tables::cell colspan="{{ count($report->getHeaders()) }}" class="text-left">
20
-                <div class="px-3 py-2">
7
+            <x-filament-tables::cell tag="th" colspan="{{ count($report->getHeaders()) }}" class="text-left">
8
+                <div class="px-3 py-3.5">
21 9
                     @foreach ($category->header as $headerRow)
22 10
                         <div
23
-                            class="text-sm {{ $loop->first ? 'font-semibold text-gray-950 dark:text-white' : 'text-gray-500 dark:text-white/50' }}">
11
+                            class="text-sm {{ $loop->first ? 'font-semibold text-gray-950 dark:text-white' : 'font-normal text-gray-500 dark:text-white/50' }}">
24 12
                             @foreach ($headerRow as $headerValue)
25 13
                                 @if (!empty($headerValue))
26 14
                                     {{ $headerValue }}
@@ -33,14 +21,13 @@
33 21
         </tr>
34 22
         <!-- Transactions Data -->
35 23
         @foreach($category->data as $dataIndex => $transaction)
36
-            <tr wire:key="category-{{ $categoryIndex }}-data-{{ $dataIndex }}"
24
+            <tr
37 25
                 @class([
38 26
                     'bg-gray-50 dark:bg-white/5' => $loop->first || $loop->last || $loop->remaining === 1,
39 27
                 ])
40 28
             >
41 29
                 @foreach($transaction as $cellIndex => $cell)
42 30
                     <x-filament-tables::cell
43
-                        wire:key="category-{{ $categoryIndex }}-data-{{ $dataIndex }}-cell-{{ $cellIndex }}"
44 31
                         @class([
45 32
                            $report->getAlignmentClass($cellIndex),
46 33
                            'whitespace-normal' => $cellIndex === 1,
@@ -80,10 +67,10 @@
80 67
         @endforeach
81 68
         <!-- Spacer Row -->
82 69
         @unless($loop->last)
83
-            <tr wire:key="category-{{ $categoryIndex }}-spacer">
84
-                <x-filament-tables::cell colspan="{{ count($report->getHeaders()) }}">
85
-                    <div class="px-3 py-2 leading-6 invisible">Hidden Text</div>
86
-                </x-filament-tables::cell>
70
+            <tr>
71
+                <td colspan="{{ count($report->getHeaders()) }}">
72
+                    <div class="min-h-12"></div>
73
+                </td>
87 74
             </tr>
88 75
         @endunless
89 76
         </tbody>

+ 31
- 0
resources/views/components/company/tables/reports/balance-sheet-summary.blade.php Näytä tiedosto

@@ -0,0 +1,31 @@
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']"/>
3
+    @foreach($report->getSummaryCategories() as $accountCategory)
4
+        <tbody class="divide-y divide-gray-200 whitespace-nowrap dark:divide-white/5">
5
+        <x-company.tables.category-header :category-headers="$accountCategory->header"
6
+                                          :alignment-class="[$report, 'getAlignmentClass']"/>
7
+        @foreach($accountCategory->types as $accountType)
8
+            <tr>
9
+                @foreach($accountType->summary as $accountTypeSummaryIndex => $accountTypeSummaryCell)
10
+                    <x-company.tables.cell :alignment-class="$report->getAlignmentClass($accountTypeSummaryIndex)">
11
+                        {{ $accountTypeSummaryCell }}
12
+                    </x-company.tables.cell>
13
+                @endforeach
14
+            </tr>
15
+        @endforeach
16
+        <tr>
17
+            @foreach($accountCategory->summary as $accountCategorySummaryIndex => $accountCategorySummaryCell)
18
+                <x-company.tables.cell :alignment-class="$report->getAlignmentClass($accountCategorySummaryIndex)"
19
+                                       bold="true">
20
+                    {{ $accountCategorySummaryCell }}
21
+                </x-company.tables.cell>
22
+            @endforeach
23
+        </tr>
24
+        <tr>
25
+            <td colspan="{{ count($report->getSummaryHeaders()) }}">
26
+                <div class="min-h-12"></div>
27
+            </td>
28
+        </tr>
29
+        </tbody>
30
+    @endforeach
31
+</table>

+ 131
- 0
resources/views/components/company/tables/reports/balance-sheet.blade.php Näytä tiedosto

@@ -0,0 +1,131 @@
1
+<table class="w-full table-auto divide-y divide-gray-200 dark:divide-white/5">
2
+    <x-company.tables.header :headers="$report->getHeaders()" :alignment-class="[$report, 'getAlignmentClass']"/>
3
+    @foreach($report->getCategories() as $accountCategory)
4
+        <tbody class="divide-y divide-gray-200 whitespace-nowrap dark:divide-white/5">
5
+        <x-company.tables.category-header :category-headers="$accountCategory->header"
6
+                                          :alignment-class="[$report, 'getAlignmentClass']"/>
7
+        @foreach($accountCategory->data as $categoryAccount)
8
+            <tr>
9
+                @foreach($categoryAccount as $accountIndex => $categoryAccountCell)
10
+                    <x-company.tables.cell :alignment-class="$report->getAlignmentClass($accountIndex)"
11
+                                           indent="true">
12
+                        @if(is_array($categoryAccountCell) && isset($categoryAccountCell['name']))
13
+                            @if($categoryAccountCell['name'] === 'Retained Earnings' && isset($categoryAccountCell['start_date']) && isset($categoryAccountCell['end_date']))
14
+                                <x-filament::link
15
+                                    color="primary"
16
+                                    target="_blank"
17
+                                    icon="heroicon-o-arrow-top-right-on-square"
18
+                                    :icon-position="\Filament\Support\Enums\IconPosition::After"
19
+                                    :icon-size="\Filament\Support\Enums\IconSize::Small"
20
+                                    href="{{ \App\Filament\Company\Pages\Reports\IncomeStatement::getUrl([
21
+                                            'startDate' => $categoryAccountCell['start_date'],
22
+                                            'endDate' => $categoryAccountCell['end_date']
23
+                                        ]) }}"
24
+                                >
25
+                                    {{ $categoryAccountCell['name'] }}
26
+                                </x-filament::link>
27
+                            @elseif(isset($categoryAccountCell['id']) && isset($categoryAccountCell['start_date']) && isset($categoryAccountCell['end_date']))
28
+                                <x-filament::link
29
+                                    color="primary"
30
+                                    target="_blank"
31
+                                    icon="heroicon-o-arrow-top-right-on-square"
32
+                                    :icon-position="\Filament\Support\Enums\IconPosition::After"
33
+                                    :icon-size="\Filament\Support\Enums\IconSize::Small"
34
+                                    href="{{ \App\Filament\Company\Pages\Reports\AccountTransactions::getUrl([
35
+                                            'startDate' => $categoryAccountCell['start_date'],
36
+                                            'endDate' => $categoryAccountCell['end_date'],
37
+                                            'selectedAccount' => $categoryAccountCell['id']
38
+                                        ]) }}"
39
+                                >
40
+                                    {{ $categoryAccountCell['name'] }}
41
+                                </x-filament::link>
42
+                            @else
43
+                                {{ $categoryAccountCell['name'] }}
44
+                            @endif
45
+                        @else
46
+                            {{ $categoryAccountCell }}
47
+                        @endif
48
+                    </x-company.tables.cell>
49
+                @endforeach
50
+            </tr>
51
+        @endforeach
52
+        @foreach($accountCategory->types as $accountType)
53
+            <tr class="bg-gray-50 dark:bg-white/5">
54
+                @foreach($accountType->header as $accountTypeHeaderIndex => $accountTypeHeaderCell)
55
+                    <x-company.tables.cell :alignment-class="$report->getAlignmentClass($accountTypeHeaderIndex)"
56
+                                           indent="true" bold="true">
57
+                        {{ $accountTypeHeaderCell }}
58
+                    </x-company.tables.cell>
59
+                @endforeach
60
+            </tr>
61
+            @foreach($accountType->data as $typeAccount)
62
+                <tr>
63
+                    @foreach($typeAccount as $accountIndex => $typeAccountCell)
64
+                        <x-company.tables.cell :alignment-class="$report->getAlignmentClass($accountIndex)"
65
+                                               indent="true">
66
+                            @if(is_array($typeAccountCell) && isset($typeAccountCell['name']))
67
+                                @if($typeAccountCell['name'] === 'Retained Earnings' && isset($typeAccountCell['start_date']) && isset($typeAccountCell['end_date']))
68
+                                    <x-filament::link
69
+                                        color="primary"
70
+                                        target="_blank"
71
+                                        icon="heroicon-o-arrow-top-right-on-square"
72
+                                        :icon-position="\Filament\Support\Enums\IconPosition::After"
73
+                                        :icon-size="\Filament\Support\Enums\IconSize::Small"
74
+                                        href="{{ \App\Filament\Company\Pages\Reports\IncomeStatement::getUrl([
75
+                                            'startDate' => $typeAccountCell['start_date'],
76
+                                            'endDate' => $typeAccountCell['end_date']
77
+                                        ]) }}"
78
+                                    >
79
+                                        {{ $typeAccountCell['name'] }}
80
+                                    </x-filament::link>
81
+                                @elseif(isset($typeAccountCell['id']) && isset($typeAccountCell['start_date']) && isset($typeAccountCell['end_date']))
82
+                                    <x-filament::link
83
+                                        color="primary"
84
+                                        target="_blank"
85
+                                        icon="heroicon-o-arrow-top-right-on-square"
86
+                                        :icon-position="\Filament\Support\Enums\IconPosition::After"
87
+                                        :icon-size="\Filament\Support\Enums\IconSize::Small"
88
+                                        href="{{ \App\Filament\Company\Pages\Reports\AccountTransactions::getUrl([
89
+                                            'startDate' => $typeAccountCell['start_date'],
90
+                                            'endDate' => $typeAccountCell['end_date'],
91
+                                            'selectedAccount' => $typeAccountCell['id']
92
+                                        ]) }}"
93
+                                    >
94
+                                        {{ $typeAccountCell['name'] }}
95
+                                    </x-filament::link>
96
+                                @else
97
+                                    {{ $typeAccountCell['name'] }}
98
+                                @endif
99
+                            @else
100
+                                {{ $typeAccountCell }}
101
+                            @endif
102
+                        </x-company.tables.cell>
103
+                    @endforeach
104
+                </tr>
105
+            @endforeach
106
+            <tr>
107
+                @foreach($accountType->summary as $accountTypeSummaryIndex => $accountTypeSummaryCell)
108
+                    <x-company.tables.cell :alignment-class="$report->getAlignmentClass($accountTypeSummaryIndex)"
109
+                                           indent="true" bold="true">
110
+                        {{ $accountTypeSummaryCell }}
111
+                    </x-company.tables.cell>
112
+                @endforeach
113
+            </tr>
114
+        @endforeach
115
+        <tr>
116
+            @foreach($accountCategory->summary as $accountCategorySummaryIndex => $accountCategorySummaryCell)
117
+                <x-company.tables.cell :alignment-class="$report->getAlignmentClass($accountCategorySummaryIndex)"
118
+                                       bold="true">
119
+                    {{ $accountCategorySummaryCell }}
120
+                </x-company.tables.cell>
121
+            @endforeach
122
+        </tr>
123
+        <tr>
124
+            <td colspan="{{ count($report->getHeaders()) }}">
125
+                <div class="min-h-12"></div>
126
+            </td>
127
+        </tr>
128
+        </tbody>
129
+    @endforeach
130
+    <x-company.tables.footer :totals="$report->getOverallTotals()" :alignment-class="[$report, 'getAlignmentClass']"/>
131
+</table>

+ 49
- 79
resources/views/components/company/tables/reports/detailed-report.blade.php Näytä tiedosto

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

+ 28
- 0
resources/views/components/company/tables/reports/income-statement-summary.blade.php Näytä tiedosto

@@ -0,0 +1,28 @@
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']"/>
3
+    @foreach($report->getSummaryCategories() as $accountCategory)
4
+        <tbody class="divide-y divide-gray-200 whitespace-nowrap dark:divide-white/5">
5
+        <tr>
6
+            @foreach($accountCategory->summary as $accountCategorySummaryIndex => $accountCategorySummaryCell)
7
+                <x-company.tables.cell :alignment-class="$report->getAlignmentClass($accountCategorySummaryIndex)">
8
+                    {{ $accountCategorySummaryCell }}
9
+                </x-company.tables.cell>
10
+            @endforeach
11
+        </tr>
12
+
13
+        @if($accountCategory->header['account_name'] === 'Cost of Goods Sold')
14
+            <tr class="bg-gray-50 dark:bg-white/5">
15
+                @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
+                @endforeach
22
+            </tr>
23
+        @endif
24
+        </tbody>
25
+    @endforeach
26
+    <x-company.tables.footer :totals="$report->getSummaryOverallTotals()"
27
+                             :alignment-class="[$report, 'getAlignmentClass']"/>
28
+</table>

+ 12
- 6
resources/views/filament/company/pages/accounting/chart.blade.php Näytä tiedosto

@@ -15,10 +15,12 @@
15 15
 
16 16
         @foreach($this->categories as $categoryValue => $subtypes)
17 17
             @if($activeTab === $categoryValue)
18
-                <div class="es-table__container overflow-hidden rounded-xl bg-white shadow-sm ring-1 ring-gray-950/5 dark:divide-white/10 dark:bg-gray-900 dark:ring-white/10">
18
+                <div
19
+                    class="es-table__container overflow-hidden rounded-xl bg-white shadow-sm ring-1 ring-gray-950/5 dark:divide-white/10 dark:bg-gray-900 dark:ring-white/10">
19 20
                     <div class="es-table__header-ctn"></div>
20 21
                     <div class="es-table__content overflow-x-auto">
21
-                        <table class="es-table table-fixed w-full divide-y divide-gray-200 text-start text-sm dark:divide-white/5">
22
+                        <table
23
+                            class="es-table table-fixed w-full divide-y divide-gray-200 text-start text-sm dark:divide-white/5">
22 24
                             <colgroup>
23 25
                                 <col span="1" style="width: 12.5%;">
24 26
                                 <col span="1" style="width: 20%;">
@@ -28,12 +30,14 @@
28 30
                                 <col span="1" style="width: 7.5%;">
29 31
                             </colgroup>
30 32
                             @foreach($subtypes as $subtype)
31
-                                <tbody class="es-table__rowgroup divide-y divide-gray-200 whitespace-nowrap dark:divide-white/5">
33
+                                <tbody
34
+                                    class="es-table__rowgroup divide-y divide-gray-200 whitespace-nowrap dark:divide-white/5">
32 35
                                 <!-- Subtype Name Header Row -->
33 36
                                 <tr class="es-table__row--header bg-gray-50 dark:bg-white/5">
34 37
                                     <td colspan="6" class="es-table__cell px-4 py-4">
35 38
                                         <div class="es-table__row-content flex items-center space-x-2">
36
-                                            <span class="es-table__row-title text-gray-800 dark:text-gray-200 font-semibold tracking-wider">
39
+                                            <span
40
+                                                class="es-table__row-title text-gray-800 dark:text-gray-200 font-semibold tracking-wider">
37 41
                                                 {{ $subtype->name }}
38 42
                                             </span>
39 43
                                             <x-tooltip
@@ -61,7 +65,8 @@
61 65
                                                 @endif
62 66
                                             </small>
63 67
                                         </td>
64
-                                        <td colspan="2" class="es-table__cell px-4 py-4">{{ $account->description }}</td>
68
+                                        <td colspan="2"
69
+                                            class="es-table__cell px-4 py-4">{{ $account->description }}</td>
65 70
                                         <td colspan="1" class="es-table__cell px-4 py-4">
66 71
                                             @if($account->archived)
67 72
                                                 <x-filament::badge color="gray" size="sm">
@@ -80,7 +85,8 @@
80 85
                                 @empty
81 86
                                     <!-- No Accounts Available Row -->
82 87
                                     <tr class="es-table__row">
83
-                                        <td colspan="5" class="es-table__cell px-4 py-4 italic text-xs text-gray-500 dark:text-gray-400">
88
+                                        <td colspan="5"
89
+                                            class="es-table__cell px-4 py-4 italic text-xs text-gray-500 dark:text-gray-400">
84 90
                                             {{ __("You haven't added any {$subtype->name} accounts yet.") }}
85 91
                                         </td>
86 92
                                     </tr>

+ 12
- 27
resources/views/filament/company/pages/reports/account-transactions.blade.php Näytä tiedosto

@@ -5,31 +5,16 @@
5 5
         @endif
6 6
     </x-filament::section>
7 7
 
8
-    <x-filament-tables::container>
9
-        <div class="es-table__header-ctn"></div>
10
-        <div
11
-            class="relative divide-y divide-gray-200 overflow-x-auto dark:divide-white/10 dark:border-t-white/10 min-h-64">
12
-            <div wire:init="applyFilters" class="flex items-center justify-center w-full h-full absolute">
13
-                <div wire:loading wire:target="applyFilters">
14
-                    <x-filament::loading-indicator class="p-6 text-primary-700 dark:text-primary-300"/>
15
-                </div>
16
-            </div>
17
-
18
-            @if($this->reportLoaded)
19
-                <div wire:loading.remove wire:target="applyFilters">
20
-                    @if($this->report && !$this->tableHasEmptyState())
21
-                        <x-company.tables.reports.account-transactions :report="$this->report"/>
22
-                    @else
23
-                        <x-filament-tables::empty-state
24
-                            :actions="$this->getEmptyStateActions()"
25
-                            :description="$this->getEmptyStateDescription()"
26
-                            :heading="$this->getEmptyStateHeading()"
27
-                            :icon="$this->getEmptyStateIcon()"
28
-                        />
29
-                    @endif
30
-                </div>
31
-            @endif
32
-        </div>
33
-        <div class="es-table__footer-ctn border-t border-gray-200"></div>
34
-    </x-filament-tables::container>
8
+    <x-company.tables.container :report-loaded="$this->reportLoaded">
9
+        @if($this->report && ! $this->tableHasEmptyState())
10
+            <x-company.tables.reports.account-transactions :report="$this->report"/>
11
+        @else
12
+            <x-filament-tables::empty-state
13
+                :actions="$this->getEmptyStateActions()"
14
+                :description="$this->getEmptyStateDescription()"
15
+                :heading="$this->getEmptyStateHeading()"
16
+                :icon="$this->getEmptyStateIcon()"
17
+            />
18
+        @endif
19
+    </x-company.tables.container>
35 20
 </x-filament-panels::page>

+ 89
- 0
resources/views/filament/company/pages/reports/balance-sheet.blade.php Näytä tiedosto

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

+ 5
- 20
resources/views/filament/company/pages/reports/detailed-report.blade.php Näytä tiedosto

@@ -20,24 +20,9 @@
20 20
         </div>
21 21
     </x-filament::section>
22 22
 
23
-    <x-filament-tables::container>
24
-        <div class="es-table__header-ctn"></div>
25
-        <div
26
-            class="relative divide-y divide-gray-200 overflow-x-auto dark:divide-white/10 dark:border-t-white/10 min-h-64">
27
-            <div wire:init="applyFilters" class="flex items-center justify-center w-full h-full absolute">
28
-                <div wire:loading wire:target="applyFilters">
29
-                    <x-filament::loading-indicator class="p-6 text-primary-700 dark:text-primary-300"/>
30
-                </div>
31
-            </div>
32
-
33
-            @if($this->reportLoaded)
34
-                <div wire:loading.remove wire:target="applyFilters">
35
-                    @if($this->report)
36
-                        <x-company.tables.reports.detailed-report :report="$this->report"/>
37
-                    @endif
38
-                </div>
39
-            @endif
40
-        </div>
41
-        <div class="es-table__footer-ctn border-t border-gray-200"></div>
42
-    </x-filament-tables::container>
23
+    <x-company.tables.container :report-loaded="$this->reportLoaded">
24
+        @if($this->report)
25
+            <x-company.tables.reports.detailed-report :report="$this->report"/>
26
+        @endif
27
+    </x-company.tables.container>
43 28
 </x-filament-panels::page>

+ 23
- 18
resources/views/filament/company/pages/reports/income-statement.blade.php Näytä tiedosto

@@ -58,24 +58,29 @@
58 58
         @endif
59 59
     </x-filament::section>
60 60
 
61
-    <x-filament-tables::container>
62
-        <div class="es-table__header-ctn"></div>
63
-        <div
64
-            class="relative divide-y divide-gray-200 overflow-x-auto dark:divide-white/10 dark:border-t-white/10 min-h-64">
65
-            <div wire:init="applyFilters" class="flex items-center justify-center w-full h-full absolute">
66
-                <div wire:loading wire:target="applyFilters">
67
-                    <x-filament::loading-indicator class="p-6 text-primary-700 dark:text-primary-300"/>
68
-                </div>
69
-            </div>
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>
70 68
 
71
-            @if($this->reportLoaded)
72
-                <div wire:loading.remove wire:target="applyFilters">
73
-                    @if($this->report)
74
-                        <x-company.tables.reports.detailed-report :report="$this->report"/>
75
-                    @endif
76
-                </div>
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.income-statement-summary :report="$this->report"/>
81
+            @elseif($activeTab === 'details')
82
+                <x-company.tables.reports.detailed-report :report="$this->report"/>
77 83
             @endif
78
-        </div>
79
-        <div class="es-table__footer-ctn border-t border-gray-200"></div>
80
-    </x-filament-tables::container>
84
+        @endif
85
+    </x-company.tables.container>
81 86
 </x-filament-panels::page>

+ 5
- 20
resources/views/filament/company/pages/reports/trial-balance.blade.php Näytä tiedosto

@@ -22,24 +22,9 @@
22 22
         </div>
23 23
     </x-filament::section>
24 24
 
25
-    <x-filament-tables::container>
26
-        <div class="es-table__header-ctn"></div>
27
-        <div
28
-            class="relative divide-y divide-gray-200 overflow-x-auto dark:divide-white/10 dark:border-t-white/10 min-h-64">
29
-            <div wire:init="applyFilters" class="flex items-center justify-center w-full h-full absolute">
30
-                <div wire:loading wire:target="applyFilters">
31
-                    <x-filament::loading-indicator class="p-6 text-primary-700 dark:text-primary-300"/>
32
-                </div>
33
-            </div>
34
-
35
-            @if($this->reportLoaded)
36
-                <div wire:loading.remove wire:target="applyFilters">
37
-                    @if($this->report)
38
-                        <x-company.tables.reports.detailed-report :report="$this->report"/>
39
-                    @endif
40
-                </div>
41
-            @endif
42
-        </div>
43
-        <div class="es-table__footer-ctn border-t border-gray-200"></div>
44
-    </x-filament-tables::container>
25
+    <x-company.tables.container :report-loaded="$this->reportLoaded">
26
+        @if($this->report)
27
+            <x-company.tables.reports.detailed-report :report="$this->report"/>
28
+        @endif
29
+    </x-company.tables.container>
45 30
 </x-filament-panels::page>

+ 3
- 3
tests/Feature/Accounting/TransactionTest.php Näytä tiedosto

@@ -226,7 +226,7 @@ it('can add an income or expense transaction', function (TransactionType $transa
226 226
     livewire(Transactions::class)
227 227
         ->mountAction($actionName)
228 228
         ->assertActionDataSet([
229
-            'posted_at' => now()->toDateTimeString(),
229
+            'posted_at' => today(),
230 230
             'type' => $transactionType,
231 231
             'bank_account_id' => $defaultBankAccount->id,
232 232
             'amount' => '0.00',
@@ -260,7 +260,7 @@ it('can add a transfer transaction', function () {
260 260
     livewire(Transactions::class)
261 261
         ->mountAction('addTransfer')
262 262
         ->assertActionDataSet([
263
-            'posted_at' => now()->toDateTimeString(),
263
+            'posted_at' => today(),
264 264
             'type' => TransactionType::Transfer,
265 265
             'bank_account_id' => $sourceBankAccount->id,
266 266
             'amount' => '0.00',
@@ -293,7 +293,7 @@ it('can add a journal transaction', function () {
293 293
     livewire(Transactions::class)
294 294
         ->mountAction('addJournalTransaction')
295 295
         ->assertActionDataSet([
296
-            'posted_at' => now()->toDateTimeString(),
296
+            'posted_at' => today(),
297 297
             'journalEntries' => [
298 298
                 ['type' => JournalEntryType::Debit, 'account_id' => $defaultDebitAccount->id, 'amount' => '0.00'],
299 299
                 ['type' => JournalEntryType::Credit, 'account_id' => $defaultCreditAccount->id, 'amount' => '0.00'],

Loading…
Peruuta
Tallenna