Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Adding/Removing items to refunds #425

Open
wants to merge 5 commits into
base: add-new-refund-form
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 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
10 changes: 10 additions & 0 deletions ecommerce/ordering/lib/ordering.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,23 @@
require_relative "ordering/events/order_expired"
require_relative "ordering/events/order_submitted"
require_relative "ordering/events/order_rejected"
require_relative "ordering/events/draft_refund_created"
require_relative "ordering/events/item_added_to_refund"
require_relative "ordering/events/item_removed_from_refund"
require_relative "ordering/commands/add_item_to_basket"
require_relative "ordering/commands/remove_item_from_basket"
require_relative "ordering/commands/submit_order"
require_relative "ordering/commands/set_order_as_expired"
require_relative "ordering/commands/accept_order"
require_relative "ordering/commands/reject_order"
require_relative "ordering/commands/create_draft_refund"
require_relative "ordering/commands/add_item_to_refund"
require_relative "ordering/commands/remove_item_from_refund"
require_relative "ordering/fake_number_generator"
require_relative "ordering/number_generator"
require_relative "ordering/service"
require_relative "ordering/order"
require_relative "ordering/refund"

module Ordering
class Configuration
Expand All @@ -32,6 +39,9 @@ def call(event_store, command_bus)
command_bus.register(SetOrderAsExpired, OnSetOrderAsExpired.new(event_store))
command_bus.register(AcceptOrder, OnAcceptOrder.new(event_store))
command_bus.register(RejectOrder, OnRejectOrder.new(event_store))
command_bus.register(CreateDraftRefund, OnCreateDraftRefund.new(event_store))
command_bus.register(AddItemToRefund, OnAddItemToRefund.new(event_store))
command_bus.register(RemoveItemFromRefund, OnRemoveItemFromRefund.new(event_store))
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module Ordering
class AddItemToRefund < Infra::Command
attribute :refund_id, Infra::Types::UUID
attribute :order_id, Infra::Types::UUID
attribute :product_id, Infra::Types::UUID

alias aggregate_id refund_id
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module Ordering
class CreateDraftRefund < Infra::Command
attribute :refund_id, Infra::Types::UUID
attribute :order_id, Infra::Types::UUID

alias aggregate_id refund_id
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module Ordering
class RemoveItemFromRefund < Infra::Command
attribute :refund_id, Infra::Types::UUID
attribute :order_id, Infra::Types::UUID
attribute :product_id, Infra::Types::UUID

alias aggregate_id refund_id
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module Ordering
class DraftRefundCreated < Infra::Event
attribute :refund_id, Infra::Types::UUID
attribute :order_id, Infra::Types::UUID
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module Ordering
class ItemAddedToRefund < Infra::Event
attribute :refund_id, Infra::Types::UUID
attribute :order_id, Infra::Types::UUID
attribute :product_id, Infra::Types::UUID
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module Ordering
class ItemRemovedFromRefund < Infra::Event
attribute :refund_id, Infra::Types::UUID
attribute :order_id, Infra::Types::UUID
attribute :product_id, Infra::Types::UUID
end
end
58 changes: 58 additions & 0 deletions ecommerce/ordering/lib/ordering/refund.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
module Ordering
class Refund
include AggregateRoot

ProductNotFoundError = Class.new(StandardError)

def initialize(id)
@id = id
@refund_items = ItemsList.new
end

def create_draft(order_id)
apply DraftRefundCreated.new(data: { refund_id: @id, order_id: order_id })
end

def add_item(product_id)
apply ItemAddedToRefund.new(data: { refund_id: @id, order_id: @order_id, product_id: product_id })
end

def remove_item(product_id)
raise ProductNotFoundError unless @refund_items.quantity(product_id).positive?
Copy link
Collaborator

Choose a reason for hiding this comment

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

I wonder if this error could have more meaningful business name. Something like RefundHaveNotBeenRequestedForThisProduct

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good idea, I changed the name 👍

apply ItemRemovedFromRefund.new(data: { refund_id: @id, order_id: @order_id, product_id: product_id })
end

on DraftRefundCreated do |event|
@order_id = event.data[:order_id]
end

on ItemAddedToRefund do |event|
@refund_items.increase_quantity(event.data[:product_id])
end

on ItemRemovedFromRefund do |event|
@refund_items.decrease_quantity(event.data[:product_id])
end
end

class ItemsList
attr_reader :refund_items

def initialize
@refund_items = Hash.new(0)
end

def increase_quantity(product_id)
lukaszreszke marked this conversation as resolved.
Show resolved Hide resolved
refund_items[product_id] = quantity(product_id) + 1
end

def decrease_quantity(product_id)
refund_items[product_id] -= 1
refund_items.delete(product_id) if refund_items.fetch(product_id).equal?(0)
end

def quantity(product_id)
refund_items[product_id]
end
end
end
36 changes: 36 additions & 0 deletions ecommerce/ordering/lib/ordering/service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,40 @@ def call(command)
end
end
end

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

def call(command)
@repository.with_aggregate(Refund, command.aggregate_id) do |refund|
refund.create_draft(command.order_id)
end
end
end

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

