Magento1 在管理后台创建订单分析

该帖子参考: https://www.siphor.com/magento-admin-order-creation/

除了下订单并通过结账的客户,Magento商家还可以在该Sales -> Orders部分的管理区域中创建订单。

然后,您将看到一个屏幕,用于选择要将订单关联到的客户。然后,下一步是指定要添加到购物车的产品,要使用的送货和付款方式以及要使用的帐单/送货地址。

请务必注意,用于配置管理订单的控制器是在CreateController.php目录中找到的app/code/core/Mage/Adminhtml/controllers/Sales/Order文件。

这是一个非常复杂的部分,它使用控制器文件和.js文件一起工作来保存数据。由于页面在保存数据时不刷新,因此这里使用了很多JavaScript功能,主要是sales.js文件 。

此处更新的任何部分都将使用loadBlockAction()控制器类中的方法。

file:app / code / core / Mage / Adminhtml / controllers / Sales / Order / CreateController.php

public function loadBlockAction()
{
    $request = $this->getRequest();
    try {
        $this->_initSession()
            ->_processData();
    }
    catch (Mage_Core_Exception $e){
        $this->_reloadQuote();
        $this->_getSession()->addError($e->getMessage());
    }
    catch (Exception $e){
        $this->_reloadQuote();
        $this->_getSession()->addException($e, $e->getMessage());
    }
 
 
    $asJson= $request->getParam('json');
    $block = $request->getParam('block');
 
    $update = $this->getLayout()->getUpdate();
    if ($asJson) {
        $update->addHandle('adminhtml_sales_order_create_load_block_json');
    } else {
        $update->addHandle('adminhtml_sales_order_create_load_block_plain');
    }
 
    if ($block) {
        $blocks = explode(',', $block);
        if ($asJson && !in_array('message', $blocks)) {
            $blocks[] = 'message';
        }
 
        foreach ($blocks as $block) {
            $update->addHandle('adminhtml_sales_order_create_load_block_' . $block);
        }
    }
    $this->loadLayoutUpdates()->generateLayoutXml()->generateLayoutBlocks();
    $result = $this->getLayout()->getBlock('content')->toHtml();
    if ($request->getParam('as_js_varname')) {
        Mage::getSingleton('adminhtml/session')->setUpdateResult($result);
        $this->_redirect('*/*/showUpdateResult');
    } else {
        $this->getResponse()->setBody($result);
    }
}

对于loadBlock()方法中传递的每个块,块名称构成生成布局XML时使用的布局句柄的一部分。

由于在从管理员创建新订单时页面永远不会刷新,因此sales.js内联JavaScript和其他内联JavaScript负责确保URL路由命中loadBlock()操作方法。

例如,一种setLoadBaseUrl()方法用于设置我们的更新URL。

<script type="text/javascript">
    var order = new AdminOrder({"customer_id":1,"addresses":[],"store_id":"1","currency_symbol":"\u00a3","shipping_method_reseted":true,"payment_method":"free"});
    order.setLoadBaseUrl('http://www.yoursite.com/index.php/admin/sales_order_create/loadBlock/key/e8f7ae2fb45a9f507a36a90127e25ef1/');
    var payment = {};
    payment.switchMethod = order.switchPaymentMethod.bind(order);
</script>

form.phtml是通过sales.xml管理布局文件添加的。

AdminOrder类是在定义的类sales.js

file:js / mage / adminhtml / sales.js

var AdminOrder = new Class.create();
AdminOrder.prototype = {
    ....
}

setLoadBaseUrl()函数位于此类中。

file:js / mage / adminhtml / sales.js

var AdminOrder = new Class.create();
AdminOrder.prototype = {
    ....
    setLoadBaseUrl : function(url){
        this.loadBaseUrl = url;
    },
    ...
}

在管理员中选择产品后,点击时Add selected products to order,我们将转向JavaScript方法。

<button id="id_c21001675b52fbae69136266d6f57714" title="Add Selected Product(s) to Order" type="button" class="scalable add" onclick="order.productGridAddSelected()" style="">
    ....
</button>

这里使用的Mage_Adminhtml_Block_Sales_Order_Create_Search类实际上是类 。

