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

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 89.47%. Comparing base (fc3adf7) to head (bd24d6b).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #6371      +/-   ##
==========================================
+ Coverage   89.45%   89.47%   +0.01%     
==========================================
  Files         974      979       +5     
  Lines       20322    20412      +90     
==========================================
+ Hits        18179    18263      +84     
- Misses       2143     2149       +6     

☔ 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.

Similar to the work in solidusio#6360, this improves the DSL for creating
benefits. Rather than having to define one method that cares for
applicability to a discountable, we now simply define that a benefit
`#can_discount?` an object if its public interface has
`#discount_{discountable_type}` defined.

This does not change the implementation of the different methods, but
that will come when we refactor the promotion system to use a single
system of record for discounts. Then, different objects will need
different discount records (line items and shipment use adjustments, but
shipping rates use shipping rate discounts etc.).

I'm not doing the work for adding deprecation warnings here, as this
code was never meant to be overridden up to now.

Also fixes some AI slop in the docs.
Instead, follow the deprecation message's instructions.
In the unlikely case people have used these modules, warn if they are
included.
This allows setting the current promotion lane to the current thread,
and run computations with the current promotion lane set.

This is useful for calculating what the discounted amount of a
promotable is within e.g. the `default` lane even if there are
adjustments for the `post` lane.

I'm using `Thread.current` here because we don't always have an order to
store the current lane on.
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 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.
Calculators now rely on `discountable_amount`, which goes back to
`previous_lane_discounts`. We need to stub it, because using the
SolidusPromotions::Promotion.within_lane helper will reset it (often
during spec setup).
@mamhoff mamhoff force-pushed the discountable-amounts-by-adjustments branch from fc5b38b to bd24d6b Compare November 22, 2025 11:19
@github-actions github-actions bot removed the changelog:solidus_core Changes to the solidus_core gem label Nov 22, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

changelog:solidus_promotions Changes to the solidus_promotions gem

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants