[WTH-404] 회비 온보딩 API 구현#80
Hidden character warning
Conversation
|
Warning Review limit reached
More reviews will be available in 55 minutes and 45 seconds. Learn how PR review limits work. Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file). ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthrough회비 장부의 단계별 등록 프로세스를 완벽하게 구현한 대규모 PR입니다. 초안 생성부터 기본정보·납부대상·이월·계좌 정보 저장, 최종 완료까지 전체 워크플로우를 커버하며, DTO 계약부터 도메인 모델, 저장소, 비즈니스 로직, API 엔드포인트, 포괄적인 테스트를 포함합니다. Changes회비 등록 플로우 종합 구현
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
✨ Finishing Touches🧪 Generate unit tests (beta)
|
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
src/test/kotlin/com/weeth/domain/account/application/usecase/query/GetAccountPaymentTargetQueryServiceTest.kt (1)
56-191: ⚡ Quick winEXCLUDED 필터 분기에 대한 테스트 케이스 누락
서비스의
targetStatus == AccountTargetStatus.EXCLUDED분기(서비스 Line 66-75)가findExcludedPaymentTargetCandidatesByCardinal을 호출하는데, 이 경로에 대한 테스트가 없습니다. TARGETED와 null(전체) 케이스는 검증되지만, EXCLUDED 필터 시나리오도 커버하면 좋겠습니다.💡 EXCLUDED 필터 테스트 케이스 예시
it("제외됨 필터에서는 EXCLUDED 후보만 페이지로 조회한다") { val clubId = 1L val accountId = 10L val userId = 100L val club = ClubTestFixture.createClub(id = clubId) val account = AccountTestFixture.createAccount(id = accountId, club = club) val excludedMember = ClubMemberTestFixture.createActiveMember(id = 21L, club = club) val pageable = PageRequest.of(0, 10) every { accountRepository.findById(accountId) } returns Optional.of(account) every { clubMemberReader.countActiveByClubIdAndCardinalNumber(clubId, 40) } returns 18L every { paymentTargetRepository.countActiveClubMemberTargetsByAccountIdAndTargetStatus( accountId, AccountTargetStatus.TARGETED, ) } returns 12L every { clubMemberReader.findExcludedPaymentTargetCandidatesByCardinal( clubId = clubId, cardinalNumber = 40, accountId = accountId, keyword = null, pageable = pageable, ) } returns PageImpl(listOf(excludedMember), pageable, 6) every { paymentTargetRepository.findAllByAccountIdAndClubMemberIdIn(accountId, listOf(21L)) } returns emptyList() val result = service.findTargets( clubId = clubId, accountId = accountId, userId = userId, page = 0, size = 10, keyword = null, targetStatus = AccountTargetStatus.EXCLUDED, ) result.targets.content.first().targetStatus shouldBe AccountTargetStatus.EXCLUDED }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/test/kotlin/com/weeth/domain/account/application/usecase/query/GetAccountPaymentTargetQueryServiceTest.kt` around lines 56 - 191, The EXCLUDED branch of service.findTargets (lines handling targetStatus == AccountTargetStatus.EXCLUDED) is not covered by tests; add a new test that mocks accountRepository.findById(accountId) to return the account, clubMemberReader.countActiveByClubIdAndCardinalNumber(...) to return the total, paymentTargetRepository.countActiveClubMemberTargetsByAccountIdAndTargetStatus(accountId, AccountTargetStatus.TARGETED) to return targetedCount, and clubMemberReader.findExcludedPaymentTargetCandidatesByCardinal(...) to return a PageImpl with an excluded member; also mock paymentTargetRepository.findAllByAccountIdAndClubMemberIdIn(accountId, listOf(excludedId)) to return emptyList(); invoke service.findTargets with targetStatus = AccountTargetStatus.EXCLUDED and assert the returned targets.content.first().targetStatus is AccountTargetStatus.EXCLUDED and the page/summary counts match expectations.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@src/main/kotlin/com/weeth/domain/account/application/usecase/command/RegisterAccountUseCase.kt`:
- Around line 159-257: The UseCase completeRegistration currently contains
business rules (registration-step validation, carry-over mismatch check,
activating the account, excluding inactive member payment targets,
creating/applying carry-over transaction, and settling the previous account) —
move these rules into the domain layer: add domain methods on Account or a
domain service such as Account.completeRegistration(userId, previousAccount,
now) or finer-grained methods like
Account.ensureRegistrationStepAtLeast(AccountRegistrationStep),
Account.verifyCarryOverMatches(previousAccount),
Account.activateAndApplyCarryOver(carryOverAmount, carryOverMemo, now), and
Account.settlePreviousBalance(previousAccount, now); update
findPreviousAccountWithLock to remain in the UseCase for locking/orchestration,
keep permission checks and repository calls (getAccountWithLock,
findPreviousAccountWithLock, transactionRepository.save,
paymentTargetRepository.findAllUnpaidTargetsWithInactiveClubMemberByAccountId)
in RegisterAccountUseCase, and replace inline logic with calls to the new domain
methods, persisting returned/modified entities as needed (e.g., via
transactionRepository.save and accountRepository.save or letting ORM flush).
In `@src/main/kotlin/com/weeth/domain/account/domain/vo/BankAccount.kt`:
- Around line 25-31: Entity column lengths in BankAccount (properties holder and
guide) don't match the migration V1__add_membership_fee_account_models.sql
(entity uses length=30 while migration sets VARCHAR(50) and VARCHAR(200])—align
them and add input validation: update `@Column`(length=...) on BankAccount.holder
and BankAccount.guide to match the migration (50 and 200 respectively) and/or
adjust the migration to match desired lengths, and update normalizeRequired and
normalizeOptional to enforce the same max lengths (trim + validate length, throw
or return a clear validation error) so runtime/data layer and Hibernate ddl-auto
validate/update won't fail. Ensure the change touches the BankAccount class,
normalizeRequired/normalizeOptional implementations, and coordinate with the
migration V1__add_membership_fee_account_models.sql or yml ddl-auto settings so
schema and entity mapping remain consistent.
---
Nitpick comments:
In
`@src/test/kotlin/com/weeth/domain/account/application/usecase/query/GetAccountPaymentTargetQueryServiceTest.kt`:
- Around line 56-191: The EXCLUDED branch of service.findTargets (lines handling
targetStatus == AccountTargetStatus.EXCLUDED) is not covered by tests; add a new
test that mocks accountRepository.findById(accountId) to return the account,
clubMemberReader.countActiveByClubIdAndCardinalNumber(...) to return the total,
paymentTargetRepository.countActiveClubMemberTargetsByAccountIdAndTargetStatus(accountId,
AccountTargetStatus.TARGETED) to return targetedCount, and
clubMemberReader.findExcludedPaymentTargetCandidatesByCardinal(...) to return a
PageImpl with an excluded member; also mock
paymentTargetRepository.findAllByAccountIdAndClubMemberIdIn(accountId,
listOf(excludedId)) to return emptyList(); invoke service.findTargets with
targetStatus = AccountTargetStatus.EXCLUDED and assert the returned
targets.content.first().targetStatus is AccountTargetStatus.EXCLUDED and the
page/summary counts match expectations.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 99154c61-d888-4127-8128-f2d1e63cd96d
📒 Files selected for processing (46)
src/main/kotlin/com/weeth/domain/account/application/dto/request/SaveAccountBankAccountRequest.ktsrc/main/kotlin/com/weeth/domain/account/application/dto/request/SaveAccountBasicRequest.ktsrc/main/kotlin/com/weeth/domain/account/application/dto/request/SaveAccountCarryOverRequest.ktsrc/main/kotlin/com/weeth/domain/account/application/dto/request/SavePaymentTargetsRequest.ktsrc/main/kotlin/com/weeth/domain/account/application/dto/request/UpdateMemberVisibilityRequest.ktsrc/main/kotlin/com/weeth/domain/account/application/dto/response/AccountCarryOverSourceResponse.ktsrc/main/kotlin/com/weeth/domain/account/application/dto/response/AccountPaymentTargetResponse.ktsrc/main/kotlin/com/weeth/domain/account/application/dto/response/AccountPaymentTargetsResponse.ktsrc/main/kotlin/com/weeth/domain/account/application/dto/response/AccountRegistrationStatusResponse.ktsrc/main/kotlin/com/weeth/domain/account/application/dto/response/AccountResponse.ktsrc/main/kotlin/com/weeth/domain/account/application/dto/response/BankAccountResponse.ktsrc/main/kotlin/com/weeth/domain/account/application/dto/response/CreateAccountDraftResponse.ktsrc/main/kotlin/com/weeth/domain/account/application/exception/AccountCarryOverAmountMismatchException.ktsrc/main/kotlin/com/weeth/domain/account/application/exception/AccountErrorCode.ktsrc/main/kotlin/com/weeth/domain/account/application/exception/AccountInvalidDraftStateException.ktsrc/main/kotlin/com/weeth/domain/account/application/exception/AccountPaymentTargetMemberInvalidException.ktsrc/main/kotlin/com/weeth/domain/account/application/exception/AccountPaymentTargetPaidException.ktsrc/main/kotlin/com/weeth/domain/account/application/exception/AccountRegistrationStepIncompleteException.ktsrc/main/kotlin/com/weeth/domain/account/application/mapper/AccountPaymentTargetMapper.ktsrc/main/kotlin/com/weeth/domain/account/application/mapper/AccountRegistrationMapper.ktsrc/main/kotlin/com/weeth/domain/account/application/usecase/AccountClubAccess.ktsrc/main/kotlin/com/weeth/domain/account/application/usecase/command/ManageAccountUseCase.ktsrc/main/kotlin/com/weeth/domain/account/application/usecase/command/RegisterAccountUseCase.ktsrc/main/kotlin/com/weeth/domain/account/application/usecase/query/GetAccountPaymentTargetQueryService.ktsrc/main/kotlin/com/weeth/domain/account/application/usecase/query/GetAccountRegistrationQueryService.ktsrc/main/kotlin/com/weeth/domain/account/domain/entity/Account.ktsrc/main/kotlin/com/weeth/domain/account/domain/entity/AccountTransaction.ktsrc/main/kotlin/com/weeth/domain/account/domain/enums/AccountRegistrationStep.ktsrc/main/kotlin/com/weeth/domain/account/domain/repository/AccountPaymentTargetRepository.ktsrc/main/kotlin/com/weeth/domain/account/domain/repository/AccountRepository.ktsrc/main/kotlin/com/weeth/domain/account/domain/vo/BankAccount.ktsrc/main/kotlin/com/weeth/domain/account/presentation/AccountManageController.ktsrc/main/kotlin/com/weeth/domain/account/presentation/AccountRegisterController.ktsrc/main/kotlin/com/weeth/domain/account/presentation/AccountResponseCode.ktsrc/main/kotlin/com/weeth/domain/club/domain/repository/ClubMemberReader.ktsrc/main/kotlin/com/weeth/domain/club/domain/repository/ClubMemberRepository.ktsrc/main/kotlin/com/weeth/domain/club/domain/service/ClubActivityDeletionPolicy.ktsrc/main/kotlin/com/weeth/global/common/response/PageResponse.ktsrc/test/kotlin/com/weeth/domain/account/application/usecase/command/ManageAccountUseCaseTest.ktsrc/test/kotlin/com/weeth/domain/account/application/usecase/command/MembershipFeeRegistrationIntegrationTest.ktsrc/test/kotlin/com/weeth/domain/account/application/usecase/command/RegisterAccountUseCaseTest.ktsrc/test/kotlin/com/weeth/domain/account/application/usecase/query/GetAccountPaymentTargetQueryServiceTest.ktsrc/test/kotlin/com/weeth/domain/account/application/usecase/query/GetAccountRegistrationQueryServiceTest.ktsrc/test/kotlin/com/weeth/domain/account/domain/entity/AccountTest.ktsrc/test/kotlin/com/weeth/domain/account/domain/repository/AccountRepositoryTest.ktsrc/test/kotlin/com/weeth/domain/account/fixture/AccountTestFixture.kt
| @Transactional | ||
| fun completeRegistration( | ||
| clubId: Long, | ||
| accountId: Long, | ||
| userId: Long, | ||
| ) { | ||
| clubPermissionPolicy.requireAdmin(clubId, userId) | ||
| val account = getAccountWithLock(clubId, accountId) | ||
| if (account.status != AccountStatus.DRAFT) throw AccountInvalidDraftStateException() | ||
| // 이월/계좌 단계를 건너뛴 채 완료하면 이월 결정 없이 이전 장부가 마감되는 부수효과가 생기므로 모든 단계 저장을 강제한다. | ||
| if (!account.registrationStep.isAtLeast(AccountRegistrationStep.REVIEW)) { | ||
| throw AccountRegistrationStepIncompleteException() | ||
| } | ||
|
|
||
| // 이월 재원 조회와 완료 사이에 이전 장부 잔액이 변했을 수 있으므로 잠금 조회 후 이월 금액과 대조한다. | ||
| val previousAccount = findPreviousAccountWithLock(clubId, account) | ||
| if (account.carryOverAmount > 0 && | ||
| previousAccount != null && | ||
| previousAccount.currentBalance != account.carryOverAmount | ||
| ) { | ||
| throw AccountCarryOverAmountMismatchException() | ||
| } | ||
|
|
||
| account.activate() | ||
|
|
||
| // 초안 작성 중 탈퇴/퇴출된 멤버의 미납 대상 행은 조회 화면에서 보이지 않아 갱신할 방법이 없으므로 | ||
| // 활성 장부로 넘기지 않고 여기서 제외 처리한다. 활성화 이후의 탈퇴는 어드민 수동 환불 정책에 따라 행을 유지한다. | ||
| paymentTargetRepository | ||
| .findAllUnpaidTargetsWithInactiveClubMemberByAccountId(accountId) | ||
| .forEach { it.exclude() } | ||
|
|
||
| if (account.carryOverAmount > 0) { | ||
| val transaction = | ||
| AccountTransaction.create( | ||
| account = account, | ||
| type = AccountTransactionType.CARRY_OVER, | ||
| title = "이월 금액", | ||
| source = null, | ||
| amount = Money.of(account.carryOverAmount), | ||
| transactedAt = LocalDateTime.now(), | ||
| memo = account.carryOverMemo, | ||
| ) | ||
| transactionRepository.save(transaction) | ||
| account.applyTransaction(transaction) | ||
| } | ||
|
|
||
| // 이월 여부와 무관하게 이전 기수 장부에 남은 잔액을 지출로 자동 정리해 장부를 마감한다. | ||
| settlePreviousAccountBalance(previousAccount, account) | ||
|
|
||
| account.markModifiedBy(userId) | ||
| } | ||
|
|
||
| /** 직전 활성 기수 장부를 잠금 조회한다. 잔액 검증과 마감에 같은 잠금 인스턴스를 재사용한다. */ | ||
| private fun findPreviousAccountWithLock( | ||
| clubId: Long, | ||
| account: Account, | ||
| ): Account? { | ||
| val previousAccount = | ||
| accountRepository.findTopByClubIdAndCardinalLessThanAndStatusOrderByCardinalDesc( | ||
| clubId = clubId, | ||
| cardinal = account.cardinal, | ||
| status = AccountStatus.ACTIVE, | ||
| ) ?: return null | ||
| return accountRepository.findByIdWithLock(previousAccount.id) | ||
| } | ||
|
|
||
| /** | ||
| * 등록 완료 시 직전 활성 기수 장부에 남은 잔액을 지출 거래로 정리해 0원으로 만든다. | ||
| * 실제 이월된 금액이 있으면 신규 장부로의 전출, 없으면 미이월 잔액 정리 명목으로 기록해 | ||
| * 같은 돈이 두 장부에 중복 집계되지 않도록 한다. | ||
| * (CARRY_OVER 수입 거래와 같은 조건이므로 전출 기록이 있을 때만 대응하는 이월 수입이 존재한다) | ||
| */ | ||
| private fun settlePreviousAccountBalance( | ||
| previousAccount: Account?, | ||
| account: Account, | ||
| ) { | ||
| if (previousAccount == null || previousAccount.currentBalance <= 0) return | ||
|
|
||
| val (title, memo) = | ||
| if (account.carryOverAmount > 0) { | ||
| "이월 잔액 전출" to "${account.cardinal}기 회비로 이월되어 자동 지출 처리되었습니다." | ||
| } else { | ||
| "미이월 잔액 정리" to "${account.cardinal}기 회비 등록 시 이월하지 않기를 선택하여 자동 지출 처리되었습니다." | ||
| } | ||
|
|
||
| val expense = | ||
| AccountTransaction.create( | ||
| account = previousAccount, | ||
| type = AccountTransactionType.EXPENSE, | ||
| title = title, | ||
| source = null, | ||
| amount = Money.of(previousAccount.currentBalance), | ||
| transactedAt = LocalDateTime.now(), | ||
| memo = memo, | ||
| ) | ||
|
|
||
| transactionRepository.save(expense) | ||
| previousAccount.applyTransaction(expense) | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift
UseCase에 도메인 규칙이 과도하게 집약되어 계층 경계가 흐려집니다.
completeRegistration/savePaymentTargets에서 단계 전이, 납부 대상 상태 전이, 이전 기수 마감 정책까지 직접 판단하고 있습니다. 이 규칙은 Account 엔티티/도메인 서비스로 이동시키고, UseCase는 권한 확인·락 조회·영속화 오케스트레이션 중심으로 축소하는 것이 좋습니다.
As per coding guidelines, **/application/usecase/**/*.kt: "UseCase classes contain orchestration only — business logic must live in Entities (Rich Domain Model)" 및 src/main/**/application/**/*UseCase.kt: "UseCase classes orchestrate only; business rules belong in entities or domain services".
Also applies to: 267-322
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@src/main/kotlin/com/weeth/domain/account/application/usecase/command/RegisterAccountUseCase.kt`
around lines 159 - 257, The UseCase completeRegistration currently contains
business rules (registration-step validation, carry-over mismatch check,
activating the account, excluding inactive member payment targets,
creating/applying carry-over transaction, and settling the previous account) —
move these rules into the domain layer: add domain methods on Account or a
domain service such as Account.completeRegistration(userId, previousAccount,
now) or finer-grained methods like
Account.ensureRegistrationStepAtLeast(AccountRegistrationStep),
Account.verifyCarryOverMatches(previousAccount),
Account.activateAndApplyCarryOver(carryOverAmount, carryOverMemo, now), and
Account.settlePreviousBalance(previousAccount, now); update
findPreviousAccountWithLock to remain in the UseCase for locking/orchestration,
keep permission checks and repository calls (getAccountWithLock,
findPreviousAccountWithLock, transactionRepository.save,
paymentTargetRepository.findAllUnpaidTargetsWithInactiveClubMemberByAccountId)
in RegisterAccountUseCase, and replace inline logic with calls to the new domain
methods, persisting returned/modified entities as needed (e.g., via
transactionRepository.save and accountRepository.save or letting ORM flush).
Source: Coding guidelines
| @Column(name = "account_holder", length = 30) | ||
| var holder: String = normalizeRequired(holder, "예금주") | ||
| private set | ||
|
|
||
| @Column(name = "bank_guide", length = 200) | ||
| @Column(name = "bank_guide", length = 30) | ||
| var guide: String? = normalizeOptional(guide) | ||
| private set |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# 기존 BankAccount 길이 제약 확인 및 마이그레이션 스크립트 존재 여부 확인
# Flyway/Liquibase 마이그레이션 파일에서 bank_account 관련 ALTER 검색
fd -e sql -e xml -e yaml . | xargs rg -l -i 'account_holder|bank_guide' 2>/dev/null || echo "No migration files found"
# 기존 컬럼 정의 확인
rg -n 'account_holder|bank_guide' --type=kotlinRepository: Team-Weeth/weeth-server
Length of output: 375
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 1) DB 마이그레이션에서 관련 컬럼/테이블 변경 여부 확인
rg -n -i 'account_holder|bank_guide|bank_account' src/main/resources/db/migration || true
# 2) BankAccount.kt 정규화 함수 호출부/정의 확인(파일 전체는 아니고 필요한 범위)
FILE="src/main/kotlin/com/weeth/domain/account/domain/vo/BankAccount.kt"
if [ -f "$FILE" ]; then
echo "---- $FILE (relevant lines) ----"
sed -n '1,120p' "$FILE" | nl -ba | sed -n '1,120p' || true
fi
# 3) normalizeRequired/normalizeOptional이 어디 정의돼 있는지 역추적
rg -n 'fun normalizeRequired|fun normalizeOptional|normalizeRequired\(|normalizeOptional\(' src/main/kotlin || trueRepository: Team-Weeth/weeth-server
Length of output: 3358
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 1) DB 마이그레이션에서 관련 컬럼/테이블 변경 여부 확인
rg -n -i 'account_holder|bank_guide|bank_account' src/main/resources/db/migration || true
# 2) BankAccount.kt 정규화 함수 호출부/정의 확인(파일 전체는 아니고 필요한 범위)
FILE="src/main/kotlin/com/weeth/domain/account/domain/vo/BankAccount.kt"
if [ -f "$FILE" ]; then
echo "---- $FILE (relevant lines) ----"
nl -ba "$FILE" | sed -n '1,160p' || true
fi
# 3) normalizeRequired/normalizeOptional이 어디 정의돼 있는지 역추적
rg -n 'fun normalizeRequired|fun normalizeOptional|normalizeRequired\(|normalizeOptional\(' src/main/kotlin || trueRepository: Team-Weeth/weeth-server
Length of output: 3358
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 1) BankAccount.kt: 컬럼 length 및 normalize 함수 구현 확인(불필요하게 전체 출력하지 않음)
FILE="src/main/kotlin/com/weeth/domain/account/domain/vo/BankAccount.kt"
echo "---- $FILE (column + normalize impl) ----"
sed -n '1,120p' "$FILE" | cat -n | sed -n '1,120p'
# 2) 마이그레이션에서 account_holder/bank_guide 변경(추가/ALTER) 이력 전체 확인
echo "---- migrations: account_holder/bank_guide/account 관련 ----"
rg -n -i 'account_holder|bank_guide|bank_account' src/main/resources/db/migration || true
# 3) 혹시 VARCHAR(30) 등으로 별도 변경이 있는지(오탐 줄이기 위해 컬럼명과 함께)
rg -n -i 'account_holder\s+varchar\(30\)|bank_guide\s+varchar\(30\)|varchar\(30\).*account_holder|varchar\(30\).*bank_guide' src/main/resources/db/migration || trueRepository: Team-Weeth/weeth-server
Length of output: 2781
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n --hidden -S 'ddl-auto|hbm2ddl|hibernate\.hbm2ddl|schema-validation|hibernate\.ddl' src/main/resources . || trueRepository: Team-Weeth/weeth-server
Length of output: 933
@Column(length)(30) vs 마이그레이션(DB VARCHAR(50/200)) 길이 불일치로 스키마 검증/업데이트 영향 점검 필요
BankAccount.kt에서 account_holder/bank_guide 길이를 각각 length = 30으로 매핑했지만, 마이그레이션 V1__add_membership_fee_account_models.sql에서는 account_holder VARCHAR(50), bank_guide VARCHAR(200)로 생성됩니다. application-dev.yml/application-prod.yml는 ddl-auto: validate라서 Hibernate 스키마 검증 시 컬럼 타입/길이 불일치로 시작 실패가 날 수 있고, application-local.yml의 ddl-auto: update에서는 컬럼 길이를 30으로 변경하려다 기존 데이터가 있으면 잘림/오류가 발생할 수 있습니다.
또한 normalizeRequired/normalizeOptional은 trim/공백 검증만 하고 길이 제한을 걸지 않으므로(30자 제한 미적용), 길이가 실제로 30으로 내려가는 경우 DB 제약 위반 가능성도 함께 고려해야 합니다.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/main/kotlin/com/weeth/domain/account/domain/vo/BankAccount.kt` around
lines 25 - 31, Entity column lengths in BankAccount (properties holder and
guide) don't match the migration V1__add_membership_fee_account_models.sql
(entity uses length=30 while migration sets VARCHAR(50) and VARCHAR(200])—align
them and add input validation: update `@Column`(length=...) on BankAccount.holder
and BankAccount.guide to match the migration (50 and 200 respectively) and/or
adjust the migration to match desired lengths, and update normalizeRequired and
normalizeOptional to enforce the same max lengths (trim + validate length, throw
or return a clear validation error) so runtime/data layer and Hibernate ddl-auto
validate/update won't fail. Ensure the change touches the BankAccount class,
normalizeRequired/normalizeOptional implementations, and coordinate with the
migration V1__add_membership_fee_account_models.sql or yml ddl-auto settings so
schema and entity mapping remain consistent.
soo0711
left a comment
There was a problem hiding this comment.
수고하셨습니다!! 👍
스웨거 설명이 정말 자세하게 작성되어 있어서 전체 플로우를 이해하기 편한 것 같아요!!
| @Operation( | ||
| summary = "[1단계] 회비 등록 초안 생성", | ||
| description = | ||
| "회비를 등록하기 전에 초안을 생성합니다. 멱등성 보장을 위해 이미 작성 중인 초안이 있다면 ID를 반환합니다. " + |
There was a problem hiding this comment.
피그마에서는 아래 순서로 정의되어 있는데
[1] 기본 정보
[2] 납부 대상
[3] 이월 설정
[4] 계좌 설정
[5] 계좌 등록
현재 API는 초안 생성이 [1단계]로 포함되어 있어 단계가 하나씩 밀려 보이는 것 같습니다!
초안 생성을 등록 플로우의 한 단계로 보고 의도적으로 분리해두신 걸까용??
There was a problem hiding this comment.
넵넵 디자인과 맞춘 단계보다는 개발하는 입장에서 연동할 단계에 집중해서 작성해뒀어용
📌 Summary
총 회비 등록을 위한 온보딩 API를 구현했습니다.
그 외 기능은 다음 PR에서 구현할 계획입니다.
📝 Changes
What
회비 온보딩 플로우 구현
Why
회비 기능 확장에 따라 변경된 플로우에 대응할 수 있게 하기 위해
How
📸 Screenshots / Logs
💡 Reviewer 참고사항
✅ Checklist
Summary by CodeRabbit
릴리스 노트
New Features
Bug Fixes