Compare commits

...

9 Commits

Author SHA1 Message Date
github-actions[bot]
6ce926e7d2 Merge pull request #10996 from firefly-iii/release-1759466816
🤖 Automatically merge the PR into the develop branch.
2025-10-03 06:47:04 +02:00
JC5
1652c3af72 🤖 Auto commit for release 'develop' on 2025-10-03 2025-10-03 06:46:56 +02:00
James Cole
012172b0b5 Add statistics routine to update event. 2025-10-03 06:42:24 +02:00
James Cole
957bc00847 Add statistics to budget, although unused atm. 2025-10-03 06:41:42 +02:00
github-actions[bot]
7f827fb277 Merge pull request #10995 from firefly-iii/release-1759464256
🤖 Automatically merge the PR into the develop branch.
2025-10-03 06:04:22 +02:00
JC5
8e700944fd 🤖 Auto commit for release 'develop' on 2025-10-03 2025-10-03 06:04:16 +02:00
James Cole
037a128942 Add missing translation. 2025-10-03 05:57:21 +02:00
James Cole
ca364dc877 Remove stats from empty objects. 2025-10-03 05:53:50 +02:00
James Cole
e55af7186c Fix #10994 2025-10-03 05:24:24 +02:00
17 changed files with 117 additions and 74 deletions

View File

@@ -4,8 +4,8 @@ Over time, many people have contributed to Firefly III. Their efforts are not al
Please find below all the people who contributed to the Firefly III code. Their names are mentioned in the year of their first contribution.
## 2025
- Nicky De Maeyer
- Denis Iskandarov
- =
- Lompi
- Jose Diaz-Gonzalez
- SoftBrix

View File

