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
8 changes: 8 additions & 0 deletions .changeset/2368-public-web-forms-guest-priority.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"hotcrm": patch
---

Clause-②: no
Metadata-protocol: none

**A visitor on the public support form can no longer open a Critical case, and the form reads as written for them (#2368).** Until now the **Contact Support** form at `/forms/support` let anyone pick **Critical**, which escalates the case on arrival and hands it to the Service Manager pool. The form no longer asks for a priority: a web case arrives at **Low** and the service team triages it. Even a submission sent past the form that names Critical is stored at **High** at most, so a web case never escalates by itself. Staff can still open and raise cases at Critical. Above the fields the form now reads "For an urgent outage, call the service hotline." (「紧急故障请拨打服务热线。」), and the name and email fields are labelled **Your name** and **Your email** (「您的姓名」「您的邮箱」) instead of the agent-side **Supplied Name** and **Supplied Email**, in English, Chinese, Spanish and Japanese. The Service Manual and the Cases page describe the form as it now is.
4 changes: 3 additions & 1 deletion content/docs/service/cases.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,12 @@ The case form (`src/service/views/case.view.ts`) is **not** tabbed. It is a sing

The platform gives an object one form, so this is the **create** form and the **edit** form both — which is why it stops where it does. What it offers is what the person raising a case actually has in hand at intake; everything the lifecycle fills in later is deliberately absent, because a creator typing an SLA deadline or a resolution time would be authoring state the app is supposed to derive. Those fields are read on the detail screen instead — see *What happens automatically* for who writes each one. **Case Number** is the one exception on the list: it is shown but never typed, because the app allocates it and the field is read-only.

The two are not one screen wearing different chrome — they overlap in only seven fields (Case Number, Subject, Contact, Supplied Name, Supplied Email, Case Origin, Description). **Case Type** is on the detail screen but not on the agent's case form at all. The public support form at `/forms/support` is a third layout again: a single section with six fields (the visitor's name and email, subject, description, type, priority).
The two are not one screen wearing different chrome — they overlap in only seven fields (Case Number, Subject, Contact, Supplied Name, Supplied Email, Case Origin, Description). **Case Type** is on the detail screen but not on the agent's case form at all. The public support form at `/forms/support` is a third layout again: a single section with five fields (the visitor's name and email, subject, description, type), labelled for the visitor: **Your name** and **Your email** there are the **Supplied Name** and **Supplied Email** an agent reads on the case.

**Who wrote in.** A case from the public support form has no account and no contact, because the visitor is anonymous. So the form asks for their name and email — the email is required, and the form will not send without it — and the case keeps them as **Supplied Name** and **Supplied Email**, beside **Contact**. They are exactly what the visitor typed: nothing checks them, and the app never links the case to a contact or an account because the address matches one. Reply to the supplied address (it is a mail link on the case), and link the case to the right contact once you have confirmed who wrote in. On a case an agent raises, both fields are optional.

**No priority from a visitor.** The public form does not ask for a priority, so a web case arrives at **Low** and the agent triages it. A visitor can never open a **Critical** case, which would escalate on arrival and go to the Service Manager pool: the app holds a visitor's case at **High** at most, whatever the submission says. Critical is set by staff, and a case an agent raises can still be opened at Critical. Above the fields the form reads *For an urgent outage, call the service hotline.* — the way a visitor with a real P1 reaches you.

## What happens automatically

- **Owner, when a case is claimed** — a case with **no owner** gets one the moment somebody moves it to *In Progress*, *Waiting on Customer* or *Waiting on Support*: whoever makes that change is stamped as the case's Owner in the same save. A case that already has an owner is never touched, and an automated or seeded write never claims — those leave the case ownerless, for the **Unassigned — triage** tab to keep showing. See *Claiming a case out of triage* below.
Expand Down
4 changes: 3 additions & 1 deletion content/docs/service/cases.zh-Hans.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,12 @@ description: 客户支持工单——主题、优先级、状态、解决方案

平台给一个对象只配一张表单,所以这既是**新建**表单也是**编辑**表单——这也正是它到此为止的原因。它提供的,就是提工单的人在受理那一刻真正手里有的东西;一切由生命周期后续填上的字段都是刻意不放的,因为让创建者去填 SLA 截止日期或解决时间,等于让他手写本该由应用自己推导出来的状态。那些字段改在详情界面上读——各自由谁写入,见下文《自动发生的事》。**工单编号**是这份清单上唯一的例外:它只展示、从不由人填写,因为编号由应用分配,字段本身是只读的。

