Skip to content

Commit c333a03

Browse files
An experiment with TotalOrderValueUpdated (to be renamed) to bring items too
This for sure simplifies InvoiceGeneration process a lot. But it might introduce some coupling to this one "god" summary event.
1 parent 0e870dc commit c333a03

12 files changed

+223
-316
lines changed

rails_application/app/processes/processes/invoice_generation.rb

Lines changed: 10 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,7 @@ def initialize(event_store, command_bus)
1010
end
1111

1212
subscribes_to(
13-
Pricing::PriceItemAdded,
14-
Pricing::PriceItemRemoved,
15-
Pricing::PercentageDiscountSet,
16-
Pricing::PercentageDiscountChanged,
17-
Pricing::PercentageDiscountRemoved,
13+
Processes::TotalOrderValueUpdated,
1814
Fulfillment::OrderRegistered
1915
)
2016

@@ -25,14 +21,8 @@ def act
2521
private
2622

2723
def create_invoice_items_for_all_products
28-
product_totals.each do |product_id, quantity, amount|
29-
create_invoice_items_for_product(product_id, quantity, amount)
30-
end
31-
end
32-
33-
def product_totals
34-
state.sub_amounts_total.map do |product_id, amounts|
35-
[product_id, amounts.fetch(:quantity), amounts.fetch(:amount)]
24+
state.items.each do |item|
25+
create_invoice_items_for_product(item.fetch(:product_id), item.fetch(:quantity), item.fetch(:amount))
3626
end
3727
end
3828

@@ -43,16 +33,8 @@ def fetch_id(event)
4333
def apply(event)
4434
@order_id = event.data.fetch(:order_id)
4535
case event
46-
when Pricing::PriceItemAdded
47-
state.add_line(event.data.fetch(:product_id), event.data.fetch(:base_price), event.data.fetch(:price))
48-
when Pricing::PriceItemRemoved
49-
state.remove_line(event.data.fetch(:product_id))
50-
when Pricing::PercentageDiscountSet
51-
state.set_discount(event.data.fetch(:type), event.data.fetch(:amount))
52-
when Pricing::PercentageDiscountChanged
53-
state.set_discount(event.data.fetch(:type), event.data.fetch(:amount))
54-
when Pricing::PercentageDiscountRemoved
55-
state.remove_discount(event.data.fetch(:type))
36+
when TotalOrderValueUpdated
37+
state.set_items(event.data.fetch(:items))
5638
when Fulfillment::OrderRegistered
5739
state.mark_placed
5840
end
@@ -76,29 +58,13 @@ def create_invoice_items_for_product(product_id, quantity, discounted_amount)
7658

7759
end
7860

79-
Invoice = Data.define(:lines, :discounts, :order_placed) do
80-
def initialize(lines: [], discounts: [], order_placed: false)
81-
super(lines: lines.freeze, discounts: discounts.freeze, order_placed: order_placed)
82-
end
83-
84-
def add_line(product_id, base_price, price)
85-
with(lines: lines + [{ product_id:, base_price:, price: }])
86-
end
87-
88-
def remove_line(product_id)
89-
with(lines: lines.dup.tap { |lines| lines.delete_at(index_of_first_line_with(product_id)) })
90-
end
91-
92-
def index_of_first_line_with(product_id)
93-
lines.find_index { |line| line.fetch(:product_id) == product_id }
94-
end
95-
96-
def set_discount(type, amount)
97-
with_discounts_applied(discounts.reject { |d| d.fetch(:type) == type } + [{ type:, amount: }])
61+
Invoice = Data.define(:items, :order_placed) do
62+
def initialize(items: [], order_placed: false)
63+
super(items: items.freeze, order_placed: order_placed)
9864
end
9965

100-
def remove_discount(type)
101-
with_discounts_applied(discounts.reject { |d| d.fetch(:type) == type })
66+
def set_items(new_items)
67+
with(items: new_items)
10268
end
10369

10470
def mark_placed
@@ -108,50 +74,6 @@ def mark_placed
10874
def placed?
10975
order_placed
11076
end
111-
112-
def apply_discounts_to_lines
113-
with(lines: lines.map { |line| apply_discount_to_line(line) })
114-
end
115-
116-
def apply_discount_to_line(line)
117-
line.merge(price: line.fetch(:base_price) * discount_multiplier)
118-
end
119-
120-
def discount_multiplier
121-
1 - (final_discount_percentage / 100.0)
122-
end
123-
124-
def sub_amounts_total
125-
lines.each_with_object({}) do |line, memo|
126-
product_id = line.fetch(:product_id)
127-
memo[product_id] ||= { base_amount: 0, amount: 0, quantity: 0 }
128-
memo[product_id][:base_amount] += line.fetch(:base_price)
129-
memo[product_id][:amount] += line.fetch(:price)
130-
memo[product_id][:quantity] += 1
131-
end
132-
end
133-
134-
private
135-
136-
def with_discounts_applied(new_discounts)
137-
with(discounts: new_discounts).apply_discounts_to_lines
138-
end
139-
140-
def subtotal
141-
lines.sum { |line| line.fetch(:price) }
142-
end
143-
144-
def total_discount_percentage
145-
discounts.sum { |discount| discount.fetch(:amount) }
146-
end
147-
148-
def final_discount_percentage
149-
[total_discount_percentage, 100].min
150-
end
151-
152-
def discounted_value
153-
subtotal * (1 - final_discount_percentage / 100.0)
154-
end
15577
end
15678

