diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index 4eef9734..fb052b8b 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -21,4 +21,17 @@ object Dependencies { //junit const val JUNIT = "org.jetbrains.kotlin:kotlin-test-junit5" const val JUNIT_PLATFORM_LAUNCHER = "org.junit.platform:junit-platform-launcher" + + //poi + const val POI = "org.apache.poi:poi:${DependencyVersions.POI_VERSION}" + const val POI_OOXML = "org.apache.poi:poi-ooxml:${DependencyVersions.POI_VERSION}" + + //Pdf + const val PDF_ITEXT = "com.itextpdf:itext7-fonts:${DependencyVersions.PDF_ITEXT}" + const val PDF_HTML = "com.itextpdf:html2pdf:${DependencyVersions.PDF_HTML}" + + const val THYMELEAF = "org.springframework.boot:spring-boot-starter-thymeleaf" + + //commons io + const val COMMONS_IO = "commons-io:commons-io:${DependencyVersions.COMMONS_IO}" } \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/DependencyVersions.kt b/buildSrc/src/main/kotlin/DependencyVersions.kt index a229be14..e4f780cf 100644 --- a/buildSrc/src/main/kotlin/DependencyVersions.kt +++ b/buildSrc/src/main/kotlin/DependencyVersions.kt @@ -7,4 +7,9 @@ object DependencyVersions { // Kotlinx Coroutines const val KOTLINX_COROUTINES_VERSION = "1.8.1" + + const val PDF_ITEXT = "7.2.0" + const val PDF_HTML = "3.0.3" + const val POI_VERSION = "5.2.3" + const val COMMONS_IO = "2.11.0" } \ No newline at end of file diff --git a/casper-application-infrastructure/build.gradle.kts b/casper-application-infrastructure/build.gradle.kts index 42b05106..1f56128a 100644 --- a/casper-application-infrastructure/build.gradle.kts +++ b/casper-application-infrastructure/build.gradle.kts @@ -22,4 +22,13 @@ dependencies { implementation(Dependencies.KOTLIN_REFLECT) testImplementation(Dependencies.KOTLIN_TEST) + + // itext + implementation(Dependencies.PDF_HTML) + implementation (Dependencies.THYMELEAF) + + //read-file + implementation(Dependencies.COMMONS_IO) + implementation(Dependencies.POI) + implementation(Dependencies.POI_OOXML) } diff --git a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/config/ConverterPropertiesCreator.kt b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/config/ConverterPropertiesCreator.kt new file mode 100644 index 00000000..baff0308 --- /dev/null +++ b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/config/ConverterPropertiesCreator.kt @@ -0,0 +1,30 @@ +package hs.kr.entrydsm.application.global.document.pdf.config + +import com.itextpdf.html2pdf.ConverterProperties +import com.itextpdf.html2pdf.resolver.font.DefaultFontProvider +import com.itextpdf.io.font.FontProgramFactory +import org.springframework.stereotype.Component +import java.io.IOException + +@Component +class ConverterPropertiesCreator { + + private var fontPath: String = "/fonts/" + + fun createConverterProperties(): ConverterProperties { + val properties = ConverterProperties() + val fontProvider = DefaultFontProvider(false, false, false) + + Font.fonts.forEach { font -> + try { + val fontProgram = FontProgramFactory.createFont("$fontPath$font") + fontProvider.addFont(fontProgram) + } catch (e: IOException) { + throw IllegalStateException("폰트 파일을 찾을 수 없습니다: $font", e) + } + } + + properties.fontProvider = fontProvider + return properties + } +} diff --git a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/config/Font.kt b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/config/Font.kt new file mode 100644 index 00000000..85c5dde7 --- /dev/null +++ b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/config/Font.kt @@ -0,0 +1,10 @@ +package hs.kr.entrydsm.application.global.document.pdf.config + +object Font { + val fonts = listOf( + "KoPubWorld Dotum Light.ttf", + "KoPubWorld Dotum Bold.ttf", + "KoPubWorld Dotum Medium.ttf", + "DejaVuSans.ttf" + ) +} diff --git a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/config/PdfConfig.kt b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/config/PdfConfig.kt new file mode 100644 index 00000000..236965cb --- /dev/null +++ b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/config/PdfConfig.kt @@ -0,0 +1,23 @@ +package hs.kr.entrydsm.application.global.document.pdf.config + +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.thymeleaf.TemplateEngine +import org.thymeleaf.templatemode.TemplateMode +import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver + +@Configuration +class PdfConfig { + + @Bean + fun templateEngine(): TemplateEngine { + val templateResolver = ClassLoaderTemplateResolver().apply { + prefix = "classpath:/templates/" + suffix = ".html" + templateMode = TemplateMode.HTML + } + return TemplateEngine().apply { + this.setTemplateResolver(templateResolver) + } + } +} diff --git a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/data/IntroductionPdfConverter.kt b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/data/IntroductionPdfConverter.kt new file mode 100644 index 00000000..ab9e0a77 --- /dev/null +++ b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/data/IntroductionPdfConverter.kt @@ -0,0 +1,54 @@ +package hs.kr.entrydsm.application.global.document.pdf.data + +import org.springframework.stereotype.Component +import java.util.HashMap + +@Component +class IntroductionPdfConverter { + + fun execute(application: Any): PdfData { + val values: MutableMap = HashMap() + setIntroduction(application, values) + setPersonalInfo(application, values) + setSchoolInfo(application, values) + setPhoneNumber(application, values) + setReceiptCode(application, values) + return PdfData(values) + } + + private fun setPersonalInfo(application: Any, values: MutableMap) { + // TODO: Application 도메인 모델 연동 필요 + values["userName"] = "더미사용자명" + values["address"] = "더미주소" + values["detailAddress"] = "더미상세주소" + } + + private fun setSchoolInfo(application: Any, values: MutableMap) { + // TODO: 교육상태 및 졸업정보 연동 필요 + // 현재는 더미 데이터로 설정 + values["schoolName"] = "더미중학교" + } + + private fun setReceiptCode(application: Any, values: MutableMap) { + // TODO: Application 도메인 모델 연동 필요 + values["receiptCode"] = "더미수험번호" + } + + private fun setPhoneNumber(application: Any, values: MutableMap) { + values["applicantTel"] = toFormattedPhoneNumber("01012345678") + } + + private fun toFormattedPhoneNumber(phoneNumber: String): String { + if (phoneNumber.length == 8) { + return phoneNumber.replace("(\\d{4})(\\d{4})".toRegex(), "$1-$2") + } + return phoneNumber.replace("(\\d{2,3})(\\d{3,4})(\\d{4})".toRegex(), "$1-$2-$3") + } + + private fun setIntroduction(application: Any, values: MutableMap) { + values["selfIntroduction"] = "더미 자기소개 내용" + values["studyPlan"] = "더미 학업계획 내용" + values["newLineChar"] = "\n" + values["examCode"] = "더미수험번호" + } +} diff --git a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/data/PdfData.kt b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/data/PdfData.kt new file mode 100644 index 00000000..bd9fd6f8 --- /dev/null +++ b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/data/PdfData.kt @@ -0,0 +1,13 @@ +package hs.kr.entrydsm.application.global.document.pdf.data + +data class PdfData( + private val values: MutableMap +) { + fun toMap(): MutableMap = values + + fun getValue(key: String): Any? = values[key] + + fun setValue(key: String, value: Any) { + values[key] = value + } +} diff --git a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/data/PdfDataConverter.kt b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/data/PdfDataConverter.kt new file mode 100644 index 00000000..c23316c0 --- /dev/null +++ b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/data/PdfDataConverter.kt @@ -0,0 +1,264 @@ +package hs.kr.entrydsm.application.global.document.pdf.data + +import org.springframework.stereotype.Component +import java.time.LocalDate +import java.time.LocalDateTime +import java.time.YearMonth +import java.util.* + +@Component +class PdfDataConverter { + + fun applicationToInfo(application: Any, score: Any): PdfData { + val values: MutableMap = HashMap() + setReceiptCode(application, values) + setEntranceYear(values) + setPersonalInfo(application, values) + setGenderInfo(application, values) + setSchoolInfo(application, values) + setPhoneNumber(application, values) + setGraduationClassification(application, values) + setUserType(application, values) + setGradeScore(application, score, values) + setLocalDate(values) + setIntroduction(application, values) + setParentInfo(application, values) + setAllSubjectScores(application, values) + setAttendanceAndVolunteer(application, values) + setExtraScore(application, values) + setTeacherInfo(application, values) + setVeteransNumber(application, values) + + // TODO: 조건부 설정 로직 추가 + // if (application.isRecommendationsRequired()) { + // setRecommendations(application, values) + // } + + // if (!application.photoPath.isNullOrBlank()) { + // setBase64Image(application, values) + // } + + return PdfData(values) + } + + private fun setReceiptCode(application: Any, values: MutableMap) { + // TODO: Application 도메인 모델 연동 필요 + values["receiptCode"] = "더미데이터" + } + + private fun setEntranceYear(values: MutableMap) { + val entranceYear: Int = LocalDate.now().plusYears(1).year + values["entranceYear"] = entranceYear.toString() + } + + private fun setVeteransNumber(application: Any, values: MutableMap) { + // TODO: Application 도메인 모델 연동 필요 + values["veteransNumber"] = "" + } + + private fun setPersonalInfo(application: Any, values: MutableMap) { + // TODO: Application 도메인 모델 연동 필요 + values["userName"] = setBlankIfNull("더미사용자명") + values["isMale"] = toBallotBox(true) + values["isFemale"] = toBallotBox(false) + values["address"] = setBlankIfNull("더미주소") + values["detailAddress"] = setBlankIfNull("더미상세주소") + values["birthday"] = setBlankIfNull("2000.01.01") + + values["region"] = "대전" + values["applicationType"] = "일반전형" + values["applicationRemark"] = "해당없음" + } + + private fun setAttendanceAndVolunteer(application: Any, values: MutableMap) { + // TODO: ApplicationCase 도메인 모델 연동 필요 + values["absenceDayCount"] = 0 + values["latenessCount"] = 0 + values["earlyLeaveCount"] = 0 + values["lectureAbsenceCount"] = 0 + values["volunteerTime"] = 0 + } + + private fun setGenderInfo(application: Any, values: MutableMap) { + values["gender"] = setBlankIfNull("남") + } + + private fun setSchoolInfo(application: Any, values: MutableMap) { + // TODO: 졸업정보 및 학교정보 연동 필요 + values["schoolCode"] = "더미학교코드" + values["schoolRegion"] = "더미지역" + values["schoolClass"] = "더미반" + values["schoolTel"] = toFormattedPhoneNumber("0421234567") + values["schoolName"] = "더미중학교" + } + + private fun setPhoneNumber(application: Any, values: MutableMap) { + values["applicantTel"] = toFormattedPhoneNumber("01012345678") + values["parentTel"] = toFormattedPhoneNumber("01087654321") + } + + private fun setGraduationClassification(application: Any, values: MutableMap) { + values.putAll(emptyGraduationClassification()) + + // TODO: 졸업정보 연동 필요 + val yearMonth = YearMonth.now() + values["graduateYear"] = yearMonth.year.toString() + values["graduateMonth"] = yearMonth.monthValue.toString() + values["educationalStatus"] = "${yearMonth.year}년 ${yearMonth.monthValue}월 중학교 졸업" + } + + private fun setUserType(application: Any, values: MutableMap) { + val list = listOf( + "isQualificationExam" to false, + "isGraduate" to true, + "isProspectiveGraduate" to false, + "isDaejeon" to true, + "isNotDaejeon" to false, + "isBasicLiving" to false, + "isFromNorth" to false, + "isLowestIncome" to false, + "isMulticultural" to false, + "isOneParent" to false, + "isTeenHouseholder" to false, + "isPrivilegedAdmission" to false, + "isNationalMerit" to false, + "isProtectedChildren" to false, + "isCommon" to true, + "isMeister" to false, + "isSocialMerit" to false + ) + + list.forEach { (key, value) -> + values[key] = toBallotBox(value) + } + } + + private fun setExtraScore(application: Any, values: MutableMap) { + // TODO: ApplicationCase 연동 필요 + values["hasCompetitionPrize"] = toCircleBallotbox(false) + values["hasCertificate"] = toCircleBallotbox(false) + } + + private fun setGradeScore(application: Any, score: Any, values: MutableMap) { + // TODO: Score 도메인 모델 연동 필요 + with(values) { + put("conversionScore1st", "80.0") + put("conversionScore2nd", "85.0") + put("conversionScore3rd", "90.0") + put("conversionScore", "255.0") + put("attendanceScore", "15.0") + put("volunteerScore", "15.0") + put("finalScore", "285.0") + } + } + + private fun setAllSubjectScores(application: Any, values: MutableMap) { + // TODO: ApplicationCase 연동 필요 - 일반졸업 케이스로 더미 데이터 + val subjects = listOf("국어", "사회", "역사", "수학", "과학", "영어", "기술가정") + + subjects.forEach { subject -> + val subjectPrefix = when (subject) { + "국어" -> "korean" + "사회" -> "social" + "역사" -> "history" + "수학" -> "math" + "과학" -> "science" + "영어" -> "english" + "기술가정" -> "techAndHome" + else -> subject.lowercase() + } + + with(values) { + put("applicationCase", "기술∙가정") + put("${subjectPrefix}ThirdGradeSecondSemester", "A") + put("${subjectPrefix}ThirdGradeFirstSemester", "A") + put("${subjectPrefix}SecondGradeSecondSemester", "B") + put("${subjectPrefix}SecondGradeFirstSemester", "B") + } + } + } + + private fun setLocalDate(values: MutableMap) { + val now: LocalDateTime = LocalDateTime.now() + with(values) { + put("year", now.year.toString()) + put("month", now.monthValue.toString()) + put("day", now.dayOfMonth.toString()) + } + } + + private fun setIntroduction(application: Any, values: MutableMap) { + values["selfIntroduction"] = setBlankIfNull("더미 자기소개") + values["studyPlan"] = setBlankIfNull("더미 학업계획") + values["newLineChar"] = "\n" + } + + private fun setTeacherInfo(application: Any, values: MutableMap) { + // TODO: 졸업정보 연동 필요 + values["teacherName"] = "더미선생님" + values["teacherTel"] = toFormattedPhoneNumber("0421234567") + } + + private fun setParentInfo(application: Any, values: MutableMap) { + values["parentName"] = "더미학부모" + values["parentRelation"] = "부" + } + + private fun setRecommendations(application: Any, values: MutableMap) { + values["isDaejeonAndMeister"] = markIfTrue(false) + values["isDaejeonAndSocialMerit"] = markIfTrue(false) + values["isNotDaejeonAndMeister"] = markIfTrue(false) + values["isNotDaejeonAndSocialMerit"] = markIfTrue(false) + } + + private fun setBase64Image(application: Any, values: MutableMap) { + // TODO: 이미지 파일 연동 필요 + values["base64Image"] = "" + } + + private fun markIfTrue(isTrue: Boolean): String { + return if (isTrue) "◯" else "" + } + + private fun emptySchoolInfo(): Map { + return mapOf( + "schoolCode" to "", + "schoolClass" to "", + "schoolTel" to "", + "schoolName" to "" + ) + } + + private fun emptyGraduationClassification(): Map { + return mapOf( + "qualificationExamPassedYear" to "20__", + "qualificationExamPassedMonth" to "__", + "graduateYear" to "20__", + "graduateMonth" to "__", + "prospectiveGraduateYear" to "20__", + "prospectiveGraduateMonth" to "__" + ) + } + + private fun toFormattedPhoneNumber(phoneNumber: String?): String { + if (phoneNumber.isNullOrBlank()) { + return "" + } + if (phoneNumber.length == 8) { + return phoneNumber.replace("(\\d{4})(\\d{4})".toRegex(), "$1-$2") + } + return phoneNumber.replace("(\\d{2,3})(\\d{3,4})(\\d{4})".toRegex(), "$1-$2-$3") + } + + private fun setBlankIfNull(input: String?): String { + return input ?: "" + } + + private fun toBallotBox(isTrue: Boolean): String { + return if (isTrue) "☑" else "☐" + } + + private fun toCircleBallotbox(isTrue: Boolean): String { + return if (isTrue) "O" else "X" + } +} diff --git a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/data/TemplateFileName.kt b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/data/TemplateFileName.kt new file mode 100644 index 00000000..47d86c46 --- /dev/null +++ b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/data/TemplateFileName.kt @@ -0,0 +1,11 @@ +package hs.kr.entrydsm.application.global.document.pdf.data + +object TemplateFileName { + const val APPLICATION_FOR_ADMISSION = "application_for_admission" + const val PRIVACY_AGREEMENT = "privacy_agreement" + const val INTRODUCTION = "introduction" + const val NON_SMOKING = "nonsmoking" + const val SMOKING_EXAMINE = "smoking_examine" + const val RECOMMENDATION = "recommendation" + const val ADMIN_INTRODUCTION = "admin_introduction" +} diff --git a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/facade/PdfDocumentFacade.kt b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/facade/PdfDocumentFacade.kt new file mode 100644 index 00000000..048c7cf4 --- /dev/null +++ b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/facade/PdfDocumentFacade.kt @@ -0,0 +1,20 @@ +package hs.kr.entrydsm.application.global.document.pdf.facade + +import com.itextpdf.kernel.pdf.PdfDocument +import com.itextpdf.kernel.pdf.PdfReader +import org.springframework.stereotype.Component +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream + +@Component +class PdfDocumentFacade { + + fun getPdfDocument(pdfStream: ByteArrayOutputStream): PdfDocument? { + return try { + val inputStream = ByteArrayInputStream(pdfStream.toByteArray()) + PdfDocument(PdfReader(inputStream)) + } catch (e: Exception) { + null + } + } +} diff --git a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/generator/ApplicationPdfGenerator.kt b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/generator/ApplicationPdfGenerator.kt new file mode 100644 index 00000000..bc72993c --- /dev/null +++ b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/generator/ApplicationPdfGenerator.kt @@ -0,0 +1,83 @@ +package hs.kr.entrydsm.application.global.document.pdf.generator + +import com.itextpdf.kernel.pdf.PdfDocument +import com.itextpdf.kernel.pdf.PdfReader +import com.itextpdf.kernel.pdf.PdfWriter +import com.itextpdf.kernel.utils.PdfMerger +import com.itextpdf.layout.Document +import hs.kr.entrydsm.application.global.document.pdf.data.PdfDataConverter +import hs.kr.entrydsm.application.global.document.pdf.data.TemplateFileName +import hs.kr.entrydsm.application.global.document.pdf.facade.PdfDocumentFacade +import org.springframework.stereotype.Component +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.io.IOException +import java.io.InputStream +import java.util.* + +@Component +class ApplicationPdfGenerator( + private val pdfProcessor: PdfProcessor, + private val pdfDataConverter: PdfDataConverter, + private val templateProcessor: TemplateProcessor, + private val pdfDocumentFacade: PdfDocumentFacade +) { + + fun generate(application: Any, score: Any): ByteArray { + return generateApplicationPdf(application, score) + } + + private fun generateApplicationPdf(application: Any, score: Any): ByteArray { + val data = pdfDataConverter.applicationToInfo(application, score) + val templates = getTemplateFileNames(application) + + val outStream = templates.stream() + .map { template -> + templateProcessor.convertTemplateIntoHtmlString(template, data.toMap()) + } + .map { html -> + pdfProcessor.convertHtmlToPdf(html) + } + .toArray { size -> arrayOfNulls(size) } + + val outputStream = ByteArrayOutputStream() + val mergedDocument = PdfDocument(PdfWriter(outputStream)) + val pdfMerger = PdfMerger(mergedDocument) + val document = Document(mergedDocument) + + for (pdfStream in outStream) { + val pdfDoc = pdfDocumentFacade.getPdfDocument(pdfStream!!) + mergeDocument(pdfMerger, pdfDoc) + } + + document.close() + + return outputStream.toByteArray() + } + + private fun mergeDocument(merger: PdfMerger, document: PdfDocument?) { + if (document != null) { + merger.merge(document, 1, document.numberOfPages) + document.close() + } + } + + private fun getTemplateFileNames(application: Any): MutableList { + val result = LinkedList( + listOf( + TemplateFileName.APPLICATION_FOR_ADMISSION, + TemplateFileName.PRIVACY_AGREEMENT, + TemplateFileName.INTRODUCTION, + TemplateFileName.NON_SMOKING, + TemplateFileName.SMOKING_EXAMINE + ) + ) + + // TODO: 조건부 추천서 추가 로직 + // if (!application.isQualificationExam() && !application.isCommonApplicationType()) { + // result.add(2, TemplateFileName.RECOMMENDATION) + // } + + return result + } +} diff --git a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/generator/IntroductionPdfGenerator.kt b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/generator/IntroductionPdfGenerator.kt new file mode 100644 index 00000000..73604cd9 --- /dev/null +++ b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/generator/IntroductionPdfGenerator.kt @@ -0,0 +1,23 @@ +package hs.kr.entrydsm.application.global.document.pdf.generator + +import hs.kr.entrydsm.application.global.document.pdf.data.IntroductionPdfConverter +import hs.kr.entrydsm.application.global.document.pdf.data.TemplateFileName +import org.springframework.stereotype.Component + +@Component +class IntroductionPdfGenerator( + private val pdfProcessor: PdfProcessor, + private val introductionPdfConverter: IntroductionPdfConverter, + private val templateProcessor: TemplateProcessor +) { + + fun generate(application: Any): ByteArray { + val data = introductionPdfConverter.execute(application) + val html = templateProcessor.convertTemplateIntoHtmlString( + TemplateFileName.ADMIN_INTRODUCTION, + data.toMap() + ) + val pdfStream = pdfProcessor.convertHtmlToPdf(html) + return pdfStream.toByteArray() + } +} diff --git a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/generator/PdfProcessor.kt b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/generator/PdfProcessor.kt new file mode 100644 index 00000000..dac284d0 --- /dev/null +++ b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/generator/PdfProcessor.kt @@ -0,0 +1,18 @@ +package hs.kr.entrydsm.application.global.document.pdf.generator + +import com.itextpdf.html2pdf.HtmlConverter +import hs.kr.entrydsm.application.global.document.pdf.config.ConverterPropertiesCreator +import org.springframework.stereotype.Component +import java.io.ByteArrayOutputStream + +@Component +class PdfProcessor( + private val converterPropertiesCreator: ConverterPropertiesCreator, +) { + + fun convertHtmlToPdf(html: String): ByteArrayOutputStream { + val outputStream = ByteArrayOutputStream() + HtmlConverter.convertToPdf(html, outputStream, converterPropertiesCreator.createConverterProperties()) + return outputStream + } +} diff --git a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/generator/TemplateProcessor.kt b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/generator/TemplateProcessor.kt new file mode 100644 index 00000000..c30e2cbb --- /dev/null +++ b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/generator/TemplateProcessor.kt @@ -0,0 +1,17 @@ +package hs.kr.entrydsm.application.global.document.pdf.generator + +import org.springframework.stereotype.Component +import org.thymeleaf.TemplateEngine +import org.thymeleaf.context.Context + +@Component +class TemplateProcessor( + private val templateEngine: TemplateEngine +) { + + fun convertTemplateIntoHtmlString(template: String?, data: MutableMap?): String { + val context = Context() + context.setVariables(data) + return templateEngine.process(template, context) + } +} diff --git a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/presentation/PdfTestController.kt b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/presentation/PdfTestController.kt new file mode 100644 index 00000000..3199fd80 --- /dev/null +++ b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/presentation/PdfTestController.kt @@ -0,0 +1,41 @@ +package hs.kr.entrydsm.application.global.document.pdf.presentation + +import hs.kr.entrydsm.application.global.document.pdf.generator.ApplicationPdfGenerator +import hs.kr.entrydsm.application.global.document.pdf.generator.IntroductionPdfGenerator +import org.springframework.http.MediaType +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController + +@RestController +@RequestMapping("/pdf") +class PdfTestController( + private val applicationPdfGenerator: ApplicationPdfGenerator, + private val introductionPdfGenerator: IntroductionPdfGenerator +) { + @GetMapping("/test") + fun testPdf(): ResponseEntity { + val dummyApp = Any() + val dummyScore = Any() + + val pdfBytes = applicationPdfGenerator.generate(dummyApp, dummyScore) + + return ResponseEntity.ok() + .contentType(MediaType.APPLICATION_PDF) + .header("Content-Disposition", "inline; filename=test.pdf") + .body(pdfBytes) + } + + @GetMapping("/test-introduction") + fun testIntroductionPdf(): ResponseEntity { + val dummyApp = Any() + + val pdfBytes = introductionPdfGenerator.generate(dummyApp) + + return ResponseEntity.ok() + .contentType(MediaType.APPLICATION_PDF) + .header("Content-Disposition", "inline; filename=test-introduction.pdf") + .body(pdfBytes) + } +} \ No newline at end of file diff --git a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/excel/generator/PrintAdmissionTicketGenerator.kt b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/excel/generator/PrintAdmissionTicketGenerator.kt new file mode 100644 index 00000000..3a633ff5 --- /dev/null +++ b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/excel/generator/PrintAdmissionTicketGenerator.kt @@ -0,0 +1,198 @@ +package hs.kr.entrydsm.application.global.excel.generator + +import org.apache.poi.ss.usermodel.* +import org.apache.poi.ss.util.CellRangeAddress +import org.apache.poi.ss.util.CellReference +import org.apache.poi.xssf.usermodel.XSSFClientAnchor +import org.apache.poi.xssf.usermodel.XSSFDrawing +import org.apache.poi.xssf.usermodel.XSSFWorkbook +import org.springframework.core.io.ClassPathResource +import org.springframework.stereotype.Component +import java.io.IOException +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter +import jakarta.servlet.http.HttpServletResponse + +@Component +class PrintAdmissionTicketGenerator { + companion object { + const val EXCEL_PATH = "/excel/excel-form.xlsx" + } + + private lateinit var drawing: XSSFDrawing + + fun execute(response: HttpServletResponse) { + val targetWorkbook = generate() + try { + setResponseHeaders(response) + targetWorkbook.write(response.outputStream) + } catch (e: IOException) { + throw IllegalArgumentException("Excel 파일 생성 중 오류가 발생했습니다.", e) + } finally { + targetWorkbook.close() + } + } + + fun generate(): Workbook { + val sourceWorkbook = loadSourceWorkbook() + val targetWorkbook = XSSFWorkbook() + + val sourceSheet = sourceWorkbook.getSheetAt(0) + val targetSheet = targetWorkbook.createSheet("수험표") + + drawing = targetSheet.createDrawingPatriarch() as XSSFDrawing + + val styleMap = createStyleMap(sourceWorkbook, targetWorkbook) + targetSheet.setDefaultColumnWidth(13) + + // 더미 데이터 + val dummyApplications = listOf( + mapOf( + "receiptCode" to 1001L, + "examCode" to "DUMMY001", + "applicantName" to "홍길동", + "schoolName" to "더미고등학교", + "isDaejeon" to "대전", + "applicationType" to "일반전형" + ), + mapOf( + "receiptCode" to 1002L, + "examCode" to "DUMMY002", + "applicantName" to "김철수", + "schoolName" to "테스트고등학교", + "isDaejeon" to "전국", + "applicationType" to "마이스터전형" + ) + ) + + var currentRowIndex = 0 + dummyApplications.forEach { dummyApp -> + fillApplicationData(sourceSheet, 0, dummyApp, sourceWorkbook) + copyRows(sourceSheet, targetSheet, 0, 16, currentRowIndex, styleMap) + copyDummyImage(targetSheet, currentRowIndex) + currentRowIndex += 20 + } + + sourceWorkbook.close() + return targetWorkbook + } + + fun loadSourceWorkbook(): Workbook { + val resource = ClassPathResource(EXCEL_PATH) + return resource.inputStream.use { XSSFWorkbook(it) } + } + + fun createStyleMap(sourceWorkbook: Workbook, targetWorkbook: Workbook): Map { + val styleCache = mutableMapOf() + return (0 until sourceWorkbook.numCellStyles).associate { i -> + val sourceStyle = sourceWorkbook.getCellStyleAt(i) + val targetStyle = styleCache.getOrPut(sourceStyle.index) { + val newStyle = targetWorkbook.createCellStyle() + newStyle.cloneStyleFrom(sourceStyle) + newStyle + } + sourceStyle to targetStyle + } + } + + fun copyRows(sourceSheet: Sheet, targetSheet: Sheet, sourceStartRow: Int, sourceEndRow: Int, targetStartRow: Int, styleMap: Map) { + for (i in sourceStartRow..sourceEndRow) { + val sourceRow = sourceSheet.getRow(i) + val targetRow = targetSheet.createRow(targetStartRow + i - sourceStartRow) + if (sourceRow != null) { + copyRow(sourceSheet, targetSheet, sourceRow, targetRow, styleMap) + } + } + } + + fun copyRow(sourceSheet: Sheet, targetSheet: Sheet, sourceRow: Row, targetRow: Row, styleMap: Map) { + targetRow.height = sourceRow.height + + for (i in 0 until sourceRow.lastCellNum) { + val oldCell = sourceRow.getCell(i) + val newCell = targetRow.createCell(i) + + if (oldCell == null) { + continue + } + + copyCell(oldCell, newCell, styleMap) + } + + for (i in 0 until sourceSheet.numMergedRegions) { + val mergedRegion = sourceSheet.getMergedRegion(i) + if (mergedRegion.firstRow == sourceRow.rowNum) { + val newMergedRegion = CellRangeAddress( + targetRow.rowNum, + targetRow.rowNum + (mergedRegion.lastRow - mergedRegion.firstRow), + mergedRegion.firstColumn, + mergedRegion.lastColumn + ) + targetSheet.addMergedRegion(newMergedRegion) + } + } + } + + fun copyCell(oldCell: Cell, newCell: Cell, styleMap: Map) { + val newStyle = styleMap[oldCell.cellStyle] + if (newStyle != null) { + newCell.cellStyle = newStyle + } + + when (oldCell.cellType) { + CellType.BLANK -> newCell.setBlank() + CellType.BOOLEAN -> newCell.setCellValue(oldCell.booleanCellValue) + CellType.ERROR -> newCell.setCellErrorValue(oldCell.errorCellValue) + CellType.FORMULA -> newCell.cellFormula = oldCell.cellFormula + CellType.NUMERIC -> newCell.setCellValue(oldCell.numericCellValue) + CellType.STRING -> newCell.setCellValue(oldCell.richStringCellValue) + else -> newCell.setCellValue(oldCell.stringCellValue) + } + } + + fun fillApplicationData(sheet: Sheet, startRowIndex: Int, dummyApp: Map, workbook: Workbook) { + setValue(sheet, "E4", dummyApp["examCode"].toString()) + setValue(sheet, "E6", dummyApp["applicantName"].toString()) + setValue(sheet, "E8", dummyApp["schoolName"].toString()) + setValue(sheet, "E10", dummyApp["isDaejeon"].toString()) + setValue(sheet, "E12", dummyApp["applicationType"].toString()) + setValue(sheet, "E14", dummyApp["receiptCode"].toString()) + } + + fun copyDummyImage(targetSheet: Sheet, targetRowIndex: Int) { + // 더미 이미지 데이터 (빈 바이트 배열) + val dummyImageBytes = ByteArray(100) { 0 } + + try { + val workbook = targetSheet.workbook + val pictureId = workbook.addPicture(dummyImageBytes, Workbook.PICTURE_TYPE_PNG) + val anchor = XSSFClientAnchor() + + anchor.setCol1(0) + anchor.row1 = targetRowIndex + 3 + anchor.setCol2(2) + anchor.row2 = targetRowIndex + 15 + + drawing.createPicture(anchor, pictureId) + } catch (e: Exception) { + // 더미 이미지 추가 실패 시 무시 + } + } + + fun setValue(sheet: Sheet, position: String, value: String) { + val ref = CellReference(position) + val r = sheet.getRow(ref.row) + if (r != null) { + val c = r.getCell(ref.col.toInt()) + c?.setCellValue(value) + } + } + + fun setResponseHeaders(response: HttpServletResponse) { + response.contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" + val formatFilename = "attachment;filename=\"수험표" + val time = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy년MM월dd일_HH시mm분")) + val fileName = String(("$formatFilename$time.xlsx\"").toByteArray(Charsets.UTF_8), Charsets.ISO_8859_1) + response.setHeader("Content-Disposition", fileName) + } +} diff --git a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/excel/generator/PrintApplicantCodesGenerator.kt b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/excel/generator/PrintApplicantCodesGenerator.kt new file mode 100644 index 00000000..4b638deb --- /dev/null +++ b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/excel/generator/PrintApplicantCodesGenerator.kt @@ -0,0 +1,48 @@ +package hs.kr.entrydsm.application.global.excel.generator + +import hs.kr.entrydsm.application.global.excel.model.ApplicantCode +import jakarta.servlet.http.HttpServletResponse +import org.apache.poi.ss.usermodel.Row +import org.springframework.stereotype.Component +import java.io.IOException +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter + +@Component +class PrintApplicantCodesGenerator { + fun execute(response: HttpServletResponse) { + val applicantCode = ApplicantCode() + val sheet = applicantCode.getSheet() + applicantCode.format() + + // 더미 데이터로 테스트 + val dummyData = listOf( + Triple("DUMMY001", 1001L, "홍길동"), + Triple("DUMMY002", 1002L, "김철수"), + Triple("DUMMY003", 1003L, "이영희") + ) + + dummyData.forEachIndexed { index, (examCode, receiptCode, name) -> + val row = sheet.createRow(index + 1) + insertCode(row, examCode, receiptCode, name) + } + + try { + response.contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" + val formatFilename = "attachment;filename=\"지원자번호목록" + val time = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy년MM월dd일_HH시mm분")) + val fileName = String(("$formatFilename$time.xlsx\"").toByteArray(Charsets.UTF_8), Charsets.ISO_8859_1) + response.setHeader("Content-Disposition", fileName) + + applicantCode.getWorkbook().write(response.outputStream) + } catch (e: IOException) { + throw IllegalArgumentException("Excel 파일 생성 중 오류가 발생했습니다.", e) + } + } + + private fun insertCode(row: Row, examCode: String, receiptCode: Long, name: String) { + row.createCell(0).setCellValue(examCode) + row.createCell(1).setCellValue(receiptCode.toString()) + row.createCell(2).setCellValue(name) + } +} diff --git a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/excel/generator/PrintApplicationCheckListGenerator.kt b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/excel/generator/PrintApplicationCheckListGenerator.kt new file mode 100644 index 00000000..2530180e --- /dev/null +++ b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/excel/generator/PrintApplicationCheckListGenerator.kt @@ -0,0 +1,307 @@ +package hs.kr.entrydsm.application.global.excel.generator + +import jakarta.servlet.ServletOutputStream +import jakarta.servlet.http.HttpServletResponse +import org.apache.poi.ss.usermodel.* +import org.apache.poi.ss.util.CellRangeAddress +import org.apache.poi.ss.util.RegionUtil +import org.apache.poi.xssf.usermodel.XSSFWorkbook +import org.springframework.stereotype.Component +import java.io.IOException +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter + +@Component +class PrintApplicationCheckListGenerator { + + private val workbook: Workbook = XSSFWorkbook() + private val sheet: Sheet = workbook.createSheet("application Check List") + + fun printApplicationCheckList(httpServletResponse: HttpServletResponse) { + var outputStream: ServletOutputStream? = null + var dh = 0 + try { + // 더미 데이터 + val dummyApplications = listOf( + createDummyApplication(1001L, "홍길동", "더미고등학교"), + createDummyApplication(1002L, "김철수", "테스트고등학교"), + createDummyApplication(1003L, "이영희", "샘플고등학교") + ) + + dummyApplications.forEach { dummyData -> + formatSheet(dh) + insertDataIntoSheet(dummyData, dh) + dh += 20 + } + + httpServletResponse.apply { + contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" + val formatFilename = "attachment;filename=\"점검표" + val time = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy년MM월dd일_HH시mm분")) + val fileName = String(("$formatFilename$time.xlsx\"").toByteArray(Charsets.UTF_8), Charsets.ISO_8859_1) + setHeader("Content-Disposition", fileName) + } + + outputStream = httpServletResponse.outputStream + workbook.write(outputStream) + outputStream.flush() + } catch (e: IOException) { + if (!httpServletResponse.isCommitted) { + httpServletResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR) + } else { + throw IllegalArgumentException("Excel 파일 생성 중 오류가 발생했습니다.") + } + } finally { + try { + outputStream?.close() + } catch (e: Exception) { + workbook.close() + } + } + } + + private fun createDummyApplication(receiptCode: Long, name: String, schoolName: String): Map { + return mapOf( + "receiptCode" to receiptCode, + "applicationType" to "일반전형", + "applicantName" to name, + "birthDate" to "2005-03-15", + "applicantTel" to "010-1234-5678", + "parentTel" to "010-9876-5432", + "educationalStatus" to "졸업예정", + "applicationRemark" to "해당없음", + "isDaejeon" to "대전", + "sex" to "남", + "schoolName" to schoolName, + "graduateYear" to "2024", + "studentNumber" to "30315", + "phoneNumber" to "010-1234-5678", + "parentPhoneNumber" to "010-9876-5432" + ) + } + + private fun formatSheet(dh: Int) { + sheet.apply { + mergeRegions(dh) + applyBorderStyles(dh) + setCellValues(dh) + } + } + + private fun Sheet.mergeRegions(rowOffset: Int) { + val mergedRegions = arrayOf( + CellRangeAddress(1 + rowOffset, 1 + rowOffset, 3, 5), + CellRangeAddress(3 + rowOffset, 3 + rowOffset, 2, 3), + CellRangeAddress(4 + rowOffset, 4 + rowOffset, 2, 3), + CellRangeAddress(5 + rowOffset, 5 + rowOffset, 2, 3), + CellRangeAddress(4 + rowOffset, 4 + rowOffset, 6, 7), + CellRangeAddress(5 + rowOffset, 5 + rowOffset, 6, 7), + CellRangeAddress(3 + rowOffset, 3 + rowOffset, 6, 7) + ) + mergedRegions.forEach { + if (!isRegionMerged(it)) { + addMergedRegion(it) + } + } + } + + private fun Sheet.isRegionMerged(region: CellRangeAddress): Boolean { + return mergedRegions.any { it.firstRow == region.firstRow && it.lastRow == region.lastRow && it.firstColumn == region.firstColumn && it.lastColumn == region.lastColumn } + } + + private fun Sheet.applyBorderStyles(dh: Int) { + val borderRegionsDashedBottom = arrayOf( + intArrayOf(3 + dh, 3 + dh, 1, 1), + intArrayOf(4 + dh, 4 + dh, 1, 3), + intArrayOf(5 + dh, 5 + dh, 1, 3), + intArrayOf(3 + dh, 3 + dh, 5, 7), + intArrayOf(4 + dh, 4 + dh, 5, 7), + intArrayOf(5 + dh, 5 + dh, 5, 7), + intArrayOf(7 + dh, 7 + dh, 1, 7), + intArrayOf(11 + dh, 11 + dh, 1, 7), + intArrayOf(12 + dh, 12 + dh, 1, 7), + intArrayOf(13 + dh, 13 + dh, 1, 7), + intArrayOf(14 + dh, 14 + dh, 1, 7), + intArrayOf(15 + dh, 15 + dh, 1, 7), + intArrayOf(16 + dh, 16 + dh, 1, 7), + ) + setBorderStyle(borderRegionsDashedBottom, BorderStyle.DASHED, Direction.BOTTOM) + + val borderRegionsThin = arrayOf( + intArrayOf(1 + dh, 1 + dh, 1, 7), + intArrayOf(3 + dh, 3 + dh, 1, 1), + intArrayOf(4 + dh, 5 + dh, 1, 3), + intArrayOf(3 + dh, 5 + dh, 5, 7), + intArrayOf(7 + dh, 8 + dh, 1, 7), + intArrayOf(10 + dh, 17 + dh, 1, 7), + intArrayOf(10 + dh, 10 + dh, 1, 5), + intArrayOf(18 + dh, 18 + dh, 1, 5), + ) + setBorderStyle(borderRegionsThin, BorderStyle.THIN, Direction.ALL) + + val borderRegionsThick = arrayOf( + intArrayOf(18 + dh, 18 + dh, 6, 7), + intArrayOf(10 + dh, 10 + dh, 6, 7), + intArrayOf(1 + dh, 1 + dh, 2, 2), + intArrayOf(3 + dh, 3 + dh, 2, 2), + intArrayOf(18 + dh, 18 + dh, 6, 7), + intArrayOf(19 + dh, 19 + dh, 6, 7) + ) + setBorderStyle(borderRegionsThick, BorderStyle.THICK, Direction.ALL) + + val borderRegionsDashedRight = arrayOf( + intArrayOf(18 + dh, 18 + dh, 2, 3), + intArrayOf(1 + dh, 1 + dh, 4, 5), + intArrayOf(1 + dh, 1 + dh, 5, 6), + intArrayOf(4 + dh, 5 + dh, 1, 2), + intArrayOf(7 + dh, 8 + dh, 1, 1), + intArrayOf(7 + dh, 8 + dh, 2, 2), + intArrayOf(7 + dh, 8 + dh, 3, 3), + intArrayOf(7 + dh, 8 + dh, 4, 4), + intArrayOf(7 + dh, 8 + dh, 5, 5), + intArrayOf(7 + dh, 8 + dh, 6, 6), + intArrayOf(10 + dh, 18 + dh, 1, 1), + intArrayOf(10 + dh, 18 + dh, 2, 2), + intArrayOf(10 + dh, 18 + dh, 3, 3), + intArrayOf(10 + dh, 18 + dh, 4, 4), + intArrayOf(10 + dh, 18 + dh, 6, 6), + intArrayOf(3 + dh, 5 + dh, 1, 1), + intArrayOf(19 + dh, 19 + dh, 6, 6) + ) + setBorderStyle(borderRegionsDashedRight, BorderStyle.DASHED, Direction.RIGHT) + + val borderRegionsThinRight = arrayOf( + intArrayOf(11 + dh, 17 + dh, 5, 5), + intArrayOf(3 + dh, 5 + dh, 5, 5) + ) + setBorderStyle(borderRegionsThinRight, BorderStyle.THIN, Direction.RIGHT) + } + + private fun Sheet.setCellValues(dh: Int) { + val cellValues = mapOf( + Pair(1 + dh, 1) to "접수번호", + Pair(3 + dh, 5) to "학번", + Pair(4 + dh, 5) to "학생", + Pair(5 + dh, 5) to "보호자", + Pair(7 + dh, 1) to "결석", + Pair(7 + dh, 2) to "지각", + Pair(7 + dh, 3) to "조퇴", + Pair(7 + dh, 4) to "결과", + Pair(7 + dh, 5) to "출석점수", + Pair(7 + dh, 6) to "봉사시간", + Pair(7 + dh, 7) to "봉사점수", + Pair(10 + dh, 1) to "과목", + Pair(10 + dh, 2) to "3_2학기", + Pair(10 + dh, 3) to "3_1학기", + Pair(10 + dh, 4) to "직전", + Pair(10 + dh, 5) to "직전전", + Pair(10 + dh, 6) to "교과성적", + Pair(11 + dh, 6) to "대회", + Pair(12 + dh, 6) to "기능사", + Pair(13 + dh, 6) to "가산점", + Pair(19 + dh, 6) to "총점", + Pair(18 + dh, 6) to "환산점수", + Pair(11 + dh, 1) to "국어", + Pair(12 + dh, 1) to "사회", + Pair(13 + dh, 1) to "역사", + Pair(14 + dh, 1) to "수학", + Pair(15 + dh, 1) to "과학", + Pair(16 + dh, 1) to "기술가정", + Pair(17 + dh, 1) to "영어", + Pair(18 + dh, 1) to "점수" + ) + cellValues.forEach { (cell, value) -> + getCell(cell.first, cell.second).setCellValue(value) + } + } + + private fun setBorderStyle(regions: Array, borderStyle: BorderStyle, direction: Direction) { + regions.forEach { region -> + val address = CellRangeAddress(region[0], region[1], region[2], region[3]) + when (direction) { + Direction.TOP -> RegionUtil.setBorderTop(borderStyle, address, sheet) + Direction.BOTTOM -> RegionUtil.setBorderBottom(borderStyle, address, sheet) + Direction.LEFT -> RegionUtil.setBorderLeft(borderStyle, address, sheet) + Direction.RIGHT -> RegionUtil.setBorderRight(borderStyle, address, sheet) + Direction.ALL -> { + RegionUtil.setBorderTop(borderStyle, address, sheet) + RegionUtil.setBorderBottom(borderStyle, address, sheet) + RegionUtil.setBorderLeft(borderStyle, address, sheet) + RegionUtil.setBorderRight(borderStyle, address, sheet) + } + } + } + } + + private fun getCell(rowNum: Int, cellNum: Int): Cell { + val row = sheet.getRow(rowNum) ?: sheet.createRow(rowNum) + return row.getCell(cellNum) ?: row.createCell(cellNum) + } + + private fun setRowHeight(rowIndex: Int, height: Int) { + val row = sheet.getRow(rowIndex) ?: sheet.createRow(rowIndex) + row.heightInPoints = height.toFloat() + } + + private fun insertDataIntoSheet(dummyData: Map, dh: Int) { + getCell(dh + 1, 2).setCellValue(dummyData["receiptCode"].toString()) + getCell(dh + 1, 3).setCellValue(dummyData["schoolName"].toString()) + getCell(dh + 1, 6).setCellValue(dummyData["educationalStatus"].toString()) + getCell(dh + 1, 7).setCellValue(dummyData["graduateYear"].toString()) + getCell(dh + 4, 1).setCellValue(dummyData["applicationType"].toString()) + getCell(dh + 3, 2).setCellValue(dummyData["applicantName"].toString()) + getCell(dh + 3, 6).setCellValue(dummyData["studentNumber"].toString()) + getCell(dh + 3, 1).setCellValue(dummyData["isDaejeon"].toString()) + getCell(dh + 4, 2).setCellValue(dummyData["birthDate"].toString()) + getCell(dh + 4, 6).setCellValue(dummyData["phoneNumber"].toString()) + getCell(dh + 5, 1).setCellValue(dummyData["applicationRemark"].toString()) + getCell(dh + 5, 2).setCellValue(dummyData["sex"].toString()) + getCell(dh + 5, 6).setCellValue(dummyData["parentPhoneNumber"].toString()) + + // 출석 관련 더미 데이터 + getCell(dh + 8, 1).setCellValue("0") + getCell(dh + 8, 2).setCellValue("0") + getCell(dh + 8, 3).setCellValue("0") + getCell(dh + 8, 4).setCellValue("0") + getCell(dh + 8, 5).setCellValue("20.0") + getCell(dh + 8, 6).setCellValue("30.0") + getCell(dh + 8, 7).setCellValue("15.0") + getCell(dh + 10, 7).setCellValue("170.5") + + // 성적 더미 데이터 + val subjects = listOf("국어", "사회", "역사", "수학", "과학", "기술가정", "영어") + val dummyGrades = listOf("A", "B", "A", "B", "A", "B", "A") + + subjects.forEachIndexed { index, subject -> + val rowIndex = dh + 11 + index + getCell(rowIndex, 1).setCellValue(subject) + getCell(rowIndex, 2).setCellValue(dummyGrades[index]) + getCell(rowIndex, 3).setCellValue(dummyGrades[index]) + getCell(rowIndex, 4).setCellValue(dummyGrades[index]) + getCell(rowIndex, 5).setCellValue(dummyGrades[index]) + } + + getCell(dh + 11, 7).setCellValue("O") + getCell(dh + 12, 7).setCellValue("X") + getCell(dh + 13, 7).setCellValue("5.0") + getCell(dh + 18, 2).setCellValue("180.0") + getCell(dh + 18, 3).setCellValue("170.0") + getCell(dh + 18, 4).setCellValue("165.0") + getCell(dh + 18, 5).setCellValue("160.0") + getCell(dh + 18, 7).setCellValue("170.5") + getCell(dh + 19, 7).setCellValue("210.5") + + setRowHeight(dh + 2, 10) + setRowHeight(dh + 6, 10) + setRowHeight(dh + 9, 10) + setRowHeight(dh + 0, 71) + } + + enum class Direction { + TOP, + BOTTOM, + LEFT, + RIGHT, + ALL + } +} diff --git a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/excel/generator/PrintApplicationInfoGenerator.kt b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/excel/generator/PrintApplicationInfoGenerator.kt new file mode 100644 index 00000000..15b4bed9 --- /dev/null +++ b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/excel/generator/PrintApplicationInfoGenerator.kt @@ -0,0 +1,118 @@ +package hs.kr.entrydsm.application.global.excel.generator + +import hs.kr.entrydsm.application.global.excel.model.ApplicationInfo +import org.apache.poi.ss.usermodel.Row +import org.springframework.stereotype.Component +import java.io.IOException +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter +import jakarta.servlet.http.HttpServletResponse + +@Component +class PrintApplicationInfoGenerator { + + fun execute(httpServletResponse: HttpServletResponse) { + val applicationInfo = ApplicationInfo() + val sheet = applicationInfo.getSheet() + applicationInfo.format() + + // 더미 데이터로 테스트 + val dummyApplications = listOf( + createDummyApplication(1001L, "홍길동", "더미고등학교"), + createDummyApplication(1002L, "김철수", "테스트고등학교"), + createDummyApplication(1003L, "이영희", "샘플고등학교") + ) + + dummyApplications.forEachIndexed { index, dummyData -> + val row = sheet.createRow(index + 1) + insertCode(row, dummyData) + } + + try { + httpServletResponse.contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" + val formatFilename = "attachment;filename=\"전형자료" + val time = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy년MM월dd일_HH시mm분")) + val fileName = String(("$formatFilename$time.xlsx\"").toByteArray(Charsets.UTF_8), Charsets.ISO_8859_1) + httpServletResponse.setHeader("Content-Disposition", fileName) + + applicationInfo.getWorkbook().use { workbook -> + workbook.write(httpServletResponse.outputStream) + } + } catch (e: IOException) { + throw IllegalArgumentException("Excel 파일 생성 중 오류가 발생했습니다.", e) + } + } + + private fun createDummyApplication(receiptCode: Long, name: String, schoolName: String): Map { + return mapOf( + "receiptCode" to receiptCode, + "applicationType" to "일반전형", + "isDaejeon" to "대전", + "applicationRemark" to "해당없음", + "applicantName" to name, + "birthDate" to "2005-03-15", + "address" to "대전광역시 유성구 대덕대로 1234", + "applicantTel" to "010-1234-5678", + "sex" to "남", + "educationalStatus" to "졸업예정", + "graduateDate" to "2024", + "schoolName" to schoolName, + "classNumber" to "3", + "parentName" to "홍부모", + "parentTel" to "010-9876-5432", + "examCode" to "DUMMY${receiptCode.toString().takeLast(3)}" + ) + } + + private fun insertCode(row: Row, dummyData: Map) { + row.createCell(0).setCellValue(dummyData["receiptCode"].toString()) + row.createCell(1).setCellValue(dummyData["applicationType"].toString()) + row.createCell(2).setCellValue(dummyData["isDaejeon"].toString()) + row.createCell(3).setCellValue(dummyData["applicationRemark"].toString()) + row.createCell(4).setCellValue(dummyData["applicantName"].toString()) + row.createCell(5).setCellValue(dummyData["birthDate"].toString()) + row.createCell(6).setCellValue(dummyData["address"].toString()) + row.createCell(7).setCellValue(dummyData["applicantTel"].toString()) + row.createCell(8).setCellValue(dummyData["sex"].toString()) + row.createCell(9).setCellValue(dummyData["educationalStatus"].toString()) + row.createCell(10).setCellValue(dummyData["graduateDate"].toString()) + row.createCell(11).setCellValue(dummyData["schoolName"].toString()) + row.createCell(12).setCellValue(dummyData["classNumber"].toString()) + row.createCell(13).setCellValue(dummyData["parentName"].toString()) + row.createCell(14).setCellValue(dummyData["parentTel"].toString()) + + // 성적 더미 데이터 + val dummyGrades = listOf("A", "B", "A", "B", "A", "B", "A") + for (i in 15..21) { + row.createCell(i).setCellValue(dummyGrades[i - 15]) + } + for (i in 22..28) { + row.createCell(i).setCellValue(dummyGrades[i - 22]) + } + for (i in 29..35) { + row.createCell(i).setCellValue(dummyGrades[i - 29]) + } + for (i in 36..42) { + row.createCell(i).setCellValue(dummyGrades[i - 36]) + } + + // 점수 더미 데이터 + row.createCell(43).setCellValue("180.0") + row.createCell(44).setCellValue("170.0") + row.createCell(45).setCellValue("165.0") + row.createCell(46).setCellValue("170.5") + row.createCell(47).setCellValue("30.0") + row.createCell(48).setCellValue("15.0") + row.createCell(49).setCellValue("0") + row.createCell(50).setCellValue("0") + row.createCell(51).setCellValue("0") + row.createCell(52).setCellValue("0") + row.createCell(53).setCellValue("20.0") + row.createCell(54).setCellValue("O") + row.createCell(55).setCellValue("X") + row.createCell(56).setCellValue("5.0") + row.createCell(57).setCellValue("210.5") + row.createCell(58).setCellValue("200.0") + row.createCell(59).setCellValue(dummyData["examCode"].toString()) + } +} diff --git a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/excel/model/ApplicantCode.kt b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/excel/model/ApplicantCode.kt new file mode 100644 index 00000000..4943c4f3 --- /dev/null +++ b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/excel/model/ApplicantCode.kt @@ -0,0 +1,26 @@ +package hs.kr.entrydsm.application.global.excel.model + +import org.apache.poi.ss.usermodel.Row +import org.apache.poi.ss.usermodel.Sheet +import org.apache.poi.ss.usermodel.Workbook +import org.apache.poi.xssf.usermodel.XSSFWorkbook + +class ApplicantCode { + private val workbook: Workbook = XSSFWorkbook() + private val sheet: Sheet = workbook.createSheet("지원자 목록") + + fun getWorkbook(): Workbook { + return workbook + } + + fun getSheet(): Sheet { + return sheet + } + + fun format() { + val row: Row = sheet.createRow(0) + row.createCell(0).setCellValue("수험번호") + row.createCell(1).setCellValue("접수번호") + row.createCell(2).setCellValue("성명") + } +} diff --git a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/excel/model/ApplicationInfo.kt b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/excel/model/ApplicationInfo.kt new file mode 100644 index 00000000..dae722ec --- /dev/null +++ b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/excel/model/ApplicationInfo.kt @@ -0,0 +1,94 @@ +package hs.kr.entrydsm.application.global.excel.model + +import org.apache.poi.ss.usermodel.Row +import org.apache.poi.ss.usermodel.Sheet +import org.apache.poi.ss.usermodel.Workbook +import org.apache.poi.xssf.usermodel.XSSFWorkbook + +class ApplicationInfo { + private val workbook: Workbook = XSSFWorkbook() + private val sheet: Sheet = workbook.createSheet("전형자료") + + fun getWorkbook(): Workbook { + return workbook + } + + fun getSheet(): Sheet { + return sheet + } + + fun format() { + val row: Row = sheet.createRow(0) + row.createCell(0).setCellValue("접수번호") + row.createCell(1).setCellValue("전형유형") + row.createCell(2).setCellValue("지역") + row.createCell(3).setCellValue("추가유형") + row.createCell(4).setCellValue("성명") + row.createCell(5).setCellValue("생년월일") + row.createCell(6).setCellValue("주소") + row.createCell(7).setCellValue("전화번호") + row.createCell(8).setCellValue("성별") + row.createCell(9).setCellValue("학력구분") + row.createCell(10).setCellValue("졸업년도") + row.createCell(11).setCellValue("출신학교") + row.createCell(12).setCellValue("반") + row.createCell(13).setCellValue("보호자 성명") + row.createCell(14).setCellValue("보호자 전화번호") + + // 3학년 2학기 성적 + row.createCell(15).setCellValue("국어 3학년 2학기") + row.createCell(16).setCellValue("사회 3학년 2학기") + row.createCell(17).setCellValue("역사 3학년 2학기") + row.createCell(18).setCellValue("수학 3학년 2학기") + row.createCell(19).setCellValue("과학 3학년 2학기") + row.createCell(20).setCellValue("기술가정 3학년 2학기") + row.createCell(21).setCellValue("영어 3학년 2학기") + + // 3학년 1학기 성적 + row.createCell(22).setCellValue("국어 3학년 1학기") + row.createCell(23).setCellValue("사회 3학년 1학기") + row.createCell(24).setCellValue("역사 3학년 1학기") + row.createCell(25).setCellValue("수학 3학년 1학기") + row.createCell(26).setCellValue("과학 3학년 1학기") + row.createCell(27).setCellValue("기술가정 3학년 1학기") + row.createCell(28).setCellValue("영어 3학년 1학기") + + // 직전 학기 성적 + row.createCell(29).setCellValue("국어 직전 학기") + row.createCell(30).setCellValue("사회 직전 학기") + row.createCell(31).setCellValue("역사 직전 학기") + row.createCell(32).setCellValue("수학 직전 학기") + row.createCell(33).setCellValue("과학 직전 학기") + row.createCell(34).setCellValue("기술가정 직전 학기") + row.createCell(35).setCellValue("영어 직전 학기") + + // 직전전 학기 성적 + row.createCell(36).setCellValue("국어 직전전 학기") + row.createCell(37).setCellValue("사회 직전전 학기") + row.createCell(38).setCellValue("역사 직전전 학기") + row.createCell(39).setCellValue("수학 직전전 학기") + row.createCell(40).setCellValue("과학 직전전 학기") + row.createCell(41).setCellValue("기술가정 직전전 학기") + row.createCell(42).setCellValue("영어 직전전 학기") + + // 종합 성적 및 기타 정보 + row.createCell(43).setCellValue("3학년 성적 총합") + row.createCell(44).setCellValue("직전 학기 성적 총합") + row.createCell(45).setCellValue("직전전 학기 성적 총합") + row.createCell(46).setCellValue("교과성적환산점수") + row.createCell(47).setCellValue("봉사시간") + row.createCell(48).setCellValue("봉사점수") + row.createCell(49).setCellValue("결석") + row.createCell(50).setCellValue("지각") + row.createCell(51).setCellValue("조퇴") + row.createCell(52).setCellValue("결과") + row.createCell(53).setCellValue("출석점수") + row.createCell(54).setCellValue("대회") + row.createCell(55).setCellValue("자격증") + row.createCell(56).setCellValue("가산점") + row.createCell(57).setCellValue("1차전형 총점") + row.createCell(58).setCellValue("일반전형(170)") + row.createCell(59).setCellValue("수험번호") + } + +} diff --git a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/excel/presentation/ExcelTestController.kt b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/excel/presentation/ExcelTestController.kt new file mode 100644 index 00000000..c199a9ad --- /dev/null +++ b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/excel/presentation/ExcelTestController.kt @@ -0,0 +1,40 @@ +package hs.kr.entrydsm.application.global.excel.presentation + +import hs.kr.entrydsm.application.global.excel.generator.PrintAdmissionTicketGenerator +import hs.kr.entrydsm.application.global.excel.generator.PrintApplicantCodesGenerator +import hs.kr.entrydsm.application.global.excel.generator.PrintApplicationCheckListGenerator +import hs.kr.entrydsm.application.global.excel.generator.PrintApplicationInfoGenerator +import jakarta.servlet.http.HttpServletResponse +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController + +@RestController +@RequestMapping("/api/excel/test") +class ExcelTestController( + private val printApplicantCodesGenerator: PrintApplicantCodesGenerator, + private val printApplicationInfoGenerator: PrintApplicationInfoGenerator, + private val printAdmissionTicketGenerator: PrintAdmissionTicketGenerator, + private val printApplicationCheckListGenerator: PrintApplicationCheckListGenerator +) { + + @GetMapping("/applicant-codes") + fun downloadApplicantCodes(response: HttpServletResponse) { + printApplicantCodesGenerator.execute(response) + } + + @GetMapping("/application-info") + fun downloadApplicationInfo(response: HttpServletResponse) { + printApplicationInfoGenerator.execute(response) + } + + @GetMapping("/admission-ticket") + fun downloadAdmissionTicket(response: HttpServletResponse) { + printAdmissionTicketGenerator.execute(response) + } + + @GetMapping("/check-list") + fun downloadCheckList(response: HttpServletResponse) { + printApplicationCheckListGenerator.printApplicationCheckList(response) + } +} \ No newline at end of file diff --git a/casper-application-infrastructure/src/main/resources/excel/excel-form.xlsx b/casper-application-infrastructure/src/main/resources/excel/excel-form.xlsx new file mode 100644 index 00000000..7950d9b3 Binary files /dev/null and b/casper-application-infrastructure/src/main/resources/excel/excel-form.xlsx differ diff --git a/casper-application-infrastructure/src/main/resources/fonts/DejaVuSans.ttf b/casper-application-infrastructure/src/main/resources/fonts/DejaVuSans.ttf new file mode 100644 index 00000000..e5f7eecc Binary files /dev/null and b/casper-application-infrastructure/src/main/resources/fonts/DejaVuSans.ttf differ diff --git a/casper-application-infrastructure/src/main/resources/fonts/KoPubWorld Dotum Bold.ttf b/casper-application-infrastructure/src/main/resources/fonts/KoPubWorld Dotum Bold.ttf new file mode 100644 index 00000000..d07d5c89 Binary files /dev/null and b/casper-application-infrastructure/src/main/resources/fonts/KoPubWorld Dotum Bold.ttf differ diff --git a/casper-application-infrastructure/src/main/resources/fonts/KoPubWorld Dotum Light.ttf b/casper-application-infrastructure/src/main/resources/fonts/KoPubWorld Dotum Light.ttf new file mode 100644 index 00000000..12faec40 Binary files /dev/null and b/casper-application-infrastructure/src/main/resources/fonts/KoPubWorld Dotum Light.ttf differ diff --git a/casper-application-infrastructure/src/main/resources/fonts/KoPubWorld Dotum Medium.ttf b/casper-application-infrastructure/src/main/resources/fonts/KoPubWorld Dotum Medium.ttf new file mode 100644 index 00000000..0a7dff5d Binary files /dev/null and b/casper-application-infrastructure/src/main/resources/fonts/KoPubWorld Dotum Medium.ttf differ diff --git a/casper-application-infrastructure/src/main/resources/templates/admin_introduction.html b/casper-application-infrastructure/src/main/resources/templates/admin_introduction.html new file mode 100644 index 00000000..2317b3c1 --- /dev/null +++ b/casper-application-infrastructure/src/main/resources/templates/admin_introduction.html @@ -0,0 +1,90 @@ + + + + + <!DOCTYPE HTML> + <html xmlns:th="http://www.thymeleaf.org"> + <head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>application.html + + + +
+

+ + 자기소개서 및 학업계획서 + + +

+
+ + + + + + + + + + + + + + + + + + +
인적
사항
성명 + + (서명 또는 인) + + 접 수 번 호 + +
연락처출신 중학교
주소
+ + + + + + + +
자기소개서 내용은 특별한 형식이 없으며 개인의 특성 및 성장 과정, 취미·특기, 학교 생활, 가족 + 안에서의 역할, 남들보다 뛰어나다고 생각하는 자신의 장점(특성 혹은 능력)과 보완·발전시켜야 할 단점에 대하여 기술하십시오. +
+

+ + + + + + + +
학업계획서는 자신이 본교를 선택하게 된 구체적인 사유(지원 동기)와 고등학생이 된 후 이루고자 하는
목표를 + 달성하기 위한 학업계획을 상세하게 기술하십시오. +
+

+ + + + + + + + \ No newline at end of file diff --git a/casper-application-infrastructure/src/main/resources/templates/application_for_admission.html b/casper-application-infrastructure/src/main/resources/templates/application_for_admission.html new file mode 100644 index 00000000..3a3d6efb --- /dev/null +++ b/casper-application-infrastructure/src/main/resources/templates/application_for_admission.html @@ -0,0 +1,425 @@ + + + + + + + +
+
+ + 2024학년도 대덕소프트웨어마이스터고등학교 입학원서 + +
+
+ + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
접수번호학교코드수험번호*기재하지 않음
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
지원자성명전화번호 + # +
생년월일출신지역
성별출신학교
졸업구분
주소
+ + + + + + + + + + +
보호자성명관계휴대전화
+
+ + + + + + + + + +
지역전형유형특기사항
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ 교과성적(성취도) + + 출결 및 봉사활동 +
교과3학년 2학기3학년 1학기직전학기직전전학기미인정결석 + 일 +
국어미인정지각 + 회 +
사회미인정조퇴 + 회 +
역사미인정결과 + 회 +
수학봉사활동 시간 + 시간 +
과학가산점
DSM알고리즘대회
영어정보처리기능사
+
+ + + + + +
+

+ 본인은 귀 고등학교에 입학하고자 소정의 서류를 갖추어 + 지원합니다. +

+

+ 월 + 일 +

+
+
+ 지원자: + + + + + (서명 또는 인) + +
+
+ 보호자: + + + + + (서명 또는 인) + +
+
+

+ 대덕소프트웨어마이스터고등학교장 귀하 +

+
+

+ 보훈번호 : () +

+

+ 위 학생은 국가유공자자녀임을 확인함. +

+

+ 월 + 일 +

+ 교사: + + (서명 또는 인) +
+
+ + +
+

+ 추 천 서 +

+

+ 본 입학원서의 내용은 사실과 다름이 없으며, 위 학생은 귀교에 입학 + 적격자로 인정되므로 추천합니다. +

+

+ 월 + 일 +

+

+ 장 (직인) +

+

+ 대덕소프트웨어마이스터고등학교장 귀하 +

+
+
+ + + + + + + +
원서작성자 + 교사: + + (서명 또는 인) + 연락처
+
+ + diff --git a/casper-application-infrastructure/src/main/resources/templates/introduction.html b/casper-application-infrastructure/src/main/resources/templates/introduction.html new file mode 100644 index 00000000..ea165934 --- /dev/null +++ b/casper-application-infrastructure/src/main/resources/templates/introduction.html @@ -0,0 +1,80 @@ + + + + + + application.html + + + +
+

+ + 자기소개서 및 학업계획서 + +

+
+ + + + + + + + + + + + + + + + + + +
인적
사항
성명 + + (서명 또는 인) + + 접 수 번 호 + +
연락처출신 중학교
주소
+ + + + + + + +
자기소개서 내용은 특별한 형식이 없으며 개인의 특성 및 성장 과정, 취미·특기, 학교 생활, 가족 + 안에서의 역할, 남들보다 뛰어나다고 생각하는 자신의 장점(특성 혹은 능력)과 보완·발전시켜야 할 단점에 대하여 기술하십시오. +
+

+ + + + + + + +
학업계획서는 자신이 본교를 선택하게 된 구체적인 사유(지원 동기)와 고등학생이 된 후 이루고자 하는
목표를 + 달성하기 위한 학업계획을 상세하게 기술하십시오. +
+

+ + + \ No newline at end of file diff --git a/casper-application-infrastructure/src/main/resources/templates/nonsmoking.html b/casper-application-infrastructure/src/main/resources/templates/nonsmoking.html new file mode 100644 index 00000000..bc295fc0 --- /dev/null +++ b/casper-application-infrastructure/src/main/resources/templates/nonsmoking.html @@ -0,0 +1,305 @@ + + + + + + + +
금 연 동 의 서
+
+ + + + + + + + + + + + + + + + + + + + + +
인적
사항
성명접 수 번 호
연락처출신 중학교
주소
+
+
+
+

하나, 나 자신의 건강을 위해서 흡연하지 않겠습니다.

+

하나, 흡연의 유혹에 절대로 흔들리지 않겠습니다.

+

+ 하나, 흡연하는 친구가 있으면 충고하여 금연할 수 있도록 + 돕겠습니다. +

+
+

+ 나 ()은(는) 장차 소프트웨어 분야를 선도할 전문가로 성장하기 위하여 + 흡연하지 않겠습니다. +

+

+ 보호자는 지원자가 금연하는 데 용기와 도움을 줄 것을 약속합니다. +

+
+
+

□ 개인정보 수집·이용 동의

+ + + + + + + + + + + +
항 목수집목적보유기간
+

학생(성명, 연락처,

+

출신 중학교, 주소)

+
금연동의서3년
+
+

+ ※ 개인정보 수집·이용에 대한 동의를 거부할 권리가 있습니다. +

+

+   그러나 동의를 거부할 경우 최종 입학에 제한을 받을 수 + 있습니다 +

+
+ + + + + +
개인정보 수집·이용 동의 + □ 예 +                + □ 아니요 +
+
+
+
+

+ + 2024년        월 +        일 +

+
+ + + + + + + + + + + +
지원자 성명(서명 또는 인)
보호자 성명(서명 또는 인)
+
+ + \ No newline at end of file diff --git a/casper-application-infrastructure/src/main/resources/templates/privacy_agreement.html b/casper-application-infrastructure/src/main/resources/templates/privacy_agreement.html new file mode 100644 index 00000000..ab02bba8 --- /dev/null +++ b/casper-application-infrastructure/src/main/resources/templates/privacy_agreement.html @@ -0,0 +1,134 @@ + + + + + + Document + + + +

개인정보 수집 및 이용 동의서

+
+
+    본 입학원서에 기재된 지원자의 개인정보는 신입생 + 입학관리업무의 원활한 수행을 위하여 개인정보의 + 수집.유출.오용.남용으로부터 사생활의 비밀 등을 보호하도록 한 + 개인정보보호법 규정에 따라 다음과 같이 수집.이용.제공됩니다. + ​ +

+
+ 1. (개인정보 처리의 법령상 근거) 본 입학원서에 기재된 개인정보의 + 처리업무는 초.중등교육법 제47조 및 동법 시행령 제81조, 제82조, 제84조, + 제98조 및 본교의 입학전형 실시계획 등에 근거하고 있습니다.

+ 2. (정보주체의 권리) 지원자는 자신이 제공한 개인정보에 대하여 개인정보 + 보호법 제4조 및 제35조부터 제38조까지에 따라 + 열람.처리.정지.정정.삭제.파기 등을 요구할 수 있으며, 개인정보 보호법을 + 위반한 행위로 인한 손해 발생 시에는 개인정보 보호법 제39조에 따라 + 손해배상을 청구할 수 있습니다.

+ 3. (개인정보 수집항목) 입학관리 업무의 원활한 수행을 위하여 + 수집하는 개인정보는 성명, 생년월일, 증명사진, 주소, 전화번호, 학력, + 출결사항, 교과성적 + 등입니다.

+ 4. (개인정보의 수집.이용 목적) 수집한 지원자의 개인정보는 + 원서접수, 지원자격.지원결격 사유 확인, 지원자 본인확인, 성적산출, + 합격자 명부 관리, 합격증명서 발급, 성적 통지, 통계자료 산출 + 등 입학관리 업무를 위한 정보로 이용됩니다.

+ 5. (개인정보 제공) 수집한 개인정보는 지원자격.지원결격 사유 조회 및 + 교과성적 확인 등을 위하여 지원자가 졸업한 중학교 등 관련된 기관에 + 제공될 수 있습니다.

+ 6. (개인정보의 보유기간 및 이용기간) 수집한 개인정보는 + 입학관리 업무를 계속하는 동안 보유.이용할 수 있으며, + 입학관리 업무 완료 후 5년간 보관의 목적으로만 관리하고 이후 + 에는 바로 폐기합니다.

+ 7. (개인정보의 수집.이용.제공에 대한 동의 거부) 지원자는 개인정보의 + 수집.이용.제공에 대한 동의를 거부할 수 있으며, 동의를 거부할 경우 + 지원결격 사유 조회 등 입학관리 업무를 수행할 수 없으므로 원서를 접수할 + 수 없습니다. +
+
+
+
+
+ + + + + +
+ 개인정보 수집·이용 + 동의                                                                 + + □ 예 +                + □ 아니요 +
+
+
+
+ 2024년 +         월 +         일 +
+ + + + + + + + + + + +
지원자 성명김정현(서명 또는 인)
보호자 성명(서명 또는 인)
+
+대덕소프트웨어마이스터고등학교장 귀하 + + \ No newline at end of file diff --git a/casper-application-infrastructure/src/main/resources/templates/recommendation.html b/casper-application-infrastructure/src/main/resources/templates/recommendation.html new file mode 100644 index 00000000..2405e1e3 --- /dev/null +++ b/casper-application-infrastructure/src/main/resources/templates/recommendation.html @@ -0,0 +1,137 @@ + + + + + + Document + + + +

학 교 장 추 천 서

+ + + + + +
접수번호 :
+


+
+
+
+
+

+
+
+
+ 3학년 +
+ 반 +
+
+
+
+ 성명: +


+
+
+
+

+ 특별전형 추천 분야 +

+ + + + + + + + + + + + + + + + + + +
+ 구분 + + 마이스터 인재 전형 + + 사회통합 전형 +
대전
전국
+
+

+ 위 학생을 2021학년도 대덕소프트웨어마이스터고등학교 특별전형 +

+

대상자로 추천합니다.

+
+
+ 2020년        월        일 +
+
+

작성자 담임: +

+ (서명 또는 인)

+
+
+ + (직인) +
+
+

대덕소프트웨어마이스터고등학교장 귀하

+
+
+
+ + \ No newline at end of file diff --git a/casper-application-infrastructure/src/main/resources/templates/smoking_examine.html b/casper-application-infrastructure/src/main/resources/templates/smoking_examine.html new file mode 100644 index 00000000..4cc60d6f --- /dev/null +++ b/casper-application-infrastructure/src/main/resources/templates/smoking_examine.html @@ -0,0 +1,291 @@ + + + + + + + +
흡 연 검 사 동 의 서
+
+ + + + + + + + + + + + + + + + + + + + + +
인적
사항
성명접 수 번 호
연락처출신 중학교
주소
+
+

+   대덕소프트웨어마이스터고등학교에서는 안전한 기숙사 생활과 + 학생들의 건강을 위해 신입생 입학 전형자를 대상으로 소변 니코틴 + 검사 측정을 합니다. 이에 흡연검사에 + 학생과 보호자의 동의를 구합니다.
+   검사 시 약간의 불편이 있더라도 흡연자 지도를 위해서는 + 필요한 사안이므로 양해와 적극적인 협조를 부탁드립니다. +
+   학생들의 건강을 위해 흡연을 규제하고 예방하도록 학생 지도에 + 최선을 다하겠습니다. 학부모님께서도 귀 자녀가 흡연의 위험성을 + 인지하고 흡연하지 않도록 지도와 협조를 부탁드립니다. +

+
+
+
+
+
+

□ 개인정보 수집 · 이용 동의

+ + + + + + + + + + + +
항 목수집목적보유기간
+

학생(성명, 연락처,

+

출신 중학교, 주소)

+
흡연검사동의3년
+
+

+ ※ 개인정보 수집 · 이용에 대한 동의를 거부할 권리가 있습니다.그러나 + 동의를 거부할 경우 최종 입학에 제한을 받을 수 있습니다. +

+
+ + + + + +
+     개인정보 수집 · 이용 동의 + + □ 예 +                + □ 아니요 +
+
+
+
+

+ + 2024년        월 +        일 +

+
+ + + + + + + + + + + +
지원자 성명:(서명 또는 인)
보호자 성명:(서명 또는 인)
+
+ + \ No newline at end of file