mirror of
https://github.com/actualbudget/actual.git
synced 2026-03-09 11:42:54 -05:00
Compare commits
5 Commits
feat/auto-
...
copilot/fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
32498b0026 | ||
|
|
f5896fe770 | ||
|
|
dfb6612333 | ||
|
|
fcf42bef63 | ||
|
|
ee2ed81c0a |
@@ -106,6 +106,20 @@ export function useTransactionBatchActions() {
|
||||
}
|
||||
|
||||
const idSet = new Set(ids);
|
||||
// Track which split transactions have been processed to avoid
|
||||
// processing the same split multiple times when multiple members are selected
|
||||
const processedSplits = new Set<string>();
|
||||
|
||||
// Helper to find the parent ID of a split transaction
|
||||
const findSplitParentId = (trans: TransactionEntity): string => {
|
||||
if (trans.is_parent) {
|
||||
return trans.id;
|
||||
}
|
||||
if (trans.is_child && trans.parent_id) {
|
||||
return trans.parent_id;
|
||||
}
|
||||
return trans.id; // Non-split transaction
|
||||
};
|
||||
|
||||
transactionsToChange.forEach(trans => {
|
||||
if (name === 'cleared' && trans.reconciled) {
|
||||
@@ -120,6 +134,16 @@ export function useTransactionBatchActions() {
|
||||
return;
|
||||
}
|
||||
|
||||
// For split transactions, skip if we've already processed this split
|
||||
// to avoid duplicate updates when multiple members are selected
|
||||
if (trans.is_parent || trans.is_child) {
|
||||
const splitParentId = findSplitParentId(trans);
|
||||
if (processedSplits.has(splitParentId)) {
|
||||
return;
|
||||
}
|
||||
processedSplits.add(splitParentId);
|
||||
}
|
||||
|
||||
let valueToSet = value;
|
||||
|
||||
if (name === 'notes') {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
applyFindReplace,
|
||||
currencyToAmount,
|
||||
getNumberFormat,
|
||||
looselyParseAmount,
|
||||
@@ -229,3 +230,51 @@ describe('utility functions', () => {
|
||||
expect(stringToInteger('−3')).toBe(-3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('applyFindReplace', () => {
|
||||
test('basic string replacement works', () => {
|
||||
expect(applyFindReplace('hello world', 'world', 'universe', false)).toBe(
|
||||
'hello universe',
|
||||
);
|
||||
expect(applyFindReplace('test test test', 'test', 'demo', false)).toBe(
|
||||
'demo demo demo',
|
||||
);
|
||||
});
|
||||
|
||||
test('handles null and empty values', () => {
|
||||
expect(applyFindReplace(null, 'test', 'replace', false)).toBe('');
|
||||
expect(applyFindReplace(undefined, 'test', 'replace', false)).toBe('');
|
||||
expect(applyFindReplace('', 'test', 'replace', false)).toBe('');
|
||||
expect(applyFindReplace('hello', '', 'replace', false)).toBe('hello');
|
||||
});
|
||||
|
||||
test('regex replacement works', () => {
|
||||
expect(applyFindReplace('test123', '\\d+', 'NUM', true)).toBe('testNUM');
|
||||
expect(applyFindReplace('foo bar baz', 'b\\w+', 'word', true)).toBe(
|
||||
'foo word word',
|
||||
);
|
||||
});
|
||||
|
||||
test('handles invalid regex gracefully', () => {
|
||||
expect(applyFindReplace('test', '[invalid', 'replace', true)).toBe('test');
|
||||
});
|
||||
|
||||
test('case-sensitive replacement', () => {
|
||||
expect(applyFindReplace('Test test TEST', 'test', 'demo', false)).toBe(
|
||||
'Test demo TEST',
|
||||
);
|
||||
});
|
||||
|
||||
test('empty string replacement', () => {
|
||||
expect(applyFindReplace('test', 'test', '', false)).toBe('');
|
||||
expect(applyFindReplace('hello test world', 'test ', '', false)).toBe(
|
||||
'hello world',
|
||||
);
|
||||
});
|
||||
|
||||
test('replaces multiple occurrences', () => {
|
||||
expect(applyFindReplace('foo foo foo', 'foo', 'bar', false)).toBe(
|
||||
'bar bar bar',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user