这两者并不是同一个界面换了层皮——它们只在七个字段上重叠(工单编号、主题、联系人、提交人姓名、提交人邮箱、来源、描述)。**类型**只在详情界面上有,客服用的这张工单表单里根本没有它。`/forms/support` 上那张公开支持表单又是第三种排布:一个分区、六个字段(访客的姓名与邮箱、主题、描述、类型、优先级)。
这两者并不是同一个界面换了层皮——它们只在七个字段上重叠(工单编号、主题、联系人、提交人姓名、提交人邮箱、来源、描述)。**类型**只在详情界面上有,客服用的这张工单表单里根本没有它。`/forms/support` 上那张公开支持表单又是第三种排布:一个分区、五个字段(访客的姓名与邮箱、主题、描述、类型),标签是写给访客看的:表单上的**您的姓名**和**您的邮箱**,就是客服在工单上看到的**提交人姓名**和**提交人邮箱**。

**是谁提交的。** 从公开支持表单进来的工单没有账户、也没有联系人,因为访客是匿名的。所以表单会问访客的姓名和邮箱——邮箱必填,不填表单就提交不了——工单把它们记作**提交人姓名**和**提交人邮箱**,放在**联系人**旁边。它们就是访客原样填写的内容:没有任何东西核实它们,应用也绝不会因为邮箱和某个联系人对得上,就自动把工单关联到那个联系人或账户。请按提交人邮箱回复(它在工单上是一个邮件链接),确认来人身份之后,再把工单关联到正确的联系人。客服自己新建的工单上,这两个字段都是选填的。

**访客不选优先级。** 公开表单不询问优先级,所以网页工单以**低**优先级到达,由客服分诊。访客永远无法开出**严重**工单——严重工单到达即升级,并交给服务经理池:无论提交内容写了什么,应用对访客工单的优先级最高只到**高**。严重由员工设定,客服自己新建的工单仍可直接设为严重。表单字段上方写着*紧急故障请拨打服务热线。*——遇到真正 P1 的访客,就是这样联系到你们。

## 自动发生的事

- **认领时写入的负责人**——一张**没有负责人**的工单,只要有人把它转到 *In Progress*、*Waiting on Customer* 或 *Waiting on Support*,就会在同一次保存里把做这个动作的人标记为它的负责人。已经有负责人的工单不受影响;自动化写入或种子写入也永远不构成认领——它们让工单保持无主,继续留在 **未分派 — 待分诊** 标签页里被看见。见下文*认领待分流的工单*。
Expand Down
4 changes: 3 additions & 1 deletion content/docs/service/cases.zh-Hant.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,12 @@ description: 客戶支援工單——主題、優先順序、狀態、解決方

平台給一個物件只配一張表單,所以這既是**新增**表單也是**編輯**表單——這也正是它到此為止的原因。它提供的,就是提工單的人在受理那一刻真正手裡有的東西;一切由生命週期後續填上的欄位都是刻意不放的,因為讓建立者去填 SLA 截止日期或解決時間,等於讓他手寫本該由應用自己推導出來的狀態。那些欄位改在詳情介面上讀——各自由誰寫入,見下文《自動發生的事》。**工單編號**是這份清單上唯一的例外:它只展示、從不由人填寫,因為編號由應用配發,欄位本身是唯讀的。

這兩者並不是同一個介面換了層皮——它們只在七個欄位上重疊(工單編號、主題、聯絡人、提交人姓名、提交人郵箱、來源、描述)。**類型**只在詳情介面上有,客服用的這張工單表單裡根本沒有它。`/forms/support` 上那張公開支援表單又是第三種排佈:一個分區、六個欄位(訪客的姓名與郵箱、主題、描述、類型、優先順序)。
這兩者並不是同一個介面換了層皮——它們只在七個欄位上重疊(工單編號、主題、聯絡人、提交人姓名、提交人郵箱、來源、描述)。**類型**只在詳情介面上有,客服用的這張工單表單裡根本沒有它。`/forms/support` 上那張公開支援表單又是第三種排佈:一個分區、五個欄位(訪客的姓名與郵箱、主題、描述、類型),標籤是寫給訪客看的:表單上的**您的姓名**和**您的郵箱**,就是客服在工單上看到的**提交人姓名**和**提交人郵箱**。

