This commit is contained in:
James Cole 2018-12-21 06:51:00 +01:00
parent cd47b45fce
commit a7585e3040
No known key found for this signature in database
GPG Key ID: C16961E655E74B5E
4 changed files with 285 additions and 149 deletions

View File

@ -93,6 +93,7 @@ class ChooseAccountsHandler implements BunqJobConfigurationInterface
* This is used to properly map transfers.
*/
$ibanToAsset = [];
Log::debug('Going to map IBANs for easy mapping later on.');
if (0 === \count($accounts)) {
throw new FireflyException('No bunq accounts found. Import cannot continue.'); // @codeCoverageIgnore
}
@ -106,10 +107,17 @@ class ChooseAccountsHandler implements BunqJobConfigurationInterface
$bunqId = (int)$bunqId;
$localId = (int)$localId;
Log::debug(sprintf('Now trying to link bunq acount #%d with Firefly III account %d', $bunqId, $localId));
// validate each
$bunqId = $this->validBunqAccount($bunqId);
$accountId = $this->validLocalAccount($localId);
$bunqIban = $this->getBunqIban($bunqId);
Log::debug(sprintf('After validation: bunq account #%d with Firefly III account %d', $bunqId, $localId));
$bunqIban = $this->getBunqIban($bunqId);
Log::debug(sprintf('IBAN for bunq account #%d is "%s"', $bunqId, $bunqIban));
if (null !== $bunqIban) {
$ibanToAsset[$bunqIban] = $accountId;
}

View File

