Initial commit

This commit is contained in:
jooho
2021-10-20 17:12:00 +09:00
parent 0c884beff8
commit 8caa4bbc5a
487 changed files with 44198 additions and 0 deletions

View File

@@ -0,0 +1,40 @@
package org.egovframe.cloud.reserveitemservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.ComponentScan;
import reactivefeign.spring.config.EnableReactiveFeignClients;
import reactor.blockhound.BlockHound;
import java.security.Security;
@ComponentScan({"org.egovframe.cloud.common", "org.egovframe.cloud.reactive", "org.egovframe.cloud.reserveitemservice"}) // org.egovframe.cloud.common package 포함하기 위해
@EnableDiscoveryClient
@EnableReactiveFeignClients
@SpringBootApplication
public class ReserveItemServiceApplication {
public static void main(String[] args) {
// TLSv1/v1.1 No longer works after upgrade, "No appropriate protocol" error
String property = Security.getProperty("jdk.tls.disabledAlgorithms").replace(", TLSv1", "").replace(", TLSv1.1", "");
Security.setProperty("jdk.tls.disabledAlgorithms", property);
//blocking 코드 감지
BlockHound.builder()
/**
* mysql r2dbc 에서 호출되는 FileInputStream.readBytes() 가 블로킹코드인데 이를 허용해주도록 한다.
* 해당 코드가 어디서 호출되는지 알지 못하는 상태에서 FileInputStream.readBytes() 자체를 허용해주는 것은 좋지 않다.
* 누군가 무분별하게 사용하게 되면 검출해 낼 수 없어 시스템의 위험요소로 남게 된다.
* r2dbc를 사용하기 위해 FileInputStream.readBytes()를 호출하는 부분만 허용하고 나머지는 여전히 검출대상으로 남기도록 한다.
*/
.allowBlockingCallsInside("dev.miku.r2dbc.mysql.client.ReactorNettyClient", "init")
.install();
SpringApplication.run(ReserveItemServiceApplication.class, args);
}
}

View File

@@ -0,0 +1,135 @@
package org.egovframe.cloud.reserveitemservice.api.location;
import io.swagger.v3.oas.annotations.Operation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.egovframe.cloud.common.dto.RequestDto;
import org.egovframe.cloud.reserveitemservice.api.location.dto.LocationResponseDto;
import org.egovframe.cloud.reserveitemservice.api.location.dto.LocationSaveRequestDto;
import org.egovframe.cloud.reserveitemservice.api.location.dto.LocationUpdateRequestDto;
import org.egovframe.cloud.reserveitemservice.service.location.LocationService;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import javax.validation.Valid;
/**
* org.egovframe.cloud.reserveitemservice.api.location.LocationApiController
* <p>
* 예약 지역 api contoller class
*
* @author 표준프레임워크센터 shinmj
* @version 1.0
* @since 2021/09/06
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/06 shinmj 최초 생성
* </pre>
*/
@Slf4j
@RequiredArgsConstructor
@RestController
public class LocationApiController {
private final LocationService locationService;
/**
* 목록 조회
*
* @param requestDto
* @param page
* @param size
* @return
*/
@GetMapping("/api/v1/locations")
@ResponseStatus(HttpStatus.OK)
@Operation(summary = "page of location")
public Mono<Page<LocationResponseDto>> search(RequestDto requestDto,
@RequestParam(name = "page") int page,
@RequestParam(name = "size") int size) {
return locationService.search(requestDto, PageRequest.of(page, size));
}
/**
* 한건 조회
*
* @param locationId
* @return
*/
@GetMapping("/api/v1/locations/{locationId}")
@ResponseStatus(HttpStatus.OK)
public Mono<LocationResponseDto> findById(@PathVariable Long locationId) {
return locationService.findById(locationId);
}
/**
* 지역 목록 조회 (사용여부 = true)
* 예약 목록 등록 시
*
* @return
*/
@GetMapping("/api/v1/locations/combo")
@ResponseStatus(HttpStatus.OK)
public Flux<LocationResponseDto> findAll() {
return locationService.findAll();
}
/**
* 지역 한건 저장
*
* @param saveRequestDto
* @return
*/
@PostMapping("/api/v1/locations")
@ResponseStatus(HttpStatus.CREATED)
public Mono<LocationResponseDto> save(@Valid @RequestBody LocationSaveRequestDto saveRequestDto) {
return locationService.save(saveRequestDto);
}
/**
* 지역 한건 수정
*
* @param locationId
* @param updateRequestDto
* @return
*/
@PutMapping("/api/v1/locations/{locationId}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public Mono<Void> update(@PathVariable Long locationId, @Valid @RequestBody LocationUpdateRequestDto updateRequestDto) {
return locationService.update(locationId, updateRequestDto);
}
/**
* 지역 한건 삭제
*
* @param locationId
* @return
*/
@DeleteMapping("/api/v1/locations/{locationId}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public Mono<Void> delete(@PathVariable Long locationId) {
return locationService.delete(locationId);
}
/**
* 지역 사용여부 toggle
*
* @param locationId
* @param isUse
* @return
*/
@PutMapping("/api/v1/locations/{locationId}/{isUse}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public Mono<Void> updateIsUse(@PathVariable Long locationId, @PathVariable Boolean isUse) {
return locationService.updateIsUse(locationId, isUse);
}
}

View File

@@ -0,0 +1,43 @@
package org.egovframe.cloud.reserveitemservice.api.location.dto;
import lombok.*;
import org.egovframe.cloud.reserveitemservice.domain.location.Location;
import java.time.LocalDateTime;
/**
* org.egovframe.cloud.reserveitemservice.api.location.dto.LocationResponseDto
* <p>
* 예약 지역 응답 dto class
*
* @author 표준프레임워크센터 shinmj
* @version 1.0
* @since 2021/09/06
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/06 shinmj 최초 생성
* </pre>
*/
@Getter
@NoArgsConstructor
@ToString
public class LocationResponseDto {
private Long locationId;
private String locationName;
private Integer sortSeq;
private Boolean isUse;
private LocalDateTime createDate;
@Builder
public LocationResponseDto(Location entity) {
this.locationId = entity.getLocationId();
this.locationName = entity.getLocationName();
this.sortSeq = entity.getSortSeq();
this.isUse = entity.getIsUse();
this.createDate = entity.getCreateDate();
}
}

View File

@@ -0,0 +1,56 @@
package org.egovframe.cloud.reserveitemservice.api.location.dto;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.egovframe.cloud.reserveitemservice.domain.location.Location;
import javax.validation.constraints.NotNull;
/**
* org.egovframe.cloud.reserveitemservice.api.location.dto.LocationSaveRequestDto
* <p>
* 예약 지역 저장 요청 dto class
*
* @author 표준프레임워크센터 shinmj
* @version 1.0
* @since 2021/09/06
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/06 shinmj 최초 생성
* </pre>
*/
@Getter
@NoArgsConstructor
@ToString
public class LocationSaveRequestDto {
@NotNull
private String locationName;
private Integer sortSeq;
private Boolean isUse;
@Builder
public LocationSaveRequestDto(String locationName, Integer sortSeq, Boolean isUse) {
this.locationName = locationName;
this.sortSeq = sortSeq;
this.isUse = isUse;
}
/**
* dto -> entity
*
* @return
*/
public Location toEntity() {
return Location.builder()
.locationName(this.locationName)
.sortSeq(this.sortSeq)
.isUse(this.isUse)
.build();
}
}

View File

@@ -0,0 +1,55 @@
package org.egovframe.cloud.reserveitemservice.api.location.dto;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.egovframe.cloud.reserveitemservice.domain.location.Location;
import javax.validation.constraints.NotNull;
/**
* org.egovframe.cloud.reserveitemservice.api.location.dto.LocationUpdateRequestDto
* <p>
* 예약 지역 수정 요청 dto class
*
* @author 표준프레임워크센터 shinmj
* @version 1.0
* @since 2021/09/08
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/08 shinmj 최초 생성
* </pre>
*/
@Getter
@NoArgsConstructor
@ToString
public class LocationUpdateRequestDto {
@NotNull
private String locationName;
private Integer sortSeq;
private Boolean isUse;
@Builder
public LocationUpdateRequestDto(String locationName, Integer sortSeq, Boolean isUse) {
this.locationName = locationName;
this.sortSeq = sortSeq;
this.isUse = isUse;
}
/**
* dto -> entity
*
* @return
*/
public Location toEntity() {
return Location.builder()
.locationName(this.locationName)
.sortSeq(this.sortSeq)
.isUse(this.isUse)
.build();
}
}

View File

@@ -0,0 +1,180 @@
package org.egovframe.cloud.reserveitemservice.api.reserveItem;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import javax.validation.Valid;
import org.egovframe.cloud.reserveitemservice.api.reserveItem.dto.ReserveItemListResponseDto;
import org.egovframe.cloud.reserveitemservice.api.reserveItem.dto.ReserveItemMainResponseDto;
import org.egovframe.cloud.reserveitemservice.api.reserveItem.dto.ReserveItemRelationResponseDto;
import org.egovframe.cloud.reserveitemservice.api.reserveItem.dto.ReserveItemRequestDto;
import org.egovframe.cloud.reserveitemservice.api.reserveItem.dto.ReserveItemResponseDto;
import org.egovframe.cloud.reserveitemservice.api.reserveItem.dto.ReserveItemSaveRequestDto;
import org.egovframe.cloud.reserveitemservice.api.reserveItem.dto.ReserveItemUpdateRequestDto;
import org.egovframe.cloud.reserveitemservice.service.reserveItem.ReserveItemService;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* org.egovframe.cloud.reserveitemservice.api.reserveItem.ReserveItemApiController
* <p>
* 예약 물품 api controller class
*
* @author 표준프레임워크센터 shinmj
* @version 1.0
* @since 2021/09/13
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/13 shinmj 최초 생성
* </pre>
*/
@Slf4j
@RequiredArgsConstructor
@RestController
public class ReserveItemApiController {
private final ReserveItemService reserveItemService;
/**
* 목록 조회
*
* @param requestDto
* @param page
* @param size
* @return
*/
@GetMapping("/api/v1/reserve-items")
@ResponseStatus(HttpStatus.OK)
public Mono<Page<ReserveItemListResponseDto>> search(ReserveItemRequestDto requestDto,
@RequestParam(name = "page") int page,
@RequestParam(name = "size") int size) {
return reserveItemService.search(requestDto, PageRequest.of(page, size));
}
/**
* 목록 조회 - 사용자 조회 시
*
* @param requestDto
* @param page
* @param size
* @return
*/
@GetMapping("/api/v1/{categoryId}/reserve-items")
@ResponseStatus(HttpStatus.OK)
public Mono<Page<ReserveItemListResponseDto>> searchForUser(@PathVariable String categoryId,
ReserveItemRequestDto requestDto,
@RequestParam(name = "page") int page,
@RequestParam(name = "size") int size) {
return reserveItemService.searchForUser(categoryId, requestDto, PageRequest.of(page, size));
}
/**
* 한건 조회
*
* @param reserveItemId
* @return
*/
@GetMapping("/api/v1/reserve-items/{reserveItemId}")
@ResponseStatus(HttpStatus.OK)
public Mono<ReserveItemResponseDto> findById(@PathVariable Long reserveItemId) {
System.out.println("findById : " + reserveItemId);
return reserveItemService.findById(reserveItemId);
}
/**
* 한건 등록
*
* @param saveRequestDto
* @return
*/
@PostMapping("/api/v1/reserve-items")
@ResponseStatus(HttpStatus.CREATED)
public Mono<ReserveItemResponseDto> save(@Valid @RequestBody ReserveItemSaveRequestDto saveRequestDto) {
return reserveItemService.save(saveRequestDto);
}
/**
* 한건 수정
*
* @param reserveItemId
* @param updateRequestDto
* @return
*/
@PutMapping("/api/v1/reserve-items/{reserveItemId}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public Mono<Void> update(@PathVariable Long reserveItemId, @Valid @RequestBody ReserveItemUpdateRequestDto updateRequestDto) {
return reserveItemService.update(reserveItemId, updateRequestDto);
}
/**
* 사용여부 업데이트
*
* @param reserveItemId
* @param isUse
* @return
*/
@PutMapping("/api/v1/reserve-items/{reserveItemId}/{isUse}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public Mono<Void> updateIsUse(@PathVariable Long reserveItemId, @PathVariable Boolean isUse) {
return reserveItemService.updateIsUse(reserveItemId, isUse);
}
/**
* 한건 조회 시 연관관계(지역, 공통코드) 데이터까지 모두 조회
*
* @param reserveItemId
* @return
*/
@GetMapping("/api/v1/reserve-items/relations/{reserveItemId}")
@ResponseStatus(HttpStatus.OK)
public Mono<ReserveItemRelationResponseDto> findByIdWithRelations(@PathVariable Long reserveItemId) {
return reserveItemService.findByIdWithRelations(reserveItemId).log();
}
/**
* 관리자가 예약 신청 시 이벤트 스트림 없이 바로 재고 변경
*
* @param reserveItemId
* @param reserveQty
* @return
*/
@PutMapping("/api/v1/reserve-items/{reserveItemId}/inventories")
@ResponseStatus(HttpStatus.OK)
public Mono<Boolean> updateInventory(@PathVariable Long reserveItemId, @RequestBody Integer reserveQty) {
System.out.println("update inventories : " + reserveItemId+" : " + reserveQty);
return reserveItemService.updateInventory(reserveItemId, reserveQty);
}
/**
* 각 카테고리별 최신 예약 물품 조회
* 파라미터로 받는 갯수만큼 조회한다.
*
* @param count 조회할 갯수 0:전체
* @return
*/
@GetMapping("/api/v1/reserve-items/latest/{count}")
@ResponseStatus(HttpStatus.OK)
public Mono<Map<String, Collection<ReserveItemMainResponseDto>>> findLatest(@PathVariable Integer count) {
return reserveItemService.findLatest(count);
}
}

