You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

226 lines
7.9 KiB

2 weeks ago
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Activity;
use App\Models\DictItem;
use App\Models\DictType;
use App\Support\ActivitySigninCodeSync;
use App\Support\ApiResponse;
use App\Support\ScheduleProgressStatus;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
class ActivityController extends Controller
{
use ApiResponse;
public function index(Request $request): JsonResponse
{
$query = Activity::query()
->with(['activityTypeItem'])
->withCount(['sessions', 'signups']);
if ($kw = $request->query('keyword')) {
$query->where(function ($q) use ($kw) {
$q->where('title', 'like', "%{$kw}%")
->orWhere('location', 'like', "%{$kw}%");
});
}
if ($request->filled('activity_type_dict_item_id')) {
$query->where('activity_type_dict_item_id', (int) $request->query('activity_type_dict_item_id'));
}
if ($request->filled('progress_status')) {
$query->where('progress_status', (int) $request->query('progress_status'));
}
if ($request->filled('published')) {
$query->where('published', (int) $request->query('published'));
}
$paginator = $query
->orderByDesc('id')
->paginate((int) $request->query('page_size', 20))
->withQueryString();
$paginator->getCollection()->transform(fn (Activity $a) => $this->serializeList($a));
return $this->paginated($paginator);
}
public function store(Request $request): JsonResponse
{
if (! DictType::query()->where('code', 'activity_type')->where('status', 1)->exists()) {
return $this->fail('字典「活动类型」未配置,请在后台维护数据字典或执行 ActivityDictionarySeeder', 422);
}
$data = $this->validatedActivity($request);
$data['progress_status'] = ScheduleProgressStatus::resolve(
$data['event_start_date'] ?? null,
$data['event_end_date'] ?? null,
$data['signup_start_date'] ?? null,
$data['signup_end_date'] ?? null,
);
$activity = Activity::query()->create($data);
ActivitySigninCodeSync::ensureActivityCode($activity->fresh());
return $this->ok(['id' => $activity->id], '已创建');
}
public function show(int $activity): JsonResponse
{
$model = Activity::query()
->with(['activityTypeItem'])
->withCount(['sessions', 'signups'])
->findOrFail($activity);
return $this->ok($this->serializeDetail($model));
}
public function update(Request $request, int $activity): JsonResponse
{
$model = Activity::query()->findOrFail($activity);
$data = $this->validatedActivity($request, partial: true);
$model->fill($data);
$model->progress_status = ScheduleProgressStatus::resolve(
$model->event_start_date?->toDateString(),
$model->event_end_date?->toDateString(),
$model->signup_start_date?->toDateString(),
$model->signup_end_date?->toDateString(),
);
$model->save();
ActivitySigninCodeSync::ensureActivityCode($model);
return $this->ok(null, '已保存');
}
public function updateShelf(Request $request, int $activity): JsonResponse
{
$data = $request->validate([
'published' => ['required', 'integer', 'in:0,1'],
]);
$model = Activity::query()->findOrFail($activity);
$model->published = (int) $data['published'];
$model->save();
return $this->ok(null, '已更新发布状态');
}
public function destroy(int $activity): JsonResponse
{
Activity::query()->findOrFail($activity)->delete();
return $this->ok(null, '已删除');
}
/**
* @return array<string, mixed>
*/
protected function validatedActivity(Request $request, bool $partial = false): array
{
$activityTypeId = DictType::query()->where('code', 'activity_type')->where('status', 1)->value('id');
return $request->validate([
'title' => [$partial ? 'sometimes' : 'required', 'string', 'max:255'],
'activity_type_dict_item_id' => $this->dictItemRules($activityTypeId, $partial ? 'sometimes' : 'required'),
'quota' => ['nullable', 'integer', 'min:0'],
'event_start_date' => [$partial ? 'sometimes' : 'required', 'date'],
'event_end_date' => $partial
? ['sometimes', 'date']
: ['required', 'date', 'after_or_equal:event_start_date'],
'signup_start_date' => [$partial ? 'sometimes' : 'required', 'date'],
'signup_end_date' => $partial
? ['sometimes', 'date']
: ['required', 'date', 'after_or_equal:signup_start_date'],
'location' => ['nullable', 'string', 'max:255'],
'intro_html' => ['nullable', 'string'],
'published' => ['nullable', 'integer', 'in:0,1'],
'remark' => ['nullable', 'string'],
'sort' => ['nullable', 'integer'],
]);
}
/**
* @return array<int, \Illuminate\Contracts\Validation\Rule|string>
*/
protected function dictItemRules(?int $dictTypeId, string $presence): array
{
if (! $dictTypeId) {
return match ($presence) {
'required' => ['required', 'integer'],
'sometimes' => ['sometimes', 'nullable', 'integer'],
default => ['nullable', 'integer'],
};
}
$exists = Rule::exists('dict_items', 'id')->where(
fn ($q) => $q->where('dict_type_id', $dictTypeId)->where('status', 1)
);
return match ($presence) {
'required' => ['required', 'integer', $exists],
'sometimes' => ['sometimes', 'nullable', 'integer', $exists],
default => ['nullable', 'integer', $exists],
};
}
/**
* @return array<string, mixed>
*/
protected function serializeList(Activity $a): array
{
return [
'id' => $a->id,
'signin_code' => ActivitySigninCodeSync::activitySigninCode($a),
'title' => $a->title,
'activity_type_dict_item_id' => $a->activity_type_dict_item_id,
'activity_type_item' => $this->serializeDictItem($a->activityTypeItem),
'quota' => (int) $a->quota,
'event_start_date' => $a->event_start_date?->toDateString(),
'event_end_date' => $a->event_end_date?->toDateString(),
'signup_start_date' => $a->signup_start_date?->toDateString(),
'signup_end_date' => $a->signup_end_date?->toDateString(),
'location' => $a->location,
'progress_status' => ScheduleProgressStatus::resolve(
$a->event_start_date?->toDateString(),
$a->event_end_date?->toDateString(),
$a->signup_start_date?->toDateString(),
$a->signup_end_date?->toDateString(),
),
'published' => (int) $a->published,
'sessions_count' => (int) ($a->sessions_count ?? 0),
'signups_count' => (int) ($a->signups_count ?? 0),
'created_at' => $a->created_at?->toIso8601String(),
];
}
/**
* @return array<string, mixed>
*/
protected function serializeDetail(Activity $a): array
{
$row = $this->serializeList($a);
$row['intro_html'] = $a->intro_html;
$row['remark'] = $a->remark;
return $row;
}
/**
* @return array{id:int,label:string,value:string}|null
*/
protected function serializeDictItem(?DictItem $item): ?array
{
if (! $item) {
return null;
}
return [
'id' => $item->id,
'label' => $item->label,
'value' => $item->value,
];
}
}