diff --git a/database/2023_12_30_204610_soft_delete.php b/database/2023_12_30_204610_soft_delete.php new file mode 100644 index 000000000..552794894 --- /dev/null +++ b/database/2023_12_30_204610_soft_delete.php @@ -0,0 +1,45 @@ +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(); + }); + } +}; diff --git a/src/Internal/Observers/TransactionObserver.php b/src/Internal/Observers/TransactionObserver.php new file mode 100644 index 000000000..56ca2bf7b --- /dev/null +++ b/src/Internal/Observers/TransactionObserver.php @@ -0,0 +1,29 @@ +wallet->resetConfirm($model); + } +} diff --git a/src/Internal/Observers/TransferObserver.php b/src/Internal/Observers/TransferObserver.php new file mode 100644 index 000000000..ec1bdee78 --- /dev/null +++ b/src/Internal/Observers/TransferObserver.php @@ -0,0 +1,38 @@ +atomicService->blocks([$model->from, $model->to], function () use ($model) { + return $model->from->resetConfirm($model->withdraw) + && $model->to->resetConfirm($model->deposit); + }); + } +} diff --git a/src/Models/Transaction.php b/src/Models/Transaction.php index 5672da404..f5b77e99b 100644 --- a/src/Models/Transaction.php +++ b/src/Models/Transaction.php @@ -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; /** @@ -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'; @@ -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); + } } diff --git a/src/Models/Transfer.php b/src/Models/Transfer.php index 8b98247d8..341628837 100644 --- a/src/Models/Transfer.php +++ b/src/Models/Transfer.php @@ -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; /** @@ -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'; @@ -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); + } } diff --git a/src/Models/Wallet.php b/src/Models/Wallet.php index 77c76f739..7cb08667b 100644 --- a/src/Models/Wallet.php +++ b/src/Models/Wallet.php @@ -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; @@ -43,6 +44,7 @@ * @property string $currency * @property DateTimeInterface $created_at * @property DateTimeInterface $updated_at + * @property DateTimeInterface $deleted_at * * @method int getKey() */ @@ -52,6 +54,7 @@ class Wallet extends Model implements Customer, WalletFloat, Confirmable, Exchan use CanExchange; use CanPayFloat; use HasGift; + use SoftDeletes; /** * @var string[] diff --git a/tests/Units/Domain/BalanceTest.php b/tests/Units/Domain/BalanceTest.php index 2c8ba7be8..294f92cd4 100644 --- a/tests/Units/Domain/BalanceTest.php +++ b/tests/Units/Domain/BalanceTest.php @@ -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); diff --git a/tests/Units/Domain/SoftDeleteTest.php b/tests/Units/Domain/SoftDeleteTest.php new file mode 100644 index 000000000..414a1b406 --- /dev/null +++ b/tests/Units/Domain/SoftDeleteTest.php @@ -0,0 +1,86 @@ +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); + } +}