diff --git a/app/jobs/one_time_jobs/backfill_card_grant_settings.rb b/app/jobs/one_time_jobs/backfill_card_grant_settings.rb new file mode 100644 index 0000000000..081ecf7883 --- /dev/null +++ b/app/jobs/one_time_jobs/backfill_card_grant_settings.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module OneTimeJobs + class BackfillCardGrantSettings + def self.perform + CardGrant.find_each do |cg| + default_card_grant_setting = CardGrantSetting.find_by(event_id: cg.id) + ActiveRecord::Base.transaction do + card_grant_setting = CardGrantSetting.find_or_create_by!(card_grant_id: cg.id) + card_grant_setting.update!({ + banned_categories: cg.banned_categories || default_card_grant_setting.banned_categories, + banned_merchants: cg.banned_merchants || default_card_grant_setting.banned_merchants, + category_lock: cg.category_lock || default_card_grant_setting.category_lock, + expiration_preference: default_card_grant_setting.expiration_preference, + invite_message: default_card_grant_setting.invite_message, + keyword_lock: cg.keyword_lock || default_card_grant_setting.keyword_lock, + merchant_lock: cg.merchant_lock || default_card_grant_setting.merchant_lock, + pre_authorization_required: cg.pre_authorization_required || default_card_grant_setting.pre_authorization_required, + reimbursement_conversions_enabled: default_card_grant_setting.reimbursement_conversions_enabled + }) + end + end + end + + end +end diff --git a/app/mailers/card_grant_mailer.rb b/app/mailers/card_grant_mailer.rb index c2472cd761..df89b26849 100644 --- a/app/mailers/card_grant_mailer.rb +++ b/app/mailers/card_grant_mailer.rb @@ -3,7 +3,7 @@ class CardGrantMailer < ApplicationMailer def card_grant_notification @card_grant = params[:card_grant] - @custom_invite_message = @card_grant.setting.invite_message + @custom_invite_message = @card_grant.invite_message purpose_text = " for #{@card_grant.purpose}" mail to: @card_grant.user.email_address_with_name, subject: "[#{@card_grant.event.name}] You've received a #{@card_grant.amount.format} grant#{purpose_text if @card_grant.purpose.present?}!" diff --git a/app/models/card_grant.rb b/app/models/card_grant.rb index 6d3da94705..b830e355e8 100644 --- a/app/models/card_grant.rb +++ b/app/models/card_grant.rb @@ -60,8 +60,10 @@ class CardGrant < ApplicationRecord belongs_to :sent_by, class_name: "User" belongs_to :disbursement, optional: true has_many :disbursements, ->(record) { where(destination_subledger_id: record.subledger_id).or(where(source_subledger_id: record.subledger_id)) }, through: :event - has_one :card_grant_setting, through: :event, required: true + has_one :default_card_grant_setting, through: :event, required: true + has_one :card_grant_setting alias_method :setting, :card_grant_setting + alias_method :default_setting, :default_card_grant_setting enum :status, { active: 0, canceled: 1, expired: 2 }, default: :active @@ -243,11 +245,11 @@ def create_stripe_card(session) end def allowed_merchants - (merchant_lock + (setting&.merchant_lock || [])).uniq + (merchant_lock || setting&.merchant_lock || default_setting&.merchant_lock || []).uniq end def disallowed_merchants - (banned_merchants + (setting&.banned_merchants || [])).uniq + (banned_merchants || setting&.banned_merchants || default_setting&.banned_merchants || []).uniq end def allowed_merchant_names @@ -255,11 +257,11 @@ def allowed_merchant_names end def allowed_categories - (category_lock + (setting&.category_lock || [])).uniq + (category_lock || setting&.category_lock || default_setting&.category_lock || []).uniq end def disallowed_categories - (banned_categories + (setting&.banned_categories || [])).uniq + (banned_categories || setting&.banned_categories || default_setting&.banned_categories || []).uniq end def allowed_category_names @@ -267,7 +269,7 @@ def allowed_category_names end def keyword_lock - super || setting&.keyword_lock + setting&.keyword_lock || default_setting&.keyword_lock end def expires_after @@ -278,6 +280,10 @@ def expires_on created_at + expires_after.days end + def invite_message + setting&.invite_message || default_setting&.invite_message + end + def last_user_change_to(...) user_id = versions.where_object_changes_to(...).last&.whodunnit @@ -307,7 +313,19 @@ def convert_to_reimbursement_report! private def create_card_grant_setting - CardGrantSetting.find_or_create_by!(event_id:) + default_card_grant_setting = CardGrantSetting.find_or_create_by!(event_id:) + card_grant_setting = CardGrantSetting.find_or_create_by!(card_grant_id: id) + card_grant_setting.update!({ + banned_categories: self.banned_categories || default_card_grant_setting.banned_categories, + banned_merchants: self.banned_merchants || default_card_grant_setting.banned_merchants, + category_lock: self.category_lock || default_card_grant_setting.category_lock, + expiration_preference: default_card_grant_setting.expiration_preference, + invite_message: default_card_grant_setting.invite_message, + keyword_lock: self.keyword_lock || default_card_grant_setting.keyword_lock, + merchant_lock: self.merchant_lock || default_card_grant_setting.merchant_lock, + pre_authorization_required: self.pre_authorization_required || default_card_grant_setting.pre_authorization_required, + reimbursement_conversions_enabled: default_card_grant_setting.reimbursement_conversions_enabled + }) end def create_user diff --git a/app/models/card_grant_setting.rb b/app/models/card_grant_setting.rb index 1969ea2e64..6d41a81303 100644 --- a/app/models/card_grant_setting.rb +++ b/app/models/card_grant_setting.rb @@ -14,11 +14,13 @@ # merchant_lock :string # pre_authorization_required :boolean default(FALSE), not null # reimbursement_conversions_enabled :boolean default(TRUE), not null -# event_id :bigint not null +# card_grant_id :bigint +# event_id :bigint # # Indexes # -# index_card_grant_settings_on_event_id (event_id) +# index_card_grant_settings_on_card_grant_id (card_grant_id) +# index_card_grant_settings_on_event_id (event_id) # # Foreign Keys # @@ -34,6 +36,7 @@ class CardGrantSetting < ApplicationRecord alias_attribute :allowed_categories, :category_lock alias_attribute :disallowed_merchants, :banned_merchants has_many :card_grants, through: :event + belongs_to :card_grant enum :expiration_preference, { "90 days": 90, diff --git a/app/models/event.rb b/app/models/event.rb index 8e160d5cac..93a8647a91 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -418,6 +418,10 @@ def ancestor_organizer_positions self.activated_at = Time.now end + before_save if: -> { plan.card_grants_enabled? } do + create_card_grant_setting! + end + before_validation do build_plan(type: parent&.subevent_plan&.class || parent&.plan&.class || Event::Plan::Standard) if plan.nil? end diff --git a/app/views/events/edit.html.erb b/app/views/events/edit.html.erb index 9e50301336..5810ed1496 100644 --- a/app/views/events/edit.html.erb +++ b/app/views/events/edit.html.erb @@ -21,7 +21,7 @@ <%= link_to "Reimbursements", edit_event_path(@event, tab: "reimbursements"), data: { turbo: true, turbo_action: "advance" } %> <% end %> <% end %> - <% if @event.card_grant_setting.present? %> + <% if @event.plan.card_grants_enabled? %> <%= settings_tab active: @settings_tab == "card_grants" do %> <%= link_to "Card grants", edit_event_path(@event, tab: "card_grants"), data: { turbo: true, turbo_action: "advance" } %> <% end %> diff --git a/app/views/events/settings/_card_grants.html.erb b/app/views/events/settings/_card_grants.html.erb index 607e95d3db..08ee3e7ca7 100644 --- a/app/views/events/settings/_card_grants.html.erb +++ b/app/views/events/settings/_card_grants.html.erb @@ -39,6 +39,7 @@
<%= card_grant_setting_fields.label :reimbursement_conversions_enabled do %> <%= card_grant_setting_fields.check_box :reimbursement_conversions_enabled, + disabled:, switch: true %> <% end %> @@ -55,6 +56,7 @@
<%= card_grant_setting_fields.label :pre_authorization_required do %> <%= card_grant_setting_fields.check_box :pre_authorization_required, + disabled:, switch: true %> <% end %> diff --git a/db/migrate/20250807195557_add_card_grant_to_card_grant_setting.rb b/db/migrate/20250807195557_add_card_grant_to_card_grant_setting.rb new file mode 100644 index 0000000000..61115a817b --- /dev/null +++ b/db/migrate/20250807195557_add_card_grant_to_card_grant_setting.rb @@ -0,0 +1,11 @@ +class AddCardGrantToCardGrantSetting < ActiveRecord::Migration[7.2] + disable_ddl_transaction! + + def up + add_reference :card_grant_settings, :card_grant, index: {algorithm: :concurrently} + end + + def down + remove_reference :card_grant_settings, :card_grant, index: {algorithm: :concurrently} + end +end diff --git a/db/migrate/20250807201752_make_event_id_on_card_grant_setting_nullable.rb b/db/migrate/20250807201752_make_event_id_on_card_grant_setting_nullable.rb new file mode 100644 index 0000000000..3becea559e --- /dev/null +++ b/db/migrate/20250807201752_make_event_id_on_card_grant_setting_nullable.rb @@ -0,0 +1,9 @@ +class MakeEventIdOnCardGrantSettingNullable < ActiveRecord::Migration[7.2] + def up + change_column_null :card_grant_settings, :event_id, true + end + + def down + raise ActiveRecord::IrreversibleMigration, "this migration cannot be reversed because event id may be null" + end +end diff --git a/db/schema.rb b/db/schema.rb index 4340e83711..0cb207c3d1 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -12,7 +12,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.2].define(version: 2025_08_02_222150) do +ActiveRecord::Schema[7.2].define(version: 2025_08_07_201752) do create_schema "google_sheets" # These are extensions that must be enabled in order to support this database @@ -455,7 +455,7 @@ end create_table "card_grant_settings", force: :cascade do |t| - t.bigint "event_id", null: false + t.bigint "event_id" t.string "merchant_lock" t.string "category_lock" t.string "invite_message" @@ -465,6 +465,8 @@ t.boolean "pre_authorization_required", default: false, null: false t.string "banned_merchants" t.string "banned_categories" + t.bigint "card_grant_id" + t.index ["card_grant_id"], name: "index_card_grant_settings_on_card_grant_id" t.index ["event_id"], name: "index_card_grant_settings_on_event_id" end