diff --git a/src/main/java/life/mosu/mosuserver/application/admin/AdminService.java b/src/main/java/life/mosu/mosuserver/application/admin/AdminService.java index 5f6c7293..d47dcddb 100644 --- a/src/main/java/life/mosu/mosuserver/application/admin/AdminService.java +++ b/src/main/java/life/mosu/mosuserver/application/admin/AdminService.java @@ -2,10 +2,12 @@ import java.util.List; import life.mosu.mosuserver.domain.admin.ApplicationQueryRepositoryImpl; +import life.mosu.mosuserver.domain.admin.RefundQueryRepositoryImpl; import life.mosu.mosuserver.domain.admin.StudentQueryRepositoryImpl; import life.mosu.mosuserver.presentation.admin.dto.ApplicationExcelDto; import life.mosu.mosuserver.presentation.admin.dto.ApplicationFilter; import life.mosu.mosuserver.presentation.admin.dto.ApplicationListResponse; +import life.mosu.mosuserver.presentation.admin.dto.RefundListResponse; import life.mosu.mosuserver.presentation.admin.dto.SchoolLunchResponse; import life.mosu.mosuserver.presentation.admin.dto.StudentExcelDto; import life.mosu.mosuserver.presentation.admin.dto.StudentFilter; @@ -23,6 +25,7 @@ public class AdminService { private final StudentQueryRepositoryImpl studentQueryRepository; private final ApplicationQueryRepositoryImpl applicationQueryRepository; + private final RefundQueryRepositoryImpl refundQueryRepository; public Page getStudents(StudentFilter filter, Pageable pageable) { return studentQueryRepository.searchAllStudents(filter, pageable); @@ -45,4 +48,8 @@ public List getApplicationExcelData() { return applicationQueryRepository.searchAllApplicationsForExcel(); } + public Page getRefunds(Pageable pageable) { + return refundQueryRepository.searchAllRefunds(pageable); + } + } diff --git a/src/main/java/life/mosu/mosuserver/domain/admin/RefundQueryRepository.java b/src/main/java/life/mosu/mosuserver/domain/admin/RefundQueryRepository.java new file mode 100644 index 00000000..910d0b7b --- /dev/null +++ b/src/main/java/life/mosu/mosuserver/domain/admin/RefundQueryRepository.java @@ -0,0 +1,10 @@ +package life.mosu.mosuserver.domain.admin; + +import life.mosu.mosuserver.presentation.admin.dto.RefundListResponse; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +public interface RefundQueryRepository { + + Page searchAllRefunds(Pageable pageable); +} diff --git a/src/main/java/life/mosu/mosuserver/domain/admin/RefundQueryRepositoryImpl.java b/src/main/java/life/mosu/mosuserver/domain/admin/RefundQueryRepositoryImpl.java new file mode 100644 index 00000000..3853ff13 --- /dev/null +++ b/src/main/java/life/mosu/mosuserver/domain/admin/RefundQueryRepositoryImpl.java @@ -0,0 +1,84 @@ +package life.mosu.mosuserver.domain.admin; + +import static life.mosu.mosuserver.domain.base.BaseTimeEntity.formatDate; + +import com.querydsl.core.Tuple; +import com.querydsl.jpa.impl.JPAQuery; +import com.querydsl.jpa.impl.JPAQueryFactory; +import java.util.List; +import life.mosu.mosuserver.domain.application.QApplicationJpaEntity; +import life.mosu.mosuserver.domain.applicationschool.QApplicationSchoolJpaEntity; +import life.mosu.mosuserver.domain.payment.PaymentMethod; +import life.mosu.mosuserver.domain.payment.QPaymentJpaEntity; +import life.mosu.mosuserver.domain.profile.QProfileJpaEntity; +import life.mosu.mosuserver.domain.refund.QRefundJpaEntity; +import life.mosu.mosuserver.presentation.admin.dto.RefundListResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class RefundQueryRepositoryImpl implements RefundQueryRepository { + + private final JPAQueryFactory queryFactory; + + QRefundJpaEntity refund = QRefundJpaEntity.refundJpaEntity; + QApplicationSchoolJpaEntity appSchool = QApplicationSchoolJpaEntity.applicationSchoolJpaEntity; + QApplicationJpaEntity application = QApplicationJpaEntity.applicationJpaEntity; + QProfileJpaEntity profile = QProfileJpaEntity.profileJpaEntity; + QPaymentJpaEntity payment = QPaymentJpaEntity.paymentJpaEntity; + + @Override + public Page searchAllRefunds(Pageable pageable) { + long total = baseQuery().fetch().size(); + + List content = baseQuery() + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch() + .stream() + .map(this::mapToResponse) + .toList(); + + return new PageImpl<>(content, pageable, total); + + } + + private JPAQuery baseQuery() { + return queryFactory + .select( + refund.id, + appSchool.examinationNumber, + profile.userName, + profile.phoneNumber, + refund.createdAt, + refund.agreedAt, + payment.paymentMethod, + refund.reason + ) + .from(refund) + .leftJoin(appSchool).on(refund.applicationSchoolId.eq(appSchool.id)) + .leftJoin(application).on(appSchool.applicationId.eq(application.id)) + .leftJoin(profile).on(profile.userId.eq(application.userId)) + .leftJoin(payment).on(payment.applicationSchoolId.eq(appSchool.id)); + } + + private RefundListResponse mapToResponse(Tuple tuple) { + PaymentMethod paymentMethod = tuple.get(payment.paymentMethod); + return new RefundListResponse( + tuple.get(refund.id), + tuple.get(appSchool.examinationNumber), + tuple.get(profile.userName), + tuple.get(profile.phoneNumber), + formatDate(tuple.get(refund.createdAt)), + formatDate(tuple.get(refund.agreedAt)), + paymentMethod != null ? paymentMethod.getName() : "N/A", + tuple.get(refund.reason) + ); + } + + +} \ No newline at end of file diff --git a/src/main/java/life/mosu/mosuserver/domain/admin/StudentQueryRepositoryImpl.java b/src/main/java/life/mosu/mosuserver/domain/admin/StudentQueryRepositoryImpl.java index c5dda81f..e77a1ed5 100644 --- a/src/main/java/life/mosu/mosuserver/domain/admin/StudentQueryRepositoryImpl.java +++ b/src/main/java/life/mosu/mosuserver/domain/admin/StudentQueryRepositoryImpl.java @@ -8,6 +8,9 @@ import java.util.List; import java.util.Optional; import life.mosu.mosuserver.domain.application.QApplicationJpaEntity; +import life.mosu.mosuserver.domain.profile.Education; +import life.mosu.mosuserver.domain.profile.Gender; +import life.mosu.mosuserver.domain.profile.Grade; import life.mosu.mosuserver.domain.profile.QProfileJpaEntity; import life.mosu.mosuserver.presentation.admin.dto.StudentExcelDto; import life.mosu.mosuserver.presentation.admin.dto.StudentFilter; @@ -118,31 +121,39 @@ private long getTotalCount(QProfileJpaEntity profile, String name, String phone) } private StudentListResponse mapToResponse(Tuple tuple, QProfileJpaEntity profile) { + Education education = tuple.get(profile.education); + Gender gender = tuple.get(profile.gender); + Grade grade = tuple.get(profile.grade); + Long examCount = Optional.ofNullable(tuple.get(examCountExpr)) .orElse(0L); return new StudentListResponse( tuple.get(profile.userName), tuple.get(profile.birth) != null ? tuple.get(profile.birth).toString() : null, tuple.get(profile.phoneNumber), - tuple.get(profile.gender) != null ? tuple.get(profile.gender).name() : null, - tuple.get(profile.education), + gender != null ? gender.getGenderName() : null, + education != null ? education.getEducationName() : null, tuple.get(profile.schoolInfo.schoolName), - tuple.get(profile.grade), + grade != null ? grade.getGradeName() : null, examCount.intValue() ); } private StudentExcelDto mapToExcel(Tuple tuple, QProfileJpaEntity profile) { + Education education = tuple.get(profile.education); + Gender gender = tuple.get(profile.gender); + Grade grade = tuple.get(profile.grade); + Long examCount = Optional.ofNullable(tuple.get(examCountExpr)) .orElse(0L); return new StudentExcelDto( tuple.get(profile.userName), tuple.get(profile.birth) != null ? tuple.get(profile.birth).toString() : null, tuple.get(profile.phoneNumber), - tuple.get(profile.gender) != null ? tuple.get(profile.gender).name() : null, - tuple.get(profile.education), + gender != null ? gender.getGenderName() : null, + education != null ? education.getEducationName() : null, tuple.get(profile.schoolInfo.schoolName), - tuple.get(profile.grade), + grade != null ? grade.getGradeName() : null, examCount.intValue() ); } diff --git a/src/main/java/life/mosu/mosuserver/domain/base/BaseTimeEntity.java b/src/main/java/life/mosu/mosuserver/domain/base/BaseTimeEntity.java index 9082fa5f..8862c638 100644 --- a/src/main/java/life/mosu/mosuserver/domain/base/BaseTimeEntity.java +++ b/src/main/java/life/mosu/mosuserver/domain/base/BaseTimeEntity.java @@ -23,6 +23,10 @@ public abstract class BaseTimeEntity { @Column(name = "updated_at") private LocalDateTime updatedAt; + public static String formatDate(java.time.LocalDateTime dateTime) { + return dateTime != null ? dateTime.toLocalDate().toString() : null; + } + public String getCreatedAt() { return createdAt != null ? createdAt.format( DateTimeFormatter.ofPattern("yyyy-MM-dd")) : null; diff --git a/src/main/java/life/mosu/mosuserver/domain/inquiry/InquiryQueryRepositoryImpl.java b/src/main/java/life/mosu/mosuserver/domain/inquiry/InquiryQueryRepositoryImpl.java index c7f42387..a1dbe2a5 100644 --- a/src/main/java/life/mosu/mosuserver/domain/inquiry/InquiryQueryRepositoryImpl.java +++ b/src/main/java/life/mosu/mosuserver/domain/inquiry/InquiryQueryRepositoryImpl.java @@ -1,12 +1,13 @@ package life.mosu.mosuserver.domain.inquiry; +import static life.mosu.mosuserver.domain.base.BaseTimeEntity.formatDate; + import com.querydsl.core.Tuple; import com.querydsl.core.types.OrderSpecifier; import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.core.types.dsl.ComparableExpressionBase; import com.querydsl.jpa.impl.JPAQuery; import com.querydsl.jpa.impl.JPAQueryFactory; -import java.time.format.DateTimeFormatter; import java.util.List; import java.util.Optional; import life.mosu.mosuserver.presentation.inquiry.dto.InquiryResponse; @@ -20,8 +21,6 @@ @RequiredArgsConstructor public class InquiryQueryRepositoryImpl implements InquiryQueryRepository { - private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); - private final JPAQueryFactory queryFactory; private final QInquiryJpaEntity inquiry = QInquiryJpaEntity.inquiryJpaEntity; @@ -92,14 +91,14 @@ private long getTotalCount(QInquiryJpaEntity inquiry, InquiryStatus status) { } private InquiryResponse mapToResponse(Tuple tuple) { + InquiryStatus status = tuple.get(inquiry.status); return new InquiryResponse( tuple.get(inquiry.id), tuple.get(inquiry.title), tuple.get(inquiry.content), tuple.get(inquiry.author), - tuple.get(inquiry.status), - tuple.get(inquiry.createdAt) != null ? tuple.get(inquiry.createdAt) - .format(FORMATTER) - : null); + status != null ? status.getStatusName() : "N/A", + formatDate(tuple.get(inquiry.createdAt)) + ); } } diff --git a/src/main/java/life/mosu/mosuserver/domain/inquiry/InquiryStatus.java b/src/main/java/life/mosu/mosuserver/domain/inquiry/InquiryStatus.java index 41cd07f9..555cb803 100644 --- a/src/main/java/life/mosu/mosuserver/domain/inquiry/InquiryStatus.java +++ b/src/main/java/life/mosu/mosuserver/domain/inquiry/InquiryStatus.java @@ -1,6 +1,13 @@ package life.mosu.mosuserver.domain.inquiry; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor public enum InquiryStatus { - PENDING, - COMPLETED, + PENDING("미응답"), + COMPLETED("완료"); + + private final String statusName; } diff --git a/src/main/java/life/mosu/mosuserver/domain/payment/PaymentMethod.java b/src/main/java/life/mosu/mosuserver/domain/payment/PaymentMethod.java index 4f9e3c23..e26f5f9e 100644 --- a/src/main/java/life/mosu/mosuserver/domain/payment/PaymentMethod.java +++ b/src/main/java/life/mosu/mosuserver/domain/payment/PaymentMethod.java @@ -1,11 +1,13 @@ package life.mosu.mosuserver.domain.payment; import java.util.Arrays; +import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @RequiredArgsConstructor @Slf4j +@Getter public enum PaymentMethod { EASY_PAY("간편결제"), CARD("카드"), diff --git a/src/main/java/life/mosu/mosuserver/presentation/admin/AdminController.java b/src/main/java/life/mosu/mosuserver/presentation/admin/AdminController.java index 307d44b5..e4761c6b 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/admin/AdminController.java +++ b/src/main/java/life/mosu/mosuserver/presentation/admin/AdminController.java @@ -12,6 +12,7 @@ import life.mosu.mosuserver.presentation.admin.dto.ApplicationExcelDto; import life.mosu.mosuserver.presentation.admin.dto.ApplicationFilter; import life.mosu.mosuserver.presentation.admin.dto.ApplicationListResponse; +import life.mosu.mosuserver.presentation.admin.dto.RefundListResponse; import life.mosu.mosuserver.presentation.admin.dto.SchoolLunchResponse; import life.mosu.mosuserver.presentation.admin.dto.StudentExcelDto; import life.mosu.mosuserver.presentation.admin.dto.StudentFilter; @@ -19,6 +20,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.web.PageableDefault; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; @@ -96,4 +98,14 @@ public void downloadApplicationInfo(HttpServletResponse response) throws IOExcep excelFile.write(response.getOutputStream()); } + @GetMapping("/refunds") +// @PreAuthorize("isAuthenticated() and hasRole('ADMIN')") + public ResponseEntity>> getRefundCounts( + @PageableDefault(size = 15) Pageable pageable + ) { + Page result = adminService.getRefunds(pageable); + return ResponseEntity.ok( + ApiResponseWrapper.success(HttpStatus.OK, "환불 신청 수 조회 성공", result)); + } + } diff --git a/src/main/java/life/mosu/mosuserver/presentation/admin/dto/RefundListResponse.java b/src/main/java/life/mosu/mosuserver/presentation/admin/dto/RefundListResponse.java new file mode 100644 index 00000000..b84bb81d --- /dev/null +++ b/src/main/java/life/mosu/mosuserver/presentation/admin/dto/RefundListResponse.java @@ -0,0 +1,15 @@ +package life.mosu.mosuserver.presentation.admin.dto; + +public record RefundListResponse( + Long refundId, + String examNumber, + String name, + String phone, + String requestedAt, + String completedAt, + String paymentMethod, + String reason + +) { + +} diff --git a/src/main/java/life/mosu/mosuserver/presentation/admin/dto/StudentExcelDto.java b/src/main/java/life/mosu/mosuserver/presentation/admin/dto/StudentExcelDto.java index 79d76ae9..8a19fae0 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/admin/dto/StudentExcelDto.java +++ b/src/main/java/life/mosu/mosuserver/presentation/admin/dto/StudentExcelDto.java @@ -1,8 +1,6 @@ package life.mosu.mosuserver.presentation.admin.dto; import io.swagger.v3.oas.annotations.media.Schema; -import life.mosu.mosuserver.domain.profile.Education; -import life.mosu.mosuserver.domain.profile.Grade; import life.mosu.mosuserver.global.annotation.ExcelColumn; import lombok.AllArgsConstructor; import lombok.Getter; @@ -30,7 +28,7 @@ public class StudentExcelDto { @Schema(description = "학력", example = "HIGH_SCHOOL") @ExcelColumn(headerName = "학력") - private final Education educationLevel; + private final String educationLevel; @Schema(description = "학교명", example = "서울고등학교") @ExcelColumn(headerName = "학교명") @@ -38,7 +36,7 @@ public class StudentExcelDto { @Schema(description = "학년", example = "THIRD") @ExcelColumn(headerName = "학년") - private final Grade grade; + private final String grade; @Schema(description = "시험 응시 횟수", example = "2") @ExcelColumn(headerName = "시험 응시 횟수") diff --git a/src/main/java/life/mosu/mosuserver/presentation/admin/dto/StudentListResponse.java b/src/main/java/life/mosu/mosuserver/presentation/admin/dto/StudentListResponse.java index c1afba9a..c5de4fa0 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/admin/dto/StudentListResponse.java +++ b/src/main/java/life/mosu/mosuserver/presentation/admin/dto/StudentListResponse.java @@ -1,8 +1,6 @@ package life.mosu.mosuserver.presentation.admin.dto; import io.swagger.v3.oas.annotations.media.Schema; -import life.mosu.mosuserver.domain.profile.Education; -import life.mosu.mosuserver.domain.profile.Grade; @Schema(description = "학생 목록 응답 DTO") public record StudentListResponse( @@ -20,13 +18,13 @@ public record StudentListResponse( String gender, @Schema(description = "학력", example = "ENROLLED") - Education educationLevel, + String educationLevel, @Schema(description = "학교명", example = "서울고등학교") String schoolName, @Schema(description = "학년", example = "HIGH_1") - Grade grade, + String grade, @Schema(description = "시험 응시 횟수", example = "2") int examCount diff --git a/src/main/java/life/mosu/mosuserver/presentation/inquiry/dto/InquiryDetailResponse.java b/src/main/java/life/mosu/mosuserver/presentation/inquiry/dto/InquiryDetailResponse.java index d58dd6e3..4adce450 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/inquiry/dto/InquiryDetailResponse.java +++ b/src/main/java/life/mosu/mosuserver/presentation/inquiry/dto/InquiryDetailResponse.java @@ -3,7 +3,6 @@ import io.swagger.v3.oas.annotations.media.Schema; import java.util.List; import life.mosu.mosuserver.domain.inquiry.InquiryJpaEntity; -import life.mosu.mosuserver.domain.inquiry.InquiryStatus; import life.mosu.mosuserver.domain.inquiryAnswer.InquiryAnswerJpaEntity; @Schema(description = "1:1 문의 응답 DTO") @@ -16,9 +15,9 @@ public record InquiryDetailResponse( String content, @Schema(description = "작성자", example = "홍길동") String author, - @Schema(description = "문의 상태 (WAITING: 답변 대기, COMPLETED: 답변 완료)", example = "WAITING") - InquiryStatus status, - @Schema(description = "문의 등록일", example = "2025-07-10T10:00:00") + @Schema(description = "문의 상태 (미응답, 완료)", example = "완료") + String status, + @Schema(description = "문의 등록일", example = "2025-07-10") String createdAt, List attachments, @@ -36,7 +35,7 @@ public static InquiryDetailResponse of( inquiry.getTitle(), inquiry.getContent(), inquiry.getAuthor(), - inquiry.getStatus(), + inquiry.getStatus().getStatusName(), inquiry.getCreatedAt(), attachments, answer diff --git a/src/main/java/life/mosu/mosuserver/presentation/inquiry/dto/InquiryResponse.java b/src/main/java/life/mosu/mosuserver/presentation/inquiry/dto/InquiryResponse.java index 8f58bc28..5c208fc6 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/inquiry/dto/InquiryResponse.java +++ b/src/main/java/life/mosu/mosuserver/presentation/inquiry/dto/InquiryResponse.java @@ -2,7 +2,6 @@ import io.swagger.v3.oas.annotations.media.Schema; import life.mosu.mosuserver.domain.inquiry.InquiryJpaEntity; -import life.mosu.mosuserver.domain.inquiry.InquiryStatus; @Schema(description = "1:1 문의 응답 DTO") public record InquiryResponse( @@ -18,8 +17,8 @@ public record InquiryResponse( @Schema(description = "작성자", example = "홍길동") String author, - @Schema(description = "문의 상태 (PENDING: 답변 대기, COMPLETED: 답변 완료)", example = "PENDING") - InquiryStatus status, + @Schema(description = "문의 상태 (PENDING: 미응답, COMPLETED: 완료)", example = "미응답") + String status, @Schema(description = "문의 등록일", example = "2025-07-10") String createdAt @@ -31,7 +30,7 @@ public static InquiryResponse of(InquiryJpaEntity inquiry) { inquiry.getTitle(), inquiry.getContent(), inquiry.getAuthor(), - inquiry.getStatus(), + inquiry.getStatus().getStatusName(), inquiry.getCreatedAt() ); } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index a48b3e63..6e06b5a0 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -28,7 +28,7 @@ spring: open-in-view: false show-sql: true hibernate: - ddl-auto: create-drop + ddl-auto: update properties: hibernate: show_sql: true