? .cvsignore
Index: stormattribute/stormattribute.install
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/storm/stormattribute/stormattribute.install,v
retrieving revision 1.2.4.16
diff -u -p -r1.2.4.16 stormattribute.install
--- stormattribute/stormattribute.install	4 Apr 2010 22:10:50 -0000	1.2.4.16
+++ stormattribute/stormattribute.install	26 Jun 2010 19:37:30 -0000
@@ -16,6 +16,9 @@ function stormattribute_install() {
     'hourly' => 'Hourly',
     'daily' => 'Daily',
     'fixed_timetracking' => 'Fixed Timetracking',
+    'fixed_project' => 'Fixed Project',
+    'fixed_task' => 'Fixed Task',
+    'fixed_ticket' => 'Fixed Ticket',
   );
 
   $attributes['Project status'] = array(
@@ -961,3 +964,17 @@ function stormattribute_update_6111() {
 
   return $ret;
 }
+
+/**
+ * Added more fixed attributes for issue 567558
+*/
+function stormattribute_update_6112() {
+  $ret = array();
+
+  $ret[] = update_sql("INSERT INTO {stormattribute} (domain, akey, avalue, weight, isactive) VALUES ('Price mode', 'fixed_project', 'Fixed Project', 0, 1)");
+  $ret[] = update_sql("INSERT INTO {stormattribute} (domain, akey, avalue, weight, isactive) VALUES ('Price mode', 'fixed_task', 'Fixed Task', 0, 1)");
+  $ret[] = update_sql("INSERT INTO {stormattribute} (domain, akey, avalue, weight, isactive) VALUES ('Price mode', 'fixed_ticket', 'Fixed Ticket', 0, 1)");
+
+  return $ret;
+}
+
Index: storminvoice/storminvoice.auto_add.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/storm/storminvoice/storminvoice.auto_add.inc,v
retrieving revision 1.1.2.3
diff -u -p -r1.1.2.3 storminvoice.auto_add.inc
--- storminvoice/storminvoice.auto_add.inc	22 Jul 2009 00:05:15 -0000	1.1.2.3
+++ storminvoice/storminvoice.auto_add.inc	26 Jun 2010 19:37:30 -0000
@@ -13,10 +13,10 @@ function storminvoice_auto_add($node, $i
       $invoice_nid = stormorganization_storminvoice_auto_add($node, $invoice);
       break;
     case 'stormproject':
-      $invoice_nid = stormorganization_storminvoice_auto_add($node, $invoice);
+      $invoice_nid = stormproject_storminvoice_auto_add($node, $invoice);
       break;
     case 'stormtask':
-      $invoice_nid = stormorganization_storminvoice_auto_add($node, $invoice);
+      $invoice_nid = stormtask_storminvoice_auto_add($node, $invoice);
       break;
     case 'stormticket':
       $invoice_nid = stormticket_storminvoice_auto_add($node, $invoice);
Index: storminvoice/storminvoice.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/storm/storminvoice/storminvoice.module,v
retrieving revision 1.4.4.77
diff -u -p -r1.4.4.77 storminvoice.module
--- storminvoice/storminvoice.module	18 May 2010 08:33:32 -0000	1.4.4.77
+++ storminvoice/storminvoice.module	26 Jun 2010 19:37:30 -0000
@@ -730,11 +730,15 @@ function _storminvoice_insert_items($nod
       (invoice_nid, invoice_vid, amount, description,
       tax1app, tax1percent, tax1, 
       tax2app, tax2percent, tax2, 
-      total, weight) VALUES
+      total, weight,
+      src_nid, src_vid
+      ) VALUES
       (%d, %d, %f, '%s',
       %d, %f, %f,
       %d, %f, %f,
-      %f, %d)",
+      %f, %d,
+      %d, %d
+      )",
       $node->nid, $node->vid, $node->items[$j]->amount, $node->items[$j]->description,
       $node->items[$j]->tax1app, $node->items[$j]->tax1percent, $node->items[$j]->tax1,
       $node->items[$j]->tax2app, $node->items[$j]->tax2percent, $node->items[$j]->tax2,
@@ -869,7 +873,6 @@ function storminvoice_getitems($invoice_
   }
   return $items;
 }
