From d257b16e8f4ffa189339f1edb6c783ba0678ebb8 Mon Sep 17 00:00:00 2001 From: soo0711 Date: Wed, 18 Feb 2026 00:58:35 +0900 Subject: [PATCH 01/45] =?UTF-8?q?refactor:=20Attendacne=20domain=20java=20?= =?UTF-8?q?->=20kotlin=EC=9C=BC=EB=A1=9C=20=ED=8F=B4=EB=8D=94=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/weeth/domain/attendance/domain/entity/Attendance.kt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/main/{java/com/weeth/domain/attendance/domain/entity/Attendance.java => kotlin/com/weeth/domain/attendance/domain/entity/Attendance.kt} (100%) diff --git a/src/main/java/com/weeth/domain/attendance/domain/entity/Attendance.java b/src/main/kotlin/com/weeth/domain/attendance/domain/entity/Attendance.kt similarity index 100% rename from src/main/java/com/weeth/domain/attendance/domain/entity/Attendance.java rename to src/main/kotlin/com/weeth/domain/attendance/domain/entity/Attendance.kt From f26d5180bd62b4987e73fdf98d3090131b3b9bd8 Mon Sep 17 00:00:00 2001 From: soo0711 Date: Wed, 18 Feb 2026 01:07:04 +0900 Subject: [PATCH 02/45] =?UTF-8?q?build:=20Lombok=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EB=B0=8F=20Kotlin=20=ED=98=B8=ED=99=98=EC=84=B1=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 1 + lombok.config | 2 ++ 2 files changed, 3 insertions(+) create mode 100644 lombok.config diff --git a/build.gradle.kts b/build.gradle.kts index 6b528cbf..cc9b1410 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,6 +8,7 @@ plugins { kotlin("jvm") version "2.1.0" kotlin("plugin.spring") version "2.1.0" kotlin("plugin.jpa") version "2.1.0" + kotlin("plugin.lombok") version "2.1.0" id("org.jlleitschuh.gradle.ktlint") version "12.1.2" } diff --git a/lombok.config b/lombok.config new file mode 100644 index 00000000..df71bb6a --- /dev/null +++ b/lombok.config @@ -0,0 +1,2 @@ +config.stopBubbling = true +lombok.addLombokGeneratedAnnotation = true From c1c3cef8334110953114d311d71ecb175f696323 Mon Sep 17 00:00:00 2001 From: soo0711 Date: Wed, 18 Feb 2026 01:17:48 +0900 Subject: [PATCH 03/45] =?UTF-8?q?refactor:=20Attendacne=20entity=20kotlin?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=AC=B8=EB=B2=95=20=EB=B3=80=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../attendance/domain/entity/Attendance.kt | 108 ++++++++---------- 1 file changed, 49 insertions(+), 59 deletions(-) diff --git a/src/main/kotlin/com/weeth/domain/attendance/domain/entity/Attendance.kt b/src/main/kotlin/com/weeth/domain/attendance/domain/entity/Attendance.kt index 35197ad3..8e8934dc 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/domain/entity/Attendance.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/domain/entity/Attendance.kt @@ -1,62 +1,52 @@ -package com.weeth.domain.attendance.domain.entity; - -import jakarta.persistence.*; -import com.weeth.domain.attendance.domain.entity.enums.Status; -import com.weeth.domain.schedule.domain.entity.Meeting; -import com.weeth.domain.user.domain.entity.User; -import com.weeth.global.common.entity.BaseEntity; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; +package com.weeth.domain.attendance.domain.entity + +import com.weeth.domain.attendance.domain.entity.enums.Status +import com.weeth.domain.schedule.domain.entity.Meeting +import com.weeth.domain.user.domain.entity.User +import com.weeth.global.common.entity.BaseEntity +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.EnumType +import jakarta.persistence.Enumerated +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id +import jakarta.persistence.JoinColumn +import jakarta.persistence.ManyToOne +import jakarta.persistence.PrePersist @Entity -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@AllArgsConstructor -@SuperBuilder -public class Attendance extends BaseEntity { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "attendance_id") - private Long id; - - @Enumerated(EnumType.STRING) - private Status status; - - @ManyToOne - @JoinColumn(name = "meeting_id") - private Meeting meeting; - - @ManyToOne - @JoinColumn(name = "user_id") - private User user; - - @PrePersist - public void init() { - this.status = Status.PENDING; - } - - public Attendance(Meeting meeting, User user) { - this.meeting = meeting; - this.user = user; - } - - public void attend() { - this.status = Status.ATTEND; - } - - public void close() { - this.status = Status.ABSENT; - } - - public boolean isPending() { - return this.status == Status.PENDING; - } - - public boolean isWrong(Integer code) { - return !this.meeting.getCode().equals(code); +class Attendance + @JvmOverloads + constructor( + @ManyToOne + @JoinColumn(name = "meeting_id") + val meeting: Meeting, + @ManyToOne + @JoinColumn(name = "user_id") + val user: User, + @Enumerated(EnumType.STRING) + var status: Status? = null, + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "attendance_id") + val id: Long = 0, + ) : BaseEntity() { + @PrePersist + fun init() { + status = Status.PENDING + } + + fun attend() { + status = Status.ATTEND + } + + fun close() { + status = Status.ABSENT + } + + val isPending: Boolean + get() = status == Status.PENDING + + fun isWrong(code: Int): Boolean = meeting.getCode() != code } -} From 070116a6839d19c7b7482801e1c37dd08c0e8cbe Mon Sep 17 00:00:00 2001 From: soo0711 Date: Wed, 18 Feb 2026 01:18:22 +0900 Subject: [PATCH 04/45] =?UTF-8?q?test:=20Attendance=20entity=20Test=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/entity/AttendanceTest.kt | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 src/test/kotlin/com/weeth/domain/attendance/domain/entity/AttendanceTest.kt diff --git a/src/test/kotlin/com/weeth/domain/attendance/domain/entity/AttendanceTest.kt b/src/test/kotlin/com/weeth/domain/attendance/domain/entity/AttendanceTest.kt new file mode 100644 index 00000000..25d51a54 --- /dev/null +++ b/src/test/kotlin/com/weeth/domain/attendance/domain/entity/AttendanceTest.kt @@ -0,0 +1,89 @@ +package com.weeth.domain.attendance.domain.entity + +import com.weeth.domain.attendance.domain.entity.enums.Status +import com.weeth.domain.attendance.fixture.AttendanceTestFixture.createActiveUser +import com.weeth.domain.attendance.fixture.AttendanceTestFixture.createAttendance +import com.weeth.domain.schedule.fixture.ScheduleTestFixture.createMeeting +import io.kotest.core.spec.style.DescribeSpec +import io.kotest.matchers.shouldBe + +class AttendanceTest : + DescribeSpec({ + + describe("attend") { + it("상태를 ATTEND로 변경한다") { + val meeting = createMeeting() + val user = createActiveUser("테스트유저") + val attendance = createAttendance(meeting, user) + attendance.init() + + attendance.attend() + + attendance.status shouldBe Status.ATTEND + } + } + + describe("close") { + it("상태를 ABSENT로 변경한다") { + val meeting = createMeeting() + val user = createActiveUser("테스트유저") + val attendance = createAttendance(meeting, user) + attendance.init() + + attendance.close() + + attendance.status shouldBe Status.ABSENT + } + } + + describe("isPending") { + it("상태가 PENDING이면 true를 반환한다") { + val meeting = createMeeting() + val user = createActiveUser("테스트유저") + val attendance = createAttendance(meeting, user) + attendance.init() + + attendance.isPending shouldBe true + } + + it("상태가 PENDING이 아니면 false를 반환한다") { + val meeting = createMeeting() + val user = createActiveUser("테스트유저") + val attendance = createAttendance(meeting, user) + attendance.init() + attendance.attend() + + attendance.isPending shouldBe false + } + } + + describe("isWrong") { + it("코드가 일치하지 않으면 true를 반환한다") { + val meeting = createMeeting() + val user = createActiveUser("테스트유저") + val attendance = createAttendance(meeting, user) + + attendance.isWrong(9999) shouldBe true + } + + it("코드가 일치하면 false를 반환한다") { + val meeting = createMeeting() + val user = createActiveUser("테스트유저") + val attendance = createAttendance(meeting, user) + + attendance.isWrong(1234) shouldBe false + } + } + + describe("init") { + it("상태를 PENDING으로 초기화한다") { + val meeting = createMeeting() + val user = createActiveUser("테스트유저") + val attendance = createAttendance(meeting, user) + + attendance.init() + + attendance.status shouldBe Status.PENDING + } + } + }) From 10b63606910fd8cb4d23e409b75495eeff22c591 Mon Sep 17 00:00:00 2001 From: soo0711 Date: Wed, 18 Feb 2026 01:20:44 +0900 Subject: [PATCH 05/45] =?UTF-8?q?refactor:=20Attendacne=20enum=20java=20->?= =?UTF-8?q?=20kotlin=EC=9C=BC=EB=A1=9C=20=ED=8F=B4=EB=8D=94=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/weeth/domain/attendance/domain/entity/enums/Status.kt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/main/{java/com/weeth/domain/attendance/domain/entity/enums/Status.java => kotlin/com/weeth/domain/attendance/domain/entity/enums/Status.kt} (100%) diff --git a/src/main/java/com/weeth/domain/attendance/domain/entity/enums/Status.java b/src/main/kotlin/com/weeth/domain/attendance/domain/entity/enums/Status.kt similarity index 100% rename from src/main/java/com/weeth/domain/attendance/domain/entity/enums/Status.java rename to src/main/kotlin/com/weeth/domain/attendance/domain/entity/enums/Status.kt From 3e831af4d9f8615236c8bf63bbc22fb36b98aeaf Mon Sep 17 00:00:00 2001 From: soo0711 Date: Wed, 18 Feb 2026 01:30:01 +0900 Subject: [PATCH 06/45] =?UTF-8?q?refactor:=20Attendacne=20eum=20kotlin?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=AC=B8=EB=B2=95=20=EB=B3=80=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../weeth/domain/attendance/domain/entity/enums/Status.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/com/weeth/domain/attendance/domain/entity/enums/Status.kt b/src/main/kotlin/com/weeth/domain/attendance/domain/entity/enums/Status.kt index 4dbd7466..636c0a24 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/domain/entity/enums/Status.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/domain/entity/enums/Status.kt @@ -1,7 +1,7 @@ -package com.weeth.domain.attendance.domain.entity.enums; +package com.weeth.domain.attendance.domain.entity.enums -public enum Status { +enum class Status { ATTEND, PENDING, - ABSENT + ABSENT, } From de94f98945ef046b6a9d3271432d21baccfcc70b Mon Sep 17 00:00:00 2001 From: soo0711 Date: Wed, 18 Feb 2026 01:32:39 +0900 Subject: [PATCH 07/45] =?UTF-8?q?refactor:=20Attendacne=20repository=20kot?= =?UTF-8?q?lin=EC=9C=BC=EB=A1=9C=20=EB=AC=B8=EB=B2=95=20=EB=B3=80=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/AttendanceRepository.java | 18 ------------------ .../domain/repository/AttendanceRepository.kt | 19 +++++++++++++++++++ 2 files changed, 19 insertions(+), 18 deletions(-) delete mode 100644 src/main/java/com/weeth/domain/attendance/domain/repository/AttendanceRepository.java create mode 100644 src/main/kotlin/com/weeth/domain/attendance/domain/repository/AttendanceRepository.kt diff --git a/src/main/java/com/weeth/domain/attendance/domain/repository/AttendanceRepository.java b/src/main/java/com/weeth/domain/attendance/domain/repository/AttendanceRepository.java deleted file mode 100644 index 6fe213fa..00000000 --- a/src/main/java/com/weeth/domain/attendance/domain/repository/AttendanceRepository.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.weeth.domain.attendance.domain.repository; - -import com.weeth.domain.attendance.domain.entity.Attendance; -import com.weeth.domain.schedule.domain.entity.Meeting; -import com.weeth.domain.user.domain.entity.enums.Status; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Modifying; -import org.springframework.data.jpa.repository.Query; - -import java.util.List; - -public interface AttendanceRepository extends JpaRepository { - List findAllByMeetingAndUserStatus(Meeting meeting, Status status); - - @Modifying - @Query("DELETE FROM Attendance a WHERE a.meeting = :meeting") - void deleteAllByMeeting(Meeting meeting); -} diff --git a/src/main/kotlin/com/weeth/domain/attendance/domain/repository/AttendanceRepository.kt b/src/main/kotlin/com/weeth/domain/attendance/domain/repository/AttendanceRepository.kt new file mode 100644 index 00000000..0ab26a48 --- /dev/null +++ b/src/main/kotlin/com/weeth/domain/attendance/domain/repository/AttendanceRepository.kt @@ -0,0 +1,19 @@ +package com.weeth.domain.attendance.domain.repository + +import com.weeth.domain.attendance.domain.entity.Attendance +import com.weeth.domain.schedule.domain.entity.Meeting +import com.weeth.domain.user.domain.entity.enums.Status +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.Modifying +import org.springframework.data.jpa.repository.Query + +interface AttendanceRepository : JpaRepository { + fun findAllByMeetingAndUserStatus( + meeting: Meeting, + status: Status, + ): List + + @Modifying + @Query("DELETE FROM Attendance a WHERE a.meeting = :meeting") + fun deleteAllByMeeting(meeting: Meeting) +} From 7bb39a20c45a993c12363654d93f612b5e637b49 Mon Sep 17 00:00:00 2001 From: soo0711 Date: Wed, 18 Feb 2026 01:37:05 +0900 Subject: [PATCH 08/45] =?UTF-8?q?refactor:=20Attendacne=20dto=20java=20->?= =?UTF-8?q?=20kotlin=EC=9C=BC=EB=A1=9C=20=ED=8F=B4=EB=8D=94=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/weeth/domain/attendance/application/dto/AttendanceDTO.kt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/main/{java/com/weeth/domain/attendance/application/dto/AttendanceDTO.java => kotlin/com/weeth/domain/attendance/application/dto/AttendanceDTO.kt} (100%) diff --git a/src/main/java/com/weeth/domain/attendance/application/dto/AttendanceDTO.java b/src/main/kotlin/com/weeth/domain/attendance/application/dto/AttendanceDTO.kt similarity index 100% rename from src/main/java/com/weeth/domain/attendance/application/dto/AttendanceDTO.java rename to src/main/kotlin/com/weeth/domain/attendance/application/dto/AttendanceDTO.kt From 4e441d3b0b12e333a6cf827b8f54a90de984cf75 Mon Sep 17 00:00:00 2001 From: soo0711 Date: Wed, 18 Feb 2026 01:47:39 +0900 Subject: [PATCH 09/45] =?UTF-8?q?refactor:=20Attendacne=20dto=20kotlin?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=AC=B8=EB=B2=95=20=EB=B3=80=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/dto/AttendanceDTO.kt | 112 +++++++++--------- 1 file changed, 56 insertions(+), 56 deletions(-) diff --git a/src/main/kotlin/com/weeth/domain/attendance/application/dto/AttendanceDTO.kt b/src/main/kotlin/com/weeth/domain/attendance/application/dto/AttendanceDTO.kt index 072ea499..d95d1a41 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/application/dto/AttendanceDTO.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/application/dto/AttendanceDTO.kt @@ -1,57 +1,57 @@ -package com.weeth.domain.attendance.application.dto; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Pattern; -import com.weeth.domain.attendance.domain.entity.enums.Status; - -import java.time.LocalDateTime; -import java.util.List; - -public class AttendanceDTO { - - public record Main( - Integer attendanceRate, - String title, - Status status, - @Schema(description = "어드민인 경우 출석 코드 노출") - Integer code, - LocalDateTime start, - LocalDateTime end, - String location - ) {} - - public record Detail( - Integer attendanceCount, - Integer total, - Integer absenceCount, - List attendances - ) {} - - public record Response( - Long id, - Status status, - String title, - LocalDateTime start, - LocalDateTime end, - String location - ) {} - - public record CheckIn( - Integer code - ) {} - - public record AttendanceInfo( - Long id, - Status status, - String name, - String position, - String department, - String studentId - ) {} - - public record UpdateStatus( - @NotNull Long attendanceId, - @NotNull @Pattern(regexp = "ATTEND|ABSENT")String status - ) {} +package com.weeth.domain.attendance.application.dto + +import com.weeth.domain.attendance.domain.entity.enums.Status +import io.swagger.v3.oas.annotations.media.Schema +import jakarta.validation.constraints.NotNull +import jakarta.validation.constraints.Pattern +import java.time.LocalDateTime + +class AttendanceDTO { + data class Main( + val attendanceRate: Int?, + val title: String, + val status: Status, + @field:Schema(description = "어드민인 경우 출석 코드 노출") + val code: Int?, + val start: LocalDateTime, + val end: LocalDateTime, + val location: String, + ) + + data class Detail( + val attendanceCount: Int, + val total: Int, + val absenceCount: Int, + val attendances: List, + ) + + data class Response( + val id: Long, + val status: Status, + val title: String, + val start: LocalDateTime, + val end: LocalDateTime, + val location: String, + ) + + data class CheckIn( + val code: Int, + ) + + data class AttendanceInfo( + val id: Long, + val status: Status, + val name: String, + val position: String, + val department: String, + val studentId: String, + ) + + data class UpdateStatus( + @field:NotNull + val attendanceId: Long, + @field:NotNull + @field:Pattern(regexp = "ATTEND|ABSENT") + val status: String, + ) } From 6c5e94a3ed8f40a7709882e7bb4975f8ac1dec05 Mon Sep 17 00:00:00 2001 From: soo0711 Date: Wed, 18 Feb 2026 02:18:21 +0900 Subject: [PATCH 10/45] =?UTF-8?q?refactor:=20Attendacne=20dto=20=EC=BD=94?= =?UTF-8?q?=ED=8B=80=EB=A6=B0=20=EC=BD=94=EB=93=9C=EC=97=90=20=EB=A7=9E?= =?UTF-8?q?=EC=B6=B0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../usecase/AttendanceUseCaseImpl.java | 4 +- .../presentation/AttendanceController.java | 2 +- .../application/dto/AttendanceDTO.kt | 30 +++++------ .../mapper/AttendanceMapperTest.kt | 52 +++++++++---------- 4 files changed, 44 insertions(+), 44 deletions(-) diff --git a/src/main/java/com/weeth/domain/attendance/application/usecase/AttendanceUseCaseImpl.java b/src/main/java/com/weeth/domain/attendance/application/usecase/AttendanceUseCaseImpl.java index 50c71581..dcfb3bb6 100644 --- a/src/main/java/com/weeth/domain/attendance/application/usecase/AttendanceUseCaseImpl.java +++ b/src/main/java/com/weeth/domain/attendance/application/usecase/AttendanceUseCaseImpl.java @@ -120,10 +120,10 @@ public void close(LocalDate now, Integer cardinal) { @Transactional public void updateAttendanceStatus(List attendanceUpdates) { attendanceUpdates.forEach(update -> { - Attendance attendance = attendanceGetService.findByAttendanceId(update.attendanceId()); + Attendance attendance = attendanceGetService.findByAttendanceId(update.getAttendanceId()); User user = attendance.getUser(); - Status newStatus = Status.valueOf(update.status()); + Status newStatus = Status.valueOf(update.getStatus()); if (newStatus == Status.ABSENT) { attendance.close(); diff --git a/src/main/java/com/weeth/domain/attendance/presentation/AttendanceController.java b/src/main/java/com/weeth/domain/attendance/presentation/AttendanceController.java index 356a2666..2d126724 100644 --- a/src/main/java/com/weeth/domain/attendance/presentation/AttendanceController.java +++ b/src/main/java/com/weeth/domain/attendance/presentation/AttendanceController.java @@ -30,7 +30,7 @@ public class AttendanceController { @PatchMapping @Operation(summary="출석체크") public CommonResponse checkIn(@Parameter(hidden = true) @CurrentUser Long userId, @RequestBody AttendanceDTO.CheckIn checkIn) throws AttendanceCodeMismatchException { - attendanceUseCase.checkIn(userId, checkIn.code()); + attendanceUseCase.checkIn(userId, checkIn.getCode()); return CommonResponse.success(ATTENDANCE_CHECKIN_SUCCESS); } diff --git a/src/main/kotlin/com/weeth/domain/attendance/application/dto/AttendanceDTO.kt b/src/main/kotlin/com/weeth/domain/attendance/application/dto/AttendanceDTO.kt index d95d1a41..e676589f 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/application/dto/AttendanceDTO.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/application/dto/AttendanceDTO.kt @@ -9,13 +9,13 @@ import java.time.LocalDateTime class AttendanceDTO { data class Main( val attendanceRate: Int?, - val title: String, - val status: Status, + val title: String?, + val status: Status?, @field:Schema(description = "어드민인 경우 출석 코드 노출") val code: Int?, - val start: LocalDateTime, - val end: LocalDateTime, - val location: String, + val start: LocalDateTime?, + val end: LocalDateTime?, + val location: String?, ) data class Detail( @@ -27,11 +27,11 @@ class AttendanceDTO { data class Response( val id: Long, - val status: Status, - val title: String, - val start: LocalDateTime, - val end: LocalDateTime, - val location: String, + val status: Status?, + val title: String?, + val start: LocalDateTime?, + val end: LocalDateTime?, + val location: String?, ) data class CheckIn( @@ -40,11 +40,11 @@ class AttendanceDTO { data class AttendanceInfo( val id: Long, - val status: Status, - val name: String, - val position: String, - val department: String, - val studentId: String, + val status: Status?, + val name: String?, + val position: String?, + val department: String?, + val studentId: String?, ) data class UpdateStatus( diff --git a/src/test/kotlin/com/weeth/domain/attendance/application/mapper/AttendanceMapperTest.kt b/src/test/kotlin/com/weeth/domain/attendance/application/mapper/AttendanceMapperTest.kt index 2c38f017..563710b7 100644 --- a/src/test/kotlin/com/weeth/domain/attendance/application/mapper/AttendanceMapperTest.kt +++ b/src/test/kotlin/com/weeth/domain/attendance/application/mapper/AttendanceMapperTest.kt @@ -31,11 +31,11 @@ class AttendanceMapperTest : val main = attendanceMapper.toMainDto(user, attendance) main.shouldNotBeNull() - main.title() shouldBe "Today" - main.status() shouldBe attendance.status - main.start() shouldBe meeting.start - main.end() shouldBe meeting.end - main.location() shouldBe meeting.location + main.title shouldBe "Today" + main.status shouldBe attendance.status + main.start shouldBe meeting.start + main.end shouldBe meeting.end + main.location shouldBe meeting.location } it("todayAttendance가 null이면 필드는 null로 매핑") { @@ -44,10 +44,10 @@ class AttendanceMapperTest : val main = attendanceMapper.toMainDto(user, null) main.shouldNotBeNull() - main.title().shouldBeNull() - main.start().shouldBeNull() - main.end().shouldBeNull() - main.location().shouldBeNull() + main.title.shouldBeNull() + main.start.shouldBeNull() + main.end.shouldBeNull() + main.location.shouldBeNull() } it("일반 유저는 출석 코드가 null로 매핑된다") { @@ -59,9 +59,9 @@ class AttendanceMapperTest : val main = attendanceMapper.toMainDto(user, attendance) main.shouldNotBeNull() - main.code().shouldBeNull() - main.title() shouldBe "Today" - main.status() shouldBe attendance.status + main.code.shouldBeNull() + main.title shouldBe "Today" + main.status shouldBe attendance.status } } @@ -74,10 +74,10 @@ class AttendanceMapperTest : val response = attendanceMapper.toResponseDto(attendance) response.shouldNotBeNull() - response.title() shouldBe "D-1" - response.start() shouldBe meeting.start - response.end() shouldBe meeting.end - response.location() shouldBe meeting.location + response.title shouldBe "D-1" + response.start shouldBe meeting.start + response.end shouldBe meeting.end + response.location shouldBe meeting.location } } @@ -98,8 +98,8 @@ class AttendanceMapperTest : val detail = attendanceMapper.toDetailDto(user, listOf(r1, r2)) detail.shouldNotBeNull() - detail.attendances() shouldBe listOf(r1, r2) - detail.total() shouldBe 5 + detail.attendances shouldBe listOf(r1, r2) + detail.total shouldBe 5 } } @@ -115,9 +115,9 @@ class AttendanceMapperTest : val info = attendanceMapper.toAttendanceInfoDto(attendance) info.shouldNotBeNull() - info.id() shouldBe 10L - info.status() shouldBe attendance.status - info.name() shouldBe "유저B" + info.id shouldBe 10L + info.status shouldBe attendance.status + info.name shouldBe "유저B" } } @@ -132,11 +132,11 @@ class AttendanceMapperTest : val main = attendanceMapper.toAdminResponse(adminUser, attendance) main.shouldNotBeNull() - main.code() shouldBe expectedCode - main.title() shouldBe "Today" - main.start() shouldBe meeting.start - main.end() shouldBe meeting.end - main.location() shouldBe meeting.location + main.code shouldBe expectedCode + main.title shouldBe "Today" + main.start shouldBe meeting.start + main.end shouldBe meeting.end + main.location shouldBe meeting.location } } }) From af5704e724ec34e15ac723af3b67f8fe61ca94c2 Mon Sep 17 00:00:00 2001 From: soo0711 Date: Wed, 18 Feb 2026 02:26:55 +0900 Subject: [PATCH 11/45] =?UTF-8?q?refactor:=20Attendacne=20mapper=20java=20?= =?UTF-8?q?->=20kotlin=EC=9C=BC=EB=A1=9C=20=ED=8F=B4=EB=8D=94=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20=EB=B0=8F=20dto=20=ED=99=95=EC=9E=A5=20=EC=A4=80?= =?UTF-8?q?=EB=B9=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/dto/AttendanceDTO.kt | 57 ------------------- .../application/mapper/AttendanceMapper.kt} | 0 2 files changed, 57 deletions(-) delete mode 100644 src/main/kotlin/com/weeth/domain/attendance/application/dto/AttendanceDTO.kt rename src/main/{java/com/weeth/domain/attendance/application/mapper/AttendanceMapper.java => kotlin/com/weeth/domain/attendance/application/mapper/AttendanceMapper.kt} (100%) diff --git a/src/main/kotlin/com/weeth/domain/attendance/application/dto/AttendanceDTO.kt b/src/main/kotlin/com/weeth/domain/attendance/application/dto/AttendanceDTO.kt deleted file mode 100644 index e676589f..00000000 --- a/src/main/kotlin/com/weeth/domain/attendance/application/dto/AttendanceDTO.kt +++ /dev/null @@ -1,57 +0,0 @@ -package com.weeth.domain.attendance.application.dto - -import com.weeth.domain.attendance.domain.entity.enums.Status -import io.swagger.v3.oas.annotations.media.Schema -import jakarta.validation.constraints.NotNull -import jakarta.validation.constraints.Pattern -import java.time.LocalDateTime - -class AttendanceDTO { - data class Main( - val attendanceRate: Int?, - val title: String?, - val status: Status?, - @field:Schema(description = "어드민인 경우 출석 코드 노출") - val code: Int?, - val start: LocalDateTime?, - val end: LocalDateTime?, - val location: String?, - ) - - data class Detail( - val attendanceCount: Int, - val total: Int, - val absenceCount: Int, - val attendances: List, - ) - - data class Response( - val id: Long, - val status: Status?, - val title: String?, - val start: LocalDateTime?, - val end: LocalDateTime?, - val location: String?, - ) - - data class CheckIn( - val code: Int, - ) - - data class AttendanceInfo( - val id: Long, - val status: Status?, - val name: String?, - val position: String?, - val department: String?, - val studentId: String?, - ) - - data class UpdateStatus( - @field:NotNull - val attendanceId: Long, - @field:NotNull - @field:Pattern(regexp = "ATTEND|ABSENT") - val status: String, - ) -} diff --git a/src/main/java/com/weeth/domain/attendance/application/mapper/AttendanceMapper.java b/src/main/kotlin/com/weeth/domain/attendance/application/mapper/AttendanceMapper.kt similarity index 100% rename from src/main/java/com/weeth/domain/attendance/application/mapper/AttendanceMapper.java rename to src/main/kotlin/com/weeth/domain/attendance/application/mapper/AttendanceMapper.kt From 250c4569e3f7370d5fc0bc2f35d7b192ece81687 Mon Sep 17 00:00:00 2001 From: soo0711 Date: Wed, 18 Feb 2026 02:31:00 +0900 Subject: [PATCH 12/45] =?UTF-8?q?refactor:=20Attendance=20DTO=20request/re?= =?UTF-8?q?sponse=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/dto/request/CheckInRequest.kt | 5 +++++ .../dto/request/UpdateAttendanceStatusRequest.kt | 12 ++++++++++++ .../dto/response/AttendanceDetailResponse.kt | 8 ++++++++ .../dto/response/AttendanceInfoResponse.kt | 12 ++++++++++++ .../dto/response/AttendanceMainResponse.kt | 16 ++++++++++++++++ .../dto/response/AttendanceResponse.kt | 13 +++++++++++++ 6 files changed, 66 insertions(+) create mode 100644 src/main/kotlin/com/weeth/domain/attendance/application/dto/request/CheckInRequest.kt create mode 100644 src/main/kotlin/com/weeth/domain/attendance/application/dto/request/UpdateAttendanceStatusRequest.kt create mode 100644 src/main/kotlin/com/weeth/domain/attendance/application/dto/response/AttendanceDetailResponse.kt create mode 100644 src/main/kotlin/com/weeth/domain/attendance/application/dto/response/AttendanceInfoResponse.kt create mode 100644 src/main/kotlin/com/weeth/domain/attendance/application/dto/response/AttendanceMainResponse.kt create mode 100644 src/main/kotlin/com/weeth/domain/attendance/application/dto/response/AttendanceResponse.kt diff --git a/src/main/kotlin/com/weeth/domain/attendance/application/dto/request/CheckInRequest.kt b/src/main/kotlin/com/weeth/domain/attendance/application/dto/request/CheckInRequest.kt new file mode 100644 index 00000000..b699cfeb --- /dev/null +++ b/src/main/kotlin/com/weeth/domain/attendance/application/dto/request/CheckInRequest.kt @@ -0,0 +1,5 @@ +package com.weeth.domain.attendance.application.dto.request + +data class CheckInRequest( + val code: Int, +) diff --git a/src/main/kotlin/com/weeth/domain/attendance/application/dto/request/UpdateAttendanceStatusRequest.kt b/src/main/kotlin/com/weeth/domain/attendance/application/dto/request/UpdateAttendanceStatusRequest.kt new file mode 100644 index 00000000..495194e3 --- /dev/null +++ b/src/main/kotlin/com/weeth/domain/attendance/application/dto/request/UpdateAttendanceStatusRequest.kt @@ -0,0 +1,12 @@ +package com.weeth.domain.attendance.application.dto.request + +import jakarta.validation.constraints.NotNull +import jakarta.validation.constraints.Pattern + +data class UpdateAttendanceStatusRequest( + @field:NotNull + val attendanceId: Long, + @field:NotNull + @field:Pattern(regexp = "ATTEND|ABSENT") + val status: String, +) diff --git a/src/main/kotlin/com/weeth/domain/attendance/application/dto/response/AttendanceDetailResponse.kt b/src/main/kotlin/com/weeth/domain/attendance/application/dto/response/AttendanceDetailResponse.kt new file mode 100644 index 00000000..580967c1 --- /dev/null +++ b/src/main/kotlin/com/weeth/domain/attendance/application/dto/response/AttendanceDetailResponse.kt @@ -0,0 +1,8 @@ +package com.weeth.domain.attendance.application.dto.response + +data class AttendanceDetailResponse( + val attendanceCount: Int, + val total: Int, + val absenceCount: Int, + val attendances: List, +) diff --git a/src/main/kotlin/com/weeth/domain/attendance/application/dto/response/AttendanceInfoResponse.kt b/src/main/kotlin/com/weeth/domain/attendance/application/dto/response/AttendanceInfoResponse.kt new file mode 100644 index 00000000..7bcc7e4a --- /dev/null +++ b/src/main/kotlin/com/weeth/domain/attendance/application/dto/response/AttendanceInfoResponse.kt @@ -0,0 +1,12 @@ +package com.weeth.domain.attendance.application.dto.response + +import com.weeth.domain.attendance.domain.entity.enums.Status + +data class AttendanceInfoResponse( + val id: Long, + val status: Status?, + val name: String?, + val position: String?, + val department: String?, + val studentId: String?, +) diff --git a/src/main/kotlin/com/weeth/domain/attendance/application/dto/response/AttendanceMainResponse.kt b/src/main/kotlin/com/weeth/domain/attendance/application/dto/response/AttendanceMainResponse.kt new file mode 100644 index 00000000..4f89a502 --- /dev/null +++ b/src/main/kotlin/com/weeth/domain/attendance/application/dto/response/AttendanceMainResponse.kt @@ -0,0 +1,16 @@ +package com.weeth.domain.attendance.application.dto.response + +import com.weeth.domain.attendance.domain.entity.enums.Status +import io.swagger.v3.oas.annotations.media.Schema +import java.time.LocalDateTime + +data class AttendanceMainResponse( + val attendanceRate: Int?, + val title: String?, + val status: Status?, + @field:Schema(description = "어드민인 경우 출석 코드 노출") + val code: Int?, + val start: LocalDateTime?, + val end: LocalDateTime?, + val location: String?, +) diff --git a/src/main/kotlin/com/weeth/domain/attendance/application/dto/response/AttendanceResponse.kt b/src/main/kotlin/com/weeth/domain/attendance/application/dto/response/AttendanceResponse.kt new file mode 100644 index 00000000..f56f81fe --- /dev/null +++ b/src/main/kotlin/com/weeth/domain/attendance/application/dto/response/AttendanceResponse.kt @@ -0,0 +1,13 @@ +package com.weeth.domain.attendance.application.dto.response + +import com.weeth.domain.attendance.domain.entity.enums.Status +import java.time.LocalDateTime + +data class AttendanceResponse( + val id: Long, + val status: Status?, + val title: String?, + val start: LocalDateTime?, + val end: LocalDateTime?, + val location: String?, +) From a236fe7acc0c8b66306769e87296aca74e5609d9 Mon Sep 17 00:00:00 2001 From: soo0711 Date: Wed, 18 Feb 2026 02:32:17 +0900 Subject: [PATCH 13/45] =?UTF-8?q?refactor:=20Attendacne=20mapper=20?= =?UTF-8?q?=EC=BD=94=ED=8B=80=EB=A6=B0=20=EC=BD=94=EB=93=9C=EC=97=90=20?= =?UTF-8?q?=EB=A7=9E=EC=B6=B0=20=EC=B0=B8=EC=A1=B0=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../usecase/AttendanceUseCase.java | 17 ++- .../usecase/AttendanceUseCaseImpl.java | 24 ++-- .../AttendanceAdminController.java | 7 +- .../presentation/AttendanceController.java | 11 +- .../application/mapper/AttendanceMapper.kt | 115 ++++++++++-------- .../mapper/AttendanceMapperTest.kt | 39 +++--- .../usecase/AttendanceUseCaseImplTest.kt | 32 ++--- 7 files changed, 132 insertions(+), 113 deletions(-) diff --git a/src/main/java/com/weeth/domain/attendance/application/usecase/AttendanceUseCase.java b/src/main/java/com/weeth/domain/attendance/application/usecase/AttendanceUseCase.java index e6d87451..49b3f272 100644 --- a/src/main/java/com/weeth/domain/attendance/application/usecase/AttendanceUseCase.java +++ b/src/main/java/com/weeth/domain/attendance/application/usecase/AttendanceUseCase.java @@ -1,25 +1,24 @@ package com.weeth.domain.attendance.application.usecase; import java.util.List; -import com.weeth.domain.attendance.application.dto.AttendanceDTO; -import com.weeth.domain.attendance.application.dto.AttendanceDTO.AttendanceInfo; +import com.weeth.domain.attendance.application.dto.request.UpdateAttendanceStatusRequest; +import com.weeth.domain.attendance.application.dto.response.AttendanceDetailResponse; +import com.weeth.domain.attendance.application.dto.response.AttendanceInfoResponse; +import com.weeth.domain.attendance.application.dto.response.AttendanceMainResponse; import com.weeth.domain.attendance.application.exception.AttendanceCodeMismatchException; import java.time.LocalDate; -import static com.weeth.domain.attendance.application.dto.AttendanceDTO.Detail; -import static com.weeth.domain.attendance.application.dto.AttendanceDTO.Main; - public interface AttendanceUseCase { void checkIn(Long userId, Integer code) throws AttendanceCodeMismatchException; - Main find(Long userId); + AttendanceMainResponse find(Long userId); - Detail findAllDetailsByCurrentCardinal(Long userId); + AttendanceDetailResponse findAllDetailsByCurrentCardinal(Long userId); - List findAllAttendanceByMeeting(Long meetingId); + List findAllAttendanceByMeeting(Long meetingId); void close(LocalDate now, Integer cardinal); - void updateAttendanceStatus(List attendanceUpdates); + void updateAttendanceStatus(List attendanceUpdates); } diff --git a/src/main/java/com/weeth/domain/attendance/application/usecase/AttendanceUseCaseImpl.java b/src/main/java/com/weeth/domain/attendance/application/usecase/AttendanceUseCaseImpl.java index dcfb3bb6..22fda428 100644 --- a/src/main/java/com/weeth/domain/attendance/application/usecase/AttendanceUseCaseImpl.java +++ b/src/main/java/com/weeth/domain/attendance/application/usecase/AttendanceUseCaseImpl.java @@ -1,6 +1,10 @@ package com.weeth.domain.attendance.application.usecase; -import com.weeth.domain.attendance.application.dto.AttendanceDTO; +import com.weeth.domain.attendance.application.dto.request.UpdateAttendanceStatusRequest; +import com.weeth.domain.attendance.application.dto.response.AttendanceDetailResponse; +import com.weeth.domain.attendance.application.dto.response.AttendanceInfoResponse; +import com.weeth.domain.attendance.application.dto.response.AttendanceMainResponse; +import com.weeth.domain.attendance.application.dto.response.AttendanceResponse; import com.weeth.domain.attendance.application.exception.AttendanceCodeMismatchException; import com.weeth.domain.attendance.application.exception.AttendanceNotFoundException; import com.weeth.domain.attendance.application.mapper.AttendanceMapper; @@ -58,7 +62,7 @@ public void checkIn(Long userId, Integer code) throws AttendanceCodeMismatchExce } @Override - public AttendanceDTO.Main find(Long userId) { + public AttendanceMainResponse find(Long userId) { User user = userGetService.find(userId); Attendance todayMeeting = user.getAttendances().stream() @@ -71,30 +75,30 @@ public AttendanceDTO.Main find(Long userId) { return mapper.toAdminResponse(user, todayMeeting); } - return mapper.toMainDto(user, todayMeeting); + return mapper.toMainResponse(user, todayMeeting); } - public AttendanceDTO.Detail findAllDetailsByCurrentCardinal(Long userId) { + public AttendanceDetailResponse findAllDetailsByCurrentCardinal(Long userId) { User user = userGetService.find(userId); Cardinal currentCardinal = userCardinalGetService.getCurrentCardinal(user); - List responses = user.getAttendances().stream() + List responses = user.getAttendances().stream() .filter(attendance -> attendance.getMeeting().getCardinal().equals(currentCardinal.getCardinalNumber())) .sorted(Comparator.comparing(attendance -> attendance.getMeeting().getStart())) - .map(mapper::toResponseDto) + .map(mapper::toResponse) .toList(); - return mapper.toDetailDto(user, responses); + return mapper.toDetailResponse(user, responses); } @Override - public List findAllAttendanceByMeeting(Long meetingId) { + public List findAllAttendanceByMeeting(Long meetingId) { Meeting meeting = meetingGetService.find(meetingId); List attendances = attendanceGetService.findAllByMeeting(meeting); return attendances.stream() - .map(mapper::toAttendanceInfoDto) + .map(mapper::toInfoResponse) .toList(); } @@ -118,7 +122,7 @@ public void close(LocalDate now, Integer cardinal) { @Override @Transactional - public void updateAttendanceStatus(List attendanceUpdates) { + public void updateAttendanceStatus(List attendanceUpdates) { attendanceUpdates.forEach(update -> { Attendance attendance = attendanceGetService.findByAttendanceId(update.getAttendanceId()); User user = attendance.getUser(); diff --git a/src/main/java/com/weeth/domain/attendance/presentation/AttendanceAdminController.java b/src/main/java/com/weeth/domain/attendance/presentation/AttendanceAdminController.java index 1d76a3c0..6ddc9cbb 100644 --- a/src/main/java/com/weeth/domain/attendance/presentation/AttendanceAdminController.java +++ b/src/main/java/com/weeth/domain/attendance/presentation/AttendanceAdminController.java @@ -3,7 +3,8 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; -import com.weeth.domain.attendance.application.dto.AttendanceDTO; +import com.weeth.domain.attendance.application.dto.request.UpdateAttendanceStatusRequest; +import com.weeth.domain.attendance.application.dto.response.AttendanceInfoResponse; import com.weeth.domain.attendance.application.exception.AttendanceErrorCode; import com.weeth.domain.attendance.application.usecase.AttendanceUseCase; import com.weeth.domain.schedule.application.dto.MeetingDTO; @@ -45,13 +46,13 @@ public CommonResponse getMeetings(@RequestParam(required = fal @GetMapping("/{meetingId}") @Operation(summary = "모든 인원 정기모임 출석 정보 조회") - public CommonResponse> getAllAttendance(@PathVariable Long meetingId) { + public CommonResponse> getAllAttendance(@PathVariable Long meetingId) { return CommonResponse.success(ATTENDANCE_FIND_DETAIL_SUCCESS, attendanceUseCase.findAllAttendanceByMeeting(meetingId)); } @PatchMapping("/status") @Operation(summary = "모든 인원 정기모임 개별 출석 상태 수정") - public CommonResponse updateAttendanceStatus(@RequestBody @Valid List attendanceUpdates) { + public CommonResponse updateAttendanceStatus(@RequestBody @Valid List attendanceUpdates) { attendanceUseCase.updateAttendanceStatus(attendanceUpdates); return CommonResponse.success(ATTENDANCE_UPDATED_SUCCESS); } diff --git a/src/main/java/com/weeth/domain/attendance/presentation/AttendanceController.java b/src/main/java/com/weeth/domain/attendance/presentation/AttendanceController.java index 2d126724..32198f6a 100644 --- a/src/main/java/com/weeth/domain/attendance/presentation/AttendanceController.java +++ b/src/main/java/com/weeth/domain/attendance/presentation/AttendanceController.java @@ -3,7 +3,9 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; -import com.weeth.domain.attendance.application.dto.AttendanceDTO; +import com.weeth.domain.attendance.application.dto.request.CheckInRequest; +import com.weeth.domain.attendance.application.dto.response.AttendanceDetailResponse; +import com.weeth.domain.attendance.application.dto.response.AttendanceMainResponse; import com.weeth.domain.attendance.application.exception.AttendanceErrorCode; import com.weeth.domain.attendance.application.usecase.AttendanceUseCase; import com.weeth.global.auth.annotation.CurrentUser; @@ -13,7 +15,6 @@ import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; -import static com.weeth.domain.attendance.application.dto.AttendanceDTO.*; import static com.weeth.domain.attendance.presentation.AttendanceResponseCode.ATTENDANCE_CHECKIN_SUCCESS; import static com.weeth.domain.attendance.presentation.AttendanceResponseCode.ATTENDANCE_FIND_ALL_SUCCESS; import static com.weeth.domain.attendance.presentation.AttendanceResponseCode.ATTENDANCE_FIND_SUCCESS; @@ -29,20 +30,20 @@ public class AttendanceController { @PatchMapping @Operation(summary="출석체크") - public CommonResponse checkIn(@Parameter(hidden = true) @CurrentUser Long userId, @RequestBody AttendanceDTO.CheckIn checkIn) throws AttendanceCodeMismatchException { + public CommonResponse checkIn(@Parameter(hidden = true) @CurrentUser Long userId, @RequestBody CheckInRequest checkIn) throws AttendanceCodeMismatchException { attendanceUseCase.checkIn(userId, checkIn.getCode()); return CommonResponse.success(ATTENDANCE_CHECKIN_SUCCESS); } @GetMapping @Operation(summary="출석 메인페이지") - public CommonResponse
find(@Parameter(hidden = true) @CurrentUser Long userId) { + public CommonResponse find(@Parameter(hidden = true) @CurrentUser Long userId) { return CommonResponse.success(ATTENDANCE_FIND_SUCCESS, attendanceUseCase.find(userId)); } @GetMapping("/detail") @Operation(summary="출석 내역 상세조회") - public CommonResponse findAll(@Parameter(hidden = true) @CurrentUser Long userId) { + public CommonResponse findAll(@Parameter(hidden = true) @CurrentUser Long userId) { return CommonResponse.success(ATTENDANCE_FIND_ALL_SUCCESS, attendanceUseCase.findAllDetailsByCurrentCardinal(userId)); } } diff --git a/src/main/kotlin/com/weeth/domain/attendance/application/mapper/AttendanceMapper.kt b/src/main/kotlin/com/weeth/domain/attendance/application/mapper/AttendanceMapper.kt index 2b592858..71fbb65e 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/application/mapper/AttendanceMapper.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/application/mapper/AttendanceMapper.kt @@ -1,58 +1,71 @@ -package com.weeth.domain.attendance.application.mapper; +package com.weeth.domain.attendance.application.mapper -import com.weeth.domain.attendance.application.dto.AttendanceDTO; -import com.weeth.domain.attendance.domain.entity.Attendance; -import com.weeth.domain.user.domain.entity.User; -import org.mapstruct.*; +import com.weeth.domain.attendance.application.dto.response.AttendanceDetailResponse +import com.weeth.domain.attendance.application.dto.response.AttendanceInfoResponse +import com.weeth.domain.attendance.application.dto.response.AttendanceMainResponse +import com.weeth.domain.attendance.application.dto.response.AttendanceResponse +import com.weeth.domain.attendance.domain.entity.Attendance +import com.weeth.domain.user.domain.entity.User +import org.springframework.stereotype.Component -import java.util.List; +@Component +class AttendanceMapper { + fun toMainResponse( + user: User, + attendance: Attendance?, + ): AttendanceMainResponse = + AttendanceMainResponse( + attendanceRate = user.attendanceRate, + title = attendance?.meeting?.title, + status = attendance?.status, + code = null, + start = attendance?.meeting?.start, + end = attendance?.meeting?.end, + location = attendance?.meeting?.location, + ) -@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, unmappedTargetPolicy = ReportingPolicy.IGNORE) -public interface AttendanceMapper { + fun toAdminResponse( + user: User, + attendance: Attendance?, + ): AttendanceMainResponse = + AttendanceMainResponse( + attendanceRate = user.attendanceRate, + title = attendance?.meeting?.title, + status = attendance?.status, + code = attendance?.meeting?.code, + start = attendance?.meeting?.start, + end = attendance?.meeting?.end, + location = attendance?.meeting?.location, + ) - @Mappings({ - @Mapping(target = "attendanceRate", source = "user.attendanceRate"), - @Mapping(target = "title", source = "attendance.meeting.title"), - @Mapping(target = "status", source = "attendance.status"), - @Mapping(target = "code", ignore = true), - @Mapping(target = "start", source = "attendance.meeting.start"), - @Mapping(target = "end", source = "attendance.meeting.end"), - @Mapping(target = "location", source = "attendance.meeting.location"), - }) - AttendanceDTO.Main toMainDto(User user, Attendance attendance); + fun toDetailResponse( + user: User, + attendances: List, + ): AttendanceDetailResponse = + AttendanceDetailResponse( + attendanceCount = user.attendanceCount ?: 0, + total = (user.attendanceCount ?: 0) + (user.absenceCount ?: 0), + absenceCount = user.absenceCount ?: 0, + attendances = attendances, + ) - @Mappings({ - @Mapping(target = "attendanceRate", source = "user.attendanceRate"), - @Mapping(target = "title", source = "attendance.meeting.title"), - @Mapping(target = "status", source = "attendance.status"), - @Mapping(target = "code", source = "attendance.meeting.code"), - @Mapping(target = "start", source = "attendance.meeting.start"), - @Mapping(target = "end", source = "attendance.meeting.end"), - @Mapping(target = "location", source = "attendance.meeting.location"), - }) - AttendanceDTO.Main toAdminResponse(User user, Attendance attendance); - - @Mappings({ - @Mapping(target = "attendances", source = "attendances"), - @Mapping(target = "total", expression = "java( user.getAttendanceCount() + user.getAbsenceCount() )") - }) - AttendanceDTO.Detail toDetailDto(User user, List attendances); - - @Mappings({ - @Mapping(target = "title", source = "attendance.meeting.title"), - @Mapping(target = "start", source = "attendance.meeting.start"), - @Mapping(target = "end", source = "attendance.meeting.end"), - @Mapping(target = "location", source = "attendance.meeting.location"), - }) AttendanceDTO.Response toResponseDto(Attendance attendance); - - @Mappings({ - @Mapping(target = "id", source = "attendance.id"), - @Mapping(target = "status", source = "attendance.status"), - @Mapping(target = "name", source = "attendance.user.name"), - @Mapping(target = "position", source = "attendance.user.position"), - @Mapping(target = "department", source = "attendance.user.department"), - @Mapping(target = "studentId", source = "attendance.user.studentId") - }) - AttendanceDTO.AttendanceInfo toAttendanceInfoDto(Attendance attendance); + fun toResponse(attendance: Attendance): AttendanceResponse = + AttendanceResponse( + id = attendance.id, + status = attendance.status, + title = attendance.meeting.title, + start = attendance.meeting.start, + end = attendance.meeting.end, + location = attendance.meeting.location, + ) + fun toInfoResponse(attendance: Attendance): AttendanceInfoResponse = + AttendanceInfoResponse( + id = attendance.id, + status = attendance.status, + name = attendance.user.name, + position = attendance.user.position?.name, + department = attendance.user.department?.name, + studentId = attendance.user.studentId, + ) } diff --git a/src/test/kotlin/com/weeth/domain/attendance/application/mapper/AttendanceMapperTest.kt b/src/test/kotlin/com/weeth/domain/attendance/application/mapper/AttendanceMapperTest.kt index 563710b7..bd8cf828 100644 --- a/src/test/kotlin/com/weeth/domain/attendance/application/mapper/AttendanceMapperTest.kt +++ b/src/test/kotlin/com/weeth/domain/attendance/application/mapper/AttendanceMapperTest.kt @@ -13,22 +13,21 @@ import io.kotest.core.spec.style.DescribeSpec import io.kotest.matchers.nulls.shouldBeNull import io.kotest.matchers.nulls.shouldNotBeNull import io.kotest.matchers.shouldBe -import org.mapstruct.factory.Mappers import java.time.LocalDate class AttendanceMapperTest : DescribeSpec({ - val attendanceMapper = Mappers.getMapper(AttendanceMapper::class.java) + val mapper = AttendanceMapper() - describe("toMainDto") { - it("사용자 + 당일 출석 객체를 Main DTO로 매핑한다") { + describe("toMainResponse") { + it("사용자 + 당일 출석 객체를 MainResponse로 매핑한다") { val today = LocalDate.now() val meeting = createOneDayMeeting(today, 1, 1111, "Today") val user = createActiveUserWithAttendances("이지훈", listOf(meeting)) val attendance = user.attendances[0] - val main = attendanceMapper.toMainDto(user, attendance) + val main = mapper.toMainResponse(user, attendance) main.shouldNotBeNull() main.title shouldBe "Today" @@ -38,10 +37,10 @@ class AttendanceMapperTest : main.location shouldBe meeting.location } - it("todayAttendance가 null이면 필드는 null로 매핑") { + it("attendance가 null이면 필드는 null로 매핑") { val user = createActiveUser("이지훈") - val main = attendanceMapper.toMainDto(user, null) + val main = mapper.toMainResponse(user, null) main.shouldNotBeNull() main.title.shouldBeNull() @@ -56,7 +55,7 @@ class AttendanceMapperTest : val user = createActiveUserWithAttendances("일반유저", listOf(meeting)) val attendance = user.attendances[0] - val main = attendanceMapper.toMainDto(user, attendance) + val main = mapper.toMainResponse(user, attendance) main.shouldNotBeNull() main.code.shouldBeNull() @@ -65,13 +64,13 @@ class AttendanceMapperTest : } } - describe("toResponseDto") { - it("단일 출석을 Response DTO로 매핑한다") { + describe("toResponse") { + it("단일 출석을 AttendanceResponse로 매핑한다") { val meeting = createOneDayMeeting(LocalDate.now().minusDays(1), 1, 2222, "D-1") val user = createActiveUser("사용자A") val attendance = createAttendance(meeting, user) - val response = attendanceMapper.toResponseDto(attendance) + val response = mapper.toResponse(attendance) response.shouldNotBeNull() response.title shouldBe "D-1" @@ -81,8 +80,8 @@ class AttendanceMapperTest : } } - describe("toDetailDto") { - it("사용자 + Response 리스트를 Detail DTO로 매핑(total = attend + absence)") { + describe("toDetailResponse") { + it("사용자 + Response 리스트를 DetailResponse로 매핑(total = attend + absence)") { val base = LocalDate.now() val m1 = createOneDayMeeting(base.minusDays(2), 1, 1000, "D-2") val m2 = createOneDayMeeting(base.minusDays(1), 1, 1001, "D-1") @@ -92,10 +91,10 @@ class AttendanceMapperTest : val a1 = createAttendance(m1, user) val a2 = createAttendance(m2, user) - val r1 = attendanceMapper.toResponseDto(a1) - val r2 = attendanceMapper.toResponseDto(a2) + val r1 = mapper.toResponse(a1) + val r2 = mapper.toResponse(a2) - val detail = attendanceMapper.toDetailDto(user, listOf(r1, r2)) + val detail = mapper.toDetailResponse(user, listOf(r1, r2)) detail.shouldNotBeNull() detail.attendances shouldBe listOf(r1, r2) @@ -103,8 +102,8 @@ class AttendanceMapperTest : } } - describe("toAttendanceInfoDto") { - it("Attendance를 Info DTO로 매핑") { + describe("toInfoResponse") { + it("Attendance를 InfoResponse로 매핑") { val meeting = createOneDayMeeting(LocalDate.now(), 1, 3333, "Info") val user = createActiveUser("유저B") enrichUserProfile(user, Position.BE, "컴퓨터공학과", "20201234") @@ -112,7 +111,7 @@ class AttendanceMapperTest : val attendance = createAttendance(meeting, user) setAttendanceId(attendance, 10L) - val info = attendanceMapper.toAttendanceInfoDto(attendance) + val info = mapper.toInfoResponse(attendance) info.shouldNotBeNull() info.id shouldBe 10L @@ -129,7 +128,7 @@ class AttendanceMapperTest : val adminUser = createAdminUserWithAttendances("관리자", listOf(meeting)) val attendance = adminUser.attendances[0] - val main = attendanceMapper.toAdminResponse(adminUser, attendance) + val main = mapper.toAdminResponse(adminUser, attendance) main.shouldNotBeNull() main.code shouldBe expectedCode diff --git a/src/test/kotlin/com/weeth/domain/attendance/application/usecase/AttendanceUseCaseImplTest.kt b/src/test/kotlin/com/weeth/domain/attendance/application/usecase/AttendanceUseCaseImplTest.kt index 6f41a783..b72ab6bd 100644 --- a/src/test/kotlin/com/weeth/domain/attendance/application/usecase/AttendanceUseCaseImplTest.kt +++ b/src/test/kotlin/com/weeth/domain/attendance/application/usecase/AttendanceUseCaseImplTest.kt @@ -1,6 +1,8 @@ package com.weeth.domain.attendance.application.usecase -import com.weeth.domain.attendance.application.dto.AttendanceDTO +import com.weeth.domain.attendance.application.dto.response.AttendanceDetailResponse +import com.weeth.domain.attendance.application.dto.response.AttendanceMainResponse +import com.weeth.domain.attendance.application.dto.response.AttendanceResponse import com.weeth.domain.attendance.application.exception.AttendanceCodeMismatchException import com.weeth.domain.attendance.application.exception.AttendanceNotFoundException import com.weeth.domain.attendance.application.mapper.AttendanceMapper @@ -68,18 +70,18 @@ class AttendanceUseCaseImplTest : it.meeting.title == "Today" } - val mapped = mockk() + val mapped = mockk() every { userGetService.find(userId) } returns user - every { attendanceMapper.toMainDto(eq(user), eq(expectedTodayAttendance)) } returns mapped + every { attendanceMapper.toMainResponse(eq(user), eq(expectedTodayAttendance)) } returns mapped val actual = attendanceUseCase.find(userId) actual shouldBe mapped - verify { attendanceMapper.toMainDto(eq(user), eq(expectedTodayAttendance)) } + verify { attendanceMapper.toMainResponse(eq(user), eq(expectedTodayAttendance)) } } - it("시작/종료 날짜가 모두 오늘인 출석이 없다면 mapper.toMainDto(user, null)을 호출") { + it("시작/종료 날짜가 모두 오늘인 출석이 없다면 mapper.toMainResponse(user, null)을 호출") { val today = LocalDate.now() val yesterdayMeeting = createOneDayMeeting(today.minusDays(1), 1, 1111, "Yesterday") @@ -91,14 +93,14 @@ class AttendanceUseCaseImplTest : listOf(yesterdayMeeting, tomorrowMeeting), ) - val mapped = mockk() + val mapped = mockk() every { userGetService.find(userId) } returns user - every { attendanceMapper.toMainDto(user, null) } returns mapped + every { attendanceMapper.toMainResponse(user, null) } returns mapped val actual = attendanceUseCase.find(userId) actual shouldBe mapped - verify { attendanceMapper.toMainDto(user, null) } + verify { attendanceMapper.toMainResponse(user, null) } } } @@ -232,19 +234,19 @@ class AttendanceUseCaseImplTest : every { currentCardinal.cardinalNumber } returns 1 every { userCardinalGetService.getCurrentCardinal(user) } returns currentCardinal - val responseFirst = mockk() - val responseSecond = mockk() - every { attendanceMapper.toResponseDto(attendanceFirst) } returns responseFirst - every { attendanceMapper.toResponseDto(attendanceSecond) } returns responseSecond + val responseFirst = mockk() + val responseSecond = mockk() + every { attendanceMapper.toResponse(attendanceFirst) } returns responseFirst + every { attendanceMapper.toResponse(attendanceSecond) } returns responseSecond - val expectedDetail = mockk() - every { attendanceMapper.toDetailDto(eq(user), any()) } returns expectedDetail + val expectedDetail = mockk() + every { attendanceMapper.toDetailResponse(eq(user), any()) } returns expectedDetail val actualDetail = attendanceUseCase.findAllDetailsByCurrentCardinal(userId) actualDetail shouldBe expectedDetail verify { - attendanceMapper.toDetailDto( + attendanceMapper.toDetailResponse( eq(user), match { it.size == 2 }, ) From 259cc0e06260df0f50df22a2c5f23901c7dc4b85 Mon Sep 17 00:00:00 2001 From: soo0711 Date: Wed, 18 Feb 2026 02:38:06 +0900 Subject: [PATCH 14/45] =?UTF-8?q?refactor:=20Attendacne=20service=20java?= =?UTF-8?q?=20->=20kotlin=EC=9C=BC=EB=A1=9C=20=ED=8F=B4=EB=8D=94=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/attendance/domain/service/AttendanceDeleteService.kt} | 0 .../domain/attendance/domain/service/AttendanceGetService.kt} | 0 .../domain/attendance/domain/service/AttendanceSaveService.kt} | 0 .../domain/attendance/domain/service/AttendanceScheduler.kt} | 0 .../domain/attendance/domain/service/AttendanceUpdateService.kt} | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename src/main/{java/com/weeth/domain/attendance/domain/service/AttendanceDeleteService.java => kotlin/com/weeth/domain/attendance/domain/service/AttendanceDeleteService.kt} (100%) rename src/main/{java/com/weeth/domain/attendance/domain/service/AttendanceGetService.java => kotlin/com/weeth/domain/attendance/domain/service/AttendanceGetService.kt} (100%) rename src/main/{java/com/weeth/domain/attendance/domain/service/AttendanceSaveService.java => kotlin/com/weeth/domain/attendance/domain/service/AttendanceSaveService.kt} (100%) rename src/main/{java/com/weeth/domain/attendance/domain/service/AttendanceScheduler.java => kotlin/com/weeth/domain/attendance/domain/service/AttendanceScheduler.kt} (100%) rename src/main/{java/com/weeth/domain/attendance/domain/service/AttendanceUpdateService.java => kotlin/com/weeth/domain/attendance/domain/service/AttendanceUpdateService.kt} (100%) diff --git a/src/main/java/com/weeth/domain/attendance/domain/service/AttendanceDeleteService.java b/src/main/kotlin/com/weeth/domain/attendance/domain/service/AttendanceDeleteService.kt similarity index 100% rename from src/main/java/com/weeth/domain/attendance/domain/service/AttendanceDeleteService.java rename to src/main/kotlin/com/weeth/domain/attendance/domain/service/AttendanceDeleteService.kt diff --git a/src/main/java/com/weeth/domain/attendance/domain/service/AttendanceGetService.java b/src/main/kotlin/com/weeth/domain/attendance/domain/service/AttendanceGetService.kt similarity index 100% rename from src/main/java/com/weeth/domain/attendance/domain/service/AttendanceGetService.java rename to src/main/kotlin/com/weeth/domain/attendance/domain/service/AttendanceGetService.kt diff --git a/src/main/java/com/weeth/domain/attendance/domain/service/AttendanceSaveService.java b/src/main/kotlin/com/weeth/domain/attendance/domain/service/AttendanceSaveService.kt similarity index 100% rename from src/main/java/com/weeth/domain/attendance/domain/service/AttendanceSaveService.java rename to src/main/kotlin/com/weeth/domain/attendance/domain/service/AttendanceSaveService.kt diff --git a/src/main/java/com/weeth/domain/attendance/domain/service/AttendanceScheduler.java b/src/main/kotlin/com/weeth/domain/attendance/domain/service/AttendanceScheduler.kt similarity index 100% rename from src/main/java/com/weeth/domain/attendance/domain/service/AttendanceScheduler.java rename to src/main/kotlin/com/weeth/domain/attendance/domain/service/AttendanceScheduler.kt diff --git a/src/main/java/com/weeth/domain/attendance/domain/service/AttendanceUpdateService.java b/src/main/kotlin/com/weeth/domain/attendance/domain/service/AttendanceUpdateService.kt similarity index 100% rename from src/main/java/com/weeth/domain/attendance/domain/service/AttendanceUpdateService.java rename to src/main/kotlin/com/weeth/domain/attendance/domain/service/AttendanceUpdateService.kt From 916ea36fa5cf4668c797f525328d6600b628c831 Mon Sep 17 00:00:00 2001 From: soo0711 Date: Wed, 18 Feb 2026 02:43:48 +0900 Subject: [PATCH 15/45] =?UTF-8?q?refactor:=20Attendacne=20service=20kotlin?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=AC=B8=EB=B2=95=20=EB=B3=80=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/service/AttendanceDeleteService.kt | 24 ++++----- .../domain/service/AttendanceGetService.kt | 36 ++++++------- .../domain/service/AttendanceSaveService.kt | 50 ++++++++----------- .../domain/service/AttendanceScheduler.kt | 40 +++++++-------- .../domain/service/AttendanceUpdateService.kt | 50 ++++++++----------- 5 files changed, 84 insertions(+), 116 deletions(-) diff --git a/src/main/kotlin/com/weeth/domain/attendance/domain/service/AttendanceDeleteService.kt b/src/main/kotlin/com/weeth/domain/attendance/domain/service/AttendanceDeleteService.kt index 853c21ab..d4a05bb7 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/domain/service/AttendanceDeleteService.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/domain/service/AttendanceDeleteService.kt @@ -1,20 +1,14 @@ -package com.weeth.domain.attendance.domain.service; +package com.weeth.domain.attendance.domain.service -import com.weeth.domain.attendance.domain.entity.Attendance; -import com.weeth.domain.attendance.domain.repository.AttendanceRepository; -import com.weeth.domain.schedule.domain.entity.Meeting; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -import java.util.List; +import com.weeth.domain.attendance.domain.repository.AttendanceRepository +import com.weeth.domain.schedule.domain.entity.Meeting +import org.springframework.stereotype.Service @Service -@RequiredArgsConstructor -public class AttendanceDeleteService { - - private final AttendanceRepository attendanceRepository; - - public void deleteAll(Meeting meeting) { - attendanceRepository.deleteAllByMeeting(meeting); +class AttendanceDeleteService( + private val attendanceRepository: AttendanceRepository, +) { + fun deleteAll(meeting: Meeting) { + attendanceRepository.deleteAllByMeeting(meeting) } } diff --git a/src/main/kotlin/com/weeth/domain/attendance/domain/service/AttendanceGetService.kt b/src/main/kotlin/com/weeth/domain/attendance/domain/service/AttendanceGetService.kt index 82c6d8c1..6d275f07 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/domain/service/AttendanceGetService.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/domain/service/AttendanceGetService.kt @@ -1,26 +1,20 @@ -package com.weeth.domain.attendance.domain.service; +package com.weeth.domain.attendance.domain.service -import com.weeth.domain.attendance.application.exception.AttendanceNotFoundException; -import com.weeth.domain.attendance.domain.entity.Attendance; -import com.weeth.domain.attendance.domain.repository.AttendanceRepository; -import com.weeth.domain.schedule.domain.entity.Meeting; -import com.weeth.domain.user.domain.entity.enums.Status; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -import java.util.List; +import com.weeth.domain.attendance.application.exception.AttendanceNotFoundException +import com.weeth.domain.attendance.domain.entity.Attendance +import com.weeth.domain.attendance.domain.repository.AttendanceRepository +import com.weeth.domain.schedule.domain.entity.Meeting +import com.weeth.domain.user.domain.entity.enums.Status +import org.springframework.data.repository.findByIdOrNull +import org.springframework.stereotype.Service @Service -@RequiredArgsConstructor -public class AttendanceGetService { - - private final AttendanceRepository attendanceRepository; +class AttendanceGetService( + private val attendanceRepository: AttendanceRepository, +) { + fun findAllByMeeting(meeting: Meeting): List = attendanceRepository.findAllByMeetingAndUserStatus(meeting, Status.ACTIVE) - public List findAllByMeeting(Meeting meeting) { - return attendanceRepository.findAllByMeetingAndUserStatus(meeting, Status.ACTIVE); - } - public Attendance findByAttendanceId(Long attendanceId) { - return attendanceRepository.findById(attendanceId) - .orElseThrow(AttendanceNotFoundException::new); - } + fun findByAttendanceId(attendanceId: Long): Attendance = + attendanceRepository.findByIdOrNull(attendanceId) + ?: throw AttendanceNotFoundException() } diff --git a/src/main/kotlin/com/weeth/domain/attendance/domain/service/AttendanceSaveService.kt b/src/main/kotlin/com/weeth/domain/attendance/domain/service/AttendanceSaveService.kt index 8ad82e6b..f19106dc 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/domain/service/AttendanceSaveService.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/domain/service/AttendanceSaveService.kt @@ -1,36 +1,30 @@ -package com.weeth.domain.attendance.domain.service; +package com.weeth.domain.attendance.domain.service -import jakarta.transaction.Transactional; -import com.weeth.domain.attendance.domain.entity.Attendance; -import com.weeth.domain.attendance.domain.repository.AttendanceRepository; -import com.weeth.domain.schedule.domain.entity.Meeting; -import com.weeth.domain.user.domain.entity.User; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -import java.util.ArrayList; -import java.util.List; +import com.weeth.domain.attendance.domain.entity.Attendance +import com.weeth.domain.attendance.domain.repository.AttendanceRepository +import com.weeth.domain.schedule.domain.entity.Meeting +import com.weeth.domain.user.domain.entity.User +import org.springframework.stereotype.Service @Service -@RequiredArgsConstructor -public class AttendanceSaveService { - - private final AttendanceRepository attendanceRepository; - - public void init(User user, List meetings) { - if (meetings != null) { - meetings.forEach(meeting -> { - Attendance attendance = attendanceRepository.save(new Attendance(meeting, user)); - user.add(attendance); - }); +class AttendanceSaveService( + private val attendanceRepository: AttendanceRepository, +) { + fun init( + user: User, + meetings: List?, + ) { + meetings?.forEach { meeting -> + val attendance = attendanceRepository.save(Attendance(meeting, user)) + user.add(attendance) } } - public void saveAll(List userList, Meeting meeting) { - List attendances = userList.stream() - .map(user -> new Attendance(meeting, user)) - .toList(); - - attendanceRepository.saveAll(attendances); + fun saveAll( + userList: List, + meeting: Meeting, + ) { + val attendances = userList.map { user -> Attendance(meeting, user) } + attendanceRepository.saveAll(attendances) } } diff --git a/src/main/kotlin/com/weeth/domain/attendance/domain/service/AttendanceScheduler.kt b/src/main/kotlin/com/weeth/domain/attendance/domain/service/AttendanceScheduler.kt index 57b541e3..1cc4f2c3 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/domain/service/AttendanceScheduler.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/domain/service/AttendanceScheduler.kt @@ -1,31 +1,25 @@ -package com.weeth.domain.attendance.domain.service; +package com.weeth.domain.attendance.domain.service -import jakarta.transaction.Transactional; -import java.util.List; -import com.weeth.domain.attendance.domain.entity.Attendance; -import com.weeth.domain.schedule.domain.entity.Meeting; -import com.weeth.domain.schedule.domain.service.MeetingGetService; -import lombok.RequiredArgsConstructor; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Service; +import com.weeth.domain.schedule.domain.service.MeetingGetService +import jakarta.transaction.Transactional +import org.springframework.scheduling.annotation.Scheduled +import org.springframework.stereotype.Service @Service -@RequiredArgsConstructor -public class AttendanceScheduler { - - private final MeetingGetService meetingGetService; - private final AttendanceGetService attendanceGetService; - private final AttendanceUpdateService attendanceUpdateService; - +class AttendanceScheduler( + private val meetingGetService: MeetingGetService, + private val attendanceGetService: AttendanceGetService, + private val attendanceUpdateService: AttendanceUpdateService, +) { @Transactional @Scheduled(cron = "0 0 22 * * THU", zone = "Asia/Seoul") - public void autoCloseAttendance() { - List meetings = meetingGetService.findAllOpenMeetingsBeforeNow(); + fun autoCloseAttendance() { + val meetings = meetingGetService.findAllOpenMeetingsBeforeNow() - meetings.forEach(meeting -> { - meeting.close(); - List attendanceList = attendanceGetService.findAllByMeeting(meeting); - attendanceUpdateService.close(attendanceList); - }); + meetings.forEach { meeting -> + meeting.close() + val attendanceList = attendanceGetService.findAllByMeeting(meeting) + attendanceUpdateService.close(attendanceList) + } } } diff --git a/src/main/kotlin/com/weeth/domain/attendance/domain/service/AttendanceUpdateService.kt b/src/main/kotlin/com/weeth/domain/attendance/domain/service/AttendanceUpdateService.kt index 4bfbeb6b..4e399b16 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/domain/service/AttendanceUpdateService.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/domain/service/AttendanceUpdateService.kt @@ -1,40 +1,32 @@ -package com.weeth.domain.attendance.domain.service; +package com.weeth.domain.attendance.domain.service -import jakarta.transaction.Transactional; -import com.weeth.domain.attendance.domain.entity.Attendance; -import com.weeth.domain.attendance.domain.entity.enums.Status; -import com.weeth.domain.user.domain.entity.User; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -import java.util.List; +import com.weeth.domain.attendance.domain.entity.Attendance +import com.weeth.domain.attendance.domain.entity.enums.Status +import org.springframework.stereotype.Service @Service -@Transactional -@RequiredArgsConstructor -public class AttendanceUpdateService { - - public void attend(Attendance attendance) { - attendance.attend(); - attendance.getUser().attend(); +class AttendanceUpdateService { + fun attend(attendance: Attendance) { + attendance.attend() + attendance.user.attend() } - public void close(List attendances) { - attendances.stream() - .filter(Attendance::isPending) - .forEach(attendance -> { - attendance.close(); - attendance.getUser().absent(); - }); + fun close(attendances: List) { + attendances + .filter { it.isPending } + .forEach { attendance -> + attendance.close() + attendance.user.absent() + } } - public void updateUserAttendanceByStatus(List attendances) { - for (Attendance attendance : attendances) { - User user = attendance.getUser(); - if (attendance.getStatus().equals(Status.ATTEND)) { - user.removeAttend(); + fun updateUserAttendanceByStatus(attendances: List) { + attendances.forEach { attendance -> + val user = attendance.user + if (attendance.status == Status.ATTEND) { + user.removeAttend() } else { - user.removeAbsent(); + user.removeAbsent() } } } From cdc9b17f44c5648c7f054ac58596a7495ba349f3 Mon Sep 17 00:00:00 2001 From: soo0711 Date: Wed, 18 Feb 2026 02:44:53 +0900 Subject: [PATCH 16/45] =?UTF-8?q?refactor:=20Attendacne=20usecase=20java?= =?UTF-8?q?=20->=20kotlin=EC=9C=BC=EB=A1=9C=20=ED=8F=B4=EB=8D=94=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/attendance/application/usecase/AttendanceUseCase.kt} | 0 .../attendance/application/usecase/AttendanceUseCaseImpl.kt} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename src/main/{java/com/weeth/domain/attendance/application/usecase/AttendanceUseCase.java => kotlin/com/weeth/domain/attendance/application/usecase/AttendanceUseCase.kt} (100%) rename src/main/{java/com/weeth/domain/attendance/application/usecase/AttendanceUseCaseImpl.java => kotlin/com/weeth/domain/attendance/application/usecase/AttendanceUseCaseImpl.kt} (100%) diff --git a/src/main/java/com/weeth/domain/attendance/application/usecase/AttendanceUseCase.java b/src/main/kotlin/com/weeth/domain/attendance/application/usecase/AttendanceUseCase.kt similarity index 100% rename from src/main/java/com/weeth/domain/attendance/application/usecase/AttendanceUseCase.java rename to src/main/kotlin/com/weeth/domain/attendance/application/usecase/AttendanceUseCase.kt diff --git a/src/main/java/com/weeth/domain/attendance/application/usecase/AttendanceUseCaseImpl.java b/src/main/kotlin/com/weeth/domain/attendance/application/usecase/AttendanceUseCaseImpl.kt similarity index 100% rename from src/main/java/com/weeth/domain/attendance/application/usecase/AttendanceUseCaseImpl.java rename to src/main/kotlin/com/weeth/domain/attendance/application/usecase/AttendanceUseCaseImpl.kt From d077a775962377a1e390406f2c1349f6f9e75afd Mon Sep 17 00:00:00 2001 From: soo0711 Date: Wed, 18 Feb 2026 02:49:49 +0900 Subject: [PATCH 17/45] =?UTF-8?q?refactor:=20Attendacne=20usecase=20kotlin?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=AC=B8=EB=B2=95=20=EB=B3=80=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/usecase/AttendanceUseCase.kt | 35 +-- .../usecase/AttendanceUseCaseImpl.kt | 223 +++++++++--------- 2 files changed, 125 insertions(+), 133 deletions(-) diff --git a/src/main/kotlin/com/weeth/domain/attendance/application/usecase/AttendanceUseCase.kt b/src/main/kotlin/com/weeth/domain/attendance/application/usecase/AttendanceUseCase.kt index 49b3f272..7084cfed 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/application/usecase/AttendanceUseCase.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/application/usecase/AttendanceUseCase.kt @@ -1,24 +1,27 @@ -package com.weeth.domain.attendance.application.usecase; +package com.weeth.domain.attendance.application.usecase -import java.util.List; -import com.weeth.domain.attendance.application.dto.request.UpdateAttendanceStatusRequest; -import com.weeth.domain.attendance.application.dto.response.AttendanceDetailResponse; -import com.weeth.domain.attendance.application.dto.response.AttendanceInfoResponse; -import com.weeth.domain.attendance.application.dto.response.AttendanceMainResponse; -import com.weeth.domain.attendance.application.exception.AttendanceCodeMismatchException; +import com.weeth.domain.attendance.application.dto.request.UpdateAttendanceStatusRequest +import com.weeth.domain.attendance.application.dto.response.AttendanceDetailResponse +import com.weeth.domain.attendance.application.dto.response.AttendanceInfoResponse +import com.weeth.domain.attendance.application.dto.response.AttendanceMainResponse +import java.time.LocalDate -import java.time.LocalDate; +interface AttendanceUseCase { + fun checkIn( + userId: Long, + code: Int, + ) -public interface AttendanceUseCase { - void checkIn(Long userId, Integer code) throws AttendanceCodeMismatchException; + fun find(userId: Long): AttendanceMainResponse - AttendanceMainResponse find(Long userId); + fun findAllDetailsByCurrentCardinal(userId: Long): AttendanceDetailResponse - AttendanceDetailResponse findAllDetailsByCurrentCardinal(Long userId); + fun findAllAttendanceByMeeting(meetingId: Long): List - List findAllAttendanceByMeeting(Long meetingId); + fun close( + now: LocalDate, + cardinal: Int, + ) - void close(LocalDate now, Integer cardinal); - - void updateAttendanceStatus(List attendanceUpdates); + fun updateAttendanceStatus(attendanceUpdates: List) } diff --git a/src/main/kotlin/com/weeth/domain/attendance/application/usecase/AttendanceUseCaseImpl.kt b/src/main/kotlin/com/weeth/domain/attendance/application/usecase/AttendanceUseCaseImpl.kt index 22fda428..e379df10 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/application/usecase/AttendanceUseCaseImpl.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/application/usecase/AttendanceUseCaseImpl.kt @@ -1,143 +1,132 @@ -package com.weeth.domain.attendance.application.usecase; - -import com.weeth.domain.attendance.application.dto.request.UpdateAttendanceStatusRequest; -import com.weeth.domain.attendance.application.dto.response.AttendanceDetailResponse; -import com.weeth.domain.attendance.application.dto.response.AttendanceInfoResponse; -import com.weeth.domain.attendance.application.dto.response.AttendanceMainResponse; -import com.weeth.domain.attendance.application.dto.response.AttendanceResponse; -import com.weeth.domain.attendance.application.exception.AttendanceCodeMismatchException; -import com.weeth.domain.attendance.application.exception.AttendanceNotFoundException; -import com.weeth.domain.attendance.application.mapper.AttendanceMapper; -import com.weeth.domain.attendance.domain.entity.Attendance; -import com.weeth.domain.attendance.domain.entity.enums.Status; -import com.weeth.domain.attendance.domain.service.AttendanceGetService; -import com.weeth.domain.attendance.domain.service.AttendanceUpdateService; -import com.weeth.domain.schedule.application.exception.MeetingNotFoundException; -import com.weeth.domain.schedule.domain.entity.Meeting; -import com.weeth.domain.schedule.domain.service.MeetingGetService; -import com.weeth.domain.user.domain.entity.Cardinal; -import com.weeth.domain.user.domain.entity.User; -import com.weeth.domain.user.domain.entity.enums.Role; -import com.weeth.domain.user.domain.service.UserCardinalGetService; -import com.weeth.domain.user.domain.service.UserGetService; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.util.Comparator; -import java.util.List; +package com.weeth.domain.attendance.application.usecase + +import com.weeth.domain.attendance.application.dto.request.UpdateAttendanceStatusRequest +import com.weeth.domain.attendance.application.dto.response.AttendanceDetailResponse +import com.weeth.domain.attendance.application.dto.response.AttendanceInfoResponse +import com.weeth.domain.attendance.application.dto.response.AttendanceMainResponse +import com.weeth.domain.attendance.application.exception.AttendanceCodeMismatchException +import com.weeth.domain.attendance.application.exception.AttendanceNotFoundException +import com.weeth.domain.attendance.application.mapper.AttendanceMapper +import com.weeth.domain.attendance.domain.entity.enums.Status +import com.weeth.domain.attendance.domain.service.AttendanceGetService +import com.weeth.domain.attendance.domain.service.AttendanceUpdateService +import com.weeth.domain.schedule.application.exception.MeetingNotFoundException +import com.weeth.domain.schedule.domain.service.MeetingGetService +import com.weeth.domain.user.domain.entity.enums.Role +import com.weeth.domain.user.domain.service.UserCardinalGetService +import com.weeth.domain.user.domain.service.UserGetService +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional +import java.time.LocalDate +import java.time.LocalDateTime @Service -@RequiredArgsConstructor -public class AttendanceUseCaseImpl implements AttendanceUseCase { - - private final UserGetService userGetService; - private final UserCardinalGetService userCardinalGetService; - - private final AttendanceGetService attendanceGetService; - private final AttendanceUpdateService attendanceUpdateService; - private final AttendanceMapper mapper; - - private final MeetingGetService meetingGetService; - - @Override +class AttendanceUseCaseImpl( + private val userGetService: UserGetService, + private val userCardinalGetService: UserCardinalGetService, + private val attendanceGetService: AttendanceGetService, + private val attendanceUpdateService: AttendanceUpdateService, + private val mapper: AttendanceMapper, + private val meetingGetService: MeetingGetService, +) : AttendanceUseCase { @Transactional - public void checkIn(Long userId, Integer code) throws AttendanceCodeMismatchException { - User user = userGetService.find(userId); - - LocalDateTime now = LocalDateTime.now(); - Attendance todayMeeting = user.getAttendances().stream() - .filter(attendance -> attendance.getMeeting().getStart().minusMinutes(10).isBefore(now) - && attendance.getMeeting().getEnd().isAfter(now)) - .findAny() - .orElseThrow(AttendanceNotFoundException::new); - - if (todayMeeting.isWrong(code)) - throw new AttendanceCodeMismatchException(); + override fun checkIn( + userId: Long, + code: Int, + ) { + val user = userGetService.find(userId) + val now = LocalDateTime.now() + + val todayAttendance = + user.attendances.firstOrNull { attendance -> + attendance.meeting.start + .minusMinutes(10) + .isBefore(now) && + attendance.meeting.end.isAfter(now) + } ?: throw AttendanceNotFoundException() + + if (todayAttendance.isWrong(code)) { + throw AttendanceCodeMismatchException() + } - if (todayMeeting.getStatus() != Status.ATTEND) - attendanceUpdateService.attend(todayMeeting); + if (todayAttendance.status != Status.ATTEND) { + attendanceUpdateService.attend(todayAttendance) + } } - @Override - public AttendanceMainResponse find(Long userId) { - User user = userGetService.find(userId); - - Attendance todayMeeting = user.getAttendances().stream() - .filter(attendance -> attendance.getMeeting().getStart().toLocalDate().isEqual(LocalDate.now()) - && attendance.getMeeting().getEnd().toLocalDate().isEqual(LocalDate.now())) - .findAny() - .orElse(null); + override fun find(userId: Long): AttendanceMainResponse { + val user = userGetService.find(userId) + val today = LocalDate.now() + + val todayAttendance = + user.attendances.firstOrNull { attendance -> + attendance.meeting.start + .toLocalDate() + .isEqual(today) && + attendance.meeting.end + .toLocalDate() + .isEqual(today) + } - if (Role.ADMIN == user.getRole()) { - return mapper.toAdminResponse(user, todayMeeting); + return if (user.role == Role.ADMIN) { + mapper.toAdminResponse(user, todayAttendance) + } else { + mapper.toMainResponse(user, todayAttendance) } - - return mapper.toMainResponse(user, todayMeeting); } - public AttendanceDetailResponse findAllDetailsByCurrentCardinal(Long userId) { - User user = userGetService.find(userId); - Cardinal currentCardinal = userCardinalGetService.getCurrentCardinal(user); + override fun findAllDetailsByCurrentCardinal(userId: Long): AttendanceDetailResponse { + val user = userGetService.find(userId) + val currentCardinal = userCardinalGetService.getCurrentCardinal(user) - List responses = user.getAttendances().stream() - .filter(attendance -> attendance.getMeeting().getCardinal().equals(currentCardinal.getCardinalNumber())) - .sorted(Comparator.comparing(attendance -> attendance.getMeeting().getStart())) + val responses = + user.attendances + .filter { it.meeting.cardinal == currentCardinal.cardinalNumber } + .sortedBy { it.meeting.start } .map(mapper::toResponse) - .toList(); - return mapper.toDetailResponse(user, responses); + return mapper.toDetailResponse(user, responses) } - @Override - public List findAllAttendanceByMeeting(Long meetingId) { - Meeting meeting = meetingGetService.find(meetingId); - - List attendances = attendanceGetService.findAllByMeeting(meeting); - - return attendances.stream() - .map(mapper::toInfoResponse) - .toList(); + override fun findAllAttendanceByMeeting(meetingId: Long): List { + val meeting = meetingGetService.find(meetingId) + val attendances = attendanceGetService.findAllByMeeting(meeting) + return attendances.map(mapper::toInfoResponse) } - @Override - public void close(LocalDate now, Integer cardinal) { - List meetings = meetingGetService.find(cardinal); - - /* - todo 차후 리팩토링 정기모임 id를 입력받아서 해당 정기모임의 출석을 마감하도록 수정 - */ - Meeting targetMeeting = meetings.stream() - .filter(meeting -> meeting.getStart().toLocalDate().isEqual(now) - && meeting.getEnd().toLocalDate().isEqual(now)) - .findAny() - .orElseThrow(MeetingNotFoundException::new); - - List attendanceList = attendanceGetService.findAllByMeeting(targetMeeting); - - attendanceUpdateService.close(attendanceList); + // todo 차후 리팩토링 정기모임 id를 입력받아서 해당 정기모임의 출석을 마감하도록 수정 + override fun close( + now: LocalDate, + cardinal: Int, + ) { + val meetings = meetingGetService.find(cardinal) + + val targetMeeting = + meetings.firstOrNull { meeting -> + meeting.start.toLocalDate().isEqual(now) && + meeting.end.toLocalDate().isEqual(now) + } ?: throw MeetingNotFoundException() + + val attendanceList = attendanceGetService.findAllByMeeting(targetMeeting) + attendanceUpdateService.close(attendanceList) } - @Override @Transactional - public void updateAttendanceStatus(List attendanceUpdates) { - attendanceUpdates.forEach(update -> { - Attendance attendance = attendanceGetService.findByAttendanceId(update.getAttendanceId()); - User user = attendance.getUser(); - - Status newStatus = Status.valueOf(update.getStatus()); + override fun updateAttendanceStatus(attendanceUpdates: List) { + attendanceUpdates.forEach { update -> + val attendance = attendanceGetService.findByAttendanceId(update.attendanceId) + val user = attendance.user + val newStatus = Status.valueOf(update.status) if (newStatus == Status.ABSENT) { - attendance.close(); - user.removeAttend(); - user.absent(); + attendance.close() + user.removeAttend() + user.absent() } else { - attendance.attend(); - user.removeAbsent(); - user.attend(); + attendance.attend() + user.removeAbsent() + user.attend() } - }); + } } } From c513eafc6563d9d4dd64a98a742174234970e502 Mon Sep 17 00:00:00 2001 From: soo0711 Date: Wed, 18 Feb 2026 02:57:01 +0900 Subject: [PATCH 18/45] =?UTF-8?q?refactor:=20Attendacne=20exception=20java?= =?UTF-8?q?=20->=20kotlin=EC=9C=BC=EB=A1=9C=20=ED=8F=B4=EB=8D=94=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/exception/AttendanceCodeMismatchException.kt} | 0 .../attendance/application/exception/AttendanceErrorCode.kt} | 0 .../exception/AttendanceEventTypeNotMatchException.kt} | 0 .../application/exception/AttendanceNotFoundException.kt} | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename src/main/{java/com/weeth/domain/attendance/application/exception/AttendanceCodeMismatchException.java => kotlin/com/weeth/domain/attendance/application/exception/AttendanceCodeMismatchException.kt} (100%) rename src/main/{java/com/weeth/domain/attendance/application/exception/AttendanceErrorCode.java => kotlin/com/weeth/domain/attendance/application/exception/AttendanceErrorCode.kt} (100%) rename src/main/{java/com/weeth/domain/attendance/application/exception/AttendanceEventTypeNotMatchException.java => kotlin/com/weeth/domain/attendance/application/exception/AttendanceEventTypeNotMatchException.kt} (100%) rename src/main/{java/com/weeth/domain/attendance/application/exception/AttendanceNotFoundException.java => kotlin/com/weeth/domain/attendance/application/exception/AttendanceNotFoundException.kt} (100%) diff --git a/src/main/java/com/weeth/domain/attendance/application/exception/AttendanceCodeMismatchException.java b/src/main/kotlin/com/weeth/domain/attendance/application/exception/AttendanceCodeMismatchException.kt similarity index 100% rename from src/main/java/com/weeth/domain/attendance/application/exception/AttendanceCodeMismatchException.java rename to src/main/kotlin/com/weeth/domain/attendance/application/exception/AttendanceCodeMismatchException.kt diff --git a/src/main/java/com/weeth/domain/attendance/application/exception/AttendanceErrorCode.java b/src/main/kotlin/com/weeth/domain/attendance/application/exception/AttendanceErrorCode.kt similarity index 100% rename from src/main/java/com/weeth/domain/attendance/application/exception/AttendanceErrorCode.java rename to src/main/kotlin/com/weeth/domain/attendance/application/exception/AttendanceErrorCode.kt diff --git a/src/main/java/com/weeth/domain/attendance/application/exception/AttendanceEventTypeNotMatchException.java b/src/main/kotlin/com/weeth/domain/attendance/application/exception/AttendanceEventTypeNotMatchException.kt similarity index 100% rename from src/main/java/com/weeth/domain/attendance/application/exception/AttendanceEventTypeNotMatchException.java rename to src/main/kotlin/com/weeth/domain/attendance/application/exception/AttendanceEventTypeNotMatchException.kt diff --git a/src/main/java/com/weeth/domain/attendance/application/exception/AttendanceNotFoundException.java b/src/main/kotlin/com/weeth/domain/attendance/application/exception/AttendanceNotFoundException.kt similarity index 100% rename from src/main/java/com/weeth/domain/attendance/application/exception/AttendanceNotFoundException.java rename to src/main/kotlin/com/weeth/domain/attendance/application/exception/AttendanceNotFoundException.kt From 29b70cc75933e9d7e17eb3d83316f77be6f1bb99 Mon Sep 17 00:00:00 2001 From: soo0711 Date: Wed, 18 Feb 2026 03:16:36 +0900 Subject: [PATCH 19/45] =?UTF-8?q?refactor:=20Attendacne=20exception=20kotl?= =?UTF-8?q?in=EC=9C=BC=EB=A1=9C=20=EB=AC=B8=EB=B2=95=20=EB=B3=80=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AttendanceCodeMismatchException.kt | 10 ++----- .../exception/AttendanceErrorCode.kt | 30 ++++++++++--------- .../AttendanceEventTypeNotMatchException.kt | 10 ++----- .../exception/AttendanceNotFoundException.kt | 10 ++----- 4 files changed, 25 insertions(+), 35 deletions(-) diff --git a/src/main/kotlin/com/weeth/domain/attendance/application/exception/AttendanceCodeMismatchException.kt b/src/main/kotlin/com/weeth/domain/attendance/application/exception/AttendanceCodeMismatchException.kt index 2de9ba5f..a62d043b 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/application/exception/AttendanceCodeMismatchException.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/application/exception/AttendanceCodeMismatchException.kt @@ -1,9 +1,5 @@ -package com.weeth.domain.attendance.application.exception; +package com.weeth.domain.attendance.application.exception -import com.weeth.global.common.exception.BaseException; +import com.weeth.global.common.exception.BaseException -public class AttendanceCodeMismatchException extends BaseException { - public AttendanceCodeMismatchException() { - super(AttendanceErrorCode.ATTENDANCE_CODE_MISMATCH); - } -} +class AttendanceCodeMismatchException : BaseException(AttendanceErrorCode.ATTENDANCE_CODE_MISMATCH) diff --git a/src/main/kotlin/com/weeth/domain/attendance/application/exception/AttendanceErrorCode.kt b/src/main/kotlin/com/weeth/domain/attendance/application/exception/AttendanceErrorCode.kt index 3cd3f3b7..bba918e5 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/application/exception/AttendanceErrorCode.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/application/exception/AttendanceErrorCode.kt @@ -1,15 +1,14 @@ -package com.weeth.domain.attendance.application.exception; +package com.weeth.domain.attendance.application.exception -import com.weeth.global.common.exception.ErrorCodeInterface; -import com.weeth.global.common.exception.ExplainError; -import lombok.AllArgsConstructor; -import lombok.Getter; -import org.springframework.http.HttpStatus; - -@Getter -@AllArgsConstructor -public enum AttendanceErrorCode implements ErrorCodeInterface { +import com.weeth.global.common.exception.ErrorCodeInterface +import com.weeth.global.common.exception.ExplainError +import org.springframework.http.HttpStatus +enum class AttendanceErrorCode( + private val code: Int, + private val status: HttpStatus, + private val message: String, +) : ErrorCodeInterface { @ExplainError("출석 정보를 찾을 수 없을 때 발생합니다.") ATTENDANCE_NOT_FOUND(2200, HttpStatus.NOT_FOUND, "출석 정보가 존재하지 않습니다."), @@ -17,9 +16,12 @@ public enum AttendanceErrorCode implements ErrorCodeInterface { ATTENDANCE_CODE_MISMATCH(2201, HttpStatus.BAD_REQUEST, "출석 코드가 일치하지 않습니다."), @ExplainError("사용자가 출석 일정을 직접 수정하려고 시도할 때 발생합니다. (출석 로직 위반)") - ATTENDANCE_EVENT_TYPE_NOT_MATCH(2202, HttpStatus.BAD_REQUEST, "출석일정은 직접 수정할 수 없습니다."); + ATTENDANCE_EVENT_TYPE_NOT_MATCH(2202, HttpStatus.BAD_REQUEST, "출석일정은 직접 수정할 수 없습니다."), + ; + + override fun getCode(): Int = code + + override fun getStatus(): HttpStatus = status - private final int code; - private final HttpStatus status; - private final String message; + override fun getMessage(): String = message } diff --git a/src/main/kotlin/com/weeth/domain/attendance/application/exception/AttendanceEventTypeNotMatchException.kt b/src/main/kotlin/com/weeth/domain/attendance/application/exception/AttendanceEventTypeNotMatchException.kt index 5c978a47..e8e2716a 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/application/exception/AttendanceEventTypeNotMatchException.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/application/exception/AttendanceEventTypeNotMatchException.kt @@ -1,9 +1,5 @@ -package com.weeth.domain.attendance.application.exception; +package com.weeth.domain.attendance.application.exception -import com.weeth.global.common.exception.BaseException; +import com.weeth.global.common.exception.BaseException -public class AttendanceEventTypeNotMatchException extends BaseException { - public AttendanceEventTypeNotMatchException() { - super(AttendanceErrorCode.ATTENDANCE_EVENT_TYPE_NOT_MATCH); - } -} +class AttendanceEventTypeNotMatchException : BaseException(AttendanceErrorCode.ATTENDANCE_EVENT_TYPE_NOT_MATCH) diff --git a/src/main/kotlin/com/weeth/domain/attendance/application/exception/AttendanceNotFoundException.kt b/src/main/kotlin/com/weeth/domain/attendance/application/exception/AttendanceNotFoundException.kt index c44b0b54..95862a76 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/application/exception/AttendanceNotFoundException.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/application/exception/AttendanceNotFoundException.kt @@ -1,9 +1,5 @@ -package com.weeth.domain.attendance.application.exception; +package com.weeth.domain.attendance.application.exception -import com.weeth.global.common.exception.BaseException; +import com.weeth.global.common.exception.BaseException -public class AttendanceNotFoundException extends BaseException { - public AttendanceNotFoundException() { - super(AttendanceErrorCode.ATTENDANCE_NOT_FOUND); - } -} +class AttendanceNotFoundException : BaseException(AttendanceErrorCode.ATTENDANCE_NOT_FOUND) From aec11395f52e0d98d5c5664914a81a3edbcf9ea0 Mon Sep 17 00:00:00 2001 From: soo0711 Date: Wed, 18 Feb 2026 03:25:43 +0900 Subject: [PATCH 20/45] =?UTF-8?q?refactor:=20Attendacne=20controller,=20re?= =?UTF-8?q?sponseCode=20java=20->=20kotlin=EC=9C=BC=EB=A1=9C=20=ED=8F=B4?= =?UTF-8?q?=EB=8D=94=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/attendance/presentation/AttendanceAdminController.kt} | 0 .../weeth/domain/attendance/presentation/AttendanceController.kt} | 0 .../domain/attendance/presentation/AttendanceResponseCode.kt} | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename src/main/{java/com/weeth/domain/attendance/presentation/AttendanceAdminController.java => kotlin/com/weeth/domain/attendance/presentation/AttendanceAdminController.kt} (100%) rename src/main/{java/com/weeth/domain/attendance/presentation/AttendanceController.java => kotlin/com/weeth/domain/attendance/presentation/AttendanceController.kt} (100%) rename src/main/{java/com/weeth/domain/attendance/presentation/AttendanceResponseCode.java => kotlin/com/weeth/domain/attendance/presentation/AttendanceResponseCode.kt} (100%) diff --git a/src/main/java/com/weeth/domain/attendance/presentation/AttendanceAdminController.java b/src/main/kotlin/com/weeth/domain/attendance/presentation/AttendanceAdminController.kt similarity index 100% rename from src/main/java/com/weeth/domain/attendance/presentation/AttendanceAdminController.java rename to src/main/kotlin/com/weeth/domain/attendance/presentation/AttendanceAdminController.kt diff --git a/src/main/java/com/weeth/domain/attendance/presentation/AttendanceController.java b/src/main/kotlin/com/weeth/domain/attendance/presentation/AttendanceController.kt similarity index 100% rename from src/main/java/com/weeth/domain/attendance/presentation/AttendanceController.java rename to src/main/kotlin/com/weeth/domain/attendance/presentation/AttendanceController.kt diff --git a/src/main/java/com/weeth/domain/attendance/presentation/AttendanceResponseCode.java b/src/main/kotlin/com/weeth/domain/attendance/presentation/AttendanceResponseCode.kt similarity index 100% rename from src/main/java/com/weeth/domain/attendance/presentation/AttendanceResponseCode.java rename to src/main/kotlin/com/weeth/domain/attendance/presentation/AttendanceResponseCode.kt From 94218d27e1c499bb00396c9c87ae93cea4126b99 Mon Sep 17 00:00:00 2001 From: soo0711 Date: Wed, 18 Feb 2026 03:27:41 +0900 Subject: [PATCH 21/45] =?UTF-8?q?refactor:=20Attendacne=20controller,=20re?= =?UTF-8?q?sponseCode=20kotlin=EC=9C=BC=EB=A1=9C=20=EB=AC=B8=EB=B2=95=20?= =?UTF-8?q?=EB=B3=80=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/AttendanceAdminController.kt | 89 ++++++++++--------- .../presentation/AttendanceController.kt | 78 ++++++++-------- .../presentation/AttendanceResponseCode.kt | 30 +++---- 3 files changed, 101 insertions(+), 96 deletions(-) diff --git a/src/main/kotlin/com/weeth/domain/attendance/presentation/AttendanceAdminController.kt b/src/main/kotlin/com/weeth/domain/attendance/presentation/AttendanceAdminController.kt index 6ddc9cbb..14baa11e 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/presentation/AttendanceAdminController.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/presentation/AttendanceAdminController.kt @@ -1,59 +1,68 @@ -package com.weeth.domain.attendance.presentation; +package com.weeth.domain.attendance.presentation -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; -import com.weeth.domain.attendance.application.dto.request.UpdateAttendanceStatusRequest; -import com.weeth.domain.attendance.application.dto.response.AttendanceInfoResponse; -import com.weeth.domain.attendance.application.exception.AttendanceErrorCode; -import com.weeth.domain.attendance.application.usecase.AttendanceUseCase; -import com.weeth.domain.schedule.application.dto.MeetingDTO; -import com.weeth.domain.schedule.application.usecase.MeetingUseCase; -import com.weeth.global.common.exception.ApiErrorCodeExample; -import com.weeth.global.common.response.CommonResponse; -import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.*; - -import java.time.LocalDate; -import java.util.List; - -import static com.weeth.domain.attendance.presentation.AttendanceResponseCode.*; +import com.weeth.domain.attendance.application.dto.request.UpdateAttendanceStatusRequest +import com.weeth.domain.attendance.application.dto.response.AttendanceInfoResponse +import com.weeth.domain.attendance.application.exception.AttendanceErrorCode +import com.weeth.domain.attendance.application.usecase.AttendanceUseCase +import com.weeth.domain.schedule.application.dto.MeetingDTO +import com.weeth.domain.schedule.application.usecase.MeetingUseCase +import com.weeth.global.common.exception.ApiErrorCodeExample +import com.weeth.global.common.response.CommonResponse +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.tags.Tag +import jakarta.validation.Valid +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PatchMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestParam +import org.springframework.web.bind.annotation.RestController +import java.time.LocalDate @Tag(name = "ATTENDANCE ADMIN", description = "[ADMIN] 출석 어드민 API") @RestController -@RequiredArgsConstructor @RequestMapping("/api/v1/admin/attendances") -@ApiErrorCodeExample(AttendanceErrorCode.class) -public class AttendanceAdminController { - - private final AttendanceUseCase attendanceUseCase; - private final MeetingUseCase meetingUseCase; - +@ApiErrorCodeExample(AttendanceErrorCode::class) +class AttendanceAdminController( + private val attendanceUseCase: AttendanceUseCase, + private val meetingUseCase: MeetingUseCase, +) { @PatchMapping - @Operation(summary="출석 마감") - public CommonResponse close(@RequestParam LocalDate now, @RequestParam Integer cardinal) { - attendanceUseCase.close(now, cardinal); - return CommonResponse.success(ATTENDANCE_CLOSE_SUCCESS); + @Operation(summary = "출석 마감") + fun close( + @RequestParam now: LocalDate, + @RequestParam cardinal: Int, + ): CommonResponse { + attendanceUseCase.close(now, cardinal) + return CommonResponse.success(AttendanceResponseCode.ATTENDANCE_CLOSE_SUCCESS) } @GetMapping("/meetings") @Operation(summary = "정기모임 조회") - public CommonResponse getMeetings(@RequestParam(required = false) Integer cardinal) { - MeetingDTO.Infos response = meetingUseCase.find(cardinal); - - return CommonResponse.success(MEETING_FIND_SUCCESS, response); + fun getMeetings( + @RequestParam(required = false) cardinal: Int?, + ): CommonResponse { + val response = meetingUseCase.find(cardinal) + return CommonResponse.success(AttendanceResponseCode.MEETING_FIND_SUCCESS, response) } @GetMapping("/{meetingId}") @Operation(summary = "모든 인원 정기모임 출석 정보 조회") - public CommonResponse> getAllAttendance(@PathVariable Long meetingId) { - return CommonResponse.success(ATTENDANCE_FIND_DETAIL_SUCCESS, attendanceUseCase.findAllAttendanceByMeeting(meetingId)); - } + fun getAllAttendance( + @PathVariable meetingId: Long, + ): CommonResponse> = + CommonResponse.success( + AttendanceResponseCode.ATTENDANCE_FIND_DETAIL_SUCCESS, + attendanceUseCase.findAllAttendanceByMeeting(meetingId), + ) @PatchMapping("/status") @Operation(summary = "모든 인원 정기모임 개별 출석 상태 수정") - public CommonResponse updateAttendanceStatus(@RequestBody @Valid List attendanceUpdates) { - attendanceUseCase.updateAttendanceStatus(attendanceUpdates); - return CommonResponse.success(ATTENDANCE_UPDATED_SUCCESS); + fun updateAttendanceStatus( + @RequestBody @Valid attendanceUpdates: List, + ): CommonResponse { + attendanceUseCase.updateAttendanceStatus(attendanceUpdates) + return CommonResponse.success(AttendanceResponseCode.ATTENDANCE_UPDATED_SUCCESS) } } diff --git a/src/main/kotlin/com/weeth/domain/attendance/presentation/AttendanceController.kt b/src/main/kotlin/com/weeth/domain/attendance/presentation/AttendanceController.kt index 32198f6a..09c3ab21 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/presentation/AttendanceController.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/presentation/AttendanceController.kt @@ -1,49 +1,53 @@ -package com.weeth.domain.attendance.presentation; +package com.weeth.domain.attendance.presentation -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.tags.Tag; -import com.weeth.domain.attendance.application.dto.request.CheckInRequest; -import com.weeth.domain.attendance.application.dto.response.AttendanceDetailResponse; -import com.weeth.domain.attendance.application.dto.response.AttendanceMainResponse; -import com.weeth.domain.attendance.application.exception.AttendanceErrorCode; -import com.weeth.domain.attendance.application.usecase.AttendanceUseCase; -import com.weeth.global.auth.annotation.CurrentUser; -import com.weeth.domain.attendance.application.exception.AttendanceCodeMismatchException; -import com.weeth.global.common.exception.ApiErrorCodeExample; -import com.weeth.global.common.response.CommonResponse; -import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.*; - -import static com.weeth.domain.attendance.presentation.AttendanceResponseCode.ATTENDANCE_CHECKIN_SUCCESS; -import static com.weeth.domain.attendance.presentation.AttendanceResponseCode.ATTENDANCE_FIND_ALL_SUCCESS; -import static com.weeth.domain.attendance.presentation.AttendanceResponseCode.ATTENDANCE_FIND_SUCCESS; +import com.weeth.domain.attendance.application.dto.request.CheckInRequest +import com.weeth.domain.attendance.application.dto.response.AttendanceDetailResponse +import com.weeth.domain.attendance.application.dto.response.AttendanceMainResponse +import com.weeth.domain.attendance.application.exception.AttendanceErrorCode +import com.weeth.domain.attendance.application.usecase.AttendanceUseCase +import com.weeth.global.auth.annotation.CurrentUser +import com.weeth.global.common.exception.ApiErrorCodeExample +import com.weeth.global.common.response.CommonResponse +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.Parameter +import io.swagger.v3.oas.annotations.tags.Tag +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PatchMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController @Tag(name = "ATTENDANCE", description = "출석 API") @RestController -@RequiredArgsConstructor @RequestMapping("/api/v1/attendances") -@ApiErrorCodeExample(AttendanceErrorCode.class) -public class AttendanceController { - - private final AttendanceUseCase attendanceUseCase; - +@ApiErrorCodeExample(AttendanceErrorCode::class) +class AttendanceController( + private val attendanceUseCase: AttendanceUseCase, +) { @PatchMapping - @Operation(summary="출석체크") - public CommonResponse checkIn(@Parameter(hidden = true) @CurrentUser Long userId, @RequestBody CheckInRequest checkIn) throws AttendanceCodeMismatchException { - attendanceUseCase.checkIn(userId, checkIn.getCode()); - return CommonResponse.success(ATTENDANCE_CHECKIN_SUCCESS); + @Operation(summary = "출석체크") + fun checkIn( + @Parameter(hidden = true) @CurrentUser userId: Long, + @RequestBody checkIn: CheckInRequest, + ): CommonResponse { + attendanceUseCase.checkIn(userId, checkIn.code) + return CommonResponse.success(AttendanceResponseCode.ATTENDANCE_CHECKIN_SUCCESS) } @GetMapping - @Operation(summary="출석 메인페이지") - public CommonResponse find(@Parameter(hidden = true) @CurrentUser Long userId) { - return CommonResponse.success(ATTENDANCE_FIND_SUCCESS, attendanceUseCase.find(userId)); - } + @Operation(summary = "출석 메인페이지") + fun find( + @Parameter(hidden = true) @CurrentUser userId: Long, + ): CommonResponse = + CommonResponse.success(AttendanceResponseCode.ATTENDANCE_FIND_SUCCESS, attendanceUseCase.find(userId)) @GetMapping("/detail") - @Operation(summary="출석 내역 상세조회") - public CommonResponse findAll(@Parameter(hidden = true) @CurrentUser Long userId) { - return CommonResponse.success(ATTENDANCE_FIND_ALL_SUCCESS, attendanceUseCase.findAllDetailsByCurrentCardinal(userId)); - } + @Operation(summary = "출석 내역 상세조회") + fun findAll( + @Parameter(hidden = true) @CurrentUser userId: Long, + ): CommonResponse = + CommonResponse.success( + AttendanceResponseCode.ATTENDANCE_FIND_ALL_SUCCESS, + attendanceUseCase.findAllDetailsByCurrentCardinal(userId), + ) } diff --git a/src/main/kotlin/com/weeth/domain/attendance/presentation/AttendanceResponseCode.kt b/src/main/kotlin/com/weeth/domain/attendance/presentation/AttendanceResponseCode.kt index d3dd0068..54ad6348 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/presentation/AttendanceResponseCode.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/presentation/AttendanceResponseCode.kt @@ -1,29 +1,21 @@ -package com.weeth.domain.attendance.presentation; +package com.weeth.domain.attendance.presentation -import com.weeth.global.common.response.ResponseCodeInterface; -import lombok.Getter; -import org.springframework.http.HttpStatus; +import com.weeth.global.common.response.ResponseCodeInterface +import org.springframework.http.HttpStatus -@Getter -public enum AttendanceResponseCode implements ResponseCodeInterface { - //AttendanceAdminController 관련 +enum class AttendanceResponseCode( + override val code: Int, + override val status: HttpStatus, + override val message: String, +) : ResponseCodeInterface { + // AttendanceAdminController 관련 ATTENDANCE_CLOSE_SUCCESS(1200, HttpStatus.OK, "출석이 성공적으로 마감되었습니다."), ATTENDANCE_UPDATED_SUCCESS(1201, HttpStatus.OK, "개별 출석 상태가 성공적으로 수정되었습니다."), ATTENDANCE_FIND_DETAIL_SUCCESS(1202, HttpStatus.OK, "모든 인원의 정기모임 출석 정보가 성공적으로 조회되었습니다."), MEETING_FIND_SUCCESS(1203, HttpStatus.OK, "기수별 정기모임 리스트를 성공적으로 조회했습니다."), - //AttendanceController 관련 + // AttendanceController 관련 ATTENDANCE_CHECKIN_SUCCESS(1204, HttpStatus.OK, "출석이 성공적으로 처리되었습니다."), ATTENDANCE_FIND_SUCCESS(1205, HttpStatus.OK, "사용자의 출석 정보가 성공적으로 조회되었습니다."), - ATTENDANCE_FIND_ALL_SUCCESS(1206, HttpStatus.OK, "사용자의 상세 출석 정보가 성공적으로 조회되었습니다."); - - private final int code; - private final HttpStatus status; - private final String message; - - AttendanceResponseCode(int code, HttpStatus status, String message) { - this.code = code; - this.status = status; - this.message = message; - } + ATTENDANCE_FIND_ALL_SUCCESS(1206, HttpStatus.OK, "사용자의 상세 출석 정보가 성공적으로 조회되었습니다."), } From d07cea9b508adca6b10a40b8d2216a8a03e6cac0 Mon Sep 17 00:00:00 2001 From: soo0711 Date: Wed, 18 Feb 2026 03:43:26 +0900 Subject: [PATCH 22/45] =?UTF-8?q?refactor:=20AttendanceScheduler=EB=A5=BC?= =?UTF-8?q?=20service/scheduler=20=ED=8C=A8=ED=82=A4=EC=A7=80=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/service/AttendanceScheduler.kt | 25 --------------- .../service/scheduler/AttendanceScheduler.kt | 31 +++++++++++++++++++ 2 files changed, 31 insertions(+), 25 deletions(-) delete mode 100644 src/main/kotlin/com/weeth/domain/attendance/domain/service/AttendanceScheduler.kt create mode 100644 src/main/kotlin/com/weeth/domain/attendance/domain/service/scheduler/AttendanceScheduler.kt diff --git a/src/main/kotlin/com/weeth/domain/attendance/domain/service/AttendanceScheduler.kt b/src/main/kotlin/com/weeth/domain/attendance/domain/service/AttendanceScheduler.kt deleted file mode 100644 index 1cc4f2c3..00000000 --- a/src/main/kotlin/com/weeth/domain/attendance/domain/service/AttendanceScheduler.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.weeth.domain.attendance.domain.service - -import com.weeth.domain.schedule.domain.service.MeetingGetService -import jakarta.transaction.Transactional -import org.springframework.scheduling.annotation.Scheduled -import org.springframework.stereotype.Service - -@Service -class AttendanceScheduler( - private val meetingGetService: MeetingGetService, - private val attendanceGetService: AttendanceGetService, - private val attendanceUpdateService: AttendanceUpdateService, -) { - @Transactional - @Scheduled(cron = "0 0 22 * * THU", zone = "Asia/Seoul") - fun autoCloseAttendance() { - val meetings = meetingGetService.findAllOpenMeetingsBeforeNow() - - meetings.forEach { meeting -> - meeting.close() - val attendanceList = attendanceGetService.findAllByMeeting(meeting) - attendanceUpdateService.close(attendanceList) - } - } -} diff --git a/src/main/kotlin/com/weeth/domain/attendance/domain/service/scheduler/AttendanceScheduler.kt b/src/main/kotlin/com/weeth/domain/attendance/domain/service/scheduler/AttendanceScheduler.kt new file mode 100644 index 00000000..a4d71a3c --- /dev/null +++ b/src/main/kotlin/com/weeth/domain/attendance/domain/service/scheduler/AttendanceScheduler.kt @@ -0,0 +1,31 @@ +package com.weeth.domain.attendance.domain.service.scheduler + +import com.weeth.domain.attendance.domain.repository.AttendanceRepository +import com.weeth.domain.schedule.domain.service.MeetingGetService +import com.weeth.domain.user.domain.entity.enums.Status +import jakarta.transaction.Transactional +import org.springframework.scheduling.annotation.Scheduled +import org.springframework.stereotype.Service + +@Service +class AttendanceScheduler( + private val meetingGetService: MeetingGetService, + private val attendanceRepository: AttendanceRepository, +) { + @Transactional + @Scheduled(cron = "0 0 22 * * THU", zone = "Asia/Seoul") + fun autoCloseAttendance() { + val meetings = meetingGetService.findAllOpenMeetingsBeforeNow() + + meetings.forEach { meeting -> + meeting.close() + val attendanceList = attendanceRepository.findAllByMeetingAndUserStatus(meeting, Status.ACTIVE) + attendanceList + .filter { it.isPending } + .forEach { attendance -> + attendance.close() + attendance.user.absent() + } + } + } +} From 0e6357f7d289d3b0ec210753b9d8aad373020f8a Mon Sep 17 00:00:00 2001 From: soo0711 Date: Wed, 18 Feb 2026 03:52:43 +0900 Subject: [PATCH 23/45] =?UTF-8?q?refactor:=20Attendance=20UseCase=20comman?= =?UTF-8?q?d/query=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/usecase/AttendanceUseCase.kt | 27 ---- .../usecase/AttendanceUseCaseImpl.kt | 132 ------------------ .../command/CheckInAttendanceUseCase.kt | 40 ++++++ .../usecase/command/CloseAttendanceUseCase.kt | 37 +++++ .../command/UpdateAttendanceStatusUseCase.kt | 35 +++++ .../query/GetAttendanceQueryService.kt | 65 +++++++++ 6 files changed, 177 insertions(+), 159 deletions(-) delete mode 100644 src/main/kotlin/com/weeth/domain/attendance/application/usecase/AttendanceUseCase.kt delete mode 100644 src/main/kotlin/com/weeth/domain/attendance/application/usecase/AttendanceUseCaseImpl.kt create mode 100644 src/main/kotlin/com/weeth/domain/attendance/application/usecase/command/CheckInAttendanceUseCase.kt create mode 100644 src/main/kotlin/com/weeth/domain/attendance/application/usecase/command/CloseAttendanceUseCase.kt create mode 100644 src/main/kotlin/com/weeth/domain/attendance/application/usecase/command/UpdateAttendanceStatusUseCase.kt create mode 100644 src/main/kotlin/com/weeth/domain/attendance/application/usecase/query/GetAttendanceQueryService.kt diff --git a/src/main/kotlin/com/weeth/domain/attendance/application/usecase/AttendanceUseCase.kt b/src/main/kotlin/com/weeth/domain/attendance/application/usecase/AttendanceUseCase.kt deleted file mode 100644 index 7084cfed..00000000 --- a/src/main/kotlin/com/weeth/domain/attendance/application/usecase/AttendanceUseCase.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.weeth.domain.attendance.application.usecase - -import com.weeth.domain.attendance.application.dto.request.UpdateAttendanceStatusRequest -import com.weeth.domain.attendance.application.dto.response.AttendanceDetailResponse -import com.weeth.domain.attendance.application.dto.response.AttendanceInfoResponse -import com.weeth.domain.attendance.application.dto.response.AttendanceMainResponse -import java.time.LocalDate - -interface AttendanceUseCase { - fun checkIn( - userId: Long, - code: Int, - ) - - fun find(userId: Long): AttendanceMainResponse - - fun findAllDetailsByCurrentCardinal(userId: Long): AttendanceDetailResponse - - fun findAllAttendanceByMeeting(meetingId: Long): List - - fun close( - now: LocalDate, - cardinal: Int, - ) - - fun updateAttendanceStatus(attendanceUpdates: List) -} diff --git a/src/main/kotlin/com/weeth/domain/attendance/application/usecase/AttendanceUseCaseImpl.kt b/src/main/kotlin/com/weeth/domain/attendance/application/usecase/AttendanceUseCaseImpl.kt deleted file mode 100644 index e379df10..00000000 --- a/src/main/kotlin/com/weeth/domain/attendance/application/usecase/AttendanceUseCaseImpl.kt +++ /dev/null @@ -1,132 +0,0 @@ -package com.weeth.domain.attendance.application.usecase - -import com.weeth.domain.attendance.application.dto.request.UpdateAttendanceStatusRequest -import com.weeth.domain.attendance.application.dto.response.AttendanceDetailResponse -import com.weeth.domain.attendance.application.dto.response.AttendanceInfoResponse -import com.weeth.domain.attendance.application.dto.response.AttendanceMainResponse -import com.weeth.domain.attendance.application.exception.AttendanceCodeMismatchException -import com.weeth.domain.attendance.application.exception.AttendanceNotFoundException -import com.weeth.domain.attendance.application.mapper.AttendanceMapper -import com.weeth.domain.attendance.domain.entity.enums.Status -import com.weeth.domain.attendance.domain.service.AttendanceGetService -import com.weeth.domain.attendance.domain.service.AttendanceUpdateService -import com.weeth.domain.schedule.application.exception.MeetingNotFoundException -import com.weeth.domain.schedule.domain.service.MeetingGetService -import com.weeth.domain.user.domain.entity.enums.Role -import com.weeth.domain.user.domain.service.UserCardinalGetService -import com.weeth.domain.user.domain.service.UserGetService -import org.springframework.stereotype.Service -import org.springframework.transaction.annotation.Transactional -import java.time.LocalDate -import java.time.LocalDateTime - -@Service -class AttendanceUseCaseImpl( - private val userGetService: UserGetService, - private val userCardinalGetService: UserCardinalGetService, - private val attendanceGetService: AttendanceGetService, - private val attendanceUpdateService: AttendanceUpdateService, - private val mapper: AttendanceMapper, - private val meetingGetService: MeetingGetService, -) : AttendanceUseCase { - @Transactional - override fun checkIn( - userId: Long, - code: Int, - ) { - val user = userGetService.find(userId) - val now = LocalDateTime.now() - - val todayAttendance = - user.attendances.firstOrNull { attendance -> - attendance.meeting.start - .minusMinutes(10) - .isBefore(now) && - attendance.meeting.end.isAfter(now) - } ?: throw AttendanceNotFoundException() - - if (todayAttendance.isWrong(code)) { - throw AttendanceCodeMismatchException() - } - - if (todayAttendance.status != Status.ATTEND) { - attendanceUpdateService.attend(todayAttendance) - } - } - - override fun find(userId: Long): AttendanceMainResponse { - val user = userGetService.find(userId) - val today = LocalDate.now() - - val todayAttendance = - user.attendances.firstOrNull { attendance -> - attendance.meeting.start - .toLocalDate() - .isEqual(today) && - attendance.meeting.end - .toLocalDate() - .isEqual(today) - } - - return if (user.role == Role.ADMIN) { - mapper.toAdminResponse(user, todayAttendance) - } else { - mapper.toMainResponse(user, todayAttendance) - } - } - - override fun findAllDetailsByCurrentCardinal(userId: Long): AttendanceDetailResponse { - val user = userGetService.find(userId) - val currentCardinal = userCardinalGetService.getCurrentCardinal(user) - - val responses = - user.attendances - .filter { it.meeting.cardinal == currentCardinal.cardinalNumber } - .sortedBy { it.meeting.start } - .map(mapper::toResponse) - - return mapper.toDetailResponse(user, responses) - } - - override fun findAllAttendanceByMeeting(meetingId: Long): List { - val meeting = meetingGetService.find(meetingId) - val attendances = attendanceGetService.findAllByMeeting(meeting) - return attendances.map(mapper::toInfoResponse) - } - - // todo 차후 리팩토링 정기모임 id를 입력받아서 해당 정기모임의 출석을 마감하도록 수정 - override fun close( - now: LocalDate, - cardinal: Int, - ) { - val meetings = meetingGetService.find(cardinal) - - val targetMeeting = - meetings.firstOrNull { meeting -> - meeting.start.toLocalDate().isEqual(now) && - meeting.end.toLocalDate().isEqual(now) - } ?: throw MeetingNotFoundException() - - val attendanceList = attendanceGetService.findAllByMeeting(targetMeeting) - attendanceUpdateService.close(attendanceList) - } - - @Transactional - override fun updateAttendanceStatus(attendanceUpdates: List) { - attendanceUpdates.forEach { update -> - val attendance = attendanceGetService.findByAttendanceId(update.attendanceId) - val user = attendance.user - val newStatus = Status.valueOf(update.status) - - if (newStatus == Status.ABSENT) { - attendance.close() - user.removeAttend() - user.absent() - } else { - attendance.attend() - user.removeAbsent() - user.attend() - } - } - } -} diff --git a/src/main/kotlin/com/weeth/domain/attendance/application/usecase/command/CheckInAttendanceUseCase.kt b/src/main/kotlin/com/weeth/domain/attendance/application/usecase/command/CheckInAttendanceUseCase.kt new file mode 100644 index 00000000..72d07a3b --- /dev/null +++ b/src/main/kotlin/com/weeth/domain/attendance/application/usecase/command/CheckInAttendanceUseCase.kt @@ -0,0 +1,40 @@ +package com.weeth.domain.attendance.application.usecase.command + +import com.weeth.domain.attendance.application.exception.AttendanceCodeMismatchException +import com.weeth.domain.attendance.application.exception.AttendanceNotFoundException +import com.weeth.domain.attendance.domain.entity.enums.Status +import com.weeth.domain.user.domain.service.UserGetService +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional +import java.time.LocalDateTime + +@Service +class CheckInAttendanceUseCase( + private val userGetService: UserGetService, +) { + @Transactional + fun execute( + userId: Long, + code: Int, + ) { + val user = userGetService.find(userId) + val now = LocalDateTime.now() + + val todayAttendance = + user.attendances.firstOrNull { attendance -> + attendance.meeting.start + .minusMinutes(10) + .isBefore(now) && + attendance.meeting.end.isAfter(now) + } ?: throw AttendanceNotFoundException() + + if (todayAttendance.isWrong(code)) { + throw AttendanceCodeMismatchException() + } + + if (todayAttendance.status != Status.ATTEND) { + todayAttendance.attend() + user.attend() + } + } +} diff --git a/src/main/kotlin/com/weeth/domain/attendance/application/usecase/command/CloseAttendanceUseCase.kt b/src/main/kotlin/com/weeth/domain/attendance/application/usecase/command/CloseAttendanceUseCase.kt new file mode 100644 index 00000000..df76ea3c --- /dev/null +++ b/src/main/kotlin/com/weeth/domain/attendance/application/usecase/command/CloseAttendanceUseCase.kt @@ -0,0 +1,37 @@ +package com.weeth.domain.attendance.application.usecase.command + +import com.weeth.domain.attendance.domain.repository.AttendanceRepository +import com.weeth.domain.schedule.application.exception.MeetingNotFoundException +import com.weeth.domain.schedule.domain.service.MeetingGetService +import com.weeth.domain.user.domain.entity.enums.Status +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional +import java.time.LocalDate + +@Service +class CloseAttendanceUseCase( + private val meetingGetService: MeetingGetService, + private val attendanceRepository: AttendanceRepository, +) { + @Transactional + fun execute( + now: LocalDate, + cardinal: Int, + ) { + val meetings = meetingGetService.find(cardinal) + + val targetMeeting = + meetings.firstOrNull { meeting -> + meeting.start.toLocalDate().isEqual(now) && + meeting.end.toLocalDate().isEqual(now) + } ?: throw MeetingNotFoundException() + + val attendanceList = attendanceRepository.findAllByMeetingAndUserStatus(targetMeeting, Status.ACTIVE) + attendanceList + .filter { it.isPending } + .forEach { attendance -> + attendance.close() + attendance.user.absent() + } + } +} diff --git a/src/main/kotlin/com/weeth/domain/attendance/application/usecase/command/UpdateAttendanceStatusUseCase.kt b/src/main/kotlin/com/weeth/domain/attendance/application/usecase/command/UpdateAttendanceStatusUseCase.kt new file mode 100644 index 00000000..401614f0 --- /dev/null +++ b/src/main/kotlin/com/weeth/domain/attendance/application/usecase/command/UpdateAttendanceStatusUseCase.kt @@ -0,0 +1,35 @@ +package com.weeth.domain.attendance.application.usecase.command + +import com.weeth.domain.attendance.application.dto.request.UpdateAttendanceStatusRequest +import com.weeth.domain.attendance.application.exception.AttendanceNotFoundException +import com.weeth.domain.attendance.domain.entity.enums.Status +import com.weeth.domain.attendance.domain.repository.AttendanceRepository +import org.springframework.data.repository.findByIdOrNull +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +@Service +class UpdateAttendanceStatusUseCase( + private val attendanceRepository: AttendanceRepository, +) { + @Transactional + fun execute(attendanceUpdates: List) { + attendanceUpdates.forEach { update -> + val attendance = + attendanceRepository.findByIdOrNull(update.attendanceId) + ?: throw AttendanceNotFoundException() + val user = attendance.user + val newStatus = Status.valueOf(update.status) + + if (newStatus == Status.ABSENT) { + attendance.close() + user.removeAttend() + user.absent() + } else { + attendance.attend() + user.removeAbsent() + user.attend() + } + } + } +} diff --git a/src/main/kotlin/com/weeth/domain/attendance/application/usecase/query/GetAttendanceQueryService.kt b/src/main/kotlin/com/weeth/domain/attendance/application/usecase/query/GetAttendanceQueryService.kt new file mode 100644 index 00000000..9cd1d786 --- /dev/null +++ b/src/main/kotlin/com/weeth/domain/attendance/application/usecase/query/GetAttendanceQueryService.kt @@ -0,0 +1,65 @@ +package com.weeth.domain.attendance.application.usecase.query + +import com.weeth.domain.attendance.application.dto.response.AttendanceDetailResponse +import com.weeth.domain.attendance.application.dto.response.AttendanceInfoResponse +import com.weeth.domain.attendance.application.dto.response.AttendanceMainResponse +import com.weeth.domain.attendance.application.mapper.AttendanceMapper +import com.weeth.domain.attendance.domain.repository.AttendanceRepository +import com.weeth.domain.schedule.domain.service.MeetingGetService +import com.weeth.domain.user.domain.entity.enums.Role +import com.weeth.domain.user.domain.entity.enums.Status +import com.weeth.domain.user.domain.service.UserCardinalGetService +import com.weeth.domain.user.domain.service.UserGetService +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional +import java.time.LocalDate + +@Service +@Transactional(readOnly = true) +class GetAttendanceQueryService( + private val userGetService: UserGetService, + private val userCardinalGetService: UserCardinalGetService, + private val meetingGetService: MeetingGetService, + private val attendanceRepository: AttendanceRepository, + private val mapper: AttendanceMapper, +) { + fun find(userId: Long): AttendanceMainResponse { + val user = userGetService.find(userId) + val today = LocalDate.now() + + val todayAttendance = + user.attendances.firstOrNull { attendance -> + attendance.meeting.start + .toLocalDate() + .isEqual(today) && + attendance.meeting.end + .toLocalDate() + .isEqual(today) + } + + return if (user.role == Role.ADMIN) { + mapper.toAdminResponse(user, todayAttendance) + } else { + mapper.toMainResponse(user, todayAttendance) + } + } + + fun findAllDetailsByCurrentCardinal(userId: Long): AttendanceDetailResponse { + val user = userGetService.find(userId) + val currentCardinal = userCardinalGetService.getCurrentCardinal(user) + + val responses = + user.attendances + .filter { it.meeting.cardinal == currentCardinal.cardinalNumber } + .sortedBy { it.meeting.start } + .map(mapper::toResponse) + + return mapper.toDetailResponse(user, responses) + } + + fun findAllAttendanceByMeeting(meetingId: Long): List { + val meeting = meetingGetService.find(meetingId) + val attendances = attendanceRepository.findAllByMeetingAndUserStatus(meeting, Status.ACTIVE) + return attendances.map(mapper::toInfoResponse) + } +} From 9c168e120250b1ba51321b57f5f438a48104acc1 Mon Sep 17 00:00:00 2001 From: soo0711 Date: Wed, 18 Feb 2026 03:53:46 +0900 Subject: [PATCH 24/45] =?UTF-8?q?test:=20Attendance=20command/query=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC=EC=97=90=20=EB=94=B0=EB=A5=B8=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=9E=AC=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../usecase/AttendanceUseCaseImplTest.kt | 289 ------------------ .../command/CheckInAttendanceUseCaseTest.kt | 145 +++++++++ .../command/CloseAttendanceUseCaseTest.kt | 61 ++++ .../UpdateAttendanceStatusUseCaseTest.kt | 69 +++++ .../query/GetAttendanceQueryServiceTest.kt | 154 ++++++++++ .../service/AttendanceUpdateServiceTest.kt | 81 ----- 6 files changed, 429 insertions(+), 370 deletions(-) delete mode 100644 src/test/kotlin/com/weeth/domain/attendance/application/usecase/AttendanceUseCaseImplTest.kt create mode 100644 src/test/kotlin/com/weeth/domain/attendance/application/usecase/command/CheckInAttendanceUseCaseTest.kt create mode 100644 src/test/kotlin/com/weeth/domain/attendance/application/usecase/command/CloseAttendanceUseCaseTest.kt create mode 100644 src/test/kotlin/com/weeth/domain/attendance/application/usecase/command/UpdateAttendanceStatusUseCaseTest.kt create mode 100644 src/test/kotlin/com/weeth/domain/attendance/application/usecase/query/GetAttendanceQueryServiceTest.kt delete mode 100644 src/test/kotlin/com/weeth/domain/attendance/domain/service/AttendanceUpdateServiceTest.kt diff --git a/src/test/kotlin/com/weeth/domain/attendance/application/usecase/AttendanceUseCaseImplTest.kt b/src/test/kotlin/com/weeth/domain/attendance/application/usecase/AttendanceUseCaseImplTest.kt deleted file mode 100644 index b72ab6bd..00000000 --- a/src/test/kotlin/com/weeth/domain/attendance/application/usecase/AttendanceUseCaseImplTest.kt +++ /dev/null @@ -1,289 +0,0 @@ -package com.weeth.domain.attendance.application.usecase - -import com.weeth.domain.attendance.application.dto.response.AttendanceDetailResponse -import com.weeth.domain.attendance.application.dto.response.AttendanceMainResponse -import com.weeth.domain.attendance.application.dto.response.AttendanceResponse -import com.weeth.domain.attendance.application.exception.AttendanceCodeMismatchException -import com.weeth.domain.attendance.application.exception.AttendanceNotFoundException -import com.weeth.domain.attendance.application.mapper.AttendanceMapper -import com.weeth.domain.attendance.domain.entity.Attendance -import com.weeth.domain.attendance.domain.entity.enums.Status -import com.weeth.domain.attendance.domain.service.AttendanceGetService -import com.weeth.domain.attendance.domain.service.AttendanceUpdateService -import com.weeth.domain.attendance.fixture.AttendanceTestFixture.createActiveUserWithAttendances -import com.weeth.domain.attendance.fixture.AttendanceTestFixture.createInProgressMeeting -import com.weeth.domain.attendance.fixture.AttendanceTestFixture.createOneDayMeeting -import com.weeth.domain.schedule.application.exception.MeetingNotFoundException -import com.weeth.domain.schedule.domain.entity.Meeting -import com.weeth.domain.schedule.domain.service.MeetingGetService -import com.weeth.domain.user.domain.entity.Cardinal -import com.weeth.domain.user.domain.entity.User -import com.weeth.domain.user.domain.service.UserCardinalGetService -import com.weeth.domain.user.domain.service.UserGetService -import io.kotest.assertions.throwables.shouldNotThrowAny -import io.kotest.assertions.throwables.shouldThrow -import io.kotest.core.spec.style.DescribeSpec -import io.kotest.matchers.shouldBe -import io.mockk.every -import io.mockk.mockk -import io.mockk.verify -import java.time.LocalDate -import java.time.LocalDateTime - -class AttendanceUseCaseImplTest : - DescribeSpec({ - - val userId = 10L - val userGetService = mockk() - val userCardinalGetService = mockk() - val attendanceGetService = mockk() - val attendanceUpdateService = mockk(relaxUnitFun = true) - val attendanceMapper = mockk() - val meetingGetService = mockk() - - val attendanceUseCase = - AttendanceUseCaseImpl( - userGetService, - userCardinalGetService, - attendanceGetService, - attendanceUpdateService, - attendanceMapper, - meetingGetService, - ) - - describe("find") { - it("여러 날짜의 출석 목록 중 시작/종료 날짜가 모두 오늘인 출석정보를 선택") { - val today = LocalDate.now() - - val meetingYesterday = createOneDayMeeting(today.minusDays(1), 1, 1111, "Yesterday") - val meetingToday = createOneDayMeeting(today, 1, 2222, "Today") - val meetingTomorrow = createOneDayMeeting(today.plusDays(1), 1, 3333, "Tomorrow") - - val user = - createActiveUserWithAttendances( - "이지훈", - listOf(meetingYesterday, meetingToday, meetingTomorrow), - ) - - val expectedTodayAttendance = - user.attendances.first { - it.meeting.title == "Today" - } - - val mapped = mockk() - - every { userGetService.find(userId) } returns user - every { attendanceMapper.toMainResponse(eq(user), eq(expectedTodayAttendance)) } returns mapped - - val actual = attendanceUseCase.find(userId) - - actual shouldBe mapped - verify { attendanceMapper.toMainResponse(eq(user), eq(expectedTodayAttendance)) } - } - - it("시작/종료 날짜가 모두 오늘인 출석이 없다면 mapper.toMainResponse(user, null)을 호출") { - val today = LocalDate.now() - - val yesterdayMeeting = createOneDayMeeting(today.minusDays(1), 1, 1111, "Yesterday") - val tomorrowMeeting = createOneDayMeeting(today.plusDays(1), 1, 3333, "Tomorrow") - - val user = - createActiveUserWithAttendances( - "이지훈", - listOf(yesterdayMeeting, tomorrowMeeting), - ) - - val mapped = mockk() - every { userGetService.find(userId) } returns user - every { attendanceMapper.toMainResponse(user, null) } returns mapped - - val actual = attendanceUseCase.find(userId) - - actual shouldBe mapped - verify { attendanceMapper.toMainResponse(user, null) } - } - } - - describe("checkIn") { - context("10분 전부터 출석이 가능한지 확인") { - it("5분 뒤 시작 회의에 출석 성공") { - val now = LocalDateTime.now() - val meeting = - Meeting - .builder() - .start(now.plusMinutes(5)) - .end(now.plusHours(2)) - .code(1234) - .title("Today") - .cardinal(1) - .build() - - val user = createActiveUserWithAttendances("이지훈", listOf(meeting)) - - every { userGetService.find(userId) } returns user - - shouldNotThrowAny { - attendanceUseCase.checkIn(userId, 1234) - } - verify(exactly = 1) { attendanceUpdateService.attend(any()) } - } - - it("11분 전에 출석시 오류 발생") { - val now = LocalDateTime.now() - val meeting = - Meeting - .builder() - .start(now.plusMinutes(11)) - .end(now.plusHours(2)) - .code(1234) - .title("Today") - .cardinal(1) - .build() - - val user = createActiveUserWithAttendances("이지훈", listOf(meeting)) - - every { userGetService.find(userId) } returns user - - shouldThrow { - attendanceUseCase.checkIn(userId, 1234) - } - } - } - - context("진행 중 정기모임이고 코드 일치하며 상태가 ATTEND가 아닐 때") { - it("출석 처리된다") { - val user = mockk() - val inProgressMeeting = createInProgressMeeting(1, 1234, "InProgress") - val attendance = mockk() - every { attendance.meeting } returns inProgressMeeting - every { attendance.isWrong(1234) } returns false - every { attendance.status } returns Status.PENDING - - every { userGetService.find(userId) } returns user - every { user.attendances } returns listOf(attendance) - - attendanceUseCase.checkIn(userId, 1234) - - verify { attendanceUpdateService.attend(attendance) } - } - } - - context("진행 중 정기모임이 없을 때") { - it("AttendanceNotFoundException") { - val user = mockk() - every { userGetService.find(userId) } returns user - every { user.attendances } returns listOf() - - shouldThrow { - attendanceUseCase.checkIn(userId, 1234) - } - } - } - - context("코드 불일치 시") { - it("AttendanceCodeMismatchException") { - val user = mockk() - val inProgressMeeting = createInProgressMeeting(1, 1234, "InProgress") - - val attendance = mockk() - every { attendance.meeting } returns inProgressMeeting - every { attendance.isWrong(9999) } returns true - - every { userGetService.find(userId) } returns user - every { user.attendances } returns listOf(attendance) - - shouldThrow { - attendanceUseCase.checkIn(userId, 9999) - } - } - } - - context("이미 ATTEND일 때") { - it("추가 처리 없이 종료") { - val user = mockk() - val inProgressMeeting = createInProgressMeeting(1, 1234, "InProgress") - - val attendance = mockk() - every { attendance.meeting } returns inProgressMeeting - every { attendance.isWrong(1234) } returns false - every { attendance.status } returns Status.ATTEND - - every { userGetService.find(userId) } returns user - every { user.attendances } returns listOf(attendance) - - attendanceUseCase.checkIn(userId, 1234) - - verify(exactly = 0) { attendanceUpdateService.attend(attendance) } - } - } - } - - describe("findAllDetailsByCurrentCardinal") { - it("현재 기수만 필터링·정렬하여 Detail 매핑") { - val today = LocalDate.now() - val meetingDayMinus1 = createOneDayMeeting(today.minusDays(1), 1, 1111, "D-1") - val meetingToday = createOneDayMeeting(today, 1, 2222, "D-Day") - val user = createActiveUserWithAttendances("이지훈", listOf(meetingDayMinus1, meetingToday)) - - val userAttendances = user.attendances - val attendanceFirst = userAttendances[0] - val attendanceSecond = userAttendances[1] - - every { userGetService.find(userId) } returns user - val currentCardinal = mockk() - every { currentCardinal.cardinalNumber } returns 1 - every { userCardinalGetService.getCurrentCardinal(user) } returns currentCardinal - - val responseFirst = mockk() - val responseSecond = mockk() - every { attendanceMapper.toResponse(attendanceFirst) } returns responseFirst - every { attendanceMapper.toResponse(attendanceSecond) } returns responseSecond - - val expectedDetail = mockk() - every { attendanceMapper.toDetailResponse(eq(user), any()) } returns expectedDetail - - val actualDetail = attendanceUseCase.findAllDetailsByCurrentCardinal(userId) - - actualDetail shouldBe expectedDetail - verify { - attendanceMapper.toDetailResponse( - eq(user), - match { it.size == 2 }, - ) - } - } - } - - describe("close") { - it("당일 정기모임을 찾아 close") { - val now = LocalDate.now() - val targetMeeting = createOneDayMeeting(now, 1, 1111, "Today") - val otherMeeting = createOneDayMeeting(now.minusDays(1), 1, 9999, "Yesterday") - - val attendance1 = mockk() - val attendance2 = mockk() - - every { meetingGetService.find(1) } returns listOf(targetMeeting, otherMeeting) - every { attendanceGetService.findAllByMeeting(targetMeeting) } returns listOf(attendance1, attendance2) - - attendanceUseCase.close(now, 1) - - verify { - attendanceUpdateService.close( - match { it.size == 2 && it.containsAll(listOf(attendance1, attendance2)) }, - ) - } - } - - it("당일 정기모임이 없으면 MeetingNotFoundException") { - val now = LocalDate.now() - val otherDayMeeting = createOneDayMeeting(now.minusDays(1), 1, 9999, "Yesterday") - - every { meetingGetService.find(1) } returns listOf(otherDayMeeting) - - shouldThrow { - attendanceUseCase.close(now, 1) - } - } - } - }) diff --git a/src/test/kotlin/com/weeth/domain/attendance/application/usecase/command/CheckInAttendanceUseCaseTest.kt b/src/test/kotlin/com/weeth/domain/attendance/application/usecase/command/CheckInAttendanceUseCaseTest.kt new file mode 100644 index 00000000..438630e9 --- /dev/null +++ b/src/test/kotlin/com/weeth/domain/attendance/application/usecase/command/CheckInAttendanceUseCaseTest.kt @@ -0,0 +1,145 @@ +package com.weeth.domain.attendance.application.usecase.command + +import com.weeth.domain.attendance.application.exception.AttendanceCodeMismatchException +import com.weeth.domain.attendance.application.exception.AttendanceNotFoundException +import com.weeth.domain.attendance.domain.entity.Attendance +import com.weeth.domain.attendance.domain.entity.enums.Status +import com.weeth.domain.attendance.fixture.AttendanceTestFixture.createActiveUserWithAttendances +import com.weeth.domain.attendance.fixture.AttendanceTestFixture.createInProgressMeeting +import com.weeth.domain.attendance.fixture.AttendanceTestFixture.setUserAttendanceStats +import com.weeth.domain.schedule.domain.entity.Meeting +import com.weeth.domain.user.domain.entity.User +import com.weeth.domain.user.domain.service.UserGetService +import io.kotest.assertions.throwables.shouldNotThrowAny +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.core.spec.style.DescribeSpec +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import java.time.LocalDateTime + +class CheckInAttendanceUseCaseTest : + DescribeSpec({ + + val userId = 10L + val userGetService = mockk() + + val useCase = CheckInAttendanceUseCase(userGetService) + + describe("execute") { + context("10분 전부터 출석이 가능한지 확인") { + it("5분 뒤 시작 회의에 출석 성공") { + val now = LocalDateTime.now() + val meeting = + Meeting + .builder() + .start(now.plusMinutes(5)) + .end(now.plusHours(2)) + .code(1234) + .title("Today") + .cardinal(1) + .build() + + val user = createActiveUserWithAttendances("이지훈", listOf(meeting)) + setUserAttendanceStats(user, 0, 0) + + every { userGetService.find(userId) } returns user + + shouldNotThrowAny { + useCase.execute(userId, 1234) + } + } + + it("11분 전에 출석시 오류 발생") { + val now = LocalDateTime.now() + val meeting = + Meeting + .builder() + .start(now.plusMinutes(11)) + .end(now.plusHours(2)) + .code(1234) + .title("Today") + .cardinal(1) + .build() + + val user = createActiveUserWithAttendances("이지훈", listOf(meeting)) + + every { userGetService.find(userId) } returns user + + shouldThrow { + useCase.execute(userId, 1234) + } + } + } + + context("진행 중 정기모임이고 코드 일치하며 상태가 ATTEND가 아닐 때") { + it("출석 처리된다") { + val user = mockk() + val inProgressMeeting = createInProgressMeeting(1, 1234, "InProgress") + val attendance = mockk(relaxUnitFun = true) + every { attendance.meeting } returns inProgressMeeting + every { attendance.isWrong(1234) } returns false + every { attendance.status } returns Status.PENDING + + every { userGetService.find(userId) } returns user + every { user.attendances } returns listOf(attendance) + every { user.attend() } returns Unit + + useCase.execute(userId, 1234) + + verify { attendance.attend() } + verify { user.attend() } + } + } + + context("진행 중 정기모임이 없을 때") { + it("AttendanceNotFoundException") { + val user = mockk() + every { userGetService.find(userId) } returns user + every { user.attendances } returns listOf() + + shouldThrow { + useCase.execute(userId, 1234) + } + } + } + + context("코드 불일치 시") { + it("AttendanceCodeMismatchException") { + val user = mockk() + val inProgressMeeting = createInProgressMeeting(1, 1234, "InProgress") + + val attendance = mockk() + every { attendance.meeting } returns inProgressMeeting + every { attendance.isWrong(9999) } returns true + + every { userGetService.find(userId) } returns user + every { user.attendances } returns listOf(attendance) + + shouldThrow { + useCase.execute(userId, 9999) + } + } + } + + context("이미 ATTEND일 때") { + it("추가 처리 없이 종료") { + val user = mockk() + val inProgressMeeting = createInProgressMeeting(1, 1234, "InProgress") + + val attendance = mockk() + every { attendance.meeting } returns inProgressMeeting + every { attendance.isWrong(1234) } returns false + every { attendance.status } returns Status.ATTEND + + every { userGetService.find(userId) } returns user + every { user.attendances } returns listOf(attendance) + + useCase.execute(userId, 1234) + + verify(exactly = 0) { attendance.attend() } + verify(exactly = 0) { user.attend() } + } + } + } + }) diff --git a/src/test/kotlin/com/weeth/domain/attendance/application/usecase/command/CloseAttendanceUseCaseTest.kt b/src/test/kotlin/com/weeth/domain/attendance/application/usecase/command/CloseAttendanceUseCaseTest.kt new file mode 100644 index 00000000..f41db7b4 --- /dev/null +++ b/src/test/kotlin/com/weeth/domain/attendance/application/usecase/command/CloseAttendanceUseCaseTest.kt @@ -0,0 +1,61 @@ +package com.weeth.domain.attendance.application.usecase.command + +import com.weeth.domain.attendance.domain.entity.Attendance +import com.weeth.domain.attendance.domain.repository.AttendanceRepository +import com.weeth.domain.attendance.fixture.AttendanceTestFixture.createOneDayMeeting +import com.weeth.domain.schedule.application.exception.MeetingNotFoundException +import com.weeth.domain.schedule.domain.service.MeetingGetService +import com.weeth.domain.user.domain.entity.enums.Status +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.core.spec.style.DescribeSpec +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import java.time.LocalDate + +class CloseAttendanceUseCaseTest : + DescribeSpec({ + + val meetingGetService = mockk() + val attendanceRepository = mockk() + + val useCase = CloseAttendanceUseCase(meetingGetService, attendanceRepository) + + describe("execute") { + it("당일 정기모임을 찾아 pending 출석을 close") { + val now = LocalDate.now() + val targetMeeting = createOneDayMeeting(now, 1, 1111, "Today") + val otherMeeting = createOneDayMeeting(now.minusDays(1), 1, 9999, "Yesterday") + + val pendingAttendance = mockk(relaxUnitFun = true) + val attendedAttendance = mockk(relaxUnitFun = true) + val pendingUser = mockk(relaxUnitFun = true) + + every { pendingAttendance.isPending } returns true + every { pendingAttendance.user } returns pendingUser + every { attendedAttendance.isPending } returns false + + every { meetingGetService.find(1) } returns listOf(targetMeeting, otherMeeting) + every { + attendanceRepository.findAllByMeetingAndUserStatus(targetMeeting, Status.ACTIVE) + } returns listOf(pendingAttendance, attendedAttendance) + + useCase.execute(now, 1) + + verify { pendingAttendance.close() } + verify { pendingUser.absent() } + verify(exactly = 0) { attendedAttendance.close() } + } + + it("당일 정기모임이 없으면 MeetingNotFoundException") { + val now = LocalDate.now() + val otherDayMeeting = createOneDayMeeting(now.minusDays(1), 1, 9999, "Yesterday") + + every { meetingGetService.find(1) } returns listOf(otherDayMeeting) + + shouldThrow { + useCase.execute(now, 1) + } + } + } + }) diff --git a/src/test/kotlin/com/weeth/domain/attendance/application/usecase/command/UpdateAttendanceStatusUseCaseTest.kt b/src/test/kotlin/com/weeth/domain/attendance/application/usecase/command/UpdateAttendanceStatusUseCaseTest.kt new file mode 100644 index 00000000..1b242e57 --- /dev/null +++ b/src/test/kotlin/com/weeth/domain/attendance/application/usecase/command/UpdateAttendanceStatusUseCaseTest.kt @@ -0,0 +1,69 @@ +package com.weeth.domain.attendance.application.usecase.command + +import com.weeth.domain.attendance.application.dto.request.UpdateAttendanceStatusRequest +import com.weeth.domain.attendance.application.exception.AttendanceNotFoundException +import com.weeth.domain.attendance.domain.entity.Attendance +import com.weeth.domain.attendance.domain.repository.AttendanceRepository +import com.weeth.domain.user.domain.entity.User +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.core.spec.style.DescribeSpec +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import org.springframework.data.repository.findByIdOrNull + +class UpdateAttendanceStatusUseCaseTest : + DescribeSpec({ + + val attendanceRepository = mockk() + + val useCase = UpdateAttendanceStatusUseCase(attendanceRepository) + + describe("execute") { + context("ABSENT로 변경 시") { + it("close + removeAttend + absent 호출") { + val user = mockk(relaxUnitFun = true) + val attendance = mockk(relaxUnitFun = true) + every { attendance.user } returns user + + every { attendanceRepository.findByIdOrNull(1L) } returns attendance + + val request = UpdateAttendanceStatusRequest(attendanceId = 1L, status = "ABSENT") + useCase.execute(listOf(request)) + + verify { attendance.close() } + verify { user.removeAttend() } + verify { user.absent() } + } + } + + context("ATTEND로 변경 시") { + it("attend + removeAbsent + attend 호출") { + val user = mockk(relaxUnitFun = true) + val attendance = mockk(relaxUnitFun = true) + every { attendance.user } returns user + + every { attendanceRepository.findByIdOrNull(1L) } returns attendance + + val request = UpdateAttendanceStatusRequest(attendanceId = 1L, status = "ATTEND") + useCase.execute(listOf(request)) + + verify { attendance.attend() } + verify { user.removeAbsent() } + verify { user.attend() } + } + } + + context("출석 정보가 없을 때") { + it("AttendanceNotFoundException") { + every { attendanceRepository.findByIdOrNull(999L) } returns null + + val request = UpdateAttendanceStatusRequest(attendanceId = 999L, status = "ABSENT") + + shouldThrow { + useCase.execute(listOf(request)) + } + } + } + } + }) diff --git a/src/test/kotlin/com/weeth/domain/attendance/application/usecase/query/GetAttendanceQueryServiceTest.kt b/src/test/kotlin/com/weeth/domain/attendance/application/usecase/query/GetAttendanceQueryServiceTest.kt new file mode 100644 index 00000000..8f4ebd63 --- /dev/null +++ b/src/test/kotlin/com/weeth/domain/attendance/application/usecase/query/GetAttendanceQueryServiceTest.kt @@ -0,0 +1,154 @@ +package com.weeth.domain.attendance.application.usecase.query + +import com.weeth.domain.attendance.application.dto.response.AttendanceDetailResponse +import com.weeth.domain.attendance.application.dto.response.AttendanceInfoResponse +import com.weeth.domain.attendance.application.dto.response.AttendanceMainResponse +import com.weeth.domain.attendance.application.dto.response.AttendanceResponse +import com.weeth.domain.attendance.application.mapper.AttendanceMapper +import com.weeth.domain.attendance.domain.repository.AttendanceRepository +import com.weeth.domain.attendance.fixture.AttendanceTestFixture.createActiveUserWithAttendances +import com.weeth.domain.attendance.fixture.AttendanceTestFixture.createOneDayMeeting +import com.weeth.domain.schedule.domain.entity.Meeting +import com.weeth.domain.schedule.domain.service.MeetingGetService +import com.weeth.domain.user.domain.entity.Cardinal +import com.weeth.domain.user.domain.entity.enums.Status +import com.weeth.domain.user.domain.service.UserCardinalGetService +import com.weeth.domain.user.domain.service.UserGetService +import io.kotest.core.spec.style.DescribeSpec +import io.kotest.matchers.shouldBe +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import java.time.LocalDate + +class GetAttendanceQueryServiceTest : + DescribeSpec({ + + val userGetService = mockk() + val userCardinalGetService = mockk() + val meetingGetService = mockk() + val attendanceRepository = mockk() + val attendanceMapper = mockk() + + val queryService = + GetAttendanceQueryService( + userGetService, + userCardinalGetService, + meetingGetService, + attendanceRepository, + attendanceMapper, + ) + + val userId = 10L + + describe("find") { + it("여러 날짜의 출석 목록 중 시작/종료 날짜가 모두 오늘인 출석정보를 선택") { + val today = LocalDate.now() + + val meetingYesterday = createOneDayMeeting(today.minusDays(1), 1, 1111, "Yesterday") + val meetingToday = createOneDayMeeting(today, 1, 2222, "Today") + val meetingTomorrow = createOneDayMeeting(today.plusDays(1), 1, 3333, "Tomorrow") + + val user = + createActiveUserWithAttendances( + "이지훈", + listOf(meetingYesterday, meetingToday, meetingTomorrow), + ) + + val expectedTodayAttendance = + user.attendances.first { + it.meeting.title == "Today" + } + + val mapped = mockk() + + every { userGetService.find(userId) } returns user + every { attendanceMapper.toMainResponse(eq(user), eq(expectedTodayAttendance)) } returns mapped + + val actual = queryService.find(userId) + + actual shouldBe mapped + verify { attendanceMapper.toMainResponse(eq(user), eq(expectedTodayAttendance)) } + } + + it("시작/종료 날짜가 모두 오늘인 출석이 없다면 mapper.toMainResponse(user, null)을 호출") { + val today = LocalDate.now() + + val yesterdayMeeting = createOneDayMeeting(today.minusDays(1), 1, 1111, "Yesterday") + val tomorrowMeeting = createOneDayMeeting(today.plusDays(1), 1, 3333, "Tomorrow") + + val user = + createActiveUserWithAttendances( + "이지훈", + listOf(yesterdayMeeting, tomorrowMeeting), + ) + + val mapped = mockk() + every { userGetService.find(userId) } returns user + every { attendanceMapper.toMainResponse(user, null) } returns mapped + + val actual = queryService.find(userId) + + actual shouldBe mapped + verify { attendanceMapper.toMainResponse(user, null) } + } + } + + describe("findAllDetailsByCurrentCardinal") { + it("현재 기수만 필터링·정렬하여 Detail 매핑") { + val today = LocalDate.now() + val meetingDayMinus1 = createOneDayMeeting(today.minusDays(1), 1, 1111, "D-1") + val meetingToday = createOneDayMeeting(today, 1, 2222, "D-Day") + val user = createActiveUserWithAttendances("이지훈", listOf(meetingDayMinus1, meetingToday)) + + val userAttendances = user.attendances + val attendanceFirst = userAttendances[0] + val attendanceSecond = userAttendances[1] + + every { userGetService.find(userId) } returns user + val currentCardinal = mockk() + every { currentCardinal.cardinalNumber } returns 1 + every { userCardinalGetService.getCurrentCardinal(user) } returns currentCardinal + + val responseFirst = mockk() + val responseSecond = mockk() + every { attendanceMapper.toResponse(attendanceFirst) } returns responseFirst + every { attendanceMapper.toResponse(attendanceSecond) } returns responseSecond + + val expectedDetail = mockk() + every { attendanceMapper.toDetailResponse(eq(user), any()) } returns expectedDetail + + val actualDetail = queryService.findAllDetailsByCurrentCardinal(userId) + + actualDetail shouldBe expectedDetail + verify { + attendanceMapper.toDetailResponse( + eq(user), + match { it.size == 2 }, + ) + } + } + } + + describe("findAllAttendanceByMeeting") { + it("해당 정기모임의 출석 정보를 조회") { + val meetingId = 1L + val meeting = mockk() + val attendance1 = mockk() + val attendance2 = mockk() + val response1 = mockk() + val response2 = mockk() + + every { meetingGetService.find(meetingId) } returns meeting + every { + attendanceRepository.findAllByMeetingAndUserStatus(meeting, Status.ACTIVE) + } returns listOf(attendance1, attendance2) + every { attendanceMapper.toInfoResponse(attendance1) } returns response1 + every { attendanceMapper.toInfoResponse(attendance2) } returns response2 + + val result = queryService.findAllAttendanceByMeeting(meetingId) + + result shouldBe listOf(response1, response2) + } + } + }) diff --git a/src/test/kotlin/com/weeth/domain/attendance/domain/service/AttendanceUpdateServiceTest.kt b/src/test/kotlin/com/weeth/domain/attendance/domain/service/AttendanceUpdateServiceTest.kt deleted file mode 100644 index 13ac8305..00000000 --- a/src/test/kotlin/com/weeth/domain/attendance/domain/service/AttendanceUpdateServiceTest.kt +++ /dev/null @@ -1,81 +0,0 @@ -package com.weeth.domain.attendance.domain.service - -import com.weeth.domain.attendance.domain.entity.Attendance -import com.weeth.domain.attendance.domain.entity.enums.Status -import com.weeth.domain.attendance.fixture.AttendanceTestFixture.createActiveUser -import com.weeth.domain.attendance.fixture.AttendanceTestFixture.createAttendance -import com.weeth.domain.schedule.fixture.ScheduleTestFixture.createMeeting -import io.kotest.core.spec.style.DescribeSpec -import io.mockk.every -import io.mockk.spyk -import io.mockk.verify - -class AttendanceUpdateServiceTest : - DescribeSpec({ - - val attendanceUpdateService = AttendanceUpdateService() - - describe("attend") { - it("attendance.attend() + user.attend()을 호출한다") { - val meeting = createMeeting() - val userSpy = spyk(createActiveUser("이지훈")) - every { userSpy.attend() } returns Unit - - val attendanceSpy = spyk(createAttendance(meeting, userSpy)) - - attendanceUpdateService.attend(attendanceSpy) - - verify { attendanceSpy.attend() } - verify { userSpy.attend() } - } - } - - describe("close") { - it("pending만 close() + user.absent()을 호출한다") { - val meeting = createMeeting() - - val pendingUserSpy = spyk(createActiveUser("pending-user")) - val nonPendingUserSpy = spyk(createActiveUser("non-pending-user")) - every { pendingUserSpy.absent() } returns Unit - every { nonPendingUserSpy.absent() } returns Unit - - val pendingAttendanceSpy = spyk(createAttendance(meeting, pendingUserSpy)) - val nonPendingAttendanceSpy = spyk(createAttendance(meeting, nonPendingUserSpy)) - every { pendingAttendanceSpy.isPending } returns true - every { nonPendingAttendanceSpy.isPending } returns false - - attendanceUpdateService.close(listOf(pendingAttendanceSpy, nonPendingAttendanceSpy)) - - verify { pendingAttendanceSpy.close() } - verify { pendingUserSpy.absent() } - - verify(exactly = 0) { nonPendingAttendanceSpy.close() } - verify(exactly = 0) { nonPendingUserSpy.absent() } - } - } - - describe("updateUserAttendanceByStatus") { - it("ATTEND면 user.removeAttend(), 그 외에는 user.removeAbsent()") { - val meeting = createMeeting() - - val attendUserSpy = spyk(createActiveUser("attend-user")) - val absentUserSpy = spyk(createActiveUser("absent-user")) - every { attendUserSpy.removeAttend() } returns Unit - every { absentUserSpy.removeAbsent() } returns Unit - - val attendAttendanceSpy = spyk(createAttendance(meeting, attendUserSpy)) - val absentAttendanceSpy = spyk(createAttendance(meeting, absentUserSpy)) - every { attendAttendanceSpy.status } returns Status.ATTEND - every { absentAttendanceSpy.status } returns Status.ABSENT - every { attendAttendanceSpy.user } returns attendUserSpy - every { absentAttendanceSpy.user } returns absentUserSpy - - attendanceUpdateService.updateUserAttendanceByStatus( - listOf(attendAttendanceSpy, absentAttendanceSpy), - ) - - verify { attendUserSpy.removeAttend() } - verify { absentUserSpy.removeAbsent() } - } - } - }) From ba54feec86437db2eb8ef2f2c5255e1af6136f10 Mon Sep 17 00:00:00 2001 From: soo0711 Date: Wed, 18 Feb 2026 03:54:46 +0900 Subject: [PATCH 25/45] =?UTF-8?q?refactor:=20Attendance=20controller=20Use?= =?UTF-8?q?Case=20=EC=9D=98=EC=A1=B4=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/AttendanceAdminController.kt | 14 +++++++++----- .../presentation/AttendanceController.kt | 12 +++++++----- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/main/kotlin/com/weeth/domain/attendance/presentation/AttendanceAdminController.kt b/src/main/kotlin/com/weeth/domain/attendance/presentation/AttendanceAdminController.kt index 14baa11e..145658da 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/presentation/AttendanceAdminController.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/presentation/AttendanceAdminController.kt @@ -3,7 +3,9 @@ package com.weeth.domain.attendance.presentation import com.weeth.domain.attendance.application.dto.request.UpdateAttendanceStatusRequest import com.weeth.domain.attendance.application.dto.response.AttendanceInfoResponse import com.weeth.domain.attendance.application.exception.AttendanceErrorCode -import com.weeth.domain.attendance.application.usecase.AttendanceUseCase +import com.weeth.domain.attendance.application.usecase.command.CloseAttendanceUseCase +import com.weeth.domain.attendance.application.usecase.command.UpdateAttendanceStatusUseCase +import com.weeth.domain.attendance.application.usecase.query.GetAttendanceQueryService import com.weeth.domain.schedule.application.dto.MeetingDTO import com.weeth.domain.schedule.application.usecase.MeetingUseCase import com.weeth.global.common.exception.ApiErrorCodeExample @@ -25,7 +27,9 @@ import java.time.LocalDate @RequestMapping("/api/v1/admin/attendances") @ApiErrorCodeExample(AttendanceErrorCode::class) class AttendanceAdminController( - private val attendanceUseCase: AttendanceUseCase, + private val closeAttendanceUseCase: CloseAttendanceUseCase, + private val updateAttendanceStatusUseCase: UpdateAttendanceStatusUseCase, + private val getAttendanceQueryService: GetAttendanceQueryService, private val meetingUseCase: MeetingUseCase, ) { @PatchMapping @@ -34,7 +38,7 @@ class AttendanceAdminController( @RequestParam now: LocalDate, @RequestParam cardinal: Int, ): CommonResponse { - attendanceUseCase.close(now, cardinal) + closeAttendanceUseCase.execute(now, cardinal) return CommonResponse.success(AttendanceResponseCode.ATTENDANCE_CLOSE_SUCCESS) } @@ -54,7 +58,7 @@ class AttendanceAdminController( ): CommonResponse> = CommonResponse.success( AttendanceResponseCode.ATTENDANCE_FIND_DETAIL_SUCCESS, - attendanceUseCase.findAllAttendanceByMeeting(meetingId), + getAttendanceQueryService.findAllAttendanceByMeeting(meetingId), ) @PatchMapping("/status") @@ -62,7 +66,7 @@ class AttendanceAdminController( fun updateAttendanceStatus( @RequestBody @Valid attendanceUpdates: List, ): CommonResponse { - attendanceUseCase.updateAttendanceStatus(attendanceUpdates) + updateAttendanceStatusUseCase.execute(attendanceUpdates) return CommonResponse.success(AttendanceResponseCode.ATTENDANCE_UPDATED_SUCCESS) } } diff --git a/src/main/kotlin/com/weeth/domain/attendance/presentation/AttendanceController.kt b/src/main/kotlin/com/weeth/domain/attendance/presentation/AttendanceController.kt index 09c3ab21..a8d52e2f 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/presentation/AttendanceController.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/presentation/AttendanceController.kt @@ -4,7 +4,8 @@ import com.weeth.domain.attendance.application.dto.request.CheckInRequest import com.weeth.domain.attendance.application.dto.response.AttendanceDetailResponse import com.weeth.domain.attendance.application.dto.response.AttendanceMainResponse import com.weeth.domain.attendance.application.exception.AttendanceErrorCode -import com.weeth.domain.attendance.application.usecase.AttendanceUseCase +import com.weeth.domain.attendance.application.usecase.command.CheckInAttendanceUseCase +import com.weeth.domain.attendance.application.usecase.query.GetAttendanceQueryService import com.weeth.global.auth.annotation.CurrentUser import com.weeth.global.common.exception.ApiErrorCodeExample import com.weeth.global.common.response.CommonResponse @@ -22,7 +23,8 @@ import org.springframework.web.bind.annotation.RestController @RequestMapping("/api/v1/attendances") @ApiErrorCodeExample(AttendanceErrorCode::class) class AttendanceController( - private val attendanceUseCase: AttendanceUseCase, + private val checkInAttendanceUseCase: CheckInAttendanceUseCase, + private val getAttendanceQueryService: GetAttendanceQueryService, ) { @PatchMapping @Operation(summary = "출석체크") @@ -30,7 +32,7 @@ class AttendanceController( @Parameter(hidden = true) @CurrentUser userId: Long, @RequestBody checkIn: CheckInRequest, ): CommonResponse { - attendanceUseCase.checkIn(userId, checkIn.code) + checkInAttendanceUseCase.execute(userId, checkIn.code) return CommonResponse.success(AttendanceResponseCode.ATTENDANCE_CHECKIN_SUCCESS) } @@ -39,7 +41,7 @@ class AttendanceController( fun find( @Parameter(hidden = true) @CurrentUser userId: Long, ): CommonResponse = - CommonResponse.success(AttendanceResponseCode.ATTENDANCE_FIND_SUCCESS, attendanceUseCase.find(userId)) + CommonResponse.success(AttendanceResponseCode.ATTENDANCE_FIND_SUCCESS, getAttendanceQueryService.find(userId)) @GetMapping("/detail") @Operation(summary = "출석 내역 상세조회") @@ -48,6 +50,6 @@ class AttendanceController( ): CommonResponse = CommonResponse.success( AttendanceResponseCode.ATTENDANCE_FIND_ALL_SUCCESS, - attendanceUseCase.findAllDetailsByCurrentCardinal(userId), + getAttendanceQueryService.findAllDetailsByCurrentCardinal(userId), ) } From b1922efa995cb5cd770c83df134e7331b5fcde19 Mon Sep 17 00:00:00 2001 From: soo0711 Date: Wed, 18 Feb 2026 04:02:34 +0900 Subject: [PATCH 26/45] =?UTF-8?q?test:=20AttendanceMapperTest=20=ED=95=98?= =?UTF-8?q?=EB=93=9C=EC=BD=94=EB=94=A9=20=EA=B0=92=EC=9D=84=20=EB=B3=80?= =?UTF-8?q?=EC=88=98=20=EC=B0=B8=EC=A1=B0=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/mapper/AttendanceMapperTest.kt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/test/kotlin/com/weeth/domain/attendance/application/mapper/AttendanceMapperTest.kt b/src/test/kotlin/com/weeth/domain/attendance/application/mapper/AttendanceMapperTest.kt index bd8cf828..d9b5ee17 100644 --- a/src/test/kotlin/com/weeth/domain/attendance/application/mapper/AttendanceMapperTest.kt +++ b/src/test/kotlin/com/weeth/domain/attendance/application/mapper/AttendanceMapperTest.kt @@ -30,7 +30,7 @@ class AttendanceMapperTest : val main = mapper.toMainResponse(user, attendance) main.shouldNotBeNull() - main.title shouldBe "Today" + main.title shouldBe meeting.title main.status shouldBe attendance.status main.start shouldBe meeting.start main.end shouldBe meeting.end @@ -59,7 +59,7 @@ class AttendanceMapperTest : main.shouldNotBeNull() main.code.shouldBeNull() - main.title shouldBe "Today" + main.title shouldBe meeting.title main.status shouldBe attendance.status } } @@ -73,7 +73,7 @@ class AttendanceMapperTest : val response = mapper.toResponse(attendance) response.shouldNotBeNull() - response.title shouldBe "D-1" + response.title shouldBe meeting.title response.start shouldBe meeting.start response.end shouldBe meeting.end response.location shouldBe meeting.location @@ -98,7 +98,7 @@ class AttendanceMapperTest : detail.shouldNotBeNull() detail.attendances shouldBe listOf(r1, r2) - detail.total shouldBe 5 + detail.total shouldBe user.attendanceCount + user.absenceCount } } @@ -114,9 +114,9 @@ class AttendanceMapperTest : val info = mapper.toInfoResponse(attendance) info.shouldNotBeNull() - info.id shouldBe 10L + info.id shouldBe attendance.id info.status shouldBe attendance.status - info.name shouldBe "유저B" + info.name shouldBe user.name } } @@ -132,7 +132,7 @@ class AttendanceMapperTest : main.shouldNotBeNull() main.code shouldBe expectedCode - main.title shouldBe "Today" + main.title shouldBe meeting.title main.start shouldBe meeting.start main.end shouldBe meeting.end main.location shouldBe meeting.location From 2872b5ce9e48839e15ae9c7c479713f7a32113a6 Mon Sep 17 00:00:00 2001 From: soo0711 Date: Wed, 18 Feb 2026 04:32:04 +0900 Subject: [PATCH 27/45] =?UTF-8?q?refactor:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20FQCN=20=EC=A0=95=EB=A6=AC=20=EB=B0=8F=20=ED=95=98=EB=93=9C?= =?UTF-8?q?=EC=BD=94=EB=94=A9=20=EA=B0=92=20=EB=B3=80=EC=88=98=20=EC=B0=B8?= =?UTF-8?q?=EC=A1=B0=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../usecase/command/CloseAttendanceUseCaseTest.kt | 3 ++- .../usecase/query/GetAttendanceQueryServiceTest.kt | 7 ++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/test/kotlin/com/weeth/domain/attendance/application/usecase/command/CloseAttendanceUseCaseTest.kt b/src/test/kotlin/com/weeth/domain/attendance/application/usecase/command/CloseAttendanceUseCaseTest.kt index f41db7b4..1e79674e 100644 --- a/src/test/kotlin/com/weeth/domain/attendance/application/usecase/command/CloseAttendanceUseCaseTest.kt +++ b/src/test/kotlin/com/weeth/domain/attendance/application/usecase/command/CloseAttendanceUseCaseTest.kt @@ -5,6 +5,7 @@ import com.weeth.domain.attendance.domain.repository.AttendanceRepository import com.weeth.domain.attendance.fixture.AttendanceTestFixture.createOneDayMeeting import com.weeth.domain.schedule.application.exception.MeetingNotFoundException import com.weeth.domain.schedule.domain.service.MeetingGetService +import com.weeth.domain.user.domain.entity.User import com.weeth.domain.user.domain.entity.enums.Status import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.DescribeSpec @@ -29,7 +30,7 @@ class CloseAttendanceUseCaseTest : val pendingAttendance = mockk(relaxUnitFun = true) val attendedAttendance = mockk(relaxUnitFun = true) - val pendingUser = mockk(relaxUnitFun = true) + val pendingUser = mockk(relaxUnitFun = true) every { pendingAttendance.isPending } returns true every { pendingAttendance.user } returns pendingUser diff --git a/src/test/kotlin/com/weeth/domain/attendance/application/usecase/query/GetAttendanceQueryServiceTest.kt b/src/test/kotlin/com/weeth/domain/attendance/application/usecase/query/GetAttendanceQueryServiceTest.kt index 8f4ebd63..89c839a2 100644 --- a/src/test/kotlin/com/weeth/domain/attendance/application/usecase/query/GetAttendanceQueryServiceTest.kt +++ b/src/test/kotlin/com/weeth/domain/attendance/application/usecase/query/GetAttendanceQueryServiceTest.kt @@ -5,6 +5,7 @@ import com.weeth.domain.attendance.application.dto.response.AttendanceInfoRespon import com.weeth.domain.attendance.application.dto.response.AttendanceMainResponse import com.weeth.domain.attendance.application.dto.response.AttendanceResponse import com.weeth.domain.attendance.application.mapper.AttendanceMapper +import com.weeth.domain.attendance.domain.entity.Attendance import com.weeth.domain.attendance.domain.repository.AttendanceRepository import com.weeth.domain.attendance.fixture.AttendanceTestFixture.createActiveUserWithAttendances import com.weeth.domain.attendance.fixture.AttendanceTestFixture.createOneDayMeeting @@ -57,7 +58,7 @@ class GetAttendanceQueryServiceTest : val expectedTodayAttendance = user.attendances.first { - it.meeting.title == "Today" + it.meeting == meetingToday } val mapped = mockk() @@ -134,8 +135,8 @@ class GetAttendanceQueryServiceTest : it("해당 정기모임의 출석 정보를 조회") { val meetingId = 1L val meeting = mockk() - val attendance1 = mockk() - val attendance2 = mockk() + val attendance1 = mockk() + val attendance2 = mockk() val response1 = mockk() val response2 = mockk() From c7d39222c52fb3fedc51bd6a443620365433663b Mon Sep 17 00:00:00 2001 From: soo0711 Date: Wed, 18 Feb 2026 13:10:59 +0900 Subject: [PATCH 28/45] =?UTF-8?q?refactor:=20Attendance=20Schduler=20Trans?= =?UTF-8?q?action=20import=20jakarta=EC=97=90=EC=84=9C=20spring=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../attendance/domain/service/scheduler/AttendanceScheduler.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/com/weeth/domain/attendance/domain/service/scheduler/AttendanceScheduler.kt b/src/main/kotlin/com/weeth/domain/attendance/domain/service/scheduler/AttendanceScheduler.kt index a4d71a3c..b6301225 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/domain/service/scheduler/AttendanceScheduler.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/domain/service/scheduler/AttendanceScheduler.kt @@ -3,7 +3,7 @@ package com.weeth.domain.attendance.domain.service.scheduler import com.weeth.domain.attendance.domain.repository.AttendanceRepository import com.weeth.domain.schedule.domain.service.MeetingGetService import com.weeth.domain.user.domain.entity.enums.Status -import jakarta.transaction.Transactional +import org.springframework.transaction.annotation.Transactional import org.springframework.scheduling.annotation.Scheduled import org.springframework.stereotype.Service From 31232c91dd439572d946d93777d33a4133704ab6 Mon Sep 17 00:00:00 2001 From: soo0711 Date: Wed, 18 Feb 2026 13:22:37 +0900 Subject: [PATCH 29/45] =?UTF-8?q?refactor:=20Attendance=20N+1=20=EB=B0=A9?= =?UTF-8?q?=EC=A7=80=20=EC=9C=84=ED=95=B4=20@ManyToOne(fetch=20=3D=20Fetch?= =?UTF-8?q?Type.LAZY)=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/weeth/domain/attendance/domain/entity/Attendance.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/weeth/domain/attendance/domain/entity/Attendance.kt b/src/main/kotlin/com/weeth/domain/attendance/domain/entity/Attendance.kt index 8e8934dc..a4c58a7a 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/domain/entity/Attendance.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/domain/entity/Attendance.kt @@ -8,6 +8,7 @@ import jakarta.persistence.Column import jakarta.persistence.Entity import jakarta.persistence.EnumType import jakarta.persistence.Enumerated +import jakarta.persistence.FetchType import jakarta.persistence.GeneratedValue import jakarta.persistence.GenerationType import jakarta.persistence.Id @@ -19,10 +20,10 @@ import jakarta.persistence.PrePersist class Attendance @JvmOverloads constructor( - @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "meeting_id") val meeting: Meeting, - @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id") val user: User, @Enumerated(EnumType.STRING) From 1051fe02838a98a1dfdd33277e5d4ec3b9279f00 Mon Sep 17 00:00:00 2001 From: soo0711 Date: Wed, 18 Feb 2026 13:25:58 +0900 Subject: [PATCH 30/45] =?UTF-8?q?refactor:=20Attendance=20=EB=8B=A8?= =?UTF-8?q?=EA=B1=B4=20=EC=A1=B0=ED=9A=8C=20N+1=20=EB=B0=A9=EC=A7=80=20?= =?UTF-8?q?=EC=9C=84=ED=95=B4=20JOIN=20FETCH=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../usecase/command/UpdateAttendanceStatusUseCase.kt | 3 +-- .../attendance/domain/repository/AttendanceRepository.kt | 5 +++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/weeth/domain/attendance/application/usecase/command/UpdateAttendanceStatusUseCase.kt b/src/main/kotlin/com/weeth/domain/attendance/application/usecase/command/UpdateAttendanceStatusUseCase.kt index 401614f0..7f81bc1a 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/application/usecase/command/UpdateAttendanceStatusUseCase.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/application/usecase/command/UpdateAttendanceStatusUseCase.kt @@ -4,7 +4,6 @@ import com.weeth.domain.attendance.application.dto.request.UpdateAttendanceStatu import com.weeth.domain.attendance.application.exception.AttendanceNotFoundException import com.weeth.domain.attendance.domain.entity.enums.Status import com.weeth.domain.attendance.domain.repository.AttendanceRepository -import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @@ -16,7 +15,7 @@ class UpdateAttendanceStatusUseCase( fun execute(attendanceUpdates: List) { attendanceUpdates.forEach { update -> val attendance = - attendanceRepository.findByIdOrNull(update.attendanceId) + attendanceRepository.findByIdWithUser(update.attendanceId) ?: throw AttendanceNotFoundException() val user = attendance.user val newStatus = Status.valueOf(update.status) diff --git a/src/main/kotlin/com/weeth/domain/attendance/domain/repository/AttendanceRepository.kt b/src/main/kotlin/com/weeth/domain/attendance/domain/repository/AttendanceRepository.kt index 0ab26a48..b88867cf 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/domain/repository/AttendanceRepository.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/domain/repository/AttendanceRepository.kt @@ -3,16 +3,21 @@ package com.weeth.domain.attendance.domain.repository import com.weeth.domain.attendance.domain.entity.Attendance import com.weeth.domain.schedule.domain.entity.Meeting import com.weeth.domain.user.domain.entity.enums.Status +import org.springframework.data.jpa.repository.EntityGraph import org.springframework.data.jpa.repository.JpaRepository import org.springframework.data.jpa.repository.Modifying import org.springframework.data.jpa.repository.Query interface AttendanceRepository : JpaRepository { + @EntityGraph(attributePaths = ["user"]) fun findAllByMeetingAndUserStatus( meeting: Meeting, status: Status, ): List + @Query("SELECT a FROM Attendance a JOIN FETCH a.user WHERE a.id = :id") + fun findByIdWithUser(id: Long): Attendance? + @Modifying @Query("DELETE FROM Attendance a WHERE a.meeting = :meeting") fun deleteAllByMeeting(meeting: Meeting) From d39e77c3dd34ad23b17e1c5613da992c4c14cf57 Mon Sep 17 00:00:00 2001 From: soo0711 Date: Wed, 18 Feb 2026 13:29:09 +0900 Subject: [PATCH 31/45] =?UTF-8?q?test:=20UpdateAttendanceStatusUseCase=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20findByIdWithUser=20=EB=B0=98?= =?UTF-8?q?=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../usecase/command/UpdateAttendanceStatusUseCaseTest.kt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/test/kotlin/com/weeth/domain/attendance/application/usecase/command/UpdateAttendanceStatusUseCaseTest.kt b/src/test/kotlin/com/weeth/domain/attendance/application/usecase/command/UpdateAttendanceStatusUseCaseTest.kt index 1b242e57..6ac5d1a6 100644 --- a/src/test/kotlin/com/weeth/domain/attendance/application/usecase/command/UpdateAttendanceStatusUseCaseTest.kt +++ b/src/test/kotlin/com/weeth/domain/attendance/application/usecase/command/UpdateAttendanceStatusUseCaseTest.kt @@ -10,7 +10,6 @@ import io.kotest.core.spec.style.DescribeSpec import io.mockk.every import io.mockk.mockk import io.mockk.verify -import org.springframework.data.repository.findByIdOrNull class UpdateAttendanceStatusUseCaseTest : DescribeSpec({ @@ -26,7 +25,7 @@ class UpdateAttendanceStatusUseCaseTest : val attendance = mockk(relaxUnitFun = true) every { attendance.user } returns user - every { attendanceRepository.findByIdOrNull(1L) } returns attendance + every { attendanceRepository.findByIdWithUser(1L) } returns attendance val request = UpdateAttendanceStatusRequest(attendanceId = 1L, status = "ABSENT") useCase.execute(listOf(request)) @@ -43,7 +42,7 @@ class UpdateAttendanceStatusUseCaseTest : val attendance = mockk(relaxUnitFun = true) every { attendance.user } returns user - every { attendanceRepository.findByIdOrNull(1L) } returns attendance + every { attendanceRepository.findByIdWithUser(1L) } returns attendance val request = UpdateAttendanceStatusRequest(attendanceId = 1L, status = "ATTEND") useCase.execute(listOf(request)) @@ -56,7 +55,7 @@ class UpdateAttendanceStatusUseCaseTest : context("출석 정보가 없을 때") { it("AttendanceNotFoundException") { - every { attendanceRepository.findByIdOrNull(999L) } returns null + every { attendanceRepository.findByIdWithUser(999L) } returns null val request = UpdateAttendanceStatusRequest(attendanceId = 999L, status = "ABSENT") From 3ac157e12a33d74c4cdba8d194e632caa9889d5d Mon Sep 17 00:00:00 2001 From: soo0711 Date: Wed, 18 Feb 2026 13:49:46 +0900 Subject: [PATCH 32/45] =?UTF-8?q?refactor:=20user.attendances=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=EB=A5=BC=20Repository=20=EC=BF=BC=EB=A6=AC=EB=A1=9C?= =?UTF-8?q?=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../command/CheckInAttendanceUseCase.kt | 10 ++-- .../query/GetAttendanceQueryService.kt | 17 +++---- .../domain/repository/AttendanceRepository.kt | 46 +++++++++++++++++++ 3 files changed, 56 insertions(+), 17 deletions(-) diff --git a/src/main/kotlin/com/weeth/domain/attendance/application/usecase/command/CheckInAttendanceUseCase.kt b/src/main/kotlin/com/weeth/domain/attendance/application/usecase/command/CheckInAttendanceUseCase.kt index 72d07a3b..e3378ea1 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/application/usecase/command/CheckInAttendanceUseCase.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/application/usecase/command/CheckInAttendanceUseCase.kt @@ -3,6 +3,7 @@ package com.weeth.domain.attendance.application.usecase.command import com.weeth.domain.attendance.application.exception.AttendanceCodeMismatchException import com.weeth.domain.attendance.application.exception.AttendanceNotFoundException import com.weeth.domain.attendance.domain.entity.enums.Status +import com.weeth.domain.attendance.domain.repository.AttendanceRepository import com.weeth.domain.user.domain.service.UserGetService import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @@ -11,6 +12,7 @@ import java.time.LocalDateTime @Service class CheckInAttendanceUseCase( private val userGetService: UserGetService, + private val attendanceRepository: AttendanceRepository, ) { @Transactional fun execute( @@ -21,12 +23,8 @@ class CheckInAttendanceUseCase( val now = LocalDateTime.now() val todayAttendance = - user.attendances.firstOrNull { attendance -> - attendance.meeting.start - .minusMinutes(10) - .isBefore(now) && - attendance.meeting.end.isAfter(now) - } ?: throw AttendanceNotFoundException() + attendanceRepository.findCurrentByUserId(userId, now, now.plusMinutes(10)) + ?: throw AttendanceNotFoundException() if (todayAttendance.isWrong(code)) { throw AttendanceCodeMismatchException() diff --git a/src/main/kotlin/com/weeth/domain/attendance/application/usecase/query/GetAttendanceQueryService.kt b/src/main/kotlin/com/weeth/domain/attendance/application/usecase/query/GetAttendanceQueryService.kt index 9cd1d786..c5697b2a 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/application/usecase/query/GetAttendanceQueryService.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/application/usecase/query/GetAttendanceQueryService.kt @@ -28,14 +28,11 @@ class GetAttendanceQueryService( val today = LocalDate.now() val todayAttendance = - user.attendances.firstOrNull { attendance -> - attendance.meeting.start - .toLocalDate() - .isEqual(today) && - attendance.meeting.end - .toLocalDate() - .isEqual(today) - } + attendanceRepository.findTodayByUserId( + userId, + today.atStartOfDay(), + today.plusDays(1).atStartOfDay(), + ) return if (user.role == Role.ADMIN) { mapper.toAdminResponse(user, todayAttendance) @@ -49,9 +46,7 @@ class GetAttendanceQueryService( val currentCardinal = userCardinalGetService.getCurrentCardinal(user) val responses = - user.attendances - .filter { it.meeting.cardinal == currentCardinal.cardinalNumber } - .sortedBy { it.meeting.start } + attendanceRepository.findAllByUserIdAndCardinal(userId, currentCardinal.cardinalNumber) .map(mapper::toResponse) return mapper.toDetailResponse(user, responses) diff --git a/src/main/kotlin/com/weeth/domain/attendance/domain/repository/AttendanceRepository.kt b/src/main/kotlin/com/weeth/domain/attendance/domain/repository/AttendanceRepository.kt index b88867cf..cd0d82a0 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/domain/repository/AttendanceRepository.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/domain/repository/AttendanceRepository.kt @@ -7,6 +7,8 @@ import org.springframework.data.jpa.repository.EntityGraph import org.springframework.data.jpa.repository.JpaRepository import org.springframework.data.jpa.repository.Modifying import org.springframework.data.jpa.repository.Query +import org.springframework.data.repository.query.Param +import java.time.LocalDateTime interface AttendanceRepository : JpaRepository { @EntityGraph(attributePaths = ["user"]) @@ -18,6 +20,50 @@ interface AttendanceRepository : JpaRepository { @Query("SELECT a FROM Attendance a JOIN FETCH a.user WHERE a.id = :id") fun findByIdWithUser(id: Long): Attendance? + @Query( + """ + SELECT a FROM Attendance a + JOIN FETCH a.meeting m + WHERE a.user.id = :userId + AND m.start <= :checkInEnd + AND m.end > :now + """, + ) + fun findCurrentByUserId( + @Param("userId") userId: Long, + @Param("now") now: LocalDateTime, + @Param("checkInEnd") checkInEnd: LocalDateTime, + ): Attendance? + + @Query( + """ + SELECT a FROM Attendance a + JOIN FETCH a.meeting m + WHERE a.user.id = :userId + AND m.start >= :dayStart + AND m.end < :dayEnd + """, + ) + fun findTodayByUserId( + @Param("userId") userId: Long, + @Param("dayStart") dayStart: LocalDateTime, + @Param("dayEnd") dayEnd: LocalDateTime, + ): Attendance? + + @Query( + """ + SELECT a FROM Attendance a + JOIN FETCH a.meeting m + WHERE a.user.id = :userId + AND m.cardinal = :cardinal + ORDER BY m.start + """, + ) + fun findAllByUserIdAndCardinal( + @Param("userId") userId: Long, + @Param("cardinal") cardinal: Int, + ): List + @Modifying @Query("DELETE FROM Attendance a WHERE a.meeting = :meeting") fun deleteAllByMeeting(meeting: Meeting) From b08828cd72c02b59d3fad7e9d52892876bcbfec6 Mon Sep 17 00:00:00 2001 From: soo0711 Date: Wed, 18 Feb 2026 13:51:14 +0900 Subject: [PATCH 33/45] =?UTF-8?q?test:=20Repository=20=EC=BF=BC=EB=A6=AC?= =?UTF-8?q?=20=EC=A0=84=ED=99=98=EC=97=90=20=EB=94=B0=EB=A5=B8=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20mock=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../command/CheckInAttendanceUseCaseTest.kt | 70 +++---------------- .../query/GetAttendanceQueryServiceTest.kt | 68 ++++++------------ 2 files changed, 29 insertions(+), 109 deletions(-) diff --git a/src/test/kotlin/com/weeth/domain/attendance/application/usecase/command/CheckInAttendanceUseCaseTest.kt b/src/test/kotlin/com/weeth/domain/attendance/application/usecase/command/CheckInAttendanceUseCaseTest.kt index 438630e9..aa0fa00b 100644 --- a/src/test/kotlin/com/weeth/domain/attendance/application/usecase/command/CheckInAttendanceUseCaseTest.kt +++ b/src/test/kotlin/com/weeth/domain/attendance/application/usecase/command/CheckInAttendanceUseCaseTest.kt @@ -4,10 +4,10 @@ import com.weeth.domain.attendance.application.exception.AttendanceCodeMismatchE import com.weeth.domain.attendance.application.exception.AttendanceNotFoundException import com.weeth.domain.attendance.domain.entity.Attendance import com.weeth.domain.attendance.domain.entity.enums.Status -import com.weeth.domain.attendance.fixture.AttendanceTestFixture.createActiveUserWithAttendances +import com.weeth.domain.attendance.domain.repository.AttendanceRepository +import com.weeth.domain.attendance.fixture.AttendanceTestFixture.createAttendance import com.weeth.domain.attendance.fixture.AttendanceTestFixture.createInProgressMeeting import com.weeth.domain.attendance.fixture.AttendanceTestFixture.setUserAttendanceStats -import com.weeth.domain.schedule.domain.entity.Meeting import com.weeth.domain.user.domain.entity.User import com.weeth.domain.user.domain.service.UserGetService import io.kotest.assertions.throwables.shouldNotThrowAny @@ -23,66 +23,20 @@ class CheckInAttendanceUseCaseTest : val userId = 10L val userGetService = mockk() + val attendanceRepository = mockk() - val useCase = CheckInAttendanceUseCase(userGetService) + val useCase = CheckInAttendanceUseCase(userGetService, attendanceRepository) describe("execute") { - context("10분 전부터 출석이 가능한지 확인") { - it("5분 뒤 시작 회의에 출석 성공") { - val now = LocalDateTime.now() - val meeting = - Meeting - .builder() - .start(now.plusMinutes(5)) - .end(now.plusHours(2)) - .code(1234) - .title("Today") - .cardinal(1) - .build() - - val user = createActiveUserWithAttendances("이지훈", listOf(meeting)) - setUserAttendanceStats(user, 0, 0) - - every { userGetService.find(userId) } returns user - - shouldNotThrowAny { - useCase.execute(userId, 1234) - } - } - - it("11분 전에 출석시 오류 발생") { - val now = LocalDateTime.now() - val meeting = - Meeting - .builder() - .start(now.plusMinutes(11)) - .end(now.plusHours(2)) - .code(1234) - .title("Today") - .cardinal(1) - .build() - - val user = createActiveUserWithAttendances("이지훈", listOf(meeting)) - - every { userGetService.find(userId) } returns user - - shouldThrow { - useCase.execute(userId, 1234) - } - } - } - context("진행 중 정기모임이고 코드 일치하며 상태가 ATTEND가 아닐 때") { it("출석 처리된다") { val user = mockk() - val inProgressMeeting = createInProgressMeeting(1, 1234, "InProgress") val attendance = mockk(relaxUnitFun = true) - every { attendance.meeting } returns inProgressMeeting every { attendance.isWrong(1234) } returns false every { attendance.status } returns Status.PENDING every { userGetService.find(userId) } returns user - every { user.attendances } returns listOf(attendance) + every { attendanceRepository.findCurrentByUserId(eq(userId), any(), any()) } returns attendance every { user.attend() } returns Unit useCase.execute(userId, 1234) @@ -96,7 +50,7 @@ class CheckInAttendanceUseCaseTest : it("AttendanceNotFoundException") { val user = mockk() every { userGetService.find(userId) } returns user - every { user.attendances } returns listOf() + every { attendanceRepository.findCurrentByUserId(eq(userId), any(), any()) } returns null shouldThrow { useCase.execute(userId, 1234) @@ -107,14 +61,11 @@ class CheckInAttendanceUseCaseTest : context("코드 불일치 시") { it("AttendanceCodeMismatchException") { val user = mockk() - val inProgressMeeting = createInProgressMeeting(1, 1234, "InProgress") - val attendance = mockk() - every { attendance.meeting } returns inProgressMeeting every { attendance.isWrong(9999) } returns true every { userGetService.find(userId) } returns user - every { user.attendances } returns listOf(attendance) + every { attendanceRepository.findCurrentByUserId(eq(userId), any(), any()) } returns attendance shouldThrow { useCase.execute(userId, 9999) @@ -125,15 +76,12 @@ class CheckInAttendanceUseCaseTest : context("이미 ATTEND일 때") { it("추가 처리 없이 종료") { val user = mockk() - val inProgressMeeting = createInProgressMeeting(1, 1234, "InProgress") - val attendance = mockk() - every { attendance.meeting } returns inProgressMeeting every { attendance.isWrong(1234) } returns false every { attendance.status } returns Status.ATTEND every { userGetService.find(userId) } returns user - every { user.attendances } returns listOf(attendance) + every { attendanceRepository.findCurrentByUserId(eq(userId), any(), any()) } returns attendance useCase.execute(userId, 1234) @@ -142,4 +90,4 @@ class CheckInAttendanceUseCaseTest : } } } - }) + }) \ No newline at end of file diff --git a/src/test/kotlin/com/weeth/domain/attendance/application/usecase/query/GetAttendanceQueryServiceTest.kt b/src/test/kotlin/com/weeth/domain/attendance/application/usecase/query/GetAttendanceQueryServiceTest.kt index 89c839a2..8a68aca2 100644 --- a/src/test/kotlin/com/weeth/domain/attendance/application/usecase/query/GetAttendanceQueryServiceTest.kt +++ b/src/test/kotlin/com/weeth/domain/attendance/application/usecase/query/GetAttendanceQueryServiceTest.kt @@ -7,8 +7,7 @@ import com.weeth.domain.attendance.application.dto.response.AttendanceResponse import com.weeth.domain.attendance.application.mapper.AttendanceMapper import com.weeth.domain.attendance.domain.entity.Attendance import com.weeth.domain.attendance.domain.repository.AttendanceRepository -import com.weeth.domain.attendance.fixture.AttendanceTestFixture.createActiveUserWithAttendances -import com.weeth.domain.attendance.fixture.AttendanceTestFixture.createOneDayMeeting +import com.weeth.domain.attendance.fixture.AttendanceTestFixture.createActiveUser import com.weeth.domain.schedule.domain.entity.Meeting import com.weeth.domain.schedule.domain.service.MeetingGetService import com.weeth.domain.user.domain.entity.Cardinal @@ -20,7 +19,6 @@ import io.kotest.matchers.shouldBe import io.mockk.every import io.mockk.mockk import io.mockk.verify -import java.time.LocalDate class GetAttendanceQueryServiceTest : DescribeSpec({ @@ -43,49 +41,27 @@ class GetAttendanceQueryServiceTest : val userId = 10L describe("find") { - it("여러 날짜의 출석 목록 중 시작/종료 날짜가 모두 오늘인 출석정보를 선택") { - val today = LocalDate.now() - - val meetingYesterday = createOneDayMeeting(today.minusDays(1), 1, 1111, "Yesterday") - val meetingToday = createOneDayMeeting(today, 1, 2222, "Today") - val meetingTomorrow = createOneDayMeeting(today.plusDays(1), 1, 3333, "Tomorrow") - - val user = - createActiveUserWithAttendances( - "이지훈", - listOf(meetingYesterday, meetingToday, meetingTomorrow), - ) - - val expectedTodayAttendance = - user.attendances.first { - it.meeting == meetingToday - } - + it("오늘 출석 정보가 있으면 mapper.toMainResponse(user, attendance) 호출") { + val user = createActiveUser("이지훈") + val todayAttendance = mockk() val mapped = mockk() every { userGetService.find(userId) } returns user - every { attendanceMapper.toMainResponse(eq(user), eq(expectedTodayAttendance)) } returns mapped + every { attendanceRepository.findTodayByUserId(eq(userId), any(), any()) } returns todayAttendance + every { attendanceMapper.toMainResponse(eq(user), eq(todayAttendance)) } returns mapped val actual = queryService.find(userId) actual shouldBe mapped - verify { attendanceMapper.toMainResponse(eq(user), eq(expectedTodayAttendance)) } + verify { attendanceMapper.toMainResponse(eq(user), eq(todayAttendance)) } } - it("시작/종료 날짜가 모두 오늘인 출석이 없다면 mapper.toMainResponse(user, null)을 호출") { - val today = LocalDate.now() - - val yesterdayMeeting = createOneDayMeeting(today.minusDays(1), 1, 1111, "Yesterday") - val tomorrowMeeting = createOneDayMeeting(today.plusDays(1), 1, 3333, "Tomorrow") - - val user = - createActiveUserWithAttendances( - "이지훈", - listOf(yesterdayMeeting, tomorrowMeeting), - ) - + it("오늘 출석이 없다면 mapper.toMainResponse(user, null) 호출") { + val user = createActiveUser("이지훈") val mapped = mockk() + every { userGetService.find(userId) } returns user + every { attendanceRepository.findTodayByUserId(eq(userId), any(), any()) } returns null every { attendanceMapper.toMainResponse(user, null) } returns mapped val actual = queryService.find(userId) @@ -96,25 +72,21 @@ class GetAttendanceQueryServiceTest : } describe("findAllDetailsByCurrentCardinal") { - it("현재 기수만 필터링·정렬하여 Detail 매핑") { - val today = LocalDate.now() - val meetingDayMinus1 = createOneDayMeeting(today.minusDays(1), 1, 1111, "D-1") - val meetingToday = createOneDayMeeting(today, 1, 2222, "D-Day") - val user = createActiveUserWithAttendances("이지훈", listOf(meetingDayMinus1, meetingToday)) - - val userAttendances = user.attendances - val attendanceFirst = userAttendances[0] - val attendanceSecond = userAttendances[1] + it("현재 기수의 출석 목록을 매핑하여 Detail 반환") { + val user = createActiveUser("이지훈") + val attendance1 = mockk() + val attendance2 = mockk() every { userGetService.find(userId) } returns user val currentCardinal = mockk() every { currentCardinal.cardinalNumber } returns 1 every { userCardinalGetService.getCurrentCardinal(user) } returns currentCardinal + every { attendanceRepository.findAllByUserIdAndCardinal(userId, 1) } returns listOf(attendance1, attendance2) - val responseFirst = mockk() - val responseSecond = mockk() - every { attendanceMapper.toResponse(attendanceFirst) } returns responseFirst - every { attendanceMapper.toResponse(attendanceSecond) } returns responseSecond + val response1 = mockk() + val response2 = mockk() + every { attendanceMapper.toResponse(attendance1) } returns response1 + every { attendanceMapper.toResponse(attendance2) } returns response2 val expectedDetail = mockk() every { attendanceMapper.toDetailResponse(eq(user), any()) } returns expectedDetail From 41796d9bee40fa82fe364c499f7ca783c4fe8011 Mon Sep 17 00:00:00 2001 From: soo0711 Date: Wed, 18 Feb 2026 14:28:28 +0900 Subject: [PATCH 34/45] =?UTF-8?q?refactor:=20AttendanceMapper=20=EC=A4=91?= =?UTF-8?q?=EB=B3=B5=20=EB=A9=94=EC=84=9C=EB=93=9C=20=ED=86=B5=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/mapper/AttendanceMapper.kt | 17 ++------- .../query/GetAttendanceQueryService.kt | 6 +--- .../mapper/AttendanceMapperTest.kt | 35 +++++++++---------- 3 files changed, 20 insertions(+), 38 deletions(-) diff --git a/src/main/kotlin/com/weeth/domain/attendance/application/mapper/AttendanceMapper.kt b/src/main/kotlin/com/weeth/domain/attendance/application/mapper/AttendanceMapper.kt index 71fbb65e..dff64581 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/application/mapper/AttendanceMapper.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/application/mapper/AttendanceMapper.kt @@ -13,26 +13,13 @@ class AttendanceMapper { fun toMainResponse( user: User, attendance: Attendance?, + isAdmin: Boolean = false, ): AttendanceMainResponse = AttendanceMainResponse( attendanceRate = user.attendanceRate, title = attendance?.meeting?.title, status = attendance?.status, - code = null, - start = attendance?.meeting?.start, - end = attendance?.meeting?.end, - location = attendance?.meeting?.location, - ) - - fun toAdminResponse( - user: User, - attendance: Attendance?, - ): AttendanceMainResponse = - AttendanceMainResponse( - attendanceRate = user.attendanceRate, - title = attendance?.meeting?.title, - status = attendance?.status, - code = attendance?.meeting?.code, + code = if (isAdmin) attendance?.meeting?.code else null, start = attendance?.meeting?.start, end = attendance?.meeting?.end, location = attendance?.meeting?.location, diff --git a/src/main/kotlin/com/weeth/domain/attendance/application/usecase/query/GetAttendanceQueryService.kt b/src/main/kotlin/com/weeth/domain/attendance/application/usecase/query/GetAttendanceQueryService.kt index c5697b2a..234ca678 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/application/usecase/query/GetAttendanceQueryService.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/application/usecase/query/GetAttendanceQueryService.kt @@ -34,11 +34,7 @@ class GetAttendanceQueryService( today.plusDays(1).atStartOfDay(), ) - return if (user.role == Role.ADMIN) { - mapper.toAdminResponse(user, todayAttendance) - } else { - mapper.toMainResponse(user, todayAttendance) - } + return mapper.toMainResponse(user, todayAttendance, isAdmin = user.role == Role.ADMIN) } fun findAllDetailsByCurrentCardinal(userId: Long): AttendanceDetailResponse { diff --git a/src/test/kotlin/com/weeth/domain/attendance/application/mapper/AttendanceMapperTest.kt b/src/test/kotlin/com/weeth/domain/attendance/application/mapper/AttendanceMapperTest.kt index d9b5ee17..5fe56a74 100644 --- a/src/test/kotlin/com/weeth/domain/attendance/application/mapper/AttendanceMapperTest.kt +++ b/src/test/kotlin/com/weeth/domain/attendance/application/mapper/AttendanceMapperTest.kt @@ -62,6 +62,23 @@ class AttendanceMapperTest : main.title shouldBe meeting.title main.status shouldBe attendance.status } + + it("ADMIN 유저는 출석 코드가 포함된다") { + val today = LocalDate.now() + val expectedCode = 1234 + val meeting = createOneDayMeeting(today, 1, expectedCode, "Today") + val adminUser = createAdminUserWithAttendances("관리자", listOf(meeting)) + val attendance = adminUser.attendances[0] + + val main = mapper.toMainResponse(adminUser, attendance, isAdmin = true) + + main.shouldNotBeNull() + main.code shouldBe expectedCode + main.title shouldBe meeting.title + main.start shouldBe meeting.start + main.end shouldBe meeting.end + main.location shouldBe meeting.location + } } describe("toResponse") { @@ -120,22 +137,4 @@ class AttendanceMapperTest : } } - describe("toAdminResponse") { - it("ADMIN 유저는 출석 코드가 포함된다") { - val today = LocalDate.now() - val expectedCode = 1234 - val meeting = createOneDayMeeting(today, 1, expectedCode, "Today") - val adminUser = createAdminUserWithAttendances("관리자", listOf(meeting)) - val attendance = adminUser.attendances[0] - - val main = mapper.toAdminResponse(adminUser, attendance) - - main.shouldNotBeNull() - main.code shouldBe expectedCode - main.title shouldBe meeting.title - main.start shouldBe meeting.start - main.end shouldBe meeting.end - main.location shouldBe meeting.location - } - } }) From 4503684ae3592ba09dc9f2e6e3c14fbf6b9831be Mon Sep 17 00:00:00 2001 From: soo0711 Date: Wed, 18 Feb 2026 14:33:34 +0900 Subject: [PATCH 35/45] =?UTF-8?q?refactor:=20AttendanceGetService=20?= =?UTF-8?q?=EB=AF=B8=EC=82=AC=EC=9A=A9=20=EB=A9=94=EC=84=9C=EB=93=9C=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../attendance/domain/service/AttendanceGetService.kt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/main/kotlin/com/weeth/domain/attendance/domain/service/AttendanceGetService.kt b/src/main/kotlin/com/weeth/domain/attendance/domain/service/AttendanceGetService.kt index 6d275f07..647910b7 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/domain/service/AttendanceGetService.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/domain/service/AttendanceGetService.kt @@ -1,11 +1,9 @@ package com.weeth.domain.attendance.domain.service -import com.weeth.domain.attendance.application.exception.AttendanceNotFoundException import com.weeth.domain.attendance.domain.entity.Attendance import com.weeth.domain.attendance.domain.repository.AttendanceRepository import com.weeth.domain.schedule.domain.entity.Meeting import com.weeth.domain.user.domain.entity.enums.Status -import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service @Service @@ -13,8 +11,4 @@ class AttendanceGetService( private val attendanceRepository: AttendanceRepository, ) { fun findAllByMeeting(meeting: Meeting): List = attendanceRepository.findAllByMeetingAndUserStatus(meeting, Status.ACTIVE) - - fun findByAttendanceId(attendanceId: Long): Attendance = - attendanceRepository.findByIdOrNull(attendanceId) - ?: throw AttendanceNotFoundException() } From cdc732dca352d5e97d88ad57eda18d95b1b55138 Mon Sep 17 00:00:00 2001 From: soo0711 Date: Wed, 18 Feb 2026 14:44:08 +0900 Subject: [PATCH 36/45] =?UTF-8?q?refactor:=20Attendance=20DTO=EC=97=90=20@?= =?UTF-8?q?Schema=20=EC=96=B4=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../attendance/application/dto/request/CheckInRequest.kt | 3 +++ .../dto/request/UpdateAttendanceStatusRequest.kt | 3 +++ .../application/dto/response/AttendanceDetailResponse.kt | 6 ++++++ .../application/dto/response/AttendanceInfoResponse.kt | 7 +++++++ .../application/dto/response/AttendanceMainResponse.kt | 8 +++++++- .../application/dto/response/AttendanceResponse.kt | 7 +++++++ 6 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/com/weeth/domain/attendance/application/dto/request/CheckInRequest.kt b/src/main/kotlin/com/weeth/domain/attendance/application/dto/request/CheckInRequest.kt index b699cfeb..104b72a4 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/application/dto/request/CheckInRequest.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/application/dto/request/CheckInRequest.kt @@ -1,5 +1,8 @@ package com.weeth.domain.attendance.application.dto.request +import io.swagger.v3.oas.annotations.media.Schema + data class CheckInRequest( + @field:Schema(description = "출석 코드", example = "1234") val code: Int, ) diff --git a/src/main/kotlin/com/weeth/domain/attendance/application/dto/request/UpdateAttendanceStatusRequest.kt b/src/main/kotlin/com/weeth/domain/attendance/application/dto/request/UpdateAttendanceStatusRequest.kt index 495194e3..475e1679 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/application/dto/request/UpdateAttendanceStatusRequest.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/application/dto/request/UpdateAttendanceStatusRequest.kt @@ -1,11 +1,14 @@ package com.weeth.domain.attendance.application.dto.request +import io.swagger.v3.oas.annotations.media.Schema import jakarta.validation.constraints.NotNull import jakarta.validation.constraints.Pattern data class UpdateAttendanceStatusRequest( + @field:Schema(description = "출석 ID", example = "1") @field:NotNull val attendanceId: Long, + @field:Schema(description = "변경할 출석 상태", example = "ATTEND") @field:NotNull @field:Pattern(regexp = "ATTEND|ABSENT") val status: String, diff --git a/src/main/kotlin/com/weeth/domain/attendance/application/dto/response/AttendanceDetailResponse.kt b/src/main/kotlin/com/weeth/domain/attendance/application/dto/response/AttendanceDetailResponse.kt index 580967c1..419ce800 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/application/dto/response/AttendanceDetailResponse.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/application/dto/response/AttendanceDetailResponse.kt @@ -1,8 +1,14 @@ package com.weeth.domain.attendance.application.dto.response +import io.swagger.v3.oas.annotations.media.Schema + data class AttendanceDetailResponse( + @field:Schema(description = "출석 횟수", example = "8") val attendanceCount: Int, + @field:Schema(description = "전체 횟수", example = "10") val total: Int, + @field:Schema(description = "결석 횟수", example = "2") val absenceCount: Int, + @field:Schema(description = "출석 내역 목록") val attendances: List, ) diff --git a/src/main/kotlin/com/weeth/domain/attendance/application/dto/response/AttendanceInfoResponse.kt b/src/main/kotlin/com/weeth/domain/attendance/application/dto/response/AttendanceInfoResponse.kt index 7bcc7e4a..6367f1cd 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/application/dto/response/AttendanceInfoResponse.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/application/dto/response/AttendanceInfoResponse.kt @@ -1,12 +1,19 @@ package com.weeth.domain.attendance.application.dto.response import com.weeth.domain.attendance.domain.entity.enums.Status +import io.swagger.v3.oas.annotations.media.Schema data class AttendanceInfoResponse( + @field:Schema(description = "출석 ID", example = "1") val id: Long, + @field:Schema(description = "출석 상태", example = "ATTEND") val status: Status?, + @field:Schema(description = "사용자 이름", example = "이지훈") val name: String?, + @field:Schema(description = "직책", example = "BE") val position: String?, + @field:Schema(description = "소속 학과", example = "컴퓨터공학과") val department: String?, + @field:Schema(description = "학번", example = "20201234") val studentId: String?, ) diff --git a/src/main/kotlin/com/weeth/domain/attendance/application/dto/response/AttendanceMainResponse.kt b/src/main/kotlin/com/weeth/domain/attendance/application/dto/response/AttendanceMainResponse.kt index 4f89a502..5c4a9edc 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/application/dto/response/AttendanceMainResponse.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/application/dto/response/AttendanceMainResponse.kt @@ -5,12 +5,18 @@ import io.swagger.v3.oas.annotations.media.Schema import java.time.LocalDateTime data class AttendanceMainResponse( + @field:Schema(description = "출석률", example = "80") val attendanceRate: Int?, + @field:Schema(description = "정기모임 제목", example = "1주차 정기모임") val title: String?, + @field:Schema(description = "출석 상태", example = "ATTEND") val status: Status?, - @field:Schema(description = "어드민인 경우 출석 코드 노출") + @field:Schema(description = "어드민인 경우 출석 코드 노출", example = "1234") val code: Int?, + @field:Schema(description = "정기모임 시작 시간") val start: LocalDateTime?, + @field:Schema(description = "정기모임 종료 시간") val end: LocalDateTime?, + @field:Schema(description = "정기모임 장소", example = "공학관 401호") val location: String?, ) diff --git a/src/main/kotlin/com/weeth/domain/attendance/application/dto/response/AttendanceResponse.kt b/src/main/kotlin/com/weeth/domain/attendance/application/dto/response/AttendanceResponse.kt index f56f81fe..49012083 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/application/dto/response/AttendanceResponse.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/application/dto/response/AttendanceResponse.kt @@ -1,13 +1,20 @@ package com.weeth.domain.attendance.application.dto.response import com.weeth.domain.attendance.domain.entity.enums.Status +import io.swagger.v3.oas.annotations.media.Schema import java.time.LocalDateTime data class AttendanceResponse( + @field:Schema(description = "출석 ID", example = "1") val id: Long, + @field:Schema(description = "출석 상태", example = "ATTEND") val status: Status?, + @field:Schema(description = "정기모임 제목", example = "1주차 정기모임") val title: String?, + @field:Schema(description = "정기모임 시작 시간") val start: LocalDateTime?, + @field:Schema(description = "정기모임 종료 시간") val end: LocalDateTime?, + @field:Schema(description = "정기모임 장소", example = "공학관 401호") val location: String?, ) From 565cc36430822303105d2fd5b1bb0d986ce86cb5 Mon Sep 17 00:00:00 2001 From: soo0711 Date: Wed, 18 Feb 2026 14:48:22 +0900 Subject: [PATCH 37/45] =?UTF-8?q?refactor:=20AttendanceMainResponse?= =?UTF-8?q?=EB=A5=BC=20AttendanceSummaryResponse=EB=A1=9C=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...tendanceMainResponse.kt => AttendanceSummaryResponse.kt} | 2 +- .../attendance/application/mapper/AttendanceMapper.kt | 6 +++--- .../application/usecase/query/GetAttendanceQueryService.kt | 4 ++-- .../domain/attendance/presentation/AttendanceController.kt | 4 ++-- .../usecase/query/GetAttendanceQueryServiceTest.kt | 6 +++--- 5 files changed, 11 insertions(+), 11 deletions(-) rename src/main/kotlin/com/weeth/domain/attendance/application/dto/response/{AttendanceMainResponse.kt => AttendanceSummaryResponse.kt} (96%) diff --git a/src/main/kotlin/com/weeth/domain/attendance/application/dto/response/AttendanceMainResponse.kt b/src/main/kotlin/com/weeth/domain/attendance/application/dto/response/AttendanceSummaryResponse.kt similarity index 96% rename from src/main/kotlin/com/weeth/domain/attendance/application/dto/response/AttendanceMainResponse.kt rename to src/main/kotlin/com/weeth/domain/attendance/application/dto/response/AttendanceSummaryResponse.kt index 5c4a9edc..bdd3b57b 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/application/dto/response/AttendanceMainResponse.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/application/dto/response/AttendanceSummaryResponse.kt @@ -4,7 +4,7 @@ import com.weeth.domain.attendance.domain.entity.enums.Status import io.swagger.v3.oas.annotations.media.Schema import java.time.LocalDateTime -data class AttendanceMainResponse( +data class AttendanceSummaryResponse( @field:Schema(description = "출석률", example = "80") val attendanceRate: Int?, @field:Schema(description = "정기모임 제목", example = "1주차 정기모임") diff --git a/src/main/kotlin/com/weeth/domain/attendance/application/mapper/AttendanceMapper.kt b/src/main/kotlin/com/weeth/domain/attendance/application/mapper/AttendanceMapper.kt index dff64581..759283a0 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/application/mapper/AttendanceMapper.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/application/mapper/AttendanceMapper.kt @@ -2,7 +2,7 @@ package com.weeth.domain.attendance.application.mapper import com.weeth.domain.attendance.application.dto.response.AttendanceDetailResponse import com.weeth.domain.attendance.application.dto.response.AttendanceInfoResponse -import com.weeth.domain.attendance.application.dto.response.AttendanceMainResponse +import com.weeth.domain.attendance.application.dto.response.AttendanceSummaryResponse import com.weeth.domain.attendance.application.dto.response.AttendanceResponse import com.weeth.domain.attendance.domain.entity.Attendance import com.weeth.domain.user.domain.entity.User @@ -14,8 +14,8 @@ class AttendanceMapper { user: User, attendance: Attendance?, isAdmin: Boolean = false, - ): AttendanceMainResponse = - AttendanceMainResponse( + ): AttendanceSummaryResponse = + AttendanceSummaryResponse( attendanceRate = user.attendanceRate, title = attendance?.meeting?.title, status = attendance?.status, diff --git a/src/main/kotlin/com/weeth/domain/attendance/application/usecase/query/GetAttendanceQueryService.kt b/src/main/kotlin/com/weeth/domain/attendance/application/usecase/query/GetAttendanceQueryService.kt index 234ca678..e499bb32 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/application/usecase/query/GetAttendanceQueryService.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/application/usecase/query/GetAttendanceQueryService.kt @@ -2,7 +2,7 @@ package com.weeth.domain.attendance.application.usecase.query import com.weeth.domain.attendance.application.dto.response.AttendanceDetailResponse import com.weeth.domain.attendance.application.dto.response.AttendanceInfoResponse -import com.weeth.domain.attendance.application.dto.response.AttendanceMainResponse +import com.weeth.domain.attendance.application.dto.response.AttendanceSummaryResponse import com.weeth.domain.attendance.application.mapper.AttendanceMapper import com.weeth.domain.attendance.domain.repository.AttendanceRepository import com.weeth.domain.schedule.domain.service.MeetingGetService @@ -23,7 +23,7 @@ class GetAttendanceQueryService( private val attendanceRepository: AttendanceRepository, private val mapper: AttendanceMapper, ) { - fun find(userId: Long): AttendanceMainResponse { + fun find(userId: Long): AttendanceSummaryResponse { val user = userGetService.find(userId) val today = LocalDate.now() diff --git a/src/main/kotlin/com/weeth/domain/attendance/presentation/AttendanceController.kt b/src/main/kotlin/com/weeth/domain/attendance/presentation/AttendanceController.kt index a8d52e2f..effc7b54 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/presentation/AttendanceController.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/presentation/AttendanceController.kt @@ -2,7 +2,7 @@ package com.weeth.domain.attendance.presentation import com.weeth.domain.attendance.application.dto.request.CheckInRequest import com.weeth.domain.attendance.application.dto.response.AttendanceDetailResponse -import com.weeth.domain.attendance.application.dto.response.AttendanceMainResponse +import com.weeth.domain.attendance.application.dto.response.AttendanceSummaryResponse import com.weeth.domain.attendance.application.exception.AttendanceErrorCode import com.weeth.domain.attendance.application.usecase.command.CheckInAttendanceUseCase import com.weeth.domain.attendance.application.usecase.query.GetAttendanceQueryService @@ -40,7 +40,7 @@ class AttendanceController( @Operation(summary = "출석 메인페이지") fun find( @Parameter(hidden = true) @CurrentUser userId: Long, - ): CommonResponse = + ): CommonResponse = CommonResponse.success(AttendanceResponseCode.ATTENDANCE_FIND_SUCCESS, getAttendanceQueryService.find(userId)) @GetMapping("/detail") diff --git a/src/test/kotlin/com/weeth/domain/attendance/application/usecase/query/GetAttendanceQueryServiceTest.kt b/src/test/kotlin/com/weeth/domain/attendance/application/usecase/query/GetAttendanceQueryServiceTest.kt index 8a68aca2..4abdc16c 100644 --- a/src/test/kotlin/com/weeth/domain/attendance/application/usecase/query/GetAttendanceQueryServiceTest.kt +++ b/src/test/kotlin/com/weeth/domain/attendance/application/usecase/query/GetAttendanceQueryServiceTest.kt @@ -2,7 +2,7 @@ package com.weeth.domain.attendance.application.usecase.query import com.weeth.domain.attendance.application.dto.response.AttendanceDetailResponse import com.weeth.domain.attendance.application.dto.response.AttendanceInfoResponse -import com.weeth.domain.attendance.application.dto.response.AttendanceMainResponse +import com.weeth.domain.attendance.application.dto.response.AttendanceSummaryResponse import com.weeth.domain.attendance.application.dto.response.AttendanceResponse import com.weeth.domain.attendance.application.mapper.AttendanceMapper import com.weeth.domain.attendance.domain.entity.Attendance @@ -44,7 +44,7 @@ class GetAttendanceQueryServiceTest : it("오늘 출석 정보가 있으면 mapper.toMainResponse(user, attendance) 호출") { val user = createActiveUser("이지훈") val todayAttendance = mockk() - val mapped = mockk() + val mapped = mockk() every { userGetService.find(userId) } returns user every { attendanceRepository.findTodayByUserId(eq(userId), any(), any()) } returns todayAttendance @@ -58,7 +58,7 @@ class GetAttendanceQueryServiceTest : it("오늘 출석이 없다면 mapper.toMainResponse(user, null) 호출") { val user = createActiveUser("이지훈") - val mapped = mockk() + val mapped = mockk() every { userGetService.find(userId) } returns user every { attendanceRepository.findTodayByUserId(eq(userId), any(), any()) } returns null From 9b2493ae05254a2cd91e0114be59662e0e22a9e5 Mon Sep 17 00:00:00 2001 From: soo0711 Date: Wed, 18 Feb 2026 14:54:04 +0900 Subject: [PATCH 38/45] =?UTF-8?q?refactor:=20UpdateAttendanceStatusRequest?= =?UTF-8?q?=20=EB=B6=88=ED=95=84=EC=9A=94=ED=95=9C=20@NotNull=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/dto/request/UpdateAttendanceStatusRequest.kt | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/kotlin/com/weeth/domain/attendance/application/dto/request/UpdateAttendanceStatusRequest.kt b/src/main/kotlin/com/weeth/domain/attendance/application/dto/request/UpdateAttendanceStatusRequest.kt index 475e1679..326fac52 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/application/dto/request/UpdateAttendanceStatusRequest.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/application/dto/request/UpdateAttendanceStatusRequest.kt @@ -1,15 +1,12 @@ package com.weeth.domain.attendance.application.dto.request import io.swagger.v3.oas.annotations.media.Schema -import jakarta.validation.constraints.NotNull import jakarta.validation.constraints.Pattern data class UpdateAttendanceStatusRequest( @field:Schema(description = "출석 ID", example = "1") - @field:NotNull val attendanceId: Long, @field:Schema(description = "변경할 출석 상태", example = "ATTEND") - @field:NotNull @field:Pattern(regexp = "ATTEND|ABSENT") val status: String, ) From db9f7f3eb813adcc0bb21bbdcd2b7c64e496896e Mon Sep 17 00:00:00 2001 From: soo0711 Date: Wed, 18 Feb 2026 14:57:56 +0900 Subject: [PATCH 39/45] =?UTF-8?q?refactor:=20toMainResponse=EB=A5=BC=20toS?= =?UTF-8?q?ummaryResponse=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/mapper/AttendanceMapper.kt | 2 +- .../usecase/query/GetAttendanceQueryService.kt | 2 +- .../application/mapper/AttendanceMapperTest.kt | 10 +++++----- .../usecase/query/GetAttendanceQueryServiceTest.kt | 12 ++++++------ 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/main/kotlin/com/weeth/domain/attendance/application/mapper/AttendanceMapper.kt b/src/main/kotlin/com/weeth/domain/attendance/application/mapper/AttendanceMapper.kt index 759283a0..2d7634d8 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/application/mapper/AttendanceMapper.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/application/mapper/AttendanceMapper.kt @@ -10,7 +10,7 @@ import org.springframework.stereotype.Component @Component class AttendanceMapper { - fun toMainResponse( + fun toSummaryResponse( user: User, attendance: Attendance?, isAdmin: Boolean = false, diff --git a/src/main/kotlin/com/weeth/domain/attendance/application/usecase/query/GetAttendanceQueryService.kt b/src/main/kotlin/com/weeth/domain/attendance/application/usecase/query/GetAttendanceQueryService.kt index e499bb32..babce244 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/application/usecase/query/GetAttendanceQueryService.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/application/usecase/query/GetAttendanceQueryService.kt @@ -34,7 +34,7 @@ class GetAttendanceQueryService( today.plusDays(1).atStartOfDay(), ) - return mapper.toMainResponse(user, todayAttendance, isAdmin = user.role == Role.ADMIN) + return mapper.toSummaryResponse(user, todayAttendance, isAdmin = user.role == Role.ADMIN) } fun findAllDetailsByCurrentCardinal(userId: Long): AttendanceDetailResponse { diff --git a/src/test/kotlin/com/weeth/domain/attendance/application/mapper/AttendanceMapperTest.kt b/src/test/kotlin/com/weeth/domain/attendance/application/mapper/AttendanceMapperTest.kt index 5fe56a74..af4d0241 100644 --- a/src/test/kotlin/com/weeth/domain/attendance/application/mapper/AttendanceMapperTest.kt +++ b/src/test/kotlin/com/weeth/domain/attendance/application/mapper/AttendanceMapperTest.kt @@ -20,14 +20,14 @@ class AttendanceMapperTest : val mapper = AttendanceMapper() - describe("toMainResponse") { + describe("toSummaryResponse") { it("사용자 + 당일 출석 객체를 MainResponse로 매핑한다") { val today = LocalDate.now() val meeting = createOneDayMeeting(today, 1, 1111, "Today") val user = createActiveUserWithAttendances("이지훈", listOf(meeting)) val attendance = user.attendances[0] - val main = mapper.toMainResponse(user, attendance) + val main = mapper.toSummaryResponse(user, attendance) main.shouldNotBeNull() main.title shouldBe meeting.title @@ -40,7 +40,7 @@ class AttendanceMapperTest : it("attendance가 null이면 필드는 null로 매핑") { val user = createActiveUser("이지훈") - val main = mapper.toMainResponse(user, null) + val main = mapper.toSummaryResponse(user, null) main.shouldNotBeNull() main.title.shouldBeNull() @@ -55,7 +55,7 @@ class AttendanceMapperTest : val user = createActiveUserWithAttendances("일반유저", listOf(meeting)) val attendance = user.attendances[0] - val main = mapper.toMainResponse(user, attendance) + val main = mapper.toSummaryResponse(user, attendance) main.shouldNotBeNull() main.code.shouldBeNull() @@ -70,7 +70,7 @@ class AttendanceMapperTest : val adminUser = createAdminUserWithAttendances("관리자", listOf(meeting)) val attendance = adminUser.attendances[0] - val main = mapper.toMainResponse(adminUser, attendance, isAdmin = true) + val main = mapper.toSummaryResponse(adminUser, attendance, isAdmin = true) main.shouldNotBeNull() main.code shouldBe expectedCode diff --git a/src/test/kotlin/com/weeth/domain/attendance/application/usecase/query/GetAttendanceQueryServiceTest.kt b/src/test/kotlin/com/weeth/domain/attendance/application/usecase/query/GetAttendanceQueryServiceTest.kt index 4abdc16c..265baebd 100644 --- a/src/test/kotlin/com/weeth/domain/attendance/application/usecase/query/GetAttendanceQueryServiceTest.kt +++ b/src/test/kotlin/com/weeth/domain/attendance/application/usecase/query/GetAttendanceQueryServiceTest.kt @@ -41,33 +41,33 @@ class GetAttendanceQueryServiceTest : val userId = 10L describe("find") { - it("오늘 출석 정보가 있으면 mapper.toMainResponse(user, attendance) 호출") { + it("오늘 출석 정보가 있으면 mapper.toSummaryResponse(user, attendance) 호출") { val user = createActiveUser("이지훈") val todayAttendance = mockk() val mapped = mockk() every { userGetService.find(userId) } returns user every { attendanceRepository.findTodayByUserId(eq(userId), any(), any()) } returns todayAttendance - every { attendanceMapper.toMainResponse(eq(user), eq(todayAttendance)) } returns mapped + every { attendanceMapper.toSummaryResponse(eq(user), eq(todayAttendance)) } returns mapped val actual = queryService.find(userId) actual shouldBe mapped - verify { attendanceMapper.toMainResponse(eq(user), eq(todayAttendance)) } + verify { attendanceMapper.toSummaryResponse(eq(user), eq(todayAttendance)) } } - it("오늘 출석이 없다면 mapper.toMainResponse(user, null) 호출") { + it("오늘 출석이 없다면 mapper.toSummaryResponse(user, null) 호출") { val user = createActiveUser("이지훈") val mapped = mockk() every { userGetService.find(userId) } returns user every { attendanceRepository.findTodayByUserId(eq(userId), any(), any()) } returns null - every { attendanceMapper.toMainResponse(user, null) } returns mapped + every { attendanceMapper.toSummaryResponse(user, null) } returns mapped val actual = queryService.find(userId) actual shouldBe mapped - verify { attendanceMapper.toMainResponse(user, null) } + verify { attendanceMapper.toSummaryResponse(user, null) } } } From 675122f882c0a9a4eef4ef99438b788430b6246b Mon Sep 17 00:00:00 2001 From: soo0711 Date: Wed, 18 Feb 2026 15:27:06 +0900 Subject: [PATCH 40/45] =?UTF-8?q?stye:=20kotlin=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=ED=8F=AC=EB=A7=B7=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/mapper/AttendanceMapper.kt | 2 +- .../query/GetAttendanceQueryService.kt | 3 +- .../service/scheduler/AttendanceScheduler.kt | 62 +++++++++---------- .../mapper/AttendanceMapperTest.kt | 1 - .../command/CheckInAttendanceUseCaseTest.kt | 2 +- .../query/GetAttendanceQueryServiceTest.kt | 2 +- 6 files changed, 36 insertions(+), 36 deletions(-) diff --git a/src/main/kotlin/com/weeth/domain/attendance/application/mapper/AttendanceMapper.kt b/src/main/kotlin/com/weeth/domain/attendance/application/mapper/AttendanceMapper.kt index 2d7634d8..a60bc189 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/application/mapper/AttendanceMapper.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/application/mapper/AttendanceMapper.kt @@ -2,8 +2,8 @@ package com.weeth.domain.attendance.application.mapper import com.weeth.domain.attendance.application.dto.response.AttendanceDetailResponse import com.weeth.domain.attendance.application.dto.response.AttendanceInfoResponse -import com.weeth.domain.attendance.application.dto.response.AttendanceSummaryResponse import com.weeth.domain.attendance.application.dto.response.AttendanceResponse +import com.weeth.domain.attendance.application.dto.response.AttendanceSummaryResponse import com.weeth.domain.attendance.domain.entity.Attendance import com.weeth.domain.user.domain.entity.User import org.springframework.stereotype.Component diff --git a/src/main/kotlin/com/weeth/domain/attendance/application/usecase/query/GetAttendanceQueryService.kt b/src/main/kotlin/com/weeth/domain/attendance/application/usecase/query/GetAttendanceQueryService.kt index babce244..145ddd3a 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/application/usecase/query/GetAttendanceQueryService.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/application/usecase/query/GetAttendanceQueryService.kt @@ -42,7 +42,8 @@ class GetAttendanceQueryService( val currentCardinal = userCardinalGetService.getCurrentCardinal(user) val responses = - attendanceRepository.findAllByUserIdAndCardinal(userId, currentCardinal.cardinalNumber) + attendanceRepository + .findAllByUserIdAndCardinal(userId, currentCardinal.cardinalNumber) .map(mapper::toResponse) return mapper.toDetailResponse(user, responses) diff --git a/src/main/kotlin/com/weeth/domain/attendance/domain/service/scheduler/AttendanceScheduler.kt b/src/main/kotlin/com/weeth/domain/attendance/domain/service/scheduler/AttendanceScheduler.kt index b6301225..93db928a 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/domain/service/scheduler/AttendanceScheduler.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/domain/service/scheduler/AttendanceScheduler.kt @@ -1,31 +1,31 @@ -package com.weeth.domain.attendance.domain.service.scheduler - -import com.weeth.domain.attendance.domain.repository.AttendanceRepository -import com.weeth.domain.schedule.domain.service.MeetingGetService -import com.weeth.domain.user.domain.entity.enums.Status -import org.springframework.transaction.annotation.Transactional -import org.springframework.scheduling.annotation.Scheduled -import org.springframework.stereotype.Service - -@Service -class AttendanceScheduler( - private val meetingGetService: MeetingGetService, - private val attendanceRepository: AttendanceRepository, -) { - @Transactional - @Scheduled(cron = "0 0 22 * * THU", zone = "Asia/Seoul") - fun autoCloseAttendance() { - val meetings = meetingGetService.findAllOpenMeetingsBeforeNow() - - meetings.forEach { meeting -> - meeting.close() - val attendanceList = attendanceRepository.findAllByMeetingAndUserStatus(meeting, Status.ACTIVE) - attendanceList - .filter { it.isPending } - .forEach { attendance -> - attendance.close() - attendance.user.absent() - } - } - } -} +package com.weeth.domain.attendance.domain.service.scheduler + +import com.weeth.domain.attendance.domain.repository.AttendanceRepository +import com.weeth.domain.schedule.domain.service.MeetingGetService +import com.weeth.domain.user.domain.entity.enums.Status +import org.springframework.scheduling.annotation.Scheduled +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +@Service +class AttendanceScheduler( + private val meetingGetService: MeetingGetService, + private val attendanceRepository: AttendanceRepository, +) { + @Transactional + @Scheduled(cron = "0 0 22 * * THU", zone = "Asia/Seoul") + fun autoCloseAttendance() { + val meetings = meetingGetService.findAllOpenMeetingsBeforeNow() + + meetings.forEach { meeting -> + meeting.close() + val attendanceList = attendanceRepository.findAllByMeetingAndUserStatus(meeting, Status.ACTIVE) + attendanceList + .filter { it.isPending } + .forEach { attendance -> + attendance.close() + attendance.user.absent() + } + } + } +} diff --git a/src/test/kotlin/com/weeth/domain/attendance/application/mapper/AttendanceMapperTest.kt b/src/test/kotlin/com/weeth/domain/attendance/application/mapper/AttendanceMapperTest.kt index af4d0241..f737afe3 100644 --- a/src/test/kotlin/com/weeth/domain/attendance/application/mapper/AttendanceMapperTest.kt +++ b/src/test/kotlin/com/weeth/domain/attendance/application/mapper/AttendanceMapperTest.kt @@ -136,5 +136,4 @@ class AttendanceMapperTest : info.name shouldBe user.name } } - }) diff --git a/src/test/kotlin/com/weeth/domain/attendance/application/usecase/command/CheckInAttendanceUseCaseTest.kt b/src/test/kotlin/com/weeth/domain/attendance/application/usecase/command/CheckInAttendanceUseCaseTest.kt index aa0fa00b..94e004b7 100644 --- a/src/test/kotlin/com/weeth/domain/attendance/application/usecase/command/CheckInAttendanceUseCaseTest.kt +++ b/src/test/kotlin/com/weeth/domain/attendance/application/usecase/command/CheckInAttendanceUseCaseTest.kt @@ -90,4 +90,4 @@ class CheckInAttendanceUseCaseTest : } } } - }) \ No newline at end of file + }) diff --git a/src/test/kotlin/com/weeth/domain/attendance/application/usecase/query/GetAttendanceQueryServiceTest.kt b/src/test/kotlin/com/weeth/domain/attendance/application/usecase/query/GetAttendanceQueryServiceTest.kt index 265baebd..694f9476 100644 --- a/src/test/kotlin/com/weeth/domain/attendance/application/usecase/query/GetAttendanceQueryServiceTest.kt +++ b/src/test/kotlin/com/weeth/domain/attendance/application/usecase/query/GetAttendanceQueryServiceTest.kt @@ -2,8 +2,8 @@ package com.weeth.domain.attendance.application.usecase.query import com.weeth.domain.attendance.application.dto.response.AttendanceDetailResponse import com.weeth.domain.attendance.application.dto.response.AttendanceInfoResponse -import com.weeth.domain.attendance.application.dto.response.AttendanceSummaryResponse import com.weeth.domain.attendance.application.dto.response.AttendanceResponse +import com.weeth.domain.attendance.application.dto.response.AttendanceSummaryResponse import com.weeth.domain.attendance.application.mapper.AttendanceMapper import com.weeth.domain.attendance.domain.entity.Attendance import com.weeth.domain.attendance.domain.repository.AttendanceRepository From dffb93c7f031b32c4ec6c46c6411f8552fb77622 Mon Sep 17 00:00:00 2001 From: soo0711 Date: Wed, 18 Feb 2026 15:49:27 +0900 Subject: [PATCH 41/45] =?UTF-8?q?test:=20toSummaryResponse=20mock=EC=97=90?= =?UTF-8?q?=20isAdmin=20=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0=20=EB=AA=85?= =?UTF-8?q?=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../usecase/query/GetAttendanceQueryServiceTest.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/test/kotlin/com/weeth/domain/attendance/application/usecase/query/GetAttendanceQueryServiceTest.kt b/src/test/kotlin/com/weeth/domain/attendance/application/usecase/query/GetAttendanceQueryServiceTest.kt index 694f9476..23e87d6a 100644 --- a/src/test/kotlin/com/weeth/domain/attendance/application/usecase/query/GetAttendanceQueryServiceTest.kt +++ b/src/test/kotlin/com/weeth/domain/attendance/application/usecase/query/GetAttendanceQueryServiceTest.kt @@ -41,33 +41,33 @@ class GetAttendanceQueryServiceTest : val userId = 10L describe("find") { - it("오늘 출석 정보가 있으면 mapper.toSummaryResponse(user, attendance) 호출") { + it("오늘 출석 정보가 있으면 mapper.toSummaryResponse(user, attendance, isAdmin=false) 호출") { val user = createActiveUser("이지훈") val todayAttendance = mockk() val mapped = mockk() every { userGetService.find(userId) } returns user every { attendanceRepository.findTodayByUserId(eq(userId), any(), any()) } returns todayAttendance - every { attendanceMapper.toSummaryResponse(eq(user), eq(todayAttendance)) } returns mapped + every { attendanceMapper.toSummaryResponse(eq(user), eq(todayAttendance), eq(false)) } returns mapped val actual = queryService.find(userId) actual shouldBe mapped - verify { attendanceMapper.toSummaryResponse(eq(user), eq(todayAttendance)) } + verify { attendanceMapper.toSummaryResponse(eq(user), eq(todayAttendance), eq(false)) } } - it("오늘 출석이 없다면 mapper.toSummaryResponse(user, null) 호출") { + it("오늘 출석이 없다면 mapper.toSummaryResponse(user, null, isAdmin=false) 호출") { val user = createActiveUser("이지훈") val mapped = mockk() every { userGetService.find(userId) } returns user every { attendanceRepository.findTodayByUserId(eq(userId), any(), any()) } returns null - every { attendanceMapper.toSummaryResponse(user, null) } returns mapped + every { attendanceMapper.toSummaryResponse(user, null, false) } returns mapped val actual = queryService.find(userId) actual shouldBe mapped - verify { attendanceMapper.toSummaryResponse(user, null) } + verify { attendanceMapper.toSummaryResponse(user, null, false) } } } From 37875a0a550a4f24231cdf056a3ea67eead2b5d4 Mon Sep 17 00:00:00 2001 From: soo0711 Date: Wed, 18 Feb 2026 17:37:09 +0900 Subject: [PATCH 42/45] =?UTF-8?q?refactor:=20UseCase=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=EB=AA=85=20execute=EB=A5=BC=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EC=9A=A9=EC=96=B4=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../usecase/command/CheckInAttendanceUseCase.kt | 2 +- .../usecase/command/CloseAttendanceUseCase.kt | 2 +- .../usecase/command/UpdateAttendanceStatusUseCase.kt | 2 +- .../presentation/AttendanceAdminController.kt | 4 ++-- .../attendance/presentation/AttendanceController.kt | 2 +- .../usecase/command/CheckInAttendanceUseCaseTest.kt | 10 +++++----- .../usecase/command/CloseAttendanceUseCaseTest.kt | 6 +++--- .../command/UpdateAttendanceStatusUseCaseTest.kt | 8 ++++---- 8 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/main/kotlin/com/weeth/domain/attendance/application/usecase/command/CheckInAttendanceUseCase.kt b/src/main/kotlin/com/weeth/domain/attendance/application/usecase/command/CheckInAttendanceUseCase.kt index e3378ea1..4b955c71 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/application/usecase/command/CheckInAttendanceUseCase.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/application/usecase/command/CheckInAttendanceUseCase.kt @@ -15,7 +15,7 @@ class CheckInAttendanceUseCase( private val attendanceRepository: AttendanceRepository, ) { @Transactional - fun execute( + fun checkIn( userId: Long, code: Int, ) { diff --git a/src/main/kotlin/com/weeth/domain/attendance/application/usecase/command/CloseAttendanceUseCase.kt b/src/main/kotlin/com/weeth/domain/attendance/application/usecase/command/CloseAttendanceUseCase.kt index df76ea3c..3913a773 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/application/usecase/command/CloseAttendanceUseCase.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/application/usecase/command/CloseAttendanceUseCase.kt @@ -14,7 +14,7 @@ class CloseAttendanceUseCase( private val attendanceRepository: AttendanceRepository, ) { @Transactional - fun execute( + fun close( now: LocalDate, cardinal: Int, ) { diff --git a/src/main/kotlin/com/weeth/domain/attendance/application/usecase/command/UpdateAttendanceStatusUseCase.kt b/src/main/kotlin/com/weeth/domain/attendance/application/usecase/command/UpdateAttendanceStatusUseCase.kt index 7f81bc1a..8bca08e7 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/application/usecase/command/UpdateAttendanceStatusUseCase.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/application/usecase/command/UpdateAttendanceStatusUseCase.kt @@ -12,7 +12,7 @@ class UpdateAttendanceStatusUseCase( private val attendanceRepository: AttendanceRepository, ) { @Transactional - fun execute(attendanceUpdates: List) { + fun updateStatus(attendanceUpdates: List) { attendanceUpdates.forEach { update -> val attendance = attendanceRepository.findByIdWithUser(update.attendanceId) diff --git a/src/main/kotlin/com/weeth/domain/attendance/presentation/AttendanceAdminController.kt b/src/main/kotlin/com/weeth/domain/attendance/presentation/AttendanceAdminController.kt index 145658da..4e8ca2a2 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/presentation/AttendanceAdminController.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/presentation/AttendanceAdminController.kt @@ -38,7 +38,7 @@ class AttendanceAdminController( @RequestParam now: LocalDate, @RequestParam cardinal: Int, ): CommonResponse { - closeAttendanceUseCase.execute(now, cardinal) + closeAttendanceUseCase.close(now, cardinal) return CommonResponse.success(AttendanceResponseCode.ATTENDANCE_CLOSE_SUCCESS) } @@ -66,7 +66,7 @@ class AttendanceAdminController( fun updateAttendanceStatus( @RequestBody @Valid attendanceUpdates: List, ): CommonResponse { - updateAttendanceStatusUseCase.execute(attendanceUpdates) + updateAttendanceStatusUseCase.updateStatus(attendanceUpdates) return CommonResponse.success(AttendanceResponseCode.ATTENDANCE_UPDATED_SUCCESS) } } diff --git a/src/main/kotlin/com/weeth/domain/attendance/presentation/AttendanceController.kt b/src/main/kotlin/com/weeth/domain/attendance/presentation/AttendanceController.kt index effc7b54..6d3bf2c3 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/presentation/AttendanceController.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/presentation/AttendanceController.kt @@ -32,7 +32,7 @@ class AttendanceController( @Parameter(hidden = true) @CurrentUser userId: Long, @RequestBody checkIn: CheckInRequest, ): CommonResponse { - checkInAttendanceUseCase.execute(userId, checkIn.code) + checkInAttendanceUseCase.checkIn(userId, checkIn.code) return CommonResponse.success(AttendanceResponseCode.ATTENDANCE_CHECKIN_SUCCESS) } diff --git a/src/test/kotlin/com/weeth/domain/attendance/application/usecase/command/CheckInAttendanceUseCaseTest.kt b/src/test/kotlin/com/weeth/domain/attendance/application/usecase/command/CheckInAttendanceUseCaseTest.kt index 94e004b7..4b254509 100644 --- a/src/test/kotlin/com/weeth/domain/attendance/application/usecase/command/CheckInAttendanceUseCaseTest.kt +++ b/src/test/kotlin/com/weeth/domain/attendance/application/usecase/command/CheckInAttendanceUseCaseTest.kt @@ -27,7 +27,7 @@ class CheckInAttendanceUseCaseTest : val useCase = CheckInAttendanceUseCase(userGetService, attendanceRepository) - describe("execute") { + describe("checkIn") { context("진행 중 정기모임이고 코드 일치하며 상태가 ATTEND가 아닐 때") { it("출석 처리된다") { val user = mockk() @@ -39,7 +39,7 @@ class CheckInAttendanceUseCaseTest : every { attendanceRepository.findCurrentByUserId(eq(userId), any(), any()) } returns attendance every { user.attend() } returns Unit - useCase.execute(userId, 1234) + useCase.checkIn(userId, 1234) verify { attendance.attend() } verify { user.attend() } @@ -53,7 +53,7 @@ class CheckInAttendanceUseCaseTest : every { attendanceRepository.findCurrentByUserId(eq(userId), any(), any()) } returns null shouldThrow { - useCase.execute(userId, 1234) + useCase.checkIn(userId, 1234) } } } @@ -68,7 +68,7 @@ class CheckInAttendanceUseCaseTest : every { attendanceRepository.findCurrentByUserId(eq(userId), any(), any()) } returns attendance shouldThrow { - useCase.execute(userId, 9999) + useCase.checkIn(userId, 9999) } } } @@ -83,7 +83,7 @@ class CheckInAttendanceUseCaseTest : every { userGetService.find(userId) } returns user every { attendanceRepository.findCurrentByUserId(eq(userId), any(), any()) } returns attendance - useCase.execute(userId, 1234) + useCase.checkIn(userId, 1234) verify(exactly = 0) { attendance.attend() } verify(exactly = 0) { user.attend() } diff --git a/src/test/kotlin/com/weeth/domain/attendance/application/usecase/command/CloseAttendanceUseCaseTest.kt b/src/test/kotlin/com/weeth/domain/attendance/application/usecase/command/CloseAttendanceUseCaseTest.kt index 1e79674e..63c3dd68 100644 --- a/src/test/kotlin/com/weeth/domain/attendance/application/usecase/command/CloseAttendanceUseCaseTest.kt +++ b/src/test/kotlin/com/weeth/domain/attendance/application/usecase/command/CloseAttendanceUseCaseTest.kt @@ -22,7 +22,7 @@ class CloseAttendanceUseCaseTest : val useCase = CloseAttendanceUseCase(meetingGetService, attendanceRepository) - describe("execute") { + describe("close") { it("당일 정기모임을 찾아 pending 출석을 close") { val now = LocalDate.now() val targetMeeting = createOneDayMeeting(now, 1, 1111, "Today") @@ -41,7 +41,7 @@ class CloseAttendanceUseCaseTest : attendanceRepository.findAllByMeetingAndUserStatus(targetMeeting, Status.ACTIVE) } returns listOf(pendingAttendance, attendedAttendance) - useCase.execute(now, 1) + useCase.close(now, 1) verify { pendingAttendance.close() } verify { pendingUser.absent() } @@ -55,7 +55,7 @@ class CloseAttendanceUseCaseTest : every { meetingGetService.find(1) } returns listOf(otherDayMeeting) shouldThrow { - useCase.execute(now, 1) + useCase.close(now, 1) } } } diff --git a/src/test/kotlin/com/weeth/domain/attendance/application/usecase/command/UpdateAttendanceStatusUseCaseTest.kt b/src/test/kotlin/com/weeth/domain/attendance/application/usecase/command/UpdateAttendanceStatusUseCaseTest.kt index 6ac5d1a6..ebbecb4e 100644 --- a/src/test/kotlin/com/weeth/domain/attendance/application/usecase/command/UpdateAttendanceStatusUseCaseTest.kt +++ b/src/test/kotlin/com/weeth/domain/attendance/application/usecase/command/UpdateAttendanceStatusUseCaseTest.kt @@ -18,7 +18,7 @@ class UpdateAttendanceStatusUseCaseTest : val useCase = UpdateAttendanceStatusUseCase(attendanceRepository) - describe("execute") { + describe("updateStatus") { context("ABSENT로 변경 시") { it("close + removeAttend + absent 호출") { val user = mockk(relaxUnitFun = true) @@ -28,7 +28,7 @@ class UpdateAttendanceStatusUseCaseTest : every { attendanceRepository.findByIdWithUser(1L) } returns attendance val request = UpdateAttendanceStatusRequest(attendanceId = 1L, status = "ABSENT") - useCase.execute(listOf(request)) + useCase.updateStatus(listOf(request)) verify { attendance.close() } verify { user.removeAttend() } @@ -45,7 +45,7 @@ class UpdateAttendanceStatusUseCaseTest : every { attendanceRepository.findByIdWithUser(1L) } returns attendance val request = UpdateAttendanceStatusRequest(attendanceId = 1L, status = "ATTEND") - useCase.execute(listOf(request)) + useCase.updateStatus(listOf(request)) verify { attendance.attend() } verify { user.removeAbsent() } @@ -60,7 +60,7 @@ class UpdateAttendanceStatusUseCaseTest : val request = UpdateAttendanceStatusRequest(attendanceId = 999L, status = "ABSENT") shouldThrow { - useCase.execute(listOf(request)) + useCase.updateStatus(listOf(request)) } } } From b1cb8cfc5e2844fca84a2d10e177141701c6a399 Mon Sep 17 00:00:00 2001 From: soo0711 Date: Wed, 18 Feb 2026 17:38:40 +0900 Subject: [PATCH 43/45] =?UTF-8?q?=20refactor:=20GetAttendanceQueryService?= =?UTF-8?q?=20=EB=A9=94=EC=84=9C=EB=93=9C=EB=AA=85=EC=9D=84=20=EB=8F=84?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=20=EC=9A=A9=EC=96=B4=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/usecase/query/GetAttendanceQueryService.kt | 2 +- .../domain/attendance/presentation/AttendanceController.kt | 2 +- .../usecase/query/GetAttendanceQueryServiceTest.kt | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/com/weeth/domain/attendance/application/usecase/query/GetAttendanceQueryService.kt b/src/main/kotlin/com/weeth/domain/attendance/application/usecase/query/GetAttendanceQueryService.kt index 145ddd3a..6a8c63cd 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/application/usecase/query/GetAttendanceQueryService.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/application/usecase/query/GetAttendanceQueryService.kt @@ -23,7 +23,7 @@ class GetAttendanceQueryService( private val attendanceRepository: AttendanceRepository, private val mapper: AttendanceMapper, ) { - fun find(userId: Long): AttendanceSummaryResponse { + fun findAttendance(userId: Long): AttendanceSummaryResponse { val user = userGetService.find(userId) val today = LocalDate.now() diff --git a/src/main/kotlin/com/weeth/domain/attendance/presentation/AttendanceController.kt b/src/main/kotlin/com/weeth/domain/attendance/presentation/AttendanceController.kt index 6d3bf2c3..8a1256b7 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/presentation/AttendanceController.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/presentation/AttendanceController.kt @@ -41,7 +41,7 @@ class AttendanceController( fun find( @Parameter(hidden = true) @CurrentUser userId: Long, ): CommonResponse = - CommonResponse.success(AttendanceResponseCode.ATTENDANCE_FIND_SUCCESS, getAttendanceQueryService.find(userId)) + CommonResponse.success(AttendanceResponseCode.ATTENDANCE_FIND_SUCCESS, getAttendanceQueryService.findAttendance(userId)) @GetMapping("/detail") @Operation(summary = "출석 내역 상세조회") diff --git a/src/test/kotlin/com/weeth/domain/attendance/application/usecase/query/GetAttendanceQueryServiceTest.kt b/src/test/kotlin/com/weeth/domain/attendance/application/usecase/query/GetAttendanceQueryServiceTest.kt index 23e87d6a..6c600674 100644 --- a/src/test/kotlin/com/weeth/domain/attendance/application/usecase/query/GetAttendanceQueryServiceTest.kt +++ b/src/test/kotlin/com/weeth/domain/attendance/application/usecase/query/GetAttendanceQueryServiceTest.kt @@ -50,7 +50,7 @@ class GetAttendanceQueryServiceTest : every { attendanceRepository.findTodayByUserId(eq(userId), any(), any()) } returns todayAttendance every { attendanceMapper.toSummaryResponse(eq(user), eq(todayAttendance), eq(false)) } returns mapped - val actual = queryService.find(userId) + val actual = queryService.findAttendance(userId) actual shouldBe mapped verify { attendanceMapper.toSummaryResponse(eq(user), eq(todayAttendance), eq(false)) } @@ -64,7 +64,7 @@ class GetAttendanceQueryServiceTest : every { attendanceRepository.findTodayByUserId(eq(userId), any(), any()) } returns null every { attendanceMapper.toSummaryResponse(user, null, false) } returns mapped - val actual = queryService.find(userId) + val actual = queryService.findAttendance(userId) actual shouldBe mapped verify { attendanceMapper.toSummaryResponse(user, null, false) } From 49334cc3816b1e6a58ba19a5c1325b7fad183059 Mon Sep 17 00:00:00 2001 From: soo0711 Date: Wed, 18 Feb 2026 18:00:19 +0900 Subject: [PATCH 44/45] =?UTF-8?q?refactor:=20AttendanceScheduler=EB=A5=BC?= =?UTF-8?q?=20infrastructure=EB=A1=9C=20=EC=9D=B4=EB=8F=99,=20Usecase?= =?UTF-8?q?=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../usecase/command/CloseAttendanceUseCase.kt | 18 ++++++++++- .../service/scheduler/AttendanceScheduler.kt | 31 ------------------- .../infrastructure/AttendanceScheduler.kt | 15 +++++++++ 3 files changed, 32 insertions(+), 32 deletions(-) delete mode 100644 src/main/kotlin/com/weeth/domain/attendance/domain/service/scheduler/AttendanceScheduler.kt create mode 100644 src/main/kotlin/com/weeth/domain/attendance/infrastructure/AttendanceScheduler.kt diff --git a/src/main/kotlin/com/weeth/domain/attendance/application/usecase/command/CloseAttendanceUseCase.kt b/src/main/kotlin/com/weeth/domain/attendance/application/usecase/command/CloseAttendanceUseCase.kt index 3913a773..7d860987 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/application/usecase/command/CloseAttendanceUseCase.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/application/usecase/command/CloseAttendanceUseCase.kt @@ -1,5 +1,6 @@ package com.weeth.domain.attendance.application.usecase.command +import com.weeth.domain.attendance.domain.entity.Attendance import com.weeth.domain.attendance.domain.repository.AttendanceRepository import com.weeth.domain.schedule.application.exception.MeetingNotFoundException import com.weeth.domain.schedule.domain.service.MeetingGetService @@ -27,7 +28,22 @@ class CloseAttendanceUseCase( } ?: throw MeetingNotFoundException() val attendanceList = attendanceRepository.findAllByMeetingAndUserStatus(targetMeeting, Status.ACTIVE) - attendanceList + closePendingAttendances(attendanceList) + } + + @Transactional + fun autoClose() { + val meetings = meetingGetService.findAllOpenMeetingsBeforeNow() + + meetings.forEach { meeting -> + meeting.close() + val attendanceList = attendanceRepository.findAllByMeetingAndUserStatus(meeting, Status.ACTIVE) + closePendingAttendances(attendanceList) + } + } + + private fun closePendingAttendances(attendances: List) { + attendances .filter { it.isPending } .forEach { attendance -> attendance.close() diff --git a/src/main/kotlin/com/weeth/domain/attendance/domain/service/scheduler/AttendanceScheduler.kt b/src/main/kotlin/com/weeth/domain/attendance/domain/service/scheduler/AttendanceScheduler.kt deleted file mode 100644 index 93db928a..00000000 --- a/src/main/kotlin/com/weeth/domain/attendance/domain/service/scheduler/AttendanceScheduler.kt +++ /dev/null @@ -1,31 +0,0 @@ -package com.weeth.domain.attendance.domain.service.scheduler - -import com.weeth.domain.attendance.domain.repository.AttendanceRepository -import com.weeth.domain.schedule.domain.service.MeetingGetService -import com.weeth.domain.user.domain.entity.enums.Status -import org.springframework.scheduling.annotation.Scheduled -import org.springframework.stereotype.Service -import org.springframework.transaction.annotation.Transactional - -@Service -class AttendanceScheduler( - private val meetingGetService: MeetingGetService, - private val attendanceRepository: AttendanceRepository, -) { - @Transactional - @Scheduled(cron = "0 0 22 * * THU", zone = "Asia/Seoul") - fun autoCloseAttendance() { - val meetings = meetingGetService.findAllOpenMeetingsBeforeNow() - - meetings.forEach { meeting -> - meeting.close() - val attendanceList = attendanceRepository.findAllByMeetingAndUserStatus(meeting, Status.ACTIVE) - attendanceList - .filter { it.isPending } - .forEach { attendance -> - attendance.close() - attendance.user.absent() - } - } - } -} diff --git a/src/main/kotlin/com/weeth/domain/attendance/infrastructure/AttendanceScheduler.kt b/src/main/kotlin/com/weeth/domain/attendance/infrastructure/AttendanceScheduler.kt new file mode 100644 index 00000000..a41113e6 --- /dev/null +++ b/src/main/kotlin/com/weeth/domain/attendance/infrastructure/AttendanceScheduler.kt @@ -0,0 +1,15 @@ +package com.weeth.domain.attendance.infrastructure + +import com.weeth.domain.attendance.application.usecase.command.CloseAttendanceUseCase +import org.springframework.scheduling.annotation.Scheduled +import org.springframework.stereotype.Component + +@Component +class AttendanceScheduler( + private val closeAttendanceUseCase: CloseAttendanceUseCase, +) { + @Scheduled(cron = "0 0 22 * * THU", zone = "Asia/Seoul") + fun autoCloseAttendance() { + closeAttendanceUseCase.autoClose() + } +} From 20ce82cbff25f4dbe90323fa7dc273da5ca0169b Mon Sep 17 00:00:00 2001 From: soo0711 Date: Thu, 19 Feb 2026 01:47:59 +0900 Subject: [PATCH 45/45] =?UTF-8?q?refactor:=20=EC=95=84=ED=82=A4=ED=85=8D?= =?UTF-8?q?=EC=B2=98=20=EA=B8=B0=EC=A4=80=EC=97=90=20=EB=A7=9E=EA=B2=8C=20?= =?UTF-8?q?attendance=20enum=20=ED=8C=A8=ED=82=A4=EC=A7=80=20=EA=B5=AC?= =?UTF-8?q?=EC=A1=B0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/dto/response/AttendanceInfoResponse.kt | 2 +- .../application/dto/response/AttendanceResponse.kt | 2 +- .../application/dto/response/AttendanceSummaryResponse.kt | 2 +- .../usecase/command/CheckInAttendanceUseCase.kt | 2 +- .../usecase/command/UpdateAttendanceStatusUseCase.kt | 2 +- .../weeth/domain/attendance/domain/entity/Attendance.kt | 2 +- .../domain/attendance/domain/{entity => }/enums/Status.kt | 2 +- .../attendance/domain/service/AttendanceUpdateService.kt | 2 +- .../usecase/command/CheckInAttendanceUseCaseTest.kt | 7 +------ .../domain/attendance/domain/entity/AttendanceTest.kt | 2 +- 10 files changed, 10 insertions(+), 15 deletions(-) rename src/main/kotlin/com/weeth/domain/attendance/domain/{entity => }/enums/Status.kt (51%) diff --git a/src/main/kotlin/com/weeth/domain/attendance/application/dto/response/AttendanceInfoResponse.kt b/src/main/kotlin/com/weeth/domain/attendance/application/dto/response/AttendanceInfoResponse.kt index 6367f1cd..434ad7a9 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/application/dto/response/AttendanceInfoResponse.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/application/dto/response/AttendanceInfoResponse.kt @@ -1,6 +1,6 @@ package com.weeth.domain.attendance.application.dto.response -import com.weeth.domain.attendance.domain.entity.enums.Status +import com.weeth.domain.attendance.domain.enums.Status import io.swagger.v3.oas.annotations.media.Schema data class AttendanceInfoResponse( diff --git a/src/main/kotlin/com/weeth/domain/attendance/application/dto/response/AttendanceResponse.kt b/src/main/kotlin/com/weeth/domain/attendance/application/dto/response/AttendanceResponse.kt index 49012083..e99559af 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/application/dto/response/AttendanceResponse.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/application/dto/response/AttendanceResponse.kt @@ -1,6 +1,6 @@ package com.weeth.domain.attendance.application.dto.response -import com.weeth.domain.attendance.domain.entity.enums.Status +import com.weeth.domain.attendance.domain.enums.Status import io.swagger.v3.oas.annotations.media.Schema import java.time.LocalDateTime diff --git a/src/main/kotlin/com/weeth/domain/attendance/application/dto/response/AttendanceSummaryResponse.kt b/src/main/kotlin/com/weeth/domain/attendance/application/dto/response/AttendanceSummaryResponse.kt index bdd3b57b..f53b633f 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/application/dto/response/AttendanceSummaryResponse.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/application/dto/response/AttendanceSummaryResponse.kt @@ -1,6 +1,6 @@ package com.weeth.domain.attendance.application.dto.response -import com.weeth.domain.attendance.domain.entity.enums.Status +import com.weeth.domain.attendance.domain.enums.Status import io.swagger.v3.oas.annotations.media.Schema import java.time.LocalDateTime diff --git a/src/main/kotlin/com/weeth/domain/attendance/application/usecase/command/CheckInAttendanceUseCase.kt b/src/main/kotlin/com/weeth/domain/attendance/application/usecase/command/CheckInAttendanceUseCase.kt index 4b955c71..8f92ae0b 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/application/usecase/command/CheckInAttendanceUseCase.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/application/usecase/command/CheckInAttendanceUseCase.kt @@ -2,7 +2,7 @@ package com.weeth.domain.attendance.application.usecase.command import com.weeth.domain.attendance.application.exception.AttendanceCodeMismatchException import com.weeth.domain.attendance.application.exception.AttendanceNotFoundException -import com.weeth.domain.attendance.domain.entity.enums.Status +import com.weeth.domain.attendance.domain.enums.Status import com.weeth.domain.attendance.domain.repository.AttendanceRepository import com.weeth.domain.user.domain.service.UserGetService import org.springframework.stereotype.Service diff --git a/src/main/kotlin/com/weeth/domain/attendance/application/usecase/command/UpdateAttendanceStatusUseCase.kt b/src/main/kotlin/com/weeth/domain/attendance/application/usecase/command/UpdateAttendanceStatusUseCase.kt index 8bca08e7..14d984bf 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/application/usecase/command/UpdateAttendanceStatusUseCase.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/application/usecase/command/UpdateAttendanceStatusUseCase.kt @@ -2,7 +2,7 @@ package com.weeth.domain.attendance.application.usecase.command import com.weeth.domain.attendance.application.dto.request.UpdateAttendanceStatusRequest import com.weeth.domain.attendance.application.exception.AttendanceNotFoundException -import com.weeth.domain.attendance.domain.entity.enums.Status +import com.weeth.domain.attendance.domain.enums.Status import com.weeth.domain.attendance.domain.repository.AttendanceRepository import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional diff --git a/src/main/kotlin/com/weeth/domain/attendance/domain/entity/Attendance.kt b/src/main/kotlin/com/weeth/domain/attendance/domain/entity/Attendance.kt index a4c58a7a..f40184ba 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/domain/entity/Attendance.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/domain/entity/Attendance.kt @@ -1,6 +1,6 @@ package com.weeth.domain.attendance.domain.entity -import com.weeth.domain.attendance.domain.entity.enums.Status +import com.weeth.domain.attendance.domain.enums.Status import com.weeth.domain.schedule.domain.entity.Meeting import com.weeth.domain.user.domain.entity.User import com.weeth.global.common.entity.BaseEntity diff --git a/src/main/kotlin/com/weeth/domain/attendance/domain/entity/enums/Status.kt b/src/main/kotlin/com/weeth/domain/attendance/domain/enums/Status.kt similarity index 51% rename from src/main/kotlin/com/weeth/domain/attendance/domain/entity/enums/Status.kt rename to src/main/kotlin/com/weeth/domain/attendance/domain/enums/Status.kt index 636c0a24..6184bf45 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/domain/entity/enums/Status.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/domain/enums/Status.kt @@ -1,4 +1,4 @@ -package com.weeth.domain.attendance.domain.entity.enums +package com.weeth.domain.attendance.domain.enums enum class Status { ATTEND, diff --git a/src/main/kotlin/com/weeth/domain/attendance/domain/service/AttendanceUpdateService.kt b/src/main/kotlin/com/weeth/domain/attendance/domain/service/AttendanceUpdateService.kt index 4e399b16..cc8f98ac 100644 --- a/src/main/kotlin/com/weeth/domain/attendance/domain/service/AttendanceUpdateService.kt +++ b/src/main/kotlin/com/weeth/domain/attendance/domain/service/AttendanceUpdateService.kt @@ -1,7 +1,7 @@ package com.weeth.domain.attendance.domain.service import com.weeth.domain.attendance.domain.entity.Attendance -import com.weeth.domain.attendance.domain.entity.enums.Status +import com.weeth.domain.attendance.domain.enums.Status import org.springframework.stereotype.Service @Service diff --git a/src/test/kotlin/com/weeth/domain/attendance/application/usecase/command/CheckInAttendanceUseCaseTest.kt b/src/test/kotlin/com/weeth/domain/attendance/application/usecase/command/CheckInAttendanceUseCaseTest.kt index 4b254509..3e96b276 100644 --- a/src/test/kotlin/com/weeth/domain/attendance/application/usecase/command/CheckInAttendanceUseCaseTest.kt +++ b/src/test/kotlin/com/weeth/domain/attendance/application/usecase/command/CheckInAttendanceUseCaseTest.kt @@ -3,20 +3,15 @@ package com.weeth.domain.attendance.application.usecase.command import com.weeth.domain.attendance.application.exception.AttendanceCodeMismatchException import com.weeth.domain.attendance.application.exception.AttendanceNotFoundException import com.weeth.domain.attendance.domain.entity.Attendance -import com.weeth.domain.attendance.domain.entity.enums.Status +import com.weeth.domain.attendance.domain.enums.Status import com.weeth.domain.attendance.domain.repository.AttendanceRepository -import com.weeth.domain.attendance.fixture.AttendanceTestFixture.createAttendance -import com.weeth.domain.attendance.fixture.AttendanceTestFixture.createInProgressMeeting -import com.weeth.domain.attendance.fixture.AttendanceTestFixture.setUserAttendanceStats import com.weeth.domain.user.domain.entity.User import com.weeth.domain.user.domain.service.UserGetService -import io.kotest.assertions.throwables.shouldNotThrowAny import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.DescribeSpec import io.mockk.every import io.mockk.mockk import io.mockk.verify -import java.time.LocalDateTime class CheckInAttendanceUseCaseTest : DescribeSpec({ diff --git a/src/test/kotlin/com/weeth/domain/attendance/domain/entity/AttendanceTest.kt b/src/test/kotlin/com/weeth/domain/attendance/domain/entity/AttendanceTest.kt index 25d51a54..bc61965e 100644 --- a/src/test/kotlin/com/weeth/domain/attendance/domain/entity/AttendanceTest.kt +++ b/src/test/kotlin/com/weeth/domain/attendance/domain/entity/AttendanceTest.kt @@ -1,6 +1,6 @@ package com.weeth.domain.attendance.domain.entity -import com.weeth.domain.attendance.domain.entity.enums.Status +import com.weeth.domain.attendance.domain.enums.Status import com.weeth.domain.attendance.fixture.AttendanceTestFixture.createActiveUser import com.weeth.domain.attendance.fixture.AttendanceTestFixture.createAttendance import com.weeth.domain.schedule.fixture.ScheduleTestFixture.createMeeting