Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 8 additions & 0 deletions module-app/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,14 @@ auth:
PathOption: ${JWT_COOKIE_PATH}
secureOption: ${JWT_SECURE_OPTION}
sameSiteOption: ${JWT_SAME_SITE}
# Exchange code 설정
exchange:
code:
ttl-seconds: 60
# OAuth2 임시 사용자 정보 TTL
oauth2:
temp-user:
ttl-minutes: 5

# 카카오 좌표 설정
kakao:
Expand Down
3 changes: 2 additions & 1 deletion module-app/src/main/resources/logback-spring.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!-- Loki/Grafana가 로그 레벨을 안정적으로 인식할 수 있도록 level 키를 앞쪽에 둔다 -->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} level=%-5level [%thread] [%X{traceId:-},%X{spanId:-}] %logger{36} - %msg%n
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} level=%-5level [%thread] [%X{traceId:-},%X{spanId:-}] %logger{36} -
%msg%n
</pattern>
</encoder>
</appender>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,21 @@ public enum SecurityErrorCode implements ErrorCode {
REFRESH_TOKEN_NOT_FOUND(401_112, HttpStatus.UNAUTHORIZED, "리프레쉬 토큰이 없기에 재로그인이 필요합니다."),
REFRESH_TOKEN_LOGOUT(401_113, HttpStatus.UNAUTHORIZED, "리프레쉬 토큰을 사용한 로그아웃에 유저가 다릅니다."),

EXCHANGE_CODE_INVALID(401_114, HttpStatus.UNAUTHORIZED, "유효하지 않은 Exchange Code입니다."),
EXCHANGE_CODE_EXPIRED(401_115, HttpStatus.UNAUTHORIZED, "만료된 Exchange Code입니다."),

// ========================
// 429 Too Many Requests
// ========================
EXCHANGE_CODE_RATE_LIMITED(429_100, HttpStatus.TOO_MANY_REQUESTS, "Exchange Code 교환 요청이 너무 많습니다. 잠시 후 다시 시도해주세요."),

// ========================
// 403 Forbidden
// ========================
FORBIDDEN(403_100, HttpStatus.FORBIDDEN, "접속 권한이 없습니다."),
ACCESS_DENY(403_101, HttpStatus.FORBIDDEN, "접근이 거부되었습니다."),
UNAUTHORIZED_POST_ACCESS(403_102, HttpStatus.FORBIDDEN, "해당 게시글에 접근할 권한이 없습니다."),
INTERNAL_API_FORBIDDEN(403_103, HttpStatus.FORBIDDEN, "내부 API 접근이 거부되었습니다."),

// ========================
// 404 Not Found
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ public static DiagnosisDetailResponse from(EvaluationContext context) {
? List.of("해당 없음")
: context.getCurrentCandidates().stream()
.map((c -> c.noticeType().getValue() + " : " + c.supplyType().getValue()))
.distinct()
.toList();

// 지원 가능한 공급 유형 (중복 제거)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public static DiagnosisResponse from(EvaluationContext context) {
? List.of("해당 없음")
: context.getCurrentCandidates().stream()
.map((c -> c.noticeType().getValue() + " : " + c.supplyType().getValue()))
.distinct()
.toList();

return DiagnosisResponse.builder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,21 +154,21 @@ private double incomeByFamily(int familyCount, Map<Integer, Double> table, doubl

/**
* 최대 총자산 제한 (단위: 원)
* 통합공공임대/국민임대/행복주택: 총자산 33,700만원 이하
* 영구임대: 총자산 23,700만원 이하
* 통합공공임대/국민임대: 총자산 34,500만원 이하
* 영구임대: 총자산 24,500만원 이하
* 장기전세/공공임대: 부동산 21,550만원 이하
*/
@Override
public long maxTotalAsset(SupplyType supply, NoticeType rental, int familyCount) {
return switch (rental) {
/// 통합공공임대 - 총자산 337,000,000원 (우선공급 중 일부 적용 제외)
case PUBLIC_INTEGRATED -> 337_000_000L;
/// 통합공공임대 - 총자산 345,000,000원
case PUBLIC_INTEGRATED -> 345_000_000L;

/// 영구임대 - 총자산 237,000,000원 (수급자 등 일부 대상 제외)
case PERMANENT_RENTAL -> 237_000_000L;
/// 영구임대 - 총자산 245,000,000원
case PERMANENT_RENTAL -> 245_000_000L;

/// 국민임대 - 총자산 337,000,000원 (우선공급 중 일부 적용 제외)
case NATIONAL_RENTAL -> 337_000_000L;
/// 국민임대 - 총자산 345,000,000원
case NATIONAL_RENTAL -> 345_000_000L;

/// 장기전세 - 부동산 215,500,000원 (우선공급 중 일부 적용 제외)
case LONG_TERM_JEONSE -> 215_500_000L;
Expand All @@ -178,11 +178,11 @@ public long maxTotalAsset(SupplyType supply, NoticeType rental, int familyCount)

/// 행복주택 - 계층별 총자산 기준 상이
case HAPPY_HOUSING -> switch (supply) {
/// 대학생 특별공급 - 총자산 104,000,000원 (1억 400만원)
case STUDENT_SPECIAL -> 104_000_000L;
/// 대학생 특별공급 - 총자산 108,000,000원
case STUDENT_SPECIAL -> 108_000_000L;

/// 청년 특별공급 - 총자산 254,000,000원 (2억 5천 4백만원) - 맞벌이 등 고려
case YOUTH_SPECIAL -> 254_000_000L;
/// 청년 특별공급 - 총자산 251,000,000원
case YOUTH_SPECIAL -> 251_000_000L;

/// 기타 - 총자산 337,000,000원 (3억 3천 7백만원)
default -> 337_000_000L;
Expand All @@ -194,12 +194,12 @@ public long maxTotalAsset(SupplyType supply, NoticeType rental, int familyCount)
}

/**
* 자동차 자산 제한 - 45,630,000원 (4,563만원)
* 자동차 자산 제한 - 45,420,000원 (4,542만원)
* 모든 임대주택 유형 공통 적용
*/
@Override
public long checkMaxCarValue() {
return 45_630_000L;
return 45_420_000L;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package co.kr.pinhouse.domain.diagnostic.rule.domain.entity;

import co.kr.pinhouse.domain.housing.notice.domain.entity.NoticeType;

/**
* 내부 정책 variant.
* 응답 DTO에는 노출하지 않고, 후보별 세부 판정과 exact-support 여부만 관리한다.
*/
public enum CandidatePolicyKey {

DEFAULT(true, null),
INTEGRATED_YOUTH_18_TO_39(true, null),
YOUTH_19_TO_39(true, null),
SINGLE_PARENT_WITH_INFANT_OR_FETUS(true, null),
NEWLY_MARRIED_GENERIC(true, null),
UNSUPPORTED_PUBLIC_RENTAL_PRODUCT(
false, "공공임대는 5년·6년·10년 유형과 모집공고별 면적 기준 정보가 필요합니다."),
UNSUPPORTED_LONG_TERM_JEONSE_REGION(
false, "장기전세는 지역별 자산 기준과 모집공고 정보가 필요합니다."),
UNSUPPORTED_STUDENT_PARENT_INCOME(
false, "행복주택 대학생·취업준비생은 부모 소득 및 자산 정보가 필요합니다."),
UNSUPPORTED_MINOR_CHILD_AGE(
false, "신생아 특별공급은 2세 미만 자녀 여부를 확인할 입력이 필요합니다."),
UNSUPPORTED_FIRST_HOME_HISTORY(
false, "생애최초 특별공급은 과거 주택 소유 이력과 소득세 납부 이력이 필요합니다."),
UNSUPPORTED_ELDER_SUPPORT_DURATION(
false, "노부모부양 특별공급은 1년 또는 3년 이상 부양기간 정보가 필요합니다.");

private final boolean exactSupported;
private final String unsupportedReason;

CandidatePolicyKey(boolean exactSupported, String unsupportedReason) {
this.exactSupported = exactSupported;
this.unsupportedReason = unsupportedReason;
}

public static CandidatePolicyKey resolve(NoticeType noticeType, SupplyType supplyType) {
if (noticeType == NoticeType.PUBLIC_RENTAL) {
return UNSUPPORTED_PUBLIC_RENTAL_PRODUCT;
}

if (noticeType == NoticeType.LONG_TERM_JEONSE) {
return UNSUPPORTED_LONG_TERM_JEONSE_REGION;
}

return switch (supplyType) {
case STUDENT_SPECIAL -> UNSUPPORTED_STUDENT_PARENT_INCOME;
case MINOR_SPECIAL -> UNSUPPORTED_MINOR_CHILD_AGE;
case FIRST_SPECIAL -> UNSUPPORTED_FIRST_HOME_HISTORY;
case ELDER_SUPPORT_SPECIAL -> UNSUPPORTED_ELDER_SUPPORT_DURATION;
case YOUTH_SPECIAL -> noticeType == NoticeType.PUBLIC_INTEGRATED
? INTEGRATED_YOUTH_18_TO_39
: YOUTH_19_TO_39;
case SINGLE_PARENT_SPECIAL -> SINGLE_PARENT_WITH_INFANT_OR_FETUS;
case NEWCOUPLE_SPECIAL -> NEWLY_MARRIED_GENERIC;
default -> DEFAULT;
};
}

public boolean exactSupported() {
return exactSupported;
}

public String unsupportedReason() {
return unsupportedReason;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,38 +9,43 @@ public class RentalSupplyMapping {

public static final Map<NoticeType, List<SupplyType>> RENTAL_SUPPLY_MAP = Map.of(
NoticeType.PUBLIC_INTEGRATED, List.of(
SupplyType.SPECIAL, // 미성년자
SupplyType.YOUTH_SPECIAL,
SupplyType.ELDER_SPECIAL,
SupplyType.NEWCOUPLE_SPECIAL,
SupplyType.SINGLE_PARENT_SPECIAL,
SupplyType.GENERAL,
SupplyType.MULTICHILD_SPECIAL,
SupplyType.ELDER_SUPPORT_SPECIAL,
SupplyType.DEMOLITION,
SupplyType.NATIONAL_MERIT,
SupplyType.LONG_SERVICE_VETERAN,
SupplyType.NORTH_DEFECTOR,
SupplyType.COMFORT_WOMAN_VICTIM,
SupplyType.DISABLED,
SupplyType.NON_HOUSING_RESIDENT
SupplyType.NON_HOUSING_RESIDENT,
SupplyType.PERMANENT_LEASE_EVICTEE
),
NoticeType.NATIONAL_RENTAL, List.of(
SupplyType.SPECIAL, // 미성년자
SupplyType.NEWCOUPLE_SPECIAL,
SupplyType.SINGLE_PARENT_SPECIAL,
SupplyType.MULTICHILD_SPECIAL,
SupplyType.ELDER_SUPPORT_SPECIAL,
SupplyType.GENERAL,
SupplyType.DEMOLITION,
SupplyType.NATIONAL_MERIT,
SupplyType.LONG_SERVICE_VETERAN,
SupplyType.NORTH_DEFECTOR,
SupplyType.DISABLED,
SupplyType.PERMANENT_LEASE_EVICTEE,
SupplyType.TEMPORARY_STRUCTURE_RESIDENT,
SupplyType.UNAUTHORIZED_BUILDING_TENANT
),
NoticeType.HAPPY_HOUSING, List.of(
SupplyType.STUDENT_SPECIAL,
SupplyType.YOUTH_SPECIAL,
SupplyType.NEWCOUPLE_SPECIAL,
SupplyType.SINGLE_PARENT_SPECIAL,
SupplyType.ELDER_SPECIAL,
SupplyType.DISABLED,
SupplyType.NATIONAL_MERIT,
SupplyType.PERMANENT_LEASE_EVICTEE,
SupplyType.TEMPORARY_STRUCTURE_RESIDENT
SupplyType.ELDER_SPECIAL
),
NoticeType.PUBLIC_RENTAL, List.of(
SupplyType.GENERAL,
Expand All @@ -50,10 +55,14 @@ public class RentalSupplyMapping {
SupplyType.FIRST_SPECIAL, // 생애최초
SupplyType.MINOR_SPECIAL, // 신생아
SupplyType.YOUTH_SPECIAL, // 청년
SupplyType.NATIONAL_MERIT
SupplyType.NATIONAL_MERIT,
SupplyType.DEMOLITION,
SupplyType.LONG_SERVICE_VETERAN,
SupplyType.NORTH_DEFECTOR,
SupplyType.DISABLED
),
NoticeType.PERMANENT_RENTAL, List.of(
SupplyType.NEWCOUPLE_SPECIAL,
SupplyType.GENERAL,
SupplyType.SINGLE_PARENT_SPECIAL,
SupplyType.ELDER_SUPPORT_SPECIAL,
SupplyType.ELDER_SPECIAL,
Expand All @@ -63,9 +72,15 @@ public class RentalSupplyMapping {
SupplyType.DISABLED
),
NoticeType.LONG_TERM_JEONSE, List.of(
SupplyType.GENERAL,
SupplyType.NEWCOUPLE_SPECIAL,
SupplyType.SINGLE_PARENT_SPECIAL,
SupplyType.MULTICHILD_SPECIAL
SupplyType.MULTICHILD_SPECIAL,
SupplyType.DEMOLITION,
SupplyType.NATIONAL_MERIT,
SupplyType.DISABLED,
SupplyType.PERMANENT_LEASE_EVICTEE,
SupplyType.TEMPORARY_STRUCTURE_RESIDENT,
SupplyType.UNAUTHORIZED_BUILDING_TENANT
)
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,13 @@
@Builder
public record SupplyRentalCandidate(
SupplyType supplyType,
NoticeType noticeType
NoticeType noticeType,
CandidatePolicyKey policyKey
) {

/// 정적 팩토리 메서드 (기본 값)
public static List<SupplyRentalCandidate> basic() {

/// 모든 값 가져오기
List<SupplyType> supplyTypes = SupplyType.getAllTypes();

/// 모든 값 가져오기
List<NoticeType> noticeTypes = NoticeType.getAllTypes();

List<SupplyRentalCandidate> candidates = new ArrayList<>();

/// for문 돌리면서 값 합치기
Expand Down Expand Up @@ -52,6 +47,7 @@ public static SupplyRentalCandidate basic(SupplyType supplyType, NoticeType noti
return SupplyRentalCandidate.builder()
.supplyType(supplyType)
.noticeType(noticeType)
.policyKey(CandidatePolicyKey.resolve(noticeType, supplyType))
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import static co.kr.pinhouse.domain.housing.notice.domain.entity.NoticeType.LONG_TERM_JEONSE;
import static co.kr.pinhouse.domain.housing.notice.domain.entity.NoticeType.NATIONAL_RENTAL;
import static co.kr.pinhouse.domain.housing.notice.domain.entity.NoticeType.PUBLIC_INTEGRATED;
import static co.kr.pinhouse.domain.housing.notice.domain.entity.NoticeType.PUBLIC_RENTAL;

import java.util.ArrayList;
Expand Down Expand Up @@ -31,7 +30,6 @@ public class AccountRule implements Rule {
/// 청약통장이 없으면 불가능한 목록들
private static final List<NoticeType> NO_ACCOUNT_ALLOWED = List.of(
NATIONAL_RENTAL,
PUBLIC_INTEGRATED,
LONG_TERM_JEONSE,
PUBLIC_RENTAL
);
Expand Down Expand Up @@ -83,6 +81,7 @@ public RuleResult evaluate(EvaluationContext ctx) {

/// 청약통장 없는 경우, NO_ACCOUNT_ALLOWED에 해당하는 RentalType 제거
candidates.removeIf(c -> NO_ACCOUNT_ALLOWED.contains(c.noticeType()));
ctx.setCurrentCandidates(candidates);

return RuleResult.pass(code(),
"청약통장 미보유로 인한 유형 제거 완료",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,7 @@ public RuleResult evaluate(EvaluationContext ctx) {
/// 나이별 후보군 필터링
if (age < 19) {
candidates.removeIf(c ->
c.supplyType() == SupplyType.NEWCOUPLE_SPECIAL
|| c.supplyType() == SupplyType.ELDER_SPECIAL
c.supplyType() == SupplyType.ELDER_SPECIAL
|| c.supplyType() == SupplyType.GENERAL
);
} else if (age <= 39) {
Expand All @@ -49,10 +48,7 @@ public RuleResult evaluate(EvaluationContext ctx) {
|| c.supplyType() == SupplyType.ELDER_SPECIAL
);
} else if (age >= 65) {
candidates.removeIf(c ->
c.supplyType() == SupplyType.YOUTH_SPECIAL
|| c.supplyType() == SupplyType.NEWCOUPLE_SPECIAL
);
candidates.removeIf(c -> c.supplyType() == SupplyType.YOUTH_SPECIAL);
}

/// 2차, 주택 소유 여부
Expand All @@ -67,6 +63,7 @@ public RuleResult evaluate(EvaluationContext ctx) {

/// 본인이 주택을 소유한 경우 모든 임대주택 신청 불가
if (ownsHouse) {
ctx.setCurrentCandidates(List.of());
return RuleResult.fail(
code(),
"주택 소유자는 임대주택 지원이 불가능",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,7 @@ public class GeneralSupplyRule implements Rule {

/// 청약통장이 필요한 임대주택 유형
private static final Set<NoticeType> SUBSCRIPTION_REQUIRED_TYPES = Set.of(
NoticeType.PUBLIC_RENTAL, // 공공임대 (분양전환 가능)
NoticeType.PUBLIC_INTEGRATED // 통합공공임대 (분양전환 가능)
NoticeType.PUBLIC_RENTAL // 공공임대 (분양전환 가능)
);
private final PolicyUseCase policyUseCase;

Expand Down
Loading
Loading