Magento创建configurable产品的要点

接着上一篇用API创建可配置的产品Configurable Product说事。Magento的产品类型可分为Simple Product、Group Product、Configurable Product、Virtual Product、Bundle Product、Downloadable Product。其中Simple的产品最简单,属于基础产品。Configurable Product和Bundle Product的产品是建立在这些Simple产品之上的。

Configurable Product 和 Bundle Product的产品都可以独立的跟踪库存 。Custom Option是无法跟踪独立产品的库存。Configurable Product是需要独立建立不同属性的产品的组合,而 Bundle Product是可以组合产品的。举个例子来说,一件衣服有2种颜色和3个大小,建立Configurable Product 需要创建6个独立的Simple产品,而Bundle 是只需要创建2+3=5个Simple产品。在属性比较多的时候,区别还是很大的。

关于Configurable Product 的创建,不得不说,Magento管理后台的用户体验很差。前段时间教我的同事创建Configurable Product,过了一阵子,忘了,为此写下这篇文章。

首先创建Configurable Product有3个要点:

1. 在属性集里必须有一个全局的属性,即属性的Scope为Global。

2. 属性的Catalog Input Type for Store Owner要选择Dropdown。

3. 属性的Use To Create Configurable Product要选择Yes。

下面以创建一个Configurable Product为例,该产品有一个cm_color的属性。有bule、red、yellow。价格分别为10、20、30。

创建Configurable Product的属性

从导航进入到CataLog->Attributes->Manage Attributes,在Properties这个tab中:

magento-create-configurable-product0

新建一个cm_color的属性,Scope选择Global,Catalog Input Type for Store Owner为Dropdown,Catalog Input Type for Store Owner为Yes,注意图中画红线的部分。

magento-create-configurable-product1

切换到Manage Label/Options,添加该属性在不同语言的store显示不同的Title,这里只需填写Admin的部分就可以了。

给该属性添加3个选项,Bule、Red、Yellow,然后Save Attribute。

magento-create-configurable-product2

将属性添加到属性集中

从导航进入到CataLog->Attributes->Manage Attributes Sets,选择Default属性集。

magento-create-configurable-product3magento-create-configurable-product4

将cm_color属性从右边的Unassigned Attribute拖到左边的Groups的General组,完成之后Save Attribute Set。

magento-create-configurable-product5

创建Configurable Product

从导航进入到CataLog->Manage products,点击Add Product。Attribute Set 选择Default,Product Type 选择Configurable Product,点击Continue。

magento-create-configurable-product6

选择具有全局属性的cm_color,点击Continue。

magento-create-configurable-product7

添加Configurable Product的基本属性,Name,Description,Short Description,SKU,Status,Visibility,Price等基本属性,点击Save and Continue Edit。

切换到Associated products选项卡,重点在红线的部分Quick Simple Product Creation。

magento-create-configurable-product8

创建一个Color为Bule的产品,Color选择Bule,price选择10,Fixed是直接定价,Percentage是百分比,该价格都是在原价价格加价。Visibility最好选择不显示Not Visible Individually。

magento-create-configurable-product9

点击Quick Create,可在下方的Super Product Attribute Configuration看到自动创建的Simple Product,并自动绑定到Color的Bule选项。

magento-create-configurable-product10

同样创建Color为Red,和Yellow的产品。

magento-create-configurable-product11

magento-create-configurable-product12

magento-create-configurable-product13

此时创建好了3个Simple Product了,并都已经绑定相应的属性上。选择Save and Continue Edit。

创建Shipping Module

Magento系统自带了大概7种运费方式:平价、运费表、免运费、ups、usps、fedex、dhl等。不过这些依然无法满足我们的需求,这时候就需要创建一个shipping module 来实现了。创建一个shipping module 很简单,需要继承Mage_Shipping_Model_Carrier_Abstract抽象类, 实现Mage_Shipping_Model_Carrier_Interface接口类,这样就能利用函数collectRates来自定义计算运费的方式。这样就可以创建一个插件来自定义shipping method。

添加模块配置信息

首先,添加模块信息,创建文件app/etc/modules/Xbc_Ship.xml

<?xml version="1.0"?>
<config>
  <modules>
    <Xbc_Ship>
      <active>true</active>
      <codePool>local</codePool>            
	        <depends>
                <Mage_Shipping />
            </depends>
      <version>1.1.0</version>
    </Xbc_Ship>
  </modules>
