自动生成排班

main
鹿和sa0ChunLuyu 1 week ago
parent fe95bcbed5
commit e035ea2cb3

@ -433,6 +433,8 @@ public function BatchGenerateByDateRange(Request $request, RosterService $roster
$allResults = []; $allResults = [];
$allTotal = 0; $allTotal = 0;
$allGeneratedDates = [];
$allSkippedDates = [];
foreach ($depts as $dept) { foreach ($depts as $dept) {
$today = date('Y-m-d'); $today = date('Y-m-d');
@ -455,6 +457,8 @@ public function BatchGenerateByDateRange(Request $request, RosterService $roster
$allResults = array_merge($allResults, $res['details']); $allResults = array_merge($allResults, $res['details']);
$allTotal += $res['total_count']; $allTotal += $res['total_count'];
$allGeneratedDates = array_merge($allGeneratedDates, $res['generated_dates']);
$allSkippedDates = array_merge($allSkippedDates, $res['skipped_dates']);
} }
$successCount = collect($allResults)->where('status', 'success')->count(); $successCount = collect($allResults)->where('status', 'success')->count();
@ -462,11 +466,15 @@ public function BatchGenerateByDateRange(Request $request, RosterService $roster
return \Yz::Return( return \Yz::Return(
true, true,
"自动生成完成,共 {$allTotal} 条 (成功: {$successCount} 资源, 失败: {$failedCount} 资源)", "自动生成完成,生成 " . count($allGeneratedDates) . " 条,跳过 " . count($allSkippedDates) . " 条,共 {$allTotal} 条 (成功: {$successCount} 资源, 失败: {$failedCount} 资源)",
[ [
'total_count' => $allTotal, 'total_count' => $allTotal,
'success_count' => $successCount, 'success_count' => $successCount,
'failed_count' => $failedCount, 'failed_count' => $failedCount,
'generated_count' => count($allGeneratedDates),
'skipped_count' => count($allSkippedDates),
'generated_dates' => $allGeneratedDates,
'skipped_dates' => $allSkippedDates,
'details' => $allResults 'details' => $allResults
] ]
); );
@ -483,7 +491,7 @@ public function BatchGenerateByDateRange(Request $request, RosterService $roster
return \Yz::Return( return \Yz::Return(
true, true,
"批量生成完成,共 {$res['total_count']} 条 (成功: {$res['success_count']} 资源, 失败: {$res['failed_count']} 资源)", "批量生成完成,生成 {$res['generated_count']} 条,跳过 {$res['skipped_count']} 条,共 {$res['total_count']} 条 (成功: {$res['success_count']} 资源, 失败: {$res['failed_count']} 资源)",
$res $res
); );
} }
@ -558,6 +566,8 @@ private function generateBatchForResources($resources, $dateRange, $HolidayEnabl
foreach ($groupedByResource as $resourcesId => $models) { foreach ($groupedByResource as $resourcesId => $models) {
$groupedByDateType = $models->groupBy('date_type'); $groupedByDateType = $models->groupBy('date_type');
$resourceCount = 0; $resourceCount = 0;
$resourceGeneratedDates = [];
$resourceSkippedDates = [];
try { try {
foreach ($groupedByDateType as $dateType => $typeModels) { foreach ($groupedByDateType as $dateType => $typeModels) {
@ -571,13 +581,17 @@ private function generateBatchForResources($resources, $dateRange, $HolidayEnabl
$HolidayEnable $HolidayEnable
); );
$resourceCount += $result['count']; $resourceCount += $result['count'];
$resourceGeneratedDates = array_merge($resourceGeneratedDates, $result['generated_dates']);
$resourceSkippedDates = array_merge($resourceSkippedDates, $result['skipped_dates']);
} }
$results[] = [ $results[] = [
'resources_id' => $resourcesId, 'resources_id' => $resourcesId,
'resources_name' => $models->first()->department_resources_name ?? '', 'resources_name' => $models->first()->department_resources_name ?? '',
'status' => 'success', 'status' => 'success',
'count' => $resourceCount 'count' => $resourceCount,
'generated_dates' => $resourceGeneratedDates,
'skipped_dates' => $resourceSkippedDates,
]; ];
$totalCount += $resourceCount; $totalCount += $resourceCount;
@ -587,7 +601,9 @@ private function generateBatchForResources($resources, $dateRange, $HolidayEnabl
'resources_name' => $models->first()->department_resources_name ?? '', 'resources_name' => $models->first()->department_resources_name ?? '',
'status' => 'failed', 'status' => 'failed',
'error' => $e->getMessage(), 'error' => $e->getMessage(),
'count' => 0 'count' => 0,
'generated_dates' => [],
'skipped_dates' => [],
]; ];
} }
} }
@ -596,10 +612,25 @@ private function generateBatchForResources($resources, $dateRange, $HolidayEnabl
$successCount = collect($results)->where('status', 'success')->count(); $successCount = collect($results)->where('status', 'success')->count();
$failedCount = collect($results)->where('status', 'failed')->count(); $failedCount = collect($results)->where('status', 'failed')->count();
$allGeneratedDates = [];
$allSkippedDates = [];
foreach ($results as $r) {
if (isset($r['generated_dates'])) {
$allGeneratedDates = array_merge($allGeneratedDates, $r['generated_dates']);
}
if (isset($r['skipped_dates'])) {
$allSkippedDates = array_merge($allSkippedDates, $r['skipped_dates']);
}
}
return [ return [
'total_count' => $totalCount, 'total_count' => $totalCount,
'success_count' => $successCount, 'success_count' => $successCount,
'failed_count' => $failedCount, 'failed_count' => $failedCount,
'generated_count' => count($allGeneratedDates),
'skipped_count' => count($allSkippedDates),
'generated_dates' => $allGeneratedDates,
'skipped_dates' => $allSkippedDates,
'details' => $results 'details' => $results
]; ];
} }

