Improve automation rules and sync error handling
- Add null safety checks in rule evaluation - Improve error messages in sync context with user-friendly formatting - Add browser test for automation rule application during import - Update test CSV data to better test rule matching - Add detailed error logging for failed transaction imports
This commit is contained in:
parent
a4ed4e73c1
commit
0b97570ffd
|
|
@ -30,6 +30,28 @@ interface SyncContextType {
|
|||
|
||||
const SyncContext = createContext<SyncContextType | undefined>(undefined);
|
||||
|
||||
function formatErrorMessage(error: string): string {
|
||||
if (error.includes('status code 5')) {
|
||||
return 'Server is temporarily unavailable. Please try again later.';
|
||||
}
|
||||
if (error.includes('status code 401') || error.includes('status code 403')) {
|
||||
return 'Your session has expired. Please refresh the page.';
|
||||
}
|
||||
if (error.includes('status code 4')) {
|
||||
return 'Something went wrong. Please try again.';
|
||||
}
|
||||
if (error.includes('Network Error') || error.includes('network')) {
|
||||
return 'Unable to connect. Check your internet connection.';
|
||||
}
|
||||
if (error.includes('timeout') || error.includes('Timeout')) {
|
||||
return 'The request took too long. Please try again.';
|
||||
}
|
||||
if (error === 'Sync already in progress') {
|
||||
return 'Sync is already running. Please wait.';
|
||||
}
|
||||
return 'Sync failed. Please try again.';
|
||||
}
|
||||
|
||||
const SYNC_INTERVAL = 5 * 60 * 1000;
|
||||
|
||||
interface SyncProviderProps {
|
||||
|
|
@ -81,7 +103,7 @@ export function SyncProvider({
|
|||
}
|
||||
|
||||
if (!isOnline) {
|
||||
setError('Cannot sync while offline');
|
||||
setError('Unable to sync while offline. Connect to the internet and try again.');
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -117,7 +139,10 @@ export function SyncProvider({
|
|||
];
|
||||
|
||||
if (allErrors.length > 0) {
|
||||
setError(allErrors.join(', '));
|
||||
const uniqueFormattedErrors = [
|
||||
...new Set(allErrors.map(formatErrorMessage)),
|
||||
];
|
||||
setError(uniqueFormattedErrors.join(' '));
|
||||
setSyncStatus('error');
|
||||
} else {
|
||||
setSyncStatus('success');
|
||||
|
|
@ -129,7 +154,9 @@ export function SyncProvider({
|
|||
}
|
||||
} catch (err) {
|
||||
console.error('Sync failed:', err);
|
||||
setError(err instanceof Error ? err.message : 'Unknown sync error');
|
||||
const errorMessage =
|
||||
err instanceof Error ? err.message : 'Unknown sync error';
|
||||
setError(formatErrorMessage(errorMessage));
|
||||
setSyncStatus('error');
|
||||
|
||||
setTimeout(() => {
|
||||
|
|
|
|||
|
|
@ -3,11 +3,12 @@ import type { Account, Bank } from '@/types/account';
|
|||
import type { AutomationRule } from '@/types/automation-rule';
|
||||
import type { Category } from '@/types/category';
|
||||
import type { DecryptedTransaction } from '@/types/transaction';
|
||||
import type { UUID } from '@/types/uuid';
|
||||
import jsonLogic from 'json-logic-js';
|
||||
|
||||
export interface RuleEvaluationResult {
|
||||
rule: AutomationRule;
|
||||
categoryId: number | null;
|
||||
categoryId: UUID | null;
|
||||
note: string | null;
|
||||
noteIv: string | null;
|
||||
}
|
||||
|
|
@ -175,7 +176,7 @@ export interface NewTransactionData {
|
|||
description: string;
|
||||
amount: number;
|
||||
transaction_date: string;
|
||||
account_id: string;
|
||||
account_id: UUID;
|
||||
notes?: string;
|
||||
}
|
||||
|
||||
|
|
@ -186,6 +187,11 @@ export function evaluateRulesForNewTransaction(
|
|||
accounts: Account[],
|
||||
banks: Bank[],
|
||||
): RuleEvaluationResult | null {
|
||||
if (!rules || !categories || !accounts || !banks) {
|
||||
consoleDebug('[Rule Engine] Missing required data for rule evaluation');
|
||||
return null;
|
||||
}
|
||||
|
||||
const sortedRules = [...rules].sort((a, b) => a.priority - b.priority);
|
||||
|
||||
const account = accounts.find((a) => a.id === transactionData.account_id);
|
||||
|
|
|
|||
|
|
@ -231,3 +231,84 @@ it('can navigate back through import steps', function () {
|
|||
->assertSee('Select the account to import transactions into')
|
||||
->assertNoJavascriptErrors();
|
||||
});
|
||||
|
||||
it('applies automation rules when importing transactions', function () {
|
||||
$user = User::factory()->create(['encryption_salt' => str_repeat('a', 24)]);
|
||||
|
||||
$groceriesCategory = Category::factory()->create([
|
||||
'user_id' => $user->id,
|
||||
'name' => 'Groceries',
|
||||
]);
|
||||
|
||||
$bank = Bank::factory()->create(['name' => 'My Bank']);
|
||||
$account = Account::factory()->create([
|
||||
'user_id' => $user->id,
|
||||
'bank_id' => $bank->id,
|
||||
'name' => 'My Checking',
|
||||
'name_iv' => str_repeat('b', 16),
|
||||
'currency_code' => 'USD',
|
||||
]);
|
||||
|
||||
\App\Models\AutomationRule::factory()->create([
|
||||
'user_id' => $user->id,
|
||||
'title' => 'Auto Categorize Groceries',
|
||||
'priority' => 1,
|
||||
'rules_json' => [
|
||||
'in' => [
|
||||
'walmart',
|
||||
['var' => 'description'],
|
||||
],
|
||||
],
|
||||
'action_category_id' => $groceriesCategory->id,
|
||||
'action_note' => null,
|
||||
'action_note_iv' => null,
|
||||
]);
|
||||
|
||||
actingAs($user);
|
||||
|
||||
$page = visit('/transactions');
|
||||
|
||||
$testFile = __DIR__.'/assets/test-transactions.csv';
|
||||
|
||||
$page->assertSee('Transactions')
|
||||
->click('button[aria-label="More actions"]')
|
||||
->wait(1)
|
||||
->press('ArrowDown')
|
||||
->wait(0.2)
|
||||
->press('Enter')
|
||||
->wait(1)
|
||||
->click('label')
|
||||
->wait(0.5)
|
||||
->click('Next')
|
||||
->waitFor('Drop your file here')
|
||||
->attach('input[type="file"]', $testFile)
|
||||
->wait(1)
|
||||
->click('Next')
|
||||
->wait(1)
|
||||
->click('#date-column')
|
||||
->wait(0.3)
|
||||
->click('Date')
|
||||
->click('#description-column')
|
||||
->wait(0.3)
|
||||
->click('Description')
|
||||
->click('#amount-column')
|
||||
->wait(0.3)
|
||||
->click('Amount')
|
||||
->wait(0.5)
|
||||
->click('Preview Transactions')
|
||||
->wait(2)
|
||||
->click('Import')
|
||||
->wait(3)
|
||||
->assertSee('imported successfully');
|
||||
|
||||
$page->wait(2);
|
||||
|
||||
expect(\App\Models\Transaction::where('user_id', $user->id)->count())->toBeGreaterThan(0);
|
||||
|
||||
$walmartTransaction = \App\Models\Transaction::where('user_id', $user->id)
|
||||
->whereRaw('LOWER(description) LIKE ?', ['%walmart%'])
|
||||
->first();
|
||||
|
||||
expect($walmartTransaction)->not->toBeNull();
|
||||
expect($walmartTransaction->category_id)->toBe($groceriesCategory->id);
|
||||
});
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 51 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 46 KiB |
|
|
@ -1,7 +1,8 @@
|
|||
Date,Description,Amount
|
||||
2024-01-15,Grocery Store Purchase,-50.25
|
||||
2024-01-15,Walmart Supercenter,-50.25
|
||||
2024-01-16,Coffee Shop,-5.50
|
||||
2024-01-17,Salary Deposit,2500.00
|
||||
2024-01-18,Electric Bill,-125.00
|
||||
2024-01-19,Restaurant Dinner,-45.75
|
||||
|
||||
|
||||
|
|
|
|||
|
Loading…
Reference in New Issue