Skip to content

Commit

Permalink
Fix schedule split template amounts (#4077)
Browse files Browse the repository at this point in the history
* Fix incorrect argument to goals schedule function

* Add argument types to prevent similar issues

* Add release notes

* Fix test types
  • Loading branch information
jfdoming authored Jan 2, 2025
1 parent 928260c commit 832fd1e
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 35 deletions.
29 changes: 15 additions & 14 deletions packages/loot-core/src/server/budget/categoryTemplate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import * as monthUtils from '../../shared/months';
import { amountToInteger } from '../../shared/util';
import { CategoryEntity } from '../../types/models';
import * as db from '../db';

import { getSheetValue, getSheetBoolean } from './actions';
Expand Down Expand Up @@ -29,18 +30,18 @@ export class CategoryTemplate {
// Class interface

// set up the class and check all templates
static async init(templates: Template[], categoryID: string, month) {
static async init(templates: Template[], category: CategoryEntity, month) {
// get all the needed setup values
const lastMonthSheet = monthUtils.sheetForMonth(
monthUtils.subMonths(month, 1),
);
const lastMonthBalance = await getSheetValue(
lastMonthSheet,
`leftover-${categoryID}`,
`leftover-${category.id}`,
);
const carryover = await getSheetBoolean(
lastMonthSheet,
`carryover-${categoryID}`,
`carryover-${category.id}`,
);
let fromLastMonth;
if (lastMonthBalance < 0 && !carryover) {
Expand All @@ -52,7 +53,7 @@ export class CategoryTemplate {
await CategoryTemplate.checkByAndScheduleAndSpend(templates, month);
await CategoryTemplate.checkPercentage(templates);
// call the private constructor
return new CategoryTemplate(templates, categoryID, month, fromLastMonth);
return new CategoryTemplate(templates, category, month, fromLastMonth);
}

getPriorities(): number[] {
Expand Down Expand Up @@ -126,7 +127,7 @@ export class CategoryTemplate {
case 'schedule': {
const budgeted = await getSheetValue(
monthUtils.sheetForMonth(this.month),
`leftover-${this.categoryID}`,
`leftover-${this.category.id}`,
);
const ret = await goalsSchedule(
scheduleFlag,
Expand All @@ -137,7 +138,7 @@ export class CategoryTemplate {
this.fromLastMonth,
toBudget,
[],
this.categoryID,
this.category,
);
toBudget = ret.to_budget;
remainder = ret.remainder;
Expand Down Expand Up @@ -203,7 +204,7 @@ export class CategoryTemplate {

//-----------------------------------------------------------------------------
// Implementation
readonly categoryID: string; //readonly so we can double check the category this is using
readonly category: CategoryEntity; //readonly so we can double check the category this is using
private month: string;
private templates = [];
private remainder = [];
Expand All @@ -223,11 +224,11 @@ export class CategoryTemplate {

private constructor(
templates: Template[],
categoryID: string,
category: CategoryEntity,
month: string,
fromLastMonth: number,
) {
this.categoryID = categoryID;
this.category = category;
this.month = month;
this.fromLastMonth = fromLastMonth;
// sort the template lines into regular template, goals, and remainder templates
Expand Down Expand Up @@ -424,7 +425,7 @@ export class CategoryTemplate {
const sheetName = monthUtils.sheetForMonth(
monthUtils.subMonths(this.month, template.lookBack),
);
return await getSheetValue(sheetName, `budget-${this.categoryID}`);
return await getSheetValue(sheetName, `budget-${this.category.id}`);
}

private runWeek(template): number {
Expand Down Expand Up @@ -472,18 +473,18 @@ export class CategoryTemplate {
//TODO figure out if I already found these values and can pass them in
const spent = await getSheetValue(
sheetName,
`sum-amount-${this.categoryID}`,
`sum-amount-${this.category.id}`,
);
const balance = await getSheetValue(
sheetName,
`leftover-${this.categoryID}`,
`leftover-${this.category.id}`,
);
alreadyBudgeted = balance - spent;
firstMonth = false;
} else {
alreadyBudgeted += await getSheetValue(
sheetName,
`budget-${this.categoryID}`,
`budget-${this.category.id}`,
);
}
}
Expand Down Expand Up @@ -536,7 +537,7 @@ export class CategoryTemplate {
const sheetName = monthUtils.sheetForMonth(
monthUtils.subMonths(this.month, i),
);
sum += await getSheetValue(sheetName, `sum-amount-${this.categoryID}`);
sum += await getSheetValue(sheetName, `sum-amount-${this.category.id}`);
}
return -Math.round(sum / template.numMonths);
}
Expand Down
20 changes: 16 additions & 4 deletions packages/loot-core/src/server/budget/goalsSchedule.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,20 @@ describe('goalsSchedule', () => {
it('should return correct budget when recurring schedule set', async () => {
// Given
const scheduleFlag = false;
const template_lines = [{ type: 'schedule', name: 'Test Schedule' }];
const template_lines = [
{
type: 'schedule',
name: 'Test Schedule',
directive: '#template schedule Test Schedule',
} as const,
];
const current_month = '2024-08-01';
const balance = 0;
const remainder = 0;
const last_month_balance = 0;
const to_budget = 0;
const errors: string[] = [];
const category = { id: 1, name: 'Test Category' };
const category = { id: '1', name: 'Test Category' };

(db.first as jest.Mock).mockResolvedValue({ id: 1, completed: 0 });
(getRuleForSchedule as jest.Mock).mockResolvedValue({
Expand Down Expand Up @@ -86,14 +92,20 @@ describe('goalsSchedule', () => {
it('should return correct budget when yearly recurring schedule set and balance is greater than target', async () => {
// Given
const scheduleFlag = false;
const template_lines = [{ type: 'schedule', name: 'Test Schedule' }];
const template_lines = [
{
type: 'schedule',
name: 'Test Schedule',
directive: '#template schedule Test Schedule',
} as const,
];
const current_month = '2024-09-01';
const balance = 12000;
const remainder = 0;
const last_month_balance = 12000;
const to_budget = 0;
const errors: string[] = [];
const category = { id: 1, name: 'Test Category' };
const category = { id: '1', name: 'Test Category' };

(db.first as jest.Mock).mockResolvedValue({ id: 1, completed: 0 });
(getRuleForSchedule as jest.Mock).mockResolvedValue({
Expand Down
34 changes: 22 additions & 12 deletions packages/loot-core/src/server/budget/goalsSchedule.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// @ts-strict-ignore
import * as monthUtils from '../../shared/months';
import { extractScheduleConds } from '../../shared/schedules';
import { CategoryEntity } from '../../types/models';
import * as db from '../db';
import {
getRuleForSchedule,
Expand All @@ -9,8 +10,13 @@ import {
} from '../schedules/app';

import { isReflectBudget } from './actions';
import { ScheduleTemplate, Template } from './types/templates';

async function createScheduleList(template, current_month, category) {
async function createScheduleList(
template: ScheduleTemplate[],
current_month: string,
category: CategoryEntity,
) {
const t = [];
const errors = [];

Expand Down Expand Up @@ -186,22 +192,26 @@ async function getSinkingTotal(t) {
}

export async function goalsSchedule(
scheduleFlag,
template_lines,
current_month,
balance,
remainder,
last_month_balance,
to_budget,
errors,
category,
scheduleFlag: boolean,
template_lines: Template[],
current_month: string,
balance: number,
remainder: number,
last_month_balance: number,
to_budget: number,
errors: string[],
category: CategoryEntity,
) {
if (!scheduleFlag) {
scheduleFlag = true;
const template = template_lines.filter(t => t.type === 'schedule');
const scheduleTemplates = template_lines.filter(t => t.type === 'schedule');
//in the case of multiple templates per category, schedules may have wrong priority level

const t = await createScheduleList(template, current_month, category);
const t = await createScheduleList(
scheduleTemplates,
current_month,
category,
);
errors = errors.concat(t.errors);

const isPayMonthOf = c =>
Expand Down
12 changes: 7 additions & 5 deletions packages/loot-core/src/server/budget/goaltemplates.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// @ts-strict-ignore
import { Notification } from '../../client/state-types/notifications';
import * as monthUtils from '../../shared/months';
import { CategoryEntity } from '../../types/models';
import * as db from '../db';
import { batchMessages } from '../sync';

Expand Down Expand Up @@ -46,7 +47,7 @@ export function runCheckTemplates() {
return checkTemplates();
}

async function getCategories() {
async function getCategories(): Promise<CategoryEntity[]> {
return await db.all(
`
SELECT categories.* FROM categories
Expand Down Expand Up @@ -150,7 +151,8 @@ async function processTemplate(
const budgetList = [];
const goalList = [];
for (let i = 0; i < categories.length; i++) {
const id = categories[i].id;
const category = categories[i];
const { id } = category;
const sheetName = monthUtils.sheetForMonth(month);
const templates = categoryTemplates[id];
const budgeted = await getSheetValue(sheetName, `budget-${id}`);
Expand All @@ -162,7 +164,7 @@ async function processTemplate(
// gather needed priorities
// gather remainder weights
try {
const obj = await CategoryTemplate.init(templates, id, month);
const obj = await CategoryTemplate.init(templates, category, month);
availBudget += budgeted;
availBudget += obj.getLimitExcess();
const p = obj.getPriorities();
Expand Down Expand Up @@ -230,9 +232,9 @@ async function processTemplate(
// finish
catObjects.forEach(o => {
const ret = o.getValues();
budgetList.push({ category: o.categoryID, budgeted: ret.budgeted });
budgetList.push({ category: o.category.id, budgeted: ret.budgeted });
goalList.push({
category: o.categoryID,
category: o.category.id,
goal: ret.goal,
longGoal: ret.longGoal ? 1 : null,
});
Expand Down
6 changes: 6 additions & 0 deletions upcoming-release-notes/4077.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
category: Bugfix
authors: [jfdoming]
---

Fix schedule split template amounts

0 comments on commit 832fd1e

Please sign in to comment.