|
|
@@ -165,6 +165,8 @@ class DeviceWorkService extends Service
|
|
|
$customer = $customer->toArray();
|
|
|
$customer['crt_name'] = Employee::where('id',$customer['crt_id'])->value('title');
|
|
|
$customer['crt_time'] = $customer['crt_time'] ? date("Y-m-d H:i:s",$customer['crt_time']): '';
|
|
|
+ $map = ArchiveService::fillIsArchive($customer['month'], $user);
|
|
|
+ $customer['is_archive'] = $map[$customer['month']] ?? false;
|
|
|
$customer['month'] = $customer['month'] ? date("Y-m",$customer['month']): '';
|
|
|
|
|
|
$details = $this->getDetail($data['id']);
|
|
|
@@ -200,7 +202,7 @@ class DeviceWorkService extends Service
|
|
|
public function monthlyDwOrderList($data,$user){
|
|
|
$model = $this->monthlyDwOrderCommon($data, $user);
|
|
|
$list = $this->limit($model,'',$data);
|
|
|
- $list = $this->fillData($list);
|
|
|
+ $list = $this->fillData($list, $user);
|
|
|
|
|
|
return [true, $list];
|
|
|
}
|
|
|
@@ -232,9 +234,9 @@ class DeviceWorkService extends Service
|
|
|
|
|
|
// 字段中文映射,用于报错
|
|
|
$fieldNames = [
|
|
|
- 'total_days' => '出勤总天数',
|
|
|
- 'rd_total_days' => '研发出勤天数',
|
|
|
- 'total_hours' => '出勤总工时',
|
|
|
+ 'total_days' => '使用总天数',
|
|
|
+ 'rd_total_days' => '研发使用天数',
|
|
|
+ 'total_hours' => '使用总工时',
|
|
|
'rd_total_hours'=> '研发总工时'
|
|
|
];
|
|
|
|
|
|
@@ -261,18 +263,18 @@ class DeviceWorkService extends Service
|
|
|
if ($sysData) {
|
|
|
// A. 内部逻辑:研发不能大于总额
|
|
|
if ($value['rd_total_days'] > $value['total_days']) {
|
|
|
- return [false, "第{$line}行:设备{$deviceDisplayName}的研发出勤天数不能大于出勤总天数"];
|
|
|
+ return [false, "第{$line}行:设备{$deviceDisplayName}的研发使用天数不能大于使用总天数"];
|
|
|
}
|
|
|
if ($value['rd_total_hours'] > $value['total_hours']) {
|
|
|
- return [false, "第{$line}行:设备{$deviceDisplayName}的研发总工时不能大于出勤总工时"];
|
|
|
+ return [false, "第{$line}行:设备{$deviceDisplayName}的研发总工时不能大于使用总工时"];
|
|
|
}
|
|
|
|
|
|
// B. 外部逻辑:不能超过系统根据日历算出的上限
|
|
|
if ($value['total_days'] != $sysData['attendance_days']) {
|
|
|
- return [false, "第{$line}行:设备{$deviceDisplayName}的出勤总天数({$value['total_days']})不等于当月标准天数({$sysData['attendance_days']})"];
|
|
|
+ return [false, "第{$line}行:设备{$deviceDisplayName}的使用总天数({$value['total_days']})不等于系统核算天数({$sysData['attendance_days']})"];
|
|
|
}
|
|
|
if ($value['total_hours'] != $sysData['final_work_hour']) {
|
|
|
- return [false, "第{$line}行:设备{$deviceDisplayName}的出勤总工时({$value['total_hours']})不等于当月标准工时({$sysData['final_work_hour']})"];
|
|
|
+ return [false, "第{$line}行:设备{$deviceDisplayName}的使用总工时({$value['total_hours']})不等于系统核算工时({$sysData['final_work_hour']})"];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -299,14 +301,17 @@ class DeviceWorkService extends Service
|
|
|
return [true, ''];
|
|
|
}
|
|
|
|
|
|
- public function fillData($data){
|
|
|
+ public function fillData($data, $user){
|
|
|
if(empty($data['data'])) return $data;
|
|
|
|
|
|
$emp = (new EmployeeService())->getEmployeeMap(array_unique(array_column($data['data'],'crt_id')));
|
|
|
+ $map = ArchiveService::fillIsArchive(array_unique(array_column($data['data'],'month')), $user);
|
|
|
+
|
|
|
foreach ($data['data'] as $key => $value){
|
|
|
$data['data'][$key]['crt_time'] = $value['crt_time'] ? date('Y-m-d H:i:s',$value['crt_time']) : '';
|
|
|
$data['data'][$key]['month'] = $value['month'] ? date('Y-m',$value['month']) : '';
|
|
|
$data['data'][$key]['crt_name'] = $emp[$value['crt_id']] ?? '';
|
|
|
+ $data['data'][$key]['is_archive'] = $map[$value['month']] ?? false;
|
|
|
}
|
|
|
|
|
|
return $data;
|
|
|
@@ -535,6 +540,8 @@ class DeviceWorkService extends Service
|
|
|
$item = Item::where('id', $customer['item_id'])->first();
|
|
|
$customer['item_title'] = $item->title;
|
|
|
$customer['item_code'] = $item->code;
|
|
|
+ $map = ArchiveService::fillIsArchive($customer['order_time'], $user);
|
|
|
+ $customer['is_archive'] = $map[$customer['order_time']] ?? false;
|
|
|
$customer['order_time'] = $customer['order_time'] ? date("Y-m-d",$customer['order_time']): '';
|
|
|
|
|
|
$details = $this->getDetailDaily($data['id']);
|
|
|
@@ -578,7 +585,7 @@ class DeviceWorkService extends Service
|
|
|
public function dailyDwOrderList($data,$user){
|
|
|
$model = $this->dailyDwOrderCommon($data, $user);
|
|
|
$list = $this->limit($model,'',$data);
|
|
|
- $list = $this->fillDataDaily($list);
|
|
|
+ $list = $this->fillDataDaily($list, $user);
|
|
|
|
|
|
return [true, $list];
|
|
|
}
|
|
|
@@ -688,11 +695,12 @@ class DeviceWorkService extends Service
|
|
|
return [true, ''];
|
|
|
}
|
|
|
|
|
|
- public function fillDataDaily($data){
|
|
|
+ public function fillDataDaily($data, $user){
|
|
|
if(empty($data['data'])) return $data;
|
|
|
|
|
|
$emp = (new EmployeeService())->getEmployeeMap(array_unique(array_column($data['data'],'crt_id')));
|
|
|
$item = (new ItemService())->getItemMap(array_unique(array_column($data['data'],'item_id')));
|
|
|
+ $map = ArchiveService::fillIsArchive(array_unique(array_column($data['data'],'month')), $user);
|
|
|
foreach ($data['data'] as $key => $value){
|
|
|
$data['data'][$key]['crt_time'] = $value['crt_time'] ? date('Y-m-d H:i:s',$value['crt_time']) : '';
|
|
|
$data['data'][$key]['order_time'] = $value['order_time'] ? date('Y-m-d',$value['order_time']) : '';
|
|
|
@@ -700,6 +708,7 @@ class DeviceWorkService extends Service
|
|
|
$item_tmp = $item[$value['item_id']] ?? [];
|
|
|
$data['data'][$key]['item_title'] = $item_tmp['title'] ?? '';
|
|
|
$data['data'][$key]['item_code'] = $item_tmp['code'] ?? '';
|
|
|
+ $data['data'][$key]['is_archive'] = $map[$value['order_time']] ?? false;
|
|
|
}
|
|
|
|
|
|
return $data;
|
|
|
@@ -1037,7 +1046,7 @@ class DeviceWorkService extends Service
|
|
|
]];
|
|
|
}
|
|
|
|
|
|
- private function calculateDailyDeviceAllocation($monthStart, $topDepartId, $user)
|
|
|
+ private function calculateDailyDeviceAllocation1($monthStart, $topDepartId, $user)
|
|
|
{
|
|
|
// 加载月度设备明细
|
|
|
$monthlyOrder = DB::table('monthly_dw_order_details as d')
|
|
|
@@ -1189,6 +1198,172 @@ class DeviceWorkService extends Service
|
|
|
return ['status' => true, 'data' => $previewList];
|
|
|
}
|
|
|
|
|
|
+ private function calculateDailyDeviceAllocation($monthStart, $topDepartId, $user)
|
|
|
+ {
|
|
|
+ // 加载月度设备明细
|
|
|
+ $monthlyOrder = DB::table('monthly_dw_order_details as d')
|
|
|
+ ->join('monthly_dw_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' => '未找到设备月度工时明细'];
|
|
|
+
|
|
|
+ $usedDeviceIds = $monthlyOrder->pluck('device_id')->unique()->toArray();
|
|
|
+
|
|
|
+ $deviceMap = DB::table('device')
|
|
|
+ ->whereIn('id', $usedDeviceIds)
|
|
|
+ ->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', 2) // 设备类型
|
|
|
+ ->where('rd.top_depart_id', $topDepartId)
|
|
|
+ ->where('r.del_time', 0)
|
|
|
+ ->where('rd.del_time', 0)
|
|
|
+ ->select('rd.*')
|
|
|
+ ->get();
|
|
|
+
|
|
|
+ $usedItemIds = $ruleSet->pluck('item_id')->unique()->toArray();
|
|
|
+ $itemMap = DB::table('item')
|
|
|
+ ->whereIn('id', $usedItemIds)
|
|
|
+ ->pluck('title', 'id')
|
|
|
+ ->toArray();
|
|
|
+
|
|
|
+ $ruleSetGrouped = $ruleSet->groupBy('data_id');
|
|
|
+
|
|
|
+ // 标准班次 & 日历
|
|
|
+ $standardWorkRanges = DB::table('work_range_details')
|
|
|
+ ->where('top_depart_id', $topDepartId)
|
|
|
+ ->where('del_time', 0)
|
|
|
+ ->get();
|
|
|
+ $dayMaxAvail = (int)$standardWorkRanges->sum('total_work_min');
|
|
|
+
|
|
|
+ $workDays = DB::table('calendar_details')
|
|
|
+ ->where('top_depart_id', $topDepartId)
|
|
|
+ ->where('month', $monthStart)
|
|
|
+ ->where('is_work', 1)
|
|
|
+ ->where('del_time', 0)
|
|
|
+ ->orderBy('time', 'asc')->get();
|
|
|
+
|
|
|
+ if ($workDays->isEmpty()) return ['status' => false, 'msg' => '未配置工作日历'];
|
|
|
+
|
|
|
+ // --- 2. 阶段一:计算每天每台设备分配的项目分钟数 (保持原逻辑) ---
|
|
|
+ $finalAlloc = [];
|
|
|
+ foreach ($monthlyOrder as $mDetail) {
|
|
|
+ $deviceId = $mDetail->device_id;
|
|
|
+ $deviceRules = $ruleSetGrouped->get($deviceId);
|
|
|
+ if (!$deviceRules) continue;
|
|
|
+
|
|
|
+ $remainingMin = (int)round((float)$mDetail->rd_total_hours * 60);
|
|
|
+ if ($remainingMin <= 0) continue;
|
|
|
+
|
|
|
+ foreach ($workDays as $dayInfo) {
|
|
|
+ if ($remainingMin <= 0) break;
|
|
|
+
|
|
|
+ $canAllocToday = min($remainingMin, $dayMaxAvail);
|
|
|
+ $allocatedInDay = 0;
|
|
|
+ $ruleCount = count($deviceRules);
|
|
|
+
|
|
|
+ foreach ($deviceRules 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[$dayInfo->time][$rule->item_id][$deviceId] = $projectMin;
|
|
|
+ $allocatedInDay += $projectMin;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ $remainingMin -= $canAllocToday;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // --- 3. 阶段二:生成预览行并对齐 30 分钟 ---
|
|
|
+ $previewList = [];
|
|
|
+ $dailyDevicePools = [];
|
|
|
+ $tempMainIdCounter = 1;
|
|
|
+ $step = 30; // 步长
|
|
|
+
|
|
|
+ foreach ($finalAlloc as $dayTs => $projects) {
|
|
|
+ foreach ($projects as $itemId => $devices) {
|
|
|
+
|
|
|
+ $currentTempMainId = $tempMainIdCounter++;
|
|
|
+ $itemTitle = $itemMap[$itemId] ?? '未知项目';
|
|
|
+
|
|
|
+ foreach ($devices as $deviceId => $toAllocMin) {
|
|
|
+ if (!isset($dailyDevicePools[$dayTs][$deviceId])) {
|
|
|
+ $pool = [];
|
|
|
+ foreach ($standardWorkRanges as $swr) {
|
|
|
+ $pool[] = [
|
|
|
+ 's' => (int)($swr->start_time_hour * 60 + $swr->start_time_min),
|
|
|
+ 'e' => (int)($swr->end_time_hour * 60 + $swr->end_time_min)
|
|
|
+ ];
|
|
|
+ }
|
|
|
+ $dailyDevicePools[$dayTs][$deviceId] = $pool;
|
|
|
+ }
|
|
|
+
|
|
|
+ $tempRem = (int)$toAllocMin;
|
|
|
+ foreach ($dailyDevicePools[$dayTs][$deviceId] as &$p) {
|
|
|
+ if ($tempRem <= 0) break;
|
|
|
+
|
|
|
+ // 【改动】起始点对齐:非30分钟整点则向上对齐
|
|
|
+ if ($p['s'] % $step != 0) {
|
|
|
+ $p['s'] = ceil($p['s'] / $step) * $step;
|
|
|
+ }
|
|
|
+
|
|
|
+ $pMax = (int)($p['e'] - $p['s']);
|
|
|
+ if ($pMax <= 0) continue;
|
|
|
+
|
|
|
+ // 【改动】取量对齐逻辑
|
|
|
+ if ($tempRem >= $step) {
|
|
|
+ // 尽量按步长分配
|
|
|
+ $take = min(floor($tempRem / $step) * $step, floor($pMax / $step) * $step);
|
|
|
+ if ($take <= 0) continue;
|
|
|
+ } else {
|
|
|
+ // 最后一丁点零头
|
|
|
+ $take = min($tempRem, $pMax);
|
|
|
+ }
|
|
|
+
|
|
|
+ $start = (int)$p['s'];
|
|
|
+ $end = $start + $take;
|
|
|
+
|
|
|
+ $previewList[] = [
|
|
|
+ 'temp_main_id' => $currentTempMainId,
|
|
|
+ 'order_time' => date('Y-m-d', $dayTs),
|
|
|
+ 'order_timestamp' => $dayTs,
|
|
|
+ 'item_id' => $itemId,
|
|
|
+ 'item_title' => $itemTitle,
|
|
|
+ 'device_id' => $deviceId,
|
|
|
+ 'device_title' => $deviceMap[$deviceId] ?? '未知设备',
|
|
|
+ 'start_time' => sprintf('%02d:%02d', floor($start / 60), $start % 60),
|
|
|
+ 'end_time' => sprintf('%02d:%02d', floor($end / 60), $end % 60),
|
|
|
+ 'start_hour' => (int)floor($start / 60),
|
|
|
+ 'start_min' => (int)($start % 60),
|
|
|
+ 'end_hour' => (int)floor($end / 60),
|
|
|
+ 'end_min' => (int)($end % 60),
|
|
|
+ 'total_work_min' => $take,
|
|
|
+ ];
|
|
|
+
|
|
|
+ $tempRem -= $take;
|
|
|
+ $p['s'] = $end;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return ['status' => true, 'data' => $previewList];
|
|
|
+ }
|
|
|
+
|
|
|
public function dailyDwOrderSave($data, $user)
|
|
|
{
|
|
|
$list = $data['list'] ?? [];
|