Skip to content

Commit 30575b1

Browse files
authored
Ensure unique units are used when creating a request [#4579] (#4835)
* Ensure unique units are used when creating a request [#4579] * Clean out some lint [#4579] * Update spec wording expectation [#4579] * Handle items out of order intermittent [#4579] * Unify call and initialize_only to use same logic [#4579] * Fix create service to make in-memory request even if invalid Errors are then reported up to the UI but not saved to the database; this is what the controller and rails forms expects. [#4579] * Fix re-editing of request units in dynamic JS [#4579] * Sate Rubocop! [#4579] * Update family and individual request validation to match create [#4579] * Only include request_unit on request_items when specified [#4579] * Make request controllers match for general and specific types [#4579]
1 parent 016e957 commit 30575b1

File tree

10 files changed

+138
-59
lines changed

10 files changed

+138
-59
lines changed

app/controllers/partners/family_requests_controller.rb

+4-2
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,14 @@ def create
3434
def validate
3535
family_requests_attributes = build_family_requests_attributes(params)
3636

37-
@partner_request = Partners::FamilyRequestCreateService.new(
37+
create_service = Partners::FamilyRequestCreateService.new(
3838
partner_user_id: current_user.id,
3939
family_requests_attributes: family_requests_attributes,
4040
request_type: "child"
4141
).initialize_only
42-
if @partner_request.valid?
42+
43+
if create_service.errors.none?
44+
@partner_request = create_service.partner_request
4345
@total_items = @partner_request.total_items
4446
@quota_exceeded = current_partner.quota_exceeded?(@total_items)
4547
body = render_to_string(template: 'partners/requests/validate', formats: [:html], layout: false)

app/controllers/partners/individuals_requests_controller.rb

+4-2
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,15 @@ def create
3434
end
3535

3636
def validate
37-
@partner_request = Partners::FamilyRequestCreateService.new(
37+
create_service = Partners::FamilyRequestCreateService.new(
3838
partner_user_id: current_user.id,
3939
comments: individuals_request_params[:comments],
4040
family_requests_attributes: individuals_request_params[:items_attributes]&.values,
4141
request_type: "individual"
4242
).initialize_only
43-
if @partner_request.valid?
43+
44+
if create_service.errors.none?
45+
@partner_request = create_service.partner_request
4446
@total_items = @partner_request.total_items
4547
@quota_exceeded = current_partner.quota_exceeded?(@total_items)
4648
body = render_to_string(template: 'partners/requests/validate', formats: [:html], layout: false)

app/controllers/partners/requests_controller.rb

+3-2
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,15 @@ def create
4343
end
4444

4545
def validate
46-
@partner_request = Partners::RequestCreateService.new(
46+
create_service = Partners::RequestCreateService.new(
4747
request_type: "quantity",
4848
partner_user_id: current_user.id,
4949
comments: partner_request_params[:comments],
5050
item_requests_attributes: partner_request_params[:item_requests_attributes]&.values || []
5151
).initialize_only
5252

53-
if @partner_request.valid?
53+
if create_service.errors.none?
54+
@partner_request = create_service.partner_request
5455
@total_items = @partner_request.total_items
5556
@quota_exceeded = current_partner.quota_exceeded?(@total_items)
5657
body = render_to_string(template: 'partners/requests/validate', formats: [:html], layout: false)

app/javascript/controllers/item_units_controller.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,17 @@ export default class extends Controller {
55
static targets = ["itemSelect", "requestSelect"]
66
static values = {
77
// hash of (item ID => hash of (request unit name => request unit plural name))
8-
"itemUnits": Object
8+
"itemUnits": Object,
9+
"selectedItemUnits": String
910
}
1011

1112
addOption(val, text) {
1213
let option = document.createElement("option");
1314
option.value = val;
1415
option.text = text;
16+
if (this.selectedItemUnitsValue === val) {
17+
option.selected = true;
18+
}
1519
this.requestSelectTarget.appendChild(option);
1620
}
1721

app/services/partners/request_create_service.rb

+46-46
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,7 @@ def initialize(request_type:, partner_user_id:, comments: nil, item_requests_att
1313
end
1414

1515
def call
16-
@partner_request = ::Request.new(
17-
partner_id: partner.id,
18-
organization_id: organization_id,
19-
comments: comments,
20-
request_type: request_type,
21-
partner_user_id: partner_user_id
22-
)
23-
@partner_request = populate_item_request(@partner_request)
24-
@partner_request.assign_attributes(additional_attrs)
25-
26-
unless @partner_request.valid?
27-
@partner_request.errors.each do |error|
28-
errors.add(error.attribute, error.message)
29-
end
30-
end
31-
16+
initialize_only
3217
return self if errors.present?
3318

3419
Request.transaction do
@@ -44,14 +29,23 @@ def call
4429
end
4530

4631
def initialize_only
47-
partner_request = ::Request.new(partner_id: partner.id,
32+
@partner_request = ::Request.new(
33+
partner_id: partner.id,
4834
organization_id: organization_id,
4935
comments: comments,
5036
request_type: request_type,
51-
partner_user_id: partner_user_id)
52-
partner_request = populate_item_request(partner_request)
53-
partner_request.assign_attributes(additional_attrs)
54-
partner_request
37+
partner_user_id: partner_user_id
38+
)
39+
@partner_request = populate_item_request(partner_request)
40+
@partner_request.assign_attributes(additional_attrs)
41+
42+
unless @partner_request.valid?
43+
@partner_request.errors.each do |error|
44+
errors.add(error.attribute, error.message)
45+
end
46+
end
47+
48+
self
5549
end
5650

5751
private
@@ -68,42 +62,48 @@ def populate_item_request(partner_request)
6862

6963
formatted_line_items.each do |input_item|
7064
pre_existing_entry = items[input_item['item_id']]
65+
7166
if pre_existing_entry
72-
pre_existing_entry.quantity = (pre_existing_entry.quantity.to_i + input_item['quantity'].to_i).to_s
73-
# NOTE: When this code was written (and maybe it's still the
74-
# case as you read it!), the FamilyRequestsController does a
75-
# ton of calculation to translate children to item quantities.
76-
# If that logic is incorrect, there's not much we can do here
77-
# to fix things. Could make sense to move more of that logic
78-
# into one of the service objects that instantiate the Request
79-
# object (either this one or the FamilyRequestCreateService).
80-
pre_existing_entry.children = (pre_existing_entry.children + (input_item['children'] || [])).uniq
81-
else
82-
if input_item['request_unit'].to_s == '-1' # nothing selected
83-
errors.add(:base, "Please select a unit for #{Item.find(input_item["item_id"]).name}")
67+
if pre_existing_entry.request_unit != input_item['request_unit']
68+
errors.add(:base, "Please use the same unit for every #{Item.find(input_item["item_id"]).name}")
69+
else
70+
pre_existing_entry.quantity = (pre_existing_entry.quantity.to_i + input_item['quantity'].to_i).to_s
71+
# NOTE: When this code was written (and maybe it's still the
72+
# case as you read it!), the FamilyRequestsController does a
73+
# ton of calculation to translate children to item quantities.
74+
# If that logic is incorrect, there's not much we can do here
75+
# to fix things. Could make sense to move more of that logic
76+
# into one of the service objects that instantiate the Request
77+
# object (either this one or the FamilyRequestCreateService).
78+
pre_existing_entry.children = (pre_existing_entry.children + (input_item['children'] || [])).uniq
8479
next
8580
end
86-
items[input_item['item_id']] = Partners::ItemRequest.new(
87-
item_id: input_item['item_id'],
88-
request_unit: input_item['request_unit'],
89-
quantity: input_item['quantity'],
90-
children: input_item['children'] || [], # will create ChildItemRequests if there are any
91-
name: fetch_organization_item_name(input_item['item_id']),
92-
partner_key: fetch_organization_partner_key(input_item['item_id'])
93-
)
9481
end
95-
end
9682

97-
item_requests = items.values
83+
if input_item['request_unit'].to_s == '-1' # nothing selected
84+
errors.add(:base, "Please select a unit for #{Item.find(input_item["item_id"]).name}")
85+
end
9886

99-
partner_request.item_requests << item_requests
87+
item_request = Partners::ItemRequest.new(
88+
item_id: input_item['item_id'],
89+
request_unit: input_item['request_unit'],
90+
quantity: input_item['quantity'],
91+
children: input_item['children'] || [], # will create ChildItemRequests if there are any
92+
name: fetch_organization_item_name(input_item['item_id']),
93+
partner_key: fetch_organization_partner_key(input_item['item_id'])
94+
)
95+
partner_request.item_requests << item_request
96+
items[input_item['item_id']] = item_request
97+
end
10098

10199
partner_request.request_items = partner_request.item_requests.map do |ir|
102100
{
103101
item_id: ir.item_id,
104-
quantity: ir.quantity
105-
}
102+
quantity: ir.quantity,
103+
request_unit: ir.request_unit
104+
}.compact
106105
end
106+
107107
partner_request
108108
end
109109

app/views/partners/requests/_error.html.erb

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
<p class='text-lg font-bold'>
1010
Ensure each line item has a item selected AND a quantity greater than 0.
1111
<% if Flipper.enabled?(:enable_packs) && current_partner.organization.request_units.any? %>
12-
Please ensure a unit is selected for each item that supports it.
12+
Please ensure a single unit is selected for each item that supports it.
1313
<% end %>
1414
</p>
1515
<p class='text-md'>

app/views/partners/requests/_item_request.html.erb

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<%= form.fields_for :item_requests, defined?(object) ? object : nil do |field| %>
2-
<tr data-controller="item-units" data-item-units-item-units-value="<%= item_units.to_json %>">
2+
<tr data-controller="item-units" data-item-units-item-units-value="<%= item_units.to_json %>" data-item-units-selected-item-units-value="<%= field.object.request_unit || -1 %>">
33
<td>
44
<%= field.label :item_id, "Item Requested", {class: 'sr-only'} %>
55
<%= field.select :item_id, @requestable_items, {include_blank: 'Select an item'},

spec/requests/partners/requests_spec.rb

+68
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@
124124
describe "POST #create" do
125125
subject { post partners_requests_path, params: request_attributes }
126126
let(:item1) { create(:item, name: "First item", organization: organization) }
127+
let(:item2) { create(:item, name: "Second item", organization: organization) }
127128

128129
let(:request_attributes) do
129130
{
@@ -142,8 +143,15 @@
142143

143144
before do
144145
sign_in(partner_user)
146+
147+
# Set up a variety of units that these two items are allowed to have
145148
FactoryBot.create(:unit, organization: organization, name: 'pack')
149+
FactoryBot.create(:unit, organization: organization, name: 'box')
150+
FactoryBot.create(:unit, organization: organization, name: 'notallowed')
146151
FactoryBot.create(:item_unit, item: item1, name: 'pack')
152+
FactoryBot.create(:item_unit, item: item1, name: 'box')
153+
FactoryBot.create(:item_unit, item: item2, name: 'pack')
154+
FactoryBot.create(:item_unit, item: item2, name: 'box')
147155
end
148156

149157
context 'when given valid parameters' do
@@ -185,6 +193,66 @@
185193
end
186194
end
187195

196+
context "when there are mixed units" do
197+
context "on different items" do
198+
let(:request_attributes) do
199+
{
200+
request: {
201+
comments: Faker::Lorem.paragraph,
202+
item_requests_attributes: {
203+
"0" => {
204+
item_id: item1.id,
205+
request_unit: 'pack',
206+
quantity: 12
207+
},
208+
"1" => {
209+
item_id: item2.id,
210+
request_unit: 'box',
211+
quantity: 17
212+
}
213+
}
214+
}
215+
}
216+
end
217+
218+
it "creates without error" do
219+
Flipper.enable(:enable_packs)
220+
expect { subject }.to change { Request.count }.by(1)
221+
expect(response).to redirect_to(partners_request_path(Request.last.id))
222+
expect(response.request.flash[:success]).to eql "Request was successfully created."
223+
end
224+
end
225+
226+
context "on the same item" do
227+
let(:request_attributes) do
228+
{
229+
request: {
230+
comments: Faker::Lorem.paragraph,
231+
item_requests_attributes: {
232+
"0" => {
233+
item_id: item1.id,
234+
request_unit: 'pack',
235+
quantity: 12
236+
},
237+
"1" => {
238+
item_id: item1.id,
239+
request_unit: 'box',
240+
quantity: 17
241+
}
242+
}
243+
}
244+
}
245+
end
246+
247+
it "results in an error" do
248+
Flipper.enable(:enable_packs)
249+
expect { post partners_requests_path, params: request_attributes }.to_not change { Request.count }
250+
expect(response).to be_unprocessable
251+
expect(response.body).to include("Please ensure a single unit is selected for each item")
252+
end
253+
end
254+
end
255+
188256
context "when a request empty" do
189257
let(:request_attributes) do
190258
{

spec/services/partners/request_create_service_spec.rb

+5-3
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@
178178

179179
describe "#initialize_only" do
180180
subject { described_class.new(**args).initialize_only }
181+
181182
let(:args) do
182183
{
183184
partner_user_id: partner_user.id,
@@ -201,9 +202,10 @@
201202
end
202203

203204
it "creates a partner request in memory only" do
204-
expect(subject.id).to be_nil
205-
expect(subject.item_requests.first.item.name).to eq(item.name)
206-
expect(subject.item_requests.first.quantity).to eq("25")
205+
expect { subject }.not_to change { Request.count }
206+
expect(subject.partner_request.id).to be_nil
207+
expect(subject.partner_request.item_requests.first.item.name).to eq(item.name)
208+
expect(subject.partner_request.item_requests.first.quantity).to eq("25")
207209
end
208210
end
209211
end

spec/system/partners/requests_system_spec.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
fill_in "request_item_requests_attributes_0_quantity", with: 50
4444
click_on "Submit Essentials Request"
4545

46-
expect(page).to have_text "Please ensure a unit is selected for each item that supports it."
46+
expect(page).to have_text "Please ensure a single unit is selected for each item that supports it."
4747
expect(Request.count).to eq(0)
4848
end
4949

0 commit comments

Comments
 (0)