Merge branch 'release/5.6.0'

This commit is contained in:
James Cole 2021-09-17 15:19:52 +02:00
commit 1d02d86f9e
203 changed files with 4304 additions and 2747 deletions

2
.gitignore vendored
View File

@ -15,4 +15,4 @@ yarn-error.log
public/google*.html public/google*.html
report.html report.html
composer.phar composer.phar
app.js.map app.js.map

View File

@ -0,0 +1,155 @@
<?php
/*
* CreateGroupMemberships.php
* Copyright (c) 2021 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Console\Commands\Upgrade;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\GroupMembership;
use FireflyIII\Models\UserGroup;
use FireflyIII\Models\UserRole;
use FireflyIII\User;
use Illuminate\Console\Command;
use Log;
/**
* Class CreateGroupMemberships
*/
class CreateGroupMemberships extends Command
{
public const CONFIG_NAME = '560_create_group_memberships';
/**
* The console command description.
*
* @var string
*/
protected $description = 'SOME DESCRIPTION';
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'firefly-iii:create-group-memberships {--F|force : Force the execution of this command.}';
/**
* Execute the console command.
*
* @return int
* @throws FireflyException
*/
public function handle(): int
{
$start = microtime(true);
if ($this->isExecuted() && true !== $this->option('force')) {
$this->warn('This command has already been executed.');
return 0;
}
$this->createGroupMemberships();
$this->markAsExecuted();
$end = round(microtime(true) - $start, 2);
$this->info(sprintf('in %s seconds.', $end));
return 0;
}
/**
* @return bool
*/
private function isExecuted(): bool
{
$configVar = app('fireflyconfig')->get(self::CONFIG_NAME, false);
if (null !== $configVar) {
return (bool)$configVar->data;
}
return false;
}
/**
*
* @throws FireflyException
*/
private function createGroupMemberships(): void
{
$users = User::get();
/** @var User $user */
foreach ($users as $user) {
Log::debug(sprintf('Manage group memberships for user #%d', $user->id));
if (!$this->hasGroupMembership($user)) {
Log::debug(sprintf('User #%d has no main group.', $user->id));
$this->createGroupMembership($user);
}
Log::debug(sprintf('Done with user #%d', $user->id));
}
}
/**
* @param User $user
*
* @return bool
*/
private function hasGroupMembership(User $user): bool
{
return $user->groupMemberships()->count() > 0;
}
/**
* @param User $user
*
* @throws FireflyException
*/
private function createGroupMembership(User $user): void
{
$userGroup = UserGroup::create(['title' => $user->email]);
$userRole = UserRole::where('title', UserRole::OWNER)->first();
if (null === $userRole) {
throw new FireflyException('Firefly III could not find a user role. Please make sure all validations have run.');
}
$membership = GroupMembership::create(
[
'user_id' => $user->id,
'user_role_id' => $userRole->id,
'user_group_id' => $userGroup->id,
]
);
if (null === $membership) {
throw new FireflyException('Firefly III could not create user group management object. Please make sure all validations have run.');
}
$user->user_group_id = $userGroup->id;
$user->save();
Log::debug(sprintf('User #%d now has main group.', $user->id));
}
/**
*
*/
private function markAsExecuted(): void
{
app('fireflyconfig')->set(self::CONFIG_NAME, true);
}
}

View File

@ -75,6 +75,7 @@ class UpgradeDatabase extends Command
'firefly-iii:migrate-tag-locations', 'firefly-iii:migrate-tag-locations',
'firefly-iii:migrate-recurrence-type', 'firefly-iii:migrate-recurrence-type',
'firefly-iii:upgrade-liabilities', 'firefly-iii:upgrade-liabilities',
'firefly-iii:create-group-memberships',
// there are 16 verify commands. // there are 16 verify commands.
'firefly-iii:fix-piggies', 'firefly-iii:fix-piggies',

View File

@ -63,7 +63,7 @@ class APIEventHandler
// see if user has alternative email address: // see if user has alternative email address:
$pref = app('preferences')->getForUser($user, 'remote_guard_alt_email'); $pref = app('preferences')->getForUser($user, 'remote_guard_alt_email');
if (null !== $pref) { if (null !== $pref) {
$email = $pref->data; $email = (string)(is_array($pref->data) ? $email : $pref->data);
} }
Log::debug(sprintf('Now in APIEventHandler::accessTokenCreated. Email is %s, IP is %s', $email, $ipAddress)); Log::debug(sprintf('Now in APIEventHandler::accessTokenCreated. Email is %s, IP is %s', $email, $ipAddress));

View File

@ -35,6 +35,9 @@ use FireflyIII\Mail\NewIPAddressWarningMail;
use FireflyIII\Mail\RegisteredUser as RegisteredUserMail; use FireflyIII\Mail\RegisteredUser as RegisteredUserMail;
use FireflyIII\Mail\RequestedNewPassword as RequestedNewPasswordMail; use FireflyIII\Mail\RequestedNewPassword as RequestedNewPasswordMail;
use FireflyIII\Mail\UndoEmailChangeMail; use FireflyIII\Mail\UndoEmailChangeMail;
use FireflyIII\Models\GroupMembership;
use FireflyIII\Models\UserGroup;
use FireflyIII\Models\UserRole;
use FireflyIII\Repositories\User\UserRepositoryInterface; use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Auth\Events\Login; use Illuminate\Auth\Events\Login;
@ -248,6 +251,34 @@ class UserEventHandler
return true; return true;
} }
/**
* @param RegisteredUser $event
*
* @return bool
* @throws FireflyException
*/
public function createGroupMembership(RegisteredUser $event): bool
{
$user = $event->user;
// create a new group.
$group = UserGroup::create(['title' => $user->email]);
$role = UserRole::where('title', UserRole::OWNER)->first();
if (null === $role) {
throw new FireflyException('The user role is unexpectedly empty. Did you run all migrations?');
}
GroupMembership::create(
[
'user_id' => $user->id,
'user_group_id' => $group->id,
'user_role_id' => $role->id,
]
);
$user->user_group_id = $group->id;
$user->save();
return true;
}
/** /**
* This method will send the user a registration mail, welcoming him or her to Firefly III. * This method will send the user a registration mail, welcoming him or her to Firefly III.
* This message is only sent when the configuration of Firefly III says so. * This message is only sent when the configuration of Firefly III says so.

View File

@ -767,4 +767,14 @@ class GroupCollector implements GroupCollectorInterface
return $groups; return $groups;
} }
/**
* @inheritDoc
*/
public function findNothing(): GroupCollectorInterface
{
$this->query->where('transaction_groups.id', -1);
return $this;
}
} }

View File

@ -57,6 +57,13 @@ interface GroupCollectorInterface
*/ */
public function amountLess(string $amount): GroupCollectorInterface; public function amountLess(string $amount): GroupCollectorInterface;
/**
* Ensure the search will find nothing at all, zero results.
*
* @return GroupCollectorInterface
*/
public function findNothing(): GroupCollectorInterface;
/** /**
* Get transactions where the amount is more than. * Get transactions where the amount is more than.
* *

View File

@ -137,10 +137,16 @@ class AvailableBudgetController extends Controller
* *
* @return RedirectResponse|Redirector * @return RedirectResponse|Redirector
*/ */
public function delete(AvailableBudget $availableBudget) public function delete(Request $request)
{ {
$this->abRepository->destroyAvailableBudget($availableBudget); $id = (int)$request->get('id');
session()->flash('success', trans('firefly.deleted_ab')); if (0 !== $id) {
$availableBudget = $this->abRepository->findById($id);
if (null !== $availableBudget) {
$this->abRepository->destroyAvailableBudget($availableBudget);
session()->flash('success', trans('firefly.deleted_ab'));
}
}
return redirect(route('budgets.index')); return redirect(route('budgets.index'));
} }

View File

