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:
parent
3d9ff3ad2a
commit
45ae4a9737
71 changed files with 1657 additions and 117 deletions
|
@ -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
|
||||
|
|
102
app/controllers/imports_controller.rb
Normal file
102
app/controllers/imports_controller.rb
Normal 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
|
|
@ -1,4 +1,6 @@
|
|||
class PagesController < ApplicationController
|
||||
layout "with_sidebar"
|
||||
|
||||
include Filterable
|
||||
|
||||
def dashboard
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
class Settings::BillingsController < ApplicationController
|
||||
class Settings::BillingsController < SettingsController
|
||||
def edit
|
||||
end
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
class Settings::HostingsController < ApplicationController
|
||||
class Settings::HostingsController < SettingsController
|
||||
before_action :verify_hosting_mode
|
||||
|
||||
def show
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
class Settings::NotificationsController < ApplicationController
|
||||
class Settings::NotificationsController < SettingsController
|
||||
def edit
|
||||
end
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
class Settings::ProfilesController < ApplicationController
|
||||
class Settings::ProfilesController < SettingsController
|
||||
def show
|
||||
end
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
class Settings::SecuritiesController < ApplicationController
|
||||
class Settings::SecuritiesController < SettingsController
|
||||
def edit
|
||||
end
|
||||
|
||||
|
|
3
app/controllers/settings_controller.rb
Normal file
3
app/controllers/settings_controller.rb
Normal file
|
@ -0,0 +1,3 @@
|
|||
class SettingsController < ApplicationController
|
||||
layout "with_sidebar"
|
||||
end
|
|
@ -1,4 +1,6 @@
|
|||
class Transactions::Categories::DeletionsController < ApplicationController
|
||||
layout "with_sidebar"
|
||||
|
||||
before_action :set_category
|
||||
before_action :set_replacement_category, only: :create
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
class Transactions::MerchantsController < ApplicationController
|
||||
layout "with_sidebar"
|
||||
|
||||
before_action :set_merchant, only: %i[ edit update destroy ]
|
||||
|
||||
def index
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
class Transactions::RulesController < ApplicationController
|
||||
layout "with_sidebar"
|
||||
|
||||
def index
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
class TransactionsController < ApplicationController
|
||||
layout "with_sidebar"
|
||||
|
||||
before_action :set_transaction, only: %i[ show edit update destroy ]
|
||||
|
||||
def index
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue