Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@
import com.ClubAccount_BE.receipt.adapter.in.web.api.FindReceiptApi;
import com.ClubAccount_BE.receipt.adapter.in.web.dto.response.ReceiptCategoryResponse;
import com.ClubAccount_BE.receipt.adapter.in.web.dto.response.ReceiptDetailResponse;
import com.ClubAccount_BE.receipt.adapter.in.web.dto.response.ReceiptExpenseResponse;
import com.ClubAccount_BE.receipt.adapter.in.web.dto.response.ReceiptResponse;
import com.ClubAccount_BE.receipt.application.port.in.FindReceiptUseCase;
import jakarta.validation.constraints.Positive;
import java.time.LocalDate;
import java.util.List;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Pageable;
Expand Down Expand Up @@ -50,4 +53,12 @@ public ReceiptCategoryResponse getReceiptCategoryRatio(
) {
return findReceiptUseCase.getReceiptCategoryRatio(link);
}

@GetMapping("/{link}/receipts/expense")
public List<ReceiptExpenseResponse> getReceiptExpenseList(
@PathVariable(value = "link") UUID link,
@Positive(message = "유효하지 않은 연도입니다.") @RequestParam int year
) {
return findReceiptUseCase.getReceiptExpenseList(link, year);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@
import com.ClubAccount_BE.core.response.PagingResponse;
import com.ClubAccount_BE.receipt.adapter.in.web.dto.response.ReceiptCategoryResponse;
import com.ClubAccount_BE.receipt.adapter.in.web.dto.response.ReceiptDetailResponse;
import com.ClubAccount_BE.receipt.adapter.in.web.dto.response.ReceiptExpenseResponse;
import com.ClubAccount_BE.receipt.adapter.in.web.dto.response.ReceiptResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.constraints.Positive;
import java.time.LocalDate;
import java.util.List;
import java.util.UUID;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
Expand Down Expand Up @@ -39,4 +42,10 @@ ReceiptDetailResponse getReceipt(
ReceiptCategoryResponse getReceiptCategoryRatio(
@PathVariable(value = "link") UUID link
);

@Operation(summary = "영수증 월별 지출 목록 조회", description = "등록된 영수증의 월별 지출을 조회한다.")
List<ReceiptExpenseResponse> getReceiptExpenseList(
@PathVariable(value = "link") UUID link,
@Positive(message = "유효하지 않은 연도입니다.") @RequestParam int year
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.ClubAccount_BE.receipt.adapter.in.web.dto.response;

import com.ClubAccount_BE.receipt.domain.DetailExpenseResult;
import java.math.BigDecimal;
import java.util.UUID;
import lombok.Builder;

@Builder
public record ReceiptExpenseResponse(
UUID id,
int year,
int month,
BigDecimal totalExpense
) {

public static ReceiptExpenseResponse of(DetailExpenseResult result) {
return ReceiptExpenseResponse.builder()
.id(UUID.nameUUIDFromBytes((result.getYear() + "-" + result.getMonth()).getBytes()))
.year(result.getYear())
.month(result.getMonth())
.totalExpense(result.getTotalExpense())
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,15 @@ public Receipt getReceipt(User user, Long receiptId) {
.orElseThrow(() -> new ApiException(RECEIPT_NOT_FOUND));
}

@Override
public List<Receipt> getReceiptExpenseList(User user, int year) {
return receiptRepository
.findByUserIdAndYear(user.getId(), year)
.stream()
.map(ReceiptMapper::toDomain)
.toList();
}

@Override
public List<Receipt> getReceiptCategoryList(User user) {
return receiptRepository
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.ClubAccount_BE.receipt.adapter.out.persistence.entity.ReceiptEntity;
import java.time.LocalDate;
import java.util.List;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

Expand All @@ -13,4 +14,6 @@ Page<ReceiptEntity> findAllByDate(
LocalDate endDate,
Pageable pageable
);

List<ReceiptEntity> findByUserIdAndYear(Long userId, int year);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.ClubAccount_BE.receipt.adapter.out.persistence.repository;

import com.ClubAccount_BE.receipt.adapter.out.persistence.entity.QReceiptEntity;
import static com.ClubAccount_BE.receipt.adapter.out.persistence.entity.QReceiptEntity.receiptEntity;

import com.ClubAccount_BE.receipt.adapter.out.persistence.entity.ReceiptEntity;
import com.querydsl.core.BooleanBuilder;
import com.querydsl.jpa.impl.JPAQuery;
Expand All @@ -24,28 +25,38 @@ public Page<ReceiptEntity> findAllByDate(
LocalDate endDate,
Pageable pageable
) {
QReceiptEntity receipt = QReceiptEntity.receiptEntity;

BooleanBuilder where = new BooleanBuilder();
where.and(receipt.user.id.eq(userId));
where.and(receiptEntity.user.id.eq(userId));

if (startDate != null && endDate != null) {
where.and(receipt.date.between(startDate, endDate));
where.and(receiptEntity.date.between(startDate, endDate));
}

List<ReceiptEntity> content = queryFactory
.selectFrom(receipt)
.selectFrom(receiptEntity)
.where(where)
.orderBy(receipt.date.desc())
.orderBy(receiptEntity.date.desc())
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.fetch();

JPAQuery<Long> count = queryFactory
.select(receipt.count())
.from(receipt)
.select(receiptEntity.count())
.from(receiptEntity)
.where(where);

return PageableExecutionUtils.getPage(content, pageable, count::fetchOne);
}

@Override
public List<ReceiptEntity> findByUserIdAndYear(Long userId, int year) {
return queryFactory
.selectFrom(receiptEntity)
.where(
receiptEntity.user.id.eq(userId),
receiptEntity.date.year().eq(year)
)
.fetch();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
import com.ClubAccount_BE.core.response.PagingResponse;
import com.ClubAccount_BE.receipt.adapter.in.web.dto.response.ReceiptCategoryResponse;
import com.ClubAccount_BE.receipt.adapter.in.web.dto.response.ReceiptDetailResponse;
import com.ClubAccount_BE.receipt.adapter.in.web.dto.response.ReceiptExpenseResponse;
import com.ClubAccount_BE.receipt.adapter.in.web.dto.response.ReceiptResponse;
import java.time.LocalDate;
import java.util.List;
import java.util.UUID;
import org.springframework.data.domain.Pageable;

Expand All @@ -20,4 +22,6 @@ PagingResponse<ReceiptResponse> getReceiptList(
);

ReceiptDetailResponse getReceipt(UUID link, Long receiptId);

List<ReceiptExpenseResponse> getReceiptExpenseList(UUID link, int year);
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,6 @@ Page<Receipt> getReceiptList(
List<Receipt> getReceiptCategoryList(User user);

Receipt getReceipt(User user, Long receiptId);

List<Receipt> getReceiptExpenseList(User user, int year);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
import com.ClubAccount_BE.core.response.PagingResponse;
import com.ClubAccount_BE.receipt.adapter.in.web.dto.response.ReceiptCategoryResponse;
import com.ClubAccount_BE.receipt.adapter.in.web.dto.response.ReceiptDetailResponse;
import com.ClubAccount_BE.receipt.adapter.in.web.dto.response.ReceiptExpenseResponse;
import com.ClubAccount_BE.receipt.adapter.in.web.dto.response.ReceiptResponse;
import com.ClubAccount_BE.receipt.application.port.in.FindReceiptUseCase;
import com.ClubAccount_BE.receipt.application.port.out.FindReceiptPort;
import com.ClubAccount_BE.receipt.domain.DetailCategoryResult;
import com.ClubAccount_BE.receipt.domain.DetailExpenseResult;
import com.ClubAccount_BE.receipt.domain.Receipt;
import com.ClubAccount_BE.receipt.domain.service.ReceiptEditor;
import com.ClubAccount_BE.user.application.port.out.FindUserPort;
Expand Down Expand Up @@ -40,6 +42,17 @@ public ReceiptDetailResponse getReceipt(UUID link, Long receiptId) {
return ReceiptDetailResponse.of(receipt);
}

@Override
public List<ReceiptExpenseResponse> getReceiptExpenseList(UUID link, int year) {

User user = findUserPort.getUserByLink(link);
List<Receipt> receiptList = findReceiptPort.getReceiptExpenseList(user, year);
List<DetailExpenseResult> results = receiptEditor.calculateExpense(receiptList, year);
return results.stream()
.map(ReceiptExpenseResponse::of)
.toList();
}


@Override
public PagingResponse<ReceiptResponse> getReceiptList(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.ClubAccount_BE.receipt.domain;

import java.math.BigDecimal;
import lombok.Builder;
import lombok.Getter;

@Getter
public class DetailExpenseResult {

private final int year;
private final int month;
private final BigDecimal totalExpense;

@Builder
private DetailExpenseResult(int year, int month, BigDecimal totalExpense) {
this.year = year;
this.month = month;
this.totalExpense = totalExpense;
}

public static DetailExpenseResult of(int year, int month, BigDecimal totalExpense) {
return DetailExpenseResult.builder()
.year(year)
.month(month)
.totalExpense(totalExpense)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package com.ClubAccount_BE.receipt.domain.service;

import com.ClubAccount_BE.receipt.domain.DetailCategoryResult;
import com.ClubAccount_BE.receipt.domain.DetailExpenseResult;
import com.ClubAccount_BE.receipt.domain.Receipt;
import com.ClubAccount_BE.receipt.domain.ReceiptItem;
import com.ClubAccount_BE.receipt.domain.type.ReceiptCategory;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.springframework.stereotype.Service;

@Service
Expand Down Expand Up @@ -46,6 +48,24 @@ public DetailCategoryResult calculateCategoryRatio(List<Receipt> receipts) {
);
}

/**
* 영수증 월별 지출 계산
*/
public List<DetailExpenseResult> calculateExpense(List<Receipt> receiptList, int year) {
Map<Integer, BigDecimal> monthlyExpense = receiptList.stream()
.collect(Collectors.groupingBy(
receipt -> receipt.getDate().getMonthValue(),
Collectors.reducing(BigDecimal.ZERO, Receipt::getAmount, BigDecimal::add)
));

return IntStream.rangeClosed(1, 12)
.mapToObj(month -> DetailExpenseResult.of(
year,
month,
monthlyExpense.getOrDefault(month, BigDecimal.ZERO)))
.collect(Collectors.toList());
}

private float ratio(Long count, int total) {
return count == null ? 0f : (count * 100f / total);
}
Expand Down