From fa06a52d7182b607948d8365fddf63f7ceb2ebaa Mon Sep 17 00:00:00 2001 From: HuangShun <148689675+huabuweixin@users.noreply.github.com> Date: Tue, 3 Feb 2026 10:31:58 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E9=9C=80=E6=B1=8285=20=E9=97=A8=E8=AF=8A?= =?UTF-8?q?=E6=8C=82=E5=8F=B7-=E3=80=8B=E9=A2=84=E7=BA=A6=E5=8F=B7?= =?UTF-8?q?=E6=BA=90=E5=B7=B2=E7=BC=B4=E8=B4=B9=E7=AD=BE=E5=88=B0=E6=9C=AA?= =?UTF-8?q?=E7=9C=8B=E8=AF=8A=E8=BF=9B=E8=A1=8C=E9=80=80=E5=8F=B7=EF=BC=9B?= =?UTF-8?q?=E5=BB=BA=E7=AB=8B=E9=80=80=E8=B4=B9=E8=A1=A8=EF=BC=8C=E5=9C=A8?= =?UTF-8?q?=E9=80=80=E8=AF=8A=E6=97=B6=E5=B0=86=E7=9B=B8=E5=BA=94=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E6=8F=92=E5=85=A5=E5=88=B0=E8=A1=A8=E4=B8=AD=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../OutpatientRegistrationAppServiceImpl.java | 137 +++++++++++++++++- .../openhis/financial/domain/RefundLog.java | 127 ++++++++++++++++ .../financial/mapper/RefundLogMapper.java | 10 ++ .../financial/service/IRefundLogService.java | 10 ++ .../service/impl/RefundLogServiceImpl.java | 14 ++ 5 files changed, 297 insertions(+), 1 deletion(-) create mode 100644 openhis-server-new/openhis-domain/src/main/java/com/openhis/financial/domain/RefundLog.java create mode 100644 openhis-server-new/openhis-domain/src/main/java/com/openhis/financial/mapper/RefundLogMapper.java create mode 100644 openhis-server-new/openhis-domain/src/main/java/com/openhis/financial/service/IRefundLogService.java create mode 100644 openhis-server-new/openhis-domain/src/main/java/com/openhis/financial/service/impl/RefundLogServiceImpl.java diff --git a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/chargemanage/appservice/impl/OutpatientRegistrationAppServiceImpl.java b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/chargemanage/appservice/impl/OutpatientRegistrationAppServiceImpl.java index 78623cba..6e65d769 100644 --- a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/chargemanage/appservice/impl/OutpatientRegistrationAppServiceImpl.java +++ b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/chargemanage/appservice/impl/OutpatientRegistrationAppServiceImpl.java @@ -10,16 +10,21 @@ import com.core.common.utils.MessageUtils; import com.core.common.utils.SecurityUtils; import com.core.common.utils.StringUtils; import com.core.common.utils.bean.BeanUtils; +import com.core.common.core.domain.entity.SysRole; +import com.core.common.core.domain.model.LoginUser; import com.openhis.administration.domain.*; import com.openhis.administration.mapper.PatientMapper; import com.openhis.administration.service.*; import com.openhis.common.constant.CommonConstants; import com.openhis.common.constant.PromptMsgConstant; import com.openhis.common.enums.*; +import com.openhis.common.enums.ybenums.YbPayment; import com.openhis.common.utils.EnumUtils; import com.openhis.common.utils.HisPageUtils; import com.openhis.common.utils.HisQueryUtils; import com.openhis.financial.domain.PaymentReconciliation; +import com.openhis.financial.domain.RefundLog; +import com.openhis.financial.service.IRefundLogService; import com.openhis.web.basicservice.dto.HealthcareServiceDto; import com.openhis.web.basicservice.mapper.HealthcareServiceBizMapper; import com.openhis.web.chargemanage.appservice.IOutpatientRegistrationAppService; @@ -40,10 +45,14 @@ import org.springframework.stereotype.Service; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; +import java.math.BigDecimal; import java.time.LocalDate; +import java.time.LocalDateTime; import java.util.*; import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; /** * 门诊挂号 应用实现类 @@ -85,6 +94,9 @@ public class OutpatientRegistrationAppServiceImpl implements IOutpatientRegistra @Resource TriageCandidateExclusionService triageCandidateExclusionService; + @Resource + IRefundLogService iRefundLogService; + /** * 门诊挂号 - 查询患者信息 * @@ -260,7 +272,7 @@ public class OutpatientRegistrationAppServiceImpl implements IOutpatientRegistra R result = iPaymentRecService.cancelRegPayment(cancelPaymentDto); PaymentReconciliation paymentRecon = null; - if (PaymentReconciliation.class.isAssignableFrom(result.getData().getClass())) { + if (result.getData() != null && PaymentReconciliation.class.isAssignableFrom(result.getData().getClass())) { paymentRecon = (PaymentReconciliation)result.getData(); } if (paymentRecon != null) { @@ -275,6 +287,9 @@ public class OutpatientRegistrationAppServiceImpl implements IOutpatientRegistra } } + // 记录退号日志 + recordRefundLog(cancelRegPaymentDto, byId, result, paymentRecon); + // 2025/05/05 该处保存费用项后,会通过统一收费处理进行收费 return R.ok(paymentRecon, MessageUtils.createMessage(PromptMsgConstant.Common.M00004, new Object[] {"退号"})); } @@ -394,4 +409,124 @@ public class OutpatientRegistrationAppServiceImpl implements IOutpatientRegistra return R.ok(null, "补打挂号成功"); } + /** + * 记录退号日志 + * + * @param cancelRegPaymentDto 退号请求对象 + * @param encounter 就诊信息 + * @param result 退号结果 + * @param paymentRecon 支付对账信息 + */ + @Transactional(propagation = Propagation.REQUIRES_NEW) + public void recordRefundLog(CancelRegPaymentDto cancelRegPaymentDto, + Encounter encounter, + R result, + PaymentReconciliation paymentRecon) { + RefundLog refundLog = new RefundLog(); + try { + // 1. 订单ID(唯一) + String orderId = String.valueOf(cancelRegPaymentDto.getEncounterId()); + refundLog.setOrderId(orderId); + + // 已存在则不重复插入(防止唯一约束异常) + long exist = iRefundLogService.lambdaQuery() + .eq(RefundLog::getOrderId, orderId) + .count(); + if (exist > 0) { + log.warn("退号日志已存在,orderId={}", orderId); + return; + } + + // 2. 患者信息 + if (encounter != null) { + refundLog.setPatientId(String.valueOf(encounter.getPatientId())); + Patient patient = patientMapper.selectById(encounter.getPatientId()); + refundLog.setPatientName(patient != null ? patient.getName() : "未知"); + } else { + refundLog.setPatientId("0"); + refundLog.setPatientName("未知"); + } + + // 3. 金额 + // 优先使用paymentRecon中的displayAmount,如果没有则尝试使用cancelRegPaymentDto中的displayAmount + BigDecimal refundAmount = BigDecimal.ZERO; + if (paymentRecon != null) { + // 使用退号后生成的paymentRecon的实际金额(这个金额应该是负数) + refundAmount = paymentRecon.getDisplayAmount() != null + ? paymentRecon.getDisplayAmount().abs() // 取绝对值,因为退费金额通常显示为正数 + : BigDecimal.ZERO; + } else if (cancelRegPaymentDto.getDisplayAmount() != null) { + refundAmount = cancelRegPaymentDto.getDisplayAmount(); + } + refundLog.setRefundAmount(refundAmount); + + // 4. 原因 & 类型 + refundLog.setRefundReason( + StringUtils.isNotEmpty(cancelRegPaymentDto.getReason()) + ? cancelRegPaymentDto.getReason() + : "诊前退号-已缴费签到未就诊" + ); + refundLog.setRefundType("FULL"); + + // 5. 退款方式 + String refundMethod = "UNKNOWN"; + if (cancelRegPaymentDto.getPaymentDetails() != null + && !cancelRegPaymentDto.getPaymentDetails().isEmpty()) { + Integer payEnum = cancelRegPaymentDto.getPaymentDetails().get(0).getPayEnum(); + if (payEnum != null) { + YbPayment ybPayment = YbPayment.getByValue(payEnum); + refundMethod = (ybPayment != null ? ybPayment.getInfo() : payEnum.toString()); + } + } + refundLog.setRefundMethod(refundMethod); + + // 6. 原交易号 + refundLog.setOriginalTradeNo( + paymentRecon != null ? paymentRecon.getPaymentNo() : null + ); + + // 7. 时间 + refundLog.setRefundTime(LocalDateTime.now()); + + // 8. 操作人 + LoginUser loginUser = SecurityUtils.getLoginUser(); + if (loginUser != null) { + refundLog.setOpUserId(String.valueOf(loginUser.getUserId())); + refundLog.setOpUserName(loginUser.getUsername()); + List roles = loginUser.getRoleList(); + refundLog.setOpRole( + roles != null && !roles.isEmpty() ? roles.get(0).getRoleName() : "SYSTEM" + ); + } else { + refundLog.setOpUserId("0"); + refundLog.setOpUserName("SYSTEM"); + refundLog.setOpRole("SYSTEM"); + } + + // 9. 状态 + if (result != null && result.getCode() == 200) { + refundLog.setState(1); + } else { + refundLog.setState(0); + refundLog.setFailReason(result != null ? result.getMsg() : "未知错误"); + } + + // 10. 创建时间 + refundLog.setCreatedAt(LocalDateTime.now()); + refundLog.setUpdatedAt(LocalDateTime.now()); + + // 11. 保存 + boolean saved = iRefundLogService.save(refundLog); + if (!saved) { + throw new RuntimeException("退号日志保存失败"); + } + + log.info("退号日志入库成功, orderId={}", orderId); + + } catch (Exception e) { + log.error("退号日志入库失败,数据={}", refundLog, e); + throw e; // 让事务感知(你也可以只记录不抛) + } + } + } diff --git a/openhis-server-new/openhis-domain/src/main/java/com/openhis/financial/domain/RefundLog.java b/openhis-server-new/openhis-domain/src/main/java/com/openhis/financial/domain/RefundLog.java new file mode 100644 index 00000000..c90ec346 --- /dev/null +++ b/openhis-server-new/openhis-domain/src/main/java/com/openhis/financial/domain/RefundLog.java @@ -0,0 +1,127 @@ +package com.openhis.financial.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import java.io.Serializable; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import lombok.Data; + +/** + * 退号日志表 + */ +@TableName(value = "refund_log") +@Data +public class RefundLog implements Serializable { + /** + * 主键ID + */ + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + /** + * 订单ID + */ + @TableField(value = "order_id") + private String orderId; + + /** + * 患者ID + */ + @TableField(value = "patient_id") + private String patientId; + + /** + * 患者姓名 + */ + @TableField(value = "patient_name") + private String patientName; + + /** + * 退款金额 + */ + @TableField(value = "refund_amount") + private BigDecimal refundAmount; + + /** + * 退款原因 + */ + @TableField(value = "refund_reason") + private String refundReason; + + /** + * 退款类型(FULL-全额退号,PART-部分退号) + */ + @TableField(value = "refund_type") + private String refundType; + + /** + * 退款方式 + */ + @TableField(value = "refund_method") + private String refundMethod; + + /** + * 原交易流水号 + */ + @TableField(value = "original_trade_no") + private String originalTradeNo; + + /** + * 退款交易流水号 + */ + @TableField(value = "refund_trade_no") + private String refundTradeNo; + + /** + * 退款时间 + */ + @TableField(value = "refund_time") + private LocalDateTime refundTime; + + /** + * 操作用户ID + */ + @TableField(value = "op_user_id") + private String opUserId; + + /** + * 操作用户名 + */ + @TableField(value = "op_user_name") + private String opUserName; + + /** + * 操作角色 + */ + @TableField(value = "op_role") + private String opRole; + + /** + * 状态(0-失败,1-成功,2-处理中) + */ + @TableField(value = "state") + private Integer state; + + /** + * 失败原因 + */ + @TableField(value = "fail_reason") + private String failReason; + + /** + * 创建时间 + */ + @TableField(value = "created_at") + private LocalDateTime createdAt; + + /** + * 更新时间 + */ + @TableField(value = "updated_at") + private LocalDateTime updatedAt; + + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/openhis-server-new/openhis-domain/src/main/java/com/openhis/financial/mapper/RefundLogMapper.java b/openhis-server-new/openhis-domain/src/main/java/com/openhis/financial/mapper/RefundLogMapper.java new file mode 100644 index 00000000..731dd82c --- /dev/null +++ b/openhis-server-new/openhis-domain/src/main/java/com/openhis/financial/mapper/RefundLogMapper.java @@ -0,0 +1,10 @@ +package com.openhis.financial.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.openhis.financial.domain.RefundLog; + +/** + * 退号日志 Mapper接口 + */ +public interface RefundLogMapper extends BaseMapper { +} \ No newline at end of file diff --git a/openhis-server-new/openhis-domain/src/main/java/com/openhis/financial/service/IRefundLogService.java b/openhis-server-new/openhis-domain/src/main/java/com/openhis/financial/service/IRefundLogService.java new file mode 100644 index 00000000..3cf9ee38 --- /dev/null +++ b/openhis-server-new/openhis-domain/src/main/java/com/openhis/financial/service/IRefundLogService.java @@ -0,0 +1,10 @@ +package com.openhis.financial.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.openhis.financial.domain.RefundLog; + +/** + * 退号日志 Service接口 + */ +public interface IRefundLogService extends IService { +} \ No newline at end of file diff --git a/openhis-server-new/openhis-domain/src/main/java/com/openhis/financial/service/impl/RefundLogServiceImpl.java b/openhis-server-new/openhis-domain/src/main/java/com/openhis/financial/service/impl/RefundLogServiceImpl.java new file mode 100644 index 00000000..d8f2381a --- /dev/null +++ b/openhis-server-new/openhis-domain/src/main/java/com/openhis/financial/service/impl/RefundLogServiceImpl.java @@ -0,0 +1,14 @@ +package com.openhis.financial.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.openhis.financial.domain.RefundLog; +import com.openhis.financial.mapper.RefundLogMapper; +import com.openhis.financial.service.IRefundLogService; +import org.springframework.stereotype.Service; + +/** + * 退号日志 Service实现类 + */ +@Service +public class RefundLogServiceImpl extends ServiceImpl implements IRefundLogService { +} \ No newline at end of file From 897afd4da2588952931c0aa6fc43c53c6a5246a6 Mon Sep 17 00:00:00 2001 From: HuangShun <148689675+huabuweixin@users.noreply.github.com> Date: Tue, 3 Feb 2026 14:55:13 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E4=BF=AE=E5=A4=8DBug130=20=E3=80=90?= =?UTF-8?q?=E9=97=A8=E8=AF=8A=E6=8C=82=E5=8F=B7=E3=80=91-=E3=80=8B?= =?UTF-8?q?=E3=80=90=E6=96=B0=E5=A2=9E=E6=82=A3=E8=80=85=E3=80=91=E6=80=A7?= =?UTF-8?q?=E5=88=AB=E8=AE=BE=E7=BD=AE=E6=9C=892=E4=B8=AA=EF=BC=8C?= =?UTF-8?q?=E5=A4=9A=E4=BA=86=E4=B8=80=E4=B8=AA=EF=BC=9B=E5=B0=86=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2=E5=9B=9E=E9=80=80=E8=87=B3=E9=9C=80=E6=B1=8237=20?= =?UTF-8?q?=E9=97=A8=E8=AF=8A=E6=8C=82=E5=8F=B7-=E3=80=8B=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E6=82=A3=E8=80=85=20/=E9=A1=B5=E9=9D=A2=EF=BC=8C?= =?UTF-8?q?=E5=B9=B6=E5=AE=9E=E7=8E=B0=E6=82=A3=E8=80=85=E6=96=B0=E5=BB=BA?= =?UTF-8?q?=E6=82=A3=E8=80=85=E6=A1=A3=E6=A1=88=E5=B9=B4=E9=BE=84=E6=A0=A1?= =?UTF-8?q?=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/patientAddDialog.vue | 278 ++++++++++++------ 1 file changed, 182 insertions(+), 96 deletions(-) diff --git a/openhis-ui-vue3/src/views/charge/outpatientregistration/components/patientAddDialog.vue b/openhis-ui-vue3/src/views/charge/outpatientregistration/components/patientAddDialog.vue index 1ef037d7..ff0780ec 100644 --- a/openhis-ui-vue3/src/views/charge/outpatientregistration/components/patientAddDialog.vue +++ b/openhis-ui-vue3/src/views/charge/outpatientregistration/components/patientAddDialog.vue @@ -10,58 +10,8 @@ - - - - - {{ item.info }} - - - - - - - - - {{ dict.label }} - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - @@ -135,7 +145,8 @@ - + + @@ -262,7 +273,7 @@ - + { // 监护人信息条件验证函数 const validateGuardianInfo = (rule, value, callback) => { - // 只有当年龄小于规定年龄时才验证监护人信息 - if (form.value.age) { - // 提取年龄数字部分 - const ageMatch = form.value.age.toString().match(/\d+/); - if (ageMatch) { - const age = parseInt(ageMatch[0]); - const guardianAgeValue = parseInt(props.guardianAge) || 16; - // 如果年龄小于规定年龄,监护人信息必须填写 - if (age < guardianAgeValue && !value) { - return callback(new Error(`年龄小于${guardianAgeValue}岁的患者必须填写监护人信息`)); + // 从系统配置获取监护人规定年龄 + getConfigKey('guardianAge').then(res => { + if (res.code === 200) { + const guardianAgeValue = parseInt(res.data) || 16; // 默认值为16 + + // 提取年龄数字部分 + const ageMatch = form.value.age.toString().match(/\d+/); + if (ageMatch) { + const age = parseInt(ageMatch[0]); + // 如果年龄小于规定年龄,监护人信息必须填写 + if (age < guardianAgeValue && !value) { + return callback(new Error(`年龄小于${guardianAgeValue}岁的患者必须填写监护人信息`)); + } } } - } - callback(); + callback(); // 如果获取不到配置或出现错误,仍然通过验证 + }).catch(error => { + console.error('获取监护人规定年龄配置失败:', error); + callback(); // 获取配置失败时也通过验证 + }); } const data = reactive({ isViewMode: false, form: { - typeCode: '01', - tempFlag: '1', - // genderEnum: 0, + typeCode: '08', // 证件类别,默认为'08'(就诊卡) + birthDate: undefined, + age: undefined, + hukouAddressSelect: undefined, + hukouAddress: undefined, + postalCode: undefined, + companyAddress: undefined, + patientDerived: undefined, }, rules: { name: [{ required: true, message: '姓名不能为空', trigger: 'change' }, @@ -616,7 +639,18 @@ const data = reactive({ genderEnum: [{ required: true, message: '请选择性别', trigger: 'change' }], age: [{ required: true, message: '年龄不能为空', trigger: 'change' }], phone: [{ required: true, message: '联系方式不能为空', trigger: 'change' }], - idCard: [{ required: true, message: '证件号不能为空', trigger: 'change' }], + identifierNo: [{ required: true, message: '就诊卡号不能为空', trigger: 'change' }], + idCard: [ + { required: false, message: '证件号码不能为空', trigger: 'change' }, + { validator: validateIdCard, trigger: 'blur' }, + { validator: validateUniquePatient, trigger: 'blur' } + ], + birthDate: [{ required: false, message: '请选择出生日期', trigger: 'change' }], + // 监护人信息条件验证规则 + guardianName: [{ validator: validateGuardianInfo, trigger: 'blur' }], + guardianRelation: [{ validator: validateGuardianInfo, trigger: 'blur' }], + guardianPhone: [{ validator: validateGuardianInfo, trigger: 'blur' }], + guardianIdNo: [{ validator: validateGuardianInfo, trigger: 'blur' }], }, }); @@ -1271,22 +1305,74 @@ function cancel() { visible.value = false; reset(); } -// 身份证号失去焦点获取年龄和性别 -const onBlur = () => { - if (form.value.typeCode === '01') { - const info = getGenderAndAge(form.value.idCard || ''); - form.value.age = info.age; - form.value.genderEnum = info.gender; +// 处理出生日期变化,自动计算年龄 +function handleBirthDateChange() { + if (form.value.birthDate) { + const birthDate = new Date(form.value.birthDate); + const today = new Date(); + + let age = today.getFullYear() - birthDate.getFullYear(); + const monthDiff = today.getMonth() - birthDate.getMonth(); + + // 计算精确年龄 + if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) { + age--; + } + + form.value.age = age; } -}; -//切换证件类型 -const typeChange = () => { - if (form.value.typeCode === '01') { - const info = getGenderAndAge(form.value.idCard || ''); - form.value.age = info.age; - form.value.genderEnum = info.gender; +} + +// 处理年龄输入,自动计算出生日期 +function handleAgeInput() { + // 提取数字部分 + const ageMatch = form.value.age.match(/\d+/); + if (ageMatch) { + const age = parseInt(ageMatch[0]); + // 移除非数字字符,保留数字和可能的单位 + form.value.age = age ; + + // 计算出生日期 + const today = new Date(); + const birthYear = today.getFullYear() - age; + const birthMonth = today.getMonth(); + const birthDay = today.getDate(); + + const birthDate = new Date(birthYear, birthMonth, birthDay); + + // 格式化为YYYY-MM-DD + const formattedBirthDate = birthDate.toISOString().split('T')[0]; + form.value.birthDate = formattedBirthDate; } -}; +} +watch( + () => form.value.idCard, + (newIdCard) => { + if (newIdCard && newIdCard.length === 18) { + const birthYear = parseInt(newIdCard.substring(6, 10)); + const birthMonth = parseInt(newIdCard.substring(10, 12)); + const birthDay = parseInt(newIdCard.substring(12, 14)); + // 设置出生日期 + form.value.birthDate = `${birthYear}-${birthMonth.toString().padStart(2, '0')}-${birthDay.toString().padStart(2, '0')}`; + const today = new Date(); + const currentYear = today.getFullYear(); + const currentMonth = today.getMonth() + 1; + const currentDay = today.getDate(); + + let age = currentYear - birthYear; + + // 如果当前月份小于出生月份,或者月份相同但当前日期小于出生日期,则年龄减1 + if ( + currentMonth < birthMonth || + (currentMonth === birthMonth && currentDay < birthDay) + ) { + age--; + } + + form.value.age = age ; + } + } +); // 设置查看模式 function setViewMode(isView) { isViewMode.value = isView;