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
7 changes: 7 additions & 0 deletions .changeset/2060-item-3-agent-loses-case.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"hotcrm": patch
---

**A service agent no longer loses a case the moment it is escalated.** An escalated case is handed to the Service Manager with the fewest open cases, and an agent only sees the cases they own. So an agent who logged a Critical case saw "Case created successfully" over a "Record not found" page. An agent who pressed Escalate Case got the same page when it refreshed.

The case now records the agent it was taken from in a new read-only **Escalated From** field. A new sharing rule, *Escalated Cases — Previous Owner*, lets that agent read the case until it is closed. They can follow the case but cannot edit it: the manager works it. No other agent gains access, and the manager's and director's access is unchanged. A case escalated with no owner names nobody, and nobody can type a name into the field.
1 change: 1 addition & 0 deletions content/docs/administration/sharing-and-security.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ Criteria-based rules are the enforced flavour: matching records materialise real
| Escalated Cases Sharing | Case | Edit | `service_manager` — open critical cases |
| Escalated Cases — Service Director | Case | Read | `service_director` — the same cases, one rung up |
| Unassigned Cases — Triage | Case | Edit | `service_agent` — unowned cases, until they are Resolved or Closed |
| Escalated Cases — Previous Owner | Case | Read | the user in the case's `escalated_from` field — the agent an escalation took it from, until it is Closed |
| Live Campaigns — Marketing Manager | Campaign | Edit | `marketing_manager` — planning / in-progress campaigns |
| Live Campaigns — Marketing Director | Campaign | Edit | `marketing_director` — the same campaigns |

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ na_sales_team eu_sales_team (区域分组)
| Escalated Cases Sharing | 工单 | 编辑 | `service_manager` —— 未关闭的严重工单 |
| Escalated Cases — Service Director | 工单 | 读取 | `service_director` —— 同一批工单,上一层级 |
| Unassigned Cases — Triage | 工单 | 编辑 | `service_agent` —— 没有负责人的工单,直到它被 Resolved 或 Closed |
| Escalated Cases — Previous Owner | 工单 | 读取 | 工单 `escalated_from` 字段里的那个人 —— 升级从他手上转走的工单,直到它被 Closed |
| Live Campaigns — Marketing Manager | 营销活动 | 编辑 | `marketing_manager` —— 筹划中 / 进行中的活动 |
| Live Campaigns — Marketing Director | 营销活动 | 编辑 | `marketing_director` —— 同一批活动 |

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ na_sales_team eu_sales_team (區域分組)
| Escalated Cases Sharing | 工單 | 編輯 | `service_manager` —— 未關閉的嚴重工單 |
| Escalated Cases — Service Director | 工單 | 讀取 | `service_director` —— 同一批工單,上一層級 |
| Unassigned Cases — Triage | 工單 | 編輯 | `service_agent` —— 沒有負責人的工單,直到它被 Resolved 或 Closed |
| Escalated Cases — Previous Owner | 工單 | 讀取 | 工單 `escalated_from` 欄位裡的那個人 —— 升級從他手上轉走的工單,直到它被 Closed |
| Live Campaigns — Marketing Manager | 行銷活動 | 編輯 | `marketing_manager` —— 籌劃中 / 進行中的活動 |
| Live Campaigns — Marketing Director | 行銷活動 | 編輯 | `marketing_director` —— 同一批活動 |

Expand Down
7 changes: 4 additions & 3 deletions content/docs/service/cases.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ This drives reporting (which channels generate the most volume) and is auto-fill

## What a case record stores

`crm_case` declares **24 fields** (`src/service/objects/case.object.ts`), and three different things organise them: the object's own field groups, the case detail screen, and the case form. Only the first of the three has six sections, and no two of them agree — so it is worth knowing which one you are being shown.
`crm_case` declares **25 fields** (`src/service/objects/case.object.ts`), and three different things organise them: the object's own field groups, the case detail screen, and the case form. Only the first of the three has six sections, and no two of them agree — so it is worth knowing which one you are being shown.

### The object's field groups

