diff --git a/app/models/account_import.rb b/app/models/account_import.rb index f5b790a09..02f19c05b 100644 --- a/app/models/account_import.rb +++ b/app/models/account_import.rb @@ -18,7 +18,19 @@ class AccountImport < Import account.save! manager = Account::OpeningBalanceManager.new(account) - result = manager.set_opening_balance(balance: row.amount.to_d) + + # Parse date if provided, otherwise use default + balance_date = if row.date.present? + begin + Date.strptime(row.date, date_format) + rescue ArgumentError => e + raise OpeningBalanceError, "Invalid date format for '#{row.date}': #{e.message}" + end + else + nil + end + + result = manager.set_opening_balance(balance: row.amount.to_d, date: balance_date) # Re-raise since we should never have an error here if result.error @@ -37,7 +49,7 @@ class AccountImport < Import end def column_keys - %i[entity_type name amount currency] + %i[entity_type name amount currency date] end def dry_run @@ -48,10 +60,10 @@ class AccountImport < Import def csv_template template = <<-CSV - Account type*,Name*,Balance*,Currency - Checking,Main Checking Account,1000.00,USD - Savings,Emergency Fund,5000.00,USD - Credit Card,Rewards Card,-500.00,USD + Account type*,Name*,Balance*,Currency,Balance Date + Checking,Main Checking Account,1000.00,USD,01/01/2024 + Savings,Emergency Fund,5000.00,USD,01/15/2024 + Credit Card,Rewards Card,-500.00,USD,02/01/2024 CSV CSV.parse(template, headers: true) diff --git a/app/views/import/configurations/_account_import.html.erb b/app/views/import/configurations/_account_import.html.erb index 28096ff9c..a8c4ec010 100644 --- a/app/views/import/configurations/_account_import.html.erb +++ b/app/views/import/configurations/_account_import.html.erb @@ -6,5 +6,13 @@ <%= form.select :amount_col_label, import.csv_headers, { include_blank: "Leave empty", label: "Balance" }, required: true %> <%= form.select :currency_col_label, import.csv_headers, { include_blank: "Default", label: "Currency" } %> +
+ <%= form.select :date_col_label, import.csv_headers, { include_blank: "Leave empty", label: "Balance Date" } %> + <%= form.select :date_format, + Family::DATE_FORMATS, + { label: "Date Format", prompt: "Select format" }, + required: @import.date_col_label.present? %> +
+ <%= form.submit "Apply configuration", disabled: import.complete? %> <% end %> diff --git a/test/models/account_import_test.rb b/test/models/account_import_test.rb index 29204c0fd..d433cab8f 100644 --- a/test/models/account_import_test.rb +++ b/test/models/account_import_test.rb @@ -63,8 +63,95 @@ class AccountImportTest < ActiveSupport::TestCase end end - test "column_keys returns expected keys" do - assert_equal %i[entity_type name amount currency], @import.column_keys + test "import creates accounts with explicit balance dates" do + import_csv = <<~CSV + type,name,amount,currency,date + depository,Main Checking,1000.00,USD,01/15/2024 + depository,Savings Account,5000.00,USD,02/01/2024 + CSV + + @import.update!( + raw_file_str: import_csv, + entity_type_col_label: "type", + name_col_label: "name", + amount_col_label: "amount", + currency_col_label: "currency", + date_col_label: "date", + date_format: "%m/%d/%Y" + ) + + @import.generate_rows_from_csv + + # Create mappings for account types + @import.mappings.create! key: "depository", value: "Depository", type: "Import::AccountTypeMapping" + + @import.reload + + # Perform the import + @import.publish + + # Check if import succeeded + if @import.failed? + fail "Import failed with error: #{@import.error}" + end + + assert_equal "complete", @import.status + + # Verify accounts were created with correct dates + accounts = @import.accounts.order(:name) + + checking_account = accounts.find { |a| a.name == "Main Checking" } + savings_account = accounts.find { |a| a.name == "Savings Account" } + + checking_valuation = checking_account.valuations.opening_anchor.first + savings_valuation = savings_account.valuations.opening_anchor.first + + assert_equal Date.parse("2024-01-15"), checking_valuation.entry.date + assert_equal Date.parse("2024-02-01"), savings_valuation.entry.date + end + + test "import creates accounts with default dates when date column not provided" do + import_csv = <<~CSV + type,name,amount,currency + depository,Main Checking,1000.00,USD + CSV + + @import.update!( + raw_file_str: import_csv, + entity_type_col_label: "type", + name_col_label: "name", + amount_col_label: "amount", + currency_col_label: "currency" + ) + + @import.generate_rows_from_csv + + # Create mappings for account types + @import.mappings.create! key: "depository", value: "Depository", type: "Import::AccountTypeMapping" + + @import.reload + + # Perform the import + @import.publish + + # Check if import succeeded + if @import.failed? + fail "Import failed with error: #{@import.error}" + end + + assert_equal "complete", @import.status + + # Verify account was created with default date (2 years ago or 1 day before oldest entry) + account = @import.accounts.first + valuation = account.valuations.opening_anchor.first + + # Default date should be 2 years ago when there are no other entries + expected_default_date = 2.years.ago.to_date + assert_equal expected_default_date, valuation.entry.date + end + + test "column_keys returns expected keys including date" do + assert_equal %i[entity_type name amount currency date], @import.column_keys end test "required_column_keys returns expected keys" do