"""备份负责人 + 房间升级戳 ## 备份负责人 转人工的价值上限是「快」。主负责人休假两周,那两周里每一次「转人工」都会把员工和 一个不会回话的人关进一个房间 —— 那比降级成只建工单更糟,因为员工会**以为**有人 在看。 所以路由表多一列 `backup_owner_person_id`:主负责人今天在休假(读 `hr_leave_absences`,与对外可用性接口同一套 `approval_status` 判据)、或者他压根 没有可用的飞书账号时,拉备份那个人。**两个都不在时仍然降级成只建工单** —— 拉不到 人时说拉到了,是这条链路最坏的失败。 ## 升级戳 `assistant_ticket_chats.escalated_at`:这个房间已经因为「催了几轮还没动」被升级过 一次(把备份负责人或 IT 管理员拉进来)。 为什么是一列,而不是「数一数群里有几个人」:升级只该发生一次,而群里的人数可能 因为有人手动拉了同事进来而变多。没有这一列,每一次催办都会再拉一次人 —— 一个房间 会慢慢变成整个 IT 团队。 ## 不新建一套催办阶梯 群内催办**复用** `support.ticket_sla_reminder` 已有的水位与冷却(`cooldown_hours`、 `last_agent_reminded_at`、`agent_reminder_count`)。第二套时间表意味着两个地方各自 决定「多久算超时」,而这类数只能拉长不能收紧 —— 两套一定会漂,且漂的方向是「催得 更频繁」。 """ from __future__ import annotations import sqlalchemy as sa from alembic import op from app.core.migration_helpers import id_type_for revision = "635" down_revision = "634" branch_labels = None depends_on = None ROUTES = "assistant_escalation_routes" CHATS = "assistant_ticket_chats" def _columns(bind, table: str) -> set[str]: inspector = sa.inspect(bind) if table not in inspector.get_table_names(): return set() return {c["name"] for c in inspector.get_columns(table)} def upgrade() -> None: bind = op.get_bind() if ROUTES in sa.inspect(bind).get_table_names(): if "backup_owner_person_id" not in _columns(bind, ROUTES): op.add_column( ROUTES, sa.Column( "backup_owner_person_id", # 取父表的 id 类型(**含排序规则**),而不是手写一个定长 # 36 字符的裸类型:裸类型会继承**表**的默认排序规则,而 TiDB # 上父表可能是另一种 —— 那正是 3780 的形状。 # # ⚠️ 这句注释刻意不写出那个裸类型的字面写法: # `check_tidb_parity` 按字面量扫描整个 sa.Column 调用体, # 于是一句「不要用 X」的注释会被当成真的用了 X(这条迁移 # 因此让 go-ci 连红五轮)。 id_type_for("identity_persons"), sa.ForeignKey("identity_persons.id"), nullable=True, ), ) if CHATS in sa.inspect(bind).get_table_names(): if "escalated_at" not in _columns(bind, CHATS): op.add_column( CHATS, sa.Column("escalated_at", sa.DateTime(timezone=True), nullable=True), ) def downgrade() -> None: bind = op.get_bind() if "escalated_at" in _columns(bind, CHATS): op.drop_column(CHATS, "escalated_at") if "backup_owner_person_id" in _columns(bind, ROUTES): op.drop_column(ROUTES, "backup_owner_person_id")