Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Allow creating custom vat rates #377

Merged
merged 4 commits into from
Aug 19, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ecommerce/taxes/lib/taxes/commands.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module Taxes
class SetVatRate < Infra::Command
attribute :product_id, Infra::Types::UUID
attribute :vat_rate, Infra::Types::VatRate
attribute :vat_rate_code, Infra::Types::String
end

class DetermineVatRate < Infra::Command
Expand Down
9 changes: 1 addition & 8 deletions ecommerce/taxes/lib/taxes/product.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,16 @@ module Taxes
class Product
include AggregateRoot

VatRateNotApplicable = Class.new(StandardError)

def initialize(id)
@id = id
end

def set_vat_rate(vat_rate, catalog)
raise VatRateNotApplicable unless vat_rate_applicable?(vat_rate.code, catalog)
def set_vat_rate(vat_rate)
apply(VatRateSet.new(data: { product_id: @id, vat_rate: vat_rate }))
end

private

def vat_rate_applicable?(vat_rate_code, catalog)
catalog.vat_rate_available?(vat_rate_code)
end

on(VatRateSet) { |_| }
end
end
7 changes: 5 additions & 2 deletions ecommerce/taxes/lib/taxes/services.rb
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
module Taxes
VatRateAlreadyExists = Class.new(StandardError)
VatRateNotApplicable = Class.new(StandardError)
class SetVatRateHandler
def initialize(event_store)
@repository = Infra::AggregateRootRepository.new(event_store)
@catalog = VatRateCatalog.new(event_store)
end

def call(cmd)
vat_rate = @catalog.vat_rate_by_code(cmd.vat_rate_code)
raise VatRateNotApplicable unless vat_rate
@repository.with_aggregate(Product, cmd.product_id) do |product|
product.set_vat_rate(cmd.vat_rate, @catalog)
product.set_vat_rate(vat_rate)
end
end
end
Expand Down Expand Up @@ -44,7 +47,7 @@ def initialize(event_store)
end

def call(cmd)
raise VatRateAlreadyExists if catalog.vat_rate_available?(cmd.vat_rate.code)
raise VatRateAlreadyExists if catalog.vat_rate_by_code(cmd.vat_rate.code)

event_store.publish(available_vat_rate_added_event(cmd), stream_name: stream_name(cmd))
end
Expand Down
8 changes: 5 additions & 3 deletions ecommerce/taxes/lib/taxes/vat_rate_catalog.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@ def vat_rate_for(product_id)
&.fetch(:vat_rate)
end

def vat_rate_available?(vat_rate_code)
def vat_rate_by_code(vat_rate_code)
@event_store
.read
.stream("Taxes::AvailableVatRate$#{vat_rate_code}")
.to_a
.any?
.last
&.data
&.fetch(:vat_rate)
&.then { |vat_rate| Infra::Types::VatRate.new(vat_rate) }
end
end
end
12 changes: 6 additions & 6 deletions ecommerce/taxes/test/taxes_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,16 @@ def test_setting_available_vat_rate
product_id = SecureRandom.uuid
vat_rate_set = VatRateSet.new(data: { product_id: product_id, vat_rate: vat_rate })
Copy link
Collaborator

Choose a reason for hiding this comment

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

small tip, you could do

vat_rate_set = VatRateSet.new(data: { product_id:, vat_rate: })

instead :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I would prefer to leave this as is for now, as it is consistent with the rest of the code in the project. In the future we can add a rubocop rule to enforce style guides :)

assert_events("Taxes::Product$#{product_id}", vat_rate_set) do
set_vat_rate(product_id, vat_rate)
set_vat_rate(product_id, vat_rate.code)
end
end

def test_setting_unavailable_vat_rate_should_raise_error
product_id = SecureRandom.uuid
unavailable_vat_rate = Infra::Types::VatRate.new(code: "20", rate: 20)

