Skip to content

Commission Calculation

Introduction

To calculate the commission, an action located in app/Actions/Application/CalculateCommissionAction.php is utilized. This action fetches the applied commission on a specific application and performs the necessary calculations to generate the final commission amount based on the commission criteria defined in the commission introduction page. Additionally, it calculates the commission for the agent involved in the application.

In cases where the commission type is Variable Amount or Variable Percentage, the action also recalculates commissions for other applications within the same institution and academic year, as these commission types involve dynamic changes based on the number of completed applications within specific ranges.

To get the applied commission config on a spacific application we use the following function located in plugins/spotlayerteam/institutionsprograms/models/Application.php class:

 
public function getCommissionConfig()
{
$programCommission = $this->program->commissions()
->where("approval", 1)
->where("year_id", $this->year_id)
->first();
if ($programCommission) {
return $programCommission;
}
$schoolCommissions = $this->school->commissions()
->where("approval", 1)
->where("year_id", $this->year_id)
->where(function ($query) {
$query->whereNull("levels")
->orWhereJsonContains('levels', intval($this->level_id));
})
->where(function ($query) {
$query->whereNull("languages")
->orWhereJsonContains('languages', intval($this->program->language_id));
})
->where(function ($query) {
$query->whereNull("fields")
->orWhereJsonContains('fields', intval($this->program->speciality->field_id));
})
->where(function ($query) {
$query->whereNull("programs")
->orWhere(function ($query) {
$query->where("programs_type", "except")
->whereJsonDoesntContain('programs', intval($this->program_id));
})->orWhere(function ($query) {
$query->where("programs_type", "include")
->whereJsonContains("programs", intval($this->program_id));
});
})
->orderBy('levels', 'desc')
->orderBy('languages', 'desc')
->orderBy('fields', 'desc')
->orderBy('programs', 'desc')
->first();
return $schoolCommissions;
}