@ -0,0 +1,244 @@
<?php
/**
* PaymentConverter.php
* Copyright (c) 2018 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Support\Import\Routine\Bunq;
use bunq\Model\Generated\Endpoint\Payment as BunqPayment;
use bunq\Model\Generated\Object\LabelMonetaryAccount;
use Carbon\Carbon;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Factory\AccountFactory;
use FireflyIII\Models\Account as LocalAccount;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\ImportJob;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
use Log;
/**
* Class PaymentConverter
*/
class PaymentConverter
{
/** @var AccountFactory */
private $accountFactory;
/** @var AccountRepositoryInterface */
private $accountRepos;
/** @var array */
private $configuration;
/** @var ImportJob */
private $importJob;
/** @var ImportJobRepositoryInterface */
private $importJobRepos;
public function __construct()
{
$this->accountRepos = app(AccountRepositoryInterface::class);
$this->importJobRepos = app(ImportJobRepositoryInterface::class);
$this->accountFactory = app(AccountFactory::class);
if ('testing' === config('app.env')) {
Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this)));
}
}
/**
* Convert a bunq transaction to a usable transaction for Firefly III.
*
* @param BunqPayment $payment
*
* @return array
* @throws FireflyException
*/
public function convert(BunqPayment $payment, LocalAccount $source): array
{
$paymentId = $payment->getId();
Log::debug(sprintf('Now in convert() for payment with ID #%d', $paymentId));
Log::debug(sprintf('Source account is assumed to be "%s" (#%d)', $source->name, $source->id));
$type = TransactionType::WITHDRAWAL;
$counterParty = $payment->getCounterpartyAlias();
$amount = $payment->getAmount();
// some debug info:
Log::debug('Assume its a witdrawal');
Log::debug(sprintf('Subtype is %s', $payment->getSubType()));
Log::debug(sprintf('Type is %s', $payment->getType()));
Log::debug(sprintf('Amount is %s %s', $amount->getCurrency(), $amount->getValue()));
$expected = AccountType::EXPENSE;
if (1 === bccomp($amount->getValue(), '0')) {
// amount + means that its a deposit.
$expected = AccountType::REVENUE;
$type = TransactionType::DEPOSIT;
Log::debug(sprintf('Amount is %s %s, so assume this is a deposit.', $amount->getCurrency(), $amount->getValue()));
}
Log::debug(sprintf('Now going to convert counter party to Firefly III account. Expect it to be a "%s" account.', $expected));
$destination = $this->convertToAccount($counterParty, $expected);
// switch source and destination if necessary.
if (1 === bccomp($amount->getValue(), '0')) {
Log::debug('Because amount is > 0, will now swap source with destination.');
[$source, $destination] = [$destination, $source];
}
if ($source->accountType->type === AccountType::ASSET && $destination->accountType->type === AccountType::ASSET) {
$type = TransactionType::TRANSFER;
Log::debug('Because both transctions are asset, will make it a transfer.');
}
$created = new Carbon($payment->getCreated());
$description = $payment->getDescription();
if ('' === $payment->getDescription() && 'SAVINGS' === $payment->getType()) {
$description = 'Auto-save for savings goal.';
}
$storeData = [
'user' => $this->importJob->user_id,
'type' => $type,
'date' => $created->format('Y-m-d'),
'timestamp' => $created->toAtomString(),
'description' => $description,
'piggy_bank_id' => null,
'piggy_bank_name' => null,
'bill_id' => null,
'bill_name' => null,
'tags' => [$payment->getType(), $payment->getSubType()],
'internal_reference' => $paymentId,
'external_id' => $paymentId,
'notes' => null,
'bunq_payment_id' => $paymentId,
'original-source' => sprintf('bunq-v%s', config('firefly.version')),
'transactions' => [
// single transaction:
[
'description' => null,
'amount' => $amount->getValue(),
'currency_id' => null,
'currency_code' => $amount->getCurrency(),
'foreign_amount' => null,
'foreign_currency_id' => null,
'foreign_currency_code' => null,
'budget_id' => null,
'budget_name' => null,
'category_id' => null,
'category_name' => null,
'source_id' => $source->id,
'source_name' => null,
'destination_id' => $destination->id,
'destination_name' => null,
'reconciled' => false,
'identifier' => 0,
],
],
];
Log::info(sprintf('Parsed %s: "%s" (%s).', $created->format('Y-m-d'), $storeData['description'], $storeData['transactions'][0]['amount']));
return $storeData;
}
/**
* @param ImportJob $importJob
*/
public function setImportJob(ImportJob $importJob): void
{
$this->importJob = $importJob;
$this->accountRepos->setUser($importJob->user);
$this->importJobRepos->setUser($importJob->user);
$this->accountFactory->setUser($importJob->user);
$this->configuration = $this->importJobRepos->getConfiguration($importJob);
}
/**
* @param LabelMonetaryAccount $party
* @param string $expectedType
*
* @return LocalAccount
* @throws FireflyException
*/
private function convertToAccount(LabelMonetaryAccount $party, string $expectedType): LocalAccount
{
Log::debug(sprintf('in convertToAccount() with LabelMonetaryAccount'));
if (null !== $party->getIban()) {
Log::debug(sprintf('Opposing party has IBAN "%s"', $party->getIban()));
// find account in 'bunq-iban' array first.
$bunqIbans = $this->configuration['bunq-iban'] ?? [];
Log::debug('Bunq ibans configuration is', $bunqIbans);
if (isset($bunqIbans[$party->getIban()])) {
Log::debug('IBAN is known in array.');
$accountId = (int)$bunqIbans[$party->getIban()];
$result = $this->accountRepos->findNull($accountId);
if (null !== $result) {
Log::debug(sprintf('Search for #%s (IBAN "%s"), found "%s" (#%d)', $accountId, $party->getIban(), $result->name, $result->id));
return $result;
}
}
// find opposing party by IBAN second.
$result = $this->accountRepos->findByIbanNull($party->getIban(), [$expectedType]);
if (null !== $result) {
Log::debug(sprintf('Search for "%s" resulted in account "%s" (#%d)', $party->getIban(), $result->name, $result->id));
return $result;
}
// try to find asset account just in case:
if ($expectedType !== AccountType::ASSET) {
$result = $this->accountRepos->findByIbanNull($party->getIban(), [AccountType::ASSET]);
if (null !== $result) {
Log::debug(sprintf('Search for Asset "%s" resulted in account %s (#%d)', $party->getIban(), $result->name, $result->id));
return $result;
}
}
}
Log::debug('Found no account for opposing party, must create a new one.');
// create new account:
$data = [
'user_id' => $this->importJob->user_id,
'iban' => $party->getIban(),
'name' => $party->getLabelUser()->getDisplayName(),
'account_type_id' => null,
'accountType' => $expectedType,
'virtualBalance' => null,
'active' => true,
];
$account = $this->accountFactory->create($data);
Log::debug(
sprintf(
'Converted label monetary account "%s" to NEW "%s" account "%s" (#%d)',
$party->getLabelUser()->getDisplayName(),
$expectedType,
$account->name, $account->id
)
);
return $account;
}
}

View File

