Andrew Wallo 1 год назад
Родитель
Сommit
0837cc79f9

+ 0
- 2
app/Filament/Company/Pages/Accounting/Transactions.php Просмотреть файл

@@ -630,14 +630,12 @@ class Transactions extends Page implements HasTable
630 630
                             ->endDateField("{$fieldPrefix}_end_date"),
631 631
                         DatePicker::make("{$fieldPrefix}_start_date")
632 632
                             ->label("{$label} From")
633
-                            ->displayFormat('Y-m-d')
634 633
                             ->columnStart(1)
635 634
                             ->afterStateUpdated(static function (Set $set) use ($fieldPrefix) {
636 635
                                 $set("{$fieldPrefix}_date_range", 'Custom');
637 636
                             }),
638 637
                         DatePicker::make("{$fieldPrefix}_end_date")
639 638
                             ->label("{$label} To")
640
-                            ->displayFormat('Y-m-d')
641 639
                             ->afterStateUpdated(static function (Set $set) use ($fieldPrefix) {
642 640
                                 $set("{$fieldPrefix}_date_range", 'Custom');
643 641
                             }),

+ 29
- 1
app/Filament/Company/Pages/Reports.php Просмотреть файл

@@ -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\IncomeStatement;
7 8
 use App\Filament\Company\Pages\Reports\TrialBalance;
8 9
 use App\Infolists\Components\ReportEntry;
9 10
 use Filament\Infolists\Components\Section;
@@ -22,9 +23,36 @@ class Reports extends Page
22 23
         return $infolist
23 24
             ->state([])
