Skip to content

v2025.11.10#100

Merged
withJihyuk merged 11 commits intomainfrom
develop
Nov 10, 2025
Merged

v2025.11.10#100
withJihyuk merged 11 commits intomainfrom
develop

Conversation

@withJihyuk
Copy link
Copy Markdown
Member

💡 JIRA TICKET

📃 작업내용

✅ PR 체크리스트

  • 이 작업으로 인해 변경이 필요한 문서가 변경되었나요? (e.g. .env, 노션, README)
  • 이 작업을 하고나서 공유해야할 팀원들에게 공유되었나요? (e.g. "API 개발 완료됐어요", "환경값 추가되었어요")
  • 작업한 코드가 정상적으로 동작하나요?
  • Merge 대상 브랜치가 올바른가요?
  • PR과 관련 없는 작업이 있지는 않나요?

@withJihyuk withJihyuk self-assigned this Nov 10, 2025
@withJihyuk withJihyuk merged commit de707ed into main Nov 10, 2025
0 of 2 checks passed
@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello @withJihyuk, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

이 PR은 공간 사용 내역 관리 시스템의 핵심 기능을 확장하고 개선하는 것을 목표로 합니다. 주요 변경 사항은 사용 내역에 대한 추가 인원 정보를 기록하는 기능 도입과, 이를 활용하여 공간별 월별 이용 현황을 연령 및 성별로 상세하게 집계하여 엑셀 파일로 내보내는 새로운 보고서 기능 추가입니다. 또한, 기존 엑셀 내보내기 로직은 더 유연하고 확장 가능한 구조로 전면 리팩토링되었습니다. 이로써 관리자는 공간 사용 데이터를 더욱 세밀하게 분석하고 활용할 수 있게 됩니다.

Highlights

  • 추가 인원 기능 구현: 공간 사용 내역에 주 사용자 외에 추가 인원(성별, 연령대, 인원수)을 기록할 수 있는 기능이 추가되었습니다. 이는 사용 내역 생성 시 함께 전달되며, AdditionalParticipant 엔티티로 관리됩니다.
  • 월별 이용 현황 엑셀 내보내기: 특정 공간의 월별 이용 현황을 연령 및 성별로 집계하여 엑셀 파일로 내보내는 새로운 기능이 구현되었습니다. 이 기능은 미리 정의된 템플릿을 기반으로 통계 데이터를 시각화하여 제공합니다.
  • 엑셀 서비스 리팩토링: 기존 엑셀 내보내기 로직이 ExcelCellWriter, ExcelStyleProvider, ExcelTemplateLoader, FormDataParser, UsageStatisticsAggregator 등 여러 전문화된 컴포넌트로 분리되어 재구성되었습니다. 이를 통해 코드의 모듈성과 유지보수성이 향상되었습니다.
  • 사용 내역 생성 API 변경: 사용자 공간 사용 내역 생성 API (POST /usage)가 @RequestParam 방식에서 CreateSpaceUsageRequest DTO를 @RequestBody로 받는 방식으로 변경되었습니다. 이를 통해 추가 인원 정보를 구조화된 형태로 함께 전달할 수 있게 되었습니다.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

이번 PR은 월별 이용 통계를 템플릿 기반의 Excel 파일로 내보내는 중요한 기능과, 공간 이용 시 추가 인원을 기록하는 기능을 구현했습니다.

전반적인 코드 품질이 높습니다. 특히 Excel 생성 로직을 단일 책임 원칙에 따라 작은 컴포넌트로 리팩토링한 점은 유지보수성 측면에서 매우 훌륭한 개선입니다. 또한 @ConfigurationProperties를 활용한 설정 관리나 성능 최적화를 위한 JOIN FETCH 사용도 좋은 선택입니다.

코드의 안정성과 유지보수성을 더욱 향상시키기 위해 몇 가지 제안 사항을 댓글로 남겼습니다. 파일명 오타 수정, 수식 파싱 로직 유연성 확보, 하드코딩된 값 제거 등에 대한 내용이니 검토 부탁드립니다.

import org.aing.danurirest.domain.admin.usecase.GetUsageHistoriesUsecase
import org.aing.danurirest.domain.admin.usecase.GetUsageHistoryUsecase
import org.aing.danurirest.domain.admin.usecase.UpdateUsageHistoryUsecase
import org.aing.danurirest.domain.admin.usecase.*
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

와일드카드(*) import는 간결해 보일 수 있지만, 어떤 클래스가 사용되는지 명확하지 않아 코드 가독성과 유지보수성을 저해할 수 있습니다. 특히 협업 환경에서는 명시적으로 클래스를 import하는 것이 충돌을 피하고 코드를 이해하는 데 도움이 됩니다. 개별 클래스를 import하는 방식으로 되돌리는 것을 고려해 보세요.

