Private
Public Access
1
0

Add Re-sync button for SimpleFIN connections

Resets LastSyncAt on all linked accounts so the next sync
re-fetches the full 90-day transaction window. Duplicate
detection prevents re-importing existing transactions.

- POST /api/bank-sync/connections/{id}/reset-sync endpoint
- Re-sync button shown only on SimpleFIN connection cards

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Catherine Renelle
2026-02-09 19:29:15 -05:00
parent 8b2f5aad56
commit 5811588698
5 changed files with 53 additions and 0 deletions
+3
View File
@@ -46,6 +46,9 @@ export const bankSyncApi = {
updateLinkedAccount: (linkedAccountId: string, data: { accountId: string | null; isEnabled: boolean }) => updateLinkedAccount: (linkedAccountId: string, data: { accountId: string | null; isEnabled: boolean }) =>
api.put<LinkedAccountInfo>(`/bank-sync/linked-accounts/${linkedAccountId}`, data), api.put<LinkedAccountInfo>(`/bank-sync/linked-accounts/${linkedAccountId}`, data),
resetSync: (connectionId: string) =>
api.post(`/bank-sync/connections/${connectionId}/reset-sync`),
syncNow: (connectionId: string) => syncNow: (connectionId: string) =>
api.post(`/bank-sync/connections/${connectionId}/sync-now`), api.post(`/bank-sync/connections/${connectionId}/sync-now`),
@@ -184,6 +184,16 @@
> >
Sync Now Sync Now
</v-btn> </v-btn>
<v-btn
v-if="conn.provider === 'SimpleFIN'"
variant="tonal"
color="secondary"
prepend-icon="mdi-refresh"
:loading="resyncingConnections[conn.id]"
@click="resyncFromScratch(conn)"
>
Re-sync
</v-btn>
<v-btn <v-btn
variant="tonal" variant="tonal"
:color="conn.isActive ? 'warning' : 'success'" :color="conn.isActive ? 'warning' : 'success'"
@@ -530,6 +540,7 @@ const connections = ref<SyncConnection[]>([])
const accounts = ref<Account[]>([]) const accounts = ref<Account[]>([])
const syncIntervals = reactive<Record<string, number>>({}) const syncIntervals = reactive<Record<string, number>>({})
const syncingConnections = reactive<Record<string, boolean>>({}) const syncingConnections = reactive<Record<string, boolean>>({})
const resyncingConnections = reactive<Record<string, boolean>>({})
// Plaid credentials // Plaid credentials
const plaidCredentials = ref<PlaidCredentials>({ isConfigured: false, environment: 'sandbox' }) const plaidCredentials = ref<PlaidCredentials>({ isConfigured: false, environment: 'sandbox' })
@@ -814,6 +825,20 @@ async function syncNow(conn: SyncConnection) {
} }
} }
async function resyncFromScratch(conn: SyncConnection) {
resyncingConnections[conn.id] = true
try {
await bankSyncApi.resetSync(conn.id)
await bankSyncApi.syncNow(conn.id)
syncingConnections[conn.id] = true
} catch (err: any) {
errorMessage.value = err.response?.data?.error || 'Re-sync failed'
showError.value = true
} finally {
resyncingConnections[conn.id] = false
}
}
// Handle sync results via SignalR notification // Handle sync results via SignalR notification
const { notifications } = useNotifications() const { notifications } = useNotifications()
watch(notifications, (notifs) => { watch(notifications, (notifs) => {
@@ -74,6 +74,13 @@ public class BankSyncController : ControllerBase
public async Task<ActionResult<LinkedAccountResponse>> UpdateLinkedAccount(Guid id, UpdateLinkedAccountRequest request) public async Task<ActionResult<LinkedAccountResponse>> UpdateLinkedAccount(Guid id, UpdateLinkedAccountRequest request)
=> Ok(await _bankSyncService.UpdateLinkedAccountAsync(UserId, id, request)); => Ok(await _bankSyncService.UpdateLinkedAccountAsync(UserId, id, request));
[HttpPost("connections/{id}/reset-sync")]
public async Task<IActionResult> ResetSync(Guid id)
{
await _bankSyncService.ResetSyncDatesAsync(UserId, id);
return NoContent();
}
[HttpPost("connections/{id}/sync-now")] [HttpPost("connections/{id}/sync-now")]
public IActionResult SyncNow(Guid id) public IActionResult SyncNow(Guid id)
{ {
@@ -311,6 +311,23 @@ public class BankSyncService : IBankSyncService
// --- Sync --- // --- Sync ---
public async Task ResetSyncDatesAsync(Guid userId, Guid connectionId)
{
var connection = await _db.SyncConnections
.Include(c => c.LinkedAccounts)
.FirstOrDefaultAsync(c => c.Id == connectionId && c.UserId == userId)
?? throw new KeyNotFoundException("Connection not found");
foreach (var la in connection.LinkedAccounts)
{
la.LastSyncAt = null;
}
connection.LastSyncAt = null;
connection.UpdatedAt = DateTime.UtcNow;
await _db.SaveChangesAsync();
}
public async Task<SyncResultResponse> SyncNowAsync(Guid userId, Guid connectionId) public async Task<SyncResultResponse> SyncNowAsync(Guid userId, Guid connectionId)
{ {
var connection = await _db.SyncConnections var connection = await _db.SyncConnections
@@ -28,6 +28,7 @@ public interface IBankSyncService
// Sync // Sync
Task<SyncResultResponse> SyncNowAsync(Guid userId, Guid connectionId); Task<SyncResultResponse> SyncNowAsync(Guid userId, Guid connectionId);
Task ResetSyncDatesAsync(Guid userId, Guid connectionId);
Task<SyncResultResponse> SyncAllAsync(Guid userId); Task<SyncResultResponse> SyncAllAsync(Guid userId);
// Logs // Logs