From 2c65f5714f1f160caf474de41dcbbee2e1d65c05 Mon Sep 17 00:00:00 2001 From: Aaron Nagucki Date: Tue, 1 Apr 2025 12:38:03 -0500 Subject: [PATCH] sense of direction --- app/models/agreements/iaa_order.rb | 18 +++- ...remove_uniqueness_from_iaa_orders_index.rb | 13 +++ db/schema.rb | 4 +- lib/cleanup/remove_integration_from_order.rb | 88 +++++++++++++++++++ spec/models/agreements/iaa_order_spec.rb | 14 ++- 5 files changed, 133 insertions(+), 4 deletions(-) create mode 100644 db/primary_migrate/20250327195800_remove_uniqueness_from_iaa_orders_index.rb create mode 100644 lib/cleanup/remove_integration_from_order.rb diff --git a/app/models/agreements/iaa_order.rb b/app/models/agreements/iaa_order.rb index 846e98d3f57..d81fc829b9c 100644 --- a/app/models/agreements/iaa_order.rb +++ b/app/models/agreements/iaa_order.rb @@ -11,12 +11,12 @@ class Agreements::IaaOrder < ApplicationRecord has_many :integrations, through: :integration_usages validates :order_number, presence: true, - uniqueness: { scope: :iaa_gtc_id }, numericality: { only_integer: true, greater_than_or_equal_to: 0 } validates :mod_number, presence: true, numericality: { only_integer: true, - greater_than_or_equal_to: 0 } + greater_than_or_equal_to: 0 }, + uniqueness: { scope: [:order_number, :iaa_gtc_id] } validates :pricing_model, presence: true, numericality: { only_integer: true, greater_than_or_equal_to: 0 } @@ -26,6 +26,7 @@ class Agreements::IaaOrder < ApplicationRecord validates :start_date, presence: true validates :end_date, presence: true validate :end_date_after_start_date + validate :no_overlapping_order_dates def status return 'pending_start' if Time.zone.today < start_date @@ -49,6 +50,19 @@ def end_date_after_start_date errors.add(:end_date, 'must be after start date', type: :invalid_end_date) end + def no_overlapping_order_dates + return unless self.class + .where(order_number: order_number, iaa_gtc_id: iaa_gtc_id) + .where.not(id: id) + .where('(start_date, end_date) OVERLAPS (?, ?)', start_date, end_date) + .exists? + + errors.add( + :base, 'Overlapping order dates for the same order number', + type: :overlapping_order_dates + ) + end + def pop_range return unless start_date.present? && end_date.present? diff --git a/db/primary_migrate/20250327195800_remove_uniqueness_from_iaa_orders_index.rb b/db/primary_migrate/20250327195800_remove_uniqueness_from_iaa_orders_index.rb new file mode 100644 index 00000000000..6f14767c6e5 --- /dev/null +++ b/db/primary_migrate/20250327195800_remove_uniqueness_from_iaa_orders_index.rb @@ -0,0 +1,13 @@ +class RemoveUniquenessFromIaaOrdersIndex < ActiveRecord::Migration[8.0] + disable_ddl_transaction! + + def up + remove_index :iaa_orders, name: "index_iaa_orders_on_iaa_gtc_id_and_order_number" + add_index :iaa_orders, [:iaa_gtc_id, :order_number], algorithm: :concurrently + end + + def down + raise ActiveRecord::IrreversibleMigration, + "Cannot reverse migration that removed uniqueness from index" + end +end diff --git a/db/schema.rb b/db/schema.rb index ef48c047c73..da33a042518 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.0].define(version: 2025_03_11_164618) do +ActiveRecord::Schema[8.0].define(version: 2025_03_27_195800) do # These are extensions that must be enabled in order to support this database enable_extension "citext" enable_extension "pg_catalog.plpgsql" @@ -483,6 +483,8 @@ create_table "recaptcha_assessments", id: :string, force: :cascade do |t| t.string "annotation", comment: "sensitive=false" t.string "annotation_reason", comment: "sensitive=false" + t.datetime "created_at", null: false, comment: "sensitive=false" + t.datetime "updated_at", null: false, comment: "sensitive=false" end create_table "registration_logs", force: :cascade do |t| diff --git a/lib/cleanup/remove_integration_from_order.rb b/lib/cleanup/remove_integration_from_order.rb new file mode 100644 index 00000000000..ccc2acc33b6 --- /dev/null +++ b/lib/cleanup/remove_integration_from_order.rb @@ -0,0 +1,88 @@ +# frozen_string_literal: true + +class RemoveIntegrationFromOrder + attr_reader :iaa_gtc_number, :order_number, :issuer, :stdin, :stdout + + def initialize(iaa_gtc_number, order_number, mod_number, issuer, stdin: STDIN, stdout: STDOUT) + @stdin = stdin + @stdout = stdout + @iaa_gtc_number = iaa_gtc_number + @order_number = order_number + @mod_number = mod_number + @issuer = issuer + end + + def run + validate_inputs + print_data + confirm_deletion + remove_integration + end + + private + + def validate_inputs + unless iaa_gtc + raise ArgumentError, "No IAA GTC found with number #{iaa_gtc_number}" + end + + unless integration + raise ArgumentError, "No integration found for issuer #{issuer}" + end + + unless integration_usage + raise ArgumentError, 'No integration usage found for this combination' + end + end + + def print_data + stdout.puts "You are about to remove integration #{issuer} from IAA order:" + stdout.puts "GTC: #{iaa_gtc_number}" + stdout.puts "Order: #{order_number}" + stdout.puts "\nIntegration details:" + stdout.puts integration.attributes.to_yaml + stdout.puts "\nIAA Order details:" + stdout.puts iaa_order.attributes.to_yaml + stdout.puts "\n" + end + + def confirm_deletion + stdout.puts "Type 'yes' and hit enter to continue and remove this integration usage:\n" + continue = stdin.gets.chomp + + unless continue == 'yes' + stdout.puts 'You have indicated there is an issue. Aborting script' + exit 1 + end + end + + def remove_integration + stdout.puts 'Removing integration usage...' + integration_usage.destroy! + stdout.puts "Successfully removed integration #{issuer} from IAA order #{iaa_gtc_number}-#{order_number}" + end + + def iaa_gtc + @iaa_gtc ||= IaaGtc.find_by(gtc_number: iaa_gtc_number) + end + + def iaa_order + @iaa_order ||= IaaOrder.includes(:iaa_gtc).find_by( + iaa_gtc: iaa_gtc, + order_number: order_number, + mod_number: mod_number, + ) + end + + def integration + @integration ||= Integration.find_by(issuer: issuer) + end + + def integration_usage + @integration_usage ||= IntegrationUsage.find_by( + iaa_order: iaa_order, + integration: integration, + mod_number: mod_number, + ) + end +end diff --git a/spec/models/agreements/iaa_order_spec.rb b/spec/models/agreements/iaa_order_spec.rb index dfd5d099c11..60b89a506cc 100644 --- a/spec/models/agreements/iaa_order_spec.rb +++ b/spec/models/agreements/iaa_order_spec.rb @@ -5,7 +5,6 @@ subject { create(:iaa_order) } it { is_expected.to validate_presence_of(:order_number) } - it { is_expected.to validate_uniqueness_of(:order_number).scoped_to(:iaa_gtc_id) } it do is_expected.to validate_numericality_of(:order_number) .only_integer @@ -36,6 +35,19 @@ expect(subject).not_to be_valid end + it 'validates that order dates do not overlap for the same order number' do + overlapping_order = build( + :iaa_order, iaa_gtc: subject.iaa_gtc, + order_number: subject.order_number + ) + + overlapping_order.start_date = subject.end_date - 1.month + overlapping_order.end_date = subject.end_date + 3.months + + expect(overlapping_order).not_to be_valid + expect(overlapping_order.errors[:base]).to include('Overlapping order dates for the same order number') + end + it { is_expected.to belong_to(:iaa_gtc) } it { is_expected.to have_one(:partner_account).through(:iaa_gtc) }