diff --git a/app/Http/Requests/BankTransaction/MatchBankTransactionRequest.php b/app/Http/Requests/BankTransaction/MatchBankTransactionRequest.php index 34c328028533..585a7c967399 100644 --- a/app/Http/Requests/BankTransaction/MatchBankTransactionRequest.php +++ b/app/Http/Requests/BankTransaction/MatchBankTransactionRequest.php @@ -47,7 +47,7 @@ class MatchBankTransactionRequest extends Request public function prepareForValidation() { $inputs = $this->all(); - + nlog($inputs); foreach ($inputs['transactions'] as $key => $input) { if (array_key_exists('id', $inputs['transactions'][$key])) { $inputs['transactions'][$key]['id'] = $this->decodePrimaryKey($input['id']); @@ -73,6 +73,7 @@ class MatchBankTransactionRequest extends Request if (array_key_exists('expense_id', $inputs['transactions'][$key]) && strlen($inputs['transactions'][$key]['expense_id']) >= 1) { $inputs['transactions'][$key]['expense_id'] = $this->decodePrimaryKey($inputs['transactions'][$key]['expense_id']); + $e = Expense::withTrashed()->where('company_id', auth()->user()->company()->id)->where('id', $inputs['transactions'][$key]['expense_id'])->first(); /*Ensure we don't relink an existing expense*/ @@ -82,6 +83,8 @@ class MatchBankTransactionRequest extends Request } } + nlog($inputs); + $this->replace($inputs); } } diff --git a/app/Jobs/Bank/MatchBankTransactions.php b/app/Jobs/Bank/MatchBankTransactions.php index 1a18fc72db18..34fd6db48b99 100644 --- a/app/Jobs/Bank/MatchBankTransactions.php +++ b/app/Jobs/Bank/MatchBankTransactions.php @@ -152,7 +152,7 @@ class MatchBankTransactions implements ShouldQueue { $this->bt = BankTransaction::find($input['id']); - if (!$this->bt || $this->bt->status_id == BankTransaction::STATUS_CONVERTED) { + if (!$this->bt) { return $this; } @@ -176,9 +176,13 @@ class MatchBankTransactions implements ShouldQueue private function coalesceExpenses($expense): string { +nlog("BTExpense: " . $this->bt->expense_id); - if(!$this->bt->expense_id || strlen($this->bt->expense_id) < 1) - return $expense; + if (!$this->bt->expense_id || strlen($this->bt->expense_id) < 1) { + nlog("coalesceExpense: " . $expense); + return $expense; + } + nlog("coalesceExpenses: " . $this->bt->expense_id . "," . $expense); return collect(explode(",", $this->bt->expense_id))->push($expense)->implode(","); } @@ -259,7 +263,7 @@ class MatchBankTransactions implements ShouldQueue $expense->should_be_invoiced = $this->company->mark_expenses_invoiceable; $expense->save(); - $this->bt->expense_id = $expense->hashed_id; + $this->bt->expense_id = $this->coalesceExpenses($expense->hashed_id); if (array_key_exists('vendor_id', $input)) { $this->bt->vendor_id = $input['vendor_id']; diff --git a/app/Models/Expense.php b/app/Models/Expense.php index f4a48d1cf35a..6c8718ba7276 100644 --- a/app/Models/Expense.php +++ b/app/Models/Expense.php @@ -259,4 +259,10 @@ class Expense extends BaseModel { return $this->belongsTo(Project::class); } + + public function transaction() + { + return $this->belongsTo(BankTransaction::class); + } + } diff --git a/app/Repositories/ExpenseRepository.php b/app/Repositories/ExpenseRepository.php index b1eb36d05a5b..6b2770590b85 100644 --- a/app/Repositories/ExpenseRepository.php +++ b/app/Repositories/ExpenseRepository.php @@ -102,9 +102,19 @@ class ExpenseRepository extends BaseRepository public function delete($expense) :Expense { + if ($expense->transaction_id) { + + $exp_ids = collect(explode(',', $expense->transaction->expense_id))->filter(function ($id) use ($expense) { + return $id != $expense->hashed_id; + })->implode(','); + $expense->transaction_id = null; $expense->saveQuietly(); + + $expense->transaction->expense_id = $exp_ids; + $expense->transaction->saveQuietly(); + } parent::delete($expense); diff --git a/tests/Feature/Bank/BankTransactionTest.php b/tests/Feature/Bank/BankTransactionTest.php index 96cce05ae155..9a58c2b631e3 100644 --- a/tests/Feature/Bank/BankTransactionTest.php +++ b/tests/Feature/Bank/BankTransactionTest.php @@ -12,15 +12,16 @@ namespace Tests\Feature\Bank; +use Tests\TestCase; +use App\Models\Expense; +use App\Models\Invoice; +use Tests\MockAccountData; +use App\Factory\InvoiceFactory; +use App\Models\BankTransaction; +use App\Factory\InvoiceItemFactory; use App\Factory\BankIntegrationFactory; use App\Factory\BankTransactionFactory; -use App\Factory\InvoiceFactory; -use App\Factory\InvoiceItemFactory; -use App\Models\BankTransaction; -use App\Models\Invoice; use Illuminate\Foundation\Testing\DatabaseTransactions; -use Tests\MockAccountData; -use Tests\TestCase; class BankTransactionTest extends TestCase { @@ -38,6 +39,167 @@ class BankTransactionTest extends TestCase ); } + public function testLinkMultipleExpensesWithDeleteToTransaction() + { + $data = []; + + $bi = BankIntegrationFactory::create($this->company->id, $this->user->id, $this->account->id); + $bi->save(); + + $bt = BankTransactionFactory::create($this->company->id, $this->user->id); + $bt->bank_integration_id = $bi->id; + $bt->status_id = BankTransaction::STATUS_UNMATCHED; + $bt->description = 'Fuel'; + $bt->amount = 10; + $bt->currency_code = $this->client->currency()->code; + $bt->date = now()->format('Y-m-d'); + $bt->transaction_id = 1234567890; + $bt->category_id = 10000003; + $bt->base_type = 'DEBIT'; + $bt->save(); + + $this->expense->vendor_id = $this->vendor->id; + $this->expense->save(); + + $data = []; + + $data['transactions'][] = [ + 'id' => $bt->hashed_id, + 'expense_id' => $this->expense->hashed_id + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/bank_transactions/match', $data); + + $response->assertStatus(200); + + $this->assertEquals($this->expense->refresh()->transaction_id, $bt->id); + $this->assertEquals($this->expense->hashed_id, $bt->refresh()->expense_id); + $this->assertEquals($this->vendor->id, $bt->vendor_id); + $this->assertEquals(BankTransaction::STATUS_CONVERTED, $bt->status_id); + + + $e = Expense::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + ]); + + $data = []; + + $data['transactions'][] = [ + 'id' => $bt->hashed_id, + 'expense_id' => $e->hashed_id + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/bank_transactions/match', $data); + + $response->assertStatus(200); + + $this->assertEquals("{$this->expense->hashed_id},{$e->hashed_id}", $bt->fresh()->expense_id); + + $e2 = Expense::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + ]); + + $data = []; + + $data['transactions'][] = [ + 'id' => $bt->hashed_id, + 'expense_id' => $e2->hashed_id + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/bank_transactions/match', $data); + + $response->assertStatus(200); + + $this->assertNotNull($e2->refresh()->transaction_id); + + $this->assertEquals("{$this->expense->hashed_id},{$e->hashed_id},{$e2->hashed_id}", $bt->fresh()->expense_id); + + $expense_repo = app('App\Repositories\ExpenseRepository'); + + $expense_repo->delete($e2); + + $this->assertEquals("{$this->expense->hashed_id},{$e->hashed_id}", $bt->fresh()->expense_id); + + } + + + + public function testLinkMultipleExpensesToTransaction() + { + $data = []; + + $bi = BankIntegrationFactory::create($this->company->id, $this->user->id, $this->account->id); + $bi->save(); + + $bt = BankTransactionFactory::create($this->company->id, $this->user->id); + $bt->bank_integration_id = $bi->id; + $bt->status_id = BankTransaction::STATUS_UNMATCHED; + $bt->description = 'Fuel'; + $bt->amount = 10; + $bt->currency_code = $this->client->currency()->code; + $bt->date = now()->format('Y-m-d'); + $bt->transaction_id = 1234567890; + $bt->category_id = 10000003; + $bt->base_type = 'DEBIT'; + $bt->save(); + + $this->expense->vendor_id = $this->vendor->id; + $this->expense->save(); + + $data = []; + + $data['transactions'][] = [ + 'id' => $bt->hashed_id, + 'expense_id' => $this->expense->hashed_id + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/bank_transactions/match', $data); + + $response->assertStatus(200); + + $this->assertEquals($this->expense->refresh()->transaction_id, $bt->id); + $this->assertEquals($this->expense->hashed_id, $bt->refresh()->expense_id); + $this->assertEquals($this->vendor->id, $bt->vendor_id); + $this->assertEquals(BankTransaction::STATUS_CONVERTED, $bt->status_id); + + + $e = Expense::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + ]); + + $data = []; + + $data['transactions'][] = [ + 'id' => $bt->hashed_id, + 'expense_id' => $e->hashed_id + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/bank_transactions/match', $data); + + $response->assertStatus(200); + + $this->assertEquals("{$this->expense->hashed_id},{$e->hashed_id}", $bt->fresh()->expense_id); + + } + public function testBankTransactionBulkActions() {