Contextual Price Calculations

Use the following procedure to define custom price calculations that need to reason about a full collection of reservations and their related records, rather than a single reservation in isolation.

GoMeddo offers two approaches for customising the reservation price: the B25.Util_PluginManager.ReservationPrice interface (see Apply custom price calculations), and the contextual price calculation described on this page. The contextual calculation is the right choice when:

  • Pricing on one reservation depends on sibling or parent reservations

  • Queries need to be done during the price calculations. The contextual calculation allows you to bulkify the queries.

1. Create your custom price calculator class

Create a class that extends B25.CustomPriceCalculation. The class must be global so that GoMeddo can instantiate it. In that class, override the calculate method with the following signature:

global override void calculate(List<B25.CustomPriceCalculation.PriceCalculationParameters> calculationRequests)

Each PriceCalculationParameters item in the list represents one reservation that needs to be priced. It exposes the following properties:

Name

Type

Purpose

beingProcessed

B25.Reservation

Wraps the reservation currently being priced. beingProcessed.reservation is the B25__Reservation__c record; beingProcessed.childRecords is a Map<String, List<SObject>> keyed by child relationship name (e.g. B25__ServiceReservations__r). This is read-only — only changes applied to the changes variable are processed.

collection

B25.ReservationCollection

The full collection being processed, exposing parentReservation and childReservations. Use this when the price of one reservation depends on another reservation in the same collection. This context is read-only — only changes applied to the changes variable are processed.

isParent

Boolean

true when the reservation being processed is the parent of its collection, false for a child.

changes

B25.CustomPriceCalculation.PriceChanges

The object on which price changes are recorded. Call changes.updateFieldValue(fieldToken, value) rather than mutating beingProcessed.reservation directly.

Why record changes on PriceChanges instead of writing directly to the record? This allows for the updating of fields that are read only. Allowing them to update in the UI and be returned as changes to the REST endpoint.

2. Activate the custom price calculator class

Register the class so GoMeddo uses it:

  1. Go to Setup

  2. Search for Custom Settings

  3. Click Manage next to System Setting

  4. Click New

  5. Name the record Contextual Price Calculation Class, and for String Value enter the name of your custom class

As long as this setting exists, GoMeddo will call your calculate method whenever a triggering field changes on a reservation (or a related record) that the calculation depends on.

3. (Optional) Control which fields trigger the calculation

Override getTriggerConfiguration to customise:

  • Which field changes trigger a price recalculation — including fields on related objects such as Service Reservation

  • Whether a change on a child reservation should also trigger recalculation of the parent

  • Whether a change on a parent reservation should also trigger recalculation of its children

global override B25.CustomPriceCalculation.CalculationTriggerConfiguration getTriggerConfiguration()

The returned CalculationTriggerConfiguration exposes the following properties:

Property

Type

Purpose

triggeringFields

Set<SObjectField>

Fields whose changes trigger recalculation. Unlike the V1 interface, this set can contain fields on related objects (for example B25__Service_Reservation__c.B25__Unit_Price__c), which will cause the reservation price to be recalculated when a service reservation changes.

parentCalculationRequiresChildren

Boolean

When true, a change on a child reservation re-runs the calculation for the parent. Useful when the parent's subtotal aggregates its children.

childCalculationRequiresParent

Boolean

When true, a change on the parent reservation re-runs the calculation for each child. Useful when a parent-level setting (such as a discount) flows down to every child.

If this method is not overridden, GoMeddo uses a default trigger configuration that covers the standard Reservation pricing fields (Start, End, Base Price, Calculation Method, Quantity, Skip Subtotal Calculation) and the core Service Reservation fields (Quantity, Unit Price), with parentCalculationRequiresChildren enabled.

Examples

Fixed discount for an Installation Partner

The sample below adds the Account field to the set of triggering fields and sets the subtotal to zero when the linked Account is an Installation Partner. The default calculation runs first via super.calculate, so the standard pricing logic is still applied to every other reservation.

Apex Code

Java
global class InstallationPartnerPriceCalculation extends B25.CustomPriceCalculation {

    global override B25.CustomPriceCalculation.CalculationTriggerConfiguration getTriggerConfiguration() {
        B25.CustomPriceCalculation.CalculationTriggerConfiguration config = super.getTriggerConfiguration();
        config.parentCalculationRequiresChildren = true;
        return config;
    }

    global override void calculate(List<B25.CustomPriceCalculation.PriceCalculationParameters> calculationRequests) {
        super.calculate(calculationRequests);

        Set<Id> accountIds = new Set<Id>();
        for (B25.CustomPriceCalculation.PriceCalculationParameters request : calculationRequests) {
            if (request.beingProcessed.reservation.B25__Account__c != null) {
                accountIds.add(request.beingProcessed.reservation.B25__Account__c);
            }
        }
        if (accountIds.isEmpty()) {
            return;
        }

        Map<Id, Account> accountsById = new Map<Id, Account>([
            SELECT Id, Type FROM Account WHERE Id IN :accountIds WITH USER_MODE
        ]);

        for (B25.CustomPriceCalculation.PriceCalculationParameters request : calculationRequests) {
            Account linkedAccount = accountsById.get(request.beingProcessed.reservation.B25__Account__c);
            if (linkedAccount != null && linkedAccount.Type == 'Installation Partner') {
                request.changes.updateFieldValue(B25__Reservation__c.B25__Subtotal__c, 0);
            }
        }
    }
}

Parent subtotal as the sum of its children

The sample below recalculates the parent reservation's subtotal as the sum of its child reservations' subtotals. Because child changes must flow up to the parent, parentCalculationRequiresChildren is kept enabled (it is on by default).

Java
global class RollupChildSubtotals extends B25.CustomPriceCalculation {
  
    global override B25.CustomPriceCalculation.CalculationTriggerConfiguration getTriggerConfiguration() {
        B25.CustomPriceCalculation.CalculationTriggerConfiguration config = super.getTriggerConfiguration();
        config.triggeringFields.add(B25__Reservation__c.B25__Account__c);
        return config;
    }
  
    global override void calculate(List<B25.CustomPriceCalculation.PriceCalculationParameters> calculationRequests) {
        super.calculate(calculationRequests);

        for (B25.CustomPriceCalculation.PriceCalculationParameters request : calculationRequests) {
            if (!request.isParent) {
                continue;
            }
            Decimal total = 0;
            for (B25.Reservation child : request.collection.childReservations) {
                total += child.reservation.B25__Subtotal__c ?? 0;
            }
            request.changes.updateFieldValue(B25__Reservation__c.B25__Subtotal__c, total);
        }
    }
}