Field job changed permissions and other things
This commit is contained in:
parent
0d9c8c8b30
commit
5f879c9436
|
|
@ -76,14 +76,29 @@ public function completedToday(Request $request)
|
||||||
public function showCase(\App\Models\ClientCase $clientCase, Request $request)
|
public function showCase(\App\Models\ClientCase $clientCase, Request $request)
|
||||||
{
|
{
|
||||||
$userId = $request->user()->id;
|
$userId = $request->user()->id;
|
||||||
|
$completedMode = (bool) $request->boolean('completed');
|
||||||
|
|
||||||
// Eager load client case with person details
|
// Eager load client case with person details
|
||||||
$case = \App\Models\ClientCase::query()
|
$case = \App\Models\ClientCase::query()
|
||||||
->with(['person' => fn ($q) => $q->with(['addresses', 'phones', 'emails', 'bankAccounts'])])
|
->with(['person' => fn ($q) => $q->with(['addresses', 'phones', 'emails', 'bankAccounts'])])
|
||||||
->findOrFail($clientCase->id);
|
->findOrFail($clientCase->id);
|
||||||
|
|
||||||
// Determine contracts of this case assigned to the current user via FieldJobs and still active
|
// Determine contracts of this case relevant to the current user
|
||||||
$assignedContractIds = FieldJob::query()
|
// - Normal mode: contracts assigned to me and still active (not completed/cancelled)
|
||||||
|
// - Completed mode (?completed=1): contracts where my field job was completed today
|
||||||
|
if ($completedMode) {
|
||||||
|
$start = now()->startOfDay();
|
||||||
|
$end = now()->endOfDay();
|
||||||
|
$contractIds = FieldJob::query()
|
||||||
|
->where('assigned_user_id', $userId)
|
||||||
|
->whereNull('cancelled_at')
|
||||||
|
->whereBetween('completed_at', [$start, $end])
|
||||||
|
->whereHas('contract', fn ($q) => $q->where('client_case_id', $case->id))
|
||||||
|
->pluck('contract_id')
|
||||||
|
->unique()
|
||||||
|
->values();
|
||||||
|
} else {
|
||||||
|
$contractIds = FieldJob::query()
|
||||||
->where('assigned_user_id', $userId)
|
->where('assigned_user_id', $userId)
|
||||||
->whereNull('completed_at')
|
->whereNull('completed_at')
|
||||||
->whereNull('cancelled_at')
|
->whereNull('cancelled_at')
|
||||||
|
|
@ -91,10 +106,11 @@ public function showCase(\App\Models\ClientCase $clientCase, Request $request)
|
||||||
->pluck('contract_id')
|
->pluck('contract_id')
|
||||||
->unique()
|
->unique()
|
||||||
->values();
|
->values();
|
||||||
|
}
|
||||||
|
|
||||||
$contracts = \App\Models\Contract::query()
|
$contracts = \App\Models\Contract::query()
|
||||||
->where('client_case_id', $case->id)
|
->where('client_case_id', $case->id)
|
||||||
->whereIn('id', $assignedContractIds)
|
->whereIn('id', $contractIds)
|
||||||
->with(['type:id,name', 'account'])
|
->with(['type:id,name', 'account'])
|
||||||
->orderByDesc('created_at')
|
->orderByDesc('created_at')
|
||||||
->get();
|
->get();
|
||||||
|
|
@ -128,7 +144,7 @@ public function showCase(\App\Models\ClientCase $clientCase, Request $request)
|
||||||
|
|
||||||
$contractDocs = \App\Models\Document::query()
|
$contractDocs = \App\Models\Document::query()
|
||||||
->where('documentable_type', \App\Models\Contract::class)
|
->where('documentable_type', \App\Models\Contract::class)
|
||||||
->whereIn('documentable_id', $assignedContractIds)
|
->whereIn('documentable_id', $contractIds)
|
||||||
->orderByDesc('created_at')
|
->orderByDesc('created_at')
|
||||||
->get()
|
->get()
|
||||||
->map(function ($d) use ($contractRefMap) {
|
->map(function ($d) use ($contractRefMap) {
|
||||||
|
|
@ -168,15 +184,41 @@ public function showCase(\App\Models\ClientCase $clientCase, Request $request)
|
||||||
return $a;
|
return $a;
|
||||||
});
|
});
|
||||||
|
|
||||||
return Inertia::render('Phone/Case/Index', [
|
// Determine segment filters from FieldJobSettings for this case/user context
|
||||||
'client' => $case->client()->with('person', fn ($q) => $q->with(['addresses', 'phones', 'emails', 'bankAccounts']))->firstOrFail(),
|
$settingIds = FieldJob::query()
|
||||||
'client_case' => $case,
|
->where('assigned_user_id', $userId)
|
||||||
'contracts' => $contracts,
|
->whereHas('contract', fn ($q) => $q->where('client_case_id', $case->id))
|
||||||
'documents' => $documents,
|
->when(
|
||||||
'types' => $types,
|
$completedMode,
|
||||||
'account_types' => \App\Models\AccountType::all(),
|
function ($q) {
|
||||||
// Provide decisions with linked email template metadata (entity_types, allow_attachments)
|
$q->whereNull('cancelled_at')
|
||||||
'actions' => \App\Models\Action::query()
|
->whereBetween('completed_at', [now()->startOfDay(), now()->endOfDay()]);
|
||||||
|
},
|
||||||
|
function ($q) {
|
||||||
|
$q->whereNull('completed_at')->whereNull('cancelled_at');
|
||||||
|
}
|
||||||
|
)
|
||||||
|
->pluck('field_job_setting_id')
|
||||||
|
->filter()
|
||||||
|
->unique()
|
||||||
|
->values();
|
||||||
|
|
||||||
|
$segmentIds = collect();
|
||||||
|
if ($settingIds->isNotEmpty()) {
|
||||||
|
$segmentIds = \App\Models\FieldJobSetting::query()
|
||||||
|
->whereIn('id', $settingIds)
|
||||||
|
->pluck('segment_id')
|
||||||
|
->filter()
|
||||||
|
->unique()
|
||||||
|
->values();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter actions and their decisions by the derived segment ids (decisions.segment_id)
|
||||||
|
$actions = \App\Models\Action::query()
|
||||||
|
->when($segmentIds->isNotEmpty(), function ($q) use ($segmentIds) {
|
||||||
|
// Filter actions by their segment_id matching the FieldJobSetting segment(s)
|
||||||
|
$q->whereIn('segment_id', $segmentIds);
|
||||||
|
})
|
||||||
->with([
|
->with([
|
||||||
'decisions' => function ($q) {
|
'decisions' => function ($q) {
|
||||||
$q->select('decisions.id', 'decisions.name', 'decisions.color_tag', 'decisions.auto_mail', 'decisions.email_template_id');
|
$q->select('decisions.id', 'decisions.name', 'decisions.color_tag', 'decisions.auto_mail', 'decisions.email_template_id');
|
||||||
|
|
@ -185,9 +227,19 @@ public function showCase(\App\Models\ClientCase $clientCase, Request $request)
|
||||||
$q->select('id', 'name', 'entity_types', 'allow_attachments');
|
$q->select('id', 'name', 'entity_types', 'allow_attachments');
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
->get(['id', 'name', 'color_tag', 'segment_id']),
|
->get(['id', 'name', 'color_tag', 'segment_id']);
|
||||||
|
|
||||||
|
return Inertia::render('Phone/Case/Index', [
|
||||||
|
'client' => $case->client()->with('person', fn ($q) => $q->with(['addresses', 'phones', 'emails', 'bankAccounts']))->firstOrFail(),
|
||||||
|
'client_case' => $case,
|
||||||
|
'contracts' => $contracts,
|
||||||
|
'documents' => $documents,
|
||||||
|
'types' => $types,
|
||||||
|
'account_types' => \App\Models\AccountType::all(),
|
||||||
|
// Provide decisions (filtered by segment) with linked email template metadata (entity_types, allow_attachments)
|
||||||
|
'actions' => $actions,
|
||||||
'activities' => $activities,
|
'activities' => $activities,
|
||||||
'completed_mode' => (bool) $request->boolean('completed'),
|
'completed_mode' => $completedMode,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -186,6 +186,7 @@ const rawMenuGroups = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Terensko delo",
|
label: "Terensko delo",
|
||||||
|
requires: { permission: "field-job" },
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
key: "fieldjobs",
|
key: "fieldjobs",
|
||||||
|
|
|
||||||
|
|
@ -130,9 +130,9 @@ const redirectIfNoContracts = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
/*onMounted(() => {
|
||||||
redirectIfNoContracts();
|
redirectIfNoContracts();
|
||||||
});
|
});*/
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => (Array.isArray(props.contracts) ? props.contracts.length : null),
|
() => (Array.isArray(props.contracts) ? props.contracts.length : null),
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ const columns = [
|
||||||
{ key: "id", label: "#", sortable: true, class: "w-16" },
|
{ key: "id", label: "#", sortable: true, class: "w-16" },
|
||||||
{ key: "name", label: "Ime", sortable: true },
|
{ key: "name", label: "Ime", sortable: true },
|
||||||
{ key: "color_tag", label: "Barva", sortable: false },
|
{ key: "color_tag", label: "Barva", sortable: false },
|
||||||
|
{ key: "segment", label: "Segment", sortable: false },
|
||||||
{ key: "decisions", label: "Odločitve", sortable: false, class: "w-32" },
|
{ key: "decisions", label: "Odločitve", sortable: false, class: "w-32" },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
@ -195,6 +196,11 @@ const destroyAction = () => {
|
||||||
<template #cell-decisions="{ row }">
|
<template #cell-decisions="{ row }">
|
||||||
{{ row.decisions?.length ?? 0 }}
|
{{ row.decisions?.length ?? 0 }}
|
||||||
</template>
|
</template>
|
||||||
|
<template #cell-segment="{ row }">
|
||||||
|
<span>
|
||||||
|
{{ row.segment?.name || "" }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
<template #actions="{ row }">
|
<template #actions="{ row }">
|
||||||
<button class="px-2" @click="openEditDrawer(row)">
|
<button class="px-2" @click="openEditDrawer(row)">
|
||||||
<EditIcon size="md" css="text-gray-500" />
|
<EditIcon size="md" css="text-gray-500" />
|
||||||
|
|
|
||||||
|
|
@ -166,7 +166,7 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
// Contract document generation (JSON) - protected by auth+verified; permission enforced inside controller service
|
// Contract document generation (JSON) - protected by auth+verified; permission enforced inside controller service
|
||||||
Route::post('contracts/{contract:uuid}/generate-document', \App\Http\Controllers\ContractDocumentGenerationController::class)->name('contracts.generate-document')->middleware("permission:create-docs");
|
Route::post('contracts/{contract:uuid}/generate-document', \App\Http\Controllers\ContractDocumentGenerationController::class)->name('contracts.generate-document')->middleware('permission:create-docs');
|
||||||
|
|
||||||
// Phone page
|
// Phone page
|
||||||
Route::get('phone', [PhoneViewController::class, 'index'])->name('phone.index');
|
Route::get('phone', [PhoneViewController::class, 'index'])->name('phone.index');
|
||||||
|
|
@ -332,7 +332,7 @@
|
||||||
Route::delete('client-cases/{client_case:uuid}/objects/{id}', [CaseObjectController::class, 'destroy'])->name('clientCase.object.delete');
|
Route::delete('client-cases/{client_case:uuid}/objects/{id}', [CaseObjectController::class, 'destroy'])->name('clientCase.object.delete');
|
||||||
// client-case / activity
|
// client-case / activity
|
||||||
Route::post('client-cases/{client_case:uuid}/activity', [ClientCaseContoller::class, 'storeActivity'])->name('clientCase.activity.store');
|
Route::post('client-cases/{client_case:uuid}/activity', [ClientCaseContoller::class, 'storeActivity'])->name('clientCase.activity.store');
|
||||||
Route::delete('client-cases/{client_case:uuid}/activity/{activity}', [ClientCaseContoller::class, 'deleteActivity'])->name('clientCase.activity.delete')->middleware("permission:activity-edit");
|
Route::delete('client-cases/{client_case:uuid}/activity/{activity}', [ClientCaseContoller::class, 'deleteActivity'])->name('clientCase.activity.delete')->middleware('permission:activity-edit');
|
||||||
// client-case / segments
|
// client-case / segments
|
||||||
Route::post('client-cases/{client_case:uuid}/segments', [ClientCaseContoller::class, 'attachSegment'])->name('clientCase.segments.attach');
|
Route::post('client-cases/{client_case:uuid}/segments', [ClientCaseContoller::class, 'attachSegment'])->name('clientCase.segments.attach');
|
||||||
// client-case / documents
|
// client-case / documents
|
||||||
|
|
@ -340,7 +340,7 @@
|
||||||
|
|
||||||
Route::get('client-cases/{client_case:uuid}/documents/{document:uuid}/view', [ClientCaseContoller::class, 'viewDocument'])->name('clientCase.document.view');
|
Route::get('client-cases/{client_case:uuid}/documents/{document:uuid}/view', [ClientCaseContoller::class, 'viewDocument'])->name('clientCase.document.view');
|
||||||
Route::get('client-cases/{client_case:uuid}/documents/{document:uuid}/download', [ClientCaseContoller::class, 'downloadDocument'])->name('clientCase.document.download');
|
Route::get('client-cases/{client_case:uuid}/documents/{document:uuid}/download', [ClientCaseContoller::class, 'downloadDocument'])->name('clientCase.document.download');
|
||||||
Route::middleware("permission:doc-edit")->group( function() {
|
Route::middleware('permission:doc-edit')->group(function () {
|
||||||
Route::patch('client-cases/{client_case:uuid}/documents/{document:uuid}', [ClientCaseContoller::class, 'updateDocument'])
|
Route::patch('client-cases/{client_case:uuid}/documents/{document:uuid}', [ClientCaseContoller::class, 'updateDocument'])
|
||||||
->withoutScopedBindings()
|
->withoutScopedBindings()
|
||||||
->name('clientCase.document.update');
|
->name('clientCase.document.update');
|
||||||
|
|
@ -375,6 +375,7 @@
|
||||||
Route::get('settings/field-job', [FieldJobSettingController::class, 'index'])->name('settings.fieldjob.index');
|
Route::get('settings/field-job', [FieldJobSettingController::class, 'index'])->name('settings.fieldjob.index');
|
||||||
|
|
||||||
// field jobs assignment
|
// field jobs assignment
|
||||||
|
Route::middleware('permission:field-job')->group(function () {
|
||||||
Route::get('field-jobs', [FieldJobController::class, 'index'])->name('fieldjobs.index');
|
Route::get('field-jobs', [FieldJobController::class, 'index'])->name('fieldjobs.index');
|
||||||
Route::post('field-jobs/assign', [FieldJobController::class, 'assign'])->name('fieldjobs.assign');
|
Route::post('field-jobs/assign', [FieldJobController::class, 'assign'])->name('fieldjobs.assign');
|
||||||
Route::post('field-jobs/assign-bulk', [FieldJobController::class, 'assignBulk'])->name('fieldjobs.assign-bulk');
|
Route::post('field-jobs/assign-bulk', [FieldJobController::class, 'assignBulk'])->name('fieldjobs.assign-bulk');
|
||||||
|
|
@ -382,6 +383,8 @@
|
||||||
Route::post('settings/field-job', [FieldJobSettingController::class, 'store'])->name('settings.fieldjob.store');
|
Route::post('settings/field-job', [FieldJobSettingController::class, 'store'])->name('settings.fieldjob.store');
|
||||||
Route::put('settings/field-job/{setting}', [FieldJobSettingController::class, 'update'])->name('settings.fieldjob.update');
|
Route::put('settings/field-job/{setting}', [FieldJobSettingController::class, 'update'])->name('settings.fieldjob.update');
|
||||||
// settings / contract-configs
|
// settings / contract-configs
|
||||||
|
});
|
||||||
|
|
||||||
Route::get('settings/contract-configs', [ContractConfigController::class, 'index'])->name('settings.contractConfigs.index');
|
Route::get('settings/contract-configs', [ContractConfigController::class, 'index'])->name('settings.contractConfigs.index');
|
||||||
Route::post('settings/contract-configs', [ContractConfigController::class, 'store'])->name('settings.contractConfigs.store');
|
Route::post('settings/contract-configs', [ContractConfigController::class, 'store'])->name('settings.contractConfigs.store');
|
||||||
Route::put('settings/contract-configs/{config}', [ContractConfigController::class, 'update'])->name('settings.contractConfigs.update');
|
Route::put('settings/contract-configs/{config}', [ContractConfigController::class, 'update'])->name('settings.contractConfigs.update');
|
||||||
|
|
@ -397,7 +400,7 @@
|
||||||
Route::get('segments', [SegmentController::class, 'index'])->name('segments.index');
|
Route::get('segments', [SegmentController::class, 'index'])->name('segments.index');
|
||||||
Route::get('segments/{segment}', [SegmentController::class, 'show'])->name('segments.show');
|
Route::get('segments/{segment}', [SegmentController::class, 'show'])->name('segments.show');
|
||||||
|
|
||||||
Route::middleware("permission:manage-imports")->group( function () {
|
Route::middleware('permission:manage-imports')->group(function () {
|
||||||
// imports
|
// imports
|
||||||
Route::get('imports/create', [ImportController::class, 'create'])->name('imports.create');
|
Route::get('imports/create', [ImportController::class, 'create'])->name('imports.create');
|
||||||
Route::get('imports', [ImportController::class, 'index'])->name('imports.index');
|
Route::get('imports', [ImportController::class, 'index'])->name('imports.index');
|
||||||
|
|
|
||||||
52
tests/Feature/Phone/CaseCompletedTodayTest.php
Normal file
52
tests/Feature/Phone/CaseCompletedTodayTest.php
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\ClientCase;
|
||||||
|
use App\Models\Contract;
|
||||||
|
use App\Models\FieldJob;
|
||||||
|
use App\Models\FieldJobSetting;
|
||||||
|
use App\Models\User;
|
||||||
|
use Inertia\Testing\AssertableInertia as Assert;
|
||||||
|
|
||||||
|
it('shows contracts completed today for the user when completed mode is on', function () {
|
||||||
|
$user = User::factory()->create();
|
||||||
|
|
||||||
|
// Create a client case and a contract under it
|
||||||
|
$case = ClientCase::factory()->create();
|
||||||
|
$contract = Contract::factory()->create([
|
||||||
|
'client_case_id' => $case->id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Create field job setting (required FK)
|
||||||
|
$setting = FieldJobSetting::factory()->create();
|
||||||
|
|
||||||
|
// Create a completed field job for this user, for this contract, completed today
|
||||||
|
FieldJob::query()->create([
|
||||||
|
'field_job_setting_id' => $setting->id,
|
||||||
|
'assigned_user_id' => $user->id,
|
||||||
|
'user_id' => $user->id,
|
||||||
|
'contract_id' => $contract->id,
|
||||||
|
'assigned_at' => now()->subHour(),
|
||||||
|
'completed_at' => now()->subMinutes(5),
|
||||||
|
'cancelled_at' => null,
|
||||||
|
'priority' => false,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// In normal mode (no completed flag), the contract should not be listed (job is completed)
|
||||||
|
$this->actingAs($user)
|
||||||
|
->get(route('phone.case', ['client_case' => $case->uuid]))
|
||||||
|
->assertInertia(fn (Assert $page) => $page
|
||||||
|
->component('Phone/Case/Index')
|
||||||
|
->where('completed_mode', false)
|
||||||
|
->has('contracts', 0)
|
||||||
|
);
|
||||||
|
|
||||||
|
// With completed=1, it should be listed
|
||||||
|
$this->actingAs($user)
|
||||||
|
->get(route('phone.case', ['client_case' => $case->uuid, 'completed' => 1]))
|
||||||
|
->assertInertia(fn (Assert $page) => $page
|
||||||
|
->component('Phone/Case/Index')
|
||||||
|
->where('completed_mode', true)
|
||||||
|
->has('contracts', 1)
|
||||||
|
->where('contracts.0.uuid', $contract->uuid)
|
||||||
|
);
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue
Block a user