Configuration & Technical Handbook

This is the complete reference for the people who design and configure Rangeen — every feature you can add, customise and connect, with worked examples. It's written to be read as a manual: skim the contents, or work through it to build a case type from nothing to a finished workflow. If you only use the system day to day, the Everyday User Guide is the friendlier starting point. Everything here reflects how the system works today.

Case types & fields Screen Designer Automation grammar Templates & tokens Queries · Reports · Jobs Roles · Add-ons · Security

1 · How the system is built

Everything hangs off the case type.

A case is the record for one matter. A case type is the mould every case of a given kind is stamped from. When you design a system in Rangeen, you are almost always working inside a case type — because a case type owns nearly everything a case can do:

  • Fields — the data a case records (Database Management).
  • Screens — how those fields are laid out (the Screen Designer).
  • Templates — the letters, emails and memos it can produce.
  • Automations — the scripted routines that run for it.
  • Tasks & queries, assignment slots, quick-view config, collaborator profiles, and its lifecycle trigger bindings.

A few things are tenant-wide rather than per case type: the correspondent directory and its types, global variables, users / teams / roles, tenant settings, and the single organisation-wide case numbering sequence. The typical build order is: create the case type → define its fields → lay out screens → write templates → add automations → wire triggers → add queries/reports. Section 30 walks that path end to end.

Rangeen keeps an automatic version of a case type's whole design as you change it, and you can restore an earlier one — so designing is safe to experiment with. See section 25.

2 · Case types

The mould for each kind of matter.

Where: Sidebar → Configuration → Case types

  • A case type has a Code (short, immutable, unique), a Name, an icon and a description. Opening a case of that type brings all of its screens, templates, automations, tasks and queries.
  • Multi-user vs single-user (AllowMultiUserAccess): single-user locks a case to one person at a time; multi-user lets several work it, with the case-lock banner warning when someone else is in.
  • Enable / disable: a disabled case type disappears from the new-case and query pickers but keeps its existing cases and history. A case type can't be deleted while any case, screen, workflow, field or query still references it.
  • Numbering is organisation-wide. Case types no longer carry their own prefix or sequence — every case, whatever its type, takes the next number from one shared six-digit sequence (see Tenant settings).
Changed: earlier versions gave each case type its own reference prefix (e.g. PI-0011). Numbering is now a single tenant-wide numeric sequence (e.g. 000123) with no per-type prefix. The old prefix/digits settings are gone from the case-type editor.

3 · Fields & data types

The data model is yours — defined field by field in Database Management.

Where: Sidebar → Database Management (needs database.manage + fields.manage)

Every field has a Code (its internal short name — letters, digits, underscores and spaces; immutable once created), a display label (editable, and overridable per screen), and a data type (also immutable). Fields can be marked audited (every change stamped with who and when) and GDPR-searchable.

The fifteen field data types

Type (as you pick it)HoldsSettings
TextFree text — names, notes, references.Optional max length (1–10,000).
Whole numberAn integer, no decimals.Optional min / max.
DecimalA number with decimals — money, rates, measurements.Min / max; decimal places (0–10, default 2).
DateA calendar date, with a picker.Allow past / future; default fixed or TODAY±N.
Date & timeA date plus a time of day.As Date, plus an optional @HH:mm default.
TimeA time of day on its own.Optional default time.
Yes / No dropdownThree states: blank, Yes, or No.Optional default.
CheckboxTwo states — ticked or not.Default checked / unchecked.
DropdownA list of predefined values (each a Code + Description, plus any extra columns).Up to 20 extra option columns; values managed separately.
Scripted (computed)A read-only value calculated from other fields.A script expression + a declared return type.
Case linkA link to a case of another type; its fields are reachable in templates & scripts.The linked case type.
CorrespondentA "holder" pointing at one correspondent of a chosen type.The linked correspondent type.
TableA repeating sub-table on the case — you define its columns.Member columns (see below).
Time recordA start/stop time log, with optional extra columns.Member columns (see below).
Embedded documentA document slot on the case, with a template document.File uploaded in Database Management.

Tables (iteration tables)

  • A Table field holds no value of its own — it's a repeating sub-table. You open it and add columns, which are ordinary fields (Text, Number, Decimal, Date/DateTime/Time, Yes/No, Checkbox, Dropdown, or a Time record).
  • You can't nest a Table, a Case link, a Correspondent, a Scripted field or an Embedded document inside a table.
  • On a case it renders as a grid with a row count and an Add row button.

Time records

  • A Time record field logs work time. Each entry is one Start / Stop run, carrying start, end, a computed duration, who recorded it, and a note.
  • You can add extra columns (e.g. billable, hourlyRate, reason) — templates can sum these for invoicing.
  • A Time record can even be a column inside a Table, so each row owns its own start/stop log.

Scripted (computed) fields

  • A Scripted field's value is never stored — it's recalculated every time the case is read, so it's always current and read-only. You give it a script expression and declare what type it returns.
  • It reads other fields with get('{FieldName}'); a field named with spaces uses its exact name, e.g. get('{Original Debt}'). It can also read globals, linked-case fields and table rows.
  • A Scripted field can produce its own value but can't write to other fields, and it never breaks a case read — a broken formula just yields blank. The field editor has a Preview button to try a formula against a real case.

Example — a "Balance Outstanding" scripted field (return type Decimal)

get('{Original Debt}') - get('{Total Paid}')

Example — a "Total Paid" scripted field summing a table's rows

(get('{Payments[]}') || []).reduce(function (sum, row) {
  return sum + (Number(row['Amount']) || 0);
}, 0)

Links & embedded documents

  • A Case link stores the linked case's reference; templates and scripts "walk through" it to read the linked case's own fields.
  • A Correspondent field is a holder pointing at one correspondent of a chosen type (a "Main Doctor" holder accepting any Doctor). Its details merge into templates and are reachable in scripts.
  • An Embedded document field carries a template document that renders on every case of the type, and can hold a per-case document.

