Without directly querying the Magento database. How can i remove all the tier prices for a certain product based on quantity and customer group?
Then add new tier prices for this product.
Example:
Remove all tier prices for a product with the SKU: FD001
where the customer group id is: 4
PHP 5.3
Magento 1.4.2
You first have to unset the tier prices and save the product, then add the tier price(s) and save again.
Mage::getModel("catalog/product")->load(123)
->unsTierPrice()
->save()
->setTierPrice(array(
array(
'website_id' => 0,
'all_groups' => 0,
'cust_group' => 4,
'price' => 99.99,
'price_qty' => 1
),
array() // another tier, etc
))
->save();
I ended up solving this by using direct database queries.
As always I'm looking for a better answer.
My Hacky solution:
$product = Mage::getModel('catalog/product')->load(9999);
$dbc = Mage::getSingleton('core/resource')->getConnection('core_write');
$dbc->query('DELETE FROM `catalog_product_entity_tier_price` WHERE `entity_id` = ' . intval($product->getId()) . ' AND `customer_group_id` IN(3,4,6,7) AND qty = 1');
$dbc->query(
'INSERT INTO `catalog_product_entity_tier_price` (`entity_id`,`all_groups`,`customer_group_id`,`qty`,`value`,`website_id`)
VALUES ('. intval($product->getId()) . ',0,' . intval($id) . ',1,' . floatval($price) . ',0)'
);
There's an API available for the product tier price.
Alternatively, you can use some PHP code that uses the Magento code to query for a list of products that match those criteria and then adjust each one. Alan Storm has an article about how Model Collections work (they're the type of object that you would use for this).
Basically it would be something like this to delete the products, I'm not sure how you would set the tier prices, but you can a look at the generated phpdoc documentation. I'm selecting every product that matches customer_group 4 and then deletes each product. You'll have to figure out how to filter things out based on the tier price...
<?php
require 'app/Mage.php';
$app = Mage::app('default'); // Mage_Core_Model_App
$store = $app->getStore(); // Mage_Core_Model_Store
$products = Mage::getModel('catalog/product')->getCollection();
// filter out anything that doesnt match the customer group 4
$products->addFieldToFilter('customer_group', '4');
// loop through each product and delete it
for ($products->load() as $product) {
$product->delete();
}
#omouse's answer pointed us to Magento's API for tier prices. [Updated Link for Magento 1.X's Tier Price API] Note: I'm working with Magento 1.9.2.2.
After looking around for the most efficient way to update a product's tier prices (without using direct SQL), I found the API is the fastest way. It's still slow, but it's better than the other methods I found which recommended doing something like:
// Works, but is slow
$product = Mage::getModel('catalog/product')->load($productId);
$product->unsTierPrice();
$product->save();
$product->load();
$product->setTierPrice($tiers);
$product->save();
Better, I made my array of tier arrays into an array of tier objects, and used the API functions to update the product:
// Build the array of tier level objects
foreach ($tierLevels as $tierLevel) {
$tiers[] = (object) array(
'website' => 'all',
'customer_group_id' => 'all',
'qty' => $tierLevel['min_qty'],
'price' => $tierLevel['unit_price']
);
}
// Use the API to do the update
try {
$tierPriceApi = Mage::getSingleton('catalog/product_attribute_tierprice_api_v2');
$tierPriceApi->update($productId, $tiers);
}
catch (Exception $e) {
// Handle the exception
}
I searched a while to find a solution, which is not pure SQL and does not need a product save, because we want to update tier prices for a catalog of 50k products, so saving all of them is surely not a solution.
I think I finally found a good way.
Mage::getResourceSingleton('catalog/product_attribute_backend_tierprice')
->deletePriceData($product->getId());
$newTierPrice['entity_id'] = $product->getId()
$newTierPrice['all_groups'] = 1;
$newTierPrice['customer_group_id'] = 0;
$newTierPrice['qty'] = 42;
$newTierPrice['value'] = 100;
$newTierPrice['website_id'] = 0;
Mage::getResourceSingleton('catalog/product_attribute_backend_tierprice')
->savePriceData(new Varien_Object($newTierPrice));
Related
I am new in Magento , I created my code to remove the special offer
and to add new price for SKU.
I could remove the special offer but I could not add the new price for SKUs. How can this be done?
My code is as follows:
$productIds[][] = array('DF-12''200','DF-98''300','DF-87''400');
$products = Mage::getResourceModel('catalog/product_collection')
->addAttributeToSelect('*') // <- careful with this
->addAttributeToFilter(
'sku', array('in' => $productIds[0])
)
->load();
foreach ($products as $_product){
$product = Mage::getModel('catalog/product')->load($_product->getId());
$product->setprice($productIds[1])
$product->setOffertext('');
$product->setSpecialFromDate('');
$product->setSpecialToDate('');
$product->setSpecialPrice('');
$product->save();
}
I'm not quite sure how you're forming the array containing your products' SKU and prices, it seems off to me. But since I can only speculate on that part with the given information, an example of how I'd programmatically update smaller product collections might be of more help. This snippet works for me in a backend model:
// array('sku' => price )
$productPricesBySku = array('DF-1234' => 180.00, 'DF-1235' => 99.95);
$collection = Mage::getModel('catalog/product')->getCollection()
->addAttributeToSelect('price') // optional
->addAttributeToSelect('special_from_date') // optional
->addAttributeToSelect('special_to_date') // optional
->addAttributeToFilter('sku', array('in'=>array_keys($productPricesBySku)));
foreach($collection as $_product){
if(!($price=$productPricesBySku[$_product->getSku()])) continue;
// no need to load the product again
// just set the data directly on our collection-item and save it
$_product->setPrice((float)$price)
->setSpecialFromDate('')
->setSpecialToDate('')
->setSpecialPrice('')
->save();
}
(To prevent memory exhaustion on large collections you should use the Mage_Core_Model_Resource_Iterator rather than foreach-loops. You can read up on that here for instance.)
What is the best practice to add filter to loaded collection in Magento? I want to hide items that are out of stocks and last updated date is not within last 30 days.
I try to create a function to hide the products in front end, then I realize it won’t affect the total item counts loaded in pager. I discovered that _getProductCollection() might the method I can use however I can’t add filter onto it.
I’m still new to Magento so any guidances are highly appreciated.
$collection = Mage::getModel('catalog/product')->getCollection();
$collection->addFieldToFilter( 'status', '1' );
$collection->addFieldToFilter(
'created_at',
array(
'lteq'=>$last_day
)
);
$collection->addFieldToFilter(
'updated_at',
array(
'gteq'=>$first_day
)
);
you can filter the out of stock products from the colection from admin settings under system->configuration->catalof->invemtory
and set `
Display Out of Stock Products option to no
Hope this will help you
`
you can add filter in _getProductCollection() function in app\code\core\Mage\Catalog\Block\Product\List.php
from
> $this->_productCollection = $layer->getProductCollection();
to
$this->_productCollection = $layer->getProductCollection()->addAttributeToFilter('created_at', 'your_value'));
`
I am a newbie in magento. Basically, I want to assign multiple products to multiple categories. I have followed this post and I have done the following code which is working fine:
$collection = Mage::getModel('catalog/product')->getCollection();//my coustom collection
$categorys_ids = array(1,2,3,4,5);//Array of ids etc
if ($categorys_ids != NULL && $collection->getData()!= NULL)
{
foreach ($collection as $product)
{
$categories_pd = $product->getCategoryIds();
$product->setCategoryIds(array_merge($product->getCategoryIds(),array($categorys_ids)));
$product->save();
}
}
Now, the main issue is that when I assign set category id for the products it takes a lot of time. I have 200 products and this takes up to two minutes or so, which is a lot of time.
I was wondering if there is a way that I can assign categories to a products array instead of assigning products to categories or something that can be optimized and take less time.
Here is how you can assign multiple products to a category and merge with the existing products.
The example is for one category but you can turn it into a loop to make it work for more.
$categoryId = 6;
$category = Mage::getModel('catalog/category')->setStoreId(Mage_Core_Model_App::ADMIN_STORE_ID)->load($categoryId);
//get the current products
$products = $category->getProductsPosition();
//now attach the other products.
$newProductIds = array(1,2,3,4,5);
foreach ($newProductIds as $id){
$products[$id] = 1;//you can put any other position number instead of 1.
}
//attach all the products to the category
$category->setPostedProducts($products);
//save the category.
$category->save();
If you want an even faster way of doing it you can do direct inserts in the table catalog_category_product.
Just make sure you reindex when you are done.
Here is how I did it, using some fast array management features from newer PHP versions:
<?php
require_once '../../app/Mage.php';
Mage::app();
Mage::app()->setCurrentStore(Mage::getModel('core/store')->load(Mage_Core_Model_App::ADMIN_STORE_ID));
$storeCode = Mage::app()->getStore()->getStoreId();
function addProductsToCategoryId($mergeProductIds, $categoryId, $storeCode) {
// load the $category by $categoryId
$category = Mage::getModel('catalog/category')->setStoreId($storeCode)->load($categoryId);
// build a flipped array of two merged arrays (1) array keys from flipped $mergeProductIds, (2) array keys from product_id keyed array in $category
$categoryProductIds = array_flip(array_merge(array_keys(array_flip($mergeProductIds)),array_keys($category->getProductsPosition())));
// combine array_keys from resulting merge with a matched index array filled with '0'
// THIS resets position of product within category, change this logic if desired
$categoryProductIds = array_combine(array_keys($categoryProductIds), array_fill(0, count($categoryProductIds), '0'));
$category->setPostedProducts($categoryProductIds);
$category->save();
// optional
// return $categoryProductIds;
}
// optional array of category IDs to test against for nin (not in) or in a find_in_set array test
// in the optional example line below, nin (not in) is used
$categoryIds = array(5,8,9,10,11,12,45,46);
$collectionIds = Mage::getModel('catalog/product')->getCollection()
->setStoreId($storeCode)
// optional inclusion of join for category_id
->joinField('category_id', 'catalog/category_product', 'category_id', 'product_id = entity_id', null, 'left')
// optional logic to only gather ids that are, or are not in a given categoryIds array, nin (not in) is shown in example
// ->addAttributeToFilter('category_id', array('nin' => array('finset' => $categoryIds)))
// optional line to test whether product is associated to ANY category
->addAttributeToFilter('category_id', array('null' => true))
// example qualifiers to affect gathered IDs
->addAttributeToFilter('sku', array('like' => 'M-H%'))
->addAttributeToFilter('sku', array('nlike' => '%E'))
->addAttributeToFilter('sku', array('nlike' => '%E#'))
->addAttributeToFilter('sku', array('nlike' => '%Euro'))
->addAttributeToFilter('sku', array('nlike' => '%Euro#'))
->getAllIds()
;
// if using a return value, you can set the results of this to a variable
// to perform further operations against the resulting data
addProductsToCategoryId($collectionIds, 8, $storeCode);
Please note, by default my method DOES NOT preserve any position for products within categories you had set. IT WILL set all positions back to a default of '0'.
Works beautifully. Requires a reindex afterward, fast mass adding. The code is a little complex, so explaining the code in direct context comments made more sense to me in this case.
I included a lot of optional extras in here, but they are all flagged as such and fully explained.
The following code worked for me:
include '../../app/Mage.php';
Mage::app('admin');
Mage::app()->setCurrentStore(Mage_Core_Model_App::ADMIN_STORE_ID);
$product_id = Mage::getModel("catalog/product")->getIdBySku($sku);
$product = Mage::getModel('catalog/product')->load($product_id);
$product->setCategoryIds($category_id);
$product->save();
I am having an issue while saving a product in Magento.
I have a product and have multiple stores (multiple groups and views).
Example Website
Store Group 1
English
French
German
Store Group 2
Bangla
Hindi
Say I am editing the product for store French and clicked on save. But the thing is, the newly saved/entered value for store French is copied to all stores (English, German, etc.).
I need to save the values (even for price values) only for specified stores. Not to all of the stores.
If the solution is a programmatic way, I agree to go with that.
Thanks
Newly Added:
I also want to know about saving price, cost, special price, grouped price, tier price
before saving set the storeId of the store you want to save the product for, same is working with loading a product
$product->setStoreId('2')->setName('Name for Store 2')->save();
$product->setStoreId('2')->load(17)->getName()
good luck!
I have solved the price, cost, special price, grouped price, tier price saving issues myself.
I searched on Google and got an extension called Store View Pricing for free. That solved my 80% of the problem. It just gives the opportunity to save prices on a per store basis.
But, to make the other fields (cost, special price, grouped price, tier price) also behave like the price field/attribute, I needed to change the Scope (is_global: column name at DB) to Store View (0: set to DB). I did run the following query on DB:
SELECT ea.attribute_id, ea.entity_type_id, ea.attribute_code, ea.frontend_label, cea.is_global
FROM `eav_attribute` AS ea
INNER JOIN `catalog_eav_attribute` AS cea ON ea.attribute_id = cea.attribute_id
WHERE `attribute_code` LIKE '%price%'
ORDER BY ea.`attribute_id` ASC
I found the attributes what I needed. And changed the is_global value of them to 0.
After doing so, all is working fine as I need. I know this kinda manual process. I am trying to get a programmatic solution.
I had this issue in 1.5
i overide app\code\core\Mage\Catalog\Model\Resource\Eav\Mysql4\Abstract.php
protected function _insertAttribute($object, $attribute, $value) {
/**
* save required attributes in global scope every time if store id different from default
*/
$storeId = Mage::app()->getStore($object->getStoreId())->getId();
if ($attribute->getIsRequired() && $this->getDefaultStoreId() != $storeId) {
$bind = array(
'entity_type_id' => $attribute->getEntityTypeId(),
'attribute_id' => $attribute->getAttributeId(),
'store_id' => $this->getDefaultStoreId(),
'entity_id' => $object->getEntityId(),
'value' => $this->_prepareValueForSave($value, $attribute)
);
$this->_getWriteAdapter()->insertOnDuplicate($attribute->getBackend()->getTable(), $bind, array('value'));
}
to
protected function _insertAttribute($object, $attribute, $value) {
/**
* save required attributes in global scope every time if store id different from default
*/
$storeId = Mage::app()->getStore($object->getStoreId())->getId();
if ($attribute->getIsRequired() && $this->getDefaultStoreId() != $storeId) {
$table = $attribute->getBackend()->getTable();
$select = $this->_getReadAdapter()->select()
->from($table)
->where('entity_type_id = ?', $attribute->getEntityTypeId())
->where('attribute_id = ?', $attribute->getAttributeId())
->where('store_id = ?', $this->getDefaultStoreId())
->where('entity_id = ?', $object->getEntityId());
$row = $this->_getReadAdapter()->fetchOne($select);
if (!$row) {
$bind = array(
'entity_type_id' => $attribute->getEntityTypeId(),
'attribute_id' => $attribute->getAttributeId(),
'store_id' => $this->getDefaultStoreId(),
'entity_id' => $object->getEntityId(),
'value' => $this->_prepareValueForSave($value, $attribute)
);
$this->_getWriteAdapter()->insertOnDuplicate($attribute->getBackend()->getTable(), $bind, array('value'));
}
}
I am using Magento 1.4.0.1.
I have over 21000 simple products, each entered into a single category.
There are hundreds of categories in my site.
Some products belong in multiple categories.
Is there some way for me to programmatically add products into multiple categories?
In PHP code you can put them into the category while you are importing them.
Say you have a product called $product and a category ID called $category_id
You can set the categories which a product belongs to by doing the following
$categories = array($category_id);
$product->setCategoryIds($categories);
$product->save();
If the product already has categories and you'd like to add one more then you can use getCategoryIds() like this:
$categories = $product->getCategoryIds();
$categories[] = $categoryId;
$product->setCategoryIds($categories);
$product->save();
Or, as mentioned by Joshua Peck in the comments, you can use the category_api model to add or remove a product from a category without affecting it's current category assignments:
Mage::getSingleton('catalog/category_api')
->assignProduct($category->getId(),$product->getId());
Mage::getSingleton('catalog/category_api')
->removeProduct($category->getId(),$product->getId());
I just want to add that you can remove and add with getSingleton category API:
To Remove product from category:
Mage::getSingleton('catalog/category_api')->removeProduct($category->getId(),$product->getId());
To Add Product to Category:
Mage::getSingleton('catalog/category_api')->assignProduct($category->getId(),$product->getId());
This will not overwrite any categories the product is already in
You can write a module (which takes time but potentially quicker at importing your data) or you can put something together with the API (less involving of Magento programming but potentially slower at importing your data).
Your starting point of what-you-know-already, how valuable your time is and how often you will need to run the update should determine your choice.
Here is the Magento API documentation for adding products to categories (see example at foot of page):
http://www.magentocommerce.com/wiki/doc/webservices-api/api/catalog_category
Well I ended up doing this with the API for some reason of laziness. This adds all the visible products into the category with ID 146:
<?php
$client= new SoapClient('http://www.example.com/api/soap/?wsdl', array('trace' => 1, "connection_timeout" => 120));
// Can be added in Magento-Admin -> Web Services with role set to admin
$sess_id= $client->login('apiuser', 'apikey');
// Get the product list through SOAP
$filters = array('visibility' => '4', 'status' => '1');
$all_products=$client->call($sess_id, 'product.list', array($filters));
// Now chuck them into category 146
foreach($all_products as $product)
{ //var_dump($product);
echo $product['sku']."\n";
$doit=$client->call($sess_id, 'category.assignProduct', array('146', $product['sku']));
}
?>
After looking into the Magento API: Magento adds products to categories in the following way:
public function assignProduct($categoryId, $productId, $position = null, $identifierType = null)
{
$category = $this->_initCategory($categoryId);
$positions = $category->getProductsPosition();
$productId = $this->_getProductId($productId);
$positions[$productId] = $position;
$category->setPostedProducts($positions);
try {
$category->save();
} catch (Mage_Core_Exception $e) {
$this->_fault('data_invalid', $e->getMessage());
}
return true;
}
And getting all the asigned products:
public function assignedProducts($categoryId, $store = null)
{
$category = $this->_initCategory($categoryId);
$storeId = $this->_getStoreId($store);
$collection = $category->setStoreId($storeId)->getProductCollection();
($storeId == 0)? $collection->addOrder('position', 'asc') : $collection->setOrder('position', 'asc');;
$result = array();
foreach ($collection as $product) {
$result[] = array(
'product_id' => $product->getId(),
'type' => $product->getTypeId(),
'set' => $product->getAttributeSetId(),
'sku' => $product->getSku(),
'position' => $product->getPosition()
);
}
return $result;
}
The best above answer points to use Mage::getSingleton('catalog/category_api')
->assignProduct($category->getId(),$product->getId());
Anyway that function is pretty slow in case you have a lot of products/categories to update.
This is because the api function assignProduct():
only accept 1 product/category at time
for every call it loads the product and the category and then save the category
( very slow in case you need to update the same category multiple times )
For example suppose you want to assign 10 products to 1 category ... it gonna load and save the same category 10 times ...
( and load all product that is not actually required if you are sure you product ids are corrects)
A faster way
I came up with the below function that is the same as the api one but it loads and save the category only one time.
This function accept an array as parameter $data that needs to contains all collect changes in the form of $category_id => array(all the products you want to assign)
It is trivial to customize it as for your needs adding, for example, a parameter for store_id ( the function use 0 by default) and position information to the products ...
Add category
function assignCategories($data)
{
foreach ($data as $cat_id => $products_ids) {
/** #var $category Mage_Catalog_Model_Category */
$category = Mage::getModel('catalog/category')
->setStoreId(0)
->load($cat_id );
$positions = $category->getProductsPosition();
foreach ($products_ids as $pid) {
$positions[$pid] = null;
}
$category->setPostedProducts($positions);
$category->save();
}
}
Remove category
function removeProduct($data)
{
foreach ($data as $cat_id => $products_ids) {
/** #var $category Mage_Catalog_Model_Category */
$category = Mage::getModel('catalog/category')
->setStoreId(0)
->load($cat_id);
$positions = $category->getProductsPosition();
foreach ($products_ids as $pid) {
unset($positions[$pid]);
}
$category->setPostedProducts($positions);
$category->save();
}
}
note
Saving the category trigger the Category Flat Data and Catalog URL Rewrites reindex ( if they are set as update on save ).
This is not exactly fast ( the API call does the same ) ...
... so you may want to set these reindex to update manually before running your changes and then do a full reindex on them after
( depending on the number of categories/product you are updating this could be the best option )
We can assign multiple products to the category programmatically using magento scripts. Please create an array of the categories and the select the products based on the custom attribute or field.
$newproducts = $product->getCollection()->addAttributeToFilter(array(array('attribute'=>'attribute_label', 'eq'=> 'attribute_id')));
Loop through the products and assign to category as shown below.
$newCategory = array( $list[0] , $list[$key]);
foreach ($newproducts as $prod)
{
$prod->setCategoryIds(array_merge($prod->getCategoryIds(), $newCategory));
$prod->save();
}
Please refer my tutorial which gives a step by step explanation.
Best way to assign the categories to the products.
Reference from core code - app\code\core\Mage\Catalog\Model\Resource\Category.php
$write = Mage::getSingleton('core/resource')->getConnection('core_write');
$categoryProductTable = Mage::getSingleton('core/resource')->getTableName('catalog/category_product');
$productData = array();$position=0;
foreach ($_productCollection as $product) {
$productData[] = array(
'category_id' => (int)$catId1,
'product_id' => (int)$product->getId(),
'position' => (int)$position
);
$productData[] = array(
'category_id' => (int)$catId2,
'product_id' => (int)$product->getId(),
'position' => (int)$position
);
}
$write->insertMultiple($categoryProductTable, $productData);