From 293be04d40a150ac011381b424d50c2474d1ffc8 Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 19 Aug 2025 19:35:12 +0200 Subject: [PATCH 001/111] Expand webhooks to support multiple delivery payloads, event triggers and responses. --- .../V1/Controllers/Webhook/ShowController.php | 9 ++ .../Commands/Upgrade/UpgradesDatabase.php | 1 + .../Commands/Upgrade/UpgradesWebhooks.php | 90 ++++++++++++ app/Http/Controllers/HomeController.php | 25 ++++ app/Models/Webhook.php | 40 ++++-- app/Models/WebhookDelivery.php | 26 ++++ app/Models/WebhookResponse.php | 26 ++++ app/Models/WebhookTrigger.php | 26 ++++ .../Webhook/WebhookRepository.php | 48 ++++--- .../JsonApi/Enrichments/WebhookEnrichment.php | 134 ++++++++++++++++++ app/Transformers/WebhookTransformer.php | 9 +- ...9_180459_create_webhook_details_tables.php | 127 +++++++++++++++++ database/seeders/AccountTypeSeeder.php | 21 +-- database/seeders/DatabaseSeeder.php | 1 + database/seeders/TransactionTypeSeeder.php | 19 +-- database/seeders/UserRoleSeeder.php | 11 +- database/seeders/WebhookDataSeeder.php | 48 +++++++ 17 files changed, 586 insertions(+), 75 deletions(-) create mode 100644 app/Console/Commands/Upgrade/UpgradesWebhooks.php create mode 100644 app/Models/WebhookDelivery.php create mode 100644 app/Models/WebhookResponse.php create mode 100644 app/Models/WebhookTrigger.php create mode 100644 app/Support/JsonApi/Enrichments/WebhookEnrichment.php create mode 100644 database/migrations/2025_08_19_180459_create_webhook_details_tables.php create mode 100644 database/seeders/WebhookDataSeeder.php diff --git a/app/Api/V1/Controllers/Webhook/ShowController.php b/app/Api/V1/Controllers/Webhook/ShowController.php index 31906c85ed..cfc2b1ab39 100644 --- a/app/Api/V1/Controllers/Webhook/ShowController.php +++ b/app/Api/V1/Controllers/Webhook/ShowController.php @@ -32,7 +32,9 @@ use FireflyIII\Generator\Webhook\MessageGeneratorInterface; use FireflyIII\Models\TransactionGroup; use FireflyIII\Models\Webhook; use FireflyIII\Repositories\Webhook\WebhookRepositoryInterface; +use FireflyIII\Support\JsonApi\Enrichments\WebhookEnrichment; use FireflyIII\Transformers\WebhookTransformer; +use FireflyIII\User; use Illuminate\Http\JsonResponse; use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Support\Collection; @@ -90,6 +92,13 @@ class ShowController extends Controller $paginator = new LengthAwarePaginator($webhooks, $count, $pageSize, $this->parameters->get('page')); $paginator->setPath(route('api.v1.webhooks.index').$this->buildParams()); + // enrich + /** @var User $admin */ + $admin = auth()->user(); + $enrichment = new WebhookEnrichment(); + $enrichment->setUser($admin); + $webhooks = $enrichment->enrich($webhooks); + /** @var WebhookTransformer $transformer */ $transformer = app(WebhookTransformer::class); $transformer->setParameters($this->parameters); diff --git a/app/Console/Commands/Upgrade/UpgradesDatabase.php b/app/Console/Commands/Upgrade/UpgradesDatabase.php index a98ff922d8..92429a9a36 100644 --- a/app/Console/Commands/Upgrade/UpgradesDatabase.php +++ b/app/Console/Commands/Upgrade/UpgradesDatabase.php @@ -75,6 +75,7 @@ class UpgradesDatabase extends Command 'upgrade:610-currency-preferences', 'upgrade:620-piggy-banks', 'upgrade:620-pc-amounts', + 'upgrade:640-upgrade-webhooks', 'firefly-iii:correct-database', ]; $args = []; diff --git a/app/Console/Commands/Upgrade/UpgradesWebhooks.php b/app/Console/Commands/Upgrade/UpgradesWebhooks.php new file mode 100644 index 0000000000..7f3e6e8152 --- /dev/null +++ b/app/Console/Commands/Upgrade/UpgradesWebhooks.php @@ -0,0 +1,90 @@ +isExecuted() && true !== $this->option('force')) { + $this->friendlyInfo('This command has already been executed.'); + + return 0; + } + + $this->upgradeWebhooks(); + $this->markAsExecuted(); + $this->friendlyPositive('Upgraded webhooks.'); + + return 0; + } + + private function isExecuted(): bool + { + $configVar = app('fireflyconfig')->get(self::CONFIG_NAME, false); + if (null !== $configVar) { + return (bool)$configVar->data; + } + + return false; + } + + + private function upgradeWebhooks(): void + { + $set = Webhook::where('delivery', '>', 1)->orWhere('trigger', '>', 1)->orWhere('response', '>', 1)->get(); + /** @var Webhook $webhook */ + foreach ($set as $webhook) { + $this->upgradeWebhook($webhook); + } + } + + private function upgradeWebhook(Webhook $webhook): void + { + $delivery = WebhookDelivery::tryFrom((int)$webhook->delivery); + $response = WebhookResponse::tryFrom((int)$webhook->response); + $trigger = WebhookTrigger::tryFrom((int)$webhook->trigger); + if (null === $delivery || null === $response || null === $trigger) { + $this->friendlyError(sprintf('[a] Webhook #%d has an invalid delivery, response or trigger value. Will not upgrade.', $webhook->id)); + return; + } + $deliveryModel = WebhookDeliveryModel::where('key', $delivery->value)->first(); + $responseModel = WebhookResponseModel::where('key', $response->value)->first(); + $triggerModel = WebhookTriggerModel::where('key', $trigger->value)->first(); + if (null === $deliveryModel || null === $responseModel || null === $triggerModel) { + $this->friendlyError(sprintf('[b] Webhook #%d has an invalid delivery, response or trigger model. Will not upgrade.', $webhook->id)); + return; + } + $webhook->webhookDeliveries()->attach([$deliveryModel->id]); + $webhook->webhookResponses()->attach([$responseModel->id]); + $webhook->webhookTriggers()->attach([$triggerModel->id]); + $webhook->delivery = 1; + $webhook->response = 1; + $webhook->trigger = 1; + $webhook->save(); + $this->friendlyPositive(sprintf('Webhook #%d upgraded.', $webhook->id)); + } + private function markAsExecuted(): void + { + app('fireflyconfig')->set(self::CONFIG_NAME, true); + } +} diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index b829724308..6142cf7701 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -31,6 +31,10 @@ use FireflyIII\Events\RequestedVersionCheckStatus; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Http\Middleware\Installer; +use FireflyIII\Models\Webhook; +use FireflyIII\Models\WebhookDelivery; +use FireflyIII\Models\WebhookResponse; +use FireflyIII\Models\WebhookTrigger; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Bill\BillRepositoryInterface; use FireflyIII\User; @@ -121,6 +125,27 @@ class HomeController extends Controller */ public function index(AccountRepositoryInterface $repository): mixed { + $trigger = WebhookTrigger::first(); + $delivery = WebhookDelivery::first(); + $response = WebhookResponse::first(); + + $webhook = new Webhook(); + $webhook->user_id = auth()->id(); + $webhook->user_group_id = auth()->user()->user_group_id; + $webhook->title = 'Test Webhook'; + $webhook->url = 'https://example.com/webhook'; + $webhook->active = true; + $webhook->secret = 'abc'; + $webhook->trigger = 1; + $webhook->response = 1; + $webhook->delivery = 1; + $webhook->save(); + $webhook->webhookDeliveries()->saveMany(new Collection()->push($delivery)); + $webhook->webhookResponses()->saveMany(new Collection()->push($response)); + $webhook->webhookTriggers()->saveMany(new Collection()->push($trigger)); + + + $types = config('firefly.accountTypesByIdentifier.asset'); $count = $repository->count($types); Log::channel('audit')->info('User visits homepage.'); diff --git a/app/Models/Webhook.php b/app/Models/Webhook.php index 7932894770..a939084257 100644 --- a/app/Models/Webhook.php +++ b/app/Models/Webhook.php @@ -24,14 +24,15 @@ declare(strict_types=1); namespace FireflyIII\Models; -use FireflyIII\Enums\WebhookDelivery; -use FireflyIII\Enums\WebhookResponse; -use FireflyIII\Enums\WebhookTrigger; +use FireflyIII\Enums\WebhookDelivery as WebhookDeliveryEnum; +use FireflyIII\Enums\WebhookResponse as WebhookResponseEnum; +use FireflyIII\Enums\WebhookTrigger as WebhookTriggerEnum; use FireflyIII\Support\Models\ReturnsIntegerIdTrait; use FireflyIII\Support\Models\ReturnsIntegerUserIdTrait; use FireflyIII\User; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\SoftDeletes; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; @@ -56,7 +57,7 @@ class Webhook extends Model public static function getDeliveries(): array { $array = []; - $set = WebhookDelivery::cases(); + $set = WebhookDeliveryEnum::cases(); foreach ($set as $item) { $array[$item->value] = $item->name; } @@ -67,7 +68,7 @@ class Webhook extends Model public static function getDeliveriesForValidation(): array { $array = []; - $set = WebhookDelivery::cases(); + $set = WebhookDeliveryEnum::cases(); foreach ($set as $item) { $array[$item->name] = $item->value; $array[$item->value] = $item->value; @@ -79,7 +80,7 @@ class Webhook extends Model public static function getResponses(): array { $array = []; - $set = WebhookResponse::cases(); + $set = WebhookResponseEnum::cases(); foreach ($set as $item) { $array[$item->value] = $item->name; } @@ -90,7 +91,7 @@ class Webhook extends Model public static function getResponsesForValidation(): array { $array = []; - $set = WebhookResponse::cases(); + $set = WebhookResponseEnum::cases(); foreach ($set as $item) { $array[$item->name] = $item->value; $array[$item->value] = $item->value; @@ -102,7 +103,7 @@ class Webhook extends Model public static function getTriggers(): array { $array = []; - $set = WebhookTrigger::cases(); + $set = WebhookTriggerEnum::cases(); foreach ($set as $item) { $array[$item->value] = $item->name; } @@ -113,7 +114,7 @@ class Webhook extends Model public static function getTriggersForValidation(): array { $array = []; - $set = WebhookTrigger::cases(); + $set = WebhookTriggerEnum::cases(); foreach ($set as $item) { $array[$item->name] = $item->value; $array[$item->value] = $item->value; @@ -130,13 +131,13 @@ 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(); + $user = auth()->user(); /** @var null|Webhook $webhook */ - $webhook = $user->webhooks()->find($webhookId); + $webhook = $user->webhooks()->find($webhookId); if (null !== $webhook) { return $webhook; } @@ -155,6 +156,21 @@ class Webhook extends Model return $this->hasMany(WebhookMessage::class); } + public function webhookDeliveries(): BelongsToMany + { + return $this->belongsToMany(WebhookDelivery::class); + } + + public function webhookResponses(): BelongsToMany + { + return $this->belongsToMany(WebhookResponse::class); + } + + public function webhookTriggers(): BelongsToMany + { + return $this->belongsToMany(WebhookTrigger::class); + } + protected function casts(): array { return [ diff --git a/app/Models/WebhookDelivery.php b/app/Models/WebhookDelivery.php new file mode 100644 index 0000000000..5538cf427d --- /dev/null +++ b/app/Models/WebhookDelivery.php @@ -0,0 +1,26 @@ + (int) $value, + ); + } +} diff --git a/app/Models/WebhookResponse.php b/app/Models/WebhookResponse.php new file mode 100644 index 0000000000..a651805f59 --- /dev/null +++ b/app/Models/WebhookResponse.php @@ -0,0 +1,26 @@ + (int) $value, + ); + } +} diff --git a/app/Models/WebhookTrigger.php b/app/Models/WebhookTrigger.php new file mode 100644 index 0000000000..fd8036face --- /dev/null +++ b/app/Models/WebhookTrigger.php @@ -0,0 +1,26 @@ + (int) $value, + ); + } +} diff --git a/app/Repositories/Webhook/WebhookRepository.php b/app/Repositories/Webhook/WebhookRepository.php index 6d684b8984..64f6230ac3 100644 --- a/app/Repositories/Webhook/WebhookRepository.php +++ b/app/Repositories/Webhook/WebhookRepository.php @@ -41,7 +41,12 @@ class WebhookRepository implements WebhookRepositoryInterface, UserGroupInterfac public function all(): Collection { - return $this->user->webhooks()->get(); + return $this->user->webhooks() + // only get upgraded webhooks + ->where('delivery', 1) + ->where('response', 1) + ->where('trigger', 1) + ->get(); } public function destroy(Webhook $webhook): void @@ -67,22 +72,20 @@ class WebhookRepository implements WebhookRepositoryInterface, UserGroupInterfac public function getMessages(Webhook $webhook): Collection { return $webhook->webhookMessages() - ->orderBy('created_at', 'DESC') - ->get(['webhook_messages.*']) - ; + ->orderBy('created_at', 'DESC') + ->get(['webhook_messages.*']); } public function getReadyMessages(Webhook $webhook): Collection { return $webhook->webhookMessages() - ->where('webhook_messages.sent', 0) - ->where('webhook_messages.errored', 0) - ->get(['webhook_messages.*']) - ->filter( - static fn (WebhookMessage $message) // @phpstan-ignore-line - => $message->webhookAttempts()->count() <= 2 - )->splice(0, 3) - ; + ->where('webhook_messages.sent', 0) + ->where('webhook_messages.errored', 0) + ->get(['webhook_messages.*']) + ->filter( + static fn(WebhookMessage $message) // @phpstan-ignore-line + => $message->webhookAttempts()->count() <= 2 + )->splice(0, 3); } public function store(array $data): Webhook @@ -93,9 +96,12 @@ class WebhookRepository implements WebhookRepositoryInterface, UserGroupInterfac 'user_group_id' => $this->user->user_group_id, 'active' => $data['active'] ?? false, 'title' => $data['title'] ?? null, - 'trigger' => $data['trigger'], - 'response' => $data['response'], - 'delivery' => $data['delivery'], + // 'trigger' => $data['trigger'], + // 'response' => $data['response'], + // 'delivery' => $data['delivery'], + 'trigger' => 1, + 'response' => 1, + 'delivery' => 1, 'secret' => $secret, 'url' => $data['url'], ]; @@ -105,12 +111,12 @@ class WebhookRepository implements WebhookRepositoryInterface, UserGroupInterfac public function update(Webhook $webhook, array $data): Webhook { - $webhook->active = $data['active'] ?? $webhook->active; - $webhook->trigger = $data['trigger'] ?? $webhook->trigger; - $webhook->response = $data['response'] ?? $webhook->response; - $webhook->delivery = $data['delivery'] ?? $webhook->delivery; - $webhook->title = $data['title'] ?? $webhook->title; - $webhook->url = $data['url'] ?? $webhook->url; + $webhook->active = $data['active'] ?? $webhook->active; +// $webhook->trigger = $data['trigger'] ?? $webhook->trigger; +// $webhook->response = $data['response'] ?? $webhook->response; +// $webhook->delivery = $data['delivery'] ?? $webhook->delivery; + $webhook->title = $data['title'] ?? $webhook->title; + $webhook->url = $data['url'] ?? $webhook->url; if (true === $data['secret']) { $secret = Str::random(24); diff --git a/app/Support/JsonApi/Enrichments/WebhookEnrichment.php b/app/Support/JsonApi/Enrichments/WebhookEnrichment.php new file mode 100644 index 0000000000..acbc315c28 --- /dev/null +++ b/app/Support/JsonApi/Enrichments/WebhookEnrichment.php @@ -0,0 +1,134 @@ +collection = $collection; + if ($this->collection->count() > 0) { + $this->collectIds(); + $this->collectInfo(); + $this->collectWebhookInfo(); + $this->appendCollectedInfo(); + } + return $this->collection; + } + + public function enrichSingle(Model|array $model): array|Model + { + Log::debug(__METHOD__); + $collection = new Collection([$model]); + $collection = $this->enrich($collection); + + return $collection->first(); + } + + public function setUser(User $user): void + { + $this->user = $user; + } + + public function setUserGroup(UserGroup $userGroup): void + { + $this->userGroup = $userGroup; + } + + private function collectIds(): void + { + /** @var Webhook $webhook */ + foreach ($this->collection as $webhook) { + $this->ids[] = $webhook->id; + } + $this->ids = array_unique($this->ids); + } + + private function collectInfo(): void + { + $all = WebhookDelivery::get(); + /** @var WebhookDelivery $item */ + foreach ($all as $item) { + $this->deliveries[$item->id] = $item->key; + } + $all = WebhookResponse::get(); + /** @var WebhookResponse $item */ + foreach ($all as $item) { + $this->responses[$item->id] = $item->key; + } + $all = WebhookTrigger::get(); + /** @var WebhookTrigger $item */ + foreach ($all as $item) { + $this->triggers[$item->id] = $item->key; + } + + } + + private function collectWebhookInfo(): void + { + $set = DB::table('webhook_webhook_delivery')->whereIn('webhook_id', $this->ids)->get(['webhook_id', 'webhook_delivery_id']); + /** @var \stdClass $item */ + foreach ($set as $item) { + $id = $item->webhook_id; + $deliveryId = $item->webhook_delivery_id; + $this->webhookDeliveries[$id][] = WebhookDeliveryEnum::from($this->deliveries[$deliveryId])->name; + } + + $set = DB::table('webhook_webhook_response')->whereIn('webhook_id', $this->ids)->get(['webhook_id', 'webhook_response_id']); + /** @var \stdClass $item */ + foreach ($set as $item) { + $id = $item->webhook_id; + $responseId = $item->webhook_response_id; + $this->webhookResponses[$id][] = WebhookResponseEnum::from($this->responses[$responseId])->name; + } + + $set = DB::table('webhook_webhook_trigger')->whereIn('webhook_id', $this->ids)->get(['webhook_id', 'webhook_trigger_id']); + /** @var \stdClass $item */ + foreach ($set as $item) { + $id = $item->webhook_id; + $triggerId = $item->webhook_trigger_id; + $this->webhookTriggers[$id][] = WebhookTriggerEnum::from($this->triggers[$triggerId])->name; + } + } + + private function appendCollectedInfo(): void + { + $this->collection = $this->collection->map(function (Webhook $item) { + $meta = [ + 'deliveries' => $this->webhookDeliveries[$item->id] ?? [], + 'responses' => $this->webhookResponses[$item->id] ?? [], + 'triggers' => $this->webhookTriggers[$item->id] ?? [], + ]; + $item->meta = $meta; + return $item; + }); + } + +} diff --git a/app/Transformers/WebhookTransformer.php b/app/Transformers/WebhookTransformer.php index d913b38f3d..5404982f34 100644 --- a/app/Transformers/WebhookTransformer.php +++ b/app/Transformers/WebhookTransformer.php @@ -51,9 +51,12 @@ class WebhookTransformer extends AbstractTransformer 'active' => $webhook->active, 'title' => $webhook->title, 'secret' => $webhook->secret, - 'trigger' => $this->getEnum('trigger', $webhook->trigger), - 'response' => $this->getEnum('response', $webhook->response), - 'delivery' => $this->getEnum('delivery', $webhook->delivery), + 'triggers' => $webhook->meta['triggers'], + 'deliveries' => $webhook->meta['deliveries'], + 'responses' => $webhook->meta['responses'], +// 'trigger' => $this->getEnum('trigger', $webhook->trigger), +// 'response' => $this->getEnum('response', $webhook->response), +// 'delivery' => $this->getEnum('delivery', $webhook->delivery), 'url' => $webhook->url, 'links' => [ [ diff --git a/database/migrations/2025_08_19_180459_create_webhook_details_tables.php b/database/migrations/2025_08_19_180459_create_webhook_details_tables.php new file mode 100644 index 0000000000..c25d4083eb --- /dev/null +++ b/database/migrations/2025_08_19_180459_create_webhook_details_tables.php @@ -0,0 +1,127 @@ +id(); + $table->timestamps(); + $table->smallInteger('key')->unsigned(); + $table->string('title', 100); + $table->unique(['key', 'title']); + }); + } + if (!Schema::hasTable('webhook_responses')) { + Schema::create('webhook_responses', function (Blueprint $table) { + $table->id(); + $table->timestamps(); + $table->smallInteger('key')->unsigned(); + $table->string('title', 100); + $table->unique(['key', 'title']); + }); + } + if (!Schema::hasTable('webhook_deliveries')) { + Schema::create('webhook_deliveries', function (Blueprint $table) { + $table->id(); + $table->timestamps(); + $table->smallInteger('key')->unsigned(); + $table->string('title', 100); + $table->unique(['key', 'title']); + }); + } + + // webhook_webhook_trigger + if (!Schema::hasTable('webhook_webhook_trigger')) { + try { + Schema::create( + 'webhook_webhook_trigger', + static function (Blueprint $table): void { + $table->increments('id'); + $table->integer('webhook_id', false, true); + $table->bigInteger('webhook_trigger_id', false, true); + $table->foreign('webhook_id')->references('id')->on('webhooks')->onDelete('cascade'); + $table->foreign('webhook_trigger_id','link_to_trigger')->references('id')->on('webhook_triggers')->onDelete('cascade'); + + // unique combi: + $table->unique(['webhook_id', 'webhook_trigger_id']); + } + ); + } catch (QueryException $e) { + app('log')->error(sprintf(self::TABLE_ERROR, 'webhook_webhook_trigger', $e->getMessage())); + app('log')->error(self::TABLE_ALREADY_EXISTS); + } + } + + + // webhook_webhook_response + if (!Schema::hasTable('webhook_webhook_response')) { + try { + Schema::create( + 'webhook_webhook_response', + static function (Blueprint $table): void { + $table->increments('id'); + $table->integer('webhook_id', false, true); + $table->bigInteger('webhook_response_id', false, true); + $table->foreign('webhook_id')->references('id')->on('webhooks')->onDelete('cascade'); + $table->foreign('webhook_response_id','link_to_response')->references('id')->on('webhook_responses')->onDelete('cascade'); + + // unique combi: + $table->unique(['webhook_id', 'webhook_response_id']); + } + ); + } catch (QueryException $e) { + app('log')->error(sprintf(self::TABLE_ERROR, 'webhook_webhook_response', $e->getMessage())); + app('log')->error(self::TABLE_ALREADY_EXISTS); + } + } + + // webhook_webhook_delivery + if (!Schema::hasTable('webhook_webhook_delivery')) { + try { + Schema::create( + 'webhook_webhook_delivery', + static function (Blueprint $table): void { + $table->increments('id'); + $table->integer('webhook_id', false, true); + $table->bigInteger('webhook_delivery_id', false, true); + $table->foreign('webhook_id')->references('id')->on('webhooks')->onDelete('cascade'); + $table->foreign('webhook_delivery_id','link_to_delivery')->references('id')->on('webhook_deliveries')->onDelete('cascade'); + + // unique combi: + $table->unique(['webhook_id', 'webhook_delivery_id']); + } + ); + } catch (QueryException $e) { + app('log')->error(sprintf(self::TABLE_ERROR, 'webhook_webhook_delivery', $e->getMessage())); + app('log')->error(self::TABLE_ALREADY_EXISTS); + } + } + + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('webhook_webhook_delivery'); + Schema::dropIfExists('webhook_webhook_trigger'); + Schema::dropIfExists('webhook_webhook_response'); + + Schema::dropIfExists('webhook_triggers'); + Schema::dropIfExists('webhook_responses'); + Schema::dropIfExists('webhook_deliveries'); + } +}; diff --git a/database/seeders/AccountTypeSeeder.php b/database/seeders/AccountTypeSeeder.php index 0b9dda0723..ab34b8b772 100644 --- a/database/seeders/AccountTypeSeeder.php +++ b/database/seeders/AccountTypeSeeder.php @@ -35,25 +35,10 @@ class AccountTypeSeeder extends Seeder { public function run(): void { - $types = [ - AccountTypeEnum::DEFAULT->value, - AccountTypeEnum::CASH->value, - AccountTypeEnum::ASSET->value, - AccountTypeEnum::EXPENSE->value, - AccountTypeEnum::REVENUE->value, - AccountTypeEnum::INITIAL_BALANCE->value, - AccountTypeEnum::BENEFICIARY->value, - AccountTypeEnum::IMPORT->value, - AccountTypeEnum::LOAN->value, - AccountTypeEnum::RECONCILIATION->value, - AccountTypeEnum::DEBT->value, - AccountTypeEnum::MORTGAGE->value, - AccountTypeEnum::LIABILITY_CREDIT->value, - ]; - foreach ($types as $type) { - if (null === AccountType::where('type', $type)->first()) { + foreach(AccountTypeEnum::cases() as $type) { + if (null === AccountType::where('type', $type->value)->first()) { try { - AccountType::create(['type' => $type]); + AccountType::create(['type' => $type->value]); } catch (PDOException $e) { // @ignoreException } diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index bdce19f553..42305fd851 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -43,5 +43,6 @@ class DatabaseSeeder extends Seeder $this->call(ConfigSeeder::class); $this->call(UserRoleSeeder::class); $this->call(ExchangeRateSeeder::class); + $this->call(WebhookDataSeeder::class); } } diff --git a/database/seeders/TransactionTypeSeeder.php b/database/seeders/TransactionTypeSeeder.php index 2a7d813164..b4ed1c1ef3 100644 --- a/database/seeders/TransactionTypeSeeder.php +++ b/database/seeders/TransactionTypeSeeder.php @@ -35,24 +35,17 @@ class TransactionTypeSeeder extends Seeder { public function run(): void { - $types = [ - TransactionTypeEnum::WITHDRAWAL->value, - TransactionTypeEnum::DEPOSIT->value, - TransactionTypeEnum::TRANSFER->value, - TransactionTypeEnum::OPENING_BALANCE->value, - TransactionTypeEnum::RECONCILIATION->value, - TransactionTypeEnum::INVALID->value, - TransactionTypeEnum::LIABILITY_CREDIT->value, - ]; - - foreach ($types as $type) { - if (null === TransactionType::where('type', $type)->first()) { + /** @var TransactionTypeEnum $type */ + foreach (TransactionTypeEnum::cases() as $type) { + if (null === TransactionType::where('type', $type->value)->first()) { try { - TransactionType::create(['type' => $type]); + TransactionType::create(['type' => $type->value]); } catch (PDOException $e) { // @ignoreException } } } + + } } diff --git a/database/seeders/UserRoleSeeder.php b/database/seeders/UserRoleSeeder.php index c779b91812..e9f1a08515 100644 --- a/database/seeders/UserRoleSeeder.php +++ b/database/seeders/UserRoleSeeder.php @@ -39,16 +39,11 @@ class UserRoleSeeder extends Seeder */ public function run(): void { - $roles = []; + /** @var UserRoleEnum $role */ foreach (UserRoleEnum::cases() as $role) { - $roles[] = $role->value; - } - - /** @var string $role */ - foreach ($roles as $role) { - if (null === UserRole::where('title', $role)->first()) { + if (null === UserRole::where('title', $role->value)->first()) { try { - UserRole::create(['title' => $role]); + UserRole::create(['title' => $role->value]); } catch (PDOException $e) { // @ignoreException } diff --git a/database/seeders/WebhookDataSeeder.php b/database/seeders/WebhookDataSeeder.php new file mode 100644 index 0000000000..75b46820a7 --- /dev/null +++ b/database/seeders/WebhookDataSeeder.php @@ -0,0 +1,48 @@ +value)->where('title', $trigger->name)->first()) { + try { + WebhookTriggerModel::create(['key' => $trigger->value, 'title' => $trigger->name]); + } catch (\PDOException $e) { + // @ignoreException + } + } + } + foreach (WebhookResponse::cases() as $response) { + if (null === WebhookResponseModel::where('key', $response->value)->where('title', $response->name)->first()) { + try { + WebhookResponseModel::create(['key' => $response->value, 'title' => $response->name]); + } catch (\PDOException $e) { + // @ignoreException + } + } + } + foreach (WebhookDelivery::cases() as $delivery) { + if (null === WebhookDeliveryModel::where('key', $delivery->value)->where('title', $delivery->name)->first()) { + try { + WebhookDeliveryModel::create(['key' => $delivery->value, 'title' => $delivery->name]); + } catch (\PDOException $e) { + // @ignoreException + } + } + } + } +} From 01cce49070f110414ae86e2ca875e3f0fcc84727 Mon Sep 17 00:00:00 2001 From: James Cole Date: Wed, 20 Aug 2025 06:22:55 +0200 Subject: [PATCH 002/111] Expand webhook API, edit and create screen. --- .../V1/Controllers/Webhook/ShowController.php | 7 ++ .../Controllers/Webhook/StoreController.php | 11 ++ .../Controllers/Webhook/UpdateController.php | 27 +++-- .../Requests/Models/Webhook/CreateRequest.php | 114 ++++++++++-------- .../Requests/Models/Webhook/UpdateRequest.php | 110 +++++++++-------- app/Enums/WebhookResponse.php | 1 + .../Webhook/WebhookRepository.php | 80 +++++++++++- config/webhooks.php | 79 ++++++++++++ public/v1/js/.gitkeep | 0 .../src/components/form/WebhookDelivery.vue | 4 +- .../src/components/form/WebhookResponse.vue | 4 +- .../v1/src/components/form/WebhookTrigger.vue | 9 +- .../v1/src/components/webhooks/Create.vue | 42 +++---- .../v1/src/components/webhooks/Edit.vue | 88 ++++---------- .../v1/src/components/webhooks/Index.vue | 28 +++-- .../v1/src/components/webhooks/Show.vue | 42 +++++-- resources/lang/en_US/firefly.php | 4 +- resources/lang/en_US/validation.php | 3 + 18 files changed, 421 insertions(+), 232 deletions(-) create mode 100644 config/webhooks.php mode change 100644 => 100755 public/v1/js/.gitkeep diff --git a/app/Api/V1/Controllers/Webhook/ShowController.php b/app/Api/V1/Controllers/Webhook/ShowController.php index cfc2b1ab39..69be75a49a 100644 --- a/app/Api/V1/Controllers/Webhook/ShowController.php +++ b/app/Api/V1/Controllers/Webhook/ShowController.php @@ -126,6 +126,13 @@ class ShowController extends Controller Log::channel('audit')->info(sprintf('User views webhook #%d.', $webhook->id)); $manager = $this->getManager(); + // enrich + /** @var User $admin */ + $admin = auth()->user(); + $enrichment = new WebhookEnrichment(); + $enrichment->setUser($admin); + $webhook = $enrichment->enrichSingle($webhook); + /** @var WebhookTransformer $transformer */ $transformer = app(WebhookTransformer::class); $transformer->setParameters($this->parameters); diff --git a/app/Api/V1/Controllers/Webhook/StoreController.php b/app/Api/V1/Controllers/Webhook/StoreController.php index 77ea1de82a..12c7b1bf75 100644 --- a/app/Api/V1/Controllers/Webhook/StoreController.php +++ b/app/Api/V1/Controllers/Webhook/StoreController.php @@ -27,7 +27,9 @@ namespace FireflyIII\Api\V1\Controllers\Webhook; use FireflyIII\Api\V1\Controllers\Controller; use FireflyIII\Api\V1\Requests\Models\Webhook\CreateRequest; use FireflyIII\Repositories\Webhook\WebhookRepositoryInterface; +use FireflyIII\Support\JsonApi\Enrichments\WebhookEnrichment; use FireflyIII\Transformers\WebhookTransformer; +use FireflyIII\User; use Illuminate\Http\JsonResponse; use Illuminate\Support\Facades\Log; use League\Fractal\Resource\Item; @@ -68,6 +70,15 @@ class StoreController extends Controller } $webhook = $this->repository->store($data); + + // enrich + /** @var User $admin */ + $admin = auth()->user(); + $enrichment = new WebhookEnrichment(); + $enrichment->setUser($admin); + $webhook = $enrichment->enrichSingle($webhook); + + $manager = $this->getManager(); Log::channel('audit')->info('User stores new webhook', $data); diff --git a/app/Api/V1/Controllers/Webhook/UpdateController.php b/app/Api/V1/Controllers/Webhook/UpdateController.php index 7e2e0e2353..96411d5ca1 100644 --- a/app/Api/V1/Controllers/Webhook/UpdateController.php +++ b/app/Api/V1/Controllers/Webhook/UpdateController.php @@ -24,15 +24,17 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Controllers\Webhook; -use FireflyIII\Api\V1\Controllers\Controller; -use FireflyIII\Api\V1\Requests\Models\Webhook\UpdateRequest; -use FireflyIII\Models\Webhook; -use FireflyIII\Repositories\Webhook\WebhookRepositoryInterface; -use FireflyIII\Transformers\WebhookTransformer; -use Illuminate\Http\JsonResponse; -use Illuminate\Support\Facades\Log; -use League\Fractal\Resource\Item; -use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; + use FireflyIII\Api\V1\Controllers\Controller; + use FireflyIII\Api\V1\Requests\Models\Webhook\UpdateRequest; + use FireflyIII\Models\Webhook; + use FireflyIII\Repositories\Webhook\WebhookRepositoryInterface; + use FireflyIII\Support\JsonApi\Enrichments\WebhookEnrichment; + use FireflyIII\Transformers\WebhookTransformer; + use FireflyIII\User; + use Illuminate\Http\JsonResponse; + use Illuminate\Support\Facades\Log; + use League\Fractal\Resource\Item; + use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** * Class UpdateController @@ -70,6 +72,13 @@ class UpdateController extends Controller $webhook = $this->repository->update($webhook, $data); $manager = $this->getManager(); + // enrich + /** @var User $admin */ + $admin = auth()->user(); + $enrichment = new WebhookEnrichment(); + $enrichment->setUser($admin); + $webhook = $enrichment->enrichSingle($webhook); + Log::channel('audit')->info(sprintf('User updates webhook #%d', $webhook->id), $data); /** @var WebhookTransformer $transformer */ diff --git a/app/Api/V1/Requests/Models/Webhook/CreateRequest.php b/app/Api/V1/Requests/Models/Webhook/CreateRequest.php index 66064a0e19..51bef14ad3 100644 --- a/app/Api/V1/Requests/Models/Webhook/CreateRequest.php +++ b/app/Api/V1/Requests/Models/Webhook/CreateRequest.php @@ -24,8 +24,7 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Requests\Models\Webhook; -use FireflyIII\Enums\WebhookResponse; -use FireflyIII\Enums\WebhookTrigger; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\Webhook; use FireflyIII\Rules\IsBoolean; use FireflyIII\Support\Request\ChecksLogin; @@ -44,24 +43,24 @@ class CreateRequest extends FormRequest public function getData(): array { - $triggers = Webhook::getTriggersForValidation(); - $responses = Webhook::getResponsesForValidation(); - $deliveries = Webhook::getDeliveriesForValidation(); - - $fields = [ - 'title' => ['title', 'convertString'], - 'active' => ['active', 'boolean'], - 'trigger' => ['trigger', 'convertString'], - 'response' => ['response', 'convertString'], - 'delivery' => ['delivery', 'convertString'], - 'url' => ['url', 'convertString'], + $fields = [ + 'title' => ['title', 'convertString'], + 'active' => ['active', 'boolean'], + 'url' => ['url', 'convertString'], ]; + $triggers = $this->get('triggers', []); + $responses = $this->get('responses', []); + $deliveries = $this->get('deliveries', []); - // this is the way. - $return = $this->getAllData($fields); - $return['trigger'] = $triggers[$return['trigger']] ?? (int)$return['trigger']; - $return['response'] = $responses[$return['response']] ?? (int)$return['response']; - $return['delivery'] = $deliveries[$return['delivery']] ?? (int)$return['delivery']; + if (0 === count($triggers) || 0 === count($responses) || 0 === count($deliveries)) { + throw new FireflyException('Unexpectedly got no responses, triggers or deliveries.'); + } + + + $return = $this->getAllData($fields); + $return['triggers'] = $triggers; + $return['responses'] = $responses; + $return['deliveries'] = $deliveries; return $return; } @@ -71,18 +70,24 @@ class CreateRequest extends FormRequest */ public function rules(): array { - $triggers = implode(',', array_keys(Webhook::getTriggersForValidation())); - $responses = implode(',', array_keys(Webhook::getResponsesForValidation())); - $deliveries = implode(',', array_keys(Webhook::getDeliveriesForValidation())); + $triggers = implode(',', array_values(Webhook::getTriggers())); + $responses = implode(',', array_values(Webhook::getResponses())); + $deliveries = implode(',', array_values(Webhook::getDeliveries())); $validProtocols = config('firefly.valid_url_protocols'); return [ - 'title' => 'required|min:1|max:255|uniqueObjectForUser:webhooks,title', - 'active' => [new IsBoolean()], - 'trigger' => sprintf('required|in:%s', $triggers), - 'response' => sprintf('required|in:%s', $responses), - 'delivery' => sprintf('required|in:%s', $deliveries), - 'url' => ['required', sprintf('url:%s', $validProtocols), 'uniqueWebhook'], + 'title' => 'required|min:1|max:255|uniqueObjectForUser:webhooks,title', + 'active' => [new IsBoolean()], + 'trigger' => 'prohibited', + 'triggers' => 'required|array|min:1|max:10', + 'triggers.*' => sprintf('required|in:%s', $triggers), + 'response' => 'prohibited', + 'responses' => 'required|array|min:1|max:1', + 'responses.*' => sprintf('required|in:%s', $responses), + 'delivery' => 'prohibited', + 'deliveries' => 'required|array|min:1|max:1', + 'deliveries.*' => sprintf('required|in:%s', $deliveries), + 'url' => ['required', sprintf('url:%s', $validProtocols)], ]; } @@ -91,37 +96,44 @@ class CreateRequest extends FormRequest $validator->after( function (Validator $validator): void { Log::debug('Validating webhook'); + if ($validator->failed()) { + return; + } $data = $validator->getData(); - $trigger = $data['trigger'] ?? null; - $response = $data['response'] ?? null; - if (null === $trigger || null === $response) { + $triggers = $data['triggers'] ?? []; + $responses = $data['responses'] ?? []; + + if (0 === count($triggers) || 0 === count($responses)) { Log::debug('No trigger or response, return.'); return; } - $triggers = array_keys(Webhook::getTriggersForValidation()); - $responses = array_keys(Webhook::getResponsesForValidation()); - if (!in_array($trigger, $triggers, true) || !in_array($response, $responses, true)) { - return; + $validTriggers = array_values(Webhook::getTriggers()); + $validResponses = array_values(Webhook::getResponses()); + foreach ($triggers as $trigger) { + if (!in_array($trigger, $validTriggers, true)) { + return; + } } - // cannot deliver budget info. - if (is_int($trigger)) { - Log::debug(sprintf('Trigger was integer (%d).', $trigger)); - $trigger = WebhookTrigger::from($trigger)->name; + foreach ($responses as $response) { + if (!in_array($response, $validResponses, true)) { + return; + } } - if (is_int($response)) { - Log::debug(sprintf('Response was integer (%d).', $response)); - $response = WebhookResponse::from($response)->name; - } - Log::debug(sprintf('Trigger is %s, response is %s', $trigger, $response)); - if (str_contains($trigger, 'TRANSACTION') && str_contains($response, 'BUDGET')) { - $validator->errors()->add('response', trans('validation.webhook_budget_info')); - } - if (str_contains($trigger, 'BUDGET') && str_contains($response, 'ACCOUNT')) { - $validator->errors()->add('response', trans('validation.webhook_account_info')); - } - if (str_contains($trigger, 'BUDGET') && str_contains($response, 'TRANSACTION')) { - $validator->errors()->add('response', trans('validation.webhook_transaction_info')); + // some combinations are illegal. + foreach ($triggers as $i => $trigger) { + $forbidden = config(sprintf('webhooks.forbidden_responses.%s', $trigger)); + if (null === $forbidden) { + $validator->errors()->add(sprintf('triggers.%d', $i), trans('validation.unknown_webhook_trigger', ['trigger' => $trigger,])); + continue; + } + foreach ($responses as $ii => $response) { + if (in_array($response, $forbidden, true)) { + Log::debug(sprintf('Trigger %s and response %s are forbidden.', $trigger, $response)); + $validator->errors()->add(sprintf('responses.%d', $ii), trans('validation.bad_webhook_combination', ['trigger' => $trigger, 'response' => $response,])); + return; + } + } } } ); diff --git a/app/Api/V1/Requests/Models/Webhook/UpdateRequest.php b/app/Api/V1/Requests/Models/Webhook/UpdateRequest.php index d235ca048c..a3c2eed05a 100644 --- a/app/Api/V1/Requests/Models/Webhook/UpdateRequest.php +++ b/app/Api/V1/Requests/Models/Webhook/UpdateRequest.php @@ -26,6 +26,7 @@ namespace FireflyIII\Api\V1\Requests\Models\Webhook; use FireflyIII\Enums\WebhookResponse; use FireflyIII\Enums\WebhookTrigger; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\Webhook; use FireflyIII\Rules\IsBoolean; use FireflyIII\Support\Request\ChecksLogin; @@ -44,35 +45,25 @@ class UpdateRequest extends FormRequest public function getData(): array { - $triggers = Webhook::getTriggersForValidation(); - $responses = Webhook::getResponsesForValidation(); - $deliveries = Webhook::getDeliveriesForValidation(); - $fields = [ 'title' => ['title', 'convertString'], 'active' => ['active', 'boolean'], - 'trigger' => ['trigger', 'convertString'], - 'response' => ['response', 'convertString'], - 'delivery' => ['delivery', 'convertString'], 'url' => ['url', 'convertString'], ]; - // this is the way. - $return = $this->getAllData($fields); - if (array_key_exists('trigger', $return)) { - $return['trigger'] = $triggers[$return['trigger']] ?? 0; - } - if (array_key_exists('response', $return)) { - $return['response'] = $responses[$return['response']] ?? 0; - } - if (array_key_exists('delivery', $return)) { - $return['delivery'] = $deliveries[$return['delivery']] ?? 0; - } - $return['secret'] = null !== $this->get('secret'); - if (null !== $this->get('title')) { - $return['title'] = $this->convertString('title'); + $triggers = $this->get('triggers', []); + $responses = $this->get('responses', []); + $deliveries = $this->get('deliveries', []); + + if (0 === count($triggers) || 0 === count($responses) || 0 === count($deliveries)) { + throw new FireflyException('Unexpectedly got no responses, triggers or deliveries.'); } + $return = $this->getAllData($fields); + $return['triggers'] = $triggers; + $return['responses'] = $responses; + $return['deliveries'] = $deliveries; + return $return; } @@ -81,9 +72,9 @@ class UpdateRequest extends FormRequest */ public function rules(): array { - $triggers = implode(',', array_keys(Webhook::getTriggersForValidation())); - $responses = implode(',', array_keys(Webhook::getResponsesForValidation())); - $deliveries = implode(',', array_keys(Webhook::getDeliveriesForValidation())); + $triggers = implode(',', array_values(Webhook::getTriggers())); + $responses = implode(',', array_values(Webhook::getResponses())); + $deliveries = implode(',', array_values(Webhook::getDeliveries())); $validProtocols = config('firefly.valid_url_protocols'); /** @var Webhook $webhook */ @@ -92,9 +83,17 @@ class UpdateRequest extends FormRequest return [ 'title' => sprintf('min:1|max:255|uniqueObjectForUser:webhooks,title,%d', $webhook->id), 'active' => [new IsBoolean()], - 'trigger' => sprintf('in:%s', $triggers), - 'response' => sprintf('in:%s', $responses), - 'delivery' => sprintf('in:%s', $deliveries), + + 'trigger' => 'prohibited', + 'triggers' => 'required|array|min:1|max:10', + 'triggers.*' => sprintf('required|in:%s', $triggers), + 'response' => 'prohibited', + 'responses' => 'required|array|min:1|max:1', + 'responses.*' => sprintf('required|in:%s', $responses), + 'delivery' => 'prohibited', + 'deliveries' => 'required|array|min:1|max:1', + 'deliveries.*' => sprintf('required|in:%s', $deliveries), + 'url' => [sprintf('url:%s', $validProtocols), sprintf('uniqueExistingWebhook:%d', $webhook->id)], ]; } @@ -104,38 +103,47 @@ class UpdateRequest extends FormRequest $validator->after( function (Validator $validator): void { Log::debug('Validating webhook'); + + if ($validator->failed()) { + return; + } $data = $validator->getData(); - $trigger = $data['trigger'] ?? null; - $response = $data['response'] ?? null; - if (null === $trigger || null === $response) { + $triggers = $data['triggers'] ?? []; + $responses = $data['responses'] ?? []; + + if (0 === count($triggers) || 0 === count($responses)) { Log::debug('No trigger or response, return.'); return; } - $triggers = array_keys(Webhook::getTriggersForValidation()); - $responses = array_keys(Webhook::getResponsesForValidation()); - if (!in_array($trigger, $triggers, true) || !in_array($response, $responses, true)) { - return; + $validTriggers = array_values(Webhook::getTriggers()); + $validResponses = array_values(Webhook::getResponses()); + foreach ($triggers as $trigger) { + if (!in_array($trigger, $validTriggers, true)) { + return; + } } - // cannot deliver budget info. - if (is_int($trigger)) { - Log::debug(sprintf('Trigger was integer (%d).', $trigger)); - $trigger = WebhookTrigger::from($trigger)->name; + foreach ($responses as $response) { + if (!in_array($response, $validResponses, true)) { + return; + } } - if (is_int($response)) { - Log::debug(sprintf('Response was integer (%d).', $response)); - $response = WebhookResponse::from($response)->name; - } - Log::debug(sprintf('Trigger is %s, response is %s', $trigger, $response)); - if (str_contains($trigger, 'TRANSACTION') && str_contains($response, 'BUDGET')) { - $validator->errors()->add('response', trans('validation.webhook_budget_info')); - } - if (str_contains($trigger, 'BUDGET') && str_contains($response, 'ACCOUNT')) { - $validator->errors()->add('response', trans('validation.webhook_account_info')); - } - if (str_contains($trigger, 'BUDGET') && str_contains($response, 'TRANSACTION')) { - $validator->errors()->add('response', trans('validation.webhook_transaction_info')); + // some combinations are illegal. + foreach ($triggers as $i => $trigger) { + $forbidden = config(sprintf('webhooks.forbidden_responses.%s', $trigger)); + if (null === $forbidden) { + $validator->errors()->add(sprintf('triggers.%d', $i), trans('validation.unknown_webhook_trigger', ['trigger' => $trigger,])); + continue; + } + foreach ($responses as $ii => $response) { + if (in_array($response, $forbidden, true)) { + Log::debug(sprintf('Trigger %s and response %s are forbidden.', $trigger, $response)); + $validator->errors()->add(sprintf('responses.%d', $ii), trans('validation.bad_webhook_combination', ['trigger' => $trigger, 'response' => $response,])); + return; + } + } } + } ); } diff --git a/app/Enums/WebhookResponse.php b/app/Enums/WebhookResponse.php index ead11117a0..f1dfa213e6 100644 --- a/app/Enums/WebhookResponse.php +++ b/app/Enums/WebhookResponse.php @@ -32,5 +32,6 @@ enum WebhookResponse: int case TRANSACTIONS = 200; case ACCOUNTS = 210; case BUDGET = 230; + case RELEVANT = 240; case NONE = 220; } diff --git a/app/Repositories/Webhook/WebhookRepository.php b/app/Repositories/Webhook/WebhookRepository.php index 64f6230ac3..7a54e0fd7c 100644 --- a/app/Repositories/Webhook/WebhookRepository.php +++ b/app/Repositories/Webhook/WebhookRepository.php @@ -24,9 +24,13 @@ declare(strict_types=1); namespace FireflyIII\Repositories\Webhook; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\Webhook; use FireflyIII\Models\WebhookAttempt; +use FireflyIII\Models\WebhookDelivery; use FireflyIII\Models\WebhookMessage; +use FireflyIII\Models\WebhookResponse; +use FireflyIII\Models\WebhookTrigger; use FireflyIII\Support\Repositories\UserGroup\UserGroupInterface; use FireflyIII\Support\Repositories\UserGroup\UserGroupTrait; use Illuminate\Support\Collection; @@ -105,26 +109,92 @@ class WebhookRepository implements WebhookRepositoryInterface, UserGroupInterfac 'secret' => $secret, 'url' => $data['url'], ]; + /** @var Webhook $webhook */ + $webhook = Webhook::create($fullData); + $triggers = new Collection(); + $responses = new Collection(); + $deliveries = new Collection(); - return Webhook::create($fullData); + foreach ($data['triggers'] as $trigger) { + // get the relevant ID: + $object = WebhookTrigger::where('title', $trigger)->first(); + if (null === $object) { + throw new FireflyException(sprintf('Could not find webhook trigger with title "%s".', $trigger)); + } + $triggers->push($object); + } + $webhook->webhookTriggers()->saveMany($triggers); + + foreach ($data['responses'] as $response) { + // get the relevant ID: + $object = WebhookResponse::where('title', $response)->first(); + if (null === $object) { + throw new FireflyException(sprintf('Could not find webhook response with title "%s".', $response)); + } + $responses->push($object); + } + $webhook->webhookResponses()->saveMany($responses); + + foreach ($data['deliveries'] as $delivery) { + // get the relevant ID: + $object = WebhookDelivery::where('title', $delivery)->first(); + if (null === $object) { + throw new FireflyException(sprintf('Could not find webhook delivery with title "%s".', $delivery)); + } + $deliveries->push($object); + } + $webhook->webhookDeliveries()->saveMany($deliveries); + + return $webhook; } public function update(Webhook $webhook, array $data): Webhook { $webhook->active = $data['active'] ?? $webhook->active; -// $webhook->trigger = $data['trigger'] ?? $webhook->trigger; -// $webhook->response = $data['response'] ?? $webhook->response; -// $webhook->delivery = $data['delivery'] ?? $webhook->delivery; $webhook->title = $data['title'] ?? $webhook->title; $webhook->url = $data['url'] ?? $webhook->url; - if (true === $data['secret']) { + if (array_key_exists('secret', $data) && true === $data['secret']) { $secret = Str::random(24); $webhook->secret = $secret; } $webhook->save(); + $triggers = new Collection(); + $responses = new Collection(); + $deliveries = new Collection(); + + foreach ($data['triggers'] as $trigger) { + // get the relevant ID: + $object = WebhookTrigger::where('title', $trigger)->first(); + if (null === $object) { + throw new FireflyException(sprintf('Could not find webhook trigger with title "%s".', $trigger)); + } + $triggers->push($object); + } + $webhook->webhookTriggers()->sync($triggers); + + foreach ($data['responses'] as $response) { + // get the relevant ID: + $object = WebhookResponse::where('title', $response)->first(); + if (null === $object) { + throw new FireflyException(sprintf('Could not find webhook response with title "%s".', $response)); + } + $responses->push($object); + } + $webhook->webhookResponses()->sync($responses); + + foreach ($data['deliveries'] as $delivery) { + // get the relevant ID: + $object = WebhookDelivery::where('title', $delivery)->first(); + if (null === $object) { + throw new FireflyException(sprintf('Could not find webhook delivery with title "%s".', $delivery)); + } + $deliveries->push($object); + } + $webhook->webhookDeliveries()->sync($deliveries); + return $webhook; } } diff --git a/config/webhooks.php b/config/webhooks.php new file mode 100644 index 0000000000..49eaff18cd --- /dev/null +++ b/config/webhooks.php @@ -0,0 +1,79 @@ + [ + WebhookTrigger::STORE_TRANSACTION->name => [ + WebhookTrigger::STORE_BUDGET->name, + WebhookTrigger::UPDATE_BUDGET->name, + WebhookTrigger::DESTROY_BUDGET->name, + WebhookTrigger::STORE_UPDATE_BUDGET_LIMIT->name, + + ], + WebhookTrigger::UPDATE_TRANSACTION->name => [ + WebhookTrigger::STORE_BUDGET->name, + WebhookTrigger::UPDATE_BUDGET->name, + WebhookTrigger::DESTROY_BUDGET->name, + WebhookTrigger::STORE_UPDATE_BUDGET_LIMIT->name, + ], + WebhookTrigger::DESTROY_TRANSACTION->name => [ + WebhookTrigger::STORE_BUDGET->name, + WebhookTrigger::UPDATE_BUDGET->name, + WebhookTrigger::DESTROY_BUDGET->name, + WebhookTrigger::STORE_UPDATE_BUDGET_LIMIT->name, + ], + WebhookTrigger::STORE_BUDGET->name => [ + WebhookTrigger::STORE_TRANSACTION->name, + WebhookTrigger::UPDATE_TRANSACTION->name, + WebhookTrigger::DESTROY_TRANSACTION->name, + + ], + WebhookTrigger::UPDATE_BUDGET->name => [ + WebhookTrigger::STORE_TRANSACTION->name, + WebhookTrigger::UPDATE_TRANSACTION->name, + WebhookTrigger::DESTROY_TRANSACTION->name, + ], + WebhookTrigger::DESTROY_BUDGET->name => [ + WebhookTrigger::STORE_TRANSACTION->name, + WebhookTrigger::UPDATE_TRANSACTION->name, + WebhookTrigger::DESTROY_TRANSACTION->name, + ], + WebhookTrigger::STORE_UPDATE_BUDGET_LIMIT->name => [ + WebhookTrigger::STORE_TRANSACTION->name, + WebhookTrigger::UPDATE_TRANSACTION->name, + WebhookTrigger::DESTROY_TRANSACTION->name, + ], + ], + 'forbidden_responses' => [ + WebhookTrigger::STORE_TRANSACTION->name => [ + WebhookResponse::BUDGET->name, + ], + WebhookTrigger::UPDATE_TRANSACTION->name => [ + WebhookResponse::BUDGET->name, + ], + WebhookTrigger::DESTROY_TRANSACTION->name => [ + WebhookResponse::BUDGET->name, + ], + WebhookTrigger::STORE_BUDGET->name => [ + WebhookResponse::TRANSACTIONS->name, + WebhookResponse::ACCOUNTS->name, + + ], + WebhookTrigger::UPDATE_BUDGET->name => [ + WebhookResponse::TRANSACTIONS->name, + WebhookResponse::ACCOUNTS->name, + ], + WebhookTrigger::DESTROY_BUDGET->name => [ + WebhookResponse::TRANSACTIONS->name, + WebhookResponse::ACCOUNTS->name, + ], + WebhookTrigger::STORE_UPDATE_BUDGET_LIMIT->name => [ + WebhookResponse::TRANSACTIONS->name, + WebhookResponse::ACCOUNTS->name, + ], + ] +]; diff --git a/public/v1/js/.gitkeep b/public/v1/js/.gitkeep old mode 100644 new mode 100755 diff --git a/resources/assets/v1/src/components/form/WebhookDelivery.vue b/resources/assets/v1/src/components/form/WebhookDelivery.vue index ef844cdb1d..ff184466ac 100644 --- a/resources/assets/v1/src/components/form/WebhookDelivery.vue +++ b/resources/assets/v1/src/components/form/WebhookDelivery.vue @@ -68,7 +68,7 @@ export default { } }, value: { - type: Number, + type: String, required: true, } }, @@ -84,7 +84,7 @@ export default { } this.deliveries.push( { - id: response.data.data.value[key], + id: key, name: this.$t('firefly.webhook_delivery_' + key), } ); diff --git a/resources/assets/v1/src/components/form/WebhookResponse.vue b/resources/assets/v1/src/components/form/WebhookResponse.vue index 754c29e609..1ce8eb32b9 100644 --- a/resources/assets/v1/src/components/form/WebhookResponse.vue +++ b/resources/assets/v1/src/components/form/WebhookResponse.vue @@ -66,7 +66,7 @@ export default { } }, value: { - type: Number, + type: String, required: true, } }, @@ -88,7 +88,7 @@ export default { } this.responses.push( { - id: response.data.data.value[key], + id: key, name: this.$t('firefly.webhook_response_' + key), } ); diff --git a/resources/assets/v1/src/components/form/WebhookTrigger.vue b/resources/assets/v1/src/components/form/WebhookTrigger.vue index c62d75a44f..0a13381b03 100644 --- a/resources/assets/v1/src/components/form/WebhookTrigger.vue +++ b/resources/assets/v1/src/components/form/WebhookTrigger.vue @@ -29,6 +29,7 @@ - + {{ $t('list.secret') }} + 'After budgeted amount change', 'webhook_response_TRANSACTIONS' => 'Transaction details', 'webhook_response_ACCOUNTS' => 'Account details', - 'webhook_response_none_BUDGET' => 'Budget details', - 'webhook_response_none_NONE' => 'No details', + 'webhook_response_BUDGET' => 'Budget details', + 'webhook_response_RELEVANT' => 'Relevant details', 'webhook_response_NONE' => 'No details', 'webhook_delivery_JSON' => 'JSON', 'inspect' => 'Inspect', diff --git a/resources/lang/en_US/validation.php b/resources/lang/en_US/validation.php index 91d04df320..33ec3b34e8 100644 --- a/resources/lang/en_US/validation.php +++ b/resources/lang/en_US/validation.php @@ -34,6 +34,9 @@ return [ 'filter_not_string' => 'Filter ":filter" is expected to be a string of text', 'bad_api_filter' => 'This API endpoint does not support ":filter" as a filter.', 'nog_logged_in' => 'You are not logged in.', + 'prohibited' => 'You must not submit anything in field.', + 'bad_webhook_combination' => 'Webhook trigger ":trigger" cannot be combined with webhook response ":response".', + 'unknown_webhook_trigger' => 'Unknown webhook trigger ":trigger".', 'bad_type_source' => 'Firefly III can\'t determine the transaction type based on this source account.', 'bad_type_destination' => 'Firefly III can\'t determine the transaction type based on this destination account.', 'missing_where' => 'Array is missing "where"-clause', From bcaa0bddeaea23e9c01c51d4901a0b8f9dbf4a48 Mon Sep 17 00:00:00 2001 From: James Cole Date: Wed, 20 Aug 2025 06:32:25 +0200 Subject: [PATCH 003/111] Update message generator. --- .../Webhook/StandardMessageGenerator.php | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/app/Generator/Webhook/StandardMessageGenerator.php b/app/Generator/Webhook/StandardMessageGenerator.php index 6143aee75f..30ba0a54d2 100644 --- a/app/Generator/Webhook/StandardMessageGenerator.php +++ b/app/Generator/Webhook/StandardMessageGenerator.php @@ -30,6 +30,7 @@ use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\Budget; use FireflyIII\Models\BudgetLimit; use FireflyIII\Models\Transaction; +use FireflyIII\Models\WebhookResponse as WebhookResponseModel; use FireflyIII\Models\TransactionGroup; use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\Webhook; @@ -123,14 +124,15 @@ class StandardMessageGenerator implements MessageGeneratorInterface 'uuid' => $uuid->toString(), 'user_id' => 0, 'user_group_id' => 0, - 'trigger' => WebhookTrigger::from((int)$webhook->trigger)->name, - 'response' => WebhookResponse::from((int)$webhook->response)->name, + 'trigger' => $this->trigger->name, + 'response' => $webhook->webhookResponses()->first()->title, // guess that the database is correct. 'url' => $webhook->url, 'version' => sprintf('v%d', $this->getVersion()), 'content' => [], ]; // depends on the model how user_id is set: + $relevantResponse = WebhookResponse::TRANSACTIONS->name; switch ($class) { default: // Line is ignored because all of Firefly III's Models have an id property. @@ -142,12 +144,14 @@ class StandardMessageGenerator implements MessageGeneratorInterface /** @var Budget $model */ $basicMessage['user_id'] = $model->user_id; $basicMessage['user_group_id'] = $model->user_group_id; + $relevantResponse = WebhookResponse::BUDGET->name; break; case BudgetLimit::class: $basicMessage['user_id'] = $model->budget->user_id; $basicMessage['user_group_id'] = $model->budget->user_group_id; + $relevantResponse = WebhookResponse::BUDGET->name; break; @@ -160,7 +164,17 @@ class StandardMessageGenerator implements MessageGeneratorInterface } // then depends on the response what to put in the message: - switch ($webhook->response) { + /** @var WebhookResponseModel $response */ + $model = $webhook->webhookResponses()->first(); + $response = $model->title; + // if it's relevant, just switch to another. + if(WebhookResponse::RELEVANT->name === $response) { + // switch to whatever is actually relevant. + $response = $relevantResponse; + } + + + switch ($response) { default: Log::error(sprintf('The response code for webhook #%d is "%d" and the message generator cant handle it. Soft fail.', $webhook->id, $webhook->response)); @@ -177,12 +191,6 @@ class StandardMessageGenerator implements MessageGeneratorInterface } if ($model instanceof BudgetLimit) { $user = $model->budget->user; - $enrichment = new BudgetEnrichment(); - $enrichment->setUser($user); - $enrichment->setStart($model->start_date); - $enrichment->setEnd($model->end_date); - $budget = $enrichment->enrichSingle($model->budget); - $enrichment = new BudgetLimitEnrichment(); $enrichment->setUser($user); From 4ee9f9bb27573592df683e0d96d31ace0f66ef8a Mon Sep 17 00:00:00 2001 From: James Cole Date: Wed, 20 Aug 2025 06:38:53 +0200 Subject: [PATCH 004/111] Fix collection and message generation. --- .../Webhook/StandardMessageGenerator.php | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/app/Generator/Webhook/StandardMessageGenerator.php b/app/Generator/Webhook/StandardMessageGenerator.php index 30ba0a54d2..6877e7581c 100644 --- a/app/Generator/Webhook/StandardMessageGenerator.php +++ b/app/Generator/Webhook/StandardMessageGenerator.php @@ -81,7 +81,12 @@ class StandardMessageGenerator implements MessageGeneratorInterface private function getWebhooks(): Collection { - return $this->user->webhooks()->where('active', true)->where('trigger', $this->trigger)->get(['webhooks.*']); + return $this->user->webhooks() + ->leftJoin('webhook_webhook_trigger','webhook_webhook_trigger.webhook_id','webhooks.id') + ->leftJoin('webhook_triggers','webhook_webhook_trigger.webhook_trigger_id','webhook_triggers.id') + ->where('active', true) + ->where('webhook_triggers.title', $this->trigger->name) + ->get(['webhooks.*']); } /** @@ -164,23 +169,25 @@ class StandardMessageGenerator implements MessageGeneratorInterface } // then depends on the response what to put in the message: - /** @var WebhookResponseModel $response */ - $model = $webhook->webhookResponses()->first(); - $response = $model->title; + /** @var WebhookResponseModel $webhookResponse */ + $webhookResponse = $webhook->webhookResponses()->first(); + $response = $webhookResponse->title; + Log::debug(sprintf('Expected response for this webhook is "%s".', $response)); // if it's relevant, just switch to another. if(WebhookResponse::RELEVANT->name === $response) { // switch to whatever is actually relevant. $response = $relevantResponse; + Log::debug(sprintf('Expected response for this webhook is now "%s".', $response)); } switch ($response) { default: - Log::error(sprintf('The response code for webhook #%d is "%d" and the message generator cant handle it. Soft fail.', $webhook->id, $webhook->response)); + Log::error(sprintf('The response code for webhook #%d is "%s" and the message generator cant handle it. Soft fail.', $webhook->id, $webhook->response)); return; - case WebhookResponse::BUDGET->value: + case WebhookResponse::BUDGET->name: $basicMessage['content'] = []; if ($model instanceof Budget) { $enrichment = new BudgetEnrichment(); @@ -206,12 +213,12 @@ class StandardMessageGenerator implements MessageGeneratorInterface break; - case WebhookResponse::NONE->value: + case WebhookResponse::NONE->name: $basicMessage['content'] = []; break; - case WebhookResponse::TRANSACTIONS->value: + case WebhookResponse::TRANSACTIONS->name: /** @var TransactionGroup $model */ $transformer = new TransactionGroupTransformer(); @@ -228,7 +235,7 @@ class StandardMessageGenerator implements MessageGeneratorInterface break; - case WebhookResponse::ACCOUNTS->value: + case WebhookResponse::ACCOUNTS->name: /** @var TransactionGroup $model */ $accounts = $this->collectAccounts($model); $enrichment = new AccountEnrichment(); From a1595d064770dec68bbc7f036a949ca9ef7ae783 Mon Sep 17 00:00:00 2001 From: James Cole Date: Wed, 20 Aug 2025 07:26:18 +0200 Subject: [PATCH 005/111] Fix #10790 --- app/Support/JsonApi/Enrichments/RecurringEnrichment.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Support/JsonApi/Enrichments/RecurringEnrichment.php b/app/Support/JsonApi/Enrichments/RecurringEnrichment.php index 989e9ea924..0f86426117 100644 --- a/app/Support/JsonApi/Enrichments/RecurringEnrichment.php +++ b/app/Support/JsonApi/Enrichments/RecurringEnrichment.php @@ -137,7 +137,7 @@ class RecurringEnrichment implements EnrichmentInterface // get the (future) occurrences for this specific type of repetition: $amount = 'daily' === $repetition->repetition_type ? 9 : 5; $set = $repository->getXOccurrencesSince($repetition, $fromDate, now(config('app.timezone')), $amount); - + $occurrences = []; /** @var Carbon $carbon */ foreach ($set as $carbon) { $occurrences[] = $carbon->toAtomString(); From 46219c4678e21c23a1197a28909fc20b1ad75fd8 Mon Sep 17 00:00:00 2001 From: James Cole Date: Wed, 20 Aug 2025 09:09:38 +0200 Subject: [PATCH 006/111] Fix #10791 --- .../transactions/ForeignAmountSelect.vue | 31 ++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/resources/assets/v1/src/components/transactions/ForeignAmountSelect.vue b/resources/assets/v1/src/components/transactions/ForeignAmountSelect.vue index 3f61bec9ed..2df223b20e 100644 --- a/resources/assets/v1/src/components/transactions/ForeignAmountSelect.vue +++ b/resources/assets/v1/src/components/transactions/ForeignAmountSelect.vue @@ -83,15 +83,15 @@ export default { }, watch: { source: function () { - // console.log('ForeignAmountSelect watch source'); + console.log('ForeignAmountSelect watch source'); this.changeData(); }, destination: function () { - // console.log('ForeignAmountSelect watch destination'); + console.log('ForeignAmountSelect watch destination'); this.changeData(); }, transactionType: function () { - // console.log('ForeignAmountSelect watch transaction type (is now ' + this.transactionType + ')'); + console.log('ForeignAmountSelect watch transaction type (is now ' + this.transactionType + ')'); this.changeData(); } }, @@ -112,12 +112,10 @@ export default { amount: this.$refs.amount.value, currency_id: this.$refs.currency_select.value, }; - // console.log(obj); - this.$emit('input', obj - ); + this.$emit('input', obj); }, changeData: function () { - // console.log('ForeignAmountSelect changeData'); + console.log('ForeignAmountSelect changeData'); this.enabledCurrencies = []; let destType = this.destination.type ? this.destination.type.toLowerCase() : 'invalid'; let srcType = this.source.type ? this.source.type.toLowerCase() : 'invalid'; @@ -160,6 +158,7 @@ export default { } } } + this.checkSelection(); return; } @@ -172,6 +171,7 @@ export default { } } } + this.checkSelection(); return; } for (const key in this.currencies) { @@ -180,6 +180,23 @@ export default { } } }, + checkSelection: function () { + let selectedCurrency = this.$refs.currency_select.value; + let hasSelected = false; + for(const key in this.enabledCurrencies) { + if(parseInt(this.enabledCurrencies[key].id) === parseInt(selectedCurrency)) { + hasSelected = true; + } + } + if(false === hasSelected) { + let obj = { + amount: '', + currency_id: null, + }; + this.$emit('input', obj); + console.warn('Reset foreign amount.'); + } + }, loadCurrencies: function () { // console.log('loadCurrencies'); // reset list of currencies: From 6ddda13c3a1be6090bf8394cdc748eb43b7ecb9c Mon Sep 17 00:00:00 2001 From: James Cole Date: Wed, 20 Aug 2025 10:13:23 +0200 Subject: [PATCH 007/111] Fix #10794 --- app/Casts/SeparateTimezoneCaster.php | 5 ++++- app/Factory/RecurrenceFactory.php | 10 ++++++---- app/Http/Controllers/Recurring/EditController.php | 1 - .../JsonApi/Enrichments/RecurringEnrichment.php | 2 +- app/Support/Request/ConvertsDataTypes.php | 4 ++-- .../components/transactions/ForeignAmountSelect.vue | 6 +++--- 6 files changed, 16 insertions(+), 12 deletions(-) diff --git a/app/Casts/SeparateTimezoneCaster.php b/app/Casts/SeparateTimezoneCaster.php index aa1091885f..c318626ba6 100644 --- a/app/Casts/SeparateTimezoneCaster.php +++ b/app/Casts/SeparateTimezoneCaster.php @@ -28,6 +28,7 @@ namespace FireflyIII\Casts; use Carbon\Carbon; use Illuminate\Contracts\Database\Eloquent\CastsAttributes; use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Facades\Log; /** * Class SeparateTimezoneCaster @@ -50,7 +51,9 @@ class SeparateTimezoneCaster implements CastsAttributes } $timeZone = $attributes[sprintf('%s_tz', $key)] ?? config('app.timezone'); - return Carbon::parse($value, $timeZone)->setTimezone(config('app.timezone')); + $result = Carbon::parse($value, $timeZone)->setTimezone(config('app.timezone')); + Log::debug(sprintf('SeparateTimezoneCaster: %s.%s = %s', str_replace('FireflyIII\\Models\\','',get_class($model)), $key, $result->toAtomString())); + return $result; } /** diff --git a/app/Factory/RecurrenceFactory.php b/app/Factory/RecurrenceFactory.php index 8254a8a4ec..16cb6cb982 100644 --- a/app/Factory/RecurrenceFactory.php +++ b/app/Factory/RecurrenceFactory.php @@ -30,6 +30,7 @@ use FireflyIII\Models\Recurrence; use FireflyIII\Services\Internal\Support\RecurringTransactionTrait; use FireflyIII\Services\Internal\Support\TransactionTypeTrait; use FireflyIII\User; +use Illuminate\Support\Facades\Log; use Illuminate\Support\MessageBag; /** @@ -62,8 +63,8 @@ class RecurrenceFactory $type = $this->findTransactionType(ucfirst((string) $data['recurrence']['type'])); } catch (FireflyException $e) { $message = sprintf('Cannot make a recurring transaction of type "%s"', $data['recurrence']['type']); - app('log')->error($message); - app('log')->error($e->getTraceAsString()); + Log::error($message); + Log::error($e->getTraceAsString()); throw new FireflyException($message, 0, $e); } @@ -107,6 +108,7 @@ class RecurrenceFactory 'title' => $title, 'description' => $description, 'first_date' => $firstDate?->format('Y-m-d'), + 'first_date_tz' => $firstDate?->format('e'), 'repeat_until' => $repetitions > 0 ? null : $repeatUntilString, 'latest_date' => null, 'repetitions' => $repetitions, @@ -125,8 +127,8 @@ class RecurrenceFactory try { $this->createTransactions($recurrence, $data['transactions'] ?? []); } catch (FireflyException $e) { - app('log')->error($e->getMessage()); - app('log')->error($e->getTraceAsString()); + Log::error($e->getMessage()); + Log::error($e->getTraceAsString()); $recurrence->forceDelete(); $message = sprintf('Could not create recurring transaction: %s', $e->getMessage()); $this->errors->add('store', $message); diff --git a/app/Http/Controllers/Recurring/EditController.php b/app/Http/Controllers/Recurring/EditController.php index 59772aa1b4..b854ae99b8 100644 --- a/app/Http/Controllers/Recurring/EditController.php +++ b/app/Http/Controllers/Recurring/EditController.php @@ -105,7 +105,6 @@ class EditController extends Controller /** @var RecurrenceTransformer $transformer */ $transformer = app(RecurrenceTransformer::class); $transformer->setParameters(new ParameterBag()); - $array = $transformer->transform($recurrence); $budgets = ExpandedForm::makeSelectListWithEmpty($this->budgetRepos->getActiveBudgets()); $bills = ExpandedForm::makeSelectListWithEmpty($this->billRepository->getActiveBills()); diff --git a/app/Support/JsonApi/Enrichments/RecurringEnrichment.php b/app/Support/JsonApi/Enrichments/RecurringEnrichment.php index 0f86426117..5e6ca0e734 100644 --- a/app/Support/JsonApi/Enrichments/RecurringEnrichment.php +++ b/app/Support/JsonApi/Enrichments/RecurringEnrichment.php @@ -129,7 +129,7 @@ class RecurringEnrichment implements EnrichmentInterface $recurrence = $this->collection->filter(function (Recurrence $item) use ($repetition) { return (int)$item->id === (int)$repetition->recurrence_id; })->first(); - $fromDate = $recurrence->latest_date ?? $recurrence->first_date; + $fromDate = clone ($recurrence->latest_date ?? $recurrence->first_date); $id = (int)$repetition->recurrence_id; $repId = (int)$repetition->id; $this->repetitions[$id] ??= []; diff --git a/app/Support/Request/ConvertsDataTypes.php b/app/Support/Request/ConvertsDataTypes.php index 60e954945a..8162b5256f 100644 --- a/app/Support/Request/ConvertsDataTypes.php +++ b/app/Support/Request/ConvertsDataTypes.php @@ -255,7 +255,7 @@ trait ConvertsDataTypes if (10 === strlen((string) $value)) { // probably a date format. try { - $carbon = Carbon::createFromFormat('Y-m-d', $value); + $carbon = Carbon::createFromFormat('Y-m-d', $value,config('app.timezone')); } catch (InvalidDateException $e) { // @phpstan-ignore-line Log::error(sprintf('[1] "%s" is not a valid date: %s', $value, $e->getMessage())); @@ -276,7 +276,7 @@ trait ConvertsDataTypes // is an atom string, I hope? try { - $carbon = Carbon::parse($value); + $carbon = Carbon::parse($value, $value,config('app.timezone')); } catch (InvalidDateException $e) { // @phpstan-ignore-line Log::error(sprintf('[3] "%s" is not a valid date or time: %s', $value, $e->getMessage())); diff --git a/resources/assets/v1/src/components/transactions/ForeignAmountSelect.vue b/resources/assets/v1/src/components/transactions/ForeignAmountSelect.vue index 2df223b20e..fdcc48f80b 100644 --- a/resources/assets/v1/src/components/transactions/ForeignAmountSelect.vue +++ b/resources/assets/v1/src/components/transactions/ForeignAmountSelect.vue @@ -83,15 +83,15 @@ export default { }, watch: { source: function () { - console.log('ForeignAmountSelect watch source'); + // console.log('ForeignAmountSelect watch source'); this.changeData(); }, destination: function () { - console.log('ForeignAmountSelect watch destination'); + // console.log('ForeignAmountSelect watch destination'); this.changeData(); }, transactionType: function () { - console.log('ForeignAmountSelect watch transaction type (is now ' + this.transactionType + ')'); + // console.log('ForeignAmountSelect watch transaction type (is now ' + this.transactionType + ')'); this.changeData(); } }, From e4aff5ff4c94ff3a4f2493d46ad1d9d5583e55a9 Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 21 Aug 2025 20:07:12 +0200 Subject: [PATCH 008/111] Update webhook code. --- .../Requests/Models/Webhook/CreateRequest.php | 50 +-------- .../Requests/Models/Webhook/UpdateRequest.php | 52 +-------- app/Enums/WebhookTrigger.php | 1 + .../Webhook/StandardMessageGenerator.php | 105 +++++++++++------- app/Support/Request/ValidatesWebhooks.php | 69 ++++++++++++ config/webhooks.php | 7 +- resources/lang/en_US/firefly.php | 1 + resources/lang/en_US/validation.php | 1 + 8 files changed, 148 insertions(+), 138 deletions(-) create mode 100644 app/Support/Request/ValidatesWebhooks.php diff --git a/app/Api/V1/Requests/Models/Webhook/CreateRequest.php b/app/Api/V1/Requests/Models/Webhook/CreateRequest.php index 51bef14ad3..71093b3ec0 100644 --- a/app/Api/V1/Requests/Models/Webhook/CreateRequest.php +++ b/app/Api/V1/Requests/Models/Webhook/CreateRequest.php @@ -29,6 +29,7 @@ use FireflyIII\Models\Webhook; use FireflyIII\Rules\IsBoolean; use FireflyIII\Support\Request\ChecksLogin; use FireflyIII\Support\Request\ConvertsDataTypes; +use FireflyIII\Support\Request\ValidatesWebhooks; use Illuminate\Contracts\Validation\Validator; use Illuminate\Foundation\Http\FormRequest; use Illuminate\Support\Facades\Log; @@ -40,6 +41,7 @@ class CreateRequest extends FormRequest { use ChecksLogin; use ConvertsDataTypes; + use ValidatesWebhooks; public function getData(): array { @@ -90,52 +92,4 @@ class CreateRequest extends FormRequest 'url' => ['required', sprintf('url:%s', $validProtocols)], ]; } - - public function withValidator(Validator $validator): void - { - $validator->after( - function (Validator $validator): void { - Log::debug('Validating webhook'); - if ($validator->failed()) { - return; - } - $data = $validator->getData(); - $triggers = $data['triggers'] ?? []; - $responses = $data['responses'] ?? []; - - if (0 === count($triggers) || 0 === count($responses)) { - Log::debug('No trigger or response, return.'); - - return; - } - $validTriggers = array_values(Webhook::getTriggers()); - $validResponses = array_values(Webhook::getResponses()); - foreach ($triggers as $trigger) { - if (!in_array($trigger, $validTriggers, true)) { - return; - } - } - foreach ($responses as $response) { - if (!in_array($response, $validResponses, true)) { - return; - } - } - // some combinations are illegal. - foreach ($triggers as $i => $trigger) { - $forbidden = config(sprintf('webhooks.forbidden_responses.%s', $trigger)); - if (null === $forbidden) { - $validator->errors()->add(sprintf('triggers.%d', $i), trans('validation.unknown_webhook_trigger', ['trigger' => $trigger,])); - continue; - } - foreach ($responses as $ii => $response) { - if (in_array($response, $forbidden, true)) { - Log::debug(sprintf('Trigger %s and response %s are forbidden.', $trigger, $response)); - $validator->errors()->add(sprintf('responses.%d', $ii), trans('validation.bad_webhook_combination', ['trigger' => $trigger, 'response' => $response,])); - return; - } - } - } - } - ); - } } diff --git a/app/Api/V1/Requests/Models/Webhook/UpdateRequest.php b/app/Api/V1/Requests/Models/Webhook/UpdateRequest.php index a3c2eed05a..7dcebc8552 100644 --- a/app/Api/V1/Requests/Models/Webhook/UpdateRequest.php +++ b/app/Api/V1/Requests/Models/Webhook/UpdateRequest.php @@ -31,6 +31,7 @@ use FireflyIII\Models\Webhook; use FireflyIII\Rules\IsBoolean; use FireflyIII\Support\Request\ChecksLogin; use FireflyIII\Support\Request\ConvertsDataTypes; +use FireflyIII\Support\Request\ValidatesWebhooks; use Illuminate\Contracts\Validation\Validator; use Illuminate\Foundation\Http\FormRequest; use Illuminate\Support\Facades\Log; @@ -42,6 +43,7 @@ class UpdateRequest extends FormRequest { use ChecksLogin; use ConvertsDataTypes; + use ValidatesWebhooks; public function getData(): array { @@ -97,54 +99,4 @@ class UpdateRequest extends FormRequest 'url' => [sprintf('url:%s', $validProtocols), sprintf('uniqueExistingWebhook:%d', $webhook->id)], ]; } - - public function withValidator(Validator $validator): void - { - $validator->after( - function (Validator $validator): void { - Log::debug('Validating webhook'); - - if ($validator->failed()) { - return; - } - $data = $validator->getData(); - $triggers = $data['triggers'] ?? []; - $responses = $data['responses'] ?? []; - - if (0 === count($triggers) || 0 === count($responses)) { - Log::debug('No trigger or response, return.'); - - return; - } - $validTriggers = array_values(Webhook::getTriggers()); - $validResponses = array_values(Webhook::getResponses()); - foreach ($triggers as $trigger) { - if (!in_array($trigger, $validTriggers, true)) { - return; - } - } - foreach ($responses as $response) { - if (!in_array($response, $validResponses, true)) { - return; - } - } - // some combinations are illegal. - foreach ($triggers as $i => $trigger) { - $forbidden = config(sprintf('webhooks.forbidden_responses.%s', $trigger)); - if (null === $forbidden) { - $validator->errors()->add(sprintf('triggers.%d', $i), trans('validation.unknown_webhook_trigger', ['trigger' => $trigger,])); - continue; - } - foreach ($responses as $ii => $response) { - if (in_array($response, $forbidden, true)) { - Log::debug(sprintf('Trigger %s and response %s are forbidden.', $trigger, $response)); - $validator->errors()->add(sprintf('responses.%d', $ii), trans('validation.bad_webhook_combination', ['trigger' => $trigger, 'response' => $response,])); - return; - } - } - } - - } - ); - } } diff --git a/app/Enums/WebhookTrigger.php b/app/Enums/WebhookTrigger.php index 9f2951ff1d..1f64cb6032 100644 --- a/app/Enums/WebhookTrigger.php +++ b/app/Enums/WebhookTrigger.php @@ -29,6 +29,7 @@ namespace FireflyIII\Enums; */ enum WebhookTrigger: int { + case ANY = 50; case STORE_TRANSACTION = 100; case UPDATE_TRANSACTION = 110; case DESTROY_TRANSACTION = 120; diff --git a/app/Generator/Webhook/StandardMessageGenerator.php b/app/Generator/Webhook/StandardMessageGenerator.php index 6877e7581c..f2748f098b 100644 --- a/app/Generator/Webhook/StandardMessageGenerator.php +++ b/app/Generator/Webhook/StandardMessageGenerator.php @@ -30,11 +30,12 @@ use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\Budget; use FireflyIII\Models\BudgetLimit; use FireflyIII\Models\Transaction; -use FireflyIII\Models\WebhookResponse as WebhookResponseModel; use FireflyIII\Models\TransactionGroup; use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\Webhook; use FireflyIII\Models\WebhookMessage; +use FireflyIII\Models\WebhookResponse as WebhookResponseModel; +use FireflyIII\Models\WebhookTrigger as WebhookTriggerModel; use FireflyIII\Support\JsonApi\Enrichments\AccountEnrichment; use FireflyIII\Support\JsonApi\Enrichments\BudgetEnrichment; use FireflyIII\Support\JsonApi\Enrichments\BudgetLimitEnrichment; @@ -82,11 +83,11 @@ class StandardMessageGenerator implements MessageGeneratorInterface private function getWebhooks(): Collection { return $this->user->webhooks() - ->leftJoin('webhook_webhook_trigger','webhook_webhook_trigger.webhook_id','webhooks.id') - ->leftJoin('webhook_triggers','webhook_webhook_trigger.webhook_trigger_id','webhook_triggers.id') - ->where('active', true) - ->where('webhook_triggers.title', $this->trigger->name) - ->get(['webhooks.*']); + ->leftJoin('webhook_webhook_trigger', 'webhook_webhook_trigger.webhook_id', 'webhooks.id') + ->leftJoin('webhook_triggers', 'webhook_webhook_trigger.webhook_trigger_id', 'webhook_triggers.id') + ->where('active', true) + ->whereIn('webhook_triggers.title', [$this->trigger->name, WebhookTrigger::ANY->name]) + ->get(['webhooks.*']); } /** @@ -121,23 +122,24 @@ class StandardMessageGenerator implements MessageGeneratorInterface */ private function generateMessage(Webhook $webhook, Model $model): void { - $class = $model::class; + $class = $model::class; // Line is ignored because all of Firefly III's Models have an id property. Log::debug(sprintf('Now in generateMessage(#%d, %s#%d)', $webhook->id, $class, $model->id)); - $uuid = Uuid::uuid4(); + $uuid = Uuid::uuid4(); + /** @var WebhookResponseModel $response */ + $response = $webhook->webhookResponses()->first(); + $triggers = $this->getTriggerTitles($webhook->webhookTriggers()->get()); $basicMessage = [ 'uuid' => $uuid->toString(), 'user_id' => 0, 'user_group_id' => 0, 'trigger' => $this->trigger->name, - 'response' => $webhook->webhookResponses()->first()->title, // guess that the database is correct. + 'response' => $response->title, // guess that the database is correct. 'url' => $webhook->url, 'version' => sprintf('v%d', $this->getVersion()), 'content' => [], ]; - // depends on the model how user_id is set: - $relevantResponse = WebhookResponse::TRANSACTIONS->name; switch ($class) { default: // Line is ignored because all of Firefly III's Models have an id property. @@ -149,14 +151,14 @@ class StandardMessageGenerator implements MessageGeneratorInterface /** @var Budget $model */ $basicMessage['user_id'] = $model->user_id; $basicMessage['user_group_id'] = $model->user_group_id; - $relevantResponse = WebhookResponse::BUDGET->name; + $relevantResponse = WebhookResponse::BUDGET->name; break; case BudgetLimit::class: $basicMessage['user_id'] = $model->budget->user_id; $basicMessage['user_group_id'] = $model->budget->user_group_id; - $relevantResponse = WebhookResponse::BUDGET->name; + $relevantResponse = WebhookResponse::BUDGET->name; break; @@ -167,21 +169,9 @@ class StandardMessageGenerator implements MessageGeneratorInterface break; } + $responseTitle = $this->getRelevantResponse($triggers, $response, $class); - // then depends on the response what to put in the message: - /** @var WebhookResponseModel $webhookResponse */ - $webhookResponse = $webhook->webhookResponses()->first(); - $response = $webhookResponse->title; - Log::debug(sprintf('Expected response for this webhook is "%s".', $response)); - // if it's relevant, just switch to another. - if(WebhookResponse::RELEVANT->name === $response) { - // switch to whatever is actually relevant. - $response = $relevantResponse; - Log::debug(sprintf('Expected response for this webhook is now "%s".', $response)); - } - - - switch ($response) { + switch ($responseTitle) { default: Log::error(sprintf('The response code for webhook #%d is "%s" and the message generator cant handle it. Soft fail.', $webhook->id, $webhook->response)); @@ -190,23 +180,23 @@ class StandardMessageGenerator implements MessageGeneratorInterface case WebhookResponse::BUDGET->name: $basicMessage['content'] = []; if ($model instanceof Budget) { - $enrichment = new BudgetEnrichment(); + $enrichment = new BudgetEnrichment(); $enrichment->setUser($model->user); $model = $enrichment->enrichSingle($model); $transformer = new BudgetTransformer(); $basicMessage['content'] = $transformer->transform($model); } if ($model instanceof BudgetLimit) { - $user = $model->budget->user; - $enrichment = new BudgetLimitEnrichment(); + $user = $model->budget->user; + $enrichment = new BudgetLimitEnrichment(); $enrichment->setUser($user); - $parameters = new ParameterBag(); + $parameters = new ParameterBag(); $parameters->set('start', $model->start_date); $parameters->set('end', $model->end_date); - $model = $enrichment->enrichSingle($model); - $transformer = new BudgetLimitTransformer(); + $model = $enrichment->enrichSingle($model); + $transformer = new BudgetLimitTransformer(); $transformer->setParameters($parameters); $basicMessage['content'] = $transformer->transform($model); } @@ -220,7 +210,7 @@ class StandardMessageGenerator implements MessageGeneratorInterface case WebhookResponse::TRANSACTIONS->name: /** @var TransactionGroup $model */ - $transformer = new TransactionGroupTransformer(); + $transformer = new TransactionGroupTransformer(); try { $basicMessage['content'] = $transformer->transformObject($model); @@ -237,13 +227,13 @@ class StandardMessageGenerator implements MessageGeneratorInterface case WebhookResponse::ACCOUNTS->name: /** @var TransactionGroup $model */ - $accounts = $this->collectAccounts($model); - $enrichment = new AccountEnrichment(); + $accounts = $this->collectAccounts($model); + $enrichment = new AccountEnrichment(); $enrichment->setDate(null); $enrichment->setUser($model->user); - $accounts = $enrichment->enrich($accounts); + $accounts = $enrichment->enrich($accounts); foreach ($accounts as $account) { - $transformer = new AccountTransformer(); + $transformer = new AccountTransformer(); $transformer->setParameters(new ParameterBag()); $basicMessage['content'][] = $transformer->transform($account); } @@ -273,7 +263,7 @@ class StandardMessageGenerator implements MessageGeneratorInterface private function storeMessage(Webhook $webhook, array $message): void { - $webhookMessage = new WebhookMessage(); + $webhookMessage = new WebhookMessage(); $webhookMessage->webhook()->associate($webhook); $webhookMessage->sent = false; $webhookMessage->errored = false; @@ -302,4 +292,41 @@ class StandardMessageGenerator implements MessageGeneratorInterface { $this->webhooks = $webhooks; } + + private function getRelevantResponse(array $triggers, WebhookResponseModel $response, $class): string + { + // return none if none. + if (WebhookResponse::NONE->name === $response->title) { + Log::debug(sprintf('Return "%s" because requested nothing.', WebhookResponse::NONE->name)); + return WebhookResponse::NONE->name; + } + + if (WebhookResponse::RELEVANT->name === $response->title) { + Log::debug('Expected response is any relevant data.'); + // depends on the $class + switch ($class) { + case TransactionGroup::class: + Log::debug(sprintf('Return "%s" because class is %s', WebhookResponse::TRANSACTIONS->name, $class)); + return WebhookResponse::TRANSACTIONS->name; + case Budget::class: + case BudgetLimit::class: + Log::debug(sprintf('Return "%s" because class is %s', WebhookResponse::BUDGET->name, $class)); + return WebhookResponse::BUDGET->name; + default: + throw new FireflyException(sprintf('Cannot deal with "relevant" if the given object is a "%s"', $class)); + } + } + Log::debug(sprintf('Return response again: %s', $response->title)); + return $response->title; + } + + private function getTriggerTitles(Collection $collection): array + { + $return = []; + /** @var WebhookTriggerModel $item */ + foreach ($collection as $item) { + $return[] = $item->title; + } + return array_unique($return); + } } diff --git a/app/Support/Request/ValidatesWebhooks.php b/app/Support/Request/ValidatesWebhooks.php new file mode 100644 index 0000000000..0bc1649515 --- /dev/null +++ b/app/Support/Request/ValidatesWebhooks.php @@ -0,0 +1,69 @@ +after( + function (Validator $validator): void { + Log::debug('Validating webhook'); + if ($validator->failed()) { + return; + } + $data = $validator->getData(); + $triggers = $data['triggers'] ?? []; + $responses = $data['responses'] ?? []; + + if (0 === count($triggers) || 0 === count($responses)) { + Log::debug('No trigger or response, return.'); + + return; + } + $validTriggers = array_values(Webhook::getTriggers()); + $validResponses = array_values(Webhook::getResponses()); + $containsAny = false; + $count = 0; + foreach ($triggers as $trigger) { + if (!in_array($trigger, $validTriggers, true)) { + return; + } + $count++; + if($trigger === WebhookTrigger::ANY->name) { + $containsAny = true; + } + } + if($containsAny && $count > 1) { + $validator->errors()->add('triggers.0', trans('validation.only_any_trigger')); + return; + } + foreach ($responses as $response) { + if (!in_array($response, $validResponses, true)) { + return; + } + } + // some combinations are illegal. + foreach ($triggers as $i => $trigger) { + $forbidden = config(sprintf('webhooks.forbidden_responses.%s', $trigger)); + if (null === $forbidden) { + $validator->errors()->add(sprintf('triggers.%d', $i), trans('validation.unknown_webhook_trigger', ['trigger' => $trigger,])); + continue; + } + foreach ($responses as $ii => $response) { + if (in_array($response, $forbidden, true)) { + Log::debug(sprintf('Trigger %s and response %s are forbidden.', $trigger, $response)); + $validator->errors()->add(sprintf('responses.%d', $ii), trans('validation.bad_webhook_combination', ['trigger' => $trigger, 'response' => $response,])); + return; + } + } + } + } + ); + } +} diff --git a/config/webhooks.php b/config/webhooks.php index 49eaff18cd..323a710c8f 100644 --- a/config/webhooks.php +++ b/config/webhooks.php @@ -10,7 +10,7 @@ return [ WebhookTrigger::STORE_TRANSACTION->name => [ WebhookTrigger::STORE_BUDGET->name, WebhookTrigger::UPDATE_BUDGET->name, - WebhookTrigger::DESTROY_BUDGET->name, + WebhookTrigger::DESTROY_BUDGET->name, WebhookTrigger::STORE_UPDATE_BUDGET_LIMIT->name, ], @@ -49,6 +49,11 @@ return [ ], ], 'forbidden_responses' => [ + WebhookTrigger::ANY->name => [ + WebhookResponse::BUDGET->name, + WebhookResponse::TRANSACTIONS->name, + WebhookResponse::ACCOUNTS->name, + ], WebhookTrigger::STORE_TRANSACTION->name => [ WebhookResponse::BUDGET->name, ], diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index 08a16a6e3b..4a2ab3d092 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -241,6 +241,7 @@ return [ 'webhooks_breadcrumb' => 'Webhooks', 'webhooks_menu_disabled' => 'disabled', 'no_webhook_messages' => 'There are no webhook messages', + 'webhook_trigger_ANY' => 'After any event', 'webhook_trigger_STORE_TRANSACTION' => 'After transaction creation', 'webhook_trigger_UPDATE_TRANSACTION' => 'After transaction update', 'webhook_trigger_DESTROY_TRANSACTION' => 'After transaction delete', diff --git a/resources/lang/en_US/validation.php b/resources/lang/en_US/validation.php index 33ec3b34e8..a57b2f814c 100644 --- a/resources/lang/en_US/validation.php +++ b/resources/lang/en_US/validation.php @@ -37,6 +37,7 @@ return [ 'prohibited' => 'You must not submit anything in field.', 'bad_webhook_combination' => 'Webhook trigger ":trigger" cannot be combined with webhook response ":response".', 'unknown_webhook_trigger' => 'Unknown webhook trigger ":trigger".', + 'only_any_trigger' => 'If you select the "Any event"-trigger, you may not select any other triggers.', 'bad_type_source' => 'Firefly III can\'t determine the transaction type based on this source account.', 'bad_type_destination' => 'Firefly III can\'t determine the transaction type based on this destination account.', 'missing_where' => 'Array is missing "where"-clause', From 6c5ddfcb8a8d789a22ea7eb4a34fa4bfe6b0887d Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 21 Aug 2025 20:10:53 +0200 Subject: [PATCH 009/111] Add newsletter link. --- .github/release-notes/alpha.md | 4 ++++ .github/release-notes/beta.md | 4 ++++ .github/release-notes/branch.md | 4 ++++ .github/release-notes/develop.md | 4 ++++ .github/release-notes/release.md | 4 ++++ 5 files changed, 20 insertions(+) diff --git a/.github/release-notes/alpha.md b/.github/release-notes/alpha.md index 888a4093fc..9d10797037 100644 --- a/.github/release-notes/alpha.md +++ b/.github/release-notes/alpha.md @@ -16,6 +16,10 @@ Alpha releases are created to test new features and fixes before they are includ The release files are signed, and you can verify them using the [Firefly III releases PGP key](https://docs.firefly-iii.org/explanation/more-information/signatures/). +## Develop with Firefly III + +Are you interested in (future) API changes to Firefly III, or other interesting dev-related updates? Sign up to the [Firefly III developer newsletter](https://firefly-iii.kit.com/dev) to receive low-frequency updates about the development of Firefly III. + ## Support Firefly III Did you know you can support the development of Firefly III? You can donate in many ways, like GitHub Sponsors or Patreon. Please [follow this link](https://bit.ly/donate-to-Firefly-III) for more information. Thank you for your consideration. diff --git a/.github/release-notes/beta.md b/.github/release-notes/beta.md index 5940d83f67..d170cfc620 100644 --- a/.github/release-notes/beta.md +++ b/.github/release-notes/beta.md @@ -16,6 +16,10 @@ Alpha releases are created to test new features and fixes before they are includ The release files are signed, and you can verify them using the [Firefly III releases PGP key](https://docs.firefly-iii.org/explanation/more-information/signatures/). +## Develop with Firefly III + +Are you interested in (future) API changes to Firefly III, or other interesting dev-related updates? Sign up to the [Firefly III developer newsletter](https://firefly-iii.kit.com/dev) to receive low-frequency updates about the development of Firefly III. + ## Support Firefly III Did you know you can support the development of Firefly III? You can donate in many ways, like GitHub Sponsors or Patreon. Please [follow this link](https://bit.ly/donate-to-Firefly-III) for more information. Thank you for your consideration. diff --git a/.github/release-notes/branch.md b/.github/release-notes/branch.md index 22e1c3cc3e..2bb439a1fd 100644 --- a/.github/release-notes/branch.md +++ b/.github/release-notes/branch.md @@ -16,6 +16,10 @@ There is no changelog for this release, as it is not final. However, [changelog. The release files are signed, and you can verify them using the [Firefly III releases PGP key](https://docs.firefly-iii.org/explanation/more-information/signatures/). +## Develop with Firefly III + +Are you interested in (future) API changes to Firefly III, or other interesting dev-related updates? Sign up to the [Firefly III developer newsletter](https://firefly-iii.kit.com/dev) to receive low-frequency updates about the development of Firefly III. + ## Support Firefly III Did you know you can support the development of Firefly III? You can donate in many ways, like GitHub Sponsors or Patreon. Please [follow this link](https://bit.ly/donate-to-Firefly-III) for more information. Thank you for your consideration. diff --git a/.github/release-notes/develop.md b/.github/release-notes/develop.md index 04774b7dc1..899f969a42 100644 --- a/.github/release-notes/develop.md +++ b/.github/release-notes/develop.md @@ -16,6 +16,10 @@ The changelog for this release may not be up-to-date, so it is not included. How The releases are signed, and you can verify them using the [Firefly III releases PGP key](https://docs.firefly-iii.org/explanation/more-information/signatures/). +## Develop with Firefly III + +Are you interested in (future) API changes to Firefly III, or other interesting dev-related updates? Sign up to the [Firefly III developer newsletter](https://firefly-iii.kit.com/dev) to receive low-frequency updates about the development of Firefly III. + ## Support Firefly III Did you know you can support the development of Firefly III? You can donate in many ways, like GitHub Sponsors or Patreon. Please [follow this link](https://bit.ly/donate-to-Firefly-III) for more information. Thank you for your consideration. diff --git a/.github/release-notes/release.md b/.github/release-notes/release.md index c1442d5630..1e1f999b6a 100644 --- a/.github/release-notes/release.md +++ b/.github/release-notes/release.md @@ -11,6 +11,10 @@ Welcome to release %version of Firefly III. It contains the latest fixes, transl The releases are signed, and you can verify them using the [Firefly III releases PGP key](https://docs.firefly-iii.org/explanation/more-information/signatures/). +## Develop with Firefly III + +Are you interested in (future) API changes to Firefly III, or other interesting dev-related updates? Sign up to the [Firefly III developer newsletter](https://firefly-iii.kit.com/dev) to receive low-frequency updates about the development of Firefly III. + ## Support Firefly III Did you know you can support the development of Firefly III? You can donate in many ways, like GitHub Sponsors or Patreon. Please [follow this link](https://bit.ly/donate-to-Firefly-III) for more information. Thank you for your consideration. From 1ba641c27911c62b9585500c051c681b0b075483 Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 21 Aug 2025 20:31:32 +0200 Subject: [PATCH 010/111] Prevent a loop. --- app/Api/V1/Controllers/Webhook/ShowController.php | 2 +- app/Casts/SeparateTimezoneCaster.php | 2 +- app/Handlers/Events/DestroyedGroupEventHandler.php | 2 +- app/Handlers/Events/StoredGroupEventHandler.php | 1 + app/Handlers/Events/UpdatedGroupEventHandler.php | 1 + app/Handlers/Observer/BudgetLimitObserver.php | 2 ++ app/Handlers/Observer/BudgetObserver.php | 7 ++++--- app/Repositories/Budget/BudgetRepository.php | 2 +- config/translations.php | 2 ++ 9 files changed, 14 insertions(+), 7 deletions(-) diff --git a/app/Api/V1/Controllers/Webhook/ShowController.php b/app/Api/V1/Controllers/Webhook/ShowController.php index 69be75a49a..c8fb5efd19 100644 --- a/app/Api/V1/Controllers/Webhook/ShowController.php +++ b/app/Api/V1/Controllers/Webhook/ShowController.php @@ -172,7 +172,7 @@ class ShowController extends Controller $engine->generateMessages(); // trigger event to send them: - Log::debug('send event RequestedSendWebhookMessages'); + Log::debug('send event RequestedSendWebhookMessages from ShowController::triggerTransaction()'); event(new RequestedSendWebhookMessages()); return response()->json([], 204); diff --git a/app/Casts/SeparateTimezoneCaster.php b/app/Casts/SeparateTimezoneCaster.php index c318626ba6..8e9f2231bf 100644 --- a/app/Casts/SeparateTimezoneCaster.php +++ b/app/Casts/SeparateTimezoneCaster.php @@ -52,7 +52,7 @@ class SeparateTimezoneCaster implements CastsAttributes $timeZone = $attributes[sprintf('%s_tz', $key)] ?? config('app.timezone'); $result = Carbon::parse($value, $timeZone)->setTimezone(config('app.timezone')); - Log::debug(sprintf('SeparateTimezoneCaster: %s.%s = %s', str_replace('FireflyIII\\Models\\','',get_class($model)), $key, $result->toAtomString())); + // Log::debug(sprintf('SeparateTimezoneCaster: %s.%s = %s', str_replace('FireflyIII\\Models\\','',get_class($model)), $key, $result->toAtomString())); return $result; } diff --git a/app/Handlers/Events/DestroyedGroupEventHandler.php b/app/Handlers/Events/DestroyedGroupEventHandler.php index fb5baf4c22..9f5926c96c 100644 --- a/app/Handlers/Events/DestroyedGroupEventHandler.php +++ b/app/Handlers/Events/DestroyedGroupEventHandler.php @@ -55,7 +55,7 @@ class DestroyedGroupEventHandler $engine->setObjects(new Collection([$group])); $engine->setTrigger(WebhookTrigger::DESTROY_TRANSACTION); $engine->generateMessages(); - + Log::debug(sprintf('send event RequestedSendWebhookMessages from %s', __METHOD__)); event(new RequestedSendWebhookMessages()); } diff --git a/app/Handlers/Events/StoredGroupEventHandler.php b/app/Handlers/Events/StoredGroupEventHandler.php index a5f806ae0c..0b471c01ac 100644 --- a/app/Handlers/Events/StoredGroupEventHandler.php +++ b/app/Handlers/Events/StoredGroupEventHandler.php @@ -121,6 +121,7 @@ class StoredGroupEventHandler $engine->generateMessages(); // trigger event to send them: + Log::debug(sprintf('send event RequestedSendWebhookMessages from %s', __METHOD__)); event(new RequestedSendWebhookMessages()); } } diff --git a/app/Handlers/Events/UpdatedGroupEventHandler.php b/app/Handlers/Events/UpdatedGroupEventHandler.php index b25c913d0b..71c04589db 100644 --- a/app/Handlers/Events/UpdatedGroupEventHandler.php +++ b/app/Handlers/Events/UpdatedGroupEventHandler.php @@ -167,6 +167,7 @@ class UpdatedGroupEventHandler $engine->setTrigger(WebhookTrigger::UPDATE_TRANSACTION); $engine->generateMessages(); + Log::debug(sprintf('send event RequestedSendWebhookMessages from %s', __METHOD__)); event(new RequestedSendWebhookMessages()); } diff --git a/app/Handlers/Observer/BudgetLimitObserver.php b/app/Handlers/Observer/BudgetLimitObserver.php index f6fcc85f03..ae6b1aac6a 100644 --- a/app/Handlers/Observer/BudgetLimitObserver.php +++ b/app/Handlers/Observer/BudgetLimitObserver.php @@ -53,6 +53,7 @@ class BudgetLimitObserver $engine->setTrigger(WebhookTrigger::STORE_UPDATE_BUDGET_LIMIT); $engine->generateMessages(); + Log::debug(sprintf('send event RequestedSendWebhookMessages from %s', __METHOD__)); event(new RequestedSendWebhookMessages()); } @@ -90,6 +91,7 @@ class BudgetLimitObserver $engine->setTrigger(WebhookTrigger::STORE_UPDATE_BUDGET_LIMIT); $engine->generateMessages(); + Log::debug(sprintf('send event RequestedSendWebhookMessages from %s', __METHOD__)); event(new RequestedSendWebhookMessages()); } } diff --git a/app/Handlers/Observer/BudgetObserver.php b/app/Handlers/Observer/BudgetObserver.php index 51a87b864f..bdb8eb52e9 100644 --- a/app/Handlers/Observer/BudgetObserver.php +++ b/app/Handlers/Observer/BudgetObserver.php @@ -25,6 +25,7 @@ namespace FireflyIII\Handlers\Observer; use FireflyIII\Enums\WebhookTrigger; use FireflyIII\Events\RequestedSendWebhookMessages; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\Generator\Webhook\MessageGeneratorInterface; use FireflyIII\Models\Attachment; use FireflyIII\Models\Budget; @@ -54,7 +55,7 @@ class BudgetObserver $engine->setObjects(new Collection()->push($budget)); $engine->setTrigger(WebhookTrigger::STORE_BUDGET); $engine->generateMessages(); - + Log::debug(sprintf('send event RequestedSendWebhookMessages from %s', __METHOD__)); event(new RequestedSendWebhookMessages()); } @@ -69,7 +70,7 @@ class BudgetObserver $engine->setObjects(new Collection()->push($budget)); $engine->setTrigger(WebhookTrigger::UPDATE_BUDGET); $engine->generateMessages(); - + Log::debug(sprintf('send event RequestedSendWebhookMessages from %s', __METHOD__)); event(new RequestedSendWebhookMessages()); } @@ -85,7 +86,7 @@ class BudgetObserver $engine->setObjects(new Collection()->push($budget)); $engine->setTrigger(WebhookTrigger::DESTROY_BUDGET); $engine->generateMessages(); - + Log::debug(sprintf('send event RequestedSendWebhookMessages from %s', __METHOD__)); event(new RequestedSendWebhookMessages()); $repository = app(AttachmentRepositoryInterface::class); diff --git a/app/Repositories/Budget/BudgetRepository.php b/app/Repositories/Budget/BudgetRepository.php index 56de39dcc6..028d874f36 100644 --- a/app/Repositories/Budget/BudgetRepository.php +++ b/app/Repositories/Budget/BudgetRepository.php @@ -268,7 +268,7 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface */ foreach ($budgets as $index => $budget) { $budget->order = $index + 1; - $budget->save(); + $budget->saveQuietly(); } // other budgets, set to 0. $this->user->budgets()->where('active', 0)->update(['order' => 0]); diff --git a/config/translations.php b/config/translations.php index 426e3ca355..2bb39e82d9 100644 --- a/config/translations.php +++ b/config/translations.php @@ -248,6 +248,7 @@ return [ 'multi_account_warning_deposit', 'multi_account_warning_transfer', + 'webhook_trigger_ANY', 'webhook_trigger_STORE_TRANSACTION', 'webhook_trigger_UPDATE_TRANSACTION', 'webhook_trigger_DESTROY_TRANSACTION', @@ -258,6 +259,7 @@ return [ 'webhook_trigger_STORE_UPDATE_BUDGET_LIMIT', 'webhook_response_TRANSACTIONS', + 'webhook_response_RELEVANT', 'webhook_response_ACCOUNTS', 'webhook_response_NONE', 'webhook_delivery_JSON', From 96a66b894aa67d2ce0da64f5ead4c497ca48e8b4 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 22 Aug 2025 06:49:16 +0200 Subject: [PATCH 011/111] Fix #10799 --- app/Http/Controllers/Budget/IndexController.php | 1 - app/Repositories/Webhook/WebhookRepository.php | 3 +++ resources/views/budgets/index.twig | 15 ++++++++------- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/app/Http/Controllers/Budget/IndexController.php b/app/Http/Controllers/Budget/IndexController.php index a370bbd00f..78f9e7179c 100644 --- a/app/Http/Controllers/Budget/IndexController.php +++ b/app/Http/Controllers/Budget/IndexController.php @@ -129,7 +129,6 @@ class IndexController extends Controller $spent = $spentArr[$this->primaryCurrency->id]['sum'] ?? '0'; unset($spentArr); } - // number of days for consistent budgeting. $activeDaysPassed = $this->activeDaysPassed($start, $end); // see method description. $activeDaysLeft = $this->activeDaysLeft($start, $end); // see method description. diff --git a/app/Repositories/Webhook/WebhookRepository.php b/app/Repositories/Webhook/WebhookRepository.php index 7a54e0fd7c..943ab278ad 100644 --- a/app/Repositories/Webhook/WebhookRepository.php +++ b/app/Repositories/Webhook/WebhookRepository.php @@ -55,6 +55,9 @@ class WebhookRepository implements WebhookRepositoryInterface, UserGroupInterfac public function destroy(Webhook $webhook): void { + // force delete all messages and attempts: + $webhook->webhookMessages()->delete(); + $webhook->delete(); } diff --git a/resources/views/budgets/index.twig b/resources/views/budgets/index.twig index d773948874..73b8814968 100644 --- a/resources/views/budgets/index.twig +++ b/resources/views/budgets/index.twig @@ -356,16 +356,17 @@
{% endif %} + {% if spentInfo.currency_id == budgetLimit.currency_id and not budgetLimit.in_range %} + + {{ formatAmountBySymbol(spentInfo.spent + budgetLimit.amount, spentInfo.currency_symbol, spentInfo.currency_decimal_places) }} + + ({{ 'unknown'|_ }}) + {% endif %} {% endfor %} {% if countLimit == 0 %} - - {# this code is used for budget limits OUTSIDE the current view range. #} - - {{ formatAmountBySymbol(spentInfo.spent, spentInfo.currency_symbol, spentInfo.currency_decimal_places) }} - -
+ {# display nothing #} {% endif %} {% endfor %} {% for budgetLimit in budget.budgeted %} From 8a8bbaf8272a87def17a79d6d16632aec3c41e4d Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 22 Aug 2025 06:57:25 +0200 Subject: [PATCH 012/111] Remove test webhook, no longer necessary. --- app/Http/Controllers/HomeController.php | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index 6142cf7701..a4978d89bd 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -125,27 +125,6 @@ class HomeController extends Controller */ public function index(AccountRepositoryInterface $repository): mixed { - $trigger = WebhookTrigger::first(); - $delivery = WebhookDelivery::first(); - $response = WebhookResponse::first(); - - $webhook = new Webhook(); - $webhook->user_id = auth()->id(); - $webhook->user_group_id = auth()->user()->user_group_id; - $webhook->title = 'Test Webhook'; - $webhook->url = 'https://example.com/webhook'; - $webhook->active = true; - $webhook->secret = 'abc'; - $webhook->trigger = 1; - $webhook->response = 1; - $webhook->delivery = 1; - $webhook->save(); - $webhook->webhookDeliveries()->saveMany(new Collection()->push($delivery)); - $webhook->webhookResponses()->saveMany(new Collection()->push($response)); - $webhook->webhookTriggers()->saveMany(new Collection()->push($trigger)); - - - $types = config('firefly.accountTypesByIdentifier.asset'); $count = $repository->count($types); Log::channel('audit')->info('User visits homepage.'); From 93085599b773ff849e192e55fb548133d1694e54 Mon Sep 17 00:00:00 2001 From: JC5 Date: Fri, 22 Aug 2025 07:04:13 +0200 Subject: [PATCH 013/111] =?UTF-8?q?=F0=9F=A4=96=20Auto=20commit=20for=20re?= =?UTF-8?q?lease=20'develop'=20on=202025-08-22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .ci/php-cs-fixer/composer.lock | 86 +++--- .../V1/Controllers/Webhook/ShowController.php | 2 +- .../Controllers/Webhook/UpdateController.php | 22 +- .../Requests/Models/Webhook/CreateRequest.php | 10 +- .../Requests/Models/Webhook/UpdateRequest.php | 18 +- app/Casts/SeparateTimezoneCaster.php | 3 +- .../Commands/Upgrade/UpgradesWebhooks.php | 25 +- app/Enums/WebhookResponse.php | 2 +- app/Factory/RecurrenceFactory.php | 22 +- .../Webhook/StandardMessageGenerator.php | 55 ++-- app/Handlers/Observer/BudgetObserver.php | 1 - app/Http/Controllers/HomeController.php | 4 - app/Models/Webhook.php | 4 +- app/Models/WebhookDelivery.php | 4 +- app/Models/WebhookResponse.php | 4 +- app/Models/WebhookTrigger.php | 4 +- .../Webhook/WebhookRepository.php | 44 +-- .../Enrichments/RecurringEnrichment.php | 3 +- .../JsonApi/Enrichments/WebhookEnrichment.php | 29 +- app/Support/Request/ConvertsDataTypes.php | 4 +- app/Support/Request/ValidatesWebhooks.php | 25 +- app/Transformers/WebhookTransformer.php | 10 +- composer.lock | 180 +++++++----- config/firefly.php | 4 +- config/webhooks.php | 8 +- package-lock.json | 266 +++++++++--------- public/v1/js/.gitkeep | 0 resources/assets/v1/src/locales/af.json | 2 + resources/assets/v1/src/locales/ar.json | 2 + resources/assets/v1/src/locales/bg.json | 2 + resources/assets/v1/src/locales/ca.json | 2 + resources/assets/v1/src/locales/cs.json | 2 + resources/assets/v1/src/locales/da.json | 2 + resources/assets/v1/src/locales/de.json | 2 + resources/assets/v1/src/locales/el.json | 2 + resources/assets/v1/src/locales/en-gb.json | 2 + resources/assets/v1/src/locales/en.json | 2 + resources/assets/v1/src/locales/es.json | 2 + resources/assets/v1/src/locales/fa.json | 2 + resources/assets/v1/src/locales/fi.json | 2 + resources/assets/v1/src/locales/fr.json | 2 + resources/assets/v1/src/locales/hu.json | 2 + resources/assets/v1/src/locales/id.json | 2 + resources/assets/v1/src/locales/it.json | 2 + resources/assets/v1/src/locales/ja.json | 2 + resources/assets/v1/src/locales/ko.json | 2 + resources/assets/v1/src/locales/nb.json | 2 + resources/assets/v1/src/locales/nl.json | 2 + resources/assets/v1/src/locales/nn.json | 2 + resources/assets/v1/src/locales/pl.json | 2 + resources/assets/v1/src/locales/pt-br.json | 2 + resources/assets/v1/src/locales/pt.json | 2 + resources/assets/v1/src/locales/ro.json | 2 + resources/assets/v1/src/locales/ru.json | 2 + resources/assets/v1/src/locales/sk.json | 2 + resources/assets/v1/src/locales/sl.json | 2 + resources/assets/v1/src/locales/sv.json | 2 + resources/assets/v1/src/locales/tr.json | 2 + resources/assets/v1/src/locales/uk.json | 2 + resources/assets/v1/src/locales/vi.json | 2 + resources/assets/v1/src/locales/zh-cn.json | 2 + resources/assets/v1/src/locales/zh-tw.json | 2 + 62 files changed, 530 insertions(+), 379 deletions(-) mode change 100755 => 100644 public/v1/js/.gitkeep diff --git a/.ci/php-cs-fixer/composer.lock b/.ci/php-cs-fixer/composer.lock index 40ac982c67..fbcb1d56ef 100644 --- a/.ci/php-cs-fixer/composer.lock +++ b/.ci/php-cs-fixer/composer.lock @@ -151,16 +151,16 @@ }, { "name": "composer/semver", - "version": "3.4.3", + "version": "3.4.4", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12" + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", - "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", + "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95", "shasum": "" }, "require": { @@ -212,7 +212,7 @@ "support": { "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.4.3" + "source": "https://github.com/composer/semver/tree/3.4.4" }, "funding": [ { @@ -222,13 +222,9 @@ { "url": "https://github.com/composer", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" } ], - "time": "2024-09-19T14:15:21+00:00" + "time": "2025-08-20T19:15:30+00:00" }, { "name": "composer/xdebug-handler", @@ -959,23 +955,23 @@ }, { "name": "react/promise", - "version": "v3.2.0", + "version": "v3.3.0", "source": { "type": "git", "url": "https://github.com/reactphp/promise.git", - "reference": "8a164643313c71354582dc850b42b33fa12a4b63" + "reference": "23444f53a813a3296c1368bb104793ce8d88f04a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/promise/zipball/8a164643313c71354582dc850b42b33fa12a4b63", - "reference": "8a164643313c71354582dc850b42b33fa12a4b63", + "url": "https://api.github.com/repos/reactphp/promise/zipball/23444f53a813a3296c1368bb104793ce8d88f04a", + "reference": "23444f53a813a3296c1368bb104793ce8d88f04a", "shasum": "" }, "require": { "php": ">=7.1.0" }, "require-dev": { - "phpstan/phpstan": "1.10.39 || 1.4.10", + "phpstan/phpstan": "1.12.28 || 1.4.10", "phpunit/phpunit": "^9.6 || ^7.5" }, "type": "library", @@ -1020,7 +1016,7 @@ ], "support": { "issues": "https://github.com/reactphp/promise/issues", - "source": "https://github.com/reactphp/promise/tree/v3.2.0" + "source": "https://github.com/reactphp/promise/tree/v3.3.0" }, "funding": [ { @@ -1028,7 +1024,7 @@ "type": "open_collective" } ], - "time": "2024-05-24T10:39:05+00:00" + "time": "2025-08-19T18:57:03+00:00" }, { "name": "react/socket", @@ -1787,7 +1783,7 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", @@ -1846,7 +1842,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0" }, "funding": [ { @@ -1857,6 +1853,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -1866,16 +1866,16 @@ }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", - "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", "shasum": "" }, "require": { @@ -1924,7 +1924,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0" }, "funding": [ { @@ -1935,16 +1935,20 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2025-06-27T09:58:17+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", @@ -2005,7 +2009,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0" }, "funding": [ { @@ -2016,6 +2020,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -2025,7 +2033,7 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", @@ -2086,7 +2094,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" }, "funding": [ { @@ -2097,6 +2105,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -2106,7 +2118,7 @@ }, { "name": "symfony/polyfill-php80", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", @@ -2166,7 +2178,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0" }, "funding": [ { @@ -2177,6 +2189,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -2186,7 +2202,7 @@ }, { "name": "symfony/polyfill-php81", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php81.git", @@ -2242,7 +2258,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.33.0" }, "funding": [ { @@ -2253,6 +2269,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" diff --git a/app/Api/V1/Controllers/Webhook/ShowController.php b/app/Api/V1/Controllers/Webhook/ShowController.php index c8fb5efd19..2d835dad82 100644 --- a/app/Api/V1/Controllers/Webhook/ShowController.php +++ b/app/Api/V1/Controllers/Webhook/ShowController.php @@ -97,7 +97,7 @@ class ShowController extends Controller $admin = auth()->user(); $enrichment = new WebhookEnrichment(); $enrichment->setUser($admin); - $webhooks = $enrichment->enrich($webhooks); + $webhooks = $enrichment->enrich($webhooks); /** @var WebhookTransformer $transformer */ $transformer = app(WebhookTransformer::class); diff --git a/app/Api/V1/Controllers/Webhook/UpdateController.php b/app/Api/V1/Controllers/Webhook/UpdateController.php index 96411d5ca1..eec804184e 100644 --- a/app/Api/V1/Controllers/Webhook/UpdateController.php +++ b/app/Api/V1/Controllers/Webhook/UpdateController.php @@ -24,17 +24,17 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Controllers\Webhook; - use FireflyIII\Api\V1\Controllers\Controller; - use FireflyIII\Api\V1\Requests\Models\Webhook\UpdateRequest; - use FireflyIII\Models\Webhook; - use FireflyIII\Repositories\Webhook\WebhookRepositoryInterface; - use FireflyIII\Support\JsonApi\Enrichments\WebhookEnrichment; - use FireflyIII\Transformers\WebhookTransformer; - use FireflyIII\User; - use Illuminate\Http\JsonResponse; - use Illuminate\Support\Facades\Log; - use League\Fractal\Resource\Item; - use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use FireflyIII\Api\V1\Controllers\Controller; +use FireflyIII\Api\V1\Requests\Models\Webhook\UpdateRequest; +use FireflyIII\Models\Webhook; +use FireflyIII\Repositories\Webhook\WebhookRepositoryInterface; +use FireflyIII\Support\JsonApi\Enrichments\WebhookEnrichment; +use FireflyIII\Transformers\WebhookTransformer; +use FireflyIII\User; +use Illuminate\Http\JsonResponse; +use Illuminate\Support\Facades\Log; +use League\Fractal\Resource\Item; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** * Class UpdateController diff --git a/app/Api/V1/Requests/Models/Webhook/CreateRequest.php b/app/Api/V1/Requests/Models/Webhook/CreateRequest.php index 71093b3ec0..73318823ca 100644 --- a/app/Api/V1/Requests/Models/Webhook/CreateRequest.php +++ b/app/Api/V1/Requests/Models/Webhook/CreateRequest.php @@ -30,9 +30,7 @@ use FireflyIII\Rules\IsBoolean; use FireflyIII\Support\Request\ChecksLogin; use FireflyIII\Support\Request\ConvertsDataTypes; use FireflyIII\Support\Request\ValidatesWebhooks; -use Illuminate\Contracts\Validation\Validator; use Illuminate\Foundation\Http\FormRequest; -use Illuminate\Support\Facades\Log; /** * Class CreateRequest @@ -45,14 +43,14 @@ class CreateRequest extends FormRequest public function getData(): array { - $fields = [ + $fields = [ 'title' => ['title', 'convertString'], 'active' => ['active', 'boolean'], 'url' => ['url', 'convertString'], ]; - $triggers = $this->get('triggers', []); - $responses = $this->get('responses', []); - $deliveries = $this->get('deliveries', []); + $triggers = $this->get('triggers', []); + $responses = $this->get('responses', []); + $deliveries = $this->get('deliveries', []); if (0 === count($triggers) || 0 === count($responses) || 0 === count($deliveries)) { throw new FireflyException('Unexpectedly got no responses, triggers or deliveries.'); diff --git a/app/Api/V1/Requests/Models/Webhook/UpdateRequest.php b/app/Api/V1/Requests/Models/Webhook/UpdateRequest.php index 7dcebc8552..aa37a0b4ed 100644 --- a/app/Api/V1/Requests/Models/Webhook/UpdateRequest.php +++ b/app/Api/V1/Requests/Models/Webhook/UpdateRequest.php @@ -24,17 +24,13 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Requests\Models\Webhook; -use FireflyIII\Enums\WebhookResponse; -use FireflyIII\Enums\WebhookTrigger; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\Webhook; use FireflyIII\Rules\IsBoolean; use FireflyIII\Support\Request\ChecksLogin; use FireflyIII\Support\Request\ConvertsDataTypes; use FireflyIII\Support\Request\ValidatesWebhooks; -use Illuminate\Contracts\Validation\Validator; use Illuminate\Foundation\Http\FormRequest; -use Illuminate\Support\Facades\Log; /** * Class UpdateRequest @@ -47,15 +43,15 @@ class UpdateRequest extends FormRequest public function getData(): array { - $fields = [ + $fields = [ 'title' => ['title', 'convertString'], 'active' => ['active', 'boolean'], 'url' => ['url', 'convertString'], ]; - $triggers = $this->get('triggers', []); - $responses = $this->get('responses', []); - $deliveries = $this->get('deliveries', []); + $triggers = $this->get('triggers', []); + $responses = $this->get('responses', []); + $deliveries = $this->get('deliveries', []); if (0 === count($triggers) || 0 === count($responses) || 0 === count($deliveries)) { throw new FireflyException('Unexpectedly got no responses, triggers or deliveries.'); @@ -83,8 +79,8 @@ class UpdateRequest extends FormRequest $webhook = $this->route()->parameter('webhook'); return [ - 'title' => sprintf('min:1|max:255|uniqueObjectForUser:webhooks,title,%d', $webhook->id), - 'active' => [new IsBoolean()], + 'title' => sprintf('min:1|max:255|uniqueObjectForUser:webhooks,title,%d', $webhook->id), + 'active' => [new IsBoolean()], 'trigger' => 'prohibited', 'triggers' => 'required|array|min:1|max:10', @@ -96,7 +92,7 @@ class UpdateRequest extends FormRequest 'deliveries' => 'required|array|min:1|max:1', 'deliveries.*' => sprintf('required|in:%s', $deliveries), - 'url' => [sprintf('url:%s', $validProtocols), sprintf('uniqueExistingWebhook:%d', $webhook->id)], + 'url' => [sprintf('url:%s', $validProtocols), sprintf('uniqueExistingWebhook:%d', $webhook->id)], ]; } } diff --git a/app/Casts/SeparateTimezoneCaster.php b/app/Casts/SeparateTimezoneCaster.php index 8e9f2231bf..67a56ae83e 100644 --- a/app/Casts/SeparateTimezoneCaster.php +++ b/app/Casts/SeparateTimezoneCaster.php @@ -51,9 +51,8 @@ class SeparateTimezoneCaster implements CastsAttributes } $timeZone = $attributes[sprintf('%s_tz', $key)] ?? config('app.timezone'); - $result = Carbon::parse($value, $timeZone)->setTimezone(config('app.timezone')); + return Carbon::parse($value, $timeZone)->setTimezone(config('app.timezone')); // Log::debug(sprintf('SeparateTimezoneCaster: %s.%s = %s', str_replace('FireflyIII\\Models\\','',get_class($model)), $key, $result->toAtomString())); - return $result; } /** diff --git a/app/Console/Commands/Upgrade/UpgradesWebhooks.php b/app/Console/Commands/Upgrade/UpgradesWebhooks.php index 7f3e6e8152..93a1c896c9 100644 --- a/app/Console/Commands/Upgrade/UpgradesWebhooks.php +++ b/app/Console/Commands/Upgrade/UpgradesWebhooks.php @@ -1,5 +1,7 @@ upgradeWebhooks(); - $this->markAsExecuted(); + $this->markAsExecuted(); $this->friendlyPositive('Upgraded webhooks.'); return 0; @@ -48,10 +50,10 @@ class UpgradesWebhooks extends Command return false; } - private function upgradeWebhooks(): void { $set = Webhook::where('delivery', '>', 1)->orWhere('trigger', '>', 1)->orWhere('response', '>', 1)->get(); + /** @var Webhook $webhook */ foreach ($set as $webhook) { $this->upgradeWebhook($webhook); @@ -60,18 +62,20 @@ class UpgradesWebhooks extends Command private function upgradeWebhook(Webhook $webhook): void { - $delivery = WebhookDelivery::tryFrom((int)$webhook->delivery); - $response = WebhookResponse::tryFrom((int)$webhook->response); - $trigger = WebhookTrigger::tryFrom((int)$webhook->trigger); + $delivery = WebhookDelivery::tryFrom((int)$webhook->delivery); + $response = WebhookResponse::tryFrom((int)$webhook->response); + $trigger = WebhookTrigger::tryFrom((int)$webhook->trigger); if (null === $delivery || null === $response || null === $trigger) { $this->friendlyError(sprintf('[a] Webhook #%d has an invalid delivery, response or trigger value. Will not upgrade.', $webhook->id)); + return; } - $deliveryModel = WebhookDeliveryModel::where('key', $delivery->value)->first(); - $responseModel = WebhookResponseModel::where('key', $response->value)->first(); - $triggerModel = WebhookTriggerModel::where('key', $trigger->value)->first(); + $deliveryModel = WebhookDeliveryModel::where('key', $delivery->value)->first(); + $responseModel = WebhookResponseModel::where('key', $response->value)->first(); + $triggerModel = WebhookTriggerModel::where('key', $trigger->value)->first(); if (null === $deliveryModel || null === $responseModel || null === $triggerModel) { $this->friendlyError(sprintf('[b] Webhook #%d has an invalid delivery, response or trigger model. Will not upgrade.', $webhook->id)); + return; } $webhook->webhookDeliveries()->attach([$deliveryModel->id]); @@ -83,6 +87,7 @@ class UpgradesWebhooks extends Command $webhook->save(); $this->friendlyPositive(sprintf('Webhook #%d upgraded.', $webhook->id)); } + private function markAsExecuted(): void { app('fireflyconfig')->set(self::CONFIG_NAME, true); diff --git a/app/Enums/WebhookResponse.php b/app/Enums/WebhookResponse.php index f1dfa213e6..20156cc4f5 100644 --- a/app/Enums/WebhookResponse.php +++ b/app/Enums/WebhookResponse.php @@ -32,6 +32,6 @@ enum WebhookResponse: int case TRANSACTIONS = 200; case ACCOUNTS = 210; case BUDGET = 230; - case RELEVANT = 240; + case RELEVANT = 240; case NONE = 220; } diff --git a/app/Factory/RecurrenceFactory.php b/app/Factory/RecurrenceFactory.php index 16cb6cb982..6147bacd22 100644 --- a/app/Factory/RecurrenceFactory.php +++ b/app/Factory/RecurrenceFactory.php @@ -102,18 +102,18 @@ class RecurrenceFactory $recurrence = new Recurrence( [ - 'user_id' => $this->user->id, - 'user_group_id' => $this->user->user_group_id, - 'transaction_type_id' => $type->id, - 'title' => $title, - 'description' => $description, - 'first_date' => $firstDate?->format('Y-m-d'), + 'user_id' => $this->user->id, + 'user_group_id' => $this->user->user_group_id, + 'transaction_type_id' => $type->id, + 'title' => $title, + 'description' => $description, + 'first_date' => $firstDate?->format('Y-m-d'), 'first_date_tz' => $firstDate?->format('e'), - 'repeat_until' => $repetitions > 0 ? null : $repeatUntilString, - 'latest_date' => null, - 'repetitions' => $repetitions, - 'apply_rules' => $applyRules, - 'active' => $active, + 'repeat_until' => $repetitions > 0 ? null : $repeatUntilString, + 'latest_date' => null, + 'repetitions' => $repetitions, + 'apply_rules' => $applyRules, + 'active' => $active, ] ); $recurrence->save(); diff --git a/app/Generator/Webhook/StandardMessageGenerator.php b/app/Generator/Webhook/StandardMessageGenerator.php index f2748f098b..f614feb292 100644 --- a/app/Generator/Webhook/StandardMessageGenerator.php +++ b/app/Generator/Webhook/StandardMessageGenerator.php @@ -83,11 +83,12 @@ class StandardMessageGenerator implements MessageGeneratorInterface private function getWebhooks(): Collection { return $this->user->webhooks() - ->leftJoin('webhook_webhook_trigger', 'webhook_webhook_trigger.webhook_id', 'webhooks.id') - ->leftJoin('webhook_triggers', 'webhook_webhook_trigger.webhook_trigger_id', 'webhook_triggers.id') - ->where('active', true) - ->whereIn('webhook_triggers.title', [$this->trigger->name, WebhookTrigger::ANY->name]) - ->get(['webhooks.*']); + ->leftJoin('webhook_webhook_trigger', 'webhook_webhook_trigger.webhook_id', 'webhooks.id') + ->leftJoin('webhook_triggers', 'webhook_webhook_trigger.webhook_trigger_id', 'webhook_triggers.id') + ->where('active', true) + ->whereIn('webhook_triggers.title', [$this->trigger->name, WebhookTrigger::ANY->name]) + ->get(['webhooks.*']) + ; } /** @@ -122,14 +123,15 @@ class StandardMessageGenerator implements MessageGeneratorInterface */ private function generateMessage(Webhook $webhook, Model $model): void { - $class = $model::class; + $class = $model::class; // Line is ignored because all of Firefly III's Models have an id property. Log::debug(sprintf('Now in generateMessage(#%d, %s#%d)', $webhook->id, $class, $model->id)); - $uuid = Uuid::uuid4(); + $uuid = Uuid::uuid4(); + /** @var WebhookResponseModel $response */ - $response = $webhook->webhookResponses()->first(); - $triggers = $this->getTriggerTitles($webhook->webhookTriggers()->get()); - $basicMessage = [ + $response = $webhook->webhookResponses()->first(); + $triggers = $this->getTriggerTitles($webhook->webhookTriggers()->get()); + $basicMessage = [ 'uuid' => $uuid->toString(), 'user_id' => 0, 'user_group_id' => 0, @@ -180,23 +182,23 @@ class StandardMessageGenerator implements MessageGeneratorInterface case WebhookResponse::BUDGET->name: $basicMessage['content'] = []; if ($model instanceof Budget) { - $enrichment = new BudgetEnrichment(); + $enrichment = new BudgetEnrichment(); $enrichment->setUser($model->user); $model = $enrichment->enrichSingle($model); $transformer = new BudgetTransformer(); $basicMessage['content'] = $transformer->transform($model); } if ($model instanceof BudgetLimit) { - $user = $model->budget->user; - $enrichment = new BudgetLimitEnrichment(); + $user = $model->budget->user; + $enrichment = new BudgetLimitEnrichment(); $enrichment->setUser($user); - $parameters = new ParameterBag(); + $parameters = new ParameterBag(); $parameters->set('start', $model->start_date); $parameters->set('end', $model->end_date); - $model = $enrichment->enrichSingle($model); - $transformer = new BudgetLimitTransformer(); + $model = $enrichment->enrichSingle($model); + $transformer = new BudgetLimitTransformer(); $transformer->setParameters($parameters); $basicMessage['content'] = $transformer->transform($model); } @@ -210,7 +212,7 @@ class StandardMessageGenerator implements MessageGeneratorInterface case WebhookResponse::TRANSACTIONS->name: /** @var TransactionGroup $model */ - $transformer = new TransactionGroupTransformer(); + $transformer = new TransactionGroupTransformer(); try { $basicMessage['content'] = $transformer->transformObject($model); @@ -227,13 +229,13 @@ class StandardMessageGenerator implements MessageGeneratorInterface case WebhookResponse::ACCOUNTS->name: /** @var TransactionGroup $model */ - $accounts = $this->collectAccounts($model); - $enrichment = new AccountEnrichment(); + $accounts = $this->collectAccounts($model); + $enrichment = new AccountEnrichment(); $enrichment->setDate(null); $enrichment->setUser($model->user); - $accounts = $enrichment->enrich($accounts); + $accounts = $enrichment->enrich($accounts); foreach ($accounts as $account) { - $transformer = new AccountTransformer(); + $transformer = new AccountTransformer(); $transformer->setParameters(new ParameterBag()); $basicMessage['content'][] = $transformer->transform($account); } @@ -263,7 +265,7 @@ class StandardMessageGenerator implements MessageGeneratorInterface private function storeMessage(Webhook $webhook, array $message): void { - $webhookMessage = new WebhookMessage(); + $webhookMessage = new WebhookMessage(); $webhookMessage->webhook()->associate($webhook); $webhookMessage->sent = false; $webhookMessage->errored = false; @@ -298,35 +300,44 @@ class StandardMessageGenerator implements MessageGeneratorInterface // return none if none. if (WebhookResponse::NONE->name === $response->title) { Log::debug(sprintf('Return "%s" because requested nothing.', WebhookResponse::NONE->name)); + return WebhookResponse::NONE->name; } if (WebhookResponse::RELEVANT->name === $response->title) { Log::debug('Expected response is any relevant data.'); + // depends on the $class switch ($class) { case TransactionGroup::class: Log::debug(sprintf('Return "%s" because class is %s', WebhookResponse::TRANSACTIONS->name, $class)); + return WebhookResponse::TRANSACTIONS->name; + case Budget::class: case BudgetLimit::class: Log::debug(sprintf('Return "%s" because class is %s', WebhookResponse::BUDGET->name, $class)); + return WebhookResponse::BUDGET->name; + default: throw new FireflyException(sprintf('Cannot deal with "relevant" if the given object is a "%s"', $class)); } } Log::debug(sprintf('Return response again: %s', $response->title)); + return $response->title; } private function getTriggerTitles(Collection $collection): array { $return = []; + /** @var WebhookTriggerModel $item */ foreach ($collection as $item) { $return[] = $item->title; } + return array_unique($return); } } diff --git a/app/Handlers/Observer/BudgetObserver.php b/app/Handlers/Observer/BudgetObserver.php index bdb8eb52e9..d7366d3a4b 100644 --- a/app/Handlers/Observer/BudgetObserver.php +++ b/app/Handlers/Observer/BudgetObserver.php @@ -25,7 +25,6 @@ namespace FireflyIII\Handlers\Observer; use FireflyIII\Enums\WebhookTrigger; use FireflyIII\Events\RequestedSendWebhookMessages; -use FireflyIII\Exceptions\FireflyException; use FireflyIII\Generator\Webhook\MessageGeneratorInterface; use FireflyIII\Models\Attachment; use FireflyIII\Models\Budget; diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index a4978d89bd..b829724308 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -31,10 +31,6 @@ use FireflyIII\Events\RequestedVersionCheckStatus; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Http\Middleware\Installer; -use FireflyIII\Models\Webhook; -use FireflyIII\Models\WebhookDelivery; -use FireflyIII\Models\WebhookResponse; -use FireflyIII\Models\WebhookTrigger; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Bill\BillRepositoryInterface; use FireflyIII\User; diff --git a/app/Models/Webhook.php b/app/Models/Webhook.php index a939084257..a836b7fad6 100644 --- a/app/Models/Webhook.php +++ b/app/Models/Webhook.php @@ -134,10 +134,10 @@ class Webhook extends Model $webhookId = (int)$value; /** @var User $user */ - $user = auth()->user(); + $user = auth()->user(); /** @var null|Webhook $webhook */ - $webhook = $user->webhooks()->find($webhookId); + $webhook = $user->webhooks()->find($webhookId); if (null !== $webhook) { return $webhook; } diff --git a/app/Models/WebhookDelivery.php b/app/Models/WebhookDelivery.php index 5538cf427d..7635c4de69 100644 --- a/app/Models/WebhookDelivery.php +++ b/app/Models/WebhookDelivery.php @@ -1,5 +1,7 @@ user->webhooks() // only get upgraded webhooks - ->where('delivery', 1) - ->where('response', 1) - ->where('trigger', 1) - ->get(); + ->where('delivery', 1) + ->where('response', 1) + ->where('trigger', 1) + ->get() + ; } public function destroy(Webhook $webhook): void @@ -79,26 +80,28 @@ class WebhookRepository implements WebhookRepositoryInterface, UserGroupInterfac public function getMessages(Webhook $webhook): Collection { return $webhook->webhookMessages() - ->orderBy('created_at', 'DESC') - ->get(['webhook_messages.*']); + ->orderBy('created_at', 'DESC') + ->get(['webhook_messages.*']) + ; } public function getReadyMessages(Webhook $webhook): Collection { return $webhook->webhookMessages() - ->where('webhook_messages.sent', 0) - ->where('webhook_messages.errored', 0) - ->get(['webhook_messages.*']) - ->filter( - static fn(WebhookMessage $message) // @phpstan-ignore-line - => $message->webhookAttempts()->count() <= 2 - )->splice(0, 3); + ->where('webhook_messages.sent', 0) + ->where('webhook_messages.errored', 0) + ->get(['webhook_messages.*']) + ->filter( + static fn (WebhookMessage $message) // @phpstan-ignore-line + => $message->webhookAttempts()->count() <= 2 + )->splice(0, 3) + ; } public function store(array $data): Webhook { - $secret = Str::random(24); - $fullData = [ + $secret = Str::random(24); + $fullData = [ 'user_id' => $this->user->id, 'user_group_id' => $this->user->user_group_id, 'active' => $data['active'] ?? false, @@ -112,6 +115,7 @@ class WebhookRepository implements WebhookRepositoryInterface, UserGroupInterfac 'secret' => $secret, 'url' => $data['url'], ]; + /** @var Webhook $webhook */ $webhook = Webhook::create($fullData); $triggers = new Collection(); @@ -154,8 +158,8 @@ class WebhookRepository implements WebhookRepositoryInterface, UserGroupInterfac public function update(Webhook $webhook, array $data): Webhook { $webhook->active = $data['active'] ?? $webhook->active; - $webhook->title = $data['title'] ?? $webhook->title; - $webhook->url = $data['url'] ?? $webhook->url; + $webhook->title = $data['title'] ?? $webhook->title; + $webhook->url = $data['url'] ?? $webhook->url; if (array_key_exists('secret', $data) && true === $data['secret']) { $secret = Str::random(24); @@ -164,9 +168,9 @@ class WebhookRepository implements WebhookRepositoryInterface, UserGroupInterfac $webhook->save(); - $triggers = new Collection(); - $responses = new Collection(); - $deliveries = new Collection(); + $triggers = new Collection(); + $responses = new Collection(); + $deliveries = new Collection(); foreach ($data['triggers'] as $trigger) { // get the relevant ID: diff --git a/app/Support/JsonApi/Enrichments/RecurringEnrichment.php b/app/Support/JsonApi/Enrichments/RecurringEnrichment.php index 5e6ca0e734..b128ddd442 100644 --- a/app/Support/JsonApi/Enrichments/RecurringEnrichment.php +++ b/app/Support/JsonApi/Enrichments/RecurringEnrichment.php @@ -137,7 +137,8 @@ class RecurringEnrichment implements EnrichmentInterface // get the (future) occurrences for this specific type of repetition: $amount = 'daily' === $repetition->repetition_type ? 9 : 5; $set = $repository->getXOccurrencesSince($repetition, $fromDate, now(config('app.timezone')), $amount); - $occurrences = []; + $occurrences = []; + /** @var Carbon $carbon */ foreach ($set as $carbon) { $occurrences[] = $carbon->toAtomString(); diff --git a/app/Support/JsonApi/Enrichments/WebhookEnrichment.php b/app/Support/JsonApi/Enrichments/WebhookEnrichment.php index acbc315c28..0a79545f4e 100644 --- a/app/Support/JsonApi/Enrichments/WebhookEnrichment.php +++ b/app/Support/JsonApi/Enrichments/WebhookEnrichment.php @@ -1,5 +1,7 @@ collection = $collection; @@ -40,10 +42,11 @@ class WebhookEnrichment implements EnrichmentInterface $this->collectWebhookInfo(); $this->appendCollectedInfo(); } + return $this->collection; } - public function enrichSingle(Model|array $model): array|Model + public function enrichSingle(array|Model $model): array|Model { Log::debug(__METHOD__); $collection = new Collection([$model]); @@ -74,16 +77,19 @@ class WebhookEnrichment implements EnrichmentInterface private function collectInfo(): void { $all = WebhookDelivery::get(); + /** @var WebhookDelivery $item */ foreach ($all as $item) { $this->deliveries[$item->id] = $item->key; } $all = WebhookResponse::get(); + /** @var WebhookResponse $item */ foreach ($all as $item) { $this->responses[$item->id] = $item->key; } $all = WebhookTrigger::get(); + /** @var WebhookTrigger $item */ foreach ($all as $item) { $this->triggers[$item->id] = $item->key; @@ -94,7 +100,8 @@ class WebhookEnrichment implements EnrichmentInterface private function collectWebhookInfo(): void { $set = DB::table('webhook_webhook_delivery')->whereIn('webhook_id', $this->ids)->get(['webhook_id', 'webhook_delivery_id']); - /** @var \stdClass $item */ + + /** @var stdClass $item */ foreach ($set as $item) { $id = $item->webhook_id; $deliveryId = $item->webhook_delivery_id; @@ -102,7 +109,8 @@ class WebhookEnrichment implements EnrichmentInterface } $set = DB::table('webhook_webhook_response')->whereIn('webhook_id', $this->ids)->get(['webhook_id', 'webhook_response_id']); - /** @var \stdClass $item */ + + /** @var stdClass $item */ foreach ($set as $item) { $id = $item->webhook_id; $responseId = $item->webhook_response_id; @@ -110,7 +118,8 @@ class WebhookEnrichment implements EnrichmentInterface } $set = DB::table('webhook_webhook_trigger')->whereIn('webhook_id', $this->ids)->get(['webhook_id', 'webhook_trigger_id']); - /** @var \stdClass $item */ + + /** @var stdClass $item */ foreach ($set as $item) { $id = $item->webhook_id; $triggerId = $item->webhook_trigger_id; @@ -127,8 +136,8 @@ class WebhookEnrichment implements EnrichmentInterface 'triggers' => $this->webhookTriggers[$item->id] ?? [], ]; $item->meta = $meta; + return $item; }); } - } diff --git a/app/Support/Request/ConvertsDataTypes.php b/app/Support/Request/ConvertsDataTypes.php index 8162b5256f..3e40f57113 100644 --- a/app/Support/Request/ConvertsDataTypes.php +++ b/app/Support/Request/ConvertsDataTypes.php @@ -255,7 +255,7 @@ trait ConvertsDataTypes if (10 === strlen((string) $value)) { // probably a date format. try { - $carbon = Carbon::createFromFormat('Y-m-d', $value,config('app.timezone')); + $carbon = Carbon::createFromFormat('Y-m-d', $value, config('app.timezone')); } catch (InvalidDateException $e) { // @phpstan-ignore-line Log::error(sprintf('[1] "%s" is not a valid date: %s', $value, $e->getMessage())); @@ -276,7 +276,7 @@ trait ConvertsDataTypes // is an atom string, I hope? try { - $carbon = Carbon::parse($value, $value,config('app.timezone')); + $carbon = Carbon::parse($value, $value, config('app.timezone')); } catch (InvalidDateException $e) { // @phpstan-ignore-line Log::error(sprintf('[3] "%s" is not a valid date or time: %s', $value, $e->getMessage())); diff --git a/app/Support/Request/ValidatesWebhooks.php b/app/Support/Request/ValidatesWebhooks.php index 0bc1649515..eb580e6e86 100644 --- a/app/Support/Request/ValidatesWebhooks.php +++ b/app/Support/Request/ValidatesWebhooks.php @@ -1,5 +1,7 @@ failed()) { return; } - $data = $validator->getData(); - $triggers = $data['triggers'] ?? []; - $responses = $data['responses'] ?? []; + $data = $validator->getData(); + $triggers = $data['triggers'] ?? []; + $responses = $data['responses'] ?? []; if (0 === count($triggers) || 0 === count($responses)) { Log::debug('No trigger or response, return.'); @@ -28,19 +30,20 @@ trait ValidatesWebhooks } $validTriggers = array_values(Webhook::getTriggers()); $validResponses = array_values(Webhook::getResponses()); - $containsAny = false; - $count = 0; + $containsAny = false; + $count = 0; foreach ($triggers as $trigger) { if (!in_array($trigger, $validTriggers, true)) { return; } - $count++; - if($trigger === WebhookTrigger::ANY->name) { + ++$count; + if ($trigger === WebhookTrigger::ANY->name) { $containsAny = true; } } - if($containsAny && $count > 1) { + if ($containsAny && $count > 1) { $validator->errors()->add('triggers.0', trans('validation.only_any_trigger')); + return; } foreach ($responses as $response) { @@ -52,13 +55,15 @@ trait ValidatesWebhooks foreach ($triggers as $i => $trigger) { $forbidden = config(sprintf('webhooks.forbidden_responses.%s', $trigger)); if (null === $forbidden) { - $validator->errors()->add(sprintf('triggers.%d', $i), trans('validation.unknown_webhook_trigger', ['trigger' => $trigger,])); + $validator->errors()->add(sprintf('triggers.%d', $i), trans('validation.unknown_webhook_trigger', ['trigger' => $trigger])); + continue; } foreach ($responses as $ii => $response) { if (in_array($response, $forbidden, true)) { Log::debug(sprintf('Trigger %s and response %s are forbidden.', $trigger, $response)); - $validator->errors()->add(sprintf('responses.%d', $ii), trans('validation.bad_webhook_combination', ['trigger' => $trigger, 'response' => $response,])); + $validator->errors()->add(sprintf('responses.%d', $ii), trans('validation.bad_webhook_combination', ['trigger' => $trigger, 'response' => $response])); + return; } } diff --git a/app/Transformers/WebhookTransformer.php b/app/Transformers/WebhookTransformer.php index 5404982f34..d3213613fa 100644 --- a/app/Transformers/WebhookTransformer.php +++ b/app/Transformers/WebhookTransformer.php @@ -51,12 +51,12 @@ class WebhookTransformer extends AbstractTransformer 'active' => $webhook->active, 'title' => $webhook->title, 'secret' => $webhook->secret, - 'triggers' => $webhook->meta['triggers'], + 'triggers' => $webhook->meta['triggers'], 'deliveries' => $webhook->meta['deliveries'], - 'responses' => $webhook->meta['responses'], -// 'trigger' => $this->getEnum('trigger', $webhook->trigger), -// 'response' => $this->getEnum('response', $webhook->response), -// 'delivery' => $this->getEnum('delivery', $webhook->delivery), + 'responses' => $webhook->meta['responses'], + // 'trigger' => $this->getEnum('trigger', $webhook->trigger), + // 'response' => $this->getEnum('response', $webhook->response), + // 'delivery' => $this->getEnum('delivery', $webhook->delivery), 'url' => $webhook->url, 'links' => [ [ diff --git a/composer.lock b/composer.lock index 0d01a6e935..dc2af06834 100644 --- a/composer.lock +++ b/composer.lock @@ -4734,16 +4734,16 @@ }, { "name": "phpoption/phpoption", - "version": "1.9.3", + "version": "1.9.4", "source": { "type": "git", "url": "https://github.com/schmittjoh/php-option.git", - "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54" + "reference": "638a154f8d4ee6a5cfa96d6a34dfbe0cffa9566d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/e3fac8b24f56113f7cb96af14958c0dd16330f54", - "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/638a154f8d4ee6a5cfa96d6a34dfbe0cffa9566d", + "reference": "638a154f8d4ee6a5cfa96d6a34dfbe0cffa9566d", "shasum": "" }, "require": { @@ -4751,7 +4751,7 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28" + "phpunit/phpunit": "^8.5.44 || ^9.6.25 || ^10.5.53 || ^11.5.34" }, "type": "library", "extra": { @@ -4793,7 +4793,7 @@ ], "support": { "issues": "https://github.com/schmittjoh/php-option/issues", - "source": "https://github.com/schmittjoh/php-option/tree/1.9.3" + "source": "https://github.com/schmittjoh/php-option/tree/1.9.4" }, "funding": [ { @@ -4805,7 +4805,7 @@ "type": "tidelift" } ], - "time": "2024-07-20T21:41:07+00:00" + "time": "2025-08-21T11:53:16+00:00" }, { "name": "phpseclib/phpseclib", @@ -7956,7 +7956,7 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", @@ -8015,7 +8015,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0" }, "funding": [ { @@ -8026,6 +8026,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -8035,16 +8039,16 @@ }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", - "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", "shasum": "" }, "require": { @@ -8093,7 +8097,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0" }, "funding": [ { @@ -8104,16 +8108,20 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2025-06-27T09:58:17+00:00" }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", @@ -8176,7 +8184,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.33.0" }, "funding": [ { @@ -8187,6 +8195,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -8196,7 +8208,7 @@ }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", @@ -8257,7 +8269,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0" }, "funding": [ { @@ -8268,6 +8280,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -8277,7 +8293,7 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", @@ -8338,7 +8354,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" }, "funding": [ { @@ -8349,6 +8365,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -8358,7 +8378,7 @@ }, { "name": "symfony/polyfill-php80", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", @@ -8418,7 +8438,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0" }, "funding": [ { @@ -8429,6 +8449,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -8438,16 +8462,16 @@ }, { "name": "symfony/polyfill-php83", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php83.git", - "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491" + "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/2fb86d65e2d424369ad2905e83b236a8805ba491", - "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/17f6f9a6b1735c0f163024d959f700cfbc5155e5", + "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5", "shasum": "" }, "require": { @@ -8494,7 +8518,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.33.0" }, "funding": [ { @@ -8505,25 +8529,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2025-07-08T02:45:35+00:00" }, { "name": "symfony/polyfill-php84", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php84.git", - "reference": "000df7860439609837bbe28670b0be15783b7fbf" + "reference": "d8ced4d875142b6a7426000426b8abc631d6b191" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/000df7860439609837bbe28670b0be15783b7fbf", - "reference": "000df7860439609837bbe28670b0be15783b7fbf", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/d8ced4d875142b6a7426000426b8abc631d6b191", + "reference": "d8ced4d875142b6a7426000426b8abc631d6b191", "shasum": "" }, "require": { @@ -8570,7 +8598,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php84/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-php84/tree/v1.33.0" }, "funding": [ { @@ -8581,25 +8609,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-02-20T12:04:08+00:00" + "time": "2025-06-24T13:30:11+00:00" }, { "name": "symfony/polyfill-php85", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php85.git", - "reference": "6fedf31ce4e3648f4ff5ca58bfd53127d38f05fd" + "reference": "d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/6fedf31ce4e3648f4ff5ca58bfd53127d38f05fd", - "reference": "6fedf31ce4e3648f4ff5ca58bfd53127d38f05fd", + "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91", + "reference": "d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91", "shasum": "" }, "require": { @@ -8646,7 +8678,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php85/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-php85/tree/v1.33.0" }, "funding": [ { @@ -8657,16 +8689,20 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-05-02T08:40:52+00:00" + "time": "2025-06-23T16:12:55+00:00" }, { "name": "symfony/polyfill-uuid", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-uuid.git", @@ -8725,7 +8761,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/polyfill-uuid/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-uuid/tree/v1.33.0" }, "funding": [ { @@ -8736,6 +8772,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -10348,16 +10388,16 @@ }, { "name": "composer/class-map-generator", - "version": "1.6.1", + "version": "1.6.2", "source": { "type": "git", "url": "https://github.com/composer/class-map-generator.git", - "reference": "134b705ddb0025d397d8318a75825fe3c9d1da34" + "reference": "ba9f089655d4cdd64e762a6044f411ccdaec0076" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/class-map-generator/zipball/134b705ddb0025d397d8318a75825fe3c9d1da34", - "reference": "134b705ddb0025d397d8318a75825fe3c9d1da34", + "url": "https://api.github.com/repos/composer/class-map-generator/zipball/ba9f089655d4cdd64e762a6044f411ccdaec0076", + "reference": "ba9f089655d4cdd64e762a6044f411ccdaec0076", "shasum": "" }, "require": { @@ -10401,7 +10441,7 @@ ], "support": { "issues": "https://github.com/composer/class-map-generator/issues", - "source": "https://github.com/composer/class-map-generator/tree/1.6.1" + "source": "https://github.com/composer/class-map-generator/tree/1.6.2" }, "funding": [ { @@ -10411,13 +10451,9 @@ { "url": "https://github.com/composer", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" } ], - "time": "2025-03-24T13:50:44+00:00" + "time": "2025-08-20T18:52:43+00:00" }, { "name": "composer/pcre", @@ -10500,16 +10536,16 @@ }, { "name": "driftingly/rector-laravel", - "version": "2.0.6", + "version": "2.0.7", "source": { "type": "git", "url": "https://github.com/driftingly/rector-laravel.git", - "reference": "5be95811801fc06126dd844beaeb6a41721ba3d3" + "reference": "625dc02cee08d47ecf0ac86de2f02a55026cf34e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/driftingly/rector-laravel/zipball/5be95811801fc06126dd844beaeb6a41721ba3d3", - "reference": "5be95811801fc06126dd844beaeb6a41721ba3d3", + "url": "https://api.github.com/repos/driftingly/rector-laravel/zipball/625dc02cee08d47ecf0ac86de2f02a55026cf34e", + "reference": "625dc02cee08d47ecf0ac86de2f02a55026cf34e", "shasum": "" }, "require": { @@ -10529,9 +10565,9 @@ "description": "Rector upgrades rules for Laravel Framework", "support": { "issues": "https://github.com/driftingly/rector-laravel/issues", - "source": "https://github.com/driftingly/rector-laravel/tree/2.0.6" + "source": "https://github.com/driftingly/rector-laravel/tree/2.0.7" }, - "time": "2025-08-08T22:10:01+00:00" + "time": "2025-08-19T20:49:47+00:00" }, { "name": "fakerphp/faker", @@ -11771,16 +11807,16 @@ }, { "name": "phpunit/phpunit", - "version": "12.3.5", + "version": "12.3.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "f10ba5f12a256026ad3c7ee4894ffe47f60d7dc7" + "reference": "a2cab3224f687150ac2f3cc13d99b64ba1e1d088" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f10ba5f12a256026ad3c7ee4894ffe47f60d7dc7", - "reference": "f10ba5f12a256026ad3c7ee4894ffe47f60d7dc7", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a2cab3224f687150ac2f3cc13d99b64ba1e1d088", + "reference": "a2cab3224f687150ac2f3cc13d99b64ba1e1d088", "shasum": "" }, "require": { @@ -11800,7 +11836,7 @@ "phpunit/php-text-template": "^5.0.0", "phpunit/php-timer": "^8.0.0", "sebastian/cli-parser": "^4.0.0", - "sebastian/comparator": "^7.1.2", + "sebastian/comparator": "^7.1.3", "sebastian/diff": "^7.0.0", "sebastian/environment": "^8.0.3", "sebastian/exporter": "^7.0.0", @@ -11848,7 +11884,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.5" + "source": "https://github.com/sebastianbergmann/phpunit/tree/12.3.6" }, "funding": [ { @@ -11872,7 +11908,7 @@ "type": "tidelift" } ], - "time": "2025-08-16T05:20:09+00:00" + "time": "2025-08-20T14:43:23+00:00" }, { "name": "rector/rector", @@ -11993,16 +12029,16 @@ }, { "name": "sebastian/comparator", - "version": "7.1.2", + "version": "7.1.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "1a7c2bce03a13a457ed3c975dfd331b3b4b133aa" + "reference": "dc904b4bb3ab070865fa4068cd84f3da8b945148" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/1a7c2bce03a13a457ed3c975dfd331b3b4b133aa", - "reference": "1a7c2bce03a13a457ed3c975dfd331b3b4b133aa", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/dc904b4bb3ab070865fa4068cd84f3da8b945148", + "reference": "dc904b4bb3ab070865fa4068cd84f3da8b945148", "shasum": "" }, "require": { @@ -12061,7 +12097,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/7.1.2" + "source": "https://github.com/sebastianbergmann/comparator/tree/7.1.3" }, "funding": [ { @@ -12081,7 +12117,7 @@ "type": "tidelift" } ], - "time": "2025-08-10T08:50:08+00:00" + "time": "2025-08-20T11:27:00+00:00" }, { "name": "sebastian/complexity", diff --git a/config/firefly.php b/config/firefly.php index 0e19932477..8d3fe2ba39 100644 --- a/config/firefly.php +++ b/config/firefly.php @@ -78,8 +78,8 @@ return [ 'running_balance_column' => env('USE_RUNNING_BALANCE', false), // see cer.php for exchange rates feature flag. ], - 'version' => '6.3.2', - 'build_time' => 1755576388, + 'version' => 'develop/2025-08-22', + 'build_time' => 1755838953, 'api_version' => '2.1.0', // field is no longer used. 'db_version' => 26, diff --git a/config/webhooks.php b/config/webhooks.php index 323a710c8f..75ff4bddcd 100644 --- a/config/webhooks.php +++ b/config/webhooks.php @@ -1,5 +1,7 @@ name, ], ], - 'forbidden_responses' => [ - WebhookTrigger::ANY->name => [ + 'forbidden_responses' => [ + WebhookTrigger::ANY->name => [ WebhookResponse::BUDGET->name, WebhookResponse::TRANSACTIONS->name, WebhookResponse::ACCOUNTS->name, @@ -80,5 +82,5 @@ return [ WebhookResponse::TRANSACTIONS->name, WebhookResponse::ACCOUNTS->name, ], - ] + ], ]; diff --git a/package-lock.json b/package-lock.json index 58048c9383..e1b2916c61 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2592,9 +2592,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.46.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.3.tgz", - "integrity": "sha512-UmTdvXnLlqQNOCJnyksjPs1G4GqXNGW1LrzCe8+8QoaLhhDeTXYBgJ3k6x61WIhlHX2U+VzEJ55TtIjR/HTySA==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.47.1.tgz", + "integrity": "sha512-lTahKRJip0knffA/GTNFJMrToD+CM+JJ+Qt5kjzBK/sFQ0EWqfKW3AYQSlZXN98tX0lx66083U9JYIMioMMK7g==", "cpu": [ "arm" ], @@ -2606,9 +2606,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.46.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.3.tgz", - "integrity": "sha512-8NoxqLpXm7VyeI0ocidh335D6OKT0UJ6fHdnIxf3+6oOerZZc+O7r+UhvROji6OspyPm+rrIdb1gTXtVIqn+Sg==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.47.1.tgz", + "integrity": "sha512-uqxkb3RJLzlBbh/bbNQ4r7YpSZnjgMgyoEOY7Fy6GCbelkDSAzeiogxMG9TfLsBbqmGsdDObo3mzGqa8hps4MA==", "cpu": [ "arm64" ], @@ -2620,9 +2620,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.46.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.3.tgz", - "integrity": "sha512-csnNavqZVs1+7/hUKtgjMECsNG2cdB8F7XBHP6FfQjqhjF8rzMzb3SLyy/1BG7YSfQ+bG75Ph7DyedbUqwq1rA==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.47.1.tgz", + "integrity": "sha512-tV6reObmxBDS4DDyLzTDIpymthNlxrLBGAoQx6m2a7eifSNEZdkXQl1PE4ZjCkEDPVgNXSzND/k9AQ3mC4IOEQ==", "cpu": [ "arm64" ], @@ -2634,9 +2634,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.46.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.3.tgz", - "integrity": "sha512-r2MXNjbuYabSIX5yQqnT8SGSQ26XQc8fmp6UhlYJd95PZJkQD1u82fWP7HqvGUf33IsOC6qsiV+vcuD4SDP6iw==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.47.1.tgz", + "integrity": "sha512-XuJRPTnMk1lwsSnS3vYyVMu4x/+WIw1MMSiqj5C4j3QOWsMzbJEK90zG+SWV1h0B1ABGCQ0UZUjti+TQK35uHQ==", "cpu": [ "x64" ], @@ -2648,9 +2648,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.46.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.3.tgz", - "integrity": "sha512-uluObTmgPJDuJh9xqxyr7MV61Imq+0IvVsAlWyvxAaBSNzCcmZlhfYcRhCdMaCsy46ccZa7vtDDripgs9Jkqsw==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.47.1.tgz", + "integrity": "sha512-79BAm8Ag/tmJ5asCqgOXsb3WY28Rdd5Lxj8ONiQzWzy9LvWORd5qVuOnjlqiWWZJw+dWewEktZb5yiM1DLLaHw==", "cpu": [ "arm64" ], @@ -2662,9 +2662,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.46.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.3.tgz", - "integrity": "sha512-AVJXEq9RVHQnejdbFvh1eWEoobohUYN3nqJIPI4mNTMpsyYN01VvcAClxflyk2HIxvLpRcRggpX1m9hkXkpC/A==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.47.1.tgz", + "integrity": "sha512-OQ2/ZDGzdOOlyfqBiip0ZX/jVFekzYrGtUsqAfLDbWy0jh1PUU18+jYp8UMpqhly5ltEqotc2miLngf9FPSWIA==", "cpu": [ "x64" ], @@ -2676,9 +2676,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.46.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.3.tgz", - "integrity": "sha512-byyflM+huiwHlKi7VHLAYTKr67X199+V+mt1iRgJenAI594vcmGGddWlu6eHujmcdl6TqSNnvqaXJqZdnEWRGA==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.47.1.tgz", + "integrity": "sha512-HZZBXJL1udxlCVvoVadstgiU26seKkHbbAMLg7680gAcMnRNP9SAwTMVet02ANA94kXEI2VhBnXs4e5nf7KG2A==", "cpu": [ "arm" ], @@ -2690,9 +2690,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.46.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.3.tgz", - "integrity": "sha512-aLm3NMIjr4Y9LklrH5cu7yybBqoVCdr4Nvnm8WB7PKCn34fMCGypVNpGK0JQWdPAzR/FnoEoFtlRqZbBBLhVoQ==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.47.1.tgz", + "integrity": "sha512-sZ5p2I9UA7T950JmuZ3pgdKA6+RTBr+0FpK427ExW0t7n+QwYOcmDTK/aRlzoBrWyTpJNlS3kacgSlSTUg6P/Q==", "cpu": [ "arm" ], @@ -2704,9 +2704,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.46.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.3.tgz", - "integrity": "sha512-VtilE6eznJRDIoFOzaagQodUksTEfLIsvXymS+UdJiSXrPW7Ai+WG4uapAc3F7Hgs791TwdGh4xyOzbuzIZrnw==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.47.1.tgz", + "integrity": "sha512-3hBFoqPyU89Dyf1mQRXCdpc6qC6At3LV6jbbIOZd72jcx7xNk3aAp+EjzAtN6sDlmHFzsDJN5yeUySvorWeRXA==", "cpu": [ "arm64" ], @@ -2718,9 +2718,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.46.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.3.tgz", - "integrity": "sha512-dG3JuS6+cRAL0GQ925Vppafi0qwZnkHdPeuZIxIPXqkCLP02l7ka+OCyBoDEv8S+nKHxfjvjW4OZ7hTdHkx8/w==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.47.1.tgz", + "integrity": "sha512-49J4FnMHfGodJWPw73Ve+/hsPjZgcXQGkmqBGZFvltzBKRS+cvMiWNLadOMXKGnYRhs1ToTGM0sItKISoSGUNA==", "cpu": [ "arm64" ], @@ -2732,9 +2732,9 @@ ] }, "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.46.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.3.tgz", - "integrity": "sha512-iU8DxnxEKJptf8Vcx4XvAUdpkZfaz0KWfRrnIRrOndL0SvzEte+MTM7nDH4A2Now4FvTZ01yFAgj6TX/mZl8hQ==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.47.1.tgz", + "integrity": "sha512-4yYU8p7AneEpQkRX03pbpLmE21z5JNys16F1BZBZg5fP9rIlb0TkeQjn5du5w4agConCCEoYIG57sNxjryHEGg==", "cpu": [ "loong64" ], @@ -2746,9 +2746,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.46.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.3.tgz", - "integrity": "sha512-VrQZp9tkk0yozJoQvQcqlWiqaPnLM6uY1qPYXvukKePb0fqaiQtOdMJSxNFUZFsGw5oA5vvVokjHrx8a9Qsz2A==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.47.1.tgz", + "integrity": "sha512-fAiq+J28l2YMWgC39jz/zPi2jqc0y3GSRo1yyxlBHt6UN0yYgnegHSRPa3pnHS5amT/efXQrm0ug5+aNEu9UuQ==", "cpu": [ "ppc64" ], @@ -2760,9 +2760,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.46.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.3.tgz", - "integrity": "sha512-uf2eucWSUb+M7b0poZ/08LsbcRgaDYL8NCGjUeFMwCWFwOuFcZ8D9ayPl25P3pl+D2FH45EbHdfyUesQ2Lt9wA==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.47.1.tgz", + "integrity": "sha512-daoT0PMENNdjVYYU9xec30Y2prb1AbEIbb64sqkcQcSaR0zYuKkoPuhIztfxuqN82KYCKKrj+tQe4Gi7OSm1ow==", "cpu": [ "riscv64" ], @@ -2774,9 +2774,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.46.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.3.tgz", - "integrity": "sha512-7tnUcDvN8DHm/9ra+/nF7lLzYHDeODKKKrh6JmZejbh1FnCNZS8zMkZY5J4sEipy2OW1d1Ncc4gNHUd0DLqkSg==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.47.1.tgz", + "integrity": "sha512-JNyXaAhWtdzfXu5pUcHAuNwGQKevR+6z/poYQKVW+pLaYOj9G1meYc57/1Xv2u4uTxfu9qEWmNTjv/H/EpAisw==", "cpu": [ "riscv64" ], @@ -2788,9 +2788,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.46.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.3.tgz", - "integrity": "sha512-MUpAOallJim8CsJK+4Lc9tQzlfPbHxWDrGXZm2z6biaadNpvh3a5ewcdat478W+tXDoUiHwErX/dOql7ETcLqg==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.47.1.tgz", + "integrity": "sha512-U/CHbqKSwEQyZXjCpY43/GLYcTVKEXeRHw0rMBJP7fP3x6WpYG4LTJWR3ic6TeYKX6ZK7mrhltP4ppolyVhLVQ==", "cpu": [ "s390x" ], @@ -2802,9 +2802,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.46.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.3.tgz", - "integrity": "sha512-F42IgZI4JicE2vM2PWCe0N5mR5vR0gIdORPqhGQ32/u1S1v3kLtbZ0C/mi9FFk7C5T0PgdeyWEPajPjaUpyoKg==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.47.1.tgz", + "integrity": "sha512-uTLEakjxOTElfeZIGWkC34u2auLHB1AYS6wBjPGI00bWdxdLcCzK5awjs25YXpqB9lS8S0vbO0t9ZcBeNibA7g==", "cpu": [ "x64" ], @@ -2816,9 +2816,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.46.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.3.tgz", - "integrity": "sha512-oLc+JrwwvbimJUInzx56Q3ujL3Kkhxehg7O1gWAYzm8hImCd5ld1F2Gry5YDjR21MNb5WCKhC9hXgU7rRlyegQ==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.47.1.tgz", + "integrity": "sha512-Ft+d/9DXs30BK7CHCTX11FtQGHUdpNDLJW0HHLign4lgMgBcPFN3NkdIXhC5r9iwsMwYreBBc4Rho5ieOmKNVQ==", "cpu": [ "x64" ], @@ -2830,9 +2830,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.46.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.3.tgz", - "integrity": "sha512-lOrQ+BVRstruD1fkWg9yjmumhowR0oLAAzavB7yFSaGltY8klttmZtCLvOXCmGE9mLIn8IBV/IFrQOWz5xbFPg==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.47.1.tgz", + "integrity": "sha512-N9X5WqGYzZnjGAFsKSfYFtAShYjwOmFJoWbLg3dYixZOZqU7hdMq+/xyS14zKLhFhZDhP9VfkzQnsdk0ZDS9IA==", "cpu": [ "arm64" ], @@ -2844,9 +2844,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.46.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.3.tgz", - "integrity": "sha512-vvrVKPRS4GduGR7VMH8EylCBqsDcw6U+/0nPDuIjXQRbHJc6xOBj+frx8ksfZAh6+Fptw5wHrN7etlMmQnPQVg==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.47.1.tgz", + "integrity": "sha512-O+KcfeCORZADEY8oQJk4HK8wtEOCRE4MdOkb8qGZQNun3jzmj2nmhV/B/ZaaZOkPmJyvm/gW9n0gsB4eRa1eiQ==", "cpu": [ "ia32" ], @@ -2858,9 +2858,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.46.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.3.tgz", - "integrity": "sha512-fi3cPxCnu3ZeM3EwKZPgXbWoGzm2XHgB/WShKI81uj8wG0+laobmqy5wbgEwzstlbLu4MyO8C19FyhhWseYKNQ==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.47.1.tgz", + "integrity": "sha512-CpKnYa8eHthJa3c+C38v/E+/KZyF1Jdh2Cz3DyKZqEWYgrM1IHFArXNWvBLPQCKUEsAqqKX27tTqVEFbDNUcOA==", "cpu": [ "x64" ], @@ -3158,9 +3158,9 @@ } }, "node_modules/@types/node-forge": { - "version": "1.3.13", - "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.13.tgz", - "integrity": "sha512-zePQJSW5QkwSHKRApqWCVKeKoSOt4xvEnLENZPjyvm9Ezdf/EyDeJM7jqLzOwjVICQQzvLZ63T55MKdJB5H6ww==", + "version": "1.3.14", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.14.tgz", + "integrity": "sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==", "dev": true, "license": "MIT", "dependencies": { @@ -3256,42 +3256,42 @@ } }, "node_modules/@vue/compiler-core": { - "version": "3.5.18", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.18.tgz", - "integrity": "sha512-3slwjQrrV1TO8MoXgy3aynDQ7lslj5UqDxuHnrzHtpON5CBinhWjJETciPngpin/T3OuW3tXUf86tEurusnztw==", + "version": "3.5.19", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.19.tgz", + "integrity": "sha512-/afpyvlkrSNYbPo94Qu8GtIOWS+g5TRdOvs6XZNw6pWQQmj5pBgSZvEPOIZlqWq0YvoUhDDQaQ2TnzuJdOV4hA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.0", - "@vue/shared": "3.5.18", + "@babel/parser": "^7.28.3", + "@vue/shared": "3.5.19", "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "node_modules/@vue/compiler-dom": { - "version": "3.5.18", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.18.tgz", - "integrity": "sha512-RMbU6NTU70++B1JyVJbNbeFkK+A+Q7y9XKE2EM4NLGm2WFR8x9MbAtWxPPLdm0wUkuZv9trpwfSlL6tjdIa1+A==", + "version": "3.5.19", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.19.tgz", + "integrity": "sha512-Drs6rPHQZx/pN9S6ml3Z3K/TWCIRPvzG2B/o5kFK9X0MNHt8/E+38tiRfojufrYBfA6FQUFB2qBBRXlcSXWtOA==", "dev": true, "license": "MIT", "dependencies": { - "@vue/compiler-core": "3.5.18", - "@vue/shared": "3.5.18" + "@vue/compiler-core": "3.5.19", + "@vue/shared": "3.5.19" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.5.18", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.18.tgz", - "integrity": "sha512-5aBjvGqsWs+MoxswZPoTB9nSDb3dhd1x30xrrltKujlCxo48j8HGDNj3QPhF4VIS0VQDUrA1xUfp2hEa+FNyXA==", + "version": "3.5.19", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.19.tgz", + "integrity": "sha512-YWCm1CYaJ+2RvNmhCwI7t3I3nU+hOrWGWMsn+Z/kmm1jy5iinnVtlmkiZwbLlbV1SRizX7vHsc0/bG5dj0zRTg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.0", - "@vue/compiler-core": "3.5.18", - "@vue/compiler-dom": "3.5.18", - "@vue/compiler-ssr": "3.5.18", - "@vue/shared": "3.5.18", + "@babel/parser": "^7.28.3", + "@vue/compiler-core": "3.5.19", + "@vue/compiler-dom": "3.5.19", + "@vue/compiler-ssr": "3.5.19", + "@vue/shared": "3.5.19", "estree-walker": "^2.0.2", "magic-string": "^0.30.17", "postcss": "^8.5.6", @@ -3299,14 +3299,14 @@ } }, "node_modules/@vue/compiler-ssr": { - "version": "3.5.18", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.18.tgz", - "integrity": "sha512-xM16Ak7rSWHkM3m22NlmcdIM+K4BMyFARAfV9hYFl+SFuRzrZ3uGMNW05kA5pmeMa0X9X963Kgou7ufdbpOP9g==", + "version": "3.5.19", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.19.tgz", + "integrity": "sha512-/wx0VZtkWOPdiQLWPeQeqpHWR/LuNC7bHfSX7OayBTtUy8wur6vT6EQIX6Et86aED6J+y8tTw43qo2uoqGg5sw==", "dev": true, "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.5.18", - "@vue/shared": "3.5.18" + "@vue/compiler-dom": "3.5.19", + "@vue/shared": "3.5.19" } }, "node_modules/@vue/component-compiler-utils": { @@ -3388,9 +3388,9 @@ "license": "MIT" }, "node_modules/@vue/shared": { - "version": "3.5.18", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.18.tgz", - "integrity": "sha512-cZy8Dq+uuIXbxCZpuLd2GJdeSO/lIzIspC2WtkqIpje5QyFbvLaI5wZtdUjLHjGZrlVX6GilejatWwVYYRc8tA==", + "version": "3.5.19", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.19.tgz", + "integrity": "sha512-IhXCOn08wgKrLQxRFKKlSacWg4Goi1BolrdEeLYn6tgHjJNXVrWJ5nzoxZqNwl5p88aLlQ8LOaoMa3AYvaKJ/Q==", "dev": true, "license": "MIT" }, @@ -4486,9 +4486,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001735", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001735.tgz", - "integrity": "sha512-EV/laoX7Wq2J9TQlyIXRxTJqIw4sxfXS4OYgudGxBYRuTv0q7AM6yMEpU/Vo1I94thg9U6EZ2NfZx9GJq83u7w==", + "version": "1.0.30001736", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001736.tgz", + "integrity": "sha512-ImpN5gLEY8gWeqfLUyEF4b7mYWcYoR2Si1VhnrbM4JizRFmfGaAQ12PhNykq6nvI4XvKLrsp8Xde74D5phJOSw==", "dev": true, "funding": [ { @@ -4930,13 +4930,13 @@ "license": "MIT" }, "node_modules/core-js-compat": { - "version": "3.45.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.45.0.tgz", - "integrity": "sha512-gRoVMBawZg0OnxaVv3zpqLLxaHmsubEGyTnqdpI/CEBvX4JadI1dMSHxagThprYRtSVbuQxvi6iUatdPxohHpA==", + "version": "3.45.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.45.1.tgz", + "integrity": "sha512-tqTt5T4PzsMIZ430XGviK4vzYSoeNJ6CXODi6c/voxOT6IZqBht5/EKaSNnYiEjjRYxjVz7DQIsOsY0XNi8PIA==", "dev": true, "license": "MIT", "dependencies": { - "browserslist": "^4.25.1" + "browserslist": "^4.25.3" }, "funding": { "type": "opencollective", @@ -5700,9 +5700,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.205", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.205.tgz", - "integrity": "sha512-gBtbT7IgOHu7CwdtIiXwbNRD1l6oG6GAyanmwMCLVqaoGy92Jfe1dSHLiSj8xUEZNxOTIVlXuaAalMMD+S4y0w==", + "version": "1.5.208", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.208.tgz", + "integrity": "sha512-ozZyibehoe7tOhNaf16lKmljVf+3npZcJIEbJRVftVsmAg5TeA1mGS9dVCZzOwr2xT7xK15V0p7+GZqSPgkuPg==", "dev": true, "license": "ISC" }, @@ -7052,9 +7052,9 @@ } }, "node_modules/i18next": { - "version": "25.3.6", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.3.6.tgz", - "integrity": "sha512-dThZ0CTCM3sUG/qS0ZtQYZQcUI6DtBN8yBHK+SKEqihPcEYmjVWh/YJ4luic73Iq6Uxhp6q7LJJntRK5+1t7jQ==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.4.0.tgz", + "integrity": "sha512-UH5aiamXsO3cfrZFurCHiB6YSs3C+s+XY9UaJllMMSbmaoXILxFgqDEZu4NbfzJFjmUo3BNMa++Rjkr3ofjfLw==", "funding": [ { "type": "individual", @@ -7958,13 +7958,13 @@ } }, "node_modules/magic-string": { - "version": "0.30.17", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", - "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "version": "0.30.18", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.18.tgz", + "integrity": "sha512-yi8swmWbO17qHhwIBNeeZxTceJMeBvWJaId6dyvTSOwTipqeHhMhOrz6513r1sOKnpvQ7zkhlG8tPrpilwTxHQ==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" + "@jridgewell/sourcemap-codec": "^1.5.5" } }, "node_modules/make-dir": { @@ -10123,9 +10123,9 @@ } }, "node_modules/rollup": { - "version": "4.46.3", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.3.tgz", - "integrity": "sha512-RZn2XTjXb8t5g13f5YclGoilU/kwT696DIkY3sywjdZidNSi3+vseaQov7D7BZXVJCPv3pDWUN69C78GGbXsKw==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.47.1.tgz", + "integrity": "sha512-iasGAQoZ5dWDzULEUX3jiW0oB1qyFOepSyDyoU6S/OhVlDIwj5knI5QBa5RRQ0sK7OE0v+8VIi2JuV+G+3tfNg==", "dev": true, "license": "MIT", "dependencies": { @@ -10139,26 +10139,26 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.46.3", - "@rollup/rollup-android-arm64": "4.46.3", - "@rollup/rollup-darwin-arm64": "4.46.3", - "@rollup/rollup-darwin-x64": "4.46.3", - "@rollup/rollup-freebsd-arm64": "4.46.3", - "@rollup/rollup-freebsd-x64": "4.46.3", - "@rollup/rollup-linux-arm-gnueabihf": "4.46.3", - "@rollup/rollup-linux-arm-musleabihf": "4.46.3", - "@rollup/rollup-linux-arm64-gnu": "4.46.3", - "@rollup/rollup-linux-arm64-musl": "4.46.3", - "@rollup/rollup-linux-loongarch64-gnu": "4.46.3", - "@rollup/rollup-linux-ppc64-gnu": "4.46.3", - "@rollup/rollup-linux-riscv64-gnu": "4.46.3", - "@rollup/rollup-linux-riscv64-musl": "4.46.3", - "@rollup/rollup-linux-s390x-gnu": "4.46.3", - "@rollup/rollup-linux-x64-gnu": "4.46.3", - "@rollup/rollup-linux-x64-musl": "4.46.3", - "@rollup/rollup-win32-arm64-msvc": "4.46.3", - "@rollup/rollup-win32-ia32-msvc": "4.46.3", - "@rollup/rollup-win32-x64-msvc": "4.46.3", + "@rollup/rollup-android-arm-eabi": "4.47.1", + "@rollup/rollup-android-arm64": "4.47.1", + "@rollup/rollup-darwin-arm64": "4.47.1", + "@rollup/rollup-darwin-x64": "4.47.1", + "@rollup/rollup-freebsd-arm64": "4.47.1", + "@rollup/rollup-freebsd-x64": "4.47.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.47.1", + "@rollup/rollup-linux-arm-musleabihf": "4.47.1", + "@rollup/rollup-linux-arm64-gnu": "4.47.1", + "@rollup/rollup-linux-arm64-musl": "4.47.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.47.1", + "@rollup/rollup-linux-ppc64-gnu": "4.47.1", + "@rollup/rollup-linux-riscv64-gnu": "4.47.1", + "@rollup/rollup-linux-riscv64-musl": "4.47.1", + "@rollup/rollup-linux-s390x-gnu": "4.47.1", + "@rollup/rollup-linux-x64-gnu": "4.47.1", + "@rollup/rollup-linux-x64-musl": "4.47.1", + "@rollup/rollup-win32-arm64-msvc": "4.47.1", + "@rollup/rollup-win32-ia32-msvc": "4.47.1", + "@rollup/rollup-win32-x64-msvc": "4.47.1", "fsevents": "~2.3.2" } }, @@ -11527,14 +11527,14 @@ } }, "node_modules/vite": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.2.tgz", - "integrity": "sha512-J0SQBPlQiEXAF7tajiH+rUooJPo0l8KQgyg4/aMunNtrOa7bwuZJsJbDWzeljqQpgftxuq5yNJxQ91O9ts29UQ==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.3.tgz", + "integrity": "sha512-OOUi5zjkDxYrKhTV3V7iKsoS37VUM7v40+HuwEmcrsf11Cdx9y3DIr2Px6liIcZFwt3XSRpQvFpL3WVy7ApkGw==", "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.25.0", - "fdir": "^6.4.6", + "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", diff --git a/public/v1/js/.gitkeep b/public/v1/js/.gitkeep old mode 100755 new mode 100644 diff --git a/resources/assets/v1/src/locales/af.json b/resources/assets/v1/src/locales/af.json index 2d9681a73a..200dcc7d5e 100644 --- a/resources/assets/v1/src/locales/af.json +++ b/resources/assets/v1/src/locales/af.json @@ -107,6 +107,7 @@ "multi_account_warning_withdrawal": "Keep in mind that the source account of subsequent splits will be overruled by whatever is defined in the first split of the withdrawal.", "multi_account_warning_deposit": "Keep in mind that the destination account of subsequent splits will be overruled by whatever is defined in the first split of the deposit.", "multi_account_warning_transfer": "Keep in mind that the source + destination account of subsequent splits will be overruled by whatever is defined in the first split of the transfer.", + "webhook_trigger_ANY": "After any event", "webhook_trigger_STORE_TRANSACTION": "After transaction creation", "webhook_trigger_UPDATE_TRANSACTION": "After transaction update", "webhook_trigger_DESTROY_TRANSACTION": "After transaction delete", @@ -115,6 +116,7 @@ "webhook_trigger_DESTROY_BUDGET": "After budget delete", "webhook_trigger_STORE_UPDATE_BUDGET_LIMIT": "After budgeted amount change", "webhook_response_TRANSACTIONS": "Transaction details", + "webhook_response_RELEVANT": "Relevant details", "webhook_response_ACCOUNTS": "Account details", "webhook_response_NONE": "No details", "webhook_delivery_JSON": "JSON", diff --git a/resources/assets/v1/src/locales/ar.json b/resources/assets/v1/src/locales/ar.json index ad8c5617c1..760136d30a 100644 --- a/resources/assets/v1/src/locales/ar.json +++ b/resources/assets/v1/src/locales/ar.json @@ -107,6 +107,7 @@ "multi_account_warning_withdrawal": "\u0636\u0639 \u0641\u064a \u0627\u0639\u062a\u0628\u0627\u0631\u0643 \u0623\u0646 \u062d\u0633\u0627\u0628 \u0627\u0644\u0645\u0635\u062f\u0631 \u0641\u064a \u0627\u0644\u062a\u0642\u0633\u064a\u0645\u0627\u062a \u0627\u0644\u0644\u0627\u062d\u0642\u0629 \u0633\u064a\u062a\u0645 \u062a\u062c\u0627\u0648\u0632\u0647 \u0628\u0645\u0627 \u0647\u0648 \u0645\u062d\u062f\u062f \u0641\u064a \u0623\u0648\u0644 \u062a\u0642\u0633\u064a\u0645 \u0644\u0644\u0633\u062d\u0628.", "multi_account_warning_deposit": "\u0636\u0639 \u0641\u064a \u0627\u0639\u062a\u0628\u0627\u0631\u0643 \u0623\u0646 \u062d\u0633\u0627\u0628 \u0627\u0644\u0648\u062c\u0647\u0629 \u0641\u064a \u0627\u0644\u062a\u0642\u0633\u064a\u0645\u0627\u062a \u0627\u0644\u0644\u0627\u062d\u0642\u0629 \u0633\u064a\u062a\u0645 \u062a\u062c\u0627\u0648\u0632\u0647 \u0628\u0645\u0627 \u0647\u0648 \u0645\u062d\u062f\u062f \u0641\u064a \u0623\u0648\u0644 \u062a\u0642\u0633\u064a\u0645 \u0644\u0644\u0625\u064a\u062f\u0627\u0639.", "multi_account_warning_transfer": "\u0636\u0639 \u0641\u064a \u0627\u0639\u062a\u0628\u0627\u0631\u0643 \u0623\u0646 \u062d\u0633\u0627\u0628 \u0627\u0644\u0645\u0635\u062f\u0631 + \u0627\u0644\u0648\u062c\u0647\u0629 \u0641\u064a \u0627\u0644\u062a\u0642\u0633\u064a\u0645\u0627\u062a \u0627\u0644\u0644\u0627\u062d\u0642\u0629 \u0633\u064a\u062a\u0645 \u062a\u062c\u0627\u0648\u0632\u0647\u0645\u0627 \u0628\u0645\u0627 \u0647\u0648 \u0645\u062d\u062f\u062f \u0641\u064a \u0623\u0648\u0644 \u062a\u0642\u0633\u064a\u0645 \u0644\u0644\u062a\u062d\u0648\u064a\u0644.", + "webhook_trigger_ANY": "After any event", "webhook_trigger_STORE_TRANSACTION": "\u0628\u0639\u062f \u0625\u0646\u0634\u0627\u0621 \u0627\u0644\u0645\u0639\u0627\u0645\u0644\u0629", "webhook_trigger_UPDATE_TRANSACTION": "\u0628\u0639\u062f \u062a\u062d\u062f\u064a\u062b \u0627\u0644\u0645\u0639\u0627\u0645\u0644\u0629", "webhook_trigger_DESTROY_TRANSACTION": "\u0628\u0639\u062f \u062d\u0630\u0641 \u0627\u0644\u0645\u0639\u0627\u0645\u0644\u0629", @@ -115,6 +116,7 @@ "webhook_trigger_DESTROY_BUDGET": "After budget delete", "webhook_trigger_STORE_UPDATE_BUDGET_LIMIT": "After budgeted amount change", "webhook_response_TRANSACTIONS": "\u062a\u0641\u0627\u0635\u064a\u0644 \u0627\u0644\u0645\u0639\u0627\u0645\u0644\u0629", + "webhook_response_RELEVANT": "Relevant details", "webhook_response_ACCOUNTS": "\u062a\u0641\u0627\u0635\u064a\u0644 \u0627\u0644\u062d\u0633\u0627\u0628", "webhook_response_NONE": "No details", "webhook_delivery_JSON": "JSON", diff --git a/resources/assets/v1/src/locales/bg.json b/resources/assets/v1/src/locales/bg.json index 30584bb53b..ec01f4d4b8 100644 --- a/resources/assets/v1/src/locales/bg.json +++ b/resources/assets/v1/src/locales/bg.json @@ -107,6 +107,7 @@ "multi_account_warning_withdrawal": "\u0418\u043c\u0430\u0439\u0442\u0435 \u043f\u0440\u0435\u0434\u0432\u0438\u0434, \u0447\u0435 \u0440\u0430\u0437\u0445\u043e\u0434\u043d\u0430 \u0441\u043c\u0435\u0442\u043a\u0430 \u043d\u0430 \u0441\u043b\u0435\u0434\u0432\u0430\u0449\u0438\u0442\u0435 \u0440\u0430\u0437\u0434\u0435\u043b\u044f\u043d\u0438\u044f \u0449\u0435 \u0431\u044a\u0434\u0435 \u0442\u0430\u0437\u0438 \u043a\u043e\u044f\u0442\u043e \u0435 \u0434\u0435\u0444\u0438\u043d\u0438\u0440\u0430\u043d\u0430 \u0432 \u043f\u044a\u0440\u0432\u0438\u044f \u0440\u0430\u0437\u0434\u0435\u043b \u043d\u0430 \u0442\u0435\u0433\u043b\u0435\u043d\u0435\u0442\u043e.", "multi_account_warning_deposit": "\u0418\u043c\u0430\u0439\u0442\u0435 \u043f\u0440\u0435\u0434\u0432\u0438\u0434, \u0447\u0435 \u043f\u0440\u0438\u0445\u043e\u0434\u043d\u0430\u0442\u0430 \u0441\u043c\u0435\u0442\u043a\u0430 \u043d\u0430 \u0441\u043b\u0435\u0434\u0432\u0430\u0449\u0438\u0442\u0435 \u0440\u0430\u0437\u0434\u0435\u043b\u044f\u043d\u0438\u044f \u0449\u0435 \u0431\u044a\u0434\u0435 \u0442\u0430\u0437\u0438 \u043a\u043e\u044f\u0442\u043e \u0435 \u0434\u0435\u0444\u0438\u043d\u0438\u0440\u0430\u043d\u0430 \u0432 \u043f\u044a\u0440\u0432\u0438\u044f \u0440\u0430\u0437\u0434\u0435\u043b \u043d\u0430 \u0434\u0435\u043f\u043e\u0437\u0438\u0442\u0430.", "multi_account_warning_transfer": "\u0418\u043c\u0430\u0439\u0442\u0435 \u043f\u0440\u0435\u0434\u0432\u0438\u0434, \u0447\u0435 \u043f\u0440\u0438\u0445\u043e\u0434\u043d\u0430\u0442\u0430 + \u0440\u0430\u0437\u0445\u043e\u0434\u043d\u0430\u0442\u0430 \u0441\u043c\u0435\u0442\u043a\u0430 \u043d\u0430 \u0441\u043b\u0435\u0434\u0432\u0430\u0449\u0438\u0442\u0435 \u0440\u0430\u0437\u0434\u0435\u043b\u044f\u043d\u0438\u044f \u0449\u0435 \u0431\u044a\u0434\u0435 \u0442\u0430\u0437\u0438 \u043a\u043e\u044f\u0442\u043e \u0435 \u0434\u0435\u0444\u0438\u043d\u0438\u0440\u0430\u043d\u0430 \u0432 \u043f\u044a\u0440\u0432\u0438\u044f \u0440\u0430\u0437\u0434\u0435\u043b \u043d\u0430 \u043f\u0440\u0435\u0445\u0432\u044a\u0440\u043b\u044f\u043d\u0435\u0442\u043e.", + "webhook_trigger_ANY": "After any event", "webhook_trigger_STORE_TRANSACTION": "After transaction creation", "webhook_trigger_UPDATE_TRANSACTION": "After transaction update", "webhook_trigger_DESTROY_TRANSACTION": "After transaction delete", @@ -115,6 +116,7 @@ "webhook_trigger_DESTROY_BUDGET": "After budget delete", "webhook_trigger_STORE_UPDATE_BUDGET_LIMIT": "After budgeted amount change", "webhook_response_TRANSACTIONS": "Transaction details", + "webhook_response_RELEVANT": "Relevant details", "webhook_response_ACCOUNTS": "Account details", "webhook_response_NONE": "No details", "webhook_delivery_JSON": "JSON", diff --git a/resources/assets/v1/src/locales/ca.json b/resources/assets/v1/src/locales/ca.json index 05bb117194..180b4f5e58 100644 --- a/resources/assets/v1/src/locales/ca.json +++ b/resources/assets/v1/src/locales/ca.json @@ -107,6 +107,7 @@ "multi_account_warning_withdrawal": "Tingues en compte que el compte d'origen de divisions posteriors ser\u00e0 anul\u00b7lat pel que es troba definit a la primera divisi\u00f3 de la retirada.", "multi_account_warning_deposit": "Tingues en compte que el compte de dest\u00ed de divisions posteriors ser\u00e0 anul\u00b7lat pel que es troba definit a la primera divisi\u00f3 del dip\u00f2sit.", "multi_account_warning_transfer": "Tingues en compte que el compte d'origen + dest\u00ed de divisions posteriors ser\u00e0 anul\u00b7lat pel que es troba definit a la primera divisi\u00f3 de la transfer\u00e8ncia.", + "webhook_trigger_ANY": "After any event", "webhook_trigger_STORE_TRANSACTION": "Despr\u00e9s de crear la transacci\u00f3", "webhook_trigger_UPDATE_TRANSACTION": "Despr\u00e9s d'actualitzar la transacci\u00f3", "webhook_trigger_DESTROY_TRANSACTION": "Despr\u00e9s d'eliminar la transacci\u00f3", @@ -115,6 +116,7 @@ "webhook_trigger_DESTROY_BUDGET": "After budget delete", "webhook_trigger_STORE_UPDATE_BUDGET_LIMIT": "After budgeted amount change", "webhook_response_TRANSACTIONS": "Detalls de la transacci\u00f3", + "webhook_response_RELEVANT": "Relevant details", "webhook_response_ACCOUNTS": "Detalls del compte", "webhook_response_NONE": "No details", "webhook_delivery_JSON": "JSON", diff --git a/resources/assets/v1/src/locales/cs.json b/resources/assets/v1/src/locales/cs.json index 85a404597f..66c89e114d 100644 --- a/resources/assets/v1/src/locales/cs.json +++ b/resources/assets/v1/src/locales/cs.json @@ -107,6 +107,7 @@ "multi_account_warning_withdrawal": "Zdrojov\u00fd \u00fa\u010det cel\u00e9 transakce je ovl\u00e1dan\u00fd prvn\u00edm rozd\u011blen\u00edm.", "multi_account_warning_deposit": "C\u00edlov\u00fd \u00fa\u010del v\u0161ech n\u00e1sleduj\u00edc\u00edch rozd\u011blen\u00ed je ovl\u00e1dan\u00fd c\u00edlov\u00fdm \u00fa\u010dtem prvn\u00edho rozd\u011blen\u00ed transakce.", "multi_account_warning_transfer": "Zdrojov\u00fd i c\u00edlov\u00fd \u00fa\u010det v\u0161ech n\u00e1sleduj\u00edc\u00edch rozd\u011blen\u00ed jsou ovl\u00e1d\u00e1ny zdrojov\u00fdm a c\u00edlov\u00fdm \u00fa\u010dtem prvn\u00edho rozd\u011blen\u00ed transakce.", + "webhook_trigger_ANY": "After any event", "webhook_trigger_STORE_TRANSACTION": "Po vytvo\u0159en\u00ed transakce", "webhook_trigger_UPDATE_TRANSACTION": "Po aktualizaci transakce", "webhook_trigger_DESTROY_TRANSACTION": "Po odstran\u011bn\u00ed transakce", @@ -115,6 +116,7 @@ "webhook_trigger_DESTROY_BUDGET": "After budget delete", "webhook_trigger_STORE_UPDATE_BUDGET_LIMIT": "After budgeted amount change", "webhook_response_TRANSACTIONS": "Podrobnosti transakce", + "webhook_response_RELEVANT": "Relevant details", "webhook_response_ACCOUNTS": "Podrobnosti \u00fa\u010dtu", "webhook_response_NONE": "No details", "webhook_delivery_JSON": "JSON", diff --git a/resources/assets/v1/src/locales/da.json b/resources/assets/v1/src/locales/da.json index cacf24b7e2..aec14e84ab 100644 --- a/resources/assets/v1/src/locales/da.json +++ b/resources/assets/v1/src/locales/da.json @@ -107,6 +107,7 @@ "multi_account_warning_withdrawal": "Husk, at kildekontoen for efterf\u00f8lgende opdelinger vil blive overstyret af hvad der er defineret i den f\u00f8rste opdeling af tilbagetr\u00e6kningen.", "multi_account_warning_deposit": "Husk, at destinationskontoen for efterf\u00f8lgende opdelinger vil blive tilsidesat af hvad der er defineret i den f\u00f8rste opsplitning af depositummet.", "multi_account_warning_transfer": "Husk p\u00e5, at kilden + destination konto for efterf\u00f8lgende opdelinger vil blive overstyret af hvad der er defineret i den f\u00f8rste opdeling af overf\u00f8rslen.", + "webhook_trigger_ANY": "After any event", "webhook_trigger_STORE_TRANSACTION": "Efter oprettelse af transaktion", "webhook_trigger_UPDATE_TRANSACTION": "Efter opdatering af transaktion", "webhook_trigger_DESTROY_TRANSACTION": "Efter sletning af transaktion", @@ -115,6 +116,7 @@ "webhook_trigger_DESTROY_BUDGET": "After budget delete", "webhook_trigger_STORE_UPDATE_BUDGET_LIMIT": "After budgeted amount change", "webhook_response_TRANSACTIONS": "Transaktionsdetaljer", + "webhook_response_RELEVANT": "Relevant details", "webhook_response_ACCOUNTS": "Kontodetaljer", "webhook_response_NONE": "No details", "webhook_delivery_JSON": "JSON", diff --git a/resources/assets/v1/src/locales/de.json b/resources/assets/v1/src/locales/de.json index 18ee0b7ab4..53524b4e92 100644 --- a/resources/assets/v1/src/locales/de.json +++ b/resources/assets/v1/src/locales/de.json @@ -107,6 +107,7 @@ "multi_account_warning_withdrawal": "Bedenken Sie, dass das Quellkonto nachfolgender Aufteilungen von dem, was in der ersten Aufteilung der Abhebung definiert ist, au\u00dfer Kraft gesetzt wird.", "multi_account_warning_deposit": "Bedenken Sie, dass das Zielkonto nachfolgender Aufteilungen von dem, was in der ersten Aufteilung der Einnahmen definiert ist, au\u00dfer Kraft gesetzt wird.", "multi_account_warning_transfer": "Bedenken Sie, dass das Quell- und Zielkonto nachfolgender Aufteilungen durch das, was in der ersten Aufteilung der \u00dcbertragung definiert ist, au\u00dfer Kraft gesetzt wird.", + "webhook_trigger_ANY": "After any event", "webhook_trigger_STORE_TRANSACTION": "Nach Erstellen einer Buchung", "webhook_trigger_UPDATE_TRANSACTION": "Nach Aktualisierung einer Buchung", "webhook_trigger_DESTROY_TRANSACTION": "Nach dem L\u00f6schen einer Buchung", @@ -115,6 +116,7 @@ "webhook_trigger_DESTROY_BUDGET": "Nach dem L\u00f6schen des Budgets", "webhook_trigger_STORE_UPDATE_BUDGET_LIMIT": "Nach dem \u00c4ndern des budgetierten Betrags", "webhook_response_TRANSACTIONS": "Buchungsdetails", + "webhook_response_RELEVANT": "Relevant details", "webhook_response_ACCOUNTS": "Kontodetails", "webhook_response_NONE": "Keine Details", "webhook_delivery_JSON": "JSON", diff --git a/resources/assets/v1/src/locales/el.json b/resources/assets/v1/src/locales/el.json index 21bac9256f..22cf8bc6aa 100644 --- a/resources/assets/v1/src/locales/el.json +++ b/resources/assets/v1/src/locales/el.json @@ -107,6 +107,7 @@ "multi_account_warning_withdrawal": "\u039b\u03ac\u03b2\u03b5\u03c4\u03b5 \u03c5\u03c0\u03cc\u03c8\u03b7 \u03cc\u03c4\u03b9 \u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03c0\u03c1\u03bf\u03ad\u03bb\u03b5\u03c5\u03c3\u03b7\u03c2 \u03c4\u03c9\u03bd \u03b5\u03c0\u03cc\u03bc\u03b5\u03bd\u03c9\u03bd \u03b4\u03b9\u03b1\u03c7\u03c9\u03c1\u03b9\u03c3\u03bc\u03ce\u03bd \u03b8\u03b1 \u03c5\u03c0\u03b5\u03c1\u03b9\u03c3\u03c7\u03cd\u03c3\u03b5\u03b9 \u03b1\u03c5\u03c4\u03bf\u03cd \u03c4\u03bf\u03c5 \u03c0\u03c1\u03ce\u03c4\u03bf\u03c5 \u03b4\u03b9\u03b1\u03c7\u03c9\u03c1\u03b9\u03c3\u03bc\u03bf\u03cd \u03c4\u03b7\u03c2 \u03b1\u03bd\u03ac\u03bb\u03b7\u03c8\u03b7\u03c2.", "multi_account_warning_deposit": "\u039b\u03ac\u03b2\u03b5\u03c4\u03b5 \u03c5\u03c0\u03cc\u03c8\u03b7 \u03cc\u03c4\u03b9 \u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03c0\u03c1\u03bf\u03bf\u03c1\u03b9\u03c3\u03bc\u03bf\u03cd \u03c4\u03c9\u03bd \u03b5\u03c0\u03cc\u03bc\u03b5\u03bd\u03c9\u03bd \u03b4\u03b9\u03b1\u03c7\u03c9\u03c1\u03b9\u03c3\u03bc\u03ce\u03bd \u03b8\u03b1 \u03c5\u03c0\u03b5\u03c1\u03b9\u03c3\u03c7\u03cd\u03c3\u03b5\u03b9 \u03b1\u03c5\u03c4\u03bf\u03cd \u03c4\u03bf\u03c5 \u03c0\u03c1\u03ce\u03c4\u03bf\u03c5 \u03b4\u03b9\u03b1\u03c7\u03c9\u03c1\u03b9\u03c3\u03bc\u03bf\u03cd \u03c4\u03b7\u03c2 \u03ba\u03b1\u03c4\u03ac\u03b8\u03b5\u03c3\u03b7\u03c2.", "multi_account_warning_transfer": "\u039b\u03ac\u03b2\u03b5\u03c4\u03b5 \u03c5\u03c0\u03cc\u03c8\u03b7 \u03cc\u03c4\u03b9 \u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03c0\u03c1\u03bf\u03ad\u03bb\u03b5\u03c5\u03c3\u03b7\u03c2 \u03ba\u03b1\u03b9 \u03c0\u03c1\u03bf\u03bf\u03c1\u03b9\u03c3\u03bc\u03bf\u03cd \u03c4\u03c9\u03bd \u03b5\u03c0\u03cc\u03bc\u03b5\u03bd\u03c9\u03bd \u03b4\u03b9\u03b1\u03c7\u03c9\u03c1\u03b9\u03c3\u03bc\u03ce\u03bd \u03b8\u03b1 \u03c5\u03c0\u03b5\u03c1\u03b9\u03c3\u03c7\u03cd\u03c3\u03b5\u03b9 \u03b1\u03c5\u03c4\u03bf\u03cd \u03c4\u03bf\u03c5 \u03c0\u03c1\u03ce\u03c4\u03bf\u03c5 \u03b4\u03b9\u03b1\u03c7\u03c9\u03c1\u03b9\u03c3\u03bc\u03bf\u03cd \u03c4\u03b7\u03c2 \u03bc\u03b5\u03c4\u03b1\u03c6\u03bf\u03c1\u03ac\u03c2.", + "webhook_trigger_ANY": "After any event", "webhook_trigger_STORE_TRANSACTION": "\u039c\u03b5\u03c4\u03ac \u03c4\u03b7 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03c3\u03c5\u03bd\u03b1\u03bb\u03bb\u03b1\u03b3\u03ae\u03c2", "webhook_trigger_UPDATE_TRANSACTION": "\u039c\u03b5\u03c4\u03ac \u03c4\u03b7\u03bd \u03b5\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03bd\u03b1\u03bb\u03bb\u03b1\u03b3\u03ae\u03c2", "webhook_trigger_DESTROY_TRANSACTION": "\u039c\u03b5\u03c4\u03ac \u03c4\u03b7 \u03b4\u03b9\u03b1\u03b3\u03c1\u03b1\u03c6\u03ae \u03c3\u03c5\u03bd\u03b1\u03bb\u03bb\u03b1\u03b3\u03ae\u03c2", @@ -115,6 +116,7 @@ "webhook_trigger_DESTROY_BUDGET": "After budget delete", "webhook_trigger_STORE_UPDATE_BUDGET_LIMIT": "After budgeted amount change", "webhook_response_TRANSACTIONS": "\u039b\u03b5\u03c0\u03c4\u03bf\u03bc\u03ad\u03c1\u03b5\u03b9\u03b5\u03c2 \u03c3\u03c5\u03bd\u03b1\u03bb\u03bb\u03b1\u03b3\u03ae\u03c2", + "webhook_response_RELEVANT": "Relevant details", "webhook_response_ACCOUNTS": "\u03a0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd", "webhook_response_NONE": "No details", "webhook_delivery_JSON": "JSON", diff --git a/resources/assets/v1/src/locales/en-gb.json b/resources/assets/v1/src/locales/en-gb.json index 76afad7c2b..c4cd99462c 100644 --- a/resources/assets/v1/src/locales/en-gb.json +++ b/resources/assets/v1/src/locales/en-gb.json @@ -107,6 +107,7 @@ "multi_account_warning_withdrawal": "Keep in mind that the source account of subsequent splits will be overruled by whatever is defined in the first split of the withdrawal.", "multi_account_warning_deposit": "Keep in mind that the destination account of subsequent splits will be overruled by whatever is defined in the first split of the deposit.", "multi_account_warning_transfer": "Keep in mind that the source + destination account of subsequent splits will be overruled by whatever is defined in the first split of the transfer.", + "webhook_trigger_ANY": "After any event", "webhook_trigger_STORE_TRANSACTION": "After transaction creation", "webhook_trigger_UPDATE_TRANSACTION": "After transaction update", "webhook_trigger_DESTROY_TRANSACTION": "After transaction delete", @@ -115,6 +116,7 @@ "webhook_trigger_DESTROY_BUDGET": "After budget delete", "webhook_trigger_STORE_UPDATE_BUDGET_LIMIT": "After budgeted amount change", "webhook_response_TRANSACTIONS": "Transaction details", + "webhook_response_RELEVANT": "Relevant details", "webhook_response_ACCOUNTS": "Account details", "webhook_response_NONE": "No details", "webhook_delivery_JSON": "JSON", diff --git a/resources/assets/v1/src/locales/en.json b/resources/assets/v1/src/locales/en.json index 3dc758de69..e8c9aa3502 100644 --- a/resources/assets/v1/src/locales/en.json +++ b/resources/assets/v1/src/locales/en.json @@ -107,6 +107,7 @@ "multi_account_warning_withdrawal": "Keep in mind that the source account of subsequent splits will be overruled by whatever is defined in the first split of the withdrawal.", "multi_account_warning_deposit": "Keep in mind that the destination account of subsequent splits will be overruled by whatever is defined in the first split of the deposit.", "multi_account_warning_transfer": "Keep in mind that the source + destination account of subsequent splits will be overruled by whatever is defined in the first split of the transfer.", + "webhook_trigger_ANY": "After any event", "webhook_trigger_STORE_TRANSACTION": "After transaction creation", "webhook_trigger_UPDATE_TRANSACTION": "After transaction update", "webhook_trigger_DESTROY_TRANSACTION": "After transaction delete", @@ -115,6 +116,7 @@ "webhook_trigger_DESTROY_BUDGET": "After budget delete", "webhook_trigger_STORE_UPDATE_BUDGET_LIMIT": "After budgeted amount change", "webhook_response_TRANSACTIONS": "Transaction details", + "webhook_response_RELEVANT": "Relevant details", "webhook_response_ACCOUNTS": "Account details", "webhook_response_NONE": "No details", "webhook_delivery_JSON": "JSON", diff --git a/resources/assets/v1/src/locales/es.json b/resources/assets/v1/src/locales/es.json index 1e7ce99453..4c13f5d122 100644 --- a/resources/assets/v1/src/locales/es.json +++ b/resources/assets/v1/src/locales/es.json @@ -107,6 +107,7 @@ "multi_account_warning_withdrawal": "Tenga en cuenta que la cuenta de origen de las divisiones posteriores ser\u00e1 anulada por lo que se defina en la primera divisi\u00f3n del gasto.", "multi_account_warning_deposit": "Tenga en cuenta que la cuenta de destino de las divisiones posteriores ser\u00e1 anulada por lo que se defina en la primera divisi\u00f3n del retiro.", "multi_account_warning_transfer": "Tenga en cuenta que la cuenta de origen + destino de divisiones posteriores ser\u00e1 anulada por lo que se defina en la primera divisi\u00f3n de la transferencia.", + "webhook_trigger_ANY": "After any event", "webhook_trigger_STORE_TRANSACTION": "Despu\u00e9s de crear la transacci\u00f3n", "webhook_trigger_UPDATE_TRANSACTION": "Despu\u00e9s de actualizar la transacci\u00f3n", "webhook_trigger_DESTROY_TRANSACTION": "Despu\u00e9s de eliminar la transacci\u00f3n", @@ -115,6 +116,7 @@ "webhook_trigger_DESTROY_BUDGET": "After budget delete", "webhook_trigger_STORE_UPDATE_BUDGET_LIMIT": "After budgeted amount change", "webhook_response_TRANSACTIONS": "Detalles de la transacci\u00f3n", + "webhook_response_RELEVANT": "Relevant details", "webhook_response_ACCOUNTS": "Detalles de la cuenta", "webhook_response_NONE": "No details", "webhook_delivery_JSON": "JSON", diff --git a/resources/assets/v1/src/locales/fa.json b/resources/assets/v1/src/locales/fa.json index 26378a18e6..97b383d4ed 100644 --- a/resources/assets/v1/src/locales/fa.json +++ b/resources/assets/v1/src/locales/fa.json @@ -107,6 +107,7 @@ "multi_account_warning_withdrawal": "\u0628\u0647 \u062e\u0627\u0637\u0631 \u062f\u0627\u0634\u062a\u0647 \u0628\u0627\u0634\u06cc\u062f \u06a9\u0647 \u062d\u0633\u0627\u0628 \u0645\u0646\u0628\u0639 \u0627\u0646\u0634\u0639\u0627\u0628\u0627\u062a \u0628\u0639\u062f\u06cc \u0628\u0627 \u0647\u0631 \u0622\u0646\u0686\u0647 \u062f\u0631 \u062a\u0642\u0633\u06cc\u0645 \u0627\u0648\u0644 \u0628\u0631\u062f\u0627\u0634\u062a \u062a\u0639\u0631\u06cc\u0641 \u0634\u062f\u0647 \u0627\u0633\u062a \u0644\u063a\u0648 \u0645\u06cc \u0634\u0648\u062f.\n", "multi_account_warning_deposit": "\u0628\u0647 \u062e\u0627\u0637\u0631 \u062f\u0627\u0634\u062a\u0647 \u0628\u0627\u0634\u06cc\u062f \u06a9\u0647 \u062d\u0633\u0627\u0628 \u0645\u0642\u0635\u062f \u062a\u0642\u0633\u06cc\u0645 \u0647\u0627\u06cc \u0628\u0639\u062f\u06cc \u0628\u0627 \u0647\u0631 \u0622\u0646\u0686\u0647 \u062f\u0631 \u0627\u0648\u0644\u06cc\u0646 \u062a\u0642\u0633\u06cc\u0645 \u0633\u067e\u0631\u062f\u0647 \u062a\u0639\u0631\u06cc\u0641 \u0634\u062f\u0647 \u0627\u0633\u062a \u0644\u063a\u0648 \u0645\u06cc \u0634\u0648\u062f.\n", "multi_account_warning_transfer": "\u0628\u0647 \u062e\u0627\u0637\u0631 \u062f\u0627\u0634\u062a\u0647 \u0628\u0627\u0634\u06cc\u062f \u06a9\u0647 \u062d\u0633\u0627\u0628 \u0645\u0628\u062f\u0627 + \u0645\u0642\u0635\u062f \u062a\u0642\u0633\u06cc\u0645\u200c\u0647\u0627\u06cc \u0628\u0639\u062f\u06cc \u0628\u0627 \u0647\u0631 \u0622\u0646\u0686\u0647 \u062f\u0631 \u062a\u0642\u0633\u06cc\u0645 \u0627\u0648\u0644 \u0627\u0646\u062a\u0642\u0627\u0644 \u062a\u0639\u0631\u06cc\u0641 \u0634\u062f\u0647 \u0627\u0633\u062a \u0644\u063a\u0648 \u0645\u06cc\u200c\u0634\u0648\u062f.\n", + "webhook_trigger_ANY": "After any event", "webhook_trigger_STORE_TRANSACTION": "\u067e\u0633 \u0627\u0632 \u0627\u06cc\u062c\u0627\u062f \u062a\u0631\u0627\u06a9\u0646\u0634\n", "webhook_trigger_UPDATE_TRANSACTION": "\u067e\u0633 \u0627\u0632 \u0628\u0647 \u0631\u0648\u0632 \u0631\u0633\u0627\u0646\u06cc \u062a\u0631\u0627\u06a9\u0646\u0634\n", "webhook_trigger_DESTROY_TRANSACTION": "\u067e\u0633 \u0627\u0632 \u062a\u0631\u0627\u06a9\u0646\u0634 \u062d\u0630\u0641 \u0634\u0648\u062f\n", @@ -115,6 +116,7 @@ "webhook_trigger_DESTROY_BUDGET": "After budget delete", "webhook_trigger_STORE_UPDATE_BUDGET_LIMIT": "After budgeted amount change", "webhook_response_TRANSACTIONS": "\u062c\u0632\u0626\u06cc\u0627\u062a \u062a\u0631\u0627\u06a9\u0646\u0634", + "webhook_response_RELEVANT": "Relevant details", "webhook_response_ACCOUNTS": "\u062c\u0632\u0626\u06cc\u0627\u062a \u062d\u0633\u0627\u0628", "webhook_response_NONE": "No details", "webhook_delivery_JSON": "JSON", diff --git a/resources/assets/v1/src/locales/fi.json b/resources/assets/v1/src/locales/fi.json index cc6abf518c..326a875713 100644 --- a/resources/assets/v1/src/locales/fi.json +++ b/resources/assets/v1/src/locales/fi.json @@ -107,6 +107,7 @@ "multi_account_warning_withdrawal": "Muista, ett\u00e4 my\u00f6hempien jakojen l\u00e4hdetili m\u00e4\u00e4r\u00e4ytyy noston ensimm\u00e4isen jaon m\u00e4\u00e4ritysten mukaan.", "multi_account_warning_deposit": "Muista, ett\u00e4 my\u00f6hempien jakojen kohdetili m\u00e4\u00e4r\u00e4ytyy talletuksen ensimm\u00e4isen jaon m\u00e4\u00e4ritysten mukaan.", "multi_account_warning_transfer": "Muista, ett\u00e4 my\u00f6hempien jakojen l\u00e4hde- ja kohdetili m\u00e4\u00e4r\u00e4ytyv\u00e4t ensimm\u00e4isen jaon m\u00e4\u00e4ritysten mukaan.", + "webhook_trigger_ANY": "After any event", "webhook_trigger_STORE_TRANSACTION": "After transaction creation", "webhook_trigger_UPDATE_TRANSACTION": "After transaction update", "webhook_trigger_DESTROY_TRANSACTION": "After transaction delete", @@ -115,6 +116,7 @@ "webhook_trigger_DESTROY_BUDGET": "After budget delete", "webhook_trigger_STORE_UPDATE_BUDGET_LIMIT": "After budgeted amount change", "webhook_response_TRANSACTIONS": "Transaction details", + "webhook_response_RELEVANT": "Relevant details", "webhook_response_ACCOUNTS": "Tilin tiedot", "webhook_response_NONE": "No details", "webhook_delivery_JSON": "JSON", diff --git a/resources/assets/v1/src/locales/fr.json b/resources/assets/v1/src/locales/fr.json index 0f56cf55b0..06ff6c4f09 100644 --- a/resources/assets/v1/src/locales/fr.json +++ b/resources/assets/v1/src/locales/fr.json @@ -107,6 +107,7 @@ "multi_account_warning_withdrawal": "Gardez en t\u00eate que le compte source des s\u00e9parations suivantes peut \u00eatre remplac\u00e9 par celui de la premi\u00e8re s\u00e9paration de la d\u00e9pense.", "multi_account_warning_deposit": "Gardez en t\u00eate que le compte de destination des s\u00e9parations suivantes peut \u00eatre remplac\u00e9 par celui de la premi\u00e8re s\u00e9paration du d\u00e9p\u00f4t.", "multi_account_warning_transfer": "Gardez en t\u00eate que les comptes source et de destination des s\u00e9parations suivantes peuvent \u00eatre remplac\u00e9s par ceux de la premi\u00e8re s\u00e9paration du transfert.", + "webhook_trigger_ANY": "After any event", "webhook_trigger_STORE_TRANSACTION": "Apr\u00e8s la cr\u00e9ation de l'op\u00e9ration", "webhook_trigger_UPDATE_TRANSACTION": "Apr\u00e8s la mise \u00e0 jour de l'op\u00e9ration", "webhook_trigger_DESTROY_TRANSACTION": "Apr\u00e8s la suppression de l'op\u00e9ration", @@ -115,6 +116,7 @@ "webhook_trigger_DESTROY_BUDGET": "Apr\u00e8s la suppression du budget", "webhook_trigger_STORE_UPDATE_BUDGET_LIMIT": "Apr\u00e8s le changement du montant budg\u00e9tis\u00e9", "webhook_response_TRANSACTIONS": "D\u00e9tails de l'op\u00e9ration", + "webhook_response_RELEVANT": "Relevant details", "webhook_response_ACCOUNTS": "D\u00e9tails du compte", "webhook_response_NONE": "Aucun d\u00e9tail", "webhook_delivery_JSON": "JSON", diff --git a/resources/assets/v1/src/locales/hu.json b/resources/assets/v1/src/locales/hu.json index d4b67100a7..8e46b0be2f 100644 --- a/resources/assets/v1/src/locales/hu.json +++ b/resources/assets/v1/src/locales/hu.json @@ -107,6 +107,7 @@ "multi_account_warning_withdrawal": "Keep in mind that the source account of subsequent splits will be overruled by whatever is defined in the first split of the withdrawal.", "multi_account_warning_deposit": "Keep in mind that the destination account of subsequent splits will be overruled by whatever is defined in the first split of the deposit.", "multi_account_warning_transfer": "Keep in mind that the source + destination account of subsequent splits will be overruled by whatever is defined in the first split of the transfer.", + "webhook_trigger_ANY": "After any event", "webhook_trigger_STORE_TRANSACTION": "Tranzakci\u00f3 l\u00e9trehoz\u00e1sa ut\u00e1n", "webhook_trigger_UPDATE_TRANSACTION": "Tranzakci\u00f3 friss\u00edt\u00e9se ut\u00e1n", "webhook_trigger_DESTROY_TRANSACTION": "Tranzakci\u00f3 t\u00f6rl\u00e9se ut\u00e1n", @@ -115,6 +116,7 @@ "webhook_trigger_DESTROY_BUDGET": "After budget delete", "webhook_trigger_STORE_UPDATE_BUDGET_LIMIT": "After budgeted amount change", "webhook_response_TRANSACTIONS": "Tranzakci\u00f3 r\u00e9szletei", + "webhook_response_RELEVANT": "Relevant details", "webhook_response_ACCOUNTS": "Sz\u00e1mlaadatok", "webhook_response_NONE": "No details", "webhook_delivery_JSON": "JSON", diff --git a/resources/assets/v1/src/locales/id.json b/resources/assets/v1/src/locales/id.json index 06d4ae1ddf..1fcffc02b9 100644 --- a/resources/assets/v1/src/locales/id.json +++ b/resources/assets/v1/src/locales/id.json @@ -107,6 +107,7 @@ "multi_account_warning_withdrawal": "Keep in mind that the source account of subsequent splits will be overruled by whatever is defined in the first split of the withdrawal.", "multi_account_warning_deposit": "Keep in mind that the destination account of subsequent splits will be overruled by whatever is defined in the first split of the deposit.", "multi_account_warning_transfer": "Keep in mind that the source + destination account of subsequent splits will be overruled by whatever is defined in the first split of the transfer.", + "webhook_trigger_ANY": "After any event", "webhook_trigger_STORE_TRANSACTION": "After transaction creation", "webhook_trigger_UPDATE_TRANSACTION": "After transaction update", "webhook_trigger_DESTROY_TRANSACTION": "After transaction delete", @@ -115,6 +116,7 @@ "webhook_trigger_DESTROY_BUDGET": "After budget delete", "webhook_trigger_STORE_UPDATE_BUDGET_LIMIT": "After budgeted amount change", "webhook_response_TRANSACTIONS": "Transaction details", + "webhook_response_RELEVANT": "Relevant details", "webhook_response_ACCOUNTS": "Account details", "webhook_response_NONE": "No details", "webhook_delivery_JSON": "JSON", diff --git a/resources/assets/v1/src/locales/it.json b/resources/assets/v1/src/locales/it.json index 8b2c53508b..dc449681e6 100644 --- a/resources/assets/v1/src/locales/it.json +++ b/resources/assets/v1/src/locales/it.json @@ -107,6 +107,7 @@ "multi_account_warning_withdrawal": "Ricorda che il conto di origine delle successive suddivisioni verr\u00e0 sovrascritto da quello definito nella prima suddivisione del prelievo.", "multi_account_warning_deposit": "Ricorda che il conto di destinazione delle successive suddivisioni verr\u00e0 sovrascritto da quello definito nella prima suddivisione del deposito.", "multi_account_warning_transfer": "Ricorda che il conto di origine e il conto di destinazione delle successive suddivisioni verranno sovrascritti da quelli definiti nella prima suddivisione del trasferimento.", + "webhook_trigger_ANY": "After any event", "webhook_trigger_STORE_TRANSACTION": "Dopo aver creato la transazione", "webhook_trigger_UPDATE_TRANSACTION": "Dopo aver aggiornato la transazione", "webhook_trigger_DESTROY_TRANSACTION": "Dopo aver eliminato la transazione", @@ -115,6 +116,7 @@ "webhook_trigger_DESTROY_BUDGET": "After budget delete", "webhook_trigger_STORE_UPDATE_BUDGET_LIMIT": "After budgeted amount change", "webhook_response_TRANSACTIONS": "Dettagli transazione", + "webhook_response_RELEVANT": "Relevant details", "webhook_response_ACCOUNTS": "Dettagli conto", "webhook_response_NONE": "No details", "webhook_delivery_JSON": "JSON", diff --git a/resources/assets/v1/src/locales/ja.json b/resources/assets/v1/src/locales/ja.json index c86bf02668..91d422ab1f 100644 --- a/resources/assets/v1/src/locales/ja.json +++ b/resources/assets/v1/src/locales/ja.json @@ -107,6 +107,7 @@ "multi_account_warning_withdrawal": "\u7d9a\u304f\u5206\u5272\u306e\u5f15\u304d\u51fa\u3057\u53e3\u5ea7\u306f\u3001\u51fa\u91d1\u306e\u6700\u521d\u306e\u5206\u5272\u306e\u5b9a\u7fa9\u306b\u3088\u3063\u3066\u8986\u3055\u308c\u308b\u3053\u3068\u306b\u6ce8\u610f\u3057\u3066\u304f\u3060\u3055\u3044\u3002", "multi_account_warning_deposit": "\u7d9a\u304f\u5206\u5272\u306e\u9810\u3051\u5165\u308c\u53e3\u5ea7\u306f\u3001\u9001\u91d1\u306e\u6700\u521d\u306e\u5206\u5272\u306e\u5b9a\u7fa9\u306b\u3088\u3063\u3066\u8986\u3055\u308c\u308b\u3053\u3068\u306b\u6ce8\u610f\u3057\u3066\u304f\u3060\u3055\u3044\u3002", "multi_account_warning_transfer": "\u7d9a\u304f\u5206\u5272\u306e\u9810\u3051\u5165\u308c\u53e3\u5ea7\u3068\u5f15\u304d\u51fa\u3057\u53e3\u5ea7\u306f\u3001\u9001\u91d1\u306e\u6700\u521d\u306e\u5206\u5272\u306e\u5b9a\u7fa9\u306b\u3088\u3063\u3066\u8986\u3055\u308c\u308b\u3053\u3068\u306b\u6ce8\u610f\u3057\u3066\u304f\u3060\u3055\u3044\u3002", + "webhook_trigger_ANY": "After any event", "webhook_trigger_STORE_TRANSACTION": "\u53d6\u5f15\u4f5c\u6210\u5f8c", "webhook_trigger_UPDATE_TRANSACTION": "\u53d6\u5f15\u66f4\u65b0\u5f8c", "webhook_trigger_DESTROY_TRANSACTION": "\u53d6\u5f15\u524a\u9664\u5f8c", @@ -115,6 +116,7 @@ "webhook_trigger_DESTROY_BUDGET": "After budget delete", "webhook_trigger_STORE_UPDATE_BUDGET_LIMIT": "After budgeted amount change", "webhook_response_TRANSACTIONS": "\u53d6\u5f15\u8a73\u7d30", + "webhook_response_RELEVANT": "Relevant details", "webhook_response_ACCOUNTS": "\u53e3\u5ea7\u8a73\u7d30", "webhook_response_NONE": "No details", "webhook_delivery_JSON": "JSON", diff --git a/resources/assets/v1/src/locales/ko.json b/resources/assets/v1/src/locales/ko.json index 2a6269caa4..534a0cc035 100644 --- a/resources/assets/v1/src/locales/ko.json +++ b/resources/assets/v1/src/locales/ko.json @@ -107,6 +107,7 @@ "multi_account_warning_withdrawal": "\ub4a4\ub530\ub974\ub294 \ubd84\ud560\uc758 \uc18c\uc2a4 \uacc4\uc815\uc740 \uccab \ubc88\uc9f8 \ucd9c\uae08 \ubd84\ud560\uc5d0 \uc815\uc758\ub41c \ub0b4\uc6a9\uc5d0 \ub530\ub77c \uc7ac\uc815\uc758\ub41c\ub2e4\ub294 \uc810\uc5d0 \uc720\uc758\ud558\uc2dc\uae30 \ubc14\ub78d\ub2c8\ub2e4.", "multi_account_warning_deposit": "\ub4a4\ub530\ub974\ub294 \ubd84\ud560\uc758 \ub300\uc0c1 \uacc4\uc815\uc740 \uccab \ubc88\uc9f8 \uc785\uae08 \ubd84\ud560\uc5d0 \uc815\uc758\ub41c \ub0b4\uc6a9\uc5d0 \ub530\ub77c \uc7ac\uc815\uc758\ub41c\ub2e4\ub294 \uc810\uc5d0 \uc720\uc758\ud558\uc2dc\uae30 \ubc14\ub78d\ub2c8\ub2e4.", "multi_account_warning_transfer": "\ub4a4\ub530\ub974\ub294 \ubd84\ud560\uc758 \uc18c\uc2a4 + \ub300\uc0c1 \uacc4\uc815\uc740 \uccab \ubc88\uc9f8 \uc774\uccb4 \ubd84\ud560\uc5d0 \uc815\uc758\ub41c \ub0b4\uc6a9\uc5d0 \ub530\ub77c \uc7ac\uc815\uc758\ub41c\ub2e4\ub294 \uc810\uc5d0 \uc720\uc758\ud558\uc2dc\uae30 \ubc14\ub78d\ub2c8\ub2e4.", + "webhook_trigger_ANY": "After any event", "webhook_trigger_STORE_TRANSACTION": "\uac70\ub798 \uc0dd\uc131 \uc774\ud6c4", "webhook_trigger_UPDATE_TRANSACTION": "\uac70\ub798 \uc5c5\ub370\uc774\ud2b8 \uc774\ud6c4", "webhook_trigger_DESTROY_TRANSACTION": "\uac70\ub798 \uc0ad\uc81c \uc774\ud6c4", @@ -115,6 +116,7 @@ "webhook_trigger_DESTROY_BUDGET": "After budget delete", "webhook_trigger_STORE_UPDATE_BUDGET_LIMIT": "After budgeted amount change", "webhook_response_TRANSACTIONS": "\uac70\ub798 \uc138\ubd80 \uc815\ubcf4", + "webhook_response_RELEVANT": "Relevant details", "webhook_response_ACCOUNTS": "\uacc4\uc815 \uc815\ubcf4", "webhook_response_NONE": "No details", "webhook_delivery_JSON": "JSON", diff --git a/resources/assets/v1/src/locales/nb.json b/resources/assets/v1/src/locales/nb.json index fcd8a9b992..e9ddda88d0 100644 --- a/resources/assets/v1/src/locales/nb.json +++ b/resources/assets/v1/src/locales/nb.json @@ -107,6 +107,7 @@ "multi_account_warning_withdrawal": "Husk at kildekontoen for etterf\u00f8lgende oppsplitting skal overlates av hva som defineres i den f\u00f8rste delen av uttrekket.", "multi_account_warning_deposit": "Husk at mottakerkontoen for etterf\u00f8lgende oppsplitting skal overstyres av det som er definert i den f\u00f8rste delen av depositumet.", "multi_account_warning_transfer": "Husk at kildens pluss destinasjonskonto med etterf\u00f8lgende oppdeling overstyres av det som er definert i en f\u00f8rste del av overf\u00f8ringen.", + "webhook_trigger_ANY": "After any event", "webhook_trigger_STORE_TRANSACTION": "Etter transaksjons opprettelse", "webhook_trigger_UPDATE_TRANSACTION": "Etter transaksjons oppdatering", "webhook_trigger_DESTROY_TRANSACTION": "Etter transaksjons sletting", @@ -115,6 +116,7 @@ "webhook_trigger_DESTROY_BUDGET": "After budget delete", "webhook_trigger_STORE_UPDATE_BUDGET_LIMIT": "After budgeted amount change", "webhook_response_TRANSACTIONS": "Transaksjonsdetaljer", + "webhook_response_RELEVANT": "Relevant details", "webhook_response_ACCOUNTS": "Kontodetaljer", "webhook_response_NONE": "No details", "webhook_delivery_JSON": "JSON", diff --git a/resources/assets/v1/src/locales/nl.json b/resources/assets/v1/src/locales/nl.json index 963d00b15e..32a8620af1 100644 --- a/resources/assets/v1/src/locales/nl.json +++ b/resources/assets/v1/src/locales/nl.json @@ -107,6 +107,7 @@ "multi_account_warning_withdrawal": "De bronrekening wordt overschreven door wat er in de eerste split staat.", "multi_account_warning_deposit": "De doelrekening wordt overschreven door wat er in de eerste split staat.", "multi_account_warning_transfer": "De bron + doelrekening wordt overschreven door wat er in de eerste split staat.", + "webhook_trigger_ANY": "After any event", "webhook_trigger_STORE_TRANSACTION": "Na het maken van een transactie", "webhook_trigger_UPDATE_TRANSACTION": "Na het updaten van een transactie", "webhook_trigger_DESTROY_TRANSACTION": "Na het verwijderen van een transactie", @@ -115,6 +116,7 @@ "webhook_trigger_DESTROY_BUDGET": "After budget delete", "webhook_trigger_STORE_UPDATE_BUDGET_LIMIT": "After budgeted amount change", "webhook_response_TRANSACTIONS": "Transactiedetails", + "webhook_response_RELEVANT": "Relevant details", "webhook_response_ACCOUNTS": "Rekeningdetails", "webhook_response_NONE": "No details", "webhook_delivery_JSON": "JSON", diff --git a/resources/assets/v1/src/locales/nn.json b/resources/assets/v1/src/locales/nn.json index 0fb4ad1645..a08bf7d7f6 100644 --- a/resources/assets/v1/src/locales/nn.json +++ b/resources/assets/v1/src/locales/nn.json @@ -107,6 +107,7 @@ "multi_account_warning_withdrawal": "Husk at kildekontoen for etterf\u00f8lgende oppsplitting skal overlates av kva som defineres i den f\u00f8rste delen av uttrekket.", "multi_account_warning_deposit": "Husk at mottakerkontoen for etterf\u00f8lgende oppsplitting skal overstyres av det som er definert i den f\u00f8rste delen av depositumet.", "multi_account_warning_transfer": "Husk at kildens pluss destinasjonskonto med etterf\u00f8lgende oppdeling overstyres av det som er definert i ein f\u00f8rste del av overf\u00f8ringen.", + "webhook_trigger_ANY": "After any event", "webhook_trigger_STORE_TRANSACTION": "Etter transaksjons opprettelse", "webhook_trigger_UPDATE_TRANSACTION": "Etter transaksjons oppdatering", "webhook_trigger_DESTROY_TRANSACTION": "Etter transaksjons sletting", @@ -115,6 +116,7 @@ "webhook_trigger_DESTROY_BUDGET": "After budget delete", "webhook_trigger_STORE_UPDATE_BUDGET_LIMIT": "After budgeted amount change", "webhook_response_TRANSACTIONS": "Transaksjonsdetaljer", + "webhook_response_RELEVANT": "Relevant details", "webhook_response_ACCOUNTS": "Kontodetaljer", "webhook_response_NONE": "No details", "webhook_delivery_JSON": "JSON", diff --git a/resources/assets/v1/src/locales/pl.json b/resources/assets/v1/src/locales/pl.json index 974fc7291c..3f8e8c6d63 100644 --- a/resources/assets/v1/src/locales/pl.json +++ b/resources/assets/v1/src/locales/pl.json @@ -107,6 +107,7 @@ "multi_account_warning_withdrawal": "Pami\u0119taj, \u017ce konto \u017ar\u00f3d\u0142owe kolejnych podzia\u0142\u00f3w zostanie ustawione na konto zdefiniowane w pierwszym podziale wyp\u0142aty.", "multi_account_warning_deposit": "Pami\u0119taj, \u017ce konto docelowe kolejnych podzia\u0142\u00f3w zostanie ustawione na konto zdefiniowane w pierwszym podziale wp\u0142aty.", "multi_account_warning_transfer": "Pami\u0119taj, \u017ce konta \u017ar\u00f3d\u0142owe i docelowe kolejnych podzia\u0142\u00f3w zostan\u0105 ustawione na konto zdefiniowane w pierwszym podziale transferu.", + "webhook_trigger_ANY": "After any event", "webhook_trigger_STORE_TRANSACTION": "Po utworzeniu transakcji", "webhook_trigger_UPDATE_TRANSACTION": "Po zmodyfikowaniu transakcji", "webhook_trigger_DESTROY_TRANSACTION": "Po usuni\u0119ciu transakcji", @@ -115,6 +116,7 @@ "webhook_trigger_DESTROY_BUDGET": "After budget delete", "webhook_trigger_STORE_UPDATE_BUDGET_LIMIT": "After budgeted amount change", "webhook_response_TRANSACTIONS": "Szczeg\u00f3\u0142y transakcji", + "webhook_response_RELEVANT": "Relevant details", "webhook_response_ACCOUNTS": "Szczeg\u00f3\u0142y konta", "webhook_response_NONE": "No details", "webhook_delivery_JSON": "JSON", diff --git a/resources/assets/v1/src/locales/pt-br.json b/resources/assets/v1/src/locales/pt-br.json index 1e3dfcfb0c..89700c8987 100644 --- a/resources/assets/v1/src/locales/pt-br.json +++ b/resources/assets/v1/src/locales/pt-br.json @@ -107,6 +107,7 @@ "multi_account_warning_withdrawal": "Tenha em mente que a conta de origem das divis\u00f5es subsequentes ser\u00e1 sobrescrita pelo que estiver definido na primeira divis\u00e3o da sa\u00edda.", "multi_account_warning_deposit": "Tenha em mente que a conta de destino das divis\u00f5es subsequentes ser\u00e1 sobrescrita pelo que estiver definido na primeira divis\u00e3o da entrada.", "multi_account_warning_transfer": "Tenha em mente que a conta de origem + de destino das divis\u00f5es subsequentes ser\u00e3o sobrescritas pelo que for definido na primeira divis\u00e3o da transfer\u00eancia.", + "webhook_trigger_ANY": "After any event", "webhook_trigger_STORE_TRANSACTION": "Ap\u00f3s cria\u00e7\u00e3o da transa\u00e7\u00e3o", "webhook_trigger_UPDATE_TRANSACTION": "Ap\u00f3s atualiza\u00e7\u00e3o da transa\u00e7\u00e3o", "webhook_trigger_DESTROY_TRANSACTION": "Ap\u00f3s exclus\u00e3o da transa\u00e7\u00e3o", @@ -115,6 +116,7 @@ "webhook_trigger_DESTROY_BUDGET": "After budget delete", "webhook_trigger_STORE_UPDATE_BUDGET_LIMIT": "After budgeted amount change", "webhook_response_TRANSACTIONS": "Detalhes da transa\u00e7\u00e3o", + "webhook_response_RELEVANT": "Relevant details", "webhook_response_ACCOUNTS": "Detalhes da conta", "webhook_response_NONE": "No details", "webhook_delivery_JSON": "JSON", diff --git a/resources/assets/v1/src/locales/pt.json b/resources/assets/v1/src/locales/pt.json index 4182e252fb..4ec6388b92 100644 --- a/resources/assets/v1/src/locales/pt.json +++ b/resources/assets/v1/src/locales/pt.json @@ -107,6 +107,7 @@ "multi_account_warning_withdrawal": "Tenha presente que a conta de origem de divis\u00f5es subsequentes ser\u00e1 sobrescrita pelo que estiver definido na primeira divis\u00e3o do levantamento.", "multi_account_warning_deposit": "Tenha presente que a conta de destino de divis\u00f5es subsequentes ser\u00e1 sobrescrita pelo que estiver definida na primeira divis\u00e3o do dep\u00f3sito.", "multi_account_warning_transfer": "Tenha presenta que a conta de origem + destino de divis\u00f5es subsequentes ser\u00e3o sobrescritas pelo que estiver definido na primeira divis\u00e3o da transfer\u00eancia.", + "webhook_trigger_ANY": "After any event", "webhook_trigger_STORE_TRANSACTION": "Ap\u00f3s criar transa\u00e7\u00e3o", "webhook_trigger_UPDATE_TRANSACTION": "Ap\u00f3s atualizar transa\u00e7\u00e3o", "webhook_trigger_DESTROY_TRANSACTION": "Ap\u00f3s eliminar transa\u00e7\u00e3o", @@ -115,6 +116,7 @@ "webhook_trigger_DESTROY_BUDGET": "After budget delete", "webhook_trigger_STORE_UPDATE_BUDGET_LIMIT": "After budgeted amount change", "webhook_response_TRANSACTIONS": "Detalhes da transa\u00e7\u00e3o", + "webhook_response_RELEVANT": "Relevant details", "webhook_response_ACCOUNTS": "Detalhes da conta", "webhook_response_NONE": "No details", "webhook_delivery_JSON": "JSON", diff --git a/resources/assets/v1/src/locales/ro.json b/resources/assets/v1/src/locales/ro.json index d3168c2f00..118eaf015b 100644 --- a/resources/assets/v1/src/locales/ro.json +++ b/resources/assets/v1/src/locales/ro.json @@ -107,6 +107,7 @@ "multi_account_warning_withdrawal": "Re\u0163ine\u0163i faptul c\u0103 sursa scind\u0103rilor ulterioare va fi anulat\u0103 de orice altceva definit \u00een prima \u00eemp\u0103r\u0163ire a retragerii.", "multi_account_warning_deposit": "\u021aine\u021bi cont de faptul c\u0103 destina\u021bia scind\u0103rilor ulterioare va fi dep\u0103\u0219it\u0103 cu orice se define\u0219te la prima \u00eemp\u0103r\u021bire a depozitului.", "multi_account_warning_transfer": "Re\u0163ine\u0163i faptul c\u0103 contul sursei + destina\u0163ia frac\u0163ion\u0103rilor ulterioare va fi anulat de orice se define\u015fte \u00een prima \u00eemp\u0103r\u0163ire a transferului.", + "webhook_trigger_ANY": "After any event", "webhook_trigger_STORE_TRANSACTION": "Dup\u0103 crearea tranzac\u021biei", "webhook_trigger_UPDATE_TRANSACTION": "Dup\u0103 actualizarea tranzac\u021biei", "webhook_trigger_DESTROY_TRANSACTION": "Dup\u0103 \u0219tergerea tranzac\u021biei", @@ -115,6 +116,7 @@ "webhook_trigger_DESTROY_BUDGET": "After budget delete", "webhook_trigger_STORE_UPDATE_BUDGET_LIMIT": "After budgeted amount change", "webhook_response_TRANSACTIONS": "Detaliile tranzac\u021biei", + "webhook_response_RELEVANT": "Relevant details", "webhook_response_ACCOUNTS": "Detalii cont", "webhook_response_NONE": "No details", "webhook_delivery_JSON": "JSON", diff --git a/resources/assets/v1/src/locales/ru.json b/resources/assets/v1/src/locales/ru.json index 66fad25315..95532e247c 100644 --- a/resources/assets/v1/src/locales/ru.json +++ b/resources/assets/v1/src/locales/ru.json @@ -107,6 +107,7 @@ "multi_account_warning_withdrawal": "\u0418\u043c\u0435\u0439\u0442\u0435 \u0432 \u0432\u0438\u0434\u0443, \u0447\u0442\u043e \u0441\u0447\u0451\u0442-\u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a \u0432 \u0434\u0440\u0443\u0433\u0438\u0445 \u0447\u0430\u0441\u0442\u044f\u0445 \u0440\u0430\u0437\u0434\u0435\u043b\u0451\u043d\u043d\u043e\u0439 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438 \u0431\u0443\u0434\u0435\u0442 \u0442\u0430\u043a\u0438\u043c \u0436\u0435, \u043a\u0430\u043a \u0432 \u043f\u0435\u0440\u0432\u043e\u0439 \u0447\u0430\u0441\u0442\u0438 \u0440\u0430\u0441\u0445\u043e\u0434\u0430.", "multi_account_warning_deposit": "\u0418\u043c\u0435\u0439\u0442\u0435 \u0432 \u0432\u0438\u0434\u0443, \u0447\u0442\u043e \u0441\u0447\u0451\u0442 \u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u0432 \u0434\u0440\u0443\u0433\u0438\u0445 \u0447\u0430\u0441\u0442\u044f\u0445 \u0440\u0430\u0437\u0434\u0435\u043b\u0451\u043d\u043d\u043e\u0439 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438 \u0431\u0443\u0434\u0435\u0442 \u0442\u0430\u043a\u0438\u043c \u0436\u0435, \u043a\u0430\u043a \u0432 \u043f\u0435\u0440\u0432\u043e\u0439 \u0447\u0430\u0441\u0442\u0438 \u0434\u043e\u0445\u043e\u0434\u0430.", "multi_account_warning_transfer": "\u0418\u043c\u0435\u0439\u0442\u0435 \u0432 \u0432\u0438\u0434\u0443, \u0447\u0442\u043e \u0441\u0447\u0451\u0442-\u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a \u0438 \u0441\u0447\u0451\u0442 \u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u0432 \u0434\u0440\u0443\u0433\u0438\u0445 \u0447\u0430\u0441\u0442\u044f\u0445 \u0440\u0430\u0437\u0434\u0435\u043b\u0451\u043d\u043d\u043e\u0439 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438 \u0431\u0443\u0434\u0443\u0442 \u0442\u0430\u043a\u0438\u043c\u0438 \u0436\u0435, \u043a\u0430\u043a \u0432 \u043f\u0435\u0440\u0432\u043e\u0439 \u0447\u0430\u0441\u0442\u0438 \u043f\u0435\u0440\u0435\u0432\u043e\u0434\u0430.", + "webhook_trigger_ANY": "After any event", "webhook_trigger_STORE_TRANSACTION": "\u041f\u043e\u0441\u043b\u0435 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438", "webhook_trigger_UPDATE_TRANSACTION": "\u041f\u043e\u0441\u043b\u0435 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438", "webhook_trigger_DESTROY_TRANSACTION": "\u041f\u043e\u0441\u043b\u0435 \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438", @@ -115,6 +116,7 @@ "webhook_trigger_DESTROY_BUDGET": "After budget delete", "webhook_trigger_STORE_UPDATE_BUDGET_LIMIT": "After budgeted amount change", "webhook_response_TRANSACTIONS": "\u0414\u0435\u0442\u0430\u043b\u0438 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438", + "webhook_response_RELEVANT": "Relevant details", "webhook_response_ACCOUNTS": "\u0421\u0432\u0435\u0434\u0435\u043d\u0438\u044f \u043e\u0431 \u0443\u0447\u0435\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438", "webhook_response_NONE": "No details", "webhook_delivery_JSON": "JSON", diff --git a/resources/assets/v1/src/locales/sk.json b/resources/assets/v1/src/locales/sk.json index bdeeb6d821..484c5353d9 100644 --- a/resources/assets/v1/src/locales/sk.json +++ b/resources/assets/v1/src/locales/sk.json @@ -107,6 +107,7 @@ "multi_account_warning_withdrawal": "Majte na pam\u00e4ti, \u017ee zdrojov\u00fd bankov\u00fd \u00fa\u010det n\u00e1sledn\u00fdch roz\u00fa\u010dtovan\u00ed bude prep\u00edsan\u00fd t\u00fdm, \u010do je definovan\u00e9 v prvom rozdelen\u00ed v\u00fdberu.", "multi_account_warning_deposit": "Majte na pam\u00e4ti, \u017ee zdrojov\u00fd bankov\u00fd \u00fa\u010det n\u00e1sledn\u00fdch roz\u00fa\u010dtovan\u00ed bude prep\u00edsan\u00fd t\u00fdm, \u010do je definovan\u00e9 v prvom roz\u00fa\u010dtovan\u00ed vkladu.", "multi_account_warning_transfer": "Majte na pam\u00e4ti, \u017ee zdrojov\u00fd a cie\u013eov\u00fd bankov\u00fd \u00fa\u010det n\u00e1sledn\u00fdch roz\u00fa\u010dtovan\u00ed bude prep\u00edsan\u00fd t\u00fdm, \u010do je definovan\u00e9 v prvom roz\u00fa\u010dtovan\u00ed prevodu.", + "webhook_trigger_ANY": "After any event", "webhook_trigger_STORE_TRANSACTION": "After transaction creation", "webhook_trigger_UPDATE_TRANSACTION": "After transaction update", "webhook_trigger_DESTROY_TRANSACTION": "After transaction delete", @@ -115,6 +116,7 @@ "webhook_trigger_DESTROY_BUDGET": "After budget delete", "webhook_trigger_STORE_UPDATE_BUDGET_LIMIT": "After budgeted amount change", "webhook_response_TRANSACTIONS": "Transaction details", + "webhook_response_RELEVANT": "Relevant details", "webhook_response_ACCOUNTS": "Account details", "webhook_response_NONE": "No details", "webhook_delivery_JSON": "JSON", diff --git a/resources/assets/v1/src/locales/sl.json b/resources/assets/v1/src/locales/sl.json index 9da2865831..dcd7f3d5b8 100644 --- a/resources/assets/v1/src/locales/sl.json +++ b/resources/assets/v1/src/locales/sl.json @@ -107,6 +107,7 @@ "multi_account_warning_withdrawal": "Upo\u0161tevajte, da bo izvorni ra\u010dun poznej\u0161ih razdelitev preglasilo tisto, kar je definirano v prvi razdelitvi odliva.", "multi_account_warning_deposit": "Upo\u0161tevajte, da bo ciljni ra\u010dun poznej\u0161ih delitev preglasilo tisto, kar je opredeljeno v prvi delitvi priliva.", "multi_account_warning_transfer": "Upo\u0161tevajte, da bo izvorni + ciljni ra\u010dun poznej\u0161ih razdelitev preglasilo tisto, kar je definirano v prvi razdelitvi prenosa.", + "webhook_trigger_ANY": "After any event", "webhook_trigger_STORE_TRANSACTION": "Po ustvarjanju transakcije", "webhook_trigger_UPDATE_TRANSACTION": "Po posodabljanju transakcije", "webhook_trigger_DESTROY_TRANSACTION": "Po brisanju transakcije", @@ -115,6 +116,7 @@ "webhook_trigger_DESTROY_BUDGET": "After budget delete", "webhook_trigger_STORE_UPDATE_BUDGET_LIMIT": "After budgeted amount change", "webhook_response_TRANSACTIONS": "Podrobnosti transakcije", + "webhook_response_RELEVANT": "Relevant details", "webhook_response_ACCOUNTS": "Podrobnosti ra\u010duna", "webhook_response_NONE": "No details", "webhook_delivery_JSON": "JSON", diff --git a/resources/assets/v1/src/locales/sv.json b/resources/assets/v1/src/locales/sv.json index 7b27b25a45..6e6f381f5b 100644 --- a/resources/assets/v1/src/locales/sv.json +++ b/resources/assets/v1/src/locales/sv.json @@ -107,6 +107,7 @@ "multi_account_warning_withdrawal": "T\u00e4nk p\u00e5 att k\u00e4llkontot f\u00f6r efterf\u00f6ljande uppdelningar kommer att upph\u00e4vas av vad som \u00e4n definieras i den f\u00f6rsta uppdelningen av uttaget.", "multi_account_warning_deposit": "T\u00e4nk p\u00e5 att destinationskontot f\u00f6r efterf\u00f6ljande uppdelningar kommer att styras av vad som \u00e4n definieras i den f\u00f6rsta uppdelningen av ins\u00e4ttningen.", "multi_account_warning_transfer": "T\u00e4nk p\u00e5 att k\u00e4ll + destinationskonto av efterf\u00f6ljande delningar kommer att styras av vad som definieras i den f\u00f6rsta uppdelningen av \u00f6verf\u00f6ringen.", + "webhook_trigger_ANY": "After any event", "webhook_trigger_STORE_TRANSACTION": "Efter skapande av transaktion", "webhook_trigger_UPDATE_TRANSACTION": "After transaction update", "webhook_trigger_DESTROY_TRANSACTION": "After transaction delete", @@ -115,6 +116,7 @@ "webhook_trigger_DESTROY_BUDGET": "After budget delete", "webhook_trigger_STORE_UPDATE_BUDGET_LIMIT": "After budgeted amount change", "webhook_response_TRANSACTIONS": "Transaktionsdetaljer", + "webhook_response_RELEVANT": "Relevant details", "webhook_response_ACCOUNTS": "Kontodetaljer", "webhook_response_NONE": "No details", "webhook_delivery_JSON": "JSON", diff --git a/resources/assets/v1/src/locales/tr.json b/resources/assets/v1/src/locales/tr.json index 3d201f426e..59c1ee3835 100644 --- a/resources/assets/v1/src/locales/tr.json +++ b/resources/assets/v1/src/locales/tr.json @@ -107,6 +107,7 @@ "multi_account_warning_withdrawal": "Sonraki b\u00f6l\u00fcnmelerin kaynak hesab\u0131n\u0131n, geri \u00e7ekilmenin ilk b\u00f6l\u00fcnmesinde tan\u0131mlanan herhangi bir \u015fey taraf\u0131ndan reddedilece\u011fini unutmay\u0131n.", "multi_account_warning_deposit": "Sonraki b\u00f6l\u00fcnmelerin hedef hesab\u0131n\u0131n, mevduat\u0131n ilk b\u00f6l\u00fcnmesinde tan\u0131mlanan herhangi bir \u015fey taraf\u0131ndan iptal edilece\u011fini unutmay\u0131n.", "multi_account_warning_transfer": "Sonraki b\u00f6l\u00fcnmelerin kaynak + hedef hesab\u0131n\u0131n, aktar\u0131m\u0131n ilk b\u00f6l\u00fcnmesinde tan\u0131mlanan her \u015fey taraf\u0131ndan ge\u00e7ersiz k\u0131l\u0131naca\u011f\u0131n\u0131 unutmay\u0131n.", + "webhook_trigger_ANY": "After any event", "webhook_trigger_STORE_TRANSACTION": "\u0130\u015flem olu\u015fturma sonras\u0131", "webhook_trigger_UPDATE_TRANSACTION": "\u0130\u015flem g\u00fcncelleme sonras\u0131", "webhook_trigger_DESTROY_TRANSACTION": "\u0130\u015flem silme sonras\u0131", @@ -115,6 +116,7 @@ "webhook_trigger_DESTROY_BUDGET": "After budget delete", "webhook_trigger_STORE_UPDATE_BUDGET_LIMIT": "After budgeted amount change", "webhook_response_TRANSACTIONS": "\u0130\u015flem detaylar\u0131", + "webhook_response_RELEVANT": "Relevant details", "webhook_response_ACCOUNTS": "Hesap detaylar\u0131", "webhook_response_NONE": "No details", "webhook_delivery_JSON": "JSON", diff --git a/resources/assets/v1/src/locales/uk.json b/resources/assets/v1/src/locales/uk.json index cceda0de8b..144f79546c 100644 --- a/resources/assets/v1/src/locales/uk.json +++ b/resources/assets/v1/src/locales/uk.json @@ -107,6 +107,7 @@ "multi_account_warning_withdrawal": "\u0417\u0430\u0443\u0432\u0430\u0436\u0442\u0435, \u0449\u043e \u0432\u0438\u0445\u0456\u0434\u043d\u0438\u043c \u0440\u0430\u0445\u0443\u043d\u043a\u043e\u043c \u0434\u043b\u044f \u043d\u0430\u0441\u0442\u0443\u043f\u043d\u0438\u0445 \u0440\u043e\u0437\u043f\u043e\u0434\u0456\u043b\u0456\u0432 \u0431\u0443\u0434\u0435 \u0432\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043e \u0440\u0430\u0445\u0443\u043d\u043e\u043a, \u0432\u0438\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439 \u0443 \u043f\u0435\u0440\u0448\u043e\u043c\u0443 \u0440\u043e\u0437\u043f\u043e\u0434\u0456\u043b\u0456 \u0432\u0438\u043f\u043b\u0430\u0442.", "multi_account_warning_deposit": "\u0417\u0430\u0443\u0432\u0430\u0436\u0442\u0435, \u0449\u043e \u0446\u0456\u043b\u044c\u043e\u0432\u0438\u043c \u0440\u0430\u0445\u0443\u043d\u043a\u043e\u043c \u0434\u043b\u044f \u043d\u0430\u0441\u0442\u0443\u043f\u043d\u0438\u0445 \u0440\u043e\u0437\u043f\u043e\u0434\u0456\u043b\u0456\u0432 \u0431\u0443\u0434\u0435 \u0432\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043e \u0440\u0430\u0445\u0443\u043d\u043e\u043a, \u0432\u0438\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439 \u0443 \u043f\u0435\u0440\u0448\u043e\u043c\u0443 \u0440\u043e\u0437\u043f\u043e\u0434\u0456\u043b\u0456 \u0434\u0435\u043f\u043e\u0437\u0438\u0442\u0443.", "multi_account_warning_transfer": "\u0417\u0430\u0443\u0432\u0430\u0436\u0442\u0435, \u0449\u043e \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0456 \u0437\u0430\u043f\u0438\u0441\u0438 \u0434\u0436\u0435\u0440\u0435\u043b\u0430 \u0442\u0430 \u043f\u0440\u0438\u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u043d\u0430\u0441\u0442\u0443\u043f\u043d\u0438\u0445 \u043f\u043e\u0434\u0456\u043b\u0456\u0432 \u0431\u0443\u0434\u0435 \u0432\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043e \u043d\u0430 \u0440\u0430\u0445\u0443\u043d\u043e\u043a, \u0432\u0438\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439 \u0443 \u043f\u0435\u0440\u0448\u043e\u043c\u0443 \u043f\u043e\u0434\u0456\u043b\u0456 \u043f\u0435\u0440\u0435\u043a\u0430\u0437\u0443.", + "webhook_trigger_ANY": "After any event", "webhook_trigger_STORE_TRANSACTION": "\u041f\u0456\u0441\u043b\u044f \u0441\u0442\u0432\u043e\u0440\u0435\u043d\u043d\u044f \u043e\u043f\u0435\u0440\u0430\u0446\u0456\u0457", "webhook_trigger_UPDATE_TRANSACTION": "\u041f\u0456\u0441\u043b\u044f \u043e\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044f \u043e\u043f\u0435\u0440\u0430\u0446\u0456\u0457", "webhook_trigger_DESTROY_TRANSACTION": "\u041f\u0456\u0441\u043b\u044f \u0432\u0438\u0434\u0430\u043b\u0435\u043d\u043d\u044f \u043e\u043f\u0435\u0440\u0430\u0446\u0456\u0457", @@ -115,6 +116,7 @@ "webhook_trigger_DESTROY_BUDGET": "After budget delete", "webhook_trigger_STORE_UPDATE_BUDGET_LIMIT": "After budgeted amount change", "webhook_response_TRANSACTIONS": "\u0414\u0435\u0442\u0430\u043b\u0456 \u043e\u043f\u0435\u0440\u0430\u0446\u0456\u0457", + "webhook_response_RELEVANT": "Relevant details", "webhook_response_ACCOUNTS": "\u0414\u0430\u043d\u0456 \u0440\u0430\u0445\u0443\u043d\u043a\u0443", "webhook_response_NONE": "No details", "webhook_delivery_JSON": "JSON", diff --git a/resources/assets/v1/src/locales/vi.json b/resources/assets/v1/src/locales/vi.json index 1a806db0ab..bd0371d48c 100644 --- a/resources/assets/v1/src/locales/vi.json +++ b/resources/assets/v1/src/locales/vi.json @@ -107,6 +107,7 @@ "multi_account_warning_withdrawal": "Keep in mind that the source account of subsequent splits will be overruled by whatever is defined in the first split of the withdrawal.", "multi_account_warning_deposit": "Keep in mind that the destination account of subsequent splits will be overruled by whatever is defined in the first split of the deposit.", "multi_account_warning_transfer": "Keep in mind that the source + destination account of subsequent splits will be overruled by whatever is defined in the first split of the transfer.", + "webhook_trigger_ANY": "After any event", "webhook_trigger_STORE_TRANSACTION": "Sau khi t\u1ea1o giao d\u1ecbch", "webhook_trigger_UPDATE_TRANSACTION": "Sau khi c\u1eadp nh\u1eadt giao d\u1ecbch", "webhook_trigger_DESTROY_TRANSACTION": "Sau khi x\u00f3a giao d\u1ecbch", @@ -115,6 +116,7 @@ "webhook_trigger_DESTROY_BUDGET": "After budget delete", "webhook_trigger_STORE_UPDATE_BUDGET_LIMIT": "After budgeted amount change", "webhook_response_TRANSACTIONS": "Chi ti\u1ebft giao d\u1ecbch", + "webhook_response_RELEVANT": "Relevant details", "webhook_response_ACCOUNTS": "Chi ti\u1ebft t\u00e0i kho\u1ea3n", "webhook_response_NONE": "No details", "webhook_delivery_JSON": "JSON", diff --git a/resources/assets/v1/src/locales/zh-cn.json b/resources/assets/v1/src/locales/zh-cn.json index bf4d219bea..f929bf5213 100644 --- a/resources/assets/v1/src/locales/zh-cn.json +++ b/resources/assets/v1/src/locales/zh-cn.json @@ -107,6 +107,7 @@ "multi_account_warning_withdrawal": "\u8bf7\u6ce8\u610f\uff0c\u540e\u7eed\u62c6\u5206\u7684\u6765\u6e90\u8d26\u6237\u5c06\u4f1a\u88ab\u652f\u51fa\u7684\u9996\u7b14\u62c6\u5206\u7684\u914d\u7f6e\u6240\u8986\u76d6\u3002", "multi_account_warning_deposit": "\u8bf7\u6ce8\u610f\uff0c\u540e\u7eed\u62c6\u5206\u7684\u76ee\u6807\u8d26\u6237\u5c06\u4f1a\u88ab\u6536\u5165\u7684\u9996\u7b14\u62c6\u5206\u7684\u914d\u7f6e\u6240\u8986\u76d6\u3002", "multi_account_warning_transfer": "\u8bf7\u6ce8\u610f\uff0c\u540e\u7eed\u62c6\u5206\u7684\u6765\u6e90\u548c\u76ee\u6807\u8d26\u6237\u5c06\u4f1a\u88ab\u8f6c\u8d26\u7684\u9996\u7b14\u62c6\u5206\u7684\u914d\u7f6e\u6240\u8986\u76d6\u3002", + "webhook_trigger_ANY": "After any event", "webhook_trigger_STORE_TRANSACTION": "\u4ea4\u6613\u521b\u5efa\u540e", "webhook_trigger_UPDATE_TRANSACTION": "\u4ea4\u6613\u66f4\u65b0\u540e", "webhook_trigger_DESTROY_TRANSACTION": "\u4ea4\u6613\u5220\u9664\u540e", @@ -115,6 +116,7 @@ "webhook_trigger_DESTROY_BUDGET": "After budget delete", "webhook_trigger_STORE_UPDATE_BUDGET_LIMIT": "After budgeted amount change", "webhook_response_TRANSACTIONS": "\u4ea4\u6613\u8be6\u60c5", + "webhook_response_RELEVANT": "Relevant details", "webhook_response_ACCOUNTS": "\u8d26\u6237\u8be6\u60c5", "webhook_response_NONE": "No details", "webhook_delivery_JSON": "JSON", diff --git a/resources/assets/v1/src/locales/zh-tw.json b/resources/assets/v1/src/locales/zh-tw.json index 4787f5212f..1341ae4594 100644 --- a/resources/assets/v1/src/locales/zh-tw.json +++ b/resources/assets/v1/src/locales/zh-tw.json @@ -107,6 +107,7 @@ "multi_account_warning_withdrawal": "\u8acb\u6ce8\u610f\uff0c\u82e5\u60a8\u5efa\u7acb\u7684\u662f\u63d0\u6b3e\u4ea4\u6613\uff0c\u5f8c\u7e8c\u62c6\u5206\u7684\u4f86\u6e90\u5e33\u6236\u5c07\u6703\u88ab\u7b2c\u4e00\u7b46\u62c6\u5206\u4e2d\u8a2d\u5b9a\u7684\u5e33\u6236\u8986\u5beb\u3002", "multi_account_warning_deposit": "\u8acb\u6ce8\u610f\uff0c\u82e5\u60a8\u5efa\u7acb\u7684\u662f\u5b58\u6b3e\u4ea4\u6613\uff0c\u5f8c\u7e8c\u62c6\u5206\u7684\u76ee\u6a19\u5e33\u6236\u5c07\u6703\u88ab\u7b2c\u4e00\u7b46\u62c6\u5206\u4e2d\u8a2d\u5b9a\u7684\u5e33\u6236\u8986\u5beb\u3002", "multi_account_warning_transfer": "\u8acb\u6ce8\u610f\uff0c\u82e5\u60a8\u5efa\u7acb\u7684\u662f\u8f49\u5e33\u4ea4\u6613\uff0c\u5f8c\u7e8c\u62c6\u5206\u7684\u4f86\u6e90\u8207\u76ee\u6a19\u5e33\u6236\u5c07\u6703\u88ab\u7b2c\u4e00\u7b46\u62c6\u5206\u4e2d\u8a2d\u5b9a\u7684\u5e33\u6236\u8986\u5beb\u3002", + "webhook_trigger_ANY": "After any event", "webhook_trigger_STORE_TRANSACTION": "\u5728\u4ea4\u6613\u5efa\u7acb\u5f8c", "webhook_trigger_UPDATE_TRANSACTION": "\u5728\u4ea4\u6613\u66f4\u65b0\u5f8c", "webhook_trigger_DESTROY_TRANSACTION": "\u5728\u4ea4\u6613\u522a\u9664\u5f8c", @@ -115,6 +116,7 @@ "webhook_trigger_DESTROY_BUDGET": "After budget delete", "webhook_trigger_STORE_UPDATE_BUDGET_LIMIT": "After budgeted amount change", "webhook_response_TRANSACTIONS": "\u4ea4\u6613\u8a73\u60c5", + "webhook_response_RELEVANT": "Relevant details", "webhook_response_ACCOUNTS": "\u5e33\u865f\u8a73\u60c5", "webhook_response_NONE": "No details", "webhook_delivery_JSON": "JSON", From ebc7ea0eb6f85d5db3d6a25525c89e2f1b79da84 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 22 Aug 2025 07:28:09 +0200 Subject: [PATCH 014/111] Fix amount display --- app/Jobs/CreateAutoBudgetLimits.php | 77 ++++++++++--------- changelog.md | 9 +++ .../partials/dashboard/account-list.blade.php | 2 +- .../v2/partials/elements/amount.blade.php | 35 ++++++--- 4 files changed, 72 insertions(+), 51 deletions(-) diff --git a/app/Jobs/CreateAutoBudgetLimits.php b/app/Jobs/CreateAutoBudgetLimits.php index 396c21222c..0d74406fb7 100644 --- a/app/Jobs/CreateAutoBudgetLimits.php +++ b/app/Jobs/CreateAutoBudgetLimits.php @@ -37,6 +37,7 @@ use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Collection; +use Illuminate\Support\Facades\Log; /** * Class CreateAutoBudgetLimits @@ -59,7 +60,7 @@ class CreateAutoBudgetLimits implements ShouldQueue $newDate = clone $date; $newDate->startOfDay(); $this->date = $newDate; - app('log')->debug(sprintf('Created new CreateAutoBudgetLimits("%s")', $this->date->format('Y-m-d'))); + Log::debug(sprintf('Created new CreateAutoBudgetLimits("%s")', $this->date->format('Y-m-d'))); } } @@ -70,9 +71,9 @@ class CreateAutoBudgetLimits implements ShouldQueue */ public function handle(): void { - app('log')->debug(sprintf('Now at start of CreateAutoBudgetLimits() job for %s.', $this->date->format('D d M Y'))); + Log::debug(sprintf('Now at start of CreateAutoBudgetLimits() job for %s.', $this->date->format('D d M Y'))); $autoBudgets = AutoBudget::get(); - app('log')->debug(sprintf('Found %d auto budgets.', $autoBudgets->count())); + Log::debug(sprintf('Found %d auto budgets.', $autoBudgets->count())); foreach ($autoBudgets as $autoBudget) { $this->handleAutoBudget($autoBudget); } @@ -84,18 +85,18 @@ class CreateAutoBudgetLimits implements ShouldQueue private function handleAutoBudget(AutoBudget $autoBudget): void { if (null === $autoBudget->budget) { - app('log')->info(sprintf('Auto budget #%d is associated with a deleted budget.', $autoBudget->id)); + Log::info(sprintf('Auto budget #%d is associated with a deleted budget.', $autoBudget->id)); $autoBudget->delete(); return; } if (false === $autoBudget->budget->active) { - app('log')->info(sprintf('Auto budget #%d is associated with an inactive budget.', $autoBudget->id)); + Log::info(sprintf('Auto budget #%d is associated with an inactive budget.', $autoBudget->id)); return; } if (!$this->isMagicDay($autoBudget)) { - app('log')->info( + Log::info( sprintf( 'Today (%s) is not a magic day for %s auto-budget #%d (part of budget #%d "%s")', $this->date->format('Y-m-d'), @@ -105,11 +106,11 @@ class CreateAutoBudgetLimits implements ShouldQueue $autoBudget->budget->name ) ); - app('log')->debug(sprintf('Done with auto budget #%d', $autoBudget->id)); + Log::debug(sprintf('Done with auto budget #%d', $autoBudget->id)); return; } - app('log')->info( + Log::info( sprintf( 'Today (%s) is a magic day for %s auto-budget #%d (part of budget #%d "%s")', $this->date->format('Y-m-d'), @@ -131,7 +132,7 @@ class CreateAutoBudgetLimits implements ShouldQueue // that's easy: create one. // do nothing else. $this->createBudgetLimit($autoBudget, $start, $end); - app('log')->debug(sprintf('Done with auto budget #%d', $autoBudget->id)); + Log::debug(sprintf('Done with auto budget #%d', $autoBudget->id)); return; } @@ -139,18 +140,18 @@ class CreateAutoBudgetLimits implements ShouldQueue if (!$budgetLimit instanceof BudgetLimit && AutoBudgetType::AUTO_BUDGET_ROLLOVER->value === (int) $autoBudget->auto_budget_type) { // budget limit exists already, $this->createRollover($autoBudget); - app('log')->debug(sprintf('Done with auto budget #%d', $autoBudget->id)); + Log::debug(sprintf('Done with auto budget #%d', $autoBudget->id)); return; } if (!$budgetLimit instanceof BudgetLimit && AutoBudgetType::AUTO_BUDGET_ADJUSTED->value === (int) $autoBudget->auto_budget_type) { // budget limit exists already, $this->createAdjustedLimit($autoBudget); - app('log')->debug(sprintf('Done with auto budget #%d', $autoBudget->id)); + Log::debug(sprintf('Done with auto budget #%d', $autoBudget->id)); return; } - app('log')->debug(sprintf('Done with auto budget #%d', $autoBudget->id)); + Log::debug(sprintf('Done with auto budget #%d', $autoBudget->id)); } /** @@ -193,7 +194,7 @@ class CreateAutoBudgetLimits implements ShouldQueue private function findBudgetLimit(Budget $budget, Carbon $start, Carbon $end): ?BudgetLimit { - app('log')->debug( + Log::debug( sprintf( 'Going to find a budget limit for budget #%d ("%s") between %s and %s', $budget->id, @@ -212,21 +213,21 @@ class CreateAutoBudgetLimits implements ShouldQueue private function createBudgetLimit(AutoBudget $autoBudget, Carbon $start, Carbon $end, ?string $amount = null): void { - app('log')->debug(sprintf('No budget limit exist. Must create one for auto-budget #%d', $autoBudget->id)); + Log::debug(sprintf('No budget limit exist. Must create one for auto-budget #%d', $autoBudget->id)); if (null !== $amount) { - app('log')->debug(sprintf('Amount is overruled and will be set to %s', $amount)); + Log::debug(sprintf('Amount is overruled and will be set to %s', $amount)); } $budgetLimit = new BudgetLimit(); $budgetLimit->budget()->associate($autoBudget->budget); $budgetLimit->transactionCurrency()->associate($autoBudget->transactionCurrency); - $budgetLimit->start_date = $start; - $budgetLimit->end_date = $end; + $budgetLimit->start_date = clone $start; + $budgetLimit->end_date = clone $end; $budgetLimit->amount = $amount ?? $autoBudget->amount; $budgetLimit->period = $autoBudget->period; $budgetLimit->generated = 1; $budgetLimit->save(); - app('log')->debug(sprintf('Created budget limit #%d.', $budgetLimit->id)); + Log::debug(sprintf('Created budget limit #%d.', $budgetLimit->id)); } /** @@ -234,7 +235,7 @@ class CreateAutoBudgetLimits implements ShouldQueue */ private function createRollover(AutoBudget $autoBudget): void { - app('log')->debug(sprintf('Will now manage rollover for auto budget #%d', $autoBudget->id)); + Log::debug(sprintf('Will now manage rollover for auto budget #%d', $autoBudget->id)); // current period: $start = app('navigation')->startOfPeriod($this->date, $autoBudget->period); $end = app('navigation')->endOfPeriod($start, $autoBudget->period); @@ -243,7 +244,7 @@ class CreateAutoBudgetLimits implements ShouldQueue $previousStart = app('navigation')->subtractPeriod($start, $autoBudget->period); $previousEnd = app('navigation')->endOfPeriod($previousStart, $autoBudget->period); - app('log')->debug( + Log::debug( sprintf( 'Current period is %s-%s, so previous period is %s-%s', $start->format('Y-m-d'), @@ -257,44 +258,44 @@ class CreateAutoBudgetLimits implements ShouldQueue $budgetLimit = $this->findBudgetLimit($autoBudget->budget, $previousStart, $previousEnd); if (!$budgetLimit instanceof BudgetLimit) { - app('log')->debug('No budget limit exists in previous period, so create one.'); + Log::debug('No budget limit exists in previous period, so create one.'); // if not, create it and we're done. $this->createBudgetLimit($autoBudget, $start, $end); - app('log')->debug(sprintf('Done with auto budget #%d', $autoBudget->id)); + Log::debug(sprintf('Done with auto budget #%d', $autoBudget->id)); return; } - app('log')->debug('Budget limit exists for previous period.'); + Log::debug('Budget limit exists for previous period.'); // if has one, calculate expenses and use that as a base. $repository = app(OperationsRepositoryInterface::class); $repository->setUser($autoBudget->budget->user); $spent = $repository->sumExpenses($previousStart, $previousEnd, null, new Collection([$autoBudget->budget]), $autoBudget->transactionCurrency); $currencyId = $autoBudget->transaction_currency_id; $spentAmount = $spent[$currencyId]['sum'] ?? '0'; - app('log')->debug(sprintf('Spent in previous budget period (%s-%s) is %s', $previousStart->format('Y-m-d'), $previousEnd->format('Y-m-d'), $spentAmount)); + Log::debug(sprintf('Spent in previous budget period (%s-%s) is %s', $previousStart->format('Y-m-d'), $previousEnd->format('Y-m-d'), $spentAmount)); // if you spent more in previous budget period, than whatever you had previous budget period, the amount resets // previous budget limit + spent $budgetLeft = bcadd($budgetLimit->amount, $spentAmount); $totalAmount = $autoBudget->amount; - app('log')->debug(sprintf('Total amount left for previous budget period is %s', $budgetLeft)); + Log::debug(sprintf('Total amount left for previous budget period is %s', $budgetLeft)); if (-1 !== bccomp('0', $budgetLeft)) { - app('log')->info(sprintf('The amount left is negative, so it will be reset to %s.', $totalAmount)); + Log::info(sprintf('The amount left is negative, so it will be reset to %s.', $totalAmount)); } if (1 !== bccomp('0', $budgetLeft)) { $totalAmount = bcadd($budgetLeft, $totalAmount); - app('log')->info(sprintf('The amount left is positive, so the new amount will be %s.', $totalAmount)); + Log::info(sprintf('The amount left is positive, so the new amount will be %s.', $totalAmount)); } // create budget limit: $this->createBudgetLimit($autoBudget, $start, $end, $totalAmount); - app('log')->debug(sprintf('Done with auto budget #%d', $autoBudget->id)); + Log::debug(sprintf('Done with auto budget #%d', $autoBudget->id)); } private function createAdjustedLimit(AutoBudget $autoBudget): void { - app('log')->debug(sprintf('Will now manage rollover for auto budget #%d', $autoBudget->id)); + Log::debug(sprintf('Will now manage rollover for auto budget #%d', $autoBudget->id)); // current period: $start = app('navigation')->startOfPeriod($this->date, $autoBudget->period); $end = app('navigation')->endOfPeriod($start, $autoBudget->period); @@ -303,7 +304,7 @@ class CreateAutoBudgetLimits implements ShouldQueue $previousStart = app('navigation')->subtractPeriod($start, $autoBudget->period); $previousEnd = app('navigation')->endOfPeriod($previousStart, $autoBudget->period); - app('log')->debug( + Log::debug( sprintf( 'Current period is %s-%s, so previous period is %s-%s', $start->format('Y-m-d'), @@ -317,13 +318,13 @@ class CreateAutoBudgetLimits implements ShouldQueue $budgetLimit = $this->findBudgetLimit($autoBudget->budget, $previousStart, $previousEnd); if (!$budgetLimit instanceof BudgetLimit) { - app('log')->debug('No budget limit exists in previous period, so create one.'); + Log::debug('No budget limit exists in previous period, so create one.'); // if not, create standard amount, and we're done. $this->createBudgetLimit($autoBudget, $start, $end); return; } - app('log')->debug('Budget limit exists for previous period.'); + Log::debug('Budget limit exists for previous period.'); // if has one, calculate expenses and use that as a base. $repository = app(OperationsRepositoryInterface::class); @@ -331,31 +332,31 @@ class CreateAutoBudgetLimits implements ShouldQueue $spent = $repository->sumExpenses($previousStart, $previousEnd, null, new Collection([$autoBudget->budget]), $autoBudget->transactionCurrency); $currencyId = $autoBudget->transaction_currency_id; $spentAmount = $spent[$currencyId]['sum'] ?? '0'; - app('log')->debug(sprintf('Spent in previous budget period (%s-%s) is %s', $previousStart->format('Y-m-d'), $previousEnd->format('Y-m-d'), $spentAmount)); + Log::debug(sprintf('Spent in previous budget period (%s-%s) is %s', $previousStart->format('Y-m-d'), $previousEnd->format('Y-m-d'), $spentAmount)); // what you spent in previous period PLUS the amount for the current period, // if that is more than zero, that's the amount that will be set. $budgetAvailable = bcadd(bcadd($budgetLimit->amount, $autoBudget->amount), $spentAmount); $totalAmount = $autoBudget->amount; - app('log')->debug(sprintf('Total amount available for current budget period is %s', $budgetAvailable)); + Log::debug(sprintf('Total amount available for current budget period is %s', $budgetAvailable)); if (-1 !== bccomp($budgetAvailable, $totalAmount)) { - app('log')->info(sprintf('There is no overspending, no need to adjust. Budget limit amount will be %s.', $budgetAvailable)); + Log::info(sprintf('There is no overspending, no need to adjust. Budget limit amount will be %s.', $budgetAvailable)); // create budget limit: $this->createBudgetLimit($autoBudget, $start, $end, $budgetAvailable); } if (1 !== bccomp($budgetAvailable, $totalAmount) && 1 === bccomp($budgetAvailable, '0')) { - app('log')->info(sprintf('There was overspending, so the new amount will be %s.', $budgetAvailable)); + Log::info(sprintf('There was overspending, so the new amount will be %s.', $budgetAvailable)); // create budget limit: $this->createBudgetLimit($autoBudget, $start, $end, $budgetAvailable); } if (1 !== bccomp($budgetAvailable, $totalAmount) && -1 === bccomp($budgetAvailable, '0')) { - app('log')->info('There was overspending, but so much even this period cant fix that. Reset it to 1.'); + Log::info('There was overspending, but so much even this period cant fix that. Reset it to 1.'); // create budget limit: $this->createBudgetLimit($autoBudget, $start, $end, '1'); } - app('log')->debug(sprintf('Done with auto budget #%d', $autoBudget->id)); + Log::debug(sprintf('Done with auto budget #%d', $autoBudget->id)); } public function setDate(Carbon $date): void diff --git a/changelog.md b/changelog.md index 556ade8e27..dd0e5d9f74 100644 --- a/changelog.md +++ b/changelog.md @@ -3,6 +3,15 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## 6.4.0 - 2025-09-01 + +## Added + +- #10725 + +New webhook triggers + + ## 6.3.2 - 2025-08-20 ### Fixed diff --git a/resources/views/v2/partials/dashboard/account-list.blade.php b/resources/views/v2/partials/dashboard/account-list.blade.php index 6f86903cce..9934551588 100644 --- a/resources/views/v2/partials/dashboard/account-list.blade.php +++ b/resources/views/v2/partials/dashboard/account-list.blade.php @@ -75,7 +75,7 @@
diff --git a/resources/views/v2/partials/elements/amount.blade.php b/resources/views/v2/partials/elements/amount.blade.php index dd756b994c..a1e847f988 100644 --- a/resources/views/v2/partials/elements/amount.blade.php +++ b/resources/views/v2/partials/elements/amount.blade.php @@ -57,20 +57,31 @@ @else @endif From 50d7f9d1ecab5ed20a3dcc51d9468e39c906b8c7 Mon Sep 17 00:00:00 2001 From: JC5 Date: Fri, 22 Aug 2025 07:31:58 +0200 Subject: [PATCH 015/111] =?UTF-8?q?=F0=9F=A4=96=20Auto=20commit=20for=20re?= =?UTF-8?q?lease=20'develop'=20on=202025-08-22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- changelog.md | 2 +- config/firefly.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/changelog.md b/changelog.md index dd0e5d9f74..cdec5f76c0 100644 --- a/changelog.md +++ b/changelog.md @@ -7,7 +7,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## Added -- #10725 +- [Discussion 10725](https://github.com/orgs/firefly-iii/discussions/10725) (New webhook triggers) started by @Billos New webhook triggers diff --git a/config/firefly.php b/config/firefly.php index 8d3fe2ba39..89c2b71302 100644 --- a/config/firefly.php +++ b/config/firefly.php @@ -79,7 +79,7 @@ return [ // see cer.php for exchange rates feature flag. ], 'version' => 'develop/2025-08-22', - 'build_time' => 1755838953, + 'build_time' => 1755840609, 'api_version' => '2.1.0', // field is no longer used. 'db_version' => 26, From 7a1021dffcbf30996d31be25949fabe8d464d690 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 22 Aug 2025 09:15:13 +0200 Subject: [PATCH 016/111] Add spent + earned info to category chart. --- .../Controllers/Chart/CategoryController.php | 60 ++++++++++++------- 1 file changed, 37 insertions(+), 23 deletions(-) diff --git a/app/Api/V1/Controllers/Chart/CategoryController.php b/app/Api/V1/Controllers/Chart/CategoryController.php index 2ed6299222..25803be107 100644 --- a/app/Api/V1/Controllers/Chart/CategoryController.php +++ b/app/Api/V1/Controllers/Chart/CategoryController.php @@ -83,7 +83,7 @@ class CategoryController extends Controller public function overview(SameDateRequest $request): JsonResponse { /** @var Carbon $start */ - $start = $this->parameters->get('start'); + $start = $this->parameters->get('start'); /** @var Carbon $end */ $end = $this->parameters->get('end'); @@ -94,25 +94,26 @@ class CategoryController extends Controller // get journals for entire period: /** @var GroupCollectorInterface $collector */ - $collector = app(GroupCollectorInterface::class); + $collector = app(GroupCollectorInterface::class); $collector->setRange($start, $end)->withAccountInformation(); $collector->setXorAccounts($accounts)->withCategoryInformation(); - $collector->setTypes([TransactionTypeEnum::WITHDRAWAL->value, TransactionTypeEnum::RECONCILIATION->value]); - $journals = $collector->getExtractedJournals(); + $collector->setTypes([TransactionTypeEnum::WITHDRAWAL->value, TransactionTypeEnum::DEPOSIT->value]); + $journals = $collector->getExtractedJournals(); /** @var array $journal */ foreach ($journals as $journal) { // find journal: - $journalCurrencyId = (int)$journal['currency_id']; - $currency = $currencies[$journalCurrencyId] ?? $this->currencyRepos->find($journalCurrencyId); - $currencies[$journalCurrencyId] = $currency; - $currencyId = (int)$currency->id; - $currencyName = (string)$currency->name; - $currencyCode = (string)$currency->code; - $currencySymbol = (string)$currency->symbol; - $currencyDecimalPlaces = (int)$currency->decimal_places; - $amount = Steam::positive($journal['amount']); - $pcAmount = null; + $journalCurrencyId = (int)$journal['currency_id']; + $type = $journal['transaction_type_type']; + $currency = $currencies[$journalCurrencyId] ?? $this->currencyRepos->find($journalCurrencyId); + $currencies[$journalCurrencyId] = $currency; + $currencyId = (int)$currency->id; + $currencyName = (string)$currency->name; + $currencyCode = (string)$currency->code; + $currencySymbol = (string)$currency->symbol; + $currencyDecimalPlaces = (int)$currency->decimal_places; + $amount = Steam::positive((string)$journal['amount']); + $pcAmount = null; // overrule if necessary: if ($this->convertToPrimary && $journalCurrencyId === $this->primaryCurrency->id) { @@ -129,8 +130,8 @@ class CategoryController extends Controller } - $categoryName = $journal['category_name'] ?? (string)trans('firefly.no_category'); - $key = sprintf('%s-%s', $categoryName, $currencyCode); + $categoryName = $journal['category_name'] ?? (string)trans('firefly.no_category'); + $key = sprintf('%s-%s', $categoryName, $currencyCode); // create arrays $return[$key] ??= [ 'label' => $categoryName, @@ -150,23 +151,36 @@ class CategoryController extends Controller 'yAxisID' => 0, 'type' => 'bar', 'entries' => [ - 'spent' => '0', + 'spent' => '0', + 'earned' => '0', ], 'pc_entries' => [ - 'spent' => '0', + 'spent' => '0', + 'earned' => '0', ], ]; // add monies - $return[$key]['entries']['spent'] = bcadd($return[$key]['entries']['spent'], (string)$amount); - if (null !== $pcAmount) { - $return[$key]['pc_entries']['spent'] = bcadd($return[$key]['pc_entries']['spent'], (string)$pcAmount); + // expenses to spent + if (TransactionTypeEnum::WITHDRAWAL->value === $type) { + $return[$key]['entries']['spent'] = bcadd($return[$key]['entries']['spent'], $amount); + if (null !== $pcAmount) { + $return[$key]['pc_entries']['spent'] = bcadd($return[$key]['pc_entries']['spent'], $pcAmount); + } + continue; + } + // positive amount = earned + if (TransactionTypeEnum::DEPOSIT->value === $type) { + $return[$key]['entries']['earned'] = bcadd($return[$key]['entries']['earned'], $amount); + if (null !== $pcAmount) { + $return[$key]['pc_entries']['earned'] = bcadd($return[$key]['pc_entries']['earned'], $pcAmount); + } } } - $return = array_values($return); + $return = array_values($return); // order by amount - usort($return, static fn (array $a, array $b) => (float)$a['entries']['spent'] < (float)$b['entries']['spent'] ? 1 : -1); + usort($return, static fn(array $a, array $b) => ((float)$a['entries']['spent'] + (float)$a['entries']['earned']) < ((float)$b['entries']['spent'] + (float)$b['entries']['earned']) ? 1 : -1); return response()->json($this->clean($return)); } From 8c410f42bdb9465f48dd73120f462e261937d27f Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 22 Aug 2025 09:15:34 +0200 Subject: [PATCH 017/111] Expand changelog. --- changelog.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index cdec5f76c0..16339aae95 100644 --- a/changelog.md +++ b/changelog.md @@ -8,8 +8,10 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## Added - [Discussion 10725](https://github.com/orgs/firefly-iii/discussions/10725) (New webhook triggers) started by @Billos +- #8345 +- New webhook triggers + -New webhook triggers ## 6.3.2 - 2025-08-20 From 80bcfd3bcd7e1272f6c84852d2d6e8873e8e57f9 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 22 Aug 2025 09:52:27 +0200 Subject: [PATCH 018/111] Small issues to fix #5532 --- app/Handlers/Observer/TransactionObserver.php | 2 +- app/Http/Controllers/Account/ShowController.php | 10 ---------- app/Http/Controllers/Chart/AccountController.php | 16 +++++++++++++--- app/Support/Http/Api/ExchangeRateConverter.php | 2 +- app/Support/Steam.php | 3 ++- resources/views/accounts/show.twig | 2 +- 6 files changed, 18 insertions(+), 17 deletions(-) diff --git a/app/Handlers/Observer/TransactionObserver.php b/app/Handlers/Observer/TransactionObserver.php index a5c5fefd73..6c720f6f3f 100644 --- a/app/Handlers/Observer/TransactionObserver.php +++ b/app/Handlers/Observer/TransactionObserver.php @@ -72,7 +72,7 @@ class TransactionObserver } $transaction->saveQuietly(); - Log::debug('Transaction primary currency amounts are updated.'); + Log::debug(sprintf('Transaction #%d primary currency amounts are updated.', $transaction->id)); } public function deleting(?Transaction $transaction): void diff --git a/app/Http/Controllers/Account/ShowController.php b/app/Http/Controllers/Account/ShowController.php index 7171291390..a8b4ed4551 100644 --- a/app/Http/Controllers/Account/ShowController.php +++ b/app/Http/Controllers/Account/ShowController.php @@ -158,18 +158,8 @@ class ShowController extends Controller Log::debug('End collect transactions'); $timer->stop('collection'); - - // enrich data in arrays. - - // enrich - // $enrichment = new TransactionGroupEnrichment(); - // $enrichment->setUser(auth()->user()); - // $groups->setCollection($enrichment->enrich($groups->getCollection())); - - $groups->setPath(route('accounts.show', [$account->id, $start->format('Y-m-d'), $end->format('Y-m-d')])); $showAll = false; - // correct $now = today()->endOfDay(); if ($now->gt($end) || $now->lt($start)) { $now = $end; diff --git a/app/Http/Controllers/Chart/AccountController.php b/app/Http/Controllers/Chart/AccountController.php index 00f387d7c4..447a4013ce 100644 --- a/app/Http/Controllers/Chart/AccountController.php +++ b/app/Http/Controllers/Chart/AccountController.php @@ -37,6 +37,7 @@ use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use FireflyIII\Support\CacheProperties; use FireflyIII\Support\Facades\Preferences; use FireflyIII\Support\Facades\Steam; +use FireflyIII\Support\Http\Api\ExchangeRateConverter; use FireflyIII\Support\Http\Controllers\AugumentData; use FireflyIII\Support\Http\Controllers\ChartGeneration; use FireflyIII\Support\Http\Controllers\DateCalculation; @@ -504,6 +505,7 @@ class AccountController extends Controller Log::debug(sprintf('Step is %s', $step)); $locale = Steam::getLocale(); $return = []; + $converter = new ExchangeRateConverter(); // fix for issue https://github.com/firefly-iii/firefly-iii/issues/8041 // have to make sure this chart is always based on the balance at the END of the period. @@ -512,10 +514,10 @@ class AccountController extends Controller $current = app('navigation')->endOfX($current, $step, null); $format = (string)trans('config.month_and_day_js', [], $locale); $accountCurrency = $this->accountRepository->getAccountCurrency($account); - + Log::debug('Get and filter balance for entire range start'); $range = Steam::finalAccountBalanceInRange($account, $start, $end, $this->convertToPrimary); $range = Steam::filterAccountBalances($range, $account, $this->convertToPrimary, $accountCurrency); - + Log::debug('Get and filter balance for entire range end'); // temp, get end balance. Log::debug(sprintf('period: Call finalAccountBalance with date/time "%s"', $end->toIso8601String())); Steam::finalAccountBalance($account, $end); @@ -552,7 +554,15 @@ class AccountController extends Controller $carbon = Carbon::createFromFormat('Y-m-d', $newRange[$expectedIndex]['date'])->endOfDay(); } } - Log::debug(sprintf('momentBalance is now %s', json_encode($momentBalance))); + Log::debug(sprintf('momentBalance[%s] is now %s', $current->format('Y-m-d H:i:s'), json_encode($momentBalance))); + + // check, perhaps recalculate the amount in currency X if the + if($accountCurrency->id !== $this->primaryCurrency->id && $this->convertToPrimary && array_key_exists($accountCurrency->code, $momentBalance)){ + $converted = $converter->convert($accountCurrency, $this->primaryCurrency, $current, $momentBalance[$accountCurrency->code]); + $momentBalance['pc_balance'] = $converted; + } + + $return = $this->updateChartKeys($return, $momentBalance); $previous = $momentBalance; diff --git a/app/Support/Http/Api/ExchangeRateConverter.php b/app/Support/Http/Api/ExchangeRateConverter.php index 1ff60dbaaa..c783f36dbe 100644 --- a/app/Support/Http/Api/ExchangeRateConverter.php +++ b/app/Support/Http/Api/ExchangeRateConverter.php @@ -103,7 +103,7 @@ class ExchangeRateConverter // find in cache if (null !== $res) { - Log::debug(sprintf('ExchangeRateConverter: Return cached rate from %s to %s on %s.', $from->code, $to->code, $date->format('Y-m-d'))); + Log::debug(sprintf('ExchangeRateConverter: Return cached rate (%s) from %s to %s on %s.', $res, $from->code, $to->code, $date->format('Y-m-d'))); return $res; } diff --git a/app/Support/Steam.php b/app/Support/Steam.php index 0d1cca50fa..039e16c0be 100644 --- a/app/Support/Steam.php +++ b/app/Support/Steam.php @@ -224,6 +224,7 @@ class Steam */ $request = clone $start; $request->subDay()->endOfDay(); + Log::debug('Get first balance to start.'); Log::debug(sprintf('finalAccountBalanceInRange: Call finalAccountBalance with date/time "%s"', $request->toIso8601String())); $startBalance = $this->finalAccountBalance($account, $request); $primaryCurrency = Amount::getPrimaryCurrencyByUserGroup($account->user->userGroup); @@ -315,7 +316,7 @@ class Steam Log::debug(sprintf('Updated entry [%s]', $carbonKey), $currentBalance); } $cache->store($balances); - Log::debug('End of method'); + Log::debug('End of method finalAccountBalanceInRange'); return $balances; } diff --git a/resources/views/accounts/show.twig b/resources/views/accounts/show.twig index e2145c1465..6a79e5fbbc 100644 --- a/resources/views/accounts/show.twig +++ b/resources/views/accounts/show.twig @@ -147,7 +147,7 @@

{{ 'transactions'|_ }} {% if balances.balance %} - ({{ formatAmountBySymbol(balances.balance, currency.symbol, currency.decimal_places, true)|raw }}) + ({{ formatAmountBySymbol(balances.balance, currency.symbol, currency.decimal_places, true)|raw }}) {% elseif balances.pc_balance %} ({{ formatAmountBySymbol(balances.pc_balance, primaryCurrency.symbol, primaryCurrency.decimal_places, true)|raw }}) {% endif %} From 0894d3bf426e628acc4f3f6a32a605b8e72b9d06 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 22 Aug 2025 11:09:17 +0200 Subject: [PATCH 019/111] Fix #10802 --- app/Api/V1/Controllers/Models/BudgetLimit/UpdateController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Api/V1/Controllers/Models/BudgetLimit/UpdateController.php b/app/Api/V1/Controllers/Models/BudgetLimit/UpdateController.php index 159c3c8b49..cd17067b5c 100644 --- a/app/Api/V1/Controllers/Models/BudgetLimit/UpdateController.php +++ b/app/Api/V1/Controllers/Models/BudgetLimit/UpdateController.php @@ -86,7 +86,7 @@ class UpdateController extends Controller $admin = auth()->user(); $enrichment = new BudgetLimitEnrichment(); $enrichment->setUser($admin); - $budgetLimit = $enrichment->enrich($budgetLimit); + $budgetLimit = $enrichment->enrichSingle($budgetLimit); /** @var BudgetLimitTransformer $transformer */ $transformer = app(BudgetLimitTransformer::class); From e8028996081d3e8ef1278b9e705f07e59a283a77 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 22 Aug 2025 11:53:08 +0200 Subject: [PATCH 020/111] Pre-filter expenses, fixes #10803 --- app/Support/JsonApi/Enrichments/BudgetLimitEnrichment.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/Support/JsonApi/Enrichments/BudgetLimitEnrichment.php b/app/Support/JsonApi/Enrichments/BudgetLimitEnrichment.php index 787f3c7844..41dd2a9e64 100644 --- a/app/Support/JsonApi/Enrichments/BudgetLimitEnrichment.php +++ b/app/Support/JsonApi/Enrichments/BudgetLimitEnrichment.php @@ -135,6 +135,7 @@ class BudgetLimitEnrichment implements EnrichmentInterface /** @var BudgetLimit $budgetLimit */ foreach ($this->collection as $budgetLimit) { $id = (int)$budgetLimit->id; + $filteredExpenses = $this->filterToBudget($expenses, $budgetLimit->budget_id); $filteredExpenses = $repository->sumCollectedExpenses($expenses, $budgetLimit->start_date, $budgetLimit->end_date, $budgetLimit->transactionCurrency, false); $this->expenses[$id] = array_values($filteredExpenses); @@ -175,4 +176,11 @@ class BudgetLimitEnrichment implements EnrichmentInterface }, $first); }, $this->expenses); } + + private function filterToBudget(array $expenses, int $budget): array + { + return array_filter($expenses, function (array $item) use ($budget) { + return (int)$item['budget_id'] === $budget; + }); + } } From ddb0e66651148ed3f570af8944186256633439b9 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 22 Aug 2025 15:45:05 +0200 Subject: [PATCH 021/111] Update changelog. --- changelog.md | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/changelog.md b/changelog.md index 16339aae95..33784fd922 100644 --- a/changelog.md +++ b/changelog.md @@ -5,11 +5,40 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## 6.4.0 - 2025-09-01 -## Added +### Added + +- #5532 +- [Discussion 10725](https://github.com/orgs/firefly-iii/discussions/10725) (New webhook triggers) started by @Billos. See the [documentation](https://docs.firefly-iii.org/how-to/firefly-iii/features/webhooks/). + +### Changed + +- Initial release. + +### Deprecated + +- Initial release. + +### Removed + +- Initial release. + +### Fixed + +- #10790 +- #10791 +- #10794 +- #10799 +- #10802 +- #10803 + +### Security + +- Initial release. + +### API -- [Discussion 10725](https://github.com/orgs/firefly-iii/discussions/10725) (New webhook triggers) started by @Billos - #8345 -- New webhook triggers + From b5ad226451629d11119f3017c981c0f24cd399f5 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 22 Aug 2025 15:45:39 +0200 Subject: [PATCH 022/111] Remove unused headers. --- changelog.md | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/changelog.md b/changelog.md index 33784fd922..f67fa57c6b 100644 --- a/changelog.md +++ b/changelog.md @@ -14,14 +14,6 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Initial release. -### Deprecated - -- Initial release. - -### Removed - -- Initial release. - ### Fixed - #10790 @@ -31,10 +23,6 @@ This project adheres to [Semantic Versioning](http://semver.org/). - #10802 - #10803 -### Security - -- Initial release. - ### API - #8345 From 87224565953af0a63114a4c980aa5963c14458e5 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 22 Aug 2025 20:19:52 +0200 Subject: [PATCH 023/111] Fix #10804 --- app/Support/JsonApi/Enrichments/AccountEnrichment.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Support/JsonApi/Enrichments/AccountEnrichment.php b/app/Support/JsonApi/Enrichments/AccountEnrichment.php index c1a162d8fc..a5d6643908 100644 --- a/app/Support/JsonApi/Enrichments/AccountEnrichment.php +++ b/app/Support/JsonApi/Enrichments/AccountEnrichment.php @@ -249,7 +249,7 @@ class AccountEnrichment implements EnrichmentInterface 'opening_balance_date' => null, 'opening_balance_amount' => null, 'account_number' => null, - 'notes' => $notes[$id] ?? null, + 'notes' => $this->notes[$id] ?? null, 'last_activity' => $this->lastActivities[$id] ?? null, ]; From d95dada0e0ab7d401b875ca0a8c1400d5934d441 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 22 Aug 2025 20:20:07 +0200 Subject: [PATCH 024/111] Update changelog for #10804 --- changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.md b/changelog.md index f67fa57c6b..43f7d220bd 100644 --- a/changelog.md +++ b/changelog.md @@ -22,6 +22,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - #10799 - #10802 - #10803 +- #10804 ### API From e90d60113bb59ec47cf2f116b6702dd279d9b3e4 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 23 Aug 2025 08:47:34 +0200 Subject: [PATCH 025/111] Extra validation and a new config variable for #10806 --- app/Api/V1/Requests/Chart/ChartRequest.php | 1 + app/Support/Binder/EitherConfigKey.php | 1 + 2 files changed, 2 insertions(+) diff --git a/app/Api/V1/Requests/Chart/ChartRequest.php b/app/Api/V1/Requests/Chart/ChartRequest.php index 2ef933a1c2..5477aae2e5 100644 --- a/app/Api/V1/Requests/Chart/ChartRequest.php +++ b/app/Api/V1/Requests/Chart/ChartRequest.php @@ -64,6 +64,7 @@ class ChartRequest extends FormRequest 'end' => 'required|date|after:1970-01-02|before:2038-01-17|after_or_equal:start', 'preselected' => sprintf('nullable|in:%s', implode(',', config('firefly.preselected_accounts'))), 'period' => sprintf('nullable|in:%s', implode(',', config('firefly.valid_view_ranges'))), + 'accounts' => 'nullable|array', 'accounts.*' => 'exists:accounts,id', ]; diff --git a/app/Support/Binder/EitherConfigKey.php b/app/Support/Binder/EitherConfigKey.php index e342c6d158..ed41ac53ca 100644 --- a/app/Support/Binder/EitherConfigKey.php +++ b/app/Support/Binder/EitherConfigKey.php @@ -52,6 +52,7 @@ class EitherConfigKey 'firefly.languages', 'app.timezone', 'firefly.valid_view_ranges', + 'firefly.preselected_accounts', // triggers and actions: 'firefly.rule-actions', From bb1b4ca5cac3d2dc5bb88fe507518334a00609b7 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 23 Aug 2025 08:54:50 +0200 Subject: [PATCH 026/111] Fix #10807 --- app/Support/JsonApi/Enrichments/AccountEnrichment.php | 2 +- app/Support/JsonApi/Enrichments/BudgetEnrichment.php | 2 +- app/Support/JsonApi/Enrichments/SubscriptionEnrichment.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/Support/JsonApi/Enrichments/AccountEnrichment.php b/app/Support/JsonApi/Enrichments/AccountEnrichment.php index a5d6643908..80cfe8fc97 100644 --- a/app/Support/JsonApi/Enrichments/AccountEnrichment.php +++ b/app/Support/JsonApi/Enrichments/AccountEnrichment.php @@ -256,7 +256,7 @@ class AccountEnrichment implements EnrichmentInterface // add object group if available if (array_key_exists($id, $this->mappedObjects)) { $key = $this->mappedObjects[$id]; - $meta['object_group_id'] = $this->objectGroups[$key]['id']; + $meta['object_group_id'] = (string) $this->objectGroups[$key]['id']; $meta['object_group_title'] = $this->objectGroups[$key]['title']; $meta['object_group_order'] = $this->objectGroups[$key]['order']; } diff --git a/app/Support/JsonApi/Enrichments/BudgetEnrichment.php b/app/Support/JsonApi/Enrichments/BudgetEnrichment.php index 5605d7f94a..a0d6a7893e 100644 --- a/app/Support/JsonApi/Enrichments/BudgetEnrichment.php +++ b/app/Support/JsonApi/Enrichments/BudgetEnrichment.php @@ -117,7 +117,7 @@ class BudgetEnrichment implements EnrichmentInterface // add object group if available if (array_key_exists($id, $this->mappedObjects)) { $key = $this->mappedObjects[$id]; - $meta['object_group_id'] = $this->objectGroups[$key]['id']; + $meta['object_group_id'] = (string) $this->objectGroups[$key]['id']; $meta['object_group_title'] = $this->objectGroups[$key]['title']; $meta['object_group_order'] = $this->objectGroups[$key]['order']; } diff --git a/app/Support/JsonApi/Enrichments/SubscriptionEnrichment.php b/app/Support/JsonApi/Enrichments/SubscriptionEnrichment.php index 88307e028d..18e195a6f3 100644 --- a/app/Support/JsonApi/Enrichments/SubscriptionEnrichment.php +++ b/app/Support/JsonApi/Enrichments/SubscriptionEnrichment.php @@ -101,7 +101,7 @@ class SubscriptionEnrichment implements EnrichmentInterface // add object group if available if (array_key_exists($id, $this->mappedObjects)) { $key = $this->mappedObjects[$id]; - $meta['object_group_id'] = $objectGroups[$key]['id']; + $meta['object_group_id'] = (string) $objectGroups[$key]['id']; $meta['object_group_title'] = $objectGroups[$key]['title']; $meta['object_group_order'] = $objectGroups[$key]['order']; } From 8ded54d7a88dd451db05a69a99f2b52c6b615189 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 23 Aug 2025 08:56:25 +0200 Subject: [PATCH 027/111] Fix #10808 --- app/Handlers/Events/BillEventHandler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Handlers/Events/BillEventHandler.php b/app/Handlers/Events/BillEventHandler.php index 6a96bf29a7..93b21155ad 100644 --- a/app/Handlers/Events/BillEventHandler.php +++ b/app/Handlers/Events/BillEventHandler.php @@ -111,7 +111,7 @@ class BillEventHandler $bill = $event->bill; /** @var bool $preference */ - Preferences::getForUser($bill->user, 'notification_bill_reminder', true)->data; + $preference = Preferences::getForUser($bill->user, 'notification_bill_reminder', true)->data; if (true === $preference) { Log::debug('Bill reminder is true!'); From 0fdccec6a8cc75f1ea0d0d081e0efa32fb7a055a Mon Sep 17 00:00:00 2001 From: JC5 Date: Sat, 23 Aug 2025 11:14:52 +0200 Subject: [PATCH 028/111] =?UTF-8?q?=F0=9F=A4=96=20Auto=20commit=20for=20re?= =?UTF-8?q?lease=20'develop'=20on=202025-08-23?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/Chart/CategoryController.php | 15 +- app/Api/V1/Requests/Chart/ChartRequest.php | 2 +- app/Handlers/Events/BillEventHandler.php | 2 +- .../Controllers/Chart/AccountController.php | 6 +- .../Enrichments/BudgetLimitEnrichment.php | 2 +- changelog.md | 18 +- composer.lock | 28 +-- config/firefly.php | 4 +- package-lock.json | 182 +++++++++--------- resources/assets/v1/src/locales/de.json | 4 +- resources/assets/v1/src/locales/fr.json | 4 +- resources/assets/v1/src/locales/sl.json | 24 +-- 12 files changed, 148 insertions(+), 143 deletions(-) diff --git a/app/Api/V1/Controllers/Chart/CategoryController.php b/app/Api/V1/Controllers/Chart/CategoryController.php index 25803be107..9626c447a5 100644 --- a/app/Api/V1/Controllers/Chart/CategoryController.php +++ b/app/Api/V1/Controllers/Chart/CategoryController.php @@ -83,7 +83,7 @@ class CategoryController extends Controller public function overview(SameDateRequest $request): JsonResponse { /** @var Carbon $start */ - $start = $this->parameters->get('start'); + $start = $this->parameters->get('start'); /** @var Carbon $end */ $end = $this->parameters->get('end'); @@ -94,11 +94,11 @@ class CategoryController extends Controller // get journals for entire period: /** @var GroupCollectorInterface $collector */ - $collector = app(GroupCollectorInterface::class); + $collector = app(GroupCollectorInterface::class); $collector->setRange($start, $end)->withAccountInformation(); $collector->setXorAccounts($accounts)->withCategoryInformation(); $collector->setTypes([TransactionTypeEnum::WITHDRAWAL->value, TransactionTypeEnum::DEPOSIT->value]); - $journals = $collector->getExtractedJournals(); + $journals = $collector->getExtractedJournals(); /** @var array $journal */ foreach ($journals as $journal) { @@ -130,8 +130,8 @@ class CategoryController extends Controller } - $categoryName = $journal['category_name'] ?? (string)trans('firefly.no_category'); - $key = sprintf('%s-%s', $categoryName, $currencyCode); + $categoryName = $journal['category_name'] ?? (string)trans('firefly.no_category'); + $key = sprintf('%s-%s', $categoryName, $currencyCode); // create arrays $return[$key] ??= [ 'label' => $categoryName, @@ -167,6 +167,7 @@ class CategoryController extends Controller if (null !== $pcAmount) { $return[$key]['pc_entries']['spent'] = bcadd($return[$key]['pc_entries']['spent'], $pcAmount); } + continue; } // positive amount = earned @@ -177,10 +178,10 @@ class CategoryController extends Controller } } } - $return = array_values($return); + $return = array_values($return); // order by amount - usort($return, static fn(array $a, array $b) => ((float)$a['entries']['spent'] + (float)$a['entries']['earned']) < ((float)$b['entries']['spent'] + (float)$b['entries']['earned']) ? 1 : -1); + usort($return, static fn (array $a, array $b) => ((float)$a['entries']['spent'] + (float)$a['entries']['earned']) < ((float)$b['entries']['spent'] + (float)$b['entries']['earned']) ? 1 : -1); return response()->json($this->clean($return)); } diff --git a/app/Api/V1/Requests/Chart/ChartRequest.php b/app/Api/V1/Requests/Chart/ChartRequest.php index 5477aae2e5..83b666943d 100644 --- a/app/Api/V1/Requests/Chart/ChartRequest.php +++ b/app/Api/V1/Requests/Chart/ChartRequest.php @@ -64,7 +64,7 @@ class ChartRequest extends FormRequest 'end' => 'required|date|after:1970-01-02|before:2038-01-17|after_or_equal:start', 'preselected' => sprintf('nullable|in:%s', implode(',', config('firefly.preselected_accounts'))), 'period' => sprintf('nullable|in:%s', implode(',', config('firefly.valid_view_ranges'))), - 'accounts' => 'nullable|array', + 'accounts' => 'nullable|array', 'accounts.*' => 'exists:accounts,id', ]; diff --git a/app/Handlers/Events/BillEventHandler.php b/app/Handlers/Events/BillEventHandler.php index 93b21155ad..9bdb7bc1d4 100644 --- a/app/Handlers/Events/BillEventHandler.php +++ b/app/Handlers/Events/BillEventHandler.php @@ -108,7 +108,7 @@ class BillEventHandler { Log::debug(sprintf('Now in %s', __METHOD__)); - $bill = $event->bill; + $bill = $event->bill; /** @var bool $preference */ $preference = Preferences::getForUser($bill->user, 'notification_bill_reminder', true)->data; diff --git a/app/Http/Controllers/Chart/AccountController.php b/app/Http/Controllers/Chart/AccountController.php index 447a4013ce..cf5cec649a 100644 --- a/app/Http/Controllers/Chart/AccountController.php +++ b/app/Http/Controllers/Chart/AccountController.php @@ -505,7 +505,7 @@ class AccountController extends Controller Log::debug(sprintf('Step is %s', $step)); $locale = Steam::getLocale(); $return = []; - $converter = new ExchangeRateConverter(); + $converter = new ExchangeRateConverter(); // fix for issue https://github.com/firefly-iii/firefly-iii/issues/8041 // have to make sure this chart is always based on the balance at the END of the period. @@ -557,8 +557,8 @@ class AccountController extends Controller Log::debug(sprintf('momentBalance[%s] is now %s', $current->format('Y-m-d H:i:s'), json_encode($momentBalance))); // check, perhaps recalculate the amount in currency X if the - if($accountCurrency->id !== $this->primaryCurrency->id && $this->convertToPrimary && array_key_exists($accountCurrency->code, $momentBalance)){ - $converted = $converter->convert($accountCurrency, $this->primaryCurrency, $current, $momentBalance[$accountCurrency->code]); + if ($accountCurrency->id !== $this->primaryCurrency->id && $this->convertToPrimary && array_key_exists($accountCurrency->code, $momentBalance)) { + $converted = $converter->convert($accountCurrency, $this->primaryCurrency, $current, $momentBalance[$accountCurrency->code]); $momentBalance['pc_balance'] = $converted; } diff --git a/app/Support/JsonApi/Enrichments/BudgetLimitEnrichment.php b/app/Support/JsonApi/Enrichments/BudgetLimitEnrichment.php index 41dd2a9e64..d2cb02f993 100644 --- a/app/Support/JsonApi/Enrichments/BudgetLimitEnrichment.php +++ b/app/Support/JsonApi/Enrichments/BudgetLimitEnrichment.php @@ -135,7 +135,7 @@ class BudgetLimitEnrichment implements EnrichmentInterface /** @var BudgetLimit $budgetLimit */ foreach ($this->collection as $budgetLimit) { $id = (int)$budgetLimit->id; - $filteredExpenses = $this->filterToBudget($expenses, $budgetLimit->budget_id); + $filteredExpenses = $this->filterToBudget($expenses, $budgetLimit->budget_id); $filteredExpenses = $repository->sumCollectedExpenses($expenses, $budgetLimit->start_date, $budgetLimit->end_date, $budgetLimit->transactionCurrency, false); $this->expenses[$id] = array_values($filteredExpenses); diff --git a/changelog.md b/changelog.md index 43f7d220bd..7892f43d8c 100644 --- a/changelog.md +++ b/changelog.md @@ -7,7 +7,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Added -- #5532 +- [Issue 5532](https://github.com/firefly-iii/firefly-iii/issues/5532) (Asset prices and exchange rates) reported by @svozniuk - [Discussion 10725](https://github.com/orgs/firefly-iii/discussions/10725) (New webhook triggers) started by @Billos. See the [documentation](https://docs.firefly-iii.org/how-to/firefly-iii/features/webhooks/). ### Changed @@ -16,17 +16,17 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Fixed -- #10790 -- #10791 -- #10794 -- #10799 -- #10802 -- #10803 -- #10804 +- [Issue 10790](https://github.com/firefly-iii/firefly-iii/issues/10790) (Undefined variable $occurrences) reported by @senna1992 +- [Issue 10791](https://github.com/firefly-iii/firefly-iii/issues/10791) (Clone and edit a transaction with a different currency doesn't clear the foreign transaction amount) reported by @jxtxzzw +- [Issue 10794](https://github.com/firefly-iii/firefly-iii/issues/10794) (Error with recurring transaction) reported by @MaximSN +- [Issue 10799](https://github.com/firefly-iii/firefly-iii/issues/10799) (Budget - "Left (per day)" not showing the correct value) reported by @GensHaze +- [Issue 10802](https://github.com/firefly-iii/firefly-iii/issues/10802) (Crash when trying to update a budget limit) reported by @Billos +- [Issue 10803](https://github.com/firefly-iii/firefly-iii/issues/10803) (Issue in /v1/budget-limits spent attribute) reported by @Billos +- [Issue 10804](https://github.com/firefly-iii/firefly-iii/issues/10804) (No notes information included in the "List all accounts" API call) reported by @gpampuro ### API -- #8345 +- [Issue 8345](https://github.com/firefly-iii/firefly-iii/issues/8345) (API: Distinguish spent & earned at `/v2/chart/category/dashboard` (or future `v2/categories`)) reported by @dreautall diff --git a/composer.lock b/composer.lock index dc2af06834..5f19b84d18 100644 --- a/composer.lock +++ b/composer.lock @@ -1370,16 +1370,16 @@ }, { "name": "guzzlehttp/promises", - "version": "2.2.0", + "version": "2.3.0", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "7c69f28996b0a6920945dd20b3857e499d9ca96c" + "reference": "481557b130ef3790cf82b713667b43030dc9c957" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/7c69f28996b0a6920945dd20b3857e499d9ca96c", - "reference": "7c69f28996b0a6920945dd20b3857e499d9ca96c", + "url": "https://api.github.com/repos/guzzle/promises/zipball/481557b130ef3790cf82b713667b43030dc9c957", + "reference": "481557b130ef3790cf82b713667b43030dc9c957", "shasum": "" }, "require": { @@ -1387,7 +1387,7 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.39 || ^9.6.20" + "phpunit/phpunit": "^8.5.44 || ^9.6.25" }, "type": "library", "extra": { @@ -1433,7 +1433,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/2.2.0" + "source": "https://github.com/guzzle/promises/tree/2.3.0" }, "funding": [ { @@ -1449,7 +1449,7 @@ "type": "tidelift" } ], - "time": "2025-03-27T13:27:01+00:00" + "time": "2025-08-22T14:34:08+00:00" }, { "name": "guzzlehttp/psr7", @@ -1569,16 +1569,16 @@ }, { "name": "guzzlehttp/uri-template", - "version": "v1.0.4", + "version": "v1.0.5", "source": { "type": "git", "url": "https://github.com/guzzle/uri-template.git", - "reference": "30e286560c137526eccd4ce21b2de477ab0676d2" + "reference": "4f4bbd4e7172148801e76e3decc1e559bdee34e1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/uri-template/zipball/30e286560c137526eccd4ce21b2de477ab0676d2", - "reference": "30e286560c137526eccd4ce21b2de477ab0676d2", + "url": "https://api.github.com/repos/guzzle/uri-template/zipball/4f4bbd4e7172148801e76e3decc1e559bdee34e1", + "reference": "4f4bbd4e7172148801e76e3decc1e559bdee34e1", "shasum": "" }, "require": { @@ -1587,7 +1587,7 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.36 || ^9.6.15", + "phpunit/phpunit": "^8.5.44 || ^9.6.25", "uri-template/tests": "1.0.0" }, "type": "library", @@ -1635,7 +1635,7 @@ ], "support": { "issues": "https://github.com/guzzle/uri-template/issues", - "source": "https://github.com/guzzle/uri-template/tree/v1.0.4" + "source": "https://github.com/guzzle/uri-template/tree/v1.0.5" }, "funding": [ { @@ -1651,7 +1651,7 @@ "type": "tidelift" } ], - "time": "2025-02-03T10:55:03+00:00" + "time": "2025-08-22T14:27:06+00:00" }, { "name": "jc5/google2fa-laravel", diff --git a/config/firefly.php b/config/firefly.php index 89c2b71302..25545845fa 100644 --- a/config/firefly.php +++ b/config/firefly.php @@ -78,8 +78,8 @@ return [ 'running_balance_column' => env('USE_RUNNING_BALANCE', false), // see cer.php for exchange rates feature flag. ], - 'version' => 'develop/2025-08-22', - 'build_time' => 1755840609, + 'version' => 'develop/2025-08-23', + 'build_time' => 1755940389, 'api_version' => '2.1.0', // field is no longer used. 'db_version' => 26, diff --git a/package-lock.json b/package-lock.json index e1b2916c61..d98d61c5a8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2592,9 +2592,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.47.1.tgz", - "integrity": "sha512-lTahKRJip0knffA/GTNFJMrToD+CM+JJ+Qt5kjzBK/sFQ0EWqfKW3AYQSlZXN98tX0lx66083U9JYIMioMMK7g==", + "version": "4.48.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.48.0.tgz", + "integrity": "sha512-aVzKH922ogVAWkKiyKXorjYymz2084zrhrZRXtLrA5eEx5SO8Dj0c/4FpCHZyn7MKzhW2pW4tK28vVr+5oQ2xw==", "cpu": [ "arm" ], @@ -2606,9 +2606,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.47.1.tgz", - "integrity": "sha512-uqxkb3RJLzlBbh/bbNQ4r7YpSZnjgMgyoEOY7Fy6GCbelkDSAzeiogxMG9TfLsBbqmGsdDObo3mzGqa8hps4MA==", + "version": "4.48.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.48.0.tgz", + "integrity": "sha512-diOdQuw43xTa1RddAFbhIA8toirSzFMcnIg8kvlzRbK26xqEnKJ/vqQnghTAajy2Dcy42v+GMPMo6jq67od+Dw==", "cpu": [ "arm64" ], @@ -2620,9 +2620,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.47.1.tgz", - "integrity": "sha512-tV6reObmxBDS4DDyLzTDIpymthNlxrLBGAoQx6m2a7eifSNEZdkXQl1PE4ZjCkEDPVgNXSzND/k9AQ3mC4IOEQ==", + "version": "4.48.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.48.0.tgz", + "integrity": "sha512-QhR2KA18fPlJWFefySJPDYZELaVqIUVnYgAOdtJ+B/uH96CFg2l1TQpX19XpUMWUqMyIiyY45wje8K6F4w4/CA==", "cpu": [ "arm64" ], @@ -2634,9 +2634,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.47.1.tgz", - "integrity": "sha512-XuJRPTnMk1lwsSnS3vYyVMu4x/+WIw1MMSiqj5C4j3QOWsMzbJEK90zG+SWV1h0B1ABGCQ0UZUjti+TQK35uHQ==", + "version": "4.48.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.48.0.tgz", + "integrity": "sha512-Q9RMXnQVJ5S1SYpNSTwXDpoQLgJ/fbInWOyjbCnnqTElEyeNvLAB3QvG5xmMQMhFN74bB5ZZJYkKaFPcOG8sGg==", "cpu": [ "x64" ], @@ -2648,9 +2648,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.47.1.tgz", - "integrity": "sha512-79BAm8Ag/tmJ5asCqgOXsb3WY28Rdd5Lxj8ONiQzWzy9LvWORd5qVuOnjlqiWWZJw+dWewEktZb5yiM1DLLaHw==", + "version": "4.48.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.48.0.tgz", + "integrity": "sha512-3jzOhHWM8O8PSfyft+ghXZfBkZawQA0PUGtadKYxFqpcYlOYjTi06WsnYBsbMHLawr+4uWirLlbhcYLHDXR16w==", "cpu": [ "arm64" ], @@ -2662,9 +2662,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.47.1.tgz", - "integrity": "sha512-OQ2/ZDGzdOOlyfqBiip0ZX/jVFekzYrGtUsqAfLDbWy0jh1PUU18+jYp8UMpqhly5ltEqotc2miLngf9FPSWIA==", + "version": "4.48.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.48.0.tgz", + "integrity": "sha512-NcD5uVUmE73C/TPJqf78hInZmiSBsDpz3iD5MF/BuB+qzm4ooF2S1HfeTChj5K4AV3y19FFPgxonsxiEpy8v/A==", "cpu": [ "x64" ], @@ -2676,9 +2676,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.47.1.tgz", - "integrity": "sha512-HZZBXJL1udxlCVvoVadstgiU26seKkHbbAMLg7680gAcMnRNP9SAwTMVet02ANA94kXEI2VhBnXs4e5nf7KG2A==", + "version": "4.48.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.48.0.tgz", + "integrity": "sha512-JWnrj8qZgLWRNHr7NbpdnrQ8kcg09EBBq8jVOjmtlB3c8C6IrynAJSMhMVGME4YfTJzIkJqvSUSVJRqkDnu/aA==", "cpu": [ "arm" ], @@ -2690,9 +2690,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.47.1.tgz", - "integrity": "sha512-sZ5p2I9UA7T950JmuZ3pgdKA6+RTBr+0FpK427ExW0t7n+QwYOcmDTK/aRlzoBrWyTpJNlS3kacgSlSTUg6P/Q==", + "version": "4.48.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.48.0.tgz", + "integrity": "sha512-9xu92F0TxuMH0tD6tG3+GtngwdgSf8Bnz+YcsPG91/r5Vgh5LNofO48jV55priA95p3c92FLmPM7CvsVlnSbGQ==", "cpu": [ "arm" ], @@ -2704,9 +2704,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.47.1.tgz", - "integrity": "sha512-3hBFoqPyU89Dyf1mQRXCdpc6qC6At3LV6jbbIOZd72jcx7xNk3aAp+EjzAtN6sDlmHFzsDJN5yeUySvorWeRXA==", + "version": "4.48.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.48.0.tgz", + "integrity": "sha512-NLtvJB5YpWn7jlp1rJiY0s+G1Z1IVmkDuiywiqUhh96MIraC0n7XQc2SZ1CZz14shqkM+XN2UrfIo7JB6UufOA==", "cpu": [ "arm64" ], @@ -2718,9 +2718,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.47.1.tgz", - "integrity": "sha512-49J4FnMHfGodJWPw73Ve+/hsPjZgcXQGkmqBGZFvltzBKRS+cvMiWNLadOMXKGnYRhs1ToTGM0sItKISoSGUNA==", + "version": "4.48.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.48.0.tgz", + "integrity": "sha512-QJ4hCOnz2SXgCh+HmpvZkM+0NSGcZACyYS8DGbWn2PbmA0e5xUk4bIP8eqJyNXLtyB4gZ3/XyvKtQ1IFH671vQ==", "cpu": [ "arm64" ], @@ -2732,9 +2732,9 @@ ] }, "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.47.1.tgz", - "integrity": "sha512-4yYU8p7AneEpQkRX03pbpLmE21z5JNys16F1BZBZg5fP9rIlb0TkeQjn5du5w4agConCCEoYIG57sNxjryHEGg==", + "version": "4.48.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.48.0.tgz", + "integrity": "sha512-Pk0qlGJnhILdIC5zSKQnprFjrGmjfDM7TPZ0FKJxRkoo+kgMRAg4ps1VlTZf8u2vohSicLg7NP+cA5qE96PaFg==", "cpu": [ "loong64" ], @@ -2746,9 +2746,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.47.1.tgz", - "integrity": "sha512-fAiq+J28l2YMWgC39jz/zPi2jqc0y3GSRo1yyxlBHt6UN0yYgnegHSRPa3pnHS5amT/efXQrm0ug5+aNEu9UuQ==", + "version": "4.48.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.48.0.tgz", + "integrity": "sha512-/dNFc6rTpoOzgp5GKoYjT6uLo8okR/Chi2ECOmCZiS4oqh3mc95pThWma7Bgyk6/WTEvjDINpiBCuecPLOgBLQ==", "cpu": [ "ppc64" ], @@ -2760,9 +2760,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.47.1.tgz", - "integrity": "sha512-daoT0PMENNdjVYYU9xec30Y2prb1AbEIbb64sqkcQcSaR0zYuKkoPuhIztfxuqN82KYCKKrj+tQe4Gi7OSm1ow==", + "version": "4.48.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.48.0.tgz", + "integrity": "sha512-YBwXsvsFI8CVA4ej+bJF2d9uAeIiSkqKSPQNn0Wyh4eMDY4wxuSp71BauPjQNCKK2tD2/ksJ7uhJ8X/PVY9bHQ==", "cpu": [ "riscv64" ], @@ -2774,9 +2774,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.47.1.tgz", - "integrity": "sha512-JNyXaAhWtdzfXu5pUcHAuNwGQKevR+6z/poYQKVW+pLaYOj9G1meYc57/1Xv2u4uTxfu9qEWmNTjv/H/EpAisw==", + "version": "4.48.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.48.0.tgz", + "integrity": "sha512-FI3Rr2aGAtl1aHzbkBIamsQyuauYtTF9SDUJ8n2wMXuuxwchC3QkumZa1TEXYIv/1AUp1a25Kwy6ONArvnyeVQ==", "cpu": [ "riscv64" ], @@ -2788,9 +2788,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.47.1.tgz", - "integrity": "sha512-U/CHbqKSwEQyZXjCpY43/GLYcTVKEXeRHw0rMBJP7fP3x6WpYG4LTJWR3ic6TeYKX6ZK7mrhltP4ppolyVhLVQ==", + "version": "4.48.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.48.0.tgz", + "integrity": "sha512-Dx7qH0/rvNNFmCcIRe1pyQ9/H0XO4v/f0SDoafwRYwc2J7bJZ5N4CHL/cdjamISZ5Cgnon6iazAVRFlxSoHQnQ==", "cpu": [ "s390x" ], @@ -2802,9 +2802,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.47.1.tgz", - "integrity": "sha512-uTLEakjxOTElfeZIGWkC34u2auLHB1AYS6wBjPGI00bWdxdLcCzK5awjs25YXpqB9lS8S0vbO0t9ZcBeNibA7g==", + "version": "4.48.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.48.0.tgz", + "integrity": "sha512-GUdZKTeKBq9WmEBzvFYuC88yk26vT66lQV8D5+9TgkfbewhLaTHRNATyzpQwwbHIfJvDJ3N9WJ90wK/uR3cy3Q==", "cpu": [ "x64" ], @@ -2816,9 +2816,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.47.1.tgz", - "integrity": "sha512-Ft+d/9DXs30BK7CHCTX11FtQGHUdpNDLJW0HHLign4lgMgBcPFN3NkdIXhC5r9iwsMwYreBBc4Rho5ieOmKNVQ==", + "version": "4.48.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.48.0.tgz", + "integrity": "sha512-ao58Adz/v14MWpQgYAb4a4h3fdw73DrDGtaiF7Opds5wNyEQwtO6M9dBh89nke0yoZzzaegq6J/EXs7eBebG8A==", "cpu": [ "x64" ], @@ -2830,9 +2830,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.47.1.tgz", - "integrity": "sha512-N9X5WqGYzZnjGAFsKSfYFtAShYjwOmFJoWbLg3dYixZOZqU7hdMq+/xyS14zKLhFhZDhP9VfkzQnsdk0ZDS9IA==", + "version": "4.48.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.48.0.tgz", + "integrity": "sha512-kpFno46bHtjZVdRIOxqaGeiABiToo2J+st7Yce+aiAoo1H0xPi2keyQIP04n2JjDVuxBN6bSz9R6RdTK5hIppw==", "cpu": [ "arm64" ], @@ -2844,9 +2844,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.47.1.tgz", - "integrity": "sha512-O+KcfeCORZADEY8oQJk4HK8wtEOCRE4MdOkb8qGZQNun3jzmj2nmhV/B/ZaaZOkPmJyvm/gW9n0gsB4eRa1eiQ==", + "version": "4.48.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.48.0.tgz", + "integrity": "sha512-rFYrk4lLk9YUTIeihnQMiwMr6gDhGGSbWThPEDfBoU/HdAtOzPXeexKi7yU8jO+LWRKnmqPN9NviHQf6GDwBcQ==", "cpu": [ "ia32" ], @@ -2858,9 +2858,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.47.1.tgz", - "integrity": "sha512-CpKnYa8eHthJa3c+C38v/E+/KZyF1Jdh2Cz3DyKZqEWYgrM1IHFArXNWvBLPQCKUEsAqqKX27tTqVEFbDNUcOA==", + "version": "4.48.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.48.0.tgz", + "integrity": "sha512-sq0hHLTgdtwOPDB5SJOuaoHyiP1qSwg+71TQWk8iDS04bW1wIE0oQ6otPiRj2ZvLYNASLMaTp8QRGUVZ+5OL5A==", "cpu": [ "x64" ], @@ -4486,9 +4486,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001736", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001736.tgz", - "integrity": "sha512-ImpN5gLEY8gWeqfLUyEF4b7mYWcYoR2Si1VhnrbM4JizRFmfGaAQ12PhNykq6nvI4XvKLrsp8Xde74D5phJOSw==", + "version": "1.0.30001737", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001737.tgz", + "integrity": "sha512-BiloLiXtQNrY5UyF0+1nSJLXUENuhka2pzy2Fx5pGxqavdrxSCW4U6Pn/PoG3Efspi2frRbHpBV2XsrPE6EDlw==", "dev": true, "funding": [ { @@ -10123,9 +10123,9 @@ } }, "node_modules/rollup": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.47.1.tgz", - "integrity": "sha512-iasGAQoZ5dWDzULEUX3jiW0oB1qyFOepSyDyoU6S/OhVlDIwj5knI5QBa5RRQ0sK7OE0v+8VIi2JuV+G+3tfNg==", + "version": "4.48.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.48.0.tgz", + "integrity": "sha512-BXHRqK1vyt9XVSEHZ9y7xdYtuYbwVod2mLwOMFP7t/Eqoc1pHRlG/WdV2qNeNvZHRQdLedaFycljaYYM96RqJQ==", "dev": true, "license": "MIT", "dependencies": { @@ -10139,26 +10139,26 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.47.1", - "@rollup/rollup-android-arm64": "4.47.1", - "@rollup/rollup-darwin-arm64": "4.47.1", - "@rollup/rollup-darwin-x64": "4.47.1", - "@rollup/rollup-freebsd-arm64": "4.47.1", - "@rollup/rollup-freebsd-x64": "4.47.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.47.1", - "@rollup/rollup-linux-arm-musleabihf": "4.47.1", - "@rollup/rollup-linux-arm64-gnu": "4.47.1", - "@rollup/rollup-linux-arm64-musl": "4.47.1", - "@rollup/rollup-linux-loongarch64-gnu": "4.47.1", - "@rollup/rollup-linux-ppc64-gnu": "4.47.1", - "@rollup/rollup-linux-riscv64-gnu": "4.47.1", - "@rollup/rollup-linux-riscv64-musl": "4.47.1", - "@rollup/rollup-linux-s390x-gnu": "4.47.1", - "@rollup/rollup-linux-x64-gnu": "4.47.1", - "@rollup/rollup-linux-x64-musl": "4.47.1", - "@rollup/rollup-win32-arm64-msvc": "4.47.1", - "@rollup/rollup-win32-ia32-msvc": "4.47.1", - "@rollup/rollup-win32-x64-msvc": "4.47.1", + "@rollup/rollup-android-arm-eabi": "4.48.0", + "@rollup/rollup-android-arm64": "4.48.0", + "@rollup/rollup-darwin-arm64": "4.48.0", + "@rollup/rollup-darwin-x64": "4.48.0", + "@rollup/rollup-freebsd-arm64": "4.48.0", + "@rollup/rollup-freebsd-x64": "4.48.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.48.0", + "@rollup/rollup-linux-arm-musleabihf": "4.48.0", + "@rollup/rollup-linux-arm64-gnu": "4.48.0", + "@rollup/rollup-linux-arm64-musl": "4.48.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.48.0", + "@rollup/rollup-linux-ppc64-gnu": "4.48.0", + "@rollup/rollup-linux-riscv64-gnu": "4.48.0", + "@rollup/rollup-linux-riscv64-musl": "4.48.0", + "@rollup/rollup-linux-s390x-gnu": "4.48.0", + "@rollup/rollup-linux-x64-gnu": "4.48.0", + "@rollup/rollup-linux-x64-musl": "4.48.0", + "@rollup/rollup-win32-arm64-msvc": "4.48.0", + "@rollup/rollup-win32-ia32-msvc": "4.48.0", + "@rollup/rollup-win32-x64-msvc": "4.48.0", "fsevents": "~2.3.2" } }, @@ -11005,13 +11005,17 @@ } }, "node_modules/tapable": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz", - "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.3.tgz", + "integrity": "sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg==", "dev": true, "license": "MIT", "engines": { "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, "node_modules/terser": { diff --git a/resources/assets/v1/src/locales/de.json b/resources/assets/v1/src/locales/de.json index 53524b4e92..01ba8e3079 100644 --- a/resources/assets/v1/src/locales/de.json +++ b/resources/assets/v1/src/locales/de.json @@ -107,7 +107,7 @@ "multi_account_warning_withdrawal": "Bedenken Sie, dass das Quellkonto nachfolgender Aufteilungen von dem, was in der ersten Aufteilung der Abhebung definiert ist, au\u00dfer Kraft gesetzt wird.", "multi_account_warning_deposit": "Bedenken Sie, dass das Zielkonto nachfolgender Aufteilungen von dem, was in der ersten Aufteilung der Einnahmen definiert ist, au\u00dfer Kraft gesetzt wird.", "multi_account_warning_transfer": "Bedenken Sie, dass das Quell- und Zielkonto nachfolgender Aufteilungen durch das, was in der ersten Aufteilung der \u00dcbertragung definiert ist, au\u00dfer Kraft gesetzt wird.", - "webhook_trigger_ANY": "After any event", + "webhook_trigger_ANY": "Nach jedem Ereignis", "webhook_trigger_STORE_TRANSACTION": "Nach Erstellen einer Buchung", "webhook_trigger_UPDATE_TRANSACTION": "Nach Aktualisierung einer Buchung", "webhook_trigger_DESTROY_TRANSACTION": "Nach dem L\u00f6schen einer Buchung", @@ -116,7 +116,7 @@ "webhook_trigger_DESTROY_BUDGET": "Nach dem L\u00f6schen des Budgets", "webhook_trigger_STORE_UPDATE_BUDGET_LIMIT": "Nach dem \u00c4ndern des budgetierten Betrags", "webhook_response_TRANSACTIONS": "Buchungsdetails", - "webhook_response_RELEVANT": "Relevant details", + "webhook_response_RELEVANT": "Relevante Details", "webhook_response_ACCOUNTS": "Kontodetails", "webhook_response_NONE": "Keine Details", "webhook_delivery_JSON": "JSON", diff --git a/resources/assets/v1/src/locales/fr.json b/resources/assets/v1/src/locales/fr.json index 06ff6c4f09..f0d13bf76d 100644 --- a/resources/assets/v1/src/locales/fr.json +++ b/resources/assets/v1/src/locales/fr.json @@ -107,7 +107,7 @@ "multi_account_warning_withdrawal": "Gardez en t\u00eate que le compte source des s\u00e9parations suivantes peut \u00eatre remplac\u00e9 par celui de la premi\u00e8re s\u00e9paration de la d\u00e9pense.", "multi_account_warning_deposit": "Gardez en t\u00eate que le compte de destination des s\u00e9parations suivantes peut \u00eatre remplac\u00e9 par celui de la premi\u00e8re s\u00e9paration du d\u00e9p\u00f4t.", "multi_account_warning_transfer": "Gardez en t\u00eate que les comptes source et de destination des s\u00e9parations suivantes peuvent \u00eatre remplac\u00e9s par ceux de la premi\u00e8re s\u00e9paration du transfert.", - "webhook_trigger_ANY": "After any event", + "webhook_trigger_ANY": "Apr\u00e8s n'importe quel \u00e9v\u00e9nement", "webhook_trigger_STORE_TRANSACTION": "Apr\u00e8s la cr\u00e9ation de l'op\u00e9ration", "webhook_trigger_UPDATE_TRANSACTION": "Apr\u00e8s la mise \u00e0 jour de l'op\u00e9ration", "webhook_trigger_DESTROY_TRANSACTION": "Apr\u00e8s la suppression de l'op\u00e9ration", @@ -116,7 +116,7 @@ "webhook_trigger_DESTROY_BUDGET": "Apr\u00e8s la suppression du budget", "webhook_trigger_STORE_UPDATE_BUDGET_LIMIT": "Apr\u00e8s le changement du montant budg\u00e9tis\u00e9", "webhook_response_TRANSACTIONS": "D\u00e9tails de l'op\u00e9ration", - "webhook_response_RELEVANT": "Relevant details", + "webhook_response_RELEVANT": "D\u00e9tails pertinents", "webhook_response_ACCOUNTS": "D\u00e9tails du compte", "webhook_response_NONE": "Aucun d\u00e9tail", "webhook_delivery_JSON": "JSON", diff --git a/resources/assets/v1/src/locales/sl.json b/resources/assets/v1/src/locales/sl.json index dcd7f3d5b8..369fd0ea6e 100644 --- a/resources/assets/v1/src/locales/sl.json +++ b/resources/assets/v1/src/locales/sl.json @@ -3,8 +3,8 @@ "administrations_page_title": "Finan\u010dne administracije", "administrations_index_menu": "Finan\u010dne administracije", "expires_at": "Pote\u010de ob", - "temp_administrations_introduction": "Firefly III will soon get the ability to manage multiple financial administrations. Right now, you only have the one. You can set the title of this administration and its primary currency. This replaces the previous setting where you would set your \"default currency\". This setting is now tied to the financial administration and can be different per administration.", - "administration_currency_form_help": "It may take a long time for the page to load if you change the primary currency because transaction may need to be converted to your (new) primary currency.", + "temp_administrations_introduction": "Firefly III bo kmalu dobil mo\u017enost upravljanja ve\u010d finan\u010dnih administracij. Trenutno imate samo eno. Nastavite lahko naziv te administracije in njeno glavno valuto. To nadome\u0161\u010da prej\u0161njo nastavitev, kjer bi nastavili svojo \"privzeto valuto\". Ta nastavitev je zdaj vezana na finan\u010dno administracijo in se lahko razlikuje glede na administracijo.", + "administration_currency_form_help": "\u010ce spremenite doma\u010do valuto, lahko traja dolgo \u010dasa, da se stran nalo\u017ei, ker bo transakcijo morda treba pretvoriti v va\u0161o (novo) doma\u010do valuto.", "administrations_page_edit_sub_title_js": "Uredi finan\u010dno administracijo \"{title}\"", "table": "Tabela", "welcome_back": "Kaj vse se dogaja?", @@ -102,23 +102,23 @@ "profile_oauth_client_secret_title": "Skrivna koda odjemalca", "profile_oauth_client_secret_expl": "Tukaj je skrivna koda va\u0161ega odjemalca. To je edini \u010das, da bo prikazana, zato je ne izgubite! Zdaj lahko uporabite to skrivno kodo za po\u0161iljanje zahtev API.", "profile_oauth_confidential": "Zaupno", - "profile_oauth_confidential_help": "Require the client to authenticate with a secret. Confidential clients can hold credentials in a secure way without exposing them to unauthorized parties. Public applications, such as native desktop or JavaScript SPA applications, are unable to hold secrets securely.", + "profile_oauth_confidential_help": "Od odjemalca zahtevajte avtentikacijo s skrivno kodo. Zaupni odjemalci imajo lahko poverilnice na varen na\u010din, ne da bi jih izpostavili nepoobla\u0161\u010denim osebam. Javne aplikacije, kot so izvorne namizne aplikacije ali aplikacije JavaScript SPA, ne morejo varno hraniti skrivnih kod.", "multi_account_warning_unknown": "Odvisno od vrste transakcije, ki jo ustvarite, lahko izvorni in\/ali ciljni ra\u010dun poznej\u0161ih razdelitev preglasi tisto, kar je definirano v prvi razdelitvi transakcije.", "multi_account_warning_withdrawal": "Upo\u0161tevajte, da bo izvorni ra\u010dun poznej\u0161ih razdelitev preglasilo tisto, kar je definirano v prvi razdelitvi odliva.", "multi_account_warning_deposit": "Upo\u0161tevajte, da bo ciljni ra\u010dun poznej\u0161ih delitev preglasilo tisto, kar je opredeljeno v prvi delitvi priliva.", "multi_account_warning_transfer": "Upo\u0161tevajte, da bo izvorni + ciljni ra\u010dun poznej\u0161ih razdelitev preglasilo tisto, kar je definirano v prvi razdelitvi prenosa.", - "webhook_trigger_ANY": "After any event", + "webhook_trigger_ANY": "Po katerem koli dogodku", "webhook_trigger_STORE_TRANSACTION": "Po ustvarjanju transakcije", "webhook_trigger_UPDATE_TRANSACTION": "Po posodabljanju transakcije", "webhook_trigger_DESTROY_TRANSACTION": "Po brisanju transakcije", - "webhook_trigger_STORE_BUDGET": "After budget creation", - "webhook_trigger_UPDATE_BUDGET": "After budget update", - "webhook_trigger_DESTROY_BUDGET": "After budget delete", - "webhook_trigger_STORE_UPDATE_BUDGET_LIMIT": "After budgeted amount change", + "webhook_trigger_STORE_BUDGET": "Po izdelavi prora\u010duna", + "webhook_trigger_UPDATE_BUDGET": "Po posodobitvi prora\u010duna", + "webhook_trigger_DESTROY_BUDGET": "Po izbrisu prora\u010duna", + "webhook_trigger_STORE_UPDATE_BUDGET_LIMIT": "Po spremembi zneska v prora\u010dunu", "webhook_response_TRANSACTIONS": "Podrobnosti transakcije", - "webhook_response_RELEVANT": "Relevant details", + "webhook_response_RELEVANT": "Relevantni podatki", "webhook_response_ACCOUNTS": "Podrobnosti ra\u010duna", - "webhook_response_NONE": "No details", + "webhook_response_NONE": "Ni podrobnosti", "webhook_delivery_JSON": "JSON", "actions": "Dejanja", "meta_data": "Meta podatki", @@ -160,7 +160,7 @@ "url": "URL", "active": "Aktivno", "interest_date": "Datum obresti", - "administration_currency": "Primary currency", + "administration_currency": "Primarna valuta", "title": "Naslov", "date": "Datum", "book_date": "Datum knji\u017eenja", @@ -180,7 +180,7 @@ "list": { "title": "Naslov", "active": "Aktiviran?", - "primary_currency": "Primary currency", + "primary_currency": "Primarna valuta", "trigger": "Spro\u017eilec", "response": "Odziv", "delivery": "Dostava", From eed2405d760381f4052b1d5de3abcb21a887f1c3 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 23 Aug 2025 11:17:05 +0200 Subject: [PATCH 029/111] Update changelog before I forget about it. --- changelog.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index 7892f43d8c..c48b0ebabf 100644 --- a/changelog.md +++ b/changelog.md @@ -23,11 +23,14 @@ This project adheres to [Semantic Versioning](http://semver.org/). - [Issue 10802](https://github.com/firefly-iii/firefly-iii/issues/10802) (Crash when trying to update a budget limit) reported by @Billos - [Issue 10803](https://github.com/firefly-iii/firefly-iii/issues/10803) (Issue in /v1/budget-limits spent attribute) reported by @Billos - [Issue 10804](https://github.com/firefly-iii/firefly-iii/issues/10804) (No notes information included in the "List all accounts" API call) reported by @gpampuro +- #10808 ### API - [Issue 8345](https://github.com/firefly-iii/firefly-iii/issues/8345) (API: Distinguish spent & earned at `/v2/chart/category/dashboard` (or future `v2/categories`)) reported by @dreautall - +- #10804 +- #10806 +- #10807 From 91f52b5dbc7bf6f894dd9853b05b25c357205294 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 23 Aug 2025 11:26:04 +0200 Subject: [PATCH 030/111] Add copyright statements. --- .../Controllers/Chart/BalanceController.php | 21 + .../ValidatesEnvironmentVariables.php | 8 +- .../System/RecalculatesRunningBalance.php | 9 +- .../Commands/System/ResetsErrorMailLimit.php | 21 + .../Commands/Upgrade/UpgradesWebhooks.php | 21 + app/Events/Model/Bill/WarnUserAboutBill.php | 21 + .../WarnUserAboutOverdueSubscriptions.php | 21 + app/Events/Model/PiggyBank/ChangedName.php | 21 + app/Models/WebhookDelivery.php | 21 + app/Models/WebhookResponse.php | 21 + app/Models/WebhookTrigger.php | 21 + .../User/SubscriptionsOverdueReminder.php | 21 + .../JsonApi/Enrichments/BudgetEnrichment.php | 21 + .../Enrichments/BudgetLimitEnrichment.php | 21 + .../Enrichments/CategoryEnrichment.php | 21 + .../Enrichments/PiggyBankEnrichment.php | 21 + .../Enrichments/PiggyBankEventEnrichment.php | 21 + .../Enrichments/RecurringEnrichment.php | 21 + .../Enrichments/SubscriptionEnrichment.php | 21 + .../JsonApi/Enrichments/WebhookEnrichment.php | 21 + .../RecalculatesAvailableBudgetsTrait.php | 21 + app/Support/Request/ValidatesWebhooks.php | 21 + .../Singleton/PreferencesSingleton.php | 21 + config/webhooks.php | 21 + .../2025_07_10_065736_rename_tag_mode.php | 21 + ...9_180459_create_webhook_details_tables.php | 21 + database/seeders/WebhookDataSeeder.php | 21 + public/index.php | 21 + .../glyphicons-halflings-regular.svg | 2 +- .../font-awesome/fontawesome-webfont.svg | 1418 ++++++++--------- .../Api/Chart/AccountControllerTest.php | 21 + .../Api/Chart/BalanceControllerTest.php | 21 + .../Api/Chart/BudgetControllerTest.php | 21 + .../Api/Chart/CategoryControllerTest.php | 21 + ...ctQueryParserInterfaceParseQueryTester.php | 21 + .../QueryParser/QueryParserParseQueryTest.php | 21 + 36 files changed, 1392 insertions(+), 717 deletions(-) diff --git a/app/Api/V1/Controllers/Chart/BalanceController.php b/app/Api/V1/Controllers/Chart/BalanceController.php index d589be1661..1895a5e35b 100644 --- a/app/Api/V1/Controllers/Chart/BalanceController.php +++ b/app/Api/V1/Controllers/Chart/BalanceController.php @@ -1,5 +1,26 @@ . + */ + declare(strict_types=1); namespace FireflyIII\Api\V1\Controllers\Chart; diff --git a/app/Console/Commands/Integrity/ValidatesEnvironmentVariables.php b/app/Console/Commands/Integrity/ValidatesEnvironmentVariables.php index 06ba95035a..c265cab7fc 100644 --- a/app/Console/Commands/Integrity/ValidatesEnvironmentVariables.php +++ b/app/Console/Commands/Integrity/ValidatesEnvironmentVariables.php @@ -1,9 +1,9 @@ . */ +declare(strict_types=1); + namespace FireflyIII\Console\Commands\Integrity; use FireflyIII\Console\Commands\ShowsFriendlyMessages; diff --git a/app/Console/Commands/System/RecalculatesRunningBalance.php b/app/Console/Commands/System/RecalculatesRunningBalance.php index 5e51a4fcc6..93e3fc8624 100644 --- a/app/Console/Commands/System/RecalculatesRunningBalance.php +++ b/app/Console/Commands/System/RecalculatesRunningBalance.php @@ -1,9 +1,9 @@ . */ +declare(strict_types=1); + namespace FireflyIII\Console\Commands\System; use FireflyIII\Console\Commands\ShowsFriendlyMessages; use FireflyIII\Support\Models\AccountBalanceCalculator; use Illuminate\Console\Command; - class RecalculatesRunningBalance extends Command { use ShowsFriendlyMessages; diff --git a/app/Console/Commands/System/ResetsErrorMailLimit.php b/app/Console/Commands/System/ResetsErrorMailLimit.php index 9aba24fa79..d24d64a1d1 100644 --- a/app/Console/Commands/System/ResetsErrorMailLimit.php +++ b/app/Console/Commands/System/ResetsErrorMailLimit.php @@ -1,5 +1,26 @@ . + */ + declare(strict_types=1); namespace FireflyIII\Console\Commands\System; diff --git a/app/Console/Commands/Upgrade/UpgradesWebhooks.php b/app/Console/Commands/Upgrade/UpgradesWebhooks.php index 93a1c896c9..46e39e11e7 100644 --- a/app/Console/Commands/Upgrade/UpgradesWebhooks.php +++ b/app/Console/Commands/Upgrade/UpgradesWebhooks.php @@ -1,5 +1,26 @@ . + */ + declare(strict_types=1); namespace FireflyIII\Console\Commands\Upgrade; diff --git a/app/Events/Model/Bill/WarnUserAboutBill.php b/app/Events/Model/Bill/WarnUserAboutBill.php index ceb23cae49..0c52d28e5e 100644 --- a/app/Events/Model/Bill/WarnUserAboutBill.php +++ b/app/Events/Model/Bill/WarnUserAboutBill.php @@ -1,6 +1,27 @@ . + */ + declare(strict_types=1); namespace FireflyIII\Events\Model\Bill; diff --git a/app/Events/Model/Bill/WarnUserAboutOverdueSubscriptions.php b/app/Events/Model/Bill/WarnUserAboutOverdueSubscriptions.php index 0e84a849dd..9306bfc208 100644 --- a/app/Events/Model/Bill/WarnUserAboutOverdueSubscriptions.php +++ b/app/Events/Model/Bill/WarnUserAboutOverdueSubscriptions.php @@ -1,5 +1,26 @@ . + */ + declare(strict_types=1); namespace FireflyIII\Events\Model\Bill; diff --git a/app/Events/Model/PiggyBank/ChangedName.php b/app/Events/Model/PiggyBank/ChangedName.php index bed6b78f20..38638c4e04 100644 --- a/app/Events/Model/PiggyBank/ChangedName.php +++ b/app/Events/Model/PiggyBank/ChangedName.php @@ -1,5 +1,26 @@ . + */ + declare(strict_types=1); namespace FireflyIII\Events\Model\PiggyBank; diff --git a/app/Models/WebhookDelivery.php b/app/Models/WebhookDelivery.php index 7635c4de69..a43a47d417 100644 --- a/app/Models/WebhookDelivery.php +++ b/app/Models/WebhookDelivery.php @@ -1,5 +1,26 @@ . + */ + declare(strict_types=1); namespace FireflyIII\Models; diff --git a/app/Models/WebhookResponse.php b/app/Models/WebhookResponse.php index cd1b86f92e..c970e8b70e 100644 --- a/app/Models/WebhookResponse.php +++ b/app/Models/WebhookResponse.php @@ -1,5 +1,26 @@ . + */ + declare(strict_types=1); namespace FireflyIII\Models; diff --git a/app/Models/WebhookTrigger.php b/app/Models/WebhookTrigger.php index 7eed476ced..4bd8cf444d 100644 --- a/app/Models/WebhookTrigger.php +++ b/app/Models/WebhookTrigger.php @@ -1,5 +1,26 @@ . + */ + declare(strict_types=1); namespace FireflyIII\Models; diff --git a/app/Notifications/User/SubscriptionsOverdueReminder.php b/app/Notifications/User/SubscriptionsOverdueReminder.php index 2db7fa53d5..b3647ad128 100644 --- a/app/Notifications/User/SubscriptionsOverdueReminder.php +++ b/app/Notifications/User/SubscriptionsOverdueReminder.php @@ -1,5 +1,26 @@ . + */ + declare(strict_types=1); namespace FireflyIII\Notifications\User; diff --git a/app/Support/JsonApi/Enrichments/BudgetEnrichment.php b/app/Support/JsonApi/Enrichments/BudgetEnrichment.php index a0d6a7893e..426fb70490 100644 --- a/app/Support/JsonApi/Enrichments/BudgetEnrichment.php +++ b/app/Support/JsonApi/Enrichments/BudgetEnrichment.php @@ -1,5 +1,26 @@ . + */ + declare(strict_types=1); namespace FireflyIII\Support\JsonApi\Enrichments; diff --git a/app/Support/JsonApi/Enrichments/BudgetLimitEnrichment.php b/app/Support/JsonApi/Enrichments/BudgetLimitEnrichment.php index d2cb02f993..3ca2c5a508 100644 --- a/app/Support/JsonApi/Enrichments/BudgetLimitEnrichment.php +++ b/app/Support/JsonApi/Enrichments/BudgetLimitEnrichment.php @@ -1,5 +1,26 @@ . + */ + declare(strict_types=1); namespace FireflyIII\Support\JsonApi\Enrichments; diff --git a/app/Support/JsonApi/Enrichments/CategoryEnrichment.php b/app/Support/JsonApi/Enrichments/CategoryEnrichment.php index 39be921a31..7547b57f4b 100644 --- a/app/Support/JsonApi/Enrichments/CategoryEnrichment.php +++ b/app/Support/JsonApi/Enrichments/CategoryEnrichment.php @@ -1,5 +1,26 @@ . + */ + declare(strict_types=1); namespace FireflyIII\Support\JsonApi\Enrichments; diff --git a/app/Support/JsonApi/Enrichments/PiggyBankEnrichment.php b/app/Support/JsonApi/Enrichments/PiggyBankEnrichment.php index 62a0ecb11d..6a50ff421e 100644 --- a/app/Support/JsonApi/Enrichments/PiggyBankEnrichment.php +++ b/app/Support/JsonApi/Enrichments/PiggyBankEnrichment.php @@ -1,5 +1,26 @@ . + */ + declare(strict_types=1); namespace FireflyIII\Support\JsonApi\Enrichments; diff --git a/app/Support/JsonApi/Enrichments/PiggyBankEventEnrichment.php b/app/Support/JsonApi/Enrichments/PiggyBankEventEnrichment.php index 76ea5318f4..e70bfb226f 100644 --- a/app/Support/JsonApi/Enrichments/PiggyBankEventEnrichment.php +++ b/app/Support/JsonApi/Enrichments/PiggyBankEventEnrichment.php @@ -1,5 +1,26 @@ . + */ + declare(strict_types=1); namespace FireflyIII\Support\JsonApi\Enrichments; diff --git a/app/Support/JsonApi/Enrichments/RecurringEnrichment.php b/app/Support/JsonApi/Enrichments/RecurringEnrichment.php index b128ddd442..4800ec98df 100644 --- a/app/Support/JsonApi/Enrichments/RecurringEnrichment.php +++ b/app/Support/JsonApi/Enrichments/RecurringEnrichment.php @@ -1,5 +1,26 @@ . + */ + declare(strict_types=1); namespace FireflyIII\Support\JsonApi\Enrichments; diff --git a/app/Support/JsonApi/Enrichments/SubscriptionEnrichment.php b/app/Support/JsonApi/Enrichments/SubscriptionEnrichment.php index 18e195a6f3..1c18db20de 100644 --- a/app/Support/JsonApi/Enrichments/SubscriptionEnrichment.php +++ b/app/Support/JsonApi/Enrichments/SubscriptionEnrichment.php @@ -1,5 +1,26 @@ . + */ + declare(strict_types=1); namespace FireflyIII\Support\JsonApi\Enrichments; diff --git a/app/Support/JsonApi/Enrichments/WebhookEnrichment.php b/app/Support/JsonApi/Enrichments/WebhookEnrichment.php index 0a79545f4e..10658c0106 100644 --- a/app/Support/JsonApi/Enrichments/WebhookEnrichment.php +++ b/app/Support/JsonApi/Enrichments/WebhookEnrichment.php @@ -1,5 +1,26 @@ . + */ + declare(strict_types=1); namespace FireflyIII\Support\JsonApi\Enrichments; diff --git a/app/Support/Observers/RecalculatesAvailableBudgetsTrait.php b/app/Support/Observers/RecalculatesAvailableBudgetsTrait.php index a6f33660dc..3f50036ab1 100644 --- a/app/Support/Observers/RecalculatesAvailableBudgetsTrait.php +++ b/app/Support/Observers/RecalculatesAvailableBudgetsTrait.php @@ -1,5 +1,26 @@ . + */ + declare(strict_types=1); namespace FireflyIII\Support\Observers; diff --git a/app/Support/Request/ValidatesWebhooks.php b/app/Support/Request/ValidatesWebhooks.php index eb580e6e86..564af6510d 100644 --- a/app/Support/Request/ValidatesWebhooks.php +++ b/app/Support/Request/ValidatesWebhooks.php @@ -1,5 +1,26 @@ . + */ + declare(strict_types=1); namespace FireflyIII\Support\Request; diff --git a/app/Support/Singleton/PreferencesSingleton.php b/app/Support/Singleton/PreferencesSingleton.php index e955a0f5ec..32b9bb94f6 100644 --- a/app/Support/Singleton/PreferencesSingleton.php +++ b/app/Support/Singleton/PreferencesSingleton.php @@ -1,5 +1,26 @@ . + */ + declare(strict_types=1); namespace FireflyIII\Support\Singleton; diff --git a/config/webhooks.php b/config/webhooks.php index 75ff4bddcd..7aada2399c 100644 --- a/config/webhooks.php +++ b/config/webhooks.php @@ -1,5 +1,26 @@ . + */ + declare(strict_types=1); // this is hard coded, which is unfortunate. diff --git a/database/migrations/2025_07_10_065736_rename_tag_mode.php b/database/migrations/2025_07_10_065736_rename_tag_mode.php index 26efd17a93..1785933cc9 100644 --- a/database/migrations/2025_07_10_065736_rename_tag_mode.php +++ b/database/migrations/2025_07_10_065736_rename_tag_mode.php @@ -1,5 +1,26 @@ . + */ + use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Log; diff --git a/database/migrations/2025_08_19_180459_create_webhook_details_tables.php b/database/migrations/2025_08_19_180459_create_webhook_details_tables.php index c25d4083eb..bed7e0b0ad 100644 --- a/database/migrations/2025_08_19_180459_create_webhook_details_tables.php +++ b/database/migrations/2025_08_19_180459_create_webhook_details_tables.php @@ -1,5 +1,26 @@ . + */ + use Illuminate\Database\Migrations\Migration; use Illuminate\Database\QueryException; use Illuminate\Database\Schema\Blueprint; diff --git a/database/seeders/WebhookDataSeeder.php b/database/seeders/WebhookDataSeeder.php index 75b46820a7..67cabb792a 100644 --- a/database/seeders/WebhookDataSeeder.php +++ b/database/seeders/WebhookDataSeeder.php @@ -1,5 +1,26 @@ . + */ + namespace Database\Seeders; use FireflyIII\Enums\WebhookTrigger; diff --git a/public/index.php b/public/index.php index e1efbd4614..d91104f6a2 100644 --- a/public/index.php +++ b/public/index.php @@ -1,4 +1,25 @@ . + */ + declare(strict_types=1); use Illuminate\Contracts\Http\Kernel; diff --git a/public/v1/fonts/vendor/bootstrap-sass/bootstrap/glyphicons-halflings-regular.svg b/public/v1/fonts/vendor/bootstrap-sass/bootstrap/glyphicons-halflings-regular.svg index 94fb5490a2..187805af66 100644 --- a/public/v1/fonts/vendor/bootstrap-sass/bootstrap/glyphicons-halflings-regular.svg +++ b/public/v1/fonts/vendor/bootstrap-sass/bootstrap/glyphicons-halflings-regular.svg @@ -285,4 +285,4 @@ - \ No newline at end of file + diff --git a/public/v1/fonts/vendor/font-awesome/fontawesome-webfont.svg b/public/v1/fonts/vendor/font-awesome/fontawesome-webfont.svg index 855c845e53..52c0773359 100644 --- a/public/v1/fonts/vendor/font-awesome/fontawesome-webfont.svg +++ b/public/v1/fonts/vendor/font-awesome/fontawesome-webfont.svg @@ -8,7 +8,7 @@ Copyright Dave Gandy 2016. All rights reserved. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tests/integration/Api/Chart/AccountControllerTest.php b/tests/integration/Api/Chart/AccountControllerTest.php index 067f347ef8..ff2b717df6 100644 --- a/tests/integration/Api/Chart/AccountControllerTest.php +++ b/tests/integration/Api/Chart/AccountControllerTest.php @@ -1,5 +1,26 @@ . + */ + declare(strict_types=1); namespace Tests\integration\Api\Chart; diff --git a/tests/integration/Api/Chart/BalanceControllerTest.php b/tests/integration/Api/Chart/BalanceControllerTest.php index 69f0fa9785..3762d0a22c 100644 --- a/tests/integration/Api/Chart/BalanceControllerTest.php +++ b/tests/integration/Api/Chart/BalanceControllerTest.php @@ -1,5 +1,26 @@ . + */ + declare(strict_types=1); namespace Tests\integration\Api\Chart; diff --git a/tests/integration/Api/Chart/BudgetControllerTest.php b/tests/integration/Api/Chart/BudgetControllerTest.php index a9457e3822..f5e6603483 100644 --- a/tests/integration/Api/Chart/BudgetControllerTest.php +++ b/tests/integration/Api/Chart/BudgetControllerTest.php @@ -1,5 +1,26 @@ . + */ + declare(strict_types=1); namespace Tests\integration\Api\Chart; diff --git a/tests/integration/Api/Chart/CategoryControllerTest.php b/tests/integration/Api/Chart/CategoryControllerTest.php index 7aa7f8ce0b..d80e4fccb2 100644 --- a/tests/integration/Api/Chart/CategoryControllerTest.php +++ b/tests/integration/Api/Chart/CategoryControllerTest.php @@ -1,5 +1,26 @@ . + */ + declare(strict_types=1); namespace Tests\integration\Api\Chart; diff --git a/tests/unit/Support/Search/QueryParser/AbstractQueryParserInterfaceParseQueryTester.php b/tests/unit/Support/Search/QueryParser/AbstractQueryParserInterfaceParseQueryTester.php index a3e99863e6..50f72fe69b 100644 --- a/tests/unit/Support/Search/QueryParser/AbstractQueryParserInterfaceParseQueryTester.php +++ b/tests/unit/Support/Search/QueryParser/AbstractQueryParserInterfaceParseQueryTester.php @@ -1,5 +1,26 @@ . + */ + declare(strict_types=1); namespace Tests\unit\Support\Search\QueryParser; diff --git a/tests/unit/Support/Search/QueryParser/QueryParserParseQueryTest.php b/tests/unit/Support/Search/QueryParser/QueryParserParseQueryTest.php index bdfffc427a..085f11b1f5 100644 --- a/tests/unit/Support/Search/QueryParser/QueryParserParseQueryTest.php +++ b/tests/unit/Support/Search/QueryParser/QueryParserParseQueryTest.php @@ -1,5 +1,26 @@ . + */ + declare(strict_types=1); namespace Tests\unit\Support\Search\QueryParser; From 450ac7e6ee10490453917022c5fd9371c8a70155 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 24 Aug 2025 06:51:28 +0200 Subject: [PATCH 031/111] Fix #10813 --- resources/views/list/bills.twig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/views/list/bills.twig b/resources/views/list/bills.twig index e326f77c99..ef0093b389 100644 --- a/resources/views/list/bills.twig +++ b/resources/views/list/bills.twig @@ -68,8 +68,8 @@ > ~ {{ formatAmountBySymbol((entry.amount_max + entry.amount_min)/2, entry.currency_symbol, entry.currency_decimal_places) }} - {% if '0' != entry.pc_amount_max %} - (~ {{ formatAmountBySymbol((entry.pc_amount_max + entry.pc_amount_min)/2, primaryCurrency.symbol, primaryCurrency.decimal_places) }}) + {% if '0' != entry.pc_amount_max and null != entry.pc_amount_max %} + (~ {{ formatAmountBySymbol((entry.pc_amount_max + entry.pc_amount_min)/2, primaryCurrency.symbol, primaryCurrency.decimal_places) }}) {% endif %} From e68850f192fcacb2129bf2a9a8d7e4585c0d6d2a Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 24 Aug 2025 06:51:48 +0200 Subject: [PATCH 032/111] Fix changelog. --- changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.md b/changelog.md index c48b0ebabf..8e3e0daaab 100644 --- a/changelog.md +++ b/changelog.md @@ -24,6 +24,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - [Issue 10803](https://github.com/firefly-iii/firefly-iii/issues/10803) (Issue in /v1/budget-limits spent attribute) reported by @Billos - [Issue 10804](https://github.com/firefly-iii/firefly-iii/issues/10804) (No notes information included in the "List all accounts" API call) reported by @gpampuro - #10808 +- #10813 ### API From 33bd2ceae80a0bc244e328964a879ca8c8f67f26 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 24 Aug 2025 13:30:41 +0200 Subject: [PATCH 033/111] Fix amounts in transaction show. --- resources/views/transactions/show.twig | 31 +++++++++++++------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/resources/views/transactions/show.twig b/resources/views/transactions/show.twig index cb0c6c1f3a..2761175f6a 100644 --- a/resources/views/transactions/show.twig +++ b/resources/views/transactions/show.twig @@ -160,12 +160,12 @@ {% for amount in amounts %} {% if first.transaction_type_type == 'Withdrawal' %} - {{ formatAmountBySymbol(amount.amount*-1,amount.symbol, amount.decimal_places) }}{% if loop.index0 != amounts|length -1 %}, {% endif %} - {% elseif first.transaction_type_type == 'Deposit' %} {{ formatAmountBySymbol(amount.amount,amount.symbol, amount.decimal_places) }}{% if loop.index0 != amounts|length -1 %}, {% endif %} + {% elseif first.transaction_type_type == 'Deposit' %} + {{ formatAmountBySymbol(amount.amount*-1,amount.symbol, amount.decimal_places) }}{% if loop.index0 != amounts|length -1 %}, {% endif %} {% elseif first.transaction_type_type == 'Transfer' %} - {{ formatAmountBySymbol(amount.amount, amount.symbol, amount.decimal_places, false) }}{% if loop.index0 != amounts|length -1 %}, {% endif %} + {{ formatAmountBySymbol(amount.amount*-1, amount.symbol, amount.decimal_places, false) }}{% if loop.index0 != amounts|length -1 %}, {% endif %} {% elseif first.transaction_type_type == 'Opening balance' %} {# Opening balance stored amount is always negative: find out which way the money goes #} @@ -289,7 +289,6 @@
- {% if 'Cash account' == journal.source_account_type %} ({{ 'cash'|_ }}) {% else %} @@ -298,12 +297,12 @@ {% endif %} {% if first.transaction_type_type == 'Withdrawal' %} - {{ formatAmountBySymbol(journal.amount*-1, journal.currency_symbol, journal.currency_decimal_places) }} - {% elseif first.transaction_type_type == 'Deposit' %} {{ formatAmountBySymbol(journal.amount, journal.currency_symbol, journal.currency_decimal_places) }} + {% elseif first.transaction_type_type == 'Deposit' %} + {{ formatAmountBySymbol(journal.amount*-1, journal.currency_symbol, journal.currency_decimal_places) }} {% elseif first.transaction_type_type == 'Transfer' or first.transaction_type_type == 'Opening balance' %} - {{ formatAmountBySymbol(journal.amount, journal.currency_symbol, journal.currency_decimal_places, false) }} + {{ formatAmountBySymbol(journal.amount*-1, journal.currency_symbol, journal.currency_decimal_places, false) }} {% elseif first.transaction_type_type == 'Liability credit' %} @@ -314,12 +313,12 @@ {% if null != journal.pc_amount and primaryCurrency.id != journal.currency_id %} {% if first.transaction_type_type == 'Withdrawal' %} - ({{ formatAmountBySymbol(journal.pc_amount*-1, primaryCurrency.symbol, primaryCurrency.decimal_places) }}) - {% elseif first.transaction_type_type == 'Deposit' %} ({{ formatAmountBySymbol(journal.pc_amount, primaryCurrency.symbol, primaryCurrency.decimal_places) }}) + {% elseif first.transaction_type_type == 'Deposit' %} + ({{ formatAmountBySymbol(journal.pc_amount*-1, primaryCurrency.symbol, primaryCurrency.decimal_places) }}) {% elseif first.transaction_type_type == 'Transfer' %} - ({{ formatAmountBySymbol(journal.pc_amount, primaryCurrency.symbol, primaryCurrency.decimal_places, false) }}) + ({{ formatAmountBySymbol(journal.pc_amount*-1, primaryCurrency.symbol, primaryCurrency.decimal_places, false) }}) {% endif %} {% endif %} @@ -327,12 +326,12 @@ {% if null != journal.foreign_amount %} {% if first.transaction_type_type == 'Withdrawal' %} - ({{ formatAmountBySymbol(journal.foreign_amount*-1, journal.foreign_currency_symbol, journal.foreign_currency_decimal_places) }}) - {% elseif first.transaction_type_type == 'Deposit' %} ({{ formatAmountBySymbol(journal.foreign_amount, journal.foreign_currency_symbol, journal.foreign_currency_decimal_places) }}) + {% elseif first.transaction_type_type == 'Deposit' %} + ({{ formatAmountBySymbol(journal.foreign_amount*-1, journal.foreign_currency_symbol, journal.foreign_currency_decimal_places) }}) {% elseif first.transaction_type_type == 'Transfer' %} - ({{ formatAmountBySymbol(journal.foreign_amount, journal.foreign_currency_symbol, journal.foreign_currency_decimal_places, false) }}) + ({{ formatAmountBySymbol(journal.foreign_amount*-1, journal.foreign_currency_symbol, journal.foreign_currency_decimal_places, false) }}) {% endif %} {% endif %} @@ -340,12 +339,12 @@ {% if null != journal.pc_foreign_amount %} {% if first.transaction_type_type == 'Withdrawal' %} - ({{ formatAmountBySymbol(journal.pc_foreign_amount*-1, primaryCurrency.symbol, primaryCurrency.decimal_places) }}) - {% elseif first.transaction_type_type == 'Deposit' %} ({{ formatAmountBySymbol(journal.pc_foreign_amount, primaryCurrency.symbol, primaryCurrency.decimal_places) }}) + {% elseif first.transaction_type_type == 'Deposit' %} + ({{ formatAmountBySymbol(journal.pc_foreign_amount*-1, primaryCurrency.symbol, primaryCurrency.decimal_places) }}) {% elseif first.transaction_type_type == 'Transfer' %} - ({{ formatAmountBySymbol(journal.pc_foreign_amount, primaryCurrency.symbol, primaryCurrency.decimal_places, false) }}) + ({{ formatAmountBySymbol(journal.pc_foreign_amount*-1, primaryCurrency.symbol, primaryCurrency.decimal_places, false) }}) {% endif %} {% endif %} From b1f79c4c0fb5d67613ccfe48f2578231221e8943 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 24 Aug 2025 13:31:00 +0200 Subject: [PATCH 034/111] Fix #10814 --- resources/views/transactions/show.twig | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/views/transactions/show.twig b/resources/views/transactions/show.twig index 2761175f6a..7bbd9428a4 100644 --- a/resources/views/transactions/show.twig +++ b/resources/views/transactions/show.twig @@ -165,6 +165,7 @@ {{ formatAmountBySymbol(amount.amount*-1,amount.symbol, amount.decimal_places) }}{% if loop.index0 != amounts|length -1 %}, {% endif %} {% elseif first.transaction_type_type == 'Transfer' %} + {{ formatAmountBySymbol(amount.amount*-1, amount.symbol, amount.decimal_places, false) }}{% if loop.index0 != amounts|length -1 %}, {% endif %} {% elseif first.transaction_type_type == 'Opening balance' %} From 2d67eece5d0455ccb44aa054e0570215a7cfb312 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 24 Aug 2025 17:14:07 +0200 Subject: [PATCH 035/111] Various code cleanup. --- .../Commands/System/OutputsInstructions.php | 33 +++++++++++++++++-- .../System/RecalculatesRunningBalance.php | 1 + resources/views/transactions/show.twig | 2 +- 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/app/Console/Commands/System/OutputsInstructions.php b/app/Console/Commands/System/OutputsInstructions.php index b144fcdf33..b765083569 100644 --- a/app/Console/Commands/System/OutputsInstructions.php +++ b/app/Console/Commands/System/OutputsInstructions.php @@ -133,6 +133,9 @@ class OutputsInstructions extends Command if ('03-31' === $today) { $colors = ['bright-blue', 'bright-red', 'white', 'white', 'bright-red', 'bright-blue', 'default', 'default']; } + if ('ru_RU' === config('firefly.default_language')) { + $colors = ['blue', 'blue', 'blue', 'yellow', 'yellow', 'yellow', 'default', 'default']; + } $this->line(sprintf(' ______ _ __ _ _____ _____ _____ ', $colors[0])); $this->line(sprintf(' | ____(_) / _| | |_ _|_ _|_ _| ', $colors[1])); @@ -238,14 +241,38 @@ class OutputsInstructions extends Command private function someQuote(): void { - $lines = [ + $lines = [ 'Forgive yourself for not being at peace.', 'Doesn\'t look like anything to me.', 'Be proud of what you make.', 'Be there or forever wonder.', 'A year from now you will wish you had started today.', ]; - $random = random_int(0, count($lines) - 1); - $this->line(sprintf(' "%s"', $lines[$random])); + $addQuotes = true; + + // fuck the Russian aggression in Ukraine. + + // There is no point even trying to be neutral, because you can’t. When I say you can’t be neutral on + // a moving train, it means the world is already moving in certain directions. Children are going + // hungry, wars are taking place. In a situation like that, to be neutral or to try to be neutral, + // to stand aside, not to take a stand, not to participate, is to collaborate with whatever is + // going on, to allow that to happen. + + if ('ru_RU' === config('firefly.default_language')) { + $addQuotes = false; + $lines = [ + '🇺🇦 Слава Україні!', + '🇺🇦 Slava Ukraini!', + ]; + } + + $random = random_int(0, count($lines) - 1); + if ($addQuotes) { + $this->line(sprintf(' "%s"', $lines[$random])); + + return; + } + $this->line(sprintf(' %s', $lines[$random])); + } } diff --git a/app/Console/Commands/System/RecalculatesRunningBalance.php b/app/Console/Commands/System/RecalculatesRunningBalance.php index 93e3fc8624..765e4fbc28 100644 --- a/app/Console/Commands/System/RecalculatesRunningBalance.php +++ b/app/Console/Commands/System/RecalculatesRunningBalance.php @@ -28,6 +28,7 @@ namespace FireflyIII\Console\Commands\System; use FireflyIII\Console\Commands\ShowsFriendlyMessages; use FireflyIII\Support\Models\AccountBalanceCalculator; use Illuminate\Console\Command; + class RecalculatesRunningBalance extends Command { use ShowsFriendlyMessages; diff --git a/resources/views/transactions/show.twig b/resources/views/transactions/show.twig index 7bbd9428a4..2bda3de9a6 100644 --- a/resources/views/transactions/show.twig +++ b/resources/views/transactions/show.twig @@ -165,7 +165,7 @@ {{ formatAmountBySymbol(amount.amount*-1,amount.symbol, amount.decimal_places) }}{% if loop.index0 != amounts|length -1 %}, {% endif %} {% elseif first.transaction_type_type == 'Transfer' %} - + {{ formatAmountBySymbol(amount.amount*-1, amount.symbol, amount.decimal_places, false) }}{% if loop.index0 != amounts|length -1 %}, {% endif %} {% elseif first.transaction_type_type == 'Opening balance' %} From 1cde7aab0c87b9585077b0a43261832e6c3eb3fb Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 24 Aug 2025 20:14:02 +0200 Subject: [PATCH 036/111] Fix #10815 --- app/Support/JsonApi/Enrichments/AccountEnrichment.php | 7 ++++++- app/Transformers/AccountTransformer.php | 6 +----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/app/Support/JsonApi/Enrichments/AccountEnrichment.php b/app/Support/JsonApi/Enrichments/AccountEnrichment.php index 80cfe8fc97..58df9f7e9a 100644 --- a/app/Support/JsonApi/Enrichments/AccountEnrichment.php +++ b/app/Support/JsonApi/Enrichments/AccountEnrichment.php @@ -327,6 +327,7 @@ class AccountEnrichment implements EnrichmentInterface $openingBalance = null; $pcOpeningBalance = null; } + $meta['current_balance_date'] =$this->getDate(); $meta['balances'] = [ 'current_balance' => $currentBalance, 'pc_current_balance' => $pcCurrentBalance, @@ -378,13 +379,17 @@ class AccountEnrichment implements EnrichmentInterface public function setDate(?Carbon $date): void { + if(null !== $date) { + $date->endOfDay(); + Log::debug(sprintf('Date is now %s', $date->toW3cString())); + } $this->date = $date; } public function getDate(): Carbon { if (null === $this->date) { - return today(); + return now(); } return $this->date; diff --git a/app/Transformers/AccountTransformer.php b/app/Transformers/AccountTransformer.php index 1f8ff4dadc..0ef5046eb6 100644 --- a/app/Transformers/AccountTransformer.php +++ b/app/Transformers/AccountTransformer.php @@ -78,10 +78,6 @@ class AccountTransformer extends AbstractTransformer $zoomLevel = $account->meta['location']['zoom_level'] ?? null; $order = $account->order; - // date (for balance etc.) - $date = $this->getDate(); - $date->endOfDay(); - // get primary currency as fallback: $currency = $this->primary; // assume primary currency if ($hasCurrencySettings) { @@ -141,7 +137,7 @@ class AccountTransformer extends AbstractTransformer 'debt_amount' => $account->meta['balances']['debt_amount'], 'pc_debt_amount' => $account->meta['balances']['pc_debt_amount'], - 'current_balance_date' => $date->toAtomString(), + 'current_balance_date' => $account->meta['current_balance_date']->toAtomString(), 'notes' => $account->meta['notes'] ?? null, 'monthly_payment_date' => $monthlyPaymentDate, 'credit_card_type' => $creditCardType, From 756e857ba0bb8d7b087866a7497cc91735f34873 Mon Sep 17 00:00:00 2001 From: JC5 Date: Sun, 24 Aug 2025 20:26:44 +0200 Subject: [PATCH 037/111] =?UTF-8?q?=F0=9F=A4=96=20Auto=20commit=20for=20re?= =?UTF-8?q?lease=20'develop'=20on=202025-08-24?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../JsonApi/Enrichments/AccountEnrichment.php | 36 +++++++++---------- changelog.md | 10 +++--- composer.lock | 30 ++++++++-------- config/firefly.php | 4 +-- package-lock.json | 6 ++-- 5 files changed, 43 insertions(+), 43 deletions(-) diff --git a/app/Support/JsonApi/Enrichments/AccountEnrichment.php b/app/Support/JsonApi/Enrichments/AccountEnrichment.php index 58df9f7e9a..632d0537c5 100644 --- a/app/Support/JsonApi/Enrichments/AccountEnrichment.php +++ b/app/Support/JsonApi/Enrichments/AccountEnrichment.php @@ -234,9 +234,9 @@ class AccountEnrichment implements EnrichmentInterface private function appendCollectedData(): void { $this->collection = $this->collection->map(function (Account $item) { - $id = (int)$item->id; - $item->full_account_type = $this->accountTypes[(int)$item->account_type_id] ?? null; - $meta = [ + $id = (int)$item->id; + $item->full_account_type = $this->accountTypes[(int)$item->account_type_id] ?? null; + $meta = [ 'currency' => null, 'location' => [ 'latitude' => null, @@ -283,28 +283,28 @@ class AccountEnrichment implements EnrichmentInterface // add balances // get currencies: - $currency = $this->primaryCurrency; // assume primary currency + $currency = $this->primaryCurrency; // assume primary currency if (null !== $meta['currency']) { $currency = $meta['currency']; } // get the current balance: - $date = $this->getDate(); + $date = $this->getDate(); // $finalBalance = Steam::finalAccountBalance($item, $date, $this->primaryCurrency, $this->convertToPrimary); - $finalBalance = $this->balances[$id]; + $finalBalance = $this->balances[$id]; Log::debug(sprintf('Call finalAccountBalance(%s) with date/time "%s"', var_export($this->convertToPrimary, true), $date->toIso8601String()), $finalBalance); // collect current balances: - $currentBalance = Steam::bcround($finalBalance[$currency->code] ?? '0', $currency->decimal_places); - $openingBalance = Steam::bcround($meta['opening_balance_amount'] ?? '0', $currency->decimal_places); - $virtualBalance = Steam::bcround($account->virtual_balance ?? '0', $currency->decimal_places); - $debtAmount = $meta['current_debt'] ?? null; + $currentBalance = Steam::bcround($finalBalance[$currency->code] ?? '0', $currency->decimal_places); + $openingBalance = Steam::bcround($meta['opening_balance_amount'] ?? '0', $currency->decimal_places); + $virtualBalance = Steam::bcround($account->virtual_balance ?? '0', $currency->decimal_places); + $debtAmount = $meta['current_debt'] ?? null; // set some pc_ default values to NULL: - $pcCurrentBalance = null; - $pcOpeningBalance = null; - $pcVirtualBalance = null; - $pcDebtAmount = null; + $pcCurrentBalance = null; + $pcOpeningBalance = null; + $pcVirtualBalance = null; + $pcDebtAmount = null; // convert to primary currency if needed: if ($this->convertToPrimary && $currency->id !== $this->primaryCurrency->id) { @@ -327,8 +327,8 @@ class AccountEnrichment implements EnrichmentInterface $openingBalance = null; $pcOpeningBalance = null; } - $meta['current_balance_date'] =$this->getDate(); - $meta['balances'] = [ + $meta['current_balance_date'] = $this->getDate(); + $meta['balances'] = [ 'current_balance' => $currentBalance, 'pc_current_balance' => $pcCurrentBalance, 'opening_balance' => $openingBalance, @@ -339,7 +339,7 @@ class AccountEnrichment implements EnrichmentInterface 'pc_debt_amount' => $pcDebtAmount, ]; // end add balances - $item->meta = $meta; + $item->meta = $meta; return $item; }); @@ -379,7 +379,7 @@ class AccountEnrichment implements EnrichmentInterface public function setDate(?Carbon $date): void { - if(null !== $date) { + if (null !== $date) { $date->endOfDay(); Log::debug(sprintf('Date is now %s', $date->toW3cString())); } diff --git a/changelog.md b/changelog.md index 8e3e0daaab..193b65e3ca 100644 --- a/changelog.md +++ b/changelog.md @@ -23,15 +23,15 @@ This project adheres to [Semantic Versioning](http://semver.org/). - [Issue 10802](https://github.com/firefly-iii/firefly-iii/issues/10802) (Crash when trying to update a budget limit) reported by @Billos - [Issue 10803](https://github.com/firefly-iii/firefly-iii/issues/10803) (Issue in /v1/budget-limits spent attribute) reported by @Billos - [Issue 10804](https://github.com/firefly-iii/firefly-iii/issues/10804) (No notes information included in the "List all accounts" API call) reported by @gpampuro -- #10808 -- #10813 +- [Issue 10808](https://github.com/firefly-iii/firefly-iii/issues/10808) (cron job Error: Undefined variable $preference) reported by @MexerSam +- [Issue 10813](https://github.com/firefly-iii/firefly-iii/issues/10813) (Error "Argument #2 ($symbol) must be of type string" while try open subscriptions section) reported by @mrResident ### API - [Issue 8345](https://github.com/firefly-iii/firefly-iii/issues/8345) (API: Distinguish spent & earned at `/v2/chart/category/dashboard` (or future `v2/categories`)) reported by @dreautall -- #10804 -- #10806 -- #10807 +- [Issue 10804](https://github.com/firefly-iii/firefly-iii/issues/10804) (No notes information included in the "List all accounts" API call) reported by @gpampuro +- [Issue 10806](https://github.com/firefly-iii/firefly-iii/issues/10806) (API: `/v1/chart/balance/balance` has undocumented `period` parameter) reported by @dreautall +- [Issue 10807](https://github.com/firefly-iii/firefly-iii/issues/10807) (API: `/v1/bills` field `object_group_id` returns int, should be string) reported by @dreautall diff --git a/composer.lock b/composer.lock index 5f19b84d18..1d6890ad5e 100644 --- a/composer.lock +++ b/composer.lock @@ -1244,22 +1244,22 @@ }, { "name": "guzzlehttp/guzzle", - "version": "7.9.3", + "version": "7.10.0", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "7b2f29fe81dc4da0ca0ea7d42107a0845946ea77" + "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/7b2f29fe81dc4da0ca0ea7d42107a0845946ea77", - "reference": "7b2f29fe81dc4da0ca0ea7d42107a0845946ea77", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", + "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", "shasum": "" }, "require": { "ext-json": "*", - "guzzlehttp/promises": "^1.5.3 || ^2.0.3", - "guzzlehttp/psr7": "^2.7.0", + "guzzlehttp/promises": "^2.3", + "guzzlehttp/psr7": "^2.8", "php": "^7.2.5 || ^8.0", "psr/http-client": "^1.0", "symfony/deprecation-contracts": "^2.2 || ^3.0" @@ -1350,7 +1350,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.9.3" + "source": "https://github.com/guzzle/guzzle/tree/7.10.0" }, "funding": [ { @@ -1366,7 +1366,7 @@ "type": "tidelift" } ], - "time": "2025-03-27T13:37:11+00:00" + "time": "2025-08-23T22:36:01+00:00" }, { "name": "guzzlehttp/promises", @@ -1453,16 +1453,16 @@ }, { "name": "guzzlehttp/psr7", - "version": "2.7.1", + "version": "2.8.0", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16" + "reference": "21dc724a0583619cd1652f673303492272778051" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/c2270caaabe631b3b44c85f99e5a04bbb8060d16", - "reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/21dc724a0583619cd1652f673303492272778051", + "reference": "21dc724a0583619cd1652f673303492272778051", "shasum": "" }, "require": { @@ -1478,7 +1478,7 @@ "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", "http-interop/http-factory-tests": "0.9.0", - "phpunit/phpunit": "^8.5.39 || ^9.6.20" + "phpunit/phpunit": "^8.5.44 || ^9.6.25" }, "suggest": { "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" @@ -1549,7 +1549,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.7.1" + "source": "https://github.com/guzzle/psr7/tree/2.8.0" }, "funding": [ { @@ -1565,7 +1565,7 @@ "type": "tidelift" } ], - "time": "2025-03-27T12:30:47+00:00" + "time": "2025-08-23T21:21:41+00:00" }, { "name": "guzzlehttp/uri-template", diff --git a/config/firefly.php b/config/firefly.php index 25545845fa..32bd7754df 100644 --- a/config/firefly.php +++ b/config/firefly.php @@ -78,8 +78,8 @@ return [ 'running_balance_column' => env('USE_RUNNING_BALANCE', false), // see cer.php for exchange rates feature flag. ], - 'version' => 'develop/2025-08-23', - 'build_time' => 1755940389, + 'version' => 'develop/2025-08-24', + 'build_time' => 1756059882, 'api_version' => '2.1.0', // field is no longer used. 'db_version' => 26, diff --git a/package-lock.json b/package-lock.json index d98d61c5a8..3e58210c6e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7052,9 +7052,9 @@ } }, "node_modules/i18next": { - "version": "25.4.0", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.4.0.tgz", - "integrity": "sha512-UH5aiamXsO3cfrZFurCHiB6YSs3C+s+XY9UaJllMMSbmaoXILxFgqDEZu4NbfzJFjmUo3BNMa++Rjkr3ofjfLw==", + "version": "25.4.2", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.4.2.tgz", + "integrity": "sha512-gD4T25a6ovNXsfXY1TwHXXXLnD/K2t99jyYMCSimSCBnBRJVQr5j+VAaU83RJCPzrTGhVQ6dqIga66xO2rtd5g==", "funding": [ { "type": "individual", From c183f91ff675fffb1caa36bed4482dd282295962 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 24 Aug 2025 20:29:39 +0200 Subject: [PATCH 038/111] Add issue to changelog --- changelog.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/changelog.md b/changelog.md index 193b65e3ca..6b1a082045 100644 --- a/changelog.md +++ b/changelog.md @@ -32,9 +32,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - [Issue 10804](https://github.com/firefly-iii/firefly-iii/issues/10804) (No notes information included in the "List all accounts" API call) reported by @gpampuro - [Issue 10806](https://github.com/firefly-iii/firefly-iii/issues/10806) (API: `/v1/chart/balance/balance` has undocumented `period` parameter) reported by @dreautall - [Issue 10807](https://github.com/firefly-iii/firefly-iii/issues/10807) (API: `/v1/bills` field `object_group_id` returns int, should be string) reported by @dreautall - - - +- #10815 ## 6.3.2 - 2025-08-20 From 28b620fb5c9aa88a33d8c34c1e3c8a01ab16773c Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 24 Aug 2025 20:30:40 +0200 Subject: [PATCH 039/111] Remove superfluous logging. --- app/Support/JsonApi/Enrichments/SubscriptionEnrichment.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Support/JsonApi/Enrichments/SubscriptionEnrichment.php b/app/Support/JsonApi/Enrichments/SubscriptionEnrichment.php index 1c18db20de..1797d086e6 100644 --- a/app/Support/JsonApi/Enrichments/SubscriptionEnrichment.php +++ b/app/Support/JsonApi/Enrichments/SubscriptionEnrichment.php @@ -383,7 +383,7 @@ class SubscriptionEnrichment implements EnrichmentInterface Log::debug(sprintf('[b] Last paid date is: %s', $return->format('Y-m-d'))); } } - Log::debug(sprintf('[c] Last paid date is: "%s"', $return?->format('Y-m-d'))); + // Log::debug(sprintf('[c] Last paid date is: "%s"', $return?->format('Y-m-d'))); return $return; } From 049cbab8615a3f34959ca07837d9c6ff6cfccb53 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 24 Aug 2025 20:31:12 +0200 Subject: [PATCH 040/111] Remove superfluous logging. --- .../JsonApi/Enrichments/TransactionGroupEnrichment.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Support/JsonApi/Enrichments/TransactionGroupEnrichment.php b/app/Support/JsonApi/Enrichments/TransactionGroupEnrichment.php index 0e552281a3..340d928304 100644 --- a/app/Support/JsonApi/Enrichments/TransactionGroupEnrichment.php +++ b/app/Support/JsonApi/Enrichments/TransactionGroupEnrichment.php @@ -143,9 +143,9 @@ class TransactionGroupEnrichment implements EnrichmentInterface continue; } if (in_array($name, $this->dateFields, true)) { - Log::debug(sprintf('Meta data for "%s" is a date : "%s"', $name, $data)); + // Log::debug(sprintf('Meta data for "%s" is a date : "%s"', $name, $data)); $this->metaData[$entry['transaction_journal_id']][$name] = Carbon::parse($data, config('app.timezone')); - Log::debug(sprintf('Meta data for "%s" converts to: "%s"', $name, $this->metaData[$entry['transaction_journal_id']][$name]->toW3CString())); + // Log::debug(sprintf('Meta data for "%s" converts to: "%s"', $name, $this->metaData[$entry['transaction_journal_id']][$name]->toW3CString())); continue; } From cbb0621fd9f01abe7288860f47233bf37d2060c6 Mon Sep 17 00:00:00 2001 From: JC5 Date: Mon, 25 Aug 2025 05:27:42 +0200 Subject: [PATCH 041/111] =?UTF-8?q?=F0=9F=A4=96=20Auto=20commit=20for=20re?= =?UTF-8?q?lease=20'develop'=20on=202025-08-25?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- changelog.md | 2 +- config/firefly.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/changelog.md b/changelog.md index 6b1a082045..227f44da93 100644 --- a/changelog.md +++ b/changelog.md @@ -32,7 +32,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - [Issue 10804](https://github.com/firefly-iii/firefly-iii/issues/10804) (No notes information included in the "List all accounts" API call) reported by @gpampuro - [Issue 10806](https://github.com/firefly-iii/firefly-iii/issues/10806) (API: `/v1/chart/balance/balance` has undocumented `period` parameter) reported by @dreautall - [Issue 10807](https://github.com/firefly-iii/firefly-iii/issues/10807) (API: `/v1/bills` field `object_group_id` returns int, should be string) reported by @dreautall -- #10815 +- [Issue 10815](https://github.com/firefly-iii/firefly-iii/issues/10815) (API: `/v1/accounts` balance is off by a day) reported by @dreautall ## 6.3.2 - 2025-08-20 diff --git a/config/firefly.php b/config/firefly.php index 32bd7754df..51495da795 100644 --- a/config/firefly.php +++ b/config/firefly.php @@ -78,8 +78,8 @@ return [ 'running_balance_column' => env('USE_RUNNING_BALANCE', false), // see cer.php for exchange rates feature flag. ], - 'version' => 'develop/2025-08-24', - 'build_time' => 1756059882, + 'version' => 'develop/2025-08-25', + 'build_time' => 1756092349, 'api_version' => '2.1.0', // field is no longer used. 'db_version' => 26, From 315dc532b6e0ef863d490c57bf65cabe4900c649 Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 25 Aug 2025 15:31:07 +0200 Subject: [PATCH 042/111] Fix #10819 --- app/Support/JsonApi/Enrichments/PiggyBankEnrichment.php | 2 +- changelog.md | 1 + tests/integration/Api/{About => System}/AboutControllerTest.php | 0 3 files changed, 2 insertions(+), 1 deletion(-) rename tests/integration/Api/{About => System}/AboutControllerTest.php (100%) diff --git a/app/Support/JsonApi/Enrichments/PiggyBankEnrichment.php b/app/Support/JsonApi/Enrichments/PiggyBankEnrichment.php index 6a50ff421e..5133b2ef40 100644 --- a/app/Support/JsonApi/Enrichments/PiggyBankEnrichment.php +++ b/app/Support/JsonApi/Enrichments/PiggyBankEnrichment.php @@ -126,7 +126,7 @@ class PiggyBankEnrichment implements EnrichmentInterface 'pc_current_amount' => '0', ]; } - $this->amounts[$id][$accountId]['current_amount'] = bcadd($this->amounts[$id][$accountId]['current_amount'], $item->current_amount); + $this->amounts[$id][$accountId]['current_amount'] = bcadd($this->amounts[$id][$accountId]['current_amount'], (string) $item->current_amount); if (null !== $this->amounts[$id][$accountId]['pc_current_amount'] && null !== $item->native_current_amount) { $this->amounts[$id][$accountId]['pc_current_amount'] = bcadd($this->amounts[$id][$accountId]['pc_current_amount'], $item->native_current_amount); } diff --git a/changelog.md b/changelog.md index 6b1a082045..a093873ef0 100644 --- a/changelog.md +++ b/changelog.md @@ -25,6 +25,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - [Issue 10804](https://github.com/firefly-iii/firefly-iii/issues/10804) (No notes information included in the "List all accounts" API call) reported by @gpampuro - [Issue 10808](https://github.com/firefly-iii/firefly-iii/issues/10808) (cron job Error: Undefined variable $preference) reported by @MexerSam - [Issue 10813](https://github.com/firefly-iii/firefly-iii/issues/10813) (Error "Argument #2 ($symbol) must be of type string" while try open subscriptions section) reported by @mrResident +- #10819 ### API diff --git a/tests/integration/Api/About/AboutControllerTest.php b/tests/integration/Api/System/AboutControllerTest.php similarity index 100% rename from tests/integration/Api/About/AboutControllerTest.php rename to tests/integration/Api/System/AboutControllerTest.php From fd32a692c1f879397d2c12cd162fa593397cecae Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 25 Aug 2025 15:31:51 +0200 Subject: [PATCH 043/111] Add some API tests. --- .../Autocomplete/RecurrenceController.php | 1 + .../Autocomplete/RuleController.php | 5 +- .../Autocomplete/RecurrenceControllerTest.php | 147 ++++++++++++++++++ .../Api/System/AboutControllerTest.php | 2 +- 4 files changed, 152 insertions(+), 3 deletions(-) create mode 100644 tests/integration/Api/Autocomplete/RecurrenceControllerTest.php diff --git a/app/Api/V1/Controllers/Autocomplete/RecurrenceController.php b/app/Api/V1/Controllers/Autocomplete/RecurrenceController.php index f061df1443..2d76c0eb6f 100644 --- a/app/Api/V1/Controllers/Autocomplete/RecurrenceController.php +++ b/app/Api/V1/Controllers/Autocomplete/RecurrenceController.php @@ -69,6 +69,7 @@ class RecurrenceController extends Controller 'id' => (string) $recurrence->id, 'name' => $recurrence->title, 'description' => $recurrence->description, + 'active' => $recurrence->active ]; } diff --git a/app/Api/V1/Controllers/Autocomplete/RuleController.php b/app/Api/V1/Controllers/Autocomplete/RuleController.php index acc44effcd..c2de7bb83b 100644 --- a/app/Api/V1/Controllers/Autocomplete/RuleController.php +++ b/app/Api/V1/Controllers/Autocomplete/RuleController.php @@ -37,7 +37,7 @@ use Illuminate\Http\JsonResponse; class RuleController extends Controller { private RuleRepositoryInterface $repository; - protected array $acceptedRoles = [UserRoleEnum::READ_RULES]; + protected array $acceptedRoles = [UserRoleEnum::READ_RULES]; /** * RuleController constructor. @@ -66,9 +66,10 @@ class RuleController extends Controller /** @var Rule $rule */ foreach ($rules as $rule) { $response[] = [ - 'id' => (string) $rule->id, + 'id' => (string)$rule->id, 'name' => $rule->title, 'description' => $rule->description, + 'active' => $rule->active, ]; } diff --git a/tests/integration/Api/Autocomplete/RecurrenceControllerTest.php b/tests/integration/Api/Autocomplete/RecurrenceControllerTest.php new file mode 100644 index 0000000000..32d5cae547 --- /dev/null +++ b/tests/integration/Api/Autocomplete/RecurrenceControllerTest.php @@ -0,0 +1,147 @@ +. + */ + +declare(strict_types=1); + +namespace Tests\integration\Api\Autocomplete; + +use FireflyIII\Models\Recurrence; +use FireflyIII\User; +use Illuminate\Foundation\Testing\RefreshDatabase; +use Tests\integration\TestCase; + +/** + * Class BillControllerTest + * + * @internal + * + * @coversNothing + */ +final class RecurrenceControllerTest extends TestCase +{ + /** + * @covers \FireflyIII\Api\V1\Controllers\Autocomplete\RecurrenceController + */ + use RefreshDatabase; + + private function createTestRecurrences(int $count, User $user): void + { + for ($i = 1; $i <= $count; ++$i) { + $recurrence = Recurrence::create([ + 'user_id' => $user->id, + 'user_group_id' => $user->user_group_id, + 'transaction_type_id' => 1, + 'title' => 'Recurrence ' . $i, + 'description' => 'Recurrence ' . $i, + 'first_date' => today(), + 'apply_rules' => 1, + 'active' => 1, + 'repetitions' => 5, + + ]); + } + } + + public function testUnAuthenticatedCall(): void + { + // test API + $response = $this->get(route('api.v1.autocomplete.recurring'), ['Accept' => 'application/json']); + $response->assertStatus(401); + $response->assertHeader('Content-Type', 'application/json'); + $response->assertContent('{"message":"Unauthenticated.","exception":"AuthenticationException"}'); + } + + public function testAuthenticatedCall(): void + { + // act as a user + $user = $this->createAuthenticatedUser(); + $this->actingAs($user); + + $response = $this->get(route('api.v1.autocomplete.recurring'), ['Accept' => 'application/json']); + $response->assertStatus(200); + $response->assertHeader('Content-Type', 'application/json'); + } + + public function testGivenAuthenticatedRequestWithItems(): void + { + $user = $this->createAuthenticatedUser(); + $this->actingAs($user); + + $this->createTestRecurrences(5, $user); + $response = $this->get(route('api.v1.autocomplete.recurring'), ['Accept' => 'application/json']); + $response->assertStatus(200); + $response->assertHeader('Content-Type', 'application/json'); + $response->assertJsonCount(5); + $response->assertJsonFragment(['name' => 'Recurrence 1']); + $response->assertJsonStructure([ + '*' => [ + 'id', + 'name', + 'active', + ], + ]); + + } + + public function testGivenAuthenticatedRequestWithItemsLimited(): void + { + $user = $this->createAuthenticatedUser(); + $this->actingAs($user); + + $this->createTestRecurrences(5, $user); + $response = $this->get(route('api.v1.autocomplete.recurring', [ + 'query' => 'Recurrence', + 'limit' => 3, + ]), ['Accept' => 'application/json']); + + $response->assertStatus(200); + $response->assertHeader('Content-Type', 'application/json'); + $response->assertJsonCount(3); + $response->assertJsonFragment(['name' => 'Recurrence 1']); + $response->assertJsonStructure([ + '*' => [ + 'id', + 'name', + 'active', + ], + ]); + + } + + public function testGivenAuthenticatedRequestWithItemsLots(): void + { + $user = $this->createAuthenticatedUser(); + $this->actingAs($user); + + $this->createTestRecurrences(20, $user); + $response = $this->get(route('api.v1.autocomplete.recurring', [ + 'query' => 'Recurrence 1', + 'limit' => 20, + ]), ['Accept' => 'application/json']); + + $response->assertStatus(200); + $response->assertHeader('Content-Type', 'application/json'); + // Bill 1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 (11) + $response->assertJsonCount(11); + $response->assertJsonMissing(['name' => 'Recurrence 2']); + } +} diff --git a/tests/integration/Api/System/AboutControllerTest.php b/tests/integration/Api/System/AboutControllerTest.php index f12179e6a1..6ffb04b044 100644 --- a/tests/integration/Api/System/AboutControllerTest.php +++ b/tests/integration/Api/System/AboutControllerTest.php @@ -22,7 +22,7 @@ declare(strict_types=1); -namespace Tests\integration\Api\About; +namespace Tests\integration\Api\System; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Testing\Fluent\AssertableJson; From 0a116cd04c387d0f4c7417bacde4e10131a93353 Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 25 Aug 2025 17:17:51 +0200 Subject: [PATCH 044/111] Add API autocomplete tests. --- .../Autocomplete/RuleGroupController.php | 1 + .../Api/Autocomplete/RuleControllerTest.php | 157 ++++++++++++++++++ .../Autocomplete/RuleGroupControllerTest.php | 146 ++++++++++++++++ .../Api/Autocomplete/TagControllerTest.php | 143 ++++++++++++++++ 4 files changed, 447 insertions(+) create mode 100644 tests/integration/Api/Autocomplete/RuleControllerTest.php create mode 100644 tests/integration/Api/Autocomplete/RuleGroupControllerTest.php create mode 100644 tests/integration/Api/Autocomplete/TagControllerTest.php diff --git a/app/Api/V1/Controllers/Autocomplete/RuleGroupController.php b/app/Api/V1/Controllers/Autocomplete/RuleGroupController.php index d5e42b6733..6882d76618 100644 --- a/app/Api/V1/Controllers/Autocomplete/RuleGroupController.php +++ b/app/Api/V1/Controllers/Autocomplete/RuleGroupController.php @@ -69,6 +69,7 @@ class RuleGroupController extends Controller 'id' => (string) $group->id, 'name' => $group->title, 'description' => $group->description, + 'active' => $group->active, ]; } diff --git a/tests/integration/Api/Autocomplete/RuleControllerTest.php b/tests/integration/Api/Autocomplete/RuleControllerTest.php new file mode 100644 index 0000000000..3e650adf12 --- /dev/null +++ b/tests/integration/Api/Autocomplete/RuleControllerTest.php @@ -0,0 +1,157 @@ +. + */ + +declare(strict_types=1); + +namespace Tests\integration\Api\Autocomplete; + +use FireflyIII\Models\Recurrence; +use FireflyIII\Models\Rule; +use FireflyIII\Models\RuleGroup; +use FireflyIII\User; +use Illuminate\Foundation\Testing\RefreshDatabase; +use Tests\integration\TestCase; + +/** + * @internal + * + * @coversNothing + */ +final class RuleControllerTest extends TestCase +{ + /** + * @covers \FireflyIII\Api\V1\Controllers\Autocomplete\RecurrenceController + */ + use RefreshDatabase; + + private function createTestRules(int $count, User $user): void + { + $ruleGroup = RuleGroup::create( + [ + 'user_id' => $user->id, + 'user_group_id' => $user->user_group_id, + 'title' => 'RuleGroup 1', + 'description' => 'RuleGroup 1', + 'order' => 1, + 'active' => 1, + 'stop_processing' => 0, + ] + ); + for ($i = 1; $i <= $count; ++$i) { + $rule = Rule::create([ + 'user_id' => $user->id, + 'user_group_id' => $user->user_group_id, + 'rule_group_id' => $ruleGroup->id, + 'title' => 'Rule ' . $i, + 'description' => 'Rule ' . $i, + 'order' => 1, + 'active' => 1, + 'stop_processing' => 0, + 'strict' => 0, + ]); + } + } + + public function testUnauthenticatedCall(): void + { + // test API + $response = $this->get(route('api.v1.autocomplete.rules'), ['Accept' => 'application/json']); + $response->assertStatus(401); + $response->assertHeader('Content-Type', 'application/json'); + $response->assertContent('{"message":"Unauthenticated.","exception":"AuthenticationException"}'); + } + + public function testAuthenticatedCall(): void + { + // act as a user + $user = $this->createAuthenticatedUser(); + $this->actingAs($user); + + $response = $this->get(route('api.v1.autocomplete.rules'), ['Accept' => 'application/json']); + $response->assertStatus(200); + $response->assertHeader('Content-Type', 'application/json'); + } + + public function testGivenAuthenticatedRequestWithItems(): void + { + $user = $this->createAuthenticatedUser(); + $this->actingAs($user); + + $this->createTestRules(5, $user); + $response = $this->get(route('api.v1.autocomplete.rules'), ['Accept' => 'application/json']); + $response->assertStatus(200); + $response->assertHeader('Content-Type', 'application/json'); + $response->assertJsonCount(5); + $response->assertJsonFragment(['name' => 'Rule 1']); + $response->assertJsonStructure([ + '*' => [ + 'id', + 'name', + 'active', + ], + ]); + + } + + public function testGivenAuthenticatedRequestWithItemsLimited(): void + { + $user = $this->createAuthenticatedUser(); + $this->actingAs($user); + + $this->createTestRules(5, $user); + $response = $this->get(route('api.v1.autocomplete.rules', [ + 'query' => 'Rule', + 'limit' => 3, + ]), ['Accept' => 'application/json']); + + $response->assertStatus(200); + $response->assertHeader('Content-Type', 'application/json'); + $response->assertJsonCount(3); + $response->assertJsonFragment(['name' => 'Rule 1']); + $response->assertJsonStructure([ + '*' => [ + 'id', + 'name', + 'active', + ], + ]); + + } + + public function testGivenAuthenticatedRequestWithItemsLots(): void + { + $user = $this->createAuthenticatedUser(); + $this->actingAs($user); + + $this->createTestRules(20, $user); + $response = $this->get(route('api.v1.autocomplete.rules', [ + 'query' => 'Rule 1', + 'limit' => 20, + ]), ['Accept' => 'application/json']); + + $response->assertStatus(200); + $response->assertHeader('Content-Type', 'application/json'); + // Bill 1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 (11) + $response->assertJsonCount(11); + $response->assertJsonMissing(['name' => 'Rule 2']); + } +} diff --git a/tests/integration/Api/Autocomplete/RuleGroupControllerTest.php b/tests/integration/Api/Autocomplete/RuleGroupControllerTest.php new file mode 100644 index 0000000000..570adaad5f --- /dev/null +++ b/tests/integration/Api/Autocomplete/RuleGroupControllerTest.php @@ -0,0 +1,146 @@ +. + */ + +declare(strict_types=1); + +namespace Tests\integration\Api\Autocomplete; + +use FireflyIII\Models\Rule; +use FireflyIII\Models\RuleGroup; +use FireflyIII\User; +use Illuminate\Foundation\Testing\RefreshDatabase; +use Tests\integration\TestCase; + +/** + * @internal + * + * @coversNothing + */ +final class RuleGroupControllerTest extends TestCase +{ + /** + * @covers \FireflyIII\Api\V1\Controllers\Autocomplete\RecurrenceController + */ + use RefreshDatabase; + + private function createTestRuleGroups(int $count, User $user): void + { + + for ($i = 1; $i <= $count; ++$i) { + $ruleGroup = RuleGroup::create( + [ + 'user_id' => $user->id, + 'user_group_id' => $user->user_group_id, + 'title' => 'RuleGroup ' . $i, + 'description' => 'RuleGroup '. $i, + 'order' => 1, + 'active' => 1, + 'stop_processing' => 0, + ] + ); + } + } + + public function testUnauthenticatedCall(): void + { + // test API + $response = $this->get(route('api.v1.autocomplete.rule-groups'), ['Accept' => 'application/json']); + $response->assertStatus(401); + $response->assertHeader('Content-Type', 'application/json'); + $response->assertContent('{"message":"Unauthenticated.","exception":"AuthenticationException"}'); + } + + public function testAuthenticatedCall(): void + { + // act as a user + $user = $this->createAuthenticatedUser(); + $this->actingAs($user); + + $response = $this->get(route('api.v1.autocomplete.rule-groups'), ['Accept' => 'application/json']); + $response->assertStatus(200); + $response->assertHeader('Content-Type', 'application/json'); + } + + public function testGivenAuthenticatedRequestWithItems(): void + { + $user = $this->createAuthenticatedUser(); + $this->actingAs($user); + + $this->createTestRuleGroups(5, $user); + $response = $this->get(route('api.v1.autocomplete.rule-groups'), ['Accept' => 'application/json']); + $response->assertStatus(200); + $response->assertHeader('Content-Type', 'application/json'); + $response->assertJsonCount(5); + $response->assertJsonFragment(['name' => 'RuleGroup 1']); + $response->assertJsonStructure([ + '*' => [ + 'id', + 'name', + 'active', + ], + ]); + + } + + public function testGivenAuthenticatedRequestWithItemsLimited(): void + { + $user = $this->createAuthenticatedUser(); + $this->actingAs($user); + + $this->createTestRuleGroups(5, $user); + $response = $this->get(route('api.v1.autocomplete.rule-groups', [ + 'query' => 'RuleGroup', + 'limit' => 3, + ]), ['Accept' => 'application/json']); + + $response->assertStatus(200); + $response->assertHeader('Content-Type', 'application/json'); + $response->assertJsonCount(3); + $response->assertJsonFragment(['name' => 'RuleGroup 1']); + $response->assertJsonStructure([ + '*' => [ + 'id', + 'name', + 'active', + ], + ]); + + } + + public function testGivenAuthenticatedRequestWithItemsLots(): void + { + $user = $this->createAuthenticatedUser(); + $this->actingAs($user); + + $this->createTestRuleGroups(20, $user); + $response = $this->get(route('api.v1.autocomplete.rule-groups', [ + 'query' => 'RuleGroup 1', + 'limit' => 20, + ]), ['Accept' => 'application/json']); + + $response->assertStatus(200); + $response->assertHeader('Content-Type', 'application/json'); + // Bill 1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 (11) + $response->assertJsonCount(11); + $response->assertJsonMissing(['name' => 'RuleGroup 2']); + } +} diff --git a/tests/integration/Api/Autocomplete/TagControllerTest.php b/tests/integration/Api/Autocomplete/TagControllerTest.php new file mode 100644 index 0000000000..33f0caaa3b --- /dev/null +++ b/tests/integration/Api/Autocomplete/TagControllerTest.php @@ -0,0 +1,143 @@ +. + */ + +declare(strict_types=1); + +namespace Tests\integration\Api\Autocomplete; + +use FireflyIII\Models\RuleGroup; +use FireflyIII\Models\Tag; +use FireflyIII\User; +use Illuminate\Foundation\Testing\RefreshDatabase; +use Tests\integration\TestCase; + +/** + * @internal + * + * @coversNothing + */ +final class TagControllerTest extends TestCase +{ + /** + * @covers \FireflyIII\Api\V1\Controllers\Autocomplete\RecurrenceController + */ + use RefreshDatabase; + + private function createTestTags(int $count, User $user): void + { + + for ($i = 1; $i <= $count; ++$i) { + $tag = Tag::create( + [ + 'user_id' => $user->id, + 'user_group_id' => $user->user_group_id, + 'tag' => 'Tag ' . $i, + 'tag_mode' => 'nothing', + ] + ); + } + } + + public function testUnauthenticatedCall(): void + { + // test API + $response = $this->get(route('api.v1.autocomplete.tags'), ['Accept' => 'application/json']); + $response->assertStatus(401); + $response->assertHeader('Content-Type', 'application/json'); + $response->assertContent('{"message":"Unauthenticated.","exception":"AuthenticationException"}'); + } + + public function testAuthenticatedCall(): void + { + // act as a user + $user = $this->createAuthenticatedUser(); + $this->actingAs($user); + + $response = $this->get(route('api.v1.autocomplete.tags'), ['Accept' => 'application/json']); + $response->assertStatus(200); + $response->assertHeader('Content-Type', 'application/json'); + } + + public function testGivenAuthenticatedRequestWithItems(): void + { + $user = $this->createAuthenticatedUser(); + $this->actingAs($user); + + $this->createTestTags(5, $user); + $response = $this->get(route('api.v1.autocomplete.tags'), ['Accept' => 'application/json']); + $response->assertStatus(200); + $response->assertHeader('Content-Type', 'application/json'); + $response->assertJsonCount(5); + $response->assertJsonFragment(['name' => 'Tag 1']); + $response->assertJsonStructure([ + '*' => [ + 'id', + 'name', + 'tag', + ], + ]); + + } + + public function testGivenAuthenticatedRequestWithItemsLimited(): void + { + $user = $this->createAuthenticatedUser(); + $this->actingAs($user); + + $this->createTestTags(5, $user); + $response = $this->get(route('api.v1.autocomplete.tags', [ + 'query' => 'Tag', + 'limit' => 3, + ]), ['Accept' => 'application/json']); + + $response->assertStatus(200); + $response->assertHeader('Content-Type', 'application/json'); + $response->assertJsonCount(3); + $response->assertJsonFragment(['name' => 'Tag 1']); + $response->assertJsonStructure([ + '*' => [ + 'id', + 'name', + 'tag', + ], + ]); + + } + + public function testGivenAuthenticatedRequestWithItemsLots(): void + { + $user = $this->createAuthenticatedUser(); + $this->actingAs($user); + + $this->createTestTags(20, $user); + $response = $this->get(route('api.v1.autocomplete.tags', [ + 'query' => 'Tag 1', + 'limit' => 20, + ]), ['Accept' => 'application/json']); + + $response->assertStatus(200); + $response->assertHeader('Content-Type', 'application/json'); + // Bill 1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 (11) + $response->assertJsonCount(11); + $response->assertJsonMissing(['name' => 'Tag 2']); + } +} From 5de5e08b1dd09ec25638ed3b60835525fada142c Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 26 Aug 2025 16:05:36 +0200 Subject: [PATCH 045/111] Fix #10820 --- app/Http/Controllers/Rule/SelectController.php | 10 ++-------- .../Controllers/RuleGroup/ExecutionController.php | 11 +---------- app/Http/Requests/SelectTransactionsRequest.php | 2 -- changelog.md | 1 + .../views/rules/rule-group/select-transactions.twig | 2 -- resources/views/rules/rule/select-transactions.twig | 2 -- 6 files changed, 4 insertions(+), 24 deletions(-) diff --git a/app/Http/Controllers/Rule/SelectController.php b/app/Http/Controllers/Rule/SelectController.php index 24c7dc0860..13a361a0f6 100644 --- a/app/Http/Controllers/Rule/SelectController.php +++ b/app/Http/Controllers/Rule/SelectController.php @@ -74,20 +74,16 @@ class SelectController extends Controller /** @var User $user */ $user = auth()->user(); $accounts = implode(',', $request->get('accounts')); - $startDate = new Carbon($request->get('start')); - $endDate = new Carbon($request->get('end')); // create new rule engine: $newRuleEngine = app(RuleEngineInterface::class); $newRuleEngine->setUser($user); // add extra operators: - $newRuleEngine->addOperator(['type' => 'date_after', 'value' => $startDate->format('Y-m-d')]); - $newRuleEngine->addOperator(['type' => 'date_before', 'value' => $endDate->format('Y-m-d')]); $newRuleEngine->addOperator(['type' => 'account_id', 'value' => $accounts]); // set rules: - $newRuleEngine->setRules(new Collection([$rule])); + $newRuleEngine->setRules(new Collection()->push($rule)); $newRuleEngine->fire(); $resultCount = $newRuleEngine->getResults(); @@ -107,11 +103,9 @@ class SelectController extends Controller return redirect(route('rules.index')); } // does the user have shared accounts? - $first = session('first', today(config('app.timezone'))->subYear())->format('Y-m-d'); - $today = today(config('app.timezone'))->format('Y-m-d'); $subTitle = (string) trans('firefly.apply_rule_selection', ['title' => $rule->title]); - return view('rules.rule.select-transactions', compact('first', 'today', 'rule', 'subTitle')); + return view('rules.rule.select-transactions', compact( 'rule', 'subTitle')); } /** diff --git a/app/Http/Controllers/RuleGroup/ExecutionController.php b/app/Http/Controllers/RuleGroup/ExecutionController.php index 4f1b3137b2..a8dc159ce9 100644 --- a/app/Http/Controllers/RuleGroup/ExecutionController.php +++ b/app/Http/Controllers/RuleGroup/ExecutionController.php @@ -42,8 +42,6 @@ use Illuminate\View\View; */ class ExecutionController extends Controller { - private RuleGroupRepositoryInterface $ruleGroupRepository; - /** * ExecutionController constructor. */ @@ -56,7 +54,6 @@ class ExecutionController extends Controller app('view')->share('title', (string) trans('firefly.rules')); app('view')->share('mainTitleIcon', 'fa-random'); - $this->ruleGroupRepository = app(RuleGroupRepositoryInterface::class); return $next($request); } @@ -74,15 +71,11 @@ class ExecutionController extends Controller /** @var User $user */ $user = auth()->user(); $accounts = implode(',', $request->get('accounts')); - $startDate = new Carbon($request->get('start')); - $endDate = new Carbon($request->get('end')); // create new rule engine: $newRuleEngine = app(RuleEngineInterface::class); $newRuleEngine->setUser($user); // add extra operators: - $newRuleEngine->addOperator(['type' => 'date_after', 'value' => $startDate->format('Y-m-d')]); - $newRuleEngine->addOperator(['type' => 'date_before', 'value' => $endDate->format('Y-m-d')]); $newRuleEngine->addOperator(['type' => 'account_id', 'value' => $accounts]); // set rules: @@ -104,10 +97,8 @@ class ExecutionController extends Controller */ public function selectTransactions(RuleGroup $ruleGroup) { - $first = session('first')->format('Y-m-d'); - $today = today(config('app.timezone'))->format('Y-m-d'); $subTitle = (string) trans('firefly.apply_rule_group_selection', ['title' => $ruleGroup->title]); - return view('rules.rule-group.select-transactions', compact('first', 'today', 'ruleGroup', 'subTitle')); + return view('rules.rule-group.select-transactions', compact( 'ruleGroup', 'subTitle')); } } diff --git a/app/Http/Requests/SelectTransactionsRequest.php b/app/Http/Requests/SelectTransactionsRequest.php index 0cbdba1784..d5564d7bc4 100644 --- a/app/Http/Requests/SelectTransactionsRequest.php +++ b/app/Http/Requests/SelectTransactionsRequest.php @@ -41,8 +41,6 @@ class SelectTransactionsRequest extends FormRequest public function rules(): array { return [ - 'start' => 'required|date|after:1970-01-02|before:2038-01-17|before:end|required_with:end', - 'end' => 'required|date|after:1970-01-02|before:2038-01-17|after:start|required_with:start', 'accounts' => 'required', 'accounts.*' => 'required|exists:accounts,id|belongsToUser:accounts', ]; diff --git a/changelog.md b/changelog.md index 785e14af69..33c6b58bbf 100644 --- a/changelog.md +++ b/changelog.md @@ -26,6 +26,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - [Issue 10808](https://github.com/firefly-iii/firefly-iii/issues/10808) (cron job Error: Undefined variable $preference) reported by @MexerSam - [Issue 10813](https://github.com/firefly-iii/firefly-iii/issues/10813) (Error "Argument #2 ($symbol) must be of type string" while try open subscriptions section) reported by @mrResident - #10819 +- #10820 ### API diff --git a/resources/views/rules/rule-group/select-transactions.twig b/resources/views/rules/rule-group/select-transactions.twig index 842458804f..81abe48208 100644 --- a/resources/views/rules/rule-group/select-transactions.twig +++ b/resources/views/rules/rule-group/select-transactions.twig @@ -24,8 +24,6 @@

- {{ ExpandedForm.date('start', first) }} - {{ ExpandedForm.date('end', today) }} {{ AccountForm.assetAccountCheckList('accounts', {'select_all': true,'class': 'account-checkbox', 'label': trans('firefly.include_transactions_from_accounts') }) }}
diff --git a/resources/views/rules/rule/select-transactions.twig b/resources/views/rules/rule/select-transactions.twig index aae7bc3b9a..a6575e53f9 100644 --- a/resources/views/rules/rule/select-transactions.twig +++ b/resources/views/rules/rule/select-transactions.twig @@ -23,8 +23,6 @@

- {{ ExpandedForm.date('start', first) }} - {{ ExpandedForm.date('end', today) }} {{ AccountForm.assetAccountCheckList('accounts', {'select_all': true, 'class': 'account-checkbox', 'label': trans('firefly.include_transactions_from_accounts') }) }}
From f3b7a3015dccafaed5c875284cb4f04761ab060b Mon Sep 17 00:00:00 2001 From: James Cole Date: Wed, 27 Aug 2025 19:00:12 +0200 Subject: [PATCH 046/111] Fix #10824 --- .../Autocomplete/AccountController.php | 1 + .../Autocomplete/BudgetController.php | 1 + .../Transaction/ConvertController.php | 18 +++++++++++++++++- changelog.md | 1 + routes/api.php | 1 + 5 files changed, 21 insertions(+), 1 deletion(-) diff --git a/app/Api/V1/Controllers/Autocomplete/AccountController.php b/app/Api/V1/Controllers/Autocomplete/AccountController.php index a9dba92ca5..bf1d7a3716 100644 --- a/app/Api/V1/Controllers/Autocomplete/AccountController.php +++ b/app/Api/V1/Controllers/Autocomplete/AccountController.php @@ -114,6 +114,7 @@ class AccountController extends Controller 'id' => (string) $account->id, 'name' => $account->name, 'name_with_balance' => $nameWithBalance, + 'active' => $account->active, 'type' => $account->accountType->type, 'currency_id' => (string) $useCurrency->id, 'currency_name' => $useCurrency->name, diff --git a/app/Api/V1/Controllers/Autocomplete/BudgetController.php b/app/Api/V1/Controllers/Autocomplete/BudgetController.php index 3fd273d329..f64500bf38 100644 --- a/app/Api/V1/Controllers/Autocomplete/BudgetController.php +++ b/app/Api/V1/Controllers/Autocomplete/BudgetController.php @@ -69,6 +69,7 @@ class BudgetController extends Controller static fn (Budget $item) => [ 'id' => (string) $item->id, 'name' => $item->name, + 'active' => $item->active, ] ); diff --git a/app/Http/Controllers/Transaction/ConvertController.php b/app/Http/Controllers/Transaction/ConvertController.php index 8f24176f59..e0a6229231 100644 --- a/app/Http/Controllers/Transaction/ConvertController.php +++ b/app/Http/Controllers/Transaction/ConvertController.php @@ -337,6 +337,9 @@ class ConvertController extends Controller 'type' => $transactionType->type, ]; + $sourceTransaction = $journal->transactions()->where('amount', '<', 0)->first(); + $amount = $sourceTransaction?->amount ?? '0'; + // also set the currency to the currency of the source account, in case you're converting a deposit into a transfer. if (TransactionTypeEnum::TRANSFER->value === $transactionType->type && TransactionTypeEnum::DEPOSIT->value === $journal->transactionType->type) { $source = $this->accountRepository->find((int) $sourceId); @@ -346,7 +349,20 @@ class ConvertController extends Controller if ($sourceCurrency instanceof TransactionCurrency && $destCurrency instanceof TransactionCurrency && $sourceCurrency->code !== $destCurrency->code) { $update['currency_id'] = $sourceCurrency->id; $update['foreign_currency_id'] = $destCurrency->id; - $update['foreign_amount'] = '1'; // not the best solution but at this point the amount is hard to get. + $update['foreign_amount'] = Steam::positive($amount); // not the best solution but at this point the amount is hard to get. + } + } + + // same thing for converting a withdrawal into a transfer, but with the currency of the destination account. + if (TransactionTypeEnum::TRANSFER->value === $transactionType->type && TransactionTypeEnum::WITHDRAWAL->value === $journal->transactionType->type) { + $source = $this->accountRepository->find((int) $sourceId); + $sourceCurrency = $this->accountRepository->getAccountCurrency($source); + $dest = $this->accountRepository->find((int) $destinationId); + $destCurrency = $this->accountRepository->getAccountCurrency($dest); + if ($sourceCurrency instanceof TransactionCurrency && $destCurrency instanceof TransactionCurrency && $sourceCurrency->code !== $destCurrency->code) { + $update['currency_id'] = $sourceCurrency->id; + $update['foreign_currency_id'] = $destCurrency->id; + $update['foreign_amount'] = Steam::positive($amount); // not the best solution but at this point the amount is hard to get. } } diff --git a/changelog.md b/changelog.md index 33c6b58bbf..989f8cc33a 100644 --- a/changelog.md +++ b/changelog.md @@ -27,6 +27,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - [Issue 10813](https://github.com/firefly-iii/firefly-iii/issues/10813) (Error "Argument #2 ($symbol) must be of type string" while try open subscriptions section) reported by @mrResident - #10819 - #10820 +- #10824 ### API diff --git a/routes/api.php b/routes/api.php index be21f9248c..64775c9b77 100644 --- a/routes/api.php +++ b/routes/api.php @@ -48,6 +48,7 @@ Route::group( // Auto complete routes Route::get('accounts', ['uses' => 'AccountController@accounts', 'as' => 'accounts']); Route::get('bills', ['uses' => 'BillController@bills', 'as' => 'bills']); + Route::get('subscriptions', ['uses' => 'BillController@bills', 'as' => 'subscriptions']); Route::get('budgets', ['uses' => 'BudgetController@budgets', 'as' => 'budgets']); Route::get('categories', ['uses' => 'CategoryController@categories', 'as' => 'categories']); Route::get('currencies', ['uses' => 'CurrencyController@currencies', 'as' => 'currencies']); From 2e4df282881827cfd5542c0109363b7705c7ad28 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 29 Aug 2025 06:51:20 +0200 Subject: [PATCH 047/111] Less logging. --- app/Handlers/Events/WebhookEventHandler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Handlers/Events/WebhookEventHandler.php b/app/Handlers/Events/WebhookEventHandler.php index f48adef9df..adad80b942 100644 --- a/app/Handlers/Events/WebhookEventHandler.php +++ b/app/Handlers/Events/WebhookEventHandler.php @@ -40,7 +40,7 @@ class WebhookEventHandler { Log::debug(sprintf('Now in %s', __METHOD__)); if (false === config('firefly.feature_flags.webhooks') || false === config('firefly.allow_webhooks')) { - Log::info('Webhook event handler is disabled, do not run sendWebhookMessages().'); + Log::debug('Webhook event handler is disabled, do not run sendWebhookMessages().'); return; } From 1b0e16b6a5af55fb47452cb97128ce9ce14d3b73 Mon Sep 17 00:00:00 2001 From: JC5 Date: Fri, 29 Aug 2025 07:23:32 +0200 Subject: [PATCH 048/111] =?UTF-8?q?=F0=9F=A4=96=20Auto=20commit=20for=20re?= =?UTF-8?q?lease=20'develop'=20on=202025-08-29?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Autocomplete/AccountController.php | 2 +- .../Autocomplete/BudgetController.php | 4 +- .../Autocomplete/RecurrenceController.php | 2 +- .../Autocomplete/RuleGroupController.php | 2 +- .../Controllers/Rule/SelectController.php | 3 +- .../RuleGroup/ExecutionController.php | 4 +- .../Transaction/ConvertController.php | 28 +- changelog.md | 6 +- composer.lock | 105 ++++---- config/firefly.php | 4 +- package-lock.json | 254 +++++++++--------- resources/assets/v1/src/locales/nl.json | 4 +- .../Autocomplete/RecurrenceControllerTest.php | 56 ++-- .../Api/Autocomplete/RuleControllerTest.php | 57 ++-- .../Autocomplete/RuleGroupControllerTest.php | 41 ++- .../Api/Autocomplete/TagControllerTest.php | 41 ++- 16 files changed, 310 insertions(+), 303 deletions(-) diff --git a/app/Api/V1/Controllers/Autocomplete/AccountController.php b/app/Api/V1/Controllers/Autocomplete/AccountController.php index bf1d7a3716..efd12f655e 100644 --- a/app/Api/V1/Controllers/Autocomplete/AccountController.php +++ b/app/Api/V1/Controllers/Autocomplete/AccountController.php @@ -114,7 +114,7 @@ class AccountController extends Controller 'id' => (string) $account->id, 'name' => $account->name, 'name_with_balance' => $nameWithBalance, - 'active' => $account->active, + 'active' => $account->active, 'type' => $account->accountType->type, 'currency_id' => (string) $useCurrency->id, 'currency_name' => $useCurrency->name, diff --git a/app/Api/V1/Controllers/Autocomplete/BudgetController.php b/app/Api/V1/Controllers/Autocomplete/BudgetController.php index f64500bf38..c292adc5b1 100644 --- a/app/Api/V1/Controllers/Autocomplete/BudgetController.php +++ b/app/Api/V1/Controllers/Autocomplete/BudgetController.php @@ -67,8 +67,8 @@ class BudgetController extends Controller $result = $this->repository->searchBudget($data['query'], $this->parameters->get('limit')); $filtered = $result->map( static fn (Budget $item) => [ - 'id' => (string) $item->id, - 'name' => $item->name, + 'id' => (string) $item->id, + 'name' => $item->name, 'active' => $item->active, ] ); diff --git a/app/Api/V1/Controllers/Autocomplete/RecurrenceController.php b/app/Api/V1/Controllers/Autocomplete/RecurrenceController.php index 2d76c0eb6f..202cf398f2 100644 --- a/app/Api/V1/Controllers/Autocomplete/RecurrenceController.php +++ b/app/Api/V1/Controllers/Autocomplete/RecurrenceController.php @@ -69,7 +69,7 @@ class RecurrenceController extends Controller 'id' => (string) $recurrence->id, 'name' => $recurrence->title, 'description' => $recurrence->description, - 'active' => $recurrence->active + 'active' => $recurrence->active, ]; } diff --git a/app/Api/V1/Controllers/Autocomplete/RuleGroupController.php b/app/Api/V1/Controllers/Autocomplete/RuleGroupController.php index 6882d76618..ff496d1e13 100644 --- a/app/Api/V1/Controllers/Autocomplete/RuleGroupController.php +++ b/app/Api/V1/Controllers/Autocomplete/RuleGroupController.php @@ -69,7 +69,7 @@ class RuleGroupController extends Controller 'id' => (string) $group->id, 'name' => $group->title, 'description' => $group->description, - 'active' => $group->active, + 'active' => $group->active, ]; } diff --git a/app/Http/Controllers/Rule/SelectController.php b/app/Http/Controllers/Rule/SelectController.php index 13a361a0f6..c266d41b21 100644 --- a/app/Http/Controllers/Rule/SelectController.php +++ b/app/Http/Controllers/Rule/SelectController.php @@ -25,7 +25,6 @@ declare(strict_types=1); namespace FireflyIII\Http\Controllers\Rule; use Throwable; -use Carbon\Carbon; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Requests\SelectTransactionsRequest; @@ -105,7 +104,7 @@ class SelectController extends Controller // does the user have shared accounts? $subTitle = (string) trans('firefly.apply_rule_selection', ['title' => $rule->title]); - return view('rules.rule.select-transactions', compact( 'rule', 'subTitle')); + return view('rules.rule.select-transactions', compact('rule', 'subTitle')); } /** diff --git a/app/Http/Controllers/RuleGroup/ExecutionController.php b/app/Http/Controllers/RuleGroup/ExecutionController.php index a8dc159ce9..9088b74442 100644 --- a/app/Http/Controllers/RuleGroup/ExecutionController.php +++ b/app/Http/Controllers/RuleGroup/ExecutionController.php @@ -25,11 +25,9 @@ declare(strict_types=1); namespace FireflyIII\Http\Controllers\RuleGroup; use Exception; -use Carbon\Carbon; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Requests\SelectTransactionsRequest; use FireflyIII\Models\RuleGroup; -use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface; use FireflyIII\TransactionRules\Engine\RuleEngineInterface; use FireflyIII\User; use Illuminate\Contracts\View\Factory; @@ -99,6 +97,6 @@ class ExecutionController extends Controller { $subTitle = (string) trans('firefly.apply_rule_group_selection', ['title' => $ruleGroup->title]); - return view('rules.rule-group.select-transactions', compact( 'ruleGroup', 'subTitle')); + return view('rules.rule-group.select-transactions', compact('ruleGroup', 'subTitle')); } } diff --git a/app/Http/Controllers/Transaction/ConvertController.php b/app/Http/Controllers/Transaction/ConvertController.php index e0a6229231..470048e852 100644 --- a/app/Http/Controllers/Transaction/ConvertController.php +++ b/app/Http/Controllers/Transaction/ConvertController.php @@ -303,22 +303,22 @@ class ConvertController extends Controller private function convertJournal(TransactionJournal $journal, TransactionType $transactionType, array $data): TransactionJournal { /** @var AccountValidator $validator */ - $validator = app(AccountValidator::class); + $validator = app(AccountValidator::class); $validator->setUser(auth()->user()); $validator->setTransactionType($transactionType->type); - $sourceId = $data['source_id'][$journal->id] ?? null; - $sourceName = $data['source_name'][$journal->id] ?? null; - $destinationId = $data['destination_id'][$journal->id] ?? null; - $destinationName = $data['destination_name'][$journal->id] ?? null; + $sourceId = $data['source_id'][$journal->id] ?? null; + $sourceName = $data['source_name'][$journal->id] ?? null; + $destinationId = $data['destination_id'][$journal->id] ?? null; + $destinationName = $data['destination_name'][$journal->id] ?? null; // double check it's not an empty string. - $sourceId = '' === $sourceId || null === $sourceId ? null : (int) $sourceId; - $sourceName = '' === $sourceName ? null : (string) $sourceName; - $destinationId = '' === $destinationId || null === $destinationId ? null : (int) $destinationId; - $destinationName = '' === $destinationName ? null : (string) $destinationName; - $validSource = $validator->validateSource(['id' => $sourceId, 'name' => $sourceName]); - $validDestination = $validator->validateDestination(['id' => $destinationId, 'name' => $destinationName]); + $sourceId = '' === $sourceId || null === $sourceId ? null : (int) $sourceId; + $sourceName = '' === $sourceName ? null : (string) $sourceName; + $destinationId = '' === $destinationId || null === $destinationId ? null : (int) $destinationId; + $destinationName = '' === $destinationName ? null : (string) $destinationName; + $validSource = $validator->validateSource(['id' => $sourceId, 'name' => $sourceName]); + $validDestination = $validator->validateDestination(['id' => $destinationId, 'name' => $destinationName]); if (false === $validSource) { throw new FireflyException(sprintf(trans('firefly.convert_invalid_source'), $journal->id)); @@ -329,7 +329,7 @@ class ConvertController extends Controller // TODO typeOverrule: the account validator may have another opinion on the transaction type. - $update = [ + $update = [ 'source_id' => $sourceId, 'source_name' => $sourceName, 'destination_id' => $destinationId, @@ -338,7 +338,7 @@ class ConvertController extends Controller ]; $sourceTransaction = $journal->transactions()->where('amount', '<', 0)->first(); - $amount = $sourceTransaction?->amount ?? '0'; + $amount = $sourceTransaction?->amount ?? '0'; // also set the currency to the currency of the source account, in case you're converting a deposit into a transfer. if (TransactionTypeEnum::TRANSFER->value === $transactionType->type && TransactionTypeEnum::DEPOSIT->value === $journal->transactionType->type) { @@ -367,7 +367,7 @@ class ConvertController extends Controller } /** @var JournalUpdateService $service */ - $service = app(JournalUpdateService::class); + $service = app(JournalUpdateService::class); $service->setTransactionJournal($journal); $service->setData($update); $service->update(); diff --git a/changelog.md b/changelog.md index 989f8cc33a..1baeb14101 100644 --- a/changelog.md +++ b/changelog.md @@ -25,9 +25,9 @@ This project adheres to [Semantic Versioning](http://semver.org/). - [Issue 10804](https://github.com/firefly-iii/firefly-iii/issues/10804) (No notes information included in the "List all accounts" API call) reported by @gpampuro - [Issue 10808](https://github.com/firefly-iii/firefly-iii/issues/10808) (cron job Error: Undefined variable $preference) reported by @MexerSam - [Issue 10813](https://github.com/firefly-iii/firefly-iii/issues/10813) (Error "Argument #2 ($symbol) must be of type string" while try open subscriptions section) reported by @mrResident -- #10819 -- #10820 -- #10824 +- [Issue 10819](https://github.com/firefly-iii/firefly-iii/issues/10819) (Internal Server Error when trying to open piggy banks) reported by @noantiq +- [Issue 10820](https://github.com/firefly-iii/firefly-iii/issues/10820) (Unable to search date 1970-01-01 to apply rule.) reported by @Kage1 +- [Issue 10824](https://github.com/firefly-iii/firefly-iii/issues/10824) (Converting withdrawal to transfer to account in different currency doesn't allow setting correct currencies) reported by @avee87 ### API diff --git a/composer.lock b/composer.lock index 1d6890ad5e..e1ef41cd56 100644 --- a/composer.lock +++ b/composer.lock @@ -1878,16 +1878,16 @@ }, { "name": "laravel/framework", - "version": "v12.25.0", + "version": "v12.26.3", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "2ee2ba94ae60efd24c7a787cbb1a2f82f714bb20" + "reference": "1a8e961a1801794c36c243bb610210d0a2bd61cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/2ee2ba94ae60efd24c7a787cbb1a2f82f714bb20", - "reference": "2ee2ba94ae60efd24c7a787cbb1a2f82f714bb20", + "url": "https://api.github.com/repos/laravel/framework/zipball/1a8e961a1801794c36c243bb610210d0a2bd61cb", + "reference": "1a8e961a1801794c36c243bb610210d0a2bd61cb", "shasum": "" }, "require": { @@ -1927,9 +1927,9 @@ "symfony/http-kernel": "^7.2.0", "symfony/mailer": "^7.2.0", "symfony/mime": "^7.2.0", - "symfony/polyfill-php83": "^1.31", - "symfony/polyfill-php84": "^1.31", - "symfony/polyfill-php85": "^1.31", + "symfony/polyfill-php83": "^1.33", + "symfony/polyfill-php84": "^1.33", + "symfony/polyfill-php85": "^1.33", "symfony/process": "^7.2.0", "symfony/routing": "^7.2.0", "symfony/uid": "^7.2.0", @@ -2091,7 +2091,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-08-18T22:20:52+00:00" + "time": "2025-08-27T13:51:06+00:00" }, { "name": "laravel/passport", @@ -5827,23 +5827,23 @@ }, { "name": "rcrowe/twigbridge", - "version": "v0.14.5", + "version": "v0.14.6", "source": { "type": "git", "url": "https://github.com/rcrowe/TwigBridge.git", - "reference": "88c83c9658a2c029c64ec80dd8a15d5a67433ac4" + "reference": "0798ee4b5e5b943d0200850acaa87ccd82e2fe45" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rcrowe/TwigBridge/zipball/88c83c9658a2c029c64ec80dd8a15d5a67433ac4", - "reference": "88c83c9658a2c029c64ec80dd8a15d5a67433ac4", + "url": "https://api.github.com/repos/rcrowe/TwigBridge/zipball/0798ee4b5e5b943d0200850acaa87ccd82e2fe45", + "reference": "0798ee4b5e5b943d0200850acaa87ccd82e2fe45", "shasum": "" }, "require": { "illuminate/support": "^9|^10|^11|^12", "illuminate/view": "^9|^10|^11|^12", "php": "^8.1", - "twig/twig": "~3.12" + "twig/twig": "~3.21" }, "require-dev": { "ext-json": "*", @@ -5893,22 +5893,22 @@ ], "support": { "issues": "https://github.com/rcrowe/TwigBridge/issues", - "source": "https://github.com/rcrowe/TwigBridge/tree/v0.14.5" + "source": "https://github.com/rcrowe/TwigBridge/tree/v0.14.6" }, - "time": "2025-04-18T18:48:57+00:00" + "time": "2025-08-20T11:25:49+00:00" }, { "name": "spatie/backtrace", - "version": "1.7.4", + "version": "1.8.1", "source": { "type": "git", "url": "https://github.com/spatie/backtrace.git", - "reference": "cd37a49fce7137359ac30ecc44ef3e16404cccbe" + "reference": "8c0f16a59ae35ec8c62d85c3c17585158f430110" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/backtrace/zipball/cd37a49fce7137359ac30ecc44ef3e16404cccbe", - "reference": "cd37a49fce7137359ac30ecc44ef3e16404cccbe", + "url": "https://api.github.com/repos/spatie/backtrace/zipball/8c0f16a59ae35ec8c62d85c3c17585158f430110", + "reference": "8c0f16a59ae35ec8c62d85c3c17585158f430110", "shasum": "" }, "require": { @@ -5946,7 +5946,8 @@ "spatie" ], "support": { - "source": "https://github.com/spatie/backtrace/tree/1.7.4" + "issues": "https://github.com/spatie/backtrace/issues", + "source": "https://github.com/spatie/backtrace/tree/1.8.1" }, "funding": [ { @@ -5958,7 +5959,7 @@ "type": "other" } ], - "time": "2025-05-08T15:41:09+00:00" + "time": "2025-08-26T08:22:30+00:00" }, { "name": "spatie/error-solutions", @@ -10726,16 +10727,16 @@ }, { "name": "larastan/larastan", - "version": "v3.6.0", + "version": "v3.6.1", "source": { "type": "git", "url": "https://github.com/larastan/larastan.git", - "reference": "6431d010dd383a9279eb8874a76ddb571738564a" + "reference": "3c223047e374befd1b64959784685d6ecccf66aa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/larastan/larastan/zipball/6431d010dd383a9279eb8874a76ddb571738564a", - "reference": "6431d010dd383a9279eb8874a76ddb571738564a", + "url": "https://api.github.com/repos/larastan/larastan/zipball/3c223047e374befd1b64959784685d6ecccf66aa", + "reference": "3c223047e374befd1b64959784685d6ecccf66aa", "shasum": "" }, "require": { @@ -10803,7 +10804,7 @@ ], "support": { "issues": "https://github.com/larastan/larastan/issues", - "source": "https://github.com/larastan/larastan/tree/v3.6.0" + "source": "https://github.com/larastan/larastan/tree/v3.6.1" }, "funding": [ { @@ -10811,7 +10812,7 @@ "type": "github" } ], - "time": "2025-07-11T06:52:52+00:00" + "time": "2025-08-25T07:24:56+00:00" }, { "name": "laravel-json-api/testing", @@ -11473,16 +11474,16 @@ }, { "name": "phpunit/php-code-coverage", - "version": "12.3.2", + "version": "12.3.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "086553c5b2e0e1e20293d782d788ab768202b621" + "reference": "733025d94635a001f67db71a2ed1bab4e7e4a9dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/086553c5b2e0e1e20293d782d788ab768202b621", - "reference": "086553c5b2e0e1e20293d782d788ab768202b621", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/733025d94635a001f67db71a2ed1bab4e7e4a9dc", + "reference": "733025d94635a001f67db71a2ed1bab4e7e4a9dc", "shasum": "" }, "require": { @@ -11538,7 +11539,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/12.3.2" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/12.3.3" }, "funding": [ { @@ -11558,7 +11559,7 @@ "type": "tidelift" } ], - "time": "2025-07-29T06:19:24+00:00" + "time": "2025-08-27T14:43:48+00:00" }, { "name": "phpunit/php-file-iterator", @@ -11807,16 +11808,16 @@ }, { "name": "phpunit/phpunit", - "version": "12.3.6", + "version": "12.3.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "a2cab3224f687150ac2f3cc13d99b64ba1e1d088" + "reference": "b8fa997c49682979ad6bfaa0d7fb25f54954965e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a2cab3224f687150ac2f3cc13d99b64ba1e1d088", - "reference": "a2cab3224f687150ac2f3cc13d99b64ba1e1d088", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b8fa997c49682979ad6bfaa0d7fb25f54954965e", + "reference": "b8fa997c49682979ad6bfaa0d7fb25f54954965e", "shasum": "" }, "require": { @@ -11830,7 +11831,7 @@ "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=8.3", - "phpunit/php-code-coverage": "^12.3.2", + "phpunit/php-code-coverage": "^12.3.3", "phpunit/php-file-iterator": "^6.0.0", "phpunit/php-invoker": "^6.0.0", "phpunit/php-text-template": "^5.0.0", @@ -11884,7 +11885,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.6" + "source": "https://github.com/sebastianbergmann/phpunit/tree/12.3.7" }, "funding": [ { @@ -11908,7 +11909,7 @@ "type": "tidelift" } ], - "time": "2025-08-20T14:43:23+00:00" + "time": "2025-08-28T05:15:46+00:00" }, { "name": "rector/rector", @@ -12400,16 +12401,16 @@ }, { "name": "sebastian/global-state", - "version": "8.0.0", + "version": "8.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "570a2aeb26d40f057af686d63c4e99b075fb6cbc" + "reference": "912dd568677a6e13c67c08321710ad6ac81e6dca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/570a2aeb26d40f057af686d63c4e99b075fb6cbc", - "reference": "570a2aeb26d40f057af686d63c4e99b075fb6cbc", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/912dd568677a6e13c67c08321710ad6ac81e6dca", + "reference": "912dd568677a6e13c67c08321710ad6ac81e6dca", "shasum": "" }, "require": { @@ -12450,15 +12451,27 @@ "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", "security": "https://github.com/sebastianbergmann/global-state/security/policy", - "source": "https://github.com/sebastianbergmann/global-state/tree/8.0.0" + "source": "https://github.com/sebastianbergmann/global-state/tree/8.0.1" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/global-state", + "type": "tidelift" } ], - "time": "2025-02-07T04:56:59+00:00" + "time": "2025-08-28T09:13:48+00:00" }, { "name": "sebastian/lines-of-code", diff --git a/config/firefly.php b/config/firefly.php index 51495da795..7b51dc5b64 100644 --- a/config/firefly.php +++ b/config/firefly.php @@ -78,8 +78,8 @@ return [ 'running_balance_column' => env('USE_RUNNING_BALANCE', false), // see cer.php for exchange rates feature flag. ], - 'version' => 'develop/2025-08-25', - 'build_time' => 1756092349, + 'version' => 'develop/2025-08-29', + 'build_time' => 1756444897, 'api_version' => '2.1.0', // field is no longer used. 'db_version' => 26, diff --git a/package-lock.json b/package-lock.json index 3e58210c6e..6723252e30 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2592,9 +2592,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.48.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.48.0.tgz", - "integrity": "sha512-aVzKH922ogVAWkKiyKXorjYymz2084zrhrZRXtLrA5eEx5SO8Dj0c/4FpCHZyn7MKzhW2pW4tK28vVr+5oQ2xw==", + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.49.0.tgz", + "integrity": "sha512-rlKIeL854Ed0e09QGYFlmDNbka6I3EQFw7iZuugQjMb11KMpJCLPFL4ZPbMfaEhLADEL1yx0oujGkBQ7+qW3eA==", "cpu": [ "arm" ], @@ -2606,9 +2606,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.48.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.48.0.tgz", - "integrity": "sha512-diOdQuw43xTa1RddAFbhIA8toirSzFMcnIg8kvlzRbK26xqEnKJ/vqQnghTAajy2Dcy42v+GMPMo6jq67od+Dw==", + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.49.0.tgz", + "integrity": "sha512-cqPpZdKUSQYRtLLr6R4X3sD4jCBO1zUmeo3qrWBCqYIeH8Q3KRL4F3V7XJ2Rm8/RJOQBZuqzQGWPjjvFUcYa/w==", "cpu": [ "arm64" ], @@ -2620,9 +2620,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.48.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.48.0.tgz", - "integrity": "sha512-QhR2KA18fPlJWFefySJPDYZELaVqIUVnYgAOdtJ+B/uH96CFg2l1TQpX19XpUMWUqMyIiyY45wje8K6F4w4/CA==", + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.49.0.tgz", + "integrity": "sha512-99kMMSMQT7got6iYX3yyIiJfFndpojBmkHfTc1rIje8VbjhmqBXE+nb7ZZP3A5skLyujvT0eIUCUsxAe6NjWbw==", "cpu": [ "arm64" ], @@ -2634,9 +2634,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.48.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.48.0.tgz", - "integrity": "sha512-Q9RMXnQVJ5S1SYpNSTwXDpoQLgJ/fbInWOyjbCnnqTElEyeNvLAB3QvG5xmMQMhFN74bB5ZZJYkKaFPcOG8sGg==", + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.49.0.tgz", + "integrity": "sha512-y8cXoD3wdWUDpjOLMKLx6l+NFz3NlkWKcBCBfttUn+VGSfgsQ5o/yDUGtzE9HvsodkP0+16N0P4Ty1VuhtRUGg==", "cpu": [ "x64" ], @@ -2648,9 +2648,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.48.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.48.0.tgz", - "integrity": "sha512-3jzOhHWM8O8PSfyft+ghXZfBkZawQA0PUGtadKYxFqpcYlOYjTi06WsnYBsbMHLawr+4uWirLlbhcYLHDXR16w==", + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.49.0.tgz", + "integrity": "sha512-3mY5Pr7qv4GS4ZvWoSP8zha8YoiqrU+e0ViPvB549jvliBbdNLrg2ywPGkgLC3cmvN8ya3za+Q2xVyT6z+vZqA==", "cpu": [ "arm64" ], @@ -2662,9 +2662,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.48.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.48.0.tgz", - "integrity": "sha512-NcD5uVUmE73C/TPJqf78hInZmiSBsDpz3iD5MF/BuB+qzm4ooF2S1HfeTChj5K4AV3y19FFPgxonsxiEpy8v/A==", + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.49.0.tgz", + "integrity": "sha512-C9KzzOAQU5gU4kG8DTk+tjdKjpWhVWd5uVkinCwwFub2m7cDYLOdtXoMrExfeBmeRy9kBQMkiyJ+HULyF1yj9w==", "cpu": [ "x64" ], @@ -2676,9 +2676,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.48.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.48.0.tgz", - "integrity": "sha512-JWnrj8qZgLWRNHr7NbpdnrQ8kcg09EBBq8jVOjmtlB3c8C6IrynAJSMhMVGME4YfTJzIkJqvSUSVJRqkDnu/aA==", + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.49.0.tgz", + "integrity": "sha512-OVSQgEZDVLnTbMq5NBs6xkmz3AADByCWI4RdKSFNlDsYXdFtlxS59J+w+LippJe8KcmeSSM3ba+GlsM9+WwC1w==", "cpu": [ "arm" ], @@ -2690,9 +2690,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.48.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.48.0.tgz", - "integrity": "sha512-9xu92F0TxuMH0tD6tG3+GtngwdgSf8Bnz+YcsPG91/r5Vgh5LNofO48jV55priA95p3c92FLmPM7CvsVlnSbGQ==", + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.49.0.tgz", + "integrity": "sha512-ZnfSFA7fDUHNa4P3VwAcfaBLakCbYaxCk0jUnS3dTou9P95kwoOLAMlT3WmEJDBCSrOEFFV0Y1HXiwfLYJuLlA==", "cpu": [ "arm" ], @@ -2704,9 +2704,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.48.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.48.0.tgz", - "integrity": "sha512-NLtvJB5YpWn7jlp1rJiY0s+G1Z1IVmkDuiywiqUhh96MIraC0n7XQc2SZ1CZz14shqkM+XN2UrfIo7JB6UufOA==", + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.49.0.tgz", + "integrity": "sha512-Z81u+gfrobVK2iV7GqZCBfEB1y6+I61AH466lNK+xy1jfqFLiQ9Qv716WUM5fxFrYxwC7ziVdZRU9qvGHkYIJg==", "cpu": [ "arm64" ], @@ -2718,9 +2718,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.48.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.48.0.tgz", - "integrity": "sha512-QJ4hCOnz2SXgCh+HmpvZkM+0NSGcZACyYS8DGbWn2PbmA0e5xUk4bIP8eqJyNXLtyB4gZ3/XyvKtQ1IFH671vQ==", + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.49.0.tgz", + "integrity": "sha512-zoAwS0KCXSnTp9NH/h9aamBAIve0DXeYpll85shf9NJ0URjSTzzS+Z9evmolN+ICfD3v8skKUPyk2PO0uGdFqg==", "cpu": [ "arm64" ], @@ -2732,9 +2732,9 @@ ] }, "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.48.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.48.0.tgz", - "integrity": "sha512-Pk0qlGJnhILdIC5zSKQnprFjrGmjfDM7TPZ0FKJxRkoo+kgMRAg4ps1VlTZf8u2vohSicLg7NP+cA5qE96PaFg==", + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.49.0.tgz", + "integrity": "sha512-2QyUyQQ1ZtwZGiq0nvODL+vLJBtciItC3/5cYN8ncDQcv5avrt2MbKt1XU/vFAJlLta5KujqyHdYtdag4YEjYQ==", "cpu": [ "loong64" ], @@ -2746,9 +2746,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.48.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.48.0.tgz", - "integrity": "sha512-/dNFc6rTpoOzgp5GKoYjT6uLo8okR/Chi2ECOmCZiS4oqh3mc95pThWma7Bgyk6/WTEvjDINpiBCuecPLOgBLQ==", + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.49.0.tgz", + "integrity": "sha512-k9aEmOWt+mrMuD3skjVJSSxHckJp+SiFzFG+v8JLXbc/xi9hv2icSkR3U7uQzqy+/QbbYY7iNB9eDTwrELo14g==", "cpu": [ "ppc64" ], @@ -2760,9 +2760,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.48.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.48.0.tgz", - "integrity": "sha512-YBwXsvsFI8CVA4ej+bJF2d9uAeIiSkqKSPQNn0Wyh4eMDY4wxuSp71BauPjQNCKK2tD2/ksJ7uhJ8X/PVY9bHQ==", + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.49.0.tgz", + "integrity": "sha512-rDKRFFIWJ/zJn6uk2IdYLc09Z7zkE5IFIOWqpuU0o6ZpHcdniAyWkwSUWE/Z25N/wNDmFHHMzin84qW7Wzkjsw==", "cpu": [ "riscv64" ], @@ -2774,9 +2774,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.48.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.48.0.tgz", - "integrity": "sha512-FI3Rr2aGAtl1aHzbkBIamsQyuauYtTF9SDUJ8n2wMXuuxwchC3QkumZa1TEXYIv/1AUp1a25Kwy6ONArvnyeVQ==", + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.49.0.tgz", + "integrity": "sha512-FkkhIY/hYFVnOzz1WeV3S9Bd1h0hda/gRqvZCMpHWDHdiIHn6pqsY3b5eSbvGccWHMQ1uUzgZTKS4oGpykf8Tw==", "cpu": [ "riscv64" ], @@ -2788,9 +2788,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.48.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.48.0.tgz", - "integrity": "sha512-Dx7qH0/rvNNFmCcIRe1pyQ9/H0XO4v/f0SDoafwRYwc2J7bJZ5N4CHL/cdjamISZ5Cgnon6iazAVRFlxSoHQnQ==", + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.49.0.tgz", + "integrity": "sha512-gRf5c+A7QiOG3UwLyOOtyJMD31JJhMjBvpfhAitPAoqZFcOeK3Kc1Veg1z/trmt+2P6F/biT02fU19GGTS529A==", "cpu": [ "s390x" ], @@ -2802,9 +2802,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.48.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.48.0.tgz", - "integrity": "sha512-GUdZKTeKBq9WmEBzvFYuC88yk26vT66lQV8D5+9TgkfbewhLaTHRNATyzpQwwbHIfJvDJ3N9WJ90wK/uR3cy3Q==", + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.49.0.tgz", + "integrity": "sha512-BR7+blScdLW1h/2hB/2oXM+dhTmpW3rQt1DeSiCP9mc2NMMkqVgjIN3DDsNpKmezffGC9R8XKVOLmBkRUcK/sA==", "cpu": [ "x64" ], @@ -2816,9 +2816,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.48.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.48.0.tgz", - "integrity": "sha512-ao58Adz/v14MWpQgYAb4a4h3fdw73DrDGtaiF7Opds5wNyEQwtO6M9dBh89nke0yoZzzaegq6J/EXs7eBebG8A==", + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.49.0.tgz", + "integrity": "sha512-hDMOAe+6nX3V5ei1I7Au3wcr9h3ktKzDvF2ne5ovX8RZiAHEtX1A5SNNk4zt1Qt77CmnbqT+upb/umzoPMWiPg==", "cpu": [ "x64" ], @@ -2830,9 +2830,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.48.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.48.0.tgz", - "integrity": "sha512-kpFno46bHtjZVdRIOxqaGeiABiToo2J+st7Yce+aiAoo1H0xPi2keyQIP04n2JjDVuxBN6bSz9R6RdTK5hIppw==", + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.49.0.tgz", + "integrity": "sha512-wkNRzfiIGaElC9kXUT+HLx17z7D0jl+9tGYRKwd8r7cUqTL7GYAvgUY++U2hK6Ar7z5Z6IRRoWC8kQxpmM7TDA==", "cpu": [ "arm64" ], @@ -2844,9 +2844,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.48.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.48.0.tgz", - "integrity": "sha512-rFYrk4lLk9YUTIeihnQMiwMr6gDhGGSbWThPEDfBoU/HdAtOzPXeexKi7yU8jO+LWRKnmqPN9NviHQf6GDwBcQ==", + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.49.0.tgz", + "integrity": "sha512-gq5aW/SyNpjp71AAzroH37DtINDcX1Qw2iv9Chyz49ZgdOP3NV8QCyKZUrGsYX9Yyggj5soFiRCgsL3HwD8TdA==", "cpu": [ "ia32" ], @@ -2858,9 +2858,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.48.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.48.0.tgz", - "integrity": "sha512-sq0hHLTgdtwOPDB5SJOuaoHyiP1qSwg+71TQWk8iDS04bW1wIE0oQ6otPiRj2ZvLYNASLMaTp8QRGUVZ+5OL5A==", + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.49.0.tgz", + "integrity": "sha512-gEtqFbzmZLFk2xKh7g0Rlo8xzho8KrEFEkzvHbfUGkrgXOpZ4XagQ6n+wIZFNh1nTb8UD16J4nFSFKXYgnbdBg==", "cpu": [ "x64" ], @@ -3256,42 +3256,42 @@ } }, "node_modules/@vue/compiler-core": { - "version": "3.5.19", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.19.tgz", - "integrity": "sha512-/afpyvlkrSNYbPo94Qu8GtIOWS+g5TRdOvs6XZNw6pWQQmj5pBgSZvEPOIZlqWq0YvoUhDDQaQ2TnzuJdOV4hA==", + "version": "3.5.20", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.20.tgz", + "integrity": "sha512-8TWXUyiqFd3GmP4JTX9hbiTFRwYHgVL/vr3cqhr4YQ258+9FADwvj7golk2sWNGHR67QgmCZ8gz80nQcMokhwg==", "dev": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.28.3", - "@vue/shared": "3.5.19", + "@vue/shared": "3.5.20", "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "node_modules/@vue/compiler-dom": { - "version": "3.5.19", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.19.tgz", - "integrity": "sha512-Drs6rPHQZx/pN9S6ml3Z3K/TWCIRPvzG2B/o5kFK9X0MNHt8/E+38tiRfojufrYBfA6FQUFB2qBBRXlcSXWtOA==", + "version": "3.5.20", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.20.tgz", + "integrity": "sha512-whB44M59XKjqUEYOMPYU0ijUV0G+4fdrHVKDe32abNdX/kJe1NUEMqsi4cwzXa9kyM9w5S8WqFsrfo1ogtBZGQ==", "dev": true, "license": "MIT", "dependencies": { - "@vue/compiler-core": "3.5.19", - "@vue/shared": "3.5.19" + "@vue/compiler-core": "3.5.20", + "@vue/shared": "3.5.20" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.5.19", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.19.tgz", - "integrity": "sha512-YWCm1CYaJ+2RvNmhCwI7t3I3nU+hOrWGWMsn+Z/kmm1jy5iinnVtlmkiZwbLlbV1SRizX7vHsc0/bG5dj0zRTg==", + "version": "3.5.20", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.20.tgz", + "integrity": "sha512-SFcxapQc0/feWiSBfkGsa1v4DOrnMAQSYuvDMpEaxbpH5dKbnEM5KobSNSgU+1MbHCl+9ftm7oQWxvwDB6iBfw==", "dev": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.28.3", - "@vue/compiler-core": "3.5.19", - "@vue/compiler-dom": "3.5.19", - "@vue/compiler-ssr": "3.5.19", - "@vue/shared": "3.5.19", + "@vue/compiler-core": "3.5.20", + "@vue/compiler-dom": "3.5.20", + "@vue/compiler-ssr": "3.5.20", + "@vue/shared": "3.5.20", "estree-walker": "^2.0.2", "magic-string": "^0.30.17", "postcss": "^8.5.6", @@ -3299,14 +3299,14 @@ } }, "node_modules/@vue/compiler-ssr": { - "version": "3.5.19", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.19.tgz", - "integrity": "sha512-/wx0VZtkWOPdiQLWPeQeqpHWR/LuNC7bHfSX7OayBTtUy8wur6vT6EQIX6Et86aED6J+y8tTw43qo2uoqGg5sw==", + "version": "3.5.20", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.20.tgz", + "integrity": "sha512-RSl5XAMc5YFUXpDQi+UQDdVjH9FnEpLDHIALg5J0ITHxkEzJ8uQLlo7CIbjPYqmZtt6w0TsIPbo1izYXwDG7JA==", "dev": true, "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.5.19", - "@vue/shared": "3.5.19" + "@vue/compiler-dom": "3.5.20", + "@vue/shared": "3.5.20" } }, "node_modules/@vue/component-compiler-utils": { @@ -3388,9 +3388,9 @@ "license": "MIT" }, "node_modules/@vue/shared": { - "version": "3.5.19", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.19.tgz", - "integrity": "sha512-IhXCOn08wgKrLQxRFKKlSacWg4Goi1BolrdEeLYn6tgHjJNXVrWJ5nzoxZqNwl5p88aLlQ8LOaoMa3AYvaKJ/Q==", + "version": "3.5.20", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.20.tgz", + "integrity": "sha512-SoRGP596KU/ig6TfgkCMbXkr4YJ91n/QSdMuqeP5r3hVIYA3CPHUBCc7Skak0EAKV+5lL4KyIh61VA/pK1CIAA==", "dev": true, "license": "MIT" }, @@ -4170,9 +4170,9 @@ "license": "ISC" }, "node_modules/bootstrap": { - "version": "5.3.7", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.7.tgz", - "integrity": "sha512-7KgiD8UHjfcPBHEpDNg+zGz8L3LqR3GVwqZiBRFX04a1BCArZOz1r2kjly2HQ0WokqTO0v1nF+QAt8dsW4lKlw==", + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.8.tgz", + "integrity": "sha512-HP1SZDqaLDPwsNiqRqi5NcP0SSXciX2s9E+RyqJIIqGo+vJeN5AJVM98CXmW/Wux0nQ5L7jeWUdplCEf0Ee+tg==", "funding": [ { "type": "github", @@ -4326,9 +4326,9 @@ } }, "node_modules/browserslist": { - "version": "4.25.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.3.tgz", - "integrity": "sha512-cDGv1kkDI4/0e5yON9yM5G/0A5u8sf5TnmdX5C9qHzI9PPu++sQ9zjm1k9NiOrf3riY4OkK0zSGqfvJyJsgCBQ==", + "version": "4.25.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.4.tgz", + "integrity": "sha512-4jYpcjabC606xJ3kw2QwGEZKX0Aw7sgQdZCvIK9dhVSPh76BKo+C+btT1RRofH7B+8iNpEbgGNVWiLki5q93yg==", "dev": true, "funding": [ { @@ -4346,8 +4346,8 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001735", - "electron-to-chromium": "^1.5.204", + "caniuse-lite": "^1.0.30001737", + "electron-to-chromium": "^1.5.211", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.3" }, @@ -5700,9 +5700,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.208", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.208.tgz", - "integrity": "sha512-ozZyibehoe7tOhNaf16lKmljVf+3npZcJIEbJRVftVsmAg5TeA1mGS9dVCZzOwr2xT7xK15V0p7+GZqSPgkuPg==", + "version": "1.5.211", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.211.tgz", + "integrity": "sha512-IGBvimJkotaLzFnwIVgW9/UD/AOJ2tByUmeOrtqBfACSbAw5b1G0XpvdaieKyc7ULmbwXVx+4e4Be8pOPBrYkw==", "dev": true, "license": "ISC" }, @@ -6159,9 +6159,9 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", - "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", "dev": true, "funding": [ { @@ -7818,9 +7818,9 @@ } }, "node_modules/laravel-vite-plugin": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-2.0.0.tgz", - "integrity": "sha512-pnaKHInJgiWpG/g+LmaISHl7D/1s5wnOXnrGiBdt4NOs+tYZRw0v/ZANELGX2/dGgHyEzO+iZ6x4idpoK04z/Q==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-2.0.1.tgz", + "integrity": "sha512-zQuvzWfUKQu9oNVi1o0RZAJCwhGsdhx4NEOyrVQwJHaWDseGP9tl7XUPLY2T8Cj6+IrZ6lmyxlR1KC8unf3RLA==", "dev": true, "license": "MIT", "dependencies": { @@ -10123,9 +10123,9 @@ } }, "node_modules/rollup": { - "version": "4.48.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.48.0.tgz", - "integrity": "sha512-BXHRqK1vyt9XVSEHZ9y7xdYtuYbwVod2mLwOMFP7t/Eqoc1pHRlG/WdV2qNeNvZHRQdLedaFycljaYYM96RqJQ==", + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.49.0.tgz", + "integrity": "sha512-3IVq0cGJ6H7fKXXEdVt+RcYvRCt8beYY9K1760wGQwSAHZcS9eot1zDG5axUbcp/kWRi5zKIIDX8MoKv/TzvZA==", "dev": true, "license": "MIT", "dependencies": { @@ -10139,26 +10139,26 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.48.0", - "@rollup/rollup-android-arm64": "4.48.0", - "@rollup/rollup-darwin-arm64": "4.48.0", - "@rollup/rollup-darwin-x64": "4.48.0", - "@rollup/rollup-freebsd-arm64": "4.48.0", - "@rollup/rollup-freebsd-x64": "4.48.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.48.0", - "@rollup/rollup-linux-arm-musleabihf": "4.48.0", - "@rollup/rollup-linux-arm64-gnu": "4.48.0", - "@rollup/rollup-linux-arm64-musl": "4.48.0", - "@rollup/rollup-linux-loongarch64-gnu": "4.48.0", - "@rollup/rollup-linux-ppc64-gnu": "4.48.0", - "@rollup/rollup-linux-riscv64-gnu": "4.48.0", - "@rollup/rollup-linux-riscv64-musl": "4.48.0", - "@rollup/rollup-linux-s390x-gnu": "4.48.0", - "@rollup/rollup-linux-x64-gnu": "4.48.0", - "@rollup/rollup-linux-x64-musl": "4.48.0", - "@rollup/rollup-win32-arm64-msvc": "4.48.0", - "@rollup/rollup-win32-ia32-msvc": "4.48.0", - "@rollup/rollup-win32-x64-msvc": "4.48.0", + "@rollup/rollup-android-arm-eabi": "4.49.0", + "@rollup/rollup-android-arm64": "4.49.0", + "@rollup/rollup-darwin-arm64": "4.49.0", + "@rollup/rollup-darwin-x64": "4.49.0", + "@rollup/rollup-freebsd-arm64": "4.49.0", + "@rollup/rollup-freebsd-x64": "4.49.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.49.0", + "@rollup/rollup-linux-arm-musleabihf": "4.49.0", + "@rollup/rollup-linux-arm64-gnu": "4.49.0", + "@rollup/rollup-linux-arm64-musl": "4.49.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.49.0", + "@rollup/rollup-linux-ppc64-gnu": "4.49.0", + "@rollup/rollup-linux-riscv64-gnu": "4.49.0", + "@rollup/rollup-linux-riscv64-musl": "4.49.0", + "@rollup/rollup-linux-s390x-gnu": "4.49.0", + "@rollup/rollup-linux-x64-gnu": "4.49.0", + "@rollup/rollup-linux-x64-musl": "4.49.0", + "@rollup/rollup-win32-arm64-msvc": "4.49.0", + "@rollup/rollup-win32-ia32-msvc": "4.49.0", + "@rollup/rollup-win32-x64-msvc": "4.49.0", "fsevents": "~2.3.2" } }, @@ -10214,9 +10214,9 @@ "license": "MIT" }, "node_modules/sass": { - "version": "1.90.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.90.0.tgz", - "integrity": "sha512-9GUyuksjw70uNpb1MTYWsH9MQHOHY6kwfnkafC24+7aOMZn9+rVMBxRbLvw756mrBFbIsFg6Xw9IkR2Fnn3k+Q==", + "version": "1.91.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.91.0.tgz", + "integrity": "sha512-aFOZHGf+ur+bp1bCHZ+u8otKGh77ZtmFyXDo4tlYvT7PWql41Kwd8wdkPqhhT+h2879IVblcHFglIMofsFd1EA==", "dev": true, "license": "MIT", "dependencies": { diff --git a/resources/assets/v1/src/locales/nl.json b/resources/assets/v1/src/locales/nl.json index 32a8620af1..9753958d94 100644 --- a/resources/assets/v1/src/locales/nl.json +++ b/resources/assets/v1/src/locales/nl.json @@ -160,7 +160,7 @@ "url": "URL", "active": "Actief", "interest_date": "Rentedatum", - "administration_currency": "Primary currency", + "administration_currency": "Standaardvaluta", "title": "Titel", "date": "Datum", "book_date": "Boekdatum", @@ -180,7 +180,7 @@ "list": { "title": "Titel", "active": "Actief?", - "primary_currency": "Primary currency", + "primary_currency": "Standaardvaluta", "trigger": "Trigger", "response": "Reactie", "delivery": "Bericht", diff --git a/tests/integration/Api/Autocomplete/RecurrenceControllerTest.php b/tests/integration/Api/Autocomplete/RecurrenceControllerTest.php index 32d5cae547..4eee2178ac 100644 --- a/tests/integration/Api/Autocomplete/RecurrenceControllerTest.php +++ b/tests/integration/Api/Autocomplete/RecurrenceControllerTest.php @@ -47,17 +47,17 @@ final class RecurrenceControllerTest extends TestCase { for ($i = 1; $i <= $count; ++$i) { $recurrence = Recurrence::create([ - 'user_id' => $user->id, - 'user_group_id' => $user->user_group_id, - 'transaction_type_id' => 1, - 'title' => 'Recurrence ' . $i, - 'description' => 'Recurrence ' . $i, - 'first_date' => today(), - 'apply_rules' => 1, - 'active' => 1, - 'repetitions' => 5, + 'user_id' => $user->id, + 'user_group_id' => $user->user_group_id, + 'transaction_type_id' => 1, + 'title' => 'Recurrence '.$i, + 'description' => 'Recurrence '.$i, + 'first_date' => today(), + 'apply_rules' => 1, + 'active' => 1, + 'repetitions' => 5, - ]); + ]); } } @@ -73,7 +73,7 @@ final class RecurrenceControllerTest extends TestCase public function testAuthenticatedCall(): void { // act as a user - $user = $this->createAuthenticatedUser(); + $user = $this->createAuthenticatedUser(); $this->actingAs($user); $response = $this->get(route('api.v1.autocomplete.recurring'), ['Accept' => 'application/json']); @@ -83,7 +83,7 @@ final class RecurrenceControllerTest extends TestCase public function testGivenAuthenticatedRequestWithItems(): void { - $user = $this->createAuthenticatedUser(); + $user = $this->createAuthenticatedUser(); $this->actingAs($user); $this->createTestRecurrences(5, $user); @@ -93,50 +93,50 @@ final class RecurrenceControllerTest extends TestCase $response->assertJsonCount(5); $response->assertJsonFragment(['name' => 'Recurrence 1']); $response->assertJsonStructure([ - '*' => [ - 'id', - 'name', - 'active', - ], - ]); + '*' => [ + 'id', + 'name', + 'active', + ], + ]); } public function testGivenAuthenticatedRequestWithItemsLimited(): void { - $user = $this->createAuthenticatedUser(); + $user = $this->createAuthenticatedUser(); $this->actingAs($user); $this->createTestRecurrences(5, $user); $response = $this->get(route('api.v1.autocomplete.recurring', [ 'query' => 'Recurrence', 'limit' => 3, - ]), ['Accept' => 'application/json']); + ]), ['Accept' => 'application/json']); $response->assertStatus(200); $response->assertHeader('Content-Type', 'application/json'); $response->assertJsonCount(3); $response->assertJsonFragment(['name' => 'Recurrence 1']); $response->assertJsonStructure([ - '*' => [ - 'id', - 'name', - 'active', - ], - ]); + '*' => [ + 'id', + 'name', + 'active', + ], + ]); } public function testGivenAuthenticatedRequestWithItemsLots(): void { - $user = $this->createAuthenticatedUser(); + $user = $this->createAuthenticatedUser(); $this->actingAs($user); $this->createTestRecurrences(20, $user); $response = $this->get(route('api.v1.autocomplete.recurring', [ 'query' => 'Recurrence 1', 'limit' => 20, - ]), ['Accept' => 'application/json']); + ]), ['Accept' => 'application/json']); $response->assertStatus(200); $response->assertHeader('Content-Type', 'application/json'); diff --git a/tests/integration/Api/Autocomplete/RuleControllerTest.php b/tests/integration/Api/Autocomplete/RuleControllerTest.php index 3e650adf12..7edba01b43 100644 --- a/tests/integration/Api/Autocomplete/RuleControllerTest.php +++ b/tests/integration/Api/Autocomplete/RuleControllerTest.php @@ -24,7 +24,6 @@ declare(strict_types=1); namespace Tests\integration\Api\Autocomplete; -use FireflyIII\Models\Recurrence; use FireflyIII\Models\Rule; use FireflyIII\Models\RuleGroup; use FireflyIII\User; @@ -58,16 +57,16 @@ final class RuleControllerTest extends TestCase ); for ($i = 1; $i <= $count; ++$i) { $rule = Rule::create([ - 'user_id' => $user->id, - 'user_group_id' => $user->user_group_id, - 'rule_group_id' => $ruleGroup->id, - 'title' => 'Rule ' . $i, - 'description' => 'Rule ' . $i, - 'order' => 1, - 'active' => 1, - 'stop_processing' => 0, - 'strict' => 0, - ]); + 'user_id' => $user->id, + 'user_group_id' => $user->user_group_id, + 'rule_group_id' => $ruleGroup->id, + 'title' => 'Rule '.$i, + 'description' => 'Rule '.$i, + 'order' => 1, + 'active' => 1, + 'stop_processing' => 0, + 'strict' => 0, + ]); } } @@ -83,7 +82,7 @@ final class RuleControllerTest extends TestCase public function testAuthenticatedCall(): void { // act as a user - $user = $this->createAuthenticatedUser(); + $user = $this->createAuthenticatedUser(); $this->actingAs($user); $response = $this->get(route('api.v1.autocomplete.rules'), ['Accept' => 'application/json']); @@ -93,7 +92,7 @@ final class RuleControllerTest extends TestCase public function testGivenAuthenticatedRequestWithItems(): void { - $user = $this->createAuthenticatedUser(); + $user = $this->createAuthenticatedUser(); $this->actingAs($user); $this->createTestRules(5, $user); @@ -103,50 +102,50 @@ final class RuleControllerTest extends TestCase $response->assertJsonCount(5); $response->assertJsonFragment(['name' => 'Rule 1']); $response->assertJsonStructure([ - '*' => [ - 'id', - 'name', - 'active', - ], - ]); + '*' => [ + 'id', + 'name', + 'active', + ], + ]); } public function testGivenAuthenticatedRequestWithItemsLimited(): void { - $user = $this->createAuthenticatedUser(); + $user = $this->createAuthenticatedUser(); $this->actingAs($user); $this->createTestRules(5, $user); $response = $this->get(route('api.v1.autocomplete.rules', [ 'query' => 'Rule', 'limit' => 3, - ]), ['Accept' => 'application/json']); + ]), ['Accept' => 'application/json']); $response->assertStatus(200); $response->assertHeader('Content-Type', 'application/json'); $response->assertJsonCount(3); $response->assertJsonFragment(['name' => 'Rule 1']); $response->assertJsonStructure([ - '*' => [ - 'id', - 'name', - 'active', - ], - ]); + '*' => [ + 'id', + 'name', + 'active', + ], + ]); } public function testGivenAuthenticatedRequestWithItemsLots(): void { - $user = $this->createAuthenticatedUser(); + $user = $this->createAuthenticatedUser(); $this->actingAs($user); $this->createTestRules(20, $user); $response = $this->get(route('api.v1.autocomplete.rules', [ 'query' => 'Rule 1', 'limit' => 20, - ]), ['Accept' => 'application/json']); + ]), ['Accept' => 'application/json']); $response->assertStatus(200); $response->assertHeader('Content-Type', 'application/json'); diff --git a/tests/integration/Api/Autocomplete/RuleGroupControllerTest.php b/tests/integration/Api/Autocomplete/RuleGroupControllerTest.php index 570adaad5f..3e4aa26e5d 100644 --- a/tests/integration/Api/Autocomplete/RuleGroupControllerTest.php +++ b/tests/integration/Api/Autocomplete/RuleGroupControllerTest.php @@ -24,7 +24,6 @@ declare(strict_types=1); namespace Tests\integration\Api\Autocomplete; -use FireflyIII\Models\Rule; use FireflyIII\Models\RuleGroup; use FireflyIII\User; use Illuminate\Foundation\Testing\RefreshDatabase; @@ -50,8 +49,8 @@ final class RuleGroupControllerTest extends TestCase [ 'user_id' => $user->id, 'user_group_id' => $user->user_group_id, - 'title' => 'RuleGroup ' . $i, - 'description' => 'RuleGroup '. $i, + 'title' => 'RuleGroup '.$i, + 'description' => 'RuleGroup '.$i, 'order' => 1, 'active' => 1, 'stop_processing' => 0, @@ -72,7 +71,7 @@ final class RuleGroupControllerTest extends TestCase public function testAuthenticatedCall(): void { // act as a user - $user = $this->createAuthenticatedUser(); + $user = $this->createAuthenticatedUser(); $this->actingAs($user); $response = $this->get(route('api.v1.autocomplete.rule-groups'), ['Accept' => 'application/json']); @@ -82,7 +81,7 @@ final class RuleGroupControllerTest extends TestCase public function testGivenAuthenticatedRequestWithItems(): void { - $user = $this->createAuthenticatedUser(); + $user = $this->createAuthenticatedUser(); $this->actingAs($user); $this->createTestRuleGroups(5, $user); @@ -92,50 +91,50 @@ final class RuleGroupControllerTest extends TestCase $response->assertJsonCount(5); $response->assertJsonFragment(['name' => 'RuleGroup 1']); $response->assertJsonStructure([ - '*' => [ - 'id', - 'name', - 'active', - ], - ]); + '*' => [ + 'id', + 'name', + 'active', + ], + ]); } public function testGivenAuthenticatedRequestWithItemsLimited(): void { - $user = $this->createAuthenticatedUser(); + $user = $this->createAuthenticatedUser(); $this->actingAs($user); $this->createTestRuleGroups(5, $user); $response = $this->get(route('api.v1.autocomplete.rule-groups', [ 'query' => 'RuleGroup', 'limit' => 3, - ]), ['Accept' => 'application/json']); + ]), ['Accept' => 'application/json']); $response->assertStatus(200); $response->assertHeader('Content-Type', 'application/json'); $response->assertJsonCount(3); $response->assertJsonFragment(['name' => 'RuleGroup 1']); $response->assertJsonStructure([ - '*' => [ - 'id', - 'name', - 'active', - ], - ]); + '*' => [ + 'id', + 'name', + 'active', + ], + ]); } public function testGivenAuthenticatedRequestWithItemsLots(): void { - $user = $this->createAuthenticatedUser(); + $user = $this->createAuthenticatedUser(); $this->actingAs($user); $this->createTestRuleGroups(20, $user); $response = $this->get(route('api.v1.autocomplete.rule-groups', [ 'query' => 'RuleGroup 1', 'limit' => 20, - ]), ['Accept' => 'application/json']); + ]), ['Accept' => 'application/json']); $response->assertStatus(200); $response->assertHeader('Content-Type', 'application/json'); diff --git a/tests/integration/Api/Autocomplete/TagControllerTest.php b/tests/integration/Api/Autocomplete/TagControllerTest.php index 33f0caaa3b..2f947b491a 100644 --- a/tests/integration/Api/Autocomplete/TagControllerTest.php +++ b/tests/integration/Api/Autocomplete/TagControllerTest.php @@ -24,7 +24,6 @@ declare(strict_types=1); namespace Tests\integration\Api\Autocomplete; -use FireflyIII\Models\RuleGroup; use FireflyIII\Models\Tag; use FireflyIII\User; use Illuminate\Foundation\Testing\RefreshDatabase; @@ -50,8 +49,8 @@ final class TagControllerTest extends TestCase [ 'user_id' => $user->id, 'user_group_id' => $user->user_group_id, - 'tag' => 'Tag ' . $i, - 'tag_mode' => 'nothing', + 'tag' => 'Tag '.$i, + 'tag_mode' => 'nothing', ] ); } @@ -69,7 +68,7 @@ final class TagControllerTest extends TestCase public function testAuthenticatedCall(): void { // act as a user - $user = $this->createAuthenticatedUser(); + $user = $this->createAuthenticatedUser(); $this->actingAs($user); $response = $this->get(route('api.v1.autocomplete.tags'), ['Accept' => 'application/json']); @@ -79,7 +78,7 @@ final class TagControllerTest extends TestCase public function testGivenAuthenticatedRequestWithItems(): void { - $user = $this->createAuthenticatedUser(); + $user = $this->createAuthenticatedUser(); $this->actingAs($user); $this->createTestTags(5, $user); @@ -89,50 +88,50 @@ final class TagControllerTest extends TestCase $response->assertJsonCount(5); $response->assertJsonFragment(['name' => 'Tag 1']); $response->assertJsonStructure([ - '*' => [ - 'id', - 'name', - 'tag', - ], - ]); + '*' => [ + 'id', + 'name', + 'tag', + ], + ]); } public function testGivenAuthenticatedRequestWithItemsLimited(): void { - $user = $this->createAuthenticatedUser(); + $user = $this->createAuthenticatedUser(); $this->actingAs($user); $this->createTestTags(5, $user); $response = $this->get(route('api.v1.autocomplete.tags', [ 'query' => 'Tag', 'limit' => 3, - ]), ['Accept' => 'application/json']); + ]), ['Accept' => 'application/json']); $response->assertStatus(200); $response->assertHeader('Content-Type', 'application/json'); $response->assertJsonCount(3); $response->assertJsonFragment(['name' => 'Tag 1']); $response->assertJsonStructure([ - '*' => [ - 'id', - 'name', - 'tag', - ], - ]); + '*' => [ + 'id', + 'name', + 'tag', + ], + ]); } public function testGivenAuthenticatedRequestWithItemsLots(): void { - $user = $this->createAuthenticatedUser(); + $user = $this->createAuthenticatedUser(); $this->actingAs($user); $this->createTestTags(20, $user); $response = $this->get(route('api.v1.autocomplete.tags', [ 'query' => 'Tag 1', 'limit' => 20, - ]), ['Accept' => 'application/json']); + ]), ['Accept' => 'application/json']); $response->assertStatus(200); $response->assertHeader('Content-Type', 'application/json'); From 8846ee9091e2f8939ca24753171ce4e41f430072 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 30 Aug 2025 07:51:28 +0200 Subject: [PATCH 049/111] Fix #10827 --- .../Models/Recurrence/ShowController.php | 26 ++-- .../Models/Recurrence/TriggerController.php | 125 ++++++++++++++++++ app/Helpers/Collector/GroupCollector.php | 4 +- .../Recurring/TriggerController.php | 16 +-- .../Recurring/RecurringRepository.php | 14 ++ .../RecurringRepositoryInterface.php | 2 + changelog.md | 1 + routes/api.php | 3 +- 8 files changed, 163 insertions(+), 28 deletions(-) create mode 100644 app/Api/V1/Controllers/Models/Recurrence/TriggerController.php diff --git a/app/Api/V1/Controllers/Models/Recurrence/ShowController.php b/app/Api/V1/Controllers/Models/Recurrence/ShowController.php index 74f29cface..ba5ae66248 100644 --- a/app/Api/V1/Controllers/Models/Recurrence/ShowController.php +++ b/app/Api/V1/Controllers/Models/Recurrence/ShowController.php @@ -70,10 +70,10 @@ class ShowController extends Controller */ public function index(): JsonResponse { - $manager = $this->getManager(); + $manager = $this->getManager(); // types to get, page size: - $pageSize = $this->parameters->get('limit'); + $pageSize = $this->parameters->get('limit'); // get list of budgets. Count it and split it. $collection = $this->repository->get(); @@ -82,20 +82,20 @@ class ShowController extends Controller // enrich /** @var User $admin */ - $admin = auth()->user(); - $enrichment = new RecurringEnrichment(); + $admin = auth()->user(); + $enrichment = new RecurringEnrichment(); $enrichment->setUser($admin); $recurrences = $enrichment->enrich($recurrences); // make paginator: - $paginator = new LengthAwarePaginator($recurrences, $count, $pageSize, $this->parameters->get('page')); - $paginator->setPath(route('api.v1.recurrences.index').$this->buildParams()); + $paginator = new LengthAwarePaginator($recurrences, $count, $pageSize, $this->parameters->get('page')); + $paginator->setPath(route('api.v1.recurrences.index') . $this->buildParams()); /** @var RecurrenceTransformer $transformer */ $transformer = app(RecurrenceTransformer::class); $transformer->setParameters($this->parameters); - $resource = new FractalCollection($recurrences, $transformer, 'recurrences'); + $resource = new FractalCollection($recurrences, $transformer, 'recurrences'); $resource->setPaginator(new IlluminatePaginatorAdapter($paginator)); return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE); @@ -109,21 +109,23 @@ class ShowController extends Controller */ public function show(Recurrence $recurrence): JsonResponse { - $manager = $this->getManager(); + $manager = $this->getManager(); // enrich /** @var User $admin */ - $admin = auth()->user(); - $enrichment = new RecurringEnrichment(); + $admin = auth()->user(); + $enrichment = new RecurringEnrichment(); $enrichment->setUser($admin); - $recurrence = $enrichment->enrichSingle($recurrence); + $recurrence = $enrichment->enrichSingle($recurrence); /** @var RecurrenceTransformer $transformer */ $transformer = app(RecurrenceTransformer::class); $transformer->setParameters($this->parameters); - $resource = new Item($recurrence, $transformer, 'recurrences'); + $resource = new Item($recurrence, $transformer, 'recurrences'); return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE); } + + } diff --git a/app/Api/V1/Controllers/Models/Recurrence/TriggerController.php b/app/Api/V1/Controllers/Models/Recurrence/TriggerController.php new file mode 100644 index 0000000000..31493b011a --- /dev/null +++ b/app/Api/V1/Controllers/Models/Recurrence/TriggerController.php @@ -0,0 +1,125 @@ +. + */ + +namespace FireflyIII\Api\V1\Controllers\Models\Recurrence; + +use FireflyIII\Api\V1\Controllers\Controller; +use FireflyIII\Api\V1\Requests\Generic\SingleDateRequest; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; +use FireflyIII\Jobs\CreateRecurringTransactions; +use FireflyIII\Models\Recurrence; +use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface; +use FireflyIII\Support\Facades\Preferences; +use FireflyIII\Support\JsonApi\Enrichments\TransactionGroupEnrichment; +use FireflyIII\Transformers\TransactionGroupTransformer; +use FireflyIII\User; +use Illuminate\Http\JsonResponse; +use Illuminate\Pagination\LengthAwarePaginator; +use Illuminate\Support\Collection; +use Illuminate\Support\Facades\Log; +use League\Fractal\Pagination\IlluminatePaginatorAdapter; +use League\Fractal\Resource\Collection as FractalCollection; + +class TriggerController extends Controller +{ + private RecurringRepositoryInterface $repository; + + /** + * RecurrenceController constructor. + */ + public function __construct() + { + parent::__construct(); + $this->middleware( + function ($request, $next) { + $this->repository = app(RecurringRepositoryInterface::class); + $this->repository->setUser(auth()->user()); + + return $next($request); + } + ); + } + + /** + */ + public function trigger(SingleDateRequest $request, Recurrence $recurrence): JsonResponse + { + // find recurrence occurrence for this date and trigger it. + // grab the date from the last time the recurrence fired: + $backupDate = $recurrence->latest_date; + $date = $request->getDate(); + + // fire the recurring cron job on the given date, then post-date the created transaction. + 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->setRecurrences(new Collection()->push($recurrence)); + $job->setDate($date); + $job->setForce(false); + $job->handle(); + Log::debug('Done with recurrence.'); + + $groups = $job->getGroups(); + $this->repository->markGroupsAsNow($groups); + $recurrence->latest_date = $backupDate; + $recurrence->latest_date_tz = $backupDate?->format('e'); + $recurrence->save(); + Preferences::mark(); + + // enrich groups and return them: + + if (0 === $groups->count()) { + $paginator = new LengthAwarePaginator(new Collection(), 0, 1,); + } + if ($groups->count() > 0) { + /** @var User $admin */ + $admin = auth()->user(); + // use new group collector: + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector + ->setUser($admin) + ->setIds($groups->pluck('id')->toArray()) + ->withAPIInformation(); + $paginator = $collector->getPaginatedGroups(); + } + + $manager = $this->getManager(); + $paginator->setPath(route('api.v1.recurrences.trigger', [$recurrence->id]) . $this->buildParams()); + + // enrich + $admin = auth()->user(); + $enrichment = new TransactionGroupEnrichment(); + $enrichment->setUser($admin); + $transactions = $enrichment->enrich($paginator->getCollection()); + + /** @var TransactionGroupTransformer $transformer */ + $transformer = app(TransactionGroupTransformer::class); + $transformer->setParameters($this->parameters); + + $resource = new FractalCollection($transactions, $transformer, 'transactions'); + $resource->setPaginator(new IlluminatePaginatorAdapter($paginator)); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE); + } + +} diff --git a/app/Helpers/Collector/GroupCollector.php b/app/Helpers/Collector/GroupCollector.php index 59b2748730..203a0283eb 100644 --- a/app/Helpers/Collector/GroupCollector.php +++ b/app/Helpers/Collector/GroupCollector.php @@ -861,8 +861,8 @@ class GroupCollector implements GroupCollectorInterface return new LengthAwarePaginator($set, $this->total, $total, 1); } - - return new LengthAwarePaginator($set, $this->total, $this->limit, $this->page); + $limit= $this->limit ?? 1; + return new LengthAwarePaginator($set, $this->total, $limit, $this->page); } /** diff --git a/app/Http/Controllers/Recurring/TriggerController.php b/app/Http/Controllers/Recurring/TriggerController.php index 818f353037..9ebfe64fac 100644 --- a/app/Http/Controllers/Recurring/TriggerController.php +++ b/app/Http/Controllers/Recurring/TriggerController.php @@ -28,8 +28,7 @@ use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Requests\TriggerRecurrenceRequest; use FireflyIII\Jobs\CreateRecurringTransactions; use FireflyIII\Models\Recurrence; -use FireflyIII\Models\TransactionGroup; -use FireflyIII\Models\TransactionJournal; +use FireflyIII\Support\Facades\Preferences; use Illuminate\Http\RedirectResponse; use Illuminate\Support\Collection; @@ -58,20 +57,11 @@ class TriggerController extends Controller app('log')->debug('Done with recurrence.'); $groups = $job->getGroups(); - - /** @var TransactionGroup $group */ - foreach ($groups as $group) { - /** @var TransactionJournal $journal */ - foreach ($group->transactionJournals as $journal) { - app('log')->debug(sprintf('Set date of journal #%d to today!', $journal->id)); - $journal->date = today(config('app.timezone')); - $journal->save(); - } - } + $this->repository->markGroupsAsNow($groups); $recurrence->latest_date = $backupDate; $recurrence->latest_date_tz = $backupDate?->format('e'); $recurrence->save(); - app('preferences')->mark(); + Preferences::mark(); if (0 === $groups->count()) { $request->session()->flash('info', (string) trans('firefly.no_new_transaction_in_recurrence')); diff --git a/app/Repositories/Recurring/RecurringRepository.php b/app/Repositories/Recurring/RecurringRepository.php index e12a543e38..c8ee7b89c1 100644 --- a/app/Repositories/Recurring/RecurringRepository.php +++ b/app/Repositories/Recurring/RecurringRepository.php @@ -35,6 +35,7 @@ use FireflyIII\Models\RecurrenceMeta; use FireflyIII\Models\RecurrenceRepetition; use FireflyIII\Models\RecurrenceTransaction; use FireflyIII\Models\RecurrenceTransactionMeta; +use FireflyIII\Models\TransactionGroup; use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionJournalMeta; use FireflyIII\Services\Internal\Destroy\RecurrenceDestroyService; @@ -582,4 +583,17 @@ class RecurringRepository implements RecurringRepositoryInterface, UserGroupInte return $service->update($recurrence, $data); } + + public function markGroupsAsNow(Collection $groups): void + { + /** @var TransactionGroup $group */ + foreach ($groups as $group) { + /** @var TransactionJournal $journal */ + foreach ($group->transactionJournals as $journal) { + Log::debug(sprintf('Set date of journal #%d to today!', $journal->id)); + $journal->date = now(config('app.timezone')); + $journal->save(); + } + } + } } diff --git a/app/Repositories/Recurring/RecurringRepositoryInterface.php b/app/Repositories/Recurring/RecurringRepositoryInterface.php index b59548b9de..9fb715e545 100644 --- a/app/Repositories/Recurring/RecurringRepositoryInterface.php +++ b/app/Repositories/Recurring/RecurringRepositoryInterface.php @@ -139,6 +139,8 @@ interface RecurringRepositoryInterface */ public function getXOccurrencesSince(RecurrenceRepetition $repetition, Carbon $date, Carbon $afterDate, int $count): array; + public function markGroupsAsNow(Collection $groups): void; + /** * Parse the repetition in a string that is user readable. */ diff --git a/changelog.md b/changelog.md index 1baeb14101..a5a464f4d8 100644 --- a/changelog.md +++ b/changelog.md @@ -36,6 +36,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - [Issue 10806](https://github.com/firefly-iii/firefly-iii/issues/10806) (API: `/v1/chart/balance/balance` has undocumented `period` parameter) reported by @dreautall - [Issue 10807](https://github.com/firefly-iii/firefly-iii/issues/10807) (API: `/v1/bills` field `object_group_id` returns int, should be string) reported by @dreautall - [Issue 10815](https://github.com/firefly-iii/firefly-iii/issues/10815) (API: `/v1/accounts` balance is off by a day) reported by @dreautall +- #10827 ## 6.3.2 - 2025-08-20 diff --git a/routes/api.php b/routes/api.php index 64775c9b77..31c902ab4b 100644 --- a/routes/api.php +++ b/routes/api.php @@ -504,7 +504,8 @@ Route::group( static function (): void { Route::get('', ['uses' => 'ShowController@index', 'as' => 'index']); Route::post('', ['uses' => 'StoreController@store', 'as' => 'store']); - Route::get('{recurrence}', ['uses' => 'ShowController@show', 'as' => 'show']); + Route::get( '{recurrence}', ['uses' => 'ShowController@show', 'as' => 'show']); + Route::post('{recurrence}/trigger', ['uses' => 'TriggerController@trigger', 'as' => 'trigger']); Route::put('{recurrence}', ['uses' => 'UpdateController@update', 'as' => 'update']); Route::delete('{recurrence}', ['uses' => 'DestroyController@destroy', 'as' => 'delete']); From d959526eb39ae11bd1d4ca6c2744a9ee3f54b2e8 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 31 Aug 2025 19:07:45 +0200 Subject: [PATCH 050/111] Fix #10837 --- .../Enrichments/RecurringEnrichment.php | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/app/Support/JsonApi/Enrichments/RecurringEnrichment.php b/app/Support/JsonApi/Enrichments/RecurringEnrichment.php index 4800ec98df..b0c7ea5ec1 100644 --- a/app/Support/JsonApi/Enrichments/RecurringEnrichment.php +++ b/app/Support/JsonApi/Enrichments/RecurringEnrichment.php @@ -59,7 +59,7 @@ class RecurringEnrichment implements EnrichmentInterface private Collection $collection; private array $ids = []; private array $transactionTypeIds = []; - private array $transactionTypes = []; + //private array $transactionTypes = []; private array $notes = []; private array $repetitions = []; private array $transactions = []; @@ -131,11 +131,11 @@ class RecurringEnrichment implements EnrichmentInterface $this->ids = array_unique($this->ids); // collect transaction types. - $transactionTypes = TransactionType::whereIn('id', array_unique($this->transactionTypeIds))->get(); - foreach ($transactionTypes as $transactionType) { - $id = (int)$transactionType->id; - $this->transactionTypes[$id] = TransactionTypeEnum::from($transactionType->type); - } +// $transactionTypes = TransactionType::whereIn('id', array_unique($this->transactionTypeIds))->get(); +// foreach ($transactionTypes as $transactionType) { +// $id = (int)$transactionType->id; +// $this->transactionTypes[$id] = TransactionTypeEnum::from($transactionType->type); +// } } private function collectRepetitions(): void @@ -400,7 +400,7 @@ class RecurringEnrichment implements EnrichmentInterface private function collectTransactionMetaData(): void { $ids = array_keys($this->transactions); - $meta = RecurrenceTransactionMeta::whereIn('rt_id', $ids)->get(); + $meta = RecurrenceTransactionMeta::whereNull('deleted_at')->whereIn('rt_id', $ids)->get(); // other meta-data to be collected: $billIds = []; $piggyBankIds = []; @@ -410,8 +410,14 @@ class RecurringEnrichment implements EnrichmentInterface foreach ($meta as $entry) { $id = (int)$entry->id; $transactionId = (int)$entry->rt_id; - $recurrenceId = $this->recurrenceIds[$transactionId]; - $name = (string)$entry->name; + + // this should refer to another array, were rtIds can be used to find the recurrence. + $recurrenceId = $this->recurrenceIds[$transactionId] ?? 0; + $name = (string)$entry->name ?? ''; + if(0 === $recurrenceId) { + Log::error(sprintf('Could not find recurrence ID for recurrence transaction ID %d', $transactionId)); + continue; + } switch ($name) { default: From 1e7ea4b76cc0d6450540fd4abbbaabcc9705c46a Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 31 Aug 2025 19:20:02 +0200 Subject: [PATCH 051/111] Improve account list and view. --- app/Api/V1/Controllers/Controller.php | 21 ++-- .../Models/Account/ShowController.php | 41 ++++---- .../Requests/Models/Account/ShowRequest.php | 96 +++++++++++++++++++ .../Models/PiggyBank/StoreRequest.php | 2 +- .../assets/v2/src/pages/accounts/index.js | 31 +++--- resources/lang/en_US/validation.php | 1 + resources/views/transactions/show.twig | 2 +- resources/views/v2/accounts/index.blade.php | 27 +++--- 8 files changed, 154 insertions(+), 67 deletions(-) create mode 100644 app/Api/V1/Requests/Models/Account/ShowRequest.php diff --git a/app/Api/V1/Controllers/Controller.php b/app/Api/V1/Controllers/Controller.php index 196cca9290..1752ef0648 100644 --- a/app/Api/V1/Controllers/Controller.php +++ b/app/Api/V1/Controllers/Controller.php @@ -108,12 +108,7 @@ abstract class Controller extends BaseController { $bag = new ParameterBag(); $page = (int)request()->get('page'); - if ($page < 1) { - $page = 1; - } - if ($page > 2 ** 16) { - $page = 2 ** 16; - } + $page = min(max(1, $page), 2 ** 16); $bag->set('page', $page); // some date fields: @@ -131,19 +126,15 @@ abstract class Controller extends BaseController $obj = null; if (null !== $date) { try { - $obj = Carbon::parse((string)$date); + $obj = Carbon::parse((string)$date, config('app.timezone')); } catch (InvalidFormatException $e) { // don't care - Log::warning( - sprintf( - 'Ignored invalid date "%s" in API controller parameter check: %s', - substr((string)$date, 0, 20), - $e->getMessage() - ) - ); + Log::warning(sprintf('Ignored invalid date "%s" in API controller parameter check: %s', substr((string)$date, 0, 20), $e->getMessage())); } } - $bag->set($field, $obj); + if($obj instanceof Carbon){ + $bag->set($field, $obj); + } } // integer fields: diff --git a/app/Api/V1/Controllers/Models/Account/ShowController.php b/app/Api/V1/Controllers/Models/Account/ShowController.php index 5cdb73d10d..cc992588b4 100644 --- a/app/Api/V1/Controllers/Models/Account/ShowController.php +++ b/app/Api/V1/Controllers/Models/Account/ShowController.php @@ -25,6 +25,7 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Controllers\Models\Account; use FireflyIII\Api\V1\Controllers\Controller; +use FireflyIII\Api\V1\Requests\Models\Account\ShowRequest; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\Account; use FireflyIII\Repositories\Account\AccountRepositoryInterface; @@ -33,7 +34,6 @@ use FireflyIII\Support\JsonApi\Enrichments\AccountEnrichment; use FireflyIII\Transformers\AccountTransformer; use FireflyIII\User; use Illuminate\Http\JsonResponse; -use Illuminate\Http\Request; use Illuminate\Pagination\LengthAwarePaginator; use League\Fractal\Pagination\IlluminatePaginatorAdapter; use League\Fractal\Resource\Collection as FractalCollection; @@ -71,41 +71,40 @@ class ShowController extends Controller * * @throws FireflyException */ - public function index(Request $request): JsonResponse + public function index(ShowRequest $request): JsonResponse { - $manager = $this->getManager(); - $type = $request->get('type') ?? 'all'; - $this->parameters->set('type', $type); + $manager = $this->getManager(); + $params = $request->getParameters(); + $this->parameters->set('type', $params['type']); // types to get, page size: - $types = $this->mapAccountTypes($this->parameters->get('type')); - $pageSize = $this->parameters->get('limit'); + $types = $this->mapAccountTypes($params['type']); // get list of accounts. Count it and split it. $this->repository->resetAccountOrder(); - $collection = $this->repository->getAccountsByType($types, $this->parameters->get('sort') ?? []); - $count = $collection->count(); + $collection = $this->repository->getAccountsByType($types, $params['sort']); + $count = $collection->count(); // continue sort: - $accounts = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize); + $accounts = $collection->slice(($this->parameters->get('page') - 1) * $params['limit'], $params['limit']); // enrich /** @var User $admin */ - $admin = auth()->user(); - $enrichment = new AccountEnrichment(); + $admin = auth()->user(); + $enrichment = new AccountEnrichment(); $enrichment->setDate($this->parameters->get('date')); $enrichment->setUser($admin); - $accounts = $enrichment->enrich($accounts); + $accounts = $enrichment->enrich($accounts); // make paginator: - $paginator = new LengthAwarePaginator($accounts, $count, $pageSize, $this->parameters->get('page')); - $paginator->setPath(route('api.v1.accounts.index').$this->buildParams()); + $paginator = new LengthAwarePaginator($accounts, $count, $params['limit'], $this->parameters->get('page')); + $paginator->setPath(route('api.v1.accounts.index') . $this->buildParams()); /** @var AccountTransformer $transformer */ $transformer = app(AccountTransformer::class); $transformer->setParameters($this->parameters); - $resource = new FractalCollection($accounts, $transformer, self::RESOURCE_KEY); + $resource = new FractalCollection($accounts, $transformer, self::RESOURCE_KEY); $resource->setPaginator(new IlluminatePaginatorAdapter($paginator)); return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE); @@ -122,21 +121,21 @@ class ShowController extends Controller // get list of accounts. Count it and split it. $this->repository->resetAccountOrder(); $account->refresh(); - $manager = $this->getManager(); + $manager = $this->getManager(); // enrich /** @var User $admin */ - $admin = auth()->user(); - $enrichment = new AccountEnrichment(); + $admin = auth()->user(); + $enrichment = new AccountEnrichment(); $enrichment->setDate($this->parameters->get('date')); $enrichment->setUser($admin); - $account = $enrichment->enrichSingle($account); + $account = $enrichment->enrichSingle($account); /** @var AccountTransformer $transformer */ $transformer = app(AccountTransformer::class); $transformer->setParameters($this->parameters); - $resource = new Item($account, $transformer, self::RESOURCE_KEY); + $resource = new Item($account, $transformer, self::RESOURCE_KEY); return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE); } diff --git a/app/Api/V1/Requests/Models/Account/ShowRequest.php b/app/Api/V1/Requests/Models/Account/ShowRequest.php new file mode 100644 index 0000000000..3deb643254 --- /dev/null +++ b/app/Api/V1/Requests/Models/Account/ShowRequest.php @@ -0,0 +1,96 @@ +. + */ + +namespace FireflyIII\Api\V1\Requests\Models\Account; + + +use Carbon\Carbon; +use FireflyIII\Models\Preference; +use FireflyIII\Support\Facades\Preferences; +use FireflyIII\Support\Http\Api\AccountFilter; +use FireflyIII\Support\Request\ConvertsDataTypes; +use FireflyIII\User; +use Illuminate\Contracts\Validation\Validator; +use Illuminate\Foundation\Http\FormRequest; + +class ShowRequest extends FormRequest +{ + use ConvertsDataTypes; + use AccountFilter; + + public function getParameters(): array + { + $limit = $this->convertInteger('limit'); + if (0 === $limit) { + // get default for user: + /** @var User $user */ + $user = auth()->user(); + + /** @var Preference $pageSize */ + $limit = (int)Preferences::getForUser($user, 'listPageSize', 50)->data; + } + + $page = $this->convertInteger('page'); + $page = min(max(1, $page), 2 ** 16); + + return [ + 'type' => $this->convertString('type', 'all'), + 'limit' => $limit, + 'sort' => $this->convertString('sort', 'order'), + 'page' => $page, + ]; + } + + public function rules(): array + { + $keys = join(',', array_keys($this->types)); + return [ + 'date' => 'date', + 'start' => 'date|present_with:end|before_or_equal:end|before:2038-01-17|after:1970-01-02', + 'end' => 'date|present_with:start|after_or_equal:start|before:2038-01-17|after:1970-01-02', + 'sort' => 'in:active,iban,name,order,-active,-iban,-name,-order', // TODO improve me. + 'type' => sprintf('in:%s', $keys), + 'limit' => 'number|min:1|max:131337', + 'page' => 'number|min:1|max:131337', + ]; + } + + public function withValidator(Validator $validator): void + { + $validator->after( + function (Validator $validator): void { + if ($validator->failed()) { + return; + } + $data = $validator->getData(); + if (array_key_exists('date', $data) && array_key_exists('start', $data) && array_key_exists('end', $data)) { + // assume valid dates, before we got here. + $start = Carbon::parse($data['start'], config('app.timezone'))->startOfDay(); + $end = Carbon::parse($data['end'], config('app.timezone'))->endOfDay(); + $date = Carbon::parse($data['date'], config('app.timezone')); + if (!$date->between($start, $end)) { + $validator->errors()->add('date', (string)trans('validation.between_date')); + } + } + }); + } + +} diff --git a/app/Api/V1/Requests/Models/PiggyBank/StoreRequest.php b/app/Api/V1/Requests/Models/PiggyBank/StoreRequest.php index a45297b832..46674967ec 100644 --- a/app/Api/V1/Requests/Models/PiggyBank/StoreRequest.php +++ b/app/Api/V1/Requests/Models/PiggyBank/StoreRequest.php @@ -78,7 +78,7 @@ class StoreRequest extends FormRequest 'object_group_id' => 'numeric|belongsToUser:object_groups,id', 'object_group_title' => ['min:1', 'max:255'], 'target_amount' => ['required', new IsValidZeroOrMoreAmount()], - 'start_date' => 'date|nullable', + 'start_date' => 'required|date|after:1970-01-01|before:2038-01-17', 'transaction_currency_id' => 'exists:transaction_currencies,id|required_without:transaction_currency_code', 'transaction_currency_code' => 'exists:transaction_currencies,code|required_without:transaction_currency_id', 'target_date' => 'date|nullable|after:start_date', diff --git a/resources/assets/v2/src/pages/accounts/index.js b/resources/assets/v2/src/pages/accounts/index.js index 109f7d14d4..0a391be704 100644 --- a/resources/assets/v2/src/pages/accounts/index.js +++ b/resources/assets/v2/src/pages/accounts/index.js @@ -32,6 +32,7 @@ import {showWizardButton} from "../../support/page-settings/show-wizard-button.j import {setVariable} from "../../store/set-variable.js"; import {getVariables} from "../../store/get-variables.js"; import pageNavigation from "../../support/page-navigation.js"; +import {getVariable} from "../../store/get-variable.js"; // set type from URL @@ -56,12 +57,12 @@ if(sortingColumn[0] === '-') { page = parseInt(params.page ?? 1); - showInternalsButton(); showWizardButton(); // TODO currency conversion // TODO page cleanup and recycle for transaction lists. +// TODO process startPeriod and endPeriod. let index = function () { return { @@ -73,9 +74,9 @@ let index = function () { show: false, text: '', url: '', }, wait: { show: false, text: '', - } }, + convertToPrimary: false, totalPages: 1, page: 1, pageUrl: '', @@ -259,6 +260,7 @@ let index = function () { {name: this.getPreferenceKey('sd'), default: ''}, {name: this.getPreferenceKey('filters'), default: this.filters}, {name: this.getPreferenceKey('grouped'), default: true}, + {name: 'convert_to_primary', default: false}, ]).then((res) => { // process columns: for (let k in res[0]) { @@ -283,6 +285,9 @@ let index = function () { // group accounts this.pageOptions.groupedAccounts = res[4]; + // convert to primary? + this.convertToPrimary = res[5]; + this.loadAccounts(); }); }, @@ -348,7 +353,6 @@ let index = function () { if('asc' === this.pageOptions.sortDirection && '' !== sorting) { sorting = '-' + sorting; } - //const sorting = [{column: this.pageOptions.sortingColumn, direction: this.pageOptions.sortDirection}]; // filter instructions let filters = {}; @@ -371,20 +375,20 @@ let index = function () { sort: sorting, filter: filters, active: active, - currentMoment: today, + date: format(today,'yyyy-MM-dd'), type: type, page: this.page, - startPeriod: start, - endPeriod: end + // startPeriod: start, + // endPeriod: end }; if (!this.tableColumns.balance_difference.enabled) { - delete params.startPeriod; - delete params.endPeriod; + // delete params.startPeriod; + // delete params.endPeriod; } this.accounts = []; let groupedAccounts = {}; - // one page only.o + // one page only (new Get()).index(params).then(response => { this.totalPages = response.meta.pagination.total_pages; for (let i = 0; i < response.data.length; i++) { @@ -406,9 +410,9 @@ let index = function () { liability_direction: current.attributes.liability_direction, interest: current.attributes.interest, interest_period: current.attributes.interest_period, - balance: current.attributes.balance, - pc_balance: current.attributes.pc_balance, - balances: current.attributes.balances, + // balance: current.attributes.balance, + // pc_balance: current.attributes.pc_balance, + // balances: current.attributes.balances, }; // get group info: let groupId = current.attributes.object_group_id; @@ -426,10 +430,9 @@ let index = function () { } } groupedAccounts[groupId].accounts.push(account); - - //this.accounts.push(account); } } + // order grouped accounts by order. let sortable = []; for (let set in groupedAccounts) { diff --git a/resources/lang/en_US/validation.php b/resources/lang/en_US/validation.php index a57b2f814c..73d78f9700 100644 --- a/resources/lang/en_US/validation.php +++ b/resources/lang/en_US/validation.php @@ -119,6 +119,7 @@ return [ 'between.file' => 'The :attribute must be between :min and :max kilobytes.', 'between.string' => 'The :attribute must be between :min and :max characters.', 'between.array' => 'The :attribute must have between :min and :max items.', + 'between_date' => 'The date must be between the given start and end date.', 'boolean' => 'The :attribute field must be true or false.', 'confirmed' => 'The :attribute confirmation does not match.', 'date' => 'The :attribute is not a valid date.', diff --git a/resources/views/transactions/show.twig b/resources/views/transactions/show.twig index 2bda3de9a6..bcfeced11a 100644 --- a/resources/views/transactions/show.twig +++ b/resources/views/transactions/show.twig @@ -441,7 +441,7 @@
{% for tag in journal.tags %}

+ href="{{ route('tags.show', [tag.id]) }}"> {{ tag.tag }}

{% endfor %} diff --git a/resources/views/v2/accounts/index.blade.php b/resources/views/v2/accounts/index.blade.php index 05d54582ae..1d5e8c62be 100644 --- a/resources/views/v2/accounts/index.blade.php +++ b/resources/views/v2/accounts/index.blade.php @@ -10,7 +10,7 @@

{{ __('firefly.net_worth') }}

- TODO + Not yet implemented.
@@ -20,17 +20,17 @@

{{ __('firefly.in_out_period') }}

- TODO + Not yet implemented.
-

TODO

+

Not yet implemented.

- TODO + Not yet implemented.
@@ -262,17 +262,14 @@
-