diff --git a/Laravel/app/Services/Admin/YeWu/PlanListService.php b/Laravel/app/Services/Admin/YeWu/PlanListService.php index 36b697d..d2c843e 100644 --- a/Laravel/app/Services/Admin/YeWu/PlanListService.php +++ b/Laravel/app/Services/Admin/YeWu/PlanListService.php @@ -1514,6 +1514,70 @@ public function YuYue($planid, $appointment_type, $mainlistids, $do_type,$is_eme throw new \Exception('名额不足,扣减失败'); } + // === 号源总量校验(扣减后、更新s_list前)=== + // 加急预约允许超量,跳过总量校验 + if ($is_emergency !== 1) { + // 校验①:合并渠道总额校验(计数器维度) + $afterCounts = DB::table('s_source_roster_detail_count') + ->where('roster_detail_id', $planid) + ->whereIn('appointment_type_id', $appointment_types) + ->get(); + $totalCountChannel = $afterCounts->sum('count'); + $totalUsedChannel = $afterCounts->sum('used_count'); + if ($totalUsedChannel > $totalCountChannel) { + Log::warning('号源总额超限-计数器维度', [ + 'planid' => $planid, + 'totalCount' => $totalCountChannel, + 'totalUsed' => $totalUsedChannel, + ]); + throw new \Exception('号源总数不足,总容量' . $totalCountChannel . ',已用' . $totalUsedChannel); + } + + // 校验②:从s_list实际记录校验(实际记录维度) + // 复用correctUsedCount的分组算法:按患者分组,共享取max,非共享累加 + $activeItems = DB::table('s_list') + ->leftJoin('s_check_item', 's_list.entrust_code', '=', 's_check_item.item_code') + ->where('s_list.roster_id', $planid) + ->whereIn('s_list.list_status', [1, 2, 3]) + ->where('s_list.is_nullify', 0) + ->where('s_list.is_del', 0) + // 改约时排除本次操作的记录(此时list_status仍为1,即将被改到新排班) + ->when($do_type == 2, function($q) use ($mainlistids) { + $q->whereNotIn('s_list.id', $mainlistids); + }) + ->select('s_list.*', 's_check_item.use_seats', 's_check_item.is_share_slot') + ->get(); + $grouped = []; + foreach ($activeItems as $item) { + $reg = $item->reg_num; + if (!isset($grouped[$reg])) { + $grouped[$reg] = ['sharedMax' => 0, 'nonSharedSum' => 0]; + } + 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); + } + } + $actualUsed = 0; + foreach ($grouped as $reg => $g) { + $actualUsed += $g['sharedMax'] + $g['nonSharedSum']; + } + // 本次新增占位量(已含共享减去已有占位的逻辑) + $totalAfterActual = $actualUsed + $zhanweiCount; + if ($totalAfterActual > $totalCountChannel) { + Log::warning('号源总额超限-实际记录维度', [ + 'planid' => $planid, + 'actualUsed' => $actualUsed, + 'zhanweiCount' => $zhanweiCount, + 'totalAfter' => $totalAfterActual, + 'totalCount' => $totalCountChannel, + ]); + throw new \Exception('号源已约满,当前已约' . $actualUsed . ',本次新增' . $zhanweiCount . ',总容量' . $totalCountChannel); + } + } // end if ($is_emergency !== 1) + // === 号源总量校验结束 === + $list_all_success = true; foreach ($mainlistids as $id) { $details = []; @@ -1894,8 +1958,6 @@ public function correctUsedCount($rosterId) // 计算总占位:按人隔离,共享取 max,非共享累加 $grouped = []; - $firstDetail = null; - foreach ($activeItems as $item) { $reg = $item->reg_num; if (!isset($grouped[$reg])) { @@ -1906,13 +1968,6 @@ public function correctUsedCount($rosterId) } else { $grouped[$reg]['nonSharedSum'] += (int)($item->use_seats ?? 1); } - // 记录第一个有 detail 的渠道 - if ($firstDetail === null) { - $details = json_decode($item->appointment_use_plan_detail, true) ?: []; - if (!empty($details)) { - $firstDetail = $details; - } - } } $totalUsed = 0; @@ -1920,24 +1975,48 @@ public function correctUsedCount($rosterId) $totalUsed += $g['sharedMax'] + $g['nonSharedSum']; } - // 按渠道分配占位 - if ($firstDetail && $totalUsed > 0) { - $totalDetailCount = array_sum(array_column($firstDetail, 'count')); - foreach ($firstDetail as $d) { + // 按渠道分配占位:遍历所有记录的 appointment_use_plan_detail 逐条累加 + $channelTotals = []; + foreach ($activeItems as $item) { + $details = json_decode($item->appointment_use_plan_detail, true) ?: []; + foreach ($details 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', $cid) - ->update(['used_count' => $channelUsed, 'version' => DB::raw('version + 1')]); + $cnt = (int)($d['count'] ?? 0); + if ($cid && $cnt > 0) { + $channelTotals[$cid] = ($channelTotals[$cid] ?? 0) + $cnt; + } + } + } + + // 共享号源比例缩放:渠道累加值与分组总量不一致时,按比例校正 + $channelSum = array_sum($channelTotals); + if ($channelSum > 0 && $channelSum != $totalUsed) { + $scale = $totalUsed / $channelSum; + foreach ($channelTotals as $cid => $val) { + $channelTotals[$cid] = (int)round($val * $scale); + } + // 处理舍入误差 + $afterScaleSum = array_sum($channelTotals); + if ($afterScaleSum != $totalUsed && !empty($channelTotals)) { + $diff = $totalUsed - $afterScaleSum; + $maxCid = array_keys($channelTotals, max($channelTotals))[0]; + $channelTotals[$maxCid] += $diff; } } + // 写回各渠道 + foreach ($channelTotals as $cid => $used) { + if ($used <= 0) continue; + DB::table('s_source_roster_detail_count') + ->where('id', $cid) + ->update(['used_count' => $used, 'version' => DB::raw('version + 1')]); + } + Log::info('[YuYueDebug] 校正号源', [ 'rosterId' => $rosterId, 'grouped' => $grouped, 'totalUsed' => $totalUsed, + 'channelTotals' => $channelTotals, 'activeItemCount' => $activeItems->count(), ]); } @@ -1990,7 +2069,6 @@ public function calculateUsedCount($rosterId, $correct = 0) // 按患者分组计算占位 $grouped = []; - $firstDetail = null; foreach ($activeItems as $item) { $reg = $item->reg_num; @@ -2024,14 +2102,6 @@ public function calculateUsedCount($rosterId, $correct = 0) 'use_seats' => $seats, ]; } - - // 记录第一个有 detail 的渠道 - if ($firstDetail === null) { - $details = json_decode($item->appointment_use_plan_detail, true) ?: []; - if (!empty($details)) { - $firstDetail = $details; - } - } } // 计算总占位 @@ -2049,17 +2119,38 @@ public function calculateUsedCount($rosterId, $correct = 0) ]; } - // 按渠道分配计算 - $channelCalculated = []; - $hasDiff = false; - if ($firstDetail && $totalUsed > 0) { - $totalDetailCount = array_sum(array_column($firstDetail, 'count')); - foreach ($firstDetail as $d) { + // 按渠道分配计算:遍历所有记录的 appointment_use_plan_detail 逐条累加 + $channelTotals = []; + foreach ($activeItems as $item) { + $details = json_decode($item->appointment_use_plan_detail, true) ?: []; + foreach ($details as $d) { $cid = $d['roster_detail_count_id'] ?? null; - if (!$cid) continue; - $ratio = $totalDetailCount > 0 ? ($d['count'] / $totalDetailCount) : 0; - $channelUsed = (int)round($totalUsed * $ratio); + $cnt = (int)($d['count'] ?? 0); + if ($cid && $cnt > 0) { + $channelTotals[$cid] = ($channelTotals[$cid] ?? 0) + $cnt; + } + } + } + // 共享号源比例缩放 + $channelSum = array_sum($channelTotals); + if ($channelSum > 0 && $channelSum != $totalUsed) { + $scale = $totalUsed / $channelSum; + foreach ($channelTotals as $cid => $val) { + $channelTotals[$cid] = (int)round($val * $scale); + } + $afterScaleSum = array_sum($channelTotals); + if ($afterScaleSum != $totalUsed && !empty($channelTotals)) { + $diff = $totalUsed - $afterScaleSum; + $maxCid = array_keys($channelTotals, max($channelTotals))[0]; + $channelTotals[$maxCid] += $diff; + } + } + + $channelCalculated = []; + $hasDiff = false; + if (!empty($channelTotals)) { + foreach ($channelTotals as $cid => $channelUsed) { $current = $channels->firstWhere('id', $cid); $currentUsed = $current ? (int)$current->used_count : 0; $diff = $channelUsed - $currentUsed;