Teren-app/app/Http/Controllers/FieldJobController.php
Simon Pocrnjič cc4c07717e Changes
2026-01-18 18:21:41 +01:00

358 lines
14 KiB
PHP

<?php
namespace App\Http\Controllers;
use App\Models\Activity;
use App\Models\Contract;
use App\Models\FieldJob;
use App\Models\FieldJobSetting;
use App\Models\User;
use Exception;
use Illuminate\Database\QueryException;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Inertia\Inertia;
class FieldJobController extends Controller
{
public function index(Request $request)
{
$setting = FieldJobSetting::query()->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');
}
}