def call(command)
@repository.with_aggregate(Refund, command.aggregate_id) do |refund|
refund.add_item(command.product_id)
end
end
end

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

def call(command)
@repository.with_aggregate(Refund, command.aggregate_id) do |refund|
refund.remove_item(command.product_id)
end
end
end
end
41 changes: 41 additions & 0 deletions ecommerce/ordering/test/add_item_to_refund_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
require_relative "test_helper"

module Ordering
class AddItemToRefundTest < Test
cover "Ordering::OnAddItemToRefund*"

def test_add_item_to_refund
order_id = SecureRandom.uuid
aggregate_id = SecureRandom.uuid
product_id = SecureRandom.uuid
stream = "Ordering::Refund$#{aggregate_id}"

arrange(
CreateDraftRefund.new(
refund_id: aggregate_id,
order_id: order_id
)
)

expected_events = [
ItemAddedToRefund.new(
data: {
refund_id: aggregate_id,
order_id: order_id,
product_id: product_id
}
)
]

assert_events(stream, *expected_events) do
act(
AddItemToRefund.new(
refund_id: aggregate_id,
order_id: order_id,
product_id: product_id
)
)
end
end
end
end
31 changes: 31 additions & 0 deletions ecommerce/ordering/test/create_draft_refund_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
require_relative "test_helper"

module Ordering
class CreateDraftRefundTest < Test
cover "Ordering::OnCreateDraftRefund*"

def test_draft_refund_created
order_id = SecureRandom.uuid
aggregate_id = SecureRandom.uuid
stream = "Ordering::Refund$#{aggregate_id}"

expected_events = [
DraftRefundCreated.new(
data: {
refund_id: aggregate_id,
order_id: order_id
}
)
]

assert_events(stream, *expected_events) do
act(
CreateDraftRefund.new(
refund_id: aggregate_id,
order_id: order_id
)
)
end
end
end
end
69 changes: 69 additions & 0 deletions ecommerce/ordering/test/remove_item_from_refund_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
require_relative "test_helper"

module Ordering
class RemoveItemFromRefundTest < Test
cover "Ordering::OnRemoveItemFromRefund*"

def test_removing_items_from_refund
order_id = SecureRandom.uuid
aggregate_id = SecureRandom.uuid
product_id = SecureRandom.uuid
stream = "Ordering::Refund$#{aggregate_id}"

arrange(
CreateDraftRefund.new(
refund_id: aggregate_id,
order_id: order_id
),
AddItemToRefund.new(
refund_id: aggregate_id,
order_id: order_id,
product_id: product_id
)
)

expected_events = [
ItemRemovedFromRefund.new(
data: {
refund_id: aggregate_id,
order_id: order_id,
product_id: product_id
}
)
]

assert_events(stream, *expected_events) do
act(
RemoveItemFromRefund.new(
refund_id: aggregate_id,
order_id: order_id,
product_id: product_id
)
)
end
end

def test_can_remove_only_added_items
order_id = SecureRandom.uuid
aggregate_id = SecureRandom.uuid
product_id = SecureRandom.uuid

arrange(
CreateDraftRefund.new(
refund_id: aggregate_id,
order_id: order_id
)
)

assert_raises(Refund::ProductNotFoundError) do
act(
RemoveItemFromRefund.new(
refund_id: aggregate_id,
order_id: order_id,
product_id: product_id
)
)
end
end
end
end
49 changes: 45 additions & 4 deletions rails_application/app/controllers/refunds_controller.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,48 @@
class RefundsController < ApplicationController
def new
@order = Orders::Order.find_by_uid(params[:order_id])
@refund = Refunds::Refund.new
@order_lines = Orders::OrderLine.where(order_uid: params[:order_id])
def edit
@refund = Refunds::Refund.find_by_uid!(params[:id])
@order = Orders::Order.find_by_uid(@refund.order_uid)
@order_lines = @order.order_lines
end

def create
refund_id = SecureRandom.uuid
create_draft_refund(refund_id)

redirect_to edit_order_refund_path(refund_id, order_id: params[:order_id])
end

def add_item
add_item_to_refund
Copy link
Collaborator

Choose a reason for hiding this comment

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

I wonder if this abstraction brings any benefit. Do you think it is more readable this way?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Tbh I followed the same pattern as is in e.g. products_controller but it is not followed everywhere so it might also be done without this abstraction.

end

def remove_item
remove_item_from_refund
end

private

def create_draft_refund_cmd(refund_id)
Ordering::CreateDraftRefund.new(refund_id: refund_id, order_id: params[:order_id])
end

def create_draft_refund(refund_id)
command_bus.(create_draft_refund_cmd(refund_id))
end

def add_item_to_refund_cmd
Ordering::AddItemToRefund.new(refund_id: params[:id], order_id: params[:order_id], product_id: params[:product_id])
end

def add_item_to_refund
command_bus.(add_item_to_refund_cmd)
end

def remove_item_from_refund_cmd
Ordering::RemoveItemFromRefund.new(refund_id: params[:id], order_id: params[:order_id], product_id: params[:product_id])
end

def remove_item_from_refund
command_bus.(remove_item_from_refund_cmd)
end
end
Loading