# 招聘流程

Version: 829db5245701f2f8

模块地址：https://dev.planahr.top/api/v1/hiring/tools；权限 hiring:read / hiring:write。写权限不包含读权限，均不扩大用户自身权限。

操作名：read、create、recommend、transition、discardRecommendation、discardDecision、resolveOfferBroadcast、assign、collaborate、note、event、jobState、jobAssign、candidatePolicy、candidateAssign、resolveImport、personalNote。工具名称前缀 hiring_ 不用于 POST 的 operation。

GET 加 ?operation=操作名 只取单项 schema；?view=summary 只取目录。POST 不带查询参数，使用 {operation,input} 和 Content-Type: application/json。

```bash
curl 'https://dev.planahr.top/api/v1/hiring/tools?operation=read' \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

- 创建招聘流程：先候选人/岗位详情确认目标与状态，再 hiring.create；一个候选人可对应多个不同岗位的流程，重复的活跃关联会被拒绝。
- 推进流程：先 hiring.read/detail 取得当前 version，再 hiring.transition 记录真实阶段与 reason；Offer/入职按 schema 填实际日期和确认字段。
- 暂停/关闭岗位：先 hiring.read/jobImpact，取得 version、impactToken 和关联流程；与用户明确影响及 Offer 处置，再 hiring.jobState。预览已过时则重新读取。
- 分配负责人：先 context 取得 active 成员的 ID，再用 hiring.assign / candidateAssign / jobAssign。转交业务负责人不会转交创作者身份或作者的私密备注。
- 新建招聘流程必须有可读取的简历原件（旧平台已迁移原件，或上传且 kind=resume 的附件），不要求 PDF 转换完成。正文、联系方式、脉脉链接、screenshot/other、原件已丢失的预览缓存不算。409 RESUME_ATTACHMENT_REQUIRED 时先补传真正的简历，不是登录/权限错误；上传需 candidates:write 和附件编辑权，不能伪造文件。
- 推荐用 recommend({id,version,reason,requestId})，需 hiring:write 和本流程编辑权。先 read detail 取版本。服务器固定岗位客户经理及真实飞书身份、简历原件版本、岗位和推荐理由；不接收调用者自选审批人或通过结果。outcome=approval_pending 不代表推荐完成，显示 stage=recommendation_pending；transition 从 sourced 到 recommended 也只发起审批，不能跳过到面试/Offer。本人是岗位客户经理也必须在飞书手动批准。已推荐=客户经理接受，不表示已发送客户；通过后按发起顾问及飞书通过时间计数，旧推荐不补审。
- read detail.recommendations 查看审批记录。后台约15秒核对，只认飞书最终同意/拒绝/撤回；退回、转交仍审批中。审批锁定仅当前流程阶段，不锁其他岗位或候选人备注。需更换材料先在飞书撤回，等待最终同步再重提；岗位客户经理中途转交不改原审批人。拒绝/撤回保留初步接洽和历史。review 表示结果/材料冲突，未推进、不计数；确认放弃时由原申请人/管理员调用 discardRecommendation({id,version,recommendationId,reason,requestId}) 后重提，不能人工改为通过。模板/身份未配置返回明确错误，不能绕过。
- recommendations[].document.url 是本次提交的简历原件鉴权地址，不含公开token；Agent 下载需 hiring:read + candidates:read + sensitive:documents，按用户进行文档读取审计。不需要下载时仅查看状态；绝不将简历全文或联系方式复制进审批理由。
- read 的资源：context（当前身份及成员 ID）、flows、detail、dashboard、personalNote、jobImpact、progress。progress 默认本人；period=today/yesterday/week/last_week/month/last_month/7d/30d/90d（北京时间），yesterday 为前一整天，week 为本周周一至今，last_week 为前一个完整周一至周日，month 为当月 1 日至今，last_month 为前一个完整自然月（非近 30 天），从返回 options[].value 选择 target。管理员可看公司/团队/个人，团队负责人仅自己/下属/团队。录入区分 automaticResumes（系统盘点）、manualResumes（顾问网页或 Agent 主动新建）、unclassifiedResumes（历史来源不明）；resumes 为三者总数。团队可见不代表可编辑；私密备注只对作者开放。
- 客户招聘看板：hiring.read 的 resource=flows，input 传真实 clientId、scope=all、state=live、page=1、pageSize=6，按需加 stageGroup。clientId 按岗位真实客户绑定筛选，不按公司名匹配；筛选先于总数、阶段计数和分页。客户 ID 可由 clients.search 获取。每张卡是一位候选人×一个岗位，同人多岗分别计数；scope=all 可看同组织概况，不授予详细安排、私密备注或修改权，按 canDetail/canEdit 继续。沿用 hiring:read，原读取仍纳入安全审计。
- progress.counts.companyIntelligence 统计有效维护批次，不代表真实性或质量：同一顾问/公司/人物或部门，从首次有效修改起 24 小时合并计 1 条，保存不延长窗口；过窗口的新修改另计，不同顾问分别计。人物按 personId 跨任职/版本去重，一次调动不算两条。按批次首次修改时间筛选 period；删除撤销对应贡献，恢复仅恢复原计数/时间，结束任职保留历史。浏览、重复保存、纯排版、复制/发布不计。团队/公司汇总成员贡献；仅返回数字，无情报正文。
- jobState/jobAssign 还需 jobs:write；candidateAssign/candidatePolicy 还需 candidates:write。明确任务需要哪些权限，再申请对应 :read/:write。
- flows/detail 的 stageEnteredAt、stageAgeDays 为当前看板阶段的进入时间和北京时间自然日停留天数，进入当天为 0。只依据真实创建/阶段变更及旧平台原始事件，不用最近更新时间或迁移时间推算。缺失可信历史时为 null；备注、转交、暂停恢复及 Offer 发出到接受不重置，退出后再次进入该列重新计时。只新增标量字段，不返回原始历史或私密备注。
- 推荐审批由岗位客户经理人工处理；系统同时抄送推荐顾问的直属部门负责人和 CEO，重复人员去重。团队负责人本人发起时可以抄送本人，公司根部门成员按 CEO 为直属负责人处理；Agent 不指定审批人或抄送人。组织或身份不完整时报错，不要猜测人员。
- 终止与 Offer 审批：先读 detail 和 version。transition({id,version,state:"closed",outcome,reason,requestId}) 的非 hired 结束结果会提交终止审批；transition({id,version,stage:"offer_sent",offerDate:"YYYY-MM-DD",confirmPlacement:true,reason,requestId}) 提交 Offer 审批。不要提交 serviceFee：由岗位客户经理在飞书审批中填写。outcome=approval_pending 只是已受理，不代表状态已改变；detail.decisions 查询结果，岗位客户经理本人也需飞书人工审批。推荐、终止和 Offer 均抄送发起顾问的直属部门长、CEO 和指定抄送人。审批通过且服务费有效才进入 Offer，自动向配置的全员群广播岗位与服务费，不含姓名、简历或联系方式。先完成 Offer 审批才能进入 offer_accepted/joined。审批中流程阶段锁定，撤回须在飞书操作并等状态同步；最终结果 review 可由发起人/管理员使用 discardDecision({id,version,kind,approvalId,reason,requestId}) 放弃，不是代批。jobState 不能批量结束流程或撤销 Offer，可先暂停岗位，再逐条走终止审批。历史已进入 Offer 的记录不补审。
- detail.offerBroadcasts 显示 Offer 群广播状态；发送结果不确定会进入 review，不自动重复发送。仅申请人/管理员先亲自核对全员群后，才可调用 resolveOfferBroadcast({id,version,approvalId,resolution:"delivered"|"not_delivered_retry",reason,requestId})；不得猜测或未经核对重试。

以下仅为虚构模板；ID、version 必须从真实读取结果取得，写入 requestId 为每个逻辑动作生成且重试保持不变。

### 确认当前身份和成员 ID（只读）

POST /api/v1/hiring/tools

```json
{
  "operation": "read",
  "input": {
    "resource": "context",
    "input": {}
  }
}
```

### 查询招聘流程（只读）

POST /api/v1/hiring/tools

```json
{
  "operation": "read",
  "input": {
    "resource": "flows",
    "input": {}
  }
}
```

### 读取自己的工作台（只读）

POST /api/v1/hiring/tools

```json
{
  "operation": "read",
  "input": {
    "resource": "dashboard",
    "input": {}
  }
}
```

### 预览岗位状态变更的影响（只读）

POST /api/v1/hiring/tools

```json
{
  "operation": "read",
  "input": {
    "resource": "jobImpact",
    "input": {
      "jobId": 1
    }
  }
}
```

### 把候选人关联到岗位（写入示例）

POST /api/v1/hiring/tools

```json
{
  "operation": "create",
  "input": {
    "candidateId": "1",
    "jobId": 1,
    "requestId": "example-flow-001"
  }
}
```
