2018-10-13 21:32:20 +02:00
<? php
namespace FireflyIII\Console\Commands ;
use Carbon\Carbon ;
use FireflyIII\Helpers\Collector\TransactionCollectorInterface ;
use FireflyIII\Models\AccountType ;
use FireflyIII\Models\Rule ;
2018-10-14 16:40:12 +02:00
use FireflyIII\Models\RuleGroup ;
2018-10-13 21:32:20 +02:00
use FireflyIII\Models\Transaction ;
use FireflyIII\Repositories\Account\AccountRepositoryInterface ;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface ;
use FireflyIII\Repositories\Rule\RuleRepositoryInterface ;
use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface ;
use FireflyIII\TransactionRules\Processor ;
use Illuminate\Console\Command ;
use Illuminate\Support\Collection ;
/**
*
* Class ApplyRules
*/
class ApplyRules extends Command
{
use VerifiesAccessToken ;
/**
* The console command description.
*
* @var string
*/
protected $description = 'This command will apply your rules and rule groups on a selection of your transactions.' ;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature
= 'firefly:apply-rules
{--user=1 : The user ID that the import should import for.}
{--token= : The user\'s access token.}
{--accounts= : A comma-separated list of asset accounts or liabilities to apply your rules to.}
{--rule_groups= : A comma-separated list of rule groups to apply. Take the ID\'s of these rule groups from the Firefly III interface.}
{--rules= : A comma-separated list of rules to apply. Take the ID\'s of these rules from the Firefly III interface. Using this option overrules the option that selects rule groups.}
{--all_rules : If set, will overrule both settings and simply apply ALL of your rules.}
{--start_date= : The date of the earliest transaction to be included (inclusive). If omitted, will be your very first transaction ever. Format: YYYY-MM-DD}
{--end_date= : The date of the latest transaction to be included (inclusive). If omitted, will be your latest transaction ever. Format: YYYY-MM-DD}' ;
/** @var Collection */
private $accounts ;
/** @var Carbon */
private $endDate ;
/** @var Collection */
2018-10-14 16:40:12 +02:00
private $results ;
/** @var Collection */
private $ruleGroups ;
/** @var Collection */
2018-10-13 21:32:20 +02:00
private $rules ;
/** @var Carbon */
private $startDate ;
/**
* Create a new command instance.
*
* @return void
*/
public function __construct ()
{
parent :: __construct ();
2018-10-14 16:40:12 +02:00
$this -> accounts = new Collection ;
$this -> rules = new Collection ;
$this -> ruleGroups = new Collection ;
$this -> results = new Collection ;
2018-10-13 21:32:20 +02:00
}
/**
* Execute the console command.
*
* @return int
* @throws \FireflyIII\Exceptions\FireflyException
*/
public function handle () : int
{
if ( ! $this -> verifyAccessToken ()) {
$this -> error ( 'Invalid access token.' );
return 1 ;
}
$result = $this -> verifyInput ();
if ( false === $result ) {
return 1 ;
}
2018-10-14 16:40:12 +02:00
2018-10-13 21:32:20 +02:00
// get transactions from asset accounts.
/** @var TransactionCollectorInterface $collector */
$collector = app ( TransactionCollectorInterface :: class );
$collector -> setUser ( $this -> getUser ());
$collector -> setAccounts ( $this -> accounts );
$collector -> setRange ( $this -> startDate , $this -> endDate );
$transactions = $collector -> getTransactions ();
$count = $transactions -> count ();
2018-10-14 16:40:12 +02:00
// first run all rule groups:
/** @var RuleGroupRepositoryInterface $ruleGroupRepos */
$ruleGroupRepos = app ( RuleGroupRepositoryInterface :: class );
$ruleGroupRepos -> setUser ( $this -> getUser ());
2018-10-13 21:32:20 +02:00
2018-10-14 16:40:12 +02:00
/** @var RuleGroup $ruleGroup */
foreach ( $this -> ruleGroups as $ruleGroup ) {
$this -> line ( sprintf ( 'Going to apply rule group "%s" to %d transaction(s).' , $ruleGroup -> title , $count ));
$rules = $ruleGroupRepos -> getActiveStoreRules ( $ruleGroup );
$this -> applyRuleSelection ( $rules , $transactions , true );
}
2018-10-13 21:32:20 +02:00
2018-10-14 16:40:12 +02:00
// then run all rules (rule groups should be empty).
if ( $this -> rules -> count () > 0 ) {
$this -> line ( sprintf ( 'Will apply %d rule(s) to %d transaction(s)' , $this -> rules -> count (), $transactions -> count ()));
$this -> applyRuleSelection ( $this -> rules , $transactions , false );
2018-10-13 21:32:20 +02:00
}
2018-10-14 16:40:12 +02:00
// filter results:
$this -> results = $this -> results -> unique (
2018-10-13 21:32:20 +02:00
function ( Transaction $transaction ) {
return ( int ) $transaction -> journal_id ;
}
);
$this -> line ( '' );
2018-10-14 16:40:12 +02:00
if ( 0 === $this -> results -> count ()) {
2018-10-13 21:32:20 +02:00
$this -> line ( 'The rules were fired but did not influence any transactions.' );
}
2018-10-14 16:40:12 +02:00
if ( $this -> results -> count () > 0 ) {
$this -> line ( sprintf ( 'The rule(s) was/were fired, and influenced %d transaction(s).' , $this -> results -> count ()));
foreach ( $this -> results as $result ) {
2018-10-13 21:32:20 +02:00
$this -> line (
vsprintf (
'Transaction #%d: "%s" (%s %s)' ,
[
$result -> journal_id ,
$result -> description ,
$result -> transaction_currency_code ,
round ( $result -> transaction_amount , $result -> transaction_currency_dp ),
]
)
);
}
}
return 0 ;
}
2018-10-14 16:40:12 +02:00
/**
* @param Collection $rules
* @param Collection $transactions
* @param bool $breakProcessing
*
* @throws \FireflyIII\Exceptions\FireflyException
*/
private function applyRuleSelection ( Collection $rules , Collection $transactions , bool $breakProcessing ) : void
{
$bar = $this -> output -> createProgressBar ( $rules -> count () * $transactions -> count ());
foreach ( $rules as $rule ) {
/** @var Processor $processor */
$processor = app ( Processor :: class );
$processor -> make ( $rule , true );
/** @var Transaction $transaction */
foreach ( $transactions as $transaction ) {
/** @var Rule $rule */
$bar -> advance ();
$result = $processor -> handleTransaction ( $transaction );
if ( true === $result ) {
$this -> results -> push ( $transaction );
}
}
if ( true === $rule -> stop_processing && true === $breakProcessing ) {
$this -> line ( '' );
$this -> line ( sprintf ( 'Rule #%d ("%s") says to stop processing.' , $rule -> id , $rule -> title ));
return ;
}
}
$this -> line ( '' );
}
2018-10-13 21:32:20 +02:00
/**
*
*/
private function grabAllRules () : void
{
if ( true === $this -> option ( 'all_rules' )) {
/** @var RuleRepositoryInterface $ruleRepos */
$ruleRepos = app ( RuleRepositoryInterface :: class );
$ruleRepos -> setUser ( $this -> getUser ());
$this -> rules = $ruleRepos -> getAll ();
2018-10-14 16:40:12 +02:00
// reset rule groups.
$this -> ruleGroups = new Collection ;
2018-10-13 21:32:20 +02:00
}
}
/**
*
*/
private function parseDates () : void
{
// parse start date.
$startDate = Carbon :: create () -> startOfMonth ();
$startString = $this -> option ( 'start_date' );
if ( null === $startString ) {
/** @var JournalRepositoryInterface $repository */
$repository = app ( JournalRepositoryInterface :: class );
$repository -> setUser ( $this -> getUser ());
$first = $repository -> firstNull ();
if ( null !== $first ) {
$startDate = $first -> date ;
}
}
if ( null !== $startString && '' !== $startString ) {
$startDate = Carbon :: createFromFormat ( 'Y-m-d' , $startString );
}
// parse end date
$endDate = Carbon :: now ();
$endString = $this -> option ( 'end_date' );
if ( null !== $endString && '' !== $endString ) {
$endDate = Carbon :: createFromFormat ( 'Y-m-d' , $endString );
}
if ( $startDate > $endDate ) {
[ $endDate , $startDate ] = [ $startDate , $endDate ];
}
$this -> startDate = $startDate ;
$this -> endDate = $endDate ;
}
/**
* @return bool
* @throws \FireflyIII\Exceptions\FireflyException
*/
private function verifyInput () : bool
{
// verify account.
$result = $this -> verifyInputAccounts ();
if ( false === $result ) {
return $result ;
}
// verify rule groups.
$result = $this -> verifyRuleGroups ();
if ( false === $result ) {
return $result ;
}
// verify rules.
$result = $this -> verifyRules ();
if ( false === $result ) {
return $result ;
}
$this -> grabAllRules ();
$this -> parseDates ();
//$this->line('Number of rules found: ' . $this->rules->count());
$this -> line ( 'Start date is ' . $this -> startDate -> format ( 'Y-m-d' ));
$this -> line ( 'End date is ' . $this -> endDate -> format ( 'Y-m-d' ));
return true ;
}
/**
* @return bool
* @throws \FireflyIII\Exceptions\FireflyException
*/
private function verifyInputAccounts () : bool
{
$accountString = $this -> option ( 'accounts' );
if ( null === $accountString || '' === $accountString ) {
$this -> error ( 'Please use the --accounts to indicate the accounts to apply rules to.' );
return false ;
}
$finalList = new Collection ;
$accountList = explode ( ',' , $accountString );
if ( 0 === \count ( $accountList )) {
$this -> error ( 'Please use the --accounts to indicate the accounts to apply rules to.' );
return false ;
}
/** @var AccountRepositoryInterface $accountRepository */
$accountRepository = app ( AccountRepositoryInterface :: class );
$accountRepository -> setUser ( $this -> getUser ());
foreach ( $accountList as $accountId ) {
$accountId = ( int ) $accountId ;
$account = $accountRepository -> findNull ( $accountId );
if ( null !== $account
&& \in_array (
$account -> accountType -> type , [ AccountType :: DEFAULT , AccountType :: DEBT , AccountType :: ASSET , AccountType :: LOAN , AccountType :: MORTGAGE ], true
)) {
$finalList -> push ( $account );
}
}
if ( 0 === $finalList -> count ()) {
$this -> error ( 'Please make sure all accounts in --accounts are asset accounts or liabilities.' );
return false ;
}
$this -> accounts = $finalList ;
return true ;
}
/**
* @return bool
* @throws \FireflyIII\Exceptions\FireflyException
*/
private function verifyRuleGroups () : bool
{
$ruleGroupString = $this -> option ( 'rule_groups' );
if ( null === $ruleGroupString || '' === $ruleGroupString ) {
// can be empty.
return true ;
}
$ruleGroupList = explode ( ',' , $ruleGroupString );
if ( 0 === \count ( $ruleGroupList )) {
// can be empty.
return true ;
}
/** @var RuleGroupRepositoryInterface $ruleGroupRepos */
$ruleGroupRepos = app ( RuleGroupRepositoryInterface :: class );
$ruleGroupRepos -> setUser ( $this -> getUser ());
foreach ( $ruleGroupList as $ruleGroupId ) {
$ruleGroupId = ( int ) $ruleGroupId ;
$ruleGroup = $ruleGroupRepos -> find ( $ruleGroupId );
2018-10-14 16:40:12 +02:00
$this -> ruleGroups -> push ( $ruleGroup );
2018-10-13 21:32:20 +02:00
}
return true ;
}
/**
* @return bool
* @throws \FireflyIII\Exceptions\FireflyException
*/
private function verifyRules () : bool
{
$ruleString = $this -> option ( 'rules' );
if ( null === $ruleString || '' === $ruleString ) {
// can be empty.
return true ;
}
$finalList = new Collection ;
$ruleList = explode ( ',' , $ruleString );
if ( 0 === \count ( $ruleList )) {
// can be empty.
return true ;
}
/** @var RuleRepositoryInterface $ruleRepos */
$ruleRepos = app ( RuleRepositoryInterface :: class );
$ruleRepos -> setUser ( $this -> getUser ());
foreach ( $ruleList as $ruleId ) {
$ruleId = ( int ) $ruleId ;
$rule = $ruleRepos -> find ( $ruleId );
if ( null !== $rule ) {
$finalList -> push ( $rule );
}
}
if ( $finalList -> count () > 0 ) {
2018-10-14 16:40:12 +02:00
// reset rule groups.
$this -> ruleGroups = new Collection ;
$this -> rules = $finalList ;
2018-10-13 21:32:20 +02:00
}
return true ;
}
}