Overview
- Introduction
- Pending Review
- Awaiting Conditional Acceptance
- Conditional Acceptance
- Awaiting Payment
- Paid
- Awaiting Final Acceptance
- Final Acceptance
- Awaiting Student Card
- Completed
Introduction
The application model is the main model of the application, it's the aggregate. the model carries a lot of the logic behind the application life-cycle.
Pending Review
It's the initial status of the application as it's created, on this status the application got assigned to CL1 along
with creation of the application. All action can be took on the application logic is on action class
within App/Actions/Application
namespace as every class will do one action within the application
$nextInsertCL = $this->choseCl1($application);if($nextInsertCL){ $application->assigned_to = $nextInsertCL; $application->cl1 = $nextInsertCL; $assignUserCL = new AssignUserCL; $assignUserCL->user_id = $nextInsertCL; $assignUserCL->type = 1; $assignUserCL->date = \Carbon\Carbon::now(); $assignUserCL->save();}
On this status their a few action can be done to the application by the CL1.
- Move the application to the next step of the cycle (Awaiting Conditional Acceptance) The action for the this status is on the
- Mark the application as registered before and move to the status (Already Registered)
- Mark the application as Awaiting student to get more info from the student This logic is within the
AwaitingStudentAction
$this->application->assigned_to = $this->application->created_by;$this->application->triggerAwaitngStudent();return back();
The code above assign the application to the creator and change the status to Awaiting Student
and return back to the
application view.
triggerAwaitngStudent()
is located with the main model of the application.
if ($this->status == 'student_wait') { $this->returnFromAwaitingStudent();} $message = 'Application Status has changed from ' . self::getStatusInfo($this->status)['name'] . ' to Awaiting Student'; $this->status = 'student_wait'; $this->recordHistory($message); $this->save();
on this method the status got changed, and we record this action on the application history before we save it.
Awaiting Conditional Acceptance
On this status application got assigned to CL2 as an assignee, on this status of the application the CL2 should upload
the conditional acceptance and send it back to the student then the application will move the the next status (
Conditional Acceptance)
The logic for this happens within 2 actions.
The first is within the UploadingConditionalAcceptanceAction
$this->application->accepted_at = now();$this->application->recordHistory('Conditional acceptance has been uploaded', createdLabel: $this->payload->createdLabel);$this->application->save(); if ($this->application->hasIntegration() && $this->application->school->supportsOCR()) { $fields = $this->application->annotateConditionalLetter(); $payload = PayloadDataObject::fromArray(['user' => $this->application->creator, ...$fields]); (new SaveConditionalAnnotationsAction(application: $this->application, payload: $payload))->handle();}
here the application registered when the conditional acceptance letter uploaded and record it to the history and save
the application.
conditionally in some cases we annotate the conditional letter and extract some info from it and delegate to another
action. we will discuss this later.
The other action taking place is SendMailAction
, This action handle the logic for all the status has mail sending
and change the status to respective status. Below the logic for the conditional acceptance only
if ($this->application->checkStatus('cacceptance')) { $this->attachFiles($this->application->conditional_acceptance, $attachments, 'Conditional acceptance file ');}// every status has a handler within this class here we assign application to the actorspublic function conditionalAcceptanceHandler(){ $message = 'The conditional acceptance sent to ' . $this->application->student->name; $this->application->follow = 1; $this->application->status = 'payment_wait'; $this->application->assigned_to = $this->application->created_by; $this->application->updated_by = $this->payload->user->id; $this->application->recordHistory($message, createdLabel: $this->payload->createdLabel); $this->application->save();}// Sending the mail to queue to sent to the studentQueue::push( EmailQueue::class, [ "html" => $template, "subject" => $this->payload->title, "to_email" => $messageTo, "cc" => $messageCC ?: "", "from_email" => $messageFrom[0], "from_name" => $messageFrom[1], "sender_email" => $messageFrom[0], "sender_name" => $messageFrom[1], "bcc" => $messageBCC, "reply_to" => $messageReplyTo[0], "reply_to_name" => $messageReplyTo[1], "attachments" => $attachments, ], "emails"); return back();
Conditional Acceptance
When the application is on the conditional acceptance status, it means we received the condition acceptance from the
institution,
and it can be sent to the student. the assignee for this status is the creator of the application. The creator will make
sure the acceptance is valid
and can be sent to the student and do this action. this action will be handled by the method onSendmail
function onSendmail() { $payload = PayloadDataObject::fromArray(['user' => user(), ...post()]); return (new SendMailAction(application:null, payload: $payload))->handle();}
this method will delegate the logic to the SendMailAction
public function handle(){ $currentUser = $this->payload->user; $student = $this->application->student; $institution = $this->application->school; if (!$this->application->creator) { return response()->json("There is no creator for this application", 422); } if (!$student->creator) { return response("Please change or add creator to the student", 400); } $message = $this->payload->message; $template = 'zainab.simplecontact::mail.notification'; $isSendOutOfSystem = $this->payload->is_omit_email_to_student == 'yes'; $attachments = []; if ($this->payload->to == 'school') { if (!$this->application->cl2) { return response("CL2 is missing and a CL2 must be assigned before email can be send", 422); } $messageCC = $institution->email_cc; $email = $institution->email; if ($this->payload->type == 'sent') { $filesLookup = [ 'Diploma file ' => $student->diploma, 'Transcript file ' => $student->transcript, 'Passport file ' => $student->passport, 'Skills file ' => $student->skills, 'Motivation Letter file ' => $student->ml, 'Recommendation Letter file ' => $student->rl, 'Others file' => $student->other, 'Blue Card file' => $student->blue_card, 'T.C file' => $student->tc, 'Course description file' => $student->course_description, ]; foreach ($filesLookup as $label => $files) { $this->attachFiles($files, $attachments, $label); } } elseif ($this->payload->type == 'paid' || $this->application->checkStatus('paid')) { $this->attachFiles($this->application->payment_receipt, $attachments, 'Payment Receipt file '); } } elseif ($this->payload->to == 'student') { $messageCC = "education@oktamam.com"; $email = $student->email; if ($this->application->checkStatus('cacceptance')) { $this->attachFiles($this->application->conditional_acceptance, $attachments, 'Conditional acceptance file '); } if ($this->application->checkStatus('refused')) { $this->attachFiles($this->application->refused, $attachments, 'Refused file '); } if ($this->application->checkStatus('facceptance')) { $this->attachFiles($this->application->final_acceptance, $attachments, 'Final acceptance file '); } } else { $email = $student->email; } $template = MailTemplate::where('code', 'application-email-template')->first(); $vars = ['message' => $message]; $template = Twig::parse($template->content_html, $vars); if ($this->application->creator->hasGroup([5, 20]) && $currentUser->hasGroup([5, 20])) { $noTemplate = MailTemplate::where('code', 'application-no-template')->first(); $vars = [ 'message' => $message, 'current_user' => $currentUser, ]; $noTemplate = Twig::parse($noTemplate->content_html, $vars); } $messageFrom = [$currentUser->email, $currentUser->name]; $messageTo = $email; $messageBCC = ['education@oktamam.com']; $messageReplyTo = [$currentUser->email, $currentUser->name]; if ($this->payload->to == 'student') { if ($this->application->creator->hasGroup([5, 20]) || isset($student->agent)) { $messageTo = $student->email; if ($currentUser->hasGroup([5, 20])) { $messageFrom = $messageFrom; $messageReplyTo = $messageReplyTo; $messageCC = ""; $messageBCC = $messageBCC; $template = $noTemplate; } else { $messageFrom = ["education@oktamam.com", "OK TAMAM"]; $messageReplyTo = [$currentUser->email, $currentUser->name]; $messageCC = "education@oktamam.com"; $messageBCC = []; } } elseif (!$student->creator->groupOf(5) && !$this->application->creator->groupOf(5) && !$currentUser->groupOf(5)) { $messageFrom = ["education@oktamam.com", "OK TAMAM"]; $messageReplyTo = [$currentUser->email, $currentUser->name]; $messageTo = $student->email; $messageCC = "education@oktamam.com"; $messageBCC = []; } } if ($this->application->checkStatus('pending')) { $this->pendingHandler(); } elseif ($this->application->checkStatus('cacceptance')) { $this->conditionalAcceptanceHandler(); } elseif ($this->application->checkStatus('refused')) { $this->refusedHandler(); } elseif ($this->application->checkStatus('paid')) { $this->paidHandler(); } elseif ($this->application->checkStatus('facceptance')) { $this->finalAcceptanceHandler(); } if ($isSendOutOfSystem || !$this->payload->title) return back(); Queue::push( EmailQueue::class, [ "html" => $template, "subject" => $this->payload->title, "to_email" => $messageTo, "cc" => $messageCC ?: "", "from_email" => $messageFrom[0], "from_name" => $messageFrom[1], "sender_email" => $messageFrom[0], "sender_name" => $messageFrom[1], "bcc" => $messageBCC, "reply_to" => $messageReplyTo[0], "reply_to_name" => $messageReplyTo[1], "attachments" => $attachments, ], "emails" ); return back();}
This will validate the data and check if it should send the application to the student or the institution. this action go triggered from different statuses. this will attach the conditional acceptance and send a mail to student with it
// Attach the conditionalif ($this->application->checkStatus('cacceptance')) { $this->attachFiles($this->application->conditional_acceptance, $attachments, 'Conditional acceptance file ');}
// Queue the mail which will be sentQueue::push( EmailQueue::class, [ "html" => $template, "subject" => $this->payload->title, "to_email" => $messageTo, "cc" => $messageCC ?: "", "from_email" => $messageFrom[0], "from_name" => $messageFrom[1], "sender_email" => $messageFrom[0], "sender_name" => $messageFrom[1], "bcc" => $messageBCC, "reply_to" => $messageReplyTo[0], "reply_to_name" => $messageReplyTo[1], "attachments" => $attachments, ], "emails");
Awaiting Payment
When application is on the awaiting payment status, it means that the application got sent to the student, and thus it
indicates the status on the system as we wait for the payment
receipt from the student.
The assignee of the application while the application on this status will be the creator of the application.
The action can took place by assignee is uploading the payment receipt to the system and move the application to the
next status.
This will trigger the method below from the application.htm
function onUploadPaymentReceipt(){ $payload = PayloadDataObject::fromArray(['user' => user(), ...post()]); (new UploadingPaymentReceiptAction(application:null, payload: $payload))->handle();}
this method all it does actually is delegate the logic to another action, in this case it's
UploadingPaymentReceiptAction
. this action isn't do much, it records to history and save the application sate.
when the uploading the done a button (Extract Data) on the view for the next action.
This button sends an ajax request for system which handled by the method below
function onPaymentReceiptAnnotation(){ $application = Application::find($this->param('applicationId')); $result = $application->annotatePaymentReceipt(); return ['receiptDate' => $result['receiptDate']];}
the result returned of this method is the date which is written on the receipt. under the hood this is a complex process as it sends the uploading receipt to an OCR service. below is logic behind that process
$annotations = Annotator::annotate($this->payment_receipt);$receiptDate = now();try { if (is_array($annotations)) { if ($annotations && $annotations[0]) { $annotationResult = explode(PHP_EOL, $annotations[0]->getText()); $extractedText = implode(' ', $annotationResult); } else { $extractedText = false; } } else { $extractedText = $annotations; } if ($extractedText) { $extractDatePattern = "/[0-9]{1,2}\\/[0-9]{1,2}\\/[0-9]{4}/"; preg_match_all($extractDatePattern, $extractedText, $matches); if (count($matches[0]) > 0) { $receiptDate = strtotime($matches[0][0]) ?: null; $receiptDate = date('d/m/Y', $receiptDate); } } return ['receiptDate' => $receiptDate];} catch (\Exception $e) { return ['receiptDate' => carbon(date("d/m/Y"), 'd/m/Y')];}
This will pass the payment receipt to the OCR
service and extract the text from it and will match the extracted text
with regex to match a date and return it wrapped in a Carbon instance.
after this request a panel will show with the receipt date extracted and pre-filled on the input on the panel
the next action get called when the user hits the button (Correct files uploaded) and this action will move the
application
to the next step, the method handle this action is onStudentReply
.
function onStudentreply(){ $application = Application::find($this->param('applicationId')); $receiptDate = post('receiptDate'); if(user()->hasGroup([5, 20])){ $result = $application->annotatePaymentReceipt(); $receiptDate = $result ? $result['receiptDate'] : carbon(date("d/m/Y"), 'd/m/Y'); } try{ if($receiptDate){ $receiptDate = carbon($receiptDate, 'd/m/Y'); $today = carbon(date("d/m/Y"), 'd/m/Y'); if(($receiptDate > $today || $receiptDate < $application->accepted_at)){ if(!user()->hasGroup([5, 20])){ return response('Receipt date out of range', 422); }else{ $receiptDate = carbon(date("d/m/Y"), 'd/m/Y'); } } } }catch(Exception $e){ $receiptDate = carbon(date("d/m/Y"), 'd/m/Y'); } $payload = PayloadDataObject::fromArray(['user' => user(), ...post(), 'extractedReceiptDate' => $receiptDate]); (new ReplyingToStudentAction(application:null, payload: $payload))->handle(); return Redirect::to('dashboard/student/'.$this->param('studentId').'/application/'.$this->param('applicationId').'/view');}
this will validate the receipt date and pass the data to ReplyingToStudentAction
, which handle most of the logic
//ReplyingToStudentActionpublic function handle(){ $this->application->status = 'paid'; if ($this->payload->type == 'registration') { if ($this->application->card_wait->count() == 0) { return response('Please upload the Student Card', 422); } $this->application->status = 'registration'; $this->application->completed_at = now(); $this->application->assigned_to = $this->application->created_by; $this->application->recordHistory('Registration on site has been confirmed', createdLabel: $this->payload->createdLabel); $this->application->follow = 0; $this->application->updated_at = now(); $this->application->updated_by = $this->payload->user->id; $this->application->update(); } if ($this->payload->type == 'paid') { if ($this->application->payment_receipt->count() == 0) { return response('Please upload the Payment Receipt', 422); } $this->application->status = 'paid'; $this->application->changeAssignee('cl2'); $this->application->recordHistory('The application status has been changed to be paid', createdLabel: $this->payload->createdLabel); $this->application->follow = 0; $this->application->updated_at = now(); $this->application->updated_by = $this->payload->user->id; $this->application->update(); session()->flash('statusChanged', true); $this->mailIfApplicable(); }}
This action actually responsible for handle most of the statues which send a mail to the student.
on this case the block where we check for the payload type match with paid
is the block which get executed
it change the assignee of the application to the cl2 and record that to history and update the status to paid and send
a mail to the institution with the payment receipt attached.
Paid
When application reach this status, it means that the student has uploaded the payment receipt and now waiting for the final acceptance.
on this status the assignee will be the CL2, he will review the payment receipt and send it through the system to the institution.
this action will be handled by the onSendMail
.
function onSendmail() { $payload = PayloadDataObject::fromArray(['user' => user(), ...post()]); return (new SendMailAction(application:null, payload: $payload))->handle();}
this method will delegate the logic to the SendMailAction
.
// Attach the payment receiptif ($this->payload->type == 'paid' || $this->application->checkStatus('paid')) { $this->attachFiles($this->application->payment_receipt, $attachments, 'Payment Receipt file ');}
// Queue the mail which will be sentQueue::push( EmailQueue::class, [ "html" => $template, "subject" => $this->payload->title, "to_email" => $messageTo, "cc" => $messageCC ?: "", "from_email" => $messageFrom[0], "from_name" => $messageFrom[1], "sender_email" => $messageFrom[0], "sender_name" => $messageFrom[1], "bcc" => $messageBCC, "reply_to" => $messageReplyTo[0], "reply_to_name" => $messageReplyTo[1], "attachments" => $attachments, ], "emails");
this status use the same handler and action as the conditional acceptance, this action will change the status to awaiting final acceptance
and push a mail to the queue.
Awaiting Final Acceptance
On this status the application has been paid, the payment receipt got sent to the institution and now waiting for the final acceptance form the
institution. on this status, the assignee upload the final acceptance and move the application to the next status
this action handled by the method onUploadFinalAcceptance
.
//onUploadFinalAcceptancefunction onUploadFinalAcceptance(){ $payload = PayloadDataObject::fromArray(['user' => user(), ...post()]); (new UploadingFinalAcceptanceAction(application:null, payload: $payload))->handle();}
this delegates to the UploadingFinalAcceptanceAction
.
//UploadingFinalAcceptanceActionpublic function handle(){ $this->application->final_at = now(); $this->application->recordHistory('Final acceptance has been uploaded', createdLabel: $this->payload->createdLabel); $this->application->save(null, $this->payload->_session_key);}
this will record the action to history and save the application along with the date of uploading the final acceptance
Final Acceptance
On this status the final acceptance has been uploaded and attached with application, now it will get sent to the student and the application will be moved to the
next status. the assignee for the application here will be the creator of the application.
the method handling this is onSendMail
. as we mentioned before this method will handle most of the statuses including sending
a mail, either to student or the institution.
function onSendmail() { $payload = PayloadDataObject::fromArray(['user' => user(), ...post()]); return (new SendMailAction(application:null, payload: $payload))->handle();}
this method will delegate the logic to the SendMailAction
.
// Attach the final acceptanceif ($this->application->checkStatus('facceptance')) { $this->attachFiles($this->application->final_acceptance, $attachments, 'Final acceptance file ');}
// Queue the mail which will be sentQueue::push( EmailQueue::class, [ "html" => $template, "subject" => $this->payload->title, "to_email" => $messageTo, "cc" => $messageCC ?: "", "from_email" => $messageFrom[0], "from_name" => $messageFrom[1], "sender_email" => $messageFrom[0], "sender_name" => $messageFrom[1], "bcc" => $messageBCC, "reply_to" => $messageReplyTo[0], "reply_to_name" => $messageReplyTo[1], "attachments" => $attachments, ], "emails");
Awaiting Student Card
On this status we are waiting to receive the student card and upload it application, the assignee here will be the creator of the application he will upload the student card and move the application to the next status.
function onUploadPaymentReceipt(){ $payload = PayloadDataObject::fromArray(['user' => user(), ...post()]); (new UploadingPaymentReceiptAction(application:null, payload: $payload))->handle();}
the method above get called when uploading the student card, it delegates to UploadingPaymentReceiptAction
public function handle(){ $this->application->recordHistory('Payment receipt has been uploaded', createdLabel: $this->payload->createdLabel); $this->application->save();}
this will record the action to the history and saves the application.
When the creator hits (Register the student), this will get handled by onRegisterstudent
.
function onRegisterstudent(){ $payload = PayloadDataObject::fromArray(['user' => user(), ...post()]); (new RegisteringStudentAction(application:null, payload: $payload))->handle(); return Redirect::to('dashboard/student/'.$this->param('studentId').'/application/'.$this->param('applicationId').'/view');}
this will delegate to RegisteringStudentAction
.
// RegisteringStudentAction.phppublic function handle(){ $this->application->status = 'registration'; $this->application->recordHistory('Registration on site has been confirmed', createdLabel: $this->payload->createdLabel); $this->application->follow = 0; $this->application->closed = 1; $this->application->updated_at = now(); $this->application->updated_by = $this->payload->user->id; $this->application->assigned_to = $this->application->created_by; $this->application->completed_at = now(); $this->application->save(); if ($this->application->status == 'registration') { // Completed (new CalculateCommissionAction( application: $this->application, payload: $this->payload) )->handle(); $other_applications = $this->application->school->applications() ->where("year_id", $this->application->year_id) ->where("id", "!=", $this->application->id) ->where("status", "registration")->get(); $other_applications->each(function($app){ (new CalculateCommissionAction( application: $app, payload: $this->payload) )->handle(); }); // very weird but the application in payload loaded without commission $application = StudentApplication::find($this->application->id); if ($this->application->affiliate_code) { $affiliateUser = AffiliateUser::where('affiliate_code', $this->application->affiliate_code)->first(); if ($affiliateUser) { $affiliateUser->storeApplication($application); } } $hasScholarship = Scholarship::where('approval', 1) ->where('institution_id', $this->application->school_id) ->where('year_id', $this->application->year_id)->exists(); if ($hasScholarship) { (new CalculateScholarshipAction( application: $this->application, payload: $this->payload ) )->handle(); } }}
This change the status to completed and close the application,it will calculate our commission and save the application.
Completed
Application here reach the end of the cycle and get marked as completed, the application here is closed and the only action can be done here is to record the commissions received for this application