2019-06-11 20:09:13 +02:00
< ? php
/**
* OtherCurrenciesCorrections . php
2020-01-23 20:35:02 +01:00
* Copyright ( c ) 2020 james @ firefly - iii . org
2019-06-11 20:09:13 +02:00
*
2019-10-02 06:37:26 +02:00
* This file is part of Firefly III ( https :// github . com / firefly - iii ) .
2019-06-11 20:09:13 +02:00
*
2019-10-02 06:37:26 +02:00
* This program is free software : you can redistribute it and / or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation , either version 3 of the
* License , or ( at your option ) any later version .
2019-06-11 20:09:13 +02:00
*
2019-10-02 06:37:26 +02:00
* This program is distributed in the hope that it will be useful ,
2019-06-11 20:09:13 +02:00
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
2019-10-02 06:37:26 +02:00
* GNU Affero General Public License for more details .
2019-06-11 20:09:13 +02:00
*
2019-10-02 06:37:26 +02:00
* You should have received a copy of the GNU Affero General Public License
* along with this program . If not , see < https :// www . gnu . org / licenses />.
2019-06-11 20:09:13 +02:00
*/
declare ( strict_types = 1 );
namespace FireflyIII\Console\Commands\Upgrade ;
use FireflyIII\Models\Account ;
use FireflyIII\Models\AccountType ;
use FireflyIII\Models\Transaction ;
use FireflyIII\Models\TransactionCurrency ;
use FireflyIII\Models\TransactionJournal ;
use FireflyIII\Models\TransactionType ;
use FireflyIII\Repositories\Account\AccountRepositoryInterface ;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface ;
2019-08-11 07:25:59 +02:00
use FireflyIII\Repositories\Journal\JournalCLIRepositoryInterface ;
2019-06-11 20:09:13 +02:00
use FireflyIII\Repositories\Journal\JournalRepositoryInterface ;
use Illuminate\Console\Command ;
/**
* Class OtherCurrenciesCorrections
*/
class OtherCurrenciesCorrections extends Command
{
2019-08-26 07:13:48 +02:00
public const CONFIG_NAME = '480_other_currencies' ;
2019-06-11 20:09:13 +02:00
/**
* The console command description .
* @ var string
*/
protected $description = 'Update all journal currency information.' ;
/**
* The name and signature of the console command .
* @ var string
*/
protected $signature = 'firefly-iii:other-currencies {--F|force : Force the execution of this command.}' ;
/** @var array */
private $accountCurrencies ;
/** @var AccountRepositoryInterface */
private $accountRepos ;
2019-08-11 07:25:59 +02:00
/** @var JournalCLIRepositoryInterface */
private $cliRepos ;
2019-06-11 20:09:13 +02:00
/** @var int */
private $count ;
2020-03-17 14:54:25 +01:00
/** @var CurrencyRepositoryInterface */
private $currencyRepos ;
/** @var JournalRepositoryInterface */
private $journalRepos ;
2019-06-11 20:09:13 +02:00
2020-07-31 12:12:54 +02:00
2019-06-11 20:09:13 +02:00
/**
* Execute the console command .
* @ return int
*/
public function handle () : int
{
2019-06-13 15:48:35 +02:00
$this -> stupidLaravel ();
2019-06-11 20:09:13 +02:00
$start = microtime ( true );
// @codeCoverageIgnoreStart
if ( $this -> isExecuted () && true !== $this -> option ( 'force' )) {
$this -> warn ( 'This command has already been executed.' );
return 0 ;
}
// @codeCoverageIgnoreEnd
$this -> updateOtherJournalsCurrencies ();
$this -> markAsExecuted ();
2019-06-13 07:17:31 +02:00
$this -> line ( sprintf ( 'Verified %d transaction(s) and journal(s).' , $this -> count ));
2019-06-11 20:09:13 +02:00
$end = round ( microtime ( true ) - $start , 2 );
$this -> info ( sprintf ( 'Verified and fixed transaction currencies in %s seconds.' , $end ));
2020-03-21 15:43:41 +01:00
2019-06-11 20:09:13 +02:00
return 0 ;
}
/**
* @ param Account $account
* @ return TransactionCurrency | null
*/
private function getCurrency ( Account $account ) : ? TransactionCurrency
{
$accountId = $account -> id ;
2020-07-31 15:12:26 +02:00
if ( array_key_exists ( $accountId , $this -> accountCurrencies ) && 0 === $this -> accountCurrencies [ $accountId ]) {
2019-06-13 07:17:31 +02:00
return null ; // @codeCoverageIgnore
2019-06-11 20:09:13 +02:00
}
if ( isset ( $this -> accountCurrencies [ $accountId ]) && $this -> accountCurrencies [ $accountId ] instanceof TransactionCurrency ) {
2019-07-27 13:54:06 +02:00
return $this -> accountCurrencies [ $accountId ]; // @codeCoverageIgnore
2019-06-11 20:09:13 +02:00
}
2020-01-07 17:18:56 +01:00
$currency = $this -> accountRepos -> getAccountCurrency ( $account );
if ( null === $currency ) {
2019-06-13 07:17:31 +02:00
// @codeCoverageIgnoreStart
2019-06-11 20:09:13 +02:00
$this -> accountCurrencies [ $accountId ] = 0 ;
return null ;
2019-06-13 07:17:31 +02:00
// @codeCoverageIgnoreEnd
2019-06-11 20:09:13 +02:00
}
2020-01-07 17:18:56 +01:00
$this -> accountCurrencies [ $accountId ] = $currency ;
2019-06-11 20:09:13 +02:00
2020-01-07 17:18:56 +01:00
return $currency ;
2019-06-11 20:09:13 +02:00
}
2020-03-17 14:54:25 +01:00
/**
* Gets the transaction that determines the transaction that " leads " and will determine
* the currency to be used by all transactions , and the journal itself .
* @ param TransactionJournal $journal
* @ return Transaction | null
*/
private function getLeadTransaction ( TransactionJournal $journal ) : ? Transaction
{
/** @var Transaction $lead */
$lead = null ;
switch ( $journal -> transactionType -> type ) {
case TransactionType :: WITHDRAWAL :
$lead = $journal -> transactions () -> where ( 'amount' , '<' , 0 ) -> first ();
break ;
case TransactionType :: DEPOSIT :
$lead = $journal -> transactions () -> where ( 'amount' , '>' , 0 ) -> first ();
break ;
case TransactionType :: OPENING_BALANCE :
// whichever isn't an initial balance account:
2020-07-31 15:12:26 +02:00
$lead = $journal -> transactions () -> leftJoin ( 'accounts' , 'transactions.account_id' , '=' , 'accounts.id' ) -> leftJoin ( 'account_types' , 'accounts.account_type_id' , '=' , 'account_types.id' ) -> where ( 'account_types.type' , '!=' , AccountType :: INITIAL_BALANCE ) -> first ([ 'transactions.*' ]);
2020-03-17 14:54:25 +01:00
break ;
case TransactionType :: RECONCILIATION :
// whichever isn't the reconciliation account:
2020-07-31 15:12:26 +02:00
$lead = $journal -> transactions () -> leftJoin ( 'accounts' , 'transactions.account_id' , '=' , 'accounts.id' ) -> leftJoin ( 'account_types' , 'accounts.account_type_id' , '=' , 'account_types.id' ) -> where ( 'account_types.type' , '!=' , AccountType :: RECONCILIATION ) -> first ([ 'transactions.*' ]);
2020-03-17 14:54:25 +01:00
break ;
}
return $lead ;
}
2019-06-11 20:09:13 +02:00
/**
* @ return bool
*/
private function isExecuted () : bool
{
$configVar = app ( 'fireflyconfig' ) -> get ( self :: CONFIG_NAME , false );
if ( null !== $configVar ) {
2020-07-31 15:12:26 +02:00
return ( bool ) $configVar -> data ;
2019-06-11 20:09:13 +02:00
}
return false ; // @codeCoverageIgnore
}
/**
*
*/
private function markAsExecuted () : void
{
app ( 'fireflyconfig' ) -> set ( self :: CONFIG_NAME , true );
}
2020-03-17 14:54:25 +01:00
/**
* Laravel will execute ALL __construct () methods for ALL commands whenever a SINGLE command is
* executed . This leads to noticeable slow - downs and class calls . To prevent this , this method should
* be called from the handle method instead of using the constructor to initialize the command .
* @ codeCoverageIgnore
*/
private function stupidLaravel () : void
{
$this -> count = 0 ;
$this -> accountCurrencies = [];
$this -> accountRepos = app ( AccountRepositoryInterface :: class );
$this -> currencyRepos = app ( CurrencyRepositoryInterface :: class );
$this -> journalRepos = app ( JournalRepositoryInterface :: class );
$this -> cliRepos = app ( JournalCLIRepositoryInterface :: class );
}
2019-06-11 20:09:13 +02:00
/**
* @ param TransactionJournal $journal
*/
private function updateJournalCurrency ( TransactionJournal $journal ) : void
{
2019-06-13 07:17:31 +02:00
$this -> accountRepos -> setUser ( $journal -> user );
$this -> journalRepos -> setUser ( $journal -> user );
$this -> currencyRepos -> setUser ( $journal -> user );
2019-08-11 07:25:59 +02:00
$this -> cliRepos -> setUser ( $journal -> user );
2019-06-13 07:17:31 +02:00
2019-06-11 20:09:13 +02:00
$leadTransaction = $this -> getLeadTransaction ( $journal );
if ( null === $leadTransaction ) {
2019-06-13 07:17:31 +02:00
// @codeCoverageIgnoreStart
2019-06-11 20:09:13 +02:00
$this -> error ( sprintf ( 'Could not reliably determine which transaction is in the lead for transaction journal #%d.' , $journal -> id ));
return ;
2019-06-13 07:17:31 +02:00
// @codeCoverageIgnoreEnd
2019-06-11 20:09:13 +02:00
}
/** @var Account $account */
$account = $leadTransaction -> account ;
$currency = $this -> getCurrency ( $account );
if ( null === $currency ) {
2019-06-13 07:17:31 +02:00
// @codeCoverageIgnoreStart
2020-07-31 15:12:26 +02:00
$this -> error ( sprintf ( 'Account #%d ("%s") has no currency preference, so transaction journal #%d can\'t be corrected' , $account -> id , $account -> name , $journal -> id ));
2019-06-13 07:17:31 +02:00
$this -> count ++ ;
2019-06-11 20:09:13 +02:00
return ;
2019-06-13 07:17:31 +02:00
// @codeCoverageIgnoreEnd
2019-06-11 20:09:13 +02:00
}
// fix each transaction:
2020-07-31 15:12:26 +02:00
$journal -> transactions -> each ( static function ( Transaction $transaction ) use ( $currency ) {
if ( null === $transaction -> transaction_currency_id ) {
$transaction -> transaction_currency_id = $currency -> id ;
$transaction -> save ();
}
2019-06-11 20:09:13 +02:00
2020-07-31 15:12:26 +02:00
// when mismatch in transaction:
if ( ! (( int ) $transaction -> transaction_currency_id === ( int ) $currency -> id )) {
$transaction -> foreign_currency_id = ( int ) $transaction -> transaction_currency_id ;
$transaction -> foreign_amount = $transaction -> amount ;
$transaction -> transaction_currency_id = $currency -> id ;
$transaction -> save ();
2019-06-11 20:09:13 +02:00
}
2020-07-31 15:12:26 +02:00
});
2019-06-11 20:09:13 +02:00
// also update the journal, of course:
$journal -> transaction_currency_id = $currency -> id ;
$this -> count ++ ;
$journal -> save ();
}
/**
* This routine verifies that withdrawals , deposits and opening balances have the correct currency settings for
* the accounts they are linked to .
* Both source and destination must match the respective currency preference of the related asset account .
* So FF3 must verify all transactions .
*/
private function updateOtherJournalsCurrencies () : void
{
2020-07-31 15:12:26 +02:00
$set = $this -> cliRepos -> getAllJournals ([ TransactionType :: WITHDRAWAL , TransactionType :: DEPOSIT , TransactionType :: OPENING_BALANCE , TransactionType :: RECONCILIATION ,]);
2019-06-11 20:09:13 +02:00
/** @var TransactionJournal $journal */
foreach ( $set as $journal ) {
$this -> updateJournalCurrency ( $journal );
}
}
2019-08-17 12:09:03 +02:00
}