
简介压缩包内提供了一套医疗器械销售全流程智能管理系统面向医疗器械企业销售、采购、库存及质量管理人员解决采购、验收入库、销售出库、退货处理、库存监控、有效期提醒与过期产品锁定等全流程管理难题。系统内置供应商资质管理、首营企业管理和首营产品审核模块可强化合规风险控制。资源共16个文件、约6.19MB主要包含exe可执行程序、dbi数据库文件、chm操作手册、docx与txt说明文档以及jpg预览图、html界面等便于直接运行、查阅说明与快速部署。已有53人学习下载。通过这套系统可获取完整的医疗器械销售流程管理方案包括采购计划下达、入库验收记录、出库核验流程、退货原因分析等模块的实现逻辑附带说明文件与附赠资源有助于理解系统部署与功能流程适合作为行业信息化建设的参考模板。1. 医疗器械销售管理系统难的不是进销存是合规医疗器械销售全流程智能管理系统表面看是一套进销存实际是把 GSP 的合规要求落进每一天的操作里采购、验收入库、销售出库、退货、库存、效期提醒、过期锁定、供商资质、首营企业、首营产品。普通 ERP 也能开单管库存但器械行业有几个绕不过去的差异——产品带效期、经营要许可、首营要审核、过期必须锁。这套系统解决的核心问题就一句话保证任何一批货在到期之前卖得掉、卖不掉也卖不出去同时每一笔采购和销售都能追溯到证照齐全的供应商和产品。适合质量负责人应对飞检内审IT 负责人替代 Excel 台账也适合刚入行的开发或产品经理理解合规逻辑。下面按模块拆开讲实现每个环节给出可以照抄的字段设计和代码最后是上线避坑。2. 效期提醒与过期产品锁定为什么这套系统把「到期」当一条硬约束做医疗器械系统最先要定下来的不是界面是效期怎么算、过期怎么锁。普通进销存不会因为一个商品过期就拒绝出库但器械会。这个模块直接决定系统能不能通过检查也决定仓库里会不会出现「明明锁了却还能卖」的事故。2.1 批号台账是地基效期只能挂在批次上不能挂在产品上很多第一次做器械系统的人会把效期字段加到产品表里这是翻车的第一步。同一个产品会有多个批号每个批号的到货时间、生产日期、有效期至、剩余数量都不一样。效期如果挂在产品上就只剩一个值第二批货来了之后第一批的效期信息直接被覆盖还没到卖货那一步数据就已经不可信了。正确的做法是把库存做成批次台账每一行代表一个批号在库的一批货。核心字段如下CREATE TABLE inventory_batch ( id BIGINT PRIMARY KEY AUTO_INCREMENT, product_id BIGINT NOT NULL COMMENT 产品ID关联产品主数据, batch_no VARCHAR(64) NOT NULL COMMENT 生产批号/序列号, production_date DATE NULL COMMENT 生产日期, expiry_date DATE NOT NULL COMMENT 有效期至, quantity_on_hand INT NOT NULL DEFAULT 0 COMMENT 在库数量, locked_qty INT NOT NULL DEFAULT 0 COMMENT 锁定数量含停售和过期冻结, stock_status VARCHAR(16) NOT NULL DEFAULT normal COMMENT normal-正常 warning-近效预警 near_expiry-临期 stop_sale-停售 blocked-过期冻结, receive_date DATE NOT NULL COMMENT 入库日期, UNIQUE KEY uk_product_batch (product_id, batch_no, expiry_date) );唯一键 (product_id, batch_no, expiry_date) 是为了防止两个供应商给了相同批号、但效期不同时被错误合并。同一个产品、同一个批号、同一个效期才允许合并库存效期不同就必须拆成两行。这个约束能在入库那一刻就把大部分效期错乱的问题挡在门外。status 字段只表示业务状态不表示物理位置。数量上quantity_on_hand 是在库总数locked_qty 是被冻结的数量真正能拿来卖的是两者之差。每次出库下单都要重新算可用量不能直接拿 on_hand 去扣。2.2 每天跑一次的状态机预警、停售、锁定怎么流转效期状态不能等出库的时候才算要每天定时批量刷新。状态流转是这样一条单行道normal正常可售离到期还有足够天数warning进入近效期预警系统开始给业务员推送清单催促促销或退货near_expiry临期质管介入确认是否继续销售stop_sale停售只允许退回供应商或报损不允许卖给客户blocked过期冻结locked_qty 自动等于 quantity_on_hand任何销售逻辑都取不到这个批次的可用量。blocked 不等于报废。它只是先锁住后续还要走报损审批、实物销毁、财务核销但那是另一个流程不能因为在库里被锁就跳过。每天凌晨跑一次定时任务刷新状态核心逻辑如下# daily_expiry_task.py 每天凌晨 2 点执行 from datetime import date, timedelta def refresh_batch_status(): today date.today() # 每个产品分类对应不同的预警阈值从配置表读取 rules get_category_rules() # [(category_id, warn_days, stop_sale_days), ...] for category_id, warn_days, stop_days in rules: sql UPDATE inventory_batch b JOIN product p ON b.product_id p.id SET b.stock_status CASE WHEN b.expiry_date CURDATE() THEN blocked WHEN b.expiry_date DATE_ADD(CURDATE(), INTERVAL %s DAY) THEN stop_sale WHEN b.expiry_date DATE_ADD(CURDATE(), INTERVAL %s DAY) THEN near_expiry WHEN b.expiry_date DATE_ADD(CURDATE(), INTERVAL %s DAY) THEN warning ELSE normal END WHERE p.category_id %s AND b.quantity_on_hand 0 execute(sql, (stop_days, 30, warn_days, category_id)) generate_expiry_alerts()这里要注意近效预警用 30 天是写死的因为它是「提醒」而不是「处理」。真正需要业务介入的是 stop_sale。停售之后还要通过邮件或企业微信把明细发给质管部不能只改个状态就结束否则锁是锁了但没人处理积压。出库模块里还要再加一道兜底不能只依赖定时任务。每次销售出库选批次时SQL 条件里强制加状态过滤SELECT batch_id, batch_no, expiry_date, (quantity_on_hand - locked_qty) AS available_qty FROM inventory_batch WHERE product_id :product_id AND stock_status IN (normal, near_expiry) AND quantity_on_hand locked_qty ORDER BY expiry_date ASC, receive_date ASC LIMIT 10;2.3 预警参数怎么定同一种阈值管不了诊断试剂和无菌耗材预警参数是最容易被当成「拍脑袋」的部分但恰恰是质管最喜欢检查的内容。全局统一设 90 天预警、30 天停售对某些产品太激进对另一些又太迟钝。我一般会按产品类别配置三套阈值每套上线前由质管部签字确认。表格效期预警参数参考产品类别首次预警临期提醒停售说明体外诊断试剂90 天30 天15 天效期一般 1224 个月90 天足够消化库存无菌耗材180 天60 天30 天效期 25 年提前半年便于调整采购计划大型医疗设备365 天90 天60 天涉及售后服务周期提前一年提醒备件问题停售天数不是拍脑袋定的。体外诊断试剂的运输和终端验收往往需要 715 天如果剩余效期少于运输 终端仓库周转的时间到客户手上已经是临期品会产生质量纠纷。所以停售阈值至少要大过「平均在途天数 终端平均库存天数」。实施项目时我会让质管部在配置表上签一个字后面出任何效期争议系统参数就是依据。这一章的核心结论效期管理不是加几个字段而是「批次台账 定时状态机 出库兜底」三条链路同时工作缺一条都会在关键时刻掉链子。3. 首营企业、首营产品与供商资质把不合规挡在采购下单之前效期管的是「货到了之后」首营和资质管的是「货还没到之前」。器械行业的采购不能像买办公用品一样先下单后补资料。GSP 要求首次发生业务关系之前供应商和产品必须完成资质审核。这套逻辑在系统里就是两张审批单首营企业审批、首营产品审批外加一套供商证照到期管理。3.1 首营审核先走完流程基础资料才建得起来首营企业指第一次向某家供应商采购首营产品指第一次经营某个品种。第一次的意思不是仅限首次而是只要系统里没有审核通过的记录就必须走完整流程。审核流程对应到系统就是一张状态为「草稿 → 质管审核 → 质量负责人批准 → 生效」的首营单。审核中状态的基础资料采购模块是选不到的这样做保证「先审后用」。如果审核还没完成采购人员就在线下口头下单系统里没有单子审完再补那这套系统和 Excel 没有区别还会因为补录造成日期逻辑前后矛盾。一张首营企业审批单需要关联的证照按类型拆开看是这个表证照类型审核要点到期处理营业执照经营范围覆盖医疗器械到期自动停用医疗器械生产/经营许可证许可范围包含所购产品类别到期自动停用产品注册证/备案凭证注册证号与产品一一对应注册证过期该产品禁止销售授权书授权链条完整从厂家到供应商逐级有效到期后重新索取质量保证协议双方盖章有效续签证照不能只存图片要把每一项拆成独立的证照记录单独记录证照类型、证照编号、生效日期、到期日期和关联的供应商或产品。这样才有办法做到期提醒和自动停用。3.2 采购下单前做实时校验确保凭据当真切有效首营审核通过只是建档不代表证照永久有效。很多团队在首营单审批完成后就松懈了证照过期半年还在正常采购质管检查时把所有单据翻出来发现采购日期晚于供应商许可证到期日这一项就是缺陷。解决思路是采购订单保存前做一次实时校验而不是只靠每日任务。下面这段逻辑放在服务层所有采购入口——不管是 PC 端下单、导入采购计划还是接口对接——都必须走同一段校验def check_supplier_and_product(supplier_id, product_id): # 1. 供应商主体证照在有效期内 invalid db.query( SELECT license_name, expire_date FROM supplier_license WHERE supplier_id :sid AND is_deleted 0 AND license_type IN (营业执照, 经营许可, 生产许可) AND (expire_date CURDATE() OR expire_date IS NULL) , sidsupplier_id) if invalid: raise BizError(f供应商证照失效{invalid[0][license_name]} 到期 {invalid[0][expire_date]}禁止采购) # 2. 该产品的首营资质在有效期内 product_lic db.query_one( SELECT 1 FROM product_license WHERE product_id :pid AND license_status active AND expire_date CURDATE() , pidproduct_id) if not product_lic: raise BizError(该产品首营资质不存在或已过期禁止下单) # 3. 距离证照到期不足 30 天的允许下单但写入预警记录 expiring db.query( SELECT license_name, expire_date FROM supplier_license WHERE supplier_id :sid AND expire_date BETWEEN CURDATE() AND DATE_ADD(CURDATE(), INTERVAL 30 DAY) , sidsupplier_id) if expiring: add_alert(f供应商 {supplier_id} 证照 {expiring[0][license_name]} 将于 f{expiring[0][expire_date]} 到期请尽快索取新证)校验顺序有讲究先供应商后产品最后预警。供应商证照过期直接报错不允许任何绕过的入口。产品首营资质过期同理。只有一切都有效时才允许下单但如果证照临期下单成功但写一条预警让采购人员知道这单可能是这家供应商的最后几单。3.3 证照到期自动停用但不能把历史数据一并「停掉」每日定时任务扫描证照到期日期到期当天把供应商状态置为 inactive产品状态置为 stop_sale 或 inactive。停用的意思是新单据选不到、接口校验不通过、不能继续发生新交易。但不能做物理删除也不能把历史单据置为无效。原因很简单医疗器械的追溯要求是证照在交易发生时有效即可。一张采购单发生在 3 月供应商许可证 6 月到期这张单没有任何问题。如果系统在 7 月自动把供应商停用后连带着把 3 月那张采购单也标记成异常那整个库存都跟着不可信财务账也平不了。我一般会在供应商表上保留三个状态active有效、inactive证照过期自动停用、manual_disabled人工停用例如发生质量问题。历史单据全部照常可查只是不能再发生新交易。定时任务里还要生成一张「证照到期预警表」提前 90 天开始每天推送给采购和质管同时看避免等过期了才被发现。第 3 章的核心结论首营和资质管理不是一个审批功能而是一条「审批建档 → 下单校验 → 到期停用」的闭环。校验必须放在服务层而不是页面上否则绕过接口的调用就是监管漏洞。4. 从采购验收到销售退货四个业务模块怎么串成一条库存流水标题里的采购、验收入库、销售出库、退货管理这四个模块本质上不是四个独立功能而是一条库存流水上的四个节点。每一个节点都有一张单据每一张单据都在改变批次的库存数量或状态。如果把它们做成互不相干的菜单库存账一定会乱。如果你拿到的是这套系统的采购.zip第一步不是找启动脚本而是在数据库里先建采购订单、到货单、验收单、入库单这几张表再把它们的关系理顺。采购单只是开头验收入库才是库存真正增长的节点。4.1 验收入库批号、效期、注册证号在这里一次性定生死采购到货后验收员要做的事比普通仓库多得多核对采购单、清点数量、检查外包装、查验合格证明、登记生产批号、生产日期、有效期至还要核对产品注册证号是否和实物标签一致。这些信息全部录进验收单验收合格才生成入库单库存批次表才增加一行。验收单是医疗器械追溯体系里最重要的记录。任何一个批次的货出了问题检查人员首先就是调验收单。所以我一般在验收表里留一个 create_by 和 verify_by 双字段验收员录单质管员复核确认两个人不能是同一个人。入库时同样要处理效期异常的情况。货物到库时有效期已经不足某个阈值的可以有两种处理方式直接拒收或者入到「待处理」状态等待质管判定。我建议是入待处理不现场拒收因为有些产品虽然剩余效期短但急着用质管判定后走特殊流程放行。直接把货退回去会让业务非常被动。4.2 销售出库用对批次比开对单更重要销售出库的核心不是卖了多少数量而是卖了哪一批。同一产品三批货效期分别是 10 天后、60 天后、200 天后系统推荐的出库顺序必须是效期最短的在前也就是 FEFO近效期先出。出库时批次分配逻辑放在服务层伪代码大概是def allocate_batches(product_id, required_qty): batches db.query( SELECT batch_id, batch_no, expiry_date, (quantity_on_hand - locked_qty) AS available_qty FROM inventory_batch WHERE product_id :pid AND stock_status IN (normal, near_expiry) AND (quantity_on_hand - locked_qty) 0 ORDER BY expiry_date ASC, receive_date ASC , pidproduct_id) allocated [] remaining required_qty for b in batches: if remaining 0: break take min(remaining, b[available_qty]) allocated.append({**b, allocated_qty: take}) remaining - take if remaining 0: raise BizError(可用库存不足当前仅能分配 {}.format(required_qty - remaining)) for a in allocated: db.execute( UPDATE inventory_batch SET locked_qty locked_qty :qty WHERE batch_id :id , qtya[allocated_qty], ida[batch_id]) return allocated分配时先把批次锁定生成销售出库单等仓库确认出库后才扣减 quantity_on_hand 并释放 locked_qty 里的对应数量。两个事务分开是为了防止「单子开了库存也扣了但货还没发出去」导致账面库存和实物不一致。这里有一个非常关键的业务判断near_expiry 状态的批次允许销售因为 30 天临期但还没到停售线。但是要在出库单上打一个「临期」标识让仓库在复核时确认客户能接受剩余效期。不然客户收货后因为效期原因退货责任就说不清了。4.3 退货管理两条反向流水方向和性质都不同退货有两条完全不同的路径销售退回是客户把货退给公司采购退货是公司把货退给供应商。它们都会让库存增加或减少但业务含义完全不同绝对不能共用一个单子。销售退回的流程是客户申请 → 退货单 → 货到公司 → 质量验收 → 合格入合格品库或不合格报废。这里最容易出的问题是退回的货直接加到库存里重新上架。医疗器械一旦出库再退回包装、运输、温度条件都可能发生变化必须重新做外观检查和效期确认。效期剩余的判定也很重要退回时剩余效期不足某个值比如 30 天的批次不允许回到可售库存直接转入待报废。采购退货的流程是向供应商发起退货 → 库存减少 → 应收退款挂账。它对应的是采购单的反向冲减不能走销售退回的界面去操作。库存台账上我建议用一张库存流水表记录每一次变动入库 、销售出库 -、销售退回 、采购退货 -、盘盈 、盘亏 -、报损 -。每个字段后面都带单据号这样任何时候都能回答同一个问题某批货现在的在库数量是怎么来的。没有这一张流水表四个业务模块永远串不成一条完整的账。5. 避坑效期预警不灵、首营漏审、退货冲乱五条血泪经验这套系统和普通进销存最大的区别就在这些隐藏细节里。以下五个问题是我在同类项目中反复遇到的每个都按「现象 → 原因 → 解决」拆开。5.1 设置了效期提醒过期批次照样出库现象系统里明明有每天的任务在跑预警也在发结果有一批过期产品还是被卖出去了。原因定时任务只更新了状态但销售出库的接口没有做批次状态二次校验。页面上的列表过滤了接口层没过滤某个绕过页面的入口或老接口直接拿走了过期批次。解决状态刷新是「日常防线」出库事务里的状态校验是「最后一道闸」。出库 SQL 里必须强制加 stock_status IN (normal, near_expiry) 和剩余的可用量判断两个条件缺一不可。同时在出库前把分配好的批次明细回查一次 status发现不是可售状态就中止出库。5.2 同批号不同效期库存被合并成一行现象两个不同供应商交付了相同批号的产品但有效期至差了半年系统却把它们合并在一个批次数量的字段里。原因批次表唯一键建成了 (product_id, batch_no)没有把 expiry_date 纳入唯一约束。解决唯一键改成 (product_id, batch_no, expiry_date)。这是最容易修复但影响最大的一个表结构问题。改表时要注意已有脏数据先跑一遍 SQL 查出冲突记录人工拆分后再加约束。5.3 供应商证照到期被停用采购还能下单现象证照到期当天系统自动停用了供应商页面也选不到但业务方通过接口或复制历史单据的方式又下了一张采购单。原因校验只放在了前端页面服务层接口没有做同样的校验。解决把资质校验下沉到服务层所有入口共用同一段校验逻辑包括接口调用、批量导入、历史单复制。前端校验只是体验服务层校验才是合规。5.4 盘点报损后过期批次又变成可售库存现象一批过期产品被盘点报损核销几天后系统里又冒出了可用库存还能被正常卖出。原因报损单执行时做的是「状态回滚」而不是「数量扣减」。比如盘点差异处理里用了一个通用冲正逻辑把 blocked 状态改回了 normal数量也跟着恢复了。解决库存状态机的流转必须是单向的。blocked 之后只能走到 scrapped已报损或 sold已销售不允许任何逻辑把它改回 normal、near_expiry 或 stop_sale。我一般会在状态更新函数里加白名单校验只允许按预定义路径迁移其他路径直接报错。5.5 销售退回直接入合格品库效期短的货又卖给下一个客户现象客户退回来的一批货剩余效期只剩 20 天系统直接加回了可售库存又被下一位客户买走引发二次投诉。原因退货流程缺少质量验收节点验收和入库被合并成了一个动作或者验收人直接点了「同意入库」没有填效期判断。解决销售退回必须走独立流程退货单 → 待验收 → 质量判断 → 合格才入可售库存。剩余效期低于停售天数的批次不允许回可售只能入待报损或待退货区。这个判断阈值要和第 2 章的停售参数保持一致不要另搞一套。6. 上线后第一个月用一张效期预警报表验证系统靠不靠谱系统上线不是把菜单摆出来就算完。我习惯在第一个月不做自动锁定而是每天跑一张效期预警报表人工复核系统算出来的状态和实际仓库是否一致。跑顺了再打开自动锁定开关这样可以避免因为参数配错第一天就把一批还能卖的货全部锁死。报表的核心数据就是按批次分组列出每个批次的效期状态、剩余数量、锁定数量、剩余天数并按剩余天数排序。第一个周末做一次全量实物盘点拿 10 个批次的实物效期去对系统的状态对不上就说明批次录入环节出了问题不要继续往下推流程。等对账稳定了再加一张「近效期库存金额表」把未来 90 天到期的库存按产品线和金额汇总。这张表直接给财务做减值准备用公司的采购计划也应围绕它调整。我见过不少项目花大力气做效期锁定但预警报表只有个数字列表没有金额维度业务根本不看预警等于白做。把金额维度加进去之后业务才有动力处理临期库存金额最高的品类先搞促销、先退货、先停止采购。这也是我判断一套系统有没有真正用起来的标准——不是谁的界面好看而是质管能不能每周从系统里导出一张表直接拿去开经营分析会。从采购.zip 到整套系统跑起来中间隔着的不只是代码而是这些流程细节。希望这套梳理能帮你少踩几个坑。本文还有配套的精品资源点击获取