当前位置: 首页 > news >正文

药品追溯码(溯源码)采集系统(二):门诊发药后端

        门诊发退药追溯码采集系统解析:

一、门诊发退药追溯码数据表

1.1、Wm_ware_dispense_bill表:该表用于存储处方信息

1.2 Wm_ware_dispense_tracecode:追溯码采集表

二、发退药后端代码

        后端代码基于Springboot架构和mybatis-plus,先看主要接口信息:

1.1、该接口用于接收处方号、部门id和操作人三个参数,回参是处方单的药品信息

/*** 接口名称: WareDispenseBillController$* 功能描述:用于存储处方单信息,以及查询信息并输出到页面*/@RestController
@RequestMapping("/wareDispenseBill")
public class WareDispenseBillController {@Resourceprivate WmWareDispenseBillService wmWareDispenseBillService;@GetMapping("/split/{Rxno}/{departmentid}/{username}")@Transactional(rollbackFor = Exception.class)public R getWareBaseInfo_tz_v_split(@PathVariable String Rxno,@PathVariable String departmentid,@PathVariable String username) {try {AtomicInteger statusCode = new AtomicInteger(0);List<WareInfoSplit> wareInfo_t = wmWareDispenseBillService.querySplit(Rxno, departmentid, username, statusCode);// 已上传医保或查询失败时,返回空数组而非错误消息if (wareInfo_t == null) {return R.OK(new ArrayList<>());}return R.OK(wareInfo_t);} catch (Exception e) {return R.FAIL("系统错误");}}
}

其中回参WareInfoSplit,用于传递给前端渲染,供发药医师确定发药情况:

  1. splitFlag代表拆零标志,如果 splitFlag=00 说明该药品是非拆零药品,非拆零药品需要门诊挨个扫码,而拆零药品需要药库提前扫码,储备子码库方表调用
  2. billid是处方表的主键
  3. wareid是商品id
  4. patientName是患者名
  5. spec是规格
  6. quantity是发药数量
  7. manufacturer是生成厂家
  8. tracecodePrefix是药品追溯码前七位,也被称为标识码
  9. scanFlag是用于院内制剂,没有追溯码的药品的标识
  10. enoughFlag是当药品为拆零药品,且储备的剩余子码不足发药数量时,用于前端提示
  11. splitTracecode是待拆追溯码
  12. subcodeids是存储拆零子码的codeid
@Data
@AllArgsConstructor
public class WareInfoSplit {private String splitFlag;private Long billid;private Integer wareid;private String patientName;private String wareName;private String spec;private Integer quantity;private String manufacturer;private String tracecodePrefix; //药品标识码,前七位private String scanFlag;  //是否需要扫码private String enoughFlag; //库存追溯码数量,是否能覆盖发药需求,00是能覆盖,10是不能覆盖private String splitTracecode;  //待拆追溯码private List<Integer> subcodeids; //存储拆零子码的codeid

1.2 上面的querySplit方法如下:

1.2.1 该方法首先需要对接his接口,该接口通过处方号来获取药品基本信息。

