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
191 changes: 184 additions & 7 deletions Harubee/Sources/Domain/UseCases/Impls/BudgetUseCaseImpl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,13 @@ final class BudgetUseCaseImpl: BudgetUseCase {
// 8. Repository에 저장하기
salaryBudgetRepository.create(salaryBudget)

// 9. 필요하다면 다음달의 salaryBudget 생성
do {
try createSalaryBudgetIfNeeded(salaryBudget: salaryBudget)
} catch {
print(error)
}

return salaryBudget
}

Expand Down Expand Up @@ -167,9 +174,90 @@ final class BudgetUseCaseImpl: BudgetUseCase {
// 9. Repository에 저장하기
salaryBudgetRepository.create(salaryBudget)

// 10. 필요하다면 다음달의 salaryBudget 생성
do {
try createSalaryBudgetIfNeeded(salaryBudget: salaryBudget)
} catch {
print(error)
}

return 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

let newFixedExpenses = salaryBudget.fixedExpenses.map {
let date = $0.date.day.convertDateBetweenStartAndEnd(
start: nextStartDate,
end: nextEndDate
)
return TransactionItem(date: date, name: $0.name, price: $0.price)
}

// 7. 새로운 기본 하루비 계산
let nextDefaultHarubee = Double(initalBalance) / Double(days + 1)

// 8. nextSalaryBudget 생성
let nextSalaryBudget = SalaryBudget(
id: UUID().uuidString,
startDate: nextStartDate,
endDate: nextEndDate,
fixedIncome: salaryBudget.fixedIncome,
fixedExpenses: newFixedExpenses,
balance: initalBalance,
defaultHarubee: nextDefaultHarubee,
dailyBudgets: nextDailyBudgets
)


// 9. 저장하기
salaryBudgetRepository.create(nextSalaryBudget)
}

func getAllSalaryBudget() throws -> [SalaryBudget] {
return try salaryBudgetRepository.readAll()
}
Expand Down Expand Up @@ -249,14 +337,18 @@ final class BudgetUseCaseImpl: BudgetUseCase {
) 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 else {
guard newBalance >= 0, nextNewBalance >= 0 else {
throw DomainError.invalidAmount
}

// 5. 기본 하루비 다시 계산하기
// 3. 현재 기본 하루비 다시 계산하기
let defaultHarubee = self.calculateDefaultHarubee(
salaryBudget: SalaryBudget(
startDate: salaryBudget.startDate,
Expand All @@ -270,7 +362,37 @@ final class BudgetUseCaseImpl: BudgetUseCase {
anchorDate: .now
)

// 6. Repository 통해 저장하기
// 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,
Expand All @@ -291,6 +413,9 @@ final class BudgetUseCaseImpl: BudgetUseCase {

let today = Date().formattedDate

// 0. 총 고정지출의 합계
let totalExpenses = expenses.reduce(0) { $0 + $1.price }

// 1. 기존 오늘 날짜 이후의 고정 지출의 합계
let oldPostTodayTotalExpenses = salaryBudget.fixedExpenses
.filter{ $0.date > today }
Expand All @@ -302,9 +427,10 @@ final class BudgetUseCaseImpl: BudgetUseCase {

// 3. 잔액에 반영
let newBalance = salaryBudget.balance - postTodayDifference
let nextNewBalance = salaryBudget.fixedIncome - totalExpenses

// 4. 잔액이 음수가 되는지 체크하기
guard newBalance >= 0 else {
guard newBalance >= 0, nextNewBalance >= 0 else {
throw DomainError.invalidAmount
}

Expand All @@ -322,7 +448,47 @@ final class BudgetUseCaseImpl: BudgetUseCase {
anchorDate: .now
)

// 6. Repository 통해 저장하기
// 6. 현재 SalaryBudget 이후에 SalaryBudgets 가져오기
let salaryBudgets = try salaryBudgetRepository.readAll()
let afterSalaryBudgets = salaryBudgets.filter {
$0.startDate > salaryBudget.startDate
}

for salaryBudget in afterSalaryBudgets {

let newFixedExpenses = expenses.map {
let date = $0.date.day.convertDateBetweenStartAndEnd(
start: salaryBudget.startDate,
end: salaryBudget.endDate
)
return TransactionItem(date: date, name: $0.name, price: $0.price)
}

// 7. 기본 하루비 계산
let defaultHarubee = self.calculateDefaultHarubee(
salaryBudget: SalaryBudget(
startDate: salaryBudget.startDate,
endDate: salaryBudget.endDate,
fixedIncome: salaryBudget.fixedIncome,
fixedExpenses: newFixedExpenses,
balance: nextNewBalance,
defaultHarubee: salaryBudget.defaultHarubee,
dailyBudgets: salaryBudget.dailyBudgets
),
anchorDate: .now
)

// 8. 반영된 수입과 잔액, 기본하루비 업데이트
try salaryBudgetRepository.updateSalaryBudget(
salaryBudget.id,
fixedIncome: .keep,
fixedExpenses: .set(newFixedExpenses),
balance: .set(nextNewBalance),
defaultHarubee: .set(defaultHarubee)
)
}

// 9. Repository 통해 저장하기
do {
return try salaryBudgetRepository.updateSalaryBudget(
salaryBudget.id,
Expand Down Expand Up @@ -501,10 +667,21 @@ final class BudgetUseCaseImpl: BudgetUseCase {

let (startDate, endDate) = Date.calculateStartAndEndDate(from: day)

// 4. 기존 salaryBudget 삭제하기
// 4. 기존 salaryBudget 삭제하기
try salaryBudgetRepository.deleteById(salaryBudget.id)

// 5. 새로운 SalaryBudget 생성
// 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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ protocol BudgetUseCase {
fixedExpenses: [TransactionItem]
) throws -> SalaryBudget

/// 다음 salaryBudget이 없다면, 생성합니다.
/// - Parameter salaryBudget: 초기 생성시에 사용했던 SalaryBudget
func createSalaryBudgetIfNeeded(
salaryBudget: SalaryBudget
) throws

/// 모든 SalaryBudget을 가져옵니다.
/// - Returns: 저장된 모든 SalaryBudget
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,18 +64,21 @@ extension TodayViewModel {
date: state.todayDate
)

// 2. 다음달의 SalaryBudget이 없다면 생성합니다.
try budgetUseCase.createSalaryBudgetIfNeeded(salaryBudget: salaryBudget)

initializeState(salaryBudget: salaryBudget)

} catch DomainError.dataNotFound {

// 2. 없다면 가장 최근 SalaryBudget을 기반으로 새 SalaryBudget 생성
// 3. 없다면 가장 최근 SalaryBudget을 기반으로 새 SalaryBudget 생성
let salaryBudgets = try? budgetUseCase.getAllSalaryBudget()

guard let recentSalaryBudget = salaryBudgets?.max(
by: { $0.endDate < $1.endDate }
) else { return }

// 3. 새로운 시작일과 종료일 계산
// 4. 새로운 시작일과 종료일 계산
let (newStartDate, newEndDate) = calculateNewSalaryBudgetDates(
referenceStartDay: Calendar.current.component(
.day,
Expand Down