@@ -44,16 +44,16 @@ class DateRequest extends FormRequest
*/
public function getAll(): array
{
$start = $this->getCarbonDate('start');
$end = $this->getCarbonDate('end');
if(null === $start) {
$start = $this->getCarbonDate('start');
$end = $this->getCarbonDate('end');
if (null === $start) {
$start = now()->startOfMonth();
}
if(null === $end) {
if (null === $end) {
$end = now()->endOfMonth();
}
// sanity check on dates:
[$start, $end] = $end < $start ? [$end, $start] : [$start, $end];
[$start, $end] = $end < $start ? [$end, $start] : [$start, $end];
$start->startOfDay();

View File

@@ -105,16 +105,28 @@ class StoredGroupEventHandler
/** @var TransactionJournal $journal */
foreach ($event->transactionGroup->transactionJournals as $journal) {
$source = $journal->transactions()->where('amount', '<', '0')->first();
$dest = $journal->transactions()->where('amount', '>', '0')->first();
$source = $journal->transactions()->where('amount', '<', '0')->first();
$dest = $journal->transactions()->where('amount', '>', '0')->first();
$repository->deleteStatisticsForModel($source->account, $journal->date);
$repository->deleteStatisticsForModel($dest->account, $journal->date);
foreach ($journal->categories as $category) {
$categories = $journal->categories;
$tags = $journal->tags;
$budgets = $journal->budgets;
foreach ($categories as $category) {
$repository->deleteStatisticsForModel($category, $journal->date);
}
foreach ($journal->tags as $tag) {
foreach ($tags as $tag) {
$repository->deleteStatisticsForModel($tag, $journal->date);
}
foreach ($budgets as $budget) {
$repository->deleteStatisticsForModel($budget, $journal->date);
}
if (0 === $categories->count()) {
$repository->deleteStatisticsForPrefix($journal->userGroup, 'no_category', $journal->date);
}
if (0 === $budgets->count()) {
$repository->deleteStatisticsForPrefix($journal->userGroup, 'no_budget', $journal->date);
}
}
}

View File

@@ -68,16 +68,30 @@ class UpdatedGroupEventHandler
/** @var TransactionJournal $journal */
foreach ($event->transactionGroup->transactionJournals as $journal) {
$source = $journal->transactions()->where('amount', '<', '0')->first();
$dest = $journal->transactions()->where('amount', '>', '0')->first();
$source = $journal->transactions()->where('amount', '<', '0')->first();
$dest = $journal->transactions()->where('amount', '>', '0')->first();
$repository->deleteStatisticsForModel($source->account, $journal->date);
$repository->deleteStatisticsForModel($dest->account, $journal->date);
foreach ($journal->categories as $category) {
$categories = $journal->categories;
$tags = $journal->tags;
$budgets = $journal->budgets;
foreach ($categories as $category) {
$repository->deleteStatisticsForModel($category, $journal->date);
}
foreach ($journal->tags as $tag) {
foreach ($tags as $tag) {
$repository->deleteStatisticsForModel($tag, $journal->date);
}
foreach ($budgets as $budget) {
$repository->deleteStatisticsForModel($budget, $journal->date);
}
if (0 === $categories->count()) {
$repository->deleteStatisticsForPrefix($journal->userGroup, 'no_category', $journal->date);
}
if (0 === $budgets->count()) {
$repository->deleteStatisticsForPrefix($journal->userGroup, 'no_budget', $journal->date);
}
}
}

View File

@@ -128,4 +128,9 @@ class Budget extends Model
get: static fn ($value) => (int)$value,
);
}
public function primaryPeriodStatistics(): MorphMany
{
return $this->morphMany(PeriodStatistic::class, 'primary_statable');
}
}

View File

@@ -81,7 +81,7 @@ class Category extends Model
}
/**
* Get all of the category's notes.
* Get all the category's notes.
*/
public function notes(): MorphMany
{

View File

@@ -25,6 +25,7 @@ namespace FireflyIII\Repositories\PeriodStatistic;
use Carbon\Carbon;
use FireflyIII\Models\PeriodStatistic;
use FireflyIII\Models\UserGroup;
use FireflyIII\Support\Repositories\UserGroup\UserGroupInterface;
use FireflyIII\Support\Repositories\UserGroup\UserGroupTrait;
use Illuminate\Database\Eloquent\Model;
@@ -133,4 +134,10 @@ class PeriodStatisticRepository implements PeriodStatisticRepositoryInterface, U
return $stat;
}
#[Override]
public function deleteStatisticsForPrefix(UserGroup $userGroup, string $prefix, Carbon $date): void
{
$userGroup->periodStatistics()->where('start', '<=', $date)->where('end', '>=', $date)->where('type', 'LIKE', sprintf('%s%%', $prefix))->delete();
}
}

View File

@@ -25,6 +25,7 @@ namespace FireflyIII\Repositories\PeriodStatistic;
use Carbon\Carbon;
use FireflyIII\Models\PeriodStatistic;
use FireflyIII\Models\UserGroup;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
@@ -43,4 +44,6 @@ interface PeriodStatisticRepositoryInterface
public function allInRangeForPrefix(string $prefix, Carbon $start, Carbon $end): Collection;
public function deleteStatisticsForModel(Model $model, Carbon $date): void;
public function deleteStatisticsForPrefix(UserGroup $userGroup, string $prefix, Carbon $date): void;
}

View File

@@ -325,26 +325,26 @@ trait PeriodOverview
private function filterStatistics(Carbon $start, Carbon $end, string $type): Collection
{
if (0 === $this->statistics->count()) {
Log::warning('Have no statistic to filter!');
Log::debug('Have no statistic to filter!');
return new Collection();
}
return $this->statistics->filter(
fn(PeriodStatistic $statistic) => $statistic->start->eq($start) && $statistic->end->eq($end) && $statistic->type === $type
fn (PeriodStatistic $statistic) => $statistic->start->eq($start) && $statistic->end->eq($end) && $statistic->type === $type
);
}
private function filterPrefixedStatistics(Carbon $start, Carbon $end, string $prefix): Collection
{
if (0 === $this->statistics->count()) {
Log::warning('Have no statistic to filter!');
Log::debug('Have no statistic to filter!');
return new Collection();
}
return $this->statistics->filter(
fn(PeriodStatistic $statistic) => $statistic->start->eq($start) && $statistic->end->eq($end) && str_starts_with($statistic->type, $prefix)
fn (PeriodStatistic $statistic) => $statistic->start->eq($start) && $statistic->end->eq($end) && str_starts_with($statistic->type, $prefix)
);
}

