diff --git a/packages/loot-core/src/server/transactions/transaction-rules.test.ts b/packages/loot-core/src/server/transactions/transaction-rules.test.ts index 949f16d8eb..099c501853 100644 --- a/packages/loot-core/src/server/transactions/transaction-rules.test.ts +++ b/packages/loot-core/src/server/transactions/transaction-rules.test.ts @@ -510,6 +510,36 @@ describe('Transaction rules', () => { // todo: isapprox }); + test('transactions can be queried by hasTags when tag starts with dollar', async () => { + await loadRules(); + const account = await db.insertAccount({ name: 'bank' }); + const payeeId = await db.insertPayee({ name: 'payee' }); + + await db.insertTransaction({ + id: '1', + date: '2020-10-01', + account, + payee: payeeId, + notes: 'Follow up #$Bug_Test issue', + amount: 123, + }); + + await db.insertTransaction({ + id: '2', + date: '2020-10-01', + account, + payee: payeeId, + notes: 'Follow up #Tag_1 issue', + amount: 123, + }); + + const transactions = await getMatchingTransactions([ + { field: 'notes', op: 'hasTags', value: '#$Bug_Test' }, + ]); + + expect(transactions.map(t => t.id)).toEqual(['1']); + }); + test('and sub expression builds $and condition', async () => { const conds = [{ field: 'category', op: 'is', value: null }]; const { filters } = conditionsToAQL(conds); diff --git a/packages/loot-core/src/server/transactions/transaction-rules.ts b/packages/loot-core/src/server/transactions/transaction-rules.ts index 6cbe5087ab..78ffb30af0 100644 --- a/packages/loot-core/src/server/transactions/transaction-rules.ts +++ b/packages/loot-core/src/server/transactions/transaction-rules.ts @@ -599,22 +599,26 @@ export function conditionsToAQL( } return { $or: values.map(v => apply(field, '$eq', v)) }; - case 'hasTags': + case 'hasTags': { const tagValues = []; + const seenTags = new Set(); for (const [_, tag] of value.matchAll(/(? t.tag === tag)) { + if (!seenTags.has(tag)) { + seenTags.add(tag); tagValues.push(tag); } } return { $and: tagValues.map(v => { - const regex = new RegExp( - `(?