Cakephp pagination sort by calculated field ( COUNT ) - sorting

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

Related

How to add a new variable in order invoice product table snippet in 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.

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.

Laravel: Eloquent how to update a model and related models in one go

Does anyone know if it is possitble to do the folowing:
Let's say we have a model called User and a model calledd BestFriend. The relation between the User and the best friend is 1:1.
I would like for these cases be able to do something like this, change my city and the city of my friend at the same time.
$me = User::find(1);
$me->update(array(
'city' => 'amsterdam',
'bestfriend.city' => 'amsterdam'
));
So basically I would like to know if Eloquent is smart enough to understand the relationship based on the array key 'bestfriend.city'.
Thanks in advance for any help!
Update:
Found the solution on the Laravel forums but im posting it here as well if someone else is looking for the same thing :)
In the model you add
// In your model...
public function setBestFriendArrayAttribute($values)
{
$this->bestfriend->update($values);
}
And then you can call it like this
$me->update(array(
'city' => 'amsterdam',
'BestFriendArray' => array(
'city' => 'amsterdam'
)
));
Works like a charm!
You don't need to set it on your model. You can do it on your controller like this.
$me = User::find(1)->bestFriend()->update(array(
'city' => 'amsterdam',
'bestfriend.city' => 'amsterdam'
));
I just modified your update a little bit.
Eloquent is pretty smart, but I don't believe it can do that. You would have to update User and BestFriend independently. But once you've done that, Eloquent does have methods for attaching the two.
$me = User::find(1);
$bff= BestFriend::find(1);
$me->city = 'amsterdam';
$bff->city = 'amsterdam';
$me->bestfriend()->associate($bff);
This is of course assuming your User model has a function that looks like...
public function bestfriend()
{
return $this->hasOne('BestFriend');
}
If you want to save same city name to both entities this will do the job:
$me = User::find(1);
$me->update(array(
'city' => 'amsterdam',
));
And in your model:
public function setCityAttribute($value)
{
$this->attributes['city'] = $value;
$this->bestfriend->city= $value;
$this->bestfriend->save();
}

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.

Laravel 4: how to update multiple fields in an Eloquent model?

How can I update multiple fields in an Eloquent model? Let's say I got it like this:
$user = User::where("username", "=", "rok");
And then I have all these model parameters:
$new_user_data = array("email" => "rok#rok.com", "is_superuser" => 1, ...);
I can't just do:
$user->update($new_user_data);
What's the proper way? I hope not a foreach.
The following does work, however. Is this the way to go?
User::where("id", "=", $user->id)->update($new_user_data);
The problem with the last one (besides it being clunky) is that when using it from an object context, the updated fields are not visible in the $this variable.
The method you're looking for is fill():
$user = User::where ("username","rok"); // note that this shortcut is available if the comparison is =
$new_user_data = array(...);
$user->fill($new_user_data);
$user->save();
Actually, you could do $user->fill($new_user_data)->save(); but I find the separate statements a little easier to read and debug.
You are looking for this:
$user = User::where("username","rok")
->update(
array(
"email" => "rok#rok.com",
"is_superuser" => 1,
// ..
)
);
Refer : http://laravel.com/docs/4.2/eloquent#insert-update-delete
I should suggest to use shorter code, such as
$new_user_data=array('a'=>'n','b'=>'m');
$user=User::whereUsername('rok');//camelCase replaces "=" sign
$user->fill($new_user_data)->save();
Or even shorter
$new_user_data=array('a'=>'n','b'=>'m');
User::whereUsername('rok')->update($new_user_data);//camelCase replaces "=" sign
I believe the last statement is easier to debug and looks nicer.
Warning: If your table contains many users named 'rok' both mentioned statements will update all those registers at once. You should always update registers with the id field value.
Try this,
// took required data out of the request
$postData = request(
[
'firstName',
'lastName',
'address1',
'address2',
'address3',
'postcode',
'phone',
'imageURL',
]
);
// persisted it to the database
DB::table('users')
->where('id', auth()->user()->id)
);

Resources