2021-04-10 00:59:54 -05:00
|
|
|
<?php
|
2021-08-10 12:31:55 -05:00
|
|
|
|
2021-04-10 00:59:54 -05:00
|
|
|
/*
|
|
|
|
* CreditRecalculateService.php
|
|
|
|
* Copyright (c) 2021 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/>.
|
|
|
|
*/
|
|
|
|
|
2021-08-10 12:31:55 -05:00
|
|
|
declare(strict_types=1);
|
|
|
|
|
2021-04-10 00:59:54 -05:00
|
|
|
namespace FireflyIII\Services\Internal\Support;
|
|
|
|
|
|
|
|
use FireflyIII\Exceptions\FireflyException;
|
2021-04-10 10:26:36 -05:00
|
|
|
use FireflyIII\Factory\AccountMetaFactory;
|
2021-04-10 00:59:54 -05:00
|
|
|
use FireflyIII\Models\Account;
|
|
|
|
use FireflyIII\Models\Transaction;
|
|
|
|
use FireflyIII\Models\TransactionGroup;
|
|
|
|
use FireflyIII\Models\TransactionJournal;
|
2021-04-10 23:41:21 -05:00
|
|
|
use FireflyIII\Models\TransactionType;
|
2021-04-10 00:59:54 -05:00
|
|
|
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
|
|
|
use Log;
|
|
|
|
|
|
|
|
class CreditRecalculateService
|
|
|
|
{
|
2021-04-10 10:26:36 -05:00
|
|
|
private ?Account $account;
|
|
|
|
private ?TransactionGroup $group;
|
|
|
|
private AccountRepositoryInterface $repository;
|
2021-09-18 03:26:12 -05:00
|
|
|
private array $work;
|
2021-04-10 10:26:36 -05:00
|
|
|
|
|
|
|
/**
|
|
|
|
* CreditRecalculateService constructor.
|
|
|
|
*/
|
|
|
|
public function __construct()
|
|
|
|
{
|
|
|
|
$this->group = null;
|
|
|
|
$this->account = null;
|
|
|
|
$this->work = [];
|
|
|
|
}
|
2021-04-10 00:59:54 -05:00
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function recalculate(): void
|
|
|
|
{
|
|
|
|
if (true !== config('firefly.feature_flags.handle_debts')) {
|
|
|
|
Log::debug('handle_debts is disabled.');
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
2021-04-10 10:26:36 -05:00
|
|
|
if (null !== $this->group && null === $this->account) {
|
2022-12-27 00:01:13 -06:00
|
|
|
Log::debug('Have to handle a group.');
|
2021-04-10 10:26:36 -05:00
|
|
|
$this->processGroup();
|
|
|
|
}
|
|
|
|
if (null !== $this->account && null === $this->group) {
|
2022-12-27 00:01:13 -06:00
|
|
|
Log::debug('Have to handle an account.');
|
2021-04-10 10:26:36 -05:00
|
|
|
// work based on account.
|
|
|
|
$this->processAccount();
|
|
|
|
}
|
2022-11-03 23:11:05 -05:00
|
|
|
if (0 === count($this->work)) {
|
2021-04-10 10:26:36 -05:00
|
|
|
Log::debug('No work accounts, do not do CreditRecalculationService');
|
2021-04-10 00:59:54 -05:00
|
|
|
|
2021-04-10 10:26:36 -05:00
|
|
|
return;
|
2021-04-10 00:59:54 -05:00
|
|
|
}
|
2021-04-10 10:26:36 -05:00
|
|
|
Log::debug('Will now do CreditRecalculationService');
|
2021-04-26 00:29:39 -05:00
|
|
|
$this->processWork();
|
2021-04-10 00:59:54 -05:00
|
|
|
}
|
|
|
|
|
2021-04-10 10:26:36 -05:00
|
|
|
/**
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
private function processGroup(): void
|
|
|
|
{
|
|
|
|
/** @var TransactionJournal $journal */
|
|
|
|
foreach ($this->group->transactionJournals as $journal) {
|
2022-11-03 23:11:05 -05:00
|
|
|
if (0 === count($this->work)) {
|
2021-04-10 10:26:36 -05:00
|
|
|
try {
|
|
|
|
$this->findByJournal($journal);
|
|
|
|
} catch (FireflyException $e) {
|
|
|
|
Log::error($e->getTraceAsString());
|
|
|
|
Log::error(sprintf('Could not find work account for transaction group #%d.', $this->group->id));
|
|
|
|
}
|
|
|
|
}
|
2021-04-10 00:59:54 -05:00
|
|
|
}
|
2021-04-10 10:26:36 -05:00
|
|
|
Log::debug(sprintf('Done with %s', __METHOD__));
|
2021-04-10 00:59:54 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2022-12-23 22:48:04 -06:00
|
|
|
* @param TransactionJournal $journal
|
2021-04-10 00:59:54 -05:00
|
|
|
*
|
|
|
|
* @throws FireflyException
|
|
|
|
*/
|
2021-04-10 10:26:36 -05:00
|
|
|
private function findByJournal(TransactionJournal $journal): void
|
2021-04-10 00:59:54 -05:00
|
|
|
{
|
2021-04-10 10:26:36 -05:00
|
|
|
$source = $this->getSourceAccount($journal);
|
|
|
|
$destination = $this->getDestinationAccount($journal);
|
|
|
|
|
|
|
|
// destination or source must be liability.
|
|
|
|
$valid = config('firefly.valid_liabilities');
|
2022-10-30 08:24:37 -05:00
|
|
|
if (in_array($destination->accountType->type, $valid, true)) {
|
2021-04-10 10:26:36 -05:00
|
|
|
Log::debug(sprintf('Dest account type is "%s", include it.', $destination->accountType->type));
|
|
|
|
$this->work[] = $destination;
|
|
|
|
}
|
2022-10-30 08:24:37 -05:00
|
|
|
if (in_array($source->accountType->type, $valid, true)) {
|
2021-04-10 10:26:36 -05:00
|
|
|
Log::debug(sprintf('Src account type is "%s", include it.', $source->accountType->type));
|
|
|
|
$this->work[] = $source;
|
|
|
|
}
|
2021-04-10 00:59:54 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2022-12-23 22:48:04 -06:00
|
|
|
* @param TransactionJournal $journal
|
2021-04-10 00:59:54 -05:00
|
|
|
*
|
|
|
|
* @return Account
|
|
|
|
* @throws FireflyException
|
|
|
|
*/
|
2021-04-10 10:26:36 -05:00
|
|
|
private function getSourceAccount(TransactionJournal $journal): Account
|
2021-04-10 00:59:54 -05:00
|
|
|
{
|
2021-04-10 10:26:36 -05:00
|
|
|
return $this->getAccountByDirection($journal, '<');
|
2021-04-10 00:59:54 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2022-12-23 22:48:04 -06:00
|
|
|
* @param TransactionJournal $journal
|
|
|
|
* @param string $direction
|
2021-04-10 00:59:54 -05:00
|
|
|
*
|
|
|
|
* @return Account
|
|
|
|
* @throws FireflyException
|
|
|
|
*/
|
2021-04-10 10:26:36 -05:00
|
|
|
private function getAccountByDirection(TransactionJournal $journal, string $direction): Account
|
2021-04-10 00:59:54 -05:00
|
|
|
{
|
|
|
|
/** @var Transaction $transaction */
|
|
|
|
$transaction = $journal->transactions()->where('amount', $direction, '0')->first();
|
|
|
|
if (null === $transaction) {
|
|
|
|
throw new FireflyException(sprintf('Cannot find "%s"-transaction of journal #%d', $direction, $journal->id));
|
|
|
|
}
|
2021-05-24 01:06:56 -05:00
|
|
|
$foundAccount = $transaction->account;
|
|
|
|
if (null === $foundAccount) {
|
2021-04-10 00:59:54 -05:00
|
|
|
throw new FireflyException(sprintf('Cannot find "%s"-account of transaction #%d of journal #%d', $direction, $transaction->id, $journal->id));
|
|
|
|
}
|
|
|
|
|
2021-05-24 01:06:56 -05:00
|
|
|
return $foundAccount;
|
2021-04-10 00:59:54 -05:00
|
|
|
}
|
|
|
|
|
2021-04-10 10:26:36 -05:00
|
|
|
/**
|
2022-12-23 22:48:04 -06:00
|
|
|
* @param TransactionJournal $journal
|
2021-04-10 10:26:36 -05:00
|
|
|
*
|
|
|
|
* @return Account
|
|
|
|
* @throws FireflyException
|
|
|
|
*/
|
|
|
|
private function getDestinationAccount(TransactionJournal $journal): Account
|
|
|
|
{
|
|
|
|
return $this->getAccountByDirection($journal, '>');
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
private function processAccount(): void
|
|
|
|
{
|
|
|
|
$valid = config('firefly.valid_liabilities');
|
2022-10-30 08:24:37 -05:00
|
|
|
if (in_array($this->account->accountType->type, $valid, true)) {
|
2021-04-10 10:26:36 -05:00
|
|
|
Log::debug(sprintf('Account type is "%s", include it.', $this->account->accountType->type));
|
|
|
|
$this->work[] = $this->account;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-09-18 03:26:12 -05:00
|
|
|
*
|
2021-04-10 10:26:36 -05:00
|
|
|
*/
|
2021-09-18 03:26:12 -05:00
|
|
|
private function processWork(): void
|
2021-04-10 10:26:36 -05:00
|
|
|
{
|
2021-09-18 03:26:12 -05:00
|
|
|
$this->repository = app(AccountRepositoryInterface::class);
|
|
|
|
foreach ($this->work as $account) {
|
|
|
|
$this->processWorkAccount($account);
|
|
|
|
}
|
|
|
|
Log::debug(sprintf('Done with %s', __METHOD__));
|
2021-04-10 10:26:36 -05:00
|
|
|
}
|
|
|
|
|
2021-04-10 00:59:54 -05:00
|
|
|
/**
|
2022-12-23 22:48:04 -06:00
|
|
|
* @param Account $account
|
2021-04-10 00:59:54 -05:00
|
|
|
*/
|
2021-09-18 03:26:12 -05:00
|
|
|
private function processWorkAccount(Account $account): void
|
2021-04-10 00:59:54 -05:00
|
|
|
{
|
2021-09-18 03:26:12 -05:00
|
|
|
Log::debug(sprintf('Now in %s(#%d)', __METHOD__, $account->id));
|
|
|
|
|
|
|
|
// get opening balance (if present)
|
|
|
|
$this->repository->setUser($account->user);
|
|
|
|
$startOfDebt = $this->repository->getOpeningBalanceAmount($account) ?? '0';
|
|
|
|
$leftOfDebt = app('steam')->positive($startOfDebt);
|
|
|
|
|
|
|
|
/** @var AccountMetaFactory $factory */
|
|
|
|
$factory = app(AccountMetaFactory::class);
|
|
|
|
$factory->crud($account, 'start_of_debt', $startOfDebt);
|
|
|
|
|
|
|
|
// get direction of liability:
|
2022-12-23 22:48:04 -06:00
|
|
|
$direction = (string)$this->repository->getMetaValue($account, 'liability_direction');
|
2021-09-18 03:26:12 -05:00
|
|
|
|
|
|
|
// now loop all transactions (except opening balance and credit thing)
|
|
|
|
$transactions = $account->transactions()->get();
|
|
|
|
/** @var Transaction $transaction */
|
|
|
|
foreach ($transactions as $transaction) {
|
|
|
|
$leftOfDebt = $this->processTransaction($account, $direction, $transaction, $leftOfDebt);
|
|
|
|
}
|
|
|
|
$factory->crud($account, 'current_debt', $leftOfDebt);
|
|
|
|
|
|
|
|
Log::debug(sprintf('Done with %s(#%d)', __METHOD__, $account->id));
|
2021-04-10 00:59:54 -05:00
|
|
|
}
|
|
|
|
|
2021-04-10 23:41:21 -05:00
|
|
|
/**
|
2022-12-23 22:48:04 -06:00
|
|
|
* @param Account $account
|
|
|
|
* @param string $direction
|
|
|
|
* @param Transaction $transaction
|
|
|
|
* @param string $amount
|
2021-04-10 23:41:21 -05:00
|
|
|
*
|
|
|
|
* @return string
|
|
|
|
*/
|
2021-05-15 02:16:54 -05:00
|
|
|
private function processTransaction(Account $account, string $direction, Transaction $transaction, string $amount): string
|
2021-04-10 23:41:21 -05:00
|
|
|
{
|
2021-04-26 00:29:39 -05:00
|
|
|
Log::debug(sprintf('Now in %s(#%d, %s)', __METHOD__, $transaction->id, $amount));
|
2022-12-23 22:48:04 -06:00
|
|
|
$journal = $transaction->transactionJournal;
|
|
|
|
$foreignCurrency = $transaction->foreignCurrency;
|
|
|
|
$accountCurrency = $this->repository->getAccountCurrency($account);
|
|
|
|
$groupId = $journal->transaction_group_id;
|
|
|
|
$type = $journal->transactionType->type;
|
|
|
|
|
|
|
|
Log::debug(sprintf('Account currency is #%d (%s)', $accountCurrency->id, $accountCurrency->code));
|
|
|
|
|
2021-05-15 02:16:54 -05:00
|
|
|
if ('' === $direction) {
|
|
|
|
Log::debug('Since direction is "", do nothing.');
|
2021-04-26 00:29:39 -05:00
|
|
|
|
2021-05-15 02:16:54 -05:00
|
|
|
return $amount;
|
|
|
|
}
|
2022-12-23 22:48:04 -06:00
|
|
|
// amount to use depends on the currency:
|
|
|
|
$usedAmount = $transaction->amount;
|
|
|
|
if (null !== $foreignCurrency && $foreignCurrency->id === $accountCurrency->id) {
|
|
|
|
$usedAmount = $transaction->foreign_amount;
|
|
|
|
Log::debug('Will now use foreign amount!');
|
|
|
|
}
|
|
|
|
|
2021-05-15 02:16:54 -05:00
|
|
|
|
|
|
|
Log::debug(sprintf('Processing group #%d, journal #%d of type "%s"', $journal->id, $groupId, $type));
|
|
|
|
|
|
|
|
// it's a withdrawal into this liability (from asset).
|
2022-12-27 00:01:13 -06:00
|
|
|
// if it's a credit ("I am owed"), this increases the amount due,
|
|
|
|
// because we're lending person X more money
|
2021-05-15 02:16:54 -05:00
|
|
|
if (
|
2021-09-18 03:21:29 -05:00
|
|
|
$type === TransactionType::WITHDRAWAL
|
2022-12-23 22:48:04 -06:00
|
|
|
&& (int)$account->id === (int)$transaction->account_id
|
|
|
|
&& 1 === bccomp($usedAmount, '0')
|
2021-05-15 02:16:54 -05:00
|
|
|
&& 'credit' === $direction
|
|
|
|
) {
|
2022-12-27 00:01:13 -06:00
|
|
|
$amount = bcadd($amount, app('steam')->positive($usedAmount));
|
2022-12-29 12:42:26 -06:00
|
|
|
Log::debug(
|
|
|
|
sprintf('Is withdrawal (%s) into credit liability #%d, will increase amount due to %s.', $transaction->account_id, $usedAmount, $amount)
|
|
|
|
);
|
2021-04-26 00:29:39 -05:00
|
|
|
return $amount;
|
|
|
|
}
|
2021-05-15 02:16:54 -05:00
|
|
|
|
2022-12-27 00:05:56 -06:00
|
|
|
// it's a deposit out of this liability (to asset).
|
|
|
|
// if it's a credit ("I am owed") this decreases the amount due.
|
|
|
|
// because the person is paying us back.
|
2021-05-15 02:16:54 -05:00
|
|
|
if (
|
2021-09-18 03:21:29 -05:00
|
|
|
$type === TransactionType::DEPOSIT
|
2022-12-23 22:48:04 -06:00
|
|
|
&& (int)$account->id === (int)$transaction->account_id
|
|
|
|
&& -1 === bccomp($usedAmount, '0')
|
2021-05-15 02:16:54 -05:00
|
|
|
&& 'credit' === $direction
|
|
|
|
) {
|
2022-12-27 00:05:56 -06:00
|
|
|
$amount = bcsub($amount, app('steam')->positive($usedAmount));
|
|
|
|
Log::debug(sprintf('Is deposit (%s) from credit liability #%d, will decrease amount due to %s.', $transaction->account_id, $usedAmount, $amount));
|
2021-04-26 00:29:39 -05:00
|
|
|
return $amount;
|
|
|
|
}
|
|
|
|
|
2021-04-10 23:41:21 -05:00
|
|
|
if (in_array($type, [TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::TRANSFER], true)) {
|
2022-12-23 22:48:04 -06:00
|
|
|
$amount = bcadd($amount, bcmul($usedAmount, '-1'));
|
2021-04-10 23:41:21 -05:00
|
|
|
}
|
2021-04-26 00:29:39 -05:00
|
|
|
Log::debug(sprintf('Amount is now %s', $amount));
|
2021-04-10 23:41:21 -05:00
|
|
|
|
|
|
|
return $amount;
|
|
|
|
}
|
|
|
|
|
2021-09-18 03:26:12 -05:00
|
|
|
/**
|
2022-12-23 22:48:04 -06:00
|
|
|
* @param Account|null $account
|
2021-09-18 03:26:12 -05:00
|
|
|
*/
|
|
|
|
public function setAccount(?Account $account): void
|
|
|
|
{
|
|
|
|
$this->account = $account;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2022-12-23 22:48:04 -06:00
|
|
|
* @param TransactionGroup $group
|
2021-09-18 03:26:12 -05:00
|
|
|
*/
|
|
|
|
public function setGroup(TransactionGroup $group): void
|
|
|
|
{
|
|
|
|
$this->group = $group;
|
|
|
|
}
|
2021-05-12 23:17:53 -05:00
|
|
|
}
|