Compare commits

..

35 Commits

Author SHA1 Message Date
github-actions
f94fdc4979 Auto commit for release 'develop' on 2024-11-06 2024-11-06 19:53:30 +01:00
James Cole
a0a0e28447 Small rewording 2024-11-06 19:44:08 +01:00
James Cole
f6f7783b94 Add command to upgrade 2024-11-06 19:42:09 +01:00
James Cole
d233cc1de8 Update packages. 2024-11-06 19:41:15 +01:00
James Cole
37671499c8 Merge branch 'main' into develop
# Conflicts:
#	composer.lock
2024-11-06 19:39:11 +01:00
James Cole
c83b79998d Undo casts 2024-11-06 19:33:10 +01:00
James Cole
ed842c2b42 Fix and undo some enums 2024-11-06 19:32:32 +01:00
James Cole
8c5f114339 Add enums to types. 2024-11-06 19:22:28 +01:00
James Cole
8b2f1d0b4f Add timezones to existing objects. 2024-11-06 19:22:10 +01:00
James Cole
591a1b3050 Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop 2024-11-06 19:16:56 +01:00
James Cole
42ec3fe02b Merge pull request #9437 from firefly-iii/dependabot/composer/composer-ee86c9e087
Bump the composer group across 1 directory with 2 updates
2024-11-06 19:16:42 +01:00
dependabot[bot]
370a398b5e Bump the composer group across 1 directory with 2 updates
Bumps the composer group with 2 updates in the / directory: [symfony/http-foundation](https://github.com/symfony/http-foundation) and [symfony/process](https://github.com/symfony/process).


Updates `symfony/http-foundation` from 7.1.5 to 7.1.7
- [Release notes](https://github.com/symfony/http-foundation/releases)
- [Changelog](https://github.com/symfony/http-foundation/blob/7.1/CHANGELOG.md)
- [Commits](https://github.com/symfony/http-foundation/compare/v7.1.5...v7.1.7)

Updates `symfony/process` from 7.1.5 to 7.1.7
- [Release notes](https://github.com/symfony/process/releases)
- [Changelog](https://github.com/symfony/process/blob/7.1/CHANGELOG.md)
- [Commits](https://github.com/symfony/process/compare/v7.1.5...v7.1.7)

---
updated-dependencies:
- dependency-name: symfony/http-foundation
  dependency-type: indirect
  dependency-group: composer
- dependency-name: symfony/process
  dependency-type: indirect
  dependency-group: composer
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-06 18:12:57 +00:00
James Cole
554d89b6e9 Merge pull request #9435 from firefly-iii/dependabot/composer/composer-cdaff7c96f 2024-11-06 19:12:21 +01:00
dependabot[bot]
cb049f5dda Bump symfony/http-client in the composer group across 1 directory
Bumps the composer group with 1 update in the / directory: [symfony/http-client](https://github.com/symfony/http-client).


Updates `symfony/http-client` from 7.1.5 to 7.1.7
- [Release notes](https://github.com/symfony/http-client/releases)
- [Changelog](https://github.com/symfony/http-client/blob/7.1/CHANGELOG.md)
- [Commits](https://github.com/symfony/http-client/compare/v7.1.5...v7.1.7)

---
updated-dependencies:
- dependency-name: symfony/http-client
  dependency-type: direct:production
  dependency-group: composer
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-06 15:27:07 +00:00
James Cole
0728668d41 Add timezone fields. 2024-11-06 14:10:34 +01:00
github-actions
dfc187874e Auto commit for release 'develop' on 2024-11-06 2024-11-06 14:02:38 +01:00
James Cole
225588f3e7 Update changelog and API 2024-11-06 13:50:47 +01:00
James Cole
06cc6c29aa Expand changelog. 2024-11-06 12:08:14 +01:00
James Cole
b2d4469908 Mild code cleanup. 2024-11-06 12:01:29 +01:00
James Cole
c398383905 Add column to fillable array 2024-11-06 11:59:37 +01:00
James Cole
7af9dce33b Add timezone to date objects 2024-11-06 11:57:12 +01:00
James Cole
038790a5d6 Add timezone info to new objects. 2024-11-06 11:12:26 +01:00
James Cole
fb3295bde1 Clean up code. 2024-11-06 11:12:12 +01:00
James Cole
43a4fd2ecb Fix https://github.com/firefly-iii/firefly-iii/issues/9427 2024-11-06 11:12:04 +01:00
James Cole
899c72d068 Add timezone. Cheap and backwards compatible. 2024-11-06 11:11:52 +01:00
James Cole
d118c0d886 Fix date requests 2024-11-06 11:11:38 +01:00
github-actions
6d4004d1ed Auto commit for release 'develop' on 2024-11-04 2024-11-04 04:14:51 +01:00
James Cole
ae60cd5b28 Fix https://github.com/firefly-iii/firefly-iii/issues/9294 2024-11-03 19:42:48 +01:00
github-actions
ab31a72199 Auto commit for release 'develop' on 2024-11-03 2024-11-03 09:01:53 +01:00
James Cole
2c1b9534f3 Sort piggy banks 2024-11-03 08:56:19 +01:00
James Cole
7028cb1546 Some code for https://github.com/firefly-iii/firefly-iii/issues/9294 2024-11-03 08:16:46 +01:00
James Cole
dc1ecf6a42 Fix https://github.com/firefly-iii/firefly-iii/issues/9389 2024-11-03 07:43:55 +01:00
James Cole
3a27f9d02c Expand piggy bank events 2024-11-02 05:17:46 +01:00
James Cole
4b27ab38f8 Fix https://github.com/firefly-iii/firefly-iii/issues/9416 2024-11-02 05:14:03 +01:00
James Cole
df5756dc86 Remove labels after finding them 2024-10-27 20:47:50 +01:00
68 changed files with 1081 additions and 773 deletions

View File

@@ -1259,16 +1259,16 @@
},
{
"name": "symfony/console",
"version": "v7.1.6",
"version": "v7.1.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "bb5192af6edc797cbab5c8e8ecfea2fe5f421e57"
"reference": "3284aafcac338b6e86fd955ee4d794cbe434151a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/bb5192af6edc797cbab5c8e8ecfea2fe5f421e57",
"reference": "bb5192af6edc797cbab5c8e8ecfea2fe5f421e57",
"url": "https://api.github.com/repos/symfony/console/zipball/3284aafcac338b6e86fd955ee4d794cbe434151a",
"reference": "3284aafcac338b6e86fd955ee4d794cbe434151a",
"shasum": ""
},
"require": {
@@ -1332,7 +1332,7 @@
"terminal"
],
"support": {
"source": "https://github.com/symfony/console/tree/v7.1.6"
"source": "https://github.com/symfony/console/tree/v7.1.7"
},
"funding": [
{
@@ -1348,7 +1348,7 @@
"type": "tidelift"
}
],
"time": "2024-10-09T08:46:59+00:00"
"time": "2024-11-05T15:34:55+00:00"
},
{
"name": "symfony/deprecation-contracts",
@@ -2246,16 +2246,16 @@
},
{
"name": "symfony/process",
"version": "v7.1.6",
"version": "v7.1.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
"reference": "6aaa189ddb4ff6b5de8fa3210f2fb42c87b4d12e"
"reference": "9b8a40b7289767aa7117e957573c2a535efe6585"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/6aaa189ddb4ff6b5de8fa3210f2fb42c87b4d12e",
"reference": "6aaa189ddb4ff6b5de8fa3210f2fb42c87b4d12e",
"url": "https://api.github.com/repos/symfony/process/zipball/9b8a40b7289767aa7117e957573c2a535efe6585",
"reference": "9b8a40b7289767aa7117e957573c2a535efe6585",
"shasum": ""
},
"require": {
@@ -2287,7 +2287,7 @@
"description": "Executes commands in sub-processes",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/process/tree/v7.1.6"
"source": "https://github.com/symfony/process/tree/v7.1.7"
},
"funding": [
{
@@ -2303,7 +2303,7 @@
"type": "tidelift"
}
],
"time": "2024-09-25T14:20:29+00:00"
"time": "2024-11-06T09:25:12+00:00"
},
{
"name": "symfony/service-contracts",

View File

@@ -4,6 +4,7 @@
feature:
issues:
# Post a comment, `{issue-author}` is an optional placeholder
unlabel: feature
comment: |
Hi there!
@@ -32,6 +33,7 @@ epic:
Thank you for your contributions.
enhancement:
unlabel: enhancement
issues:
# Post a comment, `{issue-author}` is an optional placeholder
comment: |

View File

@@ -52,7 +52,7 @@ class AboutController extends Controller
$data
= [
'version' => config('firefly.version'),
'api_version' => config('firefly.api_version'),
'api_version' => config('firefly.version'),
'php_version' => $phpVersion,
'os' => $phpOs,
'driver' => $currentDriver,

View File

@@ -46,6 +46,8 @@ class DateRequest extends FormRequest
{
$start = $this->getCarbonDate('start');
$end = $this->getCarbonDate('end');
$start->startOfDay();
$end->endOfDay();
if ($start->diffInYears($end, true) > 5) {
throw new FireflyException('Date range out of range.');
}

View File

@@ -138,7 +138,7 @@ class StoreRequest extends FormRequest
// all custom fields:
'internal_reference' => $this->clearString((string)$object['internal_reference']),
'external_id' => $this->clearString((string)$object['external_id']),
'original_source' => sprintf('ff3-v%s|api-v%s', config('firefly.version'), config('firefly.api_version')),
'original_source' => sprintf('ff3-v%s', config('firefly.version')),
'recurrence_id' => $this->integerFromValue($object['recurrence_id']),
'bunq_payment_id' => $this->clearString((string)$object['bunq_payment_id']),
'external_url' => $this->clearString((string)$object['external_url']),

View File

@@ -44,7 +44,7 @@ class DateRequest extends FormRequest
public function getAll(): array
{
return [
'start' => $this->getCarbonDate('start'),
'start' => $this->getCarbonDate('start')->startOfDay(),
'end' => $this->getCarbonDate('end')->endOfDay(),
];
}

View File

@@ -147,7 +147,7 @@ class StoreRequest extends FormRequest
// all custom fields:
'internal_reference' => $this->clearString((string)$object['internal_reference']),
'external_id' => $this->clearString((string)$object['external_id']),
'original_source' => sprintf('ff3-v%s|api-v%s', config('firefly.version'), config('firefly.api_version')),
'original_source' => sprintf('ff3-v%s', config('firefly.version')),
'recurrence_id' => $this->integerFromValue($object['recurrence_id']),
'bunq_payment_id' => $this->clearString((string)$object['bunq_payment_id']),
'external_url' => $this->clearString((string)$object['external_url']),

View File

@@ -0,0 +1,117 @@
<?php
declare(strict_types=1);
/*
* AddTimezonesToDates.php
* Copyright (c) 2024 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\Console\Commands\Integrity;
use FireflyIII\Console\Commands\ShowsFriendlyMessages;
use FireflyIII\Models\AccountBalance;
use FireflyIII\Models\AvailableBudget;
use FireflyIII\Models\Bill;
use FireflyIII\Models\BudgetLimit;
use FireflyIII\Models\CurrencyExchangeRate;
use FireflyIII\Models\InvitedUser;
use FireflyIII\Models\PiggyBank;
use FireflyIII\Models\PiggyBankEvent;
use FireflyIII\Models\PiggyBankRepetition;
use FireflyIII\Models\Recurrence;
use FireflyIII\Models\Tag;
use FireflyIII\Models\TransactionJournal;
use Illuminate\Console\Command;
use Illuminate\Database\QueryException;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
class AddTimezonesToDates extends Command
{
use ShowsFriendlyMessages;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'firefly-iii:add-timezones-to-dates';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Make sure all dates have a timezone.';
/**
* Execute the console command.
*/
public function handle(): void
{
$models = [
AccountBalance::class => ['date'], // done
AvailableBudget::class => ['start_date', 'end_date'], // done
Bill::class => ['date', 'end_date', 'extension_date'], // done
BudgetLimit::class => ['start_date', 'end_date'], // done
CurrencyExchangeRate::class => ['date'], // done
InvitedUser::class => ['expires'],
PiggyBankEvent::class => ['date'],
PiggyBankRepetition::class => ['startdate', 'targetdate'],
PiggyBank::class => ['startdate', 'targetdate'], // done
Recurrence::class => ['first_date', 'repeat_until', 'latest_date'],
Tag::class => ['date'],
TransactionJournal::class => ['date'],
];
foreach ($models as $model => $fields) {
$this->addTimezoneToModel($model, $fields);
}
}
private function addTimezoneToModel(string $model, array $fields): void
{
foreach ($fields as $field) {
$this->addTimezoneToModelField($model, $field);
}
}
private function addTimezoneToModelField(string $model, string $field): void
{
$shortModel = str_replace('FireflyIII\Models\\', '', $model);
$timezoneField = sprintf('%s_tz', $field);
$items = new Collection();
try {
$items = $model::whereNull($timezoneField)->get();
} catch (QueryException $e) {
$this->friendlyError(sprintf('Cannot add timezone information to field "%s" of model "%s". Field does not exist.', $field, $shortModel));
Log::error($e->getMessage());
}
if (0 === $items->count()) {
$this->friendlyPositive(sprintf('Timezone information is present in field "%s" of model "%s".', $field, $shortModel));
return;
}
$this->friendlyInfo(sprintf('Adding timezone information to field "%s" of model "%s".', $field, $shortModel));
foreach ($items as $item) {
$item->{$timezoneField} = config('app.timezone');
$item->saveQuietly();
}
}
}

View File

@@ -47,6 +47,7 @@ class ReportIntegrity extends Command
return 1;
}
$commands = [
'firefly-iii:add-timezones-to-dates',
'firefly-iii:create-group-memberships',
'firefly-iii:report-empty-objects',
'firefly-iii:report-sum',

View File

@@ -46,10 +46,15 @@ class Cron extends Command
protected $signature = 'firefly-iii:cron
{--F|force : Force the cron job(s) to execute.}
{--date= : Set the date in YYYY-MM-DD to make Firefly III think that\'s the current date.}
{--download-cer : Download exchange rates. Other tasks will be skipped unless also requested.}
{--create-recurring : Create recurring transactions. Other tasks will be skipped unless also requested.}
{--create-auto-budgets : Create auto budgets. Other tasks will be skipped unless also requested.}
{--send-bill-warnings : Send bill warnings. Other tasks will be skipped unless also requested.}
';
public function handle(): int
{
$doAll = !$this->option('download-cer') && !$this->option('create-recurring') && !$this->option('create-auto-budgets') && !$this->option('send-bill-warnings');
$date = null;
try {
@@ -60,7 +65,7 @@ class Cron extends Command
$force = (bool)$this->option('force'); // @phpstan-ignore-line
// Fire exchange rates cron job.
if (true === config('cer.download_enabled')) {
if (true === config('cer.download_enabled') && ($doAll || $this->option('download-cer'))) {
try {
$this->exchangeRatesCronJob($force, $date);
} catch (FireflyException $e) {
@@ -71,30 +76,36 @@ class Cron extends Command
}
// Fire recurring transaction cron job.
try {
$this->recurringCronJob($force, $date);
} catch (FireflyException $e) {
app('log')->error($e->getMessage());
app('log')->error($e->getTraceAsString());
$this->friendlyError($e->getMessage());
if ($doAll || $this->option('create-recurring')) {
try {
$this->recurringCronJob($force, $date);
} catch (FireflyException $e) {
app('log')->error($e->getMessage());
app('log')->error($e->getTraceAsString());
$this->friendlyError($e->getMessage());
}
}
// Fire auto-budget cron job:
try {
$this->autoBudgetCronJob($force, $date);
} catch (FireflyException $e) {
app('log')->error($e->getMessage());
app('log')->error($e->getTraceAsString());
$this->friendlyError($e->getMessage());
if ($doAll || $this->option('create-auto-budgets')) {
try {
$this->autoBudgetCronJob($force, $date);
} catch (FireflyException $e) {
app('log')->error($e->getMessage());
app('log')->error($e->getTraceAsString());
$this->friendlyError($e->getMessage());
}
}
// Fire bill warning cron job
try {
$this->billWarningCronJob($force, $date);
} catch (FireflyException $e) {
app('log')->error($e->getMessage());
app('log')->error($e->getTraceAsString());
$this->friendlyError($e->getMessage());
if ($doAll || $this->option('send-bill-warnings')) {
try {
$this->billWarningCronJob($force, $date);
} catch (FireflyException $e) {
app('log')->error($e->getMessage());
app('log')->error($e->getTraceAsString());
$this->friendlyError($e->getMessage());
}
}
$this->friendlyInfo('More feedback on the cron jobs can be found in the log files.');

View File

@@ -67,6 +67,7 @@ class UpgradeDatabase extends Command
'firefly-iii:restore-oauth-keys',
'firefly-iii:correct-account-balance',
// also just in case, some integrity commands:
'firefly-iii:add-timezones-to-dates',
'firefly-iii:create-group-memberships',
'firefly-iii:upgrade-group-information',
'firefly-iii:upgrade-currency-preferences',

View File

@@ -58,20 +58,23 @@ class BillFactory
/** @var Bill $bill */
$bill = Bill::create(
[
'name' => $data['name'],
'match' => 'MIGRATED_TO_RULES',
'amount_min' => $data['amount_min'],
'user_id' => $this->user->id,
'user_group_id' => $this->user->user_group_id,
'transaction_currency_id' => $currency->id,
'amount_max' => $data['amount_max'],
'date' => $data['date'],
'end_date' => $data['end_date'] ?? null,
'extension_date' => $data['extension_date'] ?? null,
'repeat_freq' => $data['repeat_freq'],
'skip' => $skip,
'automatch' => true,
'active' => $active,
'name' => $data['name'],
'match' => 'MIGRATED_TO_RULES',
'amount_min' => $data['amount_min'],
'user_id' => $this->user->id,
'user_group_id' => $this->user->user_group_id,
'transaction_currency_id' => $currency->id,
'amount_max' => $data['amount_max'],
'date' => $data['date'],
'date_tz' => $data['date']->format('e'),
'end_date' => $data['end_date'] ?? null,
'end_date_tz' => $data['end_date']?->format('e'),
'extension_date' => $data['extension_date'] ?? null,
'extension_date_tz' => $data['extension_date']?->format('e'),
'repeat_freq' => $data['repeat_freq'],
'skip' => $skip,
'automatch' => true,
'active' => $active,
]
);
} catch (QueryException $e) {

View File

@@ -225,7 +225,8 @@ class TransactionJournalFactory
'bill_id' => $billId,
'transaction_currency_id' => $currency->id,
'description' => substr($description, 0, 1000),
'date' => $carbon->format('Y-m-d H:i:s'),
'date' => $carbon,
'date_tz' => $carbon->format('e'),
'order' => $order,
'tag_count' => 0,
'completed' => 0,

View File

@@ -131,12 +131,14 @@ class BudgetLimitHandler
app('log')->debug(sprintf('Will create AB for period %s to %s', $current->format('Y-m-d'), $currentEnd->format('Y-m-d')));
$availableBudget = new AvailableBudget(
[
'user_id' => $budgetLimit->budget->user->id,
'user_group_id' => $budgetLimit->budget->user->user_group_id,
'transaction_currency_id' => $budgetLimit->transaction_currency_id,
'start_date' => $current,
'end_date' => $currentEnd,
'amount' => $amount,
'user_id' => $budgetLimit->budget->user->id,
'user_group_id' => $budgetLimit->budget->user->user_group_id,
'transaction_currency_id' => $budgetLimit->transaction_currency_id,
'start_date' => $current,
'start_date_tz' => $current->format('e'),
'end_date' => $currentEnd,
'end_date_tz' => $currentEnd->format('e'),
'amount' => $amount,
]
);
$availableBudget->save();

View File

@@ -58,6 +58,7 @@ class PiggyBankEventHandler
'piggy_bank_id' => $event->piggyBank->id,
'transaction_journal_id' => $journal?->id,
'date' => $date->format('Y-m-d'),
'date_tz' => $date->format('e'),
'amount' => $event->amount,
]
);

View File

@@ -37,7 +37,9 @@ class PiggyBankObserver
$repetition = new PiggyBankRepetition();
$repetition->piggyBank()->associate($piggyBank);
$repetition->startdate = $piggyBank->startdate;
$repetition->startdate_tz = $piggyBank->startdate->format('e');
$repetition->targetdate = $piggyBank->targetdate;
$repetition->targetdate_tz = $piggyBank->targetdate->format('e');
$repetition->currentamount = '0';
$repetition->save();
}

View File

@@ -117,6 +117,7 @@ class GroupCollector implements GroupCollectorInterface
'transaction_journals.transaction_type_id',
'transaction_journals.description',
'transaction_journals.date',
'transaction_journals.date_tz',
'transaction_journals.order',
// types

View File

@@ -151,6 +151,7 @@ class CategoryController extends Controller
*/
private function reportPeriodChart(Collection $accounts, Carbon $start, Carbon $end, ?Category $category): array
{
$income = [];
$expenses = [];
$categoryId = 0;
@@ -169,8 +170,8 @@ class CategoryController extends Controller
$categoryId = $category->id;
// this gives us all currencies
$collection = new Collection([$category]);
$expenses = $opsRepository->listExpenses($start, $end, null, $collection);
$income = $opsRepository->listIncome($start, $end, null, $collection);
$expenses = $opsRepository->listExpenses($start, $end, $accounts, $collection);
$income = $opsRepository->listIncome($start, $end, $accounts, $collection);
}
$currencies = array_unique(array_merge(array_keys($income), array_keys($expenses)));
$periods = app('navigation')->listOfPeriods($start, $end);

View File

@@ -122,6 +122,7 @@ class CategoryReportController extends Controller
public function categoryIncome(Collection $accounts, Collection $categories, Carbon $start, Carbon $end): JsonResponse
{
$result = [];
$earned = $this->opsRepository->listIncome($start, $end, $accounts, $categories);

View File

@@ -65,6 +65,16 @@ class FrontpageController extends Controller
$info[] = $entry;
}
}
// sort by current percentage (lowest at the top)
uasort(
$info,
static function (array $a, array $b) {
return $a['percentage'] <=> $b['percentage'];
}
);
$html = '';
if (0 !== count($info)) {
try {

View File

@@ -77,17 +77,18 @@ class ShowController extends Controller
*/
public function show(Recurrence $recurrence)
{
$repos = app(AttachmentRepositoryInterface::class);
$repos = app(AttachmentRepositoryInterface::class);
/** @var RecurrenceTransformer $transformer */
$transformer = app(RecurrenceTransformer::class);
$transformer = app(RecurrenceTransformer::class);
$transformer->setParameters(new ParameterBag());
$array = $transformer->transform($recurrence);
$array = $transformer->transform($recurrence);
$groups = $this->recurring->getTransactions($recurrence);
$today = today(config('app.timezone'));
$array['repeat_until'] = null !== $array['repeat_until'] ? new Carbon($array['repeat_until']) : null;
$groups = $this->recurring->getTransactions($recurrence);
$today = today(config('app.timezone'));
$array['repeat_until'] = null !== $array['repeat_until'] ? new Carbon($array['repeat_until']) : null;
$array['journal_count'] = $this->recurring->getJournalCount($recurrence);
// transform dates back to Carbon objects and expand information
foreach ($array['repetitions'] as $index => $repetition) {
@@ -103,9 +104,9 @@ class ShowController extends Controller
}
// add attachments to the recurrence object.
$attachments = $recurrence->attachments()->get();
$array['attachments'] = [];
$attachmentTransformer = app(AttachmentTransformer::class);
$attachments = $recurrence->attachments()->get();
$array['attachments'] = [];
$attachmentTransformer = app(AttachmentTransformer::class);
/** @var Attachment $attachment */
foreach ($attachments as $attachment) {
@@ -114,7 +115,16 @@ class ShowController extends Controller
$array['attachments'][] = $item;
}
$subTitle = (string)trans('firefly.overview_for_recurrence', ['title' => $recurrence->title]);
if (null !== $array['nr_of_repetitions']) {
$left = $array['nr_of_repetitions'] - $array['journal_count'];
$left = max(0, $left);
// limit each repetition to X occurrences:
foreach ($array['repetitions'] as $index => $repetition) {
$array['repetitions'][$index]['occurrences'] = array_slice($repetition['occurrences'], 0, $left);
}
}
$subTitle = (string)trans('firefly.overview_for_recurrence', ['title' => $recurrence->title]);
return view('recurring.show', compact('recurrence', 'subTitle', 'array', 'groups', 'today'));
}

View File

@@ -40,24 +40,24 @@ class TriggerController extends Controller
{
public function trigger(Recurrence $recurrence, TriggerRecurrenceRequest $request): RedirectResponse
{
$all = $request->getAll();
$date = $all['date'];
$all = $request->getAll();
$date = $all['date'];
// grab the date from the last time the recurrence fired:
$backupDate = $recurrence->latest_date;
$backupDate = $recurrence->latest_date;
// fire the recurring cron job on the given date, then post-date the created transaction.
app('log')->info(sprintf('Trigger: will now fire recurring cron job task for date "%s".', $date->format('Y-m-d H:i:s')));
/** @var CreateRecurringTransactions $job */
$job = app(CreateRecurringTransactions::class);
$job = app(CreateRecurringTransactions::class);
$job->setRecurrences(new Collection([$recurrence]));
$job->setDate($date);
$job->setForce(false);
$job->handle();
app('log')->debug('Done with recurrence.');
$groups = $job->getGroups();
$groups = $job->getGroups();
/** @var TransactionGroup $group */
foreach ($groups as $group) {
@@ -68,7 +68,8 @@ class TriggerController extends Controller
$journal->save();
}
}
$recurrence->latest_date = $backupDate;
$recurrence->latest_date = $backupDate;
$recurrence->latest_date_tz = $backupDate?->format('e');
$recurrence->save();
app('preferences')->mark();

View File

@@ -177,10 +177,11 @@ class CreateRecurringTransactions implements ShouldQueue
// has repeated X times.
$journalCount = $this->repository->getJournalCount($recurrence);
if (0 !== $recurrence->repetitions && $journalCount >= $recurrence->repetitions && false === $this->force) {
app('log')->info(sprintf('Recurrence #%d has run %d times, so will run no longer.', $recurrence->id, $recurrence->repetitions));
app('log')->info(sprintf('Recurrence #%d has run %d times, so will run no longer.', $recurrence->id, $journalCount));
return false;
}
app('log')->debug(sprintf('Recurrence #%d has run %d times, max is %d times.', $recurrence->id, $journalCount, $recurrence->repetitions));
// is no longer running
if ($this->repeatUntilHasPassed($recurrence)) {
@@ -202,8 +203,8 @@ class CreateRecurringTransactions implements ShouldQueue
sprintf(
'Recurrence #%d is set to run on %s, and today\'s date is %s. Skipped.',
$recurrence->id,
$recurrence->first_date->format('Y-m-d'),
$this->date->format('Y-m-d')
$recurrence->first_date->format('Y-m-d H:i:s'),
$this->date->format('Y-m-d H:i:s')
)
);
@@ -244,9 +245,10 @@ class CreateRecurringTransactions implements ShouldQueue
private function hasNotStartedYet(Recurrence $recurrence): bool
{
$startDate = $this->getStartDate($recurrence);
app('log')->debug(sprintf('Start date is %s', $startDate->format('Y-m-d')));
app('log')->debug(sprintf('Start date is %s', $startDate->format('Y-m-d H:i:s')));
app('log')->debug(sprintf('Ask date is %s', $this->date->format('Y-m-d H:i:s')));
return $startDate->gt($this->date);
return $startDate->gte($this->date);
}
/**
@@ -341,7 +343,7 @@ class CreateRecurringTransactions implements ShouldQueue
app('log')->debug(sprintf('%s IS today (%s)', $date->format('Y-m-d'), $this->date->format('Y-m-d')));
// count created journals on THIS day.
$journalCount = $this->repository->getJournalCount($recurrence, $date, $date);
$journalCount = $this->repository->getJournalCount($recurrence, $date, $date);
if ($journalCount > 0 && false === $this->force) {
app('log')->info(sprintf('Already created %d journal(s) for date %s', $journalCount, $date->format('Y-m-d')));
@@ -359,11 +361,11 @@ class CreateRecurringTransactions implements ShouldQueue
}
// create transaction array and send to factory.
$groupTitle = null;
$count = $recurrence->recurrenceTransactions->count();
$groupTitle = null;
$count = $recurrence->recurrenceTransactions->count();
// #8844, if there is one recurrence transaction, use the first title as the title.
// #9305, if there is one recurrence transaction, group title must be NULL.
$groupTitle = null;
$groupTitle = null;
// #8844, if there are more, use the recurrence transaction itself.
if ($count > 1) {
@@ -376,14 +378,14 @@ class CreateRecurringTransactions implements ShouldQueue
return null;
}
$array = [
$array = [
'user' => $recurrence->user_id,
'group_title' => $groupTitle,
'transactions' => $this->getTransactionData($recurrence, $repetition, $date),
];
/** @var TransactionGroup $group */
$group = $this->groupRepository->store($array);
$group = $this->groupRepository->store($array);
++$this->created;
app('log')->info(sprintf('Created new transaction group #%d', $group->id));
@@ -392,7 +394,8 @@ class CreateRecurringTransactions implements ShouldQueue
$this->groups->push($group);
// update recurring thing:
$recurrence->latest_date = $date;
$recurrence->latest_date = $date;
$recurrence->latest_date_tz = $date?->format('e');
$recurrence->save();
return $group;

View File

@@ -14,7 +14,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
class AccountBalance extends Model
{
use HasFactory;
protected $fillable = ['account_id', 'title', 'transaction_currency_id', 'balance'];
protected $fillable = ['account_id', 'title', 'transaction_currency_id', 'balance', 'date', 'date_tz'];
public function account(): BelongsTo
{

View File

@@ -23,6 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use FireflyIII\Enums\AccountTypeEnum;
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
@@ -34,19 +35,46 @@ class AccountType extends Model
{
use ReturnsIntegerIdTrait;
/** @deprecated */
public const string ASSET = 'Asset account';
/** @deprecated */
public const string BENEFICIARY = 'Beneficiary account';
/** @deprecated */
public const string CASH = 'Cash account';
/** @deprecated */
public const string CREDITCARD = 'Credit card';
/** @deprecated */
public const string DEBT = 'Debt';
/** @deprecated */
public const string DEFAULT = 'Default account';
/** @deprecated */
public const string EXPENSE = 'Expense account';
/** @deprecated */
public const string IMPORT = 'Import account';
/** @deprecated */
public const string INITIAL_BALANCE = 'Initial balance account';
/** @deprecated */
public const string LIABILITY_CREDIT = 'Liability credit account';
/** @deprecated */
public const string LOAN = 'Loan';
/** @deprecated */
public const string MORTGAGE = 'Mortgage';
/** @deprecated */
public const string RECONCILIATION = 'Reconciliation account';
/** @deprecated */
public const string REVENUE = 'Revenue account';
protected $casts
@@ -61,4 +89,11 @@ class AccountType extends Model
{
return $this->hasMany(Account::class);
}
protected function casts(): array
{
return [
// 'type' => AccountTypeEnum::class,
];
}
}

View File

@@ -24,6 +24,7 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use FireflyIII\Enums\AutoBudgetType;
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
@@ -53,6 +54,13 @@ class AutoBudget extends Model
return $this->belongsTo(TransactionCurrency::class);
}
protected function casts(): array
{
return [
// 'auto_budget_type' => AutoBudgetType::class,
];
}
protected function amount(): Attribute
{
return Attribute::make(

View File

@@ -51,7 +51,7 @@ class AvailableBudget extends Model
'transaction_currency_id' => 'int',
];
protected $fillable = ['user_id', 'user_group_id', 'transaction_currency_id', 'amount', 'start_date', 'end_date'];
protected $fillable = ['user_id', 'user_group_id', 'transaction_currency_id', 'amount', 'start_date', 'end_date', 'start_date_tz', 'end_date_tz'];
/**
* Route binder. Converts the key in the URL to the specified object (or throw 404).

View File

@@ -68,6 +68,7 @@ class Bill extends Model
'user_group_id',
'amount_max',
'date',
'date_tz',
'repeat_freq',
'skip',
'automatch',
@@ -75,6 +76,8 @@ class Bill extends Model
'transaction_currency_id',
'end_date',
'extension_date',
'end_date_tz',
'extension_date_tz',
];
protected $hidden = ['amount_min_encrypted', 'amount_max_encrypted', 'name_encrypted', 'match_encrypted'];

View File

@@ -54,7 +54,7 @@ class BudgetLimit extends Model
'deleted' => Deleted::class,
];
protected $fillable = ['budget_id', 'start_date', 'end_date', 'amount', 'transaction_currency_id'];
protected $fillable = ['budget_id', 'start_date', 'end_date', 'start_date_tz', 'end_date_tz', 'amount', 'transaction_currency_id'];
/**
* Route binder. Converts the key in the URL to the specified object (or throw 404).

View File

@@ -49,7 +49,7 @@ class CurrencyExchangeRate extends Model
'to_currency_id' => 'int',
'date' => 'datetime',
];
protected $fillable = ['user_id', 'from_currency_id', 'to_currency_id', 'date', 'rate'];
protected $fillable = ['user_id', 'from_currency_id', 'to_currency_id', 'date', 'date_tz', 'rate'];
public function fromCurrency(): BelongsTo
{

View File

@@ -44,7 +44,7 @@ class InvitedUser extends Model
'expires' => 'datetime',
'redeemed' => 'boolean',
];
protected $fillable = ['user_id', 'email', 'invite_code', 'expires', 'redeemed'];
protected $fillable = ['user_id', 'email', 'invite_code', 'expires', 'expires_tz', 'redeemed'];
/**
* Route binder. Converts the key in the URL to the specified object (or throw 404).

View File

@@ -53,7 +53,7 @@ class PiggyBank extends Model
'encrypted' => 'boolean',
];
protected $fillable = ['name', 'account_id', 'order', 'targetamount', 'startdate', 'targetdate', 'active'];
protected $fillable = ['name', 'account_id', 'order', 'targetamount', 'startdate', 'startdate_tz', 'targetdate', 'targetdate_tz', 'active'];
protected $hidden = ['targetamount_encrypted', 'encrypted'];

View File

@@ -42,7 +42,7 @@ class PiggyBankEvent extends Model
'date' => 'date',
];
protected $fillable = ['piggy_bank_id', 'transaction_journal_id', 'date', 'amount'];
protected $fillable = ['piggy_bank_id', 'transaction_journal_id', 'date', 'date_tz', 'amount'];
protected $hidden = ['amount_encrypted'];

View File

@@ -45,7 +45,7 @@ class PiggyBankRepetition extends Model
'targetdate' => 'date',
];
protected $fillable = ['piggy_bank_id', 'startdate', 'targetdate', 'currentamount'];
protected $fillable = ['piggy_bank_id', 'startdate', 'startdate_tz', 'targetdate', 'targetdate_tz', 'currentamount'];
public function piggyBank(): BelongsTo
{

View File

@@ -60,7 +60,7 @@ class Recurrence extends Model
];
protected $fillable
= ['user_id', 'transaction_type_id', 'title', 'description', 'first_date', 'repeat_until', 'latest_date', 'repetitions', 'apply_rules', 'active'];
= ['user_id', 'transaction_type_id', 'title', 'description', 'first_date', 'first_date_tz', 'repeat_until', 'repeat_until_tz', 'latest_date', 'latest_date_tz', 'repetitions', 'apply_rules', 'active'];
/** @var string The table to store the data in */
protected $table = 'recurrences';

View File

@@ -23,6 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use FireflyIII\Enums\RecurrenceRepetitionWeekend;
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
@@ -58,6 +59,13 @@ class RecurrenceRepetition extends Model
/** @var string The table to store the data in */
protected $table = 'recurrences_repetitions';
protected function casts(): array
{
return [
// 'weekend' => RecurrenceRepetitionWeekend::class,
];
}
public function recurrence(): BelongsTo
{
return $this->belongsTo(Recurrence::class);

View File

@@ -53,7 +53,7 @@ class Tag extends Model
'longitude' => 'float',
];
protected $fillable = ['user_id', 'user_group_id', 'tag', 'date', 'description', 'tagMode'];
protected $fillable = ['user_id', 'user_group_id', 'tag', 'date', 'date_tz', 'description', 'tagMode'];
protected $hidden = ['zoomLevel', 'latitude', 'longitude'];

View File

@@ -37,6 +37,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
@@ -76,6 +77,7 @@ class TransactionJournal extends Model
'completed',
'order',
'date',
'date_tz',
];
protected $hidden = ['encrypted'];
@@ -88,7 +90,7 @@ class TransactionJournal extends Model
public static function routeBinder(string $value): self
{
if (auth()->check()) {
$journalId = (int)$value;
$journalId = (int) $value;
/** @var User $user */
$user = auth()->user();
@@ -167,12 +169,16 @@ class TransactionJournal extends Model
public function scopeAfter(EloquentBuilder $query, Carbon $date): EloquentBuilder
{
return $query->where('transaction_journals.date', '>=', $date->format('Y-m-d 00:00:00'));
Log::debug(sprintf('scopeAfter("%s")', $date->format('Y-m-d H:i:s')));
return $query->where('transaction_journals.date', '>=', $date->format('Y-m-d H:i:s'));
}
public function scopeBefore(EloquentBuilder $query, Carbon $date): EloquentBuilder
{
return $query->where('transaction_journals.date', '<=', $date->format('Y-m-d 00:00:00'));
Log::debug(sprintf('scopeBefore("%s")', $date->format('Y-m-d H:i:s')));
return $query->where('transaction_journals.date', '<=', $date->format('Y-m-d H:i:s'));
}
public function scopeTransactionTypes(EloquentBuilder $query, array $types): void
@@ -238,14 +244,14 @@ class TransactionJournal extends Model
protected function order(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int)$value,
get: static fn ($value) => (int) $value,
);
}
protected function transactionTypeId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int)$value,
get: static fn ($value) => (int) $value,
);
}
}

View File

@@ -23,6 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use FireflyIII\Enums\TransactionTypeEnum;
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
@@ -53,6 +54,13 @@ class TransactionType extends Model
];
protected $fillable = ['type'];
protected function casts(): array
{
return [
// 'type' => TransactionTypeEnum::class,
];
}
/**
* Route binder. Converts the key in the URL to the specified object (or throw 404).
*

View File

@@ -53,6 +53,15 @@ class Webhook extends Model
];
protected $fillable = ['active', 'trigger', 'response', 'delivery', 'user_id', 'user_group_id', 'url', 'title', 'secret'];
protected function casts(): array
{
return [
// 'delivery' => WebhookDelivery::class,
// 'response' => WebhookResponse::class,
// 'trigger' => WebhookTrigger::class,
];
}
public static function getDeliveries(): array
{
$array = [];
@@ -130,7 +139,7 @@ class Webhook extends Model
public static function routeBinder(string $value): self
{
if (auth()->check()) {
$webhookId = (int)$value;
$webhookId = (int) $value;
/** @var User $user */
$user = auth()->user();

View File

@@ -306,6 +306,8 @@ class BillRepository implements BillRepositoryInterface
{
// app('log')->debug('Now in getPaidDatesInRange()');
Log::debug(sprintf('Search for linked journals between %s and %s', $start->toW3cString(), $end->toW3cString()));
return $bill->transactionJournals()
->before($end)->after($start)->get(
[
@@ -435,15 +437,17 @@ class BillRepository implements BillRepositoryInterface
}
// find the most recent date for this bill NOT in the future. Cache this date:
$start = clone $bill->date;
$start->startOfDay();
app('log')->debug('nextExpectedMatch: Start is '.$start->format('Y-m-d'));
while ($start < $date) {
app('log')->debug(sprintf('$start (%s) < $date (%s)', $start->format('Y-m-d'), $date->format('Y-m-d')));
app('log')->debug(sprintf('$start (%s) < $date (%s)', $start->format('Y-m-d H:i:s'), $date->format('Y-m-d H:i:s')));
$start = app('navigation')->addPeriod($start, $bill->repeat_freq, $bill->skip);
app('log')->debug('Start is now '.$start->format('Y-m-d'));
app('log')->debug('Start is now '.$start->format('Y-m-d H:i:s'));
}
$end = app('navigation')->addPeriod($start, $bill->repeat_freq, $bill->skip);
$end->endOfDay();
// see if the bill was paid in this period.
$journalCount = $bill->transactionJournals()->before($end)->after($start)->count();

View File

@@ -198,11 +198,13 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface
->where('end_date', $end->format('Y-m-d'))->first()
;
if (null === $availableBudget) {
$availableBudget = new AvailableBudget();
$availableBudget = new AvailableBudget();
$availableBudget->user()->associate($this->user);
$availableBudget->transactionCurrency()->associate($currency);
$availableBudget->start_date = $start->startOfDay()->format('Y-m-d'); // @phpstan-ignore-line
$availableBudget->end_date = $end->endOfDay()->format('Y-m-d'); // @phpstan-ignore-line
$availableBudget->start_date = $start->startOfDay()->format('Y-m-d'); // @phpstan-ignore-line
$availableBudget->start_date_tz = $start->format('e');
$availableBudget->end_date = $end->endOfDay()->format('Y-m-d'); // @phpstan-ignore-line
$availableBudget->end_date_tz = $end->format('e');
}
$availableBudget->amount = $amount;
$availableBudget->save();
@@ -235,7 +237,9 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface
'transaction_currency_id' => $data['currency_id'],
'amount' => $data['amount'],
'start_date' => $start->format('Y-m-d'),
'start_date_tz' => $start->format('e'),
'end_date' => $end->format('Y-m-d'),
'end_date_tz' => $end->format('e'),
]
);
}
@@ -255,8 +259,9 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface
if (array_key_exists('start', $data)) {
$start = $data['start'];
if ($start instanceof Carbon) {
$start = $data['start']->startOfDay();
$availableBudget->start_date = $start->format('Y-m-d');
$start = $data['start']->startOfDay();
$availableBudget->start_date = $start->format('Y-m-d');
$availableBudget->start_date_tz = $start->format('e');
$availableBudget->save();
}
}
@@ -264,8 +269,9 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface
if (array_key_exists('end', $data)) {
$end = $data['end'];
if ($end instanceof Carbon) {
$end = $data['end']->endOfDay();
$availableBudget->end_date = $end->format('Y-m-d');
$end = $data['end']->endOfDay();
$availableBudget->end_date = $end->format('Y-m-d');
$availableBudget->end_date_tz = $end->format('e');
$availableBudget->save();
}
}

View File

@@ -277,7 +277,7 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
$currency->save();
// find the budget:
$budget = $this->user->budgets()->find((int)$data['budget_id']);
$budget = $this->user->budgets()->find((int) $data['budget_id']);
if (null === $budget) {
throw new FireflyException('200004: Budget does not exist.');
}
@@ -323,8 +323,15 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
{
$budgetLimit->amount = array_key_exists('amount', $data) ? $data['amount'] : $budgetLimit->amount;
$budgetLimit->budget_id = array_key_exists('budget_id', $data) ? $data['budget_id'] : $budgetLimit->budget_id;
$budgetLimit->start_date = array_key_exists('start', $data) ? $data['start']->format('Y-m-d 00:00:00') : $budgetLimit->start_date;
$budgetLimit->end_date = array_key_exists('end', $data) ? $data['end']->format('Y-m-d 23:59:59') : $budgetLimit->end_date;
if (array_key_exists('start', $data)) {
$budgetLimit->start_date = $data['start']->startOfDay();
$budgetLimit->start_date_tz = $data['start']->format('e');
}
if (array_key_exists('end', $data)) {
$budgetLimit->end_date = $data['end']->endOfDay();
$budgetLimit->end_date_tz = $data['end']->format('e');
}
// if no currency has been provided, use the user's default currency:
$currency = null;
@@ -351,7 +358,7 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
public function updateLimitAmount(Budget $budget, Carbon $start, Carbon $end, string $amount): ?BudgetLimit
{
// count the limits:
$limits = $budget->budgetlimits()
$limits = $budget->budgetlimits()
->where('budget_limits.start_date', $start->format('Y-m-d 00:00:00'))
->where('budget_limits.end_date', $end->format('Y-m-d 00:00:00'))
->count('budget_limits.*')
@@ -360,7 +367,7 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
// there might be a budget limit for these dates:
/** @var null|BudgetLimit $limit */
$limit = $budget->budgetlimits()
$limit = $budget->budgetlimits()
->where('budget_limits.start_date', $start->format('Y-m-d 00:00:00'))
->where('budget_limits.end_date', $end->format('Y-m-d 00:00:00'))
->first(['budget_limits.*'])
@@ -395,11 +402,13 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
}
app('log')->debug('No existing budget limit, create a new one');
// or create one and return it.
$limit = new BudgetLimit();
$limit = new BudgetLimit();
$limit->budget()->associate($budget);
$limit->start_date = $start->startOfDay();
$limit->end_date = $end->startOfDay();
$limit->amount = $amount;
$limit->start_date = $start->startOfDay();
$limit->start_date_tz = $start->format('e');
$limit->end_date = $end->startOfDay();
$limit->end_date_tz = $end->format('e');
$limit->amount = $amount;
$limit->save();
app('log')->debug(sprintf('Created new budget limit with ID #%d and amount %s', $limit->id, $amount));

View File

@@ -89,11 +89,12 @@ class CurrencyRepository implements CurrencyRepositoryInterface
{
return CurrencyExchangeRate::create(
[
'user_id' => $this->user->id,
'from_currency_id' => $fromCurrency->id,
'to_currency_id' => $toCurrency->id,
'date' => $date,
'rate' => $rate,
'user_id' => $this->user->id,
'from_currency_id' => $fromCurrency->id,
'to_currency_id' => $toCurrency->id,
'date' => $date,
'date_tz' => $date->format('e'),
'rate' => $rate,
]
);
}

View File

@@ -178,12 +178,12 @@ trait ModifiesPiggyBanks
*/
public function store(array $data): PiggyBank
{
$order = $this->getMaxOrder() + 1;
$order = $this->getMaxOrder() + 1;
if (array_key_exists('order', $data)) {
$order = $data['order'];
}
$data['order'] = 31337; // very high when creating.
$piggyData = $data;
$data['order'] = 31337; // very high when creating.
$piggyData = $data;
// unset fields just in case.
unset($piggyData['object_group_title'], $piggyData['object_group_id'], $piggyData['notes'], $piggyData['current_amount']);
@@ -192,6 +192,9 @@ trait ModifiesPiggyBanks
$piggyData['targetamount'] = '0';
}
$piggyData['startdate_tz'] = $piggyData['startdate']?->format('e');
$piggyData['targetdate_tz'] = $piggyData['targetdate']?->format('e');
try {
/** @var PiggyBank $piggyBank */
$piggyBank = PiggyBank::create($piggyData);
@@ -208,13 +211,13 @@ trait ModifiesPiggyBanks
$this->updateNote($piggyBank, $data['notes']);
// repetition is auto created.
$repetition = $this->getRepetition($piggyBank);
$repetition = $this->getRepetition($piggyBank);
if (null !== $repetition && array_key_exists('current_amount', $data) && '' !== $data['current_amount']) {
$repetition->currentamount = $data['current_amount'];
$repetition->save();
}
$objectGroupTitle = $data['object_group_title'] ?? '';
$objectGroupTitle = $data['object_group_title'] ?? '';
if ('' !== $objectGroupTitle) {
$objectGroup = $this->findOrCreateObjectGroup($objectGroupTitle);
if (null !== $objectGroup) {
@@ -223,7 +226,7 @@ trait ModifiesPiggyBanks
}
}
// try also with ID
$objectGroupId = (int)($data['object_group_id'] ?? 0);
$objectGroupId = (int)($data['object_group_id'] ?? 0);
if (0 !== $objectGroupId) {
$objectGroup = $this->findObjectGroupById($objectGroupId);
if (null !== $objectGroup) {
@@ -373,10 +376,12 @@ trait ModifiesPiggyBanks
$piggyBank->targetamount = '0';
}
if (array_key_exists('targetdate', $data) && '' !== $data['targetdate']) {
$piggyBank->targetdate = $data['targetdate'];
$piggyBank->targetdate = $data['targetdate'];
$piggyBank->targetdate_tz = $data['targetdate']->format('e');
}
if (array_key_exists('startdate', $data)) {
$piggyBank->startdate = $data['startdate'];
$piggyBank->startdate = $data['startdate'];
$piggyBank->startdate_tz = $data['targetdate']->format('e');
}
$piggyBank->save();

View File

@@ -67,13 +67,13 @@ class RecurringRepository implements RecurringRepositoryInterface
$set
= TransactionJournalMeta::where(static function (Builder $q1) use ($recurrence): void {
$q1->where('name', 'recurrence_id');
$q1->where('data', json_encode((string)$recurrence->id));
$q1->where('data', json_encode((string) $recurrence->id));
})->get(['journal_meta.transaction_journal_id']);
// there are X journals made for this recurrence. Any of them meant for today?
foreach ($set as $journalMeta) {
$count = TransactionJournalMeta::where(static function (Builder $q2) use ($date): void {
$string = (string)$date;
$string = (string) $date;
app('log')->debug(sprintf('Search for date: %s', json_encode($string)));
$q2->where('name', 'recurrence_date');
$q2->where('data', json_encode($string));
@@ -141,7 +141,7 @@ class RecurringRepository implements RecurringRepositoryInterface
/** @var RecurrenceTransactionMeta $meta */
foreach ($recTransaction->recurrenceTransactionMeta as $meta) {
if ('bill_id' === $meta->name) {
$return = (int)$meta->value;
$return = (int) $meta->value;
}
}
@@ -158,7 +158,7 @@ class RecurringRepository implements RecurringRepositoryInterface
/** @var RecurrenceTransactionMeta $meta */
foreach ($recTransaction->recurrenceTransactionMeta as $meta) {
if ('budget_id' === $meta->name) {
$return = (int)$meta->value;
$return = (int) $meta->value;
}
}
@@ -175,7 +175,7 @@ class RecurringRepository implements RecurringRepositoryInterface
/** @var RecurrenceTransactionMeta $meta */
foreach ($recTransaction->recurrenceTransactionMeta as $meta) {
if ('category_id' === $meta->name) {
$return = (int)$meta->value;
$return = (int) $meta->value;
}
}
@@ -192,7 +192,7 @@ class RecurringRepository implements RecurringRepositoryInterface
/** @var RecurrenceTransactionMeta $meta */
foreach ($recTransaction->recurrenceTransactionMeta as $meta) {
if ('category_name' === $meta->name) {
$return = (string)$meta->value;
$return = (string) $meta->value;
}
}
@@ -204,6 +204,7 @@ class RecurringRepository implements RecurringRepositoryInterface
*/
public function getJournalCount(Recurrence $recurrence, ?Carbon $start = null, ?Carbon $end = null): int
{
Log::debug(sprintf('Now in getJournalCount(#%d, "%s", "%s")', $recurrence->id, $start?->format('Y-m-d H:i:s'), $end?->format('Y-m-d H:i:s')));
$query = TransactionJournal::leftJoin('journal_meta', 'journal_meta.transaction_journal_id', '=', 'transaction_journals.id')
->where('transaction_journals.user_id', $recurrence->user_id)
->whereNull('transaction_journals.deleted_at')
@@ -216,8 +217,10 @@ class RecurringRepository implements RecurringRepositoryInterface
if (null !== $end) {
$query->where('transaction_journals.date', '<=', $end->format('Y-m-d 00:00:00'));
}
$count = $query->count('transaction_journals.id');
Log::debug(sprintf('Count is %d', $count));
return $query->count('transaction_journals.id');
return $count;
}
/**
@@ -228,7 +231,7 @@ class RecurringRepository implements RecurringRepositoryInterface
return TransactionJournalMeta::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id')
->where('transaction_journals.user_id', $this->user->id)
->where('journal_meta.name', '=', 'recurrence_id')
->where('journal_meta.data', '=', json_encode((string)$recurrence->id))
->where('journal_meta.data', '=', json_encode((string) $recurrence->id))
->get(['journal_meta.transaction_journal_id'])->pluck('transaction_journal_id')->toArray()
;
}
@@ -241,7 +244,7 @@ class RecurringRepository implements RecurringRepositoryInterface
/** @var null|Note $note */
$note = $recurrence->notes()->first();
return (string)$note?->text;
return (string) $note?->text;
}
public function getPiggyBank(RecurrenceTransaction $transaction): ?int
@@ -251,7 +254,7 @@ class RecurringRepository implements RecurringRepositoryInterface
/** @var RecurrenceTransactionMeta $metaEntry */
foreach ($meta as $metaEntry) {
if ('piggy_bank_id' === $metaEntry->name) {
return (int)$metaEntry->value;
return (int) $metaEntry->value;
}
}
@@ -281,12 +284,12 @@ class RecurringRepository implements RecurringRepositoryInterface
->whereNull('transaction_journals.deleted_at')
->where('transaction_journals.user_id', $this->user->id)
->where('name', 'recurrence_id')
->where('data', json_encode((string)$recurrence->id))
->where('data', json_encode((string) $recurrence->id))
->get()->pluck('transaction_journal_id')->toArray()
;
$search = [];
foreach ($journalMeta as $journalId) {
$search[] = (int)$journalId;
$search[] = (int) $journalId;
}
/** @var GroupCollectorInterface $collector */
@@ -314,13 +317,13 @@ class RecurringRepository implements RecurringRepositoryInterface
->whereNull('transaction_journals.deleted_at')
->where('transaction_journals.user_id', $this->user->id)
->where('name', 'recurrence_id')
->where('data', json_encode((string)$recurrence->id))
->where('data', json_encode((string) $recurrence->id))
->get()->pluck('transaction_journal_id')->toArray()
;
$search = [];
foreach ($journalMeta as $journalId) {
$search[] = (int)$journalId;
$search[] = (int) $journalId;
}
if (0 === count($search)) {
return new Collection();
@@ -442,28 +445,28 @@ class RecurringRepository implements RecurringRepositoryInterface
if (is_array($language)) {
$language = 'en_US';
}
$language = (string)$language;
$language = (string) $language;
if ('daily' === $repetition->repetition_type) {
return (string)trans('firefly.recurring_daily', [], $language);
return (string) trans('firefly.recurring_daily', [], $language);
}
if ('weekly' === $repetition->repetition_type) {
$dayOfWeek = trans(sprintf('config.dow_%s', $repetition->repetition_moment), [], $language);
if ($repetition->repetition_skip > 0) {
return (string)trans('firefly.recurring_weekly_skip', ['weekday' => $dayOfWeek, 'skip' => $repetition->repetition_skip + 1], $language);
return (string) trans('firefly.recurring_weekly_skip', ['weekday' => $dayOfWeek, 'skip' => $repetition->repetition_skip + 1], $language);
}
return (string)trans('firefly.recurring_weekly', ['weekday' => $dayOfWeek], $language);
return (string) trans('firefly.recurring_weekly', ['weekday' => $dayOfWeek], $language);
}
if ('monthly' === $repetition->repetition_type) {
if ($repetition->repetition_skip > 0) {
return (string)trans(
return (string) trans(
'firefly.recurring_monthly_skip',
['dayOfMonth' => $repetition->repetition_moment, 'skip' => $repetition->repetition_skip + 1],
$language
);
}
return (string)trans(
return (string) trans(
'firefly.recurring_monthly',
['dayOfMonth' => $repetition->repetition_moment, 'skip' => $repetition->repetition_skip - 1],
$language
@@ -474,7 +477,7 @@ class RecurringRepository implements RecurringRepositoryInterface
// first part is number of week, second is weekday.
$dayOfWeek = trans(sprintf('config.dow_%s', $parts[1]), [], $language);
return (string)trans('firefly.recurring_ndom', ['weekday' => $dayOfWeek, 'dayOfMonth' => $parts[0]], $language);
return (string) trans('firefly.recurring_ndom', ['weekday' => $dayOfWeek, 'dayOfMonth' => $parts[0]], $language);
}
if ('yearly' === $repetition->repetition_type) {
$today = today(config('app.timezone'))->endOfYear();
@@ -482,11 +485,11 @@ class RecurringRepository implements RecurringRepositoryInterface
if (null === $repDate) {
$repDate = clone $today;
}
$diffInYears = (int)$today->diffInYears($repDate, true);
$diffInYears = (int) $today->diffInYears($repDate, true);
$repDate->addYears($diffInYears); // technically not necessary.
$string = $repDate->isoFormat((string)trans('config.month_and_day_no_year_js'));
$string = $repDate->isoFormat((string) trans('config.month_and_day_no_year_js'));
return (string)trans('firefly.recurring_yearly', ['date' => $string], $language);
return (string) trans('firefly.recurring_yearly', ['date' => $string], $language);
}
return '';
@@ -520,16 +523,16 @@ class RecurringRepository implements RecurringRepositoryInterface
public function totalTransactions(Recurrence $recurrence, RecurrenceRepetition $repetition): int
{
// if repeat = null just return 0.
if (null === $recurrence->repeat_until && 0 === (int)$recurrence->repetitions) {
if (null === $recurrence->repeat_until && 0 === (int) $recurrence->repetitions) {
return 0;
}
// expect X transactions then stop. Return that number
if (null === $recurrence->repeat_until && 0 !== (int)$recurrence->repetitions) {
return (int)$recurrence->repetitions;
if (null === $recurrence->repeat_until && 0 !== (int) $recurrence->repetitions) {
return (int) $recurrence->repetitions;
}
// need to calculate, this depends on the repetition:
if (null !== $recurrence->repeat_until && 0 === (int)$recurrence->repetitions) {
if (null !== $recurrence->repeat_until && 0 === (int) $recurrence->repetitions) {
$occurrences = $this->getOccurrencesInRange($repetition, $recurrence->first_date ?? today(), $recurrence->repeat_until);
return count($occurrences);

View File

@@ -276,6 +276,7 @@ class UserRepository implements UserRepositoryInterface
$invitee->email = $email;
$invitee->redeemed = false;
$invitee->expires = $now;
$invitee->expires_tz = $now->format('e');
$invitee->save();
return $invitee;

View File

@@ -105,10 +105,12 @@ class IsUniqueAccount implements ValidationRule, DataAwareRule
/**
* TODO duplicate from old validation class.
*/
private function validateAccountAnonymously(): void
private function validateAccountAnonymously(): bool
{
if (!array_key_exists('user_id', $this->data)) {
$this->fail('No user ID provided.');
return false;
}
/** @var User $user */

View File

@@ -40,6 +40,7 @@ use FireflyIII\Models\RecurrenceTransaction;
use FireflyIII\Models\RecurrenceTransactionMeta;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Validation\AccountValidator;
use Illuminate\Support\Facades\Log;
/**
* Trait RecurringTransactionTrait
@@ -212,10 +213,14 @@ trait RecurringTransactionTrait
private function setBudget(RecurrenceTransaction $transaction, int $budgetId): void
{
Log::debug(sprintf('Now in %s', __METHOD__));
$budgetFactory = app(BudgetFactory::class);
$budgetFactory->setUser($transaction->recurrence->user);
$budget = $budgetFactory->find($budgetId, null);
if (null === $budget) {
// remove budget from recurring transaction:
$transaction->recurrenceTransactionMeta()->where('name', 'budget_id')->delete();
return;
}
@@ -235,6 +240,9 @@ trait RecurringTransactionTrait
$billFactory->setUser($transaction->recurrence->user);
$bill = $billFactory->find($billId, null);
if (null === $bill) {
// remove bill from recurring transaction:
$transaction->recurrenceTransactionMeta()->where('name', 'bill_id')->delete();
return;
}

View File

@@ -145,7 +145,8 @@ class BillUpdateService
$bill->amount_max = $data['amount_max'];
}
if (array_key_exists('date', $data) && '' !== (string)$data['date']) {
$bill->date = $data['date'];
$bill->date = $data['date'];
$bill->date_tz = $data['date']->format('e');
}
if (array_key_exists('repeat_freq', $data) && '' !== (string)$data['repeat_freq']) {
$bill->repeat_freq = $data['repeat_freq'];
@@ -157,10 +158,12 @@ class BillUpdateService
$bill->active = $data['active'];
}
if (array_key_exists('end_date', $data)) {
$bill->end_date = $data['end_date'];
$bill->end_date = $data['end_date'];
$bill->end_date_tz = $data['end_date']->format('e');
}
if (array_key_exists('extension_date', $data)) {
$bill->extension_date = $data['extension_date'];
$bill->extension_date = $data['extension_date'];
$bill->extension_date_tz = $data['extension_date']->format('e');
}
$bill->match = 'EMPTY';

View File

@@ -490,6 +490,7 @@ class JournalUpdateService
}
// do some parsing.
app('log')->debug(sprintf('Create date value from string "%s".', $value));
$this->transactionJournal->date_tz = $value->format('e');
}
event(
new TriggeredAuditLog(
@@ -610,6 +611,13 @@ class JournalUpdateService
'data' => $value,
];
$factory->updateOrCreate($set);
// also set date with timezone.
$set = [
'journal' => $this->transactionJournal,
'name' => sprintf('%s_tz', $field),
'data' => $value->format('e'),
];
$factory->updateOrCreate($set);
}
}
}

View File

@@ -64,11 +64,13 @@ class RecurrenceUpdateService
$recurrence->description = $info['description'];
}
if (array_key_exists('first_date', $info)) {
$recurrence->first_date = $info['first_date'];
$recurrence->first_date = $info['first_date'];
$recurrence->first_date_tz = $info['first_date']?->format('e');
}
if (array_key_exists('repeat_until', $info)) {
$recurrence->repeat_until = $info['repeat_until'];
$recurrence->repetitions = 0;
$recurrence->repeat_until = $info['repeat_until'];
$recurrence->repeat_until_tz = $info['repeat_until']?->format('e');
$recurrence->repetitions = 0;
}
if (array_key_exists('nr_of_repetitions', $info)) {
if (0 !== (int)$info['nr_of_repetitions']) {

View File

@@ -39,7 +39,6 @@ class EitherConfigKey
// firefly iii settings
'firefly.version',
'firefly.api_version',
'firefly.default_location',
'firefly.account_to_transaction',
'firefly.allowed_opposing_types',

View File

@@ -77,9 +77,7 @@ class RecurringCronjob extends AbstractCronjob
{
app('log')->info(sprintf('Will now fire recurring cron job task for date "%s".', $this->date->format('Y-m-d H:i:s')));
/** @var CreateRecurringTransactions $job */
$job = app(CreateRecurringTransactions::class);
$job->setDate($this->date);
$job = new CreateRecurringTransactions($this->date);
$job->setForce($this->force);
$job->handle();

View File

@@ -23,6 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Support\Http\Api;
use FireflyIII\Enums\AccountTypeEnum;
use FireflyIII\Models\AccountType;
/**
@@ -32,7 +33,7 @@ trait AccountFilter
{
protected array $types = [
'all' => [
AccountType::DEFAULT,
AccountTypeEnum::DEFAULT->value,
AccountType::CASH,
AccountType::ASSET,
AccountType::EXPENSE,

View File

@@ -23,6 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Support\Http\Controllers;
use FireflyIII\Enums\AccountTypeEnum;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\Bill;
@@ -84,9 +85,9 @@ trait ModelInformation
/** @var AccountType $mortgage */
$mortgage = $repository->getAccountTypeByType(AccountType::MORTGAGE);
$liabilityTypes = [
$debt->id => (string)trans(sprintf('firefly.account_type_%s', AccountType::DEBT)),
$loan->id => (string)trans(sprintf('firefly.account_type_%s', AccountType::LOAN)),
$mortgage->id => (string)trans(sprintf('firefly.account_type_%s', AccountType::MORTGAGE)),
$debt->id => (string)trans(sprintf('firefly.account_type_%s', AccountTypeEnum::DEBT->value)),
$loan->id => (string)trans(sprintf('firefly.account_type_%s', AccountTypeEnum::LOAN->value)),
$mortgage->id => (string)trans(sprintf('firefly.account_type_%s', AccountTypeEnum::MORTGAGE->value)),
];
asort($liabilityTypes);

View File

@@ -201,148 +201,6 @@ class AccountBalanceCalculator
return $entry;
}
// private function recalculateLatest(?Account $account): void
// {
// $query = Transaction::groupBy(['transactions.account_id', 'transactions.transaction_currency_id', 'transactions.foreign_currency_id']);
//
// if (null !== $account) {
// $query->where('transactions.account_id', $account->id);
// }
// $result = $query->get(['transactions.account_id', 'transactions.transaction_currency_id', 'transactions.foreign_currency_id', \DB::raw('SUM(transactions.amount) as sum_amount'), \DB::raw('SUM(transactions.foreign_amount) as sum_foreign_amount')]);
//
// // reset account balances:
// $this->resetAccountBalancesByAccount('balance', $account);
//
// /** @var \stdClass $row */
// foreach ($result as $row) {
// $account = (int) $row->account_id;
// $transactionCurrency = (int) $row->transaction_currency_id;
// $foreignCurrency = (int) $row->foreign_currency_id;
// $sumAmount = (string) $row->sum_amount;
// $sumForeignAmount = (string) $row->sum_foreign_amount;
// $sumAmount = '' === $sumAmount ? '0' : $sumAmount;
// $sumForeignAmount = '' === $sumForeignAmount ? '0' : $sumForeignAmount;
//
// // at this point SQLite may return scientific notation because why not. Terrible.
// $sumAmount = app('steam')->floatalize($sumAmount);
// $sumForeignAmount = app('steam')->floatalize($sumForeignAmount);
//
// // first create for normal currency:
// $entry = $this->getAccountBalanceByAccount($account, $transactionCurrency);
//
// try {
// $entry->balance = bcadd((string) $entry->balance, $sumAmount);
// } catch (\ValueError $e) {
// $message = sprintf('[a] Could not add "%s" to "%s": %s', $entry->balance, $sumAmount, $e->getMessage());
// Log::error($message);
//
// throw new FireflyException($message, 0, $e);
// }
// $entry->save();
//
// // then do foreign amount, if present:
// if ($foreignCurrency > 0) {
// $entry = $this->getAccountBalanceByAccount($account, $foreignCurrency);
//
// try {
// $entry->balance = bcadd((string) $entry->balance, $sumForeignAmount);
// } catch (\ValueError $e) {
// $message = sprintf('[b] Could not add "%s" to "%s": %s', $entry->balance, $sumForeignAmount, $e->getMessage());
// Log::error($message);
//
// throw new FireflyException($message, 0, $e);
// }
// $entry->save();
// }
// }
// Log::debug(sprintf('Recalculated %d account balance(s)', $result->count()));
// }
// private function resetAccountBalancesByAccount(string $title, ?Account $account): void
// {
// if (null === $account) {
// $count = AccountBalance::whereNotNull('updated_at')->where('title', $title)->update(['balance' => '0']);
// Log::debug(sprintf('Set %d account balance(s) to zero.', $count));
//
// return;
// }
// $count = AccountBalance::where('account_id', $account->id)->where('title', $title)->update(['balance' => '0']);
// Log::debug(sprintf('Set %d account balance(s) of account #%d to zero.', $count, $account->id));
// }
// /**
// * Als je alles opnieuw doet, verzamel je alle transactions en het bedrag en zet je dat neer als "balance after
// * journal". Dat betekent, netjes op volgorde van datum en doorrekenen.
// *
// * Zodra je een transaction journal verplaatst (datum) moet je dat journal en alle latere journals opnieuw doen.
// * Maar dan moet je van de account wel een beginnetje hebben, namelijk de balance tot en met dat moment.
// *
// * 1. Dus dan search je eerst naar die SUM, som alle transactions eerder dan (niet inclusief) de journal.
// * 2. En vanaf daar pak je alle journals op of na de journal (dus ook de journal zelf) en begin je door te tellen.
// * 3. Elke voorbij gaande journal entry "balance_after_journal" geef je een update of voeg je toe.
// */
// private function recalculateJournals(?Account $account, ?TransactionJournal $transactionJournal): void
// {
// $query = Transaction::groupBy(['transactions.account_id', 'transaction_journals.id', 'transactions.transaction_currency_id', 'transactions.foreign_currency_id']);
// $query->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id');
// $query->orderBy('transaction_journals.date', 'asc');
// $amounts = [];
// if (null !== $account) {
// $query->where('transactions.account_id', $account->id);
// }
// if (null !== $account && null !== $transactionJournal) {
// $query->where('transaction_journals.date', '>=', $transactionJournal->date);
// $amounts = $this->getStartAmounts($account, $transactionJournal);
// }
// $result = $query->get(['transactions.account_id', 'transaction_journals.id', 'transactions.transaction_currency_id', 'transactions.foreign_currency_id', \DB::raw('SUM(transactions.amount) as sum_amount'), \DB::raw('SUM(transactions.foreign_amount) as sum_foreign_amount')]);
//
// /** @var \stdClass $row */
// foreach ($result as $row) {
// $account = (int) $row->account_id;
// $transactionCurrency = (int) $row->transaction_currency_id;
// $foreignCurrency = (int) $row->foreign_currency_id;
// $sumAmount = (string) $row->sum_amount;
// $sumForeignAmount = (string) $row->sum_foreign_amount;
// $journalId = (int) $row->id;
//
// // check for empty strings
// $sumAmount = '' === $sumAmount ? '0' : $sumAmount;
// $sumForeignAmount = '' === $sumForeignAmount ? '0' : $sumForeignAmount;
//
// // new amounts:
// $amounts[$account][$transactionCurrency] = bcadd($amounts[$account][$transactionCurrency] ?? '0', $sumAmount);
// $amounts[$account][$foreignCurrency] = bcadd($amounts[$account][$foreignCurrency] ?? '0', $sumForeignAmount);
//
// // first create for normal currency:
// $entry = self::getAccountBalanceByJournal('balance_after_journal', $account, $journalId, $transactionCurrency);
// $entry->balance = $amounts[$account][$transactionCurrency];
// $entry->save();
//
// // then do foreign amount, if present:
// if ($foreignCurrency > 0) {
// $entry = self::getAccountBalanceByJournal('balance_after_journal', $account, $journalId, $foreignCurrency);
// $entry->balance = $amounts[$account][$foreignCurrency];
// $entry->save();
// }
// }
//
// // select transactions.account_id, transaction_journals.id, transactions.transaction_currency_id, transactions.foreign_currency_id, sum(transactions.amount), sum(transactions.foreign_amount)
// //
// // from transactions
// //
// // left join transaction_journals ON transaction_journals.id = transactions.transaction_journal_id
// //
// // group by account_id, transaction_journals.id, transaction_currency_id, foreign_currency_id
// // order by transaction_journals.date desc
// }
// private function getStartAmounts(Account $account, TransactionJournal $journal): array
// {
// exit('here we are 1');
//
// return [];
// }
private function storeAccountBalances(array $balances): void
{
/**
@@ -372,9 +230,18 @@ class AccountBalanceCalculator
}
/** @var AccountBalance $object */
$object = $account->accountBalances()->firstOrCreate(['title' => 'running_balance', 'balance' => '0', 'transaction_currency_id' => $currencyId, 'date' => $balance[1]]);
$object = $account->accountBalances()->firstOrCreate(
[
'title' => 'running_balance',
'balance' => '0',
'transaction_currency_id' => $currencyId,
'date' => $balance[1],
'date_tz' => $balance[1]->format('e'),
]
);
$object->balance = $balance[0];
$object->date = $balance[1];
$object->date_tz = $balance[1]->format('e');
$object->save();
}
}

View File

@@ -81,7 +81,7 @@ class UpdatePiggybank implements ActionInterface
if ($source->account_id === $piggyBank->account_id) {
app('log')->debug('Piggy bank account is linked to source, so remove amount from piggy bank.');
$this->removeAmount($piggyBank, $journalObj, $destination->amount);
$this->removeAmount($piggyBank, $journal, $journalObj, $destination->amount);
event(
new TriggeredAuditLog(
@@ -102,7 +102,7 @@ class UpdatePiggybank implements ActionInterface
}
if ($destination->account_id === $piggyBank->account_id) {
app('log')->debug('Piggy bank account is linked to source, so add amount to piggy bank.');
$this->addAmount($piggyBank, $journalObj, $destination->amount);
$this->addAmount($piggyBank, $journal, $journalObj, $destination->amount);
event(
new TriggeredAuditLog(
@@ -111,10 +111,11 @@ class UpdatePiggybank implements ActionInterface
'add_to_piggy',
null,
[
'currency_symbol' => $journalObj->transactionCurrency->symbol,
'decimal_places' => $journalObj->transactionCurrency->decimal_places,
'amount' => $destination->amount,
'piggy' => $piggyBank->name,
'currency_symbol' => $journalObj->transactionCurrency->symbol,
'decimal_places' => $journalObj->transactionCurrency->decimal_places,
'amount' => $destination->amount,
'piggy' => $piggyBank->name,
'piggy_id' => $piggyBank->id,
]
)
);
@@ -138,7 +139,7 @@ class UpdatePiggybank implements ActionInterface
return $user->piggyBanks()->where('piggy_banks.name', $name)->first();
}
private function removeAmount(PiggyBank $piggyBank, TransactionJournal $journal, string $amount): void
private function removeAmount(PiggyBank $piggyBank, array $array, TransactionJournal $journal, string $amount): void
{
$repository = app(PiggyBankRepositoryInterface::class);
$repository->setUser($journal->user);
@@ -154,6 +155,7 @@ class UpdatePiggybank implements ActionInterface
// if amount is zero, stop.
if (0 === bccomp('0', $amount)) {
app('log')->warning('Amount left is zero, stop.');
event(new RuleActionFailedOnArray($this->action, $array, trans('rules.cannot_remove_zero_piggy', ['name' => $piggyBank->name])));
return;
}
@@ -161,6 +163,7 @@ class UpdatePiggybank implements ActionInterface
// make sure we can remove amount:
if (false === $repository->canRemoveAmount($piggyBank, $amount)) {
app('log')->warning(sprintf('Cannot remove %s from piggy bank.', $amount));
event(new RuleActionFailedOnArray($this->action, $array, trans('rules.cannot_remove_from_piggy', ['amount' => $amount, 'name' => $piggyBank->name])));
return;
}
@@ -169,7 +172,7 @@ class UpdatePiggybank implements ActionInterface
$repository->removeAmount($piggyBank, $amount, $journal);
}
private function addAmount(PiggyBank $piggyBank, TransactionJournal $journal, string $amount): void
private function addAmount(PiggyBank $piggyBank, array $array, TransactionJournal $journal, string $amount): void
{
$repository = app(PiggyBankRepositoryInterface::class);
$repository->setUser($journal->user);
@@ -190,6 +193,7 @@ class UpdatePiggybank implements ActionInterface
// if amount is zero, stop.
if (0 === bccomp('0', $amount)) {
app('log')->warning('Amount left is zero, stop.');
event(new RuleActionFailedOnArray($this->action, $array, trans('rules.cannot_add_zero_piggy', ['name' => $piggyBank->name])));
return;
}
@@ -197,6 +201,7 @@ class UpdatePiggybank implements ActionInterface
// make sure we can add amount:
if (false === $repository->canAddAmount($piggyBank, $amount)) {
app('log')->warning(sprintf('Cannot add %s to piggy bank.', $amount));
event(new RuleActionFailedOnArray($this->action, $array, trans('rules.cannot_add_to_piggy', ['amount' => $amount, 'name' => $piggyBank->name])));
return;
}

View File

@@ -194,11 +194,19 @@ class BillTransformer extends AbstractTransformer
$searchStart = clone $start;
$start->subDay();
app('log')->debug(sprintf('Parameters are start: %s end: %s', $start->format('Y-m-d'), $this->parameters->get('end')->format('Y-m-d')));
/** @var Carbon $end */
$end = clone $this->parameters->get('end');
$searchEnd = clone $end;
// move the search dates to the start of the day.
$searchStart->startOfDay();
$searchEnd->endOfDay();
app('log')->debug(sprintf('Parameters are start: %s end: %s', $start->format('Y-m-d'), $end->format('Y-m-d')));
app('log')->debug(sprintf('Search parameters are: start: %s', $searchStart->format('Y-m-d')));
// Get from database when bill was paid.
$set = $this->repository->getPaidDatesInRange($bill, $searchStart, $this->parameters->get('end'));
$set = $this->repository->getPaidDatesInRange($bill, $searchStart, $searchEnd);
app('log')->debug(sprintf('Count %d entries in getPaidDatesInRange()', $set->count()));
// Grab from array the most recent payment. If none exist, fall back to the start date and pretend *that* was the last paid date.

View File

@@ -3,6 +3,37 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
## 6.1.22 - 2024-11-07
### Added
- [Discussion 8092](https://github.com/orgs/firefly-iii/discussions/8092) (Fresh Install - Register -> 403 Error - Forbidden) started by @pheonix-devapps
- [Issue 9183](https://github.com/firefly-iii/firefly-iii/issues/9183) (2FA security improvements) reported by @JC5
### Fixed
- [Issue 9282](https://github.com/firefly-iii/firefly-iii/issues/9282) (Default report - no transactions for no budget) reported by @rymrg
- [Issue 9147](https://github.com/firefly-iii/firefly-iii/issues/9147) (Store/Update Bill API end_date and extension_date cant be null in request) reported by @jkano
- [Issue 9175](https://github.com/firefly-iii/firefly-iii/issues/9175) ("Attempt to read property "type" on null" when mass editing transactions) reported by @Still34
- [Issue 9225](https://github.com/firefly-iii/firefly-iii/issues/9225) (Liability amount due calculated incorrectly on liabilities list when the liability is settled with a transfer to another liability) reported by @uumas
- [Issue 9236](https://github.com/firefly-iii/firefly-iii/issues/9236) (Autocomplete not working for rrules having the bill as a trigger) reported by @pvieira84
- [Issue 9305](https://github.com/firefly-iii/firefly-iii/issues/9305) (Recurring transactions get group title on overview page) reported by @zeitwidrig
- [Issue 9106](https://github.com/firefly-iii/firefly-iii/issues/9106) (Inactive accounts are inaccessible when no active accounts of that type exist) reported by @codemicro
- [Discussion 9324](https://github.com/orgs/firefly-iii/discussions/9324) (Consistent behavior accross DB Engines) started by @stackcoder
- [Issue 9360](https://github.com/firefly-iii/firefly-iii/issues/9360) (Date incorrectly shown) reported by @enboig
- [Issue 9303](https://github.com/firefly-iii/firefly-iii/issues/9303) (Rules > Rule > Action) reported by @EricVanCaenenberghe
- [Issue 9416](https://github.com/firefly-iii/firefly-iii/issues/9416) (Linking Transaction to Bill doesn't mark as paid for 31st) reported by @harrhunt
- [Issue 9389](https://github.com/firefly-iii/firefly-iii/issues/9389) (Budget and Bill field on Recurring transactions not updating) reported by @HHUBSS
- [Discussion 9234](https://github.com/orgs/firefly-iii/discussions/9234) (Unsupported cipher or incorrect key length ( first run )) started by @spectroman
- [Issue 9294](https://github.com/firefly-iii/firefly-iii/issues/9294) (Repetition counts ignored for recurring transactions) reported by @Syncena
- [Issue 9427](https://github.com/firefly-iii/firefly-iii/issues/9427) (The standard financial report does not show all transactions for the income categories) reported by @Neroxeles
### API
- API version is no longer distinguished from Firefly III version. API jumps from v2.1.0 to v6.1.22
- API v2 is cleaned up and misses a few previously available endpoints. They will be added in the future.
- [Discussion 9271](https://github.com/orgs/firefly-iii/discussions/9271) (/v2/chart/balance/balance ignoring the `period` parameter) started by @victorbalssa
## 6.1.21 - 2024-09-30
### Added

242
composer.lock generated
View File

@@ -62,16 +62,16 @@
},
{
"name": "beberlei/assert",
"version": "v3.3.2",
"version": "v3.3.3",
"source": {
"type": "git",
"url": "https://github.com/beberlei/assert.git",
"reference": "cb70015c04be1baee6f5f5c953703347c0ac1655"
"reference": "b5fd8eacd8915a1b627b8bfc027803f1939734dd"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/beberlei/assert/zipball/cb70015c04be1baee6f5f5c953703347c0ac1655",
"reference": "cb70015c04be1baee6f5f5c953703347c0ac1655",
"url": "https://api.github.com/repos/beberlei/assert/zipball/b5fd8eacd8915a1b627b8bfc027803f1939734dd",
"reference": "b5fd8eacd8915a1b627b8bfc027803f1939734dd",
"shasum": ""
},
"require": {
@@ -79,7 +79,7 @@
"ext-json": "*",
"ext-mbstring": "*",
"ext-simplexml": "*",
"php": "^7.0 || ^8.0"
"php": "^7.1 || ^8.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "*",
@@ -123,9 +123,9 @@
],
"support": {
"issues": "https://github.com/beberlei/assert/issues",
"source": "https://github.com/beberlei/assert/tree/v3.3.2"
"source": "https://github.com/beberlei/assert/tree/v3.3.3"
},
"time": "2021-12-16T21:41:27+00:00"
"time": "2024-07-15T13:18:35+00:00"
},
{
"name": "brick/math",
@@ -2000,16 +2000,16 @@
},
{
"name": "laravel-json-api/eloquent",
"version": "v4.3.0",
"version": "v4.3.1",
"source": {
"type": "git",
"url": "https://github.com/laravel-json-api/eloquent.git",
"reference": "05644f31eb086e4d640f15eac7d03752ffcde99f"
"reference": "98899c6d3ebaf801fb35ff4d1b7b9447fcede23b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel-json-api/eloquent/zipball/05644f31eb086e4d640f15eac7d03752ffcde99f",
"reference": "05644f31eb086e4d640f15eac7d03752ffcde99f",
"url": "https://api.github.com/repos/laravel-json-api/eloquent/zipball/98899c6d3ebaf801fb35ff4d1b7b9447fcede23b",
"reference": "98899c6d3ebaf801fb35ff4d1b7b9447fcede23b",
"shasum": ""
},
"require": {
@@ -2058,9 +2058,9 @@
],
"support": {
"issues": "https://github.com/laravel-json-api/eloquent/issues",
"source": "https://github.com/laravel-json-api/eloquent/tree/v4.3.0"
"source": "https://github.com/laravel-json-api/eloquent/tree/v4.3.1"
},
"time": "2024-10-13T12:53:07+00:00"
"time": "2024-10-31T18:03:50+00:00"
},
{
"name": "laravel-json-api/encoder-neomerx",
@@ -2547,16 +2547,16 @@
},
{
"name": "laravel/framework",
"version": "v11.29.0",
"version": "v11.30.0",
"source": {
"type": "git",
"url": "https://github.com/laravel/framework.git",
"reference": "425054512c362835ba9c0307561973c8eeac7385"
"reference": "dff716442d9c229d716be82ccc9a7de52eb97193"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/framework/zipball/425054512c362835ba9c0307561973c8eeac7385",
"reference": "425054512c362835ba9c0307561973c8eeac7385",
"url": "https://api.github.com/repos/laravel/framework/zipball/dff716442d9c229d716be82ccc9a7de52eb97193",
"reference": "dff716442d9c229d716be82ccc9a7de52eb97193",
"shasum": ""
},
"require": {
@@ -2752,7 +2752,7 @@
"issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework"
},
"time": "2024-10-22T14:13:31+00:00"
"time": "2024-10-30T15:00:34+00:00"
},
{
"name": "laravel/passport",
@@ -3016,16 +3016,16 @@
},
{
"name": "laravel/slack-notification-channel",
"version": "v3.3.2",
"version": "v3.4.0",
"source": {
"type": "git",
"url": "https://github.com/laravel/slack-notification-channel.git",
"reference": "5e04e4a0834b84650a207a76b7a67de8156d0b04"
"reference": "8ffbb9f0578956cc192bffc8d75f5b07beb35aa3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/slack-notification-channel/zipball/5e04e4a0834b84650a207a76b7a67de8156d0b04",
"reference": "5e04e4a0834b84650a207a76b7a67de8156d0b04",
"url": "https://api.github.com/repos/laravel/slack-notification-channel/zipball/8ffbb9f0578956cc192bffc8d75f5b07beb35aa3",
"reference": "8ffbb9f0578956cc192bffc8d75f5b07beb35aa3",
"shasum": ""
},
"require": {
@@ -3075,9 +3075,9 @@
],
"support": {
"issues": "https://github.com/laravel/slack-notification-channel/issues",
"source": "https://github.com/laravel/slack-notification-channel/tree/v3.3.2"
"source": "https://github.com/laravel/slack-notification-channel/tree/v3.4.0"
},
"time": "2024-09-04T06:56:09+00:00"
"time": "2024-10-24T15:06:08+00:00"
},
{
"name": "laravel/ui",
@@ -3208,16 +3208,16 @@
},
{
"name": "lcobucci/jwt",
"version": "5.4.0",
"version": "5.4.1",
"source": {
"type": "git",
"url": "https://github.com/lcobucci/jwt.git",
"reference": "aac4fd512681fd5cb4b77d2105ab7ec700c72051"
"reference": "848815d2287abd5d3c285482f8e1f501b289a2e7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/lcobucci/jwt/zipball/aac4fd512681fd5cb4b77d2105ab7ec700c72051",
"reference": "aac4fd512681fd5cb4b77d2105ab7ec700c72051",
"url": "https://api.github.com/repos/lcobucci/jwt/zipball/848815d2287abd5d3c285482f8e1f501b289a2e7",
"reference": "848815d2287abd5d3c285482f8e1f501b289a2e7",
"shasum": ""
},
"require": {
@@ -3265,7 +3265,7 @@
],
"support": {
"issues": "https://github.com/lcobucci/jwt/issues",
"source": "https://github.com/lcobucci/jwt/tree/5.4.0"
"source": "https://github.com/lcobucci/jwt/tree/5.4.1"
},
"funding": [
{
@@ -3277,7 +3277,7 @@
"type": "patreon"
}
],
"time": "2024-10-08T22:06:45+00:00"
"time": "2024-11-06T06:16:04+00:00"
},
{
"name": "league/commonmark",
@@ -4367,20 +4367,20 @@
},
{
"name": "nesbot/carbon",
"version": "3.8.0",
"version": "3.8.1",
"source": {
"type": "git",
"url": "https://github.com/briannesbitt/Carbon.git",
"reference": "bbd3eef89af8ba66a3aa7952b5439168fbcc529f"
"reference": "10ac0aa86b8062219ce21e8189123d611ca3ecd9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/bbd3eef89af8ba66a3aa7952b5439168fbcc529f",
"reference": "bbd3eef89af8ba66a3aa7952b5439168fbcc529f",
"url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/10ac0aa86b8062219ce21e8189123d611ca3ecd9",
"reference": "10ac0aa86b8062219ce21e8189123d611ca3ecd9",
"shasum": ""
},
"require": {
"carbonphp/carbon-doctrine-types": "*",
"carbonphp/carbon-doctrine-types": "<100.0",
"ext-json": "*",
"php": "^8.1",
"psr/clock": "^1.0",
@@ -4469,7 +4469,7 @@
"type": "tidelift"
}
],
"time": "2024-08-19T06:22:39+00:00"
"time": "2024-11-03T16:02:24+00:00"
},
{
"name": "nette/schema",
@@ -7085,16 +7085,16 @@
},
{
"name": "symfony/cache",
"version": "v7.1.6",
"version": "v7.1.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/cache.git",
"reference": "567ef6de47fdcba56eb6c0b344b857d1fce1cce0"
"reference": "23b61c9592ee72233c31625f0ae805dd1571e928"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/cache/zipball/567ef6de47fdcba56eb6c0b344b857d1fce1cce0",
"reference": "567ef6de47fdcba56eb6c0b344b857d1fce1cce0",
"url": "https://api.github.com/repos/symfony/cache/zipball/23b61c9592ee72233c31625f0ae805dd1571e928",
"reference": "23b61c9592ee72233c31625f0ae805dd1571e928",
"shasum": ""
},
"require": {
@@ -7162,7 +7162,7 @@
"psr6"
],
"support": {
"source": "https://github.com/symfony/cache/tree/v7.1.6"
"source": "https://github.com/symfony/cache/tree/v7.1.7"
},
"funding": [
{
@@ -7178,7 +7178,7 @@
"type": "tidelift"
}
],
"time": "2024-10-25T15:39:55+00:00"
"time": "2024-11-05T15:34:55+00:00"
},
{
"name": "symfony/cache-contracts",
@@ -7332,16 +7332,16 @@
},
{
"name": "symfony/console",
"version": "v7.1.6",
"version": "v7.1.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "bb5192af6edc797cbab5c8e8ecfea2fe5f421e57"
"reference": "3284aafcac338b6e86fd955ee4d794cbe434151a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/bb5192af6edc797cbab5c8e8ecfea2fe5f421e57",
"reference": "bb5192af6edc797cbab5c8e8ecfea2fe5f421e57",
"url": "https://api.github.com/repos/symfony/console/zipball/3284aafcac338b6e86fd955ee4d794cbe434151a",
"reference": "3284aafcac338b6e86fd955ee4d794cbe434151a",
"shasum": ""
},
"require": {
@@ -7405,7 +7405,7 @@
"terminal"
],
"support": {
"source": "https://github.com/symfony/console/tree/v7.1.6"
"source": "https://github.com/symfony/console/tree/v7.1.7"
},
"funding": [
{
@@ -7421,7 +7421,7 @@
"type": "tidelift"
}
],
"time": "2024-10-09T08:46:59+00:00"
"time": "2024-11-05T15:34:55+00:00"
},
{
"name": "symfony/css-selector",
@@ -7557,16 +7557,16 @@
},
{
"name": "symfony/error-handler",
"version": "v7.1.6",
"version": "v7.1.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/error-handler.git",
"reference": "d60117093c2a9fe667baa8fedf84e8a09b9c592f"
"reference": "010e44661f4c6babaf8c4862fe68c24a53903342"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/error-handler/zipball/d60117093c2a9fe667baa8fedf84e8a09b9c592f",
"reference": "d60117093c2a9fe667baa8fedf84e8a09b9c592f",
"url": "https://api.github.com/repos/symfony/error-handler/zipball/010e44661f4c6babaf8c4862fe68c24a53903342",
"reference": "010e44661f4c6babaf8c4862fe68c24a53903342",
"shasum": ""
},
"require": {
@@ -7612,7 +7612,7 @@
"description": "Provides tools to manage errors and ease debugging PHP code",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/error-handler/tree/v7.1.6"
"source": "https://github.com/symfony/error-handler/tree/v7.1.7"
},
"funding": [
{
@@ -7628,7 +7628,7 @@
"type": "tidelift"
}
],
"time": "2024-09-25T14:20:29+00:00"
"time": "2024-11-05T15:34:55+00:00"
},
{
"name": "symfony/event-dispatcher",
@@ -7916,16 +7916,16 @@
},
{
"name": "symfony/http-client",
"version": "v7.1.6",
"version": "v7.1.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-client.git",
"reference": "274e2f6886b43a36f8bd5dfeb67215f7ebf9e291"
"reference": "90ab2a4992dcf5d1f19a9b8737eba36a7c305fd0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/http-client/zipball/274e2f6886b43a36f8bd5dfeb67215f7ebf9e291",
"reference": "274e2f6886b43a36f8bd5dfeb67215f7ebf9e291",
"url": "https://api.github.com/repos/symfony/http-client/zipball/90ab2a4992dcf5d1f19a9b8737eba36a7c305fd0",
"reference": "90ab2a4992dcf5d1f19a9b8737eba36a7c305fd0",
"shasum": ""
},
"require": {
@@ -7990,7 +7990,7 @@
"http"
],
"support": {
"source": "https://github.com/symfony/http-client/tree/v7.1.6"
"source": "https://github.com/symfony/http-client/tree/v7.1.7"
},
"funding": [
{
@@ -8006,7 +8006,7 @@
"type": "tidelift"
}
],
"time": "2024-10-22T09:40:50+00:00"
"time": "2024-11-05T16:45:54+00:00"
},
{
"name": "symfony/http-client-contracts",
@@ -8088,16 +8088,16 @@
},
{
"name": "symfony/http-foundation",
"version": "v7.1.6",
"version": "v7.1.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-foundation.git",
"reference": "3d7bbf071b25f802f7d55524d408bed414ea71e2"
"reference": "5183b61657807099d98f3367bcccb850238b17a9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/3d7bbf071b25f802f7d55524d408bed414ea71e2",
"reference": "3d7bbf071b25f802f7d55524d408bed414ea71e2",
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/5183b61657807099d98f3367bcccb850238b17a9",
"reference": "5183b61657807099d98f3367bcccb850238b17a9",
"shasum": ""
},
"require": {
@@ -8145,7 +8145,7 @@
"description": "Defines an object-oriented layer for the HTTP specification",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/http-foundation/tree/v7.1.6"
"source": "https://github.com/symfony/http-foundation/tree/v7.1.7"
},
"funding": [
{
@@ -8161,20 +8161,20 @@
"type": "tidelift"
}
],
"time": "2024-10-11T19:23:14+00:00"
"time": "2024-11-06T09:02:46+00:00"
},
{
"name": "symfony/http-kernel",
"version": "v7.1.6",
"version": "v7.1.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-kernel.git",
"reference": "5d8315899cd76b2e7e29179bf5fea103e41bdf03"
"reference": "7f137cda31fd41e422edcdc01915f2c095b84399"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/http-kernel/zipball/5d8315899cd76b2e7e29179bf5fea103e41bdf03",
"reference": "5d8315899cd76b2e7e29179bf5fea103e41bdf03",
"url": "https://api.github.com/repos/symfony/http-kernel/zipball/7f137cda31fd41e422edcdc01915f2c095b84399",
"reference": "7f137cda31fd41e422edcdc01915f2c095b84399",
"shasum": ""
},
"require": {
@@ -8259,7 +8259,7 @@
"description": "Provides a structured process for converting a Request into a Response",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/http-kernel/tree/v7.1.6"
"source": "https://github.com/symfony/http-kernel/tree/v7.1.7"
},
"funding": [
{
@@ -8275,7 +8275,7 @@
"type": "tidelift"
}
],
"time": "2024-10-27T13:54:21+00:00"
"time": "2024-11-06T09:54:34+00:00"
},
{
"name": "symfony/mailer",
@@ -9291,16 +9291,16 @@
},
{
"name": "symfony/process",
"version": "v7.1.6",
"version": "v7.1.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
"reference": "6aaa189ddb4ff6b5de8fa3210f2fb42c87b4d12e"
"reference": "9b8a40b7289767aa7117e957573c2a535efe6585"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/6aaa189ddb4ff6b5de8fa3210f2fb42c87b4d12e",
"reference": "6aaa189ddb4ff6b5de8fa3210f2fb42c87b4d12e",
"url": "https://api.github.com/repos/symfony/process/zipball/9b8a40b7289767aa7117e957573c2a535efe6585",
"reference": "9b8a40b7289767aa7117e957573c2a535efe6585",
"shasum": ""
},
"require": {
@@ -9332,7 +9332,7 @@
"description": "Executes commands in sub-processes",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/process/tree/v7.1.6"
"source": "https://github.com/symfony/process/tree/v7.1.7"
},
"funding": [
{
@@ -9348,7 +9348,7 @@
"type": "tidelift"
}
],
"time": "2024-09-25T14:20:29+00:00"
"time": "2024-11-06T09:25:12+00:00"
},
{
"name": "symfony/psr-http-message-bridge",
@@ -9932,16 +9932,16 @@
},
{
"name": "symfony/var-dumper",
"version": "v7.1.6",
"version": "v7.1.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/var-dumper.git",
"reference": "cb5bd55a6b8c2c1c7fb68b0aeae0e257948a720c"
"reference": "f6ea51f669760cacd7464bf7eaa0be87b8072db1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/var-dumper/zipball/cb5bd55a6b8c2c1c7fb68b0aeae0e257948a720c",
"reference": "cb5bd55a6b8c2c1c7fb68b0aeae0e257948a720c",
"url": "https://api.github.com/repos/symfony/var-dumper/zipball/f6ea51f669760cacd7464bf7eaa0be87b8072db1",
"reference": "f6ea51f669760cacd7464bf7eaa0be87b8072db1",
"shasum": ""
},
"require": {
@@ -9995,7 +9995,7 @@
"dump"
],
"support": {
"source": "https://github.com/symfony/var-dumper/tree/v7.1.6"
"source": "https://github.com/symfony/var-dumper/tree/v7.1.7"
},
"funding": [
{
@@ -10011,7 +10011,7 @@
"type": "tidelift"
}
],
"time": "2024-09-25T14:20:29+00:00"
"time": "2024-11-05T15:34:55+00:00"
},
{
"name": "symfony/var-exporter",
@@ -10144,16 +10144,16 @@
},
{
"name": "twig/twig",
"version": "v3.14.0",
"version": "v3.14.1",
"source": {
"type": "git",
"url": "https://github.com/twigphp/Twig.git",
"reference": "126b2c97818dbff0cdf3fbfc881aedb3d40aae72"
"reference": "f405356d20fb43603bcadc8b09bfb676cb04a379"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/126b2c97818dbff0cdf3fbfc881aedb3d40aae72",
"reference": "126b2c97818dbff0cdf3fbfc881aedb3d40aae72",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/f405356d20fb43603bcadc8b09bfb676cb04a379",
"reference": "f405356d20fb43603bcadc8b09bfb676cb04a379",
"shasum": ""
},
"require": {
@@ -10207,7 +10207,7 @@
],
"support": {
"issues": "https://github.com/twigphp/Twig/issues",
"source": "https://github.com/twigphp/Twig/tree/v3.14.0"
"source": "https://github.com/twigphp/Twig/tree/v3.14.1"
},
"funding": [
{
@@ -10219,7 +10219,7 @@
"type": "tidelift"
}
],
"time": "2024-09-09T17:55:12+00:00"
"time": "2024-11-06T18:17:38+00:00"
},
{
"name": "vlucas/phpdotenv",
@@ -10525,16 +10525,16 @@
},
{
"name": "barryvdh/laravel-ide-helper",
"version": "v3.2.0",
"version": "v3.2.2",
"source": {
"type": "git",
"url": "https://github.com/barryvdh/laravel-ide-helper.git",
"reference": "7956ccb4943f8532d008c17a1094b34bb6394d56"
"reference": "07e3bd8796f3d1414801a03d3783f9d3ec9efc08"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/7956ccb4943f8532d008c17a1094b34bb6394d56",
"reference": "7956ccb4943f8532d008c17a1094b34bb6394d56",
"url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/07e3bd8796f3d1414801a03d3783f9d3ec9efc08",
"reference": "07e3bd8796f3d1414801a03d3783f9d3ec9efc08",
"shasum": ""
},
"require": {
@@ -10603,7 +10603,7 @@
],
"support": {
"issues": "https://github.com/barryvdh/laravel-ide-helper/issues",
"source": "https://github.com/barryvdh/laravel-ide-helper/tree/v3.2.0"
"source": "https://github.com/barryvdh/laravel-ide-helper/tree/v3.2.2"
},
"funding": [
{
@@ -10615,7 +10615,7 @@
"type": "github"
}
],
"time": "2024-10-17T16:43:13+00:00"
"time": "2024-10-29T14:00:16+00:00"
},
{
"name": "barryvdh/reflection-docblock",
@@ -11114,16 +11114,16 @@
},
{
"name": "larastan/larastan",
"version": "v2.9.9",
"version": "v2.9.10",
"source": {
"type": "git",
"url": "https://github.com/larastan/larastan.git",
"reference": "148faa138f0d8acb7d85f4a55693d3e13b6048d2"
"reference": "9e7233d88f4f10796fadb8a2d85c5f2b55277c76"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/larastan/larastan/zipball/148faa138f0d8acb7d85f4a55693d3e13b6048d2",
"reference": "148faa138f0d8acb7d85f4a55693d3e13b6048d2",
"url": "https://api.github.com/repos/larastan/larastan/zipball/9e7233d88f4f10796fadb8a2d85c5f2b55277c76",
"reference": "9e7233d88f4f10796fadb8a2d85c5f2b55277c76",
"shasum": ""
},
"require": {
@@ -11193,7 +11193,7 @@
],
"support": {
"issues": "https://github.com/larastan/larastan/issues",
"source": "https://github.com/larastan/larastan/tree/v2.9.9"
"source": "https://github.com/larastan/larastan/tree/v2.9.10"
},
"funding": [
{
@@ -11213,7 +11213,7 @@
"type": "patreon"
}
],
"time": "2024-10-15T19:41:22+00:00"
"time": "2024-10-19T23:04:40+00:00"
},
{
"name": "laravel-json-api/testing",
@@ -11282,16 +11282,16 @@
},
{
"name": "maximebf/debugbar",
"version": "v1.23.2",
"version": "v1.23.3",
"source": {
"type": "git",
"url": "https://github.com/maximebf/php-debugbar.git",
"reference": "689720d724c771ac4add859056744b7b3f2406da"
"reference": "687400043d77943ef95e8417cb44e1673ee57844"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/689720d724c771ac4add859056744b7b3f2406da",
"reference": "689720d724c771ac4add859056744b7b3f2406da",
"url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/687400043d77943ef95e8417cb44e1673ee57844",
"reference": "687400043d77943ef95e8417cb44e1673ee57844",
"shasum": ""
},
"require": {
@@ -11344,9 +11344,9 @@
],
"support": {
"issues": "https://github.com/maximebf/php-debugbar/issues",
"source": "https://github.com/maximebf/php-debugbar/tree/v1.23.2"
"source": "https://github.com/maximebf/php-debugbar/tree/v1.23.3"
},
"time": "2024-09-16T11:23:09+00:00"
"time": "2024-10-29T12:24:25+00:00"
},
{
"name": "mockery/mockery",
@@ -11722,23 +11722,23 @@
},
{
"name": "phpdocumentor/type-resolver",
"version": "1.8.2",
"version": "1.9.0",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/TypeResolver.git",
"reference": "153ae662783729388a584b4361f2545e4d841e3c"
"reference": "1fb5ba8d045f5dd984ebded5b1cc66f29459422d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/153ae662783729388a584b4361f2545e4d841e3c",
"reference": "153ae662783729388a584b4361f2545e4d841e3c",
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/1fb5ba8d045f5dd984ebded5b1cc66f29459422d",
"reference": "1fb5ba8d045f5dd984ebded5b1cc66f29459422d",
"shasum": ""
},
"require": {
"doctrine/deprecations": "^1.0",
"php": "^7.3 || ^8.0",
"phpdocumentor/reflection-common": "^2.0",
"phpstan/phpdoc-parser": "^1.13"
"phpstan/phpdoc-parser": "^1.18"
},
"require-dev": {
"ext-tokenizer": "*",
@@ -11774,9 +11774,9 @@
"description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
"support": {
"issues": "https://github.com/phpDocumentor/TypeResolver/issues",
"source": "https://github.com/phpDocumentor/TypeResolver/tree/1.8.2"
"source": "https://github.com/phpDocumentor/TypeResolver/tree/1.9.0"
},
"time": "2024-02-23T11:10:43+00:00"
"time": "2024-11-03T20:11:34+00:00"
},
{
"name": "phpmyadmin/sql-parser",
@@ -12437,16 +12437,16 @@
},
{
"name": "phpunit/phpunit",
"version": "10.5.37",
"version": "10.5.38",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "c7cffa0efa2b70c22366523e6d804c9419eb2400"
"reference": "a86773b9e887a67bc53efa9da9ad6e3f2498c132"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c7cffa0efa2b70c22366523e6d804c9419eb2400",
"reference": "c7cffa0efa2b70c22366523e6d804c9419eb2400",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a86773b9e887a67bc53efa9da9ad6e3f2498c132",
"reference": "a86773b9e887a67bc53efa9da9ad6e3f2498c132",
"shasum": ""
},
"require": {
@@ -12518,7 +12518,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
"source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.37"
"source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.38"
},
"funding": [
{
@@ -12534,7 +12534,7 @@
"type": "tidelift"
}
],
"time": "2024-10-19T13:03:41+00:00"
"time": "2024-10-28T13:06:21+00:00"
},
{
"name": "sebastian/cli-parser",

View File

@@ -110,9 +110,9 @@ return [
'running_balance_column' => env('USE_RUNNING_BALANCE', false),
// see cer.php for exchange rates feature flag.
],
'version' => 'develop/2024-10-28',
'api_version' => '2.1.0',
'db_version' => 24,
'version' => 'develop/2024-11-06',
'api_version' => '2.1.0', // field is no longer used.
'db_version' => 25,
// generic settings
'maxUploadSize' => 1073741824, // 1 GB

View File

@@ -0,0 +1,60 @@
<?php
declare(strict_types=1);
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\QueryException;
use Illuminate\Database\Schema\Blueprint;
return new class () extends Migration {
private array $tables;
public function __construct()
{
$this->tables = [
'account_balances' => ['date'], // done
'available_budgets' => ['start_date', 'end_date'], // done
'bills' => ['date', 'end_date', 'extension_date'], // done
'budget_limits' => ['start_date', 'end_date'], // done
'currency_exchange_rates' => ['date'], // done
'invited_users' => ['expires'],
'piggy_bank_events' => ['date'],
'piggy_bank_repetitions' => ['startdate', 'targetdate'],
'piggy_banks' => ['startdate', 'targetdate'], // done
'recurrences' => ['first_date', 'repeat_until', 'latest_date'],
'tags' => ['date'],
'transaction_journals' => ['date'],
];
}
/**
* Run the migrations.
* TODO journal_meta, all date fields?
*/
public function up(): void
{
foreach ($this->tables as $table => $columns) {
foreach ($columns as $column) {
$newColumn = sprintf('%s_tz', $column);
if (Schema::hasColumn($table, $column) && !Schema::hasColumn($table, $newColumn)) {
try {
Schema::table(
$table,
static function (Blueprint $table) use ($column, $newColumn): void {
$table->string($newColumn, 50)->nullable()->after($column);
}
);
} catch (QueryException $e) {
app('log')->error(sprintf('Could not add column "%s" to table "%s" query: %s', $newColumn, $table, $e->getMessage()));
app('log')->error('If the column or index already exists (see error), this is not an problem. Otherwise, please open a GitHub discussion.');
}
}
}
}
}
/**
* Reverse the migrations.
*/
public function down(): void {}
};

657
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -755,7 +755,7 @@ return [
'instructions_rule_from_journal' => 'Create a rule based on one of your transactions. Complement or submit the form below.',
'rule_is_strict' => 'strict rule',
'rule_is_not_strict' => 'non-strict rule',
'rule_help_stop_processing' => 'When you check this box, later rules in this group will not be executed.',
'rule_help_stop_processing' => 'When you check this box, later rules in this group will not be executed if this particular rule is executed.',
'rule_help_strict' => 'In strict rules ALL triggers must fire for the action(s) to be executed. In non-strict rules, ANY trigger is enough for the action(s) to be executed.',
'rule_help_active' => 'Inactive rules will never fire.',
'stored_new_rule' => 'Stored new rule with title ":title"',
@@ -2627,6 +2627,7 @@ return [
'no_bills_create_default' => 'Create a bill',
// recurring transactions
'recurrence_max_count' => 'This recurring transactions will be created at most :max time(s), and has been created :count time(s) already.',
'create_right_now' => 'Create right now',
'no_new_transaction_in_recurrence' => 'No new transaction was created. Perhaps it was already fired for this date?',
'recurrences' => 'Recurring transactions',

View File

@@ -71,4 +71,8 @@ return [
'cannot_find_category' => 'Firefly III can\'t find category ":name"',
'cannot_set_budget' => 'Firefly III can\'t set budget ":name" to a transaction of type ":type"',
'journal_invalid_amount' => 'Firefly III can\'t set amount ":amount" because it is not a valid number.',
'cannot_remove_zero_piggy' => 'Cannot remove zero amount from piggy bank ":name"',
'cannot_remove_from_piggy' => 'Cannot remove ":amount" from piggy bank ":name"',
'cannot_add_zero_piggy' => 'Cannot add zero amount to piggy bank ":name"',
'cannot_add_to_piggy' => 'Cannot add ":amount" to piggy bank ":name"',
];

View File

@@ -93,10 +93,10 @@
<code>{{ logEntry.after }}</code>
{% endif %}
{% if 'add_to_piggy' == logEntry.action %}
{{ trans('firefly.ale_action_log_add', {amount: formatAmountBySymbol(logEntry.after.amount, logEntry.after.currency_symbol, logEntry.after.decimal_places, true), name: logEntry.after.name})|raw }}
{{ trans('firefly.ale_action_log_add', {amount: formatAmountBySymbol(logEntry.after.amount, logEntry.after.currency_symbol, logEntry.after.decimal_places, true), name: logEntry.after.piggy})|raw }}
{% endif %}
{% if 'remove_from_piggy' == logEntry.action %}
{{ trans('firefly.ale_action_log_remove', {amount: formatAmountBySymbol(logEntry.after.amount, logEntry.after.currency_symbol, logEntry.after.decimal_places, true), name: logEntry.after.name})|raw }}
{{ trans('firefly.ale_action_log_remove', {amount: formatAmountBySymbol(logEntry.after.amount, logEntry.after.currency_symbol, logEntry.after.decimal_places, true), name: logEntry.after.piggy})|raw }}
{% endif %}
</td>

View File

@@ -22,6 +22,17 @@
</div>
<div class="box-body">
<h4>{{ 'transaction_journal_meta'|_ }}</h4>
{% if array.nr_of_repetitions > 0 %}
<p>
{% if array.journal_count >= array.nr_of_repetitions %}
<span class="text-danger">{{ trans('firefly.recurrence_max_count', {count: array.journal_count, max: array.nr_of_repetitions}) }}</span>
{% endif %}
{% if array.journal_count < array.nr_of_repetitions %}
{{ trans('firefly.recurrence_max_count', {count: array.journal_count, max: array.nr_of_repetitions}) }}
{% endif %}
</p>
{% endif %}
<p>{{ 'description'|_ }}: <em>{{ array.description }}</em></p>
{% if array.active == false %}