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/first-response-and-lead-reports-to-source.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
---
'hotcrm': patch
---

Write the "first-response SLA", the Lead reports section and two enumeration-free
behaviour claims to source, in all three locales.

**A measurable first-response target never existed.** Three pages presented one as
if the app kept it: the setup checklist's §11 SLA matrix carried an entire *First
response* column (1 hour at Critical through 1 business day at Low), the reports
page promised *"% first-response on time"*, and the Service Cube listed both a
*First-response time (minutes)* measure and an *SLA met %*. `first_response_date`
does have a writer — `logActivityAction` in `src/actions/global.actions.ts` stamps
it the first time a call or meeting that already took place is logged on a case —
but nothing in the repo compares that stamp against a target: `case_metrics`
(`src/datasets/case.dataset.ts`) declares no first-response measure, no report or
tile reads it, and no flow alerts on it. The four numbers themselves appear
nowhere, and `content/docs/service/sla-and-escalation`, which the checklist links
to for them, does not mention a first response at all. All three pages now name
the stamp, name what is absent, and state the first-response promise for what it
is — the team's own service commitment. The SLA matrix keeps its resolution
column, with Critical marked as the one row `case_sla_defaults` turns into a
deadline. The cube's SLA measure is named as what `case_metrics` really declares:
an **SLA Violation Rate**, not a compliance percentage.

**The Service reports table listed six reports where `src/reports/case.report.ts`
publishes three.** *Cases by Status and Priority*, the SLA report and *Cases
Opened by Priority × Day* are real; *Case Volume by Origin*, *Case Resolution
Time*, *Top Accounts by Case Volume*, *Reopened Cases* and *CSAT by Agent* are
not published anywhere, and most of them ask `case_metrics` for something it
does not carry — there is no agent dimension, no account dimension, no reopen
marker on the case and no measure over **Customer Satisfaction**, so three of
those five cannot be built as custom reports either. The section now lists the
three real reports with their real grouping, and says of each absent one why it
is absent. Two subscription examples that named a report from that list now name
a published one.

**The Lead reports section listed three reports that do not exist**, and omitted
the one that does. `src/reports/` publishes exactly one lead report — **Lead
Engagement by Month × Source** (`src/reports/lead.report.ts`), a matrix of
contacted-lead volume by source and month — while the page listed *Lead Conversion
Funnel*, *Lead Source ROI* and *Aged Leads*, none of which is published anywhere.
Two of them also spelled a lead status that does not exist: *Working* is a row in
the import alias table (`src/mappings/lead_import.mapping.ts`) that maps a legacy
value onto **Contacted** at import time, not a value of `crm_lead.status`, whose
route is *New → Contacted → Qualified → Unqualified → Converted*. The section now
lists the real report, names the three absent ones, and says what `lead_metrics`
can and cannot answer.

**Two behavioural claims outside any enumeration.** The setup checklist sent
admins to a **Setup → Opportunity → Stages** screen that does not exist — the
stages are the `stage` field's options in `src/objects/_picklists.ts` and the
probabilities are the `STAGE_PROBABILITY` map in `src/objects/opportunity.hook.ts`,
which re-derives probability from the stage on every save. And the state-machines
page advised hanging automation off a transition because it is *"much more
performant"*; there is no transition to hang it on (the table is a warning-severity
validation rule that logs and emits nothing), and the comparison had no basis. The
tip now describes what the app actually does — a `record_change` flow that narrows
itself in its start condition, as `opportunity_won_alert` does.

Documentation only; `src/` unchanged. Fixes #936, #951, #952.
20 changes: 12 additions & 8 deletions content/docs/administration/setup.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -93,20 +93,24 @@ Confirm the 7-stage default pipeline matches your sales process. Stages and prob
| Closed Won | 100% |
| Closed Lost | 0% |

