Compare commits

..

11 commits

Author SHA1 Message Date
James Long
412c34f6d9 Fix test 2022-11-12 22:28:26 -05:00
James Long
e8a79c8dbf Fix lint 2022-11-12 22:13:25 -05:00
James Long
496ef039b7 Move safeNumber to shared util and tweak implementation 2022-11-10 12:37:05 -05:00
Tom French
6cbc22312f feat: add explicit value checking on saving to / reading from budget 2022-11-10 12:35:20 -05:00
Tom French
3a925948e2 fix: replace last usages of | 0 2022-11-10 12:33:19 -05:00
Tom French
6e15b985cb fix: remove unnecessary conversion to 32 bit 2022-11-10 12:33:19 -05:00
Tom French
9a442361ad fix: use Math.round in place of truncating digits 2022-11-10 12:33:19 -05:00
Tom French
f5417bcc8d fix: use Math.round in place of truncating digits 2022-11-10 12:33:19 -05:00
Tom French
fb3e12b8a7 fix: replace custom isInteger function with Number.isInteger 2022-11-10 12:33:19 -05:00
Tom French
75da379079 fix: use 64bit compatible integer check in aql compiler 2022-11-10 12:33:18 -05:00
Tom French
423f355766 fix: use integer check which doesn't require value to be 32 bit 2022-11-10 12:33:18 -05:00
27 changed files with 66569 additions and 107 deletions

View file

@ -1 +0,0 @@
app/bundle.api.js*

