Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevent changing storage location on itemizables if there has been an intervening audit of the items involved #4428

Merged
merged 6 commits into from
Jun 14, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/models/audit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def self.finalized_since?(itemizable, *location_ids)
item_ids = itemizable.line_items.pluck(:item_id)
where(status: "finalized")
.where(storage_location_id: location_ids)
.where(created_at: itemizable.created_at..)
.where(updated_at: itemizable.created_at..)
.joins(:line_items)
.where(line_items: {item_id: item_ids})
.exists?
Expand Down
19 changes: 19 additions & 0 deletions app/services/itemizable_update_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ def self.call(itemizable:, type: :increase, params: {}, event_class: nil)
from_location = to_location = itemizable.storage_location
to_location = StorageLocation.find(params[:storage_location_id]) if params[:storage_location_id]

verify_intervening_audit_on_storage_location_items(itemizable: itemizable, from_location_id: from_location.id, to_location_id: to_location.id)

apply_change_method = (type == :increase) ? :increase_inventory : :decrease_inventory
undo_change_method = (type == :increase) ? :decrease_inventory : :increase_inventory

Expand Down Expand Up @@ -58,4 +60,21 @@ def self.update_storage_location(itemizable:, apply_change_method:, undo_change_
# Apply the new changes to the storage location inventory
to_location.public_send(apply_change_method, itemizable.line_item_values)
end

# @param itemizable [Itemizable]
# @param from_location [StorageLocation]
# @param to_location [StorageLocation]
def self.verify_intervening_audit_on_storage_location_items(itemizable:, from_location_id:, to_location_id:)
return unless from_location_id != to_location_id && Audit.finalized_since?(itemizable, [from_location_id, to_location_id])
jp524 marked this conversation as resolved.
Show resolved Hide resolved

itemizable_type = itemizable.class.name.downcase
case itemizable_type
when "distribution"
raise "Cannot change the storage location because there has been an intervening audit of some items. " \
"If you need to change the storage location, please reclaim this distribution and create a new distribution from the new storage location."
else
raise "Cannot change the storage location because there has been an intervening audit of some items. " \
"If you need to change the storage location, please delete this #{itemizable_type} and create a new #{itemizable_type} with the new storage location."
end
end
end
56 changes: 42 additions & 14 deletions spec/services/itemizable_update_service_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,27 @@
expect(UpdateExistingEvent.count).to eq(1)
end

it "should update quantity in different locations" do
attributes[:storage_location_id] = new_storage_location.id
subject
expect(itemizable.reload.line_items.count).to eq(2)
expect(itemizable.line_items.sum(&:quantity)).to eq(4)
expect(storage_location.size).to eq(10)
expect(new_storage_location.size).to eq(24)
context "when storage location changes" do
context "when there is no intervening audit" do
it "should update quantity in different locations" do
attributes[:storage_location_id] = new_storage_location.id
subject
expect(itemizable.reload.line_items.count).to eq(2)
expect(itemizable.line_items.sum(&:quantity)).to eq(4)
expect(storage_location.size).to eq(10)
expect(new_storage_location.size).to eq(24)
end
end

context "when there is an intervening audit on one of the items involved" do
it "raises an error" do
msg = "Cannot change the storage location because there has been an intervening audit of some items. " \
"If you need to change the storage location, please delete this donation and create a new donation with the new storage location."
create(:audit, :with_items, item: itemizable.items.first, organization: organization, storage_location: storage_location, status: "finalized")
attributes[:storage_location_id] = new_storage_location.id
expect { subject }.to raise_error(msg)
end
end
end

it "should raise an error if any item is inactive" do
Expand Down Expand Up @@ -114,13 +128,27 @@
expect(itemizable.issued_at).to eq(2.days.ago)
end

it "should update quantity in different locations" do
attributes[:storage_location_id] = new_storage_location.id
subject
expect(itemizable.reload.line_items.count).to eq(2)
expect(itemizable.line_items.sum(&:quantity)).to eq(4)
expect(storage_location.size).to eq(30)
expect(new_storage_location.size).to eq(16)
context "when storage location changes" do
context "when there is no intervening audit" do
it "should update quantity in different locations" do
attributes[:storage_location_id] = new_storage_location.id
subject
expect(itemizable.reload.line_items.count).to eq(2)
expect(itemizable.line_items.sum(&:quantity)).to eq(4)
expect(storage_location.size).to eq(30)
expect(new_storage_location.size).to eq(16)
end
end

context "when there is an intervening audit on one of the items involved" do
it "raises an error" do
msg = "Cannot change the storage location because there has been an intervening audit of some items. " \
"If you need to change the storage location, please reclaim this distribution and create a new distribution from the new storage location."
create(:audit, :with_items, item: itemizable.items.first, organization: organization, storage_location: storage_location, status: "finalized")
attributes[:storage_location_id] = new_storage_location.id
expect { subject }.to raise_error(msg)
end
end
end

it "should raise an error if any item is inactive" do
Expand Down