How to add a new variable in order invoice product table snippet in cs cart? - cs-cart

In product table snippet there are already many variables like price, total, subtotal, tax etc.
I want to add a new variable using addon or hook. Any help will be highly appreciated if anyone has idea of it.

general doc is here
https://docs.cs-cart.com/latest/developer_guide/core/documents/snippets.html
as practical example, you can check for 2 schemes:
1. app/addons/reward_points/schemas/documents/order.post.php - it extends Order variable
2. app/addons/suppliers/schemas/snippets/order_products_table.post.php - it extends snippet for products table in invoices

You could modify or add inside your own addon at path:
/app/addons/my_changes/schemas/snippets/order_products_table.post.php
the code that looks something like this:
$schema['custom_tax_name'] = array(
'class' => '\Tygh\Template\Document\Variables\GenericVariable',
'data' => function (\Tygh\Template\Snippet\Table\ItemContext $context) {
$data = array();
$product = $context->getItem();
$data['tax_name'] = getTaxName($custom_val); //you could have function here to get whatever your needs are
return $data;
},
'arguments' => array('#context', '#config', '#formatter'),
'attributes' => array(
'tax_name'
)
);
This is what you are looking for. After this, you need to clear your cache. You will see inside product table in "Total" for exmample, the new variable you already create with name like this:
{{ custom_tax_name.tax_name }}
These names are for the above example purposes. You may have whatever your needs are with something useful and understandable.

Related

Access product information in custom source model for "dynamic" product attribute

I want to programmatically create a product attribute dropdown that shows all categories that the product is assigned to. With this, I would like to define a default category that can be used in the products 'canonical' tag url.
In order to do that I started creating a source model, and in this source model I want to access the product to dynamically create the dropdown options.
Is this possible? If so, how should I go about it? Or am I looking in the wrong place?
I found the answer thanks to a colleague of mine:
To create a product attribute which lists all categories of a product, I can load the product from the Magento registry via Mage::registry('current_product). This leads to the following content in the getAllOptions() method:
public function getAllOptions()
{
$categoryIds = Mage::registry('current_product')->getCategoryIds();
$this->result[] = array(
'value' => '-1',
'label' => 'None'
);
foreach ($categoryIds as $categoryId) {
$this->result[] = array(
'label' => Mage::getModel('catalog/category')->load($categoryId)->getName(),
'value' => $categoryId
);
}
return $this->result;
}

Magento Order Grid : Retrieve quantities and products by order

I am trying to edit the Orders Grid so that I can export data to XML with the quantity of each item sold on each line.
Now I can only access the total amount of the order, which is not sufficient. I would like to build a Grid with the information available for each order in Order View > Information > Ordered items.
Is this possible with a few lines of code ?
Here is what I did for now :
I tried to manually add columns in the _prepareColumns() function from Grid.php.
Basically, I tried to add a quantity column :
$this->addColumn('total_qty_ordered', array(
'header' => Mage::helper('sales')->__('Qty'),
'index' => 'total_qty_ordered',
'filter_index' => 'sales_flat_order.total_qty_ordered',
));
However I do not get any total quantity, and of course I do not get the product split in each order. I do not really know where to look to implement this product split.
Thanks by advance.
EDIT :
Here is what is I get thanks to the extension
Order grid
However, I cannot export this product split because the last column is a kind of 'embedded' of other information. So I get an empty column in XML.
I don't usually recommend extensions -- I believe most of them can be a bit more trouble than they are worth, but in this case, I do recommend this one:
http://www.magentocommerce.com/magento-connect/enhanced-admin-grids-editor.html
It's free -- code is fairly clean and you can add that by clicking the grid customization button on the orders page, clicking more options, then finding the items drop down and adding those columns in. Will show you a table of all of the items.
If you want to post the code you've written the custom way, I can help you customize that to do the job too.
We published a whole blog post on how to add any data to your order grid. Hope this will help you! https://grafzahl-io.blogspot.de/2016/11/how-to-display-m2e-order-data-or.html
So the solution would be to copy the Sales Grid Block to a local module and add your column like this example:
$this->addColumn('order_type', array(
'header' => Mage::helper('sales')->__('Order Type'),
'width' => '100px',
'align' => 'left',
'index' => 'order_type',
'renderer' => 'yourmodule/adminhtml_sales_grid_renderer_m2eAttribute',
'filter_condition_callback' => array($this, '_filterM2eConditionCallback')
));
The filter_condition_callback is a method in the Grid Block. The renderer is another class as you can see in the namespace. In the renderer you can define what is displayed in your column. The filter_condition_callback defines how the grid should act, in case someone will filter by your custom column.
It will look like this:
/**
* filter callback to find the order_type
* of orders through m2e (amazon, ebay, ...)
*
* #param object $collection
* #param object $column
* #return Yourname_Yourmodule_Block_Adminhtml_Sales_Order_Grid
*/
public function _filterM2eConditionCallback($collection, $column) {
if (!$value = $column->getFilter()->getValue()) {
return $this;
}
if (!empty($value) && strtolower($value) != 'magento') {
$this->getCollection()->getSelect()
// join to the m2mepro order table and select component_mode
->join(
'm2epro_order',
'main_table.entity_id=m2epro_order.magento_order_id',
array('component_mode')
)
->where(
'm2epro_order.component_mode = "' . strtolower($value) . '"');
} elseif(strtolower($value) == 'magento') {
$this->getCollection()->getSelect()
->join(
'm2epro_order',
'main_table.entity_id=m2epro_order.magento_order_id',
array('component_mode')
)
->where(
'm2epro_order.component_mode = NULL');
}
return $this;
}
As you can see, there are two joins to collect the data we need to filter for.
This is how the renderer looks like which will display the data in the grid:
class Yourname_Yourmodule_Block_Adminhtml_Sales_Grid_Renderer_M2eAttribute
extends Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Abstract
{
public function render(Varien_Object $row)
{
// do whatever you need, to display your data
// get the id of the row order data
$orderId = $row->getEntityId();
// get the related m2e order data
$orders = Mage::getModel('M2ePro/Order')
->getCollection()
->addFieldToSelect('component_mode')
->addFieldToFilter('magento_order_id', $orderId);
if($orders) {
$data = $orders->getFirstItem()->getData();
if(isset($data['component_mode'])) {
return ucfirst($data['component_mode']);
}
}
// return the string "magento" if there is no m2e relation
return 'Magento';
}
}
The link will show you other example, how to show data in the order grid.

Issues with saving product on multiple stores in magento

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'));
}
}

