cqp 2 недель назад
Родитель
Сommit
060436ffb5
3 измененных файлов с 211 добавлено и 2 удалено
  1. 143 0
      app/Exports/MonthlyPsMatrixExport.php
  2. 9 1
      app/Service/BIService.php
  3. 59 1
      app/Service/ExportFileService.php

+ 143 - 0
app/Exports/MonthlyPsMatrixExport.php

@@ -0,0 +1,143 @@
+<?php
+
+namespace App\Exports;
+
+use Maatwebsite\Excel\Concerns\WithMultipleSheets;
+use Maatwebsite\Excel\Concerns\WithEvents;
+use Maatwebsite\Excel\Concerns\WithTitle;
+use Maatwebsite\Excel\Events\AfterSheet;
+use PhpOffice\PhpSpreadsheet\Style\Alignment;
+use PhpOffice\PhpSpreadsheet\Style\Border;
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+
+// 1. 主导出类:负责拆分年份和分发 Sheet
+class MonthlyPsMatrixExport implements WithMultipleSheets
+{
+    protected $yearlyData;
+
+    public function __construct(array $yearlyData)
+    {
+        $this->yearlyData = $yearlyData;
+    }
+
+    /**
+     * 系统会自动识别这个方法,有多少个年份就自动创建多少个 Sheet
+     */
+    public function sheets(): array
+    {
+        $sheets = [];
+        foreach ($this->yearlyData as $year => $employeeRows) {
+            // 将每一年独立的数据和年份名称传给子 Sheet 类
+            $sheets[] = new SingleYearPsMatrixSheet($year, $employeeRows);
+        }
+        return $sheets;
+    }
+}
+
+// 2. 子 Sheet 类:独立负责单张年份表的样式和数据填充
+class SingleYearPsMatrixSheet implements WithEvents, WithTitle
+{
+    protected $year;
+    protected $employeeRows;
+
+    public function __construct($year, array $employeeRows)
+    {
+        $this->year = $year;
+        $this->employeeRows = $employeeRows;
+    }
+
+    /**
+     * 定义当前 Sheet 页的标签名称
+     */
+    public function title(): string
+    {
+        return $this->year . '年';
+    }
+
+    /**
+     * 编写当前单张表的渲染逻辑
+     */
+    public function registerEvents(): array
+    {
+        return [
+            AfterSheet::class => function (AfterSheet $event) {
+                $sheet = $event->sheet->getDelegate();
+
+                // 🆕 细节修改 1:去掉了工号列后,总列数减少到 14 列。
+                // A列(姓名) + B到M列(1-12月) = 13列,所以“年度合计”刚好是第 14 列,即 N 列。
+                $lastColLetter = 'N';
+                $dataCount = count($this->employeeRows);
+                $lastRow = 2 + $dataCount; // 表头占2行
+
+                // --- 1. 第一行:主标题 ---
+                $sheet->mergeCells("A1:{$lastColLetter}1");
+                $sheet->setCellValue('A1', "{$this->year}年 员工月份薪资明细");
+                $sheet->getStyle('A1')->getFont()->setBold(true)->setSize(14);
+
+                // --- 2. 第二行:表头设置 ---
+                $sheet->setCellValue('A2', '人员/月份');
+
+                // 循环生成 1月 到 12月 的列头
+                for ($m = 1; $m <= 12; $m++) {
+                    // 🆕 细节修改 2:A列是姓名(1),1月应该从 B列(2) 开始。所以是 $m + 1
+                    $colLetter = Coordinate::stringFromColumnIndex($m + 1);
+                    $sheet->setCellValue("{$colLetter}2", $m . '月');
+                }
+                // 🆕 细节修改 3:显式写入最后一列“年度合计”
+                $sheet->setCellValue("{$lastColLetter}2", '年度合计');
+                $sheet->getStyle("A2:{$lastColLetter}2")->getFont()->setBold(true);
+
+                // --- 3. 第三行开始:填充矩阵数据 ---
+                $currentRow = 3;
+                foreach ($this->employeeRows as $empKey => $monthsData) {
+                    $empInfo   = explode('_', $empKey);
+                    $empName   = $empInfo[1] ?? ($empInfo[0] ?? ''); // 预防万一,如果切不出姓名则用工号兜底
+
+                    // 🆕 细节修改 4:这一行数据从 A 列开始,第一个格子放姓名
+                    $rowData = [$empName];
+                    $rowYearTotal = 0;
+
+                    for ($m = 1; $m <= 12; $m++) {
+                        $salary = $monthsData[$m] ?? 0;
+                        if ($salary > 0) {
+                            $rowData[] = $salary;
+                            $rowYearTotal += $salary;
+                        } else {
+                            $rowData[] = '-'; // 空白月用 '-' 占位
+                        }
+                    }
+                    // 追加年度合计
+                    $rowData[] = $rowYearTotal;
+
+                    // 将一整行数组直接写入当前行的 A 列起始位置
+                    $sheet->fromArray($rowData, null, "A{$currentRow}");
+                    $currentRow++;
+                }
+
+                // --- 4. 全局对齐与样式 ---
+                $fullRange = "A1:{$lastColLetter}{$lastRow}";
+                $sheet->getStyle($fullRange)->getAlignment()->applyFromArray([
+                    'vertical'   => Alignment::VERTICAL_CENTER,
+                    'horizontal' => Alignment::HORIZONTAL_CENTER,
+                    'wrapText'   => true,
+                ]);
+
+                // --- 5. 行高控制 ---
+                $sheet->getRowDimension(1)->setRowHeight(35); // 主标题高度
+                $sheet->getRowDimension(2)->setRowHeight(30); // 表头高度
+
+                // --- 6. 列宽自适应调节 ---
+                // 🆕 细节修改 5:range范围自动变成了 A 到 N,不会多出空白列
+                foreach (range('A', $lastColLetter) as $col) {
+                    $sheet->getColumnDimension($col)->setAutoSize(true);
+                }
+
+                // --- 7. 渲染全边框 ---
+                $sheet->getStyle("A2:{$lastColLetter}{$lastRow}")
+                    ->getBorders()
+                    ->getAllBorders()
+                    ->setBorderStyle(Border::BORDER_THIN);
+            },
+        ];
+    }
+}

