diff --git a/.travis.yml b/.travis.yml index f9c80429b4..c6f9b990ef 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ sudo: required language: bash env: - - VERSION=4.8.1.6 + - VERSION=4.8.1.7 dist: xenial diff --git a/app/Api/V1/Controllers/Search/TransferController.php b/app/Api/V1/Controllers/Search/TransferController.php new file mode 100644 index 0000000000..921c49bdf8 --- /dev/null +++ b/app/Api/V1/Controllers/Search/TransferController.php @@ -0,0 +1,116 @@ +. + */ + +namespace FireflyIII\Api\V1\Controllers\Search; + + +use FireflyIII\Api\V1\Controllers\Controller; +use FireflyIII\Api\V1\Requests\Search\TransferRequest; +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; +use FireflyIII\Support\Search\TransferSearch; +use FireflyIII\Transformers\TransactionGroupTransformer; +use FireflyIII\User; +use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; +use Illuminate\Http\Response; +use League\Fractal\Pagination\IlluminatePaginatorAdapter; +use League\Fractal\Resource\Collection as FractalCollection; + +/** + * Class TransferController + */ +class TransferController extends Controller +{ + /** + * @param Request $request + * + * @return JsonResponse|Response + * @throws FireflyException + */ + public function search(TransferRequest $request) + { + // configure transfer search to search for a > b + $search = app(TransferSearch::class); + $search->setSource($request->get('source')); + $search->setDestination($request->get('destination')); + $search->setAmount($request->get('amount')); + $search->setDescription($request->get('description')); + $search->setDate($request->get('date')); + + $left = $search->search(); + + // configure transfer search to search for b > a + $search->setSource($request->get('destination')); + $search->setDestination($request->get('source')); + $search->setAmount($request->get('amount')); + $search->setDescription($request->get('description')); + $search->setDate($request->get('date')); + + $right = $search->search(); + + // add parameters to URL: + $this->parameters->set('source', $request->get('source')); + $this->parameters->set('destination', $request->get('destination')); + $this->parameters->set('amount', $request->get('amount')); + $this->parameters->set('description', $request->get('description')); + $this->parameters->set('date', $request->get('date')); + + // get all journal ID's. + $total = $left->merge($right)->unique('id')->pluck('id')->toArray(); + if (0 === count($total)) { + // forces search to be empty. + $total = [-1]; + } + + // collector to return results. + $pageSize = (int)app('preferences')->getForUser(auth()->user(), 'listPageSize', 50)->data; + $manager = $this->getManager(); + /** @var User $admin */ + $admin = auth()->user(); + + // use new group collector: + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector + ->setUser($admin) + // all info needed for the API: + ->withAPIInformation() + // set page size: + ->setLimit($pageSize) + // set page to retrieve + ->setPage(1) + ->setJournalIds($total); + + $paginator = $collector->getPaginatedGroups(); + $paginator->setPath(route('api.v1.search.transfers') . $this->buildParams()); + $transactions = $paginator->getCollection(); + + /** @var TransactionGroupTransformer $transformer */ + $transformer = app(TransactionGroupTransformer::class); + $transformer->setParameters($this->parameters); + + $resource = new FractalCollection($transactions, $transformer, 'transactions'); + $resource->setPaginator(new IlluminatePaginatorAdapter($paginator)); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + } +} \ No newline at end of file diff --git a/app/Api/V1/Controllers/TransactionController.php b/app/Api/V1/Controllers/TransactionController.php index 545126cbdb..f948ad88bc 100644 --- a/app/Api/V1/Controllers/TransactionController.php +++ b/app/Api/V1/Controllers/TransactionController.php @@ -28,6 +28,7 @@ use FireflyIII\Api\V1\Requests\TransactionStoreRequest; use FireflyIII\Api\V1\Requests\TransactionUpdateRequest; use FireflyIII\Events\StoredTransactionGroup; use FireflyIII\Events\UpdatedTransactionGroup; +use FireflyIII\Exceptions\DuplicateTransactionException; use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Models\TransactionGroup; use FireflyIII\Models\TransactionJournal; @@ -279,7 +280,20 @@ class TransactionController extends Controller Log::channel('audit') ->info('Store new transaction over API.', $data); - $transactionGroup = $this->groupRepository->store($data); + try { + $transactionGroup = $this->groupRepository->store($data); + } catch (DuplicateTransactionException $e) { + // return bad validation message. + // TODO use Laravel's internal validation thing to do this. + $response = [ + 'message' => 'The given data was invalid.', + 'errors' => [ + 'transactions.0.description' => [$e->getMessage()], + ], + ]; + + return response()->json($response, 422); + } event(new StoredTransactionGroup($transactionGroup)); diff --git a/app/Api/V1/Requests/Search/TransferRequest.php b/app/Api/V1/Requests/Search/TransferRequest.php new file mode 100644 index 0000000000..66d970bfd4 --- /dev/null +++ b/app/Api/V1/Requests/Search/TransferRequest.php @@ -0,0 +1,58 @@ +. + */ + +namespace FireflyIII\Api\V1\Requests\Search; + + +use FireflyIII\Api\V1\Requests\Request; +use FireflyIII\Rules\IsTransferAccount; + +/** + * Class TransferRequest + */ +class TransferRequest extends Request +{ + /** + * Authorize logged in users. + * + * @return bool + */ + public function authorize(): bool + { + // Only allow authenticated users + return auth()->check(); + } + + /** + * @return array + */ + public function rules(): array + { + return [ + 'source' => ['required', new IsTransferAccount], + 'destination' => ['required', new IsTransferAccount], + 'amount' => 'required|numeric|more:0', + 'description' => 'required|min:1', + 'date' => 'required|date', + ]; + } + +} \ No newline at end of file diff --git a/app/Api/V1/Requests/TransactionStoreRequest.php b/app/Api/V1/Requests/TransactionStoreRequest.php index 795eef6fca..07f5e77997 100644 --- a/app/Api/V1/Requests/TransactionStoreRequest.php +++ b/app/Api/V1/Requests/TransactionStoreRequest.php @@ -58,8 +58,9 @@ class TransactionStoreRequest extends Request public function getAll(): array { $data = [ - 'group_title' => $this->string('group_title'), - 'transactions' => $this->getTransactionData(), + 'group_title' => $this->string('group_title'), + 'error_if_duplicate_hash' => $this->boolean('error_if_duplicate_hash'), + 'transactions' => $this->getTransactionData(), ]; return $data; @@ -75,6 +76,7 @@ class TransactionStoreRequest extends Request $rules = [ // basic fields for group: 'group_title' => 'between:1,1000|nullable', + 'error_if_duplicate_hash' => [new IsBoolean], // transaction rules (in array for splits): 'transactions.*.type' => 'required|in:withdrawal,deposit,transfer,opening-balance,reconciliation', diff --git a/app/Exceptions/DuplicateTransactionException.php b/app/Exceptions/DuplicateTransactionException.php new file mode 100644 index 0000000000..8d2d62ddb1 --- /dev/null +++ b/app/Exceptions/DuplicateTransactionException.php @@ -0,0 +1,31 @@ +. + */ + +namespace FireflyIII\Exceptions; +use Exception; + +/** + * Class DuplicateTransactionException + */ +class DuplicateTransactionException extends Exception +{ + +} \ No newline at end of file diff --git a/app/Factory/TransactionGroupFactory.php b/app/Factory/TransactionGroupFactory.php index 91e7ee21fd..bb69039030 100644 --- a/app/Factory/TransactionGroupFactory.php +++ b/app/Factory/TransactionGroupFactory.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace FireflyIII\Factory; +use FireflyIII\Exceptions\DuplicateTransactionException; use FireflyIII\Models\TransactionGroup; use FireflyIII\User; @@ -52,10 +53,13 @@ class TransactionGroupFactory * @param array $data * * @return TransactionGroup + * @throws DuplicateTransactionException */ public function create(array $data): TransactionGroup { $this->journalFactory->setUser($this->user); + $this->journalFactory->setErrorOnHash($data['error_if_duplicate_hash']); + $collection = $this->journalFactory->create($data); $title = $data['group_title'] ?? null; $title = '' === $title ? null : $title; diff --git a/app/Factory/TransactionJournalFactory.php b/app/Factory/TransactionJournalFactory.php index c7e79306d4..4bd58b8dcf 100644 --- a/app/Factory/TransactionJournalFactory.php +++ b/app/Factory/TransactionJournalFactory.php @@ -26,10 +26,12 @@ namespace FireflyIII\Factory; use Carbon\Carbon; use Exception; +use FireflyIII\Exceptions\DuplicateTransactionException; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\Account; use FireflyIII\Models\TransactionCurrency; use FireflyIII\Models\TransactionJournal; +use FireflyIII\Models\TransactionJournalMeta; use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Bill\BillRepositoryInterface; @@ -72,6 +74,8 @@ class TransactionJournalFactory private $typeRepository; /** @var User The user */ private $user; + /** @var bool */ + private $errorOnHash; /** * Constructor. @@ -81,7 +85,8 @@ class TransactionJournalFactory */ public function __construct() { - $this->fields = [ + $this->errorOnHash = false; + $this->fields = [ // sepa 'sepa_cc', 'sepa_ct_op', 'sepa_ct_id', 'sepa_db', 'sepa_country', 'sepa_ep', @@ -119,6 +124,7 @@ class TransactionJournalFactory * @param array $data * * @return Collection + * @throws DuplicateTransactionException */ public function create(array $data): Collection { @@ -193,11 +199,15 @@ class TransactionJournalFactory * @param NullArrayObject $row * * @return TransactionJournal|null + * @throws Exception + * @throws DuplicateTransactionException */ private function createJournal(NullArrayObject $row): ?TransactionJournal { $row['import_hash_v2'] = $this->hashArray($row); + $this->errorIfDuplicate($row['import_hash_v2']); + /** Some basic fields */ $type = $this->typeRepository->findTransactionType(null, $row['type']); $carbon = $row['date'] ?? new Carbon; @@ -376,6 +386,30 @@ class TransactionJournalFactory return $journal; } + /** + * If this transaction already exists, throw an error. + * + * @param string $hash + * + * @throws DuplicateTransactionException + */ + private function errorIfDuplicate(string $hash): void + { + if (false === $this->errorOnHash) { + return; + } + $result = null; + if ($this->errorOnHash) { + /** @var TransactionJournalMeta $result */ + $result = TransactionJournalMeta::where('data', json_encode($hash, JSON_THROW_ON_ERROR)) + ->with(['transactionJournal', 'transactionJournal.transactionGroup']) + ->first(); + } + if (null !== $result) { + throw new DuplicateTransactionException(sprintf('Duplicate of transaction #%d.', $result->transactionJournal->transaction_group_id)); + } + } + /** * @param TransactionCurrency|null $currency * @param Account $account @@ -485,4 +519,14 @@ class TransactionJournalFactory throw new FireflyException(sprintf('Destination: %s', $this->accountValidator->destError)); // @codeCoverageIgnore } } + + /** + * @param bool $errorOnHash + */ + public function setErrorOnHash(bool $errorOnHash): void + { + $this->errorOnHash = $errorOnHash; + } + + } diff --git a/app/Http/Controllers/Transaction/CreateController.php b/app/Http/Controllers/Transaction/CreateController.php index 9276fe59e1..13f4e2782b 100644 --- a/app/Http/Controllers/Transaction/CreateController.php +++ b/app/Http/Controllers/Transaction/CreateController.php @@ -78,7 +78,7 @@ class CreateController extends Controller $defaultCurrency = app('amount')->getDefaultCurrency(); $previousUri = $this->rememberPreviousUri('transactions.create.uri'); $parts = parse_url($previousUri); - $previousUri = sprintf('%s://%s/%s', $parts['scheme'], $parts['host'], $parts['path']); + $previousUri = sprintf('%s://%s/%s', $parts['scheme'], $parts['host'] ?? '', $parts['path'] ?? ''); session()->put('preFilled', $preFilled); diff --git a/app/Http/Controllers/Transaction/EditController.php b/app/Http/Controllers/Transaction/EditController.php index 055957b9ea..4052f636c9 100644 --- a/app/Http/Controllers/Transaction/EditController.php +++ b/app/Http/Controllers/Transaction/EditController.php @@ -83,7 +83,7 @@ class EditController extends Controller $cash = $repository->getCashAccount(); $previousUri = $this->rememberPreviousUri('transactions.edit.uri'); $parts = parse_url($previousUri); - $previousUri = sprintf('%s://%s/%s', $parts['scheme'], $parts['host'], $parts['path']); + $previousUri = sprintf('%s://%s/%s', $parts['scheme'], $parts['host'], $parts['path'] ?? ''); return view('transactions.edit', compact('cash', 'transactionGroup', 'allowedOpposingTypes', 'accountToTypes', 'defaultCurrency', 'previousUri')); diff --git a/app/Repositories/TransactionGroup/TransactionGroupRepository.php b/app/Repositories/TransactionGroup/TransactionGroupRepository.php index d23853869c..5f7de565c7 100644 --- a/app/Repositories/TransactionGroup/TransactionGroupRepository.php +++ b/app/Repositories/TransactionGroup/TransactionGroupRepository.php @@ -27,6 +27,7 @@ namespace FireflyIII\Repositories\TransactionGroup; use Carbon\Carbon; use DB; use Exception; +use FireflyIII\Exceptions\DuplicateTransactionException; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Factory\TransactionGroupFactory; use FireflyIII\Models\AccountMeta; @@ -314,6 +315,7 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface * @param array $data * * @return TransactionGroup + * @throws DuplicateTransactionException */ public function store(array $data): TransactionGroup { diff --git a/app/Repositories/TransactionGroup/TransactionGroupRepositoryInterface.php b/app/Repositories/TransactionGroup/TransactionGroupRepositoryInterface.php index d184aa8f27..58c0496c2a 100644 --- a/app/Repositories/TransactionGroup/TransactionGroupRepositoryInterface.php +++ b/app/Repositories/TransactionGroup/TransactionGroupRepositoryInterface.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace FireflyIII\Repositories\TransactionGroup; +use FireflyIII\Exceptions\DuplicateTransactionException; use FireflyIII\Models\TransactionGroup; use FireflyIII\Support\NullArrayObject; use FireflyIII\User; @@ -125,6 +126,7 @@ interface TransactionGroupRepositoryInterface * @param array $data * * @return TransactionGroup + * @throws DuplicateTransactionException */ public function store(array $data): TransactionGroup; diff --git a/app/Rules/IsTransferAccount.php b/app/Rules/IsTransferAccount.php new file mode 100644 index 0000000000..68cb9d3667 --- /dev/null +++ b/app/Rules/IsTransferAccount.php @@ -0,0 +1,73 @@ +. + */ + +namespace FireflyIII\Rules; + + +use FireflyIII\Models\TransactionType; +use FireflyIII\Validation\AccountValidator; +use Illuminate\Contracts\Validation\Rule; +use Log; + +/** + * Class IsTransferAccount + */ +class IsTransferAccount implements Rule +{ + /** + * Get the validation error message. + * + * @return string|array + */ + public function message(): string + { + return (string)trans('validation.not_transfer_account'); + } + + /** + * Determine if the validation rule passes. + * + * @param string $attribute + * @param mixed $value + * + * @return bool + */ + public function passes($attribute, $value): bool + { + Log::debug(sprintf('Now in %s(%s)', __METHOD__, $value)); + /** @var AccountValidator $validator */ + $validator = app(AccountValidator::class); + $validator->setTransactionType(TransactionType::TRANSFER); + $validator->setUser(auth()->user()); + + $validAccount = $validator->validateSource(null, (string)$value); + if (true === $validAccount) { + Log::debug('Found account based on name. Return true.'); + + // found by name, use repos to return. + return true; + } + $validAccount = $validator->validateSource((int)$value, null); + Log::debug(sprintf('Search by id (%d), result is %s.', (int)$value, var_export($validAccount, true))); + + return !(false === $validAccount); + } +} \ No newline at end of file diff --git a/app/Support/Search/GenericSearchInterface.php b/app/Support/Search/GenericSearchInterface.php index 47dacfbbdd..c14e59d57e 100644 --- a/app/Support/Search/GenericSearchInterface.php +++ b/app/Support/Search/GenericSearchInterface.php @@ -22,7 +22,16 @@ namespace FireflyIII\Support\Search; +use Illuminate\Support\Collection; + +/** + * Interface GenericSearchInterface + */ interface GenericSearchInterface { + /** + * @return Collection + */ + public function search(): Collection; } \ No newline at end of file diff --git a/app/Support/Search/TransferSearch.php b/app/Support/Search/TransferSearch.php new file mode 100644 index 0000000000..dd9912b830 --- /dev/null +++ b/app/Support/Search/TransferSearch.php @@ -0,0 +1,147 @@ +. + */ + +namespace FireflyIII\Support\Search; + + +use Carbon\Carbon; +use FireflyIII\Models\Account; +use FireflyIII\Models\AccountType; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use FireflyIII\User; +use Illuminate\Database\Query\JoinClause; +use Illuminate\Support\Collection; +use InvalidArgumentException; +use Log; + +/** + * Class TransferSearch + */ +class TransferSearch implements GenericSearchInterface +{ + /** @var AccountRepositoryInterface */ + private $accountRepository; + /** @var string */ + private $amount; + /** @var Carbon */ + private $date; + /** @var string */ + private $description; + /** @var Account */ + private $destination; + /** @var Account */ + private $source; + /** @var array */ + private $types; + + public function __construct() + { + $this->accountRepository = app(AccountRepositoryInterface::class); + $this->types = [AccountType::ASSET, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE]; + } + + /** + * @return Collection + */ + public function search(): Collection + { + /** @var User $user */ + $user = auth()->user(); + + $query = $user->transactionJournals() + ->leftJoin( + 'transactions as source', static function (JoinClause $join) { + $join->on('transaction_journals.id', '=', 'source.transaction_journal_id'); + $join->where('source.amount', '<', '0'); + } + ) + ->leftJoin( + 'transactions as destination', static function (JoinClause $join) { + $join->on('transaction_journals.id', '=', 'destination.transaction_journal_id'); + $join->where('destination.amount', '>', '0'); + } + ) + ->where('source.account_id', $this->source->id) + ->where('destination.account_id', $this->destination->id) + ->where('transaction_journals.description', $this->description) + ->where('destination.amount', $this->amount) + ->where('transaction_journals.date', $this->date->format('Y-m-d 00:00:00')) + ; + + return $query->get(['transaction_journals.id', 'transaction_journals.transaction_group_id']); + } + + /** + * @param string $amount + */ + public function setAmount(string $amount): void + { + $this->amount = $amount; + } + + /** + * @param string $date + */ + public function setDate(string $date): void + { + try { + $carbon = Carbon::createFromFormat('Y-m-d', $date); + } catch (InvalidArgumentException $e) { + Log::error($e->getMessage()); + $carbon = Carbon::now(); + } + $this->date = $carbon; + } + + /** + * @param string $description + */ + public function setDescription(string $description): void + { + $this->description = $description; + } + + /** + * @param string $destination + */ + public function setDestination(string $destination): void + { + if (is_numeric($destination)) { + $this->destination = $this->accountRepository->findNull((int)$destination); + } + if (null === $this->destination) { + $this->destination = $this->accountRepository->findByName($destination, $this->types); + } + } + + /** + * @param string $source + */ + public function setSource(string $source): void + { + if (is_numeric($source)) { + $this->source = $this->accountRepository->findNull((int)$source); + } + if (null === $this->source) { + $this->source = $this->accountRepository->findByName($source, $this->types); + } + } +} \ No newline at end of file diff --git a/changelog.md b/changelog.md index e8f6ca01ad..e29549f1e0 100644 --- a/changelog.md +++ b/changelog.md @@ -2,6 +2,15 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## [4.8.1.7 (API 0.10.5)] - 2019-10-26 + +### Fixed +- Error when creating transactions from the index of Firefly III. + +### API +- Firefly III can filter duplicate transactions. +- New endpoint that can search for specific transfers. + ## [4.8.1.6 (API 0.10.4)] - 2019-10-25 ### Fixed diff --git a/composer.lock b/composer.lock index 78a401be40..7aebea7a31 100644 --- a/composer.lock +++ b/composer.lock @@ -3213,16 +3213,16 @@ }, { "name": "psr/log", - "version": "1.1.0", + "version": "1.1.1", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd" + "reference": "bf73deb2b3b896a9d9c75f3f0d88185d2faa27e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", - "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", + "url": "https://api.github.com/repos/php-fig/log/zipball/bf73deb2b3b896a9d9c75f3f0d88185d2faa27e2", + "reference": "bf73deb2b3b896a9d9c75f3f0d88185d2faa27e2", "shasum": "" }, "require": { @@ -3231,7 +3231,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "1.1.x-dev" } }, "autoload": { @@ -3256,7 +3256,7 @@ "psr", "psr-3" ], - "time": "2018-11-20T15:27:04+00:00" + "time": "2019-10-25T08:06:51+00:00" }, { "name": "psr/simple-cache", diff --git a/config/firefly.php b/config/firefly.php index 7a4a29ee8f..d838f8d7cb 100644 --- a/config/firefly.php +++ b/config/firefly.php @@ -125,8 +125,8 @@ return [ 'is_demo_site' => false, ], 'encryption' => null === env('USE_ENCRYPTION') || env('USE_ENCRYPTION') === true, - 'version' => '4.8.1.6', - 'api_version' => '0.10.4', + 'version' => '4.8.1.7', + 'api_version' => '0.10.5', 'db_version' => 11, 'maxUploadSize' => 15242880, 'send_error_message' => env('SEND_ERROR_MESSAGE', true), diff --git a/resources/lang/.gitignore b/resources/lang/.gitignore new file mode 100644 index 0000000000..ce5091f4e6 --- /dev/null +++ b/resources/lang/.gitignore @@ -0,0 +1,9 @@ +ca_ES +da_DK +he_IL +ja_JP +pt_PT +sl_SI +uk_UA +sv_SE +sr_CS diff --git a/resources/lang/cs_CZ/validation.php b/resources/lang/cs_CZ/validation.php index 746d2aad94..f22071b8c2 100644 --- a/resources/lang/cs_CZ/validation.php +++ b/resources/lang/cs_CZ/validation.php @@ -45,6 +45,7 @@ return [ 'at_least_one_repetition' => 'Potřebujete alespoň jedno opakování.', 'require_repeat_until' => 'Require either a number of repetitions, or an end date (repeat_until). Not both.', 'require_currency_info' => 'Obsah tohoto pole je neplatný bez informace o měně.', + 'not_transfer_account' => 'This account is not an account that can be used for transfers.', 'require_currency_amount' => 'The content of this field is invalid without foreign amount information.', 'equal_description' => 'Popis transakce nesmí být stejný jako globální popis.', 'file_invalid_mime' => 'Soubor ":name" je typu ":mime", který není schválen pro nahrání.', diff --git a/resources/lang/de_DE/validation.php b/resources/lang/de_DE/validation.php index 5d8efd5660..1ec036283c 100644 --- a/resources/lang/de_DE/validation.php +++ b/resources/lang/de_DE/validation.php @@ -45,6 +45,7 @@ return [ 'at_least_one_repetition' => 'Mindestens eine Wiederholung erforderlich.', 'require_repeat_until' => 'Erfordert entweder eine Anzahl von Wiederholungen oder ein Enddatum (repeat_until). Nicht beides.', 'require_currency_info' => 'Der Inhalt dieses Feldes ist ohne Währungsinformationen ungültig.', + 'not_transfer_account' => 'This account is not an account that can be used for transfers.', 'require_currency_amount' => 'Der Inhalt dieses Feldes ist ohne Fremdbetragsangaben ungültig.', 'equal_description' => 'Die Transaktionsbeschreibung darf nicht der globalen Beschreibung entsprechen.', 'file_invalid_mime' => 'Die Datei „:name” ist vom Typ „:mime”, welcher nicht zum Hochladen zugelassen ist.', diff --git a/resources/lang/el_GR/validation.php b/resources/lang/el_GR/validation.php index 458f8d200a..897ba08d29 100644 --- a/resources/lang/el_GR/validation.php +++ b/resources/lang/el_GR/validation.php @@ -45,6 +45,7 @@ return [ 'at_least_one_repetition' => 'Απαιτείται τουλάχιστον μία επανάληψη.', 'require_repeat_until' => 'Απαιτείται είτε ένας αριθμός επαναλήψεων, ή μία ημερομηνία λήξης (repeat_until). Όχι και τα δύο.', 'require_currency_info' => 'Το περιεχόμενο αυτού του πεδίου δεν είναι έγκυρη χωρίς νομισματικές πληροφορίες.', + 'not_transfer_account' => 'This account is not an account that can be used for transfers.', 'require_currency_amount' => 'Το περιεχόμενο αυτού του πεδίου δεν είναι έγκυρο χωρίς πληροφορίες ετερόχθονος ποσού.', 'equal_description' => 'Η περιγραφή της συναλλαγής δεν πρέπει να ισούται με καθολική περιγραφή.', 'file_invalid_mime' => 'Το αρχείο ":name" είναι τύπου ":mime" που δεν είναι αποδεκτός ως νέας μεταφόρτωσης.', diff --git a/resources/lang/en_US/validation.php b/resources/lang/en_US/validation.php index 3337ef46f6..338e005c2d 100644 --- a/resources/lang/en_US/validation.php +++ b/resources/lang/en_US/validation.php @@ -45,6 +45,7 @@ return [ 'at_least_one_repetition' => 'Need at least one repetition.', 'require_repeat_until' => 'Require either a number of repetitions, or an end date (repeat_until). Not both.', 'require_currency_info' => 'The content of this field is invalid without currency information.', + 'not_transfer_account' => 'This account is not an account that can be used for transfers.', 'require_currency_amount' => 'The content of this field is invalid without foreign amount 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.', diff --git a/resources/lang/es_ES/validation.php b/resources/lang/es_ES/validation.php index 4bce393ca0..d5d8c90b49 100644 --- a/resources/lang/es_ES/validation.php +++ b/resources/lang/es_ES/validation.php @@ -45,6 +45,7 @@ return [ 'at_least_one_repetition' => 'Se necesita al menos una repetición.', 'require_repeat_until' => 'Se precisa un número de repeticiones o una fecha de finalización (repeat_until). No ambas.', 'require_currency_info' => 'El contenido de este campo no es válido sin la información montearia.', + 'not_transfer_account' => 'This account is not an account that can be used for transfers.', 'require_currency_amount' => 'El contenido de este campo no es válido sin información de cantidad extranjera.', 'equal_description' => 'La descripción de la transacción no debería ser igual a la descripción global.', 'file_invalid_mime' => 'El archivo ":name" es de tipo ":mime", el cual no se acepta.', diff --git a/resources/lang/fr_FR/validation.php b/resources/lang/fr_FR/validation.php index 54d0b92682..4ce1ed17f0 100644 --- a/resources/lang/fr_FR/validation.php +++ b/resources/lang/fr_FR/validation.php @@ -45,6 +45,7 @@ return [ 'at_least_one_repetition' => 'Besoin d\'au moins une répétition.', 'require_repeat_until' => 'Besoin d’un certain nombre de répétitions ou d\'une date de fin (repeat_until). Pas les deux.', 'require_currency_info' => 'Le contenu de ce champ n\'est pas valide sans informations sur la devise.', + 'not_transfer_account' => 'This account is not an account that can be used for transfers.', 'require_currency_amount' => 'Le contenu de ce champ est invalide sans informations sur le montant étranger.', 'equal_description' => 'La description de la transaction ne doit pas être identique à la description globale.', 'file_invalid_mime' => 'Le fichier ":name" est du type ":mime" ce qui n\'est pas accepté pour un nouvel envoi.', diff --git a/resources/lang/hu_HU/validation.php b/resources/lang/hu_HU/validation.php index 7be7a66292..30ebe0b472 100644 --- a/resources/lang/hu_HU/validation.php +++ b/resources/lang/hu_HU/validation.php @@ -45,6 +45,7 @@ return [ 'at_least_one_repetition' => 'Legalább egy ismétlés szükséges.', 'require_repeat_until' => 'Legalább egy ismétlésszám vagy egy végdátum (repeat_until) kötelező. Csak az egyik.', 'require_currency_info' => 'Ennek a mezőnek a tartalma érvénytelen pénznem információ nélkül.', + 'not_transfer_account' => 'This account is not an account that can be used for transfers.', 'require_currency_amount' => 'The content of this field is invalid without foreign amount information.', 'equal_description' => 'A tranzakció leírása nem egyezhet meg a globális leírással.', 'file_invalid_mime' => '":name" fájl ":mime" típusú ami nem lehet új feltöltés.', diff --git a/resources/lang/id_ID/validation.php b/resources/lang/id_ID/validation.php index 523b54a80e..a8456a6ec3 100644 --- a/resources/lang/id_ID/validation.php +++ b/resources/lang/id_ID/validation.php @@ -45,6 +45,7 @@ return [ 'at_least_one_repetition' => 'Minimal harus ada satu pengulangan.', 'require_repeat_until' => 'Dibutuhkan hanya sebuah angka pengulangan, atau tanggal akhir (repeat_until). Bukan keduanya.', 'require_currency_info' => 'Isi dalam bidang ini tidak valid jika tidak disertai informasi mata uang.', + 'not_transfer_account' => 'This account is not an account that can be used for transfers.', 'require_currency_amount' => 'The content of this field is invalid without foreign amount information.', 'equal_description' => 'Deskripsi transaksi harus berbeda dari deskripsi umum.', 'file_invalid_mime' => 'File ":name" adalah tipe ":mime" yang tidak diterima sebagai upload baru.', diff --git a/resources/lang/it_IT/validation.php b/resources/lang/it_IT/validation.php index 68d0c1b204..bcf5b4da4c 100644 --- a/resources/lang/it_IT/validation.php +++ b/resources/lang/it_IT/validation.php @@ -45,6 +45,7 @@ return [ 'at_least_one_repetition' => 'È necessaria almeno una ripetizione.', 'require_repeat_until' => 'Richiede un numero di ripetizioni o una data di fine (ripeti fino al), non entrambi.', 'require_currency_info' => 'Il contenuto di questo campo non è valido senza informazioni sulla valuta.', + 'not_transfer_account' => 'This account is not an account that can be used for transfers.', 'require_currency_amount' => 'Il contenuto di questo campo non è valido senza le informazioni sull\'importo estero.', 'equal_description' => 'La descrizione della transazione non deve essere uguale alla descrizione globale.', 'file_invalid_mime' => 'Il file ":name" è di tipo ":mime" che non è accettato come nuovo caricamento.', diff --git a/resources/lang/nb_NO/validation.php b/resources/lang/nb_NO/validation.php index 50106d1563..ad307f5570 100644 --- a/resources/lang/nb_NO/validation.php +++ b/resources/lang/nb_NO/validation.php @@ -45,6 +45,7 @@ return [ 'at_least_one_repetition' => 'Trenger minst en gjentagelse.', 'require_repeat_until' => 'Krever enten et antall repetisjoner eller en slutt dato (gjentas til). Ikke begge.', 'require_currency_info' => 'Innholdet i dette feltet er ugyldig uten valutainformasjon.', + 'not_transfer_account' => 'This account is not an account that can be used for transfers.', 'require_currency_amount' => 'The content of this field is invalid without foreign amount information.', 'equal_description' => 'Transaksjonsbeskrivelsen bør ikke være lik global beskrivelse.', 'file_invalid_mime' => 'Kan ikke akseptere fil ":name" av typen ":mime" for opplasting.', diff --git a/resources/lang/nl_NL/validation.php b/resources/lang/nl_NL/validation.php index 3cf0956485..ac97aafae1 100644 --- a/resources/lang/nl_NL/validation.php +++ b/resources/lang/nl_NL/validation.php @@ -45,6 +45,7 @@ return [ 'at_least_one_repetition' => 'Er is op zijn minst één herhaling nodig.', 'require_repeat_until' => 'Je moet een aantal herhalingen opgeven, of een einddatum (repeat_until). Niet beide.', 'require_currency_info' => 'De inhoud van dit veld is ongeldig zonder valutagegevens.', + 'not_transfer_account' => 'This account is not an account that can be used for transfers.', 'require_currency_amount' => 'De inhoud van dit veld is ongeldig zonder bedrag in vreemde valuta.', 'equal_description' => 'Transactiebeschrijving mag niet gelijk zijn aan globale beschrijving.', 'file_invalid_mime' => 'Bestand ":name" is van het type ":mime", en die kan je niet uploaden.', diff --git a/resources/lang/pl_PL/firefly.php b/resources/lang/pl_PL/firefly.php index f0befb7c58..a6e2cdab9f 100644 --- a/resources/lang/pl_PL/firefly.php +++ b/resources/lang/pl_PL/firefly.php @@ -258,7 +258,7 @@ return [ 'search_modifier_created_on' => 'Transakcja stworzona :value', 'search_modifier_updated_on' => 'Transakcja zaktualizowana :value', 'modifiers_applies_are' => 'Zastosowano także poniższe modyfikatory:', - 'general_search_error' => 'An error occurred while searching. Please check the log files for more information.', + 'general_search_error' => 'Wystąpił błąd podczas wyszukiwania. Aby uzyskać więcej informacji proszę sprawdzić logi aplikacji.', 'search_box' => 'Szukaj', 'search_box_intro' => 'Witamy w funkcji wyszukiwania Firefly III. Wpisz zapytanie w polu. Upewnij się, że sprawdziłeś plik pomocy, ponieważ wyszukiwanie jest dość zaawansowane.', 'search_error' => 'Błąd podczas wyszukiwania', @@ -666,7 +666,7 @@ return [ 'cannot_disable_currency_budget_limits' => 'Cannot disable :name because it is used in budget limits.', 'cannot_disable_currency_current_default' => 'Cannot disable :name because it is the current default currency.', 'cannot_disable_currency_system_fallback' => 'Cannot disable :name because it is the system default currency.', - 'disable_EUR_side_effects' => 'The Euro is the system\'s emergency fallback currency. Disabling it may have unintended side-effects and may void your warranty.', + 'disable_EUR_side_effects' => 'Euro jest awaryjną walutą w systemie. Deaktywacja może mieć nieprzewidziane skutki i może spowodować wygaśnięcie gwarancji.', 'deleted_currency' => 'Waluta :name została usunięta', 'created_currency' => 'Waluta :name została utworzona', 'could_not_store_currency' => 'Nie można zapisać nowej waluty.', @@ -688,8 +688,8 @@ return [ 'options' => 'Opcje', // budgets: - 'total_available_budget' => 'Total available budget (between :start and :end)', - 'total_available_budget_in_currency' => 'Total available budget in :currency', + 'total_available_budget' => 'Dostępny budżet (pomiędzy :start i :end)', + 'total_available_budget_in_currency' => 'Dostępny budżet w :currency', 'see_below' => 'zobacz poniżej', 'create_new_budget' => 'Utwórz nowy budżet', 'store_new_budget' => 'Zapisz nowy budżet', @@ -791,7 +791,7 @@ return [ 'asset_accounts' => 'Konta aktywów', 'asset_accounts_inactive' => 'Asset accounts (inactive)', 'expense_accounts' => 'Konta wydatków', - 'expense_accounts_inactive' => 'Expense accounts (inactive)', + 'expense_accounts_inactive' => 'Konto wydatków (nieaktywne)', 'revenue_accounts' => 'Konta przychodów', 'cash_accounts' => 'Konta gotówkowe', 'Cash account' => 'Konto gotówkowe', @@ -1490,7 +1490,7 @@ return [ 'box_spent_in_currency' => 'Wydano (:currency)', 'box_earned_in_currency' => 'Zarobiono (:currency)', 'box_budgeted_in_currency' => 'Zabudżetowano (:currency)', - 'box_sum_in_currency' => 'Sum (:currency)', + 'box_sum_in_currency' => 'Suma (:currency)', 'box_bill_paid_in_currency' => 'Zapłacone rachunki (:currency)', 'box_bill_unpaid_in_currency' => 'Niezapłacone rachunki (:currency)', 'box_left_to_spend_in_currency' => 'Możliwe do wydania (:currency)', diff --git a/resources/lang/pl_PL/form.php b/resources/lang/pl_PL/form.php index 7bfafb0f96..c1df1a4ec2 100644 --- a/resources/lang/pl_PL/form.php +++ b/resources/lang/pl_PL/form.php @@ -255,7 +255,7 @@ return [ 'withdrawal_destination_id' => 'Konto docelowe', 'deposit_source_id' => 'Konto źródłowe', - 'expected_on' => 'Expected on', - 'paid' => 'Paid', + 'expected_on' => 'Oczekiwany', + 'paid' => 'Zapłacone', ]; diff --git a/resources/lang/pl_PL/list.php b/resources/lang/pl_PL/list.php index 08eb146664..de7a8181a4 100644 --- a/resources/lang/pl_PL/list.php +++ b/resources/lang/pl_PL/list.php @@ -38,11 +38,11 @@ return [ 'active' => 'Jest aktywny?', 'percentage' => 'pct.', 'next_due' => 'Next due', - 'transaction_type' => 'Type', + 'transaction_type' => 'Typ', 'lastActivity' => 'Ostatnia aktywność', 'balanceDiff' => 'Różnica sald', 'matchesOn' => 'Dopasowanie', - 'other_meta_data' => 'Other meta data', + 'other_meta_data' => 'Inne dane meta', 'account_type' => 'Typ konta', 'created_at' => 'Utworzono', 'account' => 'Konto', @@ -109,14 +109,14 @@ return [ 'account_on_spectre' => 'Konto (Spectre)', 'account_on_ynab' => 'Konto (YNAB)', 'do_import' => 'Importuj z tego konta', - 'sepa_ct_id' => 'SEPA End to End Identifier', - 'sepa_ct_op' => 'SEPA Opposing Account Identifier', - 'sepa_db' => 'SEPA Mandate Identifier', - 'sepa_country' => 'SEPA Country', - 'sepa_cc' => 'SEPA Clearing Code', + 'sepa_ct_id' => 'Identyfikator end-to-end SEPA', + 'sepa_ct_op' => 'Identyfikator przeciwnego konta SEPA', + 'sepa_db' => 'Identyfikator mandatu SEPA', + 'sepa_country' => 'Kraj SEPA', + 'sepa_cc' => 'Kod rozliczeniowy SEPA', 'sepa_ep' => 'SEPA External Purpose', 'sepa_ci' => 'SEPA Creditor Identifier', - 'sepa_batch_id' => 'SEPA Batch ID', + 'sepa_batch_id' => 'ID paczki SEPA', 'external_id' => 'Zewnętrzne ID', 'account_at_bunq' => 'Konto bunq', 'file_name' => 'Nazwa pliku', diff --git a/resources/lang/pl_PL/validation.php b/resources/lang/pl_PL/validation.php index a376e40098..a87b92840c 100644 --- a/resources/lang/pl_PL/validation.php +++ b/resources/lang/pl_PL/validation.php @@ -45,6 +45,7 @@ return [ 'at_least_one_repetition' => 'Wymaga co najmniej jednego powtórzenia.', 'require_repeat_until' => 'Wymagana jest liczba powtórzeń lub data zakończenia (repeat_until), ale nie obie jednocześnie.', 'require_currency_info' => 'Treść tego pola jest nieprawidłowa bez informacji o walucie.', + 'not_transfer_account' => 'This account is not an account that can be used for transfers.', 'require_currency_amount' => 'Treść tego pola jest nieprawidłowa bez informacji o obcej kwocie.', 'equal_description' => 'Opis transakcji nie powinien być równy globalnemu opisowi.', 'file_invalid_mime' => 'Plik ":name" jest typu ":mime", który nie jest akceptowany jako nowy plik do przekazania.', diff --git a/resources/lang/pt_BR/validation.php b/resources/lang/pt_BR/validation.php index 63ca1235fc..e954f750f8 100644 --- a/resources/lang/pt_BR/validation.php +++ b/resources/lang/pt_BR/validation.php @@ -45,6 +45,7 @@ return [ 'at_least_one_repetition' => 'Precisa de ao menos uma repetição.', 'require_repeat_until' => 'É necessário ou um número de repetições ou uma data de término (repetir até). Não ambos.', 'require_currency_info' => 'O conteúdo deste campo é inválido sem informações de moeda.', + 'not_transfer_account' => 'This account is not an account that can be used for transfers.', 'require_currency_amount' => 'O conteúdo deste campo é inválido sem a informação de moeda estrangeira.', 'equal_description' => 'A descrição da transação não pode ser igual à descrição global.', 'file_invalid_mime' => 'Arquivo ":name" é do tipo ":mime" que não é aceito como um novo upload.', diff --git a/resources/lang/ro_RO/validation.php b/resources/lang/ro_RO/validation.php index 7a713982fc..c168d1bfc9 100644 --- a/resources/lang/ro_RO/validation.php +++ b/resources/lang/ro_RO/validation.php @@ -45,6 +45,7 @@ return [ 'at_least_one_repetition' => 'Aveți nevoie de cel puțin o repetare.', 'require_repeat_until' => 'Solicitați fie un număr de repetări, fie o dată de încheiere (repeat_until). Nu amândouă.', 'require_currency_info' => 'Conținutul acestui câmp este nevalid fără informații despre monedă.', + 'not_transfer_account' => 'This account is not an account that can be used for transfers.', 'require_currency_amount' => 'The content of this field is invalid without foreign amount information.', 'equal_description' => 'Descrierea tranzacției nu trebuie să fie egală cu descrierea globală.', 'file_invalid_mime' => 'Fișierul ":name" este de tip ":mime" și nu este acceptat ca o încărcare nouă.', diff --git a/resources/lang/ru_RU/validation.php b/resources/lang/ru_RU/validation.php index e8f3507f51..5b4130bb9c 100644 --- a/resources/lang/ru_RU/validation.php +++ b/resources/lang/ru_RU/validation.php @@ -45,6 +45,7 @@ return [ 'at_least_one_repetition' => 'Необходима как минимум одна транзакция.', 'require_repeat_until' => 'Требуется либо несколько повторений, либо конечная дата (repeat_until). Но не оба параметра разом.', 'require_currency_info' => 'Содержимое этого поля недействительно без информации о валюте.', + 'not_transfer_account' => 'This account is not an account that can be used for transfers.', 'require_currency_amount' => 'Содержимое этого поля недействительно без информации о валюте.', 'equal_description' => 'Описание транзакции не должно совпадать с глобальным описанием.', 'file_invalid_mime' => 'Файл ":name" имеет тип ":mime". Загрузка файлов такого типа невозможна.', diff --git a/resources/lang/tr_TR/validation.php b/resources/lang/tr_TR/validation.php index c1de92890a..1150d8ffec 100644 --- a/resources/lang/tr_TR/validation.php +++ b/resources/lang/tr_TR/validation.php @@ -45,6 +45,7 @@ return [ 'at_least_one_repetition' => 'En az bir tekrarı gerekir.', 'require_repeat_until' => 'Require either a number of repetitions, or an end date (repeat_until). Not both.', 'require_currency_info' => 'Bu alanın içeriği para birimi bilgileri geçersiz.', + 'not_transfer_account' => 'This account is not an account that can be used for transfers.', 'require_currency_amount' => 'The content of this field is invalid without foreign amount information.', 'equal_description' => 'İşlem açıklaması genel açıklama eşit değildir.', 'file_invalid_mime' => '":name" dosyası ":mime" türünde olup yeni bir yükleme olarak kabul edilemez.', diff --git a/resources/lang/zh_CN/validation.php b/resources/lang/zh_CN/validation.php index 7ca39b9d6f..1771419dca 100644 --- a/resources/lang/zh_CN/validation.php +++ b/resources/lang/zh_CN/validation.php @@ -45,6 +45,7 @@ return [ 'at_least_one_repetition' => '至少需要一次重复。', 'require_repeat_until' => '仅需重复次数或结束日期 (repeat_until) 即可,不需两者均备。', 'require_currency_info' => '无货币资讯,此栏位内容无效。', + 'not_transfer_account' => 'This account is not an account that can be used for transfers.', 'require_currency_amount' => 'The content of this field is invalid without foreign amount information.', 'equal_description' => '交易描述不应等同全域描述。', 'file_invalid_mime' => '档案 ":name" 的类型为 ":mime",系不被允许上载的类型。', diff --git a/resources/lang/zh_TW/validation.php b/resources/lang/zh_TW/validation.php index cad2fe0e00..36f53eca9d 100644 --- a/resources/lang/zh_TW/validation.php +++ b/resources/lang/zh_TW/validation.php @@ -45,6 +45,7 @@ return [ 'at_least_one_repetition' => '至少需要一次重複。', 'require_repeat_until' => '要嘛重複次數,要嘛結束日期 (repeat_until),須二擇其一。', 'require_currency_info' => '此欄位內容須要貨幣資訊。', + 'not_transfer_account' => 'This account is not an account that can be used for transfers.', 'require_currency_amount' => '此欄位內容須要外幣資訊。', 'equal_description' => '交易描述不應等同全域描述。', 'file_invalid_mime' => '檔案 ":name" 類型為 ":mime",不允許上載。', diff --git a/routes/api.php b/routes/api.php index 6629f37eab..0bfe33e91c 100644 --- a/routes/api.php +++ b/routes/api.php @@ -342,6 +342,7 @@ Route::group( // Attachment API routes: Route::get('transactions', ['uses' => 'TransactionController@search', 'as' => 'transactions']); Route::get('accounts', ['uses' => 'AccountController@search', 'as' => 'accounts']); + Route::get('transfers', ['uses' => 'TransferController@search', 'as' => 'transfers']); } );