24 25
             ->schema([
26
+                Section::make('Financial Statements')
27
+                    ->aside()
28
+                    ->description('Key financial statements that provide a snapshot of your company’s financial health.')
29
+                    ->extraAttributes(['class' => 'es-report-card'])
30
+                    ->schema([
31
+                        ReportEntry::make('income_statement')
32
+                            ->hiddenLabel()
33
+                            ->heading('Income Statement')
34
+                            ->description('Tracks revenue and expenses to show profit or loss over a specific period of time.')
35
+                            ->icon('heroicon-o-chart-bar')
36
+                            ->iconColor(Color::Indigo)
37
+                            ->url(IncomeStatement::getUrl()),
38
+                        ReportEntry::make('balance_sheet')
39
+                            ->hiddenLabel()
40
+                            ->heading('Balance Sheet')
41
+                            ->description('Snapshot of assets, liabilities, and equity at a specific point in time.')
42
+                            ->icon('heroicon-o-clipboard-document-list')
43
+                            ->iconColor(Color::Emerald)
44
+                            ->url('#'),
45
+                        ReportEntry::make('cash_flow_statement')
46
+                            ->hiddenLabel()
47
+                            ->heading('Cash Flow Statement')
48
+                            ->description('Shows cash inflows and outflows over a specific period of time.')
49
+                            ->icon('heroicon-o-document-currency-dollar')
50
+                            ->iconColor(Color::Cyan)
51
+                            ->url('#'),
52
+                    ]),
25 53
                 Section::make('Detailed Reports')
26 54
                     ->aside()
27
-                    ->description('Dig into the details of your business’s transactions, balances, and accounts.')
55
+                    ->description('Dig into the details of your company’s transactions, balances, and accounts.')
28 56
                     ->extraAttributes(['class' => 'es-report-card'])
29 57
                     ->schema([
30 58
                         ReportEntry::make('account_balances')

+ 1
- 0
app/Filament/Company/Pages/Reports/AccountTransactions.php Просмотреть файл

@@ -50,6 +50,7 @@ class AccountTransactions extends BaseReportPage
50 50
         return [
51 51
             Column::make('date')
52 52
                 ->label('Date')
53
+                ->markAsDate()
53 54
                 ->alignment(Alignment::Left),
54 55
             Column::make('description')
55 56
                 ->label('Description')

+ 2
- 4
app/Filament/Company/Pages/Reports/BaseReportPage.php Просмотреть файл

@@ -154,8 +154,8 @@ abstract class BaseReportPage extends Page
154 154
 
155 155
     public function setDateRange(Carbon $start, Carbon $end): void
156 156
     {
157
-        $this->startDate = $start->toDateString();
158
-        $this->endDate = $end->isFuture() ? now()->toDateString() : $end->toDateString();
157
+        $this->startDate = $start->startOfDay()->toDateTimeString();
158
+        $this->endDate = $end->isFuture() ? now()->endOfDay()->toDateTimeString() : $end->endOfDay()->toDateTimeString();
159 159
     }
160 160
 
161 161
     public function toggleColumnsAction(): Action
@@ -238,7 +238,6 @@ abstract class BaseReportPage extends Page
238 238
     {
239 239
         return DatePicker::make('startDate')
240 240
             ->label('Start Date')
241
-            ->defaultDateFormat()
242 241
             ->afterStateUpdated(static function (Set $set) {
243 242
                 $set('dateRange', 'Custom');
244 243
             });
@@ -248,7 +247,6 @@ abstract class BaseReportPage extends Page
248 247
     {
249 248
         return DatePicker::make('endDate')
250 249
             ->label('End Date')
251
-            ->defaultDateFormat()
252 250
             ->afterStateUpdated(static function (Set $set) {
253 251
                 $set('dateRange', 'Custom');
254 252
             });

+ 84
- 0
app/Filament/Company/Pages/Reports/IncomeStatement.php Просмотреть файл

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

+ 2
- 2
app/Filament/Forms/Components/DateRangeSelect.php Просмотреть файл

@@ -165,7 +165,7 @@ class DateRangeSelect extends Select
165 165
 
166 166
     public function setDateRange(Carbon $start, Carbon $end, Set $set): void
167 167
     {
168
-        $set($this->startDateField, $start->format('Y-m-d'));
169
-        $set($this->endDateField, $end->isFuture() ? now()->format('Y-m-d') : $end->format('Y-m-d'));
168
+        $set($this->startDateField, $start->startOfDay()->toDateTimeString());
169
+        $set($this->endDateField, $end->isFuture() ? now()->endOfDay()->toDateTimeString() : $end->endOfDay()->toDateTimeString());
170 170
     }
171 171
 }

+ 2
- 4
app/Repositories/Accounting/JournalEntryRepository.php Просмотреть файл

@@ -4,7 +4,6 @@ namespace App\Repositories\Accounting;
4 4
 
5 5
 use App\Models\Accounting\Account;
6 6
 use Illuminate\Database\Eloquent\Builder;
7
-use Illuminate\Support\Carbon;
8 7
 
9 8
 class JournalEntryRepository
10 9
 {
@@ -13,9 +12,8 @@ class JournalEntryRepository
13 12
         $query = $account->journalEntries()->where('type', $type);
14 13
 
15 14
         if ($startDate && $endDate) {
16
-            $endOfDay = Carbon::parse($endDate)->endOfDay();
17
-            $query->whereHas('transaction', static function (Builder $query) use ($startDate, $endOfDay) {
18
-                $query->whereBetween('posted_at', [$startDate, $endOfDay]);
15
+            $query->whereHas('transaction', static function (Builder $query) use ($startDate, $endDate) {
16
+                $query->whereBetween('posted_at', [$startDate, $endDate]);
19 17
             });
20 18
         } elseif ($startDate) {
21 19
             $query->whereHas('transaction', static function (Builder $query) use ($startDate) {

+ 9
- 16
app/Services/AccountService.php Просмотреть файл

@@ -13,7 +13,6 @@ use App\ValueObjects\Money;
13 13
 use Closure;
14 14
 use Illuminate\Database\Eloquent\Builder;
15 15
 use Illuminate\Database\Query\JoinClause;
16
-use Illuminate\Support\Carbon;
17 16
 use Illuminate\Support\Facades\DB;
18 17
 
19 18
 class AccountService implements AccountHandler
@@ -56,9 +55,7 @@ class AccountService implements AccountHandler
56 55
 
57 56
     public function getTransactionDetailsSubquery(string $startDate, string $endDate): Closure
58 57
     {
59
-        $endOfDay = Carbon::parse($endDate)->endOfDay()->toDateTimeString();
60
-
61
-        return static function ($query) use ($startDate, $endOfDay) {
58
+        return static function ($query) use ($startDate, $endDate) {
62 59
             $query->select(
63 60
                 'journal_entries.id',
64 61
                 'journal_entries.account_id',
@@ -67,7 +64,7 @@ class AccountService implements AccountHandler
67 64
                 'journal_entries.amount',
68 65
                 DB::raw('journal_entries.amount * IF(journal_entries.type = "debit", 1, -1) AS signed_amount')
69 66
             )
70
-                ->whereBetween('transactions.posted_at', [$startDate, $endOfDay])
67
+                ->whereBetween('transactions.posted_at', [$startDate, $endDate])
71 68
                 ->join('transactions', 'transactions.id', '=', 'journal_entries.transaction_id')
72 69
                 ->orderBy('transactions.posted_at')
73 70
                 ->with('transaction:id,type,description,posted_at');
@@ -76,8 +73,6 @@ class AccountService implements AccountHandler
76 73
 
77 74
     public function getAccountBalances(string $startDate, string $endDate, array $accountIds = []): Builder
78 75
     {
79
-        $endOfDay = Carbon::parse($endDate)->endOfDay()->toDateTimeString();
80
-
81 76
         $query = Account::query()
82 77
             ->select([
83 78
                 'accounts.id',
@@ -110,9 +105,9 @@ class AccountService implements AccountHandler
110 105
                 "),
111 106
             ])
112 107
             ->join('journal_entries', 'journal_entries.account_id', '=', 'accounts.id')
113
-            ->join('transactions', function (JoinClause $join) use ($endOfDay) {
108
+            ->join('transactions', function (JoinClause $join) use ($endDate) {
114 109
                 $join->on('transactions.id', '=', 'journal_entries.transaction_id')
115
-                    ->where('transactions.posted_at', '<=', $endOfDay);
110
+                    ->where('transactions.posted_at', '<=', $endDate);
116 111
             })
117 112
             ->groupBy('accounts.id')
118 113
             ->with(['subtype:id,name']);
@@ -121,15 +116,13 @@ class AccountService implements AccountHandler
121 116
             $query->whereIn('accounts.id', $accountIds);
122 117
         }
123 118
 
124
-        $query->addBinding([$startDate, $startDate, $startDate, $startDate, $startDate, $endOfDay, $startDate, $endOfDay], 'select');
119
+        $query->addBinding([$startDate, $startDate, $startDate, $startDate, $startDate, $endDate, $startDate, $endDate], 'select');
125 120
 
126 121
         return $query;
127 122
     }
128 123
 
129 124
     public function getTotalBalanceForAllBankAccounts(string $startDate, string $endDate): Money
130 125
     {
131
-        $endOfDay = Carbon::parse($endDate)->endOfDay()->toDateTimeString();
132
-
133 126
         $accountIds = Account::whereHas('bankAccount')
134 127
             ->pluck('id')
135 128
             ->toArray();
@@ -139,9 +132,9 @@ class AccountService implements AccountHandler
139 132
         }
140 133
 
141 134
         $result = DB::table('journal_entries')
142
-            ->join('transactions', function (JoinClause $join) use ($endOfDay) {
135
+            ->join('transactions', function (JoinClause $join) use ($endDate) {
143 136
                 $join->on('transactions.id', '=', 'journal_entries.transaction_id')
144
-                    ->where('transactions.posted_at', '<=', $endOfDay);
137
+                    ->where('transactions.posted_at', '<=', $endDate);
145 138
             })
146 139
             ->whereIn('journal_entries.account_id', $accountIds)
147 140
             ->selectRaw('
@@ -159,9 +152,9 @@ class AccountService implements AccountHandler
159 152
                 $startDate,
160 153
                 $startDate,
161 154
                 $startDate,
162
-                $endOfDay,
155
+                $endDate,
163 156
                 $startDate,
164
-                $endOfDay,
157
+                $endDate,
165 158
             ])
166 159
             ->first();
167 160
 

+ 40
- 12
app/Services/ExportService.php Просмотреть файл

@@ -4,18 +4,20 @@ namespace App\Services;
4 4
 
5 5
 use App\Contracts\ExportableReport;
6 6
 use App\Models\Company;
7
+use App\Support\Column;
7 8
 use Barryvdh\Snappy\Facades\SnappyPdf;
9
+use Carbon\Exceptions\InvalidFormatException;
8 10
 use Illuminate\Support\Carbon;
9 11
 use Symfony\Component\HttpFoundation\StreamedResponse;
10 12
 
11 13
 class ExportService
12 14
 {
13
-    public function exportToCsv(Company $company, ExportableReport $report, string $startDate, string $endDate, bool $separateCategoryHeaders = false): StreamedResponse
15
+    public function exportToCsv(Company $company, ExportableReport $report, string $startDate, string $endDate): StreamedResponse
14 16
     {
15
-        $formattedStartDate = Carbon::parse($startDate)->format('Y-m-d');
16
-        $formattedEndDate = Carbon::parse($endDate)->format('Y-m-d');
17
+        $formattedStartDate = Carbon::parse($startDate)->toDateString();
18
+        $formattedEndDate = Carbon::parse($endDate)->toDateString();
17 19
 
18
-        $timestamp = Carbon::now()->format('Y-m-d-H_i');
20
+        $timestamp = Carbon::now()->format('Y-m-d_H-i-s');
19 21
 
20 22
         $filename = $company->name . ' ' . $report->getTitle() . ' ' . $formattedStartDate . ' to ' . $formattedEndDate . ' ' . $timestamp . '.csv';
21 23
 
@@ -24,12 +26,15 @@ class ExportService
24 26
             'Content-Disposition' => 'attachment; filename="' . $filename . '"',
25 27
         ];
26 28
 
27
-        $callback = function () use ($report, $company, $formattedStartDate, $formattedEndDate) {
29
+        $callback = function () use ($startDate, $endDate, $report, $company) {
28 30
             $file = fopen('php://output', 'wb');
29 31
 
32
+            $defaultStartDateFormat = Carbon::parse($startDate)->toDefaultDateFormat();
33
+            $defaultEndDateFormat = Carbon::parse($endDate)->toDefaultDateFormat();
34
+
30 35
             fputcsv($file, [$report->getTitle()]);
31 36
             fputcsv($file, [$company->name]);
32
-            fputcsv($file, ['Date Range: ' . $formattedStartDate . ' to ' . $formattedEndDate]);
37
+            fputcsv($file, ['Date Range: ' . $defaultStartDateFormat . ' to ' . $defaultEndDateFormat]);
33 38
             fputcsv($file, []);
34 39
 
35 40
             fputcsv($file, $report->getHeaders());
@@ -44,7 +49,30 @@ class ExportService
44 49
                 }
45 50
 
46 51
                 foreach ($category->data as $accountRow) {
47
-                    fputcsv($file, $accountRow);
52
+                    $row = [];
53
+                    $columns = $report->getColumns();
54
+
55
+                    /**
56
+                     * @var Column $column
57
+                     */
58
+                    foreach ($columns as $index => $column) {
59
+                        $cell = $accountRow[$index] ?? '';
60
+
61
+                        if ($column->isDate()) {
62
+                            try {
63
+                                $row[] = Carbon::parse($cell)->toDateString();
64
+                            } catch (InvalidFormatException) {
65
+                                $row[] = $cell;
66
+                            }
67
+                        } elseif (is_array($cell)) {
68
+                            // Handle array cells by extracting 'name' or 'description'
69
+                            $row[] = $cell['name'] ?? $cell['description'] ?? '';
70
+                        } else {
71
+                            $row[] = $cell;
72
+                        }
73
+                    }
74
+
75
+                    fputcsv($file, $row);
48 76
                 }
49 77
 
50 78
                 if (filled($category->summary)) {
@@ -66,18 +94,18 @@ class ExportService
66 94
 
67 95
     public function exportToPdf(Company $company, ExportableReport $report, string $startDate, string $endDate): StreamedResponse
68 96
     {
69
-        $formattedStartDate = Carbon::parse($startDate)->format('Y-m-d');
70
-        $formattedEndDate = Carbon::parse($endDate)->format('Y-m-d');
97
+        $formattedStartDate = Carbon::parse($startDate)->toDateString();
98
+        $formattedEndDate = Carbon::parse($endDate)->toDateString();
71 99
 
72
-        $timestamp = Carbon::now()->format('Y-m-d-H_i');
100
+        $timestamp = Carbon::now()->format('Y-m-d_H-i-s');
73 101
 
74 102
         $filename = $company->name . ' ' . $report->getTitle() . ' ' . $formattedStartDate . ' to ' . $formattedEndDate . ' ' . $timestamp . '.pdf';
75 103
 
76 104
         $pdf = SnappyPdf::loadView($report->getPdfView(), [
77 105
             'company' => $company,
78 106
             'report' => $report,
79
-            'startDate' => Carbon::parse($startDate)->format('M d, Y'),
80
-            'endDate' => Carbon::parse($endDate)->format('M d, Y'),
107
+            'startDate' => Carbon::parse($startDate)->toDefaultDateFormat(),
108
+            'endDate' => Carbon::parse($endDate)->toDefaultDateFormat(),
81 109
         ]);
82 110
 
83 111
         return response()->streamDownload(function () use ($pdf) {

+ 69
- 3
app/Services/ReportService.php Просмотреть файл

@@ -121,9 +121,7 @@ class ReportService
121 121
 
122 122
         $query = $this->accountService->getAccountBalances($startDate, $endDate, $accountIds);
123 123
 
124
-        $query->with(['journalEntries' => $this->accountService->getTransactionDetailsSubquery($startDate, $endDate)]);
125
-
126
-        $accounts = $query->get();
124
+        $accounts = $query->with(['journalEntries' => $this->accountService->getTransactionDetailsSubquery($startDate, $endDate)])->get();
127 125
 
128 126
         $reportCategories = [];
129 127
 
@@ -292,4 +290,72 @@ class ReportService
292 290
 
293 291
         return ['debit_balance' => abs($endingBalance), 'credit_balance' => 0];
294 292
     }
293
+
294
+    public function buildIncomeStatementReport(string $startDate, string $endDate, array $columns = []): ReportDTO
295
+    {
296
+        $accounts = $this->accountService->getAccountBalances($startDate, $endDate)->get();
297
+
298
+        $accountCategories = [];
299
+        $totalRevenue = 0;
300
+        $cogs = 0;
301
+        $totalExpenses = 0;
302
+
303
+        $categoryGroups = [
304
+            'Revenue' => [
305
+                'accounts' => $accounts->where('category', AccountCategory::Revenue),
306
+                'total' => &$totalRevenue,
307
+            ],
308
+            'Cost of Goods Sold' => [
309
+                'accounts' => $accounts->where('subtype.name', 'Cost of Goods Sold'),
310
+                'total' => &$cogs,
311
+            ],
312
+            'Expenses' => [
313
+                'accounts' => $accounts->where('category', AccountCategory::Expense)->where('subtype.name', '!=', 'Cost of Goods Sold'),
314
+                'total' => &$totalExpenses,
315
+            ],
316
+        ];
317
+
318
+        foreach ($categoryGroups as $label => $group) {
319
+            $categoryAccounts = [];
320
+            $netMovement = 0;
321
+
322
+            foreach ($group['accounts']->sortBy('code', SORT_NATURAL) as $account) {
323
+                $category = null;
324
+
325
+                if ($label === 'Revenue') {
326
+                    $category = AccountCategory::Revenue;
327
+                } elseif ($label === 'Expenses') {
328
+                    $category = AccountCategory::Expense;
329
+                } elseif ($label === 'Cost of Goods Sold') {
330
+                    // COGS is treated as part of Expenses, so we use AccountCategory::Expense
331
+                    $category = AccountCategory::Expense;
332
+                }
333
+
334
+                if ($category !== null) {
335
+                    $accountBalances = $this->calculateAccountBalances($account, $category);
336
+                    $movement = $accountBalances['net_movement'];
337
+                    $netMovement += $movement;
338
+                    $group['total'] += $movement;
339
+
340
+                    $categoryAccounts[] = new AccountDTO(
341
+                        $account->name,
342
+                        $account->code,
343
+                        $account->id,
344
+                        $this->formatBalances(['net_movement' => $movement]),
345
+                    );
346
+                }
347
+            }
348
+
349
+            $accountCategories[$label] = new AccountCategoryDTO(
350
+                $categoryAccounts,
351
+                $this->formatBalances(['net_movement' => $netMovement]),
352
+            );
353
+        }
354
+
355
+        $grossProfit = $totalRevenue - $cogs;
356
+        $netProfit = $grossProfit - $totalExpenses;
357
+        $formattedReportTotalBalances = $this->formatBalances(['net_movement' => $netProfit]);
358
+
359
+        return new ReportDTO($accountCategories, $formattedReportTotalBalances, $columns);
360
+    }
295 361
 }

+ 14
- 0
app/Support/Column.php Просмотреть файл

@@ -18,6 +18,8 @@ class Column extends Component
18 18
     use HasLabel;
19 19
     use HasName;
20 20
 
21
+    protected bool $isDate = false;
22
+
21 23
     final public function __construct(string $name)
22 24
     {
23 25
         $this->name($name);
@@ -40,4 +42,16 @@ class Column extends Component
40 42
             default => '',
41 43
         };
42 44
     }
45
+
46
+    public function markAsDate(): static
47
+    {
48
+        $this->isDate = true;
49
+
50
+        return $this;
51
+    }
52
+
53
+    public function isDate(): bool
54
+    {
55
+        return $this->isDate;
56
+    }
43 57
 }

+ 130
- 0
app/Transformers/IncomeStatementReportTransformer.php Просмотреть файл

@@ -0,0 +1,130 @@
1
+<?php
2
+
3
+namespace App\Transformers;
4
+
5
+use App\DTO\AccountDTO;
6
+use App\DTO\ReportCategoryDTO;
7
+use App\Support\Column;
8
+
9
+class IncomeStatementReportTransformer extends BaseReportTransformer
10
+{
11
+    protected string $totalRevenue = '$0.00';
12
+
13
+    protected string $totalCogs = '$0.00';
14
+
15
+    protected string $totalExpenses = '$0.00';
16
+
17
+    public function getTitle(): string
18
+    {
19
+        return 'Income Statement';
20
+    }
21
+
22
+    public function getHeaders(): array
23
+    {
24
+        return array_map(fn (Column $column) => $column->getLabel(), $this->getColumns());
25
+    }
26
+
27
+    public function calculateTotals(): void
28
+    {
29
+        foreach ($this->report->categories as $accountCategoryName => $accountCategory) {
30
+            match ($accountCategoryName) {
31
+                'Revenue' => $this->totalRevenue = $accountCategory->summary->netMovement ?? '',
32
+                'Cost of Goods Sold' => $this->totalCogs = $accountCategory->summary->netMovement ?? '',
33
+                'Expenses' => $this->totalExpenses = $accountCategory->summary->netMovement ?? '',
34
+            };
35
+        }
36
+    }
37
+
38
+    public function getCategories(): array
39
+    {
40
+        $categories = [];
41
+
42
+        foreach ($this->report->categories as $accountCategoryName => $accountCategory) {
43
+            // Initialize header with empty strings
44
+            $header = [];
45
+
46
+            foreach ($this->getColumns() as $index => $column) {
47
+                if ($column->getName() === 'account_name') {
48
+                    $header[$index] = $accountCategoryName;
49
+                } else {
50
+                    $header[$index] = '';
51
+                }
52
+            }
53
+
54
+            $data = array_map(function (AccountDTO $account) {
55
+                $row = [];
56
+
57
+                foreach ($this->getColumns() as $column) {
58
+                    $row[] = match ($column->getName()) {
59
+                        'account_code' => $account->accountCode,
60
+                        'account_name' => [
61
+                            'name' => $account->accountName,
62
+                            'id' => $account->accountId ?? null,
63
+                        ],
64
+                        'net_movement' => $account->balance->netMovement ?? '',
65
+                        default => '',
66
+                    };
67
+                }
68
+
69
+                return $row;
70
+            }, $accountCategory->accounts);
71
+
72
+            $summary = [];
73
+
74
+            foreach ($this->getColumns() as $column) {
75
+                $summary[] = match ($column->getName()) {
76
+                    'account_name' => 'Total ' . $accountCategoryName,
77
+                    'net_movement' => $accountCategory->summary->netMovement ?? '',
78
+                    default => '',
79
+                };
80
+            }
81
+
82
+            $categories[] = new ReportCategoryDTO(
83
+                header: $header,
84
+                data: $data,
85
+                summary: $summary,
86
+            );
87
+        }
88
+
89
+        return $categories;
90
+    }
91
+
92
+    public function getOverallTotals(): array
93
+    {
94
+        $totals = [];
95
+
96
+        foreach ($this->getColumns() as $column) {
97
+            $totals[] = match ($column->getName()) {
98
+                'account_name' => 'Net Earnings',
99
+                'net_movement' => $this->report->overallTotal->netMovement ?? '',
100
+                default => '',
101
+            };
102
+        }
103
+
104
+        return $totals;
105
+    }
106
+
107
+    public function getSummary(): array
108
+    {
109
+        $this->calculateTotals();
110
+
111
+        return [
112
+            [
113
+                'label' => 'Revenue',
114
+                'value' => $this->totalRevenue,
115
+            ],
116
+            [
117
+                'label' => 'Cost of Goods Sold',
118
+                'value' => $this->totalCogs,
119
+            ],
120
+            [
121
+                'label' => 'Expenses',
122
+                'value' => $this->totalExpenses,
123
+            ],
124
+            [
125
+                'label' => 'Net Earnings',
126
+                'value' => $this->report->overallTotal->netMovement ?? '',
127
+            ],
128
+        ];
129
+    }
130
+}

+ 70
- 70
package-lock.json Просмотреть файл

@@ -541,9 +541,9 @@
541 541
             }
542 542
         },
543 543
         "node_modules/@rollup/rollup-android-arm-eabi": {
544
-            "version": "4.20.0",
545
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.20.0.tgz",
546
-            "integrity": "sha512-TSpWzflCc4VGAUJZlPpgAJE1+V60MePDQnBd7PPkpuEmOy8i87aL6tinFGKBFKuEDikYpig72QzdT3QPYIi+oA==",
544
+            "version": "4.21.0",
545
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.21.0.tgz",
546
+            "integrity": "sha512-WTWD8PfoSAJ+qL87lE7votj3syLavxunWhzCnx3XFxFiI/BA/r3X7MUM8dVrH8rb2r4AiO8jJsr3ZjdaftmnfA==",
547 547
             "cpu": [
548 548
                 "arm"
549 549
             ],
@@ -555,9 +555,9 @@
555 555
             ]
556 556
         },
557 557
         "node_modules/@rollup/rollup-android-arm64": {
558
-            "version": "4.20.0",
559
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.20.0.tgz",
560
-            "integrity": "sha512-u00Ro/nok7oGzVuh/FMYfNoGqxU5CPWz1mxV85S2w9LxHR8OoMQBuSk+3BKVIDYgkpeOET5yXkx90OYFc+ytpQ==",
558
+            "version": "4.21.0",
559
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.21.0.tgz",
560
+            "integrity": "sha512-a1sR2zSK1B4eYkiZu17ZUZhmUQcKjk2/j9Me2IDjk1GHW7LB5Z35LEzj9iJch6gtUfsnvZs1ZNyDW2oZSThrkA==",
561 561
             "cpu": [
562 562
                 "arm64"
563 563
             ],
@@ -569,9 +569,9 @@
569 569
             ]
570 570
         },
571 571
         "node_modules/@rollup/rollup-darwin-arm64": {
572
-            "version": "4.20.0",
573
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.20.0.tgz",
574
-            "integrity": "sha512-uFVfvzvsdGtlSLuL0ZlvPJvl6ZmrH4CBwLGEFPe7hUmf7htGAN+aXo43R/V6LATyxlKVC/m6UsLb7jbG+LG39Q==",
572
+            "version": "4.21.0",
573
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.21.0.tgz",
574
+            "integrity": "sha512-zOnKWLgDld/svhKO5PD9ozmL6roy5OQ5T4ThvdYZLpiOhEGY+dp2NwUmxK0Ld91LrbjrvtNAE0ERBwjqhZTRAA==",
575 575
             "cpu": [
576 576
                 "arm64"
577 577
             ],
@@ -583,9 +583,9 @@
583 583
             ]
584 584
         },
585 585
         "node_modules/@rollup/rollup-darwin-x64": {
586
-            "version": "4.20.0",
587
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.20.0.tgz",
588
-            "integrity": "sha512-xbrMDdlev53vNXexEa6l0LffojxhqDTBeL+VUxuuIXys4x6xyvbKq5XqTXBCEUA8ty8iEJblHvFaWRJTk/icAQ==",
586
+            "version": "4.21.0",
587
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.21.0.tgz",
588
+            "integrity": "sha512-7doS8br0xAkg48SKE2QNtMSFPFUlRdw9+votl27MvT46vo44ATBmdZdGysOevNELmZlfd+NEa0UYOA8f01WSrg==",
589 589
             "cpu": [
590 590
                 "x64"
591 591
             ],
@@ -597,9 +597,9 @@
597 597
             ]
598 598
         },
599 599
         "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
600
-            "version": "4.20.0",
601
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.20.0.tgz",
602
-            "integrity": "sha512-jMYvxZwGmoHFBTbr12Xc6wOdc2xA5tF5F2q6t7Rcfab68TT0n+r7dgawD4qhPEvasDsVpQi+MgDzj2faOLsZjA==",
600
+            "version": "4.21.0",
601
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.21.0.tgz",
602
+            "integrity": "sha512-pWJsfQjNWNGsoCq53KjMtwdJDmh/6NubwQcz52aEwLEuvx08bzcy6tOUuawAOncPnxz/3siRtd8hiQ32G1y8VA==",
603 603
             "cpu": [
604 604
                 "arm"
605 605
             ],
@@ -611,9 +611,9 @@
611 611
             ]
612 612
         },
613 613
         "node_modules/@rollup/rollup-linux-arm-musleabihf": {
614
-            "version": "4.20.0",
615
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.20.0.tgz",
616
-            "integrity": "sha512-1asSTl4HKuIHIB1GcdFHNNZhxAYEdqML/MW4QmPS4G0ivbEcBr1JKlFLKsIRqjSwOBkdItn3/ZDlyvZ/N6KPlw==",
614
+            "version": "4.21.0",
615
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.21.0.tgz",
616
+            "integrity": "sha512-efRIANsz3UHZrnZXuEvxS9LoCOWMGD1rweciD6uJQIx2myN3a8Im1FafZBzh7zk1RJ6oKcR16dU3UPldaKd83w==",
617 617
             "cpu": [
618 618
                 "arm"
619 619
             ],
@@ -625,9 +625,9 @@
625 625
             ]
626 626
         },
627 627
         "node_modules/@rollup/rollup-linux-arm64-gnu": {
628
-            "version": "4.20.0",
629
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.20.0.tgz",
630
-            "integrity": "sha512-COBb8Bkx56KldOYJfMf6wKeYJrtJ9vEgBRAOkfw6Ens0tnmzPqvlpjZiLgkhg6cA3DGzCmLmmd319pmHvKWWlQ==",
628
+            "version": "4.21.0",
629
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.21.0.tgz",
630
+            "integrity": "sha512-ZrPhydkTVhyeGTW94WJ8pnl1uroqVHM3j3hjdquwAcWnmivjAwOYjTEAuEDeJvGX7xv3Z9GAvrBkEzCgHq9U1w==",
631 631
             "cpu": [
632 632
                 "arm64"
633 633
             ],
@@ -639,9 +639,9 @@
639 639
             ]
640 640
         },
641 641
         "node_modules/@rollup/rollup-linux-arm64-musl": {
642
-            "version": "4.20.0",
643
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.20.0.tgz",
644
-            "integrity": "sha512-+it+mBSyMslVQa8wSPvBx53fYuZK/oLTu5RJoXogjk6x7Q7sz1GNRsXWjn6SwyJm8E/oMjNVwPhmNdIjwP135Q==",
642
+            "version": "4.21.0",
643
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.21.0.tgz",
644
+            "integrity": "sha512-cfaupqd+UEFeURmqNP2eEvXqgbSox/LHOyN9/d2pSdV8xTrjdg3NgOFJCtc1vQ/jEke1qD0IejbBfxleBPHnPw==",
645 645
             "cpu": [
646 646
                 "arm64"
647 647
             ],
@@ -653,9 +653,9 @@
653 653
             ]
654 654
         },
655 655
         "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
656
-            "version": "4.20.0",
657
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.20.0.tgz",
658
-            "integrity": "sha512-yAMvqhPfGKsAxHN8I4+jE0CpLWD8cv4z7CK7BMmhjDuz606Q2tFKkWRY8bHR9JQXYcoLfopo5TTqzxgPUjUMfw==",
656
+            "version": "4.21.0",
657
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.21.0.tgz",
658
+            "integrity": "sha512-ZKPan1/RvAhrUylwBXC9t7B2hXdpb/ufeu22pG2psV7RN8roOfGurEghw1ySmX/CmDDHNTDDjY3lo9hRlgtaHg==",
659 659
             "cpu": [
660 660
                 "ppc64"
661 661
             ],
@@ -667,9 +667,9 @@
667 667
             ]
668 668
         },
669 669
         "node_modules/@rollup/rollup-linux-riscv64-gnu": {
670
-            "version": "4.20.0",
671
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.20.0.tgz",
672
-            "integrity": "sha512-qmuxFpfmi/2SUkAw95TtNq/w/I7Gpjurx609OOOV7U4vhvUhBcftcmXwl3rqAek+ADBwSjIC4IVNLiszoj3dPA==",
670
+            "version": "4.21.0",
671
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.21.0.tgz",
672
+            "integrity": "sha512-H1eRaCwd5E8eS8leiS+o/NqMdljkcb1d6r2h4fKSsCXQilLKArq6WS7XBLDu80Yz+nMqHVFDquwcVrQmGr28rg==",
673 673
             "cpu": [
674 674
                 "riscv64"
675 675
             ],
@@ -681,9 +681,9 @@
681 681
             ]
682 682
         },
683 683
         "node_modules/@rollup/rollup-linux-s390x-gnu": {
684
-            "version": "4.20.0",
685
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.20.0.tgz",
686
-            "integrity": "sha512-I0BtGXddHSHjV1mqTNkgUZLnS3WtsqebAXv11D5BZE/gfw5KoyXSAXVqyJximQXNvNzUo4GKlCK/dIwXlz+jlg==",
684
+            "version": "4.21.0",
685
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.21.0.tgz",
686
+            "integrity": "sha512-zJ4hA+3b5tu8u7L58CCSI0A9N1vkfwPhWd/puGXwtZlsB5bTkwDNW/+JCU84+3QYmKpLi+XvHdmrlwUwDA6kqw==",
687 687
             "cpu": [
688 688
                 "s390x"
689 689
             ],
@@ -695,9 +695,9 @@
695 695
             ]
696 696
         },
697 697
         "node_modules/@rollup/rollup-linux-x64-gnu": {
698
-            "version": "4.20.0",
699
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.20.0.tgz",
700
-            "integrity": "sha512-y+eoL2I3iphUg9tN9GB6ku1FA8kOfmF4oUEWhztDJ4KXJy1agk/9+pejOuZkNFhRwHAOxMsBPLbXPd6mJiCwew==",
698
+            "version": "4.21.0",
699
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.21.0.tgz",
700
+            "integrity": "sha512-e2hrvElFIh6kW/UNBQK/kzqMNY5mO+67YtEh9OA65RM5IJXYTWiXjX6fjIiPaqOkBthYF1EqgiZ6OXKcQsM0hg==",
701 701
             "cpu": [
702 702
                 "x64"
703 703
             ],
@@ -709,9 +709,9 @@
709 709
             ]
710 710
         },
711 711
         "node_modules/@rollup/rollup-linux-x64-musl": {
712
-            "version": "4.20.0",
713
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.20.0.tgz",
714
-            "integrity": "sha512-hM3nhW40kBNYUkZb/r9k2FKK+/MnKglX7UYd4ZUy5DJs8/sMsIbqWK2piZtVGE3kcXVNj3B2IrUYROJMMCikNg==",
712
+            "version": "4.21.0",
713
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.21.0.tgz",
714
+            "integrity": "sha512-1vvmgDdUSebVGXWX2lIcgRebqfQSff0hMEkLJyakQ9JQUbLDkEaMsPTLOmyccyC6IJ/l3FZuJbmrBw/u0A0uCQ==",
715 715
             "cpu": [
716 716
                 "x64"
717 717
             ],
@@ -723,9 +723,9 @@
723 723
             ]
724 724
         },
725 725
         "node_modules/@rollup/rollup-win32-arm64-msvc": {
726
-            "version": "4.20.0",
727
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.20.0.tgz",
728
-            "integrity": "sha512-psegMvP+Ik/Bg7QRJbv8w8PAytPA7Uo8fpFjXyCRHWm6Nt42L+JtoqH8eDQ5hRP7/XW2UiIriy1Z46jf0Oa1kA==",
726
+            "version": "4.21.0",
727
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.21.0.tgz",
728
+            "integrity": "sha512-s5oFkZ/hFcrlAyBTONFY1TWndfyre1wOMwU+6KCpm/iatybvrRgmZVM+vCFwxmC5ZhdlgfE0N4XorsDpi7/4XQ==",
729 729
             "cpu": [
730 730
                 "arm64"
731 731
             ],
@@ -737,9 +737,9 @@
737 737
             ]
738 738
         },
739 739
         "node_modules/@rollup/rollup-win32-ia32-msvc": {
740
-            "version": "4.20.0",
741
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.20.0.tgz",
742
-            "integrity": "sha512-GabekH3w4lgAJpVxkk7hUzUf2hICSQO0a/BLFA11/RMxQT92MabKAqyubzDZmMOC/hcJNlc+rrypzNzYl4Dx7A==",
740
+            "version": "4.21.0",
741
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.21.0.tgz",
742
+            "integrity": "sha512-G9+TEqRnAA6nbpqyUqgTiopmnfgnMkR3kMukFBDsiyy23LZvUCpiUwjTRx6ezYCjJODXrh52rBR9oXvm+Fp5wg==",
743 743
             "cpu": [
744 744
                 "ia32"
745 745
             ],
@@ -751,9 +751,9 @@
751 751
             ]
752 752
         },
