Index: /main/waeup.sirp/trunk/src/waeup/sirp/accesscodes/accesscodes.py
===================================================================
--- /main/waeup.sirp/trunk/src/waeup/sirp/accesscodes/accesscodes.py	(revision 6449)
+++ /main/waeup.sirp/trunk/src/waeup/sirp/accesscodes/accesscodes.py	(revision 6450)
@@ -52,4 +52,8 @@
 
     @property
+    def status(self):
+        return IWorkflowState(self).getState()
+
+    @property
     def disabled(self):
         return IWorkflowState(self).getState() == DISABLED
@@ -62,5 +66,5 @@
     def history(self):
         history = IObjectHistory(self)
-        return '\n'.join(history.messages)
+        return '<br />'.join(history.messages)
 
 class AccessCodeBatch(grok.Container):
@@ -288,17 +292,9 @@
         return
 
-    def search(self, searchterm, searchtype, ):
-        """Look for access-codes that comply with the given params.
-        """
-        results = []
-        if searchtype == 'serial':
-            try:
-                searchterm = int(searchterm)
-            except:
-                return []
-        for batchname in self.keys():
-            part_result = self[batchname].search(searchterm, searchtype)
-            results.extend(part_result)
-        return results
+    #def search(self, searchterm, searchtype):
+    #    """Look for access-codes that comply with the given params.
+    #    """
+    #    results = []
+    #    return results
 
     def getAccessCode(self, ac_id):
@@ -322,8 +318,8 @@
         if ac is None:
             return
-        ac.__parent__.disable(ac_id, user_id)
-        return
-
-    def enable(self, ac_id):
+        disable_accesscode(ac_id, user_id)
+        return
+
+    def enable(self, ac_id, user_id):
         """(Re-)enable the AC with ID ``ac_id``.
 
@@ -334,15 +330,15 @@
         if ac is None:
             return
-        ac.__parent__.enable(ac_id)
-        return
-
-    def invalidate(self, ac_id):
-        """Invalidate the AC with ID ``ac_id``.
-        """
-        ac = self.getAccessCode(ac_id)
-        if ac is None:
-            return
-        ac.__parent__.invalidate(ac_id)
-        return
+        reenable_accesscode(ac_id, user_id)
+        return
+
+    #def invalidate(self, ac_id):
+    #    """Invalidate the AC with ID ``ac_id``.
+    #    """
+    #    ac = self.getAccessCode(ac_id)
+    #    if ac is None:
+    #        return
+    #    ac.__parent__.invalidate(ac_id)
+    #    return
 
 
Index: /main/waeup.sirp/trunk/src/waeup/sirp/accesscodes/browser.py
===================================================================
--- /main/waeup.sirp/trunk/src/waeup/sirp/accesscodes/browser.py	(revision 6449)
+++ /main/waeup.sirp/trunk/src/waeup/sirp/accesscodes/browser.py	(revision 6450)
@@ -3,4 +3,5 @@
 import grok
 from datetime import datetime
+from hurry.workflow.interfaces import InvalidTransitionError
 from waeup.sirp.browser import WAeUPPage, WAeUPAddFormPage
 from waeup.sirp.browser.breadcrumbs import Breadcrumb
@@ -12,4 +13,5 @@
     IAccessCodeBatchContainer, IAccessCodeBatch,
     )
+from waeup.sirp.accesscodes.catalog import search
 
 grok.context(IWAeUPObject)
@@ -46,5 +48,5 @@
     grok.require('waeup.manageACBatches')
 
-    title = 'Create Scratch Card Batch'
+    title = 'Create Access Code Batch'
     pnav = 0
 
@@ -109,43 +111,42 @@
     grok.require('waeup.manageACBatches')
 
