Skip to content

feat: support for myself party type#185

Open
iMercyvlogs wants to merge 28 commits into
devfrom
feat/party-myself
Open

feat: support for myself party type#185
iMercyvlogs wants to merge 28 commits into
devfrom
feat/party-myself

Conversation

@iMercyvlogs

Copy link
Copy Markdown
Collaborator

added support for party type myself.... Transactions "From" myself (Income) are handled as transfers if configured so. Target issue: #169

@iMercyvlogs iMercyvlogs requested review from kofimokome and nfebe and removed request for kofimokome March 12, 2026 12:02
@sourceant

sourceant Bot commented Mar 12, 2026

Copy link
Copy Markdown

Code Review Summary

This PR introduces support for 'myself' party types, allowing transactions involving the user themselves to be automatically converted into transfers if configured. This involves changes to the Transaction controller, Party model, and User model configuration.

🚀 Key Improvements

  • Encapsulated validation logic into validateRequestData helper.
  • Added a TransferService dependency to handle the conversion logic.
  • Included a comprehensive feature test MyselfTransferTest.php.

💡 Minor Suggestions

  • Remove excessive whitespace in TransactionController and ConfigurationSeeder.
  • Use $data instead of $request inside the helper methods for consistency.

@sourceant sourceant Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review complete. See the overview comment for a summary.

private RecurringTransactionService $recurringTransactionService
private RecurringTransactionService $recurringTransactionService,
private TransferService $transferService
) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Retrieving the Party model directly in the controller and performing business logic here violates the Single Responsibility Principle. This logic should be encapsulated within the TransactionService or a specific HandleMyselfPartyAction. Additionally, using \App\Models\Party::find ignores the user scope; it should be scoped to the authenticated user for security.

Suggested change
) {
$partyId = $data['party_id'] ?? null;
if (config('app.convert_myself_to_transfer') && $partyId) {
$party = $user->parties()->find($partyId);
if ($party?->is_myself && $request->has('from_wallet_id')) {
$transfer = $this->transferService->transfer(
amountToSend: (float) $data['amount'],
fromWallet: $user->wallets()->findOrFail($request['from_wallet_id']),
amountToReceive: (float) $data['amount'],
toWallet: $user->wallets()->findOrFail($data['wallet_id']),
user: $user,
exchangeRate: 1.0,
datetime: $data['datetime'] ?? null,
transactionClientIds: ['income_transaction_client_id' => $data['client_id'] ?? null]
);
return $this->success($transfer, statusCode: 201);
}
}

@sourceant sourceant Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review complete. See the overview comment for a summary.

Comment thread app/Http/Controllers/API/v1/TransactionController.php Outdated
Comment thread database/migrations/2024_07_20_125104_create_parties_table.php Outdated
Comment thread tests/TestCase.php Outdated

@nfebe nfebe left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank for this! I took a quick look and have one comment for now :)

Comment thread database/migrations/2024_07_20_125104_create_parties_table.php Outdated

@sourceant sourceant Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review complete. See the overview comment for a summary.

Comment thread app/Http/Controllers/API/v1/TransactionController.php Outdated
Comment thread app/Http/Controllers/API/v1/TransactionController.php
@iMercyvlogs iMercyvlogs requested a review from kofimokome March 13, 2026 04:43

@sourceant sourceant Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review complete. See the overview comment for a summary.

Comment thread app/Http/Controllers/API/v1/TransactionController.php Outdated

}

private function isMyselfTransfer($data, $user, $request): bool

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method signature in the implementation includes $request, but the call site on line 263 only passes two arguments. This will cause a TypeError if line 862 is reached.

Suggested change
private function isMyselfTransfer($data, $user, $request): bool
private function isMyselfTransfer($data, $user): bool

Comment thread app/Http/Controllers/API/v1/TransactionController.php Outdated

@sourceant sourceant Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review complete. See the overview comment for a summary.

Comment thread app/Http/Controllers/API/v1/TransactionController.php Outdated

@kofimokome kofimokome left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your tests are failing