-
 /**
  * function to return the next invoice number.
  * It will look for the highest invoice number within the current year and add one.
@@ -886,24 +889,120 @@ function storminvoice_get_invoice_number
   return $new_invoice_number;
 }
 
+/**
+ * Try to obtain an hourly rate or fixed price for the new invoice item for a timetracking node.
+ * To do this we will use the first technique, among the following, to work:
+  
+ *  (1) try to obtain a rate from a ticket
+ *  (2) try to obtain a rate from a task
+ *  (3) try to obtain a rate from a project
+ *  (4) try to obtain a rate from an organization
+ *  (5) return an error if none of this worked.
+ *
+ * @return
+ *   Variables in an array.
+ */
+
+function storminvoice_get_rate($node) {
+  $rate_array = array('pricemode_used' => '', 'rate_to_use' => 0);
+
+  $hours_per_day = 8;
+  $found = FALSE;
+
+  $node_list = array();
+  
+  switch ($node->type) {
+    case 'stormtimetracking':
+      $node_list = array('ticket' => $node->ticket_nid, 'task' => $node->task_nid, 'project' => $node->project_nid, 'organization' => $node->organization_nid);
+      break;
+    case 'stormproject':
+      $node_list = array('project' => $node->nid, 'organization' => $node->organization_nid);
+      break;
+    case 'stormtask':
+      $node_list = array('task' => $node->nid, 'project' => $node->project_nid, 'organization' => $node->organization_nid);
+      break;
+    case 'stormticket':
+      $node_list = array('task' => $node->nid, 'project' => $node->project_nid, 'organization' => $node->organization_nid);
+      break;
+    }
+  
+    foreach ($node_list as $type => $nid) {
+    if($nid) {
+      $parent_item = node_load($nid);
+      switch ($parent_item->pricemode) {
+        case 'hourly':
+          $found = TRUE;
+          $rate_array['rate_to_use'] = $parent_item->price;
+          break;
+
+        case 'daily':
+          $found = TRUE;
+          $rate_array['rate_to_use'] = $parent_item->price / $hours_per_day;
+          break;
+
+        case 'fixed':
+          $found = TRUE;
+          $rate_array['rate_to_use'] = 0;
+          break;
+
+        case 'fixed_timetracking':
+          $found = TRUE;
+          $rate_array['rate_to_use'] = $parent_item->price;
+          break;
+
+        case 'fixed_project':
+          $found = TRUE;
+          $rate_array['rate_to_use'] = $parent_item->price;
+          break;
+
+        case 'fixed_task':
+          $found = TRUE;
+          $rate_array['rate_to_use'] = $parent_item->price;
+          break;
+
+        case 'fixed_ticket':
+          $found = TRUE;
+          $rate_array['rate_to_use'] = $parent_item->price;
+          break;
+
+        default:
+          continue;
+      }
+    }
+  if ($found == TRUE) {
+    $rate_array['pricemode_used'] = $parent_item->pricemode;
+    break;
+    }
+  }
+  if($found == FALSE) {
+    drupal_set_message(t('Error whilst finding a rate from ticket, task, project and organization. Consider setting the pricemode and price for your client organizations to avoid this error.'), 'error');
+    }
+  return $rate_array;
+}
+
 function storminvoice_get_item_desc($rate_array, $node) {
-  $description  = date('d M y', $node->trackingdate) . ': ';
 
   switch ($rate_array['pricemode_used']) {
     case 'hourly':
-      $description .= t('@dur hours work at @rate per hour on @desc', array('@dur' => $node->billing_duration, '@rate' => $rate_array['rate_to_use'], '@desc' => $node->title));
-      break;
-    case 'daily':
-      $description .= t('@dur hours work at @rate per day on @desc', array('@dur' => $node->billing_duration, '@rate' => $rate_array['rate_to_use'], '@desc' => $node->title));
+      $description = date('d M y', $node->trackingdate) . ': ' . t('@dur hours work at @rate per hour on @desc', array('@dur' => $node->billing_duration, '@rate' => $rate_array['rate_to_use'], '@desc' => $node->title));
       break;
     case 'daily':
-      $description .= t('@dur hours work at @rate per day on @desc', array('@dur' => $node->billing_duration, '@rate' => $rate_array['rate_to_use'], '@desc' => $node->title));
+      $description = date('d M y', $node->trackingdate) . ': ' . t('@dur hours work at @rate per day on @desc', array('@dur' => $node->billing_duration, '@rate' => $rate_array['rate_to_use'], '@desc' => $node->title));
       break;
     case 'fixed':
-      $description .= t('@dur hours unbilled work on @desc', array('@dur' => $node->billing_duration, '@desc' => $node->title));
+      $description = date('d M y', $node->trackingdate) . ': ' . t('@dur hours unbilled work on @desc', array('@dur' => $node->billing_duration, '@desc' => $node->title));
       break;
     case 'fixed_timetracking':
-      $description .= t('work billed for @desc', array('@desc' => $node->title));
+      $description = date('d M y', $node->trackingdate) . ': ' . t('work billed for @desc', array('@desc' => $node->title));
+      break;
+    case 'fixed_project':
+      $description = t('project billed: @desc', array('@desc' => $node->title));
+      break;
+    case 'fixed_task':
+      $description = t('task billed: @desc', array('@desc' => $node->title));
+      break;
+    case 'fixed_ticket':
+      $description = t('ticket billed: @desc', array('@desc' => $node->title));
       break;
   }
 
@@ -922,6 +1021,15 @@ function storminvoice_get_item_amount($r
     case 'fixed_timetracking':
       $amount = $rate_array['rate_to_use'];
       break;
+    case 'fixed_project':
+      $amount = $rate_array['rate_to_use'];
+      break;
+    case 'fixed_task':
+      $amount = $rate_array['rate_to_use'];
+      break;
+    case 'fixed_ticket':
+      $amount = $rate_array['rate_to_use'];
+      break;
     }
 
   return $amount;
Index: stormproject/stormproject.admin.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/storm/stormproject/Attic/stormproject.admin.inc,v
retrieving revision 1.9.4.20
diff -u -p -r1.9.4.20 stormproject.admin.inc
--- stormproject/stormproject.admin.inc	19 May 2010 20:33:42 -0000	1.9.4.20
+++ stormproject/stormproject.admin.inc	26 Jun 2010 19:37:30 -0000
@@ -157,6 +157,18 @@ function stormproject_list() {
     }
   }
   
+  if ($_SESSION['stormproject_list_filter']['billable'] != '-' && $_SESSION['stormproject_list_filter']['billable'] != NULL) {
+    $where[] = 'spr.billable=%d';
+    $args[] = $_SESSION['stormproject_list_filter']['billable'];
+    $filterfields[] = t('Billable');
+  }
+
+  if ($_SESSION['stormproject_list_filter']['billed'] != '-' && $_SESSION['stormproject_list_filter']['billed'] != NULL) {
+    $where[] = 'spr.billed=%d';
+    $args[] = $_SESSION['stormproject_list_filter']['billed'];
+    $filterfields[] = t('Billed');
+  }
+
   $itemsperpage = $_SESSION['stormproject_list_filter']['itemsperpage'];
   if (!$itemsperpage) {
     $itemsperpage = variable_get('storm_default_items_per_page', 10);
@@ -327,6 +339,21 @@ function stormproject_list_filter(&$form
     '#default_value' => $assigned_to,
     '#options' => $options,
   );
+
+  $form['filter']['group4']['billable'] = array(
+    '#type' => 'select',
+    '#title' => 'Billable',
+    '#options' => array('-' => t('all'), '1' => t('billable'), '0' => t('not billable')),
+    '#default_value' => $_SESSION['stormproject_list_filter']['billable'],
+  );
+
+  $form['filter']['group4']['billed'] = array(
+    '#type' => 'select',
+    '#title' => 'Billed',
+    '#options' => array('-' => t('all'), '1' => t('billed'), '0' => t('not billed')),
+    '#default_value' => $_SESSION['stormproject_list_filter']['billed'],
+  );
+
   
   $form['filter']['group5'] = array(
     '#type' => 'markup',
@@ -366,6 +393,8 @@ function stormproject_list_filter_filter
   $_SESSION['stormproject_list_filter']['datebeginto'] = $form_state['values']['datebeginto'];
   $_SESSION['stormproject_list_filter']['dateendfrom'] = $form_state['values']['dateendfrom'];
   $_SESSION['stormproject_list_filter']['dateendto'] = $form_state['values']['dateendto'];
+  $_SESSION['stormproject_list_filter']['billable'] = $form_state['values']['billable'];
+  $_SESSION['stormproject_list_filter']['billed'] = $form_state['values']['billed'];
   $_SESSION['stormproject_list_filter']['assigned_to'] = $form_state['values']['assigned_to'];
   $_SESSION['stormproject_list_filter']['itemsperpage'] = $form_state['values']['itemsperpage'];
 }
Index: stormproject/stormproject.install
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/storm/stormproject/stormproject.install,v
retrieving revision 1.2.4.9
diff -u -p -r1.2.4.9 stormproject.install
--- stormproject/stormproject.install	23 Nov 2009 20:35:46 -0000	1.2.4.9
+++ stormproject/stormproject.install	26 Jun 2010 19:37:31 -0000
@@ -39,6 +39,8 @@ function stormproject_schema() {
       'manager_title'       => array('type' => 'varchar', 'length' => 100),
       'assigned_nid'        => array('type' => 'int'),
       'assigned_title'      => array('type' => 'varchar', 'length' => 100),
+      'billable'            => array('type' => 'int', 'default' => 0),
+      'billed'              => array('type' => 'int', 'default' => 0),
     ),
     'primary key' => array('vid', 'nid'),
   );
@@ -77,4 +79,14 @@ function stormproject_update_6104() {
   db_add_field($ret, 'stormproject', 'assigned_nid', array('type' => 'int'));
   db_add_field($ret, 'stormproject', 'assigned_title', array('type' => 'varchar', 'length' => 100));
   return $ret;
+}
+
+/**
+ * Added billable and billed fields to stormproject table for issue 567558
+*/
+function stormproject_update_6105() {
+  $ret = array();
+  db_add_field($ret, 'stormproject', 'billable', array('type' => 'int', 'default' => 0));
+  db_add_field($ret, 'stormproject', 'billed', array('type' => 'int', 'default' => 0));
+  return $ret;
 }