 @Transactional@Overridepublic List<WareInfoSplit> querySplit(String RecipeNo, String departmentid, String username, AtomicInteger statusCode) {// 1. 调用外部API获取处方数据final String API_URL = "http://ip:post/QueryRxno";//创建一个 JSON 格式的字符串//这里用到了转义字符,把双引号转义为普通的字符String requestJson = String.format("{\"RecipeNo\":\"%s\"}", RecipeNo);try {// 发送API请求RestTemplate restTemplate = new RestTemplate();HttpHeaders headers = new HttpHeaders();headers.setContentType(MediaType.APPLICATION_JSON);//HttpEntity封装请求体和请求头。HttpEntity<String> requestEntity = new HttpEntity<>(requestJson, headers);//返回的ResponseEntity<String>包含响应状态码和响应体ResponseEntity<String> response = restTemplate.exchange(API_URL, HttpMethod.POST, requestEntity, String.class);if (response.getStatusCode() != HttpStatus.OK) {statusCode.set(1);   //此时就是没能成功调用接口return null;}//将 HTTP 响应中的 JSON 字符串转换为 Java 的Map<String, Object>对象ObjectMapper objectMapper = new ObjectMapper();Map<String, Object> resultMap = objectMapper.readValue(response.getBody(), new TypeReference<Map<String, Object>>() {});if (!"1".equals(resultMap.get("ReturnCode"))) { //ReturnCode如果不等于1就一定不正常statusCode.set(2);return null;}//ReturnCode等于1,也有可能回传空的列表List<Map<String, Object>> details = (List<Map<String, Object>>) resultMap.get("Details");if (details == null || details.isEmpty()) {statusCode.set(3);return null;  //未查询到处方}

医院his接口部分回参如下:

1.2.2  第二段的代码逻辑:

1)医院只会传药品编码,我需要通过维护的药品基础表来查询对应的药品基础信息记录,其中比较重要的是

tracecode_prefix:药品标识码

split_flag:拆零标志

split_ratio:拆零系数,若药品为拆零药品,假设追溯码贴在药盒上,一盒有10支,则拆零系数为10

2)源码:

        有些处方号可能之前已经扫过,这里存在两种情况:一是医生开具处方之后,患者没有及时过来拿,而医院没有报道机的情况下,就会在打印处方单的同时扫码;二是发药医师扫过处方单之后,又误扫处方单,此时在处方表内就会有历史记录,需要找到记录扫处方的记录

// 2. 性能优化 - 批量收集药品编码并查询Set<String> drugCodes = new HashSet<>();  //发药的药品编码List<String> rxNos = new ArrayList<>();   //发药的处方号List<String> rxSerialNos = new ArrayList<>();  //发药的处方明细号for (Map<String, Object> item : details) {drugCodes.add(item.get("ware_code").toString());rxNos.add(item.get("rxno").toString());rxSerialNos.add(item.get("rx_serialno").toString());}// 批量查询药品基础信息// 通过药品编码查询药品信息,并转换为Map<ware_code, PubWareBase>的结构。Map<String, PubWareBase> wareBaseMap = drugCodes.isEmpty() ? new HashMap<>() :pubWareBaseMapper.selectList(new LambdaQueryWrapper<PubWareBase>().in(PubWareBase::getWareCode, drugCodes)).stream().collect(Collectors.toMap(PubWareBase::getWareCode,Function.identity(),(existing, replacement) -> existing));List<WmWareDispenseBill> billsToSave = new ArrayList<>();   //准备插入到WmWareDispenseBill表中的记录List<WareInfoSplit> wareInfoList = new ArrayList<>();   //准备回传的接口数据// 3. 批量查询已存在的处方记录// map对象的键是rxno|rxSerialno的组合字符串,值为对应的处方对象。Map<String, WmWareDispenseBill> existingBillsMap = new HashMap<>();  //记录哪写处方以前创建过if (!rxNos.isEmpty()) {  //根据处方号和处方明细号,查询已有的处方记录List<WmWareDispenseBill> existingBills = this.list(new LambdaQueryWrapper<WmWareDispenseBill>().in(WmWareDispenseBill::getRxno, rxNos).in(WmWareDispenseBill::getRxSerialno, rxSerialNos));existingBills.forEach(bill ->existingBillsMap.put(bill.getRxno() + "|" + bill.getRxSerialno(), bill));}

        

1.2.3 对有扫码记录的处方单,我们区分了以下的几种情况。

        (1)如果此处方号是第一次扫描,则在下表内新增处方信息

        (2)如果此处方之前扫过,但是并没有扫描对应的追溯码,则处方表内有记录,且status为00,此时记录billid,返回该记录,并在高拍仪屏幕显示

