From 56148127758205de2a34ecccd226d15b73c61727 Mon Sep 17 00:00:00 2001 From: shinseungjae Date: Sun, 1 Dec 2024 16:00:56 +0900 Subject: [PATCH 1/6] =?UTF-8?q?Feature/#182:=20=EB=8B=A4=EC=9D=8C=EB=8B=AC?= =?UTF-8?q?=EC=9D=98=20SalaryBudget=EC=9D=84=20=EC=83=9D=EC=84=B1=ED=95=98?= =?UTF-8?q?=EB=8A=94=20function=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../UseCases/Impls/BudgetUseCaseImpl.swift | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/Harubee/Sources/Domain/UseCases/Impls/BudgetUseCaseImpl.swift b/Harubee/Sources/Domain/UseCases/Impls/BudgetUseCaseImpl.swift index be9ba441..5260cec0 100644 --- a/Harubee/Sources/Domain/UseCases/Impls/BudgetUseCaseImpl.swift +++ b/Harubee/Sources/Domain/UseCases/Impls/BudgetUseCaseImpl.swift @@ -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 } @@ -167,9 +174,82 @@ 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 + + // 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) + } + func getAllSalaryBudget() throws -> [SalaryBudget] { return try salaryBudgetRepository.readAll() } From 4f3c29dc9bd37b1ae9127c796ec3bc161bb7cfb4 Mon Sep 17 00:00:00 2001 From: shinseungjae Date: Sun, 1 Dec 2024 16:04:22 +0900 Subject: [PATCH 2/6] =?UTF-8?q?Feature/#182:=20=EA=B3=A0=EC=A0=95=20?= =?UTF-8?q?=EC=88=98=EC=9E=85=20=EC=88=98=EC=A0=95,=20=EA=B3=A0=EC=A0=95?= =?UTF-8?q?=20=EC=A7=80=EC=B6=9C=20=EC=88=98=EC=A0=95,=20=EC=88=98?= =?UTF-8?q?=EC=9E=85=EC=9D=BC=20=EC=88=98=EC=A0=95=EC=8B=9C=20=EC=9D=B4?= =?UTF-8?q?=ED=9B=84=20salaryBudget=EC=97=90=20=EB=B0=98=EC=98=81=EB=90=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../UseCases/Impls/BudgetUseCaseImpl.swift | 94 +++++++++++++++++-- 1 file changed, 87 insertions(+), 7 deletions(-) diff --git a/Harubee/Sources/Domain/UseCases/Impls/BudgetUseCaseImpl.swift b/Harubee/Sources/Domain/UseCases/Impls/BudgetUseCaseImpl.swift index 5260cec0..232d8efc 100644 --- a/Harubee/Sources/Domain/UseCases/Impls/BudgetUseCaseImpl.swift +++ b/Harubee/Sources/Domain/UseCases/Impls/BudgetUseCaseImpl.swift @@ -329,14 +329,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, @@ -350,7 +354,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, @@ -371,6 +405,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 } @@ -382,9 +419,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 } @@ -402,7 +440,38 @@ 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 { + // 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, @@ -581,10 +650,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, From ee5b5c7751a2d3f14b2c984ed8b62c6049a3239a Mon Sep 17 00:00:00 2001 From: shinseungjae Date: Sun, 1 Dec 2024 16:08:54 +0900 Subject: [PATCH 3/6] =?UTF-8?q?Feature/#182:=20createSalaryBudgetIfNeeded?= =?UTF-8?q?=20BudgetUseCase=EC=97=90=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Domain/UseCases/Interfaces/BudgetUseCase.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Harubee/Sources/Domain/UseCases/Interfaces/BudgetUseCase.swift b/Harubee/Sources/Domain/UseCases/Interfaces/BudgetUseCase.swift index e6db1dda..5340e897 100644 --- a/Harubee/Sources/Domain/UseCases/Interfaces/BudgetUseCase.swift +++ b/Harubee/Sources/Domain/UseCases/Interfaces/BudgetUseCase.swift @@ -35,6 +35,11 @@ protocol BudgetUseCase { fixedExpenses: [TransactionItem] ) throws -> SalaryBudget + /// 다음 salaryBudget이 없다면, 생성합니다. + /// - Parameter salaryBudget: 초기 생성시에 사용했던 SalaryBudget + func createSalaryBudgetIfNeeded( + salaryBudget: SalaryBudget + ) throws /// 모든 SalaryBudget을 가져옵니다. /// - Returns: 저장된 모든 SalaryBudget From 5861edbcbd18d1d611b04b462c8fd7e7f34a771c Mon Sep 17 00:00:00 2001 From: shinseungjae Date: Sun, 1 Dec 2024 16:11:52 +0900 Subject: [PATCH 4/6] =?UTF-8?q?Feature/#182:=20=EA=B8=B0=EC=A1=B4=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=EC=9E=90=EB=93=A4=EC=9D=B4=20=EB=93=A4?= =?UTF-8?q?=EC=96=B4=EC=99=94=EC=9D=84=EB=95=8C=20=EB=8B=A4=EC=9D=8C=20sal?= =?UTF-8?q?aryBudget=EC=9D=B4=20=EC=97=86=EC=9D=84=EC=8B=9C=20salarybudget?= =?UTF-8?q?=20=EC=83=9D=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Presentation/Today/ViewModels/TodayViewModel.swift | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Harubee/Sources/Presentation/Today/ViewModels/TodayViewModel.swift b/Harubee/Sources/Presentation/Today/ViewModels/TodayViewModel.swift index 303d7c1d..9d2b28bb 100644 --- a/Harubee/Sources/Presentation/Today/ViewModels/TodayViewModel.swift +++ b/Harubee/Sources/Presentation/Today/ViewModels/TodayViewModel.swift @@ -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, From ce0f0dc8ebba6201a554877acd4871f1c4fe72c4 Mon Sep 17 00:00:00 2001 From: shinseungjae Date: Sun, 1 Dec 2024 16:37:10 +0900 Subject: [PATCH 5/6] =?UTF-8?q?Feature/#182:=20=EB=9D=84=EC=96=B4=EC=93=B0?= =?UTF-8?q?=EA=B8=B0=20=EB=B0=8F=20=EC=A3=BC=EC=84=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Harubee/Sources/Domain/UseCases/Impls/BudgetUseCaseImpl.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Harubee/Sources/Domain/UseCases/Impls/BudgetUseCaseImpl.swift b/Harubee/Sources/Domain/UseCases/Impls/BudgetUseCaseImpl.swift index 232d8efc..7d6ee680 100644 --- a/Harubee/Sources/Domain/UseCases/Impls/BudgetUseCaseImpl.swift +++ b/Harubee/Sources/Domain/UseCases/Impls/BudgetUseCaseImpl.swift @@ -174,7 +174,7 @@ final class BudgetUseCaseImpl: BudgetUseCase { // 9. Repository에 저장하기 salaryBudgetRepository.create(salaryBudget) - // 10. 다음달의 salaryBudget 생성 + // 10. 필요하다면 다음달의 salaryBudget 생성 do { try createSalaryBudgetIfNeeded(salaryBudget: salaryBudget) } catch { @@ -190,7 +190,7 @@ final class BudgetUseCaseImpl: BudgetUseCase { // 2. 이후에 SalaryBudget이 있다면 return guard !salaryBudgets.contains( - where: {$0.startDate > salaryBudget.startDate } + where: { $0.startDate > salaryBudget.startDate } ) else { return } // 3. 기존 startDate, endDate 기준으로 다음달의 날짜 계산하기 From 2e57dfc5cf2b457c1ed7e0cf5c1ac0c3cb0c523b Mon Sep 17 00:00:00 2001 From: shinseungjae Date: Sun, 1 Dec 2024 17:38:05 +0900 Subject: [PATCH 6/6] =?UTF-8?q?Feature/#182:=20fixedExpenses=20id=20?= =?UTF-8?q?=EA=B2=B9=EC=B9=98=EB=8A=94=20=EC=98=A4=EB=A5=98=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../UseCases/Impls/BudgetUseCaseImpl.swift | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/Harubee/Sources/Domain/UseCases/Impls/BudgetUseCaseImpl.swift b/Harubee/Sources/Domain/UseCases/Impls/BudgetUseCaseImpl.swift index 7d6ee680..9cb2a5a5 100644 --- a/Harubee/Sources/Domain/UseCases/Impls/BudgetUseCaseImpl.swift +++ b/Harubee/Sources/Domain/UseCases/Impls/BudgetUseCaseImpl.swift @@ -230,6 +230,14 @@ final class BudgetUseCaseImpl: BudgetUseCase { 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) @@ -239,7 +247,7 @@ final class BudgetUseCaseImpl: BudgetUseCase { startDate: nextStartDate, endDate: nextEndDate, fixedIncome: salaryBudget.fixedIncome, - fixedExpenses: salaryBudget.fixedExpenses, + fixedExpenses: newFixedExpenses, balance: initalBalance, defaultHarubee: nextDefaultHarubee, dailyBudgets: nextDailyBudgets @@ -447,13 +455,22 @@ final class BudgetUseCaseImpl: BudgetUseCase { } 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: expenses, + fixedExpenses: newFixedExpenses, balance: nextNewBalance, defaultHarubee: salaryBudget.defaultHarubee, dailyBudgets: salaryBudget.dailyBudgets @@ -465,7 +482,7 @@ final class BudgetUseCaseImpl: BudgetUseCase { try salaryBudgetRepository.updateSalaryBudget( salaryBudget.id, fixedIncome: .keep, - fixedExpenses: .set(expenses), + fixedExpenses: .set(newFixedExpenses), balance: .set(nextNewBalance), defaultHarubee: .set(defaultHarubee) )