Skip to content
This repository was archived by the owner on Jul 27, 2025. It is now read-only.

Commit 536c82f

Browse files
authored
Feature: Add the ability to "revert" a CSV import (#1814)
* Allow reverting imports * Fix tests * Add currency column to all imports * Don't auto-enrich demo account
1 parent 60925bd commit 536c82f

File tree

17 files changed

+125
-6
lines changed

17 files changed

+125
-6
lines changed

app/controllers/imports_controller.rb

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
class ImportsController < ApplicationController
2-
before_action :set_import, only: %i[show publish destroy]
2+
before_action :set_import, only: %i[show publish destroy revert]
33

44
def publish
55
@import.publish_later
@@ -31,6 +31,11 @@ def show
3131
end
3232
end
3333

34+
def revert
35+
@import.revert_later
36+
redirect_to imports_path, notice: "Import is reverting in the background."
37+
end
38+
3439
def destroy
3540
@import.destroy
3641

app/jobs/revert_import_job.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
class RevertImportJob < ApplicationJob
2+
queue_as :latency_low
3+
4+
def perform(import)
5+
import.revert
6+
end
7+
end

app/models/demo/generator.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def reset_data!(family_names)
2424
puts "Data cleared"
2525

2626
family_names.each_with_index do |family_name, index|
27-
create_family_and_user!(family_name, "user#{index == 0 ? "" : index + 1}@maybe.local", data_enrichment_enabled: index == 0)
27+
create_family_and_user!(family_name, "user#{index == 0 ? "" : index + 1}@maybe.local")
2828
end
2929

3030
puts "Users reset"

app/models/import.rb

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,14 @@ class Import < ApplicationRecord
66

77
scope :ordered, -> { order(created_at: :desc) }
88

9-
enum :status, { pending: "pending", complete: "complete", importing: "importing", failed: "failed" }, validate: true
9+
enum :status, {
10+
pending: "pending",
11+
complete: "complete",
12+
importing: "importing",
13+
reverting: "reverting",
14+
revert_failed: "revert_failed",
15+
failed: "failed"
16+
}, validate: true, default: "pending"
1017

1118
validates :type, inclusion: { in: TYPES }
1219
validates :col_sep, inclusion: { in: [ ",", ";" ] }
@@ -35,6 +42,27 @@ def publish
3542
update! status: :failed, error: error.message
3643
end
3744

45+
def revert_later
46+
raise "Import is not revertable" unless revertable?
47+
48+
update! status: :reverting
49+
50+
RevertImportJob.perform_later(self)
51+
end
52+
53+
def revert
54+
Import.transaction do
55+
accounts.destroy_all
56+
entries.destroy_all
57+
end
58+
59+
family.sync
60+
61+
update! status: :pending
62+
rescue => error
63+
update! status: :revert_failed, error: error.message
64+
end
65+
3866
def csv_rows
3967
@csv_rows ||= parsed_csv
4068
end
@@ -113,6 +141,10 @@ def publishable?
113141
cleaned? && mappings.all?(&:valid?)
114142
end
115143

144+
def revertable?
145+
complete? || revert_failed?
146+
end
147+
116148
def has_unassigned_account?
117149
mappings.accounts.where(key: "").any?
118150
end

app/views/import/configurations/_account_import.html.erb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
<%= form.select :entity_type_col_label, import.csv_headers, { include_blank: "Leave empty", label: "Entity Type" } %>
55
<%= form.select :name_col_label, import.csv_headers, { include_blank: "Leave empty", label: "Name (optional)" } %>
66
<%= form.select :amount_col_label, import.csv_headers, { include_blank: "Leave empty", label: "Balance" } %>
7+
<%= form.select :currency_col_label, import.csv_headers, { include_blank: "Leave empty", label: "Currency" } %>
78

89
<%= form.submit "Apply configuration", class: "w-full btn btn--primary", disabled: import.complete? %>
910
<% end %>

app/views/import/configurations/_mint_import.html.erb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
<%= form.select :signage_convention, [["Incomes are negative", "inflows_negative"], ["Incomes are positive", "inflows_positive"]], { label: true }, disabled: import.complete? %>
1717
</div>
1818

19+
<%= form.select :currency_col_label, import.csv_headers, { include_blank: "Leave empty", label: "Currency" }, disabled: import.complete? %>
20+
1921
<%= form.select :account_col_label, import.csv_headers, { include_blank: "Leave empty", label: "Account (optional)" }, disabled: import.complete? %>
2022
<%= form.select :name_col_label, import.csv_headers, { include_blank: "Leave empty", label: "Name (optional)" }, disabled: import.complete? %>
2123
<%= form.select :category_col_label, import.csv_headers, { include_blank: "Leave empty", label: "Category (optional)" }, disabled: import.complete? %>

app/views/import/configurations/_trade_import.html.erb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
<%= form.select :signage_convention, [["Buys are positive qty", "inflows_positive"], ["Buys are negative qty", "inflows_negative"]], label: true %>
1212
</div>
1313

14+
<%= form.select :currency_col_label, import.csv_headers, { include_blank: "Leave empty", label: "Currency" } %>
15+
1416
<%= form.select :ticker_col_label, import.csv_headers, { include_blank: "Leave empty", label: "Ticker" } %>
1517
<%= form.select :price_col_label, import.csv_headers, { include_blank: "Leave empty", label: "Price" } %>
1618
<%= form.select :account_col_label, import.csv_headers, { include_blank: "Leave empty", label: "Account (optional)" } %>

app/views/import/configurations/_transaction_import.html.erb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
<%= form.select :signage_convention, [["Incomes are positive", "inflows_positive"], ["Incomes are negative", "inflows_negative"]], label: true %>
1212
</div>
1313

14+
<%= form.select :currency_col_label, import.csv_headers, { include_blank: "Leave empty", label: "Currency" } %>
15+
1416
<%= form.select :account_col_label, import.csv_headers, { include_blank: "Leave empty", label: "Account (optional)" } %>
1517
<%= form.select :name_col_label, import.csv_headers, { include_blank: "Leave empty", label: "Name (optional)" } %>
1618
<%= form.select :category_col_label, import.csv_headers, { include_blank: "Leave empty", label: "Category (optional)" } %>

app/views/imports/_import.html.erb

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,14 @@
1717
<span class="px-1 py text-xs rounded-full bg-red-500/5 text-red-500 border border-alpha-black-50">
1818
<%= t(".failed") %>
1919
</span>
20+
<% elsif import.reverting? %>
21+
<span class="px-1 py text-xs rounded-full bg-orange-500/5 text-orange-500 border border-alpha-black-50">
22+
<%= t(".reverting") %>
23+
</span>
24+
<% elsif import.revert_failed? %>
25+
<span class="px-1 py text-xs rounded-full bg-red-500/5 text-red-500 border border-alpha-black-50">
26+
<%= t(".revert_failed") %>
27+
</span>
2028
<% elsif import.complete? %>
2129
<span class="px-1 py text-xs rounded-full bg-green-500/5 text-green-500 border border-alpha-black-50">
2230
<%= t(".complete") %>
@@ -33,7 +41,16 @@
3341
<span><%= t(".view") %></span>
3442
<% end %>
3543

36-
<% unless import.complete? %>
44+
<% if import.complete? || import.revert_failed? %>
45+
<%= button_to revert_import_path(import),
46+
method: :put,
47+
class: "block w-full py-2 px-3 space-x-2 text-orange-600 hover:bg-orange-50 flex items-center rounded-lg",
48+
data: { turbo_confirm: true } do %>
49+
<%= lucide_icon "rotate-ccw", class: "w-5 h-5" %>
50+
51+
<span>Revert</span>
52+
<% end %>
53+
<% else %>
3754
<%= button_to import_path(import),
3855
method: :delete,
3956
class: "block w-full py-2 px-3 space-x-2 text-red-600 hover:bg-red-50 flex items-center rounded-lg",
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<%# locals: (import:) %>
2+
3+
<div class="h-full flex flex-col justify-center items-center">
4+
<div class="space-y-6 max-w-sm">
5+
<div class="mx-auto bg-red-500/5 h-8 w-8 rounded-full flex items-center justify-center">
6+
<%= lucide_icon "alert-octagon", class: "w-5 h-5 text-red-500" %>
7+
</div>
8+
9+
<div class="text-center space-y-2">
10+
<h1 class="font-medium text-gray-900 text-center text-3xl">Reverting import failed</h1>
11+
<p class="text-sm text-gray-500">Please try again or contact support.</p>
12+
</div>
13+
14+
<div>
15+
<%= button_to "Try again", revert_import_path(import), class: "btn btn--primary text-center w-full" %>
16+
</div>
17+
</div>
18+
</div>

0 commit comments

Comments
 (0)