Skip to content

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:

  1. An enum value-set entry under enums/graph/ticket.claim.line/resolution/values/<key> that makes the resolution selectable on a claim line.
  2. 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.
  3. A listening rule for OnClaimResolve that reads the resolution and any field values from the hook payload and emits effects (typically orderCommand effects).

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>

PropertyTypeRequiredDescription
label{ default: text, <locale>: text, … }The label shown in the resolution dropdown. The default entry is the fallback; additional locale-keyed entries provide translations.
huenumberOptional hue used to tint the resolution chip in the portal.
requireInspectionbooleanDefault (or forced) value for the claim line's requireInspection field when this resolution is selected. Defaults to false.
requireInspectionEditablebooleanWhether 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:

requireInspectionrequireInspectionEditableBehavior
truefalseAlways creates an RMA. The agent cannot skip inspection.
truetrueDefaults to creating an RMA; the agent can toggle it off.
falsefalseNever creates an RMA. The line is resolved immediately at claim completion via OnClaimResolve.
falsetrueDefaults to no RMA; the agent can opt into inspection per line.

Example

yaml
- 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: false

2. 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>.

PropertyTypeRequiredDescription
type'text' | 'multiline' | 'number' | 'product'Editor type. text is single-line; multiline is a textarea; number uses a DecimalInput; product uses a product-picker.
labeltextLabel shown above the editor.
resolutiontextIf 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).
defaultvalueDefault value used when no value has been set yet.
minnumberMinimum value for type: 'number'.
maxnumberMaximum value for type: 'number'.
isReadOnlybooleanIf 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 createClaims ingress and stripped by the getClaimByOrder ingress. Passing metadata.replaceProductNumber on the ingress request is equivalent to setting metadata_replaceProductNumber on the line.
  • The keys appear as-is, including the prefix on the metadata field of the OnClaimResolve hook payload. The listening rule reads them via metadata->'metadata_<fieldKey>'.

Example

yaml
- 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 customer

3. 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:

filtrera
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>

PropertyTypeRequiredDescription
label{ default: text, <locale>: text, … }Label shown in the reject-reason dropdown.
huenumberOptional hue.
setKeytextOptional 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:

yaml
- 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

© 2024 Hantera AB. All rights reserved.