Automating data pulls

This commit is contained in:
David Bomba 2022-08-12 13:41:55 +10:00
parent ea611e9256
commit 3b1d0e07e2
12 changed files with 280 additions and 18 deletions

View File

@ -17,6 +17,7 @@ use App\Jobs\Cron\RecurringInvoicesCron;
use App\Jobs\Cron\SubscriptionCron;
use App\Jobs\Ledger\LedgerBalanceUpdate;
use App\Jobs\Ninja\AdjustEmailQuota;
use App\Jobs\Ninja\BankTransactionSync;
use App\Jobs\Ninja\CompanySizeCheck;
use App\Jobs\Ninja\QueueSize;
use App\Jobs\Ninja\SystemMaintenance;
@ -68,6 +69,8 @@ class Kernel extends ConsoleKernel
$schedule->job(new SystemMaintenance)->weekly()->withoutOverlapping();
$schedule->job(new BankTransactionSync)->dailyAt('04:00')->withoutOverlapping();
if (Ninja::isSelfHost()) {
$schedule->call(function () {
Account::whereNotNull('id')->update(['is_scheduler_running' => true]);

View File

@ -30,6 +30,7 @@ class BankTransactionFactory
$bank_transaction->date = now()->format('Y-m-d');
$bank_transaction->description = '';
$bank_transaction->is_matched = 0;
$bank_transaction->base_type = 'CREDIT';
return $bank_transaction;
}

View File

@ -134,18 +134,15 @@ class IncomeTransformer implements BankRevenueInterface
{
return [
'id' => $transaction->id,
'transaction_id' => $transaction->id,
'amount' => $transaction->amount->amount,
'currency' => $transaction->amount->currency,
'currency_code' => $transaction->amount->currency,
'account_type' => $transaction->CONTAINER,
'category_id' => $transaction->categoryId,
'category_type' => $transaction->categoryType,
'date' => $transaction->date,
'account_id' => $transaction->accountId,
'bank_account_id' => $transaction->accountId,
'description' => $transaction->description->original,
'invoice_id' => '',
'expense_id' => '',
'payment_id' => '',
];
}

View File

@ -574,7 +574,8 @@ class BankIntegrationController extends BaseController
}
/**
* Return the remote list of accounts stored on the third part provider.
* Return the remote list of accounts stored on the third party provider
* and update our local cache.
*
* @return Response
*
@ -631,12 +632,49 @@ class BankIntegrationController extends BaseController
}
/**
* Return the remote list of accounts stored on the third party provider
* and update our local cache.
*
* @return Response
*
* @OA\Post(
* path="/api/v1/bank_integrations/get_transactions/account_id",
* operationId="getAccountTransactions",
* tags={"bank_integrations"},
* summary="Retrieve transactions for a account",
* description="Retrieve transactions for a account",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Response(
* response=200,
* description="Retrieve transactions for a account",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* @OA\JsonContent(ref="#/components/schemas/BankIntegration"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
*
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/
public function getTransactions(AdminBankIntegrationRequest $request)
{
$bank_account_id = auth()->user()->account->bank_integration_account_id;
$bank_account_id = 'sbMem62e1e69547bfb1';
// $bank_account_id = 'sbMem62e1e69547bfb1';
if(!$bank_account_id)
return response()->json(['message' => 'Not yet authenticated with Bank Integration service'], 400);
@ -645,17 +683,15 @@ class BankIntegrationController extends BaseController
$yodlee->setTestMode();
$data = [
'CONTAINER' => 'bank',
'categoryType' => 'INCOME, UNCATEGORIZE',
'top' => 500,
'fromDate' => '2000-10-10', /// YYYY-MM-DD
];
$transactions = $yodlee->getTransactions($data);
$transactions = (new BankService(auth()->user()->company()))->match();
BankService::dispatch(auth()->user()->company()->id, auth()->user()->company()->db));
return response()->json($transactions, 200, [], JSON_PRETTY_PRINT);
return response()->json(['message' => 'Fetching transactions....'], 200);
}
}

View File

@ -13,6 +13,7 @@
* @OA\Property(property="description", type="string", example="Potato purchases for kevin", description="The description of the transaction"),
* @OA\Property(property="category_id", type="integer", example=1, description="The category id"),
* @OA\Property(property="category_type", type="string", example="Expenses", description="The category description"),
* @OA\Property(property="base_type", type="string", example="CREDIT", description="Either CREDIT or DEBIT"),
* @OA\Property(property="date", type="string", example="2022-09-01", description="The date of the transaction"),
* @OA\Property(property="bank_account_id", type="integer", example="1", description="The ID number of the bank account"),
* )

View File

@ -0,0 +1,117 @@
<?php
/**
* Credit Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2022. Credit Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Jobs\Bank;
use App\Helpers\Bank\Yodlee\Yodlee;
use App\Models\BankIntegration;
use App\Models\BankTransaction;
use App\Services\Bank\BankService;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class ProcessBankTransactions implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
private string $bank_integration_account_id;
private BankIntegration $bank_integration;
private ?string $from_date;
private string $default_date = '2022-01-01';
/**
* Create a new job instance.
*/
public function __construct(string $bank_integration_account_id, BankIntegration $bank_integration, ?string $from_date)
{
$this->bank_integration_account_id = $bank_integration_account_id;
$this->bank_integration = $bank_integration;
$this->from_date = $from_date;
}
/**
* Execute the job.
*
*
* @return void
*/
public function handle()
{
$yodlee = new Yodlee($this->bank_integration_account_id);
$yodlee->setTestMode();
$data = [
'baseType' => 'DEBIT', //CREDIT
'top' => 500,
'fromDate' => $this->from_date ?: $this->default_date, /// YYYY-MM-DD
'accountId' => $this->bank_integration->bank_account_id,
];
//expense transactions
$transactions = $yodlee->getTransactions($data);
$company = $this->bank_integration->company;
$user_id = $company->owner()->id;
foreach($transactions as $transaction)
{
if(BankTransaction::where('transaction_id', $transaction['transaction_id'])->where('company_id', $company->id)->withTrashed()->exists())
continue;
$bt = BankTransaction::create(
$transaction
);
$bt->company_id = $company->id;
$bt->user_id = $user_id;
$bt->base_type = 'DEBIT';
$bt->save();
}
$data = [
'baseType' => 'CREDIT', //CREDIT
'top' => 500,
'fromDate' => $this->from_date ?: $this->default_date, /// YYYY-MM-DD
'accountId' => $this->bank_integration->bank_account_id,
];
//income transactions
$transactions = $yodlee->getTransactions($data);
foreach($transactions as $transaction)
{
if(BankTransaction::where('transaction_id', $transaction['transaction_id'])->where('company_id', $company->id)->withTrashed()->exists())
continue;
$bt = BankTransaction::create(
$transaction
);
$bt->company_id = $company->id;
$bt->user_id = $user_id;
$bt->base_type = 'CREDIT';
$bt->save();
}
BankService::dispatch($company->id, $company->db);
}
}

