Files
华佗 1d21661a78 feat: Spring Boot 3.5.14 全量升级 + 组件升级
核心升级:
- Spring Boot 2.7.18 → 3.5.14
- MyBatis Plus 3.5.5 → 3.5.16 (spring-boot3-starter)
- Springdoc 1.8.0 → 2.8.6 (OpenAPI 3)
- Flowable 6.8.0 → 7.1.0
- Druid 1.2.x → 1.2.28 (boot3-starter)
- kotlin-reflect 1.9.10 → 1.9.25

迁移适配:
- javax → jakarta 命名空间 (620+ 文件)
- Swagger 注解迁移到 OpenAPI 3 (@Tag/@Schema/@Operation/@Parameter)
- Spring Security 6.2 适配 (antMatchers→requestMatchers, EnableMethodSecurity)
- Druid 包名迁移 (boot→boot3)
- Redis 配置路径迁移 (spring.redis→spring.data.redis)
- Flyway 适配 (flyway-database-postgresql)
- Flowable 7.x 适配 (MULE_TASK_IMAGE 移除)

修复:
- spring-boot-maven-plugin 2.5.15→3.5.14 (SPI服务发现失效)
- mybatis-plus-boot-starter 3.5.5→3.5.16 (kotlin-reflect+fastjson2冲突)
- Flowable database-schema-update 启用自动建表

验证: 23/23 测试通过, 1374 API端点正常
2026-06-04 22:39:49 +08:00

