cqp hace 2 meses
padre
commit
c1075493cd

+ 26 - 0
app/Http/Controllers/Api/PersonWorkController.php

@@ -153,4 +153,30 @@ class PersonWorkController extends BaseController
             return $this->json_return(201,$data);
             return $this->json_return(201,$data);
         }
         }
     }
     }
+
+    public function dailyPwOrderPreview(Request $request)
+    {
+        $service = new PersonWorkService();
+        $user = $request->userData;
+        list($status,$data) = $service->dailyPwOrderPreview($request->all(),$user);
+
+        if($status){
+            return $this->json_return(200,'',$data);
+        }else{
+            return $this->json_return(201,$data);
+        }
+    }
+
+    public function dailyPwOrderSave(Request $request)
+    {
+        $service = new PersonWorkService();
+        $user = $request->userData;
+        list($status,$data) = $service->dailyPwOrderSave($request->all(),$user);
+
+        if($status){
+            return $this->json_return(200,'',$data);
+        }else{
+            return $this->json_return(201,$data);
+        }
+    }
 }
 }

+ 313 - 0
app/Service/PersonWorkService.php

@@ -1019,4 +1019,317 @@ class PersonWorkService extends Service
         }
         }
         return $pool;
         return $pool;
     }
     }
+
+
+    public function dailyPwOrderPreview($data, $user)
+    {
+        $topDepartId = $user['top_depart_id'];
+        if (empty($data['month'])) return [false, '月份不能为空'];
+
+        // 1. 前置校验 (保留你之前的校验逻辑)
+        $monthStart = $this->changeDateToDate($data['month']);
+
+        // 1. 检查是否存在月度工时明细
+        $hasMonthlyOrder = DB::table('monthly_pw_order_details as d')
+            ->join('monthly_pw_order as m', 'm.id', '=', 'd.main_id')
+            ->where('m.month', $monthStart)
+            ->where('m.top_depart_id', $topDepartId)
+            ->where('m.del_time', 0)
+            ->where('d.del_time', 0)
+            ->exists();
+
+        if (!$hasMonthlyOrder) return [false, '未找到该月份的月度工时明细,请先生成人员月度工时单'];
+
+        // 2. 检查是否配置了工作日历
+        $hasCalendar = DB::table('calendar_details')
+            ->where('month', $monthStart)
+            ->where('del_time', 0)
+            ->exists();
+
+        if (!$hasCalendar) return [false, '该月份工作日历未配置'];
+
+        // 3. 检查是否配置了项目比例规则
+        $hasRules = DB::table('rule_set as r')
+            ->where('r.month', $monthStart)
+            ->where('r.top_depart_id', $topDepartId)
+            ->where('r.del_time', 0)
+            ->exists();
+
+        if (!$hasRules) return [false, '未找到该月份的规则配置单'];
+
+        // 2. 调用核心计算逻辑 (抽取出的私有方法)
+        $result = $this->calculateDailyAllocation($monthStart, $topDepartId, $user);
+
+        if (!$result['status']) return [false, $result['msg']];
+
+        // 3. 将结果存入临时表或直接返回
+        // 建议增加一个 batch_id,防止多人操作冲突
+        $batchId = uniqid('batch_');
+        $previewData = $result['data'];
+
+        return [true, [
+            'batch_id' => $batchId,
+            'list' => $previewData // 返回给前端展示
+        ]];
+    }
+
+    /**
+     * 核心分配逻辑:计算预览数据(确保全整数分钟)
+     * @param int $monthStart 月初时间戳
+     * @param int $topDepartId 顶级部门ID
+     * @param array $user 用户信息
+     * @return array
+     */
+    private function calculateDailyAllocation($monthStart, $topDepartId, $user)
+    {
+        $monthEnd = strtotime('+1 month', $monthStart) - 1;
+        $now = time();
+
+        // --- 1. 基础数据加载 ---
+        $monthlyOrder = DB::table('monthly_pw_order_details as d')
+            ->join('monthly_pw_order as m', 'm.id', '=', 'd.main_id')
+            ->where('m.month', $monthStart)
+            ->where('m.top_depart_id', $topDepartId)
+            ->where('m.del_time', 0)
+            ->where('d.del_time', 0)
+            ->select('d.*')
+            ->get();
+
+        if ($monthlyOrder->isEmpty()) return ['status' => false, 'msg' => '未找到该月份的月度工时明细'];
+
+        $empIds = $monthlyOrder->pluck('employee_id')->unique()->toArray();
+
+        // 加载员工/标准班次
+        $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();
+
+        // 加载分配规则(项目比例)
+        $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');
+
+        // 加载日历
+        $allDays = DB::table('calendar_details')
+            ->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('m.del_time', 0)
+            ->select('d.*', 'm.order_time', 'm.type as main_type')
+            ->get()
+            ->groupBy(['employee_id', 'order_time']);
+
+        // --- 2. 阶段一:计算每个人每天在每个项目上应分配的整数分钟数 ---
+        $finalAlloc = [];
+        foreach ($monthlyOrder as $mDetail) {
+            $empId = $mDetail->employee_id;
+            $empRules = $ruleSet->get($empId);
+            if (!$empRules) continue;
+
+            // 月度总额(转为整数分钟)
+            $empRemainingMin = (int)round((float)$mDetail->rd_total_hours * 60);
+            if ($empRemainingMin <= 0) continue;
+
+            foreach ($allDays as $dayInfo) {
+                if ($empRemainingMin <= 0) break;
+
+                $dayTs = $dayInfo->time;
+
+                // 获取当天可用时间池段落(此处调用你原来的 buildAvailablePool)
+                $tempPool = $this->buildAvailablePool($empId, $dayTs, $allDays, $empWorkRanges, $standardWorkRanges, $leaveOverData);
+
+                // 计算当天总可用整数分钟
+                $dayAvailableMin = 0;
+                foreach ($tempPool as $p) {
+                    $dayAvailableMin += (int)($p['e'] - $p['s']);
+                }
+
+                if ($dayAvailableMin <= 0) continue;
+
+                // 当天实际能分配的量(取小者)
+                $canAllocToday = min($empRemainingMin, $dayAvailableMin);
+
+                // --- 核心:按项目比例切分并强制取整 ---
+                $allocatedInDay = 0;
+                $ruleCount = count($empRules);
+
+                foreach ($empRules as $index => $rule) {
+                    $rate = (float)$rule->rate / 100;
+
+                    // 为了防止四舍五入累积误差,最后一个项目用减法
+                    if ($index === $ruleCount - 1) {
+                        $projectMin = $canAllocToday - $allocatedInDay;
+                    } else {
+                        $projectMin = (int)round($canAllocToday * $rate);
+                    }
+
+                    if ($projectMin > 0) {
+                        $finalAlloc[$dayTs][$rule->item_id][$empId] = $projectMin;
+                        $allocatedInDay += $projectMin;
+                    }
+                }
+                $empRemainingMin -= $canAllocToday;
+            }
+        }
+
+        // --- 3. 阶段二:打散到具体时间点并生成预览行 ---
+
+        $previewList = [];
+        $dailyEmpTimePools = [];
+        $tempMainIdCounter = 1;
+
+        foreach ($finalAlloc as $dayTs => $projects) {
+            foreach ($projects as $itemId => $employees) {
+
+                // 同日期同项目,视为一张“模拟单据”
+                $currentTempMainId = $tempMainIdCounter++;
+
+                foreach ($employees as $empId => $toAllocMin) {
+                    // 初始化当天该员工的时间池引用
+                    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;
+
+                        $pMax = (int)($p['e'] - $p['s']);
+                        if ($pMax <= 0) continue;
+
+                        $take = min($tempRem, $pMax);
+                        $realStart = (int)$p['s'];
+                        $realEnd = $realStart + $take;
+
+                        // 构造预览行(全部字段已处理为整数)
+                        $previewList[] = [
+                            'temp_main_id'    => $currentTempMainId,
+                            'order_time'      => date('Y-m-d', $dayTs),
+                            'order_timestamp' => $dayTs,
+                            'item_id'         => $itemId,
+                            'employee_id'     => $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)
+    {
+        $list = $data['list'] ?? [];
+        if (empty($list)) return [false, '没有可保存的数据'];
+
+        $topDepartId = $user['top_depart_id'];
+        $now = time();
+
+        DB::beginTransaction();
+        try {
+            // 1. 清理旧数据 (严格按月份和部门清理,防止重复)
+            $monthStart = $this->changeDateToDate($data['month']);
+            $monthEnd = strtotime('+1 month', $monthStart) - 1;
+
+            $oldOrderIds = DB::table('daily_pw_order')
+                ->where('top_depart_id', $topDepartId)
+                ->where('order_time', '>=', $monthStart)
+                ->where('order_time', '<=', $monthEnd)
+                ->where('is_create', 1)
+                ->where('del_time', 0)
+                ->pluck('id');
+
+            if ($oldOrderIds->isNotEmpty()) {
+                DB::table('daily_pw_order')->whereIn('id', $oldOrderIds)->update(['del_time' => $now]);
+                DB::table('daily_pw_order_details')->whereIn('main_id', $oldOrderIds)->update(['del_time' => $now]);
+            }
+
+            // 2. 按 temp_main_id 分组写入
+            $grouped = collect($list)->groupBy('temp_main_id');
+
+            foreach ($grouped as $tempMainId => $details) {
+                $first = $details->first();
+
+                // 写入主表
+                $mainId = DB::table('daily_pw_order')->insertGetId([
+                    'code' => '',
+                    'item_id' => $first['item_id'],
+                    'order_time' => $first['order_timestamp'],
+                    'top_depart_id' => $topDepartId,
+                    'is_create' => 1,
+                    'crt_id' => $user['id'],
+                    'crt_time' => $now,
+                    'upd_time' => $now,
+                    'del_time' => 0,
+                ]);
+
+                // 构造批量插入的明细数组
+                $insertDetails = [];
+                foreach ($details as $d) {
+                    $insertDetails[] = [
+                        'main_id'         => $mainId,
+                        'employee_id'     => $d['employee_id'],
+                        'top_depart_id'   => $topDepartId,
+                        // 直接使用预览时生成的字段,效率更高
+                        'start_time_hour' => $d['start_hour'],
+                        'start_time_min'  => $d['start_min'],
+                        'end_time_hour'   => $d['end_hour'],
+                        'end_time_min'    => $d['end_min'],
+                        'total_work_min'  => $d['total_work_min'],
+                        'crt_time'        => $now,
+                        'upd_time'        => $now,
+                        'del_time'        => 0,
+                    ];
+                }
+
+                // 批量写入明细
+                DB::table('daily_pw_order_details')->insert($insertDetails);
+
+                // 3. 生成并回填单号
+                $code = $this->generateBillNo([
+                    'top_depart_id' => $topDepartId,
+                    'type' => DailyPwOrder::Order_type,
+                    'period' => date("Ym", $first['order_timestamp'])
+                ]);
+                DB::table('daily_pw_order')->where('id', $mainId)->update(['code' => $code]);
+            }
+
+            DB::commit();
+            return [true, ''];
+        } catch (\Exception $e) {
+            DB::rollBack();
+            return [false, '保存失败: ' . $e->getMessage() . ' (Line: ' . $e->getLine() . ')'];
+        }
+    }
 }
 }

+ 2 - 0
routes/api.php

@@ -134,6 +134,8 @@ Route::group(['middleware'=> ['checkLogin']],function ($route){
     $route->any('dailyPwOrderDetail', 'Api\PersonWorkController@dailyPwOrderDetail');
     $route->any('dailyPwOrderDetail', 'Api\PersonWorkController@dailyPwOrderDetail');
     //根据月工时生成
     //根据月工时生成
     $route->any('dailyPwOrderCreate', 'Api\PersonWorkController@dailyPwOrderCreate');
     $route->any('dailyPwOrderCreate', 'Api\PersonWorkController@dailyPwOrderCreate');
+    $route->any('dailyPwOrderPreview', 'Api\PersonWorkController@dailyPwOrderPreview');
+    $route->any('dailyPwOrderSave', 'Api\PersonWorkController@dailyPwOrderSave');
 
 
     //设备日工时单
     //设备日工时单
     $route->any('dailyDwOrderList', 'Api\DeviceWorkController@dailyDwOrderList');
     $route->any('dailyDwOrderList', 'Api\DeviceWorkController@dailyDwOrderList');