Expand All @@ -64,7 +64,7 @@ The six names below are real, but not one of them is a screen: they are `crm_cas
| **Origin & Routing** | Case Owner, Case Origin |
| **SLA & Priority** | Priority, Closed Date, First Response Date, Resolution Time (Hours), SLA Due Date, SLA Violated |
| **Resolution** | Resolution, Resolved by Article |
| **Escalation** *(collapsed by default)* | Escalated, Escalated Date, Escalation Reason |
| **Escalation** *(collapsed by default)* | Escalated, Escalated Date, Escalation Reason, Escalated From |
| **System** *(collapsed by default)* | Internal Notes, Is Closed |

Five of those six rows used to be written differently on this page, and the differences were not cosmetic: **Status** and **Case Type** are in *Case Information*, not *SLA & Priority*; **Closed Date** is in *SLA & Priority*, not *Resolution*; and *System* holds **Internal Notes** and **Is Closed** — there is no *audit trail* field on a case at all, and the timestamps sit in *SLA & Priority*. Only *Escalation* was right as it stood. **Priority Rank**, the hidden sort key that orders *My Open Cases*, is in no group at all.
Expand All @@ -85,7 +85,7 @@ The tab is shorter than its section names suggest, and that is deliberate: a sec

### The form

The case form (`src/service/views/case.view.ts`) is **not** tabbed. It is a single section, holding **9** of the 24 fields:
The case form (`src/service/views/case.view.ts`) is **not** tabbed. It is a single section, holding **9** of the 25 fields:

| Section | Fields |
| --- | --- |
Expand Down Expand Up @@ -162,6 +162,7 @@ Nothing else claims a case, and each exclusion is deliberate:
- By default, cases are **private** — only the owner. Positions are flat and **nothing rolls up**, so no manager sees a case by sitting above its owner.
- **Critical open cases** are auto-shared by two rules, one per rung: *Escalated Cases Sharing* grants read/edit to `service_manager`, and *Escalated Cases — Service Director* grants read to `service_director`. Both rungs are named explicitly because neither inherits from the other.
- **Unowned open cases** are shared with every `service_agent` by *Unassigned Cases — Triage*, read and edit — the grant behind the **Unassigned — triage** tab. It lasts exactly as long as the case has no owner and is neither *Resolved* nor *Closed*, and claiming the case is what ends it: from that point you hold the case the ordinary way, as its owner.
- **An escalated case stays readable by the agent it was taken from.** The hand-off records that agent in **Escalated From**, and *Escalated Cases — Previous Owner* shares the case with them, read-only, until it is *Closed* — so the agent who logs a Critical case or presses **Escalate Case** can still open it. Working it is the manager's: the agent cannot edit it.

## Field-level security

Expand Down
7 changes: 4 additions & 3 deletions content/docs/service/cases.zh-Hans.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ description: 客户支持工单——主题、优先级、状态、解决方案

## 工单记录存储的内容

`crm_case` 一共声明 **24 个字段**(`src/service/objects/case.object.ts`),而把它们组织起来的东西有三样:对象自己的字段分组、工单详情界面、工单表单。三者里只有第一样是六个分区,而且两两都不一致——所以要紧的是分清你看的是哪一样。
`crm_case` 一共声明 **25 个字段**(`src/service/objects/case.object.ts`),而把它们组织起来的东西有三样:对象自己的字段分组、工单详情界面、工单表单。三者里只有第一样是六个分区,而且两两都不一致——所以要紧的是分清你看的是哪一样。

### 对象自己的字段分组

Expand All @@ -64,7 +64,7 @@ description: 客户支持工单——主题、优先级、状态、解决方案
| **Origin & Routing** | 负责人、来源 |
| **SLA & Priority** | 优先级、关闭日期、首次响应日期、解决时间(小时)、SLA 截止日期、SLA Violated |
| **Resolution** | 解决方案、解决所用文章 |
| **Escalation** *(默认折叠)* | 升级标记、升级日期、升级原因 |
| **Escalation** *(默认折叠)* | 升级标记、升级日期、升级原因、升级前负责人 |
| **System** *(默认折叠)* | 内部备注、关闭标记 |

这六行里有五行,本页原先写的是另一个样子,而且差的不是细枝末节:**状态**与**类型**在 *Case Information* 里,不在 *SLA & Priority*;**关闭日期**在 *SLA & Priority* 里,不在 *Resolution*;*System* 装的是**内部备注**与**关闭标记**——工单上根本没有名为「审计跟踪」的字段,时间戳则待在 *SLA & Priority* 里。六行里只有 *Escalation* 原样就是对的。**优先级排序**——把 *我的待处理工单* 排出先后的那个隐藏排序键——则不属于任何一个分组。
Expand All @@ -85,7 +85,7 @@ description: 客户支持工单——主题、优先级、状态、解决方案

### 表单

工单表单(`src/service/views/case.view.ts`)**不是**分页签的。它只有一个分区,装着 24 个字段中的 **9** 个:
工单表单(`src/service/views/case.view.ts`)**不是**分页签的。它只有一个分区,装着 25 个字段中的 **9** 个:

| 分区 | 字段 |
| --- | --- |
Expand Down Expand Up @@ -162,6 +162,7 @@ description: 客户支持工单——主题、优先级、状态、解决方案
- 默认情况下,工单是**私有的**——只有所有者可见。岗位是扁平的,**什么都不会上卷**,没有哪位经理因为“在所有者之上”就能看到某个工单。
- **严重未结工单**由两条规则自动共享,一个层级一条:*Escalated Cases Sharing* 把读/写授予 `service_manager`,*Escalated Cases — Service Director* 把读授予 `service_director`。两个层级都被显式点名,因为谁也不会从对方那里继承。
- **无主的未结工单**由 *Unassigned Cases — Triage* 共享给每一位 `service_agent`,读 + 编辑——这就是 **未分派 — 待分诊** 标签页背后的那条授权。它的有效期恰好是「这张工单没有负责人,且既不是 *Resolved* 也不是 *Closed*」这段时间,而认领正是终结它的那个动作:从那一刻起,你以负责人的身份、用最普通的方式持有这张工单。
- **升级后的工单,被转走的那位客服仍能查看。** 交接会把这位客服记在**升级前负责人**里,*Escalated Cases — Previous Owner* 把工单以只读方式共享给他,直到它被 *Closed*——所以登记 Critical 工单或点了 **Escalate Case** 的客服仍能打开它。处理它是经理的事:这位客服不能编辑。

## 字段级安全

Expand Down
7 changes: 4 additions & 3 deletions content/docs/service/cases.zh-Hant.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ description: 客戶支援工單——主題、優先順序、狀態、解決方

## 工單記錄儲存的內容

`crm_case` 一共宣告 **24 個欄位**(`src/service/objects/case.object.ts`),而把它們組織起來的東西有三樣:物件自己的欄位分組、工單詳情介面、工單表單。三者裡只有第一樣是六個分區,而且兩兩都不一致——所以要緊的是分清你看的是哪一樣。
`crm_case` 一共宣告 **25 個欄位**(`src/service/objects/case.object.ts`),而把它們組織起來的東西有三樣:物件自己的欄位分組、工單詳情介面、工單表單。三者裡只有第一樣是六個分區,而且兩兩都不一致——所以要緊的是分清你看的是哪一樣。

### 物件自己的欄位分組

Expand All @@ -66,7 +66,7 @@ description: 客戶支援工單——主題、優先順序、狀態、解決方
| **Origin & Routing** | 負責人、來源 |
| **SLA & Priority** | 優先順序、關閉日期、首次回應日期、解決時間(小時)、SLA 截止日期、SLA Violated |
| **Resolution** | 解決方案、解決所用文章 |
| **Escalation** *(預設折疊)* | 升級標記、升級日期、升級原因 |
| **Escalation** *(預設折疊)* | 升級標記、升級日期、升級原因、升級前負責人 |
| **System** *(預設折疊)* | 內部備註、關閉標記 |

這六列裡有五列,本頁原先寫的是另一個樣子,而且差的不是細枝末節:**狀態**與**類型**在 *Case Information* 裡,不在 *SLA & Priority*;**關閉日期**在 *SLA & Priority* 裡,不在 *Resolution*;*System* 裝的是**內部備註**與**關閉標記**——工單上根本沒有名為「審計追蹤」的欄位,時間戳則待在 *SLA & Priority* 裡。六列裡只有 *Escalation* 原樣就是對的。**優先順序排序**——把 *我的待處理工單* 排出先後的那個隱藏排序鍵——則不屬於任何一個分組。
Expand All @@ -87,7 +87,7 @@ description: 客戶支援工單——主題、優先順序、狀態、解決方

### 表單

工單表單(`src/service/views/case.view.ts`)**不是**分頁籤的。它只有一個分區,裝著 24 個欄位中的 **9** 個:
工單表單(`src/service/views/case.view.ts`)**不是**分頁籤的。它只有一個分區,裝著 25 個欄位中的 **9** 個:

| 分區 | 欄位 |
| --- | --- |
Expand Down Expand Up @@ -164,6 +164,7 @@ description: 客戶支援工單——主題、優先順序、狀態、解決方
- 預設情況下,工單是**私有的**——只有所有者可見。職位是扁平的,**什麼都不會上捲**,沒有哪位經理因為「在所有者之上」就能看到某個工單。
- **嚴重未結工單**由兩條規則自動共享,一個層級一條:*Escalated Cases Sharing* 把讀/寫授予 `service_manager`,*Escalated Cases — Service Director* 把讀授予 `service_director`。兩個層級都被顯式點名,因為誰也不會從對方那裡繼承。
- **無主的未結工單**由 *Unassigned Cases — Triage* 共享給每一位 `service_agent`,讀 + 編輯——這就是 **未分派 — 待分診** 標籤頁背後的那條授權。它的有效期恰好是「這張工單沒有負責人,且既不是 *Resolved* 也不是 *Closed*」這段時間,而認領正是終結它的那個動作:從那一刻起,你以負責人的身分、用最普通的方式持有這張工單。
- **升級後的工單,被轉走的那位客服仍能檢視。** 交接會把這位客服記在**升級前負責人**裡,*Escalated Cases — Previous Owner* 把工單以唯讀方式共用給他,直到它被 *Closed*——所以登記 Critical 工單或點了 **Escalate Case** 的客服仍能開啟它。處理它是經理的事:這位客服不能編輯。

## 欄位級安全

Expand Down
2 changes: 1 addition & 1 deletion docs/STATUS.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ loader registers:

```text
HotCRM v4.0.1
Data: 18 Objects 357 Fields
Data: 18 Objects 358 Fields
UI: 1 Apps 14 Views 8 Pages 5 Dashboards 10 Reports 31 Actions
Logic: 33 Flows
Security: 12 Positions 7 Permissions
Expand Down
2 changes: 2 additions & 0 deletions src/docs/crm_admin.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ sources:
- sharing_rule:opportunity_executive_sharing
- sharing_rule:case_escalation_sharing
- sharing_rule:case_director_sharing
- sharing_rule:case_escalated_from_sharing
- sharing_rule:campaign_leadership_manager
- sharing_rule:campaign_leadership_director
- position:crm_positions
Expand Down Expand Up @@ -65,6 +66,7 @@ Ownership-based access is widened by these rules:
| Large Open Deals — Executive | Opportunity | **Read** | The same deals at the Executive rung. |
| Case Escalation | Case | **Edit** | Escalated cases become editable by the escalation handler. |
| Escalated Cases — Service Director | Case | **Read** | Service Director sees what the manager is paged about. |
| Escalated Cases — Previous Owner | Case | **Read** | The agent an escalation took the case from keeps sight of it until it is closed. |
| Live Campaigns — Marketing Manager / Director | Campaign | **Edit** | Marketing leadership works any live campaign. |