The function getCommissionConfig() retrieves the applied commission configuration for a specific application based on various criteria. It first attempts to find a program-specific commission, and if not found, it looks for a school-level commission. Here's an overview of how the function works:

  • It tries to find a program-specific commission:

    • It searches for a commission associated with the program ($this->program) that meets the following criteria:
      • Has been approved (approval is set to 1).
      • Matches the academic year (year_id matches $this->year_id).
    • If a program-specific commission is found, it returns that commission.
  • If no program-specific commission is found, it looks for a school-level commission:

    • It searches for a commission associated with the school ($this->school) based on several factors:
      • Approval status (approval is set to 1).
      • Academic year (year_id matches $this->year_id).
      • Educational levels (levels JSON contains the level ID or is null).
      • Program languages (languages JSON contains the program's language ID or is null).
      • Program fields (fields JSON contains the program's field ID or is null).
      • Programs (programs JSON either doesn't contain the program ID or follows "include" or "except" rules based on the program type).
  • The function orders the school-level commissions by descending priority in the following order: levels, languages, fields, and programs.

  • It returns the school-level commission with the highest priority if found; otherwise, it returns null if no commission matches the criteria.

This function helps determine the commission configuration applied to a specific application by considering program-specific and school-level commissions based on various factors and priorities.

Calculate Commission Action

In the CalculateCommissionAction, the handle function first retrieves the applied commission configuration specific to the application. It then applies a series of if-conditions to determine whether commission calculations should proceed or not.

 
public function handle(){
 
$config = $this->application->getCommissionConfig();
$commission = null;
$agentCommission = 0;
$agentPercent = 0;
if ($config == null){
$this->application->commission?->delete();
$this->application->agent_commission?->delete();
$this->calculateAffiliateCommission();
return null;
}
if($config->effective_date && $config->effective_date > $this->application->completed_at){
$this->calculateAffiliateCommission();
return;
}
// The only case required override calculation is range amount and
// range percentage, or i passed recalculate flag
if ($this->application->commission &&
$config->type != "range-amount" &&
$config->type != "range-percentage" &&
!($this->payload->reCalculate ?: false)
) {
$this->calculateAffiliateCommission();
return ;
}
 
// ...
 
}

Here's a breakdown of the key conditions:

  • If the commission configuration is not found ($config == null):

    • The existing commission and agent commission (if they exist) for the application are deleted.
    • The calculateAffiliateCommission function is called.
    • The calculation process is halted, and no commission is calculated.
  • If an "effective_date" is specified in the commission configuration, and it's later than the application's completion date:

    • The calculateAffiliateCommission function is called.
    • The calculation process is halted, and no commission is calculated.
  • If:

    • The application has existing commission data.
    • The commission type is not "range-amount" or "range-percentage."
    • The reCalculate flag (passed via the payload) is not set to true.
    • In this case, the calculateAffiliateCommission function is called.
    • The calculation process is halted, and no commission is calculated.

All these 3 conditions prevent the commission from calculation and make the application commission remind still, otherwise the actual commission will be calculated based on the commission configuration.

Once the conditions are met and commission calculation is necessary, the action proceeds to generate the agent's commission percentage if the application was created by an agent or one of their sub-agents.

$creator = $this->application->creator;
$year_id = $this->application->year_id;
if ($creator->groupOf(5)) {
$agentPercent = $creator->getCommissionYear($year_id);
} elseif ($creator->groupOf(20)) {
$mainAgent = $creator->getMainAgent();
if($mainAgent){
$agentPercent = $mainAgent->getCommissionYear($year_id);
}
}

Here's how the agent commission percentage is determined:

  • It identifies the creator of the application using $this->application->creator.

  • It retrieves the academic year ID from the application using $year_id = $this->application->year_id.

  • If the creator belongs to a group with an ID of 5 (likely representing agents), it fetches the commission percentage for that agent for the given academic year using $creator->getCommissionYear($year_id).

  • If the creator belongs to a group with an ID of 20 (possibly representing sub-agents), it retrieves the main agent associated with the creator using $mainAgent = $creator->getMainAgent(). If a main agent is found, it fetches the commission percentage for the main agent for the specified academic year.

This logic helps determine the agent's commission percentage based on their group and academic year, considering both agents and their sub-agents in the commission calculation.

Now, lets move to actual calculation commission part.

$currency = $this->application->program->currency;
 
$message = "";
$agentMessage = "";
if ($config->type == "none"){
$commission = null;
$agentPercent = null;
} elseif ($config->type == "exact") {
$commission = $config->details[0]->value;
$agentCommission = ($agentPercent/100) * $commission;
$message = "Commission exact amount: $commission $currency";
$agentMessage = "Agent Commission ($agentPercent%): $agentCommission $currency";
} elseif ($config->type == "percentage") {
$commission = $this->application->program->tuition * ($config->details[0]->value / 100);
$agentCommission = ($agentPercent/100) * $commission;
$message = "Commission percentage: ".number_format($config->details[0]->value, 2)." %";
$agentMessage = "Agent Commission ($agentPercent%): $agentCommission $currency";
} elseif (in_array($config->type, ["range-amount", "range-percentage", "special-range-amount", "special-range-percentage"]) ) {
$completedApps = $this->application->school->applications()
->where("year_id", $this->application->year_id)
->where("status", "registration")
// completed apps untill current app
->when($this->application->completed_at && in_array($config->type, ["special-range-amount", "special-range-percentage"]), function($query){
$query->where("completed_at", "<=", $this->application->completed_at);
})
->count();
 
$agentValue = 0;
$agentCompletedApps = 0;
if ($agentPercent) {
// Completed apps for this agent...
$agentCompletedApps = $creator->applications()
->where("school_id", $this->application->school_id)
->where("year_id", $this->application->year_id)
->where("status", "registration")
// completed apps untill current app
->when(in_array($config->type, ["special-range-amount", "special-range-percentage"]), function($query){
$query->where("completed_at", "<=", $this->application->completed_at);
})
->count();
$agentValue = $this->getSliceValue($config, $agentCompletedApps);
}
 
$value = $this->getSliceValue($config, $completedApps);
if (in_array($config->type, ["range-amount", "special-range-amount"])) {
$commission = $value;
$agentCommission = ($agentPercent/100) * $agentValue;
$message = "Completed Apps: $completedApps, Amount: $commission $currency";
} elseif (in_array($config->type, ["range-percentage", "special-range-percentage"])) {
$commission = $this->application->program->tuition * ($value / 100);
$agentTotalcommission = $this->application->program->tuition * ($agentValue / 100);
$agentCommission = $agentTotalcommission * ($agentPercent / 100);
$message = "Completed Apps: $completedApps, Percentage: ".number_format($value, 2)." %";
}
$agentMessage = "Agent Completed Apps $agentCompletedApps, Commission ($agentPercent%): $agentCommission $currency";
}

Here's a breakdown of the key conditions:

  • It begins by determining the currency associated with the program.

  • The variables $message and $agentMessage are initialized for potential messages about the commission and agent commission.

  • If the commission type is "none," both the commission and agent percentage are set to null.

  • If the commission type is "exact":

    • The commission is set to the exact value specified in the commission configuration ($config->details[0]->value).
    • The agent commission is calculated based on the agent's percentage.
    • Messages are generated to indicate the exact commission amounts.
  • If the commission type is "percentage":

    • The commission is calculated as a percentage of the program's tuition fee.
    • The agent commission is calculated based on the agent's percentage.
    • Messages are generated to indicate the commission percentage.
  • If the commission type falls into one of the range-based categories:

    • It counts the number of completed applications at the same institution and year, considering the specific range conditions.
    • If an agent percentage exists:
      • It counts the completed applications for the agent at the same institution and year, also considering the range conditions.
      • Calculates the agent value based on the agent's completed applications.
    • Calculates the commission value based on the completed applications.
    • Depending on the commission type, either the commission amount or percentage is determined.
    • Messages are generated to describe the commission calculation.

Overall, this code efficiently calculates commission amounts based on different commission configuration types, considering factors such as exact values, percentages, and range-based criteria. It also calculates agent commissions if applicable and generates messages to provide transparency regarding the commission calculations.

The last part of CalculateCommissionAction is to set the calculated commission to the application and the agent:

if($commission){
$appCommission = ApplicationCommission::where('application_id', $this->application->id)
->firstOr(fn() => new ApplicationCommission());
$appCommission->application_id = $this->application->id;
$appCommission->amount = $commission;
$appCommission->commission_id = $config->id;
$appCommission->message = $message;
$appCommission->save();
} else {
$this->application->commission?->delete();
}
if($agentCommission){
$model = AgentCommission::where('application_id', $this->application->id)
->firstOr(fn() => new AgentCommission());
$model->application_id = $this->application->id;
$model->amount = $agentCommission;
$model->commission_id = $config->id;
$model->agent_id = $this->application->created_by;
$model->message = $agentMessage;
$model->save();
} else {
$this->application->agent_commission?->delete();
}
 
$this->calculateAffiliateCommission();

In the final part of the CalculateCommissionAction, the calculated commission values are assigned to the application and the agent. Here's how this is achieved:

  • If a commission ($commission) has been calculated:

    • It checks if there is an existing ApplicationCommission record for the application with the same ID.
    • If a record is found, it updates it; otherwise, it creates a new ApplicationCommission instance.
    • The application ID, commission amount, commission ID, message, and other details are set.
    • The ApplicationCommission record is saved.
  • If an agent commission ($agentCommission) has been calculated:

    • It checks if there is an existing AgentCommission record for the application with the same ID.
    • If a record is found, it updates it; otherwise, it creates a new AgentCommission instance.
    • The application ID, agent commission amount, commission ID, agent ID, message, and other details are set.
    • The AgentCommission record is saved.
  • If no commission was calculated for the application, any existing ApplicationCommission record is deleted (if it exists), ensuring that there is no residual commission data.

  • Finally, the calculateAffiliateCommission function is called.

ReCalculate Institution Commission Action

The "ReCalculate Institution Commission Action" is executed when a commission is approved (created or changed) or when there's a change in application status. This action can be found in the app/Actions/Institution/ReCalculateInstitutionCommissionAction.php location.

public function handle($institution_id, Commission $commission)
{
$schoolApplications = Application::where("year_id", $commission->year_id)
->where("school_id", $institution_id)
->where("status", "registration")
->when($commission->effective_date, function($query) use ($commission) {
$query->where("completed_at", ">=", $commission->effective_date);
})
->orderBy("completed_at", "asc")
->get();
 
$payload = PayloadDataObject::fromArray([
"reCalculate" => true
]);
$schoolApplications->each(function($app) use ($payload){
(new CalculateCommissionAction(
application: $app,
payload: $payload
))->handle();
});
}

The handle method within the "ReCalculate Institution Commission Action" performs the following tasks:

  • It retrieves all school applications that meet the specified criteria:

    • The application must belong to the same academic year as the commission (year_id).
    • The application must be associated with the specified institution (school_id).
    • The application must have a status of "registration."
    • If an effective date is specified in the commission, it filters applications based on their completion date to consider only those completed on or after the effective date.
    • The applications are ordered by their completion date in ascending order.
    • The filtered applications are retrieved as a collection.
  • It prepares a payload data object ($payload) with a reCalculate flag set to true.

  • It iterates through each of the school applications retrieved in step 1 using the each method.

  • For each application, it calls the "Calculate Commission Action" (CalculateCommissionAction) to recalculate the commission for that specific application. The payload with the reCalculate flag is passed to this action.

In summary, this action is responsible for recalculating commissions for all school applications meeting the specified criteria within the institution and academic year. It does so by iterating through the applications and invoking the "Calculate Commission Action" for each one, ensuring that commission calculations are updated as necessary.