@ -24,15 +24,12 @@ declare(strict_types=1);
namespace FireflyIII\Support\Import\Routine\Bunq;
use bunq\Model\Generated\Endpoint\Payment as BunqPayment;
use bunq\Model\Generated\Object\LabelMonetaryAccount;
use Carbon\Carbon;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Factory\AccountFactory;
use FireflyIII\Models\Account as LocalAccount;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\ImportJob;
use FireflyIII\Models\Preference;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
use FireflyIII\Services\Bunq\ApiContext;
@ -44,8 +41,10 @@ use Log;
*/
class StageImportDataHandler
{
/** @var int */
private const DOWNLOAD_BACKWARDS = 1;
private const DOWNLOAD_FORWARDS = 2;
/** @var int */
private const DOWNLOAD_FORWARDS = 2;
/** @var bool */
public $stillRunning;
@ -53,6 +52,8 @@ class StageImportDataHandler
private $accountFactory;
/** @var AccountRepositoryInterface */
private $accountRepository;
/** @var PaymentConverter */
private $converter;
/** @var ImportJob */
private $importJob;
/** @var array */
@ -68,6 +69,7 @@ class StageImportDataHandler
{
$this->stillRunning = true;
$this->timeStart = microtime(true);
$this->converter = app(PaymentConverter::class);
}
@ -95,6 +97,7 @@ class StageImportDataHandler
public function run(): void
{
$this->getContext();
$this->converter->setImportJob($this->importJob);
$config = $this->repository->getConfiguration($this->importJob);
$accounts = $config['accounts'] ?? [];
$mapping = $config['mapping'] ?? [];
@ -141,150 +144,9 @@ class StageImportDataHandler
private function convertPayment(BunqPayment $payment, int $bunqAccountId, LocalAccount $source): array
{
Log::debug(sprintf('Now at payment with ID #%d', $payment->getId()));
Log::debug(sprintf('Object dump: %s', print_r($payment, true)));
$type = TransactionType::WITHDRAWAL;
$counterParty = $payment->getCounterpartyAlias();
$amount = $payment->getAmount();
$paymentId = $payment->getId();
// is there meta data to indicate this is a saving or something?
Log::debug(sprintf('Subtype is %s', $payment->getSubType()));
Log::debug(sprintf('Amount is %s %s', $amount->getCurrency(), $amount->getValue()));
$expected = AccountType::EXPENSE;
if (1 === bccomp($amount->getValue(), '0')) {
// amount + means that its a deposit.
$expected = AccountType::REVENUE;
$type = TransactionType::DEPOSIT;
Log::debug('Will make opposing account revenue.');
}
$destination = $this->convertToAccount($counterParty, $expected);
// switch source and destination if necessary.
if (1 === bccomp($amount->getValue(), '0')) {
Log::debug('Will make it a deposit.');
[$source, $destination] = [$destination, $source];
}
if ($source->accountType->type === AccountType::ASSET && $destination->accountType->type === AccountType::ASSET) {
$type = TransactionType::TRANSFER;
Log::debug('Both are assets, will make transfer.');
}
$created = new Carbon($payment->getCreated());
$storeData = [
'user' => $this->importJob->user_id,
'type' => $type,
'date' => $created->format('Y-m-d'),
'timestamp' => $created->toAtomString(),
'description' => $payment->getDescription(),
'piggy_bank_id' => null,
'piggy_bank_name' => null,
'bill_id' => null,
'bill_name' => null,
'tags' => [$payment->getType(), $payment->getSubType()],
'internal_reference' => $paymentId,
'external_id' => $paymentId,
'notes' => null,
'bunq_payment_id' => $paymentId,
'original-source' => sprintf('bunq-v%s', config('firefly.version')),
'transactions' => [
// single transaction:
[
'description' => null,
'amount' => $amount->getValue(),
'currency_id' => null,
'currency_code' => $amount->getCurrency(),
'foreign_amount' => null,
'foreign_currency_id' => null,
'foreign_currency_code' => null,
'budget_id' => null,
'budget_name' => null,
'category_id' => null,
'category_name' => null,
'source_id' => $source->id,
'source_name' => null,
'destination_id' => $destination->id,
'destination_name' => null,
'reconciled' => false,
'identifier' => 0,
],
],
];
Log::info(sprintf('Parsed %s: "%s" (%s).', $created->format('Y-m-d'), $storeData['description'], $storeData['transactions'][0]['amount']));
return $storeData;
}
/**
* @param LabelMonetaryAccount $party
* @param string $expectedType
*
* @return LocalAccount
* @throws FireflyException
*/
private function convertToAccount(LabelMonetaryAccount $party, string $expectedType): LocalAccount
{
Log::debug(sprintf('in convertToAccount() with LabelMonetaryAccount: %s', ''));
if (null !== $party->getIban()) {
// find account in 'bunq-iban' array first.
$bunqIbans = $this->jobConfiguration['bunq-iban'] ?? [];
if (isset($bunqIbans[$party->getIban()])) {
$accountId = (int)$bunqIbans[$party->getIban()];
$result = $this->accountRepository->findNull($accountId);
if (null !== $result) {
Log::debug(
sprintf('Search for #%s (based on IBAN %s) resulted in account %s (#%d)', $accountId, $party->getIban(), $result->name, $result->id)
);
return $result;
}
}
// find opposing party by IBAN second.
$result = $this->accountRepository->findByIbanNull($party->getIban(), [$expectedType]);
if (null !== $result) {
Log::debug(sprintf('Search for %s resulted in account %s (#%d)', $party->getIban(), $result->name, $result->id));
return $result;
}
// try to find asset account just in case:
if ($expectedType !== AccountType::ASSET) {
$result = $this->accountRepository->findByIbanNull($party->getIban(), [AccountType::ASSET]);
if (null !== $result) {
Log::debug(sprintf('Search for Asset "%s" resulted in account %s (#%d)', $party->getIban(), $result->name, $result->id));
return $result;
}
}
}
// create new account:
$data = [
'user_id' => $this->importJob->user_id,
'iban' => $party->getIban(),
'name' => $party->getLabelUser()->getDisplayName(),
'account_type_id' => null,
'accountType' => $expectedType,
'virtualBalance' => null,
'active' => true,
];
$account = $this->accountFactory->create($data);
Log::debug(
sprintf(
'Converted label monetary account "%s" to "%s" account "%s" (#%d)',
$party->getLabelUser()->getDisplayName(),
$expectedType,
$account->name, $account->id
)
);
return $account;
return $this->converter->convert($payment, $source);
exit;
}
/**

View File

@ -94,6 +94,7 @@ class StageNewHandler
*/
private function listAccounts(): array
{
Log::debug('Now in StageNewHandler::listAccounts()');
$accounts = [];
/** @var MonetaryAccount $lister */
$lister = app(MonetaryAccount::class);
@ -112,18 +113,22 @@ class StageNewHandler
$array = null;
switch (\get_class($object)) {
case MonetaryAccountBank::class:
Log::debug('Going to convert a MonetaryAccountBank');
/** @var MonetaryAccountBank $object */
$array = $this->processMab($object);
break;
case MonetaryAccountJoint::class:
Log::debug('Going to convert a MonetaryAccountJoint');
/** @var MonetaryAccountJoint $object */
$array = $this->processMaj($object);
break;
case MonetaryAccountLight::class:
Log::debug('Going to convert a MonetaryAccountLight');
/** @var MonetaryAccountLight $object */
$array = $this->processMal($object);
break;
case MonetaryAccountSavings::class;
Log::debug('Going to convert a MonetaryAccountSavings');
/** @var MonetaryAccountSavings $object */
$array = $this->processMas($object);
break;
@ -134,12 +139,13 @@ class StageNewHandler
// @codeCoverageIgnoreEnd
}
if (null !== $array) {
Log::debug('Array is not null');
$accounts[] = $array;
$this->reportFinding($array);
}
}
}
Log::info(sprintf('Found %d account(s) at bunq', \count($accounts)));
Log::info(sprintf('Found %d account(s) at bunq', \count($accounts)), $accounts);
return $accounts;
}
@ -226,6 +232,10 @@ class StageNewHandler
'name' => $alias->getName(),
'value' => $alias->getValue(),
];
// store IBAN alias separately:
if ('IBAN' === $alias->getType()) {
$return['iban'] = $alias->getValue();
}
}
}
$coOwners = $maj->getAllCoOwner() ?? [];
@ -279,6 +289,10 @@ class StageNewHandler
'name' => $alias->getName(),
'value' => $alias->getValue(),
];
// store IBAN alias separately:
if ('IBAN' === $alias->getType()) {
$return['iban'] = $alias->getValue();
}
}
}
@ -292,6 +306,7 @@ class StageNewHandler
*/
private function processMas(MonetaryAccountSavings $object): array
{
Log::debug('Now in processMas()');
$setting = $object->getSetting();
$return = [
'id' => $object->getId(),
@ -312,13 +327,19 @@ class StageNewHandler
];
}
if (null !== $object->getAlias()) {
Log::debug('MAS has aliases');
/** @var Pointer $alias */
foreach ($object->getAlias() as $alias) {
Log::debug(sprintf('Alias type is "%s", with name "%s" and value "%s"', $alias->getType(), $alias->getName(), $alias->getValue()));
$return['aliases'][] = [
'type' => $alias->getType(),
'name' => $alias->getName(),
'value' => $alias->getValue(),
];
// store IBAN alias separately:
if ('IBAN' === $alias->getType()) {
$return['iban'] = $alias->getValue();
}
}
}
$goal = $object->getSavingsGoal();
@ -327,6 +348,7 @@ class StageNewHandler
'value' => $goal->getValue(),
'percentage' => $object->getSavingsGoalProgress(),
];
Log::debug('End of processMas()', $return);
return $return;
}