@nfebe nfebe left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • We use whilesmart/eloquent-model-configs read the docs on and understand how to better set the configs

  • Inline I recommend the names of configs to set. is-myself set on party and create-transfers-for-myself-transactions. Its a boolean and is true by default. Which means if a user doesn't turn this off setting applies.

  • Many of the changes in phpunit, testcase.php, seem unrelated.

  • composer lock should not change

'files.*' => 'file|mimes:jpg,jpeg,png,pdf|max:1240',

'from_wallet_id' => 'required_if:convert_myself_to_transfer,true|integer|exists:wallets,id',
]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
]);

Create a request instead and there should likely be a dedicated commit for that

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry, I didn't quite understand this

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You haven't updated the codes to use Laravel Requests

Comment thread tests/Feature/MyselfTransferTest.php Outdated
Comment thread tests/Feature/MyselfTransferTest.php Outdated
Comment thread config/model-configuration.php Outdated
Comment thread config/app.php Outdated
Comment thread tests/TestCase.php Outdated
Comment thread phpunit.xml Outdated

@sourceant sourceant Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review complete. No specific code suggestions were generated. See the overview comment for a summary.

@sourceant sourceant Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review complete. See the overview comment for a summary.

]);
}

private function isMyselfTransfer($data, $user, $request): bool

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type hinting the parameters improves code clarity and helps static analysis tools catch errors.

Suggested change
private function isMyselfTransfer($data, $user, $request): bool
private function isMyselfTransfer(array $data, \App\Models\User $user, Request $request): bool

Comment thread phpunit.xml Outdated

@sourceant sourceant Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review complete. See the overview comment for a summary.

private RecurringTransactionService $recurringTransactionService,
private TransferService $transferService
) {
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method performs a raw query on �App\Models\Configuration within the controller. This logic is repeated and should be moved to the User model or a ConfigurationService to improve maintainability and allow for caching.

Suggested change
}
private function isMyselfTransfer(array $data, User $user, Request $request): bool
{
$myselfPartyId = $user->getMyselfPartyId();
$isMyself = isset($data['party_id']) && (string)$data['party_id'] === (string)$myselfPartyId;
return config('app.convert_myself_to_transfer') &&
$isMyself &&
$request->has('from_wallet_id');
}

Comment thread phpunit.xml Outdated

@sourceant sourceant Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review complete. See the overview comment for a summary.

$data = $validationResult['data'];
$user = $request->user();

//check if party_id is the user's "myself" party

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is significant whitespace and some redundant logic flow here. The logic for 'myself' party conversion should ideally be handled inside the service layer or a dedicated action to keep the controller lean.

Suggested change
//check if party_id is the user's "myself" party
if ($this->isMyselfTransfer($data, $user, $request)) {
$transfer = $this->handleMyselfTransfer($data, $user, $request);
return $this->success($transfer, statusCode: 201);
}

@sourceant sourceant Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review complete. See the overview comment for a summary.

Comment thread app/Http/Controllers/API/v1/TransactionController.php Outdated
Comment thread database/seeders/ConfigurationSeeder.php Outdated

@sourceant sourceant Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review complete. See the overview comment for a summary.

private RecurringTransactionService $recurringTransactionService,
private TransferService $transferService
) {
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The isMyselfTransfer logic is still performing an extra database query via $user->parties()->find($partyId) for every transaction storage request. Since party_id is already validated to exist in the request, this could be optimized or cached. Additionally, the logic for determining if a party is 'myself' should reside on the Party model itself.

Suggested change
}
private function isMyselfTransfer(array $data, User $user, Request $request): bool
{
if (!$user->getConfigValue('create-transfers-for-myself-transactions') || !$request->has('from_wallet_id')) {
return false;
}
$partyId = $data['party_id'] ?? null;
if (!$partyId) {
return false;
}
$party = $user->parties()->find($partyId);
return $party && $party->getConfigValue('is-myself');
}

@sourceant sourceant Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review complete. See the overview comment for a summary.

'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',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The validation rule required_if:convert_myself_to_transfer,true uses a hardcoded string key that isn't part of the request payload. It should likely check the user's config or handle this requirement logically, as convert_myself_to_transfer isn't a field in the request.

Suggested change
'from_wallet_id' => 'required_if:convert_myself_to_transfer,true|integer|exists:wallets,id',
'from_wallet_id' => 'nullable|integer|exists:wallets,id',

