diff --git a/resources/js/components/transactions/edit-transaction-dialog.tsx b/resources/js/components/transactions/edit-transaction-dialog.tsx index 95359f8b..ce84c833 100644 --- a/resources/js/components/transactions/edit-transaction-dialog.tsx +++ b/resources/js/components/transactions/edit-transaction-dialog.tsx @@ -163,7 +163,19 @@ export function EditTransactionDialog({ }; } - const result = evaluateRulesForNewTransaction( + const keyString = getStoredKey(); + if (!keyString) { + return { + categoryId: null, + notes: null, + notesIv: null, + ruleName: null, + }; + } + + const key = await importKey(keyString); + + const result = await evaluateRulesForNewTransaction( { description: description.trim(), amount: amount / 100, @@ -175,6 +187,7 @@ export function EditTransactionDialog({ categories, accounts, banks, + key, ); if (!result) { @@ -190,20 +203,16 @@ export function EditTransactionDialog({ const finalNotesIv = null; if (result.note && result.noteIv) { - const keyString = getStoredKey(); - if (keyString) { - const key = await importKey(keyString); - const decryptedRuleNote = await decrypt( - result.note, - key, - result.noteIv, - ); + const decryptedRuleNote = await decrypt( + result.note, + key, + result.noteIv, + ); - finalNotes = appendNoteIfNotPresent( - finalNotes || undefined, - decryptedRuleNote, - ); - } + finalNotes = appendNoteIfNotPresent( + finalNotes || undefined, + decryptedRuleNote, + ); } return { diff --git a/resources/js/components/transactions/import-transactions-drawer.tsx b/resources/js/components/transactions/import-transactions-drawer.tsx index cbd049fd..a4742d01 100644 --- a/resources/js/components/transactions/import-transactions-drawer.tsx +++ b/resources/js/components/transactions/import-transactions-drawer.tsx @@ -351,7 +351,7 @@ export function ImportTransactionsDrawer({ let notesIv: string | null = null; if (key && rules.length > 0) { - const ruleMatch = evaluateRulesForNewTransaction( + const ruleMatch = await evaluateRulesForNewTransaction( { description: transaction.description, amount: transaction.amount / 100, @@ -362,6 +362,7 @@ export function ImportTransactionsDrawer({ freshCategories, freshAccounts, freshBanks, + key, ); if (ruleMatch) { diff --git a/resources/js/hooks/use-re-evaluate-all-transactions.tsx b/resources/js/hooks/use-re-evaluate-all-transactions.tsx index 052b26c3..f066555f 100644 --- a/resources/js/hooks/use-re-evaluate-all-transactions.tsx +++ b/resources/js/hooks/use-re-evaluate-all-transactions.tsx @@ -65,12 +65,13 @@ export function useReEvaluateAllTransactions() { description: transaction.decryptedDescription, }); - const result = evaluateRules( + const result = await evaluateRules( transaction, rules, categories, accounts, banks, + key, ); if (result) { diff --git a/resources/js/lib/rule-engine.ts b/resources/js/lib/rule-engine.ts index e7a227b5..8598273b 100644 --- a/resources/js/lib/rule-engine.ts +++ b/resources/js/lib/rule-engine.ts @@ -1,3 +1,4 @@ +import { decrypt } from '@/lib/crypto'; import { consoleDebug } from '@/lib/debug'; import type { Account, Bank } from '@/types/account'; import type { AutomationRule } from '@/types/automation-rule'; @@ -56,12 +57,29 @@ function normalizeRuleJson(rulesJson: unknown): unknown { return rulesJson; } -export function prepareTransactionData( +async function decryptAccountName( + account: Account, + key: CryptoKey, +): Promise { + try { + return await decrypt(account.name, key, account.name_iv); + } catch (error) { + console.error('Failed to decrypt account name:', account.id, error); + return ''; + } +} + +const normalizeWhitespace = (str: string): string => { + return str.trim().replace(/\s+/g, ' '); +}; + +export async function prepareTransactionData( transaction: DecryptedTransaction, accounts: Account[], banks: Bank[], categories: Category[], -): TransactionData { + encryptionKey: CryptoKey, +): Promise { const account = accounts.find((a) => a.id === transaction.account_id); const bank = account?.bank?.id ? banks.find((b) => b.id === account.bank.id) @@ -70,9 +88,9 @@ export function prepareTransactionData( ? categories.find((c) => c.id === transaction.category_id) : null; - const normalizeWhitespace = (str: string): string => { - return str.trim().replace(/\s+/g, ' '); - }; + const accountName = account + ? await decryptAccountName(account, encryptionKey) + : ''; return { description: normalizeWhitespace( @@ -81,7 +99,7 @@ export function prepareTransactionData( amount: transaction.amount / 100, transaction_date: transaction.transaction_date, bank_name: bank?.name || '', - account_name: account?.name || '', + account_name: accountName.toLowerCase(), category: category?.name || null, notes: transaction.decryptedNotes ? normalizeWhitespace(transaction.decryptedNotes.toLowerCase()) @@ -89,20 +107,22 @@ export function prepareTransactionData( }; } -export function evaluateRules( +export async function evaluateRules( transaction: DecryptedTransaction, rules: AutomationRule[], categories: Category[], accounts: Account[], banks: Bank[], -): RuleEvaluationResult | null { + encryptionKey: CryptoKey, +): Promise { const sortedRules = [...rules].sort((a, b) => a.priority - b.priority); - const transactionData = prepareTransactionData( + const transactionData = await prepareTransactionData( transaction, accounts, banks, categories, + encryptionKey, ); consoleDebug('[Rule Engine] Transaction data prepared:', transactionData); @@ -150,22 +170,24 @@ export function evaluateRules( return null; } -export function evaluateRulesForTransactions( +export async function evaluateRulesForTransactions( transactions: DecryptedTransaction[], rules: AutomationRule[], categories: Category[], accounts: Account[], banks: Bank[], -): Map { + encryptionKey: CryptoKey, +): Promise> { const results = new Map(); for (const transaction of transactions) { - const result = evaluateRules( + const result = await evaluateRules( transaction, rules, categories, accounts, banks, + encryptionKey, ); if (result) { @@ -184,13 +206,14 @@ export interface NewTransactionData { notes?: string; } -export function evaluateRulesForNewTransaction( +export async function evaluateRulesForNewTransaction( transactionData: NewTransactionData, rules: AutomationRule[], categories: Category[], accounts: Account[], banks: Bank[], -): RuleEvaluationResult | null { + encryptionKey: CryptoKey, +): Promise { if (!rules || !categories || !accounts || !banks) { consoleDebug( '[Rule Engine] Missing required data for rule evaluation', @@ -215,9 +238,9 @@ export function evaluateRulesForNewTransaction( ? banks.find((b) => b.id === account.bank.id) : undefined; - const normalizeWhitespace = (str: string): string => { - return str.trim().replace(/\s+/g, ' '); - }; + const accountName = account + ? await decryptAccountName(account, encryptionKey) + : ''; const preparedData: TransactionData = { description: normalizeWhitespace( @@ -226,7 +249,7 @@ export function evaluateRulesForNewTransaction( amount: transactionData.amount, transaction_date: transactionData.transaction_date, bank_name: bank?.name || '', - account_name: account?.name || '', + account_name: accountName.toLowerCase(), category: null, notes: transactionData.notes ? normalizeWhitespace(transactionData.notes.toLowerCase()) diff --git a/resources/js/pages/transactions/index.tsx b/resources/js/pages/transactions/index.tsx index f065e4da..5f9f0c37 100644 --- a/resources/js/pages/transactions/index.tsx +++ b/resources/js/pages/transactions/index.tsx @@ -584,12 +584,13 @@ export default function Transactions({ categories, accounts, banks }: Props) { } consoleDebug('Evaluating rules against transaction...'); - const result = evaluateRules( + const result = await evaluateRules( transaction, rules, categories, accounts, banks, + key, ); consoleDebug('Rule evaluation result:', result); @@ -733,12 +734,13 @@ export default function Transactions({ categories, accounts, banks }: Props) { for (const transaction of selectedTransactions) { consoleDebug(`\nEvaluating transaction ${transaction.id}...`); - const result = evaluateRules( + const result = await evaluateRules( transaction, rules, categories, accounts, banks, + key, ); consoleDebug('Rule evaluation result:', result);