66420
packages/api/app/bundle.api.js Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,7 +1,8 @@
let bundle = require('./app/bundle.api.js'); let bundle = require('./app/bundle.api.js');
let injected = require('./injected');
let methods = require('./methods'); let methods = require('./methods');
let utils = require('./utils'); let utils = require('./utils');
let injected = require('./injected');
let actualApp; let actualApp;
async function init({ budgetId, config } = {}) { async function init({ budgetId, config } = {}) {

View file

@ -1,18 +1,9 @@
{ {
"name": "@actual-app/api", "name": "@actual-app/api",
"version": "4.1.5", "version": "4.0.2",
"license": "MIT", "license": "MIT",
"description": "An API for Actual", "description": "An API for Actual",
"main": "index.js", "main": "index.js",
"files": [
"app",
"default-db.sqlite",
"index.js",
"injected.js",
"methods.js",
"migrations",
"utils.js"
],
"dependencies": { "dependencies": {
"better-sqlite3": "^7.5.0", "better-sqlite3": "^7.5.0",
"node-fetch": "^1.6.3", "node-fetch": "^1.6.3",

View file

@ -1,6 +1,6 @@
{ {
"name": "@actual-app/web", "name": "@actual-app/web",
"version": "22.12.03", "version": "22.10.25",
"license": "MIT", "license": "MIT",
"files": [ "files": [
"build" "build"

View file

@ -1,5 +1,4 @@
default-db.sqlite default-db.sqlite
migrations/.force-copy-windows
migrations/1548957970627_remove-db-version.sql migrations/1548957970627_remove-db-version.sql
migrations/1550601598648_payees.sql migrations/1550601598648_payees.sql
migrations/1555786194328_remove_category_group_unique.sql migrations/1555786194328_remove_category_group_unique.sql
@ -15,3 +14,4 @@ migrations/1615745967948_meta.sql
migrations/1616167010796_accounts_order.sql migrations/1616167010796_accounts_order.sql
migrations/1618975177358_schedules.sql migrations/1618975177358_schedules.sql
migrations/1632571489012_remove_cache.js migrations/1632571489012_remove_cache.js
migrations/.force-copy-windows

View file

@ -35,7 +35,6 @@ import {
Stack Stack
} from 'loot-design/src/components/common'; } from 'loot-design/src/components/common';
import { KeyHandlers } from 'loot-design/src/components/KeyHandlers'; import { KeyHandlers } from 'loot-design/src/components/KeyHandlers';
import NotesButton from 'loot-design/src/components/NotesButton';
import CellValue from 'loot-design/src/components/spreadsheet/CellValue'; import CellValue from 'loot-design/src/components/spreadsheet/CellValue';
import format from 'loot-design/src/components/spreadsheet/format'; import format from 'loot-design/src/components/spreadsheet/format';
import useSheetValue from 'loot-design/src/components/spreadsheet/useSheetValue'; import useSheetValue from 'loot-design/src/components/spreadsheet/useSheetValue';
@ -107,7 +106,6 @@ function EmptyMessage({ onAdd }) {
function ReconcilingMessage({ balanceQuery, targetBalance, onDone }) { function ReconcilingMessage({ balanceQuery, targetBalance, onDone }) {
let cleared = useSheetValue({ let cleared = useSheetValue({
name: balanceQuery.name + '-cleared', name: balanceQuery.name + '-cleared',
value: 0,
query: balanceQuery.query.filter({ cleared: true }) query: balanceQuery.query.filter({ cleared: true })
}); });
let targetDiff = targetBalance - cleared; let targetDiff = targetBalance - cleared;
@ -671,46 +669,30 @@ const AccountHeader = React.memo(
/> />
</InitialFocus> </InitialFocus>
) : isNameEditable ? ( ) : isNameEditable ? (
<View <Button
bare
style={{ style={{
flexDirection: 'row', fontSize: 25,
alignItems: 'center', fontWeight: 500,
gap: 3, marginLeft: -5,
'& .hover-visible': { marginTop: -5,
opacity: 0, backgroundColor: 'transparent',
transition: 'opacity .25s' '& svg': { display: 'none' },
}, '&:hover svg': { display: 'unset' }
'&:hover .hover-visible': {
opacity: 1
}
}} }}
onClick={() => onExposeName(true)}
> >
<View {accountName}
style={{
fontSize: 25,
fontWeight: 500,
marginRight: 5,
marginBottom: 5
}}
>
{accountName}
</View>
<NotesButton id={`account-${account.id}`} /> <Pencil1
<Button style={{
bare width: 11,
className="hover-visible" height: 11,
onClick={() => onExposeName(true)} marginLeft: 5,
> color: colors.n4
<Pencil1 }}
style={{ />
width: 11, </Button>
height: 11,
color: colors.n8
}}
/>
</Button>
</View>
) : ( ) : (
<View <View
style={{ fontSize: 25, fontWeight: 500, marginBottom: 5 }} style={{ fontSize: 25, fontWeight: 500, marginBottom: 5 }}

View file

@ -336,7 +336,7 @@ function StatusCell({
? colors.y5 ? colors.y5
: selected : selected
? colors.b7 ? colors.b7
: colors.n7 : colors.n6
}; };
function onSelect() { function onSelect() {
@ -362,8 +362,7 @@ function StatusCell({
':focus': { ':focus': {
border: '1px solid ' + props.color, border: '1px solid ' + props.color,
boxShadow: `0 1px 2px ${props.color}` boxShadow: `0 1px 2px ${props.color}`
}, }
cursor: isClearedField ? 'pointer' : 'default'
}, },
isChild && { visibility: 'hidden' } isChild && { visibility: 'hidden' }

View file

@ -6,7 +6,6 @@ import { colors } from 'loot-design/src/style';
import AlertTriangle from 'loot-design/src/svg/v2/AlertTriangle'; import AlertTriangle from 'loot-design/src/svg/v2/AlertTriangle';
import CalendarIcon from 'loot-design/src/svg/v2/Calendar'; import CalendarIcon from 'loot-design/src/svg/v2/Calendar';
import CheckCircle1 from 'loot-design/src/svg/v2/CheckCircle1'; import CheckCircle1 from 'loot-design/src/svg/v2/CheckCircle1';
import CheckCircleHollow from 'loot-design/src/svg/v2/CheckCircleHollow';
import EditSkull1 from 'loot-design/src/svg/v2/EditSkull1'; import EditSkull1 from 'loot-design/src/svg/v2/EditSkull1';
import FavoriteStar from 'loot-design/src/svg/v2/FavoriteStar'; import FavoriteStar from 'loot-design/src/svg/v2/FavoriteStar';
import ValidationCheck from 'loot-design/src/svg/v2/ValidationCheck'; import ValidationCheck from 'loot-design/src/svg/v2/ValidationCheck';
@ -50,15 +49,10 @@ export function getStatusProps(status) {
backgroundColor = colors.n11; backgroundColor = colors.n11;
Icon = CalendarIcon; Icon = CalendarIcon;
break; break;
case 'cleared':
color = colors.g5;
backgroundColor = colors.n11;
Icon = CheckCircle1;
break;
default: default:
color = colors.n1; color = colors.n1;
backgroundColor = colors.n11; backgroundColor = colors.n11;
Icon = CheckCircleHollow; Icon = CheckCircle1;
break; break;
} }

View file

@ -3,7 +3,7 @@
"productName": "Actual", "productName": "Actual",
"author": "Shift Reset LLC", "author": "Shift Reset LLC",
"description": "A simple and powerful personal finance system", "description": "A simple and powerful personal finance system",
"version": "22.12.03", "version": "22.10.25",
"scripts": { "scripts": {
"clean": "rm -rf dist", "clean": "rm -rf dist",
"build": "electron-builder", "build": "electron-builder",

View file

@ -1,13 +1,9 @@
// This is a special usage of the API because this package is embedded
// into Actual itself. We only want to pull in the methods in that
// case and ignore everything else; otherwise we'd be pulling in the
// entire backend bundle from the API
const actual = require('@actual-app/api/methods');
const { amountToInteger } = require('@actual-app/api/utils');
const AdmZip = require('adm-zip');
const d = require('date-fns'); const d = require('date-fns');
const normalizePathSep = require('slash'); const normalizePathSep = require('slash');
const uuid = require('uuid'); const uuid = require('uuid');
const AdmZip = require('adm-zip');
const actual = require('@actual-app/api');
const amountToInteger = actual.utils.amountToInteger;
// Utils // Utils

View file

@ -16,7 +16,7 @@
"bin": "./index.js", "bin": "./index.js",
"homepage": "https://github.com/actualbudget/actual/tree/master/packages/import-ynab4#readme", "homepage": "https://github.com/actualbudget/actual/tree/master/packages/import-ynab4#readme",
"dependencies": { "dependencies": {
"@actual-app/api": "*", "@actual-app/api": "^1.0.0",
"adm-zip": "^0.5.9", "adm-zip": "^0.5.9",
"date-fns": "2.0.0-alpha.27", "date-fns": "2.0.0-alpha.27",
"slash": "3.0.0", "slash": "3.0.0",

View file

@ -1,10 +1,6 @@
// This is a special usage of the API because this package is embedded
// into Actual itself. We only want to pull in the methods in that
// case and ignore everything else; otherwise we'd be pulling in the
// entire backend bundle from the API
const actual = require('@actual-app/api/methods');
const d = require('date-fns'); const d = require('date-fns');
const uuid = require('uuid'); const uuid = require('uuid');
const actual = require('@actual-app/api');
function amountFromYnab(amount) { function amountFromYnab(amount) {
// ynabs multiplies amount by 1000 and actual by 100 // ynabs multiplies amount by 1000 and actual by 100

View file

@ -16,7 +16,7 @@
"bin": "./index.js", "bin": "./index.js",
"homepage": "https://github.com/actualbudget/actual/tree/master/packages/import-ynab5#readme", "homepage": "https://github.com/actualbudget/actual/tree/master/packages/import-ynab5#readme",
"dependencies": { "dependencies": {
"@actual-app/api": "*", "@actual-app/api": "^1.0.0",
"date-fns": "2.0.0-alpha.27", "date-fns": "2.0.0-alpha.27",
"uuid": "3.3.2" "uuid": "3.3.2"
} }

View file

@ -24,12 +24,21 @@ export function applyBudgetAction(month, type, args) {
case 'set-3-avg': case 'set-3-avg':
await send('budget/set-3month-avg', { month }); await send('budget/set-3month-avg', { month });
break; break;
case 'set-all-future':
await send('budget/set-all-future', { startMonth: month });
break;
case 'hold': case 'hold':
await send('budget/hold-for-next-month', { await send('budget/hold-for-next-month', {
month, month,
amount: args.amount amount: args.amount
}); });
break; break;
case 'hold-all-future':
await send('budget/hold-for-future-months', {
startMonth: month,
amount: args.amount
});
break;
case 'reset-hold': case 'reset-hold':
await send('budget/reset-hold', { month }); await send('budget/reset-hold', { month });
break; break;

View file

@ -190,6 +190,26 @@ export async function set3MonthAvg({ month }) {
}); });
} }
export async function setAllFuture({ startMonth }) {
if (!isReflectBudget()) {
throw new Error('setAllFuture only applies to report budget type');
}
let table = getBudgetTable();
let budgetData = await getBudgetData(table, dbMonth(startMonth));
let months = getAllMonths(monthUtils.addMonths(startMonth, 1));
batchMessages(() => {
for (let month of months) {
budgetData.forEach(budget => {
if (budget.is_income === 1 && !isReflectBudget()) {
return;
}
setBudget({ category: budget.category, month, amount: budget.amount });
});
}
});
}
export async function holdForNextMonth({ month, amount }) { export async function holdForNextMonth({ month, amount }) {
let row = await db.first( let row = await db.first(
'SELECT buffered FROM zero_budget_months WHERE id = ?', 'SELECT buffered FROM zero_budget_months WHERE id = ?',
@ -212,6 +232,18 @@ export async function holdForNextMonth({ month, amount }) {
return false; return false;
} }
export async function holdForFutureMonths({ startMonth, amount }) {
let months = getAllMonths(startMonth);
await batchMessages(async () => {
for (let month of months) {
if (!(await holdForNextMonth({ month, amount }))) {
break;
}
}
});
}
export async function resetHold({ month }) { export async function resetHold({ month }) {
await setBuffer(month, 0); await setBuffer(month, 0);
} }

View file

@ -12,10 +12,15 @@ app.method(
); );
app.method('budget/set-zero', mutator(undoable(actions.setZero))); app.method('budget/set-zero', mutator(undoable(actions.setZero)));
app.method('budget/set-3month-avg', mutator(undoable(actions.set3MonthAvg))); app.method('budget/set-3month-avg', mutator(undoable(actions.set3MonthAvg)));
app.method('budget/set-all-future', mutator(undoable(actions.setAllFuture)));
app.method( app.method(
'budget/hold-for-next-month', 'budget/hold-for-next-month',
mutator(undoable(actions.holdForNextMonth)) mutator(undoable(actions.holdForNextMonth))
); );
app.method(
'budget/hold-for-future-months',
mutator(undoable(actions.holdForFutureMonths))
);
app.method('budget/reset-hold', mutator(undoable(actions.resetHold))); app.method('budget/reset-hold', mutator(undoable(actions.resetHold)));
app.method( app.method(
'budget/cover-overspending', 'budget/cover-overspending',

View file

@ -368,6 +368,10 @@ export default React.memo(function BudgetSummary({ month }) {
{ {
name: 'set-3-avg', name: 'set-3-avg',
text: 'Set budgets to 3 month avg' text: 'Set budgets to 3 month avg'
},
{
name: 'set-all-future',
text: 'Apply to all future budgets'
} }
]} ]}
/> />

View file

@ -204,6 +204,10 @@ function ToBudget({ month, prevMonthName, collapsed, onBudgetAction }) {
name: 'buffer', name: 'buffer',
text: 'Hold for next month' text: 'Hold for next month'
}, },
{
name: 'buffer-future',
text: 'Hold for all future months'
},
{ {
name: 'reset-buffer', name: 'reset-buffer',
text: "Reset next month's buffer" text: "Reset next month's buffer"
@ -220,6 +224,14 @@ function ToBudget({ month, prevMonthName, collapsed, onBudgetAction }) {
}} }}
/> />
)} )}
{state.menuOpen === 'buffer-future' && (
<HoldTooltip
onClose={() => setState({ menuOpen: null })}
onSubmit={amount => {
onBudgetAction(month, 'hold-all-future', { amount });
}}
/>
)}
{state.menuOpen === 'transfer' && ( {state.menuOpen === 'transfer' && (
<TransferTooltip <TransferTooltip
initialAmountName="leftover" initialAmountName="leftover"

View file

@ -938,6 +938,10 @@ export function ModalButtons({
style style
]} ]}
> >
{/* Add a dummy button first so that when a user
presses "enter" they do a normal submit, instead of
activating the back button */}
<Button data-hidden={true} style={{ display: 'none' }} />
{leftContent} {leftContent}
<View style={{ flex: 1 }} /> <View style={{ flex: 1 }} />
{children} {children}

View file

@ -146,9 +146,7 @@ function CreateLocalAccount({ modalProps, actions, history }) {
)} )}
<ModalButtons> <ModalButtons>
<Button onClick={() => modalProps.onBack()} type="button"> <Button onClick={() => modalProps.onBack()}>Back</Button>
Back
</Button>
<Button primary style={{ marginLeft: 10 }}> <Button primary style={{ marginLeft: 10 }}>
Create Create
</Button> </Button>

View file

@ -397,9 +397,6 @@ const MenuButton = withRouter(function MenuButton({ history }) {
case 'settings': case 'settings':
history.push('/settings'); history.push('/settings');
break; break;
case 'help':
window.open('https://actualbudget.github.io/docs', '_blank');
break;
case 'close': case 'close':
dispatch(closeBudget()); dispatch(closeBudget());
break; break;
@ -414,7 +411,6 @@ const MenuButton = withRouter(function MenuButton({ history }) {
{ name: 'repair-splits', text: 'Repair split transactions' }, { name: 'repair-splits', text: 'Repair split transactions' },
Menu.line, Menu.line,
{ name: 'settings', text: 'Settings' }, { name: 'settings', text: 'Settings' },
{ name: 'help', text: 'Help' },
{ name: 'close', text: 'Close File' } { name: 'close', text: 'Close File' }
]; ];

View file

@ -41,7 +41,7 @@ export default function useSheetValue(binding, onChange) {
let spreadsheet = useContext(SpreadsheetContext); let spreadsheet = useContext(SpreadsheetContext);
let [result, setResult] = useState({ let [result, setResult] = useState({
name: sheetName + '!' + binding.name, name: sheetName + '!' + binding.name,
value: binding.value === undefined ? null : binding.value, value: binding.value,
query: binding.query query: binding.query
}); });
let latestOnChange = useRef(onChange); let latestOnChange = useRef(onChange);

View file

@ -1,19 +0,0 @@
import React from 'react';
const SvgCheckCircleHollow = props => (
<svg
{...props}
viewBox="0 0 24 24"
style={{
color: '#242134',
...props.style
}}
>
<path
d="M 12 0 C 1.3084197 0 -4.0435475 12.925204 3.515625 20.484375 C 11.074797 28.043547 24 22.69158 24 12 C 23.992285 5.3757944 18.624205 0.0077147446 12 0 z M 12.009766 1.9882812 C 17.531104 1.9947115 22.005288 6.4688953 22.011719 11.990234 C 22.011719 20.90177 11.238144 25.363144 4.9375 19.0625 C -1.3631434 12.761856 3.0982293 1.9882812 12.009766 1.9882812 z M 18.244141 6.5761719 A 1 1 0 0 0 17.316406 7.0175781 L 11.089844 15.46875 L 7.0136719 12.207031 A 1.0004882 1.0004882 0 1 0 5.7636719 13.769531 L 10.652344 17.677734 A 1.011 1.011 0 0 0 12.082031 17.488281 L 18.927734 8.1992188 A 1 1 0 0 0 18.244141 6.5761719 z "
fill="currentColor"
/>
</svg>
);
export default SvgCheckCircleHollow;

View file

@ -330,6 +330,11 @@ class Budget extends React.Component {
case 3: case 3:
this.onBudgetAction('set-3-avg'); this.onBudgetAction('set-3-avg');
break; break;
case 4:
if (budgetType === 'report') {
this.onBudgetAction('set-all-future');
break;
}
default: default:
} }
} }

View file

@ -22,11 +22,21 @@ __metadata:
languageName: unknown languageName: unknown
linkType: soft linkType: soft
"@actual-app/api@npm:^1.0.0":
version: 1.1.3
resolution: "@actual-app/api@npm:1.1.3"
dependencies:
node-ipc: 9.1.1
uuid: 3.3.2
checksum: e7fccff7583d64ac908eb7a7c93226200fd75af92b9fe9718b6e3fe0d004d92d79d87485e212b0d3d86cb685827e6733c939ece799156eea64db886bf1457a94
languageName: node
linkType: hard
"@actual-app/import-ynab4@*, @actual-app/import-ynab4@workspace:packages/import-ynab4": "@actual-app/import-ynab4@*, @actual-app/import-ynab4@workspace:packages/import-ynab4":
version: 0.0.0-use.local version: 0.0.0-use.local
resolution: "@actual-app/import-ynab4@workspace:packages/import-ynab4" resolution: "@actual-app/import-ynab4@workspace:packages/import-ynab4"
dependencies: dependencies:
"@actual-app/api": "*" "@actual-app/api": ^1.0.0
adm-zip: ^0.5.9 adm-zip: ^0.5.9
date-fns: 2.0.0-alpha.27 date-fns: 2.0.0-alpha.27
slash: 3.0.0 slash: 3.0.0
@ -40,7 +50,7 @@ __metadata:
version: 0.0.0-use.local version: 0.0.0-use.local
resolution: "@actual-app/import-ynab5@workspace:packages/import-ynab5" resolution: "@actual-app/import-ynab5@workspace:packages/import-ynab5"
dependencies: dependencies:
"@actual-app/api": "*" "@actual-app/api": ^1.0.0
date-fns: 2.0.0-alpha.27 date-fns: 2.0.0-alpha.27
uuid: 3.3.2 uuid: 3.3.2
bin: bin:
@ -8834,7 +8844,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"easy-stack@npm:^1.0.1": "easy-stack@npm:^1.0.0, easy-stack@npm:^1.0.1":
version: 1.0.1 version: 1.0.1
resolution: "easy-stack@npm:1.0.1" resolution: "easy-stack@npm:1.0.1"
checksum: 161a99e497b3857b0be4ec9e1ebbe90b241ea9d84702f9881b8e5b3f6822065b8c4e33436996935103e191bffba3607de70712a792f4d406a050def48c6bc381 checksum: 161a99e497b3857b0be4ec9e1ebbe90b241ea9d84702f9881b8e5b3f6822065b8c4e33436996935103e191bffba3607de70712a792f4d406a050def48c6bc381
@ -13803,6 +13813,13 @@ jest-snapshot@test:
languageName: node languageName: node
linkType: hard linkType: hard
"js-message@npm:1.0.5":
version: 1.0.5
resolution: "js-message@npm:1.0.5"
checksum: fd2fc8837a88a115aa2fa859bf5c13d9b335fd7eeba8426c44da6eb006b04c52cfe6675b3c27d6b112ffc51dadb8bc51d58340c3a3aa5c555d7da6bdc72ce9c0
languageName: node
linkType: hard
"js-message@npm:1.0.7": "js-message@npm:1.0.7":
version: 1.0.7 version: 1.0.7
resolution: "js-message@npm:1.0.7" resolution: "js-message@npm:1.0.7"
@ -13810,6 +13827,15 @@ jest-snapshot@test:
languageName: node languageName: node
linkType: hard linkType: hard
"js-queue@npm:2.0.0":
version: 2.0.0
resolution: "js-queue@npm:2.0.0"
dependencies:
easy-stack: ^1.0.0
checksum: 8f8e589cc20fd3bc3067db73ecaac77b55411c3ac58fdd6882868924ee19ab4203d19e68d3ec680c5c8f5e8282e30dafa377014dbec05c3f2d33be4596f4fb65
languageName: node
linkType: hard
"js-queue@npm:2.0.2": "js-queue@npm:2.0.2":
version: 2.0.2 version: 2.0.2
resolution: "js-queue@npm:2.0.2" resolution: "js-queue@npm:2.0.2"
@ -16196,6 +16222,17 @@ jest-snapshot@test:
languageName: node languageName: node
linkType: hard linkType: hard
"node-ipc@npm:9.1.1":
version: 9.1.1
resolution: "node-ipc@npm:9.1.1"
dependencies:
event-pubsub: 4.3.0
js-message: 1.0.5
js-queue: 2.0.0
checksum: 2b66099d1976e4328d34ae7fec853d3969ca337b52b5aefb48ae1d19387c37d6716c2b98d4a4934ec24aa79f0441721961d6c1beb858c294ad6a7a97ddf5460d
languageName: node
linkType: hard
"node-ipc@npm:9.1.4": "node-ipc@npm:9.1.4":
version: 9.1.4 version: 9.1.4
resolution: "node-ipc@npm:9.1.4" resolution: "node-ipc@npm:9.1.4"