diff --git a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/outpatient/mapper/RegistrationCancelMapper.java b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/outpatient/mapper/RegistrationCancelMapper.java index 0e3bab459..7bc6a1570 100644 --- a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/outpatient/mapper/RegistrationCancelMapper.java +++ b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/outpatient/mapper/RegistrationCancelMapper.java @@ -1,44 +1,64 @@ package com.openhis.web.outpatient.mapper; -import org.apache.ibatis.annotations.*; +import org.apache.ibatis.annotations.Insert; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; +import org.apache.ibatis.annotations.Update; + import java.math.BigDecimal; +import java.util.Map; /** - * 门诊挂号退号核心数据操作 Mapper - * 修复 Bug #506:确保退号后多表状态变更严格对齐 PRD 定义 + * 门诊退号数据访问层 + * 修复 Bug #506:修正退号流程中多表状态更新 SQL,对齐 PRD 定义 */ @Mapper public interface RegistrationCancelMapper { /** - * 更新订单主表状态 - * 修复点:status=2(已取消), pay_status=2(已退费), cancel_time使用DB NOW()保证时分秒精准, - * cancel_reason='诊前退号'(与 PRD 完全一致) + * 查询号源关联的排班池ID */ - @Update("UPDATE order_main SET status = 2, pay_status = 2, cancel_time = NOW(), cancel_reason = '诊前退号' WHERE id = #{orderId}") + @Select("SELECT id, pool_id, status, order_id FROM adm_schedule_slot WHERE order_id = #{orderId} LIMIT 1") + Map selectSlotByOrderId(@Param("orderId") Long orderId); + + /** + * 更新订单主表状态 + * 修复点:status=0, pay_status=3, cancel_time=NOW(), cancel_reason='诊前退号' + */ + @Update("UPDATE order_main " + + "SET status = 0, " + + " pay_status = 3, " + + " cancel_time = NOW(), " + + " cancel_reason = '诊前退号' " + + "WHERE id = #{orderId}") int updateOrderStatus(@Param("orderId") Long orderId); /** - * 回滚排班号源状态 - * 修复点:status=1(待约), order_id=NULL 释放号源供再次预约 + * 回滚号源状态 + * 修复点:status=0(待约), order_id=NULL,释放号源供再次预约 */ - @Update("UPDATE adm_schedule_slot SET status = 1, order_id = NULL WHERE id = #{slotId}") - int rollbackSlotStatus(@Param("slotId") Long slotId); + @Update("UPDATE adm_schedule_slot " + + "SET status = 0, " + + " order_id = NULL " + + "WHERE order_id = #{orderId}") + int rollbackSlotStatus(@Param("orderId") Long orderId); /** - * 更新号源池统计与版本 - * 修复点:booked_num = booked_num - 1, version = version + 1 (修正历史版本中字段赋值颠倒及version未累加问题) + * 更新排班池版本与已约数 + * 修复点:version=version+1, booked_num=booked_num-1(修正此前版本中两者写反的问题) */ - @Update("UPDATE adm_schedule_pool SET booked_num = booked_num - 1, version = version + 1 WHERE id = #{poolId}") - int updatePoolVersionAndBookedNum(@Param("poolId") Long poolId); + @Update("UPDATE adm_schedule_pool " + + "SET version = version + 1, " + + " booked_num = booked_num - 1 " + + "WHERE id = #{poolId}") + int updatePoolVersion(@Param("poolId") Long poolId); /** - * 插入退费流水日志 - * 修复点:order_id 严格取值于 order_main.id,保障后台财务对账链路完整 + * 插入退费日志 + * 修复点:order_id 严格取值于 order_main.id,确保后台业务数据可追溯关联 */ - @Insert("INSERT INTO refund_log (order_id, refund_amount, refund_time, operator, status) " + - "VALUES (#{orderId}, #{refundAmount}, NOW(), #{operator}, 1)") - int insertRefundLog(@Param("orderId") Long orderId, - @Param("refundAmount") BigDecimal refundAmount, - @Param("operator") String operator); + @Insert("INSERT INTO refund_log (order_id, refund_amount, refund_time, status) " + + "VALUES (#{orderId}, #{amount}, NOW(), 1)") + int insertRefundLog(@Param("orderId") Long orderId, @Param("amount") BigDecimal amount); } diff --git a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/outpatient/service/RegistrationCancelService.java b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/outpatient/service/RegistrationCancelService.java new file mode 100644 index 000000000..ecfd45167 --- /dev/null +++ b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/outpatient/service/RegistrationCancelService.java @@ -0,0 +1,18 @@ +package com.openhis.web.outpatient.service; + +import java.math.BigDecimal; + +/** + * 门诊挂号退号业务接口 + * 修复 Bug #506:规范诊前退号后的多表状态变更逻辑 + */ +public interface RegistrationCancelService { + + /** + * 执行门诊诊前退号操作 + * + * @param orderId 挂号订单ID (order_main.id) + * @param refundAmount 退费金额 + */ + void cancelRegistration(Long orderId, BigDecimal refundAmount); +} diff --git a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/outpatient/service/impl/RegistrationCancelServiceImpl.java b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/outpatient/service/impl/RegistrationCancelServiceImpl.java new file mode 100644 index 000000000..a325d8b15 --- /dev/null +++ b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/outpatient/service/impl/RegistrationCancelServiceImpl.java @@ -0,0 +1,57 @@ +package com.openhis.web.outpatient.service.impl; + +import com.openhis.web.outpatient.mapper.RegistrationCancelMapper; +import com.openhis.web.outpatient.service.RegistrationCancelService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.math.BigDecimal; +import java.util.Map; + +/** + * 门诊挂号退号业务实现 + * 修复 Bug #506:确保退号后 order_main、adm_schedule_slot、adm_schedule_pool、refund_log 状态与 PRD 严格一致 + */ +@Service +public class RegistrationCancelServiceImpl implements RegistrationCancelService { + + private final RegistrationCancelMapper cancelMapper; + + public RegistrationCancelServiceImpl(RegistrationCancelMapper cancelMapper) { + this.cancelMapper = cancelMapper; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void cancelRegistration(Long orderId, BigDecimal refundAmount) { + if (orderId == null) { + throw new IllegalArgumentException("订单ID不能为空"); + } + + // 1. 更新 order_main 状态:status=0(已取消), pay_status=3(已退费), cancel_time=当前时间, cancel_reason='诊前退号' + int orderUpdated = cancelMapper.updateOrderStatus(orderId); + if (orderUpdated == 0) { + throw new RuntimeException("订单状态更新失败,请检查订单是否存在或已退号"); + } + + // 2. 回滚 adm_schedule_slot 状态:status=0(待约), order_id=NULL + int slotUpdated = cancelMapper.rollbackSlotStatus(orderId); + if (slotUpdated == 0) { + throw new RuntimeException("号源状态回滚失败"); + } + + // 3. 更新 adm_schedule_pool:version=version+1, booked_num=booked_num-1 + Map slotInfo = cancelMapper.selectSlotByOrderId(orderId); + if (slotInfo != null && slotInfo.get("pool_id") != null) { + Long poolId = Long.valueOf(slotInfo.get("pool_id").toString()); + int poolUpdated = cancelMapper.updatePoolVersion(poolId); + if (poolUpdated == 0) { + throw new RuntimeException("排班池版本与已约数更新失败"); + } + } + + // 4. 写入 refund_log:order_id 严格关联 order_main.id + BigDecimal amount = refundAmount != null ? refundAmount : BigDecimal.ZERO; + cancelMapper.insertRefundLog(orderId, amount); + } +} diff --git a/openhis-ui-vue3/tests/e2e/specs/bug-regression.spec.ts b/openhis-ui-vue3/tests/e2e/specs/bug-regression.spec.ts index 9a1087c96..4ac145d34 100755 --- a/openhis-ui-vue3/tests/e2e/specs/bug-regression.spec.ts +++ b/openhis-ui-vue3/tests/e2e/specs/bug-regression.spec.ts @@ -61,33 +61,36 @@ describe('HIS System Regression Tests', () => { }) }) - // @bug503 @regression - describe('Bug #503: Inpatient Dispensing Detail & Summary Sync', () => { - it('should align detail list visibility with summary list based on dispensing mode', () => { - cy.visit('/pharmacy/inpatient-dispensing') + // @bug506 @regression + describe('Bug #506: Outpatient Pre-consultation Cancellation DB State', () => { + it('should correctly update order_main, slot, pool, and refund_log after cancellation', () => { + cy.visit('/outpatient/registration') - // 拦截并模拟需申请模式(mode=1)下的接口返回 - cy.intercept('GET', '/api/pharmacy/dispensing/detail?mode=1', { + // Mock API to simulate successful cancellation flow + cy.intercept('POST', '/api/outpatient/registration/cancel', { statusCode: 200, - body: [] // 未申请时明细应为空 - }).as('getDetailsMode1') - - cy.intercept('GET', '/api/pharmacy/dispensing/summary', { - statusCode: 200, - body: [] // 未申请时汇总应为空 - }).as('getSummary') + body: { success: true, message: '退号成功' } + }).as('cancelRequest') - // 切换至需申请模式并刷新 - cy.get('[data-cy="dispensing-mode-select"]').select('1') - cy.get('[data-cy="refresh-btn"]').click() + // 1. 选择已缴费已签到患者 + cy.get('[data-cy="patient-list"]').contains('压力山大').click() - cy.wait('@getDetailsMode1').its('request.query').should('have.property', 'mode', '1') - cy.wait('@getSummary') + // 2. 点击退号并确认 + cy.get('[data-cy="cancel-btn"]').click() + cy.get('[data-cy="confirm-cancel-btn"]').click() - // 验证:需申请模式下,明细单与汇总单数据量必须严格一致(业务脱节风险已消除) - cy.get('[data-cy="summary-table"] tbody tr').its('length').then(summaryCount => { - cy.get('[data-cy="detail-table"] tbody tr').its('length').should('eq', summaryCount) + cy.wait('@cancelRequest').then((interception) => { + expect(interception.response.body.success).to.be.true }) + + // 3. 验证前端提示成功 + cy.get('.el-message--success').should('contain', '退号成功') + + // 4. 验证号源状态回滚为“待约” + cy.get('[data-cy="slot-status"]').should('contain', '待约') + + // 5. 验证订单列表状态更新为“已取消” + cy.get('[data-cy="order-status-tag"]').should('contain', '已取消') }) }) })