|
|
@@ -1445,36 +1445,30 @@ class StatisticService extends StatisticCommonService
|
|
|
)
|
|
|
->groupBy("order_month", "item_id", "employee_id")->get()->toArray();
|
|
|
|
|
|
- // 2. 获取工资及主表月份映射
|
|
|
- $ps_query = MonthlyPsOrder::Clear($user, $data)
|
|
|
+ // 2. 获取工资及配置
|
|
|
+ $monthly_ps_order_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();
|
|
|
- $monthly_ps_order_key_list = $ps_query
|
|
|
+ $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();
|
|
|
|
|
|
- $month_employee_salary_raw = MonthlyPsOrderDetails::wherein('main_id', $monthly_ps_order_ids)
|
|
|
- ->select(
|
|
|
- "employee_id",
|
|
|
- DB::raw("(base_salary + performance_salary + bonus + other) as salary"),
|
|
|
- "main_id",
|
|
|
- "social_insurance",
|
|
|
- "public_housing_fund"
|
|
|
- )
|
|
|
+ $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", "social_insurance", "public_housing_fund")
|
|
|
->get()->toArray();
|
|
|
|
|
|
- // 3. 构建高精度工资池与工时汇总
|
|
|
+ // 3. 汇总每个人每个月的工资 (单位:分)
|
|
|
$salary_map = [];
|
|
|
- $all_salary_cents = [];
|
|
|
- foreach ($month_employee_salary_raw as $val) {
|
|
|
+ $all_salary = [];
|
|
|
+ foreach ($month_employee_salary as $val) {
|
|
|
$month = $monthly_ps_order_key_list[$val['main_id']] ?? '';
|
|
|
if ($month) {
|
|
|
$key = $val['employee_id'] . '_' . $month;
|
|
|
$salary_map[$key] = $val;
|
|
|
- $all_salary_cents[$key] = [
|
|
|
+ $all_salary[$key] = [
|
|
|
"salary" => (int)round($val['salary'] * 100),
|
|
|
"social_insurance" => (int)round($val['social_insurance'] * 100),
|
|
|
"public_housing_fund" => (int)round($val['public_housing_fund'] * 100),
|
|
|
@@ -1482,94 +1476,96 @@ class StatisticService extends StatisticCommonService
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ // 4. 计算月总工时 (分钟) 及 余额池初始化 (小时*100)
|
|
|
$employee_monthly_total_min = [];
|
|
|
foreach ($month_employee_list as $row) {
|
|
|
$key = $row['employee_id'] . '_' . $row['order_month'];
|
|
|
$employee_monthly_total_min[$key] = ($employee_monthly_total_min[$key] ?? 0) + $row['total_work'];
|
|
|
}
|
|
|
|
|
|
- // 初始化工时余额池 (以展示的小时数为基准)
|
|
|
- $all_min_cents = [];
|
|
|
- foreach ($employee_monthly_total_min as $key => $totalMin) {
|
|
|
- $all_min_cents[$key] = (int)round(($totalMin / 60), 2) * 100;
|
|
|
+ $all_min = [];
|
|
|
+ foreach ($employee_monthly_total_min as $key => $min) {
|
|
|
+ // 重要修复:以总分钟数计算出全月总小时数,作为余额池的起点
|
|
|
+ $all_min[$key] = (int)round($min / 60, 2) * 100;
|
|
|
}
|
|
|
|
|
|
- // 4. 组装数据并预加载
|
|
|
+ // 5. 初步构建待分摊列表
|
|
|
$item_month_list = [];
|
|
|
foreach ($month_employee_list as $item) {
|
|
|
$key = $item['employee_id'] . '_' . $item['order_month'];
|
|
|
$item_key = $item['order_month'] . '_' . $item['item_id'] . '_' . $item['employee_id'];
|
|
|
|
|
|
+ $total_min = $employee_monthly_total_min[$key] ?? 0;
|
|
|
+ $ratio = $total_min > 0 ? ($item['total_work'] / $total_min) : 0;
|
|
|
+
|
|
|
$item_month_list[$item_key] = [
|
|
|
"month" => $item['order_month'],
|
|
|
"employee_id" => $item['employee_id'],
|
|
|
"item_id" => $item['item_id'],
|
|
|
- "work_minutes_raw" => $item['total_work'],
|
|
|
- "total_min_raw" => $employee_monthly_total_min[$key] ?? 0,
|
|
|
"salary" => $salary_map[$key]['salary'] ?? 0,
|
|
|
"social_insurance" => $salary_map[$key]['social_insurance'] ?? 0,
|
|
|
"public_housing_fund" => $salary_map[$key]['public_housing_fund'] ?? 0,
|
|
|
+ "radio" => round($ratio, 4),
|
|
|
+ "total_min_raw" => $total_min, // 保留原始分钟,不在这里修改
|
|
|
+ "work_min_raw" => $item['total_work'],
|
|
|
];
|
|
|
}
|
|
|
|
|
|
+ // 6. 获取项目和人员信息
|
|
|
+ $items = collect($item_month_list)->pluck('item_id')->unique()->all();
|
|
|
$employee_ids = collect($item_month_list)->pluck('employee_id')->unique()->all();
|
|
|
+
|
|
|
$employee_key_list = Employee::TopClear($user, $data)->wherein('id', $employee_ids)
|
|
|
->select("major", "title", "id")->get()->keyBy('id')->toArray();
|
|
|
-
|
|
|
- $items_ids = collect($item_month_list)->pluck('item_id')->unique()->all();
|
|
|
- $item_info = Item::TopClear($user, $data)->wherein('id', $items_ids)
|
|
|
+ $item_info = Item::TopClear($user, $data)->wherein('id', $items)
|
|
|
->select("title", "code", "id")->get()->keyBy('id')->toArray();
|
|
|
|
|
|
+ // 7. 统计每人项目数用于平账
|
|
|
$employee_count = collect($item_month_list)->groupBy(fn($i) => $i['employee_id'].'_'.$i['month'])
|
|
|
->map(fn($g) => $g->count())->toArray();
|
|
|
|
|
|
- // 5. 最终循环计算
|
|
|
- $result = collect($item_month_list)->sortBy('month')->values()->transform(function ($item) use ($item_info, $employee_key_list, &$employee_count, &$all_salary_cents, &$all_min_cents) {
|
|
|
+ // 8. 核心转换
|
|
|
+ $result = collect($item_month_list)->sortBy('month')->values()->transform(function ($item) use ($item_info, $employee_key_list, &$employee_count, &$all_salary, &$all_min) {
|
|
|
$key = $item['employee_id'] . '_' . $item['month'];
|
|
|
|
|
|
- // 基础信息填充
|
|
|
$item['item_title'] = $item_info[$item['item_id']]['title'] ?? "未知项目";
|
|
|
$item['item_code'] = $item_info[$item['item_id']]['code'] ?? "-";
|
|
|
$item['employee_title'] = $employee_key_list[$item['employee_id']]['title'] ?? "未知人员";
|
|
|
$item['major'] = $employee_key_list[$item['employee_id']]['major'] ?? "-";
|
|
|
|
|
|
- // --- 修复应出勤工时逻辑 ---
|
|
|
- // total_hours 直接使用计算好的固定值,不参与递减逻辑
|
|
|
- $item['total_hours'] = round($item['total_min_raw'] / 60, 2);
|
|
|
+ // 应出勤工时:固定展示,不参与递减
|
|
|
+ $item['total_min'] = round($item['total_min_raw'] / 60, 2);
|
|
|
|
|
|
- // 研发工时占比
|
|
|
- $ratio = $item['total_min_raw'] > 0 ? ($item['work_minutes_raw'] / $item['total_min_raw']) : 0;
|
|
|
- $item['radio'] = round($ratio, 4);
|
|
|
-
|
|
|
- if (--$employee_count[$key] > 0) {
|
|
|
- // 非最后一条
|
|
|
- $work_h = round($item['work_minutes_raw'] / 60, 2);
|
|
|
+ if (isset($employee_count[$key]) && --$employee_count[$key] > 0) {
|
|
|
+ // 非最后一条项目
|
|
|
+ $work_h = round($item['work_min_raw'] / 60, 2);
|
|
|
$item['work_minutes'] = $work_h;
|
|
|
+ $all_min[$key] -= ($work_h * 100);
|
|
|
|
|
|
- // 递减池
|
|
|
- $all_min_cents[$key] -= (int)round($work_h * 100);
|
|
|
-
|
|
|
- $ws = round($item['salary'] * $ratio, 2);
|
|
|
+ // 分摊工资
|
|
|
+ $ws = round($item['salary'] * ($item['work_min_raw'] / $item['total_min_raw']), 2);
|
|
|
$item['work_salary'] = $ws;
|
|
|
- $all_salary_cents[$key]['salary'] -= (int)round($ws * 100);
|
|
|
+ if (isset($all_salary[$key])) $all_salary[$key]['salary'] -= ($ws * 100);
|
|
|
|
|
|
- $wsi = round($item['social_insurance'] * $ratio, 2);
|
|
|
+ // 分摊社保
|
|
|
+ $wsi = round($item['social_insurance'] * ($item['work_min_raw'] / $item['total_min_raw']), 2);
|
|
|
$item['work_social_insurance'] = $wsi;
|
|
|
- $all_salary_cents[$key]['social_insurance'] -= (int)round($wsi * 100);
|
|
|
+ if (isset($all_salary[$key])) $all_salary[$key]['social_insurance'] -= ($wsi * 100);
|
|
|
|
|
|
- $wphf = round($item['public_housing_fund'] * $ratio, 2);
|
|
|
+ // 分摊公积金
|
|
|
+ $wphf = round($item['public_housing_fund'] * ($item['work_min_raw'] / $item['total_min_raw']), 2);
|
|
|
$item['work_public_housing_fund'] = $wphf;
|
|
|
- $all_salary_cents[$key]['public_housing_fund'] -= (int)round($wphf * 100);
|
|
|
+ if (isset($all_salary[$key])) $all_salary[$key]['public_housing_fund'] -= ($wphf * 100);
|
|
|
+
|
|
|
} else {
|
|
|
- // 最后一条,拿回剩余金额/工时
|
|
|
- $item['work_salary'] = round(($all_salary_cents[$key]['salary'] ?? 0) / 100, 2);
|
|
|
- $item['work_social_insurance'] = round(($all_salary_cents[$key]['social_insurance'] ?? 0) / 100, 2);
|
|
|
- $item['work_public_housing_fund'] = round(($all_salary_cents[$key]['public_housing_fund'] ?? 0) / 100, 2);
|
|
|
- $item['work_minutes'] = round(($all_min_cents[$key] ?? 0) / 100, 2);
|
|
|
+ // 最后一条,从余额池取值(解决精度)
|
|
|
+ $item['work_salary'] = isset($all_salary[$key]) ? round($all_salary[$key]['salary'] / 100, 2) : 0;
|
|
|
+ $item['work_social_insurance'] = isset($all_salary[$key]) ? round($all_salary[$key]['social_insurance'] / 100, 2) : 0;
|
|
|
+ $item['work_public_housing_fund'] = isset($all_salary[$key]) ? round($all_salary[$key]['public_housing_fund'] / 100, 2) : 0;
|
|
|
+ $item['work_minutes'] = isset($all_min[$key]) ? round($all_min[$key] / 100, 2) : 0;
|
|
|
}
|
|
|
|
|
|
- // 移除辅助字段
|
|
|
- unset($item['work_minutes_raw'], $item['total_min_raw']);
|
|
|
+ unset($item['total_min_raw'], $item['work_min_raw']);
|
|
|
return $item;
|
|
|
})->all();
|
|
|
|