753 753
         "node_modules/@rollup/rollup-win32-x64-msvc": {
754
-            "version": "4.20.0",
755
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.20.0.tgz",
756
-            "integrity": "sha512-aJ1EJSuTdGnM6qbVC4B5DSmozPTqIag9fSzXRNNo+humQLG89XpPgdt16Ia56ORD7s+H8Pmyx44uczDQ0yDzpg==",
754
+            "version": "4.21.0",
755
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.21.0.tgz",
756
+            "integrity": "sha512-2jsCDZwtQvRhejHLfZ1JY6w6kEuEtfF9nzYsZxzSlNVKDX+DpsDJ+Rbjkm74nvg2rdx0gwBS+IMdvwJuq3S9pQ==",
757 757
             "cpu": [
758 758
                 "x64"
759 759
             ],
@@ -1159,9 +1159,9 @@
1159 1159
             "license": "MIT"
1160 1160
         },
1161 1161
         "node_modules/electron-to-chromium": {
1162
-            "version": "1.5.10",
1163
-            "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.10.tgz",
1164
-            "integrity": "sha512-C3RDERDjrNW262GCRvpoer3a0Ksd66CtgDLxMHhzShQ8fhL4kwnpVXsJPAKg9xJjIROXUbLBrvtOzVAjALMIWA==",
1162
+            "version": "1.5.11",
1163
+            "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.11.tgz",
1164
+            "integrity": "sha512-R1CccCDYqndR25CaXFd6hp/u9RaaMcftMkphmvuepXr5b1vfLkRml6aWVeBhXJ7rbevHkKEMJtz8XqPf7ffmew==",
1165 1165
             "dev": true,
1166 1166
             "license": "ISC"
1167 1167
         },
