830 【收费工作站-住院登记】选择一名患者进行登记时入院病区下拉框无数据回显

819 【 门诊收费工作站】打开收费详情查询会出现报错:No static resource payment/bill/page for request '/healthlink-his/payment/bill/page'.
This commit is contained in:
wangjian963
2026-06-26 16:38:35 +08:00
parent b278ad92b2
commit bbe9bd7ef5
11 changed files with 416 additions and 72 deletions

View File

@@ -521,9 +521,9 @@ public class CommonServiceImpl implements ICommonService {
}
}
// 方式 3如果仍然没有病区且 currentOrgId 为空,尝试获取所有病区
if (wardList.isEmpty() && currentOrgId == null) {
log.info("getPractitionerWard - 尝试获取所有病区");
// 方式 3如果仍然没有病区尝试获取所有活动病区
if (wardList.isEmpty()) {
log.info("getPractitionerWard - 未查到指定病区,获取所有病区");
List<Location> allWards = locationService.getWardList(null);
log.info("getPractitionerWard - 所有病区数:{}", allWards != null ? allWards.size() : 0);
if (allWards != null && !allWards.isEmpty()) {

View File

@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.core.common.core.domain.R;
import com.core.common.enums.DelFlag;
import com.core.common.exception.ServiceException;
import com.core.common.utils.*;
import com.core.common.utils.bean.BeanUtils;
@@ -87,6 +88,12 @@ public class InHospitalRegisterAppServiceImpl implements IInHospitalRegisterAppS
@Resource
private IChargeItemService iChargeItemService;
@Resource
private ILocationService iLocationService;
@Resource
private IOrganizationService iOrganizationService;
/**
* 门诊医生开住院申请
*
@@ -357,17 +364,38 @@ public class InHospitalRegisterAppServiceImpl implements IInHospitalRegisterAppS
*/
@Override
public List<LocationDto> getWardList(Long orgId) {
List<Location> wardList = inHospitalRegisterAppMapper.selectWardList(orgId, LocationForm.WARD.getValue(),
LocationStatus.ACTIVE.getValue());
// 2. 转换为 LocationDto逻辑与原代码完全一致
List<LocationDto> locationDtoList = new ArrayList<>();
for (Location location : wardList) {
LocationDto locationDto = new LocationDto();
BeanUtils.copyProperties(location, locationDto);
locationDtoList.add(locationDto);
Set<Long> orgIds = new LinkedHashSet<>();
if (orgId != null) {
orgIds.add(orgId);
// 通过 busNo 层级查找所有子孙科室busNo 用 "." 分隔层级,如 "1" → "1.1" → "1.1.1"
Organization org = iOrganizationService.getById(orgId);
if (org != null && StringUtils.isNotEmpty(org.getBusNo())) {
List<Organization> children = iOrganizationService.list(
new LambdaQueryWrapper<Organization>()
.likeRight(Organization::getBusNo, org.getBusNo() + ".")
.eq(Organization::getDeleteFlag, DelFlag.NO.getCode()));
for (Organization child : children) {
orgIds.add(child.getId());
}
}
}
// 查询所有关联科室下的病区
List<Location> wardList = new ArrayList<>();
for (Long id : orgIds) {
wardList.addAll(iLocationService.getWardList(id));
}
// 按 ID 去重
List<LocationDto> locationDtoList = new ArrayList<>();
Set<Long> seen = new HashSet<>();
for (Location location : wardList) {
if (seen.add(location.getId())) {
LocationDto dto = new LocationDto();
BeanUtils.copyProperties(location, dto);
locationDtoList.add(dto);
}
}
return locationDtoList;
}

View File

@@ -72,6 +72,10 @@ public interface IChargeBillService {
*/
R<?> checkYbNo();
/**
* 分页查询收费账单列表
*/
Map<String, Object> getBillPage(String searchKey, String billType, String payStatus,
String startTime, String endTime, Integer pageNo, Integer pageSize);
}

View File

@@ -608,4 +608,221 @@ public class ChargeBillQueryService {
map.put("insutype", "自费");
return map;
}
/**
* 分页查询收费账单列表
*/
public Map<String, Object> getBillPage(String searchKey, String billType, String payStatus,
String startTime, String endTime, Integer pageNo, Integer pageSize) {
// 预解析searchKey避免后续重复查询
List<Long> searchPatientIds = null;
List<Long> searchEncounterIds = null;
if (StringUtils.isNotEmpty(searchKey)) {
searchPatientIds = iPatientService.list(new LambdaQueryWrapper<Patient>()
.like(Patient::getName, searchKey)
.eq(Patient::getDeleteFlag, DelFlag.NO.getCode())
.select(Patient::getId))
.stream().map(Patient::getId).collect(Collectors.toList());
searchEncounterIds = iEncounterService.list(new LambdaQueryWrapper<Encounter>()
.like(Encounter::getBusNo, searchKey)
.eq(Encounter::getDeleteFlag, DelFlag.NO.getCode())
.select(Encounter::getId))
.stream().map(Encounter::getId).collect(Collectors.toList());
}
// 分页查询
LambdaQueryWrapper<PaymentReconciliation> pageWrapper = buildBillQueryWrapper(
billType, payStatus, startTime, endTime, searchPatientIds, searchEncounterIds);
pageWrapper.orderByDesc(PaymentReconciliation::getBillDate);
com.baomidou.mybatisplus.extension.plugins.pagination.Page<PaymentReconciliation> page =
new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(pageNo, pageSize);
List<PaymentReconciliation> billList = paymentReconciliationService.page(page, pageWrapper).getRecords();
// 总数
long total = paymentReconciliationService.count(buildBillQueryWrapper(
billType, payStatus, startTime, endTime, searchPatientIds, searchEncounterIds));
// 转换为DTO
List<BillListDto> records = convertToDtoList(billList);
// 汇总:查询全部匹配记录(不受分页限制)
Map<String, Object> summary = computeBillSummary(billType, payStatus, startTime, endTime,
searchPatientIds, searchEncounterIds, total);
Map<String, Object> result = new HashMap<>();
result.put("records", records);
result.put("total", total);
result.put("summary", summary);
return result;
}
/**
* 将账单实体列表转换为DTO列表
*/
private List<BillListDto> convertToDtoList(List<PaymentReconciliation> billList) {
List<BillListDto> records = new ArrayList<>();
if (billList.isEmpty()) {
return records;
}
List<Long> patientIds = billList.stream().map(PaymentReconciliation::getPatientId).distinct().collect(Collectors.toList());
List<Long> encounterIds = billList.stream().map(PaymentReconciliation::getEncounterId).distinct().collect(Collectors.toList());
List<Long> entererIds = billList.stream().map(PaymentReconciliation::getEntererId).distinct().collect(Collectors.toList());
Map<Long, Patient> patientMap = listToMap(iPatientService.listByIds(patientIds), Patient::getId);
Map<Long, Encounter> encounterMap = listToMap(iEncounterService.listByIds(encounterIds), Encounter::getId);
Map<Long, Practitioner> practitionerMap = listToMap(iPractitionerService.listByIds(entererIds), Practitioner::getId);
// 查询当前页账单的退费金额
List<Long> paymentIds = billList.stream().map(PaymentReconciliation::getId).collect(Collectors.toList());
Map<Long, BigDecimal> refundMap = new HashMap<>();
if (!paymentIds.isEmpty()) {
List<PaymentReconciliation> refunds = paymentReconciliationService.list(
new LambdaQueryWrapper<PaymentReconciliation>()
.in(PaymentReconciliation::getRelationId, paymentIds)
.eq(PaymentReconciliation::getDeleteFlag, DelFlag.NO.getCode())
.in(PaymentReconciliation::getStatusEnum,
PaymentStatus.REFUND_ALL.getValue(), PaymentStatus.REFUND_PART.getValue()));
for (PaymentReconciliation refund : refunds) {
refundMap.merge(refund.getRelationId(), refund.getTenderedAmount(), BigDecimal::add);
}
}
for (PaymentReconciliation bill : billList) {
BillListDto dto = new BillListDto();
dto.setId(bill.getId());
dto.setBillNo(bill.getPaymentNo());
dto.setPatientId(bill.getPatientId());
dto.setEncounterId(bill.getEncounterId());
dto.setTotalAmount(bill.getTenderedAmount() != null ? bill.getTenderedAmount() : BigDecimal.ZERO);
dto.setRefundAmount(refundMap.getOrDefault(bill.getId(), BigDecimal.ZERO));
dto.setPayStatus(bill.getStatusEnum());
dto.setOperatorId(bill.getEntererId());
dto.setPayTime(bill.getBillDate());
dto.setContractNo(bill.getContractNo());
Patient patient = patientMap.get(bill.getPatientId());
if (patient != null) {
dto.setPatientName(patient.getName());
dto.setGenderEnum(patient.getGenderEnum());
dto.setBirthDate(patient.getBirthDate());
if (patient.getBirthDate() != null) {
dto.setAge(AgeCalculatorUtil.calculateAge(patient.getBirthDate()));
}
}
Encounter encounter = encounterMap.get(bill.getEncounterId());
if (encounter != null) {
dto.setEncounterNo(encounter.getBusNo());
}
Practitioner practitioner = practitionerMap.get(bill.getEntererId());
if (practitioner != null) {
dto.setOperatorName(practitioner.getName());
}
if (bill.getStatusEnum() != null) {
PaymentStatus ps = PaymentStatus.getByValue(bill.getStatusEnum());
dto.setPayStatus_dictText(ps != null ? ps.getInfo() : "");
}
records.add(dto);
}
return records;
}
/**
* 将List转为MapkeyExtractor为key提取函数
*/
private <T> Map<Long, T> listToMap(List<T> list, java.util.function.Function<T, Long> keyExtractor) {
Map<Long, T> map = new HashMap<>();
for (T item : list) {
map.put(keyExtractor.apply(item), item);
}
return map;
}
/**
* 构建查询条件
* @param searchPatientIds 预解析的患者ID列表null表示无searchKey
* @param searchEncounterIds 预解析的就诊ID列表null表示无searchKey
*/
private LambdaQueryWrapper<PaymentReconciliation> buildBillQueryWrapper(
String billType, String payStatus, String startTime, String endTime,
List<Long> searchPatientIds, List<Long> searchEncounterIds) {
LambdaQueryWrapper<PaymentReconciliation> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(PaymentReconciliation::getDeleteFlag, DelFlag.NO.getCode())
.eq(PaymentReconciliation::getKindEnum, PaymentKind.OUTPATIENT_CLINIC.getValue());
if (StringUtils.isNotEmpty(payStatus)) {
int ps = Integer.parseInt(payStatus);
if (ps == 0) {
wrapper.eq(PaymentReconciliation::getStatusEnum, PaymentStatus.DRAFT.getValue());
} else if (ps == 1) {
wrapper.eq(PaymentReconciliation::getStatusEnum, PaymentStatus.SUCCESS.getValue());
} else if (ps == 2) {
wrapper.in(PaymentReconciliation::getStatusEnum,
PaymentStatus.REFUND_ALL.getValue(), PaymentStatus.REFUND_PART.getValue());
}
}
if (StringUtils.isNotEmpty(startTime)) {
wrapper.ge(PaymentReconciliation::getBillDate, startTime);
}
if (StringUtils.isNotEmpty(endTime)) {
wrapper.le(PaymentReconciliation::getBillDate, endTime);
}
// searchKey 过滤使用预解析的ID列表
if (searchPatientIds != null && searchEncounterIds != null) {
boolean hasPatients = !searchPatientIds.isEmpty();
boolean hasEncounters = !searchEncounterIds.isEmpty();
if (hasPatients || hasEncounters) {
wrapper.and(w -> {
if (hasPatients) {
w.or().in(PaymentReconciliation::getPatientId, searchPatientIds);
}
if (hasEncounters) {
w.or().in(PaymentReconciliation::getEncounterId, searchEncounterIds);
}
});
} else {
wrapper.eq(PaymentReconciliation::getId, -1L);
}
}
return wrapper;
}
/**
* 查询全部匹配记录的汇总数据(不受分页限制)
*/
private Map<String, Object> computeBillSummary(String billType, String payStatus, String startTime, String endTime,
List<Long> searchPatientIds, List<Long> searchEncounterIds,
long totalCount) {
Map<String, Object> summary = new HashMap<>();
LambdaQueryWrapper<PaymentReconciliation> wrapper = buildBillQueryWrapper(
billType, payStatus, startTime, endTime, searchPatientIds, searchEncounterIds);
wrapper.select(PaymentReconciliation::getStatusEnum, PaymentReconciliation::getTenderedAmount);
List<PaymentReconciliation> allBills = paymentReconciliationService.list(wrapper);
BigDecimal totalAmount = BigDecimal.ZERO;
BigDecimal refundAmount = BigDecimal.ZERO;
for (PaymentReconciliation bill : allBills) {
BigDecimal amount = bill.getTenderedAmount() != null ? bill.getTenderedAmount() : BigDecimal.ZERO;
Integer status = bill.getStatusEnum();
if (PaymentStatus.SUCCESS.getValue().equals(status)) {
totalAmount = totalAmount.add(amount);
} else if (PaymentStatus.REFUND_ALL.getValue().equals(status)
|| PaymentStatus.REFUND_PART.getValue().equals(status)) {
refundAmount = refundAmount.add(amount);
}
}
summary.put("totalAmount", totalAmount);
summary.put("refundAmount", refundAmount);
summary.put("actualAmount", totalAmount.subtract(refundAmount));
summary.put("totalCount", totalCount);
return summary;
}
}

View File

@@ -72,4 +72,11 @@ public class IChargeBillServiceImpl implements IChargeBillService {
public R<?> checkYbNo() {
return chargeBillStatisticsService.checkYbNo();
}
@Override
public Map<String, Object> getBillPage(String searchKey, String billType, String payStatus,
String startTime, String endTime, Integer pageNo, Integer pageSize) {
return chargeBillQueryService.getBillPage(searchKey, billType, payStatus,
startTime, endTime, pageNo, pageSize);
}
}

View File

@@ -115,4 +115,27 @@ public class ChargeBillController {
return iChargeBillService.checkYbNo();
}
/**
* 分页查询收费账单列表
*/
@GetMapping("/page")
public R<?> page(@RequestParam(value = "searchKey", required = false) String searchKey,
@RequestParam(value = "billType", required = false) String billType,
@RequestParam(value = "payStatus", required = false) String payStatus,
@RequestParam(value = "startTime", required = false) String startTime,
@RequestParam(value = "endTime", required = false) String endTime,
@RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize) {
return R.ok(iChargeBillService.getBillPage(searchKey, billType, payStatus,
startTime, endTime, pageNo, pageSize));
}
/**
* 获取收费详情按ID
*/
@GetMapping("/{id}")
public R<?> getById(@PathVariable Long id) {
return R.ok(iChargeBillService.getDetail(id));
}
}

View File

@@ -0,0 +1,78 @@
package com.healthlink.his.web.paymentmanage.dto;
import lombok.Data;
import lombok.experimental.Accessors;
import java.math.BigDecimal;
import java.util.Date;
/**
* 收费账单列表DTO
*/
@Data
@Accessors(chain = true)
public class BillListDto {
/** 账单ID */
private Long id;
/** 账单号 */
private String billNo;
/** 患者ID */
private Long patientId;
/** 患者姓名 */
private String patientName;
/** 性别枚举 */
private Integer genderEnum;
/** 性别文本 */
private String genderEnum_enumText;
/** 年龄 */
private Integer age;
/** 出生日期 */
private Date birthDate;
/** 就诊ID */
private Long encounterId;
/** 门诊号 */
private String encounterNo;
/** 收费类型枚举 */
private Integer billType;
/** 收费类型文本 */
private String billType_dictText;
/** 收费金额 */
private BigDecimal totalAmount;
/** 退费金额 */
private BigDecimal refundAmount;
/** 收费状态枚举 */
private Integer payStatus;
/** 收费状态文本 */
private String payStatus_dictText;
/** 支付方式文本 */
private String payMethod_dictText;
/** 收费员ID */
private Long operatorId;
/** 收费员姓名 */
private String operatorName;
/** 收费时间 */
private Date payTime;
/** 合同编号 */
private String contractNo;
}

View File

@@ -187,6 +187,6 @@
<if test="orgId != null">
AND organization_id = #{orgId}
</if>
AND tenant_id = 1 <!-- 租户ID若为动态可改为参数传入 -->
<!-- 租户过滤由 MyBatis Plus TenantLineInnerInterceptor 自动注入 -->
</select>
</mapper>

View File

@@ -11,10 +11,7 @@
SELECT
a.total_price,
COALESCE(b1.org_id, b2.org_id, b3.org_id, b4.offered_org_id, b5.org_id) AS org_id,
--COALESCE(b1.yb_class_enum, b2.yb_class_enum, b3.yb_class_enum) AS yb_class_enum,
b5.yb_type
-- 添加更多需要的字段
FROM
adm_charge_item a
LEFT JOIN
@@ -42,4 +39,5 @@
</foreach>
</if>
</select>
</mapper>

View File

@@ -485,10 +485,8 @@ const handleEditSubmit = () => {
const openAct = () => {
console.log(props.patientInfo, 'patientRegister.vue');
console.log(props.inHospitalInfo, 'inHospitalInfo.vue');
/* 初始化数据 */
advance.value = props.inHospitalInfo.balanceAmount || 0;
advancePaymentVisible.value = false;
RegisterFormRef.value.init();
};
const closedAct = () => {
dialogVisible.value = false;

View File

@@ -312,11 +312,12 @@ const props = defineProps({
});
const organization = ref([]);
const fullOrgTree = ref([]); // 未过滤的完整组织树(保留 children 层级)
const wardListOptions = ref([]);
const practitionerWardList = ref([]); // 当前用户有权限的病区(与护士站同源)
const contractList = ref([]);
const verificationStatusOptions = ref([]);
const diagnosisDefinitionList = ref([]);
const wardLoading = ref(false);
const rules = reactive({
inHospitalOrgId: [
{
@@ -451,17 +452,6 @@ watch(
},
{ immediate: true }
);
watch(
() => props.isRegistered,
(newVal) => {
if (newVal) {
// 已登记状态可以做一些数据锁定操作
} else {
init();
}
}
);
onMounted(async () => {
await getInitOptions();
setValue();
@@ -501,15 +491,21 @@ function handleWardClick(item) {
function getInitOptions() {
// 获取所有科室
const orgPromise = getOrgList();
// 获取所有病区
// 获取当前用户有权限的病区(与护士站入出管理同款数据源)
const wardPromise = getPractitionerWard();
const initPromise = Promise.all([orgPromise, wardPromise]).then(([orgRes, wardRes]) => {
// 保存完整组织树(含 children 层级),用于 findOrgNode 递归查找
fullOrgTree.value = orgRes.data.records || [];
// 入院科室:展示所有 typeEnum=2(科室) + classEnum含"2"(住院) 的科室
organization.value = orgRes.data.records.filter(
(record) => record.typeEnum === 2 && checkClassEnumValue(record.classEnum, 2)
);
// 保存当前用户有权限的病区列表(与护士站使用相同数据源)
practitionerWardList.value = wardRes.data || [];
// Bug #178 Fix: 如果已选科室不在列表中,手动添加以确保正确显示
const selectedOrgId = props.inHospitalInfo?.inHospitalOrgId;
const selectedOrgName = props.inHospitalInfo?.inHospitalOrgName;
@@ -545,39 +541,43 @@ function getDiagnosisInfo(value) {
}
function handleNodeClick(orgInfo) {
const savedWardId = props.inHospitalInfo?.wardLocationId; // 保存原始病区ID用于编辑模式恢复
submitForm.wardLocationId = undefined; // 切换科室时,先清空原有病区
const savedWardId = props.inHospitalInfo?.wardLocationId;
submitForm.wardLocationId = undefined;
submitForm.totalBedsNum = undefined;
submitForm.idleBedsNum = undefined;
wardListOptions.value = []; // 先清空列表,避免旧数据残留
wardLoading.value = true;
wardList({ orgId: orgInfo.id })
.then((res) => {
wardListOptions.value = res.data || [];
if (wardListOptions.value.length > 0) {
// 编辑模式:尝试恢复之前保存的病区
if (savedWardId) {
const savedWard = wardListOptions.value.find((item) => String(item.id) === String(savedWardId));
if (savedWard) {
submitForm.wardLocationId = savedWardId;
handleWardClick(savedWard);
return;
}
}
// 新增模式 或 原病区不在新科室下:自动选中第一个病区
if (!submitForm.wardLocationId) {
submitForm.wardLocationId = wardListOptions.value[0].id;
handleWardClick(wardListOptions.value[0]);
}
wardListOptions.value = [];
if (orgInfo.id) {
// 后端查询:自动包含子孙科室的病区(通过 busNo 层级匹配)
wardList({ orgId: orgInfo.id })
.then((res) => {
wardListOptions.value = res.data || [];
selectSavedOrFirstWard(savedWardId);
})
.catch(() => {
wardListOptions.value = [];
});
} else {
wardListOptions.value = practitionerWardList.value || [];
selectSavedOrFirstWard(savedWardId);
}
}
function selectSavedOrFirstWard(savedWardId) {
if (wardListOptions.value.length > 0) {
if (savedWardId) {
const savedWard = wardListOptions.value.find((item) => String(item.id) === String(savedWardId));
if (savedWard) {
submitForm.wardLocationId = savedWardId;
handleWardClick(savedWard);
return;
}
})
.catch((err) => {
console.error('加载病区失败:', err);
wardListOptions.value = [];
})
.finally(() => {
wardLoading.value = false;
});
}
if (!submitForm.wardLocationId) {
submitForm.wardLocationId = wardListOptions.value[0].id;
handleWardClick(wardListOptions.value[0]);
}
}
}
function handleChange(value) {
@@ -586,7 +586,6 @@ function handleChange(value) {
submitForm.wardLocationId = undefined;
submitForm.totalBedsNum = undefined;
submitForm.idleBedsNum = undefined;
wardLoading.value = false; // 同步停止加载
}
}
@@ -649,14 +648,6 @@ const validateData = async (callback) => {
});
};
const init = () => {
if (!props.isRegistered) {
// 只有待登记状态才重置
submitForm.inDocterWorkGroupCode = '';
submitForm.wardLocationId = '';
}
};
// 检查classEnum值是否包含指定值支持多选
function checkClassEnumValue(classEnum, targetValue) {
if (!classEnum) return false;
@@ -671,7 +662,7 @@ function checkClassEnumValue(classEnum, targetValue) {
return classEnum == targetValue;
}
defineExpose({ validateData, submitForm, init, medicalInsuranceTitle });
defineExpose({ validateData, submitForm, medicalInsuranceTitle });
</script>
<style lang="scss" scoped>
.registerForm-container {