mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-10-15 08:35:00 +00:00
User can submit new journal through API.
This commit is contained in:
@@ -27,6 +27,7 @@ namespace FireflyIII\Api\V1\Requests;
|
||||
use FireflyIII\Rules\BelongsUser;
|
||||
use FireflyIII\Rules\IsBoolean;
|
||||
use FireflyIII\Rules\IsDateOrTime;
|
||||
use FireflyIII\Support\NullArrayObject;
|
||||
use FireflyIII\Validation\TransactionValidation;
|
||||
use Illuminate\Validation\Validator;
|
||||
|
||||
@@ -59,34 +60,8 @@ class TransactionRequest extends Request
|
||||
public function getAll(): array
|
||||
{
|
||||
$data = [
|
||||
'type' => $this->string('type'),
|
||||
'date' => $this->dateTime('date'),
|
||||
'description' => $this->string('description'),
|
||||
'piggy_bank_id' => $this->integer('piggy_bank_id'),
|
||||
'piggy_bank_name' => $this->string('piggy_bank_name'),
|
||||
'bill_id' => $this->integer('bill_id'),
|
||||
'bill_name' => $this->string('bill_name'),
|
||||
'tags' => explode(',', $this->string('tags')),
|
||||
'notes' => $this->string('notes'),
|
||||
'sepa-cc' => $this->string('sepa_cc'),
|
||||
'sepa-ct-op' => $this->string('sepa_ct_op'),
|
||||
'sepa-ct-id' => $this->string('sepa_ct_id'),
|
||||
'sepa-db' => $this->string('sepa_db'),
|
||||
'sepa-country' => $this->string('sepa_country'),
|
||||
'sepa-ep' => $this->string('sepa_ep'),
|
||||
'sepa-ci' => $this->string('sepa_ci'),
|
||||
'sepa-batch-id' => $this->string('sepa_batch_id'),
|
||||
'interest_date' => $this->date('interest_date'),
|
||||
'book_date' => $this->date('book_date'),
|
||||
'process_date' => $this->date('process_date'),
|
||||
'due_date' => $this->date('due_date'),
|
||||
'payment_date' => $this->date('payment_date'),
|
||||
'invoice_date' => $this->date('invoice_date'),
|
||||
'internal_reference' => $this->string('internal_reference'),
|
||||
'bunq_payment_id' => $this->string('bunq_payment_id'),
|
||||
'external_id' => $this->string('external_id'),
|
||||
'original-source' => sprintf('ff3-v%s|api-v%s', config('firefly.version'), config('firefly.api_version')),
|
||||
'transactions' => $this->getTransactionData(),
|
||||
'group_title' => $this->string('group_title'),
|
||||
'transactions' => $this->getTransactionData(),
|
||||
];
|
||||
|
||||
return $data;
|
||||
@@ -101,61 +76,76 @@ class TransactionRequest extends Request
|
||||
public function rules(): array
|
||||
{
|
||||
$rules = [
|
||||
// basic fields for journal:
|
||||
'type' => 'required|in:withdrawal,deposit,transfer,opening-balance,reconciliation',
|
||||
'description' => 'between:1,255',
|
||||
'date' => ['required', new IsDateOrTime],
|
||||
'piggy_bank_id' => ['numeric', 'nullable', 'mustExist:piggy_banks,id', new BelongsUser],
|
||||
'piggy_bank_name' => ['between:1,255', 'nullable', new BelongsUser],
|
||||
'bill_id' => ['numeric', 'nullable', 'mustExist:bills,id', new BelongsUser],
|
||||
'bill_name' => ['between:1,255', 'nullable', new BelongsUser],
|
||||
'tags' => 'between:1,255',
|
||||
|
||||
// then, custom fields for journal
|
||||
'notes' => 'min:1,max:50000|nullable',
|
||||
|
||||
// SEPA fields:
|
||||
'sepa_cc' => 'min:1,max:255|nullable',
|
||||
'sepa_ct_op' => 'min:1,max:255|nullable',
|
||||
'sepa_ct_id' => 'min:1,max:255|nullable',
|
||||
'sepa_db' => 'min:1,max:255|nullable',
|
||||
'sepa_country' => 'min:1,max:255|nullable',
|
||||
'sepa_ep' => 'min:1,max:255|nullable',
|
||||
'sepa_ci' => 'min:1,max:255|nullable',
|
||||
'sepa_batch_id' => 'min:1,max:255|nullable',
|
||||
|
||||
// dates
|
||||
'interest_date' => 'date|nullable',
|
||||
'book_date' => 'date|nullable',
|
||||
'process_date' => 'date|nullable',
|
||||
'due_date' => 'date|nullable',
|
||||
'payment_date' => 'date|nullable',
|
||||
'invoice_date' => 'date|nullable',
|
||||
'internal_reference' => 'min:1,max:255|nullable',
|
||||
'bunq_payment_id' => 'min:1,max:255|nullable',
|
||||
'external_id' => 'min:1,max:255|nullable',
|
||||
// basic fields for group:
|
||||
'group_title' => 'between:1,255',
|
||||
|
||||
// transaction rules (in array for splits):
|
||||
'transactions.*.amount' => 'required|numeric|more:0',
|
||||
'transactions.*.description' => 'nullable|between:1,255',
|
||||
'transactions.*.type' => 'required|in:withdrawal,deposit,transfer,opening-balance,reconciliation',
|
||||
'transactions.*.date' => ['required', new IsDateOrTime],
|
||||
|
||||
// currency info
|
||||
'transactions.*.currency_id' => 'numeric|exists:transaction_currencies,id',
|
||||
'transactions.*.currency_code' => 'min:3|max:3|exists:transaction_currencies,code',
|
||||
'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',
|
||||
|
||||
// amount
|
||||
'transactions.*.amount' => 'required|numeric|more:0',
|
||||
'transactions.*.foreign_amount' => 'numeric|more:0',
|
||||
|
||||
// description
|
||||
'transactions.*.description' => 'nullable|between:1,255',
|
||||
|
||||
// source of transaction
|
||||
'transactions.*.source_id' => ['numeric', 'nullable', new BelongsUser],
|
||||
'transactions.*.source_name' => 'between:1,255|nullable',
|
||||
|
||||
// destination of transaction
|
||||
'transactions.*.destination_id' => ['numeric', 'nullable', new BelongsUser],
|
||||
'transactions.*.destination_name' => 'between:1,255|nullable',
|
||||
|
||||
// budget, category, bill and piggy
|
||||
'transactions.*.budget_id' => ['mustExist:budgets,id', new BelongsUser],
|
||||
'transactions.*.budget_name' => ['between:1,255', 'nullable', new BelongsUser],
|
||||
'transactions.*.category_id' => ['mustExist:categories,id', new BelongsUser],
|
||||
'transactions.*.category_name' => 'between:1,255|nullable',
|
||||
'transactions.*.bill_id' => ['numeric', 'nullable', 'mustExist:bills,id', new BelongsUser],
|
||||
'transactions.*.bill_name' => ['between:1,255', 'nullable', new BelongsUser],
|
||||
'transactions.*.piggy_bank_id' => ['numeric', 'nullable', 'mustExist:piggy_banks,id', new BelongsUser],
|
||||
'transactions.*.piggy_bank_name' => ['between:1,255', 'nullable', new BelongsUser],
|
||||
|
||||
// other interesting fields
|
||||
'transactions.*.reconciled' => [new IsBoolean],
|
||||
'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.*.notes' => 'min:1,max:50000|nullable',
|
||||
'transactions.*.tags' => 'between:1,255',
|
||||
|
||||
// meta info fields
|
||||
'transactions.*.internal_reference' => 'min:1,max:255|nullable',
|
||||
'transactions.*.external_id' => 'min:1,max:255|nullable',
|
||||
'transactions.*.recurrence_id' => 'min:1,max:255|nullable',
|
||||
'transactions.*.bunq_payment_id' => 'min:1,max:255|nullable',
|
||||
|
||||
// SEPA fields:
|
||||
'transactions.*.sepa_cc' => 'min:1,max:255|nullable',
|
||||
'transactions.*.sepa_ct_op' => 'min:1,max:255|nullable',
|
||||
'transactions.*.sepa_ct_id' => 'min:1,max:255|nullable',
|
||||
'transactions.*.sepa_db' => 'min:1,max:255|nullable',
|
||||
'transactions.*.sepa_country' => 'min:1,max:255|nullable',
|
||||
'transactions.*.sepa_ep' => 'min:1,max:255|nullable',
|
||||
'transactions.*.sepa_ci' => 'min:1,max:255|nullable',
|
||||
'transactions.*.sepa_batch_id' => 'min:1,max:255|nullable',
|
||||
|
||||
// dates
|
||||
'transactions.*.interest_date' => 'date|nullable',
|
||||
'transactions.*.book_date' => 'date|nullable',
|
||||
'transactions.*.process_date' => 'date|nullable',
|
||||
'transactions.*.due_date' => 'date|nullable',
|
||||
'transactions.*.payment_date' => 'date|nullable',
|
||||
'transactions.*.invoice_date' => 'date|nullable',
|
||||
];
|
||||
|
||||
if ('PUT' === $this->method()) {
|
||||
unset($rules['type'], $rules['piggy_bank_id'], $rules['piggy_bank_name']);
|
||||
unset($rules['transactions.*.type'], $rules['transactions.*.piggy_bank_id'], $rules['transactions.*.piggy_bank_name']);
|
||||
}
|
||||
|
||||
return $rules;
|
||||
@@ -174,13 +164,28 @@ class TransactionRequest extends Request
|
||||
{
|
||||
$validator->after(
|
||||
function (Validator $validator) {
|
||||
// must submit at least one transaction.
|
||||
$this->validateOneTransaction($validator);
|
||||
|
||||
// all journals must have a description
|
||||
$this->validateDescriptions($validator);
|
||||
$this->validateJournalDescription($validator);
|
||||
$this->validateSplitDescriptions($validator);
|
||||
|
||||
// all transaction types must be equal:
|
||||
$this->validateTransactionTypes($validator);
|
||||
|
||||
// validate foreign currency info
|
||||
$this->validateForeignCurrencyInformation($validator);
|
||||
|
||||
|
||||
|
||||
// validate all account info
|
||||
$this->validateAccountInformation($validator);
|
||||
|
||||
// make sure all splits have valid source + dest info
|
||||
$this->validateSplitAccounts($validator);
|
||||
|
||||
// the group must have a description if > 1 journal.
|
||||
$this->validateGroupDescription($validator);
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -195,28 +200,88 @@ class TransactionRequest extends Request
|
||||
private function getTransactionData(): array
|
||||
{
|
||||
$return = [];
|
||||
/**
|
||||
* @var int $index
|
||||
* @var array $transaction
|
||||
*/
|
||||
foreach ($this->get('transactions') as $index => $transaction) {
|
||||
$object = new NullArrayObject($transaction);
|
||||
$return[] = [
|
||||
'amount' => $transaction['amount'],
|
||||
'description' => $transaction['description'] ?? null,
|
||||
'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,
|
||||
'reconciled' => $this->convertBoolean((string)($transaction['reconciled'] ?? 'false')),
|
||||
'identifier' => $index,
|
||||
// $this->dateFromValue($object[''])
|
||||
'type' => $this->stringFromValue($object['type']),
|
||||
'date' => $this->dateFromValue($object['date']),
|
||||
'currency_id' => $this->integerFromValue($object['currency_id']),
|
||||
'currency_code' => $this->stringFromValue($object['currency_code']),
|
||||
|
||||
// foreign currency info:
|
||||
'foreign_currency_id' => $this->integerFromValue((string)$object['foreign_currency_id']),
|
||||
'foreign_currency_code' => $this->stringFromValue($object['foreign_currency_code']),
|
||||
|
||||
// amount and foreign amount. Cannot be 0.
|
||||
'amount' => $this->stringFromValue($object['amount']),
|
||||
'foreign_amount' => $this->stringFromValue($object['foreign_amount']),
|
||||
|
||||
// description.
|
||||
'description' => $this->stringFromValue($object['description']),
|
||||
|
||||
// source of transaction. If everything is null, assume cash account.
|
||||
'source_id' => $this->integerFromValue((string)$object['source_id']),
|
||||
'source_name' => $this->stringFromValue($object['source_name']),
|
||||
|
||||
// destination of transaction. If everything is null, assume cash account.
|
||||
'destination_id' => $this->integerFromValue((string)$object['destination_id']),
|
||||
'destination_name' => $this->stringFromValue($object['destination_name']),
|
||||
|
||||
// budget info
|
||||
'budget_id' => $this->integerFromValue((string)$object['budget_id']),
|
||||
'budget_name' => $this->stringFromValue($object['budget_name']),
|
||||
|
||||
// category info
|
||||
'category_id' => $this->integerFromValue((string)$object['category_id']),
|
||||
'category_name' => $this->stringFromValue($object['category_name']),
|
||||
|
||||
// journal bill reference. Optional. Will only work for withdrawals
|
||||
'bill_id' => $this->integerFromValue((string)$object['bill_id']),
|
||||
'bill_name' => $this->stringFromValue($object['bill_name']),
|
||||
|
||||
// piggy bank reference. Optional. Will only work for transfers
|
||||
'piggy_bank_id' => $this->integerFromValue((string)$object['piggy_bank_id']),
|
||||
'piggy_bank_name' => $this->stringFromValue($object['piggy_bank_name']),
|
||||
|
||||
// some other interesting properties
|
||||
'reconciled' => $this->convertBoolean((string)$object['reconciled']),
|
||||
'notes' => $this->stringFromValue($object['notes']),
|
||||
'tags' => $this->arrayFromValue($object['tags']),
|
||||
|
||||
// all custom fields:
|
||||
'internal_reference' => $this->stringFromValue($object['internal_reference']),
|
||||
'external_id' => $this->stringFromValue($object['external_id']),
|
||||
'original_source' => sprintf('ff3-v%s|api-v%s', config('firefly.version'), config('firefly.api_version')),
|
||||
'recurrence_id' => $this->integerFromValue($object['recurrence_id']),
|
||||
'bunq_payment_id' => $this->stringFromValue($object['bunq_payment_id']),
|
||||
|
||||
'sepa_cc' => $this->stringFromValue($object['sepa_cc']),
|
||||
'sepa_ct_op' => $this->stringFromValue($object['sepa_ct_op']),
|
||||
'sepa_ct_id' => $this->stringFromValue($object['sepa_ct_id']),
|
||||
'sepa_db' => $this->stringFromValue($object['sepa_db']),
|
||||
'sepa_country' => $this->stringFromValue($object['sepa_country']),
|
||||
'sepa_ep' => $this->stringFromValue($object['sepa_ep']),
|
||||
'sepa_ci' => $this->stringFromValue($object['sepa_ci']),
|
||||
'sepa_batch_id' => $this->stringFromValue($object['sepa_batch_id']),
|
||||
|
||||
|
||||
// custom date fields. Must be Carbon objects. Presence is optional.
|
||||
'interest_date' => $this->dateFromValue($object['interest_date']),
|
||||
'book_date' => $this->dateFromValue($object['book_date']),
|
||||
'process_date' => $this->dateFromValue($object['process_date']),
|
||||
'due_date' => $this->dateFromValue($object['due_date']),
|
||||
'payment_date' => $this->dateFromValue($object['payment_date']),
|
||||
'invoice_date' => $this->dateFromValue($object['invoice_date']),
|
||||
|
||||
];
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
}
|
||||
|
87
app/Console/Commands/Correction/RenameMetaFields.php
Normal file
87
app/Console/Commands/Correction/RenameMetaFields.php
Normal file
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
/**
|
||||
* RenameMetaFields.php
|
||||
* Copyright (c) 2019 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/>.
|
||||
*/
|
||||
|
||||
namespace FireflyIII\Console\Commands\Correction;
|
||||
|
||||
use DB;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
/**
|
||||
* Class RenameMetaFields
|
||||
*/
|
||||
class RenameMetaFields extends Command
|
||||
{
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Rename changed meta fields.';
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'firefly-iii:rename-meta-fields';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
$start = microtime(true);
|
||||
|
||||
$changes = [
|
||||
'original-source' => 'original_source',
|
||||
'importHash' => 'import_hash',
|
||||
'importHashV2' => 'import_hash_v2',
|
||||
'sepa-cc' => 'sepa_cc',
|
||||
'sepa-ct-op' => 'sepa_ct_op',
|
||||
'sepa-ct-id' => 'sepa_ct_id',
|
||||
'sepa-db' => 'sepa_db',
|
||||
'sepa-country' => 'sepa_country',
|
||||
'sepa-ep' => 'sepa_ep',
|
||||
'sepa-ci' => 'sepa_ci',
|
||||
'sepa-batch-id' => 'sepa_batch_id',
|
||||
];
|
||||
foreach ($changes as $original => $update) {
|
||||
$this->rename($original, $update);
|
||||
}
|
||||
|
||||
$end = round(microtime(true) - $start, 2);
|
||||
$this->info(sprintf('Renamed meta fields in %s seconds', $end));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $original
|
||||
* @param string $update
|
||||
*/
|
||||
private function rename(string $original, string $update): void
|
||||
{
|
||||
DB::table('journal_meta')
|
||||
->where('name', '=', $original)
|
||||
->update(['name' => $update]);
|
||||
}
|
||||
}
|
@@ -240,20 +240,20 @@ class MigrateToGroups extends Command
|
||||
$notes = $this->journalRepository->getNoteText($journal);
|
||||
$tags = $this->journalRepository->getTags($journal);
|
||||
$internalRef = $this->journalRepository->getMetaField($journal, 'internal-reference');
|
||||
$sepaCC = $this->journalRepository->getMetaField($journal, 'sepa-cc');
|
||||
$sepaCtOp = $this->journalRepository->getMetaField($journal, 'sepa-ct-op');
|
||||
$sepaCtId = $this->journalRepository->getMetaField($journal, 'sepa-ct-id');
|
||||
$sepaDb = $this->journalRepository->getMetaField($journal, 'sepa-db');
|
||||
$sepaCountry = $this->journalRepository->getMetaField($journal, 'sepa-country');
|
||||
$sepaEp = $this->journalRepository->getMetaField($journal, 'sepa-ep');
|
||||
$sepaCi = $this->journalRepository->getMetaField($journal, 'sepa-ci');
|
||||
$sepaBatchId = $this->journalRepository->getMetaField($journal, 'sepa-batch-id');
|
||||
$sepaCC = $this->journalRepository->getMetaField($journal, 'sepa_cc');
|
||||
$sepaCtOp = $this->journalRepository->getMetaField($journal, 'sepa_ct_op');
|
||||
$sepaCtId = $this->journalRepository->getMetaField($journal, 'sepa_ct_id');
|
||||
$sepaDb = $this->journalRepository->getMetaField($journal, 'sepa_db');
|
||||
$sepaCountry = $this->journalRepository->getMetaField($journal, 'sepa_country');
|
||||
$sepaEp = $this->journalRepository->getMetaField($journal, 'sepa_ep');
|
||||
$sepaCi = $this->journalRepository->getMetaField($journal, 'sepa_ci');
|
||||
$sepaBatchId = $this->journalRepository->getMetaField($journal, 'sepa_batch_id');
|
||||
$externalId = $this->journalRepository->getMetaField($journal, 'external-id');
|
||||
$originalSource = $this->journalRepository->getMetaField($journal, 'original-source');
|
||||
$recurrenceId = $this->journalRepository->getMetaField($journal, 'recurrence_id');
|
||||
$bunq = $this->journalRepository->getMetaField($journal, 'bunq_payment_id');
|
||||
$hash = $this->journalRepository->getMetaField($journal, 'importHash');
|
||||
$hashTwo = $this->journalRepository->getMetaField($journal, 'importHashV2');
|
||||
$hash = $this->journalRepository->getMetaField($journal, 'import_hash');
|
||||
$hashTwo = $this->journalRepository->getMetaField($journal, 'import_hash_v2');
|
||||
$interestDate = $this->journalRepository->getMetaDate($journal, 'interest_date');
|
||||
$bookDate = $this->journalRepository->getMetaDate($journal, 'book_date');
|
||||
$processDate = $this->journalRepository->getMetaDate($journal, 'process_date');
|
||||
@@ -293,20 +293,20 @@ class MigrateToGroups extends Command
|
||||
'notes' => $notes,
|
||||
'tags' => $tags,
|
||||
'internal_reference' => $internalRef,
|
||||
'sepa-cc' => $sepaCC,
|
||||
'sepa-ct-op' => $sepaCtOp,
|
||||
'sepa-ct-id' => $sepaCtId,
|
||||
'sepa-db' => $sepaDb,
|
||||
'sepa-country' => $sepaCountry,
|
||||
'sepa-ep' => $sepaEp,
|
||||
'sepa-ci' => $sepaCi,
|
||||
'sepa-batch-id' => $sepaBatchId,
|
||||
'sepa_cc' => $sepaCC,
|
||||
'sepa_ct_op' => $sepaCtOp,
|
||||
'sepa_ct_id' => $sepaCtId,
|
||||
'sepa_db' => $sepaDb,
|
||||
'sepa_country' => $sepaCountry,
|
||||
'sepa_ep' => $sepaEp,
|
||||
'sepa_ci' => $sepaCi,
|
||||
'sepa_batch_id' => $sepaBatchId,
|
||||
'external_id' => $externalId,
|
||||
'original-source' => $originalSource,
|
||||
'recurrence_id' => $recurrenceId,
|
||||
'bunq_payment_id' => $bunq,
|
||||
'importHash' => $hash,
|
||||
'importHashV2' => $hashTwo,
|
||||
'import_hash' => $hash,
|
||||
'import_hash_v2' => $hashTwo,
|
||||
'interest_date' => $interestDate,
|
||||
'book_date' => $bookDate,
|
||||
'process_date' => $processDate,
|
||||
|
@@ -31,10 +31,10 @@ use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Support\NullArrayObject;
|
||||
use FireflyIII\User;
|
||||
use FireflyIII\Validation\AccountValidator;
|
||||
use Illuminate\Database\QueryException;
|
||||
use Illuminate\Support\Collection;
|
||||
use Log;
|
||||
@@ -46,6 +46,8 @@ class TransactionFactory
|
||||
{
|
||||
/** @var AccountRepositoryInterface */
|
||||
private $accountRepository;
|
||||
/** @var AccountValidator */
|
||||
private $accountValidator;
|
||||
/** @var TransactionJournal */
|
||||
private $journal;
|
||||
/** @var User */
|
||||
@@ -60,6 +62,7 @@ class TransactionFactory
|
||||
Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this)));
|
||||
}
|
||||
$this->accountRepository = app(AccountRepositoryInterface::class);
|
||||
$this->accountValidator = app(AccountValidator::class);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -110,16 +113,17 @@ class TransactionFactory
|
||||
*/
|
||||
public function createPair(NullArrayObject $data, TransactionCurrency $currency, ?TransactionCurrency $foreignCurrency): Collection
|
||||
{
|
||||
$sourceAccount = $this->getAccount('source', $data['source'], (int)$data['source_id'], $data['source_name']);
|
||||
$destinationAccount = $this->getAccount('destination', $data['destination'], (int)$data['destination_id'], $data['destination_name']);
|
||||
$amount = $this->getAmount($data['amount']);
|
||||
$foreignAmount = $this->getForeignAmount($data['foreign_amount']);
|
||||
// validate source and destination using a new Validator.
|
||||
$this->validateAccounts($data);
|
||||
|
||||
$this->makeDramaOverAccountTypes($sourceAccount, $destinationAccount);
|
||||
// create or get source and destination accounts:
|
||||
$sourceAccount = $this->getAccount('source', (int)$data['source_id'], $data['source_name']);
|
||||
$destinationAccount = $this->getAccount('destination', (int)$data['destination_id'], $data['destination_name']);
|
||||
|
||||
|
||||
$one = $this->create($sourceAccount, $currency, app('steam')->negative($amount));
|
||||
$two = $this->create($destinationAccount, $currency, app('steam')->positive($amount));
|
||||
$amount = $this->getAmount($data['amount']);
|
||||
$foreignAmount = $this->getForeignAmount($data['foreign_amount']);
|
||||
$one = $this->create($sourceAccount, $currency, app('steam')->negative($amount));
|
||||
$two = $this->create($destinationAccount, $currency, app('steam')->positive($amount));
|
||||
|
||||
$one->reconciled = $data['reconciled'] ?? false;
|
||||
$two->reconciled = $data['reconciled'] ?? false;
|
||||
@@ -128,8 +132,8 @@ class TransactionFactory
|
||||
if (null !== $foreignCurrency) {
|
||||
$one->foreign_currency_id = $foreignCurrency->id;
|
||||
$two->foreign_currency_id = $foreignCurrency->id;
|
||||
$one->foreign_amount = $foreignAmount;
|
||||
$two->foreign_amount = $foreignAmount;
|
||||
$one->foreign_amount = app('steam')->negative($foreignAmount);
|
||||
$two->foreign_amount = app('steam')->positive($foreignAmount);
|
||||
}
|
||||
|
||||
|
||||
@@ -141,18 +145,21 @@ class TransactionFactory
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $direction
|
||||
* @param Account|null $source
|
||||
* @param int|null $sourceId
|
||||
* @param string|null $sourceName
|
||||
* @param string $direction
|
||||
* @param int|null $accountId
|
||||
* @param string|null $accountName
|
||||
*
|
||||
* @return Account
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function getAccount(string $direction, ?Account $source, ?int $sourceId, ?string $sourceName): Account
|
||||
public function getAccount(string $direction, ?int $accountId, ?string $accountName): Account
|
||||
{
|
||||
Log::debug(sprintf('Now in getAccount(%s)', $direction));
|
||||
Log::debug(sprintf('Parameters: ((account), %s, %s)', var_export($sourceId, true), var_export($sourceName, true)));
|
||||
// some debug logging:
|
||||
Log::debug(sprintf('Now in getAccount(%s, %d, %s)', $direction, $accountId, $accountName));
|
||||
|
||||
// final result:
|
||||
$result = null;
|
||||
|
||||
// expected type of source account, in order of preference
|
||||
/** @var array $array */
|
||||
$array = config('firefly.expected_source_types');
|
||||
@@ -161,64 +168,62 @@ class TransactionFactory
|
||||
|
||||
// and now try to find it, based on the type of transaction.
|
||||
$transactionType = $this->journal->transactionType->type;
|
||||
Log::debug(
|
||||
sprintf(
|
||||
'Based on the fact that the transaction is a %s, the %s account should be in: %s', $transactionType, $direction,
|
||||
implode(', ', $expectedTypes[$transactionType])
|
||||
)
|
||||
);
|
||||
$message = 'Based on the fact that the transaction is a %s, the %s account should be in: %s';
|
||||
Log::debug(sprintf($message, $transactionType, $direction, implode(', ', $expectedTypes[$transactionType])));
|
||||
|
||||
// first attempt, check the "source" object.
|
||||
if (null !== $source && $source->user_id === $this->user->id && \in_array($source->accountType->type, $expectedTypes[$transactionType], true)) {
|
||||
Log::debug(sprintf('Found "account" object for %s: #%d, %s', $direction, $source->id, $source->name));
|
||||
|
||||
return $source;
|
||||
}
|
||||
|
||||
// second attempt, find by ID.
|
||||
if (null !== $sourceId) {
|
||||
$source = $this->accountRepository->findNull($sourceId);
|
||||
if (null !== $source && \in_array($source->accountType->type, $expectedTypes[$transactionType], true)) {
|
||||
// first attempt, find by ID.
|
||||
if (null !== $accountId) {
|
||||
$search = $this->accountRepository->findNull($accountId);
|
||||
if (null !== $search && in_array($search->accountType->type, $expectedTypes[$transactionType], true)) {
|
||||
Log::debug(
|
||||
sprintf('Found "account_id" object for %s: #%d, "%s" of type %s', $direction, $source->id, $source->name, $source->accountType->type)
|
||||
sprintf('Found "account_id" object for %s: #%d, "%s" of type %s', $direction, $search->id, $search->name, $search->accountType->type)
|
||||
);
|
||||
|
||||
return $source;
|
||||
$result = $search;
|
||||
}
|
||||
}
|
||||
|
||||
// third attempt, find by name.
|
||||
if (null !== $sourceName) {
|
||||
// second attempt, find by name.
|
||||
if (null === $result && null !== $accountName) {
|
||||
Log::debug('Found nothing by account ID.');
|
||||
// find by preferred type.
|
||||
$source = $this->accountRepository->findByName($sourceName, [$expectedTypes[$transactionType][0]]);
|
||||
// or any type.
|
||||
$source = $source ?? $this->accountRepository->findByName($sourceName, $expectedTypes[$transactionType]);
|
||||
$source = $this->accountRepository->findByName($accountName, [$expectedTypes[$transactionType][0]]);
|
||||
// or any expected type.
|
||||
$source = $source ?? $this->accountRepository->findByName($accountName, $expectedTypes[$transactionType]);
|
||||
|
||||
if (null !== $source) {
|
||||
Log::debug(sprintf('Found "account_name" object for %s: #%d, %s', $direction, $source->id, $source->name));
|
||||
|
||||
return $source;
|
||||
$result = $source;
|
||||
}
|
||||
}
|
||||
if (null === $sourceName && \in_array(AccountType::CASH, $expectedTypes[$transactionType], true)) {
|
||||
return $this->accountRepository->getCashAccount();
|
||||
}
|
||||
$sourceName = $sourceName ?? '(no name)';
|
||||
// final attempt, create it.
|
||||
$preferredType = $expectedTypes[$transactionType][0];
|
||||
if (AccountType::ASSET === $preferredType) {
|
||||
throw new FireflyException(sprintf('TransactionFactory: Cannot create asset account with ID #%d or name "%s".', $sourceId, $sourceName));
|
||||
|
||||
// return cash account.
|
||||
if (null === $result && null === $accountName
|
||||
&& in_array(AccountType::CASH, $expectedTypes[$transactionType], true)) {
|
||||
$result = $this->accountRepository->getCashAccount();
|
||||
}
|
||||
|
||||
return $this->accountRepository->store(
|
||||
[
|
||||
'account_type_id' => null,
|
||||
'accountType' => $preferredType,
|
||||
'name' => $sourceName,
|
||||
'active' => true,
|
||||
'iban' => null,
|
||||
]
|
||||
);
|
||||
// return new account.
|
||||
if (null === $result) {
|
||||
$accountName = $accountName ?? '(no name)';
|
||||
// final attempt, create it.
|
||||
$preferredType = $expectedTypes[$transactionType][0];
|
||||
if (AccountType::ASSET === $preferredType) {
|
||||
throw new FireflyException(sprintf('TransactionFactory: Cannot create asset account with ID #%d or name "%s".', $accountId, $accountName));
|
||||
}
|
||||
|
||||
$result = $this->accountRepository->store(
|
||||
[
|
||||
'account_type_id' => null,
|
||||
'accountType' => $preferredType,
|
||||
'name' => $accountName,
|
||||
'active' => true,
|
||||
'iban' => null,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -246,6 +251,7 @@ class TransactionFactory
|
||||
*/
|
||||
public function getForeignAmount(?string $amount): ?string
|
||||
{
|
||||
$result = null;
|
||||
if (null === $amount) {
|
||||
Log::debug('No foreign amount info in array. Return NULL');
|
||||
|
||||
@@ -266,31 +272,6 @@ class TransactionFactory
|
||||
return $amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will throw a Firefly III Exception of the source and destination account types are not OK.
|
||||
*
|
||||
* @throws FireflyException
|
||||
*
|
||||
* @param Account $source
|
||||
* @param Account $destination
|
||||
*/
|
||||
public function makeDramaOverAccountTypes(Account $source, Account $destination): void
|
||||
{
|
||||
// if the source is X, then Y is allowed as destination.
|
||||
$combinations = config('firefly.source_dests');
|
||||
$sourceType = $source->accountType->type;
|
||||
$destType = $destination->accountType->type;
|
||||
$journalType = $this->journal->transactionType->type;
|
||||
$allowed = $combinations[$journalType][$sourceType] ?? [];
|
||||
if (!\in_array($destType, $allowed, true)) {
|
||||
throw new FireflyException(
|
||||
sprintf(
|
||||
'Journal of type "%s" has a source account of type "%s" and cannot accept a "%s"-account as destination, but only accounts of: %s', $journalType, $sourceType,
|
||||
$destType, implode(', ', $combinations[$journalType][$sourceType])
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
@@ -308,4 +289,33 @@ class TransactionFactory
|
||||
$this->user = $user;
|
||||
$this->accountRepository->setUser($user);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param NullArrayObject $data
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function validateAccounts(NullArrayObject $data): void
|
||||
{
|
||||
$transactionType = $data['type'] ?? 'invalid';
|
||||
$this->accountValidator->setTransactionType($transactionType);
|
||||
|
||||
// validate source account.
|
||||
$sourceId = isset($data['source_id']) ? (int)$data['source_id'] : null;
|
||||
$sourceName = $data['source_name'] ?? null;
|
||||
$validSource = $this->accountValidator->validateSource($sourceId, $sourceName);
|
||||
|
||||
// do something with result:
|
||||
if (false === $validSource) {
|
||||
throw new FireflyException($this->accountValidator->sourceError);
|
||||
}
|
||||
// validate destination account
|
||||
$destinationId = isset($data['destination_id']) ? (int)$data['destination_id'] : null;
|
||||
$destinationName = $data['destination_name'] ?? null;
|
||||
$validDestination = $this->accountValidator->validateDestination($destinationId, $destinationName);
|
||||
// do something with result:
|
||||
if (false === $validDestination) {
|
||||
throw new FireflyException($this->accountValidator->destError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
84
app/Factory/TransactionGroupFactory.php
Normal file
84
app/Factory/TransactionGroupFactory.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
/**
|
||||
* TransactionGroupFactory.php
|
||||
* Copyright (c) 2019 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\Factory;
|
||||
|
||||
use Exception;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\TransactionGroup;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\User;
|
||||
|
||||
/**
|
||||
* Class TransactionGroupFactory
|
||||
*/
|
||||
class TransactionGroupFactory
|
||||
{
|
||||
/** @var TransactionJournalFactory */
|
||||
private $journalFactory;
|
||||
/** @var User The user */
|
||||
private $user;
|
||||
|
||||
/**
|
||||
* TransactionGroupFactory constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->journalFactory = app(TransactionJournalFactory::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a new transaction journal.
|
||||
*
|
||||
* @param array $data
|
||||
*
|
||||
* @return TransactionGroup
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function create(array $data): TransactionGroup
|
||||
{
|
||||
$this->journalFactory->setUser($this->user);
|
||||
$collection = $this->journalFactory->create($data);
|
||||
$title = $data['group_title'] ?? null;
|
||||
/** @var TransactionJournal $first */
|
||||
$first = $collection->first();
|
||||
$group = new TransactionGroup;
|
||||
$group->user()->associate($first->user);
|
||||
$group->title = $title ?? $first->description;
|
||||
$group->save();
|
||||
|
||||
$group->transactionJournals()->saveMany($collection);
|
||||
|
||||
return $group;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the user.
|
||||
*
|
||||
* @param User $user
|
||||
*/
|
||||
public function setUser(User $user): void
|
||||
{
|
||||
$this->user = $user;
|
||||
}
|
||||
}
|
@@ -26,9 +26,9 @@ namespace FireflyIII\Factory;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Exception;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\Note;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Models\TransactionGroup;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
|
||||
@@ -77,14 +77,25 @@ class TransactionJournalFactory
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->fields = ['sepa-cc', 'sepa-ct-op', 'sepa-ct-id', 'sepa-db', 'sepa-country', 'sepa-ep', 'sepa-ci', 'interest_date', 'book_date', 'process_date',
|
||||
'due_date', 'recurrence_id', 'payment_date', 'invoice_date', 'internal_reference', 'bunq_payment_id', 'importHash', 'importHashV2',
|
||||
'external_id', 'sepa-batch-id', 'original-source'];
|
||||
$this->fields = [
|
||||
// sepa
|
||||
'sepa_cc', 'sepa_ct_op', 'sepa_ct_id',
|
||||
'sepa_db', 'sepa_country', 'sepa_ep',
|
||||
'sepa_ci', 'sepa_batch_id',
|
||||
|
||||
// dates
|
||||
'interest_date', 'book_date', 'process_date',
|
||||
'due_date', 'payment_date', 'invoice_date',
|
||||
|
||||
// others
|
||||
'recurrence_id', 'internal_reference', 'bunq_payment_id',
|
||||
'import_hash', 'import_hash_v2', 'external_id', 'original_source'];
|
||||
|
||||
|
||||
if ('testing' === config('app.env')) {
|
||||
Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this)));
|
||||
}
|
||||
|
||||
$this->currencyRepository = app(CurrencyRepositoryInterface::class);
|
||||
$this->typeRepository = app(TransactionTypeRepositoryInterface::class);
|
||||
$this->transactionFactory = app(TransactionFactory::class);
|
||||
@@ -97,26 +108,22 @@ class TransactionJournalFactory
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a new transaction journal.
|
||||
* Store a new (set of) transaction journals.
|
||||
*
|
||||
* @param array $data
|
||||
*
|
||||
* @return TransactionGroup
|
||||
* @throws Exception
|
||||
* @return Collection
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function create(array $data): TransactionGroup
|
||||
public function create(array $data): Collection
|
||||
{
|
||||
// convert to special object.
|
||||
$data = new NullArrayObject($data);
|
||||
|
||||
Log::debug('Start of TransactionJournalFactory::create()');
|
||||
$collection = new Collection;
|
||||
$transactions = $data['transactions'] ?? [];
|
||||
$type = $this->typeRepository->findTransactionType(null, $data['type']);
|
||||
$carbon = $data['date'] ?? new Carbon;
|
||||
$carbon->setTimezone(config('app.timezone'));
|
||||
|
||||
Log::debug(sprintf('Going to store a %s.', $type->type));
|
||||
|
||||
if (0 === \count($transactions)) {
|
||||
if (0 === count($transactions)) {
|
||||
Log::error('There are no transactions in the array, the TransactionJournalFactory cannot continue.');
|
||||
|
||||
return new Collection;
|
||||
@@ -124,79 +131,15 @@ class TransactionJournalFactory
|
||||
|
||||
/** @var array $row */
|
||||
foreach ($transactions as $index => $row) {
|
||||
$transaction = new NullArrayObject($row);
|
||||
Log::debug(sprintf('Now creating journal %d/%d', $index + 1, \count($transactions)));
|
||||
/** Get basic fields */
|
||||
Log::debug(sprintf('Now creating journal %d/%d', $index + 1, count($transactions)));
|
||||
|
||||
$currency = $this->currencyRepository->findCurrency(
|
||||
$transaction['currency'], (int)$transaction['currency_id'], $transaction['currency_code']
|
||||
);
|
||||
$foreignCurrency = $this->findForeignCurrency($transaction);
|
||||
|
||||
$bill = $this->billRepository->findBill($transaction['bill'], (int)$transaction['bill_id'], $transaction['bill_name']);
|
||||
$billId = TransactionType::WITHDRAWAL === $type->type && null !== $bill ? $bill->id : null;
|
||||
$description = app('steam')->cleanString((string)$transaction['description']);
|
||||
|
||||
/** Create a basic journal. */
|
||||
$journal = TransactionJournal::create(
|
||||
[
|
||||
'user_id' => $this->user->id,
|
||||
'transaction_type_id' => $type->id,
|
||||
'bill_id' => $billId,
|
||||
'transaction_currency_id' => $currency->id,
|
||||
'description' => '' === $description ? '(empty description)' : $description,
|
||||
'date' => $carbon->format('Y-m-d H:i:s'),
|
||||
'order' => 0,
|
||||
'tag_count' => 0,
|
||||
'completed' => 0,
|
||||
]
|
||||
);
|
||||
Log::debug(sprintf('Created new journal #%d: "%s"', $journal->id, $journal->description));
|
||||
|
||||
/** Create two transactions. */
|
||||
$this->transactionFactory->setJournal($journal);
|
||||
$this->transactionFactory->createPair($transaction, $currency, $foreignCurrency);
|
||||
|
||||
// verify that journal has two transactions. Otherwise, delete and cancel.
|
||||
$count = $journal->transactions()->count();
|
||||
if (2 !== $count) {
|
||||
// @codeCoverageIgnoreStart
|
||||
Log::error(sprintf('The journal unexpectedly has %d transaction(s). This is not OK. Cancel operation.', $count));
|
||||
$journal->delete();
|
||||
|
||||
return new Collection;
|
||||
// @codeCoverageIgnoreEnd
|
||||
$journal = $this->createJournal(new NullArrayObject($row));
|
||||
if (null !== $journal) {
|
||||
$collection->push($journal);
|
||||
}
|
||||
$journal->completed =true;
|
||||
$journal->save();
|
||||
|
||||
/** Link all other data to the journal. */
|
||||
|
||||
/** Link budget */
|
||||
$this->storeBudget($journal, $transaction);
|
||||
|
||||
/** Link category */
|
||||
$this->storeCategory($journal, $transaction);
|
||||
|
||||
/** Set notes */
|
||||
$this->storeNote($journal, $transaction['notes']);
|
||||
|
||||
/** Set piggy bank */
|
||||
$this->storePiggyEvent($journal, $transaction);
|
||||
|
||||
/** Set tags */
|
||||
$this->storeTags($journal, $transaction['tags']);
|
||||
|
||||
/** set all meta fields */
|
||||
$this->storeMetaFields($journal, $transaction);
|
||||
|
||||
$collection->push($journal);
|
||||
}
|
||||
|
||||
$group = $this->storeGroup($collection, $data['group_title']);
|
||||
|
||||
return $group;
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -215,31 +158,6 @@ class TransactionJournalFactory
|
||||
$this->piggyRepository->setUser($this->user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Join multiple journals in a group.
|
||||
*
|
||||
* @param Collection $collection
|
||||
* @param string|null $title
|
||||
*
|
||||
* @return TransactionGroup|null
|
||||
*/
|
||||
public function storeGroup(Collection $collection, ?string $title): ?TransactionGroup
|
||||
{
|
||||
if ($collection->count() < 2) {
|
||||
return null; // @codeCoverageIgnore
|
||||
}
|
||||
/** @var TransactionJournal $first */
|
||||
$first = $collection->first();
|
||||
$group = new TransactionGroup;
|
||||
$group->user()->associate($first->user);
|
||||
$group->title = $title ?? $first->description;
|
||||
$group->save();
|
||||
|
||||
$group->transactionJournals()->saveMany($collection);
|
||||
|
||||
return $group;
|
||||
}
|
||||
|
||||
/**
|
||||
* Link a piggy bank to this journal.
|
||||
*
|
||||
@@ -332,6 +250,88 @@ class TransactionJournalFactory
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param NullArrayObject $row
|
||||
*
|
||||
* @return TransactionJournal|null
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function createJournal(NullArrayObject $row): ?TransactionJournal
|
||||
{
|
||||
$row['import_hash_v2'] = $this->hashArray($row);
|
||||
|
||||
/** Get basic fields */
|
||||
$type = $this->typeRepository->findTransactionType(null, $row['type']);
|
||||
$carbon = $row['date'] ?? new Carbon;
|
||||
$currency = $this->currencyRepository->findCurrency((int)$row['currency_id'], $row['currency_code']);
|
||||
$foreignCurrency = $this->findForeignCurrency($row);
|
||||
$bill = $this->billRepository->findBill((int)$row['bill_id'], $row['bill_name']);
|
||||
$billId = TransactionType::WITHDRAWAL === $type->type && null !== $bill ? $bill->id : null;
|
||||
$description = app('steam')->cleanString((string)$row['description']);
|
||||
|
||||
/** Manipulate basic fields */
|
||||
$carbon->setTimezone(config('app.timezone'));
|
||||
|
||||
/** Create a basic journal. */
|
||||
$journal = TransactionJournal::create(
|
||||
[
|
||||
'user_id' => $this->user->id,
|
||||
'transaction_type_id' => $type->id,
|
||||
'bill_id' => $billId,
|
||||
'transaction_currency_id' => $currency->id,
|
||||
'description' => '' === $description ? '(empty description)' : $description,
|
||||
'date' => $carbon->format('Y-m-d H:i:s'),
|
||||
'order' => 0,
|
||||
'tag_count' => 0,
|
||||
'completed' => 0,
|
||||
]
|
||||
);
|
||||
Log::debug(sprintf('Created new journal #%d: "%s"', $journal->id, $journal->description));
|
||||
|
||||
/** Create two transactions. */
|
||||
$this->transactionFactory->setJournal($journal);
|
||||
$this->transactionFactory->createPair($row, $currency, $foreignCurrency);
|
||||
|
||||
// verify that journal has two transactions. Otherwise, delete and cancel.
|
||||
$count = $journal->transactions()->count();
|
||||
if (2 !== $count) {
|
||||
// @codeCoverageIgnoreStart
|
||||
Log::error(sprintf('The journal unexpectedly has %d transaction(s). This is not OK. Cancel operation.', $count));
|
||||
try {
|
||||
$journal->delete();
|
||||
} catch (Exception $e) {
|
||||
Log::debug(sprintf('Dont care: %s.', $e->getMessage()));
|
||||
}
|
||||
|
||||
return null;
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
$journal->completed = true;
|
||||
$journal->save();
|
||||
|
||||
/** Link all other data to the journal. */
|
||||
|
||||
/** Link budget */
|
||||
$this->storeBudget($journal, $row);
|
||||
|
||||
/** Link category */
|
||||
$this->storeCategory($journal, $row);
|
||||
|
||||
/** Set notes */
|
||||
$this->storeNote($journal, $row['notes']);
|
||||
|
||||
/** Set piggy bank */
|
||||
$this->storePiggyEvent($journal, $row);
|
||||
|
||||
/** Set tags */
|
||||
$this->storeTags($journal, $row['tags']);
|
||||
|
||||
/** set all meta fields */
|
||||
$this->storeMetaFields($journal, $row);
|
||||
|
||||
return $journal;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a separate function because "findCurrency" will default to EUR and that may not be what we want.
|
||||
*
|
||||
@@ -345,17 +345,38 @@ class TransactionJournalFactory
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->currencyRepository->findCurrency(
|
||||
$transaction['foreign_currency'], (int)$transaction['foreign_currency_id'], $transaction['foreign_currency_code']
|
||||
);
|
||||
return $this->currencyRepository->findCurrency((int)$transaction['foreign_currency_id'], $transaction['foreign_currency_code']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param NullArrayObject $row
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function hashArray(NullArrayObject $row): string
|
||||
{
|
||||
$row['import_hash_v2'] = null;
|
||||
$row['original_source'] = null;
|
||||
$json = json_encode($row);
|
||||
if (false === $json) {
|
||||
$json = json_encode((string)microtime());
|
||||
}
|
||||
$hash = hash('sha256', $json);
|
||||
Log::debug(sprintf('The hash is: %s', $hash));
|
||||
|
||||
return $hash;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
* @param NullArrayObject $data
|
||||
*/
|
||||
private function storeBudget(TransactionJournal $journal, NullArrayObject $data): void
|
||||
{
|
||||
if (TransactionType::WITHDRAWAL !== $journal->transactionType->type) {
|
||||
return;
|
||||
}
|
||||
$budget = $this->budgetRepository->findBudget($data['budget'], $data['budget_id'], $data['budget_name']);
|
||||
if (null !== $budget) {
|
||||
Log::debug(sprintf('Link budget #%d to journal #%d', $budget->id, $journal->id));
|
||||
|
@@ -86,18 +86,24 @@ class GroupCollector implements GroupCollectorInterface
|
||||
$this->limit = 50;
|
||||
$this->page = 0;
|
||||
$this->fields = [
|
||||
# group
|
||||
'transaction_groups.id as transaction_group_id',
|
||||
'transaction_groups.user_id as user_id',
|
||||
'transaction_groups.created_at as created_at',
|
||||
'transaction_groups.updated_at as updated_at',
|
||||
'transaction_groups.title as transaction_group_title',
|
||||
|
||||
# journal
|
||||
'transaction_journals.id as transaction_journal_id',
|
||||
'transaction_journals.transaction_type_id',
|
||||
'transaction_types.type as transaction_type_type',
|
||||
'transaction_journals.description',
|
||||
'transaction_journals.date',
|
||||
|
||||
# source info (always present)
|
||||
'source.id as source_transaction_id',
|
||||
'source.account_id as source_account_id',
|
||||
'source.reconciled',
|
||||
|
||||
# currency info:
|
||||
'source.amount as amount',
|
||||
@@ -113,7 +119,8 @@ class GroupCollector implements GroupCollectorInterface
|
||||
'foreign_currency.symbol as foreign_currency_symbol',
|
||||
'foreign_currency.decimal_places as foreign_currency_decimal_places',
|
||||
|
||||
# destination account info:
|
||||
# destination account info (always present)
|
||||
#'destination.id as destination_transaction_id', // not interesting.
|
||||
'destination.account_id as destination_account_id',
|
||||
];
|
||||
}
|
||||
@@ -368,6 +375,8 @@ class GroupCollector implements GroupCollectorInterface
|
||||
public function setTransactionGroup(TransactionGroup $transactionGroup): GroupCollectorInterface
|
||||
{
|
||||
$this->query->where('transaction_groups.id', $transactionGroup->id);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -534,46 +543,48 @@ class GroupCollector implements GroupCollectorInterface
|
||||
private function parseArray(Collection $collection): Collection
|
||||
{
|
||||
$groups = [];
|
||||
/** @var TransactionGroup $augumentedGroup */
|
||||
foreach ($collection as $augumentedGroup) {
|
||||
$groupId = $augumentedGroup->transaction_group_id;
|
||||
/** @var TransactionGroup $augmentedGroup */
|
||||
foreach ($collection as $augmentedGroup) {
|
||||
$groupId = $augmentedGroup->transaction_group_id;
|
||||
if (!isset($groups[$groupId])) {
|
||||
// make new array
|
||||
$groupArray = [
|
||||
'id' => $augumentedGroup->id,
|
||||
'title' => $augumentedGroup->title,
|
||||
'id' => $augmentedGroup->transaction_group_id,
|
||||
'user_id' => $augmentedGroup->user_id,
|
||||
'title' => $augmentedGroup->title,
|
||||
'count' => 1,
|
||||
'sum' => $augumentedGroup->amount,
|
||||
'foreign_sum' => $augumentedGroup->foreign_amount ?? '0',
|
||||
'sum' => $augmentedGroup->amount,
|
||||
'foreign_sum' => $augmentedGroup->foreign_amount ?? '0',
|
||||
'transactions' => [],
|
||||
];
|
||||
$journalId = (int)$augumentedGroup->transaction_journal_id;
|
||||
$groupArray['transactions'][$journalId] = $this->parseAugumentedGroup($augumentedGroup);
|
||||
$journalId = (int)$augmentedGroup->transaction_journal_id;
|
||||
$groupArray['transactions'][$journalId] = $this->parseAugmentedGroup($augmentedGroup);
|
||||
$groups[$groupId] = $groupArray;
|
||||
continue;
|
||||
}
|
||||
$groups[$groupId]['count']++;
|
||||
$groups[$groupId]['sum'] = bcadd($augumentedGroup->amount, $groups[$groupId]['sum']);
|
||||
$groups[$groupId]['foreign_sum'] = bcadd($augumentedGroup->foreign_amount ?? '0', $groups[$groupId]['foreign_sum']);
|
||||
$journalId = (int)$augumentedGroup->transaction_journal_id;
|
||||
$groups[$groupId]['transactions'][$journalId] = $this->parseAugumentedGroup($augumentedGroup);
|
||||
$groups[$groupId]['sum'] = bcadd($augmentedGroup->amount, $groups[$groupId]['sum']);
|
||||
$groups[$groupId]['foreign_sum'] = bcadd($augmentedGroup->foreign_amount ?? '0', $groups[$groupId]['foreign_sum']);
|
||||
$journalId = (int)$augmentedGroup->transaction_journal_id;
|
||||
$groups[$groupId]['transactions'][$journalId] = $this->parseAugmentedGroup($augmentedGroup);
|
||||
}
|
||||
|
||||
return new Collection($groups);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TransactionGroup $augumentedGroup
|
||||
* @param TransactionGroup $augmentedGroup
|
||||
*
|
||||
* @throws Exception
|
||||
* @return array
|
||||
* @throws Exception
|
||||
*/
|
||||
private function parseAugumentedGroup(TransactionGroup $augumentedGroup): array
|
||||
private function parseAugmentedGroup(TransactionGroup $augmentedGroup): array
|
||||
{
|
||||
$result = $augumentedGroup->toArray();
|
||||
$result = $augmentedGroup->toArray();
|
||||
$result['date'] = new Carbon($result['date']);
|
||||
$result['created_at'] = new Carbon($result['created_at']);
|
||||
$result['updated_at'] = new Carbon($result['updated_at']);
|
||||
$result['reconciled'] = 1 === (int)$result['reconciled'];
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
@@ -24,6 +24,7 @@ namespace FireflyIII\Http\Requests;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Carbon\Exceptions\InvalidDateException;
|
||||
use Exception;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Log;
|
||||
|
||||
@@ -36,6 +37,26 @@ use Log;
|
||||
*/
|
||||
class Request extends FormRequest
|
||||
{
|
||||
/**
|
||||
* @param $array
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
public function arrayFromValue($array): ?array
|
||||
{
|
||||
if (is_array($array)) {
|
||||
return $array;
|
||||
}
|
||||
if (null === $array) {
|
||||
return null;
|
||||
}
|
||||
if (is_string($array)) {
|
||||
return explode(',', $array);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a boolean value.
|
||||
*
|
||||
@@ -60,11 +81,17 @@ class Request extends FormRequest
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function convertBoolean(string $value): bool
|
||||
public function convertBoolean(?string $value): bool
|
||||
{
|
||||
if (null === $value) {
|
||||
return false;
|
||||
}
|
||||
if ('true' === $value) {
|
||||
return true;
|
||||
}
|
||||
if ('yes' === $value) {
|
||||
return true;
|
||||
}
|
||||
if (1 === $value) {
|
||||
return true;
|
||||
}
|
||||
@@ -78,6 +105,30 @@ class Request extends FormRequest
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $string
|
||||
*
|
||||
* @return Carbon|null
|
||||
*/
|
||||
public function dateFromValue(?string $string): ?Carbon
|
||||
{
|
||||
if (null === $string) {
|
||||
return null;
|
||||
}
|
||||
if ('' === $string) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
$carbon = new Carbon($string);
|
||||
} catch (Exception $e) {
|
||||
Log::debug(sprintf('Invalid date: %s: %s', $string, $e->getMessage()));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return $carbon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return floating value.
|
||||
*
|
||||
@@ -107,6 +158,22 @@ class Request extends FormRequest
|
||||
return (int)$this->get($field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse to integer
|
||||
*
|
||||
* @param string|null $string
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public function integerFromValue(?string $string): ?int
|
||||
{
|
||||
if (null === $string) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (int)$string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return string value.
|
||||
*
|
||||
@@ -121,12 +188,31 @@ class Request extends FormRequest
|
||||
return app('steam')->cleanString((string)($this->get($field) ?? ''));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and clean a string.
|
||||
*
|
||||
* @param string|null $string
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function stringFromValue(?string $string): ?string
|
||||
{
|
||||
if (null === $string) {
|
||||
return null;
|
||||
}
|
||||
$result = app('steam')->cleanString($string);
|
||||
|
||||
return '' === $result ? null : $result;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Return date or NULL.
|
||||
*
|
||||
* @param string $field
|
||||
*
|
||||
* @return Carbon|null
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function date(string $field): ?Carbon
|
||||
{
|
||||
|
@@ -220,12 +220,12 @@ class ImportArrayStorage
|
||||
*/
|
||||
private function getHash(array $transaction): string
|
||||
{
|
||||
unset($transaction['importHashV2'], $transaction['original-source']);
|
||||
unset($transaction['import_hash_v2'], $transaction['original_source']);
|
||||
$json = json_encode($transaction);
|
||||
if (false === $json) {
|
||||
// @codeCoverageIgnoreStart
|
||||
/** @noinspection ForgottenDebugOutputInspection */
|
||||
Log::error('Could not encode import array.', print_r($transaction, true));
|
||||
Log::error('Could not encode import array.', $transaction);
|
||||
throw new FireflyException('Could not encode import array. Please see the logs.');
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
@@ -438,7 +438,7 @@ class ImportArrayStorage
|
||||
Log::warning(sprintf('Row #%d seems to be a duplicate entry and will be ignored.', $index));
|
||||
continue;
|
||||
}
|
||||
$transaction['importHashV2'] = $this->getHash($transaction);
|
||||
$transaction['import_hash_v2'] = $this->getHash($transaction);
|
||||
$toStore[] = $transaction;
|
||||
}
|
||||
$count = \count($toStore);
|
||||
|
@@ -75,6 +75,8 @@ use FireflyIII\Validation\FireflyValidator;
|
||||
use Illuminate\Foundation\Application;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Twig;
|
||||
use Twig_Extension_Debug;
|
||||
use TwigBridge\Extension\Loader\Functions;
|
||||
use Validator;
|
||||
|
||||
/**
|
||||
@@ -98,19 +100,18 @@ class FireflyServiceProvider extends ServiceProvider
|
||||
}
|
||||
);
|
||||
$config = app('config');
|
||||
//Twig::addExtension(new Functions($config));
|
||||
//Twig::addRuntimeLoader(new TransactionLoader);
|
||||
//Twig::addRuntimeLoader(new AccountLoader);
|
||||
//Twig::addRuntimeLoader(new TransactionJournalLoader);
|
||||
//Twig::addRuntimeLoader(new TransactionGroupLoader);
|
||||
Twig::addExtension(new Functions($config));
|
||||
Twig::addRuntimeLoader(new TransactionLoader);
|
||||
Twig::addRuntimeLoader(new AccountLoader);
|
||||
Twig::addRuntimeLoader(new TransactionJournalLoader);
|
||||
Twig::addExtension(new General);
|
||||
Twig::addExtension(new TransactionGroupTwig);
|
||||
//Twig::addExtension(new Journal);
|
||||
Twig::addExtension(new Journal);
|
||||
Twig::addExtension(new Translation);
|
||||
//Twig::addExtension(new Transaction);
|
||||
//Twig::addExtension(new Rule);
|
||||
Twig::addExtension(new Transaction);
|
||||
Twig::addExtension(new Rule);
|
||||
Twig::addExtension(new AmountFormat);
|
||||
//Twig::addExtension(new Twig_Extension_Debug);
|
||||
Twig::addExtension(new Twig_Extension_Debug);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -89,21 +89,13 @@ class BillRepository implements BillRepositoryInterface
|
||||
/**
|
||||
* Find bill by parameters.
|
||||
*
|
||||
* @param Bill|null $bill
|
||||
* @param int|null $billId
|
||||
* @param string|null $billName
|
||||
*
|
||||
* @return Bill|null
|
||||
*/
|
||||
public function findBill(?Bill $bill, ?int $billId, ?string $billName): ?Bill
|
||||
public function findBill( ?int $billId, ?string $billName): ?Bill
|
||||
{
|
||||
Log::debug('Searching for bill information.');
|
||||
if ($bill instanceof Bill && $bill->user_id === $this->user->id) {
|
||||
Log::debug(sprintf('Bill object in parameters, will return Bill #%d', $bill->id));
|
||||
|
||||
return $bill;
|
||||
}
|
||||
|
||||
if (null !== $billId) {
|
||||
$searchResult = $this->find((int)$billId);
|
||||
if (null !== $searchResult) {
|
||||
|
@@ -52,13 +52,12 @@ interface BillRepositoryInterface
|
||||
/**
|
||||
* Find bill by parameters.
|
||||
*
|
||||
* @param Bill|null $bill
|
||||
* @param int|null $billId
|
||||
* @param string|null $billName
|
||||
*
|
||||
* @return Bill|null
|
||||
*/
|
||||
public function findBill(?Bill $bill, ?int $billId, ?string $billName): ?Bill;
|
||||
public function findBill(?int $billId, ?string $billName): ?Bill;
|
||||
|
||||
/**
|
||||
* Find a bill by name.
|
||||
|
@@ -195,8 +195,8 @@ class CurrencyRepository implements CurrencyRepositoryInterface
|
||||
*
|
||||
* @param string $currencyCode
|
||||
*
|
||||
* @deprecated
|
||||
* @return TransactionCurrency|null
|
||||
* @deprecated
|
||||
*/
|
||||
public function findByCodeNull(string $currencyCode): ?TransactionCurrency
|
||||
{
|
||||
@@ -221,8 +221,8 @@ class CurrencyRepository implements CurrencyRepositoryInterface
|
||||
*
|
||||
* @param string $currencyName
|
||||
*
|
||||
* @deprecated
|
||||
* @return TransactionCurrency
|
||||
* @deprecated
|
||||
*/
|
||||
public function findByNameNull(string $currencyName): ?TransactionCurrency
|
||||
{
|
||||
@@ -247,8 +247,8 @@ class CurrencyRepository implements CurrencyRepositoryInterface
|
||||
*
|
||||
* @param string $currencySymbol
|
||||
*
|
||||
* @deprecated
|
||||
* @return TransactionCurrency
|
||||
* @deprecated
|
||||
*/
|
||||
public function findBySymbolNull(string $currencySymbol): ?TransactionCurrency
|
||||
{
|
||||
@@ -258,25 +258,15 @@ class CurrencyRepository implements CurrencyRepositoryInterface
|
||||
/**
|
||||
* Find by object, ID or code. Returns user default or system default.
|
||||
*
|
||||
* @param TransactionCurrency|null $currency
|
||||
* @param int|null $currencyId
|
||||
* @param string|null $currencyCode
|
||||
* @param int|null $currencyId
|
||||
* @param string|null $currencyCode
|
||||
*
|
||||
* @return TransactionCurrency|null
|
||||
*/
|
||||
public function findCurrency(?TransactionCurrency $currency, ?int $currencyId, ?string $currencyCode): TransactionCurrency
|
||||
public function findCurrency(?int $currencyId, ?string $currencyCode): TransactionCurrency
|
||||
{
|
||||
Log::debug('Now in findCurrency()');
|
||||
$result = null;
|
||||
if (null !== $currency) {
|
||||
Log::debug(sprintf('Parameters contain %s, will return this.', $currency->code));
|
||||
$result = $currency;
|
||||
}
|
||||
|
||||
if (null === $result) {
|
||||
Log::debug(sprintf('Searching for currency with ID #%d...', $currencyId));
|
||||
$result = $this->find((int)$currencyId);
|
||||
}
|
||||
$result = $this->find((int)$currencyId);
|
||||
if (null === $result) {
|
||||
Log::debug(sprintf('Searching for currency with code %s...', $currencyCode));
|
||||
$result = $this->findByCode((string)$currencyCode);
|
||||
@@ -304,8 +294,8 @@ class CurrencyRepository implements CurrencyRepositoryInterface
|
||||
*
|
||||
* @param int $currencyId
|
||||
*
|
||||
* @deprecated
|
||||
* @return TransactionCurrency|null
|
||||
* @deprecated
|
||||
*/
|
||||
public function findNull(int $currencyId): ?TransactionCurrency
|
||||
{
|
||||
|
@@ -135,13 +135,12 @@ interface CurrencyRepositoryInterface
|
||||
/**
|
||||
* Find by object, ID or code. Returns user default or system default.
|
||||
*
|
||||
* @param TransactionCurrency|null $currency
|
||||
* @param int|null $currencyId
|
||||
* @param string|null $currencyCode
|
||||
*
|
||||
* @return TransactionCurrency|null
|
||||
*/
|
||||
public function findCurrency(?TransactionCurrency $currency, ?int $currencyId, ?string $currencyCode): TransactionCurrency;
|
||||
public function findCurrency(?int $currencyId, ?string $currencyCode): TransactionCurrency;
|
||||
|
||||
/**
|
||||
* Find by ID, return NULL if not found.
|
||||
|
@@ -26,6 +26,7 @@ use Carbon\Carbon;
|
||||
use DB;
|
||||
use Exception;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Factory\TransactionGroupFactory;
|
||||
use FireflyIII\Factory\TransactionJournalFactory;
|
||||
use FireflyIII\Helpers\Collector\TransactionCollectorInterface;
|
||||
use FireflyIII\Helpers\Filter\InternalTransferFilter;
|
||||
@@ -175,7 +176,7 @@ class JournalRepository implements JournalRepositoryInterface
|
||||
$result = TransactionJournalMeta::withTrashed()
|
||||
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id')
|
||||
->where('hash', $hashOfHash)
|
||||
->where('name', 'importHashV2')
|
||||
->where('name', 'import_hash_v2')
|
||||
->first(['journal_meta.*']);
|
||||
if (null === $result) {
|
||||
Log::debug('Result is null');
|
||||
@@ -782,8 +783,8 @@ class JournalRepository implements JournalRepositoryInterface
|
||||
*/
|
||||
public function store(array $data): TransactionGroup
|
||||
{
|
||||
/** @var TransactionJournalFactory $factory */
|
||||
$factory = app(TransactionJournalFactory::class);
|
||||
/** @var TransactionGroupFactory $factory */
|
||||
$factory = app(TransactionGroupFactory::class);
|
||||
$factory->setUser($this->user);
|
||||
|
||||
return $factory->create($data);
|
||||
|
@@ -185,8 +185,8 @@ class ImportTransaction
|
||||
return;
|
||||
}
|
||||
|
||||
$meta = ['sepa-ct-id', 'sepa-ct-op', 'sepa-db', 'sepa-cc', 'sepa-country', 'sepa-batch-id', 'sepa-ep', 'sepa-ci', 'internal-reference', 'date-interest',
|
||||
'date-invoice', 'date-book', 'date-payment', 'date-process', 'date-due', 'original-source'];
|
||||
$meta = ['sepa_ct_id', 'sepa_ct_op', 'sepa_db', 'sepa_cc', 'sepa_country', 'sepa_batch_id', 'sepa_ep', 'sepa_ci', 'internal_reference', 'date_interest',
|
||||
'date_invoice', 'date_book', 'date_payment', 'date_process', 'date_due', 'original_source'];
|
||||
Log::debug(sprintf('Now going to check role "%s".', $role));
|
||||
if (\in_array($role, $meta, true)) {
|
||||
Log::debug(sprintf('Role "%s" is in allowed meta roles, so store its value "%s".', $role, $columnValue->getValue()));
|
||||
|
@@ -226,14 +226,14 @@ class ImportableConverter
|
||||
|
||||
// all custom fields:
|
||||
'internal_reference' => $importable->meta['internal-reference'] ?? null,
|
||||
'sepa-cc' => $importable->meta['sepa-cc'] ?? null,
|
||||
'sepa-ct-op' => $importable->meta['sepa-ct-op'] ?? null,
|
||||
'sepa-ct-id' => $importable->meta['sepa-ct-id'] ?? null,
|
||||
'sepa-db' => $importable->meta['sepa-db'] ?? null,
|
||||
'sepa-country' => $importable->meta['sepa-country'] ?? null,
|
||||
'sepa-ep' => $importable->meta['sepa-ep'] ?? null,
|
||||
'sepa-ci' => $importable->meta['sepa-ci'] ?? null,
|
||||
'sepa-batch-id' => $importable->meta['sepa-batch-id'] ?? null,
|
||||
'sepa_cc' => $importable->meta['sepa_cc'] ?? null,
|
||||
'sepa_ct_op' => $importable->meta['sepa_ct_op'] ?? null,
|
||||
'sepa_ct_id' => $importable->meta['sepa_ct_id'] ?? null,
|
||||
'sepa_db' => $importable->meta['sepa_db'] ?? null,
|
||||
'sepa_country' => $importable->meta['sepa_country'] ?? null,
|
||||
'sepa_ep' => $importable->meta['sepa_ep'] ?? null,
|
||||
'sepa_ci' => $importable->meta['sepa_ci'] ?? null,
|
||||
'sepa_batch_id' => $importable->meta['sepa_batch_id'] ?? null,
|
||||
'interest_date' => $this->convertDateValue($importable->meta['date-interest'] ?? null),
|
||||
'book_date' => $this->convertDateValue($importable->meta['date-book'] ?? null),
|
||||
'process_date' => $this->convertDateValue($importable->meta['date-process'] ?? null),
|
||||
|
@@ -48,9 +48,9 @@ class TransactionGroupTransformer extends AbstractTransformer
|
||||
{
|
||||
$this->groupRepos = app(TransactionGroupRepositoryInterface::class);
|
||||
$this->metaFields = [
|
||||
'sepa-cc', 'sepa-ct-op', 'sepa-ct-id', 'sepa-db', 'sepa-country', 'sepa-ep',
|
||||
'sepa-ci', 'sepa-batch-id', 'internal_reference', 'bunq_payment_id', 'importHashV2',
|
||||
'recurrence_id', 'external_id', 'original-source',
|
||||
'sepa_cc', 'sepa_ct_op', 'sepa_ct_id', 'sepa_db', 'sepa_country', 'sepa_ep',
|
||||
'sepa_ci', 'sepa_batch_id', 'internal_reference', 'bunq_payment_id', 'import_hash_v2',
|
||||
'recurrence_id', 'external_id', 'original_source',
|
||||
];
|
||||
$this->metaDateFields = ['interest_date', 'book_date', 'process_date', 'due_date', 'payment_date', 'invoice_date'];
|
||||
|
||||
@@ -72,6 +72,7 @@ class TransactionGroupTransformer extends AbstractTransformer
|
||||
'id' => (int)$first['transaction_group_id'],
|
||||
'created_at' => $first['created_at']->toAtomString(),
|
||||
'updated_at' => $first['updated_at']->toAtomString(),
|
||||
'user' => (int)$data['user_id'],
|
||||
'group_title' => $data['title'],
|
||||
'transactions' => $this->transformTransactions($data),
|
||||
'links' => [
|
||||
@@ -114,57 +115,71 @@ class TransactionGroupTransformer extends AbstractTransformer
|
||||
$metaDateData = $this->groupRepos->getMetaDateFields((int)$row['transaction_journal_id'], $this->metaDateFields);
|
||||
|
||||
$result[] = [
|
||||
'transaction_journal_id' => $row['transaction_journal_id'],
|
||||
'description' => $row['description'],
|
||||
'date' => $row['date']->toAtomString(),
|
||||
'type' => $type,
|
||||
'reconciled' => $row['reconciled'],
|
||||
'source_id' => $row['source_account_id'],
|
||||
'source_name' => $row['source_account_name'],
|
||||
'source_iban' => $row['source_account_iban'],
|
||||
'source_type' => $row['source_account_type'],
|
||||
'destination_id' => $row['destination_account_id'],
|
||||
'destination_name' => $row['destination_account_name'],
|
||||
'destination_iban' => $row['destination_account_iban'],
|
||||
'destination_type' => $row['destination_account_type'],
|
||||
'amount' => $amount,
|
||||
'currency_id' => $row['currency_id'],
|
||||
'currency_code' => $row['currency_code'],
|
||||
'currency_symbol' => $row['currency_symbol'],
|
||||
'currency_decimal_places' => $row['currency_decimal_places'],
|
||||
'foreign_amount' => $foreignAmount,
|
||||
'user' => (int)$row['user_id'],
|
||||
'transaction_journal_id' => $row['transaction_journal_id'],
|
||||
'type' => strtolower($type),
|
||||
'date' => $row['date']->toAtomString(),
|
||||
|
||||
'currency_id' => $row['currency_id'],
|
||||
'currency_code' => $row['currency_code'],
|
||||
'currency_symbol' => $row['currency_symbol'],
|
||||
'currency_decimal_places' => $row['currency_decimal_places'],
|
||||
|
||||
'foreign_currency_id' => $row['foreign_currency_id'],
|
||||
'foreign_currency_code' => $row['foreign_currency_code'],
|
||||
'foreign_currency_symbol' => $row['foreign_currency_symbol'],
|
||||
'foreign_currency_decimal_places' => $row['foreign_currency_decimal_places'],
|
||||
'bill_id' => $row['bill_id'],
|
||||
'bill_name' => $row['bill_name'],
|
||||
'category_id' => $row['category_id'],
|
||||
'category_name' => $row['category_name'],
|
||||
'budget_id' => $row['budget_id'],
|
||||
'budget_name' => $row['budget_name'],
|
||||
'notes' => $this->groupRepos->getNoteText((int)$row['transaction_journal_id']),
|
||||
'tags' => $this->groupRepos->getTags((int)$row['transaction_journal_id']),
|
||||
'sepa_cc' => $metaFieldData['sepa-cc'],
|
||||
'sepa_ct_op' => $metaFieldData['sepa-ct-op'],
|
||||
'sepa_ct_id' => $metaFieldData['sepa-ct-id'],
|
||||
'sepa_db' => $metaFieldData['sepa-ddb'],
|
||||
'sepa_country' => $metaFieldData['sepa-country'],
|
||||
'sepa_ep' => $metaFieldData['sepa-ep'],
|
||||
'sepa_ci' => $metaFieldData['sepa-ci'],
|
||||
'sepa_batch_id' => $metaFieldData['sepa-batch-id'],
|
||||
'internal_reference' => $metaFieldData['internal_reference'],
|
||||
'bunq_payment_id' => $metaFieldData['bunq_payment_id'],
|
||||
'importHashV2' => $metaFieldData['importHashV2'],
|
||||
'recurrence_id' => $metaFieldData['recurrence_id'],
|
||||
'external_id' => $metaFieldData['external_id'],
|
||||
'original_source' => $metaFieldData['original-source'],
|
||||
'interest_date' => $metaDateData['interest_date'] ? $metaDateData['interest_date']->toAtomString() : null,
|
||||
'book_date' => $metaDateData['book_date'] ? $metaDateData['book_date']->toAtomString() : null,
|
||||
'process_date' => $metaDateData['process_date'] ? $metaDateData['process_date']->toAtomString() : null,
|
||||
'due_date' => $metaDateData['due_date'] ? $metaDateData['due_date']->toAtomString() : null,
|
||||
'payment_date' => $metaDateData['payment_date'] ? $metaDateData['payment_date']->toAtomString() : null,
|
||||
'invoice_date' => $metaDateData['invoice_date'] ? $metaDateData['invoice_date']->toAtomString() : null,
|
||||
|
||||
'amount' => $amount,
|
||||
'foreign_amount' => $foreignAmount,
|
||||
|
||||
'description' => $row['description'],
|
||||
|
||||
'source_id' => $row['source_account_id'],
|
||||
'source_name' => $row['source_account_name'],
|
||||
'source_iban' => $row['source_account_iban'],
|
||||
'source_type' => $row['source_account_type'],
|
||||
|
||||
'destination_id' => $row['destination_account_id'],
|
||||
'destination_name' => $row['destination_account_name'],
|
||||
'destination_iban' => $row['destination_account_iban'],
|
||||
'destination_type' => $row['destination_account_type'],
|
||||
|
||||
'budget_id' => $row['budget_id'],
|
||||
'budget_name' => $row['budget_name'],
|
||||
|
||||
'category_id' => $row['category_id'],
|
||||
'category_name' => $row['category_name'],
|
||||
|
||||
'bill_id' => $row['bill_id'],
|
||||
'bill_name' => $row['bill_name'],
|
||||
|
||||
'reconciled' => $row['reconciled'],
|
||||
'notes' => $this->groupRepos->getNoteText((int)$row['transaction_journal_id']),
|
||||
'tags' => $this->groupRepos->getTags((int)$row['transaction_journal_id']),
|
||||
|
||||
'internal_reference' => $metaFieldData['internal_reference'],
|
||||
'external_id' => $metaFieldData['external_id'],
|
||||
'original_source' => $metaFieldData['original_source'],
|
||||
'recurrence_id' => $metaFieldData['recurrence_id'],
|
||||
'bunq_payment_id' => $metaFieldData['bunq_payment_id'],
|
||||
'import_hash_v2' => $metaFieldData['import_hash_v2'],
|
||||
|
||||
'sepa_cc' => $metaFieldData['sepa_cc'],
|
||||
'sepa_ct_op' => $metaFieldData['sepa_ct_op'],
|
||||
'sepa_ct_id' => $metaFieldData['sepa_ct_id'],
|
||||
'sepa_db' => $metaFieldData['sepa_ddb'],
|
||||
'sepa_country' => $metaFieldData['sepa_country'],
|
||||
'sepa_ep' => $metaFieldData['sepa_ep'],
|
||||
'sepa_ci' => $metaFieldData['sepa_ci'],
|
||||
'sepa_batch_id' => $metaFieldData['sepa_batch_id'],
|
||||
|
||||
'interest_date' => $metaDateData['interest_date'] ? $metaDateData['interest_date']->toAtomString() : null,
|
||||
'book_date' => $metaDateData['book_date'] ? $metaDateData['book_date']->toAtomString() : null,
|
||||
'process_date' => $metaDateData['process_date'] ? $metaDateData['process_date']->toAtomString() : null,
|
||||
'due_date' => $metaDateData['due_date'] ? $metaDateData['due_date']->toAtomString() : null,
|
||||
'payment_date' => $metaDateData['payment_date'] ? $metaDateData['payment_date']->toAtomString() : null,
|
||||
'invoice_date' => $metaDateData['invoice_date'] ? $metaDateData['invoice_date']->toAtomString() : null,
|
||||
];
|
||||
}
|
||||
|
||||
|
@@ -100,14 +100,14 @@ class TransactionTransformer extends AbstractTransformer
|
||||
'budget_id' => $budget['budget_id'],
|
||||
'budget_name' => $budget['budget_name'],
|
||||
'notes' => $notes,
|
||||
'sepa_cc' => $this->repository->getMetaField($journal, 'sepa-cc'),
|
||||
'sepa_ct_op' => $this->repository->getMetaField($journal, 'sepa-ct-op'),
|
||||
'sepa_ct_id' => $this->repository->getMetaField($journal, 'sepa-ct-ud'),
|
||||
'sepa_db' => $this->repository->getMetaField($journal, 'sepa-db'),
|
||||
'sepa_country' => $this->repository->getMetaField($journal, 'sepa-country'),
|
||||
'sepa_ep' => $this->repository->getMetaField($journal, 'sepa-ep'),
|
||||
'sepa_ci' => $this->repository->getMetaField($journal, 'sepa-ci'),
|
||||
'sepa_batch_id' => $this->repository->getMetaField($journal, 'sepa-batch-id'),
|
||||
'sepa_cc' => $this->repository->getMetaField($journal, 'sepa_cc'),
|
||||
'sepa_ct_op' => $this->repository->getMetaField($journal, 'sepa_ct_op'),
|
||||
'sepa_ct_id' => $this->repository->getMetaField($journal, 'sepa_ct_ud'),
|
||||
'sepa_db' => $this->repository->getMetaField($journal, 'sepa_db'),
|
||||
'sepa_country' => $this->repository->getMetaField($journal, 'sepa_country'),
|
||||
'sepa_ep' => $this->repository->getMetaField($journal, 'sepa_ep'),
|
||||
'sepa_ci' => $this->repository->getMetaField($journal, 'sepa_ci'),
|
||||
'sepa_batch_id' => $this->repository->getMetaField($journal, 'sepa_batch_id'),
|
||||
'interest_date' => $this->repository->getMetaDateString($journal, 'interest_date'),
|
||||
'book_date' => $this->repository->getMetaDateString($journal, 'book_date'),
|
||||
'process_date' => $this->repository->getMetaDateString($journal, 'process_date'),
|
||||
@@ -116,7 +116,7 @@ class TransactionTransformer extends AbstractTransformer
|
||||
'invoice_date' => $this->repository->getMetaDateString($journal, 'invoice_date'),
|
||||
'internal_reference' => $this->repository->getMetaField($journal, 'internal_reference'),
|
||||
'bunq_payment_id' => $this->repository->getMetaField($journal, 'bunq_payment_id'),
|
||||
'importHashV2' => $this->repository->getMetaField($journal, 'importHashV2'),
|
||||
'import_hash_v2' => $this->repository->getMetaField($journal, 'import_hash_v2'),
|
||||
'recurrence_id' => (int)$this->repository->getMetaField($journal, 'recurrence_id'),
|
||||
'external_id' => $this->repository->getMetaField($journal, 'external_id'),
|
||||
'original_source' => $this->repository->getMetaField($journal, 'original-source'),
|
||||
|
415
app/Validation/AccountValidator.php
Normal file
415
app/Validation/AccountValidator.php
Normal file
@@ -0,0 +1,415 @@
|
||||
<?php
|
||||
/**
|
||||
* AccountValidator.php
|
||||
* Copyright (c) 2019 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\Validation;
|
||||
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use Log;
|
||||
|
||||
/**
|
||||
* Class AccountValidator
|
||||
*/
|
||||
class AccountValidator
|
||||
{
|
||||
/** @var bool */
|
||||
public $createMode;
|
||||
/** @var string */
|
||||
public $destError;
|
||||
/** @var Account */
|
||||
public $destination;
|
||||
/** @var Account */
|
||||
public $source;
|
||||
/** @var string */
|
||||
public $sourceError;
|
||||
/** @var AccountRepositoryInterface */
|
||||
private $accountRepository;
|
||||
/** @var array */
|
||||
private $combinations;
|
||||
/** @var string */
|
||||
private $transactionType;
|
||||
|
||||
/**
|
||||
* AccountValidator constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->createMode = false;
|
||||
$this->destError = 'No error yet.';
|
||||
$this->sourceError = 'No error yet.';
|
||||
$this->combinations = config('firefly.source_dests');
|
||||
/** @var AccountRepositoryInterface accountRepository */
|
||||
$this->accountRepository = app(AccountRepositoryInterface::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $transactionType
|
||||
*/
|
||||
public function setTransactionType(string $transactionType): void
|
||||
{
|
||||
$this->transactionType = ucfirst($transactionType);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int|null $destinationId
|
||||
* @param $destinationName
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function validateDestination(?int $destinationId, $destinationName): bool
|
||||
{
|
||||
|
||||
Log::debug(sprintf('Now in AccountValidator::validateDestination(%d, "%s")', $destinationId, $destinationName));
|
||||
if (null === $this->source) {
|
||||
Log::error('Source is NULL');
|
||||
$this->destError = 'No source account validation has taken place yet. Please do this first or overrule the object.';
|
||||
|
||||
return false;
|
||||
}
|
||||
switch ($this->transactionType) {
|
||||
default:
|
||||
$this->destError = sprintf('AccountValidator::validateDestination cannot handle "%s", so it will always return false.', $this->transactionType);
|
||||
Log::error(sprintf('AccountValidator::validateDestination cannot handle "%s", so it will always return false.', $this->transactionType));
|
||||
|
||||
$result = false;
|
||||
break;
|
||||
|
||||
case TransactionType::WITHDRAWAL:
|
||||
$result = $this->validateWithdrawalDestination($destinationId, $destinationName);
|
||||
break;
|
||||
case TransactionType::DEPOSIT:
|
||||
$result = $this->validateDepositDestination($destinationId, $destinationName);
|
||||
break;
|
||||
case TransactionType::TRANSFER:
|
||||
$result = $this->validateTransferDestination($destinationId, $destinationName);
|
||||
break;
|
||||
//case TransactionType::OPENING_BALANCE:
|
||||
//case TransactionType::RECONCILIATION:
|
||||
// die(sprintf('Cannot handle type "%s"', $this->transactionType));
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int|null $accountId
|
||||
* @param string|null $accountName
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function validateSource(?int $accountId, ?string $accountName): bool
|
||||
{
|
||||
switch ($this->transactionType) {
|
||||
default:
|
||||
$result = false;
|
||||
$this->sourceError = sprintf('Cannot handle type "%s"', $this->transactionType);
|
||||
Log::error(sprintf('AccountValidator::validateSource cannot handle "%s", so it will always return false.', $this->transactionType));
|
||||
break;
|
||||
case TransactionType::WITHDRAWAL:
|
||||
$result = $this->validateWithdrawalSource($accountId, $accountName);
|
||||
break;
|
||||
case TransactionType::DEPOSIT:
|
||||
$result = $this->validateDepositSource($accountId, $accountName);
|
||||
break;
|
||||
case TransactionType::TRANSFER:
|
||||
$result = $this->validateTransferSource($accountId, $accountName);
|
||||
break;
|
||||
//case TransactionType::OPENING_BALANCE:
|
||||
//case TransactionType::RECONCILIATION:
|
||||
// die(sprintf('Cannot handle type "%s"', $this->transactionType));
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $accountType
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function canCreateType(string $accountType): bool
|
||||
{
|
||||
$result = false;
|
||||
switch ($accountType) {
|
||||
default:
|
||||
Log::error(sprintf('AccountValidator::validateSource cannot handle "%s".', $this->transactionType));
|
||||
break;
|
||||
case AccountType::ASSET:
|
||||
case AccountType::LOAN:
|
||||
case AccountType::MORTGAGE:
|
||||
case AccountType::DEBT:
|
||||
$result = false;
|
||||
break;
|
||||
case AccountType::EXPENSE:
|
||||
case AccountType::REVENUE:
|
||||
$result = true;
|
||||
break;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $accountTypes
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function canCreateTypes(array $accountTypes): bool
|
||||
{
|
||||
/** @var string $accountType */
|
||||
foreach ($accountTypes as $accountType) {
|
||||
if ($this->canCreateType($accountType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $validTypes
|
||||
* @param int|null $accountId
|
||||
* @param string|null $accountName
|
||||
*
|
||||
* @return Account|null
|
||||
*/
|
||||
private function findExistingAccount(array $validTypes, int $accountId, string $accountName): ?Account
|
||||
{
|
||||
$result = null;
|
||||
|
||||
// find by ID
|
||||
if ($accountId > 0) {
|
||||
$first = $this->accountRepository->findNull($accountId);
|
||||
if ((null !== $first) && in_array($first->accountType->type, $validTypes, true)) {
|
||||
$result = $first;
|
||||
}
|
||||
}
|
||||
|
||||
// find by name:
|
||||
if (null === $result && '' !== $accountName) {
|
||||
$second = $this->accountRepository->findByName($accountName, $validTypes);
|
||||
if (null !== $second) {
|
||||
$result = $second;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int|null $accountId
|
||||
* @param $accountName
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function validateDepositDestination(?int $accountId, $accountName): bool
|
||||
{
|
||||
$result = null;
|
||||
Log::debug(sprintf('Now in validateDepositDestination(%d, "%s")', $accountId, $accountName));
|
||||
|
||||
// source can be any of the following types.
|
||||
$validTypes = $this->combinations[$this->transactionType][$this->source->accountType->type] ?? [];
|
||||
if (null === $accountId && null === $accountName && false === $this->canCreateTypes($validTypes)) {
|
||||
// if both values are NULL we return false,
|
||||
// because the destination of a deposit can't be created.
|
||||
$this->destError = (string)trans('validation.deposit_dest_need_data');
|
||||
Log::error('Both values are NULL, cant create deposit destination.');
|
||||
$result = false;
|
||||
}
|
||||
// if the account can be created anyway we don't need to search.
|
||||
if (null === $result && true === $this->canCreateTypes($validTypes)) {
|
||||
Log::debug('Can create some of these types, so return true.');
|
||||
$this->createDestinationAccount($accountName);
|
||||
$result = true;
|
||||
}
|
||||
|
||||
if (null === $result) {
|
||||
// otherwise try to find the account:
|
||||
$search = $this->findExistingAccount($validTypes, (int)$accountId, (string)$accountName);
|
||||
if (null === $search) {
|
||||
$this->destError = (string)trans('validation.deposit_dest_bad_data', ['id' => $accountId, 'name' => $accountName]);
|
||||
$result = false;
|
||||
}
|
||||
if (null !== $search) {
|
||||
$this->destination = $search;
|
||||
$result = true;
|
||||
}
|
||||
}
|
||||
$result = $result ?? false;
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int|null $accountId
|
||||
* @param string|null $accountName
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function validateDepositSource(?int $accountId, ?string $accountName): bool
|
||||
{
|
||||
$result = null;
|
||||
// source can be any of the following types.
|
||||
$validTypes = array_keys($this->combinations[$this->transactionType]);
|
||||
if (null === $accountId && null === $accountName && false === $this->canCreateTypes($validTypes)) {
|
||||
// if both values are NULL return false,
|
||||
// because the source of a deposit can't be created.
|
||||
// (this never happens).
|
||||
$this->sourceError = (string)trans('validation.deposit_source_need_data');
|
||||
$result = false;
|
||||
}
|
||||
|
||||
// if the account can be created anyway we don't need to search.
|
||||
if (null === $result && true === $this->canCreateTypes($validTypes)) {
|
||||
// set the source to be a (dummy) revenue account.
|
||||
$result = true;
|
||||
}
|
||||
$result = $result ?? false;
|
||||
|
||||
// don't expect to end up here:
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int|null $accountId
|
||||
* @param $accountName
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function validateTransferDestination(?int $accountId, $accountName): bool
|
||||
{
|
||||
Log::debug(sprintf('Now in validateTransferDestination(%d, "%s")', $accountId, $accountName));
|
||||
// source can be any of the following types.
|
||||
$validTypes = $this->combinations[$this->transactionType][$this->source->accountType->type] ?? [];
|
||||
if (null === $accountId && null === $accountName && false === $this->canCreateTypes($validTypes)) {
|
||||
// if both values are NULL we return false,
|
||||
// because the destination of a transfer can't be created.
|
||||
$this->destError = (string)trans('validation.transfer_dest_need_data');
|
||||
Log::error('Both values are NULL, cant create transfer destination.');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// otherwise try to find the account:
|
||||
$search = $this->findExistingAccount($validTypes, (int)$accountId, (string)$accountName);
|
||||
if (null === $search) {
|
||||
$this->destError = (string)trans('validation.transfer_dest_bad_data', ['id' => $accountId, 'name' => $accountName]);
|
||||
|
||||
return false;
|
||||
}
|
||||
$this->destination = $search;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int|null $accountId
|
||||
* @param string|null $accountName
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function validateTransferSource(?int $accountId, ?string $accountName): bool
|
||||
{
|
||||
// source can be any of the following types.
|
||||
$validTypes = array_keys($this->combinations[$this->transactionType]);
|
||||
if (null === $accountId && null === $accountName && false === $this->canCreateTypes($validTypes)) {
|
||||
// if both values are NULL we return false,
|
||||
// because the source of a withdrawal can't be created.
|
||||
$this->sourceError = (string)trans('validation.transfer_source_need_data');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// otherwise try to find the account:
|
||||
$search = $this->findExistingAccount($validTypes, (int)$accountId, (string)$accountName);
|
||||
if (null === $search) {
|
||||
$this->sourceError = (string)trans('validation.transfer_source_bad_data', ['id' => $accountId, 'name' => $accountName]);
|
||||
|
||||
return false;
|
||||
}
|
||||
$this->source = $search;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int|null $accountId
|
||||
* @param string|null $accountName
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function validateWithdrawalDestination(?int $accountId, ?string $accountName): bool
|
||||
{
|
||||
// source can be any of the following types.
|
||||
$validTypes = $this->combinations[$this->transactionType][$this->source->accountType->type] ?? [];
|
||||
if (null === $accountId && null === $accountName && false === $this->canCreateTypes($validTypes)) {
|
||||
// if both values are NULL return false,
|
||||
// because the destination of a withdrawal can never be created automatically.
|
||||
$this->destError = (string)trans('validation.withdrawal_dest_need_data');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// if the account can be created anyway don't need to search.
|
||||
if (true === $this->canCreateTypes($validTypes)) {
|
||||
|
||||
return true;
|
||||
}
|
||||
// don't expect to end up here:
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int|null $accountId
|
||||
* @param string|null $accountName
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function validateWithdrawalSource(?int $accountId, ?string $accountName): bool
|
||||
{
|
||||
// source can be any of the following types.
|
||||
$validTypes = array_keys($this->combinations[$this->transactionType]);
|
||||
if (null === $accountId && null === $accountName && false === $this->canCreateTypes($validTypes)) {
|
||||
// if both values are NULL we return false,
|
||||
// because the source of a withdrawal can't be created.
|
||||
$this->sourceError = (string)trans('validation.withdrawal_source_need_data');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// otherwise try to find the account:
|
||||
$search = $this->findExistingAccount($validTypes, (int)$accountId, (string)$accountName);
|
||||
if (null === $search) {
|
||||
$this->sourceError = (string)trans('validation.withdrawal_source_bad_data', ['id' => $accountId, 'name' => $accountName]);
|
||||
|
||||
return false;
|
||||
}
|
||||
$this->source = $search;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -23,82 +23,59 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Validation;
|
||||
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Validation\Validator;
|
||||
use Log;
|
||||
|
||||
/**
|
||||
* Trait TransactionValidation
|
||||
*/
|
||||
trait TransactionValidation
|
||||
{
|
||||
|
||||
/**
|
||||
* Validates the given account information. Switches on given transaction type.
|
||||
*
|
||||
* @param Validator $validator
|
||||
*
|
||||
* @throws \FireflyIII\Exceptions\FireflyException
|
||||
*/
|
||||
public function validateAccountInformation(Validator $validator): void
|
||||
{
|
||||
$data = $validator->getData();
|
||||
$transactions = $data['transactions'] ?? [];
|
||||
$idField = 'description';
|
||||
$transactionType = $data['type'] ?? 'invalid';
|
||||
// get transaction type:
|
||||
if (!isset($data['type'])) {
|
||||
// the journal may exist in the request:
|
||||
/** @var Transaction $transaction */
|
||||
$transaction = $this->route()->parameter('transaction');
|
||||
if (null !== $transaction) {
|
||||
$transactionType = strtolower($transaction->transactionJournal->transactionType->type);
|
||||
}
|
||||
}
|
||||
$data = $validator->getData();
|
||||
$transactions = $data['transactions'] ?? [];
|
||||
|
||||
/** @var AccountValidator $accountValidator */
|
||||
$accountValidator = app(AccountValidator::class);
|
||||
|
||||
|
||||
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);
|
||||
$transactionType = $transaction['type'] ?? 'invalid';
|
||||
$accountValidator->setTransactionType($transactionType);
|
||||
|
||||
$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);
|
||||
// validate source account.
|
||||
$sourceId = isset($transaction['source_id']) ? (int)$transaction['source_id'] : null;
|
||||
$sourceName = $transaction['source_name'] ?? null;
|
||||
$validSource = $accountValidator->validateSource($sourceId, $sourceName);
|
||||
|
||||
$idField = 'transactions.' . $index . '.destination_id';
|
||||
$nameField = 'transactions.' . $index . '.destination_name';
|
||||
$destinationAccount = $this->assetAccountExists($validator, $destinationId, $destinationName, $idField, $nameField);
|
||||
break;
|
||||
default:
|
||||
$validator->errors()->add($idField, (string)trans('validation.invalid_account_info'));
|
||||
|
||||
return;
|
||||
// do something with result:
|
||||
if (false === $validSource) {
|
||||
$validator->errors()->add(sprintf('transactions.%d.source_id', $index), $accountValidator->sourceError);
|
||||
$validator->errors()->add(sprintf('transactions.%d.source_name', $index), $accountValidator->sourceError);
|
||||
|
||||
return;
|
||||
}
|
||||
// add some errors in case of same account submitted:
|
||||
if (null !== $sourceAccount && null !== $destinationAccount && $sourceAccount->id === $destinationAccount->id) {
|
||||
$validator->errors()->add($idField, (string)trans('validation.source_equals_destination'));
|
||||
// validate destination account
|
||||
$destinationId = isset($transaction['destination_id']) ? (int)$transaction['destination_id'] : null;
|
||||
$destinationName = $transaction['destination_name'] ?? null;
|
||||
$validDestination = $accountValidator->validateDestination($destinationId, $destinationName);
|
||||
// do something with result:
|
||||
if (false === $validDestination) {
|
||||
$validator->errors()->add(sprintf('transactions.%d.destination_id', $index), $accountValidator->destError);
|
||||
$validator->errors()->add(sprintf('transactions.%d.destination_name', $index), $accountValidator->destError);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,18 +87,17 @@ trait TransactionValidation
|
||||
*/
|
||||
public function validateDescriptions(Validator $validator): void
|
||||
{
|
||||
$data = $validator->getData();
|
||||
$transactions = $data['transactions'] ?? [];
|
||||
$journalDescription = (string)($data['description'] ?? null);
|
||||
$validDescriptions = 0;
|
||||
$data = $validator->getData();
|
||||
$transactions = $data['transactions'] ?? [];
|
||||
$validDescriptions = 0;
|
||||
foreach ($transactions as $index => $transaction) {
|
||||
if ('' !== (string)($transaction['description'] ?? null)) {
|
||||
$validDescriptions++;
|
||||
}
|
||||
}
|
||||
|
||||
// no valid descriptions and empty journal description? error.
|
||||
if (0 === $validDescriptions && '' === $journalDescription) {
|
||||
// no valid descriptions?
|
||||
if (0 === $validDescriptions) {
|
||||
$validator->errors()->add('description', (string)trans('validation.filled', ['attribute' => (string)trans('validation.attributes.description')]));
|
||||
}
|
||||
}
|
||||
@@ -149,21 +125,15 @@ trait TransactionValidation
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an error to the validator when any transaction descriptions are equal to the journal description.
|
||||
*
|
||||
* @param Validator $validator
|
||||
*/
|
||||
public function validateJournalDescription(Validator $validator): void
|
||||
public function validateGroupDescription(Validator $validator): void
|
||||
{
|
||||
$data = $validator->getData();
|
||||
$transactions = $data['transactions'] ?? [];
|
||||
$journalDescription = (string)($data['description'] ?? null);
|
||||
foreach ($transactions as $index => $transaction) {
|
||||
$description = (string)($transaction['description'] ?? null);
|
||||
// description cannot be equal to journal description.
|
||||
if ($description === $journalDescription) {
|
||||
$validator->errors()->add('transactions.' . $index . '.description', (string)trans('validation.equal_description'));
|
||||
}
|
||||
$data = $validator->getData();
|
||||
$transactions = $data['transactions'] ?? [];
|
||||
$groupTitle = $data['group_title'] ?? '';
|
||||
if ('' === $groupTitle && \count($transactions) > 1) {
|
||||
$validator->errors()->add('group_title', (string)trans('validation.group_title_mandatory'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,126 +209,129 @@ trait TransactionValidation
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an error to the validator when the user submits a split transaction (more than 1 transactions)
|
||||
* but does not give them a description.
|
||||
* All types of splits must be equal.
|
||||
*
|
||||
* @param Validator $validator
|
||||
*/
|
||||
public function validateSplitDescriptions(Validator $validator): void
|
||||
public function validateTransactionTypes(Validator $validator): void
|
||||
{
|
||||
$data = $validator->getData();
|
||||
$transactions = $data['transactions'] ?? [];
|
||||
$types = [];
|
||||
foreach ($transactions as $index => $transaction) {
|
||||
$description = (string)($transaction['description'] ?? null);
|
||||
// filled description is mandatory for split transactions.
|
||||
if ('' === $description && \count($transactions) > 1) {
|
||||
$validator->errors()->add(
|
||||
'transactions.' . $index . '.description',
|
||||
(string)trans('validation.filled', ['attribute' => (string)trans('validation.attributes.transaction_description')])
|
||||
);
|
||||
}
|
||||
$types[] = $transaction['type'] ?? 'invalid';
|
||||
}
|
||||
$unique = array_unique($types);
|
||||
if (count($unique) > 1) {
|
||||
$validator->errors()->add('transactions.0.type', (string)trans('validation.transaction_types_equal'));
|
||||
|
||||
return;
|
||||
}
|
||||
$first = $unique[0] ?? 'invalid';
|
||||
if ('invalid' === $first) {
|
||||
$validator->errors()->add('transactions.0.type', (string)trans('validation.invalid_transaction_type'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
/** @var User $admin */
|
||||
$admin = auth()->user();
|
||||
$accountId = (int)$accountId;
|
||||
$accountName = (string)$accountName;
|
||||
// both empty? hard exit.
|
||||
if ($accountId < 1 && '' === $accountName) {
|
||||
$validator->errors()->add($idField, (string)trans('validation.filled', ['attribute' => $idField]));
|
||||
|
||||
return null;
|
||||
}
|
||||
// ID belongs to user and is asset account:
|
||||
/** @var AccountRepositoryInterface $repository */
|
||||
$repository = app(AccountRepositoryInterface::class);
|
||||
$repository->setUser($admin);
|
||||
$set = $repository->getAccountsById([$accountId]);
|
||||
Log::debug(sprintf('Count of accounts found by ID %d is: %d', $accountId, $set->count()));
|
||||
if (1 === $set->count()) {
|
||||
/** @var Account $first */
|
||||
$first = $set->first();
|
||||
if ($first->accountType->type !== AccountType::ASSET) {
|
||||
$validator->errors()->add($idField, (string)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, (string)trans('validation.belongs_user'));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return $account;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
/** @var User $admin */
|
||||
$admin = auth()->user();
|
||||
$accountId = (int)$accountId;
|
||||
$accountName = (string)$accountName;
|
||||
// both empty? done!
|
||||
if ($accountId < 1 && '' === $accountName) {
|
||||
return null;
|
||||
}
|
||||
if (0 !== $accountId) {
|
||||
// ID belongs to user and is $type account:
|
||||
/** @var AccountRepositoryInterface $repository */
|
||||
$repository = app(AccountRepositoryInterface::class);
|
||||
$repository->setUser($admin);
|
||||
$set = $repository->getAccountsById([$accountId]);
|
||||
if (1 === $set->count()) {
|
||||
/** @var Account $first */
|
||||
$first = $set->first();
|
||||
if ($first->accountType->type !== $type) {
|
||||
$validator->errors()->add($idField, (string)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;
|
||||
}
|
||||
// /**
|
||||
// * 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
|
||||
// {
|
||||
// /** @var User $admin */
|
||||
// $admin = auth()->user();
|
||||
// $accountId = (int)$accountId;
|
||||
// $accountName = (string)$accountName;
|
||||
// // both empty? hard exit.
|
||||
// if ($accountId < 1 && '' === $accountName) {
|
||||
// $validator->errors()->add($idField, (string)trans('validation.filled', ['attribute' => $idField]));
|
||||
//
|
||||
// return null;
|
||||
// }
|
||||
// // ID belongs to user and is asset account:
|
||||
// /** @var AccountRepositoryInterface $repository */
|
||||
// $repository = app(AccountRepositoryInterface::class);
|
||||
// $repository->setUser($admin);
|
||||
// $set = $repository->getAccountsById([$accountId]);
|
||||
// Log::debug(sprintf('Count of accounts found by ID %d is: %d', $accountId, $set->count()));
|
||||
// if (1 === $set->count()) {
|
||||
// /** @var Account $first */
|
||||
// $first = $set->first();
|
||||
// if ($first->accountType->type !== AccountType::ASSET) {
|
||||
// $validator->errors()->add($idField, (string)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, (string)trans('validation.belongs_user'));
|
||||
//
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// return $account;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 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
|
||||
// {
|
||||
// /** @var User $admin */
|
||||
// $admin = auth()->user();
|
||||
// $accountId = (int)$accountId;
|
||||
// $accountName = (string)$accountName;
|
||||
// // both empty? done!
|
||||
// if ($accountId < 1 && '' === $accountName) {
|
||||
// return null;
|
||||
// }
|
||||
// if (0 !== $accountId) {
|
||||
// // ID belongs to user and is $type account:
|
||||
// /** @var AccountRepositoryInterface $repository */
|
||||
// $repository = app(AccountRepositoryInterface::class);
|
||||
// $repository->setUser($admin);
|
||||
// $set = $repository->getAccountsById([$accountId]);
|
||||
// if (1 === $set->count()) {
|
||||
// /** @var Account $first */
|
||||
// $first = $set->first();
|
||||
// if ($first->accountType->type !== $type) {
|
||||
// $validator->errors()->add($idField, (string)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;
|
||||
// }
|
||||
}
|
||||
|
@@ -359,56 +359,56 @@ return [
|
||||
],
|
||||
|
||||
// SEPA end to end ID
|
||||
'sepa-ct-id' => [
|
||||
'sepa_ct_id' => [
|
||||
'mappable' => false,
|
||||
'pre-process-map' => false,
|
||||
'converter' => 'Description',
|
||||
'field' => 'sepa_ct_id',
|
||||
],
|
||||
// SEPA opposing account identifier
|
||||
'sepa-ct-op' => [
|
||||
'sepa_ct_op' => [
|
||||
'mappable' => false,
|
||||
'pre-process-map' => false,
|
||||
'converter' => 'Description',
|
||||
'field' => 'sepa_ct_op',
|
||||
],
|
||||
// SEPA Direct Debit Mandate Identifier
|
||||
'sepa-db' => [
|
||||
'sepa_db' => [
|
||||
'mappable' => false,
|
||||
'pre-process-map' => false,
|
||||
'converter' => 'Description',
|
||||
'field' => 'sepa_db',
|
||||
],
|
||||
// SEPA clearing code
|
||||
'sepa-cc' => [
|
||||
'sepa_cc' => [
|
||||
'mappable' => false,
|
||||
'pre-process-map' => false,
|
||||
'converter' => 'Description',
|
||||
'field' => 'sepa_cc',
|
||||
],
|
||||
// SEPA country
|
||||
'sepa-country' => [
|
||||
'sepa_country' => [
|
||||
'mappable' => false,
|
||||
'pre-process-map' => false,
|
||||
'converter' => 'Description',
|
||||
'field' => 'sepa_country',
|
||||
],
|
||||
// SEPA external purpose
|
||||
'sepa-ep' => [
|
||||
'sepa_ep' => [
|
||||
'mappable' => false,
|
||||
'pre-process-map' => false,
|
||||
'converter' => 'Description',
|
||||
'field' => 'sepa_ep',
|
||||
],
|
||||
// SEPA creditor identifier
|
||||
'sepa-ci' => [
|
||||
'sepa_ci' => [
|
||||
'mappable' => false,
|
||||
'pre-process-map' => false,
|
||||
'converter' => 'Description',
|
||||
'field' => 'sepa_ci',
|
||||
],
|
||||
// SEPA Batch ID
|
||||
'sepa-batch-id' => [
|
||||
'sepa_batch_id' => [
|
||||
'mappable' => false,
|
||||
'pre-process-map' => false,
|
||||
'converter' => 'Description',
|
||||
|
@@ -291,14 +291,14 @@ return [
|
||||
'column_rabo-debit-credit' => 'Rabobank specific debit/credit indicator',
|
||||
'column_ing-debit-credit' => 'ING specific debit/credit indicator',
|
||||
'column_generic-debit-credit' => 'Generic bank debit/credit indicator',
|
||||
'column_sepa-ct-id' => 'SEPA end-to-end Identifier',
|
||||
'column_sepa-ct-op' => 'SEPA Opposing Account Identifier',
|
||||
'column_sepa-db' => 'SEPA Mandate Identifier',
|
||||
'column_sepa-cc' => 'SEPA Clearing Code',
|
||||
'column_sepa-ci' => 'SEPA Creditor Identifier',
|
||||
'column_sepa-ep' => 'SEPA External Purpose',
|
||||
'column_sepa-country' => 'SEPA Country Code',
|
||||
'column_sepa-batch-id' => 'SEPA Batch ID',
|
||||
'column_sepa_ct_id' => 'SEPA end-to-end Identifier',
|
||||
'column_sepa_ct_op' => 'SEPA Opposing Account Identifier',
|
||||
'column_sepa_db' => 'SEPA Mandate Identifier',
|
||||
'column_sepa_cc' => 'SEPA Clearing Code',
|
||||
'column_sepa_ci' => 'SEPA Creditor Identifier',
|
||||
'column_sepa_ep' => 'SEPA External Purpose',
|
||||
'column_sepa_country' => 'SEPA Country Code',
|
||||
'column_sepa_batch_id' => 'SEPA Batch ID',
|
||||
'column_tags-comma' => 'Tags (comma separated)',
|
||||
'column_tags-space' => 'Tags (space separated)',
|
||||
'column_account-number' => 'Asset account (account number)',
|
||||
|
@@ -106,14 +106,14 @@ return [
|
||||
'account_on_spectre' => 'Account (Spectre)',
|
||||
'account_on_ynab' => 'Account (YNAB)',
|
||||
'do_import' => 'Import from this account',
|
||||
'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-ep' => 'SEPA External Purpose',
|
||||
'sepa-ci' => 'SEPA Creditor Identifier',
|
||||
'sepa-batch-id' => 'SEPA Batch ID',
|
||||
'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_ep' => 'SEPA External Purpose',
|
||||
'sepa_ci' => 'SEPA Creditor Identifier',
|
||||
'sepa_batch_id' => 'SEPA Batch ID',
|
||||
'external_id' => 'External ID',
|
||||
'account_at_bunq' => 'Account with bunq',
|
||||
'file_name' => 'File name',
|
||||
|
@@ -33,9 +33,12 @@ return [
|
||||
'rule_trigger_value' => 'This value is invalid for the selected trigger.',
|
||||
'rule_action_value' => 'This value is invalid for the selected action.',
|
||||
'file_already_attached' => 'Uploaded file ":name" is already attached to this object.',
|
||||
'file_attached' => 'Succesfully uploaded file ":name".',
|
||||
'file_attached' => 'Successfully uploaded file ":name".',
|
||||
'must_exist' => 'The ID in field :attribute does not exist in the database.',
|
||||
'all_accounts_equal' => 'All accounts in this field must be equal.',
|
||||
'group_title_mandatory' => 'A group title is mandatory when there is more than one transaction.',
|
||||
'transaction_types_equal' => 'All splits must be of the same type.',
|
||||
'invalid_transaction_type' => 'Invalid transaction type.',
|
||||
'invalid_selection' => 'Your selection is invalid.',
|
||||
'belongs_user' => 'This value is invalid for this field.',
|
||||
'at_least_one_transaction' => 'Need at least one transaction.',
|
||||
@@ -164,4 +167,19 @@ return [
|
||||
'rule-trigger.4' => 'rule trigger #4',
|
||||
'rule-trigger.5' => 'rule trigger #5',
|
||||
],
|
||||
|
||||
// validation of accounts:
|
||||
'withdrawal_source_need_data' => 'Need to get a valid source account ID and/or valid source account name to continue.',
|
||||
'withdrawal_source_bad_data' => 'Could not find a valid source account when searching for ID ":id" or name ":name".',
|
||||
'withdrawal_dest_need_data' => 'Need to get a valid destination account ID and/or valid destination account name to continue.',
|
||||
|
||||
'deposit_source_need_data' => 'Need to get a valid source account ID and/or valid source account name to continue.',
|
||||
'deposit_source_bad_data' => 'Could not find a valid source account when searching for ID ":id" or name ":name".',
|
||||
'deposit_dest_need_data' => 'Need to get a valid destination account ID and/or valid destination account name to continue.',
|
||||
'deposit_dest_bad_data' => 'Could not find a valid destination account when searching for ID ":id" or name ":name".',
|
||||
|
||||
'transfer_source_need_data' => 'Need to get a valid source account ID and/or valid source account name to continue.',
|
||||
'transfer_source_bad_data' => 'Could not find a valid source account when searching for ID ":id" or name ":name".',
|
||||
'transfer_dest_need_data' => 'Need to get a valid destination account ID and/or valid destination account name to continue.',
|
||||
'transfer_dest_bad_data' => 'Could not find a valid destination account when searching for ID ":id" or name ":name".',
|
||||
];
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<div class="list-group">
|
||||
{% for group in groups %}
|
||||
<a class="list-group-item">
|
||||
<a class="list-group-item" href="{{ route('transactions.show', [group.id]) }}">
|
||||
{% for transaction in group.transactions %}
|
||||
{{ transaction.description }}
|
||||
<span class="pull-right small">
|
||||
|
@@ -1,19 +1 @@
|
||||
<h1 style="color:red;">DO NOT USE ME</h1>
|
||||
{#<div class="list-group">
|
||||
{% for transaction in transactions %}
|
||||
<a class="list-group-item" title="{{ transaction.date.formatLocalized(trans('config.month_and_day')) }}"
|
||||
{% if transaction.transaction_type_type == 'Opening balance' %}
|
||||
href="#"
|
||||
{% else %}
|
||||
href="{{ route('transactions.show',transaction.journal_id) }}"
|
||||
{% endif %}
|
||||
>
|
||||
{{ transaction|transactionIcon }}
|
||||
{{ transaction|transactionDescription }}
|
||||
<span class="pull-right small">
|
||||
{{ transaction|transactionAmount }}
|
||||
</span>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
#}
|
||||
<h1 style="color:red;">DO NOT USE ME</h1>
|
@@ -1,3 +1,7 @@
|
||||
<h1 style="color:red;">DO NOT USE</h1>
|
||||
|
||||
|
||||
{#
|
||||
{{ transactions.render|raw }}
|
||||
|
||||
<table class="table table-hover table-condensed {% if sorting %}sortable-table{% endif %}">
|
||||
@@ -10,17 +14,14 @@
|
||||
<th class="hidden-xs hidden-sm">{{ trans('list.date') }}</th>
|
||||
<th class="hidden-xs hidden-sm hidden-md">{{ trans('list.from') }}</th>
|
||||
<th class="hidden-xs hidden-sm hidden-md">{{ trans('list.to') }}</th>
|
||||
{# Hide budgets? #}
|
||||
{% if not hideBudgets %}
|
||||
<th class="hidden-xs"><i class="fa fa-tasks fa-fw" title="{{ trans('list.budget') }}"></i></th>
|
||||
{% endif %}
|
||||
|
||||
{# Hide categories? #}
|
||||
{% if not hideCategories %}
|
||||
<th class="hidden-xs"><i class="fa fa-bar-chart fa-fw" title="{{ trans('list.category') }}"></i></th>
|
||||
{% endif %}
|
||||
|
||||
{# Hide bills? #}
|
||||
{% if not hideBills %}
|
||||
<th class="hidden-xs"><i class="fa fa-fw fa-calendar-o" title="{{ trans('list.bill') }}"></i></th>
|
||||
{% endif %}
|
||||
@@ -75,3 +76,4 @@
|
||||
var edit_bulk_selected_txt = "{{ trans('firefly.bulk_edit')|escape('js') }}";
|
||||
var delete_selected_txt = "{{ trans('firefly.delete')|escape('js') }}";
|
||||
</script>
|
||||
#}
|
@@ -214,7 +214,7 @@
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{# all other meta values #}
|
||||
{% for metaField in ['external_id','bunq_payment_id','internal_reference','sepa-batch-id','sepa-ct-id','sepa-ct-op','sepa-db','sepa-country','sepa-cc','sepa-ep','sepa-ci'] %}
|
||||
{% for metaField in ['external_id','bunq_payment_id','internal_reference','sepa_batch_id','sepa_ct_id','sepa_ct_op','sepa_db','sepa_country','sepa_cc','sepa_ep','sepa_ci'] %}
|
||||
{% if journalHasMeta(journal, metaField) %}
|
||||
<tr>
|
||||
<td>{{ trans('list.'~metaField) }}</td>
|
||||
|
@@ -21,23 +21,17 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
Route::group(
|
||||
['namespace' => 'FireflyIII\Http\Controllers\System',
|
||||
'as' => 'installer.', 'prefix' => 'install'], function () {
|
||||
Route::get('', ['uses' => 'InstallController@index', 'as' => 'index']);
|
||||
Route::post('runCommand', ['uses' => 'InstallController@runCommand', 'as' => 'runCommand']);
|
||||
// Route::post('migrate', ['uses' => 'InstallController@migrate', 'as' => 'migrate']);
|
||||
// Route::post('keys', ['uses' => 'InstallController@keys', 'as' => 'keys']);
|
||||
// Route::post('upgrade', ['uses' => 'InstallController@upgrade', 'as' => 'upgrade']);
|
||||
// Route::post('verify', ['uses' => 'InstallController@verify', 'as' => 'verify']);
|
||||
// Route::post('decrypt', ['uses' => 'InstallController@decrypt', 'as' => 'decrypt']);
|
||||
}
|
||||
);
|
||||
|
||||
Route::group(
|
||||
['middleware' => 'binders-only','namespace' => 'FireflyIII\Http\Controllers\System', 'as' => 'cron.', 'prefix' => 'cron'], function () {
|
||||
Route::get('run/{cliToken}', ['uses' => 'CronController@cron', 'as' => 'cron']);
|
||||
['middleware' => 'binders-only', 'namespace' => 'FireflyIII\Http\Controllers\System', 'as' => 'cron.', 'prefix' => 'cron'], static function () {
|
||||
Route::get('run/{cliToken}', ['uses' => 'CronController@cron', 'as' => 'cron']);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -45,7 +39,7 @@ Route::group(
|
||||
* These routes only work when the user is NOT logged in.
|
||||
*/
|
||||
Route::group(
|
||||
['middleware' => 'user-not-logged-in', 'namespace' => 'FireflyIII\Http\Controllers'], function () {
|
||||
['middleware' => 'user-not-logged-in', 'namespace' => 'FireflyIII\Http\Controllers'], static function () {
|
||||
|
||||
// Authentication Routes...
|
||||
Route::get('login', 'Auth\LoginController@showLoginForm')->name('login');
|
||||
@@ -58,7 +52,7 @@ Route::group(
|
||||
// Password Reset Routes...
|
||||
Route::get('password/reset/{token}', ['uses' => 'Auth\ResetPasswordController@showResetForm', 'as' => 'password.reset']);
|
||||
Route::post('password/email', ['uses' => 'Auth\ForgotPasswordController@sendResetLinkEmail', 'as' => 'password.email']);
|
||||
Route::post('password/reset',['uses' => 'Auth\ResetPasswordController@reset']);
|
||||
Route::post('password/reset', ['uses' => 'Auth\ResetPasswordController@reset']);
|
||||
Route::get('password/reset', ['uses' => 'Auth\ForgotPasswordController@showLinkRequestForm', 'as' => 'password.reset.request']);
|
||||
|
||||
// Change email routes:
|
||||
@@ -148,9 +142,10 @@ Route::group(
|
||||
);
|
||||
|
||||
// show reconciliation
|
||||
Route::get('reconcile/show/{tj}', ['uses' => 'Account\ReconcileController@show', 'as' => 'reconcile.show']);
|
||||
Route::get('reconcile/edit/{tj}', ['uses' => 'Account\ReconcileController@edit', 'as' => 'reconcile.edit']);
|
||||
Route::post('reconcile/update/{tj}', ['uses' => 'Account\ReconcileController@update', 'as' => 'reconcile.update']);
|
||||
// TODO improve me
|
||||
//Route::get('reconcile/show/{transactionGroup}', ['uses' => 'Account\ReconcileController@show', 'as' => 'reconcile.show']);
|
||||
//Route::get('reconcile/edit/{transactionGroup}', ['uses' => 'Account\ReconcileController@edit', 'as' => 'reconcile.edit']);
|
||||
//Route::post('reconcile/update/{transactionGroup}', ['uses' => 'Account\ReconcileController@update', 'as' => 'reconcile.update']);
|
||||
|
||||
|
||||
}
|
||||
@@ -553,10 +548,11 @@ Route::group(
|
||||
// for auto complete
|
||||
|
||||
|
||||
Route::get('transaction-journals/all', ['uses' => 'Json\AutoCompleteController@allTransactionJournals', 'as' => 'all-transaction-journals']);
|
||||
Route::get('transaction-journals/with-id/{tj}', ['uses' => 'Json\AutoCompleteController@journalsWithId', 'as' => 'journals-with-id']);
|
||||
Route::get('transaction-journals/{what}', ['uses' => 'Json\AutoCompleteController@transactionJournals', 'as' => 'transaction-journals']);
|
||||
// Route::get('transaction-types', ['uses' => 'Json\AutoCompleteController@transactionTypes', 'as' => 'transaction-types']);
|
||||
// TODO improve me.
|
||||
//Route::get('transaction-journals/all', ['uses' => 'Json\AutoCompleteController@allTransactionJournals', 'as' => 'all-transaction-journals']);
|
||||
//Route::get('transaction-journals/with-id/{tj}', ['uses' => 'Json\AutoCompleteController@journalsWithId', 'as' => 'journals-with-id']);
|
||||
//Route::get('transaction-journals/{what}', ['uses' => 'Json\AutoCompleteController@transactionJournals', 'as' => 'transaction-journals']);
|
||||
// Route::get('transaction-types', ['uses' => 'Json\AutoCompleteController@transactionTypes', 'as' => 'transaction-types']);
|
||||
|
||||
// boxes
|
||||
Route::get('box/balance', ['uses' => 'Json\BoxController@balance', 'as' => 'box.balance']);
|
||||
@@ -875,15 +871,19 @@ Route::group(
|
||||
Route::group(
|
||||
['middleware' => 'user-full-auth', 'namespace' => 'FireflyIII\Http\Controllers', 'prefix' => 'transactions', 'as' => 'transactions.'], function () {
|
||||
|
||||
Route::get('{what}/all', ['uses' => 'TransactionController@indexAll', 'as' => 'index.all'])->where(['what' => 'withdrawal|deposit|transfers|transfer']);
|
||||
Route::get('{what}/{start_date?}/{end_date?}', ['uses' => 'TransactionController@index', 'as' => 'index'])->where(
|
||||
['what' => 'withdrawal|deposit|transfers|transfer']
|
||||
);
|
||||
// Route::get('{what}/all', ['uses' => 'TransactionController@indexAll', 'as' => 'index.all'])->where(['what' => 'withdrawal|deposit|transfers|transfer']);
|
||||
// Route::get('{what}/{start_date?}/{end_date?}', ['uses' => 'TransactionController@index', 'as' => 'index'])->where(
|
||||
// ['what' => 'withdrawal|deposit|transfers|transfer']
|
||||
// );
|
||||
|
||||
Route::get('show/{tj}', ['uses' => 'TransactionController@show', 'as' => 'show']);
|
||||
Route::get('debug/{tj}', ['uses' => 'Transaction\SingleController@debugShow', 'as' => 'debug']);
|
||||
Route::post('reorder', ['uses' => 'TransactionController@reorder', 'as' => 'reorder']);
|
||||
Route::post('reconcile', ['uses' => 'TransactionController@reconcile', 'as' => 'reconcile']);
|
||||
//Route::get('show/{tj}', ['uses' => 'TransactionController@show', 'as' => 'show']);
|
||||
//Route::get('debug/{tj}', ['uses' => 'Transaction\SingleController@debugShow', 'as' => 'debug']);
|
||||
|
||||
//Route::get('show/{tj}', ['uses' => 'TransactionController@show', 'as' => 'show']);
|
||||
//Route::get('debug/{tj}', ['uses' => 'Transaction\SingleController@debugShow', 'as' => 'debug']);
|
||||
|
||||
//Route::post('reorder', ['uses' => 'TransactionController@reorder', 'as' => 'reorder']);
|
||||
//Route::post('reconcile', ['uses' => 'TransactionController@reconcile', 'as' => 'reconcile']);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -893,13 +893,13 @@ Route::group(
|
||||
Route::group(
|
||||
['middleware' => 'user-full-auth', 'namespace' => 'FireflyIII\Http\Controllers\Transaction', 'prefix' => 'transactions', 'as' => 'transactions.'],
|
||||
function () {
|
||||
Route::get('create/{what}', ['uses' => 'SingleController@create', 'as' => 'create'])->where(['what' => 'withdrawal|deposit|transfer']);
|
||||
Route::get('edit/{tj}', ['uses' => 'SingleController@edit', 'as' => 'edit']);
|
||||
Route::get('delete/{tj}', ['uses' => 'SingleController@delete', 'as' => 'delete']);
|
||||
Route::post('store/{what}', ['uses' => 'SingleController@store', 'as' => 'store'])->where(['what' => 'withdrawal|deposit|transfer']);
|
||||
Route::post('update/{tj}', ['uses' => 'SingleController@update', 'as' => 'update']);
|
||||
Route::post('destroy/{tj}', ['uses' => 'SingleController@destroy', 'as' => 'destroy']);
|
||||
Route::get('clone/{tj}', ['uses' => 'SingleController@cloneTransaction', 'as' => 'clone']);
|
||||
// Route::get('create/{what}', ['uses' => 'SingleController@create', 'as' => 'create'])->where(['what' => 'withdrawal|deposit|transfer']);
|
||||
// Route::get('edit/{tj}', ['uses' => 'SingleController@edit', 'as' => 'edit']);
|
||||
// Route::get('delete/{tj}', ['uses' => 'SingleController@delete', 'as' => 'delete']);
|
||||
// Route::post('store/{what}', ['uses' => 'SingleController@store', 'as' => 'store'])->where(['what' => 'withdrawal|deposit|transfer']);
|
||||
// Route::post('update/{tj}', ['uses' => 'SingleController@update', 'as' => 'update']);
|
||||
// Route::post('destroy/{tj}', ['uses' => 'SingleController@destroy', 'as' => 'destroy']);
|
||||
// Route::get('clone/{tj}', ['uses' => 'SingleController@cloneTransaction', 'as' => 'clone']);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -933,8 +933,8 @@ Route::group(
|
||||
Route::group(
|
||||
['middleware' => 'user-full-auth', 'namespace' => 'FireflyIII\Http\Controllers\Transaction', 'prefix' => 'transactions/split',
|
||||
'as' => 'transactions.split.'], function () {
|
||||
Route::get('edit/{tj}', ['uses' => 'SplitController@edit', 'as' => 'edit']);
|
||||
Route::post('update/{tj}', ['uses' => 'SplitController@update', 'as' => 'update']);
|
||||
// Route::get('edit/{tj}', ['uses' => 'SplitController@edit', 'as' => 'edit']);
|
||||
// Route::post('update/{tj}', ['uses' => 'SplitController@update', 'as' => 'update']);
|
||||
|
||||
}
|
||||
);
|
||||
@@ -945,8 +945,8 @@ Route::group(
|
||||
Route::group(
|
||||
['middleware' => 'user-full-auth', 'namespace' => 'FireflyIII\Http\Controllers\Transaction', 'prefix' => 'transactions/convert',
|
||||
'as' => 'transactions.convert.'], function () {
|
||||
Route::get('{transactionType}/{tj}', ['uses' => 'ConvertController@index', 'as' => 'index']);
|
||||
Route::post('{transactionType}/{tj}', ['uses' => 'ConvertController@postIndex', 'as' => 'index.post']);
|
||||
// Route::get('{transactionType}/{tj}', ['uses' => 'ConvertController@index', 'as' => 'index']);
|
||||
// Route::post('{transactionType}/{tj}', ['uses' => 'ConvertController@postIndex', 'as' => 'index.post']);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -956,8 +956,7 @@ Route::group(
|
||||
Route::group(
|
||||
['middleware' => 'user-full-auth', 'namespace' => 'FireflyIII\Http\Controllers\Transaction', 'prefix' => 'transactions/link', 'as' => 'transactions.link.'],
|
||||
function () {
|
||||
Route::post('store/{tj}', ['uses' => 'LinkController@store', 'as' => 'store']);
|
||||
|
||||
//Route::post('store/{tj}', ['uses' => 'LinkController@store', 'as' => 'store']);
|
||||
Route::get('delete/{journalLink}', ['uses' => 'LinkController@delete', 'as' => 'delete']);
|
||||
Route::get('switch/{journalLink}', ['uses' => 'LinkController@switchLink', 'as' => 'switch']);
|
||||
|
||||
|
Reference in New Issue
Block a user