@@ -2171,9 +2171,9 @@
2171 2171
             }
2172 2172
         },
2173 2173
         "node_modules/rollup": {
2174
-            "version": "4.20.0",
2175
-            "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.20.0.tgz",
2176
-            "integrity": "sha512-6rbWBChcnSGzIlXeIdNIZTopKYad8ZG8ajhl78lGRLsI2rX8IkaotQhVas2Ma+GPxJav19wrSzvRvuiv0YKzWw==",
2174
+            "version": "4.21.0",
2175
+            "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.21.0.tgz",
2176
+            "integrity": "sha512-vo+S/lfA2lMS7rZ2Qoubi6I5hwZwzXeUIctILZLbHI+laNtvhhOIon2S1JksA5UEDQ7l3vberd0fxK44lTYjbQ==",
2177 2177
             "dev": true,
2178 2178
             "license": "MIT",
2179 2179
             "dependencies": {
@@ -2187,22 +2187,22 @@
2187 2187
                 "npm": ">=8.0.0"
2188 2188
             },
2189 2189
             "optionalDependencies": {
2190
-                "@rollup/rollup-android-arm-eabi": "4.20.0",
2191
-                "@rollup/rollup-android-arm64": "4.20.0",
2192
-                "@rollup/rollup-darwin-arm64": "4.20.0",
2193
-                "@rollup/rollup-darwin-x64": "4.20.0",
2194
-                "@rollup/rollup-linux-arm-gnueabihf": "4.20.0",
2195
-                "@rollup/rollup-linux-arm-musleabihf": "4.20.0",
2196
-                "@rollup/rollup-linux-arm64-gnu": "4.20.0",
2197
-                "@rollup/rollup-linux-arm64-musl": "4.20.0",
2198
-                "@rollup/rollup-linux-powerpc64le-gnu": "4.20.0",
2199
-                "@rollup/rollup-linux-riscv64-gnu": "4.20.0",
2200
-                "@rollup/rollup-linux-s390x-gnu": "4.20.0",
2201
-                "@rollup/rollup-linux-x64-gnu": "4.20.0",
2202
-                "@rollup/rollup-linux-x64-musl": "4.20.0",
2203
-                "@rollup/rollup-win32-arm64-msvc": "4.20.0",
2204
-                "@rollup/rollup-win32-ia32-msvc": "4.20.0",
2205
-                "@rollup/rollup-win32-x64-msvc": "4.20.0",
2190
+                "@rollup/rollup-android-arm-eabi": "4.21.0",
2191
+                "@rollup/rollup-android-arm64": "4.21.0",
2192
+                "@rollup/rollup-darwin-arm64": "4.21.0",
2193
+                "@rollup/rollup-darwin-x64": "4.21.0",
2194
+                "@rollup/rollup-linux-arm-gnueabihf": "4.21.0",
2195
+                "@rollup/rollup-linux-arm-musleabihf": "4.21.0",
2196
+                "@rollup/rollup-linux-arm64-gnu": "4.21.0",
2197
+                "@rollup/rollup-linux-arm64-musl": "4.21.0",
2198
+                "@rollup/rollup-linux-powerpc64le-gnu": "4.21.0",
2199
+                "@rollup/rollup-linux-riscv64-gnu": "4.21.0",
2200
+                "@rollup/rollup-linux-s390x-gnu": "4.21.0",
2201
+                "@rollup/rollup-linux-x64-gnu": "4.21.0",
2202
+                "@rollup/rollup-linux-x64-musl": "4.21.0",
2203
+                "@rollup/rollup-win32-arm64-msvc": "4.21.0",
2204
+                "@rollup/rollup-win32-ia32-msvc": "4.21.0",
2205
+                "@rollup/rollup-win32-x64-msvc": "4.21.0",
2206 2206
                 "fsevents": "~2.3.2"
2207 2207
             }
2208 2208
         },

