UPDATED: Magento add customer attribute filter to order grid - mysql

I have extended the Mage_Adminhtml_Block_Sales_Order_Grid class with a custom module to add several customer attributes (Magento EE 1.10) to the grid.
Two of the attributes I added are text fields (i.e. they live in the customer_entity_varchar table, and I was able to add them to the collection and display them in the grid. So far so good.
A third attribute is a select, so the values live in the customer_entity_int, the eav_attribute_option and the eav_attribute_option_value tables. I added the necessary values to the collection (using $collection->getSelect()->joinLeft(.....). Again, so far so good.
My problem is being able to display and filter the attribute at the same time.
Inside the _prepareColumns() function in my MyCompany_MyModule_Block_Adminhtml_Order_Grid class, if I add a column like this, - as expected - I can display the values of the attribute on each row, but I don't get a drop down filter in the header:
protected function _prepareColumns()
{
...
$this->addColumn('bureau', array(
'header' => Mage::helper('sales')->__('Bureau'),
'index' => 'bureau',
'type' => 'text'
));
...
}
Following the example of status, and adding the column like this, gives me the drop down filter in the header, but it no longer displays the values for the attribute in each row:
protected function _prepareColumns()
{
...
$this->addColumn('bureau', array(
'header' => Mage::helper('sales')->__('Bureau'),
'index' => 'bureau',
'type' => 'options',
'options' => $this->_getBureauOptions(),
'filter_index' => 'value_option_table.option_id'
));
...
}
protected function _getBureauOptions()
{
$bureau = Mage::getResourceModel('eav/entity_attribute_collection')
->setCodeFilter('bureau')
->getFirstItem();
$bureauOptions = $bureau->getSource()->getAllOptions(false);
$optionsArr = array();
foreach ($bureauOptions as $option) {
$optionsArr[$option['value']] = $option['label'];
}
return $optionsArr;
}
Any advice / explanation would be much appreciated.
UPDATE:
It turns out that my code also causes a SQL error in a multi-website environment when an admin user only has permissions for some websites:
"SQLSTATE[23000]: Integrity constraint violation: 1052 Column 'store_id' in where clause is ambiguous"

#clockworkgeek had the answer to the first part of my question.
The problem was that my joinLeft() was retrieving text values from the attribute options, while I should have been retrieving integer values when using 'type => 'options'.
Once I changed my joinLeft() to only retrieve integer values from customer_entity_int (actually a simpler join), the filtering and display worked flawlessly - thank you sir.
I will re-post my second issue (about SQL errors caused by permissions) as a separate question.

Related

Magento 1.9 add custom column to associated products grid

We have an existing customization that appears to have broken when we upgraded from 1.7 to 1.9 community.
The customization adds a column to the associated products grid.
The customization is a local override of
app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Super/Group.php
This was done before I started on the project
$this->addColumn('breakdown_part_no', array(
'header' => Mage::helper('catalog')->__('Part No'),
'name' => 'breakdown_part_no',
'type' => 'varchar',
'index' => 'breakdown_part_no',
'width' => '120px',
'editable' => true,
));
This was added to _prepareColumns()
Another customization was added to method getSelectedGroupedProducts()
public function getSelectedGroupedProducts()
{
$associatedProducts = Mage::registry('current_product')->getTypeInstance(true)
->getAssociatedProducts(Mage::registry('current_product'));
$products = array();
foreach ($associatedProducts as $product) {
$products[$product->getId()] = array(
'qty' => $product->getQty(),
'position' => $product->getPosition(),
'breakdown_part_no' => $product->getBreakdownPartNo(),
);
}
return $products;
}
The behavior is that the column appears in the admin and can be edited, however when saved, it does not save any value.
If I modify the getSelectedGroupedProducts part and set a hard coded value, it displays still no value (blank field), but interestingly if I click save with no value, it saves the value that was hard coded. If I enter any value in the field, it saves as a blank. This is really strange behavior that makes no sense to me.
If I change one of the other fields, such as position to be a hard coded value, it appears instantly and works as expected. Please let me know the proper way for this to work.
There are several posts on various forums about how to do the above and the modification mentioned is true, but what all of the other posts left out was the adminhtml layout input. When a user edits product data in Magento Admin (Associated Products), the data is serialized and sent to the controller save action. I noticed that the fields were not present when a value was entered. This is because the value wasn't in the layout so it was being stripped off of the request before it was posted to the controller.
Add input field in adminhtml/default/default/layout/catalog.xml
adminhtml_catalog_product_supergroup
addColumnInputName

Multiple Fields with a GroupBy Statement in Laravel

Already received a great answer at this post
Laravel Query using GroupBy with distinct traits
But how can I modify it to include more than just one field. The example uses pluck which can only grab one field.
I have tried to do something like this to add multiple fields to the view as such...
$hats = $hatData->groupBy('style')
->map(function ($item){
return ['colors' => $item->color, 'price' => $item->price,'itemNumber'=>$item->itemNumber];
});
In my initial query for "hatData" I can see the fields are all there but yet I get an error saying that 'colors', (etc.) is not available on this collection instance. I can see the collection looks different than what is obtained from pluck, so it looks like when I need more fields and cant use pluck I have to format the map differently but cant see how. Can anyone explain how I can request multiple fields as well as output them on the view rather than just one field as in the original question? Thanks!
When you use groupBy() of Laravel Illuminate\Support\Collection it gives you a deeper nested arrays/objects, so that you need to do more than one map on the result in order to unveil the real models (or arrays).
I will demo this with an example of a nested collection:
$collect = collect([
collect([
'name' => 'abc',
'age' => 1
]),collect([
'name' => 'cde',
'age' => 5
]),collect([
'name' => 'abcde',
'age' => 2
]),collect([
'name' => 'cde',
'age' => 7
]),
]);
$group = $collect->groupBy('name')->values();
$result = $group->map(function($items, $key){
// here we have uncovered the first level of the group
// $key is the group names which is the key to each group
return $items->map(function ($item){
//This second level opens EACH group (or array) in my case:
return $item['age'];
});
});
The summary is that, you need another loop map(), each() over the main grouped collection.

CakePHP 3.x - Saving associated data and form inputs of those tables

I have 3 tables:
Bookings
Payments
Sessions
In my form which is part of the Bookings table, while I have several attributes exclusive to Payments (eg. Amount, Depositpaid, Status), only one has a form input - everything else is Sessions related:
echo $this->Form->hidden('payments.amount',['type'=>'number', 'value'=>'0', 'step'=>'0.01', 'class'=>'currency form-control']);
In the BookingsController, I have the following:
$booking = $this->Bookings->newEntity([
'associated' => ['Payments', 'Sessions']
]]);
if ($this->request->is('post')) {
$data = $this->request->data;
$booking = $this->Bookings->patchEntity($booking, $data, [
'associated' => ['Payments', 'Sessions']
]);
...
$save = $this->Bookings->save($booking);
...
Upon clicking submit on the form, data in both Bookings and Sessions is saved. In the post data, there are two arrays: Session array and Payments array. In the variables data inside the Booking array, there is an Associated array with two elements: 0 Payments, 1 Sessions.
However, in the SQL log, there is no Insert Into for Payments table. When doing a debug of $booking (the patchEntity one), Payments is an empty array as follows:
'payments' => [],
I also did a debug of $save that comes after patchEntity, and obtained the same result for 'payments'.
I checked my Payments table model and the corresponding structure in MySQL when it came to not nulls and validation.
Below is the Payments model (PaymentsTable) regarding validation:
public function validationDefault(Validator $validator)
{
$validator
->integer('id')
->allowEmpty('id', 'create');
$validator
->allowEmpty('paymentmethod','create');
$validator
->decimal('amount')
->requirePresence('amount', 'create')
->notEmpty('amount');
$validator
->requirePresence('status', 'create')
->notEmpty('status');
$validator
->integer('depositpaid')
->allowEmpty('depositpaid', 'create');
$validator
->allowEmpty('uniqid', 'create');
return $validator;
}
In my MySQL database, I set attribute 'status' to automatically be set to 'notpaid' upon the creation of a new data entry, and also set attribute 'amount' to have a default value of '0.00'. The only not null fields are 'id', 'booking_id' (foreign key that links to the Booking table's id), 'amount' and 'status'.
So I guess my question is: Is the reason I'm not getting this associated table saved because I did not put in form inputs (hidden or otherwise) for the remainder of the Payments attributes or is there something I'm missing?
Update:
Okay I added a 0 to the form input (payments.0.amount), and this time the submit did not succeed. Upon debugging patchEntity - 'debug($booking)' once more, I find this error in the payments array:
'[errors]' => [
'status' => [
'_required' => 'This field is required'
]
This is even though I stated status to be set to automatically put in a default string. Do I need to do something like $this->request->data['status'] in the controller? And if so, how does it vary from an attribute for non-associated data (eg. $this->request->data['attribute'] is used for non-associated data)?
I've already tried:
$this->request->data['status'] = 'notpaid';
$this->request->data['payments']['status'] = 'notpaid';
$this->request->data['payments'][0]['status'] = 'notpaid';
$this->request->data['payments']['0']['status'] = 'notpaid';
All without success and receiving the same error in the patchEntity debug. The method that does work is adding in another form input for 'status' (the attribute that says it is required), but although I've solved my issue, I'm still left wondering how to input associated data from the controller.
I fixed it by just adding in another hidden form input, although I would still like to know how to input association data through the controller instead of the view.

Cakephp 3: Modifying Results from the database

In my database there is a content table and when fetching data from this table I would like to append field url to the results, which is based on slug field which is contained in the table. Anyway, I have seen a way to do this in the previous versions of cakephp using behavior for the model of this table and then modifying results in afterFind callback in the behavior class. But in version 3 there is no afterFind callback, and they recommend using mapReduce() method instead in the manual, but this method is poorly explained in the manual and I cant figure out how to achieve this using mapReduce().
After little bit of research I realized that the best way to append the url field field to find results is using formatResults method, So this is what I did in my finders:
$query->formatResults(function (\Cake\Datasource\ResultSetInterface $results) {
return $results->map(function ($row) {
$row['url'] = array(
'controller' => 'content',
'action' => 'view',
$row['slug'],
$row['content_type']['alias']
);
return $row;
});
});

Magento JoinLeft() in custom orders grid causing SQL integrity constrain violation for non-admin user in multi-website setup

I have extended the Mage_Adminhtml_Block_Sales_Order_Grid class with a custom module to add several customer attributes (Magento EE 1.10) to the grid.
I added the custom attributes to the collection in my MyCompany_MyModule_Block_Adminhtml_Order_Grid class in the _prepareCollection() method using three joins like this:
protected function _prepareCollection()
{
$collection = Mage::getResourceModel($this->_getCollectionClass());
//get the table names for the customer attributes we'll need
$customerEntityVarchar = Mage::getSingleton('core/resource')
->getTableName('customer_entity_varchar');
$customerEntityInt = Mage::getSingleton('core/resource')
->getTableName('customer_entity_int');
// add left joins to display the necessary customer attribute values
$collection->getSelect()->joinLeft(array(
'customer_entity_int_table'=>$customerEntityInt),
'`main_table`.`customer_id`=`customer_entity_int_table`.`entity_id`
AND `customer_entity_int_table`.`attribute_id`=148',
array('bureau'=>'value'));
$collection->getSelect()->joinLeft(array(
'customer_entity_varchar_table'=>$customerEntityVarchar),
'`main_table`.`customer_id`=`customer_entity_varchar_table`.`entity_id`
AND `customer_entity_varchar_table`.`attribute_id`=149',
array('index_code'=>'value'));
$collection->getSelect()->joinLeft(array(
'customer_entity_varchar_2_table'=>$customerEntityVarchar),
'`main_table`.`customer_id`=`customer_entity_varchar_2_table`.`entity_id`
AND `customer_entity_varchar_2_table`.`attribute_id`=150',
array('did_number'=>'value'));
$this->setCollection($collection);
return parent::_prepareCollection();
}
UPDATE: While everything displays fine when viewing orders, things are not fine when I try to search / filter orders by any of the text join fields (index_code or did_number). The result is a SQL error: "SQLSTATE[23000]: Integrity constraint violation: 1052 Column 'store_id' in where clause is ambiguous."
This problem also exists if I remove all but one of the leftJoin() statements, so something is going wrong with both (either) of the joins with the customer_entity_varchar table.
As now there are two columns with the name store_id, you have to specify filter_index when you add the column to the grid:
$this->addColumn('store_id', array(
...
'filter_index'=>'main_table.store_id',
));
So that it knows which one you are referring while filtering.
I hope it helps!
More than likely it is because you are joining customer_entity_varchar_table twice.
$collection->getSelect()->joinLeft(array(
'customer_entity_varchar_table'=>$customerEntityVarchar),
'`main_table`.`customer_id`=`customer_entity_varchar_table`.`entity_id`
AND `customer_entity_varchar_table`.`attribute_id`=149',
array('index_code'=>'value'));
$collection->getSelect()->joinLeft(array(
'customer_entity_varchar_2_table'=>$customerEntityVarchar),
'`main_table`.`customer_id`=`customer_entity_varchar_2_table`.`entity_id`
AND `customer_entity_varchar_2_table`.`attribute_id`=150',
array('did_number'=>'value'));
You may want to combine those, you can also try and print the SQL to see what the Query looks like:
$collection->getSelect()->getSelectSql();
More info on collections: http://blog.chapagain.com.np/magento-collection-functions/
The problem appears to exist in two different places. One case is if logged in as a user with a single store, the other as a user who can filter various stores.
Single store user
The solution I went with was to override the addAttributeToFilter method on the collection class. Not knowing exactly what changing the Enterprise_AdminGws_Model_Collections::addStoreAttributeToFilter method would affect other behavior I wanted to avoid that, and I found adding a filter index in Mage_Adminhtml_Block_Sales_Order_Grid as Javier suggested did not work.
Instead I added the following method to Mage_Sales_Model_Resource_Order_Grid_Collection:
/**
* {#inheritdoc}
*/
public function addAttributeToFilter($attribute, $condition = null)
{
if (is_string($attribute) && 'store_id' == $attribute) {
$attribute = 'main_table.' . $attribute;
}
return parent::addFieldToFilter($attribute, $condition);
}
A patch can be found here: https://gist.github.com/josephdpurcell/baf93992ff2d941d02c946aeccd48853
Multi-store user
If a user can filter orders by store at admin/sales_order, the following change is also needed to Mage_Adminhtml_Block_Sales_Order_Grid around line 75:
if (!Mage::app()->isSingleStoreMode()) {
$this->addColumn('store_id', array(
'header' => Mage::helper('sales')->__('Purchased From (Store)'),
'index' => 'store_id',
'type' => 'store',
'store_view'=> true,
'display_deleted' => true,
'filter_index' => 'main_table.store_id',
));
}
A patch can be found here: https://gist.github.com/josephdpurcell/c96286a7c4d2f5d1fe92fb36ee5d0d5a
I had the same bug, after grepping the code, I finally found the troublemaker which is in the Enterprise_AdminGws_Model_Collections class at line ~235:
/**
* Add store_id attribute to filter of EAV-collection
*
* #param Mage_Eav_Model_Entity_Collection_Abstract $collection
*/
public function addStoreAttributeToFilter($collection)
{
$collection->addAttributeToFilter('store_id', array('in' => $this->_role->getStoreIds()));
}
You have to replace 'store_id' by 'main_table.store_id', of course you'll have to extend that particular method in your own rewrite to stick into Magento guidelines :p
Hope it helps!