@nexxai
Hi, sure here is the ConsentFormController class:
<?php
namespace App\Http\Controllers\General;
use PDF;
use Carbon\Carbon;
use Ramsey\Uuid\Uuid;
use App\Models\Client;
use App\Components\Search\ConsentFilters;
use App\Components\Consent\ConsentNotification;
use App\Models\ConsentForm;
use App\Models\ConsentSignature;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class ConsentFormController extends Controller
{
const COUNSELOR_TYPE = 'counselor';
public function __construct(ConsentNotification $notification)
{
$this->notification = $notification;
}
/**
* This view will load in another layout file
* called "external". This view will be sent to a
* legal guardian via Twilio.
*
* documentID should be a uuid generated when doc is created
*
* @param string $documentID
* @return view
*/
public function document($documentID)
{
$document = ConsentForm::with(['signatures', 'client'])
->where('document_id', $documentID)
->where('current', true)
->where('expiry', '>', Carbon::now())
->first();
if (!$document) {
abort(404);
}
return view('general.clients.consent', [
'document' => $document
]);
}
/**
* This view will be the view that displays a thank message
* and a form to provide additional info for the client
* ie medical id, medical info, etc.
*
* @param string $documentID
* @return view
*/
public function documentOptionalInfo($documentID)
{
$document = ConsentForm::where('document_id', $documentID)
->where('current', true)
->where('expiry', '>', Carbon::now())
->first();
if ( ! $document ) {
abort(404);
}
if ( ! $this->checkIfComplete($document) ) {
return redirect()->back()->with('completeError', 'Both the parent and client must sign before completing');
}
return view('general.clients.consent_additional', [
'document' => $document
]);
}
/**
* Stores additional info to client table from the
* additional/thank you page.
*
* @param Request $request
* @param string $documentID
* @return response
*/
public function storeAdditionalInfo(Request $request, $documentID)
{
$document = ConsentForm::where('document_id', $documentID)
->where('current', true)
->first();
if ( ! $document ) {
abort(404);
}
$client = Client::findOrFail($document->client_id);
$client->has_medical = $request->input('insurance_type');
$client->consent_ssn_or_medical_number = $request->input('medical_number');
$client->save();
// add expiry. quick way to redirect back with message without
// 404-ing the page
$document->expiry = Carbon::now()->addSeconds(5);
$document->save();
return redirect()->back()->with('completeSuccess', 'Thank you for submitting! Your counselor should reach out to you shortly');
}
/**
* List of pending consent forms and their status
*
* only cousnelors can see this view
*
* @return view
*/
public function documentList(ConsentFilters $filters)
{
$documents = ConsentForm::filter($filters)->latest()->paginate(50);
return view('general.consent.list', ['documents' => $documents]);
}
/**
* Document for counselor to sign.
* Only counselor's can view and is auth'd
*
* @param string $documentID
* @return view
*/
public function documentCounselor($documentID)
{
$document = ConsentForm::where('document_id', $documentID)->first();
return view('general.consent.detail', ['document' => $document]);
}
/**
* Grabs details of a document if it exists, otherwise, it returns null or 404?
*
* @param int $clientID
* @return response
*/
public function documentDetails($clientID)
{
$document = ConsentForm::where('client_id', $clientID)
->where('current', true)
->latest() // get latest record in case there are multiple
->first();
return response()->json($document);
}
/**
* Grabs signature details for public consent view
*
* @param int $document
* @return response
*/
public function pubSignatureDetails($document)
{
$types = ConsentSignature::select('type')
->where('consent_form_id', $document)
->get();
return response()->json($types);
}
/**
* Generates new document for client
*
* @note: should this be in it's down service class method? ie ClientRepository?
* @param Request $request
* @return response
*/
public function findDocOrStore(Request $request)
{
// ajax request
// if doc exists, grab document, or acknowledge it's existance
// if not, generate unique uuid to track document
// send over client details
// create store document
// current === true -> means client has not been discharged
// check if client exists. if not, throw 404
$client = Client::findOrFail($request->get('client_id'));
$document = $this->findDocument($request->get('client_id'));
if ( !$document ) {
$uuid = Uuid::uuid4()->toString();
$consent = new ConsentForm;
$consent->document_id = $uuid;
$consent->client_id = $request->get('client_id');
$consent->expiry = Carbon::now()->addDays(5);
$consent->save();
$this->notification->notify($client, $consent->document_id);
return response()->json('consent form created', 201);
}
$this->notification->notify($client, $document->document_id);
// reset expiry
$document->expiry = Carbon::now()->addDays(5);
$document->save();
return response()->json($document);
}
/**
* Stores a signature from consent form document.
*
* @param Request $request
* @param int $documentID
* @return response
*/
public function sign(Request $request, $documentID)
{
// completed signature validation
$counselorSignature = $request->get('type') == self::COUNSELOR_TYPE;
if ($counselorSignature) {
$document = ConsentForm::findOrFail($documentID);
if ($this->checkIfComplete($document)) {
$document->completed = true;
$document->expiry = Carbon::now(); // just make sure link is expired
$document->save();
} else {
return response()->json('Could not complete form. Client and guardian both must sign', 400);
}
}
$signature = new ConsentSignature;
$signature->consent_form_id = $documentID;
$signature->type = $request->get('type');
$signature->signature = $request->get('signature');
$signature->created_at = Carbon::parse($request->get('created_at'));
$signature->save();
return response()->json('consent form signature created', 201);
}
public function retrieveSignature($documentID, $type)
{
return ConsentSignature::where('consent_form_id', $documentID)
->where('type', $type)
->first();
}
/**
* Removes a signature from a consent form.
*
* @param int $id the id of signature (ie from CounselorSignatureComponent.vue state).
* @return response
*/
public function removeSignature($id)
{
$signature = ConsentSignature::findOrFail($id);
// set form as incomplete status, then delete
$signature->document->completed = false;
$signature->document->save();
if(!is_null($signature)){
$signature->delete();
}
return response()->json('Signature has been removed from consent form');
}
public function pdf($id)
{
$doc = ConsentForm::findOrFail($id);
$clientSignature = ConsentSignature::where('consent_form_id', $doc->id)
->where('type', 'client')
->first();
$guardianSignature = ConsentSignature::where('consent_form_id', $doc->id)
->where('type', 'guardian')
->first();
$counselorSignature = ConsentSignature::where('consent_form_id', $doc->id)
->where('type', 'counselor')
->first();
$pdf = PDF::loadView('general.consent.pdf', [
'doc' => $doc,
'client_signature' => $clientSignature,
'guardian_signature' => $guardianSignature,
'counselor_signature' => $counselorSignature
]);
$name = 'consent_form_' . $doc->document_id . '.pdf';
return $pdf->download($name);
}
private function findDocument($clientID)
{
return ConsentForm::where('client_id', $clientID)
->where('completed', false)
->where('current', true)
->first();
}
public function checkIfComplete($doc)
{
$completedTypes = collect($doc->signatures)->map(function($signature) {
return $signature->type;
});
if ($completedTypes->contains('guardian') && $completedTypes->contains('client')) {
return true;
}
return false;
}
}
As for the routes, none of them are in a route group.
# Web.php
Route::get('/consent/form/{document}', [ConsentFormController::class, 'document'])
->name('client.consent.form');
Route::get('/consent/form/{document}/additional', [ConsentFormController::class, 'documentOptionalInfo'])
->name('client.consent.additional');
Route::post('/consent/form/{document}/additional/submit', [ConsentFormController::class, 'storeAdditionalInfo'])
->name('client.consent.additional.submit');
// External signature
Route::post('/consent/sign/{document}', [ConsentFormController::class, 'sign'])
->name('consent.sign');
// Signature type checks
Route::get('', [ConsentFormController::class, 'pubSignatureDetails'])
->name('consent.public.signature.types');
As for permissions, we do not have any setup. We just have 4 roles that we have setup which are admin,mdf, limited and superuser all using the guard_name: web. Also for the general.clients.consent blade template is a simple blade page with 2 Vue 3 signature components that the client has to sign.