diff --git a/rails_application/app/controllers/refunds_controller.rb b/rails_application/app/controllers/refunds_controller.rb
new file mode 100644
index 00000000..2638a976
--- /dev/null
+++ b/rails_application/app/controllers/refunds_controller.rb
@@ -0,0 +1,7 @@
+class RefundsController < ApplicationController
+ def new
+ @order = Orders::Order.find_by_uid(params[:order_id])
+ @refund = Refunds::Refund.new
+ @order_lines = Orders::OrderLine.where(order_uid: params[:order_id])
+ end
+end
diff --git a/rails_application/app/read_models/refunds/configuration.rb b/rails_application/app/read_models/refunds/configuration.rb
new file mode 100644
index 00000000..271a8405
--- /dev/null
+++ b/rails_application/app/read_models/refunds/configuration.rb
@@ -0,0 +1,20 @@
+module Refunds
+ class Refund < ApplicationRecord
+ self.table_name = "refunds"
+
+ has_many :refund_items,
+ class_name: "Refunds::RefundItem",
+ foreign_key: :order_uid,
+ primary_key: :uid
+ end
+
+ class RefundItem < ApplicationRecord
+ self.table_name = "refund_items"
+ end
+
+ class Configuration
+ def call(event_store)
+ @event_store = event_store
+ end
+ end
+end
diff --git a/rails_application/app/views/orders/show.html.erb b/rails_application/app/views/orders/show.html.erb
index cf0ccd3b..979967ca 100644
--- a/rails_application/app/views/orders/show.html.erb
+++ b/rails_application/app/views/orders/show.html.erb
@@ -23,6 +23,10 @@
<%= button_to("Pay", pay_order_path(@order.uid), class: "mr-3 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-500 border-transparent text-white bg-blue-600 hover:bg-blue-700") %>
<% end %>
+ <% if @order.state == "Submitted" %>
+ <%= link_to("Refund", new_order_refund_path(order_id: @order.uid), class: "mr-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") %>
<%= button_to("Cancel Order", cancel_order_path(@order.uid), class: "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 %>
diff --git a/rails_application/app/views/refunds/new.html.erb b/rails_application/app/views/refunds/new.html.erb
new file mode 100644
index 00000000..c9cf843b
--- /dev/null
+++ b/rails_application/app/views/refunds/new.html.erb
@@ -0,0 +1,43 @@
+<% content_for(:header) do %>
+ Refund for Order <%= @order.number %>
+<% end %>
+
+<% content_for(:actions) do %>
+ <%= secondary_action_button do %>
+ <%= link_to 'Back', orders_path %>
+ <% end %>
+
+ <%= primary_form_action_button do %>
+ Submit Refund
+ <% end %>
+<% end %>
+
+
+
+
+ | Product |
+ Quantity |
+ Price |
+ Value |
+
+
+
+
+ <% @order_lines.each do |order_line| %>
+
+ | <%= order_line.product_name %> |
+ 0 / <%= order_line.quantity %> |
+ <%= number_to_currency(order_line.price) %> |
+ <%= number_to_currency(order_line.value) %> |
+ <%= button_to "Add", "#", class: "hover:underline text-blue-500" %> |
+ <%= button_to("Remove", "#", class: "hover:underline text-blue-500") %> |
+
+ <% end %>
+
+
+
+ | Total |
+ |
+
+
+
diff --git a/rails_application/config/routes.rb b/rails_application/config/routes.rb
index e52f6d35..079b59b5 100644
--- a/rails_application/config/routes.rb
+++ b/rails_application/config/routes.rb
@@ -17,6 +17,7 @@
resource :shipping_address, only: [:edit, :update]
resource :billing_address, only: [:edit, :update]
resource :invoice, only: [:create]
+ resources :refunds, only: [:new]
end
resources :shipments, only: [:index, :show]
diff --git a/rails_application/db/migrate/20241209100544_create_refunds.rb b/rails_application/db/migrate/20241209100544_create_refunds.rb
new file mode 100644
index 00000000..653396b3
--- /dev/null
+++ b/rails_application/db/migrate/20241209100544_create_refunds.rb
@@ -0,0 +1,12 @@
+class CreateRefunds < ActiveRecord::Migration[7.2]
+ def change
+ create_table :refunds do |t|
+ t.uuid :uid, null: false
+ t.uuid :order_uid, null: false
+ t.string :status, null: false
+ t.decimal :total_value, precision: 8, scale: 2, null: false
+
+ t.timestamps
+ end
+ end
+end
diff --git a/rails_application/db/migrate/20241209102208_create_refund_items.rb b/rails_application/db/migrate/20241209102208_create_refund_items.rb
new file mode 100644
index 00000000..d3539842
--- /dev/null
+++ b/rails_application/db/migrate/20241209102208_create_refund_items.rb
@@ -0,0 +1,12 @@
+class CreateRefundItems < ActiveRecord::Migration[7.2]
+ def change
+ create_table :refund_items do |t|
+ t.uuid :refund_uid, null: false
+ t.uuid :product_uid, null: false
+ t.integer :quantity, null: false
+ t.decimal :price, precision: 8, scale: 2, null: false
+
+ t.timestamps
+ end
+ end
+end
diff --git a/rails_application/db/schema.rb b/rails_application/db/schema.rb
index 69662557..0dd98845 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: 2024_11_29_122521) do
+ActiveRecord::Schema[7.2].define(version: 2024_12_09_102208) do
# These are extensions that must be enabled in order to support this database
enable_extension "pgcrypto"
enable_extension "plpgsql"
@@ -195,6 +195,24 @@
t.decimal "lowest_recent_price", precision: 8, scale: 2
end
+ create_table "refund_items", force: :cascade do |t|
+ t.uuid "refund_uid", null: false
+ t.uuid "product_uid", null: false
+ t.integer "quantity", null: false
+ t.decimal "price", precision: 8, scale: 2, null: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ end
+
+ create_table "refunds", force: :cascade do |t|
+ t.uuid "uid", null: false
+ t.uuid "order_uid", null: false
+ t.string "status", null: false
+ t.decimal "total_value", precision: 8, scale: 2, null: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ end
+
create_table "shipment_items", force: :cascade do |t|
t.bigint "shipment_id", null: false
t.string "product_name", null: false
diff --git a/rails_application/lib/configuration.rb b/rails_application/lib/configuration.rb
index 512be070..60ed8722 100644
--- a/rails_application/lib/configuration.rb
+++ b/rails_application/lib/configuration.rb
@@ -17,6 +17,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)
Ecommerce::Configuration.new(
number_generator: Rails.configuration.number_generator,
@@ -81,4 +82,8 @@ def enable_authentication_read_model(event_store)
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)
+ end
end
diff --git a/rails_application/test/integration/refunds_test.rb b/rails_application/test/integration/refunds_test.rb
new file mode 100644
index 00000000..816a1b4b
--- /dev/null
+++ b/rails_application/test/integration/refunds_test.rb
@@ -0,0 +1,38 @@
+require "test_helper"
+
+class RefundsTest < InMemoryRESIntegrationTestCase
+ def setup
+ super
+ add_available_vat_rate(10)
+ end
+
+ def test_happy_path
+ shopify_id = register_customer("Shopify")
+ order_id = SecureRandom.uuid
+ async_remote_id = register_product("Async Remote", 39, 10)
+ fearless_id = register_product("Fearless Refactoring", 49, 10)
+
+ add_product_to_basket(order_id, async_remote_id)
+ add_product_to_basket(order_id, fearless_id)
+ add_product_to_basket(order_id, fearless_id)
+ submit_order(shopify_id, order_id)
+
+ get "/orders/#{order_id}"
+
+ assert_select("a", "Refund")
+
+ get "/orders/#{order_id}/refunds/new"
+
+ assert_order_line_row(async_remote_id, "Async Remote", 1)
+ assert_order_line_row(fearless_id, "Fearless Refactoring", 2)
+ end
+
+ private
+
+ def assert_order_line_row(product_id, product_name, quantity)
+ assert_select("#order_line_product_#{product_id}") do
+ assert_select("td", product_name)
+ assert_select("td", "0 / #{quantity}")
+ end
+ end
+end