diff --git a/app/controllers/audits_controller.rb b/app/controllers/audits_controller.rb index 7d08c6949a..de69df0224 100644 --- a/app/controllers/audits_controller.rb +++ b/app/controllers/audits_controller.rb @@ -2,6 +2,7 @@ class AuditsController < ApplicationController before_action :authorize_admin before_action :set_audit, only: %i(show edit update destroy finalize) + before_action :ensure_audit_is_editable, only: %i(finalize update) def index @selected_location = filter_params[:at_location] @@ -80,6 +81,12 @@ def destroy private + def ensure_audit_is_editable + if @audit.reload.finalized? + redirect_to audit_path(@audit), error: "This audit has been finalized and cannot be edited." + end + end + def handle_audit_errors error_message = @audit.errors.uniq(&:attribute).map do |error| attr = (error.attribute.to_s == 'base') ? '' : error.attribute.capitalize diff --git a/app/models/account_request.rb b/app/models/account_request.rb index bd48ee6956..75c0ef40c0 100644 --- a/app/models/account_request.rb +++ b/app/models/account_request.rb @@ -25,6 +25,8 @@ class AccountRequest < ApplicationRecord validate :email_not_already_used_by_organization validate :email_not_already_used_by_user + validate :cannot_change_status_once_rejected, + :cannot_change_status_once_closed, on: :update belongs_to :ndbn_member, class_name: 'NDBNMember', optional: true @@ -100,4 +102,16 @@ def email_not_already_used_by_user errors.add(:email, 'already used by an existing User') end end + + def cannot_change_status_once_rejected + if status_changed? && status_was == "rejected" + errors.add(:status, "cannot be changed once rejected") + end + end + + def cannot_change_status_once_closed + if status_changed? && status_was == "admin_closed" + errors.add(:status, "cannot be changed once closed by an admin") + end + end end diff --git a/app/models/audit.rb b/app/models/audit.rb index 5a669623ca..b387473663 100644 --- a/app/models/audit.rb +++ b/app/models/audit.rb @@ -32,6 +32,7 @@ class Audit < ApplicationRecord validate :line_items_quantity_is_not_negative validate :line_items_unique_by_item_id validate :user_is_organization_admin_of_the_organization + validate :cannot_change_status_once_finalized, on: :update def self.finalized_since?(itemizable, *location_ids) item_ids = itemizable.line_items.pluck(:item_id) @@ -86,4 +87,10 @@ def line_items_unique_by_item_id def line_items_quantity_is_not_negative line_items_quantity_is_at_least(0) end + + def cannot_change_status_once_finalized + if status_changed? && status_was == "finalized" + errors.add(:status, "cannot be changed once finalized") + end + end end diff --git a/spec/models/account_request_spec.rb b/spec/models/account_request_spec.rb index b03c5b834b..cf984183e9 100644 --- a/spec/models/account_request_spec.rb +++ b/spec/models/account_request_spec.rb @@ -74,6 +74,30 @@ end end + describe '#status' do + it "does not regress from rejected to another status" do + rejected_request = create(:account_request, status: 'rejected') + + expect { rejected_request.confirm! } + .to raise_error(ActiveRecord::RecordInvalid, /cannot be changed once rejected/) + end + + it "does not regress from admin_closed to another status" do + rejected_request = create(:account_request, status: 'admin_closed') + + expect { rejected_request.confirm! } + .to raise_error(ActiveRecord::RecordInvalid, /cannot be changed once closed by an admin/) + end + + it "allows normal transitions" do + started_request = create(:account_request, status: 'started') + user_confirmed_request = create(:account_request, status: 'user_confirmed') + + expect { started_request.confirm! }.not_to raise_error + expect { user_confirmed_request.reject!('rejectable request') }.not_to raise_error + end + end + describe '.get_by_identity_token' do subject { described_class.get_by_identity_token(identity_token) } diff --git a/spec/models/audit_spec.rb b/spec/models/audit_spec.rb index 10996b97fd..9da2621ab1 100644 --- a/spec/models/audit_spec.rb +++ b/spec/models/audit_spec.rb @@ -92,6 +92,23 @@ expect(audit.save).to be_truthy end + + describe '#status' do + it "does not regress from finalized to another status" do + finalized_audit = create(:audit, organization:, status: :finalized) + + expect { finalized_audit.update!(status: :confirmed) } + .to raise_error(ActiveRecord::RecordInvalid, /cannot be changed once finalized/) + end + + it "allows normal transitions" do + in_progress_audit = create(:audit, organization:, status: :in_progress) + confirmed_audit = create(:audit, organization:, status: :confirmed) + + expect { in_progress_audit.update!(status: :confirmed) }.not_to raise_error + expect { confirmed_audit.update!(status: :finalized) }.not_to raise_error + end + end end context "Scopes >" do diff --git a/spec/requests/audits_requests_spec.rb b/spec/requests/audits_requests_spec.rb index eb22bdc857..079d0c00df 100644 --- a/spec/requests/audits_requests_spec.rb +++ b/spec/requests/audits_requests_spec.rb @@ -6,7 +6,7 @@ { organization_id: organization.id, storage_location_id: storage_location.id, - user_id: create(:organization_admin, organization: organization).id + user_id: organization_admin.id } end @@ -14,7 +14,7 @@ { organization_id: organization.id, storage_location_id: nil, - user_id: create(:organization_admin, organization: organization).id + user_id: organization_admin.id } end @@ -22,8 +22,6 @@ { organization_id: nil } end - let(:valid_session) { {} } - describe "while signed in as an organization admin" do before do sign_in(organization_admin) @@ -109,6 +107,40 @@ end end + describe "PUT #update" do + it "confirms the updated audit and redirects to the audit" do + audit = create(:audit, organization: organization, status: :in_progress) + item = create(:item) + audit.line_items << create(:line_item, quantity: 3, item: item) + + put audit_path(id: audit.to_param, audit: { + storage_location_id: storage_location.id, + line_items_attributes: {"0" => {"item_id" => item.id, "quantity" => "4"}} + }) + + expect(response).to redirect_to(audit_path(audit)) + expect(flash[:notice]).to include("Audit is confirmed.") + expect(audit.reload).to be_confirmed + end + + context "when the audit has already been finalized" do + it "does not allow updates and redirects to the finalized audit" do + finalized_audit = create(:audit, organization: organization, status: :finalized) + item = create(:item) + finalized_audit.line_items << create(:line_item, quantity: 3, item: item) + + put audit_path(id: finalized_audit.to_param, audit: { + storage_location_id: storage_location.id, + line_items_attributes: {"0" => {"item_id" => item.id, "quantity" => "4"}} + }) + + expect(response).to redirect_to(audit_path(finalized_audit)) + expect(flash[:error]).to include("This audit has been finalized and cannot be edited.") + expect(finalized_audit.line_items.first.quantity).to eq(3) + end + end + end + describe "POST #create" do context "with valid params" do it "creates a new Audit" do @@ -121,6 +153,7 @@ expect do post audits_path(audit: valid_attributes, save_progress: '') expect(Audit.last.in_progress?).to be_truthy + expect(flash[:notice]).to include("Audit's progress was successfully saved.") end.to change(Audit.in_progress, :count).by(1) end @@ -170,6 +203,19 @@ expect(audit.reload).to be_finalized expect(AuditEvent.count).to eq(1) end + + context "when the audit has already been finalized" do + it "does not create a new AuditEvent and redirects to the finalized audit" do + finalized_audit = create(:audit, organization: organization, status: :finalized) + + expect do + post audit_finalize_path(audit_id: finalized_audit.to_param) + end.not_to change(AuditEvent, :count) + + expect(response).to redirect_to(audit_path(finalized_audit)) + expect(flash[:error]).to include("This audit has been finalized and cannot be edited.") + end + end end describe "DELETE #destroy" do