项目筛选关联排班 , 共享预约 占用号源

main
鹿和sa0ChunLuyu 1 week ago
parent da405bba3f
commit d5dd1b5e79

@ -32,6 +32,12 @@ public function GetItemList($searchInfo,$page,$pageSize,$userid){
->pluck('department_number')
->toArray();
//获取当前科室下的设备 IDs
$deptDeviceIds = DB::table('s_department_resources_device')
->where('department_id', $user->department_id)
->pluck('device_id')
->toArray();
$small_id=[];
$list=DB::table('s_check_item')->where('s_check_item.is_del', 0)->whereNotNull('sheetType')->where('sheetType', '!=', '');
//科室过滤hisExecDepts 包含当前科室及子科室编号
@ -62,12 +68,12 @@ public function GetItemList($searchInfo,$page,$pageSize,$userid){
}
if (isset($searchInfo['deviceBind']) && $searchInfo['deviceBind'] !== '') {
if ($searchInfo['deviceBind'] == 1) {
$list->whereIn('s_check_item.id', function($q) {
$q->select('item_id')->from('s_check_item_device');
$list->whereIn('s_check_item.id', function($q) use ($deptDeviceIds) {
$q->select('item_id')->from('s_check_item_device')->whereIn('device_id', $deptDeviceIds);
});
} elseif ($searchInfo['deviceBind'] == 0) {
$list->whereNotIn('s_check_item.id', function($q) {
$q->select('item_id')->from('s_check_item_device');
$list->whereNotIn('s_check_item.id', function($q) use ($deptDeviceIds) {
$q->select('item_id')->from('s_check_item_device')->whereIn('device_id', $deptDeviceIds);
});
}
}
@ -93,12 +99,6 @@ public function GetItemList($searchInfo,$page,$pageSize,$userid){
//查询出所有的预约渠道
$qudaoList=DB::table('s_appointment_type')->get();
//获取当前科室下的设备 IDs
$deptDeviceIds = DB::table('s_department_resources_device')
->where('department_id', $user->department_id)
->pluck('device_id')
->toArray();
foreach ($list as $k=>$v){
$qudao_names=[];
$qudao_ids = explode(",", $v->reservation_method);

@ -1201,11 +1201,17 @@ public function YuYue($planid, $appointment_type, $mainlistids, $do_type,$is_eme
DB::beginTransaction();
try {
// 改约时提前记录旧排班 ID在 s_list 更新后校正
$oldRosterIds = [];
if ($do_type == 2) {
foreach ($oldMainInfos as $oldMainInfo) {
$this->releaseSourceFromDetail($oldMainInfo->id, $oldMainInfo);
if ($oldMainInfo->roster_id) {
$oldRosterIds[] = $oldMainInfo->roster_id;
}
}
$oldRosterIds = array_unique($oldRosterIds);
}
//查询号源渠道是否有合并
$appointment_types=[$appointment_type];
@ -1272,10 +1278,12 @@ public function YuYue($planid, $appointment_type, $mainlistids, $do_type,$is_eme
while ($remainingInChannel > 0 && $currentItemId !== null) {
$take = min($currentItemRemaining, $remainingInChannel);
if ($take > 0) {
if (!isset($itemChannelMap[$currentItemId][$channelId])) {
$itemChannelMap[$currentItemId][$channelId] = 0;
}
$itemChannelMap[$currentItemId][$channelId] += $take;
}
$currentItemRemaining -= $take;
$remainingInChannel -= $take;
@ -1313,10 +1321,12 @@ public function YuYue($planid, $appointment_type, $mainlistids, $do_type,$is_eme
while ($remainingEmergency > 0 && $currentItemId !== null) {
$take = min($currentItemRemaining, $remainingEmergency);
if ($take > 0) {
if (!isset($itemChannelMap[$currentItemId][$targetChannelId])) {
$itemChannelMap[$currentItemId][$targetChannelId] = 0;
}
$itemChannelMap[$currentItemId][$targetChannelId] += $take;
}
$currentItemRemaining -= $take;
$remainingEmergency -= $take;
@ -1452,6 +1462,23 @@ public function YuYue($planid, $appointment_type, $mainlistids, $do_type,$is_eme
}
}
// 共享非持有者:使用项目的 use_seats 写入 detail校正时自动计算总占位
$allZero = true;
foreach ($details as $d) {
if (($d['count'] ?? 0) > 0) { $allZero = false; break; }
}
if ($allZero && isset($itemSeatsData[$id]) && !empty($itemSeatsData[$id]['is_share_slot'])) {
if ($maxShareSlotId !== null && isset($itemChannelMap[$maxShareSlotId])) {
foreach ($itemChannelMap[$maxShareSlotId] as $cId => $count) {
$details[] = [
'roster_detail_count_id' => $cId,
'count' => $itemSeatsData[$id]['use_seats']
];
break;
}
}
}
$xuhao = $this->generateQueueNumber($planInfo->id);
$u_data = [
@ -1509,6 +1536,13 @@ public function YuYue($planid, $appointment_type, $mainlistids, $do_type,$is_eme
]);
}
// 校正旧排班号源s_list 已更新,旧项目已移走)
foreach ($oldRosterIds as $rid) {
$this->correctUsedCount($rid);
}
// 校正新排班号源
$this->correctUsedCount($planInfo->id);
DB::commit();
// HIS 回写通知
@ -1645,6 +1679,11 @@ public function CancelYuYue($MainListId, $reg_num,$do_user=null)
$u_mainList = DB::table('s_list')->where(['id' => $MainListId])->update($u_data);
// 校正号源
if ($mainInfo->roster_id) {
$this->correctUsedCount($mainInfo->roster_id);
}
$i_log = DB::table('s_list_log')->insert([
'list_id' => $mainInfo->id,
'reg_num' => $mainInfo->reg_num,
@ -1669,322 +1708,102 @@ public function CancelYuYue($MainListId, $reg_num,$do_user=null)
public function releaseSourceFromDetail($mainListId, $mainInfo = null)
{
if (!$mainInfo) {
$mainInfo = DB::table('s_list')->where(['id' => $mainListId])->first();
// 由 correctUsedCount 校正替代,不再在此处释放号源
return true;
}
if (!$mainInfo) {
Log::error("释放号源失败-记录不存在", ['MainListId' => $mainListId]);
private function releaseSourceFallback($mainInfo)
{
if (empty($mainInfo->roster_id) || empty($mainInfo->appointment_type_id)) {
Log::error("释放号源兜底失败-缺少必要字段", ['MainListId' => $mainInfo->id]);
return false;
}
$useDetailJson = $mainInfo->appointment_use_plan_detail;
Log::info('[YuYueDebug] releaseSourceFromDetail 入口', [
'mainListId' => $mainListId,
'entrust' => $mainInfo->entrust,
'useDetailJson' => $useDetailJson,
'reg_num' => $mainInfo->reg_num,
'roster_id' => $mainInfo->roster_id,
]);
// 共享号源逻辑:取消号源持有者时差值释放
$shareItem = DB::table('s_check_item')
->where(['item_code' => $mainInfo->entrust_code, 'status' => 1, 'is_del' => 0])
->select('is_share_slot', 'use_seats')
->first();
$isShareSlot = $shareItem && !empty($shareItem->is_share_slot);
if ($isShareSlot && !empty($useDetailJson) && $useDetailJson !== '[]') {
// 该共享项目是号源持有者:查询其他同排班同资源的共享项目
$otherSharedItems = DB::table('s_list')
->join('s_check_item', 's_list.entrust', '=', 's_check_item.item_name')
->where('s_list.reg_num', $mainInfo->reg_num)
->where('s_list.roster_id', $mainInfo->roster_id)
->where('s_list.id', '!=', $mainListId)
->where('s_list.list_status', 1)
->where('s_check_item.is_share_slot', 1)
->select('s_check_item.use_seats')
->get();
$affected = DB::table('s_source_roster_detail_count')
->where([
'roster_detail_id' => $mainInfo->roster_id,
'appointment_type_id' => $mainInfo->appointment_type_id
])
->where('used_count', '>', 0)
->decrement('used_count', 1);
if ($otherSharedItems->isNotEmpty()) {
$remainingMax = $otherSharedItems->max('use_seats');
if ($remainingMax > 0) {
// 解析当前持有量
$useDetails = json_decode($useDetailJson, true);
if (!is_array($useDetails)) {
return $this->releaseSourceFallback($mainInfo);
}
$currentCount = 0;
foreach ($useDetails as $d) {
$currentCount += (int)($d['count'] ?? 0);
if ($affected == 0) {
Log::warning("释放号源兜底-无匹配记录或已为0", ['MainListId' => $mainInfo->id]);
}
Log::info('[YuYueDebug] 共享持有者取消', [
'mainListId' => $mainListId,
'entrust' => $mainInfo->entrust,
'useDetails' => $useDetails,
'currentCount' => $currentCount,
'remainingMax' => $remainingMax,
'otherSharedCount' => $otherSharedItems->count(),
]);
// 只释放差值
$releaseCount = $currentCount - $remainingMax;
if ($releaseCount > 0) {
// 调整 detail 为只释放差值:从原始条目中按顺序扣除
$adjustedDetail = [];
$remainingToRelease = $releaseCount;
foreach ($useDetails as $d) {
$entryCount = (int)($d['count'] ?? 0);
$take = min($entryCount, $remainingToRelease);
if ($take > 0) {
$adjustedDetail[] = [
'roster_detail_count_id' => $d['roster_detail_count_id'],
'count' => $take
];
$remainingToRelease -= $take;
}
return $affected > 0;
}
// 更新剩余某个共享项目为新持有者
// 新持有者的 detail = 原始条目中未被释放的剩余部分
$newHolder = DB::table('s_list')
->join('s_check_item', 's_list.entrust', '=', 's_check_item.item_name')
->where('s_list.reg_num', $mainInfo->reg_num)
->where('s_list.roster_id', $mainInfo->roster_id)
->where('s_list.id', '!=', $mainListId)
->where('s_list.list_status', 1)
->where('s_check_item.is_share_slot', 1)
->select('s_list.id')
->first();
/**
* 校正排班号源根据项目配置is_share_slot / use_seats重新计算 used_count
*/
private function correctUsedCount($rosterId)
{
if (!$rosterId) return;
if ($newHolder) {
// 有可转移的共享项目:只释放差值
$useDetailJson = json_encode($adjustedDetail);
$newHolderDetail = [];
$adjustmentIdx = 0;
foreach ($useDetails as $d) {
$entryCount = (int)($d['count'] ?? 0);
// 该条目被释放了多少
$releasedFromEntry = 0;
if ($adjustmentIdx < count($adjustedDetail) && $adjustedDetail[$adjustmentIdx]['roster_detail_count_id'] == $d['roster_detail_count_id']) {
$releasedFromEntry = $adjustedDetail[$adjustmentIdx]['count'];
$adjustmentIdx++;
}
$remainingInEntry = $entryCount - $releasedFromEntry;
if ($remainingInEntry > 0) {
$newHolderDetail[] = [
'roster_detail_count_id' => $d['roster_detail_count_id'],
'count' => $remainingInEntry
];
}
}
DB::table('s_list')
->where('id', $newHolder->id)
->update(['appointment_use_plan_detail' => json_encode($newHolderDetail)]);
}
// 没有可转移的共享项目:不修改 useDetailJson正常释放逻辑释放全部
} else {
// currentCount <= remainingMax不释放将占位合并到新持有者
$newHolder = DB::table('s_list')
// 先重置该排班所有渠道的 used_count 为 0
DB::table('s_source_roster_detail_count')
->where('roster_detail_id', $rosterId)
->update(['used_count' => 0, 'version' => DB::raw('version + 1')]);
// 获取该排班上所有活跃项目及其配置
$activeItems = DB::table('s_list')
->join('s_check_item', 's_list.entrust', '=', 's_check_item.item_name')
->where('s_list.reg_num', $mainInfo->reg_num)
->where('s_list.roster_id', $mainInfo->roster_id)
->where('s_list.id', '!=', $mainListId)
->where('s_list.roster_id', $rosterId)
->where('s_list.list_status', 1)
->where('s_check_item.is_share_slot', 1)
->select('s_list.id')
->first();
Log::info('[YuYueDebug] else 转移-新持有者查询', [
'mainListId' => $mainListId,
'entrust' => $mainInfo->entrust,
'newHolderId' => $newHolder ? $newHolder->id : 'null',
]);
if ($newHolder) {
// 读取新持有者当前 detail合并当前项目的占位
$newHolderJson = DB::table('s_list')->where('id', $newHolder->id)->value('appointment_use_plan_detail');
$newHolderDetail = json_decode($newHolderJson, true) ?: [];
Log::info('[YuYueDebug] else 转移-合并前', [
'newHolderId' => $newHolder->id,
'newHolderDetail' => $newHolderDetail,
'cancelDetail' => $useDetails,
]);
foreach ($useDetails as $d) {
$found = false;
foreach ($newHolderDetail as &$nd) {
if ($nd['roster_detail_count_id'] == $d['roster_detail_count_id']) {
$nd['count'] += (int)($d['count'] ?? 0);
$found = true;
break;
}
}
if (!$found) {
$newHolderDetail[] = [
'roster_detail_count_id' => $d['roster_detail_count_id'],
'count' => (int)($d['count'] ?? 0)
];
}
}
Log::info('[YuYueDebug] else 转移-合并后', [
'newHolderId' => $newHolder->id,
'newHolderDetail' => $newHolderDetail,
]);
DB::table('s_list')
->where('id', $newHolder->id)
->update(['appointment_use_plan_detail' => json_encode($newHolderDetail)]);
// 更新后读回确认
$afterUpdate = DB::table('s_list')->where('id', $newHolder->id)->value('appointment_use_plan_detail');
Log::info('[YuYueDebug] else 转移-更新后读回', [
'newHolderId' => $newHolder->id,
'写入值' => json_encode($newHolderDetail),
'读回值' => $afterUpdate,
]);
}
// 不修改 useDetailJson但返回 true 阻止正常释放逻辑
return true;
}
}
// remainingMax == 0正常释放全部
}
// 没有其他共享项目:正常释放全部
}
->where('s_check_item.status', 1)
->where('s_check_item.is_del', 0)
->select('s_list.*', 's_check_item.use_seats', 's_check_item.is_share_slot')
->get();
Log::info('[YuYueDebug] 进入正常释放逻辑', [
'mainListId' => $mainListId,
'entrust' => $mainInfo->entrust,
'useDetailJson' => $useDetailJson,
'isShareSlot' => $isShareSlot ? 'true' : 'false',
]);
// 计算总占位:按人隔离,共享取 max非共享累加
$grouped = [];
$firstDetail = null;
if ($isShareSlot && empty($useDetailJson)) {
// 共享项目但不是号源持有者,不释放号源
return true;
foreach ($activeItems as $item) {
$reg = $item->reg_num;
if (!isset($grouped[$reg])) {
$grouped[$reg] = ['sharedMax' => 0, 'nonSharedSum' => 0];
}
if (empty($useDetailJson)) {
$fallbackResult = $this->releaseSourceFallback($mainInfo);
return $fallbackResult;
if (!empty($item->is_share_slot)) {
$grouped[$reg]['sharedMax'] = max($grouped[$reg]['sharedMax'], (int)($item->use_seats ?? 1));
} else {
$grouped[$reg]['nonSharedSum'] += (int)($item->use_seats ?? 1);
}
$useDetails = json_decode($useDetailJson, true);
if (!is_array($useDetails)) {
Log::error("释放号源失败-明细格式错误", ['MainListId' => $mainListId, 'detail' => $useDetailJson]);
return $this->releaseSourceFallback($mainInfo);
// 记录第一个有 detail 的渠道
if ($firstDetail === null) {
$details = json_decode($item->appointment_use_plan_detail, true) ?: [];
if (!empty($details)) {
$firstDetail = $details;
}
$allSuccess = true;
foreach ($useDetails as $detail) {
$rosterDetailCountId = $detail['roster_detail_count_id'] ?? null;
$countToRelease = (int)($detail['count'] ?? 0);
if (!$rosterDetailCountId || $countToRelease <= 0) {
continue;
}
$countRecord = DB::table('s_source_roster_detail_count')
->where(['id' => $rosterDetailCountId])
->first();
if (!$countRecord) {
Log::error("释放号源-记录缺失", ['MainListId' => $mainListId, 'count_id' => $rosterDetailCountId]);
$allSuccess = false;
continue;
}
$currentVersion = $countRecord->version;
$affected = DB::table('s_source_roster_detail_count')
->where(['id' => $rosterDetailCountId, 'version' => $currentVersion])
->where('used_count', '>=', $countToRelease)
->update([
'used_count' => DB::raw('used_count - ' . $countToRelease),
'version' => DB::raw('version + 1'),
'updated_at' => date('Y-m-d H:i:s')
]);
Log::info('[YuYueDebug] 释放号源', [
'mainListId' => $mainListId,
'entrust' => $mainInfo->entrust,
'rosterDetailCountId' => $rosterDetailCountId,
'countToRelease' => $countToRelease,
'currentVersion' => $currentVersion,
'affected' => $affected,
]);
if ($affected == 0) {
$retryRecord = DB::table('s_source_roster_detail_count')->where(['id' => $rosterDetailCountId])->first();
if (!$retryRecord) {
Log::error("释放号源重试-记录缺失", ['MainListId' => $mainListId, 'count_id' => $rosterDetailCountId]);
$allSuccess = false;
continue;
$totalUsed = 0;
foreach ($grouped as $reg => $g) {
$totalUsed += $g['sharedMax'] + $g['nonSharedSum'];
}
if ($retryRecord->used_count < $countToRelease) {
$actualRelease = $retryRecord->used_count;
if ($actualRelease > 0) {
DB::table('s_source_roster_detail_count')
->where(['id' => $rosterDetailCountId])
->update([
'used_count' => 0,
'version' => DB::raw('version + 1'),
'updated_at' => date('Y-m-d H:i:s')
]);
}
Log::warning("释放号源-数量不足", [
'MainListId' => $mainListId,
'count_id' => $rosterDetailCountId,
'expected' => $countToRelease,
'actual' => $actualRelease
]);
} else {
// 按渠道分配占位
if ($firstDetail && $totalUsed > 0) {
$totalDetailCount = array_sum(array_column($firstDetail, 'count'));
foreach ($firstDetail as $d) {
$cid = $d['roster_detail_count_id'] ?? null;
if (!$cid) continue;
$ratio = $totalDetailCount > 0 ? ($d['count'] / $totalDetailCount) : 0;
$channelUsed = (int)round($totalUsed * $ratio);
DB::table('s_source_roster_detail_count')
->where(['id' => $rosterDetailCountId])
->update([
'used_count' => DB::raw('used_count - ' . $countToRelease),
'version' => DB::raw('version + 1'),
'updated_at' => date('Y-m-d H:i:s')
]);
Log::warning("释放号源-乐观锁冲突已重试", ['MainListId' => $mainListId, 'count_id' => $rosterDetailCountId]);
}
->where('id', $cid)
->update(['used_count' => $channelUsed, 'version' => DB::raw('version + 1')]);
}
}
Log::info('[YuYueDebug] releaseSourceFromDetail 返回', [
'mainListId' => $mainListId,
'entrust' => $mainInfo->entrust,
'allSuccess' => $allSuccess ? 'true' : 'false',
Log::info('[YuYueDebug] 校正号源', [
'rosterId' => $rosterId,
'grouped' => $grouped,
'totalUsed' => $totalUsed,
'activeItemCount' => $activeItems->count(),
]);
return $allSuccess;
}
private function releaseSourceFallback($mainInfo)
{
if (empty($mainInfo->roster_id) || empty($mainInfo->appointment_type_id)) {
Log::error("释放号源兜底失败-缺少必要字段", ['MainListId' => $mainInfo->id]);
return false;
}
$affected = DB::table('s_source_roster_detail_count')
->where([
'roster_detail_id' => $mainInfo->roster_id,
'appointment_type_id' => $mainInfo->appointment_type_id
])
->where('used_count', '>', 0)
->decrement('used_count', 1);
if ($affected == 0) {
Log::warning("释放号源兜底-无匹配记录或已为0", ['MainListId' => $mainInfo->id]);
}
return $affected > 0;
}
/**

Loading…
Cancel
Save