If your process differs, adjust in **Setup → Opportunity → Stages**.
If your process differs, that is a source edit rather than a screen: **there is no Setup → Opportunity → Stages page**. The seven stages are the `stage` field's own options (`OPPORTUNITY_STAGE_OPTIONS` in `src/objects/_picklists.ts`), and the probabilities are the `STAGE_PROBABILITY` map in `src/objects/opportunity.hook.ts` — which re-derives **Probability** and **Expected Revenue** from the stage on every save, so a probability typed onto a deal by hand does not survive the next one. Changing either is a code change plus a redeploy.

### 11. Case SLAs

Confirm the default [SLA matrix](/docs/service/sla-and-escalation):

| Priority | First response | Resolution |
| --- | --- | --- |
| Critical | 1 hour | 4 hours |
| High | 2 hours | 8 hours |
| Medium | 4 hours | 2 business days |
| Low | 1 business day | 7 business days |
| Priority | Resolution |
| --- | --- |
| Critical | 4 hours |
| High | 8 hours |
| Medium | 2 business days |
| Low | 7 business days |

**Only the Critical row is a deadline the app keeps.** The `case_sla_defaults` hook (`src/objects/case.hook.ts`) stamps `sla_due_date` at *now + 4 calendar hours* on a Critical case and writes nothing at the other three priorities — and the hourly `case_sla_monitor` sweep only picks up cases whose due date has passed, so below Critical there is no date to pass. Those three rows are your team's service commitment, not a clock the app runs.

**There is no first-response target to confirm.** This checklist used to carry a whole *First response* column — 1 hour at Critical through 1 business day at Low — and none of those four numbers exists anywhere in the app; the SLA page this section links to does not mention a first response at all. What does exist is the stamp: **First Response Date** is written the first time a call or meeting that already took place is logged on the case (`logActivityAction`, `src/actions/global.actions.ts`). It is never compared against anything — `case_metrics` declares no first-response measure, so no report or dashboard tile reports on it, and no flow alerts on it. Promise your customers a first response time by all means; the app records when it happened and nothing else.

Adjust if your support promise differs.
Adjust if your support promise differs — the Critical four hours lives in `src/objects/case.hook.ts`, not on a Setup screen.

### 12. Approval process

Expand Down
20 changes: 12 additions & 8 deletions content/docs/administration/setup.zh-Hans.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -93,20 +93,24 @@ description: 让 HotCRM 为你的团队做好生产准备的第一周任务。
| Closed Won | 100% |
| Closed Lost | 0% |

如果你的流程不同,请在 **设置 → 商机 → 阶段** 中调整。
如果你的流程不同,那是一次源码修改,不是在某个屏上调整:**并不存在「设置 → 商机 → 阶段」这个页面**。七个阶段是 `stage` 字段自己的选项(`src/objects/_picklists.ts` 里的 `OPPORTUNITY_STAGE_OPTIONS`),概率则是 `src/objects/opportunity.hook.ts` 里的 `STAGE_PROBABILITY` 映射 —— 它在每次保存时都按阶段重新推导 **Probability** 与 **Expected Revenue**,所以手工填在商机上的概率撑不过下一次保存。改动其中任何一项都是代码改动加一次重新部署。

### 11. 案例 SLA

确认默认的 [SLA 矩阵](/zh-Hans/docs/service/sla-and-escalation):

| 优先级 | 首次响应 | 解决 |
| --- | --- | --- |
| Critical | 1 小时 | 4 小时 |
| High | 2 小时 | 8 小时 |
| Medium | 4 小时 | 2 个工作日 |
| Low | 1 个工作日 | 7 个工作日 |
| 优先级 | 解决 |
| --- | --- |
| Critical | 4 小时 |
| High | 8 小时 |
| Medium | 2 个工作日 |
| Low | 7 个工作日 |

**只有 Critical 这一行是应用真正在守的期限。** `case_sla_defaults` 钩子(`src/objects/case.hook.ts`)在 Critical 工单上把 `sla_due_date` 盖成 *当前时间 + 4 个自然小时*,其余三个优先级什么都不写 —— 而每小时跑一次的 `case_sla_monitor` 只捞到期日已过的工单,所以 Critical 以下根本没有一个日期可以过期。那三行是你们团队的服务承诺,不是应用替你跑的钟。

