Skip to content
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

Place backorders for linked products via DFC integration #12856

Merged
merged 37 commits into from
Oct 1, 2024

Conversation

mkllnk
Copy link
Member

@mkllnk mkllnk commented Sep 11, 2024

ℹ️ Funded Feature. Please track ALL ASSOCIATED WORK under the associated tracking code: #11678 DFC Orders

What? Why?

What should we test?

  • Use a server with access to the OIDC provider Les Communs (UK & FR staging).
  • Go to Admin, Enterprises, OIDC settings to set up your account. Be aware that only one OFN user can be connected to one DFC user. So maybe just use the testdfc user.
  • Go to Admin, Product Import and import DFC products from https://env-0105831.jcloud-ver-jpe.ik-server.com/api/dfc/Enterprises/test-hodmedod/SuppliedProducts.
  • Add retail variants from this catalog to an order cycle.
  • Your user must be the owner of the distributor.
  • Order from that order cycle.

In Shopify (https://admin.shopify.com/store/test-hodmedod):

  • A new draft order should be placed after a few seconds.
  • The ordered items should be the wholesale variants of the ordered items.
  • Ordering more in OFN should add to the draft order with a few seconds delay.
  • Four hours after the order cycle finishes (this time needs to be set before the first order is done), the draft order should complete.
  • Cancelled orders should be taken into account and the final order should have reduced quantities.

Release notes

Changelog Category (reviewers may add a label for the release notes):

  • User facing changes
  • API changes (V0, V1, DFC or Webhook)
  • Technical changes only
  • Feature toggled

The title of the pull request will be included in the release notes.

Dependencies

Documentation updates

@mkllnk mkllnk added the user facing changes Thes pull requests affect the user experience label Sep 11, 2024
@mkllnk mkllnk self-assigned this Sep 11, 2024
Copy link
Collaborator

@rioug rioug left a comment

Choose a reason for hiding this comment

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

What a journey ! I am glad I have some background knowledge on the project 🫘

I think it looks good ! 👏
I left a few comments that should help other reviewer and some suggestion to clarify a few things.

app/jobs/backorder_job.rb Outdated Show resolved Hide resolved
Comment on lines 38 to 48
it "completes an order", vcr: true do
backorder = subject.find_or_build_order(order)

expect(backorder.semanticId).to match %r{^https.*/[0-9]+$}
expect(backorder.lines.count).to eq 1

subject.complete_order(order, backorder)

remaining_open_order = subject.find_or_build_order(order)
expect(remaining_open_order.semanticId).not_to eq backorder.semanticId
end
Copy link
Collaborator

Choose a reason for hiding this comment

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

Shouldn't that be under it's own describe block ? You are testingcomplete_order aren't new ?
It will be addressed later on

app/jobs/backorder_job.rb Outdated Show resolved Hide resolved
@@ -2,13 +2,23 @@

# Finds wholesale offers for retail products.
class FdcOfferBroker
Solution = Struct.new(:product, :factor, :offer)
Copy link
Collaborator

Choose a reason for hiding this comment

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

If we were on ruby >=3.2 I would have suggested to use Data instead of Struct here : https://ruby-doc.org/3.2.5/Data.html

Copy link
Member

Choose a reason for hiding this comment

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

Interesting, I didn't know about that thanks!

@mkllnk mkllnk force-pushed the dfc-order branch 3 times, most recently from 2877d66 to 3baa845 Compare September 12, 2024 06:43
@dacook dacook self-requested a review September 12, 2024 07:27
Copy link
Member

@dacook dacook left a comment

Choose a reason for hiding this comment

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

Well done! It was a long journey with a lot to work through, but it looks like it's near to completion now. I love how it brings together a complex standard and proves that it's possible to use in the real world.

I see three TODOs; perhaps you're planning to work on those still.
Still, I think this looks good to me. My brain was mush for the last few commits but it mostly seems to make sense 👍 (apart from the few questions I have!)

app/jobs/backorder_job.rb Outdated Show resolved Hide resolved
spec/services/fdc_backorderer_spec.rb Show resolved Hide resolved
spec/jobs/backorder_job_spec.rb Show resolved Hide resolved
app/jobs/backorder_job.rb Outdated Show resolved Hide resolved
@@ -2,13 +2,23 @@

# Finds wholesale offers for retail products.
class FdcOfferBroker
Solution = Struct.new(:product, :factor, :offer)
Copy link
Member

Choose a reason for hiding this comment

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

Interesting, I didn't know about that thanks!

app/services/fdc_offer_broker.rb Show resolved Hide resolved
app/services/fdc_offer_broker.rb Outdated Show resolved Hide resolved
app/models/spree/variant.rb Show resolved Hide resolved
Comment on lines 43 to 50
line.quantity = line.quantity.to_i - wholesale_items_contained_in_stock

retail_stock_changes = wholesale_items_contained_in_stock * transformation.factor
linked_variant.on_hand -= retail_stock_changes
Copy link
Member

Choose a reason for hiding this comment

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

I have to confess my brain didn't quite make it to the end of the PR before turning into mush.
It seems that we're adjusting the backorders relative to what they were before. Would it not be more robust to look at all current retail orders and re-calculate the backorder requirement from scratch?
Hmm.. no we can't because we don't know how much of our "on_hand" stock was from a backorder, unless we look at the backorder.

I can't work out if that's a problem or not 🤯

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yeah, it's a tricky one. I would also favour recalculating from scratch, but the use of OFN stock is so much more helpful/flexible for this one (we've already encountered an edge case for one of our pilot hubs that will need to utilise that)...

