Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
e0b6104
feat : SearchController 추가, DynamicForm, MedianIncome 관련 코드 추가
yuyeol3 Apr 4, 2026
ec93439
feat : MedianIncomeService 구현
yuyeol3 Apr 4, 2026
dbcc8c4
fix : DynamicFormResponse ageBoundì 추가
yuyeol3 Apr 4, 2026
812cb08
fix : MedianIncomeService @Transactional(readOnly=true) 적용
yuyeol3 Apr 5, 2026
0b37e11
feat : 동적 폼 및 정보 입력 기능 구현
yuyeol3 Apr 5, 2026
1e79c14
fix : 사용하지 않는 Dto 제거
yuyeol3 Apr 5, 2026
4ed81e5
feat : 우대금리 조건 동적 폼 조건 및 검색 조건에 추가
yuyeol3 Apr 5, 2026
98aa005
merge : branch 'main' into yuyeol3/feat/search-and-dynamic-input
yuyeol3 Apr 5, 2026
a517264
fix : Transactional import 누락 해결
yuyeol3 Apr 5, 2026
f991704
feat: category_option에 code 부여해 enum을 통해 구분 가능하도록 하기
yuyeol3 Apr 5, 2026
f8e5d2f
feat : 아이디로 받은 키워드 옵션을 KeywordValueEnum으로 변환하도록 구현
yuyeol3 Apr 5, 2026
5c2e85c
fix : 변환 시 null로 매칭되는 경우 리스트에서 제외되도록 필터링 추가
yuyeol3 Apr 5, 2026
f40ee51
test : CategoryOptionService, DynamicFormService, MedianIncomeService…
yuyeol3 Apr 5, 2026
985778e
fix : spring-boot-starter-cache 의존성 설치
yuyeol3 Apr 5, 2026
7309f1e
fix : SearchController @RequestBody 누락 수정
yuyeol3 Apr 5, 2026
fef96d5
fix : 미취업 시 근속연수 숨기도록 수정
yuyeol3 Apr 5, 2026
2c74f8f
test : DynamicFormService 테스트 보강
yuyeol3 Apr 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
HELP.md
.gradle
build/
.gradle-user/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
Expand Down
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation "org.springframework.boot:spring-boot-starter-validation"
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
implementation 'org.springframework.boot:spring-boot-starter-cache'
implementation 'io.jsonwebtoken:jjwt-api:0.12.3'


