diff --git a/app/Contracts/ExportableReport.php b/app/Contracts/ExportableReport.php index 8f098742..40148c3c 100644 --- a/app/Contracts/ExportableReport.php +++ b/app/Contracts/ExportableReport.php @@ -3,6 +3,7 @@ namespace App\Contracts; use App\DTO\ReportCategoryDTO; +use App\Support\Column; interface ExportableReport { @@ -17,7 +18,12 @@ public function getCategories(): array; public function getOverallTotals(): array; + /** + * @return Column[] + */ public function getColumns(): array; public function getPdfView(): string; + + public function getAlignmentClass(string $columnName): string; } diff --git a/app/Contracts/HasSummaryReport.php b/app/Contracts/HasSummaryReport.php new file mode 100644 index 00000000..810cc1eb --- /dev/null +++ b/app/Contracts/HasSummaryReport.php @@ -0,0 +1,23 @@ + 'Current Assets', + self::NonCurrentAsset => 'Non-Current Assets', + self::ContraAsset => 'Contra Assets', + self::CurrentLiability => 'Current Liabilities', + self::NonCurrentLiability => 'Non-Current Liabilities', + self::ContraLiability => 'Contra Liabilities', + self::Equity => 'Equity', + self::ContraEquity => 'Contra Equity', + self::OperatingRevenue => 'Operating Revenue', + self::NonOperatingRevenue => 'Non-Operating Revenue', + self::ContraRevenue => 'Contra Revenue', + self::UncategorizedRevenue => 'Uncategorized Revenue', + self::OperatingExpense => 'Operating Expenses', + self::NonOperatingExpense => 'Non-Operating Expenses', + self::ContraExpense => 'Contra Expenses', + self::UncategorizedExpense => 'Uncategorized Expenses', + }; + } + public function getCategory(): AccountCategory { return match ($this) { diff --git a/app/Filament/Company/Clusters/Settings/Resources/CurrencyResource.php b/app/Filament/Company/Clusters/Settings/Resources/CurrencyResource.php index a780264b..7d73a8e3 100644 --- a/app/Filament/Company/Clusters/Settings/Resources/CurrencyResource.php +++ b/app/Filament/Company/Clusters/Settings/Resources/CurrencyResource.php @@ -185,6 +185,9 @@ public static function table(Table $table): Table $action->cancel(); } } + }) + ->hidden(function (Table $table) { + return $table->getAllSelectableRecordsCount() === 0; }), ]), ]) diff --git a/app/Filament/Company/Pages/Accounting/AccountChart.php b/app/Filament/Company/Pages/Accounting/AccountChart.php index c2ed8120..beec5833 100644 --- a/app/Filament/Company/Pages/Accounting/AccountChart.php +++ b/app/Filament/Company/Pages/Accounting/AccountChart.php @@ -33,12 +33,7 @@ class AccountChart extends Page protected static string $view = 'filament.company.pages.accounting.chart'; #[Url] - public ?string $activeTab = null; - - public function mount(): void - { - $this->activeTab = $this->activeTab ?? AccountCategory::Asset->value; - } + public ?string $activeTab = AccountCategory::Asset->value; protected function configureAction(Action $action): void { diff --git a/app/Filament/Company/Pages/Accounting/Transactions.php b/app/Filament/Company/Pages/Accounting/Transactions.php index 1c890a7a..6030272c 100644 --- a/app/Filament/Company/Pages/Accounting/Transactions.php +++ b/app/Filament/Company/Pages/Accounting/Transactions.php @@ -459,7 +459,7 @@ protected function buildTransactionAction(string $name, string $label, Transacti protected function getFormDefaultsForType(TransactionType $type): array { $commonDefaults = [ - 'posted_at' => now()->format('Y-m-d'), + 'posted_at' => today(), ]; return match ($type) { diff --git a/app/Filament/Company/Pages/Reports.php b/app/Filament/Company/Pages/Reports.php index e146759d..44146168 100644 --- a/app/Filament/Company/Pages/Reports.php +++ b/app/Filament/Company/Pages/Reports.php @@ -4,6 +4,7 @@ use App\Filament\Company\Pages\Reports\AccountBalances; use App\Filament\Company\Pages\Reports\AccountTransactions; +use App\Filament\Company\Pages\Reports\BalanceSheet; use App\Filament\Company\Pages\Reports\IncomeStatement; use App\Filament\Company\Pages\Reports\TrialBalance; use App\Infolists\Components\ReportEntry; @@ -41,7 +42,7 @@ public function reportsInfolist(Infolist $infolist): Infolist ->description('Snapshot of assets, liabilities, and equity at a specific point in time.') ->icon('heroicon-o-clipboard-document-list') ->iconColor(Color::Emerald) - ->url('#'), + ->url(BalanceSheet::getUrl()), ReportEntry::make('cash_flow_statement') ->hiddenLabel() ->heading('Cash Flow Statement') diff --git a/app/Filament/Company/Pages/Reports/AccountTransactions.php b/app/Filament/Company/Pages/Reports/AccountTransactions.php index 8f550e81..c1108384 100644 --- a/app/Filament/Company/Pages/Reports/AccountTransactions.php +++ b/app/Filament/Company/Pages/Reports/AccountTransactions.php @@ -71,7 +71,7 @@ public function getTable(): array ->label('Credit') ->alignment(Alignment::Right), Column::make('balance') - ->label('Balance') + ->label('Running Balance') ->alignment(Alignment::Right), ]; } diff --git a/app/Filament/Company/Pages/Reports/BalanceSheet.php b/app/Filament/Company/Pages/Reports/BalanceSheet.php new file mode 100644 index 00000000..839b3cde --- /dev/null +++ b/app/Filament/Company/Pages/Reports/BalanceSheet.php @@ -0,0 +1,87 @@ +reportService = $reportService; + $this->exportService = $exportService; + } + + public function getTable(): array + { + return [ + Column::make('account_code') + ->label('Account Code') + ->toggleable() + ->alignment(Alignment::Center), + Column::make('account_name') + ->label('Account') + ->alignment(Alignment::Left), + Column::make('ending_balance') + ->label('Amount') + ->alignment(Alignment::Right), + ]; + } + + public function filtersForm(Form $form): Form + { + return $form + ->inlineLabel() + ->columns(3) + ->schema([ + DateRangeSelect::make('dateRange') + ->label('As of') + ->selectablePlaceholder(false) + ->endDateField('asOfDate'), + $this->getAsOfDateFormComponent() + ->hiddenLabel() + ->extraFieldWrapperAttributes([]), + ]); + } + + protected function buildReport(array $columns): ReportDTO + { + return $this->reportService->buildBalanceSheetReport($this->getFormattedAsOfDate(), $columns); + } + + protected function getTransformer(ReportDTO $reportDTO): ExportableReport + { + return new BalanceSheetReportTransformer($reportDTO); + } + + public function exportCSV(): StreamedResponse + { + return $this->exportService->exportToCsv($this->company, $this->report, endDate: $this->getFilterState('asOfDate')); + } + + public function exportPDF(): StreamedResponse + { + return $this->exportService->exportToPdf($this->company, $this->report, endDate: $this->getFilterState('asOfDate')); + } +} diff --git a/app/Filament/Company/Pages/Reports/IncomeStatement.php b/app/Filament/Company/Pages/Reports/IncomeStatement.php index b327c86b..2547d8fb 100644 --- a/app/Filament/Company/Pages/Reports/IncomeStatement.php +++ b/app/Filament/Company/Pages/Reports/IncomeStatement.php @@ -11,6 +11,7 @@ use Filament\Forms\Form; use Filament\Support\Enums\Alignment; use Guava\FilamentClusters\Forms\Cluster; +use Livewire\Attributes\Url; use Symfony\Component\HttpFoundation\StreamedResponse; class IncomeStatement extends BaseReportPage @@ -25,6 +26,9 @@ class IncomeStatement extends BaseReportPage protected ExportService $exportService; + #[Url] + public ?string $activeTab = 'summary'; + public function boot(ReportService $reportService, ExportService $exportService): void { $this->reportService = $reportService; diff --git a/app/Filament/Company/Resources/Banking/AccountResource.php b/app/Filament/Company/Resources/Banking/AccountResource.php index 5a7a0abb..3a73551f 100644 --- a/app/Filament/Company/Resources/Banking/AccountResource.php +++ b/app/Filament/Company/Resources/Banking/AccountResource.php @@ -143,7 +143,10 @@ public static function table(Table $table): Table Tables\Actions\BulkActionGroup::make([ Tables\Actions\DeleteBulkAction::make() ->requiresConfirmation() - ->modalDescription('Are you sure you want to delete the selected accounts? All transactions associated with the accounts will be deleted as well.'), + ->modalDescription('Are you sure you want to delete the selected accounts? All transactions associated with the accounts will be deleted as well.') + ->hidden(function (Table $table) { + return $table->getAllSelectableRecordsCount() === 0; + }), ]), ]) ->checkIfRecordIsSelectableUsing(static function (BankAccount $record) { diff --git a/app/Filament/Company/Resources/Banking/AccountResource/Pages/EditAccount.php b/app/Filament/Company/Resources/Banking/AccountResource/Pages/EditAccount.php index 8e6d5f72..3104ddef 100644 --- a/app/Filament/Company/Resources/Banking/AccountResource/Pages/EditAccount.php +++ b/app/Filament/Company/Resources/Banking/AccountResource/Pages/EditAccount.php @@ -3,6 +3,7 @@ namespace App\Filament\Company\Resources\Banking\AccountResource\Pages; use App\Filament\Company\Resources\Banking\AccountResource; +use Filament\Actions; use Filament\Resources\Pages\EditRecord; class EditAccount extends EditRecord @@ -12,7 +13,7 @@ class EditAccount extends EditRecord protected function getHeaderActions(): array { return [ - // + Actions\DeleteAction::make(), ]; } diff --git a/app/Listeners/UpdateAccountBalances.php b/app/Listeners/UpdateAccountBalances.php index cfba3508..9ddbda7c 100644 --- a/app/Listeners/UpdateAccountBalances.php +++ b/app/Listeners/UpdateAccountBalances.php @@ -58,7 +58,7 @@ public function handle(CurrencyRateChanged $event): void 'type' => $transactionType, 'amount' => $formattedSimpleDifference, 'payment_channel' => 'other', - 'posted_at' => now(), + 'posted_at' => today(), 'description' => $description, 'pending' => false, 'reviewed' => false, diff --git a/app/Models/Accounting/Transaction.php b/app/Models/Accounting/Transaction.php index 4fef2691..dbc55b7b 100644 --- a/app/Models/Accounting/Transaction.php +++ b/app/Models/Accounting/Transaction.php @@ -48,7 +48,7 @@ class Transaction extends Model 'amount' => TransactionAmountCast::class, 'pending' => 'boolean', 'reviewed' => 'boolean', - 'posted_at' => 'datetime', + 'posted_at' => 'date', ]; public function account(): BelongsTo diff --git a/app/Services/AccountService.php b/app/Services/AccountService.php index 1ca16828..3dadb149 100644 --- a/app/Services/AccountService.php +++ b/app/Services/AccountService.php @@ -138,6 +138,7 @@ public function getAccountBalances(string $startDate, string $endDate, array $ac 'accounts.id', 'accounts.name', 'accounts.category', + 'accounts.type', 'accounts.subtype_id', 'accounts.currency_code', 'accounts.code', @@ -227,6 +228,6 @@ public function getEarliestTransactionDate(): string { $earliestDate = Transaction::min('posted_at'); - return $earliestDate ?? now()->toDateTimeString(); + return $earliestDate ?? today()->toDateTimeString(); } } diff --git a/app/Services/ExportService.php b/app/Services/ExportService.php index 0ea372ba..9bc6a268 100644 --- a/app/Services/ExportService.php +++ b/app/Services/ExportService.php @@ -4,10 +4,13 @@ use App\Contracts\ExportableReport; use App\Models\Company; -use App\Support\Column; use Barryvdh\Snappy\Facades\SnappyPdf; use Carbon\Exceptions\InvalidFormatException; use Illuminate\Support\Carbon; +use League\Csv\Bom; +use League\Csv\CannotInsertRecord; +use League\Csv\Exception; +use League\Csv\Writer; use Symfony\Component\HttpFoundation\StreamedResponse; class ExportService @@ -33,7 +36,8 @@ public function exportToCsv(Company $company, ExportableReport $report, ?string ]; $callback = function () use ($startDate, $endDate, $report, $company) { - $file = fopen('php://output', 'wb'); + $csv = Writer::createFromStream(fopen('php://output', 'wb')); + $csv->setOutputBOM(Bom::Utf8); if ($startDate && $endDate) { $defaultStartDateFormat = Carbon::parse($startDate)->toDefaultDateFormat(); @@ -43,61 +47,34 @@ public function exportToCsv(Company $company, ExportableReport $report, ?string $dateLabel = 'As of ' . Carbon::parse($endDate)->toDefaultDateFormat(); } - fputcsv($file, [$report->getTitle()]); - fputcsv($file, [$company->name]); - fputcsv($file, [$dateLabel]); - fputcsv($file, []); + $csv->insertOne([$report->getTitle()]); + $csv->insertOne([$company->name]); + $csv->insertOne([$dateLabel]); + $csv->insertOne([]); - fputcsv($file, $report->getHeaders()); + $csv->insertOne($report->getHeaders()); foreach ($report->getCategories() as $category) { - if (isset($category->header[0]) && is_array($category->header[0])) { - foreach ($category->header as $headerRow) { - fputcsv($file, $headerRow); - } - } else { - fputcsv($file, $category->header); - } + $this->writeDataRowsToCsv($csv, $category->header, $category->data, $report->getColumns()); - foreach ($category->data as $accountRow) { - $row = []; - $columns = $report->getColumns(); - - /** - * @var Column $column - */ - foreach ($columns as $index => $column) { - $cell = $accountRow[$index] ?? ''; - - if ($column->isDate()) { - try { - $row[] = Carbon::parse($cell)->toDateString(); - } catch (InvalidFormatException) { - $row[] = $cell; - } - } elseif (is_array($cell)) { - // Handle array cells by extracting 'name' or 'description' - $row[] = $cell['name'] ?? $cell['description'] ?? ''; - } else { - $row[] = $cell; - } - } + foreach ($category->types ?? [] as $type) { + $this->writeDataRowsToCsv($csv, $type->header, $type->data, $report->getColumns()); - fputcsv($file, $row); + if (filled($type->summary)) { + $csv->insertOne($type->summary); + } } if (filled($category->summary)) { - fputcsv($file, $category->summary); + $csv->insertOne($category->summary); } - fputcsv($file, []); // Empty row for spacing + $csv->insertOne([]); } if (filled($report->getOverallTotals())) { - fputcsv($file, $report->getOverallTotals()); + $csv->insertOne($report->getOverallTotals()); } - - fclose($file); }; return response()->streamDownload($callback, $filename, $headers); @@ -129,4 +106,43 @@ public function exportToPdf(Company $company, ExportableReport $report, ?string echo $pdf->inline(); }, $filename); } + + /** + * @throws CannotInsertRecord + * @throws Exception + */ + protected function writeDataRowsToCsv(Writer $csv, array $header, array $data, array $columns): void + { + if (isset($header[0]) && is_array($header[0])) { + foreach ($header as $headerRow) { + $csv->insertOne($headerRow); + } + } else { + $csv->insertOne($header); + } + + // Output data rows + foreach ($data as $rowData) { + $row = []; + + foreach ($columns as $column) { + $columnName = $column->getName(); + $cell = $rowData[$columnName] ?? ''; + + if ($column->isDate()) { + try { + $row[] = Carbon::parse($cell)->toDateString(); + } catch (InvalidFormatException) { + $row[] = $cell; + } + } elseif (is_array($cell)) { + $row[] = $cell['name'] ?? $cell['description'] ?? ''; + } else { + $row[] = $cell; + } + } + + $csv->insertOne($row); + } + } } diff --git a/app/Services/ReportService.php b/app/Services/ReportService.php index 710a3d87..ad5cce34 100644 --- a/app/Services/ReportService.php +++ b/app/Services/ReportService.php @@ -6,8 +6,10 @@ use App\DTO\AccountCategoryDTO; use App\DTO\AccountDTO; use App\DTO\AccountTransactionDTO; +use App\DTO\AccountTypeDTO; use App\DTO\ReportDTO; use App\Enums\Accounting\AccountCategory; +use App\Enums\Accounting\AccountType; use App\Models\Accounting\Account; use App\Support\Column; use App\Utilities\Currency\CurrencyAccessor; @@ -86,8 +88,8 @@ public function buildAccountBalanceReport(string $startDate, string $endDate, ar $formattedCategorySummaryBalances = $this->formatBalances($categorySummaryBalances); $accountCategories[$category->getPluralLabel()] = new AccountCategoryDTO( - $categoryAccounts, - $formattedCategorySummaryBalances, + accounts: $categoryAccounts, + summary: $formattedCategorySummaryBalances, ); } @@ -305,8 +307,8 @@ public function buildTrialBalanceReport(string $trialBalanceType, string $asOfDa $formattedCategorySummaryBalances = $this->formatBalances($categorySummaryBalances); $accountCategories[$category->getPluralLabel()] = new AccountCategoryDTO( - $categoryAccounts, - $formattedCategorySummaryBalances, + accounts: $categoryAccounts, + summary: $formattedCategorySummaryBalances, ); } @@ -417,8 +419,8 @@ public function buildIncomeStatementReport(string $startDate, string $endDate, a } $accountCategories[$label] = new AccountCategoryDTO( - $categoryAccounts, - $this->formatBalances(['net_movement' => $netMovement]) + accounts: $categoryAccounts, + summary: $this->formatBalances(['net_movement' => $netMovement]) ); } @@ -429,4 +431,108 @@ public function buildIncomeStatementReport(string $startDate, string $endDate, a return new ReportDTO($accountCategories, $formattedReportTotalBalances, $columns); } + + public function buildBalanceSheetReport(string $asOfDate, array $columns = []): ReportDTO + { + $asOfDateCarbon = Carbon::parse($asOfDate); + $startDateCarbon = Carbon::parse($this->accountService->getEarliestTransactionDate()); + + $orderedCategories = array_filter(AccountCategory::getOrderedCategories(), fn (AccountCategory $category) => $category->isReal()); + + $accounts = $this->accountService->getAccountBalances($startDateCarbon->toDateTimeString(), $asOfDateCarbon->toDateTimeString()) + ->whereIn('category', $orderedCategories) + ->orderByRaw('LENGTH(code), code') + ->get(); + + $accountCategories = []; + $reportTotalBalances = [ + 'assets' => 0, + 'liabilities' => 0, + 'equity' => 0, + ]; + + foreach ($orderedCategories as $category) { + $categorySummaryBalances = ['ending_balance' => 0]; + + $categoryAccountsByType = []; + $categoryAccounts = []; + $subCategoryTotals = []; + + /** @var Account $account */ + foreach ($accounts as $account) { + if ($account->type->getCategory() === $category) { + $accountBalances = $this->calculateAccountBalances($account, $category); + $endingBalance = $accountBalances['ending_balance'] ?? $accountBalances['net_movement']; + + $categorySummaryBalances['ending_balance'] += $endingBalance; + + $formattedAccountBalances = $this->formatBalances($accountBalances); + + $accountDTO = new AccountDTO( + $account->name, + $account->code, + $account->id, + $formattedAccountBalances, + startDate: $startDateCarbon->toDateString(), + endDate: $asOfDateCarbon->toDateString(), + ); + + if ($category === AccountCategory::Equity && $account->type === AccountType::Equity) { + $categoryAccounts[] = $accountDTO; + } else { + $accountType = $account->type->getPluralLabel(); + $categoryAccountsByType[$accountType][] = $accountDTO; + $subCategoryTotals[$accountType] = ($subCategoryTotals[$accountType] ?? 0) + $endingBalance; + } + } + } + + if ($category === AccountCategory::Equity) { + $retainedEarningsAmount = $this->calculateRetainedEarnings($startDateCarbon->toDateTimeString(), $asOfDateCarbon->toDateTimeString())->getAmount(); + + $categorySummaryBalances['ending_balance'] += $retainedEarningsAmount; + + $retainedEarningsDTO = new AccountDTO( + 'Retained Earnings', + 'RE', + null, + $this->formatBalances(['ending_balance' => $retainedEarningsAmount]), + startDate: $startDateCarbon->toDateString(), + endDate: $asOfDateCarbon->toDateString(), + ); + + $categoryAccounts[] = $retainedEarningsDTO; + } + + $subCategories = []; + foreach ($categoryAccountsByType as $accountType => $accountsInType) { + $subCategorySummary = $this->formatBalances([ + 'ending_balance' => $subCategoryTotals[$accountType] ?? 0, + ]); + + $subCategories[$accountType] = new AccountTypeDTO( + accounts: $accountsInType, + summary: $subCategorySummary + ); + } + + $reportTotalBalances[match ($category) { + AccountCategory::Asset => 'assets', + AccountCategory::Liability => 'liabilities', + AccountCategory::Equity => 'equity', + }] += $categorySummaryBalances['ending_balance']; + + $accountCategories[$category->getPluralLabel()] = new AccountCategoryDTO( + accounts: $categoryAccounts, + types: $subCategories, + summary: $this->formatBalances($categorySummaryBalances), + ); + } + + $netAssets = $reportTotalBalances['assets'] - $reportTotalBalances['liabilities']; + + $formattedReportTotalBalances = $this->formatBalances(['ending_balance' => $netAssets]); + + return new ReportDTO($accountCategories, $formattedReportTotalBalances, $columns); + } } diff --git a/app/Transformers/AccountBalanceReportTransformer.php b/app/Transformers/AccountBalanceReportTransformer.php index e149cbbb..f00c46a9 100644 --- a/app/Transformers/AccountBalanceReportTransformer.php +++ b/app/Transformers/AccountBalanceReportTransformer.php @@ -4,7 +4,6 @@ use App\DTO\AccountDTO; use App\DTO\ReportCategoryDTO; -use App\Support\Column; class AccountBalanceReportTransformer extends BaseReportTransformer { @@ -13,11 +12,6 @@ public function getTitle(): string return 'Account Balances'; } - public function getHeaders(): array - { - return array_map(fn (Column $column) => $column->getLabel(), $this->getColumns()); - } - /** * @return ReportCategoryDTO[] */ @@ -26,22 +20,17 @@ public function getCategories(): array $categories = []; foreach ($this->report->categories as $accountCategoryName => $accountCategory) { - // Initialize header with empty strings $header = []; - foreach ($this->getColumns() as $index => $column) { - if ($column->getName() === 'account_name') { - $header[$index] = $accountCategoryName; - } else { - $header[$index] = ''; - } + foreach ($this->getColumns() as $column) { + $header[$column->getName()] = $column->getName() === 'account_name' ? $accountCategoryName : ''; } $data = array_map(function (AccountDTO $account) { $row = []; foreach ($this->getColumns() as $column) { - $row[] = match ($column->getName()) { + $row[$column->getName()] = match ($column->getName()) { 'account_code' => $account->accountCode, 'account_name' => [ 'name' => $account->accountName, @@ -64,7 +53,7 @@ public function getCategories(): array $summary = []; foreach ($this->getColumns() as $column) { - $summary[] = match ($column->getName()) { + $summary[$column->getName()] = match ($column->getName()) { 'account_name' => 'Total ' . $accountCategoryName, 'starting_balance' => $accountCategory->summary->startingBalance ?? '', 'debit_balance' => $accountCategory->summary->debitBalance, @@ -90,7 +79,7 @@ public function getOverallTotals(): array $totals = []; foreach ($this->getColumns() as $column) { - $totals[] = match ($column->getName()) { + $totals[$column->getName()] = match ($column->getName()) { 'account_name' => 'Total for all accounts', 'debit_balance' => $this->report->overallTotal->debitBalance, 'credit_balance' => $this->report->overallTotal->creditBalance, diff --git a/app/Transformers/AccountTransactionReportTransformer.php b/app/Transformers/AccountTransactionReportTransformer.php index 27ce858c..05471f54 100644 --- a/app/Transformers/AccountTransactionReportTransformer.php +++ b/app/Transformers/AccountTransactionReportTransformer.php @@ -4,7 +4,6 @@ use App\DTO\AccountTransactionDTO; use App\DTO\ReportCategoryDTO; -use App\Support\Column; class AccountTransactionReportTransformer extends BaseReportTransformer { @@ -18,11 +17,6 @@ public function getTitle(): string return 'Account Transactions'; } - public function getHeaders(): array - { - return array_map(fn (Column $column) => $column->getLabel(), $this->getColumns()); - } - /** * @return ReportCategoryDTO[] */ @@ -31,17 +25,12 @@ public function getCategories(): array $categories = []; foreach ($this->report->categories as $categoryData) { - // Initialize header with account and category information - - $header = [ - array_fill(0, count($this->getColumns()), ''), - array_fill(0, count($this->getColumns()), ''), - ]; + $header = []; - foreach ($this->getColumns() as $index => $column) { + foreach ($this->getColumns() as $column) { if ($column->getName() === 'date') { - $header[0][$index] = $categoryData['category']; - $header[1][$index] = $categoryData['under']; + $header[0][$column->getName()] = $categoryData['category']; + $header[1][$column->getName()] = $categoryData['under']; } } @@ -50,7 +39,7 @@ public function getCategories(): array $row = []; foreach ($this->getColumns() as $column) { - $row[] = match ($column->getName()) { + $row[$column->getName()] = match ($column->getName()) { 'date' => $transaction->date, 'description' => [ 'id' => $transaction->id, diff --git a/app/Transformers/BalanceSheetReportTransformer.php b/app/Transformers/BalanceSheetReportTransformer.php new file mode 100644 index 00000000..c14adf98 --- /dev/null +++ b/app/Transformers/BalanceSheetReportTransformer.php @@ -0,0 +1,261 @@ +calculateTotals(); + } + + public function getTitle(): string + { + return 'Balance Sheet'; + } + + public function calculateTotals(): void + { + foreach ($this->report->categories as $accountCategoryName => $accountCategory) { + match ($accountCategoryName) { + 'Assets' => $this->totalAssets = $accountCategory->summary->endingBalance ?? '', + 'Liabilities' => $this->totalLiabilities = $accountCategory->summary->endingBalance ?? '', + 'Equity' => $this->totalEquity = $accountCategory->summary->endingBalance ?? '', + }; + } + } + + public function getCategories(): array + { + $categories = []; + + foreach ($this->report->categories as $accountCategoryName => $accountCategory) { + // Header for the main category + $header = []; + + foreach ($this->getColumns() as $column) { + $header[$column->getName()] = $column->getName() === 'account_name' ? $accountCategoryName : ''; + } + + // Category-level summary + $categorySummary = []; + foreach ($this->getColumns() as $column) { + $categorySummary[$column->getName()] = match ($column->getName()) { + 'account_name' => 'Total ' . $accountCategoryName, + 'ending_balance' => $accountCategory->summary->endingBalance ?? '', + default => '', + }; + } + + // Accounts directly under the main category + $data = array_map(function (AccountDTO $account) { + $row = []; + + foreach ($this->getColumns() as $column) { + $row[$column->getName()] = match ($column->getName()) { + 'account_code' => $account->accountCode, + 'account_name' => [ + 'name' => $account->accountName, + 'id' => $account->accountId ?? null, + 'start_date' => $account->startDate, + 'end_date' => $account->endDate, + ], + 'ending_balance' => $account->balance->endingBalance ?? '', + default => '', + }; + } + + return $row; + }, $accountCategory->accounts ?? []); + + // Subcategories (types) under the main category + $types = []; + foreach ($accountCategory->types as $typeName => $type) { + // Header for subcategory (type) + $typeHeader = []; + foreach ($this->getColumns() as $column) { + $typeHeader[$column->getName()] = $column->getName() === 'account_name' ? $typeName : ''; + } + + // Account data for the subcategory + $typeData = array_map(function (AccountDTO $account) { + $row = []; + + foreach ($this->getColumns() as $column) { + $row[$column->getName()] = match ($column->getName()) { + 'account_code' => $account->accountCode, + 'account_name' => [ + 'name' => $account->accountName, + 'id' => $account->accountId ?? null, + 'start_date' => $account->startDate, + 'end_date' => $account->endDate, + ], + 'ending_balance' => $account->balance->endingBalance ?? '', + default => '', + }; + } + + return $row; + }, $type->accounts); + + // Subcategory (type) summary + $typeSummary = []; + foreach ($this->getColumns() as $column) { + $typeSummary[$column->getName()] = match ($column->getName()) { + 'account_name' => 'Total ' . $typeName, + 'ending_balance' => $type->summary->endingBalance ?? '', + default => '', + }; + } + + // Add subcategory (type) to the list + $types[$typeName] = new ReportTypeDTO( + header: $typeHeader, + data: $typeData, + summary: $typeSummary, + ); + } + + // Add the category to the final array with its direct accounts and subcategories (types) + $categories[$accountCategoryName] = new ReportCategoryDTO( + header: $header, + data: $data, // Direct accounts under the category + summary: $categorySummary, + types: $types, // Subcategories (types) under the category + ); + } + + return $categories; + } + + public function getSummaryCategories(): array + { + $summaryCategories = []; + + $columns = $this->getSummaryColumns(); + + foreach ($this->report->categories as $accountCategoryName => $accountCategory) { + $categoryHeader = []; + + foreach ($columns as $column) { + $categoryHeader[$column->getName()] = $column->getName() === 'account_name' ? $accountCategoryName : ''; + } + + $categorySummary = []; + foreach ($columns as $column) { + $categorySummary[$column->getName()] = match ($column->getName()) { + 'account_name' => 'Total ' . $accountCategoryName, + 'ending_balance' => $accountCategory->summary->endingBalance ?? '', + default => '', + }; + } + + $types = []; + $totalTypeSummaries = 0; + + // Iterate through each account type and calculate type summaries + foreach ($accountCategory->types as $typeName => $type) { + $typeSummary = []; + $typeEndingBalance = 0; + + foreach ($columns as $column) { + $typeSummary[$column->getName()] = match ($column->getName()) { + 'account_name' => 'Total ' . $typeName, + 'ending_balance' => $type->summary->endingBalance ?? '', + default => '', + }; + + if ($column->getName() === 'ending_balance') { + $typeEndingBalance = $type->summary->endingBalance ?? 0; + } + } + + $typeEndingBalance = money($typeEndingBalance, CurrencyAccessor::getDefaultCurrency())->getAmount(); + + $totalTypeSummaries += $typeEndingBalance; + + $types[$typeName] = new ReportTypeDTO( + header: [], + data: [], + summary: $typeSummary, + ); + } + + // Only for the "Equity" category, calculate and add "Total Other Equity" + if ($accountCategoryName === 'Equity') { + $totalEquitySummary = $accountCategory->summary->endingBalance ?? 0; + $totalEquitySummary = money($totalEquitySummary, CurrencyAccessor::getDefaultCurrency())->getAmount(); + $totalOtherEquity = $totalEquitySummary - $totalTypeSummaries; + $totalOtherEquity = money($totalOtherEquity, CurrencyAccessor::getDefaultCurrency(), true)->format(); + + // Add "Total Other Equity" as a new "type" + $otherEquitySummary = []; + foreach ($columns as $column) { + $otherEquitySummary[$column->getName()] = match ($column->getName()) { + 'account_name' => 'Total Other Equity', + 'ending_balance' => $totalOtherEquity, + default => '', + }; + } + + $types['Total Other Equity'] = new ReportTypeDTO( + header: [], + data: [], + summary: $otherEquitySummary, + ); + } + + // Add the category with its types and summary to the final array + $summaryCategories[$accountCategoryName] = new ReportCategoryDTO( + header: $categoryHeader, + data: [], + summary: $categorySummary, + types: $types, + ); + } + + return $summaryCategories; + } + + public function getOverallTotals(): array + { + return []; + } + + public function getSummaryOverallTotals(): array + { + return []; + } + + public function getSummary(): array + { + return [ + [ + 'label' => 'Total Assets', + 'value' => $this->totalAssets, + ], + [ + 'label' => 'Total Liabilities', + 'value' => $this->totalLiabilities, + ], + [ + 'label' => 'Net Assets', + 'value' => $this->report->overallTotal->endingBalance ?? '', + ], + ]; + } +} diff --git a/app/Transformers/BaseReportTransformer.php b/app/Transformers/BaseReportTransformer.php index 6af8aff5..acfb0754 100644 --- a/app/Transformers/BaseReportTransformer.php +++ b/app/Transformers/BaseReportTransformer.php @@ -4,6 +4,7 @@ use App\Contracts\ExportableReport; use App\DTO\ReportDTO; +use App\Support\Column; use Filament\Support\Enums\Alignment; abstract class BaseReportTransformer implements ExportableReport @@ -15,9 +16,27 @@ public function __construct(ReportDTO $report) $this->report = $report; } + /** + * @return Column[] + */ public function getColumns(): array { - return $this->report->fields; + return once(function (): array { + return $this->report->fields; + }); + } + + public function getHeaders(): array + { + return once(function (): array { + $headers = []; + + foreach ($this->getColumns() as $column) { + $headers[$column->getName()] = $column->getLabel(); + } + + return $headers; + }); } public function getPdfView(): string @@ -25,18 +44,36 @@ public function getPdfView(): string return 'components.company.reports.report-pdf'; } - public function getAlignmentClass(int $index): string + public function getAlignment(int $index): string { $column = $this->getColumns()[$index]; if ($column->getAlignment() === Alignment::Right) { - return 'text-right'; + return 'right'; } if ($column->getAlignment() === Alignment::Center) { - return 'text-center'; + return 'center'; } - return 'text-left'; + return 'left'; + } + + public function getAlignmentClass(string $columnName): string + { + return once(function () use ($columnName): string { + /** @var Column|null $column */ + $column = collect($this->getColumns())->first(fn (Column $column) => $column->getName() === $columnName); + + if ($column?->getAlignment() === Alignment::Right) { + return 'text-right'; + } + + if ($column?->getAlignment() === Alignment::Center) { + return 'text-center'; + } + + return 'text-left'; + }); } } diff --git a/app/Transformers/IncomeStatementReportTransformer.php b/app/Transformers/IncomeStatementReportTransformer.php index e4261866..292934a7 100644 --- a/app/Transformers/IncomeStatementReportTransformer.php +++ b/app/Transformers/IncomeStatementReportTransformer.php @@ -4,24 +4,27 @@ use App\DTO\AccountDTO; use App\DTO\ReportCategoryDTO; -use App\Support\Column; +use App\DTO\ReportDTO; +use App\Utilities\Currency\CurrencyAccessor; -class IncomeStatementReportTransformer extends BaseReportTransformer +class IncomeStatementReportTransformer extends SummaryReportTransformer { - protected string $totalRevenue = '$0.00'; + protected string $totalRevenue; - protected string $totalCogs = '$0.00'; + protected string $totalCogs; - protected string $totalExpenses = '$0.00'; + protected string $totalExpenses; - public function getTitle(): string + public function __construct(ReportDTO $report) { - return 'Income Statement'; + parent::__construct($report); + + $this->calculateTotals(); } - public function getHeaders(): array + public function getTitle(): string { - return array_map(fn (Column $column) => $column->getLabel(), $this->getColumns()); + return 'Income Statement'; } public function calculateTotals(): void @@ -40,22 +43,17 @@ public function getCategories(): array $categories = []; foreach ($this->report->categories as $accountCategoryName => $accountCategory) { - // Initialize header with empty strings $header = []; - foreach ($this->getColumns() as $index => $column) { - if ($column->getName() === 'account_name') { - $header[$index] = $accountCategoryName; - } else { - $header[$index] = ''; - } + foreach ($this->getColumns() as $column) { + $header[$column->getName()] = $column->getName() === 'account_name' ? $accountCategoryName : ''; } $data = array_map(function (AccountDTO $account) { $row = []; foreach ($this->getColumns() as $column) { - $row[] = match ($column->getName()) { + $row[$column->getName()] = match ($column->getName()) { 'account_code' => $account->accountCode, 'account_name' => [ 'name' => $account->accountName, @@ -74,7 +72,7 @@ public function getCategories(): array $summary = []; foreach ($this->getColumns() as $column) { - $summary[] = match ($column->getName()) { + $summary[$column->getName()] = match ($column->getName()) { 'account_name' => 'Total ' . $accountCategoryName, 'net_movement' => $accountCategory->summary->netMovement ?? '', default => '', @@ -91,12 +89,71 @@ public function getCategories(): array return $categories; } + public function getSummaryCategories(): array + { + $summaryCategories = []; + + $columns = $this->getSummaryColumns(); + + foreach ($this->report->categories as $accountCategoryName => $accountCategory) { + // Header for the main category + $categoryHeader = []; + + foreach ($columns as $column) { + $categoryHeader[$column->getName()] = $column->getName() === 'account_name' ? $accountCategoryName : ''; + } + + // Category-level summary + $categorySummary = []; + foreach ($columns as $column) { + $categorySummary[$column->getName()] = match ($column->getName()) { + 'account_name' => $accountCategoryName, + 'net_movement' => $accountCategory->summary->netMovement ?? '', + default => '', + }; + } + + // Add the category summary to the final array + $summaryCategories[$accountCategoryName] = new ReportCategoryDTO( + header: $categoryHeader, + data: [], // No direct accounts are needed here, only summaries + summary: $categorySummary, + types: [] // No types for the income statement + ); + } + + return $summaryCategories; + } + + public function getGrossProfit(): array + { + $grossProfit = []; + + $columns = $this->getSummaryColumns(); + + $revenue = money($this->totalRevenue, CurrencyAccessor::getDefaultCurrency())->getAmount(); + $cogs = money($this->totalCogs, CurrencyAccessor::getDefaultCurrency())->getAmount(); + + $grossProfitAmount = $revenue - $cogs; + $grossProfitFormatted = money($grossProfitAmount, CurrencyAccessor::getDefaultCurrency(), true)->format(); + + foreach ($columns as $column) { + $grossProfit[$column->getName()] = match ($column->getName()) { + 'account_name' => 'Gross Profit', + 'net_movement' => $grossProfitFormatted, + default => '', + }; + } + + return $grossProfit; + } + public function getOverallTotals(): array { $totals = []; foreach ($this->getColumns() as $column) { - $totals[] = match ($column->getName()) { + $totals[$column->getName()] = match ($column->getName()) { 'account_name' => 'Net Earnings', 'net_movement' => $this->report->overallTotal->netMovement ?? '', default => '', @@ -106,10 +163,24 @@ public function getOverallTotals(): array return $totals; } - public function getSummary(): array + public function getSummaryOverallTotals(): array { - $this->calculateTotals(); + $totals = []; + $columns = $this->getSummaryColumns(); + + foreach ($columns as $column) { + $totals[$column->getName()] = match ($column->getName()) { + 'account_name' => 'Net Earnings', + 'net_movement' => $this->report->overallTotal->netMovement ?? '', + default => '', + }; + } + + return $totals; + } + public function getSummary(): array + { return [ [ 'label' => 'Revenue', diff --git a/app/Transformers/SummaryReportTransformer.php b/app/Transformers/SummaryReportTransformer.php new file mode 100644 index 00000000..5b79e297 --- /dev/null +++ b/app/Transformers/SummaryReportTransformer.php @@ -0,0 +1,34 @@ +getColumns()) + ->reject(fn (Column $column) => $column->getName() === 'account_code') + ->toArray(); + }); + } + + public function getSummaryHeaders(): array + { + return once(function (): array { + $headers = []; + + foreach ($this->getSummaryColumns() as $column) { + $headers[$column->getName()] = $column->getLabel(); + } + + return $headers; + }); + } +} diff --git a/app/Transformers/TrialBalanceReportTransformer.php b/app/Transformers/TrialBalanceReportTransformer.php index 80f15a8a..d40b20d6 100644 --- a/app/Transformers/TrialBalanceReportTransformer.php +++ b/app/Transformers/TrialBalanceReportTransformer.php @@ -4,7 +4,6 @@ use App\DTO\AccountDTO; use App\DTO\ReportCategoryDTO; -use App\Support\Column; class TrialBalanceReportTransformer extends BaseReportTransformer { @@ -16,11 +15,6 @@ public function getTitle(): string }; } - public function getHeaders(): array - { - return array_map(fn (Column $column) => $column->getLabel(), $this->getColumns()); - } - /** * @return ReportCategoryDTO[] */ @@ -32,19 +26,15 @@ public function getCategories(): array // Initialize header with empty strings $header = []; - foreach ($this->getColumns() as $index => $column) { - if ($column->getName() === 'account_name') { - $header[$index] = $accountCategoryName; - } else { - $header[$index] = ''; - } + foreach ($this->getColumns() as $column) { + $header[$column->getName()] = $column->getName() === 'account_name' ? $accountCategoryName : ''; } $data = array_map(function (AccountDTO $account) { $row = []; foreach ($this->getColumns() as $column) { - $row[] = match ($column->getName()) { + $row[$column->getName()] = match ($column->getName()) { 'account_code' => $account->accountCode, 'account_name' => [ 'name' => $account->accountName, @@ -64,7 +54,7 @@ public function getCategories(): array $summary = []; foreach ($this->getColumns() as $column) { - $summary[] = match ($column->getName()) { + $summary[$column->getName()] = match ($column->getName()) { 'account_name' => 'Total ' . $accountCategoryName, 'debit_balance' => $accountCategory->summary->debitBalance, 'credit_balance' => $accountCategory->summary->creditBalance, @@ -87,7 +77,7 @@ public function getOverallTotals(): array $totals = []; foreach ($this->getColumns() as $column) { - $totals[] = match ($column->getName()) { + $totals[$column->getName()] = match ($column->getName()) { 'account_name' => 'Total for all accounts', 'debit_balance' => $this->report->overallTotal->debitBalance, 'credit_balance' => $this->report->overallTotal->creditBalance, diff --git a/composer.lock b/composer.lock index b74ca15c..e8d5d8db 100644 --- a/composer.lock +++ b/composer.lock @@ -497,16 +497,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.323.3", + "version": "3.324.7", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "9dec2a6453bdb32b3abeb475fc63b46ba1cbd996" + "reference": "c430d00e5ea6ce23739d6345f073fdd39e8ef0dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/9dec2a6453bdb32b3abeb475fc63b46ba1cbd996", - "reference": "9dec2a6453bdb32b3abeb475fc63b46ba1cbd996", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/c430d00e5ea6ce23739d6345f073fdd39e8ef0dc", + "reference": "c430d00e5ea6ce23739d6345f073fdd39e8ef0dc", "shasum": "" }, "require": { @@ -589,9 +589,9 @@ "support": { "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.323.3" + "source": "https://github.com/aws/aws-sdk-php/tree/3.324.7" }, - "time": "2024-10-08T18:05:47+00:00" + "time": "2024-10-21T18:06:52+00:00" }, { "name": "aws/aws-sdk-php-laravel", @@ -819,16 +819,16 @@ }, { "name": "blade-ui-kit/blade-icons", - "version": "1.7.1", + "version": "1.7.2", "source": { "type": "git", "url": "https://github.com/blade-ui-kit/blade-icons.git", - "reference": "8f787baf09d88cdfd6ec4dbaba11ebfa885f0595" + "reference": "75a54a3f5a2810fcf6574ab23e91b6cc229a1b53" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/blade-ui-kit/blade-icons/zipball/8f787baf09d88cdfd6ec4dbaba11ebfa885f0595", - "reference": "8f787baf09d88cdfd6ec4dbaba11ebfa885f0595", + "url": "https://api.github.com/repos/blade-ui-kit/blade-icons/zipball/75a54a3f5a2810fcf6574ab23e91b6cc229a1b53", + "reference": "75a54a3f5a2810fcf6574ab23e91b6cc229a1b53", "shasum": "" }, "require": { @@ -896,7 +896,7 @@ "type": "paypal" } ], - "time": "2024-08-14T14:25:11+00:00" + "time": "2024-10-17T17:38:00+00:00" }, { "name": "brick/math", @@ -1209,16 +1209,16 @@ }, { "name": "doctrine/dbal", - "version": "4.1.1", + "version": "4.2.1", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "7a8252418689feb860ea8dfeab66d64a56a64df8" + "reference": "dadd35300837a3a2184bd47d403333b15d0a9bd0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/7a8252418689feb860ea8dfeab66d64a56a64df8", - "reference": "7a8252418689feb860ea8dfeab66d64a56a64df8", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/dadd35300837a3a2184bd47d403333b15d0a9bd0", + "reference": "dadd35300837a3a2184bd47d403333b15d0a9bd0", "shasum": "" }, "require": { @@ -1231,7 +1231,7 @@ "doctrine/coding-standard": "12.0.0", "fig/log-test": "^1", "jetbrains/phpstorm-stubs": "2023.2", - "phpstan/phpstan": "1.12.0", + "phpstan/phpstan": "1.12.6", "phpstan/phpstan-phpunit": "1.4.0", "phpstan/phpstan-strict-rules": "^1.6", "phpunit/phpunit": "10.5.30", @@ -1297,7 +1297,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/4.1.1" + "source": "https://github.com/doctrine/dbal/tree/4.2.1" }, "funding": [ { @@ -1313,7 +1313,7 @@ "type": "tidelift" } ], - "time": "2024-09-03T08:58:39+00:00" + "time": "2024-10-10T18:01:27+00:00" }, { "name": "doctrine/deprecations", @@ -1532,16 +1532,16 @@ }, { "name": "dragonmantank/cron-expression", - "version": "v3.3.3", + "version": "v3.4.0", "source": { "type": "git", "url": "https://github.com/dragonmantank/cron-expression.git", - "reference": "adfb1f505deb6384dc8b39804c5065dd3c8c8c0a" + "reference": "8c784d071debd117328803d86b2097615b457500" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/adfb1f505deb6384dc8b39804c5065dd3c8c8c0a", - "reference": "adfb1f505deb6384dc8b39804c5065dd3c8c8c0a", + "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/8c784d071debd117328803d86b2097615b457500", + "reference": "8c784d071debd117328803d86b2097615b457500", "shasum": "" }, "require": { @@ -1554,10 +1554,14 @@ "require-dev": { "phpstan/extension-installer": "^1.0", "phpstan/phpstan": "^1.0", - "phpstan/phpstan-webmozart-assert": "^1.0", "phpunit/phpunit": "^7.0|^8.0|^9.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, "autoload": { "psr-4": { "Cron\\": "src/Cron/" @@ -1581,7 +1585,7 @@ ], "support": { "issues": "https://github.com/dragonmantank/cron-expression/issues", - "source": "https://github.com/dragonmantank/cron-expression/tree/v3.3.3" + "source": "https://github.com/dragonmantank/cron-expression/tree/v3.4.0" }, "funding": [ { @@ -1589,7 +1593,7 @@ "type": "github" } ], - "time": "2023-08-10T19:36:49+00:00" + "time": "2024-10-09T13:47:03+00:00" }, { "name": "egulias/email-validator", @@ -1660,16 +1664,16 @@ }, { "name": "filament/actions", - "version": "v3.2.116", + "version": "v3.2.119", "source": { "type": "git", "url": "https://github.com/filamentphp/actions.git", - "reference": "79dce62359b61b755a5e3d8faa0e047a1f92ad9a" + "reference": "addd6a6f5e8e250a34c7c12bcb4060cc5ecbb9e8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filamentphp/actions/zipball/79dce62359b61b755a5e3d8faa0e047a1f92ad9a", - "reference": "79dce62359b61b755a5e3d8faa0e047a1f92ad9a", + "url": "https://api.github.com/repos/filamentphp/actions/zipball/addd6a6f5e8e250a34c7c12bcb4060cc5ecbb9e8", + "reference": "addd6a6f5e8e250a34c7c12bcb4060cc5ecbb9e8", "shasum": "" }, "require": { @@ -1709,20 +1713,20 @@ "issues": "https://github.com/filamentphp/filament/issues", "source": "https://github.com/filamentphp/filament" }, - "time": "2024-10-08T14:24:13+00:00" + "time": "2024-10-16T12:07:31+00:00" }, { "name": "filament/filament", - "version": "v3.2.116", + "version": "v3.2.119", "source": { "type": "git", "url": "https://github.com/filamentphp/panels.git", - "reference": "84f839b4b42549c0d4bd231648da17561ada70c2" + "reference": "6ab7628f7a0c164d694a540dfe69b8d79484fd7d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filamentphp/panels/zipball/84f839b4b42549c0d4bd231648da17561ada70c2", - "reference": "84f839b4b42549c0d4bd231648da17561ada70c2", + "url": "https://api.github.com/repos/filamentphp/panels/zipball/6ab7628f7a0c164d694a540dfe69b8d79484fd7d", + "reference": "6ab7628f7a0c164d694a540dfe69b8d79484fd7d", "shasum": "" }, "require": { @@ -1774,20 +1778,20 @@ "issues": "https://github.com/filamentphp/filament/issues", "source": "https://github.com/filamentphp/filament" }, - "time": "2024-10-08T14:24:12+00:00" + "time": "2024-10-17T09:39:37+00:00" }, { "name": "filament/forms", - "version": "v3.2.116", + "version": "v3.2.119", "source": { "type": "git", "url": "https://github.com/filamentphp/forms.git", - "reference": "94f0c7c66d766efc0f1f5c80e790e2391c7c09d7" + "reference": "9eaf88275da18777b1738514db6083e34b1700d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filamentphp/forms/zipball/94f0c7c66d766efc0f1f5c80e790e2391c7c09d7", - "reference": "94f0c7c66d766efc0f1f5c80e790e2391c7c09d7", + "url": "https://api.github.com/repos/filamentphp/forms/zipball/9eaf88275da18777b1738514db6083e34b1700d4", + "reference": "9eaf88275da18777b1738514db6083e34b1700d4", "shasum": "" }, "require": { @@ -1830,20 +1834,20 @@ "issues": "https://github.com/filamentphp/filament/issues", "source": "https://github.com/filamentphp/filament" }, - "time": "2024-10-08T14:24:11+00:00" + "time": "2024-10-16T12:07:33+00:00" }, { "name": "filament/infolists", - "version": "v3.2.116", + "version": "v3.2.119", "source": { "type": "git", "url": "https://github.com/filamentphp/infolists.git", - "reference": "fc5f01c094fe25ef906f3e1b88d3d8883a73d6be" + "reference": "f107a83c3ac042a4d4608d45173cf88c0c55276c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filamentphp/infolists/zipball/fc5f01c094fe25ef906f3e1b88d3d8883a73d6be", - "reference": "fc5f01c094fe25ef906f3e1b88d3d8883a73d6be", + "url": "https://api.github.com/repos/filamentphp/infolists/zipball/f107a83c3ac042a4d4608d45173cf88c0c55276c", + "reference": "f107a83c3ac042a4d4608d45173cf88c0c55276c", "shasum": "" }, "require": { @@ -1881,20 +1885,20 @@ "issues": "https://github.com/filamentphp/filament/issues", "source": "https://github.com/filamentphp/filament" }, - "time": "2024-10-08T14:24:09+00:00" + "time": "2024-10-16T12:07:29+00:00" }, { "name": "filament/notifications", - "version": "v3.2.116", + "version": "v3.2.119", "source": { "type": "git", "url": "https://github.com/filamentphp/notifications.git", - "reference": "a5f684b690354630210fc9a90bd06da9b1f6ae82" + "reference": "b56b155efd4290459d944a1b3d515b5aaf54846d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filamentphp/notifications/zipball/a5f684b690354630210fc9a90bd06da9b1f6ae82", - "reference": "a5f684b690354630210fc9a90bd06da9b1f6ae82", + "url": "https://api.github.com/repos/filamentphp/notifications/zipball/b56b155efd4290459d944a1b3d515b5aaf54846d", + "reference": "b56b155efd4290459d944a1b3d515b5aaf54846d", "shasum": "" }, "require": { @@ -1933,20 +1937,20 @@ "issues": "https://github.com/filamentphp/filament/issues", "source": "https://github.com/filamentphp/filament" }, - "time": "2024-10-08T14:24:11+00:00" + "time": "2024-10-16T12:07:28+00:00" }, { "name": "filament/support", - "version": "v3.2.116", + "version": "v3.2.119", "source": { "type": "git", "url": "https://github.com/filamentphp/support.git", - "reference": "31fcff80b873b4decdba10d5f7010310e12c8e94" + "reference": "85d83838ec0290fffba2be3f99a8b0840da1dc01" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filamentphp/support/zipball/31fcff80b873b4decdba10d5f7010310e12c8e94", - "reference": "31fcff80b873b4decdba10d5f7010310e12c8e94", + "url": "https://api.github.com/repos/filamentphp/support/zipball/85d83838ec0290fffba2be3f99a8b0840da1dc01", + "reference": "85d83838ec0290fffba2be3f99a8b0840da1dc01", "shasum": "" }, "require": { @@ -1957,7 +1961,7 @@ "illuminate/support": "^10.45|^11.0", "illuminate/view": "^10.45|^11.0", "kirschbaum-development/eloquent-power-joins": "^3.0|^4.0", - "livewire/livewire": "^3.4.10 <= 3.5.6", + "livewire/livewire": "^3.4.10", "php": "^8.1", "ryangjchandler/blade-capture-directive": "^0.2|^0.3|^1.0", "spatie/color": "^1.5", @@ -1992,20 +1996,20 @@ "issues": "https://github.com/filamentphp/filament/issues", "source": "https://github.com/filamentphp/filament" }, - "time": "2024-10-08T14:24:29+00:00" + "time": "2024-10-16T12:07:44+00:00" }, { "name": "filament/tables", - "version": "v3.2.116", + "version": "v3.2.119", "source": { "type": "git", "url": "https://github.com/filamentphp/tables.git", - "reference": "152bf46a8f2c46f047835771a67085c2866b039b" + "reference": "fac7e8e9a787aef94f45938fd47b50ea3f9d3bff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filamentphp/tables/zipball/152bf46a8f2c46f047835771a67085c2866b039b", - "reference": "152bf46a8f2c46f047835771a67085c2866b039b", + "url": "https://api.github.com/repos/filamentphp/tables/zipball/fac7e8e9a787aef94f45938fd47b50ea3f9d3bff", + "reference": "fac7e8e9a787aef94f45938fd47b50ea3f9d3bff", "shasum": "" }, "require": { @@ -2044,11 +2048,11 @@ "issues": "https://github.com/filamentphp/filament/issues", "source": "https://github.com/filamentphp/filament" }, - "time": "2024-10-08T14:24:25+00:00" + "time": "2024-10-16T12:07:46+00:00" }, { "name": "filament/widgets", - "version": "v3.2.116", + "version": "v3.2.119", "source": { "type": "git", "url": "https://github.com/filamentphp/widgets.git", @@ -2489,16 +2493,16 @@ }, { "name": "guzzlehttp/promises", - "version": "2.0.3", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8" + "reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8", - "reference": "6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8", + "url": "https://api.github.com/repos/guzzle/promises/zipball/f9c436286ab2892c7db7be8c8da4ef61ccf7b455", + "reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455", "shasum": "" }, "require": { @@ -2552,7 +2556,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/2.0.3" + "source": "https://github.com/guzzle/promises/tree/2.0.4" }, "funding": [ { @@ -2568,7 +2572,7 @@ "type": "tidelift" } ], - "time": "2024-07-18T10:29:17+00:00" + "time": "2024-10-17T10:06:22+00:00" }, { "name": "guzzlehttp/psr7", @@ -2904,16 +2908,16 @@ }, { "name": "laravel/framework", - "version": "v11.27.1", + "version": "v11.28.1", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "2634ad4a6a71da3288943f058a8abbfec6a65ebb" + "reference": "3ef5c8a85b4c598d5ffaf98afd72f6a5d6a0be2c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/2634ad4a6a71da3288943f058a8abbfec6a65ebb", - "reference": "2634ad4a6a71da3288943f058a8abbfec6a65ebb", + "url": "https://api.github.com/repos/laravel/framework/zipball/3ef5c8a85b4c598d5ffaf98afd72f6a5d6a0be2c", + "reference": "3ef5c8a85b4c598d5ffaf98afd72f6a5d6a0be2c", "shasum": "" }, "require": { @@ -3109,25 +3113,25 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2024-10-08T20:25:59+00:00" + "time": "2024-10-16T16:32:21+00:00" }, { "name": "laravel/prompts", - "version": "v0.1.25", + "version": "v0.3.1", "source": { "type": "git", "url": "https://github.com/laravel/prompts.git", - "reference": "7b4029a84c37cb2725fc7f011586e2997040bc95" + "reference": "0f3848a445562dac376b27968f753c65e7e1036e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/prompts/zipball/7b4029a84c37cb2725fc7f011586e2997040bc95", - "reference": "7b4029a84c37cb2725fc7f011586e2997040bc95", + "url": "https://api.github.com/repos/laravel/prompts/zipball/0f3848a445562dac376b27968f753c65e7e1036e", + "reference": "0f3848a445562dac376b27968f753c65e7e1036e", "shasum": "" }, "require": { + "composer-runtime-api": "^2.2", "ext-mbstring": "*", - "illuminate/collections": "^10.0|^11.0", "php": "^8.1", "symfony/console": "^6.2|^7.0" }, @@ -3136,6 +3140,7 @@ "laravel/framework": ">=10.17.0 <10.25.0" }, "require-dev": { + "illuminate/collections": "^10.0|^11.0", "mockery/mockery": "^1.5", "pestphp/pest": "^2.3", "phpstan/phpstan": "^1.11", @@ -3147,7 +3152,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "0.1.x-dev" + "dev-main": "0.3.x-dev" } }, "autoload": { @@ -3165,9 +3170,9 @@ "description": "Add beautiful and user-friendly forms to your command-line applications.", "support": { "issues": "https://github.com/laravel/prompts/issues", - "source": "https://github.com/laravel/prompts/tree/v0.1.25" + "source": "https://github.com/laravel/prompts/tree/v0.3.1" }, - "time": "2024-08-12T22:06:33+00:00" + "time": "2024-10-09T19:42:26+00:00" }, { "name": "laravel/sanctum", @@ -3622,16 +3627,16 @@ }, { "name": "league/csv", - "version": "9.16.0", + "version": "9.18.0", "source": { "type": "git", "url": "https://github.com/thephpleague/csv.git", - "reference": "998280c6c34bd67d8125fdc8b45bae28d761b440" + "reference": "b02d010e4055ae992247f6ffd1e7b103ef2a0790" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/csv/zipball/998280c6c34bd67d8125fdc8b45bae28d761b440", - "reference": "998280c6c34bd67d8125fdc8b45bae28d761b440", + "url": "https://api.github.com/repos/thephpleague/csv/zipball/b02d010e4055ae992247f6ffd1e7b103ef2a0790", + "reference": "b02d010e4055ae992247f6ffd1e7b103ef2a0790", "shasum": "" }, "require": { @@ -3639,17 +3644,16 @@ "php": "^8.1.2" }, "require-dev": { - "doctrine/collections": "^2.2.2", "ext-dom": "*", "ext-xdebug": "*", - "friendsofphp/php-cs-fixer": "^3.57.1", - "phpbench/phpbench": "^1.2.15", - "phpstan/phpstan": "^1.11.1", - "phpstan/phpstan-deprecation-rules": "^1.2.0", + "friendsofphp/php-cs-fixer": "^3.64.0", + "phpbench/phpbench": "^1.3.1", + "phpstan/phpstan": "^1.12.6", + "phpstan/phpstan-deprecation-rules": "^1.2.1", "phpstan/phpstan-phpunit": "^1.4.0", - "phpstan/phpstan-strict-rules": "^1.6.0", - "phpunit/phpunit": "^10.5.16 || ^11.1.3", - "symfony/var-dumper": "^6.4.6 || ^7.0.7" + "phpstan/phpstan-strict-rules": "^1.6.1", + "phpunit/phpunit": "^10.5.16 || ^11.4.1", + "symfony/var-dumper": "^6.4.8 || ^7.1.5" }, "suggest": { "ext-dom": "Required to use the XMLConverter and the HTMLConverter classes", @@ -3667,7 +3671,7 @@ "src/functions_include.php" ], "psr-4": { - "League\\Csv\\": "src" + "League\\Csv\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -3706,7 +3710,7 @@ "type": "github" } ], - "time": "2024-05-24T11:04:54+00:00" + "time": "2024-10-18T08:14:48+00:00" }, { "name": "league/flysystem", @@ -4148,16 +4152,16 @@ }, { "name": "livewire/livewire", - "version": "v3.5.6", + "version": "v3.5.12", "source": { "type": "git", "url": "https://github.com/livewire/livewire.git", - "reference": "597a2808d8d3001cc3ed5ce89a6ebab00f83b80f" + "reference": "3c8d1f9d7d9098aaea663093ae168f2d5d2ae73d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/livewire/livewire/zipball/597a2808d8d3001cc3ed5ce89a6ebab00f83b80f", - "reference": "597a2808d8d3001cc3ed5ce89a6ebab00f83b80f", + "url": "https://api.github.com/repos/livewire/livewire/zipball/3c8d1f9d7d9098aaea663093ae168f2d5d2ae73d", + "reference": "3c8d1f9d7d9098aaea663093ae168f2d5d2ae73d", "shasum": "" }, "require": { @@ -4165,7 +4169,7 @@ "illuminate/routing": "^10.0|^11.0", "illuminate/support": "^10.0|^11.0", "illuminate/validation": "^10.0|^11.0", - "laravel/prompts": "^0.1.24", + "laravel/prompts": "^0.1.24|^0.2|^0.3", "league/mime-type-detection": "^1.9", "php": "^8.1", "symfony/console": "^6.0|^7.0", @@ -4212,7 +4216,7 @@ "description": "A front-end framework for Laravel.", "support": { "issues": "https://github.com/livewire/livewire/issues", - "source": "https://github.com/livewire/livewire/tree/v3.5.6" + "source": "https://github.com/livewire/livewire/tree/v3.5.12" }, "funding": [ { @@ -4220,7 +4224,7 @@ "type": "github" } ], - "time": "2024-08-19T11:52:18+00:00" + "time": "2024-10-15T19:35:06+00:00" }, { "name": "masterminds/html5", @@ -4893,32 +4897,31 @@ }, { "name": "nunomaduro/termwind", - "version": "v2.1.0", + "version": "v2.2.0", "source": { "type": "git", "url": "https://github.com/nunomaduro/termwind.git", - "reference": "e5f21eade88689536c0cdad4c3cd75f3ed26e01a" + "reference": "42c84e4e8090766bbd6445d06cd6e57650626ea3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/e5f21eade88689536c0cdad4c3cd75f3ed26e01a", - "reference": "e5f21eade88689536c0cdad4c3cd75f3ed26e01a", + "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/42c84e4e8090766bbd6445d06cd6e57650626ea3", + "reference": "42c84e4e8090766bbd6445d06cd6e57650626ea3", "shasum": "" }, "require": { "ext-mbstring": "*", "php": "^8.2", - "symfony/console": "^7.0.4" + "symfony/console": "^7.1.5" }, "require-dev": { - "ergebnis/phpstan-rules": "^2.2.0", - "illuminate/console": "^11.1.1", - "laravel/pint": "^1.15.0", - "mockery/mockery": "^1.6.11", - "pestphp/pest": "^2.34.6", - "phpstan/phpstan": "^1.10.66", - "phpstan/phpstan-strict-rules": "^1.5.2", - "symfony/var-dumper": "^7.0.4", + "illuminate/console": "^11.28.0", + "laravel/pint": "^1.18.1", + "mockery/mockery": "^1.6.12", + "pestphp/pest": "^2.36.0", + "phpstan/phpstan": "^1.12.6", + "phpstan/phpstan-strict-rules": "^1.6.1", + "symfony/var-dumper": "^7.1.5", "thecodingmachine/phpstan-strict-rules": "^1.0.0" }, "type": "library", @@ -4961,7 +4964,7 @@ ], "support": { "issues": "https://github.com/nunomaduro/termwind/issues", - "source": "https://github.com/nunomaduro/termwind/tree/v2.1.0" + "source": "https://github.com/nunomaduro/termwind/tree/v2.2.0" }, "funding": [ { @@ -4977,7 +4980,7 @@ "type": "github" } ], - "time": "2024-09-05T15:25:50+00:00" + "time": "2024-10-15T16:15:16+00:00" }, { "name": "openspout/openspout", @@ -9155,16 +9158,16 @@ "packages-dev": [ { "name": "brianium/paratest", - "version": "v7.5.7", + "version": "v7.6.0", "source": { "type": "git", "url": "https://github.com/paratestphp/paratest.git", - "reference": "4890b17f569efea5e034e519dc883da53a67448d" + "reference": "68ff89a8de47d086588e391a516d2a5b5fde6254" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paratestphp/paratest/zipball/4890b17f569efea5e034e519dc883da53a67448d", - "reference": "4890b17f569efea5e034e519dc883da53a67448d", + "url": "https://api.github.com/repos/paratestphp/paratest/zipball/68ff89a8de47d086588e391a516d2a5b5fde6254", + "reference": "68ff89a8de47d086588e391a516d2a5b5fde6254", "shasum": "" }, "require": { @@ -9174,11 +9177,11 @@ "ext-simplexml": "*", "fidry/cpu-core-counter": "^1.2.0", "jean85/pretty-package-versions": "^2.0.6", - "php": "~8.2.0 || ~8.3.0", - "phpunit/php-code-coverage": "^11.0.6", + "php": "~8.2.0 || ~8.3.0 || ~8.4.0", + "phpunit/php-code-coverage": "^11.0.7", "phpunit/php-file-iterator": "^5.1.0", "phpunit/php-timer": "^7.0.1", - "phpunit/phpunit": "^11.4.0", + "phpunit/phpunit": "^11.4.1", "sebastian/environment": "^7.2.0", "symfony/console": "^6.4.11 || ^7.1.5", "symfony/process": "^6.4.8 || ^7.1.5" @@ -9187,7 +9190,6 @@ "doctrine/coding-standard": "^12.0.0", "ext-pcov": "*", "ext-posix": "*", - "infection/infection": "^0.29.7", "phpstan/phpstan": "^1.12.6", "phpstan/phpstan-deprecation-rules": "^1.2.1", "phpstan/phpstan-phpunit": "^1.4.0", @@ -9233,7 +9235,7 @@ ], "support": { "issues": "https://github.com/paratestphp/paratest/issues", - "source": "https://github.com/paratestphp/paratest/tree/v7.5.7" + "source": "https://github.com/paratestphp/paratest/tree/v7.6.0" }, "funding": [ { @@ -9245,7 +9247,7 @@ "type": "paypal" } ], - "time": "2024-10-07T06:27:54+00:00" + "time": "2024-10-15T12:38:31+00:00" }, { "name": "fakerphp/faker", @@ -9620,16 +9622,16 @@ }, { "name": "laravel/sail", - "version": "v1.35.0", + "version": "v1.36.0", "source": { "type": "git", "url": "https://github.com/laravel/sail.git", - "reference": "992bc2d9e52174c79515967f30849d21daa334d8" + "reference": "f184d3d687155d06bc8cb9ff6dc48596a138460c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/sail/zipball/992bc2d9e52174c79515967f30849d21daa334d8", - "reference": "992bc2d9e52174c79515967f30849d21daa334d8", + "url": "https://api.github.com/repos/laravel/sail/zipball/f184d3d687155d06bc8cb9ff6dc48596a138460c", + "reference": "f184d3d687155d06bc8cb9ff6dc48596a138460c", "shasum": "" }, "require": { @@ -9679,7 +9681,7 @@ "issues": "https://github.com/laravel/sail/issues", "source": "https://github.com/laravel/sail" }, - "time": "2024-10-08T14:45:26+00:00" + "time": "2024-10-10T13:26:02+00:00" }, { "name": "mockery/mockery", @@ -9826,23 +9828,23 @@ }, { "name": "nunomaduro/collision", - "version": "v8.4.0", + "version": "v8.5.0", "source": { "type": "git", "url": "https://github.com/nunomaduro/collision.git", - "reference": "e7d1aa8ed753f63fa816932bbc89678238843b4a" + "reference": "f5c101b929c958e849a633283adff296ed5f38f5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/collision/zipball/e7d1aa8ed753f63fa816932bbc89678238843b4a", - "reference": "e7d1aa8ed753f63fa816932bbc89678238843b4a", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/f5c101b929c958e849a633283adff296ed5f38f5", + "reference": "f5c101b929c958e849a633283adff296ed5f38f5", "shasum": "" }, "require": { - "filp/whoops": "^2.15.4", - "nunomaduro/termwind": "^2.0.1", + "filp/whoops": "^2.16.0", + "nunomaduro/termwind": "^2.1.0", "php": "^8.2.0", - "symfony/console": "^7.1.3" + "symfony/console": "^7.1.5" }, "conflict": { "laravel/framework": "<11.0.0 || >=12.0.0", @@ -9850,14 +9852,14 @@ }, "require-dev": { "larastan/larastan": "^2.9.8", - "laravel/framework": "^11.19.0", - "laravel/pint": "^1.17.1", - "laravel/sail": "^1.31.0", - "laravel/sanctum": "^4.0.2", - "laravel/tinker": "^2.9.0", - "orchestra/testbench-core": "^9.2.3", - "pestphp/pest": "^2.35.0 || ^3.0.0", - "sebastian/environment": "^6.1.0 || ^7.0.0" + "laravel/framework": "^11.28.0", + "laravel/pint": "^1.18.1", + "laravel/sail": "^1.36.0", + "laravel/sanctum": "^4.0.3", + "laravel/tinker": "^2.10.0", + "orchestra/testbench-core": "^9.5.3", + "pestphp/pest": "^2.36.0 || ^3.4.0", + "sebastian/environment": "^6.1.0 || ^7.2.0" }, "type": "library", "extra": { @@ -9919,39 +9921,40 @@ "type": "patreon" } ], - "time": "2024-08-03T15:32:23+00:00" + "time": "2024-10-15T16:06:32+00:00" }, { "name": "pestphp/pest", - "version": "v3.3.0", + "version": "v3.4.2", "source": { "type": "git", "url": "https://github.com/pestphp/pest.git", - "reference": "0a7bff0d246b10040e12e4152215e12a599e742a" + "reference": "2903a7e62100171d5faf4e50cda755ffeadedc57" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pestphp/pest/zipball/0a7bff0d246b10040e12e4152215e12a599e742a", - "reference": "0a7bff0d246b10040e12e4152215e12a599e742a", + "url": "https://api.github.com/repos/pestphp/pest/zipball/2903a7e62100171d5faf4e50cda755ffeadedc57", + "reference": "2903a7e62100171d5faf4e50cda755ffeadedc57", "shasum": "" }, "require": { - "brianium/paratest": "^7.5.6", - "nunomaduro/collision": "^8.4.0", - "nunomaduro/termwind": "^2.1.0", + "brianium/paratest": "^7.6.0", + "nunomaduro/collision": "^8.5.0", + "nunomaduro/termwind": "^2.2.0", "pestphp/pest-plugin": "^3.0.0", "pestphp/pest-plugin-arch": "^3.0.0", "pestphp/pest-plugin-mutate": "^3.0.5", "php": "^8.2.0", - "phpunit/phpunit": "^11.4.0" + "phpunit/phpunit": "^11.4.2" }, "conflict": { - "phpunit/phpunit": ">11.4.0", + "filp/whoops": "<2.16.0", + "phpunit/phpunit": ">11.4.2", "sebastian/exporter": "<6.0.0", "webmozart/assert": "<1.11.0" }, "require-dev": { - "pestphp/pest-dev-tools": "^3.0.0", + "pestphp/pest-dev-tools": "^3.3.0", "pestphp/pest-plugin-type-coverage": "^3.1.0", "symfony/process": "^7.1.5" }, @@ -10018,7 +10021,7 @@ ], "support": { "issues": "https://github.com/pestphp/pest/issues", - "source": "https://github.com/pestphp/pest/tree/v3.3.0" + "source": "https://github.com/pestphp/pest/tree/v3.4.2" }, "funding": [ { @@ -10030,7 +10033,7 @@ "type": "github" } ], - "time": "2024-10-06T18:25:27+00:00" + "time": "2024-10-20T11:47:25+00:00" }, { "name": "pestphp/pest-plugin", @@ -10733,16 +10736,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "1.32.0", + "version": "1.33.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "6ca22b154efdd9e3c68c56f5d94670920a1c19a4" + "reference": "82a311fd3690fb2bf7b64d5c98f912b3dd746140" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/6ca22b154efdd9e3c68c56f5d94670920a1c19a4", - "reference": "6ca22b154efdd9e3c68c56f5d94670920a1c19a4", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/82a311fd3690fb2bf7b64d5c98f912b3dd746140", + "reference": "82a311fd3690fb2bf7b64d5c98f912b3dd746140", "shasum": "" }, "require": { @@ -10774,22 +10777,22 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.32.0" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.33.0" }, - "time": "2024-09-26T07:23:32+00:00" + "time": "2024-10-13T11:25:22+00:00" }, { "name": "phpstan/phpstan", - "version": "1.12.6", + "version": "1.12.7", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "dc4d2f145a88ea7141ae698effd64d9df46527ae" + "reference": "dc2b9976bd8b0f84ec9b0e50cc35378551de7af0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/dc4d2f145a88ea7141ae698effd64d9df46527ae", - "reference": "dc4d2f145a88ea7141ae698effd64d9df46527ae", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/dc2b9976bd8b0f84ec9b0e50cc35378551de7af0", + "reference": "dc2b9976bd8b0f84ec9b0e50cc35378551de7af0", "shasum": "" }, "require": { @@ -10834,39 +10837,39 @@ "type": "github" } ], - "time": "2024-10-06T15:03:59+00:00" + "time": "2024-10-18T11:12:07+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "11.0.6", + "version": "11.0.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "ebdffc9e09585dafa71b9bffcdb0a229d4704c45" + "reference": "f7f08030e8811582cc459871d28d6f5a1a4d35ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ebdffc9e09585dafa71b9bffcdb0a229d4704c45", - "reference": "ebdffc9e09585dafa71b9bffcdb0a229d4704c45", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f7f08030e8811582cc459871d28d6f5a1a4d35ca", + "reference": "f7f08030e8811582cc459871d28d6f5a1a4d35ca", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^5.1.0", + "nikic/php-parser": "^5.3.1", "php": ">=8.2", - "phpunit/php-file-iterator": "^5.0.1", + "phpunit/php-file-iterator": "^5.1.0", "phpunit/php-text-template": "^4.0.1", "sebastian/code-unit-reverse-lookup": "^4.0.1", "sebastian/complexity": "^4.0.1", "sebastian/environment": "^7.2.0", "sebastian/lines-of-code": "^3.0.1", - "sebastian/version": "^5.0.1", + "sebastian/version": "^5.0.2", "theseer/tokenizer": "^1.2.3" }, "require-dev": { - "phpunit/phpunit": "^11.0" + "phpunit/phpunit": "^11.4.1" }, "suggest": { "ext-pcov": "PHP extension that provides line coverage", @@ -10904,7 +10907,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.6" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.7" }, "funding": [ { @@ -10912,7 +10915,7 @@ "type": "github" } ], - "time": "2024-08-22T04:37:56+00:00" + "time": "2024-10-09T06:21:38+00:00" }, { "name": "phpunit/php-file-iterator", @@ -11161,16 +11164,16 @@ }, { "name": "phpunit/phpunit", - "version": "11.4.0", + "version": "11.4.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "89fe0c530133c08f7fff89d3d727154e4e504925" + "reference": "1863643c3f04ad03dcb9c6996c294784cdda4805" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/89fe0c530133c08f7fff89d3d727154e4e504925", - "reference": "89fe0c530133c08f7fff89d3d727154e4e504925", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/1863643c3f04ad03dcb9c6996c294784cdda4805", + "reference": "1863643c3f04ad03dcb9c6996c294784cdda4805", "shasum": "" }, "require": { @@ -11184,21 +11187,21 @@ "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=8.2", - "phpunit/php-code-coverage": "^11.0.6", + "phpunit/php-code-coverage": "^11.0.7", "phpunit/php-file-iterator": "^5.1.0", "phpunit/php-invoker": "^5.0.1", "phpunit/php-text-template": "^4.0.1", "phpunit/php-timer": "^7.0.1", "sebastian/cli-parser": "^3.0.2", "sebastian/code-unit": "^3.0.1", - "sebastian/comparator": "^6.1.0", + "sebastian/comparator": "^6.1.1", "sebastian/diff": "^6.0.2", "sebastian/environment": "^7.2.0", "sebastian/exporter": "^6.1.3", "sebastian/global-state": "^7.0.2", "sebastian/object-enumerator": "^6.0.1", "sebastian/type": "^5.1.0", - "sebastian/version": "^5.0.1" + "sebastian/version": "^5.0.2" }, "suggest": { "ext-soap": "To be able to generate mocks based on WSDL files" @@ -11241,7 +11244,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/11.4.0" + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.4.2" }, "funding": [ { @@ -11257,20 +11260,20 @@ "type": "tidelift" } ], - "time": "2024-10-05T08:39:03+00:00" + "time": "2024-10-19T13:05:19+00:00" }, { "name": "rector/rector", - "version": "1.2.6", + "version": "1.2.8", "source": { "type": "git", "url": "https://github.com/rectorphp/rector.git", - "reference": "6ca85da28159dbd3bb36211c5104b7bc91278e99" + "reference": "05755bf43617449c08ee8e50fb840c85ad3b1240" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rectorphp/rector/zipball/6ca85da28159dbd3bb36211c5104b7bc91278e99", - "reference": "6ca85da28159dbd3bb36211c5104b7bc91278e99", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/05755bf43617449c08ee8e50fb840c85ad3b1240", + "reference": "05755bf43617449c08ee8e50fb840c85ad3b1240", "shasum": "" }, "require": { @@ -11308,7 +11311,7 @@ ], "support": { "issues": "https://github.com/rectorphp/rector/issues", - "source": "https://github.com/rectorphp/rector/tree/1.2.6" + "source": "https://github.com/rectorphp/rector/tree/1.2.8" }, "funding": [ { @@ -11316,7 +11319,7 @@ "type": "github" } ], - "time": "2024-10-03T08:56:44+00:00" + "time": "2024-10-18T11:54:27+00:00" }, { "name": "sebastian/cli-parser", @@ -11490,16 +11493,16 @@ }, { "name": "sebastian/comparator", - "version": "6.1.0", + "version": "6.1.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "fa37b9e2ca618cb051d71b60120952ee8ca8b03d" + "reference": "5ef523a49ae7a302b87b2102b72b1eda8918d686" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa37b9e2ca618cb051d71b60120952ee8ca8b03d", - "reference": "fa37b9e2ca618cb051d71b60120952ee8ca8b03d", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/5ef523a49ae7a302b87b2102b72b1eda8918d686", + "reference": "5ef523a49ae7a302b87b2102b72b1eda8918d686", "shasum": "" }, "require": { @@ -11555,7 +11558,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/6.1.0" + "source": "https://github.com/sebastianbergmann/comparator/tree/6.1.1" }, "funding": [ { @@ -11563,7 +11566,7 @@ "type": "github" } ], - "time": "2024-09-11T15:42:56+00:00" + "time": "2024-10-18T15:00:48+00:00" }, { "name": "sebastian/complexity", @@ -12189,16 +12192,16 @@ }, { "name": "sebastian/version", - "version": "5.0.1", + "version": "5.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/version.git", - "reference": "45c9debb7d039ce9b97de2f749c2cf5832a06ac4" + "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/45c9debb7d039ce9b97de2f749c2cf5832a06ac4", - "reference": "45c9debb7d039ce9b97de2f749c2cf5832a06ac4", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c687e3387b99f5b03b6caa64c74b63e2936ff874", + "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874", "shasum": "" }, "require": { @@ -12231,7 +12234,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/version/issues", "security": "https://github.com/sebastianbergmann/version/security/policy", - "source": "https://github.com/sebastianbergmann/version/tree/5.0.1" + "source": "https://github.com/sebastianbergmann/version/tree/5.0.2" }, "funding": [ { @@ -12239,7 +12242,7 @@ "type": "github" } ], - "time": "2024-07-03T05:13:08+00:00" + "time": "2024-10-09T05:16:32+00:00" }, { "name": "spatie/backtrace", diff --git a/config/chart-of-accounts.php b/config/chart-of-accounts.php index 4ddd4c3b..d62adc93 100644 --- a/config/chart-of-accounts.php +++ b/config/chart-of-accounts.php @@ -67,11 +67,21 @@ 'description' => 'Accounts that accumulate depreciation of tangible assets and amortization of intangible assets, reflecting the reduction in value over time.', 'multi_currency' => false, 'base_code' => '1900', + 'accounts' => [ + 'Accumulated Depreciation' => [ + 'description' => 'Used to account for the depreciation of fixed assets over time, offsetting assets like equipment or property.', + ], + ], ], 'Allowances for Receivables' => [ 'description' => 'Accounts representing estimated uncollected receivables, used to adjust the value of gross receivables to a realistic collectible amount.', 'multi_currency' => false, 'base_code' => '1940', + 'accounts' => [ + 'Allowance for Doubtful Accounts' => [ + 'description' => 'Used to account for potential bad debts that may not be collectable, offsetting receivables.', + ], + ], ], 'Valuation Adjustments' => [ '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 @@ 'Owner\'s Investment' => [ 'description' => 'The amount of money invested by the owner(s) or shareholders to start or expand the business.', ], - 'Owner\'s Drawings' => [ - 'description' => 'The amount of money withdrawn by the owner(s) or shareholders from the business for personal use.', - ], - ], - ], - 'Retained Earnings: Profit' => [ - 'description' => 'Cumulative profits retained in the business and not distributed as dividends. Indicates the company\'s financial health and profit-generating ability.', - 'multi_currency' => false, - 'base_code' => '3100', - 'accounts' => [ - 'Owner\'s Equity' => [ - '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.', - ], ], ], ], @@ -175,6 +172,11 @@ '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.', 'multi_currency' => false, 'base_code' => '3900', + 'accounts' => [ + 'Owner\'s Drawings' => [ + 'description' => 'The amount of money withdrawn by the owner(s) or shareholders from the business for personal use, reducing equity.', + ], + ], ], ], 'operating_revenue' => [ diff --git a/database/factories/Accounting/TransactionFactory.php b/database/factories/Accounting/TransactionFactory.php index 7a17e212..bc200c69 100644 --- a/database/factories/Accounting/TransactionFactory.php +++ b/database/factories/Accounting/TransactionFactory.php @@ -57,16 +57,15 @@ public function forCompanyAndBankAccount(Company $company, BankAccount $bankAcco $account = Account::where('category', $this->faker->randomElement($associatedAccountTypes)) ->where('company_id', $company->id) - ->where('id', '<>', $accountIdForBankAccount) + ->whereKeyNot($accountIdForBankAccount) ->inRandomOrder() ->first(); - // If no matching account is found, use a fallback if (! $account) { $account = Account::where('company_id', $company->id) - ->where('id', '<>', $accountIdForBankAccount) + ->whereKeyNot($accountIdForBankAccount) ->inRandomOrder() - ->firstOrFail(); // Ensure there is at least some account + ->firstOrFail(); } return [ diff --git a/database/factories/Setting/CompanyDefaultFactory.php b/database/factories/Setting/CompanyDefaultFactory.php index c30fc52a..4cf4252a 100644 --- a/database/factories/Setting/CompanyDefaultFactory.php +++ b/database/factories/Setting/CompanyDefaultFactory.php @@ -72,6 +72,7 @@ private function createCurrency(Company $company, User $user, string $currencyCo { return Currency::factory()->forCurrency($currencyCode)->createQuietly([ 'company_id' => $company->id, + 'enabled' => true, 'created_by' => $user->id, 'updated_by' => $user->id, ]); @@ -81,6 +82,7 @@ private function createSalesTax(Company $company, User $user): Tax { return Tax::factory()->salesTax()->createQuietly([ 'company_id' => $company->id, + 'enabled' => true, 'created_by' => $user->id, 'updated_by' => $user->id, ]); @@ -90,6 +92,7 @@ private function createPurchaseTax(Company $company, User $user): Tax { return Tax::factory()->purchaseTax()->createQuietly([ 'company_id' => $company->id, + 'enabled' => true, 'created_by' => $user->id, 'updated_by' => $user->id, ]); @@ -99,6 +102,7 @@ private function createSalesDiscount(Company $company, User $user): Discount { return Discount::factory()->salesDiscount()->createQuietly([ 'company_id' => $company->id, + 'enabled' => true, 'created_by' => $user->id, 'updated_by' => $user->id, ]); @@ -108,6 +112,7 @@ private function createPurchaseDiscount(Company $company, User $user): Discount { return Discount::factory()->purchaseDiscount()->createQuietly([ 'company_id' => $company->id, + 'enabled' => true, 'created_by' => $user->id, 'updated_by' => $user->id, ]); diff --git a/database/factories/Setting/CurrencyFactory.php b/database/factories/Setting/CurrencyFactory.php index c4ebfa17..ea5d9ad2 100644 --- a/database/factories/Setting/CurrencyFactory.php +++ b/database/factories/Setting/CurrencyFactory.php @@ -33,7 +33,7 @@ public function definition(): array 'symbol_first' => $defaultCurrency->isSymbolFirst(), 'decimal_mark' => $defaultCurrency->getDecimalMark(), 'thousands_separator' => $defaultCurrency->getThousandsSeparator(), - 'enabled' => true, + 'enabled' => false, ]; } @@ -53,7 +53,7 @@ public function forCurrency(string $code, ?float $rate = null): static 'symbol_first' => $currency->isSymbolFirst(), 'decimal_mark' => $currency->getDecimalMark(), 'thousands_separator' => $currency->getThousandsSeparator(), - 'enabled' => true, + 'enabled' => false, ]); } } diff --git a/database/migrations/2024_10_13_163049_update_posted_at_column_in_transactions_table.php b/database/migrations/2024_10_13_163049_update_posted_at_column_in_transactions_table.php new file mode 100644 index 00000000..acbc1bb6 --- /dev/null +++ b/database/migrations/2024_10_13_163049_update_posted_at_column_in_transactions_table.php @@ -0,0 +1,28 @@ +date('posted_at')->change(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('transactions', function (Blueprint $table) { + $table->dateTime('posted_at')->change(); + }); + } +}; diff --git a/package-lock.json b/package-lock.json index 0a088a78..87607f2c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -955,9 +955,9 @@ } }, "node_modules/browserslist": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.0.tgz", - "integrity": "sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A==", + "version": "4.24.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", + "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", "dev": true, "funding": [ { @@ -975,10 +975,10 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001663", - "electron-to-chromium": "^1.5.28", + "caniuse-lite": "^1.0.30001669", + "electron-to-chromium": "^1.5.41", "node-releases": "^2.0.18", - "update-browserslist-db": "^1.1.0" + "update-browserslist-db": "^1.1.1" }, "bin": { "browserslist": "cli.js" @@ -998,9 +998,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001667", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001667.tgz", - "integrity": "sha512-7LTwJjcRkzKFmtqGsibMeuXmvFDfZq/nzIjnmgCGzKKRVzjD72selLDK1oPF/Oxzmt4fNcPvTDvGqSDG4tCALw==", + "version": "1.0.30001669", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001669.tgz", + "integrity": "sha512-DlWzFDJqstqtIVx1zeSpIMLjunf5SmwOw0N2Ck/QSQdS8PLS4+9HrLaYei4w8BIAL7IB/UEDu889d8vhCTPA0w==", "dev": true, "funding": [ { @@ -1159,9 +1159,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.33", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.33.tgz", - "integrity": "sha512-+cYTcFB1QqD4j4LegwLfpCNxifb6dDFUAwk6RsLusCwIaZI6or2f+q8rs5tTB2YC53HhOlIbEaqHMAAC8IOIwA==", + "version": "1.5.41", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.41.tgz", + "integrity": "sha512-dfdv/2xNjX0P8Vzme4cfzHqnPm5xsZXwsolTYr0eyW18IUmNyG08vL+fttvinTfhKfIKdRoqkDIC9e9iWQCNYQ==", "dev": true, "license": "ISC" }, @@ -1313,9 +1313,9 @@ } }, "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", "dev": true, "license": "MIT", "dependencies": { @@ -1786,9 +1786,9 @@ } }, "node_modules/picocolors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", - "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true, "license": "ISC" }, @@ -2417,9 +2417,9 @@ } }, "node_modules/tailwindcss": { - "version": "3.4.13", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.13.tgz", - "integrity": "sha512-KqjHOJKogOUt5Bs752ykCeiwvi0fKVkr5oqsFNt/8px/tA8scFPIlkygsf6jXrfCqGHz7VflA6+yytWuM+XhFw==", + "version": "3.4.14", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.14.tgz", + "integrity": "sha512-IcSvOcTRcUtQQ7ILQL5quRDg7Xs93PdJEk1ZLbhhvJc7uj/OAhYOnruEiwnGgBvUtaUAJ8/mhSw1o8L2jCiENA==", "dev": true, "license": "MIT", "dependencies": { @@ -2550,9 +2550,9 @@ "license": "MIT" }, "node_modules/vite": { - "version": "5.4.8", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.8.tgz", - "integrity": "sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==", + "version": "5.4.9", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.9.tgz", + "integrity": "sha512-20OVpJHh0PAM0oSOELa5GaZNWeDjcAvQjGXy2Uyr+Tp+/D2/Hdz6NLgpJLsarPTA2QJ6v8mX2P1ZfbsSKvdMkg==", "dev": true, "license": "MIT", "dependencies": { @@ -2735,9 +2735,9 @@ } }, "node_modules/yaml": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.1.tgz", - "integrity": "sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.0.tgz", + "integrity": "sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==", "dev": true, "license": "ISC", "bin": { diff --git a/resources/css/filament/company/theme.css b/resources/css/filament/company/theme.css index 3a7608a0..8b63d65f 100644 --- a/resources/css/filament/company/theme.css +++ b/resources/css/filament/company/theme.css @@ -107,11 +107,7 @@ } .es-table__header-ctn, .es-table__footer-ctn { - @apply divide-y divide-gray-200 dark:divide-white/10 h-12; -} - -.es-table__row { - @apply [@media(hover:hover)]:transition [@media(hover:hover)]:duration-75 hover:bg-gray-50 dark:hover:bg-white/5; + @apply divide-y divide-gray-200 dark:divide-white/10 min-h-12; } .es-table .es-table__rowgroup td:first-child { diff --git a/resources/views/components/company/reports/account-transactions-report-pdf.blade.php b/resources/views/components/company/reports/account-transactions-report-pdf.blade.php index 5cb556b1..d1a6a523 100644 --- a/resources/views/components/company/reports/account-transactions-report-pdf.blade.php +++ b/resources/views/components/company/reports/account-transactions-report-pdf.blade.php @@ -49,7 +49,7 @@ .company-name { font-size: 1.125rem; - font-weight: 600; + font-weight: bold; } .date-range { @@ -71,7 +71,7 @@ .category-header-row > td { background-color: #f3f4f6; /* Gray background for category names */ - font-weight: 600; + font-weight: bold; } .table-body tr { @@ -84,7 +84,7 @@ .category-summary-row > td, .table-footer-row > td { - font-weight: 600; + font-weight: bold; background-color: #ffffff; /* White background for footer */ } diff --git a/resources/views/components/company/reports/report-pdf.blade.php b/resources/views/components/company/reports/report-pdf.blade.php index 07238eeb..7b411297 100644 --- a/resources/views/components/company/reports/report-pdf.blade.php +++ b/resources/views/components/company/reports/report-pdf.blade.php @@ -49,7 +49,7 @@ .company-name { font-size: 1.125rem; - font-weight: 600; + font-weight: bold; } .date-range { @@ -69,9 +69,16 @@ border-bottom: 1px solid #d1d5db; /* Gray border for all rows */ } - .category-header-row > td { + .category-header-row > td, + .type-header-row > td { background-color: #f3f4f6; /* Gray background for category names */ - font-weight: 600; + font-weight: bold; + } + + .type-header-row > td, + .type-data-row > td, + .type-summary-row > td { + padding-left: 1.5rem; /* Indentation for type rows */ } .table-body tr { @@ -83,6 +90,7 @@ } .category-summary-row > td, + .type-summary-row > td, .table-footer-row > td { font-weight: bold; background-color: #ffffff; /* White background for footer */ @@ -131,6 +139,43 @@ @endforeach @endforeach + + + @foreach($category->types ?? [] as $type) + + + @foreach($type->header as $index => $header) + + {{ $header }} + + @endforeach + + + + @foreach($type->data as $typeRow) + + @foreach($typeRow as $index => $cell) + + @if(is_array($cell) && isset($cell['name'])) + {{ $cell['name'] }} + @else + {{ $cell }} + @endif + + @endforeach + + @endforeach + + + + @foreach($type->summary as $index => $cell) + + {{ $cell }} + + @endforeach + + @endforeach + @foreach($category->summary as $index => $cell) diff --git a/resources/views/components/company/tables/category-header.blade.php b/resources/views/components/company/tables/category-header.blade.php new file mode 100644 index 00000000..3b4e151a --- /dev/null +++ b/resources/views/components/company/tables/category-header.blade.php @@ -0,0 +1,20 @@ +@props([ + 'categoryHeaders', + 'alignmentClass' => null, +]) + + + + @foreach($categoryHeaders as $index => $header) + $alignmentClass, + ]) + > + + {{ $header }} + + + @endforeach + diff --git a/resources/views/components/company/tables/cell.blade.php b/resources/views/components/company/tables/cell.blade.php new file mode 100644 index 00000000..76a5fa4e --- /dev/null +++ b/resources/views/components/company/tables/cell.blade.php @@ -0,0 +1,23 @@ +@props([ + 'alignmentClass', + 'indent' => false, + 'bold' => false, +]) + + $indent, + 'p-0 first-of-type:ps-1 sm:first-of-type:ps-3' => ! $indent, + ]) +> +
$bold, + ]) + > + {{ $slot }} +
+ diff --git a/resources/views/components/company/tables/container.blade.php b/resources/views/components/company/tables/container.blade.php new file mode 100644 index 00000000..c0c4f8b2 --- /dev/null +++ b/resources/views/components/company/tables/container.blade.php @@ -0,0 +1,24 @@ +@props([ + 'reportLoaded' => false, +]) + + +
+
+
+
+
+ +
+
+ + @if($reportLoaded) +
+ {{ $slot }} +
+ @endif +
+
+ +
diff --git a/resources/views/components/company/tables/footer.blade.php b/resources/views/components/company/tables/footer.blade.php new file mode 100644 index 00000000..b27bfffc --- /dev/null +++ b/resources/views/components/company/tables/footer.blade.php @@ -0,0 +1,15 @@ +@props(['totals', 'alignmentClass']) + +@if(!empty($totals)) + + + @foreach($totals as $totalIndex => $totalCell) + +
+ {{ $totalCell }} +
+
+ @endforeach + + +@endif diff --git a/resources/views/components/company/tables/header.blade.php b/resources/views/components/company/tables/header.blade.php new file mode 100644 index 00000000..4478ff78 --- /dev/null +++ b/resources/views/components/company/tables/header.blade.php @@ -0,0 +1,16 @@ +@props([ + 'headers', + 'alignmentClass', +]) + + + + @foreach($headers as $headerIndex => $headerCell) + + + {{ $headerCell }} + + + @endforeach + + diff --git a/resources/views/components/company/tables/reports/account-transactions.blade.php b/resources/views/components/company/tables/reports/account-transactions.blade.php index 3ca215c1..fa271018 100644 --- a/resources/views/components/company/tables/reports/account-transactions.blade.php +++ b/resources/views/components/company/tables/reports/account-transactions.blade.php @@ -1,26 +1,14 @@ - - - @foreach($report->getHeaders() as $index => $header) - - @endforeach - - + @foreach($report->getCategories() as $categoryIndex => $category) - + - -
+ +
@foreach ($category->header as $headerRow)
+ class="text-sm {{ $loop->first ? 'font-semibold text-gray-950 dark:text-white' : 'font-normal text-gray-500 dark:text-white/50' }}"> @foreach ($headerRow as $headerValue) @if (!empty($headerValue)) {{ $headerValue }} @@ -33,14 +21,13 @@ class="text-sm {{ $loop->first ? 'font-semibold text-gray-950 dark:text-white' :
@foreach($category->data as $dataIndex => $transaction) - $loop->first || $loop->last || $loop->remaining === 1, ]) > @foreach($transaction as $cellIndex => $cell) getAlignmentClass($cellIndex), 'whitespace-normal' => $cellIndex === 1, @@ -80,10 +67,10 @@ class="text-sm {{ $loop->first ? 'font-semibold text-gray-950 dark:text-white' : @endforeach @unless($loop->last) - - - - + + @endunless diff --git a/resources/views/components/company/tables/reports/balance-sheet-summary.blade.php b/resources/views/components/company/tables/reports/balance-sheet-summary.blade.php new file mode 100644 index 00000000..f8a806fe --- /dev/null +++ b/resources/views/components/company/tables/reports/balance-sheet-summary.blade.php @@ -0,0 +1,31 @@ +
- - {{ $header }} - -
+
+
+ + @foreach($report->getSummaryCategories() as $accountCategory) + + + @foreach($accountCategory->types as $accountType) + + @foreach($accountType->summary as $accountTypeSummaryIndex => $accountTypeSummaryCell) + + {{ $accountTypeSummaryCell }} + + @endforeach + + @endforeach + + @foreach($accountCategory->summary as $accountCategorySummaryIndex => $accountCategorySummaryCell) + + {{ $accountCategorySummaryCell }} + + @endforeach + + + + + + @endforeach +
+
+
diff --git a/resources/views/components/company/tables/reports/balance-sheet.blade.php b/resources/views/components/company/tables/reports/balance-sheet.blade.php new file mode 100644 index 00000000..86cbb3cc --- /dev/null +++ b/resources/views/components/company/tables/reports/balance-sheet.blade.php @@ -0,0 +1,131 @@ + + + @foreach($report->getCategories() as $accountCategory) + + + @foreach($accountCategory->data as $categoryAccount) + + @foreach($categoryAccount as $accountIndex => $categoryAccountCell) + + @if(is_array($categoryAccountCell) && isset($categoryAccountCell['name'])) + @if($categoryAccountCell['name'] === 'Retained Earnings' && isset($categoryAccountCell['start_date']) && isset($categoryAccountCell['end_date'])) + + {{ $categoryAccountCell['name'] }} + + @elseif(isset($categoryAccountCell['id']) && isset($categoryAccountCell['start_date']) && isset($categoryAccountCell['end_date'])) + + {{ $categoryAccountCell['name'] }} + + @else + {{ $categoryAccountCell['name'] }} + @endif + @else + {{ $categoryAccountCell }} + @endif + + @endforeach + + @endforeach + @foreach($accountCategory->types as $accountType) + + @foreach($accountType->header as $accountTypeHeaderIndex => $accountTypeHeaderCell) + + {{ $accountTypeHeaderCell }} + + @endforeach + + @foreach($accountType->data as $typeAccount) + + @foreach($typeAccount as $accountIndex => $typeAccountCell) + + @if(is_array($typeAccountCell) && isset($typeAccountCell['name'])) + @if($typeAccountCell['name'] === 'Retained Earnings' && isset($typeAccountCell['start_date']) && isset($typeAccountCell['end_date'])) + + {{ $typeAccountCell['name'] }} + + @elseif(isset($typeAccountCell['id']) && isset($typeAccountCell['start_date']) && isset($typeAccountCell['end_date'])) + + {{ $typeAccountCell['name'] }} + + @else + {{ $typeAccountCell['name'] }} + @endif + @else + {{ $typeAccountCell }} + @endif + + @endforeach + + @endforeach + + @foreach($accountType->summary as $accountTypeSummaryIndex => $accountTypeSummaryCell) + + {{ $accountTypeSummaryCell }} + + @endforeach + + @endforeach + + @foreach($accountCategory->summary as $accountCategorySummaryIndex => $accountCategorySummaryCell) + + {{ $accountCategorySummaryCell }} + + @endforeach + + + + + + @endforeach + +
+
+
diff --git a/resources/views/components/company/tables/reports/detailed-report.blade.php b/resources/views/components/company/tables/reports/detailed-report.blade.php index 4c199319..4fceed5a 100644 --- a/resources/views/components/company/tables/reports/detailed-report.blade.php +++ b/resources/views/components/company/tables/reports/detailed-report.blade.php @@ -1,97 +1,67 @@ - - - @foreach($report->getHeaders() as $index => $header) - - @endforeach - - - @foreach($report->getCategories() as $categoryIndex => $category) + + @foreach($report->getCategories() as $accountCategory) - - @foreach($category->header as $headerIndex => $header) - -
- {{ $header }} -
-
- @endforeach - - @foreach($category->data as $dataIndex => $account) + + @foreach($accountCategory->data as $categoryAccount) - @foreach($account as $cellIndex => $cell) - -
- @if(is_array($cell) && isset($cell['name'])) - @if($cell['name'] === 'Retained Earnings' && isset($cell['start_date']) && isset($cell['end_date'])) - getAlignmentClass($accountIndex)"> + @if(is_array($categoryAccountCell) && isset($categoryAccountCell['name'])) + @if($categoryAccountCell['name'] === 'Retained Earnings' && isset($categoryAccountCell['start_date']) && isset($categoryAccountCell['end_date'])) + - {{ $cell['name'] }} - - @elseif(isset($cell['id']) && isset($cell['start_date']) && isset($cell['end_date'])) - - {{ $cell['name'] }} - - @else - {{ $cell['name'] }} - @endif + > + {{ $categoryAccountCell['name'] }} + @else - {{ $cell }} + {{ $categoryAccountCell['name'] }} @endif -
-
+ @else + {{ $categoryAccountCell }} + @endif + @endforeach @endforeach - @foreach($category->summary as $summaryIndex => $cell) - -
- {{ $cell }} -
-
+ @foreach($accountCategory->summary as $accountCategorySummaryIndex => $accountCategorySummaryCell) + + {{ $accountCategorySummaryCell }} + @endforeach - - - + @endforeach - - - @foreach($report->getOverallTotals() as $index => $total) - -
- {{ $total }} -
-
- @endforeach - - +
- - {{ $header }} - -
+
+
diff --git a/resources/views/components/company/tables/reports/income-statement-summary.blade.php b/resources/views/components/company/tables/reports/income-statement-summary.blade.php new file mode 100644 index 00000000..de4abced --- /dev/null +++ b/resources/views/components/company/tables/reports/income-statement-summary.blade.php @@ -0,0 +1,28 @@ + + + @foreach($report->getSummaryCategories() as $accountCategory) + + + @foreach($accountCategory->summary as $accountCategorySummaryIndex => $accountCategorySummaryCell) + + {{ $accountCategorySummaryCell }} + + @endforeach + + + @if($accountCategory->header['account_name'] === 'Cost of Goods Sold') + + @foreach($report->getGrossProfit() as $grossProfitIndex => $grossProfitCell) + +
+ {{ $grossProfitCell }} +
+
+ @endforeach + + @endif + + @endforeach + +
diff --git a/resources/views/filament/company/pages/accounting/chart.blade.php b/resources/views/filament/company/pages/accounting/chart.blade.php index add4e00e..c067d5c1 100644 --- a/resources/views/filament/company/pages/accounting/chart.blade.php +++ b/resources/views/filament/company/pages/accounting/chart.blade.php @@ -15,10 +15,12 @@ @foreach($this->categories as $categoryValue => $subtypes) @if($activeTab === $categoryValue) -
+
- +
@@ -28,12 +30,14 @@ @foreach($subtypes as $subtype) - + - + - diff --git a/resources/views/filament/company/pages/reports/account-transactions.blade.php b/resources/views/filament/company/pages/reports/account-transactions.blade.php index ecad1c80..85aa380c 100644 --- a/resources/views/filament/company/pages/reports/account-transactions.blade.php +++ b/resources/views/filament/company/pages/reports/account-transactions.blade.php @@ -5,31 +5,16 @@ @endif - -
-
-
-
- -
-
- - @if($this->reportLoaded) -
- @if($this->report && !$this->tableHasEmptyState()) - - @else - - @endif -
- @endif -
- -
+ + @if($this->report && ! $this->tableHasEmptyState()) + + @else + + @endif + diff --git a/resources/views/filament/company/pages/reports/balance-sheet.blade.php b/resources/views/filament/company/pages/reports/balance-sheet.blade.php new file mode 100644 index 00000000..2e9a6f87 --- /dev/null +++ b/resources/views/filament/company/pages/reports/balance-sheet.blade.php @@ -0,0 +1,89 @@ + + +
+ + @if(method_exists($this, 'filtersForm')) + {{ $this->filtersForm }} + @endif + + + @if($this->hasToggleableColumns()) +
+ +
+ @endif + +
+ {{ $this->applyFiltersAction }} +
+
+
+ + + + + @if($this->reportLoaded) +
+ @foreach($this->report->getSummary() as $summary) +
+
{{ $summary['label'] }}
+ + @php + $isNetAssets = $summary['label'] === 'Net Assets'; + $isPositive = money($summary['value'], \App\Utilities\Currency\CurrencyAccessor::getDefaultCurrency())->isPositive(); + @endphp + + $isNetAssets && $isPositive, + 'text-danger-700' => $isNetAssets && ! $isPositive, + ]) + > + {{ $summary['value'] }} + +
+ + @if(! $loop->last) +
+ + {{ $loop->remaining === 1 ? '=' : '-' }} + +
+ @endif + @endforeach +
+ @endif +
+ + + + Summary + + + + Details + + + + + @if($this->report) + @if($activeTab === 'summary') + + @elseif($activeTab === 'details') + + @endif + @endif + +
+ diff --git a/resources/views/filament/company/pages/reports/detailed-report.blade.php b/resources/views/filament/company/pages/reports/detailed-report.blade.php index ed5c6c91..7bb7bda8 100644 --- a/resources/views/filament/company/pages/reports/detailed-report.blade.php +++ b/resources/views/filament/company/pages/reports/detailed-report.blade.php @@ -20,24 +20,9 @@ - -
-
-
-
- -
-
- - @if($this->reportLoaded) -
- @if($this->report) - - @endif -
- @endif -
- -
+ + @if($this->report) + + @endif + diff --git a/resources/views/filament/company/pages/reports/income-statement.blade.php b/resources/views/filament/company/pages/reports/income-statement.blade.php index 8c10a517..8297e6f5 100644 --- a/resources/views/filament/company/pages/reports/income-statement.blade.php +++ b/resources/views/filament/company/pages/reports/income-statement.blade.php @@ -58,24 +58,29 @@ class="flex flex-col md:flex-row items-center md:items-end text-center justify-c @endif - -
-
-
-
- -
-
+ + + Summary + - @if($this->reportLoaded) -
- @if($this->report) - - @endif -
+ + Details + +
+ + + @if($this->report) + @if($activeTab === 'summary') + + @elseif($activeTab === 'details') + @endif -
- -
+ @endif + diff --git a/resources/views/filament/company/pages/reports/trial-balance.blade.php b/resources/views/filament/company/pages/reports/trial-balance.blade.php index f0a1a8a1..85693e43 100644 --- a/resources/views/filament/company/pages/reports/trial-balance.blade.php +++ b/resources/views/filament/company/pages/reports/trial-balance.blade.php @@ -22,24 +22,9 @@ - -
-
-
-
- -
-
- - @if($this->reportLoaded) -
- @if($this->report) - - @endif -
- @endif -
- -
+ + @if($this->report) + + @endif + diff --git a/tests/Feature/Accounting/TransactionTest.php b/tests/Feature/Accounting/TransactionTest.php index 79b68197..e3a8517c 100644 --- a/tests/Feature/Accounting/TransactionTest.php +++ b/tests/Feature/Accounting/TransactionTest.php @@ -226,7 +226,7 @@ livewire(Transactions::class) ->mountAction($actionName) ->assertActionDataSet([ - 'posted_at' => now()->toDateTimeString(), + 'posted_at' => today(), 'type' => $transactionType, 'bank_account_id' => $defaultBankAccount->id, 'amount' => '0.00', @@ -260,7 +260,7 @@ livewire(Transactions::class) ->mountAction('addTransfer') ->assertActionDataSet([ - 'posted_at' => now()->toDateTimeString(), + 'posted_at' => today(), 'type' => TransactionType::Transfer, 'bank_account_id' => $sourceBankAccount->id, 'amount' => '0.00', @@ -293,7 +293,7 @@ livewire(Transactions::class) ->mountAction('addJournalTransaction') ->assertActionDataSet([ - 'posted_at' => now()->toDateTimeString(), + 'posted_at' => today(), 'journalEntries' => [ ['type' => JournalEntryType::Debit, 'account_id' => $defaultDebitAccount->id, 'amount' => '0.00'], ['type' => JournalEntryType::Credit, 'account_id' => $defaultCreditAccount->id, 'amount' => '0.00'],
- + {{ $subtype->name }}
{{ $account->description }}{{ $account->description }} @if($account->archived) @@ -80,7 +85,8 @@ @empty
+ {{ __("You haven't added any {$subtype->name} accounts yet.") }}