Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
4a19365
feat(Budget): 예산 관련 view 구현
mipangg May 19, 2025
7a84995
Merge branch 'dev' into feat/28-Budget_view to resolve conflictsMerge…
mipangg May 19, 2025
40b5474
feat(Service, Budget): ApiService, AuthService 최신본 업데이트, Budget View
mipangg May 19, 2025
354ade3
fix: id.string bug
KEEKE132 May 20, 2025
7a11e56
refactor(team): teamId recoil atom 값으로 관리하게 수정
johnhuh619 May 20, 2025
a84e4c6
fix(team): 로그인 이후 team-setup(팀 생성/참여/워크스페이스 이동 페이지) 경로로 리다이렉트 되게 수정
johnhuh619 May 20, 2025
d796b59
fix(team): Settlement 페이지로 리다이렉트 되게 수정
johnhuh619 May 20, 2025
1686357
fix(team): Settlement 페이지에서 teamId를 경로로 받지 않고 recoil의 atom 값을 사용하게 변경
johnhuh619 May 20, 2025
9052e5c
refactor: 팀 대시보드로 가게 수정, CSS 수정 및 콘솔 추가
pbk2312 May 20, 2025
7ae8fe7
Merge pull request #58 from prgrms-be-devcourse/feat/46-expenseView-r…
johnhuh619 May 20, 2025
c489094
Merge branch 'feat/28-Budget_view' into feat/28-Budget_view-refactor-…
johnhuh619 May 20, 2025
ee76a03
Merge pull request #59 from prgrms-be-devcourse/feat/28-Budget_view-r…
johnhuh619 May 20, 2025
f6afafd
refactor: settlementResponse 응답에 연관이름 추가
KEEKE132 May 20, 2025
91cc8fe
refactor(team): 예산 생성 불가 에러 수정
johnhuh619 May 20, 2025
43fad93
Merge remote-tracking branch 'origin/feat/28-Budget_view-refactor-tea…
johnhuh619 May 20, 2025
96aa023
refactor(Budget_view) : Team 코드를 Service파일을 새로 만들고 코드를 분리 , Import를 수…
garusitell May 20, 2025
b777cc3
fix: 경로 수정
KEEKE132 May 20, 2025
8780564
fix: 상세 페이지 수정
KEEKE132 May 20, 2025
ac0f10c
fix: 정산 목록페이지 수정
KEEKE132 May 20, 2025
4afffaf
fix: 정산 폼 수정
KEEKE132 May 20, 2025
f11f4d1
fix: updateSettlement
KEEKE132 May 20, 2025
8db73f6
fix: edit page, list page
KEEKE132 May 20, 2025
0ef2074
fix: init pagination metadata
KEEKE132 May 20, 2025
b4b0ac0
refactor(team): 중괄호 에러 수정
johnhuh619 May 20, 2025
99c9339
fix: init pagination metadata
KEEKE132 May 20, 2025
1473788
refactor: use recoilTeamId
KEEKE132 May 20, 2025
2ce7ad7
Merge remote-tracking branch 'origin/feat/28-Budget_view' into refact…
KEEKE132 May 20, 2025
bc5287c
Merge pull request #62 from prgrms-be-devcourse/refactor/56-settlemen…
KEEKE132 May 20, 2025
41ff3f7
refactor(team): overViewData에서 예산 관련한 정보를 budget으로 래핑
johnhuh619 May 20, 2025
e823264
Merge branch 'feat/28-Budget_view' into feat/28-Budget_view-refactor-…
johnhuh619 May 20, 2025
53ede41
Merge pull request #63 from prgrms-be-devcourse/feat/28-Budget_view-r…
mipangg May 20, 2025
8cbb2af
feat: 카드/현금 구분 로직 추가
KEEKE132 May 20, 2025
4837990
feat(Budget, TeamDashBoard): 예산 및 팀 대시보드 기능 수정
mipangg May 20, 2025
22f71b1
bug(budget): 예산 삭제 시 삭제가 안되는 이슈 해결
johnhuh619 May 20, 2025
d74b0ca
Merge branch 'feat/28-Budget_view' into feat/28-Budget_view-bugfix-1
johnhuh619 May 20, 2025
c8a0c7a
Merge pull request #64 from prgrms-be-devcourse/feat/28-Budget_view-b…
johnhuh619 May 20, 2025
7c54cdd
refactor: 예산 계산 수정
KEEKE132 May 20, 2025
8c26acd
Merge pull request #65 from prgrms-be-devcourse/refactor/52-settlements
KEEKE132 May 20, 2025
36636c8
fix: 메서드 누락 수정
KEEKE132 May 20, 2025
dd7674b
delete: 정산 생성 버튼 제거
KEEKE132 May 20, 2025
97ee758
refactor: 정산 완료 버튼
KEEKE132 May 20, 2025
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
@@ -1,5 +1,6 @@
package com.luckyseven.backend.domain.budget.entity;