+ 3
- 3
resources/views/components/company/reports/report-pdf.blade.php Просмотреть файл

@@ -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>
@@ -139,7 +139,7 @@
139 139
         </tr>
140 140
         </tbody>
141 141
     @endforeach
142
-    <tfoot>
142
+    <tbody>
143 143
     <tr class="table-footer-row">
144 144
         @foreach ($report->getOverallTotals() as $index => $total)
145 145
             <td class="{{ $report->getAlignmentClass($index) }}">
@@ -147,7 +147,7 @@
147 147
             </td>
148 148
         @endforeach
149 149
     </tr>
150
-    </tfoot>
150
+    </tbody>
151 151
 </table>
152 152
 </body>
153 153
 </html>

+ 10
- 5
resources/views/filament/company/pages/reports/account-transactions.blade.php Просмотреть файл

@@ -1,19 +1,24 @@
1 1
 <x-filament-panels::page>
2
-    <x-filament-tables::container>
3
-        <form wire:submit="loadReportData" class="p-6">
2
+    <x-filament::section>
3
+        <form wire:submit="loadReportData">
4 4
             {{ $this->form }}
5 5
         </form>
6
-        <div class="relative divide-y divide-gray-200 overflow-x-auto dark:divide-white/10 dark:border-t-white/10 min-h-64">
6
+    </x-filament::section>
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">
7 12
             <div wire:init="loadReportData" class="flex items-center justify-center w-full h-full absolute">