Constraints (max length, min/max, decimal places, allowed past/future dates) can be relaxed or tightened later; the field's Code and data type stay fixed for the life of the field.

4 · The Screen Designer

Lay the fields out the way the work reads.

Where: Sidebar → Screen Designer (needs screens.manage)

A screen is a data-entry form bound to one case type; a case type can have many, shown as tabs on the case. You build a screen by dragging tiles onto a grid. The tile kinds are:

TilePlaces
FieldA case field as an editable input. Also used for linked fields, embedded documents and correspondent holders.
LabelStatic text — with a chosen size and colour.
ButtonA button that runs an automation. Style: Primary / Secondary / Danger.
TableA sub-table (add / edit / delete rows). Also the tile used to place a Time record's start/stop log.
Web viewerAn embedded web page (an http(s) URL).
Image viewerA static uploaded image.
Correspondent attributeOne detail of a linked correspondent (name, email, address…).
GlobalA tenant global variable — editable (saving writes back to the global for the whole organisation).
Case controlA system property (reference, status, dates…) — always read-only.
AssignmentA detail of the user in an assignment slot — always read-only.

Draft & publish

  • Screens are edited as a draft. While you edit, everyone working cases keeps seeing the last published layout — your work-in-progress never leaks onto live cases.
  • Save & Publish makes the new layout live; Discard changes snaps back to the last published version.

Per-screen field settings

  • Label override, read-only on this screen, required on this screen — set per placement, so the same field can behave differently on different screens.
  • Display format — for Date / Date & time / Number / Decimal, picking a format renders the value as read-only formatted text; "None" keeps it editable. For Dropdown fields it picks which column the list shows.
  • Multi-line — turn a Text field into a wrapping text area.

Who sees a screen

  • Authorisation level — a numeric level a user must meet to see the screen (use it to keep, say, a "Costs" screen to supervisors).
  • Visibility rules — optionally show a screen only when the case's data meets a condition (e.g. a "Payment Plan" screen appears only once the stage is past "Pre-action"). The comparators are Equals, Not equals, Contains, Greater / Less (and or-equal), Is blank, Is not blank.

A screen can also bind an automation to run on submit — used together with the screens.open(...) verb (section 9).

5 · Quick View & case controls

A pinned summary, and the built-in properties every case has.

  • Quick View is a side rail shown on every case of the type — the fields that matter most, visible from every view. Configure it on the case-type detail page. It can hold case fields (including scripted fields and whole tables / time records), global variables, and case controls. It doesn't show correspondents, links or assignments.
  • Case controls are the built-in properties every case has, usable wherever fields are and in templates: Reference, Title, Status, Case type, Created, Created by, Last modified, Closed, Case ID.
  • Assignment slots are the case type's named roles (Case Worker, Supervisor…), each a user-picker that can be restricted to a role or team. Templates and scripts read them as {assignment:slot.attribute}.

6 · Lifecycle & field triggers

Bind an automation to a moment in a case's life.

Where: the case-type editor (lifecycle) · the field editor (on-change)

A case type has five lifecycle triggers, each an optional binding to an automation on the same case type:

TriggerFiresCan block?Can prompt?
On createRight after a new case is created — prefill values, greet the creator.NoYes (interactive create)
On openWhen a case is opened in the workspace (once per load).NoYes
On updateAfter each successful save of field values.A cancelled prompt stops the saveYes
On close requestedWhen the user clicks to close the case tab — before it closes.Yes — a throw or cancelled prompt keeps it openYes
On closedWhen marking the case closed / dead.Yes — gates the closeYes

Separately, any field can bind an automation to run on change — it fires when that field's value changes on save (in field order). And any screen can bind an automation to run on submit. All three are just ways to fire an automation; the automation itself is the same kind of script described next.

Not every trigger can pause to ask a question. Field on-change is always quiet — no prompts, and a failure doesn't undo the save. On create is quiet on the plain create path too (only the interactive New-case dialog can prompt). The other lifecycle triggers — On open, On update, On close requested, On closed — are driven from the screen and can prompt; and On update, On close requested and On closed can block the save or the close when the script fails or a prompt is cancelled. Keep ask.* prompts out of field on-change automations.

7 · Automations: the model

An automation is a small JavaScript program that reads and changes a case.

Where: Sidebar → Workflow → the case type → Automations

This changed — read this first. Automations used to be built from a visual step/rule builder. They are now code-first: the automation is a JavaScript script. There are wizards and a field picker that write the code for you, and conditions are ordinary JavaScript if / else — but the script is the real thing. Any older documentation describing a "visual rule builder" inside an automation is out of date.