View File

@@ -0,0 +1,93 @@
package org.egovframe.cloud.reserveitemservice.api.reserveItem.dto;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.egovframe.cloud.reserveitemservice.domain.reserveItem.ReserveItem;
import org.springframework.util.NumberUtils;
import org.springframework.util.StringUtils;
import java.time.LocalDateTime;
/**
* org.egovframe.cloud.reserveitemservice.api.reserveItem.dto.ReserveItemListResponseDto
* <p>
* 예약 물품 목록 응답 dto class
*
* @author 표준프레임워크센터 shinmj
* @version 1.0
* @since 2021/09/13
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/13 shinmj 최초 생성
* </pre>
*/
@Getter
@NoArgsConstructor
@ToString
public class ReserveItemListResponseDto {
private Long reserveItemId; // 예약 물품 id
private String reserveItemName; //예약 물품 명
private Long locationId; //지역 id
private String locationName;
private String categoryId; //예약유형 - 공통코드 reserve-category
private String categoryName;
private Integer totalQty; //총 재고/수용인원 수
private Integer inventoryQty; //총 재고/수용인원 수
private Boolean isUse; //사용여부
private LocalDateTime createDate; //등록일
private Boolean isPossible; //예약 가능 여부
@Builder
public ReserveItemListResponseDto(ReserveItem reserveItem) {
this.reserveItemId = reserveItem.getReserveItemId();
this.reserveItemName = reserveItem.getReserveItemName();
this.locationId = reserveItem.getLocationId();
this.locationName = reserveItem.getLocation().getLocationName();
this.categoryId = reserveItem.getCategoryId();
this.categoryName = reserveItem.getCategoryName();
this.totalQty = reserveItem.getTotalQty();
this.inventoryQty = reserveItem.getInventoryQty();
this.isUse = reserveItem.getIsUse();
this.createDate = reserveItem.getCreateDate();
this.isPossible = isReservationPossible(reserveItem);
}
/**
* 예약 가능 여부 체크
*
* @param reserveItem
* @return
*/
private boolean isReservationPossible(ReserveItem reserveItem) {
LocalDateTime now = LocalDateTime.now();
if (!reserveItem.getIsUse()) {
return false;
}
if (reserveItem.getInventoryQty() <= 0) {
return false;
}
if (reserveItem.getIsPeriod()) {
if (reserveItem.getRequestStartDate().isBefore(now) && reserveItem.getRequestEndDate().isAfter(now)) {
return true;
}else {
return false;
}
} else {
if (reserveItem.getOperationStartDate().isBefore(now) && reserveItem.getOperationEndDate().isAfter(now)) {
return true;
}else {
return false;
}
}
}
}

View File

@@ -0,0 +1,73 @@
package org.egovframe.cloud.reserveitemservice.api.reserveItem.dto;
import java.time.LocalDateTime;
import org.egovframe.cloud.reserveitemservice.domain.reserveItem.ReserveItem;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
@Getter
@NoArgsConstructor
@ToString
public class ReserveItemMainResponseDto {
private Long reserveItemId; // 예약 물품 id
private String reserveItemName; //예약 물품 명
private String categoryId; //예약유형 - 공통코드 reserve-category
private String categoryName;
private LocalDateTime startDate; //운영 시작 일 or 예약 신청 시작일
private LocalDateTime endDate; //운영 종료 일 or 예약 신청 종료일
private Boolean isPossible;
@Builder
public ReserveItemMainResponseDto (ReserveItem entity) {
this.reserveItemId = entity.getReserveItemId();
this.reserveItemName = entity.getReserveItemName();
this.categoryId = entity.getCategoryId();
this.categoryName = entity.getCategoryName();
this.startDate = entity.getOperationStartDate();
this.endDate = entity.getOperationEndDate();
if (entity.getReserveMethodId().equals("internet")) {
if (entity.getReserveMeansId().equals("realtime")) {
this.startDate = entity.getRequestStartDate();
this.endDate = entity.getRequestEndDate();
}
}
this.isPossible = isReservationPossible(entity);
}
/**
* 예약 가능 여부 체크
*
* @param entity
* @return
*/
private boolean isReservationPossible(ReserveItem entity) {
LocalDateTime now = LocalDateTime.now();
if (!entity.getIsUse()) {
return false;
}
if (entity.getInventoryQty() <= 0) {
return false;
}
if (entity.getIsPeriod()) {
if (entity.getRequestStartDate().isBefore(now) && entity.getRequestEndDate().isAfter(now)) {
return true;
}else {
return false;
}
} else {
if (entity.getOperationStartDate().isBefore(now) && entity.getOperationEndDate().isAfter(now)) {
return true;
}else {
return false;
}
}
}
}

View File

@@ -0,0 +1,110 @@
package org.egovframe.cloud.reserveitemservice.api.reserveItem.dto;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.egovframe.cloud.reserveitemservice.domain.location.Location;
import org.egovframe.cloud.reserveitemservice.domain.reserveItem.ReserveItem;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* org.egovframe.cloud.reserveitemservice.api.reserveItem.dto.ReserveItemRelationResponseDto
* <p>
* 예약 물품 relation 응답 dto class
*
* @author 표준프레임워크센터 shinmj
* @version 1.0
* @since 2021/09/27
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/27 shinmj 최초 생성
* </pre>
*/
@NoArgsConstructor
@Getter
@ToString
public class ReserveItemRelationResponseDto {
private Long reserveItemId; // 예약 물품 id
private String reserveItemName; //예약 물품 명
private Long locationId;
private Location location;
private String categoryId; //예약유형 - 공통코드 reserve-category
private String categoryName;
private Integer totalQty; //총 재고/수용인원 수
private Integer inventoryQty; // 재고/수용인원 수
private LocalDateTime operationStartDate; //운영 시작 일
private LocalDateTime operationEndDate; //운영 종료 일
private String reserveMethodId; // 예약 방법 - 공통코드 reserve-method
private String reserveMethodName;
private String reserveMeansId; // 예약 구분 (인터넷 예약 시) - 공통코드 reserve-means
private String reserveMeansName;
private LocalDateTime requestStartDate; //예약 신청 시작 일시
private LocalDateTime requestEndDate; //예약 신청 종료 일시
private Boolean isPeriod; //기간 지정 가능 여부 - true: 지정 가능, false: 지정 불가
private Integer periodMaxCount; // 최대 예약 가능 일 수
private String externalUrl; //외부링크
private String selectionMeansId; //선별 방법 - 공통코드 reserve-selection
private String selectionMeansName;
private Boolean isPaid; // 유/무료 - false: 무료, true: 유료
private BigDecimal usageCost; //이용 요금
private Boolean isUse; //사용여부
private String purpose; //용도
private String address; //주소
private String targetId; //이용 대상 - 공통코드 reserve-target
private String targetName;
private String excluded; // 사용허가 제외대상
private String homepage; //홈페이지 주소
private String contact; //문의처
private String managerDept; //담당자 소속
private String managerName; //담당자 이름
private String managerContact; //담당자 연락처
@Builder
public ReserveItemRelationResponseDto(ReserveItem entity) {
this.reserveItemId = entity.getReserveItemId();
this.reserveItemName = entity.getReserveItemName();
this.locationId = entity.getLocationId();
this.location = entity.getLocation();
this.categoryId = entity.getCategoryId();
this.categoryName = entity.getCategoryName();
this.totalQty = entity.getTotalQty();
this.inventoryQty = entity.getInventoryQty();
this.operationStartDate = entity.getOperationStartDate();
this.operationEndDate = entity.getOperationEndDate();
this.reserveMethodId = entity.getReserveMethodId();
this.reserveMethodName = entity.getReserveMethodName();
this.reserveMeansId = entity.getReserveMeansId();
this.reserveMeansName = entity.getReserveMeansName();
this.requestStartDate = entity.getRequestStartDate();
this.requestEndDate = entity.getRequestEndDate();
this.isPeriod = entity.getIsPeriod();
this.periodMaxCount = entity.getPeriodMaxCount();
this.externalUrl = entity.getExternalUrl();
this.selectionMeansId = entity.getSelectionMeansId();
this.selectionMeansName = entity.getSelectionMeansName();
this.isPaid = entity.getIsPaid();
this.usageCost = entity.getUsageCost();
this.isUse = entity.getIsUse();
this.purpose = entity.getPurpose();
this.address = entity.getAddress();
this.targetId = entity.getTargetId();
this.targetName = entity.getTargetName();
this.excluded = entity.getExcluded();
this.homepage = entity.getHomepage();
this.contact = entity.getContact();
this.managerDept = entity.getManagerDept();
this.managerName = entity.getManagerName();
this.managerContact = entity.getManagerContact();
}
}

View File

@@ -0,0 +1,31 @@
package org.egovframe.cloud.reserveitemservice.api.reserveItem.dto;
import lombok.*;
import org.egovframe.cloud.common.dto.RequestDto;
/**
* org.egovframe.cloud.reserveitemservice.api.reserveItem.dto.ReserveItemRequestDto
* <p>
* 예약 목록 조회 요청 dto class
*
* @author 표준프레임워크센터 shinmj
* @version 1.0
* @since 2021/09/27
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/27 shinmj 최초 생성
* </pre>
*/
@NoArgsConstructor
@Getter
@Setter
@ToString
public class ReserveItemRequestDto extends RequestDto {
private Long locationId;
private String categoryId;
private Boolean isUse;
}

View File

@@ -0,0 +1,94 @@
package org.egovframe.cloud.reserveitemservice.api.reserveItem.dto;
import lombok.*;
import lombok.experimental.Accessors;
import org.egovframe.cloud.reserveitemservice.domain.location.Location;
import org.egovframe.cloud.reserveitemservice.domain.reserveItem.ReserveItem;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* org.egovframe.cloud.reserveitemservice.api.reserveItem.dto.ReserveItemResponseDto
* <p>
* 예약 물품 응답 dto class
*
* @author 표준프레임워크센터 shinmj
* @version 1.0
* @since 2021/09/13
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/13 shinmj 최초 생성
* </pre>
*/
@Getter
@NoArgsConstructor
@ToString
public class ReserveItemResponseDto {
private Long reserveItemId; // 예약 물품 id
private String reserveItemName; //예약 물품 명
private Long locationId;
private String categoryId; //예약유형 - 공통코드 reserve-category
private Integer prevTotalQty; //총 재고/수용인원 수
private Integer totalQty; //총 재고/수용인원 수
private Integer inventoryQty; // 재고/수용인원 수
private LocalDateTime operationStartDate; //운영 시작 일
private LocalDateTime operationEndDate; //운영 종료 일
private String reserveMethodId; // 예약 방법 - 공통코드 reserve-method
private String reserveMeansId; // 예약 구분 (인터넷 예약 시) - 공통코드 reserve-means
private LocalDateTime requestStartDate; //예약 신청 시작 일시
private LocalDateTime requestEndDate; //예약 신청 종료 일시
private Boolean isPeriod; //기간 지정 가능 여부 - true: 지정 가능, false: 지정 불가
private Integer periodMaxCount; // 최대 예약 가능 일 수
private String externalUrl; //외부링크
private String selectionMeansId; //선별 방법 - 공통코드 reserve-selection
private Boolean isPaid; // 유/무료 - false: 무료, true: 유료
private BigDecimal usageCost; //이용 요금
private Boolean isUse; //사용여부
private String purpose; //용도
private String address; //주소
private String targetId; //이용 대상 - 공통코드 reserve-target
private String excluded; // 사용허가 제외대상
private String homepage; //홈페이지 주소
private String contact; //문의처
private String managerDept; //담당자 소속
private String managerName; //담당자 이름
private String managerContact; //담당자 연락처
@Builder
public ReserveItemResponseDto(ReserveItem reserveItem) {
this.reserveItemId = reserveItem.getReserveItemId();
this.reserveItemName = reserveItem.getReserveItemName();
this.locationId = reserveItem.getLocationId();
this.categoryId = reserveItem.getCategoryId();
this.prevTotalQty = reserveItem.getTotalQty();
this.totalQty = reserveItem.getTotalQty();
this.inventoryQty = reserveItem.getInventoryQty();
this.operationStartDate = reserveItem.getOperationStartDate();
this.operationEndDate = reserveItem.getOperationEndDate();
this.reserveMethodId = reserveItem.getReserveMethodId();
this.reserveMeansId = reserveItem.getReserveMeansId();
this.requestStartDate = reserveItem.getRequestStartDate();
this.requestEndDate = reserveItem.getRequestEndDate();
this.isPeriod = reserveItem.getIsPeriod();
this.periodMaxCount = reserveItem.getPeriodMaxCount();
this.externalUrl = reserveItem.getExternalUrl();
this.selectionMeansId = reserveItem.getSelectionMeansId();
this.isPaid = reserveItem.getIsPaid();
this.usageCost = reserveItem.getUsageCost();
this.isUse = reserveItem.getIsUse();
this.purpose = reserveItem.getPurpose();
this.address = reserveItem.getAddress();
this.targetId = reserveItem.getTargetId();
this.excluded = reserveItem.getExcluded();
this.homepage = reserveItem.getHomepage();
this.contact = reserveItem.getContact();
this.managerDept = reserveItem.getManagerDept();
this.managerName = reserveItem.getManagerName();
this.managerContact = reserveItem.getManagerContact();
}
}

