Skip to content
This repository was archived by the owner on Jul 27, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ GEM
bindex (0.8.1)
bootsnap (1.18.4)
msgpack (~> 1.2)
brakeman (6.2.2)
brakeman (7.0.0)
racc
builder (3.3.0)
capybara (3.40.0)
Expand Down
13 changes: 13 additions & 0 deletions app/assets/stylesheets/application.tailwind.css
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@
@apply focus:opacity-100 focus:outline-none focus:ring-0;
@apply placeholder-shown:opacity-50;
@apply disabled:text-gray-400;
@apply text-ellipsis overflow-hidden whitespace-nowrap;
}

select.form-field__input {
@apply pr-8;
}

.form-field__radio {
Expand All @@ -51,10 +56,18 @@
@apply border-alpha-black-200 checked:bg-gray-900 checked:ring-gray-900 focus:ring-gray-900 focus-visible:ring-gray-900 checked:hover:bg-gray-500;
}

[type='checkbox'].maybe-checkbox--light:disabled {
@apply cursor-not-allowed opacity-80 bg-gray-50 border-gray-200 checked:bg-gray-400 checked:ring-gray-400;
}

[type='checkbox'].maybe-checkbox--dark {
@apply ring-gray-900 checked:text-white;
}

[type='checkbox'].maybe-checkbox--dark:disabled {
@apply cursor-not-allowed opacity-80 ring-gray-600;
}

[type='checkbox'].maybe-checkbox--dark:checked {
background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='111827' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e");
}
Expand Down
18 changes: 0 additions & 18 deletions app/controllers/account/transactions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,6 @@ def bulk_update
redirect_back_or_to transactions_url, notice: t(".success", count: updated)
end

def mark_transfers
Current.family
.entries
.where(id: bulk_update_params[:entry_ids])
.mark_transfers!

redirect_back_or_to transactions_url, notice: t(".success")
end

def unmark_transfers
Current.family
.entries
.where(id: bulk_update_params[:entry_ids])
.update_all marked_as_transfer: false

redirect_back_or_to transactions_url, notice: t(".success")
end

private
def bulk_delete_params
params.require(:bulk_delete).permit(entry_ids: [])
Expand Down
56 changes: 56 additions & 0 deletions app/controllers/account/transfer_matches_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
class Account::TransferMatchesController < ApplicationController
before_action :set_entry

def new
@accounts = Current.family.accounts.alphabetically.where.not(id: @entry.account_id)
@transfer_match_candidates = @entry.transfer_match_candidates
end

def create
@transfer = build_transfer
@transfer.save!
@transfer.sync_account_later

redirect_back_or_to transactions_path, notice: t(".success")
end

private
def set_entry
@entry = Current.family.entries.find(params[:transaction_id])
end

def transfer_match_params
params.require(:transfer_match).permit(:method, :matched_entry_id, :target_account_id)
end

def build_transfer
if transfer_match_params[:method] == "new"
target_account = Current.family.accounts.find(transfer_match_params[:target_account_id])

missing_transaction = Account::Transaction.new(
entry: target_account.entries.build(
amount: @entry.amount * -1,
currency: @entry.currency,
date: @entry.date,
name: "Transfer to #{@entry.amount.negative? ? @entry.account.name : target_account.name}",
)
)

transfer = Transfer.find_or_initialize_by(
inflow_transaction: @entry.amount.positive? ? missing_transaction : @entry.account_transaction,
outflow_transaction: @entry.amount.positive? ? @entry.account_transaction : missing_transaction
)
transfer.status = "confirmed"
transfer
else
target_transaction = Current.family.entries.find(transfer_match_params[:matched_entry_id])

transfer = Transfer.find_or_initialize_by(
inflow_transaction: @entry.amount.negative? ? @entry.account_transaction : target_transaction.account_transaction,
outflow_transaction: @entry.amount.negative? ? target_transaction.account_transaction : @entry.account_transaction
)
transfer.status = "confirmed"
transfer
end
end
end
61 changes: 0 additions & 61 deletions app/controllers/account/transfers_controller.rb

This file was deleted.

13 changes: 8 additions & 5 deletions app/controllers/concerns/entryable_resource.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,14 @@ def update
respond_to do |format|
format.html { redirect_back_or_to account_path(@entry.account), notice: t("account.entries.update.success") }
format.turbo_stream do
render turbo_stream: turbo_stream.replace(
"header_account_entry_#{@entry.id}",
partial: "#{entryable_type.name.underscore.pluralize}/header",
locals: { entry: @entry }
)
render turbo_stream: [
turbo_stream.replace(
"header_account_entry_#{@entry.id}",
partial: "#{entryable_type.name.underscore.pluralize}/header",
locals: { entry: @entry }
),
turbo_stream.replace("account_entry_#{@entry.id}", partial: "account/entries/entry", locals: { entry: @entry })
]
end
end
else
Expand Down
11 changes: 8 additions & 3 deletions app/controllers/transactions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,15 @@ def index
search_query = Current.family.transactions.search(@q).includes(:entryable).reverse_chronological
@pagy, @transaction_entries = pagy(search_query, limit: params[:per_page] || "50")

