Support manually importing OFX files

This commit is contained in:
Hillel Coren 2016-05-15 13:58:11 +03:00
parent 52fe1b0dec
commit b067697b1c
11 changed files with 187 additions and 99 deletions

View File

@ -402,18 +402,10 @@ class AccountController extends BaseController
private function showBankAccounts() private function showBankAccounts()
{ {
$account = Auth::user()->account;
$account->load('bank_accounts');
$count = count($account->bank_accounts);
if ($count == 0) {
return Redirect::to('bank_accounts/create');
} else {
return View::make('accounts.banks', [ return View::make('accounts.banks', [
'title' => trans('texts.bank_accounts') 'title' => trans('texts.bank_accounts')
]); ]);
} }
}
private function showOnlinePayments() private function showOnlinePayments()
{ {

View File

@ -13,12 +13,14 @@ use stdClass;
use Crypt; use Crypt;
use URL; use URL;
use Utils; use Utils;
use File;
use App\Models\Gateway; use App\Models\Gateway;
use App\Models\Account; use App\Models\Account;
use App\Models\BankAccount; use App\Models\BankAccount;
use App\Ninja\Repositories\BankAccountRepository; use App\Ninja\Repositories\BankAccountRepository;
use App\Services\BankAccountService; use App\Services\BankAccountService;
use App\Http\Requests\CreateBankAccountRequest; use App\Http\Requests\CreateBankAccountRequest;
use Illuminate\Http\Request;
class BankAccountController extends BaseController class BankAccountController extends BaseController
{ {
@ -122,4 +124,28 @@ class BankAccountController extends BaseController
return $this->bankAccountService->importExpenses($bankId, Input::all()); return $this->bankAccountService->importExpenses($bankId, Input::all());
} }
public function showImportOFX()
{
return view('accounts.import_ofx');
}
public function doImportOFX(Request $request)
{
$file = File::get($request->file('ofx_file'));
try {
$data = $this->bankAccountService->parseOFX($file);
} catch (\Exception $e) {
Session::flash('error', trans('texts.ofx_parse_failed'));
return view('accounts.import_ofx');
}
$data = [
'banks' => null,
'bankAccount' => null,
'transactions' => json_encode([$data])
];
return View::make('accounts.bank_account', $data);
}
} }

View File

@ -245,6 +245,8 @@ Route::group([
Route::get('api/gateways', array('as'=>'api.gateways', 'uses'=>'AccountGatewayController@getDatatable')); Route::get('api/gateways', array('as'=>'api.gateways', 'uses'=>'AccountGatewayController@getDatatable'));
Route::post('account_gateways/bulk', 'AccountGatewayController@bulk'); Route::post('account_gateways/bulk', 'AccountGatewayController@bulk');
Route::get('bank_accounts/import_ofx', 'BankAccountController@showImportOFX');
Route::post('bank_accounts/import_ofx', 'BankAccountController@doImportOFX');
Route::resource('bank_accounts', 'BankAccountController'); Route::resource('bank_accounts', 'BankAccountController');
Route::get('api/bank_accounts', array('as'=>'api.bank_accounts', 'uses'=>'BankAccountController@getDatatable')); Route::get('api/bank_accounts', array('as'=>'api.bank_accounts', 'uses'=>'BankAccountController@getDatatable'));
Route::post('bank_accounts/bulk', 'BankAccountController@bulk'); Route::post('bank_accounts/bulk', 'BankAccountController@bulk');

View File

@ -48,6 +48,7 @@ class OFX
return $x; return $x;
} }
public static function closeTags($x) public static function closeTags($x)
{ {
$x = preg_replace('/\s+/', '', $x); $x = preg_replace('/\s+/', '', $x);
@ -233,4 +234,3 @@ class Account
} }
} }
} }

View File

