Merge branch 'release/5.6.3'

This commit is contained in:
James Cole 2021-11-12 20:27:11 +01:00
commit 7afe9fac0a
150 changed files with 2976 additions and 2558 deletions

View File

@ -177,6 +177,12 @@ MAP_DEFAULT_ZOOM=6
# https://docs.firefly-iii.org/advanced-installation/authentication
AUTHENTICATION_GUARD=web
#
# Your LDAP server may speak a dialect. You can choose between 'OpenLDAP' and 'ActiveDirectory'
# Anything else defaults to 'ActiveDirectory'
#
LDAP_DIALECT=OpenLDAP
#
# LDAP connection settings:
#
@ -193,14 +199,13 @@ LDAP_PASSWORD=super_secret
LDAP_AUTH_FIELD=uid
#
# If you wish to only authenticate users from a specific group, use the
# group filter. Leave empty or remove if not in use.
# If you wish to only authenticate users from a specific group, use the base DN above.
#
# Example: cn=Administrators,dc=local,dc=com
# If you require extra/special filters please use the LDAP_EXTRA_FILTER with a valid DN.
#
# The group filter will only be applied after the user is authenticated.
# The extra filter will only be applied after the user is authenticated.
#
LDAP_GROUP_FILTER=
LDAP_EXTRA_FILTER=
#
# Remote user guard settings
@ -297,6 +302,8 @@ FIREFLY_III_LAYOUT=v1
# It won't work. It doesn't do ANYTHING. Don't believe the lies you read online. I'm not joking.
# This configuration value WILL NOT HELP.
#
# Notable exception to this rule is Synology, which, according to some users, will use APP_URL to rewrite stuff.
#
# This variable is ONLY used in some of the emails Firefly III sends around. Nowhere else.
# So when configuring anything WEB related this variable doesn't do anything. Nothing
#

View File

@ -1,7 +0,0 @@
pull_request_rules:
- name: PR on main is never approved.
conditions:
- base=main
actions:
close:
message: Please reopen this PR on the `develop` branch. Thank you.

4
.github/stale.yml vendored
View File

@ -1,11 +1,11 @@
# Configuration for probot-stale - https://github.com/probot/stale
# Number of days of inactivity before an Issue or Pull Request becomes stale
daysUntilStale: 7
daysUntilStale: 14
# Number of days of inactivity before a stale Issue or Pull Request is closed.
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
daysUntilClose: 7
daysUntilClose: 14
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
# - "[Status] Maybe Later"

View File