View File

@@ -0,0 +1,126 @@
package org.egovframe.cloud.reserveitemservice.api.reserveItem.dto;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.egovframe.cloud.reserveitemservice.domain.reserveItem.ReserveItem;
import org.egovframe.cloud.reserveitemservice.validator.annotation.ReserveItemSaveValid;
import javax.validation.constraints.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* org.egovframe.cloud.reserveitemservice.api.reserveItem.dto.ReserveItemSaveRequestDto
* <p>
* 예약 물품 저장 요청 dto class
*
* @author 표준프레임워크센터 shinmj
* @version 1.0
* @since 2021/09/13
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/13 shinmj 최초 생성
* </pre>
*/
@Getter
@NoArgsConstructor
@ToString
@ReserveItemSaveValid
public class ReserveItemSaveRequestDto {
@NotBlank
@Size(max = 200)
private String reserveItemName; //예약 물품 명
@NotNull
private Long locationId;
@NotBlank
private String categoryId; //예약유형 - 공통코드 reserve-category
@NotNull
@PositiveOrZero
private Integer totalQty; //재고/수용인원 수
@NotNull
@PositiveOrZero
private Integer inventoryQty; //재고/수용인원 수
@NotNull
private LocalDateTime operationStartDate; //운영 시작 일
@NotNull
private LocalDateTime operationEndDate; //운영 종료 일
@NotBlank
private String reserveMethodId; // 예약 방법 - 공통코드 reserve-method
private String reserveMeansId; // 예약 구분 (인터넷 예약 시) - 공통코드 reserve-means
private LocalDateTime requestStartDate; //예약 신청 시작 일시
private LocalDateTime requestEndDate; //예약 신청 종료 일시
private Boolean isPeriod; //기간 지정 가능 여부 - true: 지정 가능, false: 지정 불가
private Integer periodMaxCount; // 최대 예약 가능 일 수
@Size(max = 500)
private String externalUrl; //외부링크
@NotBlank
private String selectionMeansId; //선별 방법 - 공통코드 reserve-selection
private Boolean isPaid; // 유/무료 - false: 무료, true: 유료
private BigDecimal usageCost; //이용 요금
private Boolean isUse; //사용여부
@Size(max = 4000)
private String purpose; //용도
@Size(max = 500)
private String address; //주소
private String targetId; //이용 대상 - 공통코드 reserve-target
@Size(max = 2000)
private String excluded; // 사용허가 제외대상
@Size(max = 500)
private String homepage; //홈페이지 주소
@Size(max = 50)
private String contact; //문의처
@Size(max = 200)
private String managerDept; //담당자 소속
@Size(max = 200)
private String managerName; //담당자 이름
@Size(max = 50)
private String managerContact; //담당자 연락처
public ReserveItem toEntity() {
return ReserveItem.builder()
.reserveItemName(this.reserveItemName)
.locationId(this.locationId)
.categoryId(this.categoryId)
.totalQty(this.totalQty)
.inventoryQty(this.inventoryQty)
.operationStartDate(this.operationStartDate)
.operationEndDate(this.operationEndDate)
.reserveMethodId(this.reserveMethodId)
.reserveMeansId(this.reserveMeansId)
.requestStartDate(this.requestStartDate)
.requestEndDate(this.requestEndDate)
.isPeriod(this.isPeriod)
.periodMaxCount(this.periodMaxCount)
.externalUrl(this.externalUrl)
.selectionMeansId(this.selectionMeansId)
.isPaid(this.isPaid)
.usageCost(this.usageCost)
.isUse(this.isUse)
.purpose(this.purpose)
.address(this.address)
.targetId(this.targetId)
.excluded(this.excluded)
.homepage(this.homepage)
.contact(this.contact)
.managerDept(this.managerDept)
.managerName(this.managerName)
.managerContact(this.managerContact)
.build();
}
}

View File

@@ -0,0 +1,117 @@
package org.egovframe.cloud.reserveitemservice.api.reserveItem.dto;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.egovframe.cloud.reserveitemservice.domain.reserveItem.ReserveItem;
import org.egovframe.cloud.reserveitemservice.validator.annotation.ReserveItemSaveValid;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* org.egovframe.cloud.reserveitemservice.api.reserveItem.dto.ReserveItemUpdateRequestDto
* <p>
* 예약 물품 수정 요청 dto class
*
* @author 표준프레임워크센터 shinmj
* @version 1.0
* @since 2021/09/13
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/13 shinmj 최초 생성
* </pre>
*/
@Getter
@NoArgsConstructor
@ToString
@ReserveItemSaveValid
public class ReserveItemUpdateRequestDto {
@NotBlank
@Size(max = 200)
private String reserveItemName; //예약 물품 명
@NotNull
private Long locationId;
@NotBlank
private String categoryId; //예약유형 - 공통코드 reserve-category
@NotNull
@PositiveOrZero
private Integer totalQty; //총 재고/수용인원 수
@NotNull
@PositiveOrZero
private Integer inventoryQty; //재고/수용인원 수
@NotNull
private LocalDateTime operationStartDate; //운영 시작 일
@NotNull
private LocalDateTime operationEndDate; //운영 종료 일
@NotBlank
private String reserveMethodId; // 예약 방법 - 공통코드 reserve-method
private String reserveMeansId; // 예약 구분 (인터넷 예약 시) - 공통코드 reserve-means
private LocalDateTime requestStartDate; //예약 신청 시작 일시
private LocalDateTime requestEndDate; //예약 신청 종료 일시
private Boolean isPeriod; //기간 지정 가능 여부 - true: 지정 가능, false: 지정 불가
private Integer periodMaxCount; // 최대 예약 가능 일 수
@Size(max = 500)
private String externalUrl; //외부링크
@NotBlank
private String selectionMeansId; //선별 방법 - 공통코드 reserve-selection
@NotNull
private Boolean isPaid; // 유/무료 - false: 무료, true: 유료
private BigDecimal usageCost; //이용 요금
private Boolean isUse; //사용여부
@Size(max = 4000)
private String purpose; //용도
@Size(max = 500)
private String address; //주소
private String targetId; //이용 대상 - 공통코드 reserve-target
@Size(max = 2000)
private String excluded; // 사용허가 제외대상
@Size(max = 500)
private String homepage; //홈페이지 주소
@Size(max = 50)
private String contact; //문의처
@Size(max = 200)
private String managerDept; //담당자 소속
@Size(max = 200)
private String managerName; //담당자 이름
@Size(max = 50)
private String managerContact; //담당자 연락처
public ReserveItem toEntity() {
return ReserveItem.builder()
.reserveItemName(this.reserveItemName)
.locationId(this.locationId)
.categoryId(this.categoryId)
.totalQty(this.totalQty)
.inventoryQty(this.inventoryQty)
.operationStartDate(this.operationStartDate)
.operationEndDate(this.operationEndDate)
.reserveMethodId(this.reserveMethodId)
.reserveMeansId(this.reserveMeansId)
.requestStartDate(this.requestStartDate)
.requestEndDate(this.requestEndDate)
.isPeriod(this.isPeriod)
.periodMaxCount(this.periodMaxCount)
.externalUrl(this.externalUrl)
.selectionMeansId(this.selectionMeansId)
.isPaid(this.isPaid)
.usageCost(this.usageCost)
.isUse(this.isUse)
.purpose(this.purpose)
.address(this.address)
.targetId(this.targetId)
.excluded(this.excluded)
.homepage(this.homepage)
.contact(this.contact)
.managerDept(this.managerDept)
.managerName(this.managerName)
.managerContact(this.managerContact)
.build();
}
}

View File

@@ -0,0 +1,64 @@
package org.egovframe.cloud.reserveitemservice.api.reserveItem.dto;
import lombok.*;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
/**
* org.egovframe.cloud.reserverequestservice.api.dto.ReserveSaveRequestDto
* <p>
* 예약 신청 저장 요청 dto class
*
* @author 표준프레임워크센터 shinmj
* @version 1.0
* @since 2021/09/17
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/17 shinmj 최초 생성
* </pre>
*/
@Getter
@NoArgsConstructor
@ToString
public class ReserveSaveRequestDto {
@Setter
private String reserveId;
@NotNull
private Long reserveItemId;
private Integer reserveQty; //예약 신청 인원/수량
@NotNull
private String reservePurposeContent; //예약 목적
private String attachmentCode; //첨부파일 코드
private LocalDateTime reserveStartDate; //예약 신청 시작일
private LocalDateTime reserveEndDate; //예약 신청 종료일
@Setter
private String reserveStatusId; //예약상태 - 공통코드(reserve-status)
@NotNull
private String userId; //예약자
@NotNull
private String userContactNo; //예약자 연락처
@NotNull
private String userEmail; //예약자 이메일
@Builder
public ReserveSaveRequestDto(Long reserveItemId, Integer reserveQty, String reservePurposeContent, String attachmentCode, LocalDateTime reserveStartDate, LocalDateTime reserveEndDate, String reserveStatusId, String userId, String userContactNo, String userEmail) {
this.reserveItemId = reserveItemId;
this.reserveQty = reserveQty;
this.reservePurposeContent = reservePurposeContent;
this.attachmentCode = attachmentCode;
this.reserveStartDate = reserveStartDate;
this.reserveEndDate = reserveEndDate;
this.reserveStatusId = reserveStatusId;
this.userId = userId;
this.userContactNo = userContactNo;
this.userEmail = userEmail;
}
}

View File

@@ -0,0 +1,39 @@
package org.egovframe.cloud.reserveitemservice.config;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
/**
* org.egovframe.cloud.reserverequestservice.config.RequestMessage
*
* 예약 신청 후 이벤트 스트림 message VO class
*
* @author 표준프레임워크센터 shinmj
* @version 1.0
* @since 2021/09/16
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/16 shinmj 최초 생성
* </pre>
*/
@NoArgsConstructor
@Getter
@ToString
public class RequestMessage {
private String reserveId;
private Boolean isItemUpdated;
private String uuid;
@Builder
public RequestMessage(String reserveId, Boolean isItemUpdated, String uuid) {
this.reserveId = reserveId;
this.isItemUpdated = isItemUpdated;
this.uuid = uuid;
}
}

View File

@@ -0,0 +1,65 @@
package org.egovframe.cloud.reserveitemservice.config;
import lombok.extern.slf4j.Slf4j;
import org.egovframe.cloud.reserveitemservice.api.reserveItem.dto.ReserveSaveRequestDto;
import org.egovframe.cloud.reserveitemservice.service.reserveItem.ReserveItemService;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Scheduler;
import reactor.core.scheduler.Schedulers;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* org.egovframe.cloud.reserverequestservice.config.ReserveEventConfig
*
* event stream 설정 class
*
* @author 표준프레임워크센터 shinmj
* @version 1.0
* @since 2021/09/16
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/16 shinmj 최초 생성
* </pre>
*/
@Slf4j
@Configuration
public class ReserveEventConfig {
@Autowired
private ReserveItemService reserveItemService;
/**
* 예약 신청 후 재고 변경에 대한 consumer
*
* @return
*/
@Bean
public Consumer<ReserveSaveRequestDto> reserveRequest() {
return reserveSaveRequestDto -> {
log.info("receive data => {}", reserveSaveRequestDto);
reserveItemService.updateInventoryThenSendMessage(
reserveSaveRequestDto.getReserveItemId(),
reserveSaveRequestDto.getReserveQty(),
reserveSaveRequestDto.getReserveId())
.subscribe();
};
}
}

View File