View File

@@ -57,10 +57,12 @@ class PiggyBankEnrichment implements EnrichmentInterface
private readonly TransactionCurrency $primaryCurrency;
private User $user;
private UserGroup $userGroup;
private ?Carbon $date;
public function __construct()
{
$this->primaryCurrency = Amount::getPrimaryCurrency();
$this->date = now(config('app.timezone'));
}
public function enrich(Collection $collection): Collection
@@ -69,7 +71,6 @@ class PiggyBankEnrichment implements EnrichmentInterface
$this->collectIds();
$this->collectObjectGroups();
$this->collectNotes();
$this->collectCurrentAmounts();
$this->appendCollectedData();
@@ -157,8 +158,8 @@ class PiggyBankEnrichment implements EnrichmentInterface
}
// get suggested per month.
$meta['save_per_month'] = Steam::bcround($this->getSuggestedMonthlyAmount($item->start_date, $item->target_date, $meta['target_amount'], $meta['current_amount']), $currency->decimal_places);
$meta['pc_save_per_month'] = Steam::bcround($this->getSuggestedMonthlyAmount($item->start_date, $item->target_date, $meta['pc_target_amount'], $meta['pc_current_amount']), $currency->decimal_places);
$meta['save_per_month'] = Steam::bcround($this->getSuggestedMonthlyAmount($this->date, $item->target_date, $meta['target_amount'], $meta['current_amount']), $currency->decimal_places);
$meta['pc_save_per_month'] = Steam::bcround($this->getSuggestedMonthlyAmount($this->date, $item->target_date, $meta['pc_target_amount'], $meta['pc_current_amount']), $currency->decimal_places);
$item->meta = $meta;
@@ -166,7 +167,10 @@ class PiggyBankEnrichment implements EnrichmentInterface
});
}
private function collectCurrentAmounts(): void {}
public function setDate(?Carbon $date): void
{
$this->date = $date;
}
private function collectIds(): void
{

View File

@@ -177,17 +177,17 @@ class UpdatePiggyBank implements ActionInterface
private function removeAmount(PiggyBank $piggyBank, array $array, TransactionJournal $journal, Account $account, string $amount): void
{
$repository = app(PiggyBankRepositoryInterface::class);
$repository = app(PiggyBankRepositoryInterface::class);
$accountRepository = app(AccountRepositoryInterface::class);
$repository->setUser($journal->user);
$accountRepository->setUser($account->user);
// how much can we remove from this piggy bank?
$toRemove = $repository->getCurrentAmount($piggyBank, $account);
$toRemove = $repository->getCurrentAmount($piggyBank, $account);
Log::debug(sprintf('Amount is %s, max to remove is %s', $amount, $toRemove));
// if $amount is bigger than $toRemove, shrink it.
$amount = -1 === bccomp($amount, $toRemove) ? $amount : $toRemove;
$amount = -1 === bccomp($amount, $toRemove) ? $amount : $toRemove;
Log::debug(sprintf('Amount is now %s', $amount));
// if amount is zero, stop.
@@ -212,7 +212,7 @@ class UpdatePiggyBank implements ActionInterface
private function addAmount(PiggyBank $piggyBank, array $array, TransactionJournal $journal, Account $account, string $amount): void
{
$repository = app(PiggyBankRepositoryInterface::class);
$repository = app(PiggyBankRepositoryInterface::class);
$accountRepository = app(AccountRepositoryInterface::class);
$repository->setUser($journal->user);
$accountRepository->setUser($account->user);

View File

@@ -24,9 +24,11 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- [Issue 10954](https://github.com/firefly-iii/firefly-iii/issues/10954) (Internal Server Error when trying to access (default) account) reported by @thomaschristory
- [Issue 10956](https://github.com/firefly-iii/firefly-iii/issues/10956) (Manual webhook trigger fail) reported by @dudu7731
- [Issue 10960](https://github.com/firefly-iii/firefly-iii/issues/10960) (404 after deleting subscription) reported by @lindely
- [Issue 10965](https://github.com/firefly-iii/firefly-iii/issues/10965) (Fix running balance on liability overview) reported by @JC5
- [Discussion 10974](https://github.com/orgs/firefly-iii/discussions/10974) (Big webhook_messages table) started by @Billos
- #10988
- [Discussion 10988](https://github.com/orgs/firefly-iii/discussions/10988) (Call to a member function startOfDay() on null.) started by @molnarti
- [Issue 10990](https://github.com/firefly-iii/firefly-iii/issues/10990) (duplicate piggy event via API) reported by @4e868df3
- [Discussion 10994](https://github.com/orgs/firefly-iii/discussions/10994) (How does the save per month attribute from a piggy bank is calculated?) started by @AdriDevelopsThings
### API

39
composer.lock generated
View File

@@ -6193,16 +6193,16 @@
},
{
"name": "spatie/laravel-html",
"version": "3.12.0",
"version": "3.12.1",
"source": {
"type": "git",
"url": "https://github.com/spatie/laravel-html.git",
"reference": "3655f335609d853f51e431698179ddfe05851126"
"reference": "72af3cad24d153c230ff6e1da9fa3b7b702834c9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/spatie/laravel-html/zipball/3655f335609d853f51e431698179ddfe05851126",
"reference": "3655f335609d853f51e431698179ddfe05851126",
"url": "https://api.github.com/repos/spatie/laravel-html/zipball/72af3cad24d153c230ff6e1da9fa3b7b702834c9",
"reference": "72af3cad24d153c230ff6e1da9fa3b7b702834c9",
"shasum": ""
},
"require": {
@@ -6259,7 +6259,7 @@
"spatie"
],
"support": {
"source": "https://github.com/spatie/laravel-html/tree/3.12.0"
"source": "https://github.com/spatie/laravel-html/tree/3.12.1"
},
"funding": [
{
@@ -6267,7 +6267,7 @@
"type": "custom"
}
],
"time": "2025-03-21T08:58:06+00:00"
"time": "2025-10-02T07:26:38+00:00"
},
{
"name": "spatie/laravel-ignition",
@@ -11333,16 +11333,11 @@
},
{
"name": "phpstan/phpstan",
"version": "2.1.29",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan-phar-composer-source.git",
"reference": "git"
},
"version": "2.1.30",
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/d618573eed4a1b6b75e37b2e0b65ac65c885d88e",
"reference": "d618573eed4a1b6b75e37b2e0b65ac65c885d88e",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/a4a7f159927983dd4f7c8020ed227d80b7f39d7d",
"reference": "a4a7f159927983dd4f7c8020ed227d80b7f39d7d",
"shasum": ""
},
"require": {
@@ -11387,7 +11382,7 @@
"type": "github"
}
],
"time": "2025-09-25T06:58:18+00:00"
"time": "2025-10-02T16:07:52+00:00"
},
{
"name": "phpstan/phpstan-deprecation-rules",
@@ -11820,16 +11815,16 @@
},
{
"name": "phpunit/phpunit",
"version": "12.3.15",
"version": "12.4.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "b035ee2cd8ecad4091885b61017ebb1d80eb0e57"
"reference": "f62aab5794e36ccd26860db2d1bbf89ac19028d9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b035ee2cd8ecad4091885b61017ebb1d80eb0e57",
"reference": "b035ee2cd8ecad4091885b61017ebb1d80eb0e57",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f62aab5794e36ccd26860db2d1bbf89ac19028d9",
"reference": "f62aab5794e36ccd26860db2d1bbf89ac19028d9",
"shasum": ""
},
"require": {
@@ -11865,7 +11860,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "12.3-dev"
"dev-main": "12.4-dev"
}
},
"autoload": {
@@ -11897,7 +11892,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
"source": "https://github.com/sebastianbergmann/phpunit/tree/12.3.15"
"source": "https://github.com/sebastianbergmann/phpunit/tree/12.4.0"
},
"funding": [
{
@@ -11921,7 +11916,7 @@
"type": "tidelift"
}
],
"time": "2025-09-28T12:10:54+00:00"
"time": "2025-10-03T04:28:03+00:00"
},
{
"name": "rector/rector",

View File

@@ -78,8 +78,8 @@ return [
'running_balance_column' => env('USE_RUNNING_BALANCE', false),
// see cer.php for exchange rates feature flag.
],
'version' => 'develop/2025-10-02',
'build_time' => 1759383469,
'version' => 'develop/2025-10-03',
'build_time' => 1759466687,
'api_version' => '2.1.0', // field is no longer used.
'db_version' => 28, // field is no longer used.

42
package-lock.json generated
View File

@@ -5726,9 +5726,9 @@
"license": "MIT"
},
"node_modules/electron-to-chromium": {
"version": "1.5.228",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.228.tgz",
"integrity": "sha512-nxkiyuqAn4MJ1QbobwqJILiDtu/jk14hEAWaMiJmNPh1Z+jqoFlBFZjdXwLWGeVSeu9hGLg6+2G9yJaW8rBIFA==",
"version": "1.5.229",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.229.tgz",
"integrity": "sha512-cwhDcZKGcT/rEthLRJ9eBlMDkh1sorgsuk+6dpsehV0g9CABsIqBxU4rLRjG+d/U6pYU1s37A4lSKrVc5lSQYg==",
"dev": true,
"license": "ISC"
},
@@ -10980,9 +10980,9 @@
}
},
"node_modules/tapable": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.3.tgz",
"integrity": "sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg==",
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz",
"integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -11085,9 +11085,9 @@
"license": "MIT"
},
"node_modules/terser-webpack-plugin/node_modules/schema-utils": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz",
"integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==",
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz",
"integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -11503,9 +11503,9 @@
}
},
"node_modules/vite": {
"version": "7.1.7",
"resolved": "https://registry.npmjs.org/vite/-/vite-7.1.7.tgz",
"integrity": "sha512-VbA8ScMvAISJNJVbRDTJdCwqQoAareR/wutevKanhR2/1EkoXVZVkkORaYm/tNVCjP/UDTKtcw3bAkwOUdedmA==",
"version": "7.1.9",
"resolved": "https://registry.npmjs.org/vite/-/vite-7.1.9.tgz",
"integrity": "sha512-4nVGliEpxmhCL8DslSAUdxlB6+SMrhB0a1v5ijlh1xB1nEPuy1mxaHxysVucLHuWryAxLWg6a5ei+U4TLn/rFg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -11984,9 +11984,9 @@
"license": "MIT"
},
"node_modules/webpack-dev-middleware/node_modules/schema-utils": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz",
"integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==",
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz",
"integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -12119,9 +12119,9 @@
}
},
"node_modules/webpack-dev-server/node_modules/schema-utils": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz",
"integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==",
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz",
"integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -12221,9 +12221,9 @@
"license": "MIT"
},
"node_modules/webpack/node_modules/schema-utils": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz",
"integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==",
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz",
"integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==",
"dev": true,
"license": "MIT",
"dependencies": {

View File

@@ -160,7 +160,7 @@
"url": "URL",
"active": "Activ",
"interest_date": "Data de interes",
"administration_currency": "Primary currency",
"administration_currency": "Moneda principala",
"title": "Titlu",
"date": "Dat\u0103",
"book_date": "Rezerv\u0103 dat\u0103",

View File

@@ -909,6 +909,7 @@ return [
'rule_trigger_tag_is' => 'Any tag is ":trigger_value"',
'rule_trigger_tag_contains_choice' => 'Any tag contains..',
'rule_trigger_tag_contains' => 'Any tag contains ":trigger_value"',
'rule_trigger_not_tag_contains' => 'No tag contains ":trigger_value"',
'rule_trigger_tag_ends_choice' => 'Any tag ends with..',
'rule_trigger_tag_ends' => 'Any tag ends with ":trigger_value"',
'rule_trigger_tag_starts_choice' => 'Any tag starts with..',