Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] 다음달의 SalaryBudget 생성하는 기능 추가 #186

Open
wants to merge 6 commits into
base: develop
Choose a base branch
from

Conversation

Monfi98
Copy link
Collaborator

@Monfi98 Monfi98 commented Dec 1, 2024

💡 PR 유형

  • Feature: 기능 추가
  • Hotfix: 작은 버그 수정
  • Bugfix: 큰 버그 수정
  • Refactor: 코드 개선
  • Chore: 환경 설정

✏️ 변경 사항

다음달의 SalaryBudget 생성하는 기능을 추가하였습니다.

🚨 관련 이슈

📝 Pull Request Task ID

Pull Request Task ID: 1208855071953970

🧪 테스트

  • 목표한 구현 정상 동작 확인

✅ 체크리스트

  • 코드/커밋이 정해진 컨벤션을 잘 따르고 있나요?
  • PR의 Assignees와 Reviewers를 설정했나요?
  • 불필요한 코드가 없고, 정상적으로 동작하는지 확인했나요?
  • 관련 이슈 번호를 작성했나요?
  • Pull Request Task ID를 작성했나요?

🔥 추가 설명

BudgetUseCase에 추가된 function

BudgetUseCase에 다음 function이 추가되었습니다.

  • func createSalaryBudgetIfNeeded(salaryBudget: SalaryBudget)
func createSalaryBudgetIfNeeded(salaryBudget: SalaryBudget) throws {
    // 1. 모든 SalaryBudget 가져오기
    let salaryBudgets = try salaryBudgetRepository.readAll()
    
    // 2. 이후에 SalaryBudget이 있다면 return
    guard !salaryBudgets.contains(
      where: {$0.startDate > salaryBudget.startDate }
    ) else { return }
    
    // 3. 기존 startDate, endDate 기준으로 다음달의 날짜 계산하기
    let nextStartDate = calendar.date(
      byAdding: .month, value: 1, to: salaryBudget.startDate
    )!
    let nextEndDate = calendar.date(
      byAdding: .month, value: 1, to: salaryBudget.endDate
    )!
    
    // 4. 일 수 계산
    let days = calendar.dateComponents(
      [.day],
      from: nextStartDate,
      to: nextEndDate
    ).day ?? 0
    
    // 5. dailyBudget 생성
    let nextDailyBudgets = (0...days).compactMap { day -> DailyBudget? in
      guard let date = calendar.date(
        byAdding: .day,
        value: day,
        to: nextStartDate
      ) else { return nil }
      
      return DailyBudget(
        id: UUID().uuidString,
        date: date,
        harubee: nil,
        memo: [],
        expense: nil,
        income: nil
      )
    }
    
    // 6. initalBalance 계산
    let totalExpense = salaryBudget.fixedExpenses.reduce(0) { $0 + $1.price }
    let initalBalance = salaryBudget.fixedIncome - totalExpense
    
    // 7. 새로운 기본 하루비 계산
    let nextDefaultHarubee = Double(initalBalance) / Double(days + 1)
    
    // 8. nextSalaryBudget 생성
    let nextSalaryBudget = SalaryBudget(
      id: UUID().uuidString,
      startDate: nextStartDate,
      endDate: nextEndDate,
      fixedIncome: salaryBudget.fixedIncome,

      fixedExpenses: salaryBudget.fixedExpenses,
      balance: initalBalance,
      defaultHarubee: nextDefaultHarubee,
      dailyBudgets: nextDailyBudgets
    )
    
    
    // 9. 저장하기
    salaryBudgetRepository.create(nextSalaryBudget)
  }

위 함수는 각 createSalaryBudgetFromOnboarding, createSalaryBudget에 추가되었습니다.

또한, 기존에 하루비를 쓰고 있는 사용자들의 salaryBudget도 생성하기 위해
todayViewModel에서 데이터를 fetch해올때도 사용되었습니다.

고정 지출 수정, 고정 수입 수정, 고정 수입일 수정

고정 지출 수정, 고정 수입 수정, 고정 수입일 수정시에도 뒤에 salarybudget에 반영되어야 하기 때문에
이부분을 수정하였습니다.

