Skip to content

Commit

Permalink
Merge pull request #4586 from rubyforgood/4403-packs-distribution
Browse files Browse the repository at this point in the history
#4403: Show units in new/edit distribution
  • Loading branch information
awwaiid committed Aug 25, 2024
2 parents 15f51d5 + ba54612 commit f47a634
Show file tree
Hide file tree
Showing 12 changed files with 250 additions and 26 deletions.
27 changes: 27 additions & 0 deletions app/assets/stylesheets/application.scss
Original file line number Diff line number Diff line change
Expand Up @@ -192,3 +192,30 @@ div.low_priority_warning {
margin: 5px;
text-align: center;
}

.distribution-title {
display: flex;
}

legend.with-request {
float: none;
width: 80%;
display: inline-block;
}

div.distribution-request-unit {
display: inline-block;
flex: 1;
text-align: right;
margin-right: 20px;
font-size: 1.5em;
}

.li-requested {
font-size: 1.5em;
width: 100px;
min-width: 100px;
text-align: right;
margin-right: 20px;
white-space: nowrap;
}
8 changes: 7 additions & 1 deletion app/controllers/distributions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,11 @@ def create
# does not match any known Request
@distribution.request = Request.find(request_id)
end
@distribution.line_items.build if @distribution.line_items.size.zero?
if @distribution.line_items.size.zero?
@distribution.line_items.build
elsif request_id
@distribution.initialize_request_items
end
@items = current_organization.items.alphabetized
if Event.read_events?(current_organization)
inventory = View::Inventory.new(@distribution.organization_id)
Expand Down Expand Up @@ -166,6 +170,7 @@ def show

def edit
@distribution = Distribution.includes(:line_items).includes(:storage_location).find(params[:id])
@distribution.initialize_request_items
if (!@distribution.complete? && @distribution.future?) ||
current_user.has_role?(Role::ORG_ADMIN, current_organization)
@distribution.line_items.build if @distribution.line_items.size.zero?
Expand Down Expand Up @@ -202,6 +207,7 @@ def update
else
flash[:error] = insufficient_error_message(result.error.message)
@distribution.line_items.build if @distribution.line_items.size.zero?
@distribution.initialize_request_items
@items = current_organization.items.alphabetized
@storage_locations = current_organization.storage_locations.active_locations.alphabetized
render :edit
Expand Down
35 changes: 30 additions & 5 deletions app/models/distribution.rb
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,31 @@ def copy_from_donation(donation_id, storage_location_id)
self.storage_location = StorageLocation.find(storage_location_id) if storage_location_id
end

# This is meant for the Edit page - we will be adding any request items that aren't in the
# distribution for whatever reason, with zero quantity.
def initialize_request_items
return if request.nil?

item_ids = Set.new
line_items.each do |line_item|
item_request = request.item_requests.find { |r| r.item_id == line_item.item_id }
if item_request
item_ids.add(item_request)
line_item.requested_item = item_request
end
end

request.item_requests.each do |item_request|
next if item_ids.include?(item_request)

line_items.new(
requested_item: item_request,
quantity: 0,
item_id: item_request.item_id
)
end
end

def copy_from_request(request_id)
request = Request.find(request_id)
self.request = request
Expand All @@ -110,12 +135,12 @@ def copy_from_request(request_id)
self.agency_rep = request.partner_user&.formatted_email
self.comment = request.comments
self.issued_at = Time.zone.today + 1.day
request.request_items.each do |item|
request.item_requests.each do |item_request|
line_items.new(
quantity: item["quantity"],
item: Item.eager_load(:base_item).find_by(organization: request.organization, id: item["item_id"]),
itemizable_id: request.id,
itemizable_type: "Distribution"
requested_item: item_request,
# if there is a custom unit, don't prefill with the quantity - they have to enter it
quantity: item_request.request_unit.present? ? nil : item_request.quantity,
item_id: item_request.item_id
)
end
end
Expand Down
4 changes: 4 additions & 0 deletions app/models/line_item.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ class LineItem < ApplicationRecord

delegate :name, to: :item

# Used in a distribution that was initialized from a request. The `item_request` will be
# populated here.
attr_accessor :requested_item

def quantity_must_be_a_number_within_range
if quantity && quantity > MAX_INT
errors.add(:quantity, "must be less than #{MAX_INT}")
Expand Down
3 changes: 3 additions & 0 deletions app/services/distribution_update_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ def call
perform_distribution_service do
@old_issued_at = distribution.issued_at
@old_delivery_method = distribution.delivery_method
@params[:line_items_attributes]&.delete_if { |_, a| a[:quantity].to_i.zero? }

# remove line_items with zero quantity