@@ -0,0 +1,45 @@
package org.egovframe.cloud.reserveitemservice.config;
import java.time.Duration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;
import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry;
/**
* org.egovframe.cloud.portalservice.config.Resilience4JConfig
* <p>
* Resilience4J Configuration
* 기본 설정값으로 운영되어도 무방하다. 이 클래스는 필수는 아니다.
* retry 기본값은 최대 3회이고, fallback 이 없는 경우에만 동작하므로 설정하지 않았다.
*
* @author 표준프레임워크센터 jaeyeolkim
* @version 1.0
* @since 2021/08/31
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/08/31 jaeyeolkim 최초 생성
* 2021/10/05 shinmj reactive로 변경
* </pre>
*/
@Configuration
public class Resilience4JConfig {
@Bean
public CircuitBreakerRegistry circuitBreakerRegistry() {
CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
.failureRateThreshold(50) // Circuit 열지 말지 결정하는 실패 threshold 퍼센테이지
.waitDurationInOpenState(Duration.ofSeconds(5)) // (half closed 전에) circuitBreaker가 open 되기 전에 기다리는 기간
.slidingWindowType(CircuitBreakerConfig.SlidingWindowType.COUNT_BASED) // circuit breaker count 기반 처리
.slidingWindowSize(10) // 통계 대상 건수 -> N건의 요청중..
.build();
return CircuitBreakerRegistry.of(circuitBreakerConfig);
}
}

View File

@@ -0,0 +1,51 @@
package org.egovframe.cloud.reserveitemservice.domain.code;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.egovframe.cloud.reactive.domain.BaseEntity;
import org.springframework.data.annotation.Id;
import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.mapping.Table;
/**
* org.egovframe.cloud.boardservice.domain.org.egovframe.cloud.reserveitemservice.domain.org.egovframe.cloud.reserveitemservice.domain.code.Code
* <p>
* 공통코드 엔티티
*
* @author 표준프레임워크센터 jaeyeolkim
* @version 1.0
* @since 2021/07/12
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/07/12 jaeyeolkim 최초 생성
* 2021/09/15 shinmj r2dbc 변경
* </pre>
*/
@Getter
@NoArgsConstructor
@ToString
@Table("code")
public class Code extends BaseEntity {
@Id
@Column
private String codeId; // 코드ID
@Column
private String parentCodeId; // 상위 코드ID
@Column
private String codeName; // 코드 명
@Builder
public Code(String codeId, String parentCodeId, String codeName) {
this.codeId = codeId;
this.parentCodeId = parentCodeId;
this.codeName = codeName;
}
}

View File

@@ -0,0 +1,65 @@
package org.egovframe.cloud.reserveitemservice.domain.location;
import lombok.*;
import org.egovframe.cloud.reactive.domain.BaseEntity;
import org.springframework.data.annotation.Id;
import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.mapping.Table;
import javax.validation.constraints.Size;
/**
* org.egovframe.cloud.reserveitemservice.domain.location.Location
*
* 예약 지역 도메인 클래스
*
* @author 표준프레임워크센터 shinmj
* @version 1.0
* @since 2021/09/06
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/06 shinmj 최초 생성
* </pre>
*/
@Getter
@NoArgsConstructor
@ToString
@Table("location")
public class Location extends BaseEntity {
@Id
private Long locationId;
@Size(max = 200)
@Column
private String locationName;
@Column
private Integer sortSeq;
@Column("use_at")
private Boolean isUse;
@Builder
public Location(Long locationId, String locationName, Integer sortSeq, Boolean isUse) {
this.locationId = locationId;
this.locationName = locationName;
this.sortSeq = sortSeq;
this.isUse = isUse;
}
public Location update(String locationName, Integer sortSeq, Boolean isUse) {
this.locationName = locationName;
this.sortSeq = sortSeq;
this.isUse = isUse;
return this;
}
public Location updateIsUse(Boolean isUse) {
this.isUse = isUse;
return this;
}
}

View File

@@ -0,0 +1,58 @@
package org.egovframe.cloud.reserveitemservice.domain.location;
import org.springframework.data.domain.Pageable;
import org.springframework.data.r2dbc.repository.R2dbcRepository;
import org.springframework.stereotype.Repository;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* org.egovframe.cloud.reserveitemservice.domain.location.LocationRepository
*
* 예약 지역 R2dbc repository 클래스
*
* @author 표준프레임워크센터 shinmj
* @version 1.0
* @since 2021/09/06
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/06 shinmj 최초 생성
* </pre>
*/
@Repository
public interface LocationRepository extends R2dbcRepository<Location, Long> {
/**
* 검색조건(지역이름)을 포함한 목록조회
*
* @param locationName
* @param pageable
* @return
*/
Flux<Location> findAllByLocationNameContainingOrderBySortSeq(String locationName, Pageable pageable);
/**
* 검색조건(지역이름)을 포함한 count
* paging 처리를 하기 위해서 조회
*
* @param locationName
* @return
*/
Mono<Long> countAllByLocationNameContaining(String locationName);
/**
* paging 처리를 하기 위한 목록 조회
*
* @param pageable
* @return
*/
Flux<Location> findAllByOrderBySortSeq (Pageable pageable);
Flux<Location> findAllByIsUseTrueOrderBySortSeq();
}

View File

@@ -0,0 +1,19 @@
package org.egovframe.cloud.reserveitemservice.domain.reserveItem;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public enum Category {
EDUCATION("education", "교육"),
EQUIPMENT("equipment", "장비"),
SPACE("space", "공간");
private final String key;
private final String title;
public boolean isEquals(String compare) {
return this.getKey().equals(compare);
}
}

View File

@@ -0,0 +1,341 @@
package org.egovframe.cloud.reserveitemservice.domain.reserveItem;
import lombok.*;
import lombok.experimental.Accessors;
import org.egovframe.cloud.reactive.domain.BaseEntity;
import org.egovframe.cloud.reserveitemservice.api.reserveItem.dto.ReserveItemUpdateRequestDto;
import org.egovframe.cloud.reserveitemservice.domain.location.Location;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Transient;
import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.mapping.Table;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* org.egovframe.cloud.reserveitemservice.domain.reserveItem.ReserveItem
*
* 예약 물품 도메인 클래스
*
* @author 표준프레임워크센터 shinmj
* @version 1.0
* @since 2021/09/09
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/09 shinmj 최초 생성
* </pre>
*/
@Getter
@NoArgsConstructor
@ToString
@Table("reserve_item")
public class ReserveItem extends BaseEntity {
@Id
@Column("reserve_item_id")
private Long reserveItemId; // 예약 물품 id
@Size(max = 200)
@NotNull
@Column("reserve_item_name")
private String reserveItemName; //예약 물품 명
@Column
private Long locationId;
@ToString.Exclude
@Transient
private Location location; //지역
@Size(max = 20)
@NotNull
@Column
private String categoryId; //예약유형 - 공통코드 reserve-category
@Transient
private String categoryName;
@Size(max = 5)
@NotNull
@Column
private Integer totalQty; //총 재고/수용인원 수
@Size(max = 5)
@Column
private Integer inventoryQty; //현재 재고/수용인원 수
@Column
private LocalDateTime operationStartDate; //운영 시작 일
@Column
private LocalDateTime operationEndDate; //운영 종료 일
@Size(max = 20)
@NotNull
@Column
private String reserveMethodId; // 예약 방법 - 공통코드 reserve-method
@Transient
private String reserveMethodName;
@Size(max = 20)
@Column
private String reserveMeansId; // 예약 구분 (인터넷 예약 시) - 공통코드 reserve-means
@Transient
private String reserveMeansName;
@Column
private LocalDateTime requestStartDate; //예약 신청 시작 일시
@Column
private LocalDateTime requestEndDate; //예약 신청 종료 일시
@Column("period_at")
private Boolean isPeriod; //기간 지정 가능 여부 - true: 지정 가능, false: 지정 불가
@Size(max = 3)
@Column
private Integer periodMaxCount; // 최대 예약 가능 일 수
@Size(max = 500)
@Column
private String externalUrl; //외부링크
@Size(max = 20)
@NotNull
@Column
private String selectionMeansId; //선별 방법 - 공통코드 reserve-selection
@Transient
private String selectionMeansName;
@Column("paid_at")
private Boolean isPaid; // 유/무료 - false: 무료, true: 유료
@Column
private BigDecimal usageCost; //이용 요금
@Column("use_at")
private Boolean isUse; //사용여부
@Size(max = 4000)
@Column("purpose_content")
private String purpose; //용도
@Size(max = 500)
@Column("item_addr")
private String address; //주소
@Size(max = 20)
@Column
private String targetId; //이용 대상 - 공통코드 reserve-target
@Transient
private String targetName;
@Size(max = 2000)
@Column("excluded_content")
private String excluded; // 사용허가 제외대상
@Size(max = 500)
@Column("homepage_url")
private String homepage; //홈페이지 주소
@Size(max = 50)
@Column("contact_no")
private String contact; //문의처
@Size(max = 200)
@Column("manager_dept_name")
private String managerDept; //담당자 소속
@Size(max = 200)
@Column("manager_name")
private String managerName; //담당자 이름
@Size(max = 50)
@Column("manager_contact_no")
private String managerContact; //담당자 연락처
@Builder
public ReserveItem(Long reserveItemId, String reserveItemName, Long locationId, Location location, String categoryId, String categoryName, Integer totalQty, Integer inventoryQty, LocalDateTime operationStartDate, LocalDateTime operationEndDate, String reserveMethodId, String reserveMethodName, String reserveMeansId, String reserveMeansName, LocalDateTime requestStartDate, LocalDateTime requestEndDate, Boolean isPeriod, Integer periodMaxCount, String externalUrl, String selectionMeansId, String selectionMeansName, Boolean isPaid, BigDecimal usageCost, Boolean isUse, String purpose, String address, String targetId, String targetName, String excluded, String homepage, String contact, String managerDept, String managerName, String managerContact) {
this.reserveItemId = reserveItemId;
this.reserveItemName = reserveItemName;
this.locationId = locationId;
this.location = location;
this.categoryId = categoryId;
this.categoryName = categoryName;
this.totalQty = totalQty;
this.inventoryQty = inventoryQty;
this.operationStartDate = operationStartDate;
this.operationEndDate = operationEndDate;
this.reserveMethodId = reserveMethodId;
this.reserveMethodName = reserveMethodName;
this.reserveMeansId = reserveMeansId;
this.reserveMeansName = reserveMeansName;
this.requestStartDate = requestStartDate;
this.requestEndDate = requestEndDate;
this.isPeriod = isPeriod;
this.periodMaxCount = periodMaxCount;
this.externalUrl = externalUrl;
this.selectionMeansId = selectionMeansId;
this.selectionMeansName = selectionMeansName;
this.isPaid = isPaid;
this.usageCost = usageCost;
this.isUse = isUse;
this.purpose = purpose;
this.address = address;
this.targetId = targetId;
this.targetName = targetName;
this.excluded = excluded;
this.homepage = homepage;
this.contact = contact;
this.managerDept = managerDept;
this.managerName = managerName;
this.managerContact = managerContact;
}
/**
* 예약 지역 정보 조회
*
* @param location
* @return
*/
public ReserveItem setLocation(Location location) {
this.location = location;
return this;
}
/**
* 예약 유형 명칭 조회 세팅
*
* @param categoryName
* @return
*/
public ReserveItem setCategoryName(String categoryName) {
this.categoryName = categoryName;
return this;
}
/**
* 예약 방법 명칭
*
* @param reserveMethodName
* @return
*/
public ReserveItem setReserveMethodName(String reserveMethodName) {
this.reserveMethodName = reserveMethodName;
return this;
}
/**
* 예약 구분 명칭
*
* @param reserveMeansName
* @return
*/
public ReserveItem setReserveMeansName(String reserveMeansName) {
this.reserveMeansName = reserveMeansName;
return this;
}
/**
* 선별 방법 명칭
*
* @param selectionMeansName
* @return
*/
public ReserveItem setSelectionMeansName(String selectionMeansName) {
this.selectionMeansName = selectionMeansName;
return this;
}
/**
* 이용 대상 명칭
*
* @param targetName
* @return
*/
public ReserveItem setTargetName(String targetName) {
this.targetName = targetName;
return this;
}
/**
* 예약 물품 정보 업데이트
*
* @param updateRequestDto
* @return
*/
public ReserveItem update(ReserveItemUpdateRequestDto updateRequestDto) {
System.out.println("============ ?? : " + updateRequestDto.toString());
this.reserveItemName = updateRequestDto.getReserveItemName();
this.locationId = updateRequestDto.getLocationId();
this.categoryId = updateRequestDto.getCategoryId();
this.totalQty = updateRequestDto.getTotalQty();
this.inventoryQty = updateRequestDto.getInventoryQty();
this.operationStartDate = updateRequestDto.getOperationStartDate();
this.operationEndDate = updateRequestDto.getOperationEndDate();
this.reserveMethodId = updateRequestDto.getReserveMethodId();
this.reserveMeansId = updateRequestDto.getReserveMeansId();
this.requestStartDate = updateRequestDto.getRequestStartDate();
this.requestEndDate = updateRequestDto.getRequestEndDate();
this.isPeriod = updateRequestDto.getIsPeriod();
this.periodMaxCount = updateRequestDto.getPeriodMaxCount();
this.externalUrl = updateRequestDto.getExternalUrl();
this.selectionMeansId = updateRequestDto.getSelectionMeansId();
this.isPaid = updateRequestDto.getIsPaid();
this.usageCost = updateRequestDto.getUsageCost();
this.isUse = updateRequestDto.getIsUse();
this.purpose = updateRequestDto.getPurpose();
this.address = updateRequestDto.getAddress();
this.targetId = updateRequestDto.getTargetId();
this.excluded = updateRequestDto.getExcluded();
this.homepage = updateRequestDto.getHomepage();
this.contact = updateRequestDto.getContact();
this.managerDept = updateRequestDto.getManagerDept();
this.managerName = updateRequestDto.getManagerName();
this.managerContact = updateRequestDto.getManagerContact();
return this;
}
/**
* 재고 변경
*
* @param inventoryQty
* @return
*/
public ReserveItem updateInventoryQty(Integer inventoryQty) {
this.inventoryQty = inventoryQty;
return this;
}
/**
* 사용여부 변경
*
* @param isUse
* @return
*/
public ReserveItem updateIsUse(Boolean isUse) {
this.isUse = isUse;
return this;
}
public ReserveItem setCreateDate(LocalDateTime createDate) {
this.createDate = createDate;
return this;
}
}

