|
@@ -1117,7 +1117,7 @@ class PersonWorkService extends Service
|
|
|
* @param array $user 用户信息
|
|
* @param array $user 用户信息
|
|
|
* @return array
|
|
* @return array
|
|
|
*/
|
|
*/
|
|
|
- private function calculateDailyAllocation($monthStart, $topDepartId, $user)
|
|
|
|
|
|
|
+ private function calculateDailyAllocation1($monthStart, $topDepartId, $user)
|
|
|
{
|
|
{
|
|
|
$monthEnd = strtotime('+1 month', $monthStart) - 1;
|
|
$monthEnd = strtotime('+1 month', $monthStart) - 1;
|
|
|
$now = time();
|
|
$now = time();
|
|
@@ -1277,6 +1277,148 @@ class PersonWorkService extends Service
|
|
|
return ['status' => true, 'data' => $previewList];
|
|
return ['status' => true, 'data' => $previewList];
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ private function calculateDailyAllocation($monthStart, $topDepartId, $user)
|
|
|
|
|
+ {
|
|
|
|
|
+ $monthEnd = strtotime('+1 month', $monthStart) - 1;
|
|
|
|
|
+ $step = 5; // 5分钟硬对齐
|
|
|
|
|
+
|
|
|
|
|
+ // --- 1. 基础数据加载 (保持原版结构) ---
|
|
|
|
|
+ $monthlyOrder = DB::table('monthly_pw_order_details as d')
|
|
|
|
|
+ ->join('monthly_pw_order as m', 'm.id', '=', 'd.main_id')
|
|
|
|
|
+ ->leftJoin('employee as e', 'e.id', '=', 'd.employee_id')
|
|
|
|
|
+ ->where('m.month', $monthStart)->where('m.top_depart_id', $topDepartId)
|
|
|
|
|
+ ->where('m.del_time', 0)->where('d.del_time', 0)
|
|
|
|
|
+ ->select('d.*', 'e.title as employee_title')->get();
|
|
|
|
|
+
|
|
|
|
|
+ if ($monthlyOrder->isEmpty()) return ['status' => false, 'msg' => '未找到明细'];
|
|
|
|
|
+
|
|
|
|
|
+ $empNameMap = $monthlyOrder->pluck('employee_title', 'employee_id')->toArray();
|
|
|
|
|
+ $empIds = array_keys($empNameMap);
|
|
|
|
|
+ $itemIds = DB::table('rule_set_details as rd')->join('rule_set as r', 'r.id', '=', 'rd.main_id')
|
|
|
|
|
+ ->where('r.month', $monthStart)->where('r.top_depart_id', $topDepartId)->pluck('rd.item_id')->unique()->toArray();
|
|
|
|
|
+ $itemMap = DB::table('item')->whereIn('id', $itemIds)->pluck('title', 'id')->toArray();
|
|
|
|
|
+ $ruleSet = DB::table('rule_set_details as rd')->join('rule_set as r', 'r.id', '=', 'rd.main_id')
|
|
|
|
|
+ ->where('r.month', $monthStart)->where('rd.type', 1)->where('r.del_time', 0)->where('rd.del_time', 0)
|
|
|
|
|
+ ->select('rd.*')->get()->groupBy('data_id');
|
|
|
|
|
+
|
|
|
|
|
+ $empWorkRanges = DB::table('employee_work_range')->whereIn('employee_id', $empIds)->where('top_depart_id', $topDepartId)->get()->groupBy('employee_id');
|
|
|
|
|
+ $standardWorkRanges = DB::table('work_range_details')->where('top_depart_id', $topDepartId)->where('del_time', 0)->get();
|
|
|
|
|
+ $allDays = DB::table('calendar_details')->where('top_depart_id', $topDepartId)->where('month', $monthStart)->where('del_time', 0)->orderBy('time', 'asc')->get();
|
|
|
|
|
+ $leaveOverData = DB::table('p_leave_over_order_details as d')->join('p_leave_over_order as m', 'd.main_id', '=', 'm.id')
|
|
|
|
|
+ ->whereBetween('m.order_time', [$monthStart, $monthEnd])->where('d.top_depart_id', $topDepartId)->where('m.del_time', 0)
|
|
|
|
|
+ ->select('d.*', 'm.order_time', 'm.type as main_type')->get()->groupBy(['employee_id', 'order_time']);
|
|
|
|
|
+
|
|
|
|
|
+ // --- 2. 阶段一:计算预分配 (加入动态余数抵消) ---
|
|
|
|
|
+ $finalAlloc = [];
|
|
|
|
|
+ $empBalance = []; // 记录每个人的“工时余额” [emp_id => minutes]
|
|
|
|
|
+
|
|
|
|
|
+ foreach ($monthlyOrder as $mDetail) {
|
|
|
|
|
+ $empId = $mDetail->employee_id;
|
|
|
|
|
+ $empRules = $ruleSet->get($empId);
|
|
|
|
|
+ if (!$empRules) continue;
|
|
|
|
|
+
|
|
|
|
|
+ $empRemainingMin = (int)round((float)$mDetail->rd_total_hours * 60);
|
|
|
|
|
+
|
|
|
|
|
+ foreach ($allDays as $dayInfo) {
|
|
|
|
|
+ if ($empRemainingMin <= 0) break;
|
|
|
|
|
+ $dayTs = $dayInfo->time;
|
|
|
|
|
+ $tempPool = $this->buildAvailablePool($empId, $dayTs, $allDays, $empWorkRanges, $standardWorkRanges, $leaveOverData);
|
|
|
|
|
+ $dayCapacity = 0;
|
|
|
|
|
+ foreach ($tempPool as $p) { $dayCapacity += (int)($p['e'] - $p['s']); }
|
|
|
|
|
+ if ($dayCapacity <= 0) continue;
|
|
|
|
|
+
|
|
|
|
|
+ // 核心变动:计算今天理想分配量,并尝试 5 分钟取整
|
|
|
|
|
+ $theoryAlloc = min($empRemainingMin, $dayCapacity);
|
|
|
|
|
+
|
|
|
|
|
+ // 如果还没到最后一天,强行把当天的分配量凑成 5 的倍数
|
|
|
|
|
+ if ($theoryAlloc < $empRemainingMin) {
|
|
|
|
|
+ // 结合之前的余额进行取整
|
|
|
|
|
+ $currentWithBalance = $theoryAlloc + ($empBalance[$empId] ?? 0);
|
|
|
|
|
+ $roundedAlloc = (int)round($currentWithBalance / $step) * $step;
|
|
|
|
|
+
|
|
|
|
|
+ // 不能超过当天池子上限
|
|
|
|
|
+ $canAllocToday = min($roundedAlloc, $dayCapacity);
|
|
|
|
|
+ $empBalance[$empId] = $currentWithBalance - $canAllocToday; // 剩下的留给明天
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 最后一天,全部吃掉(包含之前剩下的所有余额)
|
|
|
|
|
+ $canAllocToday = $empRemainingMin;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $allocatedInDay = 0;
|
|
|
|
|
+ $ruleCount = count($empRules);
|
|
|
|
|
+ foreach ($empRules as $index => $rule) {
|
|
|
|
|
+ $rate = (float)$rule->rate / 100;
|
|
|
|
|
+ $projectMin = ($index === $ruleCount - 1) ? ($canAllocToday - $allocatedInDay) : (int)round($canAllocToday * $rate);
|
|
|
|
|
+
|
|
|
|
|
+ // 内部项目分配也尽量 5 分钟取整
|
|
|
|
|
+ if ($index < $ruleCount - 1 && $projectMin > $step) {
|
|
|
|
|
+ $projectMin = (int)round($projectMin / $step) * $step;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if ($projectMin > 0) {
|
|
|
|
|
+ $finalAlloc[$dayTs][$rule->item_id][$empId] = ($finalAlloc[$dayTs][$rule->item_id][$empId] ?? 0) + $projectMin;
|
|
|
|
|
+ $allocatedInDay += $projectMin;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ $empRemainingMin -= $canAllocToday;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // --- 3. 阶段二:打散到具体点 ---
|
|
|
|
|
+ $previewList = [];
|
|
|
|
|
+ $dailyEmpTimePools = [];
|
|
|
|
|
+ $tempMainIdCounter = 1;
|
|
|
|
|
+
|
|
|
|
|
+ foreach ($finalAlloc as $dayTs => $projects) {
|
|
|
|
|
+ foreach ($projects as $itemId => $employees) {
|
|
|
|
|
+ $itemTitle = $itemMap[$itemId] ?? '未知项目';
|
|
|
|
|
+ foreach ($employees as $empId => $toAllocMin) {
|
|
|
|
|
+ $currentTempMainId = $tempMainIdCounter++;
|
|
|
|
|
+
|
|
|
|
|
+ if (!isset($dailyEmpTimePools[$dayTs][$empId])) {
|
|
|
|
|
+ $dailyEmpTimePools[$dayTs][$empId] = $this->buildAvailablePool($empId, $dayTs, $allDays, $empWorkRanges, $standardWorkRanges, $leaveOverData);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $tempRem = (int)$toAllocMin;
|
|
|
|
|
+ foreach ($dailyEmpTimePools[$dayTs][$empId] as &$p) {
|
|
|
|
|
+ if ($tempRem <= 0) break;
|
|
|
|
|
+
|
|
|
|
|
+ // 起点必须对齐 5 分钟
|
|
|
|
|
+ $realStart = (int)ceil($p['s'] / $step) * $step;
|
|
|
|
|
+ if ($realStart >= $p['e']) continue;
|
|
|
|
|
+
|
|
|
|
|
+ $pMax = (int)($p['e'] - $realStart);
|
|
|
|
|
+ $take = min($tempRem, $pMax);
|
|
|
|
|
+
|
|
|
|
|
+ $realEnd = $realStart + $take;
|
|
|
|
|
+
|
|
|
|
|
+ $previewList[] = [
|
|
|
|
|
+ 'temp_main_id' => $currentTempMainId,
|
|
|
|
|
+ 'order_time' => date('Y-m-d', $dayTs),
|
|
|
|
|
+ 'order_timestamp' => $dayTs,
|
|
|
|
|
+ 'item_id' => $itemId,
|
|
|
|
|
+ 'item_title' => $itemTitle,
|
|
|
|
|
+ 'employee_id' => $empId,
|
|
|
|
|
+ 'employee_title' => $empNameMap[$empId] ?? '未知人员',
|
|
|
|
|
+ 'start_time' => sprintf('%02d:%02d', floor($realStart / 60), $realStart % 60),
|
|
|
|
|
+ 'end_time' => sprintf('%02d:%02d', floor($realEnd / 60), $realEnd % 60),
|
|
|
|
|
+ 'start_hour' => (int)floor($realStart / 60),
|
|
|
|
|
+ 'start_min' => (int)($realStart % 60),
|
|
|
|
|
+ 'end_hour' => (int)floor($realEnd / 60),
|
|
|
|
|
+ 'end_min' => (int)($realEnd % 60),
|
|
|
|
|
+ 'total_work_min' => $take,
|
|
|
|
|
+ ];
|
|
|
|
|
+
|
|
|
|
|
+ $tempRem -= $take;
|
|
|
|
|
+ $p['s'] = $realEnd;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return ['status' => true, 'data' => $previewList];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
public function dailyPwOrderSave($data, $user)
|
|
public function dailyPwOrderSave($data, $user)
|
|
|
{
|
|
{
|
|
|
$list = $data['list'] ?? [];
|
|
$list = $data['list'] ?? [];
|