|
|
@@ -0,0 +1,203 @@
|
|
|
+<?php
|
|
|
+
|
|
|
+namespace App\Service;
|
|
|
+
|
|
|
+use Carbon\Carbon;
|
|
|
+use Illuminate\Support\Facades\DB;
|
|
|
+
|
|
|
+class RdGenerateService extends Service
|
|
|
+{
|
|
|
+ public function generate(array $data)
|
|
|
+ {
|
|
|
+ if (empty($data['month'])) {
|
|
|
+ return [false, '请选择生成研发人员工时单年月'];
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ DB::transaction(function () use ($data) {
|
|
|
+ $this->doGenerate($data['month']);
|
|
|
+ });
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ return [false, $e->getMessage()];
|
|
|
+ }
|
|
|
+
|
|
|
+ return [true, ''];
|
|
|
+ }
|
|
|
+
|
|
|
+ protected function doGenerate(string $month)
|
|
|
+ {
|
|
|
+ $monthStart = Carbon::createFromFormat('Y-m', $month)->startOfMonth()->timestamp;
|
|
|
+ $monthEnd = Carbon::createFromFormat('Y-m', $month)->endOfMonth()->timestamp;
|
|
|
+
|
|
|
+ // 日历
|
|
|
+ $calendar = DB::table('calendar')
|
|
|
+ ->where('time', $monthStart)
|
|
|
+ ->first();
|
|
|
+
|
|
|
+ if (!$calendar) {
|
|
|
+ throw new \Exception('当月日历未配置');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 工作日
|
|
|
+ $workDays = DB::table('calendar_details')
|
|
|
+ ->where('calendar_id', $calendar->id)
|
|
|
+ ->where('is_work', 1)
|
|
|
+ ->orderBy('time')
|
|
|
+ ->pluck('time')
|
|
|
+ ->toArray();
|
|
|
+
|
|
|
+ if (empty($workDays)) {
|
|
|
+ throw new \Exception('当月无工作日');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 启用项目
|
|
|
+ $items = DB::table('item')
|
|
|
+ ->where('is_use', 1)
|
|
|
+ ->where('start_time', '<=', $monthEnd)
|
|
|
+ ->where('end_time', '>=', $monthStart)
|
|
|
+ ->get();
|
|
|
+
|
|
|
+ // 删除当月所有项目的老数据
|
|
|
+ $itemIds = $items->pluck('id')->toArray();
|
|
|
+ if (!empty($itemIds)) {
|
|
|
+ $rds = DB::table('rd')
|
|
|
+ ->whereIn('item_id', $itemIds)
|
|
|
+ ->whereBetween('order_time', [$monthStart, $monthEnd])
|
|
|
+ ->pluck('id')
|
|
|
+ ->toArray();
|
|
|
+
|
|
|
+ if (!empty($rds)) {
|
|
|
+ DB::table('rd_details')->whereIn('rd_id', $rds)->delete();
|
|
|
+ DB::table('rd')->whereIn('id', $rds)->delete();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取所有员工id
|
|
|
+ $employeeIds = DB::table('item_details')
|
|
|
+ ->whereIn('item_id', $itemIds)
|
|
|
+ ->where('type', 1)
|
|
|
+ ->pluck('data_id')
|
|
|
+ ->unique()
|
|
|
+ ->toArray();
|
|
|
+
|
|
|
+ // 按员工生成当月工时单
|
|
|
+ foreach ($employeeIds as $employeeId) {
|
|
|
+ $this->generateEmployeeMonth($employeeId, $items, $workDays, $calendar);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 按员工生成当月工时单,严格保证总工时匹配
|
|
|
+ */
|
|
|
+ protected function generateEmployeeMonth(int $employeeId, $items, array $workDays, $calendar)
|
|
|
+ {
|
|
|
+ $employeeMonth = DB::table('employee_details')
|
|
|
+ ->where('employee_id', $employeeId)
|
|
|
+ ->where('time', $calendar->time)
|
|
|
+ ->first();
|
|
|
+
|
|
|
+ if (!$employeeMonth || $employeeMonth->total_hours_2 <= 0) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ $totalMinutes = (int)round($employeeMonth->total_hours_2 * 60); // 员工总分钟数
|
|
|
+ $dayMaxMinutes = 510; // 每天最大分钟数 (含午休)
|
|
|
+
|
|
|
+ // 员工参与的项目
|
|
|
+ $employeeItems = [];
|
|
|
+ foreach ($items as $item) {
|
|
|
+ $exists = DB::table('item_details')
|
|
|
+ ->where('item_id', $item->id)
|
|
|
+ ->where('type', 1)
|
|
|
+ ->where('data_id', $employeeId)
|
|
|
+ ->exists();
|
|
|
+ if ($exists) {
|
|
|
+ $employeeItems[] = $item;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (empty($employeeItems)) return;
|
|
|
+
|
|
|
+ // 生成所有槽位:项目 × 工作日(有效周期内)
|
|
|
+ $slots = [];
|
|
|
+ foreach ($employeeItems as $item) {
|
|
|
+ foreach ($workDays as $day) {
|
|
|
+ if ($day >= $item->start_time && $day <= $item->end_time) {
|
|
|
+ $slots[] = [
|
|
|
+ 'item_id' => $item->id,
|
|
|
+ 'day' => $day
|
|
|
+ ];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (empty($slots)) return;
|
|
|
+
|
|
|
+ // 均分总分钟数
|
|
|
+ $numSlots = count($slots);
|
|
|
+ $baseMinutes = floor($totalMinutes / $numSlots);
|
|
|
+ $remainder = $totalMinutes - $baseMinutes * $numSlots;
|
|
|
+
|
|
|
+ foreach ($slots as $i => $slot) {
|
|
|
+ $minutes = $baseMinutes;
|
|
|
+ if ($i === $numSlots - 1) {
|
|
|
+ // 最后 slot 补齐余数
|
|
|
+ $minutes += $remainder;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 确保单日不超过最大分钟数
|
|
|
+ $minutes = min($minutes, $dayMaxMinutes - 30); // 扣掉午休
|
|
|
+
|
|
|
+ $this->createRd($slot['item_id'], $slot['day'], $minutes, $employeeId);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 创建单条研发工时单
|
|
|
+ */
|
|
|
+ protected function createRd(int $itemId, int $dayTime, int $minutes, int $employeeId)
|
|
|
+ {
|
|
|
+ $startHour = 8;
|
|
|
+ $startMin = 0;
|
|
|
+ $maxMinutes = 510; // 每天最大分钟数 (含午休)
|
|
|
+
|
|
|
+ // 加午休 30 分钟
|
|
|
+ $workMinutes = min($minutes + 30, $maxMinutes);
|
|
|
+ $realMinutes = $workMinutes - 30; // 实际研发工时分钟数
|
|
|
+
|
|
|
+ // 随机结束时间 08:00~16:30
|
|
|
+ $endTime = Carbon::createFromTimestamp($dayTime)
|
|
|
+ ->setTime($startHour, $startMin)
|
|
|
+ ->addMinutes($workMinutes);
|
|
|
+
|
|
|
+ // 随机 crt_time 16:30 ~ 17:00
|
|
|
+ $crtTime = Carbon::createFromTimestamp($dayTime)
|
|
|
+ ->setTime(16, 30)
|
|
|
+ ->addSeconds(mt_rand(0, 30*60))
|
|
|
+ ->timestamp;
|
|
|
+
|
|
|
+ $dayPrefix = date('Ymd', $dayTime);
|
|
|
+ $orderNumber = 'RD' . $dayPrefix . mt_rand(1000, 9999) . substr(uniqid(), -3);
|
|
|
+
|
|
|
+ $rdId = DB::table('rd')->insertGetId([
|
|
|
+ 'crt_time' => $crtTime,
|
|
|
+ 'order_time' => $dayTime,
|
|
|
+ 'item_id' => $itemId,
|
|
|
+ 'start_time_hour' => $startHour,
|
|
|
+ 'start_time_min' => $startMin,
|
|
|
+ 'end_time_hour' => $endTime->hour,
|
|
|
+ 'end_time_min' => $endTime->minute,
|
|
|
+ 'total_hours' => $realMinutes,
|
|
|
+ 'order_number' => $orderNumber,
|
|
|
+ 'type' => 1
|
|
|
+ ]);
|
|
|
+
|
|
|
+ DB::table('rd_details')->insert([
|
|
|
+ 'rd_id' => $rdId,
|
|
|
+ 'data_id' => $employeeId,
|
|
|
+ 'type' => 1,
|
|
|
+ 'crt_time' => $crtTime,
|
|
|
+ 'upd_time' => $crtTime,
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+}
|