find($contractSignId); if (!$contractSign) { throw new \Exception('合同签署记录不存在'); } // 2. 获取填充数据 $fillData = json_decode($contractSign['fill_data'], true); if (!$fillData) { throw new \Exception('填充数据格式错误'); } // 3. 生成Word文档 $generatedFile = $this->generateWordDocument($contractSign, $fillData); // 4. 更新生成记录 $this->updateGenerateLog($contractSignId, 'completed', $generatedFile); return true; } catch (\Exception $e) { $this->updateGenerateLog($contractSignId, 'failed', null, $e->getMessage()); return false; } } /** * 生成Word文档 * @param ContractSign $contractSign 合同签署记录 * @param array $fillData 填充数据 * @return string 生成的文件路径 * @throws \Exception */ private function generateWordDocument(ContractSign $contractSign, array $fillData): string { // 1. 获取合同模板 $contract = \app\model\contract\Contract::find($contractSign['contract_id']); if (!$contract) { throw new \Exception('合同模板不存在'); } $templatePath = public_path() . '/' . $contract['file_path']; if (!file_exists($templatePath)) { throw new \Exception('模板文件不存在:' . $templatePath); } // 2. 获取数据源配置 $configs = \app\model\document\DocumentDataSourceConfig::where('contract_id', $contractSign['contract_id']) ->select() ->toArray(); // 3. 构建替换数据 $replacements = []; foreach ($configs as $config) { $placeholder = '{{' . $config['placeholder'] . '}}'; $value = $fillData[$config['field_name']] ?? $config['default_value'] ?? ''; $replacements[$placeholder] = $this->formatValue($value, $config['field_type']); } // 4. 创建输出目录 $outputDir = 'generated/' . date('Y/m/d/'); $fullOutputDir = public_path() . '/' . $outputDir; if (!is_dir($fullOutputDir)) { mkdir($fullOutputDir, 0755, true); } // 5. 生成文件名 $fileName = time() . '_' . $contractSign['personnel_id'] . '_contract.docx'; $outputPath = $outputDir . $fileName; $fullOutputPath = public_path() . '/' . $outputPath; // 6. 使用PhpWord处理文档 try { $phpWord = \PhpOffice\PhpWord\IOFactory::load($templatePath); // 7. 替换占位符 $this->replaceDocumentPlaceholders($phpWord, $replacements); // 8. 保存文档 $writer = \PhpOffice\PhpWord\IOFactory::createWriter($phpWord, 'Word2007'); $writer->save($fullOutputPath); // 9. 验证文件是否生成成功 if (!file_exists($fullOutputPath)) { throw new \Exception('文档生成失败'); } return $outputPath; } catch (\Exception $e) { throw new \Exception('文档处理失败:' . $e->getMessage()); } } /** * 替换文档中的占位符 * @param \PhpOffice\PhpWord\PhpWord $phpWord * @param array $replacements */ private function replaceDocumentPlaceholders(\PhpOffice\PhpWord\PhpWord $phpWord, array $replacements): void { foreach ($phpWord->getSections() as $section) { foreach ($section->getElements() as $element) { $this->replaceElementPlaceholders($element, $replacements); } } } /** * 递归替换元素中的占位符 * @param mixed $element * @param array $replacements */ private function replaceElementPlaceholders($element, array $replacements): void { if (method_exists($element, 'getElements')) { foreach ($element->getElements() as $subElement) { $this->replaceElementPlaceholders($subElement, $replacements); } } if (method_exists($element, 'getText')) { $text = $element->getText(); if (is_string($text)) { $newText = str_replace(array_keys($replacements), array_values($replacements), $text); if (method_exists($element, 'setText')) { $element->setText($newText); } } } } /** * 格式化值 * @param mixed $value 原始值 * @param string $type 字段类型 * @return string */ private function formatValue($value, string $type): string { switch ($type) { case 'datetime': if (is_numeric($value)) { return date('Y-m-d H:i:s', $value); } elseif (strtotime($value)) { return date('Y-m-d H:i:s', strtotime($value)); } break; case 'date': if (is_numeric($value)) { return date('Y-m-d', $value); } elseif (strtotime($value)) { return date('Y-m-d', strtotime($value)); } break; case 'decimal': return number_format((float)$value, 2); case 'integer': return (string)(int)$value; default: return (string)$value; } return (string)$value; } /** * 更新生成记录 * @param int $contractSignId 合同签署ID * @param string $status 状态 * @param string|null $generatedFile 生成的文件路径 * @param string|null $errorMsg 错误信息 */ private function updateGenerateLog(int $contractSignId, string $status, ?string $generatedFile = null, ?string $errorMsg = null): void { $updateData = [ 'status' => $status, 'completed_at' => time() ]; if ($generatedFile) { $updateData['generated_file'] = $generatedFile; } if ($errorMsg) { $updateData['error_msg'] = $errorMsg; } (new DocumentGenerateLog())->where('id', $contractSignId)->update($updateData); } }