\ No newline at end of file
Index: stormproject/stormproject.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/storm/stormproject/stormproject.module,v
retrieving revision 1.8.4.57
diff -u -p -r1.8.4.57 stormproject.module
--- stormproject/stormproject.module	19 Jan 2010 18:27:29 -0000	1.8.4.57
+++ stormproject/stormproject.module	26 Jun 2010 19:37:31 -0000
@@ -249,6 +249,15 @@ function stormproject_menu() {
     'type' => MENU_CALLBACK,
   );
 
+  $items['admin/settings/storm/project'] = array(
+    'title' => 'Storm Project',
+    'description' => 'Storm Project Administration Page',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('stormproject_admin_settings'),
+    'access arguments' => array('Storm: access administration pages'),
+    'type' => MENU_LOCAL_TASK,
+  );
+
   return $items;
 }
 
@@ -285,6 +294,7 @@ function stormproject_content_extra_fiel
       'group3' => array('label' => 'Date/Duration Group', 'weight' => -18),
       'group4' => array('label' => 'Price Group', 'weight' => -17),
       'group5' => array('label' => 'Manager / Assigned to Group', 'weight' => -16),
+      'group6' => array('label' => 'Billable / Billed Group', 'weight' => -15),
     );
   }
 }
@@ -319,6 +329,9 @@ function stormproject_form(&$node) {
     }
     $s_org = "SELECT n.nid, n.title FROM {stormorganization} so INNER JOIN {node} n 
               ON so.nid=n.nid WHERE n.status=1 AND so.isactive=1 AND so.iscustomer=1 AND n.type='stormorganization' ORDER BY n.title";
+
+    $node->billable = variable_get('stormproject_billable_default', FALSE);
+
   }
   else {
     $s_org = "SELECT n.nid, n.title FROM {stormorganization} so INNER JOIN {node} n 
@@ -522,6 +535,26 @@ function stormproject_form(&$node) {
     '#default_value' => $node->assigned_nid,
   );
   
+  $form['group6'] = array(
+    '#type' => 'markup',
+    '#theme' => 'storm_form_group',
+    '#weight' => module_exists('content') ? content_extra_field_weight($node->type, 'group6') : -15,
+  );
+
+  $form['group6']['billable'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Billable'),
+    '#default_value' => $node->billable,
+    '#weight' => 1,
+  );
+  
+  $form['group6']['billed'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Billed'),
+    '#default_value' => $node->billed,
+    '#weight' => 2,
+  );
+
   if ($type->has_body) {
     $form['body_field'] = node_body_field($node, $type->body_label, $type->min_word_count);
   }
@@ -550,13 +583,13 @@ function stormproject_insert($node) {
   db_query("INSERT INTO {stormproject}
     (vid, nid, organization_nid, organization_title, projectcategory, projectstatus, 
     projectpriority, pricemode, price, currency, datebegin, dateend, durationunit, duration,
-    manager_nid, manager_title, assigned_nid, assigned_title)
+    manager_nid, manager_title, assigned_nid, assigned_title, billable, billed)
     VALUES
-    (%d, %d, %d, '%s', '%s', '%s', '%s', '%s', %f, '%s', %d, %d, '%s', %f, %d, '%s', %d, '%s')",
+    (%d, %d, %d, '%s', '%s', '%s', '%s', '%s', %f, '%s', %d, %d, '%s', %f, %d, '%s', %d, '%s', %d, %d)",
     $node->vid, $node->nid, $node->organization_nid, $organization->title, $node->projectcategory,
     $node->projectstatus, $node->projectpriority, $node->pricemode, 
     $node->price, $node->currency, $node->datebegin, $node->dateend, $node->durationunit, $node->duration,
-    $node->manager_nid, $manager->title, $node->assigned_nid, $assigned->title);
+    $node->manager_nid, $manager->title, $node->assigned_nid, $assigned->title, $node->billable, $node->billed);
 }
 
 function stormproject_update($node) {
@@ -587,10 +620,12 @@ function stormproject_update($node) {
       organization_nid=%d, organization_title='%s',
       projectcategory='%s', projectstatus='%s', projectpriority='%s', pricemode='%s', 
       price=%f, currency='%s', datebegin=%d, dateend=%d, durationunit='%s', duration=%f,
-      manager_nid=%d, manager_title='%s', assigned_nid=%d, assigned_title='%s' WHERE vid = %d",
+      manager_nid=%d, manager_title='%s', assigned_nid=%d, assigned_title='%s',
+      billable=%d, billed=%d WHERE vid = %d",
       $node->organization_nid, $organization->title, $node->projectcategory, $node->projectstatus, $node->projectpriority,
       $node->pricemode, $node->price, $node->currency, $node->datebegin, $node->dateend, $node->durationunit, $node->duration,
-      $node->manager_nid, $manager->title, $node->assigned_nid, $assigned->title, $node->vid
+      $node->manager_nid, $manager->title, $node->assigned_nid, $assigned->title, 
+      $node->billable, $node->billed, $node->vid
     );
     
     if ($node->title != $node->title_old) {
@@ -675,3 +710,88 @@ function stormproject_views_api() {
     'path' => drupal_get_path('module', 'stormproject'),
   );
 }
+
+// ADMIN SETTINGS
+function stormproject_admin_settings() {
+  $form = array();
+
+  $form['stormproject_billable_default'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Default Value for billable field.'),
+    '#default_value' => variable_get('stormproject_billable_default', FALSE),
+    '#description' => t('When checked, Storm will set the project to billable by default'),
+    '#size' => 5,
+  );
+  
+  return system_settings_form($form);
+}
+
+// INVOICE AUTO ADD HANDLER
+function stormproject_storminvoice_auto_add($node, $invoice_nid = NULL) {
+  if (!module_exists('storminvoice')) {
+    drupal_set_message(t('This function should only be called from within Storm Invoice'));
+    return;
+  } elseif ($node->billed) {
+    drupal_set_message(t('This project has already been billed!'));
+    return;
+  } elseif (!$node->billable) {
+    drupal_set_message(t('This project is marked unbillable!'));
+    return;
+  } else {
+    global $user;
+    
+    if (!$invoice_nid) {
+    
+      $new_invoice = new StdClass;
+      
+      //Code copied with edits from node form
+      $new_invoice->requestdate = time();
+      $new_invoice->duedate = $new_invoice->requestdate + (variable_get('storminvoice_payment_days', 30) * 86400);
+      $new_invoice->number = storminvoice_get_invoice_number($new_invoice->requestdate);
+
+      $new_invoice->title = $node->title;
+      $new_invoice->uid = $user->uid;
+      $new_invoice->type = 'storminvoice';
+      //$new_invoice->reference
+      $new_invoice->organization_nid = $node->organization_nid;
+      $new_invoice->organization_title = $node->organization_title;
+      $new_invoice->project_nid = $node->nid;
+      $new_invoice->project_title = $node->title;
+      //$new_invoice->amount
+      //$new_invoice->tax
+      //$new_invoice->total
+      //$new_invoice->totalcustomercurr
+      //$new_invoice->taxexempt
+      $new_invoice->src_nid = $node->nid;
+      $new_invoice->src_vid = $node->vid;
+      
+      node_save($new_invoice);
+      $invoice_nid = $new_invoice->nid;
+    } else {
+      $new_invoice = node_load($invoice_nid);
+    }
+
+    $rate_array = array('pricemode_used' => '', 'rate_to_use' => 0);
+    
+    $rate_array = storminvoice_get_rate($node);
+    
+    $count = count($new_invoice->items);
+    
+    $new_invoice->items[$count]->description = storminvoice_get_item_desc($rate_array, $node);
+
+    $new_invoice->items[$count]->amount = storminvoice_get_item_amount($rate_array, $node);
+
+    // Tax percent simply uses default at the moment
+    $new_invoice->items[$count]->tax1percent = variable_get('storm_tax1_percent', 20);
+    //$new_invoice_item->items[$count]->tax1
+    //$new_invoice_item->items[$count]->total
+    
+    storm_taxation($new_invoice->items[$count]);
+    storminvoice_update($new_invoice);
+  }
+  
+  // Mark project as billed.
+  db_query("UPDATE {stormproject} SET billed=%d WHERE vid=%d", 1, $node->vid);
+  
+  return $invoice_nid;
+}
\ No newline at end of file
Index: stormproject/stormproject.theme.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/storm/stormproject/stormproject.theme.inc,v
retrieving revision 1.2.4.19
diff -u -p -r1.2.4.19 stormproject.theme.inc
--- stormproject/stormproject.theme.inc	14 Jun 2010 19:09:11 -0000	1.2.4.19
+++ stormproject/stormproject.theme.inc	26 Jun 2010 19:37:31 -0000
@@ -37,14 +37,26 @@ function theme_stormproject_view($node, 
     '#suffix' => '</dl></div>',
     '#weight' => -25,
   );
-  
+
   $node->content['links']['expenses'] = theme('storm_link', 'stormproject', 'stormexpense', $node->nid, $l_pos++);
   $node->content['links']['invoices'] = theme('storm_link', 'stormproject', 'storminvoice', $node->nid, $l_pos++);
   $node->content['links']['notes'] = theme('storm_link', 'stormproject', 'stormnote', $node->nid, $l_pos++);
   $node->content['links']['tasks'] = theme('storm_link', 'stormproject', 'stormtask', $node->nid, $l_pos++);
   $node->content['links']['tickets'] = theme('storm_link', 'stormproject', 'stormticket', $node->nid, $l_pos++);
   $node->content['links']['timetrackings'] = theme('storm_link', 'stormproject', 'stormtimetracking', $node->nid, $l_pos++);
-  
+
+  // Code to create invoice auto_add link
+  if (module_exists('storminvoice')) {
+
+    $node->content['links']['auto_invoice'] = array(
+      '#prefix' => variable_get('storm_icons_display', TRUE) ? '<dt id="storminvoices" class="stormcomponent">' : '<dt class="stormcomponent">',
+      '#suffix' => '</dt>',
+      '#value' => theme('storminvoice_autoadd_links', $node->nid, $node->billable, $node->billed),
+      '#weight' => $l_pos++,
+    );
+  }
+
+
   $node->content['group1'] = array(
     '#prefix' => '<div class="stormfields">',
     '#suffix' => '</div>',
Index: stormproject/stormproject.views.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/storm/stormproject/Attic/stormproject.views.inc,v
retrieving revision 1.1.2.7
diff -u -p -r1.1.2.7 stormproject.views.inc
--- stormproject/stormproject.views.inc	12 Dec 2009 21:38:39 -0000	1.1.2.7
+++ stormproject/stormproject.views.inc	26 Jun 2010 19:37:31 -0000
@@ -278,6 +278,34 @@ function stormproject_views_data() {
     ),
   );
   
+  $data['stormproject']['billable'] = array(
+    'title' => t('Project Billable'),
+    'help' => 'Storm Project Billable',
+    'field' => array(
+      'click sortable' => TRUE,
+    ),
+    'sort' => array(
+      'handler' => 'views_handler_sort',
+    ),
+    'filter' => array(
+      'handler' => 'views_handler_filter_numeric',
+    ),
+  );
+  
+  $data['stormproject']['billed'] = array(
+    'title' => t('Project Billed'),
+    'help' => 'Storm Project Billed',
+    'field' => array(
+      'click sortable' => TRUE,
+    ),
+    'sort' => array(
+      'handler' => 'views_handler_sort',
+    ),
+    'filter' => array(
+      'handler' => 'views_handler_filter_numeric',
+    ),
+  );
+
   return $data;
 }
 