@ -35,45 +35,31 @@ public function generatePlans($dateRange, $planModelIds, $userId, $dateType = 1,
} }
} }
// 获取部门ID (用于查重取第一个模板的部门ID假设批量操作通常针对同一部门)
// 如果业务允许跨部门混合勾选,这里可能需要调整查重逻辑(按部门分组查)
$department_id = $models->first()->department_id;
$start_date = new DateTime($dateRange[0]); $start_date = new DateTime($dateRange[0]);
$end_date = new DateTime($dateRange[1]); $end_date = new DateTime($dateRange[1]);
// ========================================== // ==========================================
// 【新增】1. 整体校验:模板与资源时令配置匹配 // 1. 整体校验:模板与资源时令配置匹配
// ========================================== // ==========================================
$this->validateTemplates($models); $this->validateTemplates($models);
// ========================================== // ==========================================
// 【新增】2. 整体校验:资源时段冲突检测 // 2. 整体校验:资源时段冲突检测
// ========================================== // ==========================================
$this->checkResourceTimeConflict($models, $start_date, $end_date); $this->checkResourceTimeConflict($models, $start_date, $end_date);
// ========================================== // 3. 获取节假日列表
// 【核心】3. 重复性检测 (在事务外执行,提高性能)
// ==========================================
$this->checkDuplicateRecords(
$department_id,
$planModelIds,
$start_date,
$end_date,
$models
);
// 如果上面没抛异常,说明没有重复,继续往下执行
// 4. 获取节假日列表
$holiday_list = DB::table('s_holiday') $holiday_list = DB::table('s_holiday')
->whereBetween('date', $dateRange) ->whereBetween('date', $dateRange)
->where(['type' => 2]) ->where(['type' => 2])
->pluck('date') ->pluck('date')
->toArray(); ->toArray();
$generatedDates = [];
$skippedDates = [];
$success_count = 0; $success_count = 0;
// 5. 开启事务 // 4. 开启事务
DB::beginTransaction(); DB::beginTransaction();
try { try {
$current_date = clone $start_date; $current_date = clone $start_date;
@ -85,12 +71,26 @@ public function generatePlans($dateRange, $planModelIds, $userId, $dateType = 1,
// 如果是"仅节假日"模式 (date_type == 2),且当天不是节假日 -> 跳过 // 如果是"仅节假日"模式 (date_type == 2),且当天不是节假日 -> 跳过
if ($dateType == 2 && !in_array($current_date_str, $holiday_list)) { if ($dateType == 2 && !in_array($current_date_str, $holiday_list)) {
foreach ($models as $model) {
$skippedDates[] = [
'date' => $current_date_str,
'template_id' => $model->id,
'reason' => '非节假日,跳过',
];
}
$current_date->modify('+1 day'); $current_date->modify('+1 day');
continue; continue;
} }
// 如果"节假日不可用" (HolidayEnable == 0),且当天是节假日 -> 跳过 // 如果"节假日不可用" (HolidayEnable == 0),且当天是节假日 -> 跳过
if ($holidayEnable == 0 && in_array($current_date_str, $holiday_list)) { if ($holidayEnable == 0 && in_array($current_date_str, $holiday_list)) {
foreach ($models as $model) {
$skippedDates[] = [
'date' => $current_date_str,
'template_id' => $model->id,
'reason' => '节假日不可用,跳过',
];
}
$current_date->modify('+1 day'); $current_date->modify('+1 day');
continue; continue;
} }
@ -108,10 +108,31 @@ public function generatePlans($dateRange, $planModelIds, $userId, $dateType = 1,
} }
} }
// 【新增】时令校验:跳过不在时令范围内的日期 // 时令校验:跳过不在时令范围内的日期
$resource = $this->getCachedResource($model->resources_id); $resource = $this->getCachedResource($model->resources_id);
if (!$this->isDateInSeasonalRange($current_date, $model->type, $resource)) { if (!$this->isDateInSeasonalRange($current_date, $model->type, $resource)) {
continue; // 跳过该模板在此日期的生成 $skippedDates[] = [
'date' => $current_date_str,
'template_id' => $model->id,
'reason' => '不在时令范围',
];
continue;
}
// 检查当天该模板是否已有号源明细(存在即跳过)
$exists = DB::table('s_source_roster_detail')
->where('roster_id', $model->id)
->where('date', $current_date_str)
->where('is_del', 0)
->exists();
if ($exists) {
$skippedDates[] = [
'date' => $current_date_str,
'template_id' => $model->id,
'reason' => '已存在',
];
continue;
} }
// --- 构造插入数据 --- // --- 构造插入数据 ---
@ -141,26 +162,36 @@ public function generatePlans($dateRange, $planModelIds, $userId, $dateType = 1,
if (!$plan_id) { if (!$plan_id) {
throw new Exception("号源明细插入失败,日期:{$current_date_str}, 模板ID{$model->id}"); throw new Exception("号源明细插入失败,日期:{$current_date_str}, 模板ID{$model->id}");
} }
$generatedDates[] = [
'date' => $current_date_str,
'template_id' => $model->id,
'detail_id' => $plan_id,
];
$success_count++; $success_count++;
// --- 插入关联表:数量配置 --- // --- 插入关联表:数量配置 ---
$this->insertCountInfo($plan_id, $model->id); $this->insertCountInfo($plan_id, $model->id);
// --- 插入关联表:设备配置 --- // --- 插入关联表:设备配置 ---
// 注意:原逻辑如果 device_id 为空会报错,这里保持原逻辑
$this->insertDeviceInfo($plan_id, $model->device_id, $model->id); $this->insertDeviceInfo($plan_id, $model->device_id, $model->id);
} }
$current_date->modify('+1 day'); $current_date->modify('+1 day');
} }
// 6. 提交事务 // 5. 提交事务
DB::commit(); DB::commit();
return ['success' => true, 'count' => $success_count]; return [
'success' => true,
'count' => $success_count,
'generated_dates' => $generatedDates,
'skipped_dates' => $skippedDates,
];
} catch (Exception $e) { } catch (Exception $e) {
// 7. 异常回滚 // 6. 异常回滚
DB::rollBack(); DB::rollBack();
// 记录日志 // 记录日志
Log::error('Roster Generation Failed: ' . $e->getMessage(), [ Log::error('Roster Generation Failed: ' . $e->getMessage(), [
@ -326,58 +357,6 @@ private function getTemplateDate($model)
return null; return null;
} }
/**
* 独立的重复检测方法
* 如果发现重复,直接抛出包含详细信息的异常
*/
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) { private function getWeekName($weekday) {
$map = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']; $map = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'];
return $map[$weekday] ?? ''; return $map[$weekday] ?? '';

Loading…
Cancel
Save