15779
end

rails_application/app/processes/processes/total_order_value.rb

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ class TotalOrderValue
1111
)
1212

1313
def act
14-
publish_total_order_value(state.subtotal, state.discounted_value)
14+
publish_total_order_value(state.subtotal, state.discounted_value, state.per_product_discounted_totals)
1515
end
1616

1717
def apply(event)
@@ -34,12 +34,13 @@ def apply(event)
3434

3535
private
3636

37-
def publish_total_order_value(total_amount, discounted_amount)
37+
def publish_total_order_value(total_amount, discounted_amount, items)
3838
event_store.publish(
39-
TotalOrderValueUpdated.new(data: {
40-
total_amount: total_amount,
39+
TotalOrderValueUpdated.new(data: {
40+
total_amount: total_amount,
4141
discounted_amount: discounted_amount,
42-
order_id: @order_id
42+
items: items,
43+
order_id: @order_id
4344
}),
4445
stream_name: "Processes::TotalOrderValue$#{@order_id}"
4546
)
@@ -105,6 +106,20 @@ def final_discount_percentage
105106
def discounted_value
106107
subtotal * (1 - final_discount_percentage / 100.0)
107108
end
109+
110+
def discount_multiplier
111+
1 - (final_discount_percentage / 100.0)
112+
end
113+
114+
def per_product_discounted_totals
115+
grouped = lines.group_by { |line| line.fetch(:product_id) }
116+
grouped.map do |product_id, lines_for_product|
117+
quantity = lines_for_product.size
118+
base_amount = lines_for_product.sum { |line| line.fetch(:price) }
119+
amount = base_amount * discount_multiplier
120+
{ product_id: product_id, quantity: quantity, amount: amount }
121+
end
122+
end
108123
end
109124

110-
end
125+
end

rails_application/test/client_orders/order_paid_test.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ def test_order_confirmed
1919
data: {
2020
order_id: order_id,
2121
discounted_amount: 30,
22-
total_amount: 30
22+
total_amount: 30,
23+
items: [ { product_id: product_id, quantity: 1, amount: 30 } ]
2324
}
2425
)
2526
)

rails_application/test/client_orders/update_order_total_value_test.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,15 @@ def test_order_created_has_draft_state
1212
customer_registered(customer_id)
1313
prepare_product(product_id)
1414

15-
event_store.publish(Processes::TotalOrderValueUpdated.new(data: { order_id: order_id, discounted_amount: 0, total_amount: 10 }))
15+
event_store.publish(Processes::TotalOrderValueUpdated.new(data: { order_id: order_id, discounted_amount: 0, total_amount: 10, items: [] }))
1616

1717
order = ClientOrders::Order.find_by(order_uid: order_id)
1818
assert_equal "Draft", order.state
1919
end
2020

2121
def test_broadcasts
2222
order_id = SecureRandom.uuid
23-
event_store.publish(Processes::TotalOrderValueUpdated.new(data: { order_id: order_id, discounted_amount: 0, total_amount: 10 }))
23+
event_store.publish(Processes::TotalOrderValueUpdated.new(data: { order_id: order_id, discounted_amount: 0, total_amount: 10, items: [] }))
2424

