cqp 2 ヶ月 前
コミット
41d69e5f90

+ 5 - 2
app/Exports/ItemSalaryFTMultipleSheetExport.php

@@ -8,6 +8,7 @@ class ItemSalaryFTMultipleSheetExport implements WithMultipleSheets
 {
     protected $monthsData;
     protected $projects;
+    protected $company;
 
     /**
      * @param array $monthsData 格式需要包含该月特有的项目列表:
@@ -22,9 +23,10 @@ class ItemSalaryFTMultipleSheetExport implements WithMultipleSheets
      * ]
      * ]
      */
-    public function __construct(array $monthsData)
+    public function __construct(array $monthsData,$company)
     {
         $this->monthsData = $monthsData;
+        $this->company = $company;
     }
 
     public function sheets(): array
@@ -35,7 +37,8 @@ class ItemSalaryFTMultipleSheetExport implements WithMultipleSheets
             $sheets[] = new ItemSalaryFTSheetExport(
                 $month,
                 $item['data'],
-                $item['projects']
+                $item['projects'],
+                $this->company
             );
         }
         return $sheets;

+ 47 - 66
app/Exports/ItemSalaryFTSheetExport.php

@@ -4,42 +4,33 @@ namespace App\Exports;
 
 use Maatwebsite\Excel\Concerns\FromCollection;
 use Maatwebsite\Excel\Concerns\WithEvents;
-use Maatwebsite\Excel\Concerns\WithStyles;
 use Maatwebsite\Excel\Concerns\WithTitle;
 use Maatwebsite\Excel\Concerns\WithCustomStartCell;
 use Maatwebsite\Excel\Events\AfterSheet;
-use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
 use PhpOffice\PhpSpreadsheet\Style\Alignment;
 use PhpOffice\PhpSpreadsheet\Style\Border;
 use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
 