1668 lines
53 KiB
Vue
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div
v-loading="pageLoading"
class="app-container"
loading-text="审批中..."
>
<el-row
v-if="viewStatus"
:gutter="10"
class="mb8"
>
<!-- <el-col :span="1.5"> -->
<!-- <el-button v-if="viewStatus != 'view'" plain type="primary" icon="Edit" @click="handelApply" -->
<!-- >审批通过</el-button -->
<!-- > -->
<!-- </el-col> -->
<!-- <el-col :span="1.5"> -->
<!-- <el-button -->
<!-- v-if="viewStatus != 'view'" -->
<!-- type="primary" -->
<!-- plain -->
<!-- icon="Edit" -->
<!-- @click="handleReject" -->
<!-- >驳回</el-button -->
<!-- > -->
<!-- </el-col> -->
</el-row>
<el-row
v-else
:gutter="10"
class="mb8"
>
<!-- v-if="scope.row.statusEnum == '1' || scope.row.statusEnum == '9'" -->
<el-col :span="1.5">
<el-button
plain
type="primary"
icon="Plus"
@click="handleSubmitApproval()"
>
提交审核
</el-button>
<!-- v-hasPermi="['system:user:remove']" -->
</el-col>
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="Printer"
:disabled="multiple"
@click="handleDelete"
>
打印
</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="EditPen"
@click="handleTotalAmount"
>
计算金额
</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="Plus"
@click="handleSave"
>
批量保存
</el-button>
</el-col>
</el-row>
<el-button
v-if="viewStatus == 'view'"
type="primary"
plain
@click="handleExport"
>
导出
</el-button>
<el-form
ref="receiptHeaderRef"
:model="receiptHeaderForm"
:inline="true"
label-width="120px"
:rules="rules"
>
<el-row :gutter="10">
<el-form-item
label="单据号:"
prop="busNo"
>
<el-input
v-model="receiptHeaderForm.busNo"
placeholder="单据号:"
clearable
style="width: 260px"
disabled
/>
</el-form-item>
<el-form-item label="单据日期:">
<el-date-picker
v-model="receiptHeaderForm.occurrenceTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="datetime"
:disabled="data.isEdit"
/>
</el-form-item>
<el-form-item
label="药品类型:"
prop="medicationType"
label-width="115px"
>
<el-select
v-model="receiptHeaderForm.medicationType"
placeholder=""
clearable
style="width: 150px"
:disabled="data.isEdit"
@change="
(value) => {
itemType = value;
}
"
>
<!-- 字典 purchase_type -->
<el-option
v-for="itemType in purchase_type"
:key="itemType.value"
:label="itemType.label"
:value="itemType.value"
/>
</el-select>
</el-form-item>
</el-row>
<el-row :gutter="10">
<el-form-item
label="源仓库类型:"
prop="sourceTypeEnum"
>
<el-select
v-model="receiptHeaderForm.sourceTypeEnum"
placeholder=""
clearable
style="width: 150px"
:disabled="data.isEdit"
@change="handleChangeSourceTypeEnum"
>
<el-option
v-for="supplier in warehous_type"
:key="supplier.value"
:label="supplier.label"
:value="supplier.value"
/>
</el-select>
</el-form-item>
<el-form-item
label="源仓库:"
prop="sourceLocationId"
required
>
<el-select
v-model="receiptHeaderForm.sourceLocationId"
placeholder=""
clearable
style="width: 150px"
:disabled="data.isEdit"
>
<el-option
v-for="supplier in sourceTypeListOptions"
:key="supplier.id"
:label="supplier.name"
:value="supplier.id"
/>
</el-select>
</el-form-item>
<el-form-item
label="源货位:"
prop="sourceLocationStoreId"
>
<el-select
v-model="receiptHeaderForm.sourceLocationStoreId"
placeholder=""
clearable
style="width: 150px"
:disabled="data.isEdit"
>
<el-option
v-for="supplier in sourceLocationStoreIdListOptions"
:key="supplier.value"
:label="supplier.label"
:value="supplier.value"
/>
</el-select>
</el-form-item>
</el-row>
<el-row :gutter="10">
<el-form-item
label="目的仓库类型:"
prop="purposeTypeEnum"
>
<el-select
v-model="receiptHeaderForm.purposeTypeEnum"
placeholder=""
clearable
style="width: 150px"
:disabled="data.isEdit"
@change="handleChangePurposeTypeEnum"
>
<el-option
v-for="dict in warehous_type"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item
label="目的仓库:"
prop="purposeLocationId"
required
>
<el-select
v-model="receiptHeaderForm.purposeLocationId"
placeholder=""
clearable
style="width: 150px"
:disabled="data.isEdit"
>
<el-option
v-for="supplier in purposeTypeListOptions"
:key="supplier.id"
:label="supplier.name"
:value="supplier.id"
/>
</el-select>
</el-form-item>
<el-form-item
label="目的货位:"
prop="purposeLocationStoreId"
>
<el-select
v-model="receiptHeaderForm.purposeLocationStoreId"
placeholder=""
clearable
style="width: 150px"
:disabled="data.isEdit"
>
<el-option
v-for="supplier in purposeLocationStoreIdListOptions"
:key="supplier.value"
:label="supplier.label"
:value="supplier.value"
/>
</el-select>
</el-form-item>
</el-row>
</el-form>
<el-tabs type="border-card">
<el-tab-pane label="调拨单据明细">
<el-row
v-if="!viewStatus"
:gutter="10"
class="mb8"
>
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="Plus"
@click="addNewRow"
>
添加行
</el-button>
</el-col>
<!-- <el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="confirmCurrentRow"
>确认当前行</el-button
>
</el-col>
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="cancelEditRow"
>取消行编辑</el-button
>
</el-col>
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="cancelEditAllRow"
>取消所有行编辑</el-button
>
</el-col> -->
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="Delete"
:disabled="multiple"
@click="deleteSelectedRows"
>
删除行
</el-button>
</el-col>
</el-row>
<el-form
ref="formRef"
:model="form"
:rules="tableRules"
:disabled="viewStatus == 'apply'"
>
<el-table
ref="tableRef"
v-loading="loading"
:data="form.purchaseinventoryList"
@selection-change="handleSelectionChange"
@row-click="handleRowClick"
>
<el-table-column
type="selection"
width="50"
align="center"
/>
<el-table-column
key="name"
label="项目"
align="center"
prop="name"
width="200"
fixed
>
<template #default="scope">
<el-form-item
:prop="`purchaseinventoryList.${scope.$index}.name`"
:rules="tableRules.name"
required
>
<el-input
v-if="viewStatus == 'view'"
v-model="scope.row.name"
placeholder=""
disabled
/>
<PopoverList
v-else
:width="1000"
:model-value="scope.row.name"
@search="handleSearch"
>
<template #popover-content="{}">
<transferManagement
:search-key="medicineSearchKey"
:source-location-id="receiptHeaderForm.sourceLocationId"
:purpose-location-id="receiptHeaderForm.purposeLocationId"
:source-location-id1="receiptHeaderForm.sourceLocationId1"
:purpose-location-id1="receiptHeaderForm.purposeLocationId1"
:item-type="receiptHeaderForm.medicationType"
@select-row="(row) => selectRow(row, scope.$index)"
/>
</template>
</PopoverList>
</el-form-item>
</template>
</el-table-column>
<el-table-column
key="volume"
label="规格"
align="center"
prop="volume"
width="190"
>
<template #default="scope">
<el-form-item
:prop="`purchaseinventoryList.${scope.$index}.volume`"
:rules="tableRules.volume"
>
<el-input
v-model="scope.row.volume"
placeholder=""
disabled
/>
</el-form-item>
</template>
</el-table-column>
<el-table-column
key="manufacturerText"
label="厂家/产地"
align="center"
prop="manufacturerText"
:show-overflow-tooltip="true"
width="220"
>
<template #default="scope">
<el-form-item
:prop="`purchaseinventoryList.${scope.$index}.manufacturerText`"
:rules="tableRules.manufacturerText"
>
<el-input
v-model="scope.row.manufacturerText"
placeholder=""
disabled
/>
</el-form-item>
</template>
</el-table-column>
<el-table-column
key="unitCode"
label="调拨单位"
align="center"
prop="unitCode"
:show-overflow-tooltip="true"
width="90"
>
<template #default="scope">
<el-form-item
:prop="`purchaseinventoryList.${scope.$index}.unitCode`"
:rules="tableRules.unitCode"
>
<div class="select_wrapper_div">
<el-select
v-model="scope.row.unitCode"
placeholder=" "
:disabled="viewStatus == 'view'"
:class="{ 'error-border': scope.row.error }"
@change="(value) => handleUnitCodeChange(scope.row, scope.$index, value)"
>
<el-option
v-for="(item, index) in scope.row.unitList"
:key="index"
:label="item.label"
:value="item.value"
/>
</el-select>
</div>
</el-form-item>
</template>
</el-table-column>
<el-table-column
key="totalSourceQuantityDisplay"
label="源仓库库存数量"
align="center"
prop="totalSourceQuantityDisplay"
width="120"
>
<template #default="scope">
<el-form-item
:prop="`purchaseinventoryList.${scope.$index}.totalSourceQuantityDisplay`"
:rules="tableRules.totalSourceQuantity"
>
<el-input
v-model="scope.row.totalSourceQuantityDisplay"
placeholder=""
disabled
/>
</el-form-item>
</template>
</el-table-column>
<el-table-column
key="totalPurposeQuantityDisplay"
label="目的仓库库存数量"
align="center"
prop="totalPurposeQuantityDisplay"
width="150"
>
<template #default="scope">
<el-form-item
:prop="`purchaseinventoryList.${scope.$index}.totalPurposeQuantityDisplay`"
:rules="tableRules.totalPurposeQuantityDisplay"
>
<el-input
v-model="scope.row.totalPurposeQuantityDisplay"
placeholder=""
disabled
/>
</el-form-item>
</template>
</el-table-column>
<el-table-column
key="itemQuantityDisplay"
label="调拨数量"
align="center"
prop="itemQuantityDisplay"
width="120"
>
<template #default="scope">
<el-form-item
:prop="`purchaseinventoryList.${scope.$index}.itemQuantityDisplay`"
:rules="tableRules.itemQuantityDisplay"
>
<div class="select_wrapper_div">
<el-input
v-model="scope.row.itemQuantityDisplay"
:disabled="viewStatus == 'view'"
placeholder=""
:class="{ 'error-border': scope.row.error }"
@input="(value) => handleItemQuantityChange(scope.row, value)"
/>
</div>
</el-form-item>
</template>
</el-table-column>
<el-table-column
key="price"
label="调拨单价 "
align="center"
prop="price"
width="130"
>
<template #default="scope">
<el-form-item
:prop="`purchaseinventoryList.${scope.$index}.price`"
:rules="tableRules.price"
>
<div class="select_wrapper_div">
<el-input
v-model="scope.row.price"
disabled
placeholder=""
:class="{ 'error-border': scope.row.error }"
>
<template #suffix>
</template>
</el-input>
</div>
</el-form-item>
</template>
</el-table-column>
<el-table-column
key="totalPrice"
label="合计金额 "
align="center"
prop="totalPrice"
width="130"
>
<template #default="scope">
<el-form-item :prop="`purchaseinventoryList.${scope.$index}.totalPrice`">
<el-input
v-model="scope.row.totalPrice"
disabled
placeholder=""
/>
</el-form-item>
</template>
</el-table-column>
<el-table-column
key="lotNumber"
label="产品批号"
align="center"
prop="lotNumber"
width="160"
>
<template #default="scope">
<el-form-item
:prop="`purchaseinventoryList.${scope.$index}.lotNumber`"
:rules="tableRules.lotNumber"
>
<el-input
v-model="scope.row.lotNumber"
placeholder=""
disabled
@blur="lotNumberBlur(scope.row, scope.$index)"
/>
</el-form-item>
</template>
</el-table-column>
<el-table-column
key="startTime"
label="生产日期"
align="center"
prop="startTime"
width="150"
>
<template #default="scope">
<el-form-item
:prop="`purchaseinventoryList.${scope.$index}.startTime`"
:rules="tableRules.startTime"
>
<el-date-picker
v-model="scope.row.startTime"
type="date"
:disabled="viewStatus == 'view'"
placeholder="选择日期"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
@change="changeValStart($event, scope.$index)"
/>
</el-form-item>
</template>
</el-table-column>
<el-table-column
key="endTime"
label="有效期至"
align="center"
prop="endTime"
width="150"
>
<template #default="scope">
<el-form-item
:prop="`purchaseinventoryList.${scope.$index}.endTime`"
:rules="tableRules.endTime"
>
<el-date-picker
v-model="scope.row.endTime"
type="date"
:disabled="viewStatus == 'view'"
placeholder="选择日期"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
@change="changeValEnd($event, scope.$index)"
/>
</el-form-item>
</template>
</el-table-column>
<el-table-column
key="traceNo"
label="药品追溯码"
align="center"
prop="traceNo"
width="130"
>
<template #default="scope">
<el-form-item
:prop="`purchaseinventoryList.${scope.$index}.traceNo`"
:rules="tableRules.traceNo"
>
<el-input
v-model="scope.row.traceNo"
placeholder=""
disabled
@blur="traceNoBlur(scope.row, scope.$index)"
/>
</el-form-item>
</template>
</el-table-column>
<el-table-column
key="remake"
label="备注"
align="center"
prop="remake"
width="130"
>
<template #default="scope">
<el-form-item
:prop="`purchaseinventoryList.${scope.$index}.remake`"
:rules="tableRules.remake"
>
<el-input
v-model="scope.row.remake"
placeholder=""
:disabled="viewStatus == 'view'"
@blur="remakeBlur(scope.row, scope.$index)"
/>
</el-form-item>
</template>
</el-table-column>
<el-table-column
v-if="viewStatus != 'view'"
label="操作"
align="center"
width="80"
class-name="small-padding fixed-width"
fixed="right"
>
<template #default="scope">
<el-button
link
type="primary"
icon="Edit"
@click="handleScan(scope.row, scope.$index)"
>
扫码
</el-button>
</template>
</el-table-column>
<!-- <el-table-column
v-if="!viewStatus"
label="操作"
align="center"
width="80"
class-name="small-padding fixed-width"
fixed="right"
>
<template #default="scope">
<el-button
link
type="primary"
icon="Edit"
@click="handleSave(scope.row, scope.$index)"
>保存</el-button
>
</template>
</el-table-column> -->
</el-table>
</el-form>
</el-tab-pane>
</el-tabs>
<el-row
:gutter="10"
class="mb8"
style="margin-top: 15px; display: flex; align-items: center; justify-content: flex-start"
>
<el-col :span="3">
<span>制单人:{{ userStore.name }}</span>
</el-col>
<!-- <el-col :span="2">
<span>审核人:</span>
</el-col>
<el-col :span="2">
<span>单据状态:</span>
</el-col> -->
<el-col :span="6">
<el-row
:gutter="8"
style="display: flex; align-items: center; justify-content: flex-start"
>
<el-col :span="10">
<span>合计金额:{{ totalAmount ? totalAmount.toFixed(4) : 0 }}</span>
</el-col>
<!-- <el-col :span="10">
<el-input v-model="totalAmount" placeholder="" disabled />
</el-col> -->
</el-row>
</el-col>
</el-row>
<TraceNoDialog
:yp-name="ypName"
:row-data="rowData"
:open-dialog="openTraceNoDialog"
@submit="submit"
@cancel="openTraceNoDialog = false"
/>
</div>
</template>
<script setup name="transferManagent">
import {
addTransferProduct,
delTransferProduct,
getBusNoInit,
getCount,
getDispensaryList,
getInit,
getPharmacyList,
getTransferProductDetail,
getWarehouseList,
productTransferApproved,
reject,
submitApproval,
} from '../components/transferManagement';
import PopoverList from '@/components/OpenHis/popoverList/index.vue';
import transferManagement from '../components/transferManagement.vue';
import TraceNoDialog from '@/components/OpenHis/TraceNoDialog/index.vue';
import {formatDate, formatDateymd} from '@/utils/index';
import useUserStore from '@/store/modules/user';
import {useStore} from '@/store/store';
import useTagsViewStore from '@/store/modules/tagsView';
const tagsViewStore = useTagsViewStore();
const store = useStore();
const router = useRouter();
const route = useRoute();
const userStore = useUserStore();
const openTraceNoDialog = ref(false);
const ypName = ref('');
const rowData = ref({});
const { proxy } = getCurrentInstance();
const { warehous_type, purchase_type } = proxy.useDict('warehous_type', 'purchase_type');
const viewStatus = ref('');
const startTimeOld = ref('');
const endTimeOld = ref('');
const purchaseinventoryList = ref([]);
const open = ref(false);
const loading = ref(false);
const ids = ref([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const title = ref('');
const visible = ref(false);
const row = ref({});
const rowIndex = ref(-1);
const totalAmount = ref(0);
const editData = ref({});
const currentIndex = ref('');
const form = reactive({
purchaseinventoryList: [],
});
const rowList = ref([]);
const receiptHeaderForm = reactive({
busNo: undefined,
occurrenceTime: formatDate(new Date()),
});
const data = reactive({
isEdit: false,
isAdding: true,
queryParams: {
pageNo: 1,
pageSize: 10,
busNo: '',
},
rules: {
purposeLocationId: [{ required: true, message: '请选择目的仓库', trigger: 'change' }],
sourceLocationId: [{ required: true, message: '请选择源仓库', trigger: 'change' }],
medicationType: [{ required: true, message: '请选择药品类型', trigger: 'change' }],
},
tableRules: {
name: [{ required: true, message: '项目不能为空', trigger: 'change' }],
// volume: [
// { required: true, message: "规格不能为空", trigger: "blur" },
// ],
unitCode: [{ required: true, message: '计量单位不能为空', trigger: 'change' }],
itemQuantity: [{ required: true, message: '调拨数量不能为空', trigger: 'blur' }],
// lotNumber: [
// { required: true, message: "产品批号不能为空", trigger: "blur" },
// ],
// traceNo: [{ required: true, message: "追溯码不能为空", trigger: "blur" }],
// startTime: [
// { required: true, message: "开始时间不能为空", trigger: "blur" },
// ],
// endTime: [{ required: true, message: "结束时间不能为空", trigger: "blur" }],
// price: [{ required: true, message: "单价不能为空", trigger: "blur" }],
// totalPrice: [{ required: true, message: "总价不能为空", trigger: "blur" }],
},
});
const { queryParams, rules, tableRules } = toRefs(data);
const purposeTypeListOptions = ref(undefined);
const sourceTypeListOptions = ref(undefined);
const sourceLocationStoreIdListOptions = ref(undefined);
const purposeLocationStoreIdListOptions = ref(undefined);
const categoryListOptions = ref(undefined);
const selectedRows = ref([]); // 用于存储选中的行
const emit = defineEmits(['refresh']);
const tableRef = ref(undefined); // 表格引用
const currentRow = ref(undefined); // 当前操作的行
const medicineSearchKey = ref('');
const itemType = ref('');
const pageLoading = ref(false);
const forms = reactive({
purchaseinventoryList: [],
});
// 监听路由变化
watch(
() => route.query,
(newValue) => {
if (newValue.isEdit) {
data.isEdit = true;
pageLoading.value = true;
getTransferProductDetail({ busNo: newValue.supplyBusNo, pageSize: 1000, pageNo: 1 }).then(
(res) => {
editTable(res.data.records);
receiptHeaderForm.busNo = newValue.supplyBusNo;
receiptHeaderForm.sourceTypeEnum = res.data.records[0].sourceTypeEnum.toString();
receiptHeaderForm.sourceLocationId = res.data.records[0].sourceLocationId;
receiptHeaderForm.purposeTypeEnum = res.data.records[0].purposeTypeEnum.toString();
receiptHeaderForm.purposeLocationId = res.data.records[0].purposeLocationId;
receiptHeaderForm.medicationType = res.data.records[0].itemType.toString();
receiptHeaderForm.occurrenceTime = proxy.formatDateStr(
res.data.records[0].applyTime,
'YYYY-MM-DD HH:mm:ss'
);
handleChangePurposeTypeEnum(receiptHeaderForm.purposeTypeEnum, 1);
handleChangeSourceTypeEnum(receiptHeaderForm.sourceTypeEnum, 1);
pageLoading.value = false;
}
);
}
},
{ immediate: true }
);
watch(
() => form.purchaseinventoryList,
(newValue) => {
if (newValue && newValue.length > 0) {
if (viewStatus.value) {
handleTotalAmount();
}
}
},
{ immediate: true }
);
// 挂载时绑定事件
onMounted(() => {
document.addEventListener('click', handleClickOutside);
});
// 卸载时移除事件
onUnmounted(() => {
document.removeEventListener('click', handleClickOutside);
});
function getNextDayZeroTimeStamp(selectedTime) {
const now = new Date(selectedTime);
const nextDay = new Date(now);
nextDay.setDate(now.getDate());
return nextDay.getTime();
}
function changeValStart(val, index) {
const selectedTime = val;
const validTime = this.getNextDayZeroTimeStamp(selectedTime);
if (form.purchaseinventoryList[index].endTime) {
const endTime = formatDateymd(form.purchaseinventoryList[index].endTime);
const getNextDayZeroTime = this.getNextDayZeroTimeStamp(endTime);
if (getNextDayZeroTime < validTime) {
proxy.$message.warning('生产日期必须小于等于有效期!');
form.purchaseinventoryList[index].startTime = startTimeOld.value;
return;
}
editBatchTransfer(index);
}
}
function changeValEnd(val, index) {
const selectedTimes = val;
const validTimes = this.getNextDayZeroTimeStamp(selectedTimes);
if (form.purchaseinventoryList[index].startTime) {
const startTime = formatDateymd(form.purchaseinventoryList[index].startTime);
const getNextDayZeroTimes = this.getNextDayZeroTimeStamp(startTime);
if (getNextDayZeroTimes > validTimes) {
proxy.$message.warning('有效期必须大于等于生产日期!');
form.purchaseinventoryList[index].endTime = endTimeOld.value;
return;
}
editBatchTransfer(index);
}
}
function addNewRow() {
proxy.$refs['receiptHeaderRef'].validate((valid) => {
if (valid) {
// if (data.isAdding) {
// proxy.$message.warning("请先保存当前行后再新增");
// return;
// }
const newRow = {
id: '',
supplyBusNo: '',
occurrenceTime: '',
typeEnum_enumText: '',
statusEnum_enumText: '',
sourceTypeEnum: '',
sourceTypeEnum_dictText: '',
sourceLocationId: '', // 源仓库
purposeLocationId: '', //目的仓库
sourceLocationName: '',
purposeLocationName: '',
approverId_dictText: '',
applicantId_dictText: '',
approvalTime: '',
createTime: '',
itemTable: '',
itemQuantity: '',
itemMaxQuantity: '',
itemId: '',
remake: '',
// supplierId: "",
purposeTypeEnum_dictText: '',
purposeTypeEnum: '',
sourceLocationStoreId: '',
sourceLocationStoreName: '',
purposeLocationStoreId: '',
purposeLocationStoreName: '',
// practitionerId: "",
traceNo: '',
invoiceNo: '',
startTime: '',
endTime: '',
price: '',
totalPrice: '',
objQuantity: '',
orgQuantity: '',
categoryCode: '',
definitionId: '',
itemBusNo: '',
itemTableName: '',
itemType: '',
itemType_enumText: '',
lotNumber: '',
manufacturerText: '',
minUnitCode: '',
minUnitCode_dictText: '',
name: '',
orgLocation: '',
partPercent: '',
productName: '',
pyStr: '',
supplier: '',
supplierId: '',
unitCode: '',
unitCode_dictText: '',
volume: '',
wbStr: '',
ybNo: '',
// sellPrice: "",
// minSellPrice: "",
// locationInventoryList: [{value:1,label:'药房'},{value:2,label:'仓库'}], // 库房列表
unitList: {}, // 单位列表
isEditing: true, // 标记当前行是否正在编辑
error: false, // 新增 error 字段
isSave: false, // 当前行是否保存
};
form.purchaseinventoryList.push(newRow);
data.isEdit = true;
data.isAdding = true; // 设置标志位为 true表示有未保存的
}
});
}
function editTable(detailsList) {
pageLoading.value = true;
form.purchaseinventoryList = detailsList.map((item) => {
return {
...item,
busNo: receiptHeaderForm.busNo,
sourceTypeEnum: receiptHeaderForm.sourceTypeEnum,
sourceLocationId: receiptHeaderForm.sourceLocationId,
purposeTypeEnum: receiptHeaderForm.purposeTypeEnum,
purposeLocationId: receiptHeaderForm.purposeLocationId,
volume: item.totalVolume,
itemTable:
receiptHeaderForm.medicationType == 1
? 'med_medication_definition'
: 'adm_device_definition',
occurrenceTime: proxy.formatDateStr(receiptHeaderForm.occurrenceTime, 'YYYY-MM-DD HH:mm:ss'),
applyTime: proxy.formatDateStr(item.applyTime, 'YYYY-MM-DD HH:mm:ss'),
applicantId: userStore.id,
startTime: proxy.formatDateStr(item.startTime, 'YYYY-MM-DD HH:mm:ss'),
endTime: proxy.formatDateStr(item.endTime, 'YYYY-MM-DD HH:mm:ss'),
itemQuantity: item.itemQuantity,
totalSourceQuantityDisplay: formatInventory(
item.totalSourceQuantity,
item.partPercent,
item.unitCode_dictText,
item.minUnitCode_dictText
),
totalPurposeQuantityDisplay: formatInventory(
item.totalPurposeQuantity,
item.partPercent,
item.unitCode_dictText,
item.minUnitCode_dictText
),
itemQuantityDisplay: formatInventory(
item.itemQuantity,
item.partPercent,
item.unitCode_dictText,
item.minUnitCode_dictText
),
// 维护一个大小单位的map用来判断当前选中单位是大/小单位
unitCodeMap: {
[item.unitCode]: 'unit',
[item.minUnitCode]: 'minUnit',
},
unitList: [
{ label: item.unitCode_dictText, value: item.unitCode },
{
label: item.minUnitCode_dictText,
value: item.minUnitCode,
},
],
};
});
pageLoading.value = false;
}
// 点击行时记录当前行
function handleRowClick(row) {
currentRow.value = row;
}
// 监听表格外的点击事件
function handleClickOutside(event) {
// if (tableRef.value && !tableRef.value.$el.contains(event.target)) {
// if (currentRow.value) {
// handleSave(currentRow.value);
// currentRow.value = null; // 清空当前行
// }
// }
}
function handleItemQuantityChange(row, value) {
let quantityTemp = ''; // 转换成小单位的临时变量 做校验
// 大单位情况
if (row.unitCodeMap[row.unitCode] == 'unit') {
// 校验调拨数量不能大于原库存数量
quantityTemp = row.partPercent * value;
row.itemQuantity = quantityTemp;
} else {
row.itemQuantity = value;
quantityTemp = value;
}
row.totalPrice = ((row.price * quantityTemp) / row.partPercent).toFixed(2);
// 数量变更后重置保存标记,允许重新提交
row.isSave = false;
// 同步更新底部合计金额
handleTotalAmount();
}
function handelApply() {
pageLoading.value = true;
productTransferApproved(route.query.supplyBusNo)
.then((res) => {
if (res.code == 200) {
pageLoading.value = false;
proxy.$modal.msgSuccess('操作成功');
tagsViewStore.delView(router.currentRoute.value);
store.clearCurrentDataDB();
// 跳转到审核页面
router.replace({ path: '/financialManagement/medicationmanagement/billapproval', query: { type: 'transferManagent' } });
// proxy.$tab.closePage(route).then(({ visitedViews }) => { // 关闭当前页
// toLastView(visitedViews, route)
// })
}
})
.catch(() => {
pageLoading.value = false;
});
}
// 驳回
function handleReject() {
reject(route.query.supplyBusNo).then((res) => {
if (res.code == 200) {
proxy.$modal.msgSuccess('操作成功');
tagsViewStore.delView(router.currentRoute.value);
store.clearCurrentDataDB();
// 跳转到审核页面
router.replace({ path: '/financialManagement/medicationmanagement/billapproval', query: { type: 'transferManagent' } });
}
});
}
/** 提交审核按钮 */
function handleSubmitApproval() {
let length = form.purchaseinventoryList.length;
if (length < 1) {
proxy.$modal.msgWarning('请先添加单据');
} else if (!form.purchaseinventoryList[length - 1].isSave) {
proxy.$modal.msgWarning('第' + length + '行单据未保存,请先保存');
} else {
submitApproval(receiptHeaderForm.busNo).then((response) => {
proxy.$modal.msgSuccess('提交审批成功');
tagsViewStore.delView(router.currentRoute.value);
router.replace({ path: 'transferManagentList' });
store.clearCurrentDataDB();
});
}
}
// 药品列表搜索
function handleSearch(value) {
medicineSearchKey.value = value;
}
// 选择药品
function selectRow(rowValue, index) {
rowIndex.value = index;
form.purchaseinventoryList[index].sourceLocationId = receiptHeaderForm.sourceLocationId;
form.purchaseinventoryList[index].purposeLocationId = receiptHeaderForm.purposeLocationId;
form.purchaseinventoryList[index].busNo = receiptHeaderForm.busNo;
form.purchaseinventoryList[index].sourceTypeEnum = receiptHeaderForm.sourceTypeEnum;
form.purchaseinventoryList[index].purposeTypeEnum = receiptHeaderForm.purposeTypeEnum;
form.purchaseinventoryList[index].itemId = rowValue.definitionId;
form.purchaseinventoryList[index].name = rowValue.name;
form.purchaseinventoryList[index].volume = rowValue.volume;
form.purchaseinventoryList[index].minUnitCode = rowValue.minUnitCode;
form.purchaseinventoryList[index].unitCode = rowValue.unitCode;
form.purchaseinventoryList[index].manufacturerText = rowValue.manufacturerText;
form.purchaseinventoryList[index].partPercent = rowValue.partPercent;
form.purchaseinventoryList[index].unitList = rowValue.unitList[0];
form.purchaseinventoryList[index].lotNumber = rowValue.lotNumber;
// 补全单位字典文本formatInventory 依赖此字段格式化库存显示
form.purchaseinventoryList[index].unitCode_dictText = rowValue.unitCode_dictText;
form.purchaseinventoryList[index].minUnitCode_dictText = rowValue.minUnitCode_dictText;
form.purchaseinventoryList[index].itemQuantity = 0;
form.purchaseinventoryList[index].totalPrice = 0;
// 维护一个大小单位的map用来判断当前选中单位是大/小单位
form.purchaseinventoryList[index].unitCodeMap = {
[rowValue.unitCode]: 'unit',
[rowValue.minUnitCode]: 'minUnit',
};
form.purchaseinventoryList[index].unitList = [
{ label: rowValue.unitCode_dictText, value: rowValue.unitCode },
{
label: rowValue.minUnitCode_dictText,
value: rowValue.minUnitCode,
},
];
// 新单/编辑单统一使用行级仓库ID不再分支判断 route.query.supplyBusNo
handleLocationClick(
form.purchaseinventoryList[index].sourceLocationId,
form.purchaseinventoryList[index].purposeLocationId,
form.purchaseinventoryList[index].itemId,
index
);
editBatchTransfer(index); // todo
}
// 获取数量
function handleLocationClick(id, purposeLocationId, itemId, index) {
getCount({
itemId: itemId,
orgLocationId: id,
objLocationId: purposeLocationId,
lotNumber: form.purchaseinventoryList[index].lotNumber,
}).then((res) => {
if (res.data && res.data.length) {
// SQL 按 locationId 分组后可能有两条记录(源/目的),根据 locationId 精确匹配而非盲目取 res.data[0]
const srcId = String(id);
const purId = String(purposeLocationId);
const sourceRow = res.data.find(item => String(item.locationId) === srcId) || {};
const purposeRow = res.data.find(item => String(item.locationId) === purId) || {};
form.purchaseinventoryList[index].itemTable = sourceRow.itemTable || '';
form.purchaseinventoryList[index].supplierId = sourceRow.supplierId || '';
form.purchaseinventoryList[index].startTime = formatDateymd(sourceRow.productionDate) || '';
form.purchaseinventoryList[index].endTime = formatDateymd(sourceRow.expirationDate) || '';
form.purchaseinventoryList[index].price = sourceRow.price;
form.purchaseinventoryList[index].totalSourceQuantity = sourceRow.orgQuantity || 0;
form.purchaseinventoryList[index].totalPurposeQuantity = purposeRow.objQuantity || 0;
form.purchaseinventoryList[index].totalSourceQuantityDisplay = formatInventory(
sourceRow.orgQuantity || 0,
form.purchaseinventoryList[index].partPercent,
form.purchaseinventoryList[index].unitCode_dictText,
form.purchaseinventoryList[index].minUnitCode_dictText
);
form.purchaseinventoryList[index].totalPurposeQuantityDisplay = formatInventory(
purposeRow.objQuantity || 0,
form.purchaseinventoryList[index].partPercent,
form.purchaseinventoryList[index].unitCode_dictText,
form.purchaseinventoryList[index].minUnitCode_dictText
);
// form.purchaseinventoryList[index].itemQuantityDisplay = formatInventory(
// res.data[0].itemQuantity,
// res.data[0].partPercent,
// res.data[0].unitCode_dictText,
// res.data[0].minUnitCode_dictText
// );
} else {
form.purchaseinventoryList[index].totalPurposeQuantity = 0;
form.purchaseinventoryList[index].totalSourceQuantity = 0;
form.purchaseinventoryList[index].price = 0;
proxy.$message.warning('仓库数量为0无法调用');
}
});
}
// 切换仓库类型获取药房/药库列表 目的仓库切换
function handleChangePurposeTypeEnum(value, type) {
if (value == 16) {
getPharmacyList().then((res) => {
purposeTypeListOptions.value = res.data;
if (!route.query.supplyBusNo && !type) {
receiptHeaderForm.purposeLocationId = '';
receiptHeaderForm.purposeLocationId1 = '';
}
});
} else if (value == 11) {
getDispensaryList().then((res) => {
purposeTypeListOptions.value = res.data;
if (!route.query.supplyBusNo && !type) {
receiptHeaderForm.purposeLocationId = '';
receiptHeaderForm.purposeLocationId1 = '';
}
});
} else if (value == 17) {
// 耗材库类型
getWarehouseList().then((res) => {
purposeTypeListOptions.value = res.data;
if (!route.query.supplyBusNo && !type) {
receiptHeaderForm.purposeLocationId = '';
receiptHeaderForm.purposeLocationId1 = '';
}
});
}
}
// 源仓库切换
function handleChangeSourceTypeEnum(value, type) {
if (value == 16) {
getPharmacyList().then((res) => {
sourceTypeListOptions.value = res.data;
if (!route.query.supplyBusNo && !type) {
receiptHeaderForm.sourceLocationId = '';
receiptHeaderForm.sourceLocationId1 = '';
}
});
} else if (value == 11) {
getDispensaryList().then((res) => {
sourceTypeListOptions.value = res.data;
if (!route.query.supplyBusNo && !type) {
receiptHeaderForm.sourceLocationId = '';
receiptHeaderForm.sourceLocationId1 = '';
}
});
} else if (value == 17) {
// 耗材库类型
getWarehouseList().then((res) => {
sourceTypeListOptions.value = res.data;
if (!route.query.supplyBusNo && !type) {
receiptHeaderForm.sourceLocationId = '';
receiptHeaderForm.sourceLocationId1 = '';
}
});
}
}
/**
* 格式化库存数量显示(大单位情况)
* @param quantity 小单位库存数量
* @param partPercent 拆零比
* @param unitCode 大单位
* @param minUnitCode 小单位
*/
function formatInventory(quantity, partPercent, unitCode, minUnitCode) {
// 处理负数情况
const isNegative = quantity < 0;
const absQuantity = Math.abs(quantity);
if (absQuantity % partPercent !== 0) {
const integerPart = Math.floor(absQuantity / partPercent);
const decimalPart = absQuantity % partPercent;
let result = integerPart.toString() + ' ' + unitCode;
if (decimalPart > 0) {
result += decimalPart.toString() + ' ' + minUnitCode;
}
return isNegative ? '-' + result : result;
}
// 整除时也需拼接单位后缀,否则显示为纯数字缺少单位信息
const result = (absQuantity / partPercent) + ' ' + unitCode;
return isNegative ? '-' + result : result;
}
// 单位处理
function handleUnitCodeChange(row, index, value) {
if (row.unitCodeMap[value] == 'unit') {
row.totalSourceQuantityDisplay = formatInventory(
row.totalSourceQuantity,
row.partPercent,
row.unitCode_dictText,
row.minUnitCode_dictText
);
row.totalPurposeQuantityDisplay = formatInventory(
row.totalPurposeQuantity,
row.partPercent,
row.unitCode_dictText,
row.minUnitCode_dictText
);
row.itemQuantityDisplay = formatInventory(
row.itemQuantity,
row.partPercent,
row.unitCode_dictText,
row.minUnitCode_dictText
);
} else {
row.totalSourceQuantityDisplay = row.totalSourceQuantity;
row.totalPurposeQuantityDisplay = row.totalPurposeQuantity;
row.itemQuantityDisplay = row.itemQuantity;
}
}
//修改备注
function remakeBlur(row, index) {
editBatchTransfer(index);
}
function lotNumberBlur(row, index) {
editBatchTransfer(index);
}
function traceNoBlur(row, index) {
editBatchTransfer(index);
}
function editBatchTransfer(index) {
if (route.query.supplyBusNo) {
if (queryParams.value.pageNo == 1) {
forms.purchaseinventoryList[index] = form.purchaseinventoryList[index];
} else {
let editIndex =
(Number(queryParams.value.pageNo) - 1) * Number(queryParams.value.pageSize) + index;
forms.purchaseinventoryList[editIndex] = form.purchaseinventoryList[index];
}
}
}
function handleSave(row, index) {
// 过滤出未保存的行,已保存的行不重复提交
const listToCheck = route.query.supplyBusNo
? forms.purchaseinventoryList
: form.purchaseinventoryList;
const unsavedList = listToCheck.filter(item => !item.isSave);
if (unsavedList.length === 0) {
proxy.$modal.msgWarning('所有行均已保存,无需重复提交');
return;
}
// 先校验表头
proxy.$refs['receiptHeaderRef'].validate((headerValid) => {
if (!headerValid) return;
// 逐行校验(避免异步回调导致重复提交)
const rowsToSave = [];
for (let i = 0; i < form.purchaseinventoryList.length; i++) {
const r = form.purchaseinventoryList[i];
if (!r) continue;
// 跳过已保存的行,避免重复提交导致预扣减库存叠加
if (r.isSave) continue;
// 校验当前行的必填字段
let rowValid = true;
for (const prop of ['name', 'unitCode']) {
const formRef = proxy.$refs['formRef'];
if (formRef && formRef.validateField) {
formRef.validateField(`purchaseinventoryList.${i}.${prop}`, (valid) => {
if (valid) rowValid = false;
});
}
}
if (!rowValid) {
proxy.$modal.msgWarning('第' + (i + 1) + '行数据不完整,请检查');
return;
}
// 单价校验
if (!r.price || r.price <= 0) {
proxy.$modal.msgWarning('第' + (i + 1) + '行调拨单价不能为空或为0');
return;
}
// 单位处理
const rowData = route.query.supplyBusNo
? JSON.parse(JSON.stringify(forms.purchaseinventoryList[i]))
: JSON.parse(JSON.stringify(r));
delete rowData.itemMaxQuantity;
if (rowData.unitCode == rowData.unitList?.minUnitCode) {
rowData.itemQuantity = r.olditemQuantity || r.itemQuantity;
} else {
rowData.itemQuantity = r.itemMaxQuantity || r.itemQuantity;
}
if (rowData.unitCode == rowData.unitCode_dictText) {
if (rowData.unitCode_dictText == rowData.unitList?.minUnitCode_dictText) {
rowData.unitCode = rowData.unitList.minUnitCode;
} else {
rowData.unitCode = rowData.unitList.unitCode;
rowData.unitCode_dictText = rowData.unitList.unitCode_dictText;
}
}
if (rowData.unitCode == rowData.unitList?.unitCode) {
rowData.unitCode_dictText = rowData.unitList.unitCode_dictText;
} else if (rowData.unitCode == rowData.unitList?.minUnitCode) {
rowData.unitCode_dictText = rowData.unitList.minUnitCode_dictText;
}
// 计算总价
r.totalPrice = r.price * rowData.itemQuantity;
rowsToSave.push(rowData);
}
// 所有行校验通过,一次性提交
if (rowsToSave.length > 0) {
addTransferProducts(rowsToSave);
}
});
}
function addTransferProducts(rowList) {
addTransferProduct(JSON.parse(JSON.stringify(rowList))).then((res) => {
if (res.data) {
proxy.$message.success('保存成功!');
let newIdIndex = 0;
form.purchaseinventoryList.map((row, index) => {
// 只有未保存的行才会拿到新 id和提交顺序一致
if (!row.isSave && res.data[newIdIndex]) {
form.purchaseinventoryList[index].id = res.data[newIdIndex];
newIdIndex++;
}
form.purchaseinventoryList[index].isSave = true;
});
if (route.query.supplyBusNo) {
// 编辑
let newIdIdx = 0;
forms.purchaseinventoryList.map((row, index) => {
if (!row.isSave && res.data[newIdIdx]) {
forms.purchaseinventoryList[index].id = res.data[newIdIdx];
newIdIdx++;
}
forms.purchaseinventoryList[index].isSave = true;
});
}
store.setCurrentDataDB({
purchaseinventoryList: form.purchaseinventoryList,
receiptHeaderForm: receiptHeaderForm,
});
if (route.query.supplyBusNo) {
// 编辑
// store.setCurrentDataDBAll({purchaseinventoryList: forms.purchaseinventoryList});
}
}
});
}
function handleScan(row, index) {
rowData.value = row;
rowData.value.locationId = receiptHeaderForm.purposeLocationId;
rowData.value.itemType = receiptHeaderForm.medicationType;
ypName.value = row.name;
openTraceNoDialog.value = true;
currentIndex.value = index;
}
function submit(value) {
if (form.purchaseinventoryList[currentIndex.value].traceNo) {
form.purchaseinventoryList[currentIndex.value].traceNo =
form.purchaseinventoryList[currentIndex.value].traceNo + ',' + value;
} else {
form.purchaseinventoryList[currentIndex.value].traceNo = value;
}
openTraceNoDialog.value = false;
}
/** 选择条数 */
function handleSelectionChange(selection) {
// selectedData.value = selection.map((item) => ({ ...item })); // 存储选择的行数据
ids.value = selection.map((item) => item.id);
selectedRows.value = selection;
single.value = selection.length != 1;
multiple.value = !selection.length;
}
function deleteSelectedRows() {
// 删除行
let length = selectedRows.value.length;
let ids = [];
if (selectedRows.value[0].id) {
ids = selectedRows.value.map((item) => {
return item.id;
});
}
if (selectedRows.value[length - 1].isSave) {
delTransferProduct(ids).then((res) => {
if (res.code == 200) {
proxy.$message.success('删除成功');
}
});
} else {
if (length > 1) {
delTransferProduct(ids).then((res) => {
if (res.code == 200) {
proxy.$message.success('删除成功');
}
});
}
}
form.purchaseinventoryList = form.purchaseinventoryList.filter(
(row) => !selectedRows.value.includes(row)
);
if (form.purchaseinventoryList && form.purchaseinventoryList.length > 0) {
data.isEdit = true;
} else {
data.isEdit = false;
}
data.isAdding = false;
}
/**计算合计金额 */
function handleTotalAmount() {
totalAmount.value = form.purchaseinventoryList.reduce((accumulator, currentRow) => {
return accumulator + (Number(currentRow.totalPrice) || 0);
}, 0);
}
/** 删除按钮操作 */
function handleDelete(row) {
const delId = row.id || ids.value;
data.isAdding = false;
proxy.$modal
.confirm('是否确认删除以上数据?')
.then(function () {
return delTransferProduct({ ids: delId.join(',') });
})
.then(() => {
proxy.$modal.msgSuccess('删除成功');
})
.catch(() => {});
}
/** 调拨管理查询下拉树结构 */
function getTransferProductTypeList() {
data.isAdding = false;
getInit().then((response) => {
categoryListOptions.value = response.data.categoryListOptions;
});
}
function getBusNoInitList() {
if (route.query.supplyBusNo) {
store.clearCurrentDataDB();
// store.clearCurrentDataDBAll()
viewStatus.value = route.query.view ? route.query.view : '';
receiptHeaderForm.busNo = route.query.supplyBusNo;
sessionStorage.setItem('busNo', '');
} else {
if (!sessionStorage.getItem('busNo')) {
// store.clearCurrentDataDBAll()
store.clearCurrentDataDB();
getBusNoInit().then((response) => {
receiptHeaderForm.busNo = response.data.busNo;
sessionStorage.setItem('busNo', receiptHeaderForm.busNo);
// busNoAdd.value = response.data.busNo; // 单据号新增
});
} else {
receiptHeaderForm.busNo = sessionStorage.getItem('busNo');
}
}
}
getTransferProductTypeList();
getBusNoInitList();
// defineExpose({
// show,
// edit,
// });
// 导出
const exportRequiredParams = ref({
pageNo: 1,
pageSize: 10,
busNo: route.query.supplyBusNo,
});
function handleExport() {
proxy.downloadGet(
'/inventory-manage/transfer/excel-out',
{
...exportRequiredParams.value,
},
`调拨单据明细记录_${proxy.formatDateStr(new Date(), 'YYYY-MM-DD')}.xlsx`
);
}
</script>
<style scoped>
.custom-tree-node {
display: flex;
align-items: center;
}
.title {
font-weight: bold;
font-size: large;
margin-bottom: 10px;
}
.error-border {
border: 1px solid red;
}
</style>