@lichidental/saas-cli 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md ADDED
@@ -0,0 +1,246 @@
1
+ # saas-cli — 使用说明(面向 AI 与人)
2
+
3
+ > **目标读者:AI 助手** — 供后端同学使用的 AI 工具(或任何 AI 编程助手)直接读取,按本文件的规则与契约使用本 CLI。
4
+ > 人类使用者请看 `docs/CLI使用手册.md`;本文件也包含完整的技术参考信息。
5
+
6
+ 励齿口腔 SaaS 数据操作命令行工具。当前提供 10 个命令:登录管理(auth login/logout/status 3 个)、预约相关 5 个(查询医生/患者/预约项目、创建预约、回查预约)、配置兼容 2 个(config set-token/show)。
7
+
8
+ **文档可信度约定**:本工程所有命令与示例均已在 test 环境实测跑通(2026-10-12);接口参数与响应字段以 `docs/api/openapi.yaml` 和 `docs/samples/`(实测真实请求/响应)为准。
9
+
10
+ ---
11
+
12
+ ## 前置条件
13
+
14
+ 1. Node.js >= 22.12(commander 15 的要求)
15
+ 2. 安装:`npm install -g @lichidental/saas-cli`(发布版;仓库开发模式在根目录用 `node cli.mjs ...`;仅 1 个第三方包 commander)
16
+ 3. 登录 / 配置 token(三种来源,优先级从高到低):
17
+ - 命令加 `--token <token>`(临时覆盖)
18
+ - 环境变量 `SAAS_TOKEN`
19
+ - 配置文件:`saas-cli auth login --env test --token <token>`(推荐:先验证再保存;兼容旧方式 `saas-cli config set-token --env test --token <token>`,不验证直接保存)
20
+
21
+ ## 硬规则(必须遵守,防止出错)
22
+
23
+ 1. **不要推测参数或字段**:本文件与 `docs/api/openapi.yaml` 未列出的请求参数一律不存在;不要凭常识"补全"字段名。
24
+ 2. **时间格式必须是 `YYYY-MM-DD HH:mm` 或 `YYYY-MM-DD HH:mm:ss`**(本机本地时区,24 小时制)。不要传 ISO 格式(带 T)、不要传时间戳。省略 `--time` 表示当前时间。
25
+ 3. **`reservation create` 非幂等(重复执行会重复创建,不会自动去重)**:每执行一次都会新建一条预约,重复执行会产生多条。失败重试前,必须先用 `reservation list` 回查确认上一次是否已经成功。
26
+ 4. **`--sms` 会给真实患者手机发送短信**。默认不发送;只有用户明确要求发短信时才加 `--sms`。
27
+ 5. **prod 环境写操作必须加 `--yes`**,否则工具会在发出任何请求前直接拒绝(退出码 2)。建议先用 `--dry-run` 预览。
28
+ 6. **全部命令零交互**:不会提问、不会等待确认,不要等待输入。唯一例外是面向人的 `auth login`(无 `--token` 时打开授权页并打印指引后**立即退出、不阻塞等待**);AI/脚本请始终用 `saas-cli auth login --token <token>`(或 `--token -` 从标准输入读取)。
29
+ 7. **机器解析请用 `--json`**:stdout 在 `--json` 模式下是单个 JSON 对象;默认(人读)模式 stdout 是摘要文本。stderr 是日志,不要解析。
30
+
31
+ ## 命令清单
32
+
33
+ | 命令 | 用途 |
34
+ | ---------------------- | -------------------------------- |
35
+ | `auth login` | 登录:验证 token 并保存;不带 --token 时打开授权页(不阻塞) |
36
+ | `auth logout` | 退出登录:删除本机保存的 token |
37
+ | `auth status` | 查看登录状态(--verify 联网验证) |
38
+ | `reservation create` | 新建预约(主命令) |
39
+ | `reservation list` | 查询预约(按患者 + 日期) |
40
+ | `reservation doctors` | 医生列表(获取 doctorId) |
41
+ | `reservation patients` | 搜索患者(获取 customCode) |
42
+ | `reservation items` | 预约项目列表(获取项目 id / 名称) |
43
+ | `config set-token` | 保存 token 到配置文件 |
44
+ | `config show` | 查看当前配置(token 打码) |
45
+
46
+ 所有命令支持通用参数:`--env test|pre|prod`(默认 test)、`--token`、`--json`、`--verbose`。运行 `saas-cli <主题> <命令> --help` 查看用法。
47
+
48
+ ---
49
+
50
+ ## reservation create — 新建预约
51
+
52
+ | 参数 | 说明 |
53
+ | -------------------------- | --------------------------------------------------------------------------------------------------------------- |
54
+ | `--patient <姓名/手机号/病历号>` | 自动搜索患者;命中多条时列出候选并报错(退出码 2),不会猜 |
55
+ | `--patient-code <编码或病历号>` | 直接指定患者编码或病历号(与 `--patient` 二选一;先搜索识别,识别不到则按编码直用) |
56
+ | `--doctor <姓名>` | 自动匹配医生;命中多条时列出候选并报错 |
57
+ | `--doctor-id <userUid>` | 直接指定医生 userUid(与 `--doctor` 二选一) |
58
+ | `--time <时间>` | 预约开始时间(格式见硬规则 2);省略 = 当前时间 |
59
+ | `--duration <分钟>` | 持续时长,15~480 整数,默认 30 |
60
+ | `--items <名称列表>` | 预约项目名称,逗号分隔(中英文逗号均可),如 `"洗牙,补牙"` |
61
+ | `--items-json <json>` | 直接指定项目 JSON 数组(跳过名称匹配),元素格式:`{"up_id":84048,"up_title":"口腔","id":84050,"title":"口腔检查"}` |
62
+ | `--first <1\|0>` | 就诊类型:1=初诊,0=复诊;省略则按患者历史自动识别 |
63
+ | `--remark <备注>` | 预约备注(可为空) |
64
+ | `--dry-run` | 只打印将要提交的请求体,不实际创建 |
65
+ | `--sms` | 创建成功后给患者发送预约短信(默认不发送) |
66
+ | `--yes` | prod 环境写操作必须显式添加 |
67
+
68
+ 实测示例:
69
+
70
+ ```bash
71
+ saas-cli reservation create --env test --patient 张三 --doctor 李医生 --time "2026-11-05 10:00" --duration 30 --items "补牙"
72
+ ```
73
+
74
+ 成功时输出(人读模式):
75
+
76
+ ```
77
+ ✓ 预约创建成功(环境:test)
78
+ 患者 张三(YONGCE26073100001)
79
+ 医生 李医生(userUid:26051131994)
80
+ 时间 2026-11-02 09:00:00 起 15 分钟
81
+ 就诊类型 复诊
82
+ 项目 口腔检查
83
+ 短信 未发送(如需发送加 --sms)
84
+ 预约ID 1260618
85
+ ```
86
+
87
+ `--json` 模式的 `data` 结构:
88
+
89
+ ```json
90
+ {
91
+ "requestBody": { "...实际提交的请求体(见开放式接口文档 addCustomMark)..." },
92
+ "response": { "status": 200, "code": "SUCCESS", "message": "处理成功", "data": { "id": 1260617, "updateNum": 1 } },
93
+ "createdId": 1260617,
94
+ "smsSent": false,
95
+ "smsError": null,
96
+ "reservation": { "...回查到的预约详情(字段见 openapi.yaml 的 CustomMark);回查失败时为 null..." }
97
+ }
98
+ ```
99
+
100
+ `--dry-run` 时 `data` 为 `{ "dryRun": true, "requestBody": {...} }`。
101
+
102
+ ## reservation list — 查询预约
103
+
104
+ | 参数 | 说明 |
105
+ | ------------------------------ | ------------------ |
106
+ | `--patient` / `--patient-code` | 指定患者(必填其一;`--patient` 支持姓名/手机号/病历号,`--patient-code` 支持编码/病历号) |
107
+ | `--date <YYYY-MM-DD>` | 查询日期,默认今天 |
108
+
109
+ ```bash
110
+ saas-cli reservation list --patient 张三 --date 2026-10-15
111
+ # 张三(YONGCE26073100001)在 2026-10-15 的预约(共 1 条):
112
+ # 1. 10:00~10:30(30分钟) 医生:李医生 状态:已预约 id:1260615
113
+ ```
114
+
115
+ `--json` 的 `data`: `{ date, patient: {customCode, customName}, reservations: [CustomMark...] }`
116
+
117
+ ## reservation doctors — 医生列表
118
+
119
+ 无参数。`--json` 的 `data`: `{ doctors: [{userUid, storeUserName, ...}] }`(实测 19 位)。
120
+
121
+ ## reservation patients — 搜索患者
122
+
123
+ | 参数 | 说明 |
124
+ | -------------------- | ---------------------------- |
125
+ | `--keyword <关键词>` | 必填。姓名 / 手机号 / 病历号 |
126
+
127
+ `--json` 的 `data`: `{ keyword, total, patients: [{customCode, customName, customPhone, ...}] }`
128
+
129
+ ## reservation items — 预约项目列表
130
+
131
+ | 参数 | 说明 |
132
+ | -------------------- | --------------- |
133
+ | `--keyword <关键词>` | 可选,按名称过滤 |
134
+
135
+ `--json` 的 `data`: `{ keyword, items: [{id, treatItemName, upTreatItemId, level, ...}] }`(level=1 分类,level=2 项目)
136
+
137
+ ## auth login / logout / status(推荐)与 config(兼容)
138
+
139
+ ```bash
140
+ saas-cli auth login --env test --token <token> # 登录:验证后保存(推荐)
141
+ saas-cli auth login # 无 --token:打开授权页并打印指引(不阻塞、不等待输入)
142
+ saas-cli auth login --token - # 从标准输入读取 token(管道/剪贴板场景)
143
+ saas-cli auth status --verify # 查看登录状态,并联网验证 token 有效性
144
+ saas-cli auth logout --env test # 退出:删除本机保存的该环境 token
145
+ saas-cli config set-token --env test --token <token> # 兼容旧用法:不验证直接保存
146
+ saas-cli config show # 查看配置(token 打码)
147
+ ```
148
+
149
+ 配置文件位置:Windows `%APPDATA%\saas-cli\config.json`;macOS/Linux `~/.config/saas-cli/config.json`。
150
+ 旧版本使用的 `create-saas-cli` 目录会被自动兼容读取,并在下次写入时迁移到新路径,无需手工处理。
151
+
152
+ ---
153
+
154
+ ## 输出契约(稳定,仅追加不破坏)
155
+
156
+ 成功:
157
+
158
+ ```json
159
+ { "ok": true, "env": "test", "command": "reservation create", "data": {} }
160
+ ```
161
+
162
+ 失败:
163
+
164
+ ```json
165
+ {
166
+ "ok": false,
167
+ "env": "test",
168
+ "command": "reservation create",
169
+ "error": {
170
+ "code": 401,
171
+ "message": "认证失败:登录失效,请重新登录!",
172
+ "hint": "token 已失效,请重新登录:saas-cli auth login --env <环境> --token <新token>"
173
+ }
174
+ }
175
+ ```
176
+
177
+ ## 退出码
178
+
179
+ | 码 | 含义 |
180
+ | --- | ---------------------------------------------------- |
181
+ | 0 | 成功 |
182
+ | 1 | 业务失败(接口 status≠200 且非 401/403) |
183
+ | 2 | 用法错误(参数缺失/格式错误/重名歧义/prod 未加 --yes) |
184
+ | 3 | 认证失败(token 失效、无权限) |
185
+ | 4 | 网络错误(超时、连接失败、响应非 JSON) |
186
+
187
+ ## 错误码与处理建议
188
+
189
+ | error.code | 场景 | 处理 |
190
+ | ------------------ | ------------ | ------------------------------------------------------------------------------- |
191
+ | 401 | token 失效 | 请用户提供新 token 后 `saas-cli auth login --token <新token>` 重新登录;不要自行重试 |
192
+ | 403 | 无权限 | 当前账号无该操作权限,联系管理员 |
193
+ | 999 | 版本校验失败 | 工具会自动读取目标环境版本号;持续出现时检查本机能否访问 `<环境地址>/index.html` |
194
+ | 400 | 业务参数错误 | `message` 为后端原文,按提示修正参数 |
195
+ | 网络错误(退出码 4) | 超时/断网 | 查询类命令可重试;写操作重试前先 `reservation list` 回查 |
196
+
197
+ ---
198
+
199
+ ## 任务 recipe
200
+
201
+ ### 0. 首次登录(标准流程)
202
+
203
+ 1. 检查登录状态:`saas-cli auth status --json`(看 `data.configured`)
204
+ 2. 未登录:向用户获取 token(引导用户运行 `saas-cli auth login` 打开授权页复制),然后执行:
205
+ `saas-cli auth login --env <环境> --token <token> --json`
206
+ 预期:`ok:true` 且 `data.verified:true`
207
+ 3. token 无效(退出码 3):请用户重新获取,不要用同一 token 重试
208
+
209
+ ### 1. 给某患者创建预约(标准流程)
210
+
211
+ 1. `saas-cli reservation patients --keyword <姓名或手机号> --json` → 取 `customCode`
212
+ - 返回多条(重名)时:向用户确认是哪一条,不要自行选择
213
+ 2. `saas-cli reservation doctors --json` → 取医生 `userUid`(按姓名匹配;重名同上)
214
+ 3. (可选)不确定项目名称时:`saas-cli reservation items --keyword <关键词> --json`
215
+ 4. 先预览:`... reservation create --patient-code <code> --doctor-id <uid> --time "2026-11-05 10:00" --items "补牙" --dry-run --json`
216
+ 5. 去掉 `--dry-run` 执行,记录返回的 `createdId`
217
+ 6. 回查确认:`saas-cli reservation list --patient-code <code> --date <YYYY-MM-DD> --json`
218
+
219
+ ### 2. 回查某患者某天的预约
220
+
221
+ `saas-cli reservation list --patient 张三 --date 2026-10-15 --json`
222
+
223
+ ---
224
+
225
+ ## 已知限制(如实声明)
226
+
227
+ - **按患者查询预约请用 `customMarkListByCustom`**(前端患者详情「就诊记录」同款;`startTime`/`endTime` 服务端过滤生效,返回 `data.cmList` 已按 markStartTime 降序;`reservation list` 与创建后回查都用它)。状态显示以 `visitStatus` 为主键(规则见 docs/api/接口说明.md 第 6 节表)。
228
+ - **发短信接口未经实测**:`sendCustomMarkSms`(对应 `--sms`)为避免给真实号码发短信,未做实测;接口参数与前端调用一致(见 openapi.yaml),首次使用时请人工确认效果。
229
+ - **与前端的两处已知行为差异**(照前端逻辑核对后的如实说明):① 前端新建预约时会把默认时间钳制到诊所营业时段内(CLI 不做——CLI 无登录态拿不到营业时间配置;不传 `--time` 时直接使用当前时间,需要落在营业时段请显式传 `--time`);② 前端有"新建预约"按钮权限控制(依赖登录返回的 authBtnList,CLI 无法获取),CLI 直接用 token 调接口,后端是否兜底校验权限未验证。
230
+ - **999 版本校验错误未实测**:未见真实触发;工具已内置动态读取版本号的规避逻辑。
231
+ - 接口规范是从前端调用行为 + 实测响应逆向整理的(test 环境,2026-10-12 校准);如与后端实现不一致,以后端为准。
232
+ - Windows Git Bash 下用 `curl` 内联 `-d` 传中文会乱码(终端编码问题,与本工具无关);用本工具或 `-d @utf8文件.json` 无此问题。
233
+ - 接口响应里的时间字段是 ISO 格式(如 `2026-11-01T11:00:00`),CLI 的输出已转为 `2026-11-01 11:00:00`。
234
+
235
+ ## 文档索引
236
+
237
+ | 文档 | 面向 | 内容 |
238
+ | ----------------------- | ------- | ------------------------------------ |
239
+ | `docs/前端配套-CLI授权页.md` | 前端 | 授权页需求 + 后端设备码接口草案 |
240
+ | `README.md` | 人 | 安装与快速开始 |
241
+ | `docs/CLI使用手册.md` | 人 | 详细上手、示例集、常见问题 |
242
+ | `AGENTS.md`(本文件) | AI | 命令契约、输出契约、任务流程、硬规则 |
243
+ | `docs/api/openapi.yaml` | AI/工具 | 接口权威规范(OpenAPI 3.0) |
244
+ | `docs/api/接口说明.md` | 人 | 字段中文含义 + curl 示例 |
245
+ | `docs/samples/` | AI/人 | 实测真实请求/响应样本 |
246
+ | `docs/开发指南.md` | 开发者 | 新增命令的模板与规范 |
package/README.md CHANGED
@@ -1,3 +1,93 @@
1
- # Temporary Holding Version
1
+ # saas-cli
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ > **目标读者:所有人** — 第一次接触本工具从这里开始(安装、登录、试跑、文档地图)。不需要编程经验;更细的使用说明见 `docs/CLI使用手册.md`。
4
+
5
+ 励齿口腔 SaaS 数据操作命令行工具:新建预约、查询医生 / 患者 / 预约项目。
6
+
7
+ ## 快速开始
8
+
9
+ > 下面出现的名词(npm、token、命令行……)如果不认识,先看本文末尾的 [名词表](#名词表看不懂的词查这里)。
10
+
11
+ 前置:Node.js 22.12 或更高版本
12
+
13
+ ```bash
14
+ # 1. 安装(二选一)
15
+ npm install -g @lichidental/saas-cli # 全局安装:装完在任何目录直接敲 saas-cli
16
+ npx @lichidental/saas-cli reservation doctors # 或者不安装,用 npx 直接跑(适合先试试)
17
+
18
+ # 2. 登录(只需一次)
19
+ saas-cli auth login # 打开授权页,页面上点「复制 token」
20
+ saas-cli auth login --token <粘贴的token> # 粘贴后回车:验证并保存到本机
21
+
22
+ # 3. 试跑:列出医生(顺便验证登录是否成功)
23
+ saas-cli reservation doctors
24
+ ```
25
+
26
+ 登录信息保存在本机,之后所有命令自动使用。常用登录管理:
27
+
28
+ ```bash
29
+ saas-cli auth status # 查看登录状态(加 --verify 可联网验证 token 是否有效)
30
+ saas-cli auth logout # 退出登录(删除本机凭证)
31
+ ```
32
+
33
+ 创建一条预约:
34
+
35
+ ```bash
36
+ saas-cli reservation create --patient 张三 --doctor 李医生 --time "2026-11-05 10:00" --duration 30 --items "补牙"
37
+ ```
38
+
39
+ **token 从哪来**:运行 `saas-cli auth login` 会打开授权页(需先在浏览器登录 SaaS 系统),页面上有「复制 token」按钮;或向后端同学要一个。
40
+ (还没有授权页之前,也可以手工取:浏览器登录 SaaS 后按 F12 → Application → Local Storage → `userInfo` → 复制 `token` 字段。)
41
+
42
+ ## 命令一览
43
+
44
+ | 命令 | 用途 |
45
+ | ----------------------------------------- | -------------------------------------- |
46
+ | `auth login` / `logout` / `status` | 登录 / 退出 / 查看登录状态 |
47
+ | `reservation create` | 新建预约(传姓名自动查患者/医生/项目) |
48
+ | `reservation list` | 查询预约(按患者 + 日期) |
49
+ | `reservation doctors` | 医生列表 |
50
+ | `reservation patients --keyword <关键词>` | 搜索患者 |
51
+ | `reservation items [--keyword]` | 预约项目列表 |
52
+ | `config set-token` / `config show` | 配置 token / 查看配置(兼容旧用法,推荐 auth) |
53
+
54
+ 通用参数:`--env test|pre|prod`(默认 test)、`--token`、`--json`(输出结构化 JSON)、`--verbose`(打印请求详情)。
55
+
56
+ ## 文档
57
+
58
+ | 文档 | 目标读者 | 内容 |
59
+ | ----------------------- | --------------- | ------------------------------------ |
60
+ | `docs/CLI使用手册.md` | 使用者(人) | 上手步骤、命令示例、常见问题排查 |
61
+ | `AGENTS.md` | AI | 命令契约、输出契约、错误码、任务流程 |
62
+ | `docs/api/openapi.yaml` | AI / 程序 | 接口权威规范(机器可读) |
63
+ | `docs/api/接口说明.md` | 后端工程师(人) | 接口字段中文含义 + 可直接复制的 curl |
64
+ | `docs/开发指南.md` | 开发者(人) | 新增命令的模板与规范 |
65
+ | `docs/前端配套-CLI授权页.md` | 前端工程师 | 登录授权页的需求说明与后端接口草案 |
66
+
67
+ ## 注意事项
68
+
69
+ - `reservation create` 是**真实写操作**,会创建预约数据;生产环境(prod)必须加 `--yes` 才执行,建议先用 `--dry-run` 预览。
70
+ - `--sms` 会**真实给患者发短信**,默认不发送。
71
+ - 医生 / 患者重名时工具不会猜,会列出候选要求指定。
72
+ - 工具启动时会自动检查是否有新版本(每天最多检查一次,失败静默);用 `SAAS_CLI_NO_UPDATE_CHECK=1` 可关闭提醒。
73
+
74
+ ## 名词表(看不懂的词查这里)
75
+
76
+ | 名词 | 通俗解释 |
77
+ | ----------------------- | ------------------------------------------------------------------------------------------------------------------------- |
78
+ | 命令行 / 终端 | 一个"敲命令"的窗口(不是鼠标点击的界面)。Windows 里可用 CMD、PowerShell 或 Git Bash,输入命令后按回车执行 |
79
+ | CLI | 命令行工具的统称:不打开网页,敲命令就能用。本工具就是一套 CLI |
80
+ | Node.js | 跑本工具需要的运行环境(相当于"播放器",本工具是"文件"),官网 nodejs.org 下载安装,版本要 22.12 以上 |
81
+ | npm / npx | Node.js 自带的工具:npm 负责"装东西",npx 负责"临时跑一个工具"(不用先安装) |
82
+ | `npm install -g` | 全局安装:装一次,之后在任何目录都能直接用命令名(如 `saas-cli`) |
83
+ | 授权页 / `auth login` | 登录用的:跑 `saas-cli auth login` 会自动打开一个网页,上面有「复制 token」按钮;复制回来再跑一条命令就完成登录 |
84
+ | token | 登录凭据,一串字符(像一张"电子通行证"),接口靠它确认你是谁、属于哪家诊所;会过期,过期了就重新登录 |
85
+ | 环境变量 SAAS_TOKEN | 一种让工具读到 token 的系统设置方式(不落盘的临时方案);怎么设见《使用手册》第二节;不设也没关系,`auth login` 更省事 |
86
+ | `--` 开头的参数 | 写在命令后面的可选项,用来控制行为,例如 `--dry-run` 只预览不创建、`--json` 输出给程序读的格式 |
87
+ | 环境(test / pre / prod) | test=测试环境(专供造数据,随便试)、pre=预发环境(上线前演练)、prod=生产环境(真实在用的系统,务必谨慎) |
88
+ | 接口 / API | 系统之间互相传数据的"窗口"。创建一条预约,就是通过"新建预约接口"完成的 |
89
+ | JSON | 一种格式固定的文本,样子像 `{"name":"张三"}`;给程序读的标准格式,人也能看懂 |
90
+ | curl | 系统自带的小工具:一行命令就能调用接口(不用打开网页),给技术同学手工测试用 |
91
+ | openapi.yaml | 接口的"机器版说明书"(格式固定,AI 和工具能直接读);给人看的中文版在 `docs/api/接口说明.md` |
92
+ | AGENTS.md | 专门写给 AI 助手看的说明书;人不看也没关系 |
93
+ | 退出码 | 命令跑完后返回的数字:0=成功,非 0=失败。写自动化脚本时用得到,手工使用不用管 |
@@ -0,0 +1,52 @@
1
+ // api/custom-mark.mjs — 预约相关接口(与前端 src/api/reservation.ts、disposalRecord.ts 对应)
2
+ // 本文件只封装 CLI 实际使用的接口;其余预约域接口(看板查询、按患者查全部记录等)见 docs/api/openapi.yaml
3
+
4
+ /**
5
+ * 新建预约(前端:新建预约弹窗的提交接口)
6
+ * @param {import("../lib/types.mjs").Http} http
7
+ * @param {{customCode: string, doctorId: string, markStartTime: string, markTimeLength: number,
8
+ * treatItemJson: string, definitionCreateTime?: string, boolFirst?: string, remark?: string}} params
9
+ * markStartTime 格式 "YYYY-MM-DD HH:mm:ss";boolFirst: "1"=初诊 / "0"=复诊
10
+ * @returns {Promise<import("../lib/types.mjs").ApiResponse>} data: { id, updateNum }(id 即新建预约的 id)
11
+ */
12
+ export function addCustomMark(http, params) {
13
+ return http.post("/user/saas/custom-mark/addCustomMark", params)
14
+ }
15
+
16
+ /**
17
+ * 按患者 + 时间范围查询预约记录(照前端:患者详情"就诊记录"与卡片"下次预约"tab 使用此接口)
18
+ * 实测:startTime/endTime 服务端过滤生效;返回已按 markStartTime 降序排序;data 为 { cmList: [...] }
19
+ * @param {import("../lib/types.mjs").Http} http
20
+ * @param {{customCode: string, startTime?: string, endTime?: string, pageSize?: number, time?: string}} params
21
+ * startTime/endTime 格式 "YYYY-MM-DD HH:mm:ss";time 为分页游标(取上一页最后一条的 markStartTime)
22
+ * @returns {Promise<import("../lib/types.mjs").ApiResponse>} data: { cmList: CustomMark[] }
23
+ */
24
+ export function listMarksByRange(http, { customCode, startTime, endTime, pageSize = 999, time }) {
25
+ return http.post("/user/saas/custom-mark/customMarkListByCustom", {
26
+ customCode,
27
+ ...(startTime ? { startTime } : {}),
28
+ ...(endTime ? { endTime } : {}),
29
+ pageSize,
30
+ ...(time ? { time } : {}),
31
+ })
32
+ }
33
+
34
+ /**
35
+ * 由患者的历史就诊/预约记录识别本次就诊类型(照前端:选中患者后自动识别初诊/复诊)
36
+ * @param {import("../lib/types.mjs").Http} http
37
+ * @param {{customCode: string}} params
38
+ * @returns {Promise<import("../lib/types.mjs").ApiResponse>} data: { visitType, ... } visitType: 1=初诊 / 0=复诊
39
+ */
40
+ export function getVisitTypeByCustom(http, { customCode }) {
41
+ return http.post("/user/saas/custom-mark/customMarkInfoByCustom", { customCode })
42
+ }
43
+
44
+ /**
45
+ * 发送预约短信给患者(照前端:创建成功后勾选发送时调用)
46
+ * @param {import("../lib/types.mjs").Http} http
47
+ * @param {{customCode: string, markStartTime: string}} params markStartTime 格式 "YYYY-MM-DD HH:mm"
48
+ * @returns {Promise<import("../lib/types.mjs").ApiResponse>}
49
+ */
50
+ export function sendCustomMarkSms(http, params) {
51
+ return http.post("/user/saas/custom-mark/sendCustomMarkSms", params)
52
+ }
package/api/custom.mjs ADDED
@@ -0,0 +1,16 @@
1
+ // api/custom.mjs — 患者相关接口(与前端 src/api/reservation.ts 对应)
2
+
3
+ /**
4
+ * 搜索患者(姓名/手机号/病历号模糊查询)
5
+ * @param {import("../lib/types.mjs").Http} http
6
+ * @param {{keyword?: string, pageNum?: number, pageSize?: number}} params
7
+ * @returns {Promise<import("../lib/types.mjs").ApiResponse>} data: { list: Patient[], total, pageNum, pages }
8
+ */
9
+ export function searchPatients(http, { keyword, pageNum = 1, pageSize = 20 }) {
10
+ return http.post("/user/saas/custom/customParamPageList", {
11
+ pageNum,
12
+ paramText: keyword,
13
+ pageSize,
14
+ dateType: 3,
15
+ })
16
+ }
@@ -0,0 +1,19 @@
1
+ // api/employee.mjs — 员工(医生)相关接口(与前端 src/api/employee.ts 对应)
2
+
3
+ // EmploymentPostEnum.DOCTOR — 岗位:医生
4
+ export const POST_DOCTOR = 1
5
+ // EmploymentStatusEnum.ON_THE_JOB / PROBATION — 在职 / 试用
6
+ export const STATUS_ON_THE_JOB = 0
7
+ export const STATUS_PROBATION = 2
8
+
9
+ /**
10
+ * 医生列表(在职 + 试用,岗位=医生)
11
+ * @param {import("../lib/types.mjs").Http} http
12
+ * @returns {Promise<import("../lib/types.mjs").ApiResponse>} data: Doctor[]
13
+ */
14
+ export function listDoctors(http) {
15
+ return http.post("/user/saas/t-lichi-user-store/getStoreUserListByParam", {
16
+ delFlags: [STATUS_ON_THE_JOB, STATUS_PROBATION],
17
+ storeUserPostIds: [POST_DOCTOR],
18
+ })
19
+ }
@@ -0,0 +1,10 @@
1
+ // api/treat-item.mjs — 预约项目接口(与前端 src/api/reservation.ts 对应)
2
+
3
+ /**
4
+ * 预约项目列表(平铺数组:level=1 一级分类,level=2 二级项目)
5
+ * @param {import("../lib/types.mjs").Http} http
6
+ * @returns {Promise<import("../lib/types.mjs").ApiResponse>} data: TreatItem[]
7
+ */
8
+ export function listTreatItems(http) {
9
+ return http.post("/user/saas/treat-item/getTreatItemList")
10
+ }
package/cli.mjs ADDED
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/env node
2
+ // cli.mjs — saas-cli 入口
3
+ // 职责:扫描 commands/<主题>/<命令>.mjs 并注册;统一错误输出与退出码
4
+ // 约定:文件名以 _ 开头的是内部模块,不注册为命令
5
+ import fs from "node:fs"
6
+ import path from "node:path"
7
+ import { fileURLToPath, pathToFileURL } from "node:url"
8
+ import { Command, CommanderError } from "commander"
9
+ import { CliError, EXIT_CODES } from "./lib/errors.mjs"
10
+ import { checkForUpdate } from "./lib/update-check.mjs"
11
+
12
+ const __dirname = path.dirname(fileURLToPath(import.meta.url))
13
+
14
+ const TOPIC_DESCRIPTIONS = {
15
+ reservation: "预约相关操作(新建、查询)",
16
+ auth: "登录与凭据管理(login / logout / status)",
17
+ config: "CLI 配置管理(token 等;兼容旧用法,推荐 auth)",
18
+ }
19
+
20
+ // 版本号单一来源:从 package.json 读取,避免两处硬编码
21
+ const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, "package.json"), "utf8"))
22
+
23
+ // 版本更新提醒:同步读缓存提示 + 后台刷新缓存(不阻塞、失败静默;SAAS_CLI_NO_UPDATE_CHECK=1 关闭)
24
+ checkForUpdate(pkg.version)
25
+
26
+ const program = new Command()
27
+ program
28
+ .name("saas-cli")
29
+ .description("励齿 SaaS 数据操作 CLI")
30
+ .version(pkg.version)
31
+ .showHelpAfterError("(运行 saas-cli --help 查看全部命令)")
32
+ program.exitOverride()
33
+ // 静默 commander 自带的错误输出,统一走下面的 fail()(便于 --json 模式输出纯 JSON)
34
+ program.configureOutput({ writeErr: () => {} })
35
+
36
+ // 扫描注册命令
37
+ const commandsRoot = path.join(__dirname, "commands")
38
+ for (const topic of fs.readdirSync(commandsRoot).sort()) {
39
+ const topicDir = path.join(commandsRoot, topic)
40
+ if (!fs.statSync(topicDir).isDirectory()) continue
41
+ const topicCmd = program.command(topic).description(TOPIC_DESCRIPTIONS[topic] ?? topic)
42
+ for (const file of fs.readdirSync(topicDir).sort()) {
43
+ if (!file.endsWith(".mjs") || file.startsWith("_")) continue
44
+ const mod = await import(pathToFileURL(path.join(topicDir, file)).href)
45
+ if (typeof mod.register !== "function") continue
46
+ mod.register(topicCmd)
47
+ }
48
+ }
49
+
50
+ try {
51
+ await program.parseAsync(process.argv)
52
+ } catch (err) {
53
+ if (err instanceof CommanderError) {
54
+ // --help / --version 属正常退出
55
+ if (["commander.helpDisplayed", "commander.help", "commander.version", "commander.outputHelp"].includes(err.code)) {
56
+ process.exit(EXIT_CODES.OK)
57
+ }
58
+ fail(new CliError(err.message.replace(/^error:\s*/i, ""), { exitCode: EXIT_CODES.USAGE }))
59
+ }
60
+ fail(err)
61
+ }
62
+
63
+ /** 统一错误输出:人读模式给 "✗ 错误 + 提示";--json 模式给结构化 JSON。退出码见 lib/errors.mjs */
64
+ function fail(err) {
65
+ const wantsJson = process.argv.includes("--json")
66
+ const envIdx = process.argv.indexOf("--env")
67
+ const env = envIdx >= 0 ? process.argv[envIdx + 1] : "test"
68
+ const command = process.argv
69
+ .slice(2)
70
+ .filter((a) => !a.startsWith("-"))
71
+ .slice(0, 2)
72
+ .join(" ")
73
+ const exitCode = typeof err?.exitCode === "number" ? err.exitCode : EXIT_CODES.BUSINESS
74
+
75
+ if (wantsJson) {
76
+ console.log(
77
+ JSON.stringify(
78
+ { ok: false, env, command, error: { code: err?.code ?? "ERROR", message: err?.message ?? String(err), hint: err?.hint ?? null } },
79
+ null,
80
+ 2
81
+ )
82
+ )
83
+ } else {
84
+ console.error(`✗ ${err?.message ?? err}`)
85
+ if (err?.hint) console.error(` ${err.hint}`)
86
+ }
87
+ process.exit(exitCode)
88
+ }