</config>

添加模块配置信息,创建文件app/code/local/Xbc/Ship/etc/config.xml

<?xml version="1.0"?>
<config>
  <modules>
    <Xbc_Ship>
      <version>1.1.0</version>
    </Xbc_Ship>
  </modules>
  <global>
    <helpers>
      <ship>
        <class>Xbc_Ship_Helper</class>
      </ship>
    </helpers>
	<resources>
	  <ship_setup>
		<setup>
		  <module>Xbc_Ship</module>
		</setup>
		<connection>
		  <use>core_setup</use>
		</connection>
	  </ship_setup>
	  <ship_write>
		<connection>
		  <use>core_write</use>
		</connection>
	  </ship_write>
	  <ship_read>
		<connection>
		  <use>core_read</use>
		</connection>
	  </ship_read>
	</resources>
	<models>
	  <ship>
		<class>Xbc_Ship_Model</class>
		<resourceModel>ship_mysql4</resourceModel>
	  </ship>
	</models>
  </global>    
	<default>
        <carriers>
            <cm_dhl>
                <active>1</active>
                <debug>0</debug>
                <model>ship/carrier_cm_dhl</model>
                <name>DHL</name>
                <title>DHL</title>
                <description>DHL</description>
                <sort_order>0</sort_order>
            </cm_dhl>
        </carriers>
    </default>
</config>

实现自定义运费

创建文件app/code/local/Hofan/Ship/Model/Carrier/Cm/Dhl.php。

<?php  
    class Xbc_Ship_Model_Carrier_Cm_Dhl     
		extends Mage_Shipping_Model_Carrier_Abstract
		implements Mage_Shipping_Model_Carrier_Interface
	{  
        protected $_code = 'cm_dhl';  
 
        /** 
        * Collect rates for this shipping method based on information in $request 
        * 
        * @param Mage_Shipping_Model_Rate_Request $data 
        * @return Mage_Shipping_Model_Rate_Result 
        */  
        public function collectRates(Mage_Shipping_Model_Rate_Request $request){  
        	//if this shipping method disabled
        	if (!$this->getConfigFlag('active')) {
            	return false;
        	}
 
            $result = Mage::getModel('shipping/rate_result');  
            $method = Mage::getModel('shipping/rate_result_method');  
            $method->setCarrier($this->_code);  
            $method->setCarrierTitle($this->getConfigData('title'));
            $method->setMethod($this->_code);  
            $method->setMethodTitle($this->getConfigData('name'));
 
            $debug = $this->getConfigData('debug');
            $rate = $this->getConfigData('rate');
 
        	//get find the country id
            $country_id = $request->getDestCountryId();
 
            //Get all items
            $items = $request->getAllItems();
            $weight = $request->getPackageWeight();
			foreach ($items as $item){
				$_product = $item->getProduct();
				if ($_product instanceof Mage_Catalog_Model_Product) {
					$product = Mage::getModel('catalog/product')->load($_product->getId());
					if($_weight = $product->getWeight()){
 
					}
				}
			}
 
			//get price
			$shippingPrice = 100;
 
		    $method->setPrice($shippingPrice);
			$method->setCost($shippingPrice);
            $result->append($method);  
            return $result;  
        }  
 
		/**
		 * Get allowed shipping methods
		 *
		 * @return array
		 */
		public function getAllowedMethods()
		{
			return array($this->_code=>$this->getConfigData('name'));
		}
    }

添加后台配置

如果完成了上面的步骤,你可以添加后台配置文件。创建文件app/code/local/Xbc/Ship/etc/system.xml

