mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-07-19 21:29:38 +02:00
Migrate valuations controller to new reconciliation methods
This commit is contained in:
parent
25f0c78c47
commit
d80cb9f812
19 changed files with 187 additions and 171 deletions
|
@ -1,6 +1,6 @@
|
||||||
<%= container do %>
|
<%= container do %>
|
||||||
<% if icon && (icon_position != :right) %>
|
<% if icon && (icon_position != :right) %>
|
||||||
<%= helpers.icon(icon, size: size, color: icon_color) %>
|
<%= helpers.icon(icon, size: size, color: icon_color, class: icon_classes) %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<% unless icon_only? %>
|
<% unless icon_only? %>
|
||||||
|
|
|
@ -5,7 +5,7 @@ class ButtonishComponent < ViewComponent::Base
|
||||||
icon_classes: "fg-inverse"
|
icon_classes: "fg-inverse"
|
||||||
},
|
},
|
||||||
secondary: {
|
secondary: {
|
||||||
container_classes: "text-secondary bg-gray-50 theme-dark:bg-gray-700 hover:bg-gray-100 theme-dark:hover:bg-gray-600 disabled:bg-gray-200 theme-dark:disabled:bg-gray-600",
|
container_classes: "text-primary bg-gray-50 theme-dark:bg-gray-700 hover:bg-gray-100 theme-dark:hover:bg-gray-600 disabled:bg-gray-200 theme-dark:disabled:bg-gray-600",
|
||||||
icon_classes: "fg-primary"
|
icon_classes: "fg-primary"
|
||||||
},
|
},
|
||||||
destructive: {
|
destructive: {
|
||||||
|
|
|
@ -46,30 +46,25 @@ module AccountableResource
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
# Handle balance update if provided
|
form = Account::OverviewForm.new(
|
||||||
if account_params[:balance].present?
|
account: @account,
|
||||||
result = @account.update_balance(balance: account_params[:balance], currency: account_params[:currency])
|
name: account_params[:name],
|
||||||
unless result.success?
|
currency: account_params[:currency],
|
||||||
@error_message = result.error_message
|
current_balance: account_params[:balance],
|
||||||
render :edit, status: :unprocessable_entity
|
current_cash_balance: @account.depository? ? account_params[:balance] : "0"
|
||||||
return
|
)
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Update remaining account attributes
|
result = form.save
|
||||||
update_params = account_params.except(:return_to, :balance, :currency, :tracking_start_date)
|
|
||||||
unless @account.update(update_params)
|
|
||||||
@error_message = @account.errors.full_messages.join(", ")
|
|
||||||
render :edit, status: :unprocessable_entity
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
@account.lock_saved_attributes!
|
|
||||||
|
|
||||||
|
if result.success?
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html { redirect_back_or_to account_path(@account), notice: accountable_type.name.underscore.humanize + " account updated" }
|
format.html { redirect_back_or_to account_path(@account), notice: accountable_type.name.underscore.humanize + " account updated" }
|
||||||
format.turbo_stream { stream_redirect_to account_path(@account), notice: accountable_type.name.underscore.humanize + " account updated" }
|
format.turbo_stream { stream_redirect_to account_path(@account), notice: accountable_type.name.underscore.humanize + " account updated" }
|
||||||
end
|
end
|
||||||
|
else
|
||||||
|
@error_message = result.error || "Unable to update account details."
|
||||||
|
render :edit, status: :unprocessable_entity
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
|
|
|
@ -9,4 +9,31 @@ class CreditCardsController < ApplicationController
|
||||||
:annual_fee,
|
:annual_fee,
|
||||||
:expiration_date
|
:expiration_date
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def update
|
||||||
|
form = Account::OverviewForm.new(
|
||||||
|
account: @account,
|
||||||
|
name: account_params[:name],
|
||||||
|
currency: account_params[:currency],
|
||||||
|
current_balance: account_params[:balance],
|
||||||
|
current_cash_balance: @account.depository? ? account_params[:balance] : "0"
|
||||||
|
)
|
||||||
|
|
||||||
|
result = form.save
|
||||||
|
|
||||||
|
if result.success?
|
||||||
|
# Update credit card-specific attributes
|
||||||
|
if account_params[:accountable_attributes].present?
|
||||||
|
@account.credit_card.update!(account_params[:accountable_attributes])
|
||||||
|
end
|
||||||
|
|
||||||
|
respond_to do |format|
|
||||||
|
format.html { redirect_back_or_to account_path(@account), notice: "Credit card account updated" }
|
||||||
|
format.turbo_stream { stream_redirect_to account_path(@account), notice: "Credit card account updated" }
|
||||||
|
end
|
||||||
|
else
|
||||||
|
@error_message = result.error || "Unable to update account details."
|
||||||
|
render :edit, status: :unprocessable_entity
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,4 +4,31 @@ class LoansController < ApplicationController
|
||||||
permitted_accountable_attributes(
|
permitted_accountable_attributes(
|
||||||
:id, :rate_type, :interest_rate, :term_months, :initial_balance
|
:id, :rate_type, :interest_rate, :term_months, :initial_balance
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def update
|
||||||
|
form = Account::OverviewForm.new(
|
||||||
|
account: @account,
|
||||||
|
name: account_params[:name],
|
||||||
|
currency: account_params[:currency],
|
||||||
|
current_balance: account_params[:balance],
|
||||||
|
current_cash_balance: @account.depository? ? account_params[:balance] : "0"
|
||||||
|
)
|
||||||
|
|
||||||
|
result = form.save
|
||||||
|
|
||||||
|
if result.success?
|
||||||
|
# Update loan-specific attributes
|
||||||
|
if account_params[:accountable_attributes].present?
|
||||||
|
@account.loan.update!(account_params[:accountable_attributes])
|
||||||
|
end
|
||||||
|
|
||||||
|
respond_to do |format|
|
||||||
|
format.html { redirect_back_or_to account_path(@account), notice: "Loan account updated" }
|
||||||
|
format.turbo_stream { stream_redirect_to account_path(@account), notice: "Loan account updated" }
|
||||||
|
end
|
||||||
|
else
|
||||||
|
@error_message = result.error || "Unable to update account details."
|
||||||
|
render :edit, status: :unprocessable_entity
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,39 +4,40 @@ class ValuationsController < ApplicationController
|
||||||
def create
|
def create
|
||||||
account = Current.family.accounts.find(params.dig(:entry, :account_id))
|
account = Current.family.accounts.find(params.dig(:entry, :account_id))
|
||||||
|
|
||||||
result = account.update_balance(
|
if entry_params[:date].to_date == Date.current
|
||||||
balance: entry_params[:amount],
|
account.update_current_balance!(balance: entry_params[:amount].to_d)
|
||||||
date: entry_params[:date],
|
else
|
||||||
currency: entry_params[:currency],
|
account.reconcile_balance!(
|
||||||
notes: entry_params[:notes]
|
balance: entry_params[:amount].to_d,
|
||||||
|
date: entry_params[:date].to_date
|
||||||
)
|
)
|
||||||
|
end
|
||||||
|
|
||||||
if result.success?
|
account.sync_later
|
||||||
@success_message = result.updated? ? "Balance updated" : "No changes made. Account is already up to date."
|
|
||||||
|
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html { redirect_back_or_to account_path(account), notice: @success_message }
|
format.html { redirect_back_or_to account_path(account), notice: "Account value updated" }
|
||||||
format.turbo_stream { stream_redirect_back_or_to(account_path(account), notice: @success_message) }
|
format.turbo_stream { stream_redirect_back_or_to(account_path(account), notice: "Account value updated") }
|
||||||
end
|
|
||||||
else
|
|
||||||
@error_message = result.error_message
|
|
||||||
render :new, status: :unprocessable_entity
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
result = @entry.account.update_balance(
|
# ActiveRecord::Base.transaction do
|
||||||
date: @entry.date,
|
@entry.account.reconcile_balance!(
|
||||||
balance: entry_params[:amount],
|
balance: entry_params[:amount].to_d,
|
||||||
currency: entry_params[:currency],
|
date: entry_params[:date].to_date
|
||||||
notes: entry_params[:notes]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if result.success?
|
if entry_params[:notes].present?
|
||||||
|
@entry.update!(notes: entry_params[:notes])
|
||||||
|
end
|
||||||
|
|
||||||
|
@entry.account.sync_later
|
||||||
|
|
||||||
@entry.reload
|
@entry.reload
|
||||||
|
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html { redirect_back_or_to account_path(@entry.account), notice: result.updated? ? "Balance updated" : "No changes made. Account is already up to date." }
|
format.html { redirect_back_or_to account_path(@entry.account), notice: "Account value updated" }
|
||||||
format.turbo_stream do
|
format.turbo_stream do
|
||||||
render turbo_stream: [
|
render turbo_stream: [
|
||||||
turbo_stream.replace(
|
turbo_stream.replace(
|
||||||
|
@ -48,15 +49,10 @@ class ValuationsController < ApplicationController
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
|
||||||
@error_message = result.error_message
|
|
||||||
render :show, status: :unprocessable_entity
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def entry_params
|
def entry_params
|
||||||
params.require(:entry)
|
params.require(:entry).permit(:date, :amount, :notes)
|
||||||
.permit(:date, :amount, :currency, :notes)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -14,8 +14,6 @@ class Account < ApplicationRecord
|
||||||
has_many :holdings, dependent: :destroy
|
has_many :holdings, dependent: :destroy
|
||||||
has_many :balances, dependent: :destroy
|
has_many :balances, dependent: :destroy
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
enum :classification, { asset: "asset", liability: "liability" }, validate: { allow_nil: true }
|
enum :classification, { asset: "asset", liability: "liability" }, validate: { allow_nil: true }
|
||||||
|
|
||||||
scope :visible, -> { where(status: [ "draft", "active" ]) }
|
scope :visible, -> { where(status: [ "draft", "active" ]) }
|
||||||
|
@ -120,11 +118,6 @@ class Account < ApplicationRecord
|
||||||
.order(amount: :desc)
|
.order(amount: :desc)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
def update_balance(balance:, date: Date.current, currency: nil, notes: nil)
|
|
||||||
Account::BalanceUpdater.new(self, balance:, currency:, date:, notes:).update
|
|
||||||
end
|
|
||||||
|
|
||||||
def update_currency!(new_currency)
|
def update_currency!(new_currency)
|
||||||
raise "Currency cannot be changed" if linked?
|
raise "Currency cannot be changed" if linked?
|
||||||
|
|
||||||
|
@ -134,7 +127,6 @@ class Account < ApplicationRecord
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
def start_date
|
def start_date
|
||||||
first_entry_date = entries.minimum(:date) || Date.current
|
first_entry_date = entries.minimum(:date) || Date.current
|
||||||
first_entry_date - 1.day
|
first_entry_date - 1.day
|
||||||
|
|
|
@ -1,55 +0,0 @@
|
||||||
class Account::BalanceUpdater
|
|
||||||
def initialize(account, balance:, currency: nil, date: Date.current, notes: nil)
|
|
||||||
@account = account
|
|
||||||
@balance = balance.to_d
|
|
||||||
@currency = currency
|
|
||||||
@date = date.to_date
|
|
||||||
@notes = notes
|
|
||||||
end
|
|
||||||
|
|
||||||
def update
|
|
||||||
return Result.new(success?: true, updated?: false) unless requires_update?
|
|
||||||
|
|
||||||
Account.transaction do
|
|
||||||
if date == Date.current
|
|
||||||
account.balance = balance
|
|
||||||
account.currency = currency if currency.present?
|
|
||||||
account.save!
|
|
||||||
end
|
|
||||||
|
|
||||||
valuation_entry = account.entries.valuations.find_or_initialize_by(date: date) do |entry|
|
|
||||||
entry.entryable = Valuation.new(
|
|
||||||
kind: "recon",
|
|
||||||
balance: balance,
|
|
||||||
cash_balance: balance
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
valuation_entry.amount = balance
|
|
||||||
valuation_entry.currency = currency if currency.present?
|
|
||||||
valuation_entry.name = valuation_name(valuation_entry, account)
|
|
||||||
valuation_entry.notes = notes if notes.present?
|
|
||||||
valuation_entry.save!
|
|
||||||
end
|
|
||||||
|
|
||||||
account.sync_later
|
|
||||||
|
|
||||||
Result.new(success?: true, updated?: true)
|
|
||||||
rescue => e
|
|
||||||
message = Rails.env.development? ? e.message : "Unable to update account values. Please try again."
|
|
||||||
Result.new(success?: false, updated?: false, error_message: message)
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
attr_reader :account, :balance, :currency, :date, :notes
|
|
||||||
|
|
||||||
Result = Struct.new(:success?, :updated?, :error_message)
|
|
||||||
|
|
||||||
def requires_update?
|
|
||||||
date != Date.current || account.balance != balance || account.currency != currency
|
|
||||||
end
|
|
||||||
|
|
||||||
def valuation_name(valuation_entry, account)
|
|
||||||
Valuation::Name.new(valuation_entry.entryable.kind, account.accountable_type).to_s
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -42,6 +42,7 @@ class Account::OverviewForm
|
||||||
# Update name if provided
|
# Update name if provided
|
||||||
if name.present? && name != account.name
|
if name.present? && name != account.name
|
||||||
account.update!(name: name)
|
account.update!(name: name)
|
||||||
|
account.lock_attr!(:name)
|
||||||
updated = true
|
updated = true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -29,18 +29,26 @@ module Account::Reconcileable
|
||||||
@opening_date ||= opening_anchor_valuation&.entry&.date
|
@opening_date ||= opening_anchor_valuation&.entry&.date
|
||||||
end
|
end
|
||||||
|
|
||||||
def reconcile_balance!(balance:, cash_balance:, date:)
|
def reconcile_balance!(balance:, cash_balance: nil, date: nil)
|
||||||
raise InvalidBalanceError, "Cash balance cannot exceed balance" if cash_balance > balance
|
raise InvalidBalanceError, "Cash balance cannot exceed balance" if cash_balance.present? && cash_balance > balance
|
||||||
raise InvalidBalanceError, "Linked accounts cannot be reconciled" if linked?
|
raise InvalidBalanceError, "Linked accounts cannot be reconciled" if linked?
|
||||||
|
|
||||||
|
derived_cash_balance = cash_balance.present? ? cash_balance : choose_cash_balance_from_balance(balance)
|
||||||
|
|
||||||
|
if date.nil?
|
||||||
|
update_current_balance!(balance:, cash_balance: derived_cash_balance)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
existing_valuation = valuations.joins(:entry).where(kind: "recon", entry: { date: date }).first
|
existing_valuation = valuations.joins(:entry).where(kind: "recon", entry: { date: date }).first
|
||||||
|
|
||||||
transaction do
|
transaction do
|
||||||
if existing_valuation.present?
|
if existing_valuation.present?
|
||||||
existing_valuation.update!(
|
existing_valuation.update!(
|
||||||
balance: balance,
|
balance: balance,
|
||||||
cash_balance: cash_balance
|
cash_balance: derived_cash_balance
|
||||||
)
|
)
|
||||||
|
existing_valuation.entry.update!(amount: balance)
|
||||||
else
|
else
|
||||||
entries.create!(
|
entries.create!(
|
||||||
date: date,
|
date: date,
|
||||||
|
@ -50,43 +58,36 @@ module Account::Reconcileable
|
||||||
entryable: Valuation.new(
|
entryable: Valuation.new(
|
||||||
kind: "recon",
|
kind: "recon",
|
||||||
balance: balance,
|
balance: balance,
|
||||||
cash_balance: cash_balance
|
cash_balance: derived_cash_balance
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Update cached balance fields on account when reconciling for current date
|
# Update cached balance fields on account when reconciling for current date
|
||||||
if date == Date.current
|
if date == Date.current
|
||||||
update!(balance: balance, cash_balance: cash_balance)
|
update!(balance: balance, cash_balance: derived_cash_balance)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_current_balance!(balance:, cash_balance:)
|
def update_current_balance!(balance:, cash_balance: nil)
|
||||||
raise InvalidBalanceError, "Cash balance cannot exceed balance" if cash_balance > balance
|
raise InvalidBalanceError, "Cash balance cannot exceed balance" if cash_balance.present? && cash_balance > balance
|
||||||
|
|
||||||
|
derived_cash_balance = cash_balance.present? ? cash_balance : choose_cash_balance_from_balance(balance)
|
||||||
|
|
||||||
transaction do
|
transaction do
|
||||||
if opening_anchor_valuation.present? && valuations.where(kind: "recon").empty?
|
# See test for explanation - Depository accounts are handled as a special case for current balance updates
|
||||||
adjust_opening_balance_with_delta(balance:, cash_balance:)
|
if opening_anchor_valuation.present? && valuations.where(kind: "recon").empty? && self.depository?
|
||||||
|
adjust_opening_balance_with_delta!(balance:, cash_balance: derived_cash_balance)
|
||||||
else
|
else
|
||||||
reconcile_balance!(balance:, cash_balance:, date: Date.current)
|
reconcile_balance!(balance:, cash_balance: derived_cash_balance, date: Date.current)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Always update cached balance fields when updating current balance
|
# Always update cached balance fields when updating current balance
|
||||||
update!(balance: balance, cash_balance: cash_balance)
|
update!(balance: balance, cash_balance: derived_cash_balance)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def adjust_opening_balance_with_delta(balance:, cash_balance:)
|
|
||||||
delta = self.balance - balance
|
|
||||||
cash_delta = self.cash_balance - cash_balance
|
|
||||||
|
|
||||||
set_or_update_opening_balance!(
|
|
||||||
balance: balance - delta,
|
|
||||||
cash_balance: cash_balance - cash_delta
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def set_or_update_opening_balance!(balance:, cash_balance:, date: nil)
|
def set_or_update_opening_balance!(balance:, cash_balance:, date: nil)
|
||||||
# A reasonable start date for most accounts to fill up adequate history for graphs
|
# A reasonable start date for most accounts to fill up adequate history for graphs
|
||||||
fallback_opening_date = 2.years.ago.to_date
|
fallback_opening_date = 2.years.ago.to_date
|
||||||
|
@ -130,4 +131,24 @@ module Account::Reconcileable
|
||||||
def current_anchor_valuation
|
def current_anchor_valuation
|
||||||
valuations.current_anchor.first
|
valuations.current_anchor.first
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def adjust_opening_balance_with_delta!(balance:, cash_balance:)
|
||||||
|
delta = self.balance - balance
|
||||||
|
cash_delta = self.cash_balance - cash_balance
|
||||||
|
|
||||||
|
set_or_update_opening_balance!(
|
||||||
|
balance: balance - delta,
|
||||||
|
cash_balance: cash_balance - cash_delta
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
# For depository accounts, the cash balance is the same as the balance always
|
||||||
|
# Otherwise, if not specified, we assume cash balance is 0
|
||||||
|
def choose_cash_balance_from_balance(balance)
|
||||||
|
if self.depository?
|
||||||
|
balance
|
||||||
|
else
|
||||||
|
0
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
|
|
||||||
<% menu.with_item(
|
<% menu.with_item(
|
||||||
variant: "link",
|
variant: "link",
|
||||||
text: "New balance",
|
text: "Account #{account.asset? ? "value" : "balance"} update",
|
||||||
icon: "circle-dollar-sign",
|
icon: "circle-dollar-sign",
|
||||||
href: new_valuation_path(account_id: @account.id),
|
href: new_valuation_path(account_id: @account.id),
|
||||||
data: { turbo_frame: :modal }) %>
|
data: { turbo_frame: :modal }) %>
|
||||||
|
|
|
@ -10,8 +10,8 @@
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
<%= form.hidden_field :name, value: "Balance update" %>
|
<%= form.hidden_field :name, value: "Balance update" %>
|
||||||
<%= form.date_field :date, label: true, required: true, value: Date.current, min: Entry.min_supported_date, max: Date.current %>
|
<%= form.date_field :date, label: true, required: true, value: Date.current, min: Entry.min_supported_date, max: Date.current %>
|
||||||
<%= form.money_field :amount, label: t(".amount"), required: true %>
|
<%= form.money_field :amount, label: t(".amount"), required: true, disable_currency: true %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%= form.submit t(".submit") %>
|
<%= form.submit "Update account #{entry.account.asset? ? "value" : "balance"}" %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
|
<% term = @entry.account.asset? ? "value" : "balance" %>
|
||||||
|
|
||||||
<%= render DialogComponent.new do |dialog| %>
|
<%= render DialogComponent.new do |dialog| %>
|
||||||
<% dialog.with_header(title: t(".title")) %>
|
<% dialog.with_header(title: "Account #{term} update") %>
|
||||||
<% dialog.with_body do %>
|
<% dialog.with_body do %>
|
||||||
<%= render "form", entry: @entry, error_message: @error_message %>
|
<%= render "form", entry: @entry, error_message: @error_message %>
|
||||||
|
|
||||||
|
<p class="text-sm text-secondary mt-4">
|
||||||
|
This action "resets" the account's <%= term %> to the new value, on the date. Subsequent entries after this date will reference the new value.
|
||||||
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
|
@ -38,7 +38,7 @@ class ValuationsControllerTest < ActionDispatch::IntegrationTest
|
||||||
entry: {
|
entry: {
|
||||||
amount: 20000,
|
amount: 20000,
|
||||||
currency: "USD",
|
currency: "USD",
|
||||||
date: Date.current
|
date: @entry.date
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,7 +4,7 @@ module EntryableResourceInterfaceTest
|
||||||
extend ActiveSupport::Testing::Declarative
|
extend ActiveSupport::Testing::Declarative
|
||||||
|
|
||||||
test "shows new form" do
|
test "shows new form" do
|
||||||
get new_polymorphic_url(@entry.entryable)
|
get new_polymorphic_url(@entry.entryable, account_id: @entry.account_id)
|
||||||
assert_response :success
|
assert_response :success
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,7 @@ class Account::OverviewFormTest < ActiveSupport::TestCase
|
||||||
)
|
)
|
||||||
|
|
||||||
@account.expects(:update!).with(name: "New Property Name").once
|
@account.expects(:update!).with(name: "New Property Name").once
|
||||||
|
@account.expects(:lock_attr!).with(:name).once
|
||||||
@account.expects(:sync_later).never # Name change should not trigger sync
|
@account.expects(:sync_later).never # Name change should not trigger sync
|
||||||
|
|
||||||
result = form.save
|
result = form.save
|
||||||
|
@ -84,6 +85,7 @@ class Account::OverviewFormTest < ActiveSupport::TestCase
|
||||||
|
|
||||||
# Simulate a validation error on opening balance update
|
# Simulate a validation error on opening balance update
|
||||||
@account.expects(:update!).with(name: "New Name").once
|
@account.expects(:update!).with(name: "New Name").once
|
||||||
|
@account.expects(:lock_attr!).with(:name).once
|
||||||
@account.expects(:set_or_update_opening_balance!).raises(Account::Reconcileable::InvalidBalanceError.new("Cash balance cannot exceed balance"))
|
@account.expects(:set_or_update_opening_balance!).raises(Account::Reconcileable::InvalidBalanceError.new("Cash balance cannot exceed balance"))
|
||||||
@account.expects(:sync_later).never # Should NOT sync if any update fails
|
@account.expects(:sync_later).never # Should NOT sync if any update fails
|
||||||
|
|
||||||
|
|
|
@ -6,16 +6,9 @@ class Account::ReconcileableTest < ActiveSupport::TestCase
|
||||||
@family = families(:dylan_family)
|
@family = families(:dylan_family)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Currency updates earn their own method because updating an account currency incurs
|
# Scope: Depository Only
|
||||||
# side effects like recalculating balances, etc.
|
#
|
||||||
test "can update the account currency" do
|
# If a user has an opening balance (valuation) for their manual *Depository* account and has 1+ transactions, the intent of
|
||||||
@account.update_currency!("EUR")
|
|
||||||
|
|
||||||
assert_equal "EUR", @account.currency
|
|
||||||
assert_equal "EUR", @account.entries.valuations.first.currency
|
|
||||||
end
|
|
||||||
|
|
||||||
# If a user has an opening balance (valuation) for their manual account and has 1+ transactions, the intent of
|
|
||||||
# "updating current balance" typically means that their start balance is incorrect. We follow that user intent
|
# "updating current balance" typically means that their start balance is incorrect. We follow that user intent
|
||||||
# by default and find the delta required, and update the opening balance so that the timeline reflects this current balance
|
# by default and find the delta required, and update the opening balance so that the timeline reflects this current balance
|
||||||
#
|
#
|
||||||
|
|
|
@ -31,4 +31,13 @@ class AccountTest < ActiveSupport::TestCase
|
||||||
assert_equal "Investments", account.short_subtype_label
|
assert_equal "Investments", account.short_subtype_label
|
||||||
assert_equal "Investments", account.long_subtype_label
|
assert_equal "Investments", account.long_subtype_label
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Currency updates earn their own method because updating an account currency incurs
|
||||||
|
# side effects like recalculating balances, etc.
|
||||||
|
test "can update the account currency" do
|
||||||
|
@account.update_currency!("EUR")
|
||||||
|
|
||||||
|
assert_equal "EUR", @account.currency
|
||||||
|
assert_equal "EUR", @account.entries.valuations.first.currency
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -23,20 +23,22 @@ class AccountsTest < ApplicationSystemTestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
test "can create property account" do
|
test "can create property account" do
|
||||||
# Step 1: Select property type and enter basic details
|
# Step 1: Enter basic property details
|
||||||
click_link "Property"
|
click_link "Property"
|
||||||
|
|
||||||
account_name = "[system test] Property Account"
|
account_name = "[system test] Property Account"
|
||||||
fill_in "Name*", with: account_name
|
fill_in "Name", with: account_name
|
||||||
select "Single Family Home", from: "Property type*"
|
fill_in "account[current_estimated_value]", with: 500000
|
||||||
fill_in "Year Built (optional)", with: 2005
|
fill_in "account[purchase_price]", with: 450000
|
||||||
fill_in "Area (optional)", with: 2250
|
fill_in "account[purchase_date]", with: "01/15/2020"
|
||||||
|
|
||||||
click_button "Next"
|
click_button "Next"
|
||||||
|
|
||||||
# Step 2: Enter balance information
|
# Step 2: Enter property details
|
||||||
assert_text "Value"
|
assert_text "Property type"
|
||||||
fill_in "account[balance]", with: 500000
|
select "Single Family Home", from: "Property type"
|
||||||
|
fill_in "Year built", with: 2005
|
||||||
|
fill_in "Area", with: 2250
|
||||||
click_button "Next"
|
click_button "Next"
|
||||||
|
|
||||||
# Step 3: Enter address information
|
# Step 3: Enter address information
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue