diff --git a/app/Http/Controllers/ChannelEntryController.php b/app/Http/Controllers/ChannelEntryController.php index eb88ec3..478f57b 100644 --- a/app/Http/Controllers/ChannelEntryController.php +++ b/app/Http/Controllers/ChannelEntryController.php @@ -68,13 +68,7 @@ class ChannelEntryController extends Controller } $user->fill($userPayload)->save(); - $application = Application::query()->firstOrCreate( - [ - 'user_id' => $user->id, - 'competition_id' => $competition->id, - ], - ['status' => 'draft'] - ); + $application = Application::firstOrCreateDraft($user->id, $competition->id); $application->fill([ 'signup_channel_id' => $channel->id, 'signup_channel_code' => $channel->channel_code, diff --git a/app/Models/Application.php b/app/Models/Application.php index 40b76f4..4dfa2ed 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -3,6 +3,7 @@ namespace App\Models; use App\Support\ProjectCode; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; @@ -11,6 +12,8 @@ use Illuminate\Validation\ValidationException; class Application extends Model { + public const EXCLUDE_SOFT_DELETED_SCOPE = 'excludeSoftDeleted'; + protected $fillable = [ 'project_code', 'user_id', @@ -44,10 +47,24 @@ class Application extends Model 'promise_signed_at' => 'datetime', 'submitted_at' => 'datetime', 'signup_channel_entered_at' => 'datetime', + 'deleted_at' => 'datetime', ]; + public static function hasDeletedAtColumn(): bool + { + return Schema::hasColumn((new static)->getTable(), 'deleted_at'); + } + protected static function booted(): void { + static::addGlobalScope(self::EXCLUDE_SOFT_DELETED_SCOPE, function (Builder $builder): void { + if (! static::hasDeletedAtColumn()) { + return; + } + + $builder->whereNull($builder->getModel()->qualifyColumn('deleted_at')); + }); + static::created(function (Application $application): void { if (! Schema::hasColumn($application->getTable(), 'project_code')) { return; @@ -63,6 +80,23 @@ class Application extends Model public static function firstOrCreateDraft(int $userId, int $competitionId): self { + if (static::hasDeletedAtColumn()) { + $trashed = static::withoutGlobalScope(self::EXCLUDE_SOFT_DELETED_SCOPE) + ->where('user_id', $userId) + ->where('competition_id', $competitionId) + ->whereNotNull('deleted_at') + ->first(); + + if ($trashed !== null) { + $trashed->forceFill([ + 'deleted_at' => null, + 'status' => 'draft', + ])->save(); + + return $trashed->fresh(); + } + } + return static::query()->firstOrCreate( [ 'user_id' => $userId, diff --git a/tests/Feature/AdminApplicationListTest.php b/tests/Feature/AdminApplicationListTest.php new file mode 100644 index 0000000..827bf8a --- /dev/null +++ b/tests/Feature/AdminApplicationListTest.php @@ -0,0 +1,55 @@ +create([ + 'username' => 'admin_list', + 'password_hash' => 'unused', + 'name' => '管理员', + 'status' => 'active', + ])); + + $competition = Competition::query()->create([ + 'slug' => 'xxf', + 'name' => '测试赛', + 'published' => true, + ]); + + $active = Application::query()->create([ + 'user_id' => User::query()->create(['mobile' => '13800138001'])->id, + 'competition_id' => $competition->id, + 'status' => 'submitted', + 'project_name' => '有效项目', + ]); + + $deleted = Application::query()->create([ + 'user_id' => User::query()->create(['mobile' => '13800138002'])->id, + 'competition_id' => $competition->id, + 'status' => 'submitted', + 'project_name' => '已删除项目', + ]); + $deleted->forceFill(['deleted_at' => now()])->save(); + + $response = $this->getJson("/api/v1/admin/competitions/{$competition->id}/applications?status=submitted") + ->assertOk(); + + $ids = collect($response->json('data'))->pluck('id')->all(); + + $this->assertContains($active->id, $ids); + $this->assertNotContains($deleted->id, $ids); + } +}