|
|
<?php
|
|
|
|
|
|
namespace App\Http\Controllers\API\Admin;
|
|
|
|
|
|
use App\Http\Controllers\Controller;
|
|
|
use App\Services\UserService;
|
|
|
use App\Services\PlanService;
|
|
|
use Illuminate\Http\Request;
|
|
|
use App\Lib\Rs;
|
|
|
use Illuminate\Support\Facades\DB;
|
|
|
use Illuminate\Support\Facades\Log;
|
|
|
use Illuminate\Validation\Rule;
|
|
|
|
|
|
class PlanController extends Controller
|
|
|
{
|
|
|
private $userService;
|
|
|
private $planService;
|
|
|
|
|
|
public function __construct(UserService $userService, PlanService $planService)
|
|
|
{
|
|
|
$this->userService = $userService;
|
|
|
$this->planService = $planService;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 获取号源列表
|
|
|
* @param Request $request
|
|
|
* @return \Illuminate\Http\JsonResponse
|
|
|
*/
|
|
|
public function list(Request $request)
|
|
|
{
|
|
|
$payload = $request->attributes->get('payload');
|
|
|
if (!$payload) {
|
|
|
return Rs::error('用户信息无效', 401);
|
|
|
}
|
|
|
|
|
|
$user = $this->userService->getUserInfoFromPayload($payload);
|
|
|
if (!$user) {
|
|
|
return Rs::error('用户不存在', 401);
|
|
|
}
|
|
|
|
|
|
$isAdmin = $this->userService->isAdmin($payload);
|
|
|
$userDeptId = $user->dept_id ?? null;
|
|
|
|
|
|
$resourceId = $request->input('resource_id', null);
|
|
|
$startDate = $request->input('start_date', null);
|
|
|
$endDate = $request->input('end_date', null);
|
|
|
|
|
|
// 必须传入资源ID
|
|
|
if (!$resourceId) {
|
|
|
return Rs::error('请选择资源');
|
|
|
}
|
|
|
|
|
|
// 必须传入日期范围
|
|
|
if (!$startDate || !$endDate) {
|
|
|
return Rs::error('请选择日期范围');
|
|
|
}
|
|
|
|
|
|
$query = DB::table('plan_batch')
|
|
|
->leftJoin('department_resource', 'plan_batch.resource_id', '=', 'department_resource.id')
|
|
|
->where('plan_batch.deleted', 0)
|
|
|
->where('plan_batch.resource_id', $resourceId)
|
|
|
->where('plan_batch.plan_date', '>=', $startDate)
|
|
|
->where('plan_batch.plan_date', '<=', $endDate)
|
|
|
->select(
|
|
|
'plan_batch.id',
|
|
|
'plan_batch.id as batch_id',
|
|
|
'plan_batch.resource_id',
|
|
|
'department_resource.name as resource_name',
|
|
|
'department_resource.slot_mode',
|
|
|
'plan_batch.plan_date',
|
|
|
'plan_batch.weekname',
|
|
|
'plan_batch.patient_type_mask',
|
|
|
'plan_batch.start_time',
|
|
|
'plan_batch.end_time',
|
|
|
'plan_batch.cutoff_time',
|
|
|
'plan_batch.total_quota',
|
|
|
'plan_batch.used_quota',
|
|
|
'plan_batch.slot_mode',
|
|
|
'plan_batch.status',
|
|
|
'plan_batch.enable_channel_quota'
|
|
|
);
|
|
|
|
|
|
// 权限控制:非管理员只能查看自己科室的号源
|
|
|
if (!$isAdmin) {
|
|
|
if (empty($userDeptId)) {
|
|
|
return Rs::error('请先绑定科室');
|
|
|
}
|
|
|
$query->where('plan_batch.department_id', $userDeptId);
|
|
|
}
|
|
|
|
|
|
$planBatches = $query->orderBy('plan_batch.plan_date', 'asc')
|
|
|
->orderBy('plan_batch.start_time', 'asc')
|
|
|
->get();
|
|
|
|
|
|
if ($planBatches->isEmpty()) {
|
|
|
return Rs::success(['list' => $planBatches]);
|
|
|
}
|
|
|
|
|
|
$allBatchIds = $planBatches->pluck('batch_id')->toArray();
|
|
|
|
|
|
// 批量预加载 locked_quota(从 plan 表按 batch_id 聚合)
|
|
|
$lockedQuotaMap = DB::table('plan')
|
|
|
->whereIn('batch_id', $allBatchIds)
|
|
|
->where('deleted', 0)
|
|
|
->selectRaw('batch_id, SUM(locked_quota) as locked_quota')
|
|
|
->groupBy('batch_id')
|
|
|
->pluck('locked_quota', 'batch_id');
|
|
|
|
|
|
foreach ($planBatches as $batch) {
|
|
|
$batch->locked_quota = $lockedQuotaMap[$batch->batch_id] ?? 0;
|
|
|
}
|
|
|
|
|
|
// 批量预加载渠道配额
|
|
|
$channelQuotaMap = DB::table('plan_batch_channel_quota as pbcq')
|
|
|
->leftJoin('channel', 'pbcq.channel_id', '=', 'channel.id')
|
|
|
->whereIn('pbcq.batch_id', $allBatchIds)
|
|
|
->where(['pbcq.deleted' => 0, 'pbcq.status' => 1])
|
|
|
->select(
|
|
|
'pbcq.id',
|
|
|
'pbcq.batch_id',
|
|
|
'pbcq.channel_id',
|
|
|
'channel.name as channel_name',
|
|
|
'channel.short_name',
|
|
|
'pbcq.total_quota',
|
|
|
'pbcq.used_quota',
|
|
|
'pbcq.locked_quota',
|
|
|
'pbcq.allow_overuse_public_pool'
|
|
|
)
|
|
|
->get()
|
|
|
->groupBy('batch_id');
|
|
|
|
|
|
// 批量预加载 plan 记录
|
|
|
$planRecordsMap = DB::table('plan')
|
|
|
->whereIn('batch_id', $allBatchIds)
|
|
|
->where('deleted', 0)
|
|
|
->select('id', 'batch_id', 'start_time', 'end_time', 'total_quota', 'used_quota', 'locked_quota', 'status')
|
|
|
->orderBy('start_time', 'asc')
|
|
|
->get()
|
|
|
->groupBy('batch_id');
|
|
|
|
|
|
foreach ($planBatches as $batch) {
|
|
|
$batch->channel_quotas = $channelQuotaMap[$batch->batch_id] ?? collect([]);
|
|
|
|
|
|
$batchPlans = $planRecordsMap[$batch->batch_id] ?? collect([]);
|
|
|
|
|
|
if ($batch->slot_mode == 2) {
|
|
|
$batch->time_points = $batchPlans->map(function ($p) {
|
|
|
return (object)[
|
|
|
'id' => $p->id,
|
|
|
'start_time' => $p->start_time,
|
|
|
'end_time' => $p->end_time,
|
|
|
'total_quota' => $p->total_quota,
|
|
|
'used_quota' => $p->used_quota,
|
|
|
'locked_quota' => $p->locked_quota,
|
|
|
'status' => $p->status,
|
|
|
];
|
|
|
});
|
|
|
} else {
|
|
|
$batch->time_points = [];
|
|
|
$firstPlan = $batchPlans->first();
|
|
|
$batch->plan_id = $firstPlan ? $firstPlan->id : null;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
$data = [
|
|
|
'list' => $planBatches
|
|
|
];
|
|
|
|
|
|
return Rs::success($data);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 删除号源批次
|
|
|
* @param Request $request
|
|
|
* @return \Illuminate\Http\JsonResponse
|
|
|
*/
|
|
|
public function delete(Request $request)
|
|
|
{
|
|
|
$payload = $request->attributes->get('payload');
|
|
|
if (!$payload) {
|
|
|
return Rs::error('用户信息无效', 401);
|
|
|
}
|
|
|
|
|
|
$user = $this->userService->getUserInfoFromPayload($payload);
|
|
|
if (!$user) {
|
|
|
return Rs::error('用户不存在', 401);
|
|
|
}
|
|
|
|
|
|
$isAdmin = $this->userService->isAdmin($payload);
|
|
|
$userDeptId = $user->dept_id ?? null;
|
|
|
|
|
|
$data = $request->validate([
|
|
|
'ids' => 'required|array',
|
|
|
'ids.*' => 'integer'
|
|
|
]);
|
|
|
|
|
|
try {
|
|
|
DB::beginTransaction();
|
|
|
|
|
|
foreach ($data['ids'] as $id) {
|
|
|
// 检查是否存在
|
|
|
$batch = DB::table('plan_batch')
|
|
|
->where('id', $id)
|
|
|
->where('deleted', 0)
|
|
|
->first();
|
|
|
|
|
|
if (!$batch) {
|
|
|
DB::rollBack();
|
|
|
return Rs::error('号源批次不存在:' . $id);
|
|
|
}
|
|
|
|
|
|
// 权限控制:非管理员只能删除自己科室的号源
|
|
|
if (!$isAdmin) {
|
|
|
if (empty($userDeptId)) {
|
|
|
DB::rollBack();
|
|
|
return Rs::error('请先绑定科室');
|
|
|
}
|
|
|
if ($batch->department_id != $userDeptId) {
|
|
|
DB::rollBack();
|
|
|
return Rs::error('无权限删除该号源批次');
|
|
|
}
|
|
|
}
|
|
|
//如果被占用则不允许删除
|
|
|
if($batch->used_quota>0){
|
|
|
DB::rollBack();
|
|
|
return Rs::error($batch->plan_date.' '.$batch->start_time.'已有预约记录,不能删除');
|
|
|
}
|
|
|
|
|
|
// 删除plan记录
|
|
|
DB::table('plan')
|
|
|
->where('batch_id', $batch->id)
|
|
|
->update([
|
|
|
'deleted' => 1,
|
|
|
'updated_at' => now()
|
|
|
]);
|
|
|
|
|
|
// 删除plan_batch_channel_quota记录
|
|
|
DB::table('plan_batch_channel_quota')
|
|
|
->where('batch_id', $batch->id)
|
|
|
->update([
|
|
|
'deleted' => 1,
|
|
|
'updated_at' => now()
|
|
|
]);
|
|
|
|
|
|
// 软删除plan_batch记录
|
|
|
DB::table('plan_batch')
|
|
|
->where('id', $id)
|
|
|
->update([
|
|
|
'deleted' => 1,
|
|
|
'updated_at' => now()
|
|
|
]);
|
|
|
}
|
|
|
|
|
|
DB::commit();
|
|
|
|
|
|
return Rs::success(null, '成功删除 ' . count($data['ids']) . ' 个号源批次');
|
|
|
} catch (\Exception $e) {
|
|
|
DB::rollBack();
|
|
|
return Rs::error('删除号源批次失败:' . $e->getMessage());
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 更新号源批次
|
|
|
* @param Request $request
|
|
|
* @return \Illuminate\Http\JsonResponse
|
|
|
*/
|
|
|
public function update(Request $request)
|
|
|
{
|
|
|
$payload = $request->attributes->get('payload');
|
|
|
if (!$payload) {
|
|
|
return Rs::error('用户信息无效', 401);
|
|
|
}
|
|
|
|
|
|
$user = $this->userService->getUserInfoFromPayload($payload);
|
|
|
if (!$user) {
|
|
|
return Rs::error('用户不存在', 401);
|
|
|
}
|
|
|
|
|
|
$isAdmin = $this->userService->isAdmin($payload);
|
|
|
$userDeptId = $user->dept_id ?? null;
|
|
|
|
|
|
$data = $request->validate([
|
|
|
'id' => 'required|integer',
|
|
|
'start_time' => 'nullable|date_format:H:i:s',
|
|
|
'end_time' => 'nullable|date_format:H:i:s',
|
|
|
'status' => 'nullable|integer|in:0,1',
|
|
|
'total_quota' => 'nullable|integer|min:0',
|
|
|
'enable_channel_quota' => 'nullable|integer|in:0,1',
|
|
|
'channel_quotas' => 'nullable|array',
|
|
|
'channel_quotas.*.channel_id' => 'required|integer',
|
|
|
'channel_quotas.*.total_quota' => 'nullable|integer|min:0',
|
|
|
'channel_quotas.*.allow_overuse_public_pool' => 'nullable|integer|in:0,1',
|
|
|
'time_points' => 'nullable|array',
|
|
|
'time_points.*.id' => 'nullable|integer',
|
|
|
'time_points.*.time_range' => 'nullable|array',
|
|
|
'time_points.*.time_range.*' => 'nullable|date_format:H:i:s',
|
|
|
'time_points.*.status' => 'nullable|integer|in:0,1'
|
|
|
]);
|
|
|
|
|
|
try {
|
|
|
DB::beginTransaction();
|
|
|
|
|
|
// 检查是否存在
|
|
|
$batch = DB::table('plan_batch')
|
|
|
->where('id', $data['id'])
|
|
|
->where('deleted', 0)
|
|
|
->first();
|
|
|
|
|
|
if (!$batch) {
|
|
|
DB::rollBack();
|
|
|
return Rs::error('号源批次不存在');
|
|
|
}
|
|
|
|
|
|
// 权限控制:非管理员只能修改自己科室的号源
|
|
|
if (!$isAdmin) {
|
|
|
if (empty($userDeptId)) {
|
|
|
DB::rollBack();
|
|
|
return Rs::error('请先绑定科室');
|
|
|
}
|
|
|
if ($batch->department_id != $userDeptId) {
|
|
|
DB::rollBack();
|
|
|
return Rs::error('无权限修改该号源批次');
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 如果是时间点模式,验证时间段时长能否被总数整除
|
|
|
if ($batch->slot_mode == 2) {
|
|
|
$startTime = strtotime('2000-01-01 ' . (isset($data['start_time']) ? $data['start_time'] : $batch->start_time));
|
|
|
$endTime = strtotime('2000-01-01 ' . (isset($data['end_time']) ? $data['end_time'] : $batch->end_time));
|
|
|
$totalMinutes = ($endTime - $startTime) / 60;
|
|
|
$totalQuota = isset($data['total_quota']) ? $data['total_quota'] : $batch->total_quota;
|
|
|
|
|
|
if ($totalMinutes % $totalQuota !== 0) {
|
|
|
DB::rollBack();
|
|
|
return Rs::error('时间点模式下,时间段总时长必须能被号源数量整除。当前时长 ' . $totalMinutes . ' 分钟,号源数量 ' . $totalQuota . ',无法整除');
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 验证时间逻辑
|
|
|
if (!empty($data['start_time']) && !empty($data['end_time'])) {
|
|
|
$startTime = strtotime('2000-01-01 ' . $data['start_time']);
|
|
|
$endTime = strtotime('2000-01-01 ' . $data['end_time']);
|
|
|
|
|
|
if ($endTime <= $startTime) {
|
|
|
DB::rollBack();
|
|
|
return Rs::error('结束时间必须晚于开始时间');
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 验证总数与渠道配额之和是否相等
|
|
|
if (!empty($data['channel_quotas'])) {
|
|
|
$channelTotal = 0;
|
|
|
foreach ($data['channel_quotas'] as $quota) {
|
|
|
$channelTotal += $quota['total_quota'];
|
|
|
}
|
|
|
|
|
|
$planTotal = isset($data['total_quota']) ? $data['total_quota'] : $batch->total_quota;
|
|
|
|
|
|
if ($channelTotal != $planTotal) {
|
|
|
DB::rollBack();
|
|
|
return Rs::error('号源总数(' . $planTotal . ')与渠道配额之和(' . $channelTotal . ')不相等,请检查后重试');
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 校验:总配额不能小于已预约数
|
|
|
if (isset($data['total_quota']) && $data['total_quota'] < $batch->used_quota) {
|
|
|
DB::rollBack();
|
|
|
return Rs::error('总配额不能小于已预约数(当前已预约 ' . $batch->used_quota . ')');
|
|
|
}
|
|
|
|
|
|
// 校验:启用渠道配额时,修改总数必须同时提交渠道配额
|
|
|
if (isset($data['total_quota']) && ($batch->enable_channel_quota ?? 0) == 1 && empty($data['channel_quotas'])) {
|
|
|
DB::rollBack();
|
|
|
return Rs::error('当前号源已启用渠道配额,修改总数时必须同时提交渠道配额分配');
|
|
|
}
|
|
|
|
|
|
// 记录乐观锁版本号
|
|
|
$batchVersion = $batch->version ?? 0;
|
|
|
|
|
|
// 更新plan_batch
|
|
|
$updateData = [];
|
|
|
if (isset($data['status'])) {
|
|
|
$updateData['status'] = $data['status'];
|
|
|
}
|
|
|
if (isset($data['total_quota'])) {
|
|
|
$updateData['total_quota'] = $data['total_quota'];
|
|
|
}
|
|
|
if (isset($data['enable_channel_quota'])) {
|
|
|
$updateData['enable_channel_quota'] = $data['enable_channel_quota'];
|
|
|
}
|
|
|
if (!empty($data['start_time'])) {
|
|
|
$updateData['start_time'] = $data['start_time'];
|
|
|
}
|
|
|
if (!empty($data['end_time'])) {
|
|
|
$updateData['end_time'] = $data['end_time'];
|
|
|
$updateData['cutoff_time'] = $data['end_time'];
|
|
|
}
|
|
|
|
|
|
if (!empty($updateData)) {
|
|
|
$updateData['updated_at'] = now();
|
|
|
$updateData['version'] = $batchVersion + 1;
|
|
|
$batchAffected = DB::table('plan_batch')
|
|
|
->where('id', $data['id'])
|
|
|
->where('version', $batchVersion)
|
|
|
->update($updateData);
|
|
|
|
|
|
if ($batchAffected === 0) {
|
|
|
DB::rollBack();
|
|
|
return Rs::error('数据已被其他操作修改,请刷新后重试');
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 时间段模式:同步 plan.total_quota
|
|
|
if ($batch->slot_mode == 1 && isset($data['total_quota'])) {
|
|
|
$planRecord = DB::table('plan')
|
|
|
->where('batch_id', $data['id'])
|
|
|
->where('deleted', 0)
|
|
|
->first();
|
|
|
|
|
|
if ($planRecord) {
|
|
|
$planAffected = DB::table('plan')
|
|
|
->where('id', $planRecord->id)
|
|
|
->where('version', $planRecord->version ?? 0)
|
|
|
->update([
|
|
|
'total_quota' => $data['total_quota'],
|
|
|
'version' => ($planRecord->version ?? 0) + 1,
|
|
|
'updated_at' => now()
|
|
|
]);
|
|
|
|
|
|
if ($planAffected === 0) {
|
|
|
DB::rollBack();
|
|
|
return Rs::error('号源数据已被其他操作修改,请刷新后重试');
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 时间点模式:如果调整了号源总量,需要重新生成时间点节点
|
|
|
if ($batch->slot_mode == 2 && isset($data['total_quota'])) {
|
|
|
$newTotalQuota = $data['total_quota'];
|
|
|
$oldTotalQuota = $batch->total_quota;
|
|
|
|
|
|
// 只有当号源总量发生变化时才处理
|
|
|
if ($newTotalQuota != $oldTotalQuota) {
|
|
|
// 获取时间范围(使用更新后的值,如果没有传则使用原值)
|
|
|
$startTime = $batch->start_time;
|
|
|
$endTime = $batch->end_time;
|
|
|
|
|
|
// 计算时间间隔(秒)
|
|
|
// 需要在同一天的基础上计算时间差
|
|
|
$baseDate = '2024-01-01';
|
|
|
$startTimeObj = \Carbon\Carbon::parse($baseDate . ' ' . $startTime);
|
|
|
$endTimeObj = \Carbon\Carbon::parse($baseDate . ' ' . $endTime);
|
|
|
$totalSeconds = $startTimeObj->diffInSeconds($endTimeObj);
|
|
|
$intervalSeconds = $totalSeconds / $newTotalQuota;
|
|
|
|
|
|
// 生成新的时间点列表
|
|
|
$newTimePoints = [];
|
|
|
for ($i = 0; $i < $newTotalQuota; $i++) {
|
|
|
$pointStartTime = $startTimeObj->copy()->addSeconds($i * $intervalSeconds);
|
|
|
$pointEndTime = $startTimeObj->copy()->addSeconds(($i + 1) * $intervalSeconds);
|
|
|
$newTimePoints[] = [
|
|
|
'start_time' => $pointStartTime->format('H:i:s'),
|
|
|
'end_time' => $pointEndTime->format('H:i:s')
|
|
|
];
|
|
|
}
|
|
|
Log::info($newTimePoints);
|
|
|
// 获取原有的 plan 节点(只查询未删除的)
|
|
|
$existingPlans = DB::table('plan')
|
|
|
->where('batch_id', $batch->id)
|
|
|
->where('deleted', 0)
|
|
|
->select('id', 'start_time', 'end_time', 'used_quota', 'status')
|
|
|
->get();
|
|
|
|
|
|
// 建立 start_time 到现有节点的映射(方便快速查找)
|
|
|
$existingPlansByStartTime = [];
|
|
|
foreach ($existingPlans as $plan) {
|
|
|
$existingPlansByStartTime[$plan->start_time] = $plan;
|
|
|
}
|
|
|
|
|
|
// 遍历新的时间点,找出需要保留和新增的
|
|
|
$keepStartTimes = []; // 记录需要保留的时间点
|
|
|
foreach ($newTimePoints as $newPoint) {
|
|
|
$startTimeStr = $newPoint['start_time'];
|
|
|
$keepStartTimes[] = $startTimeStr;
|
|
|
|
|
|
if (!isset($existingPlansByStartTime[$startTimeStr])) {
|
|
|
// 时间点不存在,新增
|
|
|
DB::table('plan')->insert([
|
|
|
'batch_id' => $batch->id,
|
|
|
'start_time' => $newPoint['start_time'],
|
|
|
'end_time' => $newPoint['end_time'],
|
|
|
'total_quota' => 1,
|
|
|
'used_quota' => 0,
|
|
|
'status' => 1,
|
|
|
'deleted' => 0,
|
|
|
'created_at' => now(),
|
|
|
'updated_at' => now()
|
|
|
]);
|
|
|
}
|
|
|
// 时间点已存在则什么都不做,保留原样
|
|
|
}
|
|
|
|
|
|
// 处理不在新节点列表中的原有节点
|
|
|
foreach ($existingPlans as $plan) {
|
|
|
if (!in_array($plan->start_time, $keepStartTimes)) {
|
|
|
// 不在新节点列表中
|
|
|
if ($plan->used_quota > 0) {
|
|
|
// 已占用,设置为不可用
|
|
|
DB::table('plan')
|
|
|
->where('id', $plan->id)
|
|
|
->update([
|
|
|
'status' => 0,
|
|
|
'updated_at' => now()
|
|
|
]);
|
|
|
} else {
|
|
|
// 未占用,软删除
|
|
|
DB::table('plan')
|
|
|
->where('id', $plan->id)
|
|
|
->update([
|
|
|
'deleted' => 1,
|
|
|
'updated_at' => now()
|
|
|
]);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 更新渠道配额
|
|
|
if (!empty($data['channel_quotas'])) {
|
|
|
// 获取前端提交的 channel_id 列表
|
|
|
$submittedChannelIds = array_map(function($quota) {
|
|
|
return $quota['channel_id'];
|
|
|
}, $data['channel_quotas']);
|
|
|
|
|
|
// 停用数据库中存在但前端未提交的渠道配额(即关闭的渠道)
|
|
|
DB::table('plan_batch_channel_quota')
|
|
|
->where('batch_id', $batch->id)
|
|
|
->where('status', 1) // 只停用正常的
|
|
|
->whereNotIn('channel_id', $submittedChannelIds)
|
|
|
->update(['status' => 0, 'updated_at' => now()]);
|
|
|
|
|
|
// 更新或插入前端提交的渠道配额
|
|
|
foreach ($data['channel_quotas'] as $quota) {
|
|
|
$updateQuotaData = [
|
|
|
'total_quota' => $quota['total_quota'],
|
|
|
'status' => 1, // 恢复或设置为正常
|
|
|
'updated_at' => now()
|
|
|
];
|
|
|
|
|
|
// 如果提供了 allow_overuse_public_pool,则更新该字段
|
|
|
if (isset($quota['allow_overuse_public_pool'])) {
|
|
|
$updateQuotaData['allow_overuse_public_pool'] = $quota['allow_overuse_public_pool'];
|
|
|
}
|
|
|
|
|
|
// 检查是否已存在该渠道配额(包括已停用的)
|
|
|
$existingQuota = DB::table('plan_batch_channel_quota')
|
|
|
->where('batch_id', $batch->id)
|
|
|
->where('channel_id', $quota['channel_id'])
|
|
|
->first();
|
|
|
|
|
|
if ($existingQuota) {
|
|
|
// 存在则更新(恢复停用的,或更新正常的,保留 used_quota)
|
|
|
DB::table('plan_batch_channel_quota')
|
|
|
->where('id', $existingQuota->id)
|
|
|
->update($updateQuotaData);
|
|
|
} else {
|
|
|
// 不存在则插入(新增渠道)
|
|
|
DB::table('plan_batch_channel_quota')->insert([
|
|
|
'batch_id' => $batch->id,
|
|
|
'channel_id' => $quota['channel_id'],
|
|
|
'total_quota' => $quota['total_quota'],
|
|
|
'used_quota' => 0,
|
|
|
'allow_overuse_public_pool' => $quota['allow_overuse_public_pool'] ?? 0,
|
|
|
'status' => 1,
|
|
|
'created_at' => now(),
|
|
|
'updated_at' => now()
|
|
|
]);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 更新时间点
|
|
|
// 如果时间点模式且调整了号源总量,已经由上面的逻辑重新生成了时间点,这里跳过处理
|
|
|
$skipTimePoints = ($batch->slot_mode == 2 && isset($data['total_quota']) && $data['total_quota'] != $batch->total_quota);
|
|
|
if (!$skipTimePoints && !empty($data['time_points'])) {
|
|
|
foreach ($data['time_points'] as $point) {
|
|
|
$updatePointData = [];
|
|
|
if (!empty($point['time_range']) && is_array($point['time_range'])) {
|
|
|
$updatePointData['start_time'] = $point['time_range'][0];
|
|
|
$updatePointData['end_time'] = $point['time_range'][1];
|
|
|
}
|
|
|
if (isset($point['status'])) {
|
|
|
$updatePointData['status'] = $point['status'];
|
|
|
}
|
|
|
|
|
|
if (!empty($updatePointData)) {
|
|
|
$updatePointData['updated_at'] = now();
|
|
|
DB::table('plan')
|
|
|
->where('id', $point['id'])
|
|
|
->update($updatePointData);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
DB::commit();
|
|
|
|
|
|
return Rs::success(null, '更新成功');
|
|
|
} catch (\Exception $e) {
|
|
|
DB::rollBack();
|
|
|
return Rs::error('更新号源批次失败:' . $e->getMessage());
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 获取号源预约记录
|
|
|
* @param Request $request
|
|
|
* @return \Illuminate\Http\JsonResponse
|
|
|
*/
|
|
|
public function appointments(Request $request)
|
|
|
{
|
|
|
$payload = $request->attributes->get('payload');
|
|
|
if (!$payload) {
|
|
|
return Rs::error('用户信息无效', 401);
|
|
|
}
|
|
|
|
|
|
$user = $this->userService->getUserInfoFromPayload($payload);
|
|
|
if (!$user) {
|
|
|
return Rs::error('用户不存在', 401);
|
|
|
}
|
|
|
|
|
|
$isAdmin = $this->userService->isAdmin($payload);
|
|
|
$userDeptId = $user->dept_id ?? null;
|
|
|
|
|
|
$planBatchId = $request->input('plan_batch_id', null);
|
|
|
|
|
|
// 必须传入号源批次ID
|
|
|
if (!$planBatchId) {
|
|
|
return Rs::error('请提供号源批次ID');
|
|
|
}
|
|
|
|
|
|
// 获取plan批次信息
|
|
|
$planBatch = DB::table('plan_batch')
|
|
|
->where('id', $planBatchId)
|
|
|
->where('deleted', 0)
|
|
|
->first();
|
|
|
|
|
|
if (!$planBatch) {
|
|
|
return Rs::error('号源批次不存在');
|
|
|
}
|
|
|
|
|
|
// 权限控制:非管理员只能查看自己科室的号源
|
|
|
if (!$isAdmin) {
|
|
|
if (empty($userDeptId)) {
|
|
|
return Rs::error('请先绑定科室');
|
|
|
}
|
|
|
if ($planBatch->department_id != $userDeptId) {
|
|
|
return Rs::error('无权限查看该号源的预约记录');
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 获取所有plan的ID(时间段和时间点模式都需要查询plan表)
|
|
|
$planIds = [];
|
|
|
$plans = DB::table('plan')
|
|
|
->where('batch_id', $planBatchId)
|
|
|
->where('deleted', 0)
|
|
|
->select('id')
|
|
|
->get();
|
|
|
foreach ($plans as $plan) {
|
|
|
$planIds[] = $plan->id;
|
|
|
}
|
|
|
|
|
|
// 查询exam_appointment表
|
|
|
$query = DB::table('exam_appointment')
|
|
|
->whereIn('time_slot_id', $planIds)
|
|
|
->whereIn('exam_appointment.status', [1,2,3])
|
|
|
->leftJoin('channel', 'exam_appointment.channel_id', '=', 'channel.id')
|
|
|
->leftJoin('exam_application', 'exam_appointment.exam_application_id', '=', 'exam_application.id')
|
|
|
->select(
|
|
|
'exam_appointment.id',
|
|
|
'exam_appointment.time_slot_id',
|
|
|
'exam_appointment.patient_id',
|
|
|
'exam_appointment.patient_name',
|
|
|
'exam_appointment.appointment_time',
|
|
|
'exam_appointment.status',
|
|
|
'exam_appointment.channel_id',
|
|
|
'channel.name as channel_name',
|
|
|
'exam_application.examination_item_name',
|
|
|
'exam_appointment.created_at'
|
|
|
)
|
|
|
->orderBy('exam_appointment.created_at', 'desc');
|
|
|
|
|
|
$appointments = $query->get();
|
|
|
|
|
|
// 批量预加载 plan → batch → resource 映射
|
|
|
$uniqueTimeSlotIds = $appointments->pluck('time_slot_id')->unique()->toArray();
|
|
|
$planMap = DB::table('plan')
|
|
|
->whereIn('id', $uniqueTimeSlotIds)
|
|
|
->where('deleted', 0)
|
|
|
->pluck('batch_id', 'id');
|
|
|
|
|
|
$uniqueBatchIds = $planMap->unique()->values()->toArray();
|
|
|
$batchMap = DB::table('plan_batch')
|
|
|
->whereIn('id', $uniqueBatchIds)
|
|
|
->pluck('resource_id', 'id');
|
|
|
|
|
|
$uniqueResourceIds = $batchMap->unique()->values()->toArray();
|
|
|
$resourceMap = DB::table('department_resource')
|
|
|
->whereIn('id', $uniqueResourceIds)
|
|
|
->pluck('name', 'id');
|
|
|
|
|
|
foreach ($appointments as $appointment) {
|
|
|
$batchId = $planMap[$appointment->time_slot_id] ?? null;
|
|
|
if ($batchId) {
|
|
|
$resourceId = $batchMap[$batchId] ?? null;
|
|
|
$appointment->resource_name = $resourceId ? ($resourceMap[$resourceId] ?? '') : '';
|
|
|
} else {
|
|
|
$appointment->resource_name = '';
|
|
|
}
|
|
|
}
|
|
|
|
|
|
$data = [
|
|
|
'list' => $appointments
|
|
|
];
|
|
|
|
|
|
return Rs::success($data);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 获取可用号源(用于预约)
|
|
|
* @param Request $request
|
|
|
* @return \Illuminate\Http\JsonResponse
|
|
|
*/
|
|
|
public function GetEnablePlan(Request $request)
|
|
|
{
|
|
|
$data = $request->validate([
|
|
|
'exam_application_ids' => 'required|array',
|
|
|
'exam_application_ids.*' => 'integer',
|
|
|
'channel_id' => 'required|integer|min:1',
|
|
|
'start_date' => 'nullable|date|after_or_equal:today'
|
|
|
]);
|
|
|
|
|
|
$examApplicationIds = $data['exam_application_ids'];
|
|
|
$channelId = $data['channel_id'];
|
|
|
$startDate = $data['start_date'] ?? date('Y-m-d');
|
|
|
|
|
|
try {
|
|
|
// 使用 PlanService 获取公共数据
|
|
|
$planData = $this->planService->queryPlanBatchesData($examApplicationIds, $channelId, $startDate, 15);
|
|
|
|
|
|
$planBatches = $planData['planBatches'];
|
|
|
$quotaByBatch = $planData['quotaByBatch'];
|
|
|
$totalUseSeats = $planData['totalUseSeats'];
|
|
|
|
|
|
// 8. 获取当前时间用于判断截止时间
|
|
|
$now = now();
|
|
|
$currentDate = $now->format('Y-m-d');
|
|
|
|
|
|
// 8.1 批量预加载所有 plan 记录,避免循环内 N+1 查询
|
|
|
$allBatchIds = $planBatches->pluck('batch_id')->toArray();
|
|
|
$allPlanRecordsMap = DB::table('plan')
|
|
|
->whereIn('batch_id', $allBatchIds)
|
|
|
->where('deleted', 0)
|
|
|
->select('id', 'batch_id', 'start_time', 'end_time', 'total_quota', 'used_quota', 'locked_quota', 'status')
|
|
|
->orderBy('start_time')
|
|
|
->get()
|
|
|
->groupBy('batch_id');
|
|
|
|
|
|
// 9. 构建响应数据
|
|
|
$resourcesData = [];
|
|
|
$batchesByResource = $planBatches->groupBy('resource_id');
|
|
|
|
|
|
foreach ($batchesByResource as $resourceId => $batches) {
|
|
|
$resource = $batches->first();
|
|
|
$slotMode = $resource->slot_mode;
|
|
|
$datesData = [];
|
|
|
$batchesByDate = $batches->groupBy('plan_date');
|
|
|
|
|
|
foreach ($batchesByDate->keys() as $date) {
|
|
|
if (!isset($batchesByDate[$date])) {
|
|
|
$datesData[$date] = ['date' => $date, 'periods' => []];
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
$periods = [];
|
|
|
foreach ($batchesByDate[$date] as $batch) {
|
|
|
// 判断是否超过截止时间
|
|
|
$isPastCutoff = ($batch->plan_date === $currentDate) &&
|
|
|
$now->gt(\Carbon\Carbon::createFromFormat('Y-m-d H:i:s', $batch->plan_date . ' ' . $batch->cutoff_time));
|
|
|
|
|
|
if ($slotMode == 1) {
|
|
|
// ========== 时间段模式 ==========
|
|
|
$slots = [];
|
|
|
$quotas = $quotaByBatch[$batch->batch_id] ?? ['public' => null, 'channel' => null];
|
|
|
$publicQuota = $quotas['public'];
|
|
|
$channelQuota = $quotas['channel'];
|
|
|
|
|
|
if (($batch->enable_channel_quota ?? 0) == 0) {
|
|
|
if (!$publicQuota) continue;
|
|
|
$effectiveQuota = $publicQuota;
|
|
|
$allowOveruse = 0;
|
|
|
} else {
|
|
|
if (!$channelQuota) continue;
|
|
|
$effectiveQuota = $channelQuota;
|
|
|
$allowOveruse = $channelQuota->allow_overuse_public_pool ?? 0;
|
|
|
}
|
|
|
|
|
|
$totalQuota = $effectiveQuota->total_quota;
|
|
|
$usedQuota = $effectiveQuota->used_quota;
|
|
|
$lockedQuota = $effectiveQuota->locked_quota ?? 0;
|
|
|
|
|
|
if ($allowOveruse && $publicQuota && ($batch->enable_channel_quota ?? 0) != 0) {
|
|
|
$totalQuota += $publicQuota->total_quota;
|
|
|
$usedQuota += $publicQuota->used_quota;
|
|
|
$lockedQuota += $publicQuota->locked_quota ?? 0;
|
|
|
}
|
|
|
|
|
|
$remainCount = max(0, $totalQuota - $usedQuota - $lockedQuota);
|
|
|
|
|
|
// 判断剩余号源是否满足所有项目的占位数,且未超过截止时间
|
|
|
$enable = $remainCount >= $totalUseSeats && !$isPastCutoff;
|
|
|
|
|
|
// 从预加载的 plan 记录中获取(时间段模式只有1条)
|
|
|
$batchPlans = $allPlanRecordsMap[$batch->batch_id] ?? collect([]);
|
|
|
$planRecord = $batchPlans->first();
|
|
|
|
|
|
$slots[] = [
|
|
|
'id' => $planRecord ? $planRecord->id : $batch->batch_id,
|
|
|
'time' => substr($batch->start_time, 0, 5),
|
|
|
'used_count' => $totalQuota - $remainCount,
|
|
|
'total_count' => $totalQuota,
|
|
|
'enable' => $enable
|
|
|
];
|
|
|
|
|
|
$periods[] = [
|
|
|
'period_id' => $batch->batch_id,
|
|
|
'period_name' => '',
|
|
|
'time_range' => substr($batch->start_time, 0, 5) . '-' . substr($batch->end_time, 0, 5),
|
|
|
'slots' => $slots
|
|
|
];
|
|
|
} else {
|
|
|
// ========== 时间点模式(关键修正)==========
|
|
|
$quotas = $quotaByBatch[$batch->batch_id] ?? ['public' => null, 'channel' => null];
|
|
|
$publicQuota = $quotas['public'];
|
|
|
$channelQuota = $quotas['channel'];
|
|
|
$enableChannelQuota = ($batch->enable_channel_quota ?? 0) == 1;
|
|
|
|
|
|
// 如果开启了渠道配额,但该渠道没有配额(无记录或status=0或deleted=1),则跳过该批次
|
|
|
if ($enableChannelQuota && !$channelQuota) {
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
// 计算整体配额状态
|
|
|
$overallTotal = 0;
|
|
|
$overallUsed = 0;
|
|
|
$hasQuota = false;
|
|
|
|
|
|
if (!$enableChannelQuota) {
|
|
|
if ($publicQuota) {
|
|
|
$overallTotal = $publicQuota->total_quota;
|
|
|
$overallUsed = $publicQuota->used_quota;
|
|
|
$hasQuota = true;
|
|
|
}
|
|
|
} else {
|
|
|
if ($channelQuota) {
|
|
|
$overallTotal = $channelQuota->total_quota;
|
|
|
$overallUsed = $channelQuota->used_quota;
|
|
|
$allowOveruse = $channelQuota->allow_overuse_public_pool ?? 0;
|
|
|
if ($allowOveruse && $publicQuota) {
|
|
|
$overallTotal += $publicQuota->total_quota;
|
|
|
$overallUsed += $publicQuota->used_quota;
|
|
|
}
|
|
|
$hasQuota = true;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 从预加载的 plan 记录中获取时间点
|
|
|
$batchPlanRecords = $allPlanRecordsMap[$batch->batch_id] ?? collect([]);
|
|
|
$timePoints = $batchPlanRecords->filter(function ($p) {
|
|
|
return $p->status == 1;
|
|
|
});
|
|
|
|
|
|
if ($timePoints->isEmpty()) {
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
$slots = [];
|
|
|
foreach ($timePoints as $tp) {
|
|
|
// 判断时间点是否过期(超过开始时间就不能用了)
|
|
|
$isTimeExpired = false;
|
|
|
if ($batch->plan_date === $currentDate) {
|
|
|
// 当日:比较时间点的开始时间与当前时间
|
|
|
$tpStartTime = \Carbon\Carbon::createFromFormat('Y-m-d H:i:s', $batch->plan_date . ' ' . $tp->start_time);
|
|
|
$isTimeExpired = $now->gt($tpStartTime);
|
|
|
} else if ($batch->plan_date < $currentDate) {
|
|
|
// 过去的日期:直接算过期
|
|
|
$isTimeExpired = true;
|
|
|
}
|
|
|
|
|
|
if (!$hasQuota || ($overallTotal - $overallUsed) <= 0) {
|
|
|
// 无配额 或 整体已满 → 强制显示为占满
|
|
|
$slots[] = [
|
|
|
'id' => $tp->id,
|
|
|
'time' => substr($tp->start_time, 0, 5),
|
|
|
'used_count' => $tp->used_quota,
|
|
|
'total_count' => $tp->total_quota,
|
|
|
'enable' => false
|
|
|
];
|
|
|
} else {
|
|
|
// 有剩余配额 → 显示真实数据,同时判断是否超过截止时间或时间点过期
|
|
|
$availableQuota = $tp->total_quota - $tp->used_quota - ($tp->locked_quota ?? 0);
|
|
|
$enable = $availableQuota > 0 && !$isPastCutoff && !$isTimeExpired;
|
|
|
$slots[] = [
|
|
|
'id' => $tp->id,
|
|
|
'time' => substr($tp->start_time, 0, 5),
|
|
|
'used_count' => $tp->used_quota,
|
|
|
'total_count' => $tp->total_quota,
|
|
|
'locked_count' => $tp->locked_quota ?? 0,
|
|
|
'enable' => $enable
|
|
|
];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
$periods[] = [
|
|
|
'period_id' => $batch->batch_id,
|
|
|
'period_name' => '',
|
|
|
'time_range' => substr($batch->start_time, 0, 5) . '-' . substr($batch->end_time, 0, 5),
|
|
|
'slots' => $slots
|
|
|
];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
$datesData[$date] = ['date' => $date, 'periods' => $periods];
|
|
|
}
|
|
|
|
|
|
$resourcesData[] = [
|
|
|
'room_id' => $resourceId,
|
|
|
'room_name' => $resource->resource_name,
|
|
|
'slot_mode' => $slotMode,
|
|
|
'slot_data' => array_values($datesData)
|
|
|
];
|
|
|
}
|
|
|
|
|
|
return Rs::success(['resources' => $resourcesData]);
|
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
return Rs::error('获取可用号源失败:' . $e->getMessage());
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 获取号源日历(按月查询)
|
|
|
* @param Request $request
|
|
|
* @return \Illuminate\Http\JsonResponse
|
|
|
*/
|
|
|
public function GetPlanCalendar(Request $request)
|
|
|
{
|
|
|
$data = $request->validate([
|
|
|
'exam_application_ids' => 'required|array',
|
|
|
'exam_application_ids.*' => 'integer',
|
|
|
'channel_id' => 'required|integer|min:1',
|
|
|
'year' => 'required|integer|min:2020|max:2099',
|
|
|
'month' => 'required|integer|min:1|max:12'
|
|
|
]);
|
|
|
|
|
|
$examApplicationIds = $data['exam_application_ids'];
|
|
|
$channelId = $data['channel_id'];
|
|
|
$year = $data['year'];
|
|
|
$month = $data['month'];
|
|
|
|
|
|
try {
|
|
|
// 1. 生成日历显示的日期范围(覆盖整个日历网格,包括上月末尾和下月开头的日期)
|
|
|
$firstDayOfMonth = strtotime(sprintf('%04d-%02d-01', $year, $month));
|
|
|
$firstDayOfWeek = date('w', $firstDayOfMonth);
|
|
|
|
|
|
$startDate = date('Y-m-d', strtotime("-{$firstDayOfWeek} days", $firstDayOfMonth));
|
|
|
|
|
|
$daysInMonth = cal_days_in_month(CAL_GREGORIAN, $month, $year);
|
|
|
$lastDayOfWeek = date('w', strtotime(sprintf('%04d-%02d-%d', $year, $month, $daysInMonth)));
|
|
|
$totalDays = $firstDayOfWeek + $daysInMonth + (6 - $lastDayOfWeek);
|
|
|
$totalRows = ceil($totalDays / 7);
|
|
|
|
|
|
$today = date('Y-m-d');
|
|
|
$calendarDates = [];
|
|
|
$currentDate = $startDate;
|
|
|
for ($i = 0; $i < $totalRows * 7; $i++) {
|
|
|
if ($currentDate >= $today) {
|
|
|
$calendarDates[] = $currentDate;
|
|
|
}
|
|
|
$currentDate = date('Y-m-d', strtotime('+1 day', strtotime($currentDate)));
|
|
|
}
|
|
|
|
|
|
if (empty($calendarDates)) {
|
|
|
return Rs::success(['resources' => []]);
|
|
|
}
|
|
|
|
|
|
// 2. 使用 PlanService 获取公共数据
|
|
|
$planData = $this->planService->queryPlanBatchesData(
|
|
|
$examApplicationIds, $channelId, '', 0, $calendarDates
|
|
|
);
|
|
|
|
|
|
$planBatches = $planData['planBatches'];
|
|
|
$quotaByBatch = $planData['quotaByBatch'];
|
|
|
|
|
|
if ($planBatches->isEmpty()) {
|
|
|
return Rs::success(['resources' => []]);
|
|
|
}
|
|
|
|
|
|
// 3. 按资源分组,聚合为日历格式
|
|
|
$resourcesData = [];
|
|
|
$batchesByResource = $planBatches->groupBy('resource_id');
|
|
|
|
|
|
foreach ($batchesByResource as $resourceId => $batches) {
|
|
|
$resource = $batches->first();
|
|
|
$batchesByDate = $batches->groupBy('plan_date');
|
|
|
|
|
|
$datesData = [];
|
|
|
foreach ($calendarDates as $date) {
|
|
|
if (!isset($batchesByDate[$date])) {
|
|
|
$datesData[$date] = [
|
|
|
'date' => $date,
|
|
|
'total_count' => 0,
|
|
|
'available_count' => 0
|
|
|
];
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
$totalCount = 0;
|
|
|
$availableCount = 0;
|
|
|
|
|
|
foreach ($batchesByDate[$date] as $batch) {
|
|
|
$remaining = $this->planService->calculateRemainingQuota(
|
|
|
$batch,
|
|
|
$channelId,
|
|
|
$quotaByBatch[$batch->batch_id]['channel'] ?? null,
|
|
|
$quotaByBatch[$batch->batch_id]['public'] ?? null,
|
|
|
1
|
|
|
);
|
|
|
|
|
|
$quotas = $quotaByBatch[$batch->batch_id] ?? ['public' => null, 'channel' => null];
|
|
|
$publicQuota = $quotas['public'];
|
|
|
$channelQuota = $quotas['channel'];
|
|
|
$enableChannelQuota = ($batch->enable_channel_quota ?? 0) == 1;
|
|
|
|
|
|
if (!$enableChannelQuota) {
|
|
|
if (!$publicQuota) continue;
|
|
|
$totalQuota = $publicQuota->total_quota;
|
|
|
$usedQuota = $publicQuota->used_quota;
|
|
|
$lockedQuota = $publicQuota->locked_quota ?? 0;
|
|
|
} else {
|
|
|
if (!$channelQuota) continue;
|
|
|
$totalQuota = $channelQuota->total_quota;
|
|
|
$usedQuota = $channelQuota->used_quota;
|
|
|
$lockedQuota = $channelQuota->locked_quota ?? 0;
|
|
|
$allowOveruse = $channelQuota->allow_overuse_public_pool ?? 0;
|
|
|
if ($allowOveruse && $publicQuota) {
|
|
|
$totalQuota += $publicQuota->total_quota;
|
|
|
$usedQuota += $publicQuota->used_quota;
|
|
|
$lockedQuota += $publicQuota->locked_quota ?? 0;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
$totalCount += $totalQuota;
|
|
|
$availableCount += max(0, $totalQuota - $usedQuota - $lockedQuota);
|
|
|
}
|
|
|
|
|
|
$datesData[$date] = [
|
|
|
'date' => $date,
|
|
|
'total_count' => $totalCount,
|
|
|
'available_count' => $availableCount
|
|
|
];
|
|
|
}
|
|
|
|
|
|
$resourcesData[] = [
|
|
|
'room_id' => $resourceId,
|
|
|
'room_name' => $resource->resource_name,
|
|
|
'slot_data' => array_values($datesData)
|
|
|
];
|
|
|
}
|
|
|
|
|
|
return Rs::success([
|
|
|
'resources' => $resourcesData
|
|
|
]);
|
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
return Rs::error('获取号源日历失败:' . $e->getMessage());
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 创建占位
|
|
|
*/
|
|
|
public function createLock(Request $request)
|
|
|
{
|
|
|
$data = $request->validate([
|
|
|
'plan_ids' => 'nullable|array',
|
|
|
'plan_ids.*' => 'nullable|integer',
|
|
|
'plan_id' => 'nullable|integer',
|
|
|
'channel_locks' => 'nullable|array',
|
|
|
'channel_locks.*.channel_id' => 'required|integer',
|
|
|
'channel_locks.*.locked_count' => 'required|integer|min:1',
|
|
|
'reason' => 'required|string|max:500'
|
|
|
]);
|
|
|
|
|
|
$payload = $request->attributes->get('payload');
|
|
|
if (!$payload) {
|
|
|
return Rs::error('用户信息无效', 401);
|
|
|
}
|
|
|
|
|
|
$user = $this->userService->getUserInfoFromPayload($payload);
|
|
|
if (!$user) {
|
|
|
return Rs::error('用户不存在', 401);
|
|
|
}
|
|
|
|
|
|
$operatorId = $user->user_id ?? null;
|
|
|
$operatorName = $user->name ?? '';
|
|
|
|
|
|
try {
|
|
|
DB::beginTransaction();
|
|
|
|
|
|
$planIds = [];
|
|
|
|
|
|
// 时间段模式:单个plan_id + 渠道占位
|
|
|
if (isset($data['plan_id'])) {
|
|
|
$planId = $data['plan_id'];
|
|
|
|
|
|
// 查询plan和batch信息
|
|
|
$plan = DB::table('plan')->where('id', $planId)->where('deleted', 0)->first();
|
|
|
if (!$plan) {
|
|
|
throw new \Exception('号源不存在');
|
|
|
}
|
|
|
|
|
|
$batch = DB::table('plan_batch')->where('id', $plan->batch_id)->first();
|
|
|
if (!$batch) {
|
|
|
throw new \Exception('批次不存在');
|
|
|
}
|
|
|
|
|
|
// 查询resource获取slot_mode
|
|
|
$resource = DB::table('department_resource')->where('id', $batch->resource_id)->first();
|
|
|
$slotMode = $resource->slot_mode ?? 1;
|
|
|
|
|
|
if (($batch->enable_channel_quota ?? 0) == 0) {
|
|
|
// 未启用渠道配额:直接在 plan 表占位
|
|
|
$lockedCount = 1;
|
|
|
if (isset($data['channel_locks']) && !empty($data['channel_locks'])) {
|
|
|
$lockedCount = $data['channel_locks'][0]['locked_count'] ?? 1;
|
|
|
}
|
|
|
|
|
|
$availableQuota = $plan->total_quota - $plan->used_quota - ($plan->locked_quota ?? 0);
|
|
|
if ($availableQuota < $lockedCount) {
|
|
|
throw new \Exception('可用配额不足,剩余 ' . $availableQuota);
|
|
|
}
|
|
|
|
|
|
DB::table('plan')
|
|
|
->where('id', $planId)
|
|
|
->increment('locked_quota', $lockedCount);
|
|
|
|
|
|
// 同步更新公共池 channel_quota 的 locked_quota,保持前端展示一致
|
|
|
DB::table('plan_batch_channel_quota')
|
|
|
->where('batch_id', $batch->id)
|
|
|
->where('channel_id', -1)
|
|
|
->where('deleted', 0)
|
|
|
->increment('locked_quota', $lockedCount);
|
|
|
|
|
|
DB::table('plan_locked_log')->insert([
|
|
|
'plan_id' => $planId,
|
|
|
'batch_id' => $batch->id,
|
|
|
'channel_id' => null,
|
|
|
'locked_quota' => $lockedCount,
|
|
|
'reason' => $data['reason'],
|
|
|
'operator_id' => $operatorId,
|
|
|
'operator_name' => $operatorName,
|
|
|
'action' => 1,
|
|
|
'slot_mode' => $slotMode,
|
|
|
'created_at' => now()
|
|
|
]);
|
|
|
} else {
|
|
|
// 启用了渠道配额:按渠道占位
|
|
|
if (!isset($data['channel_locks']) || empty($data['channel_locks'])) {
|
|
|
throw new \Exception('请提供渠道占位信息');
|
|
|
}
|
|
|
|
|
|
$channelLocks = $data['channel_locks'];
|
|
|
$totalLockedInChannels = 0;
|
|
|
|
|
|
foreach ($channelLocks as $channelLock) {
|
|
|
$channelId = $channelLock['channel_id'];
|
|
|
$lockedCount = $channelLock['locked_count'];
|
|
|
|
|
|
$channelQuota = DB::table('plan_batch_channel_quota')
|
|
|
->where('batch_id', $batch->id)
|
|
|
->where('channel_id', $channelId)
|
|
|
->where('deleted', 0)
|
|
|
->first();
|
|
|
|
|
|
if (!$channelQuota) {
|
|
|
throw new \Exception("渠道 {$channelId} 配额不存在");
|
|
|
}
|
|
|
|
|
|
$availableQuota = $channelQuota->total_quota - $channelQuota->used_quota - ($channelQuota->locked_quota ?? 0);
|
|
|
if ($availableQuota < $lockedCount) {
|
|
|
throw new \Exception("渠道 {$channelId} 可用配额不足,剩余 {$availableQuota}");
|
|
|
}
|
|
|
|
|
|
DB::table('plan_batch_channel_quota')
|
|
|
->where('id', $channelQuota->id)
|
|
|
->increment('locked_quota', $lockedCount);
|
|
|
|
|
|
$totalLockedInChannels += $lockedCount;
|
|
|
|
|
|
DB::table('plan_locked_log')->insert([
|
|
|
'plan_id' => $planId,
|
|
|
'batch_id' => $batch->id,
|
|
|
'channel_id' => $channelId,
|
|
|
'locked_quota' => $lockedCount,
|
|
|
'reason' => $data['reason'],
|
|
|
'operator_id' => $operatorId,
|
|
|
'operator_name' => $operatorName,
|
|
|
'action' => 1,
|
|
|
'slot_mode' => $slotMode,
|
|
|
'created_at' => now()
|
|
|
]);
|
|
|
}
|
|
|
|
|
|
// 同步更新 plan.locked_quota,保持列表展示一致
|
|
|
if ($totalLockedInChannels > 0) {
|
|
|
DB::table('plan')
|
|
|
->where('id', $planId)
|
|
|
->increment('locked_quota', $totalLockedInChannels);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
$planIds[] = $planId;
|
|
|
}
|
|
|
// 时间点模式:多个plan_ids(不按渠道)
|
|
|
elseif (isset($data['plan_ids']) && !empty($data['plan_ids'])) {
|
|
|
$planIdList = $data['plan_ids'];
|
|
|
|
|
|
foreach ($planIdList as $planId) {
|
|
|
// 查询plan和batch信息
|
|
|
$plan = DB::table('plan')->where('id', $planId)->where('deleted', 0)->first();
|
|
|
if (!$plan) {
|
|
|
throw new \Exception("号源 {$planId} 不存在");
|
|
|
}
|
|
|
|
|
|
$batch = DB::table('plan_batch')->where('id', $plan->batch_id)->first();
|
|
|
if (!$batch) {
|
|
|
throw new \Exception("批次 {$plan->batch_id} 不存在");
|
|
|
}
|
|
|
|
|
|
// 查询resource获取slot_mode
|
|
|
$resource = DB::table('department_resource')->where('id', $batch->resource_id)->first();
|
|
|
$slotMode = $resource->slot_mode ?? 1;
|
|
|
|
|
|
// 检查可用配额
|
|
|
$availableQuota = $plan->total_quota - $plan->used_quota - ($plan->locked_quota ?? 0);
|
|
|
if ($availableQuota < 1) {
|
|
|
throw new \Exception("时间点 {$plan->start_time} 可用配额不足");
|
|
|
}
|
|
|
|
|
|
// 增加占位数量(时间点模式始终为1)
|
|
|
DB::table('plan')
|
|
|
->where('id', $planId)
|
|
|
->increment('locked_quota', 1);
|
|
|
|
|
|
// 同步更新公共池 channel_quota 的 locked_quota,保持列表展示一致
|
|
|
DB::table('plan_batch_channel_quota')
|
|
|
->where('batch_id', $batch->id)
|
|
|
->where('channel_id', -1)
|
|
|
->where('deleted', 0)
|
|
|
->increment('locked_quota', 1);
|
|
|
|
|
|
// 记录日志(时间点模式不记录channel_id)
|
|
|
$timePointInfo = $plan->start_time ? substr($plan->start_time, 0, 5) : '';
|
|
|
$logReason = $timePointInfo ? "{$timePointInfo} {$data['reason']}" : $data['reason'];
|
|
|
DB::table('plan_locked_log')->insert([
|
|
|
'plan_id' => $planId,
|
|
|
'batch_id' => $batch->id,
|
|
|
'channel_id' => null,
|
|
|
'locked_quota' => 1,
|
|
|
'reason' => $logReason,
|
|
|
'operator_id' => $operatorId,
|
|
|
'operator_name' => $operatorName,
|
|
|
'action' => 1,
|
|
|
'slot_mode' => $slotMode,
|
|
|
'created_at' => now()
|
|
|
]);
|
|
|
|
|
|
$planIds[] = $planId;
|
|
|
}
|
|
|
} else {
|
|
|
throw new \Exception('请提供plan_id或plan_ids');
|
|
|
}
|
|
|
|
|
|
DB::commit();
|
|
|
|
|
|
return Rs::success(null, '占位成功');
|
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
DB::rollBack();
|
|
|
return Rs::error('占位失败:' . $e->getMessage());
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 释放占位
|
|
|
*/
|
|
|
public function releaseLock(Request $request)
|
|
|
{
|
|
|
$data = $request->validate([
|
|
|
'slot_mode' => 'required|integer|in:1,2',
|
|
|
'plan_ids' => 'nullable|array',
|
|
|
'plan_ids.*' => 'integer',
|
|
|
'channel_id' => 'nullable|integer',
|
|
|
'release_count' => 'nullable|integer|min:1'
|
|
|
]);
|
|
|
|
|
|
$payload = $request->attributes->get('payload');
|
|
|
if (!$payload) {
|
|
|
return Rs::error('用户信息无效', 401);
|
|
|
}
|
|
|
|
|
|
$user = $this->userService->getUserInfoFromPayload($payload);
|
|
|
if (!$user) {
|
|
|
return Rs::error('用户不存在', 401);
|
|
|
}
|
|
|
|
|
|
$operatorId = $user->user_id ?? null;
|
|
|
$operatorName = $user->name ?? '';
|
|
|
|
|
|
try {
|
|
|
DB::beginTransaction();
|
|
|
|
|
|
$slotMode = $data['slot_mode'];
|
|
|
|
|
|
// 时间点模式:slot_mode=2,使用plan_ids释放
|
|
|
if ($slotMode == 2) {
|
|
|
if (!isset($data['plan_ids']) || empty($data['plan_ids'])) {
|
|
|
throw new \Exception('时间点模式请提供plan_ids');
|
|
|
}
|
|
|
|
|
|
$planIdList = $data['plan_ids'];
|
|
|
|
|
|
foreach ($planIdList as $planId) {
|
|
|
// 查询plan信息
|
|
|
$plan = DB::table('plan')->where('id', $planId)->where('deleted', 0)->first();
|
|
|
if (!$plan) {
|
|
|
throw new \Exception("号源 {$planId} 不存在");
|
|
|
}
|
|
|
|
|
|
$lockedQuota = $plan->locked_quota ?? 0;
|
|
|
if ($lockedQuota <= 0) {
|
|
|
continue; // 该时间点无占位,跳过
|
|
|
}
|
|
|
|
|
|
// 获取batch和resource信息
|
|
|
$batch = DB::table('plan_batch')->where('id', $plan->batch_id)->first();
|
|
|
if (!$batch) {
|
|
|
throw new \Exception("批次 {$plan->batch_id} 不存在");
|
|
|
}
|
|
|
|
|
|
$resource = DB::table('department_resource')->where('id', $batch->resource_id)->first();
|
|
|
|
|
|
// 减少占位数量(时间点模式始终全部释放)
|
|
|
DB::table('plan')
|
|
|
->where('id', $planId)
|
|
|
->update(['locked_quota' => 0]);
|
|
|
|
|
|
// 同步释放公共池 channel_quota 的 locked_quota,保持配额展示一致
|
|
|
DB::table('plan_batch_channel_quota')
|
|
|
->where('batch_id', $batch->id)
|
|
|
->where('channel_id', -1)
|
|
|
->where('deleted', 0)
|
|
|
->decrement('locked_quota', $lockedQuota);
|
|
|
|
|
|
// 记录日志(时间点模式不记录channel_id)
|
|
|
$timePointInfo = $plan->start_time ? substr($plan->start_time, 0, 5) : '';
|
|
|
$logReason = $timePointInfo ? "{$timePointInfo} 释放占位" : '释放占位';
|
|
|
DB::table('plan_locked_log')->insert([
|
|
|
'plan_id' => $planId,
|
|
|
'batch_id' => $batch->id,
|
|
|
'channel_id' => null,
|
|
|
'locked_quota' => $lockedQuota,
|
|
|
'reason' => $logReason,
|
|
|
'operator_id' => $operatorId,
|
|
|
'operator_name' => $operatorName,
|
|
|
'action' => 2,
|
|
|
'slot_mode' => $slotMode,
|
|
|
'created_at' => now()
|
|
|
]);
|
|
|
}
|
|
|
}
|
|
|
// 时间段模式:slot_mode=1
|
|
|
elseif ($slotMode == 1) {
|
|
|
if (!isset($data['plan_ids']) || empty($data['plan_ids'])) {
|
|
|
throw new \Exception('时间段模式请提供plan_ids');
|
|
|
}
|
|
|
|
|
|
$planId = $data['plan_ids'][0];
|
|
|
$plan = DB::table('plan')->where('id', $planId)->where('deleted', 0)->first();
|
|
|
if (!$plan) {
|
|
|
throw new \Exception('号源不存在');
|
|
|
}
|
|
|
|
|
|
$batch = DB::table('plan_batch')->where('id', $plan->batch_id)->first();
|
|
|
if (!$batch) {
|
|
|
throw new \Exception('批次不存在');
|
|
|
}
|
|
|
|
|
|
if (($batch->enable_channel_quota ?? 0) == 0) {
|
|
|
// 未启用渠道配额:直接在 plan 表释放
|
|
|
$releaseCount = $data['release_count'] ?? 0;
|
|
|
$lockedQuota = $plan->locked_quota ?? 0;
|
|
|
|
|
|
if (!$releaseCount) {
|
|
|
$releaseCount = $lockedQuota;
|
|
|
} elseif ($releaseCount > $lockedQuota) {
|
|
|
throw new \Exception('释放数量不能超过当前占位数量');
|
|
|
}
|
|
|
|
|
|
if ($releaseCount <= 0) {
|
|
|
throw new \Exception('当前无占位可释放');
|
|
|
}
|
|
|
|
|
|
DB::table('plan')
|
|
|
->where('id', $planId)
|
|
|
->decrement('locked_quota', $releaseCount);
|
|
|
|
|
|
// 同步释放公共池 channel_quota 的 locked_quota
|
|
|
DB::table('plan_batch_channel_quota')
|
|
|
->where('batch_id', $batch->id)
|
|
|
->where('channel_id', -1)
|
|
|
->where('deleted', 0)
|
|
|
->decrement('locked_quota', $releaseCount);
|
|
|
|
|
|
DB::table('plan_locked_log')->insert([
|
|
|
'plan_id' => $planId,
|
|
|
'batch_id' => $batch->id,
|
|
|
'channel_id' => null,
|
|
|
'locked_quota' => $releaseCount,
|
|
|
'reason' => '释放占位',
|
|
|
'operator_id' => $operatorId,
|
|
|
'operator_name' => $operatorName,
|
|
|
'action' => 2,
|
|
|
'slot_mode' => $slotMode,
|
|
|
'created_at' => now()
|
|
|
]);
|
|
|
} else {
|
|
|
// 启用了渠道配额:按渠道释放
|
|
|
if (!isset($data['channel_id'])) {
|
|
|
throw new \Exception('时间段模式请提供channel_id');
|
|
|
}
|
|
|
|
|
|
$channelId = $data['channel_id'];
|
|
|
$releaseCount = $data['release_count'] ?? 0;
|
|
|
|
|
|
$channelQuota = DB::table('plan_batch_channel_quota')
|
|
|
->where('batch_id', $batch->id)
|
|
|
->where('channel_id', $channelId)
|
|
|
->where('deleted', 0)
|
|
|
->first();
|
|
|
|
|
|
if (!$channelQuota) {
|
|
|
throw new \Exception("渠道 {$channelId} 配额不存在");
|
|
|
}
|
|
|
|
|
|
if (!$releaseCount) {
|
|
|
$releaseCount = $channelQuota->locked_quota ?? 0;
|
|
|
} else {
|
|
|
if ($releaseCount > ($channelQuota->locked_quota ?? 0)) {
|
|
|
throw new \Exception('释放数量不能超过当前占位数量');
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if ($releaseCount <= 0) {
|
|
|
throw new \Exception('该渠道当前无占位可释放');
|
|
|
}
|
|
|
|
|
|
DB::table('plan_batch_channel_quota')
|
|
|
->where('id', $channelQuota->id)
|
|
|
->decrement('locked_quota', $releaseCount);
|
|
|
|
|
|
// 同步释放 plan.locked_quota,保持列表展示一致
|
|
|
DB::table('plan')
|
|
|
->where('id', $planId)
|
|
|
->decrement('locked_quota', $releaseCount);
|
|
|
|
|
|
DB::table('plan_locked_log')->insert([
|
|
|
'plan_id' => $planId,
|
|
|
'batch_id' => $batch->id,
|
|
|
'channel_id' => $channelId,
|
|
|
'locked_quota' => $releaseCount,
|
|
|
'reason' => '释放占位',
|
|
|
'operator_id' => $operatorId,
|
|
|
'operator_name' => $operatorName,
|
|
|
'action' => 2,
|
|
|
'slot_mode' => $slotMode,
|
|
|
'created_at' => now()
|
|
|
]);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
DB::commit();
|
|
|
|
|
|
return Rs::success(null, '释放成功');
|
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
DB::rollBack();
|
|
|
return Rs::error('释放失败:' . $e->getMessage());
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 查询占位记录
|
|
|
*/
|
|
|
public function getLockHistory(Request $request)
|
|
|
{
|
|
|
$data = $request->validate([
|
|
|
'plan_id' => 'nullable|integer',
|
|
|
'batch_id' => 'nullable|integer'
|
|
|
]);
|
|
|
|
|
|
try {
|
|
|
$query = DB::table('plan_locked_log as log')
|
|
|
->orderBy('log.created_at', 'desc');
|
|
|
|
|
|
if (isset($data['plan_id'])) {
|
|
|
$query->where('log.plan_id', $data['plan_id']);
|
|
|
}
|
|
|
|
|
|
if (isset($data['batch_id'])) {
|
|
|
$query->where('log.batch_id', $data['batch_id']);
|
|
|
}
|
|
|
|
|
|
$logs = $query->limit(100)->get();
|
|
|
|
|
|
return Rs::success(['list' => $logs]);
|
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
return Rs::error('查询失败:' . $e->getMessage());
|
|
|
}
|
|
|
}
|
|
|
}
|