mirror of
https://github.com/we-promise/sure.git
synced 2026-04-19 12:04:08 +00:00
Harden SimpleFIN sync: protect user data, fix stuck syncs, optimize API calls (#671)
* Implement entry protection flags for sync overwrites - Added `user_modified` and `import_locked` flags to `entries` table to prevent provider sync from overwriting user-edited and imported data. - Introduced backfill migration to mark existing entries based on conditions. - Enhanced sync and processing logic to respect protection flags, track skipped entries, and log detailed stats. - Updated UI to display skipped/protected entries and reasons in sync summaries. * Localize error details summary text and adjust `sync_account_later` method placement * Restored schema.rb --------- Co-authored-by: luckyPipewrench <luckypipewrench@proton.me>
This commit is contained in:
@@ -37,6 +37,37 @@
|
||||
<span><%= t("provider_sync_summary.transactions.updated", count: tx_updated) %></span>
|
||||
<span><%= t("provider_sync_summary.transactions.skipped", count: tx_skipped) %></span>
|
||||
</div>
|
||||
|
||||
<%# Protected entries detail - shown when entries were skipped due to protection %>
|
||||
<% if has_skipped_entries? %>
|
||||
<div class="mt-2">
|
||||
<div class="flex items-center gap-1">
|
||||
<%= helpers.icon "shield-check", size: "sm" %>
|
||||
<span class="text-secondary"><%= t("provider_sync_summary.transactions.protected", count: tx_skipped) %></span>
|
||||
</div>
|
||||
<% if skip_summary.any? %>
|
||||
<div class="text-xs text-secondary mt-1">
|
||||
<% skip_summary.each do |reason, count| %>
|
||||
<span class="mr-2"><%= t("provider_sync_summary.skip_reasons.#{reason}", default: reason.humanize) %>: <%= count %></span>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if skip_details.any? %>
|
||||
<details class="mt-1">
|
||||
<summary class="text-xs cursor-pointer text-secondary hover:text-primary">
|
||||
<%= t("provider_sync_summary.transactions.view_protected") %>
|
||||
</summary>
|
||||
<div class="mt-1 pl-2 border-l-2 border-surface-inset space-y-1">
|
||||
<% skip_details.each do |detail| %>
|
||||
<p class="text-xs text-secondary">
|
||||
<%= detail["name"] %> (<%= t("provider_sync_summary.skip_reasons.#{detail["reason"]}", default: detail["reason"].humanize) %>)
|
||||
</p>
|
||||
<% end %>
|
||||
</div>
|
||||
</details>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
@@ -62,6 +93,18 @@
|
||||
<% end %>
|
||||
<% if has_errors? %>
|
||||
<span class="text-destructive"><%= t("provider_sync_summary.health.errors", count: total_errors) %></span>
|
||||
<% if error_details.any? %>
|
||||
<details class="mt-1">
|
||||
<summary class="text-xs cursor-pointer text-secondary hover:text-primary"><%= t("provider_sync_summary.health.view_error_details") %></summary>
|
||||
<div class="mt-1 pl-2 border-l-2 border-destructive/30 space-y-1">
|
||||
<% error_details.each do |detail| %>
|
||||
<p class="text-xs text-destructive">
|
||||
<% if detail["name"].present? %><strong><%= detail["name"] %>:</strong> <% end %><%= detail["message"] %>
|
||||
</p>
|
||||
<% end %>
|
||||
</div>
|
||||
</details>
|
||||
<% end %>
|
||||
<% elsif import_started? %>
|
||||
<span class="text-success"><%= t("provider_sync_summary.health.errors", count: 0) %></span>
|
||||
<% else %>
|
||||
|
||||
@@ -68,6 +68,19 @@ class ProviderSyncSummary < ViewComponent::Base
|
||||
stats.key?("tx_seen") || stats.key?("tx_imported") || stats.key?("tx_updated")
|
||||
end
|
||||
|
||||
# Skip statistics (protected entries not overwritten)
|
||||
def has_skipped_entries?
|
||||
tx_skipped > 0
|
||||
end
|
||||
|
||||
def skip_summary
|
||||
stats["skip_summary"] || {}
|
||||
end
|
||||
|
||||
def skip_details
|
||||
stats["skip_details"] || []
|
||||
end
|
||||
|
||||
# Holdings statistics
|
||||
def holdings_found
|
||||
stats["holdings_found"].to_i
|
||||
@@ -127,6 +140,14 @@ class ProviderSyncSummary < ViewComponent::Base
|
||||
total_errors > 0
|
||||
end
|
||||
|
||||
def error_details
|
||||
stats["errors"] || []
|
||||
end
|
||||
|
||||
def error_buckets
|
||||
stats["error_buckets"] || {}
|
||||
end
|
||||
|
||||
# Stale pending transactions (auto-excluded)
|
||||
def stale_pending_excluded
|
||||
stats["stale_pending_excluded"].to_i
|
||||
|
||||
Reference in New Issue
Block a user