. */ declare(strict_types=1); namespace FireflyIII\Http\Controllers\Json; use Carbon\Carbon; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Models\Account; use FireflyIII\Models\AccountType; use FireflyIII\Models\Transaction; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use FireflyIII\Services\Internal\Update\CurrencyUpdateService; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Support\Collection; /** * * Class ReconcileController */ class ReconcileController extends Controller { /** @var CurrencyUpdateService */ private $accountRepos; /** @var AccountRepositoryInterface */ private $currencyRepos; /** @var JournalRepositoryInterface */ private $repository; /** * */ public function __construct() { parent::__construct(); // translations: $this->middleware( function ($request, $next) { app('view')->share('mainTitleIcon', 'fa-credit-card'); app('view')->share('title', trans('firefly.accounts')); $this->repository = app(JournalRepositoryInterface::class); $this->accountRepos = app(AccountRepositoryInterface::class); $this->currencyRepos = app(CurrencyRepositoryInterface::class); return $next($request); } ); } /** @noinspection MoreThanThreeArgumentsInspection */ /** * @param Request $request * @param Account $account * @param Carbon $start * @param Carbon $end * * @return JsonResponse * * @throws FireflyException * @throws \Throwable */ public function overview(Request $request, Account $account, Carbon $start, Carbon $end): JsonResponse { if (AccountType::ASSET !== $account->accountType->type) { throw new FireflyException(sprintf('Account %s is not an asset account.', $account->name)); } $startBalance = $request->get('startBalance'); $endBalance = $request->get('endBalance'); $transactionIds = $request->get('transactions') ?? []; $clearedIds = $request->get('cleared') ?? []; $amount = '0'; $clearedAmount = '0'; $route = route('accounts.reconcile.submit', [$account->id, $start->format('Ymd'), $end->format('Ymd')]); // get sum of transaction amounts: $transactions = $this->repository->getTransactionsById($transactionIds); $cleared = $this->repository->getTransactionsById($clearedIds); $countCleared = 0; /** @var Transaction $transaction */ foreach ($transactions as $transaction) { $amount = bcadd($amount, $transaction->amount); } /** @var Transaction $transaction */ foreach ($cleared as $transaction) { if ($transaction->transactionJournal->date <= $end) { $clearedAmount = bcadd($clearedAmount, $transaction->amount); ++$countCleared; } } $difference = bcadd(bcadd(bcsub($startBalance, $endBalance), $clearedAmount), $amount); $diffCompare = bccomp($difference, '0'); $return = [ 'post_uri' => $route, 'html' => view( 'accounts.reconcile.overview', compact( 'account', 'start', 'diffCompare', 'difference', 'end', 'clearedIds', 'transactionIds', 'clearedAmount', 'startBalance', 'endBalance', 'amount', 'route', 'countCleared' ) )->render(), ]; return response()->json($return); } /** * @param Account $account * @param Carbon $start * @param Carbon $end * * @return mixed * * @throws FireflyException * @throws \Throwable */ public function transactions(Account $account, Carbon $start, Carbon $end) { if (AccountType::INITIAL_BALANCE === $account->accountType->type) { return $this->redirectToOriginalAccount($account); } $startDate = clone $start; $startDate->subDays(1); $currencyId = (int)$this->accountRepos->getMetaValue($account, 'currency_id'); $currency = $this->currencyRepos->findNull($currencyId); if (0 === $currency) { $currency = app('amount')->getDefaultCurrency(); // @codeCoverageIgnore } $startBalance = round(app('steam')->balance($account, $startDate), $currency->decimal_places); $endBalance = round(app('steam')->balance($account, $end), $currency->decimal_places); // get the transactions $selectionStart = clone $start; $selectionStart->subDays(3); $selectionEnd = clone $end; $selectionEnd->addDays(3); // grab transactions: /** @var JournalCollectorInterface $collector */ $collector = app(JournalCollectorInterface::class); $collector->setAccounts(new Collection([$account])) ->setRange($selectionStart, $selectionEnd)->withBudgetInformation()->withOpposingAccount()->withCategoryInformation(); $transactions = $collector->getJournals(); $html = view( 'accounts.reconcile.transactions', compact('account', 'transactions', 'currency', 'start', 'end', 'selectionStart', 'selectionEnd') )->render(); return response()->json(['html' => $html, 'startBalance' => $startBalance, 'endBalance' => $endBalance]); } /** * @param Account $account * * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector * * @throws FireflyException */ private function redirectToOriginalAccount(Account $account) { /** @var Transaction $transaction */ $transaction = $account->transactions()->first(); if (null === $transaction) { throw new FireflyException(sprintf('Expected a transaction. Account #%d has none. BEEP, error.', $account->id)); // @codeCoverageIgnore } $journal = $transaction->transactionJournal; /** @var Transaction $opposingTransaction */ $opposingTransaction = $journal->transactions()->where('transactions.id', '!=', $transaction->id)->first(); if (null === $opposingTransaction) { throw new FireflyException('Expected an opposing transaction. This account has none. BEEP, error.'); // @codeCoverageIgnore } return redirect(route('accounts.show', [$opposingTransaction->account_id])); } }