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

Conversation

marlena-b
Copy link
Collaborator

Issue: #174

This PR lets users create custom VAT rates. Previously, VAT rates were hardcoded in the configuration files, but now users can add their own. It is not possible to add two VAT rates with the same code (it would be very confusing if we had duplicates).

In my changes VAT rate codes can be strings, like "20.0 % S" or "5.0 % R" (source). However, when adding a product, the system still checks if the VAT rate code is a number, which breaks the flow for vat rates with string names. I’ll fix that in a separate PR, along with some confusing naming, to keep this one focused.

Nagranie.z.ekranu.2024-08-13.o.16.52.42.mov

Copy link

netlify bot commented Aug 13, 2024

Deploy Preview for ecommerce-events failed.

Name Link
🔨 Latest commit 265b7c2
🔍 Latest deploy log https://app.netlify.com/sites/ecommerce-events/deploys/66c31a99f3cb3a0008ecc40f

@@ -81,11 +81,16 @@ def register_customer(name)
end

def register_product(name, price, vat_rate)
add_available_vat_rate(vat_rate)
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Do you think this is fine?
If two products will be created with the same vat rate then this will attempt to create two vat rates with the same code. The second attempt will fail and return 422 response. This does not interfere with test logic but I'd like to know if making this call every time when we register product is fine.

The other option is to change every test using register_product so they also explicitly create vat rates.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I am afraid that having such solution would lead to hacking. Also, it would make the method name invalid.

I'd prefer if the add_available_vat_rate is called just once. In fact, all we need is to have available rates defined before tests are run.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thanks 👍 I didn't know how to do it in a way that it is called only once but I added it to each test needing it (inside a setup block).

Comment on lines 5 to 7
def to_value
Infra::Types::VatRate.new(code: code, rate: rate)
end
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Other models don't have any methods but this was very useful. I can remove and refactor if we don't want to have any methods in models


def stream_name(cmd)
"Taxes::AvailableVatRate$#{cmd.vat_rate.code}"
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'm using code instead of uuid in the stream name.
This helps ensure uniqueness of vat rates and simplifies some logic.

Please let me know what you think.

Copy link
Collaborator

@lukaszreszke lukaszreszke Aug 14, 2024

Choose a reason for hiding this comment

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

Technically it is a good choice. However I am wondering if the model is correct. But I don't have an answer yet. It is good for first iteration I guess.

refute catalog.vat_rate_available?(vat_rate.code)
end

def test_returns_false_when_vat_rate_is_not_available_even_when_other_vat_rates_are_available
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 don't quite like this test but mutant complained without it:

@event_store
        .read
        .stream("Taxes::AvailableVatRate$#{vat_rate_code}")
        .to_a
        .any?

Mutant indicated that this code passes all the tests without the stream filter.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think the test if fine, but the name of it could be improved.

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 had to change the functionality of VatRateCatalog so I removed these tests.

@marlena-b marlena-b marked this pull request as ready for review August 13, 2024 16:05
def set_vat_rate(vat_rate)
raise VatRateNotApplicable unless vat_rate_applicable?(vat_rate)
def set_vat_rate(vat_rate, catalog)
raise VatRateNotApplicable unless vat_rate_applicable?(vat_rate.code, catalog)
Copy link
Collaborator

Choose a reason for hiding this comment

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

The validation checking if the vat rate is available should happen outside of the domain model

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thanks @lukaszreszke! Can you help me figure out where to put it? Is command handler a better place for it?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes - command handler is better place for checking such a logic.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done!

Comment on lines +6 to +11
attr_reader :code, :rate

def initialize(params)
@code = params[:code]
@rate = params[:rate]
end
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

def set_product_vat_rate(product_id, vat_rate_code)
vat_rate = Taxes::Configuration.available_vat_rates.find{|rate| rate.code == vat_rate_code}
def set_product_vat_rate(product_id, vat_rate)
vat_rate = VatRates::AvailableVatRate.find_by!(code: vat_rate).to_value
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think that the controller should validate if the vat rate exists

Copy link
Collaborator

Choose a reason for hiding this comment

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

Especially as it happens in the domain model (which is also not correct).

Would you move it to the SetVatRateHandler?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done 👍

Comment on lines +52 to +57
rescue ProductCatalog::AlreadyRegistered
flash[:notice] = "Product was already registered"
render "new"
rescue Taxes::VatRateNotApplicable
flash[:notice] = "Selected VAT rate is not applicable"
render "new"
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.

product_id = SecureRandom.uuid
vat_rate_set = VatRateSet.new(data: { product_id: product_id, vat_rate: available_vat_rate })
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 :)

class AvailableVatRate < ApplicationRecord
self.table_name = "available_vat_rates"

def to_value
Copy link
Collaborator

Choose a reason for hiding this comment

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

It is not used anymore, you can remove this method in next PR :)

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 already removed it :)

@marlena-b marlena-b merged commit 76954aa into RailsEventStore:master Aug 19, 2024
0 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants