Drupal feeds module: FeedsProcessor with ubercart Order

Use case: need to import batch ubercart orders from a CSV

Feeds module have a CSV parser already, have batch process build in, can process a large CSV already, and proven code. The most import, there are already 4 type of content that can be import (nodes, users, terms, feednode?), so likely there are other ways to import new content as well.

Start from “The developer's guide to Feeds”: (https://www.drupal.org/node/622700)
Everything is a plugin: fetcher, parser and processor

  • Fetcher is “the way to obtain source data”, feeds module already have file upload fetcher for the use case.
  • Parser is “how to read the source”, feeds module already have CSV parser
  • Processer’s job is to convert well formatted data into Drupal content.

So we create a new module for this feeds plugin:
ubercart_order_feeds.module:

<?php
/**
 * Implementation of hook_feeds_plugins().
 * http://drupalcontrib.org/api/drupal/contributions%21feeds%21feeds.api.php/function/hook_feeds_plugins/6
 */
function ubercart_order_feeds_feeds_plugins() {
 
$path = drupal_get_path('module', 'ubercart_order_feeds');
 
$info = array();
 
$info['FeedsOrderProcessor'] = array(
   
'name' => 'Order processor',
   
'description' => 'Create orders.',
   
'help' => 'Create orders from parsed content.',
   
'handler' => array(
     
'parent' => 'FeedsProcessor',
     
'class' => 'FeedsOrderProcessor',
     
'file' => 'FeedsOrderProcessor.inc',
     
'path' => $path,
    ),
  );
  return
$info;
}
?>

Create a new file FeedsOrderProcessor.inc:
<?php
class FeedsOrderProcessor extends FeedsProcessor {
}
?>

First, define a list of fields that may be passed in by CSV in getMappingTargets():

<?php
 
/**
   * Return available mapping targets.
   */
 
public function getMappingTargets() {
   
$targets = array(
     
'sku' => array(
       
'name' => t('Product SKU'),
       
'description' => t('SKU of the product.'),
      ),
     
'quantity' => array(
       
'name' => t('Quantity'),
       
'description' => t(''),
      ),
     
'cost' => array(
       
'name' => t('Cost'),
       
'description' => t(''),
      ),
     
'delivery_first_name' => array(
       
'name' => t('Delivery: First name'),
       
'description' => t(''),
      ),
     
'delivery_last_name' => array(
       
'name' => t('Delivery: Last name'),
       
'description' => t(''),
      ),
   
//....
?>

which the key of the array will be used again to obtain the CSV value and name will be used in mappings admin page.

I had dump a native order object to find out the default attributes and use exact keys to save me some code mapping hazel. (i.e. delivery_first_name, delivery_last_name above and more)

process() method is the main entry point, and copied some code from NodeProcessor:

<?php
 
public function process(FeedsImportBatch $batch, FeedsSource $source) {
   
// Count number of created and updated nodes.
   
$created  = $updated = $failed = 0;

    while (
$item = $batch->shiftItem()) {
     
// Map item to a term.
     
$order = $this->map($batch);
     
//.......
?>

At this point a new function map() is defined, and should return an empty ubercart order object:

<?php
 
/**
   * Execute mapping on an item.
   */
 
protected function map(FeedsImportBatch $batch, $target_order = NULL) {
   
// Prepare user account object.
   
if (empty($target_order)) {
     
$order = uc_order_new($user->uid, 'completed');
    }

   
// Have parent class do the iterating.
   
return parent::map($batch, $order);
  }
?>

parent::map() function will assign data from CSV to order object

Create order programatically

The next step in process() is adding the order’s product, and save that to DB

Load a product by vid: <?php uc_product_load() ?>
http://drupalcontrib.org/api/drupal/contributions!ubercart!uc_product!uc...

Add product to order:
<?php $order->products[] = $product; ?>

after adding some basic mail and payment method, save the order, complete the sale:

<?php
uc_order_save
($order);
uc_cart_complete_sale($order, TRUE);
?>

Optionally, add payment method as well
<?php uc_payment_enter($order->order_id, 'bank_transfer', 0, 0, NULL, t('Checkout completed for a free order.')); ?>

Full source in Github repo: https://github.com/joetsuihk/ubercart_order_feeds

One more thing

custom settings can be added to the processor
In my case, the client need to deploy to multiple sites and they have slightly different webform IDs that need to be filled, so I put the ID into a settings so client can change that ID easily:

<?php
 
/**
   * Override parent::configDefaults().
   */
 
public function configDefaults() {
    return array(
     
'card_form_id' => '',
     
'mappings' => array(),
    );
  }

  public function
configForm(&$form_state) {
   
$form = array();
   
$form['card_form_id'] = array(
     
'#type' => 'textfield',
     
'#title' => t('Card webform node ID'),
     
'#default_value' => $this->config['card_form_id'],
    );
    return
$form;
  }
?>
Google