코드는 아래와 같습니다.

func updateFixedIncome(
    salaryBudget: SalaryBudget,
    newIncome: Int
  ) throws -> SalaryBudget {
    
    // 1. 새로운 잔액 = oldBalance - (기존 월급 - 새로운 월급)
    let totalFixedExpense = salaryBudget.fixedExpenses.reduce(0) {
      $0 + $1.price
    }
    let newBalance = salaryBudget.balance - (salaryBudget.fixedIncome - newIncome)
    let nextNewBalance = newIncome - totalFixedExpense
    
    // 2. 잔액이 음수가 되는지 체크하기
    guard newBalance >= 0, nextNewBalance >= 0 else {
      throw DomainError.invalidAmount
    }
    
    // 3. 현재 기본 하루비 다시 계산하기
    let defaultHarubee = self.calculateDefaultHarubee(
      salaryBudget: SalaryBudget(
        startDate: salaryBudget.startDate,
        endDate: salaryBudget.endDate,
        fixedIncome: newIncome,
        fixedExpenses: salaryBudget.fixedExpenses,
        balance: newBalance,
        defaultHarubee: salaryBudget.defaultHarubee,
        dailyBudgets: salaryBudget.dailyBudgets
      ),
      anchorDate: .now
    )
    
    // 4. 현재 SalaryBudget 이후에 SalaryBudgets 가져오기
    let salaryBudgets = try salaryBudgetRepository.readAll()
    let afterSalaryBudgets = salaryBudgets.filter {
      $0.startDate > salaryBudget.startDate
    }
    
    for salaryBudget in afterSalaryBudgets {
      // 5. 기본 하루비 계산
      let defaultHarubee = self.calculateDefaultHarubee(
        salaryBudget: SalaryBudget(
          startDate: salaryBudget.startDate,
          endDate: salaryBudget.endDate,
          fixedIncome: newIncome,
          fixedExpenses: salaryBudget.fixedExpenses,
          balance: nextNewBalance,
          defaultHarubee: salaryBudget.defaultHarubee,
          dailyBudgets: salaryBudget.dailyBudgets
        ),
        anchorDate: .now
      )
      
      // 6. 반영된 수입과 잔액, 기본하루비 업데이트
      try salaryBudgetRepository.updateSalaryBudget(
          salaryBudget.id,
          fixedIncome: .set(newIncome),
          fixedExpenses: .keep,
          balance: .set(nextNewBalance),
          defaultHarubee: .set(defaultHarubee)
        )
    }
    
    do {
      return try salaryBudgetRepository.updateSalaryBudget(
        salaryBudget.id,
        fixedIncome: .set(newIncome),
        fixedExpenses: .keep,
        balance: .set(newBalance),
        defaultHarubee: .set(defaultHarubee)
      )
    } catch DomainError.dataNotFound {
      fatalError("고정 지출 배열을 설정했으나 SalaryBudget이 없습니다.")
    }
  }
  
  func updateFixedExpenses(
    salaryBudget: SalaryBudget,
    expenses: [TransactionItem]
  ) throws -> SalaryBudget {
    
    let today = Date().formattedDate
    
    // 0. 총 고정지출의 합계
    let totalExpenses = expenses.reduce(0) { $0 + $1.price }
    
    // 1. 기존 오늘 날짜 이후의 고정 지출의 합계
    let oldPostTodayTotalExpenses = salaryBudget.fixedExpenses
      .filter{ $0.date > today }
      .reduce(0) { $0 + $1.price }
    
    // 2. 오늘 날짜 이후의 고정지출들의 차이(new - old)
    let postTodayDifference = expenses.filter { $0.date > today }
      .reduce(0) { $0 + $1.price } - oldPostTodayTotalExpenses
    
    // 3. 잔액에 반영
    let newBalance = salaryBudget.balance - postTodayDifference
    let nextNewBalance = salaryBudget.fixedIncome - totalExpenses
    
    // 4. 잔액이 음수가 되는지 체크하기
    guard newBalance >= 0, nextNewBalance >= 0 else {
      throw DomainError.invalidAmount
    }
    
    // 5. 기본 하루비 다시 계산하기
    let defaultHarubee = self.calculateDefaultHarubee(
      salaryBudget: SalaryBudget(
        startDate: salaryBudget.startDate,
        endDate: salaryBudget.endDate,
        fixedIncome: salaryBudget.fixedIncome,
        fixedExpenses: expenses,
        balance: newBalance,
        defaultHarubee: salaryBudget.defaultHarubee,
        dailyBudgets: salaryBudget.dailyBudgets
      ),
      anchorDate: .now
    )
    
    // 6. 현재 SalaryBudget 이후에 SalaryBudgets 가져오기
    let salaryBudgets = try salaryBudgetRepository.readAll()
    let afterSalaryBudgets = salaryBudgets.filter {
      $0.startDate > salaryBudget.startDate
    }
    
    for salaryBudget in afterSalaryBudgets {
      // 7. 기본 하루비 계산
      let defaultHarubee = self.calculateDefaultHarubee(
        salaryBudget: SalaryBudget(
          startDate: salaryBudget.startDate,
          endDate: salaryBudget.endDate,
          fixedIncome: salaryBudget.fixedIncome,
          fixedExpenses: expenses,
          balance: nextNewBalance,
          defaultHarubee: salaryBudget.defaultHarubee,
          dailyBudgets: salaryBudget.dailyBudgets
        ),
        anchorDate: .now
      )
      
      // 8. 반영된 수입과 잔액, 기본하루비 업데이트
      try salaryBudgetRepository.updateSalaryBudget(
          salaryBudget.id,
          fixedIncome: .keep,
          fixedExpenses: .set(expenses),
          balance: .set(nextNewBalance),
          defaultHarubee: .set(defaultHarubee)
        )
    }
    
    // 9. Repository 통해 저장하기
    do {
      return try salaryBudgetRepository.updateSalaryBudget(
        salaryBudget.id,
        fixedIncome: .keep,
        fixedExpenses: .set(expenses),
        balance: .set(newBalance),
        defaultHarubee: .set(defaultHarubee)
      )
    } catch DomainError.dataNotFound {
      fatalError("고정 지출 배열을 설정했으나 SalaryBudget이 없습니다.")
    }
  }

