1
0
Fork 0
mirror of https://github.com/maybe-finance/maybe.git synced 2025-07-25 08:09:38 +02:00

Allow inline account creation when importing CSV (#1291)

* Allow inline account creation when importing CSV

* Sanitize numeric inputs for CSV

* CSV import date validation

* Lint fix
This commit is contained in:
Zach Gollwitzer 2024-10-10 15:14:38 -04:00 committed by GitHub
parent 1746533842
commit cd9f20747c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 44 additions and 10 deletions

View file

@ -71,10 +71,10 @@ class Import < ApplicationRecord
{
account: row[account_col_label].to_s,
date: row[date_col_label].to_s,
qty: row[qty_col_label].to_s,
qty: sanitize_number(row[qty_col_label]).to_s,
ticker: row[ticker_col_label].to_s,
price: row[price_col_label].to_s,
amount: row[amount_col_label].to_s,
price: sanitize_number(row[price_col_label]).to_s,
amount: sanitize_number(row[amount_col_label]).to_s,
currency: (row[currency_col_label] || default_currency).to_s,
name: (row[name_col_label] || default_row_name).to_s,
category: row[category_col_label].to_s,
@ -113,6 +113,10 @@ class Import < ApplicationRecord
cleaned? && mappings.all?(&:valid?)
end
def requires_account?
family.accounts.empty? && mappings.accounts.where(key: "").any?
end
private
def import!
# no-op, subclasses can implement for customization of algorithm
@ -134,4 +138,9 @@ class Import < ApplicationRecord
converters: [ ->(str) { str&.strip } ]
)
end
def sanitize_number(value)
return "" if value.nil?
value.gsub(/[^\d.]/, "")
end
end

View file

@ -4,7 +4,7 @@ class Import::Row < ApplicationRecord
validates :amount, numericality: true, allow_blank: true
validates :currency, presence: true
validate :date_matches_user_format
validate :date_valid
validate :required_columns
validate :currency_is_valid
@ -54,13 +54,21 @@ class Import::Row < ApplicationRecord
end
end
def date_matches_user_format
def date_valid
return if date.blank?
parsed_date = Date.strptime(date, import.date_format) rescue nil
if parsed_date.nil?
errors.add(:date, "must exactly match the format: #{import.date_format}")
return
end
min_date = Account::Entry.min_supported_date
max_date = Date.current
if parsed_date < min_date || parsed_date > max_date
errors.add(:date, "must be between #{min_date} and #{max_date}")
end
end

View file

@ -23,7 +23,7 @@
<div class="bg-white border border-alpha-black-100 rounded-lg p-3 flex items-center justify-between">
<div class="flex items-center gap-2">
<%= lucide_icon "alert-triangle", class: "w-4 h-4 text-red-500" %>
<p class="text-red-500">You have errors in your data</p>
<p class="text-red-500 text-sm"><%= t(".errors_notice") %></p>
</div>
<div class="flex justify-center">

View file

@ -3,12 +3,20 @@
<% mappings = mapping_class.for_import(import) %>
<% is_last_step = step_idx == import.mapping_steps.count - 1 %>
<% if import.requires_account? && mapping_class == Import::AccountMapping %>
<div class="flex items-center justify-between p-4 mb-4 gap-4 text-gray-500 bg-red-100 border border-red-200 rounded-lg w-[650px]">
<%= tag.p t(".no_accounts"), class: "text-sm" %>
<%= link_to t(".create_account"), new_account_path, class: "btn btn--primary whitespace-nowrap", data: { turbo_frame: :modal } %>
</div>
<% end %>
<div class="space-y-4">
<div class="bg-gray-25 rounded-xl p-1 space-y-1 w-[650px]">
<div class="grid grid-cols-3 gap-2 text-xs font-medium text-gray-500 uppercase px-5 py-3">
<p>CSV <%= mapping_label(mapping_class) %></p>
<p>Maybe <%= mapping_label(mapping_class) %></p>
<p class="justify-self-end">Rows</p>
<p><%= t(".csv_mapping_label", mapping: mapping_label(mapping_class)) %></p>
<p><%= t(".maybe_mapping_label", mapping: mapping_label(mapping_class)) %></p>
<p class="justify-self-end"><%= t(".rows_label") %></p>
</div>
<div class="border border-alpha-black-25 rounded-md shadow-xs divide-y divide-alpha-black-100 text-sm">

View file

@ -28,6 +28,6 @@
</div>
</div>
<div class="max-w-screen-md mx-auto flex justify-center">
<div class="max-w-screen-md mx-auto flex flex-col items-center">
<%= render partial: "import/confirms/mappings", locals: { import: @import, mapping_class: step_mapping_class, step_idx: step_idx } %>
</div>

View file

@ -4,12 +4,21 @@ en:
cleans:
show:
description: Edit your data in the table below. Red cells are invalid.
errors_notice: You have errors in your data. Hover over the error to see details.
title: Clean your data
configurations:
show:
description: Select the columns that correspond to each field in your CSV.
title: Configure your import
confirms:
mappings:
create_account: Create account
csv_mapping_label: "%{mapping} in CSV"
maybe_mapping_label: "%{mapping} in Maybe"
no_accounts: You don't have any accounts yet. Please create an account that
we can use for (unassigned) rows in your CSV or go back to the Clean step
and provide an account name we can use.
rows_label: Rows
show:
account_mapping_description: Assign all of your imported file's accounts to
Maybe's existing accounts. You can also add new accounts or leave them