|
|
@@ -6,16 +6,23 @@ use App\Model\DailyDwOrder;
|
|
|
use App\Model\DailyDwOrderDetails;
|
|
|
use App\Model\DailyPwOrder;
|
|
|
use App\Model\DailyPwOrderDetails;
|
|
|
+use App\Model\Employee;
|
|
|
use App\Model\ExpenseClaims;
|
|
|
use App\Model\ExpenseClaimsDetails;
|
|
|
use App\Model\Fee;
|
|
|
use App\Model\Item;
|
|
|
+use App\Model\ItemNode;
|
|
|
+use App\Model\ItemNodeMission;
|
|
|
+use App\Model\ItemNodeMissionContent;
|
|
|
use App\Model\MonthlyDdOrder;
|
|
|
use App\Model\MonthlyDdOrderDetails;
|
|
|
use App\Model\MonthlyPsOrder;
|
|
|
use App\Model\MonthlyPsOrderDetails;
|
|
|
use App\Model\PLeaveOverOrder;
|
|
|
use App\Model\PLeaveOverOrderDetails;
|
|
|
+use App\Model\WorkFlowInstances;
|
|
|
+use App\Model\WorkFlowInstancesNodes;
|
|
|
+use Carbon\Carbon;
|
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
|
|
class BIService extends Service
|
|
|
@@ -543,14 +550,508 @@ class BIService extends Service
|
|
|
}
|
|
|
|
|
|
public function homePageItemData($data, $user){
|
|
|
- // 项目相关查询
|
|
|
- $item = $this->ItemAboutSearch($data, $user);
|
|
|
+ $startOfMonth = Carbon::now()->startOfMonth()->timestamp;
|
|
|
+ $endOfMonth = Carbon::now()->endOfMonth()->timestamp;
|
|
|
+ $data['start_now_month'] = $startOfMonth;
|
|
|
+ $data['end_now_month'] = $endOfMonth;
|
|
|
+
|
|
|
+ // 进行中项目数量
|
|
|
+ $item_count = $this->ItemAboutSearch($data, $user);
|
|
|
+ // 进行中节点数量
|
|
|
+ $item_node_count = $this->ItemNodeAboutSearch($data, $user);
|
|
|
+ // 待审批数量(任务)
|
|
|
+ $item_node_mission_wait_count = $this->ItemNodeMissionWaitAboutSearch($data, $user);
|
|
|
+ // 超期任务数量 本月已完成任务数量 符合条件的当月任务
|
|
|
+ list($num1, $num2, $mission) = $this->ItemNodeMissionAboutSearch($data, $user);
|
|
|
+ // 🌟人效分析 人员工时对比(这里传入了当月筛选出的任务集)
|
|
|
+ $man_about = $this->ItemNodeMissionAboutManSearch($data, $user, $mission);
|
|
|
+ // 近六个月工时比较
|
|
|
+ $last_six_about = $this->ItemNodeMissionAboutLastSixMonthSearch($data, $user);
|
|
|
+ // 项目工时与人效排名
|
|
|
+ $item_man_about = $this->ItemManSearch($data, $user);
|
|
|
+
|
|
|
+ return [true, [
|
|
|
+ 'item_count' => $item_count,
|
|
|
+ 'item_node_count' => $item_node_count,
|
|
|
+ 'item_node_mission_wait_count' => $item_node_mission_wait_count,
|
|
|
+ 'cq_num' => $num1,
|
|
|
+ 'wc_num' => $num2,
|
|
|
+ 'rx' => $man_about['sub'],
|
|
|
+ 'employee_work_list' => $man_about['employee_work_list'],
|
|
|
+ 'last_six_month' => $last_six_about,
|
|
|
+ 'item_man_abount' => $item_man_about
|
|
|
+ ]];
|
|
|
}
|
|
|
|
|
|
private function ItemAboutSearch($data, $user){
|
|
|
- $model = Item::TopClear($user,$data);
|
|
|
- $model = $model->where('del_time',0)
|
|
|
- ->select('title','start_time','end_time','state','charge_id','')
|
|
|
- ->orderby('id', 'desc');
|
|
|
+ $model = Item::TopAndEmployeeClear($user, $data);
|
|
|
+ return $model->where('del_time', 0)
|
|
|
+ ->where('state', Item::TYPE_TWO)
|
|
|
+ ->where('start_time', '<=', $data['end_now_month'])
|
|
|
+ ->where('end_time', '>=', $data['start_now_month'])
|
|
|
+ ->count();
|
|
|
+ }
|
|
|
+
|
|
|
+ private function ItemNodeAboutSearch($data, $user){
|
|
|
+ $model = ItemNode::TopAndEmployeeClear($user, $data);
|
|
|
+ return $model->where('del_time', 0)
|
|
|
+ ->where('state', ItemNode::TYPE_TWO)
|
|
|
+ ->where('start_time', '<=', $data['end_now_month'])
|
|
|
+ ->where('end_time', '>=', $data['start_now_month'])
|
|
|
+// ->where('start_time', '<=', $data['start_now_month']) // 开始时间早于或等于当月结束
|
|
|
+// ->where('end_time', '>=', $data['end_now_month']) // 结束时间晚于或等于当月开始
|
|
|
+ ->count();
|
|
|
+ }
|
|
|
+
|
|
|
+ private function ItemNodeMissionWaitAboutSearch($data, $user){
|
|
|
+// return WorkFlowInstances::where('del_time', 0)
|
|
|
+// ->where('document_type', 'item_node_mission')
|
|
|
+// ->where('crt_time', '>=', $data['start_now_month'])
|
|
|
+// ->where('crt_time', '<=', $data['end_now_month'])
|
|
|
+// ->where('status', WorkFlowInstances::status_two)
|
|
|
+// ->where('top_depart_id', $user['top_depart_id'])
|
|
|
+// ->count();
|
|
|
+
|
|
|
+ return WorkFlowInstancesNodes::from('workflow_instance_nodes as nodes') // 给主表起个别名
|
|
|
+ ->join('workflow_instances as instances', 'nodes.instance_id', '=', 'instances.id') // 直接连表,注意:如果你的外键不是 instance_id,请在这里修改
|
|
|
+ ->where('nodes.del_time', 0)
|
|
|
+ ->where('nodes.top_depart_id', $user['top_depart_id'])
|
|
|
+ ->where('nodes.status', 1)
|
|
|
+ ->whereRaw('JSON_CONTAINS(nodes.assignees, JSON_OBJECT("id", ?))', [$user['id']])
|
|
|
+ ->whereRaw('NOT JSON_CONTAINS(nodes.assignees, JSON_OBJECT("id", ?, "is_pass", 1))', [$user['id']])
|
|
|
+ ->where('instances.del_time', 0)
|
|
|
+ ->where('instances.document_type', 'item_node_mission')
|
|
|
+ ->where('instances.crt_time', '>=', $data['start_now_month'])
|
|
|
+ ->where('instances.crt_time', '<=', $data['end_now_month'])
|
|
|
+ ->where('instances.status', 'pending')
|
|
|
+ ->count();
|
|
|
+ }
|
|
|
+
|
|
|
+ private function ItemNodeMissionAboutSearch($data, $user){
|
|
|
+ $model = ItemNodeMission::TopAndEmployeeClear($user,$data);
|
|
|
+
|
|
|
+ // 🌟 核心确保:不仅卡死当月区间,同时 select 出分摊所必需的 'start_time' 和 'end_time'
|
|
|
+ $list = $model->where('del_time',0)
|
|
|
+ ->where('start_time', '<=', $data['end_now_month'])
|
|
|
+ ->where('end_time', '>=', $data['start_now_month'])
|
|
|
+ ->select('state', 'end_time', 'id', 'due_work_hour', 'start_time')
|
|
|
+ ->get()->toArray();
|
|
|
+
|
|
|
+ $overdueCount = 0; // 已超期数量
|
|
|
+ $completedCount = 0; // 已完成数量
|
|
|
+ $now = time(); // 获取当前时间戳
|
|
|
+
|
|
|
+ foreach ($list as $item) {
|
|
|
+ if ($item['state'] == ItemNodeMission::TYPE_THREE) {
|
|
|
+ $completedCount++;
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($item['state'] == ItemNodeMission::TYPE_MINUS_TWO || $item['end_time'] < $now) {
|
|
|
+ $overdueCount++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return [
|
|
|
+ $overdueCount,
|
|
|
+ $completedCount,
|
|
|
+ $list
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ private function ItemNodeMissionAboutManSearch($data, $user, $mission){
|
|
|
+ $mission_id = array_column($mission, 'id');
|
|
|
+
|
|
|
+ // 🌟 严格限制:只查询当前月份内、且属于这批任务的实际工时单据 (防止把历史月份工时带入当前人员统计)
|
|
|
+ $day_work_list = ItemNodeMissionContent::where('del_time',0)
|
|
|
+ ->whereIn('item_node_mission_id', $mission_id)
|
|
|
+ ->where('order_time', '>=', $data['start_now_month'])
|
|
|
+ ->where('order_time', '<=', $data['end_now_month'])
|
|
|
+ ->select('data_id as employee_id', 'total_work_min', 'item_id', 'item_node_mission_id')
|
|
|
+ ->get()->toArray();
|
|
|
+
|
|
|
+ $emp_map = Employee::whereIn('id', array_unique(array_column($day_work_list,'employee_id')))
|
|
|
+ ->pluck('title','id')
|
|
|
+ ->toArray();
|
|
|
+
|
|
|
+ $total_work_minute = 0;
|
|
|
+ $employee_keys = [];
|
|
|
+
|
|
|
+ // 规整当前筛选月份的时间戳边界
|
|
|
+ $start_timestamp = is_numeric($data['start_now_month']) ? (int)$data['start_now_month'] : strtotime($data['start_now_month']);
|
|
|
+ $end_timestamp = is_numeric($data['end_now_month']) ? (int)$data['end_now_month'] : strtotime($data['end_now_month']);
|
|
|
+
|
|
|
+ // 1. 预先计算好每个任务在【查询当月】的生命周期交集与分摊预计工时
|
|
|
+ $yj_map = [];
|
|
|
+ foreach ($mission as $m) {
|
|
|
+ $m_id = $m['id'];
|
|
|
+
|
|
|
+ // 🌟 强力兼容:不论数据库存的是 Timestamp 还是 Y-m-d 字符串,一律规整为标准时间戳计算天数
|
|
|
+ $m_start = is_numeric($m['start_time']) ? (int)$m['start_time'] : strtotime($m['start_time']);
|
|
|
+ $m_end = is_numeric($m['end_time']) ? (int)$m['end_time'] : strtotime($m['end_time']);
|
|
|
+
|
|
|
+ if ($m_start && $m_end && $m_start <= $end_timestamp && $m_end >= $start_timestamp) {
|
|
|
+ // 计算任务总天数
|
|
|
+ $total_days = ceil(($m_end - $m_start) / 86400);
|
|
|
+ if ($total_days <= 0) $total_days = 1;
|
|
|
+
|
|
|
+ // 计算当月重叠区间
|
|
|
+ $overlap_start = max($m_start, $start_timestamp);
|
|
|
+ $overlap_end = min($m_end, $end_timestamp);
|
|
|
+ $overlap_days = ceil(($overlap_end - $overlap_start) / 86400);
|
|
|
+ if ($overlap_days < 0) $overlap_days = 0;
|
|
|
+
|
|
|
+ // 算出该任务在当月应该分摊到的工时
|
|
|
+ $yj_map[$m_id] = ($m['due_work_hour'] / $total_days) * $overlap_days;
|
|
|
+ } else {
|
|
|
+ $yj_map[$m_id] = 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ $employee_array = [];
|
|
|
+
|
|
|
+ // 2. 第一步:纯粹统计当月实际工时,并将任务 ID 归并到人头上
|
|
|
+ foreach ($day_work_list as $work) {
|
|
|
+ $emp_id = $work['employee_id'];
|
|
|
+ if (empty($emp_id)) continue;
|
|
|
+
|
|
|
+ $total_work_minute += $work['total_work_min'];
|
|
|
+ $employee_keys[$emp_id] = true;
|
|
|
+
|
|
|
+ if (isset($employee_array[$emp_id])) {
|
|
|
+ $employee_array[$emp_id]['actual_work'] += $work['total_work_min'];
|
|
|
+ if (!in_array($work['item_node_mission_id'], $employee_array[$emp_id]['mission_id'])) {
|
|
|
+ $employee_array[$emp_id]['mission_id'][] = $work['item_node_mission_id'];
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ $employee_array[$emp_id] = [
|
|
|
+ 'employee_id' => $emp_id,
|
|
|
+ 'employee_title' => $emp_map[$emp_id] ?? '',
|
|
|
+ 'actual_work' => $work['total_work_min'],
|
|
|
+ 'mission_id' => [$work['item_node_mission_id']],
|
|
|
+ 'yj_work' => 0, // 先占位
|
|
|
+ ];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 第二步:按人头独立参与的任务集合,精准分配分摊预计工时,同时记录已被触发的有效任务
|
|
|
+ $valid_mission_ids = [];
|
|
|
+ foreach ($employee_array as $emp_id => $emp_info) {
|
|
|
+ $emp_yj_work = 0;
|
|
|
+ foreach ($emp_info['mission_id'] as $m_id) {
|
|
|
+ $emp_yj_work += $yj_map[$m_id] ?? 0;
|
|
|
+ $valid_mission_ids[$m_id] = true; // 记录当月实际有人填报的任务
|
|
|
+ }
|
|
|
+
|
|
|
+ $employee_array[$emp_id]['actual_work'] = round($emp_info['actual_work'] / 60, 2);
|
|
|
+ $employee_array[$emp_id]['yj_work'] = round($emp_yj_work, 2);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 根据实际工时降序
|
|
|
+ usort($employee_array, function($a, $b) {
|
|
|
+ return $b['actual_work'] <=> $a['actual_work'];
|
|
|
+ });
|
|
|
+
|
|
|
+ $total_work_hours = round($total_work_minute / 60, 2);
|
|
|
+ $employee_count = count($employee_keys);
|
|
|
+
|
|
|
+ // 🌟 核心对齐:总预计工时【只计算当前出现在有效人员列表里的分摊工时总和】,确保和大盘数据指标完全对齐闭环
|
|
|
+ $total_yj_work_hours = 0;
|
|
|
+ foreach (array_keys($valid_mission_ids) as $m_id) {
|
|
|
+ $total_yj_work_hours += $yj_map[$m_id] ?? 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ $num = '0.00';
|
|
|
+ $num_yj = '0.00';
|
|
|
+ $num_rate = '0.00';
|
|
|
+ $num_rate_2 = '0.00';
|
|
|
+ $num_rate_3 = '0.00';
|
|
|
+ $num_rate_4 = '0.00';
|
|
|
+ $num_rate_5 = '0.00';
|
|
|
+
|
|
|
+ if ($employee_count > 0) {
|
|
|
+ $num = bcdiv($total_work_hours, $employee_count, 2);
|
|
|
+ $num_yj = bcdiv($total_yj_work_hours, $employee_count, 2);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (bccomp($num_yj, '0.00', 2) > 0) {
|
|
|
+ $num_rate = bcmul(bcdiv($num, $num_yj, 4), 100, 2);
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($total_yj_work_hours > 0 && $employee_count > 0) {
|
|
|
+ $num_rate_2 = bcmul(bcdiv($total_work_hours, bcmul($total_yj_work_hours, $employee_count, 4), 4), 100, 2);
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($total_yj_work_hours > 0) {
|
|
|
+ $num_rate_3 = bcmul(bcdiv($total_work_hours, $total_yj_work_hours, 4), 100, 2);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 成本人效比计算保持原有逻辑
|
|
|
+ $total = Item::where('del_time', 0)->whereIn('id', array_unique(array_column($day_work_list, 'item_id')))->sum('budget');
|
|
|
+ $monthly_ps_order = MonthlyPsOrder::Clear($user, $data)->where('del_time', 0)->where("month", ">=", $data['start_now_month'])
|
|
|
+ ->where("month", "<", $data['end_now_month'])->pluck('id')->toArray();
|
|
|
+ $month_employee_salary = MonthlyPsOrderDetails::whereIn('main_id', $monthly_ps_order)->select("base_salary", "performance_salary", "other", "bonus", "main_id")->get()->toArray();
|
|
|
+
|
|
|
+ $man_total = 0;
|
|
|
+ foreach ($month_employee_salary as $value) {
|
|
|
+ $currentAmount = bcadd($value['base_salary'], $value['performance_salary'], 3);
|
|
|
+ $currentAmount = bcadd($currentAmount, $value['other'], 3);
|
|
|
+ $currentAmount = bcadd($currentAmount, $value['bonus'], 3);
|
|
|
+ $man_total = bcadd($man_total, $currentAmount, 3);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (bccomp($man_total, '0.000', 3) > 0) {
|
|
|
+ $num_rate_4 = bcmul(bcdiv($total, $man_total, 4), 100, 2);
|
|
|
+ $num_rate_5 = bcmul(bcdiv(bcdiv($total, $man_total, 4), 4, 4), 100, 2);
|
|
|
+ }
|
|
|
+
|
|
|
+ $man_final = round((float)$num_rate_3 * 0.6 + (float)$num_rate_4 * 0.4, 2);
|
|
|
+
|
|
|
+ return [
|
|
|
+ 'employee_work_list' => $employee_array,
|
|
|
+ 'sub' => [
|
|
|
+ 'average_actual_hours' => $num,
|
|
|
+ 'average_du_hours' => $num_yj,
|
|
|
+ 'average_actual_hours_rate' => $num_rate,
|
|
|
+ 'load_rate' => $num_rate_2,
|
|
|
+ 'achievement_rate' => $num_rate_3,
|
|
|
+ 'man_rate' => $num_rate_4,
|
|
|
+ 'man_rate_2' => $num_rate_5,
|
|
|
+ 'man_final' => $man_final
|
|
|
+ ]
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ private function ItemNodeMissionAboutLastSixMonthSearch($data, $user)
|
|
|
+ {
|
|
|
+ // 1. 先计算出近 6 个月的大总区间(最远月份的月初 到 当月的月末)
|
|
|
+ $oldestMonthStart = date('Y-m-01 00:00:00', strtotime("-5 month"));
|
|
|
+ $currentMonthEnd = date('Y-m-t 23:59:59'); // 当月月末
|
|
|
+
|
|
|
+ $all_start_timestamp = strtotime($oldestMonthStart);
|
|
|
+ $all_end_timestamp = strtotime($currentMonthEnd);
|
|
|
+
|
|
|
+ // 2. 一次性捞出近 6 个月内所有可能相交的任务
|
|
|
+ $mission_model = ItemNodeMission::TopAndEmployeeClear($user, $data);
|
|
|
+ $all_missions = $mission_model->where('del_time', 0)
|
|
|
+ ->where('start_time', '<=', $all_end_timestamp)
|
|
|
+ ->where('end_time', '>=', $all_start_timestamp)
|
|
|
+ ->select('id', 'due_work_hour', 'start_time', 'end_time')
|
|
|
+ ->get()
|
|
|
+ ->toArray();
|
|
|
+
|
|
|
+ $all_mission_ids = array_column($all_missions, 'id');
|
|
|
+
|
|
|
+ // 3. 一次性查出这些任务所有的实际工时记录(把 crt_time 换成你表里的 order_time)
|
|
|
+ $all_work_contents = [];
|
|
|
+ if (!empty($all_mission_ids)) {
|
|
|
+ $all_work_contents = ItemNodeMissionContent::where('del_time', 0)
|
|
|
+ ->whereIn('item_node_mission_id', $all_mission_ids)
|
|
|
+ ->select('item_node_mission_id', 'total_work_min', 'order_time') // 👈 查出单据日期
|
|
|
+ ->get()
|
|
|
+ ->toArray();
|
|
|
+ }
|
|
|
+
|
|
|
+ $result = [];
|
|
|
+
|
|
|
+ // 4. 以时间为驱动循环 6 次
|
|
|
+ for ($i = 5; $i >= 0; $i--) {
|
|
|
+ $monthStart = date('Y-m-01 00:00:00', strtotime("-$i month"));
|
|
|
+ $monthEnd = date('Y-m-t 23:59:59', strtotime("-$i month"));
|
|
|
+
|
|
|
+ $start_timestamp = strtotime($monthStart);
|
|
|
+ $end_timestamp = strtotime($monthEnd);
|
|
|
+ $month_name = date('Y-m', $start_timestamp);
|
|
|
+
|
|
|
+ $month_due_hours = 0;
|
|
|
+ $month_actual_mins = 0;
|
|
|
+
|
|
|
+ // 5. 计算预计工时:按任务生命周期交集 + 天数均摊
|
|
|
+ if (!empty($all_missions)) {
|
|
|
+ foreach ($all_missions as $mission) {
|
|
|
+ // 判断当前任务是否与当前月份有交集
|
|
|
+ if ($mission['start_time'] <= $end_timestamp && $mission['end_time'] >= $start_timestamp) {
|
|
|
+
|
|
|
+ // A. 计算任务总生命周期的总天数
|
|
|
+ $total_days = ceil(($mission['end_time'] - $mission['start_time']) / 86400);
|
|
|
+ if ($total_days <= 0) $total_days = 1;
|
|
|
+
|
|
|
+ // B. 计算当前月份与该任务的【重叠区间】
|
|
|
+ $overlap_start = max($mission['start_time'], $start_timestamp);
|
|
|
+ $overlap_end = min($mission['end_time'], $end_timestamp);
|
|
|
+
|
|
|
+ // C. 计算这个月里,该任务占用了多少天
|
|
|
+ $overlap_days = ceil(($overlap_end - $overlap_start) / 86400);
|
|
|
+ if ($overlap_days < 0) $overlap_days = 0;
|
|
|
+
|
|
|
+ // D. 按比例分摊预计工时,并累加到当前月份
|
|
|
+ $share_due_hour = ($mission['due_work_hour'] / $total_days) * $overlap_days;
|
|
|
+ $month_due_hours += $share_due_hour;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 6. ✨ 计算实际工时:严格根据单据日期 (order_time) 划归到具体的自然月
|
|
|
+ if (!empty($all_work_contents)) {
|
|
|
+ foreach ($all_work_contents as $work) {
|
|
|
+ // 只有当工时记录的单据日期在当前月份区间内,才计入该月
|
|
|
+ if ($work['order_time'] >= $start_timestamp && $work['order_time'] <= $end_timestamp) {
|
|
|
+ $month_actual_mins += $work['total_work_min'];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 7. 组装当前月份的数据结果
|
|
|
+ $result[] = [
|
|
|
+ 'month' => $month_name,
|
|
|
+ 'total_due_hours' => round($month_due_hours, 2),
|
|
|
+ 'total_actual_hours' => round($month_actual_mins / 60, 2)
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ return $result;
|
|
|
+ }
|
|
|
+
|
|
|
+ private function ItemManSearch($data, $user)
|
|
|
+ {
|
|
|
+ // 1. 获取当月进行中的项目
|
|
|
+ $model = Item::TopAndEmployeeClear($user, $data);
|
|
|
+ $item_list = $model->where('del_time', 0)
|
|
|
+ ->whereIn('state', [Item::TYPE_TWO, Item::TYPE_THREE])
|
|
|
+ ->where('start_time', '<=', $data['end_now_month'])
|
|
|
+ ->where('end_time', '>=', $data['start_now_month'])
|
|
|
+ ->select('id', 'title', 'budget') // 👈 增加选出项目总金额 budget 用于计算产值/成本
|
|
|
+ ->get()->toArray();
|
|
|
+
|
|
|
+ if (empty($item_list)) {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+
|
|
|
+ $item_ids = array_column($item_list, 'id');
|
|
|
+ $start_timestamp = (int)$data['start_now_month'];
|
|
|
+ $end_timestamp = (int)$data['end_now_month'];
|
|
|
+
|
|
|
+ // ==========================================
|
|
|
+ // 🛠️ 核心步骤一:计算本月全员总实际工时与总工资
|
|
|
+ // ==========================================
|
|
|
+ // A. 本月全员总实际工时 (分钟)
|
|
|
+ $all_employee_actual_mins = ItemNodeMissionContent::where('del_time', 0)
|
|
|
+ ->where('order_time', '>=', $start_timestamp)
|
|
|
+ ->where('order_time', '<=', $end_timestamp)
|
|
|
+ ->sum('total_work_min');
|
|
|
+ $all_employee_actual_hours = round($all_employee_actual_mins / 60, 2);
|
|
|
+
|
|
|
+ // B. 本月全员总工资额
|
|
|
+ $monthly_ps_order = MonthlyPsOrder::Clear($user, $data)->where('del_time', 0)
|
|
|
+ ->where("month", ">=", $data['start_now_month'])
|
|
|
+ ->where("month", "<", $data['end_now_month'])
|
|
|
+ ->pluck('id')->toArray();
|
|
|
+ $month_employee_salary = MonthlyPsOrderDetails::whereIn('main_id', $monthly_ps_order)
|
|
|
+ ->select("base_salary", "performance_salary", "other", "bonus")
|
|
|
+ ->get()->toArray();
|
|
|
+
|
|
|
+ $total_salary = 0;
|
|
|
+ foreach ($month_employee_salary as $value) {
|
|
|
+ $currentAmount = bcadd($value['base_salary'], $value['performance_salary'], 3);
|
|
|
+ $currentAmount = bcadd($currentAmount, $value['other'], 3);
|
|
|
+ $currentAmount = bcadd($currentAmount, $value['bonus'], 3);
|
|
|
+ $total_salary = bcadd($total_salary, $currentAmount, 3);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==========================================
|
|
|
+ // 🛠️ 核心步骤二:获取项目下当月的任务并均摊预计工时
|
|
|
+ // ==========================================
|
|
|
+ // 获取这批项目下的所有任务
|
|
|
+ $missions = ItemNodeMission::where('del_time', 0)
|
|
|
+ ->whereIn('item_id', $item_ids)
|
|
|
+ ->where('start_time', '<=', $end_timestamp)
|
|
|
+ ->where('end_time', '>=', $start_timestamp)
|
|
|
+ ->select('id', 'item_id', 'due_work_hour', 'start_time', 'end_time')
|
|
|
+ ->get()->toArray();
|
|
|
+
|
|
|
+ // 按项目归总分摊后的预计工时
|
|
|
+ $item_yj_work_map = [];
|
|
|
+ foreach ($missions as $m) {
|
|
|
+ $proj_id = $m['item_id'];
|
|
|
+ $m_start = is_numeric($m['start_time']) ? (int)$m['start_time'] : strtotime($m['start_time']);
|
|
|
+ $m_end = is_numeric($m['end_time']) ? (int)$m['end_time'] : strtotime($m['end_time']);
|
|
|
+
|
|
|
+ if ($m_start && $m_end && $m_start <= $end_timestamp && $m_end >= $start_timestamp) {
|
|
|
+ // 任务总寿命天数
|
|
|
+ $total_days = ceil(($m_end - $m_start) / 86400);
|
|
|
+ if ($total_days <= 0) $total_days = 1;
|
|
|
+
|
|
|
+ // 当月重叠天数
|
|
|
+ $overlap_start = max($m_start, $start_timestamp);
|
|
|
+ $overlap_end = min($m_end, $end_timestamp);
|
|
|
+ $overlap_days = ceil(($overlap_end - $overlap_start) / 86400);
|
|
|
+ if ($overlap_days < 0) $overlap_days = 0;
|
|
|
+
|
|
|
+ // 分摊到当月的预计工时
|
|
|
+ $share_yj_hour = ($m['due_work_hour'] / $total_days) * $overlap_days;
|
|
|
+
|
|
|
+ if (!isset($item_yj_work_map[$proj_id])) {
|
|
|
+ $item_yj_work_map[$proj_id] = 0;
|
|
|
+ }
|
|
|
+ $item_yj_work_map[$proj_id] += $share_yj_hour;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==========================================
|
|
|
+ // 🛠️ 核心步骤三:获取项目下当月的实际工时
|
|
|
+ // ==========================================
|
|
|
+ $work_contents = ItemNodeMissionContent::where('del_time', 0)
|
|
|
+ ->whereIn('item_id', $item_ids)
|
|
|
+ ->where('order_time', '>=', $start_timestamp)
|
|
|
+ ->where('order_time', '<=', $end_timestamp)
|
|
|
+ ->select('item_id', 'total_work_min')
|
|
|
+ ->get()->toArray();
|
|
|
+
|
|
|
+ // 按项目归总实际工时(分钟转换为小时)
|
|
|
+ $item_actual_work_map = [];
|
|
|
+ foreach ($work_contents as $work) {
|
|
|
+ $proj_id = $work['item_id'];
|
|
|
+ if (!isset($item_actual_work_map[$proj_id])) {
|
|
|
+ $item_actual_work_map[$proj_id] = 0;
|
|
|
+ }
|
|
|
+ $item_actual_work_map[$proj_id] += $work['total_work_min'];
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==========================================
|
|
|
+ // 🛠️ 核心步骤四:组装数据并计算各项人效指标
|
|
|
+ // ==========================================
|
|
|
+ foreach ($item_list as &$item) {
|
|
|
+ $id = $item['id'];
|
|
|
+
|
|
|
+ // 1. 预计工时与实际工时
|
|
|
+ $yj_work = isset($item_yj_work_map[$id]) ? round($item_yj_work_map[$id], 2) : 0.00;
|
|
|
+ $actual_work = isset($item_actual_work_map[$id]) ? round($item_actual_work_map[$id] / 60, 2) : 0.00;
|
|
|
+
|
|
|
+ $item['yj_work'] = $yj_work;
|
|
|
+ $item['actual_work'] = $actual_work;
|
|
|
+
|
|
|
+ // 2. 计算工时达成率 = 实际 ÷ 预计 × 100%
|
|
|
+ $item['achievement_rate'] = '0.00';
|
|
|
+ if ($yj_work > 0) {
|
|
|
+ $item['achievement_rate'] = bcmul(bcdiv($actual_work, $yj_work, 4), 100, 2);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 计算项目人力成本 = 项目实际工时 × (本月工资总额 ÷ 本月全员实际工时)
|
|
|
+ $item_cost = '0.00';
|
|
|
+ if ($all_employee_actual_hours > 0) {
|
|
|
+ // 平均时薪 = 工资总额 ÷ 全员实际工时
|
|
|
+ $hourly_wage = bcdiv($total_salary, $all_employee_actual_hours, 4);
|
|
|
+ // 项目人力成本 = 项目实际工时 × 平均时薪
|
|
|
+ $item_cost = bcmul($actual_work, $hourly_wage, 2);
|
|
|
+ }
|
|
|
+ $item['item_cost'] = $item_cost;
|
|
|
+
|
|
|
+ // 4. 计算产值/成本 = 项目总金额 ÷ 项目人力成本
|
|
|
+ $item['production_cost_rate'] = '0.00';
|
|
|
+ if (bccomp($item_cost, '0.00', 2) > 0) {
|
|
|
+ $item['production_cost_rate'] = bcdiv($item['budget'], $item_cost, 2);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return $item_list;
|
|
|
}
|
|
|
}
|