feat: 분류 순서변경(reorder) + 월 예상 수입 설정 API
CI / build (push) Failing after 13m44s

- 분류: sort_order reorder 엔드포인트, 생성 시 맨 뒤 배치
- 예산: account_setting 테이블 + 월 예상 수입 GET/PUT

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
ByungCheol
2026-06-01 22:37:41 +09:00
parent 9ad6a91a56
commit cac9919cc5
11 changed files with 159 additions and 1 deletions
@@ -4,6 +4,8 @@ import com.sb.web.account.dto.BudgetPeriodStat;
import com.sb.web.account.dto.BudgetRequest;
import com.sb.web.account.dto.BudgetResponse;
import com.sb.web.account.dto.BudgetStatus;
import com.sb.web.account.dto.ExpectedIncomeRequest;
import com.sb.web.account.service.AccountSettingService;
import com.sb.web.account.service.BudgetService;
import com.sb.web.auth.dto.SessionUser;
import com.sb.web.auth.web.AuthInterceptor;
@@ -24,6 +26,23 @@ import java.util.List;
public class BudgetController {
private final BudgetService budgetService;
private final AccountSettingService settingService;
/** 월 예상 수입 조회 */
@GetMapping("/income")
public ExpectedIncomeRequest getIncome(
@RequestAttribute(AuthInterceptor.CURRENT_MEMBER) SessionUser current) {
return new ExpectedIncomeRequest(settingService.getExpectedIncome(current.getId()));
}
/** 월 예상 수입 설정 */
@PutMapping("/income")
public ExpectedIncomeRequest setIncome(
@Valid @RequestBody ExpectedIncomeRequest req,
@RequestAttribute(AuthInterceptor.CURRENT_MEMBER) SessionUser current) {
settingService.setExpectedIncome(current.getId(), req.getExpectedIncome());
return req;
}
@GetMapping
public List<BudgetResponse> list(
@@ -1,5 +1,6 @@
package com.sb.web.account.controller;
import com.sb.web.account.dto.CategoryReorderRequest;
import com.sb.web.account.dto.CategoryRequest;
import com.sb.web.account.dto.CategoryResponse;
import com.sb.web.account.service.CategoryService;
@@ -52,6 +53,14 @@ public class CategoryController {
return ResponseEntity.noContent().build();
}
/** 드래그앤드랍 순서 변경 */
@PutMapping("/reorder")
public List<CategoryResponse> reorder(
@RequestBody CategoryReorderRequest req,
@RequestAttribute(AuthInterceptor.CURRENT_MEMBER) SessionUser current) {
return categoryService.reorder(current.getId(), req.getType(), req.getIds());
}
/** 기존 내역의 분류를 목록으로 가져오기 */
@PostMapping("/import")
public List<CategoryResponse> importFromEntries(
@@ -0,0 +1,15 @@
package com.sb.web.account.dto;
import lombok.Data;
import java.util.List;
/**
* 분류 순서 변경 요청. ids 순서대로 sort_order 를 재설정한다.
*/
@Data
public class CategoryReorderRequest {
private String type; // INCOME / EXPENSE
private List<Long> ids; // 새 순서의 분류 id 목록
}
@@ -0,0 +1,18 @@
package com.sb.web.account.dto;
import jakarta.validation.constraints.PositiveOrZero;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 월 예상 수입 요청/응답.
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ExpectedIncomeRequest {
@PositiveOrZero(message = "예상 수입은 0 이상이어야 합니다.")
private Long expectedIncome;
}
@@ -0,0 +1,17 @@
package com.sb.web.account.mapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/**
* 사용자별 가계부 설정 매퍼 (account_setting).
*/
@Mapper
public interface AccountSettingMapper {
/** 월 예상 수입 (없으면 null) */
Long findExpectedIncome(@Param("memberId") Long memberId);
/** 월 예상 수입 upsert */
int upsertExpectedIncome(@Param("memberId") Long memberId, @Param("expectedIncome") Long expectedIncome);
}
@@ -28,4 +28,10 @@ public interface CategoryMapper {
int update(AccountCategory category);
int delete(@Param("id") Long id, @Param("memberId") Long memberId);
/** 순서(sort_order) 변경 */
int updateSortOrder(@Param("id") Long id, @Param("memberId") Long memberId, @Param("sortOrder") int sortOrder);
/** 해당 타입의 현재 최대 sort_order (신규 분류를 맨 뒤에 배치) */
Integer maxSortOrder(@Param("memberId") Long memberId, @Param("type") String type);
}
@@ -0,0 +1,25 @@
package com.sb.web.account.service;
import com.sb.web.account.mapper.AccountSettingMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* 사용자별 가계부 설정 서비스 (월 예상 수입 등).
*/
@Service
@RequiredArgsConstructor
public class AccountSettingService {
private final AccountSettingMapper settingMapper;
public Long getExpectedIncome(Long memberId) {
return settingMapper.findExpectedIncome(memberId);
}
@Transactional
public void setExpectedIncome(Long memberId, Long expectedIncome) {
settingMapper.upsertExpectedIncome(memberId, expectedIncome);
}
}
@@ -35,12 +35,27 @@ public class CategoryService {
if (categoryMapper.countByName(memberId, req.getType(), name, null) > 0) {
throw new ApiException(HttpStatus.CONFLICT, "이미 존재하는 분류입니다.");
}
Integer max = categoryMapper.maxSortOrder(memberId, req.getType());
int order = (max == null ? 0 : max + 1);
AccountCategory c = AccountCategory.builder()
.memberId(memberId).type(req.getType()).name(name).sortOrder(0).build();
.memberId(memberId).type(req.getType()).name(name).sortOrder(order).build();
categoryMapper.insert(c);
return CategoryResponse.from(c);
}
/** 드래그앤드랍 순서 변경: 전달된 id 순서대로 sort_order 재설정 (본인·동일 타입만) */
@Transactional
public List<CategoryResponse> reorder(Long memberId, String type, List<Long> ids) {
int i = 0;
for (Long id : ids) {
AccountCategory c = categoryMapper.findByIdAndMember(id, memberId);
if (c != null && c.getType().equals(type)) {
categoryMapper.updateSortOrder(id, memberId, i++);
}
}
return list(memberId);
}
/** 이름만 변경 가능. 변경 시 기존 내역·예산의 분류명도 함께 갱신 */
@Transactional
public CategoryResponse update(Long id, CategoryRequest req, Long memberId) {