@ -21,6 +21,7 @@
declare(strict_types=1); declare(strict_types=1);
namespace FireflyIII\Http\Controllers; namespace FireflyIII\Http\Controllers;
use FireflyIII\Exceptions\FireflyException; use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Http\Requests\CurrencyFormRequest; use FireflyIII\Http\Requests\CurrencyFormRequest;
use FireflyIII\Models\TransactionCurrency; use FireflyIII\Models\TransactionCurrency;
@ -41,7 +42,7 @@ use Log;
class CurrencyController extends Controller class CurrencyController extends Controller
{ {
protected CurrencyRepositoryInterface $repository; protected CurrencyRepositoryInterface $repository;
protected UserRepositoryInterface $userRepository; protected UserRepositoryInterface $userRepository;
/** /**
* CurrencyController constructor. * CurrencyController constructor.
@ -54,7 +55,7 @@ class CurrencyController extends Controller
$this->middleware( $this->middleware(
function ($request, $next) { function ($request, $next) {
app('view')->share('title', (string) trans('firefly.currencies')); app('view')->share('title', (string)trans('firefly.currencies'));
app('view')->share('mainTitleIcon', 'fa-usd'); app('view')->share('mainTitleIcon', 'fa-usd');
$this->repository = app(CurrencyRepositoryInterface::class); $this->repository = app(CurrencyRepositoryInterface::class);
$this->userRepository = app(UserRepositoryInterface::class); $this->userRepository = app(UserRepositoryInterface::class);
@ -63,6 +64,7 @@ class CurrencyController extends Controller
} }
); );
} }
/** /**
* Create a currency. * Create a currency.
* *
@ -75,13 +77,13 @@ class CurrencyController extends Controller
/** @var User $user */ /** @var User $user */
$user = auth()->user(); $user = auth()->user();
if (!$this->userRepository->hasRole($user, 'owner')) { if (!$this->userRepository->hasRole($user, 'owner')) {
$request->session()->flash('error', (string) trans('firefly.ask_site_owner', ['owner' => e(config('firefly.site_owner'))])); $request->session()->flash('error', (string)trans('firefly.ask_site_owner', ['owner' => e(config('firefly.site_owner'))]));
return redirect(route('currencies.index')); return redirect(route('currencies.index'));
} }
$subTitleIcon = 'fa-plus'; $subTitleIcon = 'fa-plus';
$subTitle = (string) trans('firefly.create_currency'); $subTitle = (string)trans('firefly.create_currency');
// put previous url in session if not redirect from store (not "create another"). // put previous url in session if not redirect from store (not "create another").
if (true !== session('currencies.create.fromStore')) { if (true !== session('currencies.create.fromStore')) {
@ -102,15 +104,23 @@ class CurrencyController extends Controller
* *
* @return RedirectResponse|Redirector * @return RedirectResponse|Redirector
*/ */
public function defaultCurrency(Request $request, TransactionCurrency $currency) public function defaultCurrency(Request $request)
{ {
app('preferences')->set('currencyPreference', $currency->code); $currencyId = (int)$request->get('id');
app('preferences')->mark(); if ($currencyId > 0) {
// valid currency?
$currency = $this->repository->find($currencyId);
if (null !== $currency) {
app('preferences')->set('currencyPreference', $currency->code);
app('preferences')->mark();
Log::channel('audit')->info(sprintf('Make %s the default currency.', $currency->code));
Log::channel('audit')->info(sprintf('Make %s the default currency.', $currency->code)); $this->repository->enable($currency);
$request->session()->flash('success', (string)trans('firefly.new_default_currency', ['name' => $currency->name]));
$this->repository->enable($currency); return redirect(route('currencies.index'));
$request->session()->flash('success', (string) trans('firefly.new_default_currency', ['name' => $currency->name])); }
}
return redirect(route('currencies.index')); return redirect(route('currencies.index'));
} }
@ -129,7 +139,7 @@ class CurrencyController extends Controller
$user = auth()->user(); $user = auth()->user();
if (!$this->userRepository->hasRole($user, 'owner')) { if (!$this->userRepository->hasRole($user, 'owner')) {
$request->session()->flash('error', (string) trans('firefly.ask_site_owner', ['owner' => e(config('firefly.site_owner'))])); $request->session()->flash('error', (string)trans('firefly.ask_site_owner', ['owner' => e(config('firefly.site_owner'))]));
Log::channel('audit')->info(sprintf('Tried to visit page to delete currency %s but is not site owner.', $currency->code)); Log::channel('audit')->info(sprintf('Tried to visit page to delete currency %s but is not site owner.', $currency->code));
return redirect(route('currencies.index')); return redirect(route('currencies.index'));
@ -138,7 +148,7 @@ class CurrencyController extends Controller
if ($this->repository->currencyInUse($currency)) { if ($this->repository->currencyInUse($currency)) {
$location = $this->repository->currencyInUseAt($currency); $location = $this->repository->currencyInUseAt($currency);
$message = (string) trans(sprintf('firefly.cannot_disable_currency_%s', $location), ['name' => e($currency->name)]); $message = (string)trans(sprintf('firefly.cannot_disable_currency_%s', $location), ['name' => e($currency->name)]);
$request->session()->flash('error', $message); $request->session()->flash('error', $message);
Log::channel('audit')->info(sprintf('Tried to visit page to delete currency %s but currency is in use.', $currency->code)); Log::channel('audit')->info(sprintf('Tried to visit page to delete currency %s but currency is in use.', $currency->code));
@ -147,7 +157,7 @@ class CurrencyController extends Controller
// put previous url in session // put previous url in session
$this->rememberPreviousUri('currencies.delete.uri'); $this->rememberPreviousUri('currencies.delete.uri');
$subTitle = (string) trans('form.delete_currency', ['name' => $currency->name]); $subTitle = (string)trans('form.delete_currency', ['name' => $currency->name]);
Log::channel('audit')->info(sprintf('Visit page to delete currency %s.', $currency->code)); Log::channel('audit')->info(sprintf('Visit page to delete currency %s.', $currency->code));
return prefixView('currencies.delete', compact('currency', 'subTitle')); return prefixView('currencies.delete', compact('currency', 'subTitle'));
@ -167,7 +177,7 @@ class CurrencyController extends Controller
$user = auth()->user(); $user = auth()->user();
if (!$this->userRepository->hasRole($user, 'owner')) { if (!$this->userRepository->hasRole($user, 'owner')) {
$request->session()->flash('error', (string) trans('firefly.ask_site_owner', ['owner' => e(config('firefly.site_owner'))])); $request->session()->flash('error', (string)trans('firefly.ask_site_owner', ['owner' => e(config('firefly.site_owner'))]));
Log::channel('audit')->info(sprintf('Tried to delete currency %s but is not site owner.', $currency->code)); Log::channel('audit')->info(sprintf('Tried to delete currency %s but is not site owner.', $currency->code));
return redirect(route('currencies.index')); return redirect(route('currencies.index'));
@ -175,14 +185,14 @@ class CurrencyController extends Controller
} }
if ($this->repository->currencyInUse($currency)) { if ($this->repository->currencyInUse($currency)) {
$request->session()->flash('error', (string) trans('firefly.cannot_delete_currency', ['name' => e($currency->name)])); $request->session()->flash('error', (string)trans('firefly.cannot_delete_currency', ['name' => e($currency->name)]));
Log::channel('audit')->info(sprintf('Tried to delete currency %s but is in use.', $currency->code)); Log::channel('audit')->info(sprintf('Tried to delete currency %s but is in use.', $currency->code));
return redirect(route('currencies.index')); return redirect(route('currencies.index'));
} }
if ($this->repository->isFallbackCurrency($currency)) { if ($this->repository->isFallbackCurrency($currency)) {
$request->session()->flash('error', (string) trans('firefly.cannot_delete_fallback_currency', ['name' => e($currency->name)])); $request->session()->flash('error', (string)trans('firefly.cannot_delete_fallback_currency', ['name' => e($currency->name)]));
Log::channel('audit')->info(sprintf('Tried to delete currency %s but is FALLBACK.', $currency->code)); Log::channel('audit')->info(sprintf('Tried to delete currency %s but is FALLBACK.', $currency->code));
return redirect(route('currencies.index')); return redirect(route('currencies.index'));
@ -191,7 +201,7 @@ class CurrencyController extends Controller
Log::channel('audit')->info(sprintf('Deleted currency %s.', $currency->code)); Log::channel('audit')->info(sprintf('Deleted currency %s.', $currency->code));
$this->repository->destroy($currency); $this->repository->destroy($currency);
$request->session()->flash('success', (string) trans('firefly.deleted_currency', ['name' => $currency->name])); $request->session()->flash('success', (string)trans('firefly.deleted_currency', ['name' => $currency->name]));
return redirect($this->getPreviousUri('currencies.delete.uri')); return redirect($this->getPreviousUri('currencies.delete.uri'));
} }
@ -200,8 +210,8 @@ class CurrencyController extends Controller
* @param Request $request * @param Request $request
* @param TransactionCurrency $currency * @param TransactionCurrency $currency
* *
* @throws FireflyException
* @return RedirectResponse|Redirector * @return RedirectResponse|Redirector
* @throws FireflyException
*/ */
public function disableCurrency(Request $request, TransactionCurrency $currency) public function disableCurrency(Request $request, TransactionCurrency $currency)
{ {
@ -211,7 +221,7 @@ class CurrencyController extends Controller
$user = auth()->user(); $user = auth()->user();
if (!$this->userRepository->hasRole($user, 'owner')) { if (!$this->userRepository->hasRole($user, 'owner')) {
$request->session()->flash('error', (string) trans('firefly.ask_site_owner', ['owner' => e(config('firefly.site_owner'))])); $request->session()->flash('error', (string)trans('firefly.ask_site_owner', ['owner' => e(config('firefly.site_owner'))]));
Log::channel('audit')->info(sprintf('Tried to disable currency %s but is not site owner.', $currency->code)); Log::channel('audit')->info(sprintf('Tried to disable currency %s but is not site owner.', $currency->code));
return redirect(route('currencies.index')); return redirect(route('currencies.index'));
@ -221,7 +231,7 @@ class CurrencyController extends Controller
if ($this->repository->currencyInUse($currency)) { if ($this->repository->currencyInUse($currency)) {
$location = $this->repository->currencyInUseAt($currency); $location = $this->repository->currencyInUseAt($currency);
$message = (string) trans(sprintf('firefly.cannot_disable_currency_%s', $location), ['name' => e($currency->name)]); $message = (string)trans(sprintf('firefly.cannot_disable_currency_%s', $location), ['name' => e($currency->name)]);
$request->session()->flash('error', $message); $request->session()->flash('error', $message);
Log::channel('audit')->info(sprintf('Tried to disable currency %s but is in use.', $currency->code)); Log::channel('audit')->info(sprintf('Tried to disable currency %s but is in use.', $currency->code));
@ -245,10 +255,10 @@ class CurrencyController extends Controller
} }
if ('EUR' === $currency->code) { if ('EUR' === $currency->code) {
session()->flash('warning', (string) trans('firefly.disable_EUR_side_effects')); session()->flash('warning', (string)trans('firefly.disable_EUR_side_effects'));
} }
session()->flash('success', (string) trans('firefly.currency_is_now_disabled', ['name' => $currency->name])); session()->flash('success', (string)trans('firefly.currency_is_now_disabled', ['name' => $currency->name]));
return redirect(route('currencies.index')); return redirect(route('currencies.index'));
} }
@ -267,7 +277,7 @@ class CurrencyController extends Controller
$user = auth()->user(); $user = auth()->user();
if (!$this->userRepository->hasRole($user, 'owner')) { if (!$this->userRepository->hasRole($user, 'owner')) {
$request->session()->flash('error', (string) trans('firefly.ask_site_owner', ['owner' => e(config('firefly.site_owner'))])); $request->session()->flash('error', (string)trans('firefly.ask_site_owner', ['owner' => e(config('firefly.site_owner'))]));
Log::channel('audit')->info(sprintf('Tried to edit currency %s but is not owner.', $currency->code)); Log::channel('audit')->info(sprintf('Tried to edit currency %s but is not owner.', $currency->code));
return redirect(route('currencies.index')); return redirect(route('currencies.index'));
@ -275,13 +285,13 @@ class CurrencyController extends Controller
} }
$subTitleIcon = 'fa-pencil'; $subTitleIcon = 'fa-pencil';
$subTitle = (string) trans('breadcrumbs.edit_currency', ['name' => $currency->name]); $subTitle = (string)trans('breadcrumbs.edit_currency', ['name' => $currency->name]);
$currency->symbol = htmlentities($currency->symbol); $currency->symbol = htmlentities($currency->symbol);
// code to handle active-checkboxes // code to handle active-checkboxes
$hasOldInput = null !== $request->old('_token'); $hasOldInput = null !== $request->old('_token');
$preFilled = [ $preFilled = [
'enabled' => $hasOldInput ? (bool) $request->old('enabled') : $currency->enabled, 'enabled' => $hasOldInput ? (bool)$request->old('enabled') : $currency->enabled,
]; ];
$request->session()->flash('preFilled', $preFilled); $request->session()->flash('preFilled', $preFilled);
@ -306,7 +316,7 @@ class CurrencyController extends Controller
app('preferences')->mark(); app('preferences')->mark();
$this->repository->enable($currency); $this->repository->enable($currency);
session()->flash('success', (string) trans('firefly.currency_is_now_enabled', ['name' => $currency->name])); session()->flash('success', (string)trans('firefly.currency_is_now_enabled', ['name' => $currency->name]));
Log::channel('audit')->info(sprintf('Enabled currency %s.', $currency->code)); Log::channel('audit')->info(sprintf('Enabled currency %s.', $currency->code));
return redirect(route('currencies.index')); return redirect(route('currencies.index'));
@ -323,8 +333,8 @@ class CurrencyController extends Controller
{ {
/** @var User $user */ /** @var User $user */
$user = auth()->user(); $user = auth()->user();
$page = 0 === (int) $request->get('page') ? 1 : (int) $request->get('page'); $page = 0 === (int)$request->get('page') ? 1 : (int)$request->get('page');
$pageSize = (int) app('preferences')->get('listPageSize', 50)->data; $pageSize = (int)app('preferences')->get('listPageSize', 50)->data;
$collection = $this->repository->getAll(); $collection = $this->repository->getAll();
$total = $collection->count(); $total = $collection->count();
$collection = $collection->slice(($page - 1) * $pageSize, $pageSize); $collection = $collection->slice(($page - 1) * $pageSize, $pageSize);
@ -334,12 +344,13 @@ class CurrencyController extends Controller
$defaultCurrency = $this->repository->getCurrencyByPreference(app('preferences')->get('currencyPreference', config('firefly.default_currency', 'EUR'))); $defaultCurrency = $this->repository->getCurrencyByPreference(app('preferences')->get('currencyPreference', config('firefly.default_currency', 'EUR')));
$isOwner = true; $isOwner = true;
if (!$this->userRepository->hasRole($user, 'owner')) { if (!$this->userRepository->hasRole($user, 'owner')) {
$request->session()->flash('info', (string) trans('firefly.ask_site_owner', ['owner' => config('firefly.site_owner')])); $request->session()->flash('info', (string)trans('firefly.ask_site_owner', ['owner' => config('firefly.site_owner')]));
$isOwner = false; $isOwner = false;
} }
return prefixView('currencies.index', compact('currencies', 'defaultCurrency', 'isOwner')); return prefixView('currencies.index', compact('currencies', 'defaultCurrency', 'isOwner'));
} }
/** /**
* Store new currency. * Store new currency.
* *
@ -367,15 +378,15 @@ class CurrencyController extends Controller
} catch (FireflyException $e) { } catch (FireflyException $e) {
Log::error($e->getMessage()); Log::error($e->getMessage());
Log::channel('audit')->info('Could not store (POST) currency without admin rights.', $data); Log::channel('audit')->info('Could not store (POST) currency without admin rights.', $data);
$request->session()->flash('error', (string) trans('firefly.could_not_store_currency')); $request->session()->flash('error', (string)trans('firefly.could_not_store_currency'));
$currency = null; $currency = null;
} }
$redirect = redirect($this->getPreviousUri('currencies.create.uri')); $redirect = redirect($this->getPreviousUri('currencies.create.uri'));
if (null !== $currency) { if (null !== $currency) {
$request->session()->flash('success', (string) trans('firefly.created_currency', ['name' => $currency->name])); $request->session()->flash('success', (string)trans('firefly.created_currency', ['name' => $currency->name]));
Log::channel('audit')->info('Created (POST) currency.', $data); Log::channel('audit')->info('Created (POST) currency.', $data);
if (1 === (int) $request->get('create_another')) { if (1 === (int)$request->get('create_another')) {
$request->session()->put('currencies.create.fromStore', true); $request->session()->put('currencies.create.fromStore', true);
@ -386,6 +397,7 @@ class CurrencyController extends Controller
return $redirect; return $redirect;
} }
/** /**
* Updates a currency. * Updates a currency.
* *
@ -405,7 +417,7 @@ class CurrencyController extends Controller
} }
if (!$this->userRepository->hasRole($user, 'owner')) { if (!$this->userRepository->hasRole($user, 'owner')) {
$request->session()->flash('error', (string) trans('firefly.ask_site_owner', ['owner' => e(config('firefly.site_owner'))])); $request->session()->flash('error', (string)trans('firefly.ask_site_owner', ['owner' => e(config('firefly.site_owner'))]));
Log::channel('audit')->info('Tried to update (POST) currency without admin rights.', $data); Log::channel('audit')->info('Tried to update (POST) currency without admin rights.', $data);
return redirect(route('currencies.index')); return redirect(route('currencies.index'));
@ -413,10 +425,10 @@ class CurrencyController extends Controller
} }
$currency = $this->repository->update($currency, $data); $currency = $this->repository->update($currency, $data);
Log::channel('audit')->info('Updated (POST) currency.', $data); Log::channel('audit')->info('Updated (POST) currency.', $data);
$request->session()->flash('success', (string) trans('firefly.updated_currency', ['name' => $currency->name])); $request->session()->flash('success', (string)trans('firefly.updated_currency', ['name' => $currency->name]));
app('preferences')->mark(); app('preferences')->mark();
if (1 === (int) $request->get('return_to_edit')) { if (1 === (int)$request->get('return_to_edit')) {
$request->session()->put('currencies.edit.fromUpdate', true); $request->session()->put('currencies.edit.fromUpdate', true);

View File

@ -28,6 +28,7 @@ use FireflyIII\Events\RequestedVersionCheckStatus;
use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Http\Middleware\Installer; use FireflyIII\Http\Middleware\Installer;
use FireflyIII\Models\AccountType; use FireflyIII\Models\AccountType;
use FireflyIII\Models\GroupMembership;
use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Bill\BillRepositoryInterface; use FireflyIII\Repositories\Bill\BillRepositoryInterface;
use FireflyIII\User; use FireflyIII\User;

View File

@ -86,6 +86,7 @@ class InstallController extends Controller
'firefly-iii:migrate-tag-locations' => [], 'firefly-iii:migrate-tag-locations' => [],
'firefly-iii:migrate-recurrence-type' => [], 'firefly-iii:migrate-recurrence-type' => [],
'firefly-iii:upgrade-liabilities' => [], 'firefly-iii:upgrade-liabilities' => [],
'firefly-iii:create-group-memberships' => [],
// verify commands // verify commands
'firefly-iii:fix-piggies' => [], 'firefly-iii:fix-piggies' => [],
@ -111,7 +112,7 @@ class InstallController extends Controller
// final command to set latest version in DB // final command to set latest version in DB
'firefly-iii:set-latest-version' => ['--james-is-cool' => true], 'firefly-iii:set-latest-version' => ['--james-is-cool' => true],
'firefly-iii:verify-security-alerts' => [], 'firefly-iii:verify-security-alerts' => [],
]; ];
$this->lastError = ''; $this->lastError = '';

View File

@ -39,8 +39,8 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/** /**
* Class Account * Class Account
* *
* @property int $id * @property int $id
* @property \Illuminate\Support\Carbon|null $created_at * @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at * @property \Illuminate\Support\Carbon|null $updated_at
* @property \Illuminate\Support\Carbon|null $deleted_at * @property \Illuminate\Support\Carbon|null $deleted_at
* @property int $user_id * @property int $user_id
@ -89,16 +89,16 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
* @method static Builder|Account withTrashed() * @method static Builder|Account withTrashed()
* @method static Builder|Account withoutTrashed() * @method static Builder|Account withoutTrashed()
* @mixin Eloquent * @mixin Eloquent
* @property Carbon $lastActivityDate * @property Carbon $lastActivityDate
* @property string $startBalance * @property string $startBalance
* @property string $endBalance * @property string $endBalance
* @property string $difference * @property string $difference
* @property string $interest * @property string $interest
* @property string $interestPeriod * @property string $interestPeriod
* @property string $accountTypeString * @property string $accountTypeString
* @property string $location * @property string $location
* @property string $liability_direction * @property string $liability_direction
* @property string $current_debt * @property string $current_debt
*/ */
class Account extends Model class Account extends Model
{ {
@ -149,11 +149,12 @@ class Account extends Model
} }
/** /**
* Get all of the tags for the post. * @return BelongsTo
* @codeCoverageIgnore
*/ */
public function objectGroups() public function accountType(): BelongsTo
{ {
return $this->morphToMany(ObjectGroup::class, 'object_groupable'); return $this->belongsTo(AccountType::class);
} }
/** /**
@ -165,24 +166,6 @@ class Account extends Model
return $this->morphMany(Attachment::class, 'attachable'); return $this->morphMany(Attachment::class, 'attachable');
} }
/**
* @return HasMany
* @codeCoverageIgnore
*/
public function accountMeta(): HasMany
{
return $this->hasMany(AccountMeta::class);
}
/**
* @return BelongsTo
* @codeCoverageIgnore
*/
public function accountType(): BelongsTo
{
return $this->belongsTo(AccountType::class);
}
/** /**
* Get the account number. * Get the account number.
* *
@ -198,6 +181,15 @@ class Account extends Model
return $metaValue ? $metaValue->data : ''; return $metaValue ? $metaValue->data : '';
} }
/**
* @return HasMany
* @codeCoverageIgnore
*/
public function accountMeta(): HasMany
{
return $this->hasMany(AccountMeta::class);
}
/** /**
* @return string * @return string
* @codeCoverageIgnore * @codeCoverageIgnore
@ -231,6 +223,14 @@ class Account extends Model
return $this->morphMany(Note::class, 'noteable'); return $this->morphMany(Note::class, 'noteable');
} }
/**
* Get all of the tags for the post.
*/
public function objectGroups()
{
return $this->morphToMany(ObjectGroup::class, 'object_groupable');
}
/** /**
* @return HasMany * @return HasMany
* @codeCoverageIgnore * @codeCoverageIgnore

View File

@ -27,6 +27,7 @@ use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
use JsonException;
/** /**
* Class AccountMeta * Class AccountMeta
@ -79,7 +80,7 @@ class AccountMeta extends Model
* @param mixed $value * @param mixed $value
* *
* @return mixed * @return mixed
* @throws \JsonException * @throws JsonException
* @codeCoverageIgnore * @codeCoverageIgnore
*/ */
public function getDataAttribute($value) public function getDataAttribute($value)

View File

@ -28,6 +28,7 @@ use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
/** /**
* FireflyIII\Models\AccountType * FireflyIII\Models\AccountType
* *
@ -74,7 +75,7 @@ class AccountType extends Model
public const MORTGAGE = 'Mortgage'; public const MORTGAGE = 'Mortgage';
/** @var string */ /** @var string */
public const CREDITCARD = 'Credit card'; public const CREDITCARD = 'Credit card';
/** @var string */ /** @var string */
public const LIABILITY_CREDIT = 'Liability credit account'; public const LIABILITY_CREDIT = 'Liability credit account';
/** /**
* The attributes that should be casted to native types. * The attributes that should be casted to native types.

View File

@ -37,12 +37,12 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/** /**
* FireflyIII\Models\Attachment * FireflyIII\Models\Attachment
* *
* @property int $id * @property int $id
* @property Carbon|null $created_at * @property Carbon|null $created_at
* @property Carbon|null $updated_at * @property Carbon|null $updated_at
* @property Carbon|null $deleted_at * @property Carbon|null $deleted_at
* @property int $user_id * @property int $user_id
* @property int $attachable_id * @property int $attachable_id
* @property string $attachable_type * @property string $attachable_type
* @property bool $file_exists * @property bool $file_exists
* @property string $md5 * @property string $md5

View File

@ -22,27 +22,27 @@ declare(strict_types=1);
namespace FireflyIII\Models; namespace FireflyIII\Models;
use Carbon\Carbon;
use Eloquent; use Eloquent;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Query\Builder; use Illuminate\Database\Query\Builder;
use Illuminate\Support\Carbon;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/** /**
* FireflyIII\Models\AvailableBudget * FireflyIII\Models\AvailableBudget
* *
* @property int $id * @property int $id
* @property \Illuminate\Support\Carbon|null $created_at * @property Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at * @property Carbon|null $updated_at
* @property \Illuminate\Support\Carbon|null $deleted_at * @property Carbon|null $deleted_at
* @property int $user_id * @property int $user_id
* @property int $transaction_currency_id * @property int $transaction_currency_id
* @property string $amount * @property string $amount
* @property \Illuminate\Support\Carbon $start_date * @property Carbon $start_date
* @property \Illuminate\Support\Carbon $end_date * @property Carbon $end_date
* @property-read TransactionCurrency $transactionCurrency * @property-read TransactionCurrency $transactionCurrency
* @property-read User $user * @property-read User $user
* @method static \Illuminate\Database\Eloquent\Builder|AvailableBudget newModelQuery() * @method static \Illuminate\Database\Eloquent\Builder|AvailableBudget newModelQuery()
@ -65,6 +65,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class AvailableBudget extends Model class AvailableBudget extends Model
{ {
use SoftDeletes; use SoftDeletes;
/** /**
* The attributes that should be casted to native types. * The attributes that should be casted to native types.
* *
@ -87,13 +88,13 @@ class AvailableBudget extends Model
* *
* @param string $value * @param string $value
* *
* @throws NotFoundHttpException
* @return AvailableBudget * @return AvailableBudget
* @throws NotFoundHttpException
*/ */
public static function routeBinder(string $value): AvailableBudget public static function routeBinder(string $value): AvailableBudget
{ {
if (auth()->check()) { if (auth()->check()) {
$availableBudgetId = (int) $value; $availableBudgetId = (int)$value;
/** @var User $user */ /** @var User $user */
$user = auth()->user(); $user = auth()->user();
/** @var AvailableBudget $availableBudget */ /** @var AvailableBudget $availableBudget */

View File

@ -39,9 +39,9 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
* FireflyIII\Models\Budget * FireflyIII\Models\Budget
* *
* @property int $id * @property int $id
* @property Carbon|null $created_at * @property Carbon|null $created_at
* @property Carbon|null $updated_at * @property Carbon|null $updated_at
* @property Carbon|null $deleted_at * @property Carbon|null $deleted_at
* @property int $user_id * @property int $user_id
* @property string $name * @property string $name
* @property bool $active * @property bool $active
@ -74,7 +74,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
* @method static Builder|Budget withTrashed() * @method static Builder|Budget withTrashed()
* @method static Builder|Budget withoutTrashed() * @method static Builder|Budget withoutTrashed()
* @mixin Eloquent * @mixin Eloquent
* @property string $email * @property string $email
*/ */
class Budget extends Model class Budget extends Model
{ {

View File

@ -22,23 +22,23 @@ declare(strict_types=1);
namespace FireflyIII\Models; namespace FireflyIII\Models;
use Carbon\Carbon;
use Eloquent; use Eloquent;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Carbon;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/** /**
* FireflyIII\Models\BudgetLimit * FireflyIII\Models\BudgetLimit
* *
* @property int $id * @property int $id
* @property \Illuminate\Support\Carbon|null $created_at * @property Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at * @property Carbon|null $updated_at
* @property int $budget_id * @property int $budget_id
* @property int|null $transaction_currency_id * @property int|null $transaction_currency_id
* @property \Illuminate\Support\Carbon $start_date * @property Carbon $start_date
* @property \Illuminate\Support\Carbon|null $end_date * @property Carbon|null $end_date
* @property string $amount * @property string $amount
* @property string $spent * @property string $spent
* @property string|null $period * @property string|null $period

View File

@ -96,13 +96,13 @@ class Category extends Model
* *
* @param string $value * @param string $value
* *
* @throws NotFoundHttpException
* @return Category * @return Category
* @throws NotFoundHttpException
*/ */
public static function routeBinder(string $value): Category public static function routeBinder(string $value): Category
{ {
if (auth()->check()) { if (auth()->check()) {
$categoryId = (int) $value; $categoryId = (int)$value;
/** @var User $user */ /** @var User $user */
$user = auth()->user(); $user = auth()->user();
/** @var Category $category */ /** @var Category $category */
@ -114,14 +114,6 @@ class Category extends Model
throw new NotFoundHttpException; throw new NotFoundHttpException;
} }
/**
* @codeCoverageIgnore
* @return BelongsToMany
*/
public function transactionJournals(): BelongsToMany
{
return $this->belongsToMany(TransactionJournal::class, 'category_transaction_journal', 'category_id');
}
/** /**
* @codeCoverageIgnore * @codeCoverageIgnore
* @return MorphMany * @return MorphMany
@ -140,6 +132,15 @@ class Category extends Model
return $this->morphMany(Note::class, 'noteable'); return $this->morphMany(Note::class, 'noteable');
} }
/**
* @codeCoverageIgnore
* @return BelongsToMany
*/
public function transactionJournals(): BelongsToMany
{
return $this->belongsToMany(TransactionJournal::class, 'category_transaction_journal', 'category_id');
}
/** /**
* @codeCoverageIgnore * @codeCoverageIgnore
* @return BelongsToMany * @return BelongsToMany

View File

@ -31,12 +31,12 @@ use Illuminate\Support\Carbon;
/** /**
* FireflyIII\Models\Configuration * FireflyIII\Models\Configuration
* *
* @property int $id * @property int $id
* @property Carbon|null $created_at * @property Carbon|null $created_at
* @property Carbon|null $updated_at * @property Carbon|null $updated_at
* @property Carbon|null $deleted_at * @property Carbon|null $deleted_at
* @property string $name * @property string $name
* @property mixed $data * @property mixed $data
* @method static \Illuminate\Database\Eloquent\Builder|Configuration newModelQuery() * @method static \Illuminate\Database\Eloquent\Builder|Configuration newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|Configuration newQuery() * @method static \Illuminate\Database\Eloquent\Builder|Configuration newQuery()
* @method static Builder|Configuration onlyTrashed() * @method static Builder|Configuration onlyTrashed()
@ -70,7 +70,8 @@ class Configuration extends Model
protected $table = 'configuration'; protected $table = 'configuration';
/** /**
* See reference nr. 17 * See reference nr. 17
*
* @codeCoverageIgnore * @codeCoverageIgnore
* *
* @param mixed $value * @param mixed $value

View File

@ -22,24 +22,24 @@ declare(strict_types=1);
namespace FireflyIII\Models; namespace FireflyIII\Models;
use Carbon\Carbon;
use Eloquent; use Eloquent;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Carbon;
/** /**
* FireflyIII\Models\CurrencyExchangeRate * FireflyIII\Models\CurrencyExchangeRate
* *
* @property int $id * @property int $id
* @property \Illuminate\Support\Carbon|null $created_at * @property Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at * @property Carbon|null $updated_at
* @property string|null $deleted_at * @property string|null $deleted_at
* @property int $user_id * @property int $user_id
* @property int $from_currency_id * @property int $from_currency_id
* @property int $to_currency_id * @property int $to_currency_id
* @property \Illuminate\Support\Carbon $date * @property Carbon $date
* @property string $rate * @property string $rate
* @property string|null $user_rate * @property string|null $user_rate
* @property-read TransactionCurrency $fromCurrency * @property-read TransactionCurrency $fromCurrency

View File

@ -0,0 +1,61 @@
<?php
/*
* GroupMembership.php
* Copyright (c) 2021 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Models;
use FireflyIII\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
/**
* Class GroupMembership
*/
class GroupMembership extends Model
{
protected $fillable = ['user_id', 'user_group_id', 'user_role_id'];
/**
* @return BelongsTo
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
/**
* @return BelongsTo
*/
public function userGroup(): BelongsTo
{
return $this->belongsTo(UserGroup::class);
}
/**
* @return BelongsTo
*/
public function userRole(): BelongsTo
{
return $this->belongsTo(UserRole::class);
}
}

View File

@ -22,21 +22,22 @@ declare(strict_types=1);
namespace FireflyIII\Models; namespace FireflyIII\Models;
use Carbon\Carbon;
use Eloquent; use Eloquent;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Query\Builder; use Illuminate\Database\Query\Builder;
use Illuminate\Support\Carbon;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/** /**
* FireflyIII\Models\LinkType * FireflyIII\Models\LinkType
* *
* @property int $id * @property int $id
* @property \Illuminate\Support\Carbon|null $created_at * @property Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at * @property Carbon|null $updated_at
* @property \Illuminate\Support\Carbon|null $deleted_at * @property Carbon|null $deleted_at
* @property string $name * @property string $name
* @property string $outward * @property string $outward
* @property string $inward * @property string $inward
@ -63,6 +64,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class LinkType extends Model class LinkType extends Model
{ {
use SoftDeletes; use SoftDeletes;
/** /**
* The attributes that should be casted to native types. * The attributes that should be casted to native types.
* *
@ -84,14 +86,14 @@ class LinkType extends Model
* *
* @param string $value * @param string $value
* *
* @throws NotFoundHttpException
* @return LinkType * @return LinkType
* *
* @throws NotFoundHttpException
*/ */
public static function routeBinder(string $value): LinkType public static function routeBinder(string $value): LinkType
{ {
if (auth()->check()) { if (auth()->check()) {
$linkTypeId = (int) $value; $linkTypeId = (int)$value;
$linkType = self::find($linkTypeId); $linkType = self::find($linkTypeId);
if (null !== $linkType) { if (null !== $linkType) {
return $linkType; return $linkType;

View File

@ -23,6 +23,7 @@
declare(strict_types=1); declare(strict_types=1);
namespace FireflyIII\Models; namespace FireflyIII\Models;
use Eloquent; use Eloquent;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
@ -30,6 +31,7 @@ use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphMany; use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Database\Eloquent\Relations\MorphTo; use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
/** /**
* FireflyIII\Models\Location * FireflyIII\Models\Location
* *

View File

@ -23,6 +23,7 @@
declare(strict_types=1); declare(strict_types=1);
namespace FireflyIII\Models; namespace FireflyIII\Models;
use Eloquent; use Eloquent;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
@ -32,6 +33,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphToMany; use Illuminate\Database\Eloquent\Relations\MorphToMany;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/** /**
* FireflyIII\Models\ObjectGroup * FireflyIII\Models\ObjectGroup
* *
@ -63,8 +65,6 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
*/ */
class ObjectGroup extends Model class ObjectGroup extends Model
{ {
protected $fillable = ['title', 'order', 'user_id'];
/** /**
* The attributes that should be casted to native types. * The attributes that should be casted to native types.
* *
@ -77,13 +77,36 @@ class ObjectGroup extends Model
'user_id' => 'integer', 'user_id' => 'integer',
'deleted_at' => 'datetime', 'deleted_at' => 'datetime',
]; ];
protected $fillable = ['title', 'order', 'user_id'];
/**
* Route binder. Converts the key in the URL to the specified object (or throw 404).
*
* @param string $value
*
* @return ObjectGroup
* @throws NotFoundHttpException
*/
public static function routeBinder(string $value): ObjectGroup
{
if (auth()->check()) {
$objectGroupId = (int)$value;
/** @var ObjectGroup $objectGroup */
$objectGroup = self::where('object_groups.id', $objectGroupId)
->where('object_groups.user_id', auth()->user()->id)->first();
if (null !== $objectGroup) {
return $objectGroup;
}
}
throw new NotFoundHttpException;
}
/** /**
* @return MorphToMany * @return MorphToMany
*/ */
public function piggyBanks() public function accounts()
{ {
return $this->morphedByMany(PiggyBank::class, 'object_groupable'); return $this->morphedByMany(Account::class, 'object_groupable');
} }
/** /**
@ -97,31 +120,9 @@ class ObjectGroup extends Model
/** /**
* @return MorphToMany * @return MorphToMany
*/ */
public function accounts() public function piggyBanks()
{ {
return $this->morphedByMany(Account::class, 'object_groupable'); return $this->morphedByMany(PiggyBank::class, 'object_groupable');
}
/**
* Route binder. Converts the key in the URL to the specified object (or throw 404).
*
* @param string $value
*
* @throws NotFoundHttpException
* @return ObjectGroup
*/
public static function routeBinder(string $value): ObjectGroup
{
if (auth()->check()) {
$objectGroupId = (int) $value;
/** @var ObjectGroup $objectGroup */
$objectGroup = self::where('object_groups.id', $objectGroupId)
->where('object_groups.user_id', auth()->user()->id)->first();
if (null !== $objectGroup) {
return $objectGroup;
}
}
throw new NotFoundHttpException;
} }
/** /**

View File

@ -22,7 +22,6 @@ declare(strict_types=1);
namespace FireflyIII\Models; namespace FireflyIII\Models;
use Carbon\Carbon;
use Eloquent; use Eloquent;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
@ -31,19 +30,21 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\MorphMany; use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Query\Builder; use Illuminate\Database\Query\Builder;
use Illuminate\Support\Carbon;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/** /**
* FireflyIII\Models\PiggyBank * FireflyIII\Models\PiggyBank
* *
* @property int $id * @property int $id
* @property \Illuminate\Support\Carbon|null $created_at * @property Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at * @property Carbon|null $updated_at
* @property \Illuminate\Support\Carbon|null $deleted_at * @property Carbon|null $deleted_at
* @property int $account_id * @property int $account_id
* @property string $name * @property string $name
* @property string $targetamount * @property string $targetamount
* @property \Illuminate\Support\Carbon|null $startdate * @property Carbon|null $startdate
* @property \Illuminate\Support\Carbon|null $targetdate * @property Carbon|null $targetdate
* @property int $order * @property int $order
* @property bool $active * @property bool $active
* @property bool $encrypted * @property bool $encrypted
@ -108,13 +109,13 @@ class PiggyBank extends Model
* *
* @param string $value * @param string $value
* *
* @throws NotFoundHttpException
* @return PiggyBank * @return PiggyBank
* @throws NotFoundHttpException
*/ */
public static function routeBinder(string $value): PiggyBank public static function routeBinder(string $value): PiggyBank
{ {
if (auth()->check()) { if (auth()->check()) {
$piggyBankId = (int) $value; $piggyBankId = (int)$value;
$piggyBank = self::where('piggy_banks.id', $piggyBankId) $piggyBank = self::where('piggy_banks.id', $piggyBankId)
->leftJoin('accounts', 'accounts.id', '=', 'piggy_banks.account_id') ->leftJoin('accounts', 'accounts.id', '=', 'piggy_banks.account_id')
->where('accounts.user_id', auth()->user()->id)->first(['piggy_banks.*']); ->where('accounts.user_id', auth()->user()->id)->first(['piggy_banks.*']);
@ -125,23 +126,6 @@ class PiggyBank extends Model
throw new NotFoundHttpException; throw new NotFoundHttpException;
} }
/**
* Get all of the tags for the post.
*/
public function objectGroups()
{
return $this->morphToMany(ObjectGroup::class, 'object_groupable');
}
/**
* @codeCoverageIgnore
* @return MorphMany
*/
public function attachments(): MorphMany
{
return $this->morphMany(Attachment::class, 'attachable');
}
/** /**
* @codeCoverageIgnore * @codeCoverageIgnore
* @return BelongsTo * @return BelongsTo
@ -151,6 +135,15 @@ class PiggyBank extends Model
return $this->belongsTo(Account::class); return $this->belongsTo(Account::class);
} }
/**
* @codeCoverageIgnore
* @return MorphMany
*/
public function attachments(): MorphMany
{
return $this->morphMany(Attachment::class, 'attachable');
}
/** /**
* @codeCoverageIgnore * @codeCoverageIgnore
* Get all of the piggy bank's notes. * Get all of the piggy bank's notes.
@ -160,6 +153,14 @@ class PiggyBank extends Model
return $this->morphMany(Note::class, 'noteable'); return $this->morphMany(Note::class, 'noteable');
} }
/**
* Get all of the tags for the post.
*/
public function objectGroups()
{
return $this->morphToMany(ObjectGroup::class, 'object_groupable');
}
/** /**
* @codeCoverageIgnore * @codeCoverageIgnore
* @return HasMany * @return HasMany
@ -185,6 +186,6 @@ class PiggyBank extends Model
*/ */
public function setTargetamountAttribute($value): void public function setTargetamountAttribute($value): void
{ {
$this->attributes['targetamount'] = (string) $value; $this->attributes['targetamount'] = (string)$value;
} }
} }

View File

@ -22,20 +22,21 @@ declare(strict_types=1);
namespace FireflyIII\Models; namespace FireflyIII\Models;
use Carbon\Carbon;
use Eloquent; use Eloquent;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Carbon;
/** /**
* FireflyIII\Models\PiggyBankEvent * FireflyIII\Models\PiggyBankEvent
* *
* @property int $id * @property int $id
* @property \Illuminate\Support\Carbon|null $created_at * @property Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at * @property Carbon|null $updated_at
* @property int $piggy_bank_id * @property int $piggy_bank_id
* @property int|null $transaction_journal_id * @property int|null $transaction_journal_id
* @property \Illuminate\Support\Carbon $date * @property Carbon $date
* @property string $amount * @property string $amount
* @property PiggyBank $piggyBank * @property PiggyBank $piggyBank
* @property-read TransactionJournal|null $transactionJournal * @property-read TransactionJournal|null $transactionJournal
@ -85,7 +86,7 @@ class PiggyBankEvent extends Model
*/ */
public function setAmountAttribute($value): void public function setAmountAttribute($value): void
{ {
$this->attributes['amount'] = (string) $value; $this->attributes['amount'] = (string)$value;
} }
/** /**

View File

@ -31,14 +31,14 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
/** /**
* FireflyIII\Models\PiggyBankRepetition * FireflyIII\Models\PiggyBankRepetition
* *
* @property int $id * @property int $id
* @property \Illuminate\Support\Carbon|null $created_at * @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at * @property \Illuminate\Support\Carbon|null $updated_at
* @property int $piggy_bank_id * @property int $piggy_bank_id
* @property \Illuminate\Support\Carbon|null $startdate * @property \Illuminate\Support\Carbon|null $startdate
* @property \Illuminate\Support\Carbon|null $targetdate * @property \Illuminate\Support\Carbon|null $targetdate
* @property string $currentamount * @property string $currentamount
* @property-read PiggyBank $piggyBank * @property-read PiggyBank $piggyBank
* @method static EloquentBuilder|PiggyBankRepetition newModelQuery() * @method static EloquentBuilder|PiggyBankRepetition newModelQuery()
* @method static EloquentBuilder|PiggyBankRepetition newQuery() * @method static EloquentBuilder|PiggyBankRepetition newQuery()
* @method static EloquentBuilder|PiggyBankRepetition onDates(Carbon $start, Carbon $target) * @method static EloquentBuilder|PiggyBankRepetition onDates(Carbon $start, Carbon $target)
@ -124,6 +124,6 @@ class PiggyBankRepetition extends Model
*/ */
public function setCurrentamountAttribute($value): void public function setCurrentamountAttribute($value): void
{ {
$this->attributes['currentamount'] = (string) $value; $this->attributes['currentamount'] = (string)$value;
} }
} }

View File

@ -33,13 +33,13 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/** /**
* FireflyIII\Models\Preference * FireflyIII\Models\Preference
* *
* @property int $id * @property int $id
* @property Carbon|null $created_at * @property Carbon|null $created_at
* @property Carbon|null $updated_at * @property Carbon|null $updated_at
* @property int $user_id * @property int $user_id
* @property string $name * @property string $name
* @property int|string|array|null $data * @property int|string|array|null $data
* @property-read User $user * @property-read User $user
* @method static Builder|Preference newModelQuery() * @method static Builder|Preference newModelQuery()
* @method static Builder|Preference newQuery() * @method static Builder|Preference newQuery()
* @method static Builder|Preference query() * @method static Builder|Preference query()

View File

@ -22,6 +22,7 @@
declare(strict_types=1); declare(strict_types=1);
namespace FireflyIII\Models; namespace FireflyIII\Models;
use Eloquent; use Eloquent;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
@ -37,10 +38,10 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/** /**
* FireflyIII\Models\Recurrence * FireflyIII\Models\Recurrence
* *
* @property int $id * @property int $id
* @property Carbon|null $created_at * @property Carbon|null $created_at
* @property Carbon|null $updated_at * @property Carbon|null $updated_at
* @property Carbon|null $deleted_at * @property Carbon|null $deleted_at
* @property int $user_id * @property int $user_id
* @property int $transaction_type_id * @property int $transaction_type_id
* @property string $title * @property string $title
@ -89,6 +90,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class Recurrence extends Model class Recurrence extends Model
{ {
use SoftDeletes; use SoftDeletes;
/** /**
* The attributes that should be casted to native types. * The attributes that should be casted to native types.
* *
@ -120,13 +122,13 @@ class Recurrence extends Model
* *
* @param string $value * @param string $value
* *
* @throws NotFoundHttpException
* @return Recurrence * @return Recurrence
* @throws NotFoundHttpException
*/ */
public static function routeBinder(string $value): Recurrence public static function routeBinder(string $value): Recurrence
{ {
if (auth()->check()) { if (auth()->check()) {
$recurrenceId = (int) $value; $recurrenceId = (int)$value;
/** @var User $user */ /** @var User $user */
$user = auth()->user(); $user = auth()->user();
/** @var Recurrence $recurrence */ /** @var Recurrence $recurrence */
@ -138,6 +140,15 @@ class Recurrence extends Model
throw new NotFoundHttpException; throw new NotFoundHttpException;
} }
/**
* @codeCoverageIgnore
* @return MorphMany
*/
public function attachments(): MorphMany
{
return $this->morphMany(Attachment::class, 'attachable');
}
/** /**
* @codeCoverageIgnore * @codeCoverageIgnore
* Get all of the notes. * Get all of the notes.
@ -183,15 +194,6 @@ class Recurrence extends Model
return $this->belongsTo(TransactionCurrency::class); return $this->belongsTo(TransactionCurrency::class);
} }
/**
* @codeCoverageIgnore
* @return MorphMany
*/
public function attachments(): MorphMany
{
return $this->morphMany(Attachment::class, 'attachable');
}
/** /**
* @codeCoverageIgnore * @codeCoverageIgnore
* @return BelongsTo * @return BelongsTo

View File

@ -22,6 +22,7 @@
declare(strict_types=1); declare(strict_types=1);
namespace FireflyIII\Models; namespace FireflyIII\Models;
use Eloquent; use Eloquent;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
@ -58,6 +59,7 @@ use Illuminate\Support\Carbon;
class RecurrenceMeta extends Model class RecurrenceMeta extends Model
{ {
use SoftDeletes; use SoftDeletes;
/** /**
* The attributes that should be casted to native types. * The attributes that should be casted to native types.
* *

View File

@ -22,20 +22,21 @@
declare(strict_types=1); declare(strict_types=1);
namespace FireflyIII\Models; namespace FireflyIII\Models;
use Carbon\Carbon;
use Eloquent; use Eloquent;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Query\Builder; use Illuminate\Database\Query\Builder;
use Illuminate\Support\Carbon;
/** /**
* FireflyIII\Models\RecurrenceRepetition * FireflyIII\Models\RecurrenceRepetition
* *
* @property int $id * @property int $id
* @property \Illuminate\Support\Carbon|null $created_at * @property Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at * @property Carbon|null $updated_at
* @property \Illuminate\Support\Carbon|null $deleted_at * @property Carbon|null $deleted_at
* @property int $recurrence_id * @property int $recurrence_id
* @property string $repetition_type * @property string $repetition_type
* @property string $repetition_moment * @property string $repetition_moment
@ -70,6 +71,7 @@ class RecurrenceRepetition extends Model
/** @var int */ /** @var int */
public const WEEKEND_TO_MONDAY = 4; public const WEEKEND_TO_MONDAY = 4;
use SoftDeletes; use SoftDeletes;
/** /**
* The attributes that should be casted to native types. * The attributes that should be casted to native types.
* *

View File

@ -22,20 +22,21 @@
declare(strict_types=1); declare(strict_types=1);
namespace FireflyIII\Models; namespace FireflyIII\Models;
use Eloquent; use Eloquent;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Query\Builder; use Illuminate\Database\Query\Builder;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
/** /**
* FireflyIII\Models\RecurrenceTransaction * FireflyIII\Models\RecurrenceTransaction
* *
* @property int $id * @property int $id
* @property Carbon|null $created_at * @property Carbon|null $created_at
* @property Carbon|null $updated_at * @property Carbon|null $updated_at
* @property Carbon|null $deleted_at * @property Carbon|null $deleted_at
* @property int $recurrence_id * @property int $recurrence_id
@ -49,7 +50,7 @@ use Illuminate\Support\Collection;
* @property-read Account $destinationAccount * @property-read Account $destinationAccount
* @property-read TransactionCurrency|null $foreignCurrency * @property-read TransactionCurrency|null $foreignCurrency
* @property-read Recurrence $recurrence * @property-read Recurrence $recurrence
* @property-read \Illuminate\Database\Eloquent\Collection|RecurrenceTransactionMeta[] $recurrenceTransactionMeta * @property-read Collection|RecurrenceTransactionMeta[] $recurrenceTransactionMeta
* @property-read int|null $recurrence_transaction_meta_count * @property-read int|null $recurrence_transaction_meta_count
* @property-read Account $sourceAccount * @property-read Account $sourceAccount
* @property-read TransactionCurrency $transactionCurrency * @property-read TransactionCurrency $transactionCurrency
@ -79,6 +80,7 @@ use Illuminate\Support\Collection;
class RecurrenceTransaction extends Model class RecurrenceTransaction extends Model
{ {
use SoftDeletes; use SoftDeletes;
/** /**
* The attributes that should be casted to native types. * The attributes that should be casted to native types.
* *
@ -153,6 +155,7 @@ class RecurrenceTransaction extends Model
{ {
return $this->belongsTo(TransactionCurrency::class); return $this->belongsTo(TransactionCurrency::class);
} }
/** /**
* @codeCoverageIgnore * @codeCoverageIgnore
* @return BelongsTo * @return BelongsTo

View File

@ -22,6 +22,7 @@
declare(strict_types=1); declare(strict_types=1);
namespace FireflyIII\Models; namespace FireflyIII\Models;
use Eloquent; use Eloquent;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
@ -58,6 +59,7 @@ use Illuminate\Support\Carbon;
class RecurrenceTransactionMeta extends Model class RecurrenceTransactionMeta extends Model
{ {
use SoftDeletes; use SoftDeletes;
/** /**
* The attributes that should be casted to native types. * The attributes that should be casted to native types.
* *

View File

@ -36,25 +36,25 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/** /**
* FireflyIII\Models\Rule * FireflyIII\Models\Rule
* *
* @property int $id * @property int $id
* @property Carbon|null $created_at * @property Carbon|null $created_at
* @property Carbon|null $updated_at * @property Carbon|null $updated_at
* @property Carbon|null $deleted_at * @property Carbon|null $deleted_at
* @property int $user_id * @property int $user_id
* @property int $rule_group_id * @property int $rule_group_id
* @property string $title * @property string $title
* @property string|null $description * @property string|null $description
* @property int $order * @property int $order
* @property bool $active * @property bool $active
* @property bool $stop_processing * @property bool $stop_processing
* @property bool $strict * @property bool $strict
* @property-read string $action_value * @property-read string $action_value
* @property-read Collection|RuleAction[] $ruleActions * @property-read Collection|RuleAction[] $ruleActions
* @property-read int|null $rule_actions_count * @property-read int|null $rule_actions_count
* @property-read RuleGroup $ruleGroup * @property-read RuleGroup $ruleGroup
* @property Collection|RuleTrigger[] $ruleTriggers * @property Collection|RuleTrigger[] $ruleTriggers
* @property-read int|null $rule_triggers_count * @property-read int|null $rule_triggers_count
* @property-read User $user * @property-read User $user
* @method static \Illuminate\Database\Eloquent\Builder|Rule newModelQuery() * @method static \Illuminate\Database\Eloquent\Builder|Rule newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|Rule newQuery() * @method static \Illuminate\Database\Eloquent\Builder|Rule newQuery()
* @method static Builder|Rule onlyTrashed() * @method static Builder|Rule onlyTrashed()

View File

@ -22,18 +22,18 @@ declare(strict_types=1);
namespace FireflyIII\Models; namespace FireflyIII\Models;
use Carbon\Carbon;
use Eloquent; use Eloquent;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Carbon;
/** /**
* FireflyIII\Models\RuleAction * FireflyIII\Models\RuleAction
* *
* @property int $id * @property int $id
* @property \Illuminate\Support\Carbon|null $created_at * @property Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at * @property Carbon|null $updated_at
* @property int $rule_id * @property int $rule_id
* @property string $action_type * @property string $action_type
* @property string $action_value * @property string $action_value

View File

@ -22,30 +22,31 @@ declare(strict_types=1);
namespace FireflyIII\Models; namespace FireflyIII\Models;
use Carbon\Carbon;
use Eloquent; use Eloquent;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Query\Builder; use Illuminate\Database\Query\Builder;
use Illuminate\Support\Collection; use Illuminate\Support\Carbon;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/** /**
* FireflyIII\Models\RuleGroup * FireflyIII\Models\RuleGroup
* *
* @property int $id * @property int $id
* @property \Illuminate\Support\Carbon|null $created_at * @property Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at * @property Carbon|null $updated_at
* @property \Illuminate\Support\Carbon|null $deleted_at * @property Carbon|null $deleted_at
* @property int $user_id * @property int $user_id
* @property string $title * @property string $title
* @property string|null $description * @property string|null $description
* @property int $order * @property int $order
* @property bool $active * @property bool $active
* @property bool $stop_processing * @property bool $stop_processing
* @property \Illuminate\Database\Eloquent\Collection|Rule[] $rules * @property Collection|Rule[] $rules
* @property-read int|null $rules_count * @property-read int|null $rules_count
* @property-read User $user * @property-read User $user
* @method static \Illuminate\Database\Eloquent\Builder|RuleGroup newModelQuery() * @method static \Illuminate\Database\Eloquent\Builder|RuleGroup newModelQuery()
@ -69,6 +70,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class RuleGroup extends Model class RuleGroup extends Model
{ {
use SoftDeletes; use SoftDeletes;
/** /**
* The attributes that should be casted to native types. * The attributes that should be casted to native types.
* *
@ -92,13 +94,13 @@ class RuleGroup extends Model
* *
* @param string $value * @param string $value
* *
* @throws NotFoundHttpException
* @return RuleGroup * @return RuleGroup
* @throws NotFoundHttpException
*/ */
public static function routeBinder(string $value): RuleGroup public static function routeBinder(string $value): RuleGroup
{ {
if (auth()->check()) { if (auth()->check()) {
$ruleGroupId = (int) $value; $ruleGroupId = (int)$value;
/** @var User $user */ /** @var User $user */
$user = auth()->user(); $user = auth()->user();
/** @var RuleGroup $ruleGroup */ /** @var RuleGroup $ruleGroup */

View File

@ -22,18 +22,18 @@ declare(strict_types=1);
namespace FireflyIII\Models; namespace FireflyIII\Models;
use Carbon\Carbon;
use Eloquent; use Eloquent;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Carbon;
/** /**
* FireflyIII\Models\RuleTrigger * FireflyIII\Models\RuleTrigger
* *
* @property int $id * @property int $id
* @property \Illuminate\Support\Carbon|null $created_at * @property Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at * @property Carbon|null $updated_at
* @property int $rule_id * @property int $rule_id
* @property string $trigger_type * @property string $trigger_type
* @property string $trigger_value * @property string $trigger_value

View File

@ -35,33 +35,33 @@ use Illuminate\Database\Eloquent\SoftDeletes;
/** /**
* FireflyIII\Models\Transaction * FireflyIII\Models\Transaction
* *
* @property int $id * @property int $id
* @property \Illuminate\Support\Carbon|null $created_at * @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at * @property \Illuminate\Support\Carbon|null $updated_at
* @property \Illuminate\Support\Carbon|null $deleted_at * @property \Illuminate\Support\Carbon|null $deleted_at
* @property bool $reconciled * @property bool $reconciled
* @property int $account_id * @property int $account_id
* @property int $transaction_journal_id * @property int $transaction_journal_id
* @property string|null $description * @property string|null $description
* @property int|null $transaction_currency_id * @property int|null $transaction_currency_id
* @property string $modified * @property string $modified
* @property string $modified_foreign * @property string $modified_foreign
* @property string $date * @property string $date
* @property string $max_date * @property string $max_date
* @property string $amount * @property string $amount
* @property string|null $foreign_amount * @property string|null $foreign_amount
* @property int|null $foreign_currency_id * @property int|null $foreign_currency_id
* @property int $identifier * @property int $identifier
* @property-read \FireflyIII\Models\Account $account * @property-read Account $account
* @property-read Collection|\FireflyIII\Models\Budget[] $budgets * @property-read Collection|Budget[] $budgets
* @property-read int|null $budgets_count * @property-read int|null $budgets_count
* @property-read Collection|\FireflyIII\Models\Category[] $categories * @property-read Collection|Category[] $categories
* @property-read int|null $categories_count * @property-read int|null $categories_count
* @property-read \FireflyIII\Models\TransactionCurrency|null $foreignCurrency * @property-read TransactionCurrency|null $foreignCurrency
* @property-read \FireflyIII\Models\TransactionCurrency|null $transactionCurrency * @property-read TransactionCurrency|null $transactionCurrency
* @property-read \FireflyIII\Models\TransactionJournal $transactionJournal * @property-read TransactionJournal $transactionJournal
* @method static Builder|Transaction after(\Carbon\Carbon $date) * @method static Builder|Transaction after(Carbon $date)
* @method static Builder|Transaction before(\Carbon\Carbon $date) * @method static Builder|Transaction before(Carbon $date)
* @method static Builder|Transaction newModelQuery() * @method static Builder|Transaction newModelQuery()
* @method static Builder|Transaction newQuery() * @method static Builder|Transaction newQuery()
* @method static \Illuminate\Database\Query\Builder|Transaction onlyTrashed() * @method static \Illuminate\Database\Query\Builder|Transaction onlyTrashed()
@ -83,11 +83,12 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @method static \Illuminate\Database\Query\Builder|Transaction withTrashed() * @method static \Illuminate\Database\Query\Builder|Transaction withTrashed()
* @method static \Illuminate\Database\Query\Builder|Transaction withoutTrashed() * @method static \Illuminate\Database\Query\Builder|Transaction withoutTrashed()
* @mixin Eloquent * @mixin Eloquent
* @property int $the_count * @property int $the_count
*/ */
class Transaction extends Model class Transaction extends Model
{ {
use SoftDeletes, HasFactory; use SoftDeletes, HasFactory;
/** /**
* The attributes that should be casted to native types. * The attributes that should be casted to native types.
* *
@ -110,30 +111,6 @@ class Transaction extends Model
/** @var array Hidden from view */ /** @var array Hidden from view */
protected $hidden = ['encrypted']; protected $hidden = ['encrypted'];
/**
* Check if a table is joined.
*
* @param Builder $query
* @param string $table
*
* @return bool
* @codeCoverageIgnore
*/
public static function isJoined(Builder $query, string $table): bool
{
$joins = $query->getQuery()->joins;
if (null === $joins) {
return false;
}
foreach ($joins as $join) {
if ($join->table === $table) {
return true;
}
}
return false;
}
/** /**
* Get the account this object belongs to. * Get the account this object belongs to.
* *
@ -194,6 +171,30 @@ class Transaction extends Model
$query->where('transaction_journals.date', '>=', $date->format('Y-m-d 00:00:00')); $query->where('transaction_journals.date', '>=', $date->format('Y-m-d 00:00:00'));
} }
/**
* Check if a table is joined.
*
* @param Builder $query
* @param string $table
*
* @return bool
* @codeCoverageIgnore
*/
public static function isJoined(Builder $query, string $table): bool
{
$joins = $query->getQuery()->joins;
if (null === $joins) {
return false;
}
foreach ($joins as $join) {
if ($join->table === $table) {
return true;
}
}
return false;
}
/** /**
* Check for transactions BEFORE the specified date. * Check for transactions BEFORE the specified date.
* *
@ -235,7 +236,7 @@ class Transaction extends Model
*/ */
public function setAmountAttribute($value): void public function setAmountAttribute($value): void
{ {
$this->attributes['amount'] = (string) $value; $this->attributes['amount'] = (string)$value;
} }
/** /**

View File

@ -22,21 +22,22 @@ declare(strict_types=1);
namespace FireflyIII\Models; namespace FireflyIII\Models;
use Carbon\Carbon;
use Eloquent; use Eloquent;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Query\Builder; use Illuminate\Database\Query\Builder;
use Illuminate\Support\Carbon;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/** /**
* FireflyIII\Models\TransactionCurrency * FireflyIII\Models\TransactionCurrency
* *
* @property int $id * @property int $id
* @property \Illuminate\Support\Carbon|null $created_at * @property Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at * @property Carbon|null $updated_at
* @property \Illuminate\Support\Carbon|null $deleted_at * @property Carbon|null $deleted_at
* @property bool $enabled * @property bool $enabled
* @property string $code * @property string $code
* @property string $name * @property string $name
@ -90,13 +91,13 @@ class TransactionCurrency extends Model
* *
* @param string $value * @param string $value
* *
* @throws NotFoundHttpException
* @return TransactionCurrency * @return TransactionCurrency
* @throws NotFoundHttpException
*/ */
public static function routeBinder(string $value): TransactionCurrency public static function routeBinder(string $value): TransactionCurrency
{ {
if (auth()->check()) { if (auth()->check()) {
$currencyId = (int) $value; $currencyId = (int)$value;
$currency = self::find($currencyId); $currency = self::find($currencyId);
if (null !== $currency) { if (null !== $currency) {
return $currency; return $currency;

View File

@ -32,6 +32,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Query\Builder; use Illuminate\Database\Query\Builder;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/** /**
* FireflyIII\Models\TransactionGroup * FireflyIII\Models\TransactionGroup
* *
@ -85,13 +86,13 @@ class TransactionGroup extends Model
* *
* @param string $value * @param string $value
* *
* @throws NotFoundHttpException
* @return TransactionGroup * @return TransactionGroup
* @throws NotFoundHttpException
*/ */
public static function routeBinder(string $value): TransactionGroup public static function routeBinder(string $value): TransactionGroup
{ {
if (auth()->check()) { if (auth()->check()) {
$groupId = (int) $value; $groupId = (int)$value;
/** @var User $user */ /** @var User $user */
$user = auth()->user(); $user = auth()->user();
/** @var TransactionGroup $group */ /** @var TransactionGroup $group */

View File

@ -28,6 +28,7 @@ use FireflyIII\Exceptions\FireflyException;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder; use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
@ -40,52 +41,52 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/** /**
* FireflyIII\Models\TransactionJournal * FireflyIII\Models\TransactionJournal
* *
* @property int $id * @property int $id
* @property \Carbon\Carbon|null $created_at * @property Carbon|null $created_at
* @property \Carbon\Carbon|null $updated_at * @property Carbon|null $updated_at
* @property \Carbon\Carbon|null $deleted_at * @property Carbon|null $deleted_at
* @property int $user_id * @property int $user_id
* @property int $transaction_type_id * @property int $transaction_type_id
* @property int|null $transaction_group_id * @property int|null $transaction_group_id
* @property int|null $bill_id * @property int|null $bill_id
* @property int|null $transaction_currency_id * @property int|null $transaction_currency_id
* @property string $description * @property string $description
* @property \Carbon\Carbon $date * @property Carbon $date
* @property \Carbon\Carbon|null $interest_date * @property Carbon|null $interest_date
* @property \Carbon\Carbon|null $book_date * @property Carbon|null $book_date
* @property \Carbon\Carbon|null $process_date * @property Carbon|null $process_date
* @property int $order * @property int $order
* @property int $tag_count * @property int $tag_count
* @property string $transaction_type_type * @property string $transaction_type_type
* @property bool $encrypted * @property bool $encrypted
* @property bool $completed * @property bool $completed
* @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Attachment[] $attachments * @property-read Collection|Attachment[] $attachments
* @property-read int|null $attachments_count * @property-read int|null $attachments_count
* @property-read \FireflyIII\Models\Bill|null $bill * @property-read Bill|null $bill
* @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Budget[] $budgets * @property-read Collection|Budget[] $budgets
* @property-read int|null $budgets_count * @property-read int|null $budgets_count
* @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Category[] $categories * @property-read Collection|Category[] $categories
* @property-read int|null $categories_count * @property-read int|null $categories_count
* @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\TransactionJournalLink[] $destJournalLinks * @property-read Collection|TransactionJournalLink[] $destJournalLinks
* @property-read int|null $dest_journal_links_count * @property-read int|null $dest_journal_links_count
* @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Note[] $notes * @property-read Collection|Note[] $notes
* @property-read int|null $notes_count * @property-read int|null $notes_count
* @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\PiggyBankEvent[] $piggyBankEvents * @property-read Collection|PiggyBankEvent[] $piggyBankEvents
* @property-read int|null $piggy_bank_events_count * @property-read int|null $piggy_bank_events_count
* @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\TransactionJournalLink[] $sourceJournalLinks * @property-read Collection|TransactionJournalLink[] $sourceJournalLinks
* @property-read int|null $source_journal_links_count * @property-read int|null $source_journal_links_count
* @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Tag[] $tags * @property-read Collection|Tag[] $tags
* @property-read int|null $tags_count * @property-read int|null $tags_count
* @property-read \FireflyIII\Models\TransactionCurrency|null $transactionCurrency * @property-read TransactionCurrency|null $transactionCurrency
* @property-read \FireflyIII\Models\TransactionGroup|null $transactionGroup * @property-read TransactionGroup|null $transactionGroup
* @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\TransactionJournalMeta[] $transactionJournalMeta * @property-read Collection|TransactionJournalMeta[] $transactionJournalMeta
* @property-read int|null $transaction_journal_meta_count * @property-read int|null $transaction_journal_meta_count
* @property-read \FireflyIII\Models\TransactionType $transactionType * @property-read TransactionType $transactionType
* @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Transaction[] $transactions * @property-read Collection|Transaction[] $transactions
* @property-read int|null $transactions_count * @property-read int|null $transactions_count
* @property-read User $user * @property-read User $user
* @method static EloquentBuilder|TransactionJournal after(\Carbon\Carbon $date) * @method static EloquentBuilder|TransactionJournal after(Carbon $date)
* @method static EloquentBuilder|TransactionJournal before(\Carbon\Carbon $date) * @method static EloquentBuilder|TransactionJournal before(Carbon $date)
* @method static EloquentBuilder|TransactionJournal newModelQuery() * @method static EloquentBuilder|TransactionJournal newModelQuery()
* @method static EloquentBuilder|TransactionJournal newQuery() * @method static EloquentBuilder|TransactionJournal newQuery()
* @method static \Illuminate\Database\Query\Builder|TransactionJournal onlyTrashed() * @method static \Illuminate\Database\Query\Builder|TransactionJournal onlyTrashed()
@ -112,9 +113,9 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
* @method static \Illuminate\Database\Query\Builder|TransactionJournal withTrashed() * @method static \Illuminate\Database\Query\Builder|TransactionJournal withTrashed()
* @method static \Illuminate\Database\Query\Builder|TransactionJournal withoutTrashed() * @method static \Illuminate\Database\Query\Builder|TransactionJournal withoutTrashed()
* @mixin Eloquent * @mixin Eloquent
* @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Location[] $locations * @property-read Collection|Location[] $locations
* @property-read int|null $locations_count * @property-read int|null $locations_count
* @property int $the_count * @property int $the_count
*/ */
class TransactionJournal extends Model class TransactionJournal extends Model
{ {
@ -147,31 +148,6 @@ class TransactionJournal extends Model
/** @var array Hidden from view */ /** @var array Hidden from view */
protected $hidden = ['encrypted']; protected $hidden = ['encrypted'];
/**
* Checks if tables are joined.
*
* @codeCoverageIgnore
*
* @param Builder $query
* @param string $table
*
* @return bool
*/
public static function isJoined(Builder $query, string $table): bool
{
$joins = $query->getQuery()->joins;
if (null === $joins) {
return false;
}
foreach ($joins as $join) {
if ($join->table === $table) {
return true;
}
}
return false;
}
/** /**
* Route binder. Converts the key in the URL to the specified object (or throw 404). * Route binder. Converts the key in the URL to the specified object (or throw 404).
* *
@ -215,15 +191,6 @@ class TransactionJournal extends Model
return $this->belongsTo(Bill::class); return $this->belongsTo(Bill::class);
} }
/**
* @codeCoverageIgnore
* @return MorphMany
*/
public function locations(): MorphMany
{
return $this->morphMany(Location::class, 'locatable');
}
/** /**
* @codeCoverageIgnore * @codeCoverageIgnore
* @return BelongsToMany * @return BelongsToMany
@ -264,6 +231,15 @@ class TransactionJournal extends Model
return $this->transactionType->isTransfer(); return $this->transactionType->isTransfer();
} }
/**
* @codeCoverageIgnore
* @return MorphMany
*/
public function locations(): MorphMany
{
return $this->morphMany(Location::class, 'locatable');
}
/** /**
* @codeCoverageIgnore * @codeCoverageIgnore
* Get all of the notes. * Get all of the notes.
@ -324,6 +300,31 @@ class TransactionJournal extends Model
} }
} }
/**
* Checks if tables are joined.
*
* @codeCoverageIgnore
*
* @param Builder $query
* @param string $table
*
* @return bool
*/
public static function isJoined(Builder $query, string $table): bool
{
$joins = $query->getQuery()->joins;
if (null === $joins) {
return false;
}
foreach ($joins as $join) {
if ($join->table === $table) {
return true;
}
}
return false;
}
/** /**
* @codeCoverageIgnore * @codeCoverageIgnore
* @return HasMany * @return HasMany

View File

@ -22,21 +22,21 @@ declare(strict_types=1);
namespace FireflyIII\Models; namespace FireflyIII\Models;
use Carbon\Carbon;
use Eloquent; use Eloquent;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphMany; use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Support\Carbon;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/** /**
* FireflyIII\Models\TransactionJournalLink * FireflyIII\Models\TransactionJournalLink
* *
* @property int $id * @property int $id
* @property \Illuminate\Support\Carbon|null $created_at * @property Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at * @property Carbon|null $updated_at
* @property int $link_type_id * @property int $link_type_id
* @property int $source_id * @property int $source_id
* @property int $destination_id * @property int $destination_id
@ -80,14 +80,14 @@ class TransactionJournalLink extends Model
* *
* @param string $value * @param string $value
* *
* @throws NotFoundHttpException
* @return TransactionJournalLink * @return TransactionJournalLink
* *
* @throws NotFoundHttpException
*/ */
public static function routeBinder(string $value): TransactionJournalLink public static function routeBinder(string $value): TransactionJournalLink
{ {
if (auth()->check()) { if (auth()->check()) {
$linkId = (int) $value; $linkId = (int)$value;
$link = self::where('journal_links.id', $linkId) $link = self::where('journal_links.id', $linkId)
->leftJoin('transaction_journals as t_a', 't_a.id', '=', 'source_id') ->leftJoin('transaction_journals as t_a', 't_a.id', '=', 'source_id')
->leftJoin('transaction_journals as t_b', 't_b.id', '=', 'destination_id') ->leftJoin('transaction_journals as t_b', 't_b.id', '=', 'destination_id')

View File

@ -60,6 +60,7 @@ use Illuminate\Support\Carbon;
class TransactionJournalMeta extends Model class TransactionJournalMeta extends Model
{ {
use SoftDeletes; use SoftDeletes;
/** /**
* The attributes that should be casted to native types. * The attributes that should be casted to native types.
* *

46
app/Models/UserGroup.php Normal file
View File

@ -0,0 +1,46 @@
<?php
/*
* UserGroup.php
* Copyright (c) 2021 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
/**
* Class UserGroup
*/
class UserGroup extends Model
{
protected $fillable = ['title'];
/**
* @codeCoverageIgnore
*
* @return HasMany
*/
public function groupMemberships(): HasMany
{
return $this->hasMany(GroupMembership::class);
}
}

54
app/Models/UserRole.php Normal file
View File

@ -0,0 +1,54 @@
<?php
/*
* UserRole.php
* Copyright (c) 2021 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
/**
* Class UserRole
*/
class UserRole extends Model
{
public const READ_ONLY = 'ro';
public const CHANGE_TRANSACTIONS = 'change_tx';
public const CHANGE_RULES = 'change_rules';
public const CHANGE_PIGGY_BANKS = 'change_piggies';
public const CHANGE_REPETITIONS = 'change_reps';
public const VIEW_REPORTS = 'view_reports';
public const FULL = 'full';
public const OWNER = 'owner';
protected $fillable = ['title'];
/**
* @codeCoverageIgnore
*
* @return HasMany
*/
public function groupMemberships(): HasMany
{
return $this->hasMany(GroupMembership::class);
}
}

View File

@ -22,6 +22,7 @@
declare(strict_types=1); declare(strict_types=1);
namespace FireflyIII\Models; namespace FireflyIII\Models;
use Eloquent; use Eloquent;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
@ -36,7 +37,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/** /**
* FireflyIII\Models\Webhook * FireflyIII\Models\Webhook
* *
* @property int $id * @property int $id
* @property Carbon|null $created_at * @property Carbon|null $created_at
* @property Carbon|null $updated_at * @property Carbon|null $updated_at
* @property Carbon|null $deleted_at * @property Carbon|null $deleted_at
@ -66,8 +67,8 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
* @method static \Illuminate\Database\Query\Builder|Webhook withTrashed() * @method static \Illuminate\Database\Query\Builder|Webhook withTrashed()
* @method static \Illuminate\Database\Query\Builder|Webhook withoutTrashed() * @method static \Illuminate\Database\Query\Builder|Webhook withoutTrashed()
* @mixin Eloquent * @mixin Eloquent
* @property string $title * @property string $title
* @property string $secret * @property string $secret
* @method static Builder|Webhook whereSecret($value) * @method static Builder|Webhook whereSecret($value)
* @method static Builder|Webhook whereTitle($value) * @method static Builder|Webhook whereTitle($value)
*/ */
@ -88,9 +89,6 @@ class Webhook extends Model
// delivery // delivery
public const DELIVERY_JSON = 300; public const DELIVERY_JSON = 300;
protected $fillable = ['active', 'trigger', 'response', 'delivery', 'user_id', 'url', 'title', 'secret'];
protected $casts protected $casts
= [ = [
'active' => 'boolean', 'active' => 'boolean',
@ -98,6 +96,7 @@ class Webhook extends Model
'response' => 'integer', 'response' => 'integer',
'delivery' => 'integer', 'delivery' => 'integer',
]; ];
protected $fillable = ['active', 'trigger', 'response', 'delivery', 'user_id', 'url', 'title', 'secret'];
/** /**
* Route binder. Converts the key in the URL to the specified object (or throw 404). * Route binder. Converts the key in the URL to the specified object (or throw 404).

View File

@ -64,15 +64,6 @@ class WebhookAttempt extends Model
{ {
use SoftDeletes; use SoftDeletes;
/**
* @codeCoverageIgnore
* @return BelongsTo
*/
public function webhookMessage(): BelongsTo
{
return $this->belongsTo(WebhookMessage::class);
}
/** /**
* Route binder. Converts the key in the URL to the specified object (or throw 404). * Route binder. Converts the key in the URL to the specified object (or throw 404).
* *
@ -95,4 +86,13 @@ class WebhookAttempt extends Model
} }
throw new NotFoundHttpException; throw new NotFoundHttpException;
} }
/**
* @codeCoverageIgnore
* @return BelongsTo
*/
public function webhookMessage(): BelongsTo
{
return $this->belongsTo(WebhookMessage::class);
}
} }

View File

@ -36,7 +36,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/** /**
* FireflyIII\Models\WebhookMessage * FireflyIII\Models\WebhookMessage
* *
* @property int $id * @property int $id
* @property Carbon|null $created_at * @property Carbon|null $created_at
* @property Carbon|null $updated_at * @property Carbon|null $updated_at
* @property string|null $deleted_at * @property string|null $deleted_at

View File

@ -68,6 +68,7 @@ class EventServiceProvider extends ServiceProvider
RegisteredUser::class => [ RegisteredUser::class => [
'FireflyIII\Handlers\Events\UserEventHandler@sendRegistrationMail', 'FireflyIII\Handlers\Events\UserEventHandler@sendRegistrationMail',
'FireflyIII\Handlers\Events\UserEventHandler@attachUserRole', 'FireflyIII\Handlers\Events\UserEventHandler@attachUserRole',
'FireflyIII\Handlers\Events\UserEventHandler@createGroupMembership',
], ],
// is a User related event. // is a User related event.
Login::class => [ Login::class => [

View File

@ -79,6 +79,14 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface
} }
/**
* @inheritDoc
*/
public function findById(int $id): ?AvailableBudget
{
return $this->user->availableBudgets->find($id);
}
/** /**
* Return a list of all available budgets (in all currencies) (for the selected period). * Return a list of all available budgets (in all currencies) (for the selected period).
* *

View File

@ -56,6 +56,13 @@ interface AvailableBudgetRepositoryInterface
*/ */
public function find(TransactionCurrency $currency, Carbon $start, Carbon $end): ?AvailableBudget; public function find(TransactionCurrency $currency, Carbon $start, Carbon $end): ?AvailableBudget;
/**
* @param int $id
*
* @return AvailableBudget|null
*/
public function findById(int $id): ?AvailableBudget;
/** /**
* Return a list of all available budgets (in all currencies) (for the selected period). * Return a list of all available budgets (in all currencies) (for the selected period).
* *

View File

@ -22,6 +22,7 @@
declare(strict_types=1); declare(strict_types=1);
namespace FireflyIII\Repositories\Budget; namespace FireflyIII\Repositories\Budget;
use Carbon\Carbon; use Carbon\Carbon;
use Exception; use Exception;
use FireflyIII\Exceptions\FireflyException; use FireflyIII\Exceptions\FireflyException;
@ -88,6 +89,7 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
) )
->where('budget_limits.transaction_currency_id', $currency->id) ->where('budget_limits.transaction_currency_id', $currency->id)
->whereNull('budgets.deleted_at') ->whereNull('budgets.deleted_at')
->where('budgets.active', true)
->where('budgets.user_id', $this->user->id); ->where('budgets.user_id', $this->user->id);
if (null !== $budgets && $budgets->count() > 0) { if (null !== $budgets && $budgets->count() > 0) {
$query->whereIn('budget_limits.budget_id', $budgets->pluck('id')->toArray()); $query->whereIn('budget_limits.budget_id', $budgets->pluck('id')->toArray());
@ -318,7 +320,7 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
// find the budget: // find the budget:
$budget = $this->user->budgets()->find((int)$data['budget_id']); $budget = $this->user->budgets()->find((int)$data['budget_id']);
if (null === $budget) { if (null === $budget) {
throw new FireflyException('200004: Budget does not exist.'); throw new FireflyException('200004: Budget does not exist.');
} }
// find limit with same date range and currency. // find limit with same date range and currency.
@ -328,7 +330,7 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
->where('budget_limits.transaction_currency_id', $currency->id) ->where('budget_limits.transaction_currency_id', $currency->id)
->first(['budget_limits.*']); ->first(['budget_limits.*']);
if (null !== $limit) { if (null !== $limit) {
throw new FireflyException('200027: Budget limit already exists.'); throw new FireflyException('200027: Budget limit already exists.');
} }
Log::debug('No existing budget limit, create a new one'); Log::debug('No existing budget limit, create a new one');
@ -424,6 +426,7 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
} catch (Exception $e) { // @phpstan-ignore-line } catch (Exception $e) { // @phpstan-ignore-line
// @ignoreException // @ignoreException
} }
return null; return null;
} }
// update if exists: // update if exists:

View File

@ -117,6 +117,18 @@ class BudgetRepository implements BudgetRepositoryInterface
} }
} }
/**
* Find a budget or return NULL
*
* @param int|null $budgetId |null
*
* @return Budget|null
*/
public function find(int $budgetId = null): ?Budget
{
return $this->user->budgets()->find($budgetId);
}
/** /**
* @param int|null $budgetId * @param int|null $budgetId
* @param string|null $budgetName * @param string|null $budgetName
@ -157,18 +169,6 @@ class BudgetRepository implements BudgetRepositoryInterface
return $this->user->budgets()->where('name', 'LIKE', $query)->first(); return $this->user->budgets()->where('name', 'LIKE', $query)->first();
} }
/**
* Find a budget or return NULL
*
* @param int|null $budgetId |null
*
* @return Budget|null
*/
public function find(int $budgetId = null): ?Budget
{
return $this->user->budgets()->find($budgetId);
}
/** /**
* This method returns the oldest journal or transaction date known to this budget. * This method returns the oldest journal or transaction date known to this budget.
* Will cache result. * Will cache result.

View File

@ -56,6 +56,14 @@ interface BudgetRepositoryInterface
*/ */
public function destroyAutoBudget(Budget $budget): void; public function destroyAutoBudget(Budget $budget): void;
/**
*
* @param int|null $budgetId
*
* @return Budget|null
*/
public function find(int $budgetId = null): ?Budget;
/** /**
* @param int|null $budgetId * @param int|null $budgetId
* @param string|null $budgetName * @param string|null $budgetName
@ -73,14 +81,6 @@ interface BudgetRepositoryInterface
*/ */
public function findByName(?string $name): ?Budget; public function findByName(?string $name): ?Budget;
/**
*
* @param int|null $budgetId
*
* @return Budget|null
*/
public function find(int $budgetId = null): ?Budget;
/** /**
* This method returns the oldest journal or transaction date known to this budget. * This method returns the oldest journal or transaction date known to this budget.
* Will cache result. * Will cache result.

View File

@ -22,6 +22,7 @@
declare(strict_types=1); declare(strict_types=1);
namespace FireflyIII\Repositories\Budget; namespace FireflyIII\Repositories\Budget;
use Carbon\Carbon; use Carbon\Carbon;
use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\TransactionCurrency; use FireflyIII\Models\TransactionCurrency;
@ -146,8 +147,8 @@ class NoBudgetRepository implements NoBudgetRepositoryInterface
/** @noinspection MoreThanThreeArgumentsInspection */ /** @noinspection MoreThanThreeArgumentsInspection */
/** /**
* See reference nr. 15 * See reference nr. 15
* See reference nr. 16 * See reference nr. 16
* *
* @param Carbon $start * @param Carbon $start
* @param Carbon $end * @param Carbon $end

View File

@ -22,6 +22,7 @@
declare(strict_types=1); declare(strict_types=1);
namespace FireflyIII\Repositories\Budget; namespace FireflyIII\Repositories\Budget;
use Carbon\Carbon; use Carbon\Carbon;
use FireflyIII\Models\TransactionCurrency; use FireflyIII\Models\TransactionCurrency;
use FireflyIII\User; use FireflyIII\User;

View File

@ -180,7 +180,7 @@ class OperationsRepository implements OperationsRepositoryInterface
// add journal to array: // add journal to array:
// only a subset of the fields. // only a subset of the fields.
$journalId = (int)$journal['transaction_journal_id']; $journalId = (int)$journal['transaction_journal_id'];
$array[$currencyId]['budgets'][$budgetId]['transaction_journals'][$journalId] = [ $array[$currencyId]['budgets'][$budgetId]['transaction_journals'][$journalId] = [
'amount' => app('steam')->negative($journal['amount']), 'amount' => app('steam')->negative($journal['amount']),
'destination_account_id' => $journal['destination_account_id'], 'destination_account_id' => $journal['destination_account_id'],
@ -227,7 +227,7 @@ class OperationsRepository implements OperationsRepositoryInterface
if ($accounts->count() > 0) { if ($accounts->count() > 0) {
$collector->setAccounts($accounts); $collector->setAccounts($accounts);
} }
// See reference nr. 13 // See reference nr. 13
$set = $collector->getGroups(); $set = $collector->getGroups();
$return = []; $return = [];
$total = []; $total = [];
@ -343,23 +343,13 @@ class OperationsRepository implements OperationsRepositoryInterface
$array[$foreignId]['sum'] = bcadd($array[$foreignId]['sum'], app('steam')->negative($journal['foreign_amount'])); $array[$foreignId]['sum'] = bcadd($array[$foreignId]['sum'], app('steam')->negative($journal['foreign_amount']));
} }
} }
return $array; return $array;
} }
/**
* @return Collection
*/
private function getBudgets(): Collection
{
/** @var BudgetRepositoryInterface $repos */
$repos = app(BudgetRepositoryInterface::class);
return $repos->getActiveBudgets();
}
/** /**
* For now, simply refer to whichever repository holds this function. * For now, simply refer to whichever repository holds this function.
* See reference nr. 14 * See reference nr. 14
* *
* @param Budget $budget * @param Budget $budget
* @param Carbon|null $start * @param Carbon|null $start
@ -374,4 +364,15 @@ class OperationsRepository implements OperationsRepositoryInterface
return $blRepository->getBudgetLimits($budget, $start, $end); return $blRepository->getBudgetLimits($budget, $start, $end);
} }
/**
* @return Collection
*/
private function getBudgets(): Collection
{
/** @var BudgetRepositoryInterface $repos */
$repos = app(BudgetRepositoryInterface::class);
return $repos->getActiveBudgets();
}
} }

View File

@ -43,6 +43,7 @@ interface OperationsRepositoryInterface
* @return string * @return string
*/ */
public function budgetedPerDay(Budget $budget): string; public function budgetedPerDay(Budget $budget): string;
/** /**
* @param Collection $budgets * @param Collection $budgets
* @param Collection $accounts * @param Collection $accounts

View File

@ -88,14 +88,15 @@ trait ModifiesPiggyBanks
*/ */
public function canAddAmount(PiggyBank $piggyBank, string $amount): bool public function canAddAmount(PiggyBank $piggyBank, string $amount): bool
{ {
$leftOnAccount = $this->leftOnAccount($piggyBank, today(config('app.timezone'))); $today = today(config('app.timezone'));
$leftOnAccount = $this->leftOnAccount($piggyBank, $today);
$savedSoFar = (string)$this->getRepetition($piggyBank)->currentamount; $savedSoFar = (string)$this->getRepetition($piggyBank)->currentamount;
$leftToSave = bcsub($piggyBank->targetamount, $savedSoFar); $leftToSave = bcsub($piggyBank->targetamount, $savedSoFar);
$maxAmount = 1 === bccomp($leftOnAccount, $leftToSave) ? $leftToSave : $leftOnAccount; $maxAmount = 1 === bccomp($leftOnAccount, $leftToSave) ? $leftToSave : $leftOnAccount;
$compare = bccomp($amount, $maxAmount); $compare = bccomp($amount, $maxAmount);
$result = $compare <= 0; $result = $compare <= 0;
Log::debug(sprintf('Left on account: %s', $leftOnAccount)); Log::debug(sprintf('Left on account: %s on %s', $leftOnAccount, $today->format('Y-m-d')));
Log::debug(sprintf('Saved so far: %s', $savedSoFar)); Log::debug(sprintf('Saved so far: %s', $savedSoFar));
Log::debug(sprintf('Left to save: %s', $leftToSave)); Log::debug(sprintf('Left to save: %s', $leftToSave));
Log::debug(sprintf('Maximum amount: %s', $maxAmount)); Log::debug(sprintf('Maximum amount: %s', $maxAmount));

View File

@ -345,6 +345,10 @@ class OperatorQuerySearch implements SearchInterface
if (null !== $account) { if (null !== $account) {
$this->collector->setSourceAccounts(new Collection([$account])); $this->collector->setSourceAccounts(new Collection([$account]));
} }
if (null === $account) {
// since the source does not exist, cannot return results:
$this->collector->findNothing();
}
break; break;
case 'journal_id': case 'journal_id':
$parts = explode(',', $value); $parts = explode(',', $value);
@ -383,6 +387,9 @@ class OperatorQuerySearch implements SearchInterface
if (null !== $account) { if (null !== $account) {
$this->collector->setDestinationAccounts(new Collection([$account])); $this->collector->setDestinationAccounts(new Collection([$account]));
} }
if (null === $account) {
$this->collector->findNothing();
}
break; break;
case 'account_id': case 'account_id':
$parts = explode(',', $value); $parts = explode(',', $value);
@ -396,6 +403,9 @@ class OperatorQuerySearch implements SearchInterface
if ($collection->count() > 0) { if ($collection->count() > 0) {
$this->collector->setAccounts($collection); $this->collector->setAccounts($collection);
} }
if (0 === $collection->count()) {
$this->collector->findNothing();
}
break; break;
// //
// cash account // cash account
@ -436,12 +446,18 @@ class OperatorQuerySearch implements SearchInterface
if (null !== $currency) { if (null !== $currency) {
$this->collector->setCurrency($currency); $this->collector->setCurrency($currency);
} }
if (null === $currency) {
$this->collector->findNothing();
}
break; break;
case 'foreign_currency_is': case 'foreign_currency_is':
$currency = $this->findCurrency($value); $currency = $this->findCurrency($value);
if (null !== $currency) { if (null !== $currency) {
$this->collector->setForeignCurrency($currency); $this->collector->setForeignCurrency($currency);
} }
if (null === $currency) {
$this->collector->findNothing();
}
break; break;
// //
// attachments // attachments
@ -463,6 +479,9 @@ class OperatorQuerySearch implements SearchInterface
if ($result->count() > 0) { if ($result->count() > 0) {
$this->collector->setCategories($result); $this->collector->setCategories($result);
} }
if (0 === $result->count()) {
$this->collector->findNothing();
}
break; break;
// //
// budgets // budgets
@ -478,6 +497,9 @@ class OperatorQuerySearch implements SearchInterface
if ($result->count() > 0) { if ($result->count() > 0) {
$this->collector->setBudgets($result); $this->collector->setBudgets($result);
} }
if (0 === $result->count()) {
$this->collector->findNothing();
}
break; break;
// //
// bill // bill
@ -493,6 +515,9 @@ class OperatorQuerySearch implements SearchInterface
if ($result->count() > 0) { if ($result->count() > 0) {
$this->collector->setBills($result); $this->collector->setBills($result);
} }
if (0 === $result->count()) {
$this->collector->findNothing();
}
break; break;
// //
// tags // tags
@ -508,6 +533,11 @@ class OperatorQuerySearch implements SearchInterface
if ($result->count() > 0) { if ($result->count() > 0) {
$this->collector->setTags($result); $this->collector->setTags($result);
} }
// no tags found means search must result in nothing.
if (0 === $result->count()) {
Log::info(sprintf('No valid tags in "%s"-operator, so search will not return ANY results.', $operator));
$this->collector->findNothing();
}
break; break;
// //
// notes // notes
@ -698,10 +728,8 @@ class OperatorQuerySearch implements SearchInterface
// get accounts: // get accounts:
$accounts = $this->accountRepository->searchAccount($value, $searchTypes, 25); $accounts = $this->accountRepository->searchAccount($value, $searchTypes, 25);
if (0 === $accounts->count()) { if (0 === $accounts->count()) {
Log::debug('Found zero accounts, search for invalid account.'); Log::debug('Found zero accounts, search for non existing account, NO results will be returned.');
$account = new Account; $this->collector->findNothing();
$account->id = 0;
$this->collector->$collectorMethod(new Collection([$account]));
return; return;
} }
@ -713,10 +741,8 @@ class OperatorQuerySearch implements SearchInterface
); );
if (0 === $filtered->count()) { if (0 === $filtered->count()) {
Log::debug('Left with zero accounts, search for invalid account.'); Log::debug('Left with zero accounts, so cannot find anything, NO results will be returned.');
$account = new Account; $this->collector->findNothing();
$account->id = 0;
$this->collector->$collectorMethod(new Collection([$account]));
return; return;
} }
@ -764,9 +790,7 @@ class OperatorQuerySearch implements SearchInterface
$accounts = $this->accountRepository->searchAccountNr($value, $searchTypes, 25); $accounts = $this->accountRepository->searchAccountNr($value, $searchTypes, 25);
if (0 === $accounts->count()) { if (0 === $accounts->count()) {
Log::debug('Found zero accounts, search for invalid account.'); Log::debug('Found zero accounts, search for invalid account.');
$account = new Account; $this->collector->findNothing();
$account->id = 0;
$this->collector->$collectorMethod(new Collection([$account]));
return; return;
} }
@ -791,9 +815,7 @@ class OperatorQuerySearch implements SearchInterface
if (0 === $filtered->count()) { if (0 === $filtered->count()) {
Log::debug('Left with zero, search for invalid account'); Log::debug('Left with zero, search for invalid account');
$account = new Account; $this->collector->findNothing();
$account->id = 0;
$this->collector->$collectorMethod(new Collection([$account]));
return; return;
} }

View File

@ -57,12 +57,18 @@ class UpdatePiggybank implements ActionInterface
public function actOnArray(array $journal): bool public function actOnArray(array $journal): bool
{ {
Log::debug(sprintf('Triggered rule action UpdatePiggybank on journal #%d', $journal['transaction_journal_id'])); Log::debug(sprintf('Triggered rule action UpdatePiggybank on journal #%d', $journal['transaction_journal_id']));
// refresh the transaction type.
$user = User::find($journal['user_id']);
$journalObj = $user->transactionJournals()->find($journal['transaction_journal_id']);
$type = TransactionType::find((int)$journalObj->transaction_type_id);
$journal['transaction_type_type'] = $type->type;
if (TransactionType::TRANSFER !== $journal['transaction_type_type']) { if (TransactionType::TRANSFER !== $journal['transaction_type_type']) {
Log::info(sprintf('Journal #%d is a "%s" so skip this action.', $journal['transaction_journal_id'], $journal['transaction_type_type'])); Log::info(sprintf('Journal #%d is a "%s" so skip this action.', $journal['transaction_journal_id'], $journal['transaction_type_type']));
return false; return false;
} }
$user = User::find($journal['user_id']);
$piggyBank = $this->findPiggybank($user); $piggyBank = $this->findPiggybank($user);
if (null === $piggyBank) { if (null === $piggyBank) {
@ -92,7 +98,7 @@ class UpdatePiggybank implements ActionInterface
return true; return true;
} }
Log::info('Piggy bank is not linked to source or destination, so no action will be taken.'); Log::info(sprintf('Piggy bank is not linked to source ("#%d") or destination ("#%d"), so no action will be taken.', $source->account_id, $destination->account_id));
return true; return true;
} }

View File

@ -34,6 +34,7 @@ use FireflyIII\Models\Bill;
use FireflyIII\Models\Budget; use FireflyIII\Models\Budget;
use FireflyIII\Models\Category; use FireflyIII\Models\Category;
use FireflyIII\Models\CurrencyExchangeRate; use FireflyIII\Models\CurrencyExchangeRate;
use FireflyIII\Models\GroupMembership;
use FireflyIII\Models\ObjectGroup; use FireflyIII\Models\ObjectGroup;
use FireflyIII\Models\PiggyBank; use FireflyIII\Models\PiggyBank;
use FireflyIII\Models\Preference; use FireflyIII\Models\Preference;
@ -45,8 +46,10 @@ use FireflyIII\Models\Tag;
use FireflyIII\Models\Transaction; use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionGroup; use FireflyIII\Models\TransactionGroup;
use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\UserGroup;
use FireflyIII\Models\Webhook; use FireflyIII\Models\Webhook;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasManyThrough; use Illuminate\Database\Eloquent\Relations\HasManyThrough;
@ -136,14 +139,14 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
* @method static Builder|User whereObjectguid($value) * @method static Builder|User whereObjectguid($value)
* @property string|null $provider * @property string|null $provider
* @method static Builder|User whereProvider($value) * @method static Builder|User whereProvider($value)
* @property-read \Illuminate\Database\Eloquent\Collection|ObjectGroup[] $objectGroups * @property-read \Illuminate\Database\Eloquent\Collection|ObjectGroup[] $objectGroups
* @property-read int|null $object_groups_count * @property-read int|null $object_groups_count
* @property-read \Illuminate\Database\Eloquent\Collection|Webhook[] $webhooks * @property-read \Illuminate\Database\Eloquent\Collection|Webhook[] $webhooks
* @property-read int|null $webhooks_count * @property-read int|null $webhooks_count
* @property string|null $two_factor_secret * @property string|null $two_factor_secret
* @property string|null $two_factor_recovery_codes * @property string|null $two_factor_recovery_codes
* @property string|null $guid * @property string|null $guid
* @property string|null $domain * @property string|null $domain
* @method static Builder|User whereDomain($value) * @method static Builder|User whereDomain($value)
* @method static Builder|User whereGuid($value) * @method static Builder|User whereGuid($value)
* @method static Builder|User whereTwoFactorRecoveryCodes($value) * @method static Builder|User whereTwoFactorRecoveryCodes($value)
@ -212,6 +215,16 @@ class User extends Authenticatable
return $this->hasMany(Account::class); return $this->hasMany(Account::class);
} }
/**
* @codeCoverageIgnore
*
* @return HasMany
*/
public function groupMemberships(): HasMany
{
return $this->hasMany(GroupMembership::class)->with(['userGroup','userRole']);
}
/** /**
* @codeCoverageIgnore * @codeCoverageIgnore
* Link to attachments * Link to attachments
@ -300,6 +313,14 @@ class User extends Authenticatable
return $this->hasMany(Category::class); return $this->hasMany(Category::class);
} }
/**
* @codeCoverageIgnore
* @return BelongsTo
*/
public function userGroup(): BelongsTo
{
return $this->belongsTo(UserGroup::class,);
}
/** /**
* @codeCoverageIgnore * @codeCoverageIgnore
* Link to currency exchange rates * Link to currency exchange rates
@ -449,6 +470,7 @@ class User extends Authenticatable
} }
// start LDAP related code // start LDAP related code
/** /**
* Get the database column name of the domain. * Get the database column name of the domain.
* *

View File

@ -28,7 +28,6 @@ use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Account; use FireflyIII\Models\Account;
use FireflyIII\Models\AccountMeta; use FireflyIII\Models\AccountMeta;
use FireflyIII\Models\AccountType; use FireflyIII\Models\AccountType;
use FireflyIII\Models\PiggyBank;
use FireflyIII\Models\TransactionType; use FireflyIII\Models\TransactionType;
use FireflyIII\Models\Webhook; use FireflyIII\Models\Webhook;
use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Account\AccountRepositoryInterface;
@ -43,6 +42,7 @@ use Google2FA;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Validation\Validator; use Illuminate\Validation\Validator;
use Log; use Log;
use ValueError;
use function is_string; use function is_string;
/** /**
@ -114,6 +114,7 @@ class FireflyValidator extends Validator
* @param mixed $value * @param mixed $value
* *
* @return bool * @return bool
* @throws FireflyException
*/ */
public function validateIban($attribute, $value): bool public function validateIban($attribute, $value): bool
{ {
@ -168,11 +169,15 @@ class FireflyValidator extends Validator
"\u{202F}", // narrow no-break space "\u{202F}", // narrow no-break space
"\u{3000}", // ideographic space "\u{3000}", // ideographic space
"\u{FEFF}", // zero width no -break space "\u{FEFF}", // zero width no -break space
'-',
'?'
]; ];
$replace = ''; $replace = '';
$value = str_replace($search, $replace, $value); $value = str_replace($search, $replace, $value);
$value = strtoupper($value); $value = strtoupper($value);
// replace characters outside of ASCI range.
$value = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $value);
$search = [' ', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']; $search = [' ', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
$replace = ['', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '30', '31', $replace = ['', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '30', '31',
'32', '33', '34', '35',]; '32', '33', '34', '35',];
@ -185,7 +190,14 @@ class FireflyValidator extends Validator
if (0 === strlen($iban)) { if (0 === strlen($iban)) {
return false; return false;
} }
$checksum = bcmod($iban, '97'); try {
$checksum = bcmod($iban, '97');
} catch (ValueError $e) {
$message = sprintf('Could not validate IBAN check value "%s" (IBAN "%s")', $iban, $value);
Log::error($message);
Log::error($e->getTraceAsString());
return false;
}
return 1 === (int)$checksum; return 1 === (int)$checksum;
} }
@ -471,12 +483,13 @@ class FireflyValidator extends Validator
$ignore = (int)($parameters[0] ?? 0.0); $ignore = (int)($parameters[0] ?? 0.0);
$accountTypeIds = $accountTypes->pluck('id')->toArray(); $accountTypeIds = $accountTypes->pluck('id')->toArray();
/** @var Collection $set */ /** @var Collection $set */
$set = auth()->user()->accounts()->whereIn('account_type_id', $accountTypeIds)->where('id', '!=', $ignore)->get(); $set = auth()->user()->accounts()->whereIn('account_type_id', $accountTypeIds)->where('id', '!=', $ignore)->get();
$result = $set->first( $result = $set->first(
function (Account $account) use ($value) { function (Account $account) use ($value) {
return $account->name === $value; return $account->name === $value;
} }
); );
return null === $result; return null === $result;
} }
@ -500,6 +513,7 @@ class FireflyValidator extends Validator
return $account->name === $value; return $account->name === $value;
} }
); );
return null === $result; return null === $result;
} }
@ -717,7 +731,8 @@ class FireflyValidator extends Validator
if (null !== $exclude) { if (null !== $exclude) {
$query->where('piggy_banks.id', '!=', (int)$exclude); $query->where('piggy_banks.id', '!=', (int)$exclude);
} }
$query->where('piggy_banks.name',$value); $query->where('piggy_banks.name', $value);
return null === $query->first(['piggy_banks.*']); return null === $query->first(['piggy_banks.*']);
} }

View File

@ -2,38 +2,55 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/). This project adheres to [Semantic Versioning](http://semver.org/).
## 5.6.0 - 2021-xx-xx ## 5.6.0 - 2021-09-17
⚠️ This release features new LDAP libraries. Your mileage may vary. Make sure you back up everything. Firefly III may accidentally create a new account for you instead of reusing the old one. There is no option for LDAP filters yet. ⚠️ This release features new LDAP libraries. Your mileage may vary. Make sure you back up everything. Firefly III may accidentally create a new account for you instead of reusing the old one. There is no option for LDAP filters yet.
Please refer to the [documentation](https://docs.firefly-iii.org/firefly-iii/) and support channels if you run into problems:
- [Gitter.im](https://gitter.im/firefly-iii/firefly-iii)
- [Twitter](https://twitter.com/Firefly_III/)
- [GitHub Issues](https://github.com/firefly-iii/firefly-iii/issues)
- [GitHub Discussions](https://github.com/firefly-iii/firefly-iii/discussions)
### Added ### Added
- A few new pages for the new v2 layout. Thanks @alex6480! - A few new pages for the new v2 layout. Thanks @alex6480!
- Added a new currency yay! - Added a new currency, thanks @kasperkls02!
- You can now manage loans and debts a little better. - You can now manage loans and debts a little better, see also the documentation for help.
- Some screenshots are now in the GitHub repository for better management, thanks @Flightkick!
- @LBreda has added a service worker and updated icons, thanks!
### Changed ### Changed
- @hoshsadiq has added all PHP requirements to the composer file, thanks!
- A better cache routine for layout v2 pages. - A better cache routine for layout v2 pages.
- All LDAP libraries have been upgrade. - All LDAP libraries have been upgraded.
- New issue templates and help text for easier GitHub support.
### Deprecated - First preparations for multi-administration and group membership options.
- Initial release. - The search will return nothing if you submit invalid values, instead of everything.
### Removed ### Removed
- All telemetry options have been removed. - All telemetry options have been removed.
### Fixed ### Fixed
- [Issue 4894](https://github.com/firefly-iii/firefly-iii/issues/4894) Bad number comparison - [Issue 4894](https://github.com/firefly-iii/firefly-iii/issues/4894) Bad number comparison
- [Issue 4987](https://github.com/firefly-iii/firefly-iii/issues/4987) Budgeted amount includes inactive budgets
- [Issue 4988](https://github.com/firefly-iii/firefly-iii/issues/4988) Can't select liability account from imported transactions
- #5042 HTTP 500 when creating Personal Access Token or OAuth Client
- Various Sonarqube issues, thanks @hazma-fadil! - Various Sonarqube issues, thanks @hazma-fadil!
- Correct menu display, thanks @vonsogt! - Correct menu display, thanks @vonsogt!
- The IBAN validator will filter special characters.
- In some cases, piggy banks would report being full or empty while this was not actually the case.
- Various other bugs and minor issues.
### Security ### Security
- Feature to be able to rebuild Docker images and show security warnings in new builds. - Feature to be able to rebuild Docker images and show security warnings in new builds.
- [CVE-2021-3663](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-3663) A missing rate limiter makes brute-forcing the login easy.
- It also fixes [CVE-2021-3728](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-3728), [CVE-2021-3729](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-3729) and [CVE-2021-3730](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-3730), all variations of the same security vulnerability: some actions in Firefly III were vulnerable to CSRF.
### API ### API
- You can disable webhooks with an extra field in API submissions. - You can disable webhooks with an extra field in API submissions.
- There is a static cron token (see `.env.example`) which is useful for Docker. - There is a static cron token (see `.env.example`) which is useful for Docker.
- A better endpoint to move transactions around, see [api-docs.firefly-iii.org](https://api-docs.firefly-iii.org).
## 5.5.13 - 2021-07-25 ## 5.5.13 - 2021-07-25

View File

@ -74,14 +74,18 @@
"ext-curl": "*", "ext-curl": "*",
"ext-fileinfo": "*", "ext-fileinfo": "*",
"ext-gd": "*", "ext-gd": "*",
"ext-iconv": "*",
"ext-intl": "*", "ext-intl": "*",
"ext-json": "*", "ext-json": "*",
"ext-mbstring": "*",
"ext-openssl": "*", "ext-openssl": "*",
"ext-pdo": "*", "ext-pdo": "*",
"ext-session": "*", "ext-session": "*",
"ext-simplexml": "*", "ext-simplexml": "*",
"ext-sodium": "*",
"ext-tokenizer": "*", "ext-tokenizer": "*",
"ext-xml": "*", "ext-xml": "*",
"ext-xmlwriter": "*",
"bacon/bacon-qr-code": "2.*", "bacon/bacon-qr-code": "2.*",
"diglactic/laravel-breadcrumbs": "^7.0", "diglactic/laravel-breadcrumbs": "^7.0",
"doctrine/dbal": "3.*", "doctrine/dbal": "3.*",
@ -173,6 +177,7 @@
"@php artisan firefly-iii:migrate-tag-locations", "@php artisan firefly-iii:migrate-tag-locations",
"@php artisan firefly-iii:migrate-recurrence-type", "@php artisan firefly-iii:migrate-recurrence-type",
"@php artisan firefly-iii:upgrade-liabilities", "@php artisan firefly-iii:upgrade-liabilities",
"@php artisan firefly-iii:create-group-memberships",
"@php artisan firefly-iii:fix-piggies", "@php artisan firefly-iii:fix-piggies",
"@php artisan firefly-iii:create-link-types", "@php artisan firefly-iii:create-link-types",

602
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,25 @@
<?php <?php
/*
* breadcrumbs.php
* Copyright (c) 2021 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1); declare(strict_types=1);
return [ return [

View File

@ -1,4 +1,25 @@
<?php <?php
/*
* bulk.php
* Copyright (c) 2021 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1); declare(strict_types=1);
use FireflyIII\Enums\ClauseType; use FireflyIII\Enums\ClauseType;

View File

@ -101,9 +101,9 @@ return [
'webhooks' => true, 'webhooks' => true,
'handle_debts' => true, 'handle_debts' => true,
], ],
'version' => '5.6.0-alpha.2', 'version' => '5.6.0',
'api_version' => '1.5.3', 'api_version' => '1.5.3',
'db_version' => 17, 'db_version' => 18,
// generic settings // generic settings
'maxUploadSize' => 1073741824, // 1 GB 'maxUploadSize' => 1073741824, // 1 GB

View File

@ -1,4 +1,25 @@
<?php <?php
/*
* ldap.php
* Copyright (c) 2021 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1); declare(strict_types=1);
return [ return [

40
config/user_roles.php Normal file
View File

@ -0,0 +1,40 @@
<?php
/*
* user_roles.php
* Copyright (c) 2021 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
use FireflyIII\Models\UserRole;
return [
'roles' => [
UserRole::READ_ONLY => [],
UserRole::CHANGE_TRANSACTIONS => [],
UserRole::CHANGE_RULES => [],
UserRole::CHANGE_PIGGY_BANKS => [],
UserRole::CHANGE_REPETITIONS => [],
UserRole::VIEW_REPORTS => [],
UserRole::FULL => [],
UserRole::OWNER => [],
],
];

View File

@ -0,0 +1,148 @@
<?php
/*
* 2021_08_28_073733_user_groups.php
* Copyright (c) 2021 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
/**
* Class UserGroups
*/
class UserGroups extends Migration
{
private array $tables
= ['accounts', 'attachments', 'available_budgets', 'bills', 'budgets', 'categories', 'recurrences', 'rule_groups', 'rules', 'tags',
'transaction_groups', 'transaction_journals', 'webhooks'];
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
// remove columns from tables
/** @var string $tableName */
foreach ($this->tables as $tableName) {
Schema::table(
$tableName, function (Blueprint $table) use ($tableName) {
$table->dropForeign(sprintf('%s_to_ugi', $tableName));
if (Schema::hasColumn($tableName, 'user_group_id')) {
$table->dropColumn('user_group_id');
}
}
);
}
Schema::table(
'users', function (Blueprint $table) {
$table->dropForeign('type_user_group_id');
if (Schema::hasColumn('users', 'user_group_id')) {
$table->dropColumn('user_group_id');
}
}
);
Schema::dropIfExists('group_memberships');
Schema::dropIfExists('user_roles');
Schema::dropIfExists('user_groups');
}
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
/*
* user is a member of a user_group through a user_group_role
* may have multiple roles in a group
*/
Schema::create(
'user_groups', static function (Blueprint $table) {
$table->bigIncrements('id');
$table->timestamps();
$table->softDeletes();
$table->string('title', 255);
$table->unique('title');
}
);
Schema::create(
'user_roles', static function (Blueprint $table) {
$table->bigIncrements('id');
$table->timestamps();
$table->softDeletes();
$table->string('title', 255);
$table->unique('title');
}
);
Schema::create(
'group_memberships',
static function (Blueprint $table) {
$table->bigIncrements('id');
$table->timestamps();
$table->softDeletes();
$table->integer('user_id', false, true);
$table->bigInteger('user_group_id', false, true);
$table->bigInteger('user_role_id', false, true);
$table->foreign('user_id')->references('id')->on('users')->onUpdate('cascade')->onDelete('cascade');
$table->foreign('user_group_id')->references('id')->on('user_groups')->onUpdate('cascade')->onDelete('cascade');
$table->foreign('user_role_id')->references('id')->on('user_roles')->onUpdate('cascade')->onDelete('cascade');
$table->unique(['user_id', 'user_group_id', 'user_role_id']);
}
);
Schema::table(
'users', function (Blueprint $table) {
if (!Schema::hasColumn('users', 'user_group_id')) {
$table->bigInteger('user_group_id', false, true)->nullable();
$table->foreign('user_group_id', 'type_user_group_id')->references('id')->on('user_groups')->onDelete('set null')->onUpdate('cascade');
}
}
);
// ADD columns from tables
/** @var string $tableName */
foreach ($this->tables as $tableName) {
Schema::table(
$tableName, function (Blueprint $table) use ($tableName) {
if (!Schema::hasColumn($tableName, 'user_group_id')) {
$table->bigInteger('user_group_id', false, true)->nullable()->after('user_id');
$table->foreign('user_group_id', sprintf('%s_to_ugi', $tableName))->references('id')->on('user_groups')->onDelete('set null')->onUpdate('cascade');
}
}
);
}
}
}

View File

@ -40,5 +40,6 @@ class DatabaseSeeder extends Seeder
$this->call(PermissionSeeder::class); $this->call(PermissionSeeder::class);
$this->call(LinkTypeSeeder::class); $this->call(LinkTypeSeeder::class);
$this->call(ConfigSeeder::class); $this->call(ConfigSeeder::class);
$this->call(UserRoleSeeder::class);
} }
} }

View File

@ -0,0 +1,63 @@
<?php
/*
* UserRoleSeeder.php
* Copyright (c) 2021 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace Database\Seeders;
use FireflyIII\Models\UserRole;
use Illuminate\Database\Seeder;
use PDOEXception;
/**
* Class UserRoleSeeder
*/
class UserRoleSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
$roles = [
UserRole::READ_ONLY,
UserRole::CHANGE_TRANSACTIONS,
UserRole::CHANGE_RULES,
UserRole::CHANGE_PIGGY_BANKS,
UserRole::CHANGE_REPETITIONS,
UserRole::VIEW_REPORTS,
UserRole::FULL,
UserRole::OWNER,
];
/** @var string $role */
foreach ($roles as $role) {
try {
UserRole::create(['title' => $role]);
} catch (PDOException $e) {
// @ignoreException
}
}
}
}

View File

@ -17,13 +17,13 @@
"lodash.clonedeep": "^4.5.0", "lodash.clonedeep": "^4.5.0",
"postcss": "^8.1.14", "postcss": "^8.1.14",
"resolve-url-loader": "^4.0.0", "resolve-url-loader": "^4.0.0",
"sass": "^1.37.0", "sass": "^1.39.2",
"sass-loader": "^12.0.0", "sass-loader": "^12.0.0",
"vue-i18n": "^8.24.2", "vue-i18n": "^8.24.2",
"vue-loader": "^15", "vue-loader": "^15",
"vue-template-compiler": "^2.6.12", "vue-template-compiler": "^2.6.12",
"vuex": "^3.6.2", "vuex": "^3.6.2",
"webpack": "^5.40.0" "webpack": "^5.52.1"
}, },
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-free": "^5.15.3", "@fortawesome/fontawesome-free": "^5.15.3",

View File

@ -279,8 +279,8 @@ export default {
// console.log('end of finaliseSubmission'); // console.log('end of finaliseSubmission');
}, },
handleSubmissionError: function (errors) { handleSubmissionError: function (errors) {
console.log('Bad'); console.error('Bad');
console.log(errors); console.error(errors);
this.inError = true; this.inError = true;
this.submitting = false; this.submitting = false;
this.errors = lodashClonedeep(this.defaultErrors); this.errors = lodashClonedeep(this.defaultErrors);
@ -320,12 +320,12 @@ export default {
this.parseAccount(response.data); this.parseAccount(response.data);
} }
).catch(error => { ).catch(error => {
console.log('I failed :('); console.error('I failed :(');
console.log(error); console.error(error);
}); });
}, },
storeField: function (payload) { storeField: function (payload) {
console.log(payload); //console.log(payload);
if ('location' === payload.field) { if ('location' === payload.field) {
if (true === payload.value.hasMarker) { if (true === payload.value.hasMarker) {
this.account.location = payload.value; this.account.location = payload.value;

View File

@ -20,13 +20,145 @@
<template> <template>
<div> <div>
I am a show <div class="row">
<div class="col-lg-12 col-md-6 col-sm-12 col-xs-12">
<!-- Custom Tabs -->
<div class="card">
<div class="card-header d-flex p-0">
<h3 class="card-title p-3">Tabs</h3>
<ul class="nav nav-pills ml-auto p-2">
<li class="nav-item"><a class="nav-link active" href="#main_chart" data-toggle="tab">Chart</a></li>
<li class="nav-item"><a class="nav-link" href="#budgets" data-toggle="tab">Budgets</a></li>
<li class="nav-item"><a class="nav-link" href="#categories" data-toggle="tab">Categories</a></li>
</ul>
</div><!-- /.card-header -->
<div class="card-body">
<div class="tab-content">
<div class="tab-pane active" id="main_chart">
1: main chart
</div>
<!-- /.tab-pane -->
<div class="tab-pane" id="budgets">
2: tree map from/to budget
</div>
<!-- /.tab-pane -->
<div class="tab-pane" id="categories">
2: tree map from/to cat
</div>
<!-- /.tab-pane -->
</div>
<!-- /.tab-content -->
</div><!-- /.card-body -->
</div>
<!-- ./card -->
</div>
</div>
<TransactionListLarge
:entries="rawTransactions"
:page="currentPage"
:total="total"
:per-page="perPage"
:sort-desc="sortDesc"
v-on:jump-page="jumpToPage($event)"
/>
<div class="row">
<div class="col-lg-12 col-md-6 col-sm-12 col-xs-12">
<div class="card">
<div class="card-header">
<h3 class="card-title">
Blocks
</h3>
</div>
<div class="card-body">
Blocks
</div>
</div>
</div>
</div>
</div> </div>
</template> </template>
<script> <script>
import TransactionListLarge from "../transactions/TransactionListLarge";
import format from "date-fns/format";
import {mapGetters} from "vuex";
import {configureAxios} from "../../shared/forageStore";
export default { export default {
name: "Show" name: "Show",
computed: {
...mapGetters('root', ['listPageSize', 'cacheKey']),
...mapGetters('dashboard/index', ['start', 'end',]),
'showReady': function () {
return null !== this.start && null !== this.end && null !== this.listPageSize && this.ready;
},
},
data() {
return {
accountId: 0,
rawTransactions: [],
ready: false,
loading: false,
total: 0,
sortDesc: false,
currentPage: 1,
perPage: 51,
locale: 'en-US',
api: null,
}
},
created() {
this.ready = true;
let parts = window.location.pathname.split('/');
this.accountId = parseInt(parts[parts.length - 1]);
this.perPage = this.listPageSize ?? 51;
let params = new URLSearchParams(window.location.search);
this.currentPage = params.get('page') ? parseInt(params.get('page')) : 1;
this.getTransactions();
},
components: {TransactionListLarge},
methods: {
getTransactions: function () {
if (this.showReady && !this.loading) {
this.loading = true;
configureAxios().then(async (api) => {
// console.log('Now getTransactions() x Start');
let startStr = format(this.start, 'y-MM-dd');
let endStr = format(this.end, 'y-MM-dd');
this.rawTransactions = [];
let url = './api/v1/accounts/' + this.accountId + '/transactions?page=1&limit=' + this.perPage + '&start=' + startStr + '&end=' + endStr;
api.get(url)
.then(response => {
// console.log('Now getTransactions() DONE!');
this.total = parseInt(response.data.meta.pagination.total);
this.rawTransactions = response.data.data;
this.loading = false;
}
);
});
}
},
jumpToPage: function (event) {
// console.log('noticed a change!');
this.currentPage = event.page;
this.downloadTransactionList(event.page);
},
},
watch: {
start: function () {
this.getTransactions();
},
end: function () {
this.getTransactions();
},
}
} }
</script> </script>

View File

@ -0,0 +1,108 @@
<!--
- DashboardListLarge.vue
- Copyright (c) 2020 james@firefly-iii.org
-
- This file is part of Firefly III (https://github.com/firefly-iii).
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program 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 Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<table class="table table-striped table-sm">
<caption style="display:none;">{{ $t('firefly.transaction_table_description') }}</caption>
<thead>
<tr>
<th class="text-left" scope="col">{{ $t('firefly.description') }}</th>
<th scope="col">{{ $t('firefly.opposing_account') }}</th>
<th class="text-right" scope="col">{{ $t('firefly.amount') }}</th>
<th scope="col">{{ $t('firefly.category') }}</th>
<th scope="col">{{ $t('firefly.budget') }}</th>
</tr>
</thead>
<tbody>
<tr v-for="transaction in this.transactions">
<td>
<a :href="'transactions/show/' + transaction.id " :title="transaction.date">
<span v-if="transaction.attributes.transactions.length > 1">{{ transaction.attributes.group_title }}</span>
<span v-if="1===transaction.attributes.transactions.length">{{ transaction.attributes.transactions[0].description }}</span>
</a>
</td>
<td>
<span v-for="tr in transaction.attributes.transactions">
<a v-if="'withdrawal' === tr.type" :href="'accounts/show/' + tr.destination_id">{{ tr.destination_name }}</a>
<a v-if="'deposit' === tr.type" :href="'accounts/show/' + tr.source_id">{{ tr.source_name }}</a>
<a v-if="'transfer' === tr.type && parseInt(tr.source_id) === account_id" :href="'accounts/show/' + tr.destination_id">{{ tr.destination_name }}</a>
<a v-if="'transfer' === tr.type && parseInt(tr.destination_id) === account_id" :href="'accounts/show/' + tr.source_id">{{ tr.source_name }}</a>
<br/>
</span>
</td>
<td style="text-align:right;">
<span v-for="tr in transaction.attributes.transactions">
<span v-if="'withdrawal' === tr.type" class="text-danger">
{{ Intl.NumberFormat(locale, {style: 'currency', currency: tr.currency_code}).format(tr.amount * -1) }}<br>
</span>
<span v-if="'deposit' === tr.type" class="text-success">
{{ Intl.NumberFormat(locale, {style: 'currency', currency: tr.currency_code}).format(tr.amount) }}<br>
</span>
<span v-if="'transfer' === tr.type && parseInt(tr.source_id) === account_id" class="text-info">
{{ Intl.NumberFormat(locale, {style: 'currency', currency: tr.currency_code}).format(tr.amount * -1) }}<br>
</span>
<span v-if="'transfer' === tr.type && parseInt(tr.destination_id) === account_id" class="text-info">
{{ Intl.NumberFormat(locale, {style: 'currency', currency: tr.currency_code}).format(tr.amount) }}<br>
</span>
</span>
</td>
<td>
<span v-for="tr in transaction.attributes.transactions">
<a v-if="0!==tr.category_id" :href="'categories/show/' + tr.category_id">{{ tr.category_name }}</a><br/>
</span>
</td>
<td>
<span v-for="tr in transaction.attributes.transactions">
<a v-if="0!==tr.budget_id" :href="'budgets/show/' + tr.budget_id">{{ tr.budget_name }}</a><br/>
</span>
</td>
</tr>
</tbody>
</table>
</template>
<script>
export default {
name: "DashboardListLarge",
data() {
return {
locale: 'en-US'
}
},
created() {
this.locale = localStorage.locale ?? 'en-US';
},
props: {
transactions: {
type: Array,
default: function () {
return [];
}
},
account_id: {
type: Number,
default: function () {
return 0;
}
},
}
}
</script>

View File

@ -1,5 +1,5 @@
<!-- <!--
- TransactionListMedium.vue - DashboardListMedium.vue
- Copyright (c) 2020 james@firefly-iii.org - Copyright (c) 2020 james@firefly-iii.org
- -
- This file is part of Firefly III (https://github.com/firefly-iii). - This file is part of Firefly III (https://github.com/firefly-iii).
@ -68,7 +68,7 @@
<script> <script>
export default { export default {
name: "TransactionListMedium", name: "DashboardListMedium",
data() { data() {
return { return {
locale: 'en-US' locale: 'en-US'

View File

@ -1,5 +1,5 @@
<!-- <!--
- TransactionListSmall.vue - DashboardListSmall.vue
- Copyright (c) 2020 james@firefly-iii.org - Copyright (c) 2020 james@firefly-iii.org
- -
- This file is part of Firefly III (https://github.com/firefly-iii). - This file is part of Firefly III (https://github.com/firefly-iii).
@ -59,7 +59,7 @@
<script> <script>
export default { export default {
name: "TransactionListSmall", name: "DashboardListSmall",
data() { data() {
return { return {
locale: 'en-US' locale: 'en-US'

View File

@ -107,8 +107,8 @@ export default {
this.drawChart(); this.drawChart();
}) })
.catch(error => { .catch(error => {
console.log('Has error!'); console.error('Has error!');
console.log(error); console.error(error);
this.error = true; this.error = true;
}); });
}, },

View File

@ -62,9 +62,9 @@
</div> </div>
<div class="card-body table-responsive p-0"> <div class="card-body table-responsive p-0">
<div> <div>
<transaction-list-large v-if="1===accounts.length" :account_id="account.id" :transactions="account.transactions"/> <dashboard-list-large v-if="1===accounts.length" :account_id="account.id" :transactions="account.transactions"/>
<transaction-list-medium v-if="2===accounts.length" :account_id="account.id" :transactions="account.transactions"/> <dashboard-list-medium v-if="2===accounts.length" :account_id="account.id" :transactions="account.transactions"/>
<transaction-list-small v-if="accounts.length > 2" :account_id="account.id" :transactions="account.transactions"/> <dashboard-list-small v-if="accounts.length > 2" :account_id="account.id" :transactions="account.transactions"/>
</div> </div>
</div> </div>
</div> </div>

View File

@ -830,6 +830,10 @@ export default {
//console.log('getExpectedSourceTypes.'); //console.log('getExpectedSourceTypes.');
this.sourceAllowedTypes = response.data.data.value.source[this.transactionType]; this.sourceAllowedTypes = response.data.data.value.source[this.transactionType];
this.destinationAllowedTypes = response.data.data.value.destination[this.transactionType]; this.destinationAllowedTypes = response.data.data.value.destination[this.transactionType];
// console.log('sourceAllowedTypes');
// console.log(this.sourceAllowedTypes);
// console.log('Source allowed types for ' + this.transactionType + ' is: '); // console.log('Source allowed types for ' + this.transactionType + ' is: ');
// console.log(this.sourceAllowedTypes); // console.log(this.sourceAllowedTypes);

View File

@ -28,7 +28,6 @@
<SplitPills <SplitPills
:transactions="transactions" :transactions="transactions"
:count="transactions.length" :count="transactions.length"
/> />
<div class="tab-content"> <div class="tab-content">
@ -132,6 +131,7 @@ export default {
this.groupId = parseInt(parts[parts.length - 1]); this.groupId = parseInt(parts[parts.length - 1]);
this.transactions = []; this.transactions = [];
this.getTransactionGroup(); this.getTransactionGroup();
//this.getExpectedSourceTypes();
this.getAllowedOpposingTypes(); this.getAllowedOpposingTypes();
this.getCustomFields(); this.getCustomFields();
}, },
@ -238,6 +238,9 @@ export default {
watch: { watch: {
submittedAttachments: function () { submittedAttachments: function () {
this.finaliseSubmission(); this.finaliseSubmission();
},
transactionType: function() {
this.getExpectedSourceTypes();
} }
}, },
@ -299,8 +302,12 @@ export default {
this.transactionType = array.type.charAt(0).toUpperCase() + array.type.slice(1); this.transactionType = array.type.charAt(0).toUpperCase() + array.type.slice(1);
// See reference nr. 5 // See reference nr. 5
this.sourceAllowedTypes = [array.source_type]; this.sourceAllowedTypes = [array.source_type];
this.destinationAllowedTypes = [array.destination_type]; this.destinationAllowedTypes = [array.destination_type];
// console.log('sourceAllowedTypes (parseTransaction)');
// console.log(this.sourceAllowedTypes);
this.date = array.date.substring(0, 16); this.date = array.date.substring(0, 16);
this.originalDate = array.date.substring(0, 16); this.originalDate = array.date.substring(0, 16);
} }
@ -427,6 +434,18 @@ export default {
this.originalTransactions[index].links.push(object); this.originalTransactions[index].links.push(object);
}); });
}, },
getExpectedSourceTypes: function () {
axios.get('./api/v1/configuration/firefly.expected_source_types')
.then(response => {
this.sourceAllowedTypes = response.data.data.value.source[this.transactionType];
this.destinationAllowedTypes = response.data.data.value.destination[this.transactionType];
// console.log('sourceAllowedTypes (getExpectedSourceTypes)');
// console.log(JSON.stringify(response.data.data.value.source[this.transactionType]));
// console.log(JSON.stringify(response.data.data.value.source));
// console.log(this.transactionType);
//console.log(this.transactionType);
});
},
/** /**
* Get API value. * Get API value.
*/ */

View File

@ -36,123 +36,15 @@
</div> </div>
</div> </div>
</div> </div>
<div class="row"> <!-- page is ignored for the time being -->
<div class="col-lg-8 col-md-6 col-sm-12 col-xs-12"> <TransactionListLarge
<b-pagination :entries="rawTransactions"
v-model="currentPage" :page="currentPage"
:total-rows="total" :total="total"
:per-page="perPage" :per-page="perPage"
aria-controls="my-table" :sort-desc="sortDesc"
></b-pagination> v-on:jump-page="jumpToPage($event)"
</div> />
<div class="col-lg-4 col-md-6 col-sm-12 col-xs-12">
<button @click="newCacheKey" class="btn btn-sm float-right btn-info"><span class="fas fa-sync"></span></button>
</div>
</div>
<div class="row">
<div class="col">
<div class="card">
<div class="card-body p-0">
<b-table id="my-table" small striped hover responsive="md" primary-key="key" :no-local-sorting="false"
:items="transactionRows"
:fields="fields"
:per-page="perPage"
sort-icon-left
ref="table"
:current-page="currentPage"
:busy.sync="loading"
:sort-desc.sync="sortDesc"
:sort-compare="tableSortCompare"
>
<template #table-busy>
<span class="fa fa-spinner"></span>
</template>
<template #cell(type)="data">
<span v-if="! data.item.split || data.item.split_parent === null">
<span class="fas fa-long-arrow-alt-right" v-if="'deposit' === data.item.type"></span>
<span class="fas fa-long-arrow-alt-left" v-else-if="'withdrawal' === data.item.type"></span>
<span class="fas fa-long-arrows-alt-h" v-else-if="'transfer' === data.item.type"></span>
</span>
</template>
<template #cell(description)="data">
<span class="fas fa-angle-right" v-if="data.item.split && data.item.split_parent !== null"></span>
<a :class="false === data.item.active ? 'text-muted' : ''" :href="'./transactions/show/' + data.item.id" :title="data.value">{{
data.value
}}</a>
</template>
<template #cell(amount)="data">
<span class="text-success" v-if="'deposit' === data.item.type">
{{ Intl.NumberFormat(locale, {style: 'currency', currency: data.item.currency_code}).format(data.item.amount) }}
</span>
<span class="text-danger" v-else-if="'withdrawal' === data.item.type">
{{ Intl.NumberFormat(locale, {style: 'currency', currency: data.item.currency_code}).format(-data.item.amount) }}
</span>
<span class="text-muted" v-else-if="'transfer' === data.item.type">
{{ Intl.NumberFormat(locale, {style: 'currency', currency: data.item.currency_code}).format(data.item.amount) }}
</span>
</template>
<template #cell(date)="data">
{{ data.item.date_formatted }}
</template>
<template #cell(source_account)="data">
<a :class="false === data.item.active ? 'text-muted' : ''" :href="'./accounts/show/' + data.item.source_id"
:title="data.item.source_name">{{ data.item.source_name }}</a>
</template>
<template #cell(destination_account)="data">
<a :class="false === data.item.active ? 'text-muted' : ''" :href="'./accounts/show/' + data.item.destination_id"
:title="data.item.destination_name">{{ data.item.destination_name }}</a>
</template>
<template #cell(menu)="data">
<div class="btn-group btn-group-sm" v-if="! data.item.split || data.item.split_parent === null">
<div class="dropdown">
<button class="btn btn-light btn-sm dropdown-toggle" type="button" :id="'dropdownMenuButton' + data.item.id" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="false">
{{ $t('firefly.actions') }}
</button>
<div class="dropdown-menu" :aria-labelledby="'dropdownMenuButton' + data.item.id">
<a class="dropdown-item" :href="'./transactions/edit/' + data.item.id"><span class="fa fas fa-pencil-alt"></span> {{ $t('firefly.edit') }}</a>
<a class="dropdown-item" :href="'./transactions/delete/' + data.item.id"><span class="fa far fa-trash"></span> {{ $t('firefly.delete') }}</a>
</div>
</div>
</div>
<div class="btn btn-light btn-sm" v-if="data.item.split && data.item.split_parent === null && data.item.collapsed === true"
v-on:click="toggleCollapse(data.item)">
<span class="fa fa-caret-down"></span>
{{ $t('firefly.transaction_expand_split') }}
</div>
<div class="btn btn-light btn-sm" v-else-if="data.item.split && data.item.split_parent === null && data.item.collapsed === false"
v-on:click="toggleCollapse(data.item)">
<span class="fa fa-caret-up"></span>
{{ $t('firefly.transaction_collapse_split') }}
</div>
</template>
<template #cell(category)="data">
{{ data.item.category_name }}
</template>
</b-table>
</div>
<div class="card-footer">
<a :href="'./transactions/create/' + type" class="btn btn-success"
:title="$t('firefly.create_new_transaction')">{{ $t('firefly.create_new_transaction') }}</a>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-8 col-md-6 col-sm-12 col-xs-12">
<b-pagination
v-model="currentPage"
:total-rows="total"
:per-page="perPage"
aria-controls="my-table"
></b-pagination>
</div>
<div class="col-lg-4 col-md-6 col-sm-12 col-xs-12">
<button @click="newCacheKey" class="btn btn-sm float-right btn-info"><span class="fas fa-sync"></span></button>
</div>
</div>
<div class="row"> <div class="row">
<div class="col-xl-2 col-lg-4 col-sm-6 col-xs-12" v-for="range in ranges"> <div class="col-xl-2 col-lg-4 col-sm-6 col-xs-12" v-for="range in ranges">
<div class="card"> <div class="card">
@ -176,18 +68,18 @@ import sub from "date-fns/sub";
import startOfMonth from "date-fns/startOfMonth"; import startOfMonth from "date-fns/startOfMonth";
import endOfMonth from "date-fns/endOfMonth"; import endOfMonth from "date-fns/endOfMonth";
import {configureAxios} from "../../shared/forageStore"; import {configureAxios} from "../../shared/forageStore";
import TransactionListLarge from "./TransactionListLarge";
export default { export default {
name: "Index", name: "Index",
components: {TransactionListLarge},
data() { data() {
return { return {
transactions: [], rawTransactions: [],
transactionRows: [],
type: 'all', type: 'all',
downloaded: false, downloaded: false,
loading: false, loading: false,
ready: false, ready: false,
fields: [],
currentPage: 1, currentPage: 1,
perPage: 5, perPage: 5,
total: 1, total: 1,
@ -236,6 +128,7 @@ export default {
let parts = pathName.split('/'); let parts = pathName.split('/');
this.type = parts[parts.length - 1]; this.type = parts[parts.length - 1];
this.perPage = this.listPageSize ?? 51; this.perPage = this.listPageSize ?? 51;
if (5 === parts.length) { if (5 === parts.length) {
this.urlStart = new Date(parts[3]); this.urlStart = new Date(parts[3]);
this.urlEnd = new Date(parts[4]); this.urlEnd = new Date(parts[4]);
@ -244,57 +137,27 @@ export default {
let params = new URLSearchParams(window.location.search); let params = new URLSearchParams(window.location.search);
this.currentPage = params.get('page') ? parseInt(params.get('page')) : 1; this.currentPage = params.get('page') ? parseInt(params.get('page')) : 1;
this.updateFieldList();
this.ready = true; this.ready = true;
// make object thing:
// let token = document.head.querySelector('meta[name="csrf-token"]');
// this.api = setup(
// {
// // `axios` options
// //baseURL: './',
// headers: {'X-CSRF-TOKEN': token.content, 'X-James': 'yes'},
//
// // `axios-cache-adapter` options
// cache: {
// maxAge: 15 * 60 * 1000,
// readHeaders: false,
// exclude: {
// query: false,
// },
// debug: true
// }
// });
}, },
methods: { methods: {
...mapMutations('root', ['refreshCacheKey',]), ...mapMutations('root', ['refreshCacheKey',]),
updateFieldList: function () {
this.fields = [
{key: 'type', label: ' ', sortable: false},
{key: 'description', label: this.$t('list.description'), sortable: true},
{key: 'amount', label: this.$t('list.amount'), sortable: true},
{key: 'date', label: this.$t('list.date'), sortable: true},
{key: 'source_account', label: this.$t('list.source_account'), sortable: true},
{key: 'destination_account', label: this.$t('list.destination_account'), sortable: true},
{key: 'category_name', label: this.$t('list.category'), sortable: true},
{key: 'menu', label: ' ', sortable: false},
];
},
newCacheKey: function () { newCacheKey: function () {
this.refreshCacheKey(); this.refreshCacheKey();
this.downloaded = false; this.downloaded = false;
this.accounts = []; this.accounts = [];
this.getTransactionList(); this.getTransactionList();
}, },
jumpToPage: function(event) {
// console.log('noticed a change!');
this.currentPage = event.page;
this.downloadTransactionList(event.page);
},
getTransactionList: function () { getTransactionList: function () {
// console.log('getTransactionList()');
if (this.indexReady && !this.loading && !this.downloaded) { if (this.indexReady && !this.loading && !this.downloaded) {
// console.log('Index ready, not loading and not already downloaded. Reset.');
this.loading = true; this.loading = true;
this.perPage = this.listPageSize ?? 51; this.perPage = this.listPageSize ?? 51;
this.transactions = []; this.rawTransactions = [];
this.transactionRows = []; this.downloadTransactionList(this.currentPage);
this.downloadTransactionList(1);
this.calculateDateRanges(); this.calculateDateRanges();
} }
}, },
@ -303,165 +166,39 @@ export default {
let currentDate = this.start; let currentDate = this.start;
while (currentDate > yearAgo) { while (currentDate > yearAgo) {
// start + end of month:
let st = startOfMonth(currentDate); let st = startOfMonth(currentDate);
let en = endOfMonth(currentDate); let en = endOfMonth(currentDate);
this.ranges.push({start: st, end: en}); this.ranges.push({start: st, end: en});
currentDate = sub(currentDate, {months: 1}); currentDate = sub(currentDate, {months: 1});
//console.log(currentDate);
} }
}, },
formatDate: function (date, frm) { formatDate: function (date, frm) {
return format(date, frm); return format(date, frm);
}, },
downloadTransactionList: function (page) { downloadTransactionList: function (page) {
// console.log('downloadTransactionList(' + page + ')');
configureAxios().then(async (api) => { configureAxios().then(async (api) => {
let startStr = format(this.start, 'y-MM-dd'); let startStr = format(this.start, 'y-MM-dd');
let endStr = format(this.end, 'y-MM-dd'); let endStr = format(this.end, 'y-MM-dd');
// console.log(this.urlEnd);
// console.log(this.urlStart); if (null !== this.urlEnd && null !== this.urlStart) {
if(null !== this.urlEnd && null !== this.urlStart) {
startStr = format(this.urlStart, 'y-MM-dd'); startStr = format(this.urlStart, 'y-MM-dd');
endStr = format(this.urlEnd, 'y-MM-dd'); endStr = format(this.urlEnd, 'y-MM-dd');
} }
api.get('./api/v1/transactions?type=' + this.type + '&page=' + page + "&start=" + startStr + "&end=" + endStr + '&cache=' + this.cacheKey) let url = './api/v1/transactions?type=' + this.type + '&page=' + page + "&start=" + startStr + "&end=" + endStr + '&cache=' + this.cacheKey;
api.get(url)
.then(response => { .then(response => {
//let currentPage = parseInt(response.data.meta.pagination.current_page);
//let totalPages = parseInt(response.data.meta.pagination.total_pages);
this.total = parseInt(response.data.meta.pagination.total); this.total = parseInt(response.data.meta.pagination.total);
//console.log('total is ' + this.total); this.rawTransactions = response.data.data;
this.transactions.push(...response.data.data); this.loading = false;
// if (currentPage < totalPage) {
// let nextPage = currentPage + 1;
// this.downloadTransactionList(nextPage);
// }
// if (currentPage >= totalPage) {
// console.log('Looks like all downloaded.');
this.downloaded = true;
this.createTransactionRows();
// }
} }
); );
}); });
}, },
createTransactionRows: function () {
this.transactionRows = [];
for (let i in this.transactions) {
let transaction = this.transactions[i];
let transactionRow = this.getTransactionRow(transaction, 0);
this.transactionRows.push(transactionRow);
if (transaction.attributes.transactions.length > 1) {
transactionRow.description = transaction.attributes.group_title;
transactionRow.split = true;
transactionRow.collapsed = transaction.collapsed === true || transaction.collapsed === undefined;
transactionRow.amount = transaction.attributes.transactions
.map(transaction => Number(transaction.amount))
.reduce((sum, n) => sum + n);
transactionRow.source_name = '';
transactionRow.source_id = '';
transactionRow.destination_name = '';
transactionRow.destination_id = '';
if (!transactionRow.collapsed) {
for (let i = 0; i < transaction.attributes.transactions.length; i++) {
let splitTransactionRow = this.getTransactionRow(transaction, i);
splitTransactionRow.key = splitTransactionRow.id + "." + i
splitTransactionRow.split = true;
splitTransactionRow.split_index = i + 1;
splitTransactionRow.split_parent = transactionRow;
this.transactionRows.push(splitTransactionRow);
}
}
}
}
this.loading = false;
},
getTransactionRow(transaction, index) {
let transactionRow = {};
let currentTransaction = transaction.attributes.transactions[index];
transactionRow.key = transaction.id;
transactionRow.id = transaction.id;
transactionRow.type = currentTransaction.type;
transactionRow.description = currentTransaction.description;
transactionRow.amount = currentTransaction.amount;
transactionRow.currency_code = currentTransaction.currency_code;
transactionRow.date = new Date(currentTransaction.date);
transactionRow.date_formatted = format(transactionRow.date, this.$t('config.month_and_day_fns'));
transactionRow.source_name = currentTransaction.source_name;
transactionRow.source_id = currentTransaction.source_id;
transactionRow.destination_name = currentTransaction.destination_name;
transactionRow.destination_id = currentTransaction.destination_id;
transactionRow.category_id = currentTransaction.category_id;
transactionRow.category_name = currentTransaction.category_name;
transactionRow.split = false;
transactionRow.split_index = 0;
transactionRow.split_parent = null;
return transactionRow;
},
toggleCollapse: function (row) {
let transaction = this.transactions.filter(transaction => transaction.id === row.id)[0];
if (transaction.collapsed === undefined) {
transaction.collapsed = false;
} else {
transaction.collapsed = !transaction.collapsed;
}
this.createTransactionRows();
},
tableSortCompare: function (aRow, bRow, key, sortDesc, formatter, compareOptions, compareLocale) {
let a = aRow[key]
let b = bRow[key]
if (aRow.id === bRow.id) {
// Order split transactions normally when compared to each other, except always put the header first
if (aRow.split_parent === null) {
return sortDesc ? 1 : -1;
} else if (bRow.split_parent === null) {
return sortDesc ? -1 : 1;
}
} else {
// Sort split transactions based on their parent when compared to other transactions
if (aRow.split && aRow.split_parent !== null) {
a = aRow.split_parent[key]
}
if (bRow.split && bRow.split_parent !== null) {
b = bRow.split_parent[key]
}
}
if (
(typeof a === 'number' && typeof b === 'number') ||
(a instanceof Date && b instanceof Date)
) {
// If both compared fields are native numbers or both are native dates
return a < b ? -1 : a > b ? 1 : 0
} else {
// Otherwise stringify the field data and use String.prototype.localeCompare
return toString(a).localeCompare(toString(b), compareLocale, compareOptions)
}
function toString(value) {
if (value === null || typeof value === 'undefined') {
return ''
} else if (value instanceof Object) {
return Object.keys(value)
.sort()
.map(key => toString(value[key]))
.join(' ')
} else {
return String(value)
}
}
},
}, },
} }
</script> </script>

View File

@ -19,84 +19,390 @@
--> -->
<template> <template>
<table class="table table-striped table-sm"> <div>
<caption style="display:none;">{{ $t('firefly.transaction_table_description') }}</caption> <div class="row">
<thead> <div class="col-lg-8 col-md-6 col-sm-12 col-xs-12">
<tr> <BPagination v-if="!loading"
<th class="text-left" scope="col">{{ $t('firefly.description') }}</th> v-model="currentPage"
<th scope="col">{{ $t('firefly.opposing_account') }}</th> :total-rows="total"
<th class="text-right" scope="col">{{ $t('firefly.amount') }}</th> :per-page="perPage"
<th scope="col">{{ $t('firefly.category') }}</th> aria-controls="my-table"
<th scope="col">{{ $t('firefly.budget') }}</th> ></BPagination>
</tr> </div>
</thead> <div class="col-lg-4 col-md-6 col-sm-12 col-xs-12">
<tbody> <button @click="newCacheKey" class="btn btn-sm float-right btn-info"><span class="fas fa-sync"></span></button>
<tr v-for="transaction in this.transactions"> </div>
<td> </div>
<a :href="'transactions/show/' + transaction.id " :title="transaction.date"> <div class="row">
<span v-if="transaction.attributes.transactions.length > 1">{{ transaction.attributes.group_title }}</span> <div class="col">
<span v-if="1===transaction.attributes.transactions.length">{{ transaction.attributes.transactions[0].description }}</span> <div class="card">
</a> <div class="card-body p-0">
</td> <BTable id="my-table" small striped hover responsive="md" primary-key="key" :no-local-sorting="false"
<td> :items="transactions"
<span v-for="tr in transaction.attributes.transactions"> :fields="fields"
<a v-if="'withdrawal' === tr.type" :href="'accounts/show/' + tr.destination_id">{{ tr.destination_name }}</a> :per-page="perPage"
<a v-if="'deposit' === tr.type" :href="'accounts/show/' + tr.source_id">{{ tr.source_name }}</a> sort-icon-left
<a v-if="'transfer' === tr.type && parseInt(tr.source_id) === account_id" :href="'accounts/show/' + tr.destination_id">{{ tr.destination_name }}</a> ref="table"
<a v-if="'transfer' === tr.type && parseInt(tr.destination_id) === account_id" :href="'accounts/show/' + tr.source_id">{{ tr.source_name }}</a> :current-page="currentPage"
<br/> :busy.sync="loading"
:sort-desc.sync="sortDesc"
:sort-compare="tableSortCompare"
>
<template #table-busy>
<span class="fa fa-spinner fa-spin"></span>
</template>
<template #cell(type)="data">
<span v-if="!data.item.dummy">
<span class="fas fa-long-arrow-alt-right" v-if="'deposit' === data.item.type"></span>
<span class="fas fa-long-arrow-alt-left" v-else-if="'withdrawal' === data.item.type"></span>
<span class="fas fa-long-arrows-alt-h" v-else-if="'transfer' === data.item.type"></span>
</span> </span>
</td> </template>
<td style="text-align:right;"> <template #cell(description)="data">
<span v-for="tr in transaction.attributes.transactions"> <span class="fa fa-spinner fa-spin" v-if="data.item.dummy"></span>
<span v-if="'withdrawal' === tr.type" class="text-danger"> <span v-if="!data.item.split">
{{ Intl.NumberFormat(locale, {style: 'currency', currency: tr.currency_code}).format(tr.amount * -1) }}<br> <a :href="'./transactions/show/' + data.item.id" :title="data.value">
</span> {{ data.item.description }}
<span v-if="'deposit' === tr.type" class="text-success"> </a>
{{ Intl.NumberFormat(locale, {style: 'currency', currency: tr.currency_code}).format(tr.amount) }}<br> </span>
</span> <span v-if="data.item.split">
<span v-if="'transfer' === tr.type && parseInt(tr.source_id) === account_id" class="text-info"> <!-- title first -->
{{ Intl.NumberFormat(locale, {style: 'currency', currency: tr.currency_code}).format(tr.amount * -1) }}<br> <span class="fas fa-angle-right" @click="toggleCollapse(data.item.id)" style="cursor: pointer;"></span>
</span> <a :href="'./transactions/show/' + data.item.id" :title="data.value">
<span v-if="'transfer' === tr.type && parseInt(tr.destination_id) === account_id" class="text-info"> {{ data.item.description }}
{{ Intl.NumberFormat(locale, {style: 'currency', currency: tr.currency_code}).format(tr.amount) }}<br> </a><br />
<span v-if="!data.item.collapsed">
<span v-for="(split, index) in data.item.splits" v-bind:key="index">
&nbsp; &nbsp; {{ split.description }}<br />
</span> </span>
</span>
</span> </span>
</td> </template>
<td> <template #cell(amount)="data">
<span v-for="tr in transaction.attributes.transactions"> <!-- row amount first (3x) -->
<a v-if="0!==tr.category_id" :href="'categories/show/' + tr.category_id">{{ tr.category_name }}</a><br/> <span :class="'text-success ' + (!data.item.collapsed ? 'font-weight-bold' : '')" v-if="'deposit' === data.item.type">
{{ Intl.NumberFormat(locale, {style: 'currency', currency: data.item.currency_code}).format(data.item.amount) }}
</span> </span>
</td> <span :class="'text-danger ' + (!data.item.collapsed ? 'font-weight-bold' : '')" v-if="'withdrawal' === data.item.type">
<td> {{ Intl.NumberFormat(locale, {style: 'currency', currency: data.item.currency_code}).format(-data.item.amount) }}
<span v-for="tr in transaction.attributes.transactions">
<a v-if="0!==tr.budget_id" :href="'budgets/show/' + tr.budget_id">{{ tr.budget_name }}</a><br/>
</span> </span>
</td> <span :class="'text-muted ' + (!data.item.collapsed ? 'font-weight-bold' : '')" v-if="'transfer' === data.item.type">
</tr> {{ Intl.NumberFormat(locale, {style: 'currency', currency: data.item.currency_code}).format(data.item.amount) }}
</tbody> </span>
</table> <br />
<!-- splits -->
<span v-if="!data.item.collapsed">
<span v-for="(split, index) in data.item.splits" v-bind:key="index">
{{ Intl.NumberFormat(locale, {style: 'currency', currency: split.currency_code}).format(split.amount) }}<br />
</span>
</span>
</template>
<template #cell(date)="data">
{{ data.item.date_formatted }}
</template>
<template #cell(source_account)="data">
<!-- extra break for splits -->
<span v-if="true===data.item.split && !data.item.collapsed">
<br />
</span>
<em v-if="true===data.item.split && data.item.collapsed">
...
</em>
<!-- loop all accounts, hidden if split -->
<span v-for="(split, index) in data.item.splits" v-bind:key="index" v-if="false===data.item.split || (true===data.item.split && !data.item.collapsed)">
<a :href="'./accounts/show/' + split.source_id" :title="split.source_name">{{ split.source_name }}</a><br />
</span>
</template>
<template #cell(destination_account)="data">
<!-- extra break for splits -->
<span v-if="true===data.item.split && !data.item.collapsed">
<br />
</span>
<em v-if="true===data.item.split && data.item.collapsed">
...
</em>
<!-- loop all accounts, hidden if split -->
<span v-for="(split, index) in data.item.splits" v-bind:key="index" v-if="false===data.item.split || (true===data.item.split && !data.item.collapsed)">
<a :href="'./accounts/show/' + split.destination_id" :title="split.destination_name">{{ split.destination_name }}</a><br />
</span>
</template>
<template #cell(menu)="data">
<div class="btn-group btn-group-sm">
<div class="dropdown">
<button class="btn btn-light btn-sm dropdown-toggle" type="button" :id="'dropdownMenuButton' + data.item.id" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="false">
{{ $t('firefly.actions') }}
</button>
<div class="dropdown-menu" :aria-labelledby="'dropdownMenuButton' + data.item.id">
<a class="dropdown-item" :href="'./transactions/edit/' + data.item.id"><span class="fa fas fa-pencil-alt"></span> {{
$t('firefly.edit')
}}</a>
<a class="dropdown-item" :href="'./transactions/delete/' + data.item.id"><span class="fa far fa-trash"></span> {{
$t('firefly.delete')
}}</a>
</div>
</div>
</div>
</template>
<template #cell(category_name)="data">
<!-- extra break for splits -->
<span v-if="true===data.item.split && !data.item.collapsed">
<br />
</span>
<em v-if="true===data.item.split && data.item.collapsed">
...
</em>
<!-- loop all categories, hidden if split -->
<span v-for="(split, index) in data.item.splits" v-bind:key="index" v-if="false===data.item.split || (true===data.item.split && !data.item.collapsed)">
<a :href="'./categories/show/' + split.category_id" :title="split.category_name">{{ split.category_name }}</a><br />
</span>
</template>
</BTable>
</div>
<div class="card-footer"> (button)
<!--
<a :href="'./transactions/create/' + type" class="btn btn-success"
:title="$t('firefly.create_new_transaction')">{{ $t('firefly.create_new_transaction') }}</a>
-->
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-8 col-md-6 col-sm-12 col-xs-12">
<BPagination
v-model="currentPage"
:total-rows="total"
:per-page="perPage"
aria-controls="my-table"
></BPagination>
</div>
<div class="col-lg-4 col-md-6 col-sm-12 col-xs-12">
<button @click="newCacheKey" class="btn btn-sm float-right btn-info"><span class="fas fa-sync"></span></button>
</div>
</div>
</div>
</template> </template>
<script> <script>
import {mapGetters, mapMutations} from "vuex";
import {BPagination, BTable} from 'bootstrap-vue';
import format from "date-fns/format";
export default { export default {
name: "TransactionListLarge", name: "TransactionListLarge",
components: {BPagination, BTable},
data() { data() {
return { return {
locale: 'en-US' locale: 'en-US',
fields: [],
currentPage: 1,
transactions: [],
loading: true
} }
}, },
computed: {
...mapGetters('root', ['listPageSize', 'cacheKey']),
},
created() { created() {
this.locale = localStorage.locale ?? 'en-US'; this.locale = localStorage.locale ?? 'en-US';
this.updateFieldList();
//this.currentPage = this.page;
this.parseTransactions();
}, },
watch: {
currentPage: function (value) {
// console.log('Watch currentPage go to ' + value);
this.$emit('jump-page', {page: value});
},
entries: function (value) {
this.parseTransactions();
},
},
methods: {
...mapMutations('root', ['refreshCacheKey',]),
parseTransactions: function () {
// console.log('Start of parseTransactions. Count of entries is ' + this.entries.length + ' and page is ' + this.page);
// console.log('Reported total is ' + this.total);
if (0 === this.entries.length) {
// console.log('Will not render now');
return;
}
// console.log('Now have ' + this.transactions.length + ' transactions');
for (let i = 0; i < this.total; i++) {
this.transactions.push({dummy: true});
// console.log('Push dummy to index ' + i);
// console.log('Now have ' + this.transactions.length + ' transactions');
}
// console.log('Generated ' + this.total + ' dummies');
// console.log('Now have ' + this.transactions.length + ' transactions');
let index = (this.page - 1) * this.perPage;
// console.log('Start index is ' + index);
for (let i in this.entries) {
let transaction = this.entries[i];
// build split
this.transactions[index] = this.parseTransaction(transaction);
// console.log('Push transaction to index ' + index);
// console.log('Now have ' + this.transactions.length + ' transactions');
index++;
}
// console.log('Added ' + this.entries.length + ' entries');
// console.log('Now have ' + this.transactions.length + ' transactions');
// console.log(this.transactions);
this.loading = false;
},
newCacheKey: function () {
alert('TODO');
this.refreshCacheKey();
},
updateFieldList: function () {
this.fields = [
{key: 'type', label: ' ', sortable: false},
{key: 'description', label: this.$t('list.description'), sortable: true},
{key: 'amount', label: this.$t('list.amount'), sortable: true},
{key: 'date', label: this.$t('list.date'), sortable: true},
{key: 'source_account', label: this.$t('list.source_account'), sortable: true},
{key: 'destination_account', label: this.$t('list.destination_account'), sortable: true},
{key: 'category_name', label: this.$t('list.category'), sortable: true},
{key: 'menu', label: ' ', sortable: false},
];
},
/**
* Parse a single transaction.
* @param transaction
*/
parseTransaction: function (transaction) {
let row = {};
// default values:
row.splits = [];
row.key = transaction.id;
row.id = transaction.id
row.dummy = false;
// pick this up from the first transaction
let first = transaction.attributes.transactions[0];
row.type = first.type;
row.date = new Date(first.date);
row.date_formatted = format(row.date, this.$t('config.month_and_day_fns'));
row.description = first.description;
row.collapsed = true;
row.split = false;
row.amount = 0;
row.currency_code = first.currency_code;
if (transaction.attributes.transactions.length > 1) {
row.split = true;
row.description = transaction.attributes.group_title;
}
// collapsed?
if (typeof transaction.collapsed !== 'undefined') {
row.collapsed = transaction.collapsed;
}
//console.log('is collapsed? ' + row.collapsed);
// then loop each split
for (let i in transaction.attributes.transactions) {
if (transaction.attributes.transactions.hasOwnProperty(i)) {
let info = transaction.attributes.transactions[i];
let split = {};
row.amount = row.amount + parseFloat(info.amount);
split.description = info.description;
split.amount = info.amount;
split.currency_code = info.currency_code;
split.source_name = info.source_name;
split.source_id = info.source_id;
split.destination_name = info.destination_name;
split.destination_id = info.destination_id;
split.category_id = info.category_id;
split.category_name = info.category_name;
split.split_index = i;
row.splits.push(split);
}
}
return row;
},
toggleCollapse: function (id) {
let transaction = this.transactions.filter(transaction => transaction.id === id)[0];
transaction.collapsed = !transaction.collapsed;
},
tableSortCompare: function (aRow, bRow, key, sortDesc, formatter, compareOptions, compareLocale) {
let a = aRow[key]
let b = bRow[key]
if (aRow.id === bRow.id) {
// Order split transactions normally when compared to each other, except always put the header first
if (aRow.split_parent === null) {
return sortDesc ? 1 : -1;
} else if (bRow.split_parent === null) {
return sortDesc ? -1 : 1;
}
} else {
// Sort split transactions based on their parent when compared to other transactions
if (aRow.split && aRow.split_parent !== null) {
a = aRow.split_parent[key]
}
if (bRow.split && bRow.split_parent !== null) {
b = bRow.split_parent[key]
}
}
if (
(typeof a === 'number' && typeof b === 'number') ||
(a instanceof Date && b instanceof Date)
) {
// If both compared fields are native numbers or both are native dates
return a < b ? -1 : a > b ? 1 : 0
} else {
// Otherwise stringify the field data and use String.prototype.localeCompare
return toString(a).localeCompare(toString(b), compareLocale, compareOptions)
}
function toString(value) {
if (value === null || typeof value === 'undefined') {
return ''
} else if (value instanceof Object) {
return Object.keys(value)
.sort()
.map(key => toString(value[key]))
.join(' ')
} else {
return String(value)
}
}
},
},
props: { props: {
transactions: { page: {
type: Number
},
perPage: {
type: Number,
default: 1
},
sortDesc: {
type: Boolean,
default: true
},
total: {
type: Number,
default: 1
},
entries: {
type: Array, type: Array,
default: function () { default: function () {
return []; return [];
} }
}, },
account_id: { accountId: {
type: Number, type: Number,
default: function () { default: function () {
return 0; return 0;

View File

@ -169,13 +169,13 @@
"repeat_freq_quarterly": "\u03c4\u03c1\u03b9\u03bc\u03b7\u03bd\u03b9\u03b1\u03af\u03c9\u03c2", "repeat_freq_quarterly": "\u03c4\u03c1\u03b9\u03bc\u03b7\u03bd\u03b9\u03b1\u03af\u03c9\u03c2",
"repeat_freq_monthly": "\u03bc\u03b7\u03bd\u03b9\u03b1\u03af\u03c9\u03c2", "repeat_freq_monthly": "\u03bc\u03b7\u03bd\u03b9\u03b1\u03af\u03c9\u03c2",
"repeat_freq_weekly": "\u03b5\u03b2\u03b4\u03bf\u03bc\u03b1\u03b4\u03b9\u03b1\u03af\u03c9\u03c2", "repeat_freq_weekly": "\u03b5\u03b2\u03b4\u03bf\u03bc\u03b1\u03b4\u03b9\u03b1\u03af\u03c9\u03c2",
"credit_card_type_monthlyFull": "Full payment every month", "credit_card_type_monthlyFull": "\u0395\u03be\u03cc\u03c6\u03bb\u03b7\u03c3\u03b7 \u03ba\u03ac\u03b8\u03b5 \u03bc\u03ae\u03bd\u03b1",
"update_liabilities_account": "\u0395\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7 \u03c5\u03c0\u03bf\u03c7\u03c1\u03ad\u03c9\u03c3\u03b7\u03c2", "update_liabilities_account": "\u0395\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7 \u03c5\u03c0\u03bf\u03c7\u03c1\u03ad\u03c9\u03c3\u03b7\u03c2",
"update_expense_account": "\u0395\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd \u03b4\u03b1\u03c0\u03b1\u03bd\u03ce\u03bd", "update_expense_account": "\u0395\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd \u03b4\u03b1\u03c0\u03b1\u03bd\u03ce\u03bd",
"update_revenue_account": "\u0395\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd \u03b5\u03c3\u03cc\u03b4\u03c9\u03bd", "update_revenue_account": "\u0395\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd \u03b5\u03c3\u03cc\u03b4\u03c9\u03bd",
"update_undefined_account": "Update account", "update_undefined_account": "\u0395\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd",
"update_asset_account": "\u0395\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd \u03ba\u03b5\u03c6\u03b1\u03bb\u03b1\u03af\u03bf\u03c5", "update_asset_account": "\u0395\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd \u03ba\u03b5\u03c6\u03b1\u03bb\u03b1\u03af\u03bf\u03c5",
"updated_account_js": "Updated account \"<a href=\"accounts\/show\/{ID}\">{title}<\/a>\"." "updated_account_js": "\u0395\u03bd\u03b7\u03bc\u03b5\u03c1\u03ce\u03b8\u03b7\u03ba\u03b5 \u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \"<a href=\"accounts\/show\/{ID}\">{title}<\/a>\"."
}, },
"list": { "list": {
"piggy_bank": "\u039a\u03bf\u03c5\u03bc\u03c0\u03b1\u03c1\u03ac\u03c2", "piggy_bank": "\u039a\u03bf\u03c5\u03bc\u03c0\u03b1\u03c1\u03ac\u03c2",

View File

@ -169,13 +169,13 @@
"repeat_freq_quarterly": "trimestriel", "repeat_freq_quarterly": "trimestriel",
"repeat_freq_monthly": "mensuel", "repeat_freq_monthly": "mensuel",
"repeat_freq_weekly": "hebdomadaire", "repeat_freq_weekly": "hebdomadaire",
"credit_card_type_monthlyFull": "Full payment every month", "credit_card_type_monthlyFull": "Paiement complet tous les mois",
"update_liabilities_account": "Mettre \u00e0 jour le passif", "update_liabilities_account": "Mettre \u00e0 jour le passif",
"update_expense_account": "Mettre \u00e0 jour le compte de d\u00e9penses", "update_expense_account": "Mettre \u00e0 jour le compte de d\u00e9penses",
"update_revenue_account": "Mettre \u00e0 jour le compte de recettes", "update_revenue_account": "Mettre \u00e0 jour le compte de recettes",
"update_undefined_account": "Update account", "update_undefined_account": "Mettre \u00e0 jour le compte",
"update_asset_account": "Mettre \u00e0 jour le compte d\u2019actif", "update_asset_account": "Mettre \u00e0 jour le compte d\u2019actif",
"updated_account_js": "Updated account \"<a href=\"accounts\/show\/{ID}\">{title}<\/a>\"." "updated_account_js": "Compte \"<a href=\"accounts\/show\/{ID}\">{title}<\/a>\" mis \u00e0 jour."
}, },
"list": { "list": {
"piggy_bank": "Tirelire", "piggy_bank": "Tirelire",

View File

@ -173,9 +173,9 @@
"update_liabilities_account": "\u30c1\u30e3\u30f3\u30cd\u30eb\u3092\u66f4\u65b0", "update_liabilities_account": "\u30c1\u30e3\u30f3\u30cd\u30eb\u3092\u66f4\u65b0",
"update_expense_account": "\u652f\u51fa\u5148\u30a2\u30ab\u30a6\u30f3\u30c8\uff08\u652f\u51fa\u5143\u30a2\u30ab\u30a6\u30f3\u30c8\n\uff09", "update_expense_account": "\u652f\u51fa\u5148\u30a2\u30ab\u30a6\u30f3\u30c8\uff08\u652f\u51fa\u5143\u30a2\u30ab\u30a6\u30f3\u30c8\n\uff09",
"update_revenue_account": "\u652f\u51fa\u30a2\u30ab\u30a6\u30f3\u30c8\uff08\u53ce\u5165\u30a2\u30ab\u30a6\u30f3\u30c8\uff09", "update_revenue_account": "\u652f\u51fa\u30a2\u30ab\u30a6\u30f3\u30c8\uff08\u53ce\u5165\u30a2\u30ab\u30a6\u30f3\u30c8\uff09",
"update_undefined_account": "Update account", "update_undefined_account": "\u53e3\u5ea7\u60c5\u5831\u306e\u66f4\u65b0",
"update_asset_account": "\u652f\u51fa\u30a2\u30ab\u30a6\u30f3\u30c8\uff08\u8cc7\u7523\u52d8\u5b9a\uff09", "update_asset_account": "\u652f\u51fa\u30a2\u30ab\u30a6\u30f3\u30c8\uff08\u8cc7\u7523\u52d8\u5b9a\uff09",
"updated_account_js": "Updated account \"<a href=\"accounts\/show\/{ID}\">{title}<\/a>\"." "updated_account_js": "\u53e3\u5ea7\u300c<a href=\"accounts\/show\/{ID}\">{title}<\/a>\u300d\u3092\u66f4\u65b0\u3057\u307e\u3057\u305f\u3002"
}, },
"list": { "list": {
"piggy_bank": "\u8caf\u91d1\u7bb1", "piggy_bank": "\u8caf\u91d1\u7bb1",

View File

@ -149,7 +149,7 @@
"bill_repeats_weekly_other": "Repeats every other week", "bill_repeats_weekly_other": "Repeats every other week",
"bill_repeats_monthly_other": "Repeats every other month", "bill_repeats_monthly_other": "Repeats every other month",
"bill_repeats_quarterly_other": "Repeats every other quarter", "bill_repeats_quarterly_other": "Repeats every other quarter",
"bill_repeats_half-year_other": "Repeats yearly", "bill_repeats_half-year_other": "\u041f\u043e\u0432\u0442\u043e\u0440\u044f\u0435\u0442\u0441\u044f \u0435\u0436\u0435\u0433\u043e\u0434\u043d\u043e",
"bill_repeats_yearly_other": "Repeats every other year", "bill_repeats_yearly_other": "Repeats every other year",
"bill_repeats_weekly_skip": "Repeats every {skip} weeks", "bill_repeats_weekly_skip": "Repeats every {skip} weeks",
"bill_repeats_monthly_skip": "Repeats every {skip} months", "bill_repeats_monthly_skip": "Repeats every {skip} months",

View File

@ -1,3 +1,23 @@
/*
* edit.js
* Copyright (c) 2021 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
require('../../bootstrap'); require('../../bootstrap');
import Edit from "../../components/accounts/Edit"; import Edit from "../../components/accounts/Edit";

View File

@ -21,6 +21,7 @@
require('../../bootstrap'); require('../../bootstrap');
import store from '../../components/store';
import Show from "../../components/accounts/Show"; import Show from "../../components/accounts/Show";
// i18n // i18n
@ -29,12 +30,22 @@ let i18n = require('../../i18n');
// get page name? // get page name?
let props = { let props = {};
};
const app = new Vue({ const app = new Vue({
i18n, i18n,
render(createElement) { store,
return createElement(Show, {props: props}); render(createElement) {
} return createElement(Show, {props: props});
}).$mount('#accounts_show'); },
beforeCreate() {
// See reference nr. 10
this.$store.commit('initialiseStore');
this.$store.dispatch('updateCurrencyPreference');
// init the new root store (dont care about results)
this.$store.dispatch('root/initialiseStore');
// also init the dashboard store.
this.$store.dispatch('dashboard/index/initialiseStore');
},
}).$mount('#accounts_show');

View File

@ -28,9 +28,9 @@ import MainBudgetList from '../components/dashboard/MainBudgetList';
import MainCreditList from '../components/dashboard/MainCreditList'; import MainCreditList from '../components/dashboard/MainCreditList';
import MainDebitList from '../components/dashboard/MainDebitList'; import MainDebitList from '../components/dashboard/MainDebitList';
import MainPiggyList from '../components/dashboard/MainPiggyList'; import MainPiggyList from '../components/dashboard/MainPiggyList';
import TransactionListLarge from '../components/transactions/TransactionListLarge'; import DashboardListLarge from '../components/dashboard/DashboardListLarge';
import TransactionListMedium from '../components/transactions/TransactionListMedium'; import DashboardListMedium from '../components/dashboard/DashboardListMedium';
import TransactionListSmall from '../components/transactions/TransactionListSmall'; import DashboardListSmall from '../components/dashboard/DashboardListSmall';
import Calendar from '../components/dashboard/Calendar'; import Calendar from '../components/dashboard/Calendar';
import MainCategoryList from '../components/dashboard/MainCategoryList'; import MainCategoryList from '../components/dashboard/MainCategoryList';
import Vue from 'vue'; import Vue from 'vue';
@ -48,9 +48,9 @@ import store from '../components/store';
require('../bootstrap'); require('../bootstrap');
require('chart.js'); require('chart.js');
Vue.component('transaction-list-large', TransactionListLarge); Vue.component('dashboard-list-large', DashboardListLarge);
Vue.component('transaction-list-medium', TransactionListMedium); Vue.component('dashboard-list-medium', DashboardListMedium);
Vue.component('transaction-list-small', TransactionListSmall); Vue.component('dashboard-list-small', DashboardListSmall);
// components as an example // components as an example

View File

@ -23,16 +23,12 @@ require('../../bootstrap');
import Vue from "vue"; import Vue from "vue";
import store from "../../components/store"; import store from "../../components/store";
import Index from "../../components/transactions/Index"; import Index from "../../components/transactions/Index";
import {BPagination, BTable} from 'bootstrap-vue';
import Calendar from "../../components/dashboard/Calendar"; import Calendar from "../../components/dashboard/Calendar";
// i18n // i18n
let i18n = require('../../i18n'); let i18n = require('../../i18n');
let props = {}; let props = {};
Vue.component('b-table', BTable);
Vue.component('b-pagination', BPagination);
const app = new Vue({ const app = new Vue({
i18n, i18n,
store, store,

58
frontend/src/serviceworker.js vendored Normal file
View File

@ -0,0 +1,58 @@
/*
* serviceworker.js
* Copyright (c) 2021 Lorenzo Breda (https://github.com/lbreda)
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
let staticCachePrefix = "firefly-III-"
let staticCacheName = staticCachePrefix + new Date().getTime();
let cachedFiles = [
'/offline',
'/v2/plugins/local-fonts/gf-source.css',
'/v2/css/app.css',
];
// Create cache on install
self.addEventListener("install", event => {
this.skipWaiting();
event.waitUntil(
caches.open(staticCacheName).then(cache => cache.addAll(cachedFiles))
)
});
// Clear cache on activate
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames
.filter(cacheName => (cacheName.startsWith(staticCachePrefix)))
.filter(cacheName => (cacheName !== staticCacheName))
.map(cacheName => caches.delete(cacheName))
);
})
);
});
// Serve from Cache or return the offline page
self.addEventListener("fetch", event => {
event.respondWith(
caches.match(event.request)
.then(response => (response || fetch(event.request)))
.catch(() => caches.match('offline'))
)
});

File diff suppressed because it is too large Load Diff

View File

@ -10,7 +10,7 @@
}, },
"devDependencies": { "devDependencies": {
"@johmun/vue-tags-input": "^2", "@johmun/vue-tags-input": "^2",
"@vue/compiler-sfc": "^3.1.5", "@vue/compiler-sfc": "^3.2.11",
"axios": "^0.21", "axios": "^0.21",
"bootstrap-sass": "^3", "bootstrap-sass": "^3",
"cross-env": "^7.0", "cross-env": "^7.0",
@ -21,7 +21,7 @@
"uiv": "^1.3", "uiv": "^1.3",
"vue": "^2.6", "vue": "^2.6",
"vue-i18n": "^8.25", "vue-i18n": "^8.25",
"vue-loader": "^16.3.1", "vue-loader": "^15",
"vue-template-compiler": "^2.6" "vue-template-compiler": "^2.6"
} }
} }

View File

@ -1,7 +1,7 @@
# Please report security issues related to Firefly III # For this (sub)domain, report Firefly III security issues ONLY
# to me ASAP via the email address below. # Other reports will be IGNORED
Contact: mailto:thegrumpydictator@gmail.com Contact: mailto:james@firefly-iii.org
Encryption: https://keybase.io/jc5/pgp_keys.asc?fingerprint=90f546f13b81b67a1baa5dddc16961e655e74b5e Encryption: https://keybase.io/jc5/pgp_keys.asc?fingerprint=5d22ce912243bea5185ab2289d53bf7bdccbf49e
Acknowledgements: https://github.com/firefly-iii/firefly-iii Acknowledgements: https://github.com/firefly-iii/firefly-iii
Signature: https://firefly-iii.org/.well-known/security.txt.sig Signature: https://firefly-iii.org/.well-known/security.txt.sig

View File

@ -3,15 +3,83 @@
"short_name": "Firefly III", "short_name": "Firefly III",
"start_url": "/", "start_url": "/",
"icons": [ "icons": [
{
"src": "/maskable72.png",
"sizes": "72x72",
"type": "image/png",
"scope": "maskable"
},
{
"src": "/maskable76.png",
"sizes": "76x76",
"type": "image/png",
"scope": "maskable"
},
{
"src": "/maskable96.png",
"sizes": "96x96",
"type": "image/png",
"scope": "maskable"
},
{
"src": "/maskable120.png",
"sizes": "120x120",
"type": "image/png",
"scope": "maskable"
},
{
"src": "/maskable128.png",
"sizes": "128x128",
"type": "image/png",
"scope": "maskable"
},
{
"src": "/maskable144.png",
"sizes": "144x144",
"type": "image/png",
"scope": "maskable"
},
{
"src": "/maskable152.png",
"sizes": "152x152",
"type": "image/png",
"scope": "maskable"
},
{
"src": "/maskable180.png",
"sizes": "180x180",
"type": "image/png",
"scope": "maskable"
},
{ {
"src": "/android-chrome-192x192.png", "src": "/android-chrome-192x192.png",
"sizes": "192x192", "sizes": "192x192",
"type": "image/png" "type": "image/png",
"scope": "any"
},
{
"src": "/maskable192.png",
"sizes": "192x192",
"type": "image/png",
"scope": "maskable"
},
{
"src": "/maskable384.png",
"sizes": "384x384",
"type": "image/png",
"scope": "maskable"
}, },
{ {
"src": "/android-chrome-512x512.png", "src": "/android-chrome-512x512.png",
"sizes": "512x512", "sizes": "512x512",
"type": "image/png" "type": "image/png",
"scope": "any"
},
{
"src": "/maskable512.png",
"sizes": "512x512",
"type": "image/png",
"scope": "maskable"
} }
], ],
"theme_color": "#1e6581", "theme_color": "#1e6581",

Some files were not shown because too many files have changed in this diff Show More