diff --git a/src/main/java/com/pinHouse/server/core/util/BirthDayUtil.java b/src/main/java/com/pinHouse/server/core/util/BirthDayUtil.java index 58cb93d..bc0e0dd 100644 --- a/src/main/java/com/pinHouse/server/core/util/BirthDayUtil.java +++ b/src/main/java/com/pinHouse/server/core/util/BirthDayUtil.java @@ -3,6 +3,7 @@ import org.springframework.stereotype.Component; import java.time.LocalDate; +import java.time.Period; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; @@ -23,6 +24,18 @@ public static LocalDate parseBirthday(String year, String monthDay) { } } + /** + * 생년월일로 나이 계산 (만 나이) + * @param birthday LocalDate 형태의 생년월일 + */ + public static int calculateAge(LocalDate birthday) { + if (birthday == null) { + throw new IllegalArgumentException("생년월일이 null일 수 없습니다."); + } + LocalDate today = LocalDate.now(); + return Period.between(birthday, today).getYears(); + } + } diff --git a/src/main/java/com/pinHouse/server/core/entity/BaseTimeEntity.java b/src/main/java/com/pinHouse/server/platform/BaseTimeEntity.java similarity index 91% rename from src/main/java/com/pinHouse/server/core/entity/BaseTimeEntity.java rename to src/main/java/com/pinHouse/server/platform/BaseTimeEntity.java index d3e279e..3ecdc61 100644 --- a/src/main/java/com/pinHouse/server/core/entity/BaseTimeEntity.java +++ b/src/main/java/com/pinHouse/server/platform/BaseTimeEntity.java @@ -1,4 +1,4 @@ -package com.pinHouse.server.core.entity; +package com.pinHouse.server.platform; import jakarta.persistence.EntityListeners; import jakarta.persistence.MappedSuperclass; diff --git a/src/main/java/com/pinHouse/server/platform/diagnosis/rule/application/dto/response/.gitkeep b/src/main/java/com/pinHouse/server/platform/diagnosis/rule/application/dto/response/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/com/pinHouse/server/platform/diagnosis/rule/application/service/.gitkeep b/src/main/java/com/pinHouse/server/platform/diagnosis/rule/application/service/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/com/pinHouse/server/platform/diagnosis/rule/application/usecase/.gitkeep b/src/main/java/com/pinHouse/server/platform/diagnosis/rule/application/usecase/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/com/pinHouse/server/platform/diagnosis/rule/domain/entity/.gitkeep b/src/main/java/com/pinHouse/server/platform/diagnosis/rule/domain/entity/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/com/pinHouse/server/platform/diagnosis/rule/domain/repository/.gitkeep b/src/main/java/com/pinHouse/server/platform/diagnosis/rule/domain/repository/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/com/pinHouse/server/platform/diagnosis/rule/presentation/.gitkeep b/src/main/java/com/pinHouse/server/platform/diagnosis/rule/presentation/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/com/pinHouse/server/platform/diagnosis/rule/presentation/swagger/.gitkeep b/src/main/java/com/pinHouse/server/platform/diagnosis/rule/presentation/swagger/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/com/pinHouse/server/platform/diagnosis/school/application/dto/request/.gitkeep b/src/main/java/com/pinHouse/server/platform/diagnosis/school/application/dto/request/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/com/pinHouse/server/platform/diagnosis/school/application/dto/response/.gitkeep b/src/main/java/com/pinHouse/server/platform/diagnosis/school/application/dto/response/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/com/pinHouse/server/platform/diagnosis/school/application/service/.gitkeep b/src/main/java/com/pinHouse/server/platform/diagnosis/school/application/service/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/com/pinHouse/server/platform/diagnosis/school/application/usecase/.gitkeep b/src/main/java/com/pinHouse/server/platform/diagnosis/school/application/usecase/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/com/pinHouse/server/platform/diagnosis/school/domain/entity/.gitkeep b/src/main/java/com/pinHouse/server/platform/diagnosis/school/domain/entity/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/com/pinHouse/server/platform/diagnosis/school/domain/repository/.gitkeep b/src/main/java/com/pinHouse/server/platform/diagnosis/school/domain/repository/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/com/pinHouse/server/platform/diagnosis/school/presentation/.gitkeep b/src/main/java/com/pinHouse/server/platform/diagnosis/school/presentation/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/com/pinHouse/server/platform/diagnosis/school/presentation/swagger/.gitkeep b/src/main/java/com/pinHouse/server/platform/diagnosis/school/presentation/swagger/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/com/pinHouse/server/platform/housing/facility/domain/entity/Animal.java b/src/main/java/com/pinHouse/server/platform/housing/facility/domain/entity/Animal.java index d9f67fd..30f79ac 100644 --- a/src/main/java/com/pinHouse/server/platform/housing/facility/domain/entity/Animal.java +++ b/src/main/java/com/pinHouse/server/platform/housing/facility/domain/entity/Animal.java @@ -1,6 +1,6 @@ package com.pinHouse.server.platform.housing.facility.domain.entity; -import com.pinHouse.server.core.entity.Location; +import com.pinHouse.server.platform.region.domain.entity.Location; import com.pinHouse.server.platform.housing.facility.domain.entity.infra.Facility; import jakarta.persistence.Id; import lombok.AllArgsConstructor; diff --git a/src/main/java/com/pinHouse/server/platform/housing/facility/domain/entity/Library.java b/src/main/java/com/pinHouse/server/platform/housing/facility/domain/entity/Library.java index 8f0fee1..c95b649 100644 --- a/src/main/java/com/pinHouse/server/platform/housing/facility/domain/entity/Library.java +++ b/src/main/java/com/pinHouse/server/platform/housing/facility/domain/entity/Library.java @@ -1,6 +1,6 @@ package com.pinHouse.server.platform.housing.facility.domain.entity; -import com.pinHouse.server.core.entity.Location; +import com.pinHouse.server.platform.region.domain.entity.Location; import com.pinHouse.server.platform.housing.facility.domain.entity.infra.Facility; import jakarta.persistence.Id; import lombok.AllArgsConstructor; diff --git a/src/main/java/com/pinHouse/server/platform/housing/facility/domain/entity/Park.java b/src/main/java/com/pinHouse/server/platform/housing/facility/domain/entity/Park.java index 912c445..7cd52db 100644 --- a/src/main/java/com/pinHouse/server/platform/housing/facility/domain/entity/Park.java +++ b/src/main/java/com/pinHouse/server/platform/housing/facility/domain/entity/Park.java @@ -1,6 +1,6 @@ package com.pinHouse.server.platform.housing.facility.domain.entity; -import com.pinHouse.server.core.entity.Location; +import com.pinHouse.server.platform.region.domain.entity.Location; import com.pinHouse.server.platform.housing.facility.domain.entity.infra.Facility; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.index.GeoSpatialIndexType; diff --git a/src/main/java/com/pinHouse/server/platform/housing/facility/domain/entity/Sport.java b/src/main/java/com/pinHouse/server/platform/housing/facility/domain/entity/Sport.java index b79b0f5..aa2d6cb 100644 --- a/src/main/java/com/pinHouse/server/platform/housing/facility/domain/entity/Sport.java +++ b/src/main/java/com/pinHouse/server/platform/housing/facility/domain/entity/Sport.java @@ -1,6 +1,6 @@ package com.pinHouse.server.platform.housing.facility.domain.entity; -import com.pinHouse.server.core.entity.Location; +import com.pinHouse.server.platform.region.domain.entity.Location; import com.pinHouse.server.platform.housing.facility.domain.entity.infra.Facility; import jakarta.persistence.Id; import lombok.AllArgsConstructor; diff --git a/src/main/java/com/pinHouse/server/platform/housing/facility/domain/entity/Walking.java b/src/main/java/com/pinHouse/server/platform/housing/facility/domain/entity/Walking.java index fc8d494..a2484fc 100644 --- a/src/main/java/com/pinHouse/server/platform/housing/facility/domain/entity/Walking.java +++ b/src/main/java/com/pinHouse/server/platform/housing/facility/domain/entity/Walking.java @@ -1,6 +1,6 @@ package com.pinHouse.server.platform.housing.facility.domain.entity; -import com.pinHouse.server.core.entity.Location; +import com.pinHouse.server.platform.region.domain.entity.Location; import com.pinHouse.server.platform.housing.facility.domain.entity.infra.Facility; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.index.GeoSpatialIndexType; diff --git a/src/main/java/com/pinHouse/server/platform/housing/facility/domain/entity/infra/Facility.java b/src/main/java/com/pinHouse/server/platform/housing/facility/domain/entity/infra/Facility.java index 984c5dd..4363f4a 100644 --- a/src/main/java/com/pinHouse/server/platform/housing/facility/domain/entity/infra/Facility.java +++ b/src/main/java/com/pinHouse/server/platform/housing/facility/domain/entity/infra/Facility.java @@ -1,6 +1,6 @@ package com.pinHouse.server.platform.housing.facility.domain.entity.infra; -import com.pinHouse.server.core.entity.Location; +import com.pinHouse.server.platform.region.domain.entity.Location; public interface Facility { diff --git a/src/main/java/com/pinHouse/server/platform/housing/notice/domain/entity/Notice.java b/src/main/java/com/pinHouse/server/platform/housing/notice/domain/entity/Notice.java index 7f0a337..bc8be3b 100644 --- a/src/main/java/com/pinHouse/server/platform/housing/notice/domain/entity/Notice.java +++ b/src/main/java/com/pinHouse/server/platform/housing/notice/domain/entity/Notice.java @@ -1,6 +1,6 @@ package com.pinHouse.server.platform.housing.notice.domain.entity; -import com.pinHouse.server.core.entity.Location; +import com.pinHouse.server.platform.region.domain.entity.Location; import com.pinHouse.server.platform.housing.deposit.domain.entity.NoticeSupply; import jakarta.persistence.Id; import lombok.AllArgsConstructor; diff --git a/src/main/java/com/pinHouse/server/platform/housingFit/diagnosis/application/dto/request/DiagnosisRequest.java b/src/main/java/com/pinHouse/server/platform/housingFit/diagnosis/application/dto/request/DiagnosisRequest.java new file mode 100644 index 0000000..f9573ee --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/housingFit/diagnosis/application/dto/request/DiagnosisRequest.java @@ -0,0 +1,112 @@ +package com.pinHouse.server.platform.housingFit.diagnosis.application.dto.request; + +import com.pinHouse.server.platform.housingFit.diagnosis.domain.entity.*; +import com.pinHouse.server.platform.user.domain.entity.Gender; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; + +import java.time.LocalDate; +import java.util.List; + +@Data +@Builder +@AllArgsConstructor +@Schema(description = "진단 요청 DTO") +public class DiagnosisRequest { + + /** 1) 기초 자격: 성별 */ + @Schema(description = "성별", example = "남성") + private Gender gender; // 성별 + + /** 2) 기초 자격: 나이 */ + @Schema(description = "생일", example = "2000-06-15") + private LocalDate birthday; // 생일 + + /** 5-6) 소득 요건(일자리 종사) */ + @Schema(description = "월 소득", example = "2000000") + private int monthPay; // 소득 여부 + + /** 7-10) 청약통장 요건(가입기간/예치금/상품유형) */ + @Schema(description = "청약통장 보유 여부", example = "true") + private boolean hasAccount; + + @Schema(description = "청약통장 가입 기간", example = "6개월 이상 ~ 1년 미만") + private SubscriptionPeriod accountYears; // 가입 년수(년) + + @Schema(description = "청약통장 납입 횟수", example = "6회 ~ 11회") + private SubscriptionCount accountDeposit; // 가입 횟수 + + @Schema(description = "청약통장 금액", example = "600만원 이상") + private SubscriptionAccount account; // 가입 금액 + + /** 11) 신혼 부부 요건 */ + @Schema(description = "결혼 여부", example = "false") + private boolean maritalStatus; // 결혼 여부 + + @Schema(description = "결혼 기간(년)", example = "0") + private Integer marriageYears; // 결혼 기간 + + /** 12) 자녀(다자녀) 요건 */ + @Schema(description = "태아 수", example = "0") + private int unbornChildrenCount; // 태아 수 + + @Schema(description = "6세 이하 자녀 수", example = "0") + private int under6ChildrenCount; // 6세 이하 자녀 수 + + @Schema(description = "7세 이상 미성년 자녀 수", example = "0") + private int over7MinorChildrenCount; // 7세 이상 미성년 자녀 수 + + /** 14) 대학생 요건 */ + @Schema(description = "교육 상태", example = "대학교 휴학 중이며 다음 학기 복학 예정") + private EducationStatus educationStatus; // 학생 정보 + + @Schema(description = "자동차 보유 여부", example = "false") + private boolean hasCar; // 자동차 소유 여부 + + @Schema(description = "자동차 가격", example = "0") + private long carValue; // 자동차 가격 + + /** 16-17) 세대 관련 정보 */ + @Schema(description = "세대주 여부", example = "true") + private boolean isHouseholdHead; // 세대원, 세대주 + + @Schema(description = "1인 가구 여부", example = "true") + private boolean isSingle; // 1인 가구 여부 - false면 가족들과 거주 + + @Schema(description = "태아 가구 수", example = "0") + private int fetusCount; // 태아 가구수 + + @Schema(description = "미성년자 가구 수", example = "0") + private int minorCount; // 미성년자 가구수 + + @Schema(description = "성인 가구 수", example = "1") + private int adultCount; // 성인 가구수 + + /** 18) 세대 소득 요건 */ + @Schema(description = "가구 소득 수준", example = "2구간") + private IncomeLevel incomeLevel; // 가구 소득 + + /** 19) 세대 주택 요건 */ + @Schema(description = "주택 소유 상태", example = "우리집 가구원 모두 주택을 소유하고 있지 않아요") + private HousingOwnershipStatus housingStatus; // 주택 소유 여부 + + @Schema(description = "무주택 기간(년)", example = "3") + private int housingYears; // 무주택 기간 여부 + + /** 20-22) 세대 자산 요건 */ + @Schema(description = "부동산/토지 자산", example = "0") + private long propertyAsset; // 부동산/토지 자산 + + @Schema(description = "자동차 자산", example = "0") + private long carAsset; // 자동차 가격 + + @Schema(description = "금융 자산", example = "1000000") + private long financialAsset; // 금융자산 + + /** 최종) 특수 계층 요건 */ + @Schema(description = "특수 계층 목록", example = "[\"주거급여 수급자\"]") + private List hasSpecialCategory; // 특수 계층인지 + +} diff --git a/src/main/java/com/pinHouse/server/platform/housingFit/diagnosis/application/dto/response/DiagnosisResponse.java b/src/main/java/com/pinHouse/server/platform/housingFit/diagnosis/application/dto/response/DiagnosisResponse.java new file mode 100644 index 0000000..592db8b --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/housingFit/diagnosis/application/dto/response/DiagnosisResponse.java @@ -0,0 +1,60 @@ +package com.pinHouse.server.platform.housingFit.diagnosis.application.dto.response; + +import com.pinHouse.server.platform.housingFit.diagnosis.domain.entity.Diagnosis; +import com.pinHouse.server.platform.housingFit.rule.application.dto.response.RuleResult; +import com.pinHouse.server.platform.housingFit.rule.domain.entity.EvaluationContext; +import com.pinHouse.server.platform.housingFit.rule.domain.entity.SupplyType; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; + +import java.util.List; +import java.util.UUID; + +/** + * 최종 진단 응답 DTO + * - 어떤 Rule들을 통과/실패했는지 + * - 최종 적합 여부 + * - 요약 메시지 + */ +@Data +@Builder +@AllArgsConstructor +public class DiagnosisResponse { + + private Long id; // 진단 기록 ID + private UUID userId; // 유저 ID + + private boolean eligible; // 최종 자격 여부 + private String decisionMessage; // 최종 요약 메시지 ("국민임대주택 가능", "부적합" 등) + private List recommended; // 추천 후보 리스트 + + /// 정적 팩토리 메서드 + public static DiagnosisResponse from(EvaluationContext context) { + + Diagnosis diagnosis = context.getDiagnosis(); + + // 실패 이유만 추출 + List failureReasons = context.getRuleResults().stream() + .filter(r -> !r.pass()) + .map(RuleResult::message) + .toList(); + + // 추천 후보 (후보군이 남아있으면 추천, 없으면 "해당 없음") + List recommended = context.getCurrentCandidates().isEmpty() ? + List.of("해당 없음") : + context.getCurrentCandidates().stream() + .map((c -> c.rentalType().getValue() + " : " + c.supplyType().getValue())) + .toList(); + + return DiagnosisResponse.builder() + .id(diagnosis.getId()) + .userId(diagnosis.getUser().getId()) + .eligible(!recommended.contains("해당 없음")) + .decisionMessage(!recommended.contains("해당 없음") ? + "추천 임대주택이 있습니다" : "모든 조건 미충족") + .recommended(recommended) + .build(); + } +} + diff --git a/src/main/java/com/pinHouse/server/platform/housingFit/diagnosis/application/service/DiagnosisService.java b/src/main/java/com/pinHouse/server/platform/housingFit/diagnosis/application/service/DiagnosisService.java new file mode 100644 index 0000000..7faf5b3 --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/housingFit/diagnosis/application/service/DiagnosisService.java @@ -0,0 +1,94 @@ +package com.pinHouse.server.platform.housingFit.diagnosis.application.service; + +import com.pinHouse.server.core.response.response.ErrorCode; +import com.pinHouse.server.platform.housingFit.diagnosis.application.dto.request.DiagnosisRequest; +import com.pinHouse.server.platform.housingFit.diagnosis.application.dto.response.DiagnosisResponse; +import com.pinHouse.server.platform.housingFit.diagnosis.application.usecase.DiagnosisUseCase; +import com.pinHouse.server.platform.housingFit.diagnosis.domain.repository.DiagnosisJpaRepository; +import com.pinHouse.server.platform.housingFit.rule.domain.entity.EvaluationContext; +import com.pinHouse.server.platform.housingFit.diagnosis.domain.entity.Diagnosis; +import com.pinHouse.server.platform.housingFit.rule.application.usecase.RuleChainUseCase; +import com.pinHouse.server.platform.user.application.usecase.UserUseCase; +import com.pinHouse.server.platform.user.domain.entity.User; +import lombok.*; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.NoSuchElementException; +import java.util.UUID; + +/** + * 청약 진단 서비스 + * - 단계적 파이프라인(기초자격 → 지역/통장 → 소득/자산 → 특별공급별 규칙 → 일반공급/가점 → 우선순위 산정) + * - 규칙(Rule) 인터페이스 + 체인(Chain of Responsibility) + * - 정책(Policy) 외부화: Threshold들을 Provider에서 주입(향후 YAML/DB/어드민에서 변경 가능) + * - Explainable Result: 모든 통과/실패 사유를 코드 + 메시지 + 세부값으로 축적하여 응답으로 반환 + */ +@Service +@Transactional +@RequiredArgsConstructor +public class DiagnosisService implements DiagnosisUseCase { + + /// 의존성 + private final DiagnosisJpaRepository repository; + + /// 외부 의존성 + private final UserUseCase userService; + private final RuleChainUseCase ruleChain; + + /** + * 임대주택 진단하기 + * @param userId 진단할 유저 + * @param request 요청 DTO + * @return 진단 DTO + */ + @Override + public DiagnosisResponse diagnose(UUID userId, DiagnosisRequest request) { + + /// 유저 예외 처리 + User user = getUser(userId); + + /// 진단 도메인 생성 + var diagnosis = Diagnosis.of(user, request); + Diagnosis entity = repository.save(diagnosis); + + /// 작성한 내용을 바탕으로 진단 실행 + EvaluationContext context = ruleChain.evaluateAll(entity); + + /// DTO 생성 + return DiagnosisResponse.from(context); + } + + /** + * 나의 최근 청약진단 가져오기 + * @param userId 유저ID + * @return 청약진단 DTO + */ + @Override + public DiagnosisResponse getDiagnose(UUID userId) { + + /// 유저 예외 처리 + User user = getUser(userId); + + /// DB에서 결과 조회하기, 영속성 컨텍스트에 저장 + Diagnosis diagnosis = repository.findByUser(user); + + /// 작성한 내용을 바탕으로 진단 실행 (규칙에 따라서 바뀔 수 있기에 매번 실행하도록 수정) + EvaluationContext context = ruleChain.evaluateAll(diagnosis); + + /// DTO 생성 + return DiagnosisResponse.from(context); + } + + + /** + * 나의 진단 목록 결과하기 + * @param userId 유저 ID + */ + private User getUser(UUID userId) { + return userService.loadUserById(userId) + .orElseThrow(() -> new NoSuchElementException(ErrorCode.USER_NOT_FOUND.getMessage())); + } + + +} diff --git a/src/main/java/com/pinHouse/server/platform/housingFit/diagnosis/application/usecase/DiagnosisUseCase.java b/src/main/java/com/pinHouse/server/platform/housingFit/diagnosis/application/usecase/DiagnosisUseCase.java new file mode 100644 index 0000000..510b82e --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/housingFit/diagnosis/application/usecase/DiagnosisUseCase.java @@ -0,0 +1,16 @@ +package com.pinHouse.server.platform.housingFit.diagnosis.application.usecase; + +import com.pinHouse.server.platform.housingFit.diagnosis.application.dto.request.DiagnosisRequest; +import com.pinHouse.server.platform.housingFit.diagnosis.application.dto.response.DiagnosisResponse; + +import java.util.UUID; + +public interface DiagnosisUseCase { + + /// 청약 진단하기 + DiagnosisResponse diagnose(UUID userId, DiagnosisRequest request); + + /// 나의 진단 목록 조회하기 + DiagnosisResponse getDiagnose(UUID userId); + +} diff --git a/src/main/java/com/pinHouse/server/platform/housingFit/diagnosis/domain/entity/Diagnosis.java b/src/main/java/com/pinHouse/server/platform/housingFit/diagnosis/domain/entity/Diagnosis.java new file mode 100644 index 0000000..dcfaa3a --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/housingFit/diagnosis/domain/entity/Diagnosis.java @@ -0,0 +1,162 @@ +package com.pinHouse.server.platform.housingFit.diagnosis.domain.entity; + +import com.pinHouse.server.core.util.BirthDayUtil; +import com.pinHouse.server.platform.BaseTimeEntity; +import com.pinHouse.server.platform.housingFit.diagnosis.application.dto.request.DiagnosisRequest; +import com.pinHouse.server.platform.user.domain.entity.Gender; +import com.pinHouse.server.platform.user.domain.entity.User; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@AllArgsConstructor +@NoArgsConstructor +@Builder +@Getter +public class Diagnosis extends BaseTimeEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + /** 1) 기초 자격: 성별 */ + @Enumerated(EnumType.STRING) + private Gender gender; // 성별 + + /** 2) 기초 자격: 나이 */ + private int age; // 나이 + + /** + * 5-6) 소득 요건(일자리 종사) + */ + private int monthPay; // 소득 여부 + + /** + * 7-10) 청약통장 요건(가입기간/예치금/상품유형) + */ + private boolean hasAccount; + private SubscriptionPeriod accountYears; // 가입 년수(년) + private SubscriptionCount accountDeposit; // 가입 횟수 + private SubscriptionAccount account; // 가입 금액 + + + /** + * 11) 신혼 부부 요건 + */ + private boolean maritalStatus; // 결혼 여부 + private Integer marriageYears; // 결혼 기간 + + /** + * 12) 자녀(다자녀) 요건 + */ + private int unbornChildrenCount; // 태아 수 + private int under6ChildrenCount; // 6세 이하 자녀 수 + private int over7MinorChildrenCount; // 7세 이상 미성년 자녀 수 + + /** + * 14) 대학생 요건 + */ + private EducationStatus educationStatus; // 학생 정보 + private boolean hasCar; // 자동차 소유 여부 + private long carValue; // 자동차 가격 + + /** + * 16-17) 세대 관련 정보 + */ + private boolean isHouseholdHead; // 세대원, 세대주 + private boolean isSingle; // 1인 가구 여부 - false면 가족들과 거주 + private int fetusCount; // 태아 가구수 + private int minorCount; // 미성년자 가구수 + private int adultCount; // 성인 가구수 + + /** + * 18) 세대 소득 요건 + */ + private IncomeLevel incomeLevel; // 가구 소득 + + /** + * 19) 세대 주택 요건 + */ + private HousingOwnershipStatus housingStatus; // 주택 소유 여부 + private int housingYears; // 무주택 기간 여부 + + /** + * 20-22) 세대 자산 요건 + */ + private long propertyAsset; // 부동산/토지 자산 + private long carAsset; // 자동차 가격 + private long financialAsset; // 금융자산 + private long totalAsset; // 총자산 + + /** + * 최종) 특수 계층 요건 + */ + @ElementCollection(targetClass = SpecialCategory.class) + @Enumerated(EnumType.STRING) + private List hasSpecialCategory = new ArrayList<>(); // 특수 계층인지 + + /// 정적 팩토리 메서드 + public static Diagnosis of(User user, DiagnosisRequest request) { + return Diagnosis.builder() + .user(user) + .gender(request.getGender()) + .age(BirthDayUtil.calculateAge(request.getBirthday())) + .monthPay(request.getMonthPay()) + .hasAccount(request.isHasAccount()) + .accountYears(request.getAccountYears()) + .accountDeposit(request.getAccountDeposit()) + .account(request.getAccount()) + .maritalStatus(request.isMaritalStatus()) + .marriageYears(request.getMarriageYears()) + .unbornChildrenCount(request.getUnbornChildrenCount()) + .under6ChildrenCount(request.getUnder6ChildrenCount()) + .over7MinorChildrenCount(request.getOver7MinorChildrenCount()) + .educationStatus(request.getEducationStatus()) + .hasCar(request.isHasCar()) + .carValue(request.getCarValue()) + .isHouseholdHead(request.isHouseholdHead()) + .isSingle(request.isSingle()) + .fetusCount(request.getFetusCount()) + .minorCount(request.getMinorCount()) + .adultCount(request.getAdultCount()) + .incomeLevel(request.getIncomeLevel()) + .housingStatus(request.getHousingStatus()) + .housingYears(request.getHousingYears()) + .propertyAsset(request.getPropertyAsset()) + .carAsset(request.getCarAsset()) + .financialAsset(request.getFinancialAsset()) + .hasSpecialCategory(request.getHasSpecialCategory()) + .build(); + } + + + /// 다자녀 계산 로직 + public boolean checkMultiple() { + + int sum = unbornChildrenCount + under6ChildrenCount + over7MinorChildrenCount; + + /// boolean 다자녀 + return sum >= 3; + } + + /// 총자산 계산 로직 + public long getTotalAsset() { + + return propertyAsset + carAsset + financialAsset; + } + + /// 전체 세대수 계산 로직 + public int getFamilyCount() { + return fetusCount + minorCount + adultCount; + } +} diff --git a/src/main/java/com/pinHouse/server/platform/housingFit/diagnosis/domain/entity/EducationStatus.java b/src/main/java/com/pinHouse/server/platform/housingFit/diagnosis/domain/entity/EducationStatus.java new file mode 100644 index 0000000..51e29dd --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/housingFit/diagnosis/domain/entity/EducationStatus.java @@ -0,0 +1,21 @@ +package com.pinHouse.server.platform.housingFit.diagnosis.domain.entity; + +import com.fasterxml.jackson.annotation.JsonValue; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public enum EducationStatus { + + UNIVERSITY_ENROLLED_OR_ADMITTED("대학교 재학 중이거나 다음 학기에 입학 예정"), + UNIVERSITY_ON_LEAVE("대학교 휴학 중이며 다음 학기 복학 예정"), + GRADUATED_WITHIN_2Y("대학교 혹은 고등학교 졸업/중퇴 후 2년 이내"), + GRADUATED_OVER_2Y_GRAD_SCHOOL("졸업/중퇴 후 2년이 지났지만 대학원에 재학 중"), + NONE("해당 사항 없음"); + + private final String value; + + @JsonValue + public String getValue() { + return value; + } +} diff --git a/src/main/java/com/pinHouse/server/platform/housingFit/diagnosis/domain/entity/HousingOwnershipStatus.java b/src/main/java/com/pinHouse/server/platform/housingFit/diagnosis/domain/entity/HousingOwnershipStatus.java new file mode 100644 index 0000000..1189060 --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/housingFit/diagnosis/domain/entity/HousingOwnershipStatus.java @@ -0,0 +1,23 @@ +package com.pinHouse.server.platform.housingFit.diagnosis.domain.entity; + +import com.fasterxml.jackson.annotation.JsonValue; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public enum HousingOwnershipStatus { + /** 나는 무주택자지만 우리 가구원 중 주택 소유자가 있음 */ + HOUSEHOLD_MEMBER_OWNS_HOUSE("나는 무주택자지만 우리 가구원중 주택 소유자가 있어요"), + + /** 우리집 가구원 모두 주택을 소유하고 있지 않음 */ + NO_ONE_OWNS_HOUSE("우리집 가구원 모두 주택을 소유하고 있지 않아요"), + + /** 내가 주택을 소유하고 있음 */ + OWN_HOUSE("주택을 소유하고 있어요"); + + private final String description; + + @JsonValue + public String getDescription() { + return description; + } +} diff --git a/src/main/java/com/pinHouse/server/platform/housingFit/diagnosis/domain/entity/IncomeLevel.java b/src/main/java/com/pinHouse/server/platform/housingFit/diagnosis/domain/entity/IncomeLevel.java new file mode 100644 index 0000000..149b796 --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/housingFit/diagnosis/domain/entity/IncomeLevel.java @@ -0,0 +1,32 @@ +package com.pinHouse.server.platform.housingFit.diagnosis.domain.entity; + +import com.fasterxml.jackson.annotation.JsonValue; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public enum IncomeLevel { + PERCENT_30("1구간",30), + PERCENT_50("2구간",50), + PERCENT_70("3구간", 70), + PERCENT_100("4구간", 100), + PERCENT_110("5구간", 110), + PERCENT_120("5구간", 120), + PERCENT_130("5구간", 130), + PERCENT_150("6구간", 150), + PERCENT_160("기타", 160), + PERCENT_170("기타", 170), + PERCENT_180("기타", 180), + PERCENT_190("기타", 190),; + + private final String value; + private final int percent; + + @JsonValue + public String getValue() { + return value; + } + + public int getPercent() { + return percent; + } +} diff --git a/src/main/java/com/pinHouse/server/platform/housingFit/diagnosis/domain/entity/SpecialCategory.java b/src/main/java/com/pinHouse/server/platform/housingFit/diagnosis/domain/entity/SpecialCategory.java new file mode 100644 index 0000000..a649970 --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/housingFit/diagnosis/domain/entity/SpecialCategory.java @@ -0,0 +1,80 @@ +package com.pinHouse.server.platform.housingFit.diagnosis.domain.entity; + + +import com.fasterxml.jackson.annotation.JsonValue; +import com.pinHouse.server.platform.housingFit.rule.domain.entity.SupplyType; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public enum SpecialCategory { + + /// 1) 소득 관련 특수 계층 + NO("주거급여 수급자"), + LIFE("생계/의료급여 수급자"), + + /// 13) 가정 특수 계층 + SINGLE_PARENT("한부모 가정"), + PROTECTED_SINGLE_PARENT("보호대상 한부모 가정"), + RELATIVE_FOSTER("친인척 위탁가정"), + SUBSTITUTE_CARE("대리양육가정"), + + /// 15) 기타 특수 계층 + DEMOLITION("철거민"), + NATIONAL_HERO_PERSON_OR_HOUSEHOLD("국가 유공자 본인/가구"), + COMFORT_WOMAN_PERSON_OR_HOUSEHOLD("위안부 피해자 본인/가구"), + NORTH_KOREAN_DEFECTOR("북한이탈주민 본인"), + DISABLED_PERSON_OR_HOUSEHOLD("장애인 등록자/장애인 가구"), + YEONGGU_RENTAL_EXIT("영구임대 퇴거자"), + LONG_SERVICE_VETERAN("장기복무 제대군인"), + HOUSING_VULNERABLE_OR_EMERGENCY_SUPPORT("주거 취약계층/긴급 주거지원 대상자"), + + + FOSTER_FAMILY_OR_CHILDCARE_TERMINATING_WITHIN_2Y("위탁가정/보육원 시설종료2년이내, 종료예정자"), + RETURNED_MILITARY_PERSONNEL("귀한 국군포로 본인"), + TRAFFIC_ACCIDENT_SURVIVOR_CHILD_HOUSEHOLD("교통사고 유자녀 가정"), + INDUSTRIAL_WORKER("산단근로자"), + GUARANTOR_REFUSER("보증 거절자"), + + /// 17) 노인 지원 + SUPPORTING_ELDERLY("나 또는 배우자가 노부모를 1년 이상 부양"), + LIVING_IN_GROUP_HOME("그룹홈 거주"), + GRANDPARENT_FAMILY("조손가족"); + + private final String description; + + @JsonValue + public String getDescription() { + return description; + } + + /// 변환하기 + public SupplyType toSupplyType() { + return switch (this) { + /// 철거민 + case DEMOLITION -> SupplyType.DEMOLITION; + + /// 국가유공자 + case NATIONAL_HERO_PERSON_OR_HOUSEHOLD -> SupplyType.NATIONAL_MERIT; + + /// 장기복무 + case LONG_SERVICE_VETERAN -> SupplyType.LONG_SERVICE_VETERAN; + + /// 북한 이탈주민 + case NORTH_KOREAN_DEFECTOR -> SupplyType.NORTH_DEFECTOR; + + /// 위안부 + case COMFORT_WOMAN_PERSON_OR_HOUSEHOLD -> SupplyType.COMFORT_WOMAN_VICTIM; + + /// 장애인 + case DISABLED_PERSON_OR_HOUSEHOLD -> SupplyType.DISABLED; + + /// 비주택자 + case HOUSING_VULNERABLE_OR_EMERGENCY_SUPPORT -> SupplyType.NON_HOUSING_RESIDENT; + + /// 영구임대 퇴거 + case YEONGGU_RENTAL_EXIT -> SupplyType.PERMANENT_LEASE_EVICTEE; + + default -> null; + }; + } +} diff --git a/src/main/java/com/pinHouse/server/platform/housingFit/diagnosis/domain/entity/SubscriptionAccount.java b/src/main/java/com/pinHouse/server/platform/housingFit/diagnosis/domain/entity/SubscriptionAccount.java new file mode 100644 index 0000000..ebc2bf2 --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/housingFit/diagnosis/domain/entity/SubscriptionAccount.java @@ -0,0 +1,19 @@ +package com.pinHouse.server.platform.housingFit.diagnosis.domain.entity; + +import com.fasterxml.jackson.annotation.JsonValue; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public enum SubscriptionAccount { + UNDER_600("600만원 이하"), + UPPER_600("600만원 이상"); + + private final String value; + + @JsonValue + public String getValue() { + return value; + } + +} + diff --git a/src/main/java/com/pinHouse/server/platform/housingFit/diagnosis/domain/entity/SubscriptionCount.java b/src/main/java/com/pinHouse/server/platform/housingFit/diagnosis/domain/entity/SubscriptionCount.java new file mode 100644 index 0000000..e884ba5 --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/housingFit/diagnosis/domain/entity/SubscriptionCount.java @@ -0,0 +1,24 @@ +package com.pinHouse.server.platform.housingFit.diagnosis.domain.entity; + +import com.fasterxml.jackson.annotation.JsonValue; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public enum SubscriptionCount { + + FROM_0_TO_5("0회 ~ 5회"), + FROM_6_TO_11("6회 ~ 11회"), + FROM_11_TO_24("12회 ~ 23회"), + OVER_24("24회 ~ 35회"), + FROM_36_TO_48("36회 ~ 48회"), + FROM_48_TO_60("49회 ~ 59회"), + OVER_60("60회 이상"); + + private final String description; + + @JsonValue + public String getDescription() { + return description; + } + +} diff --git a/src/main/java/com/pinHouse/server/platform/housingFit/diagnosis/domain/entity/SubscriptionPeriod.java b/src/main/java/com/pinHouse/server/platform/housingFit/diagnosis/domain/entity/SubscriptionPeriod.java new file mode 100644 index 0000000..2d9cc3a --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/housingFit/diagnosis/domain/entity/SubscriptionPeriod.java @@ -0,0 +1,27 @@ +package com.pinHouse.server.platform.housingFit.diagnosis.domain.entity; + +import com.fasterxml.jackson.annotation.JsonValue; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public enum SubscriptionPeriod { + + LESS_THAN_6_MONTHS("6개월 미만",0.5), + SIX_TO_ONE_YEARS("6개월 이상 ~ 1년 미만",0.8), + ONE_TO_TWO_YEARS("1년 이상 ~ 2년 미만",1), + OVER_TWO_YEARS("2년 이상", 2),; + + private final String description; + + private final double years; + + @JsonValue + public String getDescription() { + return description; + } + + public double getYears() { + return years; + } + +} diff --git a/src/main/java/com/pinHouse/server/platform/housingFit/diagnosis/domain/repository/DiagnosisJpaRepository.java b/src/main/java/com/pinHouse/server/platform/housingFit/diagnosis/domain/repository/DiagnosisJpaRepository.java new file mode 100644 index 0000000..5d1128e --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/housingFit/diagnosis/domain/repository/DiagnosisJpaRepository.java @@ -0,0 +1,17 @@ +package com.pinHouse.server.platform.housingFit.diagnosis.domain.repository; + +import com.pinHouse.server.platform.housingFit.diagnosis.domain.entity.Diagnosis; +import com.pinHouse.server.platform.user.domain.entity.User; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.UUID; + +public interface DiagnosisJpaRepository extends JpaRepository { + + /** + * 유저 기반으로 진단 탐색하기 + * @param user 유저 + */ + Diagnosis findByUser(User user); + +} diff --git a/src/main/java/com/pinHouse/server/platform/housingFit/diagnosis/presentation/DiagnosisApi.java b/src/main/java/com/pinHouse/server/platform/housingFit/diagnosis/presentation/DiagnosisApi.java new file mode 100644 index 0000000..56571e3 --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/housingFit/diagnosis/presentation/DiagnosisApi.java @@ -0,0 +1,36 @@ +package com.pinHouse.server.platform.housingFit.diagnosis.presentation; + +import com.pinHouse.server.core.response.response.ApiResponse; +import com.pinHouse.server.platform.housingFit.diagnosis.application.dto.response.DiagnosisResponse; +import com.pinHouse.server.platform.housingFit.diagnosis.application.usecase.DiagnosisUseCase; +import com.pinHouse.server.platform.housingFit.diagnosis.presentation.swagger.DiagnosisApiSpec; +import com.pinHouse.server.platform.housingFit.diagnosis.application.dto.request.DiagnosisRequest; +import com.pinHouse.server.security.oauth2.domain.PrincipalDetails; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/v1/diagnosis") +@RequiredArgsConstructor +public class DiagnosisApi implements DiagnosisApiSpec { + + private final DiagnosisUseCase service; + + /** + * 청약 진단하는 로직 + * + * @param principalDetails 로그인한 유저 + * @param request 청약 진단할 결과 내용 + */ + @PostMapping() + public ApiResponse diagnosis(@AuthenticationPrincipal PrincipalDetails principalDetails, + @RequestBody DiagnosisRequest request) { + + /// 서비스 + DiagnosisResponse response = service.diagnose(principalDetails.getId(), request); + + /// 리턴 + return ApiResponse.ok(response); + } +} diff --git a/src/main/java/com/pinHouse/server/platform/housingFit/diagnosis/presentation/swagger/DiagnosisApiSpec.java b/src/main/java/com/pinHouse/server/platform/housingFit/diagnosis/presentation/swagger/DiagnosisApiSpec.java new file mode 100644 index 0000000..b62f390 --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/housingFit/diagnosis/presentation/swagger/DiagnosisApiSpec.java @@ -0,0 +1,25 @@ +package com.pinHouse.server.platform.housingFit.diagnosis.presentation.swagger; + +import com.pinHouse.server.core.response.response.ApiResponse; +import com.pinHouse.server.platform.housingFit.diagnosis.application.dto.request.DiagnosisRequest; +import com.pinHouse.server.platform.housingFit.diagnosis.application.dto.response.DiagnosisResponse; +import com.pinHouse.server.platform.housingFit.rule.application.dto.response.RuleResult; +import com.pinHouse.server.security.oauth2.domain.PrincipalDetails; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.RequestBody; + +import java.util.List; + +@Tag(name = "진단 API", description = "청약 진단 기능 API 입니다") +public interface DiagnosisApiSpec { + + @Operation( + summary = "청약 진단 API", + description = "청약 진단 API 입니다." + ) + ApiResponse diagnosis(@AuthenticationPrincipal PrincipalDetails principalDetails, + @RequestBody DiagnosisRequest requestDTO); + +} diff --git a/src/main/java/com/pinHouse/server/platform/housingFit/explanation/application/dto/response/DiagnosisQuestionResponse.java b/src/main/java/com/pinHouse/server/platform/housingFit/explanation/application/dto/response/DiagnosisQuestionResponse.java new file mode 100644 index 0000000..96d2ee9 --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/housingFit/explanation/application/dto/response/DiagnosisQuestionResponse.java @@ -0,0 +1,4 @@ +package com.pinHouse.server.platform.housingFit.explanation.application.dto.response; + +public record DiagnosisQuestionResponse() { +} diff --git a/src/main/java/com/pinHouse/server/platform/housingFit/explanation/application/dto/response/ExplanationResponse.java b/src/main/java/com/pinHouse/server/platform/housingFit/explanation/application/dto/response/ExplanationResponse.java new file mode 100644 index 0000000..88f5cdd --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/housingFit/explanation/application/dto/response/ExplanationResponse.java @@ -0,0 +1,4 @@ +package com.pinHouse.server.platform.housingFit.explanation.application.dto.response; + +public record ExplanationResponse() { +} diff --git a/src/main/java/com/pinHouse/server/platform/housingFit/explanation/application/service/ExplanationService.java b/src/main/java/com/pinHouse/server/platform/housingFit/explanation/application/service/ExplanationService.java new file mode 100644 index 0000000..dde3190 --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/housingFit/explanation/application/service/ExplanationService.java @@ -0,0 +1,40 @@ +package com.pinHouse.server.platform.housingFit.explanation.application.service; + +import com.pinHouse.server.platform.housingFit.explanation.domain.entity.DiagnosisType; +import com.pinHouse.server.platform.housingFit.explanation.application.dto.response.DiagnosisQuestionResponse; +import com.pinHouse.server.platform.housingFit.explanation.application.dto.response.ExplanationResponse; +import com.pinHouse.server.platform.housingFit.explanation.application.usecase.ExplanationUseCase; +import com.pinHouse.server.platform.housingFit.explanation.domain.repository.DiagnosisQuestionJpaRepository; +import com.pinHouse.server.platform.housingFit.explanation.domain.repository.ExplanationJpaRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import java.util.List; + +@Service +@Transactional +@RequiredArgsConstructor +public class ExplanationService implements ExplanationUseCase { + + /// 질문 의존성 + private final DiagnosisQuestionJpaRepository questionRepository; + + /// 설명 의존성 + private final ExplanationJpaRepository repository; + + /** + * 진단하기 + * @param type 타입 + */ + @Override + public List getDiagnose(DiagnosisType type) { + return null; + } + + @Override + public ExplanationResponse getExplanation(Long questionId) { + return null; + } + + +} diff --git a/src/main/java/com/pinHouse/server/platform/housingFit/explanation/application/usecase/ExplanationUseCase.java b/src/main/java/com/pinHouse/server/platform/housingFit/explanation/application/usecase/ExplanationUseCase.java new file mode 100644 index 0000000..2e036dc --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/housingFit/explanation/application/usecase/ExplanationUseCase.java @@ -0,0 +1,17 @@ +package com.pinHouse.server.platform.housingFit.explanation.application.usecase; + +import com.pinHouse.server.platform.housingFit.explanation.application.dto.response.DiagnosisQuestionResponse; +import com.pinHouse.server.platform.housingFit.explanation.domain.entity.DiagnosisType; +import com.pinHouse.server.platform.housingFit.explanation.application.dto.response.ExplanationResponse; +import java.util.List; + +public interface ExplanationUseCase { + + /// 청약 진단 질문 조회하기 + List getDiagnose(DiagnosisType type); + + /// 청약 진단 질문 설명 + ExplanationResponse getExplanation(Long questionId); + + +} diff --git a/src/main/java/com/pinHouse/server/platform/housingFit/explanation/domain/entity/DiagnosisQuestion.java b/src/main/java/com/pinHouse/server/platform/housingFit/explanation/domain/entity/DiagnosisQuestion.java new file mode 100644 index 0000000..c378f82 --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/housingFit/explanation/domain/entity/DiagnosisQuestion.java @@ -0,0 +1,36 @@ +package com.pinHouse.server.platform.housingFit.explanation.domain.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@AllArgsConstructor +@NoArgsConstructor +@Builder +@Getter +public class DiagnosisQuestion { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String questionText; + + @Enumerated(EnumType.STRING) + private DiagnosisType diagnosisType; + + private int orderNo; + + /// 정적 팩토리 메서드 + public static DiagnosisQuestion of(String questionText, DiagnosisType diagnosisType) { + return DiagnosisQuestion.builder() + .questionText(questionText) + .diagnosisType(diagnosisType) + .orderNo(0) + .build(); + } + +} diff --git a/src/main/java/com/pinHouse/server/platform/housingFit/explanation/domain/entity/DiagnosisType.java b/src/main/java/com/pinHouse/server/platform/housingFit/explanation/domain/entity/DiagnosisType.java new file mode 100644 index 0000000..85ee762 --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/housingFit/explanation/domain/entity/DiagnosisType.java @@ -0,0 +1,20 @@ +package com.pinHouse.server.platform.housingFit.explanation.domain.entity; + +import com.fasterxml.jackson.annotation.JsonValue; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public enum DiagnosisType { + + YOUTH("청년"), + MIDDLE_AGED("중장년층"), + SENIOR("고령"); + + private final String description; + + @JsonValue + public String getDescription() { + return description; + } +} + diff --git a/src/main/java/com/pinHouse/server/platform/housingFit/explanation/domain/entity/Explanation.java b/src/main/java/com/pinHouse/server/platform/housingFit/explanation/domain/entity/Explanation.java new file mode 100644 index 0000000..f02fffb --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/housingFit/explanation/domain/entity/Explanation.java @@ -0,0 +1,38 @@ +package com.pinHouse.server.platform.housingFit.explanation.domain.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@AllArgsConstructor +@NoArgsConstructor +@Builder +@Getter +public class Explanation { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @OneToOne + @JoinColumn(name = "question_id") + private DiagnosisQuestion question; + + private String explanation; + + private String imageUrl; + + /// 정적 팩토리 메서드 + public static Explanation of(DiagnosisQuestion question, String explanation, String imageUrl) { + return Explanation.builder() + .explanation(explanation) + .imageUrl(imageUrl) + .question(question) + .build(); + + } + +} diff --git a/src/main/java/com/pinHouse/server/platform/housingFit/explanation/domain/repository/DiagnosisQuestionJpaRepository.java b/src/main/java/com/pinHouse/server/platform/housingFit/explanation/domain/repository/DiagnosisQuestionJpaRepository.java new file mode 100644 index 0000000..03c3214 --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/housingFit/explanation/domain/repository/DiagnosisQuestionJpaRepository.java @@ -0,0 +1,8 @@ +package com.pinHouse.server.platform.housingFit.explanation.domain.repository; + +import com.pinHouse.server.platform.housingFit.explanation.domain.entity.DiagnosisQuestion; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface DiagnosisQuestionJpaRepository extends JpaRepository { + +} diff --git a/src/main/java/com/pinHouse/server/platform/housingFit/explanation/domain/repository/ExplanationJpaRepository.java b/src/main/java/com/pinHouse/server/platform/housingFit/explanation/domain/repository/ExplanationJpaRepository.java new file mode 100644 index 0000000..8d4a26b --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/housingFit/explanation/domain/repository/ExplanationJpaRepository.java @@ -0,0 +1,7 @@ +package com.pinHouse.server.platform.housingFit.explanation.domain.repository; + +import com.pinHouse.server.platform.housingFit.explanation.domain.entity.Explanation; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ExplanationJpaRepository extends JpaRepository { +} diff --git a/src/main/java/com/pinHouse/server/platform/housingFit/explanation/presentation/ExplanationApi.java b/src/main/java/com/pinHouse/server/platform/housingFit/explanation/presentation/ExplanationApi.java new file mode 100644 index 0000000..e589f7a --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/housingFit/explanation/presentation/ExplanationApi.java @@ -0,0 +1,55 @@ +package com.pinHouse.server.platform.housingFit.explanation.presentation; + +import com.pinHouse.server.core.response.response.ApiResponse; +import com.pinHouse.server.platform.housingFit.explanation.domain.entity.DiagnosisType; +import com.pinHouse.server.platform.housingFit.explanation.application.dto.response.DiagnosisQuestionResponse; +import com.pinHouse.server.platform.housingFit.explanation.application.dto.response.ExplanationResponse; +import com.pinHouse.server.platform.housingFit.explanation.application.usecase.ExplanationUseCase; +import com.pinHouse.server.platform.housingFit.explanation.domain.entity.DiagnosisQuestion; +import com.pinHouse.server.platform.housingFit.explanation.presentation.swagger.ExplanationApiSpec; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import java.util.List; + +@RestController +@RequestMapping("/api/v1/diagnosis/info") +@RequiredArgsConstructor +public class ExplanationApi implements ExplanationApiSpec { + + private final ExplanationUseCase service; + + /** + * 질문 받는 로직 + * @param type 기본 질문 이후, 추가 질문 + */ + @GetMapping("/question") + public ApiResponse> getQuestion( + @RequestParam DiagnosisType type + ) { + + /// 서비스 + List response = service.getDiagnose(type); + + /// 리턴 + return ApiResponse.ok(response); + } + + /** + * 질문에 보조 설명을 받는 로직 + * + * @param questionId 궁금한 질문의 Id + */ + @GetMapping() + public ApiResponse getQuestion( + @RequestParam Long questionId + ) { + /// 서비스 + ExplanationResponse explanation = service.getExplanation(questionId); + + /// 리턴 + return ApiResponse.ok(explanation); + } +} diff --git a/src/main/java/com/pinHouse/server/platform/housingFit/explanation/presentation/swagger/ExplanationApiSpec.java b/src/main/java/com/pinHouse/server/platform/housingFit/explanation/presentation/swagger/ExplanationApiSpec.java new file mode 100644 index 0000000..2609563 --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/housingFit/explanation/presentation/swagger/ExplanationApiSpec.java @@ -0,0 +1,27 @@ +package com.pinHouse.server.platform.housingFit.explanation.presentation.swagger; + +import com.pinHouse.server.core.response.response.ApiResponse; +import com.pinHouse.server.platform.housingFit.explanation.application.dto.response.ExplanationResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.web.bind.annotation.RequestParam; + +@Tag(name = "질문 설명 API", description = "질문에 대한 추가 설명을 요청하는 API 입니다.") +public interface ExplanationApiSpec { + + @Operation( + summary = "질문 설명 API", + description = "질문에 대한 설명을 호출하는 함수" + ) + public ApiResponse getQuestion( + @Parameter( + description = "질문 ID", + example = "1" + ) + @RequestParam Long questionId + ); + + + +} diff --git a/src/main/java/com/pinHouse/server/platform/housingFit/rule/application/dto/response/RuleResult.java b/src/main/java/com/pinHouse/server/platform/housingFit/rule/application/dto/response/RuleResult.java new file mode 100644 index 0000000..8a93807 --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/housingFit/rule/application/dto/response/RuleResult.java @@ -0,0 +1,31 @@ +package com.pinHouse.server.platform.housingFit.rule.application.dto.response; + +import lombok.Builder; + +import java.util.Collections; +import java.util.Map; + +/** + * @param pass 통과 여부 + * @param code 규칙 코드 + * @param message 사용자 메시지 + * @param details 디버그/설명용 세부값 + */ +@Builder +public record RuleResult( + boolean pass, + String code, + String message, + Map details +) { + + /// 정적 팩토리 메서드 + public static RuleResult pass(String code, String msg, Map details) { + return new RuleResult(true, code, msg, details == null ? Collections.emptyMap() : details); + } + + /// 정적 팩토리 메서드, 불가능한 임대주택에 fail 부여 + public static RuleResult fail(String code, String msg, Map details) { + return new RuleResult(false, code, msg, details == null ? Collections.emptyMap() : details); + } +} diff --git a/src/main/java/com/pinHouse/server/platform/housingFit/rule/application/service/PolicyProvider.java b/src/main/java/com/pinHouse/server/platform/housingFit/rule/application/service/PolicyProvider.java new file mode 100644 index 0000000..666f64a --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/housingFit/rule/application/service/PolicyProvider.java @@ -0,0 +1,177 @@ +package com.pinHouse.server.platform.housingFit.rule.application.service; + +import com.pinHouse.server.platform.housingFit.rule.application.usecase.PolicyUseCase; +import com.pinHouse.server.platform.housingFit.rule.domain.entity.RentalType; +import com.pinHouse.server.platform.housingFit.rule.domain.entity.SupplyType; +import org.springframework.stereotype.Component; +import java.util.*; + +import static com.pinHouse.server.platform.housingFit.rule.domain.entity.RentalType.HAPPY_HOUSING; + +/** + * SupplyType + RentalType 세트를 모두 반영한 정책 제공 + */ +@Component +public class PolicyProvider implements PolicyUseCase { + + /** + * 최대 소득 비율 (%) -가구원수 반영 + */ + @Override + public double maxIncomeRatio(SupplyType supply, RentalType rental, int familyCount) { + return switch (rental) { + + /// 통합공공임대 경우 + case PUBLIC_INTEGRATED -> switch (supply) { + /// 국가유공자, 장기복무 제대군인, 북한이탈주민, 다자녀가구, 장애인, 비주택거주자, 청년, 신혼부부·한부모가족,고령자 + case SPECIAL, MULTICHILD_SPECIAL -> incomeByFamily(familyCount, + Map.of(1, 120.0, 2, 110.0, 3, 100.0), 100.0); + + /// 청년, 고령자 + case YOUTH_SPECIAL, ELDER_SPECIAL-> incomeByFamily(familyCount, + Map.of(1, 170.0, 2, 160.0, 3, 150.0), 150.0); + + /// 신혼부부·한부모가족는 맞벌이하느 2인(100, 190), 3인 이상(100,180)까지 증가 + case NEWCOUPLE_SPECIAL -> incomeByFamily(familyCount, + Map.of(1, 170.0, 2, 160.0, 3, 150.0, 100, 190.0, 101, 180.0), 150.0); + + default -> incomeByFamily(familyCount, + Map.of(1, 170.0, 2, 160.0, 3, 150.0), 150.0); + }; + + /// 국민임대 + case NATIONAL_RENTAL -> /// 기본은 1인 90, 2인 80, 그 이상 70 + incomeByFamily(familyCount, + Map.of(1, 90.0, 2, 80.0, 3, 70.0), 70.0); + + /// 행복주택 + case HAPPY_HOUSING -> switch (supply) { + /// 대학생, 청년, 고령자는 100 이하 + case STUDENT_SPECIAL, YOUTH_SPECIAL, ELDER_SPECIAL -> incomeByFamily(familyCount, + Map.of(1, 100.0), 100.0); + + /// 맞벌이하는 부부는 120까지 가능 + case NEWCOUPLE_SPECIAL -> incomeByFamily(familyCount, + Map.of(1, 100.0, 100, 120.0), 100.0); + + /// 기본은 + default -> incomeByFamily(familyCount, + Map.of(1, 120.0, 2, 110.0, 3, 70.0), 70.0); + }; + + /// 공공임대 + case PUBLIC_RENTAL -> switch (supply) { + /// 대학생, 청년, 고령자는 100 이하 + case STUDENT_SPECIAL, YOUTH_SPECIAL, ELDER_SPECIAL -> incomeByFamily(familyCount, + Map.of(1, 100.0), 100.0); + + case ELDER_SUPPORT_SPECIAL, MULTICHILD_SPECIAL -> incomeByFamily(familyCount, + Map.of(1, 120.0), 120.0); + + /// 맞벌이하는 부부는 120까지 가능 + case NEWCOUPLE_SPECIAL -> incomeByFamily(familyCount, + Map.of(1, 130.0, 100, 140.0), 130); + + /// 기본은 + default -> incomeByFamily(familyCount, + Map.of(1, 120.0, 2, 110.0, 3, 100.0), 100); + }; + /// 장기전세 + case LONG_TERM_JEONSE -> incomeByFamily(familyCount, + Map.of(1, 110.0, 2, 115.0, 3, 120.0), 130.0); + + /// 영구임대 + case PERMANENT_RENTAL -> incomeByFamily(familyCount, + Map.of(1, 60.0, 2, 65.0, 3, 70.0), 80.0); + + /// 장기전세 + + default -> incomeByFamily(familyCount, + Map.of(1, 140.0, 2, 145.0, 3, 150.0), 160.0); + }; + } + + /** + * familyCount별 고정 표가 있으면 그 값, 없으면 유연하게 fallback + */ + private double incomeByFamily(int familyCount, Map table, double defaultValue) { + if (table.containsKey(familyCount)) { + return table.get(familyCount); + } + // 유연 처리: 표에 없는 경우 → 가장 큰 key 초과면 defaultValue, 그 외엔 가장 가까운 값 선택 + int maxKey = table.keySet().stream().mapToInt(i -> i).max().orElse(0); + int minKey = table.keySet().stream().mapToInt(i -> i).min().orElse(0); + + if (familyCount > maxKey) { + return defaultValue; // 초과 시 fallback + } else if (familyCount < minKey) { + return table.get(minKey); // 최소값보다 작으면 최소값으로 + } else { + // 중간 값인데 정의 안 된 경우 → 가장 가까운 key 찾기 + return table.entrySet().stream() + .min((e1, e2) -> + Integer.compare( + Math.abs(e1.getKey() - familyCount), + Math.abs(e2.getKey() - familyCount))) + .map(Map.Entry::getValue) + .orElse(defaultValue); + } + } + + + /** + * 최대 금융자산 제한, 3억 3천 7백만원 + */ + @Override + public long maxTotalAsset(SupplyType supply, RentalType rental, int familyCount) { + return switch (rental) { + case HAPPY_HOUSING -> switch (supply) { + // 대학생, 청년, 고령자는 1억 400만 이하 + case STUDENT_SPECIAL -> 104_000_000L; + + // 맞벌이 청년은 2억 5천 4백까지 가능 + case YOUTH_SPECIAL -> 254_000_000L; + + // 기본은 3억 3천 7백 + default -> 337_000_000L; + }; + // HAPPY_HOUSING 이 아닌 경우는 기본값 + default -> 337_000_000L; + }; + } + + + /** + * 자동차 자산 제한, 대학생은 소유 자체 불가능 + */ + @Override + public long checkMaxCarValue() { + return 38300000L; + } + + + @Override + public int youthAgeMin() { + return 19; + } + + @Override + public int newlyMarriedMaxYears() { + return 7; + } + + @Override + public int marriedYouthAgeMin() { + return 20; + } + + @Override + public int elderAge() { + return 65; + } + + @Override + public int reApplyBanMonths(RentalType rental) { + return 24; + } +} diff --git a/src/main/java/com/pinHouse/server/platform/housingFit/rule/application/service/RuleService.java b/src/main/java/com/pinHouse/server/platform/housingFit/rule/application/service/RuleService.java new file mode 100644 index 0000000..7788f3e --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/housingFit/rule/application/service/RuleService.java @@ -0,0 +1,44 @@ +package com.pinHouse.server.platform.housingFit.rule.application.service; + +import com.pinHouse.server.platform.housingFit.diagnosis.domain.entity.Diagnosis; +import com.pinHouse.server.platform.housingFit.rule.application.dto.response.RuleResult; +import com.pinHouse.server.platform.housingFit.rule.application.usecase.RuleChainUseCase; +import com.pinHouse.server.platform.housingFit.rule.domain.entity.EvaluationContext; +import com.pinHouse.server.platform.housingFit.rule.domain.rule.Rule; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * 룰체인 실행 + */ +@Slf4j +@RequiredArgsConstructor +@Component +public class RuleService implements RuleChainUseCase { + + /// 실행할 룰 목록 조회 + private final List rules; + + /** + * 진단 요청 내용을 바탕으로 룰 실행 + * @param diagnosis 진단 엔티티 + */ + public EvaluationContext evaluateAll(Diagnosis diagnosis) { + + /// EvaluationContext 생성 + EvaluationContext context = EvaluationContext.of(diagnosis); + + /// 반복해서 룰 실행시키기 + for (Rule rule : rules) { + + /// 진행한 룰 결과 더하기 + rule.evaluate(context); + } + + return context; + } + +} diff --git a/src/main/java/com/pinHouse/server/platform/housingFit/rule/application/usecase/PolicyUseCase.java b/src/main/java/com/pinHouse/server/platform/housingFit/rule/application/usecase/PolicyUseCase.java new file mode 100644 index 0000000..00f78be --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/housingFit/rule/application/usecase/PolicyUseCase.java @@ -0,0 +1,33 @@ +package com.pinHouse.server.platform.housingFit.rule.application.usecase; + +import com.pinHouse.server.platform.housingFit.rule.domain.entity.RentalType; +import com.pinHouse.server.platform.housingFit.rule.domain.entity.SupplyType; + +public interface PolicyUseCase { + + + /// 소득 비율 + double maxIncomeRatio(SupplyType supply, RentalType rental, int familyCount); + + /// 전체 재산 관련 부분 + long maxTotalAsset(SupplyType supply, RentalType rental, int familyCount); + + /// 자동차 재산 관련 부분 + long checkMaxCarValue(); + + /// 자녀 나이 관련 + int youthAgeMin(); + + /// 신혼부부 관련 + int newlyMarriedMaxYears(); + + /// 미성년자 관련 + int marriedYouthAgeMin(); + + /// 고령자 관련 + int elderAge(); + + /// 무주택 기간 + int reApplyBanMonths(RentalType rental); +} + diff --git a/src/main/java/com/pinHouse/server/platform/housingFit/rule/application/usecase/RuleChainUseCase.java b/src/main/java/com/pinHouse/server/platform/housingFit/rule/application/usecase/RuleChainUseCase.java new file mode 100644 index 0000000..af193e3 --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/housingFit/rule/application/usecase/RuleChainUseCase.java @@ -0,0 +1,11 @@ +package com.pinHouse.server.platform.housingFit.rule.application.usecase; + +import com.pinHouse.server.platform.housingFit.diagnosis.domain.entity.Diagnosis; +import com.pinHouse.server.platform.housingFit.rule.domain.entity.EvaluationContext; + +public interface RuleChainUseCase { + + /// 도메인 기반으로 룰 진행하기 + EvaluationContext evaluateAll(Diagnosis diagnosis); + +} diff --git a/src/main/java/com/pinHouse/server/platform/housingFit/rule/domain/entity/EvaluationContext.java b/src/main/java/com/pinHouse/server/platform/housingFit/rule/domain/entity/EvaluationContext.java new file mode 100644 index 0000000..e52b529 --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/housingFit/rule/domain/entity/EvaluationContext.java @@ -0,0 +1,65 @@ +package com.pinHouse.server.platform.housingFit.rule.domain.entity; + +import com.pinHouse.server.platform.housingFit.diagnosis.domain.entity.Diagnosis; +import com.pinHouse.server.platform.housingFit.rule.application.dto.response.RuleResult; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +import java.util.ArrayList; +import java.util.List; + + +/** + * 진단을 위한 평가 배경 + */ +@Getter +@AllArgsConstructor +@Builder +public class EvaluationContext { + + /// 진단 엔티티 + private final Diagnosis diagnosis; + + /// 저장할 결과 목록 + private final List ruleResults; + + /// 진단에서 가능한 후보군 목록 + private List currentCandidates; + + + /// 정적 팩토리 메서드 + public static EvaluationContext of(Diagnosis diagnosis) { + return EvaluationContext.builder() + .diagnosis(diagnosis) + .ruleResults(new ArrayList<>()) + /// 모든 공급유형을 추가해두고 삭제하는 방식으로 구현 + .currentCandidates(SupplyRentalCandidate.basic()) + .build(); + } + +// /** +// * 하나의 Rule을 진행할 때마다, RuleResult 값 더하기 +// * @param result 룰 결과 +// */ +// public void addResult(RuleResult result) { +// +// /// 최종결과에 하나의 Rule에 대한 결과 추가 +// ruleResults.add(result); +// +// /// Rule을 통과하지 못했다면, 공급유형에서 제거하도록 설정 +// if (!result.pass()) { +// SupplyType candidate = (SupplyType) result.details().get("candidate"); +// currentCandidates.remove(candidate); +// } +// } + + + /** + * 함수 + * @param candidates + */ + public void setCurrentCandidates(List candidates) { + this.currentCandidates = candidates; + } +} diff --git a/src/main/java/com/pinHouse/server/platform/housingFit/rule/domain/entity/RentalSupplyMapping.java b/src/main/java/com/pinHouse/server/platform/housingFit/rule/domain/entity/RentalSupplyMapping.java new file mode 100644 index 0000000..d923c74 --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/housingFit/rule/domain/entity/RentalSupplyMapping.java @@ -0,0 +1,70 @@ +package com.pinHouse.server.platform.housingFit.rule.domain.entity; + +import java.util.List; +import java.util.Map; + +public class RentalSupplyMapping { + + public static final Map> RENTAL_SUPPLY_MAP = Map.of( + RentalType.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.DEMOLITION, + SupplyType.NATIONAL_MERIT, + SupplyType.LONG_SERVICE_VETERAN, + SupplyType.NORTH_DEFECTOR, + SupplyType.DISABLED, + SupplyType.NON_HOUSING_RESIDENT + ), + RentalType.NATIONAL_RENTAL, List.of( + SupplyType.SPECIAL, // 미성년자 + SupplyType.NEWCOUPLE_SPECIAL, + SupplyType.SINGLE_PARENT_SPECIAL, + SupplyType.GENERAL, + SupplyType.DEMOLITION, + SupplyType.UNAUTHORIZED_BUILDING_TENANT + ), + RentalType.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 + ), + RentalType.PUBLIC_RENTAL, List.of( + SupplyType.GENERAL, + SupplyType.MULTICHILD_SPECIAL, + SupplyType.ELDER_SUPPORT_SPECIAL, + SupplyType.NEWCOUPLE_SPECIAL, + SupplyType.FIRST_SPECIAL, // 생애최초 + SupplyType.MINOR_SPECIAL, // 신생아 + SupplyType.YOUTH_SPECIAL, // 청년 + SupplyType.NATIONAL_MERIT + ), + RentalType.PERMANENT_RENTAL, List.of( + SupplyType.NEWCOUPLE_SPECIAL, + SupplyType.SINGLE_PARENT_SPECIAL, + SupplyType.ELDER_SUPPORT_SPECIAL, + SupplyType.ELDER_SPECIAL, + SupplyType.NATIONAL_MERIT, + SupplyType.NORTH_DEFECTOR, + SupplyType.COMFORT_WOMAN_VICTIM, + SupplyType.DISABLED + ), + RentalType.LONG_TERM_JEONSE, List.of( + SupplyType.NEWCOUPLE_SPECIAL, + SupplyType.SINGLE_PARENT_SPECIAL, + SupplyType.MULTICHILD_SPECIAL + ) + ); + +} diff --git a/src/main/java/com/pinHouse/server/platform/housingFit/rule/domain/entity/RentalType.java b/src/main/java/com/pinHouse/server/platform/housingFit/rule/domain/entity/RentalType.java new file mode 100644 index 0000000..481fc6d --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/housingFit/rule/domain/entity/RentalType.java @@ -0,0 +1,34 @@ +package com.pinHouse.server.platform.housingFit.rule.domain.entity; + +import com.fasterxml.jackson.annotation.JsonGetter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; +import java.util.List; + +@RequiredArgsConstructor +public enum RentalType { + + PUBLIC_INTEGRATED("통합공공임대"), + NATIONAL_RENTAL("국민임대"), + HAPPY_HOUSING("행복주택"), + PUBLIC_RENTAL("공공임대"), + PERMANENT_RENTAL("영구임대"), + LONG_TERM_JEONSE("장기전세"), + PURCHASE_RENTAL("매입임대"), + JEONSE_RENTAL("전세임대"); + + private final String value; + + @JsonGetter + public String getValue() { + return value; + } + + /** + * 모든 SupplyType enum 반환 + */ + public static List getAllTypes() { + return Arrays.asList(RentalType.values()); + } +} diff --git a/src/main/java/com/pinHouse/server/platform/housingFit/rule/domain/entity/SupplyRentalCandidate.java b/src/main/java/com/pinHouse/server/platform/housingFit/rule/domain/entity/SupplyRentalCandidate.java new file mode 100644 index 0000000..c123d1e --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/housingFit/rule/domain/entity/SupplyRentalCandidate.java @@ -0,0 +1,58 @@ +package com.pinHouse.server.platform.housingFit.rule.domain.entity; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +import java.util.ArrayList; +import java.util.List; + +/** + * 진단을 위해서 공급/주택유형을 1:1 매핑으로 걸어두는 것 + */ + +@Builder +public record SupplyRentalCandidate( + SupplyType supplyType, + RentalType rentalType +) { + + /// 정적 팩토리 메서드 (기본 값) + public static List basic() { + + /// 모든 값 가져오기 + List supplyTypes = SupplyType.getAllTypes(); + + /// 모든 값 가져오기 + List rentalTypes = RentalType.getAllTypes(); + + List candidates = new ArrayList<>(); + + /// for문 돌리면서 값 합치기 + for (RentalType rental : RentalType.getAllTypes()) { + + /// 매핑 되는 Supply 가져오기 + List allowedSupplies = RentalSupplyMapping.RENTAL_SUPPLY_MAP.get(rental); + + /// 매핑 없는 경우 무시 + if (allowedSupplies == null) continue; + for (SupplyType supply : allowedSupplies) { + + /// 더하기 + candidates.add(SupplyRentalCandidate.basic(supply, rental)); + } + } + + return candidates; + } + + /// 정적 팩토리 메서드 + public static SupplyRentalCandidate basic(SupplyType supplyType, RentalType rentalType) { + return SupplyRentalCandidate.builder() + .supplyType(supplyType) + .rentalType(rentalType) + .build(); + } + + +} diff --git a/src/main/java/com/pinHouse/server/platform/housingFit/rule/domain/entity/SupplyType.java b/src/main/java/com/pinHouse/server/platform/housingFit/rule/domain/entity/SupplyType.java new file mode 100644 index 0000000..b70c181 --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/housingFit/rule/domain/entity/SupplyType.java @@ -0,0 +1,59 @@ +package com.pinHouse.server.platform.housingFit.rule.domain.entity; + +import com.fasterxml.jackson.annotation.JsonGetter; +import lombok.RequiredArgsConstructor; +import java.util.*; + +@RequiredArgsConstructor +public enum SupplyType { + + GENERAL("일반 공급"), + YOUTH_SPECIAL("청년 특별공급"), + STUDENT_SPECIAL("대학생 특별공급"), + NEWCOUPLE_SPECIAL("신혼부부 특별공급"), + SINGLE_PARENT_SPECIAL("한부모 특별공급"), + ELDER_SPECIAL("고령자 특별공급"), + MULTICHILD_SPECIAL("다자녀 특별공급"), + MINOR_SPECIAL("신생아 특별공급"), + FIRST_SPECIAL("생애최초 특별공급"), + ELDER_SUPPORT_SPECIAL("고령자 부양 특별공급"), + SPECIAL("미성년자 특별계층 공급"), + + + /// 특별계층 항목들 + DEMOLITION("철거민 특별공급"), + NATIONAL_MERIT("국가유공자 특별공급"), + LONG_SERVICE_VETERAN("장기복무 제대군인 특별공급"), + NORTH_DEFECTOR("북한이탈주민 특별공급"), + COMFORT_WOMAN_VICTIM("위안부 피해자 특별공급"), + DISABLED("장애인 특별공급"), + NON_HOUSING_RESIDENT("비주택거주자 특별공급"), + PERMANENT_LEASE_EVICTEE("영구임대 퇴거자 특별공급"), + TEMPORARY_STRUCTURE_RESIDENT("비닐 간이공작물 거주자 특별공급"), + UNAUTHORIZED_BUILDING_TENANT("무허가 건축물 세입자 특별공급"); + + private final String value; + + @JsonGetter + public String getValue() { + return value; + } + + + /** + * 모든 SupplyType enum 반환 + */ + public static List getAllTypes() { + return Arrays.asList(SupplyType.values()); + } + + /** + * 모든 특별계층 SupplyType enum 반환 + */ + public static List getSpecialTypes() { + return Arrays.asList(DEMOLITION, NATIONAL_MERIT, LONG_SERVICE_VETERAN, + NORTH_DEFECTOR, COMFORT_WOMAN_VICTIM, DISABLED, + NON_HOUSING_RESIDENT, PERMANENT_LEASE_EVICTEE, TEMPORARY_STRUCTURE_RESIDENT, + UNAUTHORIZED_BUILDING_TENANT); + } +} diff --git a/src/main/java/com/pinHouse/server/platform/housingFit/rule/domain/rule/AccountRule.java b/src/main/java/com/pinHouse/server/platform/housingFit/rule/domain/rule/AccountRule.java new file mode 100644 index 0000000..73f1716 --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/housingFit/rule/domain/rule/AccountRule.java @@ -0,0 +1,110 @@ +package com.pinHouse.server.platform.housingFit.rule.domain.rule; + +import com.pinHouse.server.platform.housingFit.diagnosis.domain.entity.Diagnosis; +import com.pinHouse.server.platform.housingFit.diagnosis.domain.entity.SubscriptionAccount; +import com.pinHouse.server.platform.housingFit.diagnosis.domain.entity.SubscriptionCount; +import com.pinHouse.server.platform.housingFit.diagnosis.domain.entity.SubscriptionPeriod; +import com.pinHouse.server.platform.housingFit.rule.domain.entity.EvaluationContext; +import com.pinHouse.server.platform.housingFit.rule.application.dto.response.RuleResult; +import com.pinHouse.server.platform.housingFit.rule.domain.entity.RentalType; +import com.pinHouse.server.platform.housingFit.rule.domain.entity.SupplyRentalCandidate; +import lombok.RequiredArgsConstructor; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static com.pinHouse.server.platform.housingFit.rule.domain.entity.RentalType.*; + +/** 3) 청약통장 요건(가입기간/예치금/상품유형) */ +@Order(2) +@Component +@RequiredArgsConstructor +public class AccountRule implements Rule { + + /// 청약통장이 없으면 불가능한 목록들 + private static final List NO_ACCOUNT_ALLOWED = List.of( + NATIONAL_RENTAL, + PUBLIC_INTEGRATED, + LONG_TERM_JEONSE, + PUBLIC_RENTAL + ); + + + @Override + public RuleResult evaluate(EvaluationContext ctx) { + + /// 진단 도메인 정보 가져오기 + Diagnosis diagnosis = ctx.getDiagnosis(); + + /// 가능한 리스트 추출하기 + var candidates = new ArrayList<>(ctx.getCurrentCandidates()); + + /// 규칙 돌기 + if (!diagnosis.isHasAccount()) { + + /// 청약통장 없는 경우, NO_ACCOUNT_ALLOWED에 해당하는 RentalType 제거 + candidates.removeIf(c -> NO_ACCOUNT_ALLOWED.contains(c.rentalType())); + + return RuleResult.pass(code(), + "청약통장 미보유로 인한 유형 제거 완료", + Map.of("candidate", candidates)); + } + + //TODO! 추후 가입기간에 따른 가산점으로 변경 + /// 가입 기간 체크 + SubscriptionPeriod accountYears = diagnosis.getAccountYears(); + double years = accountYears.getYears(); // 실제 연도 값 + + /// 공공임대 내부에서, 가입기간에 따른 불가능 제거 + removePUBLIC_RENTAL(candidates, years, diagnosis); + + String message = String.format("청약통장 가입기간 %.1f년, 가입기간이 길수록 우선순위가 높습니다.", years); + + ///후보군은 그대로 유지 + ctx.setCurrentCandidates(candidates); + + // 조건 통과 + return RuleResult.pass(code(), + message, + Map.of("accountYears", diagnosis.getAccountYears())); + } + + private static void removePUBLIC_RENTAL(ArrayList candidates, double years, Diagnosis diagnosis) { + + for (SupplyRentalCandidate c : new ArrayList<>(candidates)) { // 반복 중 수정 방지 + if (c.rentalType() != PUBLIC_RENTAL) continue; + + switch (c.supplyType()) { + case FIRST_SPECIAL -> { + // 생애최초: 납입금액 600만원 미만이면 삭제 + if (diagnosis.getAccount() == SubscriptionAccount.UNDER_600) { + candidates.remove(c); + } + } + + case YOUTH_SPECIAL, MINOR_SPECIAL, MULTICHILD_SPECIAL, NEWCOUPLE_SPECIAL -> { + // 가입 6개월 이상 + 6회 이상 납부 조건 체크 + boolean failPeriod = years <= 0.5; + boolean failDepositCount = diagnosis.getAccountDeposit() == SubscriptionCount.FROM_0_TO_5; + + if (failPeriod || failDepositCount) { + candidates.remove(c); + } + } + + default -> { + // 그 외는 삭제하지 않음 + } + } + } + } + + @Override public String code() { + return "ACCOUNT_REQUIREMENT"; + } + + +} diff --git a/src/main/java/com/pinHouse/server/platform/housingFit/rule/domain/rule/BaseEligibilityRule.java b/src/main/java/com/pinHouse/server/platform/housingFit/rule/domain/rule/BaseEligibilityRule.java new file mode 100644 index 0000000..ad71d8f --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/housingFit/rule/domain/rule/BaseEligibilityRule.java @@ -0,0 +1,88 @@ +package com.pinHouse.server.platform.housingFit.rule.domain.rule; + +import com.pinHouse.server.platform.housingFit.diagnosis.domain.entity.Diagnosis; +import com.pinHouse.server.platform.housingFit.rule.application.usecase.PolicyUseCase; +import com.pinHouse.server.platform.housingFit.rule.domain.entity.EvaluationContext; +import com.pinHouse.server.platform.housingFit.rule.application.dto.response.RuleResult; +import com.pinHouse.server.platform.housingFit.rule.domain.entity.SupplyRentalCandidate; +import com.pinHouse.server.platform.housingFit.rule.domain.entity.SupplyType; +import lombok.RequiredArgsConstructor; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import java.util.Map; +import java.util.List; + +/** 1) 기초 자격: 나이 + 세대주 + 무주택 */ +@Order(1) +@Component +@RequiredArgsConstructor +public class BaseEligibilityRule implements Rule { + + /// 임대주택 유형 검증기 도입 + private final PolicyUseCase policyUseCase; + + @Override + public RuleResult evaluate(EvaluationContext ctx) { + + Diagnosis diagnosis = ctx.getDiagnosis(); + + /// 1차, 나이에 따른 추천 유형 매핑 + int age = diagnosis.getAge(); + + /// 가능한 리스트 추출하기 + List candidates = ctx.getCurrentCandidates(); + + /// 나이별 후보군 필터링 + if (age < 19) { + candidates.removeIf(c -> + c.supplyType() == SupplyType.NEWCOUPLE_SPECIAL || + c.supplyType() == SupplyType.ELDER_SPECIAL || + c.supplyType() == SupplyType.GENERAL + ); + } else if (age <= 39) { + candidates.removeIf(c -> c.supplyType() == SupplyType.ELDER_SPECIAL); + } else if (age <= 64) { + candidates.removeIf(c -> + c.supplyType() == SupplyType.YOUTH_SPECIAL || + c.supplyType() == SupplyType.ELDER_SPECIAL + ); + } else if (age >= 65) { + candidates.removeIf(c -> + c.supplyType() == SupplyType.YOUTH_SPECIAL || + c.supplyType() == SupplyType.NEWCOUPLE_SPECIAL + ); + } + + /// 2차, 주택 소유 여부 + boolean hasHousehold = diagnosis.isHouseholdHead(); + if (hasHousehold) { + return RuleResult.fail( + code(), + "주택 소유는 임대주택 지원이 불가능", + Map.of("household", hasHousehold) + ); + } + + /// 결과 저장하기 + ctx.setCurrentCandidates(candidates); + + return RuleResult.pass( + code(), + "나이별 추천 타입 후보 완료", + Map.of( + "age", age, + "candidates", candidates + ) + ); + + + } + + /** + * 코드 저장 + */ + @Override public String code() { + return "BASE_ELIGIBILITY"; + } +} diff --git a/src/main/java/com/pinHouse/server/platform/housingFit/rule/domain/rule/ElderCandidateRule.java b/src/main/java/com/pinHouse/server/platform/housingFit/rule/domain/rule/ElderCandidateRule.java new file mode 100644 index 0000000..06a2093 --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/housingFit/rule/domain/rule/ElderCandidateRule.java @@ -0,0 +1,58 @@ +package com.pinHouse.server.platform.housingFit.rule.domain.rule; + +import com.pinHouse.server.platform.housingFit.diagnosis.domain.entity.Diagnosis; +import com.pinHouse.server.platform.housingFit.rule.application.usecase.PolicyUseCase; +import com.pinHouse.server.platform.housingFit.rule.domain.entity.EvaluationContext; +import com.pinHouse.server.platform.housingFit.rule.application.dto.response.RuleResult; +import com.pinHouse.server.platform.housingFit.rule.domain.entity.SupplyType; +import lombok.RequiredArgsConstructor; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.Map; + +/** 9) 고령자 제한 */ +@Order(3) +@Component +@RequiredArgsConstructor +public class ElderCandidateRule implements Rule { + + /// 임대주택 유형 검증기 도입 + private final PolicyUseCase policyUseCase; + + @Override + public RuleResult evaluate(EvaluationContext ctx) { + + Diagnosis diagnosis = ctx.getDiagnosis(); + + /// 가능한 리스트 추출하기 + var candidates = new ArrayList<>(ctx.getCurrentCandidates()); + + /// 나이가 고령자 제한이 안된다면 삭제 + if (diagnosis.getAge() < policyUseCase.elderAge()) { + + /// 만약 있다면 삭제 + candidates.removeIf(c -> + c.supplyType() == SupplyType.ELDER_SUPPORT_SPECIAL); + + /// 결과 저장하기 + ctx.setCurrentCandidates(candidates); + + return RuleResult.fail(code(), + "고령자 해당 없음", + Map.of("candidate", candidates)); + } + + /// 나이가 고령자이기에 그대로 후보로 지정 + return RuleResult.pass(code(), + "고령자 특별공급 후보", + Map.of("candidate", candidates)); + + } + + @Override public String code() { + return "CANDIDATE_ELDER_SPECIAL"; + } + +} diff --git a/src/main/java/com/pinHouse/server/platform/housingFit/rule/domain/rule/ElderSupportCandidateRule.java b/src/main/java/com/pinHouse/server/platform/housingFit/rule/domain/rule/ElderSupportCandidateRule.java new file mode 100644 index 0000000..adac38f --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/housingFit/rule/domain/rule/ElderSupportCandidateRule.java @@ -0,0 +1,61 @@ +package com.pinHouse.server.platform.housingFit.rule.domain.rule; + +import com.pinHouse.server.platform.housingFit.diagnosis.domain.entity.Diagnosis; +import com.pinHouse.server.platform.housingFit.rule.application.usecase.PolicyUseCase; +import com.pinHouse.server.platform.housingFit.rule.domain.entity.EvaluationContext; +import com.pinHouse.server.platform.housingFit.rule.application.dto.response.RuleResult; +import com.pinHouse.server.platform.housingFit.rule.domain.entity.SupplyType; +import lombok.RequiredArgsConstructor; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.Map; + +import static com.pinHouse.server.platform.housingFit.diagnosis.domain.entity.SpecialCategory.SUPPORTING_ELDERLY; + + +/** 8) 노부모 부양 지원 */ +@Order(4) +@Component +@RequiredArgsConstructor +public class ElderSupportCandidateRule implements Rule { + + /// 임대주택 유형 검증기 도입 + private final PolicyUseCase policyUseCase; + + @Override + public RuleResult evaluate(EvaluationContext ctx) { + Diagnosis diagnosis = ctx.getDiagnosis(); + + /// 가능한 리스트 추출하기 + var candidates = new ArrayList<>(ctx.getCurrentCandidates()); + + /// 부양한 노부모가 없다면 제거 + if (!diagnosis.getHasSpecialCategory().contains(SUPPORTING_ELDERLY)) { + + /// 만약 있다면 삭제 + candidates.removeIf(c -> + c.supplyType() == SupplyType.YOUTH_SPECIAL || + c.supplyType() == SupplyType.ELDER_SPECIAL); + + /// 결과 저장하기 + ctx.setCurrentCandidates(candidates); + + /// 조건이 없으므로 넘어간다. + return RuleResult.fail(code(), + "노부모 부양 특별공급 해당 없음", + Map.of("candidate", candidates)); + } + + /// 조건 충족으로 넘어간다. + return RuleResult.pass(code(), + "노부모 부양 특별공급 후보", + Map.of("candidate", candidates)); + } + + @Override public String code() { + return "CANDIDATE_ELDER_SUPPORT_SPECIAL"; + } + +} diff --git a/src/main/java/com/pinHouse/server/platform/housingFit/rule/domain/rule/FirstSpecialRule.java b/src/main/java/com/pinHouse/server/platform/housingFit/rule/domain/rule/FirstSpecialRule.java new file mode 100644 index 0000000..37e3e64 --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/housingFit/rule/domain/rule/FirstSpecialRule.java @@ -0,0 +1,65 @@ +package com.pinHouse.server.platform.housingFit.rule.domain.rule; + + +import com.pinHouse.server.platform.housingFit.diagnosis.domain.entity.Diagnosis; +import com.pinHouse.server.platform.housingFit.diagnosis.domain.entity.HousingOwnershipStatus; +import com.pinHouse.server.platform.housingFit.rule.application.dto.response.RuleResult; +import com.pinHouse.server.platform.housingFit.rule.application.usecase.PolicyUseCase; +import com.pinHouse.server.platform.housingFit.rule.domain.entity.EvaluationContext; +import com.pinHouse.server.platform.housingFit.rule.domain.entity.SupplyType; +import lombok.RequiredArgsConstructor; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.Map; + +/** + * 생애 최초 특별 공급 + */ + +@Order(5) +@Component +@RequiredArgsConstructor +public class FirstSpecialRule implements Rule { + + /// 임대주택 유형 검증기 도입 + private final PolicyUseCase policyUseCase; + + @Override + public RuleResult evaluate(EvaluationContext ctx) { + + Diagnosis diagnosis = ctx.getDiagnosis(); + + /// 가능한 리스트 추출하기 + var candidates = new ArrayList<>(ctx.getCurrentCandidates()); + + /// 무주택자 여부 + boolean noOwnHome = diagnosis.getHousingStatus().equals(HousingOwnershipStatus.NO_ONE_OWNS_HOUSE); + + if (!noOwnHome) { + + /// 만약 있다면 삭제 + candidates.removeIf(c -> + c.supplyType() == SupplyType.FIRST_SPECIAL); + + /// 결과 저장하기 + ctx.setCurrentCandidates(candidates); + + return RuleResult.fail(code(), + "무주택자 해당 없음", + Map.of("candidate", candidates)); + } + + return RuleResult.fail(code(), + "생애최초 특별공급 후보", + Map.of("candidate", candidates)); + + } + + + @Override + public String code() { + return "FIRST_SPECIAL"; + } +} diff --git a/src/main/java/com/pinHouse/server/platform/housingFit/rule/domain/rule/IncomeAssetRule.java b/src/main/java/com/pinHouse/server/platform/housingFit/rule/domain/rule/IncomeAssetRule.java new file mode 100644 index 0000000..d264260 --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/housingFit/rule/domain/rule/IncomeAssetRule.java @@ -0,0 +1,80 @@ +package com.pinHouse.server.platform.housingFit.rule.domain.rule; + +import com.pinHouse.server.platform.housingFit.diagnosis.domain.entity.Diagnosis; +import com.pinHouse.server.platform.housingFit.diagnosis.domain.entity.IncomeLevel; +import com.pinHouse.server.platform.housingFit.rule.application.usecase.PolicyUseCase; +import com.pinHouse.server.platform.housingFit.rule.domain.entity.EvaluationContext; +import com.pinHouse.server.platform.housingFit.rule.application.dto.response.RuleResult; +import lombok.RequiredArgsConstructor; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.Map; + +/** 10) 가능한 후보에서 소득/자산 한번에 체크 총량 요건 */ +@Order(15) +@Component +@RequiredArgsConstructor +public class IncomeAssetRule implements Rule { + + /// 임대주택 유형 검증기 도입 + private final PolicyUseCase policyUseCase; + + @Override + public RuleResult evaluate(EvaluationContext ctx) { + + /// 진단 정보 가져오기 + Diagnosis diagnosis = ctx.getDiagnosis(); + + /// 현재 후보 리스트 복사 + var candidates = new ArrayList<>(ctx.getCurrentCandidates()); + + /// 자동차 자산 금액 + long carValue = diagnosis.getCarValue(); + + /// 소득 수준 퍼센트 + IncomeLevel incomeLevel = diagnosis.getIncomeLevel(); + + /// 총 자산 금액 + long totalAsset = diagnosis.getTotalAsset(); + + /// 가족 수 + int familyCount = diagnosis.getFamilyCount(); + + /// Iterator로 후보 순회하며 소득/자산 요건 체크 + var iter = candidates.iterator(); + while (iter.hasNext()) { + var candidate = iter.next(); + + /// 자동차 금액 체크 + boolean carOk = carValue <= policyUseCase.checkMaxCarValue(); + + /// 소득 비율 체크 + boolean incomeOk = incomeLevel.getPercent() <= + policyUseCase.maxIncomeRatio(candidate.supplyType(), candidate.rentalType(), familyCount); + + /// 총 자산 체크 + boolean assetOk = totalAsset <= + policyUseCase.maxTotalAsset(candidate.supplyType(), candidate.rentalType(), familyCount); + + /// 요건 불충족 시 후보 제거 + if (!carOk || !incomeOk || !assetOk) { + iter.remove(); + } + } + + /// 최신 후보 리스트 반영 + ctx.setCurrentCandidates(candidates); + + /// 결과 리턴 + return RuleResult.pass(code(), + "소득/자산 요건 충족 후보", + Map.of("candidate", candidates)); + } + + @Override + public String code() { + return "INCOME_ASSET_LIMIT"; + } +} diff --git a/src/main/java/com/pinHouse/server/platform/housingFit/rule/domain/rule/MinorRule.java b/src/main/java/com/pinHouse/server/platform/housingFit/rule/domain/rule/MinorRule.java new file mode 100644 index 0000000..cfa7e92 --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/housingFit/rule/domain/rule/MinorRule.java @@ -0,0 +1,64 @@ +package com.pinHouse.server.platform.housingFit.rule.domain.rule; + +import com.pinHouse.server.platform.housingFit.diagnosis.domain.entity.Diagnosis; +import com.pinHouse.server.platform.housingFit.rule.application.dto.response.RuleResult; +import com.pinHouse.server.platform.housingFit.rule.application.usecase.PolicyUseCase; +import com.pinHouse.server.platform.housingFit.rule.domain.entity.EvaluationContext; +import com.pinHouse.server.platform.housingFit.rule.domain.entity.SupplyType; +import lombok.RequiredArgsConstructor; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.Map; + +@Order(6) +@Component +@RequiredArgsConstructor +public class MinorRule implements Rule { + + /// 임대주택 유형 검증기 도입 + private final PolicyUseCase policyUseCase; + + @Override + public RuleResult evaluate(EvaluationContext ctx) { + + /// 진단 정보 가져오기 + Diagnosis diagnosis = ctx.getDiagnosis(); + + /// 현재 후보 리스트 복사 + var candidates = new ArrayList<>(ctx.getCurrentCandidates()); + + /// 결혼 여부 & 신생아 여부 + boolean minorOk = diagnosis.getUnbornChildrenCount() >= 1; + + /// 신생아가 1명이상 없다면, 제거한다. + if (!minorOk) { + + /// 삭제 + candidates.removeIf(c -> + c.supplyType() == SupplyType.MINOR_SPECIAL); + + /// 결과 저장하기 + ctx.setCurrentCandidates(candidates); + + return RuleResult.fail(code(), + "신생아 특별공급 해당 없음", + Map.of( + "candidate", candidates, + "failReason", "신생아의 자녀가 없음" + )); + } + + return RuleResult.pass(code(), + "신생아 특별공급 후보", + Map.of( + "candidate", candidates)); + } + + + @Override + public String code() { + return "MINOR_SPECIAL"; + } +} diff --git a/src/main/java/com/pinHouse/server/platform/housingFit/rule/domain/rule/MultiChildCandidateRule.java b/src/main/java/com/pinHouse/server/platform/housingFit/rule/domain/rule/MultiChildCandidateRule.java new file mode 100644 index 0000000..6cc96fc --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/housingFit/rule/domain/rule/MultiChildCandidateRule.java @@ -0,0 +1,59 @@ +package com.pinHouse.server.platform.housingFit.rule.domain.rule; + +import com.pinHouse.server.platform.housingFit.diagnosis.domain.entity.Diagnosis; +import com.pinHouse.server.platform.housingFit.rule.application.usecase.PolicyUseCase; +import com.pinHouse.server.platform.housingFit.rule.domain.entity.EvaluationContext; +import com.pinHouse.server.platform.housingFit.rule.application.dto.response.RuleResult; +import com.pinHouse.server.platform.housingFit.rule.domain.entity.SupplyType; +import lombok.RequiredArgsConstructor; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.Map; + +/** 7) 다자녀 요건 */ + +@Order(7) +@Component +@RequiredArgsConstructor +public class MultiChildCandidateRule implements Rule { + + /// 임대주택 유형 검증기 도입 + private final PolicyUseCase policyUseCase; + + @Override + public RuleResult evaluate(EvaluationContext ctx) { + + Diagnosis diagnosis = ctx.getDiagnosis(); + + /// 가능한 리스트 추출하기 + var candidates = new ArrayList<>(ctx.getCurrentCandidates()); + + /// 자녀가 3명 이상이 아니라면 후보 제공 + if (!diagnosis.checkMultiple()) { + + /// 만약 있다면 삭제 + candidates.removeIf(c -> + c.supplyType() == SupplyType.MULTICHILD_SPECIAL); + + /// 결과 저장하기 + ctx.setCurrentCandidates(candidates); + + return RuleResult.fail(code(), + "다자녀 특별공급 해당 없음", + Map.of( + "candidate", candidates, + "failReason", "자녀가 3명 미만" + )); + } + + return RuleResult.pass(code(), + "다자녀 특별공급 후보", + Map.of("candidate", candidates)); + } + + @Override public String code() { + return "CANDIDATE_MULTICHILD_SPECIAL"; + } +} diff --git a/src/main/java/com/pinHouse/server/platform/housingFit/rule/domain/rule/NewlyMarriedCandidateRule.java b/src/main/java/com/pinHouse/server/platform/housingFit/rule/domain/rule/NewlyMarriedCandidateRule.java new file mode 100644 index 0000000..8c75957 --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/housingFit/rule/domain/rule/NewlyMarriedCandidateRule.java @@ -0,0 +1,85 @@ +package com.pinHouse.server.platform.housingFit.rule.domain.rule; + +import com.pinHouse.server.platform.housingFit.diagnosis.domain.entity.Diagnosis; +import com.pinHouse.server.platform.housingFit.rule.application.usecase.PolicyUseCase; +import com.pinHouse.server.platform.housingFit.rule.domain.entity.EvaluationContext; +import com.pinHouse.server.platform.housingFit.rule.application.dto.response.RuleResult; +import com.pinHouse.server.platform.housingFit.rule.domain.entity.SupplyType; +import lombok.RequiredArgsConstructor; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.Map; + +/** 6) 신혼 부부 요건 */ + +@Order(8) +@Component +@RequiredArgsConstructor +public class NewlyMarriedCandidateRule implements Rule { + + /// 임대주택 유형 검증기 도입 + private final PolicyUseCase policyUseCase; + + @Override + public RuleResult evaluate(EvaluationContext ctx) { + + Diagnosis diagnosis = ctx.getDiagnosis(); + + /// 가능한 리스트 추출하기 + var candidates = new ArrayList<>(ctx.getCurrentCandidates()); + + /// 결혼했는지 체크 + boolean isMarried = diagnosis.isMaritalStatus(); + + /// 7년 이하의 결혼 체크 + int marriedMaxYears = policyUseCase.newlyMarriedMaxYears(); + boolean withinYears = diagnosis.getMarriageYears() != null && diagnosis.getMarriageYears() <= marriedMaxYears; + + /// 자녀가 6세 이하라면 true + boolean withYouthAge = diagnosis.getUnbornChildrenCount() > 0 || diagnosis.getUnder6ChildrenCount() > 0; + + /// 신혼부부 특별공급 요건이 안된다면, + /// - 결혼했고 7년 이하, 또는 + /// - 결혼 7년 초과지만 자녀가 6세 이하인 경우 + + if (!(isMarried && (withinYears || withYouthAge))) { + + /// 있다면 삭제 + candidates.removeIf(c -> + c.supplyType() == SupplyType.NEWCOUPLE_SPECIAL); + + /// 결과 저장하기 + ctx.setCurrentCandidates(candidates); + + /// 실패 이유를 담아줌 + String failReason; + if (!isMarried) { + failReason = "결혼하지 않음"; + } else if (!withinYears && !withYouthAge) { + failReason = "결혼 7년 초과 및 자녀 요건 미충족"; + } else if (!withinYears) { + failReason = "결혼 7년 초과"; + } else { + failReason = "자녀 요건 미충족"; + } + + return RuleResult.fail(code(), + "신혼부부 조건 해당 없음", + Map.of( + "candidate", candidates, + "failReason", failReason + )); + } + + + return RuleResult.pass(code(), + "신혼부부 해당 조건 충족", + Map.of("candidate", candidates)); + } + + @Override public String code() { + return "CANDIDATE_NEWCOUPLE_SPECIAL"; + } +} diff --git a/src/main/java/com/pinHouse/server/platform/housingFit/rule/domain/rule/Rule.java b/src/main/java/com/pinHouse/server/platform/housingFit/rule/domain/rule/Rule.java new file mode 100644 index 0000000..dd2076f --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/housingFit/rule/domain/rule/Rule.java @@ -0,0 +1,12 @@ +package com.pinHouse.server.platform.housingFit.rule.domain.rule; + +import com.pinHouse.server.platform.housingFit.rule.domain.entity.EvaluationContext; +import com.pinHouse.server.platform.housingFit.rule.application.dto.response.RuleResult; + +public interface Rule { + + RuleResult evaluate(EvaluationContext ctx); + + String code(); +} + diff --git a/src/main/java/com/pinHouse/server/platform/housingFit/rule/domain/rule/SingleParentRule.java b/src/main/java/com/pinHouse/server/platform/housingFit/rule/domain/rule/SingleParentRule.java new file mode 100644 index 0000000..6004595 --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/housingFit/rule/domain/rule/SingleParentRule.java @@ -0,0 +1,70 @@ +package com.pinHouse.server.platform.housingFit.rule.domain.rule; + +import com.pinHouse.server.platform.housingFit.diagnosis.domain.entity.Diagnosis; +import com.pinHouse.server.platform.housingFit.diagnosis.domain.entity.SpecialCategory; +import com.pinHouse.server.platform.housingFit.rule.application.dto.response.RuleResult; +import com.pinHouse.server.platform.housingFit.rule.application.usecase.PolicyUseCase; +import com.pinHouse.server.platform.housingFit.rule.domain.entity.EvaluationContext; +import com.pinHouse.server.platform.housingFit.rule.domain.entity.SupplyType; +import lombok.RequiredArgsConstructor; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static com.pinHouse.server.platform.housingFit.diagnosis.domain.entity.SpecialCategory.PROTECTED_SINGLE_PARENT; +import static com.pinHouse.server.platform.housingFit.diagnosis.domain.entity.SpecialCategory.SINGLE_PARENT; + +/** + * 한부모 특별 공급 조사 + */ + +@Order(9) +@Component +@RequiredArgsConstructor +public class SingleParentRule implements Rule { + + /// 임대주택 유형 검증기 도입 + private final PolicyUseCase policyUseCase; + + @Override + public RuleResult evaluate(EvaluationContext ctx) { + + Diagnosis diagnosis = ctx.getDiagnosis(); + + /// 가능한 리스트 추출하기 + var candidates = new ArrayList<>(ctx.getCurrentCandidates()); + + /// 한부모 가정 여부 + List specialCategories = diagnosis.getHasSpecialCategory(); + + /// 한부모인지 + boolean hasSingleParent = specialCategories.stream() + .anyMatch(c -> c == SINGLE_PARENT || c == PROTECTED_SINGLE_PARENT); + + if (!hasSingleParent) { + + /// 한부모 계층이 아니라면 삭제 + candidates.removeIf(c -> + c.supplyType() == SupplyType.SINGLE_PARENT_SPECIAL); + + /// 결과 저장하기 + ctx.setCurrentCandidates(candidates); + + return RuleResult.fail(code(), + "한부모 특별공급 해당 없음", + Map.of("candidate", candidates)); + } + + return RuleResult.pass(code(), + "한부모 특별공급 후보", + Map.of("candidate", candidates)); + } + + @Override + public String code() { + return "SINGLE_PARENT_SPECIAL"; + } +} diff --git a/src/main/java/com/pinHouse/server/platform/housingFit/rule/domain/rule/SpecialRule.java b/src/main/java/com/pinHouse/server/platform/housingFit/rule/domain/rule/SpecialRule.java new file mode 100644 index 0000000..3a1165b --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/housingFit/rule/domain/rule/SpecialRule.java @@ -0,0 +1,81 @@ +package com.pinHouse.server.platform.housingFit.rule.domain.rule; + +import com.pinHouse.server.platform.housingFit.diagnosis.domain.entity.Diagnosis; +import com.pinHouse.server.platform.housingFit.diagnosis.domain.entity.SpecialCategory; +import com.pinHouse.server.platform.housingFit.rule.application.dto.response.RuleResult; +import com.pinHouse.server.platform.housingFit.rule.application.usecase.PolicyUseCase; +import com.pinHouse.server.platform.housingFit.rule.domain.entity.EvaluationContext; +import com.pinHouse.server.platform.housingFit.rule.domain.entity.SupplyType; +import lombok.RequiredArgsConstructor; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +@Order(10) +@Component +@RequiredArgsConstructor +public class SpecialRule implements Rule{ + + /// 임대주택 로직 + private final PolicyUseCase policyUseCase; + + @Override + public RuleResult evaluate(EvaluationContext ctx) { + + Diagnosis diagnosis = ctx.getDiagnosis(); + + /// 가능한 리스트 추출하기 + var candidates = new ArrayList<>(ctx.getCurrentCandidates()); + + /// 가지고있는 특수계층 여부 + List specialCategory = diagnosis.getHasSpecialCategory(); + + /// 모든 특수계층 남겨두기 + List specialTypes = SupplyType.getSpecialTypes(); + + /// 특수계층을 하나라도 가지고 있다면, + if (!specialCategory.isEmpty()) { + + /// 사용자가 가진 특수계층을 SupplyType으로 매핑 + List ownedTypes = specialCategory.stream() + .map(SpecialCategory::toSupplyType) + .toList(); + + /// 존재하는 계층은 냅두고, 없는 것은 삭제하기 + candidates.removeIf(c -> + specialTypes.contains(c.supplyType()) + && !ownedTypes.contains(c.supplyType())); + + ctx.setCurrentCandidates(candidates); + + return RuleResult.pass(code(), + "특수계층 공급 후보", + Map.of("candidate", candidates)); + + } + + /// 특별계층이 하나도 없다면 삭제 + else { + + /// 모든 특별계층이 있는 것 삭제 + specialTypes.forEach(supplyType -> { + candidates.removeIf(c -> + c.supplyType() == supplyType); + + }); + ctx.setCurrentCandidates(candidates); + + return RuleResult.fail(code(), + "특수계층 공급 후보 없음", + Map.of("candidate", candidates)); + } + } + + @Override + public String code() { + return ""; + } +} diff --git a/src/main/java/com/pinHouse/server/platform/housingFit/rule/domain/rule/StudentRule.java b/src/main/java/com/pinHouse/server/platform/housingFit/rule/domain/rule/StudentRule.java new file mode 100644 index 0000000..32f0c6e --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/housingFit/rule/domain/rule/StudentRule.java @@ -0,0 +1,70 @@ +package com.pinHouse.server.platform.housingFit.rule.domain.rule; + +import com.pinHouse.server.platform.housingFit.diagnosis.domain.entity.Diagnosis; +import com.pinHouse.server.platform.housingFit.diagnosis.domain.entity.EducationStatus; +import com.pinHouse.server.platform.housingFit.rule.application.dto.response.RuleResult; +import com.pinHouse.server.platform.housingFit.rule.application.usecase.PolicyUseCase; +import com.pinHouse.server.platform.housingFit.rule.domain.entity.EvaluationContext; +import com.pinHouse.server.platform.housingFit.rule.domain.entity.SupplyType; +import lombok.RequiredArgsConstructor; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.Map; + +@Order(11) +@Component +@RequiredArgsConstructor +public class StudentRule implements Rule { + + private final PolicyUseCase policyUseCase; + + @Override + public RuleResult evaluate(EvaluationContext ctx) { + + Diagnosis diagnosis = ctx.getDiagnosis(); + var candidates = new ArrayList<>(ctx.getCurrentCandidates()); + + boolean isStudent = !diagnosis.getEducationStatus().equals(EducationStatus.NONE); + boolean isMarried = diagnosis.isMaritalStatus(); + boolean hasCar = diagnosis.getCarValue() > 0 || diagnosis.isHasCar(); + + // 기본적으로 실패 이유를 수집 + var failReasons = new ArrayList(); + + if (!isStudent) { + failReasons.add("학생이 아님"); + } + if (isMarried) { + failReasons.add("기혼 상태"); + } + if (hasCar) { + failReasons.add("차량 보유"); + } + + // 조건 불충족 -> 후보에서 제거 + if (!failReasons.isEmpty()) { + candidates.removeIf(c -> c.supplyType() == SupplyType.STUDENT_SPECIAL); + ctx.setCurrentCandidates(candidates); + + return RuleResult.fail(code(), + "대학생 특별공급 해당 없음", + Map.of( + "candidate", candidates, + "failReason", failReasons + )); + } + + // 모든 조건 만족 + return RuleResult.pass(code(), + "대학생 특별공급 후보", + Map.of("candidate", candidates)); + } + + @Override + public String code() { + return "CANDIDATE_STUDENT_SPECIAL"; + } +} + diff --git a/src/main/java/com/pinHouse/server/platform/housingFit/rule/domain/rule/YouthSpecialCandidateRule.java b/src/main/java/com/pinHouse/server/platform/housingFit/rule/domain/rule/YouthSpecialCandidateRule.java new file mode 100644 index 0000000..ad49992 --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/housingFit/rule/domain/rule/YouthSpecialCandidateRule.java @@ -0,0 +1,77 @@ +package com.pinHouse.server.platform.housingFit.rule.domain.rule; + +import com.pinHouse.server.platform.housingFit.diagnosis.domain.entity.Diagnosis; +import com.pinHouse.server.platform.housingFit.rule.application.usecase.PolicyUseCase; +import com.pinHouse.server.platform.housingFit.rule.domain.entity.EvaluationContext; +import com.pinHouse.server.platform.housingFit.rule.application.dto.response.RuleResult; +import com.pinHouse.server.platform.housingFit.rule.domain.entity.SupplyType; +import lombok.RequiredArgsConstructor; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.Map; + +/** 5) 미성년자 후보 탐색 규칙 */ +@Order(12) +@Component +@RequiredArgsConstructor +public class YouthSpecialCandidateRule implements Rule { + + /// 임대주택 유형 검증기 도입 + private final PolicyUseCase policyUseCase; + + @Override + public RuleResult evaluate(EvaluationContext ctx) { + + Diagnosis diagnosis = ctx.getDiagnosis(); + + /// 가능한 리스트 추출하기 + var candidates = new ArrayList<>(ctx.getCurrentCandidates()); + + /// 미성년자인지 체크 + int ageMin = policyUseCase.marriedYouthAgeMin(); + boolean ageOk = diagnosis.getAge() < ageMin; + + /// 결혼했는지 여부 확인 + boolean isMarried = diagnosis.isMaritalStatus(); + + /// 결혼한 미성년자가 아니라면, 신청불가능 + if (!ageOk || !isMarried) { + + /// 있다면 삭제 + candidates.removeIf(c -> + c.supplyType() == SupplyType.SPECIAL); + + /// 결과 저장하기 + ctx.setCurrentCandidates(candidates); + + + // 실패 이유 분류 + String failReason; + if (!ageOk && !isMarried) { + failReason = "나이 기준 초과 및 미혼"; + } else if (!ageOk) { + failReason = "나이 기준 초과"; + } else { + failReason = "미혼"; + } + + return RuleResult.fail(code(), + "미성년자 특별공급 해당 없음", + Map.of( + "candidate", candidates, + "failReason", failReason + )); + } + + /// 미성년자 특별공급 후보 + return RuleResult.pass(code(), + "미성년자 특별공급 후보", + Map.of("candidate", candidates)); + } + + @Override public String code() { + return "CANDIDATE_YOUTH_SPECIAL"; + } +} diff --git a/src/main/java/com/pinHouse/server/platform/diagnosis/rule/application/dto/request/.gitkeep b/src/main/java/com/pinHouse/server/platform/housingFit/rule/presentation/swagger/.gitkeep similarity index 100% rename from src/main/java/com/pinHouse/server/platform/diagnosis/rule/application/dto/request/.gitkeep rename to src/main/java/com/pinHouse/server/platform/housingFit/rule/presentation/swagger/.gitkeep diff --git a/src/main/java/com/pinHouse/server/platform/housingFit/school/application/dto/request/SchoolRequest.java b/src/main/java/com/pinHouse/server/platform/housingFit/school/application/dto/request/SchoolRequest.java new file mode 100644 index 0000000..a6e6be7 --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/housingFit/school/application/dto/request/SchoolRequest.java @@ -0,0 +1,4 @@ +package com.pinHouse.server.platform.housingFit.school.application.dto.request; + +public class SchoolRequest { +} diff --git a/src/main/java/com/pinHouse/server/platform/housingFit/school/application/dto/response/SchoolResponse.java b/src/main/java/com/pinHouse/server/platform/housingFit/school/application/dto/response/SchoolResponse.java new file mode 100644 index 0000000..d3eae71 --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/housingFit/school/application/dto/response/SchoolResponse.java @@ -0,0 +1,4 @@ +package com.pinHouse.server.platform.housingFit.school.application.dto.response; + +public record SchoolResponse() { +} diff --git a/src/main/java/com/pinHouse/server/platform/housingFit/school/application/service/SchoolService.java b/src/main/java/com/pinHouse/server/platform/housingFit/school/application/service/SchoolService.java new file mode 100644 index 0000000..d705bfb --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/housingFit/school/application/service/SchoolService.java @@ -0,0 +1,4 @@ +package com.pinHouse.server.platform.housingFit.school.application.service; + +public class SchoolService { +} diff --git a/src/main/java/com/pinHouse/server/platform/housingFit/school/application/usecase/SchoolUseCase.java b/src/main/java/com/pinHouse/server/platform/housingFit/school/application/usecase/SchoolUseCase.java new file mode 100644 index 0000000..5ce0456 --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/housingFit/school/application/usecase/SchoolUseCase.java @@ -0,0 +1,7 @@ +package com.pinHouse.server.platform.housingFit.school.application.usecase; + +public interface SchoolUseCase { + + /// 대학교 검색 로직 + +} diff --git a/src/main/java/com/pinHouse/server/platform/housingFit/school/domain/entity/School.java b/src/main/java/com/pinHouse/server/platform/housingFit/school/domain/entity/School.java new file mode 100644 index 0000000..70bae59 --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/housingFit/school/domain/entity/School.java @@ -0,0 +1,31 @@ +package com.pinHouse.server.platform.housingFit.school.domain.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Table(name = "school") +@Entity +@AllArgsConstructor +@NoArgsConstructor +@Builder +@Getter +public class School { + + @Id + private String id; + + private String schoolName; + + private String schoolType; + + private String schoolCategory; + + private String sidoCode; + + private String sidoName; + + private String sigunguName; +} diff --git a/src/main/java/com/pinHouse/server/platform/housingFit/school/domain/entity/University.java b/src/main/java/com/pinHouse/server/platform/housingFit/school/domain/entity/University.java new file mode 100644 index 0000000..d75469b --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/housingFit/school/domain/entity/University.java @@ -0,0 +1,31 @@ +package com.pinHouse.server.platform.housingFit.school.domain.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.NoArgsConstructor; + +@Table(name = "university") +@Entity +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class University { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String schoolName; + + private String campusType; + + private String collegeType; + + private Integer sidoCode; + + private String sidoName; + + private String sigunguName; + +} diff --git a/src/main/java/com/pinHouse/server/platform/housingFit/school/domain/repository/SchoolJpaRepository.java b/src/main/java/com/pinHouse/server/platform/housingFit/school/domain/repository/SchoolJpaRepository.java new file mode 100644 index 0000000..acc870b --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/housingFit/school/domain/repository/SchoolJpaRepository.java @@ -0,0 +1,7 @@ +package com.pinHouse.server.platform.housingFit.school.domain.repository; + +import com.pinHouse.server.platform.housingFit.school.domain.entity.School; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface SchoolJpaRepository extends JpaRepository { +} diff --git a/src/main/java/com/pinHouse/server/platform/housingFit/school/domain/repository/UniversityJpaRepository.java b/src/main/java/com/pinHouse/server/platform/housingFit/school/domain/repository/UniversityJpaRepository.java new file mode 100644 index 0000000..cc80197 --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/housingFit/school/domain/repository/UniversityJpaRepository.java @@ -0,0 +1,7 @@ +package com.pinHouse.server.platform.housingFit.school.domain.repository; + +import com.pinHouse.server.platform.housingFit.school.domain.entity.University; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface UniversityJpaRepository extends JpaRepository { +} diff --git a/src/main/java/com/pinHouse/server/platform/housingFit/school/presentation/SchoolApi.java b/src/main/java/com/pinHouse/server/platform/housingFit/school/presentation/SchoolApi.java new file mode 100644 index 0000000..d1907c4 --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/housingFit/school/presentation/SchoolApi.java @@ -0,0 +1,6 @@ +package com.pinHouse.server.platform.housingFit.school.presentation; + +import com.pinHouse.server.platform.housingFit.school.presentation.swagger.SchoolApiSpec; + +public class SchoolApi implements SchoolApiSpec { +} diff --git a/src/main/java/com/pinHouse/server/platform/housingFit/school/presentation/swagger/SchoolApiSpec.java b/src/main/java/com/pinHouse/server/platform/housingFit/school/presentation/swagger/SchoolApiSpec.java new file mode 100644 index 0000000..e3f8d6f --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/housingFit/school/presentation/swagger/SchoolApiSpec.java @@ -0,0 +1,4 @@ +package com.pinHouse.server.platform.housingFit.school.presentation.swagger; + +public interface SchoolApiSpec { +} diff --git a/src/main/java/com/pinHouse/server/core/entity/Location.java b/src/main/java/com/pinHouse/server/platform/region/domain/entity/Location.java similarity index 89% rename from src/main/java/com/pinHouse/server/core/entity/Location.java rename to src/main/java/com/pinHouse/server/platform/region/domain/entity/Location.java index c458ea0..ca85cf5 100644 --- a/src/main/java/com/pinHouse/server/core/entity/Location.java +++ b/src/main/java/com/pinHouse/server/platform/region/domain/entity/Location.java @@ -1,4 +1,4 @@ -package com.pinHouse.server.core.entity; +package com.pinHouse.server.platform.region.domain.entity; import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.AllArgsConstructor; diff --git a/src/main/java/com/pinHouse/server/platform/region/domain/entity/Region.java b/src/main/java/com/pinHouse/server/platform/region/domain/entity/Region.java new file mode 100644 index 0000000..2087ad3 --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/region/domain/entity/Region.java @@ -0,0 +1,27 @@ +package com.pinHouse.server.platform.region.domain.entity; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class Region { + + // 코드 + private String code; + + // 법정동명 + private String address; + + public static Region of(String code, String address) { + return Region.builder() + .code(code) + .address(address) + .build(); + } + +} diff --git a/src/main/java/com/pinHouse/server/platform/region/domain/entity/RegionCode.java b/src/main/java/com/pinHouse/server/platform/region/domain/entity/RegionCode.java new file mode 100644 index 0000000..9dd4c3a --- /dev/null +++ b/src/main/java/com/pinHouse/server/platform/region/domain/entity/RegionCode.java @@ -0,0 +1,30 @@ +package com.pinHouse.server.platform.region.domain.entity; + +public enum RegionCode { + SEOUL, INCHEON, GYEONGGI, BUSAN, + DAEGU, GWANGJU, DAEJEON, ULSAN, + NON_SUDO; // 기타 지역 + + public static RegionCode from(String s) { + switch (s.toUpperCase()) { + case "SEOUL": return SEOUL; + case "INCHEON": return INCHEON; + case "GYEONGGI": return GYEONGGI; + case "BUSAN": return BUSAN; + case "DAEGU": return DAEGU; + case "GWANGJU": return GWANGJU; + case "DAEJEON": return DAEJEON; + case "ULSAN": return ULSAN; + default: return NON_SUDO; + } + } + + public boolean isSudo() { + return this == SEOUL || this == INCHEON || this == GYEONGGI || this == BUSAN; + } + + public boolean isMetro() { + return this == DAEGU || this == GWANGJU || this == DAEJEON || this == ULSAN; + } +} + diff --git a/src/main/java/com/pinHouse/server/platform/user/domain/entity/Gender.java b/src/main/java/com/pinHouse/server/platform/user/domain/entity/Gender.java index 32d9c86..ce7b71d 100644 --- a/src/main/java/com/pinHouse/server/platform/user/domain/entity/Gender.java +++ b/src/main/java/com/pinHouse/server/platform/user/domain/entity/Gender.java @@ -10,10 +10,7 @@ public enum Gender { Male("M"), @JsonProperty("여성") - Female("F"), - - @JsonProperty("기타") - Other("U"); + Female("F"); private final String value; diff --git a/src/main/java/com/pinHouse/server/platform/user/domain/entity/User.java b/src/main/java/com/pinHouse/server/platform/user/domain/entity/User.java index 7998b75..234de44 100644 --- a/src/main/java/com/pinHouse/server/platform/user/domain/entity/User.java +++ b/src/main/java/com/pinHouse/server/platform/user/domain/entity/User.java @@ -1,7 +1,7 @@ package com.pinHouse.server.platform.user.domain.entity; import com.pinHouse.server.core.util.BirthDayUtil; -import com.pinHouse.server.core.entity.BaseTimeEntity; +import com.pinHouse.server.platform.BaseTimeEntity; import com.pinHouse.server.security.oauth2.domain.OAuth2UserInfo; import jakarta.persistence.*; import lombok.*; diff --git a/src/main/java/com/pinHouse/server/security/config/RequestMatcherHolder.java b/src/main/java/com/pinHouse/server/security/config/RequestMatcherHolder.java index c1806a3..ef1f8cd 100644 --- a/src/main/java/com/pinHouse/server/security/config/RequestMatcherHolder.java +++ b/src/main/java/com/pinHouse/server/security/config/RequestMatcherHolder.java @@ -34,6 +34,9 @@ public class RequestMatcherHolder { // 상품 관련 new RequestInfo(GET, "/api/v1/notices/**", null), + // 진단 관련 + new RequestInfo(POST, "/api/v1/diagnosis/**", Role.USER), + // static resources new RequestInfo(GET, "/docs/**", null), new RequestInfo(GET, "/*.ico", null),