diff --git a/packages/loot-core/src/mocks/budget.ts b/packages/loot-core/src/mocks/budget.ts index 8c308b3393d..308b64f17ef 100644 --- a/packages/loot-core/src/mocks/budget.ts +++ b/packages/loot-core/src/mocks/budget.ts @@ -12,24 +12,26 @@ import q from '../shared/query'; import type { CategoryGroupEntity, PayeeEntity, - TransactionEntity, + NewTransactionEntity, } from '../types/models'; import random from './random'; -function pickRandom(list) { +type MockPayeeEntity = PayeeEntity & { bill?: boolean }; + +function pickRandom(list: T[]): T { return list[Math.floor(random() * list.length) % list.length]; } -function number(start, end) { +function number(start: number, end: number) { return start + (end - start) * random(); } -function integer(start, end) { +function integer(start: number, end: number) { return Math.round(number(start, end)); } -function findMin(items, field) { +function findMin(items: T[], field: K) { let item = items[0]; for (let i = 0; i < items.length; i++) { if (items[i][field] < item[field]) { @@ -39,17 +41,20 @@ function findMin(items, field) { return item; } -function getStartingBalanceCat(categories) { +function getStartingBalanceCat(categories: CategoryGroupEntity[]) { return categories.find(c => c.name === 'Starting Balances').id; } -function extractCommonThings(payees, groups) { +function extractCommonThings( + payees: MockPayeeEntity[], + groups: CategoryGroupEntity[], +) { const incomePayee = payees.find(p => p.name === 'Deposit'); const expensePayees = payees.filter( p => p.name !== 'Deposit' && p.name !== 'Starting Balance', ); - const expenseGroup = groups.find(g => g.is_income === 0); - const incomeGroup = groups.find(g => g.is_income === 1); + const expenseGroup = groups.find(g => !g.is_income); + const incomeGroup = groups.find(g => g.is_income); const categories = expenseGroup.categories.filter( c => [ @@ -73,7 +78,12 @@ function extractCommonThings(payees, groups) { }; } -async function fillPrimaryChecking(handlers, account, payees, groups) { +async function fillPrimaryChecking( + handlers, + account, + payees: MockPayeeEntity[], + groups: CategoryGroupEntity[], +) { const { incomePayee, expensePayees, @@ -107,7 +117,7 @@ async function fillPrimaryChecking(handlers, account, payees, groups) { amount = integer(0, random() < 0.05 ? -8000 : -700); } - const transaction: TransactionEntity = { + const transaction: NewTransactionEntity = { amount, payee: payee.id, account: account.id, @@ -129,7 +139,7 @@ async function fillPrimaryChecking(handlers, account, payees, groups) { amount: transaction.amount - a * 2, category: pick(), }, - ] as TransactionEntity[]; + ]; } } @@ -391,7 +401,7 @@ async function fillOther(handlers, account, payees, groups) { const numTransactions = integer(3, 6); const category = incomeGroup.categories.find(c => c.name === 'Income'); - const transactions: TransactionEntity[] = [ + const transactions: NewTransactionEntity[] = [ { amount: integer(3250, 3700) * 100 * 100, payee: payees.find(p => p.name === 'Starting Balance').id, @@ -585,7 +595,7 @@ export async function createTestBudget(handlers) { }), ); - const payees: Array = [ + const payees: Array = [ { name: 'Starting Balance' }, { name: 'Kroger' }, { name: 'Publix' }, diff --git a/packages/loot-core/src/server/accounts/transaction-rules.ts b/packages/loot-core/src/server/accounts/transaction-rules.ts index 4761375a32a..3cb418afce7 100644 --- a/packages/loot-core/src/server/accounts/transaction-rules.ts +++ b/packages/loot-core/src/server/accounts/transaction-rules.ts @@ -11,7 +11,11 @@ import { getApproxNumberThreshold, } from '../../shared/rules'; import { partitionByField, fastSetMerge } from '../../shared/util'; -import { type RuleActionEntity, type RuleEntity } from '../../types/models'; +import { + type TransactionEntity, + type RuleActionEntity, + type RuleEntity, +} from '../../types/models'; import { schemaConfig } from '../aql'; import * as db from '../db'; import { getMappings } from '../db/mappings'; @@ -682,7 +686,7 @@ export async function updateCategoryRules(transactions) { // Also look 180 days in the future to get any future transactions // (this might change when we think about scheduled transactions) - let register = await db.all( + let register: TransactionEntity[] = await db.all( `SELECT t.* FROM v_transactions t LEFT JOIN accounts a ON a.id = t.account WHERE date >= ? AND date <= ? AND is_parent = 0 AND a.closed = 0 diff --git a/packages/loot-core/src/shared/transactions.ts b/packages/loot-core/src/shared/transactions.ts index 5e99b43d4fb..d12fd0768eb 100644 --- a/packages/loot-core/src/shared/transactions.ts +++ b/packages/loot-core/src/shared/transactions.ts @@ -1,5 +1,7 @@ import { v4 as uuidv4 } from 'uuid'; +import { type TransactionEntity } from '../types/models'; + import { last, diffItems, applyChanges } from './util'; export function isPreviewId(id) { @@ -80,8 +82,8 @@ function getSplit(transactions, parentIndex) { return split; } -export function ungroupTransactions(transactions) { - const x = transactions.reduce((list, parent) => { +export function ungroupTransactions(transactions: TransactionEntity[]) { + return transactions.reduce((list, parent) => { const { subtransactions, ...trans } = parent; const _subtransactions = subtransactions || []; @@ -92,25 +94,31 @@ export function ungroupTransactions(transactions) { } return list; }, []); - return x; } function groupTransaction(split) { return { ...split[0], subtransactions: split.slice(1) }; } -export function ungroupTransaction(split) { +export function ungroupTransaction(split: TransactionEntity | null) { if (split == null) { return null; } return ungroupTransactions([split]); } -export function applyTransactionDiff(groupedTrans, diff) { +export function applyTransactionDiff( + groupedTrans: Parameters[0], + diff: Parameters[0], +) { return groupTransaction(applyChanges(diff, ungroupTransaction(groupedTrans))); } -function replaceTransactions(transactions, id, func) { +function replaceTransactions( + transactions: TransactionEntity[], + id: string, + func: (transaction: TransactionEntity) => TransactionEntity, +) { const idx = transactions.findIndex(t => t.id === id); const trans = transactions[idx]; const transactionsCopy = [...transactions]; @@ -127,7 +135,9 @@ function replaceTransactions(transactions, id, func) { } const split = getSplit(transactions, parentIndex); - let grouped = func(groupTransaction(split)); + let grouped: TransactionEntity | { id: string; _deleted: boolean } = func( + groupTransaction(split), + ); const newSplit = ungroupTransaction(grouped); let diff; @@ -159,7 +169,10 @@ function replaceTransactions(transactions, id, func) { } } -export function addSplitTransaction(transactions, id) { +export function addSplitTransaction( + transactions: TransactionEntity[], + id: string, +) { return replaceTransactions(transactions, id, trans => { if (!trans.is_parent) { return trans; diff --git a/packages/loot-core/src/shared/util.ts b/packages/loot-core/src/shared/util.ts index cf587db47ec..3c32ab5cd70 100644 --- a/packages/loot-core/src/shared/util.ts +++ b/packages/loot-core/src/shared/util.ts @@ -1,4 +1,4 @@ -export function last(arr) { +export function last(arr: Array) { return arr[arr.length - 1]; } @@ -33,7 +33,14 @@ export function hasFieldsChanged(obj1, obj2, fields) { return changed; } -export function applyChanges(changes, items) { +export function applyChanges( + changes: { + added?: T[]; + updated?: T[]; + deleted?: T[]; + }, + items: T[], +) { items = [...items]; if (changes.added) { @@ -64,7 +71,7 @@ export function applyChanges(changes, items) { return items; } -export function partitionByField(data, field) { +export function partitionByField(data: T[], field: K) { const res = new Map(); for (let i = 0; i < data.length; i++) { const item = data[i]; @@ -93,7 +100,7 @@ export function groupBy(data: T[], field: K) { // `Map` is better, but we can't swap it out because `Map` has a // different API and we need to go through and update everywhere that // uses it. -function _groupById(data) { +function _groupById(data: T[]) { const res = new Map(); for (let i = 0; i < data.length; i++) { const item = data[i]; @@ -102,7 +109,7 @@ function _groupById(data) { return res; } -export function diffItems(items, newItems) { +export function diffItems(items: T[], newItems: T[]) { const grouped = _groupById(items); const newGrouped = _groupById(newItems); const added = []; @@ -127,7 +134,7 @@ export function diffItems(items, newItems) { return { added, updated, deleted }; } -export function groupById(data) { +export function groupById(data: T[]) { const res = {}; for (let i = 0; i < data.length; i++) { const item = data[i]; @@ -168,7 +175,7 @@ export function getIn(map, keys) { return item; } -export function fastSetMerge(set1, set2) { +export function fastSetMerge(set1: Set, set2: Set) { const finalSet = new Set(set1); const iter = set2.values(); let value = iter.next(); @@ -214,7 +221,13 @@ export function setNumberFormat(config: typeof numberFormatConfig) { numberFormatConfig = config; } -export function getNumberFormat({ format, hideFraction } = numberFormatConfig) { +export function getNumberFormat({ + format, + hideFraction, +}: { + format: NumberFormats; + hideFraction: boolean; +} = numberFormatConfig) { let locale, regex, separator; switch (format) { @@ -282,11 +295,14 @@ export function safeNumber(value: number) { return value; } -export function toRelaxedNumber(value) { +export function toRelaxedNumber(value: string) { return integerToAmount(currencyToInteger(value) || 0); } -export function integerToCurrency(n, formatter = getNumberFormat().formatter) { +export function integerToCurrency( + n: number, + formatter = getNumberFormat().formatter, +) { return formatter.format(safeNumber(n) / 100); } @@ -294,7 +310,7 @@ export function amountToCurrency(n) { return getNumberFormat().formatter.format(n); } -export function currencyToAmount(str) { +export function currencyToAmount(str: string) { const amount = parseFloat( str .replace(getNumberFormat().regex, '') @@ -303,12 +319,12 @@ export function currencyToAmount(str) { return isNaN(amount) ? null : amount; } -export function currencyToInteger(str) { +export function currencyToInteger(str: string) { const amount = currencyToAmount(str); return amount == null ? null : amountToInteger(amount); } -export function stringToInteger(str) { +export function stringToInteger(str: string) { const amount = parseInt(str.replace(/[^-0-9.,]/g, '')); if (!isNaN(amount)) { return amount; @@ -316,7 +332,7 @@ export function stringToInteger(str) { return null; } -export function amountToInteger(n) { +export function amountToInteger(n: number) { return Math.round(n * 100); } @@ -328,12 +344,12 @@ export function integerToAmount(n) { // financial files and we don't want to parse based on the user's // number format, because the user could be importing from many // currencies. We extract out the numbers and just ignore separators. -export function looselyParseAmount(amount) { - function safeNumber(v) { +export function looselyParseAmount(amount: string) { + function safeNumber(v: number): null | number { return isNaN(v) ? null : v; } - function extractNumbers(v) { + function extractNumbers(v: string): string { return v.replace(/[^0-9-]/g, ''); } diff --git a/packages/loot-core/src/types/models/transaction.d.ts b/packages/loot-core/src/types/models/transaction.d.ts index 3687db05082..07a64a23175 100644 --- a/packages/loot-core/src/types/models/transaction.d.ts +++ b/packages/loot-core/src/types/models/transaction.d.ts @@ -3,15 +3,14 @@ import type { CategoryEntity } from './category'; import type { PayeeEntity } from './payee'; import type { ScheduleEntity } from './schedule'; -export interface TransactionEntity { - id?: string; +export interface NewTransactionEntity { is_parent?: boolean; is_child?: boolean; parent_id?: string; - account: AccountEntity; - category?: CategoryEntity; + account: string; + category?: string; amount: number; - payee?: PayeeEntity; + payee?: string; notes?: string; date: string; imported_id?: string; @@ -22,6 +21,15 @@ export interface TransactionEntity { cleared?: boolean; reconciled?: boolean; tombstone?: boolean; + schedule?: string; + subtransactions?: Omit[]; +} + +export interface TransactionEntity extends NewTransactionEntity { + id: string; + account: AccountEntity; + category?: CategoryEntity; + payee?: PayeeEntity; schedule?: ScheduleEntity; subtransactions?: TransactionEntity[]; } diff --git a/upcoming-release-notes/2023.md b/upcoming-release-notes/2023.md new file mode 100644 index 00000000000..d75b90fdac5 --- /dev/null +++ b/upcoming-release-notes/2023.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [MatissJanis] +--- + +Added more strict typings to `utils.ts` and some of its dependencies