Skip to content
13 changes: 13 additions & 0 deletions .changeset/2338-browser-pass-dead-ends.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
"hotcrm": patch
---

Clause-②: no
Metadata-protocol: none

**Four dead ends from the 18.2.0 browser pass are gone (#2338 items 1–4).**

- **Service Overview is offered only to people who can read cases.** A marketing user, who reads no cases, was offered it in the sidebar, and every tile said "You don't have permission to view the data behind this widget". It now asks the same question the three case reports already ask. Sales Activity stays open to marketing users: it reads their own activity, so it works. Sales Performance also stays open; one of its tiles, Quota Attainment by Rep, still refuses a member who cannot read forecasts, because the platform cannot hide a single tile by access yet.
- **Edit Case shows Resolution when you close the case.** Picking Status "Closed" on the edit form used to end in "Resolution is required when closing a case" with nowhere to type one. The form now shows Resolution as soon as Status is Resolved or Closed. A new case still opens without it.
- **Marketing Managers and Marketing Directors can edit campaigns.** Both positions now give their holders the Marketing User profile, so the people who lead marketing can edit a live campaign a marketer owns and enroll its members, as their position descriptions promise. Before, they could only read campaigns.
- **"Update Stage" is no longer a list header button on opportunities.** That button failed whatever was ticked: with two rows it said it runs on a single record, and with none it opened a dialog that targeted nothing. Tick the deals and use **Update Stage** on the selection bar, which moves them all in one run. It is now on the selection bar of every opportunity table list, not only Open Deals, and so is **Delete**, which Open Deals had lost.
2 changes: 2 additions & 0 deletions content/docs/administration/profiles.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ Besides the six profiles above, HotCRM ships one more permission set, **CRM User
- Read **knowledge articles**
- Cannot delete records

The **marketing_manager** and **marketing_director** positions give this profile to everyone who holds them, so the people who lead marketing can do everything a marketer does, editing any campaign and enrolling members included. Adding someone to either position in **Setup → Positions** is enough; there is no profile to assign by hand.

### 🌐 Guest (Public Forms)

- **Insert only** on **leads** and **cases**, through the public Web-to-Lead / Web-to-Case forms
Expand Down
2 changes: 2 additions & 0 deletions content/docs/administration/profiles.zh-Hans.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ description: 六种内置权限集,以及每一种能查看、编辑和执行
- 读取 **知识文章**
- 不能删除记录

**marketing_manager** 与 **marketing_director** 两个岗位会把这个权限档案授予岗位上的每个人,所以带领营销团队的人能做营销人员能做的一切,包括编辑任意营销活动、登记活动成员。在 **Setup → Positions** 里把人加进这两个岗位之一就够了,不需要再手动分配权限档案。

### 🌐 Guest (Public Forms)

- 通过公开的 Web-to-Lead / Web-to-Case 表单,对 **线索** 和 **工单** **仅可插入**
Expand Down
2 changes: 2 additions & 0 deletions content/docs/administration/profiles.zh-Hant.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ description: 六種內建權限集,以及每一種能檢視、編輯和執行
- 讀取 **知識文章**
- 不能刪除記錄

**marketing_manager** 與 **marketing_director** 兩個職位會把這個權限設定檔授予職位上的每個人,所以帶領行銷團隊的人能做行銷人員能做的一切,包括編輯任意行銷活動、登記活動成員。在 **Setup → Positions** 裡把人加進這兩個職位之一就夠了,不需要再手動指派權限設定檔。

### 🌐 Guest (Public Forms)

- 透過公開的 Web-to-Lead / Web-to-Case 表單,對 **線索** 和 **工單** **僅可插入**
Expand Down
2 changes: 1 addition & 1 deletion content/docs/getting-started/quick-tour.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ Inside HotCRM, the left nav opens with two pinned items — **Home**, which is t

**Reports** is collapsed when the app loads — **Service**, **Sales**, **My Work**, **Activity**, **Marketing** and **Insights** open themselves. Click it open before deciding a report isn't there.

The entries built on opportunities — **Home**, **Sales Home**, **Sales Performance**, **CRM Overview** and the four opportunity reports under **Reports** — are left out of the sidebar for anyone who cannot read opportunities, and for a service agent or service manager by position: they can still open their own deals from **Opportunities** and **My Deals**, but these dashboards and reports are not shown to them. Every other report asks the same of its own records: a service agent still gets the lead, account and case reports, and a user who cannot read cases does not get the three case reports. **Home** is left out for a sales rep as well, which is why a rep's sidebar starts at **Sales Home**. A sales rep works no cases, so a rep's sidebar also leaves out **Service Overview**, **Cases**, **My Cases** and the three case reports — **Cases by Status and Priority**, **SLA Performance** and **Cases Opened by Day**; **Knowledge** stays, because articles are read while selling too. **Service** is the first group because the app opens on the first entry a sidebar shows: for a service agent or service manager that is **Service Overview**.
The entries built on opportunities — **Home**, **Sales Home**, **Sales Performance**, **CRM Overview** and the four opportunity reports under **Reports** — are left out of the sidebar for anyone who cannot read opportunities, and for a service agent or service manager by position: they can still open their own deals from **Opportunities** and **My Deals**, but these dashboards and reports are not shown to them. Every other report asks the same of its own records: a service agent still gets the lead, account and case reports, and a user who cannot read cases does not get **Service Overview** or the three case reports. **Home** is left out for a sales rep as well, which is why a rep's sidebar starts at **Sales Home**. A sales rep works no cases, so a rep's sidebar also leaves out **Service Overview**, **Cases**, **My Cases** and the three case reports — **Cases by Status and Priority**, **SLA Performance** and **Cases Opened by Day**; **Knowledge** stays, because articles are read while selling too. **Service** is the first group because the app opens on the first entry a sidebar shows: for a service agent or service manager that is **Service Overview**.

One sidebar row per destination is deliberate. Where an object offers several ways of looking at the same records, those live as tabs across the top of that object's list page, not as extra rows in the nav — so the sidebar stays short enough to scan.

Expand Down
2 changes: 1 addition & 1 deletion content/docs/getting-started/quick-tour.zh-Hans.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ description: HotCRM 的 5 分钟演练 —— 主页、你的第一条线索、

**Reports** 这个分组在应用加载时是折叠的——**Service**、**Sales**、**My Work**、**Activity**、**Marketing**、**Insights** 都会自动展开。先把它点开,再判断某份报表在不在这里。

建立在商机之上的条目——**Home**、**Sales Home**、**Sales Performance**、**CRM Overview**,以及 **Reports** 下的四张商机报表——不能读取商机的人在侧边栏里看不到;服务坐席与服务经理也按岗位看不到它们:他们仍可从 **Opportunities** 与 **My Deals** 打开自己的商机,但这些仪表盘与报表不对他们显示。其余报表对各自的数据也是同样的要求:服务坐席照样看得到线索、客户与工单报表,而不能读取工单的人看不到那三张工单报表。销售代表也看不到 **Home**,所以销售代表的侧边栏从 **Sales Home** 开始。销售代表不处理工单,所以销售代表的侧边栏也看不到 **Service Overview**、**Cases**、**My Cases** 以及那三张工单报表——**Cases by Status and Priority**、**SLA Performance**、**Cases Opened by Day**;**Knowledge** 仍然保留,因为销售时也要查阅知识文章。**Service** 排在所有分组之首,是因为应用打开时落在侧边栏显示的第一个条目上:对服务坐席与服务经理来说,那就是 **Service Overview**。
建立在商机之上的条目——**Home**、**Sales Home**、**Sales Performance**、**CRM Overview**,以及 **Reports** 下的四张商机报表——不能读取商机的人在侧边栏里看不到;服务坐席与服务经理也按岗位看不到它们:他们仍可从 **Opportunities** 与 **My Deals** 打开自己的商机,但这些仪表盘与报表不对他们显示。其余报表对各自的数据也是同样的要求:服务坐席照样看得到线索、客户与工单报表,而不能读取工单的人看不到 **Service Overview** 和那三张工单报表。销售代表也看不到 **Home**,所以销售代表的侧边栏从 **Sales Home** 开始。销售代表不处理工单,所以销售代表的侧边栏也看不到 **Service Overview**、**Cases**、**My Cases** 以及那三张工单报表——**Cases by Status and Priority**、**SLA Performance**、**Cases Opened by Day**;**Knowledge** 仍然保留,因为销售时也要查阅知识文章。**Service** 排在所有分组之首,是因为应用打开时落在侧边栏显示的第一个条目上:对服务坐席与服务经理来说,那就是 **Service Overview**。

一个去处只占一行,这是刻意的。同一个对象如果有多种查看方式,它们是该对象列表页顶部的标签页,而不是导航里另起一行——这样侧边栏才短到能一眼扫完。

Expand Down
2 changes: 1 addition & 1 deletion content/docs/getting-started/quick-tour.zh-Hant.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ description: HotCRM 的 5 分鐘演練 —— 主頁、你的第一條線索、

**Reports** 這個群組在應用載入時是摺疊的——**Service**、**Sales**、**My Work**、**Activity**、**Marketing**、**Insights** 都會自動展開。先把它點開,再判斷某份報表在不在這裡。

建立在商機之上的條目——**Home**、**Sales Home**、**Sales Performance**、**CRM Overview**,以及 **Reports** 下的四張商機報表——不能讀取商機的人在側邊欄裡看不到;服務專員與服務經理也按職位看不到它們:他們仍可從 **Opportunities** 與 **My Deals** 打開自己的商機,但這些儀表板與報表不對他們顯示。其餘報表對各自的資料也是同樣的要求:服務專員照樣看得到線索、客戶與工單報表,而不能讀取工單的人看不到那三張工單報表。銷售代表也看不到 **Home**,所以銷售代表的側邊欄從 **Sales Home** 開始。銷售代表不處理工單,所以銷售代表的側邊欄也看不到 **Service Overview**、**Cases**、**My Cases** 以及那三張工單報表——**Cases by Status and Priority**、**SLA Performance**、**Cases Opened by Day**;**Knowledge** 仍然保留,因為銷售時也要查閱知識文章。**Service** 排在所有群組之首,是因為應用打開時落在側邊欄顯示的第一個條目上:對服務專員與服務經理來說,那就是 **Service Overview**。
建立在商機之上的條目——**Home**、**Sales Home**、**Sales Performance**、**CRM Overview**,以及 **Reports** 下的四張商機報表——不能讀取商機的人在側邊欄裡看不到;服務專員與服務經理也按職位看不到它們:他們仍可從 **Opportunities** 與 **My Deals** 打開自己的商機,但這些儀表板與報表不對他們顯示。其餘報表對各自的資料也是同樣的要求:服務專員照樣看得到線索、客戶與工單報表,而不能讀取工單的人看不到 **Service Overview** 和那三張工單報表。銷售代表也看不到 **Home**,所以銷售代表的側邊欄從 **Sales Home** 開始。銷售代表不處理工單,所以銷售代表的側邊欄也看不到 **Service Overview**、**Cases**、**My Cases** 以及那三張工單報表——**Cases by Status and Priority**、**SLA Performance**、**Cases Opened by Day**;**Knowledge** 仍然保留,因為銷售時也要查閱知識文章。**Service** 排在所有群組之首,是因為應用打開時落在側邊欄顯示的第一個條目上:對服務專員與服務經理來說,那就是 **Service Overview**。

一個去處只佔一列,這是刻意的。同一個物件如果有多種檢視方式,它們是該物件列表頁頂部的標籤頁,而不是導覽裡另起一列——這樣側邊欄才短到能一眼掃完。

Expand Down
2 changes: 1 addition & 1 deletion content/docs/service/cases.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ The two are not one screen wearing different chrome — they overlap in only sev

## Built-in rules

- **Closed cases must have a resolution** — you can't close a case with an empty Resolution field.
- **Closed cases must have a resolution** — you can't close a case with an empty Resolution field. The edit form shows **Resolution** as soon as **Status** is set to *Resolved* or *Closed*, so you can fill it in on the same save; a new case does not show it.
- **Escalated cases must have an escalation reason** — explains why a senior agent was pulled in.
- **Status progression is advice, not a gate** — the transition table says *New* leads to *In Progress*, *Waiting on Customer*, *Escalated* or *Closed*, so jumping straight to *Resolved* is outside it. That does not stop the save: the case is stored as *Resolved* and the engine writes one line to the server log, `WARN Validation rule 'case_status_progression' (warning): Invalid status transition`. Unlike the two rules above — both `error` severity, both of which do reject the save — this one only advises. See [State Machines](/docs/administration/state-machines) for what the table is good for.

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

## 内置规则

- **已关闭工单必须有解决方案**——解决方案字段为空时无法关闭工单。
- **已关闭工单必须有解决方案**——解决方案字段为空时无法关闭工单。编辑表单在状态选为「已解决」或「已关闭」时就会显示解决方案字段,可以在同一次保存里填写;新建工单时不显示它。
- **升级工单必须有升级原因**——解释为何引入了资深客服。
- **状态推进是建议,不是闸门**——转换表写的是 *New* 之后可以去 *In Progress*、*Waiting on Customer*、*Escalated* 或 *Closed*,所以直接跳到 *Resolved* 是走出了这张表。但这拦不下保存:工单照样以 *Resolved* 落库,引擎只在服务端日志里写一行 `WARN Validation rule 'case_status_progression' (warning): Invalid status transition`。与上面两条不同——那两条是 `error` 级、确实会拒绝保存——这一条只做提示。转换表到底有什么用,见[状态机](/zh-Hans/docs/administration/state-machines)。

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

## 內建規則

- **已關閉工單必須有解決方案**——解決方案欄位為空時無法關閉工單。
- **已關閉工單必須有解決方案**——解決方案欄位為空時無法關閉工單。編輯表單在狀態選為 *Resolved* 或 *Closed* 時就會顯示解決方案欄位,可以在同一次儲存裡填寫;新建工單時不顯示它。
- **升級工單必須有升級原因**——解釋為何引入了資深客服。
- **狀態推進是建議,不是閘門**——轉換表寫的是 *New* 之後可以去 *In Progress*、*Waiting on Customer*、*Escalated* 或 *Closed*,所以直接跳到 *Resolved* 是走出了這張表。但這攔不下儲存:工單照樣以 *Resolved* 落庫,引擎只在伺服器日誌裡寫一行 `WARN Validation rule 'case_status_progression' (warning): Invalid status transition`。與上面兩條不同——那兩條是 `error` 級、確實會拒絕儲存——這一條只做提示。轉換表到底有什麼用,見[狀態機](/zh-Hant/docs/administration/state-machines)。

Expand Down
19 changes: 16 additions & 3 deletions src/sales/actions/opportunity.actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,10 @@ export const CloneOpportunityAction: Action = {
*
* Both invocation halves work:
*
* - SINGLE record (row selection / toolbar with one row): the console POSTs
* `{ recordId, params: { stage } }` and the body reads `ctx.recordId`.
* - SINGLE record (a REST or MCP caller naming one `recordId`; the list
* header copy that also reached this half is gone, see `locations`): the
* call carries `{ recordId, params: { stage } }` and the body reads
* `ctx.recordId`.
* - MULTI record: the selection arrives in the BUILT-IN `_selectedIds`
* param — leading underscore — injected by the grid renderer for the
* view's `bulkActionDefs` entry with `execution: 'aggregate'`, which
Expand Down Expand Up @@ -199,7 +201,18 @@ export const MassUpdateStageAction: Action = {
capabilities: ['api.write'],
timeoutMs: 10000,
},
locations: ['list_toolbar'],
// Headless (#2338 item 4): drawn only on the SELECTION BAR, which the
// opportunity list opens by naming this action in its `bulkActionDefs`
// (`src/sales/views/opportunity.view.ts`) and which places it by name, with
// no location here. List views do not inherit the def: each grid list of
// the object declares it itself, through `GRID_BAR` in that file.
// ⛔ Do not add `list_toolbar` back. Measured on 18.2.0, the console's server
// dispatch from the list header injects no `_selectedIds`: with two rows
// ticked it refused ("This action runs on a single record — select exactly
// one row"), and with none it opened the New Stage dialog over no record and
// the body threw. The header copy failed on every selection, while the bar's
// aggregate run moved them all.
locations: [],
params: [
{
name: 'stage',
Expand Down
11 changes: 11 additions & 0 deletions src/sales/apps/crm.app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,17 @@ const HOTCRM_ICON =
* read. Since objectui#12426 (in the 18.0.0-next.2 console) the gate moves the
* landing too: the console opens an app on the first entry the member is
* SHOWN, not the first one served.
*
* Two dashboards stay open to the marketing user, measured on 18.2.0 (#2338
* item 1). Sales Activity carries no gate: every member reads their own events
* and tasks through `crm_user`, so its tiles answer, and the zeros a marketer
* sees are their own activity, not a refusal. Sales Performance refuses that
* member 1 of its 15 tiles, Quota Attainment by Rep (`forecast_metrics`; the
* set grants no `crm_forecast`), and is kept open like Customer Churn Signals
* below rather than hidden for one tile. No tile can be gated on read here: a
* widget's `requiresObject` asks whether the object is installed (the console
* filters on the served object list, which `/meta/object` serves whole to the
* marketing user), and a widget takes no `visible`.
*/

/**
Expand Down
Loading
Loading