Merchants improvements (#594)

* FIX logos

* Implement merchant mods

* FIX confirm issue

* FIX linter

* Add recently seen merchants to re-add if needed

* Update merge.html.erb

* FIX do security check

* Add error handling for update failures.
This commit is contained in:
soky srm
2026-01-09 19:38:04 +01:00
committed by GitHub
parent 140ea78b0e
commit 76dc91377c
25 changed files with 431 additions and 36 deletions

View File

@@ -8,6 +8,15 @@ class FamilyMerchantsController < ApplicationController
@family_merchants = Current.family.merchants.alphabetically
@provider_merchants = Current.family.assigned_merchants.where(type: "ProviderMerchant").alphabetically
# Show recently unlinked ProviderMerchants (within last 30 days)
# Exclude merchants that are already assigned to transactions (they appear in provider_merchants)
recently_unlinked_ids = FamilyMerchantAssociation
.where(family: Current.family)
.recently_unlinked
.pluck(:merchant_id)
assigned_ids = @provider_merchants.pluck(:id)
@unlinked_merchants = ProviderMerchant.where(id: recently_unlinked_ids - assigned_ids).alphabetically
render layout: "settings"
end
@@ -32,24 +41,91 @@ class FamilyMerchantsController < ApplicationController
end
def update
@family_merchant.update!(merchant_params)
respond_to do |format|
format.html { redirect_to family_merchants_path, notice: t(".success") }
format.turbo_stream { render turbo_stream: turbo_stream.action(:redirect, family_merchants_path) }
if @merchant.is_a?(ProviderMerchant)
# Convert ProviderMerchant to FamilyMerchant for this family only
@family_merchant = @merchant.convert_to_family_merchant_for(Current.family, merchant_params)
respond_to do |format|
format.html { redirect_to family_merchants_path, notice: t(".converted_success") }
format.turbo_stream { render turbo_stream: turbo_stream.action(:redirect, family_merchants_path) }
end
elsif @merchant.update(merchant_params)
respond_to do |format|
format.html { redirect_to family_merchants_path, notice: t(".success") }
format.turbo_stream { render turbo_stream: turbo_stream.action(:redirect, family_merchants_path) }
end
else
render :edit, status: :unprocessable_entity
end
rescue ActiveRecord::RecordInvalid => e
@family_merchant = e.record
render :edit, status: :unprocessable_entity
end
def destroy
@family_merchant.destroy!
redirect_to family_merchants_path, notice: t(".success")
if @merchant.is_a?(ProviderMerchant)
# Unlink from family's transactions only (don't delete the global merchant)
@merchant.unlink_from_family(Current.family)
redirect_to family_merchants_path, notice: t(".unlinked_success")
else
@merchant.destroy!
redirect_to family_merchants_path, notice: t(".success")
end
end
def merge
@merchants = all_family_merchants
end
def perform_merge
# Scope lookups to merchants valid for this family (FamilyMerchants + assigned ProviderMerchants)
valid_merchants = all_family_merchants
target = valid_merchants.find_by(id: params[:target_id])
unless target
return redirect_to merge_family_merchants_path, alert: t(".target_not_found")
end
sources = valid_merchants.where(id: params[:source_ids])
unless sources.any?
return redirect_to merge_family_merchants_path, alert: t(".invalid_merchants")
end
merger = Merchant::Merger.new(
family: Current.family,
target_merchant: target,
source_merchants: sources
)
if merger.merge!
redirect_to family_merchants_path, notice: t(".success", count: merger.merged_count)
else
redirect_to merge_family_merchants_path, alert: t(".no_merchants_selected")
end
rescue Merchant::Merger::UnauthorizedMerchantError => e
redirect_to merge_family_merchants_path, alert: e.message
end
private
def set_merchant
@family_merchant = Current.family.merchants.find(params[:id])
# Find merchant that either belongs to family OR is assigned to family's transactions
@merchant = Current.family.merchants.find_by(id: params[:id]) ||
Current.family.assigned_merchants.find(params[:id])
@family_merchant = @merchant # For backwards compatibility with views
end
def merchant_params
params.require(:family_merchant).permit(:name, :color)
# Handle both family_merchant and provider_merchant param keys
key = params.key?(:family_merchant) ? :family_merchant : :provider_merchant
params.require(key).permit(:name, :color)
end
def all_family_merchants
family_merchant_ids = Current.family.merchants.pluck(:id)
provider_merchant_ids = Current.family.assigned_merchants.where(type: "ProviderMerchant").pluck(:id)
combined_ids = (family_merchant_ids + provider_merchant_ids).uniq
Merchant.where(id: combined_ids)
.distinct
.order(Arel.sql("LOWER(COALESCE(name, ''))"))
end
end