ItemizableUpdateService.call(
itemizable: distribution,
Expand Down
11 changes: 8 additions & 3 deletions app/views/distributions/_form.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,18 @@
<%= f.input :comment, label: "Comment" %>

<fieldset style="margin-bottom: 2rem">
<legend>Items in this distribution</legend>
<div class="distribution-title">
<legend class="<%= 'with-request' if distribution.request %>">Items in this distribution</legend>
<% if distribution.request %>
<div class="distribution-request-unit">Requested</div>
<% end %>
</div>
<div id="distribution_line_items" data-capture-barcode="true" class="line-item-fields">
<%= render 'line_items/line_item_fields', form: f %>
<%= render 'line_items/line_item_fields', form: f, locals: { show_request_items: true } %>
</div>
<div class="row links justify-content-end">
<%= add_element_button "Add Another Item", container_selector: "#distribution_line_items" , id: "__add_line_item" do %>
<%= render 'line_items/line_item_fields', form: f, object: LineItem.new %>
<%= render 'line_items/line_item_fields', form: f, object: LineItem.new, locals: { show_request_items: true } %>
<% end %>
</div>

Expand Down
44 changes: 33 additions & 11 deletions app/views/line_items/_line_item_fields.html.erb
Original file line number Diff line number Diff line change
@@ -1,24 +1,46 @@
<%= form.simple_fields_for :line_items, defined?(object) ? object : nil do |field| %>
<% requested = field.object.requested_item %>
<section class="nested-fields line_item_section">
<div class="row mt-2 d-flex flex-row align-items-center justify-content-between">
<div class='d-flex flex-column justify-content-center'>
<div class='d-flex flex-row align-items-center'>
<%= render partial: "barcode_items/barcode_item_lookup",
locals: { index: field&.options[:child_index] || "new_item" } %>
<div id="barcode-scanner-btn" class="fa fa-barcode barcode-scanner mx-2"> </div>
</div>
<label class='my-1 mx-auto font-weight-normal'>OR</label>
<% if requested.blank? %>
<div class='d-flex flex-row align-items-center'>
<%= render partial: "barcode_items/barcode_item_lookup",
locals: { index: field&.options[:child_index] || "new_item" } %>
<div id="barcode-scanner-btn" class="fa fa-barcode barcode-scanner mx-2"> </div>
</div>
<label class='my-1 mx-auto font-weight-normal'>OR</label>
<% end %>
<div class='d-flex flex-row'>
<span class="li-name w-100">
<%= field.input :item_id, collection: @items, prompt: "Choose an item", include_blank: "", label: false, input_html: { class: "my-0 line_item_name", "data-controller": "select2" } %>
<%= field.input :item_id,
disabled: requested.present?,
collection: @items, prompt: "Choose an item",
include_blank: "",
label: false,
input_html: { class: "my-0 line_item_name", "data-controller": "select2" } %>
<% if requested.present? %>
<%= field.input :item_id, as: :hidden %>
<% end %>
</span>
<div class="li-quantity mx-2">
<%= field.input :quantity,
as: :string,
placeholder: "Quantity",
label: false,
input_html: { class: "quantity my-0", data: { quantity: "" } } %>
as: :string,
placeholder: "Quantity",
label: false,
input_html: { class: "quantity my-0", data: { quantity: "" } } %>
</div>
<% if form.object.respond_to?(:request) && form.object.request %>
<div class="li-requested mx-2">
<% if requested&.request_unit.present? %>
<%= pluralize(requested.quantity, requested.request_unit) %>
<% elsif requested %>
<%= requested.quantity %>
<% else %>
N/A
<% end %>
</div>
<% end %>
</div>
</div>

Expand Down
9 changes: 9 additions & 0 deletions spec/factories/items.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,14 @@
trait :inactive do
active { false }
end

trait :with_unit do
transient do
unit { "pack" }
end
after(:create) do |item, evaluator|
create(:item_unit, name: evaluator.unit, item: item)
end
end
end
end
1 change: 1 addition & 0 deletions spec/factories/requests.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ def random_request_items
request_items { random_request_items }
comments { "Urgent" }
partner_user { ::User.partner_users.first || create(:partner_user) }
item_requests { [] }

# For compatibility we can take in a list of request_items and turn it into a
# list of item_requests
Expand Down
8 changes: 3 additions & 5 deletions spec/models/distribution_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -250,11 +250,9 @@
item2 = create(:item, name: "Item2", organization: organization)
request = create(:request,
organization: organization,
partner_user: create(:partner_user),
request_items: [
{ item_id: item1.id, quantity: 15 },
{ item_id: item2.id, quantity: 18 }
])
partner_user: create(:partner_user))
create(:item_request, request: request, item_id: item1.id, quantity: 15)
create(:item_request, request: request, item_id: item2.id, quantity: 18)
distribution = Distribution.new
distribution.copy_from_request(request.id)
expect(distribution.line_items.size).to eq 2
Expand Down
122 changes: 121 additions & 1 deletion spec/requests/distributions_requests_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,19 @@

describe "GET #new" do
let!(:partner) { create(:partner, organization: organization) }
let(:request) { create(:request, partner: partner, organization: organization) }
let(:request) { create(:request, partner: partner, organization: organization, item_requests: item_requests) }
let(:items) {
[
create(:item, :with_unit, organization: organization, name: 'Item 1', unit: 'pack'),
create(:item, organization: organization, name: 'Item 2')
]
}
let(:item_requests) {
[
create(:item_request, item: items[0], quantity: 50, request_unit: 'pack'),
create(:item_request, item: items[1], quantity: 25)
]
}
let(:storage_location) { create(:storage_location, :with_items, organization: organization) }
let(:default_params) { { request_id: request.id } }

