@@ -161,22 +161,22 @@ public RoutineResponse.ApplyRoutineInfoDTO getRoutineApplyInfo(Long userId, Long
161161 User user = userRepository .findById (userId )
162162 .orElseThrow (() -> new ErrorHandler (ErrorStatus .USER_NOT_FOUND ));
163163
164- // 2. 소비 루틴 조회 (자신의 루틴은 허용하지 않음)
164+ // 2. 소비 루틴 조회
165165 Routine routine = routineRepository .findById (routineId )
166166 .orElseThrow (() -> new ErrorHandler (ErrorStatus .ROUTINE_NOT_FOUND ));
167167
168- // if (routine.getUser().getId().equals(userId)) {
169- // throw new ErrorHandler(ErrorStatus.ROUTINE_PREVIEW_NOT_ALLOWED);
170- // }
168+ // 본인 루틴은 미리보기 제한 (단, userId=1은 예외 허용)
169+ // if (!userId.equals(1L) && routine.getUser().getId().equals(userId)) {
170+ // throw new ErrorHandler(ErrorStatus.ROUTINE_PREVIEW_NOT_ALLOWED);
171+ // }
171172
172173 // 3. 해당 루틴의 예산 생성 월 기준으로 내 예산 조회
173174 Budget myBudget = budgetRepository .findByUserAndCreatedMonth (user , routine .getBudget ().getCreatedMonth ())
174175 .orElseThrow (() -> new ErrorHandler (ErrorStatus .BUDGET_NOT_FOUND ));
175176
176- if (!userId .equals (1L )) {
177- if (Boolean .TRUE .equals (myBudget .getIsFromRoutine ())) {
178- throw new ErrorHandler (ErrorStatus .ROUTINE_ALREADY_APPLIED );
179- }
177+ // 이미 루틴 적용된 예산이라면 예외 (단, userId=1은 중복 허용)
178+ if (!userId .equals (1L ) && Boolean .TRUE .equals (myBudget .getIsFromRoutine ())) {
179+ throw new ErrorHandler (ErrorStatus .ROUTINE_ALREADY_APPLIED );
180180 }
181181
182182 // 4. 내 예산의 소비 카테고리 로드
@@ -194,7 +194,6 @@ public RoutineResponse.ApplyRoutineInfoDTO getRoutineApplyInfo(Long userId, Long
194194
195195 // 6. Default 카테고리 처리
196196 Set <String > defaultNames = new HashSet <>(DefaultCategoryConstants .DEFAULT_CATEGORY_NAMES );
197-
198197 List <RoutineResponse .ApplyCategoryBudgetDTO > defaultCategoryBudgets =
199198 DefaultCategoryConstants .DEFAULT_CATEGORY_NAMES .stream ()
200199 .map (name -> {
@@ -224,11 +223,28 @@ public RoutineResponse.ApplyRoutineInfoDTO getRoutineApplyInfo(Long userId, Long
224223 })
225224 .map (mc -> RoutineConverter .toCategoryDTO (
226225 mc .getConsumptionCategory ().getBudgetCategoryName (),
227- 0 ,
226+ 0 , // ✅ 루틴에 포함되지 않은 CUSTOM 항목은 금액 0으로 초기화
228227 CategoryType .CUSTOM )
229228 )
230229 .collect (Collectors .toList ());
231230
231+ // 9. 요청에 없는 기존 카테고리 → 금액 0으로 초기화
232+ // (루틴 반영 applyRoutineToBudget 로직과 동일한 컨셉 적용)
233+ Set <String > requestCategoryNames = new HashSet <>();
234+ requestCategoryNames .addAll (defaultCategoryBudgets .stream ()
235+ .map (RoutineResponse .ApplyCategoryBudgetDTO ::getCategoryName )
236+ .toList ());
237+ requestCategoryNames .addAll (routineCategoryBudgets .stream ()
238+ .map (RoutineResponse .ApplyCategoryBudgetDTO ::getCategoryName )
239+ .toList ());
240+ requestCategoryNames .addAll (customCategoryBudgets .stream ()
241+ .map (RoutineResponse .ApplyCategoryBudgetDTO ::getCategoryName )
242+ .toList ());
243+
244+ myCategoryMap .values ().stream ()
245+ .filter (existing -> !requestCategoryNames .contains (existing .getConsumptionCategory ().getBudgetCategoryName ()))
246+ .forEach (existing -> existing .updateAmount (0 ));
247+
232248 log .info ("소비 루틴 예산 반영 미리보기 완료 - userId: {}, routineId: {}" , userId , routineId );
233249
234250 return RoutineConverter .toApplyRoutineInfoDTO (
0 commit comments