This issue is regarding Commerce Stock Reserve causing an incorrect calculation of taxes by Commerce. To recreate the issue:
- Create a product with a run-time tax calculation. Assume the nominal price is 10.00 and the tax is 20%.
- Add the product to the cart. The amount shown to the user is 12.00, as expected.
- Change the number of line-items in the cart from 1 to 2. The expected final amount is 24.00. The one shown is 28.80. I.e, the tax has been applied twice.
- If one queries the record of the product from the database, it will now contain the price of 12.00 instead of 10.00. I.e., the database has been overridden.
Here is how it's all related to Commerce Stock Reserve: it is the function commerce_stock_reserve() that overrides the products database, since it alters the stock levels of the cached record of the product (which has been already modified by Commerce to account for the taxes), instead of its original version in the database, containing the original price of 10.00.
The patch attached is a rough fix for the issue. I have no doubts, there are more elegant solutions. Please, help.
Thanks in advance!
Comment | File | Size | Author |
---|---|---|---|
#21 | commerce_stock_reserve-2732251-14.patch | 1.38 KB | Roman.UCLU |
#6 | commerce_stock_reserve-2732251-5.patch | 1003 bytes | pjcdawkins |
Comments
Comment #2
pjcdawkins CreditAttribution: pjcdawkins as a volunteer commentedI just noticed this issue. If I understand it correctly, that sounds like a Commerce core bug to me. Just changing an unrelated field on a product (the stock field) shouldn't mess up its price. This module doesn't touch prices, and I don't think it should.
I wonder, are you using a Redis (or non-SQL) cache for the
cache_field
bin?Comment #3
Roman.UCLU CreditAttribution: Roman.UCLU commentedThank you for your reply. No, we don't use any of the above.
I did some further debugging. It happens that the issue is significantly more complicated than I originally thought, but I still believe that it's Commerce Stock Reserve that needs to be patched. The bug results from incorrect interactions among four different modules: Commerce Stock Reserve, Commerce Product Attributes, Commerce Product Pricing, Commerce Tax. The last two are submodules of Commerce. The following happens:
_field_formatter_prepare_view()
that would allow it to act upon a product field being added to the display. Then it calls the modules necessary (e.g. Commerce Tax) to do the formatting for the price of this product.original
, where it stores the old price of the product to be restored when the view is fully formed.One might argue that the reason behind this bug is the bad architecture of Commerce Product Pricing, since this module assumes that nothing would be working with products at the same time when it does. However, rebuilding the architecture of a submodule of Commerce would be too complicated.
It was suggested that the best course of action is to make Commerce Stock Reserve independent of anything that can be messing up with products while it is doing its job, regardless of it being Commerce Product Pricing or anything else that a particular Drupal configuration might contain. This would be possible, if Commerce Stock Reserve retrieved its products directly from the database, instead of using their versions in the RAM, as it does now.
The patch is attached.
Comment #4
pjcdawkins CreditAttribution: pjcdawkins as a volunteer commentedThanks for the analysis!
I'm not confident that this will fix the issue, because I haven't had the time to reproduce it myself, but if you can test it... I'm happy.
I've attached a modification of your patch - it's functionally the same - with a few small tweaks:
TRUE
(sadly this is a Drupal-specific thing)By the way, you can set issues to
Needs review
when you upload a patch (andNeeds work
when an existing patch needs changes).Comment #6
pjcdawkins CreditAttribution: pjcdawkins as a volunteer commentedOh hang on,
entity_load()
won't cut it, it returns an array. New patch attached.Speaking of which, the helper functions entity_load_single() and commerce_product_load() could really do with a $reset argument.
Comment #8
Roman.UCLU CreditAttribution: Roman.UCLU commentedOnce again, thank you very much for your input! Indeed, the patch I submitted wouldn't be functional. I guess, I failed to test it properly.
I have tested the latest patch on my configuration and can confirm that it does solve this issue!
Comment #9
Roman.UCLU CreditAttribution: Roman.UCLU commentedComment #10
pjcdawkins CreditAttribution: pjcdawkins as a volunteer commented(see https://www.drupal.org/node/156119)
Comment #12
pjcdawkins CreditAttribution: pjcdawkins as a volunteer commented... now committed (to 7.x-1.x-dev)
Comment #18
Roman.UCLU CreditAttribution: Roman.UCLU commentedI am reopening this issue due to a knock-on effect caused by the latest patch. Namely, the automated testing of the module returns failures when ran.
Below is the code causing the issue from
testReserve()
of commerce_stock_reserve_basic.test:Note that
$product
is not passed or returned bycreateDummyOrder()
. Nonetheless, the function assumes that aftercreateDummyOrder()
is called,$product
will pick up the change automatically. In other words, the function expectscreateDummyOrder()
to find$product
in the memory and modify it, even thoughcreateDummyOrder()
does not even know that$product
as well as the code using it exist. I do not believe that this approach is correct.The test used to work before, since, indeed, the Entity Caching mechanism of Drupal forces
createDummyOrder()
to use our$product
, which I consider a lucky coincidence. In general, several rules are invoked withincreateDummyOrder()
which trigger several completely unrelated modules. It is somewhat naive to expect all of them to use caching.In our case, the test fails with the patch, since in our patch we call
entity_load()
, which clears the entity cache for the entity being loaded. Hence, the alterations Commerce Stock Reserve does to the stock value are no longer stored in$product
, the test cannot see them and returns a failure.In the patch attached I use
commerce_product_load()
to locate the right place where the changes are stored. Once picked up by the system, the test is back to being successful.Comment #21
Roman.UCLU CreditAttribution: Roman.UCLU commentedProduced the patch for a wrong directory. Fixed.
Comment #22
Roman.UCLU CreditAttribution: Roman.UCLU commentedComment #24
pjcdawkins CreditAttribution: pjcdawkins commented