CRM/backend/app/Http/Controllers/Api/PipelineController.php

85 خطوط
3.9 KiB
PHP

<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Deal;
use App\Models\DealStage;
use App\Models\Pipeline;
use App\Services\DealPipelineService;
use App\Support\AccessControl;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class PipelineController extends Controller
{
public function __construct(private DealPipelineService $service) {}
public function index(Request $request): JsonResponse
{
abort_unless($request->user()->can('view_pipelines'), 403);
$query = Pipeline::with('stages')->where('is_active', true)->orderBy('sort_order');
if (! $request->user()->hasRole('admin')) {
$teamIds = AccessControl::teamIds($request->user());
$query->where(fn ($scope) => $scope->whereNull('team_id')->orWhereIn('team_id', $teamIds));
}
return response()->json($query->get());
}
public function store(Request $request): JsonResponse
{
abort_unless($request->user()->can('manage_pipelines'), 403);
$data = $request->validate([
'name' => 'required|string|max:120', 'team_id' => 'nullable|exists:teams,id',
'is_default' => 'boolean', 'stages' => 'required|array|min:2|max:20',
'stages.*.name' => 'required|string|max:80', 'stages.*.color' => ['nullable', 'regex:/^#[0-9A-Fa-f]{6}$/'],
'stages.*.probability' => 'required|integer|min:0|max:100', 'stages.*.is_won' => 'boolean', 'stages.*.is_lost' => 'boolean',
]);
if (! $request->user()->hasRole('admin') && ! empty($data['team_id'])) {
abort_unless(in_array((int) $data['team_id'], AccessControl::teamIds($request->user()), true), 403);
}
$pipeline = DB::transaction(function () use ($data): Pipeline {
if ($data['is_default'] ?? false) {
Pipeline::query()->update(['is_default' => false]);
}
$pipeline = Pipeline::create([
'name' => $data['name'], 'slug' => Str::slug($data['name']).'-'.Str::lower(Str::random(5)),
'team_id' => $data['team_id'] ?? null, 'is_default' => $data['is_default'] ?? false, 'is_active' => true,
]);
foreach ($data['stages'] as $index => $stage) {
$pipeline->stages()->create($stage + ['slug' => Str::slug($stage['name']).'-'.$index, 'sort_order' => $index, 'is_active' => true]);
}
return $pipeline;
});
return response()->json($pipeline->load('stages'), 201);
}
public function board(Request $request, Pipeline $pipeline): JsonResponse
{
abort_unless($request->user()->can('view_pipelines'), 403);
abort_unless($this->visible($pipeline, $request), 403);
return response()->json($this->service->board($pipeline, $request->user(), $request->only(['search', 'owner_id', 'forecast_category', 'status'])));
}
public function move(Request $request, Deal $deal): JsonResponse
{
abort_unless($request->user()->can('move_deals'), 403);
abort_unless(AccessControl::canAccessDeal($request->user(), $deal), 403);
$data = $request->validate(['deal_stage_id' => 'required|exists:deal_stages,id', 'version' => 'required|integer|min:1', 'reason' => 'nullable|string|max:1000', 'final_amount' => 'nullable|numeric|min:0']);
$stage = DealStage::findOrFail($data['deal_stage_id']);
return response()->json($this->service->move($deal, $stage, $request->user(), $data['version'], $data['reason'] ?? null, isset($data['final_amount']) ? (float) $data['final_amount'] : null));
}
private function visible(Pipeline $pipeline, Request $request): bool
{
return $request->user()->hasRole('admin') || $pipeline->team_id === null || in_array($pipeline->team_id, AccessControl::teamIds($request->user()), true);
}
}