Index: stormtask/stormtask.admin.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/storm/stormtask/stormtask.admin.inc,v
retrieving revision 1.8.4.34
diff -u -p -r1.8.4.34 stormtask.admin.inc
--- stormtask/stormtask.admin.inc	23 Apr 2010 16:01:40 -0000	1.8.4.34
+++ stormtask/stormtask.admin.inc	26 Jun 2010 19:37:31 -0000
@@ -593,6 +593,18 @@ function stormtask_list() {
     }
   }
 
+  if ($_SESSION['stormtask_list_filter']['billable'] != '-' && $_SESSION['stormtask_list_filter']['billable'] != NULL) {
+    $where[] = 'sta.billable=%d';
+    $args[] = $_SESSION['stormtask_list_filter']['billable'];
+    $filterfields[] = t('Billable');
+  }
+
+  if ($_SESSION['stormtask_list_filter']['billed'] != '-' && $_SESSION['stormtask_list_filter']['billed'] != NULL) {
+    $where[] = 'sta.billed=%d';
+    $args[] = $_SESSION['stormtask_list_filter']['billed'];
+    $filterfields[] = t('Billed');
+  }
+
   $itemsperpage = $_SESSION['stormtask_list_filter']['itemsperpage'];
   if (!$itemsperpage) {
     $itemsperpage = variable_get('storm_default_items_per_page', 10);
@@ -776,7 +788,12 @@ function stormtask_list_filter(&$form_st
     '#title' => t('Date end to'),
     '#default_value' => $dateendto,
   );
-  
+
+  $form['filter']['group4_1'] = array(
+    '#type' => 'markup',
+    '#theme' => 'storm_form_group',
+  );
+
   // ASSIGNED TO
   $options = storm_get_assignment_options($node->organization_nid, $node->project_nid);
   $form['filter']['group4_1']['assigned_to'] = array(
@@ -786,6 +803,20 @@ function stormtask_list_filter(&$form_st
     '#options' => $options,
   );
 
+  $form['filter']['group4_1']['billable'] = array(
+    '#type' => 'select',
+    '#title' => 'Billable',
+    '#options' => array('-' => t('all'), '1' => t('billable'), '0' => t('not billable')),
+    '#default_value' => $_SESSION['stormtask_list_filter']['billable'],
+  );
+
+  $form['filter']['group4_1']['billed'] = array(
+    '#type' => 'select',
+    '#title' => 'Billed',
+    '#options' => array('-' => t('all'), '1' => t('billed'), '0' => t('not billed')),
+    '#default_value' => $_SESSION['stormtask_list_filter']['billed'],
+  );
+
   $form['filter']['group5'] = array(
     '#type' => 'markup',
     '#theme' => 'storm_form_group',
@@ -826,6 +857,8 @@ function stormtask_list_filter_filter($f
   $_SESSION['stormtask_list_filter']['dateendfrom'] = $form_state['values']['dateendfrom'];
   $_SESSION['stormtask_list_filter']['dateendto'] = $form_state['values']['dateendto'];
   $_SESSION['stormtask_list_filter']['assigned_to'] = $form_state['values']['assigned_to'];
+  $_SESSION['stormtask_list_filter']['billable'] = $form_state['values']['billable'];
+  $_SESSION['stormtask_list_filter']['billed'] = $form_state['values']['billed'];
   $_SESSION['stormtask_list_filter']['itemsperpage'] = $form_state['values']['itemsperpage'];
 }
 
Index: stormtask/stormtask.install
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/storm/stormtask/stormtask.install,v
retrieving revision 1.2.4.6
diff -u -p -r1.2.4.6 stormtask.install
--- stormtask/stormtask.install	17 Dec 2009 22:51:46 -0000	1.2.4.6
+++ stormtask/stormtask.install	26 Jun 2010 19:37:31 -0000
@@ -42,6 +42,8 @@ function stormtask_schema() {
       'duration'            => array('type' => 'float', 'default' => 0),
       'assigned_nid'        => array('type' => 'int'),
       'assigned_title'      => array('type' => 'varchar', 'length' => 100),
+      'billable'            => array('type' => 'int', 'default' => 0),
+      'billed'              => array('type' => 'int', 'default' => 0),
     ),
     'primary key' => array('vid', 'nid'),
   );
@@ -75,3 +77,14 @@ function stormtask_update_6103() {
   db_add_field($ret, 'stormtask', 'assigned_title', array('type' => 'varchar', 'length' => 100));
   return $ret;
 }
+
+/**
+ * Added billable and billed fields to stormtask table for issue 567558
+*/
+function stormtask_update_6106() {
+  $ret = array();
+  db_add_field($ret, 'stormtask', 'billable', array('type' => 'int', 'default' => 0));
+  db_add_field($ret, 'stormtask', 'billed', array('type' => 'int', 'default' => 0));
+  return $ret;
+}
+
Index: stormtask/stormtask.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/storm/stormtask/stormtask.module,v
retrieving revision 1.6.4.60
diff -u -p -r1.6.4.60 stormtask.module
--- stormtask/stormtask.module	19 May 2010 20:33:42 -0000	1.6.4.60
+++ stormtask/stormtask.module	26 Jun 2010 19:37:31 -0000
@@ -268,6 +268,14 @@ function stormtask_menu() {
 function stormtask_admin_settings() {
   $form = array();
   
+  $form['stormtask_billable_default'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Default Value for billable field.'),
+    '#default_value' => variable_get('stormtask_billable_default', FALSE),
+    '#description' => t('When checked, Storm will set the task to billable by default'),
+    '#size' => 5,
+  );
+
   $form['stormtask_enable_ganttchart'] = array(
     '#type' => 'checkbox',
     '#title' => t('Enable Gantt Charts (experimental feature)'),
@@ -381,6 +389,7 @@ function stormtask_content_extra_fields(
       'group3' => array('label' => 'Date/Duration Group', 'weight' => -18),
       'group4' => array('label' => 'Price Group', 'weight' => -17),
       'group5' => array('label' => 'Assigned to', 'weight' => -16),
+      'group6' => array('label' => 'Billable / Billed Group', 'weight' => -15),
     );
   }
 }
@@ -456,6 +465,9 @@ function stormtask_form(&$node) {
     }
     $s_org = "SELECT n.nid, n.title FROM {stormorganization} so INNER JOIN {node} n 
       ON so.nid=n.nid WHERE n.status=1 AND so.isactive=1 AND so.iscustomer=1 AND n.type='stormorganization' ORDER BY n.title";
+
+    $node->billable = variable_get('stormtask_billable_default', FALSE);
+
   }
   else {
     $s_org = "SELECT n.nid, n.title FROM {stormorganization} so INNER JOIN {node} n 
@@ -672,7 +684,27 @@ function stormtask_form(&$node) {
     '#options' => $options,
     '#default_value' => $node->assigned_nid,
   );
+
+  $form['group6'] = array(
+    '#type' => 'markup',
+    '#theme' => 'storm_form_group',
+    '#weight' => module_exists('content') ? content_extra_field_weight($node->type, 'group6') : -15,
+  );
+
+  $form['group6']['billable'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Billable'),
+    '#default_value' => $node->billable,
+    '#weight' => 1,
+  );
   
+  $form['group6']['billed'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Billed'),
+    '#default_value' => $node->billed,
+    '#weight' => 2,
+  );
+
   if ($type->has_body) {
     $form['body_field'] = node_body_field($node, $type->body_label, $type->min_word_count);
   }
@@ -698,16 +730,16 @@ function stormtask_insert($node) {
     organization_nid, organization_title,
     project_nid, project_title, parent_nid, weight,
     datebegin, dateend, durationunit, duration, pricemode, price, currency,
-    assigned_nid, assigned_title)
+    assigned_nid, assigned_title, billable, billed)
     VALUES
     (%d, %d, '%s', '%s', '%s', '%s',
     %d, '%s',
-    %d, '%s', %d, %d, %d, %d, '%s', %f, '%s', %f, '%s', %d, '%s')",
+    %d, '%s', %d, %d, %d, %d, '%s', %f, '%s', %f, '%s', %d, '%s', %d, %d)",
     $node->vid, $node->nid, $node->stepno, $node->taskcategory, $node->taskstatus, $node->taskpriority,
     $node->organization_nid, $node->organization_title, $node->project_nid, $node->project_title,
     $node->parent_nid, $node->weight, $node->datebegin, $node->dateend, $node->durationunit, $node->duration,
     $node->pricemode, $node->price, $node->currency,
-    $node->assigned_nid, $node->assigned_title
+    $node->assigned_nid, $node->assigned_title, $node->billable, $node->billed
   );
 }
 