import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import org.springframework.validation.annotation.Validated
import org.springframework.web.bind.annotation.*
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

와일드카드(*) import는 코드에서 사용하는 클래스의 출처를 모호하게 만들 수 있습니다. 가독성과 유지보수성을 위해 필요한 어노테이션들을 명시적으로 import하는 것이 좋습니다.

.ok()
.header("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
.header("Content-Disposition", "attachment; filename=usage_history.xlsx")
.header("Content-Disposition", "attachment; filename=usage_history_.xlsx")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

엑셀 파일명에 불필요한 밑줄(_)이 포함되어 있습니다. usage_history_.xlsx 대신 usage_history.xlsx로 수정하는 것이 자연스러워 보입니다.

Suggested change
.header("Content-Disposition", "attachment; filename=usage_history_.xlsx")
.header("Content-Disposition", "attachment; filename=usage_history.xlsx")

Comment on lines +183 to +192
fun updateFormula(
formula: String,
startRow: Int,
endRow: Int,
): String =
formula.replace(Regex("SUM\\(([A-Z]+)\\d+:([A-Z]+)\\d+\\)")) { match ->
val column1 = match.groupValues[1]
val column2 = match.groupValues[2]
"SUM(${column1}$startRow:${column2}$endRow)"
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

현재 updateFormula의 정규식은 SUM(A1:A10)과 같은 간단한 형태의 수식만 처리할 수 있습니다. 만약 템플릿의 합계 수식이 SUM(A1, B1) 또는 SUM(A:A)와 같이 다른 형태로 변경될 경우, 이 로직이 깨질 수 있습니다. 보다 유연한 수식 파싱 및 업데이트 방법을 고려해보거나, 템플릿의 수식 형태에 대한 제약을 문서화하는 것이 좋겠습니다.

try {
// 템플릿 로드
val workbook = templateLoader.loadMonthlyUsageTemplate()
val sheet = workbook.getSheetAt(0) ?: throw CustomException(CustomErrorCode.UNKNOWN_SERVER_ERROR)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

workbook.getSheetAt(0)을 사용하여 시트를 인덱스로 가져오고 있습니다. 이는 템플릿 파일의 첫 번째 시트가 항상 우리가 원하는 시트라는 것을 가정합니다. 템플릿의 구조가 변경될 경우(예: 시트 순서 변경) 버그가 발생할 수 있습니다. workbook.getSheet("시트이름")과 같이 시트 이름으로 조회하는 것이 더 안정적입니다. 시트 이름 또한 ExcelProperties에서 관리하는 것을 고려해볼 수 있습니다.

Comment on lines +252 to +264
for (day in (daysInMonth + 1)..31) {
val rowIndex = properties.data.startRow + day - 1
val row = sheet.getRow(rowIndex)
row?.let { clearRow(it) }
}

if (sheet.lastRowNum > properties.data.startRow + daysInMonth) {
sheet.shiftRows(
properties.data.startRow + 31,
sheet.lastRowNum,
-(31 - daysInMonth),
)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

handleExtraDays 메서드에서 숫자 31이 여러 곳에 하드코딩되어 있습니다. 이는 템플릿이 항상 31일 기준으로 작성되었다는 강한 가정을 내포하고 있습니다. 이 값을 ExcelProperties를 통해 설정 파일로 옮기거나, 클래스 상수로 정의하여 코드의 의도를 명확히 하고 유지보수성을 높이는 것이 좋습니다.

Comment on lines +126 to +135
private fun mapAgeOption(ageOption: String?): Age? =
when (ageOption) {
"초등학교" -> Age.ELEMENTARY
"중학교" -> Age.MIDDLE
"고등학교" -> Age.HIGH
"대학교" -> Age.COLLEGE
"학교 밖 청소년" -> Age.OUT_OF_SCHOOL_YOUTH
"성인/유아" -> Age.ADULT
else -> null
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

mapAgeOption 함수 내에서 "초등학교", "중학교" 등 UI에 표시되는 문자열을 사용하여 Age enum으로 변환하고 있습니다. 이는 UI의 문구가 변경될 경우 서버 코드를 수정해야 하는 의존성을 만듭니다.
이러한 문제를 해결하기 위해, 폼 정의에 value와 같은 내부 식별자를 추가하고, 서버에서는 이 식별자를 사용하여 매핑하는 것을 고려해볼 수 있습니다.

예시:

{
  "label": "초등학교",
  "value": "ELEMENTARY"
}

이렇게 하면 UI 변경에 더 유연하게 대처할 수 있습니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant