diff --git a/build.gradle.kts b/build.gradle.kts index 38cc7a9..e34510a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -59,6 +59,8 @@ dependencies { // firebase admin sdk implementation("com.google.firebase:firebase-admin:9.2.0") + implementation("org.apache.poi:poi-ooxml:5.2.3") + developmentOnly("org.springframework.boot:spring-boot-devtools") runtimeOnly("com.h2database:h2") runtimeOnly("com.mysql:mysql-connector-j") diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/can/coupon/CanCouponController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/can/coupon/CanCouponController.kt index cfd7b79..219996f 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/can/coupon/CanCouponController.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/can/coupon/CanCouponController.kt @@ -4,6 +4,9 @@ import kr.co.vividnext.sodalive.common.ApiResponse import kr.co.vividnext.sodalive.common.SodaException import kr.co.vividnext.sodalive.member.Member import org.springframework.data.domain.Pageable +import org.springframework.http.HttpHeaders +import org.springframework.http.MediaType +import org.springframework.http.ResponseEntity import org.springframework.security.access.prepost.PreAuthorize import org.springframework.security.core.annotation.AuthenticationPrincipal import org.springframework.web.bind.annotation.GetMapping @@ -55,4 +58,20 @@ class CanCouponController(private val service: CanCouponService) { ) ) } + + @GetMapping("/number-list/download") + @PreAuthorize("hasRole('ADMIN')") + fun downloadCouponNumberList( + @RequestParam couponId: Long, + @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? + ) = run { + if (member == null) throw SodaException("로그인 정보를 확인해주세요.") + + val fileName = "쿠폰번호리스트.xlsx" + val response = service.downloadCouponNumberList(couponId) + ResponseEntity.ok() + .header(HttpHeaders.CONTENT_DISPOSITION, "attatchment;filename=$fileName") + .contentType(MediaType.parseMediaType("application/vnd.ms-excel")) + .body(response) + } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/can/coupon/CanCouponRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/can/coupon/CanCouponRepository.kt index 2c28256..9c3653c 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/can/coupon/CanCouponRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/can/coupon/CanCouponRepository.kt @@ -12,6 +12,8 @@ interface CanCouponQueryRepository { fun getCouponList(offset: Long, limit: Long): List fun getCouponNumberTotalCount(couponId: Long): Int fun getCouponNumberList(couponId: Long, offset: Long, limit: Long): List + + fun getAllCouponNumberList(couponId: Long): List } class CanCouponQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : CanCouponQueryRepository { @@ -57,4 +59,19 @@ class CanCouponQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : .limit(limit) .fetch() } + + override fun getAllCouponNumberList(couponId: Long): List { + return queryFactory + .select( + QGetCouponNumberListItemResponse( + canCouponNumber.id, + canCouponNumber.couponNumber, + canCouponNumber.member.isNotNull + ) + ) + .from(canCouponNumber) + .where(canCouponNumber.canCoupon.id.eq(couponId)) + .orderBy(canCouponNumber.id.asc()) + .fetch() + } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/can/coupon/CanCouponService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/can/coupon/CanCouponService.kt index 5e219b7..9524e17 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/can/coupon/CanCouponService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/can/coupon/CanCouponService.kt @@ -3,8 +3,13 @@ package kr.co.vividnext.sodalive.can.coupon import com.fasterxml.jackson.databind.ObjectMapper import kr.co.vividnext.sodalive.aws.sqs.SqsEvent import kr.co.vividnext.sodalive.aws.sqs.SqsEventType +import kr.co.vividnext.sodalive.common.SodaException +import org.apache.poi.xssf.usermodel.XSSFWorkbook import org.springframework.context.ApplicationEventPublisher import org.springframework.stereotype.Service +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.io.IOException import java.time.format.DateTimeFormatter @Service @@ -48,4 +53,45 @@ class CanCouponService( val items = repository.getCouponNumberList(couponId = couponId, offset = offset, limit = limit) return GetCouponNumberListResponse(totalCount, items) } + + fun downloadCouponNumberList(couponId: Long): ByteArrayInputStream { + val header = listOf("순번", "쿠폰번호", "사용여부") + val byteArrayOutputStream = ByteArrayOutputStream() + val couponNumberList = repository.getAllCouponNumberList(couponId) + + val workbook = XSSFWorkbook() + try { + val sheet = workbook.createSheet() + val row = sheet.createRow(0) + header.forEachIndexed { index, string -> + val cell = row.createCell(index) + cell.setCellValue(string) + } + + couponNumberList.forEachIndexed { index, item -> + val couponNumberRow = sheet.createRow(index + 1) + couponNumberRow.createCell(0).setCellValue(item.couponNumberId.toString()) + couponNumberRow.createCell(1).setCellValue(insertHyphens(item.couponNumber)) + couponNumberRow.createCell(2).setCellValue( + if (item.isUsed) { + "O" + } else { + "X" + } + ) + } + + workbook.write(byteArrayOutputStream) + return ByteArrayInputStream(byteArrayOutputStream.toByteArray()) + } catch (e: IOException) { + throw SodaException("다운로드를 하지 못했습니다.\n다시 시도해 주세요.") + } finally { + workbook.close() + byteArrayOutputStream.close() + } + } + + private fun insertHyphens(input: String): String { + return input.chunked(4).joinToString("-") + } }