refactor: [approval] 재직증명서 DOCX 생성을 제거하고 content JSON 저장 + PDF 다운로드 방식으로 변경
- 상신 시 DOCX 생성 API 호출 제거, content JSON만 저장 - show 페이지에 PDF 다운로드 버튼 추가 - TCPDF 기반 PDF 생성 (기존 Pretendard 한글 폰트 활용) - EmploymentCertService에서 generateDocx/createFileRecord 제거
This commit is contained in:
@@ -279,48 +279,17 @@ public function certInfo(int $userId): JsonResponse
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 재직증명서 DOCX 생성
|
* 재직증명서 PDF 다운로드 (content JSON 기반 HTML→PDF)
|
||||||
*/
|
*/
|
||||||
public function generateCertDocx(Request $request): JsonResponse
|
public function certPdf(int $id)
|
||||||
{
|
{
|
||||||
$request->validate([
|
$approval = \App\Models\Approvals\Approval::where('tenant_id', session('selected_tenant_id'))
|
||||||
'user_id' => 'required|integer',
|
->findOrFail($id);
|
||||||
'purpose' => 'required|string|max:200',
|
|
||||||
'address' => 'nullable|string|max:500',
|
|
||||||
]);
|
|
||||||
|
|
||||||
try {
|
$content = $approval->content ?? [];
|
||||||
$tenantId = session('selected_tenant_id');
|
$service = app(EmploymentCertService::class);
|
||||||
$service = app(EmploymentCertService::class);
|
|
||||||
$certInfo = $service->getCertInfo($request->input('user_id'), $tenantId);
|
|
||||||
|
|
||||||
// 주소가 수정된 경우 덮어쓰기
|
return $service->generatePdfResponse($content);
|
||||||
if ($request->filled('address')) {
|
|
||||||
$certInfo['address'] = $request->input('address');
|
|
||||||
}
|
|
||||||
$certInfo['purpose'] = $request->input('purpose');
|
|
||||||
|
|
||||||
$storagePath = $service->generateDocx($certInfo, $tenantId);
|
|
||||||
$displayName = '재직증명서_'.($certInfo['name'] ?? '').'_'.date('Ymd').'.docx';
|
|
||||||
$fileRecord = $service->createFileRecord($storagePath, $displayName, $tenantId);
|
|
||||||
|
|
||||||
return response()->json([
|
|
||||||
'success' => true,
|
|
||||||
'data' => [
|
|
||||||
'file_id' => $fileRecord->id,
|
|
||||||
'file_name' => $displayName,
|
|
||||||
],
|
|
||||||
'message' => '재직증명서가 생성되었습니다.',
|
|
||||||
]);
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
report($e);
|
|
||||||
|
|
||||||
return response()->json([
|
|
||||||
'success' => false,
|
|
||||||
'message' => '재직증명서 생성에 실패했습니다.',
|
|
||||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
|
||||||
], 500);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|||||||
@@ -2,16 +2,24 @@
|
|||||||
|
|
||||||
namespace App\Services;
|
namespace App\Services;
|
||||||
|
|
||||||
use App\Models\Commons\File;
|
|
||||||
use App\Models\HR\Employee;
|
use App\Models\HR\Employee;
|
||||||
use App\Models\Tenants\Tenant;
|
use App\Models\Tenants\Tenant;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Facades\Log;
|
||||||
use PhpOffice\PhpWord\PhpWord;
|
|
||||||
use PhpOffice\PhpWord\SimpleType\Jc;
|
|
||||||
use PhpOffice\PhpWord\SimpleType\TblWidth;
|
|
||||||
|
|
||||||
class EmploymentCertService
|
class EmploymentCertService
|
||||||
{
|
{
|
||||||
|
private ?string $koreanFontName = null;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
if (! defined('K_PATH_FONTS')) {
|
||||||
|
$tcpdfFontsDir = dirname(__DIR__, 2).'/storage/fonts/tcpdf/';
|
||||||
|
if (is_dir($tcpdfFontsDir)) {
|
||||||
|
define('K_PATH_FONTS', $tcpdfFontsDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 사원의 재직증명서 정보 조회
|
* 사원의 재직증명서 정보 조회
|
||||||
*/
|
*/
|
||||||
@@ -47,168 +55,169 @@ public function getCertInfo(int $userId, int $tenantId): array
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DOCX 생성 (PhpWord 직접 생성 - 외부 템플릿 불필요)
|
* content JSON 기반 PDF Response 생성
|
||||||
*/
|
*/
|
||||||
public function generateDocx(array $data, int $tenantId): string
|
public function generatePdfResponse(array $content): \Illuminate\Http\Response
|
||||||
{
|
{
|
||||||
$phpWord = new PhpWord;
|
$pdf = new \TCPDF('P', 'mm', 'A4', true, 'UTF-8', false);
|
||||||
$phpWord->setDefaultFontName('맑은 고딕');
|
$pdf->SetCreator('SAM');
|
||||||
$phpWord->setDefaultFontSize(11);
|
$pdf->SetAuthor($content['company_name'] ?? 'SAM');
|
||||||
|
$pdf->SetTitle('재직증명서');
|
||||||
|
|
||||||
$section = $phpWord->addSection([
|
$pdf->setPrintHeader(false);
|
||||||
'marginTop' => 1440, // 1 inch
|
$pdf->setPrintFooter(false);
|
||||||
'marginBottom' => 1440,
|
$pdf->SetMargins(20, 20, 20);
|
||||||
'marginLeft' => 1440,
|
$pdf->SetAutoPageBreak(true, 20);
|
||||||
'marginRight' => 1440,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$hireDateFormatted = '';
|
$font = $this->getKoreanFont();
|
||||||
if (! empty($data['hire_date'])) {
|
|
||||||
try {
|
$pdf->AddPage();
|
||||||
$hireDateFormatted = date('Y년 m월 d일', strtotime($data['hire_date']));
|
|
||||||
} catch (\Throwable) {
|
|
||||||
$hireDateFormatted = $data['hire_date'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$issueDateFormatted = date('Y년 m월 d일');
|
|
||||||
|
|
||||||
// 제목
|
// 제목
|
||||||
$section->addText('재 직 증 명 서', [
|
$pdf->SetFont($font, 'B', 22);
|
||||||
'size' => 22,
|
$pdf->Cell(0, 20, '재 직 증 명 서', 0, 1, 'C');
|
||||||
'bold' => true,
|
$pdf->Ln(8);
|
||||||
], ['alignment' => Jc::CENTER, 'spaceAfter' => 400]);
|
|
||||||
|
|
||||||
$section->addTextBreak();
|
|
||||||
|
|
||||||
// === 1. 인적사항 ===
|
// === 1. 인적사항 ===
|
||||||
$section->addText('1. 인적사항', ['size' => 12, 'bold' => true], ['spaceAfter' => 120]);
|
$pdf->SetFont($font, 'B', 12);
|
||||||
|
$pdf->Cell(0, 8, '1. 인적사항', 0, 1, 'L');
|
||||||
|
$pdf->Ln(2);
|
||||||
|
|
||||||
$tableStyle = [
|
$this->addTableRow($pdf, $font, [
|
||||||
'borderSize' => 6,
|
['성 명', $content['name'] ?? '-', 40],
|
||||||
'borderColor' => '333333',
|
['주민등록번호', $content['resident_number'] ?? '-', 40],
|
||||||
'cellMargin' => 80,
|
]);
|
||||||
'width' => 100 * 50,
|
$this->addTableRow($pdf, $font, [
|
||||||
'unit' => TblWidth::PERCENT,
|
['주 소', $content['address'] ?? '-', 0],
|
||||||
];
|
]);
|
||||||
$thStyle = ['bgColor' => 'F8F9FA', 'valign' => 'center'];
|
$pdf->Ln(6);
|
||||||
$thFont = ['size' => 10, 'bold' => true];
|
|
||||||
$tdFont = ['size' => 10];
|
|
||||||
|
|
||||||
$table = $section->addTable($tableStyle);
|
|
||||||
|
|
||||||
$table->addRow(400);
|
|
||||||
$table->addCell(1800, $thStyle)->addText('성 명', $thFont);
|
|
||||||
$table->addCell(3200)->addText($data['name'] ?? '', $tdFont);
|
|
||||||
$table->addCell(1800, $thStyle)->addText('주민등록번호', $thFont);
|
|
||||||
$table->addCell(3200)->addText($data['resident_number_full'] ?? '', $tdFont);
|
|
||||||
|
|
||||||
$table->addRow(400);
|
|
||||||
$table->addCell(1800, $thStyle)->addText('주 소', $thFont);
|
|
||||||
$table->addCell(8200, ['gridSpan' => 3])->addText($data['address'] ?? '', $tdFont);
|
|
||||||
|
|
||||||
$section->addTextBreak();
|
|
||||||
|
|
||||||
// === 2. 재직사항 ===
|
// === 2. 재직사항 ===
|
||||||
$section->addText('2. 재직사항', ['size' => 12, 'bold' => true], ['spaceAfter' => 120]);
|
$pdf->SetFont($font, 'B', 12);
|
||||||
|
$pdf->Cell(0, 8, '2. 재직사항', 0, 1, 'L');
|
||||||
|
$pdf->Ln(2);
|
||||||
|
|
||||||
$table2 = $section->addTable($tableStyle);
|
$this->addTableRow($pdf, $font, [
|
||||||
|
['회 사 명', $content['company_name'] ?? '-', 0],
|
||||||
|
]);
|
||||||
|
$this->addTableRow($pdf, $font, [
|
||||||
|
['사업자번호', $content['business_num'] ?? '-', 0],
|
||||||
|
]);
|
||||||
|
$this->addTableRow($pdf, $font, [
|
||||||
|
['근무부서', $content['department'] ?? '-', 40],
|
||||||
|
['직 급', $content['position'] ?? '-', 40],
|
||||||
|
]);
|
||||||
|
|
||||||
$table2->addRow(400);
|
$hireDate = $content['hire_date'] ?? '';
|
||||||
$table2->addCell(1800, $thStyle)->addText('회 사 명', $thFont);
|
$hireDateDisplay = $hireDate ? $hireDate.' ~ 현재' : '-';
|
||||||
$table2->addCell(8200, ['gridSpan' => 3])->addText($data['company_name'] ?? '', $tdFont);
|
$this->addTableRow($pdf, $font, [
|
||||||
|
['재직기간', $hireDateDisplay, 0],
|
||||||
$table2->addRow(400);
|
]);
|
||||||
$table2->addCell(1800, $thStyle)->addText('사업자번호', $thFont);
|
$pdf->Ln(6);
|
||||||
$table2->addCell(8200, ['gridSpan' => 3])->addText($data['business_num'] ?? '', $tdFont);
|
|
||||||
|
|
||||||
$table2->addRow(400);
|
|
||||||
$table2->addCell(1800, $thStyle)->addText('근무부서', $thFont);
|
|
||||||
$table2->addCell(3200)->addText($data['department'] ?? '', $tdFont);
|
|
||||||
$table2->addCell(1800, $thStyle)->addText('직 급', $thFont);
|
|
||||||
$table2->addCell(3200)->addText($data['position'] ?? '', $tdFont);
|
|
||||||
|
|
||||||
$table2->addRow(400);
|
|
||||||
$table2->addCell(1800, $thStyle)->addText('재직기간', $thFont);
|
|
||||||
$table2->addCell(8200, ['gridSpan' => 3])->addText($hireDateFormatted.' ~ 현재', $tdFont);
|
|
||||||
|
|
||||||
$section->addTextBreak();
|
|
||||||
|
|
||||||
// === 3. 발급정보 ===
|
// === 3. 발급정보 ===
|
||||||
$section->addText('3. 발급정보', ['size' => 12, 'bold' => true], ['spaceAfter' => 120]);
|
$pdf->SetFont($font, 'B', 12);
|
||||||
|
$pdf->Cell(0, 8, '3. 발급정보', 0, 1, 'L');
|
||||||
|
$pdf->Ln(2);
|
||||||
|
|
||||||
$table3 = $section->addTable($tableStyle);
|
$this->addTableRow($pdf, $font, [
|
||||||
|
['사용용도', $content['purpose'] ?? '-', 0],
|
||||||
$table3->addRow(400);
|
]);
|
||||||
$table3->addCell(1800, $thStyle)->addText('사용용도', $thFont);
|
$pdf->Ln(12);
|
||||||
$table3->addCell(8200, ['gridSpan' => 3])->addText($data['purpose'] ?? '', $tdFont);
|
|
||||||
|
|
||||||
$section->addTextBreak(2);
|
|
||||||
|
|
||||||
// 증명 문구
|
// 증명 문구
|
||||||
$section->addText(
|
$pdf->SetFont($font, '', 12);
|
||||||
'위 사항을 증명합니다.',
|
$pdf->Cell(0, 10, '위 사항을 증명합니다.', 0, 1, 'C');
|
||||||
['size' => 12],
|
$pdf->Ln(6);
|
||||||
['alignment' => Jc::CENTER, 'spaceBefore' => 400, 'spaceAfter' => 400]
|
|
||||||
);
|
|
||||||
|
|
||||||
$section->addTextBreak();
|
|
||||||
|
|
||||||
// 발급일
|
// 발급일
|
||||||
$section->addText(
|
$issueDate = $content['issue_date'] ?? date('Y-m-d');
|
||||||
$issueDateFormatted,
|
$issueDateFormatted = $this->formatDate($issueDate);
|
||||||
['size' => 12, 'bold' => true],
|
$pdf->SetFont($font, 'B', 12);
|
||||||
['alignment' => Jc::CENTER, 'spaceAfter' => 600]
|
$pdf->Cell(0, 10, $issueDateFormatted, 0, 1, 'C');
|
||||||
);
|
$pdf->Ln(12);
|
||||||
|
|
||||||
$section->addTextBreak();
|
|
||||||
|
|
||||||
// 회사명 + 대표이사
|
// 회사명 + 대표이사
|
||||||
$section->addText(
|
$pdf->SetFont($font, 'B', 14);
|
||||||
($data['company_name'] ?? ''),
|
$pdf->Cell(0, 10, $content['company_name'] ?? '', 0, 1, 'C');
|
||||||
['size' => 14, 'bold' => true],
|
$pdf->SetFont($font, '', 12);
|
||||||
['alignment' => Jc::CENTER]
|
$pdf->Cell(0, 10, '대표이사 (인)', 0, 1, 'C');
|
||||||
);
|
|
||||||
$section->addText(
|
|
||||||
'대표이사 '.($data['ceo_name'] ?? '').' (인)',
|
|
||||||
['size' => 12],
|
|
||||||
['alignment' => Jc::CENTER]
|
|
||||||
);
|
|
||||||
|
|
||||||
// 파일 저장
|
$pdfContent = $pdf->Output('', 'S');
|
||||||
$outputDir = storage_path("app/approvals/{$tenantId}");
|
$fileName = '재직증명서_'.($content['name'] ?? '').'.pdf';
|
||||||
if (! is_dir($outputDir)) {
|
|
||||||
mkdir($outputDir, 0755, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
$storedName = Str::random(40).'.docx';
|
return response($pdfContent, 200, [
|
||||||
$storagePath = "approvals/{$tenantId}/{$storedName}";
|
'Content-Type' => 'application/pdf',
|
||||||
$fullPath = storage_path("app/{$storagePath}");
|
'Content-Disposition' => 'inline; filename="'.$fileName.'"',
|
||||||
|
]);
|
||||||
$objWriter = \PhpOffice\PhpWord\IOFactory::createWriter($phpWord, 'Word2007');
|
|
||||||
$objWriter->save($fullPath);
|
|
||||||
|
|
||||||
return $storagePath;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 파일 레코드 생성 및 approval에 첨부
|
* 테이블 행 추가 (TCPDF)
|
||||||
*/
|
*/
|
||||||
public function createFileRecord(string $storagePath, string $displayName, int $tenantId): File
|
private function addTableRow(\TCPDF $pdf, string $font, array $cells): void
|
||||||
{
|
{
|
||||||
$fullPath = storage_path("app/{$storagePath}");
|
$pageWidth = $pdf->getPageWidth() - 40; // margins
|
||||||
|
$rowHeight = 8;
|
||||||
|
$thWidth = 30;
|
||||||
|
|
||||||
return File::create([
|
if (count($cells) === 1) {
|
||||||
'tenant_id' => $tenantId,
|
// 단일 셀: th + td (전체 너비)
|
||||||
'document_type' => 'approval_attachment',
|
$pdf->SetFont($font, 'B', 10);
|
||||||
'display_name' => $displayName,
|
$pdf->SetFillColor(248, 249, 250);
|
||||||
'original_name' => $displayName,
|
$pdf->Cell($thWidth, $rowHeight, $cells[0][0], 1, 0, 'L', true);
|
||||||
'stored_name' => basename($storagePath),
|
$pdf->SetFont($font, '', 10);
|
||||||
'file_path' => $storagePath,
|
$pdf->Cell($pageWidth - $thWidth, $rowHeight, $cells[0][1], 1, 1, 'L');
|
||||||
'mime_type' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
} else {
|
||||||
'file_size' => filesize($fullPath),
|
// 복수 셀: th+td + th+td
|
||||||
'file_type' => 'docx',
|
$tdWidth = ($pageWidth - $thWidth * 2) / 2;
|
||||||
'is_temp' => false,
|
foreach ($cells as $cell) {
|
||||||
'uploaded_by' => auth()->id(),
|
$pdf->SetFont($font, 'B', 10);
|
||||||
]);
|
$pdf->SetFillColor(248, 249, 250);
|
||||||
|
$pdf->Cell($thWidth, $rowHeight, $cell[0], 1, 0, 'L', true);
|
||||||
|
$pdf->SetFont($font, '', 10);
|
||||||
|
$pdf->Cell($tdWidth, $rowHeight, $cell[1], 1, 0, 'L');
|
||||||
|
}
|
||||||
|
$pdf->Ln();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 날짜 포맷
|
||||||
|
*/
|
||||||
|
private function formatDate(string $date): string
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return date('Y년 m월 d일', strtotime($date));
|
||||||
|
} catch (\Throwable) {
|
||||||
|
return $date;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 한글 폰트 로드
|
||||||
|
*/
|
||||||
|
private function getKoreanFont(): string
|
||||||
|
{
|
||||||
|
if ($this->koreanFontName) {
|
||||||
|
return $this->koreanFontName;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defined('K_PATH_FONTS') && file_exists(K_PATH_FONTS.'pretendard.php')) {
|
||||||
|
$this->koreanFontName = 'pretendard';
|
||||||
|
|
||||||
|
return $this->koreanFontName;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fontPath = storage_path('fonts/Pretendard-Regular.ttf');
|
||||||
|
if (file_exists($fontPath)) {
|
||||||
|
try {
|
||||||
|
$this->koreanFontName = \TCPDF_FONTS::addTTFfont($fontPath, 'TrueTypeUnicode', '', 96);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
Log::warning('TCPDF 한글 폰트 등록 실패', ['error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->koreanFontName ?: 'helvetica';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -223,7 +223,6 @@ class="p-1 text-gray-400 hover:text-gray-600 transition">
|
|||||||
let isExpenseForm = false;
|
let isExpenseForm = false;
|
||||||
let isLeaveForm = false;
|
let isLeaveForm = false;
|
||||||
let isCertForm = false;
|
let isCertForm = false;
|
||||||
let certFileId = null;
|
|
||||||
|
|
||||||
// 양식코드별 표시할 유형 목록
|
// 양식코드별 표시할 유형 목록
|
||||||
const leaveTypesByFormCode = {
|
const leaveTypesByFormCode = {
|
||||||
@@ -499,7 +498,6 @@ function switchFormMode(formId) {
|
|||||||
} else if (code === 'employment_cert') {
|
} else if (code === 'employment_cert') {
|
||||||
isCertForm = true;
|
isCertForm = true;
|
||||||
certContainer.style.display = '';
|
certContainer.style.display = '';
|
||||||
certFileId = null;
|
|
||||||
// 초기 사원 정보 로드
|
// 초기 사원 정보 로드
|
||||||
const certUserId = document.getElementById('cert-user-id').value;
|
const certUserId = document.getElementById('cert-user-id').value;
|
||||||
if (certUserId) loadCertInfo(certUserId);
|
if (certUserId) loadCertInfo(certUserId);
|
||||||
@@ -641,35 +639,6 @@ function applyBodyTemplate(formId) {
|
|||||||
issue_date: document.getElementById('cert-issue-date').value,
|
issue_date: document.getElementById('cert-issue-date').value,
|
||||||
};
|
};
|
||||||
formBody = null;
|
formBody = null;
|
||||||
|
|
||||||
// DOCX 생성
|
|
||||||
if (!certFileId) {
|
|
||||||
showToast('재직증명서 DOCX를 생성 중입니다...', 'info');
|
|
||||||
try {
|
|
||||||
const certResp = await fetch('/api/admin/approvals/generate-cert-docx', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': '{{ csrf_token() }}', 'Accept': 'application/json' },
|
|
||||||
body: JSON.stringify({
|
|
||||||
user_id: formContent.cert_user_id,
|
|
||||||
purpose: purpose,
|
|
||||||
address: formContent.address,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
const certData = await certResp.json();
|
|
||||||
if (certData.success) {
|
|
||||||
certFileId = certData.data.file_id;
|
|
||||||
} else {
|
|
||||||
showToast(certData.message || 'DOCX 생성 실패', 'error');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
showToast('DOCX 생성 중 오류가 발생했습니다.', 'error');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (certFileId) {
|
|
||||||
attachmentFileIds.push(certFileId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
@@ -872,7 +841,6 @@ function closeExpenseLoadModal() {
|
|||||||
document.getElementById('cert-department').value = d.department || '';
|
document.getElementById('cert-department').value = d.department || '';
|
||||||
document.getElementById('cert-position').value = d.position || '';
|
document.getElementById('cert-position').value = d.position || '';
|
||||||
document.getElementById('cert-hire-date').value = d.hire_date ? d.hire_date + ' ~' : '';
|
document.getElementById('cert-hire-date').value = d.hire_date ? d.hire_date + ' ~' : '';
|
||||||
certFileId = null; // 사원 변경 시 DOCX 재생성 필요
|
|
||||||
} else {
|
} else {
|
||||||
showToast(data.message || '사원 정보를 불러올 수 없습니다.', 'error');
|
showToast(data.message || '사원 정보를 불러올 수 없습니다.', 'error');
|
||||||
}
|
}
|
||||||
@@ -890,7 +858,6 @@ function onCertPurposeChange() {
|
|||||||
} else {
|
} else {
|
||||||
customWrap.style.display = 'none';
|
customWrap.style.display = 'none';
|
||||||
}
|
}
|
||||||
certFileId = null; // 용도 변경 시 DOCX 재생성 필요
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCertPurpose() {
|
function getCertPurpose() {
|
||||||
|
|||||||
@@ -260,7 +260,6 @@ class="px-6 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg text-sm fon
|
|||||||
const linesData = @json($lines);
|
const linesData = @json($lines);
|
||||||
let isExpenseForm = false;
|
let isExpenseForm = false;
|
||||||
let isCertForm = false;
|
let isCertForm = false;
|
||||||
let certFileId = null;
|
|
||||||
|
|
||||||
function escapeHtml(str) {
|
function escapeHtml(str) {
|
||||||
if (!str) return '';
|
if (!str) return '';
|
||||||
@@ -440,7 +439,6 @@ function switchFormMode(formId) {
|
|||||||
} else if (code === 'employment_cert') {
|
} else if (code === 'employment_cert') {
|
||||||
isCertForm = true;
|
isCertForm = true;
|
||||||
certContainer.style.display = '';
|
certContainer.style.display = '';
|
||||||
certFileId = null;
|
|
||||||
const certUserId = document.getElementById('cert-user-id').value;
|
const certUserId = document.getElementById('cert-user-id').value;
|
||||||
if (certUserId) loadCertInfo(certUserId);
|
if (certUserId) loadCertInfo(certUserId);
|
||||||
} else {
|
} else {
|
||||||
@@ -602,35 +600,6 @@ function applyBodyTemplate(formId) {
|
|||||||
issue_date: document.getElementById('cert-issue-date').value,
|
issue_date: document.getElementById('cert-issue-date').value,
|
||||||
};
|
};
|
||||||
formBody = null;
|
formBody = null;
|
||||||
|
|
||||||
// DOCX 생성
|
|
||||||
if (!certFileId) {
|
|
||||||
showToast('재직증명서 DOCX를 생성 중입니다...', 'info');
|
|
||||||
try {
|
|
||||||
const certResp = await fetch('/api/admin/approvals/generate-cert-docx', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': '{{ csrf_token() }}', 'Accept': 'application/json' },
|
|
||||||
body: JSON.stringify({
|
|
||||||
user_id: formContent.cert_user_id,
|
|
||||||
purpose: purpose,
|
|
||||||
address: formContent.address,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
const certData = await certResp.json();
|
|
||||||
if (certData.success) {
|
|
||||||
certFileId = certData.data.file_id;
|
|
||||||
} else {
|
|
||||||
showToast(certData.message || 'DOCX 생성 실패', 'error');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
showToast('DOCX 생성 중 오류가 발생했습니다.', 'error');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (certFileId) {
|
|
||||||
attachmentFileIds.push(certFileId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
@@ -735,7 +704,6 @@ function applyBodyTemplate(formId) {
|
|||||||
document.getElementById('cert-department').value = d.department || '';
|
document.getElementById('cert-department').value = d.department || '';
|
||||||
document.getElementById('cert-position').value = d.position || '';
|
document.getElementById('cert-position').value = d.position || '';
|
||||||
document.getElementById('cert-hire-date').value = d.hire_date ? d.hire_date + ' ~' : '';
|
document.getElementById('cert-hire-date').value = d.hire_date ? d.hire_date + ' ~' : '';
|
||||||
certFileId = null;
|
|
||||||
} else {
|
} else {
|
||||||
showToast(data.message || '사원 정보를 불러올 수 없습니다.', 'error');
|
showToast(data.message || '사원 정보를 불러올 수 없습니다.', 'error');
|
||||||
}
|
}
|
||||||
@@ -753,7 +721,6 @@ function onCertPurposeChange() {
|
|||||||
} else {
|
} else {
|
||||||
customWrap.style.display = 'none';
|
customWrap.style.display = 'none';
|
||||||
}
|
}
|
||||||
certFileId = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCertPurpose() {
|
function getCertPurpose() {
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
$content (array) - approvals.content JSON
|
$content (array) - approvals.content JSON
|
||||||
--}}
|
--}}
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
{{-- 미리보기 버튼 --}}
|
{{-- 미리보기 / PDF 다운로드 버튼 --}}
|
||||||
<div class="flex justify-end">
|
<div class="flex justify-end gap-2">
|
||||||
<button type="button" onclick="openCertShowPreview()"
|
<button type="button" onclick="openCertShowPreview()"
|
||||||
class="px-3 py-1.5 bg-indigo-50 text-indigo-600 hover:bg-indigo-100 border border-indigo-200 rounded-lg text-sm font-medium transition inline-flex items-center gap-1">
|
class="px-3 py-1.5 bg-indigo-50 text-indigo-600 hover:bg-indigo-100 border border-indigo-200 rounded-lg text-sm font-medium transition inline-flex items-center gap-1">
|
||||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
@@ -14,6 +14,13 @@ class="px-3 py-1.5 bg-indigo-50 text-indigo-600 hover:bg-indigo-100 border borde
|
|||||||
</svg>
|
</svg>
|
||||||
증명서 미리보기
|
증명서 미리보기
|
||||||
</button>
|
</button>
|
||||||
|
<a href="{{ route('api.admin.approvals.cert-pdf', $approval->id) }}" target="_blank"
|
||||||
|
class="px-3 py-1.5 bg-green-50 text-green-600 hover:bg-green-100 border border-green-200 rounded-lg text-sm font-medium transition inline-flex items-center gap-1">
|
||||||
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
|
||||||
|
</svg>
|
||||||
|
PDF 다운로드
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- 인적사항 --}}
|
{{-- 인적사항 --}}
|
||||||
|
|||||||
@@ -971,7 +971,7 @@
|
|||||||
|
|
||||||
// 재직증명서
|
// 재직증명서
|
||||||
Route::get('/cert-info/{userId}', [\App\Http\Controllers\Api\Admin\ApprovalApiController::class, 'certInfo'])->name('cert-info');
|
Route::get('/cert-info/{userId}', [\App\Http\Controllers\Api\Admin\ApprovalApiController::class, 'certInfo'])->name('cert-info');
|
||||||
Route::post('/generate-cert-docx', [\App\Http\Controllers\Api\Admin\ApprovalApiController::class, 'generateCertDocx'])->name('generate-cert-docx');
|
Route::get('/{id}/cert-pdf', [\App\Http\Controllers\Api\Admin\ApprovalApiController::class, 'certPdf'])->name('cert-pdf');
|
||||||
|
|
||||||
// CRUD
|
// CRUD
|
||||||
Route::post('/', [\App\Http\Controllers\Api\Admin\ApprovalApiController::class, 'store'])->name('store');
|
Route::post('/', [\App\Http\Controllers\Api\Admin\ApprovalApiController::class, 'store'])->name('store');
|
||||||
|
|||||||
Reference in New Issue
Block a user