@@ -738,7 +770,8 @@ function stormtask_update($node) {
       organization_nid=%d, organization_title='%s',
       project_nid=%d, project_title='%s',
       parent_nid=%d, weight=%d, datebegin=%d, dateend=%d, durationunit='%s', duration=%f,
-      pricemode='%s', price=%f, currency='%s', assigned_nid=%d, assigned_title='%s'  
+      pricemode='%s', price=%f, currency='%s', assigned_nid=%d, assigned_title='%s',
+      billable=%d, billed=%d
       WHERE vid = %d",
       $node->stepno, $node->taskcategory, $node->taskstatus, $node->taskpriority,
       $node->organization_nid, $node->organization_title,
@@ -747,7 +780,7 @@ function stormtask_update($node) {
       $node->weight, $node->datebegin, $node->dateend, $node->durationunit, $node->duration, 
       $node->pricemode, $node->price, $node->currency, 
       $node->assigned_nid, $node->assigned_title,
-      $node->vid
+      $node->billable, $node->billed, $node->vid
     );
     if (($node->title != $node->title_old) || ($node->stepno != $node->stepno_old)) {
       module_invoke_all('stormtask_change', $node->nid, $node->stepno, $node->title);
@@ -914,3 +947,73 @@ function stormtask_views_api() {
     'path' => drupal_get_path('module', 'stormtask'),
   );
 }
+
+// INVOICE AUTO ADD HANDLER
+function stormtask_storminvoice_auto_add($node, $invoice_nid = NULL) {
+  if (!module_exists('storminvoice')) {
+    drupal_set_message(t('This function should only be called from within Storm Invoice'));
+    return;
+  } elseif ($node->billed) {
+    drupal_set_message(t('This task has already been billed!'));
+    return;
+  } elseif (!$node->billable) {
+    drupal_set_message(t('This task is marked unbillable!'));
+    return;
+  } else {
+    global $user;
+    
+    if (!$invoice_nid) {
+    
+      $new_invoice = new StdClass;
+      
+      //Code copied with edits from node form
+      $new_invoice->requestdate = time();
+      $new_invoice->duedate = $new_invoice->requestdate + (variable_get('storminvoice_payment_days', 30) * 86400);
+      $new_invoice->number = storminvoice_get_invoice_number($new_invoice->requestdate);
+
+      $new_invoice->title = $node->title;
+      $new_invoice->uid = $user->uid;
+      $new_invoice->type = 'storminvoice';
+      //$new_invoice->reference
+      $new_invoice->organization_nid = $node->organization_nid;
+      $new_invoice->organization_title = $node->organization_title;
+      $new_invoice->project_nid = $node->project_nid;
+      $new_invoice->project_title = $node->project_title;
+      //$new_invoice->amount
+      //$new_invoice->tax
+      //$new_invoice->total
+      //$new_invoice->totalcustomercurr
+      //$new_invoice->taxexempt
+      $new_invoice->src_nid = $node->nid;
+      $new_invoice->src_vid = $node->vid;
+      
+      node_save($new_invoice);
+      $invoice_nid = $new_invoice->nid;
+    } else {
+      $new_invoice = node_load($invoice_nid);
+    }
+
+    $rate_array = array('pricemode_used' => '', 'rate_to_use' => 0);
+    
+    $rate_array = storminvoice_get_rate($node);
+    
+    $count = count($new_invoice->items);
+    
+    $new_invoice->items[$count]->description = storminvoice_get_item_desc($rate_array, $node);
+
+    $new_invoice->items[$count]->amount = storminvoice_get_item_amount($rate_array, $node);
+
+    // Tax percent simply uses default at the moment
+    $new_invoice->items[$count]->tax1percent = variable_get('storm_tax1_percent', 20);
+    //$new_invoice_item->items[$count]->tax1
+    //$new_invoice_item->items[$count]->total
+    
+    storm_taxation($new_invoice->items[$count]);
+    storminvoice_update($new_invoice);
+  }
+  
+  // Mark task as billed.
+  db_query("UPDATE {stormtask} SET billed=%d WHERE vid=%d", 1, $node->vid);
+  
+  return $invoice_nid;
+}
\ No newline at end of file
Index: stormtask/stormtask.theme.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/storm/stormtask/stormtask.theme.inc,v
retrieving revision 1.3.4.25
diff -u -p -r1.3.4.25 stormtask.theme.inc
--- stormtask/stormtask.theme.inc	14 Jun 2010 19:09:12 -0000	1.3.4.25
+++ stormtask/stormtask.theme.inc	26 Jun 2010 19:37:31 -0000
@@ -97,6 +97,17 @@ function theme_stormtask_view($node, $te
   $node->content['links']['tickets'] = theme('storm_link', 'stormtask', 'stormticket', $node->nid, $l_pos++);
   $node->content['links']['timetrackings'] = theme('storm_link', 'stormtask', 'stormtimetracking', $node->nid, $l_pos++);
   
+  // Code to create invoice auto_add link
+  if (module_exists('storminvoice')) {
+
+    $node->content['links']['auto_invoice'] = array(
+      '#prefix' => variable_get('storm_icons_display', TRUE) ? '<dt id="storminvoices" class="stormcomponent">' : '<dt class="stormcomponent">',
+      '#suffix' => '</dt>',
+      '#value' => theme('storminvoice_autoadd_links', $node->nid, $node->billable, $node->billed),
+      '#weight' => $l_pos++,
+    );
+  }
+
   $node->content['group1'] = array(
     '#prefix' => '<div class="stormfields">',
     '#suffix' => '</div>',
Index: stormtask/stormtask.views.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/storm/stormtask/Attic/stormtask.views.inc,v
retrieving revision 1.1.2.6
diff -u -p -r1.1.2.6 stormtask.views.inc
--- stormtask/stormtask.views.inc	17 Dec 2009 22:51:46 -0000	1.1.2.6
+++ stormtask/stormtask.views.inc	26 Jun 2010 19:37:31 -0000
@@ -329,6 +329,34 @@ function stormtask_views_data() {
     ),
   );
   
+  $data['stormtask']['billable'] = array(
+    'title' => t('Task Billable'),
+    'help' => 'Storm Task Billable',
+    'field' => array(
+      'click sortable' => TRUE,
+    ),
+    'sort' => array(
+      'handler' => 'views_handler_sort',
+    ),
+    'filter' => array(
+      'handler' => 'views_handler_filter_numeric',
+    ),
+  );
+  
+  $data['stormtask']['billed'] = array(
+    'title' => t('Tasl Billed'),
+    'help' => 'Storm Task Billed',
+    'field' => array(
+      'click sortable' => TRUE,
+    ),
+    'sort' => array(
+      'handler' => 'views_handler_sort',
+    ),
+    'filter' => array(
+      'handler' => 'views_handler_filter_numeric',
+    ),
+  );
+
   return $data;
 }
 
Index: stormticket/stormticket.install
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/storm/stormticket/stormticket.install,v
retrieving revision 1.2.4.6
diff -u -p -r1.2.4.6 stormticket.install
--- stormticket/stormticket.install	17 Dec 2009 22:51:46 -0000	1.2.4.6
+++ stormticket/stormticket.install	26 Jun 2010 19:37:32 -0000
@@ -42,6 +42,8 @@ function stormticket_schema() {
       'duration'            => array('type' => 'float', 'default' => 0),
       'assigned_nid'        => array('type' => 'int'),
       'assigned_title'      => array('type' => 'varchar', 'length' => 100),
+      'billable'            => array('type' => 'int', 'default' => 0),
+      'billed'              => array('type' => 'int', 'default' => 0),
     ),
     'primary key' => array('vid', 'nid'),
   );
