diff --git a/app/views/events/_event_row.html.erb b/app/views/events/_event_row.html.erb
index 3ef1e11386..6436b0174b 100644
--- a/app/views/events/_event_row.html.erb
+++ b/app/views/events/_event_row.html.erb
@@ -1,8 +1,23 @@
<% if event.is_a?(SnapshotEvent) %>
<%= render partial: "snapshot_event_row", object: event, as: :event, locals: { items: items, storage_locs: storage_locs } %>
<% else %>
- <% from_loc = event.data.items.first&.from_storage_location %>
- <% to_loc = event.data.items.first&.to_storage_location %>
+ <%
+ # KitAllocateEvent/KitDeallocateEvent mix directions within a single event: the
+ # kit's own line item moves opposite the component items, so we can't rely on
+ # picking any one item's from/to. Every item shares the same storage location
+ # (just recorded in the opposite field for the kit vs. its components), so pull
+ # that location from whichever entry has it and place it by event type instead.
+ is_kit_event = event.is_a?(KitAllocateEvent) || event.is_a?(KitDeallocateEvent)
+ if is_kit_event
+ first_item = event.data.items.first
+ kit_storage_loc = first_item&.from_storage_location || first_item&.to_storage_location
+ from_loc = event.is_a?(KitDeallocateEvent) ? kit_storage_loc : nil
+ to_loc = event.is_a?(KitAllocateEvent) ? kit_storage_loc : nil
+ else
+ from_loc = event.data.items.first&.from_storage_location
+ to_loc = event.data.items.first&.to_storage_location
+ end
+ %>
<%= event.user&.name || "No Name Provided" %>
@@ -34,12 +49,12 @@
end %>
<% sorted_entries.each do |entry| %>
<% item = items.find { |i| i.id == entry.item_id } %>
+ <% quantity = entry.from_storage_location == from_loc ? entry.quantity : -entry.quantity %>
<% if item %>
- <%= link_to item.name, item_path(item.id) %>:
+ <%= link_to item.name, item_path(item.id) %>: <%= quantity %>
<% else %>
- Item <%= entry.item_id %> (deleted)
+ Item <%= entry.item_id %> (deleted): <%= quantity %>
<% end %>
- <%= entry.from_storage_location == from_loc ? entry.quantity : -entry.quantity %>
<% end %>
|
diff --git a/spec/requests/events_requests_spec.rb b/spec/requests/events_requests_spec.rb
index 0125fb03f3..729aa54e9f 100644
--- a/spec/requests/events_requests_spec.rb
+++ b/spec/requests/events_requests_spec.rb
@@ -263,6 +263,112 @@
expect(response.body).not_to include("99
")
end
end
+
+ context "with kit allocation and deallocation events" do
+ let(:component_item) { create(:item, organization: organization, name: "Widget") }
+ let(:kit) do
+ create_kit(organization: organization, line_items_attributes: [
+ {item_id: component_item.id, quantity: 5}
+ ])
+ end
+
+ def row_for(event_type)
+ Nokogiri::HTML(response.body).css("tbody tr").find do |row|
+ row.css("td")[1]&.text&.strip == event_type
+ end
+ end
+
+ before do
+ TestInventory.create_inventory(organization, {
+ storage_location.id => {component_item.id => 100}
+ })
+ KitAllocateEvent.publish(kit, storage_location.id, 3)
+ KitDeallocateEvent.publish(kit, storage_location.id, 1)
+ end
+
+ it "shows a positive kit quantity to the storage location and a negative item quantity for KitAllocate" do
+ subject
+ row = row_for("KitAllocate")
+ expect(row).not_to be_nil
+ cells = row.css("td")
+ expect(cells[4].text).not_to include(storage_location.name)
+ expect(cells[5].text).to include(storage_location.name)
+ expect(cells[6].text).to include("#{kit.name}: 3")
+ expect(cells[6].text).to include("Widget: -15")
+ end
+
+ it "shows a positive kit quantity from the storage location and a negative item quantity for KitDeallocate" do
+ subject
+ row = row_for("KitDeallocate")
+ expect(row).not_to be_nil
+ cells = row.css("td")
+ expect(cells[4].text).to include(storage_location.name)
+ expect(cells[5].text).not_to include(storage_location.name)
+ expect(cells[6].text).to include("#{kit.name}: 1")
+ expect(cells[6].text).to include("Widget: -5")
+ end
+ end
+
+ context "with kit events whose line items are not in the usual order" do
+ # KitAllocateEvent/KitDeallocateEvent normally append the kit's own line item
+ # last, but the view must not depend on that ordering to work out direction.
+ let(:component_item) { create(:item, organization: organization, name: "Widget") }
+ let(:kit) do
+ create_kit(organization: organization, line_items_attributes: [
+ {item_id: component_item.id, quantity: 5}
+ ])
+ end
+
+ def row_for(event_type)
+ Nokogiri::HTML(response.body).css("tbody tr").find do |row|
+ row.css("td")[1]&.text&.strip == event_type
+ end
+ end
+
+ before do
+ TestInventory.create_inventory(organization, {
+ storage_location.id => {component_item.id => 100, kit.kit_item.id => 100}
+ })
+ end
+
+ it "shows the correct direction for KitAllocate with the kit's line item first" do
+ KitAllocateEvent.create!(
+ eventable: kit,
+ organization_id: organization.id,
+ event_time: Time.zone.now,
+ data: EventTypes::InventoryPayload.new(
+ items: KitAllocateEvent.event_line_items(kit, storage_location.id, 2).reverse
+ )
+ )
+ subject
+ row = row_for("KitAllocate")
+ expect(row).not_to be_nil
+ cells = row.css("td")
+ expect(cells[4].text).not_to include(storage_location.name)
+ expect(cells[5].text).to include(storage_location.name)
+ expect(cells[6].text).to include("#{kit.name}: 2")
+ expect(cells[6].text).to include("Widget: -10")
+ end
+
+ it "shows the correct direction for KitDeallocate with the kit's line item first" do
+ KitDeallocateEvent.create!(
+ eventable: kit,
+ organization_id: organization.id,
+ event_time: Time.zone.now,
+ data: EventTypes::InventoryPayload.new(
+ items: KitDeallocateEvent.event_line_items(kit, storage_location.id, 4).reverse
+ )
+ )
+ subject
+ row = row_for("KitDeallocate")
+ expect(row).not_to be_nil
+ cells = row.css("td")
+ expect(cells[4].text).to include(storage_location.name)
+ expect(cells[5].text).not_to include(storage_location.name)
+ expect(cells[6].text).to include("#{kit.name}: 4")
+ expect(cells[6].text).to include("Widget: -20")
+ end
+ end
end
end
end