Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[11.x] add native soft delete #827

Merged
merged 1 commit into from
Dec 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions database/2023_12_30_204610_soft_delete.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

declare(strict_types=1);

use Bavix\Wallet\Models\Transaction;
use Bavix\Wallet\Models\Transfer;
use Bavix\Wallet\Models\Wallet;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class() extends Migration {
public function up(): void
{
Schema::table((new Wallet())->getTable(), static function (Blueprint $table) {
$table->dropUnique(['holder_type', 'holder_id', 'slug']);

$table->softDeletesTz();

$table->unique(['holder_type', 'holder_id', 'slug', 'deleted_at']);
});
Schema::table((new Transfer())->getTable(), static function (Blueprint $table) {
$table->softDeletesTz();
});
Schema::table((new Transaction())->getTable(), static function (Blueprint $table) {
$table->softDeletesTz();
});
}

public function down(): void
{
Schema::table((new Wallet())->getTable(), static function (Blueprint $table) {
$table->dropUnique(['holder_type', 'holder_id', 'slug', 'deleted_at']);
$table->unique(['holder_type', 'holder_id', 'slug']);

$table->dropSoftDeletes();
});
Schema::table((new Transfer())->getTable(), static function (Blueprint $table) {
$table->dropSoftDeletes();
});
Schema::table((new Transaction())->getTable(), static function (Blueprint $table) {
$table->dropSoftDeletes();
});
}
};
29 changes: 29 additions & 0 deletions src/Internal/Observers/TransactionObserver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);

namespace Bavix\Wallet\Internal\Observers;

use Bavix\Wallet\Exceptions\UnconfirmedInvalid;
use Bavix\Wallet\Exceptions\WalletOwnerInvalid;
use Bavix\Wallet\Internal\Exceptions\ExceptionInterface;
use Bavix\Wallet\Internal\Exceptions\RecordNotFoundException;
use Bavix\Wallet\Internal\Exceptions\TransactionFailedException;
use Bavix\Wallet\Models\Transaction;
use Illuminate\Database\RecordsNotFoundException;

final class TransactionObserver
{
/**
* @throws UnconfirmedInvalid
* @throws WalletOwnerInvalid
* @throws RecordNotFoundException
* @throws RecordsNotFoundException
* @throws TransactionFailedException
* @throws ExceptionInterface
*/
public function deleting(Transaction $model): bool
{
return $model->wallet->resetConfirm($model);
}
}
38 changes: 38 additions & 0 deletions src/Internal/Observers/TransferObserver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

declare(strict_types=1);

namespace Bavix\Wallet\Internal\Observers;

use Bavix\Wallet\Exceptions\UnconfirmedInvalid;
use Bavix\Wallet\Exceptions\WalletOwnerInvalid;
use Bavix\Wallet\Internal\Exceptions\ExceptionInterface;
use Bavix\Wallet\Internal\Exceptions\RecordNotFoundException;
use Bavix\Wallet\Internal\Exceptions\TransactionFailedException;
use Bavix\Wallet\Models\Transfer;
use Bavix\Wallet\Services\AtomicServiceInterface;
use Illuminate\Database\RecordsNotFoundException;

final class TransferObserver
{
public function __construct(
private readonly AtomicServiceInterface $atomicService
) {
}

/**
* @throws UnconfirmedInvalid
* @throws WalletOwnerInvalid
* @throws RecordNotFoundException
* @throws RecordsNotFoundException
* @throws TransactionFailedException
* @throws ExceptionInterface
*/
public function deleting(Transfer $model): bool
{
return $this->atomicService->blocks([$model->from, $model->to], function () use ($model) {
return $model->from->resetConfirm($model->withdraw)
&& $model->to->resetConfirm($model->deposit);
});
}
}
15 changes: 15 additions & 0 deletions src/Models/Transaction.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@
namespace Bavix\Wallet\Models;

use Bavix\Wallet\Interfaces\Wallet;
use Bavix\Wallet\Internal\Observers\TransactionObserver;
use Bavix\Wallet\Internal\Service\MathServiceInterface;
use Bavix\Wallet\Models\Wallet as WalletModel;
use Bavix\Wallet\Services\CastServiceInterface;
use DateTimeInterface;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Database\Eloquent\SoftDeletes;
use function config;

/**
Expand All @@ -28,11 +31,16 @@
* @property array $meta
* @property Wallet $payable
* @property WalletModel $wallet
* @property DateTimeInterface $created_at
* @property DateTimeInterface $updated_at
* @property DateTimeInterface $deleted_at
*
* @method int getKey()
*/
class Transaction extends Model
{
use SoftDeletes;

final public const TYPE_DEPOSIT = 'deposit';

final public const TYPE_WITHDRAW = 'withdraw';
Expand Down Expand Up @@ -113,4 +121,11 @@ public function setAmountFloatAttribute(float|int|string $amount): void

$this->amount = $math->round($math->mul($amount, $decimalPlaces));
}

