latest('id')->first(); // Only fetch contracts that are currently in either the primary segment // or the optional queue segment defined on the latest FieldJobSetting. $segmentIds = collect([ optional($setting)->queue_segment_id, optional($setting)->segment_id, ])->filter()->unique()->values(); $search = $request->input('search'); $searchAssigned = $request->input('search_assigned'); $assignedUserId = $request->input('assigned_user_id'); $unassignedClientUuids = $request->input('unassigned_client_uuids'); $assignedClientUuids = $request->input('assigned_client_uuids'); $unassignedContracts = Contract::query() ->with(['clientCase.person.addresses', 'clientCase.client.person:id,uuid,full_name', 'type', 'account']) ->when($segmentIds->isNotEmpty(), fn($q) => $q->whereHas('segments', fn($rq) => $rq->whereIn('segments.id', $segmentIds)), fn($q) => $q->whereRaw('1 = 0') ) ->when( !empty($search), fn ($q) => $q->where(fn($sq) => $sq->where('reference', 'like', "%{$search}%") ->orWhereHas('clientCase.person', fn($psq) => $psq->where('full_name', 'ilike', "%{$search}%") ) ->orWhereHas('clientCase.person.addresses', fn ($ccpaq) => $ccpaq->where('address', 'ilike', "%{$search}") ) ) ) ->when(!empty($unassignedClientUuids) && is_array($unassignedClientUuids), fn ($q) => $q->whereHas('clientCase.client', fn($cq) => $cq->whereIn('uuid', $unassignedClientUuids) ) ) ->whereDoesntHave('fieldJobs', fn ($q) => $q->whereNull('completed_at') ->whereNull('cancelled_at') ) ->latest('id'); $unassignedClients = $unassignedContracts->get() ->pluck('clientCase.client') ->filter() ->unique('id') ->values(); $assignedContracts = Contract::query() ->with(['clientCase.person.addresses', 'clientCase.client.person:id,uuid,full_name', 'type', 'account', 'lastFieldJobs', 'lastFieldJobs.assignedUser', 'lastFieldJobs.user']) ->when($segmentIds->isNotEmpty(), fn($q) => $q->whereHas('segments', fn($rq) => $rq->whereIn('segments.id', $segmentIds)), fn($q) => $q->whereRaw('1 = 0') ) ->when( !empty($searchAssigned), fn ($q) => $q->where(fn($sq) => $sq->where('reference', 'like', "%{$searchAssigned}%") ->orWhereHas('clientCase.person', fn($psq) => $psq->where('full_name', 'ilike', "%{$searchAssigned}%") ) ->orWhereHas('clientCase.person.addresses', fn ($ccpaq) => $ccpaq->where('address', 'ilike', "%{$searchAssigned}") ) ) ) ->when(!empty($assignedClientUuids) && is_array($assignedClientUuids), fn ($q) => $q->whereHas('clientCase.client', fn($cq) => $cq->whereIn('uuid', $assignedClientUuids) ) ) ->whereHas('lastFieldJobs', fn ($q) => $q->whereNull('completed_at') ->whereNull('cancelled_at') ->when($assignedUserId && $assignedUserId !== 'all', fn ($jq) => $jq->where('assigned_user_id', $assignedUserId)) ) ->latest('id'); $assignedClients = $assignedContracts->get() ->pluck('clientCase.client') ->filter() ->unique('id') ->values(); $users = User::query()->orderBy('name')->get(['id', 'name']); return Inertia::render('FieldJob/Index', [ 'setting' => $setting, 'unassignedContracts' => $unassignedContracts->paginate( $request->input('per_page_contracts', 10), ['*'], 'page_contracts', $request->input('page_contracts', 1) ), 'assignedContracts' => $assignedContracts->paginate( $request->input('per_page_assignments', 10), ['*'], 'page_assignments', $request->input('page_assignments', 1) ), 'unassignedClients' => $unassignedClients, 'assignedClients' => $assignedClients, 'users' => $users, 'filters' => [ 'search' => $search, 'search_assigned' => $searchAssigned, 'assigned_user_id' => $assignedUserId, 'unassigned_client_uuids' => $unassignedClientUuids, 'assigned_client_uuids' => $assignedClientUuids, ], ]); } public function assign(Request $request) { $data = $request->validate([ 'contract_uuid' => 'required|string|exists:contracts,uuid', 'assigned_user_id' => 'required|integer|exists:users,id', ]); try { DB::transaction(function () use ($data) { $setting = FieldJobSetting::query()->latest('id')->first(); if (! $setting) { throw new Exception('No Field Job Setting found. Create one in Settings → Field Job Settings.'); } $contract = Contract::query()->where('uuid', $data['contract_uuid'])->firstOrFail(); $job = FieldJob::create([ 'field_job_setting_id' => $setting->id, 'assigned_user_id' => $data['assigned_user_id'], 'contract_id' => $contract->id, 'assigned_at' => now(), ]); // Create an activity for the assignment if ($setting->action_id && $setting->assign_decision_id) { $assigneeName = User::query()->where('id', $data['assigned_user_id'])->value('name'); // Localized note: "Terensko opravilo dodeljeno" + assignee when present $note = 'Terensko opravilo dodeljeno'.($assigneeName ? ' uporabniku '.$assigneeName : ''); Activity::create([ 'due_date' => null, 'amount' => null, 'note' => $note, 'action_id' => $setting->action_id, 'decision_id' => $setting->assign_decision_id, 'client_case_id' => $contract->client_case_id, 'contract_id' => $contract->id, ]); // Move contract to the configured segment for field jobs $job->moveContractToSegment($setting->segment_id); } else { throw new Exception('The current Field Job Setting is missing an action or assign decision. Please update it in Settings → Field Job Settings.'); } }); return back()->with('success', 'Field job assigned.'); } catch (QueryException $e) { return back()->withErrors(['database' => 'Database error: '.$e->getMessage()]); } catch (Exception $e) { return back()->withErrors(['error' => 'Error: '.$e->getMessage()]); } } /** * Bulk assign multiple contracts to a single user. */ public function assignBulk(Request $request) { $data = $request->validate([ 'contract_uuids' => 'required|array|min:1', 'contract_uuids.*' => 'required|string|distinct|exists:contracts,uuid', 'assigned_user_id' => 'required|integer|exists:users,id', ]); try { DB::transaction(function () use ($data) { $setting = FieldJobSetting::query()->latest('id')->first(); if (! $setting) { throw new Exception('No Field Job Setting found. Create one in Settings → Field Job Settings.'); } if (! ($setting->action_id && $setting->assign_decision_id)) { throw new Exception('The current Field Job Setting is missing an action or assign decision. Please update it in Settings → Field Job Settings.'); } $assigneeName = User::query()->where('id', $data['assigned_user_id'])->value('name'); $noteBase = 'Terensko opravilo dodeljeno'.($assigneeName ? ' uporabniku '.$assigneeName : ''); // Load all contracts in one query $contracts = Contract::query()->whereIn('uuid', $data['contract_uuids'])->get(); foreach ($contracts as $contract) { // Skip if already has an active job $hasActive = FieldJob::query() ->where('contract_id', $contract->id) ->whereNull('completed_at') ->whereNull('cancelled_at') ->exists(); if ($hasActive) { continue; } $job = FieldJob::create([ 'field_job_setting_id' => $setting->id, 'assigned_user_id' => $data['assigned_user_id'], 'contract_id' => $contract->id, 'assigned_at' => now(), ]); Activity::create([ 'due_date' => null, 'amount' => null, 'note' => $noteBase, 'action_id' => $setting->action_id, 'decision_id' => $setting->assign_decision_id, 'client_case_id' => $contract->client_case_id, 'contract_id' => $contract->id, ]); // Move contract to the configured segment for field jobs $job->moveContractToSegment($setting->segment_id); } }); return back()->with('success', 'Field jobs assigned.'); } catch (QueryException $e) { return back()->withErrors(['database' => 'Database error: '.$e->getMessage()]); } catch (Exception $e) { return back()->withErrors(['error' => 'Error: '.$e->getMessage()]); } } public function cancel(Request $request) { $data = $request->validate([ 'contract_uuid' => 'required|string|exists:contracts,uuid', ]); $contract = Contract::query()->where('uuid', $data['contract_uuid'])->firstOrFail(); $job = FieldJob::query() ->where('contract_id', $contract->id) ->whereNull('completed_at') ->whereNull('cancelled_at') ->latest('id') ->first(); if ($job) { $job->cancelled_at = now(); $job->save(); // Create an activity for the cancellation, mirroring the assign flow // Prefer the job's setting for a consistent decision $job->loadMissing('setting'); $actionId = optional($job->setting)->action_id; $decisionId = optional($job->setting)->cancel_decision_id; // If no decision configured, skip logging if ($actionId && $decisionId) { Activity::create([ 'due_date' => null, 'amount' => null, 'note' => 'Terensko opravilo preklicano', 'action_id' => $actionId, 'decision_id' => $decisionId, 'client_case_id' => $contract->client_case_id, 'contract_id' => $contract->id, ]); } } return back()->with('success', 'Field job cancelled.'); } public function complete(Request $request, \App\Models\ClientCase $clientCase) { // Complete all active field jobs for contracts of this case assigned to current user $userId = optional($request->user())->id; $setting = FieldJobSetting::query()->latest('id')->first(); if (! $setting) { return back()->withErrors(['setting' => 'No Field Job Setting found.']); } $decisionId = $setting->complete_decision_id; $actionId = $setting->action_id; // Find all active jobs for this case for the current user $jobs = FieldJob::query() ->whereHas('contract', function ($q) use ($clientCase) { $q->where('client_case_id', $clientCase->id); }) ->where(function ($q) use ($userId) { if ($userId) { $q->where('assigned_user_id', $userId); } }) ->whereNull('completed_at') ->whereNull('cancelled_at') ->with(['contract:id,client_case_id', 'setting']) ->get(); DB::transaction(function () use ($jobs, $decisionId, $actionId) { foreach ($jobs as $job) { // Mark job complete $job->completed_at = now(); $job->save(); // Log completion activity on the contract/case if ($actionId && $decisionId) { Activity::create([ 'due_date' => null, 'amount' => null, 'note' => 'Terensko opravilo zaključeno', 'action_id' => $actionId, 'decision_id' => $decisionId, 'client_case_id' => $job->contract->client_case_id, 'contract_id' => $job->contract_id, ]); } // Move contract to configured return segment $job->returnContractToConfiguredSegment(); } }); // Redirect back to phone index return to_route('phone.index'); } }