Skip to content
5 changes: 5 additions & 0 deletions .changeset/2014-web-intake-hooks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'hotcrm': patch
---

Submissions from the public Web-to-Lead (Contact Us) and Web-to-Case (Support) forms are now routed like every other incoming lead and case: a web lead goes to the sales rep with the fewest open leads, and its new-lead alert reaches that rep; a web case goes to the service agent with the fewest open cases, instead of waiting in Unassigned — triage while agents are on the rota. A web enquiry from an address already in the CRM is now flagged as a suspected duplicate, and a sales rep's new lead is now flagged when it repeats a colleague's contact or lead the rep cannot see. The visitor who fills in a public form is still told only that the submission was received — never who it went to, or whether the address was already known.
2 changes: 1 addition & 1 deletion content/docs/administration/setup.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ Adjust if your support promise differs — the sixteen numbers live in `src/serv

### 12. Case routing — nothing to configure

**There is no Setup → Case Settings screen**, and there are no routing rules to write. An ownerless case — a web-to-case submission is the usual way one arrives — is assigned on insert by `case_auto_assign` (`src/service/objects/_case-assignment.ts`): it goes to whichever holder of the `service_agent` position has the **fewest open cases**, counting every case whose status is neither *Resolved* nor *Closed*. It is the same self-balancing round-robin leads get: least-loaded wins, not a rotation and not a rule matching product, region or origin; there is no territory routing and no queue. It runs **on insert only**, and only on a case that arrives with no owner — it never takes a case off an owner later, and it never fires on a case an agent creates, because the platform has already stamped that agent as the owner by the time the hook runs. It never blocks intake either: when the pool cannot be read at all — which is what the anonymous web-to-case form hits — the case is still captured, just ownerless. And if nobody holds `service_agent` the hook does nothing, so the case stays ownerless and waits in the **Unassigned — triage** tab on [Cases](/docs/service/cases). That tab is this hook's do-nothing path made visible, which makes staffing the position a behaviour change in itself: new cases stop arriving ownerless, and stop appearing there. **This is not the escalation hand-off**, which is a different pool at a different moment — that one rides on the transition to *Escalated* and moves the case to the least-loaded holder of `service_manager` (see [SLA & escalation](/docs/service/sla-and-escalation)).
**There is no Setup → Case Settings screen**, and there are no routing rules to write. An ownerless case — a web-to-case submission is the usual way one arrives — is assigned on insert by `case_auto_assign` (`src/service/objects/_case-assignment.ts`): it goes to whichever holder of the `service_agent` position has the **fewest open cases**, counting every case whose status is neither *Resolved* nor *Closed*. It is the same self-balancing round-robin leads get: least-loaded wins, not a rotation and not a rule matching product, region or origin; there is no territory routing and no queue. It runs **on insert only**, and only on a case that arrives with no owner — it never takes a case off an owner later, and it never fires on a case an agent creates, because the platform has already stamped that agent as the owner by the time the hook runs. It never blocks intake either: if the pool cannot be read, the case is still captured, just ownerless. A case from the anonymous web-to-case form is routed like any other — the visitor is only told the case was received, never who it went to. And if nobody holds `service_agent` the hook does nothing, so the case stays ownerless and waits in the **Unassigned — triage** tab on [Cases](/docs/service/cases). That tab is this hook's do-nothing path made visible, which makes staffing the position a behaviour change in itself: new cases stop arriving ownerless, and stop appearing there. **This is not the escalation hand-off**, which is a different pool at a different moment — that one rides on the transition to *Escalated* and moves the case to the least-loaded holder of `service_manager` (see [SLA & escalation](/docs/service/sla-and-escalation)).

- [ ] Assign the `service_agent` position to every agent who should receive intake — that pool *is* the routing table
- [ ] While nobody holds it, watch the **Unassigned — triage** tab on Cases: it lists exactly the cases this hook could not place
Expand Down
2 changes: 1 addition & 1 deletion content/docs/administration/setup.zh-Hans.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ description: 让 HotCRM 为你的团队做好生产准备的第一周任务。

### 12. 工单路由 —— 没有需要配置的东西

**不存在“系统设置 → 工单设置”这个屏幕**,也没有任何路由规则可写。一张无所有者的工单 —— 最常见的来路是网页转工单(web-to-case)提交 —— 会在插入时由 `case_auto_assign`(`src/service/objects/_case-assignment.ts`)分配:交给持有 `service_agent` 岗位的人中**未结工单最少**的那一位,这里的“未结”指状态既不是 *Resolved* 也不是 *Closed*。这和线索那一侧是同一种自平衡轮询:比的是谁负载最轻,而不是按次序轮换,也不是按产品、区域或来源匹配的规则;没有区域路由,也没有队列。它**只在插入时运行**,而且只处理到达时就没有所有者的工单 —— 它不会在之后把工单从某个人手里拿走,也不会在客服自己新建工单时触发,因为钩子跑起来之前平台已经把创建者盖成了所有者。它也从不阻断工单进入:当人员池根本读不到时(匿名网页转工单表单就是这种情形),工单照样被记录下来,只是无主。而如果没有人持有 `service_agent`,这个钩子什么也不做,工单就一直无主,停在[工单](/zh-Hans/docs/service/cases)页面的 **未分派 — 待分诊** 页签里。那个页签正是这个钩子“什么也不做”这条路径的可见化,所以给这个岗位配上人本身就是一次行为变化:新工单不再无主到达,也不再出现在那里。**这不是升级交接**:那是另一个人员池、另一个时机 —— 它挂在转为 *Escalated* 的状态迁移上,把工单交给持有 `service_manager` 岗位的人中负载最轻的那一位(参见 [SLA 与升级](/zh-Hans/docs/service/sla-and-escalation))。
**不存在“系统设置 → 工单设置”这个屏幕**,也没有任何路由规则可写。一张无所有者的工单 —— 最常见的来路是网页转工单(web-to-case)提交 —— 会在插入时由 `case_auto_assign`(`src/service/objects/_case-assignment.ts`)分配:交给持有 `service_agent` 岗位的人中**未结工单最少**的那一位,这里的“未结”指状态既不是 *Resolved* 也不是 *Closed*。这和线索那一侧是同一种自平衡轮询:比的是谁负载最轻,而不是按次序轮换,也不是按产品、区域或来源匹配的规则;没有区域路由,也没有队列。它**只在插入时运行**,而且只处理到达时就没有所有者的工单 —— 它不会在之后把工单从某个人手里拿走,也不会在客服自己新建工单时触发,因为钩子跑起来之前平台已经把创建者盖成了所有者。它也从不阻断工单进入:人员池读不到时,工单照样被记录下来,只是无主。匿名网页转工单表单提交的工单和其他来路一样参与分配 —— 提交者只会得知工单已收到,永远看不到它分给了谁。而如果没有人持有 `service_agent`,这个钩子什么也不做,工单就一直无主,停在[工单](/zh-Hans/docs/service/cases)页面的 **未分派 — 待分诊** 页签里。那个页签正是这个钩子“什么也不做”这条路径的可见化,所以给这个岗位配上人本身就是一次行为变化:新工单不再无主到达,也不再出现在那里。**这不是升级交接**:那是另一个人员池、另一个时机 —— 它挂在转为 *Escalated* 的状态迁移上,把工单交给持有 `service_manager` 岗位的人中负载最轻的那一位(参见 [SLA 与升级](/zh-Hans/docs/service/sla-and-escalation))。

- [ ] 把 `service_agent` 岗位分配给每一位应当接收工单的客服 —— 这个人员池*就是*路由表
- [ ] 在还没有人持有它的时候,盯住工单页面的 **未分派 — 待分诊** 页签:它列出的正是这个钩子没能分配掉的工单
Expand Down
2 changes: 1 addition & 1 deletion content/docs/administration/setup.zh-Hant.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ description: 讓 HotCRM 為你的團隊做好生產準備的第一週任務。

### 12. 工單路由 —— 沒有需要設定的東西

**不存在「Setup → Case Settings」這個畫面**,也沒有任何路由規則可寫。一張無所有者的工單 —— 最常見的來路是網頁轉工單(web-to-case)提交 —— 會在插入時由 `case_auto_assign`(`src/service/objects/_case-assignment.ts`)指派:交給持有 `service_agent` 職位的人當中**未結工單最少**的那一位,這裡的「未結」指狀態既不是 *Resolved* 也不是 *Closed*。這和潛在客戶那一側是同一種自平衡輪詢:比的是誰負載最輕,而不是按次序輪換,也不是按產品、區域或來源比對的規則;沒有區域路由,也沒有佇列。它**只在插入時執行**,而且只處理到達時就沒有所有者的工單 —— 它不會在之後把工單從某個人手上拿走,也不會在客服自己新建工單時觸發,因為掛鉤跑起來之前平台已經把建立者蓋成了所有者。它也從不阻斷工單進入:當人員池根本讀不到時(匿名網頁轉工單表單就是這種情形),工單照樣被記錄下來,只是無主。而如果沒有人持有 `service_agent`,這個掛鉤什麼也不做,工單就一直無主,停在[工單](/zh-Hant/docs/service/cases)頁面的 **未分派 — 待分診** 頁籤裡。那個頁籤正是這個掛鉤「什麼也不做」這條路徑的可見化,所以給這個職位配上人本身就是一次行為變化:新工單不再無主到達,也不再出現在那裡。**這不是升級交接**:那是另一個人員池、另一個時機 —— 它掛在轉為 *Escalated* 的狀態遷移上,把工單交給持有 `service_manager` 職位的人當中負載最輕的那一位(參見 [SLA 與升級](/zh-Hant/docs/service/sla-and-escalation))。
**不存在「Setup → Case Settings」這個畫面**,也沒有任何路由規則可寫。一張無所有者的工單 —— 最常見的來路是網頁轉工單(web-to-case)提交 —— 會在插入時由 `case_auto_assign`(`src/service/objects/_case-assignment.ts`)指派:交給持有 `service_agent` 職位的人當中**未結工單最少**的那一位,這裡的「未結」指狀態既不是 *Resolved* 也不是 *Closed*。這和潛在客戶那一側是同一種自平衡輪詢:比的是誰負載最輕,而不是按次序輪換,也不是按產品、區域或來源比對的規則;沒有區域路由,也沒有佇列。它**只在插入時執行**,而且只處理到達時就沒有所有者的工單 —— 它不會在之後把工單從某個人手上拿走,也不會在客服自己新建工單時觸發,因為掛鉤跑起來之前平台已經把建立者蓋成了所有者。它也從不阻斷工單進入:人員池讀不到時,工單照樣被記錄下來,只是無主。匿名網頁轉工單表單提交的工單和其他來路一樣參與指派 —— 提交者只會得知工單已收到,永遠看不到它指派給了誰。而如果沒有人持有 `service_agent`,這個掛鉤什麼也不做,工單就一直無主,停在[工單](/zh-Hant/docs/service/cases)頁面的 **未分派 — 待分診** 頁籤裡。那個頁籤正是這個掛鉤「什麼也不做」這條路徑的可見化,所以給這個職位配上人本身就是一次行為變化:新工單不再無主到達,也不再出現在那裡。**這不是升級交接**:那是另一個人員池、另一個時機 —— 它掛在轉為 *Escalated* 的狀態遷移上,把工單交給持有 `service_manager` 職位的人當中負載最輕的那一位(參見 [SLA 與升級](/zh-Hant/docs/service/sla-and-escalation))。

- [ ] 把 `service_agent` 職位指派給每一位應當接收工單的客服 —— 這個人員池*就是*路由表
- [ ] 在還沒有人持有它的時候,盯住工單頁面的 **未分派 — 待分診** 頁籤:它列出的正是這個掛鉤沒能指派掉的工單
Expand Down
2 changes: 1 addition & 1 deletion content/docs/sales/leads.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ Work them from the **Suspected Duplicates** view in the Leads list's view switch
- **It's a new enquiry from a known person** — clear Duplicate Status and work the lead normally. A returning prospect with a fresh budget is a real opportunity.
- **It's the same enquiry twice** — set the lead to *Unqualified* with the reason **Duplicate**. The form then requires you to name the surviving record and set Duplicate Status to **Confirmed**; the lead cannot be closed as a duplicate without saying what survives it, so the disqualification report always links to something you can open.

> Anonymous Web-to-Lead submissions are always accepted. If the public form cannot check for existing records (it deliberately has no read access to your CRM data), the lead lands unflagged — review it from the normal lead queue.
> Anonymous Web-to-Lead submissions are always accepted, and they are checked like any other lead: a web enquiry from an address already in your CRM is flagged. The check runs on your side — the visitor is only told the enquiry was received, so the public form never reveals whether an address is already known.

### When the surviving record is later deleted

Expand Down
2 changes: 1 addition & 1 deletion content/docs/sales/leads.zh-Hans.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ description: 在入站咨询成为真正交易之前对其进行捕获、评分
- **是老客户的新咨询**——清空重复状态,按正常线索推进。预算重新到位的老客户是真实商机。
- **是同一次咨询重复录入**——把线索置为*未通过*,原因选**重复**。此时表单会要求你指明存活的那条记录,并把重复状态设为**已确认重复**。不指明存活记录就无法以重复为由作废,所以作废分析里的每一条都能点开。

> 匿名 Web-to-Lead 提交永远会被接受。公开表单有意不具备读取 CRM 数据的权限,如果它无法查询已有记录,线索会以未标记的状态落库——请在常规线索队列中复核。
> 匿名 Web-to-Lead 提交永远会被接受,并且和其他线索一样会被查重:来自 CRM 中已有地址的网页咨询会被标记。查重在你这一侧完成——访客只会得知咨询已收到,因此公开表单永远不会透露某个地址是否已被记录。

## 转化一条已确认的线索

Expand Down
2 changes: 1 addition & 1 deletion content/docs/sales/leads.zh-Hant.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ description: 在入站詢問成為真正交易之前對其進行擷取、評分
- **是老客戶的新詢問**——清空重複狀態,按正常流程推進。預算重新到位的老客戶是真實商機。
- **是同一次詢問重複輸入**——把記錄設為*未通過*,原因選**重複**。此時表單會要求你指明存活的那筆記錄,並把重複狀態設為**已確認重複**。不指明存活記錄就無法以重複為由作廢,所以作廢分析裡的每一筆都能點開。

> 匿名 Web-to-Lead 提交永遠會被接受。公開表單刻意不具備讀取 CRM 資料的權限,如果它無法查詢既有記錄,記錄會以未標記的狀態寫入——請在常規佇列中複核。
> 匿名 Web-to-Lead 提交永遠會被接受,並且和其他潛在客戶一樣會被查重:來自 CRM 中既有地址的網頁詢問會被標記。查重在你這一側完成——訪客只會得知詢問已收到,因此公開表單永遠不會透露某個地址是否已被記錄。

## 轉化一筆已確認的潛在客戶

Expand Down
Loading
Loading