leftJoin('school_contract c', 'cs.contract_id = c.id') ->where($where) ->field(' cs.id as sign_id, cs.contract_id, cs.status, cs.sign_time, cs.created_at, c.contract_name, c.contract_type, c.remarks, c.contract_template ') ->order('cs.created_at desc'); $total = $query->count(); $list = $query->page($page, $limit)->select()->toArray(); // 处理每个合同的详细信息 foreach ($list as &$contract) { // 状态文本映射 $contract['status_text'] = $this->getStatusText($contract['status']); // 获取合同相关的课程信息 $courseInfo = $this->getContractCourseInfo($contract['contract_id'], $studentId); $contract = array_merge($contract, $courseInfo); // 格式化日期 $contract['sign_date'] = $contract['sign_time'] ? date('Y-m-d', strtotime($contract['sign_time'])) : null; $contract['create_date'] = date('Y-m-d', strtotime($contract['created_at'])); // 文件路径处理 $contract['contract_file_url'] = $contract['contract_template'] ? get_image_url($contract['contract_template']) : ''; // 计算课时使用进度 if ($contract['total_hours'] > 0) { $contract['progress_percent'] = round(($contract['used_hours'] / $contract['total_hours']) * 100, 1); } else { $contract['progress_percent'] = 0; } // 判断是否可以续约(生效状态且课时即将用完) $contract['can_renew'] = $contract['status'] == 3 && $contract['remaining_hours'] <= 5; } // 统计数据 $stats = $this->getContractStats($studentId); return [ 'list' => $list, 'total' => $total, 'page' => $page, 'limit' => $limit, 'has_more' => $total > $page * $limit, 'stats' => $stats ]; } /** * 获取合同详情 * @param int $contractId * @param int $studentId * @return array */ public function getContractDetail($contractId, $studentId) { // 查询合同签署记录 $contractSign = Db::table('school_contract_sign cs') ->leftJoin('school_contract c', 'cs.contract_id = c.id') ->where([ ['cs.contract_id', '=', $contractId], ['cs.student_id', '=', $studentId], ['cs.deleted_at', '=', 0] ]) ->field(' cs.id as sign_id, cs.contract_id, cs.status, cs.sign_time, cs.created_at, cs.fill_data, c.contract_name, c.contract_type, c.remarks, c.contract_template, c.contract_content, c.placeholders ') ->find(); if (!$contractSign) { throw new CommonException('合同不存在或无权限访问'); } // 获取课程信息 $courseInfo = $this->getContractCourseInfo($contractId, $studentId); $contractSign = array_merge($contractSign, $courseInfo); // 状态文本 $contractSign['status_text'] = $this->getStatusText($contractSign['status']); // 格式化日期 $contractSign['sign_date'] = $contractSign['sign_time'] ? date('Y-m-d H:i:s', strtotime($contractSign['sign_time'])) : null; $contractSign['create_date'] = date('Y-m-d H:i:s', strtotime($contractSign['created_at'])); // 文件路径 $contractSign['contract_file_url'] = $contractSign['contract_template'] ? get_image_url($contractSign['contract_template']) : ''; // 解析填写的数据 $contractSign['form_data'] = []; if ($contractSign['fill_data']) { $contractSign['form_data'] = json_decode($contractSign['fill_data'], true) ?: []; } // 合同条款(如果有内容的话) $contractSign['terms'] = $contractSign['contract_content'] ?: $contractSign['remarks']; return $contractSign; } /** * 获取合同签署表单配置 * @param int $contractId * @param int $studentId * @return array */ public function getSignForm($contractId, $studentId) { // 验证合同是否存在且用户有权限 $contractSign = Db::table('school_contract_sign') ->where([ ['contract_id', '=', $contractId], ['student_id', '=', $studentId], ['deleted_at', '=', 0] ]) ->find(); if (!$contractSign) { throw new CommonException('合同不存在或无权限访问'); } // 检查合同状态 if ($contractSign['status'] != 1) { throw new CommonException('当前合同状态不允许签署'); } // 获取合同基本信息 $contract = Db::table('school_contract') ->where('id', $contractId) ->field('id, contract_name, contract_type, contract_template, contract_content, placeholders') ->find(); if (!$contract) { throw new CommonException('合同模板不存在'); } // 获取所有字段配置 $formFields = Db::table('school_document_data_source_config') ->where('contract_id', $contractId) ->field('id, placeholder, field_type, data_type, is_required, default_value, table_name, field_name, system_function, sign_party') ->order('id ASC') ->select() ->toArray(); // 格式化表单字段 $fields = []; foreach ($formFields as $field) { // 根据数据类型预填充默认值 $defaultValue = ''; switch ($field['data_type']) { case 'database': $defaultValue = $this->getDataFromDatabase($field['table_name'], $field['field_name'], $studentId); break; case 'system': $defaultValue = $this->getSystemValue($field['system_function']); break; case 'user_input': default: $defaultValue = $field['default_value'] ?: ''; break; } $fields[] = [ 'id' => $field['id'], 'name' => $field['placeholder'], // 使用placeholder作为字段名称 'placeholder' => $field['placeholder'], 'field_type' => $field['field_type'], 'data_type' => $field['data_type'], 'is_required' => (bool)$field['is_required'], 'default_value' => $defaultValue, 'sign_party' => $field['sign_party'] ]; } return [ 'contract_id' => $contractId, 'contract_name' => $contract['contract_name'], 'contract_type' => $contract['contract_type'], 'contract_content' => $contract['contract_content'] ?: '', 'form_fields' => $fields, 'contract_template_url' => $contract['contract_template'] ? get_image_url($contract['contract_template']) : '' ]; } /** * 提交合同签署 * @param array $data * @return bool */ public function signContract($data) { $contractId = $data['contract_id']; $studentId = $data['student_id']; $formData = $data['form_data']; $signatureImage = $data['signature_image'] ?? ''; // 验证合同签署记录 $contractSign = Db::table('school_contract_sign') ->where([ ['contract_id', '=', $contractId], ['student_id', '=', $studentId], ['deleted_at', '=', 0] ]) ->find(); if (!$contractSign) { throw new CommonException('合同不存在或无权限访问'); } if ($contractSign['status'] != 1) { throw new CommonException('当前合同状态不允许签署'); } // 验证必填字段 $this->validateFormData($contractId, $formData); // 开始事务 Db::startTrans(); try { // 生成签署后的合同文档 $generatedFile = null; if ($signatureImage) { $generatedFile = $this->generateSignedContract($contractId, $studentId, $formData, $signatureImage); } // 更新合同签署状态 $updateData = [ 'status' => 2, // 已签署 'sign_time' => date('Y-m-d H:i:s'), 'fill_data' => json_encode($formData, JSON_UNESCAPED_UNICODE), 'updated_at' => date('Y-m-d H:i:s') ]; if ($signatureImage) { $updateData['signature_image'] = $signatureImage; } if ($generatedFile) { $updateData['sign_file'] = $generatedFile; } $result = Db::table('school_contract_sign') ->where('id', $contractSign['id']) ->update($updateData); if ($result === false) { throw new CommonException('合同签署失败'); } Db::commit(); return [ 'sign_id' => $contractSign['id'], 'generated_file' => $generatedFile, 'sign_time' => $updateData['sign_time'] ]; } catch (\Exception $e) { Db::rollback(); throw new CommonException('合同签署失败:' . $e->getMessage()); } } /** * 下载合同文件 * @param int $contractId * @param int $studentId * @return array */ public function downloadContract($contractId, $studentId) { // 验证权限 $contractSign = Db::table('school_contract_sign') ->where([ ['contract_id', '=', $contractId], ['student_id', '=', $studentId], ['deleted_at', '=', 0] ]) ->find(); if (!$contractSign) { throw new CommonException('合同不存在或无权限访问'); } // 获取合同文件 $contract = Db::table('school_contract') ->where('id', $contractId) ->find(); if (!$contract || !$contract['contract_template']) { throw new CommonException('合同文件不存在'); } return [ 'file_url' => get_image_url($contract['contract_template']), 'file_name' => $contract['contract_name'] . '.pdf', 'contract_name' => $contract['contract_name'] ]; } /** * 获取合同相关的课程信息 * @param int $contractId * @param int $studentId * @return array */ private function getContractCourseInfo($contractId, $studentId) { // 通过订单表获取课程信息 $orderInfo = Db::table('school_order_table ot') ->leftJoin('school_course c', 'ot.course_id = c.id') ->where([ // 这里需要根据实际业务逻辑调整关联条件 ['ot.student_id', '=', $studentId] ]) ->field(' c.course_name, c.course_type, ot.order_amount as total_amount ') ->find(); // 从课程表获取课时信息 $courseStats = Db::table('school_student_courses') ->where('student_id', $studentId) ->field(' SUM(total_hours + gift_hours) as total_hours, SUM(use_total_hours + use_gift_hours) as used_hours, SUM(total_hours + gift_hours - use_total_hours - use_gift_hours) as remaining_hours ') ->find(); return [ 'course_type' => $orderInfo['course_name'] ?? '未知课程', 'total_amount' => $orderInfo['total_amount'] ?? '0.00', 'total_hours' => (int)($courseStats['total_hours'] ?? 0), 'used_hours' => (int)($courseStats['used_hours'] ?? 0), 'remaining_hours' => (int)($courseStats['remaining_hours'] ?? 0) ]; } /** * 获取合同统计数据 * @param int $studentId * @return array */ private function getContractStats($studentId) { // 统计各状态合同数量 $statusCounts = Db::table('school_contract_sign') ->where([ ['student_id', '=', $studentId], ['deleted_at', '=', 0] ]) ->field('status, COUNT(*) as count') ->group('status') ->select() ->toArray(); $stats = [ 'total_contracts' => 0, 'active_contracts' => 0, // 已生效 'pending_contracts' => 0, // 未签署 'signed_contracts' => 0, // 已签署 'expired_contracts' => 0, // 已失效 ]; foreach ($statusCounts as $item) { $stats['total_contracts'] += $item['count']; switch ($item['status']) { case 1: $stats['pending_contracts'] = $item['count']; break; case 2: $stats['signed_contracts'] = $item['count']; break; case 3: $stats['active_contracts'] = $item['count']; break; case 4: $stats['expired_contracts'] = $item['count']; break; } } // 获取剩余总课时 $courseStats = Db::table('school_student_courses') ->where('student_id', $studentId) ->field('SUM(total_hours + gift_hours - use_total_hours - use_gift_hours) as remaining_hours') ->find(); $stats['remaining_hours'] = (int)($courseStats['remaining_hours'] ?? 0); return $stats; } /** * 获取状态文本 * @param int $status * @return string */ private function getStatusText($status) { $statusMap = [ 1 => '未签署', 2 => '已签署', 3 => '已生效', 4 => '已失效' ]; return $statusMap[$status] ?? '未知状态'; } /** * 获取学员基本信息 * @param int $studentId * @return array */ public function getStudentInfo($studentId) { $student = Db::table('school_student') ->where('id', $studentId) ->field('id, name, gender, age, headimg') ->find(); if (!$student) { throw new CommonException('学员不存在'); } return [ 'id' => $student['id'], 'name' => $student['name'], 'gender' => $student['gender'], 'age' => $student['age'], 'avatar' => $student['headimg'] ? get_image_url($student['headimg']) : '' ]; } /** * 生成签署后的合同文档 * @param int $contractId * @param int $studentId * @param array $formData * @param string $signatureImage * @return string * @throws CommonException */ private function generateSignedContract($contractId, $studentId, $formData, $signatureImage) { try { // 获取合同模板信息 $contract = Db::table('school_contract') ->where('id', $contractId) ->find(); if (!$contract || !$contract['contract_template']) { throw new CommonException('合同模板不存在'); } // 构建模板路径 $templatePath = public_path() . '/upload/' . $contract['contract_template']; if (!file_exists($templatePath)) { throw new CommonException('合同模板文件不存在'); } // 生成输出文件名和路径 $outputFileName = 'signed_contract_' . $studentId . '_' . $contractId . '_' . date('YmdHis') . '.docx'; $outputRelPath = 'contracts/signed/' . date('Y/m/') . $outputFileName; $outputFullPath = public_path() . '/upload/' . $outputRelPath; // 确保目录存在 $outputDir = dirname($outputFullPath); if (!is_dir($outputDir)) { mkdir($outputDir, 0755, true); } // 获取数据源配置并准备填充数据 $fillData = $this->prepareFillData($contractId, $studentId, $formData); // 使用PhpWord处理模板 $templateProcessor = new TemplateProcessor($templatePath); // 填充文本数据 foreach ($fillData as $placeholder => $value) { $templateProcessor->setValue($placeholder, $value); } // 处理签名图片 if ($signatureImage && $this->hasSignaturePlaceholder($templateProcessor)) { // 处理签名图片 - 支持base64和URL $signImagePath = $this->processSignatureImage($signatureImage); // 使用ContractSign服务插入签名 $contractSignService = new ContractSign(); $contractSignService->setSign($templatePath, $outputFullPath, $signImagePath, '学员签名'); // 清理临时文件 if (file_exists($signImagePath)) { unlink($signImagePath); } } else { // 没有签名时直接保存 $templateProcessor->saveAs($outputFullPath); } return $outputRelPath; } catch (\Exception $e) { throw new CommonException('生成签署合同失败:' . $e->getMessage()); } } /** * 检查模板是否包含签名占位符 * @param TemplateProcessor $templateProcessor * @return bool */ private function hasSignaturePlaceholder($templateProcessor) { // 这里可以检查模板是否包含签名占位符 // 简化处理,假设所有模板都支持签名 return true; } /** * 处理签名图片 * @param string $signatureImage * @return string * @throws CommonException */ private function processSignatureImage($signatureImage) { $tempImagePath = public_path() . '/upload/temp_sign_' . date('YmdHis') . '_' . mt_rand(1000, 9999) . '.png'; if (strpos($signatureImage, 'data:image') === 0) { // Base64图片 $imageData = base64_decode(preg_replace('#^data:image/\w+;base64,#i', '', $signatureImage)); if ($imageData === false) { throw new CommonException('签名图片格式错误'); } file_put_contents($tempImagePath, $imageData); } elseif (filter_var($signatureImage, FILTER_VALIDATE_URL)) { // URL图片 $imageContent = file_get_contents($signatureImage); if ($imageContent === false) { throw new CommonException('无法下载签名图片'); } file_put_contents($tempImagePath, $imageContent); } else { // 本地路径 $localPath = public_path() . '/upload/' . ltrim($signatureImage, '/'); if (!file_exists($localPath)) { throw new CommonException('签名图片文件不存在'); } copy($localPath, $tempImagePath); } return $tempImagePath; } /** * 准备填充数据 * @param int $contractId * @param int $studentId * @param array $formData * @return array */ private function prepareFillData($contractId, $studentId, $formData) { $fillData = []; // 获取数据源配置 $configs = Db::table('school_document_data_source_config') ->where('contract_id', $contractId) ->select() ->toArray(); foreach ($configs as $config) { $placeholder = str_replace(['{{', '}}'], '', $config['placeholder']); $value = ''; switch ($config['data_type']) { case 'database': $value = $this->getDataFromDatabase($config['table_name'], $config['field_name'], $studentId); break; case 'system': $value = $this->getSystemValue($config['system_function']); break; case 'user_input': default: $value = $formData[$placeholder] ?? $config['default_value'] ?? ''; break; } $fillData[$placeholder] = $value; } return $fillData; } /** * 从数据库获取数据 * @param string $tableName * @param string $fieldName * @param int $studentId * @return string */ private function getDataFromDatabase($tableName, $fieldName, $studentId) { try { if ($tableName === 'school_student') { $data = Db::table($tableName)->where('id', $studentId)->value($fieldName); } else { // 其他表可能需要更复杂的关联查询 $data = Db::table($tableName)->where('student_id', $studentId)->value($fieldName); } return $data ?: ''; } catch (\Exception $e) { return ''; } } /** * 获取系统值 * @param string $systemFunction * @return string */ private function getSystemValue($systemFunction) { switch ($systemFunction) { case 'current_date': return date('Y-m-d'); case 'current_time': return date('H:i:s'); case 'current_datetime': return date('Y-m-d H:i:s'); case 'current_year': return date('Y'); case 'current_month': return date('m'); case 'current_day': return date('d'); case 'random_number': return mt_rand(100000, 999999); case 'contract_generate_time': return date('Y-m-d H:i:s'); default: return ''; } } /** * 验证表单数据 * @param int $contractId * @param array $formData * @throws CommonException */ private function validateFormData($contractId, $formData) { // 获取必填字段配置 $requiredFields = Db::table('school_document_data_source_config') ->where([ ['contract_id', '=', $contractId], ['data_type', '=', 'manual'], ['is_required', '=', 1] ]) ->column('placeholder'); // 检查必填字段 foreach ($requiredFields as $field) { $fieldName = str_replace(['{{', '}}'], '', $field); if (!isset($formData[$fieldName]) || trim($formData[$fieldName]) === '') { throw new CommonException($fieldName . ' 为必填项'); } } } }