8 13
                 <div wire:loading wire:target="loadReportData">
9
-                    <x-filament::loading-indicator class="p-6 text-primary-700 dark:text-primary-300" />
14
+                    <x-filament::loading-indicator class="p-6 text-primary-700 dark:text-primary-300"/>
10 15
                 </div>
11 16
             </div>
12 17
 
13 18
             @if($this->reportLoaded)
14 19
                 <div wire:loading.remove wire:target="loadReportData">
15 20
                     @if($this->report && !$this->tableHasEmptyState())
16
-                        <x-company.tables.reports.account-transactions :report="$this->report" />
21
+                        <x-company.tables.reports.account-transactions :report="$this->report"/>
17 22
                     @else
18 23
                         <x-filament-tables::empty-state
19 24
                             :actions="$this->getEmptyStateActions()"

+ 30
- 21
resources/views/filament/company/pages/reports/detailed-report.blade.php Просмотреть файл

@@ -1,33 +1,42 @@
1 1
 <x-filament-panels::page>
2
-    <x-filament-tables::container>
3
-        <form wire:submit="loadReportData" class="p-6">
4
-            <div class="flex flex-col md:flex-row items-start md:items-center justify-center gap-4 md:gap-12">
5
-                {{ $this->form }}
6
-                @if($this->hasToggleableColumns())
7
-                    <x-filament-tables::column-toggle.dropdown
8
-                        :form="$this->toggleTableColumnForm"
9
-                        :trigger-action="$this->toggleColumnsAction"
10
-                    />
11
-                @endif
12
-                <x-filament::button type="submit" wire:target="loadReportData" class="flex-shrink-0">
13
-                    Update Report
14
-                </x-filament::button>
2
+    <x-filament::section>
3
+        <form wire:submit="loadReportData">
4
+            <div class="flex flex-col md:flex-row items-start md:items-center justify-between gap-4 md:gap-8">
5
+                <div class="flex-grow">
6
+                    {{ $this->form }}
7
+                </div>
8
+
9
+                <div class="flex flex-col md:flex-row items-start md:items-center gap-4 md:gap-8 flex-shrink-0">
10
+                    @if($this->hasToggleableColumns())
11
+                        <x-filament-tables::column-toggle.dropdown
12
+                            :form="$this->toggleTableColumnForm"
13
+                            :trigger-action="$this->toggleColumnsAction"
14
+                        />
15
+                    @endif
16
+                    <x-filament::button type="submit" wire:target="loadReportData" class="flex-shrink-0">
17
+                        Update Report
18
+                    </x-filament::button>
19
+                </div>
15 20
             </div>