@@ -75,4 +77,14 @@ function stormticket_update_6103() {
   db_add_field($ret, 'stormticket', 'assigned_nid', array('type' => 'int'));
   db_add_field($ret, 'stormticket', 'assigned_title', array('type' => 'varchar', 'length' => 100));
   return $ret;
-}
\ No newline at end of file
+}
+
+/**
+ * Added billable and billed fields to stormticket table for issue 567558
+*/
+function stormticket_update_6106() {
+  $ret = array();
+  db_add_field($ret, 'stormticket', 'billable', array('type' => 'int', 'default' => 0));
+  db_add_field($ret, 'stormticket', 'billed', array('type' => 'int', 'default' => 0));
+  return $ret;
+}
Index: stormticket/stormticket.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/storm/stormticket/stormticket.module,v
retrieving revision 1.5.4.45
diff -u -p -r1.5.4.45 stormticket.module
--- stormticket/stormticket.module	18 May 2010 08:33:32 -0000	1.5.4.45
+++ stormticket/stormticket.module	26 Jun 2010 19:37:32 -0000
@@ -241,6 +241,15 @@ function stormticket_menu() {
     'file' => 'stormticket.admin.inc',
     'type' => MENU_CALLBACK,
   );
+
+  $items['admin/settings/storm/ticket'] = array(
+    'title' => 'Storm Ticket',
+    'description' => 'Storm Ticket Administration Page',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('stormticket_admin_settings'),
+    'access arguments' => array('Storm: access administration pages'),
+    'type' => MENU_LOCAL_TASK,
+  );
   
   return $items;
 }