Each automation belongs to one case type and has a Code (how it's referenced), a Name, an optional description, its Script, an Active flag (inactive automations never fire) and a Library flag (section 10).

  • The script runs in a sandbox. Ordinary JavaScript is available (if, loops, variables, Math, JSON, Date, String…), but there is no file system, no fetch, no require — only the platform verbs described here.
  • A script never changes data directly. Each verb queues what you want to happen (a field write, a task, a letter). When the run finishes successfully, the queue is applied in one go. So a half-finished run leaves the case untouched.
  • Within a run, a field you put() and then get() reads back the new value. (Whole tables are the exception — see the next section.)

8 · Reading & writing data (get / put)

Two verbs do all of it, keyed by a token in braces.

get('{token}')          // read a value  (an array for a {…[]} token)
put(value, '{token}')   // write a value (queued until the run ends)

The token in braces is the same field-picker token you see in templates. Reads come back properly typed — a number is a number, a Yes/No is a boolean. Here are the token forms:

TokenWhat it addressesRead / write
{field}A field on this case (the token is its name).read + write
{table[]}A whole Table, as an array of row objects.read + write
{bucket[]}A whole Time record log, as rows.read + write
{view:code[]}A table view's computed rows.read only
{Holder.attr}A detail (email, name, address…) of the correspondent in a link field.read only *
{global.key}A tenant global variable.read + write
{assignment:slot}The user in an assignment slot.read + write
{case.ref}, {case.status}, {system.today}Case & system controls.read only
{field|formatter}A field rendered as formatted text.read only
{link->leaf}Drill through a Case link into the linked case (chainable, up to 10 hops).read + write

* To change who a correspondent holder points at, write the correspondent's id to the bare holder token: put(id, '{Client}').

Working with tables

A {table[]} read gives you an array of row objects. Each row is keyed by the column's exact name (spaces and capitals kept) plus a hidden _id. Writing the array back is a full replace:

  • Rows that keep their _id are updated in place.
  • Rows with no _id are inserted.
  • Rows you leave out are deleted. put([], '{table[]}') clears the table. The array order becomes the table order.

So to add a row you read, push, and put back — as in the worked example in section 11.

Changed: the old typed sub-namespaces are gone. Use the token forms above instead of the removed calls: case_.fields.get/setget/put; case_.iteration_tables.* and case_.time_records.* → the {table[]} / {bucket[]} read-modify-write pattern; globals.get/setget/put('{global.key}'); cases.find(...)query.named('CODE'). The editor's Check button flags any removed call.

9 · The verb reference

Everything a script can do, grouped.

Read, write & log

get('{token}')Read a value (an array for a {…[]} token).
put(value, '{token}')Write a value; queued until the run ends.
log.info / warn / error(msg)Write to the run log (visible in history and the Test Run tab). console.log(...) works too.

This case & the run context

case_.id / .reference / .titleThis case's identity (read-only). For its status use get('{case.status}').
actor.name / actor.emailThe person running the automation.
correspondent, template, phaseIn a template hook: the recipient, the template being actioned, and whether this is the "Before" or "After" phase.
inputArguments passed in when another automation calls this one.

Talking to the operator (interactive runs only)

ask.confirm(label)Yes/No question → a boolean.
ask.text / number / date(label)Prompt for a value.
ask.choice(label, [options])Pick one of a list → the chosen string.
ui.message(text)Show a note and wait for acknowledgement.
ui.viewDocument(attachmentId)Show a case attachment, then resume.
ui.openCase(refOrId)Navigate the operator to a case once the run ends.
screens.open(name)Open a screen as a data-entry dialog mid-run → true if saved.

Correspondence & tasks

actions.send('CODE', {…})Fire a template (letter / email / memo / call / incoming) exactly like the manual action dialog. Common options: holderField or correspondentId (the recipient), description, preview (halt for review — off by default), attach, sendWithFields, email:{ send, to, cc, bcc }, diary, format.
actions.receiptIncoming('CODE', {…})Record an inbound document under an Incoming-post template.
tasks.create(title, {…})Create a task. Options: dueInDays, actionType (Generic / Letter / Email / Memo / PhoneCall / RunAutomation / IncomingPost), template or automation, assignTo, recipientRole, description.
tasks.cancel({…filters})Delete matching open tasks. Always pass a filter — with none it cancels every open task on the case.

Correspondents, other cases & reuse

correspondents.find(numberOrId)Look up a correspondent → its details or null.
cases.open(refOrId)Open another existing case → a handle with fields.get/set and save(), or null.
cases.create('CaseTypeName', {reference, sibling?})Create a case → its new reference. It materialises after the run, so populate it via a Case-link field and a drilled put.
cases.close({caseRef?, date?})Close this case (or another).
automation.run('CODE', args?)Run another saved automation inline (it reads args as its input). The drilled form automation.run('{link->automation:CODE}') runs it on the linked case.

Finding cases (read-only)

query.named('CODE', {take?, params?})Run a saved query by code — the way to find cases by their data. Returns matching cases with their values.
query.tasks / history / attachments({…})Read this case's open tasks, history events, or attachment list.

Files, web, dates & helpers

attachments.bundleIntoZip([ids], name)Zip existing attachments into one new attachment.
http.get / post / put / patch / delete(url, …)Call an outside REST service — only hosts on the tenant's allow-list (Tenant settings).
dates.today / now / addDays / addMonths / diffDays / format(…)Date maths and formatting.
helpers.money / pad / upper / lower / trim / isBlankSmall formatting helpers.

With the Accounts add-on, an accounts.* family lets scripts raise invoices, bills and credit notes — see section 26.

10 · When automations run

Eight ways to fire the same script.

Fired byHow you set it upPrompts?
A case-type lifecycle triggerBind it on the case type (on create / open / update / close-requested / closed).Some (see §6)
A field's on-changeBind it on the field; fires when the value changes on save.No
A screen buttonPlace a Button tile; it runs the automation you pick.Yes
A screen's on-submitBind it on the screen; runs after a screens.open submit.
A Run-automation taskCreate a task of kind "Run automation"; actioning it runs the script.Yes
A template Before / After hookBind it on a template; Before can cancel the action, After is best-effort.Yes
A scheduled Auto-routine jobA background job runs a saved query and runs the automation on each matching case.No
By hand — Run / Bulk run / Test runThe case's Automation button, the editor's Bulk run (up to 200 cases), or the Test Run tab.Run & Test: yes; Bulk: no

Interactive vs quiet runs

Some contexts can pause to ask the operator a question; some can't. Interactive runs — a screen button, the Run dialog, a Run-automation task, template hooks, most lifecycle triggers (on open / update / close-requested / closed) and Test runs — support ask.*, ui.message and previews. Quiet runs — field on-change, the plain on-create path, auto-routine jobs and bulk runs — can't prompt, so an ask.* there is skipped and logged. Write quiet automations to decide everything from the data.

Library automations

Tick Library to make an automation a reusable helper. A library automation can only be called from another script via automation.run('CODE') — it's hidden from run buttons, task pickers and bulk run. Use it to share logic across several automations on the case type.

11 · Writing, testing & examples

The editor writes code for you — and never lets you save broken code.

The editor

  • Add a step opens a palette of platform actions (send a letter, put a value, create a diary task, create a case, run a query, ask the user, run another automation, raise an invoice…). Each wizard writes the exact code at your cursor — you can then edit it freely. (Ordinary logic — if, loops, comments — is just JavaScript; the palette only covers the platform actions you couldn't type yourself.)
  • The field picker inserts a get('{token}') for any field, correspondent, global, assignment or whole table; a Snippets menu drops in common patterns (read/put a field, if/else, table read-modify-write, send, a diary date, an HTTP call…).
  • Check runs the parser and the linter against your case type's real design. Anything that would break — an unknown field token, a template code that isn't on this case type, a tasks.cancel with an unknown filter — is a hard error that blocks Save. Softer issues (an option a call ignores) are warnings.
  • Test Run runs the script against a real case reference (or a synthetic test case) without saving anything — prompts and previews behave exactly as live, and the result panel shows the log and the fields it would set.
  • Bulk run runs it across up to 200 cases of the type, committing each case independently.

Example — a button that sends a letter, then leaves a reminder

// Interactive run button. Sends the welcome letter to the case's Client
// correspondent, with an operator preview, then leaves a chase reminder.
if (ask.confirm('Send the welcome letter to the client?')) {
  actions.send('LTR-WELCOME', {
    actionKind: 'Letter',        // builder metadata; the engine resolves by code
    holderField: 'Client',       // the Correspondent field on the case
    description: 'Welcome letter to {Client.displayName}',
    preview: true                // halt so the operator can review before it commits
  });

  tasks.create('Chase signed engagement letter', {
    dueInDays: 14,
    actionType: 'Generic',
    assignTo: 'case_worker',     // an assignment-slot code
    description: 'Call the client if the signed letter has not been returned.'
  });
}

Example — a field on-change that raises a task when the status flips

// Bound to the 'Status' field's on-change. When the case moves to
// "Awaiting Medical Report", raise a diary task addressed to the Solicitor.
var status = get('{status}');

if (status === 'Awaiting Medical Report') {
  tasks.create('Request medical report', {
    dueInDays: 7,
    actionType: 'Letter',
    template: 'LTR-MED-REQUEST',   // must exist on this case type
    recipientRole: 'Solicitor'     // a Correspondent holder field on the case
  });
  put(dates.today(), '{report_requested_on}');
}

Example — updating a table and logging time

// Apply a 5% uplift to every non-void row of the Disbursements table,
// drop voided rows, add a new line, then record the time spent doing it.

// --- table: read, change the array in JavaScript, put the FINAL array back ---
var rows = get('{disbursements[]}') || [];
var kept = [];
for (var i = 0; i < rows.length; i++) {
  var row = rows[i];
  if (row['Status'] === 'Void') continue;                 // left out = deleted on put
  row['Net Amount'] = (row['Net Amount'] || 0) * 1.05;    // EXACT column name
  kept.push(row);                                         // keeps _id = update in place
}
kept.push({ 'Description': 'Admin fee', 'Net Amount': 25 }); // no _id = insert
put(kept, '{disbursements[]}');

// --- time record: append one entry to the Attendance log ---
var att = get('{attendance[]}') || [];
att.push({
  started_at: dates.now(),
  ended_at: dates.now(),
  note: 'Fee uplift run'
});
put(att, '{attendance[]}');

log.info('Uplifted', kept.length, 'disbursement rows');

Token names such as disbursements, Status, Client and LTR-WELCOME are examples — they must match your case type's real field / column names and template codes, which is exactly what Check verifies.

12 · Merge tokens & formatters

Placeholders that fill themselves from the case.

Where: Sidebar → Workflow → the case type → Templates

Templates (letters, emails, memos) merge case data through single-brace tokens. Token matching is case-insensitive and ignores spaces inside the braces; a field named with spaces uses its snake-case token (Date Of Birth{date_of_birth}). An unknown token renders as nothing — never as literal braces.

To merge…Write
A case field{field_name}
A global variable{global.key} — e.g. {global.firm_name}
A case property (control){case.reference}, {case.title}, {case.status}, {case.created}
A correspondent's detail{Holder.attribute} — e.g. {Client.email}, {Client.address_block}
Through a case link{linkedMatter->claim_amount} (chainable, up to 10 hops)
An assignment slot's user{assignment:case_worker.name}
A table / time-record total{timesheet.count}, {timesheet.sum.hours}, {timesheet.latest.note}
Today / now / the sender{system.today}, {system.now}, {system.user.name}

Correspondent attributes include displayName (alias name), organisationName, contactPerson, email, phone, mobile, addressLine1/2, address_block (a multi-line postal block), city, postcode, fullAddress, plus any custom field on that correspondent type.

Changed: the merge grammar is single-brace ({claim_amount}), globals use a dot ({global.key}), case properties are the case. family, and correspondents merge through their holder field ({Client.email}). Older docs showing double-brace {{case.field}} or a control: / global: prefix describe the legacy quick-memo engine, not designed templates.

Formatters

Append a pipe to format a value: {token|formatter}. Formatters fold left to right, so {name|trim|upper} is upper(trim(name)). A large selection is built in, including:

  • Text: upper, lower, title, sentence_case, trim, initials, first_word.
  • Dates: date_uk_slash (dd/MM/yyyy), date_long_uk (1 January 2026), date_with_day_name, month_year, date_iso, and many more; plus date-part formatters (day_ordinal, month_name, year_4).
  • Times & date-times: time_24h, time_12h, datetime_uk_slash, datetime_long_uk.
  • Numbers & money: value_with_commas, value_in_words, value_2dp, currency_gbp (or pounds), pounds_in_words, currency_usd, currency_eur.
  • Durations & booleans: duration_hhmm, duration_hours_decimal, yes_no, ticked.
  • For a Dropdown field, {status|code} / {status|description} / a custom column name picks which column shows (Description is the default).

13 · Conditional blocks

One template that adapts to the case's data.

Wrap text in an inline condition so it only appears when the case matches. Blocks can nest, and the first matching branch wins:

{#if claim_amount >= 10000}
   …multi-track paragraph…
{#elseif claim_amount >= 1000}
   …fast-track paragraph…
{#else}
   …small-claims paragraph…
{#endif}

A marker on its own line is removed cleanly (no blank line left behind), and this works in plain text, HTML email/letter bodies, and Word documents — where a block may span whole paragraphs or table rows and the winning branch keeps its formatting — and in Excel cells.

The expression language

A condition is an SQL-like expression (keywords are case-insensitive). Reference a field by name ([Date Of Birth] in brackets if it has spaces), and combine with and / or / not and parentheses:

  • Comparisons: =, !=, >, <, >=, <=.
  • field is empty / field is not empty.
  • field in ('a','b') / field not in (…); field between x and y.
  • field contains 'x' / starts with 'x' / ends with 'x'.
  • Right-hand values: quoted text, numbers, true/false, empty, relative dates today/now (with +N/-N), the current user me, a runtime parameter :name, or another field.

A malformed marker is left as inert text rather than throwing, and a broken expression evaluates to false (so dubious content is simply not shown). Save-time validation flags the first bad expression.

14 · Table views in documents

Turn a case's rows into a table in a letter — including running totals.

A Table View is a named, reusable definition scoped to a case type. It reads a case's real rows (read-only) — from a Table or Time record field, filtered, sorted and with extra computed columns, or from a small script that returns rows. You reference it in a template with the {view:code…} token family:

TokenGives
{view:code} or {view:code.count}The row count.
{view:code.sum.col} / .avg. / .min. / .max.An aggregate over a column.
{view:code.first.col} / .last.colThe first / last row's cell.
{view:code[N].col}The Nth row's cell (1-based).
{view:code[].col}Repeating — put this in one table row in Word/Excel and the engine clones it per row.

Example — a billable-items table in a Word letter

| Date                                           | Description                        | Amount                                     |
| {view:billable[].work_date|date_uk_slash}      | {view:billable[].description}      | {view:billable[].amount|currency_gbp}      |

Items: {view:billable.count}     Total: {view:billable.sum.amount|currency_gbp}

Put the {view:billable[].…} tokens in a single template row inside the Word table; the engine repeats that row for every row in the view. A view can set what happens when it's empty (keep the header, remove the table, or drop in a fallback paragraph), and has a row cap (up to 500) that fails loudly rather than truncating silently.

15 · Template kinds, versions & hooks

One template library per case type.

KindBacked by
MemoRich text authored inline — an internal note-to-file.
LetterA Word (.docx), Excel (.xlsx) or PDF-form file, rendered per case.
EmailRich-text / HTML body, sent through the user's Outlook (or a shared mailbox).
LetterheadA Word file used as the masthead on letters and emails.
Phone callNo body — records a call note (take / make / both).
PDF formAn uploaded fixed PDF a Letter fills in (see next section).
Incoming postA named inbound correspondence type.

Versioning

Every save mints a new immutable version; the template points at the current one, and you can revert to any earlier version with no data loss. Crucially, a generated document keeps the version it was made from — editing a template later never alters documents already produced.

Send-with, embed & diary

  • Send-with documents — attach an Embedded-document field's file alongside the correspondence (using the case's per-case copy where present).
  • Embed into — drop the rendered output into an Embedded-document field on the case as well as into history.
  • Diary follow-up — any kind can schedule a follow-up task on action (a number of days out, with an action of your choice); the send dialog lets the user adjust or suppress it.

Before / After hooks

A template can run an automation before it's actioned (a failure there cancels the action — useful for validation) and after (best-effort — a failure is logged but never rolls back the correspondence). Point each at a saved automation on the Automation tab. These hooks are the one place a script sees the action's correspondent, template and phase.

16 · Letterheads & PDF forms

Letterheads

A Letterhead is a Word document holding your firm's header and footer (logo, address bar, page setup). Any letter or email template can pick one; at render time Rangeen transplants the letterhead's header/footer onto the letter and inherits its page size and margins, leaving your authored body untouched — "apply the branding, keep my content".

PDF forms

A PDF form template is a fixed PDF (a court or insurer form) you fill from the case. Two ways to fill it:

  • AcroForm fields — if the PDF has form fields, map each one to a token (e.g. a field to {claim_amount}); at send the tokens resolve and the fields fill.
  • Overlay boxes — drop styled text boxes onto specific pages, each bound to a token or literal, with font, size, bold/italic and alignment. At send, every box is editable in a preview so the operator can adjust before committing.

Either way the case data does the filling, and the finished PDF lands on the case.

17 · Global variables

Organisation-wide values, defined once.

Where: Configuration → Global variables (edit needs globals.manage)

  • A global is a tenant-wide value — a standard rate, a firm name, a fee amount, an address. Each has a Key (immutable), a label, a type (the same set as case fields) and a value. Mark one as a secret to mask it in the UI (scripts still see the real value).
  • Reference a global in a template as {global.key}, and in an automation as get('{global.key}') / put(value, '{global.key}').
  • Placed on a screen as a Global tile, a global is editable — and saving it changes the value for the whole organisation, not just the case.
Changed: the old globals.get('key') / globals.set(...) script calls are gone — use the get/put('{global.key}') token form.

18 · Queries

Saved questions over the caseload — build once, run forever.

Where: Queries → Manage queries (build) · Queries (run) — build needs queries.manage

A query targets one case type and combines criteria — a field, a comparator and a value — joined with And / Or and nested groups, so you can build shapes like (A or B) and (C or D). Besides the case type's own fields you can filter and sort on five built-in columns: Reference, Title, Status, Created and Last modified.

Comparators

Equals, Not equals, Contains, Starts with, Ends with, Greater than, Less than, Greater-or-equal, Less-or-equal, Between, Is blank, Is not blank, In list, Not in list.

Smart values

A value beginning with @ is resolved when the query runs:

Smart valueResolves to
@today, @today+N, @today-NToday's date (± N days).
@now, @now+N, @now-NThe current time (± N minutes).
@me (or @currentuser)Whoever is running the query.
@field:NameThe value of another field on the same case.
@param:NameA parameter the runner is prompted for.

Parameters

Declare typed parameters so one query serves every variation — types are Text, Number, Date, Date & time, Time, Yes/No, Option (a dropdown drawn from an Option field) and User. Each can carry a label, a default and a required flag.

Columns, sort, run & export

Choose the result columns and the sort order (multi-field), and whether to include closed cases. Run it from the Queries page, filling any parameters; then export to CSV or Excel (all rows or just the ones you select), or apply a bulk update to the selection. Running needs queries.run; a query never returns a case the runner isn't allowed to see.

Example — "Overdue high-value matters for a fee earner" (Litigation)

One User parameter (owner) and one Number parameter (threshold, default 50,000), reading:

Assignee   equals            @param:owner
AND Target Date  less than    @today
AND Claim Value  >=          @param:threshold
AND ( Status equals 'Open'  OR  Status equals 'On hold' )

Sort by Target Date ascending; show Reference, Title, Claim Value, Target Date, Assignee. (The last group is equivalent to a single Status in list "Open, On hold" criterion.)

19 · Reports

A query, presented properly.

Where: Reports → Manage reports (build) · Reports (run) — build needs reports.manage

A report binds a saved query and adds presentation:

  • Columns to project, and an optional inline filter that narrows the query without forking it.
  • Grouping — one or more levels; each level gives a band header per bucket (with its count) and, when you turn on subtotals, a subtotal row per bucket.
  • Roll-ups — Count, Sum, Average, Min or Max on chosen fields, shown as a grand total (and as the per-group subtotals).
  • A chart — Bar, Pie or Line, computed over the whole result set so it never disagrees with the table.
  • Output — a PDF (portrait or landscape, with a subtitle, header and footer), an Excel workbook, or a CSV; plus a max-rows cap.

Whoever runs it fills the query's parameters. Running needs reports.run; building needs reports.manage. A report can be scheduled to run and email the file automatically (next section).

Example — a report on the query above

Group by Assignee (show count + subtotals); roll up Claim Value as Sum, Average and Count; add a Bar chart of summed Claim Value per fee earner; output a landscape PDF. Running it produces one band per fee earner with a subtotal row, a grand total, and the chart above the table.

20 · Background jobs

Scheduled work, defined and watched in the open.

Where: Sidebar → Background jobs (needs jobs.manage)

You can create three kinds of job:

  • Auto routine — run a saved query on a schedule and run an automation on every matching case (with a dry-run option to preview which cases would be hit).
  • Scheduled report — render a report and email the file to a list of recipients.
  • Task digest — the daily "here's what's due" email, per person (or a global digest).

Each job has a Code, name, a cron schedule (five fields, in UTC — with preset buttons like "Daily at 08:00" or "Weekdays 08:00"), an enabled switch, and Run now. A blank schedule means on-demand only. Expanding a job shows its run history: started, trigger (scheduled or manual), status, and match / success / failure counts. The Accounts and Data Export add-ons run their own jobs through this same machinery.

21 · Correspondents & types

The tenant-wide directory of everyone cases deal with.

Where: Sidebar → Correspondents · types under Configuration (needs correspondents.manage)

  • Correspondent types are yours to define — Solicitor, Hospital, Expert, Court, Insurer, Client — each with a Code, name, icon, and up to 30 custom fields. There's no fixed built-in list.
  • A correspondent belongs to one type and carries a standard set of details (display name, organisation, contact person, full address, email, phone, mobile, website, notes) plus that type's custom fields. Each gets a tenant-wide number.
  • Cases point at correspondents through Correspondent fields on their screens — that's how a case "addresses" a letter or email, and how a correspondent's details merge into templates.
  • A correspondent's page lists the cases they appear on; the same directory feeds every picker in the system.

22 · Users, teams & roles

Who's in, how they're grouped, what they may do.

Where: Sidebar → Configuration → Users / Teams / Roles

Users

  • Create a user with an email, name and exactly one role. They receive a branded invitation with a single-use set-your-password link and finish their own setup — the invitation never carries a password.
  • One Microsoft directory backs every organisation a person belongs to, so the same person uses one credential across tenants. "Reset password" re-issues a set-password link; it doesn't change their existing password until they redeem it.
  • Users are never deleted (their name is needed in history) — they're disabled. A billable, enabled user counts against your licence allowance. Licence types are Standard, Lite, Background worker (not billable) and External (collaborators). You can't disable your own account or the last enabled administrator.
  • Custom user fields (configured in Tenant settings) add your own attributes to every user — hourly rate, branch, and so on — available to automations and reporting.

Teams

A team groups users for assignment and visibility. A team can carry its own permission grants, which are added to each member's permissions. New users can auto-join a default team.

Roles & how permissions combine

A user's effective permissions are: their one role's grants, plus the grants of every enabled team they're in, plus any extra permissions granted to them individually, minus any revoked from them (a revoke always wins). There are no hidden defaults — the role is the only baseline.

  • Three roles are seeded and can't be deleted (but can be renamed): Tenant Administrator (all permissions), Standard User and Read-only User.
  • The Tenant Administrator role is a wildcard — it holds every permission — and the system always keeps at least one enabled user in it.

Permissions are enforced on the server for every sensitive operation — the menu hiding you see is just convenience on top of that.

23 · Permission reference

The keys you assign to roles and teams.

GroupKeys
Casescases.view, cases.create, cases.edit, cases.close, cases.reopen, cases.delete, cases.bulk
Workflowworkflow.action.email, workflow.action.letter, workflow.action.memo, workflow.action.phone, workflow.action.incoming, workflow.template.manage
Correspondentscorrespondents.view, correspondents.manage
Triagetriage.view, triage.release, triage.archive
Taskstasks.view, tasks.create, tasks.complete, tasks.reassign, tasks.reschedule, tasks.delete.own, tasks.delete.other
Queries & reportsqueries.run, queries.manage, reports.run, reports.manage
Configurationcasetypes.manage, screens.manage, fields.manage, automations.manage, globals.manage, database.manage, jobs.manage
Administrationteams.manage, users.manage, roles.manage, settings.manage, collaborators.manage
Outlookoutlook.connect, outlook.shared.manage
AI Add-onai.use, ai.ask, ai.draft, ai.author_templates, ai.author_automations, ai.design, ai.pro… (only when the AI add-on is enabled)
Accounts Add-onaccounts.invoices.view, accounts.invoices.manage, accounts.integration.manage
Data Export Add-ondataexport.run, dataexport.manage

A few notes: rescheduling a task is its own key, separate from creating one; task deletion is split into "own" and "others'"; correspondents, roles, settings and collaborators management are each grantable independently. The AI, Accounts and Data Export groups only appear when the matching add-on is enabled.

24 · Tenant settings

Organisation-wide switches, in one place.

Where: Configuration → Tenant settings (needs settings.manage)

  • Organisation name — shown in the header, browser title and outbound system emails.
  • Idle sign-out — minutes of inactivity before sign-out; 0 means never (the default, so a daily-use office app doesn't log people out). When set, it's 5–1440 minutes.
  • Default case-list page size (10–500).
  • Case numbering — start from — the floor for the shared six-digit sequence; the next case is this value + 1. It only ever moves forward.
  • Email matcher fields — per case type, the extra fields inbound email is matched on beyond the case reference (including matching on a correspondent's attribute).
  • Custom user fields — the extra attributes on every user record.
  • Allowed hosts for automation HTTP — the hostnames scripts may call with http.get/http.post. Empty (the default) means outbound HTTP is off; internal / private addresses are refused.

25 · Import, export & design versions

Bulk cases

  • CSV import creates cases in bulk for one case type: a header row naming the fields (plus an optional Title column), then one case per row, up to 5,000 rows. Unknown columns are skipped and logged. This is an administrator operation via the system API.
  • CSV export produces every case of a case type — reference, title, status and every field.
  • For everyday extracts, use Queries → Export and report outputs — those are self-service.

Design versions — restore, export & clone a case type

Rangeen keeps automatic versions of a case type's whole design (its fields, screens, templates, automations and settings) as you change it. From the case type you can:

  • List versions and see who changed what, when.
  • Preview and restore an earlier version — a restore is confirm-gated for anything it would delete, and takes a fresh snapshot first so you can undo the undo.
  • Export a version (or the live design) as a zip, and import a zip to clone it into a brand-new case type — a clean way to move a design between environments or start a new type from a proven one.

26 · Accounts (Xero) Add-on

Invoices, bills and credit notes — kept beside the cases, posted to Xero.

Where: Sidebar → Accounts (when the add-on is enabled)

Xero-only. The Accounts add-on runs entirely on Xero — there is no built-in ledger to maintain inside Rangeen, no chart of accounts to keep here, and no QuickBooks option. Accounting statements (VAT, P&L, balance sheet, aged reports) live in Xero, not in Rangeen. Enabling "Accounts" simply means this organisation is on Xero.
  • Connect once under Accounts → Xero, pick your Xero organisation, and it stays connected — the connection is kept alive automatically and won't lapse from a quiet month. Only revoking it in Xero or disconnecting here ends it.
  • Documents: raise invoices (money in) and bills (money out) — two directions of one thing — and credit notes. Each flows Draft → Approve, and can be Disputed / cleared / Voided. Push to Xero, and payment status flows back (amount paid, amount due, Xero status).
  • Per-case financials: a money-in / money-out / paid-vs-outstanding rollup for each matter.
  • Mapping: default sales/purchase accounts and tax types, tracking categories, contact matching, and auto-push on approval — configured under Xero settings.

Automations can post accounting documents too, through the same service as the UI — so a script-raised invoice is identical to a hand-raised one:

// Raise a fixed-fee invoice for this case and approve it
var inv = accounts.createInvoice({
  contactName: get('{Client Name}'),
  contactEmail: get('{Client Email}'),
  reference: 'Fixed fee - ' + case_.reference,
  dueInDays: 30,
  approve: true,
  lines: [
    { description: 'Professional services', amount: 750, taxType: 'OUTPUT2' }
  ]
});
log.info('Raised invoice handle ' + inv);

The accounts.* family also includes createBill, createCreditNote, approve, dispute, void, attach and documents().

27 · AI assistant & designer Add-on

Optional help answering about a case and building the system.

The AI add-on adds a few assistants: a per-case assistant that answers questions about the case you have open; a Pro / Builder assistant for cross-case work and building; and a Designer AI pinned to a case type that helps construct that case type. Usage draws on a monthly credit pool, and an AI usage page shows the balance and recent activity. AI is gated by permission and credits, and its changes are subject to your approval — but it can never do anything a user couldn't do by hand. (This handbook keeps AI coverage light on purpose; the assistants are optional and self-explanatory in use.)

28 · Collaborate portal Add-on

External parties see exactly what you share — nothing else.

Where: Sidebar → Collaborators (admin) · the Share option on a case

  • External users are invited by email and sign in at <workspace>-collaborator.rangeen.com with their own credentials — never with staff accounts.
  • A share grants one external user access to one case. You choose the screens, each set to Hidden, Read-only or Editable, and within an editable screen you can hide or lock individual fields. The share is a frozen snapshot — editing the case type's profile later does not widen existing shares unless you re-snapshot them.
  • Per share you can set an expiry, whether it survives case closure, and the attachment rights (upload / download / see others' uploads). A named collaborator profile per case type gives you a reusable default.
  • In their portal, collaborators get My cases and the shared screens as tabs. Where allowed they edit values (including tables and time records) and upload/download attachments. They can't see tasks, notes, the audit history, unshared screens, or any other case. Everything they do lands on the case history under their name.
  • A documents conversation — a simple back-and-forth of files per (case, collaborator) — is available on the case's Collaborators view; it's deliberately kept out of the audit/fields history.

29 · Security model

How the system keeps the right people in and everyone else out.

  • Isolation — each organisation's data lives in its own database schema; separation is structural, and the tenant is resolved from the subdomain on every request.
  • Identity — staff sign in with Microsoft Entra (one directory backing many organisations); external collaborators use a separate credential system confined to their portal.
  • Authorisation — role/team permissions are enforced server-side on every sensitive operation; the front-end menu gating is convenience only.
  • Accountability — per-field audit and an immutable case history attribute every change to its actor — person, automation or collaborator — and disabled users are retained so their name persists.
  • Hardening — automation outbound HTTP is off by default and host-allow-listed (with private-address guards), CSV export is protected against formula injection, secrets are masked in the UI, and licence caps and last-admin protection are enforced server-side.
  • Transport & hosting — TLS on every address including each organisation's own subdomain; hosted on Microsoft Azure with staged, health-checked releases.

30 · Design a case type end to end

Putting it together: a "Debt Recovery" case type from nothing to a working workflow.

Step 1 — Create the case type

Configuration → Case types → New. Code DEBT, name "Debt Recovery", multi-user access on.

Step 2 — Define the fields

Database Management, on the DEBT type:

FieldType
Debtor NameText
Original DebtDecimal (2 dp)
Interest RateDecimal (2 dp, 0–100)
Date InstructedDate (default TODAY)
Payment Due DateDate (default TODAY+30d)
StageDropdown — Pre-action / LBA sent / Claim issued / Judgment / Enforcement
Assigned SolicitorCorrespondent (type = Solicitor)
PaymentsTable — columns: Payment Date (Date), Amount (Decimal), Method (Dropdown)
Total PaidScripted → Decimal (sums the Payments table)
Balance OutstandingScripted → Decimal (Original Debt − Total Paid)
Time On MatterTime record (extra columns: billable, hourlyRate)

Total Paid = (get('{Payments[]}') || []).reduce(function (s, r) { return s + (Number(r['Amount']) || 0); }, 0); Balance Outstanding = get('{Original Debt}') - get('{Total Paid}').

Step 3 — Lay out the screens

Screen Designer, on DEBT:

  • Overview — Case-control tiles (Reference, Status), the debtor & Stage, Original Debt, Balance Outstanding (read-only), the Assigned Solicitor, Payment Due Date, and an assignment tile for the Case Worker.
  • Payments — a Table tile on Payments, plus Balance Outstanding and a Global tile for the firm's default interest rate.
  • Time & Costs — a Table tile on Time On Matter (its start/stop log), with an authorisation level so only supervisors see it.

Give the Payments screen a visibility rule of Stage is not "Pre-action" so it only appears once action has started.

Step 4 — Add templates

Workflow → DEBT → Templates: a Letter Before Action (Word letter to the debtor, using {Debtor Name}, {Original Debt|currency_gbp} and a conditional paragraph on {#if [Balance Outstanding] > 5000}), and a Statement of Account letter using a {view:payments…} table view of the Payments table.

Step 5 — Add an automation

Workflow → DEBT → Automations → New, code SEND-LBA. It sends the Letter Before Action to the debtor, sets the stage, and diarises a chase:

actions.send('LTR-LBA', {
  actionKind: 'Letter',
  holderField: 'Debtor',
  description: 'Letter Before Action to {Debtor Name}'
});
put('LBA sent', '{Stage}');
tasks.create('Chase response to LBA', {
  dueInDays: 14,
  actionType: 'PhoneCall',
  assignTo: 'case_worker'
});

Run Check, then Test Run against a real DEBT case, then place a Primary button "Send Letter Before Action" on the Overview screen that runs SEND-LBA.

Step 6 — Wire triggers

On the case type, bind On create to an automation that sets Stage = "Pre-action" and Payment Due Date = TODAY+30d, and bind On close requested to one that blocks closing while there's a balance:

if (Number(get('{Balance Outstanding}')) > 0) {
  ui.message('Cannot close — a balance of ' +
             get('{Balance Outstanding|currency_gbp}') + ' is still outstanding.');
  throw new Error('Balance outstanding');   // a throw here keeps the case open
}

Step 7 — Add queries & a report

Build a query "Overdue debts" (Payment Due Date < @today AND Balance Outstanding > 0, including a Stage parameter), and a report on it grouped by Stage with a Sum of Balance Outstanding and a bar chart. Optionally add an Auto-routine job that runs "Overdue debts" every morning and fires a "chase" automation on each match.

That's a complete case type — data, screens, letters, automation, lifecycle rules and reporting — built from the pieces in this handbook. Every change you made was captured as a design version you can restore.