diff --git a/app/Http/Requests/BankTransaction/MatchBankTransactionRequest.php b/app/Http/Requests/BankTransaction/MatchBankTransactionRequest.php index effb8a47041d..e7cca9c3471d 100644 --- a/app/Http/Requests/BankTransaction/MatchBankTransactionRequest.php +++ b/app/Http/Requests/BankTransaction/MatchBankTransactionRequest.php @@ -32,7 +32,7 @@ class MatchBankTransactionRequest extends Request return [ '*.id' => 'required|bail', '*.invoice_id' => 'nullable|sometimes', - '*.expense_id' => 'nullable|sometimes', + '*.is_expense' => 'nullable|sometimes|bool', '*.amount' => 'nullable|sometimes|numeric' ]; diff --git a/app/Jobs/Bank/MatchBankTransactions.php b/app/Jobs/Bank/MatchBankTransactions.php index 30e8aada2e26..46edcee40575 100644 --- a/app/Jobs/Bank/MatchBankTransactions.php +++ b/app/Jobs/Bank/MatchBankTransactions.php @@ -13,6 +13,8 @@ namespace App\Jobs\Bank; use App\Events\Invoice\InvoiceWasPaid; use App\Events\Payment\PaymentWasCreated; +use App\Factory\ExpenseCategoryFactory; +use App\Factory\ExpenseFactory; use App\Factory\PaymentFactory; use App\Helpers\Bank\Yodlee\Yodlee; use App\Libraries\Currency\Conversion\CurrencyApi; @@ -21,10 +23,12 @@ use App\Models\BankIntegration; use App\Models\BankTransaction; use App\Models\Company; use App\Models\Currency; +use App\Models\ExpenseCategory; use App\Models\Invoice; use App\Models\Payment; use App\Services\Bank\BankService; use App\Utils\Ninja; +use App\Utils\Traits\GeneratesCounter; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; @@ -35,7 +39,7 @@ use Illuminate\Support\Carbon; class MatchBankTransactions implements ShouldQueue { - use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; + use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, GeneratesCounter; private int $company_id; @@ -78,7 +82,7 @@ class MatchBankTransactions implements ShouldQueue $yodlee = new Yodlee($this->company->account->bank_integration_account_id); - $_categories = collect($yodlee->getTransactionCategories()); + $_categories = $yodlee->getTransactionCategories(); if($_categories) $this->categories = collect($_categories->transactionCategory); @@ -87,7 +91,7 @@ class MatchBankTransactions implements ShouldQueue { if(array_key_exists('invoice_id', $match) && strlen($match['invoice_id']) > 1) $this->matchInvoicePayment($match); - elseif(array_key_exists('expense_id', $match) && strlen($match['expense_id']) > 1) + elseif(array_key_exists('is_expense', $match) && $match['is_expense']) $this->matchExpense($match); } @@ -106,7 +110,7 @@ class MatchBankTransactions implements ShouldQueue if($_invoice && $_invoice->isPayable()){ - $this->createPayment($match['id'], $amount); + $this->createPayment($match['invoice_id'], $amount); } @@ -117,7 +121,17 @@ class MatchBankTransactions implements ShouldQueue //if there is a category id, pull it from Yodlee and insert - or just reuse!! $this->bt = BankTransaction::find($match['id']); - $category_id = $this->resolveCategory(); + $expense = ExpenseFactory::create($this->bt->company_id, $this->bt->user_id); + $expense->category_id = $this->resolveCategory(); + $expense->amount = $this->bt->amount; + $expense->number = $this->getNextExpenseNumber($expense); + $expense->currency_id = $this->harvestCurrencyId(); + $expense->date = Carbon::parse($this->bt->date); + $expense->public_notes = $this->bt->description; + $expense->save(); + + nlog($expense->toArray()); + } private function createPayment(int $invoice_id, float $amount) :void @@ -178,7 +192,7 @@ class MatchBankTransactions implements ShouldQueue ->save(); $payment->ledger() - ->updatePaymentBalance($this->payable_balance * -1); + ->updatePaymentBalance($amount * -1); $this->invoice ->client @@ -201,7 +215,25 @@ class MatchBankTransactions implements ShouldQueue private function resolveCategory() :?int { - $this->categories->firstWhere('highLevelCategoryId', $this->bt->category_id) + $category = $this->categories->firstWhere('highLevelCategoryId', $this->bt->category_id); + + $ec = ExpenseCategory::where('company_id', $this->bt->company_id)->where('bank_category_id', $this->bt->category_id)->first(); + + if($ec) + return $ec->id; + + if($category) + { + + $ec = ExpenseCategoryFactory::create($this->bt->company_id, $this->bt->user_id); + $ec->bank_category_id = $this->bt->category_id; + $ec->name = $category->highLevelCategoryName; + $ec->save(); + + return $ec->id; + } + + return null; } private function harvestCurrencyId() :int @@ -228,7 +260,6 @@ class MatchBankTransactions implements ShouldQueue $exchange_rate = new CurrencyApi(); $payment->exchange_rate = $exchange_rate->exchangeRate($client_currency, $company_currency, Carbon::parse($payment->date)); - //$payment->exchange_currency_id = $client_currency; // 23/06/2021 $payment->exchange_currency_id = $company_currency; $payment->saveQuietly(); diff --git a/tests/Feature/Bank/YodleeApiTest.php b/tests/Feature/Bank/YodleeApiTest.php index 787ba827f521..e8c7c0fbd2a0 100644 --- a/tests/Feature/Bank/YodleeApiTest.php +++ b/tests/Feature/Bank/YodleeApiTest.php @@ -12,10 +12,17 @@ namespace Tests\Feature\Bank; +use App\Factory\BankIntegrationFactory; +use App\Factory\BankTransactionFactory; use App\Helpers\Bank\Yodlee\Yodlee; +use App\Helpers\Invoice\InvoiceSum; +use App\Jobs\Bank\MatchBankTransactions; use App\Jobs\Bank\ProcessBankTransactions; use App\Models\BankIntegration; use App\Models\BankTransaction; +use App\Models\Expense; +use App\Models\Invoice; +use App\Models\Payment; use App\Services\Bank\BankService; use Illuminate\Foundation\Testing\DatabaseTransactions; use Tests\MockAccountData; @@ -37,6 +44,105 @@ class YodleeApiTest extends TestCase } + public function testExpenseGenerationFromBankFeed() + { + + $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->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->save(); + + + $data = [ + [ + 'id' => $bt->id, + 'is_expense' => true + ] + ]; + + MatchBankTransactions::dispatchSync($this->company->id, $this->company->db, $data); + + $expense = Expense::where('public_notes', 'Fuel')->first(); + + $this->assertNotNull($expense); + $this->assertEquals(10, (int)$expense->amount); + + } + + public function testIncomeMatchingAndPaymentGeneration() + { + $this->account->bank_integration_account_id = 'sbMem62e1e69547bfb2'; + $this->account->save(); + + $invoice = Invoice::factory()->create(['user_id' => $this->user->id, 'company_id' => $this->company->id, 'client_id' => $this->client->id]); + $invoice->status_id = Invoice::STATUS_DRAFT; + + $invoice->line_items = $this->buildLineItems(); + $invoice->uses_inclusive_taxes = false; + $invoice->tax_rate1 = 0; + $invoice->tax_rate2 = 0; + $invoice->tax_rate3 = 0; + $invoice->discount = 0; + $invoice->number = 'TESTMATCHING'; + $invoice->date = now()->format('Y-m-d'); + + $invoice->save(); + + $invoice_calc = new InvoiceSum($invoice); + $invoice_calc->build(); + + $invoice = $invoice_calc->getInvoice(); + $invoice->save(); + $invoice = $invoice->service()->markSent()->save(); + + $this->assertEquals(Invoice::STATUS_SENT, $invoice->status_id); + $this->assertEquals(10, $invoice->amount); + $this->assertEquals(10, $invoice->balance); + + $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->description = $invoice->number; + $bt->amount = 10; + $bt->currency_code = $this->client->currency()->code; + $bt->date = now()->format('Y-m-d'); + $bt->transaction_id = 123456; + $bt->save(); + + $data = [ + [ + 'id' => $bt->id, + 'invoice_id' => $invoice->id + ] + ]; + + MatchBankTransactions::dispatchSync($this->company->id, $this->company->db, $data); + + $payment = Payment::where('transaction_reference', '123456')->first(); + + $this->assertNotNull($payment); + + $this->assertEquals(10, (int)$payment->amount); + $this->assertEquals(4, $payment->status_id); + $this->assertEquals(1, $payment->invoices()->count()); + + $invoice = $invoice->fresh(); + + $this->assertEquals(Invoice::STATUS_PAID, $invoice->status_id); + $this->assertEquals(0, $invoice->balance); + } + + public function testCategoryPropertyExists() { $yodlee = new Yodlee('sbMem62e1e69547bfb2'); @@ -89,8 +195,8 @@ class YodleeApiTest extends TestCase } } - $this->assertGreaterThan(1, BankIntegration::count()); - $this->assertGreaterThan(1, BankTransaction::count()); + $this->assertGreaterThan(0, BankIntegration::count()); + $this->assertGreaterThan(0, BankTransaction::count()); $this->invoice->number = "XXXXXX8501"; $this->invoice->save();