**这里没有「首次响应目标」可确认。** 本清单曾经带着整整一列 *首次响应*(Critical 1 小时一直到 Low 1 个工作日),这四个数字在应用里哪儿都不存在;本节链过去的那一页 SLA 文档也根本没提过首次响应。真正存在的是那个时间戳:**First Response Date** 在工单上第一次记录 *已经发生过* 的通话或会议时被写入(`logActivityAction`,`src/actions/global.actions.ts`)。它从不与任何目标比较 —— `case_metrics` 没有声明任何首次响应度量,因此没有报表、没有仪表盘磁贴会报它,也没有任何流程会为它告警。你当然可以对客户承诺一个首次响应时长;应用只是记下它何时发生,别的什么都不做。

如果你的支持承诺不同,请进行调整。
如果你的支持承诺不同,请自行调整 —— Critical 的这 4 小时写在 `src/objects/case.hook.ts` 里,不在任何设置屏上。

### 12. 审批流程

Expand Down
20 changes: 12 additions & 8 deletions content/docs/administration/setup.zh-Hant.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -93,20 +93,24 @@ description: 讓 HotCRM 為你的團隊做好生產準備的第一週任務。
| Closed Won | 100% |
| Closed Lost | 0% |

如果你的流程不同,請在 **設定 → 商機 → 階段** 中調整。
如果你的流程不同,那是一次原始碼修改,不是在某個畫面上調整:**並不存在「設定 → 商機 → 階段」這個頁面**。七個階段是 `stage` 欄位自己的選項(`src/objects/_picklists.ts` 裡的 `OPPORTUNITY_STAGE_OPTIONS`),機率則是 `src/objects/opportunity.hook.ts` 裡的 `STAGE_PROBABILITY` 對應表 —— 它在每次儲存時都按階段重新推導 **Probability** 與 **Expected Revenue**,所以手工填在商機上的機率撐不過下一次儲存。改動其中任何一項都是程式碼改動加一次重新部署。

### 11. 案例 SLA

確認預設的 [SLA 矩陣](/zh-Hant/docs/service/sla-and-escalation):

| 優先級 | 首次回應 | 解決 |
| --- | --- | --- |
| Critical | 1 小時 | 4 小時 |
| High | 2 小時 | 8 小時 |
| Medium | 4 小時 | 2 個工作日 |
| Low | 1 個工作日 | 7 個工作日 |
| 優先級 | 解決 |
| --- | --- |
| Critical | 4 小時 |
| High | 8 小時 |
| Medium | 2 個工作日 |
| Low | 7 個工作日 |

**只有 Critical 這一行是應用真正在守的期限。** `case_sla_defaults` 鉤子(`src/objects/case.hook.ts`)在 Critical 工單上把 `sla_due_date` 蓋成 *目前時間 + 4 個自然小時*,其餘三個優先級什麼都不寫 —— 而每小時跑一次的 `case_sla_monitor` 只撈到期日已過的工單,所以 Critical 以下根本沒有一個日期可以過期。那三行是你們團隊的服務承諾,不是應用替你跑的鐘。

**這裡沒有「首次回應目標」可確認。** 本清單曾經帶著整整一欄 *首次回應*(Critical 1 小時一直到 Low 1 個工作日),這四個數字在應用裡哪兒都不存在;本節連過去的那一頁 SLA 文件也根本沒提過首次回應。真正存在的是那個時間戳:**First Response Date** 在工單上第一次記錄 *已經發生過* 的通話或會議時被寫入(`logActivityAction`,`src/actions/global.actions.ts`)。它從不與任何目標比較 —— `case_metrics` 沒有宣告任何首次回應度量,因此沒有報表、沒有儀表板磁貼會報它,也沒有任何流程會為它告警。你當然可以對客戶承諾一個首次回應時長;應用只是記下它何時發生,別的什麼都不做。

