mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-05-24 02:14:21 -04:00
Bank Transaction matching
This commit is contained in:
parent
a4d7d4af54
commit
02071e93d6
@ -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'
|
||||
];
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
Loading…
x
Reference in New Issue
Block a user