2525
assert_broadcast_on(
2626
"client_orders_#{order_id}",

rails_application/test/client_orders/update_paid_orders_summary_test.rb

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ module ClientOrders
44
class UpdatePaidOrdersSummaryTest < InMemoryTestCase
55
cover "ClientOrders*"
66

7+
def setup
8+
super
9+
@order_products = Hash.new { |h, k| h[k] = [] }
10+
end
11+
712
def test_update_orders_summary
813
customer_id = SecureRandom.uuid
914
other_customer_id = SecureRandom.uuid
@@ -56,16 +61,22 @@ def set_price_to_product(product_id, price)
5661
end
5762

5863
def add_item_to_basket(order_id, product_id, price)
64+
@order_products[order_id] << product_id
5965
run_command(Pricing::AddPriceItem.new(order_id: order_id, product_id: product_id, price: price))
6066
end
6167

6268
def confirm_order(customer_id, order_id, total_amount)
69+
grouped = @order_products[order_id].group_by { |pid| pid }
70+
items = grouped.map do |pid, list|
71+
{ product_id: pid, quantity: list.size, amount: total_amount }
72+
end
6373
event_store.publish(
6474
Processes::TotalOrderValueUpdated.new(
6575
data: {
6676
order_id: order_id,
6777
discounted_amount: total_amount,
68-
total_amount: total_amount
78+
total_amount: total_amount,
79+
items: items
6980
}
7081
)
7182
)

rails_application/test/customers/update_paid_orders_summary_test.rb

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ module Customers
44
class UpdatePaidOrdersSummaryTest < InMemoryTestCase
55
cover "Customers"
66

7+
def setup
8+
super
9+
@order_products = Hash.new { |h, k| h[k] = [] }
10+
end
11+
712
def test_update_orders_summary
813
customer_id = SecureRandom.uuid
914
other_customer_id = SecureRandom.uuid
@@ -55,16 +60,22 @@ def set_vat_rate_to_product(product_id)
5560
end
5661

5762
def add_item_to_basket(order_id, product_id, price)
63+
@order_products[order_id] << product_id
5864
run_command(Pricing::AddPriceItem.new(order_id: order_id, product_id: product_id, price: price))
5965
end
6066

6167
def confirm_order(customer_id, order_id, total_amount)
68+
grouped = @order_products[order_id].group_by { |pid| pid }
69+
items = grouped.map do |pid, list|
70+
{ product_id: pid, quantity: list.size, amount: total_amount }
71+
end
6272
event_store.publish(
6373
Processes::TotalOrderValueUpdated.new(
6474
data: {
6575
order_id: order_id,
6676
discounted_amount: total_amount,
67-
total_amount: total_amount
77+
total_amount: total_amount,
78+
items: items
6879
}
6980
)
7081
)

rails_application/test/orders/broadcast_test.rb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,8 @@ def test_broadcast_update_order_value
177177
data: {
178178
order_id: order_1_id,
179179
discounted_amount: 30,
180-
total_amount: 30
180+
total_amount: 30,
181+
items: [ { product_id: product_id, quantity: 2, amount: 30 } ]
181182
}
182183
)
183184
)
@@ -189,7 +190,8 @@ def test_broadcast_update_order_value
189190
data: {
190191
order_id: order_id,
191192
discounted_amount: 30,
192-
total_amount: 30
193+
total_amount: 30,
194+
items: [ { product_id: product_id, quantity: 1, amount: 30 } ]
193195
}
194196
)
195197
)

rails_application/test/orders/order_paid_test.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def test_order_confirmed
3737
Crm::CustomerAssignedToOrder.new(data: { customer_id: customer_id, order_id: order_id })
3838
)
3939

40-
event_store.publish(Processes::TotalOrderValueUpdated.new(data: { order_id: order_id, discounted_amount: 0, total_amount: 10 }))
40+
event_store.publish(Processes::TotalOrderValueUpdated.new(data: { order_id: order_id, discounted_amount: 0, total_amount: 10, items: [ { product_id: product_id, quantity: 1, amount: 10 } ] }))
4141
order_confirmed = Fulfillment::OrderConfirmed.new(
4242
data: {
4343
order_id: order_id

rails_application/test/orders/update_order_total_value_test.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ def test_order_created_has_draft_state
1111
customer_registered(customer_id)
1212
prepare_product(product_id)
1313

14-
event_store.publish(Processes::TotalOrderValueUpdated.new(data: { order_id: order_id, discounted_amount: 0, total_amount: 10 }))
14+
event_store.publish(Processes::TotalOrderValueUpdated.new(data: { order_id: order_id, discounted_amount: 0, total_amount: 10, items: [] }))
1515

1616
order = Orders::Order.find_by(uid: order_id)
1717
assert_equal "Draft", order.state
@@ -25,8 +25,8 @@ def test_newest_event_is_always_applied
2525
prepare_product(product_id)
2626
item_added_to_basket(order_id, product_id)
2727

28-
event_store.publish(Processes::TotalOrderValueUpdated.new(data: { order_id: order_id, discounted_amount: 0, total_amount: 10 }, metadata: { timestamp: Time.current }))
29-
event_store.publish(Processes::TotalOrderValueUpdated.new(data: { order_id: order_id, discounted_amount: 10, total_amount: 20 }, metadata: { timestamp: 1.minute.ago }))
28+
event_store.publish(Processes::TotalOrderValueUpdated.new(data: { order_id: order_id, discounted_amount: 0, total_amount: 10, items: [] }, metadata: { timestamp: Time.current }))
29+
event_store.publish(Processes::TotalOrderValueUpdated.new(data: { order_id: order_id, discounted_amount: 10, total_amount: 20, items: [] }, metadata: { timestamp: 1.minute.ago }))
3030

3131
order = Orders::Order.find_by(uid: order_id)
3232
assert_equal 10, order.total_value
@@ -47,7 +47,7 @@ def test_stream
4747
private
4848

4949
def total_order_value_updated(order_id)
50-
Processes::TotalOrderValueUpdated.new(data: { order_id: order_id, discounted_amount: 0, total_amount: 10 })
50+
Processes::TotalOrderValueUpdated.new(data: { order_id: order_id, discounted_amount: 0, total_amount: 10, items: [] })
5151
end
5252

5353
def item_added_to_basket(order_id, product_id)

0 commit comments

Comments
 (0)