Files
sam-manage/app/Services/EmploymentCertService.php
김보곤 b82cba0cc9 fix: [approval] 재직증명서 DOCX 생성을 PhpWord 직접 생성으로 변경
- 외부 템플릿 파일(employment_cert.docx) 의존성 제거
- PhpWord로 테이블/텍스트 직접 생성하여 서버 배포 시 템플릿 누락 문제 해결
2026-03-05 19:17:33 +09:00

215 lines
7.4 KiB
PHP

<?php
namespace App\Services;
use App\Models\Commons\File;
use App\Models\HR\Employee;
use App\Models\Tenants\Tenant;
use Illuminate\Support\Str;
use PhpOffice\PhpWord\PhpWord;
use PhpOffice\PhpWord\SimpleType\Jc;
use PhpOffice\PhpWord\SimpleType\TblWidth;
class EmploymentCertService
{
/**
* 사원의 재직증명서 정보 조회
*/
public function getCertInfo(int $userId, int $tenantId): array
{
$employee = Employee::withoutGlobalScopes()
->where('tenant_id', $tenantId)
->where('user_id', $userId)
->with(['user', 'department'])
->firstOrFail();
$tenant = Tenant::findOrFail($tenantId);
$residentNumber = $employee->resident_number;
$maskedResident = $residentNumber
? substr($residentNumber, 0, 8).'******'
: '';
return [
'name' => $employee->user->name ?? $employee->display_name ?? '',
'resident_number' => $maskedResident,
'resident_number_full' => $residentNumber ?? '',
'address' => $employee->address ?? '',
'department' => $employee->department?->name ?? '',
'position' => $employee->position_label ?? '',
'hire_date' => $employee->hire_date ?? '',
'company_name' => $tenant->company_name ?? '',
'business_num' => $tenant->business_num ?? '',
'ceo_name' => $tenant->ceo_name ?? '',
'phone' => $tenant->phone ?? '',
'company_address' => $tenant->address ?? '',
];
}
/**
* DOCX 생성 (PhpWord 직접 생성 - 외부 템플릿 불필요)
*/
public function generateDocx(array $data, int $tenantId): string
{
$phpWord = new PhpWord;
$phpWord->setDefaultFontName('맑은 고딕');
$phpWord->setDefaultFontSize(11);
$section = $phpWord->addSection([
'marginTop' => 1440, // 1 inch
'marginBottom' => 1440,
'marginLeft' => 1440,
'marginRight' => 1440,
]);
$hireDateFormatted = '';
if (! empty($data['hire_date'])) {
try {
$hireDateFormatted = date('Y년 m월 d일', strtotime($data['hire_date']));
} catch (\Throwable) {
$hireDateFormatted = $data['hire_date'];
}
}
$issueDateFormatted = date('Y년 m월 d일');
// 제목
$section->addText('재 직 증 명 서', [
'size' => 22,
'bold' => true,
], ['alignment' => Jc::CENTER, 'spaceAfter' => 400]);
$section->addTextBreak();
// === 1. 인적사항 ===
$section->addText('1. 인적사항', ['size' => 12, 'bold' => true], ['spaceAfter' => 120]);
$tableStyle = [
'borderSize' => 6,
'borderColor' => '333333',
'cellMargin' => 80,
'width' => 100 * 50,
'unit' => TblWidth::PERCENT,
];
$thStyle = ['bgColor' => 'F8F9FA', 'valign' => 'center'];
$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. 재직사항 ===
$section->addText('2. 재직사항', ['size' => 12, 'bold' => true], ['spaceAfter' => 120]);
$table2 = $section->addTable($tableStyle);
$table2->addRow(400);
$table2->addCell(1800, $thStyle)->addText('회 사 명', $thFont);
$table2->addCell(8200, ['gridSpan' => 3])->addText($data['company_name'] ?? '', $tdFont);
$table2->addRow(400);
$table2->addCell(1800, $thStyle)->addText('사업자번호', $thFont);
$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. 발급정보 ===
$section->addText('3. 발급정보', ['size' => 12, 'bold' => true], ['spaceAfter' => 120]);
$table3 = $section->addTable($tableStyle);
$table3->addRow(400);
$table3->addCell(1800, $thStyle)->addText('사용용도', $thFont);
$table3->addCell(8200, ['gridSpan' => 3])->addText($data['purpose'] ?? '', $tdFont);
$section->addTextBreak(2);
// 증명 문구
$section->addText(
'위 사항을 증명합니다.',
['size' => 12],
['alignment' => Jc::CENTER, 'spaceBefore' => 400, 'spaceAfter' => 400]
);
$section->addTextBreak();
// 발급일
$section->addText(
$issueDateFormatted,
['size' => 12, 'bold' => true],
['alignment' => Jc::CENTER, 'spaceAfter' => 600]
);
$section->addTextBreak();
// 회사명 + 대표이사
$section->addText(
($data['company_name'] ?? ''),
['size' => 14, 'bold' => true],
['alignment' => Jc::CENTER]
);
$section->addText(
'대표이사 '.($data['ceo_name'] ?? '').' (인)',
['size' => 12],
['alignment' => Jc::CENTER]
);
// 파일 저장
$outputDir = storage_path("app/approvals/{$tenantId}");
if (! is_dir($outputDir)) {
mkdir($outputDir, 0755, true);
}
$storedName = Str::random(40).'.docx';
$storagePath = "approvals/{$tenantId}/{$storedName}";
$fullPath = storage_path("app/{$storagePath}");
$objWriter = \PhpOffice\PhpWord\IOFactory::createWriter($phpWord, 'Word2007');
$objWriter->save($fullPath);
return $storagePath;
}
/**
* 파일 레코드 생성 및 approval에 첨부
*/
public function createFileRecord(string $storagePath, string $displayName, int $tenantId): File
{
$fullPath = storage_path("app/{$storagePath}");
return File::create([
'tenant_id' => $tenantId,
'document_type' => 'approval_attachment',
'display_name' => $displayName,
'original_name' => $displayName,
'stored_name' => basename($storagePath),
'file_path' => $storagePath,
'mime_type' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'file_size' => filesize($fullPath),
'file_type' => 'docx',
'is_temp' => false,
'uploaded_by' => auth()->id(),
]);
}
}