Skip to content

Commit

Permalink
move goal calculations to separate files by type
Browse files Browse the repository at this point in the history
  • Loading branch information
shall0pass committed Nov 8, 2023
1 parent df5aa31 commit 4d38637
Show file tree
Hide file tree
Showing 8 changed files with 489 additions and 341 deletions.
47 changes: 47 additions & 0 deletions packages/loot-core/src/server/budget/goals/goalsBy.ts
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 packages/loot-core/src/server/budget/goals/goalsPercentage.ts
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 packages/loot-core/src/server/budget/goals/goalsRemainder.ts
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 packages/loot-core/src/server/budget/goals/goalsSchedule.ts
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 };
}
31 changes: 31 additions & 0 deletions packages/loot-core/src/server/budget/goals/goalsSimple.ts
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 };
}
52 changes: 52 additions & 0 deletions packages/loot-core/src/server/budget/goals/goalsSpend.ts
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 };
}
Loading

0 comments on commit 4d38637

Please sign in to comment.