assert_raises(Product::VatRateNotApplicable) do
set_vat_rate(product_id, unavailable_vat_rate)
assert_raises(Taxes::VatRateNotApplicable) do
set_vat_rate(product_id, unavailable_vat_rate.code)
end
end

Expand All @@ -30,7 +30,7 @@ def test_determining_vat_rate
product_id = SecureRandom.uuid
another_product_id = SecureRandom.uuid

set_vat_rate(product_id, vat_rate)
set_vat_rate(product_id, vat_rate.code)
vat_rate_determined = VatRateDetermined.new(data: { order_id: order_id, product_id: product_id, vat_rate: vat_rate })
assert_events("Taxes::Order$#{order_id}", vat_rate_determined) do
determine_vat_rate(order_id, product_id, vat_rate)
Expand Down Expand Up @@ -61,8 +61,8 @@ def test_should_not_allow_for_double_registration

private

def set_vat_rate(product_id, vat_rate)
run_command(SetVatRate.new(product_id: product_id, vat_rate: vat_rate))
def set_vat_rate(product_id, vat_rate_code)
run_command(SetVatRate.new(product_id: product_id, vat_rate_code: vat_rate_code))
end

def determine_vat_rate(order_id, product_id, vat_rate)
Expand Down
24 changes: 8 additions & 16 deletions ecommerce/taxes/test/vat_rate_catalog_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,21 @@

module Taxes
class VatRateCatalogTest < Test
class VatRateAvailableTest < VatRateCatalogTest
def test_returns_true_when_vat_rate_is_available
vat_rate = Infra::Types::VatRate.new(code: "50", rate: 50)
add_available_vat_rate(vat_rate)

assert catalog.vat_rate_available?(vat_rate.code)
class VatRateByCodeTest < VatRateCatalogTest
def setup
@vat_rate = Infra::Types::VatRate.new(code: "50", rate: 50)
add_available_vat_rate(@vat_rate)
end

def test_returns_false_when_vat_rate_is_not_available
vat_rate = Infra::Types::VatRate.new(code: "50", rate: 50)

refute catalog.vat_rate_available?(vat_rate.code)
def test_returns_available_vat_rate
assert_equal @vat_rate, catalog.vat_rate_by_code("50")
end

def test_returns_false_when_vat_rate_is_not_available_even_when_other_vat_rates_are_available
vat_rate = Infra::Types::VatRate.new(code: "50", rate: 50)
add_available_vat_rate(vat_rate)

refute catalog.vat_rate_available?("60")
def test_returns_nil_when_vat_rate_is_not_available
assert_nil catalog.vat_rate_by_code("60")
end
end


private

def catalog
Expand Down
19 changes: 9 additions & 10 deletions rails_application/app/controllers/products_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,15 @@ def create
if product_form.vat_rate.present?
set_product_vat_rate(product_form.product_id, product_form.vat_rate)
end
rescue ProductCatalog::AlreadyRegistered
flash[:notice] = "Product was already registered"
render "new"
rescue Taxes::Product::VatRateNotApplicable
flash[:notice] = "Selected VAT rate not applicable"
render "new"
else
redirect_to products_path, notice: "Product was successfully created"
end

redirect_to products_path, notice: "Product was successfully created"
rescue ProductCatalog::AlreadyRegistered
flash[:notice] = "Product was already registered"
render "new"
rescue Taxes::VatRateNotApplicable
flash[:notice] = "Selected VAT rate is not applicable"
render "new"
Comment on lines +52 to +57
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I extracted this because it was inside a transaction before.

end

def update
Expand Down Expand Up @@ -92,7 +92,6 @@ def set_future_product_price(product_id, price, valid_since)
end

def set_product_vat_rate(product_id, vat_rate)
vat_rate = VatRates::AvailableVatRate.find_by!(code: vat_rate).to_value
command_bus.(set_product_vat_rate_cmd(product_id, vat_rate))
end

Expand All @@ -113,7 +112,7 @@ def set_product_price_cmd(product_id, price)
end

