Whenever you load the cart page in Magento, the following code is run
$cart->init();
$cart->save();
One side effect of this is that the prices for any items in the cart are updated if the price of the product has been updated. This actually updates the entry in sales_flat_quote_item. I'm trying to track down where in code the price is updated on each quote item, and where each quote item is saved.
I know the myrid locations it could be set. I'm hoping someone knows where it actually is set. Magento 1.7x branch specifically, although info from all versions is welcome.
Dug this one up myself. So there's this
#File: app/code/core/Mage/Sales/Model/Quote.php
foreach ($this->getAllAddresses() as $address) {
...
$address->collectTotals();
...
}
which leads to this
#File: app/code/core/Mage/Sales/Model/Quote/Address.php
public function collectTotals()
{
Mage::dispatchEvent($this->_eventPrefix . '_collect_totals_before', array($this->_eventObject => $this));
foreach ($this->getTotalCollector()->getCollectors() as $model) {
$model->collect($this);
}
Mage::dispatchEvent($this->_eventPrefix . '_collect_totals_after', array($this->_eventObject => $this));
return $this;
}
The getTotalCollector object returns a sales/quote_address_total_collector object, which loads a series of collector models from global/sales/quote/totals and calls collect on them. The sub-total collector's collect method ultimately calls this
#File: app/code/core/Mage/Sales/Model/Quote/Address/Total/Subtotal.php
protected function _initItem($address, $item)
{
//...
if ($quoteItem->getParentItem() && $quoteItem->isChildrenCalculated()) {
$finalPrice = $quoteItem->getParentItem()->getProduct()->getPriceModel()->getChildFinalPrice(
$quoteItem->getParentItem()->getProduct(),
$quoteItem->getParentItem()->getQty(),
$quoteItem->getProduct(),
$quoteItem->getQty()
);
$item->setPrice($finalPrice)
->setBaseOriginalPrice($finalPrice);
$item->calcRowTotal();
} else if (!$quoteItem->getParentItem()) {
$finalPrice = $product->getFinalPrice($quoteItem->getQty());
$item->setPrice($finalPrice)
->setBaseOriginalPrice($finalPrice);
$item->calcRowTotal();
$this->_addAmount($item->getRowTotal());
$this->_addBaseAmount($item->getBaseRowTotal());
$address->setTotalQty($address->getTotalQty() + $item->getQty());
}
//...
}
and this is where the quote item gets it's price set/rest.
From a high level, the code that starts the whole process are lines 464 and 465 of Mage_Checkout_Model_Cart :
$this->getQuote()->collectTotals();
$this->getQuote()->save();
The new product price is being set against the quote in Mage_Sales_Model_Quote_Address_Total_Subtotal in the _initItem method. You will see $item->setPrice in the the if / else statement starting at line 104
If you are trying to do custom price changes on products in the cart, rather than extend and modify core classes, I use an observer sales_quote_save_before. It works great if you are trying to customize pricing (especially when I have products that can be custom length).
Related
I lose some session variables in Chrome only
After reading every similar question and answer on this site, I have to note:
It is not a 404 / missing favicon issue
Also not a redirect issue. The session is started and closed properly.
I have a $_SESSION['customer'] and $_SESSION['order'] variable, for instance.
As expected, $_SESSION['customer'] holds an object of customer data, $_SESSION['order'] holds an array of products.
When I add a product to the order, I need to look up any customer discounts for this specific product. Then it turns out in Chrome, the $_SESSION['customer'] variable is missing, and the sql query returns no discounts, since there is no customer number.
Let's give you some code to work with:
I am on the update order page, where I still have my customer and order variable.
I call this function with an ajax request to return a list of products:
public function live_search_product(){
$this->load->model('orders', 'orders');
$qry = $_GET['qry'];
if (strlen($qry)>0) {
$products = $this->orders_model->search_products($qry, false);
if ($products){
foreach ($products as $product){
echo ''.$product['short_title'] . '<br>';
}
}
}
}
I select the product from the dropdown list, and end up in this function, where I suddenly have lost my $_SESSION['customer'] object and $_SESSION['order'] array. Please note that all my other session variables still exist.
public function add_product() {
$this->load->model('orders', 'orders');
$product = $this->orders_model->get_product_by_id($this->router->id);
$discount = $this->discount->get_discount($product->id, $product->product_group_id, $_SESSION['customer']->id, $_SESSION['customer']->group_id );
$_SESSION['order'][$this->router->id] = array('product' => $product ,
'quantity' => 1,
'rental' => 0,
'bruto' => $product->price_ex,
'discount' => $discount->discount_percentage,
'netto' => ((100 - $discount->discount_percentage)/100) * $product->price_ex,
'total' => ((100 - $discount->discount_percentage)/100) * $product->price_ex);
$this->calculate_order_totals();
}
In firefox and iexplorer and edge there is no problem, it works.
I really hope you have any other suggestions.
**** EDIT ****
Every controller has an index function that is called automatically. route orders/orders will call orders/orders/index, and for instance route orders/orders/add_product, will call the function add_product in the orders controller (residing in the orders folder).
It turns out that in Chrome, somehow I get routed to the index function of this orders controller. Which is an overview orders function where I reset all order variables, to make sure nothing is left when I open a new order.
I have changed that, so this specific situation is solved.
However, can not understand why I get redirected to this function, when calling the add_product function directly.
Any ideas?
I'm currently trying to add a custom option to a specific orderline on add to cart via the following:
public function addToPackageQuote()
{
$cart = Mage::getSingleton("checkout/cart");
$quote = Mage::getSingleton("checkout/session")->getQuote();
$packageId = Mage::getModel('MyTuxedo_OPP/Package')->checkPackageId();
$products = $this->sortArray();
foreach ($products as $productInfo) {
try {
$split = explode(",", $productInfo);
$_product = Mage::getModel('catalog/product')->load($split[0]);
if($_product->isConfigurable()) {
$simpleId = $this->getConfigurableSimple($split[1],$split[3],$split[0]);
} else {
$simpleId = $split[0];
}
$product = Mage::getModel('catalog/product')->load($simpleId);
$options = new Varien_Object(array(
"qty" => 1,
"custom_options" => array(
"package" => $packageId,
"packageName" => Mage::helper('MyTuxedo_OPP')->getPackageName()
)
));
$quote->addProduct($product, $options);
$this->_getSession()->setCartWasUpdated(true);
$quote->save();
} catch (Exception $e) {
echo $e->getMessage();
}
$this->addFreeItems();
}
$cart->save();
unset($_SESSION['products']);
unset($_SESSION['productId']);
$cart->save();
// Let's unset all the package sessions (apart from a few that are needed!).
$this->kill();
}
This method is completely seperate from the generic add to cart handler, and is used purely in a packages system so that it adds simple products exclusively (also breaks down configurables super attribute to find the simple product too).
These simple products have no custom options attached to them in the Magento backend, nor is it a goal to add custom options to the product itself. What I would like to do is attach custom options to the order-line that is then transferred over to the order if a purchase is made. So effectively data that is added at the add to cart method and no where else!
The add to cart method works as expected it's just not including the custom options I am trying to attach. I have also tried defining the options object as simply:
$options = new Varien_Object(array(
"qty" => 1,
"package" => $packageId,
"packageName" => Mage::helper('MyTuxedo_OPP')->getPackageName()
)
The above info, not including qty is not in the orderline object at all, and I can't seem to work out where to move on from here.
Endlessly googling at the moment so some help would be most appreciated!!
I do appreciate I’m instantiating the product model object twice in this, however the plan is to just get it working then optimise! :)
You have to set the custom options for the product before adding it to cart.
$product->setCustomOptions($options);
The in Mage_Sales_Model_Quote::_addCatalogProduct() the custom options will be added to the cart item.
See also here: http://www.magentocommerce.com/boards/viewthread/49659/
By the way: Your code may be pretty slow because you are loading products twice in a foreach loop. You should consider some refactoring by using the product collection instead. Also it looks kind of hackish to directly access the $_SESSION variable here. You could rather use the Checkout Session for that (Mage::getSingleton('checkout/session')).
I have now resolved this, after much headache. You can add a custom option to the cart and not have to instantiate the product object and save a custom option to do this, it can be done via tacking onto an observer, and pulling the quote items.
After tacking onto: sales_quote_add_item
I then used:
public function addCustomData($observer) {
$event = $observer->getEvent();
$quote_item = $event->getQuoteItem();
$quote = $session->getQuote();
$quote_item->addOption(array("product_id" => $quote_item->getProduct()->getId(),
"product" => $quote_item->getProduct(),
"code" => 'PackageId',
"value" => Mage::getModel('MyTuxedo_OPP/Package')->checkPackageId()
));
$quote->save();
}
It is most important to include the product object and id, as the function doesn't use the loaded object for some reason.
You can then get at the object via:
$_item->getOptionByCode('PackageId')->getValue();
Quick piece of handy info, if it dumps a stack trace in front of you it can't find the defined option, lose the getValue() (if using var_dump) function to see if you are getting a null value, otherwise xdebug will give you a ton of hints to get around it.
I am trying to hack the paypal module to change the Order details that are sent via the Express Checkout API.
With PS 1.5.4 and the latest Paypal module, the paypal page looks like this:
Item Name Amount + Tax
Item Description
Item Number
Item Price + Tax
Quantity
...
Item Total Total + Tax
Shipping And Handling Shipping + Tax
Total Total
I would rather have it show prices before tax and then just have a total tax line like this:
Item Name Amount
Item Description
Item Number
Item Price
Quantity
...
Item Total Total
Shipping And Handling Shipping
Total Tax Total Tax
Total Total
I have made modifications to process.php but I must be missing something because I am getting an error with my "hacked" process.php. When I switch it back to default it works fine though.
Here is a link to the original process.php file on the github repo:
https://github.com/PrestaShop/PrestaShop-modules/blob/master/paypal/express_checkout/process.php
The diff of my hacked process.php and the backup of the original:
Comparing files process.php and PROCESS.PHP.BAK
***** process.php
private function setProductsList(&$fields, &$index, &$total) {
...
$fields['L_PAYMENTREQUEST_0_AMT'.$index] = Tools::ps_round($product['price'], $this->decimals);
$fields['L_PAYMENTREQUEST_0_QTY'.$index] = $product['quantity'];
$product_tax = $product['price_wt'] - $product['price'];
$total = $total + (($fields['L_PAYMENTREQUEST_0_AMT'.$index] + $product_tax) * $product['quantity']);
***** PROCESS.PHP.BAK
private function setProductsList(&$fields, &$index, &$total) {
...
$fields['L_PAYMENTREQUEST_0_AMT'.$index] = Tools::ps_round($product['price_wt'], $this->decimals);
$fields['L_PAYMENTREQUEST_0_QTY'.$index] = $product['quantity'];
*****
***** process.php
private function setPaymentValues(&$fields, &$index, &$total, &$taxes){
...
else
$shipping_cost_wt = $this->context->cart->getTotalShippingCost(null, false);
***** PROCESS.PHP.BAK
private function setPaymentValues(&$fields, &$index, &$total, &$taxes){
...
else
$shipping_cost_wt = $this->context->cart->getTotalShippingCost();
*****
***** process.php
private function setPaymentValues(&$fields, &$index, &$total, &$taxes) {
...
$fields['PAYMENTREQUEST_0_AMT'] = $total + $fields['PAYMENTREQUEST_0_SHIPPINGAMT'];
$fields['PAYMENTREQUEST_0_TAXAMT'] = $this->context->cart->getOrderTotal() - $this->context->cart->getOrderTotal(
false);
}
***** PROCESS.PHP.BAK
private function setPaymentValues(&$fields, &$index, &$total, &$taxes) {
...
$fields['PAYMENTREQUEST_0_AMT'] = $total + $fields['PAYMENTREQUEST_0_SHIPPINGAMT'];
}
*****
Here is the error that I get.
Error occurred:
Please try to contact the merchant:
PayPal response:
TIMESTAMP -> 2013-04-04T09:09:42Z
L_ERRORCODE0 -> 10413
L_SHORTMESSAGE0 -> Transaction refused because of an invalid argument. See additional error messages for details.
L_LONGMESSAGE0 -> The totals of the cart item amounts do not match order amounts.
L_SEVERITYCODE0 -> Error
Anyone have any advice.
Add PAYMENTREQUEST_0_ITEMAMT with the sum of the $fields['L_PAYMENTREQUEST_0_AMT'.$index] to your API call.
I met this problem when I started to add shipping cost to PAYMENTREQUEST_0_AMT which is not exactly what is happening to you.
My best advice here is to add PAYMENTREQUEST_0_ITEMAMT whenever the sum of the item costs is different from PAYMENTREQUEST_0_AMT.
Ok,
I finally fixed it. I could write up the specific code fix, but I feel it is too specialized to my situation and this version of prestashop. If someone requests it, I will add it.
The more important part is how I found the bug.
Since the module sends a request and then has to find out from the response if there was an error, var_dump or echo were not possible for debugging.
Instead I wrote a simple custom log file to dump the values to in the process.
I have read that doing so into the apache logs is frowned upon due to potential locking issues.
So TLDR:
Use PHP file functions and log the totals and other vars to a file in the same directory.
How can I find out, if a simple product is part of a configurable product and then get the master product? I need this for the product listing.
Just found out:
$_product->loadParentProductIds();
$parentIds = $_product->getParentProductIds();
Let's say that you have your simple product's Product ID.
To get all the parent configurable product IDs of this simple product, use the following code:-
<?php
$_product = Mage::getModel('catalog/product')->load(YOUR_SIMPLE_PRODUCT_ID);
$parentIdArray = $_product->loadParentProductIds()
->getData('parent_product_ids');
if(!empty($parentIdArray)) {
// currently in the master configurable product
print_r($parentIdArray); // this prints all the parent product IDs using your simple product.
}
?>
I suppose this should get you going.
For Magento 1.4.2 and above use the following method instead:
$configurable_product_model = Mage::getModel(‘catalog/product_type_configurable’);
$parentIdArray = $configurable_product_model->getParentIdsByChild($simple_product_id);
After version 1.4.2.0 the loadParentProductIds() and getParentProductIds() methods are deprecated. Don't ask me why. Personally I kind of liked those methods. So I've reintroduced them to my local Mage classes. This is how:
Copy
app/code/core/Mage/Catalog/Model/Product.php
to
app/code/local/Mage/Catalog/Model/Product.php
and change the loadParentProductIds() method, found around line 1349 to:
public function loadParentProductIds()
{
return $this->_getResource()->getParentProductIds($this);
}
This piece of code will query its resource for its parent product ids. For this to work we'll need to rewrite the getParentProductIds() method in the resource class.
So copy:
app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product.php
to
app/code/local/Mage/Catalog/Model/Resource/Eav/Mysql4/Product.php
Find the deprecated getParentProductIds() method. Should be somewhere around line 535. Overwrite it with the pre 1.4.2.0 code:
public function getParentProductIds($object){
$childId = $object->getId();
$groupedProductsTable = $this->getTable('catalog/product_link');
$groupedLinkTypeId = Mage_Catalog_Model_Product_Link::LINK_TYPE_GROUPED;
$configurableProductsTable = $this->getTable('catalog/product_super_link');
$groupedSelect = $this->_getReadAdapter()->select()
->from(array('g'=>$groupedProductsTable), 'g.product_id')
->where("g.linked_product_id = ?", $childId)
->where("link_type_id = ?", $groupedLinkTypeId);
$groupedIds = $this->_getReadAdapter()->fetchCol($groupedSelect);
$configurableSelect = $this->_getReadAdapter()->select()
->from(array('c'=>$configurableProductsTable), 'c.parent_id')
->where("c.product_id = ?", $childId);
$configurableIds = $this->_getReadAdapter()->fetchCol($configurableSelect);
return array_merge($groupedIds, $configurableIds);
}
Now you once again can do this:
$_product->loadParentProductIds()->getData('parent_product_ids');
Hope this helps you out!
I'd like to create a shopping cart price rule that gives a user 10% off their order when and if they complete a process on my Magento site.
There's a method here that inserts the rule directly to the database. That's a bit invasive for my tastes.
How would I go about this using Magento methods?
As a general principle, you should be able to do anything that the Magento system itself does without writing a single line of SQL. Almost all the Magento data structures use Magento Model classes.
Run the following code somewhere to see what a salesrule/rule model looks like. This assumes you've created a single Shopping Cart Price Rule in the admin with an ID of 1
$coupon = Mage::getModel('salesrule/rule')->load(1);
var_dump($coupon->getData());
Using the dumped data as a guide, we can programatically create a model using the following
$coupon = Mage::getModel('salesrule/rule');
$coupon->setName('test coupon')
->setDescription('this is a description')
->setFromDate('2010-05-09')
->setCouponCode('CODENAME')
->setUsesPerCoupon(1)
->setUsesPerCustomer(1)
->setCustomerGroupIds(array(1)) //an array of customer grou pids
->setIsActive(1)
//serialized conditions. the following examples are empty
->setConditionsSerialized('a:6:{s:4:"type";s:32:"salesrule/rule_condition_combine";s:9:"attribute";N;s:8:"operator";N;s:5:"value";s:1:"1";s:18:"is_value_processed";N;s:10:"aggregator";s:3:"all";}')
->setActionsSerialized('a:6:{s:4:"type";s:40:"salesrule/rule_condition_product_combine";s:9:"attribute";N;s:8:"operator";N;s:5:"value";s:1:"1";s:18:"is_value_processed";N;s:10:"aggregator";s:3:"all";}')
->setStopRulesProcessing(0)
->setIsAdvanced(1)
->setProductIds('')
->setSortOrder(0)
->setSimpleAction('by_percent')
->setDiscountAmount(10)
->setDiscountQty(null)
->setDiscountStep('0')
->setSimpleFreeShipping('0')
->setApplyToShipping('0')
->setIsRss(0)
->setWebsiteIds(array(1));
$coupon->save();
For anyone that's curious, the above is generated code, using the technique discussed here
Have a look at my code.It will add Action condition.
$coupon_rule = Mage::getModel('salesrule/rule');
$coupon_rule->setName($c_data[1])
->setDescription($c_data[2])
->setFromDate($fromDate)
->setToDate($toDate)
->setUsesPerCustomer(0)
->setCustomerGroupIds(array(0,1,2,3)) //an array of customer grou pids
->setIsActive(1)
->setCouponType(2)
->setCouponCode($c_data[0])
->setUsesPerCoupon(1)
//serialized conditions. the following examples are empty
->setConditionsSerialized('')
->setActionsSerialized('')
->setStopRulesProcessing(0)
->setIsAdvanced(1)
->setProductIds('')
->setSortOrder(0)
->setSimpleAction('by_percent')
->setDiscountAmount($c_data[5])
->setDiscountQty(1)
->setDiscountStep('0')
->setSimpleFreeShipping('0')
->setApplyToShipping('1')
->setIsRss(1)
->setWebsiteIds(explode(',',$c_data[6]));
$sku =$c_data[7]; // Put your product SKU here
$skuCond = Mage::getModel('salesrule/rule_condition_product')
->setType('salesrule/rule_condition_product')
->setAttribute('sku')
->setOperator('==')
->setValue($sku);
$coupon_rule->getActions()->addCondition($skuCond);
$coupon_rule->save();
echo "New Coupon was added and its ID is ".$coupon_rule->getId().'<br/>';<br/>
If you want to add Condition for shopping cart price rule then follow this example.
$sku =$c_data[7]; // Put your product SKU here
$found = Mage::getModel('salesrule/rule_condition_product_found')
->setType('salesrule/rule_condition_product_found')
->setValue(1) // 1 == FOUND
->setAggregator('all'); // match ALL conditions
$coupon_rule->getConditions()->addCondition($found);
$skuCond = Mage::getModel('salesrule/rule_condition_product')
->setType('salesrule/rule_condition_product')
->setAttribute('sku')
->setOperator('==')
->setValue($sku);
$found->addCondition($skuCond);
$coupon_rule->save();