View File

@@ -0,0 +1,25 @@
package org.egovframe.cloud.reserveitemservice.domain.reserveItem;
import org.springframework.data.r2dbc.repository.R2dbcRepository;
import reactor.core.publisher.Flux;
/**
* org.egovframe.cloud.reserveitemservice.domain.reserveItem.ReserveItemRepository
*
* 예약 물품 도메인 repository interface
*
* @author 표준프레임워크센터 shinmj
* @version 1.0
* @since 2021/09/09
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/09 shinmj 최초 생성
* </pre>
*/
public interface ReserveItemRepository extends R2dbcRepository<ReserveItem, Long>, ReserveItemRepositoryCustom {
}

View File

@@ -0,0 +1,43 @@
package org.egovframe.cloud.reserveitemservice.domain.reserveItem;
import java.time.LocalDateTime;
import org.egovframe.cloud.reserveitemservice.api.reserveItem.dto.ReserveItemMainResponseDto;
import org.egovframe.cloud.reserveitemservice.api.reserveItem.dto.ReserveItemRequestDto;
import org.egovframe.cloud.reserveitemservice.domain.code.Code;
import org.springframework.data.domain.Pageable;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* org.egovframe.cloud.reserveitemservice.domain.reserveItem.ReserveItemRepositoryCustom
*
* 예약 물품 도메인 repository custom(query) interface
* R2DBCEntityTemplate을 이용하여 쿼리하기 위한 Interface
*
* @author 표준프레임워크센터 shinmj
* @version 1.0
* @since 2021/09/13
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/13 shinmj 최초 생성
* </pre>
*/
public interface ReserveItemRepositoryCustom {
Flux<ReserveItem> search(ReserveItemRequestDto requestDto, Pageable pageable);
Mono<Long> searchCount(ReserveItemRequestDto requestDto, Pageable pageable);
Flux<ReserveItem> searchForUser(String categoryId, ReserveItemRequestDto requestDto, Pageable pageable);
Mono<Long> searchCountForUser(String categoryId, ReserveItemRequestDto requestDto, Pageable pageable);
Mono<ReserveItem> findWithRelation(Long reserveItemId);
Flux<ReserveItem> findLatestByCategory(Integer count, String categoryId);
Flux<Code> findCodeDetail(String codeId);
}

View File

@@ -0,0 +1,287 @@
package org.egovframe.cloud.reserveitemservice.domain.reserveItem;
import static org.springframework.data.relational.core.query.Criteria.*;
import java.util.ArrayList;
import java.util.List;
import org.egovframe.cloud.reserveitemservice.api.reserveItem.dto.ReserveItemRequestDto;
import org.egovframe.cloud.reserveitemservice.domain.code.Code;
import org.egovframe.cloud.reserveitemservice.domain.location.Location;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.r2dbc.core.R2dbcEntityTemplate;
import org.springframework.data.relational.core.query.Criteria;
import org.springframework.data.relational.core.query.Query;
import org.springframework.util.StringUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* org.egovframe.cloud.reserveitemservice.domain.reserveItem.ReserveItemRepositoryImpl
*
* 예약 물품 도메인 repository custom(query) 구현체
*
* @author 표준프레임워크센터 shinmj
* @version 1.0
* @since 2021/09/13
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/13 shinmj 최초 생성
* </pre>
*/
@Slf4j
@RequiredArgsConstructor
public class ReserveItemRepositoryImpl implements ReserveItemRepositoryCustom{
private final R2dbcEntityTemplate entityTemplate;
/**
* page 목록 조회
*
* @param requestDto
* @param pageable
* @return
*/
@Override
public Flux<ReserveItem> search(ReserveItemRequestDto requestDto, Pageable pageable) {
return entityTemplate.select(ReserveItem.class)
.matching(Query.query(Criteria.from(whereQuery(requestDto)))
.sort(Sort.by(Sort.Direction.DESC, "create_date"))
.with(pageable))
.all()
.flatMap(this::loadRelations)
.switchIfEmpty(Flux.empty());
}
/**
* 목록 total count 조회
*
* @param requestDto
* @param pageable
* @return
*/
@Override
public Mono<Long> searchCount(ReserveItemRequestDto requestDto, Pageable pageable) {
return entityTemplate.select(ReserveItem.class)
.matching(Query.query(Criteria.from(whereQuery(requestDto)))
.sort(Sort.by(Sort.Direction.DESC, "create_date"))
.with(pageable))
.count();
}
@Override
public Flux<ReserveItem> searchForUser(String categoryId, ReserveItemRequestDto requestDto, Pageable pageable) {
Criteria where = Criteria.from(whereQuery(requestDto));
if (!"all".equals(categoryId)) {
where = where.and(where("category_id").is(categoryId));
}
Query query = Query.query(where("use_at").isTrue().and(where))
.sort(Sort.by(Sort.Direction.DESC, "create_date"))
.with(pageable);
return entityTemplate.select(ReserveItem.class)
.matching(query)
.all()
.flatMap(this::loadRelations)
.switchIfEmpty(Flux.empty());
}
@Override
public Mono<Long> searchCountForUser(String categoryId, ReserveItemRequestDto requestDto, Pageable pageable) {
Criteria where = Criteria.from(whereQuery(requestDto));
if (!"all".equals(categoryId)) {
where = where.and(where("category_id").is(categoryId));
}
Query query = Query.query(where("use_at").isTrue().and(where))
.sort(Sort.by(Sort.Direction.DESC, "create_date"))
.with(pageable);
return entityTemplate.select(ReserveItem.class)
.matching(query)
.count();
}
/**
* relation 걸린 table 정보도 같이 조회
* 공통코드, 지역
*
* @param reserveItemId
* @return
*/
@Override
public Mono<ReserveItem> findWithRelation(Long reserveItemId) {
return entityTemplate.selectOne(Query.query(where("reserve_item_id").is(reserveItemId)), ReserveItem.class)
.flatMap(this::loadRelationsAll)
.switchIfEmpty(Mono.empty());
}
/**
* 카테고리별 예약 물품 최신 데이터 count 만큼 조회
*
* @param count 조회할 갯수 0:전체
* @param categoryId 카테고리 아이디
* @return
*/
@Override
public Flux<ReserveItem> findLatestByCategory(Integer count, String categoryId) {
Query query =Query.query(where("category_id").is(categoryId)
.and("use_at").isTrue()).sort(Sort.by(Sort.Order.desc("create_date")));
if (count > 0) {
query.limit(count);
}
return entityTemplate.select(ReserveItem.class)
.matching(query)
.all()
.flatMap(this::loadRelations);
}
/**
* 공통코드 조회
*
* @param codeId
* @return
*/
@Override
public Flux<Code> findCodeDetail(String codeId) {
return entityTemplate.select(Code.class)
.matching(Query.query(where("parent_code_id").is(codeId).and("use_at").isTrue()))
.all();
}
/**
* 유형만 공통코드 조회
*
* @param reserveItem
* @return
*/
private Mono<ReserveItem> loadRelations(final ReserveItem reserveItem) {
//load common code
Mono<ReserveItem> mono = Mono.just(reserveItem)
.zipWith(findCodeById(reserveItem.getCategoryId()))
.map(tuple -> tuple.getT1().setCategoryName(tuple.getT2().getCodeName()))
.switchIfEmpty(Mono.just(reserveItem));
// load location
mono = mono.zipWith(findLocationById(reserveItem.getLocationId()))
.map(tuple -> tuple.getT1().setLocation(tuple.getT2()))
.switchIfEmpty(mono);
return mono;
}
/**
* 공통코드 이름 조회 (모든 공통코드에 대해 조회)
*
* @param reserveItem
* @return
*/
private Mono<ReserveItem> loadRelationsAll(final ReserveItem reserveItem) {
//load common code
Mono<ReserveItem> mono = Mono.just(reserveItem)
.zipWith(findCodeById(reserveItem.getCategoryId()))
.map(tuple -> tuple.getT1().setCategoryName(tuple.getT2().getCodeName()))
.zipWith(findCodeById(reserveItem.getReserveMethodId()))
.map(tuple -> tuple.getT1().setReserveMethodName(tuple.getT2().getCodeName()))
.zipWith(findCodeById(reserveItem.getReserveMeansId()))
.map(tuple -> tuple.getT1().setReserveMeansName(tuple.getT2().getCodeName()))
.zipWith(findCodeById(reserveItem.getSelectionMeansId()))
.map(tuple -> tuple.getT1().setSelectionMeansName(tuple.getT2().getCodeName()))
.zipWith(findCodeById(reserveItem.getTargetId()))
.map(tuple -> tuple.getT1().setTargetName(tuple.getT2().getCodeName()))
.switchIfEmpty(Mono.just(reserveItem));
// load location
mono = mono.zipWith(findLocationById(reserveItem.getLocationId()))
.map(tuple -> tuple.getT1().setLocation(tuple.getT2()))
.switchIfEmpty(mono);
return mono;
}
/**
* 지역 조회
*
* @param locationId
* @return
*/
private Mono<Location> findLocationById(Long locationId ) {
return entityTemplate.select(Location.class)
.matching(Query.query(where("location_id").is(locationId)))
.one()
.switchIfEmpty(Mono.empty());
}
/**
* 공통 코드 조회
*
* @param codeId
* @return
*/
private Mono<Code> findCodeById(String codeId ) {
return entityTemplate.select(Code.class)
.matching(Query.query(where("code_id").is(codeId)))
.one()
.switchIfEmpty(Mono.empty());
}
/**
* 조회조건 쿼리
*
* @param requestDto
* @return
*/
private List<Criteria> whereQuery(ReserveItemRequestDto requestDto) {
String keywordType = requestDto.getKeywordType();
String keyword = requestDto.getKeyword();
List<Criteria> whereCriteria = new ArrayList<>();
if (StringUtils.hasText(keyword)) {
if ("item".equals(keywordType)) {
whereCriteria.add(where("reserve_item_name").like(likeText(keyword)));
}
}
if (requestDto.getLocationId() != null) {
whereCriteria.add(where("location_id").in(requestDto.getLocationId()));
}
if (requestDto.getCategoryId() != null ) {
whereCriteria.add(where("category_id").in(requestDto.getCategoryId()));
}
// 물품 팝업에서 조회하는 경우 인터넷 예약이 가능한 물품만 조회
if (requestDto.getIsUse()) {
whereCriteria.add(where("use_at").isTrue());
whereCriteria.add(where("reserve_method_id").is("internet"));
}
return whereCriteria;
}
/**
* like 검색
*
* @param keyword
* @return
*/
private String likeText(String keyword) {
return "%" + keyword + "%";
}
}

View File

