abdulrehmankz1/clinic-management
GitHub: abdulrehmankz1/clinic-management
基于 Payload CMS 3、Next.js 16 和 MongoDB 构建的多租户诊所管理 SaaS,为小型诊所提供预约排班、患者登记和严格的数据隔离能力。
Stars: 1 | Forks: 0
# matab — 诊所管理
[](LICENSE)
[](https://github.com/abdulrehmankz1/clinic-management/stargazers)
[](https://github.com/abdulrehmankz1/clinic-management/network/members)
一个基于 **Payload CMS 3**、**Next.js 16**
和 **MongoDB** 构建的多租户**诊所管理**
系统。多家诊所共享一个部署,但每家诊所只能看到自己的
数据 —— 围绕着一条不可妥协的核心原则构建:**一家诊所永远无法查看或触碰另一家
诊所的数据。** v1 包含多租户核心:员工与角色、患者、
带有双重预订防护的预约、Walk-in token 以及仪表盘。

## 🎬 演示 — 所有者操作指南
以诊所**所有者**身份进行的一次完整的端到端操作,涵盖了日常的前台流程:
三种方式的预订(预约、Walk-in 以及在预订时内联添加全新患者)、查看
预订在日视图中的位置、注册患者、添加医生以及查看
诊所设置。
▶ 高清版:观看 MP4
**展示内容**
- **三种方式的预订** — 为现有患者安排的(在线)预约、获取
排队 token 的**Walk-in**,以及在预订时内联添加的全新患者。
- **预订位置** — 每个预订都会显示在每位医生的**日视图 (day view)** 时间线上。
- **患者** — 可搜索的目录 + 使用完整病历
(自动分配 MRN)注册新患者。
- **员工** — 添加具有专业、费用和每周空闲时间的新医生。
- **设置** — 诊所资料、工作时间、货币与时区。
## 痛点
小诊所 —— 一到几个医生,一名接待员 —— 仍然在依赖纸质预约
登记本、散落文件袋中的患者历史记录以及现金抽屉。现有的医院系统
昂贵、复杂,且需要 IT 人员维护。这里缺少一个廉价、简单、
接待员能在半小时内学会的多诊所工具。
## ✨ 功能 (v1)
- **多租户** — 一个应用,一个数据库,完全隔离的诊所(“租户”)。
- **身份验证与角色** — `superAdmin`(平台级),以及每家诊所的 `owner`、`doctor`、
`receptionist`,每个角色都有独立的访问权限。
- **患者** — 按姓名、电话或 MRN 注册/搜索;自动分配每家诊所的患者编号
(`P-0001`);过敏提示横幅;相同号码重复警告(家庭成员共享电话)。
- **预约** — 具有两种模式的日视图:**队列列表**(按时间排序,一键
*Check in / Complete* — 专为非技术前台员工设计)和每位医生的
**时间线**(空闲窗口、当前时间线、并排显示重叠时段的通道)。
状态流程(`scheduled → checked-in → completed / cancelled / no-show`)以及
一个会拒绝重叠时段的**双重预订防护**。
- **医生排班** — 每位医生可以是 `regular`(设定工作日 + 每日窗口,例如
周一/周三/周五 4-6 点)、`on-call`(任何时间)或 `by-appointment`(客座外科医生 — 在
自动查找器中隐藏)。在常规医生的窗口之外禁止预订,并且
**“这个时间哪些医生有空?”** 查找器为电话预约流程提供支持。
- **Walk-in** — 先到先得,每家诊所每天自动分配 **token 编号**(`T-01`,`T-02`…)
显示在 Day Rail 上。
- **仪表盘** — 今天的 KPI、14 天活动图表、即将到来的列表、值班医生、
快捷操作和最近注册的患者。
- **超级管理控制台** — 创建诊所(所有者原子化创建),暂停/重新激活。
- **UI** — 基于 Tailwind v4 + shadcn/ui 的青色“临床宁静”设计系统;深色侧边栏应用
外壳;带有一键演示登录的着陆页;对每个待处理操作提供 spinner 反馈。
## 🔐 多租户设计
租户隔离墙存在于**一个地方** — 访问控制函数(`src/access/`),该函数
返回一个 Mongo `where` 约束,Payload 将其合并到每个查询中,例如
`{ tenant: { equals: user.tenant } }`。它是**默认拒绝**的:任何无法
明确确定租户的请求都会返回 `false`。
- **设定它,而不是信任它** — 一个 `beforeChange` hook 会从
登录用户那里强制设定 `tenant`;无论客户端发送什么都会被覆盖。`tenant` 在创建后是不可变的。
- **客户端永远不决定范围** — tenant 始终在服务端派生。
- **自定义,而不是插件** — 一组约 150 行的访问辅助函数,而不是官方的
多租户插件,因此每一行都是可解释的,并且没有版本耦合。
- **通过测试证明** — `tests/int/isolation.int.spec.ts` 断言跨租户的
读取/写入会失败,并且植入的外部租户会被强制纠正。
## ⏱️ 双重预订防护
同一位医生的两个预约在以下情况下冲突:
`existing.start < new.end && existing.end > new.start`(边缘接触不冲突)。
检查运行**在请求的 MongoDB transaction 内部**,并且
在 `(tenant, doctor, start)` 上的**部分唯一索引**(仅限有效状态)是确定性的兜底方案,因此对
同一时段的两个同时预订不可能同时成功 — 其中一个会中止。竞态测试
(两个相同预订的 `Promise.all`)证明了只有一个会成功。
## 🌍 设计上的市场无关性
没有硬编码任何特定于地区的内容。每家诊所都有各自的**货币**和**时区**
作为设置(默认 `PKR` / `Asia/Karachi`)。所有资金流经
`formatMoney(amount, tenant)`,所有时间流经 `formatTime(date, tenant)`。巴基斯坦是
首发市场,而不是限制。
## 🧱 技术栈
| 层级 | 选择 |
|---|---|
| CMS / API / Auth | Payload CMS 3 |
| Web | Next.js 16 (App Router) + React 19 |
| DB | MongoDB (replica set — 用于 transaction) |
| 样式 | Tailwind CSS v4 + shadcn/ui (Base UI) + lucide-react |
| 测试 | Vitest (Payload Local API 集成测试) |
## 🚀 快速开始
```
pnpm install
cp .env.example .env # then fill PAYLOAD_SECRET
docker compose up -d # MongoDB as a single-node replica set (rs0)
pnpm seed # 3 demo clinics, staff, patients, appointments
pnpm dev # http://localhost:3000
```
### 演示登录(密码:`password123`)
| 角色 | 邮箱 |
|---|---|
| Super Admin | `super@clinic.app` |
| Owner — City Care | `owner@city.app` |
| Receptionist — City Care | `reception@city.app` |
| Doctor — City Care | `doctor1@city.app` |
| Owner — Shifa | `owner@shifa.app` |
先以 City Care 登录,然后以 Shifa 登录 — 数据完全不同。这就是租户隔离墙。
## ✅ 测试
```
pnpm test # 34 integration tests: isolation, booking guard, availability, walk-in tokens
```
## 📁 项目结构
```
src/
access/ # the tenant wall — isSuperAdmin, tenantScoped, field-level rules
app/(frontend)/ # landing, login, dashboard/* (appointments, patients, staff, settings), super
app/(payload)/ # admin panel & REST/GraphQL API (super admin only)
collections/ # Tenants, Users, Patients, Appointments
components/ # DayRail (List + Timeline), BookingForm, StaffManager, SuperConsole, Sidebar, ui/
hooks/ # forceTenant (set-it-don't-trust-it)
lib/ # booking.ts, availability.ts, reports.ts, format.ts, constants.ts
seed.ts
payload.config.ts
public/images/ # landing/login photography (Unsplash) + product shot
tests/int/ # isolation, booking, overlap, availability, walk-in suites
```
## 🗺️ 路线图
v1(当前版本)是多租户基础。后续版本(临床就诊与处方、
计费、自助注册)已规划,但在目前有意排除在范围之外。
## 📜 许可证
该项目基于 **MIT License** 授权 — 详情请参阅 [LICENSE](LICENSE) 文件。
您可以将其用于商业和私人用途、修改并分发它;只需保留
版权和许可证声明完整即可。
▶ 高清版:观看 MP4
**展示内容**
- **三种方式的预订** — 为现有患者安排的(在线)预约、获取
排队 token 的**Walk-in**,以及在预订时内联添加的全新患者。
- **预订位置** — 每个预订都会显示在每位医生的**日视图 (day view)** 时间线上。
- **患者** — 可搜索的目录 + 使用完整病历
(自动分配 MRN)注册新患者。
- **员工** — 添加具有专业、费用和每周空闲时间的新医生。
- **设置** — 诊所资料、工作时间、货币与时区。
## 痛点
小诊所 —— 一到几个医生,一名接待员 —— 仍然在依赖纸质预约
登记本、散落文件袋中的患者历史记录以及现金抽屉。现有的医院系统
昂贵、复杂,且需要 IT 人员维护。这里缺少一个廉价、简单、
接待员能在半小时内学会的多诊所工具。
## ✨ 功能 (v1)
- **多租户** — 一个应用,一个数据库,完全隔离的诊所(“租户”)。
- **身份验证与角色** — `superAdmin`(平台级),以及每家诊所的 `owner`、`doctor`、
`receptionist`,每个角色都有独立的访问权限。
- **患者** — 按姓名、电话或 MRN 注册/搜索;自动分配每家诊所的患者编号
(`P-0001`);过敏提示横幅;相同号码重复警告(家庭成员共享电话)。
- **预约** — 具有两种模式的日视图:**队列列表**(按时间排序,一键
*Check in / Complete* — 专为非技术前台员工设计)和每位医生的
**时间线**(空闲窗口、当前时间线、并排显示重叠时段的通道)。
状态流程(`scheduled → checked-in → completed / cancelled / no-show`)以及
一个会拒绝重叠时段的**双重预订防护**。
- **医生排班** — 每位医生可以是 `regular`(设定工作日 + 每日窗口,例如
周一/周三/周五 4-6 点)、`on-call`(任何时间)或 `by-appointment`(客座外科医生 — 在
自动查找器中隐藏)。在常规医生的窗口之外禁止预订,并且
**“这个时间哪些医生有空?”** 查找器为电话预约流程提供支持。
- **Walk-in** — 先到先得,每家诊所每天自动分配 **token 编号**(`T-01`,`T-02`…)
显示在 Day Rail 上。
- **仪表盘** — 今天的 KPI、14 天活动图表、即将到来的列表、值班医生、
快捷操作和最近注册的患者。
- **超级管理控制台** — 创建诊所(所有者原子化创建),暂停/重新激活。
- **UI** — 基于 Tailwind v4 + shadcn/ui 的青色“临床宁静”设计系统;深色侧边栏应用
外壳;带有一键演示登录的着陆页;对每个待处理操作提供 spinner 反馈。
## 🔐 多租户设计
租户隔离墙存在于**一个地方** — 访问控制函数(`src/access/`),该函数
返回一个 Mongo `where` 约束,Payload 将其合并到每个查询中,例如
`{ tenant: { equals: user.tenant } }`。它是**默认拒绝**的:任何无法
明确确定租户的请求都会返回 `false`。
- **设定它,而不是信任它** — 一个 `beforeChange` hook 会从
登录用户那里强制设定 `tenant`;无论客户端发送什么都会被覆盖。`tenant` 在创建后是不可变的。
- **客户端永远不决定范围** — tenant 始终在服务端派生。
- **自定义,而不是插件** — 一组约 150 行的访问辅助函数,而不是官方的
多租户插件,因此每一行都是可解释的,并且没有版本耦合。
- **通过测试证明** — `tests/int/isolation.int.spec.ts` 断言跨租户的
读取/写入会失败,并且植入的外部租户会被强制纠正。
## ⏱️ 双重预订防护
同一位医生的两个预约在以下情况下冲突:
`existing.start < new.end && existing.end > new.start`(边缘接触不冲突)。
检查运行**在请求的 MongoDB transaction 内部**,并且
在 `(tenant, doctor, start)` 上的**部分唯一索引**(仅限有效状态)是确定性的兜底方案,因此对
同一时段的两个同时预订不可能同时成功 — 其中一个会中止。竞态测试
(两个相同预订的 `Promise.all`)证明了只有一个会成功。
## 🌍 设计上的市场无关性
没有硬编码任何特定于地区的内容。每家诊所都有各自的**货币**和**时区**
作为设置(默认 `PKR` / `Asia/Karachi`)。所有资金流经
`formatMoney(amount, tenant)`,所有时间流经 `formatTime(date, tenant)`。巴基斯坦是
首发市场,而不是限制。
## 🧱 技术栈
| 层级 | 选择 |
|---|---|
| CMS / API / Auth | Payload CMS 3 |
| Web | Next.js 16 (App Router) + React 19 |
| DB | MongoDB (replica set — 用于 transaction) |
| 样式 | Tailwind CSS v4 + shadcn/ui (Base UI) + lucide-react |
| 测试 | Vitest (Payload Local API 集成测试) |
## 🚀 快速开始
```
pnpm install
cp .env.example .env # then fill PAYLOAD_SECRET
docker compose up -d # MongoDB as a single-node replica set (rs0)
pnpm seed # 3 demo clinics, staff, patients, appointments
pnpm dev # http://localhost:3000
```
### 演示登录(密码:`password123`)
| 角色 | 邮箱 |
|---|---|
| Super Admin | `super@clinic.app` |
| Owner — City Care | `owner@city.app` |
| Receptionist — City Care | `reception@city.app` |
| Doctor — City Care | `doctor1@city.app` |
| Owner — Shifa | `owner@shifa.app` |
先以 City Care 登录,然后以 Shifa 登录 — 数据完全不同。这就是租户隔离墙。
## ✅ 测试
```
pnpm test # 34 integration tests: isolation, booking guard, availability, walk-in tokens
```
## 📁 项目结构
```
src/
access/ # the tenant wall — isSuperAdmin, tenantScoped, field-level rules
app/(frontend)/ # landing, login, dashboard/* (appointments, patients, staff, settings), super
app/(payload)/ # admin panel & REST/GraphQL API (super admin only)
collections/ # Tenants, Users, Patients, Appointments
components/ # DayRail (List + Timeline), BookingForm, StaffManager, SuperConsole, Sidebar, ui/
hooks/ # forceTenant (set-it-don't-trust-it)
lib/ # booking.ts, availability.ts, reports.ts, format.ts, constants.ts
seed.ts
payload.config.ts
public/images/ # landing/login photography (Unsplash) + product shot
tests/int/ # isolation, booking, overlap, availability, walk-in suites
```
## 🗺️ 路线图
v1(当前版本)是多租户基础。后续版本(临床就诊与处方、
计费、自助注册)已规划,但在目前有意排除在范围之外。
## 📜 许可证
该项目基于 **MIT License** 授权 — 详情请参阅 [LICENSE](LICENSE) 文件。
您可以将其用于商业和私人用途、修改并分发它;只需保留
版权和许可证声明完整即可。标签:MongoDB, Payload CMS, SaaS, 医疗管理, 自动化攻击, 请求拦截, 预约系统