16 21
         </form>
17
-        <div wire:init="loadReportData"
18
-             class="relative divide-y divide-gray-200 overflow-x-auto dark:divide-white/10 dark:border-t-white/10 min-h-64">
22
+    </x-filament::section>
23
+
24
+    <x-filament-tables::container>
25
+        <div class="es-table__header-ctn"></div>
26
+        <div
27
+            class="relative divide-y divide-gray-200 overflow-x-auto dark:divide-white/10 dark:border-t-white/10 min-h-64">
28
+            <div wire:init="loadReportData" class="flex items-center justify-center w-full h-full absolute">
29
+                <div wire:loading wire:target="loadReportData">
30
+                    <x-filament::loading-indicator class="p-6 text-primary-700 dark:text-primary-300"/>
31
+                </div>
32
+            </div>
33
+
19 34
             @if($this->reportLoaded)
20 35
                 <div wire:loading.remove wire:target="loadReportData">
21 36
                     @if($this->report)
22 37
                         <x-company.tables.reports.detailed-report :report="$this->report"/>
23 38
                     @endif
24 39
                 </div>
25
-            @else
26
-                <div class="absolute inset-0 flex items-center justify-center">
27
-                    <div wire:loading wire:target="loadReportData">
28
-                        <x-filament::loading-indicator class="p-6 text-primary-700 dark:text-primary-300"/>
29
-                    </div>
30
-                </div>
31 40
             @endif