..so long as everything always works, I think we're good! 🫣

@RaggedStaff RaggedStaff added the pr-staged-uk staging.openfoodnetwork.org.uk label Sep 13, 2024
user = order.distributor.owner

# We are assuming that all variants are linked to the same wholesale
# shop and its catalog:
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm happy with that assumption (for now 😉 ), and it will be true for the pilots we're running.

return if open_orders.blank?

# If there are multiple open orders, we don't know which one to choose.
# We want the order we placed for the same distributor in the same order
Copy link
Collaborator

Choose a reason for hiding this comment

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

Ok. I'm pretty sure the Shopify Orders endpoint will display old (completed orders), we should only have 1 Draft order though.

The Shopify Hub app is storing the ID of the Order returned from the initial POST request, to utilise in future PUT requests. Could we do that ?

Copy link
Member Author

Choose a reason for hiding this comment

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

We could. And maybe we need to. I tried to get around it to have minimal local data. Local data structures increase maintenance and can become out of sync. And I was worried about longer dev times as well. But I agree that the current solution isn't great.

#
# For now, we just guess:
open_orders.last.tap do |order|
# The DFC Connector doesn't recognise status values properly yet.
Copy link
Collaborator

Choose a reason for hiding this comment

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

@mkllnk could you elaborate on what you mean by "DFC Connector doesn't recognise status values properly"? 😟

I thought this was sorted & we haven't had any issues with the TS Connector. @lecoqlibre do we need to update the Ruby Connector?

Copy link
Member Author

Choose a reason for hiding this comment

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

Well, we can load the vocabulary with order states. The Connector doesn't have a built-in method for that but I did make it work in a separate branch. I could work on a pull request to make this part work with the importer. I was just shying away from the effort to meet the deadline. The current version is also not recognising the transformation links. That may just be the outdated context though. We are still on 1.8, I think. And I wasn't keen to follow the updates closely because you discovered bugs here and there. Happy to give it another go though.

