|
|
|
|
@ -323,6 +323,11 @@ WHERE
|
|
|
|
|
|
|
|
|
|
DB::beginTransaction();
|
|
|
|
|
try {
|
|
|
|
|
if ($do_type == 2) {
|
|
|
|
|
foreach ($oldMainInfos as $oldMainInfo) {
|
|
|
|
|
$this->releaseSourceFromDetail($oldMainInfo->id, $oldMainInfo);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//查询号源渠道是否有合并
|
|
|
|
|
$appointment_types=[$appointment_type];
|
|
|
|
|
@ -495,45 +500,57 @@ WHERE
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//更新计划明细表使用数量
|
|
|
|
|
//更新计划明细表使用数量(使用乐观锁)
|
|
|
|
|
$up_plan_count_all_success = true;
|
|
|
|
|
$versionMismatchIds = [];
|
|
|
|
|
foreach ($roster_detail_counts as $key => $planCount) {
|
|
|
|
|
$countToDeduct = $plan_qudao_tempCount[$key];
|
|
|
|
|
if ($countToDeduct == 0) continue;
|
|
|
|
|
|
|
|
|
|
$query = DB::table('s_source_roster_detail_count')->where(['id' => $planCount->id]);
|
|
|
|
|
|
|
|
|
|
// 非紧急模式:严格检查库存充足
|
|
|
|
|
if ($is_emergency !== 1) {
|
|
|
|
|
// 只有当 剩余物理库存 >= 本次扣减 时才执行
|
|
|
|
|
// 公式:count - used - locked >= countToDeduct => count >= used + locked + countToDeduct
|
|
|
|
|
$query->whereRaw('count >= (used_count + IFNULL(locked_count, 0) + ?)', [$countToDeduct]);
|
|
|
|
|
}
|
|
|
|
|
// 紧急模式:不加 whereRaw 限制,允许超卖 (used_count 可以大于 count)
|
|
|
|
|
|
|
|
|
|
$affected = $query->increment('used_count', $countToDeduct);
|
|
|
|
|
|
|
|
|
|
if ($affected == 0) {
|
|
|
|
|
if ($is_emergency !== 1) {
|
|
|
|
|
// 非紧急模式扣减失败,说明并发导致库存不足
|
|
|
|
|
$up_plan_count_all_success = false;
|
|
|
|
|
break;
|
|
|
|
|
$affected = DB::table('s_source_roster_detail_count')
|
|
|
|
|
->where(['id' => $planCount->id, 'version' => $planCount->version])
|
|
|
|
|
->whereRaw('count >= (used_count + IFNULL(locked_count, 0) + ?)', [$countToDeduct])
|
|
|
|
|
->update([
|
|
|
|
|
'used_count' => DB::raw('used_count + ' . $countToDeduct),
|
|
|
|
|
'version' => DB::raw('version + 1'),
|
|
|
|
|
'updated_at' => date('Y-m-d H:i:s')
|
|
|
|
|
]);
|
|
|
|
|
} else {
|
|
|
|
|
// 紧急模式扣减失败,通常是因为记录不存在,需检查
|
|
|
|
|
$affected = DB::table('s_source_roster_detail_count')
|
|
|
|
|
->where(['id' => $planCount->id, 'version' => $planCount->version])
|
|
|
|
|
->update([
|
|
|
|
|
'used_count' => DB::raw('used_count + ' . $countToDeduct),
|
|
|
|
|
'version' => DB::raw('version + 1'),
|
|
|
|
|
'updated_at' => date('Y-m-d H:i:s')
|
|
|
|
|
]);
|
|
|
|
|
if ($affected == 0) {
|
|
|
|
|
$check = DB::table('s_source_roster_detail_count')->find($planCount->id);
|
|
|
|
|
if (!$check) {
|
|
|
|
|
$up_plan_count_all_success = false;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
// 如果记录存在但 affected=0 (极少见,可能是行锁超时等),在紧急模式下我们尝试强制更新
|
|
|
|
|
// 这里为了简单,假设 increment 只要记录存在就会成功返回 1 (即使超卖)
|
|
|
|
|
// 如果确实返回 0 且记录存在,可能需要重试或报错,视具体 DB 行为而定
|
|
|
|
|
// 大多数情况下,不加 whereRaw 的 increment 都会成功
|
|
|
|
|
$affected = DB::table('s_source_roster_detail_count')
|
|
|
|
|
->where(['id' => $planCount->id])
|
|
|
|
|
->update([
|
|
|
|
|
'used_count' => DB::raw('used_count + ' . $countToDeduct),
|
|
|
|
|
'version' => DB::raw('version + 1'),
|
|
|
|
|
'updated_at' => date('Y-m-d H:i:s')
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($affected == 0 && $is_emergency !== 1) {
|
|
|
|
|
$versionMismatchIds[] = $planCount->id;
|
|
|
|
|
$up_plan_count_all_success = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!$up_plan_count_all_success && $is_emergency !== 1) {
|
|
|
|
|
if (count($versionMismatchIds) > 0) {
|
|
|
|
|
Log::warning("预约乐观锁冲突", ['planid' => $planid, 'conflict_ids' => $versionMismatchIds]);
|
|
|
|
|
throw new \Exception('号源数据已被其他用户修改,请刷新后重试');
|
|
|
|
|
}
|
|
|
|
|
throw new \Exception('名额不足,扣减失败');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -577,63 +594,8 @@ WHERE
|
|
|
|
|
throw new \Exception('更新医嘱状态失败');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
$note = "预约";
|
|
|
|
|
$note = $do_type == 2 ? "改约" : "预约";
|
|
|
|
|
foreach ($oldMainInfos as $key => $oldMainInfo) {
|
|
|
|
|
|
|
|
|
|
if ($do_type == 2) {
|
|
|
|
|
// if(count($mainlistids)>1) return \Yz::echoError1('请选择1条医嘱改约,暂不支持批量');
|
|
|
|
|
$note = "改约";
|
|
|
|
|
//如果是改约,则恢复原来的数量
|
|
|
|
|
$useDetailJson = $oldMainInfo->appointment_use_plan_detail;
|
|
|
|
|
if (empty($useDetailJson)) {
|
|
|
|
|
return \Yz::echoError1('取消失败:缺少占位明细数据,无法精确回退,请联系管理员');
|
|
|
|
|
}
|
|
|
|
|
$useDetails = json_decode($useDetailJson, true);
|
|
|
|
|
if (!is_array($useDetails)) {
|
|
|
|
|
return \Yz::echoError1('取消失败:占位明细数据格式错误');
|
|
|
|
|
}
|
|
|
|
|
foreach ($useDetails as $detail) {
|
|
|
|
|
$rosterDetailCountId = $detail['roster_detail_count_id'] ?? null;
|
|
|
|
|
$countToRelease = (int)($detail['count'] ?? 0);
|
|
|
|
|
|
|
|
|
|
if (!$rosterDetailCountId || $countToRelease <= 0) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
$affected = DB::table('s_source_roster_detail_count')
|
|
|
|
|
->where(['id' => $rosterDetailCountId])
|
|
|
|
|
->lockForUpdate()
|
|
|
|
|
->where('used_count', '>=', $countToRelease) // 防御性编程:确保不会减成负数
|
|
|
|
|
->decrement('used_count', $countToRelease);
|
|
|
|
|
if ($affected == 0) {
|
|
|
|
|
// 如果 affected 为 0,可能是记录不存在,或者 used_count 不足
|
|
|
|
|
$checkRecord = DB::table('s_source_roster_detail_count')->where(['id' => $rosterDetailCountId])->first();
|
|
|
|
|
if (!$checkRecord) {
|
|
|
|
|
Log::error("改约-号源记录缺失", [
|
|
|
|
|
'MainListId' => $oldMainInfo->id,
|
|
|
|
|
'count_id' => $rosterDetailCountId
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
if ($checkRecord->used_count < $countToRelease) {
|
|
|
|
|
// 【关键修改】:余额不足,但不阻断取消
|
|
|
|
|
$actualRelease = $checkRecord->used_count; // 最多只能退这么多
|
|
|
|
|
|
|
|
|
|
if ($actualRelease > 0) {
|
|
|
|
|
// 把剩余的余额全部退完,强制设为 0 (或者 decrement actualRelease)
|
|
|
|
|
DB::table('s_source_roster_detail_count')
|
|
|
|
|
->where(['id' => $rosterDetailCountId])
|
|
|
|
|
->update(['used_count' => 0]); // 直接置零,防止浮点数或并发问题
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 记录严重报警日志
|
|
|
|
|
$msg = "数据不一致警告:订单 {$oldMainInfo->id} 试图退还 {$countToRelease} 个名额,但渠道 {$rosterDetailCountId} 仅剩 {$checkRecord->used_count} 个。";
|
|
|
|
|
Log::error($msg);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$i_log = DB::table('s_list_log')->insert([
|
|
|
|
|
'list_id' => $oldMainInfo->id,
|
|
|
|
|
'reg_num' => $oldMainInfo->reg_num,
|
|
|
|
|
@ -644,16 +606,12 @@ WHERE
|
|
|
|
|
'data' => json_encode($u_data)
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
if ($u_mainList) {
|
|
|
|
|
|
|
|
|
|
DB::commit();
|
|
|
|
|
if(config('app.globals.预约完成短信通知')==1){
|
|
|
|
|
$this->SendMsg($oldMainInfos,$do_type);
|
|
|
|
|
}
|
|
|
|
|
return \Yz::Return(true, '预约成功', ['planid' => $planid, 'mainlistids' => $mainlistids]);
|
|
|
|
|
} else {
|
|
|
|
|
DB::rollBack();
|
|
|
|
|
return \Yz::echoError1('预约失败');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -725,10 +683,8 @@ WHERE
|
|
|
|
|
date_default_timezone_set('PRC');
|
|
|
|
|
$nowdatetime = date("Y-m-d H:i:s");
|
|
|
|
|
|
|
|
|
|
// 开启数据库事务
|
|
|
|
|
DB::beginTransaction();
|
|
|
|
|
try {
|
|
|
|
|
// 1. 查询并锁定主预约记录(防并发)
|
|
|
|
|
$mainInfo = DB::table('s_list')
|
|
|
|
|
->where(['id' => $MainListId, 'reg_num' => $reg_num])
|
|
|
|
|
->lockForUpdate()
|
|
|
|
|
@ -738,58 +694,12 @@ WHERE
|
|
|
|
|
return \Yz::echoError1('医嘱不存在');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 2. 检查当前状态是否允许取消(仅状态为1可取消)
|
|
|
|
|
if ($mainInfo->list_status != 1) {
|
|
|
|
|
return \Yz::echoError1('该记录无法取消,当前状态:' . $mainInfo->list_status);
|
|
|
|
|
}
|
|
|
|
|
$useDetailJson = $mainInfo->appointment_use_plan_detail;
|
|
|
|
|
if (empty($useDetailJson)) {
|
|
|
|
|
return \Yz::echoError1('取消失败:缺少占位明细数据,无法精确回退,请联系管理员');
|
|
|
|
|
}
|
|
|
|
|
$useDetails = json_decode($useDetailJson, true);
|
|
|
|
|
if (!is_array($useDetails)) {
|
|
|
|
|
return \Yz::echoError1('取消失败:占位明细数据格式错误');
|
|
|
|
|
}
|
|
|
|
|
foreach ($useDetails as $detail) {
|
|
|
|
|
$rosterDetailCountId = $detail['roster_detail_count_id'] ?? null;
|
|
|
|
|
$countToRelease = (int)($detail['count'] ?? 0);
|
|
|
|
|
|
|
|
|
|
if (!$rosterDetailCountId || $countToRelease <= 0) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
$affected = DB::table('s_source_roster_detail_count')
|
|
|
|
|
->where(['id' => $rosterDetailCountId])
|
|
|
|
|
->lockForUpdate()
|
|
|
|
|
->where('used_count', '>=', $countToRelease) // 防御性编程:确保不会减成负数
|
|
|
|
|
->decrement('used_count', $countToRelease);
|
|
|
|
|
if ($affected == 0) {
|
|
|
|
|
// 如果 affected 为 0,可能是记录不存在,或者 used_count 不足
|
|
|
|
|
$checkRecord = DB::table('s_source_roster_detail_count')->where(['id' => $rosterDetailCountId])->first();
|
|
|
|
|
if (!$checkRecord) {
|
|
|
|
|
Log::error("取消预约-记录缺失", [
|
|
|
|
|
'MainListId' => $MainListId,
|
|
|
|
|
'count_id' => $rosterDetailCountId
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
if ($checkRecord->used_count < $countToRelease) {
|
|
|
|
|
// 【关键修改】:余额不足,但不阻断取消
|
|
|
|
|
$actualRelease = $checkRecord->used_count; // 最多只能退这么多
|
|
|
|
|
|
|
|
|
|
if ($actualRelease > 0) {
|
|
|
|
|
// 把剩余的余额全部退完,强制设为 0 (或者 decrement actualRelease)
|
|
|
|
|
DB::table('s_source_roster_detail_count')
|
|
|
|
|
->where(['id' => $rosterDetailCountId])
|
|
|
|
|
->update(['used_count' => 0]); // 直接置零,防止浮点数或并发问题
|
|
|
|
|
}
|
|
|
|
|
$this->releaseSourceFromDetail($MainListId, $mainInfo);
|
|
|
|
|
|
|
|
|
|
// 记录严重报警日志
|
|
|
|
|
$msg = "数据不一致警告:订单 {$MainListId} 试图退还 {$countToRelease} 个名额,但渠道 {$rosterDetailCountId} 仅剩 {$checkRecord->used_count} 个。";
|
|
|
|
|
Log::error($msg);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 8. 更新主表状态为“已取消”(0)
|
|
|
|
|
$u_data = [
|
|
|
|
|
'list_status' => 0,
|
|
|
|
|
'reservation_date' => null,
|
|
|
|
|
@ -800,13 +710,13 @@ WHERE
|
|
|
|
|
'xuhao' => null,
|
|
|
|
|
'department_id' => null,
|
|
|
|
|
'appointment_type_id' => null,
|
|
|
|
|
'appointment_use_plan_detail' => null,
|
|
|
|
|
'canel_time' => $nowdatetime,
|
|
|
|
|
'is_emergency' => 0
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
$u_mainList = DB::table('s_list')->where(['id' => $MainListId])->update($u_data);
|
|
|
|
|
|
|
|
|
|
// 9. 写入操作日志
|
|
|
|
|
$i_log = DB::table('s_list_log')->insert([
|
|
|
|
|
'list_id' => $mainInfo->id,
|
|
|
|
|
'reg_num' => $mainInfo->reg_num,
|
|
|
|
|
@ -818,18 +728,131 @@ WHERE
|
|
|
|
|
'created_at' => $nowdatetime,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
// 10. 提交事务
|
|
|
|
|
DB::commit();
|
|
|
|
|
|
|
|
|
|
return \Yz::Return(true, '取消成功', []);
|
|
|
|
|
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
|
DB::rollBack();
|
|
|
|
|
// 记录错误日志(建议使用 Laravel 日志系统)
|
|
|
|
|
Log::error("取消预约失败 - ID: {$MainListId}, reg_num: {$reg_num}, 错误: " . $e->getMessage());
|
|
|
|
|
return \Yz::echoError1('取消失败:系统异常,请稍后重试');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function releaseSourceFromDetail($mainListId, $mainInfo = null)
|
|
|
|
|
{
|
|
|
|
|
if (!$mainInfo) {
|
|
|
|
|
$mainInfo = DB::table('s_list')->where(['id' => $mainListId])->first();
|
|
|
|
|
}
|
|
|
|
|
if (!$mainInfo) {
|
|
|
|
|
Log::error("释放号源失败-记录不存在", ['MainListId' => $mainListId]);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$useDetailJson = $mainInfo->appointment_use_plan_detail;
|
|
|
|
|
if (empty($useDetailJson)) {
|
|
|
|
|
$fallbackResult = $this->releaseSourceFallback($mainInfo);
|
|
|
|
|
return $fallbackResult;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$useDetails = json_decode($useDetailJson, true);
|
|
|
|
|
if (!is_array($useDetails)) {
|
|
|
|
|
Log::error("释放号源失败-明细格式错误", ['MainListId' => $mainListId, 'detail' => $useDetailJson]);
|
|
|
|
|
return $this->releaseSourceFallback($mainInfo);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$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')
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 {
|
|
|
|
|
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]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//短信提醒
|
|
|
|
|
public function SendMsg($infos,$dotype=1)
|
|
|
|
|
{
|
|
|
|
|
|