-    title = 'Search Scratch Cards'
-    pnav = 0
-
-    def update(self, search=None, searchterm=None, searchtype=None,
-               entries=None, disable=None, enable=None):
-        self.searchresults = None
-        if search is not None:
-            searchresults = self.context.search(searchterm, searchtype)
-            if len(searchresults) == 0:
-                return
-            self.searchresults = []
-            for result in searchresults:
-                status = u'unused'
-                if result.disabled is True:
-                    status = u'disabled by %s on %s' % (
-                        result.student_id,
-                        result.invalidation_date.strftime('%c')
-                        )
-                elif result.invalidation_date is not None:
-                    status = u'invalidated by %s on %s' % (
-                        result.student_id,
-                        result.invalidation_date.strftime('%c')
-                        )
-                entry = dict(
-                    serial = result.batch_serial,
-                    code = result.representation,
-                    status = status)
-                self.searchresults.append(entry)
-        if entries is None:
-            return
+    title = 'Access Codes'
+    pnav = 0
+    label = 'Search and manage access codes'
+
+    def update(self, *args, **kw):
+        form = self.request.form
+        self.hitlist = []
+        if 'searchterm' in form and form['searchterm']:
+            self.searchterm = form['searchterm']
+            self.searchtype = form['searchtype']
+        elif 'old_searchterm' in form:
+            self.searchterm = form['old_searchterm']
+            self.searchtype = form['old_searchtype']
+        else:
+            return
+        #import pdb; pdb.set_trace()
+        if not 'entries' in form:
+            self.hitlist = search(query=self.searchterm,
+                searchtype=self.searchtype, view=self)
+            return
+        entries = form['entries']
         if isinstance(entries, basestring):
             entries = [entries]
         for entry in entries:
-            if disable is not None:
-                self.context.disable(entry, self.request.principal.id)
-                self.flash('disabled %s' % entry)
-            elif enable is not None:
-                self.context.enable(entry)
-                self.flash('(re-)enabled %s' % entry)
+            if 'disable' in form:
+                try:
+                    self.context.disable(entry, self.request.principal.id)
+                    self.flash('%s disabled.' % entry)
+                except InvalidTransitionError:
+                    self.flash('Transition not allowed.')
+            elif 'enable' in form:
+                try:
+                    self.context.enable(entry, self.request.principal.id)
+                    self.flash('%s (re-)enabled.' % entry)
+                except InvalidTransitionError:
+                    self.flash('Transition not allowed.')
+        self.hitlist = search(query=self.searchterm,
+            searchtype=self.searchtype, view=self)
         return
 
@@ -164,5 +165,5 @@
     grok.context(IAccessCodeBatchContainer)
     grok.name('search')
-    title = u'Search Scratch Cards'
+    title = u'Search Access Codes'
     viewname = 'search'
     parent_viewname = 'index'
@@ -184,5 +185,5 @@
     grok.view(BatchContainerPage)
     grok.require('waeup.manageACBatches')
-    text = 'Add Scratch Card Batch'
+    text = 'Add Access Code Batch'
 
 class ReimportBatchButton(BatchOpButton):
@@ -193,5 +194,5 @@
     grok.require('waeup.manageACBatches')
     target = 'reimport'
-    text = 'Reimport Scratch Card Batch'
+    text = 'Reimport Access Code Batch'
 
 class SearchAccessCodeButton(SearchActionButton):
@@ -201,5 +202,5 @@
     grok.view(BatchContainerPage)
     grok.require('waeup.manageACBatches')
-    text = 'Search Scratch Cards'
+    text = 'Search Access Codes'
 
 class ManageAccessCodes(ManageLink):
Index: /main/waeup.sirp/trunk/src/waeup/sirp/accesscodes/browser.txt
===================================================================
--- /main/waeup.sirp/trunk/src/waeup/sirp/accesscodes/browser.txt	(revision 6449)
+++ /main/waeup.sirp/trunk/src/waeup/sirp/accesscodes/browser.txt	(revision 6450)
@@ -70,5 +70,5 @@
 action bar:
 
