Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -138,4 +138,18 @@ private void validateOwner(Transporter transporter) {
throw new GlobalException(ResultCode.FORBIDDEN);
}
}

/**
* 노출 범위 토글 (자사 <-> 통합)
* @return 변경된 상태값
*/
public CallType toggleExposure() {
if (this.call == CallType.INTERNAL) {
this.call = CallType.INTEGRATED;
} else {
this.call = CallType.INTERNAL;
}
return this.call;
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.mobility.api.domain.office.controller;

import com.mobility.api.domain.dispatch.entity.Dispatch;
import com.mobility.api.domain.office.dto.request.CancelDispatchReq;
import com.mobility.api.domain.office.dto.request.CreateDispatchReq;
import com.mobility.api.domain.office.dto.request.DispatchSearchDto;
import com.mobility.api.domain.office.dto.request.UpdateDispatchReq;
Expand All @@ -23,7 +24,9 @@
import lombok.RequiredArgsConstructor;
import org.springdoc.core.annotations.ParameterObject;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;

Expand Down Expand Up @@ -168,9 +171,11 @@ public CommonResponse<List<DispatchFeedRes>> getDispatchFeed(
@Operation(summary = "배차 등록", description = "")
@RequestMapping(path = "/dispatch", method = RequestMethod.POST)
public CommonResponse<Object> createDispatch(
@AuthenticationPrincipal PrincipalDetails user,
@Valid @RequestBody CreateDispatchReq createDispatchReq
) {
officeService.saveDispatch(createDispatchReq);

officeService.saveDispatch(createDispatchReq, user.getManager());

return CommonResponse.success(null);
}
Expand All @@ -186,6 +191,7 @@ public CommonResponse<Object> createDispatch(
@Operation(summary = "배차 수정", description = "")
@RequestMapping(path = "/dispatch/{dispatch_id}", method = RequestMethod.PATCH)
public CommonResponse<Dispatch> updateDispatch(
@AuthenticationPrincipal PrincipalDetails user,
@PathVariable("dispatch_id") Long dispatchId,
@RequestBody UpdateDispatchReq updateDispatchReq
) {
Expand All @@ -203,9 +209,11 @@ public CommonResponse<Dispatch> updateDispatch(
@Operation(summary = "배차 취소 (삭제)", description = "")
@RequestMapping(path = "/dispatch-cancel/{dispatch_id}", method = RequestMethod.POST)
public CommonResponse<Object> cancelDispatch(
@PathVariable("dispatch_id") Long dispatchId
@AuthenticationPrincipal PrincipalDetails user,
@PathVariable("dispatch_id") Long dispatchId,
@RequestBody CancelDispatchReq req
) {
officeService.cancelDispatch(dispatchId);
officeService.cancelDispatch(dispatchId, req, user.getManager());
return CommonResponse.success(null); // FIXME return값 수정
}

Expand Down Expand Up @@ -253,11 +261,25 @@ public CommonResponse<String> createTransporter(
*/
@Operation(summary = "기사 리스트 조회", description = "")
@RequestMapping(path = "/transporter", method = RequestMethod.GET)
public CommonResponse<List<TransporterRes>> getMyTransporters(
@AuthenticationPrincipal PrincipalDetails user
public CommonResponse<Page<TransporterRes>> getMyTransporters(
@AuthenticationPrincipal PrincipalDetails user,
@RequestParam(required = false) String status, // 필터: 없을 수도 있음
@RequestParam(defaultValue = "0") int page, // 페이지: 안 보내면 0
@RequestParam(defaultValue = "20") int size // 크기: 안 보내면 20
) {

// 페이징 객체 생성 (최신순 정렬 예시: id 내림차순)
Pageable pageable = PageRequest.of(page, size, Sort.by("id").descending());

// userDetails.getUsername() -> 로그인한 관리자의 ID
List<TransporterRes> result = officeService.getMyTransporters(user.getManager());
// List<TransporterRes> result = officeService.getMyTransporters(user.getManager());

// 서비스 호출
Page<TransporterRes> result = officeService.getMyTransporters(
user.getManager(),
status,
pageable
);

return CommonResponse.success(result);
}
Expand All @@ -271,7 +293,7 @@ public CommonResponse<List<TransporterRes>> getMyTransporters(
*/
@Operation(summary = "기사 상태 변경", description = "")
@RequestMapping(path = "/transporter/{transporterId}/status", method = RequestMethod.PATCH)
public CommonResponse<Integer> changeTransporterStatus(
public CommonResponse<String> changeTransporterStatus(
@AuthenticationPrincipal PrincipalDetails user,
@PathVariable Long transporterId,
@RequestBody TransporterStatusUpdateReq req
Expand All @@ -283,7 +305,28 @@ public CommonResponse<Integer> changeTransporterStatus(
user.getManager()
);

return CommonResponse.success(0);
return CommonResponse.success("기사 상태 변경 성공");
}

/**
* <pre>
* 배차 노출범위 변경
* </pre>
* @param user
* @return
*/
@Operation(summary = "배차 노출범위 변경", description = "")
@RequestMapping(path = "/dispatch/{dispatchId}/exposure", method = RequestMethod.PATCH)
public CommonResponse<String> changeDispatchExposure(
@AuthenticationPrincipal PrincipalDetails user,
@PathVariable Long dispatchId
) {

// 서비스 호출 및 결과 받기
String changedStatus = officeService.changeDispatchExposure(dispatchId, user.getManager());

// 변경된 상태를 메시지나 데이터로 주면 프론트에서 UI 갱신하기 편함
return CommonResponse.success(changedStatus);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.mobility.api.domain.office.dto.request;

public record CancelDispatchReq(
String cancelReason // 취소 사유
) {
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package com.mobility.api.domain.office.dto.request;

import com.mobility.api.domain.dispatch.entity.Dispatch;
import com.mobility.api.domain.dispatch.enums.CallType;
import com.mobility.api.domain.dispatch.enums.ServiceType;
import com.mobility.api.domain.dispatch.enums.StatusType;
import com.mobility.api.domain.dispatch.enums.*;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
Expand Down Expand Up @@ -46,20 +44,27 @@ public record CreateDispatchReq(
String clientPhoneNumber, // 고객 전화번호

// StatusType status, // 배차 상태
Long officeId, // FIXME 사무실 id, 토큰 등에서 받아오도록 변경해야 함
// CallType call, // 콜 타입

@Schema(description = "활성화 여부", example = "true")
Boolean active, // 활성화 여부

@Schema(description = "활성화 여부", example = "DELIVERY")
@Schema(description = "서비스 타입", example = "DELIVERY")
ServiceType service, // 탁송 / 대리

LocalDateTime createdAt // 생성 시간
@Schema(description = "배차 번호", example = "2024-0001")
String dispatchNumber, // 배차 번호

@Schema(description = "메모", example = "memo")
String memo, // 메모

@Schema(description = "결제 방식", example = "CASH")
PaymentType paymentType, // 결제 방식 (현금 / 후불 / 완후)

@Schema(description = "톨비 방식", example = "HIPASS")
TollType tollType // 톨비 방식 (톨포 / 톨별 / 하이패스)

) {
public CreateDispatchReq {
officeId = 1L; // FIXME 임시로 사무실 id는 1로 고정
}

public Dispatch toEntity() {
return Dispatch.builder()
Expand All @@ -72,10 +77,13 @@ public Dispatch toEntity() {
.charge(this.charge())
.clientPhoneNumber(this.clientPhoneNumber())
.status(StatusType.OPEN)
.officeId(this.officeId())
.call(CallType.INTERNAL)
.active(this.active())
.service(this.service())
.dispatchNumber(this.dispatchNumber)
.memo(this.memo)
.paymentType(this.paymentType)
.tollType(this.tollType)
.createdAt(LocalDateTime.now())
.build();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package com.mobility.api.domain.office.dto.request;

import com.mobility.api.domain.dispatch.enums.CallType;
import com.mobility.api.domain.dispatch.enums.ServiceType;
import com.mobility.api.domain.dispatch.enums.StatusType;
import com.mobility.api.domain.dispatch.enums.*;
import io.swagger.v3.oas.annotations.media.Schema;

@Schema(description = "배차 수정 request 객체")
Expand Down Expand Up @@ -42,6 +40,19 @@ public record UpdateDispatchReq(
Boolean active,

@Schema(description = "서비스 타입", example = "DELIVERY")
ServiceType service
) {
ServiceType service,

@Schema(description = "배차 번호", example = "2024-0001")
String dispatchNumber,

@Schema(description = "메모", example = "memo")
String memo,

@Schema(description = "결제 방식", example = "CASH")
PaymentType paymentType,

@Schema(description = "톨비 방식", example = "HIPASS")
TollType tollType

) {
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package com.mobility.api.domain.office.service;

import com.mobility.api.domain.dispatch.entity.Dispatch;
import com.mobility.api.domain.dispatch.enums.CallType;
import com.mobility.api.domain.dispatch.enums.StatusType;
import com.mobility.api.domain.dispatch.repository.DispatchRepository;
import com.mobility.api.domain.dispatch.service.AutoDispatchService;
import com.mobility.api.domain.office.dto.request.CancelDispatchReq;
import com.mobility.api.domain.office.dto.request.CreateDispatchReq;
import com.mobility.api.domain.office.dto.request.DispatchSearchDto;
import com.mobility.api.domain.office.dto.request.UpdateDispatchReq;
Expand Down Expand Up @@ -77,7 +79,7 @@ public Page<GetAllDispatchRes> findAllDispatch(DispatchSearchDto searchDto, Page
}

@Transactional
public void saveDispatch(CreateDispatchReq createDispatchReq) {
public void saveDispatch(CreateDispatchReq createDispatchReq, Manager manager) {
// 1. 주변 1km 내 자동배차 ON 기사 존재 여부 확인
boolean hasEligibleDrivers = transporterRepository.existsEligibleDriversWithinRadius(
createDispatchReq.startLatitude(),
Expand All @@ -87,6 +89,12 @@ public void saveDispatch(CreateDispatchReq createDispatchReq) {
// 2. 배차 엔티티 생성
Dispatch dispatch = createDispatchReq.toEntity();

if(manager == null || manager.getOffice() == null) {
throw new GlobalException(ResultCode.NOT_FOUND_OFFICE);
}

dispatch.setOfficeId(manager.getOffice().getId());

// 3. 적격 기사 유무에 따라 상태 결정
if (hasEligibleDrivers) {
// 주변에 자동배차 ON 기사가 있음 → HOLD 상태로 시작
Expand Down Expand Up @@ -129,7 +137,7 @@ public void updateDispatch(Long dispatchId, UpdateDispatchReq req) {
}

@Transactional
public void cancelDispatch(Long dispatchId) { // <- 메서드 이름도 delete -> cancel로 변경
public void cancelDispatch(Long dispatchId, CancelDispatchReq req, Manager user) { // <- 메서드 이름도 delete -> cancel로 변경

// 엔티티 조회
Dispatch dispatch = dispatchRepository.findById(dispatchId)
Expand All @@ -142,6 +150,7 @@ public void cancelDispatch(Long dispatchId) { // <- 메서드 이름도 delete -

// TODO: dispatch.cancel() 같은 엔티티 메서드로 캡슐화
dispatch.setStatus(StatusType.CANCELED);
dispatch.setCancelReason(req.cancelReason());
dispatch.setCanceledAt(java.time.LocalDateTime.now());

// @Transactional이 변경 감지(Dirty Checking)로 UPDATE
Expand Down Expand Up @@ -209,23 +218,42 @@ public void createTransporter(TransporterCreateReq req, Manager manager) {
* @param manager 로그인한 직원
*/
@Transactional(readOnly = true) // 조회 전용이므로 readOnly 권장 (성능 향상)
public List<TransporterRes> getMyTransporters(Manager manager) {

// 1. 관리자(사장님) 찾기
public Page<TransporterRes> getMyTransporters(Manager manager, String statusStr, Pageable pageable) {

// 2. 소속 사무실 확인
Office office = manager.getOffice();
if (office == null) {
throw new GlobalException(ResultCode.FIXME_FAIL);
throw new GlobalException(ResultCode.NOT_FOUND_OFFICE);
}

// 3. 해당 사무실의 기사 리스트 조회
List<Transporter> transporters = transporterRepository.findAllByOffice(office);
Page<Transporter> transporterPage;

// 1. status 파라미터가 있으면 -> 해당 상태로 필터링
if (statusStr != null && !statusStr.isBlank()) {
try {
// 프론트에서 소문자로 줘도 대문자로 변환 (active -> ACTIVE)
TransporterStatus status = TransporterStatus.valueOf(statusStr.toUpperCase());
transporterPage = transporterRepository.findAllByOfficeAndStatus(office, status, pageable);
} catch (IllegalArgumentException e) {
// 이상한 status 문자열이 들어오면 빈 페이지 리턴 or 에러 처리 (여기선 빈 페이지)
return Page.empty(pageable);
}
}
// 2. status 파라미터가 없으면 -> 전체 조회
else {
transporterPage = transporterRepository.findAllByOffice(office, pageable);
}

// 4. Entity List -> DTO List 변환하여 반환
return transporters.stream()
.map(TransporterRes::from)
.toList();
// 3. Entity Page -> DTO Page 변환
return transporterPage.map(TransporterRes::from);

// // 3. 해당 사무실의 기사 리스트 조회
// List<Transporter> transporters = transporterRepository.findAllByOffice(office);
//
// // 4. Entity List -> DTO List 변환하여 반환
// return transporters.stream()
// .map(TransporterRes::from)
// .toList();
}

@Transactional
Expand Down Expand Up @@ -317,4 +345,26 @@ public List<DispatchFeedRes> getDispatchFeed(Integer limit, Manager manager) {
.toList();
}

/**
* 배차 노출 범위 변경 (Toggle)
*/
@Transactional
public String changeDispatchExposure(Long dispatchId, Manager manager) {

// 1. 배차 조회
Dispatch dispatch = dispatchRepository.findById(dispatchId)
.orElseThrow(() -> new GlobalException(ResultCode.NOT_FOUND_DISPATCH));

// 2. 권한 검증 (내 사무실 배차인지)
if (!dispatch.getOfficeId().equals(manager.getOffice().getId())) {
throw new GlobalException(ResultCode.UNAUTHORIZED_ACCESS);
}

// 3. 상태 토글 (Entity 메서드 호출)
CallType newType = dispatch.toggleExposure();

// 4. 변경된 상태 문자열 반환 ("INTEGRATED" 등)
return newType.name();
}

}
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package com.mobility.api.domain.transporter.repository;

import com.mobility.api.domain.office.entity.Office;
import com.mobility.api.domain.transporter.TransporterStatus;
import com.mobility.api.domain.transporter.dto.TransporterDistanceProjection;
import com.mobility.api.domain.transporter.entity.Transporter;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
Expand Down Expand Up @@ -85,4 +88,10 @@ boolean existsEligibleDriversWithinRadius(
// 특정 사무실에 소속된 기사 목록 조회
List<Transporter> findAllByOffice(Office office);

// 1. 상태 필터 없이 전체 조회 (페이징)
Page<Transporter> findAllByOffice(Office office, Pageable pageable);

// 2. 상태 필터 적용 조회 (페이징)
Page<Transporter> findAllByOfficeAndStatus(Office office, TransporterStatus status, Pageable pageable);

}