runtimeOnly 'org.postgresql:postgresql'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.6'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.3'
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/apptive/fin/FinApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@SpringBootApplication
@EnableJpaAuditing
@EnableCaching
@ConfigurationPropertiesScan
public class FinApplication {

Expand Down
2 changes: 2 additions & 0 deletions src/main/java/apptive/fin/category/entity/CategoryOption.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ public class CategoryOption {

private String value;

private String code;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "category_id")
private Category category ;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package apptive.fin.category.repository;

import apptive.fin.category.entity.CategoryOption;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;

public interface CategoryOptionRepository extends JpaRepository<CategoryOption, Long> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package apptive.fin.category.service;

import apptive.fin.category.entity.CategoryOption;
import apptive.fin.category.repository.CategoryOptionRepository;
import apptive.fin.category.repository.CategoryRepository;
import apptive.fin.search.KeywordValueEnum;
import lombok.RequiredArgsConstructor;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class CategoryOptionService {

private final CategoryOptionRepository categoryOptionRepository;

@Cacheable(cacheNames = "keywordOptionMap")
public Map<Long, KeywordValueEnum> getOptionMap() {
List<CategoryOption> allOptions = categoryOptionRepository.findAll();
Map<Long, KeywordValueEnum> map = new HashMap<>();

for (CategoryOption option : allOptions) {
if (option.getCode() != null && !option.getCode().isBlank()) {
KeywordValueEnum keyword = KeywordValueEnum.from(option.getCode());
if (keyword != null) {
map.put(option.getId(), keyword);
}
}
}

return map;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import apptive.fin.category.repository.CategoryRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;
Expand Down
63 changes: 63 additions & 0 deletions src/main/java/apptive/fin/search/KeywordValueEnum.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package apptive.fin.search;

import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
public enum KeywordValueEnum {

// 1. 거주 지역
REGION_SEOUL("REGION_SEOUL"),
REGION_BUSAN("REGION_BUSAN"),
REGION_DAEGU("REGION_DAEGU"),
REGION_INCHEON("REGION_INCHEON"),
REGION_GWANGJU("REGION_GWANGJU"),
REGION_DAEJEON("REGION_DAEJEON"),
REGION_ULSAN("REGION_ULSAN"),
REGION_SEJONG("REGION_SEJONG"),
REGION_GYEONGGI("REGION_GYEONGGI"),
REGION_GANGWON("REGION_GANGWON"),
REGION_CHUNGBUK("REGION_CHUNGBUK"),
REGION_CHUNGNAM("REGION_CHUNGNAM"),
REGION_JEONBUK("REGION_JEONBUK"),
REGION_JEONNAM("REGION_JEONNAM"),
REGION_GYEONGBUK("REGION_GYEONGBUK"),
REGION_GYEONGNAM("REGION_GYEONGNAM"),
REGION_JEJU("REGION_JEJU"),

// 2. 현재 신분
STATUS_UNEMPLOYED("STATUS_UNEMPLOYED"),
STATUS_PART_TIME("STATUS_PART_TIME"),
STATUS_SME_WORKER("STATUS_SME_WORKER"),
STATUS_MILITARY("STATUS_MILITARY"),

// 3. 저축 기간
TERM_OVER_5_YEARS("TERM_OVER_5_YEARS"),
TERM_2_TO_3_YEARS("TERM_2_TO_3_YEARS"),
TERM_AROUND_1_YEAR("TERM_AROUND_1_YEAR"),

// 4. 핵심 혜택 (핵심 기간)
BENEFIT_MAX_INTEREST("BENEFIT_MAX_INTEREST"),
BENEFIT_TAX_FREE("BENEFIT_TAX_FREE"),
BENEFIT_EASY_CONDITION("BENEFIT_EASY_CONDITION"),
BENEFIT_GOV_SUBSIDY("BENEFIT_GOV_SUBSIDY"),

// 5. 상품 관심사
INTEREST_SAVINGS("INTEREST_SAVINGS"),
INTEREST_LOAN("INTEREST_LOAN"),

// 6. 은행 거래
BANK_FIRST_TRANSACTION("BANK_FIRST_TRANSACTION"),
BANK_SALARY_TRANSFER("BANK_SALARY_TRANSFER"),
BANK_CARD_USAGE("BANK_CARD_USAGE");

private final String code;

public static KeywordValueEnum from(String code) {
try {
return KeywordValueEnum.valueOf(code);
}
catch (IllegalArgumentException e) {
return null;
}
}
}
34 changes: 34 additions & 0 deletions src/main/java/apptive/fin/search/controller/SearchController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package apptive.fin.search.controller;

import apptive.fin.search.dto.DynamicFormResponseDto;
import apptive.fin.search.dto.OptionRequestDto;
import apptive.fin.search.dto.SearchRequestDto;
import apptive.fin.search.service.DynamicFormService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequiredArgsConstructor
@RequestMapping("/search")
public class SearchController {

private final DynamicFormService dynamicFormService;

@PostMapping("/dynamic-form")
public DynamicFormResponseDto dynamicForm(@Valid @RequestBody SearchRequestDto searchRequestDto) {
return dynamicFormService.calcFormCondition(searchRequestDto);
}

@PostMapping
public SearchRequestDto search(@Valid @RequestBody SearchRequestDto searchRequestDto) {
return searchRequestDto;
}

}
19 changes: 19 additions & 0 deletions src/main/java/apptive/fin/search/dto/DetailedOptionsDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package apptive.fin.search.dto;

import java.time.LocalDate;
import java.util.List;

public record DetailedOptionsDto(
LocalDate birthdate,
Long annualIncome,
Integer householdSize,
Integer householdIncomePercent,
Integer tenureMonths,
Boolean isFirstJob,
Boolean isHomeless,
Boolean isHouseholder, // 세대주 여부
Long monthlySavingsGoal,
List<String> mainBanks,
List<PreferentialInterestRateOption> selectedInterestRateOptions
) {
}
26 changes: 26 additions & 0 deletions src/main/java/apptive/fin/search/dto/DynamicFormResponseDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package apptive.fin.search.dto;

import lombok.Builder;

import java.util.List;

@Builder
public record DynamicFormResponseDto(
Boolean showTenure,
Integer ageBound,
Integer yearlyEarnDefault,
Boolean showBankInterestRateCheckList,
MedianIncomesDto medianIncomes,
List<PreferentialInterestRateOption> preferentialInterestRateOptions
) {

public DynamicFormResponseDto {
if (showTenure == null) showTenure = true;
if (ageBound == null) ageBound = 34;
// if (yearlyEarnDefault == null);
if (showBankInterestRateCheckList == null) showBankInterestRateCheckList = false;
// if (medianIncomes == null) medianIncomes = null;
if (preferentialInterestRateOptions == null) preferentialInterestRateOptions = List.of();
}

}
43 changes: 43 additions & 0 deletions src/main/java/apptive/fin/search/dto/MedianIncomesDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package apptive.fin.search.dto;

import apptive.fin.search.entity.MedianIncome;
import lombok.Builder;

import java.util.List;

@Builder
public record MedianIncomesDto(
Integer year,
Integer householdSize,
Integer p60,
Integer p80,
Integer p100,
Integer p120,
Integer p150,
Integer p180
) {
public static MedianIncomesDto from(List<MedianIncome> medianIncomes) {
MedianIncomesDtoBuilder builder = MedianIncomesDto.builder();

builder.year(medianIncomes.getFirst().getYear());
builder.householdSize(medianIncomes.getFirst().getHouseholdSize());

for (MedianIncome income : medianIncomes) {
switch (income.getEarnPercent()) {
case 60 -> builder.p60(income.getMonthlyIncome());
case 80 -> builder.p80(income.getMonthlyIncome());
case 100 -> builder.p100(income.getMonthlyIncome());
case 120 -> builder.p120(income.getMonthlyIncome());
case 150 -> builder.p150(income.getMonthlyIncome());
case 180 -> builder.p180(income.getMonthlyIncome());
}
}

return builder.build();
}

public boolean isEmpty() {
return p60 == 0 && p80 == 0 && p100 == 0
&& p120 == 0 && p150 == 0 && p180 == 0;
}
}
9 changes: 9 additions & 0 deletions src/main/java/apptive/fin/search/dto/OptionRequestDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package apptive.fin.search.dto;

import jakarta.validation.constraints.NotNull;

public record OptionRequestDto(
@NotNull Long categoryId,
@NotNull Long optionId
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package apptive.fin.search.dto;

public record PreferentialInterestRateOption() {
}
12 changes: 12 additions & 0 deletions src/main/java/apptive/fin/search/dto/SearchRequestDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package apptive.fin.search.dto;

import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;

import java.util.List;

public record SearchRequestDto(
@NotNull List<@Valid OptionRequestDto> options,
@NotNull DetailedOptionsDto detailedOptions
) {
}
36 changes: 36 additions & 0 deletions src/main/java/apptive/fin/search/entity/MedianIncome.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package apptive.fin.search.entity;

import jakarta.persistence.*;
import lombok.*;

@Entity
@Table(name = "median_incomes")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
public class MedianIncome {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(name = "year", nullable = false)
private Integer year;

@Column(name = "household_size", nullable = false)
private Integer householdSize;

@Column(name = "earn_percent", nullable = false)
private Integer earnPercent;

@Column(name = "monthly_income", nullable = false)
private Integer monthlyIncome;

@Builder
public MedianIncome(int year, int householdSize, int earnPercent, int monthlyIncome) {
this.year = year;
this.householdSize = householdSize;
this.earnPercent = earnPercent;
this.monthlyIncome = monthlyIncome;
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package apptive.fin.search.repository;


import apptive.fin.search.dto.MedianIncomesDto;
import apptive.fin.search.entity.MedianIncome;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;

public interface MedianIncomeRepository extends JpaRepository<MedianIncome, Long> {
List<MedianIncome> findAllByYearAndHouseholdSize(int year, int householdSize);
}
Loading