Detail objects — opportunity line items, quote line items, campaign members and
Expand Down
1 change: 1 addition & 0 deletions src/sales/translations/en/objects.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export const service: Record<string, ObjectTranslationData> = {
display_title: { label: 'Display Title' },
priority_rank: { label: 'Priority Rank' },
escalated_date: { label: 'Escalated Date' },
escalated_from: { label: 'Escalated From' },
},
_views: {
all_cases: { label: 'All Cases' },
Expand Down
1 change: 1 addition & 0 deletions src/sales/translations/es-ES/objects.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export const service: Record<string, ObjectTranslationData> = {
is_sla_violated: { label: 'SLA Incumplido' },
is_escalated: { label: 'Escalado' },
escalated_date: { label: 'Fecha de Escalación' },
escalated_from: { label: 'Escalado desde' },
escalation_reason: { label: 'Motivo de Escalación' },
resolution: { label: 'Resolución' },
resolved_by_article: { label: 'Resuelto por Artículo', help: 'Artículo de la base de conocimiento que resolvió este caso — la señal de desvío.' },
Expand Down
1 change: 1 addition & 0 deletions src/sales/translations/ja-JP/objects.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export const service: Record<string, ObjectTranslationData> = {
is_sla_violated: { label: 'SLA違反' },
is_escalated: { label: 'エスカレーション済' },
escalated_date: { label: 'エスカレーション日' },
escalated_from: { label: 'エスカレーション元' },
escalation_reason: { label: 'エスカレーション理由' },
resolution: { label: '解決内容' },
resolved_by_article: { label: '解決したナレッジ記事', help: 'このケースを解決したナレッジ記事 — 逸らし率の指標。' },
Expand Down
1 change: 1 addition & 0 deletions src/sales/translations/zh-CN/objects.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ export const service: Record<string, ObjectTranslationData> = {
display_title: { label: '显示名称' },
priority_rank: { label: '优先级排序' },
escalated_date: { label: '升级日期' },
escalated_from: { label: '升级前负责人' },
},
_views: {
all_cases: { label: '全部工单' },
Expand Down
8 changes: 4 additions & 4 deletions src/service/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
CasesByStatusPriorityReport, SlaPerformanceReport, CasesOpenedByDayPriorityReport,
} from './reports/index.js';
import {
CaseDirectorSharingRule, CaseEscalationSharingRule, CaseUnassignedTriageSharingRule,
CaseDirectorSharingRule, CaseEscalatedFromSharingRule, CaseEscalationSharingRule, CaseUnassignedTriageSharingRule,
} from './sharing/index.js';
import { CaseTriageSkill } from './skills/index.js';
import { CaseViews, KnowledgeArticleViews } from './views/index.js';
Expand Down Expand Up @@ -50,7 +50,7 @@ export const serviceSkills = [CaseTriageSkill];

/** This package's sharing rules — see {@link serviceHooks}. */
export const serviceSharingRules = [
CaseEscalationSharingRule, CaseDirectorSharingRule, CaseUnassignedTriageSharingRule,
CaseEscalationSharingRule, CaseDirectorSharingRule, CaseUnassignedTriageSharingRule, CaseEscalatedFromSharingRule,
];

/**
Expand All @@ -73,7 +73,7 @@ export type HotCrmServiceCollections = Pick<ObjectStackDefinitionInput, 'data'>;
* every item authored against them: their hooks, views, the case detail page,
* the seven case flows, the case and article actions, `case_metrics` and the
* three reports over it, the Service Overview dashboard, the case-triage skill,
* the three case sharing rules, the two case notification templates and the
* the four case sharing rules, the two case notification templates and the
* case and knowledge seed rows.
*
* Three properties are load-bearing:
Expand Down Expand Up @@ -109,7 +109,7 @@ export type HotCrmServiceCollections = Pick<ObjectStackDefinitionInput, 'data'>;
*
* So this list is exactly what THIS package's metadata needs: `automation` and
* `triggers` for the seven case flows, `analytics` for `case_metrics` and the
* items over it, `sharing` for the three case sharing rules. The app package's
* items over it, `sharing` for the four case sharing rules. The app package's
* surface capabilities (`auth`, `ui`, `approvals`, `hierarchy-security`) are
* not repeated.
*
Expand Down
Loading
Loading