Fix Materialized Path

Overview

GoMeddo contains an internal ID system to reduce query consumption. This system uses the B25__Booker25_Id__c and B25__Materialized_Path__c fields on the Resource object. Every Resource in GoMeddo that triggers the Resource Trigger during creation, will have a value for these fields.

The only ways that those fields are not set are:

  • the Resource has the B25__IsVirtual__c checkbox set to true (you should never use this as it's an internal-use only checkbox)

  • the Resource creation somehow did not trigger the Resource Trigger, for example when the trigger was disabled during a large data import or an org to org migration

Ideally, you should always make sure the GoMeddo Resource Trigger is run when creating Resources. If for any reason a Resource is missing values in either of the B25__Booker25_Id__c and B25__Materialized_Path__c fields, you will need to rebuild the ID structure. How to do this is explained below.

Repair the resource tree

From GoMeddo 7.3 onwards the fix is built into the app and no longer needs the Developer Console. On older versions, use Running the fix manually at the bottom of this article instead.

Before you start

  • Every Resource is given a new GoMeddo Id. Any integration, export or report that stored B25__Booker25_Id__c or B25__Materialized_Path__c values has to be refreshed afterwards.

  • The repair updates every Resource in the org. Validation rules, required fields or custom automation on the Resource object can make individual updates fail, which leaves those branches of the tree incomplete. The repair reports which Resources failed so you can fix them and run it again.

  • While the rebuild is running the tree is temporarily inconsistent, so conflict checking and availability on Resources can give incorrect results. Run the repair outside business hours where you can.

Step-by-step guide

  1. Open the GoMeddo Setup tab.

  2. Select Maintenance in the menu on the left.

  3. Click Repair Resource Tree. The dialog shows how many Resources will be renumbered and rebuilt.

  4. Click Start Repair and follow the progress bar until it reports Complete.

The action requires Apex class access to B25.Ctrl_ResourceTreeRepair, which is granted by the B25_Admin permission set only.

Only one repair can run at a time. Closing the dialog does not stop the repair, it only stops the progress from being shown; the jobs can be followed in Setup > Apex Jobs.

What the repair does

  1. Clears B25__Booker25_Id__c and B25__Materialized_Path__c on every Resource. The GoMeddo Id is a unique field, so the old values have to be gone before new ones can be handed out. Virtual Resources are cleared as well, so an id still held by a Resource that was made virtual after it was created cannot block the new numbering.

  2. Assigns new ids, numbered from 00000 in order of creation date, and stores the next free id so Resources created afterwards continue the sequence.

  3. Rebuilds B25__Materialized_Path__c from the Parent lookups. Each pass sets every path from its parent's current path, and a new pass is started until a pass changes nothing, so it takes as many passes as the hierarchy is deep.

Each step runs as a batch job, chained after the previous one, so orgs with large numbers of Resources are processed without running into Apex governor limits. The GoMeddo Resource Trigger is disabled for these updates only.

When Resources could not be repaired

The repair does not stop on a single bad record. If any Resources could not be repaired, the final message names them, up to the first 20, and counts the rest. The usual causes are:

  • a validation rule, required field or custom automation blocking the update on that Resource

  • the Resource's parent has no GoMeddo Id itself, usually because it is virtual or was deleted

  • the Resource is its own parent

  • the branch is more than 51 levels deep, which is the maximum the Materialized Path field can hold

Fix the reported Resources and run the repair again. Repeating the repair is safe, and once the first batch of failures is fixed a second run reports whichever failures did not fit in the first message. The underlying errors are also written to the debug log.

If the repair finished successfully, the GoMeddo ID system has been reset correctly. If you run into any issues, please reach out to us via support@gomeddo.com

Running the fix manually (GoMeddo 7.2 and older)

On versions without the Maintenance section in GoMeddo Setup, the ID structure has to be rebuilt with two Apex scripts. Note that these run in a single transaction, so they can hit Apex governor limits on orgs with large numbers of Resources.

Please be aware that the scripts below should be run exactly as they are written here. You should not limit scripts to run for a subset of Resources, other than the WHERE clauses already present in the scripts.

Show the manual scripts
  1. Go to the Developer Console

  2. Open an Execute Anonymous window

  3. Run the following script to remove all existing values:

    Java
    // Reset all ids and paths
    List<B25__Resource__c> allResources = [SELECT Id, B25__Materialized_Path__c, B25__Booker25_Id__c FROM B25__Resource__c WHERE B25__Booker25_Id__c != null];
    for (B25__Resource__c resource : allResources) {
    	resource.B25__Booker25_Id__c = null;
    	resource.B25__Materialized_Path__c = null;
    }
    B25.Trigger_Resource_c.blockExecution = true;
    Database.update(allResources);
    B25.Trigger_Resource_c.blockExecution = false;
    


  4. After successfully running the first script, run the following script. If any errors occur, such as validation errors, fix these first. You need to fix all the errors in order to successfully run the script. Although errors should most likely relate to custom work in your org, such as validation errors, you can always reach out to support@gomeddo.com if you need help.

    Java
    public static String incrementBase64Local(String base64Number) {
    	String encodeTable = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_';
    	Integer indexOfLastChar = base64Number.length() - 1;
    	Integer lastCharOfBase64Number = base64Number.charAt(indexOfLastChar);
    	Integer index = encodeTable.indexOfChar(lastCharOfBase64Number);
    	if (index == 63) {
    		if (base64Number.length() == 1) {
    			return '10';
    		}
    		String firstCharacters = incrementBase64Local(base64Number.left(indexOfLastChar));
    		return firstCharacters + '0';
    	}
    	base64Number = base64Number.left(indexOfLastChar) + encodeTable.substring(index + 1, index + 2);
    	return  base64Number;
    }
    
    // Reset the resource numbering.
    B25__System_Setting__c resourceIdSetting = [SELECT B25__String_Value__c FROM B25__System_Setting__c WHERE Name = 'ResourceIdNumber' FOR UPDATE];
    resourceIdSetting.B25__String_Value__c = '00000';
    
    Set<Id> parentIds = new Set<Id>{null}; // first layer
    while (!parentIds.isEmpty()) {
    	List<B25__Resource__c> resources = [SELECT B25__Materialized_Path__c, B25__Booker25_Id__c, B25__Parent__r.B25__Materialized_Path__c, B25__Parent__r.B25__Booker25_Id__c FROM B25__Resource__c WHERE B25__Parent__c IN:parentIds AND B25__IsVirtual__c = false];
    
    	// Update resource ids
    	String currentResourceId = resourceIdSetting.B25__String_Value__c;
    	for (B25__Resource__c resource : resources) {
    		resource.B25__Booker25_Id__c = currentResourceId;
    		currentResourceId = incrementBase64Local(currentResourceId);
    		if (currentResourceId.length() > 5) {
    			System.assert(false, 'Too many resources');
    		}
    	}
    	resourceIdSetting.B25__String_Value__c = currentResourceId;
    
    	for (B25__Resource__c resource : resources) {
    		if (resource.B25__Parent__c == null) {
    			resource.B25__Materialized_Path__c = '';
    			continue;
    		}
    		resource.B25__Materialized_Path__c = resource.B25__Parent__r.B25__Materialized_Path__c != null
    			? resource.B25__Parent__r.B25__Materialized_Path__c + resource.B25__Parent__r.B25__Booker25_Id__c
    			: resource.B25__Parent__r.B25__Booker25_Id__c;
    	}
    
    	B25.Trigger_Resource_c.blockExecution = true;
    	Database.update(resources);
    	B25.Trigger_Resource_c.blockExecution = false;
    	parentIds = new Set<Id>(new Map<Id, B25__Resource__c>(resources).keySet());
    }
    Database.update(resourceIdSetting);