358 lines
14 KiB
PHP
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');
|
|
}
|
|
}
|