From 67b356a98e39716e549c483891bbe3f505113105 Mon Sep 17 00:00:00 2001 From: ccjngwn Date: Wed, 11 Feb 2026 03:38:53 +0900 Subject: [PATCH 01/11] =?UTF-8?q?feat:=20=EC=94=A8=EC=95=97=20=EC=8B=AC?= =?UTF-8?q?=EA=B8=B0=20API=20=EC=B6=94=EA=B0=80=20&=20=EB=AC=BC=20?= =?UTF-8?q?=EC=A3=BC=EA=B8=B0=20API=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/SharedPlantController.java | 21 +++-- .../controller/SharedPlantControllerDocs.java | 40 +++++++-- .../converter/SharedPlantConverter.java | 28 ++++++ .../garden/dto/res/SharedPlantResDTO.java | 26 ++++++ .../itda/domain/garden/entity/Plant.java | 4 + .../domain/garden/entity/SharedPlant.java | 13 +++ .../domain/garden/entity/SharedPlantLog.java | 4 + .../itda/domain/garden/enums/GardenState.java | 6 ++ .../domain/garden/enums/GardenTimeRule.java | 14 +++ .../exception/code/SharedPlantErrorCode.java | 14 ++- .../repository/SharedPlantLogRepository.java | 4 +- .../garden/service/GardenStateCalculator.java | 41 +++++++++ .../service/PlantActionResponseBuilder.java | 31 +++++++ .../command/SharedPlantCommandService.java | 4 + .../SharedPlantCommandServiceImpl.java | 90 +++++++++++++++++++ 15 files changed, 321 insertions(+), 19 deletions(-) create mode 100644 src/main/java/com/cotato/itda/domain/garden/enums/GardenState.java create mode 100644 src/main/java/com/cotato/itda/domain/garden/enums/GardenTimeRule.java create mode 100644 src/main/java/com/cotato/itda/domain/garden/service/GardenStateCalculator.java create mode 100644 src/main/java/com/cotato/itda/domain/garden/service/PlantActionResponseBuilder.java diff --git a/src/main/java/com/cotato/itda/domain/garden/controller/SharedPlantController.java b/src/main/java/com/cotato/itda/domain/garden/controller/SharedPlantController.java index 42efa8c..844f5e6 100644 --- a/src/main/java/com/cotato/itda/domain/garden/controller/SharedPlantController.java +++ b/src/main/java/com/cotato/itda/domain/garden/controller/SharedPlantController.java @@ -1,13 +1,11 @@ package com.cotato.itda.domain.garden.controller; -import com.cotato.itda.domain.garden.dto.req.SharedPlantReqDTO; import com.cotato.itda.domain.garden.dto.res.SharedPlantResDTO; import com.cotato.itda.domain.garden.enums.SharedPlantStatus; import com.cotato.itda.domain.garden.service.command.SharedPlantCommandService; import com.cotato.itda.domain.garden.service.query.SharedPlantQueryService; import com.cotato.itda.global.common.response.ApiResponse; import com.cotato.itda.global.security.jwt.principal.JwtPrincipal; -import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; @@ -22,7 +20,7 @@ public class SharedPlantController implements SharedPlantControllerDocs { private final SharedPlantCommandService sharedPlantCommandService; private final SharedPlantQueryService sharedPlantQueryService; - // TODO: 식물 시듦 상태 변환 & 식물 함께 돌봄으로 변환 & 솔로 모드 자동 진입 + // TODO: 식물 시듦 상태 변환 & 식물 함께 돌봄으로 변환 @GetMapping public ApiResponse getSharedPlants( @@ -35,12 +33,19 @@ public ApiResponse getSharedPlants( return ApiResponse.success(sharedPlantQueryService.getSharedPlants(jwtPrincipal.memberId(), effectiveStatuses)); } - @PatchMapping("/{sharedPlantId}") - public ApiResponse wateringPlant( + @PostMapping("/{sharedPlantId}/water") + public ApiResponse wateringPlant( @AuthenticationPrincipal JwtPrincipal jwtPrincipal, - @PathVariable(name = "sharedPlantId") Long sharedPlantId, - @Valid @RequestBody SharedPlantReqDTO.WaterPlantReqDTO dto + @PathVariable(name = "sharedPlantId") Long sharedPlantId ) { - return ApiResponse.success(sharedPlantCommandService.waterPlant(sharedPlantId, dto, jwtPrincipal.memberId())); + return ApiResponse.success(sharedPlantCommandService.water(sharedPlantId, jwtPrincipal.memberId())); + } + + @PostMapping("/{sharedPlantId}/plant") + public ApiResponse plantSeed( + @AuthenticationPrincipal JwtPrincipal jwtPrincipal, + @PathVariable(name = "sharedPlantId") Long sharedPlantId + ) { + return ApiResponse.success(sharedPlantCommandService.plantSeed(sharedPlantId, jwtPrincipal.memberId())); } } diff --git a/src/main/java/com/cotato/itda/domain/garden/controller/SharedPlantControllerDocs.java b/src/main/java/com/cotato/itda/domain/garden/controller/SharedPlantControllerDocs.java index 03861e9..b755dda 100644 --- a/src/main/java/com/cotato/itda/domain/garden/controller/SharedPlantControllerDocs.java +++ b/src/main/java/com/cotato/itda/domain/garden/controller/SharedPlantControllerDocs.java @@ -1,6 +1,5 @@ package com.cotato.itda.domain.garden.controller; -import com.cotato.itda.domain.garden.dto.req.SharedPlantReqDTO; import com.cotato.itda.domain.garden.dto.res.SharedPlantResDTO; import com.cotato.itda.domain.garden.enums.SharedPlantStatus; import com.cotato.itda.global.common.response.ApiResponse; @@ -10,12 +9,10 @@ import io.swagger.v3.oas.annotations.media.ArraySchema; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponses; -import jakarta.validation.Valid; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import java.util.List; @@ -37,16 +34,41 @@ ApiResponse getSharedPlants( @RequestParam(name = "status", required = false) List statuses ); - @Operation(summary = "공유 식물 물주기 API By 정원", description = "공유 식물에 물을 줍니다.") + @Operation(summary = "공유 식물 물주기 API By 정원", description = """ + 공유 식물에 물을 줍니다. + + **물주기 규칙:** + - 번갈아가며 물을 줘야 합니다 (연속 물주기 불가) + - 72시간 이상 경과 시 영양제를 먼저 줘야 합니다 + - 영양제 투여 후에는 같은 사람이 물을 줘야 합니다 + + **상태별 동작:** + - WITHERED (48h~72h): 물주기 가능, 상태 회복 + - NUTRITION_AVAILABLE (72h+): 영양제 필요 에러 반환 + - AFTER_NUTRITION: 영양제 준 본인만 물주기 가능 + """) + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "성공"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "씨앗이 심어지지 않음 / 연속 물주기 불가 / 영양제 필요 / 참여자가 아님"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "인증 실패"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "공유 식물을 찾을 수 없음") + }) + @PostMapping("/{sharedPlantId}/water") + ApiResponse wateringPlant( + @AuthenticationPrincipal JwtPrincipal jwtPrincipal, + @Parameter(description = "공유 식물 ID") @PathVariable(name = "sharedPlantId") Long sharedPlantId + ); + + @Operation(summary = "씨앗 심기 API By 정원", description = "초대 수락 후 씨앗을 심습니다.") @ApiResponses({ @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "성공"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "이미 심어진 식물 / 참여자가 아님"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "인증 실패"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "공유 식물을 찾을 수 없음") }) - @PatchMapping("/{sharedPlantId}") - ApiResponse wateringPlant( + @PostMapping("/{sharedPlantId}/plant") + ApiResponse plantSeed( @AuthenticationPrincipal JwtPrincipal jwtPrincipal, - @Parameter(description = "공유 식물 ID") @PathVariable(name = "sharedPlantId") Long sharedPlantId, - @Valid @RequestBody SharedPlantReqDTO.WaterPlantReqDTO dto + @Parameter(description = "공유 식물 ID") @PathVariable(name = "sharedPlantId") Long sharedPlantId ); } diff --git a/src/main/java/com/cotato/itda/domain/garden/converter/SharedPlantConverter.java b/src/main/java/com/cotato/itda/domain/garden/converter/SharedPlantConverter.java index d534264..3f3f2a2 100644 --- a/src/main/java/com/cotato/itda/domain/garden/converter/SharedPlantConverter.java +++ b/src/main/java/com/cotato/itda/domain/garden/converter/SharedPlantConverter.java @@ -5,6 +5,7 @@ import com.cotato.itda.domain.garden.dto.res.SharedPlantResDTO; import com.cotato.itda.domain.garden.entity.SharedPlant; import com.cotato.itda.domain.garden.entity.SharedPlantInvite; +import com.cotato.itda.domain.garden.enums.GardenState; import com.cotato.itda.domain.member.entity.Member; import java.time.LocalDateTime; @@ -113,4 +114,31 @@ public static SharedPlantResDTO.WaterInfoResDTO toWaterInfoResDTO(SharedPlant sh .nutrientCount(currentMember.getNutrientCount()) .build(); } + + public static SharedPlantResDTO.PlantActionResDTO toPlantActionResDTO( + SharedPlant sharedPlant, + Member currentMember, + GardenState gardenState, + int percentage + ) { + SharedPlantResDTO.LastWateredByDTO lastWateredBy = null; + if (sharedPlant.getLastWateredBy() != null) { + lastWateredBy = SharedPlantResDTO.LastWateredByDTO.builder() + .memberId(sharedPlant.getLastWateredBy()) + .isMe(sharedPlant.getLastWateredBy().equals(currentMember.getId())) + .build(); + } + + return SharedPlantResDTO.PlantActionResDTO.builder() + .sharedPlantId(sharedPlant.getId()) + .growthValue(sharedPlant.getGrowthValue()) + .percentage(percentage) + .growthStage(sharedPlant.getGrowthStage()) + .gardenState(gardenState) + .status(sharedPlant.getStatus()) + .isSoloMode(sharedPlant.getIsSoloMode()) + .lastWateredBy(lastWateredBy) + .nutrientCount(currentMember.getNutrientCount()) + .build(); + } } diff --git a/src/main/java/com/cotato/itda/domain/garden/dto/res/SharedPlantResDTO.java b/src/main/java/com/cotato/itda/domain/garden/dto/res/SharedPlantResDTO.java index e89bf5b..e69c788 100644 --- a/src/main/java/com/cotato/itda/domain/garden/dto/res/SharedPlantResDTO.java +++ b/src/main/java/com/cotato/itda/domain/garden/dto/res/SharedPlantResDTO.java @@ -1,5 +1,6 @@ package com.cotato.itda.domain.garden.dto.res; +import com.cotato.itda.domain.garden.enums.GardenState; import com.cotato.itda.domain.garden.enums.PlantStage; import com.cotato.itda.domain.garden.enums.SharedPlantStatus; import com.fasterxml.jackson.annotation.JsonProperty; @@ -10,8 +11,33 @@ public class SharedPlantResDTO { + @Builder + @Schema(description = "식물 액션 후 공통 응답 DTO") + public record PlantActionResDTO( + @Schema(description = "공유 식물 ID", example = "1") + Long sharedPlantId, + @Schema(description = "성장 정도", example = "12") + Integer growthValue, + @Schema(description = "성장 퍼센티지", example = "45") + Integer percentage, + @Schema(description = "성장 단계", example = "SPROUT") + PlantStage growthStage, + @Schema(description = "정원 상태", example = "WATERED_RECENTLY") + GardenState gardenState, + @Schema(description = "DB 상태", example = "GROWING") + SharedPlantStatus status, + @JsonProperty("isSoloMode") + @Schema(description = "혼자 돌봄 모드 여부", example = "false") + boolean isSoloMode, + @Schema(description = "마지막으로 물 준 회원 정보") + LastWateredByDTO lastWateredBy, + @Schema(description = "현재 회원이 보유한 영양제 총 개수", example = "10") + Integer nutrientCount + ) {} + @Builder @Schema(description = "물 주기 후 식물 정보 응답 DTO") + @Deprecated public record WaterInfoResDTO( @Schema(description = "공유 식물 ID", example = "1") Long sharedPlantId, diff --git a/src/main/java/com/cotato/itda/domain/garden/entity/Plant.java b/src/main/java/com/cotato/itda/domain/garden/entity/Plant.java index 7569c21..2224712 100644 --- a/src/main/java/com/cotato/itda/domain/garden/entity/Plant.java +++ b/src/main/java/com/cotato/itda/domain/garden/entity/Plant.java @@ -45,6 +45,10 @@ public class Plant extends BaseEntity { @Column(name = "bloom_max", nullable = false) private int bloomMax; + @Column(name = "unlock_required", nullable = false) + @Builder.Default + private int unlockRequired = 0; + public PlantStage calculateGrowthStage(int growthValue) { if (growthValue <= seedMax) return PlantStage.SEED; else if (growthValue <= sproutMax) return PlantStage.SPROUT; diff --git a/src/main/java/com/cotato/itda/domain/garden/entity/SharedPlant.java b/src/main/java/com/cotato/itda/domain/garden/entity/SharedPlant.java index b8f7d41..49eddbd 100644 --- a/src/main/java/com/cotato/itda/domain/garden/entity/SharedPlant.java +++ b/src/main/java/com/cotato/itda/domain/garden/entity/SharedPlant.java @@ -8,6 +8,7 @@ import lombok.*; import java.time.LocalDate; +import java.time.LocalDateTime; @Entity @Builder @@ -51,6 +52,9 @@ public class SharedPlant extends BaseEntity { @Column(name = "last_watered_by") private Long lastWateredBy; + @Column(name = "last_watered_at") + private LocalDateTime lastWateredAt; + @Column(name = "is_solo_mode", nullable = false) @Builder.Default private Boolean isSoloMode = false; @@ -63,6 +67,10 @@ public class SharedPlant extends BaseEntity { @Builder.Default private SharedPlantStatus status = SharedPlantStatus.GROWING; + @Column(name = "is_planted", nullable = false) + @Builder.Default + private boolean isPlanted = false; + public void water(int growthValue, Member member, boolean isFirst) { this.growthValue += growthValue; this.growthStage = this.plant.calculateGrowthStage(this.growthValue); @@ -70,6 +78,7 @@ public void water(int growthValue, Member member, boolean isFirst) { else this.dailyGrowthCount++; this.lastWateredBy = member.getId(); this.growthDate = LocalDate.now(); + this.lastWateredAt = LocalDateTime.now(); } public void nutrient(int growthValue, Member member) { @@ -94,4 +103,8 @@ public void revive() { public boolean hasReachedMaxGrowth() { return this.growthValue >= this.plant.getBloomMax(); } + + public void plant() { + this.isPlanted = true; + } } diff --git a/src/main/java/com/cotato/itda/domain/garden/entity/SharedPlantLog.java b/src/main/java/com/cotato/itda/domain/garden/entity/SharedPlantLog.java index 748f96b..936842c 100644 --- a/src/main/java/com/cotato/itda/domain/garden/entity/SharedPlantLog.java +++ b/src/main/java/com/cotato/itda/domain/garden/entity/SharedPlantLog.java @@ -42,6 +42,10 @@ public class SharedPlantLog { @Builder.Default private boolean usedNutrient = false; + @Column(name = "stage_changed", nullable = false) + @Builder.Default + private boolean stageChanged = false; + @CreatedDate @Column(name = "created_at", nullable = false, updatable = false) private LocalDateTime createdAt; diff --git a/src/main/java/com/cotato/itda/domain/garden/enums/GardenState.java b/src/main/java/com/cotato/itda/domain/garden/enums/GardenState.java new file mode 100644 index 0000000..87067d1 --- /dev/null +++ b/src/main/java/com/cotato/itda/domain/garden/enums/GardenState.java @@ -0,0 +1,6 @@ +package com.cotato.itda.domain.garden.enums; + +public enum GardenState { + EMPTY, SEED_READY, GROWING, WATERED_RECENTLY, WATERABLE, WITHERED, + NUTRITION_AVAILABLE, AFTER_NUTRITION, COMPLETED +} diff --git a/src/main/java/com/cotato/itda/domain/garden/enums/GardenTimeRule.java b/src/main/java/com/cotato/itda/domain/garden/enums/GardenTimeRule.java new file mode 100644 index 0000000..25250e1 --- /dev/null +++ b/src/main/java/com/cotato/itda/domain/garden/enums/GardenTimeRule.java @@ -0,0 +1,14 @@ +package com.cotato.itda.domain.garden.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum GardenTimeRule { + WATERABLE(24), + WITHERED(48), + NUTRITION_AVAILABLE(72); + + private final int hours; +} diff --git a/src/main/java/com/cotato/itda/domain/garden/exception/code/SharedPlantErrorCode.java b/src/main/java/com/cotato/itda/domain/garden/exception/code/SharedPlantErrorCode.java index aa5a467..f6d1048 100644 --- a/src/main/java/com/cotato/itda/domain/garden/exception/code/SharedPlantErrorCode.java +++ b/src/main/java/com/cotato/itda/domain/garden/exception/code/SharedPlantErrorCode.java @@ -31,6 +31,7 @@ public enum SharedPlantErrorCode implements ErrorCode { "SHARED_PLANT_400_NO_PERMISSION_TO_WATER_SOLO_PLANT", "혼자 돌봄 모드에서 물을 줄 권한이 없습니다." ), + CANNOT_WATER_CONSECUTIVELY( HttpStatus.BAD_REQUEST, "SHARED_PLANT_400_CANNOT_WATER_CONSECUTIVELY", @@ -57,7 +58,7 @@ public enum SharedPlantErrorCode implements ErrorCode { SHARED_PLANT_NOT_FOUND( HttpStatus.NOT_FOUND, - "SHARED_PLANT_400_SHARED_PLANT_NOT_FOUND", + "SHARED_PLANT_404_SHARED_PLANT_NOT_FOUND", "함께 키우는 식물을 찾지 못했습니다." ), @@ -85,6 +86,17 @@ public enum SharedPlantErrorCode implements ErrorCode { "시든 식물에는 영양제가 필요합니다." ), + ALREADY_PLANTED( + HttpStatus.BAD_REQUEST, + "SHARED_PLANT_400_ALREADY_PLANTED", + "이미 씨앗이 심겨진 식물입니다." + ), + + NOT_PLANTED( + HttpStatus.BAD_REQUEST, + "SHARED_PLANT_400_NOT_PLANTED", + "아직 씨앗이 심기지 않았습니다." + ), ; private final HttpStatus httpStatus; diff --git a/src/main/java/com/cotato/itda/domain/garden/repository/SharedPlantLogRepository.java b/src/main/java/com/cotato/itda/domain/garden/repository/SharedPlantLogRepository.java index 7dc36e4..c369b89 100644 --- a/src/main/java/com/cotato/itda/domain/garden/repository/SharedPlantLogRepository.java +++ b/src/main/java/com/cotato/itda/domain/garden/repository/SharedPlantLogRepository.java @@ -2,13 +2,13 @@ import com.cotato.itda.domain.garden.entity.SharedPlant; import com.cotato.itda.domain.garden.entity.SharedPlantLog; -import com.cotato.itda.domain.member.entity.Member; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import java.time.LocalDateTime; import java.util.List; +import java.util.Optional; public interface SharedPlantLogRepository extends JpaRepository { @@ -28,4 +28,6 @@ List findLatestLogsBySharedPlantIdsAndMemberId( @Param("sharedPlantIds") List sharedPlantIds, @Param("memberId") Long memberId ); + + Optional findTopBySharedPlantOrderByCreatedAtDesc(SharedPlant sharedPlant); } diff --git a/src/main/java/com/cotato/itda/domain/garden/service/GardenStateCalculator.java b/src/main/java/com/cotato/itda/domain/garden/service/GardenStateCalculator.java new file mode 100644 index 0000000..b8db48d --- /dev/null +++ b/src/main/java/com/cotato/itda/domain/garden/service/GardenStateCalculator.java @@ -0,0 +1,41 @@ +package com.cotato.itda.domain.garden.service; + +import com.cotato.itda.domain.garden.entity.SharedPlant; +import com.cotato.itda.domain.garden.entity.SharedPlantLog; +import com.cotato.itda.domain.garden.enums.GardenState; +import com.cotato.itda.domain.garden.enums.SharedPlantStatus; +import com.cotato.itda.domain.garden.repository.SharedPlantLogRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.Optional; + +@Component +@RequiredArgsConstructor +public class GardenStateCalculator { + + private final SharedPlantLogRepository sharedPlantLogRepository; + + public GardenState calculate(SharedPlant plant) { + if (plant.getStatus() == SharedPlantStatus.COMPLETED) return GardenState.COMPLETED; + if (!plant.isPlanted()) return GardenState.SEED_READY; + + Optional lastLog = sharedPlantLogRepository.findTopBySharedPlantOrderByCreatedAtDesc(plant); + if (lastLog.isEmpty()) return GardenState.SEED_READY; + + SharedPlantLog log = lastLog.get(); + long hours = ChronoUnit.HOURS.between(log.getWateredAt(), LocalDateTime.now()); + + if (log.isUsedNutrient() && plant.getStatus() == SharedPlantStatus.WITHERED) { + return GardenState.AFTER_NUTRITION; + } + + if (hours >= 72) return GardenState.NUTRITION_AVAILABLE; + if (hours >= 48) return GardenState.WITHERED; + if (log.isStageChanged()) return GardenState.GROWING; + if (hours <= 24) return GardenState.WATERED_RECENTLY; + return GardenState.WATERABLE; + } +} diff --git a/src/main/java/com/cotato/itda/domain/garden/service/PlantActionResponseBuilder.java b/src/main/java/com/cotato/itda/domain/garden/service/PlantActionResponseBuilder.java new file mode 100644 index 0000000..deb4eb7 --- /dev/null +++ b/src/main/java/com/cotato/itda/domain/garden/service/PlantActionResponseBuilder.java @@ -0,0 +1,31 @@ +package com.cotato.itda.domain.garden.service; + +import com.cotato.itda.domain.garden.converter.SharedPlantConverter; +import com.cotato.itda.domain.garden.dto.res.SharedPlantResDTO; +import com.cotato.itda.domain.garden.entity.SharedPlant; +import com.cotato.itda.domain.garden.enums.GardenState; +import com.cotato.itda.domain.member.entity.Member; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class PlantActionResponseBuilder { + + private final GardenStateCalculator gardenStateCalculator; + + public SharedPlantResDTO.PlantActionResDTO build(SharedPlant plant, Member member) { + GardenState gardenState = gardenStateCalculator.calculate(plant); + int percentage = calculatePercentage(plant); + + return SharedPlantConverter.toPlantActionResDTO(plant, member, gardenState, percentage); + } + + private int calculatePercentage(SharedPlant plant) { + int bloomMax = plant.getPlant().getBloomMax(); + if (bloomMax <= 0) return 0; + + int percentage = (plant.getGrowthValue() * 100) / bloomMax; + return Math.min(percentage, 100); + } +} diff --git a/src/main/java/com/cotato/itda/domain/garden/service/command/SharedPlantCommandService.java b/src/main/java/com/cotato/itda/domain/garden/service/command/SharedPlantCommandService.java index 0624df9..192920f 100644 --- a/src/main/java/com/cotato/itda/domain/garden/service/command/SharedPlantCommandService.java +++ b/src/main/java/com/cotato/itda/domain/garden/service/command/SharedPlantCommandService.java @@ -5,4 +5,8 @@ public interface SharedPlantCommandService { SharedPlantResDTO.WaterInfoResDTO waterPlant(Long sharedPlantId, SharedPlantReqDTO.WaterPlantReqDTO dto, Long memberId); + + SharedPlantResDTO.PlantActionResDTO water(Long sharedPlantId, Long memberId); + + SharedPlantResDTO.PlantActionResDTO plantSeed(Long sharedPlantId, Long memberId); } diff --git a/src/main/java/com/cotato/itda/domain/garden/service/command/SharedPlantCommandServiceImpl.java b/src/main/java/com/cotato/itda/domain/garden/service/command/SharedPlantCommandServiceImpl.java index ef7a0f7..c3e3fe2 100644 --- a/src/main/java/com/cotato/itda/domain/garden/service/command/SharedPlantCommandServiceImpl.java +++ b/src/main/java/com/cotato/itda/domain/garden/service/command/SharedPlantCommandServiceImpl.java @@ -6,12 +6,15 @@ import com.cotato.itda.domain.garden.dto.res.SharedPlantResDTO; import com.cotato.itda.domain.garden.entity.SharedPlant; import com.cotato.itda.domain.garden.entity.SharedPlantLog; +import com.cotato.itda.domain.garden.enums.GardenTimeRule; +import com.cotato.itda.domain.garden.enums.GrowthType; import com.cotato.itda.domain.garden.enums.SharedPlantStatus; import com.cotato.itda.domain.garden.enums.SupplyType; import com.cotato.itda.domain.garden.exception.SharedPlantException; import com.cotato.itda.domain.garden.exception.code.SharedPlantErrorCode; import com.cotato.itda.domain.garden.repository.SharedPlantLogRepository; import com.cotato.itda.domain.garden.repository.SharedPlantRepository; +import com.cotato.itda.domain.garden.service.PlantActionResponseBuilder; import com.cotato.itda.domain.garden.service.command.strategy.WateringStrategy; import com.cotato.itda.domain.member.entity.Member; import com.cotato.itda.domain.member.repository.MemberRepository; @@ -21,6 +24,8 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; import java.util.List; @Service @@ -32,6 +37,7 @@ public class SharedPlantCommandServiceImpl implements SharedPlantCommandService private final SharedPlantRepository sharedPlantRepository; private final MemberRepository memberRepository; private final SharedPlantLogRepository sharedPlantLogRepository; + private final PlantActionResponseBuilder plantActionResponseBuilder; @Override public SharedPlantResDTO.WaterInfoResDTO waterPlant(Long sharedPlantId, SharedPlantReqDTO.WaterPlantReqDTO dto, Long memberId) { @@ -97,4 +103,88 @@ public SharedPlantResDTO.WaterInfoResDTO waterPlant(Long sharedPlantId, SharedPl return SharedPlantConverter.toWaterInfoResDTO(sharedPlant, currentMember); } + + @Override + public SharedPlantResDTO.PlantActionResDTO water(Long sharedPlantId, Long memberId) { + Member member = findMemberById(memberId); + SharedPlant sharedPlant = findSharedPlantById(sharedPlantId); + validateParticipant(sharedPlant, memberId); + + if (!sharedPlant.isPlanted()) { + throw new SharedPlantException(SharedPlantErrorCode.NOT_PLANTED); + } + + if (sharedPlant.getStatus() == SharedPlantStatus.COMPLETED) { + throw new SharedPlantException(SharedPlantErrorCode.CANNOT_WATER_COMPLETED_PLANT); + } + + + boolean wasWithered = sharedPlant.getStatus() == SharedPlantStatus.WITHERED; + if (wasWithered && sharedPlant.getLastWateredAt() != null + && ChronoUnit.HOURS.between(sharedPlant.getLastWateredAt(), LocalDateTime.now()) >= GardenTimeRule.NUTRITION_AVAILABLE.getHours()) { + SharedPlantLog lastLog = sharedPlantLogRepository.findTopBySharedPlantOrderByCreatedAtDesc(sharedPlant).orElse(null); + + boolean isAfterMyNutrition = lastLog != null + && lastLog.isUsedNutrient() + && lastLog.getWateredBy().equals(memberId); + + if (!isAfterMyNutrition) { + throw new SharedPlantException(SharedPlantErrorCode.WITHERED_REQUIRES_NUTRIENT); + } + } + + if (sharedPlant.getIsSoloMode() && !memberId.equals(sharedPlant.getSoloPowerMemberId())) { + sharedPlant.exitSoloMode(); + } + + if (sharedPlant.getLastWateredBy() != null && memberId.equals(sharedPlant.getLastWateredBy())) { + throw new SharedPlantException(SharedPlantErrorCode.CANNOT_WATER_CONSECUTIVELY); + } + + boolean isFirst = sharedPlant.getLastWateredAt() == null && sharedPlant.getLastWateredBy() == null && sharedPlant.getGrowthDate() == null; + sharedPlant.water(GrowthType.WATER.getGrowth(), member, isFirst); + + if (wasWithered) { + sharedPlant.revive(); + } + + if (sharedPlant.hasReachedMaxGrowth()) { + sharedPlant.complete(); + } + + sharedPlantLogRepository.save(SharedPlantLogConverter.toSharedPlantLog(sharedPlant, memberId, true, GrowthType.WATER.getGrowth(), false)); + + return plantActionResponseBuilder.build(sharedPlant, member); + } + + @Override + public SharedPlantResDTO.PlantActionResDTO plantSeed(Long sharedPlantId, Long memberId) { + Member member = findMemberById(memberId); + SharedPlant sharedPlant = findSharedPlantById(sharedPlantId); + validateParticipant(sharedPlant, memberId); + + if (sharedPlant.isPlanted()) { + throw new SharedPlantException(SharedPlantErrorCode.ALREADY_PLANTED); + } + + sharedPlant.plant(); + + return plantActionResponseBuilder.build(sharedPlant, member); + } + + private Member findMemberById(Long memberId) { + return memberRepository.findById(memberId) + .orElseThrow(() -> new BusinessException(UserErrorCode.USER_NOT_FOUND)); + } + + private SharedPlant findSharedPlantById(Long sharedPlantId) { + return sharedPlantRepository.findById(sharedPlantId) + .orElseThrow(() -> new SharedPlantException(SharedPlantErrorCode.SHARED_PLANT_NOT_FOUND)); + } + + private void validateParticipant(SharedPlant sharedPlant, Long memberId) { + if (!(memberId.equals(sharedPlant.getMemberA().getId()) || memberId.equals(sharedPlant.getMemberB().getId()))) { + throw new SharedPlantException(SharedPlantErrorCode.NOT_A_PARTICIPANT); + } + } } From 13f568cf683c9f963f68d5f2671b4eb0dbdaa82d Mon Sep 17 00:00:00 2001 From: ccjngwn Date: Wed, 11 Feb 2026 18:30:45 +0900 Subject: [PATCH 02/11] =?UTF-8?q?feat:=20=EC=98=81=EC=96=91=EC=A0=9C=20API?= =?UTF-8?q?=20=EC=97=94=EB=93=9C=ED=8F=AC=EC=9D=B8=ED=8A=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/SharedPlantController.java | 8 ++++ .../controller/SharedPlantControllerDocs.java | 30 +++++++++++++- .../exception/code/SharedPlantErrorCode.java | 16 +++++--- .../command/SharedPlantCommandService.java | 2 + .../SharedPlantCommandServiceImpl.java | 39 ++++++++++++++++++- 5 files changed, 86 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/cotato/itda/domain/garden/controller/SharedPlantController.java b/src/main/java/com/cotato/itda/domain/garden/controller/SharedPlantController.java index 844f5e6..f81342a 100644 --- a/src/main/java/com/cotato/itda/domain/garden/controller/SharedPlantController.java +++ b/src/main/java/com/cotato/itda/domain/garden/controller/SharedPlantController.java @@ -41,6 +41,14 @@ public ApiResponse wateringPlant( return ApiResponse.success(sharedPlantCommandService.water(sharedPlantId, jwtPrincipal.memberId())); } + @PostMapping("/{sharedPlantId}/nutrient") + public ApiResponse giveNutrient( + @AuthenticationPrincipal JwtPrincipal jwtPrincipal, + @PathVariable(name = "sharedPlantId") Long sharedPlantId + ) { + return ApiResponse.success(sharedPlantCommandService.giveNutrient(sharedPlantId, jwtPrincipal.memberId())); + } + @PostMapping("/{sharedPlantId}/plant") public ApiResponse plantSeed( @AuthenticationPrincipal JwtPrincipal jwtPrincipal, diff --git a/src/main/java/com/cotato/itda/domain/garden/controller/SharedPlantControllerDocs.java b/src/main/java/com/cotato/itda/domain/garden/controller/SharedPlantControllerDocs.java index b755dda..2287e74 100644 --- a/src/main/java/com/cotato/itda/domain/garden/controller/SharedPlantControllerDocs.java +++ b/src/main/java/com/cotato/itda/domain/garden/controller/SharedPlantControllerDocs.java @@ -49,8 +49,9 @@ ApiResponse getSharedPlants( """) @ApiResponses({ @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "성공"), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "씨앗이 심어지지 않음 / 연속 물주기 불가 / 영양제 필요 / 참여자가 아님"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "씨앗이 심어지지 않음 / 연속 물주기 불가 / 영양제 필요 / 완료된 식물"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "인증 실패"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "403", description = "참여자가 아님"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "공유 식물을 찾을 수 없음") }) @PostMapping("/{sharedPlantId}/water") @@ -59,11 +60,36 @@ ApiResponse wateringPlant( @Parameter(description = "공유 식물 ID") @PathVariable(name = "sharedPlantId") Long sharedPlantId ); + @Operation(summary = "영양제 주기 API By 정원", description = """ + 시든 식물에 영양제를 줍니다. + + **영양제 규칙:** + - 72시간 이상 물을 주지 않은 경우에만 사용 가능 + - 영양제 투여 후 같은 사람이 물을 줘야 식물이 회복됩니다 + - 영양제 보유 개수가 1개 이상이어야 합니다 + + **상태 변화:** + - NUTRITION_AVAILABLE (72h+) → AFTER_NUTRITION + """) + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "성공"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "씨앗이 심어지지 않음 / 영양제를 줄 수 없음 / 영양제 없음 / 완료된 식물"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "인증 실패"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "403", description = "참여자가 아님"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "공유 식물을 찾을 수 없음") + }) + @PostMapping("/{sharedPlantId}/nutrient") + ApiResponse giveNutrient( + @AuthenticationPrincipal JwtPrincipal jwtPrincipal, + @Parameter(description = "공유 식물 ID") @PathVariable(name = "sharedPlantId") Long sharedPlantId + ); + @Operation(summary = "씨앗 심기 API By 정원", description = "초대 수락 후 씨앗을 심습니다.") @ApiResponses({ @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "성공"), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "이미 심어진 식물 / 참여자가 아님"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "이미 심어진 식물 / 완료된 식물"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "인증 실패"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "403", description = "참여자가 아님"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "공유 식물을 찾을 수 없음") }) @PostMapping("/{sharedPlantId}/plant") diff --git a/src/main/java/com/cotato/itda/domain/garden/exception/code/SharedPlantErrorCode.java b/src/main/java/com/cotato/itda/domain/garden/exception/code/SharedPlantErrorCode.java index f6d1048..b1444f8 100644 --- a/src/main/java/com/cotato/itda/domain/garden/exception/code/SharedPlantErrorCode.java +++ b/src/main/java/com/cotato/itda/domain/garden/exception/code/SharedPlantErrorCode.java @@ -69,15 +69,15 @@ public enum SharedPlantErrorCode implements ErrorCode { ), NOT_A_PARTICIPANT( - HttpStatus.BAD_REQUEST, - "SHARED_PLANT_400_NOT_A_PARTICIPANT", + HttpStatus.FORBIDDEN, + "SHARED_PLANT_403_NOT_A_PARTICIPANT", "함께 키우는 식물 참여자가 아닙니다." ), - CANNOT_WATER_COMPLETED_PLANT( + CANNOT_ACTION_COMPLETED_PLANT( HttpStatus.BAD_REQUEST, - "SHARED_PLANT_400_CANNOT_WATER_COMPLETED_PLANT", - "키우기가 완료된 식물에는 물을 줄 수 없습니다." + "SHARED_PLANT_400_CANNOT_ACTION_COMPLETED_PLANT", + "키우기가 완료된 식물에는 행동할 수 없습니다." ), WITHERED_REQUIRES_NUTRIENT( @@ -97,6 +97,12 @@ public enum SharedPlantErrorCode implements ErrorCode { "SHARED_PLANT_400_NOT_PLANTED", "아직 씨앗이 심기지 않았습니다." ), + + CANNOT_GIVE_NUTRIENT( + HttpStatus.BAD_REQUEST, + "SHARED_PLANT_400_CANNOT_GIVE_NUTRIENT", + "아직 영양제를 줄 수 없습니다." + ), ; private final HttpStatus httpStatus; diff --git a/src/main/java/com/cotato/itda/domain/garden/service/command/SharedPlantCommandService.java b/src/main/java/com/cotato/itda/domain/garden/service/command/SharedPlantCommandService.java index 192920f..3e5e88c 100644 --- a/src/main/java/com/cotato/itda/domain/garden/service/command/SharedPlantCommandService.java +++ b/src/main/java/com/cotato/itda/domain/garden/service/command/SharedPlantCommandService.java @@ -8,5 +8,7 @@ public interface SharedPlantCommandService { SharedPlantResDTO.PlantActionResDTO water(Long sharedPlantId, Long memberId); + SharedPlantResDTO.PlantActionResDTO giveNutrient(Long sharedPlantId, Long memberId); + SharedPlantResDTO.PlantActionResDTO plantSeed(Long sharedPlantId, Long memberId); } diff --git a/src/main/java/com/cotato/itda/domain/garden/service/command/SharedPlantCommandServiceImpl.java b/src/main/java/com/cotato/itda/domain/garden/service/command/SharedPlantCommandServiceImpl.java index c3e3fe2..62fcf09 100644 --- a/src/main/java/com/cotato/itda/domain/garden/service/command/SharedPlantCommandServiceImpl.java +++ b/src/main/java/com/cotato/itda/domain/garden/service/command/SharedPlantCommandServiceImpl.java @@ -56,7 +56,7 @@ public SharedPlantResDTO.WaterInfoResDTO waterPlant(Long sharedPlantId, SharedPl } if (sharedPlant.getStatus() == SharedPlantStatus.COMPLETED) { - throw new SharedPlantException(SharedPlantErrorCode.CANNOT_WATER_COMPLETED_PLANT); + throw new SharedPlantException(SharedPlantErrorCode.CANNOT_ACTION_COMPLETED_PLANT); } boolean wasWithered = sharedPlant.getStatus() == SharedPlantStatus.WITHERED; @@ -115,7 +115,7 @@ public SharedPlantResDTO.PlantActionResDTO water(Long sharedPlantId, Long member } if (sharedPlant.getStatus() == SharedPlantStatus.COMPLETED) { - throw new SharedPlantException(SharedPlantErrorCode.CANNOT_WATER_COMPLETED_PLANT); + throw new SharedPlantException(SharedPlantErrorCode.CANNOT_ACTION_COMPLETED_PLANT); } @@ -157,6 +157,41 @@ public SharedPlantResDTO.PlantActionResDTO water(Long sharedPlantId, Long member return plantActionResponseBuilder.build(sharedPlant, member); } + @Override + public SharedPlantResDTO.PlantActionResDTO giveNutrient(Long sharedPlantId, Long memberId) { + Member member = findMemberById(memberId); + SharedPlant sharedPlant = findSharedPlantById(sharedPlantId); + validateParticipant(sharedPlant, memberId); + + if (!sharedPlant.isPlanted()) { + throw new SharedPlantException(SharedPlantErrorCode.NOT_PLANTED); + } + + if (sharedPlant.getStatus() == SharedPlantStatus.COMPLETED) { + throw new SharedPlantException(SharedPlantErrorCode.CANNOT_ACTION_COMPLETED_PLANT); + } + + if (sharedPlant.getStatus() != SharedPlantStatus.WITHERED + || sharedPlant.getLastWateredAt() == null + || ChronoUnit.HOURS.between(sharedPlant.getLastWateredAt(), LocalDateTime.now()) < GardenTimeRule.NUTRITION_AVAILABLE.getHours()) { + throw new SharedPlantException(SharedPlantErrorCode.CANNOT_GIVE_NUTRIENT); + } + + if (member.getNutrientCount() <= 0) { + throw new SharedPlantException(SharedPlantErrorCode.DONT_HAVE_NUTRIENT); + } + + if (sharedPlant.getIsSoloMode() && !memberId.equals(sharedPlant.getSoloPowerMemberId())) { + sharedPlant.exitSoloMode(); + } + + sharedPlant.nutrient(GrowthType.NUTRIENT.getGrowth(), member); + + sharedPlantLogRepository.save(SharedPlantLogConverter.toSharedPlantLog(sharedPlant, memberId, true, GrowthType.NUTRIENT.getGrowth(), true)); + + return plantActionResponseBuilder.build(sharedPlant, member); + } + @Override public SharedPlantResDTO.PlantActionResDTO plantSeed(Long sharedPlantId, Long memberId) { Member member = findMemberById(memberId); From a4d5e9165928e7eb8454b1c5bb5fed17171eefda Mon Sep 17 00:00:00 2001 From: ccjngwn Date: Wed, 11 Feb 2026 18:33:57 +0900 Subject: [PATCH 03/11] =?UTF-8?q?remove:=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EC=A0=84=EB=9E=B5=20/=20?= =?UTF-8?q?=EB=8D=B0=EC=BD=94=EB=A0=88=EC=9D=B4=ED=84=B0=20=ED=8C=A8?= =?UTF-8?q?=ED=84=B4=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/code/SharedPlantErrorCode.java | 36 --------- .../command/SharedPlantCommandService.java | 1 - .../SharedPlantCommandServiceImpl.java | 71 ----------------- .../strategy/NutrientWateringStrategy.java | 78 ------------------- .../strategy/SoloWateringStrategy.java | 32 -------- .../strategy/TeamWateringStrategy.java | 50 ------------ .../command/strategy/WateringStrategy.java | 26 ------- 7 files changed, 294 deletions(-) delete mode 100644 src/main/java/com/cotato/itda/domain/garden/service/command/strategy/NutrientWateringStrategy.java delete mode 100644 src/main/java/com/cotato/itda/domain/garden/service/command/strategy/SoloWateringStrategy.java delete mode 100644 src/main/java/com/cotato/itda/domain/garden/service/command/strategy/TeamWateringStrategy.java delete mode 100644 src/main/java/com/cotato/itda/domain/garden/service/command/strategy/WateringStrategy.java diff --git a/src/main/java/com/cotato/itda/domain/garden/exception/code/SharedPlantErrorCode.java b/src/main/java/com/cotato/itda/domain/garden/exception/code/SharedPlantErrorCode.java index b1444f8..5346777 100644 --- a/src/main/java/com/cotato/itda/domain/garden/exception/code/SharedPlantErrorCode.java +++ b/src/main/java/com/cotato/itda/domain/garden/exception/code/SharedPlantErrorCode.java @@ -14,60 +14,24 @@ public enum SharedPlantErrorCode implements ErrorCode { "SHARED_PLANT_400_ALREADY_HAS_SHARED_PLANT", "이미 키우고 있는 식물이 있습니다."), - ALREADY_WATERED( - HttpStatus.BAD_REQUEST, - "SHARED_PLANT_400_ALREADY_WATERED", - "이미 물을 줬습니다." - ), - - EXCEED_WATER_COUNT( - HttpStatus.BAD_REQUEST, - "SHARED_PLANT_400_EXCEED_WATER_COUNT", - "물 주기 가능 횟수를 초과했습니다." - ), - - NO_PERMISSION_TO_WATER_SOLO_PLANT( - HttpStatus.BAD_REQUEST, - "SHARED_PLANT_400_NO_PERMISSION_TO_WATER_SOLO_PLANT", - "혼자 돌봄 모드에서 물을 줄 권한이 없습니다." - ), - CANNOT_WATER_CONSECUTIVELY( HttpStatus.BAD_REQUEST, "SHARED_PLANT_400_CANNOT_WATER_CONSECUTIVELY", "연속으로 물을 줄 수 없습니다." ), - NOT_FOUND_STRATEGY( - HttpStatus.NOT_FOUND, - "SHARED_PLANT_404_NOT_FOUND_STRATEGY", - "적절한 기본 물주기 전략을 찾을 수 없습니다." - ), - DONT_HAVE_NUTRIENT( HttpStatus.BAD_REQUEST, "SHARED_PLANT_400_DONT_HAVE_NUTRIENT", "영양제가 없습니다." ), - ALREADY_GAVE_NUTRIENT_TODAY( - HttpStatus.BAD_REQUEST, - "SHARED_PLANT_400_ALREADY_GAVE_NUTRIENT_TODAY", - "오늘 이미 영양제를 줬습니다." - ), - SHARED_PLANT_NOT_FOUND( HttpStatus.NOT_FOUND, "SHARED_PLANT_404_SHARED_PLANT_NOT_FOUND", "함께 키우는 식물을 찾지 못했습니다." ), - CANNOT_USE_NUTRIENT_YET( - HttpStatus.BAD_REQUEST, - "SHARED_PLANT_400_CANNOT_USE_NUTRIENT_YET", - "아직 영양제를 줄 수 없습니다." - ), - NOT_A_PARTICIPANT( HttpStatus.FORBIDDEN, "SHARED_PLANT_403_NOT_A_PARTICIPANT", diff --git a/src/main/java/com/cotato/itda/domain/garden/service/command/SharedPlantCommandService.java b/src/main/java/com/cotato/itda/domain/garden/service/command/SharedPlantCommandService.java index 3e5e88c..a7bd425 100644 --- a/src/main/java/com/cotato/itda/domain/garden/service/command/SharedPlantCommandService.java +++ b/src/main/java/com/cotato/itda/domain/garden/service/command/SharedPlantCommandService.java @@ -4,7 +4,6 @@ import com.cotato.itda.domain.garden.dto.res.SharedPlantResDTO; public interface SharedPlantCommandService { - SharedPlantResDTO.WaterInfoResDTO waterPlant(Long sharedPlantId, SharedPlantReqDTO.WaterPlantReqDTO dto, Long memberId); SharedPlantResDTO.PlantActionResDTO water(Long sharedPlantId, Long memberId); diff --git a/src/main/java/com/cotato/itda/domain/garden/service/command/SharedPlantCommandServiceImpl.java b/src/main/java/com/cotato/itda/domain/garden/service/command/SharedPlantCommandServiceImpl.java index 62fcf09..49cfc2a 100644 --- a/src/main/java/com/cotato/itda/domain/garden/service/command/SharedPlantCommandServiceImpl.java +++ b/src/main/java/com/cotato/itda/domain/garden/service/command/SharedPlantCommandServiceImpl.java @@ -1,21 +1,17 @@ package com.cotato.itda.domain.garden.service.command; -import com.cotato.itda.domain.garden.converter.SharedPlantConverter; import com.cotato.itda.domain.garden.converter.SharedPlantLogConverter; -import com.cotato.itda.domain.garden.dto.req.SharedPlantReqDTO; import com.cotato.itda.domain.garden.dto.res.SharedPlantResDTO; import com.cotato.itda.domain.garden.entity.SharedPlant; import com.cotato.itda.domain.garden.entity.SharedPlantLog; import com.cotato.itda.domain.garden.enums.GardenTimeRule; import com.cotato.itda.domain.garden.enums.GrowthType; import com.cotato.itda.domain.garden.enums.SharedPlantStatus; -import com.cotato.itda.domain.garden.enums.SupplyType; import com.cotato.itda.domain.garden.exception.SharedPlantException; import com.cotato.itda.domain.garden.exception.code.SharedPlantErrorCode; import com.cotato.itda.domain.garden.repository.SharedPlantLogRepository; import com.cotato.itda.domain.garden.repository.SharedPlantRepository; import com.cotato.itda.domain.garden.service.PlantActionResponseBuilder; -import com.cotato.itda.domain.garden.service.command.strategy.WateringStrategy; import com.cotato.itda.domain.member.entity.Member; import com.cotato.itda.domain.member.repository.MemberRepository; import com.cotato.itda.global.error.constant.UserErrorCode; @@ -26,84 +22,17 @@ import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; -import java.util.List; @Service @RequiredArgsConstructor @Transactional public class SharedPlantCommandServiceImpl implements SharedPlantCommandService { - private final List wateringStrategies; private final SharedPlantRepository sharedPlantRepository; private final MemberRepository memberRepository; private final SharedPlantLogRepository sharedPlantLogRepository; private final PlantActionResponseBuilder plantActionResponseBuilder; - @Override - public SharedPlantResDTO.WaterInfoResDTO waterPlant(Long sharedPlantId, SharedPlantReqDTO.WaterPlantReqDTO dto, Long memberId) { - - Member currentMember = memberRepository.findById(memberId) - .orElseThrow(() -> new BusinessException(UserErrorCode.USER_NOT_FOUND)); - SharedPlant sharedPlant = sharedPlantRepository.findById(sharedPlantId) - .orElseThrow(() -> new SharedPlantException(SharedPlantErrorCode.SHARED_PLANT_NOT_FOUND)); - - Long memberAId = sharedPlant.getMemberA().getId(); - Long memberBId = sharedPlant.getMemberB().getId(); - Long currentMemberId = currentMember.getId(); - - if (!(currentMemberId.equals(memberAId) || currentMemberId.equals(memberBId))) { - throw new SharedPlantException(SharedPlantErrorCode.NOT_A_PARTICIPANT); - } - - if (sharedPlant.getStatus() == SharedPlantStatus.COMPLETED) { - throw new SharedPlantException(SharedPlantErrorCode.CANNOT_ACTION_COMPLETED_PLANT); - } - - boolean wasWithered = sharedPlant.getStatus() == SharedPlantStatus.WITHERED; - if (wasWithered && dto.supplyType() != SupplyType.NUTRIENT) { - throw new SharedPlantException(SharedPlantErrorCode.WITHERED_REQUIRES_NUTRIENT); - } - - if (sharedPlant.getIsSoloMode() && !sharedPlant.getSoloPowerMemberId().equals(currentMemberId)) { - sharedPlant.exitSoloMode(); - } - - int growthBefore = sharedPlant.getGrowthValue(); - - WateringStrategy strategy = wateringStrategies.stream() - .filter(s -> s.supports(sharedPlant, dto)) - .findFirst() - .orElseThrow(() -> new SharedPlantException(SharedPlantErrorCode.NOT_FOUND_STRATEGY)); - - strategy.water(sharedPlant, currentMember, wateringStrategies); - - if (wasWithered) { - sharedPlant.revive(); - } - - // 최대 성장 도달 시 COMPLETED 처리 - if (sharedPlant.hasReachedMaxGrowth()) { - sharedPlant.complete(); - } - - int growthAfter = sharedPlant.getGrowthValue(); - int growthIncrement = growthAfter - growthBefore; - boolean affectedGrowth = growthIncrement > 0; - boolean usedNutrient = dto.supplyType() == SupplyType.NUTRIENT; - - SharedPlantLog log = SharedPlantLogConverter.toSharedPlantLog( - sharedPlant, - currentMember.getId(), - affectedGrowth, - growthIncrement, - usedNutrient - ); - - sharedPlantLogRepository.save(log); - - return SharedPlantConverter.toWaterInfoResDTO(sharedPlant, currentMember); - } - @Override public SharedPlantResDTO.PlantActionResDTO water(Long sharedPlantId, Long memberId) { Member member = findMemberById(memberId); diff --git a/src/main/java/com/cotato/itda/domain/garden/service/command/strategy/NutrientWateringStrategy.java b/src/main/java/com/cotato/itda/domain/garden/service/command/strategy/NutrientWateringStrategy.java deleted file mode 100644 index ec52010..0000000 --- a/src/main/java/com/cotato/itda/domain/garden/service/command/strategy/NutrientWateringStrategy.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.cotato.itda.domain.garden.service.command.strategy; - -import com.cotato.itda.domain.garden.dto.req.SharedPlantReqDTO; -import com.cotato.itda.domain.garden.entity.SharedPlant; -import com.cotato.itda.domain.garden.enums.GrowthType; -import com.cotato.itda.domain.garden.enums.SupplyType; -import com.cotato.itda.domain.garden.exception.SharedPlantException; -import com.cotato.itda.domain.garden.exception.code.SharedPlantErrorCode; -import com.cotato.itda.domain.garden.repository.SharedPlantLogRepository; -import com.cotato.itda.domain.member.entity.Member; -import org.springframework.stereotype.Component; - -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.time.temporal.ChronoUnit; -import java.util.List; - -@Component -public class NutrientWateringStrategy implements WateringStrategy { - - private final SharedPlantLogRepository sharedPlantLogRepository; - - public NutrientWateringStrategy(SharedPlantLogRepository sharedPlantLogRepository) { - this.sharedPlantLogRepository = sharedPlantLogRepository; - } - - @Override - public boolean supports(SharedPlant sharedPlant, SharedPlantReqDTO.WaterPlantReqDTO dto) { - return dto.supplyType() == SupplyType.NUTRIENT; - } - - @Override - public void water(SharedPlant sharedPlant, Member currentMember, List allStrategies) { - WateringStrategy baseStrategy = findBaseStrategy(sharedPlant, allStrategies); - - if (sharedPlant.getGrowthDate() != null) { - long daysSinceWatered = Math.max(0, ChronoUnit.DAYS.between(sharedPlant.getGrowthDate(), LocalDate.now()) - 1); - if (daysSinceWatered <= 1) { - throw new SharedPlantException(SharedPlantErrorCode.CANNOT_USE_NUTRIENT_YET); - } - } - - if (currentMember.getNutrientCount() <= 0) { - throw new SharedPlantException(SharedPlantErrorCode.DONT_HAVE_NUTRIENT); - } - - LocalDateTime startOfDay = LocalDate.now().atStartOfDay(); - LocalDateTime endOfDay = LocalDate.now().atTime(LocalTime.MAX); - - boolean alreadyGaveNutrient = sharedPlantLogRepository.existsBySharedPlantAndWateredByAndUsedNutrientAndCreatedAtBetween( - sharedPlant, - currentMember.getId(), - true, - startOfDay, - endOfDay - ); - - if (alreadyGaveNutrient) { - throw new SharedPlantException(SharedPlantErrorCode.ALREADY_GAVE_NUTRIENT_TODAY); - } - - baseStrategy.water(sharedPlant, currentMember, allStrategies); - - sharedPlant.nutrient(GrowthType.NUTRIENT.getGrowth(), currentMember); - } - - private WateringStrategy findBaseStrategy(SharedPlant sharedPlant, List allStrategies) { - - SharedPlantReqDTO.WaterPlantReqDTO dto = new SharedPlantReqDTO.WaterPlantReqDTO(SupplyType.WATER); - - return allStrategies.stream() - .filter(strategy -> !(strategy instanceof NutrientWateringStrategy)) - .filter(strategy -> strategy.supports(sharedPlant, dto)) - .findFirst() - .orElseThrow(() -> new SharedPlantException(SharedPlantErrorCode.NOT_FOUND_STRATEGY)); - } -} diff --git a/src/main/java/com/cotato/itda/domain/garden/service/command/strategy/SoloWateringStrategy.java b/src/main/java/com/cotato/itda/domain/garden/service/command/strategy/SoloWateringStrategy.java deleted file mode 100644 index 8c0abe3..0000000 --- a/src/main/java/com/cotato/itda/domain/garden/service/command/strategy/SoloWateringStrategy.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.cotato.itda.domain.garden.service.command.strategy; - -import com.cotato.itda.domain.garden.dto.req.SharedPlantReqDTO; -import com.cotato.itda.domain.garden.entity.SharedPlant; -import com.cotato.itda.domain.garden.enums.GrowthType; -import com.cotato.itda.domain.garden.enums.SupplyType; -import com.cotato.itda.domain.garden.exception.SharedPlantException; -import com.cotato.itda.domain.garden.exception.code.SharedPlantErrorCode; -import com.cotato.itda.domain.member.entity.Member; -import org.springframework.stereotype.Component; - -import java.time.LocalDate; -import java.util.List; - -@Component -public class SoloWateringStrategy implements WateringStrategy { - - @Override - public boolean supports(SharedPlant sharedPlant, SharedPlantReqDTO.WaterPlantReqDTO dto) { - return sharedPlant.getIsSoloMode() && dto.supplyType() == SupplyType.WATER; - } - - @Override - public void water(SharedPlant sharedPlant, Member currentMember, List allStrategies) { - - if (sharedPlant.getGrowthDate().isEqual(LocalDate.now())) { - throw new SharedPlantException(SharedPlantErrorCode.ALREADY_WATERED); - } - - sharedPlant.water(GrowthType.WATER.getGrowth(), currentMember, true); - } -} diff --git a/src/main/java/com/cotato/itda/domain/garden/service/command/strategy/TeamWateringStrategy.java b/src/main/java/com/cotato/itda/domain/garden/service/command/strategy/TeamWateringStrategy.java deleted file mode 100644 index 7b19bf1..0000000 --- a/src/main/java/com/cotato/itda/domain/garden/service/command/strategy/TeamWateringStrategy.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.cotato.itda.domain.garden.service.command.strategy; - -import com.cotato.itda.domain.garden.dto.req.SharedPlantReqDTO; -import com.cotato.itda.domain.garden.entity.SharedPlant; -import com.cotato.itda.domain.garden.enums.GrowthType; -import com.cotato.itda.domain.garden.enums.MaxGrowthCount; -import com.cotato.itda.domain.garden.enums.SupplyType; -import com.cotato.itda.domain.garden.exception.SharedPlantException; -import com.cotato.itda.domain.garden.exception.code.SharedPlantErrorCode; -import com.cotato.itda.domain.member.entity.Member; -import org.springframework.stereotype.Component; - -import java.time.LocalDate; -import java.util.List; - -@Component -public class TeamWateringStrategy implements WateringStrategy { - - @Override - public boolean supports(SharedPlant sharedPlant, SharedPlantReqDTO.WaterPlantReqDTO dto) { - return !sharedPlant.getIsSoloMode() && dto.supplyType() == SupplyType.WATER; - } - - @Override - public void water(SharedPlant sharedPlant, Member currentMember, List allStrategies) { - - if (sharedPlant.getLastWateredBy() == null && sharedPlant.getGrowthDate() == null) { - sharedPlant.water(GrowthType.WATER.getGrowth(), currentMember, true); - return; - } - - if (sharedPlant.getLastWateredBy() != null && sharedPlant.getLastWateredBy().equals(currentMember.getId())) { - throw new SharedPlantException(SharedPlantErrorCode.CANNOT_WATER_CONSECUTIVELY); - } - - if (sharedPlant.getGrowthDate().isEqual(LocalDate.now())) { - if (sharedPlant.getDailyGrowthCount() >= MaxGrowthCount.TEAM_GROW.getMaxGrowthCount()) { - throw new SharedPlantException(SharedPlantErrorCode.EXCEED_WATER_COUNT); - } - - if (sharedPlant.getDailyGrowthCount() < MaxGrowthCount.TEAM_WATER.maxGrowthCount) { - sharedPlant.water(GrowthType.WATER.getGrowth(), currentMember, false); - } else { - sharedPlant.water(GrowthType.NONE.getGrowth(), currentMember, false); - } - } else { - sharedPlant.water(GrowthType.WATER.getGrowth(), currentMember, true); - } - } -} diff --git a/src/main/java/com/cotato/itda/domain/garden/service/command/strategy/WateringStrategy.java b/src/main/java/com/cotato/itda/domain/garden/service/command/strategy/WateringStrategy.java deleted file mode 100644 index dc2274b..0000000 --- a/src/main/java/com/cotato/itda/domain/garden/service/command/strategy/WateringStrategy.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.cotato.itda.domain.garden.service.command.strategy; - -import com.cotato.itda.domain.garden.dto.req.SharedPlantReqDTO; -import com.cotato.itda.domain.garden.entity.SharedPlant; -import com.cotato.itda.domain.member.entity.Member; - -import java.util.List; - -public interface WateringStrategy { - - /** - * 이 전략이 현재 요청을 처리해야 하는지 판단합니다. - * @param sharedPlant 대상 식물 - * @param dto 요청 DTO - * @return 처리해야 하면 true - */ - boolean supports(SharedPlant sharedPlant, SharedPlantReqDTO.WaterPlantReqDTO dto); - - /** - * 실제 물 주기 또는 추가 기능 로직을 실행합니다. - * @param sharedPlant 대상 식물 - * @param currentMember 요청한 회원 - * @param allStrategies 데코레이터가 기본 전략을 찾기 위해 필요한 모든 전략 리스트 - */ - void water(SharedPlant sharedPlant, Member currentMember, List allStrategies); -} From 598be9454fd4bcbcc1ea04377bb15bba102dfc43 Mon Sep 17 00:00:00 2001 From: ccjngwn Date: Wed, 11 Feb 2026 18:46:39 +0900 Subject: [PATCH 04/11] =?UTF-8?q?remove:=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EC=A3=BC=EC=84=9D=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/garden/service/command/SharedPlantCommandService.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/com/cotato/itda/domain/garden/service/command/SharedPlantCommandService.java b/src/main/java/com/cotato/itda/domain/garden/service/command/SharedPlantCommandService.java index a7bd425..2b58c10 100644 --- a/src/main/java/com/cotato/itda/domain/garden/service/command/SharedPlantCommandService.java +++ b/src/main/java/com/cotato/itda/domain/garden/service/command/SharedPlantCommandService.java @@ -1,6 +1,5 @@ package com.cotato.itda.domain.garden.service.command; -import com.cotato.itda.domain.garden.dto.req.SharedPlantReqDTO; import com.cotato.itda.domain.garden.dto.res.SharedPlantResDTO; public interface SharedPlantCommandService { From 2100ad207c4f44a27f6af1d1b05bf1bbed954e36 Mon Sep 17 00:00:00 2001 From: ccjngwn Date: Wed, 11 Feb 2026 19:36:28 +0900 Subject: [PATCH 05/11] =?UTF-8?q?refactor:=20SharedPlantValidator=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=B6=94=EC=B6=9C=20=EB=B0=8F=20?= =?UTF-8?q?=EB=AF=B8=EC=82=AC=EC=9A=A9=20=ED=95=84=EB=93=9C=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/garden/entity/SharedPlant.java | 13 +-- .../garden/service/SharedPlantValidator.java | 92 +++++++++++++++++++ .../SharedPlantCommandServiceImpl.java | 80 +++++----------- 3 files changed, 115 insertions(+), 70 deletions(-) create mode 100644 src/main/java/com/cotato/itda/domain/garden/service/SharedPlantValidator.java diff --git a/src/main/java/com/cotato/itda/domain/garden/entity/SharedPlant.java b/src/main/java/com/cotato/itda/domain/garden/entity/SharedPlant.java index 49eddbd..b748344 100644 --- a/src/main/java/com/cotato/itda/domain/garden/entity/SharedPlant.java +++ b/src/main/java/com/cotato/itda/domain/garden/entity/SharedPlant.java @@ -7,7 +7,6 @@ import jakarta.persistence.*; import lombok.*; -import java.time.LocalDate; import java.time.LocalDateTime; @Entity @@ -42,13 +41,6 @@ public class SharedPlant extends BaseEntity { @Builder.Default private PlantStage growthStage = PlantStage.SEED; - @Column(name = "daily_growth_count", nullable = false) - @Builder.Default - private int dailyGrowthCount = 0; - - @Column(name = "growth_date") - private LocalDate growthDate; - @Column(name = "last_watered_by") private Long lastWateredBy; @@ -71,13 +63,10 @@ public class SharedPlant extends BaseEntity { @Builder.Default private boolean isPlanted = false; - public void water(int growthValue, Member member, boolean isFirst) { + public void water(int growthValue, Member member) { this.growthValue += growthValue; this.growthStage = this.plant.calculateGrowthStage(this.growthValue); - if (isFirst) this.dailyGrowthCount = 1; - else this.dailyGrowthCount++; this.lastWateredBy = member.getId(); - this.growthDate = LocalDate.now(); this.lastWateredAt = LocalDateTime.now(); } diff --git a/src/main/java/com/cotato/itda/domain/garden/service/SharedPlantValidator.java b/src/main/java/com/cotato/itda/domain/garden/service/SharedPlantValidator.java new file mode 100644 index 0000000..fbf6b87 --- /dev/null +++ b/src/main/java/com/cotato/itda/domain/garden/service/SharedPlantValidator.java @@ -0,0 +1,92 @@ +package com.cotato.itda.domain.garden.service; + +import com.cotato.itda.domain.garden.entity.SharedPlant; +import com.cotato.itda.domain.garden.entity.SharedPlantLog; +import com.cotato.itda.domain.garden.enums.GardenTimeRule; +import com.cotato.itda.domain.garden.enums.SharedPlantStatus; +import com.cotato.itda.domain.garden.exception.SharedPlantException; +import com.cotato.itda.domain.garden.exception.code.SharedPlantErrorCode; +import com.cotato.itda.domain.garden.repository.SharedPlantLogRepository; +import com.cotato.itda.domain.member.entity.Member; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; + +@Component +@RequiredArgsConstructor +public class SharedPlantValidator { + + private final SharedPlantLogRepository sharedPlantLogRepository; + + // 참여자 검증 + public void validateParticipant(SharedPlant sharedPlant, Long memberId) { + if (!(memberId.equals(sharedPlant.getMemberA().getId()) || memberId.equals(sharedPlant.getMemberB().getId()))) { + throw new SharedPlantException(SharedPlantErrorCode.NOT_A_PARTICIPANT); + } + } + + // 완료되지 않은 상태 검증 + public void validateNotCompleted(SharedPlant sharedPlant) { + if (sharedPlant.getStatus() == SharedPlantStatus.COMPLETED) { + throw new SharedPlantException(SharedPlantErrorCode.CANNOT_ACTION_COMPLETED_PLANT); + } + } + + // 심어진 상태 검증 + public void validatePlanted(SharedPlant sharedPlant) { + if (!sharedPlant.isPlanted()) { + throw new SharedPlantException(SharedPlantErrorCode.NOT_PLANTED); + } + } + + // 심어지지 않은 상태 검증 + public void validateNotPlanted(SharedPlant sharedPlant) { + if (sharedPlant.isPlanted()) { + throw new SharedPlantException(SharedPlantErrorCode.ALREADY_PLANTED); + } + } + + // 물주기 가능 여부 검증 (72시간 시듦 체크 + 연속 물 주기 체크) + public void validateCanWater(SharedPlant sharedPlant, Long memberId) { + validatePlanted(sharedPlant); + validateNotCompleted(sharedPlant); + + // 72시간 + 시듦 상태면 영양제 먼저 줘야함 + boolean wasWithered = sharedPlant.getStatus() == SharedPlantStatus.WITHERED; + if (wasWithered && sharedPlant.getLastWateredAt() != null + && ChronoUnit.HOURS.between(sharedPlant.getLastWateredAt(), LocalDateTime.now()) >= GardenTimeRule.NUTRITION_AVAILABLE.getHours()) { + SharedPlantLog lastLog = sharedPlantLogRepository.findTopBySharedPlantOrderByCreatedAtDesc(sharedPlant).orElse(null); + + boolean isAfterMyNutrition = lastLog != null + && lastLog.isUsedNutrient() + && lastLog.getWateredBy().equals(memberId); + + if (!isAfterMyNutrition) { + throw new SharedPlantException(SharedPlantErrorCode.WITHERED_REQUIRES_NUTRIENT); + } + } + + if (sharedPlant.getLastWateredBy() != null && memberId.equals(sharedPlant.getLastWateredBy())) { + throw new SharedPlantException(SharedPlantErrorCode.CANNOT_WATER_CONSECUTIVELY); + } + } + + // 영양제 주기 가능 여부 검증 (WITHERED + 72h + 영양제 보유) + public void validateCanGiveNutrient(SharedPlant sharedPlant, Member member) { + validatePlanted(sharedPlant); + validateNotCompleted(sharedPlant); + + if (sharedPlant.getStatus() != SharedPlantStatus.WITHERED + || sharedPlant.getLastWateredAt() == null + || ChronoUnit.HOURS.between(sharedPlant.getLastWateredAt(), LocalDateTime.now()) < GardenTimeRule.NUTRITION_AVAILABLE.getHours() + ) { + throw new SharedPlantException(SharedPlantErrorCode.CANNOT_GIVE_NUTRIENT); + } + + if (member.getNutrientCount() <= 0) { + throw new SharedPlantException(SharedPlantErrorCode.DONT_HAVE_NUTRIENT); + } + } +} diff --git a/src/main/java/com/cotato/itda/domain/garden/service/command/SharedPlantCommandServiceImpl.java b/src/main/java/com/cotato/itda/domain/garden/service/command/SharedPlantCommandServiceImpl.java index 49cfc2a..93d260d 100644 --- a/src/main/java/com/cotato/itda/domain/garden/service/command/SharedPlantCommandServiceImpl.java +++ b/src/main/java/com/cotato/itda/domain/garden/service/command/SharedPlantCommandServiceImpl.java @@ -3,8 +3,6 @@ import com.cotato.itda.domain.garden.converter.SharedPlantLogConverter; import com.cotato.itda.domain.garden.dto.res.SharedPlantResDTO; import com.cotato.itda.domain.garden.entity.SharedPlant; -import com.cotato.itda.domain.garden.entity.SharedPlantLog; -import com.cotato.itda.domain.garden.enums.GardenTimeRule; import com.cotato.itda.domain.garden.enums.GrowthType; import com.cotato.itda.domain.garden.enums.SharedPlantStatus; import com.cotato.itda.domain.garden.exception.SharedPlantException; @@ -12,6 +10,7 @@ import com.cotato.itda.domain.garden.repository.SharedPlantLogRepository; import com.cotato.itda.domain.garden.repository.SharedPlantRepository; import com.cotato.itda.domain.garden.service.PlantActionResponseBuilder; +import com.cotato.itda.domain.garden.service.SharedPlantValidator; import com.cotato.itda.domain.member.entity.Member; import com.cotato.itda.domain.member.repository.MemberRepository; import com.cotato.itda.global.error.constant.UserErrorCode; @@ -20,9 +19,6 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.time.LocalDateTime; -import java.time.temporal.ChronoUnit; - @Service @RequiredArgsConstructor @Transactional @@ -32,55 +28,39 @@ public class SharedPlantCommandServiceImpl implements SharedPlantCommandService private final MemberRepository memberRepository; private final SharedPlantLogRepository sharedPlantLogRepository; private final PlantActionResponseBuilder plantActionResponseBuilder; + private final SharedPlantValidator sharedPlantValidator; @Override public SharedPlantResDTO.PlantActionResDTO water(Long sharedPlantId, Long memberId) { Member member = findMemberById(memberId); SharedPlant sharedPlant = findSharedPlantById(sharedPlantId); - validateParticipant(sharedPlant, memberId); - if (!sharedPlant.isPlanted()) { - throw new SharedPlantException(SharedPlantErrorCode.NOT_PLANTED); - } - - if (sharedPlant.getStatus() == SharedPlantStatus.COMPLETED) { - throw new SharedPlantException(SharedPlantErrorCode.CANNOT_ACTION_COMPLETED_PLANT); - } + // 참여자 검증 + sharedPlantValidator.validateParticipant(sharedPlant, memberId); + // 물주기 가능 여부 검증 + sharedPlantValidator.validateCanWater(sharedPlant, memberId); boolean wasWithered = sharedPlant.getStatus() == SharedPlantStatus.WITHERED; - if (wasWithered && sharedPlant.getLastWateredAt() != null - && ChronoUnit.HOURS.between(sharedPlant.getLastWateredAt(), LocalDateTime.now()) >= GardenTimeRule.NUTRITION_AVAILABLE.getHours()) { - SharedPlantLog lastLog = sharedPlantLogRepository.findTopBySharedPlantOrderByCreatedAtDesc(sharedPlant).orElse(null); - - boolean isAfterMyNutrition = lastLog != null - && lastLog.isUsedNutrient() - && lastLog.getWateredBy().equals(memberId); - - if (!isAfterMyNutrition) { - throw new SharedPlantException(SharedPlantErrorCode.WITHERED_REQUIRES_NUTRIENT); - } - } + // 혼자 돌봄 모드 종료 로직 if (sharedPlant.getIsSoloMode() && !memberId.equals(sharedPlant.getSoloPowerMemberId())) { sharedPlant.exitSoloMode(); } - if (sharedPlant.getLastWateredBy() != null && memberId.equals(sharedPlant.getLastWateredBy())) { - throw new SharedPlantException(SharedPlantErrorCode.CANNOT_WATER_CONSECUTIVELY); - } - - boolean isFirst = sharedPlant.getLastWateredAt() == null && sharedPlant.getLastWateredBy() == null && sharedPlant.getGrowthDate() == null; - sharedPlant.water(GrowthType.WATER.getGrowth(), member, isFirst); + sharedPlant.water(GrowthType.WATER.getGrowth(), member); + // WITHERED 상태에서 물을 주면 GROWING으로 if (wasWithered) { sharedPlant.revive(); } + // 성장 완료 시 COMPLETED로 if (sharedPlant.hasReachedMaxGrowth()) { sharedPlant.complete(); } + // 로그 저장 sharedPlantLogRepository.save(SharedPlantLogConverter.toSharedPlantLog(sharedPlant, memberId, true, GrowthType.WATER.getGrowth(), false)); return plantActionResponseBuilder.build(sharedPlant, member); @@ -90,32 +70,21 @@ public SharedPlantResDTO.PlantActionResDTO water(Long sharedPlantId, Long member public SharedPlantResDTO.PlantActionResDTO giveNutrient(Long sharedPlantId, Long memberId) { Member member = findMemberById(memberId); SharedPlant sharedPlant = findSharedPlantById(sharedPlantId); - validateParticipant(sharedPlant, memberId); - - if (!sharedPlant.isPlanted()) { - throw new SharedPlantException(SharedPlantErrorCode.NOT_PLANTED); - } - if (sharedPlant.getStatus() == SharedPlantStatus.COMPLETED) { - throw new SharedPlantException(SharedPlantErrorCode.CANNOT_ACTION_COMPLETED_PLANT); - } + // 참여자 검증 + sharedPlantValidator.validateParticipant(sharedPlant, memberId); - if (sharedPlant.getStatus() != SharedPlantStatus.WITHERED - || sharedPlant.getLastWateredAt() == null - || ChronoUnit.HOURS.between(sharedPlant.getLastWateredAt(), LocalDateTime.now()) < GardenTimeRule.NUTRITION_AVAILABLE.getHours()) { - throw new SharedPlantException(SharedPlantErrorCode.CANNOT_GIVE_NUTRIENT); - } - - if (member.getNutrientCount() <= 0) { - throw new SharedPlantException(SharedPlantErrorCode.DONT_HAVE_NUTRIENT); - } + // 영양제 주기 가능 여부 검증 + sharedPlantValidator.validateCanGiveNutrient(sharedPlant, member); + // 혼자 돌봄 모드 -> 공동 돌봄 모드로 if (sharedPlant.getIsSoloMode() && !memberId.equals(sharedPlant.getSoloPowerMemberId())) { sharedPlant.exitSoloMode(); } sharedPlant.nutrient(GrowthType.NUTRIENT.getGrowth(), member); + // 로그 저장 sharedPlantLogRepository.save(SharedPlantLogConverter.toSharedPlantLog(sharedPlant, memberId, true, GrowthType.NUTRIENT.getGrowth(), true)); return plantActionResponseBuilder.build(sharedPlant, member); @@ -125,11 +94,12 @@ public SharedPlantResDTO.PlantActionResDTO giveNutrient(Long sharedPlantId, Long public SharedPlantResDTO.PlantActionResDTO plantSeed(Long sharedPlantId, Long memberId) { Member member = findMemberById(memberId); SharedPlant sharedPlant = findSharedPlantById(sharedPlantId); - validateParticipant(sharedPlant, memberId); - if (sharedPlant.isPlanted()) { - throw new SharedPlantException(SharedPlantErrorCode.ALREADY_PLANTED); - } + // 참여자 검증 + sharedPlantValidator.validateParticipant(sharedPlant, memberId); + + // 심어지지 않은 상태 검증 + sharedPlantValidator.validateNotPlanted(sharedPlant); sharedPlant.plant(); @@ -145,10 +115,4 @@ private SharedPlant findSharedPlantById(Long sharedPlantId) { return sharedPlantRepository.findById(sharedPlantId) .orElseThrow(() -> new SharedPlantException(SharedPlantErrorCode.SHARED_PLANT_NOT_FOUND)); } - - private void validateParticipant(SharedPlant sharedPlant, Long memberId) { - if (!(memberId.equals(sharedPlant.getMemberA().getId()) || memberId.equals(sharedPlant.getMemberB().getId()))) { - throw new SharedPlantException(SharedPlantErrorCode.NOT_A_PARTICIPANT); - } - } } From b289c3a160772bb36c08d035ec1f0ae08ce8bb12 Mon Sep 17 00:00:00 2001 From: ccjngwn Date: Wed, 11 Feb 2026 19:40:43 +0900 Subject: [PATCH 06/11] =?UTF-8?q?remove:=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20DTO=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../converter/SharedPlantConverter.java | 14 ------------- .../garden/dto/res/SharedPlantResDTO.java | 21 ------------------- 2 files changed, 35 deletions(-) diff --git a/src/main/java/com/cotato/itda/domain/garden/converter/SharedPlantConverter.java b/src/main/java/com/cotato/itda/domain/garden/converter/SharedPlantConverter.java index 3f3f2a2..87a507f 100644 --- a/src/main/java/com/cotato/itda/domain/garden/converter/SharedPlantConverter.java +++ b/src/main/java/com/cotato/itda/domain/garden/converter/SharedPlantConverter.java @@ -101,20 +101,6 @@ public static SharedPlantResDTO.LastWateredByDTO toLastWateredByDTO(Member membe .build(); } - public static SharedPlantResDTO.WaterInfoResDTO toWaterInfoResDTO(SharedPlant sharedPlant, Member currentMember) { - SharedPlantResDTO.LastWateredByDTO lastWateredBy = toLastWateredByDTO(currentMember); - - return SharedPlantResDTO.WaterInfoResDTO.builder() - .sharedPlantId(sharedPlant.getId()) - .growthValue(sharedPlant.getGrowthValue()) - .growthStage(sharedPlant.getGrowthStage()) - .lastWateredBy(lastWateredBy) - .status(sharedPlant.getStatus()) - .isSoloMode(sharedPlant.getIsSoloMode()) - .nutrientCount(currentMember.getNutrientCount()) - .build(); - } - public static SharedPlantResDTO.PlantActionResDTO toPlantActionResDTO( SharedPlant sharedPlant, Member currentMember, diff --git a/src/main/java/com/cotato/itda/domain/garden/dto/res/SharedPlantResDTO.java b/src/main/java/com/cotato/itda/domain/garden/dto/res/SharedPlantResDTO.java index e69c788..9f1160d 100644 --- a/src/main/java/com/cotato/itda/domain/garden/dto/res/SharedPlantResDTO.java +++ b/src/main/java/com/cotato/itda/domain/garden/dto/res/SharedPlantResDTO.java @@ -35,27 +35,6 @@ public record PlantActionResDTO( Integer nutrientCount ) {} - @Builder - @Schema(description = "물 주기 후 식물 정보 응답 DTO") - @Deprecated - public record WaterInfoResDTO( - @Schema(description = "공유 식물 ID", example = "1") - Long sharedPlantId, - @Schema(description = "성장 정도", example = "12") - Integer growthValue, - @Schema(description = "성장 단계", example = "SPROUT") - PlantStage growthStage, - @Schema(description = "마지막으로 물 준 회원 정보") - LastWateredByDTO lastWateredBy, - @Schema(description = "공유 식물의 현재 상태", example = "GROWING") - SharedPlantStatus status, - @JsonProperty("isSoloMode") - @Schema(description = "혼자 돌봄 모드 여부", example = "false") - boolean isSoloMode, - @Schema(description = "현재 회원이 보유한 영양제 총 개수", example = "10") - Integer nutrientCount - ) {} - @Builder @Schema(description = "마지막으로 물 준 회원 정보 DTO") public record LastWateredByDTO( From b56169154aae06baa436e7eecfff1420289de668 Mon Sep 17 00:00:00 2001 From: ccjngwn Date: Thu, 12 Feb 2026 17:36:56 +0900 Subject: [PATCH 07/11] =?UTF-8?q?fix:=20=EA=B3=B5=EC=9C=A0=20=EC=8B=9D?= =?UTF-8?q?=EB=AC=BC=20=EC=A1=B0=ED=9A=8C=20=EC=9D=91=EB=8B=B5=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../converter/SharedPlantConverter.java | 26 ++++----------- .../garden/dto/res/SharedPlantResDTO.java | 11 +++++-- .../garden/service/GardenStateCalculator.java | 10 +++++- .../service/PlantActionResponseBuilder.java | 31 ------------------ .../SharedPlantCommandServiceImpl.java | 23 ++++++++++--- .../query/SharedPlantQueryServiceImpl.java | 32 +++++++++---------- 6 files changed, 57 insertions(+), 76 deletions(-) delete mode 100644 src/main/java/com/cotato/itda/domain/garden/service/PlantActionResponseBuilder.java diff --git a/src/main/java/com/cotato/itda/domain/garden/converter/SharedPlantConverter.java b/src/main/java/com/cotato/itda/domain/garden/converter/SharedPlantConverter.java index 87a507f..a740e92 100644 --- a/src/main/java/com/cotato/itda/domain/garden/converter/SharedPlantConverter.java +++ b/src/main/java/com/cotato/itda/domain/garden/converter/SharedPlantConverter.java @@ -1,17 +1,14 @@ package com.cotato.itda.domain.garden.converter; import com.cotato.itda.domain.friendship.entity.Friendship; -import com.cotato.itda.domain.garden.dto.SharedPlantWithFriendship; import com.cotato.itda.domain.garden.dto.res.SharedPlantResDTO; import com.cotato.itda.domain.garden.entity.SharedPlant; import com.cotato.itda.domain.garden.entity.SharedPlantInvite; import com.cotato.itda.domain.garden.enums.GardenState; import com.cotato.itda.domain.member.entity.Member; -import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.List; -import java.util.Map; public class SharedPlantConverter { @@ -45,7 +42,8 @@ public static SharedPlantResDTO.SharedPlantInfoDTO toSharedPlantInfoDTO( SharedPlant sharedPlant, Friendship friendship, Long currentMemberId, - LocalDateTime myLastWateredAt + GardenState gardenState, + int percentage ) { SharedPlantResDTO.LastWateredByDTO lastWateredBy = null; if (sharedPlant.getLastWateredBy() != null) { @@ -62,31 +60,21 @@ public static SharedPlantResDTO.SharedPlantInfoDTO toSharedPlantInfoDTO( .plantId(sharedPlant.getPlant().getId()) .nickname(sharedPlant.getNickname()) .growthValue(sharedPlant.getGrowthValue()) + .percentage(percentage) .growthStage(sharedPlant.getGrowthStage()) + .gardenState(gardenState) .status(sharedPlant.getStatus()) .isSoloMode(sharedPlant.getIsSoloMode()) .lastWateredBy(lastWateredBy) - .myLastWateredAt(myLastWateredAt != null ? myLastWateredAt.format(FORMATTER) : null) + .lastWateredAt(sharedPlant.getLastWateredAt()) .createdAt(sharedPlant.getCreatedAt().format(FORMATTER)) .build(); } public static SharedPlantResDTO.SharedPlantInfoListDTO toSharedPlantInfoListDTO( - List sharedPlantsWithFriendships, - int nutrientCount, - Long currentMemberId, - Map myLastWateredAtMap + List sharedPlantInfoDTOs, + int nutrientCount ) { - List sharedPlantInfoDTOs = sharedPlantsWithFriendships.stream() - .filter(pair -> pair.friendship() != null) - .map(pair -> toSharedPlantInfoDTO( - pair.sharedPlant(), - pair.friendship(), - currentMemberId, - myLastWateredAtMap.get(pair.sharedPlant().getId()) - )) - .toList(); - return SharedPlantResDTO.SharedPlantInfoListDTO.builder() .totalCount(sharedPlantInfoDTOs.size()) .nutrientCount(nutrientCount) diff --git a/src/main/java/com/cotato/itda/domain/garden/dto/res/SharedPlantResDTO.java b/src/main/java/com/cotato/itda/domain/garden/dto/res/SharedPlantResDTO.java index 9f1160d..f364b20 100644 --- a/src/main/java/com/cotato/itda/domain/garden/dto/res/SharedPlantResDTO.java +++ b/src/main/java/com/cotato/itda/domain/garden/dto/res/SharedPlantResDTO.java @@ -7,6 +7,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; +import java.time.LocalDateTime; import java.util.List; public class SharedPlantResDTO { @@ -73,17 +74,21 @@ public record SharedPlantInfoDTO( String nickname, @Schema(description = "성장 정도", example = "0") int growthValue, + @Schema(description = "성장 퍼센티지", example = "45") + int percentage, @Schema(description = "성장 단계", example = "SEED") PlantStage growthStage, - @Schema(description = "상태", example = "GROWING") + @Schema(description = "정원 상태", example = "WATERABLE") + GardenState gardenState, + @Schema(description = "DB 상태", example = "GROWING") SharedPlantStatus status, @JsonProperty("isSoloMode") @Schema(description = "혼자 돌봄 모드 여부", example = "false") boolean isSoloMode, @Schema(description = "마지막으로 물 준 회원 정보") LastWateredByDTO lastWateredBy, - @Schema(description = "내가 마지막으로 물 준 시간 (물을 준 적 없으면 null)", example = "2025-01-15 14:30") - String myLastWateredAt, + @Schema(description = "마지막으로 물 준 시간") + LocalDateTime lastWateredAt, @Schema(description = "생성 일시", example = "2025-12-28 12:34") String createdAt ) {} diff --git a/src/main/java/com/cotato/itda/domain/garden/service/GardenStateCalculator.java b/src/main/java/com/cotato/itda/domain/garden/service/GardenStateCalculator.java index b8db48d..10460fd 100644 --- a/src/main/java/com/cotato/itda/domain/garden/service/GardenStateCalculator.java +++ b/src/main/java/com/cotato/itda/domain/garden/service/GardenStateCalculator.java @@ -18,7 +18,7 @@ public class GardenStateCalculator { private final SharedPlantLogRepository sharedPlantLogRepository; - public GardenState calculate(SharedPlant plant) { + public GardenState calculateState(SharedPlant plant) { if (plant.getStatus() == SharedPlantStatus.COMPLETED) return GardenState.COMPLETED; if (!plant.isPlanted()) return GardenState.SEED_READY; @@ -38,4 +38,12 @@ public GardenState calculate(SharedPlant plant) { if (hours <= 24) return GardenState.WATERED_RECENTLY; return GardenState.WATERABLE; } + + public int calculatePercentage(SharedPlant plant) { + int bloomMax = plant.getPlant().getBloomMax(); + if (bloomMax <= 0) return 0; + + int percentage = (plant.getGrowthValue() * 100) / bloomMax; + return Math.min(percentage, 100); + } } diff --git a/src/main/java/com/cotato/itda/domain/garden/service/PlantActionResponseBuilder.java b/src/main/java/com/cotato/itda/domain/garden/service/PlantActionResponseBuilder.java deleted file mode 100644 index deb4eb7..0000000 --- a/src/main/java/com/cotato/itda/domain/garden/service/PlantActionResponseBuilder.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.cotato.itda.domain.garden.service; - -import com.cotato.itda.domain.garden.converter.SharedPlantConverter; -import com.cotato.itda.domain.garden.dto.res.SharedPlantResDTO; -import com.cotato.itda.domain.garden.entity.SharedPlant; -import com.cotato.itda.domain.garden.enums.GardenState; -import com.cotato.itda.domain.member.entity.Member; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Component; - -@Component -@RequiredArgsConstructor -public class PlantActionResponseBuilder { - - private final GardenStateCalculator gardenStateCalculator; - - public SharedPlantResDTO.PlantActionResDTO build(SharedPlant plant, Member member) { - GardenState gardenState = gardenStateCalculator.calculate(plant); - int percentage = calculatePercentage(plant); - - return SharedPlantConverter.toPlantActionResDTO(plant, member, gardenState, percentage); - } - - private int calculatePercentage(SharedPlant plant) { - int bloomMax = plant.getPlant().getBloomMax(); - if (bloomMax <= 0) return 0; - - int percentage = (plant.getGrowthValue() * 100) / bloomMax; - return Math.min(percentage, 100); - } -} diff --git a/src/main/java/com/cotato/itda/domain/garden/service/command/SharedPlantCommandServiceImpl.java b/src/main/java/com/cotato/itda/domain/garden/service/command/SharedPlantCommandServiceImpl.java index 93d260d..015c9ea 100644 --- a/src/main/java/com/cotato/itda/domain/garden/service/command/SharedPlantCommandServiceImpl.java +++ b/src/main/java/com/cotato/itda/domain/garden/service/command/SharedPlantCommandServiceImpl.java @@ -1,5 +1,6 @@ package com.cotato.itda.domain.garden.service.command; +import com.cotato.itda.domain.garden.converter.SharedPlantConverter; import com.cotato.itda.domain.garden.converter.SharedPlantLogConverter; import com.cotato.itda.domain.garden.dto.res.SharedPlantResDTO; import com.cotato.itda.domain.garden.entity.SharedPlant; @@ -9,7 +10,7 @@ import com.cotato.itda.domain.garden.exception.code.SharedPlantErrorCode; import com.cotato.itda.domain.garden.repository.SharedPlantLogRepository; import com.cotato.itda.domain.garden.repository.SharedPlantRepository; -import com.cotato.itda.domain.garden.service.PlantActionResponseBuilder; +import com.cotato.itda.domain.garden.service.GardenStateCalculator; import com.cotato.itda.domain.garden.service.SharedPlantValidator; import com.cotato.itda.domain.member.entity.Member; import com.cotato.itda.domain.member.repository.MemberRepository; @@ -27,7 +28,7 @@ public class SharedPlantCommandServiceImpl implements SharedPlantCommandService private final SharedPlantRepository sharedPlantRepository; private final MemberRepository memberRepository; private final SharedPlantLogRepository sharedPlantLogRepository; - private final PlantActionResponseBuilder plantActionResponseBuilder; + private final GardenStateCalculator gardenStateCalculator; private final SharedPlantValidator sharedPlantValidator; @Override @@ -63,7 +64,11 @@ public SharedPlantResDTO.PlantActionResDTO water(Long sharedPlantId, Long member // 로그 저장 sharedPlantLogRepository.save(SharedPlantLogConverter.toSharedPlantLog(sharedPlant, memberId, true, GrowthType.WATER.getGrowth(), false)); - return plantActionResponseBuilder.build(sharedPlant, member); + return SharedPlantConverter.toPlantActionResDTO( + sharedPlant, member, + gardenStateCalculator.calculateState(sharedPlant), + gardenStateCalculator.calculatePercentage(sharedPlant) + ); } @Override @@ -87,7 +92,11 @@ public SharedPlantResDTO.PlantActionResDTO giveNutrient(Long sharedPlantId, Long // 로그 저장 sharedPlantLogRepository.save(SharedPlantLogConverter.toSharedPlantLog(sharedPlant, memberId, true, GrowthType.NUTRIENT.getGrowth(), true)); - return plantActionResponseBuilder.build(sharedPlant, member); + return SharedPlantConverter.toPlantActionResDTO( + sharedPlant, member, + gardenStateCalculator.calculateState(sharedPlant), + gardenStateCalculator.calculatePercentage(sharedPlant) + ); } @Override @@ -103,7 +112,11 @@ public SharedPlantResDTO.PlantActionResDTO plantSeed(Long sharedPlantId, Long me sharedPlant.plant(); - return plantActionResponseBuilder.build(sharedPlant, member); + return SharedPlantConverter.toPlantActionResDTO( + sharedPlant, member, + gardenStateCalculator.calculateState(sharedPlant), + gardenStateCalculator.calculatePercentage(sharedPlant) + ); } private Member findMemberById(Long memberId) { diff --git a/src/main/java/com/cotato/itda/domain/garden/service/query/SharedPlantQueryServiceImpl.java b/src/main/java/com/cotato/itda/domain/garden/service/query/SharedPlantQueryServiceImpl.java index 56ba50e..bfc40db 100644 --- a/src/main/java/com/cotato/itda/domain/garden/service/query/SharedPlantQueryServiceImpl.java +++ b/src/main/java/com/cotato/itda/domain/garden/service/query/SharedPlantQueryServiceImpl.java @@ -7,10 +7,10 @@ import com.cotato.itda.domain.garden.dto.SharedPlantWithFriendship; import com.cotato.itda.domain.garden.dto.res.SharedPlantResDTO; import com.cotato.itda.domain.garden.entity.SharedPlant; -import com.cotato.itda.domain.garden.entity.SharedPlantLog; +import com.cotato.itda.domain.garden.enums.GardenState; import com.cotato.itda.domain.garden.enums.SharedPlantStatus; -import com.cotato.itda.domain.garden.repository.SharedPlantLogRepository; import com.cotato.itda.domain.garden.repository.SharedPlantRepository; +import com.cotato.itda.domain.garden.service.GardenStateCalculator; import com.cotato.itda.domain.member.entity.Member; import com.cotato.itda.domain.member.repository.MemberRepository; import com.cotato.itda.global.error.constant.UserErrorCode; @@ -19,7 +19,6 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.time.LocalDateTime; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -30,9 +29,9 @@ public class SharedPlantQueryServiceImpl implements SharedPlantQueryService { private final SharedPlantRepository sharedPlantRepository; - private final SharedPlantLogRepository sharedPlantLogRepository; private final FriendshipRepository friendshipRepository; private final MemberRepository memberRepository; + private final GardenStateCalculator gardenStateCalculator; @Override public SharedPlantResDTO.SharedPlantInfoListDTO getSharedPlants(Long memberId, List statuses) { @@ -74,20 +73,19 @@ public SharedPlantResDTO.SharedPlantInfoListDTO getSharedPlants(Long memberId, L }) .toList(); - // 5. 현재 멤버의 마지막 물주기 시간 조회 - List sharedPlantIds = sharedPlants.stream() - .map(SharedPlant::getId) + // 5. DTO 변환 (gardenState, percentage 계산 포함) + List sharedPlantInfoDTOs = pairs.stream() + .filter(pair -> pair.friendship() != null) + .map(pair -> { + SharedPlant sp = pair.sharedPlant(); + GardenState gardenState = gardenStateCalculator.calculateState(sp); + int percentage = gardenStateCalculator.calculatePercentage(sp); + return SharedPlantConverter.toSharedPlantInfoDTO( + sp, pair.friendship(), memberId, gardenState, percentage + ); + }) .toList(); - List latestLogs = sharedPlantLogRepository.findLatestLogsBySharedPlantIdsAndMemberId( - sharedPlantIds, memberId); - - Map myLastWateredAtMap = latestLogs.stream() - .collect(Collectors.toMap( - log -> log.getSharedPlant().getId(), - SharedPlantLog::getWateredAt - )); - - return SharedPlantConverter.toSharedPlantInfoListDTO(pairs, member.getNutrientCount(), memberId, myLastWateredAtMap); + return SharedPlantConverter.toSharedPlantInfoListDTO(sharedPlantInfoDTOs, member.getNutrientCount()); } } From cb32d869dc3a524fc2c8608a148dcaa0dfa08fd8 Mon Sep 17 00:00:00 2001 From: ccjngwn Date: Thu, 12 Feb 2026 17:49:47 +0900 Subject: [PATCH 08/11] =?UTF-8?q?fix:=20converter=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../converter/SharedPlantConverter.java | 22 ++++++------------- .../garden/dto/res/SharedPlantResDTO.java | 4 ++-- 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/cotato/itda/domain/garden/converter/SharedPlantConverter.java b/src/main/java/com/cotato/itda/domain/garden/converter/SharedPlantConverter.java index a740e92..b5e8855 100644 --- a/src/main/java/com/cotato/itda/domain/garden/converter/SharedPlantConverter.java +++ b/src/main/java/com/cotato/itda/domain/garden/converter/SharedPlantConverter.java @@ -12,8 +12,6 @@ public class SharedPlantConverter { - private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); - public static SharedPlant toSharedPlant(SharedPlantInvite invite) { Member inviter = invite.getInviter(); Member invitee = invite.getInvitee(); @@ -34,7 +32,7 @@ public static SharedPlantResDTO.AcceptedSharedPlantDTO toAcceptedSharedPlantDTO( .sharedPlantId(sharedPlant.getId()) .plantId(sharedPlant.getPlant().getId()) .nickname(sharedPlant.getNickname()) - .createdAt(sharedPlant.getCreatedAt().format(FORMATTER)) + .createdAt(sharedPlant.getCreatedAt()) .build(); } @@ -47,10 +45,7 @@ public static SharedPlantResDTO.SharedPlantInfoDTO toSharedPlantInfoDTO( ) { SharedPlantResDTO.LastWateredByDTO lastWateredBy = null; if (sharedPlant.getLastWateredBy() != null) { - lastWateredBy = SharedPlantResDTO.LastWateredByDTO.builder() - .memberId(sharedPlant.getLastWateredBy()) - .isMe(sharedPlant.getLastWateredBy().equals(currentMemberId)) - .build(); + lastWateredBy = SharedPlantConverter.toLastWateredByDTO(sharedPlant, currentMemberId); } return SharedPlantResDTO.SharedPlantInfoDTO.builder() @@ -67,7 +62,7 @@ public static SharedPlantResDTO.SharedPlantInfoDTO toSharedPlantInfoDTO( .isSoloMode(sharedPlant.getIsSoloMode()) .lastWateredBy(lastWateredBy) .lastWateredAt(sharedPlant.getLastWateredAt()) - .createdAt(sharedPlant.getCreatedAt().format(FORMATTER)) + .createdAt(sharedPlant.getCreatedAt()) .build(); } @@ -82,10 +77,10 @@ public static SharedPlantResDTO.SharedPlantInfoListDTO toSharedPlantInfoListDTO( .build(); } - public static SharedPlantResDTO.LastWateredByDTO toLastWateredByDTO(Member member) { + public static SharedPlantResDTO.LastWateredByDTO toLastWateredByDTO(SharedPlant sharedPlant, Long currentMemberId) { return SharedPlantResDTO.LastWateredByDTO.builder() - .memberId(member.getId()) - .isMe(true) + .memberId(sharedPlant.getLastWateredBy()) + .isMe(sharedPlant.getLastWateredBy().equals(currentMemberId)) .build(); } @@ -97,10 +92,7 @@ public static SharedPlantResDTO.PlantActionResDTO toPlantActionResDTO( ) { SharedPlantResDTO.LastWateredByDTO lastWateredBy = null; if (sharedPlant.getLastWateredBy() != null) { - lastWateredBy = SharedPlantResDTO.LastWateredByDTO.builder() - .memberId(sharedPlant.getLastWateredBy()) - .isMe(sharedPlant.getLastWateredBy().equals(currentMember.getId())) - .build(); + lastWateredBy = SharedPlantConverter.toLastWateredByDTO(sharedPlant, currentMember.getId()); } return SharedPlantResDTO.PlantActionResDTO.builder() diff --git a/src/main/java/com/cotato/itda/domain/garden/dto/res/SharedPlantResDTO.java b/src/main/java/com/cotato/itda/domain/garden/dto/res/SharedPlantResDTO.java index f364b20..af82d19 100644 --- a/src/main/java/com/cotato/itda/domain/garden/dto/res/SharedPlantResDTO.java +++ b/src/main/java/com/cotato/itda/domain/garden/dto/res/SharedPlantResDTO.java @@ -56,7 +56,7 @@ public record AcceptedSharedPlantDTO( @Schema(description = "식물 닉네임", example = "두쫀쿠") String nickname, @Schema(description = "생성 일시", example = "2025-12-28 12:34") - String createdAt + LocalDateTime createdAt ) {} @Builder @@ -90,7 +90,7 @@ public record SharedPlantInfoDTO( @Schema(description = "마지막으로 물 준 시간") LocalDateTime lastWateredAt, @Schema(description = "생성 일시", example = "2025-12-28 12:34") - String createdAt + LocalDateTime createdAt ) {} @Builder From b5394b11b947509ae2ee32a7cfd69d9656447749 Mon Sep 17 00:00:00 2001 From: ccjngwn Date: Thu, 12 Feb 2026 17:57:22 +0900 Subject: [PATCH 09/11] =?UTF-8?q?remove:=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20enum,=20reqDTO=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/garden/dto/req/SharedPlantReqDTO.java | 12 ------------ .../itda/domain/garden/enums/MaxGrowthCount.java | 15 --------------- .../itda/domain/garden/enums/SupplyType.java | 6 ------ 3 files changed, 33 deletions(-) delete mode 100644 src/main/java/com/cotato/itda/domain/garden/dto/req/SharedPlantReqDTO.java delete mode 100644 src/main/java/com/cotato/itda/domain/garden/enums/MaxGrowthCount.java delete mode 100644 src/main/java/com/cotato/itda/domain/garden/enums/SupplyType.java diff --git a/src/main/java/com/cotato/itda/domain/garden/dto/req/SharedPlantReqDTO.java b/src/main/java/com/cotato/itda/domain/garden/dto/req/SharedPlantReqDTO.java deleted file mode 100644 index 21f60cd..0000000 --- a/src/main/java/com/cotato/itda/domain/garden/dto/req/SharedPlantReqDTO.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.cotato.itda.domain.garden.dto.req; - -import com.cotato.itda.domain.garden.enums.SupplyType; -import jakarta.validation.constraints.NotNull; - -public class SharedPlantReqDTO { - - public record WaterPlantReqDTO( - @NotNull(message = "물주기 타입은 필수입니다.") - SupplyType supplyType - ) {} -} diff --git a/src/main/java/com/cotato/itda/domain/garden/enums/MaxGrowthCount.java b/src/main/java/com/cotato/itda/domain/garden/enums/MaxGrowthCount.java deleted file mode 100644 index 1c246c9..0000000 --- a/src/main/java/com/cotato/itda/domain/garden/enums/MaxGrowthCount.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.cotato.itda.domain.garden.enums; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -@Getter -@RequiredArgsConstructor -public enum MaxGrowthCount { - - TEAM_GROW(4), - TEAM_WATER(2), - SOLO_WATER(1); - - public final int maxGrowthCount; -} diff --git a/src/main/java/com/cotato/itda/domain/garden/enums/SupplyType.java b/src/main/java/com/cotato/itda/domain/garden/enums/SupplyType.java deleted file mode 100644 index 47e40be..0000000 --- a/src/main/java/com/cotato/itda/domain/garden/enums/SupplyType.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.cotato.itda.domain.garden.enums; - -public enum SupplyType { - - WATER, NUTRIENT -} From bbd1a008d275b9188357dbb3c832f345a77b27e0 Mon Sep 17 00:00:00 2001 From: ccjngwn Date: Thu, 12 Feb 2026 18:07:48 +0900 Subject: [PATCH 10/11] =?UTF-8?q?fix:=20GardenStateCalculaotr=20=EC=8B=9C?= =?UTF-8?q?=EA=B0=84=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../converter/SharedPlantLogConverter.java | 4 +++- .../itda/domain/garden/entity/SharedPlant.java | 8 ++++++-- .../garden/service/GardenStateCalculator.java | 17 +++++++++-------- .../command/SharedPlantCommandServiceImpl.java | 8 ++++---- 4 files changed, 22 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/cotato/itda/domain/garden/converter/SharedPlantLogConverter.java b/src/main/java/com/cotato/itda/domain/garden/converter/SharedPlantLogConverter.java index 4a7f5f0..d340c2d 100644 --- a/src/main/java/com/cotato/itda/domain/garden/converter/SharedPlantLogConverter.java +++ b/src/main/java/com/cotato/itda/domain/garden/converter/SharedPlantLogConverter.java @@ -10,7 +10,8 @@ public static SharedPlantLog toSharedPlantLog( Long wateredBy, boolean affectedGrowth, int growthIncrement, - boolean usedNutrient + boolean usedNutrient, + boolean stageChanged ) { return SharedPlantLog.builder() .sharedPlant(sharedPlant) @@ -18,6 +19,7 @@ public static SharedPlantLog toSharedPlantLog( .affectedGrowth(affectedGrowth) .growthIncrement(growthIncrement) .usedNutrient(usedNutrient) + .stageChanged(stageChanged) .build(); } } diff --git a/src/main/java/com/cotato/itda/domain/garden/entity/SharedPlant.java b/src/main/java/com/cotato/itda/domain/garden/entity/SharedPlant.java index b748344..8515fd9 100644 --- a/src/main/java/com/cotato/itda/domain/garden/entity/SharedPlant.java +++ b/src/main/java/com/cotato/itda/domain/garden/entity/SharedPlant.java @@ -63,17 +63,21 @@ public class SharedPlant extends BaseEntity { @Builder.Default private boolean isPlanted = false; - public void water(int growthValue, Member member) { + public boolean water(int growthValue, Member member) { + PlantStage previousStage = this.growthStage; this.growthValue += growthValue; this.growthStage = this.plant.calculateGrowthStage(this.growthValue); this.lastWateredBy = member.getId(); this.lastWateredAt = LocalDateTime.now(); + return this.growthStage != previousStage; } - public void nutrient(int growthValue, Member member) { + public boolean nutrient(int growthValue, Member member) { + PlantStage previousStage = this.growthStage; this.growthValue += growthValue; this.growthStage = this.plant.calculateGrowthStage(this.growthValue); member.decreaseNutrient(); + return this.growthStage != previousStage; } public void exitSoloMode() { diff --git a/src/main/java/com/cotato/itda/domain/garden/service/GardenStateCalculator.java b/src/main/java/com/cotato/itda/domain/garden/service/GardenStateCalculator.java index 10460fd..6fbf84f 100644 --- a/src/main/java/com/cotato/itda/domain/garden/service/GardenStateCalculator.java +++ b/src/main/java/com/cotato/itda/domain/garden/service/GardenStateCalculator.java @@ -21,20 +21,21 @@ public class GardenStateCalculator { public GardenState calculateState(SharedPlant plant) { if (plant.getStatus() == SharedPlantStatus.COMPLETED) return GardenState.COMPLETED; if (!plant.isPlanted()) return GardenState.SEED_READY; + if (plant.getLastWateredAt() == null) return GardenState.WATERABLE; - Optional lastLog = sharedPlantLogRepository.findTopBySharedPlantOrderByCreatedAtDesc(plant); - if (lastLog.isEmpty()) return GardenState.SEED_READY; - - SharedPlantLog log = lastLog.get(); - long hours = ChronoUnit.HOURS.between(log.getWateredAt(), LocalDateTime.now()); + long hours = ChronoUnit.HOURS.between(plant.getLastWateredAt(), LocalDateTime.now()); - if (log.isUsedNutrient() && plant.getStatus() == SharedPlantStatus.WITHERED) { - return GardenState.AFTER_NUTRITION; + Optional lastLog = sharedPlantLogRepository.findTopBySharedPlantOrderByCreatedAtDesc(plant); + if (lastLog.isPresent()) { + SharedPlantLog log = lastLog.get(); + if (log.isUsedNutrient() && plant.getStatus() == SharedPlantStatus.WITHERED) { + return GardenState.AFTER_NUTRITION; + } + if (log.isStageChanged() && hours <= 24) return GardenState.GROWING; } if (hours >= 72) return GardenState.NUTRITION_AVAILABLE; if (hours >= 48) return GardenState.WITHERED; - if (log.isStageChanged()) return GardenState.GROWING; if (hours <= 24) return GardenState.WATERED_RECENTLY; return GardenState.WATERABLE; } diff --git a/src/main/java/com/cotato/itda/domain/garden/service/command/SharedPlantCommandServiceImpl.java b/src/main/java/com/cotato/itda/domain/garden/service/command/SharedPlantCommandServiceImpl.java index 015c9ea..e7d0d7a 100644 --- a/src/main/java/com/cotato/itda/domain/garden/service/command/SharedPlantCommandServiceImpl.java +++ b/src/main/java/com/cotato/itda/domain/garden/service/command/SharedPlantCommandServiceImpl.java @@ -49,7 +49,7 @@ public SharedPlantResDTO.PlantActionResDTO water(Long sharedPlantId, Long member sharedPlant.exitSoloMode(); } - sharedPlant.water(GrowthType.WATER.getGrowth(), member); + boolean stageChanged = sharedPlant.water(GrowthType.WATER.getGrowth(), member); // WITHERED 상태에서 물을 주면 GROWING으로 if (wasWithered) { @@ -62,7 +62,7 @@ public SharedPlantResDTO.PlantActionResDTO water(Long sharedPlantId, Long member } // 로그 저장 - sharedPlantLogRepository.save(SharedPlantLogConverter.toSharedPlantLog(sharedPlant, memberId, true, GrowthType.WATER.getGrowth(), false)); + sharedPlantLogRepository.save(SharedPlantLogConverter.toSharedPlantLog(sharedPlant, memberId, true, GrowthType.WATER.getGrowth(), false, stageChanged)); return SharedPlantConverter.toPlantActionResDTO( sharedPlant, member, @@ -87,10 +87,10 @@ public SharedPlantResDTO.PlantActionResDTO giveNutrient(Long sharedPlantId, Long sharedPlant.exitSoloMode(); } - sharedPlant.nutrient(GrowthType.NUTRIENT.getGrowth(), member); + boolean stageChanged = sharedPlant.nutrient(GrowthType.NUTRIENT.getGrowth(), member); // 로그 저장 - sharedPlantLogRepository.save(SharedPlantLogConverter.toSharedPlantLog(sharedPlant, memberId, true, GrowthType.NUTRIENT.getGrowth(), true)); + sharedPlantLogRepository.save(SharedPlantLogConverter.toSharedPlantLog(sharedPlant, memberId, true, GrowthType.NUTRIENT.getGrowth(), true, stageChanged)); return SharedPlantConverter.toPlantActionResDTO( sharedPlant, member, From c2c3e5ad322ed628588341707ddd2f392d2c5501 Mon Sep 17 00:00:00 2001 From: ccjngwn Date: Thu, 12 Feb 2026 19:24:49 +0900 Subject: [PATCH 11/11] =?UTF-8?q?fix:=20WITHERED=20status=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/garden/controller/SharedPlantController.java | 2 +- .../com/cotato/itda/domain/garden/entity/SharedPlant.java | 4 ---- .../cotato/itda/domain/garden/enums/GardenTimeRule.java | 8 ++++++++ .../itda/domain/garden/enums/SharedPlantStatus.java | 2 +- .../domain/garden/repository/SharedPlantRepository.java | 4 ++-- .../itda/domain/garden/service/GardenStateCalculator.java | 3 ++- .../itda/domain/garden/service/SharedPlantValidator.java | 5 ++--- .../service/command/SharedPlantCommandServiceImpl.java | 7 ------- 8 files changed, 16 insertions(+), 19 deletions(-) diff --git a/src/main/java/com/cotato/itda/domain/garden/controller/SharedPlantController.java b/src/main/java/com/cotato/itda/domain/garden/controller/SharedPlantController.java index f81342a..240433f 100644 --- a/src/main/java/com/cotato/itda/domain/garden/controller/SharedPlantController.java +++ b/src/main/java/com/cotato/itda/domain/garden/controller/SharedPlantController.java @@ -28,7 +28,7 @@ public ApiResponse getSharedPlants( @RequestParam(name = "status", required = false) List statuses ) { List effectiveStatuses = (statuses == null || statuses.isEmpty()) - ? List.of(SharedPlantStatus.GROWING, SharedPlantStatus.WITHERED) + ? List.of(SharedPlantStatus.GROWING) : statuses; return ApiResponse.success(sharedPlantQueryService.getSharedPlants(jwtPrincipal.memberId(), effectiveStatuses)); } diff --git a/src/main/java/com/cotato/itda/domain/garden/entity/SharedPlant.java b/src/main/java/com/cotato/itda/domain/garden/entity/SharedPlant.java index 8515fd9..04351fa 100644 --- a/src/main/java/com/cotato/itda/domain/garden/entity/SharedPlant.java +++ b/src/main/java/com/cotato/itda/domain/garden/entity/SharedPlant.java @@ -89,10 +89,6 @@ public void complete() { this.status = SharedPlantStatus.COMPLETED; } - public void revive() { - this.status = SharedPlantStatus.GROWING; - } - public boolean hasReachedMaxGrowth() { return this.growthValue >= this.plant.getBloomMax(); } diff --git a/src/main/java/com/cotato/itda/domain/garden/enums/GardenTimeRule.java b/src/main/java/com/cotato/itda/domain/garden/enums/GardenTimeRule.java index 25250e1..aa9a3d9 100644 --- a/src/main/java/com/cotato/itda/domain/garden/enums/GardenTimeRule.java +++ b/src/main/java/com/cotato/itda/domain/garden/enums/GardenTimeRule.java @@ -3,6 +3,9 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; + @Getter @RequiredArgsConstructor public enum GardenTimeRule { @@ -11,4 +14,9 @@ public enum GardenTimeRule { NUTRITION_AVAILABLE(72); private final int hours; + + public static boolean isWithered(LocalDateTime lastWateredAt) { + if (lastWateredAt == null) return false; + return ChronoUnit.HOURS.between(lastWateredAt, LocalDateTime.now()) >= WITHERED.getHours(); + } } diff --git a/src/main/java/com/cotato/itda/domain/garden/enums/SharedPlantStatus.java b/src/main/java/com/cotato/itda/domain/garden/enums/SharedPlantStatus.java index 2398cdc..fc7497b 100644 --- a/src/main/java/com/cotato/itda/domain/garden/enums/SharedPlantStatus.java +++ b/src/main/java/com/cotato/itda/domain/garden/enums/SharedPlantStatus.java @@ -1,5 +1,5 @@ package com.cotato.itda.domain.garden.enums; public enum SharedPlantStatus { - GROWING, WITHERED, COMPLETED + GROWING, COMPLETED } diff --git a/src/main/java/com/cotato/itda/domain/garden/repository/SharedPlantRepository.java b/src/main/java/com/cotato/itda/domain/garden/repository/SharedPlantRepository.java index a2a12a0..b815700 100644 --- a/src/main/java/com/cotato/itda/domain/garden/repository/SharedPlantRepository.java +++ b/src/main/java/com/cotato/itda/domain/garden/repository/SharedPlantRepository.java @@ -12,12 +12,12 @@ public interface SharedPlantRepository extends JpaRepository @Query("SELECT CASE WHEN sp.memberA.id = :memberId THEN sp.memberB.id ELSE sp.memberA.id END " + "FROM SharedPlant sp " + "WHERE (sp.memberA.id = :memberId OR sp.memberB.id = :memberId) " + - "AND sp.status IN (com.cotato.itda.domain.garden.enums.SharedPlantStatus.GROWING, com.cotato.itda.domain.garden.enums.SharedPlantStatus.WITHERED)") + "AND sp.status = com.cotato.itda.domain.garden.enums.SharedPlantStatus.GROWING") List findGrowingOrWitheredSharedPlantFriendIds(@Param("memberId") Long memberId); @Query("SELECT COUNT(sp) > 0 FROM SharedPlant sp " + "WHERE sp.memberA.id = :memberId1 AND sp.memberB.id = :memberId2 " + - "AND sp.status IN (com.cotato.itda.domain.garden.enums.SharedPlantStatus.GROWING, com.cotato.itda.domain.garden.enums.SharedPlantStatus.WITHERED)") + "AND sp.status = com.cotato.itda.domain.garden.enums.SharedPlantStatus.GROWING") boolean existsGrowingOrWitheredBetween(@Param("memberId1") Long memberId1, @Param("memberId2") Long memberId2); @Query("SELECT sp FROM SharedPlant sp " + diff --git a/src/main/java/com/cotato/itda/domain/garden/service/GardenStateCalculator.java b/src/main/java/com/cotato/itda/domain/garden/service/GardenStateCalculator.java index 6fbf84f..b6c3c79 100644 --- a/src/main/java/com/cotato/itda/domain/garden/service/GardenStateCalculator.java +++ b/src/main/java/com/cotato/itda/domain/garden/service/GardenStateCalculator.java @@ -3,6 +3,7 @@ import com.cotato.itda.domain.garden.entity.SharedPlant; import com.cotato.itda.domain.garden.entity.SharedPlantLog; import com.cotato.itda.domain.garden.enums.GardenState; +import com.cotato.itda.domain.garden.enums.GardenTimeRule; import com.cotato.itda.domain.garden.enums.SharedPlantStatus; import com.cotato.itda.domain.garden.repository.SharedPlantLogRepository; import lombok.RequiredArgsConstructor; @@ -28,7 +29,7 @@ public GardenState calculateState(SharedPlant plant) { Optional lastLog = sharedPlantLogRepository.findTopBySharedPlantOrderByCreatedAtDesc(plant); if (lastLog.isPresent()) { SharedPlantLog log = lastLog.get(); - if (log.isUsedNutrient() && plant.getStatus() == SharedPlantStatus.WITHERED) { + if (log.isUsedNutrient() && GardenTimeRule.isWithered(plant.getLastWateredAt())) { return GardenState.AFTER_NUTRITION; } if (log.isStageChanged() && hours <= 24) return GardenState.GROWING; diff --git a/src/main/java/com/cotato/itda/domain/garden/service/SharedPlantValidator.java b/src/main/java/com/cotato/itda/domain/garden/service/SharedPlantValidator.java index fbf6b87..01cff48 100644 --- a/src/main/java/com/cotato/itda/domain/garden/service/SharedPlantValidator.java +++ b/src/main/java/com/cotato/itda/domain/garden/service/SharedPlantValidator.java @@ -54,8 +54,7 @@ public void validateCanWater(SharedPlant sharedPlant, Long memberId) { validateNotCompleted(sharedPlant); // 72시간 + 시듦 상태면 영양제 먼저 줘야함 - boolean wasWithered = sharedPlant.getStatus() == SharedPlantStatus.WITHERED; - if (wasWithered && sharedPlant.getLastWateredAt() != null + if (GardenTimeRule.isWithered(sharedPlant.getLastWateredAt()) && sharedPlant.getLastWateredAt() != null && ChronoUnit.HOURS.between(sharedPlant.getLastWateredAt(), LocalDateTime.now()) >= GardenTimeRule.NUTRITION_AVAILABLE.getHours()) { SharedPlantLog lastLog = sharedPlantLogRepository.findTopBySharedPlantOrderByCreatedAtDesc(sharedPlant).orElse(null); @@ -78,7 +77,7 @@ public void validateCanGiveNutrient(SharedPlant sharedPlant, Member member) { validatePlanted(sharedPlant); validateNotCompleted(sharedPlant); - if (sharedPlant.getStatus() != SharedPlantStatus.WITHERED + if (!GardenTimeRule.isWithered(sharedPlant.getLastWateredAt()) || sharedPlant.getLastWateredAt() == null || ChronoUnit.HOURS.between(sharedPlant.getLastWateredAt(), LocalDateTime.now()) < GardenTimeRule.NUTRITION_AVAILABLE.getHours() ) { diff --git a/src/main/java/com/cotato/itda/domain/garden/service/command/SharedPlantCommandServiceImpl.java b/src/main/java/com/cotato/itda/domain/garden/service/command/SharedPlantCommandServiceImpl.java index e7d0d7a..cf9f325 100644 --- a/src/main/java/com/cotato/itda/domain/garden/service/command/SharedPlantCommandServiceImpl.java +++ b/src/main/java/com/cotato/itda/domain/garden/service/command/SharedPlantCommandServiceImpl.java @@ -42,8 +42,6 @@ public SharedPlantResDTO.PlantActionResDTO water(Long sharedPlantId, Long member // 물주기 가능 여부 검증 sharedPlantValidator.validateCanWater(sharedPlant, memberId); - boolean wasWithered = sharedPlant.getStatus() == SharedPlantStatus.WITHERED; - // 혼자 돌봄 모드 종료 로직 if (sharedPlant.getIsSoloMode() && !memberId.equals(sharedPlant.getSoloPowerMemberId())) { sharedPlant.exitSoloMode(); @@ -51,11 +49,6 @@ public SharedPlantResDTO.PlantActionResDTO water(Long sharedPlantId, Long member boolean stageChanged = sharedPlant.water(GrowthType.WATER.getGrowth(), member); - // WITHERED 상태에서 물을 주면 GROWING으로 - if (wasWithered) { - sharedPlant.revive(); - } - // 성장 완료 시 COMPLETED로 if (sharedPlant.hasReachedMaxGrowth()) { sharedPlant.complete();