diff --git a/app/controllers/purchases_controller.rb b/app/controllers/purchases_controller.rb index 6a675b3900..e989a6a0ed 100644 --- a/app/controllers/purchases_controller.rb +++ b/app/controllers/purchases_controller.rb @@ -101,7 +101,7 @@ def destroy def load_form_collections @storage_locations = current_organization.storage_locations.active.alphabetized @items = current_organization.items.active.alphabetized - @vendors = current_organization.vendors.alphabetized + @vendors = current_organization.vendors.active.alphabetized end def purchase_params diff --git a/app/controllers/vendors_controller.rb b/app/controllers/vendors_controller.rb index 69630f3ed0..4226997bc9 100644 --- a/app/controllers/vendors_controller.rb +++ b/app/controllers/vendors_controller.rb @@ -3,7 +3,14 @@ class VendorsController < ApplicationController include Importable def index - @vendors = current_organization.vendors.with_volumes.alphabetized + @vendors = current_organization + .vendors + .with_volumes + .alphabetized + .class_filter(filter_params) + + @vendors = @vendors.active unless params[:include_inactive_vendors] + @include_inactive_vendors = params[:include_inactive_vendors] respond_to do |format| format.html @@ -53,6 +60,34 @@ def update end end + def deactivate + vendor = current_organization.vendors.find(params[:id]) + + begin + vendor.deactivate! + rescue => e + flash[:error] = e.message + redirect_back(fallback_location: vendors_path) + return + end + + redirect_to vendors_path, notice: "#{vendor.business_name} has been deactivated." + end + + def reactivate + vendor = current_organization.vendors.find(params[:id]) + + begin + vendor.reactivate! + rescue => e + flash[:error] = e.message + redirect_back(fallback_location: vendors_path) + return + end + + redirect_to vendors_path, notice: "#{vendor.business_name} has been reactivated." + end + private def vendor_params @@ -61,7 +96,9 @@ def vendor_params end helper_method \ - def filter_params - {} + def filter_params(_parameters = nil) + return {} unless params.key?(:filters) + + params.require(:filters).permit(:include_inactive_vendors) end end diff --git a/app/models/vendor.rb b/app/models/vendor.rb index 3e742a436e..b543cfde70 100644 --- a/app/models/vendor.rb +++ b/app/models/vendor.rb @@ -3,6 +3,7 @@ # Table name: vendors # # id :bigint not null, primary key +# active :boolean default(TRUE) # address :string # business_name :string # comment :string @@ -20,16 +21,32 @@ class Vendor < ApplicationRecord has_paper_trail include Provideable include Geocodable + include Filterable has_many :purchases, inverse_of: :vendor, dependent: :destroy validates :business_name, presence: true scope :alphabetized, -> { order(:business_name) } - + scope :active, -> { where(active: true) } scope :with_volumes, -> { left_joins(purchases: :line_items) .select("vendors.*, SUM(COALESCE(line_items.quantity, 0)) AS volume") .group(:id) } + + def volume + LineItem.where( + itemizable_type: "Purchase", + itemizable_id: purchase_ids + ).sum(:quantity) + end + + def deactivate! + update!(active: false) + end + + def reactivate! + update!(active: true) + end end diff --git a/app/views/vendors/_vendor_row.html.erb b/app/views/vendors/_vendor_row.html.erb index 2637601138..ce81480412 100644 --- a/app/views/vendors/_vendor_row.html.erb +++ b/app/views/vendors/_vendor_row.html.erb @@ -7,5 +7,14 @@ <%= view_button_to vendor_row %> <%= edit_button_to edit_vendor_path(vendor_row) %> + <% if vendor_row.active? %> + <%= deactivate_button_to deactivate_vendor_path(vendor_row), + text: 'Deactivate', + confirm: confirm_deactivate_msg(vendor_row.business_name) %> + <% else %> + <%= reactivate_button_to reactivate_vendor_path(vendor_row), + text: 'Reactivate', + confirm: confirm_reactivate_msg(vendor_row.business_name) %> + <% end %> diff --git a/app/views/vendors/index.html.erb b/app/views/vendors/index.html.erb index c1863a5c2d..4d437d6ba3 100644 --- a/app/views/vendors/index.html.erb +++ b/app/views/vendors/index.html.erb @@ -29,20 +29,38 @@
- + + +
+ <%= form_tag(vendors_path, method: :get) do |f| %> +
+
+ <%= filter_checkbox(label: "Also include inactive vendors", scope: "include_inactive_vendors", selected: @include_inactive_vendors) %> +
+
+
+ + <% end # form %> +
- + +
diff --git a/config/routes.rb b/config/routes.rb index 9e9ee17700..b6f5eed019 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -173,6 +173,10 @@ def set_up_flipper collection do post :import_csv end + member do + put :deactivate + put :reactivate + end end resources :kits do diff --git a/db/migrate/20250129154820_add_active_to_vendors.rb b/db/migrate/20250129154820_add_active_to_vendors.rb new file mode 100644 index 0000000000..715d8ea460 --- /dev/null +++ b/db/migrate/20250129154820_add_active_to_vendors.rb @@ -0,0 +1,11 @@ +# Add active column to vendors to make it possible to delete vendors without actually deleting them +class AddActiveToVendors < ActiveRecord::Migration[7.2] + def up + add_column :vendors, :active, :boolean + change_column_default :vendors, :active, true + end + + def down + remove_column :vendors, :active + end +end diff --git a/db/migrate/20250201151720_set_existing_vendors_active.rb b/db/migrate/20250201151720_set_existing_vendors_active.rb new file mode 100644 index 0000000000..53380319cb --- /dev/null +++ b/db/migrate/20250201151720_set_existing_vendors_active.rb @@ -0,0 +1,6 @@ +# Add active column to vendors to make it possible to delete vendors without actually deleting them +class SetExistingVendorsActive < ActiveRecord::Migration[7.2] + def change + Vendor.update_all(active: true) + end +end diff --git a/db/schema.rb b/db/schema.rb index a2106ab259..a1bcbe308f 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,8 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. - -ActiveRecord::Schema[7.2].define(version: 2025_01_29_015253) do +ActiveRecord::Schema[7.2].define(version: 2025_02_01_151720) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -856,6 +855,7 @@ t.float "longitude" t.datetime "created_at", precision: nil, null: false t.datetime "updated_at", precision: nil, null: false + t.boolean "active", default: true t.index ["latitude", "longitude"], name: "index_vendors_on_latitude_and_longitude" end diff --git a/spec/models/vendor_spec.rb b/spec/models/vendor_spec.rb index 7eef7f7abf..1512193214 100644 --- a/spec/models/vendor_spec.rb +++ b/spec/models/vendor_spec.rb @@ -3,6 +3,7 @@ # Table name: vendors # # id :bigint not null, primary key +# active :boolean default(TRUE) # address :string # business_name :string # comment :string @@ -37,6 +38,22 @@ expect(subject.first.volume).to eq(10) end end + + describe "deactivate!" do + it "deactivates the vendor" do + vendor = create(:vendor) + vendor.deactivate! + expect(vendor.active).to be(false) + end + end + + describe "reactivate!" do + it "reactivates the vendor" do + vendor = create(:vendor, active: false) + vendor.reactivate! + expect(vendor.active).to be(true) + end + end end describe "versioning" do diff --git a/spec/requests/purchases_requests_spec.rb b/spec/requests/purchases_requests_spec.rb index 0988a79647..bd68d5ce18 100644 --- a/spec/requests/purchases_requests_spec.rb +++ b/spec/requests/purchases_requests_spec.rb @@ -122,6 +122,11 @@ it "should include the storage location name" do expect(subject.body).to include("Pawane Location") end + + it 'does not show inactive vendors in the vendor dropdown' do + deactivated_vendor = create(:vendor, business_name: 'Deactivated Vendor', organization: organization, active: false) + expect(subject.body).not_to include(deactivated_vendor.business_name) + end end describe "POST#create" do diff --git a/spec/requests/vendors_requests_spec.rb b/spec/requests/vendors_requests_spec.rb index 7625ee7766..aa26b549d2 100644 --- a/spec/requests/vendors_requests_spec.rb +++ b/spec/requests/vendors_requests_spec.rb @@ -16,9 +16,22 @@ before { create(:vendor) } context "html" do + let!(:first_vendor) { create(:vendor, business_name: "Abc", organization: organization) } + let!(:deactivated_vendor) { create(:vendor, business_name: "Deactivated", organization: organization, active: false) } + let(:response_format) { 'html' } it { is_expected.to be_successful } + + it "should have only activated vendor names" do + subject + expect(response.body).to include(first_vendor.business_name) + expect(response.body).not_to include(deactivated_vendor.business_name) + end + + it "should have a deactivate button for each active vendor" do + expect(subject.body.scan("Deactivate").count).to eq(2) + end end context "csv" do diff --git a/spec/system/vendor_system_spec.rb b/spec/system/vendor_system_spec.rb index 7e35802fbc..1112b2983f 100644 --- a/spec/system/vendor_system_spec.rb +++ b/spec/system/vendor_system_spec.rb @@ -13,11 +13,41 @@ @third = create(:vendor, business_name: "Cde") visit vendors_path end + it "should have the vendor names in alphabetical order" do expect(page).to have_xpath("//table//tr", count: 4) expect(page.find(:xpath, "//table/tbody/tr[1]/td[1]")).to have_content(@first.business_name) expect(page.find(:xpath, "//table/tbody/tr[3]/td[1]")).to have_content(@third.business_name) end + + it "should deactivate a vendor when the deactivate button is clicked" do + expect { click_link "Deactivate", match: :first }.to change { @first.reload.active }.to(false) + end + + it "should reactivate a vendor when the reactivate button is clicked" do + expect { click_link "Deactivate", match: :first }.to change { @first.reload.active }.to(false) + + check "include_inactive_vendors" + click_button "Filter" + + expect { click_link "Reactivate", match: :first }.to change { @first.reload.active }.to(true) + end + + context "When using the include_inactive_vendors filter" do + before(:each) do + @active_vendor = create(:vendor, business_name: "Active Vendor", active: true) + @inactive_vendor = create(:vendor, business_name: "Inactive Vendor", active: false) + visit vendors_path + end + + it "shows inactive vendors when the filter is applied" do + check "include_inactive_vendors" + click_button "Filter" + + expect(page).to have_content(@active_vendor.business_name) + expect(page).to have_content(@inactive_vendor.business_name) + end + end end context "when creating a new vendor" do