ResearchExpenseSummarySheetExport.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. <?php
  2. namespace App\Exports;
  3. use Maatwebsite\Excel\Concerns\WithEvents;
  4. use Maatwebsite\Excel\Concerns\WithTitle;
  5. use Maatwebsite\Excel\Events\AfterSheet;
  6. use PhpOffice\PhpSpreadsheet\Style\Alignment;
  7. use PhpOffice\PhpSpreadsheet\Style\Border;
  8. use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
  9. class ResearchExpenseSummarySheetExport implements WithEvents, WithTitle
  10. {
  11. protected $year;
  12. protected $items;
  13. protected $taxId;
  14. protected $companyName;
  15. protected $dynamicHeaders; // 动态传入的明细科目
  16. public function __construct($year, array $payload, array $dynamicHeaders = [])
  17. {
  18. $this->year = $year;
  19. $this->items = $payload['items'] ?? [];
  20. $this->taxId = $payload['tax_id'] ?? '';
  21. $this->companyName = $payload['company_name'] ?? '';
  22. // 确保至少包含基础的:人员人工, 折旧
  23. $this->dynamicHeaders = $dynamicHeaders ?: ['人员人工费用', '折旧费用'];
  24. }
  25. public function title(): string { return $this->year . '年汇总表'; }
  26. public function registerEvents(): array
  27. {
  28. return [
  29. AfterSheet::class => function (AfterSheet $event) {
  30. $sheet = $event->sheet->getDelegate();
  31. // 1. 计算关键列的字母索引
  32. $baseColCount = 4; // A:编号, B:名称, C:完成, D:类型
  33. $totalDynamic = count($this->dynamicHeaders);
  34. // 允许加计扣除合计列 (E)
  35. $col_E = 'E';
  36. // 明细科目起始列 (F开始)
  37. $startDetailIdx = 6;
  38. // 小计列 (6列)
  39. $col_6 = Coordinate::stringFromColumnIndex($startDetailIdx + $totalDynamic);
  40. // 7.1列 (其他相关费用合计)
  41. $col_7_1 = Coordinate::stringFromColumnIndex($startDetailIdx + $totalDynamic + 1);
  42. // 7.2列 (调整后)
  43. $col_7_2 = Coordinate::stringFromColumnIndex($startDetailIdx + $totalDynamic + 2);
  44. // 8.1列 (委托境内)
  45. $col_8_1 = Coordinate::stringFromColumnIndex($startDetailIdx + $totalDynamic + 3);
  46. // 8.2列 (8.1*80%)
  47. $col_8_2 = Coordinate::stringFromColumnIndex($startDetailIdx + $totalDynamic + 4);
  48. // 8.3列 (委托境外)
  49. $col_8_3 = Coordinate::stringFromColumnIndex($startDetailIdx + $totalDynamic + 5);
  50. // 8.4列 (调整后境外)
  51. $col_8_4 = Coordinate::stringFromColumnIndex($startDetailIdx + $totalDynamic + 6);
  52. $highestColumn = $col_8_4;
  53. // 2. 设置列宽
  54. $sheet->getColumnDimension('B')->setWidth(40);
  55. for ($i = 1; $i <= Coordinate::columnIndexFromString($highestColumn); $i++) {
  56. $col = Coordinate::stringFromColumnIndex($i);
  57. if ($col != 'B') $sheet->getColumnDimension($col)->setWidth(12);
  58. }
  59. // 3. 绘制表头
  60. $this->drawDynamicHeader($sheet, $col_E, $col_6, $col_7_1, $col_8_1, $highestColumn);
  61. // 4. 填充明细数据
  62. $currentRow = 8;
  63. $capRows = []; $expRows = [];
  64. foreach ($this->items as $item) {
  65. $sheet->setCellValue("A{$currentRow}", $item['no']);
  66. $sheet->setCellValue("B{$currentRow}", $item['name']);
  67. $sheet->setCellValue("C{$currentRow}", $item['status'] == 3 ? '已完成' : '进行中');
  68. $sheet->setCellValue("D{$currentRow}", $item['type']);
  69. // 填充动态明细列 (F -> 小计前)
  70. foreach ($this->dynamicHeaders as $idx => $header) {
  71. $col = Coordinate::stringFromColumnIndex($startDetailIdx + $idx);
  72. $sheet->setCellValue("{$col}{$currentRow}", $item['values'][$idx] ?? 0);
  73. }
  74. // 填充固定逻辑列
  75. $sheet->setCellValue("{$col_7_1}{$currentRow}", $item['val7_1'] ?? 0);
  76. $sheet->setCellValue("{$col_8_1}{$currentRow}", $item['val8_1'] ?? 0);
  77. $sheet->setCellValue("{$col_8_3}{$currentRow}", $item['val8_3'] ?? 0);
  78. // 行公式
  79. $firstDetailCol = Coordinate::stringFromColumnIndex($startDetailIdx);
  80. $lastDetailCol = Coordinate::stringFromColumnIndex($startDetailIdx + $totalDynamic - 1);
  81. $sheet->setCellValue("{$col_6}{$currentRow}", "=SUM({$firstDetailCol}{$currentRow}:{$lastDetailCol}{$currentRow})");
  82. $sheet->setCellValue("{$col_7_2}{$currentRow}", "={$col_7_1}{$currentRow}");
  83. $sheet->setCellValue("{$col_8_2}{$currentRow}", "={$col_8_1}{$currentRow}*0.8");
  84. $sheet->setCellValue("{$col_8_4}{$currentRow}", "={$col_8_3}{$currentRow}");
  85. // E = 6 + 7.2 + 8.2 + 8.4
  86. $sheet->setCellValue("{$col_E}{$currentRow}", "={$col_6}{$currentRow}+{$col_7_2}{$currentRow}+{$col_8_2}{$currentRow}+{$col_8_4}{$currentRow}");
  87. if ($item['type'] == '资本化支出' && $item['status'] == 3) $capRows[] = $currentRow;
  88. elseif ($item['type'] == '费用化支出') $expRows[] = $currentRow;
  89. $currentRow++;
  90. }
  91. // 5. 应用合计勾稽逻辑
  92. $this->applyDynamicSummaryLogic($sheet, $currentRow, $capRows, $expRows, $startDetailIdx, $totalDynamic, $col_E, $col_6, $col_7_1, $col_7_2, $col_8_1, $col_8_2, $col_8_3, $col_8_4);
  93. // 6. 最终样式
  94. $lastDataRow = $currentRow + 3;
  95. $sheet->getStyle("A5:{$highestColumn}{$lastDataRow}")->applyFromArray(['borders' => ['allBorders' => ['borderStyle' => Border::BORDER_THIN]]]);
  96. $sheet->getStyle("E8:{$highestColumn}{$lastDataRow}")->getNumberFormat()->setFormatCode('#,##0.00');
  97. $sheet->getStyle("A5:{$highestColumn}7")->getAlignment()->setWrapText(true)->setHorizontal('center')->setVertical('center');
  98. },
  99. ];
  100. }
  101. private function drawDynamicHeader($sheet, $col_E, $col_6, $col_7_1, $col_8_1, $highestColumn)
  102. {
  103. // --- 1. 大标题 (第2行) ---
  104. $sheet->mergeCells("A2:{$highestColumn}2");
  105. $sheet->setCellValue('A2', '研发支出辅助账汇总表');
  106. $sheet->getStyle('A2')->applyFromArray([
  107. 'font' => ['size' => 16, 'bold' => true],
  108. 'alignment' => [
  109. 'horizontal' => Alignment::HORIZONTAL_CENTER,
  110. 'vertical' => Alignment::VERTICAL_CENTER,
  111. ],
  112. ]);
  113. // --- 2. 项目信息行 (第3行) ---
  114. // 纳税人识别号 (靠左)
  115. $sheet->setCellValue('A3', '纳税人识别号(统一社会信用代码):' . $this->taxId);
  116. // 纳税人名称 (居中偏右,通常定位在 J 列附近,或者根据动态列数灵活调整)
  117. $nameCol = Coordinate::stringFromColumnIndex(max(10, count($this->dynamicHeaders) + 4));
  118. $sheet->setCellValue($nameCol . '3', '纳税人名称:' . $this->companyName);
  119. // 属期 (定位在倒数第 2 或第 3 列)
  120. $periodCol = Coordinate::stringFromColumnIndex(Coordinate::columnIndexFromString($highestColumn) - 2);
  121. $sheet->setCellValue($periodCol . '3', "属期:{$this->year}年");
  122. // 单位 (固定最右侧)
  123. $sheet->setCellValue($highestColumn . '3', '单位:元');
  124. $sheet->getStyle($highestColumn . '3')->getAlignment()->setHorizontal(Alignment::HORIZONTAL_RIGHT);
  125. // --- 3. 第4行留空 (保持默认即可,不赋值) ---
  126. // --- 4. 表头 (5-7行) ---
  127. // A-E 固定列
  128. $vHeaders = ['A'=>'项目编号', 'B'=>'项目名称', 'C'=>'完成情况', 'D'=>'支出类型', 'E'=>"允许加计\n扣除金额合计"];
  129. foreach ($vHeaders as $col => $text) {
  130. $sheet->mergeCells("{$col}5:{$col}7");
  131. $sheet->setCellValue("{$col}5", $text);
  132. }
  133. // 动态明细表头 (F列开始)
  134. $startIdx = 6;
  135. foreach ($this->dynamicHeaders as $idx => $text) {
  136. $col = Coordinate::stringFromColumnIndex($startIdx + $idx);
  137. $sheet->mergeCells("{$col}5:{$col}6");
  138. $sheet->setCellValue("{$col}5", $text);
  139. $sheet->setCellValue("{$col}7", $idx + 1);
  140. }
  141. // 小计列
  142. $sheet->mergeCells("{$col_6}5:{$col_6}6");
  143. $sheet->setCellValue("{$col_6}5", "前" . count($this->dynamicHeaders) . "项 小计");
  144. $sheet->setCellValue("{$col_6}7", count($this->dynamicHeaders) + 1);
  145. // 其他相关费用 (7.1, 7.2)
  146. $col_7_2 = Coordinate::stringFromColumnIndex(Coordinate::columnIndexFromString($col_7_1) + 1);
  147. $sheet->mergeCells("{$col_7_1}5:{$col_7_2}5");
  148. $sheet->setCellValue("{$col_7_1}5", '其他相关费用及限额');
  149. $sheet->setCellValue("{$col_7_1}6", '其他相关费用合计');
  150. $sheet->setCellValue("{$col_7_1}7", '7.1');
  151. $sheet->setCellValue("{$col_7_2}6", "经限额调整后的其他相关费用");
  152. $sheet->setCellValue("{$col_7_2}7", '7.2');
  153. // 委托研发 (8.1-8.4)
  154. $sheet->mergeCells("{$col_8_1}5:{$highestColumn}5");
  155. $sheet->setCellValue("{$col_8_1}5", '委托研发费用及限额');
  156. $startColIdx = Coordinate::columnIndexFromString($col_8_1);
  157. $subHeaderNames = [
  158. '委托境内机构或个人进行研发活动所发生的费用',
  159. '允许加计扣除的委托境内机构或个人进行研发活动所发生的费用',
  160. '委托境外机构进行研发活动所发生的费用',
  161. '经限额调整后的委托境外机构进行研发活动所发生的费用'
  162. ];
  163. $nums = ['8.1','8.2','8.3','8.4'];
  164. for($i=0; $i<4; $i++) {
  165. $colLetter = Coordinate::stringFromColumnIndex($startColIdx + $i);
  166. $sheet->setCellValue($colLetter . '6', $subHeaderNames[$i]);
  167. $sheet->setCellValue($colLetter . '7', $nums[$i]);
  168. }
  169. // 设置第3行行高和垂直居中
  170. $sheet->getRowDimension('3')->setRowHeight(25);
  171. $sheet->getStyle("A3:{$highestColumn}3")->getAlignment()->setVertical(Alignment::VERTICAL_CENTER);
  172. }
  173. private function applyDynamicSummaryLogic($sheet, $r, $capRows, $expRows, $startIdx, $dynamicCount, $col_E, $col_6, $col_7_1, $col_7_2, $col_8_1, $col_8_2, $col_8_3, $col_8_4)
  174. {
  175. $r_cap = $r; $r_exp = $r + 1; $r_oth = $r + 2; $r_all = $r + 3;
  176. $labels = ['资本化金额小计', '费用化金额小计', '其中:其他事项', '全额合计'];
  177. foreach ($labels as $i => $label) {
  178. $sheet->mergeCells("A" . ($r+$i) . ":D" . ($r+$i));
  179. $sheet->setCellValue("A" . ($r+$i), $label);
  180. }
  181. // 所有需要汇总计算的数值列
  182. $allValCols = [];
  183. for($i=0; $i<$dynamicCount; $i++) { $allValCols[] = Coordinate::stringFromColumnIndex($startIdx + $i); }
  184. $allValCols = array_merge($allValCols, [$col_7_1, $col_8_1, $col_8_3]);
  185. foreach ($allValCols as $col) {
  186. $f_cap = !empty($capRows) ? "=SUM(".implode(',', array_map(fn($row)=>"{$col}{$row}", $capRows)).")" : "0";
  187. $f_exp = !empty($expRows) ? "=SUM(".implode(',', array_map(fn($row)=>"{$col}{$row}", $expRows)).") + {$col}{$r_oth}" : "{$col}{$r_oth}";
  188. $sheet->setCellValue("{$col}{$r_cap}", $f_cap);
  189. $sheet->setCellValue("{$col}{$r_exp}", $f_exp);
  190. $sheet->setCellValue("{$col}{$r_all}", "={$col}{$r_cap}+{$col}{$r_exp}");
  191. }
  192. // 核心勾稽公式应用
  193. $firstDetailCol = Coordinate::stringFromColumnIndex($startIdx);
  194. $lastDetailCol = Coordinate::stringFromColumnIndex($startIdx + $dynamicCount - 1);
  195. foreach ([$r_cap, $r_exp, $r_all] as $row) {
  196. $sheet->setCellValue("{$col_6}{$row}", "=SUM({$firstDetailCol}{$row}:{$lastDetailCol}{$row})");
  197. $sheet->setCellValue("{$col_8_2}{$row}", "={$col_8_1}{$row}*0.8");
  198. // 规则六:7.2列 (限额调整)
  199. if ($row == $r_all) {
  200. $sheet->setCellValue("{$col_7_2}{$row}", "=MIN({$col_7_1}{$row}, {$col_6}{$row}*0.1/0.9)");
  201. } else {
  202. $sheet->setCellValue("{$col_7_2}{$row}", "=IF({$col_7_1}{$r_all}>0, ({$col_7_2}{$r_all}/{$col_7_1}{$r_all})*{$col_7_1}{$row}, 0)");
  203. }
  204. // 规则八:8.4列 (境外限额)
  205. if ($row == $r_all) {
  206. $sheet->setCellValue("{$col_8_4}{$row}", "=MIN(({$col_6}{$row}+{$col_7_2}{$row}+{$col_8_2}{$row})*2/3, {$col_8_3}{$row}*0.8)");
  207. } else {
  208. $sheet->setCellValue("{$col_8_4}{$row}", "=IF({$col_8_3}{$r_all}>0, ({$col_8_4}{$r_all}/{$col_8_3}{$r_all})*{$col_8_3}{$row}, 0)");
  209. }
  210. // 最终合计列 E
  211. $sheet->setCellValue("{$col_E}{$row}", "={$col_6}{$row}+{$col_7_2}{$row}+{$col_8_2}{$row}+{$col_8_4}{$row}");
  212. }
  213. }
  214. }