
简介一份名为《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) ) ENGINEInnoDB 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) ) ENGINEInnoDB 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_noorder_no, amount-abs(refund_amount), reason_codePATIENT_REQUEST, operator_idoperator_id, ) update_charge_order_status( order_noorder_no, target_statusREFUNDED, operator_idoperator_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_no100861, visit_date2025-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 只画了两个分支开发时只写了两个判断部分结清场景就会落到空分支最终表现为出院结算金额始终对不上。这部分审查不能交给业务人员需要信息科把流程图里的每个分支映射到具体接口错误码和前端提示文案才能形成闭环回归清单。对照这份清单跑完一轮基本能把正逆流程的盲区扫干净。本文还有配套的精品资源点击获取