如果你的支援承諾不同,請進行調整。
如果你的支援承諾不同,請自行調整 —— Critical 的這 4 小時寫在 `src/objects/case.hook.ts` 裡,不在任何設定畫面上。

### 12. 審批流程

Expand Down
2 changes: 1 addition & 1 deletion content/docs/administration/state-machines.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ What the tables do give an agent is what they give a report author: the route, d

- ✅ **Don't over-constrain** — too many transition rules become a friction tax on the team.
- ✅ When adding a new status, **decide the entry and exit conditions first** — the rest follows.
- ✅ Use state-machine transitions to **trigger automation** rather than running automation on every save — much more performant.
- ✅ **Don't hang automation off a transition** — there is nothing to hang it on. The table is a validation rule, evaluated on the save that makes the move; it writes a log line and emits no event for automation to subscribe to. Automation that should run on one move is a `record_change` flow that fires on the save and narrows *itself* in its start condition, comparing `record.stage` against `previous.stage` — `opportunity_won_alert` (`src/flows/opportunity-won-alert.flow.ts`) does exactly that, so the congratulations blast goes out on the move into *Closed Won* and not on every later edit of a won deal.
- ✅ **Test the wizard flows** (convert lead, close opportunity) after every change.

## Tips for users
Expand Down
2 changes: 1 addition & 1 deletion content/docs/administration/state-machines.zh-Hans.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ Prospecting / Qualification / Needs Analysis / Proposal / Negotiation ──▶

- ✅ **不要过度约束** —— 太多转换规则会成为团队的摩擦税。
- ✅ 添加新状态时,**先决定进入和退出条件** —— 其余的自然就有了。
- ✅ 使用状态机转换来 **触发自动化**,而非在每次保存时运行自动化 —— 性能要好得多。
- ✅ **不要把自动化挂在转换上** —— 根本没有可挂的东西。转换表是一条验证规则,在做出这次移动的那一次保存里被求值;它写下一行日志,不产生任何可供自动化订阅的事件。只应在某一次移动上跑的自动化,是一条 `record_change` 流程:它在那次保存上触发,并在自己的起始条件里把 `record.stage` 与 `previous.stage` 比对来收窄 **自己** —— `opportunity_won_alert`(`src/flows/opportunity-won-alert.flow.ts`)正是这么写的,所以那条祝贺通知只在移入 *Closed Won* 的那一次发出,而不是在这笔已赢商机之后的每一次编辑上都发。
- ✅ 每次变更后 **测试向导流程**(转化潜在客户、关闭商机)。

## 给用户的提示
Expand Down
2 changes: 1 addition & 1 deletion content/docs/administration/state-machines.zh-Hant.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ Prospecting / Qualification / Needs Analysis / Proposal / Negotiation ──▶

- ✅ **不要過度約束** —— 太多轉換規則會成為團隊的摩擦稅。
- ✅ 新增狀態時,**先決定進入和退出條件** —— 其餘的自然就有了。
- ✅ 使用狀態機轉換來 **觸發自動化**,而非在每次儲存時執行自動化 —— 效能要好得多。
- ✅ **不要把自動化掛在轉換上** —— 根本沒有可掛的東西。轉換表是一條驗證規則,在做出這次移動的那一次儲存裡被求值;它寫下一行日誌,不產生任何可供自動化訂閱的事件。只應在某一次移動上跑的自動化,是一條 `record_change` 流程:它在那次儲存上觸發,並在自己的起始條件裡把 `record.stage` 與 `previous.stage` 比對來收窄 **自己** —— `opportunity_won_alert`(`src/flows/opportunity-won-alert.flow.ts`)正是這麼寫的,所以那條祝賀通知只在移入 *Closed Won* 的那一次發出,而不是在這筆已贏商機之後的每一次編輯上都發。
- ✅ 每次變更後 **測試精靈流程**(轉化潛在客戶、關閉商機)。

