사용자가 대기열에 등록하고 대기 상태를 업데이트하며, 대기 정보를 확인할 수 있도록 합니다.
구현된 줄서기 및 현장 접수 기능 요약하면…
QueueInfoDto.of()를 사용하지 않고 직접 생성자를 호출하는 경우에는 DTO 생성 로직의 중복과 QueueInfoDto 클래스의 생성자 매개변수가 변경될 경우, 해당 클래스를 사용하는 모든 곳에서 변경 사항을 반영해야하기 때문에 유지보수에 어려움이 있습니다.
또한, 생성자 대신에 정적 팩토리 메서드를 사용하여 객체를 생성하는 것은 보다 간단하고 직관적입니다. 특히 QueueInfoDto 객체를 생성할 때 매개변수가 많기 때문에 static 메서드를 활용하고자 했습니다.
즉, 생성자를 직접 호출하는 대신 QueueInfoDto.of() 메서드를 사용함으로써 객체 생성의 구체적인 구현을 숨기고, 추상화된 인터페이스를 제공하였습니다.
@Override
@Transactional(readOnly = true)
public List<QueueInfoDto> getQueueInfoByMemberId(Long memberId) {
Member member = memberService.findById(memberId)
.orElseThrow(() -> new IllegalArgumentException("해당 회원 ID를 찾을 수 없습니다. ID: " + memberId));
List<RegisterChart> registerCharts = findRegisterChartsByMember(member);
return registerCharts.stream()
.map(this::createQueueInfoFromRegisterChartDto)
.collect(Collectors.toList());
}
@Override
@Transactional(readOnly = true)
public QueueInfoDto getQueueInfo(Long hospitalId, Long subjectId) {
Admin admin = findAdmin(hospitalId, subjectId);
Member currentUser = getCurrentUser();
Subject subject = findSubject(subjectId);
Hospital hospital = findHospital(hospitalId);
Optional<RegisterChart> optRegisterChart = Optional.ofNullable(findRegisterChart(admin, currentUser));
return optRegisterChart.map(rc -> createQueueInfoDto(rc, subject, hospital, admin))
.orElseGet(() -> createQueueInfoWithoutRegisterChart(subject, hospital, admin));
}
private QueueInfoDto createQueueInfoDto(RegisterChart registerChart, Subject subject, Hospital hospital, Admin admin) {
Optional<RegisterChart> optRegisterChart = Optional.ofNullable(registerChart);
Long waitingCount = optRegisterChart.map(rc -> calculateCurrentUserWaitingIndex(hospital.getId(), subject.getId(), rc)).orElse(null);
String waitingStatus = optRegisterChart.map(rc -> waitingStatus(waitingCount, rc)).orElse(null);
Long expectedWaitingTime = optRegisterChart.map(chart -> calculateExpectedWaitingTime(waitingCount)).orElse(null);
return QueueInfoDto.of(registerChart, subject, hospital, admin, waitingCount, expectedWaitingTime, waitingStatus);
}
private QueueInfoDto createQueueInfoWithoutRegisterChart(Subject subject, Hospital hospital, Admin admin) {
Long waitingCount = calculateTotalWaitingCount(hospital.getId(), subject.getId());
Long expectedWaitingTime = calculateExpectedWaitingTime(waitingCount);
return QueueInfoDto.of(null, subject, hospital, admin, waitingCount, expectedWaitingTime, waitingCount.toString());
}
비즈니스 로직 구현
getQueueInfoByMemberId() : 회원 ID를 이용하여 해당 회원의 등록 차트 목록을 조회하고, 각 등록 차트에 대한 QueueInfoDto 객체를 생성하여 리스트로 반환합니다
getQueueInfo() : 병원 ID와 진료 과목 ID를 이용하여 현재 사용자의 등록 차트를 조회하고, 해당 등록 차트가 없는 경우에 대한 대기열 정보를 생성합니다. 이때도 createQueueInfoDto() 메서드와 createQueueInfoWithoutRegisterChart() 메서드에서 QueueInfoDto.of() 메서드를 사용하여 QueueInfoDto 객체를 생성합니다.
createQueueInfoFromRegisterChartDto() : 각 등록 차트에서 QueueInfoDto 객체를 생성하는데, 이때 QueueInfoDto.of() 메서드를 사용합니다.
A. 대기열 조회
/members/subjects/{subject-id}/hospitals/{hospital-id}/queues 엔드포인트에 GET 요청을 보냄.RegisterChartController의 showQueue 메서드가 호출됨.registerChartService를 사용하여 병원과 과목에 대한 대기열 정보를 조회함.QueueInfoDto 객체에 매핑되고, ModelAndView 객체에 추가됨.ModelAndView 객체는 "members/queues" 페이지로 전달됨.B. 대기열 등록
/members/subjects/{subject-id}/hospitals/{hospital-id}/queues 엔드포인트에 POST 요청을 보냄.RegisterChartController의 createRegister 메서드가 호출됨.registerChartService를 사용하여 대기열에 사용자를 등록함.C. 대기열 삭제
/members/subjects/{subject-id}/hospitals/{hospital-id}/queues 엔드포인트에 DELETE 요청을 보냄.RegisterChartController의 deleteRegister 메서드가 호출됨.registerChartService를 사용하여 대기열에서 사용자를 삭제함./members 페이지로 리디렉션됨. 그렇지 않으면 오류 메시지와 함께 HTTP 400 에러 응답을 받음.D. WebSocket을 통한 실시간 업데이트
RegisterChartDto 객체를 생성하고 WebSocket 메시지를 통해 클라이언트에게 전송함.A. 줄서기 조회 및 생성
사용자는 대기열을 조회하거나 새로운 대기열을 생성할 수 있습니다.
findByAdminIdAndMemberIdAndIsDeletedFalse: 특정 관리자와 회원의 대기열을 조회할 수 있습니다. 만약 해당하는 대기열이 없으면 빈Optional을 반환합니다.
getRegisterChartsByMemberIdAndStatus: 특정 회원의 대기열을 조회할 수 있습니다. 대기열 상태가COMPLETE인 것만 가져와서RegisterChartDto로 매핑하여 반환합니다.
registerToChart: 새로운 대기열을 등록합니다. 중복된 대기열이나 예약된 상태인 경우에는 추가하지 않습니다.
B. 줄서기 업데이트 및 처리
대기열의 상태를 업데이트하거나 처리할 수 있습니다.
insert(Long hospitalId, Long subjectId): 회원이 특정 병원과 진료 과목에 대한 대기열을 등록합니다.
updateStatusByAdminAndMember(Admin admin, Long memberId, RegisterChartStatus status): 관리자와 회원 ID, 대기열 상태를 기반으로 대기열의 상태를 업데이트합니다. 상태가 취소 또는 완료인 경우 삭제 플래그를 설정합니다.
processRegisterChart(Long hospitalId, Long subjectId): 특정 병원과 진료 과목에 대한 대기열을 처리합니다(삭제 및 상태 업데이트).대기열을 삭제하고 취소되지 않은 경우에는 상태를 "CANCEL"로 변경합니다.
C. 대기 현황(대기 인원 및 대기 시간)
대기열과 관련된 다양한 정보를 가져올 수 있습니다.
메인 페이지에서는 회원 ID를 기반으로 정보를 가져오고, 줄서기 등록 페이지에서는 특정 병원과 진료과목을 기반으로 정보를 가져옵니다.
getMedicalCharts메서드를 사용하여 현재 사용자의 대기열과 예약 목록을 시간순으로 정렬하여 반환합니다.
getQueueInfoByMemberId(Long memberId): 메인페이지_회원 ID를 기반으로 대기열 정보를 조회합니다.
getQueueInfo(Long hospitalId, Long subjectId): 줄서기 등록 페이지_특정 병원과 진료 과목에 대한 대기열 정보를 조회합니다.
조금 더 자세히 대기 인원 및 시간의 비지니스 로직을 작성해보면 아래와 같습니다.
대기 현황 표시: 사용자가 해당 병원과 진료 과목에서의 대기 현황을 조회할 수 있습니다. 대기 중인 사용자의 대기 순번과 상태를 확인할 수 있습니다.
1. getQueueInfo: 사용자가 대기 현황을 조회할 때 호출되는 메서드입니다. 병원, 진료 과목, 현재 사용자 정보를 받아와 등록 차트를 검색하고, 대기 현황을 생성하여 QueueInfoDto 객체를 반환합니다.
2. createQueueInfoDto: 등록 차트가 있는 경우 대기 순번과 대기 상태, 예상 대기 시간을 계산하여 QueueInfoDto 객체를 생성합니다.
3. calculateCurrentUserWaitingIndex: 대기 순번을 계산하는 메서드입니다. 해당 병원과 진료 과목에 대한 대기 중인 등록 차트 목록을 가져와 현재 사용자의 등록 차트가 몇 번째로 대기 중인지를 계산해 대기 순번을 계산합니다.
@Override
@Transactional(readOnly = true)
public QueueInfoDto getQueueInfo(Long hospitalId, Long subjectId) {
// 병원과 진료 과목에 대한 관리자 정보 검색
Admin admin = findAdmin(hospitalId, subjectId);
// 현재 사용자 정보 검색
Member currentUser = getCurrentUser();
// 진료 과목 정보 검색
Subject subject = findSubject(subjectId);
// 병원 정보 검색
Hospital hospital = findHospital(hospitalId);
// 해당 관리자와 사용자 정보를 사용하여 등록 차트 검색
Optional<RegisterChart> optRegisterChart = Optional.ofNullable(findRegisterChart(admin, currentUser));
// 등록 차트가 존재하는 경우
return optRegisterChart.map(rc -> createQueueInfoDto(rc, subject, hospital, admin))
// 등록 차트가 없는 경우
.orElseGet(() -> createQueueInfoWithoutRegisterChart(subject, hospital, admin));
}
/**
* 등록 차트가 있는 경우 대기 현황 및 대기 시간을 계산하여 QueueInfoDto를 생성합니다.
*
* @param registerChart 등록 차트
* @param subject 진료 과목
* @param hospital 병원
* @param admin 관리자
* @return 대기 현황 정보를 포함한 QueueInfoDto 객체
*/
private QueueInfoDto createQueueInfoDto(RegisterChart registerChart, Subject subject, Hospital hospital, Admin admin) {
// 등록 차트의 존재 여부를 Optional로 wrapping
Optional<RegisterChart> optRegisterChart = Optional.ofNullable(registerChart);
// 대기 순번 계산
Long waitingCount = optRegisterChart.map(rc -> calculateCurrentUserWaitingIndex(hospital.getId(), subject.getId(), rc)).orElse(null);
// 대기 상태 결정
String waitingStatus = optRegisterChart.map(rc -> waitingStatus(waitingCount, rc)).orElse(null);
// 예상 대기 시간 계산
Long expectedWaitingTime = optRegisterChart.map(chart -> calculateExpectedWaitingTime(waitingCount)).orElse(null);
// QueueInfoDto 객체 생성 및 반환
return QueueInfoDto.of(registerChart, subject, hospital, admin, waitingCount, expectedWaitingTime, waitingStatus);
}
/**
* 대기 순번을 계산합니다.
*
* @param hospitalId 병원 ID
* @param subjectId 진료 과목 ID
* @param registerChart 등록 차트
* @return 대기 순번
*/
private Long **calculateCurrentUserWaitingIndex**(Long hospitalId, Long subjectId, RegisterChart registerChart) {
// 해당 병원과 진료 과목에 대한 대기 중인 등록 차트 목록 검색
List<RegisterChart> registerCharts = registerChartRepository.findByAdminHospitalIdAndAdminSubjectIdAndStatusInOrderByTimeAsc(hospitalId, subjectId, Arrays.asList(RegisterChartStatus.WAITING, RegisterChartStatus.ENTER));
// 현재 사용자의 대기 순번 계산
int currentUserIndex = registerCharts.indexOf(registerChart);
// 대기 순번 반환
return (currentUserIndex != -1) ? Long.valueOf(currentUserIndex) : Long.valueOf(registerCharts.size());
}
현재 대기 상태:
- 현재 대기 상태 : 대기 중인 사용자의 대기 순번이 0이면 "내 차례"로 표시하고, 그렇지 않으면 대기 순번을 문자열로 반환합니다.
getQueueInfo 메서드에서는 병원, 진료 과목, 현재 사용자 정보를 가져옵니다.
2. findRegisterChart 메서드를 사용하여 현재 사용자의 등록 차트를 찾습니다.
3. Optional을 사용하여 등록 차트가 존재하는지 확인하고, 대기 정보를 생성합니다.
4. 대기 정보를 생성하는 동안에는 대기 순번, 대기 상태, 예상 대기 시간을 계산합니다.
5. isRegisterChartWaitingOrEnter(RegisterChart registerChart) 메서드는 RegisterChart 객체의 상태가 대기 중인지 여부를 확인합니다. 이 정보는 대기 중인 회원 수를 나타내는 변수에 따라 내 차례인지 혹은 대기 중인 회원 수를 표시합니다.
6. 마지막으로 생성된 대기 정보를 포함하는 QueueInfoDto 객체를 반환합니다.
@Override
@Transactional(readOnly = true)
public QueueInfoDto getQueueInfo(Long hospitalId, Long subjectId) {
// 병원, 진료 과목, 현재 사용자 정보를 가져옵니다.
Admin admin = findAdmin(hospitalId, subjectId);
Member currentUser = getCurrentUser();
Subject subject = findSubject(subjectId);
Hospital hospital = findHospital(hospitalId);
// 현재 사용자의 등록 차트를 찾습니다.
Optional<RegisterChart> optRegisterChart = Optional.ofNullable(findRegisterChart(admin, currentUser));
// Optional을 사용하여 등록 차트가 존재하는지 확인하고 대기 정보를 생성합니다.
return optRegisterChart.map(rc -> createQueueInfoDto(rc, subject, hospital, admin))
.orElseGet(() -> createQueueInfoWithoutRegisterChart(subject, hospital, admin));
}
// 대기 정보를 생성하는 메서드
private QueueInfoDto createQueueInfoDto(RegisterChart registerChart, Subject subject, Hospital hospital, Admin admin) {
// Optional을 사용하여 registerChart가 null인지 확인합니다.
Optional<RegisterChart> optRegisterChart = Optional.ofNullable(registerChart);
// 대기 순번을 계산합니다.
Long waitingCount = optRegisterChart.map(rc -> calculateCurrentUserWaitingIndex(hospital.getId(), subject.getId(), rc)).orElse(null);
// 대기 상태를 결정합니다.
String waitingStatus = optRegisterChart.map(rc -> waitingStatus(waitingCount, rc)).orElse(null);
// 예상 대기 시간을 계산합니다.
Long expectedWaitingTime = optRegisterChart.map(chart -> calculateExpectedWaitingTime(waitingCount)).orElse(null);
// QueueInfoDto 객체를 생성하여 반환합니다.
return QueueInfoDto.of(registerChart, subject, hospital, admin, waitingCount, expectedWaitingTime, waitingStatus);
}
// 대기 상태를 결정하는 메서드
private String waitingStatus(Long waitingCount, RegisterChart registerChart) {
// 대기 중인지 또는 진료 중인지 여부를 확인합니다.
return isRegisterChartWaitingOrEnter(registerChart) ? (waitingCount == 0 ? "내 차례" : waitingCount.toString()) : waitingCount.toString();
}
// 대기 중인지 또는 진료 중인지 여부를 확인하는 메서드
private boolean isRegisterChartWaitingOrEnter(RegisterChart registerChart) {
// 등록 차트가 null이 아니고 대기 중 또는 진료 중인지 확인합니다.
return registerChart != null && (registerChart.getStatus() == RegisterChartStatus.WAITING || registerChart.getStatus() == RegisterChartStatus.ENTER);
}
calculateTotalWaitingCount: 해당 병원과 진료 과목에 대한 모든 대기 중인 등록 차트의 수를 계산합니다.private Long calculateTotalWaitingCount(Long hospitalId, Long subjectId) {
List<RegisterChart> registerCharts = registerChartRepository.findByAdminHospitalIdAndAdminSubjectIdAndStatusInOrderByTimeAsc(hospitalId, subjectId, Arrays.asList(RegisterChartStatus.WAITING, RegisterChartStatus.ENTER));
return (long) registerCharts.size();
}calculateExpectedWaitingTime: 대기 중인 등록 차트의 수를 기반으로 예상 대기 시간을 계산합니다. 예상 대기 시간은 각 등록 차트가 처리되는 데 걸리는 시간의 총합으로 계산됩니다.private Long calculateExpectedWaitingTime(Long waitingCount) {
return (waitingCount != null) ? waitingCount * 5 : null;
}D. 회원 등록 및 유효성 검사
현장접수는 관리자에 의해 이루어집니다.
registerNewMember(String providerType, String username, String tempPassword, String email): 새로운 회원을 등록합니다.
registerNewUser(OnsiteRegisterDto onsiteRegisterDto): 새로운 사용자를 등록합니다.
validateAndCreateMember(String providerType, String username, String email, String tempPassword): 회원의 유효성을 검사하고 새로운 회원을 생성합니다.
E. 기타 유틸리티 메서드
- 데이터 유효성 검사, 시간 계산, 정렬 등의 기능을 수행하는 유틸리티 메서드가 구현되어 있습니다.
줄서기 기능 요약
- 회원이 병원에 방문할 때 대기열에 등록: 회원이 병원에 방문하면, 해당 병원과 진료 과목에 대한 관리자를 찾아와야 합니다. 이후에 회원과 관리자 간에 대기열을 관리하는
RegisterChart객체를 생성합니다.- 중복 접수 방지: 회원이 이미 해당 병원과 관련된 진료 과목에 대기 중이거나 진료를 받고 있다면, 중복 접수를 방지하기 위해 추가 접수를 거부합니다.
- 대기 중인 회원 수 및 대기 시간 표시: 대기 중인 회원의 수와 대기 시간을 계산하여 회원에게 제공합니다. 이를 통해 회원은 현재 대기 중인 상태를 알고, 예상 대기 시간을 예측할 수 있습니다.
- 진료가 완료되면 대기열에서 제거: 회원의 진료가 완료되면, 해당 회원을 대기열에서 제거합니다. 이를 통해 대기열이 항상 최신 상태로 유지됩니다.
@Service
@RequiredArgsConstructor
public class RegisterChartServiceImpl implements RegisterChartService {
private final RegisterChartRepository registerChartRepository;
private final AdminService adminService;
private final MemberService memberService;
private final ReservationService reservationService;
private final HospitalService hospitalService;
private final SubjectService subjectService;
private final Rq rq;
// 특정 관리자와 회원에 대한 등록 차트를 찾는 메서드
// 관리자 ID와 회원 ID를 기반으로 삭제되지 않은 등록 차트를 찾는 메서드입니다.
// 해당 정보가 있는 경우에는 Optional로 해당 등록 차트를 반환합니다.
@Transactional(readOnly = true)
public Optional<RegisterChart> **findByAdminIdAndMemberIdAndIsDeletedFalse**(Long adminId, Long memberId){
return registerChartRepository.findByAdminIdAndMemberIdAndIsDeletedFalse(adminId, memberId);
}
// 회원 ID와 상태에 따라 등록 차트 목록을 가져오는 메서드
// 해당 회원의 등록 차트 중 상태가 "완료"인 것들을 가져와서 RegisterChartDto로 변환하여 리스트로 반환합니다.
@Transactional(readOnly = true)
public List<RegisterChartDto> **getRegisterChartsByMemberIdAndStatus**(Long memberId){
List<RegisterChart> registerCharts = registerChartRepository.findByMemberIdAndStatus(memberId, RegisterChartStatus.COMPLETE);
return registerCharts.stream().map(RegisterChart::toResponse).collect(Collectors.toList());
}
// 병원 ID와 진료 과목 ID로 관리자를 찾는 메서드
private Admin findAdmin(Long hospitalId, Long subjectId) {
return getEntity(adminService.findByHospitalIdAndSubjectId(hospitalId, subjectId), "해당 병원과 진료 과목에 해당하는 관리자를 찾을 수 없습니다.");
}
// 현재 사용자 정보를 가져오는 메서드
private Member getCurrentUser() {
return getEntity(memberService.getCurrentUser(), "현재 로그인한 사용자 정보를 가져오지 못했습니다.");
}
// 엔티티를 찾는 메서드
private <T> T getEntity(Optional<T> optionalEntity, String errorMessage) {
return optionalEntity.orElseThrow(() -> new EntityNotFoundException(errorMessage));
}
// 새로운 등록 차트를 삽입하는 메서드
// 병원 ID와 진료 과목 ID를 기반으로 새로운 등록 차트를 생성하는 메서드입니다.
// 현재 로그인한 사용자 정보를 가져와서 해당 병원과 진료 과목에 해당하는 관리자를 찾고, 중복 여부와 예약 여부를 확인한 후 새로운 등록 차트를 생성하여 저장합니다.
@Override
@Transactional
public RsData<RegisterChart> **insert**(Long hospitalId, Long subjectId){
Member currentUser = getCurrentUser();
return registerToChart(currentUser, hospitalId, subjectId);
}
// 등록 차트를 생성하는 메서드
private RsData<RegisterChart> registerToChart(Member currentUser, Long hospitalId, Long subjectId) {
Admin admin = findAdmin(hospitalId, subjectId);
return checkForDuplicatesAndInsert(currentUser, admin);
}
// 중복 여부를 확인하고 등록 차트를 삽입하는 메서드
private RsData<RegisterChart> checkForDuplicatesAndInsert(Member currentUser, Admin admin) {
boolean isDuplicated = registerChartRepository.existsByAdminIdAndMemberIdAndIsDeletedFalse(admin.getId(), currentUser.getId());
if (isDuplicated) {
return RsData.of("F-4", "이미 접수되었습니다.");
}
return checkForReservationsAndInsert(currentUser, admin);
}
// 예약 여부를 확인하고 등록 차트를 삽입하는 메서드
private RsData<RegisterChart> checkForReservationsAndInsert(Member currentUser, Admin admin) {
Optional<Reservation> reservationOptional = reservationService.findByAdminIdAndMemberId(admin.getId(), currentUser.getId());
if ((reservationOptional.isPresent()) && (LocalDate.now().isEqual(reservationOptional.get().getDate().toLocalDate()))) {
return RsData.of("F-5", "당일 예약이 존재하여 줄서기 등록이 불가능합니다.");
}
return insertRegisterChart(currentUser, admin);
}
// 등록 차트를 삽입하는 메서드
private RsData<RegisterChart> insertRegisterChart(Member currentUser, Admin admin) {
RegisterChart registerChart = RegisterChart.builder()
.status(RegisterChartStatus.WAITING)
.admin(admin)
.member(currentUser)
.isDeleted(false)
.build();
RegisterChart savedRegisterChart = registerChartRepository.save(registerChart);
return RsData.of("S-1", "접수 테이블에 삽입되었습니다.", savedRegisterChart);
}
// 관리자와 회원에 의해 상태가 업데이트되는 메서드
// 관리자와 회원 ID, 상태를 기반으로 등록 차트의 상태를 업데이트하는 메서드입니다.
// 해당 등록 차트가 존재하고 상태가 "취소" 또는 "완료"인 경우 삭제 플래그를 설정하고, 상태를 업데이트한 후 저장합니다.
@Override
@Transactional
public RsData<RegisterChart> **updateStatusByAdminAndMember**(Admin admin, Long memberId, RegisterChartStatus status) {
RegisterChart registerChart = getEntity(registerChartRepository.findRegisterChartByAdminIdAndMemberIdAndIsDeletedFalse(admin.getId(), memberId), "줄서기 정보를 찾을 수 없습니다.");
if(isStatusCancelOrComplete(status)){
registerChart.markAsDeleted(true);
}
registerChart.setStatus(status);
registerChartRepository.save(registerChart);
return RsData.of("S-1", "줄서기 상태가 업데이트 되었습니다.", registerChart);
}
// 취소 또는 완료 상태 여부를 확인하는 메서드
private boolean isStatusCancelOrComplete(RegisterChartStatus status) {
return status.equals(RegisterChartStatus.CANCEL) || status.equals(RegisterChartStatus.COMPLETE);
}
// 등록 차트를 처리하는 메서드
// 병원 ID와 진료 과목 ID를 기반으로 등록 차트를 처리하는 메서드입니다.
// 해당 병원과 진료 과목에 해당하는 등록 차트를 찾고 삭제 플래그를 설정하여 저장합니다.
@Override
@Transactional
public void **processRegisterChart**(Long hospitalId, Long subjectId) {
RegisterChart registerChart = findRegisterChart(hospitalId, subjectId);
deleteRegisterChart(registerChart);
}
// 등록 차트를 찾는 메서드
private RegisterChart findRegisterChart(Long hospitalId, Long subjectId) {
Admin admin = findAdmin(hospitalId, subjectId);
Member currentUser = getCurrentUser();
return getEntity(registerChartRepository.findByAdminIdAndMemberIdAndIsDeletedFalse(admin.getId(), currentUser.getId()), "등록 차트를 찾을 수 없습니다.");
}
// 등록 차트를 삭제하는 메서드
private void deleteRegisterChart(RegisterChart registerChart) {
registerChart.markAsDeleted(true);
if(!isStatusCancelOrComplete(registerChart.getStatus())){
registerChart.setStatus(RegisterChartStatus.CANCEL);
}
registerChartRepository.save(registerChart);
}
// 회원의 등록 차트 및 예약 목록을 가져오는 메서드
// 현재 로그인한 사용자의 의료 차트 목록을 가져오는 메서드입니다.
// 회원 ID를 기반으로 해당 회원의 등록 차트 목록과 예약 목록을 가져와서 시간에 따라 정렬하여 반환합니다.
public List<RegisterChartDto> **getMedicalCharts()** {
Member currentUser = getCurrentUser();
List<RegisterChartDto> registers = getRegisterChartsByMemberIdAndStatus(currentUser.getId());
List<RegisterChartDto> reservations = reservationService.getReservationsByMemberIdAndRegisterStatus(currentUser.getId());
List<RegisterChartDto> combinedList = Stream.concat(registers.stream(), reservations.stream())
.collect(Collectors.toList());
return getSortedListByTime(combinedList);
}
// 시간에 따라 정렬된 목록을 가져오는 메서드
private List<RegisterChartDto> getSortedListByTime(List<RegisterChartDto> unsortedList){
return unsortedList.stream()
.sorted(Comparator.comparing(RegisterChartDto::getTime).reversed())
.collect(Collectors.toList());
}
// 새로운 회원을 등록하는 메서드 (현장 접수)
// 제공된 정보를 기반으로 새로운 회원을 생성하고, 현재 로그인한 관리자 정보를 사용하여 등록 차트를 생성하여 저장합니다.
@Override
@Transactional
public RegisterChart **registerNewMember**(String providerType, String username, String tempPassword, String email) {
Admin currentAdmin = getEntity(adminService.getCurrentAdmin(), "로그인한 관리자가 아닙니다.");
Member newMember = validateAndCreateMember(providerType, username, email, tempPassword);
return createAndSaveRegisterChart(currentAdmin, newMember);
}
// 새로운 사용자를 등록하는 메서드 (현장 접수)
// 현장 등록 데이터를 기반으로 새로운 회원을 등록하고, 해당 회원을 사용하여 등록 차트를 생성하여 저장합니다.
@Override
@Transactional
public void **registerNewUser**(OnsiteRegisterDto onsiteRegisterDto) {
registerNewMember("careQ", onsiteRegisterDto.getUsername(), generateTempPassword(), onsiteRegisterDto.getEmail());
}
// 임시 비밀번호를 생성하는 메서드
private String generateTempPassword() {
return UUID.randomUUID().toString();
}
// 회원을 검증하고 생성하는 메서드
private Member validateAndCreateMember(String providerType, String username, String email, String tempPassword) {
validateJoinRequest(providerType, username, email);
return memberService.createMember(providerType, username, tempPassword, email);
}
// 가입 요청을 검증하는 메서드
private void validateJoinRequest(String providerType, String username, String email) {
validate(providerType, username, true);
validate(providerType, email, false);
}
// 검증하는 메서드
private void validate(String providerType, String value, boolean isUsername) {
RsData<Member> validation = memberService.validateJoinRequest(providerType, isUsername ? value : null, isUsername ? null : value);
if (!validation.isSuccess()) {
throw new RuntimeException(validation.getMsg());
}
}
// 등록 차트를 생성하고 저장하는 메서드
private RegisterChart createAndSaveRegisterChart(Admin currentAdmin, Member newMember) {
RegisterChart newRegisterChart = RegisterChart.builder()
.status(RegisterChartStatus.WAITING)
.admin(currentAdmin)
.member(newMember)
.isDeleted(false)
.build();
return registerChartRepository.save(newRegisterChart);
}
// 회원 ID로 대기열 정보를 가져오는 메서드 (대기현황_줄서기 등록 페이지)
// 해당 회원의 등록 차트 목록을 조회하여 각각에 대한 대기열 정보를 생성하여 반환합니다.
@Override
@Transactional(readOnly = true)
public List<QueueInfoDto> **getQueueInfoByMemberId**(Long memberId) {
Member member = memberService.findById(memberId)
.orElseThrow(() -> new IllegalArgumentException("해당 회원 ID를 찾을 수 없습니다. ID: " + memberId));
List<RegisterChart> registerCharts = findRegisterChartsByMember(member);
return registerCharts.stream()
.map(this::createQueueInfoFromRegisterChartDto)
.collect(Collectors.toList());
}
// 병원 ID와 진료 과목 ID로 대기열 정보를 가져오는 메서드 (대기현황_메인 페이지)
// 해당 병원과 진료 과목에 대한 대기열 정보를 생성하여 반환합니다.
// 만약 회원의 등록 차트가 없는 경우 대기열에 추가될 정보를 생성합니다.
@Override
@Transactional(readOnly = true)
public QueueInfoDto **getQueueInfo**(Long hospitalId, Long subjectId) {
Admin admin = findAdmin(hospitalId, subjectId);
Member currentUser = getCurrentUser();
Subject subject = findSubject(subjectId);
Hospital hospital = findHospital(hospitalId);
Optional<RegisterChart> optRegisterChart = Optional.ofNullable(findRegisterChart(admin, currentUser));
return optRegisterChart.map(rc -> createQueueInfoDto(rc, subject, hospital, admin))
.orElseGet(() -> createQueueInfoWithoutRegisterChart(subject, hospital, admin));
}
// 회원의 등록 차트 목록을 가져오는 메서드
private List<RegisterChart> findRegisterChartsByMember(Member member) {
return registerChartRepository.findByMemberAndIsDeletedFalse(member);
}
// 등록 차트에서 대기열 정보를 가져오는 메서드
private QueueInfoDto createQueueInfoFromRegisterChartDto(RegisterChart registerChart) {
Admin admin = registerChart.getAdmin();
Subject subject = findSubject(admin.getSubject().getId());
Hospital hospital = findHospital(admin.getHospital().getId());
return createQueueInfoDto(registerChart, subject, hospital, admin);
}
// 대기열 정보를 생성하는 메서드
private QueueInfoDto createQueueInfoDto(RegisterChart registerChart, Subject subject, Hospital hospital, Admin admin) {
Optional<RegisterChart> optRegisterChart = Optional.ofNullable(registerChart);
Long waitingCount = optRegisterChart.map(rc -> calculateCurrentUserWaitingIndex(hospital.getId(), subject.getId(), rc)).orElse(null);
String waitingStatus = optRegisterChart.map(rc -> waitingStatus(waitingCount, rc)).orElse(null);
Long expectedWaitingTime = optRegisterChart.map(chart -> calculateExpectedWaitingTime(waitingCount)).orElse(null);
return QueueInfoDto.of(registerChart, subject, hospital, admin, waitingCount, expectedWaitingTime, waitingStatus);
}
// 등록 차트 없이 대기열 정보를 생성하는 메서드
private QueueInfoDto createQueueInfoWithoutRegisterChart(Subject subject, Hospital hospital, Admin admin) {
Long waitingCount = calculateTotalWaitingCount(hospital.getId(), subject.getId());
Long expectedWaitingTime = calculateExpectedWaitingTime(waitingCount);
return QueueInfoDto.of(null, subject, hospital, admin, waitingCount, expectedWaitingTime, waitingCount.toString());
}
// 총 대기열 수를 계산하는 메서드
private Long calculateTotalWaitingCount(Long hospitalId, Long subjectId) {
List<RegisterChart> registerCharts = registerChartRepository.findByAdminHospitalIdAndAdminSubjectIdAndStatusInOrderByTimeAsc(hospitalId, subjectId, Arrays.asList(RegisterChartStatus.WAITING, RegisterChartStatus.ENTER));
return (long) registerCharts.size();
}
// 현재 사용자의 대기열 인덱스를 계산하는 메서드
private Long calculateCurrentUserWaitingIndex(Long hospitalId, Long subjectId, RegisterChart registerChart) {
List<RegisterChart> registerCharts = registerChartRepository.findByAdminHospitalIdAndAdminSubjectIdAndStatusInOrderByTimeAsc(hospitalId, subjectId, Arrays.asList(RegisterChartStatus.WAITING, RegisterChartStatus.ENTER));
int currentUserIndex = registerCharts.indexOf(registerChart);
return (currentUserIndex != -1) ? Long.valueOf(currentUserIndex) : Long.valueOf(registerCharts.size());
}
// 대기 상태를 확인하는 메서드
private String waitingStatus(Long waitingCount, RegisterChart registerChart) {
return isRegisterChartWaitingOrEnter(registerChart) ? (waitingCount == 0 ? "내 차례" : waitingCount.toString()) : waitingCount.toString();
}
// 예상 대기 시간을 계산하는 메서드
private Long calculateExpectedWaitingTime(Long waitingCount) {
return (waitingCount != null) ? waitingCount * 5 : null;
}
// 등록 차트가 대기 중 또는 입장 중인지 확인하는 메서드
private boolean isRegisterChartWaitingOrEnter(RegisterChart registerChart) {
return registerChart != null && (registerChart.getStatus() == RegisterChartStatus.WAITING || registerChart.getStatus() == RegisterChartStatus.ENTER);
}
// 등록 차트를 찾는 메서드
private RegisterChart findRegisterChart(Admin admin, Member currentUser) {
Optional<RegisterChart> registerChart = registerChartRepository.findByAdminIdAndMemberIdAndIsDeletedFalse(admin.getId(), currentUser.getId());
return registerChart.orElse(null);
}
// 진료 과목을 찾는 메서드
private Subject findSubject(Long subjectId) {
return getEntity(subjectService.findById(subjectId), "진료 과목을 찾을 수 없습니다.");
}
// 병원을 찾는 메서드
private Hospital findHospital(Long hospitalId) {
return getEntity(hospitalService.findById(hospitalId), "해당 병원을 찾을 수 없습니다.");
}
}