ResearchExpenseSummarySheetExport.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  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. $this->dynamicHeaders = $dynamicHeaders ?: ['人员人工费用', '折旧费用'];
  23. }
  24. public function title(): string { return $this->year . '年汇总表'; }
  25. public function registerEvents(): array
  26. {
  27. return [
  28. AfterSheet::class => function (AfterSheet $event) {
  29. $sheet = $event->sheet->getDelegate();
  30. // 1. 计算列索引(因插入了开始和结束时间,核心列整体右移 2 列)
  31. $totalDynamic = count($this->dynamicHeaders);
  32. $startDetailIdx = 8; // 动态科目从第 8 列(H列)开始
  33. $col_G = 'G'; // 允许加计扣除金额合计列(右移至 G 列)
  34. $col_6 = Coordinate::stringFromColumnIndex($startDetailIdx + $totalDynamic);
  35. $col_7_1 = Coordinate::stringFromColumnIndex($startDetailIdx + $totalDynamic + 1);
  36. $col_7_2 = Coordinate::stringFromColumnIndex($startDetailIdx + $totalDynamic + 2);
  37. $col_8_1 = Coordinate::stringFromColumnIndex($startDetailIdx + $totalDynamic + 3);
  38. $col_8_2 = Coordinate::stringFromColumnIndex($startDetailIdx + $totalDynamic + 4);
  39. $col_8_3 = Coordinate::stringFromColumnIndex($startDetailIdx + $totalDynamic + 5);
  40. $col_8_4 = Coordinate::stringFromColumnIndex($startDetailIdx + $totalDynamic + 6);
  41. $highestColumn = $col_8_4;
  42. // 2. 设置布局
  43. $sheet->getColumnDimension('B')->setWidth(40);
  44. $sheet->getColumnDimension('D')->setWidth(15); // 开始时间列宽
  45. $sheet->getColumnDimension('E')->setWidth(15); // 结束时间列宽
  46. $lastColNum = Coordinate::columnIndexFromString($highestColumn);
  47. for ($i = 1; $i <= $lastColNum; $i++) {
  48. $col = Coordinate::stringFromColumnIndex($i);
  49. if (!in_array($col, ['B', 'D', 'E'])) {
  50. $sheet->getColumnDimension($col)->setWidth(13);
  51. }
  52. }
  53. // 3. 绘制表头
  54. $this->drawDynamicHeader($sheet, $col_G, $col_6, $col_7_1, $col_8_1, $highestColumn);
  55. // 4. 填充明细
  56. $currentRow = 8;
  57. $capRows = []; $expRows = [];
  58. foreach ($this->items as $item) {
  59. $sheet->setCellValue("A{$currentRow}", $item['no']);
  60. $sheet->setCellValue("B{$currentRow}", $item['name']);
  61. $sheet->setCellValue("C{$currentRow}", $item['status'] == 3 ? '已完成' : '进行中');
  62. // 新增的时间列注入
  63. $sheet->setCellValue("D{$currentRow}", $item['start_time'] ?? '');
  64. $sheet->setCellValue("E{$currentRow}", $item['end_time'] ?? '');
  65. $sheet->setCellValue("F{$currentRow}", $item['type']);
  66. // 动态明细从第 8 列(H列)向右填充
  67. foreach ($this->dynamicHeaders as $idx => $header) {
  68. $col = Coordinate::stringFromColumnIndex($startDetailIdx + $idx);
  69. $sheet->setCellValue("{$col}{$currentRow}", $item['values'][$idx] ?? 0);
  70. }
  71. // 固定数值列(由传来数据赋值)
  72. $sheet->setCellValue("{$col_7_1}{$currentRow}", $item['val7_1'] ?? 0);
  73. $sheet->setCellValue("{$col_7_2}{$currentRow}", $item['val7_2'] ?? 0);
  74. $sheet->setCellValue("{$col_8_1}{$currentRow}", $item['val8_1'] ?? 0);
  75. $sheet->setCellValue("{$col_8_3}{$currentRow}", $item['val8_3'] ?? 0);
  76. // 行公式
  77. $firstDetailCol = Coordinate::stringFromColumnIndex($startDetailIdx);
  78. $lastDetailCol = Coordinate::stringFromColumnIndex($startDetailIdx + $totalDynamic - 1);
  79. // 前n项之和公式
  80. $sheet->setCellValue("{$col_6}{$currentRow}", "=SUM({$firstDetailCol}{$currentRow}:{$lastDetailCol}{$currentRow})");
  81. // 委托研发八折
  82. $sheet->setCellValue("{$col_8_2}{$currentRow}", "={$col_8_1}{$currentRow}*0.8");
  83. $sheet->setCellValue("{$col_8_4}{$currentRow}", "={$col_8_3}{$currentRow}*0.8");
  84. // ⭐ 只改 G 列(原E列)的公式逻辑,严格匹配原判断逻辑
  85. $sheet->setCellValue(
  86. "{$col_G}{$currentRow}",
  87. "=IF(({$col_6}{$currentRow}+{$col_7_1}{$currentRow})*0.1 < {$col_7_1}{$currentRow}, ({$col_6}{$currentRow}+{$col_7_1}{$currentRow})*0.1 + {$col_6}{$currentRow}, {$col_7_1}{$currentRow} + {$col_6}{$currentRow})"
  88. );
  89. // 严格清洗类型字符串进行归集
  90. $cleanType = preg_replace('/[\s\v\t\r\n]+/u', '', (string)$item['type']);
  91. if ($cleanType === '资本化支出' && $item['status'] == 3) {
  92. $capRows[] = $currentRow;
  93. } elseif ($cleanType === '费用化支出') {
  94. $expRows[] = $currentRow;
  95. }
  96. $currentRow++;
  97. }
  98. // 5. 应用底部合计逻辑
  99. $this->applyDynamicSummaryLogic($sheet, $currentRow, $capRows, $expRows, $startDetailIdx, $totalDynamic, $col_G, $col_6, $col_7_1, $col_7_2, $col_8_1, $col_8_2, $col_8_3, $col_8_4);
  100. // 6. 最终样式区域调整
  101. $lastDataRow = $currentRow + 3;
  102. $sheet->getStyle("A5:{$highestColumn}{$lastDataRow}")->applyFromArray(['borders' => ['allBorders' => ['borderStyle' => Border::BORDER_THIN]]]);
  103. $sheet->getStyle("G8:{$highestColumn}{$lastDataRow}")->getNumberFormat()->setFormatCode('#,##0.00'); // 从G列开始应用财务数字格式
  104. $sheet->getStyle("A5:{$highestColumn}7")->getAlignment()->setWrapText(true)->setHorizontal('center')->setVertical('center');
  105. },
  106. ];
  107. }
  108. private function drawDynamicHeader($sheet, $col_G, $col_6, $col_7_1, $col_8_1, $highestColumn)
  109. {
  110. $sheet->mergeCells("A2:{$highestColumn}2");
  111. $sheet->setCellValue('A2', '研发支出辅助账汇总表');
  112. $sheet->getStyle('A2')->applyFromArray(['font' => ['size' => 16, 'bold' => true], 'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER]]);
  113. // 第3行企业基本信息布局
  114. $totalCols = Coordinate::columnIndexFromString($highestColumn);
  115. $sheet->setCellValue('A3', '纳税人识别号(统一社会信用代码):' . $this->taxId);
  116. $nameCol = Coordinate::stringFromColumnIndex(max(9, floor($totalCols * 0.55)));
  117. $sheet->setCellValue($nameCol . '3', '纳税人名称:' . $this->companyName);
  118. $periodCol = Coordinate::stringFromColumnIndex($totalCols - 3);
  119. $sheet->setCellValue($periodCol . '3', "属期:{$this->year}年");
  120. $sheet->setCellValue($highestColumn . '3', '单位:元');
  121. $sheet->getStyle($highestColumn . '3')->getAlignment()->setHorizontal(Alignment::HORIZONTAL_RIGHT);
  122. // 表头 5-7 行(重排并插入了时间列)
  123. $vHeaders = [
  124. 'A' => '项目编号',
  125. 'B' => '项目名称',
  126. 'C' => '完成情况',
  127. 'D' => "开始时间",
  128. 'E' => "结束时间",
  129. 'F' => '支出类型',
  130. 'G' => "允许加计\n扣除金额合计"
  131. ];
  132. foreach ($vHeaders as $col => $text) {
  133. $sheet->mergeCells("{$col}5:{$col}7");
  134. $sheet->setCellValue("{$col}5", $text);
  135. }
  136. $startIdx = 8; // 动态科目调整至 H列(第8列)开始
  137. foreach ($this->dynamicHeaders as $idx => $text) {
  138. $col = Coordinate::stringFromColumnIndex($startIdx + $idx);
  139. $sheet->mergeCells("{$col}5:{$col}6");
  140. $sheet->setCellValue("{$col}5", $text);
  141. $sheet->setCellValue("{$col}7", $idx + 1);
  142. }
  143. $sheet->mergeCells("{$col_6}5:{$col_6}6");
  144. $sheet->setCellValue("{$col_6}5", "前" . count($this->dynamicHeaders) . "项小计");
  145. $sheet->setCellValue("{$col_6}7", count($this->dynamicHeaders) + 1);
  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", '其他相关费用合计'); $sheet->setCellValue("{$col_7_1}7", '7.1');
  150. $sheet->setCellValue("{$col_7_2}6", "经限额调整后的其他相关费用"); $sheet->setCellValue("{$col_7_2}7", '7.2');
  151. $sheet->mergeCells("{$col_8_1}5:{$highestColumn}5");
  152. $sheet->setCellValue("{$col_8_1}5", '委托研发费用及限额');
  153. $sIdx = Coordinate::columnIndexFromString($col_8_1);
  154. $subs = ['委托境内机构或个人进行研发 activity 所发生的费用', '允许加计扣除的委托境内机构或个人进行研发活动所发生的费用', '委托境外机构进行研发活动所发生的费用', '经限额调整后的委托境外机构进行研发活动所发生的费用'];
  155. $nums = ['8.1','8.2','8.3','8.4'];
  156. for($i=0;$i<4;$i++) {
  157. $c = Coordinate::stringFromColumnIndex($sIdx + $i);
  158. $sheet->setCellValue($c . '6', $subs[$i]);
  159. $sheet->setCellValue($c . '7', $nums[$i]);
  160. }
  161. }
  162. private function applyDynamicSummaryLogic($sheet, $r, $capRows, $expRows, $startIdx, $dynamicCount, $col_G, $col_6, $col_7_1, $col_7_2, $col_8_1, $col_8_2, $col_8_3, $col_8_4)
  163. {
  164. $r_cap = $r; $r_exp = $r + 1; $r_oth = $r + 2; $r_all = $r + 3;
  165. $labels = ['资本化金额小计', '费用化金额小计', '其中:其他事项', '全额合计'];
  166. foreach ($labels as $i => $label) {
  167. // 合计行的左侧文本合并范围同步扩展到 A-F 列(第1到第6列)
  168. $sheet->mergeCells("A" . ($r+$i) . ":F" . ($r+$i));
  169. $sheet->setCellValue("A" . ($r+$i), $label);
  170. }
  171. $allValCols = [];
  172. for($i=0; $i<$dynamicCount; $i++) { $allValCols[] = Coordinate::stringFromColumnIndex($startIdx + $i); }
  173. $allValCols = array_merge($allValCols, [$col_7_1, $col_8_1, $col_8_3]);
  174. foreach ($allValCols as $col) {
  175. $f_cap = !empty($capRows) ? "=SUM(".implode(',', array_map(fn($row)=>"{$col}{$row}", $capRows)).")" : "0";
  176. $f_exp_base = !empty($expRows) ? "SUM(".implode(',', array_map(fn($row)=>"{$col}{$row}", $expRows)).")" : "0";
  177. $sheet->setCellValue("{$col}{$r_cap}", $f_cap);
  178. $sheet->setCellValue("{$col}{$r_exp}", "={$f_exp_base}+{$col}{$r_oth}");
  179. $sheet->setCellValue("{$col}{$r_all}", "={$col}{$r_cap}+{$col}{$r_exp}");
  180. }
  181. $fCol = Coordinate::stringFromColumnIndex($startIdx);
  182. $lCol = Coordinate::stringFromColumnIndex($startIdx + $dynamicCount - 1);
  183. foreach ([$r_cap, $r_exp, $r_all] as $row) {
  184. $sheet->setCellValue("{$col_6}{$row}", "=SUM({$fCol}{$row}:{$lCol}{$row})");
  185. $sheet->setCellValue("{$col_8_2}{$row}", "={$col_8_1}{$row}*0.8");
  186. if ($row == $r_all) {
  187. $sheet->setCellValue("{$col_7_2}{$row}", "=MIN({$col_7_1}{$row}, {$col_6}{$row}*0.1/0.9)");
  188. } else {
  189. $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)");
  190. }
  191. if ($row == $r_all) {
  192. $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)");
  193. } else {
  194. $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)");
  195. }
  196. // 底部小计求和联动更新为新的 $col_G
  197. $sheet->setCellValue("{$col_G}{$row}", "={$col_6}{$row}+{$col_7_2}{$row}+{$col_8_2}{$row}+{$col_8_4}{$row}");
  198. }
  199. }
  200. }