Skip to content

feat(ticket): 补齐 #9 —— 用户关单/催单、接单/转交、部门/分类/关联 - #2

Closed
worable233 wants to merge 0 commit into
mainfrom
feat/ticket-full-lifecycle
Closed

worable233 wants to merge 0 commit into
mainfrom
feat/ticket-full-lifecycle

Conversation

@worable233

Copy link
Copy Markdown
Owner

背景

审计 #9 把「工单」压成一行笼统能力,结果是同时高估和漏记:

  • 高估:把「能改 assigneeId」说成了完整的接单/转交流程;
  • 漏记:用户关单、催单、部门/分类/关联业务对象在源码里根本不存在。

此前工单插件只有「提交 / 回话 / 管理员改状态」三条路。这份 PR 按真实缺口逐条补上,并保持已有接口完全兼容(旧 kernel-tests/ticket.test.ts 9/9 原样通过,一行没改)。

状态机

新增 PENDING_USER(等用户回话):

谁做了什么 之前 现在
管理员回复 状态不动 推到 PENDING_USER
用户回话 状态不动 从 PENDING_USER/RESOLVED 回到 IN_PROGRESS

之前「谁在等谁」无法表达,工单会一直停在 IN_PROGRESS 而实际上用户在等客服。CLOSED 成为真正终态:管理端不能把已关闭工单改回其它状态,否则「关闭」形同虚设。

用户侧

  • POST /tickets/:id/close
    • 只能关自己的单(别人的返回 403)
    • 幂等:重复关闭不报错、不重复写消息
    • 关闭原因写成一条普通消息而不是独立字段 —— 它属于时间线,未来回看时上下文才不会断
  • POST /tickets/:id/remind
    • 只累计 reminderCount + 记最后催单时间,不写消息(不污染时间线)
    • 10 分钟冷却,防刷屏
    • 已关闭工单拒绝催单(409),不会让用户以为还有人在处理

管理侧

  • 接单不新增接口,而是「指派给自己」的一次 PATCH:assigneeId 非空且原状态为 OPEN 时自动进 IN_PROGRESS。
    刻意只有一条写状态的路径 —— 两套接口迟早会产出不一致的 assignee/status 组合。
  • 转交 = 改 assigneeId,通知新处理人,不动状态。
  • 内部备注:消息带 internal,只对管理员可见。
    过滤放在读取路径(而不是只在 UI 隐藏),并且该字段不进索引 —— 内核 where 只接受已声明索引字段,否则直接 422。
  • 管理端筛选新增 category / departmentId / unassigned(「待接单」队列)。

数据模型

工单新增:category、departmentId、relatedType + relatedId(成对校验,单边数据没有追溯价值)、reminderCount、lastRemindedAtMs、closedAtMs、closedBy;消息新增 internal。

全部新列可空且有默认值:扩展引擎只做 ADD COLUMN IF NOT EXISTS,老数据读出来是 null,不会因为加字段而校验失败。

前端

共享 TicketWorkspace 只开挂载点(extraActions + 可选文案),业务动词与文案留在插件里 —— 内核 UI 不该承载某个插件的工单语义。

  • 用户详情页:催单 / 关闭工单
  • 管理详情页:接单、分类与部门字段、回复框的「内部备注」勾选
  • 四语言文案补齐(zh-CN / zh-TW / zh-HK / en-US)

验证

apps/api/tests/integration/ticket.test.ts   14 passed(真实 Postgres + 真实安装产物)
旧 kernel-tests/ticket.test.ts              9 passed(接口零破坏)
tsc(插件 / 内核 sdk+ui)                    干净
prettier / i18n 目录检查                     通过

内核侧 14 个用例锁的是行为而不是「接口存在」:

用例 锁住的语义
关单越权 别人的单返回 403
关单幂等 第二次关闭不重复写「关闭原因」消息
催单冷却 首次 200,10 分钟内第二次 429
关闭后 回复 409、催单 409
接单 assigneeId = 自己,且状态从 OPEN 变 IN_PROGRESS
转交 换人但不动状态
内部备注 用户视图读不到,管理员视图 internal: true
非管理员写内部备注 403
终态 已关闭工单 PATCH 回 OPEN → 409
RESOLVED 回话 自动回到 IN_PROGRESS
关联对象 relatedId 单边提交 → 400
筛选 category=billing、unassigned=true 计数正确

未做(明确披露)

  • 部门实体本身:本次落地的是工单侧的 departmentId 与筛选。部门表(名称/成员/路由规则)属于组织架构域,需要独立的数据模型与管理员 CRUD,不与工单强耦合。
  • SLA 计时 / 自动升级:需要「服务时段 + 优先级矩阵」的产品口径,属于新能力。
  • 附件:现由媒体域承载,工单侧不重复实现。

@worable233 worable233 closed this Oct 24, 2026
@worable233
worable233 force-pushed the feat/ticket-full-lifecycle branch from 6978635 to eb1fee4 Compare October 24, 2026 10:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant