Skip to content

Fix #4172 donation validation display error #4483

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

Merged
merged 11 commits into from
Aug 4, 2024
Merged
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -787,4 +787,4 @@ DEPENDENCIES
webmock (~> 3.23)

BUNDLED WITH
2.5.11
2.5.14
17 changes: 15 additions & 2 deletions app/controllers/donations_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -77,14 +77,27 @@ 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))
line_items = []
donation_params[:line_items_attributes].values.each { |attr|
attr.delete(:_destroy)
line_items.push(attr)
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think this would put back any line items that the user removed?

Copy link
Contributor Author

@jimmyli97 jimmyli97 Jul 16, 2024

Choose a reason for hiding this comment

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

I think you would have been right, but I originally put this line to pass donation controller request rspec tests which sent the _destroy parameter. Those tests I found out are outdated, the way the donation form is currently implemented the _destroy parameter is never sent, the donation object is just overridden with the new line_items_attributes. updated those rspec tests and removed this line in 3560157

@previous_input.line_items.build(line_items)

render "edit", status: :conflict
end

def destroy
Expand Down Expand Up @@ -116,7 +129,7 @@ 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
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
2 changes: 1 addition & 1 deletion spec/controllers/donations_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@
}
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
Expand Down
72 changes: 72 additions & 0 deletions spec/requests/donations_requests_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -172,5 +172,77 @@
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 = create(:item, organization: organization, name: "Brightbloom Seed")
storage_location = create(:storage_location, :with_items, item: item, item_quantity: 0, organization: organization)
extra_item = create(:item, organization: organization, name: "Extra Item")
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 = create(:manufacturer_donation, :with_items, item: item, item_quantity: original_quantity, issued_at: original_date, organization: organization, storage_location: storage_location, source: original_source)
distribution = {
storage_location_id: storage_location.id,
partner_id: create(:partner).id,
delivery_method: :delivery,
line_items_attributes: {
"0": { item_id: item.id, quantity: 90 }
}
}

post distributions_path(distribution: distribution, format: :turbo_stream)

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

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>")
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'd prefer to specify the name etc. (anything we're actually checking) when creating the data. This makes the tests less flaky and more easily reproducible.

Copy link
Contributor Author

@jimmyli97 jimmyli97 Jul 17, 2024

Choose a reason for hiding this comment

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

resolved by 82a76fd

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]")
end
end
end
end