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

Fix #4172 donation validation display error #4483

Merged
merged 11 commits into from
Aug 4, 2024
14 changes: 11 additions & 3 deletions app/controllers/donations_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -90,14 +90,22 @@ def show

def update
@donation = Donation.find(params[:id])
@original_source = @donation.source
ItemizableUpdateService.call(itemizable: @donation,
params: donation_params,
type: :increase,
event_class: DonationEvent)
flash.clear
flash[:notice] = "Donation updated!"
redirect_to donations_path
rescue => e
flash[:alert] = "Error updating donation: #{e.message}"
render "edit"
load_form_collections
# calling new(donation_params) triggers a validation error if line_item quantity is invalid
@previous_input = Donation.new(donation_params.except(:line_items_attributes))
@previous_input.line_items.build(donation_params[:line_items_attributes].values)

render "edit", status: :conflict
end

def destroy
Expand Down Expand Up @@ -129,14 +137,14 @@ def clean_donation_money_raised
params[:donation][:money_raised] = money_raised.gsub(/[$,.]/, "") if money_raised

money_raised_in_dollars = params[:donation][:money_raised_in_dollars]
params[:donation][:money_raised] = money_raised_in_dollars.gsub(/[$,]/, "").to_d * 100 if money_raised_in_dollars
params[:donation][:money_raised] = (money_raised_in_dollars.gsub(/[$,]/, "").to_d * 100).to_s if money_raised_in_dollars
end

def donation_params
strip_unnecessary_params
clean_donation_money_raised
params = compact_line_items
params.require(:donation).permit(:source, :comment, :storage_location_id, :money_raised, :issued_at, :donation_site_id, :product_drive_id, :product_drive_participant_id, :manufacturer_id, line_items_attributes: %i(id item_id quantity _destroy)).merge(organization: current_organization)
params.require(:donation).permit(:source, :comment, :storage_location_id, :money_raised, :issued_at, :donation_site_id, :product_drive_id, :product_drive_participant_id, :manufacturer_id, line_items_attributes: %i(id item_id quantity)).merge(organization: current_organization)
end

def donation_item_params
Expand Down
27 changes: 20 additions & 7 deletions app/views/donations/_donation_form.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
<div class="col-xs-8 col-md-6 col-lg-3">
<%= f.input :source,
collection: Donation::SOURCES.values,
selected: donation_form.source,
include_blank: true,
label: "Source",
error: "What effort or initiative did this donation come from?",
wrapper: :input_group %>
Expand All @@ -16,6 +18,8 @@
<div class="col-xs-8 col-md-6 col-lg-3">
<%= f.association :donation_site,
collection: @donation_sites,
selected: donation_form.donation_site_id,
include_blank: true,
label: "Donation Site",
error: "Where was this donation dropped off?",
wrapper: :input_group %>
Expand All @@ -25,6 +29,8 @@
<div class="col-xs-8 col-md-6 col-lg-3">
<%= f.association :product_drive,
collection: @product_drives,
selected: donation_form.product_drive_id,
include_blank: true,
label_method: lambda { |x| "#{x.try(:name) }" },
label: "Product Drive",
error: "Which product drive was this from?",
Expand All @@ -33,6 +39,8 @@
<div class="col-xs-8 col-md-6 col-lg-3">
<%= f.association :product_drive_participant,
collection: @product_drive_participants,
selected: donation_form.product_drive_participant_id,
include_blank: true,
label_method: lambda { |x| "#{x.try(:business_name) }" },
label: "Product Drive Participant",
error: "Which product drive participant was this from?",
Expand All @@ -44,7 +52,8 @@
<div class="col-xs-8 col-md-6 col-lg-3">
<%= f.association :manufacturer,
collection: @manufacturers,
prompt: "Choose one...",
selected: donation_form.manufacturer_id,
include_blank: true,
label_method: lambda { |x| "#{x.try(:name) }" },
label: "Manufacturer",
error: "Which Manufacturer was this from?",
Expand All @@ -58,7 +67,7 @@
collection: @storage_locations,
label: "Storage Location",
error: "Where is it being stored?",
selected: f.object.storage_location&.id || current_organization.intake_location,
selected: donation_form.storage_location&.id || current_organization.intake_location,
include_blank: true,
wrapper: :input_group %>
</div>
Expand All @@ -68,15 +77,18 @@
<div class="col-xs-8 col-md-6 col-lg-3">
<%= f.input :money_raised_in_dollars, as: :money_raised_in_dollars, wrapper: :input_group do %>
<span class="input-group-text"><i class="fa fa-usd"></i></span>
<%= f.input_field :money_raised_in_dollars, class: "form-control" %>
<%= f.input_field :money_raised_in_dollars,
class: "form-control",
value: donation_form.money_raised_in_dollars %>
<% end %>
</div>
</div>

<div class="row">
<div class="col-xs-8 col-md-6">
<%= f.input :comment,
wrapper: :input_group %>
<%= f.input :comment, wrapper: :input_group do %>
<%= f.text_area :comment, value: donation_form.comment, cols: 90, rows: 2, class: "text optional form-control" %>
<% end %>
</div>
</div>