-class ItemSalaryFTSheetExport implements FromCollection, WithEvents, WithStyles, WithCustomStartCell, WithTitle
+class ItemSalaryFTSheetExport implements FromCollection, WithEvents, WithCustomStartCell, WithTitle
 {
     protected $month;
     protected $data;
     protected $projects;
+    protected $company;
 
-    public function __construct(string $month, array $data, array $projects)
+    public function __construct(string $month, array $data, array $projects, $company = '')
     {
         $this->month = $month;
         $this->data = $data;
         $this->projects = $projects;
+        $this->company = $company;
     }
 
-    public function title(): string
-    {
-        return $this->month;
-    }
+    public function title(): string { return $this->month; }
 
-    public function startCell(): string
-    {
-        return 'A5'; // 数据从 A5 开始写入 (1-4行是表头)
-    }
+    public function startCell(): string { return 'A5'; }
 
-    public function collection()
-    {
-        return collect($this->data);
-    }
+    public function collection() { return collect($this->data); }
 
     public function registerEvents(): array
     {
@@ -48,63 +39,79 @@ class ItemSalaryFTSheetExport implements FromCollection, WithEvents, WithStyles,
                 $sheet = $event->sheet->getDelegate();
 
                 $projectCount = count($this->projects);
-                // 总列数 = 序号(1) + 姓名(1) + 工资(1) + 总工时(1) + 月工时(项目数+合计1) + 对应金额(项目数+总计1)
+                // 总列数计算:4(基础) + 项目数 + 1(合计工时) + 项目数 + 1(总计工资)
                 $totalColNum = 4 + ($projectCount + 1) + ($projectCount + 1);
                 $highestColumn = Coordinate::stringFromColumnIndex($totalColNum);
+                $highestRow = $sheet->getHighestRow();
+
+                // 保证即便数据少,也画出至少 10 行的格子,显得美观
+                $renderToRow = max($highestRow, 10);
 
                 // --- 1. 第一行:主标题 ---
                 $sheet->mergeCells("A1:{$highestColumn}1");
                 $sheet->setCellValue('A1', "{$this->month}研发工资分摊表");
-                $sheet->getStyle('A1')->getFont()->setSize(14);
-                $sheet->getStyle('A1')->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER);
+                $sheet->getStyle('A1')->getFont()->setSize(16)->setBold(true);
 
                 // --- 2. 第二行:单位名称 ---
                 $sheet->mergeCells("A2:{$highestColumn}2");
-                $sheet->setCellValue('A2', "单位:");
-                $sheet->getStyle('A2')->getFont()->setSize(14);
+                $sheet->setCellValue('A2', "单位:{$this->company}");
+                $sheet->getStyle('A2')->getFont()->setSize(12);
 
                 // --- 3. 第三、四行:复合表头 ---
-                // 序号、人员、工资、总工时 垂直合并
+                // 垂直合并基础列
                 foreach (['A', 'B', 'C', 'D'] as $col) { $sheet->mergeCells("{$col}3:{$col}4"); }
                 $sheet->setCellValue('A3', '序号');
                 $sheet->setCellValue('B3', '技术人员');
                 $sheet->setCellValue('C3', '工资');
                 $sheet->setCellValue('D3', '月总工时');
 
-                // A. 月工时区域
+                // A. 月工时区域 (E列开始)
                 $workHourStartCol = 5;
-                $workHourEndCol = $workHourStartCol + $projectCount; // 包含“合计工时”列
+                $workHourEndCol = $workHourStartCol + $projectCount;
                 $workHourStartLetter = Coordinate::stringFromColumnIndex($workHourStartCol);
                 $workHourEndLetter = Coordinate::stringFromColumnIndex($workHourEndCol);
-
                 $sheet->mergeCells("{$workHourStartLetter}3:{$workHourEndLetter}3");
                 $sheet->setCellValue("{$workHourStartLetter}3", '月 工 时');
-
-                // 填充 RD 项目 (月工时下)
-                foreach ($this->projects as $index => $rdCode) {
-                    $colLetter = Coordinate::stringFromColumnIndex($workHourStartCol + $index);
-                    $sheet->setCellValue($colLetter . '4', $rdCode);
+                foreach ($this->projects as $idx => $code) {
+                    $sheet->setCellValue(Coordinate::stringFromColumnIndex($workHourStartCol + $idx) . '4', $code);
                 }
                 $sheet->setCellValue($workHourEndLetter . '4', '合计工时');
 
-                // B. 项目应计工资金额区域
+                // B. 金额区域
                 $moneyStartCol = $workHourEndCol + 1;
-                $moneyEndCol = $moneyStartCol + $projectCount; // 包含“总计工资”列
+                $moneyEndCol = $moneyStartCol + $projectCount;
                 $moneyStartLetter = Coordinate::stringFromColumnIndex($moneyStartCol);
                 $moneyEndLetter = Coordinate::stringFromColumnIndex($moneyEndCol);
-
                 $sheet->mergeCells("{$moneyStartLetter}3:{$moneyEndLetter}3");
                 $sheet->setCellValue("{$moneyStartLetter}3", '项目应计工资金额');
-
-                // 填充 RD 项目 (金额下)
-                foreach ($this->projects as $index => $rdCode) {
-                    $colLetter = Coordinate::stringFromColumnIndex($moneyStartCol + $index);
-                    $sheet->setCellValue($colLetter . '4', $rdCode);
+                foreach ($this->projects as $idx => $code) {
+                    $sheet->setCellValue(Coordinate::stringFromColumnIndex($moneyStartCol + $idx) . '4', $code);
                 }
                 $sheet->setCellValue($moneyEndLetter . '4', '总计工资');
 
-                // --- 4. 设置列宽 ---
-                $sheet->getColumnDimension('A')->setWidth(8);
+                // --- 4. 样式美化 ---
+                // 仅对表格主体(3行开始)加边框
+                $tableRange = "A3:{$highestColumn}{$renderToRow}";
+                $sheet->getStyle($tableRange)->applyFromArray([
+                    'borders' => ['allBorders' => ['borderStyle' => Border::BORDER_THIN]],
+                    'alignment' => [
+                        'vertical' => Alignment::VERTICAL_CENTER,
+                        'horizontal' => Alignment::HORIZONTAL_CENTER,
+                    ],
+                    'font' => ['name' => '宋体', 'size' => 10],
+                ]);
+
+                // 标题和单位行样式
+                $sheet->getStyle("A1:A2")->getAlignment()->setHorizontal(Alignment::HORIZONTAL_LEFT);
+                $sheet->getStyle("A1")->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER);
+                $sheet->getStyle("A3:{$highestColumn}4")->getFont()->setBold(true);
+
+                // 格式化金额(解决 0.02 问题)
+                $sheet->getStyle("C5:C{$renderToRow}")->getNumberFormat()->setFormatCode('#,##0.00');
+                $sheet->getStyle("{$moneyStartLetter}5:{$highestColumn}{$renderToRow}")->getNumberFormat()->setFormatCode('#,##0.00');
+
+                // 设置列宽
+                $sheet->getColumnDimension('A')->setWidth(6);
                 $sheet->getColumnDimension('B')->setWidth(12);
                 for ($i = 3; $i <= $totalColNum; $i++) {
                     $sheet->getColumnDimension(Coordinate::stringFromColumnIndex($i))->setWidth(13);
@@ -112,30 +119,4 @@ class ItemSalaryFTSheetExport implements FromCollection, WithEvents, WithStyles,
             },
         ];
     }
