Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions .changeset/sla-views-reports-write-real.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
---
'hotcrm': patch
---

Write the views / report / configuration half of `service/sla-and-escalation` to
what the app actually ships, in all three locales. Six claims sent readers
looking for screens, dropdowns and switches that do not exist; each was
re-confirmed against `origin/main` before being rewritten, and each keeps the
name it used so a reader who remembers it can find out what happened to it.

**Business hours are not configurable — there is no business-hours anything.**
The callout and the admin tip both promised that the clock could be told to
count 9-5 Mon-Fri instead of calendar hours, "controlled by your tenant's
business-hours setup". No working-day calendar, holiday list or setting of that
kind exists anywhere in `src/`; the only code that computes an SLA deadline is
`due.setHours(due.getHours() + 4)` in `src/objects/case.hook.ts`, straight off
the wall clock. Both places now say so, with the consequence spelled out: a
Critical case opened at 4pm Friday is due 8pm Friday, and nights, weekends and
holidays all count.

**The SLA Performance report has one breakdown, not four.** `sla_performance`
declares `rows: ['priority']` (`src/reports/case.report.ts`), so *Priority* was
the only one of the four bullets that was real — and the other three are not
merely absent from the report, they are unreachable in the semantic layer
underneath it. `case_metrics` (`src/datasets/case.dataset.ts`) declares five
dimensions — Status, Priority, Origin, Type, Created — with no owner/agent
dimension, no crossing over to `crm_account.tier`, and `created_date` bucketed
by **day** rather than month and not in that report's `rows` at all. The section
now states the one dimension, names each missing one with why it cannot be
selected, and records that the report measures the **SLA Violation Rate** over
closed cases (`runtimeFilter: { is_closed: true }`) rather than a compliance
percentage.

**Breached SLA and Critical Cases are not list views.** `crm_case` ships seven
(`src/views/case.view.ts`): *All Cases*, *Service Workflow*, *SLA Calendar*,
*Case Timeline*, *My Open Cases*, *Escalated Cases*, *⏰ SLA at Risk* — and none
of them filters on `is_sla_violated`. The two surfaces that do are metric tiles
on the Service Dashboard. The cadence table and the manager tip now point at
**Escalated Cases** (every case the sweep flags gets escalated into it) plus the
**SLA Violations** tile, and say plainly that the two old names name nothing.

**The kanban board is called Service Workflow.** `case_workflow` carries
`label: 'Service Workflow'` and appears on the case list as the **Workflow**
tab; *Service Board* exists nowhere in the app.

**My Open Cases is a priority queue, not a deadline queue.** Its sort is
`priority_rank` descending first, `sla_due_date` ascending only as a
tie-breaker — and since only Critical cases are stamped with a due date, the
tie-breaker has nothing to order the lower bands by.

**Waiting on Customer does not pause the SLA clock**, and there is no config
that makes it. `sla_due_date` is written once and never recomputed, and
`case_sla_monitor`'s `status: { $nin: ['resolved', 'closed'] }` does not exclude
`waiting_customer` — so a case parked on the customer keeps running down its
four hours and is flagged and escalated on schedule. This was the costly one:
an agent who believed the tip stopped watching a live clock.

Whether the platform *should* offer a business-hours calendar, per-agent or
per-tier SLA breakdowns, or a pausable clock stays open in #595 — this change
records today's behaviour only. Documentation in three locales; no metadata
under `src/` changed. Refs #917, #903, #886.
31 changes: 19 additions & 12 deletions content/docs/service/sla-and-escalation.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ In a list view, the thing that actually reports a breach is the **SLA Violated**

Critical's four hours is the one target the system keeps, and it lives in the hook rather than on a Setup screen — see the *Tips for admins* section below.

> **Business hours** — the system can be configured to count only business hours (e.g., 9-5 Mon-Fri) rather than calendar hours. See [Administration › Setup](/docs/administration/setup).
> **Those four hours are calendar hours, not business hours.** This app has no business-hours setting — no working-day calendar, no holiday list, nothing anywhere in its metadata that a deadline could be counted against. The hook adds four hours to the wall clock (`due.setHours(due.getHours() + 4)` in `src/objects/case.hook.ts`), so a Critical case opened at 4pm on a Friday is due at 8pm that same Friday, and one opened at 11pm is due at 3am. Nights, weekends and holidays all count against the target.

## How the SLA is tracked

Expand All @@ -44,12 +44,15 @@ Two views help with that, neither of them a timer:

## SLA performance reporting

The **SLA Performance** report shows the percentage of cases resolved within their SLA target, broken down by:
The **SLA Performance** report breaks its numbers down by **one** dimension: **priority**. Its `rows` are `['priority']` and nothing else (`src/reports/case.report.ts`). What it reports is not a compliance percentage either, but the **SLA Violation Rate** — the average of the **SLA Violated** flag — shown next to the case count and the average resolution time, and computed over **closed cases only** (`runtimeFilter: { is_closed: true }`). Of the four questions this page used to promise, it answers one: *which priority bucket is leaking?*

