mirror of
				https://github.com/invoiceninja/invoiceninja.git
				synced 2025-10-31 14:47:31 -04:00 
			
		
		
		
	
		
			
				
	
	
		
			241 lines
		
	
	
		
			8.0 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			241 lines
		
	
	
		
			8.0 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php namespace App\Services;
 | |
| 
 | |
| use stdClass;
 | |
| use Utils;
 | |
| use URL;
 | |
| use Hash;
 | |
| use App\Models\Gateway;
 | |
| use App\Models\BankSubaccount;
 | |
| use App\Models\Vendor;
 | |
| use App\Models\Expense;
 | |
| use App\Services\BaseService;
 | |
| use App\Ninja\Repositories\BankAccountRepository;
 | |
| use App\Ninja\Repositories\ExpenseRepository;
 | |
| use App\Ninja\Repositories\VendorRepository;
 | |
| use App\Libraries\Finance;
 | |
| use App\Libraries\Login;
 | |
| 
 | |
| class BankAccountService extends BaseService
 | |
| {
 | |
|     protected $bankAccountRepo;
 | |
|     protected $expenseRepo;
 | |
|     protected $vendorRepo;
 | |
|     protected $datatableService;
 | |
| 
 | |
|     public function __construct(BankAccountRepository $bankAccountRepo, ExpenseRepository $expenseRepo, VendorRepository $vendorRepo, DatatableService $datatableService)
 | |
|     {
 | |
|         $this->bankAccountRepo = $bankAccountRepo;
 | |
|         $this->vendorRepo = $vendorRepo;
 | |
|         $this->expenseRepo = $expenseRepo;
 | |
|         $this->datatableService = $datatableService;
 | |
|     }
 | |
| 
 | |
|     protected function getRepo()
 | |
|     {
 | |
|         return $this->bankAccountRepo;
 | |
|     }
 | |
| 
 | |
|     public function loadBankAccounts($bankId, $username, $password, $includeTransactions = true)
 | |
|     {
 | |
|         if ( ! $bankId || ! $username || ! $password) {
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         $expenses = Expense::scope()
 | |
|                         ->whereBankId($bankId)
 | |
|                         ->where('transaction_id', '!=', '')
 | |
|                         ->withTrashed()
 | |
|                         ->get(['transaction_id'])
 | |
|                         ->toArray();
 | |
|         $expenses = array_flip(array_map(function($val) {
 | |
|             return $val['transaction_id'];
 | |
|         }, $expenses));
 | |
| 
 | |
|         $bankAccounts = BankSubaccount::scope()
 | |
|                             ->whereHas('bank_account', function($query) use ($bankId) {
 | |
|                                 $query->where('bank_id', '=', $bankId);
 | |
|                             })
 | |
|                             ->get();
 | |
|         $bank = Utils::getFromCache($bankId, 'banks');
 | |
|         $data = [];
 | |
| 
 | |
|         // load OFX trnansactions
 | |
|         try {
 | |
|             $finance = new Finance();
 | |
|             $finance->banks[$bankId] = $bank->getOFXBank($finance);
 | |
|             $finance->banks[$bankId]->logins[] = new Login($finance->banks[$bankId], $username, $password);
 | |
|             
 | |
|             foreach ($finance->banks as $bank) {
 | |
|                 foreach ($bank->logins as $login) {
 | |
|                     $login->setup();
 | |
|                     foreach ($login->accounts as $account) {
 | |
|                         $account->setup($includeTransactions);
 | |
|                         if ($account = $this->parseBankAccount($account, $bankAccounts, $expenses, $includeTransactions)) {
 | |
|                             $data[] = $account;
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             return $data;
 | |
|         } catch (\Exception $e) {
 | |
|             return false;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     private function parseBankAccount($account, $bankAccounts, $expenses, $includeTransactions)
 | |
|     {
 | |
|         $obj = new stdClass;
 | |
|         $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) {
 | |
|             $ofxParser = new \OfxParser\Parser;
 | |
|             $ofx = $ofxParser->loadFromString($account->response);
 | |
| 
 | |
|             $obj->start_date = $ofx->BankAccount->Statement->startDate;
 | |
|             $obj->end_date = $ofx->BankAccount->Statement->endDate;
 | |
|             $obj->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;
 | |
|                 }
 | |
|                 $transaction->vendor = $this->prepareValue(substr($transaction->name, 0, 20));
 | |
|                 $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;
 | |
|                 $obj->transactions[] = $transaction;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         return $obj;
 | |
|     }
 | |
| 
 | |
|     private function prepareValue($value) {
 | |
|         return ucwords(strtolower(trim($value)));
 | |
|     }
 | |
| 
 | |
|     public function importExpenses($bankId, $input) {
 | |
|         $countVendors = 0;
 | |
|         $countExpenses = 0;
 | |
| 
 | |
|         // create a vendor map
 | |
|         $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;
 | |
|         }
 | |
| 
 | |
|         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'],
 | |
|                     'vendorcontact' => []
 | |
|                 ]);
 | |
|                 $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
 | |
|         ]);
 | |
|     }
 | |
| 
 | |
|     private function determineInfoField($value) {
 | |
|         if (preg_match("/^[0-9\-\(\)\.]+$/", $value)) {
 | |
|             return 'work_phone';
 | |
|         } elseif (strpos($value, '.') !== false) {
 | |
|             return 'private_notes';
 | |
|         } else {
 | |
|             return 'city';
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     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) {
 | |
|                     return link_to("bank_accounts/{$model->public_id}/edit", $model->bank_name);
 | |
|                 }
 | |
|             ],
 | |
|             [
 | |
|                 '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");
 | |
|                 }
 | |
|             ]
 | |
|         ];
 | |
|     }
 | |
| 
 | |
| } |