1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-10 13:12:50 +01:00
invoiceninja/app/Services/BankAccountService.php

277 lines
8.9 KiB
PHP
Raw Normal View History

2016-01-20 00:07:31 +01:00
<?php namespace App\Services;
use stdClass;
use Utils;
use URL;
2016-01-26 21:22:33 +01:00
use Hash;
use App\Models\BankSubaccount;
use App\Models\Vendor;
use App\Models\Expense;
2016-01-20 00:07:31 +01:00
use App\Services\BaseService;
use App\Ninja\Repositories\BankAccountRepository;
2016-01-26 21:22:33 +01:00
use App\Ninja\Repositories\ExpenseRepository;
use App\Ninja\Repositories\VendorRepository;
2016-01-20 00:07:31 +01:00
use App\Libraries\Finance;
use App\Libraries\Login;
class BankAccountService extends BaseService
{
protected $bankAccountRepo;
2016-01-26 21:22:33 +01:00
protected $expenseRepo;
protected $vendorRepo;
2016-01-20 00:07:31 +01:00
protected $datatableService;
2016-01-26 21:22:33 +01:00
public function __construct(BankAccountRepository $bankAccountRepo, ExpenseRepository $expenseRepo, VendorRepository $vendorRepo, DatatableService $datatableService)
2016-01-20 00:07:31 +01:00
{
$this->bankAccountRepo = $bankAccountRepo;
2016-01-26 21:22:33 +01:00
$this->vendorRepo = $vendorRepo;
$this->expenseRepo = $expenseRepo;
2016-01-20 00:07:31 +01:00
$this->datatableService = $datatableService;
}
protected function getRepo()
{
return $this->bankAccountRepo;
}
2016-05-15 12:58:11 +02:00
private function getExpenses($bankId = null)
2016-01-20 00:07:31 +01:00
{
2016-01-26 21:22:33 +01:00
$expenses = Expense::scope()
2016-05-15 12:58:11 +02:00
->bankId($bankId)
2016-01-26 21:22:33 +01:00
->where('transaction_id', '!=', '')
->withTrashed()
->get(['transaction_id'])
->toArray();
2016-02-27 21:41:23 +01:00
$expenses = array_flip(array_map(function ($val) {
2016-01-26 21:22:33 +01:00
return $val['transaction_id'];
}, $expenses));
2016-05-15 12:58:11 +02:00
return $expenses;
}
public function loadBankAccounts($bankId, $username, $password, $includeTransactions = true)
{
if (! $bankId || ! $username || ! $password) {
return false;
}
$expenses = $this->getExpenses();
2016-02-27 21:41:23 +01:00
$vendorMap = $this->createVendorMap();
2016-01-26 21:22:33 +01:00
$bankAccounts = BankSubaccount::scope()
2016-02-27 21:41:23 +01:00
->whereHas('bank_account', function ($query) use ($bankId) {
2016-01-26 21:22:33 +01:00
$query->where('bank_id', '=', $bankId);
})
->get();
2016-01-20 00:07:31 +01:00
$bank = Utils::getFromCache($bankId, 'banks');
$data = [];
2016-01-26 21:22:33 +01:00
// load OFX trnansactions
2016-01-20 00:07:31 +01:00
try {
$finance = new Finance();
$finance->banks[$bankId] = $bank->getOFXBank($finance);
$finance->banks[$bankId]->logins[] = new Login($finance->banks[$bankId], $username, $password);
2016-02-27 21:41:23 +01:00
2016-01-20 00:07:31 +01:00
foreach ($finance->banks as $bank) {
foreach ($bank->logins as $login) {
$login->setup();
foreach ($login->accounts as $account) {
$account->setup($includeTransactions);
2016-02-27 21:41:23 +01:00
if ($account = $this->parseBankAccount($account, $bankAccounts, $expenses, $includeTransactions, $vendorMap)) {
2016-01-26 21:22:33 +01:00
$data[] = $account;
}
2016-01-20 00:07:31 +01:00
}
}
}
2016-01-26 21:22:33 +01:00
2016-01-20 00:07:31 +01:00
return $data;
} catch (\Exception $e) {
return false;
}
}
2016-02-27 21:41:23 +01:00
private function parseBankAccount($account, $bankAccounts, $expenses, $includeTransactions, $vendorMap)
2016-01-26 21:22:33 +01:00
{
2016-02-27 21:41:23 +01:00
$obj = new stdClass();
2016-01-26 21:22:33 +01:00
$obj->account_name = '';
// look up bank account name
foreach ($bankAccounts as $bankAccount) {
if (Hash::check($account->id, $bankAccount->account_number)) {
$obj->account_name = $bankAccount->account_name;
}
}
// if we can't find a match skip the account
if (count($bankAccounts) && ! $obj->account_name) {
return false;
}
$obj->masked_account_number = Utils::maskAccountNumber($account->id);
$obj->hashed_account_number = bcrypt($account->id);
$obj->type = $account->type;
$obj->balance = Utils::formatMoney($account->ledgerBalance, CURRENCY_DOLLAR);
if ($includeTransactions) {
2016-05-15 12:58:11 +02:00
$obj = $this->parseTransactions($obj, $account->response, $expenses, $vendorMap);
}
2016-01-26 21:22:33 +01:00
2016-05-15 12:58:11 +02:00
return $obj;
}
2016-01-26 21:22:33 +01:00
2016-05-15 12:58:11 +02:00
private function parseTransactions($account, $data, $expenses, $vendorMap)
{
$ofxParser = new \OfxParser\Parser();
$ofx = $ofxParser->loadFromString($data);
2016-03-02 14:36:42 +01:00
2016-05-15 12:58:11 +02:00
$account->start_date = $ofx->BankAccount->Statement->startDate;
$account->end_date = $ofx->BankAccount->Statement->endDate;
$account->transactions = [];
foreach ($ofx->BankAccount->Statement->transactions as $transaction) {
// ensure transactions aren't imported as expenses twice
if (isset($expenses[$transaction->uniqueId])) {
continue;
}
if ($transaction->amount >= 0) {
continue;
2016-01-26 21:22:33 +01:00
}
2016-05-15 12:58:11 +02:00
// if vendor has already been imported use current name
$vendorName = trim(substr($transaction->name, 0, 20));
$key = strtolower($vendorName);
$vendor = isset($vendorMap[$key]) ? $vendorMap[$key] : null;
$transaction->vendor = $vendor ? $vendor->name : $this->prepareValue($vendorName);
$transaction->info = $this->prepareValue(substr($transaction->name, 20));
$transaction->memo = $this->prepareValue($transaction->memo);
$transaction->date = \Auth::user()->account->formatDate($transaction->date);
$transaction->amount *= -1;
$account->transactions[] = $transaction;
2016-01-26 21:22:33 +01:00
}
2016-05-15 12:58:11 +02:00
return $account;
2016-01-26 21:22:33 +01:00
}
2016-02-27 21:41:23 +01:00
private function prepareValue($value)
{
2016-01-26 21:22:33 +01:00
return ucwords(strtolower(trim($value)));
}
2016-05-15 12:58:11 +02:00
public function parseOFX($data)
{
$account = new stdClass;
$expenses = $this->getExpenses();
$vendorMap = $this->createVendorMap();
return $this->parseTransactions($account, $data, $expenses, $vendorMap);
}
2016-02-27 21:41:23 +01:00
private function createVendorMap()
{
2016-01-26 21:22:33 +01:00
$vendorMap = [];
$vendors = Vendor::scope()
->withTrashed()
->get(['id', 'name', 'transaction_name']);
foreach ($vendors as $vendor) {
$vendorMap[strtolower($vendor->name)] = $vendor;
$vendorMap[strtolower($vendor->transaction_name)] = $vendor;
}
2016-02-27 21:41:23 +01:00
return $vendorMap;
}
2016-05-15 12:58:11 +02:00
public function importExpenses($bankId = 0, $input)
2016-02-27 21:41:23 +01:00
{
$vendorMap = $this->createVendorMap();
$countVendors = 0;
$countExpenses = 0;
2016-01-26 21:22:33 +01:00
foreach ($input as $transaction) {
$vendorName = $transaction['vendor'];
$key = strtolower($vendorName);
$info = $transaction['info'];
// find vendor otherwise create it
if (isset($vendorMap[$key])) {
$vendor = $vendorMap[$key];
} else {
$field = $this->determineInfoField($info);
$vendor = $this->vendorRepo->save([
$field => $info,
'name' => $vendorName,
'transaction_name' => $transaction['vendor_orig'],
2016-05-15 13:06:25 +02:00
'vendor_contact' => [],
2016-01-26 21:22:33 +01:00
]);
$vendorMap[$key] = $vendor;
$vendorMap[$transaction['vendor_orig']] = $vendor;
$countVendors++;
}
// create the expense record
$this->expenseRepo->save([
'vendor_id' => $vendor->id,
'amount' => $transaction['amount'],
'public_notes' => $transaction['memo'],
'expense_date' => $transaction['date'],
'transaction_id' => $transaction['id'],
'bank_id' => $bankId,
'should_be_invoiced' => true,
]);
$countExpenses++;
}
return trans('texts.imported_expenses', [
'count_vendors' => $countVendors,
'count_expenses' => $countExpenses
]);
}
2016-02-27 21:41:23 +01:00
private function determineInfoField($value)
{
2016-01-26 21:22:33 +01:00
if (preg_match("/^[0-9\-\(\)\.]+$/", $value)) {
return 'work_phone';
} elseif (strpos($value, '.') !== false) {
return 'private_notes';
} else {
return 'city';
}
}
2016-01-20 00:07:31 +01:00
public function getDatatable($accountId)
{
$query = $this->bankAccountRepo->find($accountId);
return $this->createDatatable(ENTITY_BANK_ACCOUNT, $query, false);
}
protected function getDatatableColumns($entityType, $hideClient)
{
return [
[
'bank_name',
function ($model) {
2016-03-02 14:36:42 +01:00
return link_to("bank_accounts/{$model->public_id}/edit", $model->bank_name)->toHtml();
2016-02-27 21:41:23 +01:00
},
2016-01-20 00:07:31 +01:00
],
[
'bank_library_id',
function ($model) {
return 'OFX';
}
],
];
}
protected function getDatatableActions($entityType)
{
return [
[
uctrans('texts.edit_bank_account'),
function ($model) {
return URL::to("bank_accounts/{$model->public_id}/edit");
2016-02-27 21:41:23 +01:00
},
2016-01-20 00:07:31 +01:00
]
];
}
2016-02-27 21:41:23 +01:00
}