file:app / code / core / Mage / Adminhtml / Block / Sales / Order / Create / Search.php

<?php
 
class Mage_Adminhtml_Block_Sales_Order_Create_Search extends Mage_Adminhtml_Block_Sales_Order_Create_Abstract
{
 
    public function __construct()
    {
        parent::__construct();
        $this->setId('sales_order_create_search');
    }
 
    public function getHeaderText()
    {
        return Mage::helper('sales')->__('Please Select Products to Add');
    }
 
    public function getButtonsHtml()
    {
        $addButtonData = array(
            'label' => Mage::helper('sales')->__('Add Selected Product(s) to Order'),
            'onclick' => 'order.productGridAddSelected()',
            'class' => 'add',
        );
        return $this->getLayout()->createBlock('adminhtml/widget_button')->setData($addButtonData)->toHtml();
    }
 
    public function getHeaderCssClass()
    {
        return 'head-catalog-product';
    }
 
}

我们可以在getButtonsHtml()方法中看到,这是我们的onClick值来自button元素的位置。

file:js / mage / adminhtml / sales.js

productGridAddSelected : function(){
    if(this.productGridShowButton) Element.show(this.productGridShowButton);
    var area = ['search', 'items', 'shipping_method', 'totals', 'giftmessage','billing_method'];
    // prepare additional fields and filtered items of products
    var fieldsPrepare = {};
    var itemsFilter = [];
    var products = this.gridProducts.toObject();
    for (var productId in products) {
        itemsFilter.push(productId);
        var paramKey = 'item['+productId+']';
        for (var productParamKey in products[productId]) {
            paramKey += '['+productParamKey+']';
            fieldsPrepare[paramKey] = products[productId][productParamKey];
        }
    }
    this.productConfigureSubmit('product_to_add', area, fieldsPrepare, itemsFilter);
    productConfigure.clean('quote_items');
    this.hideArea('search');
    this.gridProducts = $H({});
}

从这里拿走的要点是产品ID在第一个for循环中收集并推送到一个itemsFilter数组,任何产品参数都通过第二个for循环添加。

productConfigureSubmit()方法是我们最后提交我们的信息。

file:js / mage / adminhtml / sales.js

productConfigureSubmit : function(listType, area, fieldsPrepare, itemsFilter) {
    // prepare loading areas and build url
    area = this.prepareArea(area);
    this.loadingAreas = area;
    var url = this.loadBaseUrl + 'block/' + area + '?isAjax=true';
 
    // prepare additional fields
    fieldsPrepare = this.prepareParams(fieldsPrepare);
    fieldsPrepare.reset_shipping = 1;
    fieldsPrepare.json = 1;
 
    // create fields
    var fields = [];
    for (var name in fieldsPrepare) {
        fields.push(new Element('input', {type: 'hidden', name: name, value: fieldsPrepare[name]}));
    }
    productConfigure.addFields(fields);
 
    // filter items
    if (itemsFilter) {
        productConfigure.addItemsFilter(listType, itemsFilter);
    }
 
    // prepare and do submit
    productConfigure.addListType(listType, {urlSubmit: url});
    productConfigure.setOnLoadIFrameCallback(listType, function(response){
        this.loadAreaResponseHandler(response);
    }.bind(this));
    productConfigure.submit(listType);
    // clean
    this.productConfigureAddFields = {};
}

productConfigureSubmit()简单来说,该方法准备url变量并提交表单。

例如,如果我们要加载我们的运输方法,则可以使用该loadArea()方法完成。

file:js / mage / adminhtml / sales.js

loadArea : function(area, indicator, params){
    var url = this.loadBaseUrl;
    if (area) {
        area = this.prepareArea(area);
        url += 'block/' + area;
    }
    if (indicator === true) indicator = 'html-body';
    params = this.prepareParams(params);
    params.json = true;
    if (!this.loadingAreas) this.loadingAreas = [];
    if (indicator) {
        this.loadingAreas = area;
        new Ajax.Request(url, {
            parameters:params,
            loaderArea: indicator,
            onSuccess: function(transport) {
                var response = transport.responseText.evalJSON();
                this.loadAreaResponseHandler(response);
            }.bind(this)
        });
    }
    else {
        new Ajax.Request(url, {parameters:params,loaderArea: indicator});
    }
    if (typeof productConfigure != 'undefined' && area instanceof Array && area.indexOf('items') != -1) {
        productConfigure.clean('quote_items');
    }
}