@ -1,58 +0,0 @@
---
build:
nodes:
analysis:
project_setup:
override: true
tests:
override:
- php-scrutinizer-run
checks:
javascript: true
php:
align_assignments: true
avoid_fixme_comments: true
avoid_multiple_statements_on_same_line: true
avoid_perl_style_comments: true
avoid_todo_comments: true
duplication: false
encourage_single_quotes: true
newline_at_end_of_file: true
no_goto: true
no_long_variable_names:
maximum: "20"
no_short_method_names:
minimum: "3"
no_short_variable_names:
minimum: "3"
optional_parameters_at_the_end: true
parameter_doc_comments: true
remove_extra_empty_lines: true
return_doc_comment_if_not_inferrable: true
return_doc_comments: true
uppercase_constants: true
use_self_instead_of_fqcn: true
coding_style:
php:
spaces:
around_operators:
concatenation: true
other:
after_type_cast: false
filter:
excluded_paths:
- database/migrations/*
- bootstrap/*
- config/*
- docker/*
- public/js/lib/*
- public/lib/adminlte/js/*
- public/lib/bootstrap/js/*
- resources/*
- routes/*
- storage/*
paths:
- app/*
- public/js/ff/*
tools:
external_code_coverage: false

View File

@ -1,20 +0,0 @@
language: php
php:
- '7.4'
dist: xenial
os: linux
cache:
directories:
- "/home/travis/.config"
- "/home/travis/build/firefly-iii/firefly-iii/vendor"
branches:
only:
- develop
before_script:
- phpenv config-rm xdebug.ini || return 0
script:
- "./.ci/phpstan.sh"
- "./.ci/phpunit.sh"

View File

@ -211,7 +211,7 @@ class StoreRequest extends FormRequest
// budget, category, bill and piggy
'transactions.*.budget_id' => ['mustExist:budgets,id', new BelongsUser],
'transactions.*.budget_name' => ['between:1,255', 'nullable', new BelongsUser],
'transactions.*.category_id' => ['mustExist:categories,id', new BelongsUser],
'transactions.*.category_id' => ['mustExist:categories,id', new BelongsUser,'nullable'],
'transactions.*.category_name' => 'between:1,255|nullable',
'transactions.*.bill_id' => ['numeric', 'nullable', 'mustExist:bills,id', new BelongsUser],
'transactions.*.bill_name' => ['between:1,255', 'nullable', new BelongsUser],

View File

@ -154,6 +154,7 @@ class ExportData extends Command
/**
* @return array
* @throws FireflyException
* @throws Exception
*/
private function parseOptions(): array
{
@ -201,12 +202,17 @@ class ExportData extends Command
$error = true;
}
}
if(null === $this->option($field)) {
Log::info(sprintf('No date given in field "%s"', $field));
$error = true;
}
if (true === $error && 'start' === $field) {
$journal = $this->journalRepository->firstNull();
$date = null === $journal ? Carbon::now()->subYear() : $journal->date;
$date->startOfDay();
}
if (true === $error && 'end' === $field) {
$date = today(config('app.timezone'));
$date->endOfDay();

View File

@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
/*
* ActuallyLoggedIn.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/>.
*/
namespace FireflyIII\Events;
use FireflyIII\User;
use Illuminate\Queue\SerializesModels;
/**
* Class ActuallyLoggedIn
*/
class ActuallyLoggedIn extends Event
{
use SerializesModels;
public User $user;
/**
* @param User $user
*/
public function __construct(User $user)
{
$this->user = $user;
}
}

View File

@ -25,6 +25,7 @@ namespace FireflyIII\Handlers\Events;
use Carbon\Carbon;
use Exception;
use FireflyIII\Events\ActuallyLoggedIn;
use FireflyIII\Events\DetectedNewIPAddress;
use FireflyIII\Events\RegisteredUser;
use FireflyIII\Events\RequestedNewPassword;
@ -116,9 +117,24 @@ class UserEventHandler
*/
public function createGroupMembership(RegisteredUser $event): bool
{
$user = $event->user;
$user = $event->user;
$groupExists = true;
$groupTitle = $user->email;
$index = 1;
// create a new group.
$group = UserGroup::create(['title' => $user->email]);
while (true === $groupExists) {
$groupExists = UserGroup::where('title', $groupTitle)->count() > 0;
if(false === $groupExists) {
$group = UserGroup::create(['title' => $groupTitle]);
break;
}
$groupTitle = sprintf('%s-%d', $user->email, $index);
$index++;
if($index > 99) {
throw new FireflyException('Email address can no longer be used for registrations.');
}
}
$role = UserRole::where('title', UserRole::OWNER)->first();
if (null === $role) {
throw new FireflyException('The user role is unexpectedly empty. Did you run all migrations?');
@ -317,12 +333,11 @@ class UserEventHandler
}
/**
* @param Login $event
*
* @throws FireflyException
* @param ActuallyLoggedIn $event
*/
public function storeUserIPAddress(Login $event): void
public function storeUserIPAddress(ActuallyLoggedIn $event): void
{
Log::debug('Now in storeUserIPAddress');
/** @var User $user */
$user = $event->user;
/** @var array $preference */

View File

@ -79,7 +79,7 @@ class NetWorth implements NetWorthInterface
$netWorth = [];
$result = [];
Log::debug(sprintf('Now in getNetWorthByCurrency(%s)', $date->format('Y-m-d')));
//Log::debug(sprintf('Now in getNetWorthByCurrency(%s)', $date->format('Y-m-d')));
// get default currency
$default = app('amount')->getDefaultCurrencyByUser($this->user);
@ -90,16 +90,16 @@ class NetWorth implements NetWorthInterface
// get the preferred currency for this account
/** @var Account $account */
foreach ($accounts as $account) {
Log::debug(sprintf('Now at account #%d: "%s"', $account->id, $account->name));
//Log::debug(sprintf('Now at account #%d: "%s"', $account->id, $account->name));
$currencyId = (int)$this->accountRepository->getMetaValue($account, 'currency_id');
$currencyId = 0 === $currencyId ? $default->id : $currencyId;
Log::debug(sprintf('Currency ID is #%d', $currencyId));
//Log::debug(sprintf('Currency ID is #%d', $currencyId));
// balance in array:
$balance = $balances[$account->id] ?? '0';
Log::debug(sprintf('Balance is %s', $balance));
//Log::debug(sprintf('Balance is %s', $balance));
// always subtract virtual balance.
$virtualBalance = (string)$account->virtual_balance;
@ -107,14 +107,14 @@ class NetWorth implements NetWorthInterface
$balance = bcsub($balance, $virtualBalance);
}
Log::debug(sprintf('Balance corrected to %s because of virtual balance (%s)', $balance, $virtualBalance));
//Log::debug(sprintf('Balance corrected to %s because of virtual balance (%s)', $balance, $virtualBalance));
if (!array_key_exists($currencyId, $netWorth)) {
$netWorth[$currencyId] = '0';
}
$netWorth[$currencyId] = bcadd($balance, $netWorth[$currencyId]);
Log::debug(sprintf('Total net worth for currency #%d is %s', $currencyId, $netWorth[$currencyId]));
//Log::debug(sprintf('Total net worth for currency #%d is %s', $currencyId, $netWorth[$currencyId]));
}
ksort($netWorth);

View File

@ -209,10 +209,10 @@ class PopupReport implements PopupReportInterface
$collector = app(GroupCollectorInterface::class);
// set report accounts + the request accounts:
$set = $attributes['accounts'] ?? new Collection;
$set->push($account);
//$set = $attributes['accounts'] ?? new Collection;
//$set->push($account);
$collector->setBothAccounts($set)
$collector->setDestinationAccounts(new Collection([$account]))
->setRange($attributes['startDate'], $attributes['endDate'])
->withAccountInformation()
->withBudgetInformation()
@ -222,7 +222,6 @@ class PopupReport implements PopupReportInterface
if (null !== $currency) {
$collector->setCurrency($currency);
}
return $collector->getExtractedJournals();
}

View File

@ -101,7 +101,7 @@ class IndexController extends Controller
$accounts->each(
function (Account $account) use ($activities, $startBalances, $endBalances) {
$account->lastActivityDate = $this->isInArray($activities, $account->id);
$account->lastActivityDate = $this->isInArrayDate($activities, $account->id);
$account->startBalance = $this->isInArray($startBalances, $account->id);
$account->endBalance = $this->isInArray($endBalances, $account->id);
$account->difference = bcsub($account->endBalance, $account->startBalance);
@ -163,7 +163,7 @@ class IndexController extends Controller
$accounts->each(
function (Account $account) use ($activities, $startBalances, $endBalances) {
// See reference nr. 68
$account->lastActivityDate = $this->isInArray($activities, $account->id);
$account->lastActivityDate = $this->isInArrayDate($activities, $account->id);
$account->startBalance = $this->isInArray($startBalances, $account->id);
$account->endBalance = $this->isInArray($endBalances, $account->id);
$account->difference = bcsub($account->endBalance, $account->startBalance);

View File

@ -25,6 +25,7 @@ namespace FireflyIII\Http\Controllers\Auth;
use Adldap;
use Cookie;
use DB;
use FireflyIII\Events\ActuallyLoggedIn;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Providers\RouteServiceProvider;
@ -119,6 +120,10 @@ class LoginController extends Controller
// if you just logged in, it can't be that you have a valid 2FA cookie.
// send a custom login event because laravel will also fire a login event if a "remember me"-cookie
// restores the event.
event(new ActuallyLoggedIn($this->guard()->user()));
return $this->sendLoginResponse($request);
}
Log::warning('Login attempt failed.');

View File

@ -59,7 +59,6 @@ class ReportController extends Controller
'category-entry' => $this->categoryEntry($attributes),
'budget-entry' => $this->budgetEntry($attributes),
};
return response()->json(['html' => $html]);
}
}

View File

@ -648,10 +648,9 @@ class CategoryController extends Controller
* @param Carbon $start
* @param Carbon $end
*
* @return mixed|string
* @throws JsonException
* @return string
*/
public function operations(Collection $accounts, Carbon $start, Carbon $end)
public function operations(Collection $accounts, Carbon $start, Carbon $end): string
{
// chart properties for cache:
$cache = new CacheProperties;
@ -673,7 +672,7 @@ class CategoryController extends Controller
try {
$result = prefixView('reports.partials.categories', compact('report'))->render();
$result = (string)prefixView('reports.partials.categories', compact('report'))->render();
$cache->store($result);
} catch (Throwable $e) { // @phpstan-ignore-line
Log::error(sprintf('Could not render category::expenses: %s', $e->getMessage()));

View File

@ -34,6 +34,7 @@ use FireflyIII\Support\Http\Controllers\ModelInformation;
use FireflyIII\Support\Http\Controllers\RuleManagement;
use FireflyIII\Support\Search\SearchInterface;
use Illuminate\Contracts\View\Factory;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Redirector;
@ -237,15 +238,17 @@ class CreateController extends Controller
/**
* @param Rule $rule
*
* @return RedirectResponse
* @return JsonResponse
*/
public function duplicate(Rule $rule): RedirectResponse
public function duplicate(Request $request): JsonResponse
{
$newRule = $this->ruleRepos->duplicate($rule);
$ruleId = (int)$request->get('id');
$rule = $this->ruleRepos->find($ruleId);
if (null !== $rule) {
$this->ruleRepos->duplicate($rule);
}
session()->flash('success', trans('firefly.duplicated_rule', ['title' => $rule->title, 'newTitle' => $newRule->title]));
return redirect(route('rules.index'));
return new JsonResponse(['OK']);
}
/**

View File

@ -28,6 +28,7 @@ use FireflyIII\Http\Requests\RuleGroupFormRequest;
use FireflyIII\Models\RuleGroup;
use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface;
use Illuminate\Contracts\View\Factory;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Redirector;
@ -62,24 +63,38 @@ class EditController extends Controller
}
/**
* Move a rule group down.
* Move a rule group in either direction.
*
* @param RuleGroup $ruleGroup
* @param Request $request
*
* @return RedirectResponse|Redirector
* @return JsonResponse
*/
public function down(RuleGroup $ruleGroup)
public function moveGroup(Request $request): JsonResponse
{
$maxOrder = $this->repository->maxOrder();
$order = (int)$ruleGroup->order;
if ($order < $maxOrder) {
$newOrder = $order + 1;
$this->repository->setOrder($ruleGroup, $newOrder);
$groupId = (int)$request->get('id');
$ruleGroup= $this->repository->find($groupId);
if(null !== $ruleGroup) {
$direction = $request->get('direction');
if('down' === $direction) {
$maxOrder = $this->repository->maxOrder();
$order = (int)$ruleGroup->order;
if ($order < $maxOrder) {
$newOrder = $order + 1;
$this->repository->setOrder($ruleGroup, $newOrder);
}
}
if('up' === $direction) {
$order = (int)$ruleGroup->order;
if ($order > 1) {
$newOrder = $order - 1;
$this->repository->setOrder($ruleGroup, $newOrder);
}
}
}
return redirect(route('rules.index'));
return new JsonResponse(['OK']);
}
/**
* Edit a rule group.
*
@ -106,25 +121,6 @@ class EditController extends Controller
return prefixView('rules.rule-group.edit', compact('ruleGroup', 'subTitle'));
}
/**
* Move the rule group up.
*
* @param RuleGroup $ruleGroup
*
* @return RedirectResponse|Redirector
*
*/
public function up(RuleGroup $ruleGroup)
{
$order = (int)$ruleGroup->order;
if ($order > 1) {
$newOrder = $order - 1;
$this->repository->setOrder($ruleGroup, $newOrder);
}
return redirect(route('rules.index'));
}
/**
* Update the rule group.
*

View File

@ -32,9 +32,6 @@ use Illuminate\Contracts\Config\Repository;
*/
class TrustProxies extends Middleware
{
/** @var int The headers to check. */
//protected $headers = Request::HEADER_X_FORWARDED_ALL;
/**
* TrustProxies constructor.
*
@ -42,11 +39,7 @@ class TrustProxies extends Middleware
*/
public function __construct(Repository $config)
{
$trustedProxies = (string)config('firefly.trusted_proxies');
$this->proxies = explode(',', $trustedProxies);
if ('**' === $trustedProxies) {
$this->proxies = '**';
}
$this->proxies = (string)config('firefly.trusted_proxies');
parent::__construct($config);
}
}

View File

@ -69,7 +69,7 @@ class BillStoreRequest extends FormRequest
'transaction_currency_id' => 'required|exists:transaction_currencies,id',
'date' => 'required|date',
'repeat_freq' => 'required|in:weekly,monthly,quarterly,half-year,yearly',
'skip' => 'required|between:0,31',
'skip' => 'required|integer|gte:0|lte:31',
'active' => 'boolean',
];
}

View File

@ -73,7 +73,7 @@ class BillUpdateRequest extends FormRequest
'transaction_currency_id' => 'required|exists:transaction_currencies,id',
'date' => 'required|date',
'repeat_freq' => 'required|in:weekly,monthly,quarterly,half-year,yearly',
'skip' => 'required|between:0,31',
'skip' => 'required|integer|gte:0|lte:31',
'active' => 'boolean',
];
}

View File

