From e94acd22575df3f5b3f954b60aaea622a9260cc4 Mon Sep 17 00:00:00 2001 From: iMercy Date: Thu, 12 Mar 2026 07:54:32 -0400 Subject: [PATCH 01/25] enh: Myself party changed to transfer --- .../API/v1/TransactionController.php | 38 +- app/Models/Party.php | 2 + composer.lock | 512 +++++++++--------- config/app.php | 10 + ...2024_07_20_125104_create_parties_table.php | 1 + phpunit.xml | 7 +- tests/Feature/MyselfTransferTest.php | 90 +++ tests/TestCase.php | 4 + 8 files changed, 407 insertions(+), 257 deletions(-) create mode 100644 tests/Feature/MyselfTransferTest.php diff --git a/app/Http/Controllers/API/v1/TransactionController.php b/app/Http/Controllers/API/v1/TransactionController.php index 038c1aa0..a13da31b 100644 --- a/app/Http/Controllers/API/v1/TransactionController.php +++ b/app/Http/Controllers/API/v1/TransactionController.php @@ -17,6 +17,7 @@ use OpenApi\Attributes as OA; use Symfony\Component\HttpKernel\Exception\HttpException; use Throwable; +use App\Services\TransferService; #[OA\Tag(name: 'Transactions', description: 'Endpoints for managing transactions')] class TransactionController extends ApiController @@ -24,7 +25,8 @@ class TransactionController extends ApiController use ApiQueryable; public function __construct( - private RecurringTransactionService $recurringTransactionService + private RecurringTransactionService $recurringTransactionService, + private TransferService $transferService ) { } @@ -265,6 +267,8 @@ public function store(Request $request): JsonResponse 'categories.*' => 'integer|exists:categories,id', 'files' => 'nullable|array', 'files.*' => 'file|mimes:jpg,jpeg,png,pdf|max:1240', + + 'from_wallet_id' => 'required_if:convert_myself_to_transfer,true|integer|exists:wallets,id', ]); if (! $validationResult['isValidated']) { @@ -274,6 +278,38 @@ public function store(Request $request): JsonResponse $data = $validationResult['data']; $user = $request->user(); + //check if party has been set to 'myself' and convert to transfer if config is enabled + $party = isset($data['party_id']) ? \App\Models\Party::find($data['party_id']) : null; + if ( + config('app.convert_myself_to_transfer') && + $party && + $party->is_myself && + $request->has('from_wallet_id') + ) { + $fromWallet = $user->wallets()->findOrFail($request['from_wallet_id']); + $toWallet = $user->wallets()->findOrFail($data['wallet_id']); + + $transfer = $this->transferService->transfer( + amountToSend: (float) $data['amount'], + fromWallet: $fromWallet, + amountToReceive: (float) $data['amount'], + toWallet: $toWallet, + user: $user, + exchangeRate: 1.0, + datetime: $data['datetime'] ?? null, + transactionClientIds: [ + 'income_transaction_client_id' => $data['client_id'] ?? null + ] + ); + + //return response here to prevent controller from creating a third transaction below + return $this->success($transfer, statusCode: 201); + } + + + + + if (! empty($data['client_id'])) { $existingTransaction = Transaction::findByClientId($data['client_id'], $user); if ($existingTransaction) { diff --git a/app/Models/Party.php b/app/Models/Party.php index 3e8b1827..d3fe5bf9 100644 --- a/app/Models/Party.php +++ b/app/Models/Party.php @@ -21,6 +21,7 @@ new OA\Property(property: 'id', description: 'ID of the icon', type: 'integer'), new OA\Property(property: 'path', description: 'Image of the icon', type: 'string'), new OA\Property(property: 'type', description: 'type of icon( image or icon or emoji)', type: 'string'), + new OA\Property(property: 'is_myself', description: 'Whether this party represents the user themselves', type: 'boolean'), ], type: 'object'), ], type: 'object' @@ -43,6 +44,7 @@ class Party extends Model 'description', 'user_id', 'type', + 'is_myself' ]; protected $appends = ['last_synced_at', 'client_generated_id', 'icon']; diff --git a/composer.lock b/composer.lock index 117019bb..6b534959 100644 --- a/composer.lock +++ b/composer.lock @@ -210,16 +210,16 @@ }, { "name": "brick/math", - "version": "0.14.5", + "version": "0.14.8", "source": { "type": "git", "url": "https://github.com/brick/math.git", - "reference": "618a8077b3c326045e10d5788ed713b341fcfe40" + "reference": "63422359a44b7f06cae63c3b429b59e8efcc0629" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/618a8077b3c326045e10d5788ed713b341fcfe40", - "reference": "618a8077b3c326045e10d5788ed713b341fcfe40", + "url": "https://api.github.com/repos/brick/math/zipball/63422359a44b7f06cae63c3b429b59e8efcc0629", + "reference": "63422359a44b7f06cae63c3b429b59e8efcc0629", "shasum": "" }, "require": { @@ -258,7 +258,7 @@ ], "support": { "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.14.5" + "source": "https://github.com/brick/math/tree/0.14.8" }, "funding": [ { @@ -266,7 +266,7 @@ "type": "github" } ], - "time": "2026-02-03T18:06:51+00:00" + "time": "2026-02-10T14:33:43+00:00" }, { "name": "carbonphp/carbon-doctrine-types", @@ -992,16 +992,16 @@ }, { "name": "firebase/php-jwt", - "version": "v6.11.1", + "version": "v7.0.3", "source": { "type": "git", "url": "https://github.com/firebase/php-jwt.git", - "reference": "d1e91ecf8c598d073d0995afa8cd5c75c6e19e66" + "reference": "28aa0694bcfdfa5e2959c394d5a1ee7a5083629e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/firebase/php-jwt/zipball/d1e91ecf8c598d073d0995afa8cd5c75c6e19e66", - "reference": "d1e91ecf8c598d073d0995afa8cd5c75c6e19e66", + "url": "https://api.github.com/repos/firebase/php-jwt/zipball/28aa0694bcfdfa5e2959c394d5a1ee7a5083629e", + "reference": "28aa0694bcfdfa5e2959c394d5a1ee7a5083629e", "shasum": "" }, "require": { @@ -1049,9 +1049,9 @@ ], "support": { "issues": "https://github.com/firebase/php-jwt/issues", - "source": "https://github.com/firebase/php-jwt/tree/v6.11.1" + "source": "https://github.com/firebase/php-jwt/tree/v7.0.3" }, - "time": "2025-04-09T20:32:01+00:00" + "time": "2026-02-25T22:16:40+00:00" }, { "name": "fruitcake/php-cors", @@ -2078,16 +2078,16 @@ }, { "name": "kreait/firebase-php", - "version": "7.24.0", + "version": "7.24.1", "source": { "type": "git", "url": "https://github.com/beste/firebase-php.git", - "reference": "7e2c321265dcb2eeea6fdb72e3819db684e0d4be" + "reference": "935292e2af33af0120654ce704f1dc8f1c03025b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/beste/firebase-php/zipball/7e2c321265dcb2eeea6fdb72e3819db684e0d4be", - "reference": "7e2c321265dcb2eeea6fdb72e3819db684e0d4be", + "url": "https://api.github.com/repos/beste/firebase-php/zipball/935292e2af33af0120654ce704f1dc8f1c03025b", + "reference": "935292e2af33af0120654ce704f1dc8f1c03025b", "shasum": "" }, "require": { @@ -2100,7 +2100,7 @@ "ext-json": "*", "ext-mbstring": "*", "fig/http-message-util": "^1.1.5", - "firebase/php-jwt": "^6.10.2", + "firebase/php-jwt": "^6.10.2 || ^7.0.2", "google/auth": "^1.45", "google/cloud-storage": "^1.48.7", "guzzlehttp/guzzle": "^7.9.2", @@ -2175,7 +2175,7 @@ "type": "github" } ], - "time": "2025-11-27T22:03:59+00:00" + "time": "2026-02-18T23:43:18+00:00" }, { "name": "kreait/firebase-tokens", @@ -2547,16 +2547,16 @@ }, { "name": "laravel/prompts", - "version": "v0.3.12", + "version": "v0.3.13", "source": { "type": "git", "url": "https://github.com/laravel/prompts.git", - "reference": "4861ded9003b7f8a158176a0b7666f74ee761be8" + "reference": "ed8c466571b37e977532fb2fd3c272c784d7050d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/prompts/zipball/4861ded9003b7f8a158176a0b7666f74ee761be8", - "reference": "4861ded9003b7f8a158176a0b7666f74ee761be8", + "url": "https://api.github.com/repos/laravel/prompts/zipball/ed8c466571b37e977532fb2fd3c272c784d7050d", + "reference": "ed8c466571b37e977532fb2fd3c272c784d7050d", "shasum": "" }, "require": { @@ -2600,36 +2600,36 @@ "description": "Add beautiful and user-friendly forms to your command-line applications.", "support": { "issues": "https://github.com/laravel/prompts/issues", - "source": "https://github.com/laravel/prompts/tree/v0.3.12" + "source": "https://github.com/laravel/prompts/tree/v0.3.13" }, - "time": "2026-02-03T06:57:26+00:00" + "time": "2026-02-06T12:17:10+00:00" }, { "name": "laravel/sanctum", - "version": "v4.3.0", + "version": "v4.3.1", "source": { "type": "git", "url": "https://github.com/laravel/sanctum.git", - "reference": "c978c82b2b8ab685468a7ca35224497d541b775a" + "reference": "e3b85d6e36ad00e5db2d1dcc27c81ffdf15cbf76" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/sanctum/zipball/c978c82b2b8ab685468a7ca35224497d541b775a", - "reference": "c978c82b2b8ab685468a7ca35224497d541b775a", + "url": "https://api.github.com/repos/laravel/sanctum/zipball/e3b85d6e36ad00e5db2d1dcc27c81ffdf15cbf76", + "reference": "e3b85d6e36ad00e5db2d1dcc27c81ffdf15cbf76", "shasum": "" }, "require": { "ext-json": "*", - "illuminate/console": "^11.0|^12.0", - "illuminate/contracts": "^11.0|^12.0", - "illuminate/database": "^11.0|^12.0", - "illuminate/support": "^11.0|^12.0", + "illuminate/console": "^11.0|^12.0|^13.0", + "illuminate/contracts": "^11.0|^12.0|^13.0", + "illuminate/database": "^11.0|^12.0|^13.0", + "illuminate/support": "^11.0|^12.0|^13.0", "php": "^8.2", - "symfony/console": "^7.0" + "symfony/console": "^7.0|^8.0" }, "require-dev": { "mockery/mockery": "^1.6", - "orchestra/testbench": "^9.15|^10.8", + "orchestra/testbench": "^9.15|^10.8|^11.0", "phpstan/phpstan": "^1.10" }, "type": "library", @@ -2665,20 +2665,20 @@ "issues": "https://github.com/laravel/sanctum/issues", "source": "https://github.com/laravel/sanctum" }, - "time": "2026-01-22T22:27:01+00:00" + "time": "2026-02-07T17:19:31+00:00" }, { "name": "laravel/serializable-closure", - "version": "v2.0.9", + "version": "v2.0.10", "source": { "type": "git", "url": "https://github.com/laravel/serializable-closure.git", - "reference": "8f631589ab07b7b52fead814965f5a800459cb3e" + "reference": "870fc81d2f879903dfc5b60bf8a0f94a1609e669" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/8f631589ab07b7b52fead814965f5a800459cb3e", - "reference": "8f631589ab07b7b52fead814965f5a800459cb3e", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/870fc81d2f879903dfc5b60bf8a0f94a1609e669", + "reference": "870fc81d2f879903dfc5b60bf8a0f94a1609e669", "shasum": "" }, "require": { @@ -2726,36 +2726,36 @@ "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" }, - "time": "2026-02-03T06:55:34+00:00" + "time": "2026-02-20T19:59:49+00:00" }, { "name": "laravel/socialite", - "version": "v5.24.2", + "version": "v5.24.3", "source": { "type": "git", "url": "https://github.com/laravel/socialite.git", - "reference": "5cea2eebf11ca4bc6c2f20495c82a70a9b3d1613" + "reference": "0feb62267e7b8abc68593ca37639ad302728c129" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/socialite/zipball/5cea2eebf11ca4bc6c2f20495c82a70a9b3d1613", - "reference": "5cea2eebf11ca4bc6c2f20495c82a70a9b3d1613", + "url": "https://api.github.com/repos/laravel/socialite/zipball/0feb62267e7b8abc68593ca37639ad302728c129", + "reference": "0feb62267e7b8abc68593ca37639ad302728c129", "shasum": "" }, "require": { "ext-json": "*", "firebase/php-jwt": "^6.4|^7.0", "guzzlehttp/guzzle": "^6.0|^7.0", - "illuminate/contracts": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", - "illuminate/http": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", - "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", + "illuminate/contracts": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0|^13.0", + "illuminate/http": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0|^13.0", + "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0|^13.0", "league/oauth1-client": "^1.11", "php": "^7.2|^8.0", "phpseclib/phpseclib": "^3.0" }, "require-dev": { "mockery/mockery": "^1.0", - "orchestra/testbench": "^4.18|^5.20|^6.47|^7.55|^8.36|^9.15|^10.8", + "orchestra/testbench": "^4.18|^5.20|^6.47|^7.55|^8.36|^9.15|^10.8|^11.0", "phpstan/phpstan": "^1.12.23", "phpunit/phpunit": "^8.0|^9.3|^10.4|^11.5|^12.0" }, @@ -2798,20 +2798,20 @@ "issues": "https://github.com/laravel/socialite/issues", "source": "https://github.com/laravel/socialite" }, - "time": "2026-01-10T16:07:28+00:00" + "time": "2026-02-21T13:32:50+00:00" }, { "name": "laravel/tinker", - "version": "v2.11.0", + "version": "v2.11.1", "source": { "type": "git", "url": "https://github.com/laravel/tinker.git", - "reference": "3d34b97c9a1747a81a3fde90482c092bd8b66468" + "reference": "c9f80cc835649b5c1842898fb043f8cc098dd741" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/tinker/zipball/3d34b97c9a1747a81a3fde90482c092bd8b66468", - "reference": "3d34b97c9a1747a81a3fde90482c092bd8b66468", + "url": "https://api.github.com/repos/laravel/tinker/zipball/c9f80cc835649b5c1842898fb043f8cc098dd741", + "reference": "c9f80cc835649b5c1842898fb043f8cc098dd741", "shasum": "" }, "require": { @@ -2862,9 +2862,9 @@ ], "support": { "issues": "https://github.com/laravel/tinker/issues", - "source": "https://github.com/laravel/tinker/tree/v2.11.0" + "source": "https://github.com/laravel/tinker/tree/v2.11.1" }, - "time": "2025-12-19T19:16:45+00:00" + "time": "2026-02-06T14:12:35+00:00" }, { "name": "lcobucci/jwt", @@ -3130,16 +3130,16 @@ }, { "name": "league/flysystem", - "version": "3.31.0", + "version": "3.32.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "1717e0b3642b0df65ecb0cc89cdd99fa840672ff" + "reference": "254b1595b16b22dbddaaef9ed6ca9fdac4956725" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/1717e0b3642b0df65ecb0cc89cdd99fa840672ff", - "reference": "1717e0b3642b0df65ecb0cc89cdd99fa840672ff", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/254b1595b16b22dbddaaef9ed6ca9fdac4956725", + "reference": "254b1595b16b22dbddaaef9ed6ca9fdac4956725", "shasum": "" }, "require": { @@ -3207,9 +3207,9 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/3.31.0" + "source": "https://github.com/thephpleague/flysystem/tree/3.32.0" }, - "time": "2026-01-23T15:38:47+00:00" + "time": "2026-02-25T17:01:41+00:00" }, { "name": "league/flysystem-local", @@ -3850,16 +3850,16 @@ }, { "name": "nette/schema", - "version": "v1.3.3", + "version": "v1.3.5", "source": { "type": "git", "url": "https://github.com/nette/schema.git", - "reference": "2befc2f42d7c715fd9d95efc31b1081e5d765004" + "reference": "f0ab1a3cda782dbc5da270d28545236aa80c4002" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/schema/zipball/2befc2f42d7c715fd9d95efc31b1081e5d765004", - "reference": "2befc2f42d7c715fd9d95efc31b1081e5d765004", + "url": "https://api.github.com/repos/nette/schema/zipball/f0ab1a3cda782dbc5da270d28545236aa80c4002", + "reference": "f0ab1a3cda782dbc5da270d28545236aa80c4002", "shasum": "" }, "require": { @@ -3867,8 +3867,10 @@ "php": "8.1 - 8.5" }, "require-dev": { - "nette/tester": "^2.5.2", - "phpstan/phpstan-nette": "^2.0@stable", + "nette/phpstan-rules": "^1.0", + "nette/tester": "^2.6", + "phpstan/extension-installer": "^1.4@stable", + "phpstan/phpstan": "^2.1.39@stable", "tracy/tracy": "^2.8" }, "type": "library", @@ -3909,22 +3911,22 @@ ], "support": { "issues": "https://github.com/nette/schema/issues", - "source": "https://github.com/nette/schema/tree/v1.3.3" + "source": "https://github.com/nette/schema/tree/v1.3.5" }, - "time": "2025-10-30T22:57:59+00:00" + "time": "2026-02-23T03:47:12+00:00" }, { "name": "nette/utils", - "version": "v4.1.2", + "version": "v4.1.3", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "f76b5dc3d6c6d3043c8d937df2698515b99cbaf5" + "reference": "bb3ea637e3d131d72acc033cfc2746ee893349fe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/f76b5dc3d6c6d3043c8d937df2698515b99cbaf5", - "reference": "f76b5dc3d6c6d3043c8d937df2698515b99cbaf5", + "url": "https://api.github.com/repos/nette/utils/zipball/bb3ea637e3d131d72acc033cfc2746ee893349fe", + "reference": "bb3ea637e3d131d72acc033cfc2746ee893349fe", "shasum": "" }, "require": { @@ -3936,8 +3938,10 @@ }, "require-dev": { "jetbrains/phpstorm-attributes": "^1.2", + "nette/phpstan-rules": "^1.0", "nette/tester": "^2.5", - "phpstan/phpstan": "^2.0@stable", + "phpstan/extension-installer": "^1.4@stable", + "phpstan/phpstan": "^2.1@stable", "tracy/tracy": "^2.9" }, "suggest": { @@ -3998,9 +4002,9 @@ ], "support": { "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v4.1.2" + "source": "https://github.com/nette/utils/tree/v4.1.3" }, - "time": "2026-02-03T17:21:09+00:00" + "time": "2026-02-13T03:05:33+00:00" }, { "name": "nikic/php-parser", @@ -4062,31 +4066,31 @@ }, { "name": "nunomaduro/termwind", - "version": "v2.3.3", + "version": "v2.4.0", "source": { "type": "git", "url": "https://github.com/nunomaduro/termwind.git", - "reference": "6fb2a640ff502caace8e05fd7be3b503a7e1c017" + "reference": "712a31b768f5daea284c2169a7d227031001b9a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/6fb2a640ff502caace8e05fd7be3b503a7e1c017", - "reference": "6fb2a640ff502caace8e05fd7be3b503a7e1c017", + "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/712a31b768f5daea284c2169a7d227031001b9a8", + "reference": "712a31b768f5daea284c2169a7d227031001b9a8", "shasum": "" }, "require": { "ext-mbstring": "*", "php": "^8.2", - "symfony/console": "^7.3.6" + "symfony/console": "^7.4.4 || ^8.0.4" }, "require-dev": { - "illuminate/console": "^11.46.1", - "laravel/pint": "^1.25.1", + "illuminate/console": "^11.47.0", + "laravel/pint": "^1.27.1", "mockery/mockery": "^1.6.12", - "pestphp/pest": "^2.36.0 || ^3.8.4 || ^4.1.3", + "pestphp/pest": "^2.36.0 || ^3.8.4 || ^4.3.2", "phpstan/phpstan": "^1.12.32", "phpstan/phpstan-strict-rules": "^1.6.2", - "symfony/var-dumper": "^7.3.5", + "symfony/var-dumper": "^7.3.5 || ^8.0.4", "thecodingmachine/phpstan-strict-rules": "^1.0.0" }, "type": "library", @@ -4118,7 +4122,7 @@ "email": "enunomaduro@gmail.com" } ], - "description": "Its like Tailwind CSS, but for the console.", + "description": "It's like Tailwind CSS, but for the console.", "keywords": [ "cli", "console", @@ -4129,7 +4133,7 @@ ], "support": { "issues": "https://github.com/nunomaduro/termwind/issues", - "source": "https://github.com/nunomaduro/termwind/tree/v2.3.3" + "source": "https://github.com/nunomaduro/termwind/tree/v2.4.0" }, "funding": [ { @@ -4145,7 +4149,7 @@ "type": "github" } ], - "time": "2025-11-20T02:34:59+00:00" + "time": "2026-02-16T23:10:27+00:00" }, { "name": "paragonie/constant_time_encoding", @@ -4961,16 +4965,16 @@ }, { "name": "psy/psysh", - "version": "v0.12.19", + "version": "v0.12.20", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "a4f766e5c5b6773d8399711019bb7d90875a50ee" + "reference": "19678eb6b952a03b8a1d96ecee9edba518bb0373" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/a4f766e5c5b6773d8399711019bb7d90875a50ee", - "reference": "a4f766e5c5b6773d8399711019bb7d90875a50ee", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/19678eb6b952a03b8a1d96ecee9edba518bb0373", + "reference": "19678eb6b952a03b8a1d96ecee9edba518bb0373", "shasum": "" }, "require": { @@ -5034,9 +5038,9 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.12.19" + "source": "https://github.com/bobthecow/psysh/tree/v0.12.20" }, - "time": "2026-01-30T17:33:13+00:00" + "time": "2026-02-11T15:05:28+00:00" }, { "name": "ralouphie/getallheaders", @@ -5485,16 +5489,16 @@ }, { "name": "symfony/cache", - "version": "v7.4.5", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "8dde98d5a4123b53877aca493f9be57b333f14bd" + "reference": "1d06192e8f164e2729b0031e6807d72a6195b8bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/8dde98d5a4123b53877aca493f9be57b333f14bd", - "reference": "8dde98d5a4123b53877aca493f9be57b333f14bd", + "url": "https://api.github.com/repos/symfony/cache/zipball/1d06192e8f164e2729b0031e6807d72a6195b8bb", + "reference": "1d06192e8f164e2729b0031e6807d72a6195b8bb", "shasum": "" }, "require": { @@ -5565,7 +5569,7 @@ "psr6" ], "support": { - "source": "https://github.com/symfony/cache/tree/v7.4.5" + "source": "https://github.com/symfony/cache/tree/v7.4.6" }, "funding": [ { @@ -5585,7 +5589,7 @@ "type": "tidelift" } ], - "time": "2026-01-27T16:16:02+00:00" + "time": "2026-02-21T23:29:27+00:00" }, { "name": "symfony/cache-contracts", @@ -5743,16 +5747,16 @@ }, { "name": "symfony/console", - "version": "v7.4.4", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "41e38717ac1dd7a46b6bda7d6a82af2d98a78894" + "reference": "6d643a93b47398599124022eb24d97c153c12f27" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/41e38717ac1dd7a46b6bda7d6a82af2d98a78894", - "reference": "41e38717ac1dd7a46b6bda7d6a82af2d98a78894", + "url": "https://api.github.com/repos/symfony/console/zipball/6d643a93b47398599124022eb24d97c153c12f27", + "reference": "6d643a93b47398599124022eb24d97c153c12f27", "shasum": "" }, "require": { @@ -5817,7 +5821,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.4.4" + "source": "https://github.com/symfony/console/tree/v7.4.6" }, "funding": [ { @@ -5837,20 +5841,20 @@ "type": "tidelift" } ], - "time": "2026-01-13T11:36:38+00:00" + "time": "2026-02-25T17:02:47+00:00" }, { "name": "symfony/css-selector", - "version": "v7.4.0", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "ab862f478513e7ca2fe9ec117a6f01a8da6e1135" + "reference": "2e7c52c647b406e2107dd867db424a4dbac91864" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/ab862f478513e7ca2fe9ec117a6f01a8da6e1135", - "reference": "ab862f478513e7ca2fe9ec117a6f01a8da6e1135", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/2e7c52c647b406e2107dd867db424a4dbac91864", + "reference": "2e7c52c647b406e2107dd867db424a4dbac91864", "shasum": "" }, "require": { @@ -5886,7 +5890,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v7.4.0" + "source": "https://github.com/symfony/css-selector/tree/v7.4.6" }, "funding": [ { @@ -5906,7 +5910,7 @@ "type": "tidelift" } ], - "time": "2025-10-30T13:39:42+00:00" + "time": "2026-02-17T07:53:42+00:00" }, { "name": "symfony/deprecation-contracts", @@ -6220,16 +6224,16 @@ }, { "name": "symfony/finder", - "version": "v7.4.5", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "ad4daa7c38668dcb031e63bc99ea9bd42196a2cb" + "reference": "8655bf1076b7a3a346cb11413ffdabff50c7ffcf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/ad4daa7c38668dcb031e63bc99ea9bd42196a2cb", - "reference": "ad4daa7c38668dcb031e63bc99ea9bd42196a2cb", + "url": "https://api.github.com/repos/symfony/finder/zipball/8655bf1076b7a3a346cb11413ffdabff50c7ffcf", + "reference": "8655bf1076b7a3a346cb11413ffdabff50c7ffcf", "shasum": "" }, "require": { @@ -6264,7 +6268,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.4.5" + "source": "https://github.com/symfony/finder/tree/v7.4.6" }, "funding": [ { @@ -6284,20 +6288,20 @@ "type": "tidelift" } ], - "time": "2026-01-26T15:07:59+00:00" + "time": "2026-01-29T09:40:50+00:00" }, { "name": "symfony/http-foundation", - "version": "v7.4.5", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "446d0db2b1f21575f1284b74533e425096abdfb6" + "reference": "fd97d5e926e988a363cef56fbbf88c5c528e9065" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/446d0db2b1f21575f1284b74533e425096abdfb6", - "reference": "446d0db2b1f21575f1284b74533e425096abdfb6", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/fd97d5e926e988a363cef56fbbf88c5c528e9065", + "reference": "fd97d5e926e988a363cef56fbbf88c5c528e9065", "shasum": "" }, "require": { @@ -6346,7 +6350,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v7.4.5" + "source": "https://github.com/symfony/http-foundation/tree/v7.4.6" }, "funding": [ { @@ -6366,20 +6370,20 @@ "type": "tidelift" } ], - "time": "2026-01-27T16:16:02+00:00" + "time": "2026-02-21T16:25:55+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.4.5", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "229eda477017f92bd2ce7615d06222ec0c19e82a" + "reference": "002ac0cf4cd972a7fd0912dcd513a95e8a81ce83" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/229eda477017f92bd2ce7615d06222ec0c19e82a", - "reference": "229eda477017f92bd2ce7615d06222ec0c19e82a", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/002ac0cf4cd972a7fd0912dcd513a95e8a81ce83", + "reference": "002ac0cf4cd972a7fd0912dcd513a95e8a81ce83", "shasum": "" }, "require": { @@ -6421,7 +6425,7 @@ "symfony/config": "^6.4|^7.0|^8.0", "symfony/console": "^6.4|^7.0|^8.0", "symfony/css-selector": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4.1|^7.0.1|^8.0", "symfony/dom-crawler": "^6.4|^7.0|^8.0", "symfony/expression-language": "^6.4|^7.0|^8.0", "symfony/finder": "^6.4|^7.0|^8.0", @@ -6465,7 +6469,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v7.4.5" + "source": "https://github.com/symfony/http-kernel/tree/v7.4.6" }, "funding": [ { @@ -6485,20 +6489,20 @@ "type": "tidelift" } ], - "time": "2026-01-28T10:33:42+00:00" + "time": "2026-02-26T08:30:57+00:00" }, { "name": "symfony/mailer", - "version": "v7.4.4", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "7b750074c40c694ceb34cb926d6dffee231c5cd6" + "reference": "b02726f39a20bc65e30364f5c750c4ddbf1f58e9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/7b750074c40c694ceb34cb926d6dffee231c5cd6", - "reference": "7b750074c40c694ceb34cb926d6dffee231c5cd6", + "url": "https://api.github.com/repos/symfony/mailer/zipball/b02726f39a20bc65e30364f5c750c4ddbf1f58e9", + "reference": "b02726f39a20bc65e30364f5c750c4ddbf1f58e9", "shasum": "" }, "require": { @@ -6549,7 +6553,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v7.4.4" + "source": "https://github.com/symfony/mailer/tree/v7.4.6" }, "funding": [ { @@ -6569,20 +6573,20 @@ "type": "tidelift" } ], - "time": "2026-01-08T08:25:11+00:00" + "time": "2026-02-25T16:50:00+00:00" }, { "name": "symfony/mime", - "version": "v7.4.5", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "b18c7e6e9eee1e19958138df10412f3c4c316148" + "reference": "9fc881d95feae4c6c48678cb6372bd8a7ba04f5f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/b18c7e6e9eee1e19958138df10412f3c4c316148", - "reference": "b18c7e6e9eee1e19958138df10412f3c4c316148", + "url": "https://api.github.com/repos/symfony/mime/zipball/9fc881d95feae4c6c48678cb6372bd8a7ba04f5f", + "reference": "9fc881d95feae4c6c48678cb6372bd8a7ba04f5f", "shasum": "" }, "require": { @@ -6593,7 +6597,7 @@ }, "conflict": { "egulias/email-validator": "~3.0.0", - "phpdocumentor/reflection-docblock": "<5.2|>=6", + "phpdocumentor/reflection-docblock": "<5.2|>=7", "phpdocumentor/type-resolver": "<1.5.1", "symfony/mailer": "<6.4", "symfony/serializer": "<6.4.3|>7.0,<7.0.3" @@ -6601,7 +6605,7 @@ "require-dev": { "egulias/email-validator": "^2.1.10|^3.1|^4", "league/html-to-markdown": "^5.0", - "phpdocumentor/reflection-docblock": "^5.2", + "phpdocumentor/reflection-docblock": "^5.2|^6.0", "symfony/dependency-injection": "^6.4|^7.0|^8.0", "symfony/process": "^6.4|^7.0|^8.0", "symfony/property-access": "^6.4|^7.0|^8.0", @@ -6638,7 +6642,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v7.4.5" + "source": "https://github.com/symfony/mime/tree/v7.4.6" }, "funding": [ { @@ -6658,7 +6662,7 @@ "type": "tidelift" } ], - "time": "2026-01-27T08:59:58+00:00" + "time": "2026-02-05T15:57:06+00:00" }, { "name": "symfony/polyfill-ctype", @@ -7476,16 +7480,16 @@ }, { "name": "symfony/routing", - "version": "v7.4.4", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "0798827fe2c79caeed41d70b680c2c3507d10147" + "reference": "238d749c56b804b31a9bf3e26519d93b65a60938" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/0798827fe2c79caeed41d70b680c2c3507d10147", - "reference": "0798827fe2c79caeed41d70b680c2c3507d10147", + "url": "https://api.github.com/repos/symfony/routing/zipball/238d749c56b804b31a9bf3e26519d93b65a60938", + "reference": "238d749c56b804b31a9bf3e26519d93b65a60938", "shasum": "" }, "require": { @@ -7537,7 +7541,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v7.4.4" + "source": "https://github.com/symfony/routing/tree/v7.4.6" }, "funding": [ { @@ -7557,7 +7561,7 @@ "type": "tidelift" } ], - "time": "2026-01-12T12:19:02+00:00" + "time": "2026-02-25T16:50:00+00:00" }, { "name": "symfony/service-contracts", @@ -7648,16 +7652,16 @@ }, { "name": "symfony/string", - "version": "v7.4.4", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "1c4b10461bf2ec27537b5f36105337262f5f5d6f" + "reference": "9f209231affa85aa930a5e46e6eb03381424b30b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/1c4b10461bf2ec27537b5f36105337262f5f5d6f", - "reference": "1c4b10461bf2ec27537b5f36105337262f5f5d6f", + "url": "https://api.github.com/repos/symfony/string/zipball/9f209231affa85aa930a5e46e6eb03381424b30b", + "reference": "9f209231affa85aa930a5e46e6eb03381424b30b", "shasum": "" }, "require": { @@ -7715,7 +7719,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.4.4" + "source": "https://github.com/symfony/string/tree/v7.4.6" }, "funding": [ { @@ -7735,20 +7739,20 @@ "type": "tidelift" } ], - "time": "2026-01-12T10:54:30+00:00" + "time": "2026-02-09T09:33:46+00:00" }, { "name": "symfony/translation", - "version": "v7.4.4", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "bfde13711f53f549e73b06d27b35a55207528877" + "reference": "1888cf064399868af3784b9e043240f1d89d25ce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/bfde13711f53f549e73b06d27b35a55207528877", - "reference": "bfde13711f53f549e73b06d27b35a55207528877", + "url": "https://api.github.com/repos/symfony/translation/zipball/1888cf064399868af3784b9e043240f1d89d25ce", + "reference": "1888cf064399868af3784b9e043240f1d89d25ce", "shasum": "" }, "require": { @@ -7815,7 +7819,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v7.4.4" + "source": "https://github.com/symfony/translation/tree/v7.4.6" }, "funding": [ { @@ -7835,7 +7839,7 @@ "type": "tidelift" } ], - "time": "2026-01-13T10:40:19+00:00" + "time": "2026-02-17T07:53:42+00:00" }, { "name": "symfony/translation-contracts", @@ -7999,16 +8003,16 @@ }, { "name": "symfony/var-dumper", - "version": "v7.4.4", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "0e4769b46a0c3c62390d124635ce59f66874b282" + "reference": "045321c440ac18347b136c63d2e9bf28a2dc0291" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/0e4769b46a0c3c62390d124635ce59f66874b282", - "reference": "0e4769b46a0c3c62390d124635ce59f66874b282", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/045321c440ac18347b136c63d2e9bf28a2dc0291", + "reference": "045321c440ac18347b136c63d2e9bf28a2dc0291", "shasum": "" }, "require": { @@ -8062,7 +8066,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.4.4" + "source": "https://github.com/symfony/var-dumper/tree/v7.4.6" }, "funding": [ { @@ -8082,7 +8086,7 @@ "type": "tidelift" } ], - "time": "2026-01-01T22:13:48+00:00" + "time": "2026-02-15T10:53:20+00:00" }, { "name": "symfony/var-exporter", @@ -8167,16 +8171,16 @@ }, { "name": "symfony/yaml", - "version": "v7.4.1", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "24dd4de28d2e3988b311751ac49e684d783e2345" + "reference": "58751048de17bae71c5aa0d13cb19d79bca26391" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/24dd4de28d2e3988b311751ac49e684d783e2345", - "reference": "24dd4de28d2e3988b311751ac49e684d783e2345", + "url": "https://api.github.com/repos/symfony/yaml/zipball/58751048de17bae71c5aa0d13cb19d79bca26391", + "reference": "58751048de17bae71c5aa0d13cb19d79bca26391", "shasum": "" }, "require": { @@ -8219,7 +8223,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v7.4.1" + "source": "https://github.com/symfony/yaml/tree/v7.4.6" }, "funding": [ { @@ -8239,7 +8243,7 @@ "type": "tidelift" } ], - "time": "2025-12-04T18:11:45+00:00" + "time": "2026-02-09T09:33:46+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -8679,16 +8683,16 @@ }, { "name": "zircote/swagger-php", - "version": "5.8.1", + "version": "5.8.3", "source": { "type": "git", "url": "https://github.com/zircote/swagger-php.git", - "reference": "8adf6bb57561243aca48339e7f39bddf27ae546c" + "reference": "098223019f764a16715f64089a58606096719c98" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zircote/swagger-php/zipball/8adf6bb57561243aca48339e7f39bddf27ae546c", - "reference": "8adf6bb57561243aca48339e7f39bddf27ae546c", + "url": "https://api.github.com/repos/zircote/swagger-php/zipball/098223019f764a16715f64089a58606096719c98", + "reference": "098223019f764a16715f64089a58606096719c98", "shasum": "" }, "require": { @@ -8761,7 +8765,7 @@ ], "support": { "issues": "https://github.com/zircote/swagger-php/issues", - "source": "https://github.com/zircote/swagger-php/tree/5.8.1" + "source": "https://github.com/zircote/swagger-php/tree/5.8.3" }, "funding": [ { @@ -8769,7 +8773,7 @@ "type": "github" } ], - "time": "2026-02-03T23:48:39+00:00" + "time": "2026-03-02T00:47:18+00:00" } ], "packages-dev": [ @@ -9146,40 +9150,40 @@ }, { "name": "larastan/larastan", - "version": "v3.9.2", + "version": "v3.9.3", "source": { "type": "git", "url": "https://github.com/larastan/larastan.git", - "reference": "2e9ed291bdc1969e7f270fb33c9cdf3c912daeb2" + "reference": "64a52bcc5347c89fdf131cb59f96ebfbc8d1ad65" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/larastan/larastan/zipball/2e9ed291bdc1969e7f270fb33c9cdf3c912daeb2", - "reference": "2e9ed291bdc1969e7f270fb33c9cdf3c912daeb2", + "url": "https://api.github.com/repos/larastan/larastan/zipball/64a52bcc5347c89fdf131cb59f96ebfbc8d1ad65", + "reference": "64a52bcc5347c89fdf131cb59f96ebfbc8d1ad65", "shasum": "" }, "require": { "ext-json": "*", "iamcal/sql-parser": "^0.7.0", - "illuminate/console": "^11.44.2 || ^12.4.1", - "illuminate/container": "^11.44.2 || ^12.4.1", - "illuminate/contracts": "^11.44.2 || ^12.4.1", - "illuminate/database": "^11.44.2 || ^12.4.1", - "illuminate/http": "^11.44.2 || ^12.4.1", - "illuminate/pipeline": "^11.44.2 || ^12.4.1", - "illuminate/support": "^11.44.2 || ^12.4.1", + "illuminate/console": "^11.44.2 || ^12.4.1 || ^13", + "illuminate/container": "^11.44.2 || ^12.4.1 || ^13", + "illuminate/contracts": "^11.44.2 || ^12.4.1 || ^13", + "illuminate/database": "^11.44.2 || ^12.4.1 || ^13", + "illuminate/http": "^11.44.2 || ^12.4.1 || ^13", + "illuminate/pipeline": "^11.44.2 || ^12.4.1 || ^13", + "illuminate/support": "^11.44.2 || ^12.4.1 || ^13", "php": "^8.2", "phpstan/phpstan": "^2.1.32" }, "require-dev": { "doctrine/coding-standard": "^13", - "laravel/framework": "^11.44.2 || ^12.7.2", + "laravel/framework": "^11.44.2 || ^12.7.2 || ^13", "mockery/mockery": "^1.6.12", "nikic/php-parser": "^5.4", - "orchestra/canvas": "^v9.2.2 || ^10.0.1", - "orchestra/testbench-core": "^9.12.0 || ^10.1", + "orchestra/canvas": "^v9.2.2 || ^10.0.1 || ^11", + "orchestra/testbench-core": "^9.12.0 || ^10.1 || ^11", "phpstan/phpstan-deprecation-rules": "^2.0.1", - "phpunit/phpunit": "^10.5.35 || ^11.5.15" + "phpunit/phpunit": "^10.5.35 || ^11.5.15 || ^12.5.8" }, "suggest": { "orchestra/testbench": "Using Larastan for analysing a package needs Testbench", @@ -9224,7 +9228,7 @@ ], "support": { "issues": "https://github.com/larastan/larastan/issues", - "source": "https://github.com/larastan/larastan/tree/v3.9.2" + "source": "https://github.com/larastan/larastan/tree/v3.9.3" }, "funding": [ { @@ -9232,20 +9236,20 @@ "type": "github" } ], - "time": "2026-01-30T15:16:32+00:00" + "time": "2026-02-20T12:07:12+00:00" }, { "name": "laravel/pint", - "version": "v1.27.0", + "version": "v1.27.1", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "c67b4195b75491e4dfc6b00b1c78b68d86f54c90" + "reference": "54cca2de13790570c7b6f0f94f37896bee4abcb5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/c67b4195b75491e4dfc6b00b1c78b68d86f54c90", - "reference": "c67b4195b75491e4dfc6b00b1c78b68d86f54c90", + "url": "https://api.github.com/repos/laravel/pint/zipball/54cca2de13790570c7b6f0f94f37896bee4abcb5", + "reference": "54cca2de13790570c7b6f0f94f37896bee4abcb5", "shasum": "" }, "require": { @@ -9256,13 +9260,13 @@ "php": "^8.2.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.92.4", - "illuminate/view": "^12.44.0", - "larastan/larastan": "^3.8.1", - "laravel-zero/framework": "^12.0.4", + "friendsofphp/php-cs-fixer": "^3.93.1", + "illuminate/view": "^12.51.0", + "larastan/larastan": "^3.9.2", + "laravel-zero/framework": "^12.0.5", "mockery/mockery": "^1.6.12", "nunomaduro/termwind": "^2.3.3", - "pestphp/pest": "^3.8.4" + "pestphp/pest": "^3.8.5" }, "bin": [ "builds/pint" @@ -9299,32 +9303,32 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2026-01-05T16:49:17+00:00" + "time": "2026-02-10T20:00:20+00:00" }, { "name": "laravel/sail", - "version": "v1.52.0", + "version": "v1.53.0", "source": { "type": "git", "url": "https://github.com/laravel/sail.git", - "reference": "64ac7d8abb2dbcf2b76e61289451bae79066b0b3" + "reference": "e340eaa2bea9b99192570c48ed837155dbf24fbb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/sail/zipball/64ac7d8abb2dbcf2b76e61289451bae79066b0b3", - "reference": "64ac7d8abb2dbcf2b76e61289451bae79066b0b3", + "url": "https://api.github.com/repos/laravel/sail/zipball/e340eaa2bea9b99192570c48ed837155dbf24fbb", + "reference": "e340eaa2bea9b99192570c48ed837155dbf24fbb", "shasum": "" }, "require": { - "illuminate/console": "^9.52.16|^10.0|^11.0|^12.0", - "illuminate/contracts": "^9.52.16|^10.0|^11.0|^12.0", - "illuminate/support": "^9.52.16|^10.0|^11.0|^12.0", + "illuminate/console": "^9.52.16|^10.0|^11.0|^12.0|^13.0", + "illuminate/contracts": "^9.52.16|^10.0|^11.0|^12.0|^13.0", + "illuminate/support": "^9.52.16|^10.0|^11.0|^12.0|^13.0", "php": "^8.0", - "symfony/console": "^6.0|^7.0", - "symfony/yaml": "^6.0|^7.0" + "symfony/console": "^6.0|^7.0|^8.0", + "symfony/yaml": "^6.0|^7.0|^8.0" }, "require-dev": { - "orchestra/testbench": "^7.0|^8.0|^9.0|^10.0", + "orchestra/testbench": "^7.0|^8.0|^9.0|^10.0|^11.0", "phpstan/phpstan": "^2.0" }, "bin": [ @@ -9362,7 +9366,7 @@ "issues": "https://github.com/laravel/sail/issues", "source": "https://github.com/laravel/sail" }, - "time": "2026-01-01T02:46:03+00:00" + "time": "2026-02-06T12:16:02+00:00" }, { "name": "mockery/mockery", @@ -9870,11 +9874,11 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.38", + "version": "2.1.40", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/dfaf1f530e1663aa167bc3e52197adb221582629", - "reference": "dfaf1f530e1663aa167bc3e52197adb221582629", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/9b2c7aeb83a75d8680ea5e7c9b7fca88052b766b", + "reference": "9b2c7aeb83a75d8680ea5e7c9b7fca88052b766b", "shasum": "" }, "require": { @@ -9919,7 +9923,7 @@ "type": "github" } ], - "time": "2026-01-30T17:12:46+00:00" + "time": "2026-02-23T15:04:35+00:00" }, { "name": "phpunit/php-code-coverage", @@ -11596,23 +11600,23 @@ }, { "name": "spatie/laravel-ignition", - "version": "2.10.0", + "version": "2.11.0", "source": { "type": "git", "url": "https://github.com/spatie/laravel-ignition.git", - "reference": "2abefdcca6074a9155f90b4ccb3345af8889d5f5" + "reference": "11f38d1ff7abc583a61c96bf3c1b03610a69cccd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/2abefdcca6074a9155f90b4ccb3345af8889d5f5", - "reference": "2abefdcca6074a9155f90b4ccb3345af8889d5f5", + "url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/11f38d1ff7abc583a61c96bf3c1b03610a69cccd", + "reference": "11f38d1ff7abc583a61c96bf3c1b03610a69cccd", "shasum": "" }, "require": { "ext-curl": "*", "ext-json": "*", "ext-mbstring": "*", - "illuminate/support": "^11.0|^12.0", + "illuminate/support": "^11.0|^12.0|^13.0", "nesbot/carbon": "^2.72|^3.0", "php": "^8.2", "spatie/ignition": "^1.15.1", @@ -11620,10 +11624,10 @@ "symfony/var-dumper": "^7.4|^8.0" }, "require-dev": { - "livewire/livewire": "^3.7.0|^4.0", + "livewire/livewire": "^3.7.0|^4.0|dev-josh/v3-laravel-13-support", "mockery/mockery": "^1.6.12", - "openai-php/client": "^0.10.3", - "orchestra/testbench": "^v9.16.0|^10.6", + "openai-php/client": "^0.10.3|^0.19", + "orchestra/testbench": "^v9.16.0|^10.6|^11.0", "pestphp/pest": "^3.7|^4.0", "phpstan/extension-installer": "^1.4.3", "phpstan/phpstan-deprecation-rules": "^2.0.3", @@ -11684,20 +11688,20 @@ "type": "github" } ], - "time": "2026-01-20T13:16:11+00:00" + "time": "2026-02-22T19:14:05+00:00" }, { "name": "symfony/config", - "version": "v7.4.4", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "4275b53b8ab0cf37f48bf273dc2285c8178efdfb" + "reference": "9400e2f9226b3b64ebb0a8ae967ae84e54e39640" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/4275b53b8ab0cf37f48bf273dc2285c8178efdfb", - "reference": "4275b53b8ab0cf37f48bf273dc2285c8178efdfb", + "url": "https://api.github.com/repos/symfony/config/zipball/9400e2f9226b3b64ebb0a8ae967ae84e54e39640", + "reference": "9400e2f9226b3b64ebb0a8ae967ae84e54e39640", "shasum": "" }, "require": { @@ -11743,7 +11747,7 @@ "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/config/tree/v7.4.4" + "source": "https://github.com/symfony/config/tree/v7.4.6" }, "funding": [ { @@ -11763,20 +11767,20 @@ "type": "tidelift" } ], - "time": "2026-01-13T11:36:38+00:00" + "time": "2026-02-25T16:50:00+00:00" }, { "name": "symfony/dependency-injection", - "version": "v7.4.5", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "76a02cddca45a5254479ad68f9fa274ead0a7ef2" + "reference": "a3f7d594ca53a34a7d39ae683fbca09408b0c598" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/76a02cddca45a5254479ad68f9fa274ead0a7ef2", - "reference": "76a02cddca45a5254479ad68f9fa274ead0a7ef2", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/a3f7d594ca53a34a7d39ae683fbca09408b0c598", + "reference": "a3f7d594ca53a34a7d39ae683fbca09408b0c598", "shasum": "" }, "require": { @@ -11827,7 +11831,7 @@ "description": "Allows you to standardize and centralize the way objects are constructed in your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dependency-injection/tree/v7.4.5" + "source": "https://github.com/symfony/dependency-injection/tree/v7.4.6" }, "funding": [ { @@ -11847,20 +11851,20 @@ "type": "tidelift" } ], - "time": "2026-01-27T16:16:02+00:00" + "time": "2026-02-25T16:50:00+00:00" }, { "name": "symfony/filesystem", - "version": "v7.4.0", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "d551b38811096d0be9c4691d406991b47c0c630a" + "reference": "3ebc794fa5315e59fd122561623c2e2e4280538e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/d551b38811096d0be9c4691d406991b47c0c630a", - "reference": "d551b38811096d0be9c4691d406991b47c0c630a", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/3ebc794fa5315e59fd122561623c2e2e4280538e", + "reference": "3ebc794fa5315e59fd122561623c2e2e4280538e", "shasum": "" }, "require": { @@ -11897,7 +11901,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v7.4.0" + "source": "https://github.com/symfony/filesystem/tree/v7.4.6" }, "funding": [ { @@ -11917,7 +11921,7 @@ "type": "tidelift" } ], - "time": "2025-11-27T13:27:24+00:00" + "time": "2026-02-25T16:50:00+00:00" }, { "name": "theseer/tokenizer", @@ -11973,14 +11977,14 @@ "aliases": [], "minimum-stability": "stable", "stability-flags": { - "phpmd/phpmd": 0, - "smartpings/php-sdk": 20 + "smartpings/php-sdk": 20, + "phpmd/phpmd": 0 }, "prefer-stable": true, "prefer-lowest": false, "platform": { "php": "^8.2" }, - "platform-dev": {}, - "plugin-api-version": "2.9.0" + "platform-dev": [], + "plugin-api-version": "2.6.0" } diff --git a/config/app.php b/config/app.php index 0a241bdc..ec8f29b3 100644 --- a/config/app.php +++ b/config/app.php @@ -185,4 +185,14 @@ // 'Example' => App\Facades\Example::class, ])->toArray(), + + /* + |-------------------------------------------------------------------------- + | Convert Myself to Transfer + |-------------------------------------------------------------------------- + | + | Toggle feature to convert "myself" party to "transfer" type when creating a transaction. This is useful for users who want to track their own transactions without creating a separate party for themselves. + | + */ + 'convert_myself_to_transfer' => env('CONVERT_MYSELF_TO_TRANSFER', true), ]; diff --git a/database/migrations/2024_07_20_125104_create_parties_table.php b/database/migrations/2024_07_20_125104_create_parties_table.php index 22ad565b..5fc1886b 100644 --- a/database/migrations/2024_07_20_125104_create_parties_table.php +++ b/database/migrations/2024_07_20_125104_create_parties_table.php @@ -16,6 +16,7 @@ public function up(): void $table->string('type')->nullable(); $table->text('description')->nullable(); $table->unsignedBigInteger('user_id'); + $table->boolean('is_myself')->default(false); $table->timestamps(); $table->softDeletes(); diff --git a/phpunit.xml b/phpunit.xml index 2f813bae..6dc4ca7d 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -24,13 +24,16 @@ - - + + + + + diff --git a/tests/Feature/MyselfTransferTest.php b/tests/Feature/MyselfTransferTest.php new file mode 100644 index 00000000..96b51efd --- /dev/null +++ b/tests/Feature/MyselfTransferTest.php @@ -0,0 +1,90 @@ +create(); + $walletSource = Wallet::factory()->create(['user_id' => $user->id, 'balance' => 1000]); + $walletDest = Wallet::factory()->create(['user_id' => $user->id, 'balance' => 0]); + + // Create the "Myself" Party + $myselfParty = Party::factory()->create([ + 'user_id' => $user->id, + 'is_myself' => true, + 'name' => 'My Personal Cash' + ]); + + $fakeClientId = \Illuminate\Support\Str::uuid() . ':' . \Illuminate\Support\Str::uuid(); + // 2. The Payload + $payload = [ + 'amount' => 200, // Validator needs 'amount' + 'type' => 'income', // Validator needs 'type' + 'party_id' => $myselfParty->id, // Triggers the 'is_myself' logic + 'wallet_id' => $walletDest->id, // Becomes 'toWallet' + 'from_wallet_id' => $walletSource->id, // Becomes 'fromWallet' + 'client_id' => $fakeClientId, // To ensure idempotency in tests + 'description' => 'Moving cash to bank', + 'datetime' => now()->toIso8601String(), + ]; + + // 3. Act + $response = $this->actingAs($user, 'sanctum') + ->postJson('/api/v1/transactions', $payload); + + // 4. Assertions + $response->assertStatus(201); + + // Check if a Transfer record was created instead of just a loose transaction + $this->assertDatabaseHas('transfers', [ + 'from_wallet_id' => $walletSource->id, + 'to_wallet_id' => $walletDest->id, + 'amount' => 200, + ]); + + // Check balances + $this->assertEquals(800, $walletSource->fresh()->balance); + $this->assertEquals(200, $walletDest->fresh()->balance); + + // Ensure the transaction is linked to a transfer + $this->assertNotNull(Transaction::where('amount', 200)->first()->transfer_id); + } + + public function test_normal_income_stays_as_transaction() + { + $user = User::factory()->create(); + $wallet = Wallet::factory()->create(['user_id' => $user->id, 'balance' => 0]); + $normalParty = Party::factory()->create([ + 'is_myself' => false, + 'user_id' => $user->id, + ]); + + $payload = [ + 'amount' => 100, + 'type' => 'income', + 'party_id' => $normalParty->id, + 'wallet_id' => $wallet->id, + ]; + + $this->actingAs($user, 'sanctum') + ->postJson('/api/v1/transactions', $payload) + ->assertStatus(201); + + // Should NOT create a transfer + $this->assertEquals(0, Transfer::count()); + $this->assertEquals(100, $wallet->fresh()->balance); + } +} \ No newline at end of file diff --git a/tests/TestCase.php b/tests/TestCase.php index c806d729..042ba51e 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -17,5 +17,9 @@ protected function setUp(): void $this->artisan('migrate:fresh --seed'); self::$setUpHasRunOnce = true; } + // This tells Laravel to ignore the index error for SQLite + if (config('database.default') === 'sqlite') { + \Illuminate\Support\Facades\Schema::disableForeignKeyConstraints(); + } } } From 7e2d3c43f29e3352d0f49421142180610ea9a746 Mon Sep 17 00:00:00 2001 From: iMercy Date: Fri, 13 Mar 2026 00:41:40 -0400 Subject: [PATCH 02/25] enh: Myself party strictly by config no db change --- .../API/v1/TransactionController.php | 16 +- app/Models/Party.php | 1 - composer.lock | 309 +++++++----------- config/model-configuration.php | 2 + ...2024_07_20_125104_create_parties_table.php | 1 - tests/Feature/MyselfTransferTest.php | 27 +- 6 files changed, 146 insertions(+), 210 deletions(-) diff --git a/app/Http/Controllers/API/v1/TransactionController.php b/app/Http/Controllers/API/v1/TransactionController.php index a13da31b..d89fe67f 100644 --- a/app/Http/Controllers/API/v1/TransactionController.php +++ b/app/Http/Controllers/API/v1/TransactionController.php @@ -278,12 +278,20 @@ public function store(Request $request): JsonResponse $data = $validationResult['data']; $user = $request->user(); - //check if party has been set to 'myself' and convert to transfer if config is enabled - $party = isset($data['party_id']) ? \App\Models\Party::find($data['party_id']) : null; + // find the user's "myself" party ID from configuration + // $party = isset($data['party_id']) ? \App\Models\Party::find($data['party_id']) : null; + $myselfPartyId = \App\Models\Configuration::where('configurable_id', $user->id) + ->where('configurable_type', User::class) + ->where('key', 'myself-party-id') + ->value('value'); + + $isMyself = isset($data['party_id']) && (string)$data['party_id'] === (string)$myselfPartyId; + + //check if feature is enabled and IDs match if ( config('app.convert_myself_to_transfer') && - $party && - $party->is_myself && + $isMyself && + // (int) $data['party_id'] === (int) $myselfPartyId && $request->has('from_wallet_id') ) { $fromWallet = $user->wallets()->findOrFail($request['from_wallet_id']); diff --git a/app/Models/Party.php b/app/Models/Party.php index d3fe5bf9..9f5b9207 100644 --- a/app/Models/Party.php +++ b/app/Models/Party.php @@ -44,7 +44,6 @@ class Party extends Model 'description', 'user_id', 'type', - 'is_myself' ]; protected $appends = ['last_synced_at', 'client_generated_id', 'icon']; diff --git a/composer.lock b/composer.lock index d0a69dd8..1dc71712 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b4401762fee0a682f1d207c388a6663c", + "content-hash": "ad630813bb305eb9fe1e5ac11aed9785", "packages": [ { "name": "beste/clock", @@ -1371,16 +1371,16 @@ }, { "name": "google/gax", - "version": "v1.42.0", + "version": "v1.42.1", "source": { "type": "git", "url": "https://github.com/googleapis/gax-php.git", - "reference": "7ad13e5b9ca201a5f60e7b3342842d1217bc23a6" + "reference": "ab334c4869f4f16b6112acc95972a97b81dbfdda" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/googleapis/gax-php/zipball/7ad13e5b9ca201a5f60e7b3342842d1217bc23a6", - "reference": "7ad13e5b9ca201a5f60e7b3342842d1217bc23a6", + "url": "https://api.github.com/repos/googleapis/gax-php/zipball/ab334c4869f4f16b6112acc95972a97b81dbfdda", + "reference": "ab334c4869f4f16b6112acc95972a97b81dbfdda", "shasum": "" }, "require": { @@ -1388,7 +1388,7 @@ "google/common-protos": "^4.4", "google/grpc-gcp": "^0.4", "google/longrunning": "~0.4", - "google/protobuf": "^4.31", + "google/protobuf": "^4.31||^5.0", "grpc/grpc": "^1.13", "guzzlehttp/promises": "^2.0", "guzzlehttp/psr7": "^2.0", @@ -1399,10 +1399,10 @@ "ext-protobuf": "<4.31.0" }, "require-dev": { + "google/cloud-tools": "^0.16.1", "phpspec/prophecy-phpunit": "^2.1", "phpstan/phpstan": "^2.0", - "phpunit/phpunit": "^9.6", - "squizlabs/php_codesniffer": "4.*" + "phpunit/phpunit": "^9.6" }, "type": "library", "autoload": { @@ -1422,27 +1422,27 @@ ], "support": { "issues": "https://github.com/googleapis/gax-php/issues", - "source": "https://github.com/googleapis/gax-php/tree/v1.42.0" + "source": "https://github.com/googleapis/gax-php/tree/v1.42.1" }, - "time": "2026-01-22T20:31:50+00:00" + "time": "2026-03-12T21:22:20+00:00" }, { "name": "google/grpc-gcp", - "version": "v0.4.1", + "version": "0.4.2", "source": { "type": "git", "url": "https://github.com/GoogleCloudPlatform/grpc-gcp-php.git", - "reference": "e585b7721bbe806ef45b5c52ae43dfc2bff89968" + "reference": "1049c0c15b6a1789fdeb52af688a94d540932469" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/GoogleCloudPlatform/grpc-gcp-php/zipball/e585b7721bbe806ef45b5c52ae43dfc2bff89968", - "reference": "e585b7721bbe806ef45b5c52ae43dfc2bff89968", + "url": "https://api.github.com/repos/GoogleCloudPlatform/grpc-gcp-php/zipball/1049c0c15b6a1789fdeb52af688a94d540932469", + "reference": "1049c0c15b6a1789fdeb52af688a94d540932469", "shasum": "" }, "require": { "google/auth": "^1.3", - "google/protobuf": "^v3.25.3||^4.26.1", + "google/protobuf": "^v3.25.3||^4.26.1||^5.0", "grpc/grpc": "^v1.13.0", "php": "^8.0", "psr/cache": "^1.0.1||^2.0.0||^3.0.0" @@ -1467,9 +1467,9 @@ "description": "gRPC GCP library for channel management", "support": { "issues": "https://github.com/GoogleCloudPlatform/grpc-gcp-php/issues", - "source": "https://github.com/GoogleCloudPlatform/grpc-gcp-php/tree/v0.4.1" + "source": "https://github.com/GoogleCloudPlatform/grpc-gcp-php/tree/v0.4.2" }, - "time": "2025-02-19T21:53:22+00:00" + "time": "2026-03-12T22:56:09+00:00" }, { "name": "google/longrunning", @@ -1876,16 +1876,16 @@ }, { "name": "guzzlehttp/psr7", - "version": "2.8.0", + "version": "2.9.0", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "21dc724a0583619cd1652f673303492272778051" + "reference": "7d0ed42f28e42d61352a7a79de682e5e67fec884" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/21dc724a0583619cd1652f673303492272778051", - "reference": "21dc724a0583619cd1652f673303492272778051", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/7d0ed42f28e42d61352a7a79de682e5e67fec884", + "reference": "7d0ed42f28e42d61352a7a79de682e5e67fec884", "shasum": "" }, "require": { @@ -1901,6 +1901,7 @@ "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", "http-interop/http-factory-tests": "0.9.0", + "jshttp/mime-db": "1.54.0.1", "phpunit/phpunit": "^8.5.44 || ^9.6.25" }, "suggest": { @@ -1972,7 +1973,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.8.0" + "source": "https://github.com/guzzle/psr7/tree/2.9.0" }, "funding": [ { @@ -1988,7 +1989,7 @@ "type": "tidelift" } ], - "time": "2025-08-23T21:21:41+00:00" + "time": "2026-03-10T16:41:02+00:00" }, { "name": "guzzlehttp/uri-template", @@ -2547,16 +2548,16 @@ }, { "name": "laravel/prompts", - "version": "v0.3.13", + "version": "v0.3.14", "source": { "type": "git", "url": "https://github.com/laravel/prompts.git", - "reference": "ed8c466571b37e977532fb2fd3c272c784d7050d" + "reference": "9f0e371244eedfe2ebeaa72c79c54bb5df6e0176" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/prompts/zipball/ed8c466571b37e977532fb2fd3c272c784d7050d", - "reference": "ed8c466571b37e977532fb2fd3c272c784d7050d", + "url": "https://api.github.com/repos/laravel/prompts/zipball/9f0e371244eedfe2ebeaa72c79c54bb5df6e0176", + "reference": "9f0e371244eedfe2ebeaa72c79c54bb5df6e0176", "shasum": "" }, "require": { @@ -2600,9 +2601,9 @@ "description": "Add beautiful and user-friendly forms to your command-line applications.", "support": { "issues": "https://github.com/laravel/prompts/issues", - "source": "https://github.com/laravel/prompts/tree/v0.3.13" + "source": "https://github.com/laravel/prompts/tree/v0.3.14" }, - "time": "2026-02-06T12:17:10+00:00" + "time": "2026-03-01T09:02:38+00:00" }, { "name": "laravel/sanctum", @@ -2730,16 +2731,16 @@ }, { "name": "laravel/socialite", - "version": "v5.24.3", + "version": "v5.25.0", "source": { "type": "git", "url": "https://github.com/laravel/socialite.git", - "reference": "0feb62267e7b8abc68593ca37639ad302728c129" + "reference": "231f572e1a37c9ca1fb8085e9fb8608285beafb3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/socialite/zipball/0feb62267e7b8abc68593ca37639ad302728c129", - "reference": "0feb62267e7b8abc68593ca37639ad302728c129", + "url": "https://api.github.com/repos/laravel/socialite/zipball/231f572e1a37c9ca1fb8085e9fb8608285beafb3", + "reference": "231f572e1a37c9ca1fb8085e9fb8608285beafb3", "shasum": "" }, "require": { @@ -2798,7 +2799,7 @@ "issues": "https://github.com/laravel/socialite/issues", "source": "https://github.com/laravel/socialite" }, - "time": "2026-02-21T13:32:50+00:00" + "time": "2026-02-27T13:56:35+00:00" }, { "name": "laravel/tinker", @@ -2941,16 +2942,16 @@ }, { "name": "league/commonmark", - "version": "2.8.0", + "version": "2.8.1", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "4efa10c1e56488e658d10adf7b7b7dcd19940bfb" + "reference": "84b1ca48347efdbe775426f108622a42735a6579" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/4efa10c1e56488e658d10adf7b7b7dcd19940bfb", - "reference": "4efa10c1e56488e658d10adf7b7b7dcd19940bfb", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/84b1ca48347efdbe775426f108622a42735a6579", + "reference": "84b1ca48347efdbe775426f108622a42735a6579", "shasum": "" }, "require": { @@ -2975,9 +2976,9 @@ "phpstan/phpstan": "^1.8.2", "phpunit/phpunit": "^9.5.21 || ^10.5.9 || ^11.0.0", "scrutinizer/ocular": "^1.8.1", - "symfony/finder": "^5.3 | ^6.0 | ^7.0", - "symfony/process": "^5.4 | ^6.0 | ^7.0", - "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 | ^7.0", + "symfony/finder": "^5.3 | ^6.0 | ^7.0 || ^8.0", + "symfony/process": "^5.4 | ^6.0 | ^7.0 || ^8.0", + "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 | ^7.0 || ^8.0", "unleashedtech/php-coding-standard": "^3.1.1", "vimeo/psalm": "^4.24.0 || ^5.0.0 || ^6.0.0" }, @@ -3044,7 +3045,7 @@ "type": "tidelift" } ], - "time": "2025-11-26T21:48:24+00:00" + "time": "2026-03-05T21:37:03+00:00" }, { "name": "league/config", @@ -3745,16 +3746,16 @@ }, { "name": "nesbot/carbon", - "version": "3.11.1", + "version": "3.11.3", "source": { "type": "git", "url": "https://github.com/CarbonPHP/carbon.git", - "reference": "f438fcc98f92babee98381d399c65336f3a3827f" + "reference": "6a7e652845bb018c668220c2a545aded8594fbbf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/f438fcc98f92babee98381d399c65336f3a3827f", - "reference": "f438fcc98f92babee98381d399c65336f3a3827f", + "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/6a7e652845bb018c668220c2a545aded8594fbbf", + "reference": "6a7e652845bb018c668220c2a545aded8594fbbf", "shasum": "" }, "require": { @@ -3846,7 +3847,7 @@ "type": "tidelift" } ], - "time": "2026-01-29T09:26:29+00:00" + "time": "2026-03-11T17:23:39+00:00" }, { "name": "nette/schema", @@ -4965,16 +4966,16 @@ }, { "name": "psy/psysh", - "version": "v0.12.20", + "version": "v0.12.21", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "19678eb6b952a03b8a1d96ecee9edba518bb0373" + "reference": "4821fab5b7cd8c49a673a9fd5754dc9162bb9e97" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/19678eb6b952a03b8a1d96ecee9edba518bb0373", - "reference": "19678eb6b952a03b8a1d96ecee9edba518bb0373", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/4821fab5b7cd8c49a673a9fd5754dc9162bb9e97", + "reference": "4821fab5b7cd8c49a673a9fd5754dc9162bb9e97", "shasum": "" }, "require": { @@ -5038,9 +5039,9 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.12.20" + "source": "https://github.com/bobthecow/psysh/tree/v0.12.21" }, - "time": "2026-02-11T15:05:28+00:00" + "time": "2026-03-06T21:21:28+00:00" }, { "name": "ralouphie/getallheaders", @@ -5410,16 +5411,16 @@ }, { "name": "symfony/cache", - "version": "v7.4.6", + "version": "v7.4.7", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "1d06192e8f164e2729b0031e6807d72a6195b8bb" + "reference": "665522ec357540e66c294c08583b40ee576574f0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/1d06192e8f164e2729b0031e6807d72a6195b8bb", - "reference": "1d06192e8f164e2729b0031e6807d72a6195b8bb", + "url": "https://api.github.com/repos/symfony/cache/zipball/665522ec357540e66c294c08583b40ee576574f0", + "reference": "665522ec357540e66c294c08583b40ee576574f0", "shasum": "" }, "require": { @@ -5490,7 +5491,7 @@ "psr6" ], "support": { - "source": "https://github.com/symfony/cache/tree/v7.4.6" + "source": "https://github.com/symfony/cache/tree/v7.4.7" }, "funding": [ { @@ -5510,7 +5511,7 @@ "type": "tidelift" } ], - "time": "2026-02-21T23:29:27+00:00" + "time": "2026-03-06T08:14:57+00:00" }, { "name": "symfony/cache-contracts", @@ -5668,16 +5669,16 @@ }, { "name": "symfony/console", - "version": "v7.4.6", + "version": "v7.4.7", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "6d643a93b47398599124022eb24d97c153c12f27" + "reference": "e1e6770440fb9c9b0cf725f81d1361ad1835329d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/6d643a93b47398599124022eb24d97c153c12f27", - "reference": "6d643a93b47398599124022eb24d97c153c12f27", + "url": "https://api.github.com/repos/symfony/console/zipball/e1e6770440fb9c9b0cf725f81d1361ad1835329d", + "reference": "e1e6770440fb9c9b0cf725f81d1361ad1835329d", "shasum": "" }, "require": { @@ -5742,7 +5743,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.4.6" + "source": "https://github.com/symfony/console/tree/v7.4.7" }, "funding": [ { @@ -5762,7 +5763,7 @@ "type": "tidelift" } ], - "time": "2026-02-25T17:02:47+00:00" + "time": "2026-03-06T14:06:20+00:00" }, { "name": "symfony/css-selector", @@ -6213,16 +6214,16 @@ }, { "name": "symfony/http-foundation", - "version": "v7.4.6", + "version": "v7.4.7", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "fd97d5e926e988a363cef56fbbf88c5c528e9065" + "reference": "f94b3e7b7dafd40e666f0c9ff2084133bae41e81" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/fd97d5e926e988a363cef56fbbf88c5c528e9065", - "reference": "fd97d5e926e988a363cef56fbbf88c5c528e9065", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/f94b3e7b7dafd40e666f0c9ff2084133bae41e81", + "reference": "f94b3e7b7dafd40e666f0c9ff2084133bae41e81", "shasum": "" }, "require": { @@ -6271,7 +6272,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v7.4.6" + "source": "https://github.com/symfony/http-foundation/tree/v7.4.7" }, "funding": [ { @@ -6291,20 +6292,20 @@ "type": "tidelift" } ], - "time": "2026-02-21T16:25:55+00:00" + "time": "2026-03-06T13:15:18+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.4.6", + "version": "v7.4.7", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "002ac0cf4cd972a7fd0912dcd513a95e8a81ce83" + "reference": "3b3fcf386c809be990c922e10e4c620d6367cab1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/002ac0cf4cd972a7fd0912dcd513a95e8a81ce83", - "reference": "002ac0cf4cd972a7fd0912dcd513a95e8a81ce83", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/3b3fcf386c809be990c922e10e4c620d6367cab1", + "reference": "3b3fcf386c809be990c922e10e4c620d6367cab1", "shasum": "" }, "require": { @@ -6390,7 +6391,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v7.4.6" + "source": "https://github.com/symfony/http-kernel/tree/v7.4.7" }, "funding": [ { @@ -6410,7 +6411,7 @@ "type": "tidelift" } ], - "time": "2026-02-26T08:30:57+00:00" + "time": "2026-03-06T16:33:18+00:00" }, { "name": "symfony/mailer", @@ -6498,16 +6499,16 @@ }, { "name": "symfony/mime", - "version": "v7.4.6", + "version": "v7.4.7", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "9fc881d95feae4c6c48678cb6372bd8a7ba04f5f" + "reference": "da5ab4fde3f6c88ab06e96185b9922f48b677cd1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/9fc881d95feae4c6c48678cb6372bd8a7ba04f5f", - "reference": "9fc881d95feae4c6c48678cb6372bd8a7ba04f5f", + "url": "https://api.github.com/repos/symfony/mime/zipball/da5ab4fde3f6c88ab06e96185b9922f48b677cd1", + "reference": "da5ab4fde3f6c88ab06e96185b9922f48b677cd1", "shasum": "" }, "require": { @@ -6563,7 +6564,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v7.4.6" + "source": "https://github.com/symfony/mime/tree/v7.4.7" }, "funding": [ { @@ -6583,7 +6584,7 @@ "type": "tidelift" } ], - "time": "2026-02-05T15:57:06+00:00" + "time": "2026-03-05T15:24:09+00:00" }, { "name": "symfony/polyfill-ctype", @@ -9224,16 +9225,16 @@ }, { "name": "laravel/pint", - "version": "v1.27.1", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "54cca2de13790570c7b6f0f94f37896bee4abcb5" + "reference": "bdec963f53172c5e36330f3a400604c69bf02d39" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/54cca2de13790570c7b6f0f94f37896bee4abcb5", - "reference": "54cca2de13790570c7b6f0f94f37896bee4abcb5", + "url": "https://api.github.com/repos/laravel/pint/zipball/bdec963f53172c5e36330f3a400604c69bf02d39", + "reference": "bdec963f53172c5e36330f3a400604c69bf02d39", "shasum": "" }, "require": { @@ -9244,13 +9245,14 @@ "php": "^8.2.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.93.1", - "illuminate/view": "^12.51.0", - "larastan/larastan": "^3.9.2", + "friendsofphp/php-cs-fixer": "^3.94.2", + "illuminate/view": "^12.54.1", + "larastan/larastan": "^3.9.3", "laravel-zero/framework": "^12.0.5", "mockery/mockery": "^1.6.12", - "nunomaduro/termwind": "^2.3.3", - "pestphp/pest": "^3.8.5" + "nunomaduro/termwind": "^2.4.0", + "pestphp/pest": "^3.8.6", + "shipfastlabs/agent-detector": "^1.1.0" }, "bin": [ "builds/pint" @@ -9287,7 +9289,7 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2026-02-10T20:00:20+00:00" + "time": "2026-03-12T15:51:39+00:00" }, { "name": "laravel/sail", @@ -11294,16 +11296,16 @@ }, { "name": "spatie/backtrace", - "version": "1.8.1", + "version": "1.8.2", "source": { "type": "git", "url": "https://github.com/spatie/backtrace.git", - "reference": "8c0f16a59ae35ec8c62d85c3c17585158f430110" + "reference": "8ffe78be5ed355b5009e3dd989d183433e9a5adc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/backtrace/zipball/8c0f16a59ae35ec8c62d85c3c17585158f430110", - "reference": "8c0f16a59ae35ec8c62d85c3c17585158f430110", + "url": "https://api.github.com/repos/spatie/backtrace/zipball/8ffe78be5ed355b5009e3dd989d183433e9a5adc", + "reference": "8ffe78be5ed355b5009e3dd989d183433e9a5adc", "shasum": "" }, "require": { @@ -11314,7 +11316,7 @@ "laravel/serializable-closure": "^1.3 || ^2.0", "phpunit/phpunit": "^9.3 || ^11.4.3", "spatie/phpunit-snapshot-assertions": "^4.2 || ^5.1.6", - "symfony/var-dumper": "^5.1 || ^6.0 || ^7.0" + "symfony/var-dumper": "^5.1|^6.0|^7.0|^8.0" }, "type": "library", "autoload": { @@ -11342,7 +11344,7 @@ ], "support": { "issues": "https://github.com/spatie/backtrace/issues", - "source": "https://github.com/spatie/backtrace/tree/1.8.1" + "source": "https://github.com/spatie/backtrace/tree/1.8.2" }, "funding": [ { @@ -11354,7 +11356,7 @@ "type": "other" } ], - "time": "2025-08-26T08:22:30+00:00" + "time": "2026-03-11T13:48:28+00:00" }, { "name": "spatie/error-solutions", @@ -11753,97 +11755,18 @@ ], "time": "2025-11-10T16:43:36+00:00" }, - { - "name": "squizlabs/php_codesniffer", - "version": "4.0.1", - "source": { - "type": "git", - "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "0525c73950de35ded110cffafb9892946d7771b5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/0525c73950de35ded110cffafb9892946d7771b5", - "reference": "0525c73950de35ded110cffafb9892946d7771b5", - "shasum": "" - }, - "require": { - "ext-simplexml": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": ">=7.2.0" - }, - "require-dev": { - "phpunit/phpunit": "^8.4.0 || ^9.3.4 || ^10.5.32 || 11.3.3 - 11.5.28 || ^11.5.31" - }, - "bin": [ - "bin/phpcbf", - "bin/phpcs" - ], - "type": "library", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Greg Sherwood", - "role": "Former lead" - }, - { - "name": "Juliette Reinders Folmer", - "role": "Current lead" - }, - { - "name": "Contributors", - "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" - } - ], - "description": "PHP_CodeSniffer tokenizes PHP files and detects violations of a defined set of coding standards.", - "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", - "keywords": [ - "phpcs", - "standards", - "static analysis" - ], - "support": { - "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", - "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", - "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", - "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" - }, - "funding": [ - { - "url": "https://github.com/PHPCSStandards", - "type": "github" - }, - { - "url": "https://github.com/jrfnl", - "type": "github" - }, - { - "url": "https://opencollective.com/php_codesniffer", - "type": "open_collective" - }, - { - "url": "https://thanks.dev/u/gh/phpcsstandards", - "type": "thanks_dev" - } - ], - "time": "2025-11-10T16:43:36+00:00" - }, { "name": "symfony/config", - "version": "v7.4.6", + "version": "v7.4.7", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "9400e2f9226b3b64ebb0a8ae967ae84e54e39640" + "reference": "6c17162555bfb58957a55bb0e43e00035b6ae3d5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/9400e2f9226b3b64ebb0a8ae967ae84e54e39640", - "reference": "9400e2f9226b3b64ebb0a8ae967ae84e54e39640", + "url": "https://api.github.com/repos/symfony/config/zipball/6c17162555bfb58957a55bb0e43e00035b6ae3d5", + "reference": "6c17162555bfb58957a55bb0e43e00035b6ae3d5", "shasum": "" }, "require": { @@ -11889,7 +11812,7 @@ "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/config/tree/v7.4.6" + "source": "https://github.com/symfony/config/tree/v7.4.7" }, "funding": [ { @@ -11909,20 +11832,20 @@ "type": "tidelift" } ], - "time": "2026-02-25T16:50:00+00:00" + "time": "2026-03-06T10:41:14+00:00" }, { "name": "symfony/dependency-injection", - "version": "v7.4.6", + "version": "v7.4.7", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "a3f7d594ca53a34a7d39ae683fbca09408b0c598" + "reference": "0f651e58f4917fb0e2cd261ccbfe3d71e6e0f5db" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/a3f7d594ca53a34a7d39ae683fbca09408b0c598", - "reference": "a3f7d594ca53a34a7d39ae683fbca09408b0c598", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/0f651e58f4917fb0e2cd261ccbfe3d71e6e0f5db", + "reference": "0f651e58f4917fb0e2cd261ccbfe3d71e6e0f5db", "shasum": "" }, "require": { @@ -11973,7 +11896,7 @@ "description": "Allows you to standardize and centralize the way objects are constructed in your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dependency-injection/tree/v7.4.6" + "source": "https://github.com/symfony/dependency-injection/tree/v7.4.7" }, "funding": [ { @@ -11993,7 +11916,7 @@ "type": "tidelift" } ], - "time": "2026-02-25T16:50:00+00:00" + "time": "2026-03-03T07:48:48+00:00" }, { "name": "symfony/filesystem", @@ -12119,9 +12042,9 @@ "aliases": [], "minimum-stability": "stable", "stability-flags": { - "phpmd/phpmd": 0, "smartpings/php-sdk": 20, - "whilesmart/eloquent-roles": 20 + "whilesmart/eloquent-roles": 20, + "phpmd/phpmd": 0 }, "prefer-stable": true, "prefer-lowest": false, diff --git a/config/model-configuration.php b/config/model-configuration.php index a3f5354f..03c7dc7d 100644 --- a/config/model-configuration.php +++ b/config/model-configuration.php @@ -23,6 +23,8 @@ 'notifications-insights' => 'boolean', 'notifications-inactivity' => 'boolean', 'insights-frequency' => 'string|in:daily,weekly,monthly', + + 'myself-party-id' => 'integer', // store the "Myself" Party ID ], 'model' => \App\Models\Configuration::class, 'hooks' => [ModelConfigurationFilterHook::class], diff --git a/database/migrations/2024_07_20_125104_create_parties_table.php b/database/migrations/2024_07_20_125104_create_parties_table.php index 5fc1886b..22ad565b 100644 --- a/database/migrations/2024_07_20_125104_create_parties_table.php +++ b/database/migrations/2024_07_20_125104_create_parties_table.php @@ -16,7 +16,6 @@ public function up(): void $table->string('type')->nullable(); $table->text('description')->nullable(); $table->unsignedBigInteger('user_id'); - $table->boolean('is_myself')->default(false); $table->timestamps(); $table->softDeletes(); diff --git a/tests/Feature/MyselfTransferTest.php b/tests/Feature/MyselfTransferTest.php index 96b51efd..30f34b5f 100644 --- a/tests/Feature/MyselfTransferTest.php +++ b/tests/Feature/MyselfTransferTest.php @@ -7,27 +7,33 @@ use App\Models\Wallet; use App\Models\Transaction; use App\Models\Transfer; -use Illuminate\Foundation\Testing\DatabaseMigrations; +use Illuminate\Foundation\Testing\RefreshDatabase; use Tests\TestCase; class MyselfTransferTest extends TestCase { - use DatabaseMigrations; + use RefreshDatabase; public function test_income_from_myself_is_handled_as_transfer() { // 1. Setup Data $user = User::factory()->create(); + //create the myself party + $myselfParty = Party::factory()->create([ 'user_id' => $user->id]); $walletSource = Wallet::factory()->create(['user_id' => $user->id, 'balance' => 1000]); $walletDest = Wallet::factory()->create(['user_id' => $user->id, 'balance' => 0]); - - // Create the "Myself" Party - $myselfParty = Party::factory()->create([ - 'user_id' => $user->id, - 'is_myself' => true, - 'name' => 'My Personal Cash' + + config(['app.convert_myself_to_transfer' => true]); // Ensure the feature is enabled for the test + + //manually set the configuration for this user + \App\Models\Configuration::create([ + 'configurable_id' => $user->id, + 'configurable_type' => User::class, + 'key' => 'myself-party-id', + 'value' => $myselfParty->id, ]); + $fakeClientId = \Illuminate\Support\Str::uuid() . ':' . \Illuminate\Support\Str::uuid(); // 2. The Payload $payload = [ @@ -58,7 +64,7 @@ public function test_income_from_myself_is_handled_as_transfer() // Check balances $this->assertEquals(800, $walletSource->fresh()->balance); $this->assertEquals(200, $walletDest->fresh()->balance); - + // Ensure the transaction is linked to a transfer $this->assertNotNull(Transaction::where('amount', 200)->first()->transfer_id); } @@ -68,7 +74,6 @@ public function test_normal_income_stays_as_transaction() $user = User::factory()->create(); $wallet = Wallet::factory()->create(['user_id' => $user->id, 'balance' => 0]); $normalParty = Party::factory()->create([ - 'is_myself' => false, 'user_id' => $user->id, ]); @@ -87,4 +92,4 @@ public function test_normal_income_stays_as_transaction() $this->assertEquals(0, Transfer::count()); $this->assertEquals(100, $wallet->fresh()->balance); } -} \ No newline at end of file +} From 81cc8e3cd9c9a40d9c61dfb5980f0aaeb007b356 Mon Sep 17 00:00:00 2001 From: iMercy Date: Fri, 13 Mar 2026 01:54:24 -0400 Subject: [PATCH 03/25] chore: Add helper functions in store 2 pass md test --- .../API/v1/TransactionController.php | 115 ++++++++++-------- public/docs/api.json | 4 + 2 files changed, 68 insertions(+), 51 deletions(-) diff --git a/app/Http/Controllers/API/v1/TransactionController.php b/app/Http/Controllers/API/v1/TransactionController.php index d89fe67f..14967011 100644 --- a/app/Http/Controllers/API/v1/TransactionController.php +++ b/app/Http/Controllers/API/v1/TransactionController.php @@ -249,27 +249,7 @@ enum: ['daily', 'weekly', 'monthly', 'yearly'] */ public function store(Request $request): JsonResponse { - $validationResult = $this->validateRequest($request, [ - 'client_id' => ['nullable', 'string', new ValidateClientId()], - 'amount' => 'required|numeric|min:0.01', - 'type' => 'required|string|in:income,expense', - 'description' => 'nullable|string', - 'datetime' => ['nullable', new Iso8601DateTime()], - 'created_at' => ['nullable', new Iso8601DateTime()], - 'group_id' => 'nullable|integer|exists:groups,id', - 'party_id' => 'nullable|integer|exists:parties,id', - 'wallet_id' => 'required|integer|exists:wallets,id', - 'categories' => 'nullable|array', - 'is_recurring' => 'nullable|boolean', - 'recurrence_period' => 'nullable|string|in:daily,weekly,monthly,yearly', - 'recurrence_interval' => 'nullable|integer|min:1', - 'recurrence_ends_at' => ['nullable', 'date', 'after:today', new Iso8601DateTime()], - 'categories.*' => 'integer|exists:categories,id', - 'files' => 'nullable|array', - 'files.*' => 'file|mimes:jpg,jpeg,png,pdf|max:1240', - - 'from_wallet_id' => 'required_if:convert_myself_to_transfer,true|integer|exists:wallets,id', - ]); + $validationResult = $this->validateRequestData($request); if (! $validationResult['isValidated']) { return $this->failure($validationResult['message'], $validationResult['code'], $validationResult['errors']); @@ -278,40 +258,12 @@ public function store(Request $request): JsonResponse $data = $validationResult['data']; $user = $request->user(); - // find the user's "myself" party ID from configuration - // $party = isset($data['party_id']) ? \App\Models\Party::find($data['party_id']) : null; - $myselfPartyId = \App\Models\Configuration::where('configurable_id', $user->id) - ->where('configurable_type', User::class) - ->where('key', 'myself-party-id') - ->value('value'); - - $isMyself = isset($data['party_id']) && (string)$data['party_id'] === (string)$myselfPartyId; - //check if feature is enabled and IDs match if ( - config('app.convert_myself_to_transfer') && - $isMyself && - // (int) $data['party_id'] === (int) $myselfPartyId && - $request->has('from_wallet_id') + $this->isMyselfTransfer($data, $user) ) { - $fromWallet = $user->wallets()->findOrFail($request['from_wallet_id']); - $toWallet = $user->wallets()->findOrFail($data['wallet_id']); - - $transfer = $this->transferService->transfer( - amountToSend: (float) $data['amount'], - fromWallet: $fromWallet, - amountToReceive: (float) $data['amount'], - toWallet: $toWallet, - user: $user, - exchangeRate: 1.0, - datetime: $data['datetime'] ?? null, - transactionClientIds: [ - 'income_transaction_client_id' => $data['client_id'] ?? null - ] - ); - //return response here to prevent controller from creating a third transaction below - return $this->success($transfer, statusCode: 201); + return $this->handleMyselfTransfer($data, $user, $request); } @@ -868,4 +820,65 @@ private function validateResourceOwnership(array $data, array $categories = []): } } } + + private function validateRequestData(Request $request): array + { + return $this->validateRequest($request, [ + 'client_id' => ['nullable', 'string', new ValidateClientId()], + 'amount' => 'required|numeric|min:0.01', + 'type' => 'required|string|in:income,expense', + 'description' => 'nullable|string', + 'datetime' => ['nullable', new Iso8601DateTime()], + 'created_at' => ['nullable', new Iso8601DateTime()], + 'group_id' => 'nullable|integer|exists:groups,id', + 'party_id' => 'nullable|integer|exists:parties,id', + 'wallet_id' => 'required|integer|exists:wallets,id', + 'categories' => 'nullable|array', + 'is_recurring' => 'nullable|boolean', + 'recurrence_period' => 'nullable|string|in:daily,weekly,monthly,yearly', + 'recurrence_interval' => 'nullable|integer|min:1', + 'recurrence_ends_at' => ['nullable', 'date', 'after:today', new Iso8601DateTime()], + 'categories.*' => 'integer|exists:categories,id', + 'files' => 'nullable|array', + 'files.*' => 'file|mimes:jpg,jpeg,png,pdf|max:1240', + + 'from_wallet_id' => 'required_if:convert_myself_to_transfer,true|integer|exists:wallets,id', + ]); + + } + + private function isMyselfTransfer($data, $user, $request): bool + { + // find the user's "myself" party ID from configuration + $myselfPartyId = \App\Models\Configuration::where('configurable_id', $user->id) + ->where('configurable_type', User::class) + ->where('key', 'myself-party-id') + ->value('value'); + + $isMyself = isset($data['party_id']) && (string)$data['party_id'] === (string)$myselfPartyId; + + return config('app.convert_myself_to_transfer') && + $isMyself && + $request->has('from_wallet_id'); + + } + + private function handleMyselfTransfer($data, $user, $request) + { + $fromWallet = $user->wallets()->findOrFail($request['from_wallet_id']); + $toWallet = $user->wallets()->findOrFail($data['wallet_id']); + + return $this->transferService->transfer( + amountToSend: (float) $data['amount'], + fromWallet: $fromWallet, + amountToReceive: (float) $data['amount'], + toWallet: $toWallet, + user: $user, + exchangeRate: 1.0, + datetime: $data['datetime'] ?? null, + transactionClientIds: [ + 'income_transaction_client_id' => $data['client_id'] ?? null + ] + ); + } } diff --git a/public/docs/api.json b/public/docs/api.json index 68c86074..a59eed90 100644 --- a/public/docs/api.json +++ b/public/docs/api.json @@ -4111,6 +4111,10 @@ "type": { "description": "type of icon( image or icon or emoji)", "type": "string" + }, + "is_myself": { + "description": "Whether this party represents the user themselves", + "type": "boolean" } }, "type": "object" From 37bc40f2546120466cb250fa7fbf7e174f91ff8b Mon Sep 17 00:00:00 2001 From: iMercy Date: Fri, 13 Mar 2026 02:12:51 -0400 Subject: [PATCH 04/25] chore: Fix code standards --- app/Http/Controllers/API/v1/TransactionController.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/Http/Controllers/API/v1/TransactionController.php b/app/Http/Controllers/API/v1/TransactionController.php index 14967011..f99a4aeb 100644 --- a/app/Http/Controllers/API/v1/TransactionController.php +++ b/app/Http/Controllers/API/v1/TransactionController.php @@ -844,7 +844,6 @@ private function validateRequestData(Request $request): array 'from_wallet_id' => 'required_if:convert_myself_to_transfer,true|integer|exists:wallets,id', ]); - } private function isMyselfTransfer($data, $user, $request): bool @@ -860,7 +859,6 @@ private function isMyselfTransfer($data, $user, $request): bool return config('app.convert_myself_to_transfer') && $isMyself && $request->has('from_wallet_id'); - } private function handleMyselfTransfer($data, $user, $request) From 2e777e167a86afbb0315289dda7a5fcadc19ad0f Mon Sep 17 00:00:00 2001 From: iMercy Date: Mon, 16 Mar 2026 06:20:02 -0400 Subject: [PATCH 05/25] chore: Added argument controller method to pass tests --- app/Http/Controllers/API/v1/TransactionController.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/API/v1/TransactionController.php b/app/Http/Controllers/API/v1/TransactionController.php index f99a4aeb..9ef342cf 100644 --- a/app/Http/Controllers/API/v1/TransactionController.php +++ b/app/Http/Controllers/API/v1/TransactionController.php @@ -258,9 +258,9 @@ public function store(Request $request): JsonResponse $data = $validationResult['data']; $user = $request->user(); - //check if feature is enabled and IDs match + //check if party_id is the user's "myself" party and if convert_myself_to_transfer is enabled in configuration. If so, create a transfer instead of a regular transaction if ( - $this->isMyselfTransfer($data, $user) + $this->isMyselfTransfer($data, $user, $request) ) { //return response here to prevent controller from creating a third transaction below return $this->handleMyselfTransfer($data, $user, $request); From 5e166a20ce43af786cf67eb7f06c684082b489c9 Mon Sep 17 00:00:00 2001 From: iMercy Date: Mon, 16 Mar 2026 10:12:11 -0400 Subject: [PATCH 06/25] chore: Change result to jsonresponse and take more user types --- .../Controllers/API/v1/TransactionController.php | 9 +++++++-- phpunit.xml | 12 +++++++++--- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/app/Http/Controllers/API/v1/TransactionController.php b/app/Http/Controllers/API/v1/TransactionController.php index 9ef342cf..8404f003 100644 --- a/app/Http/Controllers/API/v1/TransactionController.php +++ b/app/Http/Controllers/API/v1/TransactionController.php @@ -262,8 +262,9 @@ public function store(Request $request): JsonResponse if ( $this->isMyselfTransfer($data, $user, $request) ) { + $transfer = $this->handleMyselfTransfer($data, $user, $request); //return response here to prevent controller from creating a third transaction below - return $this->handleMyselfTransfer($data, $user, $request); + return $this->success($transfer, statusCode: 201); } @@ -850,7 +851,11 @@ private function isMyselfTransfer($data, $user, $request): bool { // find the user's "myself" party ID from configuration $myselfPartyId = \App\Models\Configuration::where('configurable_id', $user->id) - ->where('configurable_type', User::class) + ->whereIn('configurable_type', [ + get_class($user), + $user->getMorphClass(), + 'User' // fallback + ]) ->where('key', 'myself-party-id') ->value('value'); diff --git a/phpunit.xml b/phpunit.xml index 6dc4ca7d..e51ac3f3 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -24,8 +24,14 @@ - + + + + + + + @@ -33,7 +39,7 @@ - - + From bbb2cc3349572bac7e9d7a308c5e52fa9a823332 Mon Sep 17 00:00:00 2001 From: iMercy Date: Mon, 16 Mar 2026 10:22:25 -0400 Subject: [PATCH 07/25] chore: Fix php code standards --- app/Http/Controllers/API/v1/TransactionController.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/API/v1/TransactionController.php b/app/Http/Controllers/API/v1/TransactionController.php index 8404f003..da90d950 100644 --- a/app/Http/Controllers/API/v1/TransactionController.php +++ b/app/Http/Controllers/API/v1/TransactionController.php @@ -258,7 +258,8 @@ public function store(Request $request): JsonResponse $data = $validationResult['data']; $user = $request->user(); - //check if party_id is the user's "myself" party and if convert_myself_to_transfer is enabled in configuration. If so, create a transfer instead of a regular transaction + //check if party_id is the user's "myself" party + //and if convert_myself_to_transfer is enabled in configuration. If so, create a transfer instead of a regular transaction if ( $this->isMyselfTransfer($data, $user, $request) ) { From 58b2098087b4a083b0d448bc42918b215d58e751 Mon Sep 17 00:00:00 2001 From: iMercy Date: Mon, 16 Mar 2026 10:41:30 -0400 Subject: [PATCH 08/25] chore: Revert testcase and phpunit changes --- phpunit.xml | 2 -- tests/TestCase.php | 4 ---- 2 files changed, 6 deletions(-) diff --git a/phpunit.xml b/phpunit.xml index e51ac3f3..bc385ded 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -39,7 +39,5 @@ - diff --git a/tests/TestCase.php b/tests/TestCase.php index 042ba51e..c806d729 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -17,9 +17,5 @@ protected function setUp(): void $this->artisan('migrate:fresh --seed'); self::$setUpHasRunOnce = true; } - // This tells Laravel to ignore the index error for SQLite - if (config('database.default') === 'sqlite') { - \Illuminate\Support\Facades\Schema::disableForeignKeyConstraints(); - } } } From b933206f91f9885eb40fb65c8b24005611ebf80a Mon Sep 17 00:00:00 2001 From: iMercy Date: Mon, 16 Mar 2026 14:14:29 -0400 Subject: [PATCH 09/25] enh: Import and use config package --- .../API/v1/TransactionController.php | 32 +++++++++-------- app/Models/Party.php | 3 ++ app/Models/User.php | 9 +++++ composer.json | 2 +- composer.lock | 2 +- config/app.php | 1 - config/model-configuration.php | 1 - ..._30_120453_create_configurations_table.php | 34 +++++++++++++++++++ database/seeders/ConfigurationSeeder.php | 24 +++++++++++++ tests/Feature/MyselfTransferTest.php | 27 +++++++++------ 10 files changed, 105 insertions(+), 30 deletions(-) create mode 100644 database/migrations/2025_06_30_120453_create_configurations_table.php create mode 100644 database/seeders/ConfigurationSeeder.php diff --git a/app/Http/Controllers/API/v1/TransactionController.php b/app/Http/Controllers/API/v1/TransactionController.php index da90d950..7e06f056 100644 --- a/app/Http/Controllers/API/v1/TransactionController.php +++ b/app/Http/Controllers/API/v1/TransactionController.php @@ -850,21 +850,23 @@ private function validateRequestData(Request $request): array private function isMyselfTransfer($data, $user, $request): bool { - // find the user's "myself" party ID from configuration - $myselfPartyId = \App\Models\Configuration::where('configurable_id', $user->id) - ->whereIn('configurable_type', [ - get_class($user), - $user->getMorphClass(), - 'User' // fallback - ]) - ->where('key', 'myself-party-id') - ->value('value'); - - $isMyself = isset($data['party_id']) && (string)$data['party_id'] === (string)$myselfPartyId; - - return config('app.convert_myself_to_transfer') && - $isMyself && - $request->has('from_wallet_id'); + //get user preferences (boolean) + $featureEnabled = $user->getConfigValue('create-transfers-for-myself-transactions'); + + //identify the party and check its 'is_myself' property; + $partyId = $data['party_id'] ?? null; + $party = \App\Models\Party::find($partyId); + + // DEBUG: See what is actually being compared + // dd([ + // 'isMyselfParty' => $party->getConfigValue('is-myself'), + // 'feature_enabled' => $user->getConfigValue('create-transfers-for-myself-transactions'), + // 'has_from_wallet' => $request->has('from_wallet_id') + // ]); + + $isMyselfParty = $party && $party->getConfigValue('is-myself'); + + return $featureEnabled && $isMyselfParty && $request->has('from_wallet_id'); } private function handleMyselfTransfer($data, $user, $request) diff --git a/app/Models/Party.php b/app/Models/Party.php index 9f5b9207..d662350b 100644 --- a/app/Models/Party.php +++ b/app/Models/Party.php @@ -9,6 +9,8 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; use OpenApi\Attributes as OA; +use Whilesmart\ModelConfiguration\Traits\Configurable; + #[OA\Schema( schema: 'Party', @@ -28,6 +30,7 @@ )] class Party extends Model { + use configurable; use HasClientCreatedAt; use HasFactory; use Iconable; diff --git a/app/Models/User.php b/app/Models/User.php index a66c3c42..6f095176 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -114,4 +114,13 @@ public function getAvatarUrlAttribute(): ?string { return $this->getConfigValue('avatar'); } + protected static function booted() + { + static::created(function ($user) { + // Automatically enable this setting for every new user + $user->setConfigValue('create-transfers-for-myself-transactions', + true, + \Whilesmart\ModelConfiguration\Enums\ConfigValueType::Boolean); + }); + } } diff --git a/composer.json b/composer.json index 2eab7c66..b4a41ee0 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ "laravel/tinker": "^2.8", "rlanvin/php-rrule": "^2.6", "smartpings/php-sdk": "dev-main", - "whilesmart/eloquent-model-configuration": "^1.0.9", + "whilesmart/eloquent-model-configuration": "^1.0", "whilesmart/eloquent-roles": "dev-dev", "whilesmart/laravel-plugin-engine": "^1.0.0", "whilesmart/laravel-user-authentication": "^1.0", diff --git a/composer.lock b/composer.lock index 1dc71712..522a6a05 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "ad630813bb305eb9fe1e5ac11aed9785", + "content-hash": "94fd879f04f0e6e36614dfe63981dfcb", "packages": [ { "name": "beste/clock", diff --git a/config/app.php b/config/app.php index ec8f29b3..cd74d1ed 100644 --- a/config/app.php +++ b/config/app.php @@ -194,5 +194,4 @@ | Toggle feature to convert "myself" party to "transfer" type when creating a transaction. This is useful for users who want to track their own transactions without creating a separate party for themselves. | */ - 'convert_myself_to_transfer' => env('CONVERT_MYSELF_TO_TRANSFER', true), ]; diff --git a/config/model-configuration.php b/config/model-configuration.php index 03c7dc7d..2ff23646 100644 --- a/config/model-configuration.php +++ b/config/model-configuration.php @@ -24,7 +24,6 @@ 'notifications-inactivity' => 'boolean', 'insights-frequency' => 'string|in:daily,weekly,monthly', - 'myself-party-id' => 'integer', // store the "Myself" Party ID ], 'model' => \App\Models\Configuration::class, 'hooks' => [ModelConfigurationFilterHook::class], diff --git a/database/migrations/2025_06_30_120453_create_configurations_table.php b/database/migrations/2025_06_30_120453_create_configurations_table.php new file mode 100644 index 00000000..dfa0654e --- /dev/null +++ b/database/migrations/2025_06_30_120453_create_configurations_table.php @@ -0,0 +1,34 @@ +id(); + $table->string('key'); + $table->json('value'); + $table->string('configurable_type'); + $table->unsignedBigInteger('configurable_id'); + $table->string('type')->default('string'); + $table->timestamps(); + + $table->unique(['configurable_id', 'configurable_type', 'key']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('configurations'); + } +}; diff --git a/database/seeders/ConfigurationSeeder.php b/database/seeders/ConfigurationSeeder.php new file mode 100644 index 00000000..859f6976 --- /dev/null +++ b/database/seeders/ConfigurationSeeder.php @@ -0,0 +1,24 @@ +setConfigValue('create-transfers-for-myself-transactions', true, ConfigValueType::BOOLEAN); + } + + + + + } +} diff --git a/tests/Feature/MyselfTransferTest.php b/tests/Feature/MyselfTransferTest.php index 30f34b5f..0b4e702b 100644 --- a/tests/Feature/MyselfTransferTest.php +++ b/tests/Feature/MyselfTransferTest.php @@ -9,6 +9,8 @@ use App\Models\Transfer; use Illuminate\Foundation\Testing\RefreshDatabase; use Tests\TestCase; +use Whilesmart\ModelConfiguration\Models\Configuration; +use Whilesmart\ModelConfiguration\Enums\ConfigValueType; class MyselfTransferTest extends TestCase { @@ -18,20 +20,23 @@ public function test_income_from_myself_is_handled_as_transfer() { // 1. Setup Data $user = User::factory()->create(); + $party = Party::factory()->create(['user_id' => $user->id]); + + //set user preference to use "myself" party + $user->setConfigValue('create-transfers-for-myself-transactions', + true, + ConfigValueType::Boolean + ); + //set party flag + $party->setConfigValue('is-myself', + true, + ConfigValueType::Boolean + ); + //create the myself party - $myselfParty = Party::factory()->create([ 'user_id' => $user->id]); $walletSource = Wallet::factory()->create(['user_id' => $user->id, 'balance' => 1000]); $walletDest = Wallet::factory()->create(['user_id' => $user->id, 'balance' => 0]); - config(['app.convert_myself_to_transfer' => true]); // Ensure the feature is enabled for the test - - //manually set the configuration for this user - \App\Models\Configuration::create([ - 'configurable_id' => $user->id, - 'configurable_type' => User::class, - 'key' => 'myself-party-id', - 'value' => $myselfParty->id, - ]); $fakeClientId = \Illuminate\Support\Str::uuid() . ':' . \Illuminate\Support\Str::uuid(); @@ -39,7 +44,7 @@ public function test_income_from_myself_is_handled_as_transfer() $payload = [ 'amount' => 200, // Validator needs 'amount' 'type' => 'income', // Validator needs 'type' - 'party_id' => $myselfParty->id, // Triggers the 'is_myself' logic + 'party_id' => $party->id, // Triggers the 'is_myself' logic 'wallet_id' => $walletDest->id, // Becomes 'toWallet' 'from_wallet_id' => $walletSource->id, // Becomes 'fromWallet' 'client_id' => $fakeClientId, // To ensure idempotency in tests From 8000581c0d5728703d64d7832547d4cf5de15493 Mon Sep 17 00:00:00 2001 From: iMercy Date: Mon, 16 Mar 2026 14:16:49 -0400 Subject: [PATCH 10/25] chore: Coding standard tests pass --- app/Models/Party.php | 1 - app/Models/User.php | 8 +++++--- .../2025_06_30_120453_create_configurations_table.php | 3 +-- database/seeders/ConfigurationSeeder.php | 3 +-- tests/Feature/MyselfTransferTest.php | 11 ++++++----- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/app/Models/Party.php b/app/Models/Party.php index d662350b..7e600819 100644 --- a/app/Models/Party.php +++ b/app/Models/Party.php @@ -11,7 +11,6 @@ use OpenApi\Attributes as OA; use Whilesmart\ModelConfiguration\Traits\Configurable; - #[OA\Schema( schema: 'Party', properties: [ diff --git a/app/Models/User.php b/app/Models/User.php index 6f095176..06e5e899 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -118,9 +118,11 @@ protected static function booted() { static::created(function ($user) { // Automatically enable this setting for every new user - $user->setConfigValue('create-transfers-for-myself-transactions', - true, - \Whilesmart\ModelConfiguration\Enums\ConfigValueType::Boolean); + $user->setConfigValue( + 'create-transfers-for-myself-transactions', + true, + \Whilesmart\ModelConfiguration\Enums\ConfigValueType::Boolean + ); }); } } diff --git a/database/migrations/2025_06_30_120453_create_configurations_table.php b/database/migrations/2025_06_30_120453_create_configurations_table.php index dfa0654e..62d4f967 100644 --- a/database/migrations/2025_06_30_120453_create_configurations_table.php +++ b/database/migrations/2025_06_30_120453_create_configurations_table.php @@ -4,8 +4,7 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -return new class extends Migration -{ +return new class () extends Migration { /** * Run the migrations. */ diff --git a/database/seeders/ConfigurationSeeder.php b/database/seeders/ConfigurationSeeder.php index 859f6976..613b5f80 100644 --- a/database/seeders/ConfigurationSeeder.php +++ b/database/seeders/ConfigurationSeeder.php @@ -2,7 +2,6 @@ namespace Database\Seeders; -use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; class ConfigurationSeeder extends Seeder @@ -17,7 +16,7 @@ public function run(): void $user->setConfigValue('create-transfers-for-myself-transactions', true, ConfigValueType::BOOLEAN); } - + } diff --git a/tests/Feature/MyselfTransferTest.php b/tests/Feature/MyselfTransferTest.php index 0b4e702b..212a4114 100644 --- a/tests/Feature/MyselfTransferTest.php +++ b/tests/Feature/MyselfTransferTest.php @@ -9,7 +9,6 @@ use App\Models\Transfer; use Illuminate\Foundation\Testing\RefreshDatabase; use Tests\TestCase; -use Whilesmart\ModelConfiguration\Models\Configuration; use Whilesmart\ModelConfiguration\Enums\ConfigValueType; class MyselfTransferTest extends TestCase @@ -23,13 +22,15 @@ public function test_income_from_myself_is_handled_as_transfer() $party = Party::factory()->create(['user_id' => $user->id]); //set user preference to use "myself" party - $user->setConfigValue('create-transfers-for-myself-transactions', - true, + $user->setConfigValue( + 'create-transfers-for-myself-transactions', + true, ConfigValueType::Boolean ); //set party flag - $party->setConfigValue('is-myself', - true, + $party->setConfigValue( + 'is-myself', + true, ConfigValueType::Boolean ); From 4e945665e6843b5cf37db842c1321d356df2f668 Mon Sep 17 00:00:00 2001 From: iMercy Date: Wed, 18 Mar 2026 20:11:15 -0400 Subject: [PATCH 11/25] chore: Use authenticated user in ismyselftransfer check n update config seeder --- app/Http/Controllers/API/v1/TransactionController.php | 2 +- database/seeders/ConfigurationSeeder.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/API/v1/TransactionController.php b/app/Http/Controllers/API/v1/TransactionController.php index 7e06f056..bf6daa2a 100644 --- a/app/Http/Controllers/API/v1/TransactionController.php +++ b/app/Http/Controllers/API/v1/TransactionController.php @@ -855,7 +855,7 @@ private function isMyselfTransfer($data, $user, $request): bool //identify the party and check its 'is_myself' property; $partyId = $data['party_id'] ?? null; - $party = \App\Models\Party::find($partyId); + $party = $user->parties()->find($partyId); // DEBUG: See what is actually being compared // dd([ diff --git a/database/seeders/ConfigurationSeeder.php b/database/seeders/ConfigurationSeeder.php index 613b5f80..a7638baf 100644 --- a/database/seeders/ConfigurationSeeder.php +++ b/database/seeders/ConfigurationSeeder.php @@ -13,7 +13,7 @@ public function run(): void { //enable feature for all existing users by default, can be turned off by user if they want foreach (\App\Models\User::all() as $user) { - $user->setConfigValue('create-transfers-for-myself-transactions', true, ConfigValueType::BOOLEAN); + $user->setConfigValue('create-transfers-for-myself-transactions', true, \Whilesmart\ModelConfiguration\Enums\ConfigValueType::Boolean); } From cbdafc8d4c70ae31163e6dd96f1305fefb634099 Mon Sep 17 00:00:00 2001 From: iMercy Date: Wed, 18 Mar 2026 20:22:43 -0400 Subject: [PATCH 12/25] chore: Phpunit file content reverted to original --- phpunit.xml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/phpunit.xml b/phpunit.xml index bc385ded..88c8a42e 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -25,13 +25,7 @@ - - - - - - - + From 19ccd149b60450dbb02cfb8666156554484f212f Mon Sep 17 00:00:00 2001 From: iMercy Date: Sun, 29 Mar 2026 19:55:22 -0400 Subject: [PATCH 13/25] chore: Undo changes in composer files --- composer.json | 2 +- composer.lock | 602 +++++++++++++++++++++++++------------------------- 2 files changed, 299 insertions(+), 305 deletions(-) diff --git a/composer.json b/composer.json index b4a41ee0..2eab7c66 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ "laravel/tinker": "^2.8", "rlanvin/php-rrule": "^2.6", "smartpings/php-sdk": "dev-main", - "whilesmart/eloquent-model-configuration": "^1.0", + "whilesmart/eloquent-model-configuration": "^1.0.9", "whilesmart/eloquent-roles": "dev-dev", "whilesmart/laravel-plugin-engine": "^1.0.0", "whilesmart/laravel-user-authentication": "^1.0", diff --git a/composer.lock b/composer.lock index 522a6a05..e0d8c7d8 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "94fd879f04f0e6e36614dfe63981dfcb", + "content-hash": "b4401762fee0a682f1d207c388a6663c", "packages": [ { "name": "beste/clock", @@ -210,16 +210,16 @@ }, { "name": "brick/math", - "version": "0.14.8", + "version": "0.14.5", "source": { "type": "git", "url": "https://github.com/brick/math.git", - "reference": "63422359a44b7f06cae63c3b429b59e8efcc0629" + "reference": "618a8077b3c326045e10d5788ed713b341fcfe40" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/63422359a44b7f06cae63c3b429b59e8efcc0629", - "reference": "63422359a44b7f06cae63c3b429b59e8efcc0629", + "url": "https://api.github.com/repos/brick/math/zipball/618a8077b3c326045e10d5788ed713b341fcfe40", + "reference": "618a8077b3c326045e10d5788ed713b341fcfe40", "shasum": "" }, "require": { @@ -258,7 +258,7 @@ ], "support": { "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.14.8" + "source": "https://github.com/brick/math/tree/0.14.5" }, "funding": [ { @@ -266,7 +266,7 @@ "type": "github" } ], - "time": "2026-02-10T14:33:43+00:00" + "time": "2026-02-03T18:06:51+00:00" }, { "name": "carbonphp/carbon-doctrine-types", @@ -992,16 +992,16 @@ }, { "name": "firebase/php-jwt", - "version": "v7.0.3", + "version": "v6.11.1", "source": { "type": "git", "url": "https://github.com/firebase/php-jwt.git", - "reference": "28aa0694bcfdfa5e2959c394d5a1ee7a5083629e" + "reference": "d1e91ecf8c598d073d0995afa8cd5c75c6e19e66" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/firebase/php-jwt/zipball/28aa0694bcfdfa5e2959c394d5a1ee7a5083629e", - "reference": "28aa0694bcfdfa5e2959c394d5a1ee7a5083629e", + "url": "https://api.github.com/repos/firebase/php-jwt/zipball/d1e91ecf8c598d073d0995afa8cd5c75c6e19e66", + "reference": "d1e91ecf8c598d073d0995afa8cd5c75c6e19e66", "shasum": "" }, "require": { @@ -1049,9 +1049,9 @@ ], "support": { "issues": "https://github.com/firebase/php-jwt/issues", - "source": "https://github.com/firebase/php-jwt/tree/v7.0.3" + "source": "https://github.com/firebase/php-jwt/tree/v6.11.1" }, - "time": "2026-02-25T22:16:40+00:00" + "time": "2025-04-09T20:32:01+00:00" }, { "name": "fruitcake/php-cors", @@ -1371,16 +1371,16 @@ }, { "name": "google/gax", - "version": "v1.42.1", + "version": "v1.42.0", "source": { "type": "git", "url": "https://github.com/googleapis/gax-php.git", - "reference": "ab334c4869f4f16b6112acc95972a97b81dbfdda" + "reference": "7ad13e5b9ca201a5f60e7b3342842d1217bc23a6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/googleapis/gax-php/zipball/ab334c4869f4f16b6112acc95972a97b81dbfdda", - "reference": "ab334c4869f4f16b6112acc95972a97b81dbfdda", + "url": "https://api.github.com/repos/googleapis/gax-php/zipball/7ad13e5b9ca201a5f60e7b3342842d1217bc23a6", + "reference": "7ad13e5b9ca201a5f60e7b3342842d1217bc23a6", "shasum": "" }, "require": { @@ -1388,7 +1388,7 @@ "google/common-protos": "^4.4", "google/grpc-gcp": "^0.4", "google/longrunning": "~0.4", - "google/protobuf": "^4.31||^5.0", + "google/protobuf": "^4.31", "grpc/grpc": "^1.13", "guzzlehttp/promises": "^2.0", "guzzlehttp/psr7": "^2.0", @@ -1399,10 +1399,10 @@ "ext-protobuf": "<4.31.0" }, "require-dev": { - "google/cloud-tools": "^0.16.1", "phpspec/prophecy-phpunit": "^2.1", "phpstan/phpstan": "^2.0", - "phpunit/phpunit": "^9.6" + "phpunit/phpunit": "^9.6", + "squizlabs/php_codesniffer": "4.*" }, "type": "library", "autoload": { @@ -1422,27 +1422,27 @@ ], "support": { "issues": "https://github.com/googleapis/gax-php/issues", - "source": "https://github.com/googleapis/gax-php/tree/v1.42.1" + "source": "https://github.com/googleapis/gax-php/tree/v1.42.0" }, - "time": "2026-03-12T21:22:20+00:00" + "time": "2026-01-22T20:31:50+00:00" }, { "name": "google/grpc-gcp", - "version": "0.4.2", + "version": "v0.4.1", "source": { "type": "git", "url": "https://github.com/GoogleCloudPlatform/grpc-gcp-php.git", - "reference": "1049c0c15b6a1789fdeb52af688a94d540932469" + "reference": "e585b7721bbe806ef45b5c52ae43dfc2bff89968" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/GoogleCloudPlatform/grpc-gcp-php/zipball/1049c0c15b6a1789fdeb52af688a94d540932469", - "reference": "1049c0c15b6a1789fdeb52af688a94d540932469", + "url": "https://api.github.com/repos/GoogleCloudPlatform/grpc-gcp-php/zipball/e585b7721bbe806ef45b5c52ae43dfc2bff89968", + "reference": "e585b7721bbe806ef45b5c52ae43dfc2bff89968", "shasum": "" }, "require": { "google/auth": "^1.3", - "google/protobuf": "^v3.25.3||^4.26.1||^5.0", + "google/protobuf": "^v3.25.3||^4.26.1", "grpc/grpc": "^v1.13.0", "php": "^8.0", "psr/cache": "^1.0.1||^2.0.0||^3.0.0" @@ -1467,9 +1467,9 @@ "description": "gRPC GCP library for channel management", "support": { "issues": "https://github.com/GoogleCloudPlatform/grpc-gcp-php/issues", - "source": "https://github.com/GoogleCloudPlatform/grpc-gcp-php/tree/v0.4.2" + "source": "https://github.com/GoogleCloudPlatform/grpc-gcp-php/tree/v0.4.1" }, - "time": "2026-03-12T22:56:09+00:00" + "time": "2025-02-19T21:53:22+00:00" }, { "name": "google/longrunning", @@ -1876,16 +1876,16 @@ }, { "name": "guzzlehttp/psr7", - "version": "2.9.0", + "version": "2.8.0", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "7d0ed42f28e42d61352a7a79de682e5e67fec884" + "reference": "21dc724a0583619cd1652f673303492272778051" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/7d0ed42f28e42d61352a7a79de682e5e67fec884", - "reference": "7d0ed42f28e42d61352a7a79de682e5e67fec884", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/21dc724a0583619cd1652f673303492272778051", + "reference": "21dc724a0583619cd1652f673303492272778051", "shasum": "" }, "require": { @@ -1901,7 +1901,6 @@ "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", "http-interop/http-factory-tests": "0.9.0", - "jshttp/mime-db": "1.54.0.1", "phpunit/phpunit": "^8.5.44 || ^9.6.25" }, "suggest": { @@ -1973,7 +1972,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.9.0" + "source": "https://github.com/guzzle/psr7/tree/2.8.0" }, "funding": [ { @@ -1989,7 +1988,7 @@ "type": "tidelift" } ], - "time": "2026-03-10T16:41:02+00:00" + "time": "2025-08-23T21:21:41+00:00" }, { "name": "guzzlehttp/uri-template", @@ -2079,16 +2078,16 @@ }, { "name": "kreait/firebase-php", - "version": "7.24.1", + "version": "7.24.0", "source": { "type": "git", "url": "https://github.com/beste/firebase-php.git", - "reference": "935292e2af33af0120654ce704f1dc8f1c03025b" + "reference": "7e2c321265dcb2eeea6fdb72e3819db684e0d4be" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/beste/firebase-php/zipball/935292e2af33af0120654ce704f1dc8f1c03025b", - "reference": "935292e2af33af0120654ce704f1dc8f1c03025b", + "url": "https://api.github.com/repos/beste/firebase-php/zipball/7e2c321265dcb2eeea6fdb72e3819db684e0d4be", + "reference": "7e2c321265dcb2eeea6fdb72e3819db684e0d4be", "shasum": "" }, "require": { @@ -2101,7 +2100,7 @@ "ext-json": "*", "ext-mbstring": "*", "fig/http-message-util": "^1.1.5", - "firebase/php-jwt": "^6.10.2 || ^7.0.2", + "firebase/php-jwt": "^6.10.2", "google/auth": "^1.45", "google/cloud-storage": "^1.48.7", "guzzlehttp/guzzle": "^7.9.2", @@ -2176,7 +2175,7 @@ "type": "github" } ], - "time": "2026-02-18T23:43:18+00:00" + "time": "2025-11-27T22:03:59+00:00" }, { "name": "kreait/firebase-tokens", @@ -2548,16 +2547,16 @@ }, { "name": "laravel/prompts", - "version": "v0.3.14", + "version": "v0.3.12", "source": { "type": "git", "url": "https://github.com/laravel/prompts.git", - "reference": "9f0e371244eedfe2ebeaa72c79c54bb5df6e0176" + "reference": "4861ded9003b7f8a158176a0b7666f74ee761be8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/prompts/zipball/9f0e371244eedfe2ebeaa72c79c54bb5df6e0176", - "reference": "9f0e371244eedfe2ebeaa72c79c54bb5df6e0176", + "url": "https://api.github.com/repos/laravel/prompts/zipball/4861ded9003b7f8a158176a0b7666f74ee761be8", + "reference": "4861ded9003b7f8a158176a0b7666f74ee761be8", "shasum": "" }, "require": { @@ -2601,36 +2600,36 @@ "description": "Add beautiful and user-friendly forms to your command-line applications.", "support": { "issues": "https://github.com/laravel/prompts/issues", - "source": "https://github.com/laravel/prompts/tree/v0.3.14" + "source": "https://github.com/laravel/prompts/tree/v0.3.12" }, - "time": "2026-03-01T09:02:38+00:00" + "time": "2026-02-03T06:57:26+00:00" }, { "name": "laravel/sanctum", - "version": "v4.3.1", + "version": "v4.3.0", "source": { "type": "git", "url": "https://github.com/laravel/sanctum.git", - "reference": "e3b85d6e36ad00e5db2d1dcc27c81ffdf15cbf76" + "reference": "c978c82b2b8ab685468a7ca35224497d541b775a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/sanctum/zipball/e3b85d6e36ad00e5db2d1dcc27c81ffdf15cbf76", - "reference": "e3b85d6e36ad00e5db2d1dcc27c81ffdf15cbf76", + "url": "https://api.github.com/repos/laravel/sanctum/zipball/c978c82b2b8ab685468a7ca35224497d541b775a", + "reference": "c978c82b2b8ab685468a7ca35224497d541b775a", "shasum": "" }, "require": { "ext-json": "*", - "illuminate/console": "^11.0|^12.0|^13.0", - "illuminate/contracts": "^11.0|^12.0|^13.0", - "illuminate/database": "^11.0|^12.0|^13.0", - "illuminate/support": "^11.0|^12.0|^13.0", + "illuminate/console": "^11.0|^12.0", + "illuminate/contracts": "^11.0|^12.0", + "illuminate/database": "^11.0|^12.0", + "illuminate/support": "^11.0|^12.0", "php": "^8.2", - "symfony/console": "^7.0|^8.0" + "symfony/console": "^7.0" }, "require-dev": { "mockery/mockery": "^1.6", - "orchestra/testbench": "^9.15|^10.8|^11.0", + "orchestra/testbench": "^9.15|^10.8", "phpstan/phpstan": "^1.10" }, "type": "library", @@ -2666,20 +2665,20 @@ "issues": "https://github.com/laravel/sanctum/issues", "source": "https://github.com/laravel/sanctum" }, - "time": "2026-02-07T17:19:31+00:00" + "time": "2026-01-22T22:27:01+00:00" }, { "name": "laravel/serializable-closure", - "version": "v2.0.10", + "version": "v2.0.9", "source": { "type": "git", "url": "https://github.com/laravel/serializable-closure.git", - "reference": "870fc81d2f879903dfc5b60bf8a0f94a1609e669" + "reference": "8f631589ab07b7b52fead814965f5a800459cb3e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/870fc81d2f879903dfc5b60bf8a0f94a1609e669", - "reference": "870fc81d2f879903dfc5b60bf8a0f94a1609e669", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/8f631589ab07b7b52fead814965f5a800459cb3e", + "reference": "8f631589ab07b7b52fead814965f5a800459cb3e", "shasum": "" }, "require": { @@ -2727,36 +2726,36 @@ "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" }, - "time": "2026-02-20T19:59:49+00:00" + "time": "2026-02-03T06:55:34+00:00" }, { "name": "laravel/socialite", - "version": "v5.25.0", + "version": "v5.24.2", "source": { "type": "git", "url": "https://github.com/laravel/socialite.git", - "reference": "231f572e1a37c9ca1fb8085e9fb8608285beafb3" + "reference": "5cea2eebf11ca4bc6c2f20495c82a70a9b3d1613" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/socialite/zipball/231f572e1a37c9ca1fb8085e9fb8608285beafb3", - "reference": "231f572e1a37c9ca1fb8085e9fb8608285beafb3", + "url": "https://api.github.com/repos/laravel/socialite/zipball/5cea2eebf11ca4bc6c2f20495c82a70a9b3d1613", + "reference": "5cea2eebf11ca4bc6c2f20495c82a70a9b3d1613", "shasum": "" }, "require": { "ext-json": "*", "firebase/php-jwt": "^6.4|^7.0", "guzzlehttp/guzzle": "^6.0|^7.0", - "illuminate/contracts": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0|^13.0", - "illuminate/http": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0|^13.0", - "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0|^13.0", + "illuminate/contracts": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", + "illuminate/http": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", + "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", "league/oauth1-client": "^1.11", "php": "^7.2|^8.0", "phpseclib/phpseclib": "^3.0" }, "require-dev": { "mockery/mockery": "^1.0", - "orchestra/testbench": "^4.18|^5.20|^6.47|^7.55|^8.36|^9.15|^10.8|^11.0", + "orchestra/testbench": "^4.18|^5.20|^6.47|^7.55|^8.36|^9.15|^10.8", "phpstan/phpstan": "^1.12.23", "phpunit/phpunit": "^8.0|^9.3|^10.4|^11.5|^12.0" }, @@ -2799,20 +2798,20 @@ "issues": "https://github.com/laravel/socialite/issues", "source": "https://github.com/laravel/socialite" }, - "time": "2026-02-27T13:56:35+00:00" + "time": "2026-01-10T16:07:28+00:00" }, { "name": "laravel/tinker", - "version": "v2.11.1", + "version": "v2.11.0", "source": { "type": "git", "url": "https://github.com/laravel/tinker.git", - "reference": "c9f80cc835649b5c1842898fb043f8cc098dd741" + "reference": "3d34b97c9a1747a81a3fde90482c092bd8b66468" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/tinker/zipball/c9f80cc835649b5c1842898fb043f8cc098dd741", - "reference": "c9f80cc835649b5c1842898fb043f8cc098dd741", + "url": "https://api.github.com/repos/laravel/tinker/zipball/3d34b97c9a1747a81a3fde90482c092bd8b66468", + "reference": "3d34b97c9a1747a81a3fde90482c092bd8b66468", "shasum": "" }, "require": { @@ -2863,9 +2862,9 @@ ], "support": { "issues": "https://github.com/laravel/tinker/issues", - "source": "https://github.com/laravel/tinker/tree/v2.11.1" + "source": "https://github.com/laravel/tinker/tree/v2.11.0" }, - "time": "2026-02-06T14:12:35+00:00" + "time": "2025-12-19T19:16:45+00:00" }, { "name": "lcobucci/jwt", @@ -2942,16 +2941,16 @@ }, { "name": "league/commonmark", - "version": "2.8.1", + "version": "2.8.0", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "84b1ca48347efdbe775426f108622a42735a6579" + "reference": "4efa10c1e56488e658d10adf7b7b7dcd19940bfb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/84b1ca48347efdbe775426f108622a42735a6579", - "reference": "84b1ca48347efdbe775426f108622a42735a6579", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/4efa10c1e56488e658d10adf7b7b7dcd19940bfb", + "reference": "4efa10c1e56488e658d10adf7b7b7dcd19940bfb", "shasum": "" }, "require": { @@ -2976,9 +2975,9 @@ "phpstan/phpstan": "^1.8.2", "phpunit/phpunit": "^9.5.21 || ^10.5.9 || ^11.0.0", "scrutinizer/ocular": "^1.8.1", - "symfony/finder": "^5.3 | ^6.0 | ^7.0 || ^8.0", - "symfony/process": "^5.4 | ^6.0 | ^7.0 || ^8.0", - "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 | ^7.0 || ^8.0", + "symfony/finder": "^5.3 | ^6.0 | ^7.0", + "symfony/process": "^5.4 | ^6.0 | ^7.0", + "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 | ^7.0", "unleashedtech/php-coding-standard": "^3.1.1", "vimeo/psalm": "^4.24.0 || ^5.0.0 || ^6.0.0" }, @@ -3045,7 +3044,7 @@ "type": "tidelift" } ], - "time": "2026-03-05T21:37:03+00:00" + "time": "2025-11-26T21:48:24+00:00" }, { "name": "league/config", @@ -3131,16 +3130,16 @@ }, { "name": "league/flysystem", - "version": "3.32.0", + "version": "3.31.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "254b1595b16b22dbddaaef9ed6ca9fdac4956725" + "reference": "1717e0b3642b0df65ecb0cc89cdd99fa840672ff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/254b1595b16b22dbddaaef9ed6ca9fdac4956725", - "reference": "254b1595b16b22dbddaaef9ed6ca9fdac4956725", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/1717e0b3642b0df65ecb0cc89cdd99fa840672ff", + "reference": "1717e0b3642b0df65ecb0cc89cdd99fa840672ff", "shasum": "" }, "require": { @@ -3208,9 +3207,9 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/3.32.0" + "source": "https://github.com/thephpleague/flysystem/tree/3.31.0" }, - "time": "2026-02-25T17:01:41+00:00" + "time": "2026-01-23T15:38:47+00:00" }, { "name": "league/flysystem-local", @@ -3746,16 +3745,16 @@ }, { "name": "nesbot/carbon", - "version": "3.11.3", + "version": "3.11.1", "source": { "type": "git", "url": "https://github.com/CarbonPHP/carbon.git", - "reference": "6a7e652845bb018c668220c2a545aded8594fbbf" + "reference": "f438fcc98f92babee98381d399c65336f3a3827f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/6a7e652845bb018c668220c2a545aded8594fbbf", - "reference": "6a7e652845bb018c668220c2a545aded8594fbbf", + "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/f438fcc98f92babee98381d399c65336f3a3827f", + "reference": "f438fcc98f92babee98381d399c65336f3a3827f", "shasum": "" }, "require": { @@ -3847,20 +3846,20 @@ "type": "tidelift" } ], - "time": "2026-03-11T17:23:39+00:00" + "time": "2026-01-29T09:26:29+00:00" }, { "name": "nette/schema", - "version": "v1.3.5", + "version": "v1.3.3", "source": { "type": "git", "url": "https://github.com/nette/schema.git", - "reference": "f0ab1a3cda782dbc5da270d28545236aa80c4002" + "reference": "2befc2f42d7c715fd9d95efc31b1081e5d765004" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/schema/zipball/f0ab1a3cda782dbc5da270d28545236aa80c4002", - "reference": "f0ab1a3cda782dbc5da270d28545236aa80c4002", + "url": "https://api.github.com/repos/nette/schema/zipball/2befc2f42d7c715fd9d95efc31b1081e5d765004", + "reference": "2befc2f42d7c715fd9d95efc31b1081e5d765004", "shasum": "" }, "require": { @@ -3868,10 +3867,8 @@ "php": "8.1 - 8.5" }, "require-dev": { - "nette/phpstan-rules": "^1.0", - "nette/tester": "^2.6", - "phpstan/extension-installer": "^1.4@stable", - "phpstan/phpstan": "^2.1.39@stable", + "nette/tester": "^2.5.2", + "phpstan/phpstan-nette": "^2.0@stable", "tracy/tracy": "^2.8" }, "type": "library", @@ -3912,22 +3909,22 @@ ], "support": { "issues": "https://github.com/nette/schema/issues", - "source": "https://github.com/nette/schema/tree/v1.3.5" + "source": "https://github.com/nette/schema/tree/v1.3.3" }, - "time": "2026-02-23T03:47:12+00:00" + "time": "2025-10-30T22:57:59+00:00" }, { "name": "nette/utils", - "version": "v4.1.3", + "version": "v4.1.2", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "bb3ea637e3d131d72acc033cfc2746ee893349fe" + "reference": "f76b5dc3d6c6d3043c8d937df2698515b99cbaf5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/bb3ea637e3d131d72acc033cfc2746ee893349fe", - "reference": "bb3ea637e3d131d72acc033cfc2746ee893349fe", + "url": "https://api.github.com/repos/nette/utils/zipball/f76b5dc3d6c6d3043c8d937df2698515b99cbaf5", + "reference": "f76b5dc3d6c6d3043c8d937df2698515b99cbaf5", "shasum": "" }, "require": { @@ -3939,10 +3936,8 @@ }, "require-dev": { "jetbrains/phpstorm-attributes": "^1.2", - "nette/phpstan-rules": "^1.0", "nette/tester": "^2.5", - "phpstan/extension-installer": "^1.4@stable", - "phpstan/phpstan": "^2.1@stable", + "phpstan/phpstan": "^2.0@stable", "tracy/tracy": "^2.9" }, "suggest": { @@ -4003,9 +3998,9 @@ ], "support": { "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v4.1.3" + "source": "https://github.com/nette/utils/tree/v4.1.2" }, - "time": "2026-02-13T03:05:33+00:00" + "time": "2026-02-03T17:21:09+00:00" }, { "name": "nikic/php-parser", @@ -4067,31 +4062,31 @@ }, { "name": "nunomaduro/termwind", - "version": "v2.4.0", + "version": "v2.3.3", "source": { "type": "git", "url": "https://github.com/nunomaduro/termwind.git", - "reference": "712a31b768f5daea284c2169a7d227031001b9a8" + "reference": "6fb2a640ff502caace8e05fd7be3b503a7e1c017" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/712a31b768f5daea284c2169a7d227031001b9a8", - "reference": "712a31b768f5daea284c2169a7d227031001b9a8", + "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/6fb2a640ff502caace8e05fd7be3b503a7e1c017", + "reference": "6fb2a640ff502caace8e05fd7be3b503a7e1c017", "shasum": "" }, "require": { "ext-mbstring": "*", "php": "^8.2", - "symfony/console": "^7.4.4 || ^8.0.4" + "symfony/console": "^7.3.6" }, "require-dev": { - "illuminate/console": "^11.47.0", - "laravel/pint": "^1.27.1", + "illuminate/console": "^11.46.1", + "laravel/pint": "^1.25.1", "mockery/mockery": "^1.6.12", - "pestphp/pest": "^2.36.0 || ^3.8.4 || ^4.3.2", + "pestphp/pest": "^2.36.0 || ^3.8.4 || ^4.1.3", "phpstan/phpstan": "^1.12.32", "phpstan/phpstan-strict-rules": "^1.6.2", - "symfony/var-dumper": "^7.3.5 || ^8.0.4", + "symfony/var-dumper": "^7.3.5", "thecodingmachine/phpstan-strict-rules": "^1.0.0" }, "type": "library", @@ -4123,7 +4118,7 @@ "email": "enunomaduro@gmail.com" } ], - "description": "It's like Tailwind CSS, but for the console.", + "description": "Its like Tailwind CSS, but for the console.", "keywords": [ "cli", "console", @@ -4134,7 +4129,7 @@ ], "support": { "issues": "https://github.com/nunomaduro/termwind/issues", - "source": "https://github.com/nunomaduro/termwind/tree/v2.4.0" + "source": "https://github.com/nunomaduro/termwind/tree/v2.3.3" }, "funding": [ { @@ -4150,7 +4145,7 @@ "type": "github" } ], - "time": "2026-02-16T23:10:27+00:00" + "time": "2025-11-20T02:34:59+00:00" }, { "name": "paragonie/constant_time_encoding", @@ -4966,16 +4961,16 @@ }, { "name": "psy/psysh", - "version": "v0.12.21", + "version": "v0.12.19", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "4821fab5b7cd8c49a673a9fd5754dc9162bb9e97" + "reference": "a4f766e5c5b6773d8399711019bb7d90875a50ee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/4821fab5b7cd8c49a673a9fd5754dc9162bb9e97", - "reference": "4821fab5b7cd8c49a673a9fd5754dc9162bb9e97", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/a4f766e5c5b6773d8399711019bb7d90875a50ee", + "reference": "a4f766e5c5b6773d8399711019bb7d90875a50ee", "shasum": "" }, "require": { @@ -5039,9 +5034,9 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.12.21" + "source": "https://github.com/bobthecow/psysh/tree/v0.12.19" }, - "time": "2026-03-06T21:21:28+00:00" + "time": "2026-01-30T17:33:13+00:00" }, { "name": "ralouphie/getallheaders", @@ -5411,16 +5406,16 @@ }, { "name": "symfony/cache", - "version": "v7.4.7", + "version": "v7.4.5", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "665522ec357540e66c294c08583b40ee576574f0" + "reference": "8dde98d5a4123b53877aca493f9be57b333f14bd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/665522ec357540e66c294c08583b40ee576574f0", - "reference": "665522ec357540e66c294c08583b40ee576574f0", + "url": "https://api.github.com/repos/symfony/cache/zipball/8dde98d5a4123b53877aca493f9be57b333f14bd", + "reference": "8dde98d5a4123b53877aca493f9be57b333f14bd", "shasum": "" }, "require": { @@ -5491,7 +5486,7 @@ "psr6" ], "support": { - "source": "https://github.com/symfony/cache/tree/v7.4.7" + "source": "https://github.com/symfony/cache/tree/v7.4.5" }, "funding": [ { @@ -5511,7 +5506,7 @@ "type": "tidelift" } ], - "time": "2026-03-06T08:14:57+00:00" + "time": "2026-01-27T16:16:02+00:00" }, { "name": "symfony/cache-contracts", @@ -5669,16 +5664,16 @@ }, { "name": "symfony/console", - "version": "v7.4.7", + "version": "v7.4.4", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "e1e6770440fb9c9b0cf725f81d1361ad1835329d" + "reference": "41e38717ac1dd7a46b6bda7d6a82af2d98a78894" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/e1e6770440fb9c9b0cf725f81d1361ad1835329d", - "reference": "e1e6770440fb9c9b0cf725f81d1361ad1835329d", + "url": "https://api.github.com/repos/symfony/console/zipball/41e38717ac1dd7a46b6bda7d6a82af2d98a78894", + "reference": "41e38717ac1dd7a46b6bda7d6a82af2d98a78894", "shasum": "" }, "require": { @@ -5743,7 +5738,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.4.7" + "source": "https://github.com/symfony/console/tree/v7.4.4" }, "funding": [ { @@ -5763,20 +5758,20 @@ "type": "tidelift" } ], - "time": "2026-03-06T14:06:20+00:00" + "time": "2026-01-13T11:36:38+00:00" }, { "name": "symfony/css-selector", - "version": "v7.4.6", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "2e7c52c647b406e2107dd867db424a4dbac91864" + "reference": "ab862f478513e7ca2fe9ec117a6f01a8da6e1135" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/2e7c52c647b406e2107dd867db424a4dbac91864", - "reference": "2e7c52c647b406e2107dd867db424a4dbac91864", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/ab862f478513e7ca2fe9ec117a6f01a8da6e1135", + "reference": "ab862f478513e7ca2fe9ec117a6f01a8da6e1135", "shasum": "" }, "require": { @@ -5812,7 +5807,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v7.4.6" + "source": "https://github.com/symfony/css-selector/tree/v7.4.0" }, "funding": [ { @@ -5832,7 +5827,7 @@ "type": "tidelift" } ], - "time": "2026-02-17T07:53:42+00:00" + "time": "2025-10-30T13:39:42+00:00" }, { "name": "symfony/deprecation-contracts", @@ -6146,16 +6141,16 @@ }, { "name": "symfony/finder", - "version": "v7.4.6", + "version": "v7.4.5", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "8655bf1076b7a3a346cb11413ffdabff50c7ffcf" + "reference": "ad4daa7c38668dcb031e63bc99ea9bd42196a2cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/8655bf1076b7a3a346cb11413ffdabff50c7ffcf", - "reference": "8655bf1076b7a3a346cb11413ffdabff50c7ffcf", + "url": "https://api.github.com/repos/symfony/finder/zipball/ad4daa7c38668dcb031e63bc99ea9bd42196a2cb", + "reference": "ad4daa7c38668dcb031e63bc99ea9bd42196a2cb", "shasum": "" }, "require": { @@ -6190,7 +6185,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.4.6" + "source": "https://github.com/symfony/finder/tree/v7.4.5" }, "funding": [ { @@ -6210,20 +6205,20 @@ "type": "tidelift" } ], - "time": "2026-01-29T09:40:50+00:00" + "time": "2026-01-26T15:07:59+00:00" }, { "name": "symfony/http-foundation", - "version": "v7.4.7", + "version": "v7.4.5", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "f94b3e7b7dafd40e666f0c9ff2084133bae41e81" + "reference": "446d0db2b1f21575f1284b74533e425096abdfb6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/f94b3e7b7dafd40e666f0c9ff2084133bae41e81", - "reference": "f94b3e7b7dafd40e666f0c9ff2084133bae41e81", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/446d0db2b1f21575f1284b74533e425096abdfb6", + "reference": "446d0db2b1f21575f1284b74533e425096abdfb6", "shasum": "" }, "require": { @@ -6272,7 +6267,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v7.4.7" + "source": "https://github.com/symfony/http-foundation/tree/v7.4.5" }, "funding": [ { @@ -6292,20 +6287,20 @@ "type": "tidelift" } ], - "time": "2026-03-06T13:15:18+00:00" + "time": "2026-01-27T16:16:02+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.4.7", + "version": "v7.4.5", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "3b3fcf386c809be990c922e10e4c620d6367cab1" + "reference": "229eda477017f92bd2ce7615d06222ec0c19e82a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/3b3fcf386c809be990c922e10e4c620d6367cab1", - "reference": "3b3fcf386c809be990c922e10e4c620d6367cab1", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/229eda477017f92bd2ce7615d06222ec0c19e82a", + "reference": "229eda477017f92bd2ce7615d06222ec0c19e82a", "shasum": "" }, "require": { @@ -6347,7 +6342,7 @@ "symfony/config": "^6.4|^7.0|^8.0", "symfony/console": "^6.4|^7.0|^8.0", "symfony/css-selector": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4.1|^7.0.1|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", "symfony/dom-crawler": "^6.4|^7.0|^8.0", "symfony/expression-language": "^6.4|^7.0|^8.0", "symfony/finder": "^6.4|^7.0|^8.0", @@ -6391,7 +6386,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v7.4.7" + "source": "https://github.com/symfony/http-kernel/tree/v7.4.5" }, "funding": [ { @@ -6411,20 +6406,20 @@ "type": "tidelift" } ], - "time": "2026-03-06T16:33:18+00:00" + "time": "2026-01-28T10:33:42+00:00" }, { "name": "symfony/mailer", - "version": "v7.4.6", + "version": "v7.4.4", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "b02726f39a20bc65e30364f5c750c4ddbf1f58e9" + "reference": "7b750074c40c694ceb34cb926d6dffee231c5cd6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/b02726f39a20bc65e30364f5c750c4ddbf1f58e9", - "reference": "b02726f39a20bc65e30364f5c750c4ddbf1f58e9", + "url": "https://api.github.com/repos/symfony/mailer/zipball/7b750074c40c694ceb34cb926d6dffee231c5cd6", + "reference": "7b750074c40c694ceb34cb926d6dffee231c5cd6", "shasum": "" }, "require": { @@ -6475,7 +6470,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v7.4.6" + "source": "https://github.com/symfony/mailer/tree/v7.4.4" }, "funding": [ { @@ -6495,20 +6490,20 @@ "type": "tidelift" } ], - "time": "2026-02-25T16:50:00+00:00" + "time": "2026-01-08T08:25:11+00:00" }, { "name": "symfony/mime", - "version": "v7.4.7", + "version": "v7.4.5", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "da5ab4fde3f6c88ab06e96185b9922f48b677cd1" + "reference": "b18c7e6e9eee1e19958138df10412f3c4c316148" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/da5ab4fde3f6c88ab06e96185b9922f48b677cd1", - "reference": "da5ab4fde3f6c88ab06e96185b9922f48b677cd1", + "url": "https://api.github.com/repos/symfony/mime/zipball/b18c7e6e9eee1e19958138df10412f3c4c316148", + "reference": "b18c7e6e9eee1e19958138df10412f3c4c316148", "shasum": "" }, "require": { @@ -6519,7 +6514,7 @@ }, "conflict": { "egulias/email-validator": "~3.0.0", - "phpdocumentor/reflection-docblock": "<5.2|>=7", + "phpdocumentor/reflection-docblock": "<5.2|>=6", "phpdocumentor/type-resolver": "<1.5.1", "symfony/mailer": "<6.4", "symfony/serializer": "<6.4.3|>7.0,<7.0.3" @@ -6527,7 +6522,7 @@ "require-dev": { "egulias/email-validator": "^2.1.10|^3.1|^4", "league/html-to-markdown": "^5.0", - "phpdocumentor/reflection-docblock": "^5.2|^6.0", + "phpdocumentor/reflection-docblock": "^5.2", "symfony/dependency-injection": "^6.4|^7.0|^8.0", "symfony/process": "^6.4|^7.0|^8.0", "symfony/property-access": "^6.4|^7.0|^8.0", @@ -6564,7 +6559,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v7.4.7" + "source": "https://github.com/symfony/mime/tree/v7.4.5" }, "funding": [ { @@ -6584,7 +6579,7 @@ "type": "tidelift" } ], - "time": "2026-03-05T15:24:09+00:00" + "time": "2026-01-27T08:59:58+00:00" }, { "name": "symfony/polyfill-ctype", @@ -7402,16 +7397,16 @@ }, { "name": "symfony/routing", - "version": "v7.4.6", + "version": "v7.4.4", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "238d749c56b804b31a9bf3e26519d93b65a60938" + "reference": "0798827fe2c79caeed41d70b680c2c3507d10147" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/238d749c56b804b31a9bf3e26519d93b65a60938", - "reference": "238d749c56b804b31a9bf3e26519d93b65a60938", + "url": "https://api.github.com/repos/symfony/routing/zipball/0798827fe2c79caeed41d70b680c2c3507d10147", + "reference": "0798827fe2c79caeed41d70b680c2c3507d10147", "shasum": "" }, "require": { @@ -7463,7 +7458,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v7.4.6" + "source": "https://github.com/symfony/routing/tree/v7.4.4" }, "funding": [ { @@ -7483,7 +7478,7 @@ "type": "tidelift" } ], - "time": "2026-02-25T16:50:00+00:00" + "time": "2026-01-12T12:19:02+00:00" }, { "name": "symfony/service-contracts", @@ -7574,16 +7569,16 @@ }, { "name": "symfony/string", - "version": "v7.4.6", + "version": "v7.4.4", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "9f209231affa85aa930a5e46e6eb03381424b30b" + "reference": "1c4b10461bf2ec27537b5f36105337262f5f5d6f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/9f209231affa85aa930a5e46e6eb03381424b30b", - "reference": "9f209231affa85aa930a5e46e6eb03381424b30b", + "url": "https://api.github.com/repos/symfony/string/zipball/1c4b10461bf2ec27537b5f36105337262f5f5d6f", + "reference": "1c4b10461bf2ec27537b5f36105337262f5f5d6f", "shasum": "" }, "require": { @@ -7641,7 +7636,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.4.6" + "source": "https://github.com/symfony/string/tree/v7.4.4" }, "funding": [ { @@ -7661,20 +7656,20 @@ "type": "tidelift" } ], - "time": "2026-02-09T09:33:46+00:00" + "time": "2026-01-12T10:54:30+00:00" }, { "name": "symfony/translation", - "version": "v7.4.6", + "version": "v7.4.4", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "1888cf064399868af3784b9e043240f1d89d25ce" + "reference": "bfde13711f53f549e73b06d27b35a55207528877" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/1888cf064399868af3784b9e043240f1d89d25ce", - "reference": "1888cf064399868af3784b9e043240f1d89d25ce", + "url": "https://api.github.com/repos/symfony/translation/zipball/bfde13711f53f549e73b06d27b35a55207528877", + "reference": "bfde13711f53f549e73b06d27b35a55207528877", "shasum": "" }, "require": { @@ -7741,7 +7736,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v7.4.6" + "source": "https://github.com/symfony/translation/tree/v7.4.4" }, "funding": [ { @@ -7761,7 +7756,7 @@ "type": "tidelift" } ], - "time": "2026-02-17T07:53:42+00:00" + "time": "2026-01-13T10:40:19+00:00" }, { "name": "symfony/translation-contracts", @@ -7925,16 +7920,16 @@ }, { "name": "symfony/var-dumper", - "version": "v7.4.6", + "version": "v7.4.4", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "045321c440ac18347b136c63d2e9bf28a2dc0291" + "reference": "0e4769b46a0c3c62390d124635ce59f66874b282" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/045321c440ac18347b136c63d2e9bf28a2dc0291", - "reference": "045321c440ac18347b136c63d2e9bf28a2dc0291", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/0e4769b46a0c3c62390d124635ce59f66874b282", + "reference": "0e4769b46a0c3c62390d124635ce59f66874b282", "shasum": "" }, "require": { @@ -7988,7 +7983,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.4.6" + "source": "https://github.com/symfony/var-dumper/tree/v7.4.4" }, "funding": [ { @@ -8008,7 +8003,7 @@ "type": "tidelift" } ], - "time": "2026-02-15T10:53:20+00:00" + "time": "2026-01-01T22:13:48+00:00" }, { "name": "symfony/var-exporter", @@ -8093,16 +8088,16 @@ }, { "name": "symfony/yaml", - "version": "v7.4.6", + "version": "v7.4.1", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "58751048de17bae71c5aa0d13cb19d79bca26391" + "reference": "24dd4de28d2e3988b311751ac49e684d783e2345" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/58751048de17bae71c5aa0d13cb19d79bca26391", - "reference": "58751048de17bae71c5aa0d13cb19d79bca26391", + "url": "https://api.github.com/repos/symfony/yaml/zipball/24dd4de28d2e3988b311751ac49e684d783e2345", + "reference": "24dd4de28d2e3988b311751ac49e684d783e2345", "shasum": "" }, "require": { @@ -8145,7 +8140,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v7.4.6" + "source": "https://github.com/symfony/yaml/tree/v7.4.1" }, "funding": [ { @@ -8165,7 +8160,7 @@ "type": "tidelift" } ], - "time": "2026-02-09T09:33:46+00:00" + "time": "2025-12-04T18:11:45+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -8668,16 +8663,16 @@ }, { "name": "zircote/swagger-php", - "version": "5.8.3", + "version": "5.8.1", "source": { "type": "git", "url": "https://github.com/zircote/swagger-php.git", - "reference": "098223019f764a16715f64089a58606096719c98" + "reference": "8adf6bb57561243aca48339e7f39bddf27ae546c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zircote/swagger-php/zipball/098223019f764a16715f64089a58606096719c98", - "reference": "098223019f764a16715f64089a58606096719c98", + "url": "https://api.github.com/repos/zircote/swagger-php/zipball/8adf6bb57561243aca48339e7f39bddf27ae546c", + "reference": "8adf6bb57561243aca48339e7f39bddf27ae546c", "shasum": "" }, "require": { @@ -8750,7 +8745,7 @@ ], "support": { "issues": "https://github.com/zircote/swagger-php/issues", - "source": "https://github.com/zircote/swagger-php/tree/5.8.3" + "source": "https://github.com/zircote/swagger-php/tree/5.8.1" }, "funding": [ { @@ -8758,7 +8753,7 @@ "type": "github" } ], - "time": "2026-03-02T00:47:18+00:00" + "time": "2026-02-03T23:48:39+00:00" } ], "packages-dev": [ @@ -9135,40 +9130,40 @@ }, { "name": "larastan/larastan", - "version": "v3.9.3", + "version": "v3.9.2", "source": { "type": "git", "url": "https://github.com/larastan/larastan.git", - "reference": "64a52bcc5347c89fdf131cb59f96ebfbc8d1ad65" + "reference": "2e9ed291bdc1969e7f270fb33c9cdf3c912daeb2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/larastan/larastan/zipball/64a52bcc5347c89fdf131cb59f96ebfbc8d1ad65", - "reference": "64a52bcc5347c89fdf131cb59f96ebfbc8d1ad65", + "url": "https://api.github.com/repos/larastan/larastan/zipball/2e9ed291bdc1969e7f270fb33c9cdf3c912daeb2", + "reference": "2e9ed291bdc1969e7f270fb33c9cdf3c912daeb2", "shasum": "" }, "require": { "ext-json": "*", "iamcal/sql-parser": "^0.7.0", - "illuminate/console": "^11.44.2 || ^12.4.1 || ^13", - "illuminate/container": "^11.44.2 || ^12.4.1 || ^13", - "illuminate/contracts": "^11.44.2 || ^12.4.1 || ^13", - "illuminate/database": "^11.44.2 || ^12.4.1 || ^13", - "illuminate/http": "^11.44.2 || ^12.4.1 || ^13", - "illuminate/pipeline": "^11.44.2 || ^12.4.1 || ^13", - "illuminate/support": "^11.44.2 || ^12.4.1 || ^13", + "illuminate/console": "^11.44.2 || ^12.4.1", + "illuminate/container": "^11.44.2 || ^12.4.1", + "illuminate/contracts": "^11.44.2 || ^12.4.1", + "illuminate/database": "^11.44.2 || ^12.4.1", + "illuminate/http": "^11.44.2 || ^12.4.1", + "illuminate/pipeline": "^11.44.2 || ^12.4.1", + "illuminate/support": "^11.44.2 || ^12.4.1", "php": "^8.2", "phpstan/phpstan": "^2.1.32" }, "require-dev": { "doctrine/coding-standard": "^13", - "laravel/framework": "^11.44.2 || ^12.7.2 || ^13", + "laravel/framework": "^11.44.2 || ^12.7.2", "mockery/mockery": "^1.6.12", "nikic/php-parser": "^5.4", - "orchestra/canvas": "^v9.2.2 || ^10.0.1 || ^11", - "orchestra/testbench-core": "^9.12.0 || ^10.1 || ^11", + "orchestra/canvas": "^v9.2.2 || ^10.0.1", + "orchestra/testbench-core": "^9.12.0 || ^10.1", "phpstan/phpstan-deprecation-rules": "^2.0.1", - "phpunit/phpunit": "^10.5.35 || ^11.5.15 || ^12.5.8" + "phpunit/phpunit": "^10.5.35 || ^11.5.15" }, "suggest": { "orchestra/testbench": "Using Larastan for analysing a package needs Testbench", @@ -9213,7 +9208,7 @@ ], "support": { "issues": "https://github.com/larastan/larastan/issues", - "source": "https://github.com/larastan/larastan/tree/v3.9.3" + "source": "https://github.com/larastan/larastan/tree/v3.9.2" }, "funding": [ { @@ -9221,20 +9216,20 @@ "type": "github" } ], - "time": "2026-02-20T12:07:12+00:00" + "time": "2026-01-30T15:16:32+00:00" }, { "name": "laravel/pint", - "version": "v1.29.0", + "version": "v1.27.0", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "bdec963f53172c5e36330f3a400604c69bf02d39" + "reference": "c67b4195b75491e4dfc6b00b1c78b68d86f54c90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/bdec963f53172c5e36330f3a400604c69bf02d39", - "reference": "bdec963f53172c5e36330f3a400604c69bf02d39", + "url": "https://api.github.com/repos/laravel/pint/zipball/c67b4195b75491e4dfc6b00b1c78b68d86f54c90", + "reference": "c67b4195b75491e4dfc6b00b1c78b68d86f54c90", "shasum": "" }, "require": { @@ -9245,14 +9240,13 @@ "php": "^8.2.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.94.2", - "illuminate/view": "^12.54.1", - "larastan/larastan": "^3.9.3", - "laravel-zero/framework": "^12.0.5", + "friendsofphp/php-cs-fixer": "^3.92.4", + "illuminate/view": "^12.44.0", + "larastan/larastan": "^3.8.1", + "laravel-zero/framework": "^12.0.4", "mockery/mockery": "^1.6.12", - "nunomaduro/termwind": "^2.4.0", - "pestphp/pest": "^3.8.6", - "shipfastlabs/agent-detector": "^1.1.0" + "nunomaduro/termwind": "^2.3.3", + "pestphp/pest": "^3.8.4" }, "bin": [ "builds/pint" @@ -9289,32 +9283,32 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2026-03-12T15:51:39+00:00" + "time": "2026-01-05T16:49:17+00:00" }, { "name": "laravel/sail", - "version": "v1.53.0", + "version": "v1.52.0", "source": { "type": "git", "url": "https://github.com/laravel/sail.git", - "reference": "e340eaa2bea9b99192570c48ed837155dbf24fbb" + "reference": "64ac7d8abb2dbcf2b76e61289451bae79066b0b3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/sail/zipball/e340eaa2bea9b99192570c48ed837155dbf24fbb", - "reference": "e340eaa2bea9b99192570c48ed837155dbf24fbb", + "url": "https://api.github.com/repos/laravel/sail/zipball/64ac7d8abb2dbcf2b76e61289451bae79066b0b3", + "reference": "64ac7d8abb2dbcf2b76e61289451bae79066b0b3", "shasum": "" }, "require": { - "illuminate/console": "^9.52.16|^10.0|^11.0|^12.0|^13.0", - "illuminate/contracts": "^9.52.16|^10.0|^11.0|^12.0|^13.0", - "illuminate/support": "^9.52.16|^10.0|^11.0|^12.0|^13.0", + "illuminate/console": "^9.52.16|^10.0|^11.0|^12.0", + "illuminate/contracts": "^9.52.16|^10.0|^11.0|^12.0", + "illuminate/support": "^9.52.16|^10.0|^11.0|^12.0", "php": "^8.0", - "symfony/console": "^6.0|^7.0|^8.0", - "symfony/yaml": "^6.0|^7.0|^8.0" + "symfony/console": "^6.0|^7.0", + "symfony/yaml": "^6.0|^7.0" }, "require-dev": { - "orchestra/testbench": "^7.0|^8.0|^9.0|^10.0|^11.0", + "orchestra/testbench": "^7.0|^8.0|^9.0|^10.0", "phpstan/phpstan": "^2.0" }, "bin": [ @@ -9352,7 +9346,7 @@ "issues": "https://github.com/laravel/sail/issues", "source": "https://github.com/laravel/sail" }, - "time": "2026-02-06T12:16:02+00:00" + "time": "2026-01-01T02:46:03+00:00" }, { "name": "mockery/mockery", @@ -9860,11 +9854,11 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.40", + "version": "2.1.38", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/9b2c7aeb83a75d8680ea5e7c9b7fca88052b766b", - "reference": "9b2c7aeb83a75d8680ea5e7c9b7fca88052b766b", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/dfaf1f530e1663aa167bc3e52197adb221582629", + "reference": "dfaf1f530e1663aa167bc3e52197adb221582629", "shasum": "" }, "require": { @@ -9909,7 +9903,7 @@ "type": "github" } ], - "time": "2026-02-23T15:04:35+00:00" + "time": "2026-01-30T17:12:46+00:00" }, { "name": "phpunit/php-code-coverage", @@ -11296,16 +11290,16 @@ }, { "name": "spatie/backtrace", - "version": "1.8.2", + "version": "1.8.1", "source": { "type": "git", "url": "https://github.com/spatie/backtrace.git", - "reference": "8ffe78be5ed355b5009e3dd989d183433e9a5adc" + "reference": "8c0f16a59ae35ec8c62d85c3c17585158f430110" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/backtrace/zipball/8ffe78be5ed355b5009e3dd989d183433e9a5adc", - "reference": "8ffe78be5ed355b5009e3dd989d183433e9a5adc", + "url": "https://api.github.com/repos/spatie/backtrace/zipball/8c0f16a59ae35ec8c62d85c3c17585158f430110", + "reference": "8c0f16a59ae35ec8c62d85c3c17585158f430110", "shasum": "" }, "require": { @@ -11316,7 +11310,7 @@ "laravel/serializable-closure": "^1.3 || ^2.0", "phpunit/phpunit": "^9.3 || ^11.4.3", "spatie/phpunit-snapshot-assertions": "^4.2 || ^5.1.6", - "symfony/var-dumper": "^5.1|^6.0|^7.0|^8.0" + "symfony/var-dumper": "^5.1 || ^6.0 || ^7.0" }, "type": "library", "autoload": { @@ -11344,7 +11338,7 @@ ], "support": { "issues": "https://github.com/spatie/backtrace/issues", - "source": "https://github.com/spatie/backtrace/tree/1.8.2" + "source": "https://github.com/spatie/backtrace/tree/1.8.1" }, "funding": [ { @@ -11356,7 +11350,7 @@ "type": "other" } ], - "time": "2026-03-11T13:48:28+00:00" + "time": "2025-08-26T08:22:30+00:00" }, { "name": "spatie/error-solutions", @@ -11586,23 +11580,23 @@ }, { "name": "spatie/laravel-ignition", - "version": "2.11.0", + "version": "2.10.0", "source": { "type": "git", "url": "https://github.com/spatie/laravel-ignition.git", - "reference": "11f38d1ff7abc583a61c96bf3c1b03610a69cccd" + "reference": "2abefdcca6074a9155f90b4ccb3345af8889d5f5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/11f38d1ff7abc583a61c96bf3c1b03610a69cccd", - "reference": "11f38d1ff7abc583a61c96bf3c1b03610a69cccd", + "url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/2abefdcca6074a9155f90b4ccb3345af8889d5f5", + "reference": "2abefdcca6074a9155f90b4ccb3345af8889d5f5", "shasum": "" }, "require": { "ext-curl": "*", "ext-json": "*", "ext-mbstring": "*", - "illuminate/support": "^11.0|^12.0|^13.0", + "illuminate/support": "^11.0|^12.0", "nesbot/carbon": "^2.72|^3.0", "php": "^8.2", "spatie/ignition": "^1.15.1", @@ -11610,10 +11604,10 @@ "symfony/var-dumper": "^7.4|^8.0" }, "require-dev": { - "livewire/livewire": "^3.7.0|^4.0|dev-josh/v3-laravel-13-support", + "livewire/livewire": "^3.7.0|^4.0", "mockery/mockery": "^1.6.12", - "openai-php/client": "^0.10.3|^0.19", - "orchestra/testbench": "^v9.16.0|^10.6|^11.0", + "openai-php/client": "^0.10.3", + "orchestra/testbench": "^v9.16.0|^10.6", "pestphp/pest": "^3.7|^4.0", "phpstan/extension-installer": "^1.4.3", "phpstan/phpstan-deprecation-rules": "^2.0.3", @@ -11674,7 +11668,7 @@ "type": "github" } ], - "time": "2026-02-22T19:14:05+00:00" + "time": "2026-01-20T13:16:11+00:00" }, { "name": "squizlabs/php_codesniffer", @@ -11757,16 +11751,16 @@ }, { "name": "symfony/config", - "version": "v7.4.7", + "version": "v7.4.4", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "6c17162555bfb58957a55bb0e43e00035b6ae3d5" + "reference": "4275b53b8ab0cf37f48bf273dc2285c8178efdfb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/6c17162555bfb58957a55bb0e43e00035b6ae3d5", - "reference": "6c17162555bfb58957a55bb0e43e00035b6ae3d5", + "url": "https://api.github.com/repos/symfony/config/zipball/4275b53b8ab0cf37f48bf273dc2285c8178efdfb", + "reference": "4275b53b8ab0cf37f48bf273dc2285c8178efdfb", "shasum": "" }, "require": { @@ -11812,7 +11806,7 @@ "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/config/tree/v7.4.7" + "source": "https://github.com/symfony/config/tree/v7.4.4" }, "funding": [ { @@ -11832,20 +11826,20 @@ "type": "tidelift" } ], - "time": "2026-03-06T10:41:14+00:00" + "time": "2026-01-13T11:36:38+00:00" }, { "name": "symfony/dependency-injection", - "version": "v7.4.7", + "version": "v7.4.5", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "0f651e58f4917fb0e2cd261ccbfe3d71e6e0f5db" + "reference": "76a02cddca45a5254479ad68f9fa274ead0a7ef2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/0f651e58f4917fb0e2cd261ccbfe3d71e6e0f5db", - "reference": "0f651e58f4917fb0e2cd261ccbfe3d71e6e0f5db", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/76a02cddca45a5254479ad68f9fa274ead0a7ef2", + "reference": "76a02cddca45a5254479ad68f9fa274ead0a7ef2", "shasum": "" }, "require": { @@ -11896,7 +11890,7 @@ "description": "Allows you to standardize and centralize the way objects are constructed in your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dependency-injection/tree/v7.4.7" + "source": "https://github.com/symfony/dependency-injection/tree/v7.4.5" }, "funding": [ { @@ -11916,20 +11910,20 @@ "type": "tidelift" } ], - "time": "2026-03-03T07:48:48+00:00" + "time": "2026-01-27T16:16:02+00:00" }, { "name": "symfony/filesystem", - "version": "v7.4.6", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "3ebc794fa5315e59fd122561623c2e2e4280538e" + "reference": "d551b38811096d0be9c4691d406991b47c0c630a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/3ebc794fa5315e59fd122561623c2e2e4280538e", - "reference": "3ebc794fa5315e59fd122561623c2e2e4280538e", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/d551b38811096d0be9c4691d406991b47c0c630a", + "reference": "d551b38811096d0be9c4691d406991b47c0c630a", "shasum": "" }, "require": { @@ -11966,7 +11960,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v7.4.6" + "source": "https://github.com/symfony/filesystem/tree/v7.4.0" }, "funding": [ { @@ -11986,7 +11980,7 @@ "type": "tidelift" } ], - "time": "2026-02-25T16:50:00+00:00" + "time": "2025-11-27T13:27:24+00:00" }, { "name": "theseer/tokenizer", @@ -12042,15 +12036,15 @@ "aliases": [], "minimum-stability": "stable", "stability-flags": { + "phpmd/phpmd": 0, "smartpings/php-sdk": 20, - "whilesmart/eloquent-roles": 20, - "phpmd/phpmd": 0 + "whilesmart/eloquent-roles": 20 }, "prefer-stable": true, "prefer-lowest": false, "platform": { "php": "^8.2" }, - "platform-dev": [], - "plugin-api-version": "2.6.0" + "platform-dev": {}, + "plugin-api-version": "2.9.0" } From 49358f41e97d84d152e20ddfd9c5fa464c0dee27 Mon Sep 17 00:00:00 2001 From: iMercy Date: Sun, 12 Apr 2026 17:45:55 -0400 Subject: [PATCH 14/25] chore: Delete comments in transactioncontroller --- app/Http/Controllers/API/v1/TransactionController.php | 7 ------- 1 file changed, 7 deletions(-) diff --git a/app/Http/Controllers/API/v1/TransactionController.php b/app/Http/Controllers/API/v1/TransactionController.php index bf6daa2a..c07c599d 100644 --- a/app/Http/Controllers/API/v1/TransactionController.php +++ b/app/Http/Controllers/API/v1/TransactionController.php @@ -857,13 +857,6 @@ private function isMyselfTransfer($data, $user, $request): bool $partyId = $data['party_id'] ?? null; $party = $user->parties()->find($partyId); - // DEBUG: See what is actually being compared - // dd([ - // 'isMyselfParty' => $party->getConfigValue('is-myself'), - // 'feature_enabled' => $user->getConfigValue('create-transfers-for-myself-transactions'), - // 'has_from_wallet' => $request->has('from_wallet_id') - // ]); - $isMyselfParty = $party && $party->getConfigValue('is-myself'); return $featureEnabled && $isMyselfParty && $request->has('from_wallet_id'); From 6f15a3868ed110137a88eb350bd513495e37fd57 Mon Sep 17 00:00:00 2001 From: iMercy Date: Sun, 12 Apr 2026 20:39:54 -0400 Subject: [PATCH 15/25] chore: Delete comments in app config --- config/app.php | 9 --------- 1 file changed, 9 deletions(-) diff --git a/config/app.php b/config/app.php index cd74d1ed..0a241bdc 100644 --- a/config/app.php +++ b/config/app.php @@ -185,13 +185,4 @@ // 'Example' => App\Facades\Example::class, ])->toArray(), - - /* - |-------------------------------------------------------------------------- - | Convert Myself to Transfer - |-------------------------------------------------------------------------- - | - | Toggle feature to convert "myself" party to "transfer" type when creating a transaction. This is useful for users who want to track their own transactions without creating a separate party for themselves. - | - */ ]; From ae50a3bafd7130e048ae1e4d747d531f4f3b8672 Mon Sep 17 00:00:00 2001 From: iMercy Date: Sun, 12 Apr 2026 22:46:15 -0400 Subject: [PATCH 16/25] chore: Add helper function to pass phpmd --- .../API/v1/TransactionController.php | 42 ++++++++++--------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/app/Http/Controllers/API/v1/TransactionController.php b/app/Http/Controllers/API/v1/TransactionController.php index c07c599d..bc3e9b38 100644 --- a/app/Http/Controllers/API/v1/TransactionController.php +++ b/app/Http/Controllers/API/v1/TransactionController.php @@ -249,7 +249,7 @@ enum: ['daily', 'weekly', 'monthly', 'yearly'] */ public function store(Request $request): JsonResponse { - $validationResult = $this->validateRequestData($request); + $validationResult = $this->validateStoreData($request); if (! $validationResult['isValidated']) { return $this->failure($validationResult['message'], $validationResult['code'], $validationResult['errors']); @@ -571,23 +571,8 @@ public function show($transactionId, Request $request): JsonResponse */ public function update(Request $request, $transactionId): JsonResponse { - $validationResult = $this->validateRequest($request, [ - 'client_id' => ['nullable', 'string', new ValidateClientId()], - 'amount' => 'nullable|numeric|min:0.01', - 'type' => 'nullable|string|in:income,expense', - 'datetime' => ['nullable', new Iso8601DateTime()], - 'description' => 'nullable|string', - 'party_id' => 'nullable|integer|exists:parties,id', - 'wallet_id' => 'sometimes|integer|exists:wallets,id', - 'group_id' => 'nullable|integer|exists:groups,id', - 'categories' => 'nullable|array', - 'categories.*' => 'integer|exists:categories,id', - 'is_recurring' => 'nullable|boolean', - 'recurrence_period' => 'nullable|string|in:daily,weekly,monthly,yearly', - 'recurrence_interval' => 'nullable|integer|min:1', - 'recurrence_ends_at' => ['nullable', 'date', 'after:today', new Iso8601DateTime()], - 'updated_at' => ['nullable', new Iso8601DateTime()], - ]); + + $validationResult = $this->validateUpdateData($request); if (! $validationResult['isValidated']) { return $this->failure($validationResult['message'], $validationResult['code'], $validationResult['errors']); @@ -823,7 +808,7 @@ private function validateResourceOwnership(array $data, array $categories = []): } } - private function validateRequestData(Request $request): array + private function validateStoreData(Request $request): array { return $this->validateRequest($request, [ 'client_id' => ['nullable', 'string', new ValidateClientId()], @@ -848,6 +833,25 @@ private function validateRequestData(Request $request): array ]); } + private function validateUpdateData(Request $request): array + { + return $this->validateRequest($request, [ + 'client_id' => ['nullable', 'string', new ValidateClientId()], + 'amount' => 'nullable|numeric|min:0.01', + 'type' => 'nullable|string|in:income,expense', + 'description' => 'nullable|string', + 'datetime' => ['nullable', new Iso8601DateTime()], + 'group_id' => 'nullable|integer|exists:groups,id', + 'party_id' => 'nullable|integer|exists:parties,id', + 'wallet_id' => 'nullable|integer|exists:wallets,id', + 'categories' => 'nullable|array', + 'is_recurring' => 'nullable|boolean', + 'recurrence_period' => 'nullable|string|in:daily,weekly,monthly,yearly', + 'recurrence_interval' => 'nullable|integer|min:1', + + ]); + } + private function isMyselfTransfer($data, $user, $request): bool { //get user preferences (boolean) From de2b185363bce981d732b4916d052c9077cf95cb Mon Sep 17 00:00:00 2001 From: iMercy Date: Sun, 12 Apr 2026 23:40:36 -0400 Subject: [PATCH 17/25] chore: Helper functions to reduce cyclo complexity --- .../API/v1/TransactionController.php | 361 +++++++++--------- 1 file changed, 180 insertions(+), 181 deletions(-) diff --git a/app/Http/Controllers/API/v1/TransactionController.php b/app/Http/Controllers/API/v1/TransactionController.php index bc3e9b38..fb89daa1 100644 --- a/app/Http/Controllers/API/v1/TransactionController.php +++ b/app/Http/Controllers/API/v1/TransactionController.php @@ -268,10 +268,6 @@ public function store(Request $request): JsonResponse return $this->success($transfer, statusCode: 201); } - - - - if (! empty($data['client_id'])) { $existingTransaction = Transaction::findByClientId($data['client_id'], $user); if ($existingTransaction) { @@ -279,25 +275,7 @@ public function store(Request $request): JsonResponse } } - $recurringTransactionData = []; - - if (isset($data['is_recurring']) && $data['is_recurring']) { - $result = $this->validateRequest($request, [ - 'recurrence_period' => 'required', - ]); - if (! $result['isValidated']) { - return $this->failure($result['message'], $result['code'], $result['errors']); - } - - $recurringTransactionData['recurrence_period'] = $data['recurrence_period']; - $recurringTransactionData['recurrence_interval'] = $data['recurrence_interval'] ?? null; - $recurringTransactionData['recurrence_ends_at'] = $data['recurrence_ends_at'] ?? null; - } - // remove recurring transaction data from $data - unset($data['recurrence_ends_at']); - unset($data['recurrence_interval']); - unset($data['recurrence_period']); - unset($data['is_recurring']); + $recurringTransactionData = $this->extractAndValidateRecurringData($request, $data); if (isset($recurringTransactionData['recurrence_ends_at'])) { $recurringTransactionData['recurrence_ends_at'] = @@ -306,70 +284,14 @@ public function store(Request $request): JsonResponse ); } - if (isset($data['datetime'])) { - $data['datetime'] = format_iso8601_to_sql($data['datetime']); - } - - if (isset($data['created_at'])) { - $data['created_at'] = format_iso8601_to_sql($data['created_at']); - } - - $categories = []; - if (isset($data['categories'])) { - $categories = $data['categories']; - unset($data['categories']); - } + $this->formatTransactionDates($data); + $categories = $this->extractCategories($data); try { // Validate ownership of all resources before creating the transaction $this->validateResourceOwnership($data, $categories); - $transaction = DB::transaction(function () use ($request, $data, $categories, $recurringTransactionData, $user) { - /** @var Transaction $transaction */ - $transaction = $user->transactions()->create($data); - - if (isset($data['client_id'])) { - $transaction->setClientGeneratedId($data['client_id'], $user); - } - - $transaction->markAsSynced(); - - if (! empty($categories)) { - $transaction->categories()->sync($categories); - } - - if (isset($data['group_id'])) { - $transaction->groups()->sync($data['group_id']); - } - - if ($request->hasFile('files')) { - foreach ($request->file('files') as $file) { - $path = $file->store('transactions'); - $transaction->files()->create([ - 'path' => $path, - 'type' => 'file', - ]); - } - } - - // check if this transaction is recurring - if (! empty($recurringTransactionData)) { - // Create recurring transaction - $recurring_transaction = new RecurringTransactionRule($recurringTransactionData); - $recurring_transaction->next_scheduled_at = - $this->recurringTransactionService->getNextScheduleDate( - $recurring_transaction - ); - $recurring_transaction->transaction_id = $transaction->id; - $recurring_transaction->save(); - - RecurrentTransactionJob::dispatch( - (int) $recurring_transaction->id - )->delay($recurring_transaction->next_scheduled_at); - } - - return $transaction; - }); + $transaction = $this->createTransaction($user, $data, $categories, $recurringTransactionData, $request); } catch (HttpException $e) { return $this->failure($e->getMessage(), $e->getStatusCode()); } catch (Throwable $e) { @@ -579,26 +501,8 @@ public function update(Request $request, $transactionId): JsonResponse } $validatedData = $validationResult['data']; - $recurringTransactionData = []; - - if (isset($validatedData['is_recurring']) && $validatedData['is_recurring']) { - $result = $this->validateRequest($request, [ - 'recurrence_period' => 'required', - ]); - if (! $result['isValidated']) { - return $this->failure($result['message'], $result['code'], $result['errors']); - } - - $recurringTransactionData['recurrence_period'] = $validatedData['recurrence_period']; - $recurringTransactionData['recurrence_interval'] = $validatedData['recurrence_interval'] ?? null; - $recurringTransactionData['recurrence_ends_at'] = $validatedData['recurrence_ends_at'] ?? null; - } - // remove recurring transaction data from $data - unset($validatedData['recurrence_ends_at']); - unset($validatedData['recurrence_interval']); - unset($validatedData['recurrence_period']); - unset($validatedData['is_recurring']); + $recurringTransactionData = $this->extractAndValidateRecurringData($request, $validatedData); if (isset($recurringTransactionData['recurrence_ends_at'])) { $recurringTransactionData['recurrence_ends_at'] = format_iso8601_to_sql( @@ -606,13 +510,7 @@ public function update(Request $request, $transactionId): JsonResponse ); } - if (isset($validatedData['datetime'])) { - $validatedData['datetime'] = format_iso8601_to_sql($validatedData['datetime']); - } - - if (isset($validatedData['updated_at'])) { - $validatedData['updated_at'] = format_iso8601_to_sql($validatedData['updated_at']); - } + $this->formatTransactionDates($validatedData); /** @var Transaction */ $transaction = Transaction::find($transactionId); @@ -628,85 +526,14 @@ public function update(Request $request, $transactionId): JsonResponse } // Extract categories before validation - $categories = $validatedData['categories'] ?? []; + $categories = $this->extractCategories($validatedData); try { // Validate ownership of all resources before updating the transaction $this->validateResourceOwnership($validatedData, $categories); $this->checkUpdatedAt($transaction, $validatedData); - $transaction = DB::transaction(function () use ( - $validatedData, - $transaction, - $recurringTransactionData, - $request - ) { - - $transaction->update(array_filter($validatedData, fn ($value) => $value !== null)); - $transaction->markAsSynced(); - - if (isset($validatedData['categories'])) { - $transaction->categories()->sync($validatedData['categories']); - } - - if (isset($validatedData['group_id'])) { - $transaction->groups()->sync([$validatedData['group_id']]); - } - - $user = $request->user(); - if (isset($request['client_id']) && ! $transaction->client_id) { - $transaction->setClientGeneratedId($request['client_id'], $user); - } - - $recurring_transaction = $transaction->recurringTransactionRule()->first(); - - // check if this transaction is recurring - if (empty($recurringTransactionData)) { - if (! is_null($recurring_transaction)) { - $recurring_transaction->delete(); - } - } else { - $schedule_job = true; - - if (is_null($recurring_transaction)) { - $recurring_transaction = new RecurringTransactionRule( - $recurringTransactionData - ); // create temporay instance from data array - $recurring_transaction->next_scheduled_at = - $this->recurringTransactionService->getNextScheduleDate($recurring_transaction); - $recurring_transaction->transaction_id = $transaction->id; - $recurring_transaction->save(); - } else { - // Check if details have changed. If not, do not reschedule the job - if ( - ( - $recurringTransactionData['recurrence_period'] - == - $recurring_transaction->recurrence_period - ) - && - ( - $recurringTransactionData['recurrence_interval'] - == - $recurring_transaction->recurrence_interval - ) - ) { - $schedule_job = false; - } else { - $recurringTransactionData['next_scheduled_at'] = - $this->recurringTransactionService->getNextScheduleDate($recurring_transaction); - } - $recurring_transaction->update($recurringTransactionData); - } - if ($schedule_job) { - RecurrentTransactionJob::dispatch( - (int) $recurring_transaction->id - )->delay($recurring_transaction->next_scheduled_at); - } - } - - return $transaction; - }); + $transaction = $this->updateTransaction($transaction, $validatedData, $categories, $recurringTransactionData, $request); return $this->success($transaction, 200); } catch (HttpException $e) { @@ -884,4 +711,176 @@ private function handleMyselfTransfer($data, $user, $request) ] ); } + + private function extractAndValidateRecurringData(Request $request, array &$data): array + { + $recurringTransactionData = []; + + if (isset($data['is_recurring']) && $data['is_recurring']) { + $result = $this->validateRequest($request, [ + 'recurrence_period' => 'required', + ]); + if (! $result['isValidated']) { + return $this->failure($result['message'], $result['code'], $result['errors']); + } + + $recurringTransactionData['recurrence_period'] = $data['recurrence_period']; + $recurringTransactionData['recurrence_interval'] = $data['recurrence_interval'] ?? null; + $recurringTransactionData['recurrence_ends_at'] = $data['recurrence_ends_at'] ?? null; + } + // remove recurring transaction data from $data + unset($data['recurrence_ends_at']); + unset($data['recurrence_interval']); + unset($data['recurrence_period']); + unset($data['is_recurring']); + + return $recurringTransactionData; + } + + private function formatTransactionDates(array &$data): void + { + if (isset($data['datetime'])) { + $data['datetime'] = format_iso8601_to_sql($data['datetime']); + } + + if (isset($data['created_at'])) { + $data['created_at'] = format_iso8601_to_sql($data['created_at']); + } + + if (isset($data['updated_at'])) { + $data['updated_at'] = format_iso8601_to_sql($data['updated_at']); + } + } + + private function extractCategories(array &$data): array + { + $categories = []; + if (isset($data['categories'])) { + $categories = $data['categories']; + unset($data['categories']); + } + + return $categories; + } + + private function createTransaction($user, array $data, array $categories, array $recurringTransactionData, Request $request) + { + return DB::transaction(function () use ($request, $data, $categories, $recurringTransactionData, $user) { + /** @var Transaction $transaction */ + $transaction = $user->transactions()->create($data); + + if (isset($data['client_id'])) { + $transaction->setClientGeneratedId($data['client_id'], $user); + } + + $transaction->markAsSynced(); + + if (! empty($categories)) { + $transaction->categories()->sync($categories); + } + + if (isset($data['group_id'])) { + $transaction->groups()->sync($data['group_id']); + } + + if ($request->hasFile('files')) { + foreach ($request->file('files') as $file) { + $path = $file->store('transactions'); + $transaction->files()->create([ + 'path' => $path, + 'type' => 'file', + ]); + } + } + + // check if this transaction is recurring + if (! empty($recurringTransactionData)) { + // Create recurring transaction + $recurring_transaction = new RecurringTransactionRule($recurringTransactionData); + $recurring_transaction->next_scheduled_at = + $this->recurringTransactionService->getNextScheduleDate( + $recurring_transaction + ); + $recurring_transaction->transaction_id = $transaction->id; + $recurring_transaction->save(); + + RecurrentTransactionJob::dispatch( + (int) $recurring_transaction->id + )->delay($recurring_transaction->next_scheduled_at); + } + + return $transaction; + }); + } + + + private function updateTransaction(Transaction $transaction, array $validatedData, array $recurringTransactionData, Request $request) + { + return DB::transaction(function () use ($validatedData, $transaction, $recurringTransactionData, $request) { + $transaction->update(array_filter($validatedData, fn ($value) => $value !== null)); + $transaction->markAsSynced(); + + if (isset($validatedData['categories'])) { + $transaction->categories()->sync($validatedData['categories']); + } + + if (isset($validatedData['group_id'])) { + $transaction->groups()->sync([$validatedData['group_id']]); + } + + $user = $request->user(); + if (isset($request['client_id']) && ! $transaction->client_id) { + $transaction->setClientGeneratedId($request['client_id'], $user); + } + + $recurring_transaction = $transaction->recurringTransactionRule()->first(); + + // check if this transaction is recurring + if (empty($recurringTransactionData)) { + if (! is_null($recurring_transaction)) { + $recurring_transaction->delete(); + } + } else { + $schedule_job = true; + + if (is_null($recurring_transaction)) { + $recurring_transaction = new RecurringTransactionRule( + $recurringTransactionData + ); // create temporary instance from data array + $recurring_transaction->next_scheduled_at = + $this->recurringTransactionService->getNextScheduleDate($recurring_transaction); + $recurring_transaction->transaction_id = $transaction->id; + $recurring_transaction->save(); + } else { + // Check if details have changed. If not, do not reschedule the job + if ( + ( + $recurringTransactionData['recurrence_period'] + == + $recurring_transaction->recurrence_period + ) + && + ( + $recurringTransactionData['recurrence_interval'] + == + $recurring_transaction->recurrence_interval + ) + ) { + $schedule_job = false; + } else { + $recurringTransactionData['next_scheduled_at'] = + $this->recurringTransactionService->getNextScheduleDate($recurring_transaction); + } + $recurring_transaction->update($recurringTransactionData); + } + if ($schedule_job) { + RecurrentTransactionJob::dispatch( + (int) $recurring_transaction->id + )->delay($recurring_transaction->next_scheduled_at); + } + } + + return $transaction; + }); + } } From c83223c88368dd5d44c1a74853cc957c5b1b8ff4 Mon Sep 17 00:00:00 2001 From: iMercy Date: Mon, 13 Apr 2026 00:07:51 -0400 Subject: [PATCH 18/25] Revert "chore: Undo previous changes since they rather increase complexity to 91" This reverts commit de2b185363bce981d732b4916d052c9077cf95cb. --- .../API/v1/TransactionController.php | 361 +++++++++--------- 1 file changed, 181 insertions(+), 180 deletions(-) diff --git a/app/Http/Controllers/API/v1/TransactionController.php b/app/Http/Controllers/API/v1/TransactionController.php index fb89daa1..bc3e9b38 100644 --- a/app/Http/Controllers/API/v1/TransactionController.php +++ b/app/Http/Controllers/API/v1/TransactionController.php @@ -268,6 +268,10 @@ public function store(Request $request): JsonResponse return $this->success($transfer, statusCode: 201); } + + + + if (! empty($data['client_id'])) { $existingTransaction = Transaction::findByClientId($data['client_id'], $user); if ($existingTransaction) { @@ -275,7 +279,25 @@ public function store(Request $request): JsonResponse } } - $recurringTransactionData = $this->extractAndValidateRecurringData($request, $data); + $recurringTransactionData = []; + + if (isset($data['is_recurring']) && $data['is_recurring']) { + $result = $this->validateRequest($request, [ + 'recurrence_period' => 'required', + ]); + if (! $result['isValidated']) { + return $this->failure($result['message'], $result['code'], $result['errors']); + } + + $recurringTransactionData['recurrence_period'] = $data['recurrence_period']; + $recurringTransactionData['recurrence_interval'] = $data['recurrence_interval'] ?? null; + $recurringTransactionData['recurrence_ends_at'] = $data['recurrence_ends_at'] ?? null; + } + // remove recurring transaction data from $data + unset($data['recurrence_ends_at']); + unset($data['recurrence_interval']); + unset($data['recurrence_period']); + unset($data['is_recurring']); if (isset($recurringTransactionData['recurrence_ends_at'])) { $recurringTransactionData['recurrence_ends_at'] = @@ -284,14 +306,70 @@ public function store(Request $request): JsonResponse ); } - $this->formatTransactionDates($data); - $categories = $this->extractCategories($data); + if (isset($data['datetime'])) { + $data['datetime'] = format_iso8601_to_sql($data['datetime']); + } + + if (isset($data['created_at'])) { + $data['created_at'] = format_iso8601_to_sql($data['created_at']); + } + + $categories = []; + if (isset($data['categories'])) { + $categories = $data['categories']; + unset($data['categories']); + } try { // Validate ownership of all resources before creating the transaction $this->validateResourceOwnership($data, $categories); - $transaction = $this->createTransaction($user, $data, $categories, $recurringTransactionData, $request); + $transaction = DB::transaction(function () use ($request, $data, $categories, $recurringTransactionData, $user) { + /** @var Transaction $transaction */ + $transaction = $user->transactions()->create($data); + + if (isset($data['client_id'])) { + $transaction->setClientGeneratedId($data['client_id'], $user); + } + + $transaction->markAsSynced(); + + if (! empty($categories)) { + $transaction->categories()->sync($categories); + } + + if (isset($data['group_id'])) { + $transaction->groups()->sync($data['group_id']); + } + + if ($request->hasFile('files')) { + foreach ($request->file('files') as $file) { + $path = $file->store('transactions'); + $transaction->files()->create([ + 'path' => $path, + 'type' => 'file', + ]); + } + } + + // check if this transaction is recurring + if (! empty($recurringTransactionData)) { + // Create recurring transaction + $recurring_transaction = new RecurringTransactionRule($recurringTransactionData); + $recurring_transaction->next_scheduled_at = + $this->recurringTransactionService->getNextScheduleDate( + $recurring_transaction + ); + $recurring_transaction->transaction_id = $transaction->id; + $recurring_transaction->save(); + + RecurrentTransactionJob::dispatch( + (int) $recurring_transaction->id + )->delay($recurring_transaction->next_scheduled_at); + } + + return $transaction; + }); } catch (HttpException $e) { return $this->failure($e->getMessage(), $e->getStatusCode()); } catch (Throwable $e) { @@ -501,8 +579,26 @@ public function update(Request $request, $transactionId): JsonResponse } $validatedData = $validationResult['data']; + $recurringTransactionData = []; + + if (isset($validatedData['is_recurring']) && $validatedData['is_recurring']) { + $result = $this->validateRequest($request, [ + 'recurrence_period' => 'required', + ]); + if (! $result['isValidated']) { + return $this->failure($result['message'], $result['code'], $result['errors']); + } + + $recurringTransactionData['recurrence_period'] = $validatedData['recurrence_period']; + $recurringTransactionData['recurrence_interval'] = $validatedData['recurrence_interval'] ?? null; + $recurringTransactionData['recurrence_ends_at'] = $validatedData['recurrence_ends_at'] ?? null; + } - $recurringTransactionData = $this->extractAndValidateRecurringData($request, $validatedData); + // remove recurring transaction data from $data + unset($validatedData['recurrence_ends_at']); + unset($validatedData['recurrence_interval']); + unset($validatedData['recurrence_period']); + unset($validatedData['is_recurring']); if (isset($recurringTransactionData['recurrence_ends_at'])) { $recurringTransactionData['recurrence_ends_at'] = format_iso8601_to_sql( @@ -510,7 +606,13 @@ public function update(Request $request, $transactionId): JsonResponse ); } - $this->formatTransactionDates($validatedData); + if (isset($validatedData['datetime'])) { + $validatedData['datetime'] = format_iso8601_to_sql($validatedData['datetime']); + } + + if (isset($validatedData['updated_at'])) { + $validatedData['updated_at'] = format_iso8601_to_sql($validatedData['updated_at']); + } /** @var Transaction */ $transaction = Transaction::find($transactionId); @@ -526,14 +628,85 @@ public function update(Request $request, $transactionId): JsonResponse } // Extract categories before validation - $categories = $this->extractCategories($validatedData); + $categories = $validatedData['categories'] ?? []; try { // Validate ownership of all resources before updating the transaction $this->validateResourceOwnership($validatedData, $categories); $this->checkUpdatedAt($transaction, $validatedData); - $transaction = $this->updateTransaction($transaction, $validatedData, $categories, $recurringTransactionData, $request); + $transaction = DB::transaction(function () use ( + $validatedData, + $transaction, + $recurringTransactionData, + $request + ) { + + $transaction->update(array_filter($validatedData, fn ($value) => $value !== null)); + $transaction->markAsSynced(); + + if (isset($validatedData['categories'])) { + $transaction->categories()->sync($validatedData['categories']); + } + + if (isset($validatedData['group_id'])) { + $transaction->groups()->sync([$validatedData['group_id']]); + } + + $user = $request->user(); + if (isset($request['client_id']) && ! $transaction->client_id) { + $transaction->setClientGeneratedId($request['client_id'], $user); + } + + $recurring_transaction = $transaction->recurringTransactionRule()->first(); + + // check if this transaction is recurring + if (empty($recurringTransactionData)) { + if (! is_null($recurring_transaction)) { + $recurring_transaction->delete(); + } + } else { + $schedule_job = true; + + if (is_null($recurring_transaction)) { + $recurring_transaction = new RecurringTransactionRule( + $recurringTransactionData + ); // create temporay instance from data array + $recurring_transaction->next_scheduled_at = + $this->recurringTransactionService->getNextScheduleDate($recurring_transaction); + $recurring_transaction->transaction_id = $transaction->id; + $recurring_transaction->save(); + } else { + // Check if details have changed. If not, do not reschedule the job + if ( + ( + $recurringTransactionData['recurrence_period'] + == + $recurring_transaction->recurrence_period + ) + && + ( + $recurringTransactionData['recurrence_interval'] + == + $recurring_transaction->recurrence_interval + ) + ) { + $schedule_job = false; + } else { + $recurringTransactionData['next_scheduled_at'] = + $this->recurringTransactionService->getNextScheduleDate($recurring_transaction); + } + $recurring_transaction->update($recurringTransactionData); + } + if ($schedule_job) { + RecurrentTransactionJob::dispatch( + (int) $recurring_transaction->id + )->delay($recurring_transaction->next_scheduled_at); + } + } + + return $transaction; + }); return $this->success($transaction, 200); } catch (HttpException $e) { @@ -711,176 +884,4 @@ private function handleMyselfTransfer($data, $user, $request) ] ); } - - private function extractAndValidateRecurringData(Request $request, array &$data): array - { - $recurringTransactionData = []; - - if (isset($data['is_recurring']) && $data['is_recurring']) { - $result = $this->validateRequest($request, [ - 'recurrence_period' => 'required', - ]); - if (! $result['isValidated']) { - return $this->failure($result['message'], $result['code'], $result['errors']); - } - - $recurringTransactionData['recurrence_period'] = $data['recurrence_period']; - $recurringTransactionData['recurrence_interval'] = $data['recurrence_interval'] ?? null; - $recurringTransactionData['recurrence_ends_at'] = $data['recurrence_ends_at'] ?? null; - } - // remove recurring transaction data from $data - unset($data['recurrence_ends_at']); - unset($data['recurrence_interval']); - unset($data['recurrence_period']); - unset($data['is_recurring']); - - return $recurringTransactionData; - } - - private function formatTransactionDates(array &$data): void - { - if (isset($data['datetime'])) { - $data['datetime'] = format_iso8601_to_sql($data['datetime']); - } - - if (isset($data['created_at'])) { - $data['created_at'] = format_iso8601_to_sql($data['created_at']); - } - - if (isset($data['updated_at'])) { - $data['updated_at'] = format_iso8601_to_sql($data['updated_at']); - } - } - - private function extractCategories(array &$data): array - { - $categories = []; - if (isset($data['categories'])) { - $categories = $data['categories']; - unset($data['categories']); - } - - return $categories; - } - - private function createTransaction($user, array $data, array $categories, array $recurringTransactionData, Request $request) - { - return DB::transaction(function () use ($request, $data, $categories, $recurringTransactionData, $user) { - /** @var Transaction $transaction */ - $transaction = $user->transactions()->create($data); - - if (isset($data['client_id'])) { - $transaction->setClientGeneratedId($data['client_id'], $user); - } - - $transaction->markAsSynced(); - - if (! empty($categories)) { - $transaction->categories()->sync($categories); - } - - if (isset($data['group_id'])) { - $transaction->groups()->sync($data['group_id']); - } - - if ($request->hasFile('files')) { - foreach ($request->file('files') as $file) { - $path = $file->store('transactions'); - $transaction->files()->create([ - 'path' => $path, - 'type' => 'file', - ]); - } - } - - // check if this transaction is recurring - if (! empty($recurringTransactionData)) { - // Create recurring transaction - $recurring_transaction = new RecurringTransactionRule($recurringTransactionData); - $recurring_transaction->next_scheduled_at = - $this->recurringTransactionService->getNextScheduleDate( - $recurring_transaction - ); - $recurring_transaction->transaction_id = $transaction->id; - $recurring_transaction->save(); - - RecurrentTransactionJob::dispatch( - (int) $recurring_transaction->id - )->delay($recurring_transaction->next_scheduled_at); - } - - return $transaction; - }); - } - - - private function updateTransaction(Transaction $transaction, array $validatedData, array $recurringTransactionData, Request $request) - { - return DB::transaction(function () use ($validatedData, $transaction, $recurringTransactionData, $request) { - $transaction->update(array_filter($validatedData, fn ($value) => $value !== null)); - $transaction->markAsSynced(); - - if (isset($validatedData['categories'])) { - $transaction->categories()->sync($validatedData['categories']); - } - - if (isset($validatedData['group_id'])) { - $transaction->groups()->sync([$validatedData['group_id']]); - } - - $user = $request->user(); - if (isset($request['client_id']) && ! $transaction->client_id) { - $transaction->setClientGeneratedId($request['client_id'], $user); - } - - $recurring_transaction = $transaction->recurringTransactionRule()->first(); - - // check if this transaction is recurring - if (empty($recurringTransactionData)) { - if (! is_null($recurring_transaction)) { - $recurring_transaction->delete(); - } - } else { - $schedule_job = true; - - if (is_null($recurring_transaction)) { - $recurring_transaction = new RecurringTransactionRule( - $recurringTransactionData - ); // create temporary instance from data array - $recurring_transaction->next_scheduled_at = - $this->recurringTransactionService->getNextScheduleDate($recurring_transaction); - $recurring_transaction->transaction_id = $transaction->id; - $recurring_transaction->save(); - } else { - // Check if details have changed. If not, do not reschedule the job - if ( - ( - $recurringTransactionData['recurrence_period'] - == - $recurring_transaction->recurrence_period - ) - && - ( - $recurringTransactionData['recurrence_interval'] - == - $recurring_transaction->recurrence_interval - ) - ) { - $schedule_job = false; - } else { - $recurringTransactionData['next_scheduled_at'] = - $this->recurringTransactionService->getNextScheduleDate($recurring_transaction); - } - $recurring_transaction->update($recurringTransactionData); - } - if ($schedule_job) { - RecurrentTransactionJob::dispatch( - (int) $recurring_transaction->id - )->delay($recurring_transaction->next_scheduled_at); - } - } - - return $transaction; - }); - } } From 146607b4c29a99382bb821dcc25cd8eaa3621622 Mon Sep 17 00:00:00 2001 From: iMercy Date: Mon, 13 Apr 2026 00:11:40 -0400 Subject: [PATCH 19/25] Revert "chore: Undo changes since they rather increase complexity to 90" This reverts commit ae50a3bafd7130e048ae1e4d747d531f4f3b8672. --- .../API/v1/TransactionController.php | 42 +++++++++---------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/app/Http/Controllers/API/v1/TransactionController.php b/app/Http/Controllers/API/v1/TransactionController.php index bc3e9b38..c07c599d 100644 --- a/app/Http/Controllers/API/v1/TransactionController.php +++ b/app/Http/Controllers/API/v1/TransactionController.php @@ -249,7 +249,7 @@ enum: ['daily', 'weekly', 'monthly', 'yearly'] */ public function store(Request $request): JsonResponse { - $validationResult = $this->validateStoreData($request); + $validationResult = $this->validateRequestData($request); if (! $validationResult['isValidated']) { return $this->failure($validationResult['message'], $validationResult['code'], $validationResult['errors']); @@ -571,8 +571,23 @@ public function show($transactionId, Request $request): JsonResponse */ public function update(Request $request, $transactionId): JsonResponse { - - $validationResult = $this->validateUpdateData($request); + $validationResult = $this->validateRequest($request, [ + 'client_id' => ['nullable', 'string', new ValidateClientId()], + 'amount' => 'nullable|numeric|min:0.01', + 'type' => 'nullable|string|in:income,expense', + 'datetime' => ['nullable', new Iso8601DateTime()], + 'description' => 'nullable|string', + 'party_id' => 'nullable|integer|exists:parties,id', + 'wallet_id' => 'sometimes|integer|exists:wallets,id', + 'group_id' => 'nullable|integer|exists:groups,id', + 'categories' => 'nullable|array', + 'categories.*' => 'integer|exists:categories,id', + 'is_recurring' => 'nullable|boolean', + 'recurrence_period' => 'nullable|string|in:daily,weekly,monthly,yearly', + 'recurrence_interval' => 'nullable|integer|min:1', + 'recurrence_ends_at' => ['nullable', 'date', 'after:today', new Iso8601DateTime()], + 'updated_at' => ['nullable', new Iso8601DateTime()], + ]); if (! $validationResult['isValidated']) { return $this->failure($validationResult['message'], $validationResult['code'], $validationResult['errors']); @@ -808,7 +823,7 @@ private function validateResourceOwnership(array $data, array $categories = []): } } - private function validateStoreData(Request $request): array + private function validateRequestData(Request $request): array { return $this->validateRequest($request, [ 'client_id' => ['nullable', 'string', new ValidateClientId()], @@ -833,25 +848,6 @@ private function validateStoreData(Request $request): array ]); } - private function validateUpdateData(Request $request): array - { - return $this->validateRequest($request, [ - 'client_id' => ['nullable', 'string', new ValidateClientId()], - 'amount' => 'nullable|numeric|min:0.01', - 'type' => 'nullable|string|in:income,expense', - 'description' => 'nullable|string', - 'datetime' => ['nullable', new Iso8601DateTime()], - 'group_id' => 'nullable|integer|exists:groups,id', - 'party_id' => 'nullable|integer|exists:parties,id', - 'wallet_id' => 'nullable|integer|exists:wallets,id', - 'categories' => 'nullable|array', - 'is_recurring' => 'nullable|boolean', - 'recurrence_period' => 'nullable|string|in:daily,weekly,monthly,yearly', - 'recurrence_interval' => 'nullable|integer|min:1', - - ]); - } - private function isMyselfTransfer($data, $user, $request): bool { //get user preferences (boolean) From eb32e9c306b96163343aaa79993982c2954343f0 Mon Sep 17 00:00:00 2001 From: iMercy Date: Sun, 19 Apr 2026 21:35:17 -0400 Subject: [PATCH 20/25] fix: Suppress cyclocomplexity issue --- app/Http/Controllers/API/v1/TransactionController.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/Http/Controllers/API/v1/TransactionController.php b/app/Http/Controllers/API/v1/TransactionController.php index c07c599d..0e5a8af0 100644 --- a/app/Http/Controllers/API/v1/TransactionController.php +++ b/app/Http/Controllers/API/v1/TransactionController.php @@ -20,6 +20,9 @@ use App\Services\TransferService; #[OA\Tag(name: 'Transactions', description: 'Endpoints for managing transactions')] +/** + * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) + */ class TransactionController extends ApiController { use ApiQueryable; From 63af365e44c20e2ecc71f73c3d9b4ab48f543aa0 Mon Sep 17 00:00:00 2001 From: iMercy Date: Thu, 30 Apr 2026 03:23:33 -0400 Subject: [PATCH 21/25] enh: Add convertmyselftotransfer in payload --- app/Http/Controllers/API/v1/TransactionController.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Http/Controllers/API/v1/TransactionController.php b/app/Http/Controllers/API/v1/TransactionController.php index 0e5a8af0..c5bbc438 100644 --- a/app/Http/Controllers/API/v1/TransactionController.php +++ b/app/Http/Controllers/API/v1/TransactionController.php @@ -829,6 +829,7 @@ private function validateResourceOwnership(array $data, array $categories = []): private function validateRequestData(Request $request): array { return $this->validateRequest($request, [ + 'convert_myself_to_transfer' => 'sometimes|boolean', 'client_id' => ['nullable', 'string', new ValidateClientId()], 'amount' => 'required|numeric|min:0.01', 'type' => 'required|string|in:income,expense', From d1588b9d9279c0f372245eabe86eb3b2620ea16f Mon Sep 17 00:00:00 2001 From: iMercy Date: Thu, 30 Apr 2026 07:20:30 -0400 Subject: [PATCH 22/25] chore: Find party only when id is not null --- app/Http/Controllers/API/v1/TransactionController.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/API/v1/TransactionController.php b/app/Http/Controllers/API/v1/TransactionController.php index c5bbc438..3f9f88d9 100644 --- a/app/Http/Controllers/API/v1/TransactionController.php +++ b/app/Http/Controllers/API/v1/TransactionController.php @@ -829,7 +829,7 @@ private function validateResourceOwnership(array $data, array $categories = []): private function validateRequestData(Request $request): array { return $this->validateRequest($request, [ - 'convert_myself_to_transfer' => 'sometimes|boolean', + 'convert_myself_to_transfer' => 'sometimes|boolean', //'sometimes' to allow for the possibility of null entries when user is not transfering to themselves 'client_id' => ['nullable', 'string', new ValidateClientId()], 'amount' => 'required|numeric|min:0.01', 'type' => 'required|string|in:income,expense', @@ -859,7 +859,7 @@ private function isMyselfTransfer($data, $user, $request): bool //identify the party and check its 'is_myself' property; $partyId = $data['party_id'] ?? null; - $party = $user->parties()->find($partyId); + $party = $partyId ? $user->parties()->find($partyId) : null; $isMyselfParty = $party && $party->getConfigValue('is-myself'); From a3c24f28b6e2f1240ccb1968ab417c51ab0e521f Mon Sep 17 00:00:00 2001 From: iMercy Date: Thu, 30 Apr 2026 07:53:21 -0400 Subject: [PATCH 23/25] enh: Return transaction instead of transfer --- app/Http/Controllers/API/v1/TransactionController.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/API/v1/TransactionController.php b/app/Http/Controllers/API/v1/TransactionController.php index 3f9f88d9..4d8a04c6 100644 --- a/app/Http/Controllers/API/v1/TransactionController.php +++ b/app/Http/Controllers/API/v1/TransactionController.php @@ -268,7 +268,9 @@ public function store(Request $request): JsonResponse ) { $transfer = $this->handleMyselfTransfer($data, $user, $request); //return response here to prevent controller from creating a third transaction below - return $this->success($transfer, statusCode: 201); + // Extract the "Transaction" object from the transfer. + $transaction = $transfer->incomeTransaction; + return $this->success($transaction, statusCode: 201); } From 378bab198f04a3f8ae75ac71fb226762a3f4b898 Mon Sep 17 00:00:00 2001 From: iMercy Date: Thu, 30 Apr 2026 07:56:32 -0400 Subject: [PATCH 24/25] chore: Remove config migration table --- ..._30_120453_create_configurations_table.php | 33 ------------------- 1 file changed, 33 deletions(-) delete mode 100644 database/migrations/2025_06_30_120453_create_configurations_table.php diff --git a/database/migrations/2025_06_30_120453_create_configurations_table.php b/database/migrations/2025_06_30_120453_create_configurations_table.php deleted file mode 100644 index 62d4f967..00000000 --- a/database/migrations/2025_06_30_120453_create_configurations_table.php +++ /dev/null @@ -1,33 +0,0 @@ -id(); - $table->string('key'); - $table->json('value'); - $table->string('configurable_type'); - $table->unsignedBigInteger('configurable_id'); - $table->string('type')->default('string'); - $table->timestamps(); - - $table->unique(['configurable_id', 'configurable_type', 'key']); - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::dropIfExists('configurations'); - } -}; From 4f5dddb8567aea5ba390b46b281246df3cc2a24f Mon Sep 17 00:00:00 2001 From: iMercy Date: Thu, 30 Apr 2026 08:15:48 -0400 Subject: [PATCH 25/25] chore: Reduce line characters to pass code standards test --- app/Http/Controllers/API/v1/TransactionController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Controllers/API/v1/TransactionController.php b/app/Http/Controllers/API/v1/TransactionController.php index b454a401..d56bfd80 100644 --- a/app/Http/Controllers/API/v1/TransactionController.php +++ b/app/Http/Controllers/API/v1/TransactionController.php @@ -931,7 +931,7 @@ private function validateResourceOwnership(array $data, array $categories = []): private function validateRequestData(Request $request): array { return $this->validateRequest($request, [ - 'convert_myself_to_transfer' => 'sometimes|boolean', //'sometimes' to allow for the possibility of null entries when user is not transfering to themselves + 'convert_myself_to_transfer' => 'sometimes|boolean', //'sometimes' to allow for the possibility of null entries 'client_id' => ['nullable', 'string', new ValidateClientId()], 'amount' => 'required|numeric|min:0.01', 'type' => 'required|string|in:income,expense',