自动匹配

main
鹿和sa0ChunLuyu 2 weeks ago
parent 3d22681335
commit 6eb4f6a0f0

@ -427,97 +427,189 @@ public function GetOptimalPlan($type, $regnum, $entrustids, $episodeid, $appoint
{
date_default_timezone_set('PRC');
$dateRange = config('app.globals.可用号源查询范围');
$result = [];
$usedCapacity = []; // 记录已分配的号源容量key: roster_detail_id, value: 已分配数
// 遍历日期范围
$startDate = new DateTime();
$endDate = new DateTime();
$endDate->modify('+' . $dateRange . ' day');
if ($type === 'full_day') {
return $this->doFullDayAssign($startDate, $endDate, $regnum, $entrustids, $episodeid, $appointment_type, $items, $scope, $userDeptId);
} else {
return $this->doOptimalTimeAssign($startDate, $endDate, $regnum, $entrustids, $episodeid, $appointment_type, $items, $scope, $userDeptId);
}
}
// 最优时间分配:逐个 item 找最早可用号源,可跨天
private function doOptimalTimeAssign($startDate, $endDate, $regnum, $entrustids, $episodeid, $appointment_type, $items, $scope, $userDeptId)
{
$result = [];
$usedCapacity = [];
$assignedPlans = []; // 已分配的时间段 [{resource_name, begin, end}]
$currentDate = clone $startDate;
$remainingItems = $items;
while ($currentDate <= $endDate) {
while ($currentDate <= $endDate && !empty($remainingItems)) {
$nowdate = $currentDate->format('Y-m-d');
$availablePlans = $this->getAvailablePlansForDate($regnum, $entrustids, $episodeid, $appointment_type, $nowdate, $scope, $userDeptId);
// 获取当前日期的可用号源
$s = $this->GetEnablePlan($regnum, $entrustids, $episodeid, $appointment_type, $nowdate, $scope, $userDeptId);
if (!$s['status']) {
if (empty($availablePlans)) {
$currentDate->modify('+1 day');
continue;
}
$planList = $s['data']['plan_list'] ?? [];
if (count($planList) == 0) {
$currentDate->modify('+1 day');
continue;
}
$newRemaining = [];
foreach ($remainingItems as $item) {
$assigned = false;
foreach ($availablePlans as $plan) {
$planId = $plan->id;
$remaining = ($plan->count ?? 0) - ($plan->used_count ?? 0) - ($plan->locked_count ?? 0);
$used = $usedCapacity[$planId] ?? 0;
// 筛选未过期的、可用的号源
$nowtime = date('Y-m-d H:i:s');
$availablePlans = [];
foreach ($planList as $plan) {
$planEndTime = $plan->date . ' ' . $plan->end_time;
if ($planEndTime > $nowtime && $plan->plan_enable) {
$availablePlans[] = $plan;
if (($remaining - $used) <= 0) continue;
// 冲突检查:不同排班且时间段重叠
if ($this->isTimeConflict($assignedPlans, $plan)) continue;
// 分配
$result[] = [
'id' => $item['id'],
'plan_id' => $planId,
'department_resources_name' => $plan->department_resources_name,
'date' => $plan->date,
'begin_time' => $plan->begin_time,
'end_time' => $plan->end_time
];
$usedCapacity[$planId] = ($usedCapacity[$planId] ?? 0) + 1;
$assignedPlans[] = [
'resource_name' => $plan->department_resources_name,
'begin_time' => $plan->begin_time,
'end_time' => $plan->end_time
];
$assigned = true;
break;
}
if (!$assigned) {
$newRemaining[] = $item;
}
}
$remainingItems = $newRemaining;
$currentDate->modify('+1 day');
}
return \Yz::Return(true, '获取成功', $result);
}
// 单天全检分配:所有 item 必须在同一天完成
private function doFullDayAssign($startDate, $endDate, $regnum, $entrustids, $episodeid, $appointment_type, $items, $scope, $userDeptId)
{
$currentDate = clone $startDate;
while ($currentDate <= $endDate) {
$nowdate = $currentDate->format('Y-m-d');
$availablePlans = $this->getAvailablePlansForDate($regnum, $entrustids, $episodeid, $appointment_type, $nowdate, $scope, $userDeptId);
if (count($availablePlans) == 0) {
if (empty($availablePlans)) {
$currentDate->modify('+1 day');
continue;
}
// 按日期、时间排序
usort($availablePlans, function($a, $b) {
$timeA = $a->date . ' ' . ($a->begin_time ?? '00:00:00');
$timeB = $b->date . ' ' . ($b->begin_time ?? '00:00:00');
return $timeA <=> $timeB;
});
$result = [];
$usedCapacity = [];
$assignedPlans = [];
$allAssigned = true;
// 遍历 items逐个分配
foreach ($items as $item) {
// 如果该 item 已经分配,跳过
$alreadyAssigned = false;
foreach ($result as $r) {
if ($r['id'] == $item['id']) {
$alreadyAssigned = true;
break;
}
}
if ($alreadyAssigned) continue;
// 找可用号源
$assigned = false;
foreach ($availablePlans as $plan) {
$planId = $plan->id;
$remaining = ($plan->count ?? 0) - ($plan->used_count ?? 0) - ($plan->locked_count ?? 0);
$used = $usedCapacity[$planId] ?? 0;
if (($remaining - $used) > 0) {
// 分配此号源
$result[] = [
'id' => $item['id'],
'plan_id' => $planId,
'department_resources_name' => $plan->department_resources_name,
'date' => $plan->date,
'begin_time' => $plan->begin_time,
'end_time' => $plan->end_time
];
$usedCapacity[$planId] = ($usedCapacity[$planId] ?? 0) + 1;
break;
}
if (($remaining - $used) <= 0) continue;
// 冲突检查:不同排班且时间段重叠
if ($this->isTimeConflict($assignedPlans, $plan)) continue;
$result[] = [
'id' => $item['id'],
'plan_id' => $planId,
'department_resources_name' => $plan->department_resources_name,
'date' => $plan->date,
'begin_time' => $plan->begin_time,
'end_time' => $plan->end_time
];
$usedCapacity[$planId] = ($usedCapacity[$planId] ?? 0) + 1;
$assignedPlans[] = [
'resource_name' => $plan->department_resources_name,
'begin_time' => $plan->begin_time,
'end_time' => $plan->end_time
];
$assigned = true;
break;
}
if (!$assigned) {
$allAssigned = false;
break;
}
}
// 如果所有 items 都已分配,停止遍历
if (count($result) >= count($items)) {
break;
if ($allAssigned) {
return \Yz::Return(true, '获取成功', $result);
}
$currentDate->modify('+1 day');
}
return \Yz::Return(true, '获取成功', $result);
return \Yz::Return(true, '获取成功', []);
}
// 获取指定日期的可用号源(已排序)
private function getAvailablePlansForDate($regnum, $entrustids, $episodeid, $appointment_type, $date, $scope, $userDeptId)
{
$s = $this->GetEnablePlan($regnum, $entrustids, $episodeid, $appointment_type, $date, $scope, $userDeptId);
if (!$s['status']) return [];
$planList = $s['data']['plan_list'] ?? [];
if (count($planList) == 0) return [];
$nowtime = date('Y-m-d H:i:s');
$availablePlans = [];
foreach ($planList as $plan) {
$planEndTime = $plan->date . ' ' . $plan->end_time;
if ($planEndTime > $nowtime && $plan->plan_enable) {
$availablePlans[] = $plan;
}
}
if (count($availablePlans) == 0) return [];
usort($availablePlans, function($a, $b) {
$timeA = $a->date . ' ' . ($a->begin_time ?? '00:00:00');
$timeB = $b->date . ' ' . ($b->begin_time ?? '00:00:00');
return $timeA <=> $timeB;
});
return $availablePlans;
}
// 冲突检查:同一排班资源允许重叠,不同排班时间不能重叠
private function isTimeConflict($assignedPlans, $plan)
{
foreach ($assignedPlans as $assigned) {
if ($assigned['resource_name'] === $plan->department_resources_name) {
continue; // 同排班,允许重叠
}
// 不同排班:检查时间段是否重叠
$aStart = $assigned['begin_time'];
$aEnd = $assigned['end_time'];
$bStart = $plan->begin_time;
$bEnd = $plan->end_time;
// [aStart, aEnd) ∩ [bStart, bEnd) ≠ ∅ → 冲突
if ($aStart < $bEnd && $bStart < $aEnd) {
return true;
}
}
return false;
}
//开始预约占用名额

@ -85,7 +85,7 @@
<el-checkbox label="1">预约完成后打印申请单</el-checkbox>
</el-checkbox-group>
<el-button class="do_button" type="primary" style="margin-left: 20px;" @click="optimalTimeClick"></el-button>
<el-button class="do_button" type="primary" @click="">单天全检</el-button>
<el-button class="do_button" type="primary" @click="fullDayClick"></el-button>
</div>
<div>
<el-date-picker v-model="startDate" type="date" placeholder="跳转日期" @change="DatePickerChange()" />
@ -781,6 +781,74 @@
}
})
}
//
const fullDayClick = () => {
if (selectedEntrustId.value.length === 0) {
ElMessage.error("请选择检查项目")
return
}
planLoading.value = true
//
selectedRows.value.forEach(row => {
const cachedRow = cachedListData.value.find(v => v.id === row.id)
if (!cachedRow) return
const target = entrustTableDate.value.find(v => v.id === row.id)
if (!target) return
target.department_resources = cachedRow.department_resources
target.reservation_date = cachedRow.reservation_date
target.period_begin_time = cachedRow.period_begin_time
target.period_end_time = cachedRow.period_end_time
target.temp_plan_id = undefined
})
//
const items = entrustTableDate.value
.filter(v => v.list_status === 0)
.map(v => ({
id: v.id,
entrust_id: v.entrust_id,
department_resources_name: v.department_resources?.department_resources_name || '',
date: v.reservation_date || '',
begin_time: v.period_begin_time || '',
end_time: v.period_end_time || ''
}))
//
GetOptimalPlan({
type: 'full_day',
regnum: props.reg_num,
entrustid: selectedEntrustId.value,
episodeid: props.episode_id,
appointment_type: props.appointment_type,
items: items,
scope: props.scope,
do_user: props.do_user
}).then(res => {
planLoading.value = false
if (res.status) {
//
res.data.forEach(item => {
const target = entrustTableDate.value.find(v => v.id === item.id)
if (!target) return
if (!target.department_resources) {
target.department_resources = {}
}
target.department_resources.department_resources_name = item.department_resources_name
target.reservation_date = item.date
target.period_begin_time = item.begin_time
target.period_end_time = item.end_time
target.temp_plan_id = item.plan_id
})
//
if (res.data.length > 0) {
selectedPlanId.value = res.data[0].plan_id
}
} else {
ElMessage.error(res.msg)
}
})
}
const checkDaijianFuc = () => {
planLoading.value = true
CheckIsDaiJian({

Loading…
Cancel
Save