@ -63,7 +63,7 @@ class BudgetFormStoreRequest extends FormRequest
return [
'name' => 'required|between:1,100|uniqueObjectForUser:budgets,name',
'active' => 'numeric|between:0,1',
'auto_budget_type' => 'numeric|between:0,2',
'auto_budget_type' => 'numeric|integer|gte:0|lte:2',
'auto_budget_currency_id' => 'exists:transaction_currencies,id',
'auto_budget_amount' => 'min:0|max:1000000000|required_if:auto_budget_type,1|required_if:auto_budget_type,2',
'auto_budget_period' => 'in:daily,weekly,monthly,quarterly,half_year,yearly',

View File

@ -73,7 +73,7 @@ class BudgetFormUpdateRequest extends FormRequest
return [
'name' => $nameRule,
'active' => 'numeric|between:0,1',
'auto_budget_type' => 'numeric|between:0,2',
'auto_budget_type' => 'numeric|integer|gte:0|lte:31',
'auto_budget_currency_id' => 'exists:transaction_currencies,id',
'auto_budget_amount' => 'min:0|max:1000000000|required_if:auto_budget_type,1|required_if:auto_budget_type,2',
'auto_budget_period' => 'in:daily,weekly,monthly,quarterly,half_year,yearly',

View File

@ -192,7 +192,7 @@ class RecurrenceFormRequest extends FormRequest
'title' => 'required|between:1,255|uniqueObjectForUser:recurrences,title',
'first_date' => 'required|date|after:' . $today->format('Y-m-d'),
'repetition_type' => ['required', new ValidRecurrenceRepetitionValue, new ValidRecurrenceRepetitionType, 'between:1,20'],
'skip' => 'required|numeric|between:0,31',
'skip' => 'required|numeric|integer|gte:0|lte:31',
// optional for recurrence:
'recurring_description' => 'between:0,65000',

View File

@ -431,7 +431,7 @@ class CreateRecurringTransactions implements ShouldQueue
'user' => $recurrence->user_id,
'currency_id' => (int)$transaction->transaction_currency_id,
'currency_code' => null,
'description' => $recurrence->recurrenceTransactions()->first()->description,
'description' => $transactions->first()->description,
'amount' => $transaction->amount,
'budget_id' => $this->repository->getBudget($transaction),
'budget_name' => null,
@ -452,7 +452,7 @@ class CreateRecurringTransactions implements ShouldQueue
'tags' => $this->repository->getTags($transaction),
'piggy_bank_id' => $this->repository->getPiggyBank($transaction),
'piggy_bank_name' => null,
'bill_id' => null,
'bill_id' => $this->repository->getBillId($transaction),
'bill_name' => null,
'recurrence_total' => $total,
'recurrence_count' => $count,

View File

@ -4,7 +4,8 @@ declare(strict_types=1);
namespace FireflyIII\Ldap\Rules;
use LdapRecord\Laravel\Auth\Rule;
use LdapRecord\Models\ActiveDirectory\Group;
use LdapRecord\Models\Attributes\DistinguishedName;
use LdapRecord\Query\ObjectNotFoundException;
use Log;
/**
@ -16,22 +17,51 @@ class UserDefinedRule extends Rule
* Check if the rule passes validation.
*
* @return bool
* @throws ObjectNotFoundException
*/
public function isValid()
{
// LDAP_GROUP_FILTER
$groupFilter = config('ldap.group_filter');
Log::debug(sprintf('UserDefinedRule with group filter "%s"', $groupFilter));
if (null !== $groupFilter && '' !== (string)$groupFilter) {
Log::debug('Group filter is not empty, will now apply it.');
$administrators = Group::find($groupFilter);
$result = $this->user->groups()->recursive()->exists($administrators);
Log::debug(sprintf('Search result is %s.', var_export($result, true)));
$extraFilter = config('ldap.extra_filter');
Log::debug(sprintf('UserDefinedRule with extra filter "%s"', $extraFilter));
return $result;
if (empty($extraFilter)) {
Log::debug('Extra filter is empty, return true.');
return true;
}
Log::debug('Group filter is empty or NULL, so will return true.');
Log::debug('Extra filter is not empty, continue.');
return true;
// group class:
// use ;
$openLDAP = class_exists(\LdapRecord\Models\OpenLDAP\Group::class) ? \LdapRecord\Models\OpenLDAP\Group::class : '';
$activeDirectory = class_exists(\LdapRecord\Models\ActiveDirectory\Group::class) ? \LdapRecord\Models\ActiveDirectory\Group::class : '';
$groupClass = config('ldap.dialect') === 'OpenLDAP' ? $openLDAP : $activeDirectory;
Log::debug(sprintf('Will use dialect group class "%s"', $groupClass));
// We've been given an invalid group filter. We will assume the
// developer is using some group ANR attribute, and attempt
// to check the user's membership with the resulting group.
if (!DistinguishedName::isValid($extraFilter)) {
Log::debug('UserDefinedRule: Is not valid DN');
return $this->user->groups()->recursive()->exists($groupClass::findByAnrOrFail($extraFilter));
}
$head = strtolower(DistinguishedName::make($extraFilter)->head());
Log::debug(sprintf('UserDefinedRule: Head is "%s"', $head));
// If the head of the DN we've been given is an OU, we will assume
// the developer is looking to filter users based on hierarchy.
// Otherwise, we'll attempt locating a group by the given
// group filter and checking the users group membership.
if ('ou' === $head) {
Log::debug('UserDefinedRule: Will return if user is a descendant of.');
return $this->user->isDescendantOf($extraFilter);
}
Log::debug('UserDefinedRule: Will return if user exists in group.');
return $this->user->groups()->recursive()->exists($groupClass::findOrFail($extraFilter));
}
}

View File

@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace FireflyIII\Ldap\Scopes;
use LdapRecord\Models\Model;
use LdapRecord\Models\Scope;
use LdapRecord\Query\Model\Builder;
use Log;
/**
* Class UserDefinedScope
*/
class UserDefinedScope implements Scope
{
/**
* Apply the scope to the given query.
*
* @param Builder $query
* @param Model $model
*
* @return void
*/
public function apply(Builder $query, Model $model)
{
}
}

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Providers;
use Exception;
use FireflyIII\Events\ActuallyLoggedIn;
use FireflyIII\Events\AdminRequestedTestMessage;
use FireflyIII\Events\DestroyedTransactionGroup;
use FireflyIII\Events\DetectedNewIPAddress;
@ -74,6 +75,8 @@ class EventServiceProvider extends ServiceProvider
Login::class => [
'FireflyIII\Handlers\Events\UserEventHandler@checkSingleUserIsAdmin',
'FireflyIII\Handlers\Events\UserEventHandler@demoUserBackToEnglish',
],
ActuallyLoggedIn::class => [
'FireflyIII\Handlers\Events\UserEventHandler@storeUserIPAddress',
],
DetectedNewIPAddress::class => [

View File

@ -251,7 +251,7 @@ class BillRepository implements BillRepositoryInterface
$journalIds = $set->pluck('id')->toArray();
$amount = (string)Transaction::whereIn('transaction_journal_id', $journalIds)->where('amount', '<', 0)->sum('amount');
$sum = bcadd($sum, $amount);
Log::debug(sprintf('Total > 0, so add to sum %f, which becomes %f', $amount, $sum));
//Log::debug(sprintf('Total > 0, so add to sum %f, which becomes %f', $amount, $sum));
}
}
@ -281,7 +281,7 @@ class BillRepository implements BillRepositoryInterface
$amount = (string)Transaction::whereIn('transaction_journal_id', $journalIds)->where('amount', '<', 0)->sum('amount');
$return[$currencyId] = $return[$currencyId] ?? '0';
$return[$currencyId] = bcadd($amount, $return[$currencyId]);
Log::debug(sprintf('Total > 0, so add to sum %f, which becomes %f (currency %d)', $amount, $return[$currencyId], $currencyId));
//Log::debug(sprintf('Total > 0, so add to sum %f, which becomes %f (currency %d)', $amount, $return[$currencyId], $currencyId));
}
}
@ -302,18 +302,18 @@ class BillRepository implements BillRepositoryInterface
$sum = '0';
/** @var Bill $bill */
foreach ($bills as $bill) {
Log::debug(sprintf('Now at bill #%d (%s)', $bill->id, $bill->name));
//Log::debug(sprintf('Now at bill #%d (%s)', $bill->id, $bill->name));
$dates = $this->getPayDatesInRange($bill, $start, $end);
$count = $bill->transactionJournals()->after($start)->before($end)->count();
$total = $dates->count() - $count;
Log::debug(sprintf('Dates = %d, journalCount = %d, total = %d', $dates->count(), $count, $total));
//Log::debug(sprintf('Dates = %d, journalCount = %d, total = %d', $dates->count(), $count, $total));
if ($total > 0) {
$average = bcdiv(bcadd($bill->amount_max, $bill->amount_min), '2');
$multi = bcmul($average, (string)$total);
$sum = bcadd($sum, $multi);
Log::debug(sprintf('Total > 0, so add to sum %f, which becomes %f', $multi, $sum));
//Log::debug(sprintf('Total > 0, so add to sum %f, which becomes %f', $multi, $sum));
}
}
@ -334,20 +334,20 @@ class BillRepository implements BillRepositoryInterface
$return = [];
/** @var Bill $bill */
foreach ($bills as $bill) {
Log::debug(sprintf('Now at bill #%d (%s)', $bill->id, $bill->name));
//Log::debug(sprintf('Now at bill #%d (%s)', $bill->id, $bill->name));
$dates = $this->getPayDatesInRange($bill, $start, $end);
$count = $bill->transactionJournals()->after($start)->before($end)->count();
$total = $dates->count() - $count;
$currencyId = (int)$bill->transaction_currency_id;
Log::debug(sprintf('Dates = %d, journalCount = %d, total = %d', $dates->count(), $count, $total));
//Log::debug(sprintf('Dates = %d, journalCount = %d, total = %d', $dates->count(), $count, $total));
if ($total > 0) {
$average = bcdiv(bcadd($bill->amount_max, $bill->amount_min), '2');
$multi = bcmul($average, (string)$total);
$return[$currencyId] = $return[$currencyId] ?? '0';
$return[$currencyId] = bcadd($return[$currencyId], $multi);
Log::debug(sprintf('Total > 0, so add to sum %f, which becomes %f (for currency %d)', $multi, $return[$currencyId], $currencyId));
//Log::debug(sprintf('Total > 0, so add to sum %f, which becomes %f (for currency %d)', $multi, $return[$currencyId], $currencyId));
}
}
@ -481,7 +481,7 @@ class BillRepository implements BillRepositoryInterface
//Log::debug(sprintf('First currentstart is %s', $currentStart->format('Y-m-d')));
while ($currentStart <= $end) {
Log::debug(sprintf('Currentstart is now %s.', $currentStart->format('Y-m-d')));
//Log::debug(sprintf('Currentstart is now %s.', $currentStart->format('Y-m-d')));
$nextExpectedMatch = $this->nextDateMatch($bill, $currentStart);
//Log::debug(sprintf('Next Date match after %s is %s', $currentStart->format('Y-m-d'), $nextExpectedMatch->format('Y-m-d')));
if ($nextExpectedMatch > $end) {// If nextExpectedMatch is after end, we continue

View File

@ -35,8 +35,7 @@ use Illuminate\Support\Collection;
*/
class OperationsRepository implements OperationsRepositoryInterface
{
/** @var User */
private $user;
private User $user;
/**
* This method returns a list of all the withdrawal transaction journals (as arrays) set in that period
@ -340,4 +339,136 @@ class OperationsRepository implements OperationsRepositoryInterface
{
return $this->user->categories()->get();
}
/**
* @inheritDoc
*/
public function listTransferredIn(Carbon $start, Carbon $end, Collection $accounts, ?Collection $categories = null): array
{
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setUser($this->user)->setRange($start, $end)->setTypes([TransactionType::TRANSFER])
->setDestinationAccounts($accounts)->excludeSourceAccounts($accounts);
if (null !== $categories && $categories->count() > 0) {
$collector->setCategories($categories);
}
if (null === $categories || (null !== $categories && 0 === $categories->count())) {
$collector->setCategories($this->getCategories());
}
$collector->withCategoryInformation()->withAccountInformation()->withBudgetInformation();
$journals = $collector->getExtractedJournals();
$array = [];
foreach ($journals as $journal) {
$currencyId = (int)$journal['currency_id'];
$categoryId = (int)$journal['category_id'];
$categoryName = (string)$journal['category_name'];
// catch "no category" entries.
if (0 === $categoryId) {
continue;
}
// info about the currency:
$array[$currencyId] = $array[$currencyId] ?? [
'categories' => [],
'currency_id' => $currencyId,
'currency_name' => $journal['currency_name'],
'currency_symbol' => $journal['currency_symbol'],
'currency_code' => $journal['currency_code'],
'currency_decimal_places' => $journal['currency_decimal_places'],
];
// info about the categories:
$array[$currencyId]['categories'][$categoryId] = $array[$currencyId]['categories'][$categoryId] ?? [
'id' => $categoryId,
'name' => $categoryName,
'transaction_journals' => [],
];
// add journal to array:
// only a subset of the fields.
$journalId = (int)$journal['transaction_journal_id'];
$array[$currencyId]['categories'][$categoryId]['transaction_journals'][$journalId] = [
'amount' => app('steam')->positive($journal['amount']),
'date' => $journal['date'],
'source_account_id' => $journal['source_account_id'],
'category_name' => $journal['category_name'],
'source_account_name' => $journal['source_account_name'],
'destination_account_id' => $journal['destination_account_id'],
'destination_account_name' => $journal['destination_account_name'],
'description' => $journal['description'],
'transaction_group_id' => $journal['transaction_group_id'],
];
}
return $array;
}
/**
* @inheritDoc
*/
public function listTransferredOut(Carbon $start, Carbon $end, Collection $accounts, ?Collection $categories = null): array
{
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setUser($this->user)->setRange($start, $end)->setTypes([TransactionType::TRANSFER])
->setSourceAccounts($accounts)->excludeDestinationAccounts($accounts);
if (null !== $categories && $categories->count() > 0) {
$collector->setCategories($categories);
}
if (null === $categories || (null !== $categories && 0 === $categories->count())) {
$collector->setCategories($this->getCategories());
}
$collector->withCategoryInformation()->withAccountInformation()->withBudgetInformation();
$journals = $collector->getExtractedJournals();
$array = [];
foreach ($journals as $journal) {
$currencyId = (int)$journal['currency_id'];
$categoryId = (int)$journal['category_id'];
$categoryName = (string)$journal['category_name'];
// catch "no category" entries.
if (0 === $categoryId) {
continue;
}
// info about the currency:
$array[$currencyId] = $array[$currencyId] ?? [
'categories' => [],
'currency_id' => $currencyId,
'currency_name' => $journal['currency_name'],
'currency_symbol' => $journal['currency_symbol'],
'currency_code' => $journal['currency_code'],
'currency_decimal_places' => $journal['currency_decimal_places'],
];
// info about the categories:
$array[$currencyId]['categories'][$categoryId] = $array[$currencyId]['categories'][$categoryId] ?? [
'id' => $categoryId,
'name' => $categoryName,
'transaction_journals' => [],
];
// add journal to array:
// only a subset of the fields.
$journalId = (int)$journal['transaction_journal_id'];
$array[$currencyId]['categories'][$categoryId]['transaction_journals'][$journalId] = [
'amount' => app('steam')->negative($journal['amount']),
'date' => $journal['date'],
'source_account_id' => $journal['source_account_id'],
'category_name' => $journal['category_name'],
'source_account_name' => $journal['source_account_name'],
'destination_account_id' => $journal['destination_account_id'],
'destination_account_name' => $journal['destination_account_name'],
'description' => $journal['description'],
'transaction_group_id' => $journal['transaction_group_id'],
];
}
return $array;
}
}

View File

@ -47,6 +47,36 @@ interface OperationsRepositoryInterface
*/
public function listExpenses(Carbon $start, Carbon $end, ?Collection $accounts = null, ?Collection $categories = null): array;
/**
* This method returns a list of all the transfer transaction journals (as arrays) set in that period
* which have the specified category set to them, transferred INTO the listed accounts.
* It excludes any transfers between the listed accounts.
* It's grouped per currency, with as few details in the array as possible. Amounts are always negative.
*
* @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
* @param Collection|null $categories
*
* @return array
*/
public function listTransferredIn(Carbon $start, Carbon $end, Collection $accounts, ?Collection $categories = null): array;
/**
* This method returns a list of all the transfer transaction journals (as arrays) set in that period
* which have the specified category set to them, transferred FROM the listed accounts.
* It excludes any transfers between the listed accounts.
* It's grouped per currency, with as few details in the array as possible. Amounts are always negative.
*
* @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
* @param Collection|null $categories
*
* @return array
*/
public function listTransferredOut(Carbon $start, Carbon $end, Collection $accounts, ?Collection $categories = null): array;
/**
* This method returns a list of all the deposit transaction journals (as arrays) set in that period
* which have the specified category set to them. It's grouped per currency, with as few details in the array

View File

@ -613,4 +613,20 @@ class RecurringRepository implements RecurringRepositoryInterface
return $filtered;
}
/**
* @inheritDoc
*/
public function getBillId(RecurrenceTransaction $recTransaction): ?int
{
$return = null;
/** @var RecurrenceTransactionMeta $meta */
foreach ($recTransaction->recurrenceTransactionMeta as $meta) {
if ('bill_id' === $meta->name) {
$return = (int)$meta->value;
}
}
return $return;
}
}

View File

@ -82,6 +82,15 @@ interface RecurringRepositoryInterface
*/
public function getCategoryId(RecurrenceTransaction $recTransaction): ?int;
/**
* Get the category from a recurring transaction transaction.
*
* @param RecurrenceTransaction $recTransaction
*
* @return null|int
*/
public function getBillId(RecurrenceTransaction $recTransaction): ?int;
/**
* Get the category from a recurring transaction transaction.
*

View File

@ -329,10 +329,8 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface
*/
public function resetOrder(): bool
{
$this->user->ruleGroups()->where('active', false)->update(['order' => 0]);
$set = $this->user
->ruleGroups()
->where('active', true)
->whereNull('deleted_at')
->orderBy('order', 'ASC')
->orderBy('title', 'DESC')
@ -363,7 +361,6 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface
{
$set = $ruleGroup->rules()
->orderBy('order', 'ASC')
->where('active', true)
->orderBy('title', 'DESC')
->orderBy('updated_at', 'DESC')
->get(['rules.*']);

View File

@ -26,6 +26,7 @@ use Exception;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\BudgetLimit;
use FireflyIII\Models\Role;
use FireflyIII\Models\UserGroup;
use FireflyIII\User;
use Illuminate\Database\QueryException;
use Illuminate\Support\Collection;
@ -164,7 +165,10 @@ class UserRepository implements UserRepositoryInterface
public function destroy(User $user): bool
{
Log::debug(sprintf('Calling delete() on user %d', $user->id));
$user->groupMemberships()->delete();
$user->delete();
$this->deleteEmptyGroups();
return true;
}
@ -396,4 +400,20 @@ class UserRepository implements UserRepositoryInterface
return true;
}
/**
* @inheritDoc
*/
public function deleteEmptyGroups(): void
{
$groups = UserGroup::get();
/** @var UserGroup $group */
foreach ($groups as $group) {
$count = $group->groupMemberships()->count();
if (0 === $count) {
Log::info(sprintf('Deleted empty group #%d ("%s")', $group->id, $group->title));
$group->delete();
}
}
}
}

View File

@ -39,6 +39,11 @@ interface UserRepositoryInterface
*/
public function all(): Collection;
/**
*
*/
public function deleteEmptyGroups(): void;
/**
* Gives a user a role.
*

View File

@ -23,6 +23,8 @@ declare(strict_types=1);
namespace FireflyIII\Support\Http\Controllers;
use Carbon\Carbon;
/**
* Trait BasicDataSupport
*
@ -30,15 +32,28 @@ namespace FireflyIII\Support\Http\Controllers;
trait BasicDataSupport
{
/**
* Find the ID in a given array. Return '0' of not there (amount).
* Find the ID in a given array. Return '0' if not there (amount).
*
* @param array $array
* @param int $entryId
*
* @return null|mixed
*/
protected function isInArray(array $array, int $entryId) // helper for data (math, calculations)
protected function isInArray(array $array, int $entryId)
{
return $array[$entryId] ?? '0';
}
/**
* Find the ID in a given array. Return null if not there (amount).
*
* @param array $array
* @param int $entryId
*
* @return null|Carbon
*/
protected function isInArrayDate(array $array, int $entryId): ?Carbon
{
return $array[$entryId] ?? null;
}
}