def set_product_vat_rate_cmd(product_id, vat_rate)
Taxes::SetVatRate.new(product_id: product_id, vat_rate: vat_rate)
Taxes::SetVatRate.new(product_id: product_id, vat_rate_code: vat_rate)
end

def set_product_future_price_cmd(product_id, price, valid_since)
Expand Down
2 changes: 1 addition & 1 deletion rails_application/db/seeds.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
ProductCatalog::RegisterProduct.new(product_id: product_id),
ProductCatalog::NameProduct.new(product_id: product_id, name: name_price_tuple[0]),
Pricing::SetPrice.new(product_id: product_id, price: name_price_tuple[1]),
Taxes::SetVatRate.new(product_id: product_id, vat_rate: VatRates::AvailableVatRate.first.to_value)
Taxes::SetVatRate.new(product_id: product_id, vat_rate_code: "20")
].each do |command|
command_bus.call(command)
end
Expand Down
1 change: 1 addition & 0 deletions rails_application/test/integration/client_orders_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ def setup
ClientOrders::Client.destroy_all
ClientOrders::Order.destroy_all
Orders::Order.destroy_all
add_available_vat_rate(10)
end

def test_happy_path
Expand Down
5 changes: 5 additions & 0 deletions rails_application/test/integration/customers_test.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
require "test_helper"

class CustomersTest < InMemoryRESIntegrationTestCase
def setup
super
add_available_vat_rate(10)
end

def test_list_customers
get "/customers"
assert_response :success
Expand Down
2 changes: 1 addition & 1 deletion rails_application/test/integration/discount_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ class DiscountTest < InMemoryRESIntegrationTestCase
def setup
super
Orders::Order.destroy_all
add_available_vat_rate(10)
end

def test_reset_discount
Expand Down Expand Up @@ -40,4 +41,3 @@ def apply_discount_10_percent(order_id)
assert_select("td", "$123.30")
end
end

1 change: 1 addition & 0 deletions rails_application/test/integration/orders_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ def setup
super
Rails.configuration.payment_gateway.call.reset
Orders::Order.destroy_all
add_available_vat_rate(10)
end

def test_submitting_empty_order
Expand Down
1 change: 1 addition & 0 deletions rails_application/test/integration/public_offer_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
class PublicOfferTest < InMemoryRESIntegrationTestCase
def setup
super
add_available_vat_rate(10)
end

def test_happy_path
Expand Down
19 changes: 10 additions & 9 deletions rails_application/test/invoices/invoices_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,27 +29,28 @@ def test_product_name_change_affects_existing_invoices
product_id = SecureRandom.uuid
initial_product_name = "Initial Name"
updated_product_name = "Updated Name"


add_available_vat_rate(20)
product_id = register_product(initial_product_name, 100, 20)
customer_id = register_customer("Test Customer")

order_id = SecureRandom.uuid
add_product_to_basket(order_id, product_id)
submit_order(customer_id, order_id)

update_product_name(product_id, updated_product_name)

assert_invoice_product_name(order_id, initial_product_name)

new_order_id = SecureRandom.uuid
add_product_to_basket(new_order_id, product_id)
submit_order(customer_id, new_order_id)

assert_invoice_product_name(new_order_id, updated_product_name)
end

private

def update_product_name(product_id, new_name)
patch "/products/#{product_id}",
params: {
Expand All @@ -58,11 +59,11 @@ def update_product_name(product_id, new_name)
name: new_name,
}
end

def assert_invoice_product_name(order_id, expected_name)
get "/invoices/#{order_id}"
assert_response :success
assert_select ".py-2", text: expected_name
end
end
end
end
1 change: 0 additions & 1 deletion rails_application/test/test_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ def register_customer(name)
end

def register_product(name, price, vat_rate)
add_available_vat_rate(vat_rate)
product_id = SecureRandom.uuid
post "/products", params: { product_id: product_id, name: name, price: price, vat_rate: vat_rate }
product_id
Expand Down