<?xml version="1.0"?>
<config>
	<sections>
		<carriers  translate="label" module="ship">            
				<groups>
				      <cm_dhl translate="label"> 
					  <label>Hofan DHL</label>
					  <frontend_type>text</frontend_type>
					  <sort_order>0</sort_order>
					  <show_in_default>1</show_in_default>
					  <show_in_website>1</show_in_website>
					  <show_in_store>1</show_in_store>
					  <model>ship/carrier_cm_dhl</model>
				       <fields>                        
				         <active translate="label">
                            			<label>Enabled</label>
                            			<frontend_type>select</frontend_type>
                            			<source_model>adminhtml/system_config_source_yesno</source_model>
                            			<sort_order>10</sort_order>
                            			<show_in_default>1</show_in_default>
                            			<show_in_website>1</show_in_website>
                            			<show_in_store>1</show_in_store>
                        		</active>
                        		<debug translate="label">
                            			<label>Debug Mode</label>
                            			<frontend_type>select</frontend_type>
                            			<source_model>adminhtml/system_config_source_yesno</source_model>
                            			<sort_order>20</sort_order>
                            			<show_in_default>1</show_in_default>
                            			<show_in_website>1</show_in_website>
                            			<show_in_store>1</show_in_store>
                        		</debug>
                        		<title translate="label">
                            			<label>Title</label>
                            			<frontend_type>text</frontend_type>
                            			<sort_order>30</sort_order>
                            			<show_in_default>1</show_in_default>
                            			<show_in_website>1</show_in_website>
                            			<show_in_store>1</show_in_store>
                        		</title>
                        		<name translate="label">
                            			<label>Method Name</label>
                            			<frontend_type>text</frontend_type>
                            			<sort_order>40</sort_order>
                            			<show_in_default>1</show_in_default>
                            			<show_in_website>1</show_in_website>
                            			<show_in_store>1</show_in_store>
                        		</name>
                        		<description translate="label">
                            			<label>Description</label>
                            			<frontend_type>textarea</frontend_type>
                            			<sort_order>50</sort_order>
                            			<show_in_default>1</show_in_default>
                            			<show_in_website>1</show_in_website>
                            			<show_in_store>1</show_in_store>
                        		</description>
                        		<sort_order translate="label">
                            			<label>Sort Order</label>
                            			<frontend_type>text</frontend_type>
                            			<sort_order>100</sort_order>
                            			<show_in_default>1</show_in_default>
                            			<show_in_website>1</show_in_website>
                            			<show_in_store>1</show_in_store>
                        		</sort_order>
					</fields>
				</cm_dhl>
			</groups>
		</carriers>
	  </sections>
</config>

该插件在Magento CE 1.6.2上测试通过。

参考资料

Adding New Shipping Module In Magento

自定义Magento导航目录

Magento的默认主题中导航目录是垂直下拉式菜单:鼠标滑过时会显示下级菜单。实际上你如果要把菜单改成水平下拉式的话,最好的方式重写目录导航的block,自定义生成你所需要的导航样式。定义目录导航的文件位于/app/code/core/Mage/Catalog/Block/Navigation.php。主要由这renderCategoriesMenuHtml和_renderCategoryMenuItemHtml两个函数来实现。

函数renderCategoriesMenuHtml取出所有一级目录,然后丢给renderCategoryMenuItemHtml去循环判断是否有子目录,如果子目录是可显示的话,才能在前台显示。其实可以由一个递归函数来实现嘛。为此,我写一个小插件来实现这个功能。

首先添加插件的模块信息app/etc/modules/Cm_Menu.xml

<?xml version="1.0"?>
<config>
  <modules>
    <Cm_Menu>
      <active>true</active>
      <codePool>local</codePool>
      <version>1.0.0</version>
    </Cm_Menu>
  </modules>
</config>

添加模块配置信息,重写了catalog/navigation这个block

<?xml version="1.0"?>
<config>
  <modules>
    <Cm_Menu>
      <version>1.0.0</version>
    </Cm_Menu>
  </modules>
  <global>
    <helpers>
      <menu>
        <class>Cm_Menu_Helper</class>
      </menu>
    </helpers>
    <blocks>
    	<menu>
        	<class>Cm_Menu_Block</class>
        </menu>
    	<catalog>
    		<rewrite>
    			<navigation>Cm_Menu_Block_Navigation</navigation>
    		</rewrite>
    	</catalog>
    </blocks>
  </global>
</config>

自定义renderMenu函数来生成导航。Cm_Menu_Block_Navigation 继承Mage_Catalog_Block_Navigation,这样就可以使用在phtml使用自定义的函数。

<?php
/**
 * Author: Matt
 * URI:	   http://www.xbc.me
 * Time:   2012.7.1
 */