@ -99,6 +99,15 @@ class Expense extends EntityModel
return $array; return $array;
} }
public function scopeBankId($query, $bankdId = null)
{
if ($bankdId) {
$query->whereBankId($bankId);
}
return $query;
}
} }
Expense::creating(function ($expense) { Expense::creating(function ($expense) {

View File

@ -34,14 +34,10 @@ class BankAccountService extends BaseService
return $this->bankAccountRepo; return $this->bankAccountRepo;
} }
public function loadBankAccounts($bankId, $username, $password, $includeTransactions = true) private function getExpenses($bankId = null)
{ {
if (! $bankId || ! $username || ! $password) {
return false;
}
$expenses = Expense::scope() $expenses = Expense::scope()
->whereBankId($bankId) ->bankId($bankId)
->where('transaction_id', '!=', '') ->where('transaction_id', '!=', '')
->withTrashed() ->withTrashed()
->get(['transaction_id']) ->get(['transaction_id'])
@ -50,6 +46,16 @@ class BankAccountService extends BaseService
return $val['transaction_id']; return $val['transaction_id'];
}, $expenses)); }, $expenses));
return $expenses;
}
public function loadBankAccounts($bankId, $username, $password, $includeTransactions = true)
{
if (! $bankId || ! $username || ! $password) {
return false;
}
$expenses = $this->getExpenses();
$vendorMap = $this->createVendorMap(); $vendorMap = $this->createVendorMap();
$bankAccounts = BankSubaccount::scope() $bankAccounts = BankSubaccount::scope()
->whereHas('bank_account', function ($query) use ($bankId) { ->whereHas('bank_account', function ($query) use ($bankId) {
@ -106,12 +112,20 @@ class BankAccountService extends BaseService
$obj->balance = Utils::formatMoney($account->ledgerBalance, CURRENCY_DOLLAR); $obj->balance = Utils::formatMoney($account->ledgerBalance, CURRENCY_DOLLAR);
if ($includeTransactions) { if ($includeTransactions) {
$ofxParser = new \OfxParser\Parser(); $obj = $this->parseTransactions($obj, $account->response, $expenses, $vendorMap);
$ofx = $ofxParser->loadFromString($account->response); }
$obj->start_date = $ofx->BankAccount->Statement->startDate; return $obj;
$obj->end_date = $ofx->BankAccount->Statement->endDate; }
$obj->transactions = [];
private function parseTransactions($account, $data, $expenses, $vendorMap)
{
$ofxParser = new \OfxParser\Parser();
$ofx = $ofxParser->loadFromString($data);
$account->start_date = $ofx->BankAccount->Statement->startDate;
$account->end_date = $ofx->BankAccount->Statement->endDate;
$account->transactions = [];
foreach ($ofx->BankAccount->Statement->transactions as $transaction) { foreach ($ofx->BankAccount->Statement->transactions as $transaction) {
// ensure transactions aren't imported as expenses twice // ensure transactions aren't imported as expenses twice
@ -132,11 +146,10 @@ class BankAccountService extends BaseService
$transaction->memo = $this->prepareValue($transaction->memo); $transaction->memo = $this->prepareValue($transaction->memo);
$transaction->date = \Auth::user()->account->formatDate($transaction->date); $transaction->date = \Auth::user()->account->formatDate($transaction->date);
$transaction->amount *= -1; $transaction->amount *= -1;
$obj->transactions[] = $transaction; $account->transactions[] = $transaction;
}
} }
return $obj; return $account;
} }
private function prepareValue($value) private function prepareValue($value)
@ -144,6 +157,15 @@ class BankAccountService extends BaseService
return ucwords(strtolower(trim($value))); return ucwords(strtolower(trim($value)));
} }
public function parseOFX($data)
{
$account = new stdClass;
$expenses = $this->getExpenses();
$vendorMap = $this->createVendorMap();
return $this->parseTransactions($account, $data, $expenses, $vendorMap);
}
private function createVendorMap() private function createVendorMap()
{ {
$vendorMap = []; $vendorMap = [];
@ -158,7 +180,7 @@ class BankAccountService extends BaseService
return $vendorMap; return $vendorMap;
} }
public function importExpenses($bankId, $input) public function importExpenses($bankId = 0, $input)
{ {
$vendorMap = $this->createVendorMap(); $vendorMap = $this->createVendorMap();
$countVendors = 0; $countVendors = 0;

View File

@ -1295,7 +1295,9 @@ $LANG = array(
'no_payment_method_specified' => 'No payment method specified', 'no_payment_method_specified' => 'No payment method specified',
'chart_type' => 'Chart Type', 'chart_type' => 'Chart Type',
'format' => 'Format', 'format' => 'Format',
'import_ofx' => 'Import OFX',
'ofx_file' => 'OFX File',
'ofx_parse_failed' => 'Failed to parse OFX file',
); );
return $LANG; return $LANG;

View File

@ -482,6 +482,11 @@
window.model = new ViewModel(); window.model = new ViewModel();
ko.applyBindings(model); ko.applyBindings(model);
@if (!empty($transactions))
loadTransactions({!! $transactions !!});
model.setPage('import');
@endif
</script> </script>

View File

@ -1,17 +1,21 @@
@extends('header') @extends('header')
@section('content') @section('content')
@parent @parent
@include('accounts.nav', ['selected' => ACCOUNT_BANKS]) @include('accounts.nav', ['selected' => ACCOUNT_BANKS])
<div class="pull-right">
{!! Button::normal(trans('texts.import_ofx'))
->asLinkTo(URL::to('/bank_accounts/import_ofx'))
->appendIcon(Icon::create('open')) !!}
{!! Button::primary(trans('texts.add_bank_account')) {!! Button::primary(trans('texts.add_bank_account'))
->asLinkTo(URL::to('/bank_accounts/create')) ->asLinkTo(URL::to('/bank_accounts/create'))
->withAttributes(['class' => 'pull-right'])
->appendIcon(Icon::create('plus-sign')) !!} ->appendIcon(Icon::create('plus-sign')) !!}
</div>
@include('partials.bulk_form', ['entityType' => ENTITY_BANK_ACCOUNT]) @include('partials.bulk_form', ['entityType' => ENTITY_BANK_ACCOUNT])
{!! Datatable::table() {!! Datatable::table()
->addColumn( ->addColumn(
trans('texts.name'), trans('texts.name'),
trans('texts.integration_type'), trans('texts.integration_type'),
@ -24,8 +28,8 @@
->setOptions('aoColumnDefs', [['bSortable'=>false, 'aTargets'=>[2]]]) ->setOptions('aoColumnDefs', [['bSortable'=>false, 'aTargets'=>[2]]])
->render('datatable') !!} ->render('datatable') !!}
<script> <script>
window.onDatatableReady = actionListHandler; window.onDatatableReady = actionListHandler;
</script> </script>
@stop @stop

View File

@ -0,0 +1,26 @@
@extends('header')
@section('content')
@parent
@include('accounts.nav', ['selected' => ACCOUNT_BANKS])
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{{ trans('texts.import_ofx') }}</h3>
</div>
<div class="panel-body">
{!! Former::open_for_files('bank_accounts/import_ofx')
->rules(['ofx_file' => 'required'])
->addClass('warn-on-exit') !!}
{!! Former::file("ofx_file") !!}
{!! Former::actions( Button::info(trans('texts.upload'))->submit()->large()->appendIcon(Icon::create('open'))) !!}
{!! Former::close() !!}
</div>
</div>
@stop