mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-08-09 23:45:21 +02:00
Apply attribute locks on user edits
This commit is contained in:
parent
f8e140e39d
commit
77b914150f
17 changed files with 225 additions and 63 deletions
|
@ -3,9 +3,30 @@ class Account::TransactionsController < ApplicationController
|
|||
|
||||
permitted_entryable_attributes :id, :category_id, :merchant_id, { tag_ids: [] }
|
||||
|
||||
def create
|
||||
@entry = build_entry
|
||||
|
||||
if @entry.save
|
||||
@entry.sync_account_later
|
||||
@entry.lock_saved_attributes!
|
||||
@entry.account_transaction.lock!(:tag_ids) if @entry.account_transaction.tags.any?
|
||||
|
||||
flash[:notice] = t("account.entries.create.success")
|
||||
|
||||
respond_to do |format|
|
||||
format.html { redirect_back_or_to account_path(@entry.account) }
|
||||
format.turbo_stream { stream_redirect_back_or_to account_path(@entry.account) }
|
||||
end
|
||||
else
|
||||
render :new, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
if @entry.update(update_entry_params)
|
||||
@entry.sync_account_later
|
||||
@entry.lock_saved_attributes!
|
||||
@entry.account_transaction.lock!(:tag_ids) if @entry.account_transaction.tags.any?
|
||||
|
||||
transaction = @entry.account_transaction
|
||||
|
||||
|
|
|
@ -37,11 +37,15 @@ module AccountableResource
|
|||
|
||||
def create
|
||||
@account = Current.family.accounts.create_and_sync(account_params.except(:return_to))
|
||||
@account.lock_saved_attributes!
|
||||
|
||||
redirect_to account_params[:return_to].presence || @account, notice: t("accounts.create.success", type: accountable_type.name.underscore.humanize)
|
||||
end
|
||||
|
||||
def update
|
||||
@account.update_with_sync!(account_params.except(:return_to))
|
||||
@account.lock_saved_attributes!
|
||||
|
||||
redirect_back_or_to @account, notice: t("accounts.update.success", type: accountable_type.name.underscore.humanize)
|
||||
end
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@ module EntryableResource
|
|||
|
||||
if @entry.save
|
||||
@entry.sync_account_later
|
||||
@entry.lock_saved_attributes!
|
||||
|
||||
flash[:notice] = t("account.entries.create.success")
|
||||
|
||||
|
@ -47,6 +48,8 @@ module EntryableResource
|
|||
def update
|
||||
if @entry.update(update_entry_params)
|
||||
@entry.sync_account_later
|
||||
@entry.lock_saved_attributes!
|
||||
@entry.entryable.lock_saved_attributes!
|
||||
|
||||
respond_to do |format|
|
||||
format.html { redirect_back_or_to account_path(@entry.account), notice: t("account.entries.update.success") }
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
class Account < ApplicationRecord
|
||||
include Syncable, Monetizable, Chartable, Linkable, Convertible
|
||||
include Syncable, Monetizable, Chartable, Linkable, Convertible, Enrichable
|
||||
|
||||
validates :name, :balance, :currency, presence: true
|
||||
|
||||
|
@ -127,6 +127,11 @@ class Account < ApplicationRecord
|
|||
first_entry_date - 1.day
|
||||
end
|
||||
|
||||
def lock_saved_attributes!
|
||||
super
|
||||
accountable.lock_saved_attributes!
|
||||
end
|
||||
|
||||
private
|
||||
def sync_balances
|
||||
strategy = linked? ? :reverse : :forward
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
class Account::Entry < ApplicationRecord
|
||||
include Monetizable, LockableAttributes
|
||||
include Monetizable, Enrichable
|
||||
|
||||
monetize :amount
|
||||
|
||||
|
@ -34,6 +34,11 @@ class Account::Entry < ApplicationRecord
|
|||
)
|
||||
}
|
||||
|
||||
def lock_saved_attributes!
|
||||
super
|
||||
entryable.lock_saved_attributes!
|
||||
end
|
||||
|
||||
def sync_account_later
|
||||
sync_start_date = [ date_previously_was, date ].compact.min unless destroyed?
|
||||
account.sync_later(start_date: sync_start_date)
|
||||
|
@ -78,6 +83,10 @@ class Account::Entry < ApplicationRecord
|
|||
all.each do |entry|
|
||||
bulk_attributes[:entryable_attributes][:id] = entry.entryable_id if bulk_attributes[:entryable_attributes].present?
|
||||
entry.update! bulk_attributes
|
||||
|
||||
entry.lock_saved_attributes!
|
||||
entry.entryable.lock_saved_attributes!
|
||||
entry.entryable.lock!(:tag_ids) if entry.account_transaction? && entry.account_transaction.tags.any?
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -8,6 +8,8 @@ module Account::Entryable
|
|||
end
|
||||
|
||||
included do
|
||||
include Enrichable
|
||||
|
||||
has_one :entry, as: :entryable, touch: true
|
||||
|
||||
scope :with_entry, -> { joins(:entry) }
|
||||
|
|
|
@ -15,6 +15,21 @@ class Account::TradeBuilder
|
|||
buildable.save
|
||||
end
|
||||
|
||||
def lock_saved_attributes!
|
||||
if buildable.is_a?(Transfer)
|
||||
buildable.inflow_transaction.entry.lock_saved_attributes!
|
||||
buildable.outflow_transaction.entry.lock_saved_attributes!
|
||||
else
|
||||
buildable.lock_saved_attributes!
|
||||
end
|
||||
end
|
||||
|
||||
def entryable
|
||||
return nil if buildable.is_a?(Transfer)
|
||||
|
||||
buildable.entryable
|
||||
end
|
||||
|
||||
def errors
|
||||
buildable.errors
|
||||
end
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
class Account::Transaction < ApplicationRecord
|
||||
include Account::Entryable, Transferable, Ruleable, LockableAttributes
|
||||
include Account::Entryable, Transferable, Ruleable
|
||||
|
||||
belongs_to :category, optional: true
|
||||
belongs_to :merchant, optional: true
|
||||
|
|
|
@ -9,6 +9,8 @@ module Accountable
|
|||
end
|
||||
|
||||
included do
|
||||
include Enrichable
|
||||
|
||||
has_one :account, as: :accountable, touch: true
|
||||
end
|
||||
|
||||
|
|
51
app/models/concerns/enrichable.rb
Normal file
51
app/models/concerns/enrichable.rb
Normal file
|
@ -0,0 +1,51 @@
|
|||
# Enrichable models can have 1+ of their fields enriched by various
|
||||
# external sources (i.e. Plaid) or internal sources (i.e. Rules)
|
||||
#
|
||||
# This module defines how models should, lock, unlock, and edit attributes
|
||||
# based on the source of the edit. User edits always take highest precedence.
|
||||
#
|
||||
# For example:
|
||||
#
|
||||
# If a Rule tells us to set the category to "Groceries", but the user later overrides
|
||||
# a transaction with a category of "Food", we should not override the category again.
|
||||
#
|
||||
module Enrichable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
InvalidAttributeError = Class.new(StandardError)
|
||||
|
||||
included do
|
||||
scope :enrichable, ->(attrs) {
|
||||
attrs = Array(attrs).map(&:to_s)
|
||||
json_condition = attrs.each_with_object({}) { |attr, hash| hash[attr] = true }
|
||||
where.not(Arel.sql("#{table_name}.locked_attributes ?| array[:keys]"), keys: attrs)
|
||||
}
|
||||
end
|
||||
|
||||
def locked?(attr)
|
||||
locked_attributes[attr.to_s].present?
|
||||
end
|
||||
|
||||
def enrichable?(attr)
|
||||
!locked?(attr)
|
||||
end
|
||||
|
||||
def lock!(attr)
|
||||
update!(locked_attributes: locked_attributes.merge(attr.to_s => Time.current))
|
||||
end
|
||||
|
||||
def unlock!(attr)
|
||||
update!(locked_attributes: locked_attributes.except(attr.to_s))
|
||||
end
|
||||
|
||||
def lock_saved_attributes!
|
||||
saved_changes.keys.reject { |attr| ignored_enrichable_attributes.include?(attr) }.each do |attr|
|
||||
lock!(attr)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def ignored_enrichable_attributes
|
||||
%w[updated_at created_at]
|
||||
end
|
||||
end
|
|
@ -1,29 +0,0 @@
|
|||
# Marks model attributes as "locked" so Rules and other external data enrichment
|
||||
# sources know which attributes they can modify.
|
||||
module LockableAttributes
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
scope :attributes_unlocked, ->(attrs) {
|
||||
attrs = Array(attrs).map(&:to_s)
|
||||
json_condition = attrs.each_with_object({}) { |attr, hash| hash[attr] = true }
|
||||
where.not(Arel.sql("#{table_name}.locked_fields ?| array[:keys]"), keys: attrs)
|
||||
}
|
||||
end
|
||||
|
||||
def locked?(attr)
|
||||
locked_fields[attr.to_s] == true
|
||||
end
|
||||
|
||||
def lock!(attr)
|
||||
update!(locked_fields: locked_fields.merge(attr.to_s => true))
|
||||
end
|
||||
|
||||
def unlock!(attr)
|
||||
update!(locked_fields: locked_fields.except(attr.to_s))
|
||||
end
|
||||
|
||||
def attribute_unlocked(attr)
|
||||
!locked?(attr)
|
||||
end
|
||||
end
|
|
@ -29,7 +29,6 @@ class Transfer < ApplicationRecord
|
|||
currency: converted_amount.currency.iso_code,
|
||||
date: date,
|
||||
name: "Transfer from #{from_account.name}",
|
||||
entryable: Account::Transaction.new
|
||||
)
|
||||
),
|
||||
outflow_transaction: Account::Transaction.new(
|
||||
|
@ -38,7 +37,6 @@ class Transfer < ApplicationRecord
|
|||
currency: from_account.currency,
|
||||
date: date,
|
||||
name: "Transfer to #{to_account.name}",
|
||||
entryable: Account::Transaction.new
|
||||
)
|
||||
),
|
||||
status: "confirmed"
|
||||
|
|
|
@ -1,12 +1,32 @@
|
|||
class CreateDataEnrichments < ActiveRecord::Migration[7.2]
|
||||
def change
|
||||
create_table :data_enrichments, id: :uuid do |t|
|
||||
t.references :enrichable, polymorphic: true, null: false, type: :uuid
|
||||
t.string :source
|
||||
t.string :attribute_name
|
||||
t.jsonb :value
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
add_column :account_transactions, :locked_fields, :jsonb, default: {}
|
||||
add_column :account_entries, :locked_fields, :jsonb, default: {}
|
||||
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: {}
|
||||
|
||||
# Accounts
|
||||
add_column :accounts, :locked_attributes, :jsonb, default: {}
|
||||
add_column :depositories, :locked_attributes, :jsonb, default: {}
|
||||
add_column :investments, :locked_attributes, :jsonb, default: {}
|
||||
add_column :cryptos, :locked_attributes, :jsonb, default: {}
|
||||
add_column :properties, :locked_attributes, :jsonb, default: {}
|
||||
add_column :vehicles, :locked_attributes, :jsonb, default: {}
|
||||
add_column :other_assets, :locked_attributes, :jsonb, default: {}
|
||||
add_column :credit_cards, :locked_attributes, :jsonb, default: {}
|
||||
add_column :loans, :locked_attributes, :jsonb, default: {}
|
||||
add_column :other_liabilities, :locked_attributes, :jsonb, default: {}
|
||||
end
|
||||
end
|
||||
|
|
22
db/schema.rb
generated
22
db/schema.rb
generated
|
@ -47,7 +47,7 @@ ActiveRecord::Schema[7.2].define(version: 2025_04_11_191422) do
|
|||
t.string "plaid_id"
|
||||
t.datetime "enriched_at"
|
||||
t.string "enriched_name"
|
||||
t.jsonb "locked_fields", default: {}
|
||||
t.jsonb "locked_attributes", default: {}
|
||||
t.index ["account_id"], name: "index_account_entries_on_account_id"
|
||||
t.index ["import_id"], name: "index_account_entries_on_import_id"
|
||||
end
|
||||
|
@ -74,6 +74,7 @@ ActiveRecord::Schema[7.2].define(version: 2025_04_11_191422) do
|
|||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.string "currency"
|
||||
t.jsonb "locked_attributes", default: {}
|
||||
t.index ["security_id"], name: "index_account_trades_on_security_id"
|
||||
end
|
||||
|
||||
|
@ -82,7 +83,7 @@ ActiveRecord::Schema[7.2].define(version: 2025_04_11_191422) do
|
|||
t.datetime "updated_at", null: false
|
||||
t.uuid "category_id"
|
||||
t.uuid "merchant_id"
|
||||
t.jsonb "locked_fields", default: {}
|
||||
t.jsonb "locked_attributes", default: {}
|
||||
t.index ["category_id"], name: "index_account_transactions_on_category_id"
|
||||
t.index ["merchant_id"], name: "index_account_transactions_on_merchant_id"
|
||||
end
|
||||
|
@ -90,6 +91,7 @@ ActiveRecord::Schema[7.2].define(version: 2025_04_11_191422) do
|
|||
create_table "account_valuations", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.jsonb "locked_attributes", default: {}
|
||||
end
|
||||
|
||||
create_table "accounts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
||||
|
@ -109,6 +111,7 @@ ActiveRecord::Schema[7.2].define(version: 2025_04_11_191422) do
|
|||
t.boolean "scheduled_for_deletion", default: false
|
||||
t.datetime "last_synced_at"
|
||||
t.decimal "cash_balance", precision: 19, scale: 4, default: "0.0"
|
||||
t.jsonb "locked_attributes", default: {}
|
||||
t.index ["accountable_id", "accountable_type"], name: "index_accounts_on_accountable_id_and_accountable_type"
|
||||
t.index ["accountable_type"], name: "index_accounts_on_accountable_type"
|
||||
t.index ["family_id", "accountable_type"], name: "index_accounts_on_family_id_and_accountable_type"
|
||||
|
@ -217,22 +220,31 @@ ActiveRecord::Schema[7.2].define(version: 2025_04_11_191422) do
|
|||
t.decimal "apr", precision: 10, scale: 2
|
||||
t.date "expiration_date"
|
||||
t.decimal "annual_fee", precision: 10, scale: 2
|
||||
t.jsonb "locked_attributes", default: {}
|
||||
end
|
||||
|
||||
create_table "cryptos", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.jsonb "locked_attributes", default: {}
|
||||
end
|
||||
|
||||
create_table "data_enrichments", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
||||
t.string "enrichable_type", null: false
|
||||
t.uuid "enrichable_id", null: false
|
||||
t.string "source"
|
||||
t.string "attribute_name"
|
||||
t.jsonb "value"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["enrichable_id", "enrichable_type", "source", "attribute_name"], name: "idx_on_enrichable_id_enrichable_type_source_attribu_5be5f63e08", unique: true
|
||||
t.index ["enrichable_type", "enrichable_id"], name: "index_data_enrichments_on_enrichable"
|
||||
end
|
||||
|
||||
create_table "depositories", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.jsonb "locked_attributes", default: {}
|
||||
end
|
||||
|
||||
create_table "exchange_rates", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
||||
|
@ -354,6 +366,7 @@ ActiveRecord::Schema[7.2].define(version: 2025_04_11_191422) do
|
|||
create_table "investments", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.jsonb "locked_attributes", default: {}
|
||||
end
|
||||
|
||||
create_table "invitations", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
||||
|
@ -386,6 +399,7 @@ ActiveRecord::Schema[7.2].define(version: 2025_04_11_191422) do
|
|||
t.string "rate_type"
|
||||
t.decimal "interest_rate", precision: 10, scale: 3
|
||||
t.integer "term_months"
|
||||
t.jsonb "locked_attributes", default: {}
|
||||
end
|
||||
|
||||
create_table "merchants", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
||||
|
@ -422,11 +436,13 @@ ActiveRecord::Schema[7.2].define(version: 2025_04_11_191422) do
|
|||
create_table "other_assets", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.jsonb "locked_attributes", default: {}
|
||||
end
|
||||
|
||||
create_table "other_liabilities", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.jsonb "locked_attributes", default: {}
|
||||
end
|
||||
|
||||
create_table "plaid_accounts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
||||
|
@ -470,6 +486,7 @@ ActiveRecord::Schema[7.2].define(version: 2025_04_11_191422) do
|
|||
t.integer "year_built"
|
||||
t.integer "area_value"
|
||||
t.string "area_unit"
|
||||
t.jsonb "locked_attributes", default: {}
|
||||
end
|
||||
|
||||
create_table "rejected_transfers", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
||||
|
@ -678,6 +695,7 @@ ActiveRecord::Schema[7.2].define(version: 2025_04_11_191422) do
|
|||
t.string "mileage_unit"
|
||||
t.string "make"
|
||||
t.string "model"
|
||||
t.jsonb "locked_attributes", default: {}
|
||||
end
|
||||
|
||||
add_foreign_key "account_balances", "accounts", on_delete: :cascade
|
||||
|
|
|
@ -12,7 +12,7 @@ class Account::TradesControllerTest < ActionDispatch::IntegrationTest
|
|||
assert_no_difference [ "Account::Entry.count", "Account::Trade.count" ] do
|
||||
patch account_trade_url(@entry), params: {
|
||||
account_entry: {
|
||||
currency: "USD",
|
||||
currency: "EUR",
|
||||
entryable_attributes: {
|
||||
id: @entry.entryable_id,
|
||||
qty: 20,
|
||||
|
@ -24,11 +24,17 @@ class Account::TradesControllerTest < ActionDispatch::IntegrationTest
|
|||
|
||||
@entry.reload
|
||||
|
||||
assert @entry.locked?(:currency)
|
||||
assert @entry.locked?(:amount)
|
||||
|
||||
assert @entry.account_trade.locked?(:qty)
|
||||
assert @entry.account_trade.locked?(:price)
|
||||
|
||||
assert_enqueued_with job: SyncJob
|
||||
|
||||
assert_equal 20, @entry.account_trade.qty
|
||||
assert_equal 20, @entry.account_trade.price
|
||||
assert_equal "USD", @entry.currency
|
||||
assert_equal "EUR", @entry.currency
|
||||
|
||||
assert_redirected_to account_url(@entry.account)
|
||||
end
|
||||
|
@ -111,6 +117,9 @@ class Account::TradesControllerTest < ActionDispatch::IntegrationTest
|
|||
|
||||
created_entry = Account::Entry.order(created_at: :desc).first
|
||||
|
||||
assert created_entry.locked?(:currency)
|
||||
assert created_entry.locked?(:amount)
|
||||
|
||||
assert created_entry.amount.negative?
|
||||
assert_redirected_to @entry.account
|
||||
end
|
||||
|
@ -132,6 +141,12 @@ class Account::TradesControllerTest < ActionDispatch::IntegrationTest
|
|||
|
||||
created_entry = Account::Entry.order(created_at: :desc).first
|
||||
|
||||
assert created_entry.locked?(:currency)
|
||||
assert created_entry.locked?(:amount)
|
||||
|
||||
assert created_entry.account_trade.locked?(:qty)
|
||||
assert created_entry.account_trade.locked?(:price)
|
||||
|
||||
assert created_entry.amount.positive?
|
||||
assert created_entry.account_trade.qty.positive?
|
||||
assert_equal "Entry created", flash[:notice]
|
||||
|
@ -156,6 +171,12 @@ class Account::TradesControllerTest < ActionDispatch::IntegrationTest
|
|||
|
||||
created_entry = Account::Entry.order(created_at: :desc).first
|
||||
|
||||
assert created_entry.locked?(:currency)
|
||||
assert created_entry.locked?(:amount)
|
||||
|
||||
assert created_entry.account_trade.locked?(:qty)
|
||||
assert created_entry.account_trade.locked?(:price)
|
||||
|
||||
assert created_entry.amount.negative?
|
||||
assert created_entry.account_trade.qty.negative?
|
||||
assert_equal "Entry created", flash[:notice]
|
||||
|
|
|
@ -28,6 +28,16 @@ class Account::TransactionsControllerTest < ActionDispatch::IntegrationTest
|
|||
end
|
||||
|
||||
created_entry = Account::Entry.order(:created_at).last
|
||||
created_transaction = created_entry.account_transaction
|
||||
|
||||
assert created_entry.locked?(:name)
|
||||
assert created_entry.locked?(:date)
|
||||
assert created_entry.locked?(:amount)
|
||||
assert created_entry.locked?(:currency)
|
||||
|
||||
assert created_transaction.locked?(:tag_ids)
|
||||
assert created_transaction.locked?(:category_id)
|
||||
assert created_transaction.locked?(:merchant_id)
|
||||
|
||||
assert_redirected_to account_url(created_entry.account)
|
||||
assert_equal "Entry created", flash[:notice]
|
||||
|
@ -49,8 +59,8 @@ class Account::TransactionsControllerTest < ActionDispatch::IntegrationTest
|
|||
entryable_attributes: {
|
||||
id: @entry.entryable_id,
|
||||
tag_ids: [ Tag.first.id, Tag.second.id ],
|
||||
category_id: Category.first.id,
|
||||
merchant_id: Merchant.first.id
|
||||
category_id: categories(:subcategory).id,
|
||||
merchant_id: merchants(:netflix).id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -63,11 +73,20 @@ class Account::TransactionsControllerTest < ActionDispatch::IntegrationTest
|
|||
assert_equal "USD", @entry.currency
|
||||
assert_equal -100, @entry.amount
|
||||
assert_equal [ Tag.first.id, Tag.second.id ], @entry.entryable.tag_ids.sort
|
||||
assert_equal Category.first.id, @entry.entryable.category_id
|
||||
assert_equal Merchant.first.id, @entry.entryable.merchant_id
|
||||
assert_equal categories(:subcategory).id, @entry.entryable.category_id
|
||||
assert_equal merchants(:netflix).id, @entry.entryable.merchant_id
|
||||
assert_equal "test notes", @entry.notes
|
||||
assert_equal false, @entry.excluded
|
||||
|
||||
assert @entry.locked?(:name)
|
||||
assert @entry.locked?(:date)
|
||||
assert @entry.locked?(:amount)
|
||||
assert @entry.locked?(:notes)
|
||||
|
||||
assert @entry.account_transaction.locked?(:tag_ids)
|
||||
assert @entry.account_transaction.locked?(:category_id)
|
||||
assert @entry.account_transaction.locked?(:merchant_id)
|
||||
|
||||
assert_equal "Entry updated", flash[:notice]
|
||||
assert_redirected_to account_url(@entry.account)
|
||||
assert_enqueued_with(job: SyncJob)
|
||||
|
@ -90,15 +109,18 @@ class Account::TransactionsControllerTest < ActionDispatch::IntegrationTest
|
|||
end
|
||||
|
||||
test "can update many transactions at once" do
|
||||
transactions = @user.family.entries.account_transactions
|
||||
transaction_entries = @user.family.entries.account_transactions
|
||||
|
||||
new_category = @user.family.categories.create!(name: "New category")
|
||||
new_merchant = @user.family.merchants.create!(type: "FamilyMerchant", name: "New merchant")
|
||||
|
||||
assert_difference [ "Account::Entry.count", "Account::Transaction.count" ], 0 do
|
||||
post bulk_update_account_transactions_url, params: {
|
||||
bulk_update: {
|
||||
entry_ids: transactions.map(&:id),
|
||||
date: 1.day.ago.to_date,
|
||||
category_id: Category.second.id,
|
||||
merchant_id: Merchant.second.id,
|
||||
entry_ids: transaction_entries.map(&:id),
|
||||
date: 5.days.ago.to_date,
|
||||
category_id: new_category.id,
|
||||
merchant_id: new_merchant.id,
|
||||
tag_ids: [ Tag.first.id, Tag.second.id ],
|
||||
notes: "Updated note"
|
||||
}
|
||||
|
@ -106,14 +128,21 @@ class Account::TransactionsControllerTest < ActionDispatch::IntegrationTest
|
|||
end
|
||||
|
||||
assert_redirected_to transactions_url
|
||||
assert_equal "#{transactions.count} transactions updated", flash[:notice]
|
||||
assert_equal "#{transaction_entries.count} transactions updated", flash[:notice]
|
||||
|
||||
transactions.reload.each do |transaction|
|
||||
assert_equal 1.day.ago.to_date, transaction.date
|
||||
assert_equal Category.second, transaction.account_transaction.category
|
||||
assert_equal Merchant.second, transaction.account_transaction.merchant
|
||||
assert_equal "Updated note", transaction.notes
|
||||
assert_equal [ Tag.first.id, Tag.second.id ], transaction.entryable.tag_ids.sort
|
||||
transaction_entries.reload.each do |transaction_entry|
|
||||
assert_equal 5.days.ago.to_date, transaction_entry.date
|
||||
assert_equal new_category, transaction_entry.account_transaction.category
|
||||
assert_equal new_merchant, transaction_entry.account_transaction.merchant
|
||||
assert_equal "Updated note", transaction_entry.notes
|
||||
assert_equal [ Tag.first.id, Tag.second.id ], transaction_entry.entryable.tag_ids.sort
|
||||
|
||||
assert transaction_entry.locked?(:date)
|
||||
assert transaction_entry.locked?(:notes)
|
||||
|
||||
assert transaction_entry.account_transaction.locked?(:category_id)
|
||||
assert transaction_entry.account_transaction.locked?(:merchant_id)
|
||||
assert transaction_entry.account_transaction.locked?(:tag_ids)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
7
test/fixtures/data_enrichments.yml
vendored
7
test/fixtures/data_enrichments.yml
vendored
|
@ -1,7 +0,0 @@
|
|||
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
|
||||
|
||||
one:
|
||||
source: MyString
|
||||
|
||||
two:
|
||||
source: MyString
|
Loading…
Add table
Add a link
Reference in a new issue