@mkllnk mkllnk force-pushed the dfc-order branch 3 times, most recently from c82527d to 585bd88 Compare September 19, 2024 07:07
@RaggedStaff RaggedStaff added pr-staged-uk staging.openfoodnetwork.org.uk and removed pr-staged-uk staging.openfoodnetwork.org.uk labels Sep 19, 2024
@dacook dacook added pr-staged-uk staging.openfoodnetwork.org.uk and removed pr-staged-uk staging.openfoodnetwork.org.uk labels Sep 24, 2024
The simplified API was only returning the response body, not allowing us
to inspect if an error occurred. Since an error should be an exception
when communicating with a standardised protocol, we raise an error and
keep our simple API.
Apparently, the FDC implementation uses those dates to finalise orders.
The job class is getting too big.
We were already importing stock levels from offers but now we are
looking at catalog items as well.
This was abandoned when the checkout was re-designed. But I want to
refactor the order locking mechanism and it would be good to know that I
don't break anything.
From Sidekiq's view, the job is successful when we rescue an error and
it will discard it. But we want the option to inspect the job and retry
it. Failing jobs are also reported to Bugsnag automatically.

I didn't specify `retry: false` because that discards the job as well.
But `retry: 0` should sent it straight to the dead set. No automatic
retries but it's treated like a failed job.
@mkllnk mkllnk marked this pull request as ready for review September 26, 2024 05:08
Copy link
Member

@dacook dacook left a comment

Choose a reason for hiding this comment

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

Fantastic! I have to admit I don't fully follow the web of semantics, but everything has been put together with clear and comprehensive steps, well done.

As you said, it shouldn't affect any existing functionality (from memory, any changes in the app code are very minor and should be covered by specs anyway).

So I think this is good to merge, ready to test in real life 👍

filename = File.basename(supplied_product.image)

Spree::Image.new.tap do |image|
PrivateAddressCheck.only_public_connections do
Copy link
Member

Choose a reason for hiding this comment

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

So glad There's A Gem For That.

@@ -240,6 +240,8 @@ def set_cost_currency
end

def create_stock_items
return unless stock_items.empty?
Copy link
Member

Choose a reason for hiding this comment

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

Not covered directly by spec, but not a big deal.

Comment on lines +14 to +17
variant.stock_items << Spree::StockItem.new(
stock_location: DefaultStockLocation.find_or_create,
variant:,
)
Copy link
Member

Choose a reason for hiding this comment

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

I wonder if the variant model should be doing this part.


it "creates a new Spree::Product and variant" do
# We need this to save stock:
DefaultStockLocation.find_or_create
Copy link
Member

Choose a reason for hiding this comment

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

I'm probably missing something, but it looks like this is already performed in CatalogItemBuilder. but maybe it is needed before that.

order.next # => address
order.next # => delivery
order.next # => payment
order.next # => confirmation
Copy link
Member

Choose a reason for hiding this comment

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

It might not hurt to expect order.state to be confirmation, if perhaps the states change in the future.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe we should be using Orders::WorkflowService#next to better replicate the checkout process ?

expect(order.completed?).to be true
expect(order.payments.count).to eq 1
end
end
Copy link
Member

Choose a reason for hiding this comment

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

🏅

order.line_items.map(&:variant),
)
end
end
Copy link
Member

Choose a reason for hiding this comment

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

💯
So much to build for one seemingly small task, well done, and thanks for adding a preview.


# Combined example for performance
expect(Bugsnag).to receive(:notify).and_call_original

Copy link
Member

Choose a reason for hiding this comment

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

😢 It's a shame we can't spec this behaviour of sidekiq's

@filipefurtad0
Copy link
Contributor

Oops, apologies @RaggedStaff - I did not notice the staging-FR label on this PR... I'll be sure to re-stage this PR, once I'm done with testing.

@filipefurtad0 filipefurtad0 added pr-staged-fr staging.coopcircuits.fr and removed pr-staged-fr staging.coopcircuits.fr labels Sep 30, 2024
Copy link
Collaborator

@rioug rioug left a comment

Choose a reason for hiding this comment

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

I had a quick look as well, looks good 👍

@mkllnk mkllnk merged commit 1969561 into openfoodfoundation:master Oct 1, 2024
52 of 53 checks passed
@mkllnk mkllnk deleted the dfc-order branch October 1, 2024 00:51
@mkllnk mkllnk removed the pr-staged-fr staging.coopcircuits.fr label Oct 1, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
user facing changes Thes pull requests affect the user experience
Projects
Status: Done
5 participants