Magento: Display number of times a product has been sold in the Product Grid

I am looking to add a column to the product grid (in the admin area to be clear) to display how many times this product has been sold. Here is what I have so far after piecing together from several other posts:
In app/code/local/Namespace/Qtysold/Block/Adminhtml/Catalog/Product/Grid.php
<?php
class Namespace_Qtysold_Block_Adminhtml_Catalog_Product_Grid extends Mage_Adminhtml_Block_Catalog_Product_Grid
{
/* Overwritten to be able to add custom columns to the product grid. Normally
* one would overwrite the function _prepareCollection, but it won't work because
* you have to call parent::_prepareCollection() first to get the collection.
*
* But since parent::_prepareCollection() also finishes the collection, the
* joins and attributes to select added in the overwritten _prepareCollection()
* are 'forgotten'.
*
* By overwriting setCollection (which is called in parent::_prepareCollection()),
* we are able to add the join and/or attribute select in a proper way.
*
*/
public function setCollection($collection)
{
/* #var $collection Mage_Catalog_Model_Resource_Product_Collection */
$store = $this->_getStore();
if ($store->getId() && !isset($this->_joinAttributes['qty_sold'])) {
$collection->joinAttribute(
'qty_sold',
'reports/product_collection',
'entity_id',
null,
'left',
$store->getId()
);
}
else {
$collection->addAttributeToSelect('qty_sold');
}
echo "<pre>";
var_dump((string) $collection->getSelect());
echo "</pre>";
parent::setCollection($collection);
}
protected function _prepareColumns()
{
$store = $this->_getStore();
$this->addColumnAfter('qty_sold',
array(
'header'=> Mage::helper('catalog')->__('Qty Sold'),
'type' => 'number',
'index' => 'qty_sold',
),
'price'
);
return parent::_prepareColumns();
}
}
A couple of things here. 1) $store->getId() returns 0 so it never goes into that first block in setCollection, is that correct behavior since it is the Admin area? 2) If I force the joinAttribute to run, it causes an exception (Invalid entity...) which is sort of expected since reports doesn't appear to really be an entity, but I'm not really clear on this whole entity business. 3) In other examples (like this one: http://www.creativemediagroup.net/creative-media-web-services/magento-blog/30-show-quantity-sold-on-product-page-magento) they use something like this:
$_productCollection = Mage::getResourceModel('reports/product_collection')
->addOrderedQty($from, $to, true)
->addAttributeToFilter('sku', $sku)
->setOrder('ordered_qty', 'desc')
->getFirstItem();
And I am not sure if there is any way to "join" with this reports/product_collection or if there is any way to recreate its "addOrderedQty" data?
This is on Magento 1.7. I can provide further details as needed. I am a beginner with Magento development so any help at all (including resources to learn) would be greatly appreciated. Thanks!
1) Admin Area does have the store ID = 0, so yes this will always be returned.
this will also mean your conditional always fails and never does any joining, it will just try and add the qty_sold to the collection, which of course will not work as it's not part of that entities data.
The issue is the joinAttribute method will only work on "Entites" (it depends on the classes used by the models you are trying to join), and as the reports/product collection isn't one of these you will have to join another way using methods like this:
join() or joinLeft()
with this kind of thing:
$collection->getSelect()
->join(
'customer_entity',
'main_table.customer_id = customer_entity.entity_id',
array('customer_name' => 'email')
);
Was also dealing with this issue and solved it as follows:
protected function _prepareCollection() {
// [...]
$collection = Mage::getModel('catalog/product')->getCollection();
// Add subquery join to sales_flat_order_item to get a SUM of how many times this product has been ordered
$totalOrderedQuery = Mage::getSingleton('core/resource')->getConnection('core_read')
->select()
->from('sales_flat_order_item', array('product_id', 'qty_ordered' => 'SUM(`qty_ordered`)'))
->group('product_id');
$collection->joinField('qty_ordered', $totalOrderedQuery, 'qty_ordered', 'product_id=entity_id', null, 'left');
// [...]
return parent::_prepareCollection();
}
Note the usage of $collection->joinField(), tried using the mentioned $collection->getSelect()->join(), but it gives all sorts of issues with sort orders and filtering due to the internal Magento data collection not recognizing the column as part of the data set.
Then in _prepareColumns you can simply add the column:
$this->addColumn('qty_ordered', array(
'header' => Mage::helper('xyz')->__('Total quantity ordered'),
'sortable' => true,
'width' => '10%',
'index' => 'qty_ordered',
));
You might want to add a renderer which checks and converts a NULL value to '0', otherwise you will end up with empty columns if the product hasn't been ordered yet (you could also fix this in the SELECT query by using IFNULL for qty_ordered).
I was able to achieve what I needed (though it is hardly perfect) thanks to that tip from Andrew. Here is my updated code for setCollection:
public function setCollection($collection)
{
/* #var $collection Mage_Catalog_Model_Resource_Product_Collection */
$store = $this->_getStore();
$collection->getSelect()->joinLeft(
array('oi' => 'sales_flat_order_item'),
'e.entity_id = oi.product_id',
array("qty_sold" => 'SUM(oi.qty_ordered)')
)->group('e.entity_id');
parent::setCollection($collection);
}
I was curious if any Magento developers see a serious flaw in this approach (I'm aware of some of the business logic such as not counting products that were canceled, etc) or recommendations for a better approach. Either way this is "good enough" for now and meets my needs, so I'll post in case others need it.

Cakephp pagination sort by calculated field ( COUNT )

I had a problem sorting a paginated list when using a calculated field such as COUNT() in cakephp 1.3
Let's say that i have two models: Article and Comments (1 article x N comments) and i want to display a paginated list of the articles including the number of comments for each one. I'd have something like this:
Controller:
$this->paginate = array('limit'=>80,
'recursive'=>-1,
'fields'=>array("Article.*","COUNT(Comment.id) as nbr_comments"),
'joins'=>array(array( 'table' => 'comments',
'alias' => 'Comment',
'type' => 'LEFT',
'conditions' => array('Comment.article_id = Article.id'))
),
'group'=>"Article.id"
);
(i had to overwrite the findCount() method in order to paginate using group by)
The problem is that in the view, the sort() method won't work:
<th><?php echo $this->Paginator->sort('nbr_comments');?></th> //life is not that easy
I was able to create a workaround by "cheating" the pagination and sort:
Controller
$order = "Article.title";
$direction = "asc";
if(isset($this->passedArgs['sort']) && $this->passedArgs['sort']=="nbr_comments")
$order = $this->passedArgs['sort'];
$direction = $this->passedArgs['direction'];
unset($this->passedArgs['sort']);
unset($this->passedArgs['direction']);
}
$this->paginate = array(... 'order'=>$order." ".$direction, ...);
$this->set('articles', $this->paginate());
if($order == "clicks"){
$this->passedArgs['sort'] = $order;
$this->passedArgs['direction'] = $direction;
}
View
<?php $direction = (isset($this->passedArgs['direction']) && isset($this->passedArgs['sort']) && $this->passedArgs['sort'] == "nbr_comments" && $this->passedArgs['direction'] == "desc")?"asc":"desc";?>
<th><?php echo $this->Paginator->sort('Hits','clicks',array('direction'=>$direction));?></th>
And it works.. but it seems that is too much code for something that should be transparent to the developper. (It feels like i'm doing cake's work) So i'm asking if there's another simpler way. Maybe cake has this functionallity but decided to hide it.. o_O.. there's nothing about this on the documentation, and i haven't found another good solution on S.O... how do you do it?
Thanks in advance!
maybe your problem can be solved using Virtual fields ?
If you create a custom field for your computed field, you will be able to sort using Paginator->sort() method on that custom field.
I found that solution there in the comments (there is no need to customize the paginate method etc. using custom fields instead of in adnan's initial solution).
What you may want to do is use Cake's counterCache functionality in your model definition. Rather than calculating the comment counts at read time, you add nbr_comments as an int field in your articles table. Every time a Comment is inserted or deleted, nbr_comments will be updated automatically.
In your Comment model:
var $belongsTo = array(
'Article' => array(
'counterCache' => 'nbr_comments'
)
);
Now you can just use $this->Paginator->sort('Article.nbr_comments'); You wont need to do anything funky in your controller.
My solution for this problem was the following:
put your calculate field as a virtual field
$this->Comment->virtualFields = array(
'nbr_comments' => 'COUNT(Comment.id)'
);
then, you can use that field, in your paginate order variable
$order = array("Comment.nbr_comments" => "DESC");
$this->Paginator->settings = array("order" => $order);

Resources