From 7664d582187968884d29a4723e184fdac3d96dee Mon Sep 17 00:00:00 2001 From: Luke Oldenburg <87272260+Luke-Oldenburg@users.noreply.github.com> Date: Thu, 7 Aug 2025 19:27:39 +0000 Subject: [PATCH 01/25] Show card grants settings as long as they are enabled --- app/views/events/edit.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 %> From 5b7a510c92c798d195c41c0bf0d53b844b3b84c8 Mon Sep 17 00:00:00 2001 From: Luke Oldenburg <87272260+Luke-Oldenburg@users.noreply.github.com> Date: Thu, 7 Aug 2025 19:53:17 +0000 Subject: [PATCH 02/25] Add disabled to sliders --- app/views/events/settings/_card_grants.html.erb | 2 ++ 1 file changed, 2 insertions(+) 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 %> From cf40795d9a886774fd7f50205ad262a13291775f Mon Sep 17 00:00:00 2001 From: Luke Oldenburg <87272260+Luke-Oldenburg@users.noreply.github.com> Date: Thu, 7 Aug 2025 20:34:34 +0000 Subject: [PATCH 03/25] Create card grant setting on event as long as plan allows --- app/models/event.rb | 4 ++++ 1 file changed, 4 insertions(+) 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 From 30b588b55afb847fb4a04afdcfe1a924990402ef Mon Sep 17 00:00:00 2001 From: Luke Oldenburg <87272260+Luke-Oldenburg@users.noreply.github.com> Date: Thu, 7 Aug 2025 20:35:28 +0000 Subject: [PATCH 04/25] Add card grant to card grant setting --- app/models/card_grant.rb | 3 +++ app/models/card_grant_setting.rb | 7 +++++-- ...0807195557_add_card_grant_to_card_grant_setting.rb | 11 +++++++++++ ...52_make_event_id_on_card_grant_setting_nullable.rb | 9 +++++++++ db/schema.rb | 6 ++++-- 5 files changed, 32 insertions(+), 4 deletions(-) create mode 100644 db/migrate/20250807195557_add_card_grant_to_card_grant_setting.rb create mode 100644 db/migrate/20250807201752_make_event_id_on_card_grant_setting_nullable.rb diff --git a/app/models/card_grant.rb b/app/models/card_grant.rb index 6d3da94705..749f5456f2 100644 --- a/app/models/card_grant.rb +++ b/app/models/card_grant.rb @@ -62,6 +62,9 @@ class CardGrant < ApplicationRecord 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 alias_method :setting, :card_grant_setting + # TODO + # has_one :default_card_grant_setting, through: :event, required: true + # has_one :card_grant_setting enum :status, { active: 0, canceled: 1, expired: 2 }, default: :active 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/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 From dd7547c3eeba0204199266e2308a9d1baa4c0414 Mon Sep 17 00:00:00 2001 From: Luke Oldenburg <87272260+Luke-Oldenburg@users.noreply.github.com> Date: Thu, 7 Aug 2025 20:35:43 +0000 Subject: [PATCH 05/25] Add one time job to backfill card grant settings --- .../backfill_card_grant_settings.rb | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 app/jobs/one_time_jobs/backfill_card_grant_settings.rb 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..39ab92c8da --- /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 From 8e88971b4d63d9b07146463125a89d3346b57633 Mon Sep 17 00:00:00 2001 From: Luke Oldenburg <87272260+Luke-Oldenburg@users.noreply.github.com> Date: Thu, 7 Aug 2025 16:39:58 -0400 Subject: [PATCH 06/25] Update app/jobs/one_time_jobs/backfill_card_grant_settings.rb Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- app/jobs/one_time_jobs/backfill_card_grant_settings.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/jobs/one_time_jobs/backfill_card_grant_settings.rb b/app/jobs/one_time_jobs/backfill_card_grant_settings.rb index 39ab92c8da..d9a9dc8756 100644 --- a/app/jobs/one_time_jobs/backfill_card_grant_settings.rb +++ b/app/jobs/one_time_jobs/backfill_card_grant_settings.rb @@ -2,7 +2,7 @@ module OneTimeJobs class BackfillCardGrantSettings - def self.perform() + def self.perform CardGrant.find_each do |cg| default_card_grant_setting = CardGrantSetting.find_by(event_id: cg.id) ActiveRecord::Base.transaction do From 8b656636dd56ecc39d09fa0e26c89b116d6bd563 Mon Sep 17 00:00:00 2001 From: Luke Oldenburg <87272260+Luke-Oldenburg@users.noreply.github.com> Date: Thu, 7 Aug 2025 16:40:05 -0400 Subject: [PATCH 07/25] Update app/jobs/one_time_jobs/backfill_card_grant_settings.rb Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .../backfill_card_grant_settings.rb | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/app/jobs/one_time_jobs/backfill_card_grant_settings.rb b/app/jobs/one_time_jobs/backfill_card_grant_settings.rb index d9a9dc8756..081ecf7883 100644 --- a/app/jobs/one_time_jobs/backfill_card_grant_settings.rb +++ b/app/jobs/one_time_jobs/backfill_card_grant_settings.rb @@ -8,16 +8,16 @@ def self.perform 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 - }) + 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 From 86a3d7e4b7770da49ac4e938c4dbe67e12ec512c Mon Sep 17 00:00:00 2001 From: Luke Oldenburg <87272260+Luke-Oldenburg@users.noreply.github.com> Date: Fri, 8 Aug 2025 00:22:07 +0000 Subject: [PATCH 08/25] Give each new card grant a card grant setting --- app/mailers/card_grant_mailer.rb | 2 +- app/models/card_grant.rb | 35 +++++++++++++++++++++++--------- 2 files changed, 26 insertions(+), 11 deletions(-) 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 749f5456f2..188b99691b 100644 --- a/app/models/card_grant.rb +++ b/app/models/card_grant.rb @@ -60,11 +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 - # TODO - # has_one :default_card_grant_setting, through: :event, required: true - # has_one :card_grant_setting + alias_method :default_setting, :default_card_grant_setting enum :status, { active: 0, canceled: 1, expired: 2 }, default: :active @@ -246,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 @@ -258,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 @@ -270,7 +269,7 @@ def allowed_category_names end def keyword_lock - super || setting&.keyword_lock + super || setting&.keyword_lock || default_setting&.keyword_lock end def expires_after @@ -281,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 @@ -310,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 From e420889852e6ebaf3fed121cfc5d863b3c9fd470 Mon Sep 17 00:00:00 2001 From: Luke Oldenburg <87272260+Luke-Oldenburg@users.noreply.github.com> Date: Thu, 7 Aug 2025 20:26:56 -0400 Subject: [PATCH 09/25] Update app/models/card_grant.rb Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- app/models/card_grant.rb | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/app/models/card_grant.rb b/app/models/card_grant.rb index 188b99691b..1903ac288e 100644 --- a/app/models/card_grant.rb +++ b/app/models/card_grant.rb @@ -316,16 +316,16 @@ def create_card_grant_setting 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 - }) + 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 From 68c5f2837a314bba184397c757607a3ce039dbac Mon Sep 17 00:00:00 2001 From: Luke Oldenburg <87272260+Luke-Oldenburg@users.noreply.github.com> Date: Tue, 12 Aug 2025 12:52:09 -0400 Subject: [PATCH 10/25] Update app/models/card_grant.rb --- app/models/card_grant.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/card_grant.rb b/app/models/card_grant.rb index 1903ac288e..b830e355e8 100644 --- a/app/models/card_grant.rb +++ b/app/models/card_grant.rb @@ -269,7 +269,7 @@ def allowed_category_names end def keyword_lock - super || setting&.keyword_lock || default_setting&.keyword_lock + setting&.keyword_lock || default_setting&.keyword_lock end def expires_after From a7709003c2b88c03a4320b44f1b64a947b1e87d6 Mon Sep 17 00:00:00 2001 From: Luke Oldenburg <87272260+Luke-Oldenburg@users.noreply.github.com> Date: Sun, 24 Aug 2025 00:44:44 +0000 Subject: [PATCH 11/25] Update job to reflect data better --- .../backfill_card_grant_settings.rb | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/app/jobs/one_time_jobs/backfill_card_grant_settings.rb b/app/jobs/one_time_jobs/backfill_card_grant_settings.rb index 081ecf7883..b99c86eb6d 100644 --- a/app/jobs/one_time_jobs/backfill_card_grant_settings.rb +++ b/app/jobs/one_time_jobs/backfill_card_grant_settings.rb @@ -4,20 +4,22 @@ module OneTimeJobs class BackfillCardGrantSettings def self.perform CardGrant.find_each do |cg| - default_card_grant_setting = CardGrantSetting.find_by(event_id: cg.id) + default_cg_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 - }) + cg_setting = CardGrantSetting.find_or_create_by!(card_grant_id: cg.id) + cg_setting.update!( + { + banned_categories: (cg.banned_categories + (default_cg_setting&.banned_categories || [])).uniq, + banned_merchants: (cg.banned_merchants || (default_cg_setting&.banned_merchants || [])).uniq, + category_lock: (cg.category_lock || (default_cg_setting&.category_lock || [])).uniq, + expiration_preference: default_cg_setting.expiration_preference, + invite_message: default_cg_setting.invite_message, + keyword_lock: cg.keyword_lock || default_cg_setting&.keyword_lock, + merchant_lock: (cg.merchant_lock || (default_cg_setting&.merchant_lock || [])).uniq, + pre_authorization_required: [cg.pre_authorization_required, default_cg_setting.pre_authorization_required].first(&:present?), + reimbursement_conversions_enabled: default_cg_setting.reimbursement_conversions_enabled + } + ) end end end From b6b8339ac9b8cd59eb7ab5e5c456615330dc0788 Mon Sep 17 00:00:00 2001 From: Luke Oldenburg <87272260+Luke-Oldenburg@users.noreply.github.com> Date: Sun, 24 Aug 2025 00:48:29 +0000 Subject: [PATCH 12/25] Fix job --- app/jobs/one_time_jobs/backfill_card_grant_settings.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/jobs/one_time_jobs/backfill_card_grant_settings.rb b/app/jobs/one_time_jobs/backfill_card_grant_settings.rb index b99c86eb6d..07d0c32414 100644 --- a/app/jobs/one_time_jobs/backfill_card_grant_settings.rb +++ b/app/jobs/one_time_jobs/backfill_card_grant_settings.rb @@ -10,12 +10,12 @@ def self.perform cg_setting.update!( { banned_categories: (cg.banned_categories + (default_cg_setting&.banned_categories || [])).uniq, - banned_merchants: (cg.banned_merchants || (default_cg_setting&.banned_merchants || [])).uniq, - category_lock: (cg.category_lock || (default_cg_setting&.category_lock || [])).uniq, + banned_merchants: (cg.banned_merchants + (default_cg_setting&.banned_merchants || [])).uniq, + category_lock: (cg.category_lock + (default_cg_setting&.category_lock || [])).uniq, expiration_preference: default_cg_setting.expiration_preference, invite_message: default_cg_setting.invite_message, keyword_lock: cg.keyword_lock || default_cg_setting&.keyword_lock, - merchant_lock: (cg.merchant_lock || (default_cg_setting&.merchant_lock || [])).uniq, + merchant_lock: (cg.merchant_lock + (default_cg_setting&.merchant_lock || [])).uniq, pre_authorization_required: [cg.pre_authorization_required, default_cg_setting.pre_authorization_required].first(&:present?), reimbursement_conversions_enabled: default_cg_setting.reimbursement_conversions_enabled } From c322bfb99d81586c8539e0ba1f22328b9b93deb4 Mon Sep 17 00:00:00 2001 From: Luke Oldenburg <87272260+Luke-Oldenburg@users.noreply.github.com> Date: Sun, 24 Aug 2025 00:49:57 +0000 Subject: [PATCH 13/25] & --- app/jobs/one_time_jobs/backfill_card_grant_settings.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/jobs/one_time_jobs/backfill_card_grant_settings.rb b/app/jobs/one_time_jobs/backfill_card_grant_settings.rb index 07d0c32414..fa0ab98748 100644 --- a/app/jobs/one_time_jobs/backfill_card_grant_settings.rb +++ b/app/jobs/one_time_jobs/backfill_card_grant_settings.rb @@ -12,12 +12,12 @@ def self.perform banned_categories: (cg.banned_categories + (default_cg_setting&.banned_categories || [])).uniq, banned_merchants: (cg.banned_merchants + (default_cg_setting&.banned_merchants || [])).uniq, category_lock: (cg.category_lock + (default_cg_setting&.category_lock || [])).uniq, - expiration_preference: default_cg_setting.expiration_preference, - invite_message: default_cg_setting.invite_message, + expiration_preference: default_cg_setting&.expiration_preference, + invite_message: default_cg_setting&.invite_message, keyword_lock: cg.keyword_lock || default_cg_setting&.keyword_lock, merchant_lock: (cg.merchant_lock + (default_cg_setting&.merchant_lock || [])).uniq, - pre_authorization_required: [cg.pre_authorization_required, default_cg_setting.pre_authorization_required].first(&:present?), - reimbursement_conversions_enabled: default_cg_setting.reimbursement_conversions_enabled + pre_authorization_required: [cg.pre_authorization_required, default_cg_setting&.pre_authorization_required].first(&:present?), + reimbursement_conversions_enabled: default_cg_setting&.reimbursement_conversions_enabled } ) end From cf585a6b2f525b3842508e16db0e96d8b61de9b0 Mon Sep 17 00:00:00 2001 From: Luke Oldenburg <87272260+Luke-Oldenburg@users.noreply.github.com> Date: Sun, 24 Aug 2025 00:59:38 +0000 Subject: [PATCH 14/25] Make event id non null again --- app/models/card_grant_setting.rb | 2 +- ...make_event_id_not_null_on_card_grant_settings.rb | 6 ++++++ ...make_event_id_not_null_on_card_grant_settings.rb | 13 +++++++++++++ db/schema.rb | 4 ++-- 4 files changed, 22 insertions(+), 3 deletions(-) create mode 100644 db/migrate/20250824005217_make_event_id_not_null_on_card_grant_settings.rb create mode 100644 db/migrate/20250824005727_validate_make_event_id_not_null_on_card_grant_settings.rb diff --git a/app/models/card_grant_setting.rb b/app/models/card_grant_setting.rb index 6d41a81303..24d0b5abd9 100644 --- a/app/models/card_grant_setting.rb +++ b/app/models/card_grant_setting.rb @@ -15,7 +15,7 @@ # pre_authorization_required :boolean default(FALSE), not null # reimbursement_conversions_enabled :boolean default(TRUE), not null # card_grant_id :bigint -# event_id :bigint +# event_id :bigint not null # # Indexes # diff --git a/db/migrate/20250824005217_make_event_id_not_null_on_card_grant_settings.rb b/db/migrate/20250824005217_make_event_id_not_null_on_card_grant_settings.rb new file mode 100644 index 0000000000..c098a5f1e9 --- /dev/null +++ b/db/migrate/20250824005217_make_event_id_not_null_on_card_grant_settings.rb @@ -0,0 +1,6 @@ +class MakeEventIdNotNullOnCardGrantSettings < ActiveRecord::Migration[7.2] + def change + add_check_constraint :card_grant_settings, "event_id IS NOT NULL", name: "card_grant_settings_event_id_null", validate: false + end + +end diff --git a/db/migrate/20250824005727_validate_make_event_id_not_null_on_card_grant_settings.rb b/db/migrate/20250824005727_validate_make_event_id_not_null_on_card_grant_settings.rb new file mode 100644 index 0000000000..0b8ed7c73a --- /dev/null +++ b/db/migrate/20250824005727_validate_make_event_id_not_null_on_card_grant_settings.rb @@ -0,0 +1,13 @@ +class ValidateMakeEventIdNotNullOnCardGrantSettings < ActiveRecord::Migration[7.2] + def up + validate_check_constraint :card_grant_settings, name: "card_grant_settings_event_id_null" + change_column_null :card_grant_settings, :event_id, false + remove_check_constraint :card_grant_settings, name: "card_grant_settings_event_id_null" + end + + def down + add_check_constraint :card_grant_settings, "event_id IS NOT NULL", name: "card_grant_settings_event_id_null", validate: false + change_column_null :card_grant_settings, :event_id, true + end + +end diff --git a/db/schema.rb b/db/schema.rb index ed3b2509d9..6cc54b679d 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_14_191925) do +ActiveRecord::Schema[7.2].define(version: 2025_08_24_005727) 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" + t.bigint "event_id", null: false t.string "merchant_lock" t.string "category_lock" t.string "invite_message" From ae016430b027e79ffbd5f66ddbdfa72231bed144 Mon Sep 17 00:00:00 2001 From: Luke Oldenburg <87272260+Luke-Oldenburg@users.noreply.github.com> Date: Fri, 29 Aug 2025 01:28:49 +0000 Subject: [PATCH 15/25] Update logic for finding card grant settings --- app/models/card_grant.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/models/card_grant.rb b/app/models/card_grant.rb index b830e355e8..e921814190 100644 --- a/app/models/card_grant.rb +++ b/app/models/card_grant.rb @@ -245,11 +245,11 @@ def create_stripe_card(session) end def allowed_merchants - (merchant_lock || setting&.merchant_lock || default_setting&.merchant_lock || []).uniq + setting&.merchant_lock || (merchant_lock + (default_setting&.merchant_lock || [])).uniq end def disallowed_merchants - (banned_merchants || setting&.banned_merchants || default_setting&.banned_merchants || []).uniq + setting&.banned_merchants || (banned_merchants + (default_setting&.banned_merchants || [])).uniq end def allowed_merchant_names @@ -257,11 +257,11 @@ def allowed_merchant_names end def allowed_categories - (category_lock || setting&.category_lock || default_setting&.category_lock || []).uniq + setting&.category_lock || (category_lock + (default_setting&.category_lock || [])).uniq end def disallowed_categories - (banned_categories || setting&.banned_categories || default_setting&.banned_categories || []).uniq + setting&.banned_categories || (banned_categories + (default_setting&.banned_categories || [])).uniq end def allowed_category_names From aa6af8025bb5ecf593925b04c78bebb16d59a705 Mon Sep 17 00:00:00 2001 From: Luke Oldenburg <87272260+Luke-Oldenburg@users.noreply.github.com> Date: Fri, 29 Aug 2025 01:45:32 +0000 Subject: [PATCH 16/25] Create card grant setting with card grant --- app/controllers/card_grants_controller.rb | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/app/controllers/card_grants_controller.rb b/app/controllers/card_grants_controller.rb index 4cf2f7e981..26847e9b5d 100644 --- a/app/controllers/card_grants_controller.rb +++ b/app/controllers/card_grants_controller.rb @@ -30,6 +30,23 @@ def create # exception as under the hood it calls `DisbursementService::Create` and a # number of other methods (e.g. `save!`) which either succeed or raise. @card_grant.save! + + default_cg_setting = @event.card_grant_setting + CardGrantSetting.create!( + { + banned_categories: default_cg_setting.banned_categories, + banned_merchants: default_cg_setting.banned_merchants, + category_lock: default_cg_setting.category_lock, + expiration_preference: default_cg_setting.expiration_preference, + invite_message: default_cg_setting.invite_message, + keyword_lock: default_cg_setting.keyword_lock, + merchant_lock: default_cg_setting.merchant_lock, + pre_authorization_required: default_cg_setting.pre_authorization_required, + reimbursement_conversions_enabled: default_cg_setting.reimbursement_conversions_enabled, + card_grant_id: @card_grant.id, + event_id: @event.id + } + ) rescue => e case e when ActiveRecord::RecordInvalid From 2c8184d6e7d3c44cd824b6e820ba5f0bce790d47 Mon Sep 17 00:00:00 2001 From: Luke Oldenburg <87272260+Luke-Oldenburg@users.noreply.github.com> Date: Fri, 29 Aug 2025 01:48:37 +0000 Subject: [PATCH 17/25] Update setting parameters when card grant is updated --- app/controllers/card_grants_controller.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/controllers/card_grants_controller.rb b/app/controllers/card_grants_controller.rb index 26847e9b5d..9b52310590 100644 --- a/app/controllers/card_grants_controller.rb +++ b/app/controllers/card_grants_controller.rb @@ -100,6 +100,10 @@ def edit_withdraw def update authorize @card_grant + if @card_grant.setting + @card_grant.setting.update(params.require(:card_grant).permit(:merchant_lock, :category_lock, :keyword_lock)) + end + if @card_grant.update(params.require(:card_grant).permit(:purpose, :merchant_lock, :category_lock, :keyword_lock)) flash[:success] = "Grant's purpose has been successfully updated!" else From 8ec76dc43f4b80b2201882d28f6821129dcee12b Mon Sep 17 00:00:00 2001 From: Luke Oldenburg <87272260+Luke-Oldenburg@users.noreply.github.com> Date: Fri, 29 Aug 2025 01:52:43 +0000 Subject: [PATCH 18/25] Use values from cg params --- app/controllers/card_grants_controller.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/controllers/card_grants_controller.rb b/app/controllers/card_grants_controller.rb index 9b52310590..ef5904db61 100644 --- a/app/controllers/card_grants_controller.rb +++ b/app/controllers/card_grants_controller.rb @@ -21,7 +21,8 @@ def new def create params[:card_grant][:amount_cents] = Monetize.parse(params[:card_grant][:amount_cents]).cents - @card_grant = @event.card_grants.build(params.require(:card_grant).permit(:amount_cents, :email, :keyword_lock, :purpose, :one_time_use, :pre_authorization_required, :instructions).merge(sent_by: current_user)) + cg_params = params.require(:card_grant).permit(:amount_cents, :email, :keyword_lock, :purpose, :one_time_use, :pre_authorization_required, :instructions).merge(sent_by: current_user) + @card_grant = @event.card_grants.build(cg_params) authorize @card_grant @@ -39,9 +40,9 @@ def create category_lock: default_cg_setting.category_lock, expiration_preference: default_cg_setting.expiration_preference, invite_message: default_cg_setting.invite_message, - keyword_lock: default_cg_setting.keyword_lock, + keyword_lock: cg_params.card_grant.keyword_lock, merchant_lock: default_cg_setting.merchant_lock, - pre_authorization_required: default_cg_setting.pre_authorization_required, + pre_authorization_required: cg_params.card_grant.pre_authorization_required, reimbursement_conversions_enabled: default_cg_setting.reimbursement_conversions_enabled, card_grant_id: @card_grant.id, event_id: @event.id From 5e280fd426bfa694e2bcae332a072991ecc675ca Mon Sep 17 00:00:00 2001 From: Luke Oldenburg <87272260+Luke-Oldenburg@users.noreply.github.com> Date: Fri, 29 Aug 2025 01:54:47 +0000 Subject: [PATCH 19/25] Use safety operator --- app/controllers/card_grants_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/card_grants_controller.rb b/app/controllers/card_grants_controller.rb index ef5904db61..2ac3cce59f 100644 --- a/app/controllers/card_grants_controller.rb +++ b/app/controllers/card_grants_controller.rb @@ -101,7 +101,7 @@ def edit_withdraw def update authorize @card_grant - if @card_grant.setting + if @card_grant&.setting @card_grant.setting.update(params.require(:card_grant).permit(:merchant_lock, :category_lock, :keyword_lock)) end From 531764cf7e0bac4e2f862d1582a6f25946897343 Mon Sep 17 00:00:00 2001 From: Luke Oldenburg <87272260+Luke-Oldenburg@users.noreply.github.com> Date: Fri, 29 Aug 2025 01:57:39 +0000 Subject: [PATCH 20/25] Properly use safe navigation --- app/controllers/card_grants_controller.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/controllers/card_grants_controller.rb b/app/controllers/card_grants_controller.rb index 2ac3cce59f..d00b98e6d7 100644 --- a/app/controllers/card_grants_controller.rb +++ b/app/controllers/card_grants_controller.rb @@ -101,9 +101,7 @@ def edit_withdraw def update authorize @card_grant - if @card_grant&.setting - @card_grant.setting.update(params.require(:card_grant).permit(:merchant_lock, :category_lock, :keyword_lock)) - end + @card_grant&.setting.update(params.require(:card_grant).permit(:merchant_lock, :category_lock, :keyword_lock)) if @card_grant.update(params.require(:card_grant).permit(:purpose, :merchant_lock, :category_lock, :keyword_lock)) flash[:success] = "Grant's purpose has been successfully updated!" From 9424baefb4a18cc876a78fb1f520d560ce9fb46c Mon Sep 17 00:00:00 2001 From: Luke Oldenburg <87272260+Luke-Oldenburg@users.noreply.github.com> Date: Fri, 29 Aug 2025 02:01:43 +0000 Subject: [PATCH 21/25] Fix updating --- app/controllers/card_grants_controller.rb | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/app/controllers/card_grants_controller.rb b/app/controllers/card_grants_controller.rb index d00b98e6d7..42f58ce3b8 100644 --- a/app/controllers/card_grants_controller.rb +++ b/app/controllers/card_grants_controller.rb @@ -101,9 +101,17 @@ def edit_withdraw def update authorize @card_grant - @card_grant&.setting.update(params.require(:card_grant).permit(:merchant_lock, :category_lock, :keyword_lock)) + cg_params = params.require(:card_grant).permit(:purpose, :merchant_lock, :category_lock, :keyword_lock) - if @card_grant.update(params.require(:card_grant).permit(:purpose, :merchant_lock, :category_lock, :keyword_lock)) + @card_grant&.setting.update( + { + merchant_lock: cg_params.card_grant.merchant_lock, + category_lock: cg_params.card_grant.category_lock, + keyword_lock: cg_params.card_grant.keyword_lock + } + ) + + if @card_grant.update(cg_params) flash[:success] = "Grant's purpose has been successfully updated!" else flash[:error] = @card_grant.errors.full_messages.to_sentence From dc7a022e7b569f6c7a1ce89955c0dce8ef408411 Mon Sep 17 00:00:00 2001 From: Luke Oldenburg <87272260+Luke-Oldenburg@users.noreply.github.com> Date: Fri, 29 Aug 2025 02:02:55 +0000 Subject: [PATCH 22/25] Add to API --- .../api/v4/card_grants_controller.rb | 33 +++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/v4/card_grants_controller.rb b/app/controllers/api/v4/card_grants_controller.rb index 706fabe6e1..c6e8e13750 100644 --- a/app/controllers/api/v4/card_grants_controller.rb +++ b/app/controllers/api/v4/card_grants_controller.rb @@ -16,7 +16,9 @@ def index def create @event = Event.find_by_public_id(params[:event_id]) || Event.friendly.find(params[:event_id]) - @card_grant = @event.card_grants.build(params.permit(:amount_cents, :email, :merchant_lock, :category_lock, :keyword_lock, :purpose, :one_time_use, :pre_authorization_required, :instructions).merge(sent_by: current_user)) + cg_params = params.permit(:amount_cents, :email, :merchant_lock, :category_lock, :keyword_lock, :purpose, :one_time_use, :pre_authorization_required, :instructions).merge(sent_by: current_user) + + @card_grant = @event.card_grants.build(cg_params) authorize @card_grant @@ -25,6 +27,23 @@ def create # exception as under the hood it calls `DisbursementService::Create` and a # number of other methods (e.g. `save!`) which either succeed or raise. @card_grant.save! + + default_cg_setting = @event.card_grant_setting + CardGrantSetting.create!( + { + banned_categories: default_cg_setting.banned_categories, + banned_merchants: default_cg_setting.banned_merchants, + category_lock: cg_params.category_lock, + expiration_preference: default_cg_setting.expiration_preference, + invite_message: default_cg_setting.invite_message, + keyword_lock: cg_params.keyword_lock, + merchant_lock: cg_params.merchant_lock, + pre_authorization_required: cg_params.pre_authorization_required, + reimbursement_conversions_enabled: default_cg_setting.reimbursement_conversions_enabled, + card_grant_id: @card_grant.id, + event_id: @event.id + } + ) rescue => e messages = [] @@ -76,7 +95,17 @@ def update authorize @card_grant - @card_grant.update!(params.permit(:merchant_lock, :category_lock, :keyword_lock, :purpose, :one_time_use, :instructions)) + cg_params = params.permit(:merchant_lock, :category_lock, :keyword_lock, :purpose, :one_time_use, :instructions) + + @card_grant&.setting.update( + { + merchant_lock: cg_params.merchant_lock, + category_lock: cg_params.category_lock, + keyword_lock: cg_params.keyword_lock + } + ) + + @card_grant.update!(cg_params) render :show end From 7370046c01a1a72d4464f9232e6f4c5415d70ae3 Mon Sep 17 00:00:00 2001 From: Luke Oldenburg <87272260+Luke-Oldenburg@users.noreply.github.com> Date: Fri, 29 Aug 2025 02:05:55 +0000 Subject: [PATCH 23/25] Fix safety operators --- app/controllers/api/v4/card_grants_controller.rb | 2 +- app/controllers/card_grants_controller.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/v4/card_grants_controller.rb b/app/controllers/api/v4/card_grants_controller.rb index c6e8e13750..37c153698e 100644 --- a/app/controllers/api/v4/card_grants_controller.rb +++ b/app/controllers/api/v4/card_grants_controller.rb @@ -97,7 +97,7 @@ def update cg_params = params.permit(:merchant_lock, :category_lock, :keyword_lock, :purpose, :one_time_use, :instructions) - @card_grant&.setting.update( + @card_grant&.setting&.update( { merchant_lock: cg_params.merchant_lock, category_lock: cg_params.category_lock, diff --git a/app/controllers/card_grants_controller.rb b/app/controllers/card_grants_controller.rb index 42f58ce3b8..db39f35a49 100644 --- a/app/controllers/card_grants_controller.rb +++ b/app/controllers/card_grants_controller.rb @@ -103,7 +103,7 @@ def update cg_params = params.require(:card_grant).permit(:purpose, :merchant_lock, :category_lock, :keyword_lock) - @card_grant&.setting.update( + @card_grant&.setting&.update( { merchant_lock: cg_params.card_grant.merchant_lock, category_lock: cg_params.card_grant.category_lock, From af468aa32ba59f58a51dbe18360888075e7a7f4c Mon Sep 17 00:00:00 2001 From: Luke Oldenburg <87272260+Luke-Oldenburg@users.noreply.github.com> Date: Thu, 28 Aug 2025 22:18:32 -0400 Subject: [PATCH 24/25] Update app/jobs/one_time_jobs/backfill_card_grant_settings.rb --- app/jobs/one_time_jobs/backfill_card_grant_settings.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/jobs/one_time_jobs/backfill_card_grant_settings.rb b/app/jobs/one_time_jobs/backfill_card_grant_settings.rb index fa0ab98748..55aa0915e5 100644 --- a/app/jobs/one_time_jobs/backfill_card_grant_settings.rb +++ b/app/jobs/one_time_jobs/backfill_card_grant_settings.rb @@ -4,7 +4,7 @@ module OneTimeJobs class BackfillCardGrantSettings def self.perform CardGrant.find_each do |cg| - default_cg_setting = CardGrantSetting.find_by(event_id: cg.id) + default_cg_setting = CardGrantSetting.where(event_id: cg.id, card_grant_id: nil).first ActiveRecord::Base.transaction do cg_setting = CardGrantSetting.find_or_create_by!(card_grant_id: cg.id) cg_setting.update!( From 612bc41a607a6529046e6ec9a563518fa325af48 Mon Sep 17 00:00:00 2001 From: Luke Oldenburg <87272260+Luke-Oldenburg@users.noreply.github.com> Date: Thu, 28 Aug 2025 22:19:28 -0400 Subject: [PATCH 25/25] Update app/jobs/one_time_jobs/backfill_card_grant_settings.rb --- app/jobs/one_time_jobs/backfill_card_grant_settings.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/jobs/one_time_jobs/backfill_card_grant_settings.rb b/app/jobs/one_time_jobs/backfill_card_grant_settings.rb index 55aa0915e5..435356d6ae 100644 --- a/app/jobs/one_time_jobs/backfill_card_grant_settings.rb +++ b/app/jobs/one_time_jobs/backfill_card_grant_settings.rb @@ -4,7 +4,7 @@ module OneTimeJobs class BackfillCardGrantSettings def self.perform CardGrant.find_each do |cg| - default_cg_setting = CardGrantSetting.where(event_id: cg.id, card_grant_id: nil).first + default_cg_setting = CardGrantSetting.where(event_id: cg.event.id, card_grant_id: nil).first ActiveRecord::Base.transaction do cg_setting = CardGrantSetting.find_or_create_by!(card_grant_id: cg.id) cg_setting.update!(