Comment thread app/Http/Controllers/API/v1/TransactionController.php Outdated

@sourceant sourceant Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review complete. No specific code suggestions were generated. See the overview comment for a summary.

@iMercyvlogs iMercyvlogs requested a review from kofimokome April 28, 2026 20:22
Comment thread app/Http/Controllers/API/v1/TransactionController.php
'files.*' => 'file|mimes:jpg,jpeg,png,pdf|max:1240',

'from_wallet_id' => 'required_if:convert_myself_to_transfer,true|integer|exists:wallets,id',
]);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread app/Http/Controllers/API/v1/TransactionController.php Outdated
Comment thread app/Http/Controllers/API/v1/TransactionController.php

@nfebe nfebe left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to publish the configurations table and use FormRequest like Kofi mentioned.

Comment thread database/migrations/2025_06_30_120453_create_configurations_table.php Outdated

@sourceant sourceant Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review complete. See the overview comment for a summary.


$isMyselfParty = $party && $party->getConfigValue('is-myself');

return $featureEnabled && $isMyselfParty && $request->has('from_wallet_id');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The check $request->has('from_wallet_id') is redundant because the validation rules (line 851) already enforce required_if:convert_myself_to_transfer,true. However, if convert_myself_to_transfer is false but the key exists, this logic might still trigger unexpectedly if the other conditions meet. It's safer to rely strictly on the validated data.

Suggested change
return $featureEnabled && $isMyselfParty && $request->has('from_wallet_id');
return $featureEnabled && $isMyselfParty && isset($data['from_wallet_id']);

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) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Iterating through all users and executing a query for each in a seeder is inefficient for large datasets. Consider using a bulk insert or a more performant update strategy.

Suggested change
foreach (\App\Models\User::all() as $user) {
\App\Models\User::all()->each(function ($user) {
$user->setConfigValue('create-transfers-for-myself-transactions', true, \Whilesmart\ModelConfiguration\Enums\ConfigValueType::Boolean);
});

@sourceant sourceant Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review complete. No specific code suggestions were generated. See the overview comment for a summary.

@github-actions

github-actions Bot commented May 4, 2026

Copy link
Copy Markdown

Coverage Report
PR coverage: 71.88%
Baseline: 71.71%
Change: ✅+0.2%

@iMercyvlogs iMercyvlogs requested review from kofimokome and nfebe May 4, 2026 07:01
'files.*' => 'file|mimes:jpg,jpeg,png,pdf|max:1240',

'from_wallet_id' => 'required_if:convert_myself_to_transfer,true|integer|exists:wallets,id',
]);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You haven't updated the codes to use Laravel Requests

@iMercyvlogs

Copy link
Copy Markdown
Collaborator Author

@kofimokome Notice that in the code, prior to being assigned the task of supporting myself party type the validation was done inlline through a custom validateRequest() method inherited from ApiController. So I simply moved the existing logic into a new helper method(validateRequestData()) with aim of reducing the store() method's complexity. Are you now saying I should change the validation strategy to rather use Laravel's built-in form request classes? In such a case, wouldn't I need to change all instances within the code base where validation was done?

@iMercyvlogs iMercyvlogs requested a review from kofimokome May 10, 2026 22:13
@kofimokome

kofimokome commented May 11, 2026

Copy link
Copy Markdown
Collaborator

I strongly disagree with your last point. You would still need to use a form request.

You can decide to do it in another PR.

@nfebe nfebe left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@iMercyvlogs Use FormRequest that is the correct way to do it, if not there is not need to refactor this into a function.

Plus using the FormRequest will increase pattern usage of forms which is a good thing as opposed to writing class-based validators like this.

@iMercyvlogs

Copy link
Copy Markdown
Collaborator Author

@kofimokome @nfebe #232

@nfebe nfebe left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is_myself is not limited to transfers. That is already clear for transfers as users cannot transfer to anything else except a wallet they own.

So the is-myself config on parties make sense but it has/should have wider applications beyond transfers, it could also have a user specific config to enable the behavior.

Lets keep this open until I have time to look deeper and also research what will be best for UX.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants