Skip to content

Commit

Permalink
Merge pull request #827 from bavix/11.x-soft-delete
Browse files Browse the repository at this point in the history
[11.x] add native soft delete
  • Loading branch information
rez1dent3 authored Dec 30, 2023
2 parents 83e3de5 + e7d64c9 commit bf9148b
Show file tree
Hide file tree
Showing 8 changed files with 232 additions and 1 deletion.
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;

Check warning on line 8 in database/2023_12_30_204610_soft_delete.php

View workflow job for this annotation

GitHub Actions / Qodana for PHP

Undefined class

Undefined class 'Migration'

Check warning on line 8 in database/2023_12_30_204610_soft_delete.php

View workflow job for this annotation

GitHub Actions / Qodana for PHP

Undefined namespace

Undefined namespace 'Migrations'
use Illuminate\Database\Schema\Blueprint;

Check warning on line 9 in database/2023_12_30_204610_soft_delete.php

View workflow job for this annotation

GitHub Actions / Qodana for PHP

Undefined class

Undefined class 'Blueprint'

Check warning on line 9 in database/2023_12_30_204610_soft_delete.php

View workflow job for this annotation

GitHub Actions / Qodana for PHP

Undefined namespace

Undefined namespace 'Schema'
use Illuminate\Support\Facades\Schema;

Check warning on line 10 in database/2023_12_30_204610_soft_delete.php

View workflow job for this annotation

GitHub Actions / Qodana for PHP

Undefined class

Undefined class 'Schema'

Check warning on line 10 in database/2023_12_30_204610_soft_delete.php

View workflow job for this annotation

GitHub Actions / Qodana for PHP

Undefined namespace

Undefined namespace 'Facades'

