diff --git a/app/Http/Controllers/Admin/PackageController.php b/app/Http/Controllers/Admin/PackageController.php index f507c4f..7b946eb 100644 --- a/app/Http/Controllers/Admin/PackageController.php +++ b/app/Http/Controllers/Admin/PackageController.php @@ -26,6 +26,14 @@ public function index(Request $request): Response $packages = Package::query() ->latest('id') ->paginate(25); + + return Inertia::render('Admin/Packages/Index', [ + 'packages' => $packages, + ]); + } + + public function create(Request $request): Response + { // Minimal lookups for create form (active only) $profiles = \App\Models\SmsProfile::query() ->where('active', true) @@ -58,8 +66,7 @@ public function index(Request $request): Response }) ->values(); - return Inertia::render('Admin/Packages/Index', [ - 'packages' => $packages, + return Inertia::render('Admin/Packages/Create', [ 'profiles' => $profiles, 'senders' => $senders, 'templates' => $templates, @@ -312,7 +319,7 @@ public function contracts(Request $request, PhoneSelector $selector): \Illuminat $request->validate([ 'segment_id' => ['nullable', 'integer', 'exists:segments,id'], 'q' => ['nullable', 'string'], - 'per_page' => ['nullable', 'integer', 'min:1', 'max:100'], + 'client_id' => ['nullable', 'integer', 'exists:clients,id'], 'only_mobile' => ['nullable', 'boolean'], 'only_validated' => ['nullable', 'boolean'], @@ -323,7 +330,7 @@ public function contracts(Request $request, PhoneSelector $selector): \Illuminat ]); $segmentId = $request->input('segment_id') ? (int) $request->input('segment_id') : null; - $perPage = (int) ($request->input('per_page') ?? 25); + $query = Contract::query() ->with([ @@ -390,9 +397,9 @@ public function contracts(Request $request, PhoneSelector $selector): \Illuminat }); } - $contracts = $query->paginate($perPage); + $contracts = $query->get(); - $data = collect($contracts->items())->map(function (Contract $contract) use ($selector) { + $data = collect($contracts)->map(function (Contract $contract) use ($selector) { $person = $contract->clientCase?->person; $selected = $person ? $selector->selectForPerson($person) : ['phone' => null, 'reason' => 'no_person']; $phone = $selected['phone']; @@ -431,13 +438,7 @@ public function contracts(Request $request, PhoneSelector $selector): \Illuminat }); return response()->json([ - 'data' => $data, - 'meta' => [ - 'current_page' => $contracts->currentPage(), - 'last_page' => $contracts->lastPage(), - 'per_page' => $contracts->perPage(), - 'total' => $contracts->total(), - ], + 'data' => $data ]); } diff --git a/app/Http/Controllers/ClientCaseContoller.php b/app/Http/Controllers/ClientCaseContoller.php index 0c8f1fc..6ec2aa5 100644 --- a/app/Http/Controllers/ClientCaseContoller.php +++ b/app/Http/Controllers/ClientCaseContoller.php @@ -311,6 +311,9 @@ public function storeActivity(ClientCase $clientCase, Request $request) 'action_id' => 'exists:\App\Models\Action,id', 'decision_id' => 'exists:\App\Models\Decision,id', 'contract_uuid' => 'nullable|uuid', + 'contract_uuids' => 'nullable|array', + 'contract_uuids.*' => 'uuid', + 'create_for_all_contracts' => 'nullable|boolean', 'phone_view' => 'nullable|boolean', 'send_auto_mail' => 'sometimes|boolean', 'attachment_document_ids' => 'sometimes|array', @@ -318,61 +321,102 @@ public function storeActivity(ClientCase $clientCase, Request $request) ]); $isPhoneView = $attributes['phone_view'] ?? false; + $createForAll = $attributes['create_for_all_contracts'] ?? false; + $contractUuids = $attributes['contract_uuids'] ?? []; - // Map contract_uuid to contract_id within the same client case, if provided - $contractId = null; - if (! empty($attributes['contract_uuid'])) { + // Determine which contracts to process + $contractIds = []; + if ($createForAll && !empty($contractUuids)) { + // Get all contract IDs from the provided UUIDs + $contracts = Contract::withTrashed() + ->whereIn('uuid', $contractUuids) + ->where('client_case_id', $clientCase->id) + ->get(); + $contractIds = $contracts->pluck('id')->toArray(); + } elseif (!empty($contractUuids) && isset($contractUuids[0])) { + // Single contract mode + $contract = Contract::withTrashed() + ->where('uuid', $contractUuids[0]) + ->where('client_case_id', $clientCase->id) + ->first(); + if ($contract) { + $contractIds = [$contract->id]; + } + } elseif (!empty($attributes['contract_uuid'])) { + // Legacy single contract_uuid support $contract = Contract::withTrashed() ->where('uuid', $attributes['contract_uuid']) ->where('client_case_id', $clientCase->id) ->first(); if ($contract) { - // Archived contracts are allowed: link activity regardless of active flag - $contractId = $contract->id; + $contractIds = [$contract->id]; } } - // Create activity - $row = $clientCase->activities()->create([ - 'due_date' => $attributes['due_date'] ?? null, - 'amount' => $attributes['amount'] ?? null, - 'note' => $attributes['note'] ?? null, - 'action_id' => $attributes['action_id'], - 'decision_id' => $attributes['decision_id'], - 'contract_id' => $contractId, - ]); - - if ($isPhoneView && $contractId) { - $fieldJob = $contract->fieldJobs() - ->whereNull('completed_at') - ->whereNull('cancelled_at') - ->where('assigned_user_id', \Auth::id()) - ->orderByDesc('id') - ->first(); - - if ($fieldJob) { - $fieldJob->update([ - 'added_activity' => true, - 'last_activity' => $row->created_at, - ]); - - } + // If no contracts specified, create a single activity without contract + if (empty($contractIds)) { + $contractIds = [null]; } - logger()->info('Activity successfully inserted', $attributes); + $createdActivities = []; + $sendFlag = (bool) ($attributes['send_auto_mail'] ?? true); + + // Disable auto mail if creating activities for multiple contracts + if ($sendFlag && count($contractIds) > 1) { + $sendFlag = false; + logger()->info('Auto mail disabled: multiple contracts selected', ['contract_count' => count($contractIds)]); + } - // Auto mail dispatch (best-effort) - try { - $sendFlag = (bool) ($attributes['send_auto_mail'] ?? true); - $row->load(['decision', 'clientCase.client.person', 'clientCase.person', 'contract']); - // Filter attachments to those belonging to the selected contract - $attachmentIds = collect($attributes['attachment_document_ids'] ?? []) - ->filter() - ->map(fn ($v) => (int) $v) - ->values(); - $validAttachmentIds = collect(); - if ($attachmentIds->isNotEmpty() && $contractId) { - $validAttachmentIds = Document::query() + foreach ($contractIds as $contractId) { + // Create activity + $row = $clientCase->activities()->create([ + 'due_date' => $attributes['due_date'] ?? null, + 'amount' => $attributes['amount'] ?? null, + 'note' => $attributes['note'] ?? null, + 'action_id' => $attributes['action_id'], + 'decision_id' => $attributes['decision_id'], + 'contract_id' => $contractId, + ]); + + $createdActivities[] = $row; + + if ($isPhoneView && $contractId) { + $contract = Contract::find($contractId); + if ($contract) { + $fieldJob = $contract->fieldJobs() + ->whereNull('completed_at') + ->whereNull('cancelled_at') + ->where('assigned_user_id', \Auth::id()) + ->orderByDesc('id') + ->first(); + + if ($fieldJob) { + $fieldJob->update([ + 'added_activity' => true, + 'last_activity' => $row->created_at, + ]); + } + } + } + + logger()->info('Activity successfully inserted', array_merge($attributes, ['contract_id' => $contractId])); + + // Auto mail dispatch (best-effort) + try { + $row->load(['decision', 'clientCase.client.person', 'clientCase.person', 'contract']); + // Filter attachments to those belonging to the selected contract + $attachmentIds = collect($attributes['attachment_document_ids'] ?? []) + ->filter() + ->map(fn ($v) => (int) $v) + ->values(); + $validAttachmentIds = collect(); + if ($attachmentIds->isNotEmpty() && $contractId) { + $validAttachmentIds = Document::query() + ->where('documentable_type', Contract::class) + ->where('documentable_id', $contractId) + ->whereIn('id', $attachmentIds) + ->pluck('id'); + $validAttachmentIds = Document::query() ->where('documentable_type', Contract::class) ->where('documentable_id', $contractId) ->whereIn('id', $attachmentIds) @@ -383,19 +427,25 @@ public function storeActivity(ClientCase $clientCase, Request $request) ]); if (($result['skipped'] ?? null) === 'missing-contract' && $sendFlag) { // If template requires contract and user attempted to send, surface a validation message - return back()->with('warning', 'Email not queued: required contract is missing for the selected template.'); + logger()->warning('Email not queued: required contract is missing for the selected template.'); } if (($result['skipped'] ?? null) === 'no-recipients' && $sendFlag) { - return back()->with('warning', 'Email not queued: no eligible client emails to receive auto mails.'); + logger()->warning('Email not queued: no eligible client emails to receive auto mails.'); } } catch (\Throwable $e) { // Do not fail activity creation due to mailing issues logger()->warning('Auto mail dispatch failed: '.$e->getMessage()); } + } + + $activityCount = count($createdActivities); + $successMessage = $activityCount > 1 + ? "Successfully created {$activityCount} activities!" + : 'Successfully created activity!'; // Stay on the current page (desktop or phone) instead of forcing a redirect to the desktop route. // Use 303 to align with Inertia's recommended POST/Redirect/GET behavior. - return back(303)->with('success', 'Successful created!')->with('flash_method', 'POST'); + return back(303)->with('success', $successMessage)->with('flash_method', 'POST'); } catch (QueryException $e) { logger()->error('Database error occurred:', ['error' => $e->getMessage()]); diff --git a/resources/js/Components/DataTable/DataTableNew2.vue b/resources/js/Components/DataTable/DataTableNew2.vue index 0c61595..74067be 100644 --- a/resources/js/Components/DataTable/DataTableNew2.vue +++ b/resources/js/Components/DataTable/DataTableNew2.vue @@ -462,6 +462,17 @@ function keyOf(row) { return row[props.rowKey]; return row?.uuid ?? row?.id ?? Math.random().toString(36).slice(2); } + +// Expose methods for parent component +defineExpose({ + clearSelection: () => { + table.resetRowSelection(); + rowSelection.value = {}; + }, + getSelectedRows: () => { + return Object.keys(rowSelection.value).filter((key) => rowSelection.value[key]); + }, +});