mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-02-25 18:45:27 -06:00
Make sure the convert controller works again.
This commit is contained in:
parent
3c5c14ff5a
commit
7fd3f77c3e
@ -111,7 +111,7 @@ class NetWorth implements NetWorthInterface
|
||||
|
||||
// if the account is a credit card, subtract the virtual balance from the balance,
|
||||
// to better reflect that this is not money that is actually "yours".
|
||||
$role = (string)$this->accountRepository->getMetaValue($account, 'accountRole');
|
||||
$role = (string)$this->accountRepository->getMetaValue($account, 'account_role');
|
||||
$virtualBalance = (string)$account->virtual_balance;
|
||||
if ('ccAsset' === $role && '' !== $virtualBalance && (float)$virtualBalance > 0) {
|
||||
$balance = bcsub($balance, $virtualBalance);
|
||||
|
@ -93,6 +93,37 @@ class AutoCompleteController extends Controller
|
||||
return response()->json($return);
|
||||
}
|
||||
|
||||
/**
|
||||
* An auto-complete specifically for revenue accounts, used when converting transactions mostly.
|
||||
* @param Request $request
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function revenueAccounts(Request $request): JsonResponse
|
||||
{
|
||||
$search = $request->get('search');
|
||||
/** @var AccountRepositoryInterface $repository */
|
||||
$repository = app(AccountRepositoryInterface::class);
|
||||
|
||||
// filter the account types:
|
||||
$allowedAccountTypes = [AccountType::REVENUE];
|
||||
Log::debug('Now in accounts(). Filtering results.', $allowedAccountTypes);
|
||||
|
||||
$return = [];
|
||||
$result = $repository->searchAccount((string)$search, $allowedAccountTypes);
|
||||
|
||||
/** @var Account $account */
|
||||
foreach ($result as $account) {
|
||||
$return[] = [
|
||||
'id' => $account->id,
|
||||
'name' => $account->name,
|
||||
'type' => $account->accountType->type,
|
||||
];
|
||||
}
|
||||
|
||||
return response()->json($return);
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches in the titles of all transaction journals.
|
||||
* The result is limited to the top 15 unique results.
|
||||
|
@ -22,17 +22,26 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Controllers\Transaction;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Events\UpdatedTransactionGroup;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\TransactionGroup;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
|
||||
use FireflyIII\Services\Internal\Update\JournalUpdateService;
|
||||
use FireflyIII\Support\Http\Controllers\ModelInformation;
|
||||
use FireflyIII\Transformers\TransactionGroupTransformer;
|
||||
use FireflyIII\Validation\AccountValidator;
|
||||
use Illuminate\Http\Request;
|
||||
use Log;
|
||||
use View;
|
||||
|
||||
|
||||
/**
|
||||
* Class ConvertController.
|
||||
*/
|
||||
@ -45,6 +54,7 @@ class ConvertController extends Controller
|
||||
|
||||
/**
|
||||
* ConvertController constructor.
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
@ -68,110 +78,271 @@ class ConvertController extends Controller
|
||||
* Show overview of a to be converted transaction.
|
||||
*
|
||||
* @param TransactionType $destinationType
|
||||
* @param TransactionJournal $journal
|
||||
* @param TransactionGroup $group
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function index(TransactionType $destinationType, TransactionJournal $journal)
|
||||
public function index(TransactionType $destinationType, TransactionGroup $group)
|
||||
{
|
||||
// @codeCoverageIgnoreStart
|
||||
if ($this->isOpeningBalance($journal)) {
|
||||
Log::debug('This is an opening balance.');
|
||||
/** @var TransactionGroupTransformer $transformer */
|
||||
$transformer = app(TransactionGroupTransformer::class);
|
||||
|
||||
return $this->redirectToAccount($journal);
|
||||
/** @var TransactionJournal $first */
|
||||
$first = $group->transactionJournals()->first();
|
||||
$sourceType = $first->transactionType;
|
||||
// return to account.
|
||||
if (!in_array($sourceType->type, [TransactionType::WITHDRAWAL, TransactionType::TRANSFER, TransactionType::DEPOSIT], true)) {
|
||||
return $this->redirectToAccount($first);
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
$positiveAmount = $this->repository->getJournalTotal($journal);
|
||||
$sourceType = $journal->transactionType;
|
||||
$subTitle = (string)trans('firefly.convert_to_' . $destinationType->type, ['description' => $journal->description]);
|
||||
|
||||
$groupTitle = $group->title ?? $first->description;
|
||||
$groupArray = $transformer->transformObject($group);
|
||||
$subTitle = (string)trans('firefly.convert_to_' . $destinationType->type, ['description' => $groupTitle]);
|
||||
$subTitleIcon = 'fa-exchange';
|
||||
|
||||
// get a list of asset accounts and liabilities and stuff, in various combinations:
|
||||
$validDepositSources = $this->getValidDepositSources();
|
||||
$validWithdrawalDests = $this->getValidWithdrawalDests();
|
||||
$liabilities = $this->getLiabilities();
|
||||
$assets = $this->getAssetAccounts();
|
||||
|
||||
// old input variables:
|
||||
$preFilled = [
|
||||
'source_name' => old('source_name'),
|
||||
];
|
||||
|
||||
if ($sourceType->type === $destinationType->type) { // cannot convert to its own type.
|
||||
Log::debug('This is already a transaction of the expected type..');
|
||||
session()->flash('info', (string)trans('firefly.convert_is_already_type_' . $destinationType->type));
|
||||
|
||||
return redirect(route('transactions.show', [$journal->id]));
|
||||
return redirect(route('transactions.show', [$group->id]));
|
||||
}
|
||||
|
||||
if ($journal->transactions()->count() > 2) { // cannot convert split.
|
||||
Log::info('This journal has more than two transactions.');
|
||||
session()->flash('error', (string)trans('firefly.cannot_convert_split_journal'));
|
||||
|
||||
return redirect(route('transactions.show', [$journal->id]));
|
||||
}
|
||||
|
||||
// get source and destination account:
|
||||
$sourceAccount = $this->repository->getJournalSourceAccounts($journal)->first();
|
||||
$destinationAccount = $this->repository->getJournalDestinationAccounts($journal)->first();
|
||||
|
||||
return view(
|
||||
'transactions.convert', compact(
|
||||
'sourceType', 'destinationType', 'journal', 'positiveAmount', 'sourceAccount', 'destinationAccount', 'sourceType',
|
||||
'sourceType', 'destinationType',
|
||||
'group', 'groupTitle', 'groupArray', 'assets', 'validDepositSources', 'liabilities',
|
||||
'validWithdrawalDests', 'preFilled',
|
||||
'subTitle', 'subTitleIcon'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Do the conversion.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param TransactionType $destinationType
|
||||
* @param TransactionJournal $journal
|
||||
* @param TransactionGroup $group
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
*
|
||||
* @throws FireflyException
|
||||
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
|
||||
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
|
||||
*/
|
||||
public function postIndex(Request $request, TransactionType $destinationType, TransactionJournal $journal)
|
||||
public function postIndex(Request $request, TransactionType $destinationType, TransactionGroup $group)
|
||||
{
|
||||
throw new FireflyException('Needs refactor');
|
||||
// @codeCoverageIgnoreStart
|
||||
if ($this->isOpeningBalance($journal)) {
|
||||
Log::debug('Journal is opening balance, return to account.');
|
||||
/** @var TransactionJournal $journal */
|
||||
foreach ($group->transactionJournals as $journal) {
|
||||
// catch FF exception.
|
||||
try {
|
||||
$this->convertJournal($journal, $destinationType, $request->all());
|
||||
} catch (FireflyException $e) {
|
||||
session()->flash('error', $e->getMessage());
|
||||
|
||||
return $this->redirectToAccount($journal);
|
||||
return redirect()->route('transactions.convert.index', [strtolower($destinationType->type), $group->id])->withInput();
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
$data = $request->all();
|
||||
|
||||
if ($journal->transactionType->type === $destinationType->type) {
|
||||
Log::info('Journal is already of the desired type.');
|
||||
session()->flash('error', (string)trans('firefly.convert_is_already_type_' . $destinationType->type));
|
||||
|
||||
return redirect(route('transactions.show', [$journal->id]));
|
||||
}
|
||||
|
||||
if ($journal->transactions()->count() > 2) {
|
||||
Log::info('Journal has more than two transactions.');
|
||||
session()->flash('error', (string)trans('firefly.cannot_convert_split_journal'));
|
||||
|
||||
return redirect(route('transactions.show', [$journal->id]));
|
||||
}
|
||||
|
||||
// get the new source and destination account:
|
||||
$source = $this->getSourceAccount($journal, $destinationType, $data);
|
||||
$destination = $this->getDestinationAccount($journal, $destinationType, $data);
|
||||
|
||||
// update the journal:
|
||||
$errors = $this->repository->convert($journal, $destinationType, $source, $destination);
|
||||
|
||||
if ($errors->count() > 0) {
|
||||
Log::error('Errors while converting: ', $errors->toArray());
|
||||
|
||||
return redirect(route('transactions.convert.index', [strtolower($destinationType->type), $journal->id]))->withErrors($errors)->withInput();
|
||||
}
|
||||
|
||||
// Success? Fire rules!
|
||||
session()->flash('success', (string)trans('firefly.converted_to_' . $destinationType->type));
|
||||
event(new UpdatedTransactionGroup($group));
|
||||
|
||||
return redirect(route('transactions.show', [$group->id]));
|
||||
}
|
||||
|
||||
session()->flash('success', (string)trans('firefly.converted_to_' . $destinationType->type));
|
||||
/**
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function getAssetAccounts(): array
|
||||
{
|
||||
// make repositories
|
||||
/** @var AccountRepositoryInterface $repository */
|
||||
$repository = app(AccountRepositoryInterface::class);
|
||||
$accountList = $repository->getActiveAccountsByType([AccountType::ASSET]);
|
||||
$defaultCurrency = app('amount')->getDefaultCurrency();
|
||||
$grouped = [];
|
||||
// group accounts:
|
||||
/** @var Account $account */
|
||||
foreach ($accountList as $account) {
|
||||
$balance = app('steam')->balance($account, new Carbon);
|
||||
$currency = $repository->getAccountCurrency($account) ?? $defaultCurrency;
|
||||
$role = (string)$repository->getMetaValue($account, 'account_role');
|
||||
if ('' === $role) {
|
||||
$role = 'no_account_type'; // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
return redirect(route('transactions.show', [$journal->id]));
|
||||
$key = (string)trans('firefly.opt_group_' . $role);
|
||||
$grouped[$key][$account->id] = $account->name . ' (' . app('amount')->formatAnything($currency, $balance, false) . ')';
|
||||
}
|
||||
|
||||
return $grouped;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function getLiabilities(): array
|
||||
{
|
||||
// make repositories
|
||||
/** @var AccountRepositoryInterface $repository */
|
||||
$repository = app(AccountRepositoryInterface::class);
|
||||
$accountList = $repository->getActiveAccountsByType([AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE]);
|
||||
$defaultCurrency = app('amount')->getDefaultCurrency();
|
||||
$grouped = [];
|
||||
// group accounts:
|
||||
/** @var Account $account */
|
||||
foreach ($accountList as $account) {
|
||||
$balance = app('steam')->balance($account, new Carbon);
|
||||
$currency = $repository->getAccountCurrency($account) ?? $defaultCurrency;
|
||||
$role = 'l_' . $account->accountType->type; // @codeCoverageIgnore
|
||||
$key = (string)trans('firefly.opt_group_' . $role);
|
||||
$grouped[$key][$account->id] = $account->name . ' (' . app('amount')->formatAnything($currency, $balance, false) . ')';
|
||||
}
|
||||
|
||||
return $grouped;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function getValidDepositSources(): array
|
||||
{
|
||||
// make repositories
|
||||
/** @var AccountRepositoryInterface $repository */
|
||||
$repository = app(AccountRepositoryInterface::class);
|
||||
$liabilityTypes = [AccountType::MORTGAGE, AccountType::DEBT, AccountType::CREDITCARD, AccountType::LOAN];
|
||||
$accountList = $repository
|
||||
->getActiveAccountsByType([AccountType::REVENUE, AccountType::CASH, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE]);
|
||||
$grouped = [];
|
||||
// group accounts:
|
||||
/** @var Account $account */
|
||||
foreach ($accountList as $account) {
|
||||
$role = (string)$repository->getMetaValue($account, 'account_role');
|
||||
$name = $account->name;
|
||||
if ('' === $role) {
|
||||
$role = 'no_account_type'; // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
// maybe it's a liability thing:
|
||||
if (in_array($account->accountType->type, $liabilityTypes, true)) {
|
||||
$role = 'l_' . $account->accountType->type; // @codeCoverageIgnore
|
||||
}
|
||||
if (AccountType::CASH === $account->accountType->type) {
|
||||
$role = 'cash_account';
|
||||
$name = sprintf('(%s)', trans('firefly.cash'));
|
||||
}
|
||||
if (AccountType::REVENUE === $account->accountType->type) {
|
||||
$role = 'revenue_account';
|
||||
}
|
||||
|
||||
$key = (string)trans('firefly.opt_group_' . $role);
|
||||
$grouped[$key][$account->id] = $name;
|
||||
}
|
||||
|
||||
return $grouped;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function getValidWithdrawalDests(): array
|
||||
{
|
||||
// make repositories
|
||||
/** @var AccountRepositoryInterface $repository */
|
||||
$repository = app(AccountRepositoryInterface::class);
|
||||
$liabilityTypes = [AccountType::MORTGAGE, AccountType::DEBT, AccountType::CREDITCARD, AccountType::LOAN];
|
||||
$accountList = $repository
|
||||
->getActiveAccountsByType([AccountType::EXPENSE, AccountType::CASH, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE]);
|
||||
$grouped = [];
|
||||
// group accounts:
|
||||
/** @var Account $account */
|
||||
foreach ($accountList as $account) {
|
||||
$role = (string)$repository->getMetaValue($account, 'account_role');
|
||||
$name = $account->name;
|
||||
if ('' === $role) {
|
||||
$role = 'no_account_type'; // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
// maybe it's a liability thing:
|
||||
if (in_array($account->accountType->type, $liabilityTypes, true)) {
|
||||
$role = 'l_' . $account->accountType->type; // @codeCoverageIgnore
|
||||
}
|
||||
if (AccountType::CASH === $account->accountType->type) {
|
||||
$role = 'cash_account';
|
||||
$name = sprintf('(%s)', trans('firefly.cash'));
|
||||
}
|
||||
if (AccountType::EXPENSE === $account->accountType->type) {
|
||||
$role = 'expense_account';
|
||||
}
|
||||
|
||||
$key = (string)trans('firefly.opt_group_' . $role);
|
||||
$grouped[$key][$account->id] = $name;
|
||||
}
|
||||
|
||||
return $grouped;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
* @param TransactionType $transactionType
|
||||
* @param array $data
|
||||
* @return TransactionJournal
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function convertJournal(TransactionJournal $journal, TransactionType $transactionType, array $data): TransactionJournal
|
||||
{
|
||||
$sourceType = $journal->transactionType->type;
|
||||
// make a switch based on original + dest type.
|
||||
/** @var AccountValidator $validator */
|
||||
$validator = app(AccountValidator::class);
|
||||
$validator->setUser(auth()->user());
|
||||
$validator->setTransactionType($transactionType->type);
|
||||
|
||||
$sourceId = $data['source_id'][$journal->id] ?? null;
|
||||
$sourceName = $data['source_name'][$journal->id] ?? null;
|
||||
$destinationId = $data['destination_id'][$journal->id] ?? null;
|
||||
$destinationName = $data['destination_name'][$journal->id] ?? null;
|
||||
|
||||
// double check its not an empty string.
|
||||
$sourceId = '' === $sourceId || null === $sourceId ? null : (int)$sourceId;
|
||||
$sourceName = '' === $sourceName ? null : $sourceName;
|
||||
$destinationId = '' === $destinationId || null === $destinationId ? null : (int)$destinationId;
|
||||
$destinationName = '' === $destinationName ? null : $destinationName;
|
||||
$validSource = $validator->validateSource($sourceId, $sourceName);
|
||||
$validDestination = $validator->validateDestination($destinationId, $destinationName);
|
||||
|
||||
if (false === $validSource) {
|
||||
throw new FireflyException(sprintf(trans('firefly.convert_invalid_source'), $journal->id));
|
||||
}
|
||||
if (false === $validDestination) {
|
||||
throw new FireflyException(sprintf(trans('firefly.convert_invalid_destination'), $journal->id));
|
||||
}
|
||||
|
||||
$update = [
|
||||
'source_id' => $sourceId,
|
||||
'source_name' => $sourceName,
|
||||
'destination_id' => $destinationId,
|
||||
'destination_name' => $destinationName,
|
||||
'type' => $transactionType->type,
|
||||
];
|
||||
/** @var JournalUpdateService $service */
|
||||
$service = app(JournalUpdateService::class);
|
||||
$service->setTransactionJournal($journal);
|
||||
$service->setData($update);
|
||||
$service->update();
|
||||
$journal->refresh();
|
||||
|
||||
return $journal;
|
||||
}
|
||||
}
|
||||
|
@ -78,7 +78,6 @@ class IndexController extends Controller
|
||||
$types = config('firefly.transactionTypesByType.' . $objectType);
|
||||
$page = (int)$request->get('page');
|
||||
$pageSize = (int)app('preferences')->get('listPageSize', 50)->data;
|
||||
$pageSize =3;
|
||||
if (null === $start) {
|
||||
$start = session('start');
|
||||
$end = session('end');
|
||||
|
@ -103,7 +103,7 @@ class AccountFormRequest extends Request
|
||||
'account_number' => 'between:1,255|uniqueAccountNumberForUser|nullable',
|
||||
'account_role' => 'in:' . $accountRoles,
|
||||
'active' => 'boolean',
|
||||
'ccType' => 'in:' . $ccPaymentTypes,
|
||||
'cc_type' => 'in:' . $ccPaymentTypes,
|
||||
'cc_monthly_payment_date' => 'date',
|
||||
'amount_currency_id_opening_balance' => 'exists:transaction_currencies,id',
|
||||
'amount_currency_id_virtual_balance' => 'exists:transaction_currencies,id',
|
||||
|
@ -268,7 +268,7 @@ class AccountRepository implements AccountRepositoryInterface
|
||||
/** @var Collection $result */
|
||||
$query = $this->user->accounts()->with(
|
||||
['accountmeta' => function (HasMany $query) {
|
||||
$query->where('name', 'accountRole');
|
||||
$query->where('name', 'account_role');
|
||||
}]
|
||||
);
|
||||
if (count($types) > 0) {
|
||||
|
@ -75,7 +75,7 @@ class ExpandedForm
|
||||
$balance = app('steam')->balance($account, new Carbon);
|
||||
$currencyId = (int)$repository->getMetaValue($account, 'currency_id');
|
||||
$currency = $currencyRepos->findNull($currencyId);
|
||||
$role = $repository->getMetaValue($account, 'accountRole');
|
||||
$role = $repository->getMetaValue($account, 'account_role');
|
||||
if ('' === $role) {
|
||||
$role = 'no_account_type'; // @codeCoverageIgnore
|
||||
}
|
||||
@ -321,7 +321,7 @@ class ExpandedForm
|
||||
// group accounts:
|
||||
/** @var Account $account */
|
||||
foreach ($assetAccounts as $account) {
|
||||
$role = $repository->getMetaValue($account, 'accountRole');
|
||||
$role = $repository->getMetaValue($account, 'account_role');
|
||||
if (null === $role) {
|
||||
$role = 'no_account_type'; // @codeCoverageIgnore
|
||||
}
|
||||
@ -364,7 +364,7 @@ class ExpandedForm
|
||||
$balance = app('steam')->balance($account, new Carbon);
|
||||
$currencyId = (int)$repository->getMetaValue($account, 'currency_id');
|
||||
$currency = $currencyRepos->findNull($currencyId);
|
||||
$role = (string)$repository->getMetaValue($account, 'accountRole');
|
||||
$role = (string)$repository->getMetaValue($account, 'account_role');
|
||||
if ('' === $role) {
|
||||
$role = 'no_account_type'; // @codeCoverageIgnore
|
||||
}
|
||||
@ -618,7 +618,7 @@ class ExpandedForm
|
||||
$balance = app('steam')->balance($account, new Carbon);
|
||||
$currencyId = (int)$repository->getMetaValue($account, 'currency_id');
|
||||
$currency = $currencyRepos->findNull($currencyId);
|
||||
$role = (string)$repository->getMetaValue($account, 'accountRole');
|
||||
$role = (string)$repository->getMetaValue($account, 'account_role'); // TODO bad form for currency
|
||||
if ('' === $role) {
|
||||
$role = 'no_account_type'; // @codeCoverageIgnore
|
||||
}
|
||||
|
@ -137,7 +137,7 @@ class AccountTransformer extends AbstractTransformer
|
||||
*/
|
||||
private function getAccountRole(Account $account, string $accountType): ?string
|
||||
{
|
||||
$accountRole = $this->repository->getMetaValue($account, 'accountRole');
|
||||
$accountRole = $this->repository->getMetaValue($account, 'account_role');
|
||||
if ('asset' !== $accountType || '' === (string)$accountRole) {
|
||||
$accountRole = null;
|
||||
}
|
||||
@ -157,8 +157,8 @@ class AccountTransformer extends AbstractTransformer
|
||||
$monthlyPaymentDate = null;
|
||||
$creditCardType = null;
|
||||
if ('ccAsset' === $accountRole && 'asset' === $accountType) {
|
||||
$creditCardType = $this->repository->getMetaValue($account, 'ccType');
|
||||
$monthlyPaymentDate = $this->repository->getMetaValue($account, 'ccMonthlyPaymentDate');
|
||||
$creditCardType = $this->repository->getMetaValue($account, 'cc_type');
|
||||
$monthlyPaymentDate = $this->repository->getMetaValue($account, 'cc_monthly_payment_date');
|
||||
}
|
||||
|
||||
return [$creditCardType, $monthlyPaymentDate];
|
||||
|
@ -127,9 +127,6 @@ class AccountValidator
|
||||
case TransactionType::RECONCILIATION:
|
||||
$result = $this->validateReconciliationDestination($destinationId);
|
||||
break;
|
||||
//case TransactionType::OPENING_BALANCE:
|
||||
//case TransactionType::RECONCILIATION:
|
||||
// die(sprintf('Cannot handle type "%s"', $this->transactionType));
|
||||
}
|
||||
|
||||
return $result;
|
||||
@ -479,8 +476,8 @@ class AccountValidator
|
||||
return false;
|
||||
}
|
||||
$this->destination = $search;
|
||||
|
||||
return true;
|
||||
// must not be the same as the source account
|
||||
return !(null !== $this->source && $this->source->id === $this->destination->id);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -22,6 +22,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Validation;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Config;
|
||||
use DB;
|
||||
use FireflyIII\Models\Account;
|
||||
|
32
public/v1/js/ff/transactions/convert.js
vendored
32
public/v1/js/ff/transactions/convert.js
vendored
@ -28,7 +28,35 @@ $(document).ready(function () {
|
||||
* Set the auto-complete JSON things.
|
||||
*/
|
||||
function setAutocompletes() {
|
||||
initRevenueACField('source_account_revenue');
|
||||
initExpenseACField('destination_account_expense');
|
||||
//initRevenueACField('source_account_revenue');
|
||||
//initExpenseACField('destination_account_expense');
|
||||
|
||||
makeRevenueAC();
|
||||
}
|
||||
|
||||
function makeRevenueAC() {
|
||||
var sourceNames = new Bloodhound({
|
||||
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('name'),
|
||||
queryTokenizer: Bloodhound.tokenizers.whitespace,
|
||||
prefetch: {
|
||||
url: 'json/revenue-accounts?uid=' + uid,
|
||||
filter: function (list) {
|
||||
return $.map(list, function (object) {
|
||||
return {name: object.name};
|
||||
});
|
||||
}
|
||||
},
|
||||
remote: {
|
||||
url: 'json/revenue-accounts?search=%QUERY&uid=' + uid,
|
||||
wildcard: '%QUERY',
|
||||
filter: function (list) {
|
||||
return $.map(list, function (object) {
|
||||
return {name: object.name};
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
sourceNames.initialize();
|
||||
$('.input-revenue').typeahead({hint: true, highlight: true,}, {source: sourceNames, displayKey: 'name', autoSelect: false});
|
||||
}
|
||||
|
||||
|
@ -864,6 +864,7 @@ return [
|
||||
'opt_group_expense_account' => 'Expense accounts',
|
||||
'opt_group_revenue_account' => 'Revenue accounts',
|
||||
'opt_group_l_Loan' => 'Liability: Loan',
|
||||
'opt_group_cash_account' => 'Cash account',
|
||||
'opt_group_l_Debt' => 'Liability: Debt',
|
||||
'opt_group_l_Mortgage' => 'Liability: Mortgage',
|
||||
'opt_group_l_Credit card' => 'Liability: Credit card',
|
||||
@ -1258,12 +1259,11 @@ return [
|
||||
'split_this_withdrawal' => 'Split this withdrawal',
|
||||
'split_this_deposit' => 'Split this deposit',
|
||||
'split_this_transfer' => 'Split this transfer',
|
||||
'cannot_edit_multiple_source' => 'You cannot edit splitted transaction #:id with description ":description" because it contains multiple source accounts.',
|
||||
'cannot_edit_multiple_dest' => 'You cannot edit splitted transaction #:id with description ":description" because it contains multiple destination accounts.',
|
||||
'cannot_edit_reconciled' => 'You cannot edit transaction #:id with description ":description" because it has been marked as reconciled.',
|
||||
'cannot_edit_opening_balance' => 'You cannot edit the opening balance of an account.',
|
||||
'no_edit_multiple_left' => 'You have selected no valid transactions to edit.',
|
||||
'cannot_convert_split_journal' => 'Cannot convert a split transaction',
|
||||
'breadcrumb_convert_group' => 'Convert transaction',
|
||||
'convert_invalid_source' => 'Source information is invalid for transaction #%d.',
|
||||
'convert_invalid_destination' => 'Destination information is invalid for transaction #%d.',
|
||||
|
||||
// Import page (general strings only)
|
||||
'import_index_title' => 'Import transactions into Firefly III',
|
||||
|
@ -1,29 +1,292 @@
|
||||
{% extends "./layout/default" %}
|
||||
|
||||
{% block breadcrumbs %}
|
||||
{{ Breadcrumbs.render(Route.getCurrentRoute.getName, destinationType, journal) }}
|
||||
{{ Breadcrumbs.render(Route.getCurrentRoute.getName, group, groupTitle) }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<form method="POST" action="{{ route('transactions.convert.index.post', [destinationType.type|lower, journal.id]) }}" accept-charset="UTF-8"
|
||||
class="form-horizontal" id="store"
|
||||
<form method="POST" action="{{ route('transactions.convert.index.post', [destinationType.type|lower, group.id]) }}" accept-charset="UTF-8"
|
||||
class="form-horizontal"
|
||||
enctype="multipart/form-data">
|
||||
<input name="_token" type="hidden" value="{{ csrf_token() }}">
|
||||
<div class="row">
|
||||
<div class="col-lg-10 col-md-12 col-sm-12">
|
||||
<div class="col-lg-8 col-lg-offset-2 col-md-12 col-sm-12">
|
||||
<div class="box box-primary">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">{{ ('convert_options_'~sourceType.type~destinationType.type)|_ }}</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
{{ ExpandedForm.staticText('type', sourceType.type|_) }}
|
||||
{{ ExpandedForm.staticText('description', '<a href="'~route('transactions.show', journal.id)~'">'~journal.description~'</a>') }}
|
||||
{{ ExpandedForm.staticText('date', journal.date.formatLocalized(monthAndDayFormat)) }}
|
||||
|
||||
<p>
|
||||
{# ONE: WITHDRAWAL TO DEPOSIT #}
|
||||
{% if sourceType.type == 'Withdrawal' and destinationType.type == 'Deposit' %}
|
||||
When converting from a withdrawal to a deposit, the money will be deposited into the
|
||||
displayed destination account(s), instead of being withdrawn from them. To complete the conversion,
|
||||
please set the new source account(s) below.
|
||||
{% endif %}
|
||||
|
||||
{# TWO: WITHDRAWAL TO TRANSFER #}
|
||||
{% if sourceType.type == 'Withdrawal' and destinationType.type == 'Transfer' %}
|
||||
When converting a withdrawal into a transfer, the money will be transferred away from the source
|
||||
account(s) into other asset or liability account(s) instead of being spent on the original
|
||||
expense accounts. To complete the conversion, please select new destination account(s).
|
||||
{% endif %}
|
||||
|
||||
{# THREE: DEPOSIT TO WITHDRAWAL #}
|
||||
{% if sourceType.type == 'Deposit' and destinationType.type == 'Withdrawal' %}
|
||||
When converting a deposit into a withdrawal, the money will be withdrawn from the
|
||||
displayed source account(s), instead of being deposited into them. To complete the conversion,
|
||||
please select new destination accounts.
|
||||
{% endif %}
|
||||
|
||||
{# FOUR: DEPOSIT TO TRANSFER#}
|
||||
{% if sourceType.type == 'Deposit' and destinationType.type == 'Transfer' %}
|
||||
When you convert a deposit into a transfer, the money will be deposited into
|
||||
the listed destination account(s) from any of your asset or liability account(s).
|
||||
Please select the new source account(s) to complete the conversion.
|
||||
{% endif %}
|
||||
|
||||
{# FIVE: TRANSFER TO WITHDRAWAL #}
|
||||
{% if sourceType.type == 'Transfer' and destinationType.type == 'Withdrawal' %}
|
||||
When you convert a transfer into a withdrawal, the money will be spent
|
||||
on the destination account(s) you set here, instead of being transferred away.
|
||||
Please select the new destination account(s) to complete the conversion.
|
||||
{% endif %}
|
||||
|
||||
{# SIX: TRANSFER TO DEPOSIT #}
|
||||
{% if sourceType.type == 'Transfer' and destinationType.type == 'Deposit' %}
|
||||
When you convert a transfer into a deposit, the money will be deposited
|
||||
into the destination account(s) you see here, instead of being transferred into them.
|
||||
Please select the new source account(s) to complete the conversion.
|
||||
{% endif %}
|
||||
</p>
|
||||
|
||||
<table class="table">
|
||||
<tr>
|
||||
<th style="width:10%;">ID</th>
|
||||
<th style="width:25%;">Description</th>
|
||||
<th style="width:25%;">Source account</th>
|
||||
<th style="width:25%;">Destination account</th>
|
||||
<th style="width:15%;">Amount</th>
|
||||
</tr>
|
||||
{% for transaction in groupArray.transactions %}
|
||||
<tr>
|
||||
<td>#{{ transaction.transaction_journal_id }}</td>
|
||||
<td>{{ transaction.description }}</td>
|
||||
<td>
|
||||
{# ONE: WITHDRAWAL TO DEPOSIT #}
|
||||
{% if sourceType.type == 'Withdrawal' and destinationType.type == 'Deposit' %}
|
||||
{# NEW DESTINATION = Asset, SOURCE MUST BE [Revenue, Cash, Loan, Debt, Mortgage] #}
|
||||
{% if
|
||||
transaction.source_type == 'Asset account' %}
|
||||
{{ Form.select('source_id['~transaction.transaction_journal_id~']', validDepositSources, null, {class: 'form-control'}) }}
|
||||
{% endif %}
|
||||
{# NEW DESTINATION = [Loan, Debt, Mortgage], SOURCE MUST BE [Revenue] #}
|
||||
{% if
|
||||
transaction.source_type == 'Loan' or
|
||||
transaction.source_type == 'Debt' or
|
||||
transaction.source_type == 'Mortgage' %}
|
||||
<input
|
||||
autocomplete="off"
|
||||
placeholder="Source account"
|
||||
name="source_name[{{ transaction.transaction_journal_id }}]"
|
||||
type="text"
|
||||
value="{% if transaction.destination_type != "Cash account" %}{{ preFilled.source_name[transaction.transaction_journal_id]|default(transaction.destination_name) }}{% endif %}"
|
||||
class="form-control tt-input input-revenue"
|
||||
spellcheck="false" dir="auto">
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{# TWO: WITHDRAWAL TO TRANSFER #}
|
||||
{% if sourceType.type == 'Withdrawal' and destinationType.type == 'Transfer' %}
|
||||
<a href="{{ route('accounts.show', [transaction.source_id]) }}"
|
||||
title="{{ transaction.source_iban|default(transaction.source_name) }}">{{ transaction.source_name }}</a>
|
||||
{# hide source in hidden input #}
|
||||
<input type="hidden" name="source_id[{{ transaction.transaction_journal_id }}]" value="{{ transaction.source_id }}">
|
||||
{% endif %}
|
||||
|
||||
{# THREE: DEPOSIT TO WITHDRAWAL #}
|
||||
{% if sourceType.type == 'Deposit' and destinationType.type == 'Withdrawal' %}
|
||||
<a href="{{ route('accounts.show', [transaction.destination_id]) }}"
|
||||
title="{{ transaction.destination_iban|default(transaction.destination_name) }}">{{ transaction.destination_name }}</a>
|
||||
|
||||
{# hide new source in hidden input #}
|
||||
<input type="hidden" name="source_id[{{ transaction.transaction_journal_id }}]" value="{{ transaction.destination_id }}">
|
||||
{% endif %}
|
||||
|
||||
{# FOUR: DEPOSIT TO TRANSFER#}
|
||||
{% if sourceType.type == 'Deposit' and destinationType.type == 'Transfer' %}
|
||||
{# if new destination is asset, then asset#}
|
||||
{% if transaction.destination_type == 'Asset account' %}
|
||||
{{ Form.select('source_id['~transaction.transaction_journal_id~']', assets,null, {class: 'form-control'}) }}
|
||||
{% endif %}
|
||||
{% if transaction.destination_type == 'Loan' or
|
||||
transaction.destination_type == 'Debt' or
|
||||
transaction.destination_type == 'Mortgage' %}
|
||||
{{ Form.select('source_id['~transaction.transaction_journal_id~']', liabilities,null, {class: 'form-control'}) }}
|
||||
{% endif %}
|
||||
|
||||
{# if new destination liability, then liability.#}
|
||||
|
||||
{% endif %}
|
||||
|
||||
{# FIVE: TRANSFER TO WITHDRAWAL #}
|
||||
{% if sourceType.type == 'Transfer' and destinationType.type == 'Withdrawal' %}
|
||||
<a href="{{ route('accounts.show', [transaction.source_id]) }}"
|
||||
title="{{ transaction.source_iban|default(transaction.source_name) }}">{{ transaction.source_name }}</a>
|
||||
|
||||
{# hide source in hidden input #}
|
||||
<input type="hidden" name="source_id[{{ transaction.transaction_journal_id }}]" value="{{ transaction.source_id }}">
|
||||
{% endif %}
|
||||
|
||||
{# SIX: TRANSFER TO DEPOSIT #}
|
||||
{% if sourceType.type == 'Transfer' and destinationType.type == 'Deposit' %}
|
||||
{# NEW DESTINATION = Asset, SOURCE MUST BE [Revenue, Cash, Loan, Debt, Mortgage] #}
|
||||
{% if
|
||||
transaction.source_type == 'Asset account' %}
|
||||
{{ Form.select('source_id['~transaction.transaction_journal_id~']', validDepositSources, null, {class: 'form-control'}) }}
|
||||
{% endif %}
|
||||
{# NEW DESTINATION = [Debt, Mortgage, Load], SOURCE MUST BE [Revenue] #}
|
||||
{% if
|
||||
transaction.source_type == 'Loan' or
|
||||
transaction.source_type == 'Debt' or
|
||||
transaction.source_type == 'Mortgage' %}
|
||||
<input
|
||||
autocomplete="off"
|
||||
placeholder="Source account"
|
||||
name="source_name[{{ transaction.transaction_journal_id }}]"
|
||||
type="text"
|
||||
value="{% if transaction.destination_type != "Cash account" %}{{ transaction.source_name }}{% endif %}"
|
||||
class="form-control tt-input"
|
||||
spellcheck="false" dir="auto">
|
||||
{% endif %}
|
||||
|
||||
{% endif %}
|
||||
|
||||
</td>
|
||||
<td>
|
||||
{# ONE: WITHDRAWAL TO DEPOSIT #}
|
||||
{% if sourceType.type == 'Withdrawal' and destinationType.type == 'Deposit' %}
|
||||
<a href="{{ route('accounts.show', [transaction.source_id]) }}"
|
||||
title="{{ transaction.source_iban|default(transaction.source_name) }}">{{ transaction.source_name }}</a>
|
||||
|
||||
{# hide destination in hidden input #}
|
||||
<input type="hidden" name="destination_id[{{ transaction.transaction_journal_id }}]" value="{{ transaction.source_id }}">
|
||||
{% endif %}
|
||||
|
||||
{# TWO: WITHDRAWAL TO TRANSFER #}
|
||||
{% if sourceType.type == 'Withdrawal' and destinationType.type == 'Transfer' %}
|
||||
{#if the source is a liability, destination must also be a liability.#}
|
||||
{% if
|
||||
transaction.source_type == 'Loan' or
|
||||
transaction.source_type == 'Debt' or
|
||||
transaction.source_type == 'Mortgage' %}
|
||||
{{ Form.select('destination_id['~transaction.transaction_journal_id~']', liabilities, null, {class: 'form-control'}) }}
|
||||
{% endif %}
|
||||
|
||||
{# if the source is an asset, destination can only be an asset. #}
|
||||
{% if transaction.source_type == 'Asset account' %}
|
||||
{{ Form.select('destination_id['~transaction.transaction_journal_id~']', assets, null, {class: 'form-control'}) }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{# THREE: DEPOSIT TO WITHDRAWAL #}
|
||||
{% if sourceType.type == 'Deposit' and destinationType.type == 'Withdrawal' %}
|
||||
|
||||
{# if new source is Asset, destination must be [Expense, Loan, Debt or Mortgage] #}
|
||||
{% if transaction.destination_type == 'Asset account' %}
|
||||
{{ Form.select('destination_id['~transaction.transaction_journal_id~']', validWithdrawalDests, null, {class: 'form-control'}) }}
|
||||
{% endif %}
|
||||
{% if transaction.destination_type == 'Loan' or
|
||||
transaction.destination_type == 'Debt' or
|
||||
transaction.destination_type == 'Mortgage' %}
|
||||
{# if new source is Liability, destination must be expense account. #}
|
||||
{# hier ben je. #}
|
||||
|
||||
<input
|
||||
autocomplete="off"
|
||||
placeholder="Destination account"
|
||||
name="destination_name[{{ transaction.transaction_journal_id }}]"
|
||||
type="text"
|
||||
value="{% if transaction.source_type != "Cash account" %}{{ transaction.source_name }}{% endif %}"
|
||||
class="form-control tt-input"
|
||||
spellcheck="false" dir="auto">
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{# FOUR: DEPOSIT TO TRANSFER#}
|
||||
{% if sourceType.type == 'Deposit' and destinationType.type == 'Transfer' %}
|
||||
<a href="{{ route('accounts.show', [transaction.destination_id]) }}"
|
||||
title="{{ transaction.destination_iban|default(transaction.destination_name) }}">{{ transaction.destination_name }}</a>
|
||||
|
||||
{# hide destination in hidden input #}
|
||||
<input type="hidden" name="destination_id[{{ transaction.transaction_journal_id }}]" value="{{ transaction.destination_id }}">
|
||||
{% endif %}
|
||||
|
||||
{# FIVE: TRANSFER TO WITHDRAWAL #}
|
||||
{% if sourceType.type == 'Transfer' and destinationType.type == 'Withdrawal' %}
|
||||
|
||||
{% if transaction.source_type == 'Asset account' %}
|
||||
{{ Form.select('destination_id['~transaction.transaction_journal_id~']', validWithdrawalDests, null, {class: 'form-control'}) }}
|
||||
{% endif %}
|
||||
{% if transaction.source_type == 'Loan' or
|
||||
transaction.source_type == 'Debt' or
|
||||
transaction.source_type == 'Mortgage' %}
|
||||
<input
|
||||
autocomplete="off"
|
||||
placeholder="Destination account"
|
||||
name="destination_name[{{ transaction.transaction_journal_id }}]"
|
||||
type="text"
|
||||
value="{% if transaction.source_type != "Cash account" %}{{ transaction.destination_name }}{% endif %}"
|
||||
class="form-control tt-input"
|
||||
spellcheck="false" dir="auto">
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{# SIX: TRANSFER TO DEPOSIT #}
|
||||
{% if sourceType.type == 'Transfer' and destinationType.type == 'Deposit' %}
|
||||
<a href="{{ route('accounts.show', [transaction.destination_id]) }}"
|
||||
title="{{ transaction.destination_iban|default(transaction.destination_name) }}">{{ transaction.destination_name }}</a>
|
||||
|
||||
{# hide destination in hidden input #}
|
||||
<input type="hidden" name="destination_id[{{ transaction.transaction_journal_id }}]" value="{{ transaction.destination_id }}">
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if transaction.transaction_type_type == 'Deposit' %}
|
||||
{{ formatAmountBySymbol(transaction.amount*-1, transaction.currency_symbol, transaction.currency_symbol_decimal_places) }}
|
||||
{% if null != transaction.foreign_amount %}
|
||||
({{ formatAmountBySymbol(transaction.foreign_amount*-1, transaction.foreign_currency_symbol, transaction.foreign_currency_symbol_decimal_places) }})
|
||||
{% endif %}
|
||||
{% elseif transaction.transaction_type_type == 'Transfer' %}
|
||||
<span class="text-info">{{ formatAmountBySymbol(transaction.amount*-1, transaction.currency_symbol, transaction.currency_symbol_decimal_places, false) }}
|
||||
{% if null != transaction.foreign_amount %}
|
||||
({{ formatAmountBySymbol(transaction.foreign_amount*-1, transaction.foreign_currency_symbol, transaction.foreign_currency_symbol_decimal_places, false) }})
|
||||
{% endif %}</span>
|
||||
{% else %}
|
||||
{{ formatAmountBySymbol(transaction.amount, transaction.currency_symbol, transaction.currency_symbol_decimal_places) }}
|
||||
{% if null != transaction.foreign_amount %}
|
||||
({{ formatAmountBySymbol(transaction.foreign_amount, transaction.foreign_currency_symbol, transaction.foreign_currency_symbol_decimal_places) }})
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
|
||||
{#
|
||||
|
||||
|
||||
{{ ExpandedForm.staticText('description', '<a href="'~route('transactions.show', group.id)~'">'~journal.description~'</a>') }}
|
||||
{{ ExpandedForm.staticText('type', sourceType.type|_) }}
|
||||
{{ ExpandedForm.staticText('date', journal.date.formatLocalized(monthAndDayFormat)) }}
|
||||
#}
|
||||
{# in case of withdrawal #}
|
||||
{% if sourceType.type == "Withdrawal" %}
|
||||
{#
|
||||
{% if journalType.type == "Withdrawal" %}
|
||||
{{ ExpandedForm.staticText('source_account_asset', '<a href="'~route('accounts.show',[sourceAccount.id])~'">'~sourceAccount.name~'</a>') }}
|
||||
{# if destination is cash, show (cash) #}
|
||||
<!-- if destination is cash, show (cash) -->
|
||||
{% if destinationAccount.accountType.type == "Cash account" %}
|
||||
{{ ExpandedForm.staticText('destination_account_expense', '<span class="text-success">(cash)</a>') }}
|
||||
{% else %}
|
||||
@ -31,10 +294,11 @@
|
||||
{% endif %}
|
||||
|
||||
{% endif %}
|
||||
|
||||
#}
|
||||
{# in case of deposit #}
|
||||
{% if sourceType.type == "Deposit" %}
|
||||
{# if source is cash, show (cash) #}
|
||||
{#
|
||||
{% if journalType.type == "Deposit" %}
|
||||
<!-- if source is cash, show (cash) -->
|
||||
{% if sourceAccount.accountType.type == "Cash account" %}
|
||||
{{ ExpandedForm.staticText('source_account_revenue', '<span class="text-success">(cash)</a>') }}
|
||||
{% else %}
|
||||
@ -42,15 +306,22 @@
|
||||
{% endif %}
|
||||
{{ ExpandedForm.staticText('destination_account_asset', '<a href="'~route('accounts.show',[destinationAccount.id])~'">'~destinationAccount.name~'</a>') }}
|
||||
{% endif %}
|
||||
#}
|
||||
|
||||
{# in case of transfer #}
|
||||
{% if sourceType.type == "Transfer" %}
|
||||
{#
|
||||
{% if journalType.type == "Transfer" %}
|
||||
{{ ExpandedForm.staticText('source_account_asset', '<a href="'~route('accounts.show',[sourceAccount.id])~'">'~sourceAccount.name~'</a>') }}
|
||||
{{ ExpandedForm.staticText('destination_account_asset', '<a href="'~route('accounts.show',[destinationAccount.id])~'">'~destinationAccount.name~'</a>') }}
|
||||
{% endif %}
|
||||
#}
|
||||
|
||||
{# ONE #}
|
||||
{% if sourceType.type == 'Withdrawal' and destinationType.type == 'Deposit' %}
|
||||
{#
|
||||
{% if journalType.type == 'Withdrawal' and destinationType.type == 'Deposit' %}
|
||||
ONE
|
||||
{% endif %}
|
||||
{% if journalType.type == 'Withdrawal' and destinationType.type == 'Deposit' %}
|
||||
<p><em>
|
||||
{{ trans('firefly.convert_explanation_withdrawal_deposit',
|
||||
{
|
||||
@ -72,9 +343,10 @@
|
||||
{{ ExpandedForm.text('source_account_revenue', destinationAccount.name) }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
#}
|
||||
{# TWO #}
|
||||
{% if sourceType.type == 'Withdrawal' and destinationType.type == 'Transfer' %}
|
||||
{#
|
||||
{% if journalType.type == 'Withdrawal' and destinationType.type == 'Transfer' %}
|
||||
<p><em>
|
||||
{{ trans('firefly.convert_explanation_withdrawal_transfer',
|
||||
{
|
||||
@ -95,9 +367,10 @@
|
||||
{{ ExpandedForm.activeLongAccountList('destination_account_asset', null) }}
|
||||
|
||||
{% endif %}
|
||||
|
||||
#}
|
||||
{# THREE #}
|
||||
{% if sourceType.type == 'Deposit' and destinationType.type == 'Withdrawal' %}
|
||||
{#
|
||||
{% if journalType.type == 'Deposit' and destinationType.type == 'Withdrawal' %}
|
||||
<p>
|
||||
<em>
|
||||
{{ trans('firefly.convert_explanation_deposit_withdrawal',
|
||||
@ -123,9 +396,10 @@
|
||||
{% endif %}
|
||||
|
||||
{% endif %}
|
||||
|
||||
#}
|
||||
{# FOUR #}
|
||||
{% if sourceType.type == 'Deposit' and destinationType.type == 'Transfer' %}
|
||||
{#
|
||||
{% if journalType.type == 'Deposit' and destinationType.type == 'Transfer' %}
|
||||
|
||||
<p>
|
||||
<em>
|
||||
@ -147,9 +421,10 @@
|
||||
</p>
|
||||
{{ ExpandedForm.activeLongAccountList('source_account_asset', null) }}
|
||||
{% endif %}
|
||||
|
||||
#}
|
||||
{# FIVE #}
|
||||
{% if sourceType.type == 'Transfer' and destinationType.type == 'Withdrawal' %}
|
||||
{#
|
||||
{% if journalType.type == 'Transfer' and destinationType.type == 'Withdrawal' %}
|
||||
|
||||
<p>
|
||||
<em>
|
||||
@ -173,9 +448,10 @@
|
||||
{{ ExpandedForm.text('destination_account_expense', destinationAccount.name) }}
|
||||
|
||||
{% endif %}
|
||||
|
||||
#}
|
||||
{# SIX #}
|
||||
{% if sourceType.type == 'Transfer' and destinationType.type == 'Deposit' %}
|
||||
{#
|
||||
{% if journalType.type == 'Transfer' and destinationType.type == 'Deposit' %}
|
||||
|
||||
|
||||
<p>
|
||||
@ -200,10 +476,10 @@
|
||||
{{ ExpandedForm.text('source_account_revenue', sourceAccount.name) }}
|
||||
|
||||
{% endif %}
|
||||
|
||||
#}
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
<a href="{{ route('transactions.show', journal.id) }}" class="btn btn-danger">{{ 'cancel'|_ }}</a>
|
||||
<a href="{{ route('transactions.show', group.id) }}" class="btn btn-danger">{{ 'cancel'|_ }}</a>
|
||||
<button type="submit" id="transaction-btn" class="btn btn-success pull-right">
|
||||
{{ trans('form.convert_'~sourceType.type) }}
|
||||
</button>
|
||||
@ -216,6 +492,5 @@
|
||||
{% endblock %}
|
||||
{% block scripts %}
|
||||
<script type="text/javascript" src="v1/js/lib/typeahead/typeahead.bundle.min.js?v={{ FF_VERSION }}"></script>
|
||||
<script type="text/javascript" src="v1/js/ff/common/autocomplete.js?v={{ FF_VERSION }}"></script>
|
||||
<script type="text/javascript" src="v1/js/ff/transactions/convert.js?v={{ FF_VERSION }}"></script>
|
||||
{% endblock %}
|
||||
|
@ -70,20 +70,23 @@
|
||||
<div class="box-footer">
|
||||
<div class="btn-group btn-group-xs">
|
||||
<a href="{{ route('transactions.edit', [transactionGroup.id]) }}" class="btn btn-default"><i class="fa fa-pencil"></i> {{ 'edit'|_ }}</a>
|
||||
{% if type != 'Opening balance' and type != 'Reconciliation' %}
|
||||
{#<a href="{{ route('transactions.clone', [transactionGroup.id]) }}" class="btn btn-default"><i class="fa fa-copy"></i> {{ 'clone'|_ }}</a>#}
|
||||
|
||||
{#
|
||||
{% if type != 'Withdrawal' %}
|
||||
<a href="{{ route('transactions.convert', [transactionGroup.id, 'withdrawal']) }}" class="btn btn-default"><i class="fa fa-exchange"></i> {{ 'convert_to_withdrawal'|_ }}</a>
|
||||
{% if groupArray.transactions[0].type != 'withdrawal' %}
|
||||
<a href="{{ route('transactions.convert.index', ['withdrawal', transactionGroup.id]) }}" class="btn btn-default"><i class="fa fa-exchange"></i> {{ 'convert_to_withdrawal'|_ }}</a>
|
||||
{% endif %}
|
||||
{% if type != 'Deposit' %}
|
||||
<a href="{{ route('transactions.convert', [transactionGroup.id, 'deposit']) }}" class="btn btn-default"><i class="fa fa-exchange"></i> {{ 'convert_to_deposit'|_ }}</a>
|
||||
|
||||
{% if groupArray.transactions[0].type != 'deposit' %}
|
||||
<a href="{{ route('transactions.convert.index', ['deposit', transactionGroup.id]) }}" class="btn btn-default"><i class="fa fa-exchange"></i> {{ 'convert_to_deposit'|_ }}</a>
|
||||
{% endif %}
|
||||
{% if type != 'Transfer' %}
|
||||
<a href="{{ route('transactions.convert', [transactionGroup.id, 'transfer']) }}" class="btn btn-default"><i class="fa fa-exchange"></i> {{ 'convert_to_transfer'|_ }}</a>
|
||||
|
||||
{% if groupArray.transactions[0].type != 'transfer' %}
|
||||
<a href="{{ route('transactions.convert.index', ['transfer', transactionGroup.id]) }}" class="btn btn-default"><i class="fa fa-exchange"></i> {{ 'convert_to_transfer'|_ }}</a>
|
||||
{% endif %}
|
||||
#}
|
||||
|
||||
|
||||
{% if groupArray.transactions[0].type != 'opening balance' and groupArray.transactions[0].type != 'reconciliation' %}
|
||||
CLONE
|
||||
{#<a href="{{ route('transactions.clone', [transactionGroup.id]) }}" class="btn btn-default"><i class="fa fa-copy"></i> {{ 'clone'|_ }}</a>#}
|
||||
{% endif %}
|
||||
{#
|
||||
<a href="{{ route('transactions.delete', [transactionGroup.id]) }}" class="btn btn-danger"><i class="fa fa-trash"></i> {{ 'delete'|_ }}</a>
|
||||
|
@ -1084,11 +1084,11 @@ try {
|
||||
|
||||
Breadcrumbs::register(
|
||||
'transactions.convert.index',
|
||||
function (BreadcrumbsGenerator $breadcrumbs, TransactionType $destinationType, TransactionJournal $journal) {
|
||||
$breadcrumbs->parent('transactions.show', $journal);
|
||||
function (BreadcrumbsGenerator $breadcrumbs, TransactionGroup $group, string $groupTitle) {
|
||||
$breadcrumbs->parent('transactions.show', $group);
|
||||
$breadcrumbs->push(
|
||||
trans('firefly.convert_to_' . $destinationType->type, ['description' => limitStringLength($journal->description)]),
|
||||
route('transactions.convert.index', [strtolower($destinationType->type), $journal->id])
|
||||
trans('firefly.breadcrumb_convert_group', ['description' => limitStringLength($groupTitle)]),
|
||||
route('transactions.convert.index', [$group->id, 'something'])
|
||||
);
|
||||
}
|
||||
);
|
||||
|
@ -532,6 +532,8 @@ Route::group(
|
||||
|
||||
// for auto complete
|
||||
Route::get('accounts', ['uses' => 'Json\AutoCompleteController@accounts', 'as' => 'autocomplete.accounts']);
|
||||
Route::get('revenue-accounts', ['uses' => 'Json\AutoCompleteController@revenueAccounts', 'as' => 'autocomplete.revenue-accounts']);
|
||||
Route::get('expense-accounts', ['uses' => 'Json\AutoCompleteController@expenseAccounts', 'as' => 'autocomplete.expense-accounts']);
|
||||
Route::get('budgets', ['uses' => 'Json\AutoCompleteController@budgets', 'as' => 'autocomplete.budgets']);
|
||||
Route::get('categories', ['uses' => 'Json\AutoCompleteController@categories', 'as' => 'autocomplete.categories']);
|
||||
Route::get('currencies', ['uses' => 'Json\AutoCompleteController@currencies', 'as' => 'autocomplete.currencies']);
|
||||
@ -954,15 +956,13 @@ Route::group(
|
||||
/**
|
||||
* Transaction Convert Controller
|
||||
*/
|
||||
//Route::group(
|
||||
// ['middleware' => 'user-full-auth', 'namespace' => 'FireflyIII\Http\Controllers\Transaction', 'prefix' => 'transactions/convert',
|
||||
// 'as' => 'transactions.convert.'], function () {
|
||||
// // TODO improve these routes
|
||||
// Route::get('{transactionType}/{tj}', ['uses' => 'ConvertController@index', 'as' => 'index']);
|
||||
// Route::post('{transactionType}/{tj}', ['uses' => 'ConvertController@postIndex', 'as' => 'index.post']);
|
||||
// // TODO end of todo
|
||||
//}
|
||||
//);
|
||||
Route::group(
|
||||
['middleware' => 'user-full-auth', 'namespace' => 'FireflyIII\Http\Controllers\Transaction', 'prefix' => 'transactions/convert',
|
||||
'as' => 'transactions.convert.'], static function () {
|
||||
Route::get('{transactionType}/{transactionGroup}', ['uses' => 'ConvertController@index', 'as' => 'index']);
|
||||
Route::post('{transactionType}/{transactionGroup}', ['uses' => 'ConvertController@postIndex', 'as' => 'index.post']);
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Transaction Link Controller
|
||||
|
@ -22,13 +22,16 @@ declare(strict_types=1);
|
||||
|
||||
namespace Tests\Feature\Controllers\Transaction;
|
||||
|
||||
use Amount;
|
||||
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
||||
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
|
||||
use FireflyIII\Repositories\User\UserRepositoryInterface;
|
||||
use Illuminate\Support\Collection;
|
||||
use Log;
|
||||
use Mockery;
|
||||
use Preferences;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
@ -54,101 +57,35 @@ class BulkControllerTest extends TestCase
|
||||
* @covers \FireflyIII\Http\Controllers\Transaction\BulkController
|
||||
* @covers \FireflyIII\Http\Controllers\Transaction\BulkController
|
||||
*/
|
||||
public function testEdit(): void
|
||||
public function testEditWithdrawal(): void
|
||||
{
|
||||
$this->markTestIncomplete('Needs to be rewritten for v4.8.0');
|
||||
|
||||
return;
|
||||
// mock stuff:
|
||||
$journalRepos = $this->mock(JournalRepositoryInterface::class);
|
||||
$journalRepos = $this->mockDefaultSession();
|
||||
$budgetRepos = $this->mock(BudgetRepositoryInterface::class);
|
||||
$userRepos = $this->mock(UserRepositoryInterface::class);
|
||||
$collector = $this->mock(GroupCollectorInterface::class);
|
||||
$withdrawal = $this->getRandomWithdrawal();
|
||||
$withdrawalArray = $this->getRandomWithdrawalAsArray();
|
||||
|
||||
Amount::shouldReceive('formatAnything')->atLeast()->once()->andReturn('-100');
|
||||
|
||||
$collector->shouldReceive('setTypes')
|
||||
->withArgs([[TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::TRANSFER]])->atLeast()->once()->andReturnSelf();
|
||||
$collector->shouldReceive('withCategoryInformation')->atLeast()->once()->andReturnSelf();
|
||||
$collector->shouldReceive('withBudgetInformation')->atLeast()->once()->andReturnSelf();
|
||||
$collector->shouldReceive('withTagInformation')->atLeast()->once()->andReturnSelf();
|
||||
$collector->shouldReceive('setJournalIds')->atLeast()->once()->withArgs([[$withdrawal->id]])->andReturnSelf();
|
||||
$collector->shouldReceive('getExtractedJournals')->atLeast()->once()->andReturn([$withdrawalArray]);
|
||||
|
||||
$userRepos->shouldReceive('hasRole')->withArgs([Mockery::any(), 'owner'])->atLeast()->once()->andReturn(true);
|
||||
$budgetRepos->shouldReceive('getActiveBudgets')->andReturn(new Collection);
|
||||
$journalRepos->shouldReceive('getJournalSourceAccounts')->andReturn(new Collection);
|
||||
$journalRepos->shouldReceive('getJournalDestinationAccounts')->andReturn(new Collection);
|
||||
$journalRepos->shouldReceive('firstNull')->andReturn(new TransactionJournal);
|
||||
$journalRepos->shouldReceive('getTransactionType')->andReturn('Transfer');
|
||||
$journalRepos->shouldReceive('isJournalReconciled')->andReturn(false);
|
||||
|
||||
$transfers = TransactionJournal::where('transaction_type_id', 3)->where('user_id', $this->user()->id)->take(4)->get()->pluck('id')->toArray();
|
||||
|
||||
$this->be($this->user());
|
||||
$response = $this->get(route('transactions.bulk.edit', $transfers));
|
||||
$response->assertStatus(200);
|
||||
$response->assertSee('Bulk edit a number of transactions');
|
||||
// has bread crumb
|
||||
$response->assertSee('<ol class="breadcrumb">');
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \FireflyIII\Http\Controllers\Transaction\BulkController
|
||||
*/
|
||||
public function testEditMultiple(): void
|
||||
{
|
||||
$this->markTestIncomplete('Needs to be rewritten for v4.8.0');
|
||||
|
||||
return;
|
||||
// mock stuff:
|
||||
$journalRepos = $this->mock(JournalRepositoryInterface::class);
|
||||
$budgetRepos = $this->mock(BudgetRepositoryInterface::class);
|
||||
$userRepos = $this->mock(UserRepositoryInterface::class);
|
||||
|
||||
$userRepos->shouldReceive('hasRole')->withArgs([Mockery::any(), 'owner'])->atLeast()->once()->andReturn(true);
|
||||
$budgetRepos->shouldReceive('getActiveBudgets')->andReturn(new Collection);
|
||||
$journalRepos->shouldReceive('firstNull')->andReturn(new TransactionJournal);
|
||||
$journalRepos->shouldReceive('getJournalSourceAccounts')
|
||||
->andReturn(new Collection([1, 2, 3]), new Collection, new Collection, new Collection, new Collection([1]));
|
||||
$journalRepos->shouldReceive('getJournalDestinationAccounts')
|
||||
->andReturn(new Collection, new Collection([1, 2, 3]), new Collection, new Collection, new Collection([1]));
|
||||
$journalRepos->shouldReceive('getTransactionType')
|
||||
->andReturn('Withdrawal', 'Opening balance', 'Withdrawal', 'Withdrawal', 'Withdrawal');
|
||||
$journalRepos->shouldReceive('isJournalReconciled')
|
||||
->andReturn(true, false, false, false, false);
|
||||
|
||||
// default transactions
|
||||
$collection = $this->user()->transactionJournals()->take(5)->get();
|
||||
$allIds = $collection->pluck('id')->toArray();
|
||||
$route = route('transactions.bulk.edit', implode(',', $allIds));
|
||||
$this->be($this->user());
|
||||
$response = $this->get($route);
|
||||
$response->assertStatus(200);
|
||||
$response->assertSee('Bulk edit a number of transactions');
|
||||
$response->assertSessionHas('info');
|
||||
// has bread crumb
|
||||
$response->assertSee('<ol class="breadcrumb">');
|
||||
$response->assertSee('marked as reconciled');
|
||||
$response->assertSee('multiple source accounts');
|
||||
$response->assertSee('multiple destination accounts');
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \FireflyIII\Http\Controllers\Transaction\BulkController
|
||||
* @covers \FireflyIII\Http\Controllers\Transaction\BulkController
|
||||
*/
|
||||
public function testEditNull(): void
|
||||
{
|
||||
$this->markTestIncomplete('Needs to be rewritten for v4.8.0');
|
||||
|
||||
return;
|
||||
// mock stuff:
|
||||
$journalRepos = $this->mock(JournalRepositoryInterface::class);
|
||||
$budgetRepos = $this->mock(BudgetRepositoryInterface::class);
|
||||
$userRepos = $this->mock(UserRepositoryInterface::class);
|
||||
|
||||
$userRepos->shouldReceive('hasRole')->withArgs([Mockery::any(), 'owner'])->atLeast()->once()->andReturn(true);
|
||||
$budgetRepos->shouldReceive('getActiveBudgets')->andReturn(new Collection);
|
||||
$journalRepos->shouldReceive('getJournalSourceAccounts')->andReturn(new Collection);
|
||||
$journalRepos->shouldReceive('getJournalDestinationAccounts')->andReturn(new Collection);
|
||||
$journalRepos->shouldReceive('firstNull')->andReturn(new TransactionJournal, null);
|
||||
$journalRepos->shouldReceive('getTransactionType')->andReturn('Transfer');
|
||||
$journalRepos->shouldReceive('isJournalReconciled')->andReturn(false);
|
||||
|
||||
$transfers = TransactionJournal::where('transaction_type_id', 3)->where('user_id', $this->user()->id)->take(4)->get()->pluck('id')->toArray();
|
||||
|
||||
$this->be($this->user());
|
||||
$response = $this->get(route('transactions.bulk.edit', $transfers));
|
||||
$response = $this->get(route('transactions.bulk.edit', [$withdrawal->id]));
|
||||
$response->assertStatus(200);
|
||||
$response->assertSee('Bulk edit a number of transactions');
|
||||
// has bread crumb
|
||||
@ -161,34 +98,26 @@ class BulkControllerTest extends TestCase
|
||||
*/
|
||||
public function testUpdate(): void
|
||||
{
|
||||
$this->markTestIncomplete('Needs to be rewritten for v4.8.0');
|
||||
|
||||
return;
|
||||
$tags = ['a', 'b', 'c'];
|
||||
$collection = TransactionJournal::where('transaction_type_id', 1)->where('user_id', $this->user()->id)->take(4)->get();
|
||||
$allIds = $collection->pluck('id')->toArray();
|
||||
|
||||
$budget = $this->getRandomBudget();
|
||||
$category = $this->getRandomCategory();
|
||||
$withdrawal = $this->getRandomWithdrawalAsArray();
|
||||
$data = [
|
||||
'category' => 'Some new category',
|
||||
'budget_id' => 1,
|
||||
'category' => $category->name,
|
||||
'budget_id' => $budget->id,
|
||||
'tags' => 'a,b,c',
|
||||
'journals' => $allIds,
|
||||
'journals' => [$withdrawal['transaction_journal_id']],
|
||||
];
|
||||
|
||||
$repository = $this->mock(JournalRepositoryInterface::class);
|
||||
$userRepos = $this->mock(UserRepositoryInterface::class);
|
||||
$repository = $this->mockDefaultSession();
|
||||
|
||||
$repository->shouldReceive('firstNull')->once()->andReturn(new TransactionJournal);
|
||||
$repository->shouldReceive('findNull')->times(4)->andReturn(new TransactionJournal);
|
||||
Preferences::shouldReceive('mark')->atLeast()->once();
|
||||
|
||||
$repository->shouldReceive('updateCategory')->times(4)->andReturn(new TransactionJournal())
|
||||
->withArgs([Mockery::any(), $data['category']]);
|
||||
$repository->shouldReceive('updateBudget')->atLeast()->once()->andReturn(new TransactionJournal())->withArgs([Mockery::any(), $data['budget_id']]);
|
||||
$repository->shouldReceive('updateCategory')->atLeast()->once()->andReturn(new TransactionJournal())->withArgs([Mockery::any(), $data['category']]);
|
||||
$repository->shouldReceive('updateTags')->atLeast()->once()->andReturn(new TransactionJournal())->withArgs([Mockery::any(), $tags]);
|
||||
|
||||
$repository->shouldReceive('updateBudget')->times(4)->andReturn(new TransactionJournal())
|
||||
->withArgs([Mockery::any(), $data['budget_id']]);
|
||||
|
||||
$repository->shouldReceive('updateTags')->times(4)->andReturn(new TransactionJournal())
|
||||
->withArgs([Mockery::any(), ['tags' => $tags]]);
|
||||
$repository->shouldReceive('findNull')->atLeast()->once()->andReturn(new TransactionJournal);
|
||||
|
||||
|
||||
$route = route('transactions.bulk.update');
|
||||
@ -198,40 +127,34 @@ class BulkControllerTest extends TestCase
|
||||
$response->assertSessionHas('success');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @covers \FireflyIII\Http\Controllers\Transaction\BulkController
|
||||
* @covers \FireflyIII\Http\Requests\BulkEditJournalRequest
|
||||
*/
|
||||
public function testUpdateNull(): void
|
||||
public function testUpdateIgnoreAll(): void
|
||||
{
|
||||
$this->markTestIncomplete('Needs to be rewritten for v4.8.0');
|
||||
|
||||
return;
|
||||
$tags = ['a', 'b', 'c'];
|
||||
$collection = TransactionJournal::where('transaction_type_id', 1)->where('user_id', $this->user()->id)->take(4)->get();
|
||||
$allIds = $collection->pluck('id')->toArray();
|
||||
|
||||
$budget = $this->getRandomBudget();
|
||||
$category = $this->getRandomCategory();
|
||||
$withdrawal = $this->getRandomWithdrawalAsArray();
|
||||
$data = [
|
||||
'category' => 'Some new category',
|
||||
'budget_id' => 1,
|
||||
'category' => $category->name,
|
||||
'budget_id' => $budget->id,
|
||||
'tags' => 'a,b,c',
|
||||
'journals' => $allIds,
|
||||
'journals' => [$withdrawal['transaction_journal_id']],
|
||||
'ignore_category' => '1',
|
||||
'ignore_budget' => '1',
|
||||
'ignore_tags' => '1',
|
||||
];
|
||||
|
||||
$repository = $this->mock(JournalRepositoryInterface::class);
|
||||
$userRepos = $this->mock(UserRepositoryInterface::class);
|
||||
$repository = $this->mockDefaultSession();
|
||||
|
||||
$repository->shouldReceive('firstNull')->once()->andReturn(new TransactionJournal);
|
||||
$repository->shouldReceive('findNull')->times(4)->andReturn(new TransactionJournal, null);
|
||||
Preferences::shouldReceive('mark')->atLeast()->once();
|
||||
|
||||
$repository->shouldReceive('updateCategory')->times(1)->andReturn(new TransactionJournal())
|
||||
->withArgs([Mockery::any(), $data['category']]);
|
||||
|
||||
$repository->shouldReceive('updateBudget')->times(1)->andReturn(new TransactionJournal())
|
||||
->withArgs([Mockery::any(), $data['budget_id']]);
|
||||
|
||||
$repository->shouldReceive('updateTags')->times(1)->andReturn(new TransactionJournal())
|
||||
->withArgs([Mockery::any(), ['tags' => $tags]]);
|
||||
$repository->shouldNotReceive('updateBudget');
|
||||
$repository->shouldNotReceive('updateCategory');
|
||||
$repository->shouldNotReceive('updateTags');
|
||||
$repository->shouldReceive('findNull')->atLeast()->once()->andReturn(new TransactionJournal);
|
||||
|
||||
|
||||
$route = route('transactions.bulk.update');
|
||||
|
Loading…
Reference in New Issue
Block a user