Skip to content

Rate Rounding Rules

A rate rounding rule is a named price convention for a workspace, such as “round down to the nearest 5, ending in .99”. A product group that derives its rates from a parent can select one rule, and its derived nightly, weekly and monthly prices then land on that convention instead of on the exact figure the percentage produces. Several groups can select the same rule, so editing the rule changes all of them.

A rule has a name and one currency-neutral definition:

Part Meaning
stepUnits Whole major currency units between allowed prices, from 1 to 100.
ending Where an allowed price sits relative to each multiple of the step.
direction How the starting price is picked: DOWN, UP or NEAREST. A tie in NEAREST takes the higher price.

The ending is either FRACTION, with hundredths from 0 to 99 (for example 99 for .99), or ONE_UNIT_UNDER, one major unit below each multiple. ONE_UNIT_UNDER needs a step greater than 1 and takes no hundredths: leave the field out when you send it. In responses hundredths is null for that ending.

A derived group never ends up at or above the price of its parent when its percentage is negative. In that case the price steps to the allowed price below.

Every rule has an integer version. A new rule starts at version 0. A change to the name or definition moves it up by one. Changing a rule or deleting it needs the expectedVersion you last read. If someone else changed the rule in between, the request fails with RATE_ROUNDING_RULE_VERSION_CONFLICT and nothing is written. Read the rule again and retry.

A group selects a rule through its derived rate strategy, in one of three ways:

  • assignRoundingRule selects a rule and keeps the group’s percentage and parent as they are.
  • unassignRoundingRule turns rounding off and keeps the percentage and parent.
  • rateStrategy.update saves the percentage and the rule together. Leaving roundingRuleId out keeps the current selection. Sending null clears it.

A group and its rule must be in the same workspace. A new or restored derived strategy starts with rounding off.

Saving a selection on a group changes that group’s prices straight away. Editing a rule’s arithmetic or deleting a rule reprices every group that selects it in the background. The response to those requests means the change was saved, not that every price has been recalculated, so stored prices can briefly lag.

Deleting a rule clears it from every group that selected it, including archived groups. Those groups go back to unrounded prices. Creating a rule or renaming it never changes prices.

Prices of existing bookings and quotes you have already issued are not recalculated when a rule is edited or deleted. Only the catalog prices used for new bookings and new quotes change.

Field Type Description
id ID! Identifier used to read, update, delete or assign the rule.
name String! Label for the rule, up to 150 characters.
version Int! Content revision used for optimistic concurrency.
definition RateRoundingDefinition! The arithmetic: stepUnits, ending and direction.
usage RateRoundingUsage status (IN_USE or NOT_IN_USE) and productGroupCount.

usage is null, with an error, if it cannot be worked out. The rest of the rule is still returned. A missing usage is never reported as zero groups.

roundingRule: RateRoundingRule on a derived product group’s rate strategy is the rule the group currently selects. It is null when rounding is off.

You need permission to read the workspace and permission to read its product groups, whichever route you use: awc.workspace, as below, or a catalog route such as catalog.productGroup(id).workspace.rateRoundingRules. Rules are ordered by name and then ID. A page holds at most 25 rules and first defaults to 25. The name filter takes the usual string operators.

query RateRoundingRules($workspace: ID!) {
awc {
workspace(id: $workspace) {
rateRoundingRules(first: 25) {
nodes {
id
name
version
definition {
stepUnits
ending {
kind
hundredths
}
direction
}
usage {
status
productGroupCount
}
}
pageInfo {
hasNextPage
endCursor
}
totalCount
}
}
}
}
{ "workspace": "workspace-id" }

An authorised caller with no rules gets an empty list. A caller without access gets an error and null for the collection.

query RateRoundingRule($id: ID!) {
catalog {
rateRoundingRule(id: $id) {
id
name
version
definition {
stepUnits
ending {
kind
hundredths
}
direction
}
usage {
status
productGroupCount
}
}
}
}
{ "id": "rule-id" }

A rule that does not exist, was deleted, or belongs to a workspace where the caller holds none of the workspace permissions returns null with the error code RATE_ROUNDING_RULE_NOT_FOUND. The three cases look the same, so the response never confirms that a rule exists. The same applies to roundingRule on a group’s derived rate strategy, to reading a rule’s usage and to updating or deleting a rule.

query ProductGroupRounding($id: ID!) {
catalog {
productGroup(id: $id) {
rateStrategy {
... on DerivedProductRateStrategy {
modifierPercentage
roundingRule {
id
name
definition {
stepUnits
ending {
kind
hundredths
}
direction
}
}
}
}
}
}
}
{ "id": "product-group-id" }

Each mutation returns only an id. Read the rule or the group again to see the stored result.

mutation CreateRateRoundingRule($input: CreateRateRoundingRuleMutationInput!) {
catalog {
rateRoundingRule {
create(input: $input) {
id
}
}
}
}
{
"input": {
"workspaceId": "workspace-id",
"rule": {
"name": "Nightly convention",
"definition": {
"stepUnits": 5,
"ending": { "kind": "FRACTION", "hundredths": 99 },
"direction": "DOWN"
}
}
}
}

Creating a rule twice creates two rules. clientMutationId is accepted but not returned, and it does not deduplicate.

Send only what changes. A definition replaces the whole arithmetic. An empty rule or an explicit null is rejected.

mutation UpdateRateRoundingRule($input: UpdateRateRoundingRuleMutationInput!) {
catalog {
rateRoundingRule {
update(input: $input) {
id
}
}
}
}
{
"input": {
"ruleId": "rule-id",
"expectedVersion": 0,
"rule": { "name": "Nightly convention revised" }
}
}
mutation DeleteRateRoundingRule($input: DeleteRateRoundingRuleMutationInput!) {
catalog {
rateRoundingRule {
delete(input: $input) {
id
}
}
}
}
{ "input": { "ruleId": "rule-id", "expectedVersion": 1 } }
mutation AssignRoundingRule($input: AssignProductGroupRoundingRuleMutationInput!) {
catalog {
productGroup {
rateStrategy {
assignRoundingRule(input: $input) {
id
}
}
}
}
}
{ "input": { "productGroupId": "product-group-id", "roundingRuleId": "rule-id" } }
mutation UnassignRoundingRule($input: UnassignProductGroupRoundingRuleMutationInput!) {
catalog {
productGroup {
rateStrategy {
unassignRoundingRule(input: $input) {
id
}
}
}
}
}
{ "input": { "productGroupId": "product-group-id" } }
mutation UpdateRateStrategy($input: UpdateProductGroupRateStrategyMutationInput!) {
catalog {
productGroup {
rateStrategy {
update(input: $input) {
id
}
}
}
}
}
{
"input": {
"productGroupId": "product-group-id",
"rateStrategy": {
"strategy": "DERIVE_FROM_PARENT",
"derived": { "modifierPercentage": -5, "roundingRuleId": "rule-id" }
}
}
}
Code Meaning
VALIDATION_ERROR The name or definition is not valid, or a rule ID is empty. The error lists which input fields to fix.
RATE_ROUNDING_RULE_VERSION_CONFLICT expectedVersion is out of date. Read the rule and try again.
RATE_ROUNDING_RULE_NOT_FOUND The rule does not exist, was deleted, or is in a workspace where the caller has no access. While the permission service is unreachable, a rule can also read as not found.
USER_NOT_ALLOWED_ERROR The caller has some access to the workspace but not the permission this needs, or the permission check returned an error. Reading and writing are separate permissions.

Error messages are fixed text and never repeat what you sent.