@@ -359,6 +368,9 @@ function stormticket_form(&$node) {
     if (array_key_exists('task_nid', $_GET)) $node->task_nid = $_GET['task_nid'];
     $s_org = "SELECT n.nid, n.title FROM {stormorganization} so INNER JOIN {node} n 
               ON so.nid=n.nid WHERE n.status=1 AND so.isactive=1 AND so.iscustomer=1 AND n.type='stormorganization' ORDER BY n.title";
+
+    $node->billable = variable_get('stormticket_billable_default', FALSE);
+
   }
   else {
     $s_org = "SELECT n.nid, n.title FROM {stormorganization} so INNER JOIN {node} n 
@@ -530,7 +542,7 @@ function stormticket_form(&$node) {
   $form['group5'] = array(
     '#type' => 'markup',
     '#theme' => 'storm_form_group',
-    '#weight' => module_exists('content') ? content_extra_field_weight($node->type, 'group5') : -16,
+    '#weight' => module_exists('content') ? content_extra_field_weight($node->type, 'group5') : -15,
   );
   
   $options = storm_get_assignment_options($node->organization_nid, $node->project_nid);
@@ -541,7 +553,27 @@ function stormticket_form(&$node) {
     '#options' => $options,
     '#default_value' => $node->assigned_nid,
   );
+
+  $form['group6'] = array(
+    '#type' => 'markup',
+    '#theme' => 'storm_form_group',
+    '#weight' => module_exists('content') ? content_extra_field_weight($node->type, 'group6') : -14,
+  );
+
+  $form['group6']['billable'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Billable'),
+    '#default_value' => $node->billable,
+    '#weight' => 1,
+  );
   
+  $form['group6']['billed'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Billed'),
+    '#default_value' => $node->billed,
+    '#weight' => 2,
+  );
+
   if ($type->has_body) {
     $form['body_field'] = node_body_field($node, $type->body_label, $type->min_word_count);
   }
@@ -562,15 +594,15 @@ function stormticket_insert($node) {
     (vid, nid, organization_nid, organization_title, project_nid, project_title,
     task_nid, task_title, task_stepno, ticketcategory, ticketstatus, ticketpriority, 
     datebegin, dateend, durationunit, duration, pricemode, price, currency, 
-    assigned_nid, assigned_title)
+    assigned_nid, assigned_title, billable, billed)
     VALUES
     (%d, %d, %d, '%s', %d, '%s',
     %d, '%s', '%s', '%s', '%s', '%s',
-    %d, %d, '%s', %f, '%s', %f, '%s', %d, '%s')",
+    %d, %d, '%s', %f, '%s', %f, '%s', %d, '%s', %d, %d)",
     $node->vid, $node->nid, $node->organization_nid, $node->organization_title, $node->project_nid, $node->project_title,
     $node->task_nid, $node->task_title, $node->task_stepno, $node->ticketcategory, $node->ticketstatus, $node->ticketpriority,
     $node->datebegin, $node->dateend, $node->durationunit, $node->duration, $node->pricemode, $node->price, $node->currency,
-    $node->assigned_nid, $node->assigned_title
+    $node->assigned_nid, $node->assigned_title, $node->billable, $node->billed
   );
 }
 
@@ -606,13 +638,13 @@ function stormticket_update($node) {
       organization_nid=%d, organization_title='%s', project_nid=%d, project_title='%s',
       task_nid=%d, task_title='%s', task_stepno='%s', ticketcategory='%s', ticketstatus='%s', 
       ticketpriority='%s', datebegin=%d, dateend=%d, durationunit='%s', duration=%f, pricemode='%s', 
-      price=%f, currency='%s', assigned_nid=%d, assigned_title='%s' WHERE vid = %d",
+      price=%f, currency='%s', assigned_nid=%d, assigned_title='%s', billable=%d, billed=%d WHERE vid = %d",
       $node->organization_nid, $node->organization_title, $node->project_nid, $node->project_title,
       $node->task_nid, $node->task_title, $node->task_stepno, $node->ticketcategory, $node->ticketstatus, 
       $node->ticketpriority, $node->datebegin, $node->dateend, $node->durationunit, $node->duration, $node->pricemode, 
       $node->price, $node->currency, 
       $node->assigned_nid, $node->assigned_title,
-      $node->vid
+      $node->billable, $node->billed, $node->vid
     );
     if ($node->title != $node->title_old) {
       module_invoke_all('stormticket_change', $node->nid, $node->title);
@@ -697,3 +729,88 @@ function stormticket_views_api() {
     'path' => drupal_get_path('module', 'stormticket'),
   );
 }