totals_query = search_query.incomes_and_expenses
family_currency = Current.family.currency
count_with_transfers = search_query.count
count_without_transfers = totals_query.count

@totals = {
count: search_query.select { |t| t.currency == Current.family.currency }.count,
income: search_query.income_total(Current.family.currency).abs,
expense: search_query.expense_total(Current.family.currency)
count: ((count_with_transfers - count_without_transfers) / 2) + count_without_transfers,
income: totals_query.income_total(family_currency).abs,
expense: totals_query.expense_total(family_currency)
}
end

Expand Down
66 changes: 66 additions & 0 deletions app/controllers/transfers_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
class TransfersController < ApplicationController
layout :with_sidebar

before_action :set_transfer, only: %i[destroy show update]

def new
@transfer = Transfer.new
end

def show
end

def create
from_account = Current.family.accounts.find(transfer_params[:from_account_id])
to_account = Current.family.accounts.find(transfer_params[:to_account_id])

@transfer = Transfer.from_accounts(
from_account: from_account,
to_account: to_account,
date: transfer_params[:date],
amount: transfer_params[:amount].to_d
)

if @transfer.save
@transfer.sync_account_later

flash[:notice] = t(".success")

respond_to do |format|
format.html { redirect_back_or_to transactions_path }
redirect_target_url = request.referer || transactions_path
format.turbo_stream { render turbo_stream: turbo_stream.action(:redirect, redirect_target_url) }
end
else
render :new, status: :unprocessable_entity
end
end

def update
@transfer.update!(transfer_update_params)
respond_to do |format|
format.html { redirect_back_or_to transactions_url, notice: t(".success") }
format.turbo_stream
end
end

def destroy
@transfer.destroy!
redirect_back_or_to transactions_url, notice: t(".success")
end

private
def set_transfer
@transfer = Transfer.find(params[:id])

raise ActiveRecord::RecordNotFound unless @transfer.belongs_to_family?(Current.family)
end

def transfer_params
params.require(:transfer).permit(:from_account_id, :to_account_id, :amount, :date, :name, :excluded)
end

def transfer_update_params
params.require(:transfer).permit(:notes, :status)
end
end
17 changes: 12 additions & 5 deletions app/helpers/account/entries_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,6 @@ def permitted_entryable_partial_path(entry, relative_partial_path)
"account/entries/entryables/#{permitted_entryable_key(entry)}/#{relative_partial_path}"
end

def unconfirmed_transfer?(entry)
entry.marked_as_transfer? && entry.transfer.nil?
end

def transfer_entries(entries)
transfers = entries.select { |e| e.transfer_id.present? }
transfers.map(&:transfer).uniq
Expand All @@ -18,8 +14,19 @@ def entries_by_date(entries, selectable: true, totals: false)
yield grouped_entries
end

next if content.blank?

render partial: "account/entries/entry_group", locals: { date:, entries: grouped_entries, content:, selectable:, totals: }
end.join.html_safe
end.compact.join.html_safe
end

def entry_name_detailed(entry)
[
entry.date,
format_money(entry.amount_money),
entry.account.name,
entry.display_name
].join(" • ")
end

private
Expand Down
2 changes: 0 additions & 2 deletions app/helpers/account/transfers_helper.rb

This file was deleted.

4 changes: 2 additions & 2 deletions app/helpers/application_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,9 @@ def drawer(reload_on_close: false, &block)
render partial: "shared/drawer", locals: { content:, reload_on_close: }
end

def disclosure(title, &block)
def disclosure(title, default_open: true, &block)
content = capture &block
render partial: "shared/disclosure", locals: { title: title, content: content }
render partial: "shared/disclosure", locals: { title: title, content: content, open: default_open }
end

def sidebar_link_to(name, path, options = {})
Expand Down
18 changes: 18 additions & 0 deletions app/helpers/categories_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,24 @@ def null_category
color: Category::UNCATEGORIZED_COLOR
end

def transfer_category
Category.new \
name: "⇄ Transfer",
color: Category::TRANSFER_COLOR
end

def payment_category
Category.new \
name: "→ Payment",
color: Category::PAYMENT_COLOR
end

def trade_category
Category.new \
name: "Trade",
color: Category::TRADE_COLOR
end

def family_categories
[ null_category ].concat(Current.family.categories.alphabetically)
end
Expand Down
8 changes: 6 additions & 2 deletions app/javascript/controllers/bulk_select_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,9 @@ export default class extends Controller {
}

_rowsForGroup(group) {
return this.rowTargets.filter((row) => group.contains(row));
return this.rowTargets.filter(
(row) => group.contains(row) && !row.disabled,
);
}

_addToSelection(idToAdd) {
Expand All @@ -115,7 +117,9 @@ export default class extends Controller {
}

_selectAll() {
this.selectedIdsValue = this.rowTargets.map((t) => t.dataset.id);
this.selectedIdsValue = this.rowTargets
.filter((t) => !t.disabled)
.map((t) => t.dataset.id);
}

_updateView = () => {
Expand Down
Loading
Loading