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.
279 lines
9.6 KiB
279 lines
9.6 KiB
|
2 weeks ago
|
<?php
|
||
|
|
|
||
|
|
namespace App\Http\Controllers\Admin;
|
||
|
|
|
||
|
|
use App\Http\Controllers\Controller;
|
||
|
|
use App\Models\Demand;
|
||
|
|
use App\Models\DemandHandleLog;
|
||
|
|
use App\Models\DictItem;
|
||
|
|
use App\Models\DictType;
|
||
|
|
use App\Models\Teacher;
|
||
|
|
use App\Support\ApiResponse;
|
||
|
|
use Illuminate\Http\JsonResponse;
|
||
|
|
use Illuminate\Http\Request;
|
||
|
|
use Illuminate\Validation\Rule;
|
||
|
|
|
||
|
|
class DemandController extends Controller
|
||
|
|
{
|
||
|
|
use ApiResponse;
|
||
|
|
|
||
|
|
public function index(Request $request): JsonResponse
|
||
|
|
{
|
||
|
|
$query = Demand::query()
|
||
|
|
->with(['teacher.university', 'typeItem', 'statusItem'])
|
||
|
|
->withCount('handleLogs');
|
||
|
|
|
||
|
|
if ($kw = $request->query('keyword')) {
|
||
|
|
$query->where(function ($q) use ($kw) {
|
||
|
|
$q->where('title', 'like', "%{$kw}%")
|
||
|
|
->orWhere('contact_name', 'like', "%{$kw}%")
|
||
|
|
->orWhere('company', 'like', "%{$kw}%")
|
||
|
|
->orWhere('content', 'like', "%{$kw}%")
|
||
|
|
->orWhereHas('typeItem', fn ($tq) => $tq->where('label', 'like', "%{$kw}%"));
|
||
|
|
});
|
||
|
|
}
|
||
|
|
if ($request->filled('teacher_id')) {
|
||
|
|
$query->where('teacher_id', (int) $request->query('teacher_id'));
|
||
|
|
}
|
||
|
|
if ($request->filled('type_dict_item_id')) {
|
||
|
|
$query->where('type_dict_item_id', (int) $request->query('type_dict_item_id'));
|
||
|
|
}
|
||
|
|
if ($request->filled('status_dict_item_id')) {
|
||
|
|
$query->where('status_dict_item_id', (int) $request->query('status_dict_item_id'));
|
||
|
|
}
|
||
|
|
|
||
|
|
$paginator = $query
|
||
|
|
->orderByDesc('submitted_at')
|
||
|
|
->orderByDesc('id')
|
||
|
|
->paginate((int) $request->query('page_size', 20))
|
||
|
|
->withQueryString();
|
||
|
|
|
||
|
|
$paginator->getCollection()->transform(fn (Demand $d) => $this->serializeList($d));
|
||
|
|
|
||
|
|
return $this->paginated($paginator);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function store(Request $request): JsonResponse
|
||
|
|
{
|
||
|
|
$data = $this->validatedDemand($request);
|
||
|
|
$teacher = null;
|
||
|
|
if (! empty($data['teacher_id'])) {
|
||
|
|
$teacher = Teacher::query()->with('university')->findOrFail($data['teacher_id']);
|
||
|
|
}
|
||
|
|
|
||
|
|
$row = Demand::query()->create([
|
||
|
|
'teacher_id' => $data['teacher_id'] ?? null,
|
||
|
|
'type_dict_item_id' => $data['type_dict_item_id'],
|
||
|
|
'status_dict_item_id' => $data['status_dict_item_id'],
|
||
|
|
'title' => $data['title'],
|
||
|
|
'content' => $data['content'],
|
||
|
|
'contact_name' => $data['contact_name'] ?? $teacher?->name,
|
||
|
|
'company' => $data['company'] ?? $teacher?->university?->name,
|
||
|
|
'submitted_at' => now(),
|
||
|
|
]);
|
||
|
|
|
||
|
|
return $this->ok(['id' => $row->id], '已发布需求');
|
||
|
|
}
|
||
|
|
|
||
|
|
public function show(int $demand): JsonResponse
|
||
|
|
{
|
||
|
|
$model = Demand::query()
|
||
|
|
->with(['teacher.university', 'typeItem', 'statusItem'])
|
||
|
|
->withCount('handleLogs')
|
||
|
|
->findOrFail($demand);
|
||
|
|
|
||
|
|
return $this->ok($this->serializeDetail($model));
|
||
|
|
}
|
||
|
|
|
||
|
|
public function update(Request $request, int $demand): JsonResponse
|
||
|
|
{
|
||
|
|
$model = Demand::query()->findOrFail($demand);
|
||
|
|
$data = $this->validatedDemand($request, partial: true);
|
||
|
|
$model->fill($data);
|
||
|
|
$model->save();
|
||
|
|
|
||
|
|
return $this->ok(null, '已保存');
|
||
|
|
}
|
||
|
|
|
||
|
|
public function destroy(int $demand): JsonResponse
|
||
|
|
{
|
||
|
|
Demand::query()->findOrFail($demand)->delete();
|
||
|
|
|
||
|
|
return $this->ok(null, '已删除');
|
||
|
|
}
|
||
|
|
|
||
|
|
public function handleLogs(int $demand): JsonResponse
|
||
|
|
{
|
||
|
|
Demand::query()->findOrFail($demand);
|
||
|
|
|
||
|
|
$items = DemandHandleLog::query()
|
||
|
|
->with(['adminUser', 'statusItem'])
|
||
|
|
->where('demand_id', $demand)
|
||
|
|
->orderByDesc('handled_at')
|
||
|
|
->orderByDesc('id')
|
||
|
|
->get()
|
||
|
|
->map(fn (DemandHandleLog $log) => $this->serializeHandleLog($log));
|
||
|
|
|
||
|
|
return $this->ok(['items' => $items]);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function storeHandleLog(Request $request, int $demand): JsonResponse
|
||
|
|
{
|
||
|
|
$model = Demand::query()->findOrFail($demand);
|
||
|
|
$statusTypeId = DictType::query()->where('code', 'demand_status')->where('status', 1)->value('id');
|
||
|
|
|
||
|
|
$data = $request->validate([
|
||
|
|
'handled_at' => ['required', 'date'],
|
||
|
|
'admin_user_id' => ['nullable', 'integer', 'exists:admin_users,id'],
|
||
|
|
'status_dict_item_id' => $this->dictItemRules($statusTypeId, 'required'),
|
||
|
|
'content' => ['required', 'string'],
|
||
|
|
'next_plan' => ['nullable', 'string', 'max:255'],
|
||
|
|
'next_follow_date' => ['nullable', 'date'],
|
||
|
|
]);
|
||
|
|
|
||
|
|
$log = DemandHandleLog::query()->create([
|
||
|
|
'demand_id' => $model->id,
|
||
|
|
'admin_user_id' => $data['admin_user_id'] ?? $request->user()?->id,
|
||
|
|
'status_dict_item_id' => $data['status_dict_item_id'],
|
||
|
|
'content' => $data['content'],
|
||
|
|
'next_plan' => $data['next_plan'] ?? null,
|
||
|
|
'next_follow_date' => $data['next_follow_date'] ?? null,
|
||
|
|
'handled_at' => $data['handled_at'],
|
||
|
|
]);
|
||
|
|
|
||
|
|
$model->status_dict_item_id = $data['status_dict_item_id'];
|
||
|
|
$model->save();
|
||
|
|
|
||
|
|
return $this->ok(['id' => $log->id], '已保存跟进');
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @return array<string, mixed>
|
||
|
|
*/
|
||
|
|
protected function validatedDemand(Request $request, bool $partial = false): array
|
||
|
|
{
|
||
|
|
$typeTypeId = DictType::query()->where('code', 'demand_type')->where('status', 1)->value('id');
|
||
|
|
$statusTypeId = DictType::query()->where('code', 'demand_status')->where('status', 1)->value('id');
|
||
|
|
|
||
|
|
if (! $partial && (! $typeTypeId || ! $statusTypeId)) {
|
||
|
|
throw new \Illuminate\Http\Exceptions\HttpResponseException(
|
||
|
|
response()->json([
|
||
|
|
'message' => '需求字典未配置,请执行 DemandDictionarySeeder',
|
||
|
|
'data' => null,
|
||
|
|
], 422)
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
$activeStatusId = $statusTypeId
|
||
|
|
? DictItem::query()->where('dict_type_id', $statusTypeId)->where('value', 'active')->where('status', 1)->value('id')
|
||
|
|
: null;
|
||
|
|
|
||
|
|
$rules = [
|
||
|
|
'teacher_id' => ['nullable', 'integer', 'exists:teachers,id'],
|
||
|
|
'type_dict_item_id' => $this->dictItemRules($typeTypeId, $partial ? 'sometimes' : 'required'),
|
||
|
|
'status_dict_item_id' => $this->dictItemRules($statusTypeId, $partial ? 'sometimes' : 'nullable'),
|
||
|
|
'title' => [$partial ? 'sometimes' : 'required', 'string', 'max:255'],
|
||
|
|
'content' => [$partial ? 'sometimes' : 'required', 'string'],
|
||
|
|
'contact_name' => ['nullable', 'string', 'max:64'],
|
||
|
|
'company' => ['nullable', 'string', 'max:128'],
|
||
|
|
];
|
||
|
|
|
||
|
|
$data = $request->validate($rules);
|
||
|
|
|
||
|
|
if (! $partial && empty($data['status_dict_item_id']) && $activeStatusId) {
|
||
|
|
$data['status_dict_item_id'] = $activeStatusId;
|
||
|
|
}
|
||
|
|
|
||
|
|
return $data;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @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(Demand $d): array
|
||
|
|
{
|
||
|
|
return [
|
||
|
|
'id' => $d->id,
|
||
|
|
'teacher_id' => $d->teacher_id,
|
||
|
|
'teacher_name' => $d->teacher?->name,
|
||
|
|
'type_dict_item_id' => $d->type_dict_item_id,
|
||
|
|
'type_item' => $this->serializeDictItem($d->typeItem),
|
||
|
|
'status_dict_item_id' => $d->status_dict_item_id,
|
||
|
|
'status_item' => $this->serializeDictItem($d->statusItem),
|
||
|
|
'title' => $d->title,
|
||
|
|
'content' => $d->content,
|
||
|
|
'contact_name' => $d->contact_name,
|
||
|
|
'company' => $d->company,
|
||
|
|
'submitted_at' => $d->submitted_at?->toDateString(),
|
||
|
|
'handle_logs_count' => (int) ($d->handle_logs_count ?? 0),
|
||
|
|
];
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @return array<string, mixed>
|
||
|
|
*/
|
||
|
|
protected function serializeDetail(Demand $d): array
|
||
|
|
{
|
||
|
|
$row = $this->serializeList($d);
|
||
|
|
$row['university_name'] = $d->teacher?->university?->name;
|
||
|
|
|
||
|
|
return $row;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @return array<string, mixed>
|
||
|
|
*/
|
||
|
|
protected function serializeHandleLog(DemandHandleLog $log): array
|
||
|
|
{
|
||
|
|
return [
|
||
|
|
'id' => $log->id,
|
||
|
|
'handled_at' => $log->handled_at?->toDateString(),
|
||
|
|
'operator_name' => $log->adminUser?->real_name ?: $log->adminUser?->username,
|
||
|
|
'status_item' => $this->serializeDictItem($log->statusItem),
|
||
|
|
'content' => $log->content,
|
||
|
|
'next_plan' => $log->next_plan,
|
||
|
|
'next_follow_date' => $log->next_follow_date?->toDateString(),
|
||
|
|
];
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @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,
|
||
|
|
];
|
||
|
|
}
|
||
|
|
}
|