Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Display time promotion on order page #409

Merged
merged 2 commits into from
Nov 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions ecommerce/#/lib/#.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
require_relative "#/promotions_calendar"
require_relative "#/calculate_order_sub_amounts_value"
require_relative "#/calculate_order_total_value"
require_relative "#/apply_time_promotion"

module #
def self.command_bus=(value)
Expand Down Expand Up @@ -90,6 +91,23 @@ def call(event_store, command_bus)
UseCoupon,
UseCouponHandler.new(event_store)
)
command_bus.register(
SetTimePromotionDiscount,
SetTimePromotionDiscountHandler.new(event_store)
)
command_bus.register(
ResetTimePromotionDiscount,
ResetTimePromotionDiscountHandler.new(event_store)
)
event_store.subscribe(ApplyTimePromotion, to: [
PriceItemAdded,
PriceItemRemoved,
PercentageDiscountSet,
PercentageDiscountReset,
PercentageDiscountChanged,
ProductMadeFreeForOrder,
FreeProductRemovedFromOrder
])
event_store.subscribe(CalculateOrderTotalValue, to: [
PriceItemAdded,
PriceItemRemoved,
Expand Down
25 changes: 25 additions & 0 deletions ecommerce/#/lib/#/apply_time_promotion.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
module #
class ApplyTimePromotion
def call(event)
discount = PromotionsCalendar.new(event_store).current_time_promotions_discount

if discount.exists?
command_bus.(SetTimePromotionDiscount.new(order_id: event.data.fetch(:order_id), amount: discount.value))
else
command_bus.(ResetTimePromotionDiscount.new(order_id: event.data.fetch(:order_id)))
end

rescue NotPossibleToAssignDiscountTwice, NotPossibleToResetWithoutDiscount
end

private

def command_bus
#.command_bus
end

def event_store
#.event_store
end
end
end
13 changes: 13 additions & 0 deletions ecommerce/#/lib/#/commands.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,19 @@ class ResetPercentageDiscount < Infra::Command
alias aggregate_id order_id
end

class SetTimePromotionDiscount < Infra::Command
attribute :order_id, Infra::Types::UUID
attribute :amount, Infra::Types::PercentageDiscount

alias aggregate_id order_id
end

class ResetTimePromotionDiscount < Infra::Command
attribute :order_id, Infra::Types::UUID

alias aggregate_id order_id
end

class RegisterCoupon < Infra::Command
attribute :coupon_id, Infra::Types::UUID
attribute :name, Infra::Types::String
Expand Down
7 changes: 3 additions & 4 deletions ecommerce/#/lib/#/discounts.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
module #
module Discounts
GENERAL_DISCOUNT = "general_discount"
TIME_PROMOTION_DISCOUNT = "time_promotion_discount"

class UnacceptableDiscountRange < StandardError
end

Expand Down Expand Up @@ -53,10 +56,6 @@ def add(other_discount)
other_discount
end

def value
0
end

def exists?
end
end
Expand Down
5 changes: 4 additions & 1 deletion ecommerce/#/lib/#/events.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ class PriceItemValueCalculated < Infra::Event

class PercentageDiscountSet < Infra::Event
attribute :order_id, Infra::Types::UUID
attribute :amount, Infra::Types::Price
attribute :type, Infra::Types::String
attribute :amount, Infra::Types::PercentageDiscount
end

class PriceItemAdded < Infra::Event
Expand All @@ -49,10 +50,12 @@ class PriceItemRemoved < Infra::Event

class PercentageDiscountReset < Infra::Event
attribute :order_id, Infra::Types::UUID
attribute :type, Infra::Types::String
end

class PercentageDiscountChanged < Infra::Event
attribute :order_id, Infra::Types::UUID
attribute :type, Infra::Types::String
attribute :amount, Infra::Types::Price
end

Expand Down
41 changes: 22 additions & 19 deletions ecommerce/#/lib/#/offer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ class Offer
def initialize(id)
@id = id
@list = List.new
@discount = Discounts::NoPercentageDiscount.new
@discounts = {}
end

def add_item(product_id)
Expand All @@ -26,31 +26,34 @@ def remove_item(product_id)
)
end

def apply_discount(discount)
raise NotPossibleToAssignDiscountTwice if @discount.exists?
def apply_discount(type, discount)
raise NotPossibleToAssignDiscountTwice if @discounts.include?(type)
apply PercentageDiscountSet.new(
data: {
order_id: @id,
type: type,
amount: discount.value
}
)
end

def change_discount(discount)
raise NotPossibleToChangeDiscount unless @discount.exists?
def change_discount(type, discount)
raise NotPossibleToChangeDiscount unless @discounts.include?(type)
apply PercentageDiscountChanged.new(
data: {
order_id: @id,
type: type,
amount: discount.value
}
)
end

def reset_discount
raise NotPossibleToResetWithoutDiscount unless @discount.exists?
def reset_discount(type)
raise NotPossibleToResetWithoutDiscount unless @discounts.include?(type)
apply PercentageDiscountReset.new(
data: {
order_id: @id
order_id: @id,
type: type
}
)
end
Expand All @@ -75,10 +78,10 @@ def remove_free_product(order_id, product_id)
)
end