Expand Down Expand Up @@ -166,6 +178,44 @@
expect(page.css(%(#distribution_storage_location_id option[selected][value="#{storage_location.id}"]))).not_to be_empty
end
end

context 'with units' do
before(:each) do
Flipper.enable(:enable_packs)
end

it 'should behave correctly' do
get new_distribution_path(default_params)
expect(response).to be_successful
page = Nokogiri::HTML(response.body)

# should have a disabled select and a hidden input
expect(page.css('select[disabled][name="distribution[line_items_attributes][0][item_id]"]')).not_to be_empty
expect(page.css('input[name="distribution[line_items_attributes][0][item_id]"]')).not_to be_empty
expect(page.css('select[disabled][name="distribution[line_items_attributes][1][item_id]"]')).not_to be_empty
expect(page.css('input[name="distribution[line_items_attributes][1][item_id]"]')).not_to be_empty

# input with packs should be blank
expect(page.css('#distribution_line_items_attributes_0_quantity').attr('value')).to eq(nil)

# input with no packs should show quantity
expect(page.css('#distribution_line_items_attributes_1_quantity').attr('value').value).to eq('25')
end

context 'with no request' do
it 'should have no inputs' do
get new_distribution_path({})
expect(response).to be_successful
page = Nokogiri::HTML(response.body)

# blank input shown
expect(page.css('select[name="distribution[line_items_attributes][0][item_id]"]')).not_to be_empty
expect(page.css('#distribution_line_items_attributes_0_quantity').attr('value')).to eq(nil)
# in the template
expect(page.css('select[name="distribution[line_items_attributes][1][item_id]"]')).not_to be_empty
end
end
end
end

describe "GET #show" do
Expand Down Expand Up @@ -414,6 +464,76 @@
expect(response.body).not_to include("You’ve had an audit since this distribution was started.")
end

context 'with units' do
let!(:request) {
create(:request,
partner: partner,
organization: organization,
distribution_id: distribution.id,
item_requests: item_requests)
}
let(:items) {
[
create(:item, :with_unit, organization: organization, name: 'Item 1', unit: 'pack'),
create(:item, organization: organization, name: 'Item 2'),
create(:item, organization: organization, name: 'Item 3')
]
}
let!(:item_requests) {
[
create(:item_request, item: items[0], quantity: 50, request_unit: 'pack'),
create(:item_request, item: items[1], quantity: 25)
]
}
before(:each) do
Flipper.enable(:enable_packs)
create(:line_item, itemizable: distribution, item_id: items[0].id, quantity: 25)
create(:line_item, itemizable: distribution, item_id: items[2].id, quantity: 10)
end

it 'should behave correctly' do
get edit_distribution_path(id: distribution.id)
expect(response).to be_successful
page = Nokogiri::HTML(response.body)

# should have a regular select and no hidden input
expect(page.css('select[disabled][name="distribution[line_items_attributes][0][item_id]"]')).not_to be_empty
expect(page.css('input[name="distribution[line_items_attributes][0][item_id]"]')).not_to be_empty

# should have a regular select and no hidden input
expect(page.css('select[name="distribution[line_items_attributes][1][item_id]"]')).not_to be_empty
expect(page.css('select[disabled][name="distribution[line_items_attributes][1][item_id]"]')).to be_empty
expect(page.css('input[name="distribution[line_items_attributes][1][item_id]"]')).to be_empty

# should have a disabled select and a hidden input
expect(page.css('select[disabled][name="distribution[line_items_attributes][2][item_id]"]')).not_to be_empty
expect(page.css('input[name="distribution[line_items_attributes][2][item_id]"]')).not_to be_empty

# existing inputs should show numbers
expect(page.css('#distribution_line_items_attributes_0_quantity').attr('value').value).to eq('25')
expect(page.css('#distribution_line_items_attributes_1_quantity').attr('value').value).to eq('10')

# input from request should show 0
expect(page.css('#distribution_line_items_attributes_2_quantity').attr('value').value).to eq('0')
end

context 'with no request' do
it 'should have everything enabled' do
request.destroy
get edit_distribution_path(id: distribution.id)
expect(response).to be_successful
page = Nokogiri::HTML(response.body)

expect(page.css('select[name="distribution[line_items_attributes][0][item_id]"]')).not_to be_empty
expect(page.css('select[disabled][name="distribution[line_items_attributes][0][item_id]"]')).to be_empty
expect(page.css('input[name="distribution[line_items_attributes][0][item_id]"]')).to be_empty
expect(page.css('select[name="distribution[line_items_attributes][1][item_id]"]')).not_to be_empty
expect(page.css('select[disabled][name="distribution[line_items_attributes][1][item_id]"]')).to be_empty
expect(page.css('input[name="distribution[line_items_attributes][1][item_id]"]')).to be_empty
end
end
end

# Bug fix #4537
context "when distribution sets storage location total inventory to zero" do
let(:item1) { create(:item, name: "Item 1", organization: organization) }
Expand Down
Loading

0 comments on commit f47a634

Please sign in to comment.