|
|
<?php
|
|
|
|
|
|
namespace App\Services\Admin\YeWu;
|
|
|
|
|
|
use DateTime;
|
|
|
use Illuminate\Support\Facades\DB;
|
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
|
|
use Exception;
|
|
|
|
|
|
class RosterService
|
|
|
{
|
|
|
public function generatePlans($dateRange, $planModelIds, $userId, $dateType = 1, $holidayEnable = 1)
|
|
|
{
|
|
|
// 1. 基础参数校验
|
|
|
if (!is_array($dateRange) || count($dateRange) != 2 || empty($planModelIds)) {
|
|
|
throw new Exception('日期范围或模板ID参数错误');
|
|
|
}
|
|
|
|
|
|
// 2. 获取模板信息并校验状态
|
|
|
$models = DB::table('s_source_roster')
|
|
|
->whereIn('id', $planModelIds)
|
|
|
->get();
|
|
|
|
|
|
if ($models->isEmpty()) {
|
|
|
throw new Exception('未找到有效的排班模板');
|
|
|
}
|
|
|
|
|
|
foreach ($models as $model) {
|
|
|
if ($model->status != 1 || $model->is_del != 0) {
|
|
|
throw new Exception("模板状态异常,请重新选择!异常模板Id: {$model->id}");
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 获取部门ID (用于查重,取第一个模板的部门ID,假设批量操作通常针对同一部门)
|
|
|
// 如果业务允许跨部门混合勾选,这里可能需要调整查重逻辑(按部门分组查)
|
|
|
$department_id = $models->first()->department_id;
|
|
|
|
|
|
$start_date = new DateTime($dateRange[0]);
|
|
|
$end_date = new DateTime($dateRange[1]);
|
|
|
|
|
|
// ==========================================
|
|
|
// 【核心】3. 重复性检测 (在事务外执行,提高性能)
|
|
|
// ==========================================
|
|
|
$this->checkDuplicateRecords(
|
|
|
$department_id,
|
|
|
$planModelIds,
|
|
|
$start_date,
|
|
|
$end_date,
|
|
|
$models
|
|
|
);
|
|
|
// 如果上面没抛异常,说明没有重复,继续往下执行
|
|
|
|
|
|
// 4. 获取节假日列表
|
|
|
$holiday_list = DB::table('s_holiday')
|
|
|
->whereBetween('date', $dateRange)
|
|
|
->where(['type' => 2])
|
|
|
->pluck('date')
|
|
|
->toArray();
|
|
|
|
|
|
$success_count = 0;
|
|
|
|
|
|
// 5. 开启事务
|
|
|
DB::beginTransaction();
|
|
|
try {
|
|
|
$current_date = clone $start_date;
|
|
|
|
|
|
while ($current_date <= $end_date) {
|
|
|
$current_date_str = $current_date->format('Y-m-d');
|
|
|
|
|
|
// --- 逻辑判断:节假日过滤 ---
|
|
|
|
|
|
// 如果是“仅节假日”模式 (date_type == 2),且当天不是节假日 -> 跳过
|
|
|
if ($dateType == 2 && !in_array($current_date_str, $holiday_list)) {
|
|
|
$current_date->modify('+1 day');
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
// 如果“节假日不可用” (HolidayEnable == 0),且当天是节假日 -> 跳过
|
|
|
if ($holidayEnable == 0 && in_array($current_date_str, $holiday_list)) {
|
|
|
$current_date->modify('+1 day');
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
// --- 获取星期 ---
|
|
|
$weekday = (int)$current_date->format('w');
|
|
|
$weekname = $this->getWeekName($weekday);
|
|
|
|
|
|
foreach ($models as $model) {
|
|
|
// --- 逻辑判断:星期匹配 ---
|
|
|
// 如果是“按星期”模式 (date_type == 1) 且模板也是按星期定义的,必须星期一致
|
|
|
if ($dateType == 1 && isset($model->date_type) && $model->date_type == 1) {
|
|
|
if ($model->weekname !== $weekname) {
|
|
|
continue;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// --- 构造插入数据 ---
|
|
|
$data = [
|
|
|
'roster_id' => $model->id,
|
|
|
'date' => $current_date_str,
|
|
|
'weekname' => $weekname,
|
|
|
'department_id' => $model->department_id,
|
|
|
'resources_id' => $model->resources_id ?? null,
|
|
|
'device_id' => $model->device_id ?? null,
|
|
|
'period_id' => $model->period_id ?? null,
|
|
|
'patient_type' => $model->patient_type ?? null,
|
|
|
'begin_time' => $model->begin_time,
|
|
|
'end_time' => $model->end_time,
|
|
|
'end_reservation_time' => $model->end_reservation_time ?? null,
|
|
|
'time_unit' => $model->time_unit ?? null,
|
|
|
'status' => 1,
|
|
|
'adduser' => $userId,
|
|
|
'is_del' => 0,
|
|
|
'created_at' => date('Y-m-d H:i:s'),
|
|
|
'updated_at' => date('Y-m-d H:i:s'),
|
|
|
];
|
|
|
|
|
|
// --- 插入主表 ---
|
|
|
$plan_id = DB::table('s_source_roster_detail')->insertGetId($data);
|
|
|
|
|
|
if (!$plan_id) {
|
|
|
throw new Exception("号源明细插入失败,日期:{$current_date_str}, 模板ID:{$model->id}");
|
|
|
}
|
|
|
$success_count++;
|
|
|
|
|
|
// --- 插入关联表:数量配置 ---
|
|
|
$this->insertCountInfo($plan_id, $model->id);
|
|
|
|
|
|
// --- 插入关联表:设备配置 ---
|
|
|
// 注意:原逻辑如果 device_id 为空会报错,这里保持原逻辑
|
|
|
$this->insertDeviceInfo($plan_id, $model->device_id, $model->id);
|
|
|
}
|
|
|
|
|
|
$current_date->modify('+1 day');
|
|
|
}
|
|
|
|
|
|
// 6. 提交事务
|
|
|
DB::commit();
|
|
|
|
|
|
return ['success' => true, 'count' => $success_count];
|
|
|
|
|
|
} catch (Exception $e) {
|
|
|
// 7. 异常回滚
|
|
|
DB::rollBack();
|
|
|
// 记录日志
|
|
|
Log::error('Roster Generation Failed: ' . $e->getMessage(), [
|
|
|
'dateRange' => $dateRange,
|
|
|
'user_id' => $userId
|
|
|
]);
|
|
|
// 重新抛出,让 Controller 处理
|
|
|
throw $e;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 独立的重复检测方法
|
|
|
* 如果发现重复,直接抛出包含详细信息的异常
|
|
|
*/
|
|
|
private function checkDuplicateRecords($department_id, $planModelIds, $startDate, $endDate, $models)
|
|
|
{
|
|
|
$startStr = $startDate->format('Y-m-d');
|
|
|
$endStr = $endDate->format('Y-m-d');
|
|
|
|
|
|
// 查询已存在的记录
|
|
|
$checkList = DB::table('s_source_roster_detail')
|
|
|
->where('department_id', $department_id)
|
|
|
->whereIn('roster_id', $planModelIds)
|
|
|
->where('date', '>=', $startStr)
|
|
|
->where('date', '<=', $endStr)
|
|
|
->where('is_del', 0)
|
|
|
->get();
|
|
|
|
|
|
if ($checkList->isNotEmpty()) {
|
|
|
// 构造详细的错误提示信息 (完全还原你原代码的逻辑)
|
|
|
$msg = '已有重复的计划明细,禁止创建!当前选中的';
|
|
|
$msglist = '';
|
|
|
$msgIds = '';
|
|
|
|
|
|
// 优化:将检查结果转为映射数组,避免双重循环 O(N*M)
|
|
|
// key: roster_id, value: array of items
|
|
|
$checkMap = [];
|
|
|
foreach ($checkList as $item) {
|
|
|
if (!isset($checkMap[$item->roster_id])) {
|
|
|
$checkMap[$item->roster_id] = [];
|
|
|
}
|
|
|
$checkMap[$item->roster_id][] = $item;
|
|
|
}
|
|
|
|
|
|
foreach ($models as $model) {
|
|
|
if (isset($checkMap[$model->id])) {
|
|
|
foreach ($checkMap[$model->id] as $item) {
|
|
|
$msglist .= $item->date . ' ';
|
|
|
$msgIds .= $item->id . ' ';
|
|
|
// 拼接模板信息
|
|
|
$msg .= " " . $model->weekname . $model->begin_time . '-' . $model->end_time . " ";
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
$fullErrorMessage = $msg . '已存在相同记录,</br>存在于:</br>' . $msglist . '</br>对应记录Id为:' . $msgIds . '</br>请先删除后再操作';
|
|
|
|
|
|
// 抛出异常,中断流程
|
|
|
throw new Exception($fullErrorMessage);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private function getWeekName($weekday) {
|
|
|
$map = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'];
|
|
|
return $map[$weekday] ?? '';
|
|
|
}
|
|
|
|
|
|
private function insertCountInfo($detailId, $rosterId) {
|
|
|
$model_count_info = DB::table('s_source_roster_count')->where(['roster_id' => $rosterId])->get();
|
|
|
|
|
|
if ($model_count_info->isEmpty()) {
|
|
|
throw new Exception("模板数量信息异常,请重新选择!异常模板Id: {$rosterId}");
|
|
|
}
|
|
|
|
|
|
foreach ($model_count_info as $info) {
|
|
|
$success = DB::table('s_source_roster_detail_count')->insert([
|
|
|
'roster_detail_id' => $detailId,
|
|
|
'appointment_type_id' => $info->appointment_type_id,
|
|
|
'count' => $info->count,
|
|
|
'max_total' => $info->max_total,
|
|
|
'created_at' => date('Y-m-d H:i:s'),
|
|
|
'updated_at' => date('Y-m-d H:i:s'),
|
|
|
]);
|
|
|
if (!$success) {
|
|
|
throw new Exception("渠道数量创建失败");
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private function insertDeviceInfo($detailId, $deviceIdStr, $modelId) {
|
|
|
// 保持原逻辑:如果模板没配设备,视为异常
|
|
|
if (empty($deviceIdStr)) {
|
|
|
throw new Exception("模板未关联设备,请重新选择!异常模板Id: {$modelId}");
|
|
|
}
|
|
|
|
|
|
$device_ids = explode(",", $deviceIdStr);
|
|
|
foreach ($device_ids as $dv_value) {
|
|
|
$dv_value = trim($dv_value);
|
|
|
if ($dv_value === '') continue;
|
|
|
|
|
|
$success = DB::table('s_source_roster_detail_device')->insert([
|
|
|
'roster_detail_id' => $detailId,
|
|
|
'device_id' => $dv_value,
|
|
|
'created_at' => date('Y-m-d H:i:s'),
|
|
|
'updated_at' => date('Y-m-d H:i:s'),
|
|
|
]);
|
|
|
|
|
|
if (!$success) {
|
|
|
throw new Exception("设备关联创建失败");
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|