简介:一份名为《HIS系统流程.ppt》的流程说明文档,以流程图为主,系统梳理东华HIS系统在门诊和住院两大业务板块的核心环节。文档面向医院信息科工作人员、HIS实施与运维工程师,也适用于产品经理和临床业务骨干在项目启动、培训或流程优化时查阅。内容按门诊和住院两个维度展开:门诊部分依次说明门诊整体流程、卡业务、挂号分诊、门诊医生站、收费退费、门诊药房及检查检验;住院部分覆盖住院整体流程、入院、退院/出院/召回、住院护士站、住院医生站、手术、住院药房和住院退费。每个流程都配有对应业务流程图,方便对照梳理环节衔接、角色分工和系统操作要点。资源包仅含1个PPT文件,大小约1.72MB,便于下载后投影演示或按需截取;已有4473人学习,适合作为医院信息化培训课件、HIS上线前的流程确认材料以及系统二次开发时的业务参考。
1. HIS系统流程不只是流程图,更是一组状态机
很多刚接触医疗信息化的工程师,拿到东华 HIS 系统这份《HIS系统流程.ppt》时,会习惯性把它当成普通业务图:门诊从挂号走到发药,住院从入院走到手术,看起来逻辑顺畅。但真正开始写代码或者做接口对接时才发现,PPT 里画出来的正流程只是“理想路径”,系统要稳定跑起来,绕不开逆流程、异常分支和并发控制。巴中市第一人民医院信息科这份流程说明把门诊、住院的主链路整理得很清楚,同时明确标注“不包括逆流程”。这句话恰恰是重点:医院每天都在发生退费、退药、取消出院、召回,这些反向操作才是开发排期里最容易低估的部分。下面我会把这份 PPT 里的正流程拆成可落地的状态机、数据表设计和事务边界,再结合退费、召回这类补偿场景做展开,适合正在做 HIS 实施、二次开发或系统联调的同行。
2. 门诊流程的节点拆解与数据模型
2.1 从卡业务到检查检验,每个环节都是一张独立单据
PPT 里门诊整体流程依次是:卡业务、挂号、分诊、门诊医生站、收费、药房、检查检验。看起来是一根线,实际上每个环节都有自己的业务对象。以卡业务为例,就诊卡本身有申请、审核、发卡、挂失、补卡、退卡状态,挂失后的卡在解挂前不能被系统识别为有效卡,否则挂号模块会收到一张没有对应卡档案的请求。这个细节在流程图上只是一个矩形,落到表设计上却要求卡表至少包含card_status、loss_flag、issued_at、invalid_at几个关键字段。另一个容易漏的点是分诊环节:分诊不产生独立计费,但它决定了患者进入哪个诊室、排在哪个队列,也直接决定门诊医生站的工作台里能看到哪些患者。如果把分诊做成简单更新状态,一旦并发叫号就会覆盖队列信息,所以分诊通常需要独立的排队队列表和状态变更记录。
核心表结构建议
下面给出一个简化版的门诊就诊主记录表,后续所有环节都通过current_status和current_node配合驱动,不建议直接用多个表的状态拼业务进度。
CREATE TABLE visit_outpatient ( id BIGINT PRIMARY KEY AUTO_INCREMENT, patient_id BIGINT NOT NULL COMMENT '患者ID', card_no VARCHAR(32) NOT NULL COMMENT '就诊卡号', visit_date DATE NOT NULL COMMENT '就诊日期', current_status TINYINT NOT NULL DEFAULT 1 COMMENT '状态:1挂号 2分诊 3就诊中 4医嘱开立 5已收费 6已发药 7完成 -1退号 -2退费', current_node VARCHAR(32) COMMENT '当前环节:REGISTER/TRIAGE/DOCTOR/CHARGE/PHARMACY/EXAM', operator_id BIGINT COMMENT '当前操作员ID', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, KEY idx_visit_date_status (visit_date, current_status) ) ENGINE=InnoDB COMMENT='门诊就诊主记录';说明:current_status用来约束业务是否允许下一步,current_node用来定位患者当前在哪个工位,两者不能互相替代。比如状态是“已收费”,节点可以是CHARGE,也可能是PHARMACY正在等药师操作,单看状态判断页面落点会出错。operator_id存操作人,配合updated_at可以还原一条就诊记录完整变动轨迹。实际项目中我还会加一个source_biz_type字段,用来区分是窗口挂号还是线上 App 预约,避免后端接口为了兼容不同来源写大量分支。
有了这张主表,门诊整体进度的查询就简单了。一个典型场景是信息科需要实时查看某天未完成就诊的患者分布,可以这样查:
SELECT v.current_status, v.current_node, COUNT(*) AS patient_count, SUM(CASE WHEN v.updated_at < DATE_SUB(NOW(), INTERVAL 1 HOUR) THEN 1 ELSE 0 END) AS timeout_count FROM visit_outpatient v WHERE v.visit_date = '2025-01-20' AND v.current_status NOT IN (-1, -2) GROUP BY v.current_status, v.current_node ORDER BY v.current_status;这个查询的作用是定位“卡在哪个环节”和“哪个环节积压超时”。这里的时间阈值参数可以根据医院日均门诊量调整,二甲医院一般把 1 小时作为普通环节超时标准,手术或特检可以放宽到 2 小时。如果发现timeout_count持续偏高,往往不是流程逻辑错了,而是分诊叫号或者药房发药的人手瓶颈。
2.2 用状态机约束流程顺序,而不是靠前端按钮显隐
门诊流程里最让人头疼的问题不是正向流程走不通,而是操作员在多个窗口同时操作同一笔就诊记录。比如收费员刚点退费,药房同时确认发药,如果代码里只写if status == 已收费 then 发药,这个判断在并发下会被击穿。正确的做法是在后端维护一张状态转移表,所有状态修改必须走同一个校验入口。我给门诊流程预设了这样一组合法转移:
| 当前状态 | 允许的下一个状态 | 触发操作 |
|---|---|---|
| 已挂号 | 已分诊、已退号 | 分诊台排队叫号 / 窗口退号 |
| 已分诊 | 就诊中 | 医生站接诊 |
| 就诊中 | 医嘱已开立 | 医生保存门诊医嘱 |
| 医嘱已开立 | 已收费 | 收费处收款成功 |
| 已收费 | 已发药、已退费 | 药房发药 / 收费处退费 |
| 已发药 | 已完成 | 药师确认发药完成 |
这个表的意义在于把业务规则从流程图中提炼成程序可以直接执行的判定条件。下面用一段 Python 代码表达同样的逻辑,便于把校验集中到一个方法里:
ALLOWED_TRANSITIONS = { 1: [2, -1], # 已挂号 -> 已分诊 / 已退号 2: [3], # 已分诊 -> 就诊中 3: [4], # 就诊中 -> 医嘱已开立 4: [5], # 医嘱已开立 -> 已收费 5: [6, -2], # 已收费 -> 已发药 / 已退费 6: [7], # 已发药 -> 已完成 } def transition_status(current_status, target_status, operator_id): if target_status not in ALLOWED_TRANSITIONS.get(current_status, []): raise InvalidStateTransition( f"状态不允许跳转: {current_status} -> {target_status}" ) # 额外的业务校验写在这里,比如退费必须关联原始收费记录 return update_visit_outpatient_status(current_status, target_status, operator_id)参数说明:current_status是数据库里读到的最新状态;target_status是本次操作期望变成的状态;operator_id会写入审计日志。这里ALLOWED_TRANSITIONS定义的是最短路径,实际项目里可能还需要支持“已收费”直接到“已完成”这类加急场景,但是要加操作类型字段区分,不能无脑放开。把状态转移集中到一个函数后,所有第三方面诊接口、自助机、移动端都走同一套校验,就不会出现 App 上显示已退费但药房还能发药的情况。
带状态机的设计也方便追溯问题。每个状态变更都记录from_status、to_status、operation_code、operator_id,一旦医患纠纷查到某笔订单,信息科可以快速定位是哪台终端、哪个操作员、在什么时间做了动作。这部分在 PPT 里没有任何说明,但对上线后的运维是刚需。
3. 住院流程的床位-医嘱-手术联动
3.1 住院流程为什么比门诊更依赖状态一致性
住院流程跟门诊最大的不同是引入了“床位”这个强约束资源。PPT 把住院整体流程串成入院、护士站、医生站、手术、药房、出院几个环节,实际执行时,患者状态必须和床位状态、护理级别、当前医嘱周期联动。比如一个患者要转科,需要先由转入科室分配床位,再通过护士站执行“入区”,患者主记录状态从“待入区”变成“在院”,同时原床位置为“空闲”,新床位改为“占用”。如果这两个动作不在一个事务里完成,大概率会出现同一张床被分配给两个人,或者患者已经办理入区但原床还没释放。
我对住院记录的状态设计更倾向于分成三层:visit_status表示整体在院状态,ward_status表示床位状态,order_status表示当前医嘱执行状态。visit_status可以是待入院、在院、预出院、已出院、已召回。床位状态分空闲、占用、清洁中、维护中。两层状态不能混在一起,否则查询“还有几张可分配床位”会很难写。实际开发时,床位分配使用乐观锁控制并发,SQL 类似下面这样:
UPDATE ward_bed SET patient_id = :patientId, ward_status = 'OCCUPIED', version = version + 1 WHERE bed_id = :bedId AND ward_status = 'FREE' AND version = :expectedVersion;这个语句是处理“两个护士同时抢同一张空床”的标准做法。:bedId是床位的物理 ID,:patientId是办理入院的患者 ID,:expectedVersion是前端或多步操作场景下读取床位信息时拿到的版本号。UPDATE影响行数为 1 表示抢占成功,为 0 则说明床位已经被别人占用或者状态变了,此时业务层要重新返回可选床位列表并提示护士刷新。版本号字段很关键,它是防止覆盖写入的兜底,比单纯判断ward_status = 'FREE'可靠得多。
3.2 医嘱与手术的状态解耦
医嘱状态通常有新开立、护士审核、执行中、停止、作废五种。手术申请则有待排程、已排程、已取消、已完成。这两套状态是有交叉但又不完全同步的。PPT 里把手术列为住院流程的一个独立节点,实际上手术排程完成后,麻醉医嘱和术前医嘱要同时转换成执行状态,手术结束后主刀医生还要补录手术记录并触发术后医嘱。如果只盯着手术表的状态变化,很容易漏掉医嘱表的联动。
我在病区业务里常用一张“医嘱生命周期表”记录每一次状态变化的原因,这是定位手术后医嘱有没有漏开的核心手段:
CREATE TABLE order_lifecycle ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_id BIGINT NOT NULL COMMENT '医嘱ID', from_status VARCHAR(20) NOT NULL, to_status VARCHAR(20) NOT NULL, change_reason VARCHAR(200) COMMENT '变更原因,如手术完成、护士核停', operator_name VARCHAR(50) NOT NULL, changed_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, KEY idx_order_id (order_id) ) ENGINE=InnoDB COMMENT='医嘱生命周期变更记录';这张表的核心价值是“只追加,不修改”。假设术后发现某条抗生素医嘱没有停,查一下该订单的change_reason就能知道是医生没开停止,还是护士漏执行停止操作。很多部署现场反映“医嘱下了但药房不发药”,排查方向通常是查状态是否卡在护士审核,这张表可以给出准确的时间线。同时也可以用来统计每个操作环节的平均耗时,辅助医院优化护理排班。
常见做法是,手术排程完成后由系统自动创建一组“术后默认医嘱模板”,而不是让医生从零开始录入。医嘱模板在 HIS 系统里属于高频功能,医院上线时一般会把本院的常用抗生素、输液方案和换药方案整理成模板。下面这个查询就是用来统计模板价值的:
SELECT template_id, template_name, COUNT(*) AS used_count, AVG(TIMESTAMPDIFF(SECOND, order_created_at, order_reviewed_at)) AS avg_review_seconds FROM doctor_order o JOIN order_template t ON o.template_id = t.id WHERE o.created_at >= '2025-01-01' GROUP BY template_id, template_name ORDER BY used_count DESC LIMIT 20;这里avg_review_seconds是医生从开出医嘱到护士审核的平均间隔时间。如果模板化医嘱的使用率高但avg_review_seconds仍然长,问题大概率不在录入端而在护士排班端,需要信息科配合护理部看审核提醒的配置。注意模板不能代替医生判断,系统只负责把重复录入降到最低,最终审核权一定在人。
4. 逆流程不是反着走一遍:退费、退药与召回
4.1 为什么“不包括逆流程”是最大的坑
PPT 开篇明确说了“不包括逆流程”,但真正部署到巴中市第一人民医院这类等级医院后,退费、退药、取消出院、召回每天都频繁发生。如果开发人员把逆流程简单理解为“把状态改回去”,会在财务对账时出大问题。以门诊退费为例,一笔挂号费已经进入当日结算报表,这时患者因故退号,系统不能只把挂号单状态改回初始值,而要生成一条负数收费记录做冲正,同时保留原挂号单记录。这样财务统账时,收入汇总表里的金额和每一笔原始流水加冲正记录是完全对等的,任何中间状态丢失都能从流水里找回来。
逆流程的第一个设计原则是“允许回退操作,但不允许物理删除业务单据”。第二个原则是“每一步逆操作都要有独立操作凭证”,比如退费单号、退药单号、召回单号。第三个原则是“严格控制逆流程的权限”,不能所有账号都有退费权。很多信息科出安全事件,都是因为收费员和管理员角色权限没有区分。
4.2 门诊退费的补偿事务设计
退费不是简单改状态,需要校验当前单据是否允许退、是否重复退、是否已经关联发药。下面是一个典型的退费伪代码,我用 Python 风格表达事务边界:
def refund_outpatient_charge(order_no, refund_amount, operator_id): order = get_charge_order(order_no) if order.status != CHARGE_SUCCESS: raise BusinessException("只有收费成功的单据才能退费") if order.refund_flag: raise BusinessException("该单据已经退费,禁止重复操作") if get_prescription_status(order_no) in [DISPENSED, PARTIAL_DISPENSED]: raise BusinessException("药品已发出,请先到药房完成退药再退费") # 开启本地事务,保证冲正与状态更新原子性 with db.transaction(): create_refund_record( order_no=order_no, amount=-abs(refund_amount), reason_code="PATIENT_REQUEST", operator_id=operator_id, ) update_charge_order_status( order_no=order_no, target_status=REFUNDED, operator_id=operator_id, ) # 如果需要与财务系统同步,这里发 MQ 消息 mq_producer.send("finance.reverse", { "order_no": order_no, "amount": -abs(refund_amount), "operator_id": operator_id, }) return refund_order_no逻辑说明:函数先断言原收费单状态是收费成功,再查refund_flag防止多终端重复退费,然后检查处方发药状态。只有当药品未发或者已经完成退药后才能进入事务。事务里先创建冲正记录,金额取负数,随后把原单状态置为已退费。最后的 MQ 消息是可选的,如果医院有独立的财务系统或医保前置服务,需要把冲正动作异步通知出去,保证两边一致。这里的refund_order_no是系统生成的退费单号,后续所有查询和追溯都基于这个单号。
这里有个细节容易被忽略:参数refund_amount不直接使用原单金额,而是由前端传入。因此后端必须校验abs(refund_amount) <= order.paid_amount - order.refunded_amount,否则可能有权限的收费员用小额退费接口把大额费用冲平。如果你的系统同时支持部分退费和全额退费,需要把“退款金额”和“是否全部退”拆成两个独立参数,并且每次写退费记录时更新订单的refunded_amount累计值。
4.3 出院召回与状态快照
住院患者的召回首选出现在医保拒付、费用争议或病情反复这三种场景。召回操作不能简单从“已出院”改成“在院”,因为出院时可能已经完成了结算、病案归档和床位释放。正确做法是保留已经完成的出院记录,同时创建一个新的“召回记录”,把visit_status改成“已召回”,并生成一张新的在院记录。旧出院单上的费用不受影响,新增费用从召回时刻重新累计。
具体执行时,我建议按下面这个顺序操作:
- 操作员从住院医生站发起“召回申请”,填写召回原因和预定床位。
- 护士站校验原床是否空闲,若已被占用则进入待床状态。
- 系统创建召回记录,状态为待入区,同时将原出院记录状态标记为“已召回”。
- 患者到病区完成入区后,召回记录状态变为“在院”。
- 结算时医保系统看到的是一段可追溯的“出院-召回”时间序列,而不是被抹掉的记录。
这套流程关键在第三步的表结构设计:召回记录必须携带原出院记录 ID,否则审计时找不到患者上一次出院和这次入院之间的关联。实际部署中,我看到不少团队把召回做成更新原出院单状态,结果病案室打出来的报表总缺一次住院记录,最后卡在医保稽核环节。
5. 把 PPT 变成可执行的流程验证清单
5.1 用状态机脚本校验门诊主流程
PPT 里画了很多流程图,但上线前必须验证真实系统是否按照图中路线走。最直接的验证方式不是手工点击,而是写一个遍历脚本,把门诊主流程从头走到尾,断言每一步状态是否正确。下面这段 Python 脚本可以模拟一次完整的门诊就医路径:
# visit_id 是已挂号患者,依次执行分诊、医生站开单、收费、发药 flow = [ ("TRIAGE", 2), ("DOCTOR_OPEN_ORDER", 3), ("DOCTOR_SAVE_PRESCRIPTION", 4), ("CHARGE_SUCCESS", 5), ("PHARMACY_DISPENSE", 6), ("FINISH", 7), ] visit_id = get_visit_id(card_no="100861", visit_date="2025-01-20") for node_code, expected_status in flow: current = get_visit_status(visit_id, node_code) assert current == expected_status, \ f"环节 {node_code} 期望状态 {expected_status},实际 {current}" # 这里可以调用被测系统的真实接口执行下一步操作 call_his_api(visit_id, node_code) time.sleep(0.5) print("门诊主流程状态链校验通过")这个脚本的核心价值是把 PPT 中的线性流程变成机器可断言的检查项。visit_id是通过挂卡、挂号两个前置接口拿到的,node_code对应后端服务里的操作类别。如果中间任何一步返回的状态和expected_status不一致,脚本立刻失败并给出当前节点,信息科可以直接定位是哪个服务没有按既定路径更新状态。对于测试人员来说,把这张状态转移表直接转换成自动测试用例,比手工点页面快一个数量级。
5.2 用并发请求验证退费、床位分配边界
流程验证不能只跑正流程,还要验证并发异常。退费和床位分配是最容易出现并发问题的地方。我通常会准备两个并发场景:第一个是同一笔收费单同时发起两笔退费请求,预期只能成功一笔;第二个是同一张床同时被两个入院登记请求分配,预期只能成功一个。下面这个命令行示例模拟 30 个并发退费请求命中同一个单号:
seq 1 30 | xargs -P 10 -I {} \ curl -s -X POST 'http://his-gateway/outpatient/refund' \ -H 'Content-Type: application/json' \ -d '{"charge_order_no":"CH202501200010","refund_amount":1.00,"operator_id":1001}'执行后在refund_order表里查charge_order_no = 'CH202501200010',预期只有一条退款成功记录,其余返回BUSINESS_ERROR并且未生成冲正流水。如果发现多条成功记录,说明退费接口缺少refund_flag的防重校验,或者状态更新和冲正记录没有放在同一个事务里。床位并发测试同理,用多线程同时执行前面第 3 章那条乐观锁UPDATE,最后ward_bed表里同一张床只能有一个非空patient_id。若出问题,优先检查是否没有WHERE version = :expectedVersion条件。
5.3 流程图符号与代码分支的对应审查
PPT 里流程图的矩形代表处理步骤,菱形代表判断节点,而这些在代码里都有对应结构。审查时我习惯把每个菱形分支标号,再对代码里的if/switch分支逐一核对,确保没有“图上能走通,代码走不到”的分支。比如“费用是否已结清”这个菱形,代码里可能出现三种情况:已结清走算费、未结清走欠费、部分结清走中间态。如果 PPT 只画了两个分支,开发时只写了两个判断,部分结清场景就会落到空分支,最终表现为出院结算金额始终对不上。这部分审查不能交给业务人员,需要信息科把流程图里的每个分支映射到具体接口错误码和前端提示文案,才能形成闭环回归清单。对照这份清单跑完一轮,基本能把正逆流程的盲区扫干净。
本文还有配套的精品资源,点击获取