Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
This article is a companion to Understanding and customizing resource matching in Universal Resource Scheduling.
In this step-by-step guide, we extend Universal Resource Scheduling resources with a Language constraint. Consider an organization that wants to filter resources by the languages they speak. They also want to capture on the Requirement record the language required for the job. This constraint follows a similar pattern to the built-in Territory constraint. A new main entity Language stores the different languages a resource can speak. A Resource record can then be associated to many Languages through a many-to-many relationship entity. On the Requirement entity, we model this by creating two new lookup attributes: Primary Language and Secondary Language. When the system looks for available resources for a requirement, only resources associated with either the Primary Language or the Secondary Language show.
Tip
An entity is also known as a table.
Create the new tables and relationships
Create the new schema for the main Language table and update the Resource and Requirement tables with the corresponding relationships to the new Language table.
Create a new publisher
Go to the Power Platform Environment Settings app. Select Customizations > Publishers and create a new publisher. Learn more in Create a publisher.
Enter the following information:
Field Value Display name Language Name Language Prefix lang Select Save.
Create a new solution
Select Customizations > Solutions and create a new solution. Learn more in Create a solution.
Enter the following information:
Field Value Display Name Language Name Language Publisher Language Version 1.0.0.0 Select Create.
Create the language table
Open the Language solution and select New > Table > Table (advanced properties). Learn more Create new tables.
Enter the following information:
Field Value Display Name Language Plural Name Languages The schema name under Advanced options automatically fills in| lang_Language. The
lang_prefix is read-only.Select Save.
Create the many-to-many relationship from the Resource table to the Language table
- Open the Language table and New > Relationship > Many-to-many. Learn more Create many-to-many table relationships.
- For the Related (Many) Table, select Bookable Resource.
- Select Done.
- Close the Language table.
Create the relationships from the existing Requirement table to the Language table
Open the Language solution and select Add existing > Table > Resource Requirement.
If presented with a Missing Required Components dialog, select No, and don't include required components.
Select Add.
In the Resource Requirement table, create a new column.
Enter the following information:
Field Value Display name Primary Language Data type Lookup Related table Language Select Save.
In the Resource Requirement table, create a new column.
Enter the following information:
Field Value Display name Secondary Language Data type Lookup Related table Language Select Save.
Update the Requirement main form
- In the Resource Requirement table, select Forms.
- Select Add exiting form > Information and Add. If a Missing Required Components dialog appears, select No, and don't include required components.
- Open the Information form and add the two new attributes, primary language and secondary language to the form so users can enter this information as they create requirements.
- Select Save and publish.
Add language data to resources and requirements
Add new records to the Language table. You can then associate resource records to the new language records by opening a resource record and navigating to the language relationship in the navigation bar. For requirement records, you can fill in the new Primary Language and Secondary Language fields on the Requirement form.
Customize the Schedule Board
To filter resources in the Schedule Board with the new Language constraint, update the Retrieve Resources Query and the Filter Layout configuration records.
Note
These schedule board customizations apply to all tabs uniformly and can't be set individually per tab. This customization applies to Schedule Assistant Filter Layout, "Schedule Assistant Retrieve Resources Query, Schedule Assistant Resource Cell Template, and Schedule Assistant Retrieve Constraints Query.
Configure the Filter layout
For the following steps, use a text editor that supports XML syntax highlighting to make your changes, and then paste your changes back into the Universal Resource Scheduling editor.
The Filter Layout configuration is an XML layout definition to customize the layout of the Filter panel.
Note
Remove all default filters shipped with Universal Resource Scheduling from the Filter panel and add Languages as the only available filter.
<control type="combo" source="entity" key="Languages" inactive-state="1" label-id="Languages" entity="lang_language" multi="true" />
The control element adds a new control to the Filter panel. Here's the description of each attribute:
| Name | Description |
|---|---|
type |
The type of filter control. A combo control renders a dropdown with values to choose from |
source |
The source of the values for the dropdown control. An entity source shows entity records in the dropdown |
key |
The key to use to store the selected values in the constraints property bag |
inactive-state |
The inactive statecode for this entity, which is used to exclude inactive records from the dropdown |
label-id |
The localized label to use for this control |
entity |
This entity's records are displayed in the dropdown |
multi |
Configures the dropdown to allow selecting a single record or multiple records |
Filter Layout:
<?xml version="1.0" encoding="utf-8" ?>
<filter>
<controls>
<control type="combo" source="entity" key="Languages" inactive-state="1" label-id="Languages" entity="lang_language" multi="true" />
</controls>
</filter>
Create a new Languages board
To keep these changes isolated, create a separate Schedule Board and implement the changes. You can always make these changes on the default Schedule Board so that other Schedule Boards can automatically inherit these changes.
- Go to the Resource Scheduling app and select Schedule Board.
- Select the + sign to create a new board.
- Name the new board language.
- At the bottom of the dialog, select Add. The new board is created.
Configure the Schedule Board Filter Layout
Next, create a new configuration record that stores filter layouts and queries used by the Schedule Board. Then, link the newly created Schedule Board to the new configuration record. There are multiple ways to do this, but here's the quickest method:
- On the Schedule Board, select the ellipses (…) in the top right and Scheduler settings.
- Select All board settings at the bottom of the pane. Then select Other.
- Locate the Filter Layout field, and select the pencil icon to open the editor.
- Enter "Language Filter Layout" in the Name field and add the Filter Layout code and select Save as new.
- Select Save. A new configuration record is created which links this Schedule Board to the record.
- Select Filters. The Filter panel appears with the new layout. Only the Languages filter is available. Filtering doesn't work until you update the Retrieve Resources Query.
Update the Retrieve Resources Query
For the following steps, use a text editor that supports XML syntax highlighting to make your changes, and then paste your changes back into the Universal Resource Scheduling editor.
The Retrieve Resources Query configuration is a Universal FetchXML (UFX) Query used by the Resource Matching API. It takes as input the values entered in the Filter panel and dynamically constructs the correct FetchXML to find matching resources.
Add these snippets to the Retrieve Resources Query to match and order by the Resources' Languages.
Add the joins from bookableresource to lang_language
<link-entity name="lang_lang_language_bookableresource" from="bookableresourceid" to="bookableresourceid" alias="lang_primary" link-type="outer" ufx:if="$input/Languages/bag[1]">
<attribute name="lang_languageid" alias="lang_primary" groupby="true" />
<filter>
<condition attribute="lang_languageid" operator="eq">
<ufx:value select="$input/Languages/bag[1]/@ufx-id" attribute="value" />
</condition>
</filter>
</link-entity>
<link-entity name="lang_lang_language_bookableresource" from="bookableresourceid" to="bookableresourceid" alias="lang_secondary" link-type="outer" ufx:if="$input/Languages/bag[2]">
<attribute name="lang_languageid" alias="lang_secondary" groupby="true" />
<filter>
<condition attribute="lang_languageid" operator="eq">
<ufx:value select="$input/Languages/bag[2]/@ufx-id" attribute="value" />
</condition>
</filter>
</link-entity>
<link-entity name="lang_lang_language_bookableresource" from="bookableresourceid" to="bookableresourceid" alias="lang_others" link-type="outer" ufx:if="$input/Languages/bag[position() > 2]">
<filter>
<condition attribute="lang_languageid" operator="in">
<ufx:apply select="$input/Languages/bag[position() > 2]">
<value>
<ufx:value select="@ufx-id" />
</value>
</ufx:apply>
</condition>
</filter>
</link-entity>
<filter type="or">
<condition entityname="lang_primary" attribute="lang_languageid" operator="not-null" ufx:if="$input/Languages/bag[1]" />
<condition entityname="lang_secondary" attribute="lang_languageid" operator="not-null" ufx:if="$input/Languages/bag[2]" />
<condition entityname="lang_others" attribute="lang_languageid" operator="not-null" ufx:if="$input/Languages/bag[position() > 2]" />
</filter>
The values selected in the Filter panel is passed as input to the query and is available in the XPath
$inputvariable
The Retrieve Resources Query uses FetchXML to query the Resource (bookableresource) entity. We selected the FetchXML link-entity element to only return resources associated with the Language records selected in the Filter panel. Use multiple link-entity joins to show the matched languages and order by primary or secondary language, as described in the section Resource Cell Template.
Here's the description of each element and attribute:
| Name | Description |
|---|---|
link-entity |
Create a join to the many-to-many relationship between the Resource and Language entities |
ufx:if |
Only emit this FetchXML element (link-entity) if the XPath expression in this attribute returns a value |
attribute |
Return the primary or secondary language matched |
filter and condition |
Filter the many-to-many relationship records to only the ones that match the specified Language IDs |
ufx:value and select |
Outputs the result of the XPath expression in the select attribute |
ufx:apply and select |
Emit the child FetchXML elements for each result returned from the XPath expression in the select attribute |
value |
Contains the ID of a Language record |
Determine a resource's sort order
After you retrieve the matching resources, based on each resource's assigned languages, assign a new lang_order property to determine its sort order.
<bag>
<lang_order ufx:select="iif(lang_primary and lang_secondary, 1, iif(lang_primary, 2, iif(lang_secondary, 3, 4)))" />
</bag>
The description of each element and attribute:
| Name | Description |
|---|---|
lang_order |
Create a new property in each Resource returned from the FetchXML query named lang_order |
ufx:select |
Assign the result of the XPath expression in this attribute to the lang_order property. The lang_primary and lang_secondary properties, retrieved earlier in the query, is used together with the XPath iif function to determine the resource matching order. |
Order the results
<Resources ufx:select="order(Resources, 'lang_order')" />
UFX Queries are processed in sequential order. After the resources are retrieved through FetchXML, the results are assigned to the Resources property. Sort the results based on the lang_order property added earlier and reassign the sorted results to the Resources property.
The description of each element and attribute:
| Name | Description |
|---|---|
Resources |
Reassign the Resources property |
ufx:select |
Assign the result of the XPath expression in this attribute to the Resources property. The XPath order function is used to order the Resources list on its lang_order property. |
Note
The default Retrieve Resources Query shipped with Universal Resource Scheduling is a large query that supports all the resource constraints included with Universal Resource Scheduling. For this exercise, we use only a subset of the default query and add Languages as the only filter.
<?xml version="1.0" encoding="utf-8" ?>
<bag xmlns:ufx="https://schemas.microsoft.com/dynamics/2017/universalfetchxml">
<Resources ufx:source="fetch">
<fetch mapping="logical" aggregate="true">
<entity name="bookableresource">
<attribute name="bookableresourceid" alias="bookableresourceid" groupby="true"/>
<attribute name="name" alias="name" groupby="true"/>
<attribute name="calendarid" alias="calendarid" groupby="true"/>
<attribute name="resourcetype" alias="resourcetype" groupby="true"/>
<attribute name="msdyn_startlocation" alias="startlocation" groupby="true"/>
<!-- Language join -->
<link-entity name="lang_lang_language_bookableresource" from="bookableresourceid" to="bookableresourceid" alias="lang_primary" link-type="outer" ufx:if="$input/Languages/bag[1]">
<attribute name="lang_languageid" alias="lang_primary" groupby="true" />
<filter>
<condition attribute="lang_languageid" operator="eq">
<ufx:value select="$input/Languages/bag[1]/@ufx-id" attribute="value" />
</condition>
</filter>
</link-entity>
<link-entity name="lang_lang_language_bookableresource" from="bookableresourceid" to="bookableresourceid" alias="lang_secondary" link-type="outer" ufx:if="$input/Languages/bag[2]">
<attribute name="lang_languageid" alias="lang_secondary" groupby="true" />
<filter>
<condition attribute="lang_languageid" operator="eq">
<ufx:value select="$input/Languages/bag[2]/@ufx-id" attribute="value" />
</condition>
</filter>
</link-entity>
<link-entity name="lang_lang_language_bookableresource" from="bookableresourceid" to="bookableresourceid" alias="lang_others" link-type="outer" ufx:if="$input/Languages/bag[position() > 2]">
<filter>
<condition attribute="lang_languageid" operator="in">
<ufx:apply select="$input/Languages/bag[position() > 2]">
<value>
<ufx:value select="@ufx-id" />
</value>
</ufx:apply>
</condition>
</filter>
</link-entity>
<filter type="or">
<condition entityname="lang_primary" attribute="lang_languageid" operator="not-null" ufx:if="$input/Languages/bag[1]" />
<condition entityname="lang_secondary" attribute="lang_languageid" operator="not-null" ufx:if="$input/Languages/bag[2]" />
<condition entityname="lang_others" attribute="lang_languageid" operator="not-null" ufx:if="$input/Languages/bag[position() > 2]" />
</filter>
<link-entity name="systemuser" from="systemuserid" to="userid" link-type="outer">
<attribute name="systemuserid" alias="systemuserid" groupby="true" />
<attribute name="entityimage_url" alias="userimagepath" groupby="true"/>
</link-entity>
<link-entity name="contact" from="contactid" to="contactid" link-type="outer">
<attribute name="contactid" alias="contactid" groupby="true"/>
<attribute name="entityimage_url" alias="contactimagepath" groupby="true"/>
</link-entity>
<link-entity name="account" from="accountid" to="accountid" link-type="outer">
<attribute name="accountid" alias="accountid" groupby="true"/>
<attribute name="entityimage_url" alias="accountimagepath" groupby="true"/>
</link-entity>
</entity>
</fetch>
<bag>
<imagepath ufx:select="accountimagepath | contactimagepath | userimagepath" />
<accountimagepath ufx:select="$null" />
<contactimagepath ufx:select="$null" />
<userimagepath ufx:select="$null" />
<lang_order ufx:select="iif(lang_primary and lang_secondary, 1, iif(lang_primary, 2, iif(lang_secondary, 3, 4)))" />
</bag>
</Resources>
<Resources ufx:select="order(Resources, 'lang_order')" />
</bag>
Update the Schedule Board Retrieve Resources Query
- On the Schedule Board, select the ellipses (…) in the top right and Scheduler settings.
- Select All board settings at the bottom of the pane. Then select Other.
- Locate the Retrieve Resources Query field and select the pencil icon to open the editor.
- Enter "Language Resources Query" in the Name field, add the Retrieve Resources Query code, and select Save as new.
- Select Save. A new configuration record is created which links this Schedule Board to the record.
The board reloads with the updated configuration. Filtering works. If you create Language records and associate them with Resource records, you can filter resources by their associated languages.
Customize the Schedule Assistant
Customize the Schedule Assistant Filter Layout and Retrieve Constraints Query configurations to use the new Language constraints in the Schedule Assistant.
Unlike the Schedule Board customizations, where each board can be individually customized, the Schedule Assistant customizations affects all boards where the Schedule Assistant is used. The Schedule Assistant customizations can be specific to a schedulable type or for all types. In this example, we customize the Schedule Assistant for all types.
Configure the Schedule Assistant Filter Layout
For the following steps, use a text editor that supports XML syntax highlighting to make your changes, and then paste your changes back into the Universal Resource Scheduling editor.
The Schedule Assistant Filter Layout configuration, like the Schedule Board Filter Layout, defines the layout of the controls in the Filter panel. Since the Schedule Assistant uses more filters than the Schedule Board, like Start Time, End Time, and Duration, a different layout is used.
Note
For this exercise, we reuse only a subset of the default filters shipped in Universal Resource Scheduling from the Schedule Assistant Filter Layout configuration and add the Languages dropdown as the only available filter.
The filter we're adding to the layout is the same one in Configure the Schedule Board Filter Layout. The other controls are needed to modify the Schedule Assistant search parameters.
The complete Schedule Assistant Filter Layout
<?xml version="1.0" encoding="utf-8" ?>
<filter>
<controls>
<control type="twocolumn">
<control type="combo" source="optionset" key="Requirement/msdyn_worklocation" label-id="ScheduleAssistant.West.settingsform.WorkLocation" entity="msdyn_resourcerequirement" attribute="msdyn_worklocation">
<bind property="disabled" to="Requirement/InitialWorkLocation" operator="eq" value="690970002" />
<data>
<value id="690970000" />
<value id="690970001" />
<value id="690970002" />
</data>
</control>
<control type="duration" key="Requirement/msdyn_remainingduration" label-id="ScheduleAssistant.West.settingsform.AvailableDuration" />
</control>
<control type="distance" key="Requirement/Radius" label-id="ScheduleAssistant.West.settingsform.Radius" min="1">
<bind property="disabled" to="Requirement/msdyn_worklocation" operator="eq" value="690970002" />
</control>
<control type="datetime" key="Requirement/msdyn_fromdate" label-id="ScheduleAssistant.West.settingsform.StartDay">
<bind property="disabled" to="Requirement/RealTimeMode" operator="eq" value="true" />
<bind property="max" to="Requirement/msdyn_todate" operator="eq" />
</control>
<control type="datetime" key="Requirement/msdyn_todate" label-id="ScheduleAssistant.West.settingsform.EndDay">
<bind property="min" to="Requirement/msdyn_fromdate" operator="eq" />
</control>
<control type="combo" source="entity" key="Languages" inactive-state="1" label-id="Languages" entity="lang_language" multi="true" />
</controls>
</filter>
Update the Schedule Assistant Filter Layout
- On the Schedule Board, select the ellipses (…) in the top right and Scheduler settings.
- Select All board settings at the bottom of the pane.
- Scroll to Schedule Types and select None.
- Select Edit defaults located at the bottom.
- Locate the Schedule assistant filter layout field, select the pencil icon to open the editor.
- Enter Language Schedule Assistant Filter Layout in the Name field.
- Update the Schedule assistant filter layout field with the code and select Save as new. A new configuration record is created which links this Schedule Board to the record.
- At the bottom of the Board settings dialog, select Save.
The board reloads. Change the Retrieve Constraints Query to include the languages set on the Requirement in the Schedule Assistant search.
Configure the Retrieve Constraints Query
For the following steps, use a text editor that supports XML syntax highlighting to make your changes, and then paste your changes back into the Universal Resource Scheduling editor.
The Retrieve Constraints Query configuration is a UFX Query used by the Retrieve Requirement Constraints API. It takes as input the ID of a Requirement record (selected in the UI) and returns the Requirement record and all its child records.
Note
The default Retrieve Constraints Query shipped with Universal Resource Scheduling is a large query that supports all the requirement constraints included with Universal Resource Scheduling. For this exercise, we use only a subset of the default query and add Languages as the only filter.
<Languages ufx:select="lookup-to-list(Requirement/lang_primarylanguage, Requirement/lang_secondarylanguage)" />
UFX Queries are processed in sequential order. The Retrieve Constraints Query uses FetchXML to query the Requirement (msdyn_resourcerequirement) entity and assigns the result, a Requirement record, to the Requirement property. We're adding to the constraints property bag a new property Languages that combines both attributes, the Primary Language and Secondary Language, into a single list of entities (EntityCollection). This property is required since we're showing the Languages control in the Filter panel as a list of records. An alternative would be to create two separate controls in the Filter panel for the two attributes.
The description of each element and attribute:
| Name | Description |
|---|---|
Languages |
Create a new property in the result constraints property bag named Languages |
ufx:select |
Assign the result of the XPath expression in this attribute to the Languages property. The lang_primarylanguage and lang_secondarylanguage properties, retrieved earlier in the query and available in the Requirement property, is passed to the lookup-to-list XPath function, which converts multiple lookup properties to a single list (EntityCollection) |
Retrieve Constraints Query:
<?xml version="1.0" encoding="utf-8" ?>
<bag xmlns:ufx="https://schemas.microsoft.com/dynamics/2017/universalfetchxml">
<Requirement ufx:source="fetch" ufx:select="bag[1]">
<fetch top="1">
<entity name="msdyn_resourcerequirement">
<all-attributes />
<filter>
<condition attribute="statecode" operator="eq" value="0" />
<condition attribute="msdyn_resourcerequirementid" operator="eq">
<ufx:value select="$input/@ufx-id" attribute="value" />
</condition>
</filter>
</entity>
</fetch>
<bag>
<InitialWorkLocation ufx:select="msdyn_worklocation" />
</bag>
</Requirement>
<Languages ufx:select="lookup-to-list(Requirement/lang_primarylanguage, Requirement/lang_secondarylanguage)" />
</bag>
Update the Retrieve Constraints Query
- On the Schedule Board, select the ellipses (…) in the top right and Scheduler settings.
- Select All board settings at the bottom of the pane.
- Scroll to Schedule Types and select None.
- Select Edit defaults.
- Locate the Schedule assistant retrieve constraints query field and select the pencil icon to open the editor.
- Enter "Language Constraints Query" in the Name field.
- Update the Schedule assistant retrieve constraints query field with the Retrieve Constraints Query code and select Save as new.
- Select Save. A new configuration record is created which links this Schedule Board to the record.
- Locate the Schedule assistant retrieve resources query field and select the Languages Resources Query we created for the Schedule Board Customizations.
- At the bottom of the dialog, select Save.
The board reloads with the updated configuration. Schedule Assistant filtering works. If you create Language records and associate them with Requirement records, you can select a Requirement record in the bottom of the Schedule Board, select Find Availability to see only resources that match the languages saved on the requirement.
Configure the Resource Cell Template
For the following steps, use a text editor that supports HTML syntax highlighting to make your changes, and then paste your changes back into the Universal Resource Scheduling editor.
The Resource Cell Template configuration is a Handlebars template used to render content in the resource cell. The output from the Retrieve Resources Query is available to the template.
We're modifying the default resource template to show a green ✔✱ indicator if the resource matched the primary and secondary languages, a green ✔ indicator if the resource only matched the primary language, and a yellow ✔ indicator if the resource matched only the secondary language.
{{#if lang_primary}}
<span style='color:green;'>✔{{#if lang_secondary}} ✱{{/if}}</span>
{{else if lang_secondary}}
<span style='color:#ffe700;'>✔</span>
{{/if}}
The lang_primary and lang_secondary properties are returned from our custom Retrieve Resources Query we setup. Consult the Handlebars website for documentation on the templating syntax.
Resource Cell Template:
<div class='resource-card-wrapper {{iif ResourceCellSelected "resource-cell-selected" ""}} {{iif ResourceUnavailable "resource-unavailable" ""}} {{iif IsMatchingAvailability "availability-match" ""}}'>
{{#if imagepath}}
<img class='resource-image' src='{{client-url}}{{imagepath}}' />
{{else}}
<div class='resource-image unknown-resource'></div>
{{/if}}
<div class='resource-info'>
<div class='resource-name primary-text ellipsis' title='{{name}}'>{{name}}</div>
<div class='secondary-text ellipsis'>
{{#if (eq (is-sa-grid-view) false) }}
<div class='booked-duration'>{{BookedDuration}}</div>
<div class='booked-percentage'>
{{BookedPercentage}}%
{{#if lang_primary}}
<span style='color:green;'>✔{{#if lang_secondary}} ✱{{/if}}</span>
{{else if lang_secondary}}
<span style='color:#ffe700;'>✔</span>
{{/if}}
</div>
{{/if}}
</div>
{{#if (eq (is-sa-grid-view) false) }}
<div class='matching-indicator'></div>
{{/if}}
</div>
{{#if (eq (is-sa-grid-view) false) }}
{{> resource-map-pin-template this }}
{{/if}}
</div>
Update the Resource Cell Template
- On the Schedule Board, select the ellipses (…) in the top right and Scheduler settings.
- Select All board settings at the bottom of the pane.
- Scroll to Schedule Types and select None.
- Select Edit defaults.
- Locate the Schedule Assistant Resource Cell Template field and select the pencil icon to open the editor.
- Enter "Language Resource Cell Template" in the Name field.
- Update the Resource Cell Template field with the Resource Cell Template code and select Save as new.
- Select Save. A new configuration record is created which links this Schedule Board to the record.
- Locate the Schedule assistant retrieve resources query field and select the Languages Resources Query we created for the Schedule Board Customizations.
- At the bottom of the dialog, select Save.
The board reloads with the updated configuration. The resource cell shows how a resource matches the language constraint in the Filter panel.