Sfoglia il codice sorgente

Merge remote-tracking branch 'origin/master'

gogs 2 mesi fa
parent
commit
af391b9e54

+ 4 - 3
app/Exports/ResearchExpenseMultipleSheetExport.php

@@ -24,11 +24,12 @@ class ResearchExpenseMultipleSheetExport implements WithMultipleSheets
     {
         $sheets = [];
         foreach ($this->sheetsData as $sheetTitle => $content) {
-            // 将每一个唯一的 Key (年份-项目) 传给具体的 Sheet 类
             $sheets[] = new ResearchExpenseSheetExport(
-                (string)$sheetTitle, // 这里的 title 会显示在 Excel 标签页上
+                (string)$sheetTitle,
                 $content['data'],
-                $content['project'] ?? []
+                $content['project'] ?? [],
+                $content['dynamic_headers'] ?? [], // 传递动态表头
+                $content['year']
             );
         }
         return $sheets;

+ 99 - 86
app/Exports/ResearchExpenseSheetExport.php

@@ -11,38 +11,29 @@ use Maatwebsite\Excel\Events\AfterSheet;
 use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
 use PhpOffice\PhpSpreadsheet\Style\Alignment;
 use PhpOffice\PhpSpreadsheet\Style\Border;
+use PhpOffice\PhpSpreadsheet\Style\Fill;
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
 
 class ResearchExpenseSheetExport implements FromCollection, WithEvents, WithStyles, WithCustomStartCell, WithTitle
 {
-    protected $sheetTitle; // Sheet 标签名,如 "2025-RD01"
+    protected $sheetTitle;
     protected $data;
     protected $projectInfo;
     protected $year;
+    protected $dynamicHeaders;
 
-    public function __construct(string $sheetTitle, array $data, array $projectInfo = [])
+    public function __construct(string $sheetTitle, array $data, array $projectInfo = [], array $dynamicHeaders = [], string $year)
     {
         $this->sheetTitle = $sheetTitle;
         $this->data = $data;
         $this->projectInfo = $projectInfo;
-
-        // 自动从 Sheet 标题中提取前4位作为大标题年份
-        $this->year = substr($sheetTitle, 0, 4);
-    }
-
-    public function title(): string
-    {
-        return $this->sheetTitle;
-    }
-
-    public function startCell(): string
-    {
-        return 'A7'; // 数据从 A7 开始写入
+        $this->year = $year;
+        $this->dynamicHeaders = $dynamicHeaders;
     }
 
-    public function collection()
-    {
-        return collect($this->data);
-    }
+    public function title(): string { return $this->sheetTitle; }
+    public function startCell(): string { return 'A7'; }
+    public function collection() { return collect($this->data); }
 
     public function registerEvents(): array
     {
@@ -50,31 +41,18 @@ class ResearchExpenseSheetExport implements FromCollection, WithEvents, WithStyl
             AfterSheet::class => function(AfterSheet $event) {
                 $sheet = $event->sheet->getDelegate();
 
-                // --- 1. 设置列宽 (精简适中版) ---
-                $sheet->getColumnDimension('A')->setWidth(12);  // 日期
-                $sheet->getColumnDimension('B')->setWidth(6);   // 种类
-                $sheet->getColumnDimension('C')->setWidth(6);   // 号数
-                $sheet->getColumnDimension('D')->setWidth(30);  // 摘要
-                $sheet->getColumnDimension('E')->setWidth(14);  // 会计金额
-                $sheet->getColumnDimension('F')->setWidth(14);  // 税法金额
-                $sheet->getColumnDimension('G')->setWidth(13);  // 人员人工
-                $sheet->getColumnDimension('H')->setWidth(13);  // 直接投入
-                $sheet->getColumnDimension('I')->setWidth(11);  // 折旧
-                $sheet->getColumnDimension('J')->setWidth(11);  // 无形资产
-                $sheet->getColumnDimension('K')->setWidth(11);  // 新产品
-                $sheet->getColumnDimension('L')->setWidth(11);  // 其他
-                $sheet->getColumnDimension('M')->setWidth(18);  // 委托境内
-                $sheet->getColumnDimension('N')->setWidth(18);  // 委托境外
-
-                // --- 2. 设置行高 ---
-                $sheet->getRowDimension('2')->setRowHeight(45); // 大标题行
-                $sheet->getRowDimension('3')->setRowHeight(40); // 项目信息行
-                $sheet->getRowDimension('4')->setRowHeight(22); // 表头1
-                $sheet->getRowDimension('5')->setRowHeight(25); // 表头2
-                $sheet->getRowDimension('6')->setRowHeight(60); // 表头底行 (容纳长文字)
-
-                // --- 3. 第二行:大标题 (居中/加粗) ---
-                $sheet->mergeCells("A2:N2");
+                // --- 1. 计算列信息 ---
+                $baseColumnCount = 8; // A-H (日期 到 折旧)
+                $dynamicCount = count($this->dynamicHeaders);
+                $totalColumnCount = $baseColumnCount + $dynamicCount + 2; // 加上委托研发2列
+                $highestColumn = Coordinate::stringFromColumnIndex($totalColumnCount);
+                $highestRow = $sheet->getHighestRow(); // 获取最后一行(合计行)
+
+                // --- 2. 样式初始化:防止灰色块和默认填充 ---
+                $sheet->getStyle("A1:{$highestColumn}{$highestRow}")->getFill()->setFillType(Fill::FILL_NONE);
+
+                // --- 3. 大标题 (A2) ---
+                $sheet->mergeCells("A2:{$highestColumn}2");
                 $sheet->setCellValue('A2', "{$this->year}年研发支出辅助账");
                 $sheet->getStyle('A2')->applyFromArray([
                     'font' => ['size' => 16],
@@ -84,29 +62,17 @@ class ResearchExpenseSheetExport implements FromCollection, WithEvents, WithStyl
                     ],
                 ]);
 
-                // --- 4. 第三行:项目信息 (精准合并/无边框) ---
-                // 项目编号:标签 ABC,值 DE
+                // --- 4. 项目信息行 (A3) ---
                 $sheet->mergeCells("A3:C3");
-                $sheet->setCellValue('A3', '项目编号:');
-                $sheet->mergeCells("D3:E3");
-                $sheet->setCellValue('D3', $this->projectInfo['code'] ?? '');
-
-                // 项目名称:标签 F,值 G (开启换行)
-                $sheet->setCellValue('F3', '项目名称:');
-                $sheet->setCellValue('G3', $this->projectInfo['name'] ?? '');
-                $sheet->getStyle('G3')->getAlignment()->setWrapText(true);
-
-                $sheet->setCellValue('H3', '完成情况:');
-                $sheet->setCellValue('I3', '已完成');
-                $sheet->setCellValue('J3', '支出类型:');
-                $sheet->setCellValue('K3', '费用化');
-                $sheet->setCellValue('M3', '金额单位:');
-                $sheet->setCellValue('N3', '元');
-
-                // 第三行对齐
-                $sheet->getStyle('A3:N3')->getAlignment()->setVertical(Alignment::VERTICAL_CENTER);
-
-                // --- 5. 复杂表头绘制 (第 4-6 行) ---
+                $sheet->setCellValue('A3', '项目编号:' . ($this->projectInfo['code'] ?? ''));
+                $sheet->mergeCells("D3:G3");
+                $sheet->setCellValue('D3', '项目名称:' . ($this->projectInfo['name'] ?? ''));
+
+                // 单位放在最右边一列
+                $sheet->setCellValue($highestColumn . '3', '单位:元');
+                $sheet->getStyle($highestColumn . '3')->getAlignment()->setHorizontal(Alignment::HORIZONTAL_RIGHT);
+
+                // --- 5. 复杂表头绘制 (4-6行) ---
                 // A-D 凭证信息
                 $sheet->mergeCells("A4:D5");
                 $sheet->setCellValue('A4', '凭证信息');
@@ -115,34 +81,78 @@ class ResearchExpenseSheetExport implements FromCollection, WithEvents, WithStyl
                 $sheet->setCellValue('C6', '号数');
                 $sheet->setCellValue('D6', '摘要');
 
-                // E-F 金额列 (垂直合并)
+                // E-F 金额固定列 (垂直合并)
                 $sheet->mergeCells("E4:E6");
                 $sheet->setCellValue('E4', "会计凭证记载\n金额");
                 $sheet->mergeCells("F4:F6");
                 $sheet->setCellValue('F4', "税法规定的归\n集金额");
 
-                // G-N 费用明细总标题
-                $sheet->mergeCells("G4:N4");
+                // G-末尾: 费用明细总标题
+                $sheet->mergeCells("G4:{$highestColumn}4");
                 $sheet->setCellValue('G4', '费用明细(税法规定)');
 
-                // 各科目垂直合并 (5-6行)
-                $subItems = [
-                    'G' => '人员人工费用', 'H' => '直接投入费用', 'I' => '折旧费用',
-                    'J' => '无形资产摊销', 'K' => '新产品设计费等', 'L' => '其他相关费用'
-                ];
-                foreach ($subItems as $col => $text) {
-                    $sheet->mergeCells("{$col}5:{$col}6");
-                    $sheet->setCellValue("{$col}5", $text);
+                // 固定明细项:人员人工(G), 折旧(H)
+                $sheet->mergeCells("G5:G6"); $sheet->setCellValue("G5", '人员人工费用');
+                $sheet->mergeCells("H5:H6"); $sheet->setCellValue("H5", '折旧费用');
+
+                // 动态明细项 (从 I/第9列开始)
+                $currentColIdx = 9;
+                foreach ($this->dynamicHeaders as $headerText) {
+                    $colLetter = Coordinate::stringFromColumnIndex($currentColIdx);
+                    $sheet->mergeCells("{$colLetter}5:{$colLetter}6");
+                    $sheet->setCellValue("{$colLetter}5", $headerText);
+                    $currentColIdx++;
                 }
 
-                // 委托研发费用
-                $sheet->mergeCells("M5:N5");
-                $sheet->setCellValue('M5', '委托研发费用');
-                $sheet->setCellValue('M6', "委托境内机构或个人进行研\n发活动所发生的费用");
-                $sheet->setCellValue('N6', "委托境外机构进行研发活动\n所发生的费用");
+                // 委托研发 (最后两列)
+                $mCol = Coordinate::stringFromColumnIndex($currentColIdx);
+                $nCol = Coordinate::stringFromColumnIndex($currentColIdx + 1);
+                $sheet->mergeCells("{$mCol}5:{$nCol}5");
+                $sheet->setCellValue("{$mCol}5", '委托研发费用');
+                $sheet->setCellValue("{$mCol}6", "委托境内研发");
+                $sheet->setCellValue("{$nCol}6", "委托境外研发");
+
+                // --- 6. 强制刷新表头样式 (边框和居中) ---
+                $headerRange = "A4:{$highestColumn}6";
+                $sheet->getStyle($headerRange)->applyFromArray([
+                    'borders' => [
+                        'allBorders' => ['borderStyle' => Border::BORDER_THIN],
+                    ],
+                    'alignment' => [
+                        'wrapText' => true,
+                        'vertical' => Alignment::VERTICAL_CENTER,
+                        'horizontal' => Alignment::HORIZONTAL_CENTER,
+                    ],
+                ]);
+
+                // --- 7. 合计行特殊处理 (合并 A-D 列) ---
+                // 注意:由于数据从 A7 开始,且我们在 Controller 塞入了合计行,
+                // 所以 $highestRow 对应的就是那一行。
+                $sheet->mergeCells("A{$highestRow}:D{$highestRow}");
+                // 确保合计单元格样式加粗且有背景色
+                $footerRange = "A{$highestRow}:{$highestColumn}{$highestRow}";
+                $sheet->getStyle($footerRange)->applyFromArray([
+                    'fill' => [
+                        'fillType' => Fill::FILL_SOLID,
+                        'startColor' => ['argb' => 'FFF2F2F2'], // 灰色背景区分
+                    ],
+                    'borders' => [
+                        'allBorders' => ['borderStyle' => Border::BORDER_THIN],
+                    ],
+                ]);
+                $sheet->getStyle("A{$highestRow}")->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER);
+
+                // --- 8. 添加会计主管 (合计行下方空一行) ---
+                $supervisorRow = $highestRow + 1;
+                $sheet->setCellValue("A{$supervisorRow}", '会计主管:');
+                $sheet->getStyle("A{$supervisorRow}")->applyFromArray([
+                    'font' => ['size' => 10],
+                    'alignment' => ['horizontal' => Alignment::HORIZONTAL_LEFT]
+                ]);
 
-                // --- 6. 开启换行 ---
-                $sheet->getStyle('A4:N6')->getAlignment()->setWrapText(true);
+                // 设置行高
+                $sheet->getRowDimension('2')->setRowHeight(35);
+                $sheet->getRowDimension('6')->setRowHeight(45);
             },
         ];
     }
