From 98b7c99484a21a72a717ad33f2f4d748963cba2f Mon Sep 17 00:00:00 2001 From: jsgjsg Date: Tue, 13 Jan 2026 00:05:49 +0900 Subject: [PATCH 1/2] =?UTF-8?q?[#3]=20feat:=20=EC=82=AC=EB=AC=B4=EC=8B=A4?= =?UTF-8?q?=20-=20=EA=B8=B0=EC=82=AC=20=EC=B6=94=EA=B0=80=20api=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../office/controller/OfficeV1Controller.java | 15 ++++++++ .../domain/office/service/OfficeService.java | 35 +++++++++++++++++++ .../dto/request/TransporterCreateReq.java | 14 ++++++++ .../transporter/entity/Transporter.java | 5 +++ 4 files changed, 69 insertions(+) create mode 100644 src/main/java/com/mobility/api/domain/transporter/dto/request/TransporterCreateReq.java diff --git a/src/main/java/com/mobility/api/domain/office/controller/OfficeV1Controller.java b/src/main/java/com/mobility/api/domain/office/controller/OfficeV1Controller.java index c28aa17..6f19be3 100644 --- a/src/main/java/com/mobility/api/domain/office/controller/OfficeV1Controller.java +++ b/src/main/java/com/mobility/api/domain/office/controller/OfficeV1Controller.java @@ -6,8 +6,10 @@ import com.mobility.api.domain.office.dto.request.UpdateDispatchReq; import com.mobility.api.domain.office.dto.response.GetAllDispatchRes; import com.mobility.api.domain.office.service.OfficeService; +import com.mobility.api.domain.transporter.dto.request.TransporterCreateReq; import com.mobility.api.global.annotation.SwaggerPageable; import com.mobility.api.global.response.CommonResponse; +import com.mobility.api.global.security.PrincipalDetails; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -17,6 +19,7 @@ import org.springdoc.core.annotations.ParameterObject; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; @Tag(name = "사무실 관련 요청(/api/v1/office/...)") @@ -108,4 +111,16 @@ public CommonResponse getStatistics() { return CommonResponse.success("프론트 개발 후 작업 예정입니다."); } + @RequestMapping(path = "/transporter", method = RequestMethod.POST) + public CommonResponse createTransporter( + @RequestBody TransporterCreateReq req, + @AuthenticationPrincipal PrincipalDetails user // 👈 토큰에서 사용자 정보 추출 + ) { + + // userDetails.getUsername()에는 토큰에 넣었던 subject(loginId)가 들어있습니다. + officeService.createTransporter(req, user.getManager()); + + return CommonResponse.success("기사 등록 성공"); + } + } diff --git a/src/main/java/com/mobility/api/domain/office/service/OfficeService.java b/src/main/java/com/mobility/api/domain/office/service/OfficeService.java index 0d85063..9663394 100644 --- a/src/main/java/com/mobility/api/domain/office/service/OfficeService.java +++ b/src/main/java/com/mobility/api/domain/office/service/OfficeService.java @@ -8,6 +8,11 @@ import com.mobility.api.domain.office.dto.request.DispatchSearchDto; import com.mobility.api.domain.office.dto.request.UpdateDispatchReq; import com.mobility.api.domain.office.dto.response.GetAllDispatchRes; +import com.mobility.api.domain.office.entity.Manager; +import com.mobility.api.domain.office.entity.Office; +import com.mobility.api.domain.transporter.dto.request.TransporterCreateReq; +import com.mobility.api.domain.transporter.entity.Transporter; +import com.mobility.api.domain.transporter.repository.TransporterRepository; import com.mobility.api.global.enums.ApiResponseCode; import com.mobility.api.global.exception.BusinessException; import com.mobility.api.global.exception.GlobalException; @@ -30,6 +35,7 @@ public class OfficeService { private final DispatchRepository dispatchRepository; + private final TransporterRepository transporterRepository; private final AutoDispatchService autoDispatchService; public Page findAllDispatch(DispatchSearchDto searchDto, Pageable pageable) { @@ -117,4 +123,33 @@ public void cancelDispatch(Long dispatchId) { // <- 메서드 이름도 delete - // @Transactional이 변경 감지(Dirty Checking)로 UPDATE } + /** + * 기사 등록 (사장님이 호출) + * @param req 기사 정보 + * @param manager 로그인한 직원 (토큰에서 추출) + */ + @Transactional + public void createTransporter(TransporterCreateReq req, Manager manager) { + + // 1. 현재 로그인한 사장님 조회 + + // 2. 사장님의 소속 사무실 가져오기 + Office office = manager.getOffice(); + + // 3. 기사 전화번호 중복 검사 + if (transporterRepository.existsByPhone(req.phone())) { + throw new GlobalException(ResultCode.FIXME_FAIL); // 이미 등록된 기사 + } + + // 4. 기사 저장 (사무실 정보 자동 주입) + Transporter transporter = Transporter.builder() + .name(req.name()) + .phone(req.phone()) + .isAutoDispatch(req.isAutoDispatch()) + .office(office) // 직원의 사무실을 자동으로 넣어줌 + .build(); + + transporterRepository.save(transporter); + } + } diff --git a/src/main/java/com/mobility/api/domain/transporter/dto/request/TransporterCreateReq.java b/src/main/java/com/mobility/api/domain/transporter/dto/request/TransporterCreateReq.java new file mode 100644 index 0000000..85c7632 --- /dev/null +++ b/src/main/java/com/mobility/api/domain/transporter/dto/request/TransporterCreateReq.java @@ -0,0 +1,14 @@ +package com.mobility.api.domain.transporter.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; + +public record TransporterCreateReq( + @Schema(description = "기사 이름", example = "김기사") + String name, + + @Schema(description = "기사 전화번호", example = "010-1234-1234") + String phone, + + @Schema(description = "자동배차 여부", example = "false") + Boolean isAutoDispatch +) {} \ No newline at end of file diff --git a/src/main/java/com/mobility/api/domain/transporter/entity/Transporter.java b/src/main/java/com/mobility/api/domain/transporter/entity/Transporter.java index 626f5c5..b95deda 100644 --- a/src/main/java/com/mobility/api/domain/transporter/entity/Transporter.java +++ b/src/main/java/com/mobility/api/domain/transporter/entity/Transporter.java @@ -1,5 +1,6 @@ package com.mobility.api.domain.transporter.entity; +import com.mobility.api.domain.office.entity.Office; import jakarta.persistence.*; import lombok.*; import org.hibernate.annotations.CreationTimestamp; @@ -35,6 +36,10 @@ public class Transporter { @Column(name = "is_auto_dispatch") private boolean isAutoDispatch; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "office_id") // DB 컬럼명: office_id + private Office office; + @CreationTimestamp @Column(name = "created_at", updatable = false) private LocalDateTime createdAt; From 8ac6405db53fcc0d1dc7c029871529d3f3cd5c68 Mon Sep 17 00:00:00 2001 From: jsgjsg Date: Tue, 13 Jan 2026 00:26:00 +0900 Subject: [PATCH 2/2] =?UTF-8?q?[#3]=20feat:=20=EC=82=AC=EB=AC=B4=EC=8B=A4?= =?UTF-8?q?=20-=20=EA=B8=B0=EC=82=AC=20=EC=A1=B0=ED=9A=8C=20api=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 응답 일부 수정 필요 --- .../office/controller/OfficeV1Controller.java | 13 ++++++++++ .../domain/office/service/OfficeService.java | 25 ++++++++++++++++++ .../dto/response/TransporterRes.java | 26 +++++++++++++++++++ .../repository/TransporterRepository.java | 4 +++ 4 files changed, 68 insertions(+) create mode 100644 src/main/java/com/mobility/api/domain/transporter/dto/response/TransporterRes.java diff --git a/src/main/java/com/mobility/api/domain/office/controller/OfficeV1Controller.java b/src/main/java/com/mobility/api/domain/office/controller/OfficeV1Controller.java index 6f19be3..c19b8c7 100644 --- a/src/main/java/com/mobility/api/domain/office/controller/OfficeV1Controller.java +++ b/src/main/java/com/mobility/api/domain/office/controller/OfficeV1Controller.java @@ -7,6 +7,7 @@ import com.mobility.api.domain.office.dto.response.GetAllDispatchRes; import com.mobility.api.domain.office.service.OfficeService; import com.mobility.api.domain.transporter.dto.request.TransporterCreateReq; +import com.mobility.api.domain.transporter.dto.response.TransporterRes; import com.mobility.api.global.annotation.SwaggerPageable; import com.mobility.api.global.response.CommonResponse; import com.mobility.api.global.security.PrincipalDetails; @@ -22,6 +23,8 @@ import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; +import java.util.List; + @Tag(name = "사무실 관련 요청(/api/v1/office/...)") @RestController @RequiredArgsConstructor @@ -123,4 +126,14 @@ public CommonResponse createTransporter( return CommonResponse.success("기사 등록 성공"); } + @RequestMapping(path = "/transporter", method = RequestMethod.GET) + public CommonResponse> getMyTransporters( + @AuthenticationPrincipal PrincipalDetails user + ) { + // userDetails.getUsername() -> 로그인한 관리자의 ID + List result = officeService.getMyTransporters(user.getManager()); + + return CommonResponse.success(result); + } + } diff --git a/src/main/java/com/mobility/api/domain/office/service/OfficeService.java b/src/main/java/com/mobility/api/domain/office/service/OfficeService.java index 9663394..911258e 100644 --- a/src/main/java/com/mobility/api/domain/office/service/OfficeService.java +++ b/src/main/java/com/mobility/api/domain/office/service/OfficeService.java @@ -11,6 +11,7 @@ import com.mobility.api.domain.office.entity.Manager; import com.mobility.api.domain.office.entity.Office; import com.mobility.api.domain.transporter.dto.request.TransporterCreateReq; +import com.mobility.api.domain.transporter.dto.response.TransporterRes; import com.mobility.api.domain.transporter.entity.Transporter; import com.mobility.api.domain.transporter.repository.TransporterRepository; import com.mobility.api.global.enums.ApiResponseCode; @@ -152,4 +153,28 @@ public void createTransporter(TransporterCreateReq req, Manager manager) { transporterRepository.save(transporter); } + /** + * 내 사무실 기사 목록 조회 + * @param manager 로그인한 직원 + */ + @Transactional(readOnly = true) // 조회 전용이므로 readOnly 권장 (성능 향상) + public List getMyTransporters(Manager manager) { + + // 1. 관리자(사장님) 찾기 + + // 2. 소속 사무실 확인 + Office office = manager.getOffice(); + if (office == null) { + throw new GlobalException(ResultCode.FIXME_FAIL); + } + + // 3. 해당 사무실의 기사 리스트 조회 + List transporters = transporterRepository.findAllByOffice(office); + + // 4. Entity List -> DTO List 변환하여 반환 + return transporters.stream() + .map(TransporterRes::from) + .toList(); + } + } diff --git a/src/main/java/com/mobility/api/domain/transporter/dto/response/TransporterRes.java b/src/main/java/com/mobility/api/domain/transporter/dto/response/TransporterRes.java new file mode 100644 index 0000000..82bf93b --- /dev/null +++ b/src/main/java/com/mobility/api/domain/transporter/dto/response/TransporterRes.java @@ -0,0 +1,26 @@ +package com.mobility.api.domain.transporter.dto.response; + +import com.mobility.api.domain.transporter.entity.Transporter; +import lombok.Builder; + +import java.time.LocalDateTime; + +@Builder +public record TransporterRes( + Long transporterId, + String name, + String phoneNumber, + Boolean isAutoDispatch, + LocalDateTime createdAt +) { + // Entity -> DTO 변환 메서드 + public static TransporterRes from(Transporter transporter) { + return TransporterRes.builder() + .transporterId(transporter.getId()) + .name(transporter.getName()) + .phoneNumber(transporter.getPhone()) + .isAutoDispatch(transporter.isAutoDispatch()) + .createdAt(transporter.getCreatedAt()) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/com/mobility/api/domain/transporter/repository/TransporterRepository.java b/src/main/java/com/mobility/api/domain/transporter/repository/TransporterRepository.java index a55ddce..0f9cc42 100644 --- a/src/main/java/com/mobility/api/domain/transporter/repository/TransporterRepository.java +++ b/src/main/java/com/mobility/api/domain/transporter/repository/TransporterRepository.java @@ -1,5 +1,6 @@ package com.mobility.api.domain.transporter.repository; +import com.mobility.api.domain.office.entity.Office; import com.mobility.api.domain.transporter.dto.TransporterDistanceProjection; import com.mobility.api.domain.transporter.entity.Transporter; import org.springframework.data.jpa.repository.JpaRepository; @@ -64,4 +65,7 @@ List findEligibleDriversForAutoDispatch( // 로그인 시 기사 조회용 (기사 id는 전화번호이므로 username을 받아서 phone을 조회함) Optional findByPhone(String username); + // 특정 사무실에 소속된 기사 목록 조회 + List findAllByOffice(Office office); + }