## 給使用者的提示
Expand Down
7 changes: 4 additions & 3 deletions content/docs/analytics/cubes.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,12 @@ The cube behind every case-related dashboard and SLA report.
- Open case count
- New case count
- Resolved case count
- First-response time (minutes)
- Resolution time (hours)
- SLA met % (first response and resolution)
- **SLA Violation Rate** — the average of the **SLA Violated** flag (`avg_sla_violated` in `case_metrics`), which is a breach rate and not a compliance percentage
- CSAT score

**There is no first-response measure here**, and no "SLA met %". `case_metrics` (`src/datasets/case.dataset.ts`) declares neither, so nothing in analytics can aggregate, compare or chart a first response. The **First Response Date** stamp on the case is real — see [Cases](/docs/service/cases) for when it is written — but it stops there: nothing measures it, and nothing alerts on it.

**Dimensions**:
- Status, priority, category, origin
- Agent (and team)
Expand All @@ -94,7 +95,7 @@ The cube behind every case-related dashboard and SLA report.
- Period

**Sample questions**:
- *"SLA met % by priority by month."*
- *"SLA Violation Rate by priority."*
- *"Cases by category by product — which products generate the most support load?"*
- *"Reopened cases by agent — quality signal."*

Expand Down
7 changes: 4 additions & 3 deletions content/docs/analytics/cubes.zh-Hans.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,12 @@ description: 为 HotCRM 分析提供支持的数据层——四个内置多维
- 未结工单数
- 新建工单数
- 已解决工单数
- 首次响应时间(分钟)
- 解决时间(小时)
- SLA 达标 %(首次响应和解决)
- **SLA 违约率** —— **SLA Violated** 标志的平均值(`case_metrics` 里的 `avg_sla_violated`),它是违约率,不是达标率
- CSAT 评分

**这里没有首次响应度量**,也没有「SLA 达标 %」。`case_metrics`(`src/datasets/case.dataset.ts`)两者都没有声明,所以分析侧根本无法对首次响应做聚合、比较或出图。工单上的 **First Response Date** 时间戳是真的 —— 它何时被写入见 [工单](/zh-Hans/docs/service/cases) —— 但也就到此为止:没有任何东西度量它,也没有任何东西为它告警。

**维度**:
- 状态、优先级、类别、来源
- 客服(及团队)
Expand All @@ -94,7 +95,7 @@ description: 为 HotCRM 分析提供支持的数据层——四个内置多维
- 周期

**示例问题**:
- *"按优先级按月的 SLA 达标 %。"*
- *"按优先级的 SLA 违约率。"*
- *"按类别按产品的工单——哪些产品产生的支持负荷最大?"*
- *"按客服的重开工单——质量信号。"*

Expand Down
7 changes: 4 additions & 3 deletions content/docs/analytics/cubes.zh-Hant.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,12 @@ description: 為 HotCRM 分析提供支援的資料層——四個內建多維
- 未結工單數
- 新建工單數
- 已解決工單數
- 首次回應時間(分鐘)
- 解決時間(小時)
- SLA 達標 %(首次回應和解決)
- **SLA 違約率** —— **SLA Violated** 標誌的平均值(`case_metrics` 裡的 `avg_sla_violated`),它是違約率,不是達標率
- CSAT 評分

**這裡沒有首次回應度量**,也沒有「SLA 達標 %」。`case_metrics`(`src/datasets/case.dataset.ts`)兩者都沒有宣告,所以分析側根本無法對首次回應做彙總、比較或出圖。工單上的 **First Response Date** 時間戳是真的 —— 它何時被寫入見 [工單](/zh-Hant/docs/service/cases) —— 但也就到此為止:沒有任何東西度量它,也沒有任何東西為它告警。

**維度**:
- 狀態、優先順序、類別、來源
- 客服(及團隊)
Expand All @@ -94,7 +95,7 @@ description: 為 HotCRM 分析提供支援的資料層——四個內建多維
- 週期

**範例問題**:
- *"按優先順序按月的 SLA 達標 %。"*
- *"按優先順序的 SLA 違約率。"*
- *"按類別按產品的工單——哪些產品產生的支援負荷最大?"*
- *"按客服的重開工單——品質訊號。"*

Expand Down
Loading
Loading