From 2bbcbaee739290921654fd1bae67af4ccfccc00e Mon Sep 17 00:00:00 2001 From: gust0717 <36779504+gust0717@users.noreply.github.com> Date: Wed, 8 Apr 2026 11:45:00 -0300 Subject: [PATCH] Fixes query for tag starting in $ (#7324) * Fixes query for tag starting in $ * Update packages/loot-core/src/server/transactions/transaction-rules.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Apply coderabbitai suggestion --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../transactions/transaction-rules.test.ts | 30 +++++++++++++++++++ .../server/transactions/transaction-rules.ts | 16 ++++++---- upcoming-release-notes/7324.md | 6 ++++ 3 files changed, 46 insertions(+), 6 deletions(-) create mode 100644 upcoming-release-notes/7324.md 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( - `(?