def calculate_total_value(#_catalog, time_promotion_discount)
def calculate_total_value(#_catalog)
total_value = @list.base_sum(#_catalog)
discounted_value = @discounts.values.inject(Discounts::NoPercentageDiscount.new, :add).apply(total_value)

discounted_value = @discount.add(time_promotion_discount).apply(total_value)
apply(
OrderTotalValueCalculated.new(
data: {
Expand All @@ -90,9 +93,9 @@ def calculate_total_value(#_catalog, time_promotion_discount)
)
end

def calculate_sub_amounts(#_catalog, time_promotions_discount)
def calculate_sub_amounts(#_catalog)
sub_amounts_total = @list.sub_amounts_total(#_catalog)
sub_discounts = calculate_total_sub_discounts(#_catalog, time_promotions_discount)
sub_discounts = calculate_total_sub_discounts(#_catalog)

products = @list.products
quantities = @list.quantities
Expand Down Expand Up @@ -138,15 +141,15 @@ def use_coupon(coupon_id, discount)
end

on PercentageDiscountSet do |event|
@discount = Discounts::PercentageDiscount.new(event.data.fetch(:amount))
@discounts[event.data.fetch(:type)] = Discounts::PercentageDiscount.new(event.data.fetch(:amount))
end

on PercentageDiscountChanged do |event|
@discount = Discounts::PercentageDiscount.new(event.data.fetch(:amount))
@discounts[event.data.fetch(:type)] = Discounts::PercentageDiscount.new(event.data.fetch(:amount))
end

on PercentageDiscountReset do |event|
@discount = Discounts::NoPercentageDiscount.new
@discounts.delete(event.data.fetch(:type))
end

on ProductMadeFreeForOrder do |event|
Expand All @@ -157,8 +160,8 @@ def use_coupon(coupon_id, discount)
@list.replace(FreeProduct, Product, event.data.fetch(:product_id))
end

def calculate_total_sub_discounts(#_catalog, time_promotions_discount)
@list.sub_discounts(#_catalog, time_promotions_discount, @discount)
def calculate_total_sub_discounts(#_catalog)
@list.sub_discounts(#_catalog, @discounts)
end

on CouponUsed do |event|
Expand Down Expand Up @@ -209,10 +212,10 @@ def sub_amounts_total(#_catalog)
@products_quantities.map { |product, quantity| quantity * #_catalog.price_for(product) }
end

def sub_discounts(#_catalog, time_promotions_discount, discount)
def sub_discounts(#_catalog, discounts)
@products_quantities.map do |product, quantity|
catalog_price_for_single = #_catalog.price_for(product)
with_total_discount_single = discount.add(time_promotions_discount).apply(catalog_price_for_single)
with_total_discount_single = discounts.values.inject(Discounts::NoPercentageDiscount.new, :add).apply(catalog_price_for_single)
quantity * (catalog_price_for_single - with_total_discount_single)
end
end
Expand Down
43 changes: 29 additions & 14 deletions ecommerce/#/lib/#/services.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def initialize(event_store)

def call(cmd)
@repository.with_aggregate(Offer, cmd.aggregate_id) do |order|
order.apply_discount(Discounts::PercentageDiscount.new(cmd.amount))
order.apply_discount(Discounts::GENERAL_DISCOUNT, Discounts::PercentageDiscount.new(cmd.amount))
end
end
end
Expand All @@ -33,7 +33,7 @@ def initialize(event_store)

def call(cmd)
@repository.with_aggregate(Offer, cmd.aggregate_id) do |order|
order.reset_discount
order.reset_discount(Discounts::GENERAL_DISCOUNT)
end
end
end
Expand All @@ -45,7 +45,31 @@ def initialize(event_store)

def call(cmd)
@repository.with_aggregate(Offer, cmd.aggregate_id) do |order|
order.change_discount(Discounts::PercentageDiscount.new(cmd.amount))
order.change_discount(Discounts::GENERAL_DISCOUNT, Discounts::PercentageDiscount.new(cmd.amount))
Copy link
Collaborator

Choose a reason for hiding this comment

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

Just to verbalise my thoughts: I wonder if the information about the type of the discount is needed here. I don't know for now, but I think I would transfer this information through the PercentageDiscount class. That way we don't have to change method's interface. And it would be simple to add some default (such as GENERAL_DISCOUNT). What do you think @marlena-b?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

That was my first thought as well. The main problem is that once we add it to PercentageDiscount class it no longer is a value object. There can be only one discount of a specific type assigned to the order so type is actually an identifier of a discount.

This also leads to problems like what type should we use if we want to add two discounts? Or what type should be used for the initial accumulator in the inject here: discounts.values.inject(Discounts::NoPercentageDiscount.new, :add).apply(catalog_price_for_single)? These problems made me feel it is not a right design to add it to this class.

Copy link
Collaborator

Choose a reason for hiding this comment

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

The main problem is that once we add it to PercentageDiscount class it no longer is a value object. There can be only one discount of a specific type assigned to the order so type is actually an identifier of a discount.

The type would still be a type. If only one discount of specifiic type can be assigned to an order this is just a rule that can be exectued within the order class. But I wouldn't say that extending PercentageDiscount by type would make it an entity.

I assume that would still be true:

PercentageDiscount.new(type: GENERAL, amount: 100).eql? PercentageDiscount.new(type: GENERAL, amount: 100)

I'll refer to the second part of your reply later :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Ok, I can give it another shot 👍 I will merge this PR and fix it in another one.

end
end
end

class SetTimePromotionDiscountHandler
def initialize(event_store)
@repository = Infra::AggregateRootRepository.new(event_store)
end

def call(cmd)
@repository.with_aggregate(Offer, cmd.aggregate_id) do |order|
order.apply_discount(Discounts::TIME_PROMOTION_DISCOUNT, Discounts::PercentageDiscount.new(cmd.amount))
end
end
end

class ResetTimePromotionDiscountHandler
def initialize(event_store)
@repository = Infra::AggregateRootRepository.new(event_store)
end

def call(cmd)
@repository.with_aggregate(Offer, cmd.aggregate_id) do |order|
order.reset_discount(Discounts::TIME_PROMOTION_DISCOUNT)
end
end
end
Expand Down Expand Up @@ -124,27 +148,18 @@ def initialize(event_store)
def call(command)
with_retry do
@repository.with_aggregate(Offer, command.aggregate_id) do |order|
order.calculate_total_value(#Catalog.new(@event_store), time_promotions_discount)
order.calculate_total_value(#Catalog.new(@event_store))
end
end
end



def calculate_sub_amounts(command)
with_retry do
@repository.with_aggregate(Offer, command.aggregate_id) do |order|
order.calculate_sub_amounts(#Catalog.new(@event_store), time_promotions_discount)
order.calculate_sub_amounts(#Catalog.new(@event_store))
end
end
end

private

def time_promotions_discount
PromotionsCalendar.new(@event_store).current_time_promotions_discount
end

end

class OnCouponRegister
Expand Down
Loading
Loading