From 2e600e52c7dc7bafbfe358dcbf2aa8bd9f41aaaa Mon Sep 17 00:00:00 2001 From: Joel Jeremy Marquez Date: Sat, 6 Apr 2024 15:19:12 -0600 Subject: [PATCH 1/3] [Maintenance] Update TransactionEdit conponent onEdit function to use serialized transactions (#2555) * [Maintenance] Update onEdit to use serialized transactions * Release notes --- .../mobile/transactions/TransactionEdit.jsx | 66 ++++++------------- upcoming-release-notes/2555.md | 6 ++ 2 files changed, 25 insertions(+), 47 deletions(-) create mode 100644 upcoming-release-notes/2555.md diff --git a/packages/desktop-client/src/components/mobile/transactions/TransactionEdit.jsx b/packages/desktop-client/src/components/mobile/transactions/TransactionEdit.jsx index 98b02cd0e9e..42be12c67ea 100644 --- a/packages/desktop-client/src/components/mobile/transactions/TransactionEdit.jsx +++ b/packages/desktop-client/src/components/mobile/transactions/TransactionEdit.jsx @@ -495,10 +495,10 @@ const TransactionEditInner = memo(function TransactionEditInner({ }; const onSave = async () => { - const [transaction] = unserializedTransactions; + const [unserializedTransaction] = unserializedTransactions; const onConfirmSave = async () => { - const { account: accountId } = transaction; + const { account: accountId } = unserializedTransaction; const account = accountsById[accountId]; if (unserializedTransactions.find(t => t.account == null)) { @@ -516,7 +516,7 @@ const TransactionEditInner = memo(function TransactionEditInner({ navigate(`/accounts/${account.id}`, { replace: true }); }; - if (transaction.reconciled) { + if (unserializedTransaction.reconciled) { // On mobile any save gives the warning. // On the web only certain changes trigger a warning. // Should we bring that here as well? Or does the nature of the editing form @@ -536,31 +536,22 @@ const TransactionEditInner = memo(function TransactionEditInner({ onSave(); }; - const onEdit = async (transaction, name, value) => { - const newTransaction = { ...transaction, [name]: value }; + const onEdit = async (serializedTransaction, name, value) => { + const newTransaction = { ...serializedTransaction, [name]: value }; await props.onEdit(newTransaction); onClearActiveEdit(); }; const onClick = (transactionId, name) => { onRequestActiveEdit?.(getFieldName(transaction.id, name), () => { - const transaction = unserializedTransactions.find( - t => t.id === transactionId, - ); + const transactionToEdit = transactions.find(t => t.id === transactionId); switch (name) { case 'category': dispatch( pushModal('category-autocomplete', { categoryGroups, onSelect: categoryId => { - // This is a deficiency of this API, need to fix. It - // assumes that it receives a serialized transaction, - // but we only have access to the raw transaction - onEdit( - serializeTransaction(transaction, dateFormat), - name, - categoryId, - ); + onEdit(transactionToEdit, name, categoryId); }, onClose: () => { onClearActiveEdit(); @@ -572,14 +563,7 @@ const TransactionEditInner = memo(function TransactionEditInner({ dispatch( pushModal('account-autocomplete', { onSelect: accountId => { - // This is a deficiency of this API, need to fix. It - // assumes that it receives a serialized transaction, - // but we only have access to the raw transaction - onEdit( - serializeTransaction(transaction, dateFormat), - name, - accountId, - ); + onEdit(transactionToEdit, name, accountId); }, onClose: () => { onClearActiveEdit(); @@ -591,14 +575,7 @@ const TransactionEditInner = memo(function TransactionEditInner({ dispatch( pushModal('payee-autocomplete', { onSelect: payeeId => { - // This is a deficiency of this API, need to fix. It - // assumes that it receives a serialized transaction, - // but we only have access to the raw transaction - onEdit( - serializeTransaction(transaction, dateFormat), - name, - payeeId, - ); + onEdit(transactionToEdit, name, payeeId); }, onClose: () => { onClearActiveEdit(); @@ -611,14 +588,7 @@ const TransactionEditInner = memo(function TransactionEditInner({ pushModal('edit-field', { name, onSubmit: (name, value) => { - // This is a deficiency of this API, need to fix. It - // assumes that it receives a serialized transaction, - // but we only have access to the raw transaction - onEdit( - serializeTransaction(transaction, dateFormat), - name, - value, - ); + onEdit(transactionToEdit, name, value); }, onClose: () => { onClearActiveEdit(); @@ -631,18 +601,18 @@ const TransactionEditInner = memo(function TransactionEditInner({ }; const onDelete = id => { - const [transaction, ..._childTransactions] = unserializedTransactions; + const [unserializedTransaction] = unserializedTransactions; const onConfirmDelete = () => { props.onDelete(id); - if (transaction.id !== id) { + if (unserializedTransaction.id !== id) { // Only a child transaction was deleted. onClearActiveEdit(); return; } - const { account: accountId } = transaction; + const { account: accountId } = unserializedTransaction; if (accountId) { navigate(`/accounts/${accountId}`, { replace: true }); } else { @@ -650,7 +620,7 @@ const TransactionEditInner = memo(function TransactionEditInner({ } }; - if (transaction.reconciled) { + if (unserializedTransaction.reconciled) { dispatch( pushModal('confirm-transaction-edit', { onConfirm: onConfirmDelete, @@ -683,9 +653,11 @@ const TransactionEditInner = memo(function TransactionEditInner({ }; useEffect(() => { - const noAmountTransaction = childTransactions.find(t => t.amount === 0); - if (noAmountTransaction) { - scrollChildTransactionIntoView(noAmountTransaction.id); + const noAmountChildTransaction = childTransactions.find( + t => t.amount === 0, + ); + if (noAmountChildTransaction) { + scrollChildTransactionIntoView(noAmountChildTransaction.id); } }, [childTransactions]); diff --git a/upcoming-release-notes/2555.md b/upcoming-release-notes/2555.md new file mode 100644 index 00000000000..49af4a717b6 --- /dev/null +++ b/upcoming-release-notes/2555.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [joel-jeremy] +--- + +Update TransactionEdit component onEdit function to use serialized transactions. From b6550094771d1b35e9c88a3d544a311109d51416 Mon Sep 17 00:00:00 2001 From: Neil <55785687+carkom@users.noreply.github.com> Date: Sat, 6 Apr 2024 22:46:58 +0100 Subject: [PATCH 2/3] Custom Reports: Fix live range (#2557) * fix live range * card updates * type fix * notes --- .../src/components/reports/ReportSidebar.jsx | 41 ++--------------- .../src/components/reports/getLiveRange.ts | 44 +++++++++++++++++++ .../src/components/reports/reportRanges.ts | 2 +- .../reports/reports/CustomReport.jsx | 34 +++++++++----- .../reports/reports/CustomReportListCards.tsx | 13 +++++- .../reports/reports/GetCardData.tsx | 31 +++++++++---- upcoming-release-notes/2557.md | 6 +++ 7 files changed, 111 insertions(+), 60 deletions(-) create mode 100644 packages/desktop-client/src/components/reports/getLiveRange.ts create mode 100644 upcoming-release-notes/2557.md diff --git a/packages/desktop-client/src/components/reports/ReportSidebar.jsx b/packages/desktop-client/src/components/reports/ReportSidebar.jsx index e916183c79e..3b2585eba05 100644 --- a/packages/desktop-client/src/components/reports/ReportSidebar.jsx +++ b/packages/desktop-client/src/components/reports/ReportSidebar.jsx @@ -11,14 +11,10 @@ import { View } from '../common/View'; import { Tooltip } from '../tooltips'; import { CategorySelector } from './CategorySelector'; +import { getLiveRange } from './getLiveRange'; import { ModeButton } from './ModeButton'; import { ReportOptions } from './ReportOptions'; -import { - getSpecificRange, - validateEnd, - validateRange, - validateStart, -} from './reportRanges'; +import { validateEnd, validateStart } from './reportRanges'; export function ReportSidebar({ customReportItems, @@ -48,38 +44,7 @@ export function ReportSidebar({ const onSelectRange = cond => { onReportChange({ type: 'modify' }); setDateRange(cond); - let dateStart; - let dateEnd; - switch (cond) { - case 'All time': - onChangeDates(earliestTransaction, monthUtils.currentDay()); - break; - case 'Year to date': - [dateStart, dateEnd] = validateRange( - earliestTransaction, - monthUtils.getYearStart(monthUtils.currentMonth()) + '-01', - monthUtils.currentDay(), - ); - onChangeDates(dateStart, dateEnd); - break; - case 'Last year': - [dateStart, dateEnd] = validateRange( - earliestTransaction, - monthUtils.getYearStart( - monthUtils.prevYear(monthUtils.currentMonth()), - ) + '-01', - monthUtils.getYearEnd(monthUtils.prevYear(monthUtils.currentDate())) + - '-31', - ); - onChangeDates(dateStart, dateEnd); - break; - default: - [dateStart, dateEnd] = getSpecificRange( - ReportOptions.dateRangeMap.get(cond), - cond === 'Last month' ? 0 : null, - ); - onChangeDates(dateStart, dateEnd); - } + onChangeDates(...getLiveRange(cond, earliestTransaction)); }; const onChangeMode = cond => { diff --git a/packages/desktop-client/src/components/reports/getLiveRange.ts b/packages/desktop-client/src/components/reports/getLiveRange.ts new file mode 100644 index 00000000000..a1a44274b1b --- /dev/null +++ b/packages/desktop-client/src/components/reports/getLiveRange.ts @@ -0,0 +1,44 @@ +import * as monthUtils from 'loot-core/src/shared/months'; + +import { ReportOptions } from './ReportOptions'; +import { getSpecificRange, validateRange } from './reportRanges'; + +export function getLiveRange(cond: string, earliestTransaction: string) { + let dateStart; + let dateEnd; + const rangeName = ReportOptions.dateRangeMap.get(cond); + switch (rangeName) { + case 'yearToDate': + [dateStart, dateEnd] = validateRange( + earliestTransaction, + monthUtils.getYearStart(monthUtils.currentMonth()) + '-01', + monthUtils.currentDay(), + ); + break; + case 'lastYear': + [dateStart, dateEnd] = validateRange( + earliestTransaction, + monthUtils.getYearStart( + monthUtils.prevYear(monthUtils.currentMonth()), + ) + '-01', + monthUtils.getYearEnd(monthUtils.prevYear(monthUtils.currentDate())) + + '-31', + ); + break; + case 'allMonths': + dateStart = earliestTransaction; + dateEnd = monthUtils.currentDay(); + break; + default: + if (typeof rangeName === 'number') { + [dateStart, dateEnd] = getSpecificRange( + rangeName, + cond === 'Last month' ? 0 : null, + ); + } else { + break; + } + } + + return [dateStart, dateEnd]; +} diff --git a/packages/desktop-client/src/components/reports/reportRanges.ts b/packages/desktop-client/src/components/reports/reportRanges.ts index 588c56291e1..48892d63fd1 100644 --- a/packages/desktop-client/src/components/reports/reportRanges.ts +++ b/packages/desktop-client/src/components/reports/reportRanges.ts @@ -115,7 +115,7 @@ function boundedRange( return [start, end]; } -export function getSpecificRange(offset: number, addNumber: number) { +export function getSpecificRange(offset: number, addNumber: number | null) { const currentDay = monthUtils.currentDay(); const dateStart = monthUtils.subMonths(currentDay, offset) + '-01'; const dateEnd = monthUtils.getMonthEnd( diff --git a/packages/desktop-client/src/components/reports/reports/CustomReport.jsx b/packages/desktop-client/src/components/reports/reports/CustomReport.jsx index c7e89460ba4..c6da3eb0a0d 100644 --- a/packages/desktop-client/src/components/reports/reports/CustomReport.jsx +++ b/packages/desktop-client/src/components/reports/reports/CustomReport.jsx @@ -21,6 +21,7 @@ import { AppliedFilters } from '../../filters/AppliedFilters'; import { PrivacyFilter } from '../../PrivacyFilter'; import { ChooseGraph } from '../ChooseGraph'; import { defaultsList, disabledList } from '../disabledList'; +import { getLiveRange } from '../getLiveRange'; import { Header } from '../Header'; import { LoadingIndicator } from '../LoadingIndicator'; import { ReportLegend } from '../ReportLegend'; @@ -93,18 +94,6 @@ export function CustomReport() { location.state ? (location.state.report ? 'saved' : 'new') : 'new', ); - useEffect(() => { - const format = - ReportOptions.intervalMap.get(interval).toLowerCase() + 'FromDate'; - - const dateStart = monthUtils[format](startDate); - const dateEnd = monthUtils[format](endDate); - - setIntervals( - monthUtils[ReportOptions.intervalRange.get(interval)](dateStart, dateEnd), - ); - }, [interval, startDate, endDate]); - useEffect(() => { if (selectedCategories === undefined && categories.list.length !== 0) { setSelectedCategories(categories.list); @@ -138,10 +127,31 @@ export function CustomReport() { .reverse(); setAllIntervals(allInter); + + if (!isDateStatic) { + const [dateStart, dateEnd] = getLiveRange( + dateRange, + trans ? trans.date : monthUtils.currentDay(), + ); + setStartDate(dateStart); + setEndDate(dateEnd); + } } run(); }, [interval]); + useEffect(() => { + const format = + ReportOptions.intervalMap.get(interval).toLowerCase() + 'FromDate'; + + const dateStart = monthUtils[format](startDate); + const dateEnd = monthUtils[format](endDate); + + setIntervals( + monthUtils[ReportOptions.intervalRange.get(interval)](dateStart, dateEnd), + ); + }, [interval, startDate, endDate]); + const balanceTypeOp = ReportOptions.balanceTypeMap.get(balanceType); const payees = usePayees(); const accounts = useAccounts(); diff --git a/packages/desktop-client/src/components/reports/reports/CustomReportListCards.tsx b/packages/desktop-client/src/components/reports/reports/CustomReportListCards.tsx index 292352d183a..4ef5c08932b 100644 --- a/packages/desktop-client/src/components/reports/reports/CustomReportListCards.tsx +++ b/packages/desktop-client/src/components/reports/reports/CustomReportListCards.tsx @@ -1,6 +1,7 @@ -import React, { createRef, useMemo, useState } from 'react'; +import React, { createRef, useEffect, useMemo, useState } from 'react'; import { send, sendCatch } from 'loot-core/platform/client/fetch/index'; +import * as monthUtils from 'loot-core/src/shared/months'; import { type CustomReportEntity } from 'loot-core/types/models/reports'; import { useAccounts } from '../../../hooks/useAccounts'; @@ -72,6 +73,7 @@ export function CustomReportListCards({ const [err, setErr] = useState(''); const [name, setName] = useState(''); const inputRef = createRef(); + const [earliestTransaction, setEarliestTransaction] = useState(''); const payees = usePayees(); const accounts = useAccounts(); @@ -85,6 +87,14 @@ export function CustomReportListCards({ onDeleteMenuOpen(reportData === undefined ? '' : reportData, false); }; + useEffect(() => { + async function run() { + const trans = await send('get-earliest-transaction'); + setEarliestTransaction(trans ? trans.date : monthUtils.currentDay()); + } + run(); + }, []); + const onAddUpdate = async ({ reportData, }: { @@ -216,6 +226,7 @@ export function CustomReportListCards({ payees={payees} accounts={accounts} categories={categories} + earliestTransaction={earliestTransaction} /> diff --git a/packages/desktop-client/src/components/reports/reports/GetCardData.tsx b/packages/desktop-client/src/components/reports/reports/GetCardData.tsx index ade04b9cc4f..44f58d36806 100644 --- a/packages/desktop-client/src/components/reports/reports/GetCardData.tsx +++ b/packages/desktop-client/src/components/reports/reports/GetCardData.tsx @@ -11,6 +11,7 @@ import { styles } from '../../../style/styles'; import { theme } from '../../../style/theme'; import { Text } from '../../common/Text'; import { ChooseGraph } from '../ChooseGraph'; +import { getLiveRange } from '../getLiveRange'; import { LoadingIndicator } from '../LoadingIndicator'; import { ReportOptions } from '../ReportOptions'; import { createCustomSpreadsheet } from '../spreadsheets/custom-spreadsheet'; @@ -35,16 +36,30 @@ export function GetCardData({ payees, accounts, categories, + earliestTransaction, }: { report: CustomReportEntity; payees: PayeeEntity[]; accounts: AccountEntity[]; categories: { list: CategoryEntity[]; grouped: CategoryGroupEntity[] }; + earliestTransaction: string; }) { + let startDate = report.startDate; + let endDate = report.endDate; + + if (!report.isDateStatic) { + const [dateStart, dateEnd] = getLiveRange( + report.dateRange, + earliestTransaction, + ); + startDate = dateStart || report.startDate; + endDate = dateEnd || report.startDate; + } + const getGroupData = useMemo(() => { return createGroupedSpreadsheet({ - startDate: report.startDate, - endDate: report.endDate, + startDate, + endDate, interval: report.interval, categories, selectedCategories: report.selectedCategories ?? categories.list, @@ -56,11 +71,11 @@ export function GetCardData({ showUncategorized: report.showUncategorized, balanceTypeOp: ReportOptions.balanceTypeMap.get(report.balanceType), }); - }, [report, categories]); + }, [report, categories, startDate, endDate]); const getGraphData = useMemo(() => { return createCustomSpreadsheet({ - startDate: report.startDate, - endDate: report.endDate, + startDate, + endDate, interval: report.interval, categories, selectedCategories: report.selectedCategories ?? categories.list, @@ -76,7 +91,7 @@ export function GetCardData({ accounts, graphType: report.graphType, }); - }, [report, categories, payees, accounts]); + }, [report, categories, payees, accounts, startDate, endDate]); const graphData = useReport('default' + report.name, getGraphData); const groupedData = useReport('grouped' + report.name, getGroupData); @@ -86,8 +101,8 @@ export function GetCardData({ return data?.data ? ( Date: Sat, 6 Apr 2024 19:12:02 -0500 Subject: [PATCH 3/3] Cleanup tool group enhancement (#2480) * add group enhancement * warnings * note * add more group functions * add Global: to differentiate warning from group warnings * weights not properly recorded for sinking groups, safeNumber error --- .../src/server/budget/cleanup-template.pegjs | 20 +- .../src/server/budget/cleanup-template.ts | 189 +++++++++++++++++- upcoming-release-notes/2480.md | 6 + 3 files changed, 202 insertions(+), 13 deletions(-) create mode 100644 upcoming-release-notes/2480.md diff --git a/packages/loot-core/src/server/budget/cleanup-template.pegjs b/packages/loot-core/src/server/budget/cleanup-template.pegjs index ba958eaab2c..b01e097c713 100644 --- a/packages/loot-core/src/server/budget/cleanup-template.pegjs +++ b/packages/loot-core/src/server/budget/cleanup-template.pegjs @@ -2,9 +2,19 @@ expr = source - { return { type: 'source' } } - / sink _? weight: weight? - { return { type: 'sink', weight: +weight || 1 } } + { return { group: null, type: 'source' }} + / + sink _? weight: weight? + { return { type: 'sink', weight: +weight || 1, group: null } } + / + group: sourcegroup _? source + {return {group: group || null, type: 'source'}} + / + group: sinkgroup? _? sink _? weight: weight? + { return { type: 'sink', weight: +weight || 1, group: group || null } } + / + group: sourcegroup + {return {group: group, type: null}} source = 'source' sink = 'sink' @@ -12,4 +22,6 @@ sink = 'sink' _ 'space' = ' '+ d 'digit' = [0-9] -weight 'weight' = weight: $(d+) { return +weight } \ No newline at end of file +weight 'weight' = weight: $(d+) { return +weight } +sourcegroup 'Name'= $(string:(!" source" .)*) +sinkgroup 'Name' = $(string:(!" sink" .)*) diff --git a/packages/loot-core/src/server/budget/cleanup-template.ts b/packages/loot-core/src/server/budget/cleanup-template.ts index e9b431dc7a2..a9f7ca96a4b 100644 --- a/packages/loot-core/src/server/budget/cleanup-template.ts +++ b/packages/loot-core/src/server/budget/cleanup-template.ts @@ -10,6 +10,117 @@ export function cleanupTemplate({ month }: { month: string }) { return processCleanup(month); } +async function applyGroupCleanups( + month: string, + sourceGroups, + sinkGroups, + generalGroups, +) { + const sheetName = monthUtils.sheetForMonth(month); + const warnings = []; + const db_month = parseInt(month.replace('-', '')); + let groupLength = sourceGroups.length; + while (groupLength > 0) { + //function for each unique group + const groupName = sourceGroups[0].group; + const tempSourceGroups = sourceGroups.filter(c => c.group === groupName); + const sinkGroup = sinkGroups.filter(c => c.group === groupName); + const generalGroup = generalGroups.filter(c => c.group === groupName); + let total_weight = 0; + + if (sinkGroup.length > 0 || generalGroup.length > 0) { + //only return group source funds to To Budget if there are corresponding sinking groups or underfunded included groups + for (let ii = 0; ii < tempSourceGroups.length; ii++) { + const balance = await getSheetValue( + sheetName, + `leftover-${tempSourceGroups[ii].category}`, + ); + const budgeted = await getSheetValue( + sheetName, + `budget-${tempSourceGroups[ii].category}`, + ); + await setBudget({ + category: tempSourceGroups[ii].category, + month, + amount: budgeted - balance, + }); + } + + //calculate total weight for sinking funds + for (let ii = 0; ii < sinkGroup.length; ii++) { + total_weight += sinkGroup[ii].weight; + } + + //fill underfunded categories within the group first + for (let ii = 0; ii < generalGroup.length; ii++) { + const budgetAvailable = await getSheetValue(sheetName, `to-budget`); + const balance = await getSheetValue( + sheetName, + `leftover-${generalGroup[ii].category}`, + ); + const budgeted = await getSheetValue( + sheetName, + `budget-${generalGroup[ii].category}`, + ); + const to_budget = budgeted + Math.abs(balance); + const categoryId = generalGroup[ii].category; + let carryover = await db.first( + `SELECT carryover FROM zero_budgets WHERE month = ? and category = ?`, + [db_month, categoryId], + ); + + if (carryover === null) { + carryover = { carryover: 0 }; + } + + if ( + balance < 0 && + Math.abs(balance) <= budgetAvailable && + !generalGroup[ii].category.is_income && + carryover.carryover === 0 + ) { + await setBudget({ + category: generalGroup[ii].category, + month, + amount: to_budget, + }); + } else if ( + balance < 0 && + !generalGroup[ii].category.is_income && + carryover.carryover === 0 && + Math.abs(balance) > budgetAvailable + ) { + await setBudget({ + category: generalGroup[ii].category, + month, + amount: budgeted + budgetAvailable, + }); + } + } + const budgetAvailable = await getSheetValue(sheetName, `to-budget`); + for (let ii = 0; ii < sinkGroup.length; ii++) { + const budgeted = await getSheetValue( + sheetName, + `budget-${sinkGroup[ii].category}`, + ); + const to_budget = + budgeted + + Math.round((sinkGroup[ii].weight / total_weight) * budgetAvailable); + await setBudget({ + category: sinkGroup[ii].category, + month, + amount: to_budget, + }); + } + } else { + warnings.push(groupName + ' has no matching sink categories.'); + } + sourceGroups = sourceGroups.filter(c => c.group !== groupName); + groupLength = sourceGroups.length; + } + return warnings; +} + async function processCleanup(month: string): Promise { let num_sources = 0; let num_sinks = 0; @@ -25,11 +136,62 @@ async function processCleanup(month: string): Promise { 'SELECT * FROM v_categories WHERE tombstone = 0', ); const sheetName = monthUtils.sheetForMonth(month); + const groupSource = []; + const groupSink = []; + const groupGeneral = []; + + //filter out category groups + for (let c = 0; c < categories.length; c++) { + const category = categories[c]; + const template = category_templates[category.id]; + + //filter out source and sink groups for processing + if (template) { + if ( + template.filter(t => t.type === 'source' && t.group !== null).length > 0 + ) { + groupSource.push({ + category: category.id, + group: template.filter( + t => t.type === 'source' && t.group !== null, + )[0].group, + }); + } + if ( + template.filter(t => t.type === 'sink' && t.group !== null).length > 0 + ) { + //only supports 1 sink reference per category. Need more? + groupSink.push({ + category: category.id, + group: template.filter(t => t.type === 'sink' && t.group !== null)[0] + .group, + weight: template.filter(t => t.type === 'sink' && t.group !== null)[0] + .weight, + }); + } + if ( + template.filter(t => t.type === null && t.group !== null).length > 0 + ) { + groupGeneral.push({ category: category.id, group: template[0].group }); + } + } + } + //run category groups + const newWarnings = await applyGroupCleanups( + month, + groupSource, + groupSink, + groupGeneral, + ); + warnings.splice(1, 0, ...newWarnings); + for (let c = 0; c < categories.length; c++) { const category = categories[c]; const template = category_templates[category.id]; if (template) { - if (template.filter(t => t.type === 'source').length > 0) { + if ( + template.filter(t => t.type === 'source' && t.group === null).length > 0 + ) { const balance = await getSheetValue( sheetName, `leftover-${category.id}`, @@ -39,10 +201,10 @@ async function processCleanup(month: string): Promise { `budget-${category.id}`, ); if (balance >= 0) { - const spent = await getSheetValue( - sheetName, - `sum-amount-${category.id}`, - ); + // const spent = await getSheetValue( + // sheetName, + // `sum-amount-${category.id}`, + // ); await setBudget({ category: category.id, month, @@ -51,7 +213,7 @@ async function processCleanup(month: string): Promise { await setGoal({ category: category.id, month, - goal: -spent, + goal: budgeted - balance, }); num_sources += 1; } else { @@ -68,7 +230,9 @@ async function processCleanup(month: string): Promise { } } } - if (template.filter(t => t.type === 'sink').length > 0) { + if ( + template.filter(t => t.type === 'sink' && t.group === null).length > 0 + ) { sinkCategory.push({ cat: category, temp: template }); num_sinks += 1; total_weight += template.filter(w => w.type === 'sink')[0].weight; @@ -120,9 +284,10 @@ async function processCleanup(month: string): Promise { const budgetAvailable = await getSheetValue(sheetName, `to-budget`); if (budgetAvailable <= 0) { - warnings.push('No funds are available to reallocate.'); + warnings.push('Global: No funds are available to reallocate.'); } + //fill sinking categories for (let c = 0; c < sinkCategory.length; c++) { const budgeted = await getSheetValue( sheetName, @@ -160,7 +325,7 @@ async function processCleanup(month: string): Promise { } else if (warnings.length) { return { type: 'warning', - message: 'Funds not available:', + message: 'Global: Funds not available:', pre: warnings.join('\n\n'), }; } else { @@ -179,6 +344,12 @@ async function processCleanup(month: string): Promise { message: `${applied} There were errors interpreting some templates:`, pre: errors.join('\n\n'), }; + } else if (warnings.length) { + return { + type: 'warning', + message: 'Global: Funds not available:', + pre: warnings.join('\n\n'), + }; } else { return { type: 'message', diff --git a/upcoming-release-notes/2480.md b/upcoming-release-notes/2480.md new file mode 100644 index 00000000000..90cfc6f5fad --- /dev/null +++ b/upcoming-release-notes/2480.md @@ -0,0 +1,6 @@ +--- +category: Enhancements +authors: [shall0pass] +--- + +Add category groups to end of month cleanup templates. \ No newline at end of file