Skip to content

Overview

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 actors
public 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 student
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();

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 conditional
if ($this->application->checkStatus('cacceptance')) {
$this->attachFiles($this->application->conditional_acceptance, $attachments, 'Conditional acceptance file ');
}
// Queue the mail which will be sent
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"
);

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

//ReplyingToStudentAction
public 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.

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 receipt
if ($this->payload->type == 'paid' || $this->application->checkStatus('paid')) {
$this->attachFiles($this->application->payment_receipt, $attachments, 'Payment Receipt file ');
}
// Queue the mail which will be sent
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"
);

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.

//onUploadFinalAcceptance
function onUploadFinalAcceptance(){
$payload = PayloadDataObject::fromArray(['user' => user(), ...post()]);
(new UploadingFinalAcceptanceAction(application:null, payload: $payload))->handle();
}

this delegates to the UploadingFinalAcceptanceAction.

//UploadingFinalAcceptanceAction
public 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 acceptance
if ($this->application->checkStatus('facceptance')) {
$this->attachFiles($this->application->final_acceptance, $attachments, 'Final acceptance file ');
}
// Queue the mail which will be sent
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"
);

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.php
public 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