继续从一些JavaScript功能,如前面提到的主CreateController.php文件这样一些有趣的方法。

_construct~()方法是一个例子。

file:app / code / core / Mage / Adminhtml / controllers / Sales / Order / CreateController.php

protectedfunction_construct(){$this->setUsedModuleName('Mage_Sales');// During order creation in the backend admin has ability to add any products to orderMage::helper('catalog/product')->setSkipSaleableCheck(true);}

正如评论所暗示的那样,这允许我们在订单中添加任何产品,例如缺货产品。

file:app / code / core / Mage / Adminhtml / controllers / Sales / Order / CreateController.php

protected function _processData()
{
    return $this->_processActionData();
}

_processActionData() 方法可能太大而无法粘贴到一个代码块中,因此它将被分解为块。

file:app / code / core / Mage / Adminhtml / controllers / Sales / Order / CreateController.php
$eventData = array(
    'order_create_model' => $this->_getOrderCreateModel(),
    'request_model'      => $this->getRequest(),
    'session'            => $this->_getSession(),
);
 
Mage::dispatchEvent('adminhtml_sales_order_create_process_data_before', $eventData);

首先,$eventData使用会话,请求模型和订单创建模式创建数组。订单创建模型只是模型的单例Mage_Adminhtml_Model_Sales_Order_Create

file:app / code / core / Mage / Adminhtml / controllers / Sales / Order / CreateController.php
protected function _getOrderCreateModel()
{
    return Mage::getSingleton('adminhtml/sales_order_create');
}

回到_processActionData()方法,接下来是我们是否调用save()了控制器的动作方法。

file:app / code / core / Mage / Adminhtml / controllers / Sales / Order / CreateController.php
/**
 * Saving order data
 */
 if ($data = $this->getRequest()->getPost('order')) {
    $this->_getOrderCreateModel()->importPostData($data);
 }
 
/**
 * Initialize catalog rule data
 */
 $this->_getOrderCreateModel()->initRuleData();

save()方法将调用_processActionData()我们稍后会看到的。应该对订单应用的任何目录规则也都会初始化。

然后,我们会收集结算和送货地址信息。

file:app / code / core / Mage / Adminhtml / controllers / Sales / Order / CreateController.php

/**
 * init first billing address, need for virtual products
 */
$this->_getOrderCreateModel()->getBillingAddress();
 
/**
 * Flag for using billing address for shipping
 */
if (!$this->_getOrderCreateModel()->getQuote()->isVirtual()) {
    $syncFlag = $this->getRequest()->getPost('shipping_as_billing');
    $shippingMethod = $this->_getOrderCreateModel()->getShippingAddress()->getShippingMethod();
    if (is_null($syncFlag)
        && $this->_getOrderCreateModel()->getShippingAddress()->getSameAsBilling()
        && empty($shippingMethod)
    ) {
        $this->_getOrderCreateModel()->setShippingAsBilling(1);
    } else {
        $this->_getOrderCreateModel()->setShippingAsBilling((int)$syncFlag);
    }
}
 
/**
 * Change shipping address flag
 */
if (!$this->_getOrderCreateModel()->getQuote()->isVirtual() && $this->getRequest()->getPost('reset_shipping')) {
    $this->_getOrderCreateModel()->resetShippingMethod(true);
}

然后收集运费

file:app / code / core / Mage / Adminhtml / controllers / Sales / Order / CreateController.php
/**
 * Collecting shipping rates
 */
if (!$this->_getOrderCreateModel()->getQuote()->isVirtual() && $this->getRequest()->getPost('collect_shipping_rates')) {
    $this->_getOrderCreateModel()->collectShippingRates();
}
 
/**
 * Apply mass changes from sidebar
 */
if ($data = $this->getRequest()->getPost('sidebar')) {
    $this->_getOrderCreateModel()->applySidebarData($data);
}

然后,我们处理已添加或从客户的购物车或心愿单中移出的产品。

file:app / code / core / Mage / Adminhtml / controllers / Sales / Order / CreateController.php
/**
 * Adding product to quote from shopping cart, wishlist etc.
 */
if ($productId = (int) $this->getRequest()->getPost('add_product')) {
    $this->_getOrderCreateModel()->addProduct($productId, $this->getRequest()->getPost());
}
 
/**
 * Adding products to quote from special grid
 */
if ($this->getRequest()->has('item') && !$this->getRequest()->getPost('update_items') && !($action == 'save')) {
    $items = $this->getRequest()->getPost('item');
    $items = $this->_processFiles($items);
    $this->_getOrderCreateModel()->addProducts($items);
}
 
/**
 * Update quote items
 */
if ($this->getRequest()->getPost('update_items')) {
    $items = $this->getRequest()->getPost('item', array());
    $items = $this->_processFiles($items);
    $this->_getOrderCreateModel()->updateQuoteItems($items);
}
 
/**
 * Remove quote item
 */
$removeItemId = (int) $this->getRequest()->getPost('remove_item');
$removeFrom = (string) $this->getRequest()->getPost('from');
if ($removeItemId && $removeFrom) {
    $this->_getOrderCreateModel()->removeItem($removeItemId, $removeFrom);
}
 
/**
 * Move quote item
 */
$moveItemId = (int) $this->getRequest()->getPost('move_item');
$moveTo = (string) $this->getRequest()->getPost('to');
if ($moveItemId && $moveTo) {
    $this->_getOrderCreateModel()->moveQuoteItem($moveItemId, $moveTo);
}

有趣的是,在该updateQuoteItems()方法中,这是针对产品设置定制价格的地方。

file:app / code / core / Mage / Adminhtml / Model / Sales / Order / Create.php
public function updateQuoteItems($data)
{
    ....
    if (empty($info['action']) || !empty($info['configured'])) {
        $item->setQty($itemQty);
        $item->setCustomPrice($itemPrice);
        $item->setOriginalCustomPrice($itemPrice);
        $item->setNoDiscount($noDiscount);
        $item->getProduct()->setIsSuperMode(true);
        $item->getProduct()->unsSkipCheckRequiredOption();
        $item->checkData();
    }
    ....
}

返回_processActionData()方法,接下来添加付款方式并保存。

file:app / code / core / Mage / Adminhtml / controllers / Sales / Order / CreateController.php
if ($paymentData = $this->getRequest()->getPost('payment')) {
    $this->_getOrderCreateModel()->getQuote()->getPayment()->addData($paymentData);
}
 
$eventData = array(
    'order_create_model' => $this->_getOrderCreateModel(),
    'request'            => $this->getRequest()->getPost(),
);
 
Mage::dispatchEvent('adminhtml_sales_order_create_process_data', $eventData);
 
$this->_getOrderCreateModel()
    ->saveQuote();
 
if ($paymentData = $this->getRequest()->getPost('payment')) {
    $this->_getOrderCreateModel()->getQuote()->getPayment()->addData($paymentData);
}

该方法底部的其他部分包括应用礼品消息和validatin优惠券代码(如果已使用)。

因此,当我们去保存创建的订单时,我们调用类的saveAction()方法CreateController.php

file:app / code / core / Mage / Adminhtml / controllers / Sales / Order / CreateController.php
public function saveAction()
{
    try {
        $this->_processActionData('save');
        $paymentData = $this->getRequest()->getPost('payment');
        if ($paymentData) {
            $paymentData['checks'] = Mage_Payment_Model_Method_Abstract::CHECK_USE_INTERNAL
                | Mage_Payment_Model_Method_Abstract::CHECK_USE_FOR_COUNTRY
                | Mage_Payment_Model_Method_Abstract::CHECK_USE_FOR_CURRENCY
                | Mage_Payment_Model_Method_Abstract::CHECK_ORDER_TOTAL_MIN_MAX
                | Mage_Payment_Model_Method_Abstract::CHECK_ZERO_TOTAL;
            $this->_getOrderCreateModel()->setPaymentData($paymentData);
            $this->_getOrderCreateModel()->getQuote()->getPayment()->addData($paymentData);
        }
 
        $order = $this->_getOrderCreateModel()
            ->setIsValidate(true)
            ->importPostData($this->getRequest()->getPost('order'))
            ->createOrder();
 
        $this->_getSession()->clear();
        Mage::getSingleton('adminhtml/session')->addSuccess($this->__('The order has been created.'));
        if (Mage::getSingleton('admin/session')->isAllowed('sales/order/actions/view')) {
            $this->_redirect('*/sales_order/view', array('order_id' => $order->getId()));
        } else {
            $this->_redirect('*/sales_order/index');
        }
    } catch (Mage_Payment_Model_Info_Exception $e) {
        $this->_getOrderCreateModel()->saveQuote();
        $message = $e->getMessage();
        if( !empty($message) ) {
            $this->_getSession()->addError($message);
        }
        $this->_redirect('*/*/');
    } catch (Mage_Core_Exception $e){
        $message = $e->getMessage();
        if( !empty($message) ) {
            $this->_getSession()->addError($message);
        }
        $this->_redirect('*/*/');
    }
    catch (Exception $e){
        $this->_getSession()->addException($e, $this->__('Order saving error: %s', $e->getMessage()));
        $this->_redirect('*/*/');
     }
}

方法顶部的一些检查包括所选择的付款方式是否可以在内部使用(即在管理区域中)并检查总计为零等。

最后,createOrder()Mage_Adminhtml_Model_Sales_Order_Create类中调用该方法。

file:app / code / core / Mage / Adminhtml / Model / Sales / Order / Create.php
public function createOrder()
{
    $this->_prepareCustomer();
    $this->_validate();
    $quote = $this->getQuote();
    $this->_prepareQuoteItems();
 
    $service = Mage::getModel('sales/service_quote', $quote);
    /** @var Mage_Sales_Model_Order $oldOrder */
    $oldOrder = $this->getSession()->getOrder();
    if ($oldOrder->getId()) {
        $originalId = $oldOrder->getOriginalIncrementId();
        if (!$originalId) {
            $originalId = $oldOrder->getIncrementId();
        }
        $orderData = array(
            'original_increment_id'     => $originalId,
            'relation_parent_id'        => $oldOrder->getId(),
            'relation_parent_real_id'   => $oldOrder->getIncrementId(),
            'edit_increment'            => $oldOrder->getEditIncrement()+1,
            'increment_id'              => $originalId.'-'.($oldOrder->getEditIncrement()+1)
        );
        $quote->setReservedOrderId($orderData['increment_id']);
        $service->setOrderData($orderData);
 
        $oldOrder->cancel();
    }
 
    /** @var Mage_Sales_Model_Order $order */
    $order = $service->submit();
    $customer = $quote->getCustomer();
    if ((!$customer->getId() || !$customer->isInStore($this->getSession()->getStore()))
        && !$quote->getCustomerIsGuest()
    ) {
        $customer->setCreatedAt($order->getCreatedAt());
        $customer
            ->save()
            ->sendNewAccountEmail('registered', '', $quote->getStoreId());;
    }
    if ($oldOrder->getId()) {
        $oldOrder->setRelationChildId($order->getId());
        $oldOrder->setRelationChildRealId($order->getIncrementId());
        $oldOrder->save();
        $order->save();
    }
    if ($this->getSendConfirmation()) {
        $order->queueNewOrderEmail();
    }
 
    Mage::dispatchEvent('checkout_submit_all_after', array('order' => $order, 'quote' => $quote));
 
    return $order;
}

有趣的是,这是对$oldOrder变量的检查。如果您正在编辑现有订单,则此变量将存储在会话中,并且最终将在创建新订单时取消。

relation_child_idrelation_child_real_idrelation_parent_idrelation_parent_real_id列然后在更新sales_flat_order表。请注意,real_id它来自订单增量ID。

请注意checkout_submit_all_after,在方法结束时调度该事件。监听此事件的观察者的主要目的是减少系统中的库存水平。

因此,与从前端创建订单相比,我们在从管理员创建订单时看到的一些差异包括:

  • 跳过可销售的支票
  • 根据产品设置自定义价格

Leave a comment

您的电子邮箱地址不会被公开。 必填项已用 * 标注