Is now capable of updating transactions over the API.

This commit is contained in:
James Cole 2019-04-06 08:10:50 +02:00
parent b692cccdfb
commit c519b4d0df
36 changed files with 1840 additions and 709 deletions

View File

@ -24,7 +24,8 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers; namespace FireflyIII\Api\V1\Controllers;
use FireflyIII\Api\V1\Requests\TransactionRequest; use FireflyIII\Api\V1\Requests\TransactionStoreRequest;
use FireflyIII\Api\V1\Requests\TransactionUpdateRequest;
use FireflyIII\Events\StoredTransactionGroup; use FireflyIII\Events\StoredTransactionGroup;
use FireflyIII\Events\UpdatedTransactionGroup; use FireflyIII\Events\UpdatedTransactionGroup;
use FireflyIII\Exceptions\FireflyException; use FireflyIII\Exceptions\FireflyException;
@ -32,6 +33,7 @@ use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\TransactionGroup; use FireflyIII\Models\TransactionGroup;
use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionJournal;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use FireflyIII\Repositories\TransactionGroup\TransactionGroupRepositoryInterface;
use FireflyIII\Support\Http\Api\TransactionFilter; use FireflyIII\Support\Http\Api\TransactionFilter;
use FireflyIII\Transformers\AttachmentTransformer; use FireflyIII\Transformers\AttachmentTransformer;
use FireflyIII\Transformers\PiggyBankEventTransformer; use FireflyIII\Transformers\PiggyBankEventTransformer;
@ -44,16 +46,18 @@ use League\Fractal\Pagination\IlluminatePaginatorAdapter;
use League\Fractal\Resource\Collection as FractalCollection; use League\Fractal\Resource\Collection as FractalCollection;
use League\Fractal\Resource\Item; use League\Fractal\Resource\Item;
use League\Fractal\Serializer\JsonApiSerializer; use League\Fractal\Serializer\JsonApiSerializer;
use Log;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/** /**
* Class TransactionController * Class TransactionController
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/ */
class TransactionController extends Controller class TransactionController extends Controller
{ {
use TransactionFilter; use TransactionFilter;
/** @var TransactionGroupRepositoryInterface Group repository. */
private $groupRepository;
/** @var JournalRepositoryInterface The journal repository */ /** @var JournalRepositoryInterface The journal repository */
private $repository; private $repository;
@ -68,9 +72,10 @@ class TransactionController extends Controller
/** @var User $admin */ /** @var User $admin */
$admin = auth()->user(); $admin = auth()->user();
/** @var JournalRepositoryInterface repository */ $this->repository = app(JournalRepositoryInterface::class);
$this->repository = app(JournalRepositoryInterface::class); $this->groupRepository = app(TransactionGroupRepositoryInterface::class);
$this->repository->setUser($admin); $this->repository->setUser($admin);
$this->groupRepository->setUser($admin);
return $next($request); return $next($request);
} }
@ -247,18 +252,16 @@ class TransactionController extends Controller
/** /**
* Store a new transaction. * Store a new transaction.
* *
* @param TransactionRequest $request * @param TransactionStoreRequest $request
* *
* @param JournalRepositoryInterface $repository
*
* @throws FireflyException
* @return JsonResponse * @return JsonResponse
* @throws FireflyException
*/ */
public function store(TransactionRequest $request, JournalRepositoryInterface $repository): JsonResponse public function store(TransactionStoreRequest $request): JsonResponse
{ {
$data = $request->getAll(); $data = $request->getAll();
$data['user'] = auth()->user()->id; $data['user'] = auth()->user()->id;
$transactionGroup = $repository->store($data); $transactionGroup = $this->groupRepository->store($data);
event(new StoredTransactionGroup($transactionGroup)); event(new StoredTransactionGroup($transactionGroup));
@ -294,16 +297,16 @@ class TransactionController extends Controller
/** /**
* Update a transaction. * Update a transaction.
* *
* @param TransactionRequest $request * @param TransactionUpdateRequest $request
* @param TransactionGroup $transactionGroup * @param TransactionGroup $transactionGroup
* *
* @return JsonResponse * @return JsonResponse
*/ */
public function update(TransactionRequest $request, TransactionGroup $transactionGroup): JsonResponse public function update(TransactionUpdateRequest $request, TransactionGroup $transactionGroup): JsonResponse
{ {
Log::debug('Now in update routine.');
$data = $request->getAll(); $data = $request->getAll();
$data['user'] = auth()->user()->id; $transactionGroup = $this->groupRepository->update($transactionGroup, $data);
$transactionGroup = $this->repository->update($transactionGroup, $data);
$manager = new Manager(); $manager = new Manager();
$baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1';
$manager->setSerializer(new JsonApiSerializer($baseUrl)); $manager->setSerializer(new JsonApiSerializer($baseUrl));

View File

@ -1,7 +1,7 @@
<?php <?php
/** /**
* TransactionRequest.php * TransactionStoreRequest.php
* Copyright (c) 2018 thegrumpydictator@gmail.com * Copyright (c) 2018 thegrumpydictator@gmail.com
* *
* This file is part of Firefly III. * This file is part of Firefly III.
@ -33,9 +33,9 @@ use Illuminate\Validation\Validator;
/** /**
* Class TransactionRequest * Class TransactionStoreRequest
*/ */
class TransactionRequest extends Request class TransactionStoreRequest extends Request
{ {
use TransactionValidation; use TransactionValidation;
@ -82,6 +82,7 @@ class TransactionRequest extends Request
// transaction rules (in array for splits): // transaction rules (in array for splits):
'transactions.*.type' => 'required|in:withdrawal,deposit,transfer,opening-balance,reconciliation', 'transactions.*.type' => 'required|in:withdrawal,deposit,transfer,opening-balance,reconciliation',
'transactions.*.date' => ['required', new IsDateOrTime], 'transactions.*.date' => ['required', new IsDateOrTime],
'transactions.*.order' => 'numeric|min:0',
// currency info // currency info
'transactions.*.currency_id' => 'numeric|exists:transaction_currencies,id', 'transactions.*.currency_id' => 'numeric|exists:transaction_currencies,id',
@ -144,10 +145,6 @@ class TransactionRequest extends Request
'transactions.*.invoice_date' => 'date|nullable', 'transactions.*.invoice_date' => 'date|nullable',
]; ];
if ('PUT' === $this->method()) {
unset($rules['transactions.*.type'], $rules['transactions.*.piggy_bank_id'], $rules['transactions.*.piggy_bank_name']);
}
return $rules; return $rules;
@ -156,7 +153,7 @@ class TransactionRequest extends Request
/** /**
* Configure the validator instance. * Configure the validator instance.
* *
* @param Validator $validator * @param Validator $validator
* *
* @return void * @return void
*/ */
@ -170,14 +167,13 @@ class TransactionRequest extends Request
// all journals must have a description // all journals must have a description
$this->validateDescriptions($validator); $this->validateDescriptions($validator);
// all transaction types must be equal: // all transaction types must be equal:
$this->validateTransactionTypes($validator); $this->validateTransactionTypes($validator);
// validate foreign currency info // validate foreign currency info
$this->validateForeignCurrencyInformation($validator); $this->validateForeignCurrencyInformation($validator);
// validate all account info // validate all account info
$this->validateAccountInformation($validator); $this->validateAccountInformation($validator);
@ -186,6 +182,8 @@ class TransactionRequest extends Request
// the group must have a description if > 1 journal. // the group must have a description if > 1 journal.
$this->validateGroupDescription($validator); $this->validateGroupDescription($validator);
// TODO validate that the currency fits the source and/or destination account.
} }
); );
} }
@ -207,9 +205,10 @@ class TransactionRequest extends Request
foreach ($this->get('transactions') as $index => $transaction) { foreach ($this->get('transactions') as $index => $transaction) {
$object = new NullArrayObject($transaction); $object = new NullArrayObject($transaction);
$return[] = [ $return[] = [
// $this->dateFromValue($object['']) 'type' => $this->stringFromValue($object['type']),
'type' => $this->stringFromValue($object['type']), 'date' => $this->dateFromValue($object['date']),
'date' => $this->dateFromValue($object['date']), 'order' => $this->integerFromValue((string)$object['order']),
'currency_id' => $this->integerFromValue($object['currency_id']), 'currency_id' => $this->integerFromValue($object['currency_id']),
'currency_code' => $this->stringFromValue($object['currency_code']), 'currency_code' => $this->stringFromValue($object['currency_code']),

View File

@ -0,0 +1,316 @@
<?php
/**
* TransactionUpdateRequest.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\Api\V1\Requests;
use FireflyIII\Models\TransactionGroup;
use FireflyIII\Rules\BelongsUser;
use FireflyIII\Rules\IsBoolean;
use FireflyIII\Rules\IsDateOrTime;
use FireflyIII\Validation\TransactionValidation;
use Illuminate\Validation\Validator;
/**
* Class TransactionUpdateRequest
*/
class TransactionUpdateRequest extends Request
{
use TransactionValidation;
/** @var array Array values. */
private $arrayFields;
/** @var array Boolean values. */
private $booleanFields;
/** @var array Fields that contain date values. */
private $dateFields;
/** @var array Fields that contain integer values. */
private $integerFields;
/** @var array Fields that contain string values. */
private $stringFields;
/**
* Authorize logged in users.
*
* @return bool
*/
public function authorize(): bool
{
// Only allow authenticated users
return auth()->check();
}
/**
* Get all data. Is pretty complex because of all the ??-statements.
*
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.NPathComplexity)
* @return array
*/
public function getAll(): array
{
$this->integerFields = [
'order',
'currency_id',
'foreign_currency_id',
'source_id',
'destination_id',
'budget_id',
'category_id',
'bill_id',
'recurrence_id',
];
$this->dateFields = [
'date',
'interest_date',
'book_date',
'process_date',
'due_date',
'payment_date',
'invoice_date',
];
$this->stringFields = [
'type',
'currency_code',
'foreign_currency_code',
'amount',
'foreign_amount',
'description',
'source_name',
'destination_name',
'budget_name',
'category_name',
'bill_name',
'notes',
'internal_reference',
'external_id',
'bunq_payment_id',
'sepa_cc',
'sepa_ct_op',
'sepa_ct_id',
'sepa_db',
'sepa_country',
'sepa_ep',
'sepa_ci',
'sepa_batch_id',
];
$this->booleanFields = [
'reconciled',
];
$this->arrayFields = [
'tags',
];
$data = [
'transactions' => $this->getTransactionData(),
];
if ($this->has('group_title')) {
$data['group_title'] = $this->string('group_title');
}
return $data;
}
/**
* The rules that the incoming request must be matched against.
*
* @return array
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
public function rules(): array
{
$rules = [
// basic fields for group:
'group_title' => 'between:1,255',
// transaction rules (in array for splits):
'transactions.*.type' => 'in:withdrawal,deposit,transfer,opening-balance,reconciliation',
'transactions.*.date' => [new IsDateOrTime],
'transactions.*.order' => 'numeric|min:0',
// currency info
'transactions.*.currency_id' => 'numeric|exists:transaction_currencies,id',
'transactions.*.currency_code' => 'min:3|max:3|exists:transaction_currencies,code',
'transactions.*.foreign_currency_id' => 'numeric|exists:transaction_currencies,id',
'transactions.*.foreign_currency_code' => 'min:3|max:3|exists:transaction_currencies,code',
// amount
'transactions.*.amount' => 'numeric|more:0',
'transactions.*.foreign_amount' => 'numeric|gte: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],
// other interesting fields
'transactions.*.reconciled' => [new IsBoolean],
'transactions.*.notes' => 'min:1,max:50000|nullable',
'transactions.*.tags' => 'between:0,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',
];
return $rules;
}
/**
* Configure the validator instance.
*
* @param Validator $validator
*
* @return void
*/
public function withValidator(Validator $validator): void
{
/** @var TransactionGroup $transactionGroup */
$transactionGroup = $this->route()->parameter('transactionGroup');
$validator->after(
function (Validator $validator) use ($transactionGroup) {
// must submit at least one transaction.
$this->validateOneTransaction($validator);
// if more than one, verify that there are journal ID's present.
$this->validateJournalIds($validator, $transactionGroup);
// all transaction types must be equal:
$this->validateTransactionTypesForUpdate($validator);
// if type is set, source + destination info is mandatory.
$this->validateAccountPresence($validator);
// TODO validate that the currency fits the source and/or destination account.
// all journals must have a description
//$this->validateDescriptions($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);
}
);
}
/**
* Get transaction data.
*
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.NPathComplexity)
* @return array
*/
private function getTransactionData(): array
{
$return = [];
/**
* @var int $index
* @var array $transaction
*/
foreach ($this->get('transactions') as $index => $transaction) {
// default response is to update nothing in the transaction:
$current = [];
// for each field, add it to the array if a reference is present in the request:
foreach ($this->integerFields as $fieldName) {
if (array_key_exists($fieldName, $transaction)) {
$current[$fieldName] = $this->integerFromValue((string)$transaction[$fieldName]);
}
}
foreach ($this->stringFields as $fieldName) {
if (array_key_exists($fieldName, $transaction)) {
$current[$fieldName] = $this->stringFromValue((string)$transaction[$fieldName]);
}
}
foreach ($this->dateFields as $fieldName) {
if (array_key_exists($fieldName, $transaction)) {
$current[$fieldName] = $this->dateFromValue((string)$transaction[$fieldName]);
}
}
foreach ($this->booleanFields as $fieldName) {
if (array_key_exists($fieldName, $transaction)) {
$current[$fieldName] = $this->convertBoolean((string)$transaction[$fieldName]);
}
}
foreach ($this->arrayFields as $fieldName) {
if (array_key_exists($fieldName, $transaction)) {
$current[$fieldName] = $this->arrayFromValue((string)$transaction[$fieldName]);
}
}
$return[] = $current;
}
return $return;
}
}

View File

@ -68,6 +68,7 @@ class CorrectDatabase extends Command
'firefly-iii:delete-empty-journals', 'firefly-iii:delete-empty-journals',
'firefly-iii:delete-empty-groups', 'firefly-iii:delete-empty-groups',
'firefly-iii:fix-account-types', 'firefly-iii:fix-account-types',
'firefly-iii:rename-meta-fields'
]; ];
foreach ($commands as $command) { foreach ($commands as $command) {
$this->line(sprintf('Now executing %s', $command)); $this->line(sprintf('Now executing %s', $command));

View File

@ -23,7 +23,7 @@ namespace FireflyIII\Console\Commands\Upgrade;
use DB; use DB;
use Exception; use Exception;
use FireflyIII\Factory\TransactionJournalFactory; use FireflyIII\Factory\TransactionGroupFactory;
use FireflyIII\Models\Transaction; use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionJournal;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
@ -54,13 +54,10 @@ class MigrateToGroups extends Command
* @var string * @var string
*/ */
protected $signature = 'firefly-iii:migrate-to-groups {--F|force : Force the migration, even if it fired before.}'; protected $signature = 'firefly-iii:migrate-to-groups {--F|force : Force the migration, even if it fired before.}';
/** @var TransactionGroupFactory */
/** @var TransactionJournalFactory */ private $groupFactory;
private $journalFactory;
/** @var JournalRepositoryInterface */ /** @var JournalRepositoryInterface */
private $journalRepository; private $journalRepository;
/** @var JournalDestroyService */ /** @var JournalDestroyService */
private $service; private $service;
@ -72,9 +69,9 @@ class MigrateToGroups extends Command
public function __construct() public function __construct()
{ {
parent::__construct(); parent::__construct();
$this->journalFactory = app(TransactionJournalFactory::class);
$this->journalRepository = app(JournalRepositoryInterface::class); $this->journalRepository = app(JournalRepositoryInterface::class);
$this->service = app(JournalDestroyService::class); $this->service = app(JournalDestroyService::class);
$this->groupFactory = app(TransactionGroupFactory::class);
} }
/** /**
@ -119,7 +116,7 @@ class MigrateToGroups extends Command
private function findOpposingTransaction(TransactionJournal $journal, Transaction $transaction): ?Transaction private function findOpposingTransaction(TransactionJournal $journal, Transaction $transaction): ?Transaction
{ {
$set = $journal->transactions->filter( $set = $journal->transactions->filter(
function (Transaction $subject) use ($transaction) { static function (Transaction $subject) use ($transaction) {
return $transaction->amount * -1 === (float)$subject->amount && $transaction->identifier === $subject->identifier; return $transaction->amount * -1 === (float)$subject->amount && $transaction->identifier === $subject->identifier;
} }
); );
@ -135,7 +132,7 @@ class MigrateToGroups extends Command
private function getDestinationTransactions(TransactionJournal $journal): Collection private function getDestinationTransactions(TransactionJournal $journal): Collection
{ {
return $journal->transactions->filter( return $journal->transactions->filter(
function (Transaction $transaction) { static function (Transaction $transaction) {
return $transaction->amount > 0; return $transaction->amount > 0;
} }
); );
@ -224,13 +221,10 @@ class MigrateToGroups extends Command
Log::debug(sprintf('Will now try to convert journal #%d', $journal->id)); Log::debug(sprintf('Will now try to convert journal #%d', $journal->id));
$this->journalRepository->setUser($journal->user); $this->journalRepository->setUser($journal->user);
$this->journalFactory->setUser($journal->user); $this->groupFactory->setUser($journal->user);
$data = [ $data = [
// mandatory fields. // mandatory fields.
'type' => strtolower($journal->transactionType->type),
'date' => $journal->date,
'user' => $journal->user_id,
'group_title' => $journal->description, 'group_title' => $journal->description,
'transactions' => [], 'transactions' => [],
]; ];
@ -280,6 +274,9 @@ class MigrateToGroups extends Command
} }
$tArray = [ $tArray = [
'type' => strtolower($journal->transactionType->type),
'date' => $journal->date,
'user' => $journal->user_id,
'currency_id' => $transaction->transaction_currency_id, 'currency_id' => $transaction->transaction_currency_id,
'foreign_currency_id' => $transaction->foreign_currency_id, 'foreign_currency_id' => $transaction->foreign_currency_id,
'amount' => $transaction->amount, 'amount' => $transaction->amount,
@ -318,21 +315,18 @@ class MigrateToGroups extends Command
$data['transactions'][] = $tArray; $data['transactions'][] = $tArray;
} }
Log::debug(sprintf('Now calling transaction journal factory (%d transactions in array)', count($data['transactions']))); Log::debug(sprintf('Now calling transaction journal factory (%d transactions in array)', count($data['transactions'])));
$result = $this->journalFactory->create($data); $group = $this->groupFactory->create($data);
Log::debug('Done calling transaction journal factory'); Log::debug('Done calling transaction journal factory');
// delete the old transaction journal. // delete the old transaction journal.
$this->service->destroy($journal); $this->service->destroy($journal);
// first group ID
$first = $result->first() ? $result->first()->transaction_group_id : 0;
// report on result: // report on result:
Log::debug( Log::debug(
sprintf('Migrated journal #%d into group #%d with these journals: #%s', $journal->id, $first, implode(', #', $result->pluck('id')->toArray())) sprintf('Migrated journal #%d into group #%d with these journals: #%s', $journal->id, $group->id, implode(', #', $group->transactionJournals->pluck('id')->toArray()))
); );
$this->line( $this->line(
sprintf('Migrated journal #%d into group #%d with these journals: #%s', $journal->id, $first, implode(', #', $result->pluck('id')->toArray())) sprintf('Migrated journal #%d into group #%d with these journals: #%s', $journal->id, $group->id, implode(', #', $group->transactionJournals->pluck('id')->toArray()))
); );
} }

View File

@ -27,11 +27,11 @@ namespace FireflyIII\Factory;
use FireflyIII\Exceptions\FireflyException; use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Account; use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\Transaction; use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionCurrency; use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionJournal;
use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Services\Internal\Support\JournalServiceTrait;
use FireflyIII\Support\NullArrayObject; use FireflyIII\Support\NullArrayObject;
use FireflyIII\User; use FireflyIII\User;
use FireflyIII\Validation\AccountValidator; use FireflyIII\Validation\AccountValidator;
@ -44,8 +44,8 @@ use Log;
*/ */
class TransactionFactory class TransactionFactory
{ {
/** @var AccountRepositoryInterface */ use JournalServiceTrait;
private $accountRepository;
/** @var AccountValidator */ /** @var AccountValidator */
private $accountValidator; private $accountValidator;
/** @var TransactionJournal */ /** @var TransactionJournal */
@ -113,12 +113,16 @@ class TransactionFactory
*/ */
public function createPair(NullArrayObject $data, TransactionCurrency $currency, ?TransactionCurrency $foreignCurrency): Collection public function createPair(NullArrayObject $data, TransactionCurrency $currency, ?TransactionCurrency $foreignCurrency): Collection
{ {
Log::debug('Going to create a pair of transactions.');
Log::debug(sprintf('Source info: ID #%d, name "%s"', $data['source_id'], $data['source_name']));
Log::debug(sprintf('Destination info: ID #%d, name "%s"', $data['destination_id'], $data['destination_name']));
// validate source and destination using a new Validator. // validate source and destination using a new Validator.
$this->validateAccounts($data); $this->validateAccounts($data);
// create or get source and destination accounts: // create or get source and destination accounts:
$sourceAccount = $this->getAccount('source', (int)$data['source_id'], $data['source_name']); $type = $this->journal->transactionType->type;
$destinationAccount = $this->getAccount('destination', (int)$data['destination_id'], $data['destination_name']); $sourceAccount = $this->getAccount($type, 'source', (int)$data['source_id'], $data['source_name']);
$destinationAccount = $this->getAccount($type, 'destination', (int)$data['destination_id'], $data['destination_name']);
$amount = $this->getAmount($data['amount']); $amount = $this->getAmount($data['amount']);
$foreignAmount = $this->getForeignAmount($data['foreign_amount']); $foreignAmount = $this->getForeignAmount($data['foreign_amount']);
@ -129,7 +133,7 @@ class TransactionFactory
$two->reconciled = $data['reconciled'] ?? false; $two->reconciled = $data['reconciled'] ?? false;
// add foreign currency info to $one and $two if necessary. // add foreign currency info to $one and $two if necessary.
if (null !== $foreignCurrency) { if (null !== $foreignCurrency && null !== $foreignAmount) {
$one->foreign_currency_id = $foreignCurrency->id; $one->foreign_currency_id = $foreignCurrency->id;
$two->foreign_currency_id = $foreignCurrency->id; $two->foreign_currency_id = $foreignCurrency->id;
$one->foreign_amount = app('steam')->negative($foreignAmount); $one->foreign_amount = app('steam')->negative($foreignAmount);
@ -144,134 +148,6 @@ class TransactionFactory
} }
/**
* @param string $direction
* @param int|null $accountId
* @param string|null $accountName
*
* @return Account
* @throws FireflyException
*/
public function getAccount(string $direction, ?int $accountId, ?string $accountName): Account
{
// 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');
$expectedTypes = $array[$direction];
unset($array);
// and now try to find it, based on the type of transaction.
$transactionType = $this->journal->transactionType->type;
$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, 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, $search->id, $search->name, $search->accountType->type)
);
$result = $search;
}
}
// 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($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));
$result = $source;
}
}
// return cash account.
if (null === $result && null === $accountName
&& in_array(AccountType::CASH, $expectedTypes[$transactionType], true)) {
$result = $this->accountRepository->getCashAccount();
}
// 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;
}
/**
* @param string $amount
*
* @return string
* @throws FireflyException
*/
public function getAmount(string $amount): string
{
if ('' === $amount) {
throw new FireflyException(sprintf('The amount cannot be an empty string: "%s"', $amount));
}
if (0 === bccomp('0', $amount)) {
throw new FireflyException(sprintf('The amount seems to be zero: "%s"', $amount));
}
return $amount;
}
/**
* @param string|null $amount
*
* @return string
*/
public function getForeignAmount(?string $amount): ?string
{
$result = null;
if (null === $amount) {
Log::debug('No foreign amount info in array. Return NULL');
return null;
}
if ('' === $amount) {
Log::debug('Foreign amount is empty string, return NULL.');
return null;
}
if (0 === bccomp('0', $amount)) {
Log::debug('Foreign amount is 0.0, return NULL.');
return null;
}
Log::debug(sprintf('Foreign amount is %s', $amount));
return $amount;
}
/** /**
* @param TransactionJournal $journal * @param TransactionJournal $journal
@ -298,6 +174,7 @@ class TransactionFactory
private function validateAccounts(NullArrayObject $data): void private function validateAccounts(NullArrayObject $data): void
{ {
$transactionType = $data['type'] ?? 'invalid'; $transactionType = $data['type'] ?? 'invalid';
$this->accountValidator->setUser($this->journal->user);
$this->accountValidator->setTransactionType($transactionType); $this->accountValidator->setTransactionType($transactionType);
// validate source account. // validate source account.
@ -309,6 +186,7 @@ class TransactionFactory
if (false === $validSource) { if (false === $validSource) {
throw new FireflyException($this->accountValidator->sourceError); throw new FireflyException($this->accountValidator->sourceError);
} }
Log::debug('Source seems valid.');
// validate destination account // validate destination account
$destinationId = isset($data['destination_id']) ? (int)$data['destination_id'] : null; $destinationId = isset($data['destination_id']) ? (int)$data['destination_id'] : null;
$destinationName = $data['destination_name'] ?? null; $destinationName = $data['destination_name'] ?? null;

View File

@ -27,7 +27,6 @@ namespace FireflyIII\Factory;
use Carbon\Carbon; use Carbon\Carbon;
use Exception; use Exception;
use FireflyIII\Exceptions\FireflyException; use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Note;
use FireflyIII\Models\TransactionCurrency; use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType; use FireflyIII\Models\TransactionType;
@ -37,6 +36,7 @@ use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface; use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
use FireflyIII\Repositories\TransactionType\TransactionTypeRepositoryInterface; use FireflyIII\Repositories\TransactionType\TransactionTypeRepositoryInterface;
use FireflyIII\Services\Internal\Support\JournalServiceTrait;
use FireflyIII\Support\NullArrayObject; use FireflyIII\Support\NullArrayObject;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
@ -47,12 +47,10 @@ use Log;
*/ */
class TransactionJournalFactory class TransactionJournalFactory
{ {
use JournalServiceTrait;
/** @var BillRepositoryInterface */ /** @var BillRepositoryInterface */
private $billRepository; private $billRepository;
/** @var BudgetRepositoryInterface */
private $budgetRepository;
/** @var CategoryRepositoryInterface */
private $categoryRepository;
/** @var CurrencyRepositoryInterface */ /** @var CurrencyRepositoryInterface */
private $currencyRepository; private $currencyRepository;
/** @var array */ /** @var array */
@ -61,8 +59,6 @@ class TransactionJournalFactory
private $piggyEventFactory; private $piggyEventFactory;
/** @var PiggyBankRepositoryInterface */ /** @var PiggyBankRepositoryInterface */
private $piggyRepository; private $piggyRepository;
/** @var TagFactory */
private $tagFactory;
/** @var TransactionFactory */ /** @var TransactionFactory */
private $transactionFactory; private $transactionFactory;
/** @var TransactionTypeRepositoryInterface */ /** @var TransactionTypeRepositoryInterface */
@ -88,7 +84,7 @@ class TransactionJournalFactory
'due_date', 'payment_date', 'invoice_date', 'due_date', 'payment_date', 'invoice_date',
// others // others
'recurrence_id', 'internal_reference', 'bunq_payment_id', 'recurrence_id', 'internal_reference', 'bunq_payment_id',
'import_hash', 'import_hash_v2', 'external_id', 'original_source']; 'import_hash', 'import_hash_v2', 'external_id', 'original_source'];
@ -151,6 +147,7 @@ class TransactionJournalFactory
{ {
$this->user = $user; $this->user = $user;
$this->currencyRepository->setUser($this->user); $this->currencyRepository->setUser($this->user);
$this->tagFactory->setUser($user);
$this->transactionFactory->setUser($this->user); $this->transactionFactory->setUser($this->user);
$this->billRepository->setUser($this->user); $this->billRepository->setUser($this->user);
$this->budgetRepository->setUser($this->user); $this->budgetRepository->setUser($this->user);
@ -184,31 +181,6 @@ class TransactionJournalFactory
Log::debug('Create no piggy event'); Log::debug('Create no piggy event');
} }
/**
* Link tags to journal.
*
* @param TransactionJournal $journal
* @param array $tags
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
*/
public function storeTags(TransactionJournal $journal, ?array $tags): void
{
$this->tagFactory->setUser($journal->user);
$set = [];
if (!\is_array($tags)) {
return;
}
foreach ($tags as $string) {
if ('' !== $string) {
$tag = $this->tagFactory->findOrCreate($string);
if (null !== $tag) {
$set[] = $tag->id;
}
}
}
$journal->tags()->sync($set);
}
/** /**
* @param TransactionJournal $journal * @param TransactionJournal $journal
* @param NullArrayObject $data * @param NullArrayObject $data
@ -229,27 +201,6 @@ class TransactionJournalFactory
$factory->updateOrCreate($set); $factory->updateOrCreate($set);
} }
/**
* @param TransactionJournal $journal
* @param string $notes
*/
protected function storeNote(TransactionJournal $journal, ?string $notes): void
{
$notes = (string)$notes;
if ('' !== $notes) {
$note = $journal->notes()->first();
if (null === $note) {
$note = new Note;
$note->noteable()->associate($journal);
}
$note->text = $notes;
$note->save();
Log::debug(sprintf('Stored notes for journal #%d', $journal->id));
return;
}
}
/** /**
* @param NullArrayObject $row * @param NullArrayObject $row
* *
@ -263,8 +214,9 @@ class TransactionJournalFactory
/** Get basic fields */ /** Get basic fields */
$type = $this->typeRepository->findTransactionType(null, $row['type']); $type = $this->typeRepository->findTransactionType(null, $row['type']);
$carbon = $row['date'] ?? new Carbon; $carbon = $row['date'] ?? new Carbon;
$order = $row['order'] ?? 0;
$currency = $this->currencyRepository->findCurrency((int)$row['currency_id'], $row['currency_code']); $currency = $this->currencyRepository->findCurrency((int)$row['currency_id'], $row['currency_code']);
$foreignCurrency = $this->findForeignCurrency($row); $foreignCurrency = $this->currencyRepository->findCurrencyNull($row['foreign_currency_id'], $row['foreign_currency_code']);
$bill = $this->billRepository->findBill((int)$row['bill_id'], $row['bill_name']); $bill = $this->billRepository->findBill((int)$row['bill_id'], $row['bill_name']);
$billId = TransactionType::WITHDRAWAL === $type->type && null !== $bill ? $bill->id : null; $billId = TransactionType::WITHDRAWAL === $type->type && null !== $bill ? $bill->id : null;
$description = app('steam')->cleanString((string)$row['description']); $description = app('steam')->cleanString((string)$row['description']);
@ -281,7 +233,7 @@ class TransactionJournalFactory
'transaction_currency_id' => $currency->id, 'transaction_currency_id' => $currency->id,
'description' => '' === $description ? '(empty description)' : $description, 'description' => '' === $description ? '(empty description)' : $description,
'date' => $carbon->format('Y-m-d H:i:s'), 'date' => $carbon->format('Y-m-d H:i:s'),
'order' => 0, 'order' => $order,
'tag_count' => 0, 'tag_count' => 0,
'completed' => 0, 'completed' => 0,
] ]
@ -318,7 +270,7 @@ class TransactionJournalFactory
$this->storeCategory($journal, $row); $this->storeCategory($journal, $row);
/** Set notes */ /** Set notes */
$this->storeNote($journal, $row['notes']); $this->storeNotes($journal, $row['notes']);
/** Set piggy bank */ /** Set piggy bank */
$this->storePiggyEvent($journal, $row); $this->storePiggyEvent($journal, $row);
@ -332,22 +284,6 @@ class TransactionJournalFactory
return $journal; return $journal;
} }
/**
* This is a separate function because "findCurrency" will default to EUR and that may not be what we want.
*
* @param NullArrayObject $transaction
*
* @return TransactionCurrency|null
*/
private function findForeignCurrency(NullArrayObject $transaction): ?TransactionCurrency
{
if (null === $transaction['foreign_currency'] && null === $transaction['foreign_currency_id'] && null === $transaction['foreign_currency_code']) {
return null;
}
return $this->currencyRepository->findCurrency((int)$transaction['foreign_currency_id'], $transaction['foreign_currency_code']);
}
/** /**
* @param NullArrayObject $row * @param NullArrayObject $row
* *
@ -355,7 +291,7 @@ class TransactionJournalFactory
*/ */
private function hashArray(NullArrayObject $row): string private function hashArray(NullArrayObject $row): string
{ {
$row['import_hash_v2'] = null; $row['import_hash_v2'] = null;
$row['original_source'] = null; $row['original_source'] = null;
$json = json_encode($row); $json = json_encode($row);
if (false === $json) { if (false === $json) {
@ -367,36 +303,6 @@ class TransactionJournalFactory
return $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));
$journal->budgets()->sync([$budget->id]);
}
}
/**
* @param TransactionJournal $journal
* @param NullArrayObject $data
*/
private function storeCategory(TransactionJournal $journal, NullArrayObject $data): void
{
$category = $this->categoryRepository->findCategory($data['category'], $data['category_id'], $data['category_name']);
if (null !== $category) {
Log::debug(sprintf('Link category #%d to journal #%d', $category->id, $journal->id));
$journal->categories()->sync([$category->id]);
}
}
/** /**
* @param TransactionJournal $journal * @param TransactionJournal $journal
* @param NullArrayObject $transaction * @param NullArrayObject $transaction

View File

@ -53,10 +53,12 @@ class TransactionJournalMetaFactory
*/ */
public function updateOrCreate(array $data): ?TransactionJournalMeta public function updateOrCreate(array $data): ?TransactionJournalMeta
{ {
Log::debug('In updateOrCreate()');
$value = $data['data']; $value = $data['data'];
/** @var TransactionJournalMeta $entry */ /** @var TransactionJournalMeta $entry */
$entry = $data['journal']->transactionJournalMeta()->where('name', $data['name'])->first(); $entry = $data['journal']->transactionJournalMeta()->where('name', $data['name'])->first();
if (null === $value && null !== $entry) { if (null === $value && null !== $entry) {
Log::debug('Value is empty, delete meta value.');
try { try {
$entry->delete(); $entry->delete();
} catch (Exception $e) { // @codeCoverageIgnore } catch (Exception $e) { // @codeCoverageIgnore
@ -67,11 +69,14 @@ class TransactionJournalMetaFactory
} }
if ($data['data'] instanceof Carbon) { if ($data['data'] instanceof Carbon) {
Log::debug('Is a carbon object.');
$value = $data['data']->toW3cString(); $value = $data['data']->toW3cString();
} }
if ('' === (string)$value) { if ('' === (string)$value) {
Log::debug('Is an empty string.');
// don't store blank strings. // don't store blank strings.
if (null !== $entry) { if (null !== $entry) {
Log::debug('Will not store empty strings, delete meta value');
try { try {
$entry->delete(); $entry->delete();
} catch (Exception $e) { // @codeCoverageIgnore } catch (Exception $e) { // @codeCoverageIgnore
@ -83,11 +88,13 @@ class TransactionJournalMetaFactory
} }
if (null === $entry) { if (null === $entry) {
Log::debug('Will create new object.');
Log::debug(sprintf('Going to create new meta-data entry to store "%s".', $data['name'])); Log::debug(sprintf('Going to create new meta-data entry to store "%s".', $data['name']));
$entry = new TransactionJournalMeta(); $entry = new TransactionJournalMeta();
$entry->transactionJournal()->associate($data['journal']); $entry->transactionJournal()->associate($data['journal']);
$entry->name = $data['name']; $entry->name = $data['name'];
} }
Log::debug('Will update value and return.');
$entry->data = $value; $entry->data = $value;
$entry->save(); $entry->save();

View File

@ -44,7 +44,7 @@ class UpdatedGroupEventHandler
public function processRules(UpdatedTransactionGroup $updatedJournalEvent): bool public function processRules(UpdatedTransactionGroup $updatedJournalEvent): bool
{ {
// get all the user's rule groups, with the rules, order by 'order'. // get all the user's rule groups, with the rules, order by 'order'.
$journals = $updatedJournalEvent->transactionGroup; $journals = $updatedJournalEvent->transactionGroup->transactionJournals;
/** @var RuleGroupRepositoryInterface $ruleGroupRepos */ /** @var RuleGroupRepositoryInterface $ruleGroupRepos */
$ruleGroupRepos = app(RuleGroupRepositoryInterface::class); $ruleGroupRepos = app(RuleGroupRepositoryInterface::class);

View File

@ -99,6 +99,7 @@ class GroupCollector implements GroupCollectorInterface
'transaction_types.type as transaction_type_type', 'transaction_types.type as transaction_type_type',
'transaction_journals.description', 'transaction_journals.description',
'transaction_journals.date', 'transaction_journals.date',
'transaction_journals.order',
# source info (always present) # source info (always present)
'source.id as source_transaction_id', 'source.id as source_transaction_id',

View File

@ -82,7 +82,7 @@ class InstallController extends Controller
'firefly-iii:migrate-to-groups' => [], 'firefly-iii:migrate-to-groups' => [],
'firefly-iii:back-to-journals' => [], 'firefly-iii:back-to-journals' => [],
// there are 12 verify commands. // there are 13 verify commands.
'firefly-iii:fix-piggies' => [], 'firefly-iii:fix-piggies' => [],
'firefly-iii:create-link-types' => [], 'firefly-iii:create-link-types' => [],
'firefly-iii:create-access-tokens' => [], 'firefly-iii:create-access-tokens' => [],
@ -95,8 +95,10 @@ class InstallController extends Controller
'firefly-iii:delete-empty-journals' => [], 'firefly-iii:delete-empty-journals' => [],
'firefly-iii:delete-empty-groups' => [], 'firefly-iii:delete-empty-groups' => [],
'firefly-iii:fix-account-types' => [], 'firefly-iii:fix-account-types' => [],
'firefly-iii:rename-meta-fields' => [],
]; ];
} }
/** /**
* Show index. * Show index.
* *

View File

@ -170,6 +170,9 @@ class Request extends FormRequest
if (null === $string) { if (null === $string) {
return null; return null;
} }
if ('' === $string) {
return null;
}
return (int)$string; return (int)$string;
} }

View File

@ -134,9 +134,8 @@ class TransactionJournal extends Model
/** @var array Fields that can be filled */ /** @var array Fields that can be filled */
protected $fillable protected $fillable
= ['user_id', 'transaction_type_id', 'bill_id', 'interest_date', 'book_date', 'process_date', = ['user_id', 'transaction_type_id', 'bill_id', 'tag_count','transaction_currency_id', 'description', 'completed', 'order',
'transaction_currency_id', 'description', 'completed', 'date'];
'date', 'rent_date', 'encrypted', 'tag_count',];
/** @var array Hidden from view */ /** @var array Hidden from view */
protected $hidden = ['encrypted']; protected $hidden = ['encrypted'];

View File

@ -97,8 +97,18 @@ class JournalServiceProvider extends ServiceProvider
private function registerGroupRepository() private function registerGroupRepository()
{ {
// password verifier thing $this->app->bind(
$this->app->bind(TransactionGroupRepositoryInterface::class, TransactionGroupRepository::class); TransactionGroupRepositoryInterface::class,
function (Application $app) {
/** @var TransactionGroupRepositoryInterface $repository */
$repository = app(TransactionGroupRepository::class);
if ($app->auth->check()) {
$repository->setUser(auth()->user());
}
return $repository;
}
);
} }
/** /**

View File

@ -220,25 +220,16 @@ class BudgetRepository implements BudgetRepositoryInterface
} }
/** /**
* @param Budget|null $budget
* @param int|null $budgetId * @param int|null $budgetId
* @param string|null $budgetName * @param string|null $budgetName
* *
* @return Budget|null * @return Budget|null
*/ */
public function findBudget(?Budget $budget, ?int $budgetId, ?string $budgetName): ?Budget public function findBudget(?int $budgetId, ?string $budgetName): ?Budget
{ {
Log::debug('Now in findBudget()'); Log::debug('Now in findBudget()');
$result = null; Log::debug(sprintf('Searching for budget with ID #%d...', $budgetId));
if (null !== $budget) { $result = $this->findNull((int)$budgetId);
Log::debug(sprintf('Parameters contain budget #%d, will return this.', $budget->id));
$result = $budget;
}
if (null === $result) {
Log::debug(sprintf('Searching for budget with ID #%d...', $budgetId));
$result = $this->findNull((int)$budgetId);
}
if (null === $result) { if (null === $result) {
Log::debug(sprintf('Searching for budget with name %s...', $budgetName)); Log::debug(sprintf('Searching for budget with name %s...', $budgetName));
$result = $this->findByName((string)$budgetName); $result = $this->findByName((string)$budgetName);

View File

@ -82,13 +82,12 @@ interface BudgetRepositoryInterface
public function destroyBudgetLimit(BudgetLimit $budgetLimit): void; public function destroyBudgetLimit(BudgetLimit $budgetLimit): void;
/** /**
* @param Budget|null $budget
* @param int|null $budgetId * @param int|null $budgetId
* @param string|null $budgetName * @param string|null $budgetName
* *
* @return Budget|null * @return Budget|null
*/ */
public function findBudget(?Budget $budget, ?int $budgetId, ?string $budgetName): ?Budget; public function findBudget( ?int $budgetId, ?string $budgetName): ?Budget;
/** /**
* Find budget by name. * Find budget by name.

View File

@ -253,25 +253,16 @@ class CategoryRepository implements CategoryRepositoryInterface
} }
/** /**
* @param Category|null $category * @param int|null $categoryId
* @param int|null $categoryId * @param string|null $categoryName
* @param string|null $categoryName
* *
* @return Category|null * @return Category|null
*/ */
public function findCategory(?Category $category, ?int $categoryId, ?string $categoryName): ?Category public function findCategory(?int $categoryId, ?string $categoryName): ?Category
{ {
Log::debug('Now in findCategory()'); Log::debug('Now in findCategory()');
$result = null; Log::debug(sprintf('Searching for category with ID #%d...', $categoryId));
if (null !== $category) { $result = $this->findNull((int)$categoryId);
Log::debug(sprintf('Parameters contain category #%d, will return this.', $category->id));
$result = $category;
}
if (null === $result) {
Log::debug(sprintf('Searching for category with ID #%d...', $categoryId));
$result = $this->findNull((int)$categoryId);
}
if (null === $result) { if (null === $result) {
Log::debug(sprintf('Searching for category with name %s...', $categoryName)); Log::debug(sprintf('Searching for category with name %s...', $categoryName));
$result = $this->findByName((string)$categoryName); $result = $this->findByName((string)$categoryName);

View File

@ -34,13 +34,12 @@ interface CategoryRepositoryInterface
{ {
/** /**
* @param Category|null $category
* @param int|null $categoryId * @param int|null $categoryId
* @param string|null $categoryName * @param string|null $categoryName
* *
* @return Category|null * @return Category|null
*/ */
public function findCategory(?Category $category, ?int $categoryId, ?string $categoryName): ?Category; public function findCategory( ?int $categoryId, ?string $categoryName): ?Category;
/** /**
* @param Category $category * @param Category $category

View File

@ -265,16 +265,13 @@ class CurrencyRepository implements CurrencyRepositoryInterface
*/ */
public function findCurrency(?int $currencyId, ?string $currencyCode): TransactionCurrency public function findCurrency(?int $currencyId, ?string $currencyCode): TransactionCurrency
{ {
Log::debug('Now in findCurrency()'); $result = $this->findCurrencyNull($currencyId, $currencyCode);
$result = $this->find((int)$currencyId);
if (null === $result) {
Log::debug(sprintf('Searching for currency with code %s...', $currencyCode));
$result = $this->findByCode((string)$currencyCode);
}
if (null === $result) { if (null === $result) {
Log::debug('Grabbing default currency for this user...'); Log::debug('Grabbing default currency for this user...');
$result = app('amount')->getDefaultCurrencyByUser($this->user); $result = app('amount')->getDefaultCurrencyByUser($this->user);
} }
if (null === $result) { if (null === $result) {
Log::debug('Grabbing EUR as fallback.'); Log::debug('Grabbing EUR as fallback.');
$result = $this->findByCode('EUR'); $result = $this->findByCode('EUR');
@ -288,6 +285,30 @@ class CurrencyRepository implements CurrencyRepositoryInterface
return $result; return $result;
} }
/**
* Find by object, ID or code. Returns NULL if nothing found.
*
* @param int|null $currencyId
* @param string|null $currencyCode
*
* @return TransactionCurrency|null
*/
public function findCurrencyNull(?int $currencyId, ?string $currencyCode): ?TransactionCurrency
{
Log::debug('Now in findCurrencyNull()');
$result = $this->find((int)$currencyId);
if (null === $result) {
Log::debug(sprintf('Searching for currency with code %s...', $currencyCode));
$result = $this->findByCode((string)$currencyCode);
}
if (null !== $result && false === $result->enabled) {
Log::debug(sprintf('Also enabled currency %s', $result->code));
$this->enable($result);
}
return $result;
}
/** /**
* Find by ID, return NULL if not found. * Find by ID, return NULL if not found.
* Used in Import Currency! * Used in Import Currency!

View File

@ -135,13 +135,23 @@ interface CurrencyRepositoryInterface
/** /**
* Find by object, ID or code. Returns user default or system default. * Find by object, ID or code. Returns user default or system default.
* *
* @param int|null $currencyId * @param int|null $currencyId
* @param string|null $currencyCode * @param string|null $currencyCode
* *
* @return TransactionCurrency|null * @return TransactionCurrency|null
*/ */
public function findCurrency(?int $currencyId, ?string $currencyCode): TransactionCurrency; public function findCurrency(?int $currencyId, ?string $currencyCode): TransactionCurrency;
/**
* Find by object, ID or code. Returns NULL if nothing found.
*
* @param int|null $currencyId
* @param string|null $currencyCode
*
* @return TransactionCurrency|null
*/
public function findCurrencyNull(?int $currencyId, ?string $currencyCode): ?TransactionCurrency;
/** /**
* Find by ID, return NULL if not found. * Find by ID, return NULL if not found.
* *

View File

@ -774,40 +774,6 @@ class JournalRepository implements JournalRepositoryInterface
$this->user = $user; $this->user = $user;
} }
/**
* @param array $data
*
* @return TransactionGroup
*
* @throws FireflyException
*/
public function store(array $data): TransactionGroup
{
/** @var TransactionGroupFactory $factory */
$factory = app(TransactionGroupFactory::class);
$factory->setUser($this->user);
return $factory->create($data);
}
/**
* @param TransactionGroup $journal
* @param array $data
*
* @return TransactionGroup
*
* @throws FireflyException
* @throws FireflyException
*/
public function update(TransactionGroup $journal, array $data): TransactionGroup
{
/** @var JournalUpdateService $service */
$service = app(JournalUpdateService::class);
$journal = $service->update($journal, $data);
return $journal;
}
/** /**
* Update budget for a journal. * Update budget for a journal.
* *

View File

@ -319,21 +319,7 @@ interface JournalRepositoryInterface
*/ */
public function setUser(User $user); public function setUser(User $user);
/**
* @param array $data
*
* @throws FireflyException
* @return TransactionJournal
*/
public function store(array $data): TransactionGroup;
/**
* @param TransactionGroup $transactionGroup
* @param array $data
*
* @return TransactionGroup
*/
public function update(TransactionGroup $transactionGroup, array $data): TransactionGroup;
/** /**
* Update budget for a journal. * Update budget for a journal.

View File

@ -27,8 +27,12 @@ namespace FireflyIII\Repositories\TransactionGroup;
use Carbon\Carbon; use Carbon\Carbon;
use DB; use DB;
use Exception; use Exception;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Factory\TransactionGroupFactory;
use FireflyIII\Models\Note; use FireflyIII\Models\Note;
use FireflyIII\Models\TransactionGroup;
use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionJournal;
use FireflyIII\Services\Internal\Update\GroupUpdateService;
use FireflyIII\Support\NullArrayObject; use FireflyIII\Support\NullArrayObject;
/** /**
@ -36,6 +40,8 @@ use FireflyIII\Support\NullArrayObject;
*/ */
class TransactionGroupRepository implements TransactionGroupRepositoryInterface class TransactionGroupRepository implements TransactionGroupRepositoryInterface
{ {
private $user;
/** /**
* Constructor. * Constructor.
*/ */
@ -61,6 +67,7 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface
::table('journal_meta') ::table('journal_meta')
->where('transaction_journal_id', $journalId) ->where('transaction_journal_id', $journalId)
->whereIn('name', $fields) ->whereIn('name', $fields)
->whereNull('deleted_at')
->get(['name', 'data']); ->get(['name', 'data']);
$return = []; $return = [];
@ -85,6 +92,7 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface
::table('journal_meta') ::table('journal_meta')
->where('transaction_journal_id', $journalId) ->where('transaction_journal_id', $journalId)
->whereIn('name', $fields) ->whereIn('name', $fields)
->whereNull('deleted_at')
->get(['name', 'data']); ->get(['name', 'data']);
$return = []; $return = [];
@ -133,4 +141,45 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface
return $result->pluck('tag')->toArray(); return $result->pluck('tag')->toArray();
} }
/**
* @param mixed $user
*/
public function setUser($user): void
{
$this->user = $user;
}
/**
* @param array $data
*
* @return TransactionGroup
*
* @throws FireflyException
*/
public function store(array $data): TransactionGroup
{
/** @var TransactionGroupFactory $factory */
$factory = app(TransactionGroupFactory::class);
$factory->setUser($this->user);
return $factory->create($data);
}
/**
* @param TransactionGroup $transactionGroup
* @param array $data
*
* @return TransactionGroup
*
* @throws FireflyException
*/
public function update(TransactionGroup $transactionGroup, array $data): TransactionGroup
{
/** @var GroupUpdateService $service */
$service = app(GroupUpdateService::class);
$updatedGroup = $service->update($transactionGroup, $data);
return $updatedGroup;
}
} }

View File

@ -23,7 +23,10 @@ declare(strict_types=1);
namespace FireflyIII\Repositories\TransactionGroup; namespace FireflyIII\Repositories\TransactionGroup;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\TransactionGroup;
use FireflyIII\Support\NullArrayObject; use FireflyIII\Support\NullArrayObject;
use FireflyIII\User;
/** /**
* Interface TransactionGroupRepositoryInterface * Interface TransactionGroupRepositoryInterface
@ -67,4 +70,32 @@ interface TransactionGroupRepositoryInterface
* @return array * @return array
*/ */
public function getTags(int $journalId): array; public function getTags(int $journalId): array;
/**
* Set the user.
*
* @param User $user
*/
public function setUser(User $user): void;
/**
* Create a new transaction group.
*
* @param array $data
*
* @return TransactionGroup
* @throws FireflyException
*/
public function store(array $data): TransactionGroup;
/**
* Update an existing transaction group.
*
* @param TransactionGroup $transactionGroup
* @param array $data
*
* @return TransactionGroup
*/
public function update(TransactionGroup $transactionGroup, array $data): TransactionGroup;
} }

View File

@ -25,6 +25,7 @@ namespace FireflyIII\Repositories\TransactionType;
use FireflyIII\Models\TransactionType; use FireflyIII\Models\TransactionType;
use Log; use Log;
/** /**
* Class TransactionTypeRepository * Class TransactionTypeRepository
*/ */
@ -57,7 +58,8 @@ class TransactionTypeRepository implements TransactionTypeRepositoryInterface
return $type; return $type;
} }
$search = $this->findByType($typeString); $typeString = $typeString ?? TransactionType::WITHDRAWAL;
$search = $this->findByType($typeString);
if (null === $search) { if (null === $search) {
$search = $this->findByType(TransactionType::WITHDRAWAL); $search = $this->findByType(TransactionType::WITHDRAWAL);
} }

View File

@ -24,16 +24,17 @@ declare(strict_types=1);
namespace FireflyIII\Services\Internal\Support; namespace FireflyIII\Services\Internal\Support;
use Exception; use Exception;
use FireflyIII\Factory\BillFactory; use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Factory\BudgetFactory;
use FireflyIII\Factory\CategoryFactory;
use FireflyIII\Factory\TagFactory; use FireflyIII\Factory\TagFactory;
use FireflyIII\Factory\TransactionJournalMetaFactory; use FireflyIII\Models\Account;
use FireflyIII\Models\Bill; use FireflyIII\Models\AccountType;
use FireflyIII\Models\Budget;
use FireflyIII\Models\Category;
use FireflyIII\Models\Note; use FireflyIII\Models\Note;
use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
use FireflyIII\Support\NullArrayObject;
use Log; use Log;
/** /**
@ -42,26 +43,229 @@ use Log;
*/ */
trait JournalServiceTrait trait JournalServiceTrait
{ {
/** @var AccountRepositoryInterface */
private $accountRepository;
/** @var BudgetRepositoryInterface */
private $budgetRepository;
/** @var CategoryRepositoryInterface */
private $categoryRepository;
/** @var TagFactory */
private $tagFactory;
/**
* @param string|null $amount
*
* @return string
*/
protected function getForeignAmount(?string $amount): ?string
{
$result = null;
if (null === $amount) {
Log::debug('No foreign amount info in array. Return NULL');
return null;
}
if ('' === $amount) {
Log::debug('Foreign amount is empty string, return NULL.');
return null;
}
if (0 === bccomp('0', $amount)) {
Log::debug('Foreign amount is 0.0, return NULL.');
return null;
}
Log::debug(sprintf('Foreign amount is %s', $amount));
return $amount;
}
/**
* @param string $transactionType
* @param string $direction
* @param int|null $accountId
* @param string|null $accountName
*
* @return Account
* @throws FireflyException
*/
protected function getAccount(string $transactionType, string $direction, ?int $accountId, ?string $accountName): Account
{
// 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');
$expectedTypes = $array[$direction];
unset($array);
// and now try to find it, based on the type of transaction.
$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, 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, $search->id, $search->name, $search->accountType->type)
);
$result = $search;
}
}
// 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($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));
$result = $source;
}
}
// return cash account.
if (null === $result && null === $accountName
&& in_array(AccountType::CASH, $expectedTypes[$transactionType], true)) {
$result = $this->accountRepository->getCashAccount();
}
// 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;
}
/**
* @param string $amount
*
* @return string
* @throws FireflyException
*/
protected function getAmount(string $amount): string
{
if ('' === $amount) {
throw new FireflyException(sprintf('The amount cannot be an empty string: "%s"', $amount));
}
if (0 === bccomp('0', $amount)) {
throw new FireflyException(sprintf('The amount seems to be zero: "%s"', $amount));
}
return $amount;
}
/**
* @param TransactionJournal $journal
* @param NullArrayObject $data
*/
protected function storeBudget(TransactionJournal $journal, NullArrayObject $data): void
{
if (TransactionType::WITHDRAWAL !== $journal->transactionType->type) {
$journal->budgets()->sync([]);
return;
}
$budget = $this->budgetRepository->findBudget($data['budget_id'], $data['budget_name']);
if (null !== $budget) {
Log::debug(sprintf('Link budget #%d to journal #%d', $budget->id, $journal->id));
$journal->budgets()->sync([$budget->id]);
return;
}
// if the budget is NULL, sync empty.
$journal->budgets()->sync([]);
}
/**
* @param TransactionJournal $journal
* @param NullArrayObject $data
*/
protected function storeCategory(TransactionJournal $journal, NullArrayObject $data): void
{
$category = $this->categoryRepository->findCategory($data['category_id'], $data['category_name']);
if (null !== $category) {
Log::debug(sprintf('Link category #%d to journal #%d', $category->id, $journal->id));
$journal->categories()->sync([$category->id]);
return;
}
// if the category is NULL, sync empty.
$journal->categories()->sync([]);
}
/**
* @param TransactionJournal $journal
* @param string $notes
*/
protected function storeNotes(TransactionJournal $journal, ?string $notes): void
{
$notes = (string)$notes;
$note = $journal->notes()->first();
if ('' !== $notes) {
if (null === $note) {
$note = new Note;
$note->noteable()->associate($journal);
}
$note->text = $notes;
$note->save();
Log::debug(sprintf('Stored notes for journal #%d', $journal->id));
return;
}
if ('' === $notes && null !== $note) {
// try to delete existing notes.
try {
$note->delete();
} catch (Exception $e) {
Log::debug(sprintf('Could not delete journal notes: %s', $e->getMessage()));
}
}
}
/** /**
* Link tags to journal. * Link tags to journal.
* *
* @param TransactionJournal $journal * @param TransactionJournal $journal
* @param array $data * @param array $tags
* @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.CyclomaticComplexity)
*/ */
public function connectTags(TransactionJournal $journal, array $data): void protected function storeTags(TransactionJournal $journal, ?array $tags): void
{ {
/** @var TagFactory $factory */ $this->tagFactory->setUser($journal->user);
$factory = app(TagFactory::class);
$factory->setUser($journal->user);
$set = []; $set = [];
if (!\is_array($data['tags'])) { if (!is_array($tags)) {
return; // @codeCoverageIgnore return;
} }
foreach ($data['tags'] as $string) { foreach ($tags as $string) {
if ('' !== $string) { if ('' !== $string) {
$tag = $factory->findOrCreate($string); $tag = $this->tagFactory->findOrCreate($string);
if (null !== $tag) { if (null !== $tag) {
$set[] = $tag->id; $set[] = $tag->id;
} }
@ -71,117 +275,147 @@ trait JournalServiceTrait
} }
/**
* @param int|null $budgetId
* @param null|string $budgetName
*
* @return Budget|null
*/
protected function findBudget(?int $budgetId, ?string $budgetName): ?Budget
{
/** @var BudgetFactory $factory */
$factory = app(BudgetFactory::class);
$factory->setUser($this->user);
return $factory->find($budgetId, $budgetName); //
} // /**
// * Link tags to journal.
/** // *
* @param int|null $categoryId // * @param TransactionJournal $journal
* @param null|string $categoryName // * @param array $data
* // * @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @return Category|null // */
*/ // public function connectTags(TransactionJournal $journal, array $data): void
protected function findCategory(?int $categoryId, ?string $categoryName): ?Category // {
{ // /** @var TagFactory $factory */
Log::debug(sprintf('Going to find or create category #%d, with name "%s"', $categoryId, $categoryName)); // $factory = app(TagFactory::class);
/** @var CategoryFactory $factory */ // $factory->setUser($journal->user);
$factory = app(CategoryFactory::class); // $set = [];
$factory->setUser($this->user); // if (!\is_array($data['tags'])) {
// return; // @codeCoverageIgnore
return $factory->findOrCreate($categoryId, $categoryName); // }
} // foreach ($data['tags'] as $string) {
// if ('' !== $string) {
// $tag = $factory->findOrCreate($string);
/** // if (null !== $tag) {
* @param TransactionJournal $journal // $set[] = $tag->id;
* @param Budget|null $budget // }
*/ // }
protected function setBudget(TransactionJournal $journal, ?Budget $budget): void // }
{ // $journal->tags()->sync($set);
if (null === $budget) { // }
$journal->budgets()->sync([]); //
//
return; // /**
} // * @param int|null $budgetId
$journal->budgets()->sync([$budget->id]); // * @param null|string $budgetName
// *
} // * @return Budget|null
// */
// protected function findBudget(?int $budgetId, ?string $budgetName): ?Budget
/** // {
* @param TransactionJournal $journal // /** @var BudgetFactory $factory */
* @param Category|null $category // $factory = app(BudgetFactory::class);
*/ // $factory->setUser($this->user);
protected function setCategory(TransactionJournal $journal, ?Category $category): void //
{ // return $factory->find($budgetId, $budgetName);
if (null === $category) { // }
$journal->categories()->sync([]); //
// /**
return; // * @param int|null $categoryId
} // * @param null|string $categoryName
$journal->categories()->sync([$category->id]); // *
// * @return Category|null
} // */
// protected function findCategory(?int $categoryId, ?string $categoryName): ?Category
// {
/** // Log::debug(sprintf('Going to find or create category #%d, with name "%s"', $categoryId, $categoryName));
* @param TransactionJournal $journal // /** @var CategoryFactory $factory */
* @param array $data // $factory = app(CategoryFactory::class);
* @param string $field // $factory->setUser($this->user);
*/ //
protected function storeMeta(TransactionJournal $journal, array $data, string $field): void // return $factory->findOrCreate($categoryId, $categoryName);
{ // }
$set = [ //
'journal' => $journal, //
'name' => $field, // /**
'data' => (string)($data[$field] ?? ''), // * @param TransactionJournal $journal
]; // * @param Budget|null $budget
// */
Log::debug(sprintf('Going to store meta-field "%s", with value "%s".', $set['name'], $set['data'])); // protected function setBudget(TransactionJournal $journal, ?Budget $budget): void
// {
/** @var TransactionJournalMetaFactory $factory */ // if (null === $budget) {
$factory = app(TransactionJournalMetaFactory::class); // $journal->budgets()->sync([]);
$factory->updateOrCreate($set); //
} // return;
// }
/** // $journal->budgets()->sync([$budget->id]);
* @param TransactionJournal $journal //
* @param string $notes // }
*/ //
protected function storeNote(TransactionJournal $journal, ?string $notes): void //
{ // /**
$notes = (string)$notes; // * @param TransactionJournal $journal
if ('' !== $notes) { // * @param Category|null $category
$note = $journal->notes()->first(); // */
if (null === $note) { // protected function setCategory(TransactionJournal $journal, ?Category $category): void
$note = new Note; // {
$note->noteable()->associate($journal); // if (null === $category) {
} // $journal->categories()->sync([]);
$note->text = $notes; //
$note->save(); // return;
// }
return; // $journal->categories()->sync([$category->id]);
} //
$note = $journal->notes()->first(); // }
if (null !== $note) { //
try { //
$note->delete(); // /**
} catch (Exception $e) { // * @param TransactionJournal $journal
Log::debug(sprintf('Journal service trait could not delete note: %s', $e->getMessage())); // * @param array $data
} // * @param string $field
} // */
// protected function storeMeta(TransactionJournal $journal, array $data, string $field): void
// {
} // $set = [
// 'journal' => $journal,
// 'name' => $field,
// 'data' => (string)($data[$field] ?? ''),
// ];
//
// Log::debug(sprintf('Going to store meta-field "%s", with value "%s".', $set['name'], $set['data']));
//
// /** @var TransactionJournalMetaFactory $factory */
// $factory = app(TransactionJournalMetaFactory::class);
// $factory->updateOrCreate($set);
// }
//
// /**
// * @param TransactionJournal $journal
// * @param string $notes
// */
// protected function storeNote(TransactionJournal $journal, ?string $notes): void
// {
// $notes = (string)$notes;
// if ('' !== $notes) {
// $note = $journal->notes()->first();
// if (null === $note) {
// $note = new Note;
// $note->noteable()->associate($journal);
// }
// $note->text = $notes;
// $note->save();
//
// return;
// }
// $note = $journal->notes()->first();
// if (null !== $note) {
// try {
// $note->delete();
// } catch (Exception $e) {
// Log::debug(sprintf('Journal service trait could not delete note: %s', $e->getMessage()));
// }
// }
//
//
// }
} }

View File

@ -0,0 +1,84 @@
<?php
/**
* GroupUpdateService.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\Services\Internal\Update;
use FireflyIII\Models\TransactionGroup;
use FireflyIII\Models\TransactionJournal;
use Log;
/**
* Class GroupUpdateService
*/
class GroupUpdateService
{
/**
* Update a transaction group.
*
* @param TransactionGroup $transactionGroup
* @param array $data
*
* @return TransactionGroup
*/
public function update(TransactionGroup $transactionGroup, array $data): TransactionGroup
{
Log::debug('Now in group update service');
$transactions = $data['transactions'] ?? [];
// update group name.
if (array_key_exists('group_title', $data)) {
Log::debug(sprintf('Update transaction group #%d title.', $transactionGroup->id));
$transactionGroup->title = $data['group_title'];
$transactionGroup->save();
}
if (1 === count($transactions) && 1 === $transactionGroup->transactionJournals()->count()) {
/** @var TransactionJournal $first */
$first = $transactionGroup->transactionJournals()->first();
Log::debug(sprintf('Will now update journal #%d (only journal in group #%d)', $first->id, $transactionGroup->id));
$this->updateTransactionJournal($transactionGroup, $first, reset($transactions));
$transactionGroup->refresh();
return $transactionGroup;
}
die('cannot update split');
app('preferences')->mark();
}
/**
* Update single journal.
*
* @param TransactionGroup $transactionGroup
* @param TransactionJournal $journal
* @param array $data
*/
private function updateTransactionJournal(TransactionGroup $transactionGroup, TransactionJournal $journal, array $data): void
{
/** @var JournalUpdateService $updateService */
$updateService = app(JournalUpdateService::class);
$updateService->setTransactionGroup($transactionGroup);
$updateService->setTransactionJournal($journal);
$updateService->setData($data);
$updateService->update();
}
}

View File

@ -23,12 +23,25 @@ declare(strict_types=1);
namespace FireflyIII\Services\Internal\Update; namespace FireflyIII\Services\Internal\Update;
use FireflyIII\Factory\TransactionFactory; use Carbon\Carbon;
use Exception;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Factory\TagFactory;
use FireflyIII\Factory\TransactionJournalMetaFactory;
use FireflyIII\Factory\TransactionTypeFactory;
use FireflyIII\Models\Account;
use FireflyIII\Models\Transaction; use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionGroup;
use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType; use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use FireflyIII\Services\Internal\Support\JournalServiceTrait; use FireflyIII\Services\Internal\Support\JournalServiceTrait;
use Illuminate\Support\Collection; use FireflyIII\Support\NullArrayObject;
use FireflyIII\Validation\AccountValidator;
use Log; use Log;
/** /**
@ -40,163 +53,618 @@ class JournalUpdateService
{ {
use JournalServiceTrait; use JournalServiceTrait;
/** @var BillRepositoryInterface */
private $billRepository;
/** @var CurrencyRepositoryInterface */
private $currencyRepository;
/** @var array The data to update the journal with. */
private $data;
/** @var Account The destination account. */
private $destinationAccount;
/** @var Transaction */
private $destinationTransaction;
/** @var array All meta values that are dates. */
private $metaDate;
/** @var array All meta values that are strings. */
private $metaString;
/** @var Account Source account of the journal */
private $sourceAccount;
/** @var Transaction Source transaction of the journal. */
private $sourceTransaction;
/** @var TransactionGroup The parent group. */
private $transactionGroup;
/** @var TransactionJournal The journal to update. */
private $transactionJournal;
/** @var Account If new account info is submitted, this array will hold the valid destination. */
private $validDestination;
/** @var Account If new account info is submitted, this array will hold the valid source. */
private $validSource;
/** /**
* Constructor. * JournalUpdateService constructor.
*/ */
public function __construct() public function __construct()
{ {
if ('testing' === config('app.env')) { $this->billRepository = app(BillRepositoryInterface::class);
Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); $this->categoryRepository = app(CategoryRepositoryInterface::class);
$this->budgetRepository = app(BudgetRepositoryInterface::class);
$this->tagFactory = app(TagFactory::class);
$this->accountRepository = app(AccountRepositoryInterface::class);
$this->currencyRepository = app(CurrencyRepositoryInterface::class);
$this->metaString = ['sepa_cc', 'sepa_ct_op', 'sepa_ct_id', 'sepa_db', 'sepa_country', 'sepa_ep', 'sepa_ci', 'sepa_batch_id', 'recurrence_id',
'internal_reference', 'bunq_payment_id', 'external_id',];
$this->metaDate = ['interest_date', 'book_date', 'process_date', 'due_date', 'payment_date', 'invoice_date',];
}
/**
* @param array $data
*/
public function setData(array $data): void
{
$this->data = $data;
}
/**
* @param TransactionGroup $transactionGroup
*/
public function setTransactionGroup(TransactionGroup $transactionGroup): void
{
$this->transactionGroup = $transactionGroup;
$this->billRepository->setUser($transactionGroup->user);
$this->categoryRepository->setUser($transactionGroup->user);
$this->budgetRepository->setUser($transactionGroup->user);
$this->tagFactory->setUser($transactionGroup->user);
$this->accountRepository->setUser($transactionGroup->user);
}
/**
* @param TransactionJournal $transactionJournal
*/
public function setTransactionJournal(TransactionJournal $transactionJournal): void
{
$this->transactionJournal = $transactionJournal;
}
/**
*
*/
public function update(): void
{
Log::debug(sprintf('Now in JournalUpdateService for journal #%d.', $this->transactionJournal->id));
// can we update account data using the new type?
if ($this->hasValidAccounts()) {
Log::info('-- account info is valid, now update.');
// update accounts:
$this->updateAccounts();
// then also update transaction journal type ID:
$this->updateType();
$this->transactionJournal->refresh();
}
// find and update bill, if possible.
$this->updateBill();
// update journal fields.
$this->updateField('description');
$this->updateField('date');
$this->updateField('order');
$this->transactionJournal->save();
$this->transactionJournal->refresh();
// update category
if ($this->hasFields(['category_id', 'category_name'])) {
Log::debug('Will update category.');
$this->storeCategory($this->transactionJournal, new NullArrayObject($this->data));
}
// update budget
if ($this->hasFields(['budget_id', 'budget_name'])) {
Log::debug('Will update budget.');
$this->storeBudget($this->transactionJournal, new NullArrayObject($this->data));
}
// update tags
if ($this->hasFields(['tags'])) {
Log::debug('Will update tags.');
$tags = $this->data['tags'] ?? null;
$this->storeTags($this->transactionJournal, $tags);
}
// update notes.
if ($this->hasFields(['notes'])) {
$notes = '' === (string)$this->data['notes'] ? null : $this->data['notes'];
$this->storeNotes($this->transactionJournal, $notes);
}
// update meta fields.
// first string
if ($this->hasFields($this->metaString)) {
Log::debug('Meta string fields are present.');
$this->updateMetaFields();
}
// then date fields.
if ($this->hasFields($this->metaDate)) {
Log::debug('Meta date fields are present.');
$this->updateMetaDateFields();
}
// update transactions.
if ($this->hasFields(['currency_id', 'currency_code'])) {
$this->updateCurrency();
}
if ($this->hasFields(['amount'])) {
$this->updateAmount();
}
// amount, foreign currency.
if ($this->hasFields(['foreign_currency_id', 'foreign_currency_code', 'foreign_amount'])) {
$this->updateForeignAmount();
}
// TODO update hash
app('preferences')->mark();
$this->transactionJournal->refresh();
}
/**
* Get destination transaction.
*
* @return Transaction
*/
private function getDestinationTransaction(): Transaction
{
if (null === $this->destinationTransaction) {
$this->destinationTransaction = $this->transactionJournal->transactions()->where('amount', '>', 0)->first();
}
return $this->destinationTransaction;
}
/**
* This method returns the current or expected type of the journal (in case of a change) based on the data in the array.
*
* If the array contains key 'type' and the value is correct, this is returned. Otherwise, the original type is returned.
*
* @return string
*/
private function getExpectedType(): string
{
Log::debug('Now in getExpectedType()');
if ($this->hasFields(['type'])) {
return ucfirst('opening-balance' === $this->data['type'] ? 'opening balance' : $this->data['type']);
}
return $this->transactionJournal->transactionType->type;
}
/**
* @return Account
*/
private function getOriginalDestinationAccount(): Account
{
if (null === $this->destinationAccount) {
$destination = $this->getSourceTransaction();
$this->destinationAccount = $destination->account;
}
return $this->destinationAccount;
}
/**
* @return Account
*/
private function getOriginalSourceAccount(): Account
{
if (null === $this->sourceAccount) {
$source = $this->getSourceTransaction();
$this->sourceAccount = $source->account;
}
return $this->sourceAccount;
}
/**
* @return Transaction
*/
private function getSourceTransaction(): Transaction
{
if (null === $this->sourceTransaction) {
$this->sourceTransaction = $this->transactionJournal->transactions()->with(['account'])->where('amount', '<', 0)->first();
}
return $this->sourceTransaction;
}
/**
* Does a validation and returns the destination account. This method will break if the dest isn't really valid.
*
* @return Account
*/
private function getValidDestinationAccount(): Account
{
Log::debug('Now in getValidDestinationAccount().');
if (!$this->hasFields(['destination_id', 'destination_name'])) {
return $this->getOriginalDestinationAccount();
}
$destId = $this->data['destination_id'] ?? null;
$destName = $this->data['destination_name'] ?? null;
// make new account validator.
$expectedType = $this->getExpectedType();
Log::debug(sprintf('Expected type (new or unchanged) is %s', $expectedType));
try {
$result = $this->getAccount($expectedType, 'destination', $destId, $destName);
} catch (FireflyException $e) {
Log::error(sprintf('getValidDestinationAccount() threw unexpected error: %s', $e->getMessage()));
$result = $this->getOriginalDestinationAccount();
}
return $result;
}
/**
* Does a validation and returns the source account. This method will break if the source isn't really valid.
*
* @return Account
*/
private function getValidSourceAccount(): Account
{
Log::debug('Now in getValidSourceAccount().');
$sourceId = $this->data['source_id'] ?? null;
$sourceName = $this->data['source_name'] ?? null;
if (!$this->hasFields(['source_id', 'source_name'])) {
return $this->getOriginalSourceAccount();
}
$expectedType = $this->getExpectedType();
try {
$result = $this->getAccount($expectedType, 'source', $sourceId, $sourceName);
} catch (FireflyException $e) {
Log::error(sprintf('Cant get the valid source account: %s', $e->getMessage()));
$result = $this->getOriginalSourceAccount();
}
Log::debug(sprintf('getValidSourceAccount() will return #%d ("%s")', $result->id, $result->name));
return $result;
}
/**
* @param array $fields
*
* @return bool
*/
private function hasFields(array $fields): bool
{
foreach ($fields as $field) {
if (array_key_exists($field, $this->data)) {
return true;
}
}
return false;
}
/**
* @return bool
*/
private function hasValidAccounts(): bool
{
return $this->hasValidSourceAccount() && $this->hasValidDestinationAccount();
}
/**
* @return bool
*/
private function hasValidDestinationAccount(): bool
{
Log::debug('Now in hasValidDestinationAccount().');
$destId = $this->data['destination_id'] ?? null;
$destName = $this->data['destination_name'] ?? null;
if (!$this->hasFields(['destination_id', 'destination_name'])) {
$destination = $this->getOriginalDestinationAccount();
$destId = $destination->id;
$destName = $destination->name;
}
// make new account validator.
$expectedType = $this->getExpectedType();
Log::debug(sprintf('Expected type (new or unchanged) is %s', $expectedType));
// make a new validator.
/** @var AccountValidator $validator */
$validator = app(AccountValidator::class);
$validator->setTransactionType($expectedType);
$validator->setUser($this->transactionJournal->user);
$validator->source = $this->getValidSourceAccount();
$result = $validator->validateDestination($destId, $destName);
Log::debug(sprintf('hasValidDestinationAccount(%d, "%s") will return %s', $destId, $destName, var_export($result, true)));
// validate submitted info:
return $result;
}
/**
* @return bool
*/
private function hasValidSourceAccount(): bool
{
Log::debug('Now in hasValidSourceAccount().');
$sourceId = $this->data['source_id'] ?? null;
$sourceName = $this->data['source_name'] ?? null;
if (!$this->hasFields(['source_id', 'source_name'])) {
$sourceAccount = $this->getOriginalSourceAccount();
$sourceId = $sourceAccount->id;
$sourceName = $sourceAccount->name;
}
// make new account validator.
$expectedType = $this->getExpectedType();
Log::debug(sprintf('Expected type (new or unchanged) is %s', $expectedType));
// make a new validator.
/** @var AccountValidator $validator */
$validator = app(AccountValidator::class);
$validator->setTransactionType($expectedType);
$validator->setUser($this->transactionJournal->user);
$result = $validator->validateSource($sourceId, $sourceName);
Log::debug(sprintf('hasValidSourceAccount(%d, "%s") will return %s', $sourceId, $sourceName, var_export($result, true)));
// validate submitted info:
return $result;
}
/**
* Will update the source and destination accounts of this journal. Assumes they are valid.
*/
private function updateAccounts(): void
{
$source = $this->getValidSourceAccount();
$destination = $this->getValidDestinationAccount();
// cowardly refuse to update if both accounts are the same.
if ($source->id === $destination->id) {
Log::error(sprintf('Source + dest accounts are equal (%d, "%s")', $source->id, $source->name));
return;
}
$sourceTransaction = $this->getSourceTransaction();
$sourceTransaction->account()->associate($source);
$sourceTransaction->save();
$destinationTransaction = $this->getDestinationTransaction();
$destinationTransaction->account()->associate($destination);
$destinationTransaction->save();
Log::debug(sprintf('Will set source to #%d ("%s")', $source->id, $source->name));
Log::debug(sprintf('Will set dest to #%d ("%s")', $destination->id, $destination->name));
}
/**
*
*/
private function updateAmount(): void
{
$value = $this->data['amount'] ?? '';
try {
$amount = $this->getAmount($value);
} catch (FireflyException $e) {
Log::debug(sprintf('getAmount("%s") returns error: %s', $value, $e->getMessage()));
return;
}
Log::debug(sprintf('Updated amount to %s', $amount));
$sourceTransaction = $this->getSourceTransaction();
$sourceTransaction->amount = app('steam')->negative($value);
$sourceTransaction->save();
$destinationTransaction = $this->getDestinationTransaction();
$destinationTransaction->amount = app('steam')->positive($value);
$destinationTransaction->save();
}
/**
* Update journal bill information.
*/
private function updateBill(): void
{
$type = $this->transactionJournal->transactionType->type;
if ((
array_key_exists('bill_id', $this->data)
|| array_key_exists('bill_name', $this->data)
)
&& TransactionType::WITHDRAWAL === $type
) {
$billId = (int)($this->data['bill_id'] ?? 0);
$billName = (string)($this->data['bill_name'] ?? '');
$bill = $this->billRepository->findBill($billId, $billName);
$this->transactionJournal->bill_id = null === $bill ? null : $bill->id;
Log::debug('Updated bill ID');
}
}
/**
*
*/
private function updateCurrency(): void
{
$currencyId = $this->data['currency_id'] ?? null;
$currencyCode = $this->data['currency_code'] ?? null;
$currency = $this->currencyRepository->findCurrency($currencyId, $currencyCode);
if (null !== $currency) {
// update currency everywhere.
$this->transactionJournal->transaction_currency_id = $currency->id;
$this->transactionJournal->save();
$source = $this->getSourceTransaction();
$source->transaction_currency_id = $currency->id;
$source->save();
$dest = $this->getDestinationTransaction();
$dest->transaction_currency_id = $currency->id;
$dest->save();
Log::debug(sprintf('Updated currency to #%d (%s)', $currency->id, $currency->code));
}
}
/**
* Update journal generic field. Cannot be set to NULL.
*
* @param $fieldName
*/
private function updateField($fieldName): void
{
if (array_key_exists($fieldName, $this->data) && '' !== (string)$this->data[$fieldName]) {
$this->transactionJournal->$fieldName = $this->data[$fieldName];
Log::debug(sprintf('Updated %s', $fieldName));
} }
} }
/** /**
* @param TransactionJournal $journal
* @param array $data
* *
* @return TransactionJournal
* @throws \FireflyIII\Exceptions\FireflyException
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
*/ */
public function update(TransactionJournal $journal, array $data): TransactionJournal private function updateForeignAmount(): void
{ {
// update journal: $amount = $this->data['foreign_amount'] ?? null;
$journal->description = $data['description']; $foreignAmount = $this->getForeignAmount($amount);
$journal->date = $data['date']; $source = $this->getSourceTransaction();
$journal->save(); $dest = $this->getDestinationTransaction();
$foreignCurrency = $source->foreignCurrency;
// update transactions: // find currency in data array
/** @var TransactionUpdateService $service */ $newForeignId = $this->data['foreign_currency_id'] ?? null;
$service = app(TransactionUpdateService::class); $newForeignCode = $this->data['foreign_currency_code'] ?? null;
$service->setUser($journal->user); $foreignCurrency = $this->currencyRepository->findCurrencyNull($newForeignId, $newForeignCode) ?? $foreignCurrency;
// create transactions: // not the same as normal currency
/** @var TransactionFactory $factory */ if (null !== $foreignCurrency && $foreignCurrency->id === $this->transactionJournal->transaction_currency_id) {
$factory = app(TransactionFactory::class); Log::error(sprintf('Foreign currency is equal to normal currency (%s)', $foreignCurrency->code));
$factory->setUser($journal->user);
Log::debug(sprintf('Found %d rows in array (should result in %d transactions', \count($data['transactions']), \count($data['transactions']) * 2)); return;
/**
* @var int $identifier
* @var array $trData
*/
foreach ($data['transactions'] as $identifier => $trData) {
// exists transaction(s) with this identifier? update!
/** @var Collection $existing */
$existing = $journal->transactions()->where('identifier', $identifier)->get();
Log::debug(sprintf('Found %d transactions with identifier %d', $existing->count(), $identifier));
if ($existing->count() > 0) {
$existing->each(
function (Transaction $transaction) use ($service, $trData) {
Log::debug(sprintf('Update transaction #%d (identifier %d)', $transaction->id, $trData['identifier']));
$service->update($transaction, $trData);
}
);
continue;
}
Log::debug('Found none, so create a pair.');
// otherwise, create!
$factory->createPair($journal, $trData);
} }
// could be that journal has more transactions than submitted (remove split)
$transactions = $journal->transactions()->where('amount', '>', 0)->get(); // add foreign currency info to source and destination if possible.
Log::debug(sprintf('Journal #%d has %d transactions', $journal->id, $transactions->count())); if (null !== $foreignCurrency && null !== $foreignAmount) {
/** @var Transaction $transaction */ $source->foreign_currency_id = $foreignCurrency->id;
foreach ($transactions as $transaction) { $source->foreign_amount = app('steam')->negative($foreignAmount);
Log::debug(sprintf('Now at transaction %d with identifier %d', $transaction->id, $transaction->identifier)); $source->save();
if (!isset($data['transactions'][$transaction->identifier])) {
Log::debug('No such entry in array, delete this set of transactions.');
$journal->transactions()->where('identifier', $transaction->identifier)->delete(); $dest->foreign_currency_id = $foreignCurrency->id;
} $dest->foreign_amount = app('steam')->positive($foreignAmount);
$dest->save();
Log::debug(sprintf('Update foreign info to %s (#%d) %s', $foreignCurrency->code, $foreignCurrency->id, $foreignAmount));
return;
} }
Log::debug(sprintf('New count is %d, transactions array held %d items', $journal->transactions()->count(), \count($data['transactions']))); if ('0' === $amount) {
$source->foreign_currency_id = null;
$source->foreign_amount = null;
$source->save();
// connect bill: $dest->foreign_currency_id = null;
$this->connectBill($journal, $data); $dest->foreign_amount = null;
$dest->save();
// connect tags: Log::debug(sprintf('Foreign amount is "%s" so remove foreign amount info.', $amount));
$this->connectTags($journal, $data); }
Log::info('Not enough info to update foreign currency info.');
// remove category from journal:
$journal->categories()->sync([]);
// remove budgets from journal:
$journal->budgets()->sync([]);
// update or create custom fields:
// store date meta fields (if present):
$this->storeMeta($journal, $data, 'interest_date');
$this->storeMeta($journal, $data, 'book_date');
$this->storeMeta($journal, $data, 'process_date');
$this->storeMeta($journal, $data, 'due_date');
$this->storeMeta($journal, $data, 'payment_date');
$this->storeMeta($journal, $data, 'invoice_date');
$this->storeMeta($journal, $data, 'internal_reference');
// store note:
$this->storeNote($journal, $data['notes']);
return $journal;
} }
/** /**
* Update budget for a journal.
* *
* @param TransactionJournal $journal
* @param int $budgetId
*
* @return TransactionJournal
*/ */
public function updateBudget(TransactionJournal $journal, int $budgetId): TransactionJournal private function updateMetaDateFields(): void
{ {
/** @var TransactionUpdateService $service */ /** @var TransactionJournalMetaFactory $factory */
$service = app(TransactionUpdateService::class); $factory = app(TransactionJournalMetaFactory::class);
$service->setUser($journal->user);
if (TransactionType::WITHDRAWAL === $journal->transactionType->type) { foreach ($this->metaDate as $field) {
/** @var Transaction $transaction */ if ($this->hasFields([$field])) {
foreach ($journal->transactions as $transaction) { try {
$service->updateBudget($transaction, $budgetId); $value = '' === $this->data[$field] ? null : new Carbon($this->data[$field]);
} catch (Exception $e) {
Log::debug(sprintf('%s is not a valid date value: %s', $this->data[$field], $e->getMessage()));
return;
}
Log::debug(sprintf('Field "%s" is present ("%s"), try to update it.', $field, $value));
$set = [
'journal' => $this->transactionJournal,
'name' => $field,
'data' => $value,
];
$factory->updateOrCreate($set);
} }
return $journal;
} }
// clear budget.
/** @var Transaction $transaction */
foreach ($journal->transactions as $transaction) {
$transaction->budgets()->sync([]);
}
// remove budgets from journal:
$journal->budgets()->sync([]);
return $journal;
} }
/** /**
* Update category for a journal.
* *
* @param TransactionJournal $journal
* @param string $category
*
* @return TransactionJournal
*/ */
public function updateCategory(TransactionJournal $journal, string $category): TransactionJournal private function updateMetaFields(): void
{ {
/** @var TransactionUpdateService $service */ /** @var TransactionJournalMetaFactory $factory */
$service = app(TransactionUpdateService::class); $factory = app(TransactionJournalMetaFactory::class);
$service->setUser($journal->user);
/** @var Transaction $transaction */ foreach ($this->metaString as $field) {
foreach ($journal->transactions as $transaction) { if ($this->hasFields([$field])) {
$service->updateCategory($transaction, $category); $value = '' === $this->data[$field] ? null : $this->data[$field];
Log::debug(sprintf('Field "%s" is present ("%s"), try to update it.', $field, $value));
$set = [
'journal' => $this->transactionJournal,
'name' => $field,
'data' => $value,
];
$factory->updateOrCreate($set);
}
} }
// make journal empty:
$journal->categories()->sync([]);
return $journal;
} }
/**
* Updates journal transaction type.
*/
private function updateType(): void
{
Log::debug('Now in updateType()');
if ($this->hasFields(['type'])) {
$type = 'opening-balance' === $this->data['type'] ? 'opening balance' : $this->data['type'];
Log::debug(
sprintf(
'Trying to change journal #%d from a %s to a %s.',
$this->transactionJournal->id, $this->transactionJournal->transactionType->type, $type
)
);
/** @var TransactionTypeFactory $typeFactory */
$typeFactory = app(TransactionTypeFactory::class);
$result = $typeFactory->find($this->data['type']);
if (null !== $result) {
Log::debug('Changed transaction type!');
$this->transactionJournal->transaction_type_id = $result->id;
$this->transactionJournal->save();
return;
}
return;
}
Log::debug('No type field present.');
}
} }

View File

@ -0,0 +1,53 @@
<?php
/**
* TransactionGroup.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\Support\Binder;
use FireflyIII\Models\TransactionJournal;
use Illuminate\Routing\Route;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
* Class TransactionGroup.
*/
class TransactionGroup implements BinderInterface
{
/**
* @param string $value
* @param Route $route
*
* @return TransactionGroup
* @throws NotFoundHttpException
*/
public static function routeBinder(string $value, Route $route): TransactionGroup
{
if (auth()->check()) {
$group = auth()->user()->transactionGroups()
->find($value);
if (null !== $group) {
return $group;
}
}
throw new NotFoundHttpException;
}
}

View File

@ -195,8 +195,8 @@ class ImportTransaction
return; return;
} }
$modifiers = ['rabo-debit-credit', 'ing-debit-credit']; $modifiers = ['generic-debit-credit'];
if (\in_array($role, $modifiers, true)) { if (in_array($role, $modifiers, true)) {
$this->modifiers[$role] = $columnValue->getValue(); $this->modifiers[$role] = $columnValue->getValue();
return; return;

View File

@ -119,6 +119,7 @@ class TransactionGroupTransformer extends AbstractTransformer
'transaction_journal_id' => $row['transaction_journal_id'], 'transaction_journal_id' => $row['transaction_journal_id'],
'type' => strtolower($type), 'type' => strtolower($type),
'date' => $row['date']->toAtomString(), 'date' => $row['date']->toAtomString(),
'order' => $row['order'],
'currency_id' => $row['currency_id'], 'currency_id' => $row['currency_id'],
'currency_code' => $row['currency_code'], 'currency_code' => $row['currency_code'],
@ -163,7 +164,7 @@ class TransactionGroupTransformer extends AbstractTransformer
'original_source' => $metaFieldData['original_source'], 'original_source' => $metaFieldData['original_source'],
'recurrence_id' => $metaFieldData['recurrence_id'], 'recurrence_id' => $metaFieldData['recurrence_id'],
'bunq_payment_id' => $metaFieldData['bunq_payment_id'], 'bunq_payment_id' => $metaFieldData['bunq_payment_id'],
'import_hash_v2' => $metaFieldData['import_hash_v2'], 'import_hash_v2' => $metaFieldData['import_hash_v2'],
'sepa_cc' => $metaFieldData['sepa_cc'], 'sepa_cc' => $metaFieldData['sepa_cc'],
'sepa_ct_op' => $metaFieldData['sepa_ct_op'], 'sepa_ct_op' => $metaFieldData['sepa_ct_op'],

View File

@ -27,6 +27,7 @@ use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType; use FireflyIII\Models\AccountType;
use FireflyIII\Models\TransactionType; use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\User;
use Log; use Log;
/** /**
@ -50,6 +51,8 @@ class AccountValidator
private $combinations; private $combinations;
/** @var string */ /** @var string */
private $transactionType; private $transactionType;
/** @var User */
private $user;
/** /**
* AccountValidator constructor. * AccountValidator constructor.
@ -69,9 +72,19 @@ class AccountValidator
*/ */
public function setTransactionType(string $transactionType): void public function setTransactionType(string $transactionType): void
{ {
Log::debug(sprintf('Transaction type for validator is now %s', ucfirst($transactionType)));
$this->transactionType = ucfirst($transactionType); $this->transactionType = ucfirst($transactionType);
} }
/**
* @param User $user
*/
public function setUser(User $user): void
{
$this->user = $user;
$this->accountRepository->setUser($user);
}
/** /**
* @param int|null $destinationId * @param int|null $destinationId
* @param $destinationName * @param $destinationName
@ -83,7 +96,7 @@ class AccountValidator
Log::debug(sprintf('Now in AccountValidator::validateDestination(%d, "%s")', $destinationId, $destinationName)); Log::debug(sprintf('Now in AccountValidator::validateDestination(%d, "%s")', $destinationId, $destinationName));
if (null === $this->source) { if (null === $this->source) {
Log::error('Source is NULL'); Log::error('Source is NULL, always FALSE.');
$this->destError = 'No source account validation has taken place yet. Please do this first or overrule the object.'; $this->destError = 'No source account validation has taken place yet. Please do this first or overrule the object.';
return false; return false;
@ -121,9 +134,10 @@ class AccountValidator
*/ */
public function validateSource(?int $accountId, ?string $accountName): bool public function validateSource(?int $accountId, ?string $accountName): bool
{ {
Log::debug(sprintf('Now in AccountValidator::validateSource(%d, "%s")', $accountId, $accountName));
switch ($this->transactionType) { switch ($this->transactionType) {
default: default:
$result = false; $result = false;
$this->sourceError = sprintf('Cannot handle type "%s"', $this->transactionType); $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)); Log::error(sprintf('AccountValidator::validateSource cannot handle "%s", so it will always return false.', $this->transactionType));
break; break;
@ -241,7 +255,6 @@ class AccountValidator
// if the account can be created anyway we don't need to search. // if the account can be created anyway we don't need to search.
if (null === $result && true === $this->canCreateTypes($validTypes)) { if (null === $result && true === $this->canCreateTypes($validTypes)) {
Log::debug('Can create some of these types, so return true.'); Log::debug('Can create some of these types, so return true.');
$this->createDestinationAccount($accountName);
$result = true; $result = true;
} }
@ -249,15 +262,18 @@ class AccountValidator
// otherwise try to find the account: // otherwise try to find the account:
$search = $this->findExistingAccount($validTypes, (int)$accountId, (string)$accountName); $search = $this->findExistingAccount($validTypes, (int)$accountId, (string)$accountName);
if (null === $search) { if (null === $search) {
Log::debug('findExistingAccount() returned NULL, so the result is false.');
$this->destError = (string)trans('validation.deposit_dest_bad_data', ['id' => $accountId, 'name' => $accountName]); $this->destError = (string)trans('validation.deposit_dest_bad_data', ['id' => $accountId, 'name' => $accountName]);
$result = false; $result = false;
} }
if (null !== $search) { if (null !== $search) {
Log::debug(sprintf('findExistingAccount() returned #%d ("%s"), so the result is true.', $search->id, $search->name));
$this->destination = $search; $this->destination = $search;
$result = true; $result = true;
} }
} }
$result = $result ?? false; $result = $result ?? false;
Log::debug(sprintf('validateDepositDestination(%d, "%s") will return %s', $accountId, $accountName, var_export($result, true)));
return $result; return $result;
} }
@ -270,6 +286,7 @@ class AccountValidator
*/ */
private function validateDepositSource(?int $accountId, ?string $accountName): bool private function validateDepositSource(?int $accountId, ?string $accountName): bool
{ {
Log::debug(sprintf('Now in validateDepositSource(%d, "%s")', $accountId, $accountName));
$result = null; $result = null;
// source can be any of the following types. // source can be any of the following types.
$validTypes = array_keys($this->combinations[$this->transactionType]); $validTypes = array_keys($this->combinations[$this->transactionType]);
@ -281,10 +298,25 @@ class AccountValidator
$result = false; $result = false;
} }
// if the user submits an ID only but that ID is not of the correct type,
// return false.
if (null !== $accountId && null === $accountName) {
$search = $this->accountRepository->findNull($accountId);
if (null !== $search && !in_array($search->accountType->type, $validTypes, true)) {
Log::debug(sprintf('User submitted only an ID (#%d), which is a "%s", so this is not a valid source.', $accountId, $search->accountType->type));
$result = false;
}
}
// if the account can be created anyway we don't need to search. // if the account can be created anyway we don't need to search.
if (null === $result && true === $this->canCreateTypes($validTypes)) { if (null === $result && true === $this->canCreateTypes($validTypes)) {
// set the source to be a (dummy) revenue account.
$result = true; $result = true;
// set the source to be a (dummy) revenue account.
$account = new Account;
$accountType = AccountType::whereType(AccountType::REVENUE)->first();
$account->accountType = $accountType;
$this->source = $account;
} }
$result = $result ?? false; $result = $result ?? false;
@ -332,6 +364,7 @@ class AccountValidator
*/ */
private function validateTransferSource(?int $accountId, ?string $accountName): bool private function validateTransferSource(?int $accountId, ?string $accountName): bool
{ {
Log::debug(sprintf('Now in validateTransferSource(%d, "%s")', $accountId, $accountName));
// source can be any of the following types. // source can be any of the following types.
$validTypes = array_keys($this->combinations[$this->transactionType]); $validTypes = array_keys($this->combinations[$this->transactionType]);
if (null === $accountId && null === $accountName && false === $this->canCreateTypes($validTypes)) { if (null === $accountId && null === $accountName && false === $this->canCreateTypes($validTypes)) {
@ -362,6 +395,7 @@ class AccountValidator
*/ */
private function validateWithdrawalDestination(?int $accountId, ?string $accountName): bool private function validateWithdrawalDestination(?int $accountId, ?string $accountName): bool
{ {
Log::debug(sprintf('Now in validateWithdrawalDestination(%d, "%s")', $accountId, $accountName));
// source can be any of the following types. // source can be any of the following types.
$validTypes = $this->combinations[$this->transactionType][$this->source->accountType->type] ?? []; $validTypes = $this->combinations[$this->transactionType][$this->source->accountType->type] ?? [];
if (null === $accountId && null === $accountName && false === $this->canCreateTypes($validTypes)) { if (null === $accountId && null === $accountName && false === $this->canCreateTypes($validTypes)) {
@ -377,6 +411,7 @@ class AccountValidator
return true; return true;
} }
// don't expect to end up here: // don't expect to end up here:
return false; return false;
} }
@ -389,6 +424,7 @@ class AccountValidator
*/ */
private function validateWithdrawalSource(?int $accountId, ?string $accountName): bool private function validateWithdrawalSource(?int $accountId, ?string $accountName): bool
{ {
Log::debug(sprintf('Now in validateWithdrawalSource(%d, "%s")', $accountId, $accountName));
// source can be any of the following types. // source can be any of the following types.
$validTypes = array_keys($this->combinations[$this->transactionType]); $validTypes = array_keys($this->combinations[$this->transactionType]);
if (null === $accountId && null === $accountName && false === $this->canCreateTypes($validTypes)) { if (null === $accountId && null === $accountName && false === $this->canCreateTypes($validTypes)) {

View File

@ -24,6 +24,7 @@ declare(strict_types=1);
namespace FireflyIII\Validation; namespace FireflyIII\Validation;
use FireflyIII\Models\Transaction; use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionGroup;
use Illuminate\Validation\Validator; use Illuminate\Validation\Validator;
/** /**
@ -31,6 +32,16 @@ use Illuminate\Validation\Validator;
*/ */
trait TransactionValidation trait TransactionValidation
{ {
/**
* If type is set, source + destination info is mandatory.
*
* @param Validator $validator
*/
protected function validateAccountPresence(Validator $validator): void
{
// TODO
}
/** /**
* Validates the given account information. Switches on given transaction type. * Validates the given account information. Switches on given transaction type.
@ -98,7 +109,9 @@ trait TransactionValidation
// no valid descriptions? // no valid descriptions?
if (0 === $validDescriptions) { if (0 === $validDescriptions) {
$validator->errors()->add('description', (string)trans('validation.filled', ['attribute' => (string)trans('validation.attributes.description')])); $validator->errors()->add(
'transactions.0.description', (string)trans('validation.filled', ['attribute' => (string)trans('validation.attributes.description')])
);
} }
} }
@ -148,7 +161,7 @@ trait TransactionValidation
$transactions = $data['transactions'] ?? []; $transactions = $data['transactions'] ?? [];
// need at least one transaction // need at least one transaction
if (0 === \count($transactions)) { if (0 === \count($transactions)) {
$validator->errors()->add('description', (string)trans('validation.at_least_one_transaction')); $validator->errors()->add('transactions.0.description', (string)trans('validation.at_least_one_transaction'));
} }
} }
@ -233,6 +246,47 @@ trait TransactionValidation
} }
} }
/**
* All types of splits must be equal.
*
* @param Validator $validator
*/
public function validateTransactionTypesForUpdate(Validator $validator): void
{
$data = $validator->getData();
$transactions = $data['transactions'] ?? [];
$types = [];
foreach ($transactions as $index => $transaction) {
$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;
}
}
/**
* @param Validator $validator
* @param TransactionGroup $transactionGroup
*/
private function validateJournalIds(Validator $validator, TransactionGroup $transactionGroup): void
{
$data = $validator->getData();
$transactions = $data['transactions'] ?? [];
if (count($transactions) < 2) {
return;
}
foreach ($transactions as $index => $transaction) {
$journalId = (int)($transaction['transaction_journal_id'] ?? 0);
$count = $transactionGroup->transactionJournals()->where('id', $journalId)->count();
if (0 === $journalId || 0 === $count) {
$validator->errors()->add(sprintf('transactions.%d.source_name', $index), (string)trans('validation.need_id_in_edit'));
}
}
}
// /** // /**
// * Throws an error when this asset account is invalid. // * Throws an error when this asset account is invalid.
// * // *

View File

@ -24,10 +24,44 @@
declare(strict_types=1); declare(strict_types=1);
use FireflyIII\Export\Exporter\CsvExporter; use FireflyIII\Export\Exporter\CsvExporter;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType; use FireflyIII\Models\AccountType;
use FireflyIII\Models\Attachment;
use FireflyIII\Models\AvailableBudget;
use FireflyIII\Models\Bill;
use FireflyIII\Models\Budget;
use FireflyIII\Models\BudgetLimit;
use FireflyIII\Models\Category;
use FireflyIII\Models\ExportJob;
use FireflyIII\Models\ImportJob;
use FireflyIII\Models\LinkType;
use FireflyIII\Models\PiggyBank;
use FireflyIII\Models\Preference;
use FireflyIII\Models\Recurrence;
use FireflyIII\Models\Rule;
use FireflyIII\Models\RuleGroup;
use FireflyIII\Models\Tag;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\TransactionGroup;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionJournalLink;
use FireflyIII\Models\TransactionType as TransactionTypeModel; use FireflyIII\Models\TransactionType as TransactionTypeModel;
use FireflyIII\Services\Currency\FixerIOv2; use FireflyIII\Services\Currency\FixerIOv2;
use FireflyIII\Services\Currency\RatesApiIOv1; use FireflyIII\Services\Currency\RatesApiIOv1;
use FireflyIII\Support\Binder\AccountList;
use FireflyIII\Support\Binder\BudgetList;
use FireflyIII\Support\Binder\CategoryList;
use FireflyIII\Support\Binder\CLIToken;
use FireflyIII\Support\Binder\ConfigurationName;
use FireflyIII\Support\Binder\CurrencyCode;
use FireflyIII\Support\Binder\Date;
use FireflyIII\Support\Binder\ImportProvider;
use FireflyIII\Support\Binder\JournalList;
use FireflyIII\Support\Binder\SimpleJournalList;
use FireflyIII\Support\Binder\TagList;
use FireflyIII\Support\Binder\TagOrId;
use FireflyIII\Support\Binder\UnfinishedJournal;
use FireflyIII\TransactionRules\Actions\AddTag; use FireflyIII\TransactionRules\Actions\AddTag;
use FireflyIII\TransactionRules\Actions\AppendDescription; use FireflyIII\TransactionRules\Actions\AppendDescription;
use FireflyIII\TransactionRules\Actions\AppendNotes; use FireflyIII\TransactionRules\Actions\AppendNotes;
@ -82,6 +116,7 @@ use FireflyIII\TransactionRules\Triggers\ToAccountIs;
use FireflyIII\TransactionRules\Triggers\ToAccountStarts; use FireflyIII\TransactionRules\Triggers\ToAccountStarts;
use FireflyIII\TransactionRules\Triggers\TransactionType; use FireflyIII\TransactionRules\Triggers\TransactionType;
use FireflyIII\TransactionRules\Triggers\UserAction; use FireflyIII\TransactionRules\Triggers\UserAction;
use FireflyIII\User;
/* /*
* DO NOT EDIT THIS FILE. IT IS AUTO GENERATED. * DO NOT EDIT THIS FILE. IT IS AUTO GENERATED.
@ -320,54 +355,55 @@ return [
], ],
'bindables' => [ 'bindables' => [
// models // models
'account' => \FireflyIII\Models\Account::class, 'account' => Account::class,
'attachment' => \FireflyIII\Models\Attachment::class, 'attachment' => Attachment::class,
'availableBudget' => \FireflyIII\Models\AvailableBudget::class, 'availableBudget' => AvailableBudget::class,
'bill' => \FireflyIII\Models\Bill::class, 'bill' => Bill::class,
'budget' => \FireflyIII\Models\Budget::class, 'budget' => Budget::class,
'budgetLimit' => \FireflyIII\Models\BudgetLimit::class, 'budgetLimit' => BudgetLimit::class,
'category' => \FireflyIII\Models\Category::class, 'category' => Category::class,
'linkType' => \FireflyIII\Models\LinkType::class, 'linkType' => LinkType::class,
'transactionType' => \FireflyIII\Models\TransactionType::class, 'transactionType' => TransactionTypeModel::class,
'journalLink' => \FireflyIII\Models\TransactionJournalLink::class, 'journalLink' => TransactionJournalLink::class,
'currency' => \FireflyIII\Models\TransactionCurrency::class, 'currency' => TransactionCurrency::class,
'piggyBank' => \FireflyIII\Models\PiggyBank::class, 'piggyBank' => PiggyBank::class,
'preference' => \FireflyIII\Models\Preference::class, 'preference' => Preference::class,
'tj' => \FireflyIII\Models\TransactionJournal::class, 'tj' => TransactionJournal::class,
'tag' => \FireflyIII\Models\Tag::class, 'tag' => Tag::class,
'recurrence' => \FireflyIII\Models\Recurrence::class, 'recurrence' => Recurrence::class,
'rule' => \FireflyIII\Models\Rule::class, 'rule' => Rule::class,
'ruleGroup' => \FireflyIII\Models\RuleGroup::class, 'ruleGroup' => RuleGroup::class,
'exportJob' => \FireflyIII\Models\ExportJob::class, 'exportJob' => ExportJob::class,
'importJob' => \FireflyIII\Models\ImportJob::class, 'importJob' => ImportJob::class,
'transaction' => \FireflyIII\Models\Transaction::class, 'transaction' => Transaction::class,
'user' => \FireflyIII\User::class, 'transactionGroup' => TransactionGroup::class,
'user' => User::class,
// strings // strings
'import_provider' => \FireflyIII\Support\Binder\ImportProvider::class, 'import_provider' => ImportProvider::class,
'currency_code' => \FireflyIII\Support\Binder\CurrencyCode::class, 'currency_code' => CurrencyCode::class,
// dates // dates
'start_date' => \FireflyIII\Support\Binder\Date::class, 'start_date' => Date::class,
'end_date' => \FireflyIII\Support\Binder\Date::class, 'end_date' => Date::class,
'date' => \FireflyIII\Support\Binder\Date::class, 'date' => Date::class,
// lists // lists
'accountList' => \FireflyIII\Support\Binder\AccountList::class, 'accountList' => AccountList::class,
'expenseList' => \FireflyIII\Support\Binder\AccountList::class, 'expenseList' => AccountList::class,
'budgetList' => \FireflyIII\Support\Binder\BudgetList::class, 'budgetList' => BudgetList::class,
'journalList' => \FireflyIII\Support\Binder\JournalList::class, 'journalList' => JournalList::class,
'categoryList' => \FireflyIII\Support\Binder\CategoryList::class, 'categoryList' => CategoryList::class,
'tagList' => \FireflyIII\Support\Binder\TagList::class, 'tagList' => TagList::class,
'simpleJournalList' => \FireflyIII\Support\Binder\SimpleJournalList::class, 'simpleJournalList' => SimpleJournalList::class,
// others // others
'fromCurrencyCode' => \FireflyIII\Support\Binder\CurrencyCode::class, 'fromCurrencyCode' => CurrencyCode::class,
'toCurrencyCode' => \FireflyIII\Support\Binder\CurrencyCode::class, 'toCurrencyCode' => CurrencyCode::class,
'unfinishedJournal' => \FireflyIII\Support\Binder\UnfinishedJournal::class, 'unfinishedJournal' => UnfinishedJournal::class,
'cliToken' => \FireflyIII\Support\Binder\CLIToken::class, 'cliToken' => CLIToken::class,
'tagOrId' => \FireflyIII\Support\Binder\TagOrId::class, 'tagOrId' => TagOrId::class,
'configName' => \FireflyIII\Support\Binder\ConfigurationName::class, 'configName' => ConfigurationName::class,
], ],

View File

@ -182,4 +182,5 @@ return [
'transfer_source_bad_data' => 'Could not find a valid source account when searching for ID ":id" or name ":name".', '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_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".', 'transfer_dest_bad_data' => 'Could not find a valid destination account when searching for ID ":id" or name ":name".',
'need_id_in_edit' => 'When updating a transaction with splits, each split must have a valid transaction journal id (field "transaction_journal_id").',
]; ];

View File

@ -43,7 +43,7 @@ declare(strict_types=1);
Route::group( Route::group(
['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'about', 'as' => 'api.v1.about.'], ['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'about', 'as' => 'api.v1.about.'],
function () { static function () {
// Accounts API routes: // Accounts API routes:
Route::get('', ['uses' => 'AboutController@about', 'as' => 'index']); Route::get('', ['uses' => 'AboutController@about', 'as' => 'index']);
@ -54,7 +54,7 @@ Route::group(
Route::group( Route::group(
['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'accounts', 'as' => 'api.v1.accounts.'], ['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'accounts', 'as' => 'api.v1.accounts.'],
function () { static function () {
// Accounts API routes: // Accounts API routes:
Route::get('', ['uses' => 'AccountController@index', 'as' => 'index']); Route::get('', ['uses' => 'AccountController@index', 'as' => 'index']);
@ -71,7 +71,7 @@ Route::group(
Route::group( Route::group(
['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'attachments', 'as' => 'api.v1.attachments.'], ['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'attachments', 'as' => 'api.v1.attachments.'],
function () { static function () {
// Attachment API routes: // Attachment API routes:
Route::get('', ['uses' => 'AttachmentController@index', 'as' => 'index']); Route::get('', ['uses' => 'AttachmentController@index', 'as' => 'index']);
@ -87,7 +87,7 @@ Route::group(
Route::group( Route::group(
['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'available_budgets', ['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'available_budgets',
'as' => 'api.v1.available_budgets.'], 'as' => 'api.v1.available_budgets.'],
function () { static function () {
// Available Budget API routes: // Available Budget API routes:
Route::get('', ['uses' => 'AvailableBudgetController@index', 'as' => 'index']); Route::get('', ['uses' => 'AvailableBudgetController@index', 'as' => 'index']);
@ -99,7 +99,7 @@ Route::group(
); );
Route::group( Route::group(
['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'bills', 'as' => 'api.v1.bills.'], function () { ['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'bills', 'as' => 'api.v1.bills.'], static function () {
// Bills API routes: // Bills API routes:
Route::get('', ['uses' => 'BillController@index', 'as' => 'index']); Route::get('', ['uses' => 'BillController@index', 'as' => 'index']);
@ -117,7 +117,7 @@ Route::group(
Route::group( Route::group(
['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'budgets/limits', 'as' => 'api.v1.budget_limits.'], ['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'budgets/limits', 'as' => 'api.v1.budget_limits.'],
function () { static function () {
// Budget Limit API routes: // Budget Limit API routes:
Route::get('', ['uses' => 'BudgetLimitController@index', 'as' => 'index']); Route::get('', ['uses' => 'BudgetLimitController@index', 'as' => 'index']);
@ -131,7 +131,7 @@ Route::group(
Route::group( Route::group(
['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'budgets', 'as' => 'api.v1.budgets.'], ['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'budgets', 'as' => 'api.v1.budgets.'],
function () { static function () {
// Budget API routes: // Budget API routes:
Route::get('', ['uses' => 'BudgetController@index', 'as' => 'index']); Route::get('', ['uses' => 'BudgetController@index', 'as' => 'index']);
@ -147,7 +147,7 @@ Route::group(
Route::group( Route::group(
['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'categories', 'as' => 'api.v1.categories.'], ['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'categories', 'as' => 'api.v1.categories.'],
function () { static function () {
// Category API routes: // Category API routes:
Route::get('', ['uses' => 'CategoryController@index', 'as' => 'index']); Route::get('', ['uses' => 'CategoryController@index', 'as' => 'index']);
@ -167,7 +167,7 @@ Route::group(
Route::group( Route::group(
['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers\Chart', 'prefix' => 'chart/account', ['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers\Chart', 'prefix' => 'chart/account',
'as' => 'api.v1.chart.account.'], 'as' => 'api.v1.chart.account.'],
function () { static function () {
Route::get('overview', ['uses' => 'AccountController@overview', 'as' => 'overview']); Route::get('overview', ['uses' => 'AccountController@overview', 'as' => 'overview']);
Route::get('expense', ['uses' => 'AccountController@expenseOverview', 'as' => 'expense']); Route::get('expense', ['uses' => 'AccountController@expenseOverview', 'as' => 'expense']);
Route::get('revenue', ['uses' => 'AccountController@revenueOverview', 'as' => 'revenue']); Route::get('revenue', ['uses' => 'AccountController@revenueOverview', 'as' => 'revenue']);
@ -179,7 +179,7 @@ Route::group(
Route::group( Route::group(
['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers\Chart', 'prefix' => 'chart/ab', ['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers\Chart', 'prefix' => 'chart/ab',
'as' => 'api.v1.chart.ab.'], 'as' => 'api.v1.chart.ab.'],
function () { static function () {
// Overview API routes: // Overview API routes:
Route::get('overview/{availableBudget}', ['uses' => 'AvailableBudgetController@overview', 'as' => 'overview']); Route::get('overview/{availableBudget}', ['uses' => 'AvailableBudgetController@overview', 'as' => 'overview']);
@ -190,7 +190,7 @@ Route::group(
Route::group( Route::group(
['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers\Chart', 'prefix' => 'chart/category', ['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers\Chart', 'prefix' => 'chart/category',
'as' => 'api.v1.chart.category.'], 'as' => 'api.v1.chart.category.'],
function () { static function () {
// Overview API routes: // Overview API routes:
Route::get('overview', ['uses' => 'CategoryController@overview', 'as' => 'overview']); Route::get('overview', ['uses' => 'CategoryController@overview', 'as' => 'overview']);
@ -203,7 +203,7 @@ Route::group(
Route::group( Route::group(
['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'configuration', 'as' => 'api.v1.configuration.'], ['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'configuration', 'as' => 'api.v1.configuration.'],
function () { static function () {
// Configuration API routes: // Configuration API routes:
Route::get('', ['uses' => 'ConfigurationController@index', 'as' => 'index']); Route::get('', ['uses' => 'ConfigurationController@index', 'as' => 'index']);
@ -213,7 +213,7 @@ Route::group(
Route::group( Route::group(
['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'cer', 'as' => 'api.v1.cer.'], ['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'cer', 'as' => 'api.v1.cer.'],
function () { static function () {
// Currency Exchange Rate API routes: // Currency Exchange Rate API routes:
Route::get('', ['uses' => 'CurrencyExchangeRateController@index', 'as' => 'index']); Route::get('', ['uses' => 'CurrencyExchangeRateController@index', 'as' => 'index']);
@ -222,7 +222,7 @@ Route::group(
Route::group( Route::group(
['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'import', 'as' => 'api.v1.import.'], ['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'import', 'as' => 'api.v1.import.'],
function () { static function () {
// Transaction Links API routes: // Transaction Links API routes:
Route::get('list', ['uses' => 'ImportController@listAll', 'as' => 'list']); Route::get('list', ['uses' => 'ImportController@listAll', 'as' => 'list']);
@ -232,7 +232,7 @@ Route::group(
); );
Route::group( Route::group(
['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'link_types', 'as' => 'api.v1.link_types.'], ['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'link_types', 'as' => 'api.v1.link_types.'],
function () { static function () {
// Link Type API routes: // Link Type API routes:
Route::get('', ['uses' => 'LinkTypeController@index', 'as' => 'index']); Route::get('', ['uses' => 'LinkTypeController@index', 'as' => 'index']);
@ -247,7 +247,7 @@ Route::group(
Route::group( Route::group(
['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'transaction_links', ['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'transaction_links',
'as' => 'api.v1.transaction_links.'], 'as' => 'api.v1.transaction_links.'],
function () { static function () {
// Transaction Links API routes: // Transaction Links API routes:
Route::get('', ['uses' => 'TransactionLinkController@index', 'as' => 'index']); Route::get('', ['uses' => 'TransactionLinkController@index', 'as' => 'index']);
@ -261,7 +261,7 @@ Route::group(
Route::group( Route::group(
['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'piggy_banks', 'as' => 'api.v1.piggy_banks.'], ['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'piggy_banks', 'as' => 'api.v1.piggy_banks.'],
function () { static function () {
// Piggy Bank API routes: // Piggy Bank API routes:
Route::get('', ['uses' => 'PiggyBankController@index', 'as' => 'index']); Route::get('', ['uses' => 'PiggyBankController@index', 'as' => 'index']);
@ -275,7 +275,7 @@ Route::group(
Route::group( Route::group(
['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'preferences', 'as' => 'api.v1.preferences.'], ['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'preferences', 'as' => 'api.v1.preferences.'],
function () { static function () {
// Preference API routes: // Preference API routes:
Route::get('', ['uses' => 'PreferenceController@index', 'as' => 'index']); Route::get('', ['uses' => 'PreferenceController@index', 'as' => 'index']);
@ -286,7 +286,7 @@ Route::group(
Route::group( Route::group(
['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'recurrences', 'as' => 'api.v1.recurrences.'], ['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'recurrences', 'as' => 'api.v1.recurrences.'],
function () { static function () {
// Recurrence API routes: // Recurrence API routes:
Route::get('', ['uses' => 'RecurrenceController@index', 'as' => 'index']); Route::get('', ['uses' => 'RecurrenceController@index', 'as' => 'index']);
@ -301,7 +301,7 @@ Route::group(
Route::group( Route::group(
['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'rules', 'as' => 'api.v1.rules.'], ['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'rules', 'as' => 'api.v1.rules.'],
function () { static function () {
// Rules API routes: // Rules API routes:
Route::get('', ['uses' => 'RuleController@index', 'as' => 'index']); Route::get('', ['uses' => 'RuleController@index', 'as' => 'index']);
@ -316,7 +316,7 @@ Route::group(
Route::group( Route::group(
['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'rule_groups', 'as' => 'api.v1.rule_groups.'], ['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'rule_groups', 'as' => 'api.v1.rule_groups.'],
function () { static function () {
// Rules API routes: // Rules API routes:
Route::get('', ['uses' => 'RuleGroupController@index', 'as' => 'index']); Route::get('', ['uses' => 'RuleGroupController@index', 'as' => 'index']);
@ -333,7 +333,7 @@ Route::group(
Route::group( Route::group(
['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'summary', ['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'summary',
'as' => 'api.v1.summary.'], 'as' => 'api.v1.summary.'],
function () { static function () {
// Overview API routes: // Overview API routes:
Route::get('basic', ['uses' => 'SummaryController@basic', 'as' => 'basic']); Route::get('basic', ['uses' => 'SummaryController@basic', 'as' => 'basic']);
@ -343,7 +343,7 @@ Route::group(
Route::group( Route::group(
['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'currencies', 'as' => 'api.v1.currencies.'], ['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'currencies', 'as' => 'api.v1.currencies.'],
function () { static function () {
// Transaction currency API routes: // Transaction currency API routes:
Route::get('', ['uses' => 'CurrencyController@index', 'as' => 'index']); Route::get('', ['uses' => 'CurrencyController@index', 'as' => 'index']);
@ -369,7 +369,7 @@ Route::group(
Route::group( Route::group(
['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'tags', 'as' => 'api.v1.tags.'], ['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'tags', 'as' => 'api.v1.tags.'],
function () { static function () {
// Tag API routes: // Tag API routes:
Route::get('', ['uses' => 'TagController@index', 'as' => 'index']); Route::get('', ['uses' => 'TagController@index', 'as' => 'index']);
Route::post('', ['uses' => 'TagController@store', 'as' => 'store']); Route::post('', ['uses' => 'TagController@store', 'as' => 'store']);
@ -382,7 +382,7 @@ Route::group(
Route::group( Route::group(
['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'tag-cloud', 'as' => 'api.v1.tag-cloud.'], ['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'tag-cloud', 'as' => 'api.v1.tag-cloud.'],
function () { static function () {
// Tag cloud API routes (to prevent collisions) // Tag cloud API routes (to prevent collisions)
Route::get('', ['uses' => 'TagController@cloud', 'as' => 'cloud']); Route::get('', ['uses' => 'TagController@cloud', 'as' => 'cloud']);
} }
@ -391,7 +391,7 @@ Route::group(
Route::group( Route::group(
['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'transactions', 'as' => 'api.v1.transactions.'], ['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'transactions', 'as' => 'api.v1.transactions.'],
function () { static function () {
// Transaction API routes: // Transaction API routes:
Route::get('', ['uses' => 'TransactionController@index', 'as' => 'index']); Route::get('', ['uses' => 'TransactionController@index', 'as' => 'index']);
@ -409,7 +409,7 @@ Route::group(
Route::group( Route::group(
['middleware' => ['auth:api', 'bindings', \FireflyIII\Http\Middleware\IsAdmin::class], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'users', ['middleware' => ['auth:api', 'bindings', \FireflyIII\Http\Middleware\IsAdmin::class], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'users',
'as' => 'api.v1.users.'], 'as' => 'api.v1.users.'],
function () { static function () {
// Users API routes: // Users API routes:
Route::get('', ['uses' => 'UserController@index', 'as' => 'index']); Route::get('', ['uses' => 'UserController@index', 'as' => 'index']);