mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-02-25 18:45:27 -06:00
Expand recurring transactions API
This commit is contained in:
parent
d0db1117f7
commit
2d7b7c2f3f
@ -56,20 +56,6 @@ class PreferenceController extends Controller
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the resource.
|
||||
*
|
||||
* @param string $object
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function delete(string $object): JsonResponse
|
||||
{
|
||||
// todo delete object.
|
||||
|
||||
return response()->json([], 204);
|
||||
}
|
||||
|
||||
/**
|
||||
* List all of them.
|
||||
*
|
||||
|
@ -23,8 +23,9 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Api\V1\Controllers;
|
||||
|
||||
use FireflyIII\Api\V1\Requests\RecurrenceRequest;
|
||||
use FireflyIII\Models\Recurrence;
|
||||
use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface;
|
||||
use FireflyIII\Transformers\PiggyBankTransformer;
|
||||
use FireflyIII\Transformers\RecurrenceTransformer;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
@ -33,6 +34,7 @@ use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use League\Fractal\Manager;
|
||||
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
|
||||
use League\Fractal\Resource\Collection as FractalCollection;
|
||||
use League\Fractal\Resource\Item;
|
||||
use League\Fractal\Serializer\JsonApiSerializer;
|
||||
|
||||
/**
|
||||
@ -68,9 +70,9 @@ class RecurrenceController extends Controller
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function delete(string $object): JsonResponse
|
||||
public function delete(Recurrence $recurrence): JsonResponse
|
||||
{
|
||||
// todo delete object.
|
||||
$this->repository->destroy($recurrence);
|
||||
|
||||
return response()->json([], 204);
|
||||
}
|
||||
@ -112,28 +114,44 @@ class RecurrenceController extends Controller
|
||||
/**
|
||||
* List single resource.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param string $object
|
||||
* @param Request $request
|
||||
* @param Recurrence $recurrence
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function show(Request $request, string $object): JsonResponse
|
||||
public function show(Request $request, Recurrence $recurrence): JsonResponse
|
||||
{
|
||||
// todo implement me.
|
||||
$manager = new Manager();
|
||||
// add include parameter:
|
||||
$include = $request->get('include') ?? '';
|
||||
$manager->parseIncludes($include);
|
||||
|
||||
$baseUrl = $request->getSchemeAndHttpHost() . '/api/v1';
|
||||
$manager->setSerializer(new JsonApiSerializer($baseUrl));
|
||||
|
||||
$resource = new Item($recurrence, new RecurrenceTransformer($this->parameters), 'recurrences');
|
||||
|
||||
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json');
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Store new object.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param RecurrenceRequest $request
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function store(Request $request): JsonResponse
|
||||
public function store(RecurrenceRequest $request): JsonResponse
|
||||
{
|
||||
// todo replace code and replace request object.
|
||||
$recurrence = $this->repository->store($request->getAll());
|
||||
$manager = new Manager();
|
||||
$baseUrl = $request->getSchemeAndHttpHost() . '/api/v1';
|
||||
$manager->setSerializer(new JsonApiSerializer($baseUrl));
|
||||
$resource = new Item($recurrence, new RecurrenceTransformer($this->parameters), 'recurrences');
|
||||
|
||||
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json');
|
||||
}
|
||||
|
||||
/**
|
||||
|
480
app/Api/V1/Requests/RecurrenceRequest.php
Normal file
480
app/Api/V1/Requests/RecurrenceRequest.php
Normal file
@ -0,0 +1,480 @@
|
||||
<?php
|
||||
/**
|
||||
* RecurrenceRequest.php
|
||||
* Copyright (c) 2018 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This file is part of Firefly III.
|
||||
*
|
||||
* Firefly III is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Firefly III 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Api\V1\Requests;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Rules\BelongsUser;
|
||||
use Illuminate\Validation\Validator;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Class RecurrenceRequest
|
||||
*/
|
||||
class RecurrenceRequest extends Request
|
||||
{
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
// Only allow authenticated users
|
||||
return auth()->check();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getAll(): array
|
||||
{
|
||||
$return = [
|
||||
'recurrence' => [
|
||||
'type' => $this->string('type'),
|
||||
'title' => $this->string('title'),
|
||||
'description' => $this->string('description'),
|
||||
'first_date' => $this->date('first_date'),
|
||||
'repeat_until' => $this->date('repeat_until'),
|
||||
'repetitions' => $this->integer('nr_of_repetitions'),
|
||||
'apply_rules' => $this->boolean('apply_rules'),
|
||||
'active' => $this->boolean('active'),
|
||||
],
|
||||
'meta' => [
|
||||
'piggy_bank_id' => $this->integer('piggy_bank_id'),
|
||||
'piggy_bank_name' => $this->string('piggy_bank_name'),
|
||||
'tags' => explode(',', $this->string('tags')),
|
||||
],
|
||||
'transactions' => [],
|
||||
'repetitions' => [],
|
||||
];
|
||||
|
||||
// repetition data:
|
||||
/** @var array $repetitions */
|
||||
$repetitions = $this->get('repetitions');
|
||||
/** @var array $repetition */
|
||||
foreach ($repetitions as $repetition) {
|
||||
$return['repetitions'][] = [
|
||||
'type' => $repetition['type'],
|
||||
'moment' => $repetition['moment'],
|
||||
'skip' => (int)$repetition['skip'],
|
||||
'weekend' => (int)$repetition['weekend'],
|
||||
];
|
||||
}
|
||||
// transaction data:
|
||||
/** @var array $transactions */
|
||||
$transactions = $this->get('transactions');
|
||||
/** @var array $transaction */
|
||||
foreach ($transactions as $transaction) {
|
||||
$return['transactions'][] = [
|
||||
'amount' => $transaction['amount'],
|
||||
|
||||
'currency_id' => isset($transaction['currency_id']) ? (int)$transaction['currency_id'] : null,
|
||||
'currency_code' => $transaction['currency_code'] ?? null,
|
||||
|
||||
'foreign_amount' => $transaction['foreign_amount'] ?? null,
|
||||
'foreign_currency_id' => isset($transaction['foreign_currency_id']) ? (int)$transaction['foreign_currency_id'] : null,
|
||||
'foreign_currency_code' => $transaction['foreign_currency_code'] ?? null,
|
||||
|
||||
'budget_id' => isset($transaction['budget_id']) ? (int)$transaction['budget_id'] : null,
|
||||
'budget_name' => $transaction['budget_name'] ?? null,
|
||||
'category_id' => isset($transaction['category_id']) ? (int)$transaction['category_id'] : null,
|
||||
'category_name' => $transaction['category_name'] ?? null,
|
||||
|
||||
'source_id' => isset($transaction['source_id']) ? (int)$transaction['source_id'] : null,
|
||||
'source_name' => isset($transaction['source_name']) ? (string)$transaction['source_name'] : null,
|
||||
'destination_id' => isset($transaction['destination_id']) ? (int)$transaction['destination_id'] : null,
|
||||
'destination_name' => isset($transaction['destination_name']) ? (string)$transaction['destination_name'] : null,
|
||||
|
||||
'description' => $transaction['description'],
|
||||
];
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$today = new Carbon;
|
||||
$today->addDay();
|
||||
|
||||
return [
|
||||
'type' => 'required|in:withdrawal,transfer,deposit',
|
||||
'title' => 'required|between:1,255',
|
||||
'description' => 'between:1,65000',
|
||||
'first_date' => sprintf('required|date|after:%s', $today->format('Y-m-d')),
|
||||
'repeat_until' => sprintf('date|after:%s', $today->format('Y-m-d')),
|
||||
'nr_of_repetitions' => 'numeric|between:1,31',
|
||||
'apply_rules' => 'required|boolean',
|
||||
'active' => 'required|boolean',
|
||||
|
||||
// rules for meta values:
|
||||
'tags' => 'between:1,64000',
|
||||
'piggy_bank_id' => 'numeric',
|
||||
|
||||
// rules for repetitions.
|
||||
'repetitions.*.type' => 'required|in:daily,weekly,ndom,monthly,yearly',
|
||||
'repetitions.*.moment' => 'between:0,10',
|
||||
'repetitions.*.skip' => 'required|between:0,31',
|
||||
'repetitions.*.weekend' => 'required|between:1,4',
|
||||
|
||||
// rules for transactions.
|
||||
'transactions.*.currency_id' => 'numeric|exists:transaction_currencies,id|required_without:transactions.*.currency_code',
|
||||
'transactions.*.currency_code' => 'min:3|max:3|exists:transaction_currencies,code|required_without:transactions.*.currency_id',
|
||||
'transactions.*.foreign_amount' => 'numeric|more:0',
|
||||
'transactions.*.foreign_currency_id' => 'numeric|exists:transaction_currencies,id',
|
||||
'transactions.*.foreign_currency_code' => 'min:3|max:3|exists:transaction_currencies,code',
|
||||
'transactions.*.budget_id' => ['mustExist:budgets,id', new BelongsUser],
|
||||
'transactions.*.category_name' => 'between:1,255|nullable',
|
||||
'transactions.*.source_id' => ['numeric', 'nullable', new BelongsUser],
|
||||
'transactions.*.source_name' => 'between:1,255|nullable',
|
||||
'transactions.*.destination_id' => ['numeric', 'nullable', new BelongsUser],
|
||||
'transactions.*.destination_name' => 'between:1,255|nullable',
|
||||
'transactions.*.amount' => 'required|numeric|more:0',
|
||||
'transactions.*.description' => 'required|between:1,255',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the validator instance.
|
||||
*
|
||||
* @param Validator $validator
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function withValidator(Validator $validator): void
|
||||
{
|
||||
$validator->after(
|
||||
function (Validator $validator) {
|
||||
$this->atLeastOneTransaction($validator);
|
||||
$this->atLeastOneRepetition($validator);
|
||||
$this->validRepeatsUntil($validator);
|
||||
$this->validRepetitionMoment($validator);
|
||||
$this->foreignCurrencyInformation($validator);
|
||||
$this->validateAccountInformation($validator);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws an error when this asset account is invalid.
|
||||
*
|
||||
* @noinspection MoreThanThreeArgumentsInspection
|
||||
*
|
||||
* @param Validator $validator
|
||||
* @param int|null $accountId
|
||||
* @param null|string $accountName
|
||||
* @param string $idField
|
||||
* @param string $nameField
|
||||
*
|
||||
* @return null|Account
|
||||
*/
|
||||
protected function assetAccountExists(Validator $validator, ?int $accountId, ?string $accountName, string $idField, string $nameField): ?Account
|
||||
{
|
||||
$accountId = (int)$accountId;
|
||||
$accountName = (string)$accountName;
|
||||
// both empty? hard exit.
|
||||
if ($accountId < 1 && '' === $accountName) {
|
||||
$validator->errors()->add($idField, trans('validation.filled', ['attribute' => $idField]));
|
||||
|
||||
return null;
|
||||
}
|
||||
// ID belongs to user and is asset account:
|
||||
/** @var AccountRepositoryInterface $repository */
|
||||
$repository = app(AccountRepositoryInterface::class);
|
||||
$repository->setUser(auth()->user());
|
||||
$set = $repository->getAccountsById([$accountId]);
|
||||
if ($set->count() === 1) {
|
||||
/** @var Account $first */
|
||||
$first = $set->first();
|
||||
if ($first->accountType->type !== AccountType::ASSET) {
|
||||
$validator->errors()->add($idField, trans('validation.belongs_user'));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// we ignore the account name at this point.
|
||||
return $first;
|
||||
}
|
||||
|
||||
$account = $repository->findByName($accountName, [AccountType::ASSET]);
|
||||
if (null === $account) {
|
||||
$validator->errors()->add($nameField, trans('validation.belongs_user'));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return $account;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an error to the validator when there are no repetitions in the array of data.
|
||||
*
|
||||
* @param Validator $validator
|
||||
*/
|
||||
protected function atLeastOneRepetition(Validator $validator): void
|
||||
{
|
||||
$data = $validator->getData();
|
||||
$repetitions = $data['repetitions'] ?? [];
|
||||
// need at least one transaction
|
||||
if (\count($repetitions) === 0) {
|
||||
$validator->errors()->add('description', trans('validation.at_least_one_repetition'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an error to the validator when there are no transactions in the array of data.
|
||||
*
|
||||
* @param Validator $validator
|
||||
*/
|
||||
protected function atLeastOneTransaction(Validator $validator): void
|
||||
{
|
||||
$data = $validator->getData();
|
||||
$transactions = $data['transactions'] ?? [];
|
||||
// need at least one transaction
|
||||
if (\count($transactions) === 0) {
|
||||
$validator->errors()->add('description', trans('validation.at_least_one_transaction'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO can be made a rule?
|
||||
* If the transactions contain foreign amounts, there must also be foreign currency information.
|
||||
*
|
||||
* @param Validator $validator
|
||||
*/
|
||||
protected function foreignCurrencyInformation(Validator $validator): void
|
||||
{
|
||||
$data = $validator->getData();
|
||||
$transactions = $data['transactions'] ?? [];
|
||||
foreach ($transactions as $index => $transaction) {
|
||||
// must have currency info.
|
||||
if (isset($transaction['foreign_amount'])
|
||||
&& !(isset($transaction['foreign_currency_id'])
|
||||
|| isset($transaction['foreign_currency_code']))) {
|
||||
$validator->errors()->add(
|
||||
'transactions.' . $index . '.foreign_amount',
|
||||
trans('validation.require_currency_info')
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws an error when the given opposing account (of type $type) is invalid.
|
||||
* Empty data is allowed, system will default to cash.
|
||||
*
|
||||
* @noinspection MoreThanThreeArgumentsInspection
|
||||
*
|
||||
* @param Validator $validator
|
||||
* @param string $type
|
||||
* @param int|null $accountId
|
||||
* @param null|string $accountName
|
||||
* @param string $idField
|
||||
*
|
||||
* @return null|Account
|
||||
*/
|
||||
protected function opposingAccountExists(Validator $validator, string $type, ?int $accountId, ?string $accountName, string $idField): ?Account
|
||||
{
|
||||
$accountId = (int)$accountId;
|
||||
$accountName = (string)$accountName;
|
||||
// both empty? done!
|
||||
if ($accountId < 1 && \strlen($accountName) === 0) {
|
||||
return null;
|
||||
}
|
||||
if ($accountId !== 0) {
|
||||
// ID belongs to user and is $type account:
|
||||
/** @var AccountRepositoryInterface $repository */
|
||||
$repository = app(AccountRepositoryInterface::class);
|
||||
$repository->setUser(auth()->user());
|
||||
$set = $repository->getAccountsById([$accountId]);
|
||||
if ($set->count() === 1) {
|
||||
/** @var Account $first */
|
||||
$first = $set->first();
|
||||
if ($first->accountType->type !== $type) {
|
||||
$validator->errors()->add($idField, trans('validation.belongs_user'));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// we ignore the account name at this point.
|
||||
return $first;
|
||||
}
|
||||
}
|
||||
|
||||
// not having an opposing account by this name is NOT a problem.
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO can be a rule?
|
||||
*
|
||||
* Validates the given account information. Switches on given transaction type.
|
||||
*
|
||||
* @param Validator $validator
|
||||
*/
|
||||
protected function validateAccountInformation(Validator $validator): void
|
||||
{
|
||||
$data = $validator->getData();
|
||||
$transactions = $data['transactions'] ?? [];
|
||||
$idField = 'description';
|
||||
$transactionType = $data['type'] ?? 'false';
|
||||
foreach ($transactions as $index => $transaction) {
|
||||
$sourceId = isset($transaction['source_id']) ? (int)$transaction['source_id'] : null;
|
||||
$sourceName = $transaction['source_name'] ?? null;
|
||||
$destinationId = isset($transaction['destination_id']) ? (int)$transaction['destination_id'] : null;
|
||||
$destinationName = $transaction['destination_name'] ?? null;
|
||||
$sourceAccount = null;
|
||||
$destinationAccount = null;
|
||||
switch ($transactionType) {
|
||||
case 'withdrawal':
|
||||
$idField = 'transactions.' . $index . '.source_id';
|
||||
$nameField = 'transactions.' . $index . '.source_name';
|
||||
$sourceAccount = $this->assetAccountExists($validator, $sourceId, $sourceName, $idField, $nameField);
|
||||
$idField = 'transactions.' . $index . '.destination_id';
|
||||
$destinationAccount = $this->opposingAccountExists($validator, AccountType::EXPENSE, $destinationId, $destinationName, $idField);
|
||||
break;
|
||||
case 'deposit':
|
||||
$idField = 'transactions.' . $index . '.source_id';
|
||||
$sourceAccount = $this->opposingAccountExists($validator, AccountType::REVENUE, $sourceId, $sourceName, $idField);
|
||||
|
||||
$idField = 'transactions.' . $index . '.destination_id';
|
||||
$nameField = 'transactions.' . $index . '.destination_name';
|
||||
$destinationAccount = $this->assetAccountExists($validator, $destinationId, $destinationName, $idField, $nameField);
|
||||
break;
|
||||
case 'transfer':
|
||||
$idField = 'transactions.' . $index . '.source_id';
|
||||
$nameField = 'transactions.' . $index . '.source_name';
|
||||
$sourceAccount = $this->assetAccountExists($validator, $sourceId, $sourceName, $idField, $nameField);
|
||||
|
||||
$idField = 'transactions.' . $index . '.destination_id';
|
||||
$nameField = 'transactions.' . $index . '.destination_name';
|
||||
$destinationAccount = $this->assetAccountExists($validator, $destinationId, $destinationName, $idField, $nameField);
|
||||
break;
|
||||
default:
|
||||
$validator->errors()->add($idField, trans('validation.invalid_account_info'));
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
// add some errors in case of same account submitted:
|
||||
if (null !== $sourceAccount && null !== $destinationAccount && $sourceAccount->id === $destinationAccount->id) {
|
||||
$validator->errors()->add($idField, trans('validation.source_equals_destination'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Validator $validator
|
||||
*/
|
||||
private function validRepeatsUntil(Validator $validator): void
|
||||
{
|
||||
$data = $validator->getData();
|
||||
$repetitions = $data['nr_of_repetitions'] ?? null;
|
||||
$repeatUntil = $data['repeat_until'] ?? null;
|
||||
if (null !== $repetitions && null !== $repeatUntil) {
|
||||
// expect a date OR count:
|
||||
$validator->errors()->add('repeat_until', trans('validation.require_repeat_until'));
|
||||
$validator->errors()->add('repetitions', trans('validation.require_repeat_until'));
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO merge this in a rule somehow.
|
||||
*
|
||||
* @param Validator $validator
|
||||
*/
|
||||
private function validRepetitionMoment(Validator $validator): void
|
||||
{
|
||||
$data = $validator->getData();
|
||||
$repetitions = $data['repetitions'] ?? [];
|
||||
/**
|
||||
* @var int $index
|
||||
* @var array $repetition
|
||||
*/
|
||||
foreach ($repetitions as $index => $repetition) {
|
||||
switch ($repetition['type']) {
|
||||
default:
|
||||
$validator->errors()->add(sprintf('repetitions.%d.type', $index), trans('validation.valid_recurrence_rep_type'));
|
||||
|
||||
return;
|
||||
case 'daily':
|
||||
if ('' !== (string)$repetition['moment']) {
|
||||
$validator->errors()->add(sprintf('repetitions.%d.moment', $index), trans('validation.valid_recurrence_rep_moment'));
|
||||
}
|
||||
|
||||
return;
|
||||
case 'monthly':
|
||||
$dayOfMonth = (int)$repetition['moment'];
|
||||
if ($dayOfMonth < 1 || $dayOfMonth > 31) {
|
||||
$validator->errors()->add(sprintf('repetitions.%d.moment', $index), trans('validation.valid_recurrence_rep_moment'));
|
||||
}
|
||||
|
||||
return;
|
||||
case 'ndom':
|
||||
$parameters = explode(',', $repetition['moment']);
|
||||
if (\count($parameters) !== 2) {
|
||||
$validator->errors()->add(sprintf('repetitions.%d.moment', $index), trans('validation.valid_recurrence_rep_moment'));
|
||||
|
||||
return;
|
||||
}
|
||||
$nthDay = (int)($parameters[0] ?? 0.0);
|
||||
$dayOfWeek = (int)($parameters[1] ?? 0.0);
|
||||
if ($nthDay < 1 || $nthDay > 5) {
|
||||
$validator->errors()->add(sprintf('repetitions.%d.moment', $index), trans('validation.valid_recurrence_rep_moment'));
|
||||
|
||||
return;
|
||||
}
|
||||
if ($dayOfWeek < 1 || $dayOfWeek > 7) {
|
||||
$validator->errors()->add(sprintf('repetitions.%d.moment', $index), trans('validation.valid_recurrence_rep_moment'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
case 'weekly':
|
||||
$dayOfWeek = (int)$repetition['moment'];
|
||||
if ($dayOfWeek < 1 || $dayOfWeek > 7) {
|
||||
$validator->errors()->add(sprintf('repetitions.%d.moment', $index), trans('validation.valid_recurrence_rep_moment'));
|
||||
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 'yearly':
|
||||
try {
|
||||
Carbon::createFromFormat('Y-m-d', $repetition['moment']);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
$validator->errors()->add(sprintf('repetitions.%d.moment', $index), trans('validation.valid_recurrence_rep_moment'));
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -188,6 +188,8 @@ class TransactionRequest extends Request
|
||||
/**
|
||||
* Throws an error when this asset account is invalid.
|
||||
*
|
||||
* @noinspection MoreThanThreeArgumentsInspection
|
||||
*
|
||||
* @param Validator $validator
|
||||
* @param int|null $accountId
|
||||
* @param null|string $accountName
|
||||
@ -256,7 +258,7 @@ class TransactionRequest extends Request
|
||||
*
|
||||
* @param Validator $validator
|
||||
*/
|
||||
protected function checkValidDescriptions(Validator $validator)
|
||||
protected function checkValidDescriptions(Validator $validator): void
|
||||
{
|
||||
$data = $validator->getData();
|
||||
$transactions = $data['transactions'] ?? [];
|
||||
@ -317,6 +319,8 @@ class TransactionRequest extends Request
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO can be made a rule?
|
||||
*
|
||||
* If the transactions contain foreign amounts, there must also be foreign currency information.
|
||||
*
|
||||
* @param Validator $validator
|
||||
@ -342,6 +346,8 @@ class TransactionRequest extends Request
|
||||
* Throws an error when the given opposing account (of type $type) is invalid.
|
||||
* Empty data is allowed, system will default to cash.
|
||||
*
|
||||
* @noinspection MoreThanThreeArgumentsInspection
|
||||
*
|
||||
* @param Validator $validator
|
||||
* @param string $type
|
||||
* @param int|null $accountId
|
||||
@ -454,7 +460,7 @@ class TransactionRequest extends Request
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
protected function validateSplitAccounts(Validator $validator)
|
||||
protected function validateSplitAccounts(Validator $validator): void
|
||||
{
|
||||
$data = $validator->getData();
|
||||
$count = isset($data['transactions']) ? \count($data['transactions']) : 0;
|
||||
|
@ -30,6 +30,7 @@ use FireflyIII\Services\Internal\Support\RecurringTransactionTrait;
|
||||
use FireflyIII\Services\Internal\Support\TransactionServiceTrait;
|
||||
use FireflyIII\Services\Internal\Support\TransactionTypeTrait;
|
||||
use FireflyIII\User;
|
||||
use Log;
|
||||
|
||||
/**
|
||||
* Class RecurrenceFactory
|
||||
@ -44,12 +45,17 @@ class RecurrenceFactory
|
||||
/**
|
||||
* @param array $data
|
||||
*
|
||||
* @throws FireflyException
|
||||
* @return Recurrence
|
||||
*/
|
||||
public function create(array $data): Recurrence
|
||||
public function create(array $data): ?Recurrence
|
||||
{
|
||||
$type = $this->findTransactionType(ucfirst($data['recurrence']['type']));
|
||||
try {
|
||||
$type = $this->findTransactionType(ucfirst($data['recurrence']['type']));
|
||||
} catch (FireflyException $e) {
|
||||
Log::error($e->getMessage());
|
||||
|
||||
return null;
|
||||
}
|
||||
$repetitions = (int)$data['recurrence']['repetitions'];
|
||||
$recurrence = new Recurrence(
|
||||
[
|
||||
|
@ -150,9 +150,9 @@ class CreateRecurringTransactions implements ShouldQueue
|
||||
'budget_name' => null,
|
||||
'category_id' => null,
|
||||
'category_name' => $this->repository->getCategory($transaction),
|
||||
'source_id' => $transaction->source_account_id,
|
||||
'source_id' => $transaction->source_id,
|
||||
'source_name' => null,
|
||||
'destination_id' => $transaction->destination_account_id,
|
||||
'destination_id' => $transaction->destination_id,
|
||||
'destination_name' => null,
|
||||
'foreign_currency_id' => $transaction->foreign_currency_id,
|
||||
'foreign_currency_code' => null,
|
||||
|
@ -35,8 +35,8 @@ use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
*
|
||||
* @property int $transaction_currency_id,
|
||||
* @property int $foreign_currency_id
|
||||
* @property int $source_account_id
|
||||
* @property int $destination_account_id
|
||||
* @property int $source_id
|
||||
* @property int $destination_id
|
||||
* @property string $amount
|
||||
* @property string $foreign_amount
|
||||
* @property string $description
|
||||
@ -62,7 +62,7 @@ class RecurrenceTransaction extends Model
|
||||
];
|
||||
/** @var array */
|
||||
protected $fillable
|
||||
= ['recurrence_id', 'transaction_currency_id', 'foreign_currency_id', 'source_account_id', 'destination_account_id', 'amount', 'foreign_amount',
|
||||
= ['recurrence_id', 'transaction_currency_id', 'foreign_currency_id', 'source_id', 'destination_id', 'amount', 'foreign_amount',
|
||||
'description'];
|
||||
/** @var string */
|
||||
protected $table = 'recurrences_transactions';
|
||||
|
@ -39,7 +39,7 @@ class RecurrenceTransactionMeta extends Model
|
||||
use SoftDeletes;
|
||||
/** @var array */
|
||||
protected $casts
|
||||
= [
|
||||
= [
|
||||
'created_at' => 'datetime',
|
||||
'updated_at' => 'datetime',
|
||||
'deleted_at' => 'datetime',
|
||||
|
@ -508,7 +508,6 @@ class RecurringRepository implements RecurringRepositoryInterface
|
||||
/**
|
||||
* @param array $data
|
||||
*
|
||||
* @throws FireflyException
|
||||
* @return Recurrence
|
||||
*/
|
||||
public function store(array $data): Recurrence
|
||||
|
@ -155,7 +155,6 @@ interface RecurringRepositoryInterface
|
||||
*
|
||||
* @param array $data
|
||||
*
|
||||
* @throws FireflyException
|
||||
* @return Recurrence
|
||||
*/
|
||||
public function store(array $data): Recurrence;
|
||||
|
@ -25,6 +25,7 @@ namespace FireflyIII\Services\Internal\Destroy;
|
||||
|
||||
use Exception;
|
||||
use FireflyIII\Models\Recurrence;
|
||||
use FireflyIII\Models\RecurrenceTransaction;
|
||||
use Log;
|
||||
|
||||
/**
|
||||
@ -39,6 +40,19 @@ class RecurrenceDestroyService
|
||||
public function destroy(Recurrence $recurrence): void
|
||||
{
|
||||
try {
|
||||
// delete all meta data
|
||||
$recurrence->recurrenceMeta()->delete();
|
||||
|
||||
// delete all transactions.
|
||||
/** @var RecurrenceTransaction $transaction */
|
||||
foreach($recurrence->recurrenceTransactions as $transaction) {
|
||||
$transaction->recurrenceTransactionMeta()->delete();
|
||||
$transaction->delete();
|
||||
}
|
||||
// delete all repetitions
|
||||
$recurrence->recurrenceRepetitions()->delete();
|
||||
|
||||
// delete recurrence
|
||||
$recurrence->delete();
|
||||
} catch (Exception $e) { // @codeCoverageIgnore
|
||||
Log::error(sprintf('Could not delete recurrence: %s', $e->getMessage())); // @codeCoverageIgnore
|
||||
|
@ -66,8 +66,6 @@ trait RecurringTransactionTrait
|
||||
/**
|
||||
* @param Recurrence $recurrence
|
||||
* @param array $transactions
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function createTransactions(Recurrence $recurrence, array $transactions): void
|
||||
{
|
||||
@ -76,19 +74,17 @@ trait RecurringTransactionTrait
|
||||
$source = null;
|
||||
$destination = null;
|
||||
switch ($recurrence->transactionType->type) {
|
||||
default:
|
||||
throw new FireflyException(sprintf('Cannot create "%s".', $recurrence->transactionType->type));
|
||||
case TransactionType::WITHDRAWAL:
|
||||
$source = $this->findAccount(AccountType::ASSET, $array['source_account_id'], null);
|
||||
$destination = $this->findAccount(AccountType::EXPENSE, null, $array['destination_account_name']);
|
||||
$source = $this->findAccount(AccountType::ASSET, $array['source_id'], $array['source_name'] ?? null);
|
||||
$destination = $this->findAccount(AccountType::EXPENSE, $array['destination_id'] ?? null, $array['destination_name']);
|
||||
break;
|
||||
case TransactionType::DEPOSIT:
|
||||
$source = $this->findAccount(AccountType::REVENUE, null, $array['source_account_name']);
|
||||
$destination = $this->findAccount(AccountType::ASSET, $array['destination_account_id'], null);
|
||||
$source = $this->findAccount(AccountType::REVENUE, $array['source_id'] ?? null, $array['source_name']);
|
||||
$destination = $this->findAccount(AccountType::ASSET, $array['destination_id'], $array['destination_name'] ?? null);
|
||||
break;
|
||||
case TransactionType::TRANSFER:
|
||||
$source = $this->findAccount(AccountType::ASSET, $array['source_account_id'], null);
|
||||
$destination = $this->findAccount(AccountType::ASSET, $array['destination_account_id'], null);
|
||||
$source = $this->findAccount(AccountType::ASSET, $array['source_id'], $array['source_name'] ?? null);
|
||||
$destination = $this->findAccount(AccountType::ASSET, $array['destination_id'], $array['destination_name'] ?? null);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -96,9 +92,9 @@ trait RecurringTransactionTrait
|
||||
[
|
||||
'recurrence_id' => $recurrence->id,
|
||||
'transaction_currency_id' => $array['transaction_currency_id'],
|
||||
'foreign_currency_id' => '' === (string)$array['foreign_amount'] ? null : $array['foreign_currency_id'],
|
||||
'source_account_id' => $source->id,
|
||||
'destination_account_id' => $destination->id,
|
||||
'foreign_currency_id' => '' === (string)$array['foreign_currency_id'] ? null : $array['foreign_currency_id'],
|
||||
'source_id' => $source->id,
|
||||
'destination_id' => $destination->id,
|
||||
'amount' => $array['amount'],
|
||||
'foreign_amount' => '' === (string)$array['foreign_amount'] ? null : (string)$array['foreign_amount'],
|
||||
'description' => $array['description'],
|
||||
|
@ -66,8 +66,8 @@ class ChangesForV475 extends Migration
|
||||
$table->integer('recurrence_id', false, true);
|
||||
$table->integer('transaction_currency_id', false, true);
|
||||
$table->integer('foreign_currency_id', false, true)->nullable();
|
||||
$table->integer('source_account_id', false, true);
|
||||
$table->integer('destination_account_id', false, true);
|
||||
$table->integer('source_id', false, true);
|
||||
$table->integer('destination_id', false, true);
|
||||
|
||||
$table->decimal('amount', 22, 12);
|
||||
$table->decimal('foreign_amount', 22, 12)->nullable();
|
||||
@ -77,8 +77,8 @@ class ChangesForV475 extends Migration
|
||||
$table->foreign('recurrence_id')->references('id')->on('recurrences')->onDelete('cascade');
|
||||
$table->foreign('transaction_currency_id')->references('id')->on('transaction_currencies')->onDelete('cascade');
|
||||
$table->foreign('foreign_currency_id')->references('id')->on('transaction_currencies')->onDelete('set null');
|
||||
$table->foreign('source_account_id')->references('id')->on('accounts')->onDelete('cascade');
|
||||
$table->foreign('destination_account_id')->references('id')->on('accounts')->onDelete('cascade');
|
||||
$table->foreign('source_id')->references('id')->on('accounts')->onDelete('cascade');
|
||||
$table->foreign('destination_id')->references('id')->on('accounts')->onDelete('cascade');
|
||||
}
|
||||
);
|
||||
|
||||
|
36
public/js/ff/recurring/create.js
vendored
36
public/js/ff/recurring/create.js
vendored
@ -155,15 +155,15 @@ function initializeAutoComplete() {
|
||||
);
|
||||
});
|
||||
|
||||
if ($('input[name="destination_account_name"]').length > 0) {
|
||||
if ($('input[name="destination_name"]').length > 0) {
|
||||
$.getJSON('json/expense-accounts').done(function (data) {
|
||||
$('input[name="destination_account_name"]').typeahead({source: data, autoSelect: false});
|
||||
$('input[name="destination_name"]').typeahead({source: data, autoSelect: false});
|
||||
});
|
||||
}
|
||||
|
||||
if ($('input[name="source_account_name"]').length > 0) {
|
||||
if ($('input[name="source_name"]').length > 0) {
|
||||
$.getJSON('json/revenue-accounts').done(function (data) {
|
||||
$('input[name="source_account_name"]').typeahead({source: data, autoSelect: false});
|
||||
$('input[name="source_name"]').typeahead({source: data, autoSelect: false});
|
||||
});
|
||||
}
|
||||
|
||||
@ -208,16 +208,16 @@ function updateFormFields() {
|
||||
|
||||
if (transactionType === 'withdrawal') {
|
||||
// hide source account name:
|
||||
$('#source_account_name_holder').hide();
|
||||
$('#source_name_holder').hide();
|
||||
|
||||
// show source account ID:
|
||||
$('#source_account_id_holder').show();
|
||||
|
||||
// show destination name:
|
||||
$('#destination_account_name_holder').show();
|
||||
$('#source_id_holder').show();
|
||||
|
||||
// hide destination ID:
|
||||
$('#destination_account_id_holder').hide();
|
||||
$('#destination_id_holder').hide();
|
||||
|
||||
// show destination name:
|
||||
$('#destination_name_holder').show();
|
||||
|
||||
// show budget
|
||||
$('#budget_id_holder').show();
|
||||
@ -227,19 +227,19 @@ function updateFormFields() {
|
||||
}
|
||||
|
||||
if (transactionType === 'deposit') {
|
||||
$('#source_account_name_holder').show();
|
||||
$('#source_account_id_holder').hide();
|
||||
$('#destination_account_name_holder').hide();
|
||||
$('#destination_account_id_holder').show();
|
||||
$('#source_name_holder').show();
|
||||
$('#source_id_holder').hide();
|
||||
$('#destination_name_holder').hide();
|
||||
$('#destination_id_holder').show();
|
||||
$('#budget_id_holder').hide();
|
||||
$('#piggy_bank_id_holder').hide();
|
||||
}
|
||||
|
||||
if (transactionType === 'transfer') {
|
||||
$('#source_account_name_holder').hide();
|
||||
$('#source_account_id_holder').show();
|
||||
$('#destination_account_name_holder').hide();
|
||||
$('#destination_account_id_holder').show();
|
||||
$('#source_name_holder').hide();
|
||||
$('#source_id_holder').show();
|
||||
$('#destination_name_holder').hide();
|
||||
$('#destination_id_holder').show();
|
||||
$('#budget_id_holder').hide();
|
||||
$('#piggy_bank_id_holder').show();
|
||||
}
|
||||
|
32
public/js/ff/recurring/edit.js
vendored
32
public/js/ff/recurring/edit.js
vendored
@ -156,15 +156,15 @@ function initializeAutoComplete() {
|
||||
);
|
||||
});
|
||||
|
||||
if ($('input[name="destination_account_name"]').length > 0) {
|
||||
if ($('input[name="destination_name"]').length > 0) {
|
||||
$.getJSON('json/expense-accounts').done(function (data) {
|
||||
$('input[name="destination_account_name"]').typeahead({source: data, autoSelect: false});
|
||||
$('input[name="destination_name"]').typeahead({source: data, autoSelect: false});
|
||||
});
|
||||
}
|
||||
|
||||
if ($('input[name="source_account_name"]').length > 0) {
|
||||
if ($('input[name="source_name"]').length > 0) {
|
||||
$.getJSON('json/revenue-accounts').done(function (data) {
|
||||
$('input[name="source_account_name"]').typeahead({source: data, autoSelect: false});
|
||||
$('input[name="source_name"]').typeahead({source: data, autoSelect: false});
|
||||
});
|
||||
}
|
||||
|
||||
@ -209,16 +209,16 @@ function updateFormFields() {
|
||||
|
||||
if (transactionType === 'withdrawal') {
|
||||
// hide source account name:
|
||||
$('#source_account_name_holder').hide();
|
||||
$('#source_name_holder').hide();
|
||||
|
||||
// show source account ID:
|
||||
$('#source_account_id_holder').show();
|
||||
$('#source_id_holder').show();
|
||||
|
||||
// show destination name:
|
||||
$('#destination_account_name_holder').show();
|
||||
$('#destination_name_holder').show();
|
||||
|
||||
// hide destination ID:
|
||||
$('#destination_account_id_holder').hide();
|
||||
$('#destination_id_holder').hide();
|
||||
|
||||
// show budget
|
||||
$('#budget_id_holder').show();
|
||||
@ -228,19 +228,19 @@ function updateFormFields() {
|
||||
}
|
||||
|
||||
if (transactionType === 'deposit') {
|
||||
$('#source_account_name_holder').show();
|
||||
$('#source_account_id_holder').hide();
|
||||
$('#destination_account_name_holder').hide();
|
||||
$('#destination_account_id_holder').show();
|
||||
$('#source_name_holder').show();
|
||||
$('#source_id_holder').hide();
|
||||
$('#destination_name_holder').hide();
|
||||
$('#destination_id_holder').show();
|
||||
$('#budget_id_holder').hide();
|
||||
$('#piggy_bank_id_holder').hide();
|
||||
}
|
||||
|
||||
if (transactionType === 'transfer') {
|
||||
$('#source_account_name_holder').hide();
|
||||
$('#source_account_id_holder').show();
|
||||
$('#destination_account_name_holder').hide();
|
||||
$('#destination_account_id_holder').show();
|
||||
$('#source_name_holder').hide();
|
||||
$('#source_id_holder').show();
|
||||
$('#destination_name_holder').hide();
|
||||
$('#destination_id_holder').show();
|
||||
$('#budget_id_holder').hide();
|
||||
$('#piggy_bank_id_holder').show();
|
||||
}
|
||||
|
@ -24,66 +24,66 @@ declare(strict_types=1);
|
||||
|
||||
return [
|
||||
// new user:
|
||||
'bank_name' => 'Bank name',
|
||||
'bank_balance' => 'Balance',
|
||||
'savings_balance' => 'Savings balance',
|
||||
'credit_card_limit' => 'Credit card limit',
|
||||
'automatch' => 'Match automatically',
|
||||
'skip' => 'Skip',
|
||||
'name' => 'Name',
|
||||
'active' => 'Active',
|
||||
'amount_min' => 'Minimum amount',
|
||||
'amount_max' => 'Maximum amount',
|
||||
'match' => 'Matches on',
|
||||
'strict' => 'Strict mode',
|
||||
'repeat_freq' => 'Repeats',
|
||||
'journal_currency_id' => 'Currency',
|
||||
'currency_id' => 'Currency',
|
||||
'transaction_currency_id' => 'Currency',
|
||||
'external_ip' => 'Your server\'s external IP',
|
||||
'attachments' => 'Attachments',
|
||||
'journal_amount' => 'Amount',
|
||||
'journal_source_account_name' => 'Revenue account (source)',
|
||||
'journal_source_account_id' => 'Asset account (source)',
|
||||
'BIC' => 'BIC',
|
||||
'verify_password' => 'Verify password security',
|
||||
'source_account' => 'Source account',
|
||||
'destination_account' => 'Destination account',
|
||||
'journal_destination_account_id' => 'Asset account (destination)',
|
||||
'asset_destination_account' => 'Asset account (destination)',
|
||||
'asset_source_account' => 'Asset account (source)',
|
||||
'journal_description' => 'Description',
|
||||
'note' => 'Notes',
|
||||
'split_journal' => 'Split this transaction',
|
||||
'split_journal_explanation' => 'Split this transaction in multiple parts',
|
||||
'currency' => 'Currency',
|
||||
'account_id' => 'Asset account',
|
||||
'budget_id' => 'Budget',
|
||||
'openingBalance' => 'Opening balance',
|
||||
'tagMode' => 'Tag mode',
|
||||
'tag_position' => 'Tag location',
|
||||
'virtualBalance' => 'Virtual balance',
|
||||
'targetamount' => 'Target amount',
|
||||
'accountRole' => 'Account role',
|
||||
'openingBalanceDate' => 'Opening balance date',
|
||||
'ccType' => 'Credit card payment plan',
|
||||
'ccMonthlyPaymentDate' => 'Credit card monthly payment date',
|
||||
'piggy_bank_id' => 'Piggy bank',
|
||||
'returnHere' => 'Return here',
|
||||
'returnHereExplanation' => 'After storing, return here to create another one.',
|
||||
'returnHereUpdateExplanation' => 'After updating, return here.',
|
||||
'description' => 'Description',
|
||||
'expense_account' => 'Expense account',
|
||||
'revenue_account' => 'Revenue account',
|
||||
'decimal_places' => 'Decimal places',
|
||||
'exchange_rate_instruction' => 'Foreign currencies',
|
||||
'source_amount' => 'Amount (source)',
|
||||
'destination_amount' => 'Amount (destination)',
|
||||
'native_amount' => 'Native amount',
|
||||
'new_email_address' => 'New email address',
|
||||
'verification' => 'Verification',
|
||||
'api_key' => 'API key',
|
||||
'remember_me' => 'Remember me',
|
||||
'bank_name' => 'Bank name',
|
||||
'bank_balance' => 'Balance',
|
||||
'savings_balance' => 'Savings balance',
|
||||
'credit_card_limit' => 'Credit card limit',
|
||||
'automatch' => 'Match automatically',
|
||||
'skip' => 'Skip',
|
||||
'name' => 'Name',
|
||||
'active' => 'Active',
|
||||
'amount_min' => 'Minimum amount',
|
||||
'amount_max' => 'Maximum amount',
|
||||
'match' => 'Matches on',
|
||||
'strict' => 'Strict mode',
|
||||
'repeat_freq' => 'Repeats',
|
||||
'journal_currency_id' => 'Currency',
|
||||
'currency_id' => 'Currency',
|
||||
'transaction_currency_id' => 'Currency',
|
||||
'external_ip' => 'Your server\'s external IP',
|
||||
'attachments' => 'Attachments',
|
||||
'journal_amount' => 'Amount',
|
||||
'journal_source_name' => 'Revenue account (source)',
|
||||
'journal_source_id' => 'Asset account (source)',
|
||||
'BIC' => 'BIC',
|
||||
'verify_password' => 'Verify password security',
|
||||
'source_account' => 'Source account',
|
||||
'destination_account' => 'Destination account',
|
||||
'journal_destination_id' => 'Asset account (destination)',
|
||||
'asset_destination_account' => 'Asset account (destination)',
|
||||
'asset_source_account' => 'Asset account (source)',
|
||||
'journal_description' => 'Description',
|
||||
'note' => 'Notes',
|
||||
'split_journal' => 'Split this transaction',
|
||||
'split_journal_explanation' => 'Split this transaction in multiple parts',
|
||||
'currency' => 'Currency',
|
||||
'account_id' => 'Asset account',
|
||||
'budget_id' => 'Budget',
|
||||
'openingBalance' => 'Opening balance',
|
||||
'tagMode' => 'Tag mode',
|
||||
'tag_position' => 'Tag location',
|
||||
'virtualBalance' => 'Virtual balance',
|
||||
'targetamount' => 'Target amount',
|
||||
'accountRole' => 'Account role',
|
||||
'openingBalanceDate' => 'Opening balance date',
|
||||
'ccType' => 'Credit card payment plan',
|
||||
'ccMonthlyPaymentDate' => 'Credit card monthly payment date',
|
||||
'piggy_bank_id' => 'Piggy bank',
|
||||
'returnHere' => 'Return here',
|
||||
'returnHereExplanation' => 'After storing, return here to create another one.',
|
||||
'returnHereUpdateExplanation' => 'After updating, return here.',
|
||||
'description' => 'Description',
|
||||
'expense_account' => 'Expense account',
|
||||
'revenue_account' => 'Revenue account',
|
||||
'decimal_places' => 'Decimal places',
|
||||
'exchange_rate_instruction' => 'Foreign currencies',
|
||||
'source_amount' => 'Amount (source)',
|
||||
'destination_amount' => 'Amount (destination)',
|
||||
'native_amount' => 'Native amount',
|
||||
'new_email_address' => 'New email address',
|
||||
'verification' => 'Verification',
|
||||
'api_key' => 'API key',
|
||||
'remember_me' => 'Remember me',
|
||||
|
||||
'source_account_asset' => 'Source account (asset account)',
|
||||
'destination_account_expense' => 'Destination account (expense account)',
|
||||
|
@ -37,6 +37,8 @@ return [
|
||||
'invalid_selection' => 'Your selection is invalid',
|
||||
'belongs_user' => 'This value is invalid for this field.',
|
||||
'at_least_one_transaction' => 'Need at least one transaction.',
|
||||
'at_least_one_repetition' => 'Need at least one repetition.',
|
||||
'require_repeat_until' => 'Require either a number of repetitions, or an end date (repeats_until). Not both.',
|
||||
'require_currency_info' => 'The content of this field is invalid without currency information.',
|
||||
'equal_description' => 'Transaction description should not equal global description.',
|
||||
'file_invalid_mime' => 'File ":name" is of type ":mime" which is not accepted as a new upload.',
|
||||
@ -115,6 +117,8 @@ return [
|
||||
'unique_piggy_bank_for_user' => 'The name of the piggy bank must be unique.',
|
||||
'secure_password' => 'This is not a secure password. Please try again. For more information, visit http://bit.ly/FF3-password-security',
|
||||
'valid_recurrence_rep_type' => 'Invalid repetition type for recurring transactions',
|
||||
'valid_recurrence_rep_moment' => 'Invalid repetition moment for this type of repetition.',
|
||||
'invalid_account_info' => 'Invalid account information',
|
||||
'attributes' => [
|
||||
'email' => 'email address',
|
||||
'description' => 'description',
|
||||
|
@ -89,16 +89,16 @@
|
||||
{{ ExpandedForm.amountNoCurrency('amount', []) }}
|
||||
|
||||
{# source account if withdrawal, or if transfer: #}
|
||||
{{ ExpandedForm.activeAssetAccountList('source_account_id', null, {label: trans('form.asset_source_account')}) }}
|
||||
{{ ExpandedForm.activeAssetAccountList('source_id', null, {label: trans('form.asset_source_account')}) }}
|
||||
|
||||
{# source account name for deposits: #}
|
||||
{{ ExpandedForm.text('source_account_name', null, {label: trans('form.revenue_account')}) }}
|
||||
{{ ExpandedForm.text('source_name', null, {label: trans('form.revenue_account')}) }}
|
||||
|
||||
{# destination if deposit or transfer: #}
|
||||
{{ ExpandedForm.activeAssetAccountList('destination_account_id', null, {label: trans('form.asset_destination_account')} ) }}
|
||||
{{ ExpandedForm.activeAssetAccountList('destination_id', null, {label: trans('form.asset_destination_account')} ) }}
|
||||
|
||||
{# destination account name for withdrawals #}
|
||||
{{ ExpandedForm.text('destination_account_name', null, {label: trans('form.expense_account')}) }}
|
||||
{{ ExpandedForm.text('destination_name', null, {label: trans('form.expense_account')}) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -88,16 +88,16 @@
|
||||
{{ ExpandedForm.amountNoCurrency('amount', array.transactions[0].amount) }}
|
||||
|
||||
{# source account if withdrawal, or if transfer: #}
|
||||
{{ ExpandedForm.assetAccountList('source_account_id', array.transactions[0].source_account_id, {label: trans('form.asset_source_account')}) }}
|
||||
{{ ExpandedForm.assetAccountList('source_id', array.transactions[0].source_id, {label: trans('form.asset_source_account')}) }}
|
||||
|
||||
{# source account name for deposits: #}
|
||||
{{ ExpandedForm.text('source_account_name', array.transactions[0].source_account_name, {label: trans('form.revenue_account')}) }}
|
||||
{{ ExpandedForm.text('source_name', array.transactions[0].source_name, {label: trans('form.revenue_account')}) }}
|
||||
|
||||
{# destination if deposit or transfer: #}
|
||||
{{ ExpandedForm.assetAccountList('destination_account_id', array.transactions[0].destination_account_id, {label: trans('form.asset_destination_account')} ) }}
|
||||
{{ ExpandedForm.assetAccountList('destination_id', array.transactions[0].destination_id, {label: trans('form.asset_destination_account')} ) }}
|
||||
|
||||
{# destination account name for withdrawals #}
|
||||
{{ ExpandedForm.text('destination_account_name', array.transactions[0].destination_account_name, {label: trans('form.expense_account')}) }}
|
||||
{{ ExpandedForm.text('destination_name', array.transactions[0].destination_name, {label: trans('form.expense_account')}) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -75,9 +75,9 @@
|
||||
{% if null != rtt['foreign_amount'] %}
|
||||
({{ formatAmountBySymbol(rtt['foreign_amount'],rtt['foreign_currency_symbol'],rtt['foreign_currency_dp']) }}),
|
||||
{% endif %}
|
||||
<a href="{{ route('accounts.show', rtt['source_account_id']) }}">{{ rtt['source_account_name'] }}</a>
|
||||
<a href="{{ route('accounts.show', rtt['source_id']) }}">{{ rtt['source_name'] }}</a>
|
||||
→
|
||||
<a href="{{ route('accounts.show', rtt['destination_account_id']) }}">{{ rtt['destination_account_name'] }}</a>
|
||||
<a href="{{ route('accounts.show', rtt['destination_id']) }}">{{ rtt['destination_name'] }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ol>
|
||||
|
@ -101,11 +101,11 @@
|
||||
<tbody>
|
||||
{% for transaction in array.transactions %}
|
||||
<tr>
|
||||
<td data-value="{{ transaction.source_account_name }}">
|
||||
<a href="{{ route('accounts.show', [transaction.source_account_id]) }}">{{ transaction.source_account_name }}</a>
|
||||
<td data-value="{{ transaction.source_name }}">
|
||||
<a href="{{ route('accounts.show', [transaction.source_id]) }}">{{ transaction.source_name }}</a>
|
||||
</td>
|
||||
<td data-value="{{ transaction.destination_account_name }}">
|
||||
<a href="{{ route('accounts.show', [transaction.destination_account_id]) }}">{{ transaction.destination_account_name }}</a>
|
||||
<td data-value="{{ transaction.destination_name }}">
|
||||
<a href="{{ route('accounts.show', [transaction.destination_id]) }}">{{ transaction.destination_name }}</a>
|
||||
</td>
|
||||
<td>
|
||||
{{ formatAmountBySymbol(transaction.amount,transaction.currency_symbol,transaction.currency_dp) }}
|
||||
|
Loading…
Reference in New Issue
Block a user