465 lines
18 KiB
PHP
465 lines
18 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Models\ClientCase;
|
|
use App\Models\Contract;
|
|
use App\Models\Document;
|
|
use Illuminate\Support\Facades\Gate;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Exception;
|
|
use Illuminate\Database\QueryException;
|
|
use Illuminate\Http\Request;
|
|
use App\Http\Requests\StoreContractRequest;
|
|
use App\Http\Requests\UpdateContractRequest;
|
|
use Inertia\Inertia;
|
|
|
|
class ClientCaseContoller extends Controller
|
|
{
|
|
/**
|
|
* Display a listing of the resource.
|
|
*/
|
|
public function index(ClientCase $clientCase, Request $request)
|
|
{
|
|
return Inertia::render('Cases/Index', [
|
|
'client_cases' => $clientCase::with(['person'])
|
|
->when($request->input('search'), fn($que, $search) =>
|
|
$que->whereHas(
|
|
'person',
|
|
fn($q) => $q->where('full_name', 'ilike', '%' . $search . '%')
|
|
)
|
|
)
|
|
->where('active', 1)
|
|
->orderByDesc('created_at')
|
|
->paginate(15, ['*'], 'client-cases-page')
|
|
->withQueryString(),
|
|
'filters' => $request->only(['search'])
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Show the form for creating a new resource.
|
|
*/
|
|
public function create()
|
|
{
|
|
//
|
|
}
|
|
|
|
/**
|
|
* Store a newly created resource in storage.
|
|
*/
|
|
public function store(Request $request)
|
|
{
|
|
//
|
|
$cuuid = $request->input('client_uuid');
|
|
|
|
$client = \App\Models\Client::where('uuid', $cuuid)->firstOrFail();
|
|
|
|
if( isset($client->id) ){
|
|
|
|
\DB::transaction(function() use ($request, $client){
|
|
$pq = $request->input('person');
|
|
|
|
$person = $client->person()->create([
|
|
'nu' => rand(100000,200000),
|
|
'first_name' => $pq['first_name'],
|
|
'last_name' => $pq['last_name'],
|
|
'full_name' => $pq['full_name'],
|
|
'gender' => null,
|
|
'birthday' => null,
|
|
'tax_number' => $pq['tax_number'],
|
|
'social_security_number' => $pq['social_security_number'],
|
|
'description' => $pq['description'],
|
|
'group_id' => 2,
|
|
'type_id' => 1
|
|
]);
|
|
|
|
$person->addresses()->create([
|
|
'address' => $pq['address']['address'],
|
|
'country' => $pq['address']['country'],
|
|
'type_id' => $pq['address']['type_id']
|
|
]);
|
|
|
|
$person->phones()->create([
|
|
'nu' => $pq['phone']['nu'],
|
|
'country_code' => $pq['phone']['country_code'],
|
|
'type_id' => $pq['phone']['type_id']
|
|
]);
|
|
|
|
$person->clientCase()->create([
|
|
'client_id' => $client->id
|
|
]);
|
|
});
|
|
}
|
|
|
|
return to_route('client.show', $client);
|
|
}
|
|
|
|
public function storeContract(ClientCase $clientCase, StoreContractRequest $request)
|
|
{
|
|
|
|
\DB::transaction(function() use ($request, $clientCase){
|
|
|
|
// Create contract
|
|
$contract = $clientCase->contracts()->create([
|
|
'reference' => $request->input('reference'),
|
|
'start_date' => date('Y-m-d', strtotime($request->input('start_date'))),
|
|
'type_id' => $request->input('type_id'),
|
|
'description' => $request->input('description'),
|
|
]);
|
|
|
|
// Note: Contract config auto-application is handled in Contract model created hook.
|
|
|
|
// Optionally create/update related account amounts
|
|
$initial = $request->input('initial_amount');
|
|
$balance = $request->input('balance_amount');
|
|
if (!is_null($initial) || !is_null($balance)) {
|
|
$contract->account()->create([
|
|
'initial_amount' => $initial ?? 0,
|
|
'balance_amount' => $balance ?? 0,
|
|
]);
|
|
}
|
|
|
|
});
|
|
|
|
return to_route('clientCase.show', $clientCase);
|
|
}
|
|
|
|
public function updateContract(ClientCase $clientCase, String $uuid, UpdateContractRequest $request)
|
|
{
|
|
$contract = Contract::where('uuid', $uuid)->firstOrFail();
|
|
|
|
\DB::transaction(function() use ($request, $contract){
|
|
$contract->update([
|
|
'reference' => $request->input('reference'),
|
|
'type_id' => $request->input('type_id'),
|
|
'description' => $request->input('description'),
|
|
'start_date' => $request->filled('start_date') ? date('Y-m-d', strtotime($request->input('start_date'))) : $contract->start_date,
|
|
]);
|
|
|
|
$initial = $request->input('initial_amount');
|
|
$balance = $request->input('balance_amount');
|
|
if (!is_null($initial) || !is_null($balance)) {
|
|
$accountData = [
|
|
'initial_amount' => $initial ?? 0,
|
|
'balance_amount' => $balance ?? 0,
|
|
];
|
|
if ($contract->account) {
|
|
$contract->account->update($accountData);
|
|
} else {
|
|
$contract->account()->create($accountData);
|
|
}
|
|
}
|
|
|
|
});
|
|
|
|
return to_route('clientCase.show', $clientCase);
|
|
}
|
|
|
|
public function storeActivity(ClientCase $clientCase, Request $request) {
|
|
try {
|
|
$attributes = $request->validate([
|
|
'due_date' => 'nullable|date',
|
|
'amount' => 'nullable|decimal:0,4',
|
|
'note' => 'nullable|string',
|
|
'action_id' => 'exists:\App\Models\Action,id',
|
|
'decision_id' => 'exists:\App\Models\Decision,id',
|
|
'contract_uuid' => 'nullable|uuid',
|
|
]);
|
|
|
|
// Map contract_uuid to contract_id within the same client case, if provided
|
|
$contractId = null;
|
|
if (!empty($attributes['contract_uuid'])) {
|
|
$contract = $clientCase->contracts()->where('uuid', $attributes['contract_uuid'])->firstOrFail('id');
|
|
if ($contract) {
|
|
$contractId = $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,
|
|
]);
|
|
/*foreach ($activity->decision->events as $e) {
|
|
$class = '\\App\\Events\\' . $e->name;
|
|
event(new $class($clientCase));
|
|
}*/
|
|
|
|
logger()->info('Activity successfully inserted', $attributes);
|
|
return to_route('clientCase.show', $clientCase)->with('success', 'Successful created!');
|
|
} catch (QueryException $e) {
|
|
logger()->error('Database error occurred:', ['error' => $e->getMessage()]);
|
|
return back()->with('error', 'Failed to insert activity. ' . $e->getMessage());
|
|
} catch (Exception $e) {
|
|
logger()->error('An unexpected error occurred:', ['error' => $e->getMessage()]);
|
|
// Return a generic error response
|
|
return back()->with('error', 'An unexpected error occurred. Please try again later.');
|
|
}
|
|
|
|
}
|
|
|
|
public function deleteContract(ClientCase $clientCase, String $uuid, Request $request) {
|
|
$contract = Contract::where('uuid', $uuid)->firstOrFail();
|
|
|
|
\DB::transaction(function() use ($request, $contract){
|
|
$contract->delete();
|
|
});
|
|
|
|
return to_route('clientCase.show', $clientCase);
|
|
}
|
|
|
|
public function updateContractSegment(ClientCase $clientCase, string $uuid, Request $request)
|
|
{
|
|
$validated = $request->validate([
|
|
'segment_id' => ['required', 'integer', 'exists:segments,id'],
|
|
]);
|
|
|
|
$contract = $clientCase->contracts()->where('uuid', $uuid)->firstOrFail();
|
|
|
|
\DB::transaction(function () use ($contract, $validated) {
|
|
// Deactivate current active relation(s)
|
|
\DB::table('contract_segment')
|
|
->where('contract_id', $contract->id)
|
|
->where('active', true)
|
|
->update(['active' => false]);
|
|
|
|
// Attach or update the selected segment as active
|
|
$existing = \DB::table('contract_segment')
|
|
->where('contract_id', $contract->id)
|
|
->where('segment_id', $validated['segment_id'])
|
|
->first();
|
|
|
|
if ($existing) {
|
|
\DB::table('contract_segment')
|
|
->where('id', $existing->id)
|
|
->update(['active' => true, 'updated_at' => now()]);
|
|
} else {
|
|
$contract->segments()->attach($validated['segment_id'], ['active' => true, 'created_at' => now(), 'updated_at' => now()]);
|
|
}
|
|
});
|
|
|
|
return back()->with('success', 'Contract segment updated.');
|
|
}
|
|
|
|
public function attachSegment(ClientCase $clientCase, Request $request)
|
|
{
|
|
$validated = $request->validate([
|
|
'segment_id' => ['required', 'integer', 'exists:segments,id'],
|
|
'contract_uuid' => ['nullable', 'uuid'],
|
|
'make_active_for_contract' => ['sometimes', 'boolean'],
|
|
]);
|
|
|
|
\DB::transaction(function () use ($clientCase, $validated) {
|
|
// Attach segment to client case if not already attached
|
|
$attached = \DB::table('client_case_segment')
|
|
->where('client_case_id', $clientCase->id)
|
|
->where('segment_id', $validated['segment_id'])
|
|
->first();
|
|
if (!$attached) {
|
|
$clientCase->segments()->attach($validated['segment_id'], ['active' => true]);
|
|
} else if (!$attached->active) {
|
|
\DB::table('client_case_segment')
|
|
->where('id', $attached->id)
|
|
->update(['active' => true, 'updated_at' => now()]);
|
|
}
|
|
|
|
// Optionally make it active for a specific contract
|
|
if (!empty($validated['contract_uuid']) && ($validated['make_active_for_contract'] ?? false)) {
|
|
$contract = $clientCase->contracts()->where('uuid', $validated['contract_uuid'])->firstOrFail();
|
|
\DB::table('contract_segment')
|
|
->where('contract_id', $contract->id)
|
|
->where('active', true)
|
|
->update(['active' => false]);
|
|
|
|
$existing = \DB::table('contract_segment')
|
|
->where('contract_id', $contract->id)
|
|
->where('segment_id', $validated['segment_id'])
|
|
->first();
|
|
if ($existing) {
|
|
\DB::table('contract_segment')
|
|
->where('id', $existing->id)
|
|
->update(['active' => true, 'updated_at' => now()]);
|
|
} else {
|
|
$contract->segments()->attach($validated['segment_id'], ['active' => true, 'created_at' => now(), 'updated_at' => now()]);
|
|
}
|
|
}
|
|
});
|
|
|
|
return back()->with('success', 'Segment attached to case.');
|
|
}
|
|
|
|
public function storeDocument(ClientCase $clientCase, Request $request)
|
|
{
|
|
$validated = $request->validate([
|
|
'file' => 'required|file|max:25600|mimes:doc,docx,pdf,txt,csv,xls,xlsx,jpeg,png', // 25MB and allowed types
|
|
'name' => 'nullable|string|max:255',
|
|
'description' => 'nullable|string',
|
|
'is_public' => 'sometimes|boolean',
|
|
]);
|
|
|
|
$file = $validated['file'];
|
|
$disk = 'public';
|
|
$directory = 'cases/' . $clientCase->uuid . '/documents';
|
|
$path = $file->store($directory, $disk);
|
|
|
|
$doc = new Document([
|
|
'name' => $validated['name'] ?? pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME),
|
|
'description' => $validated['description'] ?? null,
|
|
'user_id' => optional($request->user())->id,
|
|
'disk' => $disk,
|
|
'path' => $path,
|
|
'file_name' => basename($path),
|
|
'original_name' => $file->getClientOriginalName(),
|
|
'extension' => $file->getClientOriginalExtension(),
|
|
'mime_type' => $file->getMimeType(),
|
|
'size' => $file->getSize(),
|
|
'checksum' => null,
|
|
'is_public' => (bool)($validated['is_public'] ?? false),
|
|
]);
|
|
|
|
$clientCase->documents()->save($doc);
|
|
|
|
// Generate preview immediately for Office docs to avoid first-view delay
|
|
$ext = strtolower($doc->extension ?? pathinfo($doc->original_name ?? $doc->file_name, PATHINFO_EXTENSION));
|
|
if (in_array($ext, ['doc','docx'])) {
|
|
\App\Jobs\GenerateDocumentPreview::dispatch($doc->id);
|
|
}
|
|
|
|
return back()->with('success', 'Document uploaded.');
|
|
}
|
|
|
|
public function viewDocument(ClientCase $clientCase, Document $document, Request $request)
|
|
{
|
|
// Ensure the document belongs to this client case
|
|
if ($document->documentable_type !== ClientCase::class || $document->documentable_id !== $clientCase->id) {
|
|
abort(404);
|
|
}
|
|
|
|
// Optional: add authz checks here (e.g., policies)
|
|
$disk = $document->disk ?: 'public';
|
|
|
|
// If a preview exists (e.g., PDF generated for doc/docx), stream that
|
|
$previewDisk = config('files.preview_disk', 'public');
|
|
if ($document->preview_path && Storage::disk($previewDisk)->exists($document->preview_path)) {
|
|
$stream = Storage::disk($previewDisk)->readStream($document->preview_path);
|
|
if ($stream === false) abort(404);
|
|
return response()->stream(function () use ($stream) { fpassthru($stream); }, 200, [
|
|
'Content-Type' => $document->preview_mime ?: 'application/pdf',
|
|
'Content-Disposition' => 'inline; filename="' . addslashes(($document->original_name ?: $document->file_name) . '.pdf') . '"',
|
|
'Cache-Control' => 'private, max-age=0, no-cache',
|
|
'Pragma' => 'no-cache',
|
|
]);
|
|
}
|
|
|
|
// If it's a DOC/DOCX and no preview yet, queue generation and show 202 Accepted
|
|
$ext = strtolower(pathinfo($document->original_name ?: $document->file_name, PATHINFO_EXTENSION));
|
|
if (in_array($ext, ['doc','docx'])) {
|
|
\App\Jobs\GenerateDocumentPreview::dispatch($document->id);
|
|
return response('Preview is being generated. Please try again shortly.', 202);
|
|
}
|
|
|
|
if (!Storage::disk($disk)->exists($document->path)) {
|
|
abort(404);
|
|
}
|
|
|
|
$stream = Storage::disk($disk)->readStream($document->path);
|
|
if ($stream === false) {
|
|
abort(404);
|
|
}
|
|
|
|
return response()->stream(function () use ($stream) {
|
|
fpassthru($stream);
|
|
}, 200, [
|
|
'Content-Type' => $document->mime_type ?: 'application/octet-stream',
|
|
'Content-Disposition' => 'inline; filename="' . addslashes($document->original_name ?: $document->file_name) . '"',
|
|
'Cache-Control' => 'private, max-age=0, no-cache',
|
|
'Pragma' => 'no-cache',
|
|
]);
|
|
}
|
|
|
|
public function downloadDocument(ClientCase $clientCase, Document $document, Request $request)
|
|
{
|
|
if ($document->documentable_type !== ClientCase::class || $document->documentable_id !== $clientCase->id) {
|
|
abort(404);
|
|
}
|
|
$disk = $document->disk ?: 'public';
|
|
if (!Storage::disk($disk)->exists($document->path)) {
|
|
abort(404);
|
|
}
|
|
$name = $document->original_name ?: $document->file_name;
|
|
$stream = Storage::disk($disk)->readStream($document->path);
|
|
if ($stream === false) {
|
|
abort(404);
|
|
}
|
|
return response()->stream(function () use ($stream) {
|
|
fpassthru($stream);
|
|
}, 200, [
|
|
'Content-Type' => $document->mime_type ?: 'application/octet-stream',
|
|
'Content-Disposition' => 'attachment; filename="' . addslashes($name) . '"',
|
|
'Cache-Control' => 'private, max-age=0, no-cache',
|
|
'Pragma' => 'no-cache',
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Display the specified resource.
|
|
*/
|
|
public function show(ClientCase $clientCase)
|
|
{
|
|
$case = $clientCase::with([
|
|
'person' => fn($que) => $que->with(['addresses', 'phones'])
|
|
])->where('active', 1)->findOrFail($clientCase->id);
|
|
|
|
$types = [
|
|
'address_types' => \App\Models\Person\AddressType::all(),
|
|
'phone_types' => \App\Models\Person\PhoneType::all()
|
|
];
|
|
|
|
return Inertia::render('Cases/Show', [
|
|
'client' => $case->client()->with('person', fn($q) => $q->with(['addresses', 'phones']))->firstOrFail(),
|
|
'client_case' => $case,
|
|
'contracts' => $case->contracts()
|
|
->with(['type', 'account', 'objects', 'segments:id,name'])
|
|
->orderByDesc('created_at')->get(),
|
|
'activities' => $case->activities()->with(['action', 'decision', 'contract:id,uuid,reference'])
|
|
->orderByDesc('created_at')
|
|
->paginate(20, ['*'], 'activities'),
|
|
'documents' => $case->documents()->orderByDesc('created_at')->get(),
|
|
'contract_types' => \App\Models\ContractType::whereNull('deleted_at')->get(),
|
|
'actions' => \App\Models\Action::with('decisions')->get(),
|
|
'types' => $types,
|
|
'segments' => $case->segments()->wherePivot('active', true)->get(['segments.id','segments.name']),
|
|
'all_segments' => \App\Models\Segment::query()->where('active', true)->get(['id','name'])
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Show the form for editing the specified resource.
|
|
*/
|
|
public function edit(string $id)
|
|
{
|
|
//
|
|
}
|
|
|
|
/**
|
|
* Update the specified resource in storage.
|
|
*/
|
|
public function update(Request $request, string $id)
|
|
{
|
|
//
|
|
}
|
|
|
|
/**
|
|
* Remove the specified resource from storage.
|
|
*/
|
|
public function destroy(string $id)
|
|
{
|
|
//
|
|
}
|
|
}
|