32 41
         </div>
33 42
         <div class="es-table__footer-ctn border-t border-gray-200"></div>

+ 84
- 0
resources/views/filament/company/pages/reports/income-statement.blade.php Просмотреть файл

@@ -0,0 +1,84 @@
1
+<x-filament-panels::page>
2
+    <x-filament::section>
3
+        <form wire:submit="loadReportData">
4
+            <div class="flex flex-col md:flex-row items-start md:items-center justify-between gap-4 md:gap-8">
5
+                <!-- Form Container -->
6
+                <div class="flex-grow">
7
+                    {{ $this->form }}
8
+                </div>
9
+
10
+                <!-- Grouping Button and Column Toggle -->
11
+                <div class="flex flex-col md:flex-row items-start md:items-center gap-4 md:gap-8 flex-shrink-0">
12
+                    @if($this->hasToggleableColumns())
13
+                        <x-filament-tables::column-toggle.dropdown
14
+                            :form="$this->toggleTableColumnForm"
15
+                            :trigger-action="$this->toggleColumnsAction"
16
+                        />
17
+                    @endif
18
+                    <x-filament::button type="submit" wire:target="loadReportData" class="flex-shrink-0">
19
+                        Update Report
20
+                    </x-filament::button>
21
+                </div>
22
+            </div>
23
+        </form>
24
+    </x-filament::section>
25
+
26
+
27
+    <x-filament::section>
28
+        <!-- Summary Section -->
29
+        @if($this->reportLoaded)
30
+            <div
31
+                class="flex flex-col md:flex-row items-center md:items-end text-center justify-center gap-4 md:gap-8">
32
+                @foreach($this->report->getSummary() as $summary)
33
+                    <div class="text-sm">
34
+                        <div class="text-gray-600 font-medium mb-2">{{ $summary['label'] }}</div>
35
+
36
+                        @php
37
+                            $isNetEarnings = $summary['label'] === 'Net Earnings';
38
+                            $isPositive = money($summary['value'], \App\Utilities\Currency\CurrencyAccessor::getDefaultCurrency())->isPositive();
39
+                        @endphp
40
+
41
+                        <strong
42
+                            @class([
43
+                                'text-lg',
44
+                                'text-green-700' => $isNetEarnings && $isPositive,
45
+                                'text-danger-700' => $isNetEarnings && ! $isPositive,
46
+                            ])
47
+                        >
48
+                            {{ $summary['value'] }}
49
+                        </strong>
50
+                    </div>
51
+
52
+                    @if(! $loop->last)
53
+                        <div class="flex items-center justify-center px-2">
54
+                            <strong class="text-lg">
55
+                                {{ $loop->remaining === 1 ? '=' : '-' }}
56
+                            </strong>
57
+                        </div>
58
+                    @endif
59
+                @endforeach
60
+            </div>
61
+        @endif
62
+    </x-filament::section>
63
+
64
+    <x-filament-tables::container>
65
+        <div class="es-table__header-ctn"></div>
66
+        <div wire:init="loadReportData"
67
+             class="relative divide-y divide-gray-200 overflow-x-auto dark:divide-white/10 dark:border-t-white/10 min-h-64">
68
+            @if($this->reportLoaded)
69
+                <div wire:loading.remove wire:target="loadReportData">
70
+                    @if($this->report)
71
+                        <x-company.tables.reports.detailed-report :report="$this->report"/>
72
+                    @endif
73
+                </div>
74
+            @else
75
+                <div class="absolute inset-0 flex items-center justify-center">
76
+                    <div wire:loading wire:target="loadReportData">
77
+                        <x-filament::loading-indicator class="p-6 text-primary-700 dark:text-primary-300"/>
78
+                    </div>
79
+                </div>
80
+            @endif
81
+        </div>
82
+        <div class="es-table__footer-ctn border-t border-gray-200"></div>
83
+    </x-filament-tables::container>
84
+</x-filament-panels::page>

Загрузка…
Отмена
Сохранить