2020-05-09 14:47:47 +02:00
< ? php
2020-06-30 19:05:35 +02:00
/**
2025-01-04 08:44:02 +01:00
* UpdatePiggyBank . php
2020-06-30 19:05:35 +02:00
* Copyright ( c ) 2020 james @ firefly - iii . org
*
* This file is part of Firefly III ( https :// github . com / firefly - iii ) .
*
* 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 .
*
* This program 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 Affero General Public License for more details .
*
* 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 />.
*/
2020-05-09 14:47:47 +02:00
declare ( strict_types = 1 );
2021-04-05 22:12:57 +02:00
2020-05-09 14:47:47 +02:00
namespace FireflyIII\TransactionRules\Actions ;
2021-04-05 22:12:57 +02:00
2023-08-13 15:01:12 +02:00
use FireflyIII\Events\Model\Rule\RuleActionFailedOnArray ;
2022-10-02 06:23:31 +02:00
use FireflyIII\Events\TriggeredAuditLog ;
2025-01-01 16:43:31 +01:00
use FireflyIII\Models\Account ;
2020-05-09 14:47:47 +02:00
use FireflyIII\Models\PiggyBank ;
use FireflyIII\Models\RuleAction ;
use FireflyIII\Models\Transaction ;
2022-10-02 14:37:50 +02:00
use FireflyIII\Models\TransactionJournal ;
2020-05-09 14:47:47 +02:00
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface ;
use FireflyIII\User ;
2025-01-01 16:43:31 +01:00
use Illuminate\Support\Facades\Log ;
2020-05-09 14:47:47 +02:00
2025-01-04 08:42:06 +01:00
class UpdatePiggyBank implements ActionInterface
2020-05-09 14:47:47 +02:00
{
/**
* TriggerInterface constructor .
*/
2025-05-04 13:55:42 +02:00
public function __construct ( private readonly RuleAction $action ) {}
2020-05-09 14:47:47 +02:00
2021-03-23 06:42:26 +01:00
public function actOnArray ( array $journal ) : bool
2020-05-09 14:47:47 +02:00
{
2024-03-07 17:18:46 -05:00
$actionValue = $this -> action -> getValue ( $journal );
2025-01-04 08:44:02 +01:00
Log :: debug ( sprintf ( 'Triggered rule action UpdatePiggyBank on journal #%d' , $journal [ 'transaction_journal_id' ]));
2021-09-11 06:52:49 +02:00
// refresh the transaction type.
2023-11-28 17:18:31 +01:00
/** @var User $user */
2025-01-01 16:47:50 +01:00
$user = User :: find ( $journal [ 'user_id' ]);
2023-12-20 19:35:52 +01:00
2022-10-02 14:37:50 +02:00
/** @var TransactionJournal $journalObj */
2025-01-01 16:47:50 +01:00
$journalObj = $user -> transactionJournals () -> find ( $journal [ 'transaction_journal_id' ]);
2021-09-11 06:52:49 +02:00
2025-01-01 16:47:50 +01:00
$piggyBank = $this -> findPiggyBank ( $user , $actionValue );
2021-03-23 06:42:26 +01:00
if ( null === $piggyBank ) {
2025-01-01 16:43:31 +01:00
Log :: info (
2024-03-07 17:18:46 -05:00
sprintf ( 'No piggy bank named "%s", cant execute action #%d of rule #%d' , $actionValue , $this -> action -> id , $this -> action -> rule_id )
2021-03-23 06:42:26 +01:00
);
2024-03-07 17:18:46 -05:00
event ( new RuleActionFailedOnArray ( $this -> action , $journal , trans ( 'rules.cannot_find_piggy' , [ 'name' => $actionValue ])));
2023-12-20 19:35:52 +01:00
2021-03-23 06:42:26 +01:00
return false ;
2020-05-09 14:47:47 +02:00
}
2025-01-01 16:43:31 +01:00
Log :: debug ( sprintf ( 'Found piggy bank #%d ("%s")' , $piggyBank -> id , $piggyBank -> name ));
2023-12-20 19:35:52 +01:00
2022-12-31 13:32:42 +01:00
/** @var Transaction $destination */
2022-12-11 07:29:06 +01:00
$destination = $journalObj -> transactions () -> where ( 'amount' , '>' , 0 ) -> first ();
2021-03-23 06:42:26 +01:00
2025-01-01 16:47:50 +01:00
$accounts = $this -> getAccounts ( $journalObj );
2025-01-01 16:43:31 +01:00
Log :: debug ( sprintf ( 'Source account is #%d: "%s"' , $accounts [ 'source' ] -> id , $accounts [ 'source' ] -> name ));
Log :: debug ( sprintf ( 'Destination account is #%d: "%s"' , $accounts [ 'destination' ] -> id , $accounts [ 'source' ] -> name ));
2021-03-23 06:42:26 +01:00
2025-01-01 16:43:31 +01:00
// if connected to source but not to destination, needs to be removed from source account connected to piggy bank.
if ( $this -> isConnected ( $piggyBank , $accounts [ 'source' ]) && ! $this -> isConnected ( $piggyBank , $accounts [ 'destination' ])) {
Log :: debug ( 'Piggy bank account is linked to source, so remove amount from piggy bank.' );
$this -> removeAmount ( $piggyBank , $journal , $journalObj , $accounts [ 'source' ], $destination -> amount );
2022-12-29 19:42:26 +01:00
event (
new TriggeredAuditLog (
$this -> action -> rule ,
$journalObj ,
'remove_from_piggy' ,
null ,
[
'currency_symbol' => $journalObj -> transactionCurrency -> symbol ,
'decimal_places' => $journalObj -> transactionCurrency -> decimal_places ,
'amount' => $destination -> amount ,
'piggy' => $piggyBank -> name ,
]
)
);
2022-10-02 06:23:31 +02:00
2021-03-23 06:42:26 +01:00
return true ;
2020-05-09 14:47:47 +02:00
}
2025-01-01 16:43:31 +01:00
// if connected to destination but not to source, needs to be removed from source account connected to piggy bank.
if ( ! $this -> isConnected ( $piggyBank , $accounts [ 'source' ]) && $this -> isConnected ( $piggyBank , $accounts [ 'destination' ])) {
Log :: debug ( 'Piggy bank account is linked to source, so add amount to piggy bank.' );
$this -> addAmount ( $piggyBank , $journal , $journalObj , $accounts [ 'destination' ], $destination -> amount );
2020-05-09 14:47:47 +02:00
2022-12-29 19:42:26 +01:00
event (
new TriggeredAuditLog (
$this -> action -> rule ,
$journalObj ,
'add_to_piggy' ,
null ,
[
2024-12-22 08:43:12 +01:00
'currency_symbol' => $journalObj -> transactionCurrency -> symbol ,
'decimal_places' => $journalObj -> transactionCurrency -> decimal_places ,
'amount' => $destination -> amount ,
'piggy' => $piggyBank -> name ,
'piggy_id' => $piggyBank -> id ,
2022-12-29 19:42:26 +01:00
]
)
);
2022-10-02 06:23:31 +02:00
2021-03-23 06:42:26 +01:00
return true ;
}
2025-01-01 16:43:31 +01:00
if ( $this -> isConnected ( $piggyBank , $accounts [ 'source' ]) && $this -> isConnected ( $piggyBank , $accounts [ 'destination' ])) {
Log :: info ( sprintf ( 'Piggy bank is linked to BOTH source ("#%d") and destination ("#%d"), so no action will be taken.' , $accounts [ 'source' ] -> id , $accounts [ 'destination' ] -> id ));
event ( new RuleActionFailedOnArray ( $this -> action , $journal , trans ( 'rules.no_link_piggy' , [ 'name' => $actionValue ])));
2025-01-01 16:47:50 +01:00
2025-01-01 16:43:31 +01:00
return false ;
}
Log :: info ( sprintf ( 'Piggy bank is not linked to source ("#%d") or destination ("#%d"), so no action will be taken.' , $accounts [ 'source' ] -> id , $accounts [ 'destination' ] -> id ));
2024-03-07 17:18:46 -05:00
event ( new RuleActionFailedOnArray ( $this -> action , $journal , trans ( 'rules.no_link_piggy' , [ 'name' => $actionValue ])));
2023-12-20 19:35:52 +01:00
2022-07-05 19:12:22 +02:00
return false ;
2020-05-09 14:47:47 +02:00
}
2024-03-06 20:54:50 -05:00
private function findPiggyBank ( User $user , string $name ) : ? PiggyBank
2023-06-21 12:34:58 +02:00
{
2025-01-01 16:43:31 +01:00
/** @var PiggyBankRepositoryInterface $repository */
$repository = app ( PiggyBankRepositoryInterface :: class );
$repository -> setUser ( $user );
2025-01-01 16:47:50 +01:00
2025-01-01 16:43:31 +01:00
return $repository -> findByName ( $name );
2023-06-21 12:34:58 +02:00
}
2025-05-04 17:41:26 +02:00
private function getAccounts ( TransactionJournal $journal ) : array
{
return [
'source' => $journal -> transactions () -> where ( 'amount' , '<' , '0' ) -> first () ? -> account ,
'destination' => $journal -> transactions () -> where ( 'amount' , '>' , '0' ) -> first () ? -> account ,
];
}
private function isConnected ( PiggyBank $piggyBank , ? Account $link ) : bool
{
if ( null === $link ) {
return false ;
}
foreach ( $piggyBank -> accounts as $account ) {
if ( $account -> id === $link -> id ) {
return true ;
}
}
Log :: debug ( sprintf ( 'Piggy bank is not connected to account #%d "%s"' , $link -> id , $link -> name ));
return false ;
}
2025-01-01 16:43:31 +01:00
private function removeAmount ( PiggyBank $piggyBank , array $array , TransactionJournal $journal , Account $account , string $amount ) : void
2020-05-09 14:47:47 +02:00
{
$repository = app ( PiggyBankRepositoryInterface :: class );
$repository -> setUser ( $journal -> user );
2023-06-21 12:34:58 +02:00
// how much can we remove from this piggy bank?
2025-01-01 16:47:50 +01:00
$toRemove = $repository -> getCurrentAmount ( $piggyBank , $account );
2025-01-01 16:43:31 +01:00
Log :: debug ( sprintf ( 'Amount is %s, max to remove is %s' , $amount , $toRemove ));
2022-12-11 07:29:06 +01:00
2023-06-21 12:34:58 +02:00
// if $amount is bigger than $toRemove, shrink it.
2025-01-01 16:47:50 +01:00
$amount = - 1 === bccomp ( $amount , $toRemove ) ? $amount : $toRemove ;
2025-01-01 16:43:31 +01:00
Log :: debug ( sprintf ( 'Amount is now %s' , $amount ));
2020-05-09 14:47:47 +02:00
// if amount is zero, stop.
if ( 0 === bccomp ( '0' , $amount )) {
2025-01-01 16:43:31 +01:00
Log :: warning ( 'Amount left is zero, stop.' );
2024-11-02 05:17:46 +01:00
event ( new RuleActionFailedOnArray ( $this -> action , $array , trans ( 'rules.cannot_remove_zero_piggy' , [ 'name' => $piggyBank -> name ])));
2020-05-09 14:47:47 +02:00
return ;
}
2025-01-01 16:43:31 +01:00
if ( false === $repository -> canRemoveAmount ( $piggyBank , $account , $amount )) {
Log :: warning ( sprintf ( 'Cannot remove %s from piggy bank.' , $amount ));
2024-11-03 09:01:53 +01:00
event ( new RuleActionFailedOnArray ( $this -> action , $array , trans ( 'rules.cannot_remove_from_piggy' , [ 'amount' => $amount , 'name' => $piggyBank -> name ])));
2020-05-09 14:47:47 +02:00
return ;
}
2025-01-01 16:43:31 +01:00
Log :: debug ( sprintf ( 'Will now remove %s from piggy bank.' , $amount ));
2023-05-29 13:56:55 +02:00
2025-01-01 16:43:31 +01:00
$repository -> removeAmount ( $piggyBank , $account , $amount , $journal );
2020-05-09 14:47:47 +02:00
}
2020-08-23 07:42:14 +02:00
2025-01-01 16:43:31 +01:00
private function addAmount ( PiggyBank $piggyBank , array $array , TransactionJournal $journal , Account $account , string $amount ) : void
2020-08-23 07:42:14 +02:00
{
2021-03-23 06:42:26 +01:00
$repository = app ( PiggyBankRepositoryInterface :: class );
$repository -> setUser ( $journal -> user );
2020-08-28 11:24:55 +02:00
2023-06-21 12:34:58 +02:00
// how much can we add to the piggy bank?
2024-11-30 16:02:30 +01:00
if ( 0 !== bccomp ( $piggyBank -> target_amount , '0' )) {
2025-01-01 16:47:50 +01:00
$toAdd = bcsub ( $piggyBank -> target_amount , $repository -> getCurrentAmount ( $piggyBank , $account ));
2025-01-01 16:43:31 +01:00
Log :: debug ( sprintf ( 'Max amount to add to piggy bank is %s, amount is %s' , $toAdd , $amount ));
2023-06-21 12:34:58 +02:00
// update amount to fit:
$amount = - 1 === bccomp ( $amount , $toAdd ) ? $amount : $toAdd ;
2025-01-01 16:43:31 +01:00
Log :: debug ( sprintf ( 'Amount is now %s' , $amount ));
2023-06-21 12:34:58 +02:00
}
2024-11-30 16:02:30 +01:00
if ( 0 === bccomp ( $piggyBank -> target_amount , '0' )) {
2025-01-01 16:43:31 +01:00
Log :: debug ( 'Target amount is zero, can add anything.' );
2023-06-21 12:34:58 +02:00
}
2020-08-28 11:24:55 +02:00
2021-03-23 06:42:26 +01:00
// if amount is zero, stop.
if ( 0 === bccomp ( '0' , $amount )) {
2025-01-01 16:43:31 +01:00
Log :: warning ( 'Amount left is zero, stop.' );
2024-11-02 05:17:46 +01:00
event ( new RuleActionFailedOnArray ( $this -> action , $array , trans ( 'rules.cannot_add_zero_piggy' , [ 'name' => $piggyBank -> name ])));
2024-11-03 09:01:53 +01:00
2021-03-23 06:42:26 +01:00
return ;
2020-08-28 11:24:55 +02:00
}
2025-01-01 16:43:31 +01:00
if ( false === $repository -> canAddAmount ( $piggyBank , $account , $amount )) {
Log :: warning ( sprintf ( 'Cannot add %s to piggy bank.' , $amount ));
2024-11-03 09:01:53 +01:00
event ( new RuleActionFailedOnArray ( $this -> action , $array , trans ( 'rules.cannot_add_to_piggy' , [ 'amount' => $amount , 'name' => $piggyBank -> name ])));
2021-03-23 06:42:26 +01:00
return ;
2020-08-28 11:24:55 +02:00
}
2025-01-01 16:43:31 +01:00
Log :: debug ( sprintf ( 'Will now add %s to piggy bank.' , $amount ));
$repository -> addAmount ( $piggyBank , $account , $amount , $journal );
}
2020-05-09 14:47:47 +02:00
}