Skip to content

Commit 15dac06

Browse files
jarednormanadammathysAlistairNormanforkataHarmony Bouvier
committed
Refactor line item total calculations
While working on the in-memory updater in solidusio#5872, we found the need to change how item totals were being calculated, so that we could mark adjustments for destruction without actually destroying them, while still keeping tax adjustments intact. This change is completely backwards-compatible with the current OrderUpdater, so to reduce the scope of our PR, we wanted to make this change separately. Since the OrderUpdater is already very large, this helps reduce its responsibilities and makes it easier to test this behaviour. We don't see it as necessary to make this a configurable class, but this change leaves that option open in the future. Co-authored-by: Adam Mueller <adam@super.gd> Co-authored-by: Alistair Norman <alistair@super.gd> Co-authored-by: Chris Todorov <chris@super.gd> Co-authored-by: Harmony Bouvier <harmony@super.gd> Co-authored-by: Sofia Besenski <sofia@super.gd> Co-authored-by: benjamin wil <benjamin@super.gd>
1 parent 9f7a53f commit 15dac06

File tree

4 files changed

+115
-31
lines changed

4 files changed

+115
-31
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# frozen_string_literal: true
2+
3+
class Spree::ItemTotal
4+
def initialize(item)
5+
@item = item
6+
end
7+
8+
def recalculate!
9+
tax_adjustments = item.adjustments.select { |adjustment|
10+
adjustment.tax? && !adjustment.marked_for_destruction?
11+
}
12+
13+
# Included tax adjustments are those which are included in the price.
14+
# These ones should not affect the eventual total price.
15+
#
16+
# Additional tax adjustments are the opposite, affecting the final total.
17+
item.included_tax_total = tax_adjustments.select(&:included?).sum(&:amount)
18+
item.additional_tax_total = tax_adjustments.reject(&:included?).sum(&:amount)
19+
20+
item.adjustment_total = item.adjustments.reject { |adjustment|
21+
adjustment.marked_for_destruction? || adjustment.included?
22+
}.sum(&:amount)
23+
end
24+
25+
private
26+
27+
attr_reader :item
28+
end

core/app/models/spree/order_updater.rb

Lines changed: 13 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ def recalculate_adjustments
113113
# It also fits the criteria for sales tax as outlined here:
114114
# http://www.boe.ca.gov/formspubs/pub113/
115115
update_promotions
116-
update_taxes
116+
update_tax_adjustments
117117
update_item_totals
118118
end
119119

@@ -198,21 +198,8 @@ def update_promotions
198198
Spree::Config.promotions.order_adjuster_class.new(order).call
199199
end
200200

201-
def update_taxes
201+
def update_tax_adjustments
202202
Spree::Config.tax_adjuster_class.new(order).adjust!
203-
204-
[*line_items, *shipments].each do |item|
205-
tax_adjustments = item.adjustments.select(&:tax?)
206-
# Tax adjustments come in not one but *two* exciting flavours:
207-
# Included & additional
208-
209-
# Included tax adjustments are those which are included in the price.
210-
# These ones should not affect the eventual total price.
211-
#
212-
# Additional tax adjustments are the opposite, affecting the final total.
213-
item.included_tax_total = tax_adjustments.select(&:included?).sum(&:amount)
214-
item.additional_tax_total = tax_adjustments.reject(&:included?).sum(&:amount)
215-
end
216203
end
217204

218205
def update_cancellations
@@ -221,21 +208,17 @@ def update_cancellations
221208

