1
0
Fork 0
mirror of https://github.com/maybe-finance/maybe.git synced 2025-08-04 21:15:19 +02:00

CSV Transaction Imports (#708)

Introduces a basic CSV import module for bulk-importing account transactions.

Changes include:

- User can load a CSV
- User can configure the column mappings for a CSV
- Imported CSV shows invalid cells
- User can clean up their data directly in the UI
- User can see a preview of the import rows and confirm import
- Layout refactor + Import nav stepper
- System test stability improvements
This commit is contained in:
Zach Gollwitzer 2024-05-17 09:09:32 -04:00 committed by GitHub
parent 3d9ff3ad2a
commit 45ae4a9737
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
71 changed files with 1657 additions and 117 deletions

View file

@ -1,4 +1,6 @@
class AccountsController < ApplicationController
layout "with_sidebar"
include Filterable
before_action :set_account, only: %i[ show update destroy sync ]
@ -48,7 +50,7 @@ class AccountsController < ApplicationController
end
end
else
render "edit", status: :unprocessable_entity
render "show", status: :unprocessable_entity
end
end
@ -84,11 +86,11 @@ class AccountsController < ApplicationController
private
def set_account
@account = Current.family.accounts.find(params[:id])
end
def set_account
@account = Current.family.accounts.find(params[:id])
end
def account_params
params.require(:account).permit(:name, :accountable_type, :balance, :start_date, :currency, :subtype, :is_active)
end
def account_params
params.require(:account).permit(:name, :accountable_type, :balance, :start_date, :currency, :subtype, :is_active)
end
end

View file

@ -0,0 +1,102 @@
require "ostruct"
class ImportsController < ApplicationController
before_action :set_import, except: %i[ index new create ]
def index
@imports = Current.family.imports
render layout: "with_sidebar"
end
def new
@import = Import.new
end
def edit
end
def update
account = Current.family.accounts.find(params[:import][:account_id])
@import.update! account: account
redirect_to load_import_path(@import), notice: t(".import_updated")
end
def create
account = Current.family.accounts.find(params[:import][:account_id])
@import = Import.create!(account: account)
redirect_to load_import_path(@import), notice: t(".import_created")
end
def destroy
@import.destroy!
redirect_to imports_url, notice: t(".import_destroyed"), status: :see_other
end
def load
end
def load_csv
if @import.update(import_params)
redirect_to configure_import_path(@import), notice: t(".import_loaded")
else
flash.now[:error] = @import.errors.full_messages.to_sentence
render :load, status: :unprocessable_entity
end
end
def configure
unless @import.loaded?
redirect_to load_import_path(@import), alert: t(".invalid_csv")
end
end
def update_mappings
@import.update! import_params(@import.expected_fields.map(&:key))
redirect_to clean_import_path(@import), notice: t(".column_mappings_saved")
end
def clean
unless @import.loaded?
redirect_to load_import_path(@import), alert: t(".invalid_csv")
end
end
def update_csv
update_params = import_params[:csv_update]
@import.update_csv! \
row_idx: update_params[:row_idx],
col_idx: update_params[:col_idx],
value: update_params[:value]
render :clean
end
def confirm
unless @import.cleaned?
redirect_to clean_import_path(@import), alert: t(".invalid_data")
end
end
def publish
if @import.valid?
@import.publish_later
redirect_to imports_path, notice: t(".import_published")
else
flash.now[:error] = t(".invalid_data")
render :confirm, status: :unprocessable_entity
end
end
private
def set_import
@import = Current.family.imports.find(params[:id])
end
def import_params(permitted_mappings = nil)
params.require(:import).permit(:raw_csv_str, column_mappings: permitted_mappings, csv_update: [ :row_idx, :col_idx, :value ])
end
end

View file

@ -1,4 +1,6 @@
class PagesController < ApplicationController
layout "with_sidebar"
include Filterable
def dashboard

View file

@ -1,4 +1,4 @@
class Settings::BillingsController < ApplicationController
class Settings::BillingsController < SettingsController
def edit
end

View file

@ -1,4 +1,4 @@
class Settings::HostingsController < ApplicationController
class Settings::HostingsController < SettingsController
before_action :verify_hosting_mode
def show

View file

@ -1,4 +1,4 @@
class Settings::NotificationsController < ApplicationController
class Settings::NotificationsController < SettingsController
def edit
end

View file

@ -1,4 +1,4 @@
class Settings::PreferencesController < ApplicationController
class Settings::PreferencesController < SettingsController
def edit
end
@ -14,11 +14,12 @@ class Settings::PreferencesController < ApplicationController
redirect_to settings_preferences_path, notice: t(".success")
else
redirect_to settings_preferences_path, notice: t(".success")
render :edit, status: :unprocessable_entity
render :show, status: :unprocessable_entity
end
end
private
def preference_params
params.require(:user).permit(family_attributes: [ :id, :currency ])
end

View file

@ -1,4 +1,4 @@
class Settings::ProfilesController < ApplicationController
class Settings::ProfilesController < SettingsController
def show
end

View file

@ -1,4 +1,4 @@
class Settings::SecuritiesController < ApplicationController
class Settings::SecuritiesController < SettingsController
def edit
end

View file

@ -0,0 +1,3 @@
class SettingsController < ApplicationController
layout "with_sidebar"
end

View file

@ -1,4 +1,6 @@
class Transactions::Categories::DeletionsController < ApplicationController
layout "with_sidebar"
before_action :set_category
before_action :set_replacement_category, only: :create

View file

@ -1,4 +1,6 @@
class Transactions::CategoriesController < ApplicationController
layout "with_sidebar"
before_action :set_category, only: %i[ edit update ]
before_action :set_transaction, only: :create

View file

@ -1,4 +1,6 @@
class Transactions::MerchantsController < ApplicationController
layout "with_sidebar"
before_action :set_merchant, only: %i[ edit update destroy ]
def index

View file

@ -1,4 +1,6 @@
class Transactions::RulesController < ApplicationController
layout "with_sidebar"
def index
end
end

View file

@ -1,4 +1,6 @@
class TransactionsController < ApplicationController
layout "with_sidebar"
before_action :set_transaction, only: %i[ show edit update destroy ]
def index