-    >>> browser.getLink('Add Scratch Card Batch').click()
+    >>> browser.getLink('Add Access Code Batch').click()
 
 The add screen shows a form where we have to enter a prefix, the
@@ -92,5 +92,5 @@
 'Create batch' in the form:
 
-    >>> browser.getLink('Add Scratch Card Batch').click()
+    >>> browser.getLink('Add Access Code Batch').click()
     >>> browser.getControl(name='form.prefix').value = 'APP'
     >>> browser.getControl(name='form.entry_num').value = '5'
@@ -126,5 +126,5 @@
 works:
 
-    >>> browser.getLink('Add Scratch Card Batch').click()
+    >>> browser.getLink('Add Access Code Batch').click()
     >>> browser.getControl(name='form.prefix').value = 'APP'
     >>> browser.getControl(name='form.entry_num').value = '5'
@@ -134,5 +134,5 @@
 And a third one that can be deleted afterwards:
 
-    >>> browser.getLink('Add Scratch Card Batch').click()
+    >>> browser.getLink('Add Access Code Batch').click()
     >>> browser.getControl(name='form.prefix').value = 'BLA'
     >>> browser.getControl(name='form.entry_num').value = '3'
@@ -192,5 +192,5 @@
 list of importable files is empty:
 
-    >>> browser.getLink('Reimport Scratch Card Batch').click()
+    >>> browser.getLink('Reimport Access Code Batch').click()
     >>> print browser.contents
     <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
@@ -218,5 +218,5 @@
 Now the file will be presented as import source:
 
-    >>> browser.getLink('Reimport Scratch Card Batch').click()
+    >>> browser.getLink('Reimport Access Code Batch').click()
     >>> filename in browser.contents
     True
@@ -232,5 +232,5 @@
 Now let's really reimport the batch:
 
-    >>> browser.getLink('Reimport Scratch Card Batch').click()
+    >>> browser.getLink('Reimport Access Code Batch').click()
     >>> ctrl = browser.getControl(name='filenames')
     >>> ctrl.getControl(value=filename).selected = True
@@ -246,5 +246,5 @@
 If we try to reimport an existing batch, that won't work:
 
-    >>> browser.getLink('Reimport Scratch Card Batch').click()
+    >>> browser.getLink('Reimport Access Code Batch').click()
     >>> ctrl = browser.getControl(name='filenames')
     >>> ctrl.getControl(value=filename).selected = True
Index: /main/waeup.sirp/trunk/src/waeup/sirp/accesscodes/browser_templates/searchpage.pt
===================================================================
--- /main/waeup.sirp/trunk/src/waeup/sirp/accesscodes/browser_templates/searchpage.pt	(revision 6449)
+++ /main/waeup.sirp/trunk/src/waeup/sirp/accesscodes/browser_templates/searchpage.pt	(revision 6450)
@@ -1,39 +1,40 @@
 <form method="POST">
+
+<br />
+
   <input type="submit" name="search" value="Search" />
-  for scratch cards 
+  for access codes
 
   <select name="searchtype">
-    <option value="stud_id">
-      used by student with ID
-    </option>
-    <option value="pin">with PIN</option>
-    <option value="serial">with serial</option>
-  </select>:
+    <option value="code">with PIN</option>
+    <option value="batch_serial">with serial</option>
+    <option value="history">with message term</option>
+  </select>
 
   <input type="text" name="searchterm" />
-  <br /><br />
-  <b>complete numbers only</b>
-  (Student ID means registration number for APP SCs)
-  <br />
 
   <p>&nbsp;</p>
-  <div tal:condition="view/searchresults">
+  <div tal:condition="view/hitlist">
     <h3>Search Results</h3>
-    <table>
+    <input type="hidden" name="old_searchterm"
+         tal:attributes="value view/searchterm" />
+    <input type="hidden" name="old_searchtype"
+         tal:attributes="value view/searchtype" />
+    <table class = "zebra">
       <thead>
