diff --git a/app/Filament/App/Resources/BankStatementResource.php b/app/Filament/App/Resources/BankStatementResource.php new file mode 100644 index 00000000..a3b6740a --- /dev/null +++ b/app/Filament/App/Resources/BankStatementResource.php @@ -0,0 +1,76 @@ +schema([ + Forms\Components\DatePicker::make('statement_date') + ->required(), + Forms\Components\Select::make('account_id') + ->relationship('account', 'name') + ->required(), + Forms\Components\TextInput::make('total_credits') + ->required() + ->numeric(), + Forms\Components\TextInput::make('total_debits') + ->required() + ->numeric(), + Forms\Components\TextInput::make('ending_balance') + ->required() + ->numeric(), + ]); + } + + public static function table(Table $table): Table + { + return $table + ->columns([ + Tables\Columns\TextColumn::make('statement_date'), + Tables\Columns\TextColumn::make('account.name'), + Tables\Columns\TextColumn::make('total_credits'), + Tables\Columns\TextColumn::make('total_debits'), + Tables\Columns\TextColumn::make('ending_balance'), + ]) + ->filters([ + // + ]) + ->actions([ + Tables\Actions\EditAction::make(), + ]) + ->bulkActions([ + Tables\Actions\DeleteBulkAction::make(), + ]); + } + + public static function getRelations(): array + { + return [ + // + ]; + } + + public static function getPages(): array + { + return [ + 'index' => Pages\ListBankStatements::route('/'), + 'create' => Pages\CreateBankStatement::route('/create'), + 'edit' => Pages\EditBankStatement::route('/{record}/edit'), + ]; + } +} \ No newline at end of file diff --git a/app/Filament/App/Resources/TransactionResource.php b/app/Filament/App/Resources/TransactionResource.php index 8c917bf3..d7de8519 100644 --- a/app/Filament/App/Resources/TransactionResource.php +++ b/app/Filament/App/Resources/TransactionResource.php @@ -43,9 +43,13 @@ public static function form(Form $form): Form BelongsToSelect::make('credit_account_id') ->relationship('creditAccount', 'name') ->label('Credit Account'), + Forms\Components\Toggle::make('reconciled') + ->label('Reconciled'), + Forms\Components\Textarea::make('discrepancy_notes') + ->label('Discrepancy Notes'), ]); } - + public static function table(Table $table): Table { return $table @@ -69,9 +73,18 @@ public static function table(Table $table): Table ->label('Credit Account') ->searchable() ->sortable(), + Tables\Columns\IconColumn::make('reconciled') + ->boolean(), + TextColumn::make('discrepancy_notes') + ->label('Discrepancy Notes') + ->searchable(), ]) ->filters([ - // + Tables\Filters\SelectFilter::make('reconciled') + ->options([ + true => 'Reconciled', + false => 'Not Reconciled', + ]), ]) ->actions([ Tables\Actions\EditAction::make(), diff --git a/app/Models/BankStatement.php b/app/Models/BankStatement.php new file mode 100644 index 00000000..fd22c131 --- /dev/null +++ b/app/Models/BankStatement.php @@ -0,0 +1,29 @@ +belongsTo(Account::class); + } + + public function transactions() + { + return $this->hasMany(Transaction::class); + } +} \ No newline at end of file diff --git a/app/Models/Transaction.php b/app/Models/Transaction.php index b8752520..2997d759 100644 --- a/app/Models/Transaction.php +++ b/app/Models/Transaction.php @@ -17,7 +17,12 @@ class Transaction extends Model 'amount', 'debit_account_id', 'credit_account_id', - // Add any other necessary fields for double-entry accounting here + 'reconciled', + 'discrepancy_notes', + ]; + + protected $casts = [ + 'reconciled' => 'boolean', ]; public function debitAccount() diff --git a/app/Services/ReconciliationService.php b/app/Services/ReconciliationService.php new file mode 100644 index 00000000..3ba10e23 --- /dev/null +++ b/app/Services/ReconciliationService.php @@ -0,0 +1,49 @@ +account_id) + ->whereBetween('transaction_date', [$bankStatement->statement_date->startOfMonth(), $bankStatement->statement_date->endOfMonth()]) + ->get(); + + $totalCredits = 0; + $totalDebits = 0; + + foreach ($transactions as $transaction) { + if ($transaction->amount > 0) { + $totalCredits += $transaction->amount; + } else { + $totalDebits += abs($transaction->amount); + } + + $this->matchTransaction($transaction, $bankStatement); + } + + $discrepancy = ($totalCredits - $totalDebits) - ($bankStatement->total_credits - $bankStatement->total_debits); + + return [ + 'matched_transactions' => $transactions->where('reconciled', true)->count(), + 'unmatched_transactions' => $transactions->where('reconciled', false)->count(), + 'discrepancy' => $discrepancy, + ]; + } + + private function matchTransaction(Transaction $transaction, BankStatement $bankStatement) + { + // Implement matching logic here + // For example, match by date and amount + $matched = $bankStatement->transactions() + ->where('transaction_date', $transaction->transaction_date) + ->where('amount', $transaction->amount) + ->exists(); + + $transaction->update(['reconciled' => $matched]); + } +} \ No newline at end of file diff --git a/tests/Feature/Services/ReconciliationServiceTest.php b/tests/Feature/Services/ReconciliationServiceTest.php new file mode 100644 index 00000000..910f1bc3 --- /dev/null +++ b/tests/Feature/Services/ReconciliationServiceTest.php @@ -0,0 +1,46 @@ +create(); + $bankStatement = BankStatement::factory()->create([ + 'account_id' => $account->id, + 'statement_date' => now(), + 'total_credits' => 1000, + 'total_debits' => 500, + 'ending_balance' => 500, + ]); + + Transaction::factory()->count(3)->create([ + 'account_id' => $account->id, + 'transaction_date' => now(), + 'amount' => 300, + ]); + + Transaction::factory()->count(2)->create([ + 'account_id' => $account->id, + 'transaction_date' => now(), + 'amount' => -200, + ]); + + $reconciliationService = new ReconciliationService(); + $result = $reconciliationService->reconcile($bankStatement); + + $this->assertEquals(5, $result['matched_transactions']); + $this->assertEquals(0, $result['unmatched_transactions']); + $this->assertEquals(0, $result['discrepancy']); + } +} \ No newline at end of file