feat: port wallet charge/withdraw modal from tauri to patient admin page
Add manual wallet withdrawal (debit) endpoint mirroring the offline app's
balance guard, and rebuild the patient کیف پول tab around a single
charge/withdraw toggle modal (quick amounts, تومان→ریال conversion,
transaction filters).
Backend:
- POST /api/v1/patient/{uuid}/wallet/withdraw — creates a debit
WalletTransaction; 422 ERR_WALLET_INSUFFICIENT when amount exceeds balance.
- ErrorCodes: ERR_WALLET_INSUFFICIENT ('موجودی کیف پول کافی نیست').
- docs/api/patient.md updated.
Frontend:
- usePatientWallet hook (balance + charge/withdraw mutations).
- WalletTransactionModal (toggle, quick amounts, UI-only payment fields).
- WalletTab: charge button, همه/واریزی/برداشت filters.
Tests: backend withdraw success/insufficient/non-positive/ownership;
frontend modal + wallet tab interactions.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -100,6 +100,61 @@ class PatientFinancialsTest extends ApiTestCase
|
||||
self::assertSame(422, $this->responseCode());
|
||||
}
|
||||
|
||||
public function testWithdrawWalletCreatesDebitAndReducesBalance(): void
|
||||
{
|
||||
[$owner, $record, $patient] = $this->recordFor();
|
||||
|
||||
$this->em->persist(new WalletTransaction($patient, 500000, 'credit', 500000));
|
||||
$this->em->flush();
|
||||
|
||||
$res = $this->authJson('POST', '/api/v1/patient/' . $record->getUuid() . '/wallet/withdraw', $owner, [
|
||||
'amount_rials' => 200000, 'description' => 'عودت وجه',
|
||||
]);
|
||||
self::assertSame(201, $this->responseCode());
|
||||
self::assertSame(300000, $res['data']['balance_rials']);
|
||||
self::assertSame('debit', $res['data']['transaction']['type']);
|
||||
self::assertSame('عودت وجه', $res['data']['transaction']['description']);
|
||||
|
||||
$wallet = $this->authJson('GET', '/api/v1/patient/' . $record->getUuid() . '/wallet', $owner);
|
||||
self::assertSame(300000, $wallet['data']['balance_rials']);
|
||||
}
|
||||
|
||||
public function testWithdrawWalletDefaultsDescription(): void
|
||||
{
|
||||
[$owner, $record, $patient] = $this->recordFor();
|
||||
|
||||
$this->em->persist(new WalletTransaction($patient, 400000, 'credit', 400000));
|
||||
$this->em->flush();
|
||||
|
||||
$res = $this->authJson('POST', '/api/v1/patient/' . $record->getUuid() . '/wallet/withdraw', $owner, [
|
||||
'amount_rials' => 400000,
|
||||
]);
|
||||
self::assertSame(201, $this->responseCode());
|
||||
self::assertSame(0, $res['data']['balance_rials']);
|
||||
self::assertSame('برداشت از کیف پول', $res['data']['transaction']['description']);
|
||||
}
|
||||
|
||||
public function testWithdrawWalletRejectsAmountAboveBalance(): void
|
||||
{
|
||||
[$owner, $record, $patient] = $this->recordFor();
|
||||
|
||||
$this->em->persist(new WalletTransaction($patient, 100000, 'credit', 100000));
|
||||
$this->em->flush();
|
||||
|
||||
$res = $this->authJson('POST', '/api/v1/patient/' . $record->getUuid() . '/wallet/withdraw', $owner, [
|
||||
'amount_rials' => 150000,
|
||||
]);
|
||||
self::assertSame(422, $this->responseCode());
|
||||
self::assertSame('ERR_WALLET_INSUFFICIENT', $res['errors'][0]['code']);
|
||||
}
|
||||
|
||||
public function testWithdrawWalletRejectsNonPositiveAmount(): void
|
||||
{
|
||||
[$owner, $record] = $this->recordFor();
|
||||
$this->authJson('POST', '/api/v1/patient/' . $record->getUuid() . '/wallet/withdraw', $owner, ['amount_rials' => 0]);
|
||||
self::assertSame(422, $this->responseCode());
|
||||
}
|
||||
|
||||
public function testFinancialsAreOwnershipScoped(): void
|
||||
{
|
||||
[, $record] = $this->recordFor();
|
||||
@@ -110,6 +165,8 @@ class PatientFinancialsTest extends ApiTestCase
|
||||
self::assertSame(404, $this->responseCode());
|
||||
$this->authJson('POST', '/api/v1/patient/' . $record->getUuid() . '/wallet/charge', $other, ['amount_rials' => 1000]);
|
||||
self::assertSame(404, $this->responseCode());
|
||||
$this->authJson('POST', '/api/v1/patient/' . $record->getUuid() . '/wallet/withdraw', $other, ['amount_rials' => 1000]);
|
||||
self::assertSame(404, $this->responseCode());
|
||||
$this->authJson('GET', '/api/v1/patient/' . $record->getUuid() . '/wallet', $other);
|
||||
self::assertSame(404, $this->responseCode());
|
||||
$this->authJson('GET', '/api/v1/patient/' . $record->getUuid() . '/wallet/transactions', $other);
|
||||
|
||||
Reference in New Issue
Block a user