func setIncomeDay(
    day: Int,
    salaryBudget: SalaryBudget
  ) throws -> SalaryBudget {
    
    // 1. 월급일이 1일부터 31일 사이에 속하는지 확인하기
    guard (1...31).contains(day) else {
      throw DomainError.dateOutOfRange
    }
    
    // 2. UserDefaults에 설정하기
    try userDefaultsRepository.saveIncomeDay(day)
    
    let (startDate, endDate) = Date.calculateStartAndEndDate(from: day)
    
    // 4. 기존 salaryBudget 삭제하기
    try salaryBudgetRepository.deleteById(salaryBudget.id)
    
    // 5. 현재 salaryBudgte 이후에 salaryBudget 가져오기
    let salaryBudgets = try salaryBudgetRepository.readAll()
    let afterSalaryBudgets = salaryBudgets.filter {
      $0.startDate > salaryBudget.startDate
    }
    
    // 6. 아이디 찾아서 삭제하기
    for salaryBudget in afterSalaryBudgets {
      try salaryBudgetRepository.deleteById(salaryBudget.id)
    }
    
    // 7. 새로운 SalaryBudget 생성
    return try self.createSalaryBudget(
      startDate: startDate,
      endDate: endDate,
      fixedIncome: salaryBudget.fixedIncome,
      fixedExpenses: salaryBudget.fixedExpenses
    )
  }

여러번 검수했지만 혹시 모르니 같이 한번 봐주시면 감사하겠습니다.

@Monfi98 Monfi98 self-assigned this Dec 1, 2024
Copy link
Collaborator

@namudongs namudongs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

로직은 정상 동작하는거 확인했고, 코드의 퀄리티는 나중에 얘기하기로 해요.
참고로 현재 Develop의 최신 커밋이랑 충돌이 있으니 풀 받으신 후 머지 부탁드립니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants