Index: main/waeup.ikoba/trunk/src/waeup/ikoba/customers/workflow.py
===================================================================
--- main/waeup.ikoba/trunk/src/waeup/ikoba/customers/workflow.py	(revision 11981)
+++ main/waeup.ikoba/trunk/src/waeup/ikoba/customers/workflow.py	(revision 11982)
@@ -122,7 +122,4 @@
 def handle_customer_transition_event(obj, event):
     """Append message to customer history and log file when transition happened.
-
-    Lock and unlock clearance form.
-    Trigger actions after school fee payment.
     """
 
Index: main/waeup.ikoba/trunk/src/waeup/ikoba/documents/__init__.py
===================================================================
--- main/waeup.ikoba/trunk/src/waeup/ikoba/documents/__init__.py	(revision 11982)
+++ main/waeup.ikoba/trunk/src/waeup/ikoba/documents/__init__.py	(revision 11982)
@@ -0,0 +1,10 @@
+"""This package contains everything regarding documents.
+"""
+# Make this a package.
+from waeup.ikoba.documents.container import DocumentsContainer
+from waeup.ikoba.documents.document import Document
+
+__all__ = [
+    'DocumentsContainer',
+    'Document',
+    ]
Index: main/waeup.ikoba/trunk/src/waeup/ikoba/documents/catalog.py
===================================================================
--- main/waeup.ikoba/trunk/src/waeup/ikoba/documents/catalog.py	(revision 11982)
+++ main/waeup.ikoba/trunk/src/waeup/ikoba/documents/catalog.py	(revision 11982)
@@ -0,0 +1,31 @@
+## $Id: catalog.py 8700 2012-06-12 21:01:17Z henrik $
+##
+## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+"""Components to help cataloging and searching documents.
+"""
+import grok
+from waeup.ikoba.interfaces import ICompany
+from waeup.ikoba.documents.interfaces import IDocument
+
+class DocumentIndexes(grok.Indexes):
+    """A catalog for all documents.
+    """
+    grok.site(ICompany)
+    grok.name('documents_catalog')
+    grok.context(IDocument)
+
+    id = grok.index.Field(attribute='id')
Index: main/waeup.ikoba/trunk/src/waeup/ikoba/documents/container.py
===================================================================
--- main/waeup.ikoba/trunk/src/waeup/ikoba/documents/container.py	(revision 11982)
+++ main/waeup.ikoba/trunk/src/waeup/ikoba/documents/container.py	(revision 11982)
@@ -0,0 +1,43 @@
+## $Id: container.py 7811 2012-03-08 19:00:51Z uli $
+##
+## Copyright (C) 2014 Uli Fouquet & Henrik Bettermann
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+"""
+Containers which contain document objects.
+"""
+import grok
+from grok import index
+from waeup.ikoba.documents.interfaces import IDocumentsContainer, IDocument
+from waeup.ikoba.utils.helpers import attrs_to_fields
+
+class DocumentsContainer(grok.Container):
+    """This is a container for all kind of documents.
+    """
+    grok.implements(IDocumentsContainer)
+    grok.provides(IDocumentsContainer)
+
+    def __init__(self):
+        super(DocumentsContainer, self).__init__()
+        return
+
+    def addDocument(self, document):
+        if not IDocument.providedBy(document):
+            raise TypeError(
+                'DocumentsContainers contain only IDocument instances')
+        self[document.id] = document
+        return
+
+DocumentsContainer = attrs_to_fields(DocumentsContainer)
Index: main/waeup.ikoba/trunk/src/waeup/ikoba/documents/document.py
===================================================================
--- main/waeup.ikoba/trunk/src/waeup/ikoba/documents/document.py	(revision 11982)
+++ main/waeup.ikoba/trunk/src/waeup/ikoba/documents/document.py	(revision 11982)
@@ -0,0 +1,81 @@
+## $Id: document.py 10842 2013-12-11 13:21:37Z henrik $
+##
+## Copyright (C) 2014 Uli Fouquet & Henrik Bettermann
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+"""
+These are the document tickets.
+"""
+import grok
+from datetime import datetime
+from grok import index
+from hurry.workflow.interfaces import IWorkflowInfo, IWorkflowState
+from zope.event import notify
+from zope.component import getUtility
+from zope.component.interfaces import IFactory
+from zope.interface import implementedBy
+from zope.i18n import translate
+from waeup.ikoba.interfaces import IIkobaUtils
+from waeup.ikoba.interfaces import MessageFactory as _
+from waeup.ikoba.documents.interfaces import IDocument, IDocument
+from waeup.ikoba.utils.helpers import attrs_to_fields, get_current_principal
+from waeup.ikoba.utils.logger import Logger
+
+class Document(grok.Container, Logger):
+    """This is a document.
+    """
+    grok.implements(IDocument)
+    grok.provides(IDocument)
+    grok.baseclass()
+
+    logger_name = 'waeup.ikoba.${sitename}.documents'
+    logger_filename = 'documents.log'
+    logger_format_str = '"%(asctime)s","%(user)s",%(message)s'
+
+    def logger_info(self, comment=None):
+        """Get the logger's info method.
+        """
+        self.logger.info('%s' % comment)
+        return
+
+    def __init__(self):
+        super(Document, self).__init__()
+        self.id = None
+        return
+
+Document = attrs_to_fields(Document)
+
+class DocumentFactory(grok.GlobalUtility):
+    """A factory for documents.
+    """
+    grok.implements(IFactory)
+    grok.name(u'waeup.Document')
+    title = u"Create a new document.",
+    description = u"This factory instantiates new documents."
+
+    def __call__(self, *args, **kw):
+        return Document(*args, **kw)
+
+    def getInterfaces(self):
+        return implementedBy(Document)
+
+@grok.subscribe(IDocument, grok.IObjectAddedEvent)
+def handle_document_added(document, event):
+    """If a document is added the transition create is fired.
+    The latter produces a logging message.
+    """
+    if IWorkflowState(document).getState() is None:
+        IWorkflowInfo(document).fireTransition('create')
+    return
Index: main/waeup.ikoba/trunk/src/waeup/ikoba/documents/interfaces.py
===================================================================
--- main/waeup.ikoba/trunk/src/waeup/ikoba/documents/interfaces.py	(revision 11982)
+++ main/waeup.ikoba/trunk/src/waeup/ikoba/documents/interfaces.py	(revision 11982)
@@ -0,0 +1,49 @@
+## $Id: interfaces.py 11450 2014-02-27 06:25:18Z henrik $
+##
+## Copyright (C) 2014 Uli Fouquet & Henrik Bettermann
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+from zope.interface import Attribute
+from zope import schema
+from waeup.ikoba.interfaces import (
+    IIkobaObject, SimpleIkobaVocabulary, 
+    ContextualDictSourceFactoryBase)
+from waeup.ikoba.interfaces import MessageFactory as _
+
+class DocumentCategorySource(ContextualDictSourceFactoryBase):
+    """A document category source delivers all categories of documents.
+
+    """
+    #: name of dict to deliver from ikoba utils.
+    DICT_NAME = 'DOCUMENT_CATEGORIES'
+
+class IDocumentsContainer(IIkobaObject):
+    """A container for all kind of document objects.
+
+    """
+
+class IDocument(IIkobaObject):
+    """A base representation of documents.
+
+    """
+    id = Attribute('Document Identifier')
+
+    category = schema.Choice(
+        title = _(u'Document Category'),
+        source = DocumentCategorySource(),
+        required = True,
+        )
+
+
Index: main/waeup.ikoba/trunk/src/waeup/ikoba/documents/tests.py
===================================================================
--- main/waeup.ikoba/trunk/src/waeup/ikoba/documents/tests.py	(revision 11982)
+++ main/waeup.ikoba/trunk/src/waeup/ikoba/documents/tests.py	(revision 11982)
@@ -0,0 +1,79 @@
+## $Id: tests.py 9469 2012-10-30 17:49:17Z henrik $
+##
+## Copyright (C) 2014 Uli Fouquet & Henrik Bettermann
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+"""
+Tests for documents.
+"""
+from zope.interface.verify import verifyClass, verifyObject
+from zope.component import createObject
+from hurry.workflow.interfaces import (
+    IWorkflowInfo, IWorkflowState, InvalidTransitionError)
+from waeup.ikoba.documents.interfaces import (
+    IDocumentsContainer, IDocument)
+from waeup.ikoba.documents.container import DocumentsContainer
+from waeup.ikoba.documents.document import Document
+from waeup.ikoba.testing import (FunctionalLayer, FunctionalTestCase)
+
+class DocumentsContainerTestCase(FunctionalTestCase):
+
+    layer = FunctionalLayer
+
+    def test_interfaces(self):
+        # Make sure the correct interfaces are implemented.
+        self.assertTrue(
+            verifyClass(
+                IDocumentsContainer, DocumentsContainer)
+            )
+        self.assertTrue(
+            verifyObject(
+                IDocumentsContainer, DocumentsContainer())
+            )
+        self.assertTrue(
+            verifyClass(
+                IDocument, Document)
+            )
+        self.assertTrue(
+            verifyObject(
+                IDocument, Document())
+            )
+        return
+
+    def test_addDocument(self):
+        container = DocumentsContainer()
+        document = createObject(u'waeup.Document')
+        document.id = 'my_document'
+        container.addDocument(document)
+        self.assertEqual(container['my_document'], document)
+        self.assertRaises(TypeError, container.addDocument, object())
+        return
+
+    def test_document_workflow(self):
+        document = createObject(u'waeup.Document')
+        IWorkflowInfo(document).fireTransition('create')
+        self.assertEqual(IWorkflowState(document).getState(), 'created')
+        IWorkflowInfo(document).fireTransition('submit')
+        self.assertEqual(IWorkflowState(document).getState(), 'submitted')
+        IWorkflowInfo(document).fireTransition('verify')
+        self.assertEqual(IWorkflowState(document).getState(), 'verified')
+        IWorkflowInfo(document).fireTransition('reset2')
+        self.assertEqual(IWorkflowState(document).getState(), 'created')
+        self.assertRaises(InvalidTransitionError,
+            IWorkflowInfo(document).fireTransition, 'verify')
+        IWorkflowInfo(document).fireTransition('submit')
+        IWorkflowInfo(document).fireTransition('reset3')
+        self.assertEqual(IWorkflowState(document).getState(), 'created')
+        return
Index: main/waeup.ikoba/trunk/src/waeup/ikoba/documents/workflow.py
===================================================================
--- main/waeup.ikoba/trunk/src/waeup/ikoba/documents/workflow.py	(revision 11982)
+++ main/waeup.ikoba/trunk/src/waeup/ikoba/documents/workflow.py	(revision 11982)
@@ -0,0 +1,127 @@
+## $Id: batching.py 11891 2014-10-28 20:02:45Z henrik $
+##
+## Copyright (C) 2014 Uli Fouquet & Henrik Bettermann
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+"""Workflow for documents.
+"""
+import grok
+from datetime import datetime
+from zope.component import getUtility
+from hurry.workflow.workflow import Transition, WorkflowState, NullCondition
+from hurry.workflow.interfaces import IWorkflowState, IWorkflowTransitionEvent
+from waeup.ikoba.interfaces import (
+    IObjectHistory, IIkobaWorkflowInfo, IIkobaUtils)
+from waeup.ikoba.interfaces import MessageFactory as _
+from waeup.ikoba.workflow import IkobaWorkflow, IkobaWorkflowInfo
+from waeup.ikoba.utils.helpers import get_current_principal
+from waeup.ikoba.documents.interfaces import IDocument
+
+CREATED = 'created'
+SUBMITTED = 'submitted'
+VERIFIED = 'verified'
+REJECTED = 'rejected'
+
+IMPORTABLE_STATES = (CREATED, SUBMITTED, VERIFIED, REJECTED)
+
+REGISTRATION_TRANSITIONS = (
+    Transition(
+        transition_id = 'create',
+        title = _('Create document'),
+        source = None,
+        condition = NullCondition,
+        msg = _('Document created'),
+        destination = CREATED),
+
+    Transition(
+        transition_id = 'submit',
+        title = _('Submit for verification'),
+        msg = _('Submitted for verification'),
+        source = CREATED,
+        destination = SUBMITTED),
+
+    Transition(
+        transition_id = 'verify',
+        title = _('Verify'),
+        msg = _('Verified'),
+        source = SUBMITTED,
+        destination = VERIFIED),
+
+    Transition(
+        transition_id = 'reject',
+        title = _('Reject'),
+        msg = _('REJECTED'),
+        source = SUBMITTED,
+        destination = REJECTED),
+
+    Transition(
+        transition_id = 'reset1',
+        title = _('Reset'),
+        msg = _('Reset to initial state'),
+        source = REJECTED,
+        destination = CREATED),
+
+    Transition(
+        transition_id = 'reset2',
+        title = _('Reset'),
+        msg = _('Reset to initial state'),
+        source = VERIFIED,
+        destination = CREATED),
+
+    Transition(
+        transition_id = 'reset3',
+        title = _('Reset'),
+        msg = _('Reset to initial state'),
+        source = SUBMITTED,
+        destination = CREATED),
+    )
+
+
+IMPORTABLE_TRANSITIONS = [i.transition_id for i in REGISTRATION_TRANSITIONS]
+
+verification_workflow = IkobaWorkflow(REGISTRATION_TRANSITIONS)
+
+class VerificationWorkflowState(WorkflowState, grok.Adapter):
+    """An adapter to adapt Document objects to workflow states.
+    """
+    grok.context(IDocument)
+    grok.provides(IWorkflowState)
+
+    state_key = 'wf.verification.state'
+    state_id = 'wf.verification.id'
+
+class VerificationWorkflowInfo(IkobaWorkflowInfo, grok.Adapter):
+    """Adapter to adapt Document objects to workflow info objects.
+    """
+    grok.context(IDocument)
+    grok.provides(IIkobaWorkflowInfo)
+
+    def __init__(self, context):
+        self.context = context
+        self.wf = verification_workflow
+
+@grok.subscribe(IDocument, IWorkflowTransitionEvent)
+def handle_document_transition_event(obj, event):
+    """Append message to document history and log file when transition happened.
+    """
+    msg = event.transition.user_data['msg']
+    history = IObjectHistory(obj)
+    history.addMessage(msg)
+    try:
+        customers_container = grok.getSite()['customers']
+        customers_container.logger.info('%s - %s' % (obj.customer_id,msg))
+    except (TypeError, AttributeError):
+        pass
+    return
