diff -r 6f67d00e556d hermes/lib/Driver/kolab.php
--- a/hermes/lib/Driver/kolab.php	Mon Jul 07 08:00:13 2008 +0200
+++ b/hermes/lib/Driver/kolab.php	Mon Jul 07 08:09:09 2008 +0200
@@ -71,6 +71,8 @@
         if (is_a($result, 'PEAR_Error')) {
             return $result;
         }
+
+        $this->_doComplete($info);
     }
 
     /**
@@ -119,9 +121,24 @@
                 if (is_a($result, 'PEAR_Error')) {
                     return $result;
                 }
+                $this->_doComplete($info);
+           }
+        }
+        return true;
+    }
+
+    function _doComplete($info)
+    {
+        if (!empty($info['complete']) && isset($info['costobject'])
+            && strpos($info['costobject'], ':') !== false) {
+            list($app, $app_id) = explode(':', $info['costobject'], 2);
+            $result = $GLOBALS['registry']->callByPackage($app, 'complete', array($app_id));
+            if (is_a($result, 'PEAR_Error')) {
+                Horde::logMessage(sprintf("Could not complete cost object %s: %s",
+                                          $app_id, $result->getMessage()),
+                                  __FILE__, __LINE__, PEAR_LOG_WARNING);
             }
         }
-        return true;
     }
 
     function getHours($filters = array(), $fields = array())
diff -r 6f67d00e556d hermes/lib/Forms/Time.php
--- a/hermes/lib/Forms/Time.php	Mon Jul 07 08:00:13 2008 +0200
+++ b/hermes/lib/Forms/Time.php	Mon Jul 07 08:09:09 2008 +0200
@@ -222,6 +222,9 @@
             $this->addVariable(_("Billable?"), 'billable', 'enum', true, false, null, array($yesno));
         }
 
+        $yesno = array(1 => _("Yes"), 0 => _("No"));
+        $this->addVariable(_("Complete cost object?"), 'complete', 'enum', true, false, null, array($yesno));
+
         if ($vars->exists('client')) {
             $info = $hermes->getClientSettings($vars->get('client'));
             if (!is_a($info, 'PEAR_Error') && !$info['enterdescription']) {
diff -r 6f67d00e556d nag/lib/api.php
--- a/nag/lib/api.php	Mon Jul 07 08:00:13 2008 +0200
+++ b/nag/lib/api.php	Mon Jul 07 08:09:09 2008 +0200
@@ -96,6 +96,11 @@
 
 $_services['replace'] = array(
     'args' => array('uid' => 'string', 'content' => 'string', 'contentType' => 'string'),
+    'type' => 'boolean',
+);
+
+$_services['complete'] = array(
+    'args' => array('uid' => '{urn:horde}stringArray'),
     'type' => 'boolean',
 );
 
@@ -1172,6 +1177,34 @@
 }
 
 /**
+ * Completes a task identified by UID.
+ *
+ * @param string|array $uid  Identify the task to complete, either a single UID
+ *                           or an array.
+ *
+ * @return boolean  Success or failure.
+ */
+function _nag_complete($uid)
+{
+    require_once dirname(__FILE__) . '/base.php';
+
+    $storage = &Nag_Driver::singleton();
+    $task = $storage->getByUID($uid);
+    if (is_a($task, 'PEAR_Error')) {
+        return $task;
+    }
+
+    if (!Auth::isAdmin() &&
+        !array_key_exists($task->tasklist,
+                          Nag::listTasklists(false, PERMS_EDIT))) {
+        return PEAR::raiseError(_("Permission Denied"));
+    }
+
+    $task->toggleComplete();
+    return $task->save();
+}
+
+/**
  * Replaces the task identified by UID with the content represented in the
  * specified content type.
  *