-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
move goal calculations to separate files by type
- Loading branch information
1 parent
df5aa31
commit 4d38637
Showing
8 changed files
with
489 additions
and
341 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import * as monthUtils from '../../../shared/months'; | ||
import { amountToInteger } from '../../../shared/util'; | ||
import { isReflectBudget } from '../actions'; | ||
|
||
export async function goalsBy( | ||
template_lines, | ||
current_month, | ||
template, | ||
l, | ||
remainder, | ||
last_month_balance, | ||
to_budget, | ||
errors, | ||
) { | ||
// by has 'amount' and 'month' params | ||
if (!isReflectBudget()) { | ||
let target = 0; | ||
let target_month = `${template_lines[l].month}-01`; | ||
let num_months = monthUtils.differenceInCalendarMonths( | ||
target_month, | ||
current_month, | ||
); | ||
let repeat = | ||
template.type === 'by' ? template.repeat : (template.repeat || 1) * 12; | ||
while (num_months < 0 && repeat) { | ||
target_month = monthUtils.addMonths(target_month, repeat); | ||
num_months = monthUtils.differenceInCalendarMonths( | ||
template_lines[l].month, | ||
current_month, | ||
); | ||
} | ||
if (l === 0) remainder = last_month_balance; | ||
remainder = amountToInteger(template_lines[l].amount) - remainder; | ||
if (remainder >= 0) { | ||
target = remainder; | ||
remainder = 0; | ||
} else { | ||
target = 0; | ||
remainder = Math.abs(remainder); | ||
} | ||
let increment = num_months >= 0 ? Math.round(target / (num_months + 1)) : 0; | ||
to_budget += increment; | ||
} else { | ||
errors.push(`by templates are not supported in Report budgets`); | ||
} | ||
return { to_budget, errors }; | ||
} |
55 changes: 55 additions & 0 deletions
55
packages/loot-core/src/server/budget/goals/goalsPercentage.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import * as monthUtils from '../../../shared/months'; | ||
import * as db from '../../db'; | ||
import { getSheetValue } from '../actions'; | ||
|
||
export async function goalsPercentage( | ||
template, | ||
month, | ||
available_start, | ||
sheetName, | ||
to_budget, | ||
errors, | ||
) { | ||
let percent = template.percent; | ||
let monthlyIncome = 0; | ||
|
||
if (template.category.toLowerCase() === 'all income') { | ||
if (template.previous) { | ||
let sheetName_lastmonth = monthUtils.sheetForMonth( | ||
monthUtils.addMonths(month, -1), | ||
); | ||
monthlyIncome = await getSheetValue(sheetName_lastmonth, 'total-income'); | ||
} else { | ||
monthlyIncome = await getSheetValue(sheetName, `total-income`); | ||
} | ||
} else if (template.category.toLowerCase() === 'available funds') { | ||
monthlyIncome = available_start; | ||
} else { | ||
let income_category = (await db.getCategories()).find( | ||
c => | ||
c.is_income && c.name.toLowerCase() === template.category.toLowerCase(), | ||
); | ||
if (!income_category) { | ||
errors.push(`Could not find category “${template.category}”`); | ||
return { errors }; | ||
} | ||
if (template.previous) { | ||
let sheetName_lastmonth = monthUtils.sheetForMonth( | ||
monthUtils.addMonths(month, -1), | ||
); | ||
monthlyIncome = await getSheetValue( | ||
sheetName_lastmonth, | ||
`sum-amount-${income_category.id}`, | ||
); | ||
} else { | ||
monthlyIncome = await getSheetValue( | ||
sheetName, | ||
`sum-amount-${income_category.id}`, | ||
); | ||
} | ||
} | ||
|
||
let increment = Math.max(0, Math.round(monthlyIncome * (percent / 100))); | ||
to_budget += increment; | ||
return { to_budget, errors }; | ||
} |
21 changes: 21 additions & 0 deletions
21
packages/loot-core/src/server/budget/goals/goalsRemainder.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
export async function goalsRemainder( | ||
template, | ||
budgetAvailable, | ||
remainder_scale, | ||
to_budget, | ||
) { | ||
if (remainder_scale >= 0) { | ||
to_budget += | ||
remainder_scale === 0 | ||
? Math.round(template.weight) | ||
: Math.round(remainder_scale * template.weight); | ||
// can over budget with the rounding, so checking that | ||
if (to_budget >= budgetAvailable) { | ||
to_budget = budgetAvailable; | ||
// check if there is 1 cent leftover from rounding | ||
} else if (budgetAvailable - to_budget === 1) { | ||
to_budget = to_budget + 1; | ||
} | ||
} | ||
return { to_budget }; | ||
} |
169 changes: 169 additions & 0 deletions
169
packages/loot-core/src/server/budget/goals/goalsSchedule.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
import * as monthUtils from '../../../shared/months'; | ||
import { extractScheduleConds } from '../../../shared/schedules'; | ||
import * as db from '../../db'; | ||
import { getRuleForSchedule, getNextDate } from '../../schedules/app'; | ||
import { isReflectBudget } from '../actions'; | ||
|
||
export async function goalsSchededule( | ||
scheduleFlag, | ||
template_lines, | ||
current_month, | ||
balance, | ||
remainder, | ||
last_month_balance, | ||
to_budget, | ||
errors, | ||
) { | ||
if (!scheduleFlag) { | ||
scheduleFlag = true; | ||
let template = template_lines.filter(t => t.type === 'schedule'); | ||
//in the case of multiple templates per category, schedules may have wrong priority level | ||
let t = []; | ||
let totalScheduledGoal = 0; | ||
|
||
for (let ll = 0; ll < template.length; ll++) { | ||
let { id: sid, completed: complete } = await db.first( | ||
'SELECT * FROM schedules WHERE name = ?', | ||
[template[ll].name], | ||
); | ||
console.log(complete); | ||
let rule = await getRuleForSchedule(sid); | ||
let conditions = rule.serialize().conditions; | ||
let { date: dateConditions, amount: amountCondition } = | ||
extractScheduleConds(conditions); | ||
let target = | ||
amountCondition.op === 'isbetween' | ||
? -Math.round( | ||
amountCondition.value.num1 + amountCondition.value.num2, | ||
) / 2 | ||
: -amountCondition.value; | ||
let next_date_string = getNextDate( | ||
dateConditions, | ||
monthUtils._parse(current_month), | ||
); | ||
let target_interval = dateConditions.value.interval | ||
? dateConditions.value.interval | ||
: 1; | ||
let target_frequency = dateConditions.value.frequency; | ||
let isRepeating = | ||
Object(dateConditions.value) === dateConditions.value && | ||
'frequency' in dateConditions.value; | ||
let num_months = monthUtils.differenceInCalendarMonths( | ||
next_date_string, | ||
current_month, | ||
); | ||
t.push({ | ||
template: template[ll], | ||
target: target, | ||
next_date_string: next_date_string, | ||
target_interval: target_interval, | ||
target_frequency: target_frequency, | ||
num_months: num_months, | ||
completed: complete, | ||
}); | ||
if (!complete) { | ||
if (isRepeating) { | ||
let monthlyTarget = 0; | ||
let next_month = monthUtils.addMonths( | ||
current_month, | ||
t[ll].num_months + 1, | ||
); | ||
let next_date = getNextDate( | ||
dateConditions, | ||
monthUtils._parse(current_month), | ||
); | ||
while (next_date < next_month) { | ||
monthlyTarget += -target; | ||
next_date = monthUtils.addDays(next_date, 1); | ||
next_date = getNextDate( | ||
dateConditions, | ||
monthUtils._parse(next_date), | ||
); | ||
} | ||
t[ll].target = -monthlyTarget; | ||
totalScheduledGoal += target; | ||
} | ||
} else { | ||
errors.push(`Schedule ${t[ll].template.name} is a completed schedule.`); | ||
} | ||
} | ||
|
||
t = t.filter(t => t.completed === 0); | ||
t = t.sort((a, b) => b.target - a.target); | ||
|
||
let increment = 0; | ||
if (balance >= totalScheduledGoal) { | ||
for (let ll = 0; ll < t.length; ll++) { | ||
if (t[ll].num_months < 0) { | ||
errors.push( | ||
`Non-repeating schedule ${t[ll].template.name} was due on ${t[ll].next_date_string}, which is in the past.`, | ||
); | ||
break; | ||
} | ||
if ( | ||
(t[ll].template.full && t[ll].num_months === 0) || | ||
t[ll].target_frequency === 'weekly' || | ||
t[ll].target_frequency === 'daily' | ||
) { | ||
increment += t[ll].target; | ||
} else if (t[ll].template.full && t[ll].num_months > 0) { | ||
increment += 0; | ||
} else { | ||
increment += t[ll].target / t[ll].target_interval; | ||
} | ||
} | ||
} else if (balance < totalScheduledGoal) { | ||
for (let ll = 0; ll < t.length; ll++) { | ||
if (isReflectBudget()) { | ||
if (!t[ll].template.full) { | ||
errors.push( | ||
`Report budgets require the full option for Schedules.`, | ||
); | ||
break; | ||
} | ||
if (t[ll].template.full && t[ll].num_months === 0) { | ||
to_budget += t[ll].target; | ||
} | ||
} | ||
if (!isReflectBudget()) { | ||
if (t[ll].num_months < 0) { | ||
errors.push( | ||
`Non-repeating schedule ${t[ll].template.name} was due on ${t[ll].next_date_string}, which is in the past.`, | ||
); | ||
break; | ||
} | ||
if (t[ll].template.full && t[ll].num_months > 0) { | ||
remainder = 0; | ||
} else if (ll === 0 && !t[ll].template.full) { | ||
remainder = t[ll].target - last_month_balance; | ||
} else { | ||
remainder = t[ll].target - remainder; | ||
} | ||
let tg = 0; | ||
if (remainder >= 0) { | ||
tg = remainder; | ||
remainder = 0; | ||
} else { | ||
tg = 0; | ||
remainder = Math.abs(remainder); | ||
} | ||
if ( | ||
t[ll].template.full || | ||
t[ll].num_months === 0 || | ||
t[ll].target_frequency === 'weekly' || | ||
t[ll].target_frequency === 'daily' | ||
) { | ||
increment += tg; | ||
} else if (t[ll].template.full && t[ll].num_months > 0) { | ||
increment += 0; | ||
} else { | ||
increment += tg / (t[ll].num_months + 1); | ||
} | ||
} | ||
} | ||
} | ||
increment = Math.round(increment); | ||
to_budget += increment; | ||
} | ||
return { to_budget, errors }; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import { amountToInteger } from '../../../shared/util'; | ||
|
||
export async function goalsSimple( | ||
template, | ||
limitCheck, | ||
errors, | ||
limit, | ||
hold, | ||
to_budget, | ||
) { | ||
// simple has 'monthly' and/or 'limit' params | ||
if (template.limit != null) { | ||
if (limitCheck) { | ||
errors.push(`More than one “up to” limit found.`); | ||
return { errors }; | ||
} else { | ||
limitCheck = true; | ||
limit = amountToInteger(template.limit.amount); | ||
hold = template.limit.hold; | ||
} | ||
} | ||
let increment = 0; | ||
if (template.monthly != null) { | ||
let monthly = amountToInteger(template.monthly); | ||
increment = monthly; | ||
} else { | ||
increment = limit; | ||
} | ||
to_budget += increment; | ||
return { to_budget, errors }; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import * as monthUtils from '../../../shared/months'; | ||
import { amountToInteger } from '../../../shared/util'; | ||
import { getSheetValue } from '../actions'; | ||
|
||
export async function goalsSpend( | ||
template, | ||
last_month_balance, | ||
current_month, | ||
to_budget, | ||
errors, | ||
category, | ||
) { | ||
// spend has 'amount' and 'from' and 'month' params | ||
let from_month = `${template.from}-01`; | ||
let to_month = `${template.month}-01`; | ||
let already_budgeted = last_month_balance; | ||
let first_month = true; | ||
for ( | ||
let m = from_month; | ||
monthUtils.differenceInCalendarMonths(current_month, m) > 0; | ||
m = monthUtils.addMonths(m, 1) | ||
) { | ||
let sheetName = monthUtils.sheetForMonth(monthUtils.format(m, 'yyyy-MM')); | ||
|
||
if (first_month) { | ||
let spent = await getSheetValue(sheetName, `sum-amount-${category.id}`); | ||
let balance = await getSheetValue(sheetName, `leftover-${category.id}`); | ||
already_budgeted = balance - spent; | ||
first_month = false; | ||
} else { | ||
let budgeted = await getSheetValue(sheetName, `budget-${category.id}`); | ||
already_budgeted += budgeted; | ||
} | ||
} | ||
let num_months = monthUtils.differenceInCalendarMonths( | ||
to_month, | ||
monthUtils._parse(current_month), | ||
); | ||
let target = amountToInteger(template.amount); | ||
|
||
let increment = 0; | ||
if (num_months < 0) { | ||
errors.push(`${template.month} is in the past.`); | ||
return { errors }; | ||
} else if (num_months === 0) { | ||
increment = target - already_budgeted; | ||
} else { | ||
increment = Math.round((target - already_budgeted) / (num_months + 1)); | ||
} | ||
to_budget = increment; | ||
return { to_budget, errors }; | ||
} |
Oops, something went wrong.