Expand All @@ -86,15 +98,16 @@
label: "Issued on",
as: :date,
html5: true,
wrapper: :input_group %>
wrapper: :input_group,
input_html: { value: donation_form.issued_at } %>
</div>
</div>

<fieldset style="margin-bottom: 2rem;" class='w-70'>
<legend>Items in this donation</legend>
<div id="donation_line_items" data-capture-barcode="true">

<%= render 'line_items/line_item_fields', form: f %>
<%= render 'line_items/line_item_fields', form: f, object: donation_form.line_items %>
</div>

<div class="row links justify-content-end">
Expand Down
8 changes: 4 additions & 4 deletions app/views/donations/edit.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
<div class="container-fluid">
<div class="row mb-2">
<div class="col-sm-6">
<% content_for :title, "Edit - Donations - #{@donation.source} - #{current_organization.name}" %>
<% content_for :title, "Edit - Donations - #{@original_source ? @original_source : @donation.source} - #{current_organization.name}" %>
<h1>
Editing Donation
<small>from <%= @donation.source %></small>
<small>from <%= @original_source ? @original_source : @donation.source %></small>
</h1>
</div>
<div class="col-sm-6">
Expand All @@ -16,7 +16,7 @@
</li>
<li class="breadcrumb-item"><%= link_to "Donations", donations_path %></li>
<li class="breadcrumb-item">
<a href="#">Editing <%= @donation.source %> on <%= @donation.created_at.to_fs(:distribution_date) %></a></li>
<a href="#">Editing <%= @original_source ? @original_source : @donation.source %> on <%= @donation.created_at.to_fs(:distribution_date) %></a></li>
</ol>
</div>
</div>
Expand All @@ -39,7 +39,7 @@
<div class="card card-primary">
<!-- /.card-header -->
<div class="card-body">
<%= render partial: "donation_form", object: @donation %>
<%= render partial: "donation_form", object: @previous_input ? @previous_input : @donation %>
<%= link_to "Add vendor", new_vendor_path, {:remote => true, "data-bs-toggle" => "modal", "data-bs-target" => "#modal-window", id: "new_vendor", "style" => "display: none;"} %>
</div><!-- /.box -->
</div>
Expand Down
28 changes: 10 additions & 18 deletions spec/controllers/donations_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@
line_item = donation.line_items.first
line_item_params = {
"0" => {
"_destroy" => "false",
item_id: line_item.item_id,
quantity: "15",
id: line_item.id
Expand All @@ -82,7 +81,6 @@
line_item = donation.line_items.first
line_item_params = {
"0" => {
"_destroy" => "false",
item_id: line_item.item_id,
quantity: "8",
id: line_item.id
Expand Down Expand Up @@ -111,40 +109,34 @@
line_item = donation.line_items.first
line_item_params = {
"0" => {
"_destroy" => "false",
item_id: line_item.item_id,
quantity: "1",
id: line_item.id
}
}
donation_params = { source: donation.source, storage_location: new_storage_location, line_items_attributes: line_item_params }
put :update, params: { id: donation.id, donation: donation_params }
expect(response).not_to redirect_to(anything)
expect(response).not_to redirect_to(edit_donation_path(donation))
expect(original_storage_location.size).to eq 5
expect(new_storage_location.size).to eq 0
expect(donation.reload.line_items.first.quantity).to eq 10
end
end

describe "when removing a line item" do
it "updates storage invetory item quantity correctly" do
donation = create(:donation, :with_items, item_quantity: 10)
line_item = donation.line_items.first
line_item_params = {
"0" => {
"_destroy" => "true",
item_id: line_item.item_id,
id: line_item.id
}
}
donation_params = { source: donation.source, line_items_attributes: line_item_params }
it "updates storage inventory item quantity correctly" do
item_id = 1
item_quantity = 10
donation = create(:donation, :with_items, item: create(:item, id: item_id), item_quantity: item_quantity)
# if all line items including blanks are deleted line_items_attributes parameter is not sent
donation_params = { source: donation.source }
expect do
put :update, params: { id: donation.id, donation: donation_params }
end.to change { donation.storage_location.inventory_items.first.quantity }.by(-10)
end.to change { donation.storage_location.inventory_items.first.quantity }.by(-1 * item_quantity)
.and change {
View::Inventory.new(donation.organization_id)
.quantity_for(storage_location: donation.storage_location_id, item_id: line_item.item_id)
}.by(-10)
.quantity_for(storage_location: donation.storage_location_id, item_id: item_id)
}.by(-1 * item_quantity)
end
end
end
Expand Down
8 changes: 4 additions & 4 deletions spec/factories/donations.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,15 @@
end

trait :with_items do
transient do
item_quantity { 100 }
item { nil }
end
storage_location do
create :storage_location, :with_items,
item: item || create(:item, value_in_cents: 100),
organization: organization
end
transient do
item_quantity { 100 }
item { nil }
end

after(:build) do |donation, evaluator|
event_item = View::Inventory.new(donation.organization_id)
Expand Down
78 changes: 78 additions & 0 deletions spec/requests/donations_requests_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -192,5 +192,83 @@
expect(response.body).to_not include("you’ll need to make an adjustment to the inventory as well.")
end
end

# Bug fix - Issue #4172
context "when donated items are distributed to less than donated amount " \
"and you edit the donation to less than distributed amount" do
it "shows a warning and displays original names and amounts and info the user entered" do
item_name = "Brightbloom Seed"
item = create(:item, organization: organization, name: item_name)
storage_location = create(:storage_location, :with_items, item: item, item_quantity: 0, organization: organization)
extra_item_name = "Extra Item"
extra_item = create(:item, organization: organization, name: extra_item_name)
item_to_delete_name = "Item To Delete"
item_to_delete = create(:item, organization: organization, name: item_to_delete_name)
TestInventory.create_inventory(organization, { storage_location.id => { item.id => 0, extra_item.id => 1 } })
original_quantity = 100
original_source = Donation::SOURCES[:manufacturer]
original_date = DateTime.new(2024)
donation = build(:manufacturer_donation, issued_at: original_date, organization: organization, storage_location: storage_location, source: original_source)
donation.line_items << build(:line_item, item: item, quantity: original_quantity)
donation.line_items << build(:line_item, item: item_to_delete, quantity: 1)

DonationCreateService.call(donation)

# simulate distribution by setting item inventory to 10
TestInventory.create_inventory(organization, { storage_location.id => { item.id => 10, extra_item.id => 1 } })

edited_source = Donation::SOURCES[:product_drive]
edited_source_drive_name = "Test Product Drive"
edited_source_drive = create(:product_drive, name: edited_source_drive_name, organization: organization)
edited_source_drive_participant_business_name = "Test Business Name"
edited_source_drive_participant = create(:product_drive_participant, business_name: edited_source_drive_participant_business_name, organization: organization)
edited_storage_location_name = "Test Storage"
edited_storage_location = create(:storage_location, name: edited_storage_location_name, organization: organization)
edited_money = 10.0
edited_comment = "New test comment"
edited_date = "2019-01-01"
extra_quantity = 1
edited_quantity = 1

# deleted item is removed from line_items_attributes entirely
edited_donation = {
source: edited_source,
product_drive_id: edited_source_drive.id,
product_drive_participant_id: edited_source_drive_participant.id,
storage_location_id: edited_storage_location.id,
money_raised_in_dollars: edited_money,
comment: edited_comment,
issued_at: edited_date,
line_items_attributes: {
"0": { item_id: item.id, quantity: edited_quantity },
"1": { item_id: extra_item.id, quantity: extra_quantity }
}
}

put donation_path(id: donation.id, donation: edited_donation)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is really what we're trying to hit, right? Why are we hitting all those other endpoints rather than just directly creating the data we want to interact with on this page?

Copy link
Contributor Author

@jimmyli97 jimmyli97 Jul 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think when I wrote this (several weeks ago at this point, I don't remember) I didn't know how the inventory system worked and that DistributionCreateService or TestInventory existed. I think this can be rewritten to just use TestInventory and set inventory to 0 before editing the donation, I will try that.

I am thinking of adding TestInventory info and linking to the wiki Event page in CONTRIBUTING.md so new contributors have a easier time writing tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolved by ba5af77


if Event.read_events?(organization)
expect(flash[:alert]).to include("Error updating donation: Could not reduce quantity")
else # TODO remove this branch when switching to events
expect(flash[:alert]).to include("Error updating donation: Requested items exceed the available inventory")
end

expect(response.body).to include("Edit - Donations - #{original_source}")
expect(response.body).to include("Editing Donation\n <small>from #{original_source}")
expect(response.body).to include("<li class=\"breadcrumb-item\">\n <a href=\"#\">Editing #{original_source}")
expect(response.body).to include("<option selected=\"selected\" value=\"#{edited_source}\">#{edited_source}</option>")
expect(response.body).to include("<option selected=\"selected\" value=\"#{edited_source_drive.id}\">#{edited_source_drive_name}</option>")
expect(response.body).to include("<option selected=\"selected\" value=\"#{edited_source_drive_participant.id}\">#{edited_source_drive_participant_business_name}</option>")
expect(response.body).to include("<option selected=\"selected\" value=\"#{edited_storage_location.id}\">#{edited_storage_location_name}</option>")
expect(response.body).to include(edited_comment)
expect(response.body).to include("value=\"#{edited_money}\" type=\"text\" name=\"donation[money_raised_in_dollars]")
expect(response.body).to include(edited_date)
expect(response.body).to include("<option selected=\"selected\" value=\"#{item.id}\">#{item_name}</option>")
expect(response.body).to include("value=\"#{edited_quantity}\" name=\"donation[line_items_attributes][0][quantity]")
expect(response.body).to include("<option selected=\"selected\" value=\"#{extra_item.id}\">#{extra_item_name}</option>")
expect(response.body).to include("value=\"#{extra_quantity}\" name=\"donation[line_items_attributes][1][quantity]")
expect(response.body).not_to include("<option selected=\"selected\" value=\"#{item_to_delete.id}\">#{item_to_delete_name}</option>")
end
end
end
end