protected static function boot(): void
{
parent::boot();

static::observe(TransactionObserver::class);
}
}
15 changes: 15 additions & 0 deletions src/Models/Transfer.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@

namespace Bavix\Wallet\Models;

use Bavix\Wallet\Internal\Observers\TransferObserver;
use DateTimeInterface;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\SoftDeletes;
use function config;

/**
Expand All @@ -24,11 +27,16 @@
* @property string $fee
* @property Transaction $deposit
* @property Transaction $withdraw
* @property DateTimeInterface $created_at
* @property DateTimeInterface $updated_at
* @property DateTimeInterface $deleted_at
*
* @method int getKey()
*/
class Transfer extends Model
{
use SoftDeletes;

final public const STATUS_EXCHANGE = 'exchange';

final public const STATUS_TRANSFER = 'transfer';
Expand Down Expand Up @@ -103,4 +111,11 @@ public function withdraw(): BelongsTo
{
return $this->belongsTo(Transaction::class, 'withdraw_id');
}

protected static function boot(): void
{
parent::boot();

static::observe(TransferObserver::class);
}
}
3 changes: 3 additions & 0 deletions src/Models/Wallet.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
use DateTimeInterface;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\RecordsNotFoundException;
use Illuminate\Support\Str;
use function app;
Expand All @@ -43,6 +44,7 @@
* @property string $currency
* @property DateTimeInterface $created_at
* @property DateTimeInterface $updated_at
* @property DateTimeInterface $deleted_at
*
* @method int getKey()
*/
Expand All @@ -52,6 +54,7 @@ class Wallet extends Model implements Customer, WalletFloat, Confirmable, Exchan
use CanExchange;
use CanPayFloat;
use HasGift;
use SoftDeletes;

/**
* @var string[]
Expand Down
2 changes: 1 addition & 1 deletion tests/Units/Domain/BalanceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ public function testSimple(): void
self::assertSame(1000, $wallet->balanceInt);

$key = $wallet->getKey();
self::assertTrue($wallet->delete());
self::assertTrue($wallet->forceDelete());
self::assertFalse($wallet->exists);
self::assertSame($wallet->getKey(), $key);
$result = app(RegulatorServiceInterface::class)->increase($wallet, 100);
Expand Down
86 changes: 86 additions & 0 deletions tests/Units/Domain/SoftDeleteTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<?php

declare(strict_types=1);

namespace Bavix\Wallet\Test\Units\Domain;

use Bavix\Wallet\Test\Infra\Factories\BuyerFactory;
use Bavix\Wallet\Test\Infra\Models\Buyer;
use Bavix\Wallet\Test\Infra\TestCase;

/**
* @internal
*/
final class SoftDeleteTest extends TestCase
{
public function testDefaultWalletSoftDelete(): void
{
/** @var Buyer $buyer */
$buyer = BuyerFactory::new()->create();
self::assertFalse($buyer->relationLoaded('wallet'));
self::assertFalse($buyer->wallet->exists);

$buyer->deposit(1);

$oldWallet = $buyer->wallet;

self::assertTrue($buyer->wallet->exists);
self::assertTrue($buyer->wallet->delete());
self::assertNotNull($buyer->wallet->deleted_at);

/** @var Buyer $buyer */
$buyer = Buyer::query()->find($buyer->getKey());

$buyer->deposit(2);

self::assertNotSame($buyer->wallet->getKey(), $oldWallet->getKey());

self::assertSame(1, $oldWallet->balanceInt);
self::assertSame(2, $buyer->balanceInt);
}

public function testTransactionDelete(): void
{
/** @var Buyer $buyer */
$buyer = BuyerFactory::new()->create();
self::assertFalse($buyer->relationLoaded('wallet'));
self::assertFalse($buyer->wallet->exists);

$transaction = $buyer->deposit(1);

self::assertTrue($buyer->wallet->exists);
self::assertSame(1, $buyer->balanceInt);

self::assertTrue($transaction->delete());

self::assertSame(0, $buyer->balanceInt);
self::assertFalse($transaction->confirmed);
}

public function testTransferDelete(): void
{
/** @var Buyer $user1 */
/** @var Buyer $user2 */
[$user1, $user2] = BuyerFactory::times(2)->create();

self::assertFalse($user1->relationLoaded('wallet'));
self::assertFalse($user1->wallet->exists);

self::assertFalse($user2->relationLoaded('wallet'));
self::assertFalse($user2->wallet->exists);

$transfer = $user1->forceTransfer($user2, 100);

self::assertNotNull($transfer);
self::assertSame(100, $transfer->deposit->amount);
self::assertSame(-100, $transfer->withdraw->amount);

self::assertSame(-100, $user1->balanceInt);
self::assertSame(100, $user2->balanceInt);

self::assertTrue($transfer->delete());

self::assertSame(0, $user1->balanceInt);
self::assertSame(0, $user2->balanceInt);
}
}