From 639c9fabbe33f3e8d5aa5d7fbdb791ff69643227 Mon Sep 17 00:00:00 2001 From: kangcheolung Date: Thu, 9 Jul 2026 23:00:52 +0900 Subject: [PATCH 1/8] =?UTF-8?q?feat:=20Medication=20=EC=97=94=ED=8B=B0?= =?UTF-8?q?=ED=8B=B0=EC=97=90=20update=20=EB=A9=94=EC=84=9C=EB=93=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- .../domain/medication/entity/Medication.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/main/java/com/piuda/callcare/domain/medication/entity/Medication.java b/src/main/java/com/piuda/callcare/domain/medication/entity/Medication.java index 342c992..1439338 100644 --- a/src/main/java/com/piuda/callcare/domain/medication/entity/Medication.java +++ b/src/main/java/com/piuda/callcare/domain/medication/entity/Medication.java @@ -102,6 +102,19 @@ public Medication(Senior senior, String hospitalName, DrugInfo drugInfo, this.ocrResultId = ocrResultId; } + public void update(String drugName, String dosagePerTime, Integer timesPerDay, + Integer totalDays, LocalDate startDate, LocalDate endDate, + String hospitalName, String memo) { + if (drugName != null) this.drugName = drugName; + if (dosagePerTime != null) this.dosagePerTime = dosagePerTime; + if (timesPerDay != null) this.timesPerDay = timesPerDay; + if (totalDays != null) this.totalDays = totalDays; + if (startDate != null) this.startDate = startDate; + if (endDate != null) this.endDate = endDate; + if (hospitalName != null) this.hospitalName = hospitalName; + if (memo != null) this.memo = memo; + } + public void deactivate() { this.isActive = false; } From 07ce0223fde5522aa0ca6ae23b5d7505f21d425b Mon Sep 17 00:00:00 2001 From: kangcheolung Date: Thu, 9 Jul 2026 23:01:00 +0900 Subject: [PATCH 2/8] =?UTF-8?q?feat:=20=EC=95=BD=EB=AC=BC=EB=85=B8?= =?UTF-8?q?=ED=8A=B8=20=EC=A1=B0=ED=9A=8C/=EA=B2=80=EC=83=89/=EC=88=98?= =?UTF-8?q?=EC=A0=95=EC=9A=A9=20DTO=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MedicationUpdateRequest, MedicationNoteGroupResponse, MedicationNoteItemResponse 추가 MedicationNoteItemResponse에 isActive 포함 — 프론트 재등록 버튼 노출 판단용 Co-Authored-By: Claude Sonnet 4.6 --- .../dto/request/MedicationUpdateRequest.java | 30 ++++++++++++++ .../response/MedicationNoteGroupResponse.java | 19 +++++++++ .../response/MedicationNoteItemResponse.java | 39 +++++++++++++++++++ 3 files changed, 88 insertions(+) create mode 100644 src/main/java/com/piuda/callcare/domain/medication/dto/request/MedicationUpdateRequest.java create mode 100644 src/main/java/com/piuda/callcare/domain/medication/dto/response/MedicationNoteGroupResponse.java create mode 100644 src/main/java/com/piuda/callcare/domain/medication/dto/response/MedicationNoteItemResponse.java diff --git a/src/main/java/com/piuda/callcare/domain/medication/dto/request/MedicationUpdateRequest.java b/src/main/java/com/piuda/callcare/domain/medication/dto/request/MedicationUpdateRequest.java new file mode 100644 index 0000000..dbe5d62 --- /dev/null +++ b/src/main/java/com/piuda/callcare/domain/medication/dto/request/MedicationUpdateRequest.java @@ -0,0 +1,30 @@ +package com.piuda.callcare.domain.medication.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; + +import java.time.LocalDate; + +@Schema(description = "약 수정 요청 — null 필드는 변경하지 않음") +public record MedicationUpdateRequest( + + @Schema(description = "약 이름 (null이면 변경 안 함)") + String drugName, + + @Schema(description = "1회 복용량 (null이면 변경 안 함)") + String dosagePerTime, + + @Schema(description = "1일 복용 횟수 (null이면 변경 안 함, 변경 시 스케줄 재생성)") + Integer timesPerDay, + + @Schema(description = "총 복용 일수 (null이면 변경 안 함)") + Integer totalDays, + + @Schema(description = "복용 시작일 (null이면 변경 안 함)") + LocalDate startDate, + + @Schema(description = "병원명 (null이면 변경 안 함)") + String hospitalName, + + @Schema(description = "메모 (null이면 변경 안 함)") + String memo +) {} diff --git a/src/main/java/com/piuda/callcare/domain/medication/dto/response/MedicationNoteGroupResponse.java b/src/main/java/com/piuda/callcare/domain/medication/dto/response/MedicationNoteGroupResponse.java new file mode 100644 index 0000000..37fb4b5 --- /dev/null +++ b/src/main/java/com/piuda/callcare/domain/medication/dto/response/MedicationNoteGroupResponse.java @@ -0,0 +1,19 @@ +package com.piuda.callcare.domain.medication.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; + +import java.time.LocalDate; +import java.util.List; + +@Schema(description = "약물노트 그룹 카드 — 복용 시작일 + 병원 기준 묶음") +public record MedicationNoteGroupResponse( + + @Schema(description = "복용 시작일 (그룹 키)") + LocalDate startDate, + + @Schema(description = "병원명 (없으면 null)") + String hospitalName, + + @Schema(description = "그룹 내 약 리스트") + List medications +) {} diff --git a/src/main/java/com/piuda/callcare/domain/medication/dto/response/MedicationNoteItemResponse.java b/src/main/java/com/piuda/callcare/domain/medication/dto/response/MedicationNoteItemResponse.java new file mode 100644 index 0000000..2d88067 --- /dev/null +++ b/src/main/java/com/piuda/callcare/domain/medication/dto/response/MedicationNoteItemResponse.java @@ -0,0 +1,39 @@ +package com.piuda.callcare.domain.medication.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; + +import java.time.LocalDate; + +@Schema(description = "약물노트 그룹 카드 내 약 단건") +public record MedicationNoteItemResponse( + + @Schema(description = "약 ID") + Long medicationId, + + @Schema(description = "약 이름") + String drugName, + + @Schema(description = "약 종류 (없으면 null)") + String drugType, + + @Schema(description = "약 이미지 URL (없으면 null)") + String imageUrl, + + @Schema(description = "1일 복용 횟수") + Integer timesPerDay, + + @Schema(description = "1회 복용량 (예: 1정, 5ml)") + String dosagePerTime, + + @Schema(description = "복용 시작일") + LocalDate startDate, + + @Schema(description = "복용 종료일 (없으면 null)") + LocalDate endDate, + + @Schema(description = "총 복용 일수 (없으면 null)") + Integer totalDays, + + @Schema(description = "복용 중 여부 — false면 종료된 약, 프론트에서 재등록 버튼 노출에 사용") + Boolean isActive +) {} From cbc0611ac57008a5ea54d4690d5a909c8b8485ae Mon Sep 17 00:00:00 2001 From: kangcheolung Date: Thu, 9 Jul 2026 23:01:04 +0900 Subject: [PATCH 3/8] =?UTF-8?q?feat:=20MedicationConverter=EC=97=90=20toNo?= =?UTF-8?q?teItemResponse=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- .../converter/MedicationConverter.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/main/java/com/piuda/callcare/domain/medication/converter/MedicationConverter.java b/src/main/java/com/piuda/callcare/domain/medication/converter/MedicationConverter.java index 9bfa8d5..83768f3 100644 --- a/src/main/java/com/piuda/callcare/domain/medication/converter/MedicationConverter.java +++ b/src/main/java/com/piuda/callcare/domain/medication/converter/MedicationConverter.java @@ -1,6 +1,7 @@ package com.piuda.callcare.domain.medication.converter; import com.piuda.callcare.domain.medication.dto.response.MedicationGroupItemResponse; +import com.piuda.callcare.domain.medication.dto.response.MedicationNoteItemResponse; import com.piuda.callcare.domain.medication.dto.response.MedicationResponse; import com.piuda.callcare.domain.medication.entity.Medication; import com.piuda.callcare.domain.medication.entity.MedicationSchedule; @@ -30,6 +31,22 @@ public MedicationGroupItemResponse toGroupItemResponse(Medication medication) { ); } + // Medication → MedicationNoteItemResponse (약물노트 리스트 카드 내 약 단건) + public MedicationNoteItemResponse toNoteItemResponse(Medication medication) { + return new MedicationNoteItemResponse( + medication.getId(), + medication.getDrugName(), + medication.getDrugType(), + medication.getImageUrl(), + medication.getTimesPerDay(), + medication.getDosagePerTime(), + medication.getStartDate(), + medication.getEndDate(), + medication.getTotalDays(), + medication.getIsActive() + ); + } + // Medication + 스케줄 목록 → MedicationResponse public MedicationResponse toResponse(Medication medication, List schedules) { List mealTimes = schedules.stream() From 49f17f1e5712ee1d520341865d5fdd32780cefad Mon Sep 17 00:00:00 2001 From: kangcheolung Date: Thu, 9 Jul 2026 23:01:11 +0900 Subject: [PATCH 4/8] =?UTF-8?q?feat:=20=EC=95=BD=EB=AC=BC=EB=85=B8?= =?UTF-8?q?=ED=8A=B8=20=EB=AA=A9=EB=A1=9D/=EA=B2=80=EC=83=89=20=EC=BF=BC?= =?UTF-8?q?=EB=A6=AC=20=EB=B0=8F=20=EC=8A=A4=EC=BC=80=EC=A4=84=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit findAllBySeniorId — 활성/비활성 전체 조회 (isActive 조건 제거) searchByKeyword — 약 이름/별명/병원명 키워드 + 기간 필터 deleteAllByMedication_Id — 약 삭제 시 스케줄 일괄 제거용 Co-Authored-By: Claude Sonnet 4.6 --- .../repository/MedicationRepository.java | 22 +++++++++++++++++++ .../MedicationScheduleRepository.java | 2 ++ 2 files changed, 24 insertions(+) diff --git a/src/main/java/com/piuda/callcare/domain/medication/repository/MedicationRepository.java b/src/main/java/com/piuda/callcare/domain/medication/repository/MedicationRepository.java index 0ccf481..43959b9 100644 --- a/src/main/java/com/piuda/callcare/domain/medication/repository/MedicationRepository.java +++ b/src/main/java/com/piuda/callcare/domain/medication/repository/MedicationRepository.java @@ -29,6 +29,28 @@ public interface MedicationRepository extends JpaRepository { """) List findActiveWithDrugInfoBySeniorId(@Param("seniorId") Long seniorId); + // 약물노트 전체 리스트: 활성/비활성 관계없이 전체 조회 (startDate DESC → hospitalName ASC 정렬) + @Query(""" + SELECT m FROM Medication m + WHERE m.senior.id = :seniorId + ORDER BY m.startDate DESC, m.hospitalName ASC NULLS LAST + """) + List findAllBySeniorId(@Param("seniorId") Long seniorId); + + // 약물노트 검색: 약 이름/별명/병원명 키워드 + 날짜 범위 필터 (활성/비활성 모두 포함) + @Query(""" + SELECT m FROM Medication m + WHERE m.senior.id = :seniorId + AND m.startDate >= :fromDate + AND (LOWER(m.drugName) LIKE LOWER(CONCAT('%', :keyword, '%')) + OR LOWER(m.drugNickname) LIKE LOWER(CONCAT('%', :keyword, '%')) + OR LOWER(m.hospitalName) LIKE LOWER(CONCAT('%', :keyword, '%'))) + ORDER BY m.startDate DESC, m.hospitalName ASC NULLS LAST + """) + List searchByKeyword(@Param("seniorId") Long seniorId, + @Param("keyword") String keyword, + @Param("fromDate") LocalDate fromDate); + // 약물노트 그룹 상세 조회: 병원명 + 처방일 조합이 그룹 키 (둘 다 null 가능) @Query(""" SELECT m FROM Medication m diff --git a/src/main/java/com/piuda/callcare/domain/medication/repository/MedicationScheduleRepository.java b/src/main/java/com/piuda/callcare/domain/medication/repository/MedicationScheduleRepository.java index 75a90a0..db552ad 100644 --- a/src/main/java/com/piuda/callcare/domain/medication/repository/MedicationScheduleRepository.java +++ b/src/main/java/com/piuda/callcare/domain/medication/repository/MedicationScheduleRepository.java @@ -11,6 +11,8 @@ public interface MedicationScheduleRepository extends JpaRepository { + void deleteAllByMedication_Id(Long medicationId); + // 토글 검증용: 해당 약에 그 시간대의 오늘 활성(복용 기간 내) 스케줄이 실제 존재하는지 // (홈카드 완료 계산의 findActiveSchedulesForHomeCards와 동일한 active/기간 조건으로 게이트를 맞춘다) @Query(""" From e118f29ade01a9f14746627ef265d4b93fdd533f Mon Sep 17 00:00:00 2001 From: kangcheolung Date: Thu, 9 Jul 2026 23:01:18 +0900 Subject: [PATCH 5/8] =?UTF-8?q?feat:=20MedicationCommandService=EC=97=90?= =?UTF-8?q?=20=EC=95=BD=20=EC=88=98=EC=A0=95/=EC=82=AD=EC=A0=9C=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit update — null 필드 유지, timesPerDay 변경 시 스케줄 재생성, endDate 자동 재계산 delete — 스케줄 먼저 삭제 후 Medication 하드 삭제 (Figma 스펙 기준 복구 불가) Co-Authored-By: Claude Sonnet 4.6 --- .../command/MedicationCommandService.java | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/main/java/com/piuda/callcare/domain/medication/service/command/MedicationCommandService.java b/src/main/java/com/piuda/callcare/domain/medication/service/command/MedicationCommandService.java index 2fbe12e..485da7a 100644 --- a/src/main/java/com/piuda/callcare/domain/medication/service/command/MedicationCommandService.java +++ b/src/main/java/com/piuda/callcare/domain/medication/service/command/MedicationCommandService.java @@ -4,6 +4,7 @@ import com.piuda.callcare.domain.druginfo.repository.DrugInfoRepository; import com.piuda.callcare.domain.medication.converter.MedicationConverter; import com.piuda.callcare.domain.medication.dto.request.MedicationCreateRequest; +import com.piuda.callcare.domain.medication.dto.request.MedicationUpdateRequest; import com.piuda.callcare.domain.medication.dto.response.MedicationResponse; import com.piuda.callcare.domain.medication.entity.Medication; import com.piuda.callcare.domain.medication.entity.MedicationSchedule; @@ -33,6 +34,7 @@ public class MedicationCommandService { private final DrugInfoRepository drugInfoRepository; private final MedicationConverter medicationConverter; + // 약 1건 이상 일괄 등록 — 스케줄 자동 생성, 하나라도 실패 시 전체 롤백 @Transactional public List registerBatch(Long userId, List requests) { return requests.stream() @@ -107,6 +109,44 @@ private DrugInfo resolveDrugInfo(Long drugInfoId) { .orElseThrow(() -> new CallCareException(ErrorCode.DRUG_NOT_FOUND)); } + // 약 정보 수정 — timesPerDay 변경 시 스케줄 삭제 후 재생성 + @Transactional + public void update(Long userId, Long medicationId, MedicationUpdateRequest request) { + Medication medication = medicationRepository.findById(medicationId) + .orElseThrow(() -> new CallCareException(ErrorCode.MEDICATION_NOT_FOUND)); + seniorRepository.findByIdAndUser_Id(medication.getSenior().getId(), userId) + .orElseThrow(() -> new CallCareException(ErrorCode.FORBIDDEN)); + + // startDate 또는 totalDays 가 바뀌면 endDate 재계산 + LocalDate newEndDate = null; + if (request.startDate() != null || request.totalDays() != null) { + LocalDate effectiveStart = request.startDate() != null ? request.startDate() : medication.getStartDate(); + Integer effectiveDays = request.totalDays() != null ? request.totalDays() : medication.getTotalDays(); + newEndDate = effectiveDays != null ? effectiveStart.plusDays(effectiveDays - 1) : null; + } + + medication.update(request.drugName(), request.dosagePerTime(), request.timesPerDay(), + request.totalDays(), request.startDate(), newEndDate, request.hospitalName(), request.memo()); + + // 복용 횟수 변경 시 스케줄 삭제 후 재생성( 3회→2회로 바꾸면 점심 스케줄 삭제, 2회→3회로 바꾸면 점심 스케줄 새로 생성) + if (request.timesPerDay() != null) { + medicationScheduleRepository.deleteAllByMedication_Id(medicationId); + List newSchedules = createSchedules(medication, request.timesPerDay()); + medicationScheduleRepository.saveAll(newSchedules); + } + } + + // 약 하드 삭제 — 스케줄 먼저 제거 후 Medication 완전 삭제 + @Transactional + public void delete(Long userId, Long medicationId) { + Medication medication = medicationRepository.findById(medicationId) + .orElseThrow(() -> new CallCareException(ErrorCode.MEDICATION_NOT_FOUND)); + seniorRepository.findByIdAndUser_Id(medication.getSenior().getId(), userId) + .orElseThrow(() -> new CallCareException(ErrorCode.FORBIDDEN)); + medicationScheduleRepository.deleteAllByMedication_Id(medicationId); + medicationRepository.delete(medication); + } + private String buildUsageStorageInfo(DrugInfo drugInfo) { if (drugInfo == null) return null; String usage = drugInfo.getUseMethodQesitm(); From 546d744bf34661c5b72a79bfb4dc8a825589ae75 Mon Sep 17 00:00:00 2001 From: kangcheolung Date: Thu, 9 Jul 2026 23:01:23 +0900 Subject: [PATCH 6/8] =?UTF-8?q?feat:=20MedicationQueryService=EC=97=90=20?= =?UTF-8?q?=EC=95=BD=EB=AC=BC=EB=85=B8=ED=8A=B8=20=EB=AA=A9=EB=A1=9D/?= =?UTF-8?q?=EA=B2=80=EC=83=89=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit getNoteList — 전체 약 startDate+병원 기준 그룹화 반환 searchNotes — 키워드 + 기간 필터(1w/1m/3m/1y) 검색 Co-Authored-By: Claude Sonnet 4.6 --- .../service/query/MedicationQueryService.java | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/main/java/com/piuda/callcare/domain/medication/service/query/MedicationQueryService.java b/src/main/java/com/piuda/callcare/domain/medication/service/query/MedicationQueryService.java index 7bebaf0..a33ad34 100644 --- a/src/main/java/com/piuda/callcare/domain/medication/service/query/MedicationQueryService.java +++ b/src/main/java/com/piuda/callcare/domain/medication/service/query/MedicationQueryService.java @@ -2,6 +2,8 @@ import com.piuda.callcare.domain.medication.converter.MedicationConverter; import com.piuda.callcare.domain.medication.dto.response.MedicationGroupItemResponse; +import com.piuda.callcare.domain.medication.dto.response.MedicationNoteGroupResponse; +import com.piuda.callcare.domain.medication.dto.response.MedicationNoteItemResponse; import com.piuda.callcare.domain.medication.entity.Medication; import com.piuda.callcare.domain.medication.repository.MedicationRepository; import com.piuda.callcare.domain.senior.repository.SeniorRepository; @@ -12,7 +14,10 @@ import org.springframework.transaction.annotation.Transactional; import java.time.LocalDate; +import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; @Service @Transactional(readOnly = true) @@ -23,6 +28,7 @@ public class MedicationQueryService { private final SeniorRepository seniorRepository; private final MedicationConverter medicationConverter; + // 병원+처방일 그룹의 약 상세 목록 조회 public List getGroup(Long userId, Long seniorId, String hospitalName, LocalDate prescriptionDate) { if (userId == null) { throw new CallCareException(ErrorCode.FORBIDDEN); @@ -34,4 +40,50 @@ public List getGroup(Long userId, Long seniorId, St .map(medicationConverter::toGroupItemResponse) .toList(); } + + // 시니어의 약 전체 목록 (활성/비활성 모두) — startDate+병원 기준 그룹화 + public List getNoteList(Long userId, Long seniorId) { + if (userId == null) throw new CallCareException(ErrorCode.FORBIDDEN); + seniorRepository.findByIdAndUser_Id(seniorId, userId) + .orElseThrow(() -> new CallCareException(ErrorCode.SENIOR_NOT_FOUND)); + List medications = medicationRepository.findAllBySeniorId(seniorId); + return toNoteGroupResponses(medications); + } + + // 약 이름/별명/병원명 키워드 검색 + 기간 필터 (1w·1m·3m·1y) + public List searchNotes(Long userId, Long seniorId, String keyword, String period) { + if (userId == null) throw new CallCareException(ErrorCode.FORBIDDEN); + seniorRepository.findByIdAndUser_Id(seniorId, userId) + .orElseThrow(() -> new CallCareException(ErrorCode.SENIOR_NOT_FOUND)); + LocalDate fromDate = resolveFromDate(period); + List medications = medicationRepository.searchByKeyword(seniorId, keyword, fromDate); + return toNoteGroupResponses(medications); + } + + private List toNoteGroupResponses(List medications) { + Map> grouped = new LinkedHashMap<>(); + for (Medication m : medications) { + String key = m.getStartDate() + "|" + m.getHospitalName(); + grouped.computeIfAbsent(key, k -> new ArrayList<>()).add(m); + } + return grouped.values().stream() + .map(group -> { + Medication first = group.get(0); + List items = group.stream() + .map(medicationConverter::toNoteItemResponse) + .toList(); + return new MedicationNoteGroupResponse(first.getStartDate(), first.getHospitalName(), items); + }) + .toList(); + } + + private LocalDate resolveFromDate(String period) { + LocalDate today = LocalDate.now(); + return switch (period == null ? "1y" : period) { + case "1w" -> today.minusWeeks(1); + case "1m" -> today.minusMonths(1); + case "3m" -> today.minusMonths(3); + default -> today.minusYears(1); + }; + } } From 394158ecc6eb37595c186fbddd5e65e8ae38be54 Mon Sep 17 00:00:00 2001 From: kangcheolung Date: Thu, 9 Jul 2026 23:01:29 +0900 Subject: [PATCH 7/8] =?UTF-8?q?feat:=20MedicationController=EC=97=90=20?= =?UTF-8?q?=EC=95=BD=EB=AC=BC=EB=85=B8=ED=8A=B8=20=EB=AA=A9=EB=A1=9D/?= =?UTF-8?q?=EA=B2=80=EC=83=89/=EC=88=98=EC=A0=95/=EC=82=AD=EC=A0=9C=20?= =?UTF-8?q?=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 GET /api/medications/notes GET /api/medications/notes/search PATCH /api/medications/{medicationId} DELETE /api/medications/{medicationId} Co-Authored-By: Claude Sonnet 4.6 --- .../controller/MedicationController.java | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/main/java/com/piuda/callcare/domain/medication/controller/MedicationController.java b/src/main/java/com/piuda/callcare/domain/medication/controller/MedicationController.java index 96eb833..c787667 100644 --- a/src/main/java/com/piuda/callcare/domain/medication/controller/MedicationController.java +++ b/src/main/java/com/piuda/callcare/domain/medication/controller/MedicationController.java @@ -1,7 +1,9 @@ package com.piuda.callcare.domain.medication.controller; import com.piuda.callcare.domain.medication.dto.request.MedicationCreateRequest; +import com.piuda.callcare.domain.medication.dto.request.MedicationUpdateRequest; import com.piuda.callcare.domain.medication.dto.response.MedicationGroupItemResponse; +import com.piuda.callcare.domain.medication.dto.response.MedicationNoteGroupResponse; import com.piuda.callcare.domain.medication.dto.response.MedicationResponse; import com.piuda.callcare.domain.medication.service.command.MedicationCommandService; import com.piuda.callcare.domain.medication.service.query.MedicationQueryService; @@ -47,4 +49,45 @@ public ResponseEntity>> getGroup( ) { return ResponseUtils.ok(medicationQueryService.getGroup(userId, seniorId, hospitalName, prescriptionDate)); } + + @Operation(summary = "약물노트 목록 조회", description = "복용 시작일 + 병원 기준으로 그룹화된 약물 목록을 반환합니다.") + @GetMapping("/notes") + public ResponseEntity>> getNoteList( + @AuthenticationPrincipal Long userId, + @RequestParam Long seniorId + ) { + return ResponseUtils.ok(medicationQueryService.getNoteList(userId, seniorId)); + } + + @Operation(summary = "약물노트 검색", description = "약 이름/별명/병원명으로 검색합니다. period: 1w·1m·3m·1y (기본 1y)") + @GetMapping("/notes/search") + public ResponseEntity>> searchNotes( + @AuthenticationPrincipal Long userId, + @RequestParam Long seniorId, + @RequestParam String keyword, + @RequestParam(required = false, defaultValue = "1y") String period + ) { + return ResponseUtils.ok(medicationQueryService.searchNotes(userId, seniorId, keyword, period)); + } + + @Operation(summary = "약 수정", description = "null 필드는 변경하지 않습니다. timesPerDay 변경 시 스케줄을 재생성합니다.") + @PatchMapping("/{medicationId}") + public ResponseEntity> update( + @AuthenticationPrincipal Long userId, + @PathVariable Long medicationId, + @RequestBody MedicationUpdateRequest request + ) { + medicationCommandService.update(userId, medicationId, request); + return ResponseUtils.ok(); + } + + @Operation(summary = "약 삭제", description = "약과 복용 스케줄을 완전 삭제합니다. 복구 불가능합니다.") + @DeleteMapping("/{medicationId}") + public ResponseEntity> delete( + @AuthenticationPrincipal Long userId, + @PathVariable Long medicationId + ) { + medicationCommandService.delete(userId, medicationId); + return ResponseUtils.noContent(); + } } From 0abff885c9aa891132a60b20aa7e6c23f3d1fa24 Mon Sep 17 00:00:00 2001 From: kangcheolung Date: Thu, 9 Jul 2026 23:12:36 +0900 Subject: [PATCH 8/8] =?UTF-8?q?fix:=20=EC=BD=94=EB=93=9C=20=EB=A6=AC?= =?UTF-8?q?=EB=B7=B0=20=EC=A7=80=EC=A0=81=20=EC=82=AC=ED=95=AD=20=EB=B0=98?= =?UTF-8?q?=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - update 엔드포인트에 @Valid 추가 - MedicationUpdateRequest timesPerDay/totalDays에 @Min(1) 추가 - effectiveStart null 가드 추가로 NPE 방지 - resolveFromDate period 소문자 정규화 Co-Authored-By: Claude Sonnet 4.6 --- .../domain/medication/controller/MedicationController.java | 2 +- .../medication/dto/request/MedicationUpdateRequest.java | 5 +++-- .../service/command/MedicationCommandService.java | 6 ++++-- .../medication/service/query/MedicationQueryService.java | 2 +- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/piuda/callcare/domain/medication/controller/MedicationController.java b/src/main/java/com/piuda/callcare/domain/medication/controller/MedicationController.java index c787667..3ad7419 100644 --- a/src/main/java/com/piuda/callcare/domain/medication/controller/MedicationController.java +++ b/src/main/java/com/piuda/callcare/domain/medication/controller/MedicationController.java @@ -75,7 +75,7 @@ public ResponseEntity>> searchNote public ResponseEntity> update( @AuthenticationPrincipal Long userId, @PathVariable Long medicationId, - @RequestBody MedicationUpdateRequest request + @RequestBody @Valid MedicationUpdateRequest request ) { medicationCommandService.update(userId, medicationId, request); return ResponseUtils.ok(); diff --git a/src/main/java/com/piuda/callcare/domain/medication/dto/request/MedicationUpdateRequest.java b/src/main/java/com/piuda/callcare/domain/medication/dto/request/MedicationUpdateRequest.java index dbe5d62..c60c8c9 100644 --- a/src/main/java/com/piuda/callcare/domain/medication/dto/request/MedicationUpdateRequest.java +++ b/src/main/java/com/piuda/callcare/domain/medication/dto/request/MedicationUpdateRequest.java @@ -1,6 +1,7 @@ package com.piuda.callcare.domain.medication.dto.request; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Min; import java.time.LocalDate; @@ -14,10 +15,10 @@ public record MedicationUpdateRequest( String dosagePerTime, @Schema(description = "1일 복용 횟수 (null이면 변경 안 함, 변경 시 스케줄 재생성)") - Integer timesPerDay, + @Min(1) Integer timesPerDay, @Schema(description = "총 복용 일수 (null이면 변경 안 함)") - Integer totalDays, + @Min(1) Integer totalDays, @Schema(description = "복용 시작일 (null이면 변경 안 함)") LocalDate startDate, diff --git a/src/main/java/com/piuda/callcare/domain/medication/service/command/MedicationCommandService.java b/src/main/java/com/piuda/callcare/domain/medication/service/command/MedicationCommandService.java index 485da7a..39d2dde 100644 --- a/src/main/java/com/piuda/callcare/domain/medication/service/command/MedicationCommandService.java +++ b/src/main/java/com/piuda/callcare/domain/medication/service/command/MedicationCommandService.java @@ -121,8 +121,10 @@ public void update(Long userId, Long medicationId, MedicationUpdateRequest reque LocalDate newEndDate = null; if (request.startDate() != null || request.totalDays() != null) { LocalDate effectiveStart = request.startDate() != null ? request.startDate() : medication.getStartDate(); - Integer effectiveDays = request.totalDays() != null ? request.totalDays() : medication.getTotalDays(); - newEndDate = effectiveDays != null ? effectiveStart.plusDays(effectiveDays - 1) : null; + if (effectiveStart != null) { + Integer effectiveDays = request.totalDays() != null ? request.totalDays() : medication.getTotalDays(); + newEndDate = effectiveDays != null ? effectiveStart.plusDays(effectiveDays - 1) : null; + } } medication.update(request.drugName(), request.dosagePerTime(), request.timesPerDay(), diff --git a/src/main/java/com/piuda/callcare/domain/medication/service/query/MedicationQueryService.java b/src/main/java/com/piuda/callcare/domain/medication/service/query/MedicationQueryService.java index a33ad34..4a96a78 100644 --- a/src/main/java/com/piuda/callcare/domain/medication/service/query/MedicationQueryService.java +++ b/src/main/java/com/piuda/callcare/domain/medication/service/query/MedicationQueryService.java @@ -79,7 +79,7 @@ private List toNoteGroupResponses(List private LocalDate resolveFromDate(String period) { LocalDate today = LocalDate.now(); - return switch (period == null ? "1y" : period) { + return switch (period == null ? "1y" : period.toLowerCase()) { case "1w" -> today.minusWeeks(1); case "1m" -> today.minusMonths(1); case "3m" -> today.minusMonths(3);