class Cm_Menu_Block_Navigation extends Mage_Catalog_Block_Navigation{
	public function renderMenu($level = 0, $category = '' , $isFirst = false){
		$activeCategories = array();
		if($level == 0 && $category == ''){
			//only get all category on top level
        	foreach ($this->getStoreCategories() as $child) {
            	if ($child->getIsActive()) {
                	$activeCategories[] = $child;
            	}
        	}
		}else{
			if (!$category->getIsActive()) {
            	return '';
        	}	
        	// get all children
        	if (Mage::helper('catalog/category_flat')->isEnabled()) {
            	$children = (array)$category->getChildrenNodes();
        	} else {
            	$children = $category->getChildren();
        	}
			foreach ($children as $child) {
            	if ($child->getIsActive()) {
                	$activeCategories[] = $child;
            	}
        	}
		}
        $activeCategoriesCount = count($activeCategories);
        $hasActiveCategoriesCount = ($activeCategoriesCount > 0);
        //only check parent category 
		if($level == 0 && $category == ''){
        	if (!$hasActiveCategoriesCount) {
            	return '';
        	}
		}
		$_html = '';
        $j = 0;
        if($level == 0 && $category == ''){
        	foreach ($activeCategories as $category) {
            	$_html .= $this->renderMenu( $level, $category , ($j == 0));
            	$j++;
        	}
        	return $_html;
        }else{
        	$_url = $this->getCategoryUrl($category);
        	$_name = $this->escapeHtml($category->getName());
        	//$_child = Mage::getModel( 'catalog/category' )->load($category->getId());
        	//$_description = $_child->getDescription();
        	if(!$_description){
        		$_description = $_name;
        	}
        	$html = array();
        	foreach ($activeCategories as $category) {
            	$_html .= $this->renderMenu( $level + 1, $category , ($j == 0));
            	$j++;
        	}
        	$classes = array();
			if ($isFirst) {
            	$classes[] = 'fore';
        	} 
			if($level == 0){
        		$classes[] = 'item';
        		$attributes = array();
        		if (count($classes) > 0) {
            		$attributes['class'] = implode(' ', $classes);
        		}
        		$htmlLi = '<div';
        		foreach ($attributes as $attrName => $attrValue) {
            		$htmlLi .= ' ' . $attrName . '="' . str_replace('"', '\"', $attrValue) . '"';
        		}
        		$htmlLi .= '>';
        		$html[] = $htmlLi;
 
        		$html[] = '<span>';
        		$html[] = '<h3>';
        		$html[] = '<a href="'.$_url.'" title="'.$_description.'">'.$_name.'</a>';
        		$html[] = '</h3>';
        		$html[] = '</span>';
        		$html[] = '<div class="i-mc">';
	        	$html[] = '<div class="subitem">';
        		$html[] = $_html;	
        		$html[] = '</div>';
	        	$html[] = '</div>';
	        	$html[] = '</div>';
        	}
        	if($level == 1){
        		$attributes = array();
        		if (count($classes) > 0) {
            		$attributes['class'] = implode(' ', $classes);
        		}
        		$htmlLi = '<dl';
        		foreach ($attributes as $attrName => $attrValue) {
            		$htmlLi .= ' ' . $attrName . '="' . str_replace('"', '\"', $attrValue) . '"';
        		}
        		$htmlLi .= '>';
        		$html[] = $htmlLi;
 
	        	$html[] = '<dt>';
	        	$html[] = '<a href="'.$_url.'" title="'.$_description.'">'.$_name.'</a>';
	        	$html[] = '</dt>';
	        	$html[] = '<dd>';
	        	$html[] = $_html;
	        	$html[] = '</dd>';
	        	$html[] = '</dl>';
        	}
        	if($level == 2){
        		$html[] = '<em>';
        		$html[] = '<a href="'.$_url.'" title="'.$_description.'">'.$_name.'</a>';
        		$html[] = '</em>';
        	}
			$_html = implode("\n", $html);
        	return $_html;
        }
	}
}
?>

在renderMenu函数中,函数有3个参数,$level,$category,$j。$level参数是目录的层级,参数$category是子目录的实例。目录的层级限制在3层以内,在第一次循环的的时候,$level=0,会从系统取出全部的一级目录分类,存入$activeCategories数组中,通过计算$activeCategories数组的个数来判断时候是否存在二级目录。如果存在二级目录,再丢给renderMenu函数自己,此时$level=0,$category参数是子目录的实例,根据不同的$level进行循环,得到最后的目录列表。

将block的type换成

<block type="menu/navigation" name="left.category" template="widget/category.phtml" />

在模板中只需要调用renderMenu函数

<?php $_menu = $this->renderMenu();?>
<?php if($_menu): ?>
<div class="nav-box">
   <?php echo $_menu ?>
