Index: main/waeup.kofa/branches/henrik-regista/src/waeup/ikoba/app.py
===================================================================
--- main/waeup.kofa/branches/henrik-regista/src/waeup/ikoba/app.py	(revision 11955)
+++ main/waeup.kofa/branches/henrik-regista/src/waeup/ikoba/app.py	(revision 11956)
@@ -32,4 +32,6 @@
 from waeup.ikoba.configuration import ConfigurationContainer
 
+from waeup.ikoba.customers.container import CustomersContainer
+
 class Company(grok.Application, grok.Container, Logger):
     """A company.
@@ -59,4 +61,5 @@
         self['configuration'] = ConfigurationContainer()
         self['mandates'] = MandatesContainer()
+        self['customers'] = CustomersContainer()
         self._createPlugins()
 
Index: main/waeup.kofa/branches/henrik-regista/src/waeup/ikoba/customers/__init__.py
===================================================================
--- main/waeup.kofa/branches/henrik-regista/src/waeup/ikoba/customers/__init__.py	(revision 11956)
+++ main/waeup.kofa/branches/henrik-regista/src/waeup/ikoba/customers/__init__.py	(revision 11956)
@@ -0,0 +1,4 @@
+"""This package contains everything regarding customers.
+"""
+# Make this a package.
+
Index: main/waeup.kofa/branches/henrik-regista/src/waeup/ikoba/customers/container.py
===================================================================
--- main/waeup.kofa/branches/henrik-regista/src/waeup/ikoba/customers/container.py	(revision 11956)
+++ main/waeup.kofa/branches/henrik-regista/src/waeup/ikoba/customers/container.py	(revision 11956)
@@ -0,0 +1,82 @@
+## $Id: container.py 8737 2012-06-17 07:32:08Z 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
+##
+"""
+Containers for customers.
+"""
+import grok
+from thread import allocate_lock
+from transaction import commit
+from zope.component import getUtility
+from waeup.ikoba.customers.interfaces import (
+    ICustomersContainer, ICustomer, ICustomersUtils)
+from waeup.ikoba.utils.helpers import attrs_to_fields
+from waeup.ikoba.utils.logger import Logger
+
+lock = allocate_lock() # a lock object to lock threads.
+
+class CustomersContainer(grok.Container, Logger):
+    """
+    The node containing the customer models
+    """
+
+    grok.implements(ICustomersContainer)
+
+    _curr_cust_id = 10 ** 6
+
+    logger_name = 'waeup.ikoba.${sitename}.customers'
+    logger_filename = 'customers.log'
+
+    @property
+    def unique_customer_id(self):
+        """A unique customer id.
+
+        The customer id returned is guaranteed to be unique. It
+        consists of some prefix (normally a single letter) followed by
+        a number with at least 7 digits.
+
+        Once a customer id was issued, it won't be issued again.
+
+        Obtaining a customer id is currently not thread-safe but can be
+        made easily by enabling commented lines.
+        """
+        prefix = getUtility(ICustomersUtils).CUSTOMER_ID_PREFIX
+
+        # lock.acquire() # lock data
+        new_id = u'%s%s' % (prefix, self._curr_cust_id)
+        self._curr_cust_id += 1
+        # self._p_changed = True
+        # commit()
+        # lock.release() # end of lock
+        return new_id
+
+    def archive(self, id=None):
+        raise NotImplementedError()
+
+    def clear(self, id=None, archive=True):
+        raise NotImplementedError()
+
+    def addCustomer(self, customer):
+        """Add a customer with subcontainers.
+        """
+        if not ICustomer.providedBy(customer):
+            raise TypeError(
+                'CustomersContainers contain only ICustomer instances')
+        self[customer.customer_id] = customer
+        return
+
+CustomersContainer = attrs_to_fields(CustomersContainer)
Index: main/waeup.kofa/branches/henrik-regista/src/waeup/ikoba/customers/interfaces.py
===================================================================
--- main/waeup.kofa/branches/henrik-regista/src/waeup/ikoba/customers/interfaces.py	(revision 11956)
+++ main/waeup.kofa/branches/henrik-regista/src/waeup/ikoba/customers/interfaces.py	(revision 11956)
@@ -0,0 +1,77 @@
+## $Id: interfaces.py 11589 2014-04-22 07:13:16Z 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 datetime import datetime
+from zope.component import getUtility
+from zope.interface import Attribute, Interface
+from zope import schema
+from zc.sourcefactory.contextual import BasicContextualSourceFactory
+from waeup.ikoba.interfaces import MessageFactory as _
+from waeup.ikoba.schema import TextLineChoice, FormattedDate, PhoneNumber
+from waeup.ikoba.interfaces import IIkobaObject
+
+class ICustomersUtils(Interface):
+    """A collection of methods which are subject to customization.
+
+    """
+
+class ICustomersContainer(IIkobaObject):
+    """A customers container contains company customers.
+
+    """
+    def addCustomer(customer):
+        """Add an ICustomer object and subcontainers.
+
+        """
+
+    def archive(id=None):
+        """Create on-dist archive of customers.
+
+        If id is `None`, all customers are archived.
+
+        If id contains a single id string, only the respective
+        customers are archived.
+
+        If id contains a list of id strings all of the respective
+        customers types are saved to disk.
+        """
+
+    def clear(id=None, archive=True):
+        """Remove customers of type given by 'id'.
+
+        Optionally archive the customers.
+
+        If id is `None`, all customers are archived.
+
+        If id contains a single id string, only the respective
+        customers are archived.
+
+        If id contains a list of id strings all of the respective
+        customer types are saved to disk.
+
+        If `archive` is ``False`` none of the archive-handling is done
+        and respective customers are simply removed from the
+        database.
+        """
+
+    unique_customer_id = Attribute("""A unique customer id.""")
+
+class ICustomer(IIkobaObject):
+    """Representation of a customer.
+
+    """
+
Index: main/waeup.kofa/branches/henrik-regista/src/waeup/ikoba/customers/tests/__init__.py
===================================================================
--- main/waeup.kofa/branches/henrik-regista/src/waeup/ikoba/customers/tests/__init__.py	(revision 11956)
+++ main/waeup.kofa/branches/henrik-regista/src/waeup/ikoba/customers/tests/__init__.py	(revision 11956)
@@ -0,0 +1,1 @@
+# Make this a package.
Index: main/waeup.kofa/branches/henrik-regista/src/waeup/ikoba/customers/tests/test_container.py
===================================================================
--- main/waeup.kofa/branches/henrik-regista/src/waeup/ikoba/customers/tests/test_container.py	(revision 11956)
+++ main/waeup.kofa/branches/henrik-regista/src/waeup/ikoba/customers/tests/test_container.py	(revision 11956)
@@ -0,0 +1,106 @@
+## $Id: test_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
+##
+"""
+Tests for customers containers.
+"""
+import tempfile
+import shutil
+from zope.interface.verify import verifyClass, verifyObject
+from zope.component.hooks import setSite, clearSite
+from waeup.ikoba.app import Company
+from waeup.ikoba.customers.interfaces import (
+    ICustomersContainer,
+    )
+from waeup.ikoba.customers.container import (
+    CustomersContainer,
+    )
+from waeup.ikoba.testing import (
+    FunctionalLayer, FunctionalTestCase, remove_logger)
+
+class CustomersContainerTestCase(FunctionalTestCase):
+
+    layer = FunctionalLayer
+
+    def setUp(self):
+        remove_logger('waeup.ikoba.app.customers')
+        super(CustomersContainerTestCase, self).setUp()
+        # Setup a sample site for each test
+        # Prepopulate the ZODB...
+        app = Company()
+        self.getRootFolder()['app'] = app
+        self.app = self.getRootFolder()['app']
+        setSite(self.app)
+        self.dc_root = tempfile.mkdtemp()
+        app['datacenter'].setStoragePath(self.dc_root)
+        return
+
+    def tearDown(self):
+        super(CustomersContainerTestCase, self).tearDown()
+        clearSite()
+        shutil.rmtree(self.dc_root)
+        return
+
+    def test_interfaces(self):
+        # Make sure the correct interfaces are implemented.
+        self.assertTrue(
+            verifyClass(
+                ICustomersContainer, CustomersContainer)
+            )
+        self.assertTrue(
+            verifyObject(
+                ICustomersContainer, CustomersContainer())
+            )
+        return
+
+    def test_base(self):
+        # We cannot call the fundamental methods of a base in that case
+        container = CustomersContainer()
+        self.assertRaises(
+            NotImplementedError, container.archive)
+        self.assertRaises(
+            NotImplementedError, container.clear)
+        # We cannot add arbitrary objects
+        #department = Department()
+        #self.assertRaises(
+        #    TypeError, container.addCustomer, department)
+
+
+    def test_logger(self):
+        # We can get a logger from root
+        logger = self.app['customers'].logger
+        assert logger is not None
+        assert logger.name == 'waeup.ikoba.app.customers'
+        handlers = logger.handlers
+        assert len(handlers) == 1
+        filename = logger.handlers[0].baseFilename
+        assert filename.endswith('customers.log')
+        assert filename.startswith(self.dc_root)
+
+    def test_logger_multiple(self):
+        # Make sure the logger is still working after 2nd call
+        # First time we call it, it might be registered
+        logger = self.app['customers'].logger
+        # At second call the already registered logger should be returned
+        logger = self.app['customers'].logger
+        assert logger is not None
+        assert logger.name == 'waeup.ikoba.app.customers'
+        handlers = logger.handlers
+        assert len(handlers) == 1
+        filename = logger.handlers[0].baseFilename
+        assert filename.endswith('customers.log')
+        assert filename.startswith(self.dc_root)
Index: main/waeup.kofa/branches/henrik-regista/src/waeup/ikoba/customers/utils.py
===================================================================
--- main/waeup.kofa/branches/henrik-regista/src/waeup/ikoba/customers/utils.py	(revision 11956)
+++ main/waeup.kofa/branches/henrik-regista/src/waeup/ikoba/customers/utils.py	(revision 11956)
@@ -0,0 +1,35 @@
+## $Id: utils.py 11875 2014-10-24 12:47:13Z 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
+##
+"""General helper functions and utilities for the customer section.
+"""
+import grok
+from waeup.ikoba.customers.interfaces import ICustomersUtils
+
+def generate_customer_id():
+    customers = grok.getSite()['customers']
+    new_id = customers.unique_customer_id
+    return new_id
+
+class CustomersUtils(grok.GlobalUtility):
+    """A collection of methods subject to customization.
+    """
+    grok.implements(ICustomersUtils)
+
+    #: A prefix used when generating new customer ids. Each customer id will
+    #: start with this string. The default is 'K'.
+    CUSTOMER_ID_PREFIX = u'K'