+
+// ADMIN SETTINGS
+function stormticket_admin_settings() {
+  $form = array();
+
+  $form['stormticket_billable_default'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Default Value for billable field.'),
+    '#default_value' => variable_get('stormticket_billable_default', FALSE),
+    '#description' => t('When checked, Storm will set the ticket to billable by default'),
+    '#size' => 5,
+  );
+  
+  return system_settings_form($form);
+}
+
+// INVOICE AUTO ADD HANDLER
+function stormticket_storminvoice_auto_add($node, $invoice_nid = NULL) {
+  if (!module_exists('storminvoice')) {
+    drupal_set_message(t('This function should only be called from within Storm Invoice'));
+    return;
+  } elseif ($node->billed) {
+    drupal_set_message(t('This ticket has already been billed!'));
+    return;
+  } elseif (!$node->billable) {
+    drupal_set_message(t('This ticket is marked unbillable!'));
+    return;
+  } else {
+    global $user;
+    
+    if (!$invoice_nid) {
+    
+      $new_invoice = new StdClass;
+      
+      //Code copied with edits from node form
+      $new_invoice->requestdate = time();
+      $new_invoice->duedate = $new_invoice->requestdate + (variable_get('storminvoice_payment_days', 30) * 86400);
+      $new_invoice->number = storminvoice_get_invoice_number($new_invoice->requestdate);
+
+      $new_invoice->title = $node->title;
+      $new_invoice->uid = $user->uid;
+      $new_invoice->type = 'storminvoice';
+      //$new_invoice->reference
+      $new_invoice->organization_nid = $node->organization_nid;
+      $new_invoice->organization_title = $node->organization_title;
+      $new_invoice->project_nid = $node->project_nid;
+      $new_invoice->project_title = $node->project_title;
+      //$new_invoice->amount
+      //$new_invoice->tax
+      //$new_invoice->total
+      //$new_invoice->totalcustomercurr
+      //$new_invoice->taxexempt
+      $new_invoice->src_nid = $node->nid;
+      $new_invoice->src_vid = $node->vid;
+      
+      node_save($new_invoice);
+      $invoice_nid = $new_invoice->nid;
+    } else {
+      $new_invoice = node_load($invoice_nid);
+    }
+
+    $rate_array = array('pricemode_used' => '', 'rate_to_use' => 0);
+    
+    $rate_array = storminvoice_get_rate($node);
+    
+    $count = count($new_invoice->items);
+    
+    $new_invoice->items[$count]->description = storminvoice_get_item_desc($rate_array, $node);
+
+    $new_invoice->items[$count]->amount = storminvoice_get_item_amount($rate_array, $node);
+
+    // Tax percent simply uses default at the moment
+    $new_invoice->items[$count]->tax1percent = variable_get('storm_tax1_percent', 20);
+    //$new_invoice_item->items[$count]->tax1
+    //$new_invoice_item->items[$count]->total
+    
+    storm_taxation($new_invoice->items[$count]);
+    storminvoice_update($new_invoice);
+  }
+  
+  // Mark task as billed.
+  db_query("UPDATE {stormticket} SET billed=%d WHERE vid=%d", 1, $node->vid);
+  
+  return $invoice_nid;
+}
Index: stormticket/stormticket.theme.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/storm/stormticket/stormticket.theme.inc,v
retrieving revision 1.5.4.18
diff -u -p -r1.5.4.18 stormticket.theme.inc
--- stormticket/stormticket.theme.inc	14 Jun 2010 19:09:12 -0000	1.5.4.18
+++ stormticket/stormticket.theme.inc	26 Jun 2010 19:37:32 -0000
@@ -48,6 +48,17 @@ function theme_stormticket_view($node, $
   $node->content['links']['expenses'] = theme('storm_link', 'stormticket', 'stormexpense', $node->nid, $l_pos++);
   $node->content['links']['timetrackings'] = theme('storm_link', 'stormticket', 'stormtimetracking', $node->nid, $l_pos++);
 
+  // Code to create invoice auto_add link
+  if (module_exists('storminvoice')) {
+
+    $node->content['links']['auto_invoice'] = array(
+      '#prefix' => variable_get('storm_icons_display', TRUE) ? '<dt id="storminvoices" class="stormcomponent">' : '<dt class="stormcomponent">',
+      '#suffix' => '</dt>',
+      '#value' => theme('storminvoice_autoadd_links', $node->nid, $node->billable, $node->billed),
+      '#weight' => $l_pos++,
+    );
+  }
+
   $node->content['group1'] = array(
     '#prefix' => '<div class="stormfields">',
     '#suffix' => '</div>',
Index: stormticket/stormticket.views.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/storm/stormticket/Attic/stormticket.views.inc,v
retrieving revision 1.1.2.10
diff -u -p -r1.1.2.10 stormticket.views.inc
--- stormticket/stormticket.views.inc	23 Apr 2010 15:59:19 -0000	1.1.2.10
+++ stormticket/stormticket.views.inc	26 Jun 2010 19:37:32 -0000
@@ -227,6 +227,34 @@ function stormticket_views_data() {
     ),
   );
   
+  $data['stormtask']['billable'] = array(
+    'title' => t('Task Billable'),
+    'help' => 'Storm Task Billable',
+    'field' => array(
+      'click sortable' => TRUE,
+    ),
+    'sort' => array(
+      'handler' => 'views_handler_sort',
+    ),
+    'filter' => array(
+      'handler' => 'views_handler_filter_numeric',
+    ),
+  );
+  
+  $data['stormtask']['billed'] = array(
+    'title' => t('Tasl Billed'),
+    'help' => 'Storm Task Billed',
+    'field' => array(
+      'click sortable' => TRUE,
+    ),
+    'sort' => array(
+      'handler' => 'views_handler_sort',
+    ),
+    'filter' => array(
+      'handler' => 'views_handler_filter_numeric',
+    ),
+  );
+
   return $data;
 }
 
Index: stormtimetracking/stormtimetracking.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/storm/stormtimetracking/stormtimetracking.module,v
retrieving revision 1.10.4.52
diff -u -p -r1.10.4.52 stormtimetracking.module
--- stormtimetracking/stormtimetracking.module	13 May 2010 09:51:24 -0000	1.10.4.52
+++ stormtimetracking/stormtimetracking.module	26 Jun 2010 19:37:32 -0000
@@ -690,9 +690,9 @@ function stormtimetracking_storminvoice_
       $new_invoice = node_load($invoice_nid);
     }
 
-    $rate_array = array('pricemode_used' => '', 'use_fixed' => FALSE, 'use_hourly' => FALSE, 'use_fixed_timetracking' => FALSE, 'rate_to_use' => 0);
+    $rate_array = array('pricemode_used' => '', 'rate_to_use' => 0);
     
-    $rate_array = stormtimetracking_timetracking_get_rate($node);
+    $rate_array = storminvoice_get_rate($node);
     
     $count = count($new_invoice->items);
     
@@ -723,64 +723,3 @@ function stormtimetracking_views_api() {
   );
 }
 
-/**
- * Try to obtain an hourly rate or fixed price for the new invoice item for a timetracking node.
- * To do this we will use the first technique, among the following, to work:
-  
- *  (1) try to obtain a rate from a ticket
- *  (2) try to obtain a rate from a task
- *  (3) try to obtain a rate from a project
- *  (4) try to obtain a rate from an organization
- *  (5) return an error if none of this worked.
- *
- * @return
- *   Variables in an array.
- */
-function stormtimetracking_timetracking_get_rate($node) {
-  $rate_array = array('pricemode_used' => '', 'use_fixed' => FALSE, 'use_hourly' => FALSE, 'use_fixed_timetracking' => FALSE, 'rate_to_use' => 0);
-
-  $hours_per_day = 8;
-  $found = FALSE;
-  
-  foreach (array('ticket' => $node->ticket_nid, 'task' => $node->task_nid, 'project' => $node->project_nid, 'organization' => $node->organization_nid) as $type => $nid) {
-    if($nid) {
-      $parent_item = node_load($nid);
-      switch ($parent_item->pricemode) {
-        case 'hourly':
-          $rate_array['use_hourly'] = TRUE;
-          $found = TRUE;
-          $rate_array['rate_to_use'] = $parent_item->price;
-          break;
-
-        case 'daily':
-          $rate_array['use_hourly'] = TRUE;
-          $found = TRUE;
-          $rate_array['rate_to_use'] = $parent_item->price / $hours_per_day;
-          break;
-
-        case 'fixed':
-          $rate_array['use_fixed'] = TRUE;
-          $found = TRUE;
-          $rate_array['rate_to_use'] = 0;
-          break;
-
-        case 'fixed_timetracking':
-          $rate_array['use_fixed_timetracking'] = TRUE;
-          $found = TRUE;
-          $rate_array['rate_to_use'] = $parent_item->price;
-          break;
-
-        default:
-          continue;
-      }
-    }
-  if ($found == TRUE) {
-  	$rate_array['pricemode_used'] = $parent_item->pricemode;
-    break;
-    }
-  }
-  if($found == FALSE) {
-    drupal_set_message(t('Error whilst finding a rate from ticket, task, project and organization. Consider setting the pricemode and price for your client organizations to avoid this error.'), 'error');
-    }
-  return $rate_array;
-}