**是誰提交的。** 從公開支援表單進來的工單沒有帳戶、也沒有聯絡人,因為訪客是匿名的。所以表單會問訪客的姓名和郵箱——郵箱必填,不填表單就送不出——工單把它們記作**提交人姓名**和**提交人郵箱**,放在**聯絡人**旁邊。它們就是訪客原樣填寫的內容:沒有任何東西核實它們,應用也絕不會因為郵箱和某個聯絡人對得上,就自動把工單關聯到那個聯絡人或帳戶。請按提交人郵箱回覆(它在工單上是一個郵件連結),確認來人身分之後,再把工單關聯到正確的聯絡人。客服自己新增的工單上,這兩個欄位都是選填的。

**訪客不選優先順序。** 公開表單不詢問優先順序,所以網頁工單以**低**優先順序到達,由客服分診。訪客永遠無法開出**嚴重**工單——嚴重工單到達即升級,並交給服務經理池:無論提交內容寫了什麼,應用對訪客工單的優先順序最高只到**高**。嚴重由員工設定,客服自己新增的工單仍可直接設為嚴重。表單欄位上方寫著*緊急故障請撥打服務熱線。*——遇到真正 P1 的訪客,就是這樣聯絡到你們。

## 自動發生的事

- **認領時寫入的負責人**——一張**沒有負責人**的工單,只要有人把它轉到 *In Progress*、*Waiting on Customer* 或 *Waiting on Support*,就會在同一次儲存裡把做這個動作的人標記為它的負責人。已經有負責人的工單不受影響;自動化寫入或種子寫入也永遠不構成認領——它們讓工單保持無主,繼續留在 **未分派 — 待分診** 標籤頁裡被看見。見下文*認領待分流的工單*。
Expand Down
5 changes: 4 additions & 1 deletion src/sales/translations/es-ES/objects.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,14 @@ export const service: Record<string, ObjectTranslationData> = {
case: { label: 'Caso' },
how_can_we_help: {
label: '¿Cómo podemos ayudarte?',
description: 'Para una caída urgente, llama a la línea de soporte.',
fields: {
subject: { placeholder: 'Resumen breve del problema' },
description: { placeholder: 'Pasos para reproducirlo, mensajes de error, capturas de pantalla...' },
type: { helpText: 'Elige la opción que mejor describa tu problema.' },
priority: { helpText: 'Los problemas críticos también deben comunicarse por teléfono.' },
// Etiquetas para el visitante (#2368); las del objeto son para el agente.
supplied_name: { label: 'Tu nombre' },
supplied_email: { label: 'Tu correo electrónico' },
},
},
// Claves de sección del objeto (case.object.ts) que usan los
Expand Down
5 changes: 4 additions & 1 deletion src/sales/translations/ja-JP/objects.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,14 @@ export const service: Record<string, ObjectTranslationData> = {
case: { label: 'ケース' },
how_can_we_help: {
label: 'どのようなご用件でしょうか?',
description: '緊急の障害はサービスホットラインにお電話ください。',
fields: {
subject: { placeholder: '問題の簡単な概要' },
description: { placeholder: '再現手順、エラーメッセージ、スクリーンショットなど…' },
type: { helpText: 'お問い合わせ内容に最も近い選択肢をお選びください。' },
priority: { helpText: '重大な問題は電話でもご連絡ください。' },
// 訪問者向けのラベル (#2368)。オブジェクトのラベルは担当者向け。
supplied_name: { label: 'お名前' },
supplied_email: { label: 'メールアドレス' },
},
},
// オブジェクト定義のセクションキー(case.object.ts)— 入力フォームと、
Expand Down
5 changes: 4 additions & 1 deletion src/sales/translations/zh-CN/objects.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,11 +134,14 @@ export const service: Record<string, ObjectTranslationData> = {
case: { label: '工单' },
how_can_we_help: {
label: '我们能帮您什么?',
description: '紧急故障请拨打服务热线。',
fields: {
subject: { placeholder: '问题的简要概述' },
description: { placeholder: '复现步骤、错误信息、截图……' },
type: { helpText: '请选择最符合您问题的选项。' },
priority: { helpText: '严重问题请同时致电报告。' },
// 访客视角的称呼 (#2368):对象字段名「提交人姓名 / 提交人邮箱」是给坐席看的。
supplied_name: { label: '您的姓名' },
supplied_email: { label: '您的邮箱' },
},
},
// Object-level section keys (case.object.ts), used by record forms and
Expand Down
15 changes: 15 additions & 0 deletions src/service/docs/crm_service_cases.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,21 @@ for the SLA clock:
> the Service Manager pool, and alert the agent it came from — and both leave it
> where it is when that pool is unstaffed.

## Cases from the public support form

Visitors who are not signed in raise cases on the public support form
(**Contact Support**, at `/forms/support`). It asks for their name and email
(**Your name** and **Your email**, stored on the case as **Supplied Name** and
**Supplied Email**), a subject, a description and a case type, and above the
fields it reads: *For an urgent outage, call the service hotline.*

The form does not ask for a priority. A web case arrives at **Low**, with
**Case Origin** set to **Web**, and you triage it from there. A visitor can never
open a **Critical** case: the app holds a visitor's case at **High** at most, so
a web case never escalates on arrival and never goes to the Service Manager pool
by itself. Critical is a staff decision. When a visitor's problem really is
urgent, raise the priority yourself and the escalation above runs.

---

**Related:** recording which article solved a case — see
Expand Down
6 changes: 6 additions & 0 deletions src/service/docs/crm_service_cases.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,12 @@ stateDiagram-v2

> 两张安全网,两个触发条件:**优先级 = 严重**会*立即*升级;**错过 SLA 到期**会*在违约时*升级。两者都会标记工单、把它交给服务经理池、并提醒原来的客服——服务经理池无人时,两者都让工单留在原处。

## 来自公开支持表单的工单

未登录的访客在公开支持表单(**联系支持团队**,地址 `/forms/support`)上提交工单。表单询问他们的姓名和邮箱(**您的姓名**、**您的邮箱**,在工单上保存为**提交人姓名**和**提交人邮箱**)、主题、描述和类型,字段上方写着:*紧急故障请拨打服务热线。*

表单不询问优先级。网页工单以**低**优先级到达,**工单来源**为**网站**,由你从这里开始分诊。访客永远无法开出**严重**工单:应用对访客工单的优先级最高只到**高**,所以网页工单到达时不会升级,也不会自行进入服务经理池。设为严重是员工的决定。访客的问题确实紧急时,由你提高优先级,上面的升级随即发生。

---

**相关:** 记录是哪篇文章解决了工单——见[知识库](./crm_service_knowledge.md)。
20 changes: 18 additions & 2 deletions src/service/objects/case.hook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,8 @@ const caseValidation: Hook = {
// staff-side (`case_escalation`, `case_sla_monitor`, the `escalate_case`
// screen flow). The public `web_to_case` form (`src/service/views/case.view.ts`)
// collects exactly `supplied_name`, `supplied_email`, `subject`,
// `description`, `type` and `priority`, so nulling this drops nothing any
// guest surface asks for. ⛔ The two `supplied_*` columns (#2190) are NOT
// `description` and `type`, so nulling this drops nothing any guest
// surface asks for. ⛔ The two `supplied_*` columns (#2190) are NOT
// in the strip below, deliberately: they are the visitor's own name and
// reply address, the one fact about themselves the form exists to collect,
// and nothing downstream reads them as verified. `guest_portal`
Expand Down Expand Up @@ -194,6 +194,22 @@ const caseValidation: Hook = {
// medium") that the app does not honour. Whether a web-submitted case
// SHOULD outrank a staff-created one is a product question, and it is not
// settled by leaving dead code here that says it already is.
//
// A guest picks at most High (#2368, maintainer ruling). Critical
// escalates on insert (`case_escalation_on_create`), hands the case to
// the service manager pool and takes the 4-hour SLA row, so it is a
// staff decision. A form row's `options` subset does not narrow the
// door (`POST /forms/support/submit`): the posted value is validated
// against the OBJECT's options, so a form that asks for priority lets a
// crafted `critical` reach this line. It is CAPPED rather than refused: the
// visitor's case is real and the hotline hint tells them how to get a
// P1 handled; refusing would lose the case to save a staff re-triage.
// Capping here, before `priority` is read below, keeps the rank, the SLA
// row and every after-insert flow on the stored `high`.
// On 18.2.0 the form asks no priority at all (no per-form option subset
// exists), so the door's allow-list drops a posted one first; this line
// is the layer that holds the day the form's list moves (#1505).
if (input.priority === 'critical') input.priority = 'high';
input.owner_id = null;
input.is_escalated = false;
input.escalation_reason = null;
Expand Down
Loading
Loading