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| %> -