        (3)如果 此处方之前扫过,且已经扫过追溯码并提交,则默认为该患者开方当天并未取药,此时应重新扫描追溯码并作废之前已扫追溯码

        (4)如果此处方之前扫过,且扫过追溯码,且已提交医保,则不返回任何回参。

for (Map<String, Object> item : details) {WareInfoSplit wareInfo = new WareInfoSplit(); //需上传数据String rxno = item.get("rxno").toString();String rxSerialno = item.get("rx_serialno").toString();String key = rxno + "|" + rxSerialno;String drugCode = item.get("ware_code").toString();PubWareBase pubWareBase = wareBaseMap.get(drugCode);// 跳过无药品信息的记录if (pubWareBase == null) {continue;}// 这个处方记录以前创建过if (existingBillsMap.containsKey(key)) {//获取其对应的处方记录对象WmWareDispenseBill existingBill = existingBillsMap.get(key);// 情况1: status = '00',这个已创建的记录仍是待扫码,if ("00".equals(existingBill.getStatus())) {//新增,现在这种情况,该记录可能是从视图抓取并插入进来的数据,要把他没有但是接口给的数据补充上LambdaUpdateWrapper<WmWareDispenseBill> updateWrapper = new LambdaUpdateWrapper<>();updateWrapper.eq(WmWareDispenseBill::getBillid, existingBill.getBillid()).set(WmWareDispenseBill::getOperator, username).set(WmWareDispenseBill::getOperateTime, new Date());if (!this.update(updateWrapper)) {throw new RuntimeException("更新处方部门信息失败,未找到对应记录或更新失败");}//创建最终回传的一个WareInfo对象wareInfo = createWareInfo_split(item, pubWareBase,departmentid);wareInfo.setBillid(existingBill.getBillid());wareInfoList.add(wareInfo); //添加到最终回传的数据中continue;}// 情况2: status = '10' 且 uploadStatus为null或'00',此时虽然已经扫过码,但是还没有上传医保,仍可以更改if ("10".equals(existingBill.getStatus()) &&(existingBill.getUploadStatus() == null || "00".equals(existingBill.getUploadStatus()))) {//把这条处方记录的状态值、上传状态值、操作员等信息通过billid来更新WmWareDispenseBill updateEntity = new WmWareDispenseBill();updateEntity.setBillid(existingBill.getBillid()); //billid取以前创建的billidupdateEntity.setStatus("00");updateEntity.setUploadStatus("00");updateEntity.setOperator(username);updateEntity.setOperateTime(new Date());//更新处方信息if (!this.updateById(updateEntity)) {throw new RuntimeException("处方单状态重置失败");}wareInfo = createWareInfo_split(item, pubWareBase,departmentid);wareInfo.setBillid(existingBill.getBillid());wareInfoList.add(wareInfo);//添加到最终回传的数据中continue;}// 情况3: status = '10' 且 uploadStatus = '10'if ("10".equals(existingBill.getStatus()) && "10".equals(existingBill.getUploadStatus())) {statusCode.set(4);return null; //此时不可更改}}// 程序运行到这,说明这个处方是新处方,所以需要准备需要新建的处方数据WmWareDispenseBill bill = createNewBill_n(item, wareBaseMap, username, "patientId", departmentid);billsToSave.add(bill);  //添加到准备插入处方表的集合中wareInfoList.add(createWareInfo_split(item, pubWareBase,departmentid));}// 程序运行到这,说明这个处方是新处方,所以需要准备需要新建的处方数据WmWareDispenseBill bill = createNewBill_n(item, wareBaseMap, username, "patientId", departmentid);billsToSave.add(bill);  //添加到准备插入处方表的集合中wareInfoList.add(createWareInfo_split(item, pubWareBase,departmentid));}// 5. 新处方if (!billsToSave.isEmpty()) { //需要插入新处方if (!this.saveBatch(billsToSave)) { //保存到处方表throw new RuntimeException("保存处方数据失败");}//遍历wareInfoList,为billid为null的记录设置正确的billidupdateWareInfoListWithBillIds_split(billsToSave, wareInfoList);}return wareInfoList;} catch (Exception e) {log.error("处方数据获取失败:", e);return null;}}

        这里解释下,情况2: status = '10' 且 uploadStatus为null或'00',此时虽然已经扫过码,但是还没有上传医保,也就是说无论是误扫还是隔夜取药,都认为是这种情况。此时并不在wm_ware_dispense_bill表中插入新的记录,而是沿用之前的记录,并且把状态值复原。

        这里用到的updateById是在 MyBatis(以及 MyBatis-Plus)中用于根据实体对象的主键(ID)更新对应的数据记录

  • 它会根据传入实体对象的 主键字段(通常是id 定位到数据库中对应的记录
  • 然后将实体对象中非空的字段值更新到数据库表中对应的字段

        情况3意味着该条记录已经上传医保,此时不可再重新扫码,因为已经被监管机构记录,因此扫描该处方单时是不会出现药品信息的。

1.2.4 如果wm_ware_dispense_bill表中没有接口回传的处方信息,则会用到下面的创建处方表记录的代码

//处方对象原先不存在时,创建一个新的WmWareDispenseBill对象private WmWareDispenseBill createNewBill_n(Map<String, Object> item, Map<String, PubWareBase> wareBaseMap,String username, String patientid, String departmentid) {WmWareDispenseBill bill = new WmWareDispenseBill();bill.setHospitalid(1L);bill.setDepartmentid(Long.valueOf(departmentid));bill.setBillType(item.get("type").toString());bill.setRxno(item.get("rxno").toString());bill.setRxSerialno(item.get("rx_serialno").toString());bill.setPatientName(item.get("patient_name").toString());String drugCode = item.get("ware_code").toString();PubWareBase pubWareBase = wareBaseMap.get(drugCode);bill.setWareId(pubWareBase != null ? pubWareBase.getWareid() : null);if (pubWareBase != null) {String description = String.format("[%s]%s(%s)/%s/%s/%s",pubWareBase.getWareCode(),pubWareBase.getFormalName(),pubWareBase.getWareName(),pubWareBase.getSpec(),pubWareBase.getUnitName(),pubWareBase.getManufacturer());bill.setDescription(description);}bill.setQuantity(Integer.parseInt(item.get("quantity").toString()));bill.setOperator(username);bill.setOperateTime(new Date());bill.setStatus("00");bill.setPatientid(patientid);try {String dateStr = item.get("rx_date").toString();Date date = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse(dateStr);bill.setRxDate(date);} catch (ParseException e) {System.out.println(e);}bill.setDoctorName(item.get("doctor_name").toString());bill.setSettlementno(item.get("mdtrt_id").toString());return bill;}

1.2.5 拆零子码的分配

        扫描处方时,会有些药是拆零药品,发药时按照1支1粒这种样式,此时储备相应的子码就很有必要。

        具体拆零子码如何进表以后再讲,这里只要知道一个拆零药品的追溯码会根据其拆零系数,在该表内拆分成相应数量的子码。

        有上面的子码,就可以在扫描处方单时,分配需要的子码:

private WareInfoSplit createWareInfo_split(Map<String, Object> item, PubWareBase pubWareBase,String departmentid) {WareInfoSplit wareInfo = new WareInfoSplit();if ("10".equals(pubWareBase.getSplitFlag()) && !"盒".equals(item.get("price_unit").toString())) {int quantitySplit = Integer.parseInt(item.get("quantity").toString());List<PubTracecodeSubcode> lockedSubcodes = pubTracecodeSubcodeMapper.selectList(new LambdaQueryWrapper<PubTracecodeSubcode>().eq(PubTracecodeSubcode::getTracecodePrefix, pubWareBase.getTracecodePrefix()).eq(PubTracecodeSubcode::getStatus, "00").eq(PubTracecodeSubcode::getLockedStatus, "00").eq(PubTracecodeSubcode::getDepartmentid, departmentid).orderByAsc(PubTracecodeSubcode::getCodeid) // 按固定顺序避免死锁.last("LIMIT " + quantitySplit + " FOR UPDATE") // 核心:锁定指定数量记录);// 检查是否锁定到足够数量 ===if (lockedSubcodes.size() < quantitySplit) {wareInfo.setEnoughFlag("10"); // 数量不足}else {// 更新锁定状态 ===List<Integer> codeIdsToLock = lockedSubcodes.stream().map(PubTracecodeSubcode::getCodeid).collect(Collectors.toList());pubTracecodeSubcodeMapper.update(null, new LambdaUpdateWrapper<PubTracecodeSubcode>().in(PubTracecodeSubcode::getCodeid, codeIdsToLock).set(PubTracecodeSubcode::getLockedStatus, "10"));// 设置分配结果wareInfo.setEnoughFlag("00");wareInfo.setSubcodeids(codeIdsToLock);wareInfo.setSplitTracecode(lockedSubcodes.get(0).getTracecode());}}wareInfo.setSplitFlag(pubWareBase.getSplitFlag());wareInfo.setWareid(pubWareBase.getWareid().intValue());wareInfo.setPatientName(item.get("patient_name").toString());wareInfo.setWareName(pubWareBase.getWareName());wareInfo.setSpec(pubWareBase.getSpec());wareInfo.setQuantity(Integer.parseInt(item.get("quantity").toString()));wareInfo.setManufacturer(pubWareBase.getManufacturer());wareInfo.setTracecodePrefix(pubWareBase.getTracecodePrefix());wareInfo.setScanFlag(pubWareBase.getScanFlag());return wareInfo;}

http://www.dtcms.com/a/342577.html

相关文章:

  • 【Linux系统】进程信号:信号的产生和保存
  • 使用EasyExcel 导出复杂的合并单元格
  • 第四届中国高校机器人实验教学创新大赛团队参赛总结
  • selenium一些进阶方法如何使用
  • 大模型0基础开发入门与实践:第11章 进阶:LangChain与外部工具调用
  • 打破传统课程模式,IP变现的创新玩法 | 创客匠人
  • 从零开始学 Selenium:浏览器驱动、元素定位与实战技巧
  • 微服务:现代软件架构的主流范式
  • Linux mmap内存映射
  • 集中式负载均衡 vs. 分布式负载均衡
  • 【赵渝强老师】Redis Cluster分布式集群
  • #千问海报大赛
  • 订单簿动力学与深度学习模型的融合大单识别与短期市场价格波动预测
  • Java多线程编程基础篇
  • 多级缓存一致性矩阵:ABP vNext 下的旁路 / 写穿 / 写回组合实战
  • Qt的moveToThread使用
  • SQL-leetcode—3451. 查找无效的 IP 地址
  • centos常用命令
  • Visual Studio Code (VS Code) 工作区配置文件的作用
  • CentOS7安装部署NexusRepository
  • 【Spring Cloud 微服务】2.守护神网关Gateway
  • 告别人工建模:AI 自动化 ETL 工具对比,数据 pipeline 搭建时间缩短 60% 的实践
  • 洛谷 P2656 采蘑菇-普及+/提高
  • k 均值聚类算法总结
  • 从入门到精通:Java设计模式——单例模式
  • 8.21IPSEC安全基础后篇,IKE工作过程
  • 【TWT】
  • 常德二院信创转型实战:全栈国产化提升医疗效率与安全
  • 嵌入式linux设备升级和sm32升级对比
  • 计算机视觉(opencv)实战六——图像形态学(腐蚀、膨胀、开运算、闭运算、梯度、顶帽、黑帽)