import com.luckyseven.backend.domain.budget.dto.BudgetUpdateRequest;
import com.luckyseven.backend.domain.team.entity.Team;
import com.luckyseven.backend.sharedkernel.entity.BaseEntity;
import jakarta.persistence.Column;
Expand Down Expand Up @@ -38,17 +39,16 @@ public class Budget extends BaseEntity {
@Column(nullable = false)
private Long setBy;

@Setter
@Column(nullable = false)
private BigDecimal balance;
@Setter

private BigDecimal foreignBalance;

@Setter
@Enumerated(EnumType.STRING)
@Column(nullable = false, length = 3)
private CurrencyCode foreignCurrency;

@Setter
private BigDecimal avgExchangeRate;

@Builder
Expand All @@ -66,7 +66,13 @@ public Budget(Team team, BigDecimal totalAmount, Long setBy,

public void setTotalAmount(BigDecimal totalAmount) {
this.totalAmount = totalAmount;
this.balance = totalAmount;
}

public void addBalance(BudgetUpdateRequest request) {
if (request.additionalBudget() == null) {
return;
}
this.balance = this.balance.add(request.additionalBudget());
}

public void setExchangeInfo(boolean isExchanged, BigDecimal amount, BigDecimal exchangeRate) {
Expand All @@ -77,29 +83,30 @@ public void setExchangeInfo(boolean isExchanged, BigDecimal amount, BigDecimal e
}

updateForeignBalance(amount, exchangeRate);
this.avgExchangeRate = exchangeRate;

}

public void updateExchangeInfo(boolean isExchanged, BigDecimal amount, BigDecimal exchangeRate) {
if (!isExchanged) {
return;
}

updateForeignBalance(amount, exchangeRate);
updateAvgExchangeRate(amount, exchangeRate);
updateForeignBalance(amount, exchangeRate);

}

// 예산 추가 후 외화잔고 및 평균환율 수정
private void updateAvgExchangeRate(BigDecimal amount, BigDecimal exchangeRate) {
if (this.avgExchangeRate == null) {
if (this.avgExchangeRate == null || this.avgExchangeRate.compareTo(BigDecimal.ZERO) == 0) {
avgExchangeRate = exchangeRate;
return;
}
this.avgExchangeRate = (this.balance.multiply(this.avgExchangeRate)
.add(amount.multiply(exchangeRate)))
.divide(this.balance.add(amount), 2, RoundingMode.HALF_UP);
BigDecimal foreignAmount = amount.divide(exchangeRate, 10,
RoundingMode.HALF_UP); // 외화 환산, 충분한 정밀도 확보
BigDecimal totalCost = this.foreignBalance.multiply(this.avgExchangeRate).add(amount);
BigDecimal totalForeign = this.foreignBalance.add(foreignAmount);
this.avgExchangeRate = totalCost.divide(totalForeign, 2, RoundingMode.HALF_UP);

}

private void updateForeignBalance(BigDecimal amount, BigDecimal exchangeRate) {
Expand All @@ -116,6 +123,10 @@ public void setForeignBalance() {
}
}

public void setForeignBalance(BigDecimal amount) {
this.foreignBalance = amount;
}

public Budget setTeam(Team team) {
// 기존 연결 해제
if (this.team != null) {
Expand All @@ -131,6 +142,7 @@ public Budget setTeam(Team team) {

return this;
}

public void updateBalance(BigDecimal balance) {
if (balance != null) {
this.balance = balance;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public BudgetCreateResponse save(Long teamId, Long loginMemberId, BudgetCreateRe
request.exchangeRate());

budgetRepository.save(budget);
team.setBudget(budget);

return budgetMapper.toCreateResponse(budget);
}
Expand Down Expand Up @@ -74,7 +75,14 @@ public BudgetUpdateResponse updateByTeamId(Long teamId, Long loginMemberId,

@Transactional
public void deleteByTeamId(Long teamId) {
Team team = teamRepository.findById(teamId)
.orElseThrow(() -> new EntityNotFoundException("팀을 찾을 수 없습니다: " + teamId));


Budget budget = budgetValidator.validateBudgetExist(teamId);

team.setBudget(null);
teamRepository.save(team);
budgetRepository.delete(budget);
}

Expand All @@ -85,6 +93,7 @@ private static void addBudget(BudgetUpdateRequest request, Budget budget) {
request.additionalBudget(),
request.exchangeRate());
budget.setTotalAmount(budget.getTotalAmount().add(request.additionalBudget()));
budget.addBalance(request);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import com.luckyseven.backend.domain.expense.dto.ExpenseResponse;
import com.luckyseven.backend.domain.expense.dto.ExpenseUpdateRequest;
import com.luckyseven.backend.domain.expense.entity.Expense;
import com.luckyseven.backend.domain.expense.enums.PaymentMethod;
import com.luckyseven.backend.domain.expense.mapper.ExpenseMapper;
import com.luckyseven.backend.domain.expense.repository.ExpenseRepository;
import com.luckyseven.backend.domain.member.entity.Member;
Expand Down Expand Up @@ -46,13 +47,24 @@ public CreateExpenseResponse saveExpense(Long teamId, ExpenseRequest request) {
Member payer = findPayerOrThrow(request.payerId());

Budget budget = team.getBudget();
validateSufficientBudget(request.amount(), budget.getBalance());

if (request.paymentMethod() == PaymentMethod.CASH) {
BigDecimal foreignAmount = request.amount();
BigDecimal KRWAmount = foreignAmount.multiply(budget.getAvgExchangeRate());
validateSufficientBudget(KRWAmount, budget.getBalance());
validateSufficientBudget(foreignAmount, budget.getForeignBalance());
budget.updateBalance(budget.getBalance().subtract(KRWAmount));
budget.setForeignBalance(budget.getForeignBalance().subtract(foreignAmount));

} else if (request.paymentMethod() == PaymentMethod.CARD) {
validateSufficientBudget(request.amount(), budget.getBalance());
budget.updateBalance(budget.getBalance().subtract(request.amount()));
}

Expense expense = ExpenseMapper.fromExpenseRequest(request, team, payer);
Expense saved = expenseRepository.save(expense);

// TODO: 낙관적 락(Lock) 적용 검토
budget.updateBalance(budget.getBalance().subtract(request.amount()));

createAllSettlements(request, payer, saved);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ private Expense getExpense(SettlementUpdateRequest request) {
@Transactional
public SettlementResponse settleSettlement(Long id) {
Settlement settlement = findSettlementOrThrow(id);
settlement.setSettled();
settlement.convertSettled();
return SettlementMapper.toSettlementResponse(settlementRepository.save(settlement));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,12 @@ public record SettlementResponse(
BigDecimal amount,
Boolean isSettled,
Long settlerId,
String settlerNickname,
Long payerId,
Long expenseId
String payerNickname,
Long expenseId,
String expenseDescription,
Long teamId
) {

}
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ public void update(BigDecimal amount, Member settler, Member payer, Expense expe
}
}

public void setSettled() {
this.isSettled = true;
public void convertSettled() {
this.isSettled = !this.isSettled;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,12 @@ public static SettlementResponse toSettlementResponse(Settlement settlement) {
.updatedAt(settlement.getUpdatedAt())
.isSettled(settlement.getIsSettled())
.settlerId(settlement.getSettler().getId())
.settlerNickname(settlement.getSettler().getNickname())
.payerId(settlement.getPayer().getId())
.payerNickname(settlement.getPayer().getNickname())
.expenseId(settlement.getExpense().getId())
.expenseDescription(settlement.getExpense().getDescription())
.teamId(settlement.getExpense().getTeam().getId())
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import com.luckyseven.backend.domain.team.util.TeamMapper;
import com.luckyseven.backend.sharedkernel.exception.CustomLogicException;
import com.luckyseven.backend.sharedkernel.exception.ExceptionCode;
import java.math.BigDecimal;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -67,20 +66,20 @@ public TeamCreateResponse createTeam(MemberDetails memberDetails
// 리더를 TeamMember 에 추가
teamMemberRepository.save(teamMember);

// <TODO> 예산 생성(임시로 구현)
Budget budget = Budget.builder()
.foreignCurrency(com.luckyseven.backend.domain.budget.entity.CurrencyCode.KRW) // Set default currency to KRW
.balance(BigDecimal.ZERO)
.foreignBalance(BigDecimal.ZERO)
.totalAmount(BigDecimal.ZERO)
.avgExchangeRate(BigDecimal.ONE)
.setBy(memberId) // Set the creator as the setter
.build();

// Team이 Budget의 주인이므로, Team 에서 Budget set
Budget savedBudget = budgetRepository.save(budget);
savedTeam.setBudget(savedBudget);
savedBudget.setTeam(savedTeam);
// // <TODO> 예산 생성(임시로 구현)
// Budget budget = Budget.builder()
// .foreignCurrency(com.luckyseven.backend.domain.budget.entity.CurrencyCode.KRW) // Set default currency to KRW
// .balance(BigDecimal.ZERO)
// .foreignBalance(BigDecimal.ZERO)
// .totalAmount(BigDecimal.ZERO)
// .avgExchangeRate(BigDecimal.ONE)
// .setBy(memberId) // Set the creator as the setter
// .build();
//
// // Team이 Budget의 주인이므로, Team 에서 Budget set
// Budget savedBudget = budgetRepository.save(budget);
// savedTeam.setBudget(savedBudget);
// savedBudget.setTeam(savedTeam);

savedTeam.addTeamMember(teamMember);
return TeamMapper.toTeamCreateResponse(savedTeam);
Expand Down Expand Up @@ -164,9 +163,12 @@ public TeamDashboardResponse getTeamDashboard(Long teamId) {
.orElseThrow(() -> new CustomLogicException(ExceptionCode.TEAM_NOT_FOUND,
"ID가 [%d]인 팀을 찾을 수 없습니다", teamId));

Budget budget = budgetRepository.findByTeamId(teamId)
.orElseThrow(() -> new CustomLogicException(ExceptionCode.BUDGET_NOT_FOUND,
"팀 ID [%d]의 예산 정보가 없습니다", teamId));
// Budget budget = budgetRepository.findByTeamId(teamId)
// .orElseThrow(() -> new CustomLogicException(ExceptionCode.BUDGET_NOT_FOUND,
// "팀 ID [%d]의 예산 정보가 없습니다", teamId));

// 예산이 없는 경우 null로 처리 (Optional 사용)
Budget budget = budgetRepository.findByTeamId(teamId).orElse(null);

Pageable pageable = PageRequest.of(0, 5, Sort.by("createdAt").descending());
Page<Expense> expensePage = expenseRepository.findByTeamId(teamId, pageable);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.luckyseven.backend.domain.budget.service.BudgetService;
import com.luckyseven.backend.domain.budget.validator.BudgetValidator;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
Expand Down Expand Up @@ -51,19 +52,11 @@ void create_should_return_201() throws Exception {

// given
Long teamId = 1L;
BudgetCreateRequest request = BudgetCreateRequest.builder()
.totalAmount(BigDecimal.valueOf(100000))
.foreignCurrency(CurrencyCode.USD)
.isExchanged(true)
.exchangeRate(BigDecimal.valueOf(1393.7))
.build();
BudgetCreateResponse response = BudgetCreateResponse.builder()
.id(1L)
.balance(BigDecimal.valueOf(100000))
.foreignBalance(BigDecimal.valueOf(71.75))
.avgExchangeRate(BigDecimal.valueOf(1393.7))
.build();

BudgetCreateRequest request = new BudgetCreateRequest(BigDecimal.valueOf(100000),
true, BigDecimal.valueOf(1393.7), CurrencyCode.USD);
BudgetCreateResponse response = new BudgetCreateResponse(1L, LocalDateTime.now(),
1L, BigDecimal.valueOf(100000), BigDecimal.valueOf(1393.7),
BigDecimal.valueOf(71.75));
when(budgetService.save(teamId, 2L, request)).thenReturn(response);

// when
Expand All @@ -86,15 +79,10 @@ void read_should_return_200() throws Exception {

// given
Long teamId = 1L;
BudgetReadResponse response = BudgetReadResponse.builder()
.id(1L)
.totalAmount(BigDecimal.valueOf(100000))
.setBy(1L)
.balance(BigDecimal.valueOf(100000))
.foreignCurrency(CurrencyCode.USD)
.foreignBalance(BigDecimal.valueOf(71.75))
.avgExchangeRate(BigDecimal.valueOf(1393.70))
.build();
BudgetReadResponse response = new BudgetReadResponse(1L, LocalDateTime.now(), 1L,
BigDecimal.valueOf(100000), BigDecimal.valueOf(100000), CurrencyCode.USD,
BigDecimal.valueOf(1393.70), BigDecimal.valueOf(71.75));


when(budgetService.getByTeamId(teamId)).thenReturn(response);

Expand All @@ -113,16 +101,12 @@ void patch_should_return_200() throws Exception {

// given
Long teamId = 1L;
BudgetUpdateRequest request = BudgetUpdateRequest.builder()
.totalAmount(BigDecimal.valueOf(150000))
.build();
BudgetUpdateResponse response = BudgetUpdateResponse.builder()
.id(1L)
.balance(BigDecimal.valueOf(150000))
.foreignBalance(BigDecimal.valueOf(107.63))
.foreignCurrency(CurrencyCode.USD)
.avgExchangeRate(BigDecimal.valueOf(1393.7))
.build();
BudgetUpdateRequest request = new BudgetUpdateRequest(BigDecimal.valueOf(150000),
false, null, null);

BudgetUpdateResponse response = new BudgetUpdateResponse(1L, LocalDateTime.now(),
1L, BigDecimal.valueOf(150000), CurrencyCode.USD, BigDecimal.valueOf(1393.7),
BigDecimal.valueOf(107.63));

when(budgetService.updateByTeamId(teamId, 2L, request)).thenReturn(response);

Expand Down
Loading