View File

@ -0,0 +1,79 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Jobs\Ninja;
use App\Jobs\Bank\ProcessBankTransactions;
use App\Libraries\MultiDB;
use App\Models\Account;
use App\Utils\Ninja;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Cache;
class BankTransactionSync implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
if (! Ninja::isHosted()) {
return;
}
//multiDB environment, need to
foreach (MultiDB::$dbs as $db) {
MultiDB::setDB($db);
$this->syncTransactions();
}
}
public function syncTransactions()
{
$a = Account::with('bank_integrations')->whereNotNull('bank_integration_account_id')->cursor()->each(function ($account){
$queue = Ninja::isHosted() ? 'bank' : 'default';
if($account->isPaid())
{
$account->bank_integrations->each(function ($bank_integration) use ($queue, $account){
ProcessBankTransactions::dispatch($account->bank_integration_account_id, $bank_integration)->onQueue($queue);
});
}
});
}
}

View File

@ -138,6 +138,11 @@ class Account extends BaseModel
return $this->hasMany(Company::class);
}
public function bank_integrations()
{
return $this->hasMany(BankIntegration::class);
}
public function company_users()
{
return $this->hasMany(CompanyUser::class);

View File

@ -11,18 +11,38 @@
namespace App\Services\Bank;
use App\Libraries\MultiDB;
use App\Models\Company;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class BankService
class BankService implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public Company $company;
private $company_id;
private Company $company;
private $db;
private $invoices;
public function __construct(Company $company)
public function __construct($company_id, $db)
{
$this->company = $company;
$this->company_id = $company_id;
$this->db = $db;
}
public function handle()
{
MultiDB::setDb($this->db);
$this->company = Company::find($this->company_id);
$this->invoices = $this->company->invoices()->whereIn('status_id', [1,2,3])
->where('is_deleted', 0)

View File

@ -60,6 +60,7 @@ class BankTransactionTransformer extends EntityTransformer
'date' => (string) $bank_transaction->date ?: '',
'bank_account_id' => (int) $bank_transaction->bank_account_id,
'description' => (string) $bank_transaction->description ?: '',
'base_type' => (string) $bank_transaction->base_type ?: '',
'invoice_id' => (string) $this->encodePrimaryKey($bank_transaction->invoice_id) ?: '',
'expense_id'=> (string) $this->encodePrimaryKey($bank_transaction->expense_id) ?: '',
'is_matched'=> (bool) $bank_transaction->is_matched ?: '',

View File

@ -55,10 +55,11 @@ return new class extends Migration
$table->unsignedInteger('company_id');
$table->unsignedInteger('user_id');
$table->unsignedBigInteger('bank_integration_id');
$table->unsignedBigInteger('transaction_id')->nullable();
$table->unsignedBigInteger('transaction_id')->index();
$table->decimal('amount', 20, 6);
$table->string('currency_code');
$table->string('account_type');
$table->string('base_type')->index();
$table->unsignedInteger('category_id');
$table->string('category_type');
$table->date('date');

View File

@ -109,8 +109,9 @@ Route::group(['middleware' => ['throttle:300,1', 'api_db', 'token_auth', 'locale
Route::put('accounts/{account}', [AccountController::class, 'update'])->name('account.update');
Route::resource('bank_integrations', BankIntegrationController::class); // name = (clients. index / create / show / update / destroy / edit
Route::post('bank_integrations/refresh_accounts', [BankIntegrationController::class, 'refreshAccounts'])->name('bank_integrations.refresh_accounts');
Route::post('bank_integrations/transactions', [BankIntegrationController::class, 'getTransactions'])->name('bank_integrations.transactions');
Route::post('bank_integrations/remove_account/{acc_id}', [BankIntegrationController::class, 'removeAccount'])->name('bank_integrations.remove_account');
Route::post('bank_integrations/get_transactions/{acc_id}', [BankIntegrationController::class, 'getTransactions'])->name('bank_integrations.transactions');
Route::post('bank_integrations/bulk', [BankIntegrationController::class, 'bulk'])->name('bank_integrations.bulk');
Route::resource('bank_transactions', BankTransactionController::class); // name = (clients. index / create / show / update / destroy / edit