View File

@ -249,7 +249,7 @@ class BudgetReportGenerator
'budget_limits' => [],
];
$noBudget = $this->nbRepository->sumExpenses($this->start, $this->end);
$noBudget = $this->nbRepository->sumExpenses($this->start, $this->end, $this->accounts);
foreach ($noBudget as $noBudgetEntry) {
// currency information:

View File

@ -66,6 +66,11 @@ class CategoryReportGenerator
{
$earnedWith = $this->opsRepository->listIncome($this->start, $this->end, $this->accounts);
$spentWith = $this->opsRepository->listExpenses($this->start, $this->end, $this->accounts);
// also transferred out and transferred into these accounts in this category:
$transferredIn = $this->opsRepository->listTransferredIn($this->start, $this->end, $this->accounts);
$transferredOut = $this->opsRepository->listTransferredOut($this->start, $this->end, $this->accounts);
$earnedWithout = $this->noCatRepository->listIncome($this->start, $this->end, $this->accounts);
$spentWithout = $this->noCatRepository->listExpenses($this->start, $this->end, $this->accounts);
@ -75,7 +80,7 @@ class CategoryReportGenerator
];
// needs four for-each loops.
foreach ([$earnedWith, $spentWith, $earnedWithout, $spentWithout] as $data) {
foreach ([$earnedWith, $spentWith, $earnedWithout, $spentWithout, $transferredIn, $transferredOut] as $data) {
$this->processOpsArray($data);
}
}

View File

@ -156,7 +156,7 @@ class OperatorQuerySearch implements SearchInterface
$parser = new QueryParser();
try {
$query1 = $parser->parse($query);
} catch (TypeError|LogicException $e) {
} catch (TypeError | LogicException $e) {
Log::error($e->getMessage());
Log::error(sprintf('Could not parse search: "%s".', $query));
throw new FireflyException('Invalid search value. See the logs.', 0, $e);
@ -245,6 +245,7 @@ class OperatorQuerySearch implements SearchInterface
private function handleSearchNode(Node $searchNode): void
{
$class = get_class($searchNode);
Log::debug(sprintf('Now in handleSearchNode(%s)', $class));
switch ($class) {
default:
Log::error(sprintf('Cannot handle node %s', $class));
@ -252,7 +253,7 @@ class OperatorQuerySearch implements SearchInterface
case Subquery::class:
// loop all notes in subquery:
foreach ($searchNode->getNodes() as $subNode) { // @phpstan-ignore-line
$this->handleSearchNode($subNode); // lets hope its not too recursive!
$this->handleSearchNode($subNode); // let's hope it's not too recursive!
}
break;
case Word::class:
@ -282,13 +283,15 @@ class OperatorQuerySearch implements SearchInterface
'value' => (string)$value,
]
);
} else {
Log::debug(sprintf('Added operator type "%s"', $operator));
}
if (!in_array($operator, $this->validOperators, true)) {
Log::debug(sprintf('Added INVALID operator type "%s"', $operator));
$this->invalidOperators[] = [
'type' => $operator,
'value' => (string)$value,
];
}
break;
}
}
@ -302,7 +305,7 @@ class OperatorQuerySearch implements SearchInterface
*/
private function updateCollector(string $operator, string $value): bool
{
Log::debug(sprintf('updateCollector("%s", "%s")', $operator, $value));
Log::debug(sprintf('Now in updateCollector("%s", "%s")', $operator, $value));
// check if alias, replace if necessary:
$operator = self::getRootOperator($operator);

View File

@ -406,7 +406,7 @@ class Steam
->get(['transactions.account_id', DB::raw('MAX(transaction_journals.date) AS max_date')]); // @phpstan-ignore-line
foreach ($set as $entry) {
$date = new Carbon($entry->max_date, 'UTC');
$date = new Carbon($entry->max_date, config('app.timezone'));
$date->setTimezone(config('app.timezone'));
$list[(int)$entry->account_id] = $date;
}

View File

@ -49,6 +49,7 @@ class General extends AbstractExtension
$this->mimeIcon(),
$this->markdown(),
$this->floatval(),
$this->phpHostName(),
];
}
@ -91,6 +92,24 @@ class General extends AbstractExtension
);
}
/**
* Show URL host name
*
* @return TwigFilter
*/
protected function phpHostName(): TwigFilter
{
return new TwigFilter(
'phphost',
static function (string $string): string {
$proto = (string)parse_url($string, PHP_URL_SCHEME);
$host = (string)parse_url($string, PHP_URL_HOST);
return e(sprintf('%s://%s', $proto, $host));
}
);
}
/**
* Used to convert 1024 to 1kb etc.
*

View File

@ -60,9 +60,11 @@ class DeleteTransaction implements ActionInterface
// trigger delete factory:
$journal = TransactionJournal::find($journal['transaction_group_id']);
/** @var JournalDestroyService $service */
$service = app(JournalDestroyService::class);
$service->destroy($journal);
if (null !== $journal) {
/** @var JournalDestroyService $service */
$service = app(JournalDestroyService::class);
$service->destroy($journal);
}
return true;
}

