mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-07-24 07:39:39 +02:00
Account namespace updates: part 6 (transactions) (#904)
* Move Transaction to Account namespace * Fix improper routes, improve separation of concerns * Replace account transactions list partial with view * Remove logs * Consolidate transaction views * Remove unused code * Transfer style tweaks * Remove more unused code * Add back totals by currency helper
This commit is contained in:
parent
cb3fd34f90
commit
da18c3d850
72 changed files with 575 additions and 522 deletions
|
@ -1,4 +1,4 @@
|
||||||
class Transaction::RowsController < ApplicationController
|
class Account::Transaction::RowsController < ApplicationController
|
||||||
before_action :set_transaction, only: %i[ show update ]
|
before_action :set_transaction, only: %i[ show update ]
|
||||||
|
|
||||||
def show
|
def show
|
||||||
|
@ -7,7 +7,7 @@ class Transaction::RowsController < ApplicationController
|
||||||
def update
|
def update
|
||||||
@transaction.update! transaction_params
|
@transaction.update! transaction_params
|
||||||
|
|
||||||
redirect_to transaction_row_path(@transaction)
|
redirect_to account_transaction_row_path(@transaction.account, @transaction)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
@ -17,6 +17,6 @@ class Transaction::RowsController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_transaction
|
def set_transaction
|
||||||
@transaction = Current.family.transactions.find(params[:id])
|
@transaction = Current.family.accounts.find(params[:account_id]).transactions.find(params[:transaction_id])
|
||||||
end
|
end
|
||||||
end
|
end
|
6
app/controllers/account/transaction/rules_controller.rb
Normal file
6
app/controllers/account/transaction/rules_controller.rb
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
class Account::Transaction::RulesController < ApplicationController
|
||||||
|
layout "with_sidebar"
|
||||||
|
|
||||||
|
def index
|
||||||
|
end
|
||||||
|
end
|
44
app/controllers/account/transactions_controller.rb
Normal file
44
app/controllers/account/transactions_controller.rb
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
class Account::TransactionsController < ApplicationController
|
||||||
|
layout "with_sidebar"
|
||||||
|
|
||||||
|
before_action :set_account
|
||||||
|
before_action :set_transaction, only: %i[ show update destroy ]
|
||||||
|
|
||||||
|
def index
|
||||||
|
@transactions = @account.transactions.ordered
|
||||||
|
end
|
||||||
|
|
||||||
|
def show
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
@transaction.update! transaction_params
|
||||||
|
@transaction.sync_account_later
|
||||||
|
|
||||||
|
redirect_back_or_to account_transaction_url(@transaction.account, @transaction), notice: t(".success")
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
@transaction.destroy!
|
||||||
|
@transaction.sync_account_later
|
||||||
|
redirect_back_or_to account_url(@transaction.account), notice: t(".success")
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_account
|
||||||
|
@account = Current.family.accounts.find(params[:account_id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_transaction
|
||||||
|
@transaction = @account.transactions.find(params[:id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def search_params
|
||||||
|
params.fetch(:q, {}).permit(:start_date, :end_date, :search, accounts: [], account_ids: [], categories: [], merchants: [])
|
||||||
|
end
|
||||||
|
|
||||||
|
def transaction_params
|
||||||
|
params.require(:transaction).permit(:name, :date, :amount, :currency, :notes, :excluded, :category_id, :merchant_id, tag_ids: [])
|
||||||
|
end
|
||||||
|
end
|
|
@ -21,7 +21,7 @@ class PagesController < ApplicationController
|
||||||
|
|
||||||
@accounts = Current.family.accounts
|
@accounts = Current.family.accounts
|
||||||
@account_groups = @accounts.by_group(period: @period, currency: Current.family.currency)
|
@account_groups = @accounts.by_group(period: @period, currency: Current.family.currency)
|
||||||
@transactions = Current.family.transactions.limit(5).order(date: :desc)
|
@transactions = Current.family.transactions.limit(6).order(date: :desc)
|
||||||
|
|
||||||
# TODO: Placeholders for trendlines
|
# TODO: Placeholders for trendlines
|
||||||
placeholder_series_data = 10.times.map do |i|
|
placeholder_series_data = 10.times.map do |i|
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
class Transaction::RulesController < ApplicationController
|
|
||||||
layout "with_sidebar"
|
|
||||||
|
|
||||||
def index
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,8 +1,6 @@
|
||||||
class TransactionsController < ApplicationController
|
class TransactionsController < ApplicationController
|
||||||
layout "with_sidebar"
|
layout "with_sidebar"
|
||||||
|
|
||||||
before_action :set_transaction, only: %i[ show edit update destroy ]
|
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@q = search_params
|
@q = search_params
|
||||||
result = Current.family.transactions.search(@q).ordered
|
result = Current.family.transactions.search(@q).ordered
|
||||||
|
@ -15,41 +13,22 @@ class TransactionsController < ApplicationController
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def show
|
|
||||||
end
|
|
||||||
|
|
||||||
def new
|
def new
|
||||||
@transaction = Transaction.new.tap do |txn|
|
@transaction = Account::Transaction.new.tap do |txn|
|
||||||
if params[:account_id]
|
if params[:account_id]
|
||||||
txn.account = Current.family.accounts.find(params[:account_id])
|
txn.account = Current.family.accounts.find(params[:account_id])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def edit
|
|
||||||
end
|
|
||||||
|
|
||||||
def create
|
def create
|
||||||
@transaction = Current.family.accounts
|
@transaction = Current.family.accounts
|
||||||
.find(params[:transaction][:account_id])
|
.find(params[:transaction][:account_id])
|
||||||
.transactions.build(transaction_params.merge(amount: amount))
|
.transactions
|
||||||
|
.create!(transaction_params.merge(amount: amount))
|
||||||
|
|
||||||
@transaction.save!
|
|
||||||
@transaction.sync_account_later
|
@transaction.sync_account_later
|
||||||
redirect_to transactions_url, notice: t(".success")
|
redirect_back_or_to account_path(@transaction.account), notice: t(".success")
|
||||||
end
|
|
||||||
|
|
||||||
def update
|
|
||||||
@transaction.update! transaction_params
|
|
||||||
@transaction.sync_account_later
|
|
||||||
|
|
||||||
redirect_to transaction_url(@transaction), notice: t(".success")
|
|
||||||
end
|
|
||||||
|
|
||||||
def destroy
|
|
||||||
@transaction.destroy!
|
|
||||||
@transaction.sync_account_later
|
|
||||||
redirect_to transactions_url, notice: t(".success")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def bulk_delete
|
def bulk_delete
|
||||||
|
@ -90,10 +69,6 @@ class TransactionsController < ApplicationController
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def set_transaction
|
|
||||||
@transaction = Current.family.transactions.find(params[:id])
|
|
||||||
end
|
|
||||||
|
|
||||||
def amount
|
def amount
|
||||||
if nature.income?
|
if nature.income?
|
||||||
transaction_params[:amount].to_d * -1
|
transaction_params[:amount].to_d * -1
|
||||||
|
@ -119,6 +94,6 @@ class TransactionsController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def transaction_params
|
def transaction_params
|
||||||
params.require(:transaction).permit(:name, :date, :amount, :currency, :notes, :excluded, :category_id, :merchant_id, tag_ids: [])
|
params.require(:transaction).permit(:name, :date, :amount, :currency, :category_id, tag_ids: [])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
module Transactions::SearchesHelper
|
module Account::Transaction::SearchesHelper
|
||||||
def transaction_search_filters
|
def transaction_search_filters
|
||||||
[
|
[
|
||||||
{ key: "account_filter", name: "Account", icon: "layers" },
|
{ key: "account_filter", name: "Account", icon: "layers" },
|
|
@ -1,23 +1,4 @@
|
||||||
module TransactionsHelper
|
module Account::TransactionsHelper
|
||||||
def transactions_group(date, transactions, transaction_partial_path = "transactions/transaction")
|
|
||||||
header_left = content_tag :span do
|
|
||||||
"#{date.strftime('%b %d, %Y')} · #{transactions.size}".html_safe
|
|
||||||
end
|
|
||||||
|
|
||||||
header_right = content_tag :span do
|
|
||||||
format_money(-transactions.sum(&:amount_money))
|
|
||||||
end
|
|
||||||
|
|
||||||
header = header_left.concat(header_right)
|
|
||||||
|
|
||||||
content = render partial: transaction_partial_path, collection: transactions
|
|
||||||
|
|
||||||
render partial: "shared/list_group", locals: {
|
|
||||||
header: header,
|
|
||||||
content: content
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def unconfirmed_transfer?(transaction)
|
def unconfirmed_transfer?(transaction)
|
||||||
transaction.marked_as_transfer && transaction.transfer.nil?
|
transaction.marked_as_transfer && transaction.transfer.nil?
|
||||||
end
|
end
|
|
@ -1,6 +1,6 @@
|
||||||
module Account::ValuationsHelper
|
module Account::ValuationsHelper
|
||||||
def valuation_icon(valuation)
|
def valuation_icon(valuation)
|
||||||
if valuation.first_of_series?
|
if valuation.oldest?
|
||||||
"keyboard"
|
"keyboard"
|
||||||
elsif valuation.trend.direction.up?
|
elsif valuation.trend.direction.up?
|
||||||
"arrow-up"
|
"arrow-up"
|
||||||
|
@ -12,7 +12,7 @@ module Account::ValuationsHelper
|
||||||
end
|
end
|
||||||
|
|
||||||
def valuation_style(valuation)
|
def valuation_style(valuation)
|
||||||
color = valuation.first_of_series? ? "#D444F1" : valuation.trend.color
|
color = valuation.oldest? ? "#D444F1" : valuation.trend.color
|
||||||
|
|
||||||
<<-STYLE.strip
|
<<-STYLE.strip
|
||||||
background-color: color-mix(in srgb, #{color} 5%, white);
|
background-color: color-mix(in srgb, #{color} 5%, white);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
class Transaction < ApplicationRecord
|
class Account::Transaction < ApplicationRecord
|
||||||
include Monetizable
|
include Monetizable
|
||||||
|
|
||||||
monetize :amount
|
monetize :amount
|
||||||
|
@ -17,22 +17,22 @@ class Transaction < ApplicationRecord
|
||||||
scope :active, -> { where(excluded: false) }
|
scope :active, -> { where(excluded: false) }
|
||||||
scope :inflows, -> { where("amount <= 0") }
|
scope :inflows, -> { where("amount <= 0") }
|
||||||
scope :outflows, -> { where("amount > 0") }
|
scope :outflows, -> { where("amount > 0") }
|
||||||
scope :by_name, ->(name) { where("transactions.name ILIKE ?", "%#{name}%") }
|
scope :by_name, ->(name) { where("account_transactions.name ILIKE ?", "%#{name}%") }
|
||||||
scope :with_categories, ->(categories) { joins(:category).where(categories: { name: categories }) }
|
scope :with_categories, ->(categories) { joins(:category).where(categories: { name: categories }) }
|
||||||
scope :with_accounts, ->(accounts) { joins(:account).where(accounts: { name: accounts }) }
|
scope :with_accounts, ->(accounts) { joins(:account).where(accounts: { name: accounts }) }
|
||||||
scope :with_account_ids, ->(account_ids) { joins(:account).where(accounts: { id: account_ids }) }
|
scope :with_account_ids, ->(account_ids) { joins(:account).where(accounts: { id: account_ids }) }
|
||||||
scope :with_merchants, ->(merchants) { joins(:merchant).where(merchants: { name: merchants }) }
|
scope :with_merchants, ->(merchants) { joins(:merchant).where(merchants: { name: merchants }) }
|
||||||
scope :on_or_after_date, ->(date) { where("transactions.date >= ?", date) }
|
scope :on_or_after_date, ->(date) { where("account_transactions.date >= ?", date) }
|
||||||
scope :on_or_before_date, ->(date) { where("transactions.date <= ?", date) }
|
scope :on_or_before_date, ->(date) { where("account_transactions.date <= ?", date) }
|
||||||
scope :with_converted_amount, ->(currency = Current.family.currency) {
|
scope :with_converted_amount, ->(currency = Current.family.currency) {
|
||||||
# Join with exchange rates to convert the amount to the given currency
|
# Join with exchange rates to convert the amount to the given currency
|
||||||
# If no rate is available, exclude the transaction from the results
|
# If no rate is available, exclude the transaction from the results
|
||||||
select(
|
select(
|
||||||
"transactions.*",
|
"account_transactions.*",
|
||||||
"transactions.amount * COALESCE(er.rate, 1) AS converted_amount"
|
"account_transactions.amount * COALESCE(er.rate, 1) AS converted_amount"
|
||||||
)
|
)
|
||||||
.joins(sanitize_sql_array([ "LEFT JOIN exchange_rates er ON transactions.date = er.date AND transactions.currency = er.base_currency AND er.converted_currency = ?", currency ]))
|
.joins(sanitize_sql_array([ "LEFT JOIN exchange_rates er ON account_transactions.date = er.date AND account_transactions.currency = er.base_currency AND er.converted_currency = ?", currency ]))
|
||||||
.where("er.rate IS NOT NULL OR transactions.currency = ?", currency)
|
.where("er.rate IS NOT NULL OR account_transactions.currency = ?", currency)
|
||||||
}
|
}
|
||||||
|
|
||||||
def inflow?
|
def inflow?
|
|
@ -15,14 +15,10 @@ class Account::Valuation < ApplicationRecord
|
||||||
@trend ||= create_trend
|
@trend ||= create_trend
|
||||||
end
|
end
|
||||||
|
|
||||||
def first_of_series?
|
def oldest?
|
||||||
account.valuations.chronological.limit(1).pluck(:date).first == self.date
|
account.valuations.chronological.limit(1).pluck(:date).first == self.date
|
||||||
end
|
end
|
||||||
|
|
||||||
def last_of_series?
|
|
||||||
account.valuations.reverse_chronological.limit(1).pluck(:date).first == self.date
|
|
||||||
end
|
|
||||||
|
|
||||||
def sync_account_later
|
def sync_account_later
|
||||||
if destroyed?
|
if destroyed?
|
||||||
sync_start_date = previous_valuation&.date
|
sync_start_date = previous_valuation&.date
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
class Category < ApplicationRecord
|
class Category < ApplicationRecord
|
||||||
has_many :transactions, dependent: :nullify
|
has_many :transactions, dependent: :nullify, class_name: "Account::Transaction"
|
||||||
belongs_to :family
|
belongs_to :family
|
||||||
|
|
||||||
validates :name, :color, :family, presence: true
|
validates :name, :color, :family, presence: true
|
||||||
|
|
|
@ -3,7 +3,7 @@ class Family < ApplicationRecord
|
||||||
has_many :tags, dependent: :destroy
|
has_many :tags, dependent: :destroy
|
||||||
has_many :accounts, dependent: :destroy
|
has_many :accounts, dependent: :destroy
|
||||||
has_many :institutions, dependent: :destroy
|
has_many :institutions, dependent: :destroy
|
||||||
has_many :transactions, through: :accounts
|
has_many :transactions, through: :accounts, class_name: "Account::Transaction"
|
||||||
has_many :imports, through: :accounts
|
has_many :imports, through: :accounts
|
||||||
has_many :categories, dependent: :destroy
|
has_many :categories, dependent: :destroy
|
||||||
has_many :merchants, dependent: :destroy
|
has_many :merchants, dependent: :destroy
|
||||||
|
@ -40,9 +40,9 @@ class Family < ApplicationRecord
|
||||||
"COALESCE(SUM(amount) FILTER (WHERE amount > 0), 0) AS spending",
|
"COALESCE(SUM(amount) FILTER (WHERE amount > 0), 0) AS spending",
|
||||||
"COALESCE(SUM(-amount) FILTER (WHERE amount < 0), 0) AS income"
|
"COALESCE(SUM(-amount) FILTER (WHERE amount < 0), 0) AS income"
|
||||||
)
|
)
|
||||||
.where("transactions.date >= ?", period.date_range.begin)
|
.where("account_transactions.date >= ?", period.date_range.begin)
|
||||||
.where("transactions.date <= ?", period.date_range.end)
|
.where("account_transactions.date <= ?", period.date_range.end)
|
||||||
.where("transactions.marked_as_transfer = ?", false)
|
.where("account_transactions.marked_as_transfer = ?", false)
|
||||||
.group("id")
|
.group("id")
|
||||||
.to_a
|
.to_a
|
||||||
|
|
||||||
|
@ -60,7 +60,7 @@ class Family < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def snapshot_transactions
|
def snapshot_transactions
|
||||||
rolling_totals = Transaction.daily_rolling_totals(transactions, period: Period.last_30_days, currency: self.currency)
|
rolling_totals = Account::Transaction.daily_rolling_totals(transactions, period: Period.last_30_days, currency: self.currency)
|
||||||
|
|
||||||
spending = []
|
spending = []
|
||||||
income = []
|
income = []
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
class Merchant < ApplicationRecord
|
class Merchant < ApplicationRecord
|
||||||
has_many :transactions, dependent: :nullify
|
has_many :transactions, dependent: :nullify, class_name: "Account::Transaction"
|
||||||
belongs_to :family
|
belongs_to :family
|
||||||
|
|
||||||
validates :name, :color, :family, presence: true
|
validates :name, :color, :family, presence: true
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
class Tag < ApplicationRecord
|
class Tag < ApplicationRecord
|
||||||
belongs_to :family
|
belongs_to :family
|
||||||
has_many :taggings, dependent: :destroy
|
has_many :taggings, dependent: :destroy
|
||||||
has_many :transactions, through: :taggings, source: :taggable, source_type: "Transaction"
|
has_many :transactions, through: :taggings, source: :taggable, source_type: "Account::Transaction"
|
||||||
|
|
||||||
validates :name, presence: true, uniqueness: { scope: :family }
|
validates :name, presence: true, uniqueness: { scope: :family }
|
||||||
|
|
||||||
|
|
1
app/views/account/transaction/rows/show.html.erb
Normal file
1
app/views/account/transaction/rows/show.html.erb
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<%= render "account/transactions/transaction", transaction: @transaction %>
|
|
@ -1,6 +1,7 @@
|
||||||
<% content_for :sidebar do %>
|
<% content_for :sidebar do %>
|
||||||
<%= render "settings/nav" %>
|
<%= render "settings/nav" %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<h1 class="text-gray-900 text-xl font-medium mb-4">Rules</h1>
|
<h1 class="text-gray-900 text-xl font-medium mb-4">Rules</h1>
|
||||||
<div class="bg-white shadow-xs border border-alpha-black-25 rounded-xl p-4">
|
<div class="bg-white shadow-xs border border-alpha-black-25 rounded-xl p-4">
|
5
app/views/account/transactions/_loading.html.erb
Normal file
5
app/views/account/transactions/_loading.html.erb
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<div class="bg-white space-y-4 p-5 border border-alpha-black-25 rounded-xl shadow-xs">
|
||||||
|
<div class="p-5 flex justify-center items-center">
|
||||||
|
<%= tag.p t(".loading"), class: "text-gray-500 animate-pulse text-sm" %>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -12,6 +12,7 @@
|
||||||
builder: ActionView::Helpers::FormBuilder,
|
builder: ActionView::Helpers::FormBuilder,
|
||||||
scope: "bulk_update",
|
scope: "bulk_update",
|
||||||
data: {
|
data: {
|
||||||
|
turbo_frame: "_top",
|
||||||
turbo_confirm: {
|
turbo_confirm: {
|
||||||
title: t(".mark_transfers"),
|
title: t(".mark_transfers"),
|
||||||
body: t(".mark_transfers_message"),
|
body: t(".mark_transfers_message"),
|
||||||
|
@ -30,7 +31,7 @@
|
||||||
<%= lucide_icon "pencil-line", class: "w-5 group-hover:text-white" %>
|
<%= lucide_icon "pencil-line", class: "w-5 group-hover:text-white" %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%= form_with url: bulk_delete_transactions_path, builder: ActionView::Helpers::FormBuilder, data: { turbo_confirm: true } do %>
|
<%= form_with url: bulk_delete_transactions_path, builder: ActionView::Helpers::FormBuilder, data: { turbo_confirm: true, turbo_frame: "_top" } do %>
|
||||||
<button type="button" data-bulk-select-scope-param="bulk_delete" data-action="bulk-select#submitBulkRequest" class="p-1.5 group hover:bg-gray-700 flex items-center justify-center rounded-md" title="Delete">
|
<button type="button" data-bulk-select-scope-param="bulk_delete" data-action="bulk-select#submitBulkRequest" class="p-1.5 group hover:bg-gray-700 flex items-center justify-center rounded-md" title="Delete">
|
||||||
<%= lucide_icon "trash-2", class: "w-5 group-hover:text-white" %>
|
<%= lucide_icon "trash-2", class: "w-5 group-hover:text-white" %>
|
||||||
</button>
|
</button>
|
89
app/views/account/transactions/_transaction.html.erb
Normal file
89
app/views/account/transactions/_transaction.html.erb
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
<%# locals: (transaction:, selectable: true, editable: true, short: false, show_tags: false) %>
|
||||||
|
<%= turbo_frame_tag dom_id(transaction), class: "grid grid-cols-12 items-center text-gray-900 text-sm font-medium p-4" do %>
|
||||||
|
|
||||||
|
<% name_col_span = transaction.transfer? ? "col-span-10" : short ? "col-span-6" : "col-span-4" %>
|
||||||
|
<div class="pr-10 flex items-center gap-4 <%= name_col_span %>">
|
||||||
|
<% if selectable %>
|
||||||
|
<%= check_box_tag dom_id(transaction, "selection"),
|
||||||
|
class: "maybe-checkbox maybe-checkbox--light",
|
||||||
|
data: { id: transaction.id, "bulk-select-target": "row", action: "bulk-select#toggleRowSelection" } %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<div class="max-w-full">
|
||||||
|
<%= content_tag :div, class: ["flex items-center gap-2"] do %>
|
||||||
|
<div class="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-gray-600/5 text-gray-600">
|
||||||
|
<%= transaction.name[0].upcase %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="truncate text-gray-900">
|
||||||
|
<% if transaction.new_record? %>
|
||||||
|
<%= content_tag :p, transaction.name %>
|
||||||
|
<% else %>
|
||||||
|
<%= link_to transaction.name,
|
||||||
|
account_transaction_path(transaction.account, transaction),
|
||||||
|
data: { turbo_frame: "drawer", turbo_prefetch: false },
|
||||||
|
class: "hover:underline hover:text-gray-800" %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<% if unconfirmed_transfer?(transaction) %>
|
||||||
|
<% if editable %>
|
||||||
|
<%= form_with url: unmark_transfers_transactions_path, builder: ActionView::Helpers::FormBuilder, class: "flex items-center", data: {
|
||||||
|
turbo_confirm: {
|
||||||
|
title: t(".remove_transfer"),
|
||||||
|
body: t(".remove_transfer_body"),
|
||||||
|
accept: t(".remove_transfer_confirm"),
|
||||||
|
},
|
||||||
|
turbo_frame: "_top"
|
||||||
|
} do |f| %>
|
||||||
|
<%= f.hidden_field "bulk_update[transaction_ids][]", value: transaction.id %>
|
||||||
|
<%= f.button class: "flex items-center justify-center group", title: "Remove transfer" do %>
|
||||||
|
<%= lucide_icon "arrow-left-right", class: "group-hover:hidden text-gray-500 w-4 h-4" %>
|
||||||
|
<%= lucide_icon "unlink", class: "hidden group-hover:inline-block text-gray-900 w-4 h-4" %>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
<% else %>
|
||||||
|
<%= lucide_icon "arrow-left-right", class: "text-gray-500 w-4 h-4" %>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<% unless transaction.transfer? %>
|
||||||
|
<% unless short %>
|
||||||
|
<div class="flex items-center gap-1 <%= show_tags ? "col-span-6" : "col-span-3" %>">
|
||||||
|
<% if editable %>
|
||||||
|
<%= render "categories/menu", transaction: transaction %>
|
||||||
|
<% else %>
|
||||||
|
<%= render "categories/badge", category: transaction.category %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<% if show_tags %>
|
||||||
|
<% transaction.tags.each do |tag| %>
|
||||||
|
<%= render partial: "tags/badge", locals: { tag: tag } %>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<% unless show_tags %>
|
||||||
|
<%= tag.div class: short ? "col-span-4" : "col-span-3" do %>
|
||||||
|
<% if transaction.new_record? %>
|
||||||
|
<%= tag.p transaction.account.name %>
|
||||||
|
<% else %>
|
||||||
|
<%= link_to transaction.account.name,
|
||||||
|
account_path(transaction.account, tab: "transactions"),
|
||||||
|
data: { turbo_frame: "_top" },
|
||||||
|
class: "hover:underline" %>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<div class="col-span-2 ml-auto">
|
||||||
|
<%= content_tag :p,
|
||||||
|
format_money(-transaction.amount_money),
|
||||||
|
class: ["text-green-600": transaction.inflow?] %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
21
app/views/account/transactions/_transaction_group.html.erb
Normal file
21
app/views/account/transactions/_transaction_group.html.erb
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<%# locals: (date:, transactions:, transfers: [], selectable: true, **transaction_opts) %>
|
||||||
|
<div id="date-group-<%= date %>" class="bg-gray-25 rounded-xl p-1 w-full" data-bulk-select-target="group">
|
||||||
|
<div class="py-2 px-4 flex items-center justify-between font-medium text-xs text-gray-500">
|
||||||
|
<div class="flex pl-0.5 items-center gap-4">
|
||||||
|
<% if selectable %>
|
||||||
|
<%= check_box_tag "#{date}_transactions_selection",
|
||||||
|
class: ["maybe-checkbox maybe-checkbox--light", "hidden": transactions.count == 0],
|
||||||
|
id: "selection_transaction_#{date}",
|
||||||
|
data: { action: "bulk-select#toggleGroupSelection" } %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<%= tag.span "#{date.strftime('%b %d, %Y')} · #{transactions.size + (transfers.size * 2)}" %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<%= totals_by_currency(collection: transactions, money_method: :amount_money, negate: true) %>
|
||||||
|
</div>
|
||||||
|
<div class="bg-white shadow-xs rounded-md border border-alpha-black-25 divide-y divide-alpha-black-50">
|
||||||
|
<%= render transactions, selectable:, **transaction_opts.except(:selectable) %>
|
||||||
|
<%= render transfers %>
|
||||||
|
</div>
|
||||||
|
</div>
|
29
app/views/account/transactions/index.html.erb
Normal file
29
app/views/account/transactions/index.html.erb
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
<%= turbo_frame_tag dom_id(@account, "transactions") do %>
|
||||||
|
<div class="bg-white space-y-4 p-5 border border-alpha-black-25 rounded-xl shadow-xs">
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<h3 class="font-medium text-lg"><%= t(".transactions") %></h3>
|
||||||
|
<%= link_to new_transaction_path(account_id: @account),
|
||||||
|
class: "flex gap-1 font-medium items-center bg-gray-50 text-gray-900 p-2 rounded-lg",
|
||||||
|
data: { turbo_frame: :modal } do %>
|
||||||
|
<%= lucide_icon("plus", class: "w-5 h-5 text-gray-900") %>
|
||||||
|
<span class="text-sm"><%= t(".new") %></span>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="transactions" data-controller="bulk-select" data-bulk-select-resource-value="<%= t(".transaction") %>">
|
||||||
|
<div hidden id="transaction-selection-bar" data-bulk-select-target="selectionBar">
|
||||||
|
<%= render "account/transactions/selection_bar" %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<% if @transactions.empty? %>
|
||||||
|
<p class="text-gray-500 py-4"><%= t(".no_transactions") %></p>
|
||||||
|
<% else %>
|
||||||
|
<div class="space-y-6">
|
||||||
|
<% group_transactions_by_date(@transactions).each do |date, group| %>
|
||||||
|
<%= render "transaction_group", date:, transactions: group[:transactions], transfers: group[:transfers] %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
|
@ -90,7 +90,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%= button_to t(".delete"),
|
<%= button_to t(".delete"),
|
||||||
transaction_path(@transaction),
|
account_transaction_path(@transaction),
|
||||||
method: :delete,
|
method: :delete,
|
||||||
class: "rounded-lg px-3 py-2 text-red-500 text-sm font-medium border border-alpha-black-200",
|
class: "rounded-lg px-3 py-2 text-red-500 text-sm font-medium border border-alpha-black-200",
|
||||||
data: { turbo_confirm: true, turbo_frame: "_top" } %>
|
data: { turbo_confirm: true, turbo_frame: "_top" } %>
|
|
@ -1,6 +1,6 @@
|
||||||
<%= turbo_frame_tag dom_id(transfer), class: "block" do %>
|
<%= turbo_frame_tag dom_id(transfer), class: "block" do %>
|
||||||
<details class="group flex items-center text-gray-900 p-4 text-sm font-medium">
|
<details class="group flex items-center text-gray-900 text-sm font-medium">
|
||||||
<summary class="flex items-center justify-between">
|
<summary class="flex items-center justify-between p-4">
|
||||||
<div class="flex items-center gap-4">
|
<div class="flex items-center gap-4">
|
||||||
<%= button_to account_transfer_path(transfer),
|
<%= button_to account_transfer_path(transfer),
|
||||||
method: :delete,
|
method: :delete,
|
||||||
|
@ -25,16 +25,9 @@
|
||||||
<%= lucide_icon "chevron-down", class: "group-open:transform group-open:rotate-180 text-gray-500 w-5" %>
|
<%= lucide_icon "chevron-down", class: "group-open:transform group-open:rotate-180 text-gray-500 w-5" %>
|
||||||
</summary>
|
</summary>
|
||||||
|
|
||||||
<div class="pt-2 divide-y divide-alpha-black-200">
|
<div class="divide-y divide-alpha-black-200">
|
||||||
<% transfer.transactions.each do |transaction| %>
|
<% transfer.transactions.each do |transaction| %>
|
||||||
<div class="py-3 grid grid-cols-12 items-center">
|
<%= render transaction, selectable: false, editable: false %>
|
||||||
<div class="col-span-10">
|
|
||||||
<%= render "transactions/name", transaction: transaction %>
|
|
||||||
</div>
|
|
||||||
<div class="col-span-2 ml-auto">
|
|
||||||
<%= render "transactions/amount", transaction: transaction %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
|
|
5
app/views/account/valuations/_loading.html.erb
Normal file
5
app/views/account/valuations/_loading.html.erb
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<div class="bg-white space-y-4 p-5 border border-alpha-black-25 rounded-xl shadow-xs">
|
||||||
|
<div class="p-5 flex justify-center items-center">
|
||||||
|
<%= tag.p t(".loading"), class: "text-gray-500 animate-pulse text-sm" %>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -1,5 +1,3 @@
|
||||||
<%# locals: (valuation:) %>
|
|
||||||
|
|
||||||
<%= turbo_frame_tag dom_id(valuation) do %>
|
<%= turbo_frame_tag dom_id(valuation) do %>
|
||||||
<div class="p-4 grid grid-cols-10 items-center">
|
<div class="p-4 grid grid-cols-10 items-center">
|
||||||
<div class="col-span-5 flex items-center gap-4">
|
<div class="col-span-5 flex items-center gap-4">
|
||||||
|
@ -9,7 +7,7 @@
|
||||||
|
|
||||||
<div class="text-sm">
|
<div class="text-sm">
|
||||||
<%= tag.p valuation.date, class: "text-gray-900 font-medium" %>
|
<%= tag.p valuation.date, class: "text-gray-900 font-medium" %>
|
||||||
<%= tag.p valuation.first_of_series? ? t(".start_balance") : t(".value_update"), class: "text-gray-500" %>
|
<%= tag.p valuation.oldest? ? t(".start_balance") : t(".value_update"), class: "text-gray-500" %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -43,8 +41,4 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<% unless valuation.last_of_series? %>
|
|
||||||
<div class="h-px bg-alpha-black-50 ml-16 mr-4"></div>
|
|
||||||
<% end %>
|
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
1
app/views/account/valuations/_valuation_ruler.html.erb
Normal file
1
app/views/account/valuations/_valuation_ruler.html.erb
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<div class="h-px bg-alpha-black-50 ml-16 mr-4"></div>
|
|
@ -1,15 +1,35 @@
|
||||||
<%= turbo_frame_tag dom_id(@account, "valuations") do %>
|
<%= turbo_frame_tag dom_id(@account, "valuations") do %>
|
||||||
<div class="grid grid-cols-10 items-center uppercase text-xs font-medium text-gray-500 px-4 py-2">
|
<div class="bg-white space-y-4 p-5 border border-alpha-black-25 rounded-xl shadow-xs">
|
||||||
<%= tag.p t(".date"), class: "col-span-5" %>
|
<div class="flex items-center justify-between">
|
||||||
<%= tag.p t(".value"), class: "col-span-2 justify-self-end" %>
|
<%= tag.h2 t(".valuations"), class: "font-medium text-lg" %>
|
||||||
<%= tag.p t(".change"), class: "col-span-2 justify-self-end" %>
|
<%= link_to new_account_valuation_path(@account),
|
||||||
<%= tag.div class: "col-span-1" %>
|
data: { turbo_frame: dom_id(Account::Valuation.new) },
|
||||||
</div>
|
class: "flex gap-1 font-medium items-center bg-gray-50 text-gray-900 p-2 rounded-lg" do %>
|
||||||
|
<%= lucide_icon("plus", class: "w-5 h-5 text-gray-900") %>
|
||||||
|
<%= tag.span t(".new_entry"), class: "text-sm" %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="rounded-lg bg-white border-alpha-black-25 shadow-xs">
|
<div class="rounded-xl bg-gray-25 p-1">
|
||||||
<%= turbo_frame_tag dom_id(Account::Valuation.new) %>
|
<div class="grid grid-cols-10 items-center uppercase text-xs font-medium text-gray-500 px-4 py-2">
|
||||||
<% @account.valuations.reverse_chronological.each do |valuation| %>
|
<%= tag.p t(".date"), class: "col-span-5" %>
|
||||||
<%= render valuation %>
|
<%= tag.p t(".value"), class: "col-span-2 justify-self-end" %>
|
||||||
<% end %>
|
<%= tag.p t(".change"), class: "col-span-2 justify-self-end" %>
|
||||||
|
<%= tag.div class: "col-span-1" %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="rounded-lg bg-white border-alpha-black-25 shadow-xs">
|
||||||
|
<%= turbo_frame_tag dom_id(Account::Valuation.new) %>
|
||||||
|
|
||||||
|
<% valuations = @account.valuations.reverse_chronological %>
|
||||||
|
<% if valuations.any? %>
|
||||||
|
<%= render partial: "valuation",
|
||||||
|
collection: @account.valuations.reverse_chronological,
|
||||||
|
spacer_template: "valuation_ruler" %>
|
||||||
|
<% else %>
|
||||||
|
<p class="text-gray-500 text-sm p-4"><%= t(".no_valuations") %></p>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
<%# locals: (account:, transactions:)%>
|
|
||||||
<div class="bg-white space-y-4 p-5 border border-alpha-black-25 rounded-xl shadow-xs">
|
|
||||||
<div class="flex justify-between items-center">
|
|
||||||
<h3 class="font-medium text-lg">Transactions</h3>
|
|
||||||
<%= link_to new_transaction_path(account_id: account), class: "flex gap-1 font-medium items-center bg-gray-50 text-gray-900 p-2 rounded-lg", data: { turbo_frame: :modal } do %>
|
|
||||||
<%= lucide_icon("plus", class: "w-5 h-5 text-gray-900") %>
|
|
||||||
<span class="text-sm">New transaction</span>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<% if transactions.empty? %>
|
|
||||||
<p class="text-gray-500 py-4">No transactions for this account yet.</p>
|
|
||||||
<% else %>
|
|
||||||
<div class="space-y-6">
|
|
||||||
<% transactions.group_by(&:date).each do |date, transactions| %>
|
|
||||||
<%= transactions_group(date, transactions, "accounts/transactions/transaction") %>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
|
@ -1,4 +1,5 @@
|
||||||
<%= turbo_stream_from @account %>
|
<%= turbo_stream_from @account %>
|
||||||
|
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div class="flex justify-between items-center">
|
<div class="flex justify-between items-center">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
|
@ -70,34 +71,23 @@
|
||||||
<%= render partial: "shared/line_chart", locals: { series: @balance_series } %>
|
<%= render partial: "shared/line_chart", locals: { series: @balance_series } %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div data-controller="tabs" data-tabs-active-class="bg-gray-100" data-tabs-default-tab-value="account-history-tab">
|
|
||||||
<div class="flex gap-1 text-sm text-gray-900 font-medium mb-4">
|
|
||||||
<button data-id="account-history-tab" class="p-2 rounded-lg" data-tabs-target="btn" data-action="click->tabs#select">History</button>
|
|
||||||
<button data-id="account-transactions-tab" class="p-2 rounded-lg" data-tabs-target="btn" data-action="click->tabs#select">Transactions</button>
|
|
||||||
</div>
|
|
||||||
<div class="min-h-[800px]">
|
|
||||||
<div data-tabs-target="tab" id="account-history-tab">
|
|
||||||
<div class="bg-white space-y-4 p-5 border border-alpha-black-25 rounded-xl shadow-xs">
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<%= tag.h2 t(".valuations"), class: "font-medium text-lg" %>
|
|
||||||
<%= link_to new_account_valuation_path(@account), data: { turbo_frame: dom_id(Account::Valuation.new) }, class: "flex gap-1 font-medium items-center bg-gray-50 text-gray-900 p-2 rounded-lg" do %>
|
|
||||||
<%= lucide_icon("plus", class: "w-5 h-5 text-gray-900") %>
|
|
||||||
<%= tag.span t(".new_entry"), class: "text-sm" %>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="rounded-xl bg-gray-25 p-1">
|
<% selected_tab = params[:tab] || "history" %>
|
||||||
<%= turbo_frame_tag dom_id(@account, "valuations"), src: account_valuations_path(@account) do %>
|
|
||||||
<div class="p-5 flex justify-center items-center">
|
<div class="flex gap-1 text-sm text-gray-900 font-medium mb-4">
|
||||||
<%= tag.p t(".loading_history"), class: "text-gray-500 animate-pulse text-sm" %>
|
<%= link_to "History", account_path(tab: "history"), class: ["p-2 rounded-lg", "bg-gray-100": selected_tab == "history"] %>
|
||||||
</div>
|
<%= link_to "Transactions", account_path(tab: "transactions"), class: ["p-2 rounded-lg", "bg-gray-100": selected_tab == "transactions"] %>
|
||||||
<% end %>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
<div class="min-h-[800px]">
|
||||||
</div>
|
<% if selected_tab == "transactions" %>
|
||||||
<div data-tabs-target="tab" id="account-transactions-tab" class="hidden">
|
<%= turbo_frame_tag dom_id(@account, "transactions"), src: account_transactions_path(@account) do %>
|
||||||
<%= render partial: "accounts/transactions", locals: { account: @account, transactions: @account.transactions.order(date: :desc) } %>
|
<%= render "account/transactions/loading" %>
|
||||||
</div>
|
<% end %>
|
||||||
</div>
|
<% else %>
|
||||||
|
<%= turbo_frame_tag dom_id(@account, "valuations"), src: account_valuations_path(@account) do %>
|
||||||
|
<%= render "account/valuations/loading" %>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,24 +0,0 @@
|
||||||
<%= turbo_frame_tag dom_id(transaction), class: "grid grid-cols-12 items-center text-gray-900 py-4 text-sm font-medium px-4" do %>
|
|
||||||
<div class="col-span-4">
|
|
||||||
<%= render "transactions/name", transaction: transaction %>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-span-3">
|
|
||||||
<% if transaction.marked_as_transfer %>
|
|
||||||
<div class="flex items-center gap-1 text-gray-500 pl-5">
|
|
||||||
<%= lucide_icon "arrow-right-left", class: "w-4 h-4 text-gray-500" %>
|
|
||||||
<p>Transfer</p>
|
|
||||||
</div>
|
|
||||||
<% else %>
|
|
||||||
<%= render "categories/badge", category: transaction.category %>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<%= link_to transaction.account.name,
|
|
||||||
account_path(transaction.account),
|
|
||||||
class: ["col-span-3 hover:underline"] %>
|
|
||||||
|
|
||||||
<div class="col-span-2 ml-auto">
|
|
||||||
<%= render "transactions/amount", transaction: transaction %>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
|
@ -2,7 +2,7 @@
|
||||||
<% is_selected = category.id === @selected_category&.id %>
|
<% is_selected = category.id === @selected_category&.id %>
|
||||||
|
|
||||||
<%= content_tag :div, class: ["filterable-item flex justify-between items-center border-none rounded-lg px-2 py-1 group w-full", { "bg-gray-25": is_selected }], data: { filter_name: category.name } do %>
|
<%= content_tag :div, class: ["filterable-item flex justify-between items-center border-none rounded-lg px-2 py-1 group w-full", { "bg-gray-25": is_selected }], data: { filter_name: category.name } do %>
|
||||||
<%= button_to transaction_row_path(@transaction, transaction: { category_id: category.id }), method: :patch, data: { turbo_frame: dom_id(@transaction) }, class: "flex w-full items-center gap-1.5 cursor-pointer" do %>
|
<%= button_to account_transaction_row_path(@transaction.account, @transaction, transaction: { category_id: category.id }), method: :patch, data: { turbo_frame: dom_id(@transaction) }, class: "flex w-full items-center gap-1.5 cursor-pointer" do %>
|
||||||
<span class="w-5 h-5">
|
<span class="w-5 h-5">
|
||||||
<%= lucide_icon("check", class: "w-5 h-5 text-gray-500") if is_selected %>
|
<%= lucide_icon("check", class: "w-5 h-5 text-gray-500") if is_selected %>
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<% if @transaction.category %>
|
<% if @transaction.category %>
|
||||||
<%= button_to transaction_row_path(@transaction),
|
<%= button_to account_transaction_row_path(@transaction.account, @transaction),
|
||||||
method: :patch,
|
method: :patch,
|
||||||
data: { turbo_frame: dom_id(@transaction) },
|
data: { turbo_frame: dom_id(@transaction) },
|
||||||
params: { transaction: { category_id: nil } },
|
params: { transaction: { category_id: nil } },
|
||||||
|
|
|
@ -9,8 +9,15 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-8 space-y-4">
|
<div class="mb-8 space-y-4">
|
||||||
<% @import.dry_run.group_by(&:date).each do |date, draft_transactions| %>
|
<% transactions = @import.dry_run %>
|
||||||
<%= transactions_group(date, draft_transactions, "imports/transactions/transaction") %>
|
<% group_transactions_by_date(transactions).each do |date, group| %>
|
||||||
|
<%= render "account/transactions/transaction_group",
|
||||||
|
date: date,
|
||||||
|
transactions: group[:transactions],
|
||||||
|
transfers: group[:transfers],
|
||||||
|
show_tags: true,
|
||||||
|
selectable: false,
|
||||||
|
editable: false %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between gap-4">
|
<div class="flex justify-between gap-4">
|
||||||
<%= previous_setting("Rules", transaction_rules_path) %>
|
<%= previous_setting("Rules", account_transaction_rules_path) %>
|
||||||
<%= next_setting("What's new", changelog_path) %>
|
<%= next_setting("What's new", changelog_path) %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
<%# locals: (transaction:) %>
|
|
||||||
<div class="text-gray-900 grid grid-cols-8 items-center py-4 text-sm font-medium px-4">
|
|
||||||
<div class="col-span-3">
|
|
||||||
<%= render "transactions/name", transaction: transaction %>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-span-2">
|
|
||||||
<%= render partial: "categories/badge", locals: { category: transaction.category } %>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-span-2 flex items-center gap-1">
|
|
||||||
<% transaction.tags.each do |tag| %>
|
|
||||||
<%= render partial: "tags/badge", locals: { tag: tag } %>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-span-1 justify-self-end">
|
|
||||||
<%= render "transactions/amount", transaction: transaction %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
|
@ -35,6 +35,6 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between gap-4">
|
<div class="flex justify-between gap-4">
|
||||||
<%= previous_setting("Categories", categories_path) %>
|
<%= previous_setting("Categories", categories_path) %>
|
||||||
<%= next_setting("Rules", transaction_rules_path) %>
|
<%= next_setting("Rules", account_transaction_rules_path) %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<div>
|
<div>
|
||||||
<h1 class="sr-only">Dashboard</h1>
|
<h1 class="sr-only">Dashboard</h1>
|
||||||
<p class="text-xl font-medium text-gray-900 mb-1"><%= t(".greeting", name: Current.user.first_name ) %></p>
|
<p class="text-xl font-medium text-gray-900 mb-1"><%= t(".greeting", name: Current.user.first_name ) %></p>
|
||||||
<% if !@accounts.blank? %>
|
<% unless @accounts.blank? %>
|
||||||
<p class="text-gray-500 text-sm"><%= t(".subtitle") %></p>
|
<p class="text-gray-500 text-sm"><%= t(".subtitle") %></p>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
@ -162,8 +162,14 @@
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="text-gray-500 p-1 space-y-1 bg-gray-25 rounded-xl">
|
<div class="text-gray-500 p-1 space-y-1 bg-gray-25 rounded-xl">
|
||||||
<% @transactions.group_by(&:date).each do |date, transactions| %>
|
<% group_transactions_by_date(@transactions).each do |date, group| %>
|
||||||
<%= transactions_group(date, transactions, "pages/dashboard/transactions/transaction") %>
|
<%= render "account/transactions/transaction_group",
|
||||||
|
date: date,
|
||||||
|
transactions: group[:transactions],
|
||||||
|
transfers: group[:transfers],
|
||||||
|
selectable: false,
|
||||||
|
editable: false,
|
||||||
|
short: true %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<p class="py-2 text-sm text-center"><%= link_to t(".view_all"), transactions_path %></p>
|
<p class="py-2 text-sm text-center"><%= link_to t(".view_all"), transactions_path %></p>
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
<div class="text-gray-900 flex items-center py-4 text-sm font-medium px-4">
|
|
||||||
<div class="grow max-w-72">
|
|
||||||
<%= render "transactions/name", transaction: transaction %>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="ml-auto">
|
|
||||||
<%= render "transactions/amount", transaction: transaction %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
|
@ -56,7 +56,7 @@
|
||||||
<%= sidebar_link_to t(".merchants_label"), merchants_path, icon: "store" %>
|
<%= sidebar_link_to t(".merchants_label"), merchants_path, icon: "store" %>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<%= sidebar_link_to t(".rules_label"), transaction_rules_path, icon: "list-checks" %>
|
<%= sidebar_link_to t(".rules_label"), account_transaction_rules_path, icon: "list-checks" %>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<%= sidebar_link_to t(".imports_label"), imports_path, icon: "download" %>
|
<%= sidebar_link_to t(".imports_label"), imports_path, icon: "download" %>
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
<%= render "transactions/transaction", transaction: @transaction %>
|
|
|
@ -1,3 +0,0 @@
|
||||||
<%= content_tag :p,
|
|
||||||
format_money(-transaction.amount_money),
|
|
||||||
class: ["text-green-600": transaction.inflow?] %>
|
|
|
@ -1,21 +0,0 @@
|
||||||
<%# locals: (date:, group:) %>
|
|
||||||
<div id="date-group-<%= date %>" class="bg-gray-25 rounded-xl p-1 w-full" data-bulk-select-target="group">
|
|
||||||
<div class="py-2 px-4 flex items-center justify-between font-medium text-xs text-gray-500">
|
|
||||||
<div class="flex pl-0.5 items-center gap-4">
|
|
||||||
<%= check_box_tag "#{date}_transactions_selection",
|
|
||||||
class: ["maybe-checkbox maybe-checkbox--light", "hidden": group[:transactions].count == 0],
|
|
||||||
id: "selection_transaction_#{date}",
|
|
||||||
data: { action: "bulk-select#toggleGroupSelection" } %>
|
|
||||||
|
|
||||||
<%= tag.span "#{date.strftime('%b %d, %Y')} · #{group[:transactions].size + (group[:transfers].size * 2)}" %>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<%= totals_by_currency(collection: group[:transactions], money_method: :amount_money, negate: true) %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="bg-white shadow-xs rounded-md border border-alpha-black-25 divide-y divide-alpha-black-50">
|
|
||||||
<%= render group[:transactions] %>
|
|
||||||
<%= render group[:transfers] %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
|
@ -1,4 +1,4 @@
|
||||||
<%= form_with model: @transaction, data: { turbo_frame: "_top" } do |f| %>
|
<%= form_with model: @transaction, url: transactions_path, scope: "transaction", data: { turbo_frame: "_top" } do |f| %>
|
||||||
<section>
|
<section>
|
||||||
<fieldset class="bg-gray-50 rounded-lg p-1 grid grid-flow-col justify-stretch gap-x-2">
|
<fieldset class="bg-gray-50 rounded-lg p-1 grid grid-flow-col justify-stretch gap-x-2">
|
||||||
<%= radio_tab_tag form: f, name: :nature, value: :expense, label: t(".expense"), icon: "minus-circle", checked: params[:nature] == "expense" || params[:nature].nil? %>
|
<%= radio_tab_tag form: f, name: :nature, value: :expense, label: t(".expense"), icon: "minus-circle", checked: params[:nature] == "expense" || params[:nature].nil? %>
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
<%= content_tag :div, class: ["flex items-center gap-2"] do %>
|
|
||||||
<div class="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-gray-600/5 text-gray-600">
|
|
||||||
<%= transaction.name[0].upcase %>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="truncate text-gray-900">
|
|
||||||
<% if transaction.new_record? %>
|
|
||||||
<%= content_tag :p, transaction.name %>
|
|
||||||
<% else %>
|
|
||||||
<%= link_to transaction.name,
|
|
||||||
transaction_path(transaction),
|
|
||||||
data: { turbo_frame: "drawer", turbo_prefetch: false },
|
|
||||||
class: "hover:underline hover:text-gray-800" %>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
<%# locals: (pagy:) %>
|
||||||
<nav class="flex items-center justify-between px-4 mt-4 sm:px-0 w-full">
|
<nav class="flex items-center justify-between px-4 mt-4 sm:px-0 w-full">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<div>
|
<div>
|
||||||
|
@ -38,7 +39,6 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% per_page = params[:per_page] || "50" %>
|
|
||||||
<div class="flex items-center gap-4">
|
<div class="flex items-center gap-4">
|
||||||
<%= form_with url: transactions_path,
|
<%= form_with url: transactions_path,
|
||||||
builder: ActionView::Helpers::FormBuilder,
|
builder: ActionView::Helpers::FormBuilder,
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
<%= turbo_frame_tag dom_id(transaction), class: "grid grid-cols-12 items-center text-gray-900 text-sm font-medium p-4" do %>
|
|
||||||
<div class="pr-10 flex items-center gap-4 <%= unconfirmed_transfer?(transaction) ? "col-span-10" : "col-span-4" %>">
|
|
||||||
<%= check_box_tag dom_id(transaction, "selection"),
|
|
||||||
class: "maybe-checkbox maybe-checkbox--light",
|
|
||||||
data: { id: transaction.id, "bulk-select-target": "row", action: "bulk-select#toggleRowSelection" } %>
|
|
||||||
|
|
||||||
<div class="max-w-full">
|
|
||||||
<%= render "transactions/name", transaction: transaction %>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<% if unconfirmed_transfer?(transaction) %>
|
|
||||||
<%= form_with url: unmark_transfers_transactions_path, builder: ActionView::Helpers::FormBuilder, class: "flex items-center", data: {
|
|
||||||
turbo_confirm: {
|
|
||||||
title: t(".remove_transfer"),
|
|
||||||
body: t(".remove_transfer_body"),
|
|
||||||
accept: t(".remove_transfer_confirm"),
|
|
||||||
},
|
|
||||||
turbo_frame: "_top"
|
|
||||||
} do |f| %>
|
|
||||||
<%= f.hidden_field "bulk_update[transaction_ids][]", value: transaction.id %>
|
|
||||||
<%= f.button class: "flex items-center justify-center group", title: "Remove transfer" do %>
|
|
||||||
<%= lucide_icon "arrow-left-right", class: "group-hover:hidden text-gray-500 w-4 h-4" %>
|
|
||||||
<%= lucide_icon "unlink", class: "hidden group-hover:inline-block text-gray-900 w-4 h-4" %>
|
|
||||||
<% end %>
|
|
||||||
<% end %>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<% unless unconfirmed_transfer?(transaction) %>
|
|
||||||
<div class="col-span-3">
|
|
||||||
<%= render "categories/menu", transaction: transaction %>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<%= link_to transaction.account.name,
|
|
||||||
account_path(transaction.account),
|
|
||||||
data: { turbo_frame: "_top" },
|
|
||||||
class: ["col-span-3 hover:underline"] %>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<div class="col-span-2 ml-auto">
|
|
||||||
<%= render "transactions/amount", transaction: transaction %>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
|
@ -1,17 +1,16 @@
|
||||||
<div class="space-y-4 h-full flex flex-col overflow-y-auto">
|
<div class="space-y-4 h-full flex flex-col overflow-y-auto">
|
||||||
<%= render "header" %>
|
<%= render "header" %>
|
||||||
|
|
||||||
<%= render partial: "transactions/summary", locals: { totals: @totals } %>
|
<%= render "summary", totals: @totals %>
|
||||||
|
|
||||||
<div id="transactions" data-controller="bulk-select" data-bulk-select-resource-value="<%= t(".transaction") %>" class="overflow-y-auto flex flex-col bg-white rounded-xl border border-alpha-black-25 shadow-xs pb-4">
|
<div id="transactions" data-controller="bulk-select" data-bulk-select-resource-value="<%= t(".transaction") %>" class="overflow-y-auto flex flex-col bg-white rounded-xl border border-alpha-black-25 shadow-xs pb-4">
|
||||||
|
|
||||||
<div class="p-4 pb-0">
|
<div class="p-4 pb-0">
|
||||||
<%= render partial: "transactions/searches/search", locals: { transactions: @transactions } %>
|
<%= render partial: "transactions/searches/search", locals: { transactions: @transactions } %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<% if @transactions.present? %>
|
<% if @transactions.present? %>
|
||||||
<div hidden id="transaction-selection-bar" data-bulk-select-target="selectionBar">
|
<div hidden id="transaction-selection-bar" data-bulk-select-target="selectionBar">
|
||||||
<%= render "selection_bar" %>
|
<%= render "account/transactions/selection_bar" %>
|
||||||
</div>
|
</div>
|
||||||
<div class="grow overflow-y-auto px-4">
|
<div class="grow overflow-y-auto px-4">
|
||||||
<div class="grid grid-cols-12 bg-gray-25 rounded-xl px-5 py-3 text-xs uppercase font-medium text-gray-500 items-center mb-4">
|
<div class="grid grid-cols-12 bg-gray-25 rounded-xl px-5 py-3 text-xs uppercase font-medium text-gray-500 items-center mb-4">
|
||||||
|
@ -27,13 +26,13 @@
|
||||||
<p class="col-span-2 justify-self-end">amount</p>
|
<p class="col-span-2 justify-self-end">amount</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
<% group_transactions_by_date(@transactions).each do |date, group| %>
|
<% group_transactions_by_date(@transactions).each do |date, group| %>
|
||||||
<%= render partial: "date_group", locals: { date:, group: } %>
|
<%= render "account/transactions/transaction_group", date:, transactions: group[:transactions], transfers: group[:transfers] %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<%= render "empty" %>
|
<%= render "account/transactions/empty" %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<div class="px-4">
|
<div class="px-4">
|
||||||
|
@ -41,6 +40,5 @@
|
||||||
<%= render "pagination", pagy: @pagy %>
|
<%= render "pagination", pagy: @pagy %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
49
config/locales/views/account/transactions/en.yml
Normal file
49
config/locales/views/account/transactions/en.yml
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
---
|
||||||
|
en:
|
||||||
|
account:
|
||||||
|
transactions:
|
||||||
|
destroy:
|
||||||
|
success: Transaction deleted successfully
|
||||||
|
empty:
|
||||||
|
description: Try adding a transaction, editing filters or refining your search
|
||||||
|
title: No transactions found
|
||||||
|
index:
|
||||||
|
new: New transaction
|
||||||
|
no_transactions: No transactions for this account yet.
|
||||||
|
transaction: transaction
|
||||||
|
transactions: Transactions
|
||||||
|
loading:
|
||||||
|
loading: Loading transactions...
|
||||||
|
selection_bar:
|
||||||
|
mark_transfers: Mark as transfers?
|
||||||
|
mark_transfers_confirm: Mark as transfers
|
||||||
|
mark_transfers_message: By marking transactions as transfers, they will no
|
||||||
|
longer be included in income or spending calculations.
|
||||||
|
show:
|
||||||
|
account_label: Account
|
||||||
|
account_placeholder: Select an account
|
||||||
|
additional: Additional
|
||||||
|
category_label: Category
|
||||||
|
category_placeholder: Select a category
|
||||||
|
date_label: Date
|
||||||
|
delete: Delete
|
||||||
|
delete_subtitle: This permanently deletes the transaction, affects your historical
|
||||||
|
balances, and cannot be undone.
|
||||||
|
delete_title: Delete transaction
|
||||||
|
exclude_subtitle: This excludes the transaction from any in-app features or
|
||||||
|
analytics.
|
||||||
|
exclude_title: Exclude transaction
|
||||||
|
merchant_label: Merchant
|
||||||
|
merchant_placeholder: Select a merchant
|
||||||
|
name_label: Name
|
||||||
|
note_label: Notes
|
||||||
|
note_placeholder: Enter a note
|
||||||
|
overview: Overview
|
||||||
|
settings: Settings
|
||||||
|
tags_label: Select one or more tags
|
||||||
|
transaction:
|
||||||
|
remove_transfer: Remove transfer
|
||||||
|
remove_transfer_body: This will remove the transfer from this transaction
|
||||||
|
remove_transfer_confirm: Confirm
|
||||||
|
update:
|
||||||
|
success: Transaction updated successfully
|
|
@ -9,7 +9,12 @@ en:
|
||||||
index:
|
index:
|
||||||
change: change
|
change: change
|
||||||
date: date
|
date: date
|
||||||
|
new_entry: New entry
|
||||||
|
no_valuations: No valuations for this account yet
|
||||||
|
valuations: Value history
|
||||||
value: value
|
value: value
|
||||||
|
loading:
|
||||||
|
loading: Loading history...
|
||||||
update:
|
update:
|
||||||
success: Valuation updated
|
success: Valuation updated
|
||||||
valuation:
|
valuation:
|
||||||
|
|
|
@ -58,12 +58,9 @@ en:
|
||||||
confirm_title: Delete account?
|
confirm_title: Delete account?
|
||||||
edit: Edit
|
edit: Edit
|
||||||
import: Import transactions
|
import: Import transactions
|
||||||
loading_history: Loading account history...
|
|
||||||
new_entry: New entry
|
|
||||||
sync_message_missing_rates: Since exchange rates haven't been synced, balance
|
sync_message_missing_rates: Since exchange rates haven't been synced, balance
|
||||||
graphs may not reflect accurate values.
|
graphs may not reflect accurate values.
|
||||||
sync_message_unknown_error: An error has occurred during the sync.
|
sync_message_unknown_error: An error has occurred during the sync.
|
||||||
valuations: Value history
|
|
||||||
summary:
|
summary:
|
||||||
new: New account
|
new: New account
|
||||||
sync:
|
sync:
|
||||||
|
|
|
@ -24,11 +24,6 @@ en:
|
||||||
success: "%{count} transactions updated"
|
success: "%{count} transactions updated"
|
||||||
create:
|
create:
|
||||||
success: New transaction created successfully
|
success: New transaction created successfully
|
||||||
destroy:
|
|
||||||
success: Transaction deleted successfully
|
|
||||||
empty:
|
|
||||||
description: Try adding a transaction, editing filters or refining your search
|
|
||||||
title: No transactions found
|
|
||||||
form:
|
form:
|
||||||
account: Account
|
account: Account
|
||||||
account_prompt: Select an Account
|
account_prompt: Select an Account
|
||||||
|
@ -52,38 +47,5 @@ en:
|
||||||
success: Marked as transfer
|
success: Marked as transfer
|
||||||
pagination:
|
pagination:
|
||||||
rows_per_page: Rows per page
|
rows_per_page: Rows per page
|
||||||
selection_bar:
|
|
||||||
mark_transfers: Mark as transfers?
|
|
||||||
mark_transfers_confirm: Mark as transfers
|
|
||||||
mark_transfers_message: By marking transactions as transfers, they will no longer
|
|
||||||
be included in income or spending calculations.
|
|
||||||
show:
|
|
||||||
account_label: Account
|
|
||||||
account_placeholder: Select an account
|
|
||||||
additional: Additional
|
|
||||||
category_label: Category
|
|
||||||
category_placeholder: Select a category
|
|
||||||
date_label: Date
|
|
||||||
delete: Delete
|
|
||||||
delete_subtitle: This permanently deletes the transaction, affects your historical
|
|
||||||
balances, and cannot be undone.
|
|
||||||
delete_title: Delete transaction
|
|
||||||
exclude_subtitle: This excludes the transaction from any in-app features or
|
|
||||||
analytics.
|
|
||||||
exclude_title: Exclude transaction
|
|
||||||
merchant_label: Merchant
|
|
||||||
merchant_placeholder: Select a merchant
|
|
||||||
name_label: Name
|
|
||||||
note_label: Notes
|
|
||||||
note_placeholder: Enter a note
|
|
||||||
overview: Overview
|
|
||||||
settings: Settings
|
|
||||||
tags_label: Select one or more tags
|
|
||||||
transaction:
|
|
||||||
remove_transfer: Remove transfer
|
|
||||||
remove_transfer_body: This will remove the transfer from this transaction
|
|
||||||
remove_transfer_confirm: Confirm
|
|
||||||
unmark_transfers:
|
unmark_transfers:
|
||||||
success: Transfer removed
|
success: Transfer removed
|
||||||
update:
|
|
||||||
success: Transaction updated successfully
|
|
||||||
|
|
|
@ -51,23 +51,12 @@ Rails.application.routes.draw do
|
||||||
|
|
||||||
resources :merchants, only: %i[ index new create edit update destroy ]
|
resources :merchants, only: %i[ index new create edit update destroy ]
|
||||||
|
|
||||||
namespace :transaction do
|
|
||||||
resources :rows, only: %i[ show update ]
|
|
||||||
resources :rules, only: %i[ index ]
|
|
||||||
end
|
|
||||||
|
|
||||||
resources :transactions do
|
|
||||||
collection do
|
|
||||||
post "bulk_delete"
|
|
||||||
get "bulk_edit"
|
|
||||||
post "bulk_update"
|
|
||||||
post "mark_transfers"
|
|
||||||
post "unmark_transfers"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
namespace :account do
|
namespace :account do
|
||||||
resources :transfers, only: %i[ new create destroy ]
|
resources :transfers, only: %i[ new create destroy ]
|
||||||
|
|
||||||
|
namespace :transaction do
|
||||||
|
resources :rules, only: %i[ index ]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
resources :accounts do
|
resources :accounts do
|
||||||
|
@ -82,7 +71,22 @@ Rails.application.routes.draw do
|
||||||
|
|
||||||
scope module: :account do
|
scope module: :account do
|
||||||
resource :logo, only: :show
|
resource :logo, only: :show
|
||||||
|
|
||||||
resources :valuations
|
resources :valuations
|
||||||
|
|
||||||
|
resources :transactions, only: %i[ index show update destroy ] do
|
||||||
|
resource :row, only: %i[ show update ], module: :transaction
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
resources :transactions, only: %i[ index new create ] do
|
||||||
|
collection do
|
||||||
|
post "bulk_delete"
|
||||||
|
get "bulk_edit"
|
||||||
|
post "bulk_update"
|
||||||
|
post "mark_transfers"
|
||||||
|
post "unmark_transfers"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
15
db/migrate/20240621212528_rename_transactions_table.rb
Normal file
15
db/migrate/20240621212528_rename_transactions_table.rb
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
class RenameTransactionsTable < ActiveRecord::Migration[7.2]
|
||||||
|
def change
|
||||||
|
rename_table :transactions, :account_transactions
|
||||||
|
|
||||||
|
reversible do |dir|
|
||||||
|
dir.up do
|
||||||
|
Tagging.where(taggable_type: 'Transaction').update_all(taggable_type: "Account::Transaction")
|
||||||
|
end
|
||||||
|
|
||||||
|
dir.down do
|
||||||
|
Tagging.where(taggable_type: 'Account::Transaction').update_all(taggable_type: "Transaction")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
50
db/schema.rb
generated
50
db/schema.rb
generated
|
@ -10,7 +10,7 @@
|
||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema[7.2].define(version: 2024_06_20_221801) do
|
ActiveRecord::Schema[7.2].define(version: 2024_06_21_212528) do
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "pgcrypto"
|
enable_extension "pgcrypto"
|
||||||
enable_extension "plpgsql"
|
enable_extension "plpgsql"
|
||||||
|
@ -32,6 +32,26 @@ ActiveRecord::Schema[7.2].define(version: 2024_06_20_221801) do
|
||||||
t.index ["account_id"], name: "index_account_balances_on_account_id"
|
t.index ["account_id"], name: "index_account_balances_on_account_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
create_table "account_transactions", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
||||||
|
t.string "name"
|
||||||
|
t.date "date", null: false
|
||||||
|
t.decimal "amount", precision: 19, scale: 4, null: false
|
||||||
|
t.string "currency", default: "USD", null: false
|
||||||
|
t.uuid "account_id", null: false
|
||||||
|
t.datetime "created_at", null: false
|
||||||
|
t.datetime "updated_at", null: false
|
||||||
|
t.uuid "category_id"
|
||||||
|
t.boolean "excluded", default: false
|
||||||
|
t.text "notes"
|
||||||
|
t.uuid "merchant_id"
|
||||||
|
t.uuid "transfer_id"
|
||||||
|
t.boolean "marked_as_transfer", default: false, null: false
|
||||||
|
t.index ["account_id"], name: "index_account_transactions_on_account_id"
|
||||||
|
t.index ["category_id"], name: "index_account_transactions_on_category_id"
|
||||||
|
t.index ["merchant_id"], name: "index_account_transactions_on_merchant_id"
|
||||||
|
t.index ["transfer_id"], name: "index_account_transactions_on_transfer_id"
|
||||||
|
end
|
||||||
|
|
||||||
create_table "account_transfers", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
create_table "account_transfers", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
|
@ -309,26 +329,6 @@ ActiveRecord::Schema[7.2].define(version: 2024_06_20_221801) do
|
||||||
t.index ["family_id"], name: "index_tags_on_family_id"
|
t.index ["family_id"], name: "index_tags_on_family_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "transactions", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
|
||||||
t.string "name"
|
|
||||||
t.date "date", null: false
|
|
||||||
t.decimal "amount", precision: 19, scale: 4, null: false
|
|
||||||
t.string "currency", default: "USD", null: false
|
|
||||||
t.uuid "account_id", null: false
|
|
||||||
t.datetime "created_at", null: false
|
|
||||||
t.datetime "updated_at", null: false
|
|
||||||
t.uuid "category_id"
|
|
||||||
t.boolean "excluded", default: false
|
|
||||||
t.text "notes"
|
|
||||||
t.uuid "merchant_id"
|
|
||||||
t.uuid "transfer_id"
|
|
||||||
t.boolean "marked_as_transfer", default: false, null: false
|
|
||||||
t.index ["account_id"], name: "index_transactions_on_account_id"
|
|
||||||
t.index ["category_id"], name: "index_transactions_on_category_id"
|
|
||||||
t.index ["merchant_id"], name: "index_transactions_on_merchant_id"
|
|
||||||
t.index ["transfer_id"], name: "index_transactions_on_transfer_id"
|
|
||||||
end
|
|
||||||
|
|
||||||
create_table "users", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
create_table "users", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
||||||
t.uuid "family_id", null: false
|
t.uuid "family_id", null: false
|
||||||
t.string "first_name"
|
t.string "first_name"
|
||||||
|
@ -352,6 +352,10 @@ ActiveRecord::Schema[7.2].define(version: 2024_06_20_221801) do
|
||||||
end
|
end
|
||||||
|
|
||||||
add_foreign_key "account_balances", "accounts", on_delete: :cascade
|
add_foreign_key "account_balances", "accounts", on_delete: :cascade
|
||||||
|
add_foreign_key "account_transactions", "account_transfers", column: "transfer_id"
|
||||||
|
add_foreign_key "account_transactions", "accounts", on_delete: :cascade
|
||||||
|
add_foreign_key "account_transactions", "categories", on_delete: :nullify
|
||||||
|
add_foreign_key "account_transactions", "merchants"
|
||||||
add_foreign_key "account_valuations", "accounts", on_delete: :cascade
|
add_foreign_key "account_valuations", "accounts", on_delete: :cascade
|
||||||
add_foreign_key "accounts", "families"
|
add_foreign_key "accounts", "families"
|
||||||
add_foreign_key "accounts", "institutions"
|
add_foreign_key "accounts", "institutions"
|
||||||
|
@ -363,9 +367,5 @@ ActiveRecord::Schema[7.2].define(version: 2024_06_20_221801) do
|
||||||
add_foreign_key "merchants", "families"
|
add_foreign_key "merchants", "families"
|
||||||
add_foreign_key "taggings", "tags"
|
add_foreign_key "taggings", "tags"
|
||||||
add_foreign_key "tags", "families"
|
add_foreign_key "tags", "families"
|
||||||
add_foreign_key "transactions", "account_transfers", column: "transfer_id"
|
|
||||||
add_foreign_key "transactions", "accounts", on_delete: :cascade
|
|
||||||
add_foreign_key "transactions", "categories", on_delete: :nullify
|
|
||||||
add_foreign_key "transactions", "merchants"
|
|
||||||
add_foreign_key "users", "families"
|
add_foreign_key "users", "families"
|
||||||
end
|
end
|
||||||
|
|
40
test/controllers/account/transactions_controller_test.rb
Normal file
40
test/controllers/account/transactions_controller_test.rb
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
require "test_helper"
|
||||||
|
|
||||||
|
class Account::TransactionsControllerTest < ActionDispatch::IntegrationTest
|
||||||
|
setup do
|
||||||
|
sign_in @user = users(:family_admin)
|
||||||
|
@transaction = account_transactions(:checking_one)
|
||||||
|
@account = @transaction.account
|
||||||
|
@recent_transactions = @user.family.transactions.ordered.limit(20).to_a
|
||||||
|
end
|
||||||
|
|
||||||
|
test "should show transaction" do
|
||||||
|
get account_transaction_url(@transaction.account, @transaction)
|
||||||
|
assert_response :success
|
||||||
|
end
|
||||||
|
|
||||||
|
test "should update transaction" do
|
||||||
|
patch account_transaction_url(@transaction.account, @transaction), params: {
|
||||||
|
transaction: {
|
||||||
|
account_id: @transaction.account_id,
|
||||||
|
amount: @transaction.amount,
|
||||||
|
currency: @transaction.currency,
|
||||||
|
date: @transaction.date,
|
||||||
|
name: @transaction.name,
|
||||||
|
tag_ids: [ Tag.first.id, Tag.second.id ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_redirected_to account_transaction_url(@transaction.account, @transaction)
|
||||||
|
assert_enqueued_with(job: AccountSyncJob)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "should destroy transaction" do
|
||||||
|
assert_difference("Account::Transaction.count", -1) do
|
||||||
|
delete account_transaction_url(@transaction.account, @transaction)
|
||||||
|
end
|
||||||
|
|
||||||
|
assert_redirected_to account_url(@transaction.account)
|
||||||
|
assert_enqueued_with(job: AccountSyncJob)
|
||||||
|
end
|
||||||
|
end
|
|
@ -26,7 +26,7 @@ class Account::TransfersControllerTest < ActionDispatch::IntegrationTest
|
||||||
end
|
end
|
||||||
|
|
||||||
test "can destroy transfer" do
|
test "can destroy transfer" do
|
||||||
assert_difference -> { Account::Transfer.count } => -1, -> { Transaction.count } => 0 do
|
assert_difference -> { Account::Transfer.count } => -1, -> { Account::Transaction.count } => 0 do
|
||||||
delete account_transfer_url(account_transfers(:credit_card_payment))
|
delete account_transfer_url(account_transfers(:credit_card_payment))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -37,7 +37,7 @@ class CategoriesControllerTest < ActionDispatch::IntegrationTest
|
||||||
|
|
||||||
assert_difference "Category.count", +1 do
|
assert_difference "Category.count", +1 do
|
||||||
post categories_url, params: {
|
post categories_url, params: {
|
||||||
transaction_id: transactions(:checking_one).id,
|
transaction_id: account_transactions(:checking_one).id,
|
||||||
category: {
|
category: {
|
||||||
name: "New Category",
|
name: "New Category",
|
||||||
color: color } }
|
color: color } }
|
||||||
|
@ -48,7 +48,7 @@ class CategoriesControllerTest < ActionDispatch::IntegrationTest
|
||||||
assert_redirected_to transactions_url
|
assert_redirected_to transactions_url
|
||||||
assert_equal "New Category", new_category.name
|
assert_equal "New Category", new_category.name
|
||||||
assert_equal color, new_category.color
|
assert_equal color, new_category.color
|
||||||
assert_equal transactions(:checking_one).reload.category, new_category
|
assert_equal account_transactions(:checking_one).reload.category, new_category
|
||||||
end
|
end
|
||||||
|
|
||||||
test "edit" do
|
test "edit" do
|
||||||
|
|
|
@ -30,7 +30,7 @@ class Category::DeletionsControllerTest < ActionDispatch::IntegrationTest
|
||||||
assert_not_empty @category.transactions
|
assert_not_empty @category.transactions
|
||||||
|
|
||||||
assert_difference "Category.count", -1 do
|
assert_difference "Category.count", -1 do
|
||||||
assert_difference "Transaction.where(category: nil).count", @category.transactions.count do
|
assert_difference "Account::Transaction.where(category: nil).count", @category.transactions.count do
|
||||||
post category_deletions_url(@category)
|
post category_deletions_url(@category)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,10 +3,81 @@ require "test_helper"
|
||||||
class TransactionsControllerTest < ActionDispatch::IntegrationTest
|
class TransactionsControllerTest < ActionDispatch::IntegrationTest
|
||||||
setup do
|
setup do
|
||||||
sign_in @user = users(:family_admin)
|
sign_in @user = users(:family_admin)
|
||||||
@transaction = transactions(:checking_one)
|
@transaction = account_transactions(:checking_one)
|
||||||
@recent_transactions = @user.family.transactions.ordered.limit(20).to_a
|
@recent_transactions = @user.family.transactions.ordered.limit(20).to_a
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "should get new" do
|
||||||
|
get new_transaction_url
|
||||||
|
assert_response :success
|
||||||
|
end
|
||||||
|
|
||||||
|
test "prefills account_id" do
|
||||||
|
get new_transaction_url(account_id: @transaction.account.id)
|
||||||
|
assert_response :success
|
||||||
|
assert_select "option[selected][value='#{@transaction.account.id}']"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "should create transaction" do
|
||||||
|
account = @user.family.accounts.first
|
||||||
|
transaction_params = {
|
||||||
|
account_id: account.id,
|
||||||
|
amount: 100.45,
|
||||||
|
currency: "USD",
|
||||||
|
date: Date.current,
|
||||||
|
name: "Test transaction"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_difference("Account::Transaction.count") do
|
||||||
|
post transactions_url, params: { transaction: transaction_params }
|
||||||
|
end
|
||||||
|
|
||||||
|
assert_equal transaction_params[:amount].to_d, Account::Transaction.order(created_at: :desc).first.amount
|
||||||
|
assert_equal "New transaction created successfully", flash[:notice]
|
||||||
|
assert_enqueued_with(job: AccountSyncJob)
|
||||||
|
assert_redirected_to account_url(account)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "expenses are positive" do
|
||||||
|
assert_difference("Account::Transaction.count") do
|
||||||
|
post transactions_url, params: {
|
||||||
|
transaction: {
|
||||||
|
nature: "expense",
|
||||||
|
account_id: @transaction.account_id,
|
||||||
|
amount: @transaction.amount,
|
||||||
|
currency: @transaction.currency,
|
||||||
|
date: @transaction.date,
|
||||||
|
name: @transaction.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
created_transaction = Account::Transaction.order(created_at: :desc).first
|
||||||
|
|
||||||
|
assert_redirected_to account_url(@transaction.account)
|
||||||
|
assert created_transaction.amount.positive?, "Amount should be positive"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "incomes are negative" do
|
||||||
|
assert_difference("Account::Transaction.count") do
|
||||||
|
post transactions_url, params: {
|
||||||
|
transaction: {
|
||||||
|
nature: "income",
|
||||||
|
account_id: @transaction.account_id,
|
||||||
|
amount: @transaction.amount,
|
||||||
|
currency: @transaction.currency,
|
||||||
|
date: @transaction.date,
|
||||||
|
name: @transaction.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
created_transaction = Account::Transaction.order(created_at: :desc).first
|
||||||
|
|
||||||
|
assert_redirected_to account_url(@transaction.account)
|
||||||
|
assert created_transaction.amount.negative?, "Amount should be negative"
|
||||||
|
end
|
||||||
|
|
||||||
test "should get paginated index with most recent transactions first" do
|
test "should get paginated index with most recent transactions first" do
|
||||||
get transactions_url
|
get transactions_url
|
||||||
assert_response :success
|
assert_response :success
|
||||||
|
@ -49,104 +120,14 @@ class TransactionsControllerTest < ActionDispatch::IntegrationTest
|
||||||
assert_dom "#" + dom_id(user_oldest_transaction), count: 1
|
assert_dom "#" + dom_id(user_oldest_transaction), count: 1
|
||||||
end
|
end
|
||||||
|
|
||||||
test "should get new" do
|
|
||||||
get new_transaction_url
|
|
||||||
assert_response :success
|
|
||||||
end
|
|
||||||
|
|
||||||
test "prefills account_id if provided" do
|
|
||||||
get new_transaction_url(account_id: @transaction.account_id)
|
|
||||||
assert_response :success
|
|
||||||
assert_select "option[selected][value='#{@transaction.account_id}']"
|
|
||||||
end
|
|
||||||
|
|
||||||
test "should create transaction" do
|
|
||||||
account = @user.family.accounts.first
|
|
||||||
transaction_params = {
|
|
||||||
account_id: account.id,
|
|
||||||
amount: 100.45,
|
|
||||||
currency: "USD",
|
|
||||||
date: Date.current,
|
|
||||||
name: "Test transaction"
|
|
||||||
}
|
|
||||||
|
|
||||||
assert_difference("Transaction.count") do
|
|
||||||
post transactions_url, params: { transaction: transaction_params }
|
|
||||||
end
|
|
||||||
|
|
||||||
assert_equal transaction_params[:amount].to_d, Transaction.order(created_at: :desc).first.amount
|
|
||||||
assert_equal "New transaction created successfully", flash[:notice]
|
|
||||||
assert_enqueued_with(job: AccountSyncJob)
|
|
||||||
assert_redirected_to transactions_url
|
|
||||||
end
|
|
||||||
|
|
||||||
test "expenses are positive" do
|
|
||||||
assert_difference("Transaction.count") do
|
|
||||||
post transactions_url, params: { transaction: {
|
|
||||||
nature: "expense",
|
|
||||||
account_id: @transaction.account_id,
|
|
||||||
amount: @transaction.amount,
|
|
||||||
currency: @transaction.currency,
|
|
||||||
date: @transaction.date,
|
|
||||||
name: @transaction.name } }
|
|
||||||
end
|
|
||||||
|
|
||||||
assert_redirected_to transactions_url
|
|
||||||
assert Transaction.order(created_at: :desc).first.amount.positive?, "Amount should be positive"
|
|
||||||
end
|
|
||||||
|
|
||||||
test "incomes are negative" do
|
|
||||||
assert_difference("Transaction.count") do
|
|
||||||
post transactions_url, params: {
|
|
||||||
transaction: {
|
|
||||||
nature: "income",
|
|
||||||
account_id: @transaction.account_id,
|
|
||||||
amount: @transaction.amount,
|
|
||||||
currency: @transaction.currency,
|
|
||||||
date: @transaction.date,
|
|
||||||
name: @transaction.name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
assert_redirected_to transactions_url
|
|
||||||
assert Transaction.order(created_at: :desc).first.amount.negative?, "Amount should be negative"
|
|
||||||
end
|
|
||||||
|
|
||||||
test "should show transaction" do
|
|
||||||
get transaction_url(@transaction)
|
|
||||||
assert_response :success
|
|
||||||
end
|
|
||||||
|
|
||||||
test "should update transaction" do
|
|
||||||
patch transaction_url(@transaction), params: {
|
|
||||||
transaction: {
|
|
||||||
account_id: @transaction.account_id,
|
|
||||||
amount: @transaction.amount,
|
|
||||||
currency: @transaction.currency,
|
|
||||||
date: @transaction.date,
|
|
||||||
name: @transaction.name,
|
|
||||||
tag_ids: [ Tag.first.id, Tag.second.id ]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assert_redirected_to transaction_url(@transaction)
|
|
||||||
assert_enqueued_with(job: AccountSyncJob)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "should destroy transaction" do
|
|
||||||
assert_difference("Transaction.count", -1) do
|
|
||||||
delete transaction_url(@transaction)
|
|
||||||
end
|
|
||||||
|
|
||||||
assert_redirected_to transactions_url
|
|
||||||
assert_enqueued_with(job: AccountSyncJob)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "can destroy many transactions at once" do
|
test "can destroy many transactions at once" do
|
||||||
delete_count = 10
|
delete_count = 10
|
||||||
assert_difference("Transaction.count", -delete_count) do
|
assert_difference("Account::Transaction.count", -delete_count) do
|
||||||
post bulk_delete_transactions_url, params: { bulk_delete: { transaction_ids: @recent_transactions.first(delete_count).pluck(:id) } }
|
post bulk_delete_transactions_url, params: {
|
||||||
|
bulk_delete: {
|
||||||
|
transaction_ids: @recent_transactions.first(delete_count).pluck(:id)
|
||||||
|
}
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
assert_redirected_to transactions_url
|
assert_redirected_to transactions_url
|
||||||
|
|
4
test/fixtures/taggings.yml
vendored
4
test/fixtures/taggings.yml
vendored
|
@ -1,10 +1,10 @@
|
||||||
one:
|
one:
|
||||||
tag: hawaii_trip
|
tag: hawaii_trip
|
||||||
taggable: checking_one
|
taggable: checking_one
|
||||||
taggable_type: Transaction
|
taggable_type: Account::Transaction
|
||||||
|
|
||||||
two:
|
two:
|
||||||
tag: emergency_fund
|
tag: emergency_fund
|
||||||
taggable: checking_two
|
taggable: checking_two
|
||||||
taggable_type: Transaction
|
taggable_type: Account::Transaction
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
require "test_helper"
|
require "test_helper"
|
||||||
|
|
||||||
class TransactionTest < ActiveSupport::TestCase
|
class Account::TransactionTest < ActiveSupport::TestCase
|
||||||
setup do
|
setup do
|
||||||
@transaction = transactions(:checking_one)
|
@transaction = account_transactions(:checking_one)
|
||||||
@family = families(:dylan_family)
|
@family = families(:dylan_family)
|
||||||
end
|
end
|
||||||
|
|
||||||
# See: https://github.com/maybe-finance/maybe/wiki/vision#signage-of-money
|
# See: https://github.com/maybe-finance/maybe/wiki/vision#signage-of-money
|
||||||
test "negative amounts are inflows, positive amounts are outflows to an account" do
|
test "negative amounts are inflows, positive amounts are outflows to an account" do
|
||||||
inflow_transaction = transactions(:checking_four)
|
inflow_transaction = account_transactions(:checking_four)
|
||||||
outflow_transaction = transactions(:checking_five)
|
outflow_transaction = account_transactions(:checking_five)
|
||||||
|
|
||||||
assert inflow_transaction.amount < 0
|
assert inflow_transaction.amount < 0
|
||||||
assert inflow_transaction.inflow?
|
assert inflow_transaction.inflow?
|
||||||
|
@ -35,8 +35,8 @@ class TransactionTest < ActiveSupport::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
test "triggers sync with correct start date when transaction deleted" do
|
test "triggers sync with correct start date when transaction deleted" do
|
||||||
prior_transaction = transactions(:checking_two) # 12 days ago
|
prior_transaction = account_transactions(:checking_two) # 12 days ago
|
||||||
current_transaction = transactions(:checking_one) # 5 days ago
|
current_transaction = account_transactions(:checking_one) # 5 days ago
|
||||||
current_transaction.destroy!
|
current_transaction.destroy!
|
||||||
|
|
||||||
current_transaction.account.expects(:sync_later).with(prior_transaction.date)
|
current_transaction.account.expects(:sync_later).with(prior_transaction.date)
|
|
@ -15,7 +15,7 @@ class Account::TransferTest < ActiveSupport::TestCase
|
||||||
|
|
||||||
test "transfer must have 2 transactions" do
|
test "transfer must have 2 transactions" do
|
||||||
invalid_transfer_1 = Account::Transfer.new transactions: [ @outflow ]
|
invalid_transfer_1 = Account::Transfer.new transactions: [ @outflow ]
|
||||||
invalid_transfer_2 = Account::Transfer.new transactions: [ @inflow, @outflow, transactions(:savings_four) ]
|
invalid_transfer_2 = Account::Transfer.new transactions: [ @inflow, @outflow, account_transactions(:savings_four) ]
|
||||||
|
|
||||||
assert invalid_transfer_1.invalid?
|
assert invalid_transfer_1.invalid?
|
||||||
assert invalid_transfer_2.invalid?
|
assert invalid_transfer_2.invalid?
|
||||||
|
|
|
@ -99,7 +99,7 @@ class AccountTest < ActiveSupport::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
test "should destroy dependent transactions" do
|
test "should destroy dependent transactions" do
|
||||||
assert_difference("Transaction.count", -@account.transactions.count) do
|
assert_difference("Account::Transaction.count", -@account.transactions.count) do
|
||||||
@account.destroy
|
@account.destroy
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -34,7 +34,7 @@ class CategoryTest < ActiveSupport::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
test "replacing and destroying" do
|
test "replacing and destroying" do
|
||||||
transctions = categories(:food_and_drink).transactions.to_a
|
transactions = categories(:food_and_drink).transactions.to_a
|
||||||
|
|
||||||
categories(:food_and_drink).replace_and_destroy!(categories(:income))
|
categories(:food_and_drink).replace_and_destroy!(categories(:income))
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,7 @@ class ImportTest < ActiveSupport::TestCase
|
||||||
# Fixtures already define "Food & Drink" and "Income", so these should not be created
|
# Fixtures already define "Food & Drink" and "Income", so these should not be created
|
||||||
# "Shopping" is a new category, but should only be created 1x during import
|
# "Shopping" is a new category, but should only be created 1x during import
|
||||||
assert_difference \
|
assert_difference \
|
||||||
-> { Transaction.count } => 4,
|
-> { Account::Transaction.count } => 4,
|
||||||
-> { Category.count } => 1,
|
-> { Category.count } => 1,
|
||||||
-> { Tagging.count } => 4,
|
-> { Tagging.count } => 4,
|
||||||
-> { Tag.count } => 2 do
|
-> { Tag.count } => 2 do
|
||||||
|
@ -59,11 +59,11 @@ class ImportTest < ActiveSupport::TestCase
|
||||||
|
|
||||||
test "publishes a valid import with missing data" do
|
test "publishes a valid import with missing data" do
|
||||||
@empty_import.update! raw_csv_str: valid_csv_with_missing_data
|
@empty_import.update! raw_csv_str: valid_csv_with_missing_data
|
||||||
assert_difference -> { Category.count } => 1, -> { Transaction.count } => 2 do
|
assert_difference -> { Category.count } => 1, -> { Account::Transaction.count } => 2 do
|
||||||
@empty_import.publish
|
@empty_import.publish
|
||||||
end
|
end
|
||||||
|
|
||||||
assert_not_nil Transaction.find_sole_by(name: Import::FALLBACK_TRANSACTION_NAME)
|
assert_not_nil Account::Transaction.find_sole_by(name: Import::FALLBACK_TRANSACTION_NAME)
|
||||||
|
|
||||||
@empty_import.reload
|
@empty_import.reload
|
||||||
|
|
||||||
|
@ -73,7 +73,7 @@ class ImportTest < ActiveSupport::TestCase
|
||||||
test "failed publish results in error status" do
|
test "failed publish results in error status" do
|
||||||
@empty_import.update! raw_csv_str: valid_csv_with_invalid_values
|
@empty_import.update! raw_csv_str: valid_csv_with_invalid_values
|
||||||
|
|
||||||
assert_difference "Transaction.count", 0 do
|
assert_difference "Account::Transaction.count", 0 do
|
||||||
@empty_import.publish
|
@empty_import.publish
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ class SettingsTest < ApplicationSystemTestCase
|
||||||
[ "Tags", "Tags", tags_path ],
|
[ "Tags", "Tags", tags_path ],
|
||||||
[ "Categories", "Categories", categories_path ],
|
[ "Categories", "Categories", categories_path ],
|
||||||
[ "Merchants", "Merchants", merchants_path ],
|
[ "Merchants", "Merchants", merchants_path ],
|
||||||
[ "Rules", "Rules", transaction_rules_path ],
|
[ "Rules", "Rules", account_transaction_rules_path ],
|
||||||
[ "Imports", "Imports", imports_path ],
|
[ "Imports", "Imports", imports_path ],
|
||||||
[ "What's New", "What's New", changelog_path ],
|
[ "What's New", "What's New", changelog_path ],
|
||||||
[ "Feedback", "Feedback", feedback_path ],
|
[ "Feedback", "Feedback", feedback_path ],
|
||||||
|
|
|
@ -132,7 +132,7 @@ class TransactionsTest < ApplicationSystemTestCase
|
||||||
private
|
private
|
||||||
|
|
||||||
def number_of_transactions_on_page
|
def number_of_transactions_on_page
|
||||||
page_size = 50
|
page_size = 10
|
||||||
|
|
||||||
[ @user.family.transactions.where(transfer_id: nil).count, page_size ].min
|
[ @user.family.transactions.where(transfer_id: nil).count, page_size ].min
|
||||||
end
|
end
|
||||||
|
|
|
@ -33,8 +33,8 @@ class TransfersTest < ApplicationSystemTestCase
|
||||||
|
|
||||||
test "can match 2 transactions and create a transfer" do
|
test "can match 2 transactions and create a transfer" do
|
||||||
transfer_date = Date.current
|
transfer_date = Date.current
|
||||||
outflow = Transaction.create! name: "Outflow from savings account", date: transfer_date, account: accounts(:savings), amount: 100
|
outflow = Account::Transaction.create! name: "Outflow from savings account", date: transfer_date, account: accounts(:savings), amount: 100
|
||||||
inflow = Transaction.create! name: "Inflow to checking account", date: transfer_date, account: accounts(:checking), amount: -100
|
inflow = Account::Transaction.create! name: "Inflow to checking account", date: transfer_date, account: accounts(:checking), amount: -100
|
||||||
|
|
||||||
visit transactions_url
|
visit transactions_url
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue