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

View File

@ -1,7 +1,7 @@
<?php
/**
* TransactionRequest.php
* TransactionStoreRequest.php
* Copyright (c) 2018 thegrumpydictator@gmail.com
*
* 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;
@ -82,6 +82,7 @@ class TransactionRequest extends Request
// transaction rules (in array for splits):
'transactions.*.type' => 'required|in:withdrawal,deposit,transfer,opening-balance,reconciliation',
'transactions.*.date' => ['required', new IsDateOrTime],
'transactions.*.order' => 'numeric|min:0',
// currency info
'transactions.*.currency_id' => 'numeric|exists:transaction_currencies,id',
@ -144,10 +145,6 @@ class TransactionRequest extends Request
'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;
@ -156,7 +153,7 @@ class TransactionRequest extends Request
/**
* Configure the validator instance.
*
* @param Validator $validator
* @param Validator $validator
*
* @return void
*/
@ -170,14 +167,13 @@ class TransactionRequest extends Request
// all journals must have a description
$this->validateDescriptions($validator);
// all transaction types must be equal:
$this->validateTransactionTypes($validator);
// all transaction types must be equal:
$this->validateTransactionTypes($validator);
// validate foreign currency info
$this->validateForeignCurrencyInformation($validator);
// validate all account info
$this->validateAccountInformation($validator);
@ -186,6 +182,8 @@ class TransactionRequest extends Request
// the group must have a description if > 1 journal.
$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) {
$object = new NullArrayObject($transaction);
$return[] = [
// $this->dateFromValue($object[''])
'type' => $this->stringFromValue($object['type']),
'date' => $this->dateFromValue($object['date']),
'type' => $this->stringFromValue($object['type']),
'date' => $this->dateFromValue($object['date']),
'order' => $this->integerFromValue((string)$object['order']),
'currency_id' => $this->integerFromValue($object['currency_id']),
'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-groups',
'firefly-iii:fix-account-types',
'firefly-iii:rename-meta-fields'
];
foreach ($commands as $command) {
$this->line(sprintf('Now executing %s', $command));

View File

@ -23,7 +23,7 @@ namespace FireflyIII\Console\Commands\Upgrade;
use DB;
use Exception;
use FireflyIII\Factory\TransactionJournalFactory;
use FireflyIII\Factory\TransactionGroupFactory;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
@ -54,13 +54,10 @@ class MigrateToGroups extends Command
* @var string
*/
protected $signature = 'firefly-iii:migrate-to-groups {--F|force : Force the migration, even if it fired before.}';
/** @var TransactionJournalFactory */
private $journalFactory;
/** @var TransactionGroupFactory */
private $groupFactory;
/** @var JournalRepositoryInterface */
private $journalRepository;
/** @var JournalDestroyService */
private $service;
@ -72,9 +69,9 @@ class MigrateToGroups extends Command
public function __construct()
{
parent::__construct();
$this->journalFactory = app(TransactionJournalFactory::class);
$this->journalRepository = app(JournalRepositoryInterface::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
{
$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;
}
);
@ -135,7 +132,7 @@ class MigrateToGroups extends Command
private function getDestinationTransactions(TransactionJournal $journal): Collection
{
return $journal->transactions->filter(
function (Transaction $transaction) {
static function (Transaction $transaction) {
return $transaction->amount > 0;
}
);
@ -224,13 +221,10 @@ class MigrateToGroups extends Command
Log::debug(sprintf('Will now try to convert journal #%d', $journal->id));
$this->journalRepository->setUser($journal->user);
$this->journalFactory->setUser($journal->user);
$this->groupFactory->setUser($journal->user);
$data = [
// mandatory fields.
'type' => strtolower($journal->transactionType->type),
'date' => $journal->date,
'user' => $journal->user_id,
'group_title' => $journal->description,
'transactions' => [],
];
@ -280,6 +274,9 @@ class MigrateToGroups extends Command
}
$tArray = [
'type' => strtolower($journal->transactionType->type),
'date' => $journal->date,
'user' => $journal->user_id,
'currency_id' => $transaction->transaction_currency_id,
'foreign_currency_id' => $transaction->foreign_currency_id,
'amount' => $transaction->amount,
@ -318,21 +315,18 @@ class MigrateToGroups extends Command
$data['transactions'][] = $tArray;
}
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');
// delete the old transaction journal.
$this->service->destroy($journal);
// first group ID
$first = $result->first() ? $result->first()->transaction_group_id : 0;
// report on result:
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(
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\Models\Account;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Services\Internal\Support\JournalServiceTrait;
use FireflyIII\Support\NullArrayObject;
use FireflyIII\User;
use FireflyIII\Validation\AccountValidator;
@ -44,8 +44,8 @@ use Log;
*/
class TransactionFactory
{
/** @var AccountRepositoryInterface */
private $accountRepository;
use JournalServiceTrait;
/** @var AccountValidator */
private $accountValidator;
/** @var TransactionJournal */
@ -113,12 +113,16 @@ class TransactionFactory
*/
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.
$this->validateAccounts($data);
// create or get source and destination accounts:
$sourceAccount = $this->getAccount('source', (int)$data['source_id'], $data['source_name']);
$destinationAccount = $this->getAccount('destination', (int)$data['destination_id'], $data['destination_name']);
$type = $this->journal->transactionType->type;
$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']);
$foreignAmount = $this->getForeignAmount($data['foreign_amount']);
@ -129,7 +133,7 @@ class TransactionFactory
$two->reconciled = $data['reconciled'] ?? false;
// add foreign currency info to $one and $two if necessary.
if (null !== $foreignCurrency) {
if (null !== $foreignCurrency && null !== $foreignAmount) {
$one->foreign_currency_id = $foreignCurrency->id;
$two->foreign_currency_id = $foreignCurrency->id;
$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
@ -298,6 +174,7 @@ class TransactionFactory
private function validateAccounts(NullArrayObject $data): void
{
$transactionType = $data['type'] ?? 'invalid';
$this->accountValidator->setUser($this->journal->user);
$this->accountValidator->setTransactionType($transactionType);
// validate source account.
@ -309,6 +186,7 @@ class TransactionFactory
if (false === $validSource) {
throw new FireflyException($this->accountValidator->sourceError);
}
Log::debug('Source seems valid.');
// validate destination account
$destinationId = isset($data['destination_id']) ? (int)$data['destination_id'] : null;
$destinationName = $data['destination_name'] ?? null;

View File

@ -27,7 +27,6 @@ namespace FireflyIII\Factory;
use Carbon\Carbon;
use Exception;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Note;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
@ -37,6 +36,7 @@ use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
use FireflyIII\Repositories\TransactionType\TransactionTypeRepositoryInterface;
use FireflyIII\Services\Internal\Support\JournalServiceTrait;
use FireflyIII\Support\NullArrayObject;
use FireflyIII\User;
use Illuminate\Support\Collection;
@ -47,12 +47,10 @@ use Log;
*/
class TransactionJournalFactory
{
use JournalServiceTrait;
/** @var BillRepositoryInterface */
private $billRepository;
/** @var BudgetRepositoryInterface */
private $budgetRepository;
/** @var CategoryRepositoryInterface */
private $categoryRepository;
/** @var CurrencyRepositoryInterface */
private $currencyRepository;
/** @var array */
@ -61,8 +59,6 @@ class TransactionJournalFactory
private $piggyEventFactory;
/** @var PiggyBankRepositoryInterface */
private $piggyRepository;
/** @var TagFactory */
private $tagFactory;
/** @var TransactionFactory */
private $transactionFactory;
/** @var TransactionTypeRepositoryInterface */
@ -88,7 +84,7 @@ class TransactionJournalFactory
'due_date', 'payment_date', 'invoice_date',
// others
'recurrence_id', 'internal_reference', 'bunq_payment_id',
'recurrence_id', 'internal_reference', 'bunq_payment_id',
'import_hash', 'import_hash_v2', 'external_id', 'original_source'];
@ -151,6 +147,7 @@ class TransactionJournalFactory
{
$this->user = $user;
$this->currencyRepository->setUser($this->user);
$this->tagFactory->setUser($user);
$this->transactionFactory->setUser($this->user);
$this->billRepository->setUser($this->user);
$this->budgetRepository->setUser($this->user);
@ -184,31 +181,6 @@ class TransactionJournalFactory
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 NullArrayObject $data
@ -229,27 +201,6 @@ class TransactionJournalFactory
$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
*
@ -263,8 +214,9 @@ class TransactionJournalFactory
/** Get basic fields */
$type = $this->typeRepository->findTransactionType(null, $row['type']);
$carbon = $row['date'] ?? new Carbon;
$order = $row['order'] ?? 0;
$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']);
$billId = TransactionType::WITHDRAWAL === $type->type && null !== $bill ? $bill->id : null;
$description = app('steam')->cleanString((string)$row['description']);
@ -281,7 +233,7 @@ class TransactionJournalFactory
'transaction_currency_id' => $currency->id,
'description' => '' === $description ? '(empty description)' : $description,
'date' => $carbon->format('Y-m-d H:i:s'),
'order' => 0,
'order' => $order,
'tag_count' => 0,
'completed' => 0,
]
@ -318,7 +270,7 @@ class TransactionJournalFactory
$this->storeCategory($journal, $row);
/** Set notes */
$this->storeNote($journal, $row['notes']);
$this->storeNotes($journal, $row['notes']);
/** Set piggy bank */
$this->storePiggyEvent($journal, $row);
@ -332,22 +284,6 @@ class TransactionJournalFactory
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
*
@ -355,7 +291,7 @@ class TransactionJournalFactory
*/
private function hashArray(NullArrayObject $row): string
{
$row['import_hash_v2'] = null;
$row['import_hash_v2'] = null;
$row['original_source'] = null;
$json = json_encode($row);
if (false === $json) {
@ -367,36 +303,6 @@ class TransactionJournalFactory
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 NullArrayObject $transaction

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -97,8 +97,18 @@ class JournalServiceProvider extends ServiceProvider
private function registerGroupRepository()
{
// password verifier thing
$this->app->bind(TransactionGroupRepositoryInterface::class, TransactionGroupRepository::class);
$this->app->bind(
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 string|null $budgetName
*
* @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()');
$result = null;
if (null !== $budget) {
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);
}
Log::debug(sprintf('Searching for budget with ID #%d...', $budgetId));
$result = $this->findNull((int)$budgetId);
if (null === $result) {
Log::debug(sprintf('Searching for budget with name %s...', $budgetName));
$result = $this->findByName((string)$budgetName);

View File

@ -82,13 +82,12 @@ interface BudgetRepositoryInterface
public function destroyBudgetLimit(BudgetLimit $budgetLimit): void;
/**
* @param Budget|null $budget
* @param int|null $budgetId
* @param string|null $budgetName
*
* @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.

View File

@ -253,25 +253,16 @@ class CategoryRepository implements CategoryRepositoryInterface
}
/**
* @param Category|null $category
* @param int|null $categoryId
* @param string|null $categoryName
* @param int|null $categoryId
* @param string|null $categoryName
*
* @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()');
$result = null;
if (null !== $category) {
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);
}
Log::debug(sprintf('Searching for category with ID #%d...', $categoryId));
$result = $this->findNull((int)$categoryId);
if (null === $result) {
Log::debug(sprintf('Searching for category with name %s...', $categoryName));
$result = $this->findByName((string)$categoryName);

View File

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

View File

@ -265,16 +265,13 @@ class CurrencyRepository implements CurrencyRepositoryInterface
*/
public function findCurrency(?int $currencyId, ?string $currencyCode): TransactionCurrency
{
Log::debug('Now in findCurrency()');
$result = $this->find((int)$currencyId);
if (null === $result) {
Log::debug(sprintf('Searching for currency with code %s...', $currencyCode));
$result = $this->findByCode((string)$currencyCode);
}
$result = $this->findCurrencyNull($currencyId, $currencyCode);
if (null === $result) {
Log::debug('Grabbing default currency for this user...');
$result = app('amount')->getDefaultCurrencyByUser($this->user);
}
if (null === $result) {
Log::debug('Grabbing EUR as fallback.');
$result = $this->findByCode('EUR');
@ -288,6 +285,30 @@ class CurrencyRepository implements CurrencyRepositoryInterface
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.
* Used in Import Currency!

View File

@ -135,13 +135,23 @@ interface CurrencyRepositoryInterface
/**
* Find by object, ID or code. Returns user default or system default.
*
* @param int|null $currencyId
* @param string|null $currencyCode
* @param int|null $currencyId
* @param string|null $currencyCode
*
* @return TransactionCurrency|null
*/
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.
*

View File

@ -774,40 +774,6 @@ class JournalRepository implements JournalRepositoryInterface
$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.
*

View File

@ -319,21 +319,7 @@ interface JournalRepositoryInterface
*/
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.

View File

@ -27,8 +27,12 @@ namespace FireflyIII\Repositories\TransactionGroup;
use Carbon\Carbon;
use DB;
use Exception;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Factory\TransactionGroupFactory;
use FireflyIII\Models\Note;
use FireflyIII\Models\TransactionGroup;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Services\Internal\Update\GroupUpdateService;
use FireflyIII\Support\NullArrayObject;
/**
@ -36,6 +40,8 @@ use FireflyIII\Support\NullArrayObject;
*/
class TransactionGroupRepository implements TransactionGroupRepositoryInterface
{
private $user;
/**
* Constructor.
*/
@ -61,6 +67,7 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface
::table('journal_meta')
->where('transaction_journal_id', $journalId)
->whereIn('name', $fields)
->whereNull('deleted_at')
->get(['name', 'data']);
$return = [];
@ -85,6 +92,7 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface
::table('journal_meta')
->where('transaction_journal_id', $journalId)
->whereIn('name', $fields)
->whereNull('deleted_at')
->get(['name', 'data']);
$return = [];
@ -133,4 +141,45 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface
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;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\TransactionGroup;
use FireflyIII\Support\NullArrayObject;
use FireflyIII\User;
/**
* Interface TransactionGroupRepositoryInterface
@ -67,4 +70,32 @@ interface TransactionGroupRepositoryInterface
* @return 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 Log;
/**
* Class TransactionTypeRepository
*/
@ -57,7 +58,8 @@ class TransactionTypeRepository implements TransactionTypeRepositoryInterface
return $type;
}
$search = $this->findByType($typeString);
$typeString = $typeString ?? TransactionType::WITHDRAWAL;
$search = $this->findByType($typeString);
if (null === $search) {
$search = $this->findByType(TransactionType::WITHDRAWAL);
}

View File

@ -24,16 +24,17 @@ declare(strict_types=1);
namespace FireflyIII\Services\Internal\Support;
use Exception;
use FireflyIII\Factory\BillFactory;
use FireflyIII\Factory\BudgetFactory;
use FireflyIII\Factory\CategoryFactory;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Factory\TagFactory;
use FireflyIII\Factory\TransactionJournalMetaFactory;
use FireflyIII\Models\Bill;
use FireflyIII\Models\Budget;
use FireflyIII\Models\Category;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\Note;
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;
/**
@ -42,26 +43,229 @@ use Log;
*/
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.
*
* @param TransactionJournal $journal
* @param array $data
* @param array $tags
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
*/
public function connectTags(TransactionJournal $journal, array $data): void
protected function storeTags(TransactionJournal $journal, ?array $tags): void
{
/** @var TagFactory $factory */
$factory = app(TagFactory::class);
$factory->setUser($journal->user);
$this->tagFactory->setUser($journal->user);
$set = [];
if (!\is_array($data['tags'])) {
return; // @codeCoverageIgnore
if (!is_array($tags)) {
return;
}
foreach ($data['tags'] as $string) {
foreach ($tags as $string) {
if ('' !== $string) {
$tag = $factory->findOrCreate($string);
$tag = $this->tagFactory->findOrCreate($string);
if (null !== $tag) {
$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);
}
/**
* @param int|null $categoryId
* @param null|string $categoryName
*
* @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));
/** @var CategoryFactory $factory */
$factory = app(CategoryFactory::class);
$factory->setUser($this->user);
return $factory->findOrCreate($categoryId, $categoryName);
}
/**
* @param TransactionJournal $journal
* @param Budget|null $budget
*/
protected function setBudget(TransactionJournal $journal, ?Budget $budget): void
{
if (null === $budget) {
$journal->budgets()->sync([]);
return;
}
$journal->budgets()->sync([$budget->id]);
}
/**
* @param TransactionJournal $journal
* @param Category|null $category
*/
protected function setCategory(TransactionJournal $journal, ?Category $category): void
{
if (null === $category) {
$journal->categories()->sync([]);
return;
}
$journal->categories()->sync([$category->id]);
}
/**
* @param TransactionJournal $journal
* @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()));
}
}
}
//
// /**
// * Link tags to journal.
// *
// * @param TransactionJournal $journal
// * @param array $data
// * @SuppressWarnings(PHPMD.CyclomaticComplexity)
// */
// public function connectTags(TransactionJournal $journal, array $data): void
// {
// /** @var TagFactory $factory */
// $factory = app(TagFactory::class);
// $factory->setUser($journal->user);
// $set = [];
// if (!\is_array($data['tags'])) {
// return; // @codeCoverageIgnore
// }
// foreach ($data['tags'] as $string) {
// if ('' !== $string) {
// $tag = $factory->findOrCreate($string);
// if (null !== $tag) {
// $set[] = $tag->id;
// }
// }
// }
// $journal->tags()->sync($set);
// }
//
//
// /**
// * @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);
// }
//
// /**
// * @param int|null $categoryId
// * @param null|string $categoryName
// *
// * @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));
// /** @var CategoryFactory $factory */
// $factory = app(CategoryFactory::class);
// $factory->setUser($this->user);
//
// return $factory->findOrCreate($categoryId, $categoryName);
// }
//
//
// /**
// * @param TransactionJournal $journal
// * @param Budget|null $budget
// */
// protected function setBudget(TransactionJournal $journal, ?Budget $budget): void
// {
// if (null === $budget) {
// $journal->budgets()->sync([]);
//
// return;
// }
// $journal->budgets()->sync([$budget->id]);
//
// }
//
//
// /**
// * @param TransactionJournal $journal
// * @param Category|null $category
// */
// protected function setCategory(TransactionJournal $journal, ?Category $category): void
// {
// if (null === $category) {
// $journal->categories()->sync([]);
//
// return;
// }
// $journal->categories()->sync([$category->id]);
//
// }
//
//
// /**
// * @param TransactionJournal $journal
// * @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;
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\TransactionGroup;
use FireflyIII\Models\TransactionJournal;
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 Illuminate\Support\Collection;
use FireflyIII\Support\NullArrayObject;
use FireflyIII\Validation\AccountValidator;
use Log;
/**
@ -40,163 +53,618 @@ class JournalUpdateService
{
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()
{
if ('testing' === config('app.env')) {
Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this)));
$this->billRepository = app(BillRepositoryInterface::class);
$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:
$journal->description = $data['description'];
$journal->date = $data['date'];
$journal->save();
$amount = $this->data['foreign_amount'] ?? null;
$foreignAmount = $this->getForeignAmount($amount);
$source = $this->getSourceTransaction();
$dest = $this->getDestinationTransaction();
$foreignCurrency = $source->foreignCurrency;
// update transactions:
/** @var TransactionUpdateService $service */
$service = app(TransactionUpdateService::class);
$service->setUser($journal->user);
// find currency in data array
$newForeignId = $this->data['foreign_currency_id'] ?? null;
$newForeignCode = $this->data['foreign_currency_code'] ?? null;
$foreignCurrency = $this->currencyRepository->findCurrencyNull($newForeignId, $newForeignCode) ?? $foreignCurrency;
// create transactions:
/** @var TransactionFactory $factory */
$factory = app(TransactionFactory::class);
$factory->setUser($journal->user);
// not the same as normal currency
if (null !== $foreignCurrency && $foreignCurrency->id === $this->transactionJournal->transaction_currency_id) {
Log::error(sprintf('Foreign currency is equal to normal currency (%s)', $foreignCurrency->code));
Log::debug(sprintf('Found %d rows in array (should result in %d transactions', \count($data['transactions']), \count($data['transactions']) * 2));
/**
* @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);
return;
}
// could be that journal has more transactions than submitted (remove split)
$transactions = $journal->transactions()->where('amount', '>', 0)->get();
Log::debug(sprintf('Journal #%d has %d transactions', $journal->id, $transactions->count()));
/** @var Transaction $transaction */
foreach ($transactions as $transaction) {
Log::debug(sprintf('Now at transaction %d with identifier %d', $transaction->id, $transaction->identifier));
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();
}
// add foreign currency info to source and destination if possible.
if (null !== $foreignCurrency && null !== $foreignAmount) {
$source->foreign_currency_id = $foreignCurrency->id;
$source->foreign_amount = app('steam')->negative($foreignAmount);
$source->save();
$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:
$this->connectBill($journal, $data);
// connect tags:
$this->connectTags($journal, $data);
// 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;
$dest->foreign_currency_id = null;
$dest->foreign_amount = null;
$dest->save();
Log::debug(sprintf('Foreign amount is "%s" so remove foreign amount info.', $amount));
}
Log::info('Not enough info to update foreign currency info.');
}
/**
* 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 */
$service = app(TransactionUpdateService::class);
$service->setUser($journal->user);
if (TransactionType::WITHDRAWAL === $journal->transactionType->type) {
/** @var Transaction $transaction */
foreach ($journal->transactions as $transaction) {
$service->updateBudget($transaction, $budgetId);
/** @var TransactionJournalMetaFactory $factory */
$factory = app(TransactionJournalMetaFactory::class);
foreach ($this->metaDate as $field) {
if ($this->hasFields([$field])) {
try {
$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 */
$service = app(TransactionUpdateService::class);
$service->setUser($journal->user);
/** @var TransactionJournalMetaFactory $factory */
$factory = app(TransactionJournalMetaFactory::class);
/** @var Transaction $transaction */
foreach ($journal->transactions as $transaction) {
$service->updateCategory($transaction, $category);
foreach ($this->metaString as $field) {
if ($this->hasFields([$field])) {
$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;
}
$modifiers = ['rabo-debit-credit', 'ing-debit-credit'];
if (\in_array($role, $modifiers, true)) {
$modifiers = ['generic-debit-credit'];
if (in_array($role, $modifiers, true)) {
$this->modifiers[$role] = $columnValue->getValue();
return;

View File

@ -119,6 +119,7 @@ class TransactionGroupTransformer extends AbstractTransformer
'transaction_journal_id' => $row['transaction_journal_id'],
'type' => strtolower($type),
'date' => $row['date']->toAtomString(),
'order' => $row['order'],
'currency_id' => $row['currency_id'],
'currency_code' => $row['currency_code'],
@ -163,7 +164,7 @@ class TransactionGroupTransformer extends AbstractTransformer
'original_source' => $metaFieldData['original_source'],
'recurrence_id' => $metaFieldData['recurrence_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_ct_op' => $metaFieldData['sepa_ct_op'],

View File

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

View File

@ -24,6 +24,7 @@ declare(strict_types=1);
namespace FireflyIII\Validation;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionGroup;
use Illuminate\Validation\Validator;
/**
@ -31,6 +32,16 @@ use Illuminate\Validation\Validator;
*/
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.
@ -98,7 +109,9 @@ trait TransactionValidation
// no valid descriptions?
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'] ?? [];
// need at least one transaction
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.
// *

View File

@ -24,10 +24,44 @@
declare(strict_types=1);
use FireflyIII\Export\Exporter\CsvExporter;
use FireflyIII\Models\Account;
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\Services\Currency\FixerIOv2;
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\AppendDescription;
use FireflyIII\TransactionRules\Actions\AppendNotes;
@ -82,6 +116,7 @@ use FireflyIII\TransactionRules\Triggers\ToAccountIs;
use FireflyIII\TransactionRules\Triggers\ToAccountStarts;
use FireflyIII\TransactionRules\Triggers\TransactionType;
use FireflyIII\TransactionRules\Triggers\UserAction;
use FireflyIII\User;
/*
* DO NOT EDIT THIS FILE. IT IS AUTO GENERATED.
@ -320,54 +355,55 @@ return [
],
'bindables' => [
// models
'account' => \FireflyIII\Models\Account::class,
'attachment' => \FireflyIII\Models\Attachment::class,
'availableBudget' => \FireflyIII\Models\AvailableBudget::class,
'bill' => \FireflyIII\Models\Bill::class,
'budget' => \FireflyIII\Models\Budget::class,
'budgetLimit' => \FireflyIII\Models\BudgetLimit::class,
'category' => \FireflyIII\Models\Category::class,
'linkType' => \FireflyIII\Models\LinkType::class,
'transactionType' => \FireflyIII\Models\TransactionType::class,
'journalLink' => \FireflyIII\Models\TransactionJournalLink::class,
'currency' => \FireflyIII\Models\TransactionCurrency::class,
'piggyBank' => \FireflyIII\Models\PiggyBank::class,
'preference' => \FireflyIII\Models\Preference::class,
'tj' => \FireflyIII\Models\TransactionJournal::class,
'tag' => \FireflyIII\Models\Tag::class,
'recurrence' => \FireflyIII\Models\Recurrence::class,
'rule' => \FireflyIII\Models\Rule::class,
'ruleGroup' => \FireflyIII\Models\RuleGroup::class,
'exportJob' => \FireflyIII\Models\ExportJob::class,
'importJob' => \FireflyIII\Models\ImportJob::class,
'transaction' => \FireflyIII\Models\Transaction::class,
'user' => \FireflyIII\User::class,
'account' => Account::class,
'attachment' => Attachment::class,
'availableBudget' => AvailableBudget::class,
'bill' => Bill::class,
'budget' => Budget::class,
'budgetLimit' => BudgetLimit::class,
'category' => Category::class,
'linkType' => LinkType::class,
'transactionType' => TransactionTypeModel::class,
'journalLink' => TransactionJournalLink::class,
'currency' => TransactionCurrency::class,
'piggyBank' => PiggyBank::class,
'preference' => Preference::class,
'tj' => TransactionJournal::class,
'tag' => Tag::class,
'recurrence' => Recurrence::class,
'rule' => Rule::class,
'ruleGroup' => RuleGroup::class,
'exportJob' => ExportJob::class,
'importJob' => ImportJob::class,
'transaction' => Transaction::class,
'transactionGroup' => TransactionGroup::class,
'user' => User::class,
// strings
'import_provider' => \FireflyIII\Support\Binder\ImportProvider::class,
'currency_code' => \FireflyIII\Support\Binder\CurrencyCode::class,
'import_provider' => ImportProvider::class,
'currency_code' => CurrencyCode::class,
// dates
'start_date' => \FireflyIII\Support\Binder\Date::class,
'end_date' => \FireflyIII\Support\Binder\Date::class,
'date' => \FireflyIII\Support\Binder\Date::class,
'start_date' => Date::class,
'end_date' => Date::class,
'date' => Date::class,
// lists
'accountList' => \FireflyIII\Support\Binder\AccountList::class,
'expenseList' => \FireflyIII\Support\Binder\AccountList::class,
'budgetList' => \FireflyIII\Support\Binder\BudgetList::class,
'journalList' => \FireflyIII\Support\Binder\JournalList::class,
'categoryList' => \FireflyIII\Support\Binder\CategoryList::class,
'tagList' => \FireflyIII\Support\Binder\TagList::class,
'simpleJournalList' => \FireflyIII\Support\Binder\SimpleJournalList::class,
'accountList' => AccountList::class,
'expenseList' => AccountList::class,
'budgetList' => BudgetList::class,
'journalList' => JournalList::class,
'categoryList' => CategoryList::class,
'tagList' => TagList::class,
'simpleJournalList' => SimpleJournalList::class,
// others
'fromCurrencyCode' => \FireflyIII\Support\Binder\CurrencyCode::class,
'toCurrencyCode' => \FireflyIII\Support\Binder\CurrencyCode::class,
'unfinishedJournal' => \FireflyIII\Support\Binder\UnfinishedJournal::class,
'cliToken' => \FireflyIII\Support\Binder\CLIToken::class,
'tagOrId' => \FireflyIII\Support\Binder\TagOrId::class,
'configName' => \FireflyIII\Support\Binder\ConfigurationName::class,
'fromCurrencyCode' => CurrencyCode::class,
'toCurrencyCode' => CurrencyCode::class,
'unfinishedJournal' => UnfinishedJournal::class,
'cliToken' => CLIToken::class,
'tagOrId' => TagOrId::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_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".',
'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(
['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'about', 'as' => 'api.v1.about.'],
function () {
static function () {
// Accounts API routes:
Route::get('', ['uses' => 'AboutController@about', 'as' => 'index']);
@ -54,7 +54,7 @@ Route::group(
Route::group(
['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'accounts', 'as' => 'api.v1.accounts.'],
function () {
static function () {
// Accounts API routes:
Route::get('', ['uses' => 'AccountController@index', 'as' => 'index']);
@ -71,7 +71,7 @@ Route::group(
Route::group(
['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'attachments', 'as' => 'api.v1.attachments.'],
function () {
static function () {
// Attachment API routes:
Route::get('', ['uses' => 'AttachmentController@index', 'as' => 'index']);
@ -87,7 +87,7 @@ Route::group(
Route::group(
['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'available_budgets',
'as' => 'api.v1.available_budgets.'],
function () {
static function () {
// Available Budget API routes:
Route::get('', ['uses' => 'AvailableBudgetController@index', 'as' => 'index']);
@ -99,7 +99,7 @@ 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:
Route::get('', ['uses' => 'BillController@index', 'as' => 'index']);
@ -117,7 +117,7 @@ Route::group(
Route::group(
['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'budgets/limits', 'as' => 'api.v1.budget_limits.'],
function () {
static function () {
// Budget Limit API routes:
Route::get('', ['uses' => 'BudgetLimitController@index', 'as' => 'index']);
@ -131,7 +131,7 @@ Route::group(
Route::group(
['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'budgets', 'as' => 'api.v1.budgets.'],
function () {
static function () {
// Budget API routes:
Route::get('', ['uses' => 'BudgetController@index', 'as' => 'index']);
@ -147,7 +147,7 @@ Route::group(
Route::group(
['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'categories', 'as' => 'api.v1.categories.'],
function () {
static function () {
// Category API routes:
Route::get('', ['uses' => 'CategoryController@index', 'as' => 'index']);
@ -167,7 +167,7 @@ Route::group(
Route::group(
['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers\Chart', 'prefix' => 'chart/account',
'as' => 'api.v1.chart.account.'],
function () {
static function () {
Route::get('overview', ['uses' => 'AccountController@overview', 'as' => 'overview']);
Route::get('expense', ['uses' => 'AccountController@expenseOverview', 'as' => 'expense']);
Route::get('revenue', ['uses' => 'AccountController@revenueOverview', 'as' => 'revenue']);
@ -179,7 +179,7 @@ Route::group(
Route::group(
['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers\Chart', 'prefix' => 'chart/ab',
'as' => 'api.v1.chart.ab.'],
function () {
static function () {
// Overview API routes:
Route::get('overview/{availableBudget}', ['uses' => 'AvailableBudgetController@overview', 'as' => 'overview']);
@ -190,7 +190,7 @@ Route::group(
Route::group(
['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers\Chart', 'prefix' => 'chart/category',
'as' => 'api.v1.chart.category.'],
function () {
static function () {
// Overview API routes:
Route::get('overview', ['uses' => 'CategoryController@overview', 'as' => 'overview']);
@ -203,7 +203,7 @@ Route::group(
Route::group(
['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'configuration', 'as' => 'api.v1.configuration.'],
function () {
static function () {
// Configuration API routes:
Route::get('', ['uses' => 'ConfigurationController@index', 'as' => 'index']);
@ -213,7 +213,7 @@ Route::group(
Route::group(
['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'cer', 'as' => 'api.v1.cer.'],
function () {
static function () {
// Currency Exchange Rate API routes:
Route::get('', ['uses' => 'CurrencyExchangeRateController@index', 'as' => 'index']);
@ -222,7 +222,7 @@ Route::group(
Route::group(
['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'import', 'as' => 'api.v1.import.'],
function () {
static function () {
// Transaction Links API routes:
Route::get('list', ['uses' => 'ImportController@listAll', 'as' => 'list']);
@ -232,7 +232,7 @@ Route::group(
);
Route::group(
['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'link_types', 'as' => 'api.v1.link_types.'],
function () {
static function () {
// Link Type API routes:
Route::get('', ['uses' => 'LinkTypeController@index', 'as' => 'index']);
@ -247,7 +247,7 @@ Route::group(
Route::group(
['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'transaction_links',
'as' => 'api.v1.transaction_links.'],
function () {
static function () {
// Transaction Links API routes:
Route::get('', ['uses' => 'TransactionLinkController@index', 'as' => 'index']);
@ -261,7 +261,7 @@ Route::group(
Route::group(
['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'piggy_banks', 'as' => 'api.v1.piggy_banks.'],
function () {
static function () {
// Piggy Bank API routes:
Route::get('', ['uses' => 'PiggyBankController@index', 'as' => 'index']);
@ -275,7 +275,7 @@ Route::group(
Route::group(
['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'preferences', 'as' => 'api.v1.preferences.'],
function () {
static function () {
// Preference API routes:
Route::get('', ['uses' => 'PreferenceController@index', 'as' => 'index']);
@ -286,7 +286,7 @@ Route::group(
Route::group(
['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'recurrences', 'as' => 'api.v1.recurrences.'],
function () {
static function () {
// Recurrence API routes:
Route::get('', ['uses' => 'RecurrenceController@index', 'as' => 'index']);
@ -301,7 +301,7 @@ Route::group(
Route::group(
['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'rules', 'as' => 'api.v1.rules.'],
function () {
static function () {
// Rules API routes:
Route::get('', ['uses' => 'RuleController@index', 'as' => 'index']);
@ -316,7 +316,7 @@ Route::group(
Route::group(
['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'rule_groups', 'as' => 'api.v1.rule_groups.'],
function () {
static function () {
// Rules API routes:
Route::get('', ['uses' => 'RuleGroupController@index', 'as' => 'index']);
@ -333,7 +333,7 @@ Route::group(
Route::group(
['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'summary',
'as' => 'api.v1.summary.'],
function () {
static function () {
// Overview API routes:
Route::get('basic', ['uses' => 'SummaryController@basic', 'as' => 'basic']);
@ -343,7 +343,7 @@ Route::group(
Route::group(
['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'currencies', 'as' => 'api.v1.currencies.'],
function () {
static function () {
// Transaction currency API routes:
Route::get('', ['uses' => 'CurrencyController@index', 'as' => 'index']);
@ -369,7 +369,7 @@ Route::group(
Route::group(
['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'tags', 'as' => 'api.v1.tags.'],
function () {
static function () {
// Tag API routes:
Route::get('', ['uses' => 'TagController@index', 'as' => 'index']);
Route::post('', ['uses' => 'TagController@store', 'as' => 'store']);
@ -382,7 +382,7 @@ Route::group(
Route::group(
['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)
Route::get('', ['uses' => 'TagController@cloud', 'as' => 'cloud']);
}
@ -391,7 +391,7 @@ Route::group(
Route::group(
['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'transactions', 'as' => 'api.v1.transactions.'],
function () {
static function () {
// Transaction API routes:
Route::get('', ['uses' => 'TransactionController@index', 'as' => 'index']);
@ -409,7 +409,7 @@ Route::group(
Route::group(
['middleware' => ['auth:api', 'bindings', \FireflyIII\Http\Middleware\IsAdmin::class], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'users',
'as' => 'api.v1.users.'],
function () {
static function () {
// Users API routes:
Route::get('', ['uses' => 'UserController@index', 'as' => 'index']);