3 Commits

Author SHA1 Message Date
3f0fa3bbb3 Merge remote-tracking branch 'origin/develop' into develop 2026-03-26 17:00:41 +08:00
71e3601d51 feat(prescription): 更新处方列表数据结构并优化药品管理界面功能
- 在处方列表中新增总价、剂量和剂量数量字段
- 修复药品审批页面跳转时仓库信息丢失问题
- 扩展药品列表列宽度并启用溢出提示功能
- 为采购单界面添加多种视图状态下的字段禁用逻辑
- 优化采购单仓库位置字段的初始化流程,防止数据丢失
2026-03-26 16:54:20 +08:00
f04c3d112c fix(core): 解决ID字段精度丢失和账户ID为空问题
- 在前端请求处理中添加convertIdsToString函数,将超过安全范围的数字转换为字符串
- 使用json-bigint库处理大数字序列化,防止精度丢失
- 在医嘱保存逻辑中确保accountId不为null,自动创建自费账户
- 添加IAccountService依赖注入支持账户操作
- 在产品转移详情DTO中添加@TableField注解标识非数据库字段
2026-03-26 15:36:17 +08:00
9 changed files with 158 additions and 21 deletions

View File

@@ -1106,6 +1106,28 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
log.info("BugFix#219: ========== handDevice END =========="); log.info("BugFix#219: ========== handDevice END ==========");
for (AdviceSaveDto adviceSaveDto : insertOrUpdateList) { for (AdviceSaveDto adviceSaveDto : insertOrUpdateList) {
// 🔧 Bug Fix: 确保accountId不为null
if (adviceSaveDto.getAccountId() == null) {
// 尝试从患者就诊中获取默认账户ID自费账户
Account selfAccount = iAccountService.getSelfAccount(adviceSaveDto.getEncounterId());
if (selfAccount != null) {
adviceSaveDto.setAccountId(selfAccount.getId());
} else {
// 自动创建自费账户
Account newAccount = new Account();
newAccount.setPatientId(adviceSaveDto.getPatientId());
newAccount.setEncounterId(adviceSaveDto.getEncounterId());
newAccount.setContractNo(CommonConstants.BusinessName.DEFAULT_CONTRACT_NO);
newAccount.setTypeCode(AccountType.PERSONAL_CASH_ACCOUNT.getCode());
newAccount.setBalanceAmount(BigDecimal.ZERO);
newAccount.setStatusEnum(AccountStatus.ACTIVE.getValue());
newAccount.setEncounterFlag(Whether.YES.getValue());
newAccount.setName(AccountType.PERSONAL_CASH_ACCOUNT.getInfo());
Long newAccountId = iAccountService.saveAccountByRegister(newAccount);
adviceSaveDto.setAccountId(newAccountId);
}
}
deviceRequest = new DeviceRequest(); deviceRequest = new DeviceRequest();
deviceRequest.setId(adviceSaveDto.getRequestId()); // 主键id deviceRequest.setId(adviceSaveDto.getRequestId()); // 主键id
deviceRequest.setStatusEnum(is_save ? RequestStatus.DRAFT.getValue() : RequestStatus.ACTIVE.getValue()); // 请求状态 deviceRequest.setStatusEnum(is_save ? RequestStatus.DRAFT.getValue() : RequestStatus.ACTIVE.getValue()); // 请求状态

View File

@@ -10,8 +10,10 @@ import com.core.common.utils.AssignSeqUtil;
import com.core.common.utils.MessageUtils; import com.core.common.utils.MessageUtils;
import com.core.common.utils.SecurityUtils; import com.core.common.utils.SecurityUtils;
import com.core.common.utils.StringUtils; import com.core.common.utils.StringUtils;
import com.openhis.administration.domain.Account;
import com.openhis.administration.domain.ChargeItem; import com.openhis.administration.domain.ChargeItem;
import com.openhis.administration.domain.EncounterDiagnosis; import com.openhis.administration.domain.EncounterDiagnosis;
import com.openhis.administration.service.IAccountService;
import com.openhis.administration.service.IChargeItemService; import com.openhis.administration.service.IChargeItemService;
import com.openhis.administration.service.IEncounterDiagnosisService; import com.openhis.administration.service.IEncounterDiagnosisService;
import com.openhis.clinical.domain.Condition; import com.openhis.clinical.domain.Condition;
@@ -80,6 +82,9 @@ public class DoctorStationChineseMedicalAppServiceImpl implements IDoctorStation
@Resource @Resource
AdviceUtils adviceUtils; AdviceUtils adviceUtils;
@Resource
IAccountService iAccountService;
/** /**
* 查询中医诊断数据 * 查询中医诊断数据
* *
@@ -475,6 +480,28 @@ public class DoctorStationChineseMedicalAppServiceImpl implements IDoctorStation
// 医嘱签发编码 // 医嘱签发编码
String signCode = assignSeqUtil.getSeq(AssignSeqEnum.ADVICE_SIGN.getPrefix(), 10); String signCode = assignSeqUtil.getSeq(AssignSeqEnum.ADVICE_SIGN.getPrefix(), 10);
for (AdviceSaveDto adviceSaveDto : insertOrUpdateList) { for (AdviceSaveDto adviceSaveDto : insertOrUpdateList) {
// 🔧 Bug Fix: 确保accountId不为null
if (adviceSaveDto.getAccountId() == null) {
// 尝试从患者就诊中获取默认账户ID自费账户
Account selfAccount = iAccountService.getSelfAccount(adviceSaveDto.getEncounterId());
if (selfAccount != null) {
adviceSaveDto.setAccountId(selfAccount.getId());
} else {
// 自动创建自费账户
Account newAccount = new Account();
newAccount.setPatientId(adviceSaveDto.getPatientId());
newAccount.setEncounterId(adviceSaveDto.getEncounterId());
newAccount.setContractNo(CommonConstants.BusinessName.DEFAULT_CONTRACT_NO);
newAccount.setTypeCode(AccountType.PERSONAL_CASH_ACCOUNT.getCode());
newAccount.setBalanceAmount(BigDecimal.ZERO);
newAccount.setStatusEnum(AccountStatus.ACTIVE.getValue());
newAccount.setEncounterFlag(Whether.YES.getValue());
newAccount.setName(AccountType.PERSONAL_CASH_ACCOUNT.getInfo());
Long newAccountId = iAccountService.saveAccountByRegister(newAccount);
adviceSaveDto.setAccountId(newAccountId);
}
}
// 中药付数 // 中药付数
BigDecimal chineseHerbsDoseQuantity = adviceSaveDto.getChineseHerbsDoseQuantity(); BigDecimal chineseHerbsDoseQuantity = adviceSaveDto.getChineseHerbsDoseQuantity();
medicationRequest = new MedicationRequest(); medicationRequest = new MedicationRequest();

View File

@@ -11,6 +11,8 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.openhis.administration.domain.ChargeItem; import com.openhis.administration.domain.ChargeItem;
import com.openhis.administration.domain.Account;
import com.openhis.administration.service.IAccountService;
import com.openhis.administration.service.IChargeItemService; import com.openhis.administration.service.IChargeItemService;
import com.openhis.common.constant.CommonConstants; import com.openhis.common.constant.CommonConstants;
import com.openhis.common.enums.*; import com.openhis.common.enums.*;
@@ -71,6 +73,9 @@ public class AdviceUtils {
@Resource @Resource
IDoctorStationAdviceAppService iDoctorStationAdviceAppService; IDoctorStationAdviceAppService iDoctorStationAdviceAppService;
@Resource
IAccountService iAccountService;
/** /**
* 校验库存 * 校验库存
* *
@@ -305,6 +310,28 @@ public class AdviceUtils {
*/ */
public void handleActivityChild(String childrenJson, Long organizationId, public void handleActivityChild(String childrenJson, Long organizationId,
ActivityChildrenJsonParams activityChildrenJsonParams) { ActivityChildrenJsonParams activityChildrenJsonParams) {
// 🔧 Bug Fix: 确保accountId不为null
if (activityChildrenJsonParams.getAccountId() == null) {
// 尝试从患者就诊中获取默认账户ID自费账户
Account selfAccount = iAccountService.getSelfAccount(activityChildrenJsonParams.getEncounterId());
if (selfAccount != null) {
activityChildrenJsonParams.setAccountId(selfAccount.getId());
} else {
// 自动创建自费账户
Account newAccount = new Account();
newAccount.setPatientId(activityChildrenJsonParams.getPatientId());
newAccount.setEncounterId(activityChildrenJsonParams.getEncounterId());
newAccount.setContractNo(CommonConstants.BusinessName.DEFAULT_CONTRACT_NO);
newAccount.setTypeCode(AccountType.PERSONAL_CASH_ACCOUNT.getCode());
newAccount.setBalanceAmount(BigDecimal.ZERO);
newAccount.setStatusEnum(AccountStatus.ACTIVE.getValue());
newAccount.setEncounterFlag(Whether.YES.getValue());
newAccount.setName(AccountType.PERSONAL_CASH_ACCOUNT.getInfo());
Long newAccountId = iAccountService.saveAccountByRegister(newAccount);
activityChildrenJsonParams.setAccountId(newAccountId);
}
}
// 治疗类型 (长期/临时) // 治疗类型 (长期/临时)
Integer therapyEnum = activityChildrenJsonParams.getTherapyEnum(); Integer therapyEnum = activityChildrenJsonParams.getTherapyEnum();
// 当前登录账号的科室id // 当前登录账号的科室id

View File

@@ -4,6 +4,7 @@
package com.openhis.web.inventorymanage.dto; package com.openhis.web.inventorymanage.dto;
import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId;
import com.core.common.annotation.Excel; import com.core.common.annotation.Excel;
import com.core.common.annotation.ExcelExtra; import com.core.common.annotation.ExcelExtra;
@@ -248,7 +249,8 @@ public class ProductTransferDetailDto {
private Date occurrenceTime; private Date occurrenceTime;
/** /**
* 单位列表 * 单位列表(非数据库字段,业务逻辑填充)
*/ */
@TableField(exist = false)
private List<UnitDto> unitList; private List<UnitDto> unitList;
} }

View File

@@ -11,6 +11,38 @@ import JSONBig from 'json-bigint'
// 初始化json-bigint配置大数字转字符串关键storeAsString: true // 初始化json-bigint配置大数字转字符串关键storeAsString: true
const jsonBig = JSONBig({ storeAsString: true }) const jsonBig = JSONBig({ storeAsString: true })
// 🔧 Bug Fix #281: 转换所有ID字段为字符串防止BigInt精度丢失
const convertIdsToString = (obj) => {
if (obj === null || obj === undefined) return obj
if (typeof obj === 'number' && obj > 9007199254740991) {
// 如果是超过安全范围的数字,转为字符串
return String(obj)
}
if (typeof obj === 'object') {
if (Array.isArray(obj)) {
return obj.map(item => convertIdsToString(item))
} else {
const newObj = {}
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
const value = obj[key]
// 如果key以Id结尾或者是id且值是数字转为字符串
if ((key === 'id' || key.endsWith('Id') || key.endsWith('ID')) && typeof value === 'number') {
newObj[key] = String(value)
} else {
newObj[key] = convertIdsToString(value)
}
}
}
return newObj
}
}
return obj
}
let downloadLoadingInstance; let downloadLoadingInstance;
// 是否显示重新登录 // 是否显示重新登录
export let isRelogin = { show: false }; export let isRelogin = { show: false };
@@ -35,16 +67,23 @@ const service = axios.create({
} }
} }
], ],
// 可选:请求体序列化,无需额外处理,默认即可(保留也不影响) // 可选:请求体序列化,使用json-bigint处理大数字
transformRequest: [ transformRequest: [
function (data) { function (data) {
return JSON.stringify(data) if (!data) return data
// 🔧 Bug Fix #281: 使用json-bigint序列化保留大数字精度
return jsonBig.stringify(data)
} }
] ]
}) })
// request拦截器 // request拦截器
service.interceptors.request.use(config => { service.interceptors.request.use(config => {
// 🔧 Bug Fix #281: 转换请求数据中的ID字段为字符串
if (config.data && typeof config.data === 'object') {
config.data = convertIdsToString(config.data)
}
// 是否需要设置 token // 是否需要设置 token
const isToken = (config.headers || {}).isToken === false const isToken = (config.headers || {}).isToken === false
// 是否需要防止数据重复提交 // 是否需要防止数据重复提交
@@ -62,10 +101,11 @@ service.interceptors.request.use(config => {
if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) { if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) {
const requestObj = { const requestObj = {
url: config.url, url: config.url,
data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data, data: typeof config.data === 'object' ? jsonBig.stringify(config.data) : config.data,
time: new Date().getTime() time: new Date().getTime()
} }
const requestSize = Object.keys(JSON.stringify(requestObj)).length; // 请求数据大小 // 🔧 Bug Fix #281: 使用json-bigint计算大小
const requestSize = Object.keys(jsonBig.stringify(requestObj)).length; // 请求数据大小
const limitSize = 5 * 1024 * 1024; // 限制存放数据5M const limitSize = 5 * 1024 * 1024; // 限制存放数据5M
if (requestSize >= limitSize) { if (requestSize >= limitSize) {
console.warn(`[${config.url}]: ` + '请求数据大小超出允许的5M限制无法进行防重复提交验证。') console.warn(`[${config.url}]: ` + '请求数据大小超出允许的5M限制无法进行防重复提交验证。')

View File

@@ -2149,7 +2149,10 @@ function handleDelete() {
adviceType: item.adviceType, adviceType: item.adviceType,
statusEnum: item.statusEnum, statusEnum: item.statusEnum,
adviceName: item.adviceName, adviceName: item.adviceName,
uniqueKey: item.uniqueKey uniqueKey: item.uniqueKey,
totalPrice: item.totalPrice,
dose: item.dose,
doseQuantity: item.doseQuantity
}))); })));
for (let i = prescriptionList.value.length - 1; i >= 0; i--) { for (let i = prescriptionList.value.length - 1; i >= 0; i--) {

View File

@@ -310,6 +310,11 @@ function handelApplys(row, view) {
// 采购入库 1 // 采购入库 1
getpurchaseInventoryDetail(row.supplyBusNo).then((response) => { getpurchaseInventoryDetail(row.supplyBusNo).then((response) => {
let currentData = response.data; let currentData = response.data;
// 从明细数据中获取仓库ID并设置到row确保跳转后仓库字段能正确显示
if (currentData && currentData.length > 0 && !row.purposeLocationId) {
row.purposeLocationId = currentData[0].purposeLocationId;
row.purposeLocationId_dictText = currentData[0].purposeLocationName;
}
store.setCurrentData({ editRow: row, item: currentData }); store.setCurrentData({ editRow: row, item: currentData });
router.replace({ router.replace({
path: '/medicationmanagement/medicationmanagement/purchaseDocument', path: '/medicationmanagement/medicationmanagement/purchaseDocument',

View File

@@ -16,7 +16,8 @@
label="项目名称" label="项目名称"
align="center" align="center"
prop="name" prop="name"
width="180" width="250"
:show-overflow-tooltip="true"
/> />
<el-table-column <el-table-column
label="项目类型" label="项目类型"

View File

@@ -118,6 +118,7 @@
clearable clearable
filterable filterable
style="width: 150px" style="width: 150px"
:disabled="viewStatus == 'view' || viewStatus == 'apply'"
@change="handleMedicationTypeChange" @change="handleMedicationTypeChange"
> >
<el-option <el-option
@@ -134,6 +135,7 @@
placeholder="" placeholder=""
clearable clearable
style="width: 150px" style="width: 150px"
:disabled="viewStatus == 'view' || viewStatus == 'apply'"
@visible-change="handlePurposeTypeEnumVisibleChange" @visible-change="handlePurposeTypeEnumVisibleChange"
@change="handleChangeLocationType" @change="handleChangeLocationType"
> >
@@ -152,6 +154,7 @@
clearable clearable
filterable filterable
style="width: 200px" style="width: 200px"
:disabled="viewStatus == 'view' || viewStatus == 'apply'"
@visible-change="handleHeaderLocationVisibleChange" @visible-change="handleHeaderLocationVisibleChange"
> >
<el-option <el-option
@@ -200,7 +203,7 @@
:model="form" :model="form"
:rules="tableRules" :rules="tableRules"
ref="formRef" ref="formRef"
:disabled="viewStatus == 'apply'" :disabled="viewStatus == 'view' || viewStatus == 'apply'"
> >
<!-- @cell-mouse-enter="handleMouseEnter" --> <!-- @cell-mouse-enter="handleMouseEnter" -->
<!-- v-click-outside-row="handleClickOutside" @row-click="handleRowClick" 点击行以外的部分自动保存 --> <!-- v-click-outside-row="handleClickOutside" @row-click="handleRowClick" 点击行以外的部分自动保存 -->
@@ -219,8 +222,9 @@
align="center" align="center"
key="name" key="name"
prop="name" prop="name"
width="200" width="250"
fixed fixed
:show-overflow-tooltip="true"
> >
<template #default="scope"> <template #default="scope">
<el-form-item <el-form-item
@@ -228,7 +232,7 @@
:rules="tableRules.name" :rules="tableRules.name"
> >
<el-input <el-input
v-if="viewStatus == 'view'" v-if="viewStatus == 'view' || viewStatus == 'apply'"
v-model="scope.row.name" v-model="scope.row.name"
placeholder="" placeholder=""
disabled disabled
@@ -298,7 +302,7 @@
> >
<div class="select_wrapper_div"> <div class="select_wrapper_div">
<el-input <el-input
:disabled="viewStatus == 'view'" :disabled="viewStatus == 'view' || viewStatus == 'apply'"
v-model="scope.row.itemQuantity" v-model="scope.row.itemQuantity"
placeholder="" placeholder=""
@blur="handleTotalPrice(scope.$index)" @blur="handleTotalPrice(scope.$index)"
@@ -328,7 +332,7 @@
placeholder=" " placeholder=" "
:class="{ 'error-border': scope.row.error }" :class="{ 'error-border': scope.row.error }"
:clearable="false" :clearable="false"
:disabled="viewStatus == 'view'" :disabled="viewStatus == 'view' || viewStatus == 'apply'"
@change=" @change="
(value) => handleUnitCodeChange(scope.row, scope.$index, value) (value) => handleUnitCodeChange(scope.row, scope.$index, value)
" "
@@ -378,7 +382,7 @@
:rules="tableRules.traceNo" :rules="tableRules.traceNo"
> >
<el-input <el-input
:disabled="viewStatus == 'view'" :disabled="viewStatus == 'view' || viewStatus == 'apply'"
v-model="scope.row.traceNo" v-model="scope.row.traceNo"
@change="(value) => handleTraceNoInput(value, scope.row)" @change="(value) => handleTraceNoInput(value, scope.row)"
placeholder="" placeholder=""
@@ -403,7 +407,7 @@
<el-input <el-input
v-model="scope.row.packagingLevels" v-model="scope.row.packagingLevels"
placeholder="" placeholder=""
:disabled="viewStatus == 'view'" :disabled="viewStatus == 'view' || viewStatus == 'apply'"
/> />
</el-form-item> </el-form-item>
</template> </template>
@@ -426,7 +430,7 @@
placeholder="" placeholder=""
@blur="handleTotalPrice(scope.$index)" @blur="handleTotalPrice(scope.$index)"
:class="{ 'error-border': scope.row.error }" :class="{ 'error-border': scope.row.error }"
:disabled="viewStatus == 'view'" :disabled="viewStatus == 'view' || viewStatus == 'apply'"
> >
<template #suffix></template> <template #suffix></template>
</el-input> </el-input>
@@ -467,7 +471,7 @@
v-model="scope.row.lotNumber" v-model="scope.row.lotNumber"
placeholder="" placeholder=""
maxlength="100" maxlength="100"
:disabled="viewStatus == 'view'" :disabled="viewStatus == 'view' || viewStatus == 'apply'"
/> />
</el-form-item> </el-form-item>
</template> </template>
@@ -490,7 +494,7 @@
placeholder="选择日期" placeholder="选择日期"
format="YYYY-MM-DD" format="YYYY-MM-DD"
value-format="YYYY-MM-DD" value-format="YYYY-MM-DD"
:disabled="viewStatus == 'view'" :disabled="viewStatus == 'view' || viewStatus == 'apply'"
@change="changeValStart($event, scope.$index)" @change="changeValStart($event, scope.$index)"
/> />
</el-form-item> </el-form-item>
@@ -509,7 +513,7 @@
:rules="tableRules.endTime" :rules="tableRules.endTime"
> >
<el-date-picker <el-date-picker
:disabled="viewStatus == 'view'" :disabled="viewStatus == 'view' || viewStatus == 'apply'"
v-model="scope.row.endTime" v-model="scope.row.endTime"
type="date" type="date"
placeholder="选择日期" placeholder="选择日期"
@@ -536,7 +540,7 @@
v-model="scope.row.invoiceNo" v-model="scope.row.invoiceNo"
placeholder="" placeholder=""
style="text-align: center !important" style="text-align: center !important"
:disabled="viewStatus == 'view'" :disabled="viewStatus == 'view' || viewStatus == 'apply'"
/> />
</el-form-item> </el-form-item>
</template> </template>
@@ -557,7 +561,7 @@
<el-input <el-input
v-model="scope.row.remake" v-model="scope.row.remake"
placeholder="" placeholder=""
:disabled="viewStatus == 'view'" :disabled="viewStatus == 'view' || viewStatus == 'apply'"
/> />
</el-form-item> </el-form-item>
</template> </template>
@@ -1855,13 +1859,19 @@ function edit() {
: editData.value.item[0].purposeTypeEnum.toString(); : editData.value.item[0].purposeTypeEnum.toString();
receiptHeaderForm.medicationType = receiptHeaderForm.medicationType =
editData.value.item[0].itemTable == "med_medication_definition" ? "1" : "2"; editData.value.item[0].itemTable == "med_medication_definition" ? "1" : "2";
receiptHeaderForm.headerLocationId = editData.value.editRow?.purposeLocationId || null; // 先保存仓库ID因为handleChangeLocationType会清空headerLocationId
const savedLocationId =
editData.value.editRow?.purposeLocationId ||
(editData.value.item && editData.value.item.length > 0 ? editData.value.item[0].purposeLocationId : null) ||
null;
total.value = form.purchaseinventoryList.length; total.value = form.purchaseinventoryList.length;
handleChangeLocationType( handleChangeLocationType(
editData.value.editRow.purposeTypeEnum editData.value.editRow.purposeTypeEnum
? editData.value.editRow.purposeTypeEnum.toString() ? editData.value.editRow.purposeTypeEnum.toString()
: editData.value.item[0].purposeTypeEnum.toString() : editData.value.item[0].purposeTypeEnum.toString()
); );
// 在handleChangeLocationType之后再设置仓库ID
receiptHeaderForm.headerLocationId = savedLocationId;
setTimeout(() => { setTimeout(() => {
form.purchaseinventoryList = editData.value.item.map((item) => { form.purchaseinventoryList = editData.value.item.map((item) => {
return { return {