feat: person tags system - org-level skills with self-reported and organiser-assigned sources
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -31,6 +31,29 @@ final class PersonController extends Controller
|
||||
$query->where('status', $request->input('status'));
|
||||
}
|
||||
|
||||
if ($request->filled('tag')) {
|
||||
$organisation = $event->organisation;
|
||||
$query->whereHas('user', function ($q) use ($request, $organisation) {
|
||||
$q->whereHas('organisationTags', function ($q2) use ($request, $organisation) {
|
||||
$q2->where('organisation_id', $organisation->id)
|
||||
->where('person_tag_id', $request->input('tag'));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if ($request->filled('tags')) {
|
||||
$organisation = $event->organisation;
|
||||
$tagIds = explode(',', $request->input('tags'));
|
||||
foreach ($tagIds as $tagId) {
|
||||
$query->whereHas('user', function ($q) use ($tagId, $organisation) {
|
||||
$q->whereHas('organisationTags', function ($q2) use ($tagId, $organisation) {
|
||||
$q2->where('organisation_id', $organisation->id)
|
||||
->where('person_tag_id', $tagId);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return new PersonCollection($query->get());
|
||||
}
|
||||
|
||||
@@ -38,7 +61,7 @@ final class PersonController extends Controller
|
||||
{
|
||||
Gate::authorize('view', [$person, $event]);
|
||||
|
||||
$person->load(['crowdType', 'company']);
|
||||
$person->load(['crowdType', 'company', 'user']);
|
||||
|
||||
return $this->success(new PersonResource($person));
|
||||
}
|
||||
|
||||
67
api/app/Http/Controllers/Api/V1/PersonTagController.php
Normal file
67
api/app/Http/Controllers/Api/V1/PersonTagController.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api\V1;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Api\V1\StorePersonTagRequest;
|
||||
use App\Http\Requests\Api\V1\UpdatePersonTagRequest;
|
||||
use App\Http\Resources\Api\V1\PersonTagResource;
|
||||
use App\Models\Organisation;
|
||||
use App\Models\PersonTag;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
|
||||
final class PersonTagController extends Controller
|
||||
{
|
||||
public function index(Organisation $organisation): AnonymousResourceCollection
|
||||
{
|
||||
Gate::authorize('viewAny', [PersonTag::class, $organisation]);
|
||||
|
||||
$tags = $organisation->personTags()->active()->ordered()->get();
|
||||
|
||||
return PersonTagResource::collection($tags);
|
||||
}
|
||||
|
||||
public function store(StorePersonTagRequest $request, Organisation $organisation): JsonResponse
|
||||
{
|
||||
Gate::authorize('create', [PersonTag::class, $organisation]);
|
||||
|
||||
$tag = $organisation->personTags()->create($request->validated());
|
||||
|
||||
return $this->created(new PersonTagResource($tag));
|
||||
}
|
||||
|
||||
public function update(UpdatePersonTagRequest $request, Organisation $organisation, PersonTag $personTag): JsonResponse
|
||||
{
|
||||
Gate::authorize('update', [$personTag, $organisation]);
|
||||
|
||||
$personTag->update($request->validated());
|
||||
|
||||
return $this->success(new PersonTagResource($personTag->fresh()));
|
||||
}
|
||||
|
||||
public function destroy(Organisation $organisation, PersonTag $personTag): JsonResponse
|
||||
{
|
||||
Gate::authorize('delete', [$personTag, $organisation]);
|
||||
|
||||
$personTag->update(['is_active' => false]);
|
||||
|
||||
return response()->json(null, 204);
|
||||
}
|
||||
|
||||
public function categories(Organisation $organisation): JsonResponse
|
||||
{
|
||||
Gate::authorize('viewAny', [PersonTag::class, $organisation]);
|
||||
|
||||
$categories = $organisation->personTags()
|
||||
->whereNotNull('category')
|
||||
->distinct()
|
||||
->orderBy('category')
|
||||
->pluck('category');
|
||||
|
||||
return response()->json(['data' => $categories]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api\V1;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Api\V1\StoreUserOrganisationTagRequest;
|
||||
use App\Http\Requests\Api\V1\SyncUserOrganisationTagsRequest;
|
||||
use App\Http\Resources\Api\V1\UserOrganisationTagResource;
|
||||
use App\Models\Organisation;
|
||||
use App\Models\User;
|
||||
use App\Models\UserOrganisationTag;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
|
||||
final class UserOrganisationTagController extends Controller
|
||||
{
|
||||
public function index(Organisation $organisation, User $user): AnonymousResourceCollection
|
||||
{
|
||||
Gate::authorize('viewAny', [UserOrganisationTag::class, $organisation]);
|
||||
|
||||
$tags = $user->organisationTags()
|
||||
->where('organisation_id', $organisation->id)
|
||||
->with(['personTag', 'assignedBy'])
|
||||
->get();
|
||||
|
||||
return UserOrganisationTagResource::collection($tags);
|
||||
}
|
||||
|
||||
public function store(StoreUserOrganisationTagRequest $request, Organisation $organisation, User $user): JsonResponse
|
||||
{
|
||||
Gate::authorize('create', [UserOrganisationTag::class, $organisation]);
|
||||
|
||||
$data = $request->validated();
|
||||
$data['user_id'] = $user->id;
|
||||
$data['organisation_id'] = $organisation->id;
|
||||
$data['assigned_at'] = now();
|
||||
|
||||
if ($data['source'] === 'organiser_assigned') {
|
||||
$data['assigned_by_user_id'] = $request->user()->id;
|
||||
}
|
||||
|
||||
$tag = UserOrganisationTag::create($data);
|
||||
$tag->load(['personTag', 'assignedBy']);
|
||||
|
||||
return $this->created(new UserOrganisationTagResource($tag));
|
||||
}
|
||||
|
||||
public function destroy(Organisation $organisation, User $user, UserOrganisationTag $userOrganisationTag): JsonResponse
|
||||
{
|
||||
Gate::authorize('delete', [$userOrganisationTag, $organisation]);
|
||||
|
||||
$userOrganisationTag->delete();
|
||||
|
||||
return response()->json(null, 204);
|
||||
}
|
||||
|
||||
public function sync(SyncUserOrganisationTagsRequest $request, Organisation $organisation, User $user): AnonymousResourceCollection
|
||||
{
|
||||
Gate::authorize('sync', [UserOrganisationTag::class, $organisation]);
|
||||
|
||||
$source = $request->validated('source');
|
||||
$tagIds = $request->validated('tag_ids');
|
||||
|
||||
// Remove tags of this source that are not in the new list
|
||||
$user->organisationTags()
|
||||
->where('organisation_id', $organisation->id)
|
||||
->where('source', $source)
|
||||
->whereNotIn('person_tag_id', $tagIds)
|
||||
->delete();
|
||||
|
||||
// Add new tags that don't exist yet
|
||||
$existingTagIds = $user->organisationTags()
|
||||
->where('organisation_id', $organisation->id)
|
||||
->where('source', $source)
|
||||
->pluck('person_tag_id')
|
||||
->toArray();
|
||||
|
||||
$newTagIds = array_diff($tagIds, $existingTagIds);
|
||||
|
||||
foreach ($newTagIds as $tagId) {
|
||||
UserOrganisationTag::create([
|
||||
'user_id' => $user->id,
|
||||
'organisation_id' => $organisation->id,
|
||||
'person_tag_id' => $tagId,
|
||||
'source' => $source,
|
||||
'assigned_by_user_id' => $source === 'organiser_assigned' ? $request->user()->id : null,
|
||||
'assigned_at' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
// Return updated full tag list for this user in this org
|
||||
$tags = $user->organisationTags()
|
||||
->where('organisation_id', $organisation->id)
|
||||
->with(['personTag', 'assignedBy'])
|
||||
->get();
|
||||
|
||||
return UserOrganisationTagResource::collection($tags);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user