Resolution Types
A resolution type is the decision an agent makes about a claim line — refund, replace, compensate, send a manual reply, and so on. The Returns app has no built-in resolution logic. Instead, resolutions are entirely registry-driven, and their side effects are produced by rules listening to the OnClaimResolve hook.
A resolution type consists of three pieces, all expressed as registry entries:
- An enum value-set entry under
enums/graph/ticket.claim.line/resolution/values/<key>that makes the resolution selectable on a claim line. - Zero or more per-resolution custom-field definitions under
apps/returns/claims/fieldDefinitions/line/<key>that show up in the claim-line panel when that resolution is selected. - A listening rule for
OnClaimResolvethat reads the resolution and any field values from the hook payload and emits effects (typicallyorderCommandeffects).
1. Resolution Enum Value
Each resolution is a value in the ticket.claim.line/resolution enum value set.
Registry path: /enums/graph/ticket.claim.line/resolution/values/<resolutionKey>
| Property | Type | Required | Description |
|---|---|---|---|
label | { default: text, <locale>: text, … } | ✓ | The label shown in the resolution dropdown. The default entry is the fallback; additional locale-keyed entries provide translations. |
hue | number | Optional hue used to tint the resolution chip in the portal. | |
requireInspection | boolean | Default (or forced) value for the claim line's requireInspection field when this resolution is selected. Defaults to false. | |
requireInspectionEditable | boolean | Whether the agent can toggle requireInspection after selecting this resolution. Defaults to false. |
Inspection Behavior Matrix
The combination of requireInspection and requireInspectionEditable controls whether the app creates an RMA for the line when the claim is completed:
requireInspection | requireInspectionEditable | Behavior |
|---|---|---|
true | false | Always creates an RMA. The agent cannot skip inspection. |
true | true | Defaults to creating an RMA; the agent can toggle it off. |
false | false | Never creates an RMA. The line is resolved immediately at claim completion via OnClaimResolve. |
false | true | Defaults to no RMA; the agent can opt into inspection per line. |
Example
- path: /enums/graph/ticket.claim.line/resolution/values/refund
value:
label:
default: Refund upon accepted return
requireInspection: true
requireInspectionEditable: false
- path: /enums/graph/ticket.claim.line/resolution/values/replace
value:
label:
default: Replace item
requireInspection: true
requireInspectionEditable: true
- path: /enums/graph/ticket.claim.line/resolution/values/compensateAmount
value:
label:
default: Compensate with fixed amount
requireInspection: false
requireInspectionEditable: false
- path: /enums/graph/ticket.claim.line/resolution/values/compensatePercentage
value:
label:
default: Compensate by percent
requireInspection: false
requireInspectionEditable: false
- path: /enums/graph/ticket.claim.line/resolution/values/manual
value:
label:
default: Manual action
requireInspection: false
requireInspectionEditable: false2. Custom Field Definitions
Resolution types often need extra inputs from the agent — a refund amount, a replacement product, a discount percentage, a message to the customer. These are declared as registry entries that the portal reads and renders inline in the claim-line panel.
Per-Resolution Fields (Line Level)
Registry path: /apps/returns/claims/fieldDefinitions/line/<fieldKey>
A field with a resolution property is only shown when that resolution is selected on the line. The value is stored as a dynamic field on the claim line under the key metadata_<fieldKey>.
| Property | Type | Required | Description |
|---|---|---|---|
type | 'text' | 'multiline' | 'number' | 'product' | ✓ | Editor type. text is single-line; multiline is a textarea; number uses a DecimalInput; product uses a product-picker. |
label | text | ✓ | Label shown above the editor. |
resolution | text | If set, the field is only shown when this resolution key is selected. If omitted, the field is shown for all resolutions (a claim-line-level field). | |
default | value | Default value used when no value has been set yet. | |
min | number | Minimum value for type: 'number'. | |
max | number | Maximum value for type: 'number'. | |
isReadOnly | boolean | If true, the editor is rendered disabled even when the claim is open. |
Claim-Level Fields
Registry path: /apps/returns/claims/fieldDefinitions/claim/<fieldKey>
Same shape as line fields, but rendered in the claim summary instead of the line panel, and stored on the claim's dynamic fields (still with the metadata_ prefix). Claim-level fields do not support the resolution property — they're always shown.
Storage and Hook Payload
All custom-field values are stored as dynamic fields named metadata_<fieldKey>. This means:
- The same prefix is used by the
createClaimsingress and stripped by thegetClaimByOrderingress. Passingmetadata.replaceProductNumberon the ingress request is equivalent to settingmetadata_replaceProductNumberon the line. - The keys appear as-is, including the prefix on the
metadatafield of theOnClaimResolvehook payload. The listening rule reads them viametadata->'metadata_<fieldKey>'.
Example
- path: /apps/returns/claims/fieldDefinitions/line/compensateAmount
value:
type: number
min: 0
resolution: compensateAmount
label: Refund Amount
- path: /apps/returns/claims/fieldDefinitions/line/compensatePercentage
value:
type: number
default: 0
max: 100
min: 0
resolution: compensatePercentage
label: Refund Percent
- path: /apps/returns/claims/fieldDefinitions/line/replaceProductNumber
value:
type: product
resolution: replace
label: Replace with Product
- path: /apps/returns/claims/fieldDefinitions/line/manualResolution
value:
type: multiline
resolution: manual
label: Message for customer3. Hook Listener
The third piece is a rule that listens to OnClaimResolve and turns the resolution and its metadata into actual effects — typically orderCommand effects targeting the connected order.
The standard listener pattern:
param input: {
hook: 'OnClaimResolve'
claimId: uuid
orderId: uuid | nothing
lines: [{
claimLineId: uuid
resolution: text
orderLineId: uuid | nothing
acceptedQuantity: number
productNumber: text | nothing
metadata: { text -> value }
}]
}
import 'iterators'
import 'maps'
// === compensatePercentage: emit a percentage order-line discount ===
from
input.lines
where is { orderLineId: uuid }
where l => l.resolution == 'compensatePercentage' and l.acceptedQuantity > 0
select l =>
let percentage = l.metadata->'metadata_compensatePercentage' match
(n: number) |> n
|> 0
from percentage > 0 match
true |> {
effect = 'orderCommand'
type = 'createStaticOrderLineDiscount'
orderLineId = l.orderLineId
isPercentage = true
value = percentage / 100
description = 'Claim compensation'
}
where is not nothing
// === compensateAmount: emit a fixed-amount order-line discount ===
from
input.lines
where is { orderLineId: uuid }
where l => l.resolution == 'compensateAmount' and l.acceptedQuantity > 0
select l =>
let amount = l.metadata->'metadata_compensateAmount' match
(n: number) |> n
|> 0
from amount > 0 match
true |> {
effect = 'orderCommand'
type = 'createStaticOrderLineDiscount'
orderLineId = l.orderLineId
isPercentage = false
value = amount
description = 'Claim compensation'
}
where is not nothing
// === replace: create a new delivery + order line for the replacement product ===
let replaceLines =
input.lines
where is { orderLineId: uuid }
where l => l.resolution == 'replace' and l.acceptedQuantity > 0
from replaceLines
select l =>
let replaceProductNumber = l.metadata->'metadata_replaceProductNumber' match
(v: text) |> v
|> l.productNumber
from {
effect = 'orderCommand'
type = 'createOrderLine'
productNumber = replaceProductNumber
quantity = l.acceptedQuantity
unitPrice = 0
}The condensed example above ignores some details that a real implementation needs (delivery routing for the replacement product, address handling, idempotency).
Order-bound orderCommands
orderCommand effects produced by OnClaimResolve listeners are applied to the connected order in a single transactional batch — your listener should not message the order directly.
Reject Reasons and Templates
Claims and claim lines can also be rejected rather than resolved. Reject reasons are declared as a global enum definition so the same set of reasons is available whether the entire claim is rejected or only individual lines, and each reason can be paired with a localized message template that pre-fills the message-to-customer field in the portal.
Reject reason value
Registry path: /enums/definitions/claimRejectReason/values/<reasonKey>
| Property | Type | Required | Description |
|---|---|---|---|
label | { default: text, <locale>: text, … } | ✓ | Label shown in the reject-reason dropdown. |
hue | number | Optional hue. | |
setKey | text | Optional category key — references a category at /enums/definitions/claimRejectReason/categories/<setKey>. |
The values are surfaced on the rejectReason graph field of both ticket.claim (whole-claim rejection) and ticket.claim.line (per-line rejection), which both declare enumDefinition: claimRejectReason.
Reject message template
Registry path: /apps/returns/claims/rejectMessageTemplates/<reasonKey>
The value is an i18n map of message strings keyed by locale, with default as the fallback:
- path: /enums/definitions/claimRejectReason/values/duplicate
value:
label:
default: Duplicate claim
- path: /apps/returns/claims/rejectMessageTemplates/duplicate
value:
default: 'Our records show that a previous claim has already been filed for the same issue.'
sv: 'Vi har redan tagit emot en reklamation för samma fråga.'When the agent selects a reject reason in the portal, the template for that reason in the order's locale is loaded into the message field, which the agent can then edit before sending.
See Also
- Returns Hooks —
OnClaimResolvereference. - Returns Graph — Where
resolutionand themetadata_*fields live. - Custom Hooks — Underlying
triggerHookruntime function.