diff --git a/ecommerce/ordering/.mutant.yml b/ecommerce/ordering/.mutant.yml index 1413b3146..6e1e61b06 100644 --- a/ecommerce/ordering/.mutant.yml +++ b/ecommerce/ordering/.mutant.yml @@ -11,4 +11,4 @@ matcher: - Ordering::Test* - Ordering::Configuration#call - Ordering::Configuration#initialize - - Ordering::RefundableProducts#call + - Ordering::ReturnableProducts#call diff --git a/ecommerce/ordering/lib/ordering.rb b/ecommerce/ordering/lib/ordering.rb index deda53e7e..4410dabc8 100644 --- a/ecommerce/ordering/lib/ordering.rb +++ b/ecommerce/ordering/lib/ordering.rb @@ -1,13 +1,13 @@ require "infra" -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/create_draft_refund" -require_relative "ordering/commands/add_item_to_refund" -require_relative "ordering/commands/remove_item_from_refund" +require_relative "ordering/events/draft_return_created" +require_relative "ordering/events/item_added_to_return" +require_relative "ordering/events/item_removed_from_return" +require_relative "ordering/commands/create_draft_return" +require_relative "ordering/commands/add_item_to_return" +require_relative "ordering/commands/remove_item_from_return" require_relative "ordering/service" -require_relative "ordering/refund" -require_relative "ordering/refundable_products" +require_relative "ordering/return" +require_relative "ordering/returnable_products" module Ordering class Configuration diff --git a/ecommerce/ordering/lib/ordering/commands/add_item_to_return.rb b/ecommerce/ordering/lib/ordering/commands/add_item_to_return.rb new file mode 100644 index 000000000..f8de8e592 --- /dev/null +++ b/ecommerce/ordering/lib/ordering/commands/add_item_to_return.rb @@ -0,0 +1,11 @@ +module Ordering + class AddItemToReturn < Infra::Command + attribute :return_id, Infra::Types::UUID + attribute :order_id, Infra::Types::UUID + attribute :product_id, Infra::Types::UUID + + alias aggregate_id return_id + end + + AddItemToRefund = AddItemToReturn +end diff --git a/ecommerce/ordering/lib/ordering/commands/create_draft_return.rb b/ecommerce/ordering/lib/ordering/commands/create_draft_return.rb new file mode 100644 index 000000000..32cf34ea4 --- /dev/null +++ b/ecommerce/ordering/lib/ordering/commands/create_draft_return.rb @@ -0,0 +1,10 @@ +module Ordering + class CreateDraftReturn < Infra::Command + attribute :return_id, Infra::Types::UUID + attribute :order_id, Infra::Types::UUID + + alias aggregate_id return_id + end + + CreateDraftRefund = CreateDraftReturn +end diff --git a/ecommerce/ordering/lib/ordering/commands/remove_item_from_return.rb b/ecommerce/ordering/lib/ordering/commands/remove_item_from_return.rb new file mode 100644 index 000000000..88f1328f0 --- /dev/null +++ b/ecommerce/ordering/lib/ordering/commands/remove_item_from_return.rb @@ -0,0 +1,11 @@ +module Ordering + class RemoveItemFromReturn < Infra::Command + attribute :return_id, Infra::Types::UUID + attribute :order_id, Infra::Types::UUID + attribute :product_id, Infra::Types::UUID + + alias aggregate_id return_id + end + + RemoveItemFromRefund = RemoveItemFromReturn +end diff --git a/ecommerce/ordering/lib/ordering/events/draft_return_created.rb b/ecommerce/ordering/lib/ordering/events/draft_return_created.rb new file mode 100644 index 000000000..b519845eb --- /dev/null +++ b/ecommerce/ordering/lib/ordering/events/draft_return_created.rb @@ -0,0 +1,14 @@ +module Ordering + class DraftReturnCreated < Infra::Event + attribute :return_id, Infra::Types::UUID + attribute :order_id, Infra::Types::UUID + attribute :returnable_products, Infra::Types::Array.of( + Infra::Types::Hash.schema( + product_id: Infra::Types::UUID, + quantity: Infra::Types::Integer + ) + ) + end + + DraftRefundCreated = DraftReturnCreated +end diff --git a/ecommerce/ordering/lib/ordering/events/item_added_to_return.rb b/ecommerce/ordering/lib/ordering/events/item_added_to_return.rb new file mode 100644 index 000000000..958a94473 --- /dev/null +++ b/ecommerce/ordering/lib/ordering/events/item_added_to_return.rb @@ -0,0 +1,9 @@ +module Ordering + class ItemAddedToReturn < Infra::Event + attribute :return_id, Infra::Types::UUID + attribute :order_id, Infra::Types::UUID + attribute :product_id, Infra::Types::UUID + end + + ItemAddedToRefund = ItemAddedToReturn +end diff --git a/ecommerce/ordering/lib/ordering/events/item_removed_from_return.rb b/ecommerce/ordering/lib/ordering/events/item_removed_from_return.rb new file mode 100644 index 000000000..449b93696 --- /dev/null +++ b/ecommerce/ordering/lib/ordering/events/item_removed_from_return.rb @@ -0,0 +1,9 @@ +module Ordering + class ItemRemovedFromReturn < Infra::Event + attribute :return_id, Infra::Types::UUID + attribute :order_id, Infra::Types::UUID + attribute :product_id, Infra::Types::UUID + end + + ItemRemovedFromRefund = ItemRemovedFromReturn +end diff --git a/ecommerce/ordering/lib/ordering/return.rb b/ecommerce/ordering/lib/ordering/return.rb new file mode 100644 index 000000000..a9ff94e44 --- /dev/null +++ b/ecommerce/ordering/lib/ordering/return.rb @@ -0,0 +1,75 @@ +module Ordering + class Return + include AggregateRoot + + ExceedsOrderQuantityError = Class.new(StandardError) + ReturnHaveNotBeenRequestedForThisProductError = Class.new(StandardError) + + def initialize(id) + @id = id + @return_items = ItemsList.new + end + + def create_draft(order_id, returnable_products) + apply DraftReturnCreated.new(data: { return_id: @id, order_id: order_id, returnable_products: returnable_products }) + end + + def add_item(product_id) + raise ExceedsOrderQuantityError unless enough_items?(product_id) + apply ItemAddedToReturn.new(data: { return_id: @id, order_id: @order_id, product_id: product_id }) + end + + def remove_item(product_id) + raise ReturnHaveNotBeenRequestedForThisProductError unless @return_items.quantity(product_id).positive? + apply ItemRemovedFromReturn.new(data: { return_id: @id, order_id: @order_id, product_id: product_id }) + end + + on DraftReturnCreated do |event| + @order_id = event.data[:order_id] + @returnable_products = event.data[:returnable_products] + end + + on ItemAddedToReturn do |event| + @return_items.increase_quantity(event.data[:product_id]) + end + + on ItemRemovedFromReturn do |event| + @return_items.decrease_quantity(event.data[:product_id]) + end + + private + + def enough_items?(product_id) + @return_items.quantity(product_id) < returnable_quantity(product_id) + end + + def returnable_quantity(product_id) + product = @returnable_products.find { |product| product.fetch(:product_id) == product_id } + product.fetch(:quantity) + end + end + + class ItemsList + attr_reader :return_items + + def initialize + @return_items = Hash.new(0) + end + + def increase_quantity(product_id) + return_items[product_id] = quantity(product_id) + 1 + end + + def decrease_quantity(product_id) + return_items[product_id] -= 1 + return_items.delete(product_id) if quantity(product_id).equal?(0) + end + + def quantity(product_id) + return_items[product_id] + end + end + + Refund = Return + RefundableProducts = ReturnableProducts if defined?(ReturnableProducts) +end diff --git a/ecommerce/ordering/lib/ordering/returnable_products.rb b/ecommerce/ordering/lib/ordering/returnable_products.rb new file mode 100644 index 000000000..430a70449 --- /dev/null +++ b/ecommerce/ordering/lib/ordering/returnable_products.rb @@ -0,0 +1,20 @@ +module Ordering + class ReturnableProducts + def call(event_store, order_id) + accepted_event = event_store + .read.backward + .stream("Pricing::Offer$#{order_id}") + .of_type(Pricing::OfferAccepted) + .first + + placed_event = event_store + .read + .stream("Fulfillment::Order$#{order_id}") + .first + + accepted_event.data.fetch(:order_lines) if placed_event + end + end + + RefundableProducts = ReturnableProducts +end diff --git a/ecommerce/ordering/lib/ordering/service.rb b/ecommerce/ordering/lib/ordering/service.rb index efa23c0dc..3f139dc46 100644 --- a/ecommerce/ordering/lib/ordering/service.rb +++ b/ecommerce/ordering/lib/ordering/service.rb @@ -1,47 +1,51 @@ module Ordering - class OnCreateDraftRefund + class OnCreateDraftReturn def initialize(event_store) @repository = Infra::AggregateRootRepository.new(event_store) @event_store = event_store end def call(command) - @repository.with_aggregate(Refund, command.aggregate_id) do |refund| - refund.create_draft( + @repository.with_aggregate(Return, command.aggregate_id) do |return_order| + return_order.create_draft( command.order_id, - refundable_products(command.order_id) + returnable_products(command.order_id) ) end end private - def refundable_products(order_id) - RefundableProducts.new.call(@event_store, order_id) + def returnable_products(order_id) + ReturnableProducts.new.call(@event_store, order_id) end end - class OnAddItemToRefund + class OnAddItemToReturn 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) + @repository.with_aggregate(Return, command.aggregate_id) do |return_order| + return_order.add_item(command.product_id) end end end - class OnRemoveItemFromRefund + class OnRemoveItemFromReturn 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) + @repository.with_aggregate(Return, command.aggregate_id) do |return_order| + return_order.remove_item(command.product_id) end end end + + OnCreateDraftRefund = OnCreateDraftReturn + OnAddItemToRefund = OnAddItemToReturn + OnRemoveItemFromRefund = OnRemoveItemFromReturn end diff --git a/ecommerce/ordering/test/add_item_to_refund_test.rb b/ecommerce/ordering/test/add_item_to_return_test.rb similarity index 74% rename from ecommerce/ordering/test/add_item_to_refund_test.rb rename to ecommerce/ordering/test/add_item_to_return_test.rb index b036bda9e..7ab6146e1 100644 --- a/ecommerce/ordering/test/add_item_to_refund_test.rb +++ b/ecommerce/ordering/test/add_item_to_return_test.rb @@ -1,16 +1,16 @@ require_relative "test_helper" module Ordering - class AddItemToRefundTest < Test - cover "Ordering::OnAddItemToRefund*" + class AddItemToReturnTest < Test + cover "Ordering::OnAddItemToReturn*" - def test_add_item_to_refund + def test_add_item_to_return order_id = SecureRandom.uuid aggregate_id = SecureRandom.uuid product_1_id = SecureRandom.uuid product_2_id = SecureRandom.uuid product_3_id = SecureRandom.uuid - stream = "Ordering::Refund$#{aggregate_id}" + stream = "Ordering::Return$#{aggregate_id}" arrange( Pricing::SetPrice.new(product_id: product_1_id, price: 11), @@ -22,18 +22,18 @@ def test_add_item_to_refund Pricing::AddPriceItem.new(order_id: order_id, product_id: product_3_id, price: 33), Pricing::AcceptOffer.new(order_id: order_id), Fulfillment::RegisterOrder.new(order_id: order_id), - CreateDraftRefund.new(refund_id: aggregate_id, order_id: order_id), - AddItemToRefund.new( - refund_id: aggregate_id, + CreateDraftReturn.new(return_id: aggregate_id, order_id: order_id), + AddItemToReturn.new( + return_id: aggregate_id, order_id: order_id, product_id: product_2_id ) ) expected_events = [ - ItemAddedToRefund.new( + ItemAddedToReturn.new( data: { - refund_id: aggregate_id, + return_id: aggregate_id, order_id: order_id, product_id: product_2_id } @@ -42,8 +42,8 @@ def test_add_item_to_refund assert_events(stream, *expected_events) do act( - AddItemToRefund.new( - refund_id: aggregate_id, + AddItemToReturn.new( + return_id: aggregate_id, order_id: order_id, product_id: product_2_id ) @@ -61,16 +61,16 @@ def test_add_item_raises_exceeds_order_quantity_error Pricing::AddPriceItem.new(order_id: order_id, product_id: product_id, price: 11), Pricing::AcceptOffer.new(order_id: order_id), Fulfillment::RegisterOrder.new(order_id: order_id), - CreateDraftRefund.new(refund_id: aggregate_id, order_id: order_id), - AddItemToRefund.new( - refund_id: aggregate_id, + CreateDraftReturn.new(return_id: aggregate_id, order_id: order_id), + AddItemToReturn.new( + return_id: aggregate_id, order_id: order_id, product_id: product_id ) ) - assert_raises(Ordering::Refund::ExceedsOrderQuantityError) do - act(AddItemToRefund.new(refund_id: aggregate_id, order_id: order_id, product_id: product_id)) + assert_raises(Ordering::Return::ExceedsOrderQuantityError) do + act(AddItemToReturn.new(return_id: aggregate_id, order_id: order_id, product_id: product_id)) end end end diff --git a/ecommerce/ordering/test/create_draft_refund_test.rb b/ecommerce/ordering/test/create_draft_return_test.rb similarity index 68% rename from ecommerce/ordering/test/create_draft_refund_test.rb rename to ecommerce/ordering/test/create_draft_return_test.rb index 837db0051..31ce3e4cf 100644 --- a/ecommerce/ordering/test/create_draft_refund_test.rb +++ b/ecommerce/ordering/test/create_draft_return_test.rb @@ -1,14 +1,14 @@ require_relative "test_helper" module Ordering - class CreateDraftRefundTest < Test - cover "Ordering::OnCreateDraftRefund*" + class CreateDraftReturnTest < Test + cover "Ordering::OnCreateDraftReturn*" - def test_draft_refund_created + def test_draft_return_created order_id = SecureRandom.uuid aggregate_id = SecureRandom.uuid product_id = SecureRandom.uuid - stream = "Ordering::Refund$#{aggregate_id}" + stream = "Ordering::Return$#{aggregate_id}" arrange( Pricing::SetPrice.new(product_id: product_id, price: 11), @@ -19,19 +19,19 @@ def test_draft_refund_created ) expected_events = [ - DraftRefundCreated.new( + DraftReturnCreated.new( data: { - refund_id: aggregate_id, + return_id: aggregate_id, order_id: order_id, - refundable_products: [{ product_id:, quantity: 2 }] + returnable_products: [{ product_id:, quantity: 2 }] } ) ] assert_events(stream, *expected_events) do act( - CreateDraftRefund.new( - refund_id: aggregate_id, + CreateDraftReturn.new( + return_id: aggregate_id, order_id: order_id ) ) diff --git a/ecommerce/ordering/test/remove_item_from_refund_test.rb b/ecommerce/ordering/test/remove_item_from_return_test.rb similarity index 73% rename from ecommerce/ordering/test/remove_item_from_refund_test.rb rename to ecommerce/ordering/test/remove_item_from_return_test.rb index 06fbeebff..f6c84f391 100644 --- a/ecommerce/ordering/test/remove_item_from_refund_test.rb +++ b/ecommerce/ordering/test/remove_item_from_return_test.rb @@ -1,16 +1,16 @@ require_relative "test_helper" module Ordering - class RemoveItemFromRefundTest < Test - cover "Ordering::OnRemoveItemFromRefund*" + class RemoveItemFromReturnTest < Test + cover "Ordering::OnRemoveItemFromReturn*" - def test_removing_items_from_refund + def test_removing_items_from_return order_id = SecureRandom.uuid aggregate_id = SecureRandom.uuid product_1_id = SecureRandom.uuid product_2_id = SecureRandom.uuid product_3_id = SecureRandom.uuid - stream = "Ordering::Refund$#{aggregate_id}" + stream = "Ordering::Return$#{aggregate_id}" arrange( Pricing::SetPrice.new(product_id: product_1_id, price: 11), @@ -22,21 +22,21 @@ def test_removing_items_from_refund Pricing::AddPriceItem.new(order_id: order_id, product_id: product_3_id, price: 33), Pricing::AcceptOffer.new(order_id: order_id), Fulfillment::RegisterOrder.new(order_id: order_id), - CreateDraftRefund.new( - refund_id: aggregate_id, + CreateDraftReturn.new( + return_id: aggregate_id, order_id: order_id ), - AddItemToRefund.new( - refund_id: aggregate_id, + AddItemToReturn.new( + return_id: aggregate_id, order_id: order_id, product_id: product_1_id ) ) expected_events = [ - ItemRemovedFromRefund.new( + ItemRemovedFromReturn.new( data: { - refund_id: aggregate_id, + return_id: aggregate_id, order_id: order_id, product_id: product_1_id } @@ -45,8 +45,8 @@ def test_removing_items_from_refund assert_events(stream, *expected_events) do act( - RemoveItemFromRefund.new( - refund_id: aggregate_id, + RemoveItemFromReturn.new( + return_id: aggregate_id, order_id: order_id, product_id: product_1_id ) @@ -64,26 +64,26 @@ def test_cant_remove_item_with_0_quantity Pricing::AddPriceItem.new(order_id: order_id, product_id: product_id, price: 11), Pricing::AcceptOffer.new(order_id: order_id), Fulfillment::RegisterOrder.new(order_id: order_id), - CreateDraftRefund.new( - refund_id: aggregate_id, + CreateDraftReturn.new( + return_id: aggregate_id, order_id: order_id ), - AddItemToRefund.new( - refund_id: aggregate_id, + AddItemToReturn.new( + return_id: aggregate_id, order_id: order_id, product_id: product_id ), - RemoveItemFromRefund.new( - refund_id: aggregate_id, + RemoveItemFromReturn.new( + return_id: aggregate_id, order_id: order_id, product_id: product_id ) ) - assert_raises(Refund::RefundHaveNotBeenRequestedForThisProductError) do + assert_raises(Return::ReturnHaveNotBeenRequestedForThisProductError) do act( - RemoveItemFromRefund.new( - refund_id: aggregate_id, + RemoveItemFromReturn.new( + return_id: aggregate_id, order_id: order_id, product_id: product_id ) diff --git a/ecommerce/ordering/test/refund_items_list_test.rb b/ecommerce/ordering/test/return_items_list_test.rb similarity index 73% rename from ecommerce/ordering/test/refund_items_list_test.rb rename to ecommerce/ordering/test/return_items_list_test.rb index f9592c778..426ed287e 100644 --- a/ecommerce/ordering/test/refund_items_list_test.rb +++ b/ecommerce/ordering/test/return_items_list_test.rb @@ -1,12 +1,12 @@ require_relative "test_helper" module Ordering - class RefundItemsListTest < Test + class ReturnItemsListTest < Test def test_initialize list = ItemsList.new - assert_equal 0, list.refund_items.size + assert_equal 0, list.return_items.size end def test_increase_item_quantity @@ -16,12 +16,12 @@ def test_increase_item_quantity list.increase_quantity(product_one_id) - assert_equal 1, list.refund_items.size + assert_equal 1, list.return_items.size assert_equal 1, list.quantity(product_one_id) list.increase_quantity(product_two_id) - assert_equal 2, list.refund_items.size + assert_equal 2, list.return_items.size assert_equal 1, list.quantity(product_two_id) end @@ -32,17 +32,17 @@ def test_decrease_item_quantity list.increase_quantity(product_id) list.increase_quantity(product_id) - assert_equal 1, list.refund_items.size + assert_equal 1, list.return_items.size assert_equal 2, list.quantity(product_id) list.decrease_quantity(product_id) - assert_equal 1, list.refund_items.size + assert_equal 1, list.return_items.size assert_equal 1, list.quantity(product_id) list.decrease_quantity(product_id) - assert_equal 0, list.refund_items.size + assert_equal 0, list.return_items.size end end end diff --git a/ecommerce/ordering/test/refundable_products_test.rb b/ecommerce/ordering/test/returnable_products_test.rb similarity index 86% rename from ecommerce/ordering/test/refundable_products_test.rb rename to ecommerce/ordering/test/returnable_products_test.rb index 72b8676f6..9750250a5 100644 --- a/ecommerce/ordering/test/refundable_products_test.rb +++ b/ecommerce/ordering/test/returnable_products_test.rb @@ -1,10 +1,10 @@ require_relative "test_helper" module Ordering - class RefundableProductsTest < Test - cover "Ordering::RefundableProducts" + class ReturnableProductsTest < Test + cover "Ordering::ReturnableProducts" - def test_product_quantity_available_to_refund + def test_product_quantity_available_to_return order_id = SecureRandom.uuid order_number = Fulfillment::FakeNumberGenerator::FAKE_NUMBER product_1_id = SecureRandom.uuid @@ -42,9 +42,9 @@ def test_product_quantity_available_to_refund stream_name: "Fulfillment::Order$#{order_id}" ) - refundable_products = RefundableProducts.new.call(event_store, order_id) + returnable_products = ReturnableProducts.new.call(event_store, order_id) - assert_equal([{ product_id: product_1_id, quantity: 1 }, { product_id: product_2_id, quantity: 1 }], refundable_products) + assert_equal([{ product_id: product_1_id, quantity: 1 }, { product_id: product_2_id, quantity: 1 }], returnable_products) end def test_order_have_to_be_placed @@ -66,7 +66,7 @@ def test_order_have_to_be_placed ], stream_name: stream_name) - assert_nil(RefundableProducts.new.call(event_store, order_id)) + assert_nil(ReturnableProducts.new.call(event_store, order_id)) end end end diff --git a/infra/lib/infra/event_store.rb b/infra/lib/infra/event_store.rb index 3c84fc31e..948da045c 100644 --- a/infra/lib/infra/event_store.rb +++ b/infra/lib/infra/event_store.rb @@ -1,7 +1,27 @@ module Infra class EventStore < SimpleDelegator def self.main - new(RailsEventStore::JSONClient.new) + require_relative "../../../rails_application/lib/transformations/refund_to_return_event_mapper" rescue nil + + if defined?(Transformations::RefundToReturnEventMapper) + mapper = RubyEventStore::Mappers::PipelineMapper.new( + RubyEventStore::Mappers::Pipeline.new( + Transformations::RefundToReturnEventMapper.new( + 'Ordering::DraftRefundCreated' => 'Ordering::DraftReturnCreated', + 'Ordering::ItemAddedToRefund' => 'Ordering::ItemAddedToReturn', + 'Ordering::ItemRemovedFromRefund' => 'Ordering::ItemRemovedFromReturn' + ), + RubyEventStore::Mappers::Transformation::DomainEvent.new, + RubyEventStore::Mappers::Transformation::SymbolizeMetadataKeys.new, + RubyEventStore::Mappers::Transformation::PreserveTypes.new + ) + ) + client = RailsEventStore::JSONClient.new(mapper: mapper) + else + client = RailsEventStore::JSONClient.new + end + + new(client) end def self.in_memory @@ -31,5 +51,6 @@ def link_event_to_stream(event, stream, expected_version: :any) expected_version: expected_version ) end + end end diff --git a/rails_application/app/controllers/refunds_controller.rb b/rails_application/app/controllers/refunds_controller.rb deleted file mode 100644 index 02d5c18a3..000000000 --- a/rails_application/app/controllers/refunds_controller.rb +++ /dev/null @@ -1,71 +0,0 @@ -class RefundsController < ApplicationController - def edit - @refund = Refunds::Refund.find_by_uid!(params[:id]) - @order = Orders::Order.find_by_uid!(@refund.order_uid) - @refund_items = build_refund_items_list(@order.order_lines, @refund.refund_items) - 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 - redirect_to edit_order_refund_path(params[:id], order_id: params[:order_id]) - rescue Ordering::Refund::ExceedsOrderQuantityError - flash[:alert] = "You cannot add more of this product to the refund than is in the original order." - redirect_to edit_order_refund_path(params[:id], order_id: params[:order_id]) - end - - def remove_item - remove_item_from_refund - redirect_to edit_order_refund_path(params[:id], order_id: params[:order_id]) - rescue Ordering::Refund::RefundHaveNotBeenRequestedForThisProductError - flash[:alert] = "This product is not added to the refund." - redirect_to edit_order_refund_path(params[:id], order_id: params[:order_id]) - 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 - - def build_refund_items_list(order_lines, refund_items) - order_lines.map { |order_line| build_refund_item(order_line, refund_items) } - end - - def build_refund_item(order_line, refund_items) - refund_item = refund_items.find { |item| item.product_uid == order_line.product_id } || initialize_refund_item(order_line) - - refund_item.order_line = order_line - refund_item - end - - def initialize_refund_item(order_line) - Refunds::RefundItem.new(product_uid: order_line.product_id, quantity: 0, price: order_line.price) - end -end diff --git a/rails_application/app/controllers/returns_controller.rb b/rails_application/app/controllers/returns_controller.rb new file mode 100644 index 000000000..bfe72e88c --- /dev/null +++ b/rails_application/app/controllers/returns_controller.rb @@ -0,0 +1,71 @@ +class ReturnsController < ApplicationController + def edit + @return = Returns::Return.find_by_uid!(params[:id]) + @order = Orders::Order.find_by_uid!(@return.order_uid) + @return_items = build_return_items_list(@order.order_lines, @return.return_items) + end + + def create + return_id = SecureRandom.uuid + create_draft_return(return_id) + + redirect_to edit_order_return_path(return_id, order_id: params[:order_id]) + end + + def add_item + add_item_to_return + redirect_to edit_order_return_path(params[:id], order_id: params[:order_id]) + rescue Ordering::Return::ExceedsOrderQuantityError + flash[:alert] = "You cannot add more of this product to the return than is in the original order." + redirect_to edit_order_return_path(params[:id], order_id: params[:order_id]) + end + + def remove_item + remove_item_from_return + redirect_to edit_order_return_path(params[:id], order_id: params[:order_id]) + rescue Ordering::Return::ReturnHaveNotBeenRequestedForThisProductError + flash[:alert] = "This product is not added to the return." + redirect_to edit_order_return_path(params[:id], order_id: params[:order_id]) + end + + private + + def create_draft_return_cmd(return_id) + Ordering::CreateDraftReturn.new(return_id: return_id, order_id: params[:order_id]) + end + + def create_draft_return(return_id) + command_bus.(create_draft_return_cmd(return_id)) + end + + def add_item_to_return_cmd + Ordering::AddItemToReturn.new(return_id: params[:id], order_id: params[:order_id], product_id: params[:product_id]) + end + + def add_item_to_return + command_bus.(add_item_to_return_cmd) + end + + def remove_item_from_return_cmd + Ordering::RemoveItemFromReturn.new(return_id: params[:id], order_id: params[:order_id], product_id: params[:product_id]) + end + + def remove_item_from_return + command_bus.(remove_item_from_return_cmd) + end + + def build_return_items_list(order_lines, return_items) + order_lines.map { |order_line| build_return_item(order_line, return_items) } + end + + def build_return_item(order_line, return_items) + return_item = return_items.find { |item| item.product_uid == order_line.product_id } || initialize_return_item(order_line) + + return_item.order_line = order_line + return_item + end + + def initialize_return_item(order_line) + Returns::ReturnItem.new(product_uid: order_line.product_id, quantity: 0, price: order_line.price) + end +end diff --git a/rails_application/app/processes/processes/reservation_process.rb b/rails_application/app/processes/processes/reservation_process.rb index 6c7a20e3a..56e4fee56 100644 --- a/rails_application/app/processes/processes/reservation_process.rb +++ b/rails_application/app/processes/processes/reservation_process.rb @@ -30,9 +30,14 @@ def act def apply(event) case event when Pricing::OfferAccepted + puts "[DEBUG] OfferAccepted event.data: #{event.data.inspect}" + puts "[DEBUG] event.data.keys: #{event.data.keys.inspect}" + puts "[DEBUG] event.data[:order_lines]: #{event.data[:order_lines].inspect}" + order_lines_hash = event.data.fetch(:order_lines).map { |ol| [ol.fetch(:product_id), ol.fetch(:quantity)] }.to_h + puts "[DEBUG] Created order_lines_hash: #{order_lines_hash.inspect}" state.with( order: :accepted, - order_lines: event.data.fetch(:order_lines).map { |ol| [ol.fetch(:product_id), ol.fetch(:quantity)] }.to_h + order_lines: order_lines_hash ) when Fulfillment::OrderCancelled state.with(order: :cancelled) diff --git a/rails_application/app/read_models/refunds/add_item_to_refund.rb b/rails_application/app/read_models/refunds/add_item_to_refund.rb deleted file mode 100644 index c33c19e69..000000000 --- a/rails_application/app/read_models/refunds/add_item_to_refund.rb +++ /dev/null @@ -1,21 +0,0 @@ -module Refunds - class AddItemToRefund - def call(event) - refund = Refund.find_by!(uid: event.data.fetch(:refund_id)) - product = Orders::Product.find_by!(uid: event.data.fetch(:product_id)) - - item = refund.refund_items.find_or_create_by(product_uid: product.uid) do |item| - item.price = product.price - item.quantity = 0 - end - - refund.total_value += item.price - item.quantity += 1 - - ActiveRecord::Base.transaction do - refund.save! - item.save! - end - end - end -end diff --git a/rails_application/app/read_models/refunds/configuration.rb b/rails_application/app/read_models/refunds/configuration.rb deleted file mode 100644 index 763fd7600..000000000 --- a/rails_application/app/read_models/refunds/configuration.rb +++ /dev/null @@ -1,37 +0,0 @@ -module Refunds - class Refund < ApplicationRecord - self.table_name = "refunds" - - has_many :refund_items, - class_name: "Refunds::RefundItem", - foreign_key: :refund_uid, - primary_key: :uid - end - - class RefundItem < ApplicationRecord - self.table_name = "refund_items" - - attr_accessor :order_line - delegate :product_name, to: :order_line - - def max_quantity? - quantity == order_quantity - end - - def order_quantity - order_line.quantity - end - - def value - quantity * price - end - end - - class Configuration - def call(event_store) - event_store.subscribe(CreateDraftRefund.new, to: [Ordering::DraftRefundCreated]) - event_store.subscribe(AddItemToRefund.new, to: [Ordering::ItemAddedToRefund]) - event_store.subscribe(RemoveItemFromRefund.new, to: [Ordering::ItemRemovedFromRefund]) - end - end -end diff --git a/rails_application/app/read_models/refunds/create_draft_refund.rb b/rails_application/app/read_models/refunds/create_draft_refund.rb deleted file mode 100644 index be0e9cd01..000000000 --- a/rails_application/app/read_models/refunds/create_draft_refund.rb +++ /dev/null @@ -1,7 +0,0 @@ -module Refunds - class CreateDraftRefund - def call(event) - Refund.create!(uid: event.data[:refund_id], order_uid: event.data[:order_id], status: "Draft", total_value: 0) - end - end -end diff --git a/rails_application/app/read_models/refunds/remove_item_from_refund.rb b/rails_application/app/read_models/refunds/remove_item_from_refund.rb deleted file mode 100644 index ce1e775c9..000000000 --- a/rails_application/app/read_models/refunds/remove_item_from_refund.rb +++ /dev/null @@ -1,16 +0,0 @@ -module Refunds - class RemoveItemFromRefund - def call(event) - refund = Refund.find_by!(uid: event.data.fetch(:refund_id)) - item = refund.refund_items.find_by!(product_uid: event.data.fetch(:product_id)) - - refund.total_value -= item.price - item.quantity -= 1 - - ActiveRecord::Base.transaction do - refund.save! - item.quantity > 0 ? item.save! : item.destroy! - end - end - end -end diff --git a/rails_application/app/read_models/returns/add_item_to_return.rb b/rails_application/app/read_models/returns/add_item_to_return.rb new file mode 100644 index 000000000..acc8a75f7 --- /dev/null +++ b/rails_application/app/read_models/returns/add_item_to_return.rb @@ -0,0 +1,24 @@ +module Returns + class AddItemToReturn + def call(event) + return_record = Return.find_by!(uid: event.data.fetch(:return_id)) + product = Orders::Product.find_by!(uid: event.data.fetch(:product_id)) + + item = return_record.return_items.find_or_create_by(product_uid: product.uid) do |item| + item.price = product.price + item.quantity = 0 + end + + return_record.total_value += item.price + item.quantity += 1 + + ActiveRecord::Base.transaction do + return_record.save! + item.save! + end + end + end + + # Backward compatibility alias + AddItemToRefund = AddItemToReturn +end diff --git a/rails_application/app/read_models/returns/configuration.rb b/rails_application/app/read_models/returns/configuration.rb new file mode 100644 index 000000000..ca13cdd11 --- /dev/null +++ b/rails_application/app/read_models/returns/configuration.rb @@ -0,0 +1,45 @@ +module Returns + class Return < ApplicationRecord + self.table_name = "returns" + + has_many :return_items, + class_name: "Returns::ReturnItem", + foreign_key: :return_uid, + primary_key: :uid + end + + class ReturnItem < ApplicationRecord + self.table_name = "return_items" + + attr_accessor :order_line + delegate :product_name, to: :order_line + + def max_quantity? + quantity == order_quantity + end + + def order_quantity + order_line.quantity + end + + def value + quantity * price + end + end + + class Configuration + def call(event_store) + event_store.subscribe(CreateDraftReturn.new, to: [Ordering::DraftReturnCreated]) + event_store.subscribe(AddItemToReturn.new, to: [Ordering::ItemAddedToReturn]) + event_store.subscribe(RemoveItemFromReturn.new, to: [Ordering::ItemRemovedFromReturn]) + + event_store.subscribe(CreateDraftRefund.new, to: [Ordering::DraftRefundCreated]) + event_store.subscribe(AddItemToRefund.new, to: [Ordering::ItemAddedToRefund]) + event_store.subscribe(RemoveItemFromRefund.new, to: [Ordering::ItemRemovedFromRefund]) + end + end + + # Backward compatibility aliases + Refund = Return + RefundItem = ReturnItem +end diff --git a/rails_application/app/read_models/returns/create_draft_return.rb b/rails_application/app/read_models/returns/create_draft_return.rb new file mode 100644 index 000000000..e6002db36 --- /dev/null +++ b/rails_application/app/read_models/returns/create_draft_return.rb @@ -0,0 +1,10 @@ +module Returns + class CreateDraftReturn + def call(event) + Return.create!(uid: event.data[:return_id], order_uid: event.data[:order_id], status: "Draft", total_value: 0) + end + end + + # Backward compatibility alias + CreateDraftRefund = CreateDraftReturn +end diff --git a/rails_application/app/read_models/returns/remove_item_from_return.rb b/rails_application/app/read_models/returns/remove_item_from_return.rb new file mode 100644 index 000000000..c19a75cbe --- /dev/null +++ b/rails_application/app/read_models/returns/remove_item_from_return.rb @@ -0,0 +1,19 @@ +module Returns + class RemoveItemFromReturn + def call(event) + return_record = Return.find_by!(uid: event.data.fetch(:return_id)) + item = return_record.return_items.find_by!(product_uid: event.data.fetch(:product_id)) + + return_record.total_value -= item.price + item.quantity -= 1 + + ActiveRecord::Base.transaction do + return_record.save! + item.quantity > 0 ? item.save! : item.destroy! + end + end + end + + # Backward compatibility alias + RemoveItemFromRefund = RemoveItemFromReturn +end diff --git a/rails_application/app/views/orders/show.html.erb b/rails_application/app/views/orders/show.html.erb index 43659d24e..0f46fcbce 100644 --- a/rails_application/app/views/orders/show.html.erb +++ b/rails_application/app/views/orders/show.html.erb @@ -24,7 +24,7 @@ <% end %> <% if @order.state == "Paid" %> - <%= button_to("Refund", order_refunds_path(order_id: @order.uid), class: "ml-3 inline-flex items-center px-4 py-2 border rounded-md shadow-sm text-sm font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-50 border-gray-300 text-gray-700 bg-white hover:bg-gray-50") %> + <%= button_to("Return", order_returns_path(order_id: @order.uid), class: "ml-3 inline-flex items-center px-4 py-2 border rounded-md shadow-sm text-sm font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-50 border-gray-300 text-gray-700 bg-white hover:bg-gray-50") %> <% end %> <% if (@order.state == "Submitted") %> diff --git a/rails_application/app/views/refunds/edit.html.erb b/rails_application/app/views/returns/edit.html.erb similarity index 54% rename from rails_application/app/views/refunds/edit.html.erb rename to rails_application/app/views/returns/edit.html.erb index 57747b087..b1b02a400 100644 --- a/rails_application/app/views/refunds/edit.html.erb +++ b/rails_application/app/views/returns/edit.html.erb @@ -1,5 +1,5 @@ <% content_for(:header) do %> - Refund for Order <%= @order.number %> + Return for Order <%= @order.number %> <% end %> <% content_for(:actions) do %> @@ -8,7 +8,7 @@ <% end %> <%= primary_form_action_button do %> - Submit Refund + Submit Return <% end %> <% end %> @@ -25,20 +25,20 @@ - <% @refund_items.each do |refund_item| %> - - <%= refund_item.product_name %> - <%= refund_item.quantity %> / <%= refund_item.order_quantity %> - <%= number_to_currency(refund_item.price) %> - <%= number_to_currency(refund_item.value) %> + <% @return_items.each do |return_item| %> + + <%= return_item.product_name %> + <%= return_item.quantity %> / <%= return_item.order_quantity %> + <%= number_to_currency(return_item.price) %> + <%= number_to_currency(return_item.value) %> - <% unless refund_item.max_quantity? %> - <%= button_to "Add", add_item_order_refund_path(order_id: @order.uid, id: @refund.uid, product_id: refund_item.product_uid), class: "hover:underline text-blue-500" %> + <% unless return_item.max_quantity? %> + <%= button_to "Add", add_item_order_return_path(order_id: @order.uid, id: @return.uid, product_id: return_item.product_uid), class: "hover:underline text-blue-500" %> <% end %> - <% unless refund_item.quantity.zero? %> - <%= button_to "Remove", remove_item_order_refund_path(order_id: @order.uid, id: @refund.uid, product_id: refund_item.product_uid), class: "hover:underline text-blue-500" %> + <% unless return_item.quantity.zero? %> + <%= button_to "Remove", remove_item_order_return_path(order_id: @order.uid, id: @return.uid, product_id: return_item.product_uid), class: "hover:underline text-blue-500" %> <% end %> @@ -47,7 +47,7 @@ Total - <%= number_to_currency(@refund.total_value) %> + <%= number_to_currency(@return.total_value) %> diff --git a/rails_application/config/routes.rb b/rails_application/config/routes.rb index af38e443a..4cb621fe4 100644 --- a/rails_application/config/routes.rb +++ b/rails_application/config/routes.rb @@ -17,7 +17,7 @@ resource :shipping_address, only: [:edit, :update] resource :billing_address, only: [:edit, :update] resource :invoice, only: [:create] - resources :refunds, only: [:edit, :create] do + resources :returns, only: [:edit, :create] do member do post :add_item post :remove_item diff --git a/rails_application/db/migrate/20250908135920_rename_refunds_to_returns.rb b/rails_application/db/migrate/20250908135920_rename_refunds_to_returns.rb new file mode 100644 index 000000000..96f29b032 --- /dev/null +++ b/rails_application/db/migrate/20250908135920_rename_refunds_to_returns.rb @@ -0,0 +1,8 @@ +class RenameRefundsToReturns < ActiveRecord::Migration[7.2] + def change + rename_table :refunds, :returns + rename_table :refund_items, :return_items + + rename_column :return_items, :refund_uid, :return_uid + end +end diff --git a/rails_application/db/schema.rb b/rails_application/db/schema.rb index 700f7a31d..db3b35d91 100644 --- a/rails_application/db/schema.rb +++ b/rails_application/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.2].define(version: 2025_05_07_182202) do +ActiveRecord::Schema[7.2].define(version: 2025_09_08_135920) do # These are extensions that must be enabled in order to support this database enable_extension "pgcrypto" enable_extension "plpgsql" @@ -204,8 +204,8 @@ t.decimal "lowest_recent_price", precision: 8, scale: 2 end - create_table "refund_items", force: :cascade do |t| - t.uuid "refund_uid", null: false + create_table "return_items", force: :cascade do |t| + t.uuid "return_uid", null: false t.uuid "product_uid", null: false t.integer "quantity", null: false t.decimal "price", precision: 8, scale: 2, null: false @@ -213,7 +213,7 @@ t.datetime "updated_at", null: false end - create_table "refunds", force: :cascade do |t| + create_table "returns", force: :cascade do |t| t.uuid "uid", null: false t.uuid "order_uid", null: false t.string "status", null: false diff --git a/rails_application/lib/configuration.rb b/rails_application/lib/configuration.rb index 638881993..4ff1f16cb 100644 --- a/rails_application/lib/configuration.rb +++ b/rails_application/lib/configuration.rb @@ -18,7 +18,7 @@ def call(event_store, command_bus) enable_availability_read_model(event_store) enable_authentication_read_model(event_store) enable_vat_rates_read_model(event_store) - enable_refunds_read_model(event_store) + enable_returns_read_model(event_store) configure_processes(event_store, command_bus) Ecommerce::Configuration.new( @@ -89,8 +89,8 @@ def enable_vat_rates_read_model(event_store) VatRates::Configuration.new.call(event_store) end - def enable_refunds_read_model(event_store) - Refunds::Configuration.new.call(event_store) + def enable_returns_read_model(event_store) + Returns::Configuration.new.call(event_store) end def configure_processes(event_store, command_bus) diff --git a/rails_application/lib/transformations/refund_to_return_event_mapper.rb b/rails_application/lib/transformations/refund_to_return_event_mapper.rb new file mode 100644 index 000000000..aa455a8e7 --- /dev/null +++ b/rails_application/lib/transformations/refund_to_return_event_mapper.rb @@ -0,0 +1,54 @@ +module Transformations + class RefundToReturnEventMapper + def initialize(class_map) + @class_map = class_map + end + + def dump(record) + record + end + + def load(record) + old_class_name = record.event_type + new_class_name = @class_map.fetch(old_class_name, old_class_name) + + if old_class_name != new_class_name + transformed_data = transform_payload(record.data, old_class_name) + record.class.new( + event_id: record.event_id, + event_type: new_class_name, + data: transformed_data, + metadata: record.metadata, + timestamp: record.timestamp || Time.now.utc, + valid_at: record.valid_at || Time.now.utc + ) + else + record + end + end + + private + + def transform_payload(data, old_class_name) + case old_class_name + when 'Ordering::DraftRefundCreated' + data = transform_refund_to_return_payload(data, :refund_id, :return_id) + transform_refund_to_return_payload(data, :refundable_products, :returnable_products) + when 'Ordering::ItemAddedToRefund', 'Ordering::ItemRemovedFromRefund' + transform_refund_to_return_payload(data, :refund_id, :return_id) + else + data + end + end + + def transform_refund_to_return_payload(data, old_key, new_key) + if data.key?(old_key) + data_copy = data.dup + data_copy[new_key] = data_copy.delete(old_key) + data_copy + else + data + end + end + end +end diff --git a/rails_application/test/integration/refunds_test.rb b/rails_application/test/integration/returns_test.rb similarity index 73% rename from rails_application/test/integration/refunds_test.rb rename to rails_application/test/integration/returns_test.rb index b46ff1338..6a39b66a7 100644 --- a/rails_application/test/integration/refunds_test.rb +++ b/rails_application/test/integration/returns_test.rb @@ -1,6 +1,6 @@ require "test_helper" -class RefundsTest < InMemoryRESIntegrationTestCase +class ReturnsTest < InMemoryRESIntegrationTestCase def setup super add_available_vat_rate(10) @@ -20,16 +20,16 @@ def test_happy_path get "/orders/#{order_id}" - assert_select("button", "Refund") + assert_select("button", "Return") - post "/orders/#{order_id}/refunds" + post "/orders/#{order_id}/returns" follow_redirect! assert_order_line_row(async_remote_id, "Async Remote", 1) assert_order_line_row(fearless_id, "Fearless Refactoring", 2) end - def test_renders_error_when_exceeds_available_quantity_to_refund + def test_renders_error_when_exceeds_available_quantity_to_return shopify_id = register_customer("Shopify") order_id = SecureRandom.uuid async_remote_id = register_product("Async Remote", 39, 10) @@ -38,16 +38,16 @@ def test_renders_error_when_exceeds_available_quantity_to_refund submit_order(shopify_id, order_id) pay_order(order_id) - post "/orders/#{order_id}/refunds" + post "/orders/#{order_id}/returns" follow_redirect! - refund = Refunds::Refund.last + return_record = Returns::Return.last - add_item_to_refund(order_id, refund.uid, async_remote_id) - add_item_to_refund(order_id, refund.uid, async_remote_id) + add_item_to_return(order_id, return_record.uid, async_remote_id) + add_item_to_return(order_id, return_record.uid, async_remote_id) follow_redirect! - assert_select("#alert", "You cannot add more of this product to the refund than is in the original order.") + assert_select("#alert", "You cannot add more of this product to the return than is in the original order.") end def test_renders_error_when_trying_to_remove_not_added_product @@ -59,15 +59,15 @@ def test_renders_error_when_trying_to_remove_not_added_product submit_order(shopify_id, order_id) pay_order(order_id) - post "/orders/#{order_id}/refunds" + post "/orders/#{order_id}/returns" follow_redirect! - refund = Refunds::Refund.last + return_record = Returns::Return.last - remove_item_from_refund(order_id, refund.uid, async_remote_id) + remove_item_from_return(order_id, return_record.uid, async_remote_id) follow_redirect! - assert_select("#alert", "This product is not added to the refund.") + assert_select("#alert", "This product is not added to the return.") end private diff --git a/rails_application/test/orders/item_added_to_refund_test.rb b/rails_application/test/orders/item_added_to_return_test.rb similarity index 60% rename from rails_application/test/orders/item_added_to_refund_test.rb rename to rails_application/test/orders/item_added_to_return_test.rb index 756922b96..04c027ca8 100644 --- a/rails_application/test/orders/item_added_to_refund_test.rb +++ b/rails_application/test/orders/item_added_to_return_test.rb @@ -1,28 +1,28 @@ require "test_helper" -module Refunds - class ItemAddedToRefundTest < InMemoryTestCase +module Returns + class ItemAddedToReturnTest < InMemoryTestCase cover "Orders*" - def test_add_item_to_refund - refund_id = SecureRandom.uuid + def test_add_item_to_return + return_id = SecureRandom.uuid product_id = SecureRandom.uuid order_id = SecureRandom.uuid prepare_product(product_id, 50) place_order(order_id, product_id, 50) - create_draft_refund(refund_id, order_id) + create_draft_return(return_id, order_id) - AddItemToRefund.new.call(item_added_to_refund(refund_id, order_id, product_id)) + AddItemToReturn.new.call(item_added_to_return(return_id, order_id, product_id)) - assert_equal(1, Refunds::RefundItem.count) - refund_item = Refunds::RefundItem.find_by(refund_uid: refund_id, product_uid: product_id) - assert_equal(product_id, refund_item.product_uid) - assert_equal(1, refund_item.quantity) - assert_equal(50, refund_item.price) + assert_equal(1, Returns::ReturnItem.count) + return_item = Returns::ReturnItem.find_by(return_uid: return_id, product_uid: product_id) + assert_equal(product_id, return_item.product_uid) + assert_equal(1, return_item.quantity) + assert_equal(50, return_item.price) - assert_equal(1, Refunds::Refund.count) - refund = Refunds::Refund.find_by(uid: refund_id) - assert_equal("Draft", refund.status) + assert_equal(1, Returns::Return.count) + return_record = Returns::Return.find_by(uid: return_id) + assert_equal("Draft", return_record.status) end private @@ -70,15 +70,15 @@ def place_order(order_id, product_id, price) ) end - def create_draft_refund(refund_id, order_id) - draft_refund_created = Ordering::DraftRefundCreated.new( - data: { refund_id: refund_id, order_id: order_id, refundable_products: [] } + def create_draft_return(return_id, order_id) + draft_return_created = Ordering::DraftReturnCreated.new( + data: { return_id: return_id, order_id: order_id, returnable_products: [] } ) - CreateDraftRefund.new.call(draft_refund_created) + CreateDraftReturn.new.call(draft_return_created) end - def item_added_to_refund(refund_id, order_id, product_id) - Ordering::ItemAddedToRefund.new(data: { refund_id: refund_id, order_id: order_id, product_id: product_id }) + def item_added_to_return(return_id, order_id, product_id) + Ordering::ItemAddedToReturn.new(data: { return_id: return_id, order_id: order_id, product_id: product_id }) end end end diff --git a/rails_application/test/orders/item_removed_from_refund_test.rb b/rails_application/test/orders/item_removed_from_refund_test.rb deleted file mode 100644 index 538a7957d..000000000 --- a/rails_application/test/orders/item_removed_from_refund_test.rb +++ /dev/null @@ -1,70 +0,0 @@ -require "test_helper" - -module Refunds - class ItemRemovedFromRefundTest < InMemoryTestCase - cover "Orders*" - - def test_remove_item_from_refund - refund_id = SecureRandom.uuid - product_id = SecureRandom.uuid - another_product_id = SecureRandom.uuid - order_id = SecureRandom.uuid - refundable_products = [{product_id: product_id, quantity: 1}, {product_id: another_product_id, quantity: 1}] - create_draft_refund(refund_id, order_id, refundable_products) - prepare_product(product_id, 50) - prepare_product(another_product_id, 30) - AddItemToRefund.new.call(item_added_to_refund(refund_id, order_id, product_id)) - AddItemToRefund.new.call(item_added_to_refund(refund_id, order_id, another_product_id)) - - RemoveItemFromRefund.new.call(item_removed_from_refund(refund_id, order_id, product_id)) - - assert_equal(1, Refunds::RefundItem.count) - refund_item = Refunds::RefundItem.find_by(refund_uid: refund_id, product_uid: another_product_id) - assert_equal(another_product_id, refund_item.product_uid) - assert_equal(1, refund_item.quantity) - assert_equal(30, refund_item.price) - - assert_equal(1, Refunds::Refund.count) - refund = Refunds::Refund.find_by(uid: refund_id) - assert_equal("Draft", refund.status) - end - - private - - def create_draft_refund(refund_id, order_id, refundable_products) - draft_refund_created = Ordering::DraftRefundCreated.new(data: { refund_id: refund_id, order_id: order_id, refundable_products: refundable_products }) - CreateDraftRefund.new.call(draft_refund_created) - end - - def prepare_product(product_id, price) - event_store.publish( - ProductCatalog::ProductRegistered.new( - data: { - product_id: product_id - } - ) - ) - event_store.publish( - ProductCatalog::ProductNamed.new( - data: { - product_id: product_id, - name: "Async Remote" - } - ) - ) - event_store.publish(Pricing::PriceSet.new(data: { product_id: product_id, price: price })) - end - - def event_store - Rails.configuration.event_store - end - - def item_added_to_refund(refund_id, order_id, product_id) - Ordering::ItemAddedToRefund.new(data: { refund_id: refund_id, order_id: order_id, product_id: product_id }) - end - - def item_removed_from_refund(refund_id, order_id, product_id) - Ordering::ItemRemovedFromRefund.new(data: { refund_id: refund_id, order_id: order_id, product_id: product_id }) - end - end -end diff --git a/rails_application/test/orders/item_removed_from_return_test.rb b/rails_application/test/orders/item_removed_from_return_test.rb new file mode 100644 index 000000000..554b5f89e --- /dev/null +++ b/rails_application/test/orders/item_removed_from_return_test.rb @@ -0,0 +1,70 @@ +require "test_helper" + +module Returns + class ItemRemovedFromReturnTest < InMemoryTestCase + cover "Orders*" + + def test_remove_item_from_return + return_id = SecureRandom.uuid + product_id = SecureRandom.uuid + another_product_id = SecureRandom.uuid + order_id = SecureRandom.uuid + returnable_products = [{product_id: product_id, quantity: 1}, {product_id: another_product_id, quantity: 1}] + create_draft_return(return_id, order_id, returnable_products) + prepare_product(product_id, 50) + prepare_product(another_product_id, 30) + AddItemToReturn.new.call(item_added_to_return(return_id, order_id, product_id)) + AddItemToReturn.new.call(item_added_to_return(return_id, order_id, another_product_id)) + + RemoveItemFromReturn.new.call(item_removed_from_return(return_id, order_id, product_id)) + + assert_equal(1, Returns::ReturnItem.count) + return_item = Returns::ReturnItem.find_by(return_uid: return_id, product_uid: another_product_id) + assert_equal(another_product_id, return_item.product_uid) + assert_equal(1, return_item.quantity) + assert_equal(30, return_item.price) + + assert_equal(1, Returns::Return.count) + return_record = Returns::Return.find_by(uid: return_id) + assert_equal("Draft", return_record.status) + end + + private + + def create_draft_return(return_id, order_id, returnable_products) + draft_return_created = Ordering::DraftReturnCreated.new(data: { return_id: return_id, order_id: order_id, returnable_products: returnable_products }) + CreateDraftReturn.new.call(draft_return_created) + end + + def prepare_product(product_id, price) + event_store.publish( + ProductCatalog::ProductRegistered.new( + data: { + product_id: product_id + } + ) + ) + event_store.publish( + ProductCatalog::ProductNamed.new( + data: { + product_id: product_id, + name: "Async Remote" + } + ) + ) + event_store.publish(Pricing::PriceSet.new(data: { product_id: product_id, price: price })) + end + + def event_store + Rails.configuration.event_store + end + + def item_added_to_return(return_id, order_id, product_id) + Ordering::ItemAddedToReturn.new(data: { return_id: return_id, order_id: order_id, product_id: product_id }) + end + + def item_removed_from_return(return_id, order_id, product_id) + Ordering::ItemRemovedFromReturn.new(data: { return_id: return_id, order_id: order_id, product_id: product_id }) + end + end +end diff --git a/rails_application/test/test_helper.rb b/rails_application/test/test_helper.rb index 978117b3d..9037c53fc 100644 --- a/rails_application/test/test_helper.rb +++ b/rails_application/test/test_helper.rb @@ -217,12 +217,12 @@ def add_product_to_basket(order_id, product_id) post "/orders/#{order_id}/add_item?product_id=#{product_id}" end - def add_item_to_refund(order_id, refund_id, product_id) - post "/orders/#{order_id}/refunds/#{refund_id}/add_item?product_id=#{product_id}" + def add_item_to_return(order_id, return_id, product_id) + post "/orders/#{order_id}/returns/#{return_id}/add_item?product_id=#{product_id}" end - def remove_item_from_refund(order_id, refund_id, product_id) - post "/orders/#{order_id}/refunds/#{refund_id}/remove_item?product_id=#{product_id}" + def remove_item_from_return(order_id, return_id, product_id) + post "/orders/#{order_id}/returns/#{return_id}/remove_item?product_id=#{product_id}" end def run_command(command) diff --git a/rails_application/test/transformations/refund_to_return_event_mapper_test.rb b/rails_application/test/transformations/refund_to_return_event_mapper_test.rb new file mode 100644 index 000000000..64887d73f --- /dev/null +++ b/rails_application/test/transformations/refund_to_return_event_mapper_test.rb @@ -0,0 +1,49 @@ +require "test_helper" + +class RefundToReturnEventMapperTest < ActiveSupport::TestCase + def setup + @mapper = RubyEventStore::Mappers::PipelineMapper.new( + RubyEventStore::Mappers::Pipeline.new( + Transformations::RefundToReturnEventMapper.new( + 'Ordering::DraftRefundCreated' => 'Ordering::DraftReturnCreated', + 'Ordering::ItemAddedToRefund' => 'Ordering::ItemAddedToReturn', + 'Ordering::ItemRemovedFromRefund' => 'Ordering::ItemRemovedFromReturn' + ) + ) + ) + + @event_store = RailsEventStore::Client.new( + repository: RubyEventStore::InMemoryRepository.new, + mapper: @mapper + ) + end + + def test_transforms_old_events_when_reading + stream = "Test$#{SecureRandom.uuid}" + + old_event = Ordering::DraftReturnCreated.new( + data: { return_id: SecureRandom.uuid, order_id: SecureRandom.uuid, returnable_products: [] } + ) + + @event_store.publish(old_event, stream_name: stream) + events = @event_store.read.stream(stream).to_a + + assert_equal 1, events.size + assert_equal 'Ordering::DraftReturnCreated', events.first.class.name + end + + def test_new_events_work_normally + stream = "Test$#{SecureRandom.uuid}" + + new_event = Ordering::ItemAddedToReturn.new( + data: { return_id: SecureRandom.uuid, order_id: SecureRandom.uuid, product_id: SecureRandom.uuid } + ) + + @event_store.publish(new_event, stream_name: stream) + events = @event_store.read.stream(stream).to_a + + assert_equal 1, events.size + assert_equal 'Ordering::ItemAddedToReturn', events.first.class.name + assert_not_nil events.first.data[:return_id] + end +end