222209
def update_item_totals
223210
[*line_items, *shipments].each do |item|
224-
# The cancellation_total isn't persisted anywhere but is included in
225-
# the adjustment_total
226-
item.adjustment_total = item.adjustments.
227-
reject(&:included?).
228-
sum(&:amount)
229-
230-
if item.changed?
231-
item.update_columns(
232-
promo_total: item.promo_total,
233-
included_tax_total: item.included_tax_total,
234-
additional_tax_total: item.additional_tax_total,
235-
adjustment_total: item.adjustment_total,
236-
updated_at: Time.current,
237-
)
238-
end
211+
Spree::ItemTotal.new(item).recalculate!
212+
213+
next unless item.changed?
214+
215+
item.update_columns(
216+
promo_total: item.promo_total,
217+
included_tax_total: item.included_tax_total,
218+
additional_tax_total: item.additional_tax_total,
219+
adjustment_total: item.adjustment_total,
220+
updated_at: Time.current,
221+
)
239222
end
240223
end
241224
end
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# frozen_string_literal: true
2+
3+
require 'rails_helper'
4+
5+
RSpec.describe Spree::ItemTotal do
6+
describe "#recalculate!" do
7+
subject { described_class.new(item).recalculate! }
8+
9+
let!(:item) { create :line_item, adjustments: }
10+
11+
let(:tax_rate) { create(:tax_rate) }
12+
13+
let(:arbitrary_adjustment) { create :adjustment, amount: 1, source: nil }
14+
let(:included_tax_adjustment) { create :adjustment, amount: 2, source: tax_rate, included: true }
15+
let(:additional_tax_adjustment) { create :adjustment, amount: 3, source: tax_rate, included: false }
16+
17+
context "with multiple types of adjustments" do
18+
let(:marked_for_destruction_included_tax_adjustment) { create(:adjustment, amount: 5, source: tax_rate, included: true) }
19+
let(:marked_for_destruction_additional_tax_adjustment) { create(:adjustment, amount: 7, source: tax_rate, included: false) }
20+
21+
let(:adjustments) {
22+
[
23+
arbitrary_adjustment,
24+
included_tax_adjustment,
25+
additional_tax_adjustment,
26+
marked_for_destruction_included_tax_adjustment,
27+
marked_for_destruction_additional_tax_adjustment
28+
]
29+
}
30+
31+
before do
32+
[marked_for_destruction_included_tax_adjustment, marked_for_destruction_additional_tax_adjustment]
33+
.each(&:mark_for_destruction)
34+
end
35+
36+
it "updates item totals" do
37+
expect {
38+
subject
39+
}.to change(item, :adjustment_total).from(0).to(4).
40+
and change { item.included_tax_total }.from(0).to(2).
41+
and change { item.additional_tax_total }.from(0).to(3)
42+
end
43+
end
44+
45+
context "with only an arbitrary adjustment" do
46+
let(:adjustments) { [arbitrary_adjustment] }
47+
48+
it "updates the adjustment total" do
49+
expect {
50+
subject
51+
}.to change { item.adjustment_total }.from(0).to(1)
52+
end
53+
end
54+
55+
context "with only tax adjustments" do
56+
let(:adjustments) { [included_tax_adjustment, additional_tax_adjustment] }
57+
58+
it "updates the adjustment total" do
59+
expect {
60+
subject
61+
}.to change { item.adjustment_total }.from(0).to(3).
62+
and change { item.included_tax_total }.from(0).to(2).
63+
and change { item.additional_tax_total }.from(0).to(3)
64+
end
65+
end
66+
end
67+
end

legacy_promotions/app/decorators/models/solidus_legacy_promotions/spree_order_updater_decorator.rb

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,14 @@ def update_adjustment_total
1717

1818
def update_item_totals
1919
[*line_items, *shipments].each do |item|
20+
Spree::ItemTotal.new(item).recalculate!
21+
2022
# The cancellation_total isn't persisted anywhere but is included in
21-
# the adjustment_total
23+
# the adjustment_total.
24+
#
25+
# Core doesn't have "eligible" adjustments anymore, so we need to
26+
# override the adjustment_total calculation to exclude them for legacy
27+
# promotions.
2228
item.adjustment_total = item.adjustments.
2329
select(&:eligible?).
2430
reject(&:included?).

0 commit comments

Comments
 (0)