|
|
@@ -97,82 +97,6 @@ class StatisticService extends StatisticCommonService
|
|
|
return [true, $month_employee_list];
|
|
|
}
|
|
|
|
|
|
- public function employeeMonthSalaryStatistic3($data, $user)
|
|
|
- {
|
|
|
- list($status, $month_start, $month_end) = $this->commonRule($data);
|
|
|
- if (!$status) return [false, $month_start];
|
|
|
-
|
|
|
- // 1. 获取基础工时数据 (人+项目+月)
|
|
|
- $month_employee_list = DailyPwOrderDetails::Clear($user, $data)
|
|
|
- ->where("order_time", ">=", $month_start)->where("order_time", "<", $month_end)->where('del_time', 0)
|
|
|
- ->select("item_id", "employee_id", DB::raw("FROM_UNIXTIME(order_time, '%Y-%m') as order_month"), DB::raw("SUM(total_work_min) as total_work"))
|
|
|
- ->groupBy("order_month", "item_id", "employee_id")->get();
|
|
|
-
|
|
|
- // 2. 获取工资及配置
|
|
|
- $ps_query = MonthlyPsOrder::Clear($user, $data)->where('del_time', 0)->where("month", ">=", $month_start)->where("month", "<", $month_end);
|
|
|
- $monthly_ps_order_ids = $ps_query->pluck('id')->toArray();
|
|
|
- $month_map = $ps_query->select('id', DB::raw("FROM_UNIXTIME(month, '%Y-%m') as month_str"))->pluck('month_str', 'id')->toArray();
|
|
|
-
|
|
|
- // 3. 构建人员月工资 Map (单位:分)
|
|
|
- $salary_details = MonthlyPsOrderDetails::whereIn('main_id', $monthly_ps_order_ids)
|
|
|
- ->select("employee_id", "main_id", DB::raw("(base_salary + performance_salary + bonus + other) as s_orig"))->get();
|
|
|
-
|
|
|
- $salary_map = [];
|
|
|
- foreach ($salary_details as $val) {
|
|
|
- $m = $month_map[$val['main_id']] ?? '';
|
|
|
- if ($m) {
|
|
|
- $key = $val['employee_id'] . '_' . $m;
|
|
|
- $salary_map[$key] = ($salary_map[$key] ?? 0) + (int)round($val['s_orig'] * 100);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 4. 计算每人每月总工时及项目数(平账用)
|
|
|
- $emp_total_min = [];
|
|
|
- $emp_item_count = [];
|
|
|
- foreach ($month_employee_list as $row) {
|
|
|
- $k = $row->employee_id . '_' . $row->order_month;
|
|
|
- $emp_total_min[$k] = ($emp_total_min[$k] ?? 0) + $row->total_work;
|
|
|
- $emp_item_count[$k] = ($emp_item_count[$k] ?? 0) + 1;
|
|
|
- }
|
|
|
-
|
|
|
- // 5. 按“人+项目”分摊并按“项目”汇总
|
|
|
- $rem_salary_pool = $salary_map;
|
|
|
- $project_agg = [];
|
|
|
- foreach ($month_employee_list as $item) {
|
|
|
- $k = $item->employee_id . '_' . $item->order_month;
|
|
|
- $total_s = $salary_map[$k] ?? 0;
|
|
|
- $total_m = $emp_total_min[$k] ?? 0;
|
|
|
-
|
|
|
- if ($total_m > 0 && --$emp_item_count[$k] > 0) {
|
|
|
- $allocated = (int)floor(($item->total_work / $total_m) * $total_s);
|
|
|
- $rem_salary_pool[$k] -= $allocated;
|
|
|
- } else {
|
|
|
- $allocated = $rem_salary_pool[$k] ?? 0;
|
|
|
- }
|
|
|
-
|
|
|
- $aggKey = $item->order_month . '_' . $item->item_id;
|
|
|
- if (!isset($project_agg[$aggKey])) {
|
|
|
- $project_agg[$aggKey] = ['month' => $item->order_month, 'item_id' => $item->item_id, 's_cents' => 0, 'mins' => 0];
|
|
|
- }
|
|
|
- $project_agg[$aggKey]['s_cents'] += $allocated;
|
|
|
- $project_agg[$aggKey]['mins'] += $item->total_work;
|
|
|
- }
|
|
|
-
|
|
|
- // 6. 填充项目信息
|
|
|
- $item_ids = collect($project_agg)->pluck('item_id')->unique()->all();
|
|
|
- $item_info = Item::TopClear($user, $data)->whereIn('id', $item_ids)->get()->keyBy('id');
|
|
|
-
|
|
|
- $result = collect($project_agg)->sortBy('month')->transform(function ($v) use ($item_info) {
|
|
|
- $v['item_title'] = $item_info[$v['item_id']]->title ?? "未知({$v['item_id']})";
|
|
|
- $v['item_code'] = $item_info[$v['item_id']]->code ?? "-";
|
|
|
- $v['allocated_salary'] = round($v['s_cents'] / 100, 2);
|
|
|
- $v['days'] = (int)round($v['mins'] / 60 / 8);
|
|
|
- return $v;
|
|
|
- })->values()->all();
|
|
|
-
|
|
|
- return [true, $result];
|
|
|
- }
|
|
|
-
|
|
|
public function itemDaySalaryStatistic($data, $user)
|
|
|
{
|
|
|
list($status, $month_start, $month_end) = $this->commonRule($data);
|
|
|
@@ -254,11 +178,11 @@ class StatisticService extends StatisticCommonService
|
|
|
|
|
|
public function employeeMonthSalaryStatistic($data, $user)
|
|
|
{
|
|
|
- // 1. 基础规则验证与时间范围获取
|
|
|
+ // 1. 基础规则验证
|
|
|
list($status, $month_start, $month_end) = $this->commonRule($data);
|
|
|
if (!$status) return [false, $month_start];
|
|
|
|
|
|
- // 2. 查询员工工时明细 (按月、项目、员工聚合)
|
|
|
+ // 2. 获取所有工时明细(按月、项目、人聚合)
|
|
|
$month_employee_list = DailyPwOrderDetails::Clear($user, $data)
|
|
|
->where("order_time", ">=", $month_start)
|
|
|
->where("order_time", "<", $month_end)
|
|
|
@@ -270,111 +194,97 @@ class StatisticService extends StatisticCommonService
|
|
|
DB::raw("SUM(total_work_min) as total_work")
|
|
|
)
|
|
|
->groupBy("order_month", "item_id", "employee_id")
|
|
|
- ->get()->toArray();
|
|
|
+ ->get();
|
|
|
|
|
|
- // 3. 查询工资主表 (为了获取月份字符串映射)
|
|
|
- $monthly_ps_order_query = MonthlyPsOrder::Clear($user, $data)
|
|
|
+ if ($month_employee_list->isEmpty()) {
|
|
|
+ return [true, []];
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 获取工资数据
|
|
|
+ $ps_query = MonthlyPsOrder::Clear($user, $data)
|
|
|
->where('del_time', 0)
|
|
|
->where("month", ">=", $month_start)
|
|
|
->where("month", "<", $month_end);
|
|
|
|
|
|
- $monthly_ps_order_ids = $monthly_ps_order_query->pluck('id')->toArray();
|
|
|
- $monthly_ps_order_key_list = $monthly_ps_order_query
|
|
|
- ->select('id', DB::raw("FROM_UNIXTIME(month, '%Y-%m') as month_str"))
|
|
|
- ->pluck('month_str', 'id')
|
|
|
- ->toArray();
|
|
|
-
|
|
|
- // 4. 查询工资详情 (计算每人在每个月的分摊基数)
|
|
|
- $month_employee_salary = MonthlyPsOrderDetails::whereIn('main_id', $monthly_ps_order_ids)
|
|
|
- ->select("employee_id", DB::raw("(base_salary + performance_salary + bonus + other) as salary"), "main_id")
|
|
|
- ->get()->toArray();
|
|
|
+ $monthly_ps_order_ids = $ps_query->pluck('id')->toArray();
|
|
|
+ $month_map = $ps_query->select('id', DB::raw("FROM_UNIXTIME(month, '%Y-%m') as month_str"))
|
|
|
+ ->pluck('month_str', 'id')->toArray();
|
|
|
|
|
|
- // 汇总:员工工资映射表 及 每月总工资池
|
|
|
- $salary_map = []; // [employee_id_month => salary]
|
|
|
- $all_salary_pool = []; // [month => total_salary_of_month]
|
|
|
- foreach ($month_employee_salary as $val) {
|
|
|
- $month_str = $monthly_ps_order_key_list[$val['main_id']] ?? '';
|
|
|
- if ($month_str) {
|
|
|
- $s_key = $val['employee_id'] . '_' . $month_str;
|
|
|
- $salary_map[$s_key] = $val['salary'];
|
|
|
+ $salary_details = MonthlyPsOrderDetails::whereIn('main_id', $monthly_ps_order_ids)
|
|
|
+ ->select("employee_id", "main_id", DB::raw("(base_salary + performance_salary + bonus + other) as s_orig"))
|
|
|
+ ->get();
|
|
|
|
|
|
- if (!isset($all_salary_pool[$month_str])) $all_salary_pool[$month_str] = 0;
|
|
|
- $all_salary_pool[$month_str] = round($all_salary_pool[$month_str] + $val['salary'], 2);
|
|
|
+ // 4. 建立工资映射(统一使用分单位,避免浮点误差)
|
|
|
+ $salary_map = [];
|
|
|
+ foreach ($salary_details as $val) {
|
|
|
+ $m = $month_map[$val['main_id']] ?? '';
|
|
|
+ if ($m) {
|
|
|
+ $k = $val['employee_id'] . '_' . $m;
|
|
|
+ // 采用与 itemDaySalaryStatistic 一致的整数处理
|
|
|
+ $salary_map[$k] = ($salary_map[$k] ?? 0) + (int)round($val['s_orig'] * 100);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // 5. 计算员工全月总工时 (用于算比例)
|
|
|
- $employee_monthly_total_min = [];
|
|
|
+ // 5. 统计每人每月总工时和参与的项目数(用于精准分摊尾差)
|
|
|
+ $emp_total_min = [];
|
|
|
+ $emp_item_count = [];
|
|
|
foreach ($month_employee_list as $row) {
|
|
|
- $key = $row['employee_id'] . '_' . $row['order_month'];
|
|
|
- if (!isset($employee_monthly_total_min[$key])) $employee_monthly_total_min[$key] = 0;
|
|
|
- $employee_monthly_total_min[$key] += $row['total_work'];
|
|
|
+ $k = $row->employee_id . '_' . $row->order_month;
|
|
|
+ $emp_total_min[$k] = ($emp_total_min[$k] ?? 0) + $row->total_work;
|
|
|
+ $emp_item_count[$k] = ($emp_item_count[$k] ?? 0) + 1;
|
|
|
}
|
|
|
|
|
|
- // 6. 按项目分摊 (初步计算,不四舍五入)
|
|
|
- $item_month_list = [];
|
|
|
+ // 6. 执行分摊逻辑并按项目汇总
|
|
|
+ $item_summary = []; // 存放项目维度的汇总结果
|
|
|
+ $rem_salary_pool = $salary_map; // 余额池,按人+月控制
|
|
|
+
|
|
|
foreach ($month_employee_list as $item) {
|
|
|
- $e_key = $item['employee_id'] . '_' . $item['order_month'];
|
|
|
- $item_key = $item['order_month'] . '_' . $item['item_id'];
|
|
|
+ $k = $item->employee_id . '_' . $item->order_month;
|
|
|
+ $total_s = $salary_map[$k] ?? 0;
|
|
|
+ $total_m = $emp_total_min[$k] ?? 0;
|
|
|
|
|
|
- if (!isset($item_month_list[$item_key])) {
|
|
|
- $item_month_list[$item_key] = [
|
|
|
- "month" => $item['order_month'],
|
|
|
- "item_id" => $item['item_id'],
|
|
|
- "allocated_salary" => 0,
|
|
|
- "work_minutes" => 0,
|
|
|
- ];
|
|
|
+ // 计算当前员工在该项目上的分摊金额(单位:分)
|
|
|
+ if ($total_m > 0 && --$emp_item_count[$k] > 0) {
|
|
|
+ // 非该员工的最后一个项目:计算分摊
|
|
|
+ $allocated = (int)floor(($item->total_work / $total_m) * $total_s);
|
|
|
+ $rem_salary_pool[$k] -= $allocated;
|
|
|
+ } else {
|
|
|
+ // 该员工的最后一个项目:取余额清零
|
|
|
+ $allocated = $rem_salary_pool[$k] ?? 0;
|
|
|
}
|
|
|
|
|
|
- $total_salary = $salary_map[$e_key] ?? 0;
|
|
|
- $total_min = $employee_monthly_total_min[$e_key] ?? 0;
|
|
|
-
|
|
|
- if ($total_min > 0) {
|
|
|
- // 这里是关键:(项目工时 / 员工月总工时) * 员工月工资
|
|
|
- // 累加时保持高精度
|
|
|
- $ratio = $item['total_work'] / $total_min;
|
|
|
- $item_month_list[$item_key]['allocated_salary'] += ($ratio * $total_salary);
|
|
|
+ // 按项目+月份进行累加
|
|
|
+ $item_key = $item->order_month . '_' . $item->item_id;
|
|
|
+ if (!isset($item_summary[$item_key])) {
|
|
|
+ $item_summary[$item_key] = [
|
|
|
+ 'month' => $item->order_month,
|
|
|
+ 'item_id' => $item->item_id,
|
|
|
+ 'allocated_salary' => 0,
|
|
|
+ 'work_minutes' => 0,
|
|
|
+ ];
|
|
|
}
|
|
|
- $item_month_list[$item_key]['work_minutes'] += $item['total_work'];
|
|
|
+ $item_summary[$item_key]['allocated_salary'] += $allocated;
|
|
|
+ $item_summary[$item_key]['work_minutes'] += $item['total_work'];
|
|
|
}
|
|
|
|
|
|
- // 7. 准备项目基础信息
|
|
|
- $item_month_list = collect($item_month_list)->sortBy('month')->values();
|
|
|
- $item_ids = $item_month_list->pluck('item_id')->unique()->toArray();
|
|
|
- $item_info = Item::TopClear($user, $data)->whereIn('id', $item_ids)->get()->keyBy('id');
|
|
|
-
|
|
|
- // 8. 最终格式化与尾差修正
|
|
|
- $month_group_count = $item_month_list->groupBy('month')->map->count()->toArray();
|
|
|
- $month_day_pool = $item_month_list->groupBy('month')->map(fn($g) => round($g->sum('work_minutes') / 480))->toArray();
|
|
|
-
|
|
|
- $final_list = $item_month_list->transform(function ($item) use (
|
|
|
- $item_info,
|
|
|
- &$all_salary_pool,
|
|
|
- &$month_day_pool,
|
|
|
- &$month_group_count
|
|
|
- ) {
|
|
|
- $month = $item['month'];
|
|
|
- $item['item_title'] = $item_info[$item['item_id']]->title ?? "未知项目";
|
|
|
- $item['item_code'] = $item_info[$item['item_id']]->code ?? "N/A";
|
|
|
+ // 7. 获取项目信息并进行最终格式化
|
|
|
+ $i_ids = collect($item_summary)->pluck('item_id')->unique()->all();
|
|
|
+ $i_info = Item::TopClear($user, $data)->whereIn('id', $i_ids)->get()->keyBy('id');
|
|
|
|
|
|
- if ($month_group_count[$month] > 1) {
|
|
|
- // 非本月最后一个项目:四舍五入并扣除余额
|
|
|
- $item['days'] = round($item['work_minutes'] / 480);
|
|
|
- $item['allocated_salary'] = round($item['allocated_salary'], 2);
|
|
|
+ $result = collect($item_summary)->sortBy('month')->transform(function ($v) use ($i_info) {
|
|
|
+ $v['item_title'] = $i_info[$v['item_id']]->title ?? "未知项目({$v['item_id']})";
|
|
|
+ $v['item_code'] = $i_info[$v['item_id']]->code ?? "-";
|
|
|
|
|
|
- $month_day_pool[$month] -= $item['days'];
|
|
|
- $all_salary_pool[$month] = round($all_salary_pool[$month] - $item['allocated_salary'], 2);
|
|
|
- $month_group_count[$month]--;
|
|
|
- } else {
|
|
|
- // 本月最后一个项目:直接拿走剩下的所有钱和天数,解决精度问题
|
|
|
- $item['days'] = $month_day_pool[$month];
|
|
|
- $item['allocated_salary'] = $all_salary_pool[$month];
|
|
|
- }
|
|
|
+ // 将“分”转回“元”
|
|
|
+ $v['allocated_salary'] = round($v['allocated_salary'] / 100, 2);
|
|
|
+ // 计算天数(按 8 小时/天)
|
|
|
+ $v['days'] = round($v['work_minutes'] / 480);
|
|
|
|
|
|
- return $item;
|
|
|
- });
|
|
|
+ unset($v['work_minutes']); // 移除中间计算字段
|
|
|
+ return $v;
|
|
|
+ })->values()->all();
|
|
|
|
|
|
- return [true, $final_list->all()];
|
|
|
+ return [true, $result];
|
|
|
}
|
|
|
|
|
|
|