@@ -0,0 +1,185 @@
package org.egovframe.cloud.reserveitemservice.service.location;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.egovframe.cloud.common.dto.RequestDto;
import org.egovframe.cloud.common.exception.BusinessMessageException;
import org.egovframe.cloud.common.exception.EntityNotFoundException;
import org.egovframe.cloud.common.service.AbstractService;
import org.egovframe.cloud.common.util.MessageUtil;
import org.egovframe.cloud.reactive.service.ReactiveAbstractService;
import org.egovframe.cloud.reserveitemservice.api.location.dto.LocationResponseDto;
import org.egovframe.cloud.reserveitemservice.api.location.dto.LocationSaveRequestDto;
import org.egovframe.cloud.reserveitemservice.api.location.dto.LocationUpdateRequestDto;
import org.egovframe.cloud.reserveitemservice.domain.location.Location;
import org.egovframe.cloud.reserveitemservice.domain.location.LocationRepository;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* org.egovframe.cloud.reserveitemservice.service.location.LocationService
*
* 예약 지역 service 클래스
*
* @author 표준프레임워크센터 shinmj
* @version 1.0
* @since 2021/09/06
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/06 shinmj 최초 생성
* </pre>
*/
@Slf4j
@RequiredArgsConstructor
@Transactional
@Service
public class LocationService extends ReactiveAbstractService {
private final LocationRepository locationRepository;
/**
* 검색조건 없을 경우 전체 목록 조회
*
* @param pageable
* @return
*/
private Mono<Page<LocationResponseDto>> findAll(Pageable pageable) {
return locationRepository.findAllByOrderBySortSeq(pageable)
.flatMap(this::convertLocationResponseDto)
.collectList()
.zipWith(locationRepository.count())
.flatMap(tuple -> Mono.just(new PageImpl<>(tuple.getT1(), pageable, tuple.getT2())));
}
/**
* entity를 응답 dto 형태로 변환
*
* @param location
* @return
*/
private Mono<LocationResponseDto> convertLocationResponseDto(Location location) {
return Mono.just(LocationResponseDto.builder()
.entity(location)
.build());
}
/**
* 예약 지역 목록 조회
*
* @param requestDto
* @param pageable
* @return
*/
@Transactional(readOnly = true)
public Mono<Page<LocationResponseDto>> search(RequestDto requestDto, Pageable pageable) {
if (!StringUtils.hasText(requestDto.getKeywordType()) || !StringUtils.hasText(requestDto.getKeyword())) {
return findAll(pageable);
}
if ("locationName".equals(requestDto.getKeywordType())
&& StringUtils.hasText(requestDto.getKeyword())
) {
return locationRepository.findAllByLocationNameContainingOrderBySortSeq(requestDto.getKeyword(), pageable)
.flatMap(this::convertLocationResponseDto)
.collectList()
.zipWith(locationRepository.countAllByLocationNameContaining(requestDto.getKeyword()))
.flatMap(tuple -> Mono.just(new PageImpl<>(tuple.getT1(), pageable, tuple.getT2())));
}
return findAll(pageable);
}
/**
* 예약 지역 한건 조회
*
* @param locationId
* @return
*/
@Transactional(readOnly = true)
public Mono<LocationResponseDto> findById(Long locationId) {
return locationRepository.findById(locationId)
.switchIfEmpty(monoResponseStatusEntityNotFoundException(locationId))
.flatMap(this::convertLocationResponseDto);
}
/**
* 지역 목록 조회 - 사용여부 = true
*
* @return
*/
@Transactional(readOnly = true)
public Flux<LocationResponseDto> findAll() {
return locationRepository.findAllByIsUseTrueOrderBySortSeq()
.flatMap(this::convertLocationResponseDto);
}
/**
* 예약 지역 저장
*
* @param saveRequestDto
* @return
*/
public Mono<LocationResponseDto> save(LocationSaveRequestDto saveRequestDto) {
return locationRepository.save(saveRequestDto.toEntity())
.flatMap(this::convertLocationResponseDto);
}
/**
* 예약 지역 한건 저장
*
* @param locationId
* @param updateRequestDto
* @return
*/
public Mono<Void> update(Long locationId, LocationUpdateRequestDto updateRequestDto) {
return locationRepository.findById(locationId)
.switchIfEmpty(monoResponseStatusEntityNotFoundException(locationId))
.map(location ->
location.update(updateRequestDto.getLocationName(),
updateRequestDto.getSortSeq(),
updateRequestDto.getIsUse())
)
.flatMap(locationRepository::save)
.then();
}
/**
* 예약 지역 한건 삭제
*
* @param locationId
* @return
*/
public Mono<Void> delete(Long locationId) {
return locationRepository.findById(locationId)
.switchIfEmpty(monoResponseStatusEntityNotFoundException(locationId))
.flatMap(locationRepository::delete)
.onErrorResume(DataIntegrityViolationException.class,
throwable -> Mono.error(new BusinessMessageException("참조하는 데이터가 있어 삭제할 수 없습니다.")));
}
/**
* 예약 지역 사용여부 토글
*
* @param locationId
* @param isUse
* @return
*/
public Mono<Void> updateIsUse(Long locationId, Boolean isUse) {
return locationRepository.findById(locationId)
.switchIfEmpty(monoResponseStatusEntityNotFoundException(locationId))
.map(location -> location.updateIsUse(isUse))
.flatMap(locationRepository::save)
.then();
}
}

View File

@@ -0,0 +1,278 @@
package org.egovframe.cloud.reserveitemservice.service.reserveItem;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.egovframe.cloud.common.exception.BusinessMessageException;
import org.egovframe.cloud.reactive.service.ReactiveAbstractService;
import org.egovframe.cloud.reserveitemservice.api.reserveItem.dto.ReserveItemListResponseDto;
import org.egovframe.cloud.reserveitemservice.api.reserveItem.dto.ReserveItemMainResponseDto;
import org.egovframe.cloud.reserveitemservice.api.reserveItem.dto.ReserveItemRelationResponseDto;
import org.egovframe.cloud.reserveitemservice.api.reserveItem.dto.ReserveItemRequestDto;
import org.egovframe.cloud.reserveitemservice.api.reserveItem.dto.ReserveItemResponseDto;
import org.egovframe.cloud.reserveitemservice.api.reserveItem.dto.ReserveItemSaveRequestDto;
import org.egovframe.cloud.reserveitemservice.api.reserveItem.dto.ReserveItemUpdateRequestDto;
import org.egovframe.cloud.reserveitemservice.config.RequestMessage;
import org.egovframe.cloud.reserveitemservice.domain.reserveItem.Category;
import org.egovframe.cloud.reserveitemservice.domain.reserveItem.ReserveItem;
import org.egovframe.cloud.reserveitemservice.domain.reserveItem.ReserveItemRepository;
import org.springframework.cloud.stream.function.StreamBridge;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
/**
* org.egovframe.cloud.reserveitemservice.service.reserveItem.ReserveItemService
*
* 예약 물품 service class
*
* @author 표준프레임워크센터 shinmj
* @version 1.0
* @since 2021/09/13
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/13 shinmj 최초 생성
* </pre>
*/
@Slf4j
@RequiredArgsConstructor
@Transactional
@Service
public class ReserveItemService extends ReactiveAbstractService {
private static final String RESERVE_CATEGORY_CODE = "reserve-category";
private final ReserveItemRepository reserveItemRepository;
private final StreamBridge streamBridge;
/**
* entity -> dto 변환
*
* @param reserveItem
* @return
*/
private Mono<ReserveItemResponseDto> convertReserveItemResponseDto(ReserveItem reserveItem) {
return Mono.just(ReserveItemResponseDto.builder().reserveItem(reserveItem).build());
}
/**
* entity -> dto 변환
*
* @param reserveItem
* @return
*/
private Mono<ReserveItemListResponseDto> convertReserveItemListResponseDto(ReserveItem reserveItem) {
return Mono.just(ReserveItemListResponseDto.builder().reserveItem(reserveItem).build());
}
/**
* 목록 조회
*
* @param requestDto
* @param pageable
* @return
*/
@Transactional(readOnly = true)
public Mono<Page<ReserveItemListResponseDto>> search(ReserveItemRequestDto requestDto, Pageable pageable) {
return reserveItemRepository.search(requestDto, pageable)
.flatMap(this::convertReserveItemListResponseDto)
.collectList()
.zipWith(reserveItemRepository.searchCount(requestDto, pageable))
.flatMap(tuple -> Mono.just(new PageImpl<>(tuple.getT1(), pageable, tuple.getT2())));
}
/**
* 목록 조회 - 사용자가 조회 시
*
* @param categoryId
* @param requestDto
* @param pageable
* @return
*/
@Transactional(readOnly = true)
public Mono<Page<ReserveItemListResponseDto>> searchForUser(String categoryId, ReserveItemRequestDto requestDto, Pageable pageable) {
return reserveItemRepository.searchForUser(categoryId, requestDto, pageable)
.flatMap(this::convertReserveItemListResponseDto)
.collectList()
.zipWith(reserveItemRepository.searchCountForUser(categoryId, requestDto, pageable))
.flatMap(tuple -> Mono.just(new PageImpl<>(tuple.getT1(), pageable, tuple.getT2())));
}
/**
* 한건 조회
*
* @param reserveItemId
* @return
*/
@Transactional(readOnly = true)
public Mono<ReserveItemResponseDto> findById(Long reserveItemId) {
return reserveItemRepository.findById(reserveItemId)
.switchIfEmpty(monoResponseStatusEntityNotFoundException(reserveItemId))
.flatMap(this::convertReserveItemResponseDto);
}
/**
* 저장
*
* @param saveRequestDto
* @return
*/
public Mono<ReserveItemResponseDto> save(ReserveItemSaveRequestDto saveRequestDto) {
return reserveItemRepository.save(saveRequestDto.toEntity())
.flatMap(this::convertReserveItemResponseDto);
}
/**
* 수정
*
* @param id
* @param updateRequestDto
* @return
*/
public Mono<Void> update(Long id, ReserveItemUpdateRequestDto updateRequestDto) {
return reserveItemRepository.findById(id)
.switchIfEmpty(monoResponseStatusEntityNotFoundException(id))
.map(reserveItem -> reserveItem.update(updateRequestDto))
.flatMap(reserveItemRepository::save)
.then();
}
/**
* 사용여부 업데이트
*
* @param reserveItemId
* @param isUse
* @return
*/
public Mono<Void> updateIsUse(Long reserveItemId, Boolean isUse) {
return reserveItemRepository.findById(reserveItemId)
.switchIfEmpty(monoResponseStatusEntityNotFoundException(reserveItemId))
.map(reserveItem -> reserveItem.updateIsUse(isUse))
.flatMap(reserveItemRepository::save)
.then();
}
/**
* 예약 신청(관리자) 시 재고 변경
*
* @param reserveItemId
* @param reserveQty
* @return
*/
public Mono<Boolean> updateInventory(Long reserveItemId, Integer reserveQty) {
return reserveItemRepository.findById(reserveItemId)
.switchIfEmpty(monoResponseStatusEntityNotFoundException(reserveItemId))
.flatMap(reserveItem -> {
int qty = reserveItem.getInventoryQty() - reserveQty;
if (qty < 0) {
return Mono.just(false);
}
return reserveItemRepository.save(reserveItem.updateInventoryQty(qty)).thenReturn(true);
});
}
/**
* 예약 신청(사용자) 시 재고 변경
*
* @param reserveItemId
* @param reserveQty
* @return
*/
public Mono<Void> updateInventoryThenSendMessage(Long reserveItemId, Integer reserveQty, String reserveId) {
return reserveItemRepository.findById(reserveItemId)
.switchIfEmpty(monoResponseStatusEntityNotFoundException(reserveItemId))
.flatMap(reserveItem -> {
if (!Category.EDUCATION.isEquals(reserveItem.getCategoryId())) {
return Mono.error(new BusinessMessageException("저장할 수 없습니다."));
}
LocalDateTime now = LocalDateTime.now();
if (!(now.isAfter(reserveItem.getRequestStartDate()) && now.isBefore(reserveItem.getRequestEndDate()))) {
return Mono.error(new BusinessMessageException("예약 가능 일자가 아닙니다."));
}
int qty = reserveItem.getInventoryQty() - reserveQty;
if (qty < 0) {
return Mono.error(new BusinessMessageException("재고가 없습니다."));
}
return Mono.just(reserveItem.updateInventoryQty(qty));
})
.flatMap(reserveItemRepository::save)
.publishOn(Schedulers.boundedElastic())
.doOnNext(reserveItem -> {
log.info("reserve item inventory updated success");
sendMessage(reserveId, true);
})
.doOnError(throwable -> {
log.info("reserve item inventory updated fail = {}", throwable.getMessage());
sendMessage(reserveId, false);
}).then();
}
/**
* 재고 변경 성공 여부 이벤트 발생
*
* @param reserveId
* @param isItemUpdated
*/
private void sendMessage(String reserveId, Boolean isItemUpdated) {
streamBridge.send("inventoryUpdated-out-0",
MessageBuilder.withPayload(
RequestMessage.builder()
.reserveId(reserveId)
.isItemUpdated(isItemUpdated)
.build())
.setHeader("reserveUUID", reserveId).build());
}
/**
* 한건 조회 - 연관된 데이터도 같이 조회 (e.g. codename, location)
*
* @param reserveItemId
* @return
*/
public Mono<ReserveItemRelationResponseDto> findByIdWithRelations(Long reserveItemId) {
return reserveItemRepository.findWithRelation(reserveItemId)
.switchIfEmpty(monoResponseStatusEntityNotFoundException(reserveItemId))
.flatMap(reserveItem ->
Mono.just(ReserveItemRelationResponseDto.builder().entity(reserveItem).build()));
}
/**
* 각 카테고리별 최신 예약 물품 조회
* 파라미터로 받는 갯수만큼 조회한다.
*
* @param count 조회할 갯수 0:전체
* @return
*/
public Mono<Map<String, Collection<ReserveItemMainResponseDto>>> findLatest(Integer count) {
return reserveItemRepository.findCodeDetail(
RESERVE_CATEGORY_CODE)
.flatMap(code -> reserveItemRepository.findLatestByCategory(count, code.getCodeId()))
.map(reserveItem -> ReserveItemMainResponseDto.builder().entity(reserveItem).build())
.collectMultimap(reserveItem -> reserveItem.getCategoryName());
}
}

View File

