From 40de746600aee7ef5fa903d87e9c4ab296efe280 Mon Sep 17 00:00:00 2001 From: marlena-b Date: Fri, 25 Oct 2024 11:03:22 +0200 Subject: [PATCH] Estime final price of an order for a client --- .../client_orders/order_handlers.rb | 16 ++++ .../client_orders/rendering/edit_order.rb | 93 ++++++++++++++----- .../update_order_total_value_test.rb | 28 +++++- .../test/integration/client_orders_test.rb | 30 ++++++ 4 files changed, 144 insertions(+), 23 deletions(-) diff --git a/rails_application/app/read_models/client_orders/order_handlers.rb b/rails_application/app/read_models/client_orders/order_handlers.rb index 517cad68..a96b3f01 100644 --- a/rails_application/app/read_models/client_orders/order_handlers.rb +++ b/rails_application/app/read_models/client_orders/order_handlers.rb @@ -31,6 +31,22 @@ def call(event) order.discounted_value = event.data.fetch(:discounted_amount) order.total_value = event.data.fetch(:total_amount) order.save! + + broadcast_update(order.order_uid, "total_value", number_to_currency(order.total_value)) + broadcast_update(order.order_uid, "discounted_value", number_to_currency(order.discounted_value)) + end + + private + + def broadcast_update(order_id, target, content) + Turbo::StreamsChannel.broadcast_update_to( + "client_orders_#{order_id}", + target: "client_orders_#{order_id}_#{target}", + html: content) + end + + def number_to_currency(number) + ActiveSupport::NumberHelper.number_to_currency(number) end end diff --git a/rails_application/app/read_models/client_orders/rendering/edit_order.rb b/rails_application/app/read_models/client_orders/rendering/edit_order.rb index 6428d0b5..ca42415a 100644 --- a/rails_application/app/read_models/client_orders/rendering/edit_order.rb +++ b/rails_application/app/read_models/client_orders/rendering/edit_order.rb @@ -6,31 +6,32 @@ class EditOrder < Arbre::Component include ActionView::Helpers::UrlHelper def self.build(view_context, order_id) + order = ClientOrders::Order.find_or_initialize_by(order_uid: order_id) do |order| + order.total_value = 0 + order.discounted_value = 0 + end order_lines = ClientOrders::OrderLine.where(order_uid: order_id) products = ClientOrders::Product.all - new(Arbre::Context.new(nil, view_context)).build(order_id, order_lines, products) + time_promotions = TimePromotions::TimePromotion.current + new(Arbre::Context.new(nil, view_context)).build(order, order_lines, products, time_promotions) end - def build(order_id, order_lines, products, attributes = {}) + def build(order, order_lines, products, time_promotions, attributes = {}) super(attributes) div do - products_table(order_id, products, order_lines) - coupon_form(order_id) - submit_form(order_id) + products_table(order, products, order_lines, time_promotions) + coupon_form(order) + submit_form(order) end end private - def products_table(order_id, products, order_lines) + def products_table(order, products, order_lines, time_promotions) table class: "w-full" do headers_row - tbody do - text_node turbo_stream_from "client_orders_#{order_id}" - products.each do |product| - product_row(order_id, product, order_lines) - end - end + products_rows(order, products, order_lines) + footer_rows(order, time_promotions) end end @@ -46,16 +47,34 @@ def headers_row end end - def product_row(order_id, product, order_lines) + def products_rows(order, products, order_lines) + tbody do + text_node turbo_stream_from "client_orders_#{order.order_uid}" + products.each do |product| + product_row(order, product, order_lines) + end + end + end + + def product_row(order, product, order_lines) order_line = order_lines&.find { |order_line| order_line.product_id == product.uid } tr class: "border-b" do td(class: "py-2") { product.name } td(class: "py-2") { out_of_stock_badge unless product.available? } td(class: "py-2", id: "client_orders_#{product.uid}_product_quantity") { order_line.try(&:product_quantity) || 0 } td(class: "py-2") { number_to_currency(product.price) } - td(class: "py-2", id: "client_orders_#{product.uid}_value") { number_to_currency(order_line.try(&:value)) } - td(class: "py-2 text-right") { add_item_button(order_id, product.uid) } - td(class: "py-2 text-right", id: "client_orders_#{product.uid}_remove_item_button") { remove_item_button(order_id, product.uid) if order_line } + td(class: "py-2", id: "client_orders_#{product.uid}_value") { number_to_currency(order_line.try(&:value)) || "$0.00" } + td(class: "py-2 text-right") { add_item_button(order.order_uid, product.uid) } + td(class: "py-2 text-right", id: "client_orders_#{product.uid}_remove_item_button") { remove_item_button(order, product.uid) if order_line } + end + end + + def footer_rows(order, time_promotions) + tfoot class:"border-t-4" do + before_discounts_row(order) if order.percentage_discount || time_promotions.any? + coupon_discount_row(order) if order.percentage_discount + time_promotions_rows(time_promotions) + total_row(order) end end @@ -67,12 +86,42 @@ def add_item_button(order_id, product_id) button_to "Add", add_item_client_order_path(id: order_id, product_id: product_id), class: "hover:underline text-blue-500" end - def remove_item_button(order_id, product_id) - button_to "Remove", remove_item_client_order_path(id: order_id, product_id: product_id), class: "hover:underline text-blue-500" + def remove_item_button(order, product_id) + button_to "Remove", remove_item_client_order_path(id: order.order_uid, product_id: product_id), class: "hover:underline text-blue-500" + end + + def before_discounts_row(order) + tr(class: "border-t") do + td(class: "py-2", colspan: 4) { "Before discounts" } + td(class: "py-2", id: "client_orders_#{order.order_uid}_total_value") { number_to_currency(order.total_value) } + end + end + + def coupon_discount_row(order) + tr(class: "border-t") do + td(class: "py-2", colspan: 4) { "Coupon discount" } + td(class: "py-2", id: "client_orders_#{order.order_uid}_percentage_discount") { "#{order.percentage_discount}%" } + end + end + + def time_promotions_rows(time_promotions) + time_promotions.each do |time_promotion| + tr(class: "border-t") do + td(class: "py-2", colspan: 4) { "Promotion: #{time_promotion.label} (if you buy before #{time_promotion.end_time})"} + td(class: "py-2") { "#{time_promotion.discount}%" } + end + end + end + + def total_row(order) + tr(class: "border-t") do + td(class: "py-2", colspan: 4) { "Total" } + td(class: "py-2 font-bold", id: "client_orders_#{order.order_uid}_discounted_value") { number_to_currency(order.discounted_value) } + end end - def coupon_form(order_id) - form(action: use_coupon_client_order_path(id: order_id), method: :post, class: "inline-flex gap-4 mt-8") do + def coupon_form(order) + form(action: use_coupon_client_order_path(id: order.order_uid), method: :post, class: "inline-flex gap-4 mt-8") do input( id: "coupon_code", type: :text, @@ -84,9 +133,9 @@ def coupon_form(order_id) end end - def submit_form(order_id) + def submit_form(order) form(id: "form", action: client_orders_path, method: :post) do - input(type: :hidden, name: :order_id, value: order_id) + input(type: :hidden, name: :order_id, value: order.order_uid) div(class: "mt-8") do input type: :submit, value: "Create Order", class: "bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded" end diff --git a/rails_application/test/client_orders/update_order_total_value_test.rb b/rails_application/test/client_orders/update_order_total_value_test.rb index 205b461e..05834a20 100644 --- a/rails_application/test/client_orders/update_order_total_value_test.rb +++ b/rails_application/test/client_orders/update_order_total_value_test.rb @@ -2,6 +2,7 @@ module ClientOrders class UpdateOrderTotalValueTest < InMemoryTestCase + include ActionCable::TestHelper cover "ClientOrders*" def test_order_created_has_draft_state @@ -17,6 +18,28 @@ def test_order_created_has_draft_state assert_equal "Draft", order.state end + def test_broadcasts + order_id = SecureRandom.uuid + event_store.publish(Pricing::OrderTotalValueCalculated.new(data: { order_id: order_id, discounted_amount: 0, total_amount: 10 })) + + assert_broadcast_on( + "client_orders_#{order_id}", + turbo_stream_action_tag( + action: "update", + target: "client_orders_#{order_id}_total_value", + template: "$10.00" + ) + ) + assert_broadcast_on( + "client_orders_#{order_id}", + turbo_stream_action_tag( + action: "update", + target: "client_orders_#{order_id}_discounted_value", + template: "$0.00" + ) + ) + end + private def item_added_to_basket(order_id, product_id) @@ -45,6 +68,9 @@ def customer_registered(customer_id) def event_store Rails.configuration.event_store end + + def turbo_stream_action_tag(action:, target:, template:) + "" + end end end - diff --git a/rails_application/test/integration/client_orders_test.rb b/rails_application/test/integration/client_orders_test.rb index 394cfcbf..71110470 100644 --- a/rails_application/test/integration/client_orders_test.rb +++ b/rails_application/test/integration/client_orders_test.rb @@ -250,6 +250,23 @@ def test_using_coupon_twice assert_select "#alert", "Coupon already used!" end + def test_shows_estimated_final_price_including_discounts + customer_id = register_customer("Customer Shop") + product_id = register_product("Fearless Refactoring", 4, 10) + register_coupon("Coupon", "coupon10", 10) + time_promotion_end_time = Time.current + 1.day + create_current_time_promotion(discount: 50, end_time: time_promotion_end_time) + + login(customer_id) + visit_client_orders + + order_id = SecureRandom.uuid + as_client_add_item_to_basket_for_order(product_id, order_id) + as_client_use_coupon(order_id, "COUPON10") + + assert_order_final_price_with_discounts("$4.00", "10.0%", "50%", "$1.60", time_promotion_end_time) + end + private def submit_order_for_customer(customer_id, order_id) @@ -304,6 +321,19 @@ def assert_orders_summary(summary) end end + def assert_order_final_price_with_discounts(before_discounts, coupon, time_promotion, after_discounts, time_promotion_end_time) + assert_select "tr" do + assert_select "td", "Before discounts" + assert_select "td", before_discounts + assert_select "td", "Coupon discount" + assert_select "td", coupon + assert_select "td", "Promotion: Last Minute (if you buy before #{time_promotion_end_time})" + assert_select "td", time_promotion + assert_select "td", "Total" + assert_select "td", after_discounts + end + end + def update_price(product_id, new_price) patch "/products/#{product_id}", params: {