-
-
Notifications
You must be signed in to change notification settings - Fork 725
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 When user deletes a shipping method existing orders are updated with a random shipping method #10135
Conversation
2068e4d
to
6f1f3fb
Compare
6f1f3fb
to
513d3fa
Compare
3386876
to
4e82a34
Compare
The two tests fail because the selected shipment method is expected to be removed.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hello @abdellani and a Happy New Year! 💐
I have a question about the existing code: What is the purpose of #refresh_rates
? From a quick glance, it checks if the existing shipping method is still available and assigns a new one if not. But now we want to preserve the existing method even if it has been deleted. So is there any other functionality we have to preserve? Otherwise I'm wondering if we can get rid of the whole thing.
I probably didn't consider the case of multiple rates per shipping method and that the quantity of items could change. Then we need to refresh the rate, guess. I'm not familiar with this code at the moment.
In those cases, when a better rate is available, would your addition prevent the update? Or do we have only one rate per method?
If the solution is validated, I'll fix those tests.
Ideally, we would change specs before fixing the code. I know that sometimes we don't know which specs are affected and can change them only afterwards but it would be good to verify your solution with specs before review.
app/models/spree/shipment.rb
Outdated
new_shapping_rates = OrderManagement::Stock::Estimator.new(order).shipping_rates(to_package) | ||
|
||
if selected_shipping_rate.present? && new_shapping_rates.none?{ |shipping_rate| | ||
shipping_rate.shipping_method_id == selected_shipping_rate.shipping_method_id | ||
} | ||
new_shapping_rates << selected_shipping_rate | ||
end | ||
self.shipping_rates = new_shapping_rates |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This part could go into its own method new_shipping_rates
which makes sure that the current rate is in there.
app/models/spree/shipment.rb
Outdated
if selected_shipping_rate.present? && new_shapping_rates.none?{ |shipping_rate| | ||
shipping_rate.shipping_method_id == selected_shipping_rate.shipping_method_id | ||
} | ||
new_shapping_rates << selected_shipping_rate | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find this code difficult to read. You are adding the selected rate if it's not in the collection already, right? The added block within the if
is confusing, I think.
Isn't this the same as (new_shapping_rates + [selected_shipping_rate]).uniq
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the suggested syntax, but new_shapping_rates
will always contain new objects.
how about?
new_shapping_rates.map(&:shipping_method_id).exclude?(selected_shipping_rate.shipping_method_id)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that's easier to read.
@@ -246,6 +246,7 @@ | |||
|
|||
before do | |||
order.shipments.first.shipping_methods = [shipping_method1, shipping_method2] | |||
order.shipments.each(&:refresh_rates) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The commit message doesn't explain why we need to refresh the rates here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think now, we don't need to use
order.shipments.first.shipping_methods = [shipping_method1, shipping_method2]
because this will create shipping rates with cost = 0.
Instead, we can use
order.shipments.each(&:refresh_rates)
refresh_rates
will automatically load the shipping methods from the distributor and calculate the cost before creating the shipping_rates
.
openfoodnetwork/app/models/spree/shipment.rb
Lines 117 to 121 in f122f4b
# The call to Stock::Estimator below will replace the current shipping_method | |
original_shipping_method_id = shipping_method.try(:id) | |
self.shipping_rates = OrderManagement::Stock::Estimator.new(order).shipping_rates(to_package) | |
keep_original_shipping_method_selection(original_shipping_method_id) |
openfoodnetwork/engines/order_management/app/services/order_management/stock/estimator.rb
Lines 18 to 21 in f122f4b
shipping_methods.each do |shipping_method| | |
cost = calculate_cost(shipping_method, package) | |
shipping_rates << shipping_method.shipping_rates.new(cost: cost) unless cost.nil? | |
end |
Happy New Year @mkllnk :)
In my understanding:
Suppose that the distributor offers two shipping methods "sm1" and "sm2", The problem comes when
So, we want to preserve the existing shipping method in the order only if this method is selected. If it's not selected, it's ok to lose it.
Now that I think of it, maybe under some conditions, my solution will create a problem. openfoodnetwork/engines/order_management/app/services/order_management/stock/estimator.rb Lines 60 to 62 in f122f4b
|
Ah, thank you. I read a bit more and the existing code is really hard to read. One complicating design fault is the Anyway, I think that your idea is really good. When the selected shipping rate is not available any more, we want to keep it. But that needs to apply only for a selected rate. And the user still needs to be able to select another one. Maybe check if there are test cases for this already or set some up. Then it's easier to check if the code is doing what we think it does. |
4e82a34
to
11c0481
Compare
@mkllnk |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I implemented a system test that reproduces the same context described in the issue.
Yes very good. I was wondering though if we need to also cover other cases which are not covered yet.
For example, if we change the amount of a shipping method then the new amount will be applied, correct? Do we want that? Shouldn't the original rate be kept in all cases?
I'm wondering if it would be useful to override the eql?
method here.
app/models/spree/shipment.rb
Outdated
if selected_shipping_rate.present? && new_shapping_rates.none?{ |shipping_rate| | ||
shipping_rate.shipping_method_id == selected_shipping_rate.shipping_method_id | ||
} | ||
new_shapping_rates << selected_shipping_rate | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that's easier to read.
11c0481
to
b09ab89
Compare
If we want to recalculate the shipment price, we can reuse the estimator. estimator = OrderManagement::Stock::Estimator.new(order)
selected_shipping_rate.update(cost: estimator.calculate_cost(selected_shipping_rate, to_package) |
b09ab89
to
729c4e2
Compare
So, we have three parameters to take into account: Based on those, I suggest testing the following cases:
|
729c4e2
to
fad7fe3
Compare
@filipefurtad0 At this level, I'm stuck with the |
fad7fe3
to
7d61638
Compare
@@ -420,7 +420,7 @@ def new_order_with_distribution(distributor, order_cycle) | |||
|
|||
describe "viewing the edit page" do | |||
let!(:shipping_method_for_distributor1) do | |||
create(:shipping_method, name: "Normal", distributors: [distributor1]) | |||
create(:shipping_method_with, :flat_rate, name: "Normal", amount: 12, distributors: [distributor1]) | |||
end | |||
let!(:order) do | |||
create(:order_with_taxes, distributor: distributor1, ship_address: create(:address), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the issue is that, creating an order_with_taxes
(which has completed_order_with_totals
as parent) will create a shipment for it too. So, later on, when... (continuing below)
I debugged the test: openfoodnetwork/app/models/concerns/order_shipment.rb Lines 39 to 49 in 1ea7304
Now, the shipping method will be updated, as expected, but there's one thing that I don't understand. The cost will not be updated! fee_adjustment.amount = selected_shipping_rate.cost if fee_adjustment.open? Why do we authorize updating the shipping methods, and check if the |
7d61638
to
7636adf
Compare
I fixed the new test by calling 'open' and 'close' on the shipment adjustment. |
I think this might be legacy code, related to a functionality which used to be displayed in the UI, see this comment for reference: Please note that this functionality was removed - notice the radio button It was removed on this PR.
Just to be sure, when you mean
What is the state of shipment, in this case? There might be a good reason for the code to be designed in this way. I can imagine this is relevant, when an order is shipped ( |
Yes
Do you mean I can remove this condition
The shipment state is pending, but the problem is related to the state of the adjustment. |
44490dd
to
c0051cd
Compare
@filipefurtad0 |
Hey @abdellani ,
The failure seems consistent, but it does not seem related to this PR. If I pull this branch and run the spec locally, it passes. The test should look like this: But for some reason, in the CI, we're getting: I'll try to debug this. It might be an order related failure. But it's a blocker for merging I guess... My debugging strategy (please feel free to comment):
|
@filipefurtad0 Failure/Error: expect(page).to have_content(/#{order5.number}.*#{order4.number}.*#{order3.number}.*#{order2.number}/m)
expected to find text matching /R445887202.*R518028774.*R884221646.*R424560362/m in "Logged in as : [email protected] Account Logout Store DASHBOARD PRODUCTS ORDER CYCLES ORDERS REPORTS CONFIGURATION ENTERPRISES CUSTOMERS GROUPS USERS ORDERS BULK ORDER MANAGEMENT SUBSCRIPTIONS Listing Orders NEW ORDER SEARCH DATE RANGE STATUS cart address delivery payment confirmation complete cancelled awaiting return returned resumed INVOICE NUMBER: EMAIL FIRST NAME BEGINS WITH LAST NAME BEGINS WITH ONLY SHOW COMPLETE ORDERS SHIPPING METHOD UPS Ground UPS Ground Pick-up at the farm UPS Ground Signed, sealed, delivered UPS Ground UPS Ground UPS Ground UPS Ground DISTRIBUTORS Enterprise 99 Enterprise 100 Enterprise 101 Enterprise 98 Enterprise 102 ORDER CYCLES Four Three Two Five FILTER RESULTS CLEAR FILTERS No order selected ACTIONS 15 per page 4 Results found. Viewing 1 to 4. DISTRIBUTOR COMPLETED AT NUMBER STATE PAYMENT STATE SHIPMENT STATE EMAIL NAME TOTAL ▲ Enterprise 100 February 15, 2023 R884221646 COMPLETE CREDIT OWED ($-10,000.00) READY [email protected] Winston Mayer $50.00 Enterprise 102 February 15, 2023 R445887202 COMPLETE PAID READY [email protected] Ashlee Rolfson $50.00 Enterprise 101 February 15, 2023 R518028774 COMPLETE CREDIT OWED ($-9,980.00) READY [email protected] Meaghan Wilkinson $70.00 Enterprise 99 February 13, 2023 R424560362 COMPLETE BALANCE DUE ($40.00) PENDING [email protected] Fermin Morar $90.00 « FIRST PREVIOUS 1 NEXT LAST »" order2.number,order3.number,order4.number,order5.number are on the page content but in a different order. |
Sure, I think it is ok to change the spec according to the new sorting. I'm just wondering whether it is a consistent behavior as I cannot make the spec fail in my local env. It seems to only fail on the CI, so I was tipping on order-dependent failure. But I'm not sure, bisecting the specs did not help, as they always pass (for me). So I don't have a clear answer, I don't know what's causing this failure - I mean, I don't know why the order totals changes, if this PR does not change this spec. |
175d2eb
to
e8c0689
Compare
Everything is green Yay !! 🎉 I think that the problem is fixed now. |
Moved back to In Dev because changes were requested by Filipe. I will review after those have been addressed. 😄 |
da26ccf
to
28e98b4
Compare
…d if distributor no longer support it
Co-authored-by: Filipe <[email protected]>
28e98b4
to
0738133
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good from the specs, as far as I could gather. I think in the manual testing phase we need a good look at states, and to verify if we keep a consistent behavior when the admin wishes to change the shipping method of a placed (complete, but not shipped) order.
Thanks for all you work here @abdellani!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great. This is looking good. 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have to admit I'm not familiar with this part of the system so it's hard to comment.
At a high level, I would have hoped to simply relax a condition somewhere to allow a shipping method to be used, despite being removed from distributors_shipping_methods
. Maybe that's what you're doing here.
So looking at this implementation, it's calling a private method which I think is a bad idea. Can you re-arrange it?
app/models/spree/shipment.rb
Outdated
if original_shipping_method_id.present? && | ||
distributor_shipping_rates.map(&:shipping_method_id) | ||
.exclude?(original_shipping_method_id) | ||
cost = estimator.__send__(:calculate_cost, shipping_method, to_package) | ||
unless cost.nil? | ||
original_shipping_rate = shipping_method.shipping_rates.new(cost: cost) | ||
self.shipping_rates = distributor_shipping_rates + [original_shipping_rate] | ||
self.selected_shipping_rate_id = original_shipping_rate.id | ||
end | ||
else | ||
self.shipping_rates = distributor_shipping_rates | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I noticed a call to a private method with __send__
. This was a hint for me: I think the purpose of this code is to re-implement OrderManagement::Stock::Estimator
in a specific way.
I'm wondering if the Estimator class should be updated. Would this implementation be safe to apply directly in Estimator? I see that it is called from perhaps one other place: Order#create_proposed_shipments
(though I didn't search extensively).
Otherwise, perhaps this should be an alternate method next to Estimator#shipping_rates
. Or at the very least, calculate_cost
could be promoted to a public method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't want to copy-and-paste the code from the estimator to avoid potential issues in case the logic is updated.
I'll promote calculate_cost
to a public method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, I'm still concerned that this code belongs in Estimator
, but let's go ahead like this for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the Estimator
gets the list of the shipping methods from the Package
.
Based on the returned list, the Estimator
will create the shipping rates
The Package
retrieves the list of the shipping methods from the order's distributor.
openfoodnetwork/engines/order_management/app/services/order_management/stock/package.rb
Lines 81 to 85 in c3fe399
def shipping_methods | |
return [] unless order.distributor.present? | |
order.distributor.shipping_methods.uniq.to_a | |
end |
I agree that the Estimator
is a better place to fix the issue.
we can add an extra parameter like keep_original_shipping_rate = false
openfoodnetwork/engines/order_management/app/services/order_management/stock/estimator.rb
Lines 13 to 30 in c3fe399
def shipping_rates(package, frontend_only = true) | |
shipping_rates = [] | |
shipping_methods = shipping_methods(package) | |
return [] unless shipping_methods | |
shipping_methods.each do |shipping_method| | |
cost = calculate_cost(shipping_method, package) | |
shipping_rates << shipping_method.shipping_rates.new(cost: cost) unless cost.nil? | |
end | |
shipping_rates.sort_by! { |r| r.cost || 0 } | |
unless shipping_rates.empty? || order.manual_shipping_selection | |
select_first_shipping_method(shipping_rates, frontend_only) | |
end | |
shipping_rates | |
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess we should have moved back to 'In Dev' while under discussion ;)
I think this is still a good idea. Would you be confident to try it in a new PR?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll do that in a different PR :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I've missed these new messages - went ahead, tested and merged 😬
Hey @abdellani Starting from an order with a given shipping method with a fee: Then, deactivating the shipping method: Is displayed on the reports: So in summary, deactivating a shipping method:
This looks great to me. We should still find out though if we can still edit orders, change shipping methods and update fees (regression testing). I found this to be very robust -> because we now keep it after deleting, let's say we wish to remove that shipping method from an order: After selecting and updating fees, we can see that the amounts are correct: And that the correct shipping fee appears on the invoice - 10 EUR instead of 20 EUR: I can't think of anything else to test, so... |
What? Why?
Summary
The original issue:
sm1
sm1
source of the problem:
Spree::Shipment#refresh_rates
Fix:
we'll keep the selected shipping method when
refresh_rates
is called.Potential issues (not sure if we have to solve it on this PR):
If the distributor hits
Update & Recalculate Fees
When I tested it, it was not replaced.
When I tested it, it was not recalculated.
What should we test?
Release notes
Changelog Category: Technical changes
Dependencies
Documentation updates