. */ declare(strict_types=1); namespace FireflyIII\Console\Commands\Upgrade; use FireflyIII\Console\Commands\ShowsFriendlyMessages; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Factory\AccountMetaFactory; use FireflyIII\Models\Account; use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionJournal; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Services\Internal\Support\CreditRecalculateService; use FireflyIII\User; use Illuminate\Console\Command; use Psr\Container\ContainerExceptionInterface; use Psr\Container\NotFoundExceptionInterface; /** * Class UpgradeLiabilities */ class UpgradeLiabilities extends Command { use ShowsFriendlyMessages; public const CONFIG_NAME = '560_upgrade_liabilities'; protected $description = 'Upgrade liabilities to new 5.6.0 structure.'; protected $signature = 'firefly-iii:upgrade-liabilities {--F|force : Force the execution of this command.}'; /** * Execute the console command. * * @return int * @throws ContainerExceptionInterface * @throws FireflyException * @throws NotFoundExceptionInterface */ public function handle(): int { if ($this->isExecuted() && true !== $this->option('force')) { $this->friendlyInfo('This command has already been executed.'); return 0; } $this->upgradeLiabilities(); $this->markAsExecuted(); return 0; } /** * @return bool * @throws ContainerExceptionInterface * @throws NotFoundExceptionInterface */ private function isExecuted(): bool { $configVar = app('fireflyconfig')->get(self::CONFIG_NAME, false); if (null !== $configVar) { return (bool)$configVar->data; } return false; } /** * */ private function upgradeLiabilities(): void { $users = User::get(); /** @var User $user */ foreach ($users as $user) { $this->upgradeForUser($user); } } /** * @param User $user */ private function upgradeForUser(User $user): void { $accounts = $user->accounts() ->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id') ->whereIn('account_types.type', config('firefly.valid_liabilities')) ->get(['accounts.*']); /** @var Account $account */ foreach ($accounts as $account) { $this->upgradeLiability($account); $service = app(CreditRecalculateService::class); $service->setAccount($account); $service->recalculate(); } } /** * @param Account $account */ private function upgradeLiability(Account $account): void { /** @var AccountRepositoryInterface $repository */ $repository = app(AccountRepositoryInterface::class); $repository->setUser($account->user); // get opening balance, and correct if necessary. $openingBalance = $repository->getOpeningBalance($account); if (null !== $openingBalance) { // correct if necessary $this->correctOpeningBalance($account, $openingBalance); } // add liability direction property (if it does not yet exist!) $value = $repository->getMetaValue($account, 'liability_direction'); if (null === $value) { /** @var AccountMetaFactory $factory */ $factory = app(AccountMetaFactory::class); $factory->crud($account, 'liability_direction', 'debit'); } } /** * @param Account $account * @param TransactionJournal $openingBalance */ private function correctOpeningBalance(Account $account, TransactionJournal $openingBalance): void { $source = $this->getSourceTransaction($openingBalance); $destination = $this->getDestinationTransaction($openingBalance); if (null === $source || null === $destination) { return; } // source MUST be the liability. if ((int)$destination->account_id === (int)$account->id) { // so if not, switch things around: $sourceAccountId = (int)$source->account_id; $source->account_id = $destination->account_id; $destination->account_id = $sourceAccountId; $source->save(); $destination->save(); } } /** * @param TransactionJournal $journal * * @return Transaction|null */ private function getSourceTransaction(TransactionJournal $journal): ?Transaction { return $journal->transactions()->where('amount', '<', 0)->first(); } /** * @param TransactionJournal $journal * * @return Transaction|null */ private function getDestinationTransaction(TransactionJournal $journal): ?Transaction { return $journal->transactions()->where('amount', '>', 0)->first(); } /** * */ private function markAsExecuted(): void { app('fireflyconfig')->set(self::CONFIG_NAME, true); } }