@@ -0,0 +1,195 @@
package org.egovframe.cloud.reserveitemservice.validator;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.egovframe.cloud.reserveitemservice.validator.annotation.ReserveItemSaveValid;
import org.springframework.util.StringUtils;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.lang.reflect.Field;
import java.time.LocalDateTime;
/**
* org.egovframe.cloud.reserveitemservice.validator.ReserveItemSaveValidator
*
* 예약 물품 저장 시 validation check를 하기 위한 custom validator
*
* @author 표준프레임워크센터 shinmj
* @version 1.0
* @since 2021/09/13
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/13 shinmj 최초 생성
* </pre>
*/
@Slf4j
public class ReserveItemSaveValidator implements ConstraintValidator<ReserveItemSaveValid, Object> {
private String message;
@Override
public void initialize(ReserveItemSaveValid constraintAnnotation) {
message = constraintAnnotation.message();
}
/**
* 예약 물품 저장 시 비지니스 로직에 의한 validation check
*
* @param value
* @param context
* @return
*/
@SneakyThrows
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
boolean fieldValid = true;
// 운영 시작일, 종료일 체크
LocalDateTime operationStartDate = (LocalDateTime) getFieldValue(value, "operationStartDate");
LocalDateTime operationEndDate = (LocalDateTime) getFieldValue(value, "operationEndDate");
if (operationStartDate.isAfter(operationEndDate)) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate("시작일이 종료일 보다 큽니다.")
.addPropertyNode("operationStartDate")
.addConstraintViolation();
fieldValid = false;
}
String reserveMethodId = String.valueOf(getFieldValue(value, "reserveMethodId"));
//예약 방법이 '인터넷' 인경우
if ("internet".equals(reserveMethodId)) {
// 예약 구분 필수
if (isNull(value, "reserveMeansId")) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate("인터넷 예약인 경우 예약 구분은 필수입니다.")
.addPropertyNode("reserveMeansId")
.addConstraintViolation();
fieldValid = false;
}else {
String reserveMeansId = String.valueOf(getFieldValue(value, "reserveMeansId"));
//예약 구분이 실시간 인 경우
if ("realtime".equals(reserveMeansId)) {
// 예약 신청 기간 필수
if (isNull(value, "requestStartDate")) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate("인터넷 예약인 경우 예약 신청 시작 기간은 필수입니다.")
.addPropertyNode("requestStartDate")
.addConstraintViolation();
fieldValid = false;
} else if (isNull(value, "requestEndDate")) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate("인터넷 예약인 경우 예약 신청 종료 기간은 필수입니다.")
.addPropertyNode("requestEndDate")
.addConstraintViolation();
fieldValid = false;
}else {
LocalDateTime requestStartDate = (LocalDateTime) getFieldValue(value, "requestStartDate");
LocalDateTime requestEndDate = (LocalDateTime) getFieldValue(value, "requestEndDate");
if (requestStartDate.isAfter(requestEndDate)) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate("시작일이 종료일 보다 큽니다.")
.addPropertyNode("requestStartDate")
.addConstraintViolation();
fieldValid = false;
}
}
//기간 지정 필수
if (isNull(value, "isPeriod")) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate("인터넷 예약인 경우 기간 지정 여부는 필수입니다.")
.addPropertyNode("requestEndDate")
.addConstraintViolation();
fieldValid = false;
}else {
Boolean isPeriod = Boolean.valueOf(String.valueOf(getFieldValue(value, "isPeriod")));
// 기간 지정 가능인 경우 최대 얘약일 수 필수
if (isPeriod && isNull(value, "periodMaxCount")) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate("기간 지정이 가능인 경우 최대 예약 일수는 필수입니다.")
.addPropertyNode("periodMaxCount")
.addConstraintViolation();
fieldValid = false;
}
}
}else if ("external".equals(reserveMeansId)) {
//예약 구분이 외부 링크인 경우 외부 링크 url 필수
if (isNull(value, "externalUrl")) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate("예약 구분이 외부링크인 경우 외부링크 url 값은 필수입니다.")
.addPropertyNode("externalUrl")
.addConstraintViolation();
fieldValid = false;
}
}
}
} else if ("telephone".equals(reserveMethodId)) {
//예약 방법인 '전화'인 경우 contact 필수
if (isNull(value, "contact")) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate("전화예약인 경우 문의처는 필수입니다.")
.addPropertyNode("contact")
.addConstraintViolation();
fieldValid = false;
}
}else if ("visit".equals(reserveMethodId)) {
//예약 방법인 '방문'인 경우 주소 필수
if (isNull(value, "address")) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate("방문예약인 경우 주소는 필수입니다.")
.addPropertyNode("address")
.addConstraintViolation();
fieldValid = false;
}
}
// 유료인 경우 이용 요금 필수
Boolean isPaid = Boolean.valueOf(String.valueOf(getFieldValue(value, "isPaid")));
if (isPaid && isNull(value, "usageCost")) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate("유료인 경우 이용 요금은 필수입니다.")
.addPropertyNode("usageCost")
.addConstraintViolation();
fieldValid = false;
}
return fieldValid;
}
/**
* 해당하는 field의 값 조회
*
* @param object
* @param fieldName
* @return
* @throws NoSuchFieldException
* @throws IllegalAccessException
*/
private Object getFieldValue(Object object, String fieldName) throws NoSuchFieldException, IllegalAccessException {
Class<?> clazz = object.getClass();
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
return field.get(object);
}
/**
* 해당하는 Field가 null인지 체크
*
* @param object
* @param fieldName
* @return
* @throws NoSuchFieldException
* @throws IllegalAccessException
*/
private boolean isNull(Object object, String fieldName) throws NoSuchFieldException, IllegalAccessException {
Class<?> clazz = object.getClass();
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
return field.get(object) == null || !StringUtils.hasLength(String.valueOf(field.get(object)));
}
}

View File

