占用号源配置

main
鹿和sa0ChunLuyu 1 week ago
parent 24a292c61e
commit 3013ecffaa

@ -434,6 +434,21 @@ public function GetOptimalPlan($type, $regnum, $entrustids, $episodeid, $appoint
$endDate = new DateTime(); $endDate = new DateTime();
$endDate->modify('+' . $dateRange . ' day'); $endDate->modify('+' . $dateRange . ' day');
// 批量查询每个检查项目的 use_seats
$entrustIds = array_column($items, 'entrust_id');
$useSeatsMap = [];
if (!empty($entrustIds)) {
$useSeatsMap = DB::table('s_list')
->join('s_check_item', 's_list.entrust', '=', 's_check_item.item_name')
->whereIn('s_list.entrust_id', $entrustIds)
->pluck('s_check_item.use_seats', 's_list.entrust_id')
->toArray();
}
foreach ($items as &$item) {
$item['use_seats'] = $useSeatsMap[$item['entrust_id']] ?? 1;
}
unset($item);
if ($type === 'full_day') { if ($type === 'full_day') {
return $this->doFullDayAssign($startDate, $endDate, $regnum, $entrustids, $episodeid, $appointment_type, $items, $scope, $userDeptId, $occupied_plan_ids); return $this->doFullDayAssign($startDate, $endDate, $regnum, $entrustids, $episodeid, $appointment_type, $items, $scope, $userDeptId, $occupied_plan_ids);
} elseif ($type === 'same_day_slot') { } elseif ($type === 'same_day_slot') {
@ -496,10 +511,11 @@ private function doOptimalTimeAssign($startDate, $endDate, $regnum, $entrustids,
$assigned = false; $assigned = false;
foreach ($availablePlans as $plan) { foreach ($availablePlans as $plan) {
$planId = $plan->id; $planId = $plan->id;
$useSeats = $item['use_seats'] ?? 1;
$remaining = ($plan->count ?? 0) - ($plan->used_count ?? 0); $remaining = ($plan->count ?? 0) - ($plan->used_count ?? 0);
$used = $usedCapacity[$planId] ?? 0; $used = $usedCapacity[$planId] ?? 0;
if (($remaining - $used) <= 0) continue; if (($remaining - $used) < $useSeats) continue;
// 冲突检查:同一科室组内,不同排班时间不能重叠 // 冲突检查:同一科室组内,不同排班时间不能重叠
if ($this->isTimeConflict($assignedPlans, $plan)) continue; if ($this->isTimeConflict($assignedPlans, $plan)) continue;
@ -514,7 +530,7 @@ private function doOptimalTimeAssign($startDate, $endDate, $regnum, $entrustids,
'begin_time' => $plan->begin_time, 'begin_time' => $plan->begin_time,
'end_time' => $plan->end_time 'end_time' => $plan->end_time
]; ];
$usedCapacity[$planId] = ($usedCapacity[$planId] ?? 0) + 1; $usedCapacity[$planId] = ($usedCapacity[$planId] ?? 0) + $useSeats;
$assignedPlans[] = [ $assignedPlans[] = [
'resource_name' => $plan->department_resources_name, 'resource_name' => $plan->department_resources_name,
'begin_time' => $plan->begin_time, 'begin_time' => $plan->begin_time,
@ -587,10 +603,11 @@ private function doFullDayAssign($startDate, $endDate, $regnum, $entrustids, $ep
$assigned = false; $assigned = false;
foreach ($availablePlans as $plan) { foreach ($availablePlans as $plan) {
$planId = $plan->id; $planId = $plan->id;
$useSeats = $item['use_seats'] ?? 1;
$remaining = ($plan->count ?? 0) - ($plan->used_count ?? 0); $remaining = ($plan->count ?? 0) - ($plan->used_count ?? 0);
$used = $usedCapacity[$planId] ?? 0; $used = $usedCapacity[$planId] ?? 0;
if (($remaining - $used) <= 0) continue; if (($remaining - $used) < $useSeats) continue;
// 冲突检查:同一科室组内,不同排班时间不能重叠 // 冲突检查:同一科室组内,不同排班时间不能重叠
if ($this->isTimeConflict($assignedPlans, $plan)) continue; if ($this->isTimeConflict($assignedPlans, $plan)) continue;
@ -603,7 +620,7 @@ private function doFullDayAssign($startDate, $endDate, $regnum, $entrustids, $ep
'begin_time' => $plan->begin_time, 'begin_time' => $plan->begin_time,
'end_time' => $plan->end_time 'end_time' => $plan->end_time
]; ];
$usedCapacity[$planId] = ($usedCapacity[$planId] ?? 0) + 1; $usedCapacity[$planId] = ($usedCapacity[$planId] ?? 0) + $useSeats;
$assignedPlans[] = [ $assignedPlans[] = [
'resource_name' => $plan->department_resources_name, 'resource_name' => $plan->department_resources_name,
'begin_time' => $plan->begin_time, 'begin_time' => $plan->begin_time,
@ -705,10 +722,11 @@ private function doSameDaySlotAssign($startDate, $endDate, $regnum, $entrustids,
$assigned = false; $assigned = false;
foreach ($availablePlans as $plan) { foreach ($availablePlans as $plan) {
$planId = $plan->id; $planId = $plan->id;
$useSeats = $item['use_seats'] ?? 1;
$remaining = ($plan->count ?? 0) - ($plan->used_count ?? 0); $remaining = ($plan->count ?? 0) - ($plan->used_count ?? 0);
$used = $usedCapacity[$planId] ?? 0; $used = $usedCapacity[$planId] ?? 0;
if (($remaining - $used) <= 0) continue; if (($remaining - $used) < $useSeats) continue;
// 冲突检查:同一科室组内,不同排班时间不能重叠 // 冲突检查:同一科室组内,不同排班时间不能重叠
if ($this->isTimeConflict($assignedPlans, $plan)) continue; if ($this->isTimeConflict($assignedPlans, $plan)) continue;
@ -721,7 +739,7 @@ private function doSameDaySlotAssign($startDate, $endDate, $regnum, $entrustids,
'begin_time' => $plan->begin_time, 'begin_time' => $plan->begin_time,
'end_time' => $plan->end_time 'end_time' => $plan->end_time
]; ];
$usedCapacity[$planId] = ($usedCapacity[$planId] ?? 0) + 1; $usedCapacity[$planId] = ($usedCapacity[$planId] ?? 0) + $useSeats;
$assignedPlans[] = [ $assignedPlans[] = [
'resource_name' => $plan->department_resources_name, 'resource_name' => $plan->department_resources_name,
'begin_time' => $plan->begin_time, 'begin_time' => $plan->begin_time,

@ -206,6 +206,17 @@
<el-button type="primary" @click="resultDialogVisible = false">确定</el-button> <el-button type="primary" @click="resultDialogVisible = false">确定</el-button>
</template> </template>
</el-dialog> </el-dialog>
<el-dialog v-model="recommendResultVisible" title="预约推荐结果" width="800">
<div style="display: flex; align-items: flex-start; gap: 12px;">
<el-icon style="color: #e6a23c; font-size: 24px; flex-shrink: 0; margin-top: 2px;">
<WarningFilled />
</el-icon>
<div v-html="recommendResultHtml"></div>
</div>
<template #footer>
<el-button type="primary" @click="recommendResultVisible = false">确定</el-button>
</template>
</el-dialog>
</div> </div>
</template> </template>
@ -292,10 +303,13 @@
let crossTimeDialogVisible = ref(false) let crossTimeDialogVisible = ref(false)
let crossTimeDialogHtml = ref('') let crossTimeDialogHtml = ref('')
let cachedCrossDialogData = ref(null) // dialog { html, idx } let cachedCrossDialogData = ref(null) // dialog { html, idx }
let cachedRecommendResultData = ref(null) // dialog { html, idx }
let conflictDialogVisible = ref(false) let conflictDialogVisible = ref(false)
let conflictDialogHtml = ref('') let conflictDialogHtml = ref('')
let resultDialogVisible = ref(false) let resultDialogVisible = ref(false)
let resultDialogHtml = ref('') let resultDialogHtml = ref('')
let recommendResultVisible = ref(false)
let recommendResultHtml = ref('')
const getWeekday = (date1) => { const getWeekday = (date1) => {
let days = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']; let days = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
const date = new Date(date1); // : const date = new Date(date1); // :
@ -406,6 +420,17 @@
cachedCrossDialogData.value = null cachedCrossDialogData.value = null
} }
} }
// dialog
if (cachedRecommendResultData.value) {
const currentIdx = entrustTableDate.value.findIndex(
r => selectedMianListId.value.includes(r.id)
) + 1
if (currentIdx === cachedRecommendResultData.value.idx) {
recommendResultHtml.value = cachedRecommendResultData.value.html
recommendResultVisible.value = true
cachedRecommendResultData.value = null
}
}
checkSameSourceRoom() checkSameSourceRoom()
} }
const checkSameSourceRoom = () => { const checkSameSourceRoom = () => {
@ -775,6 +800,36 @@
} }
} }
//
const matchedIds = new Set(res.data.map(v => v.id))
const unmatched = items.filter(v => !matchedIds.has(v.id))
if (unmatched.length > 0) {
let html = '<table style="width:100%;border-collapse:collapse;font-size:14px;">'
html += '<tr style="background:#d5dce6;"><td style="padding:4px 8px;border:1px solid #ddd;font-weight:700;">序号</td><td style="padding:4px 8px;border:1px solid #ddd;font-weight:700;">项目名称</td><td style="padding:4px 8px;border:1px solid #ddd;font-weight:700;">排班资源</td><td style="padding:4px 8px;border:1px solid #ddd;font-weight:700;">日期</td><td style="padding:4px 8px;border:1px solid #ddd;font-weight:700;">时间段</td><td style="padding:4px 8px;border:1px solid #ddd;font-weight:700;">结果</td></tr>'
let idx = 1
res.data.forEach(v => {
const entrustName = entrustTableDate.value.find(r => r.id === v.id)?.entrust || ''
html += '<tr><td style="padding:4px 8px;border:1px solid #ddd;">' + (idx++) + '</td><td style="padding:4px 8px;border:1px solid #ddd;">' + entrustName + '</td><td style="padding:4px 8px;border:1px solid #ddd;">' + v.department_resources_name + '</td><td style="padding:4px 8px;border:1px solid #ddd;">' + v.date + '</td><td style="padding:4px 8px;border:1px solid #ddd;">' + v.begin_time?.substring(0, 5) + '-' + v.end_time?.substring(0, 5) + '</td><td style="padding:4px 8px;border:1px solid #ddd;color:green;">✅ 成功匹配</td></tr>'
})
unmatched.forEach(v => {
const entrustName = entrustTableDate.value.find(r => r.id === v.id)?.entrust || ''
html += '<tr><td style="padding:4px 8px;border:1px solid #ddd;">' + (idx++) + '</td><td style="padding:4px 8px;border:1px solid #ddd;">' + entrustName + '</td><td style="padding:4px 8px;border:1px solid #ddd;" colspan="3">—</td><td style="padding:4px 8px;border:1px solid #ddd;color:red;">❌ 未找到合适号源</td></tr>'
})
html += '</table>'
//
const unmatchedIndices = unmatched.map(v => entrustTableDate.value.findIndex(r => r.id === v.id) + 1).filter(i => i > 0)
const minUnmatchedIdx = Math.min(...unmatchedIndices)
const selectedIdx = entrustTableDate.value.findIndex(
r => selectedMianListId.value.includes(r.id)
) + 1
if (selectedIdx === minUnmatchedIdx) {
recommendResultHtml.value = html
recommendResultVisible.value = true
} else {
cachedRecommendResultData.value = { html: html, idx: minUnmatchedIdx }
}
}
// //
const newHighlightIds = findHighlightPlanIds() const newHighlightIds = findHighlightPlanIds()
if (newHighlightIds.length > 0) { if (newHighlightIds.length > 0) {
@ -1008,11 +1063,21 @@
const matchedIds = new Set(res.data.map(v => v.id)) const matchedIds = new Set(res.data.map(v => v.id))
const unmatched = items.filter(v => !matchedIds.has(v.id)) const unmatched = items.filter(v => !matchedIds.has(v.id))
if (unmatched.length > 0) { if (unmatched.length > 0) {
let msg = '✅ 成功匹配 ' + res.data.length + ' 项:\n' //
msg += res.data.map(v => ' ' + v.department_resources_name + ' ' + v.date + ' ' + v.begin_time?.substring(0, 5) + '-' + v.end_time?.substring(0, 5)).join('\n') let html = '<table style="width:100%;border-collapse:collapse;font-size:14px;">'
msg += '\n\n❌ 未匹配 ' + unmatched.length + ' 项:\n' html += '<tr style="background:#d5dce6;"><td style="padding:4px 8px;border:1px solid #ddd;font-weight:700;">序号</td><td style="padding:4px 8px;border:1px solid #ddd;font-weight:700;">项目名称</td><td style="padding:4px 8px;border:1px solid #ddd;font-weight:700;">排班资源</td><td style="padding:4px 8px;border:1px solid #ddd;font-weight:700;">日期</td><td style="padding:4px 8px;border:1px solid #ddd;font-weight:700;">时间段</td><td style="padding:4px 8px;border:1px solid #ddd;font-weight:700;">结果</td></tr>'
msg += unmatched.map(v => ' ' + (items.findIndex(i => i.id === v.id) + 1) + '、' + v.entrust_id + ':未找到合适号源').join('\n') let idx = 1
ElMessageBox.confirm(msg, '预约推荐结果', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning', showCancelButton: false }) res.data.forEach(v => {
const entrustName = entrustTableDate.value.find(r => r.id === v.id)?.entrust || ''
html += '<tr><td style="padding:4px 8px;border:1px solid #ddd;">' + (idx++) + '</td><td style="padding:4px 8px;border:1px solid #ddd;">' + entrustName + '</td><td style="padding:4px 8px;border:1px solid #ddd;">' + v.department_resources_name + '</td><td style="padding:4px 8px;border:1px solid #ddd;">' + v.date + '</td><td style="padding:4px 8px;border:1px solid #ddd;">' + v.begin_time?.substring(0, 5) + '-' + v.end_time?.substring(0, 5) + '</td><td style="padding:4px 8px;border:1px solid #ddd;color:green;">✅ 成功匹配</td></tr>'
})
unmatched.forEach(v => {
const entrustName = entrustTableDate.value.find(r => r.id === v.id)?.entrust || ''
html += '<tr><td style="padding:4px 8px;border:1px solid #ddd;">' + (idx++) + '</td><td style="padding:4px 8px;border:1px solid #ddd;">' + entrustName + '</td><td style="padding:4px 8px;border:1px solid #ddd;" colspan="3">—</td><td style="padding:4px 8px;border:1px solid #ddd;color:red;">❌ 未找到合适号源</td></tr>'
})
html += '</table>'
recommendResultHtml.value = html
recommendResultVisible.value = true
} else if (res.data.length === 0) { } else if (res.data.length === 0) {
ElMessageBox.confirm('所有项目均未匹配到合适号源,请重新选择号源', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'error', showCancelButton: false }) ElMessageBox.confirm('所有项目均未匹配到合适号源,请重新选择号源', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'error', showCancelButton: false })
} }

@ -127,6 +127,10 @@
<div class="row"><span class="title">检查时间</span><el-input v-model="selectedItemInfo.check_time" <div class="row"><span class="title">检查时间</span><el-input v-model="selectedItemInfo.check_time"
placeholder="请输入此项检查的时长" style="width: 200px;" /></div> placeholder="请输入此项检查的时长" style="width: 200px;" /></div>
<div class="row"><span class="title">占用号源</span><el-input-number v-model="selectedItemInfo.use_seats"
:min="1" :max="99" style="width: 200px;" />
<div style="margin-left: 4px;">预约时占用的号源数量默认1</div>
</div>
<div class="row"><span class="title">等待时间</span><el-input v-model="selectedItemInfo.check_begin_time" <div class="row"><span class="title">等待时间</span><el-input v-model="selectedItemInfo.check_begin_time"
placeholder="开医嘱后,等待时间" style="width: 200px;" /> placeholder="开医嘱后,等待时间" style="width: 200px;" />
<div style="margin-left: 4px;">医嘱开具后预约时间需在设定的等待期之后,单位分钟</div> <div style="margin-left: 4px;">医嘱开具后预约时间需在设定的等待期之后,单位分钟</div>
@ -350,7 +354,8 @@
check_time: '', check_time: '',
check_begin_time: '', check_begin_time: '',
check_notice: '', check_notice: '',
warm_tips: '' warm_tips: '',
use_seats: 1
}) })
//list //list
let BigClassList = ref([]); let BigClassList = ref([]);
@ -646,6 +651,7 @@
selectedItemInfo.value.limosis = row.limosis selectedItemInfo.value.limosis = row.limosis
selectedItemInfo.value.reservation_method = row.reservation_method selectedItemInfo.value.reservation_method = row.reservation_method
selectedItemInfo.value.check_time = row.check_time selectedItemInfo.value.check_time = row.check_time
selectedItemInfo.value.use_seats = row.use_seats || 1
if (row.check_begin_time == '' || row.check_begin_time == null) { if (row.check_begin_time == '' || row.check_begin_time == null) {
row.check_begin_time = 0 row.check_begin_time = 0
} }

@ -113,6 +113,10 @@
<div class="row"><span class="title">检查时间</span><el-input v-model="selectedItemInfo.check_time" <div class="row"><span class="title">检查时间</span><el-input v-model="selectedItemInfo.check_time"
placeholder="请输入此项检查的时长" style="width: 200px;" /></div> placeholder="请输入此项检查的时长" style="width: 200px;" /></div>
<div class="row"><span class="title">占用号源</span><el-input-number v-model="selectedItemInfo.use_seats"
:min="1" :max="99" style="width: 200px;" />
<div style="margin-left: 4px;">预约时占用的号源数量默认1</div>
</div>
<div class="row"><span class="title">等待时间</span><el-input v-model="selectedItemInfo.check_begin_time" <div class="row"><span class="title">等待时间</span><el-input v-model="selectedItemInfo.check_begin_time"
placeholder="开医嘱后,等待时间" style="width: 200px;" /> placeholder="开医嘱后,等待时间" style="width: 200px;" />
<div style="margin-left: 4px;">医嘱开具后预约时间需在设定的等待期之后,单位分钟</div> <div style="margin-left: 4px;">医嘱开具后预约时间需在设定的等待期之后,单位分钟</div>
@ -323,7 +327,8 @@
check_time: '', check_time: '',
check_begin_time: '', check_begin_time: '',
check_notice: '', check_notice: '',
warm_tips: '' warm_tips: '',
use_seats: 1
}) })
//list //list
let BigClassList = ref([]); let BigClassList = ref([]);
@ -595,6 +600,7 @@
selectedItemInfo.value.limosis = row.limosis selectedItemInfo.value.limosis = row.limosis
selectedItemInfo.value.reservation_method = row.reservation_method selectedItemInfo.value.reservation_method = row.reservation_method
selectedItemInfo.value.check_time = row.check_time selectedItemInfo.value.check_time = row.check_time
selectedItemInfo.value.use_seats = row.use_seats || 1
if (row.check_begin_time == '' || row.check_begin_time == null) { if (row.check_begin_time == '' || row.check_begin_time == null) {
row.check_begin_time = 0 row.check_begin_time = 0
} }

Loading…
Cancel
Save