diff --git a/systems/application/application-adapter-in/src/test/kotlin/hs/kr/entrydsm/application/adapterin/web/ApplicationControllerTest.kt b/systems/application/application-adapter-in/src/test/kotlin/hs/kr/entrydsm/application/adapterin/web/ApplicationControllerTest.kt index eba273d7..33df532c 100644 --- a/systems/application/application-adapter-in/src/test/kotlin/hs/kr/entrydsm/application/adapterin/web/ApplicationControllerTest.kt +++ b/systems/application/application-adapter-in/src/test/kotlin/hs/kr/entrydsm/application/adapterin/web/ApplicationControllerTest.kt @@ -37,9 +37,9 @@ class ApplicationControllerTest { val response = controller.getLanding(10L) assertEquals("홍길동", response.data?.applicantName) - assertEquals(APPLICATION_START_AT, response.data?.schedule?.applicationPeriod?.startAt) - assertEquals(APPLICATION_END_AT, response.data?.schedule?.applicationPeriod?.endAt) - assertEquals(RESULT_ANNOUNCED_AT, response.data?.schedule?.resultAnnouncedAt) + assertEquals(applicationStartAt, response.data?.schedule?.applicationPeriod?.startAt) + assertEquals(applicationEndAt, response.data?.schedule?.applicationPeriod?.endAt) + assertEquals(resultAnnouncedAt, response.data?.schedule?.resultAnnouncedAt) } private class FakeApplicationPort : ApplicationPort { @@ -61,15 +61,15 @@ class ApplicationControllerTest { } private companion object { - val APPLICATION_START_AT: LocalDateTime = LocalDateTime.parse("2026-04-01T09:00:00") - val APPLICATION_END_AT: LocalDateTime = LocalDateTime.parse("2026-04-30T17:00:00") - val RESULT_ANNOUNCED_AT: LocalDateTime = LocalDateTime.parse("2026-05-15T10:00:00") + val applicationStartAt: LocalDateTime = LocalDateTime.parse("2026-10-19T09:00:00") + val applicationEndAt: LocalDateTime = LocalDateTime.parse("2026-10-23T17:00:00") + val resultAnnouncedAt: LocalDateTime = LocalDateTime.parse("2026-10-30T10:00:00") fun scheduleProperties(): LandingScheduleProperties = LandingScheduleProperties( - applicationStartAt = APPLICATION_START_AT, - applicationEndAt = APPLICATION_END_AT, - resultAnnouncedAt = RESULT_ANNOUNCED_AT, + applicationStartAt = applicationStartAt, + applicationEndAt = applicationEndAt, + resultAnnouncedAt = resultAnnouncedAt, ) } } diff --git a/systems/application/application-application/BUILD.bazel b/systems/application/application-application/BUILD.bazel index caf1b688..9f5c38f5 100644 --- a/systems/application/application-application/BUILD.bazel +++ b/systems/application/application-application/BUILD.bazel @@ -17,5 +17,5 @@ kt_jvm_test( javac_opts = "//:javac_options", kotlinc_opts = "//:kotlinc_options", test_class = "hs.kr.entrydsm.application.application.ApplicationApplicationModuleTest", - deps = MODULE_DEPS + TEST_DEPS, + deps = [":main"] + MODULE_DEPS + TEST_DEPS, ) diff --git a/systems/application/application-application/src/main/kotlin/hs/kr/entrydsm/application/application/service/ApplicationCommandService.kt b/systems/application/application-application/src/main/kotlin/hs/kr/entrydsm/application/application/service/ApplicationCommandService.kt index 4ebe3b8d..c482f33d 100644 --- a/systems/application/application-application/src/main/kotlin/hs/kr/entrydsm/application/application/service/ApplicationCommandService.kt +++ b/systems/application/application-application/src/main/kotlin/hs/kr/entrydsm/application/application/service/ApplicationCommandService.kt @@ -129,6 +129,10 @@ class ApplicationCommandService( applicant.region = region applicant.graduationType = graduationType applicant.graduationDate = graduationDate + if (graduationType == GraduationType.GED) { + applicant.middleSchoolInfo = null + applicant.academicRecord?.subjectGrades?.clear() + } saveTouched(applicant) } diff --git a/systems/application/application-application/src/test/kotlin/hs/kr/entrydsm/TestMain.kt b/systems/application/application-application/src/test/kotlin/hs/kr/entrydsm/TestMain.kt index f2a878bd..f6c73050 100644 --- a/systems/application/application-application/src/test/kotlin/hs/kr/entrydsm/TestMain.kt +++ b/systems/application/application-application/src/test/kotlin/hs/kr/entrydsm/TestMain.kt @@ -1,11 +1,13 @@ package hs.kr.entrydsm.application.application -import org.junit.Assert.assertTrue -import org.junit.Test +import hs.kr.entrydsm.application.application.service.ApplicationCommandServiceTest +import hs.kr.entrydsm.application.application.service.EvaluationCommandServiceTest +import org.junit.runner.RunWith +import org.junit.runners.Suite -class ApplicationApplicationModuleTest { - @Test - fun moduleLoads() { - assertTrue(true) - } -} +@RunWith(Suite::class) +@Suite.SuiteClasses( + ApplicationCommandServiceTest::class, + EvaluationCommandServiceTest::class, +) +class ApplicationApplicationModuleTest diff --git a/systems/application/application-application/src/test/kotlin/hs/kr/entrydsm/application/application/service/ApplicationCommandServiceTest.kt b/systems/application/application-application/src/test/kotlin/hs/kr/entrydsm/application/application/service/ApplicationCommandServiceTest.kt new file mode 100644 index 00000000..9a703818 --- /dev/null +++ b/systems/application/application-application/src/test/kotlin/hs/kr/entrydsm/application/application/service/ApplicationCommandServiceTest.kt @@ -0,0 +1,84 @@ +package hs.kr.entrydsm.application.application.service + +import hs.kr.entrydsm.application.application.port.out.ApplicantRepository +import hs.kr.entrydsm.application.domain.enum.AdmissionType +import hs.kr.entrydsm.application.domain.enum.GraduationType +import hs.kr.entrydsm.application.domain.enum.Region +import hs.kr.entrydsm.application.domain.enum.SchoolSemester +import hs.kr.entrydsm.application.domain.enum.SubjectGrade +import hs.kr.entrydsm.application.domain.model.AcademicRecord +import hs.kr.entrydsm.application.domain.model.Applicant +import hs.kr.entrydsm.application.domain.model.MiddleSchoolInfo +import hs.kr.entrydsm.application.domain.model.SubjectGrades +import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue +import org.junit.Test + +class ApplicationCommandServiceTest { + @Test + fun updateTypeClearsMiddleSchoolInfoAndSubjectGradesWhenChangedToGed() { + val repository = FakeApplicantRepository( + Applicant( + id = 1L, + accountId = 10L, + graduationType = GraduationType.PROSPECTIVE, + middleSchoolInfo = MiddleSchoolInfo( + schoolName = "대덕중학교", + studentNumber = "30101", + schoolPhone = "042-000-0000", + teacherName = "담임", + ), + academicRecord = AcademicRecord( + subjectGrades = linkedMapOf( + SchoolSemester.THIRD_GRADE_FIRST_SEMESTER to all(SubjectGrade.A), + ), + ), + ), + ) + val service = ApplicationCommandService(repository) + + service.updateType( + applicantId = 1L, + userId = 10L, + admissionType = AdmissionType.REGULAR, + region = Region.DAEJEON, + graduationType = GraduationType.GED, + graduationDate = null, + ) + + val savedApplicant = requireNotNull(repository.savedApplicant) + assertNull(savedApplicant.middleSchoolInfo) + assertTrue(savedApplicant.academicRecord?.subjectGrades?.isEmpty() == true) + } + + private class FakeApplicantRepository( + private var applicant: Applicant, + ) : ApplicantRepository { + var savedApplicant: Applicant? = null + + override fun save(applicant: Applicant): Applicant { + savedApplicant = applicant + this.applicant = applicant + return applicant + } + + override fun findById(id: Long): Applicant? = + applicant.takeIf { it.id == id } + + override fun findByAccountId(accountId: Long): Applicant? = + applicant.takeIf { it.accountId == accountId } + } + + private companion object { + fun all(grade: SubjectGrade): SubjectGrades = + SubjectGrades( + koreanGrade = grade, + mathGrade = grade, + englishGrade = grade, + scienceGrade = grade, + societyGrade = grade, + technologyGrade = grade, + historyGrade = grade, + ) + } +} diff --git a/systems/application/application-application/src/test/kotlin/hs/kr/entrydsm/application/application/service/EvaluationCommandServiceTest.kt b/systems/application/application-application/src/test/kotlin/hs/kr/entrydsm/application/application/service/EvaluationCommandServiceTest.kt new file mode 100644 index 00000000..7c31614b --- /dev/null +++ b/systems/application/application-application/src/test/kotlin/hs/kr/entrydsm/application/application/service/EvaluationCommandServiceTest.kt @@ -0,0 +1,75 @@ +package hs.kr.entrydsm.application.application.service + +import hs.kr.entrydsm.application.application.port.out.ApplicantRepository +import hs.kr.entrydsm.application.domain.enum.AdmissionType +import hs.kr.entrydsm.application.domain.enum.GraduationType +import hs.kr.entrydsm.application.domain.enum.SchoolSemester +import hs.kr.entrydsm.application.domain.enum.SubjectGrade +import hs.kr.entrydsm.application.domain.model.AcademicRecord +import hs.kr.entrydsm.application.domain.model.Applicant +import hs.kr.entrydsm.application.domain.model.SubjectGrades +import hs.kr.entrydsm.application.domain.service.ScoreCalculator +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull +import org.junit.Test + +class EvaluationCommandServiceTest { + @Test + fun calculateResultSavesScoreForApplicantsAdmissionType() { + val repository = FakeApplicantRepository( + Applicant( + id = 1L, + accountId = 10L, + admissionType = AdmissionType.REGULAR, + graduationType = GraduationType.PROSPECTIVE, + academicRecord = AcademicRecord( + volunteerTime = 15, + isDsmAlgorithmAwarded = true, + subjectGrades = linkedMapOf( + SchoolSemester.THIRD_GRADE_FIRST_SEMESTER to all(SubjectGrade.A), + SchoolSemester.SECOND_GRADE_SECOND_SEMESTER to all(SubjectGrade.A), + SchoolSemester.SECOND_GRADE_FIRST_SEMESTER to all(SubjectGrade.A), + ), + ), + ), + ) + val service = EvaluationCommandService(repository, ScoreCalculator()) + + service.calculateResult(userId = 10L) + + val savedApplicant = requireNotNull(repository.savedApplicant) + assertEquals(173.0, savedApplicant.totalScore ?: 0.0, 0.0) + assertNotNull(savedApplicant.totalScoreUpdatedAt) + } + + private class FakeApplicantRepository( + private var applicant: Applicant, + ) : ApplicantRepository { + var savedApplicant: Applicant? = null + + override fun save(applicant: Applicant): Applicant { + savedApplicant = applicant + this.applicant = applicant + return applicant + } + + override fun findById(id: Long): Applicant? = + applicant.takeIf { it.id == id } + + override fun findByAccountId(accountId: Long): Applicant? = + applicant.takeIf { it.accountId == accountId } + } + + private companion object { + fun all(grade: SubjectGrade): SubjectGrades = + SubjectGrades( + koreanGrade = grade, + mathGrade = grade, + englishGrade = grade, + scienceGrade = grade, + societyGrade = grade, + technologyGrade = grade, + historyGrade = grade, + ) + } +} diff --git a/systems/application/application-bootstrap/BUILD.bazel b/systems/application/application-bootstrap/BUILD.bazel index 42ef8691..bde71cd7 100644 --- a/systems/application/application-bootstrap/BUILD.bazel +++ b/systems/application/application-bootstrap/BUILD.bazel @@ -8,7 +8,7 @@ kt_jvm_binary( srcs = glob(["src/main/kotlin/**/*.kt"]), javac_opts = "//:javac_options", kotlinc_opts = "//:kotlinc_options", - main_class = "hs.kr.entrydsm.application.ApplicationBootstrapApplicationKt", + main_class = "hs.kr.entrydsm.application.ExampleApplicationKt", plugins = ["//:spring_allopen"], resources = glob(["src/main/resources/**"]), deps = MODULE_DEPS + [ diff --git a/systems/application/application-bootstrap/deps.bzl b/systems/application/application-bootstrap/deps.bzl index bfdb5aef..4d5535f1 100644 --- a/systems/application/application-bootstrap/deps.bzl +++ b/systems/application/application-bootstrap/deps.bzl @@ -1,11 +1,13 @@ SPRING_DEPS = [ "@maven//:org_springframework_boot_spring_boot_starter_web", "@maven//:org_springframework_boot_spring_boot_starter_actuator", + "@maven//:org_springframework_boot_spring_boot_starter_data_jpa", ] KOTLIN_DEPS = [ "@maven//:org_jetbrains_kotlin_kotlin_reflect", "@maven//:com_fasterxml_jackson_module_jackson_module_kotlin", + "@maven//:com_mysql_mysql_connector_j", ] TEST_DEPS = [ diff --git a/systems/application/application-bootstrap/src/main/kotlin/hs/kr/entrydsm/application/config/ApplicationUseCaseConfig.kt b/systems/application/application-bootstrap/src/main/kotlin/hs/kr/entrydsm/application/config/ApplicationUseCaseConfig.kt new file mode 100644 index 00000000..fbe8f586 --- /dev/null +++ b/systems/application/application-bootstrap/src/main/kotlin/hs/kr/entrydsm/application/config/ApplicationUseCaseConfig.kt @@ -0,0 +1,27 @@ +package hs.kr.entrydsm.application.config + +import hs.kr.entrydsm.application.application.port.`in`.ApplicationPort +import hs.kr.entrydsm.application.application.port.`in`.EvaluationPort +import hs.kr.entrydsm.application.application.port.out.ApplicantRepository +import hs.kr.entrydsm.application.application.service.ApplicationCommandService +import hs.kr.entrydsm.application.application.service.EvaluationCommandService +import hs.kr.entrydsm.application.domain.service.ScoreCalculator +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration + +@Configuration(proxyBeanMethods = false) +class ApplicationUseCaseConfig { + @Bean + fun scoreCalculator(): ScoreCalculator = ScoreCalculator() + + @Bean + fun applicationService( + applicantRepository: ApplicantRepository, + ): ApplicationPort = ApplicationCommandService(applicantRepository) + + @Bean + fun evaluationService( + applicantRepository: ApplicantRepository, + scoreCalculator: ScoreCalculator, + ): EvaluationPort = EvaluationCommandService(applicantRepository, scoreCalculator) +} diff --git a/systems/application/application-bootstrap/src/main/resources/application-dev.yaml b/systems/application/application-bootstrap/src/main/resources/application-dev.yaml new file mode 100644 index 00000000..f6212f58 --- /dev/null +++ b/systems/application/application-bootstrap/src/main/resources/application-dev.yaml @@ -0,0 +1,24 @@ +spring: + config: + activate: + on-profile: dev + datasource: + url: ${DB_URL} + username: ${DB_USERNAME} + password: ${DB_PASSWORD} + driver-class-name: com.mysql.cj.jdbc.Driver + jpa: + hibernate: + ddl-auto: update + open-in-view: false + show-sql: false + properties: + hibernate: + dialect: org.hibernate.dialect.MySQLDialect + +entrydsm: + application: + schedule: + application-start-at: ${APPLICATION_START_AT} + application-end-at: ${APPLICATION_END_AT} + result-announced-at: ${RESULT_ANNOUNCED_AT} diff --git a/systems/application/application-bootstrap/src/main/resources/application-prod.yaml b/systems/application/application-bootstrap/src/main/resources/application-prod.yaml new file mode 100644 index 00000000..8dcb612b --- /dev/null +++ b/systems/application/application-bootstrap/src/main/resources/application-prod.yaml @@ -0,0 +1,24 @@ +spring: + config: + activate: + on-profile: prod + datasource: + url: ${DB_URL} + username: ${DB_USERNAME} + password: ${DB_PASSWORD} + driver-class-name: com.mysql.cj.jdbc.Driver + jpa: + hibernate: + ddl-auto: validate + open-in-view: false + show-sql: false + properties: + hibernate: + dialect: org.hibernate.dialect.MySQLDialect + +entrydsm: + application: + schedule: + application-start-at: ${APPLICATION_START_AT} + application-end-at: ${APPLICATION_END_AT} + result-announced-at: ${RESULT_ANNOUNCED_AT} diff --git a/systems/application/application-bootstrap/src/main/resources/application.yaml b/systems/application/application-bootstrap/src/main/resources/application.yaml index 03aced9e..3d701e28 100644 --- a/systems/application/application-bootstrap/src/main/resources/application.yaml +++ b/systems/application/application-bootstrap/src/main/resources/application.yaml @@ -2,11 +2,16 @@ spring: application: name: application profiles: - default: local + default: dev main: lazy-initialization: true + server: shutdown: graceful + +grpc: + port: ${GRPC_PORT:9090} + management: endpoints: web: @@ -16,6 +21,7 @@ management: health: probes: enabled: true + logging: level: root: INFO diff --git a/systems/application/application-domain/src/main/kotlin/hs/kr/entrydsm/application/domain/service/ScoreCalculator.kt b/systems/application/application-domain/src/main/kotlin/hs/kr/entrydsm/application/domain/service/ScoreCalculator.kt index d5966e6b..a23404bb 100644 --- a/systems/application/application-domain/src/main/kotlin/hs/kr/entrydsm/application/domain/service/ScoreCalculator.kt +++ b/systems/application/application-domain/src/main/kotlin/hs/kr/entrydsm/application/domain/service/ScoreCalculator.kt @@ -117,15 +117,16 @@ class ScoreCalculator { return EMPTY_SCORE } - val convertedAbsences = record.absentCount + floor( + val convertedAbsences = record.absentCount.toLong() + floor( ( - record.lateCount + - record.earlyLeaveCount + - record.classAbsenceCount + record.lateCount.toLong() + + record.earlyLeaveCount.toLong() + + record.classAbsenceCount.toLong() ) / ATTENDANCE_CONVERSION_UNIT.toDouble(), - ).toInt() + ).toLong() - return (ATTENDANCE_MAX_SCORE - convertedAbsences).coerceAtLeast(EMPTY_SCORE) + return (ATTENDANCE_MAX_SCORE - convertedAbsences) + .coerceIn(EMPTY_SCORE, ATTENDANCE_MAX_SCORE) } private fun calculateVolunteerScore( diff --git a/systems/application/application-domain/src/test/kotlin/hs/kr/entrydsm/application/domain/service/ScoreCalculatorTest.kt b/systems/application/application-domain/src/test/kotlin/hs/kr/entrydsm/application/domain/service/ScoreCalculatorTest.kt index bd883ba8..43c6ac44 100644 --- a/systems/application/application-domain/src/test/kotlin/hs/kr/entrydsm/application/domain/service/ScoreCalculatorTest.kt +++ b/systems/application/application-domain/src/test/kotlin/hs/kr/entrydsm/application/domain/service/ScoreCalculatorTest.kt @@ -104,6 +104,32 @@ class ScoreCalculatorTest { ) } + @Test + fun clampsAttendanceScoreWhenAttendanceCountsAreTooLarge() { + val applicant = Applicant( + id = 1L, + accountId = 1L, + graduationType = GraduationType.PROSPECTIVE, + academicRecord = AcademicRecord( + absentCount = Int.MAX_VALUE, + lateCount = Int.MAX_VALUE, + earlyLeaveCount = Int.MAX_VALUE, + classAbsenceCount = Int.MAX_VALUE, + subjectGrades = linkedMapOf( + SchoolSemester.THIRD_GRADE_FIRST_SEMESTER to all(SubjectGrade.A), + SchoolSemester.SECOND_GRADE_SECOND_SEMESTER to all(SubjectGrade.A), + SchoolSemester.SECOND_GRADE_FIRST_SEMESTER to all(SubjectGrade.A), + ), + ), + ) + + val result = calculator.calculate(applicant) + + assertEquals(140.0, result.getValue(AdmissionType.REGULAR), 0.0) + assertEquals(80.0, result.getValue(AdmissionType.SOCIAL), 0.0) + assertEquals(80.0, result.getValue(AdmissionType.MEISTER), 0.0) + } + @Test fun returnsZeroWhenAcademicRecordDoesNotExist() { val result = calculator.calculate(Applicant(id = 1L, accountId = 1L))