Custom calculations for Quote totals¶
Warning
This method is not recommended anymore as it's likely to cause problems. Since this article has been written, the functionality has been significantly extended to support more complex financial calculations. Changing the calculated amount in the hook will likely break the calculation logic.
Note: The same mechanism is also available for Sales Orders and Invoices.
Problem¶
You have added custom fields to quote items or/and quote entity types. You want Total Amount field and other totals to be calculated considering new custom fields.
Resolution¶
Server-side calculation¶
You need to create a custom Hook for the Quote entity type.
Create a new file:
custom/Espo/Custom/Hooks/Quote/CalculateItems.php
<?php
namespace Espo\Custom\Hooks\Quote;
use Espo\ORM\Entity;
class CalculateItems
{
public static int $order = 10;
public function __construct(
// Define needed dependencies.
) {}
public function beforeSave(Entity $entity, array $options): void
{
if (!$entity->has('itemList')) {
$entity->loadItemListField();
}
$itemList = $entity->get('itemList');
$amount = 0.0;
foreach ($itemList as $item) {
$amount += $item->quantity * $item->unitPrice * $item->factor;
}
$entity->set('amount', $amount);
}
}
Note: For other entity types, e.g. SalesOrder, do the same but use SalesOrder namespace and file location instead of Quote.
Client-side calculation¶
In Quote's clientDefs you need to specify custom calculation handler:
File: custom/Espo/Custom/Resources/metadata/clientDefs/Quote.json
{
"calculationHandler": "custom:quote-calculation-handler"
}
Note: For Sales Orders use SalesOrder.json file names. For Invoices use Invoice.json file names.
Create a new file:
client/custom/src/quote-calculation-handler.js
define(['sales:quote-calculation-handler'], (Dep) => {
return class extends Dep {
// Define custom calculations here.
// Use client/modules/sales/quote-calculation-handler.js as an example.
/**
* Calculate.
*
* @param {import('model').default} model An order.
*/
calculate(model) {
super.calculate(model);
}
/**
* Calculate item.
*
* @param {import('model').default} model An item.
* @param {string} [field] A field that was changed.
*/
calculateItem(model, field) {
super.calculateItem(model, field);
}
/**
* Select product.
*
* @param {import('model').default} model
* @param {import('model').default} product
*/
selectProduct(model, product) {
super.selectProduct(model, product);
}
/**
* Get select product attributes.
*
* @param {import('model').default} model
* @return {string[]}
*/
getSelectProductAttributeList(model) {
super.getSelectProductAttributeList(model);
}
}
});
It's also possible to make certain product fields copied to a quote item on product selection. The logic is determined in selectProduct method.