+ 9 - 1
app/Service/BIService.php

@@ -14,6 +14,7 @@ use App\Model\MonthlyPsOrder;
 use App\Model\MonthlyPsOrderDetails;
 use App\Model\PLeaveOverOrder;
 use App\Model\PLeaveOverOrderDetails;
+use Illuminate\Support\Facades\DB;
 
 class BIService extends Service
 {
@@ -49,6 +50,12 @@ class BIService extends Service
 //        $data['month_start'] = "2024-01-01";
         list($status, $month_start, $month_end) = $this->commonRule($data);
         if (!$status) return [false, $month_start];
+
+        //当年项目总数
+        $intersectCount = $model->where('start_time', '<=', $month_end)   // 项目开始时间 <= 区间结束时间
+            ->where('end_time', '>=', $month_start)   // 项目结束时间 >= 区间开始时间
+            ->count();
+
         //人员费用
         list($total_man, $salary_map) = $this->getEmployeeSalary($month_start, $month_end, $data, $user);
 
@@ -102,7 +109,7 @@ class BIService extends Service
         //加计扣除金额
         $statisticService = new StatisticService();
         $attendance = $statisticService->employeeAttendanceMonthStatistic(["year"=>$year, "s" => "/api/employeeAttendanceMonthStatistic"],$user);
-        $discount = 0; //todo
+        $discount = 0;
         foreach ($attendance[1]['list'] as $v){
             $discount += $v['jj_total_amount']*100;
         }
@@ -123,6 +130,7 @@ class BIService extends Service
             'rd_rate' => $rd_rate, // 研发费用报销占比
             'man_work' => $man_work, //项目人员工时汇总
             'year' => date('Y',strtotime($year)),
+            'item_num' => $intersectCount
         ]];
     }
 