</div>
<?php endif ?>

本插件在Magento CE 1.6.2 上测试通过

Magento的Module layout update

在Magento中xml是模块配置的利器,有时开发一个Magento的模块时,需要添加模块的layout update。

添加前台的layout XML需要在模块的config.xml添加frontend节点。

<frontend>
    <layout>
        <updates>
	    <name><!-- 模块的名字一般模块命名moduleNameSpace_moduleName,主要这里是moduleName。-->
	        <file>name.xml</file><!-- layout Xml命名自定义-->
	    </name>
	</updates>
    </layout>
</frontend>

添加后台的layout xml需要在config.xml添加adminhtml节点

<adminhtml>
    <layout>
        <updates>
	    <name><!-- 模块的名字一般模块命名moduleNameSpace_moduleName,主要这里是moduleName。-->
	        <file>name.xml</file><!-- layout Xml命名自定义-->
	    </name>
	</updates>
    </layout>
</adminhtml>

需要注意的是name.xml的位置,Magento 查找layout文件的顺序为

/app/design/frontend/default/default/layout/name.xml
/app/design/frontend/base/default/layout/name.xml

推荐放在

/app/design/frontend/default/default/layout/name.xml

即可。

在name.xml文件中可以修改页面的布局。

<?xml version="1.0" encoding="UTF-8"?>
<layout>
    <customer_account_create>
        <reference name="head">
	    <action method="addItem" ifconfig="customer/address/enabled"><type>skin_js</type><name>js/name.js</name><params/></action>
	</reference>
    </customer_account_create>
</layout>

在这个name.xml中向head添加了一段js。神奇的地方在于可以在xml里自定义加载条件,如ifconfig属性。

Magento解析到该模块的layout xml时会判断ifconfig属性的值为true or false。根据ifconfig的值来决定是否执行addItem方法。

换成php语言的逻辑如下:

 
if(customer/address/enabled == true){
//exe addItem method
//add name.js to head
}

注意name.js的路径和name.xml的类似,同样是从查找顺序为:

/skin/frontend/default/default/js/name.js
 
/skin/frontend/base/default/js/name.js

Magento 数据导入sku自动增长

近来由于业务需要研究了下Magento的数据导入模块,只能说很好很强大。起初的目的是为了业务组的一些BT要求(此处对业务组500字的谴责…):要求导入产品的时候根据产品的id自动生成sku,忽略sku的意义。在粗略的研究了导入模块的源码之后,用了一个不是很优雅的方法实现。唉,每个优雅的接口都有个龌龊的实现。

Magento的导入模块从csv中读取数据后,会把数据序列化后存在表importexport_importdata中。之后导入的时候会从表里读取数据,这样办法就有了。我们在序列化之前,修改导入产品的sku,然后存到数据库中就ok了。
具体实现如下

//app/code/local/Mage/ImportExport/Model/Import/Entity/Abstract.php
 /**
     * Import product sku
     *
     * @var int
     */
    protected $_sku = 0;
 
/**
     * Change row data before saving in DB table.
     *
     * @param array $rowData
     * @return array
     */
    protected function _prepareRowForDb(array $rowData)
    {
        /**
         * Convert all empty strings to null values, as
         * a) we don't use empty string in DB
         * b) empty strings instead of numeric values will product errors in Sql Server
         */
        foreach ($rowData as $key => $val) {
            if ($val === '') {
                $rowData[$key] = null;
            }
        }
        //add product sku auto increment
        $key = Mage_ImportExport_Model_Import_Entity_Product::COL_SKU;
        $type = Mage_ImportExport_Model_Import_Entity_Product::COL_TYPE;
        $set = Mage_ImportExport_Model_Import_Entity_Product::COL_ATTR_SET;
        if( $rowData[$key] &&  ( $rowData[$type] == 'simple' && $rowData[$set] == 'Default' || $rowData[$type] == 'configurable' ) ) {
            if(!$this->_sku){
                //get last product entity id
                $res = Mage::getSingleton('core/resource');
                $readConnection = $res->getConnection('core_read');
                $table = $res->getTableName('catalog_product_entity');
                $this->_sku = Mage::getResourceHelper('importexport')->getNextAutoincrement("{$table}");
            }
            $rowData[$key] = strval($this->_sku);
        }
        return $rowData;
    }

就这样子吧。