diff --git a/src/main/kotlin/site/billilge/api/backend/domain/payer/controller/AdminPayerController.kt b/src/main/kotlin/site/billilge/api/backend/domain/payer/controller/AdminPayerController.kt index ed74ab8..33aa989 100644 --- a/src/main/kotlin/site/billilge/api/backend/domain/payer/controller/AdminPayerController.kt +++ b/src/main/kotlin/site/billilge/api/backend/domain/payer/controller/AdminPayerController.kt @@ -1,5 +1,8 @@ package site.billilge.api.backend.domain.payer.controller +import org.springframework.core.io.InputStreamResource +import org.springframework.http.ContentDisposition +import org.springframework.http.HttpHeaders import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* import site.billilge.api.backend.domain.payer.dto.request.PayerDeleteRequest @@ -9,6 +12,8 @@ import site.billilge.api.backend.domain.payer.service.PayerService import site.billilge.api.backend.global.annotation.OnlyAdmin import site.billilge.api.backend.global.dto.PageableCondition import site.billilge.api.backend.global.dto.SearchCondition +import java.time.LocalDate +import java.time.format.DateTimeFormatter @RestController @RequestMapping("/admin/members/payers") @@ -35,4 +40,20 @@ class AdminPayerController( payerService.deletePayers(request) return ResponseEntity.noContent().build() } + + @GetMapping("/excel") + fun createPayerExcel(): ResponseEntity { + val excel = payerService.createPayerExcel() + val currentDate = LocalDate.now() + val dateFormatter = DateTimeFormatter.ofPattern("yyyyMMdd") + val headers = HttpHeaders().apply { + contentDisposition = ContentDisposition.builder("attachment") + .filename("kmusw_payers_${dateFormatter.format(currentDate)}.xlsx") + .build() + } + + return ResponseEntity.ok() + .headers(headers) + .body(InputStreamResource((excel))) + } } \ No newline at end of file diff --git a/src/main/kotlin/site/billilge/api/backend/domain/payer/repository/PayerRepository.kt b/src/main/kotlin/site/billilge/api/backend/domain/payer/repository/PayerRepository.kt index a545714..ce0468a 100644 --- a/src/main/kotlin/site/billilge/api/backend/domain/payer/repository/PayerRepository.kt +++ b/src/main/kotlin/site/billilge/api/backend/domain/payer/repository/PayerRepository.kt @@ -15,4 +15,6 @@ interface PayerRepository : JpaRepository { @Query("SELECT p FROM Payer p WHERE p.name LIKE CONCAT('%', :name, '%')") fun findAllByNameContaining(@Param("name") name: String, pageable: Pageable): Page + + fun findAllByEnrollmentYear(enrollmentYear: String): List } \ No newline at end of file diff --git a/src/main/kotlin/site/billilge/api/backend/domain/payer/service/PayerService.kt b/src/main/kotlin/site/billilge/api/backend/domain/payer/service/PayerService.kt index df1e9d3..4ab0b56 100644 --- a/src/main/kotlin/site/billilge/api/backend/domain/payer/service/PayerService.kt +++ b/src/main/kotlin/site/billilge/api/backend/domain/payer/service/PayerService.kt @@ -14,12 +14,19 @@ import site.billilge.api.backend.domain.payer.entity.Payer import site.billilge.api.backend.domain.payer.repository.PayerRepository import site.billilge.api.backend.global.dto.PageableCondition import site.billilge.api.backend.global.dto.SearchCondition +import site.billilge.api.backend.global.utils.ExcelGenerator +import site.billilge.api.backend.global.utils.ExcelRow +import java.io.ByteArrayInputStream +import java.time.Year @Service @Transactional(readOnly = true) class PayerService( private val payerRepository: PayerRepository, - private val memberRepository: MemberRepository + + private val memberRepository: MemberRepository, + + private val excelGenerator: ExcelGenerator ) { fun isPayer(name: String, studentId: String): Boolean { val enrollmentYear = studentId.substring(0, 4) @@ -71,6 +78,7 @@ class PayerService( @Transactional fun addPayers(request: PayerRequest) { + val newPayers = mutableListOf() request.payers.forEach { payerItem -> val name = payerItem.name val studentId = payerItem.studentId @@ -87,18 +95,19 @@ class PayerService( this.registered = registered } - payerRepository.save(payer) + newPayers.add(payer) } registeredMember?.isFeePaid = true } + + payerRepository.saveAll(newPayers) } @Transactional fun deletePayers(request: PayerDeleteRequest) { val payerStudentIds = payerRepository.findAllByIds(request.payerIds) .mapNotNull { it.studentId } - .toList() memberRepository.findAllByStudentIds(payerStudentIds) .forEach { member -> @@ -107,4 +116,21 @@ class PayerService( payerRepository.deleteAllById(request.payerIds) } + + fun createPayerExcel(): ByteArrayInputStream { + val startYear = 2015 + val currentYear = Year.now().value + val headerTitles = arrayOf("이름", "학번") + val sheetData = mutableMapOf, List>>() + + for (year in startYear..currentYear) { + val yearText = "$year" + val payersByYearExcelRow = payerRepository.findAllByEnrollmentYear(yearText) + .map { payer -> ExcelRow(payer.name, payer.studentId ?: "${yearText}XXXX") } + + sheetData.put(yearText, headerTitles to payersByYearExcelRow) + } + + return excelGenerator.generateByMultipleSheets(sheetData) + } } \ No newline at end of file diff --git a/src/main/kotlin/site/billilge/api/backend/global/config/AsyncConfig.kt b/src/main/kotlin/site/billilge/api/backend/global/config/AsyncConfig.kt new file mode 100644 index 0000000..71b0bd9 --- /dev/null +++ b/src/main/kotlin/site/billilge/api/backend/global/config/AsyncConfig.kt @@ -0,0 +1,9 @@ +package site.billilge.api.backend.global.config + +import org.springframework.context.annotation.Configuration +import org.springframework.scheduling.annotation.EnableAsync + +@EnableAsync +@Configuration +class AsyncConfig { +} \ No newline at end of file diff --git a/src/main/kotlin/site/billilge/api/backend/global/utils/ExcelGenerator.kt b/src/main/kotlin/site/billilge/api/backend/global/utils/ExcelGenerator.kt new file mode 100644 index 0000000..c3469fd --- /dev/null +++ b/src/main/kotlin/site/billilge/api/backend/global/utils/ExcelGenerator.kt @@ -0,0 +1,63 @@ +package site.billilge.api.backend.global.utils + +import org.apache.poi.ss.usermodel.IndexedColors +import org.apache.poi.xssf.streaming.SXSSFSheet +import org.apache.poi.xssf.streaming.SXSSFWorkbook +import org.springframework.stereotype.Component +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream + +private const val HEADER_ROW = 0 + +@Component +class ExcelGenerator { + + fun generateByMultipleSheets( + sheetData: Map, List>> + ): ByteArrayInputStream { + val workbook = SXSSFWorkbook() + + sheetData.forEach { (sheetName, sheetContent) -> + val (headerTitles, rows) = sheetContent + val sheet = workbook.createSheet(sheetName) + styleHeaders(workbook, sheet, headerTitles) + fillData(sheet, rows, headerTitles.size) + } + + val out = ByteArrayOutputStream() + workbook.write(out) + workbook.close() + + return ByteArrayInputStream(out.toByteArray()) + } + + private fun styleHeaders(workbook: SXSSFWorkbook, sheet: SXSSFSheet, headerTitles: Array) { + val headerFont = workbook.createFont() + headerFont.bold = true + + val headerCellStyle = workbook.createCellStyle() + headerCellStyle.setFont(headerFont) + headerCellStyle.fillForegroundColor = IndexedColors.GREY_25_PERCENT.index + headerCellStyle.fillPattern = org.apache.poi.ss.usermodel.FillPatternType.SOLID_FOREGROUND + + val headerRow = sheet.createRow(HEADER_ROW) + headerTitles.forEachIndexed { col, title -> + val cell = headerRow.createCell(col) + cell.setCellValue(title) + cell.cellStyle = headerCellStyle + } + } + + private fun fillData(sheet: SXSSFSheet, rows: List, columnSize: Int) { + sheet.trackAllColumnsForAutoSizing() + + rows.forEachIndexed { index, excelRow -> + val row = sheet.createRow(index + 1) + excelRow.data.forEachIndexed { propertyIndex, property -> + row.createCell(propertyIndex).setCellValue(property) + } + } + + repeat(columnSize) { col -> sheet.autoSizeColumn(col) } + } +} \ No newline at end of file diff --git a/src/main/kotlin/site/billilge/api/backend/global/utils/ExcelRow.kt b/src/main/kotlin/site/billilge/api/backend/global/utils/ExcelRow.kt new file mode 100644 index 0000000..c717b82 --- /dev/null +++ b/src/main/kotlin/site/billilge/api/backend/global/utils/ExcelRow.kt @@ -0,0 +1,7 @@ +package site.billilge.api.backend.global.utils + +data class ExcelRow( + val data: List +) { + constructor(vararg data: String) : this(data.toList()) +} \ No newline at end of file