- Agent (who's hitting their SLA?)
- Priority (which priority bucket is leaking?)
- Account tier (are customers being treated worse than prospects?)
- Month (is performance trending up or down?)
The other three are not merely missing from the report — they are unreachable in the semantic layer underneath it. The `case_metrics` dataset (`src/datasets/case.dataset.ts`) declares exactly five dimensions — **Status**, **Priority**, **Origin**, **Type** and **Created** — so:

- **Agent** — there is no owner or agent dimension. `owner_id` is a field on the case, but the dataset does not expose it, so no report and no dashboard widget can group by it.
- **Account tier** — `crm_account.tier` exists on the account object, but `case_metrics` reads `crm_case` alone and never crosses over to the account, so tier is not selectable anywhere in analytics.
- **Month** — **Created** buckets by **day** (`dateGranularity: 'day'`), not by month, and the SLA Performance report does not put it in `rows` at all. The report that does use it is **Cases Opened by Priority × Day**, a matrix of priority against day.

Any of the three would mean changing `case_metrics` first; no setting on the report itself can bring them back.

This report is the support team's most-used KPI.

Expand Down Expand Up @@ -119,26 +122,30 @@ If priority resolves to **Critical**, the Copilot immediately recommends escalat

| Role | What to check | When |
| --- | --- | --- |
| **Agent** | My Open Cases (sorted by SLA Due Date) | Hourly during your shift |
| **Service Manager** | Breached SLA + Critical Cases | First thing every morning |
| **Agent** | My Open Cases — a priority queue, not a deadline queue | Hourly during your shift |
| **Service Manager** | Escalated Cases and SLA at Risk, plus the Service Dashboard's **SLA Violations** tile | First thing every morning |
| **Service Director** | SLA Performance report | Weekly |
| **Executive** | Service Dashboard | Weekly |

Two names this table used to carry are not views at all. **Breached SLA** and **Critical Cases** do not exist as list views: `crm_case` ships seven — *All Cases*, *Service Workflow*, *SLA Calendar*, *Case Timeline*, *My Open Cases*, *Escalated Cases* and *⏰ SLA at Risk* (`src/views/case.view.ts`) — and not one of them filters on **SLA Violated**. The only two surfaces that do filter on a breach are metric tiles on the [Service Dashboard](/docs/analytics/dashboards): **SLA Violations** (`is_sla_violated: true`) and **Critical Cases** (open and Critical). For a list you can actually work through, **Escalated Cases** is the closest thing: `case_sla_monitor` escalates every case it flags as breached, so each breach lands in it — mixed in with the cases escalated on priority alone.

**My Open Cases is sorted by priority, not by SLA Due Date.** Its keys are `priority_rank` descending first, and `sla_due_date` ascending only as a tie-breaker (`src/views/case.view.ts`) — so a Low case due within the hour still sits below every Critical one. And because only Critical cases are stamped with a due date at all, the tie-breaker has no values to order the bands below Critical by: inside those bands the order is whatever the store returns. Keep **SLA at Risk** or **SLA Calendar** open next to it if you are working to deadlines.

## Tips for service agents

- ✅ Watch **SLA Due Date** yourself — there is no countdown and no amber zone to warn you, so keep **SLA at Risk** or **SLA Calendar** open during your shift and ask for help before the date passes.
- ✅ Use **Waiting on Customer** when you're genuinely blocked — depending on your config, this can pause the SLA clock.
- ✅ Use **Waiting on Customer** when you're genuinely blocked — but know that it **does not pause the SLA clock**, whatever your configuration. Nothing in this app pauses, extends or recomputes a deadline: `sla_due_date` is written once, the first time the case is Critical, and no later status change touches it; the hourly `case_sla_monitor` sweep collects open cases whose status is neither *Resolved* nor *Closed*, and *Waiting on Customer* is not on that exclusion list (`src/flows/case-sla-monitor.flow.ts`). So a Critical case parked on the customer runs its four hours down and is flagged **SLA Violated** and escalated on schedule. Set the status — it tells the team where the case stands — but keep watching the clock.
- ✅ Don't artificially down-prioritise to extend your SLA — managers see priority change history.

## Tips for service managers

- ✅ Run the **Breached SLA** list view every morning.
- ✅ Use the **Service Board** kanban for daily standups.
- ✅ There is no **Breached SLA** list view to run. Work the **Escalated Cases** view instead — every case the SLA sweep flags gets escalated into it — and read the **SLA Violations** tile on the Service Dashboard for the count.
- ✅ Use the kanban board for daily standups — it is named **Service Workflow** (`case_workflow`) and reaches the case list as the **Workflow** tab. Nothing in this app is called *Service Board*.
- ✅ Coach agents whose breach rate trends up — surface it 1:1 before it becomes a pattern.

## Tips for admins

- The SLA target per priority is set via the case object's automation (a SLA-calculation hook). To adjust, edit `src/objects/case.hook.ts` (binds to `crm_case`) or change the underlying configuration — see [Administration › Automation](/docs/administration/automation).
- Business-hours vs. calendar-hours behaviour is controlled by your tenant's business-hours setup. See [Administration › Setup](/docs/administration/setup).
- **There is no business-hours vs. calendar-hours switch**, and no tenant business-hours setup behind it — this app ships no working-day calendar and no holiday list at all. The four-hour Critical target is added to the wall clock in `src/objects/case.hook.ts`, so nights, weekends and holidays count like any other hours; making a deadline skip non-working time means teaching that hook a calendar.
- The escalation trigger — Critical priority, and nothing else; there is no High+Customer condition — lives in the [Case Escalation flow definition](/docs/administration/automation#flows-multi-step), i.e. `src/flows/case-escalation.flow.ts`. Two flows carry it: `case_escalation` (on update) and `case_escalation_on_create` (on insert). Change the condition in **both**, or a case created at the new priority still slips through.
- Neither recipient list is configurable on a Setup screen. *Notify on Critical* is the Case Escalation flow's `notify` node, and its list is the single entry `{caseRecord.owner_id}` — edit it in `src/flows/case-escalation.flow.ts`. *Notify on Escalation* has no recipient list at all, because it sends no message: what the escalated status fires is the `case_status_side_effects` hook in `src/objects/case.hook.ts`, and the person it reaches is the account owner, through the follow-up task.
Loading
Loading