|
|
@@ -2,12 +2,15 @@
|
|
|
|
|
|
namespace App\Service;
|
|
|
|
|
|
+use App\Jobs\ProcessDataJob;
|
|
|
+use App\Model\CalendarDetails;
|
|
|
use App\Model\DailyPwOrder;
|
|
|
use App\Model\DailyPwOrderDetails;
|
|
|
use App\Model\Employee;
|
|
|
use App\Model\Item;
|
|
|
use App\Model\MonthlyPwOrder;
|
|
|
use App\Model\MonthlyPwOrderDetails;
|
|
|
+use App\Model\RuleSetDetails;
|
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
|
|
class PersonWorkService extends Service
|
|
|
@@ -779,12 +782,239 @@ class PersonWorkService extends Service
|
|
|
return $res;
|
|
|
}
|
|
|
|
|
|
- public function dailyPwOrderCreate($data,$user){
|
|
|
- $data['top_depart_id'] = $user['top_depart_id'];
|
|
|
- if(empty($data['month'])) return [false, '月份不能为空'];
|
|
|
+ public function dailyPwOrderCreate($data, $user)
|
|
|
+ {
|
|
|
+ $topDepartId = $user['top_depart_id'];
|
|
|
+ if (empty($data['month'])) return [false, '月份不能为空'];
|
|
|
+
|
|
|
+ $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, '未找到该月份的规则配置单'];
|
|
|
+
|
|
|
+ $data['type'] = "p_work";
|
|
|
+ ProcessDataJob::dispatch($data, $user)->onQueue(DailyPwOrder::job);
|
|
|
+
|
|
|
+ return [true, '生成任务已提交,系统正在后台处理,请稍后查看'];
|
|
|
+ }
|
|
|
+
|
|
|
+ public function dailyPwOrderCreateMain($data, $user)
|
|
|
+ {
|
|
|
+ $topDepartId = $user['top_depart_id'];
|
|
|
+ if (empty($data['month'])) return [false, '月份不能为空'];
|
|
|
|
|
|
$monthStart = $this->changeDateToDate($data['month']);
|
|
|
- $data['month'] = $monthStart;
|
|
|
$monthEnd = strtotime('+1 month', $monthStart) - 1;
|
|
|
+ $now = time();
|
|
|
+
|
|
|
+ DB::beginTransaction();
|
|
|
+ try {
|
|
|
+ // --- 0. 清理旧数据 ---
|
|
|
+ $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]);
|
|
|
+ }
|
|
|
+
|
|
|
+ // --- 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 [false, '未找到月度工时明细'];
|
|
|
+
|
|
|
+ $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', RuleSetDetails::type_one)
|
|
|
+ ->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 = (float)$mDetail->rd_total_hours * 60;
|
|
|
+ if ($empRemainingMin <= 0) continue;
|
|
|
+
|
|
|
+ foreach ($allDays as $dayInfo) {
|
|
|
+ if ($empRemainingMin <= 0) break;
|
|
|
+ $dayTs = $dayInfo->time;
|
|
|
+ $isWorkDay = ($dayInfo->is_work == CalendarDetails::TYPE_ONE);
|
|
|
+ $todaySpecials = data_get($leaveOverData, "$empId.$dayTs", collect());
|
|
|
+ $todayOvertimes = $todaySpecials->where('main_type', 2);
|
|
|
+
|
|
|
+ if (!$isWorkDay && $todayOvertimes->isEmpty()) continue;
|
|
|
+
|
|
|
+ // 计算当天可用总时长
|
|
|
+ $dayAvailableMin = 0;
|
|
|
+ if ($isWorkDay) {
|
|
|
+ $baseRanges = $empWorkRanges->has($empId) ? $empWorkRanges->get($empId) : $standardWorkRanges;
|
|
|
+ foreach ($baseRanges as $br) {
|
|
|
+ $brS = $br->start_time_hour * 60 + $br->start_time_min;
|
|
|
+ $brE = $br->end_time_hour * 60 + $br->end_time_min;
|
|
|
+ $avail = (float)$br->total_work_min;
|
|
|
+ foreach ($todaySpecials->where('main_type', 1) as $lv) {
|
|
|
+ $overlap = min($brE, ($lv->end_time_hour * 60 + $lv->end_time_min)) - max($brS, ($lv->start_time_hour * 60 + $lv->start_time_min));
|
|
|
+ if ($overlap > 0) $avail -= $overlap;
|
|
|
+ }
|
|
|
+ $dayAvailableMin += max(0, $avail);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ foreach ($todayOvertimes as $ot) $dayAvailableMin += (float)$ot->total_min;
|
|
|
+
|
|
|
+ $canAllocToday = min($empRemainingMin, $dayAvailableMin);
|
|
|
+ if ($canAllocToday <= 0) continue;
|
|
|
+
|
|
|
+ foreach ($empRules as $rule) {
|
|
|
+ $rate = (float)$rule->rate / 100;
|
|
|
+ $projectMin = $canAllocToday * $rate;
|
|
|
+ if ($projectMin > 0) {
|
|
|
+ // 结果存入:[日期][项目][人员]
|
|
|
+ $finalAlloc[$dayTs][$rule->item_id][$empId] = $projectMin;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ $empRemainingMin -= $canAllocToday;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // --- 3. 生成单据:解决时间重叠的核心逻辑 ---
|
|
|
+ $newOrderIds = [];
|
|
|
+ // 为了解决重叠,我们需要按 [日期][人员] 来追踪时间池的消耗进度
|
|
|
+ $dailyEmpTimePools = [];
|
|
|
+
|
|
|
+ foreach ($finalAlloc as $dayTs => $projects) {
|
|
|
+ foreach ($projects as $itemId => $employees) {
|
|
|
+ $mainId = DB::table('daily_pw_order')->insertGetId([
|
|
|
+ 'code' => '', 'item_id' => $itemId, 'order_time' => $dayTs, 'top_depart_id' => $topDepartId,
|
|
|
+ 'is_create' => 1, 'crt_id' => $user['id'], 'crt_time' => $now, 'upd_time' => $now,
|
|
|
+ ]);
|
|
|
+ $newOrderIds[] = ['id' => $mainId, 'time' => $dayTs];
|
|
|
+
|
|
|
+ foreach ($employees as $empId => $toAllocMin) {
|
|
|
+ // 如果该员工当天的池子还没构建,先构建一次
|
|
|
+ if (!isset($dailyEmpTimePools[$dayTs][$empId])) {
|
|
|
+ $dailyEmpTimePools[$dayTs][$empId] = $this->buildAvailablePool($empId, $dayTs, $allDays, $empWorkRanges, $standardWorkRanges, $leaveOverData);
|
|
|
+ }
|
|
|
+
|
|
|
+ $tempRem = $toAllocMin;
|
|
|
+ // 指向该员工当天的可用池引用,这样处理完项目A,池子里的时间会自动被“消耗”
|
|
|
+ foreach ($dailyEmpTimePools[$dayTs][$empId] as &$p) {
|
|
|
+ if ($tempRem <= 0) break;
|
|
|
+
|
|
|
+ $pMax = $p['e'] - $p['s'];
|
|
|
+ if ($pMax <= 0) continue;
|
|
|
+
|
|
|
+ $take = min($tempRem, $pMax);
|
|
|
+ $realStart = $p['s'];
|
|
|
+ $realEnd = $p['s'] + $take;
|
|
|
+
|
|
|
+ DB::table('daily_pw_order_details')->insert([
|
|
|
+ 'main_id' => $mainId, 'employee_id' => $empId, 'top_depart_id' => $topDepartId,
|
|
|
+ 'start_time_hour' => floor($realStart / 60), 'start_time_min' => $realStart % 60,
|
|
|
+ 'end_time_hour' => floor($realEnd / 60), 'end_time_min' => $realEnd % 60,
|
|
|
+ 'total_work_min' => $take, 'crt_time' => $now, 'upd_time' => $now,
|
|
|
+ ]);
|
|
|
+
|
|
|
+ $tempRem -= $take;
|
|
|
+ // 重要:消耗掉这个时段的起始位置,确保下一个项目从这里开始
|
|
|
+ $p['s'] = $realEnd;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // --- 4. 回填单号 ---
|
|
|
+ if (empty($newOrderIds)) return [false, '未生成数据'];
|
|
|
+ foreach ($newOrderIds as $item) {
|
|
|
+ $code = $this->generateBillNo(['top_depart_id' => $topDepartId, 'type' => DailyPwOrder::Order_type, 'period' => date("Ym", $item['time'])]);
|
|
|
+ DB::table('daily_pw_order')->where('id', $item['id'])->update(['code' => $code]);
|
|
|
+ }
|
|
|
+ DB::commit();
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ DB::rollBack();
|
|
|
+ return [false, '错误: ' . $e->getMessage() . ' 行: ' . $e->getLine()];
|
|
|
+ }
|
|
|
+ return [true, ''];
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 辅助函数:构建某人某天的初始可用时段池
|
|
|
+ */
|
|
|
+ private function buildAvailablePool($empId, $dayTs, $allDays, $empWorkRanges, $standardWorkRanges, $leaveOverData)
|
|
|
+ {
|
|
|
+ $dayInfo = $allDays->where('time', $dayTs)->first();
|
|
|
+ $todaySpecials = data_get($leaveOverData, "$empId.$dayTs", collect());
|
|
|
+ $pool = [];
|
|
|
+
|
|
|
+ if ($dayInfo && $dayInfo->is_work == CalendarDetails::TYPE_ONE) {
|
|
|
+ $baseRanges = $empWorkRanges->has($empId) ? $empWorkRanges->get($empId) : $standardWorkRanges;
|
|
|
+ foreach ($baseRanges as $br) {
|
|
|
+ $currentS = $br->start_time_hour * 60 + $br->start_time_min;
|
|
|
+ $eMin = $br->end_time_hour * 60 + $br->end_time_min;
|
|
|
+ $sortedLeaves = $todaySpecials->where('main_type', 1)->sortBy('start_time_hour');
|
|
|
+
|
|
|
+ foreach ($sortedLeaves as $lv) {
|
|
|
+ $lvS = $lv->start_time_hour * 60 + $lv->start_time_min;
|
|
|
+ $lvE = $lv->end_time_hour * 60 + $lv->end_time_min;
|
|
|
+ if ($lvS < $eMin && $lvE > $currentS) {
|
|
|
+ if ($lvS > $currentS) $pool[] = ['s' => $currentS, 'e' => $lvS];
|
|
|
+ $currentS = max($currentS, $lvE);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if ($currentS < $eMin) $pool[] = ['s' => $currentS, 'e' => $eMin];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ foreach ($todaySpecials->where('main_type', 2) as $ot) {
|
|
|
+ $otS = $ot->start_time_hour * 60 + $ot->start_time_min;
|
|
|
+ $pool[] = ['s' => $otS, 'e' => $otS + (float)$ot->total_min];
|
|
|
+ }
|
|
|
+ return $pool;
|
|
|
}
|
|
|
}
|