? example.patch
? uc_taxes/uc_taxes.export.inc
Index: payment/uc_payment/uc_payment.js
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ubercart/payment/uc_payment/uc_payment.js,v
retrieving revision 1.5.2.7
diff -u -p -r1.5.2.7 uc_payment.js
--- payment/uc_payment/uc_payment.js	20 Oct 2009 20:58:06 -0000	1.5.2.7
+++ payment/uc_payment/uc_payment.js	20 Jun 2010 14:05:16 -0000
@@ -98,6 +98,12 @@ function serializeOrder() {
       type = temp[0];
     }
     else {
+      if (temp[0] == 'tax' && temp[1] != 'subtotal') {
+        type = temp[0];
+      }
+      else {
+        type = key;
+      }    
       type = key;
     }
     line_item = line_item + 'i:' + i + ';a:5:{s:5:"title";s:' + li_titles[key].bytes() + ':"' + li_titles[key] + '";s:4:"type";s:'+ type.bytes() + ':"'+ type + '";s:6:"amount";d:' + li_values[key] + ';s:6:"weight";d:' + li_weight[key] + ';s:6:"summed";i:' + li_summed[key] + ';}';
Index: uc_taxes/uc_taxes.admin.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ubercart/uc_taxes/uc_taxes.admin.inc,v
retrieving revision 1.1.2.11
diff -u -p -r1.1.2.11 uc_taxes.admin.inc
--- uc_taxes/uc_taxes.admin.inc	18 Mar 2010 03:09:42 -0000	1.1.2.11
+++ uc_taxes/uc_taxes.admin.inc	20 Jun 2010 14:05:19 -0000
@@ -18,10 +18,11 @@ function uc_taxes_admin_settings() {
   foreach (uc_taxes_rate_load() as $rate_id => $rate) {
     // Build the operations links for the tax rate.
     $ops = array(
-       l(t('edit'), 'admin/store/settings/taxes/'. $rate_id .'/edit'),
-       l(t('conditions'), CA_UI_PATH .'/uc_taxes_'. $rate_id .'/edit/conditions'),
-       l(t('clone'), 'admin/store/settings/taxes/'. $rate_id .'/clone'),
-       l(t('delete'), 'admin/store/settings/taxes/'. $rate_id .'/delete'),
+      l(t('edit'), 'admin/store/settings/taxes/'. $rate_id .'/edit'),
+      l(t('conditions'), CA_UI_PATH .'/uc_taxes_'. $rate_id .'/edit/conditions'),
+      l(t('clone'), 'admin/store/settings/taxes/'. $rate_id .'/clone'),
+      l(t('delete'), 'admin/store/settings/taxes/'. $rate_id .'/delete'),
+      l(t('export'), 'admin/store/settings/taxes/'. $rate_id .'/export'),
     );
 
     // Add the row to the table.
@@ -65,15 +66,22 @@ function uc_taxes_form($form_state, $rat
   if ($rate_id) {
     // Load the tax rate and set the page title.
     $rate = uc_taxes_rate_load($rate_id);
-
     drupal_set_title($rate->name);
   }
 
   $form['id'] = array(
-    '#type' => 'value',
-    '#value' => $rate_id,
+    '#type' => 'textfield',
+    '#title' => t('Tax Id'),
+    '#default_value' => $rate->id,
+    '#description' => t('The machine-readable name of this tax. This name must contain only lowercase letters, numbers, and underscores. This name must be unique.'),
+    '#required' => TRUE,
   );
 
+  if ($rate_id) {
+    $form['id']['#disabled'] = TRUE;
+    $form['id']['#value'] = $rate->id;
+  }
+
   $form['name'] = array(
     '#type' => 'textfield',
     '#title' => t('Name'),
@@ -93,7 +101,7 @@ function uc_taxes_form($form_state, $rat
 
   $options = array(
     '0' => t('Apply tax to any product regardless of its shipability.'),
-    1 => t('Apply tax to shippable products only.'),
+    '1' => t('Apply tax to shippable products only.'),
   );
 
   $form['shippable'] = array(
@@ -154,9 +162,17 @@ function uc_taxes_form($form_state, $rat
  * @see uc_taxes_form()
  */
 function uc_taxes_form_validate($form, &$form_state) {
+
   if (!empty($form_state['values']['rate']) && (floatval($form_state['values']['rate']) < 0)) {
     form_set_error('rate', t('Rate must be a positive number. No commas and only one decimal point.'));
   }
+  if (arg(4) == 'add') {
+    // We can't add a tax that already exists.
+    $rate = uc_taxes_rate_load($form_state['values']['id']);
+    if ($rate->id) {
+      form_set_error('id', t('Tax id has to be unique.'));
+    }
+  }
 }
 
 /**
@@ -181,6 +197,7 @@ function uc_taxes_form_submit($form, &$f
     'weight' => $form_state['values']['weight'],
     'shippable' => $form_state['values']['shippable'],
   );
+
   uc_taxes_rate_save((object) $rate);
 
   // Update the name of the associated predicate.
@@ -200,9 +217,9 @@ function uc_taxes_clone($rate_id) {
   $rate = uc_taxes_rate_load($rate_id);
   $name = $rate->name;
 
-  // Tweak the name and unset the rate ID.
+  // Tweak the name and generate the rate ID.
   $rate->name = t('Copy of !name', array('!name' => $rate->name));
-  $rate->id = NULL;
+  $rate->id = uc_taxes_generate_rate_id($rate_id, 'clone');
 
   // Save the new rate.
   $rate = uc_taxes_rate_save($rate);
@@ -258,3 +275,26 @@ function uc_taxes_delete_form_submit($fo
 
   $form_state['redirect'] = 'admin/store/settings/taxes';
 }
+
+/**
+ * Generate a rate id based in one that might exists.
+ */
+function uc_taxes_generate_rate_id($rate_id = NULL, $mode = NULL) {
+  if ($mode == 'clone') { // if we are cloning the rate, we assume that the id already exists.
+    $used = array();
+    $found = FALSE;
+    while (!$found) {
+      $i = 0;
+      while (in_array($i == 0 ? $rate_id : "{$rate_id}_{$i}", $used)) {
+        $i++;
+      }
+      $new_id = $i == 0 ? $rate_id : "{$rate_id}_{$i}";
+      $used[] = $new_id;
+      $rate = uc_taxes_rate_load($new_id);
+      if (!$rate->id) {
+        $found = TRUE;
+      }
+    }
+    return $new_id;
+  }
+}
Index: uc_taxes/uc_taxes.install
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ubercart/uc_taxes/uc_taxes.install,v
retrieving revision 1.10.2.9
diff -u -p -r1.10.2.9 uc_taxes.install
--- uc_taxes/uc_taxes.install	19 Mar 2010 21:27:18 -0000	1.10.2.9
+++ uc_taxes/uc_taxes.install	20 Jun 2010 14:05:19 -0000
@@ -14,11 +14,22 @@ function uc_taxes_schema() {
 
   $schema['uc_taxes'] = array(
     'description' => 'Stores tax information.',
+    'export' => array(
+      'key' => 'id',
+      'identifier' => 'rate',
+      'default hook' => 'default_uc_taxes_rate',
+      'api' => array(
+        'owner' => 'uc_taxes',
+        'api' => 'default_uc_taxes_rates',
+        'minimum_version' => 1,
+        'current_version' => 1,
+      ),  
+    ),    
     'fields' => array(
       'id' => array(
         'description' => 'Primary key: Unique tax rate id.',
-        'type' => 'serial',
-        'unsigned' => TRUE,
+        'type' => 'varchar',
+        'length' => 255,
         'not null' => TRUE,
       ),
       'name' => array(
@@ -285,3 +296,45 @@ function uc_taxes_update_6002() {
 
   return $ret;
 }
+
+/**
+ * Migrate old id taxes to new ones.
+ */
+function uc_taxes_update_6003() {
+  $ret = array();
+
+  // Pull all existing taxes, and then delete existing ones. We will reinsert.
+  $result = db_query("SELECT * FROM {uc_taxes}");
+  $ret[] = update_sql("DELETE FROM {uc_taxes}");
+
+  db_drop_field($ret, 'uc_taxes', 'id');
+  $new_field = array(
+    'description' => 'The primary identifier for taxes.',
+    'type' => 'varchar',
+    'length' => 255,
+    'not null' => TRUE,
+  );
+  db_add_field($ret, 'uc_taxes', 'id', $new_field);
+  db_add_primary_key($ret, 'uc_taxes', array('id'));
+
+  $used = array();
+  while ($row = db_fetch_array($result)) {
+    // Generate a machine-readable string
+    $old_id = $row['id'];
+    $id = strtolower(preg_replace('/[^a-zA-Z0-9_]+/', '_', $row['name']));
+    $i = 0;
+    while (in_array($i == 0 ? $id : "{$id}_{$i}", $used)) {
+      $i++;
+    }
+    $row['id'] = $i == 0 ? $id : "{$id}_{$i}";
+
+    $placeholders = implode(', ', array_keys($row));
+    $tokens = implode(', ', array_fill(0, count($row), "'%s'"));
+    db_query("INSERT INTO {uc_taxes} ($placeholders) VALUES($tokens)", $row);
+    // Update the name of the associated predicate.
+    db_query("UPDATE {ca_predicates} SET pid = '%s' WHERE pid = '%s'", 'uc_taxes_'. $row['id'], 'uc_taxes_'. $old_id);
+    $used[] = $row['id'];
+    $ret[] = "Converted tax {$row['id']}.";
+  }
+  return $ret;
+}
\ No newline at end of file
Index: uc_taxes/uc_taxes.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ubercart/uc_taxes/uc_taxes.module,v
retrieving revision 1.12.2.20
diff -u -p -r1.12.2.20 uc_taxes.module
--- uc_taxes/uc_taxes.module	27 Jan 2010 22:23:32 -0000	1.12.2.20
+++ uc_taxes/uc_taxes.module	20 Jun 2010 14:05:20 -0000
@@ -86,6 +86,22 @@ function uc_taxes_menu() {
     'type' => MENU_CALLBACK,
     'file' => 'uc_taxes.admin.inc',
   );
+  $items['admin/store/settings/taxes/%/export'] = array(
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('uc_taxes_export_rates', 4),
+    'access arguments' => array('configure taxes'),
+    'type' => MENU_CALLBACK,
+    'file' => 'uc_taxes.export.inc',
+  );
+  $items['admin/store/settings/taxes/import'] = array(
+    'title' => 'Import tax rate',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('uc_taxes_import_rates'),
+    'access arguments' => array('configure taxes'),
+    'weight' => 6,
+    'type' => MENU_LOCAL_TASK,
+    'file' => 'uc_taxes.export.inc',
+  );
   $items['taxes/calculate'] = array(
     'page callback' => 'uc_taxes_javascript',
     'access arguments' => array('access content'),
@@ -298,15 +314,13 @@ function uc_line_item_tax_subtotal($op, 
  *   The saved tax rate object including the rate ID for new rates.
  */
 function uc_taxes_rate_save($rate) {
-  // Save it as a new rate if no ID is specified.
-  if (!$rate->id) {
-    drupal_write_record('uc_taxes', $rate);
+  $rate_exists = uc_taxes_rate_load($rate->id);
+  if ($rate_exists->id) {
+    drupal_write_record('uc_taxes', $rate, array('id'));
   }
-  // Otherwise update the existing tax rate's data.
   else {
-    drupal_write_record('uc_taxes', $rate, array('id'));
+    drupal_write_record('uc_taxes', $rate);
   }
-
   return $rate;
 }
 
@@ -355,7 +369,7 @@ function uc_taxes_rate_load($rate_id = N
  */
 function uc_taxes_rate_delete($rate_id) {
   // Delete the tax rate record.
-  db_query("DELETE FROM {uc_taxes} WHERE id = %d", $rate_id);
+  db_query("DELETE FROM {uc_taxes} WHERE id = '%s'", $rate_id);
 
   // Delete the associated predicated if it has been saved to the database.
   ca_delete_predicate('uc_taxes_'. $rate_id);
@@ -478,6 +492,7 @@ function uc_taxes_javascript() {
       }
     }
   }
+  dd($taxes);
   drupal_json((array) $taxes);
 }
 
@@ -555,3 +570,12 @@ function uc_taxes_apply_tax($order, $tax
     return $line_item;
   }
 }
+
+/**
+ * Implementation of hook_ctools_plugin_api().
+ */
+function uc_taxes_ctools_plugin_api($owner, $api) {
+  if ($owner == 'uc_taxes' && $api == 'default_uc_taxes_taxes') {
+    return array('version' => 1);
+  }
+}