@@ -150,9 +160,12 @@ class ResearchExpenseSheetExport implements FromCollection, WithEvents, WithStyl
     public function styles(Worksheet $sheet)
     {
         $highestRow = $sheet->getHighestRow();
+        // 样式中也需要重新动态获取列,否则 styles 的作用范围会跟不上
+        $totalCol = 8 + count($this->dynamicHeaders) + 2;
+        $highestCol = Coordinate::stringFromColumnIndex($totalCol);
 
-        // 全局基础样式:从第 4 行开始加边框 (跳过第 3 行项目信息)
-        $sheet->getStyle("A4:N{$highestRow}")->applyFromArray([
+        // 数据区域边框 (从第7行开始)
+        $sheet->getStyle("A7:{$highestCol}{$highestRow}")->applyFromArray([
             'borders' => [
                 'allBorders' => ['borderStyle' => Border::BORDER_THIN],
             ],

+ 103 - 0
app/Service/ExportFileService.php

@@ -8,6 +8,7 @@ use App\Exports\ItemSalarySheetExport;
 use App\Exports\ManMonthlyWorkHourMultipleSheetExport;
 use App\Exports\MultiSheetExport;
 use App\Exports\ProjectDepreciationMultipleSheetExport;
+use App\Exports\ResearchExpenseMultipleSheetExport;
 use App\Model\Depart;
 use App\Model\PLeaveOverOrder;
 use Maatwebsite\Excel\Facades\Excel;
@@ -361,6 +362,7 @@ class ExportFileService extends Service
         return [true, $this->saveExportData($return,$header)];
     }
 
+    // 项目工资统计表
     public function exportEmployeeSalary($data, $user)
     {
         $service = new StatisticService();
@@ -420,6 +422,7 @@ class ExportFileService extends Service
         return [true, $filename];
     }
 
+    // 人员月工时统计表
     public function exportManMonthlyWorkHour($data, $user)
     {
         // 1. 获取报表基础数据
@@ -486,6 +489,7 @@ class ExportFileService extends Service
         return [true, $filename];
     }
 
+    // 项目设备折旧
     public function exportDeviceZj(array $data, $user)
     {
         $service = new StatisticService();
@@ -542,6 +546,7 @@ class ExportFileService extends Service
         return [true, $filename];
     }
 
+    // 项目工资分摊
     public function exportItemSalaryFT(array $data, $user)
     {
         $service = new StatisticService();
@@ -635,6 +640,104 @@ class ExportFileService extends Service
         return [true, $filename];
     }
 
+    // 年度研发支出辅助账汇总表
+    public function exportResearchExpense(array $data, $user)
+    {
+        $service = new StatisticService();
+        list($status, $result) = $service->auxiliaryStatistic($data, $user);
+        if (!$status) return [false, $result];
+
+        $fee_type_list = $result['fee_type_list'];
+        $raw_list = $result['list'];
+        if(empty($raw_list)) return [false, '暂无导出数据'];
+
+        // 预读年份和动态表头长度
+        $year = date("Y", strtotime($raw_list[0]['voucher_date']));
+        $company = Depart::where('id', $user['top_depart_id'])->value('title');
+        $dynamicHeaderTitles = array_column($fee_type_list, 'title');
+        $dynamicCount = count($dynamicHeaderTitles);
+        $totalColCount = 8 + $dynamicCount + 2; // 基础6 + 固定2 + 动态N + 委托2
+
+        $groupedData = [];
+
+        foreach ($raw_list as $row) {
+            $sheetKey = $year . $row['code'];
+
+            if (!isset($groupedData[$sheetKey])) {
+                $groupedData[$sheetKey] = [
+                    'project' => [
+                        'code' => $row['code'],
+                        'name' => $row['title'],
+                    ],
+                    'dynamic_headers' => $dynamicHeaderTitles,
+                    'data' => [],
+                    'year' => $company . $year,
+                    'totals' => array_fill(0, $totalColCount, 0)
+                ];
+                $groupedData[$sheetKey]['totals'][0] = '合计'; // 第一列标识
+            }
+
+            // 组织明细行数据 (逻辑不变)
+            $excelRow = [
+                $row['voucher_date'], $row['voucher_type'], $row['voucher_no'], $row['voucher_remark'],
+                (float)$row['voucher_amount'],
+                (float)$row['aggregation_amount'],
+                ($row['type'] == 1 ? (float)$row['total_amount'] : 0), // 人员
+                ($row['type'] == 2 ? (float)$row['total_amount'] : 0)  // 折旧
+            ];
+
+            // 动态列填充
+            foreach ($fee_type_list as $feeId => $feeItem) {
+                $excelRow[] = ($row['type'] == 3 && $row['fee_id'] == $feeId) ? (float)$row['total_amount'] : 0;
+            }
+
+            // 委托列
+            $excelRow[] = (float)($row['entrust1_amount'] ?? 0);
+            $excelRow[] = (float)($row['entrust2_amount'] ?? 0);
+
+            // 【性能优化】:同步累加金额 (从索引 4 开始是金额列)
+            for ($i = 4; $i < $totalColCount; $i++) {
+                $groupedData[$sheetKey]['totals'][$i] += $excelRow[$i];
+            }
+
+            $groupedData[$sheetKey]['data'][] = $excelRow;
+        }
+
+        foreach ($groupedData as &$group) {
+            $group['data'][] = $group['totals'];
+            unset($group['totals']); // 释放内存
+        }
+
+        // 8. 导出逻辑保持不变
+        $file_name = "年度研发支出辅助账汇总统计表_" . date("Y-m-d") . "_". rand(1000,9999);
+        $filename =  $file_name . '.xlsx';
+
+        Excel::store(
+            new ResearchExpenseMultipleSheetExport($groupedData),
+            "public/export/{$filename}"
+        );
+
+        return [true, $filename];
+    }
+
+    /**
+     * 辅助方法:计算合计行
+     */
+    private function calculateTotalRow(array $data, int $dynamicCount)
+    {
+        $totalColumnCount = 8 + $dynamicCount + 2;
+        $totals = array_fill(0, $totalColumnCount, 0);
+        $totals[0] = '合计';
+        $totals[1] = $totals[2] = $totals[3] = '';
+
+        foreach ($data as $row) {
+            for ($i = 4; $i < $totalColumnCount; $i++) {
+                $totals[$i] += (float)($row[$i] ?? 0);
+            }
+        }
+        return $totals;
+    }
+
     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';