mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-08-09 15:35:22 +02:00
Form UI improvements, ignore attribute locks on manual rule application
This commit is contained in:
parent
1016d4f7cf
commit
bc7b96863f
14 changed files with 47 additions and 32 deletions
|
@ -31,7 +31,7 @@ class AccountsController < ApplicationController
|
|||
family.sync_later
|
||||
end
|
||||
|
||||
redirect_to accounts_path
|
||||
redirect_back_or_to accounts_path
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -38,7 +38,7 @@ class RulesController < ApplicationController
|
|||
|
||||
def apply
|
||||
@rule.update!(active: true)
|
||||
@rule.apply_later
|
||||
@rule.apply_later(ignore_attribute_locks: true)
|
||||
redirect_back_or_to rules_path, notice: "#{@rule.resource_type.humanize} rule activated"
|
||||
end
|
||||
|
||||
|
|
|
@ -15,14 +15,17 @@ class TransactionCategoriesController < ApplicationController
|
|||
}
|
||||
end
|
||||
|
||||
transaction.lock_saved_attributes!
|
||||
@entry.lock_saved_attributes!
|
||||
|
||||
respond_to do |format|
|
||||
format.html { redirect_back_or_to transaction_path(@entry) }
|
||||
format.turbo_stream do
|
||||
render turbo_stream: [
|
||||
turbo_stream.replace(
|
||||
dom_id(@entry, :category_menu),
|
||||
dom_id(transaction, :category_menu),
|
||||
partial: "categories/menu",
|
||||
locals: { transaction: @entry.transaction }
|
||||
locals: { transaction: transaction }
|
||||
),
|
||||
*flash_notification_stream_items
|
||||
]
|
||||
|
|
|
@ -54,8 +54,8 @@ export default class extends Controller {
|
|||
|
||||
for (const operator of conditionFilter.operators) {
|
||||
const optionEl = document.createElement("option");
|
||||
optionEl.value = operator;
|
||||
optionEl.textContent = operator;
|
||||
optionEl.value = operator[1];
|
||||
optionEl.textContent = operator[0];
|
||||
this.operatorSelectTarget.appendChild(optionEl);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
class RuleJob < ApplicationJob
|
||||
queue_as :default
|
||||
|
||||
def perform(rule)
|
||||
rule.apply
|
||||
def perform(rule, ignore_attribute_locks: false)
|
||||
rule.apply(ignore_attribute_locks: ignore_attribute_locks)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -40,14 +40,14 @@ class Rule < ApplicationRecord
|
|||
matching_resources_scope.count
|
||||
end
|
||||
|
||||
def apply
|
||||
def apply(ignore_attribute_locks: false)
|
||||
actions.each do |action|
|
||||
action.apply(matching_resources_scope)
|
||||
action.apply(matching_resources_scope, ignore_attribute_locks: ignore_attribute_locks)
|
||||
end
|
||||
end
|
||||
|
||||
def apply_later
|
||||
RuleJob.perform_later(self)
|
||||
def apply_later(ignore_attribute_locks: false)
|
||||
RuleJob.perform_later(self, ignore_attribute_locks: ignore_attribute_locks)
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -4,9 +4,9 @@ class Rule::ConditionFilter
|
|||
TYPES = [ "text", "number", "select" ]
|
||||
|
||||
OPERATORS_MAP = {
|
||||
"text" => [ "like", "=" ],
|
||||
"number" => [ ">", ">=", "<", "<=", "=" ],
|
||||
"select" => [ "=" ]
|
||||
"text" => [ [ "Contains", "like" ], [ "Equal to", "=" ] ],
|
||||
"number" => [ [ "Greater than", ">" ], [ "Greater or equal to", ">=" ], [ "Less than", "<" ], [ "Less than or equal to", "<=" ], [ "Is equal to", "=" ] ],
|
||||
"select" => [ [ "Equal to", "=" ] ]
|
||||
}
|
||||
|
||||
def initialize(rule)
|
||||
|
@ -70,7 +70,7 @@ class Rule::ConditionFilter
|
|||
end
|
||||
|
||||
def sanitize_operator(operator)
|
||||
raise UnsupportedOperatorError, "Unsupported operator: #{operator} for type: #{type}" unless operators.include?(operator)
|
||||
raise UnsupportedOperatorError, "Unsupported operator: #{operator} for type: #{type}" unless operators.map(&:last).include?(operator)
|
||||
|
||||
if operator == "like"
|
||||
"ILIKE"
|
||||
|
|
|
@ -8,9 +8,11 @@
|
|||
<%= form.hidden_field :_destroy, value: false, data: { rule__actions_target: "destroyField" } %>
|
||||
|
||||
<div class="grow flex gap-2 items-center h-full">
|
||||
<%= form.select :action_type, rule.action_executors.map { |executor| [ executor.label, executor.key ] }, {}, data: { action: "rule--actions#handleActionTypeChange" } %>
|
||||
<div class="grow shrink-0">
|
||||
<%= form.select :action_type, rule.action_executors.map { |executor| [ executor.label, executor.key ] }, {}, data: { action: "rule--actions#handleActionTypeChange" } %>
|
||||
</div>
|
||||
|
||||
<%= tag.div class: class_names("flex items-center gap-2", "hidden" => !needs_value),
|
||||
<%= tag.div class: class_names("min-w-1/2 flex items-center gap-2", "hidden" => !needs_value),
|
||||
data: { rule__actions_target: "actionValue" } do %>
|
||||
<span class="font-medium uppercase text-xs">to</span>
|
||||
<%= form.select :value, action.options, {}, disabled: !needs_value %>
|
||||
|
|
|
@ -13,15 +13,21 @@
|
|||
<div class="grow flex gap-2 items-center h-full">
|
||||
<%= form.hidden_field :_destroy, value: false, data: { rule__conditions_target: "destroyField" } %>
|
||||
|
||||
<%= form.select :condition_type, rule.condition_filters.map { |filter| [ filter.label, filter.key ] }, {}, data: { action: "rule--conditions#handleConditionTypeChange" } %>
|
||||
<div class="w-2/5 shrink-0">
|
||||
<%= form.select :condition_type, rule.condition_filters.map { |filter| [ filter.label, filter.key ] }, {}, data: { action: "rule--conditions#handleConditionTypeChange" } %>
|
||||
</div>
|
||||
|
||||
<%= form.select :operator, condition.operators, { container_class: "w-fit min-w-16" }, data: { rule__conditions_target: "operatorSelect" } %>
|
||||
<%= form.select :operator, condition.operators, { container_class: "w-fit min-w-36" }, data: { rule__conditions_target: "operatorSelect" } %>
|
||||
|
||||
<div data-rule--conditions-target="filterValue">
|
||||
<div data-rule--conditions-target="filterValue" class="grow">
|
||||
<% if condition.filter.type == "select" %>
|
||||
<%= form.select :value, condition.options, {} %>
|
||||
<% else %>
|
||||
<%= form.text_field :value, placeholder: "Enter a value" %>
|
||||
<% if condition.filter.type == "number" %>
|
||||
<%= form.number_field :value, placeholder: "10" %>
|
||||
<% else %>
|
||||
<%= form.text_field :value, placeholder: "Enter a value" %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<%# locals: (rule:) %>
|
||||
|
||||
<%= styled_form_with model: rule, class: "space-y-4",
|
||||
<%= styled_form_with model: rule, class: "space-y-4 w-[550px]",
|
||||
data: { controller: "rules", rule_registry_value: rule.registry.to_json } do |f| %>
|
||||
|
||||
<%= f.hidden_field :resource_type, value: rule.resource_type %>
|
||||
|
@ -80,13 +80,13 @@
|
|||
<div class="space-y-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<%= f.radio_button :effective_date_enabled, false, checked: rule.effective_date.nil?, data: { action: "rules#clearEffectiveDate" } %>
|
||||
<%= f.label :effective_date_enabled_false, "To all past and future #{rule.resource_type}s" %>
|
||||
<%= f.label :effective_date_enabled_false, "To all past and future #{rule.resource_type}s", class: "text-sm" %>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<%= f.radio_button :effective_date_enabled, true, checked: rule.effective_date.present? %>
|
||||
<%= f.label :effective_date_enabled_true, "Starting from" %>
|
||||
<%= f.label :effective_date_enabled_true, "Starting from", class: "text-sm" %>
|
||||
</div>
|
||||
|
||||
<%= f.date_field :effective_date, container_class: "w-fit", data: { rules_target: "effectiveDateInput" } %>
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
<div class="flex justify-between items-center gap-4 bg-white shadow-border-xs rounded-md p-4">
|
||||
<div class="text-sm space-y-1.5">
|
||||
|
||||
<p class="flex items-center flex-wrap gap-1.5">
|
||||
<span class="px-2 py-1 border border-alpha-black-200 rounded-full">
|
||||
<%= rule.actions.first.executor.label %>
|
||||
|
|
|
@ -4,6 +4,11 @@
|
|||
<div class="flex items-center gap-5">
|
||||
<div class="flex items-center gap-2">
|
||||
<%= contextual_menu do %>
|
||||
<% if Rails.env.development? %>
|
||||
<%= button_to "Dev only: Sync all", sync_all_accounts_path, class: "btn btn--ghost w-full" %>
|
||||
<% end %>
|
||||
<%= contextual_menu_item "New rule", url: new_rule_path(resource_type: "transaction"), icon: "plus", turbo_frame: :modal %>
|
||||
<%= contextual_menu_item "Edit rules", url: rules_path, icon: "git-branch", turbo_frame: :_top %>
|
||||
<%= contextual_menu_modal_action_item t(".edit_categories"), categories_path, icon: "shapes", turbo_frame: :_top %>
|
||||
<%= contextual_menu_modal_action_item t(".edit_tags"), tags_path, icon: "tags", turbo_frame: :_top %>
|
||||
<%= contextual_menu_modal_action_item t(".edit_merchants"), family_merchants_path, icon: "store", turbo_frame: :_top %>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
class CreateDataEnrichments < ActiveRecord::Migration[7.2]
|
||||
class DataEnrichmentsAndLocks < ActiveRecord::Migration[7.2]
|
||||
def change
|
||||
create_table :data_enrichments, id: :uuid do |t|
|
||||
t.references :enrichable, polymorphic: true, null: false, type: :uuid
|
||||
|
@ -12,10 +12,10 @@ class CreateDataEnrichments < ActiveRecord::Migration[7.2]
|
|||
add_index :data_enrichments, [ :enrichable_id, :enrichable_type, :source, :attribute_name ], unique: true
|
||||
|
||||
# Entries
|
||||
add_column :account_entries, :locked_attributes, :jsonb, default: {}
|
||||
add_column :account_transactions, :locked_attributes, :jsonb, default: {}
|
||||
add_column :account_trades, :locked_attributes, :jsonb, default: {}
|
||||
add_column :account_valuations, :locked_attributes, :jsonb, default: {}
|
||||
add_column :entries, :locked_attributes, :jsonb, default: {}
|
||||
add_column :transactions, :locked_attributes, :jsonb, default: {}
|
||||
add_column :trades, :locked_attributes, :jsonb, default: {}
|
||||
add_column :valuations, :locked_attributes, :jsonb, default: {}
|
||||
|
||||
# Accounts
|
||||
add_column :accounts, :locked_attributes, :jsonb, default: {}
|
2
db/schema.rb
generated
2
db/schema.rb
generated
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[7.2].define(version: 2025_04_13_141446) do
|
||||
ActiveRecord::Schema[7.2].define(version: 2025_04_15_125256) do
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "pgcrypto"
|
||||
enable_extension "plpgsql"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue