My suggestion would be to put an observer on the sales_quote_collect_totals_before
event which is fired in the Mage_Sales_Model_Quote::collectTotals
method before it starts the total collection process. Then from inside this observer method, iterate the quote items and change the tax class on the (already loaded) product object you can retrieve from the quote item.
After you set the information on the product object, whatever you do, DO NOT try and save it to the database. Having the tax class set as needed on the product object in memory will be good enough to have the collect totals logic found in Mage_Tax_Model_Sales_Total_Quote_Tax
pickup which tax class it should base it's calculations on. Saving the product (as you seem to be trying to do in your code sample above) will cause major performance issues, will create race conditions in the calculation process, and is simply not good practice.
The reason that the events you are trying to work with are not enabling you to accomplish what you are trying to accomplish is because they all come after the total calculation, a process which is only be run once prior to saving the quote.
Worth pointing out about the collect totals process is that once run, without doing extra work, you cannot call it again to have it re-calculate based on changes you've made to the quote items. See this tie-bit I've taken from the blog series a colleage of mine recently put together on the collect totals process:
Now that you understand what occurs during the totals collection
process, you may find it convenient or necessary to call it directly
yourself. Before you start feeling too confident with using
collectTotals for your own purposes, though, keep the following rule
in mind:
Products cannot be added to the quote after collectTotals is run!
. . . unless the quote addresses' item caches are cleared.
Nearly every total model's "collect" method relies on fetching the
quote items from the address and looping through them. The first time
getAllItems is run on a quote address, the item collection is actually
cached with a unique key, and it's this cached collection that is
returned on subsequent calls.
If you do happen to have an inkling for really diving into the depths of how the collect totals process works, you can check out the first of the four part series on total collection here for more in-depth reading: Unravelling Magento's collectTotals: Introduction
To summarize, you need to be catching an event which runs before the collect totals process (and before getAllItems is called on the quote addresses) so that changes you make to the items will be used by the total collectors. I've not verified that the suggested sales_quote_collect_totals_before
event runs before any calls to the getAllItems
on the quote address, but I'm almost certain that it will work for what you need. But if not, hopefully I've provided enough context for you to figure out which event you need to catch to make it work.
I'm going to explain how I handled an ajax scenario in Magento and let's see if you can use this to help yourself!
I needed to update an area of the page via Ajax when the user presses a button that looks like this:
<button type="button" title="<?php echo $buttonTitle ?>" id="buttonAddToCart" class="button btn-cart" onclick="productAddToCartForm.submit(this)"><?php echo $buttonTitle ?></button>
So, when the user presses this button it passes the contents of the form to a function called productAddToCartForm.submit(). This is the function that will initiate the ajax request. Within this function a number of things need to be specified. First, you need a URL pointing towards a controller. This is the 'link' in the ajax 'chain' connecting the user to the server. So let's take a look at that function.
<script type="text/javascript">
//<![CDATA[
var productAddToCartForm = new VarienForm('product_addtocart_form');
productAddToCartForm.submit = function (button, url) {
if (this.validator.validate()) {
var form = this.form;
var oldUrl = form.action;
if (url) {
form.action = url;
}
var e = null;
if (!url) {
url = jQuery('#product_addtocart_form').attr('action');
}
if (url.search('wishlist') !== -1) {
this.form.submit();
} else {
/**
* add to cart form data is sent to indexcontroller via AJAX. TopCartContent is updated and displayed to the user.
**/
url = url.replace("checkout/cart", "checkout/index");
var data = jQuery('#product_addtocart_form').serialize();
data += '&isAjax=1';
jQuery('#ajax_loader').show();
jQuery('#buttonAddToCart').prop("disabled", true);
try {
jQuery.ajax({
url: url,
dataType: 'json',
type: 'post',
data: data,
success: function (data) {
jQuery('#img-thumb').attr('src', data.image);
jQuery('#prod-name').html(data.name);
jQuery('#prod-price').html(data.price);
jQuery('#header-cart-checkout').html(data.topCart);
Enterprise.TopCart.initialize('topCartContent');
Enterprise.TopCart.showCart(8);
jQuery('#ajax_loader').hide();
jQuery('#buttonAddToCart').prop('disabled', false);
}
});
} catch (e) {
}
this.form.action = oldUrl;
if (e) {
throw e;
}
}
}
}.bind(productAddToCartForm);
This instantiates the forms information and validates it's contents. The meat-and-potatoes of this function is wedged inbetween the 'try' section. It issues a jQuery.Ajax request to the URL, it expects a 'json' format response. It uses a post method to query the database, and is sending data to our controller.
All the things after the success:function(data) area is upon a successful response from our controller. So, before I explain that to you -- I'll show you a section of the code from my controller that will hopefully clear things up for you. It is here where the data is sent and new data is fetched from the server and given to the user, remember you must add a URL that is directed at this controller. In the above example, I overrode the core checkout/cartcontroller and replaced it with a custom checkout/indexcontroller.
class Companyspace_Checkout_IndexController extends Mage_Checkout_CartController{
$product = $this->_initProduct();
if ($params['isAjax'] == 1) {
$response = array();
try {
$message = $this->__('%s was added to your shopping cart.', Mage::helper('core')->escapeHtml($product->getName()));
$response['status'] = 'SUCCESS';
$response['message'] = $message;
$this->loadLayout();
$topCart = $this->getLayout()->getBlock('topCart')->toHtml();
$response['topCart'] = $topCart;
$response['image'] = (string)Mage::helper('catalog/image')->init($product, 'thumbnail');
$response['name'] = $product->getname();
$response['price'] = Mage::helper('core')->formatPrice($product->getPrice(), true);
}
$this->getResponse()->setBody(Mage::helper('core')->jsonEncode($response));
return;
}
This recieves the request from the previous function and prepares a response array. This response array is populated by a number of things, including a re-initialized $topCart section (this is what I mainly use to refresh a certain section of my page). It then encodes this using jsonEncode and sends it back to the user.
Then, this function uses JQuery to populate the elements with new information!
I hope this helps!
Best Answer
According to your code, you have replace c
urrent quote and items and other data
at session class checkout/sessionMage::getSingleton('checkout/session')
using functionreplaceQuote()
.As per as magento,the
session variable does not affective
until onetime page rendered
andcart block content depend on checkout session
. So you need to rendered the page and update cart block content via ajax.