@@ -0,0 +1,38 @@
package org.egovframe.cloud.reserveitemservice.validator.annotation;
import org.egovframe.cloud.reserveitemservice.validator.ReserveItemSaveValidator;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* org.egovframe.cloud.reserveitemservice.validator.annotation.ReserveItemSaveValid
*
* 예약 물품 저장 시 validation check를 하기 위한 custom annotation
*
* @author 표준프레임워크센터 shinmj
* @version 1.0
* @since 2021/09/13
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/13 shinmj 최초 생성
* </pre>
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = ReserveItemSaveValidator.class)
public @interface ReserveItemSaveValid {
String message() default "저장할 수 없습니다.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}

View File

@@ -0,0 +1,13 @@
spring:
application:
name: reserve-item-service
server:
port: 0
# config server actuator
management:
endpoints:
web:
exposure:
include: refresh, health, beans

View File

@@ -0,0 +1,5 @@
spring:
cloud:
config:
uri: http://localhost:8888
name: reserve-item-service

View File

@@ -0,0 +1,60 @@
-- location Table Create SQL
CREATE TABLE IF NOT EXISTS location
(
location_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '지역 id',
location_name VARCHAR(200) NULL COMMENT '지역 이름',
sort_seq SMALLINT(3) NULL COMMENT '정렬 순서',
use_at TINYINT(1) NULL DEFAULT 1 COMMENT '사용 여부',
created_by VARCHAR(255) NULL COMMENT '생성자',
create_date DATETIME NULL COMMENT '생성일',
last_modified_by VARCHAR(255) NULL COMMENT '수정자',
modified_date DATETIME NULL COMMENT '수정일',
PRIMARY KEY (location_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
ALTER TABLE location COMMENT '예약 지역';
-- reserve_item Table Create SQL
CREATE TABLE IF NOT EXISTS reserve_item
(
reserve_item_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '예약 물품 id',
reserve_item_name VARCHAR(200) NULL COMMENT '예약 물품 이름',
location_id BIGINT NULL COMMENT '지역 id',
category_id VARCHAR(20) NULL COMMENT '예약유형 - 공통코드 reserve-category',
total_qty BIGINT(18) NULL COMMENT '총 재고/수용인원 수',
inventory_qty BIGINT(18) NULL COMMENT '현재 남은 재고/수용인원 수',
operation_start_date DATETIME NULL COMMENT '운영 시작 일',
operation_end_date DATETIME NULL COMMENT '운영 종료 일',
reserve_method_id VARCHAR(20) NULL COMMENT '예약 방법 - 공통코드 reserve-method',
reserve_means_id VARCHAR(20) NULL COMMENT '예약 구분 (인터넷 예약 시) - 공통코드 reserve-means',
request_start_date DATETIME NULL COMMENT '예약 신청 시작 일시',
request_end_date DATETIME NULL COMMENT '예약 신청 종료 일시',
period_at TINYINT(1) NULL DEFAULT 0 COMMENT '기간 지정 가능 여부 - true: 지정 가능, false: 지정 불가',
period_max_count SMALLINT(3) NULL COMMENT '최대 예약 가능 일 수',
external_url VARCHAR(500) NULL COMMENT '외부링크',
selection_means_id VARCHAR(20) NULL COMMENT '선별 방법 - 공통코드 reserve-selection-means',
paid_at TINYINT(1) NULL DEFAULT 0 COMMENT '유/무료 - false: 무료, true: 유료',
usage_cost DECIMAL(18, 0) NULL COMMENT '이용 요금',
use_at TINYINT(1) NULL DEFAULT 1 COMMENT '사용 여부',
purpose_content VARCHAR(4000) NULL COMMENT '용도',
item_addr VARCHAR(500) NULL COMMENT '주소',
target_id VARCHAR(20) NULL COMMENT '이용 대상 - 공통코드 reserve-target',
excluded_content VARCHAR(2000) NULL COMMENT '사용허가 제외대상',
homepage_url VARCHAR(500) NULL COMMENT '홈페이지 url',
contact_no VARCHAR(50) NULL COMMENT '문의처',
manager_dept_name VARCHAR(200) NULL COMMENT '담당자 소속',
manager_name VARCHAR(200) NULL COMMENT '담당자 이름',
manager_contact_no VARCHAR(50) NULL COMMENT '담당자 연락처',
create_date DATETIME NULL COMMENT '생성일',
created_by VARCHAR(255) NULL COMMENT '생성자',
modified_date DATETIME NULL COMMENT '수정일',
last_modified_by VARCHAR(255) NULL COMMENT '수정자',
PRIMARY KEY (reserve_item_id),
CONSTRAINT FK_reserve_item_location_id FOREIGN KEY (location_id)
REFERENCES location (location_id) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
ALTER TABLE reserve_item COMMENT '예약 물품';

View File

@@ -0,0 +1,13 @@
package org.egovframe.cloud.reserveitemservice;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class ReserveItemServiceApplicationTests {
@Test
void contextLoads() {
}
}

View File

@@ -0,0 +1,144 @@
package org.egovframe.cloud.reserveitemservice.api.location;
import org.egovframe.cloud.reserveitemservice.api.location.dto.LocationSaveRequestDto;
import org.egovframe.cloud.reserveitemservice.api.location.dto.LocationUpdateRequestDto;
import org.egovframe.cloud.reserveitemservice.config.R2dbcConfig;
import org.egovframe.cloud.reserveitemservice.domain.location.Location;
import org.egovframe.cloud.reserveitemservice.domain.location.LocationRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentMatchers;
import org.mockito.BDDMockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.data.domain.Pageable;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.web.reactive.function.BodyInserters;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@EnableConfigurationProperties
@TestPropertySource(properties = {"spring.config.location=classpath:application-test.yml"})
@ActiveProfiles("test")
@Import({R2dbcConfig.class})
class LocationApiControllerTest {
@MockBean
private LocationRepository locationRepository;
@Autowired
private WebTestClient webTestClient;
private final static String API_URL = "/api/v1/locations";
private Location location = Location.builder()
.locationId(1L)
.locationName("location")
.isUse(true)
.sortSeq(1)
.build();
@BeforeEach
public void setup() {
BDDMockito.when(locationRepository.findById(ArgumentMatchers.anyLong()))
.thenReturn(Mono.just(location));
//조회조건 있는 경우
BDDMockito.when(locationRepository.findAllByLocationNameContainingOrderBySortSeq(
ArgumentMatchers.anyString(), ArgumentMatchers.any(Pageable.class)))
.thenReturn(Flux.just(location));
BDDMockito.when(locationRepository.countAllByLocationNameContaining(ArgumentMatchers.anyString()))
.thenReturn(Mono.just(1L));
//조회조건 없는 경우
BDDMockito.when(locationRepository.findAllByOrderBySortSeq(ArgumentMatchers.any(Pageable.class)))
.thenReturn(Flux.just(location));
BDDMockito.when(locationRepository.count()).thenReturn(Mono.just(1L));
BDDMockito.when(locationRepository.save(ArgumentMatchers.any(Location.class)))
.thenReturn(Mono.just(location));
}
@Test
public void 한건조회_성공() throws Exception {
webTestClient.get()
.uri(API_URL+"/{locationId}", location.getLocationId())
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.locationName").isEqualTo(location.getLocationName());
}
@Test
public void 조회조건있는경우_페이지목록조회_성공() throws Exception {
webTestClient.get()
.uri(API_URL+"?keywordType=locationName&keyword=location&page=0&size=3")
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.totalElements").isEqualTo(1)
.jsonPath("$.content[0].locationName").isEqualTo(location.getLocationName());
}
@Test
public void 조회조건없는경우_페이지목록조회_성공() throws Exception {
webTestClient.get()
.uri(API_URL+"?page=0&size=3")
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.totalElements").isEqualTo(1)
.jsonPath("$.content[0].locationName").isEqualTo(location.getLocationName());
}
@Test
public void 한건저장_성공() throws Exception {
webTestClient.post()
.uri(API_URL)
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(LocationSaveRequestDto.builder()
.locationName(location.getLocationName())
.isUse(location.getIsUse())
.sortSeq(location.getSortSeq())
.build()))
.exchange()
.expectStatus().isCreated()
.expectBody().jsonPath("$.locationName").isEqualTo(location.getLocationName());
}
@Test
public void 한건수정_성공() throws Exception {
webTestClient.put()
.uri(API_URL+"/{locationId}", location.getLocationId())
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(LocationUpdateRequestDto.builder()
.locationName("updateLocation")
.isUse(location.getIsUse())
.sortSeq(location.getSortSeq())
.build()))
.exchange()
.expectStatus().isNoContent();
}
@Test
public void 한건삭제_참조데이터존재_삭제실패() throws Exception {
BDDMockito.when(locationRepository.delete(ArgumentMatchers.any(Location.class)))
.thenReturn(Mono.error(new DataIntegrityViolationException("integrity test")));
webTestClient.delete()
.uri(API_URL+"/{locationId}", 1L)
.exchange()
.expectStatus().isBadRequest();
}
}

View File

@@ -0,0 +1,164 @@
package org.egovframe.cloud.reserveitemservice.api.reserveItem;
import java.time.LocalDateTime;
import java.util.Arrays;
import org.egovframe.cloud.reserveitemservice.api.reserveItem.dto.ReserveItemRequestDto;
import org.egovframe.cloud.reserveitemservice.config.R2dbcConfig;
import org.egovframe.cloud.reserveitemservice.domain.code.Code;
import org.egovframe.cloud.reserveitemservice.domain.location.Location;
import org.egovframe.cloud.reserveitemservice.domain.reserveItem.ReserveItem;
import org.egovframe.cloud.reserveitemservice.domain.reserveItem.ReserveItemRepository;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentMatchers;
import org.mockito.BDDMockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import org.springframework.data.domain.Pageable;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.web.reactive.server.WebTestClient;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@EnableConfigurationProperties
@TestPropertySource(properties = {"spring.config.location=classpath:application-test.yml"})
@ActiveProfiles(profiles = "test")
@Import({R2dbcConfig.class})
class ReserveItemApiControllerTest {
@MockBean
ReserveItemRepository reserveItemRepository;
@Autowired
WebTestClient webTestClient;
private final static String API_URL = "/api/v1/reserve-items";
@Test
public void 사용자별_검색_목록_조회_성공() throws Exception {
LocalDateTime startDate = LocalDateTime.of(2021, 1, 28, 1,1);
LocalDateTime endDate = LocalDateTime.of(2021, 12, 6, 1,1);
Location location = Location.builder()
.locationId(1L)
.locationName("location")
.sortSeq(1)
.isUse(true)
.build();
ReserveItem reserveItem = ReserveItem.builder()
.reserveItemId(1L)
.location(location)
.locationId(location.getLocationId())
.reserveItemName("test")
.categoryId("education")
.categoryName("교육")
.totalQty(100)
.inventoryQty(80)
.operationEndDate(endDate)
.operationStartDate(startDate)
.isPeriod(false)
.isUse(true)
.build();
BDDMockito.when(reserveItemRepository.searchForUser(ArgumentMatchers.anyString(),
ArgumentMatchers.any(ReserveItemRequestDto.class), ArgumentMatchers.any(Pageable.class)))
.thenReturn(Flux.just(reserveItem));
BDDMockito.when(reserveItemRepository.searchCountForUser(ArgumentMatchers.anyString(),
ArgumentMatchers.any(ReserveItemRequestDto.class), ArgumentMatchers.any(Pageable.class)))
.thenReturn(Mono.just(1L));
webTestClient.get()
.uri("/api/v1/{categoryId}/reserve-items?keywordType=locationName&keyword=location&page=0&size=3", "education")
.exchange()
.expectStatus().isOk();
}
@Test
public void main_예약물품조회_성공() throws Exception {
BDDMockito.when(reserveItemRepository.findCodeDetail(ArgumentMatchers.anyString()))
.thenReturn(Flux.fromIterable(Arrays.asList(Code.builder().codeId("education").codeName("교육").build(),
Code.builder().codeId("equipment").codeName("장비").build(),
Code.builder().codeId("space").codeName("장소").build())));
LocalDateTime startDate = LocalDateTime.of(2021, 1, 28, 1,1);
LocalDateTime endDate = LocalDateTime.of(2021, 12, 6, 1,1);
Location location = Location.builder()
.locationId(1L)
.locationName("location")
.sortSeq(1)
.isUse(true)
.build();
ReserveItem reserveItem1 = ReserveItem.builder()
.reserveItemId(1L)
.location(location)
.locationId(location.getLocationId())
.reserveItemName("test")
.categoryId("education")
.categoryName("교육")
.totalQty(100)
.inventoryQty(80)
.operationEndDate(endDate)
.operationStartDate(startDate)
.reserveMethodId("visit")
.isPeriod(false)
.isUse(true)
.build();
ReserveItem reserveItem2 = ReserveItem.builder()
.reserveItemId(1L)
.location(location)
.locationId(location.getLocationId())
.reserveItemName("test")
.categoryId("education")
.categoryName("장비")
.totalQty(100)
.inventoryQty(80)
.operationEndDate(endDate)
.operationStartDate(startDate)
.reserveMethodId("visit")
.isPeriod(false)
.isUse(true)
.build();
ReserveItem reserveItem3 = ReserveItem.builder()
.reserveItemId(1L)
.location(location)
.locationId(location.getLocationId())
.reserveItemName("test")
.categoryId("education")
.categoryName("공간")
.totalQty(100)
.inventoryQty(80)
.operationEndDate(endDate)
.operationStartDate(startDate)
.reserveMethodId("visit")
.isPeriod(false)
.isUse(true)
.build();
reserveItem1.setCreateDate(LocalDateTime.now());
reserveItem2.setCreateDate(LocalDateTime.now());
reserveItem3.setCreateDate(LocalDateTime.now());
BDDMockito.when(reserveItemRepository.findLatestByCategory(ArgumentMatchers.anyInt(), ArgumentMatchers.anyString()))
.thenReturn(Flux.fromIterable(Arrays.asList(reserveItem1, reserveItem2, reserveItem3)));
webTestClient.get()
.uri("/api/v1/reserve-items/latest/3")
.exchange()
.expectStatus().isOk();
}
}

View File

@@ -0,0 +1,40 @@
package org.egovframe.cloud.reserveitemservice.config;
import io.r2dbc.h2.H2ConnectionConfiguration;
import io.r2dbc.h2.H2ConnectionFactory;
import io.r2dbc.h2.H2ConnectionOption;
import io.r2dbc.spi.ConnectionFactory;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Profile;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.r2dbc.config.AbstractR2dbcConfiguration;
import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories;
import org.springframework.r2dbc.connection.init.CompositeDatabasePopulator;
import org.springframework.r2dbc.connection.init.ConnectionFactoryInitializer;
import org.springframework.r2dbc.connection.init.ResourceDatabasePopulator;
@Profile("test")
@TestConfiguration
@EnableR2dbcRepositories
public class R2dbcConfig{
@Bean
public H2ConnectionFactory connectionFactory() {
return new H2ConnectionFactory(H2ConnectionConfiguration.builder()
.tcp("localhost", "~/querydsl")
.property(H2ConnectionOption.DB_CLOSE_DELAY, "-1")
.username("sa")
.build());
}
@Bean
public ConnectionFactoryInitializer initializer(ConnectionFactory connectionFactory) {
ConnectionFactoryInitializer initializer = new ConnectionFactoryInitializer();
initializer.setConnectionFactory(connectionFactory);
CompositeDatabasePopulator populator = new CompositeDatabasePopulator();
populator.addPopulators(new ResourceDatabasePopulator(new ClassPathResource("schema-h2.sql")));
initializer.setDatabasePopulator(populator);
return initializer;
}
}

View File

@@ -0,0 +1,44 @@
spring:
application:
name: reserve-item-service
datasource:
url: jdbc:h2:mem:testdb;MODE=MYSQL;DB_CLOSE_DELAY=-1;DATABASE_TO_UPPER=false
username: sa
password:
driver-class-name: org.h2.Driver
jpa:
hibernate:
generate-ddl: true
ddl-auto: create-drop
properties:
hibernate:
format_sql: true
default_batch_fetch_size: 1000
show-sql: true
h2:
console:
enabled: true
path: /h2
logging.level:
org.hibernate.SQL: debug
file:
directory: ${user.home}/msa-attach-volume
messages:
directory: ${file.directory}/messages
# jwt token
token:
secret: egovframe_user_token
# ftp server
ftp:
enabled: false # ftp 사용 여부, FTP 서버에 최상위 디렉토리 자동 생성 및 구현체를 결정하게 된다.
# eureka 가 포함되면 eureka server 도 등록되므로 해제한다.
eureka:
client:
register-with-eureka: false
fetch-registry: false

View File

@@ -0,0 +1,19 @@
spring:
application:
name: reserve-item-service
logging:
level:
org:
springramework:
data:
r2dbc: DEBUG
file:
location: ${user.home}/msa-attach-volume
# jwt token
token:
secret: egovframe_user_token

View File

@@ -0,0 +1,5 @@
spring:
cloud:
config:
uri: http://localhost:8888
name: reserve-item-service

View File

@@ -0,0 +1,49 @@
CREATE TABLE IF NOT EXISTS location(
location_id BIGINT AUTO_INCREMENT,
location_name VARCHAR(200),
use_at tinyint(1) default 1 null,
sort_seq smallint(3) null,
create_date DATE null,
modified_date DATE null,
created_by VARCHAR(255) null,
last_modified_by VARCHAR(255) null,
CONSTRAINT PERSON_PK PRIMARY KEY (location_id)
);
-- reserve_item Table Create SQL
CREATE TABLE IF NOT EXISTS reserve_item
(
reserve_item_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '예약 물품 id',
reserve_item_name VARCHAR(200) NULL COMMENT '예약 물품 이름',
location_id BIGINT NULL COMMENT '지역 id',
category_id VARCHAR(20) NULL COMMENT '예약유형 - 공통코드 reserve-category',
capacity_count MEDIUMINT(5) NULL COMMENT '재고/수용인원 수',
operation_start_date DATETIME NULL COMMENT '운영 시작 일',
operation_end_date DATETIME NULL COMMENT '운영 종료 일',
reserve_method_id VARCHAR(20) NULL COMMENT '예약 방법 - 공통코드 reserve-method',
reserve_means_id VARCHAR(20) NULL COMMENT '예약 구분 (인터넷 예약 시) - 공통코드 reserve-means',
request_start_date DATETIME NULL COMMENT '예약 신청 시작 일시',
request_end_date DATETIME NULL COMMENT '예약 신청 종료 일시',
period_at TINYINT(1) NULL DEFAULT 0 COMMENT '기간 지정 가능 여부 - true: 지정 가능, false: 지정 불가',
period_max_count SMALLINT(3) NULL COMMENT '최대 예약 가능 일 수',
external_url VARCHAR(500) NULL COMMENT '외부링크',
selection_means_id VARCHAR(20) NULL COMMENT '선별 방법 - 공통코드 reserve-selection-means',
free_at TINYINT(1) NULL DEFAULT 1 COMMENT '유/무료 - true: 무료, false: 유료',
usage_cost DECIMAL(18, 0) NULL COMMENT '이용 요금',
use_at TINYINT(1) NULL DEFAULT 1 COMMENT '사용 여부',
purpose_content VARCHAR(4000) NULL COMMENT '용도',
item_addr VARCHAR(500) NULL COMMENT '주소',
target_id VARCHAR(20) NULL COMMENT '이용 대상 - 공통코드 reserve-target',
excluded_content VARCHAR(2000) NULL COMMENT '사용허가 제외대상',
homepage_url VARCHAR(500) NULL COMMENT '홈페이지 url',
contact_no VARCHAR(50) NULL COMMENT '문의처',
manager_dept_name VARCHAR(200) NULL COMMENT '담당자 소속',
manager_name VARCHAR(200) NULL COMMENT '담당자 이름',
manager_contact_no VARCHAR(50) NULL COMMENT '담당자 연락처',
create_date DATETIME NULL COMMENT '생성일',
created_by VARCHAR(255) NULL COMMENT '생성자',
modified_date DATETIME NULL COMMENT '수정일',
last_modified_by VARCHAR(255) NULL COMMENT '수정자',
PRIMARY KEY (reserve_item_id)
);