return new class() extends Migration {

Check warning on line 12 in database/2023_12_30_204610_soft_delete.php

View workflow job for this annotation

GitHub Actions / Qodana for PHP

Undefined class

Undefined class 'Migration'
public function up(): void
{
Schema::table((new Wallet())->getTable(), static function (Blueprint $table) {

Check warning on line 15 in database/2023_12_30_204610_soft_delete.php

View workflow job for this annotation

GitHub Actions / Qodana for PHP

Undefined class

Undefined class 'Blueprint'

Check warning on line 15 in database/2023_12_30_204610_soft_delete.php

View workflow job for this annotation

GitHub Actions / Qodana for PHP

Undefined class

Undefined class 'Schema'
$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) {

Check warning on line 22 in database/2023_12_30_204610_soft_delete.php

View workflow job for this annotation

GitHub Actions / Qodana for PHP

Undefined class

Undefined class 'Blueprint'

Check warning on line 22 in database/2023_12_30_204610_soft_delete.php

View workflow job for this annotation

GitHub Actions / Qodana for PHP

Undefined class

Undefined class 'Schema'
$table->softDeletesTz();
});
Schema::table((new Transaction())->getTable(), static function (Blueprint $table) {

Check warning on line 25 in database/2023_12_30_204610_soft_delete.php

View workflow job for this annotation

GitHub Actions / Qodana for PHP

Undefined class

Undefined class 'Blueprint'

Check warning on line 25 in database/2023_12_30_204610_soft_delete.php

View workflow job for this annotation

GitHub Actions / Qodana for PHP

Undefined class

Undefined class 'Schema'
$table->softDeletesTz();
});
}

public function down(): void
{
Schema::table((new Wallet())->getTable(), static function (Blueprint $table) {

Check warning on line 32 in database/2023_12_30_204610_soft_delete.php

View workflow job for this annotation

GitHub Actions / Qodana for PHP

Undefined class

Undefined class 'Blueprint'

Check warning on line 32 in database/2023_12_30_204610_soft_delete.php

View workflow job for this annotation

GitHub Actions / Qodana for PHP

Undefined class

Undefined class 'Schema'
$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) {

Check warning on line 38 in database/2023_12_30_204610_soft_delete.php

View workflow job for this annotation

GitHub Actions / Qodana for PHP

Undefined class

Undefined class 'Blueprint'

Check warning on line 38 in database/2023_12_30_204610_soft_delete.php

View workflow job for this annotation

GitHub Actions / Qodana for PHP

Undefined class

Undefined class 'Schema'
$table->dropSoftDeletes();
});
Schema::table((new Transaction())->getTable(), static function (Blueprint $table) {

Check warning on line 41 in database/2023_12_30_204610_soft_delete.php

View workflow job for this annotation

GitHub Actions / Qodana for PHP

Undefined class

Undefined class 'Schema'

Check warning on line 41 in database/2023_12_30_204610_soft_delete.php

View workflow job for this annotation

GitHub Actions / Qodana for PHP

Undefined class

Undefined class 'Blueprint'
$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;

Check warning on line 13 in src/Internal/Observers/TransactionObserver.php

View workflow job for this annotation

GitHub Actions / Qodana for PHP

Undefined class

Undefined class 'RecordsNotFoundException'

Check warning on line 13 in src/Internal/Observers/TransactionObserver.php

View workflow job for this annotation

GitHub Actions / Qodana for PHP

Undefined namespace

Undefined namespace 'Database'

final class TransactionObserver
{
/**
* @throws UnconfirmedInvalid
* @throws WalletOwnerInvalid
* @throws RecordNotFoundException
* @throws RecordsNotFoundException

Check warning on line 21 in src/Internal/Observers/TransactionObserver.php

View workflow job for this annotation

GitHub Actions / Qodana for PHP

Undefined class

Undefined class '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;

Check warning on line 14 in src/Internal/Observers/TransferObserver.php

View workflow job for this annotation

GitHub Actions / Qodana for PHP

Undefined class

Undefined class 'RecordsNotFoundException'

Check warning on line 14 in src/Internal/Observers/TransferObserver.php

View workflow job for this annotation

GitHub Actions / Qodana for PHP

Undefined namespace

Undefined namespace 'Database'

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

/**
* @throws UnconfirmedInvalid
* @throws WalletOwnerInvalid
* @throws RecordNotFoundException
* @throws RecordsNotFoundException

Check warning on line 27 in src/Internal/Observers/TransferObserver.php

View workflow job for this annotation

GitHub Actions / Qodana for PHP

Undefined class

Undefined class '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;

Check warning on line 13 in src/Models/Transaction.php

View workflow job for this annotation

GitHub Actions / Qodana for PHP

Undefined namespace

Undefined namespace 'Eloquent'
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Database\Eloquent\SoftDeletes;

Check warning on line 16 in src/Models/Transaction.php

View workflow job for this annotation

GitHub Actions / Qodana for PHP

Undefined class

Undefined class '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;

Check warning on line 42 in src/Models/Transaction.php

View workflow job for this annotation

GitHub Actions / Qodana for PHP

Undefined class

Undefined class '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);

Check warning on line 129 in src/Models/Transaction.php

View workflow job for this annotation

GitHub Actions / Qodana for PHP

Undefined method

Method 'observe' is undefined
}
}
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;

Check warning on line 11 in src/Models/Transfer.php

View workflow job for this annotation

GitHub Actions / Qodana for PHP

Undefined class

Undefined class 'SoftDeletes'

Check warning on line 11 in src/Models/Transfer.php

View workflow job for this annotation

GitHub Actions / Qodana for PHP

Undefined namespace

Undefined namespace 'Eloquent'
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;

Check warning on line 38 in src/Models/Transfer.php

View workflow job for this annotation

GitHub Actions / Qodana for PHP

Undefined class

Undefined class '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);

Check warning on line 119 in src/Models/Transfer.php

View workflow job for this annotation

GitHub Actions / Qodana for PHP

Undefined method

Method 'observe' is undefined
}
}
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;

Check warning on line 22 in src/Models/Wallet.php

View workflow job for this annotation

GitHub Actions / Qodana for PHP

Undefined namespace

Undefined namespace 'Eloquent'
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Database\Eloquent\SoftDeletes;

Check warning on line 24 in src/Models/Wallet.php

View workflow job for this annotation

GitHub Actions / Qodana for PHP

Undefined class

Undefined class '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;

Check warning on line 57 in src/Models/Wallet.php

View workflow job for this annotation

GitHub Actions / Qodana for PHP

Undefined class

Undefined class '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);
}
}

0 comments on commit bf9148b

Please sign in to comment.