Skip to content

Commit

Permalink
Update shared transaction module to strict typescript (#2388)
Browse files Browse the repository at this point in the history
* Update shared transaction module to strict typescript
  • Loading branch information
twk3 authored Mar 3, 2024
1 parent b700aee commit 4b03446
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 30 deletions.
79 changes: 49 additions & 30 deletions packages/loot-core/src/shared/transactions.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,27 @@
// @ts-strict-ignore
import { v4 as uuidv4 } from 'uuid';

import { type TransactionEntity } from '../types/models';
import {
type TransactionEntity,
type NewTransactionEntity,
} from '../types/models';

import { last, diffItems, applyChanges } from './util';

export function isPreviewId(id) {
interface TransactionEntityWithError extends TransactionEntity {
error: ReturnType<typeof SplitTransactionError> | null;
_deleted?: boolean;
}

export function isPreviewId(id: string) {
return id.indexOf('preview/') !== -1;
}

// The amount might be null when adding a new transaction
function num(n) {
function num(n: number | null | undefined) {
return typeof n === 'number' ? n : 0;
}

function SplitTransactionError(total, parent) {
function SplitTransactionError(total: number, parent: TransactionEntity) {
const difference = num(parent.amount) - total;

return {
Expand All @@ -24,14 +31,22 @@ function SplitTransactionError(total, parent) {
};
}

export function makeChild(parent, data) {
type GenericTransactionEntity =
| NewTransactionEntity
| TransactionEntity
| TransactionEntityWithError;

export function makeChild<T extends GenericTransactionEntity>(
parent: T,
data: object,
) {
const prefix = parent.id === 'temp' ? 'temp' : '';

return {
amount: 0,
...data,
payee: data.payee || parent.payee,
id: data.id ? data.id : prefix + uuidv4(),
payee: 'payee' in data ? data.payee : parent.payee,
id: 'id' in data ? data.id : prefix + uuidv4(),
account: parent.account,
date: parent.date,
cleared: parent.cleared != null ? parent.cleared : null,
Expand All @@ -42,24 +57,24 @@ export function makeChild(parent, data) {
is_child: true,
parent_id: parent.id,
error: null,
};
} as unknown as T;
}

export function recalculateSplit(trans) {
export function recalculateSplit(trans: TransactionEntity) {
// Calculate the new total of split transactions and make sure
// that it equals the parent amount
const total = trans.subtransactions.reduce(
const total = (trans.subtransactions || []).reduce(
(acc, t) => acc + num(t.amount),
0,
);
return {
...trans,
error:
total === num(trans.amount) ? null : SplitTransactionError(total, trans),
};
} as TransactionEntityWithError;
}

function findParentIndex(transactions, idx) {
function findParentIndex(transactions: TransactionEntity[], idx: number) {
// This relies on transactions being sorted in a way where parents
// are always before children, which is enforced in the db layer.
// Walk backwards and find the last parent;
Expand All @@ -73,7 +88,7 @@ function findParentIndex(transactions, idx) {
return null;
}

function getSplit(transactions, parentIndex) {
function getSplit(transactions: TransactionEntity[], parentIndex: number) {
const split = [transactions[parentIndex]];
let curr = parentIndex + 1;
while (curr < transactions.length && transactions[curr].is_child) {
Expand All @@ -97,8 +112,8 @@ export function ungroupTransactions(transactions: TransactionEntity[]) {
}, []);
}

export function groupTransaction(split) {
return { ...split[0], subtransactions: split.slice(1) };
export function groupTransaction(split: TransactionEntity[]) {
return { ...split[0], subtransactions: split.slice(1) } as TransactionEntity;
}

export function ungroupTransaction(split: TransactionEntity | null) {
Expand All @@ -113,14 +128,19 @@ export function applyTransactionDiff(
diff: Parameters<typeof applyChanges>[0],
) {
return groupTransaction(
applyChanges(diff, ungroupTransaction(groupedTrans) || []),
applyChanges(
diff,
ungroupTransaction(groupedTrans) || [],
) as TransactionEntity[],
);
}

function replaceTransactions(
transactions: TransactionEntity[],
id: string,
func: (transaction: TransactionEntity) => TransactionEntity,
func: (
transaction: TransactionEntity,
) => TransactionEntity | TransactionEntityWithError | null,
) {
const idx = transactions.findIndex(t => t.id === id);
const trans = transactions[idx];
Expand All @@ -138,17 +158,15 @@ function replaceTransactions(
}

const split = getSplit(transactions, parentIndex);
let grouped: TransactionEntity | { id: string; _deleted: boolean } = func(
groupTransaction(split),
);
let grouped = func(groupTransaction(split));
const newSplit = ungroupTransaction(grouped);

let diff;
if (newSplit == null) {
// If everything was deleted, just delete the parent which will
// delete everything
diff = { deleted: [{ id: split[0].id }], updated: [] };
grouped = { id: split[0].id, _deleted: true };
grouped = { ...split[0], _deleted: true };
transactionsCopy.splice(parentIndex, split.length);
} else {
diff = diffItems(split, newSplit);
Expand All @@ -166,7 +184,10 @@ function replaceTransactions(

return {
data: transactionsCopy,
newTransaction: grouped || { id: trans.id, _deleted: true },
newTransaction: grouped || {
...trans,
_deleted: true,
},
diff: diffItems([trans], newTrans),
};
}
Expand Down Expand Up @@ -233,10 +254,10 @@ export function deleteTransaction(
} else if (trans.subtransactions?.length === 1) {
return {
...trans,
subtransactions: null,
subtransactions: undefined,
is_parent: false,
error: null,
};
} as TransactionEntityWithError;
} else {
const sub = trans.subtransactions?.filter(t => t.id !== id);
return recalculateSplit({ ...trans, subtransactions: sub });
Expand All @@ -261,14 +282,12 @@ export function splitTransaction(
is_parent: true,
error: num(trans.amount) === 0 ? null : SplitTransactionError(0, trans),
subtransactions: [makeChild(trans, { amount: 0, sort_order: -1 })],
};
} as TransactionEntityWithError;
});
}

export function realizeTempTransactions(transactions) {
let parent = transactions.find(t => !t.is_child);
parent = { ...parent, id: uuidv4() };

export function realizeTempTransactions(transactions: TransactionEntity[]) {
const parent = { ...transactions.find(t => !t.is_child), id: uuidv4() };
const children = transactions.filter(t => t.is_child);
return [
parent,
Expand Down
1 change: 1 addition & 0 deletions packages/loot-core/src/types/models/transaction.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { PayeeEntity } from './payee';
import type { ScheduleEntity } from './schedule';

export interface NewTransactionEntity {
id?: string;
is_parent?: boolean;
is_child?: boolean;
parent_id?: string;
Expand Down
6 changes: 6 additions & 0 deletions upcoming-release-notes/2388.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
category: Maintenance
authors: [twk3]
---

Update shared transaction module to strict typescript.

0 comments on commit 4b03446

Please sign in to comment.