View File

@ -351,7 +351,7 @@ class TransactionGroupTransformer extends AbstractTransformer
$bill = $this->getBill($journal->bill);
if (null !== $foreignAmount && null !== $foreignCurrency) {
$foreignAmount = number_format((float)$foreignAmount, $foreignCurrency->decimal_places ?? 0, '.', '');
$foreignAmount = number_format((float)$foreignAmount, $foreignCurrency['decimal_places'] ?? 0, '.', '');
}
$longitude = null;

View File

@ -67,7 +67,7 @@ trait TransferValidation
return false;
}
// otherwise try to find the account:
// or try to find the account:
$search = $this->findExistingAccount($validTypes, (int)$accountId, (string)$accountName);
if (null === $search) {
$this->destError = (string)trans('validation.transfer_dest_bad_data', ['id' => $accountId, 'name' => $accountName]);

View File

@ -41,15 +41,13 @@ trait ValidatesBulkTransactionQuery
protected function validateTransactionQuery(Validator $validator): void
{
$data = $validator->getData();
// assumption is all validation has already taken place
// and the query key exists.
// assumption is all validation has already taken place and the query key exists.
$json = json_decode($data['query'], true, 8, JSON_THROW_ON_ERROR);
if (array_key_exists('account_id', $json['where'])
&& array_key_exists('account_id', $json['update'])
) {
// find both accounts
// must be same type.
// find both accounts, must be same type.
// already validated: belongs to this user.
$repository = app(AccountRepositoryInterface::class);
$source = $repository->find((int)$json['where']['account_id']);

View File

@ -2,7 +2,26 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
## 5.6.2 - 2021-10-xx
## 5.6.3 - 2021-11-12
### Changed
- [Issue 5133](https://github.com/firefly-iii/firefly-iii/issues/5133) Several possible fixes for LDAP filter.
### Fixed
- [Issue 5116](https://github.com/firefly-iii/firefly-iii/issues/5116) Fix missing icon
- [Issue 5173](https://github.com/firefly-iii/firefly-iii/issues/5173) Firefly III would email too often about logins
- [Issue 5178](https://github.com/firefly-iii/firefly-iii/issues/5178) Fix export parameter
- [Issue 5179](https://github.com/firefly-iii/firefly-iii/issues/5179) Possible fix for issue with empty date strings
- [Issue 5196](https://github.com/firefly-iii/firefly-iii/issues/5196) Fix issue with foreign amount formatting
- [Issue 5200](https://github.com/firefly-iii/firefly-iii/issues/5200) Link bill to recurring transaction
- [Issue 5218](https://github.com/firefly-iii/firefly-iii/issues/5218) Error in search
- [Issue 5226](https://github.com/firefly-iii/firefly-iii/issues/5226) Could submit negative "skip" values
- [Issue 5229](https://github.com/firefly-iii/firefly-iii/issues/5229) Fix serviceworker registration
### Security
- Logout is now POST and other minor CSRF issues.
## 5.6.2 - 2021-10-09
### Added
- `/health` will return `200 OK` if Firefly III is up and running, thanks @ajgon!

View File

@ -91,25 +91,25 @@
"doctrine/dbal": "3.*",
"fideloper/proxy": "4.*",
"gdbots/query-parser": "^2.0",
"guzzlehttp/guzzle": "^7.2",
"guzzlehttp/guzzle": "^7.4",
"jc5/google2fa-laravel": "2.0.6",
"jc5/recovery": "^2",
"laravel/framework": "^8.51",
"laravel/framework": "^8.69",
"laravel/passport": "10.*",
"laravel/ui": "^3.0",
"laravel/ui": "^3.3",
"laravelcollective/html": "6.*",
"league/commonmark": "2.*",
"league/csv": "^9.6",
"league/fractal": "0.*",
"pragmarx/google2fa": "^8.0",
"predis/predis": "^1.1",
"psr/log": "<3",
"ramsey/uuid": "^4.2",
"rcrowe/twigbridge": "^0.12.1",
"spatie/data-transfer-object": "^3.1",
"psr/log": "<2"
"spatie/data-transfer-object": "^3.1"
},
"require-dev": {
"barryvdh/laravel-debugbar": "^3.3",
"barryvdh/laravel-debugbar": "^3.6",
"barryvdh/laravel-ide-helper": "2.*",
"filp/whoops": "2.*",
"fakerphp/faker": "1.*",

545
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -24,6 +24,9 @@ declare(strict_types=1);
use FireflyIII\Ldap\AttributeHandler;
use FireflyIII\Ldap\Rules\UserDefinedRule;
$openLDAP = class_exists(LdapRecord\Models\OpenLDAP\User::class) ? LdapRecord\Models\OpenLDAP\User::class : '';
$activeDirectory = class_exists(LdapRecord\Models\ActiveDirectory\User::class) ? LdapRecord\Models\ActiveDirectory\User::class : '';
return [
/*
|--------------------------------------------------------------------------
@ -108,8 +111,7 @@ return [
'ldap' => [
'driver' => 'ldap',
//'model' => LdapRecord\Models\ActiveDirectory\User::class,
'model' => LdapRecord\Models\OpenLDAP\User::class,
'model' => env('LDAP_DIALECT') === 'OpenLDAP' ? $openLDAP : $activeDirectory,
'rules' => [
UserDefinedRule::class,
],

View File

@ -101,7 +101,7 @@ return [
'webhooks' => true,
'handle_debts' => true,
],
'version' => '5.6.2',
'version' => '5.6.3',
'api_version' => '1.5.4',
'db_version' => 18,

View File

@ -36,8 +36,8 @@ return [
*/
'default' => env('LDAP_CONNECTION', 'default'),
'group_filter' => env('LDAP_GROUP_FILTER'),
'extra_filter' => env('LDAP_EXTRA_FILTER'),
'dialect' => env('LDAP_DIALECT'),
/*
|--------------------------------------------------------------------------

View File

@ -15,15 +15,15 @@
"laravel-mix": "^6",
"lodash": "^4.17.21",
"lodash.clonedeep": "^4.5.0",
"postcss": "^8.1.14",
"postcss": "^8.3.11",
"resolve-url-loader": "^4.0.0",
"sass": "^1.39.2",
"sass-loader": "^12.0.0",
"vue-i18n": "^8.24.2",
"sass": "^1.43.3",
"sass-loader": "^12.2.0",
"vue-i18n": "^8.26.7",
"vue-loader": "^15",
"vue-template-compiler": "^2.6.12",
"vuex": "^3.6.2",
"webpack": "^5.52.1"
"webpack": "^5.62.1"
},
"dependencies": {
"@fortawesome/fontawesome-free": "^5.15.3",
@ -32,7 +32,7 @@
"axios-cache-adapter": "^2.7.3",
"bootstrap": "^4.6.0",
"bootstrap-vue": "^2.21.2",
"chart.js": "^3.4.0",
"chart.js": "^3.6.0",
"icheck-bootstrap": "^3.0.1",
"jquery-ui": "^1.12.1",
"leaflet": "^1.7.1",
@ -40,7 +40,7 @@
"localforage-memoryStorageDriver": "^0.9.2",
"overlayscrollbars": "^1.13.1",
"sortablejs": "^1.14.0",
"uiv": "^1.3.1",
"uiv": "^1.4.1",
"v-calendar": "^2.3.2",
"vue-typeahead-bootstrap": "^2.8.0",
"vue2-leaflet": "^2.7.1"

View File

@ -97,7 +97,7 @@
<GenericLocation :disabled="submitting" v-model="location" :title="$t('form.location')" :errors="errors.location"
v-on:set-field="storeField($event)"/>
<!-- attachments -->
<GenericAttachments :disabled="submitting" :title="$t('form.attachments')" field-name="attachments" :errors="errors.attachments"
v-on:selected-attachments="selectedAttachments($event)"
v-on:selected-no-attachments="selectedNoAttachments($event)"
@ -275,6 +275,8 @@ export default {
this.hasAttachments = false;
},
uploadedAttachments: function (e) {
console.log('Response to event uploaded-attachments');
console.log(e);
this.finishSubmission();
},
submitForm: function (e) {

View File

@ -23,6 +23,7 @@
<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>
@ -31,36 +32,34 @@
<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>
<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>
</div>
<!-- ./card -->
-->
</div>
</div>
<TransactionListLarge
:entries="rawTransactions"
:page="currentPage"
ref="list"
:total="total"
:per-page="perPage"
:sort-desc="sortDesc"
v-on:jump-page="jumpToPage($event)"
v-on:refreshed-cache-key="refreshedKey"
/>
<div class="row">
<div class="col-lg-12 col-md-6 col-sm-12 col-xs-12">
@ -106,7 +105,7 @@ export default {
perPage: 51,
locale: 'en-US',
api: null,
nameLoading:false
nameLoading: false
}
},
created() {
@ -132,16 +131,23 @@ export default {
.then(response => {
let start = new Intl.DateTimeFormat(this.locale, {year: 'numeric', month: 'long', day: 'numeric'}).format(this.start);
let end = new Intl.DateTimeFormat(this.locale, {year: 'numeric', month: 'long', day: 'numeric'}).format(this.end);
document.getElementById('page-subTitle').innerText = this.$t('firefly.journals_in_period_for_account_js', {start: start, end: end, title: response.data.data.attributes.name});
document.getElementById('page-subTitle').innerText = this.$t('firefly.journals_in_period_for_account_js', {
start: start,
end: end,
title: response.data.data.attributes.name
});
});
});
}
},
refreshedKey: function () {
this.loading = false;
this.getTransactions();
this.updatePageTitle();
},
getTransactions: function () {
if (this.showReady && !this.loading) {
this.loading = true;
configureAxios().then(async (api) => {
// console.log('Now getTransactions() x Start');
@ -149,8 +155,7 @@ export default {
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;
let url = './api/v1/accounts/' + this.accountId + '/transactions?page=1&limit=' + this.perPage + '&start=' + startStr + '&end=' + endStr + '&cache=' + this.cacheKey;
api.get(url)
.then(response => {

View File

@ -30,7 +30,6 @@
aria-valuemax="100" aria-valuemin="0" class="progress-bar bg-success" role="progressbar">
<span v-if="budgetLimit.pctGreen > 35">
{{ $t('firefly.spent_x_of_y', {amount: Intl.NumberFormat(locale, {style: 'currency', currency: budgetLimit.currency_code}).format(budgetLimit.spent), total: Intl.NumberFormat(locale, {style: 'currency', currency: budgetLimit.currency_code}).format(budgetLimit.amount)}) }}
<!-- -->
</span>
@ -45,7 +44,7 @@
<div :aria-valuenow="budgetLimit.pctRed" :style="'width: '+ budgetLimit.pctRed + '%;'"
aria-valuemax="100" aria-valuemin="0" class="progress-bar bg-danger" role="progressbar">
<span v-if="budgetLimit.pctOrange <= 50 && budgetLimit.pctRed > 35" class="text-muted">
<span v-if="budgetLimit.pctOrange <= 50 && budgetLimit.pctRed > 35" class="text-white">
{{ $t('firefly.spent_x_of_y', {amount: Intl.NumberFormat(locale, {style: 'currency', currency: budgetLimit.currency_code}).format(budgetLimit.spent), total: Intl.NumberFormat(locale, {style: 'currency', currency: budgetLimit.currency_code}).format(budgetLimit.amount)}) }}
</span>
</div>

View File

@ -24,6 +24,7 @@
<td style="width:25%;">
<a :href="'./budgets/show/' + budget.id">{{ budget.name }}</a>
</td>
<td>&nbsp;</td>
<td class="align-middle text-right">
<span class="text-danger">
{{ Intl.NumberFormat(locale, {style: 'currency', currency: budget.currency_code}).format(parseFloat(budget.spent)) }}

View File

@ -214,8 +214,8 @@ export default {
let pctGreen = 0;
let pctOrange = 0;
let pctRed = 0;
//console.log('Collected "' + period + '" budget limit #' + currentId + ' (part of budget #' + budgetId + ')');
//console.log('Spent ' + spentFloat + ' of ' + amount);
console.log('Collected "' + period + '" budget limit #' + currentId + ' (part of budget #' + budgetId + ')');
console.log('Spent ' + spentFloatPos + ' of ' + amount);
// remove budget info from rawBudgets if it's there:
this.filterBudgets(budgetId, currencyId);
@ -230,6 +230,12 @@ export default {
pctOrange = (spentFloatPos / amount) * 100;
pctRed = 100 - pctOrange;
}
// spent exactly on budget
if (0.0 !== spentFloatPos && spentFloatPos === amount) {
pctOrange = 0;
pctRed = 100;
}
let obj = {
id: currentId,
amount: current.attributes.amount,

View File

@ -97,22 +97,22 @@ export default {
// });
// new code
// console.log('start of new');
console.log('start of new');
let files = this.$refs.att.files;
this.uploads = files.length;
// loop all files and create attachments.
for (let i in files) {
if (files.hasOwnProperty(i) && /^0$|^[1-9]\d*$/.test(i) && i <= 4294967294) {
// console.log('Now at file ' + (parseInt(i) + 1) + ' / ' + files.length);
console.log('Now at file ' + (parseInt(i) + 1) + ' / ' + files.length);
// read file into file reader:
let current = files[i];
let fileReader = new FileReader();
let theParent = this; // dont ask me why i need to do this.
fileReader.onloadend = evt => {
if (evt.target.readyState === FileReader.DONE) {
// console.log('I am done reading file ' + (parseInt(i) + 1));
console.log('I am done reading file ' + (parseInt(i) + 1));
this.createAttachment(current.name).then(response => {
// console.log('Created attachment. Now upload (1)');
console.log('Created attachment. Now upload (1)');
return theParent.uploadAttachment(response.data.data.id, new Blob([evt.target.result]));
}).then(theParent.countAttachment);
}
@ -121,7 +121,7 @@ export default {
}
}
if (0 === files.length) {
// console.log('No files to upload. Emit event!');
console.log('No files to upload. Emit event!');
this.$emit('uploaded-attachments', this.transaction_journal_id);
}
// Promise.all(promises).then(response => {
@ -138,15 +138,15 @@ export default {
methods: {
countAttachment: function () {
this.uploaded++;
// console.log('Uploaded ' + this.uploaded + ' / ' + this.uploads);
console.log('Uploaded ' + this.uploaded + ' / ' + this.uploads);
if (this.uploaded >= this.uploads) {
// console.log('All files uploaded. Emit event for ' + this.transaction_journal_id + '(' + this.index + ')');
this.$emit('uploaded-attachments', this.transaction_journal_id);
console.log('All files uploaded. Emit event for ' + this.uploadObjectId);
this.$emit('uploaded-attachments', this.uploadObjectId);
}
},
uploadAttachment: function (attachmentId, data) {
this.created++;
// console.log('Now in uploadAttachment()');
console.log('Now in uploadAttachment()');
const uploadUri = './api/v1/attachments/' + attachmentId + '/upload';
return axios.post(uploadUri, data)
},

View File

@ -31,6 +31,7 @@
:count="transactions.length"
:custom-fields="customFields"
:date="date"
ref="splitForms"
:destination-allowed-types="destinationAllowedTypes"
:index="index"
:source-allowed-types="sourceAllowedTypes"
@ -70,7 +71,7 @@
&nbsp;
</div>
<button type="button" class="btn btn-outline-primary btn-block" @click="addTransactionArray"><span class="far fa-clone"></span> {{
$t('firefly.add_another_split')
$t('firefly.add_another_split')
}}
</button>
</div>
@ -333,7 +334,6 @@ export default {
for (let i in this.transactions) {
if (this.transactions.hasOwnProperty(i) && /^0$|^[1-9]\d*$/.test(i) && i <= 4294967294) {
if (this.transactions.hasOwnProperty(i)) {
//this.
// console.log('Reset attachment #' + i);
this.updateField({index: i, field: 'transaction_journal_id', value: 0});
this.updateField({index: i, field: 'errors', value: this.defaultErrors})
@ -344,7 +344,8 @@ export default {
// reset the form:
if (this.resetFormAfter) {
this.resetTransactions();
this.addTransaction();
setTimeout(this.addTransaction, 50);
}
return Promise.resolve({response: 'from finaliseSubmission'});
},
@ -433,6 +434,12 @@ export default {
this.updateField({index: payload.index, field: payload.direction + '_account_currency_symbol', value: payload.currency_symbol});
//this.calculateTransactionType(payload.index);
if ('source' === payload.direction && true === payload.user_selected) {
this.$refs.splitForms[payload.index].$refs.destinationAccount.giveFocus();
}
if ('destination' === payload.direction && true === payload.user_selected) {
this.$refs.splitForms[payload.index].$refs.amount.giveFocus();
}
},
storeField: function (payload) {
this.updateField(payload);

View File

@ -20,6 +20,7 @@
<template>
<div>
<!--
<div class="row">
<div class="col">
<div class="card">
@ -36,6 +37,7 @@
</div>
</div>
</div>
-->
<!-- page is ignored for the time being -->
<TransactionListLarge
:entries="rawTransactions"
@ -44,7 +46,9 @@
:per-page="perPage"
:sort-desc="sortDesc"
v-on:jump-page="jumpToPage($event)"
v-on:refreshed-cache-key="refreshedKey"
/>
<!--
<div class="row">
<div class="col-xl-2 col-lg-4 col-sm-6 col-xs-12" v-for="range in ranges">
<div class="card">
@ -56,7 +60,9 @@
</div>
</div>
</div>
</div>
-->
</div>
</template>
@ -141,13 +147,12 @@ export default {
},
methods: {
...mapMutations('root', ['refreshCacheKey',]),
newCacheKey: function () {
this.refreshCacheKey();
refreshedKey: function () {
this.downloaded = false;
this.accounts = [];
this.rawTransactions = [];
this.getTransactionList();
},
jumpToPage: function(event) {
jumpToPage: function (event) {
// console.log('noticed a change!');
this.currentPage = event.page;
this.downloadTransactionList(event.page);

View File

@ -58,6 +58,8 @@
:source-allowed-types="sourceAllowedTypes"
:transaction-type="transactionType"
direction="source"
ref="sourceAccount"
v-on:selected-account="triggerNextAccount($event)"
/>
</div>
<!-- switcharoo! -->
@ -79,6 +81,7 @@
:destination-allowed-types="destinationAllowedTypes"
:errors="transaction.errors.destination"
:index="index"
ref="destinationAccount"
:transaction-type="transactionType"
:source-allowed-types="sourceAllowedTypes"
direction="destination"
@ -93,6 +96,7 @@
<!-- AMOUNT -->
<TransactionAmount
v-on="$listeners"
ref="amount"
:amount="transaction.amount"
:destination-currency-symbol="this.transaction.destination_account_currency_symbol"
:errors="transaction.errors.amount"
@ -379,6 +383,13 @@ export default {
// console.log('Will remove transaction ' + this.index);
this.$emit('remove-transaction', {index: this.index});
},
triggerNextAccount: function(e) {
//alert(e);
if('source' === e) {
console.log('Jump to destination!');
this.$refs.destinationAccount.giveFocus();
}
}
},
computed: {
splitDate: function () {

View File

@ -37,6 +37,7 @@
:placeholder="$t('firefly.' + direction + '_account')"
:serializer="item => item.name_with_balance"
:showOnFocus=true
ref="inputThing"
aria-autocomplete="none"
autocomplete="off"
@hit="userSelectedAccount"
@ -119,6 +120,12 @@ export default {
getACURL: function (types, query) {
return './api/v1/autocomplete/accounts?types=' + types.join(',') + '&query=' + query;
},
giveFocus: function() {
console.log('I want focus! now OK: ' + this.direction + ' l: ' + this.accounts.length);
//console.log(this.$refs.inputThing.$refs.input.value);
this.$refs.inputThing.$refs.input.focus();
console.log(this.$refs.inputThing.isFocused);
},
userSelectedAccount: function (event) {
// console.log('userSelectedAccount!');
// console.log('To prevent invalid propogation, set selectedAccountTrigger = true');
@ -205,10 +212,14 @@ export default {
currency_id: value.currency_id,
currency_code: value.currency_code,
currency_symbol: value.currency_symbol,
user_selected: true,
}
// jump to next field somehow.
);
//console.log('watch::selectedAccount() will now set accountName because selectedAccountTrigger = true');
this.accountName = value.name;
}
if (false === this.selectedAccountTrigger) {
//console.log('watch::selectedAccount() will NOT set accountName because selectedAccountTrigger = false');
@ -238,6 +249,7 @@ export default {
currency_id: null,
currency_code: null,
currency_symbol: null,
user_selected: false
}
);
// this.account = {name: value, type: null, id: null, currency_id: null, currency_code: null, currency_symbol: null};

View File

@ -33,6 +33,7 @@
autocomplete="off"
name="amount[]"
type="number"
ref="input"
step="any"
>
</div>
@ -71,7 +72,10 @@ export default {
methods: {
formatNumber(str) {
return parseFloat(str).toFixed(this.fractionDigits);
}
},
giveFocus: function() {
this.$refs.input.focus();
},
},
data() {
return {

View File

@ -53,9 +53,9 @@
</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 class="fas fa-long-arrow-alt-right" v-if="'deposit' === data.item.type.toLowerCase()"></span>
<span class="fas fa-long-arrow-alt-left" v-if="'withdrawal' === data.item.type.toLowerCase()"></span>
<span class="fas fa-arrows-alt-h" v-if="'transfer' === data.item.type.toLowerCase()"></span>
</span>
</template>
<template #cell(description)="data">
@ -86,7 +86,7 @@
<span :class="'text-danger ' + (!data.item.collapsed ? 'font-weight-bold' : '')" v-if="'withdrawal' === data.item.type">
{{ Intl.NumberFormat(locale, {style: 'currency', currency: data.item.currency_code}).format(-data.item.amount) }}
</span>
<span :class="'text-muted ' + (!data.item.collapsed ? 'font-weight-bold' : '')" v-if="'transfer' === data.item.type">
<span :class="'text-muted ' + (!data.item.collapsed ? 'font-weight-bold' : '')" v-if="'transfer' === data.item.type.toLowerCase()">
{{ Intl.NumberFormat(locale, {style: 'currency', currency: data.item.currency_code}).format(data.item.amount) }}
</span>
<br />
@ -220,21 +220,26 @@ export default {
this.$emit('jump-page', {page: value});
},
entries: function (value) {
console.log('detected new transactions!');
this.parseTransactions();
},
value: function(value) {
console.log('Watch value!');
}
},
methods: {
...mapMutations('root', ['refreshCacheKey',]),
parseTransactions: function () {
this.transactions = [];
// 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');
console.log('Will not render now');
return;
}
// console.log('Now have ' + this.transactions.length + ' transactions');
console.log('Now have ' + this.transactions.length + ' transactions');
for (let i = 0; i < this.total; i++) {
this.transactions.push({dummy: true});
this.transactions.push({dummy: true,type: 'x'});
// console.log('Push dummy to index ' + i);
// console.log('Now have ' + this.transactions.length + ' transactions');
}
@ -259,8 +264,9 @@ export default {
this.loading = false;
},
newCacheKey: function () {
alert('TODO');
this.refreshCacheKey();
console.log('Cache key is now ' + this.cacheKey);
this.$emit('refreshed-cache-key');
},
updateFieldList: function () {
this.fields = [
@ -314,6 +320,7 @@ export default {
let info = transaction.attributes.transactions[i];
let split = {};
row.amount = row.amount + parseFloat(info.amount);
split.type = info.type;
split.description = info.description;
split.amount = info.amount;
split.currency_code = info.currency_code;

View File

@ -70,7 +70,7 @@
"daily_budgets": "Denn\u00ed rozpo\u010dty",
"weekly_budgets": "T\u00fddenn\u00ed rozpo\u010dty",
"monthly_budgets": "M\u011bs\u00ed\u010dn\u00ed rozpo\u010dty",
"journals_in_period_for_account_js": "All transactions for account {title} between {start} and {end}",
"journals_in_period_for_account_js": "Ve\u0161ker\u00e9 transakce pro \u00fa\u010det {title} mezi {start} a {end}",
"quarterly_budgets": "\u010ctvrtletn\u00ed rozpo\u010dty",
"create_new_expense": "Vytvo\u0159it v\u00fddajov\u00fd \u00fa\u010det",
"create_new_revenue": "Vytvo\u0159it nov\u00fd p\u0159\u00edjmov\u00fd \u00fa\u010det",

View File

@ -70,7 +70,7 @@
"daily_budgets": "Presupuestos diarios",
"weekly_budgets": "Presupuestos semanales",
"monthly_budgets": "Presupuestos mensuales",
"journals_in_period_for_account_js": "All transactions for account {title} between {start} and {end}",
"journals_in_period_for_account_js": "Todas las transacciones de la cuenta {title} entre {start} y {end}",
"quarterly_budgets": "Presupuestos trimestrales",
"create_new_expense": "Crear nueva cuenta de gastos",
"create_new_revenue": "Crear nueva cuenta de ingresos",
@ -142,27 +142,27 @@
"transaction_expand_split": "Expandir divisi\u00f3n",
"transaction_collapse_split": "Colapsar divisi\u00f3n",
"default_group_title_name": "(sin agrupaci\u00f3n)",
"bill_repeats_weekly": "Repeats weekly",
"bill_repeats_monthly": "Repeats monthly",
"bill_repeats_quarterly": "Repeats quarterly",
"bill_repeats_half-year": "Repeats every half year",
"bill_repeats_yearly": "Repeats yearly",
"bill_repeats_weekly_other": "Repeats every other week",
"bill_repeats_monthly_other": "Repeats every other month",
"bill_repeats_quarterly_other": "Repeats every other quarter",
"bill_repeats_half-year_other": "Repeats yearly",
"bill_repeats_yearly_other": "Repeats every other year",
"bill_repeats_weekly_skip": "Repeats every {skip} weeks",
"bill_repeats_monthly_skip": "Repeats every {skip} months",
"bill_repeats_quarterly_skip": "Repeats every {skip} quarters",
"bill_repeats_half-year_skip": "Repeats every {skip} half years",
"bill_repeats_yearly_skip": "Repeats every {skip} years",
"bill_repeats_weekly": "Repetir semanalmente",
"bill_repeats_monthly": "Repetir mensualmente",
"bill_repeats_quarterly": "Repite trimestralmente",
"bill_repeats_half-year": "Repetir cada 6 meses",
"bill_repeats_yearly": "Repetir anualmente",
"bill_repeats_weekly_other": "Repetir cada dos semanas",
"bill_repeats_monthly_other": "Repetir cada dos meses",
"bill_repeats_quarterly_other": "Repetir cada dos trimestres",
"bill_repeats_half-year_other": "Repetir anualmente",
"bill_repeats_yearly_other": "Repetir cada dos a\u00f1os",
"bill_repeats_weekly_skip": "Repetir cada {skip} semanas",
"bill_repeats_monthly_skip": "Repetir cada {skip} meses",
"bill_repeats_quarterly_skip": "Repetir cada {skip} trimestres",
"bill_repeats_half-year_skip": "Repetir cada {skip} medios a\u00f1os",
"bill_repeats_yearly_skip": "Repetir cada {skip} a\u00f1os",
"not_expected_period": "No se espera en este per\u00edodo",
"subscriptions": "Subscriptions",
"subscriptions": "Suscripciones",
"bill_expected_date_js": "Expected {date}",
"inactive": "Inactivo",
"forever": "Forever",
"extension_date_is": "Extension date is {date}",
"forever": "Siempre",
"extension_date_is": "Fecha de extensi\u00f3n es {date}",
"create_new_bill": "Crear nueva factura",
"store_new_bill": "Crear factura",
"repeat_freq_yearly": "anualmente",
@ -170,13 +170,13 @@
"repeat_freq_quarterly": "trimestralmente",
"repeat_freq_monthly": "mensualmente",
"repeat_freq_weekly": "semanalmente",
"credit_card_type_monthlyFull": "Full payment every month",
"credit_card_type_monthlyFull": "Pago completo cada mes",
"update_liabilities_account": "Actualizar pasivo",
"update_expense_account": "Actualizar cuenta de gastos",
"update_revenue_account": "Actualizar cuenta de ingresos",
"update_undefined_account": "Update account",
"update_undefined_account": "Actualizar cuenta",
"update_asset_account": "Actualizar cuenta de activos",
"updated_account_js": "Updated account \"<a href=\"accounts\/show\/{ID}\">{title}<\/a>\"."
"updated_account_js": "Cuenta actualizada \"<a href=\"accounts\/show\/{ID}\">{title}<\/a>\"."
},
"list": {
"piggy_bank": "Alcancilla",
@ -197,10 +197,10 @@
"liability_direction": "Pasivo entrada\/salida",
"currentBalance": "Balance actual",
"next_expected_match": "Pr\u00f3xima coincidencia esperada",
"expected_info": "Next expected transaction",
"start_date": "Start date",
"end_date": "End date",
"payment_info": "Payment information"
"expected_info": "Siguiente transacci\u00f3n esperada",
"start_date": "Fecha de inicio",
"end_date": "Fecha fin",
"payment_info": "Informaci\u00f3n del pago"
},
"config": {
"html_language": "es",
@ -221,7 +221,7 @@
"repeat_freq": "Repetici\u00f3n",
"skip": "Saltar",
"startdate": "Fecha de inicio",
"enddate": "End date",
"enddate": "Fecha fin",
"object_group": "Grupo",
"attachments": "Adjuntos",
"active": "Activo",
@ -252,6 +252,6 @@
"amount_max": "Importe m\u00e1ximo",
"start_date": "Inicio del rango",
"end_date": "Final del rango",
"extension_date": "Extension date"
"extension_date": "Fecha de extensi\u00f3n"
}
}

View File

@ -70,7 +70,7 @@
"daily_budgets": "Dagliga budgetar",
"weekly_budgets": "Veckovis budgetar",
"monthly_budgets": "M\u00e5natliga budgetar",
"journals_in_period_for_account_js": "All transactions for account {title} between {start} and {end}",
"journals_in_period_for_account_js": "Alla transaktioner f\u00f6r konto {title} mellan {start} och {end}",
"quarterly_budgets": "Kvartalsbudgetar",
"create_new_expense": "Skapa ett nytt utgiftskonto",
"create_new_revenue": "Skapa ett nytt int\u00e4ktskonto",
@ -174,9 +174,9 @@
"update_liabilities_account": "Uppdatera skuld",
"update_expense_account": "Uppdatera utgiftskonto",
"update_revenue_account": "Uppdatera int\u00e4ktskonto",
"update_undefined_account": "Update account",
"update_undefined_account": "Uppdatera konto",
"update_asset_account": "Uppdatera tillg\u00e5ngskonto",
"updated_account_js": "Updated account \"<a href=\"accounts\/show\/{ID}\">{title}<\/a>\"."
"updated_account_js": "Uppdaterade kontot \"<a href=\"accounts\/show\/{ID}\">{title}<\/a>\"."
},
"list": {
"piggy_bank": "Spargris",

File diff suppressed because it is too large Load Diff

View File

@ -10,17 +10,17 @@
},
"devDependencies": {
"@johmun/vue-tags-input": "^2",
"@vue/compiler-sfc": "^3.2.19",
"axios": "^0.22",
"@vue/compiler-sfc": "^3.2.21",
"axios": "^0.24",
"bootstrap-sass": "^3",
"cross-env": "^7.0",
"font-awesome": "^4.7.0",
"jquery": "^3",
"laravel-mix": "^6.0",
"postcss": "^8.3",
"uiv": "^1.3",
"uiv": "^1.4",
"vue": "^2.6",
"vue-i18n": "^8.25",
"vue-i18n": "^8.26",
"vue-loader": "^15",
"vue-template-compiler": "^2.6"
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -59,6 +59,32 @@ function readCookie(name) {
return null;
}
function moveRuleGroup(e) {
let box = $(e.currentTarget);
var direction = box.data('direction');
var groupId = box.data('id');
$.post(moveRuleGroupUrl, {_token: token, direction: direction, id: groupId}).then(function () {
location.reload();
}).fail(function() {
alert('I failed :(');
});
return false;
}
function duplicateRule(e) {
let box = $(e.currentTarget);
var ruleId = box.data('id');
$.post(duplicateRuleUrl, {_token: token, id: ruleId}).then(function () {
location.reload();
}).fail(function() {
alert('I failed :(');
});
return false;
}
$(function () {
"use strict";
@ -71,6 +97,9 @@ $(function () {
}
);
$('.move-group').click(moveRuleGroup);
$('.duplicate-rule').click(duplicateRule);
$('.rules-box').each(function (i, v) {
var box = $(v);
var groupId = box.data('group');

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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