-
-    public function styles(Worksheet $sheet)
-    {
-        $highestRow = $sheet->getHighestRow();
-        $highestColumn = $sheet->getHighestColumn();
-
-        // 1. 全表基础样式(居中 + 细边框 + 宋体)
-        $sheet->getStyle("A1:{$highestColumn}{$highestRow}")->applyFromArray([
-            'borders' => [
-                'allBorders' => ['borderStyle' => Border::BORDER_THIN],
-            ],
-            'alignment' => [
-                'vertical' => Alignment::VERTICAL_CENTER,
-                'horizontal' => Alignment::HORIZONTAL_CENTER,
-            ],
-            'font' => ['name' => '宋体'],
-        ]);
-
-        // 2. 特殊处理:第二行左对齐
-        $sheet->getStyle("A2:{$highestColumn}2")->getAlignment()->setHorizontal(Alignment::HORIZONTAL_LEFT);
-
-        // 4. 数据行样式
-        $sheet->getStyle("A5:{$highestColumn}{$highestRow}")->getFont()->setSize(10);
-
-        return [];
-    }
 }

+ 4 - 2
app/Exports/ItemSalarySheetExport.php

@@ -16,11 +16,13 @@ class ItemSalarySheetExport implements FromCollection, WithEvents, WithStyles, W
 {
     protected $projects;
     protected $data;
+    protected $company;
 
-    public function __construct(array $projects, array $data)
+    public function __construct(array $projects, array $data, $company)
     {
         $this->projects = $projects;
         $this->data = $data;
+        $this->company = $company;
     }
 
     /**
@@ -86,7 +88,7 @@ class ItemSalarySheetExport implements FromCollection, WithEvents, WithStyles, W
 
                 // --- 5. 第二行:单位名称 ---
                 $sheet->mergeCells("A2:{$highestColumn}2");
-                $sheet->setCellValue('A2', '单位名称:');
+                $sheet->setCellValue('A2', '单位名称:'. $this->company);
                 $sheet->getStyle('A2')->getFont()->setSize(14);
 
                 // --- 6. 垂直合并“年月”和“合计” ---

+ 6 - 1
app/Exports/ManMonthlyWorkHourSheetExport.php

@@ -75,7 +75,7 @@ class ManMonthlyWorkHourSheetExport implements WithEvents, WithTitle
                 for ($day = 1; $day <= $this->daysInMonth; $day++) {
                     $colLetter = Coordinate::stringFromColumnIndex($day + 2); // 从 C 列开始
                     $sheet->setCellValue($colLetter . '3', $day);
-                    $sheet->getColumnDimension($colLetter)->setWidth(4);
+                    $sheet->getColumnDimension($colLetter)->setWidth(8);
                 }
 
                 // 设置第三行行高
@@ -96,6 +96,11 @@ class ManMonthlyWorkHourSheetExport implements WithEvents, WithTitle
                 // --- 5. 全局样式微调 ---
                 $highestRow = $sheet->getHighestRow();
 
+                // 1. 设置工时数据区域的格式为数字,并保留两位小数
+                $dataRange = "C4:{$highestColumn}{$highestRow}";
+                $sheet->getStyle($dataRange)->getNumberFormat()->setFormatCode('#,##0.00');
+                // 或者用 NumberFormat::FORMAT_NUMBER_00
+
                 // 所有单元格垂直居中
                 $sheet->getStyle("A1:{$highestColumn}{$highestRow}")->getAlignment()->setVertical(Alignment::VERTICAL_CENTER);
 

+ 2 - 1
app/Exports/ProjectDepreciationSheetExport.php

@@ -44,7 +44,8 @@ class ProjectDepreciationSheetExport implements WithEvents, WithTitle
 
                     // 1. 标题 (A-J)
                     $sheet->mergeCells("A{$currentRow}:J{$currentRow}");
-                    $sheet->setCellValue("A{$currentRow}", "2025年度研发项目折旧费用调整表");
+                    $displayYear = date('Y', strtotime($month));
+                    $sheet->setCellValue("A{$currentRow}", "{$displayYear}年度研发项目折旧费用调整表");
                     $sheet->getStyle("A{$currentRow}")->getFont()->setBold(true)->setSize(16);
                     $sheet->getStyle("A{$currentRow}")->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER);
                     $currentRow++;

+ 280 - 1
app/Service/ExportFileService.php

@@ -3,7 +3,12 @@
 namespace App\Service;
 
 use App\Exports\ExportOrder;
+use App\Exports\ItemSalaryFTMultipleSheetExport;
+use App\Exports\ItemSalarySheetExport;
+use App\Exports\ManMonthlyWorkHourMultipleSheetExport;
 use App\Exports\MultiSheetExport;
+use App\Exports\ProjectDepreciationMultipleSheetExport;
+use App\Model\Depart;
 use App\Model\PLeaveOverOrder;
 use Maatwebsite\Excel\Facades\Excel;
 
@@ -54,7 +59,7 @@ class ExportFileService extends Service
 //        $service = new TableHeadService();
 //        if(method_exists($service,$header_f)) $service->$header_f($header_default);
 
-        $user['e_header_default'] = $header['array'];
+        $user['e_header_default'] = $header['array'] ?? [];
 
         return $funcName;
     }
@@ -356,6 +361,280 @@ class ExportFileService extends Service
         return [true, $this->saveExportData($return,$header)];
     }
 
+    public function exportEmployeeSalary($data, $user)
+    {
+        $service = new StatisticService();
+        // 1. 调用你现有的查询方法获取基础数据
+        list($status, $itemMonthList) = $service->employeeMonthSalaryStatistic($data, $user);
+        if (!$status) return $itemMonthList; // 返回错误信息
+
+        // 2. 提取所有涉及到的唯一项目 (按 Code 排序,保证列顺序固定)
+        $projects = collect($itemMonthList)->pluck('item_code')->unique()->sort()->values()->toArray();
+
+        // 3. 按月份对数据进行分组
+        $groupedByMonth = collect($itemMonthList)->groupBy('month')->sortKeys();
+
+        $exportData = [];
+        $columnTotals = array_fill(0, count($projects) * 2, 0); // 用于存储每列的合计
+        $grandTotalSalary = 0; // 总计金额
+
+        // 4. 循环每个月,构造行数据
+        foreach ($groupedByMonth as $month => $items) {
+            $row = [$month]; // A列:月份
+            $monthTotalSalary = 0;
+
+            // 创建该月项目的映射,方便快速查找
+            $monthItemsMap = $items->keyBy('item_code');
+
+            foreach ($projects as $index => $code) {
+                $itemDetail = $monthItemsMap->get($code);
+                $days = $itemDetail['days'] ?? 0;
+                $salary = $itemDetail['allocated_salary'] ?? 0;
+
+                $row[] = $days > 0 ? $days : '';     // 天数列
+                $row[] = $salary > 0 ? $salary : ''; // 工资列
+
+                // 累加合计行(列合计)
+                $columnTotals[$index * 2] += $days;
+                $columnTotals[$index * 2 + 1] += $salary;
+                $monthTotalSalary += $salary;
+            }
+
+            $row[] = $monthTotalSalary; // 最后一列:该月合计工资
+            $grandTotalSalary += $monthTotalSalary;
+            $exportData[] = $row;
+        }
+
+        // 5. 构造最后的“合计”行
+        $totalRow = ['合计'];
+        foreach ($columnTotals as $val) {
+            $totalRow[] = $val > 0 ? $val : 0;
+        }
+        $totalRow[] = $grandTotalSalary;
+        $exportData[] = $totalRow;
+
+        $file_name = "项目工资统计表_" . date("Y-m-d") . "_". rand(1000,9999);
+        $filename =  $file_name . '.' . 'xlsx';
+        $bool = Excel::store(new ItemSalarySheetExport($projects, $exportData, Depart::where('id', $user['top_depart_id'])->value('title')),"/public/export/{$filename}", null, 'Xlsx', []);
+
+        return [true, $filename];
+    }
+
+    public function exportManMonthlyWorkHour($data, $user)
+    {
+        // 1. 获取报表基础数据
+        $service = new StatisticService();
+        list($status, $result) = $service->employeeDayHourStatistic($data, $user);
+        if (!$status) return $result;
+
+        $sourceData = collect($result['data']);
+
+        // 2. 按月份分组,准备多 Sheet 数据
+        // 结构:[ '2024-04' => [ 'days' => 30, 'data' => [...] ], ... ]
+        $monthsData = [];
+
+        // 按月分组数据
+        $groupedByMonth = $sourceData->groupBy(function ($item) {
+            return date('Y年m月', strtotime($item['order_date']));
+        });
+
+        foreach ($groupedByMonth as $monthName => $monthItems) {
+            // A. 计算该月总天数
+            // 转换 '2024年04月' 为 '2024-04' 获取天数
+            $formatMonth = str_replace(['年', '月'], ['-', ''], $monthName);
+            $daysInMonth = date('t', strtotime($formatMonth . '-01'));
+
+            // B. 进一步按 项目+人员 分组,因为一行显示一个项目一个人的全月工时
+            $groupedByUserItem = $monthItems->groupBy(function ($item) {
+                return $item['item_code'] . '_' . $item['employee_id'];
+            });
+
+            $sheetRows = [];
+            foreach ($groupedByUserItem as $key => $records) {
+                $first = $records->first();
+                // 初始化行:前两列是 项目编号 和 姓名
+                $row = [
+                    $first['item_code'],
+                    $first['employee_name']
+                ];
+
+                // C. 循环 1 号到该月最后一天,填充工时
+                // 将该员工该项目在这个月的记录转为 日期 => 工时 的映射
+                $dayMap = $records->keyBy(function($r){
+                    return (int)date('d', strtotime($r['order_date']));
+                });
+
+                for ($d = 1; $d <= $daysInMonth; $d++) {
+                    $workHour = $dayMap->get($d)['total_work_hours'] ?? '';
+                    // 如果工时为 0 或空,按你要求的格式传空字符串
+                    $row[] = ($workHour > 0) ?(float) $workHour : '';
+                }
+
+                $sheetRows[] = $row;
+            }
+
+            $monthsData[$monthName] = [
+                'days' => (int)$daysInMonth,
+                'data' => $sheetRows
+            ];
+        }
+
+        $file_name = "人员月工时统计表_" . date("Y-m-d") . "_". rand(1000,9999);
+        $filename =  $file_name . '.' . 'xlsx';
+
+        $bool =  Excel::store(new ManMonthlyWorkHourMultipleSheetExport($monthsData), "/public/export/{$filename}", null, 'Xlsx', []);
+        return [true, $filename];
+    }
+
+    public function exportDeviceZj(array $data, $user)
+    {
+        $service = new StatisticService();
+        list($status, $result) = $service->itemDeviceMonthStatistic($data, $user);
+        if (!$status) return $result;
+
+        // 2. 将数据按 [项目编号][月份] 进行分组
+        // 预期结构:$monthsData['RD01']['months']['2025-01'] = [设备1, 设备2...]
+        $groupedData = collect($result)->groupBy('item_code');
+
+        $finalExportData = [];
+
+        foreach ($groupedData as $itemCode => $itemRecords) {
+            $projectName = $itemRecords->first()['item_title'] ?? '';
+
+            // 2. 获取年份(从 order_month 字段提取,如 "2026-01" 取前4位)
+            $firstMonth = $firstRecord['month'] ?? date('Y-m');
+            $year = substr($firstMonth, 0, 4);
+
+            // 3. 构造新的 Key:年-项目 (例如: 2026-53code)
+            $newSheetKey = $year . '年度项目' . $itemCode;
+
+            // 按月份进一步分组
+            $monthGroups = $itemRecords->groupBy('month');
+
+            $monthsPayload = [];
+            foreach ($monthGroups as $month => $devices) {
+                $monthData = [];
+                foreach ($devices as $dev) {
+                    $monthData[] = [
+                        'device_name'         => $dev['device_title'],
+                        'original_value'      => $dev['device_original'],    // 设备原值
+                        'total_depreciation'  => $dev['total_depreciatio'], // 当月总折旧
+                        'project_hours'       => $dev['hours'],              // 本项目工时
+                        'total_hours'         => $dev['total_hours'],        // 当月总工时
+                        'ratio'               => bcmul($dev['ratio'], 100,2),              // 研发工时占比
+                        'allocated_depreciation' => $dev['allocated_depreciatio'], // 本项目分摊折旧
+                    ];
+                }
+                $monthsPayload[$month] = $monthData;
+            }
+
+            // 构造 Sheet 所需结构
+            $finalExportData[$newSheetKey] = [
+                'project_name' => $projectName,
+                'months'       => $monthsPayload
+            ];
+        }
+
+        $file_name = "项目设备折旧费用统计表_" . date("Y-m-d") . "_". rand(1000,9999);
+        $filename =  $file_name . '.' . 'xlsx';
+
+        $bool =  Excel::store(new ProjectDepreciationMultipleSheetExport($finalExportData), "/public/export/{$filename}", null, 'Xlsx', []);
+        return [true, $filename];
+    }
+
+    public function exportItemSalaryFT(array $data, $user)
+    {
+        $service = new StatisticService();
+        list($status, $result) = $service->itemDaySalaryStatistic($data, $user);
+        if (!$status) return $result;
+
+        $sourceData = collect($result);
+        $groupedByMonth = $sourceData->groupBy('month');
+        $monthsData = [];
+
+        foreach ($groupedByMonth as $month => $monthRecords) {
+            // A. 获取本月参与的所有唯一项目编码
+            $monthProjects = $monthRecords->pluck('item_code')->unique()->sort()->values()->all();
+
+            // B. 按人员分组组织数据
+            $groupedByEmployee = $monthRecords->groupBy('employee_id');
+            $sheetRows = [];
+            $index = 1;
+
+            // 初始化列合计
+            $colTotals = [
+                'total_salary' => 0,
+                'total_min_hours' => 0, // 月总工时合计
+                'project_days' => array_fill_keys($monthProjects, 0),
+                'project_salary' => array_fill_keys($monthProjects, 0),
+                'total_attendance_days' => 0, // 合计工时列的合计
+            ];
+
+            foreach ($groupedByEmployee as $employeeId => $records) {
+                $first = $records->first();
+                $empMonthSalary = (float)$first['total_salary'];
+                $empTotalHours = round((float)$first['total_min'] / 60, 2); // 月总工时(小时)
+
+                // 1-4列:序号、姓名、工资、月总工时
+                $row = [$index++, $first['employee_title'], $empMonthSalary, $empTotalHours];
+
+                // 5. 动态项目工时列
+                $empMap = $records->keyBy('item_code');
+                $rowProjectDaysSum = 0;
+                foreach ($monthProjects as $code) {
+                    $days = $empMap->has($code) ? (float)$empMap->get($code)['days'] : 0;
+                    $row[] = $days > 0 ? $days : '';
+                    $rowProjectDaysSum += $days;
+                    $colTotals['project_days'][$code] += $days;
+                }
+
+                // 6. 合计工时列
+                $row[] = $rowProjectDaysSum;
+                $colTotals['total_attendance_days'] += $rowProjectDaysSum;
+
+                // 7. 动态项目金额列
+                foreach ($monthProjects as $code) {
+                    $salary = $empMap->has($code) ? (float)$empMap->get($code)['allocated_salary'] : 0;
+                    $row[] = $salary > 0 ? $salary : '';
+                    $colTotals['project_salary'][$code] += $salary;
+                }
+
+                // 8. 总计工资列
+                $row[] = $empMonthSalary;
+
+                // 累加合计
+                $colTotals['total_salary'] += $empMonthSalary;
+                $colTotals['total_min_hours'] += $empTotalHours;
+
+                $sheetRows[] = $row;
+            }
+
+            // C. 构造合计行
+            $totalRow = ['合计', '', $colTotals['total_salary'], $colTotals['total_min_hours']];
+            foreach ($monthProjects as $code) { $totalRow[] = $colTotals['project_days'][$code]; }
+            $totalRow[] = $colTotals['total_attendance_days'];
+            foreach ($monthProjects as $code) { $totalRow[] = $colTotals['project_salary'][$code]; }
+            $totalRow[] = $colTotals['total_salary'];
+
+            $sheetRows[] = $totalRow;
+
+            $monthsData[$month] = [
+                'projects' => $monthProjects,
+                'data'     => $sheetRows
+            ];
+        }
+
+        $file_name = "项目工资分摊统计表_" . date("Y-m-d") . "_". rand(1000,9999);
+        $filename =  $file_name . '.xlsx';
+
+        Excel::store(
+            new ItemSalaryFTMultipleSheetExport($monthsData, Depart::where('id', $user['top_depart_id'])->value('title')),
+            "/public/export/{$filename}"
+        );
+
+        return [true, $filename];
+    }
+
     public function saveExportData($data, $headers, $type = 'default',$file_name = ''){
         if(empty($file_name)) $file_name = self::$filename . "_". date("Y-m-d") . "_". rand(1000,9999);
         $filename =  $file_name . '.' . 'xlsx';