diff --git a/packages/loot-core/src/server/budget/goal-template.pegjs b/packages/loot-core/src/server/budget/goal-template.pegjs index 9c3fd1b8059ea94d5d0b1b2cd725c11adac2483f..e0b6d97c83bd99dbfa697d1d5c224fc58f22be10 100644 --- a/packages/loot-core/src/server/budget/goal-template.pegjs +++ b/packages/loot-core/src/server/budget/goal-template.pegjs @@ -15,12 +15,14 @@ expr priority: +priority } } / priority: priority? _? monthly: amount limit: limit? - { return { type: 'simple', monthly, limit, priority: +priority } } + { return { type: 'simple', monthly, limit, priority: +priority } } / priority: priority? _? limit: limit { return { type: 'simple', limit , priority: +priority } } / priority: priority? _? schedule _ full:full? name: name - { return { type: 'schedule', name, priority: +priority, full } } - + { return { type: 'schedule', name, priority: +priority, full } } + / priority: priority? _? remainder: remainder + { return { type: 'remainder', priority: null, weight: remainder } } + repeat 'repeat interval' = 'month'i { return { annual: false } } @@ -48,6 +50,7 @@ upTo = 'up'i _ 'to'i schedule = 'schedule'i full = 'full'i _ {return true} priority = '-'i number: number _ {return number} +remainder = 'remainder'i _? weight: positive? { return +weight || 1 } _ 'space' = ' '+ d 'digit' = [0-9] diff --git a/packages/loot-core/src/server/budget/goaltemplates.ts b/packages/loot-core/src/server/budget/goaltemplates.ts index 66644df6e5b7fe161455f5fc55a8a84cec42963a..94a1e4b1a4739eec5bc6dc04f25ab47bdcdc4aaa 100644 --- a/packages/loot-core/src/server/budget/goaltemplates.ts +++ b/packages/loot-core/src/server/budget/goaltemplates.ts @@ -80,8 +80,35 @@ async function processTemplate(month, force) { }); } } + // find all remainder templates, place them after all other templates + let remainder_found; + let remainder_priority = lowestPriority + 1; + let remainder_weight_total = 0; + for (let c = 0; c < categories.length; c++) { + let category = categories[c]; + let templates = category_templates[category.id]; + if (templates) { + for (let i = 0; i < templates.length; i++) { + if (templates[i].type === 'remainder') { + templates[i].priority = remainder_priority; + remainder_weight_total += templates[i].weight; + remainder_found = true; + } + } + } + } + // so the remainders don't get skiped + if (remainder_found) lowestPriority = remainder_priority; for (let priority = 0; priority <= lowestPriority; priority++) { + // setup scaling for remainder + let remainder_scale = 1; + if (priority === lowestPriority) { + let sheetName = monthUtils.sheetForMonth(month); + let budgetAvailable = await getSheetValue(sheetName, `to-budget`); + remainder_scale = Math.round(budgetAvailable / remainder_weight_total); + } + for (let c = 0; c < categories.length; c++) { let category = categories[c]; let template = category_templates[category.id]; @@ -132,6 +159,7 @@ async function processTemplate(month, force) { template, month, priority, + remainder_scale, force, ); if (to_budget != null) { @@ -235,6 +263,7 @@ async function applyCategoryTemplate( template_lines, month, priority, + remainder_scale, force, ) { let current_month = getCorrectedDate(`${month}-01`); @@ -549,6 +578,12 @@ async function applyCategoryTemplate( } break; } + case 'remainder': { + to_budget = Math.round(remainder_scale * template.weight); + // can over budget with the rounding, so checking that + if (to_budget > budgetAvailable) to_budget = budgetAvailable; + break; + } case 'error': return { errors }; default: diff --git a/upcoming-release-notes/1101.md b/upcoming-release-notes/1101.md new file mode 100644 index 0000000000000000000000000000000000000000..70ad7e55d2c1a2d46859464c47b8c500bdfbccb8 --- /dev/null +++ b/upcoming-release-notes/1101.md @@ -0,0 +1,6 @@ +--- +category: Enhancements +authors: [youngcw] +--- + +Goals: Add remainder option to budget all extra funds automatically.