Skip to content

Conversation

@mamhoff
Copy link
Contributor

@mamhoff mamhoff commented Nov 17, 2025

Summary

This PR changes the new promotion system to calculate discounts entirely via adjustments rather than using in-memory ItemDiscount objects.

The advantage of this approach is that it allows to a) calculate discountable amounts for line items outside of the DiscountOrder loop. This is useful for calculating discounted prices, a feature I have on the roadmap. It specifically covers needs like the one addressed in this commit. This way, we have one system of record, not two.

The other advantage is that we can remove the pretty complex PersistDiscountedOrder code, and just rely on order.save! to do the right thing.

It also dovetails nicely with the work on the in-memory order updater.

Checklist

Check out our PR guidelines for more details.

The following are mandatory for all PRs:

The following are not always needed:

  • 📖 I have updated the README to account for my changes.
  • 📑 I have documented new code with YARD.
  • 🛣️ I have opened a PR to update the guides.
  • ✅ I have added automated tests to cover my changes.
  • 📸 I have attached screenshots to demo visual changes.

@github-actions github-actions bot added changelog:solidus_core Changes to the solidus_core gem changelog:solidus_promotions Changes to the solidus_promotions gem labels Nov 17, 2025
@codecov
Copy link

codecov bot commented Nov 17, 2025

Codecov Report

❌ Patch coverage is 98.52941% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 89.46%. Comparing base (f54d7ff) to head (fc5b38b).

Files with missing lines Patch % Lines
...romotions/app/models/solidus_promotions/benefit.rb 83.33% 1 Missing ⚠️
...idus_promotions/benefits/create_discounted_item.rb 90.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #6371      +/-   ##
==========================================
+ Coverage   89.45%   89.46%   +0.01%     
==========================================
  Files         974      974              
  Lines       20322    20351      +29     
==========================================
+ Hits        18179    18207      +28     
- Misses       2143     2144       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

it " will not create the adjustment" do
expect {
subject
order.save!
Copy link
Member

Choose a reason for hiding this comment

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

We only want to check for amount zero and marked for destruction here and not save the order.

amount + previous_discounts.sum(&:amount)
end

def current_discounts
Copy link
Member

Choose a reason for hiding this comment

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

current_lane_discounts

def level
:shipment
end
deprecate :level, deprecator: Spree.deprecator
Copy link
Member

Choose a reason for hiding this comment

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

this is worth its dedicated pr

For in-memory promotion recalculation, it's important that shipping
rates and their associated discounts are persisted when the underlying
order is `save`d. This only works with the `inverse_of: shipping_rates`
is set on `ShippingRate#shipment` along with the `autosave: true` on
`Shipment#shipping_rates`.
Shipping Rate taxes are something we want to be able to modify in the
order updater without having to update the order graph one by one.
This attribute will only be set during promotion calculation so that
we can now which adjustments are relevant while recalculating promotion
benefits.
This method allows us to set the currently active promotion lane when
calculating promotions.
This will return the lanes that came before the current lane. Useful
for finding the discountable amount by lane.
This can be used to select all adjustments of an adjustable - a line
item or shipment - that are associated to a promotion benefit and by
lanes. This is useful for getting the current discountable amount
programmatically, as will be seen in upcoming commits.
This can be used to calculate the current discountable amount while
calculating promotions.
When we call order.reset_discounts, we don't want to deal with those
that are currently valid/eligible during promotion recalulcation, we
want all discounts to be zero.

We could destroy them, but zeroing them is more efficient, because it's
likely that an adjustment might not change during adjustment
recalculation, in which case we don't need to destroy and re-create.
We want the individual benefits to issue exactly the kind of object that
will discount it, so we need to make some adjustments:

- Dynamically call a discount_#{discountable_type} method
- Create the right kind of discount object per discountable type in
  those methods
- Always test the compute_* methods simulating a calculation process
This way we can have a single source of truth for what's being
discounted.
Also removes the now-unnecessary `PersistDiscountedOrder` class.
@mamhoff mamhoff force-pushed the discountable-amounts-by-adjustments branch from 5f2c828 to fc5b38b Compare November 20, 2025 21:15
@mamhoff mamhoff mentioned this pull request Nov 20, 2025
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

changelog:solidus_core Changes to the solidus_core gem changelog:solidus_promotions Changes to the solidus_promotions gem

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants