From d79690a3712d2c1656846a6e46229bbe1867fdc5 Mon Sep 17 00:00:00 2001 From: guanyu Date: Sat, 25 Apr 2026 20:00:58 +0800 Subject: [PATCH 1/3] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D#442=E6=89=8B?= =?UTF-8?q?=E6=9C=AF=E8=AE=A1=E8=B4=B9=E5=88=A0=E9=99=A4=E5=BE=85=E7=AD=BE?= =?UTF-8?q?=E5=8F=91=E8=80=97=E6=9D=90=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根因: handleDel方法中使用iProcedureService.listByIds(requestIds)错误, requestIds是WOR_DEVICE_REQUEST/WOR_SERVICE_REQUEST表主键, 不是CLI_PROCEDURE表主键。 修复: 改用iProcedureService.getProcedureRecords(requestIds, serviceTable) 通过request_id字段正确关联执行记录。 --- .../appservice/impl/NurseBillingAppService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/inhospitalnursestation/appservice/impl/NurseBillingAppService.java b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/inhospitalnursestation/appservice/impl/NurseBillingAppService.java index 8152b0d1..3c8bf7a9 100644 --- a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/inhospitalnursestation/appservice/impl/NurseBillingAppService.java +++ b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/inhospitalnursestation/appservice/impl/NurseBillingAppService.java @@ -665,7 +665,7 @@ public class NurseBillingAppService implements INurseBillingAppService { // 1. 校验:待删除项是否已收费,已收费则抛出异常阻止删除 checkDeletedDeviceChargeStatus(requestIds); // 软删除执行记录 - List procedureList = iProcedureService.listByIds(requestIds); + List procedureList = iProcedureService.getProcedureRecords(requestIds, serviceTable); List procedureIds = procedureList.stream().filter(Objects::nonNull) // 过滤掉null的Procedure对象 .map(Procedure::getId).filter(Objects::nonNull) // 过滤掉id为null的记录(按需添加) .collect(Collectors.toList()); From fc84fd61ab1d2daba7c822b86dd565103118de4e Mon Sep 17 00:00:00 2001 From: xunyu Date: Sat, 25 Apr 2026 20:04:48 +0800 Subject: [PATCH 2/3] =?UTF-8?q?fix(#437):=20=E6=95=B0=E6=8D=AE=E5=BA=93?= =?UTF-8?q?=E5=B1=82=E4=BF=AE=E5=A4=8D=E6=89=8B=E6=9C=AF=E8=AE=A1=E8=B4=B9?= =?UTF-8?q?=E9=87=8D=E5=A4=8D=E7=94=9F=E6=88=90=E6=94=B6=E8=B4=B9=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加复合唯一约束 uk_charge_item_encounter_service_product_source 防止同一就诊下同一来源服务+产品产生重复收费项 约束字段:encounter_id + service_table + service_id + product_table + product_id + generate_source_enum - 添加索引 idx_charge_item_generate_source_product 加速手术计费查询 - 添加索引 idx_charge_item_encounter_status 加速按就诊状态查询 - 提供重复数据检测SQL供运维排查历史数据 根因分析: 1. adm_charge_item 表无任何唯一约束,同一收费项可被多次插入 2. 前端手术计费页面使用 sourceBillNo 过滤,但该字段不存在于 ChargeItem 实体中 3. 多处代码路径(SurgeryAppServiceImpl/RequestFormManageAppServiceImpl)均可生成收费项 4. 缺少数据库层面的兜底防护 Author: xunyu --- .../202604251957_check_437_duplicate_charge_items.sql | 59 +++++++++++++++++++ ..._fix_437_add_unique_constraint_adm_charge_item.sql | 28 +++++++++ 2 files changed, 87 insertions(+) create mode 100644 sql/迁移记录-DB变更记录/202604251957_check_437_duplicate_charge_items.sql create mode 100644 sql/迁移记录-DB变更记录/202604251957_fix_437_add_unique_constraint_adm_charge_item.sql diff --git a/sql/迁移记录-DB变更记录/202604251957_check_437_duplicate_charge_items.sql b/sql/迁移记录-DB变更记录/202604251957_check_437_duplicate_charge_items.sql new file mode 100644 index 00000000..6c9e5524 --- /dev/null +++ b/sql/迁移记录-DB变更记录/202604251957_check_437_duplicate_charge_items.sql @@ -0,0 +1,59 @@ +-- Bug #437 数据排查:查询手术计费中的重复收费项 +-- 用法:在生产环境执行前先在测试环境验证 +-- 作者:荀彧 + +-- 1. 查找手术计费中的重复收费项(同一手术+同一产品产生多条记录) +SELECT + encounter_id AS "就诊ID", + product_table AS "产品表", + product_id AS "产品ID", + service_table AS "服务表", + service_id AS "服务ID", + generate_source_enum AS "生成来源", + COUNT(*) AS "重复次数", + STRING_AGG(id::TEXT, ',') AS "收费项ID列表", + STRING_AGG(status_enum::TEXT, ',') AS "状态列表", + STRING_AGG(total_price::TEXT, ',') AS "金额列表", + STRING_AGG(create_time::TEXT, ',') AS "创建时间列表" +FROM adm_charge_item +WHERE delete_flag = '0' + AND generate_source_enum IN (1, 2) -- 医生开立(1) 或 护士划价(2) + AND product_table IN ('cli_surgery', 'wor_device_request', 'wor_activity_definition') +GROUP BY encounter_id, product_table, product_id, service_table, service_id, generate_source_enum +HAVING COUNT(*) > 1 +ORDER BY COUNT(*) DESC; + +-- 2. 查找同一手术单号下是否有重复的收费项 +SELECT + ci.product_id AS "手术ID", + cs.surgery_no AS "手术单号", + cs.surgery_name AS "手术名称", + COUNT(ci.id) AS "收费项数量", + STRING_AGG(ci.id::TEXT || '(' || ci.status_enum || ')' || '=' || ci.total_price, '; ') AS "收费项详情" +FROM adm_charge_item ci +LEFT JOIN cli_surgery cs ON ci.product_id = cs.id +WHERE ci.delete_flag = '0' + AND ci.product_table = 'cli_surgery' + AND ci.generate_source_enum = 1 +GROUP BY ci.product_id, cs.surgery_no, cs.surgery_name +HAVING COUNT(ci.id) > 1 +ORDER BY COUNT(ci.id) DESC; + +-- 3. 统计重复数据量(用于评估影响范围) +SELECT + generate_source_enum AS "生成来源(1=医生开立,2=护士划价)", + product_table AS "产品表", + COUNT(*) AS "重复组数", + SUM(cnt - 1) AS "多余记录数" +FROM ( + SELECT + generate_source_enum, + product_table, + COUNT(*) AS cnt + FROM adm_charge_item + WHERE delete_flag = '0' + GROUP BY encounter_id, product_table, product_id, service_table, service_id, generate_source_enum + HAVING COUNT(*) > 1 +) sub +GROUP BY generate_source_enum, product_table +ORDER BY SUM(cnt - 1) DESC; diff --git a/sql/迁移记录-DB变更记录/202604251957_fix_437_add_unique_constraint_adm_charge_item.sql b/sql/迁移记录-DB变更记录/202604251957_fix_437_add_unique_constraint_adm_charge_item.sql new file mode 100644 index 00000000..bdca923f --- /dev/null +++ b/sql/迁移记录-DB变更记录/202604251957_fix_437_add_unique_constraint_adm_charge_item.sql @@ -0,0 +1,28 @@ +-- Bug #437 修复:手术计费重复生成三条收费记录 +-- 根因:adm_charge_item 表缺少唯一约束,同一手术/耗材的收费项可被重复插入 +-- 影响范围:手术计费、护士划价等所有生成收费项的场景 +-- 作者:荀彧 +-- 日期:2026-04-25 + +-- 1. 添加复合唯一约束:防止同一就诊下同一来源服务+产品产生重复收费项 +-- 约束字段:就诊ID + 医疗服务表 + 医疗服务ID + 产品表 + 产品ID + 账单生成来源 +-- 这些字段组合唯一确定一笔收费项,重复插入将被数据库拒绝 +ALTER TABLE adm_charge_item +ADD CONSTRAINT uk_charge_item_encounter_service_product_source +UNIQUE (encounter_id, service_table, service_id, product_table, product_id, generate_source_enum); + +-- 2. 添加索引:加速手术计费查询(按手术单号过滤) +-- 前端手术计费页面使用 generate_source_enum=2 过滤手术计费项 +CREATE INDEX idx_charge_item_generate_source_product +ON adm_charge_item (generate_source_enum, product_table, product_id) +WHERE delete_flag = '0'; + +-- 3. 添加索引:加速按就诊ID+状态查询收费项 +CREATE INDEX idx_charge_item_encounter_status +ON adm_charge_item (encounter_id, status_enum) +WHERE delete_flag = '0'; + +-- DOWN 迁移(回滚脚本) +-- ALTER TABLE adm_charge_item DROP CONSTRAINT IF EXISTS uk_charge_item_encounter_service_product_source; +-- DROP INDEX IF EXISTS idx_charge_item_generate_source_product; +-- DROP INDEX IF EXISTS idx_charge_item_encounter_status; From 290e8f8f15dbb9229ed447e7cca1798c40bfa920 Mon Sep 17 00:00:00 2001 From: guanyu Date: Sat, 25 Apr 2026 20:10:25 +0800 Subject: [PATCH 3/3] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D#445=E9=97=A8?= =?UTF-8?q?=E8=AF=8A=E6=89=8B=E6=9C=AF=E5=BE=85=E7=94=9F=E6=88=90=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E6=9C=AA=E5=89=94=E9=99=A4=E5=B7=B2=E7=94=9F=E6=88=90?= =?UTF-8?q?=E5=8C=BB=E5=98=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根因: getSurgeryPage查询只查cli_surgery表,没有关联wor_service_request表检查是否已生成医嘱。 所有手术都会显示在待生成列表中,不管是否已处理。 修复: 在getSurgeryPage查询中LEFT JOIN wor_service_request表, 通过sr.id IS NULL过滤掉已生成医嘱的手术。 --- .../main/resources/mapper/clinicalmanage/SurgeryMapper.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openhis-server-new/openhis-application/src/main/resources/mapper/clinicalmanage/SurgeryMapper.xml b/openhis-server-new/openhis-application/src/main/resources/mapper/clinicalmanage/SurgeryMapper.xml index 5239fbd4..17c94402 100644 --- a/openhis-server-new/openhis-application/src/main/resources/mapper/clinicalmanage/SurgeryMapper.xml +++ b/openhis-server-new/openhis-application/src/main/resources/mapper/clinicalmanage/SurgeryMapper.xml @@ -331,8 +331,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" ) t WHERE rn = 1 ) pi ON s.patient_id = pi.patient_id + + LEFT JOIN wor_service_request sr ON sr.activity_id = s.id AND sr.delete_flag = '0' AND sr.category_enum = 4 s.delete_flag = '0' + + AND sr.id IS NULL