+ 59 - 1
app/Service/ExportFileService.php

@@ -6,6 +6,7 @@ use App\Exports\ExportOrder;
 use App\Exports\ItemSalaryFTMultipleSheetExport;
 use App\Exports\ItemSalarySheetExport;
 use App\Exports\ManMonthlyWorkHourMultipleSheetExport;
+use App\Exports\MonthlyPsMatrixExport;
 use App\Exports\MultiSheetExport;
 use App\Exports\ProjectDepreciationMultipleSheetExport;
 use App\Exports\ProjectStaffExport;
@@ -238,7 +239,7 @@ class ExportFileService extends Service
         return [true, $this->saveExportData($return,$header)];
     }
 
-    public function monthPsOrder($ergs,$user){
+    public function monthPsOrder1($ergs,$user){
         // 导出数据
         $return = [];
         $header_default = $user['e_header_default'];
@@ -256,6 +257,63 @@ class ExportFileService extends Service
         return [true, $this->saveExportData($return,$header)];
     }
 
+    public function monthPsOrder($ergs, $user)
+    {
+        $service = new PersonSalaryService();
+        // 1. 获取主表查询模型
+        $model = $service->monthlyPsOrderCommon($ergs, $user);
+
+        $yearlyData = []; // 用于存放归类后的数据:[年份][员工Key][月份] = 薪资数据
+
+        // 2. 分批捞取主表数据并按年、人、月进行归类
+        $model->chunk(500, function ($mainOrders) use (&$yearlyData, $service) {
+            $mainIds = $mainOrders->pluck('id')->toArray();
+            // 获取详情映射 [main_id => [details...]]
+            $detailsMap = $service->getDetailsMap($mainIds);
+
+            foreach ($mainOrders as $main) {
+                // 解析年份和月份
+                if (empty($main['month'])) continue;
+                $year  = date('Y', $main['month']); // 例如: "2025"
+                $month = (int)date('m', $main['month']); // 例如: 5 (直接转成数字 1-12)
+
+                $details = $detailsMap[$main['id']] ?? [];
+                foreach ($details as $sub) {
+                    // 构造员工在表格左侧的唯一标识(工号 + 姓名)
+                    $empKey = $sub['employee_number'] . '_' . $sub['employee_title'];
+
+                    // 计算当前月份该员工的总薪资 = 基本 + 绩效 + 奖金 + 其他
+                    $totalSalary = (float)$sub['base_salary']
+                        + (float)$sub['performance_salary']
+                        + (float)$sub['bonus']
+                        + (float)$sub['other'];
+
+                    // 归入矩阵:如果该年该人该月已经有值,做累加(防止单月重复发薪单)
+                    if (!isset($yearlyData[$year][$empKey][$month])) {
+                        $yearlyData[$year][$empKey][$month] = 0;
+                    }
+                    $yearlyData[$year][$empKey][$month] += $totalSalary;
+                }
+            }
+        });
+
+        if (empty($yearlyData)) {
+            return [false, '没有可导出的数据'];
+        }
+
+        // 3. 排序:年份按从小到大,员工按工号排序
+        ksort($yearlyData);
+        foreach ($yearlyData as $year => &$emps) {
+            ksort($emps); // 员工按工号等 Key 排序
+        }
+
+        $file_name = "人员月工资单统计表_" . date("Y-m-d") . "_". rand(1000,9999);
+        $filename =  $file_name . '.' . 'xlsx';
+
+        $bool =  Excel::store(new MonthlyPsMatrixExport($yearlyData), "/public/export/{$filename}", null, 'Xlsx', []);
+        return [true, $filename];
+    }
+
     public function monthDdOrder($ergs,$user){
         // 导出数据
         $return = [];