Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/controllers/import/configurations_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ def import_params
:number_format,
:signage_convention,
:amount_type_strategy,
:amount_type_identifier_value,
:amount_type_inflow_value,
)
end
Expand Down
40 changes: 40 additions & 0 deletions app/javascript/controllers/import_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export default class extends Controller {
"signedAmountFieldset",
"customColumnFieldset",
"amountTypeValue",
"amountTypeInflowValue",
"amountTypeStrategySelect",
];

Expand All @@ -20,6 +21,9 @@ export default class extends Controller {
this.amountTypeColumnKeyValue
) {
this.#showAmountTypeValueTargets(this.amountTypeColumnKeyValue);
if (this.amountTypeValueTarget.querySelector("select")?.value) {
this.#showAmountTypeInflowValueTargets();
}
}
}

Expand All @@ -31,6 +35,9 @@ export default class extends Controller {

if (this.amountTypeColumnKeyValue) {
this.#showAmountTypeValueTargets(this.amountTypeColumnKeyValue);
if (this.amountTypeValueTarget.querySelector("select")?.value) {
this.#showAmountTypeInflowValueTargets();
}
}
}

Expand All @@ -45,6 +52,10 @@ export default class extends Controller {
this.#showAmountTypeValueTargets(amountTypeColumnKey);
}

handleAmountTypeIdentifierChange(event) {
this.#showAmountTypeInflowValueTargets();
}

#showAmountTypeValueTargets(amountTypeColumnKey) {
const selectableValues = this.#uniqueValuesForColumn(amountTypeColumnKey);

Expand Down Expand Up @@ -72,6 +83,29 @@ export default class extends Controller {
select.appendChild(fragment);
}

#showAmountTypeInflowValueTargets() {
// Called when amount_type_identifier_value changes
// Updates the displayed identifier value in the UI text and shows/hides the inflow value dropdown
const identifierValueSelect = this.amountTypeValueTarget.querySelector("select");
const selectedValue = identifierValueSelect.value;

if (!selectedValue) {
this.amountTypeInflowValueTarget.classList.add("hidden");
this.amountTypeInflowValueTarget.classList.remove("flex");
return;
}

// Show the inflow value dropdown
this.amountTypeInflowValueTarget.classList.remove("hidden");
this.amountTypeInflowValueTarget.classList.add("flex");

// Update the displayed identifier value in the text
const identifierSpan = this.amountTypeInflowValueTarget.querySelector("span.font-medium");
if (identifierSpan) {
identifierSpan.textContent = selectedValue;
}
}

#uniqueValuesForColumn(column) {
const colIdx = this.csvValue[0].indexOf(column);
const values = this.csvValue.slice(1).map((row) => row[colIdx]);
Expand Down Expand Up @@ -101,6 +135,12 @@ export default class extends Controller {
this.customColumnFieldsetTarget.classList.add("hidden");
this.signedAmountFieldsetTarget.classList.remove("hidden");

// Hide the inflow value targets when using signed amount strategy
this.amountTypeValueTarget.classList.add("hidden");
this.amountTypeValueTarget.classList.remove("flex");
this.amountTypeInflowValueTarget.classList.add("hidden");
this.amountTypeInflowValueTarget.classList.remove("flex");

// Remove required from custom column fields
this.customColumnFieldsetTarget
.querySelectorAll("select, input")
Expand Down
8 changes: 8 additions & 0 deletions app/models/import.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class Import < ApplicationRecord
validates :col_sep, inclusion: { in: SEPARATORS.map(&:last) }
validates :signage_convention, inclusion: { in: SIGNAGE_CONVENTIONS }, allow_nil: true
validates :number_format, presence: true, inclusion: { in: NUMBER_FORMATS.keys }
validate :custom_column_import_requires_identifier

has_many :rows, dependent: :destroy
has_many :mappings, dependent: :destroy
Expand Down Expand Up @@ -287,4 +288,11 @@ def sanitize_number(value)
def set_default_number_format
self.number_format ||= "1,234.56" # Default to US/UK format
end

def custom_column_import_requires_identifier
return unless amount_type_strategy == "custom_column"
return if amount_type_identifier_value.present? || amount_type_inflow_value.present?

errors.add(:base, "Custom column imports require either an identifier value or a legacy inflow indicator")
end
end
23 changes: 18 additions & 5 deletions app/models/import/row.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,25 @@ def apply_transaction_signage_convention(value)
if import.amount_type_strategy == "signed_amount"
value * (import.signage_convention == "inflows_positive" ? -1 : 1)
elsif import.amount_type_strategy == "custom_column"
inflow_value = import.amount_type_inflow_value

if entity_type == inflow_value
value * -1
legacy_identifier = import.amount_type_inflow_value
selected_identifier =
if import.amount_type_identifier_value.present?
import.amount_type_identifier_value
else
legacy_identifier
end

inflow_treatment =
if import.amount_type_inflow_value.in?(%w[inflows_positive inflows_negative])
import.amount_type_inflow_value
else
"inflows_positive"
end

if entity_type == selected_identifier
value * (inflow_treatment == "inflows_positive" ? -1 : 1)
else
value
value * (inflow_treatment == "inflows_positive" ? 1 : -1)
end
else
raise "Unknown amount type strategy for import: #{import.amount_type_strategy}"
Expand Down
16 changes: 13 additions & 3 deletions app/views/import/configurations/_transaction_import.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,21 @@
<div class="items-center gap-2 text-sm <%= @import.entity_type_col_label.nil? ? "hidden" : "flex" %>" data-import-target="amountTypeValue">
<span class="shrink-0 text-secondary">↪</span>
<span class="text-secondary">Set</span>
<%= form.select :amount_type_inflow_value,
<%= form.select :amount_type_identifier_value,
@import.selectable_amount_type_values,
{ prompt: "Select column", container_class: "w-48 px-3 py-1.5 border border-secondary rounded-md" },
{ prompt: "Select value", container_class: "w-48 px-3 py-1.5 border border-secondary rounded-md" },
required: @import.amount_type_strategy == "custom_column",
data: { action: "import#handleAmountTypeIdentifierChange" } %>
<span class="text-secondary">as identifier value</span>
</div>

<div class="items-center gap-2 text-sm <%= @import.amount_type_identifier_value.nil? ? "hidden" : "flex" %>" data-import-target="amountTypeInflowValue">
<span class="shrink-0 text-secondary">↪</span>
<span class="text-secondary">Treat "<span class="font-medium"><%= @import.amount_type_identifier_value %></span>" as</span>
<%= form.select :amount_type_inflow_value,
[["Income (inflow)", "inflows_positive"], ["Expense (outflow)", "inflows_negative"]],
{ prompt: "Select type", container_class: "w-48 px-3 py-1.5 border border-secondary rounded-md" },
required: @import.amount_type_strategy == "custom_column" %>
<span class="text-secondary">as "income" (inflow) value</span>
</div>
</div>
<% end %>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class AddAmountTypeIdentifierValueToImports < ActiveRecord::Migration[7.2]
def change
add_column :imports, :amount_type_identifier_value, :string
end
end
3 changes: 2 additions & 1 deletion test/models/transaction_import_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ class TransactionImportTest < ActiveSupport::TestCase
date_format: "%m/%d/%Y",
amount_col_label: "amount",
entity_type_col_label: "amount_type",
amount_type_inflow_value: "debit",
amount_type_identifier_value: "debit",
amount_type_inflow_value: "inflows_positive",
amount_type_strategy: "custom_column",
signage_convention: nil # Explicitly set to nil to prove this is not needed
)
Expand Down
Loading