-	<tr>
-	  <th>&nbsp;</th>
-	  <th>Serial</th><th>AC</th><th>Status</th>
-	</tr>
+    	<tr>
+    	  <th>&nbsp;</th>
+    	  <th>Serial</th><th>AC</th><th>Status</th><th>History</th>
+    	</tr>
       </thead>
       <tbody>
-	<tr tal:repeat="item view/searchresults"
-	    tal:attributes="class python: repeat['item'].odd() and 'even' or 'odd'">
-	  <td><input type="checkbox" name="entries"
-	             tal:attributes="value item/code" /></td>
-	  <td tal:content="item/serial">1</td>
-	  <td tal:content="item/code">APP-1-1234567890</td>
-	  <td tal:content="item/status">unused</td>
-	</tr>
+    	<tr tal:repeat="item view/hitlist">
+    	  <td><input type="checkbox" name="entries"
+    	             tal:attributes="value item/code" /></td>
+    	  <td tal:content="item/batch_serial">1</td>
+    	  <td tal:content="item/code">APP-1-1234567890</td>
+    	  <td tal:content="item/status">unused</td>
+          <td tal:content="structure item/history">history</td>
+    	</tr>
       </tbody>
     </table>
Index: /main/waeup.sirp/trunk/src/waeup/sirp/accesscodes/catalog.py
===================================================================
--- /main/waeup.sirp/trunk/src/waeup/sirp/accesscodes/catalog.py	(revision 6449)
+++ /main/waeup.sirp/trunk/src/waeup/sirp/accesscodes/catalog.py	(revision 6450)
@@ -2,5 +2,8 @@
 """
 import grok
-from waeup.sirp.interfaces import IUniversity
+from hurry.query import Eq, Text
+from hurry.query.query import Query
+from zope.index.text.parsetree import ParseError
+from waeup.sirp.interfaces import IUniversity, IQueryResultItem
 from waeup.sirp.accesscodes.interfaces import IAccessCode
 
@@ -16,2 +19,39 @@
     disabled = grok.index.Field(attribute='disabled')
     used = grok.index.Field(attribute='used')
+    batch_serial = grok.index.Field(attribute='batch_serial')
+    status = grok.index.Field(attribute='status')
+
+class AccessCodeQueryResultItem(object):
+    grok.implements(IQueryResultItem)
+
+    def __init__(self, context, view):
+        self.context = context
+        #self.url = view.url(context)
+        self.code = context.representation
+        self.history = context.history
+        self.status = context.status
+        self.batch_serial = context.batch_serial
+
+def search(query=None, searchtype=None, view=None):
+    if not query:
+        view.flash('Empty search string.')
+        return
+    hitlist = []
+    try:
+        if searchtype == 'history':
+            results = Query().searchResults(
+                Text(('accesscodes_catalog', searchtype), query))
+        elif searchtype == 'batch_serial':
+            results = Query().searchResults(
+                Eq(('accesscodes_catalog', searchtype), int(query)))
+        else:
+            results = Query().searchResults(
+                Eq(('accesscodes_catalog', searchtype), query))
+        for result in results:
+            hitlist.append(AccessCodeQueryResultItem(result, view=view))
+    except ParseError:
+        view.flash('Search string not allowed.')
+        return
+    return hitlist
+
+
Index: /main/waeup.sirp/trunk/src/waeup/sirp/accesscodes/interfaces.py
===================================================================
--- /main/waeup.sirp/trunk/src/waeup/sirp/accesscodes/interfaces.py	(revision 6449)
+++ /main/waeup.sirp/trunk/src/waeup/sirp/accesscodes/interfaces.py	(revision 6450)
@@ -23,4 +23,7 @@
         title = u'Cost of access code',
         default = 0.0, min = 0.0,
+        )
+    status = schema.TextLine(
+        title = u'Workflow status',
         )
     disabled = schema.Bool(
