protected function _prepareCollection()
{
$collection = Mage::getResourceModel($this->_getCollectionClass());
$collection->getSelect()->join('sales_flat_order_address', 'main_table.entity_id = sales_flat_order_address.parent_id',array('company'));
$this->setCollection($collection);
}
I use the above code to add a company field on order listing grid.
but it shows "Item (Mage_Sales_Model_Order) with the same id "1038" already exist"
Maybe try an inner join instead:
$collection->getSelect()->joinInner(
array(
'order_address' => 'sales_flat_order_address'
),
'order_address.parent_id = main_table.entity_id'
);
Also, echo out your SQL and see what the collection returns, then try and run that sql on your database. This should help you figure out what you're doing wrong.
echo $collection->getSelect()->__toString();
Keep in mind that alone won't add the column to the grid. You'll need to add the column in _prepareColumns()
EDIT:
Actually, thinking about it, an inner join probably won't help you here. The issue you have is that sales_flat_order_address contains multiple entries for every parent_id, so you need to account for duplicates by using GROUP BY or SELECT DISTINCT. try something like this:
$collection->getSelect()->joinInner(
array(
'order_address' => 'sales_flat_order_address'
),
'order_address.parent_id = main_table.entity_id'
)->group(array('entity_id', 'parent_id'));
It's not perfect, but what you're trying to do is inherently imperfect, as one order has many addresses. The alternative is to explicitly join with only the billing address, or only the shipping address.
Related
I would like to sort products in a category based on the most sold qty by using reports. I tried below code but it's only showing products that are sold but not unsold products. I want to know how do I get both sold and unsold products in the product collection sort by desc order. Thanks
if (Mage::helper('abc_xyz')->displayMostSoldProducts()) {
$collection = Mage::getResourceModel('reports/product_collection')
->addAttributeToSelect('*')
->addOrderedQty()
->addCategoryFilter($category)
->setOrder('ordered_qty', 'desc');
}
When sending a query to the product collection, you are using the standard methods. They, in turn, rewrite the Select and make it use the data from the sales_flat_quote_item table. As you probably know, this table doesn't store data on the sold products.
Hence, you need to rewrite the Select the way you want to have it. It should be something like this:
$collection = Mage::getResourceModel('reports/product_collection');
$select = $collection->getSelect();
$select->joinLeft(
array('sfoi' => $collection->getTable('sales/order_item')),
'`e`.`entity_id` = `sfoi`.`product_id` ',
array(
'ordered_qty' => 'SUM(sfoi.qty_ordered)',
'order_items_name' => 'sfoi.name',
'entity_id' => 'sfoi.product_id',
))
->where('sfoi.parent_item_id IS NULL')
->group('e.entity_id');
In my case, I've taken the collection itself and added all the necessary info.
As a result, I've got this:
https://gyazo.com/4a0b02298173f2c9356b7d7df783c0ff
That includes all unsold products:
https://gyazo.com/24859373f52d31dba50560a02e0d0d79
But note, when solving this issue, you should take into account all peculiarities of your case. Unfortunately, there's no any standard method to implement this.
I am writing a Plugin and added a own db table to the magento database.
Now i want to get some data in sorted order from the table and i added the code bellow.
But everytime i dump the result of the last line i get the data in the order they are in the table not sorted by the given parameter. Can anyone help me with this?
$collection = $this->getCollection();
$collection->setOrder(array('fieldname' => 'asc', 'fieldname' => 'desc'));
$collection->getFirstItem()->getData();
You can call setOrder function multiple times for different columns.
$collection = $this->getCollection()
->setOrder('fieldname', 'asc')
->setOrder('fieldname2');
Note: Direction DESC will be used by default.
I wrote a post last year about this. The first part, how to sort on a 1st-level Association, was answered (thanks again!) but the second part of the question, about how to sort on a 2nd-level Association, was never answered. It wasn't a big issue, and we ran out of time so I never actually implemented it. But now we're updating the web, and the client wants this functionality, namely, with the following Models:
Company belongsTo City
City belongsTo Country, hasMany Companies
Country hasMany Cities
In the Companies page I want to sort on City.Country.name. Even putting recursive=2, it doesn't work. Cake ignores my 'order' condition; in the generated SQL there simply is no 'order by' at all.
It works fine if I sort on sort on City.name, however.
Is there any way to do this? I've been scouring the docs and Stackoverflow. I've looked at virtual fields, custom queries.
One way that seemed to look promising was to use Model->query() in the CompaniesController:
$companies = $this->Company->query("SELECT * FROM companies com left join cities c on c.id = com.city_id left join countries c2 on c2.id = c.country_id order by c2.name");
But, is this the best/only way to go? And do I not have to now worry about overriding pagination? I don't mind that but I would still like to use "normal" built-in pagination elsewhere for Companies. Will that be possible?
Also, in the examples in the docs, it says to do something like Model->query('SELECT * FROM pictures AS Picture LIMIT 2') so the resulting Array will use the model name as the array key. But how can I do this with my complex query? Where would the "AS" go?
I was rather hoping I'd be able to avoid having to do it like this though. Is there a simpler way to do it?
EDIT
Hi, thanks for your help. By "pagination technique" you mean Cake's built-in pagination? Yes, that's what I want. My default paging conditions in the controller are:
$this->paginate = array('conditions' => 'order' => array('Company.name' => 'ASC');
And it sorts on company name. The SQL is
SELECT Company.id, etc. FROM companies AS Company LEFT JOIN cities AS City ON Company.city_id = City.id order by Company.name
And when I create paging links in the View like this
$paginator->sort('City.name')
it adds these parameters to the url
.../companies/sort:City.name/direction:desc
it sorts on City name. The SQL is
SELECT Company.id, etc. FROM companies AS Company LEFT JOIN cities AS City ON Company.city_id = City.id order by City.name
But when I try this:
$paginator->sort('City.Country.name');
it adds these parameters to the url
.../companies/sort:City.Country.name/direction:asc
and the generated SQL is
SELECT Company.id, etc. FROM companies LEFT JOIN cities AS City ON (Company.city_id = City.id)
It completely ignores the 'sort' condition, and there is no 'order by' at all. I'm not sure why. Maybe Cake just can't do this? I have 'recursive' set to 2.
The other option is Model->query, which I tried and got working, but I'd rather not use because I would have to override paginate and paginateCount methods, which isn't that hard, but the problem is that on the same page, and on other pages, I am already using 'normal' Cake paging for Companies. So if I override paginate and paginateCount, won't I have to change all of those pages to use the new, over-ridden paging? I'd like to avoid that, because it seems like overkill, and is working fine everywhere else, except for this one case.
Any assistance much appreciated.
Bob
Well, in the end I managed it, after perusing the docs and many examples online, like this:
First, in the Model, declare and implement custom findMethods.
class Company extends AppModel {
public $findMethods = array('companiesOrderByCountry' => true);
protected function _findCompaniesOrderByCountry($state, $query, $results = array()) {
if ($state === 'before') {
$query['joins'] = array(
array(
'table' => 'cities',
'alias' => 'City',
'type' => 'LEFT',
'conditions' => 'City.id = Company.city_id'
),
array(
'table' => 'countries',
'alias' => 'Country',
'type' => 'LEFT',
'conditions' => 'Country.id = City.country_id'
)
);
$query['order'] = array('Country.name' => 'asc');
return $query;
}
return $results;
}
Then in the Controller, conditionally call it, based on named params in request
if (!empty($this->request->params['named']['sort']) && $this->request->params['named']['sort'] == 'Country.name') {
// here we need to sort on Country.name. Call custom findMethod in Model
$this->paginate = array('companiesOrderByCountry','limit' => 10);
$companies = $this->paginate();
$this->set(compact('companies'));
} else {
// do a normal search
$this->paginate = array('limit' => 10,'conditions' => array('order' => array('Company.nombre' => 'ASC' ));
$companies = $this->paginate('Company');
$this->set('companies', $companies);
}
Lastly, create a link, passing the named parameter, in the .ctp
<?php $paginator = $this->Paginator; ?>
<?php echo $paginator->sort('Country.name', __('Country')); ?>
There's probably a more elegant way to do this, but I was sick of dealing with it. The generated sql is just what I need and now I can sort on Country, which is a 2nd-level (Company->City->Country) association.
Hope this helps someone someday!
Bob
If you want to try using the query method, then I would not use the asterisks (*). Instead explicitly list the fields you want.
Your query should look something like this: {My personal best practice, use COALESCE on fields that may be NULL - especially when using a LEFT JOIN}
SELECT companies.name AS CompanyName,
COALESCE(cities.name, 'Unknown') AS CityName,
COALESCE(countries.name, 'Unknown') AS CountryName
FROM companies
LEFT JOIN cities
ON companies.city_id = cities.id
LEFT JOIN countries
ON cities.country_id = countries.id
ORDER BY CountryName, CityName
Use AS to separate the field from its alias. It also separates a table from its alias ( e.g., the examples you listed.) AS is optional, but I feel it adds to readability.
--
(If you want to try the pagination technique, please show the MySQL SELECT statement that is failing when RECURSIVE = 2, along with what the controller's code looks like - specifically the 'order'=> array)
I have one maganto installation (1.4.0.1) with several web site and several store/store view. Also I have different product in all store views. When I go My Account-> My orders in the table is show the orders from all stores. Can I config somewhere or change something in code to have list with order just from current open store view?
With other word, if I go on store 2 on my web site mywebsite.com/store2 in my orders mywebsite.com/store2/sales/order/history/ it show orders history from all web sites, but I need me to show orders just form "store2".
I hope someone have answer for this or similar issue and I will be grateful if help me.
Jordan
Pretty simple.
Override the respective Sales order history block app\code\core\Mage\Sales\Block\Order\History.php file into your local code pool
and just filter your collection by current store Id.
$store_id = Mage::app()->getStore()->getId();
$orders = Mage::getResourceModel('sales/order_collection')
->addFieldToSelect('*')
->addFieldToFilter('customer_id', Mage::getSingleton('customer/session')->getCustomer()->getId())
->addFieldToFilter('state', array('in' => Mage::getSingleton('sales/order_config')->getVisibleOnFrontStates()))
->addFieldToFilter('store_id',$store_id)
->setOrder('created_at', 'desc')
;
Note these lines in the code above
/*Current Store View ID*/
$store_id = Mage::app()->getStore()->getId();
/*Filtering the order collection by current store id*/
->addFieldToFilter('store_id',$store_id)
/*GetCurrent Website Name*/
$currentWebsiteName = Mage::app()->getStore()->getWebsite()->getName();
$currentWebsiteName_LIKE_PHRASE = '%'.$currentWebsiteName.'%';
/*Filter the Order Collection by Current Website Name*/
$orders = Mage::getResourceModel('sales/order_collection')
->addFieldToSelect('*')
->addFieldToFilter('customer_id', Mage::getSingleton('customer/session')->getCustomer()->getId())
->addFieldToFilter('state', array('in' => Mage::getSingleton('sales/order_config')->getVisibleOnFrontStates()))
->addAttributeToFilter('store_name', array('like' => $currentWebsiteName_LIKE_PHRASE))
->setOrder('created_at', 'desc');
/*Note the Below Line*/
->addAttributeToFilter('store_name', array('like' => $currentWebsiteName_LIKE_PHRASE))
Am filtering by current website name. Just got the sales_flat_order table can view this column 'store_name' where you can find it also contains the website Name.
Unfortunately i can't find website id in this main order table or in its related table to make a JOIN statement to the order collection. I think you might have some order table with **_website suffix**, if thats the case you can easily join that to our existing collection
I want to pre-filter* data in the invoice grid visible in Magento's admin panel.
Here is a question that I asked earlier, and this one is related to the solution presented for that, hence it might act as a good explanation.
So, I am modifying the Mage_Adminhtml_Block_Sales_Invoice_Grid::_prepareCollection method so that it first fetches customer referred by the logged in admin. Then it will fetch orders from these customer(s) - ideally only the order id's - Then join this collection to sales/order_invoice_grid, to get invoices to be listed for this admin.
Based on the last answer and using these docs, following are 3 ways I have tried joining this information: (Code Sample 1)
$collection = Mage::getResourceModel('customer/customer_collection');
$collection->joinTable('sales/order_grid', 'customer_id=entity_id', array('*'));
$collection->joinTable('sales/invoice_grid', 'order_id=main_table.entity_id', array('*'));
When I do the above, I see the following error:
A joint field with this alias (0) is already declared.
#0 /var/www/magento/app/code/core/Mage/Eav/Model/Entity/Collection/Abstract.php(706): Mage::exception('Mage_Eav', 'A joint field w...')
#1 /var/www/magento/app/code/local/Myproject/Adminhtml/Block/Sales/Invoice/Grid.php(41): Mage_Eav_Model_Entity_Collection_Abstract->joinTable('sales/invoice_g...', 'order_id=main_t...', Array)
#2 /var/www/magento/app/code/core/Mage/Adminhtml/Block/Widget/Grid.php(576): Myproject_Adminhtml_Block_Sales_Invoice_Grid->_prepareCollection()
#3 /var/www/magento/app/code/core/Mage/Adminhtml/Block/Widget/Grid.php(582): Mage_Adminhtml_Block_Widget_Grid->_prepareGrid()
If I remove the second call to joinTable, the above code works, but it is not what I want.
The other method I tried is with this code:
$collection = Mage::getResourceModel('customer/customer_collection');
$collection->joinTable('sales/order_grid', 'customer_id=entity_id', array('entity_id as order_entity_id'));
$collection->joinTable('sales/invoice_grid', 'order_id=main_table.entity_id', array('*'));
Here the error appears in the second line, where I am actually trying to alias the field order.entity_id so that it does not conflict with invoice tables entity_id. However that produces an error like:
Item (Mage_Customer_Model_Customer)
with the same id "1" already exist
I only need order id's so that I can get related invoices, which suggests that I can also use joinField function, which I tried as follows:
$collection = Mage::getResourceModel('customer/customer_collection');
$collection->joinField('order_entity_id', 'sales/order_grid', 'entity_id', 'customer_id=entity_id' , null, 'left');
But it gives me the following error:
Item (Mage_Customer_Model_Customer) with the same id "1" already exist
I am looking for a solution that joins customer->invoices.
By pre-filter I mean that data listed in the grid is filtered even before anything is presented in the grid.
Ok, now my code looks like:
$collection =
Mage::getResourceModel('customer/customer_collection');
$collection->joinTable('sales/order_grid', 'customer_id=entity_id', array('entity_id' => 'order_entity_id'));
And the error that I get is:
SELECT `e`.*, `sales_flat_order_grid`.`order_entity_id` AS `entity_id` FROM `customer_entity` AS `e`
INNER JOIN `sales_flat_order_grid` ON (sales_flat_order_grid.customer_id=e.entity_id) WHERE (e.entity_type_id = '1') ORDER BY `e`.`created_at` desc, `e`.`created_at` desc LIMIT 20
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'sales_flat_order_grid.order_entity_id' in 'field list'
Here is the total of my test script. To use put it in a file at the Magento root and type it's URL directly in your browser, it is not handled by Magento's controllers. This is a good way of experimenting as it is not as influenced by other modules, page layouts, etc.
<pre><?php
require 'app/Mage.php';
Mage::app();
$collection = Mage::getResourceModel('customer/customer_collection');
$collection->getSelect()->reset('columns');
$collection->joinTable('sales/order_grid', 'customer_id=entity_id', array('order_entity_id' => 'entity_id'));
$collection->joinTable('sales/invoice_grid', 'order_id=order_entity_id', array('*'));
foreach ($collection as $invoice)
print_r($invoice->debug());
?></pre>
As with your previous question I choose to reset the initial columns because I don't believe in giving the database more work than necessary. However it's not essential, the test still succeeds without it.
If this doesn't work in your installation then we need to consider what that outside influence could be.
The error "A joint field with this alias (0) is already declared." occurs because it uses array keys as aliases. Since you have two joinTable() calls, each with an array, it is trying to use the zero-based index of both and obviously having a conflict.
So instead of
array('entity_id as order_entity_id')
try
array('entity_id' => 'order_entity_id')
to avoid the conflict.
I finally did achieve this by going from invoice->order->customer as 'Anda B' suggested. I am just pasting my solution here as a reference, but will be using this solution from clockworkgeek, since it seems much cleaner. And my solution still needs to be made cleaner by getting the 'id' of eav_attribute (agent_id) from the database at runtime, instead of hard coding it, as pasted here:
class Myproject_Adminhtml_Block_Sales_Invoice_Grid extends Mage_Adminhtml_Block_Sales_Invoice_Grid
{
const AGENT_ID_ATTRIBUTE_ID = 118;
protected function _prepareCollection()
{
$collection = Mage::getResourceModel($this->_getCollectionClass());
$collection->join('order_grid', 'order_id = order_grid.entity_id', array ('order_entity_id' => 'order_grid.entity_id'));
$collection->getSelect()->join( 'customer_entity', 'customer_id = customer_entity.entity_id', array('customer_entity_id' => 'entity_id', 'email'));
$collection->getSelect()->joinLeft( 'customer_entity_int', 'customer_entity_int.entity_id = customer_entity.entity_id AND attribute_id = ' . Myproject_Adminhtml_Block_Sales_Invoice_Grid::AGENT_ID_ATTRIBUTE_ID,
array('attribute_entity_id' => 'customer_entity_int.entity_id', 'attribute_id' , 'value'));
//Apply Desired Data Filters here
$this->setCollection($collection);
return $collection;
It gives you this error "Item (Mage_Customer_Model_Customer) with the same id "1" already exist" because a customer can have multiple orders and so could have two or more entries with the same customer id - you are creating a collection of customers and you must have unique entries in the collection.
You have to start from invoices and join them with the customers.
The simplest way I found out from the magento forum ,
In the
protected function _prepareCollection()
{
$collection = Mage::getResourceModel('customer/customer_collection')
We can use custom queries as
$collection->getSelect()->columns(array('filename' => new Zend_Db_Expr ("(SELECT filename FROM cat WHERE customer_id =e.entity_id)")));
and it works
$this->setCollection($collection);
var_dump($collection);