diff --git a/src/main/java/flowfit/domain/ptrelation/domain/repository/PtRelationRepository.java b/src/main/java/flowfit/domain/ptrelation/domain/repository/PtRelationRepository.java index d59afe5..47fb444 100644 --- a/src/main/java/flowfit/domain/ptrelation/domain/repository/PtRelationRepository.java +++ b/src/main/java/flowfit/domain/ptrelation/domain/repository/PtRelationRepository.java @@ -1,6 +1,5 @@ package flowfit.domain.ptrelation.domain.repository; -import flowfit.domain.ptrelation.domain.entity.prepare.PreparePt; import flowfit.domain.ptrelation.domain.entity.ptrelation.PtRelation; import flowfit.domain.user.domain.entity.member.Member; import flowfit.domain.user.domain.entity.trainer.Trainer; @@ -22,4 +21,5 @@ public interface PtRelationRepository extends JpaRepository { List findAllByTrainer(Trainer trainer); Optional findByIdAndTrainer(Long relationId, Trainer trainer); + List findAllByTrainer_IdAndMember_NameContaining(String trainerId, String name); } diff --git a/src/main/java/flowfit/domain/ptsession/application/service/ScheduleService.java b/src/main/java/flowfit/domain/ptsession/application/service/ScheduleService.java new file mode 100644 index 0000000..6d59ae8 --- /dev/null +++ b/src/main/java/flowfit/domain/ptsession/application/service/ScheduleService.java @@ -0,0 +1,21 @@ +package flowfit.domain.ptsession.application.service; + +import flowfit.domain.ptsession.presentation.dto.req.ScheduleRequest; +import flowfit.domain.ptsession.presentation.dto.res.CalendarMonthResponse; +import flowfit.domain.ptsession.presentation.dto.res.ScheduleResponse; + +import java.time.LocalDate; +import java.util.List; + +public interface ScheduleService { + + Long createSession(ScheduleRequest request); + + void updateSession(Long sessionId, ScheduleRequest request); + + void deleteSession(Long sessionId, String reason, boolean charge); + + CalendarMonthResponse getMonthlySummary(int year, int month); + + List getSessionsByDate(LocalDate date); +} diff --git a/src/main/java/flowfit/domain/ptsession/application/service/impl/ScheduleServiceImpl.java b/src/main/java/flowfit/domain/ptsession/application/service/impl/ScheduleServiceImpl.java new file mode 100644 index 0000000..f78d5c7 --- /dev/null +++ b/src/main/java/flowfit/domain/ptsession/application/service/impl/ScheduleServiceImpl.java @@ -0,0 +1,111 @@ +package flowfit.domain.ptsession.application.service.impl; + +import flowfit.domain.ptrelation.domain.entity.ptrelation.PtRelation; +import flowfit.domain.ptrelation.domain.repository.PtRelationRepository; +import flowfit.domain.ptsession.application.service.ScheduleService; +import flowfit.domain.ptsession.domain.entity.PtSession; +import flowfit.domain.ptsession.domain.entity.Status; +import flowfit.domain.ptsession.domain.repository.PtSessionRepository; +import flowfit.domain.ptsession.presentation.dto.req.ScheduleRequest; +import flowfit.domain.ptsession.presentation.dto.res.CalendarMonthResponse; +import flowfit.domain.ptsession.presentation.dto.res.ScheduleResponse; +import flowfit.domain.user.infra.exception.PtSessionNotFoundException; +import flowfit.domain.user.infra.exception.PtRelationNotFoundException; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Service; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +import static java.util.stream.Collectors.toList; + +@Service +@RequiredArgsConstructor +public class ScheduleServiceImpl implements ScheduleService { + + private final PtSessionRepository sessionRepository; + private final PtRelationRepository relationRepository; + + @Override + public Long createSession(ScheduleRequest req) { + PtRelation relation = relationRepository.findById(req.getRelationId()) + .orElseThrow(PtRelationNotFoundException::new); + + LocalDateTime start = LocalDateTime.parse(req.getDate() + "T" + req.getStartTime()); + LocalDateTime end = LocalDateTime.parse(req.getDate() + "T" + req.getEndTime()); + + PtSession session = PtSession.builder() + .ptRelation(relation) + .startTime(start) + .endTime(end) + .status(Status.PROCESS) + .message(req.getMessage()) + .build(); + + return sessionRepository.save(session).getId(); + } + + @Override + public void updateSession(Long sessionId, ScheduleRequest req) { + PtSession session = sessionRepository.findById(sessionId) + .orElseThrow(PtSessionNotFoundException::new); + + session.updateStartTime(LocalDateTime.parse(req.getDate() + "T" + req.getStartTime())); + session.updateEndTime(LocalDateTime.parse(req.getDate() + "T" + req.getEndTime())); + session.updateMessage(req.getMessage()); + } + + @Override + public void deleteSession(Long sessionId, String reason, boolean charge) { + PtSession session = sessionRepository.findById(sessionId) + .orElseThrow(PtSessionNotFoundException::new); + session.updateStatus(Status.CANCEL_T); // TODO: 사유 분기처리 + } + + @Override + public List getSessionsByDate(LocalDate date) { + LocalDateTime startOfDay = date.atStartOfDay(); + LocalDateTime endOfDay = date.atTime(23, 59); + + String trainerId = getCurrentTrainerId(); + + return sessionRepository + .findAllByPtRelation_Trainer_IdAndStartTimeBetween(trainerId, startOfDay, endOfDay) + .stream() + .map(ScheduleResponse::from) + .collect(toList()); + } + + private String getCurrentTrainerId() { + return SecurityContextHolder.getContext().getAuthentication().getName(); + } + + @Override + public CalendarMonthResponse getMonthlySummary(int year, int month) { + String trainerId = getCurrentTrainerId(); + LocalDate start = LocalDate.of(year, month, 1); + LocalDate end = start.withDayOfMonth(start.lengthOfMonth()); + + List sessions = sessionRepository + .findAllByPtRelation_Trainer_IdAndStartTimeBetween(trainerId, start.atStartOfDay(), end.atTime(23, 59)); + + // 일 수입을 구하고 더하는 식으로 월 수입 도출 + Map dailyIncome = new TreeMap<>(); + long total = 0L; + + for (PtSession s : sessions) { + if (s.getStatus() == Status.PROCESS || s.getStatus() == Status.COMPLETED) { + LocalDate day = s.getStartTime().toLocalDate(); + long income = s.getPtRelation().getTotalMoney() / s.getPtRelation().getSession(); + dailyIncome.put(day, dailyIncome.getOrDefault(day, 0L) + income); + total += income; + } + } + + return CalendarMonthResponse.of(total, dailyIncome); + } +} \ No newline at end of file diff --git a/src/main/java/flowfit/domain/ptsession/domain/entity/PtSession.java b/src/main/java/flowfit/domain/ptsession/domain/entity/PtSession.java index fffcfce..c44d6d6 100644 --- a/src/main/java/flowfit/domain/ptsession/domain/entity/PtSession.java +++ b/src/main/java/flowfit/domain/ptsession/domain/entity/PtSession.java @@ -19,7 +19,6 @@ public class PtSession { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @Column(nullable = false) private LocalDateTime startTime; diff --git a/src/main/java/flowfit/domain/ptsession/domain/repository/PtSessionRepository.java b/src/main/java/flowfit/domain/ptsession/domain/repository/PtSessionRepository.java index 77d3af4..f866be5 100644 --- a/src/main/java/flowfit/domain/ptsession/domain/repository/PtSessionRepository.java +++ b/src/main/java/flowfit/domain/ptsession/domain/repository/PtSessionRepository.java @@ -8,6 +8,7 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; +import java.time.LocalDateTime; import java.util.List; import java.util.Optional; @@ -31,4 +32,10 @@ List findByPtRelationAndDateAll(@Param("ptRelation") PtRelation ptRel List findAllByPtRelation(PtRelation ptRelation); List findByPtRelationAndStatus(PtRelation ptRelation, Status status); + + List findAllByPtRelation_Trainer_IdAndStartTimeBetween( + String trainerId, + LocalDateTime start, + LocalDateTime end + ); } diff --git a/src/main/java/flowfit/domain/ptsession/presentation/controller/ClientSearchController.java b/src/main/java/flowfit/domain/ptsession/presentation/controller/ClientSearchController.java new file mode 100644 index 0000000..81ef8d5 --- /dev/null +++ b/src/main/java/flowfit/domain/ptsession/presentation/controller/ClientSearchController.java @@ -0,0 +1,37 @@ +package flowfit.domain.ptsession.presentation.controller; + +import flowfit.domain.ptrelation.application.service.PtRelationService; +import flowfit.domain.ptrelation.domain.entity.ptrelation.PtRelation; +import flowfit.domain.ptrelation.domain.repository.PtRelationRepository; +import flowfit.domain.ptsession.presentation.dto.res.ClientSearchResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/clients") +public class ClientSearchController { + + private final PtRelationRepository ptRelationRepository; + + @GetMapping("/search") + public ResponseEntity searchClientByName(@RequestParam String name) { + String trainerId = SecurityContextHolder.getContext().getAuthentication().getName(); + + List relations = ptRelationRepository + .findAllByTrainer_IdAndMember_NameContaining(trainerId, name); + + List clients = relations.stream() + .map(ClientSearchResponse::from) + .toList(); + + return ResponseEntity.ok().body(new ClientSearchResponse.Wrapper(clients)); + } +} diff --git a/src/main/java/flowfit/domain/ptsession/presentation/controller/ScheduleController.java b/src/main/java/flowfit/domain/ptsession/presentation/controller/ScheduleController.java index a9b09ba..e37903d 100644 --- a/src/main/java/flowfit/domain/ptsession/presentation/controller/ScheduleController.java +++ b/src/main/java/flowfit/domain/ptsession/presentation/controller/ScheduleController.java @@ -1,4 +1,50 @@ package flowfit.domain.ptsession.presentation.controller; +import flowfit.domain.ptsession.application.service.ScheduleService; +import flowfit.domain.ptsession.presentation.dto.req.ScheduleRequest; +import flowfit.domain.ptsession.presentation.dto.res.CalendarMonthResponse; +import flowfit.domain.ptsession.presentation.dto.res.ScheduleResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.time.LocalDate; +import java.util.List; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/sessions") public class ScheduleController { + + private final ScheduleService scheduleService; + + @PostMapping + public ResponseEntity create(@RequestBody ScheduleRequest request) { + Long id = scheduleService.createSession(request); + return ResponseEntity.ok().body(id); + } + + @PatchMapping("/{sessionId}") + public ResponseEntity update(@PathVariable Long sessionId, @RequestBody ScheduleRequest request) { + scheduleService.updateSession(sessionId, request); + return ResponseEntity.ok().build(); + } + + @DeleteMapping("/{sessionId}") + public ResponseEntity delete(@PathVariable Long sessionId, + @RequestParam String reason, + @RequestParam boolean charge) { + scheduleService.deleteSession(sessionId, reason, charge); + return ResponseEntity.ok().build(); + } + + @GetMapping(params = {"year", "month"}) + public ResponseEntity getMonthly(@RequestParam int year, @RequestParam int month) { + return ResponseEntity.ok(scheduleService.getMonthlySummary(year, month)); + } + + @GetMapping(params = "date") + public ResponseEntity> getDaily(@RequestParam String date) { + return ResponseEntity.ok(scheduleService.getSessionsByDate(LocalDate.parse(date))); + } } diff --git a/src/main/java/flowfit/domain/ptsession/presentation/dto/req/ScheduleRequest.java b/src/main/java/flowfit/domain/ptsession/presentation/dto/req/ScheduleRequest.java new file mode 100644 index 0000000..6a021b1 --- /dev/null +++ b/src/main/java/flowfit/domain/ptsession/presentation/dto/req/ScheduleRequest.java @@ -0,0 +1,14 @@ +package flowfit.domain.ptsession.presentation.dto.req; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class ScheduleRequest { + private Long relationId; + private String date; // "2025-01-28" + private String startTime; // "19:00" + private String endTime; // "20:00" + private String message; // optional +} \ No newline at end of file diff --git a/src/main/java/flowfit/domain/ptsession/presentation/dto/res/CalendarMonthResponse.java b/src/main/java/flowfit/domain/ptsession/presentation/dto/res/CalendarMonthResponse.java new file mode 100644 index 0000000..61f861a --- /dev/null +++ b/src/main/java/flowfit/domain/ptsession/presentation/dto/res/CalendarMonthResponse.java @@ -0,0 +1,36 @@ +package flowfit.domain.ptsession.presentation.dto.res; + +import lombok.Builder; +import lombok.Getter; + +import java.time.LocalDate; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Getter +@Builder +public class CalendarMonthResponse { + + private long totalIncome; + private List monthlySchedules; + + @Getter + @Builder + public static class DailyIncome { + private LocalDate date; + private long income; + } + + public static CalendarMonthResponse of(long total, Map incomeMap) { + List daily = incomeMap.entrySet().stream() + .map(e -> DailyIncome.builder().date(e.getKey()).income(e.getValue()).build()) + .collect(Collectors.toList()); + + return CalendarMonthResponse.builder() + .totalIncome(total) + .monthlySchedules(daily) + .build(); + + } +} diff --git a/src/main/java/flowfit/domain/ptsession/presentation/dto/res/ClientSearchResponse.java b/src/main/java/flowfit/domain/ptsession/presentation/dto/res/ClientSearchResponse.java new file mode 100644 index 0000000..7334a53 --- /dev/null +++ b/src/main/java/flowfit/domain/ptsession/presentation/dto/res/ClientSearchResponse.java @@ -0,0 +1,34 @@ +package flowfit.domain.ptsession.presentation.dto.res; + +import flowfit.domain.ptrelation.domain.entity.ptrelation.PtRelation; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +import java.util.List; + +@Getter +@Builder +@AllArgsConstructor +public class ClientSearchResponse { + + private Long memberId; + private String name; + private String phone; + private int remainingSessions; + + public static ClientSearchResponse from(PtRelation relation) { + return ClientSearchResponse.builder() + .memberId(Long.parseLong(relation.getMember().getId())) + .name(relation.getMember().getName()) + .phone(relation.getMember().getPhoneNumber()) + .remainingSessions(relation.getSession()) + .build(); + } + + @Getter + @AllArgsConstructor + public static class Wrapper { + private List clients; + } +} diff --git a/src/main/java/flowfit/domain/ptsession/presentation/dto/res/ScheduleResponse.java b/src/main/java/flowfit/domain/ptsession/presentation/dto/res/ScheduleResponse.java new file mode 100644 index 0000000..7bbdecd --- /dev/null +++ b/src/main/java/flowfit/domain/ptsession/presentation/dto/res/ScheduleResponse.java @@ -0,0 +1,25 @@ +package flowfit.domain.ptsession.presentation.dto.res; + +import flowfit.domain.ptsession.domain.entity.PtSession; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class ScheduleResponse { + private Long sessionId; + private String memberName; + private String startTime; + private String endTime; + private String status; + + public static ScheduleResponse from(PtSession session) { + return ScheduleResponse.builder() + .sessionId(session.getId()) + .memberName(session.getPtRelation().getMember().getName()) + .startTime(session.getStartTime().toLocalTime().toString()) + .endTime(session.getEndTime().toLocalTime().toString()) + .status(session.getStatus().name()) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/flowfit/domain/user/infra/exception/PtRelationNotFoundException.java b/src/main/java/flowfit/domain/user/infra/exception/PtRelationNotFoundException.java new file mode 100644 index 0000000..4fc793d --- /dev/null +++ b/src/main/java/flowfit/domain/user/infra/exception/PtRelationNotFoundException.java @@ -0,0 +1,10 @@ +package flowfit.domain.user.infra.exception; + +import flowfit.global.infra.exception.FlowfitException; +import org.springframework.http.HttpStatus; + +public class PtRelationNotFoundException extends FlowfitException { + public PtRelationNotFoundException() { + super(HttpStatus.NOT_FOUND, "해당 PT 계약이 존재하지 않습니다."); + } +} diff --git a/src/main/java/flowfit/domain/user/infra/exception/PtSessionNotFoundException.java b/src/main/java/flowfit/domain/user/infra/exception/PtSessionNotFoundException.java new file mode 100644 index 0000000..85336d7 --- /dev/null +++ b/src/main/java/flowfit/domain/user/infra/exception/PtSessionNotFoundException.java @@ -0,0 +1,10 @@ +package flowfit.domain.user.infra.exception; + +import flowfit.global.infra.exception.FlowfitException; +import org.springframework.http.HttpStatus; + +public class PtSessionNotFoundException extends FlowfitException { + public PtSessionNotFoundException() { + super(HttpStatus.NOT_FOUND, "해당 PT 세션이 존재하지 않습니다."); + } +}