From dc26ced4a15734979e1426c625ebe69bd35fd7d6 Mon Sep 17 00:00:00 2001
From: Rafael Monnerat <rafael@nexedi.com>
Date: Wed, 22 Dec 2010 18:07:52 +0000
Subject: [PATCH] ERP5 Configurator Product initial commit.

ERP5 Configurator allows developer creates configuration wizards for configure an ERP5 Instance.


git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@41680 20353a03-c40f-0410-a6d1-a30d3c3de9de
---
 .../ERP5Configurator/Constraint/__init__.py   |   0
 .../Document/AccountConfiguratorItem.py       |  76 +++
 .../AccountingPeriodConfiguratorItem.py       |  68 +++
 .../Document/BusinessConfiguration.py         | 476 ++++++++++++++++++
 .../CatalogKeywordKeyConfiguratorItem.py      |  65 +++
 .../CategoriesSpreadsheetConfiguratorItem.py  | 155 ++++++
 .../Document/CategoryConfiguratorItem.py      |  64 +++
 .../Document/ConfigurationSave.py             |  61 +++
 .../Document/CurrencyConfiguratorItem.py      |  73 +++
 .../Document/CustomerBT5ConfiguratorItem.py   |  64 +++
 .../ExportCustomerBT5ConfiguratorItem.py      |  66 +++
 .../Document/OrganisationConfiguratorItem.py  |  78 +++
 .../Document/PermissionConfiguratorItem.py    |  83 +++
 .../Document/PersonConfiguratorItem.py        |  96 ++++
 .../Document/PortalTypeConfiguratorItem.py    |  79 +++
 ...talTypeRolesSpreadsheetConfiguratorItem.py | 103 ++++
 .../Document/PreferenceConfiguratorItem.py    | 118 +++++
 .../Document/RoleConfiguratorItem.py          |  98 ++++
 .../Document/RuleConfiguratorItem.py          |  71 +++
 .../Document/ServiceConfiguratorItem.py       |  66 +++
 .../Document/SitePropertyConfiguratorItem.py  |  64 +++
 .../Document/StandardBT5ConfiguratorItem.py   | 107 ++++
 .../SystemPreferenceConfiguratorItem.py       | 120 +++++
 .../WorkflowSecurityConfiguratorItem.py       | 123 +++++
 product/ERP5Configurator/Document/__init__.py |   0
 .../ConfigurationTemplate_readOOoCalcFile.py  | 126 +++++
 product/ERP5Configurator/Permissions.py       |   0
 .../PropertySheet/BusinessConfiguration.py    |  42 ++
 .../CategoriesSpreadsheetConfiguratorItem.py  |  47 ++
 .../PropertySheet/ConfigurationItem.py        |  37 ++
 ...talTypeRolesSpreadsheetConfiguratorItem.py |  46 ++
 .../PropertySheet/ServiceConfiguratorItem.py  |  37 ++
 .../SitePropertyConfiguratorItem.py           |  36 ++
 .../StandardBT5ConfiguratorItem.py            |  37 ++
 .../PropertySheet/__init__.py                 |   1 +
 .../ERP5Configurator/Tool/ConfiguratorTool.py | 476 ++++++++++++++++++
 product/ERP5Configurator/Tool/__init__.py     |   0
 product/ERP5Configurator/VERSION.txt          |   1 +
 product/ERP5Configurator/__init__.py          |  60 +++
 product/ERP5Configurator/help/README          |   1 +
 .../ERP5Configurator/interfaces/__init__.py   |   0
 .../interfaces/configurator_item.py           |  46 ++
 product/ERP5Configurator/mixin/__init__.py    |   0
 .../mixin/configurator_item.py                |  50 ++
 product/ERP5Configurator/skins/__init__.py    |   0
 product/ERP5Configurator/tests/__init__.py    |   1 +
 product/ERP5Configurator/tool.png             | Bin 0 -> 287 bytes
 47 files changed, 3418 insertions(+)
 create mode 100644 product/ERP5Configurator/Constraint/__init__.py
 create mode 100644 product/ERP5Configurator/Document/AccountConfiguratorItem.py
 create mode 100644 product/ERP5Configurator/Document/AccountingPeriodConfiguratorItem.py
 create mode 100644 product/ERP5Configurator/Document/BusinessConfiguration.py
 create mode 100644 product/ERP5Configurator/Document/CatalogKeywordKeyConfiguratorItem.py
 create mode 100644 product/ERP5Configurator/Document/CategoriesSpreadsheetConfiguratorItem.py
 create mode 100644 product/ERP5Configurator/Document/CategoryConfiguratorItem.py
 create mode 100644 product/ERP5Configurator/Document/ConfigurationSave.py
 create mode 100644 product/ERP5Configurator/Document/CurrencyConfiguratorItem.py
 create mode 100644 product/ERP5Configurator/Document/CustomerBT5ConfiguratorItem.py
 create mode 100644 product/ERP5Configurator/Document/ExportCustomerBT5ConfiguratorItem.py
 create mode 100644 product/ERP5Configurator/Document/OrganisationConfiguratorItem.py
 create mode 100644 product/ERP5Configurator/Document/PermissionConfiguratorItem.py
 create mode 100644 product/ERP5Configurator/Document/PersonConfiguratorItem.py
 create mode 100644 product/ERP5Configurator/Document/PortalTypeConfiguratorItem.py
 create mode 100644 product/ERP5Configurator/Document/PortalTypeRolesSpreadsheetConfiguratorItem.py
 create mode 100644 product/ERP5Configurator/Document/PreferenceConfiguratorItem.py
 create mode 100644 product/ERP5Configurator/Document/RoleConfiguratorItem.py
 create mode 100644 product/ERP5Configurator/Document/RuleConfiguratorItem.py
 create mode 100644 product/ERP5Configurator/Document/ServiceConfiguratorItem.py
 create mode 100644 product/ERP5Configurator/Document/SitePropertyConfiguratorItem.py
 create mode 100644 product/ERP5Configurator/Document/StandardBT5ConfiguratorItem.py
 create mode 100644 product/ERP5Configurator/Document/SystemPreferenceConfiguratorItem.py
 create mode 100644 product/ERP5Configurator/Document/WorkflowSecurityConfiguratorItem.py
 create mode 100644 product/ERP5Configurator/Document/__init__.py
 create mode 100644 product/ERP5Configurator/Extensions/ConfigurationTemplate_readOOoCalcFile.py
 create mode 100644 product/ERP5Configurator/Permissions.py
 create mode 100644 product/ERP5Configurator/PropertySheet/BusinessConfiguration.py
 create mode 100644 product/ERP5Configurator/PropertySheet/CategoriesSpreadsheetConfiguratorItem.py
 create mode 100644 product/ERP5Configurator/PropertySheet/ConfigurationItem.py
 create mode 100644 product/ERP5Configurator/PropertySheet/PortalTypeRolesSpreadsheetConfiguratorItem.py
 create mode 100644 product/ERP5Configurator/PropertySheet/ServiceConfiguratorItem.py
 create mode 100644 product/ERP5Configurator/PropertySheet/SitePropertyConfiguratorItem.py
 create mode 100644 product/ERP5Configurator/PropertySheet/StandardBT5ConfiguratorItem.py
 create mode 100644 product/ERP5Configurator/PropertySheet/__init__.py
 create mode 100644 product/ERP5Configurator/Tool/ConfiguratorTool.py
 create mode 100644 product/ERP5Configurator/Tool/__init__.py
 create mode 100644 product/ERP5Configurator/VERSION.txt
 create mode 100644 product/ERP5Configurator/__init__.py
 create mode 100644 product/ERP5Configurator/help/README
 create mode 100644 product/ERP5Configurator/interfaces/__init__.py
 create mode 100644 product/ERP5Configurator/interfaces/configurator_item.py
 create mode 100644 product/ERP5Configurator/mixin/__init__.py
 create mode 100644 product/ERP5Configurator/mixin/configurator_item.py
 create mode 100644 product/ERP5Configurator/skins/__init__.py
 create mode 100644 product/ERP5Configurator/tests/__init__.py
 create mode 100644 product/ERP5Configurator/tool.png

diff --git a/product/ERP5Configurator/Constraint/__init__.py b/product/ERP5Configurator/Constraint/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/product/ERP5Configurator/Document/AccountConfiguratorItem.py b/product/ERP5Configurator/Document/AccountConfiguratorItem.py
new file mode 100644
index 0000000000..430813dfc8
--- /dev/null
+++ b/product/ERP5Configurator/Document/AccountConfiguratorItem.py
@@ -0,0 +1,76 @@
+##############################################################################
+#
+# Copyright (c) 2006 Nexedi SARL and Contributors. All Rights Reserved.
+#                    Ivan Tyagov <ivan@nexedi.com>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# 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 AccessControl import ClassSecurityInfo
+from Products.ERP5Type import Permissions, PropertySheet
+from Products.ERP5Type.XMLObject import XMLObject
+from Products.ERP5Configurator.mixin.configurator_item import ConfiguratorItemMixin
+
+class AccountConfiguratorItem(ConfiguratorItemMixin, XMLObject):
+  """ Setup an Accounting Account. """
+
+  meta_type = 'ERP5 Account Configurator Item'
+  portal_type = 'Account Configurator Item'
+  add_permission = Permissions.AddPortalContent
+  isPortalContent = 1
+  isRADContent = 1
+
+  # Declarative security
+  security = ClassSecurityInfo()
+  security.declareObjectProtected(Permissions.AccessContentsInformation)
+
+  # Declarative properties
+  property_sheets = ( PropertySheet.Base
+                    , PropertySheet.XMLObject
+                    , PropertySheet.CategoryCore
+                    , PropertySheet.DublinCore
+                    , PropertySheet.Account )
+
+  def build(self, business_configuration):
+    portal = self.getPortalObject()
+    account_module = portal.account_module
+
+    extra_kw = {}
+    account_id = getattr(self, 'account_id', None)
+    if account_id:
+      # XXX FIXME This cause conflict when use configuration
+      # more then once.
+      #extra_kw['id'] = account_id 
+      pass
+    account = account_module.newContent(
+                portal_type='Account',
+                title=self.getTitle(),
+                account_type=self.getAccountType(),
+                gap=self.getGap(),
+                financial_section=self.getFinancialSection(),
+                credit_account=self.isCreditAccount(),
+                description=self.getDescription(),
+                **extra_kw)
+
+    ## add to customer template
+    self.install(account, business_configuration)
diff --git a/product/ERP5Configurator/Document/AccountingPeriodConfiguratorItem.py b/product/ERP5Configurator/Document/AccountingPeriodConfiguratorItem.py
new file mode 100644
index 0000000000..4c682d0dea
--- /dev/null
+++ b/product/ERP5Configurator/Document/AccountingPeriodConfiguratorItem.py
@@ -0,0 +1,68 @@
+##############################################################################
+#
+# Copyright (c) 2008 Nexedi SA and Contributors. All Rights Reserved.
+#                    Jerome Perrin <jerome@nexedi.com>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# 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 AccessControl import ClassSecurityInfo
+from Products.ERP5Type import Permissions, PropertySheet
+from Products.ERP5Type.XMLObject import XMLObject
+from Products.ERP5Configurator.mixin.configurator_item import ConfiguratorItemMixin
+
+class AccountingPeriodConfiguratorItem(ConfiguratorItemMixin, XMLObject):
+  """ Setup an Accounting Period. """
+  
+  meta_type = 'ERP5 Accounting Period Configurator Item'
+  portal_type = 'Accounting Period Configurator Item'
+  add_permission = Permissions.AddPortalContent
+  isPortalContent = 1
+  isRADContent = 1
+
+  # Declarative security
+  security = ClassSecurityInfo()
+  security.declareObjectProtected(Permissions.AccessContentsInformation)
+
+  # Declarative properties
+  property_sheets = ( PropertySheet.Base
+                    , PropertySheet.XMLObject
+                    , PropertySheet.CategoryCore
+                    , PropertySheet.DublinCore
+                    , PropertySheet.Task )
+
+  def build(self, business_configuration):
+    portal = self.getPortalObject()
+    organisation_id = business_configuration.\
+                                 getGlobalConfigurationAttr('organisation_id')
+    organisation = portal.organisation_module._getOb(organisation_id)
+
+    period = organisation.newContent(
+                portal_type='Accounting Period',
+                start_date=self.getStartDate(),
+                stop_date=self.getStopDate(),
+                short_title=self.getShortTitle(),
+                title=self.getTitle())
+
+    # no need to 'install' in the business template, because it's contain as
+    # subobject of an organisation we already added.
diff --git a/product/ERP5Configurator/Document/BusinessConfiguration.py b/product/ERP5Configurator/Document/BusinessConfiguration.py
new file mode 100644
index 0000000000..e14b4c204d
--- /dev/null
+++ b/product/ERP5Configurator/Document/BusinessConfiguration.py
@@ -0,0 +1,476 @@
+##############################################################################
+#
+# Copyright (c) 2006 Nexedi SARL and Contributors. All Rights Reserved.
+#                    Romain Courteaud <romain@nexedi.com>
+#                    Ivan Tyagov <ivan@nexedi.com>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# 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.
+#
+##############################################################################
+
+import time
+from AccessControl import ClassSecurityInfo
+from Globals import PersistentMapping
+from Acquisition import aq_base
+from Products.ERP5Type import Permissions, PropertySheet
+from zLOG import LOG, INFO
+from cStringIO import StringIO
+
+from Products.ERP5Configurator.Tool.ConfiguratorTool import _validateFormToRequest
+from Products.ERP5.Document.Url import Url
+from Products.ERP5.Document.Item import Item
+
+## Workflow states definitions
+INITIAL_STATE_TITLE = 'Start'
+DOWNLOAD_STATE_TITLE = 'Download'
+END_STATE_TITLE = 'End'
+
+class BusinessConfiguration(Item, Url):
+  """
+    BusinessConfiguration store the values enter by the wizard. 
+  """
+
+  meta_type = 'ERP5 Business Configuration'
+  portal_type = 'Business Configuration'
+  add_permission = Permissions.AddPortalContent
+  isPortalContent = 1
+  isRADContent = 1
+
+  # Declarative security
+  security = ClassSecurityInfo()
+  security.declareObjectProtected(Permissions.AccessContentsInformation)
+
+  # Declarative properties
+  property_sheets = ( PropertySheet.Base
+                    , PropertySheet.XMLObject
+                    , PropertySheet.CategoryCore
+                    , PropertySheet.DublinCore
+                    , PropertySheet.Item
+                    , PropertySheet.Arrow
+                    , PropertySheet.BusinessConfiguration
+                    , PropertySheet.Comment
+                    , PropertySheet.Url
+                    , PropertySheet.Version
+                    )
+
+  security.declareProtected(Permissions.View, 'isInitialConfigurationState')
+  def isInitialConfigurationState(self):
+    """ Check if the Business Configuration is on initial workflow state
+    """
+    workflow =  self.getResourceValue()
+    if workflow is not None:
+      return self.getCurrentState() == workflow.getSource()
+    return None
+
+  security.declareProtected(Permissions.View, 'isDownloadConfigurationState')
+  def isDownloadConfigurationState(self):
+    """ Check if the Business Configuration is on Download State
+    """
+    return self.getCurrentStateTitle() == DOWNLOAD_STATE_TITLE 
+
+  security.declareProtected(Permissions.View, 'isEndConfigurationState')
+  def isEndConfigurationState(self):
+    """ Check if the Business Configuration is on End State
+    """
+    return self.getCurrentStateTitle() == END_STATE_TITLE
+
+  security.declareProtected(Permissions.View, 'getNextTransition')
+  def getNextTransition(self):
+    """ Return next transition. """
+    current_state = self.getCurrentStateValue()
+    if current_state is None:
+      return None
+    transition_list = current_state.getAvailableTransitionList(self)
+    transition_number = len(transition_list)
+    if transition_number > 1:
+      raise TypeError, "More than one transition is available."
+    elif transition_number == 0:
+      return None
+    
+    return transition_list[0]
+
+  security.declarePrivate('_executeTransition')
+  def _executeTransition(self, \
+                        form_kw=None,
+                        request_kw=None):
+    """ Execute the transition. """
+    root_conf_save = None
+    if form_kw is None:
+      form_kw = {}
+    current_state = self.getCurrentStateValue()
+    transition = self.getNextTransition()
+    next_state = self.unrestrictedTraverse(transition.getDestination())
+    ## it's possible that we have already saved a configuration save object 
+    ## in workflow_history for this state so we use it
+    root_conf_save = self._getConfSaveForStateFromWorkflowHistory()
+    if root_conf_save is None:
+      ## we haven't saved any configuration save for this state so create new one
+      root_conf_save = self.newContent(portal_type='Configuration Save')
+    else:
+      ## we have already created configuration save for this state
+      ## so remove from it already existing configuration items
+      if root_conf_save!=self: ## don't delete ourselves
+        existing_conf_items = root_conf_save.objectIds()
+        existing_conf_items = map(None, existing_conf_items)
+        root_conf_save.manage_delObjects(existing_conf_items)
+    ## save ...
+    root_conf_save.edit(**form_kw)
+    ## Add some variables so we can get use them in workflow after scripts
+    form_kw['configuration_save_url'] = root_conf_save.getRelativeUrl()
+    form_kw['transition'] = transition.getRelativeUrl()
+    current_state.executeTransition(transition, self, form_kw=form_kw)
+
+  security.declarePrivate('_displayNextForm')
+  def _displayNextForm(self, \
+                       validation_errors=None, \
+                       context=None, \
+                       transition=None):
+    """ Render next form. """
+    if transition is None:
+      transition = self.getNextTransition()
+    while transition is not None:
+      form_id = transition.getTransitionFormId()
+      current_state = self.getCurrentStateValue()
+      if self.isDownloadConfigurationState():
+        ## exec next transition for this business configuration 
+        self._executeTransition()
+        transition = self.getNextTransition()
+        return None, None, None, None, None
+      if form_id is None:
+        ## go on until you find a form
+        self._executeTransition()
+        transition = self.getNextTransition()
+      else:
+        if context is None:
+          ## examine workflow_history for already saved 
+          ## 'Configuration Save' objects for this state
+          context = self._getConfSaveForStateFromWorkflowHistory()
+        ## get form object in a proper context
+        form_html, form_title = self._renderFormInContext(form_id, context, validation_errors)
+        ## check if we've can shown 'Previous' button
+        previous = None
+        translate = self.Base_translateString
+        if self._isAlreadyConfSaveInWorkflowHistory(transition):
+          previous = translate("Previous")
+        return previous, form_html, form_title, \
+               translate(transition.getTitle()), self.getServerBuffer()
+
+  security.declarePrivate('_renderFormInContext')
+  def _renderFormInContext(self, form_id, context, validation_errors):
+    html = ""
+    html_forms = []
+    isMultiEntryTransition = self._isMultiEntryTransition()
+    forms_number = isMultiEntryTransition
+    if context is None:
+      form = getattr(self, form_id)
+      if not isMultiEntryTransition:
+        if validation_errors is not None:
+          self.REQUEST.set('field_errors', form.ErrorFields(validation_errors))
+        html = form()
+      else:
+        template_html = form()
+        for form_counter in range(0, forms_number):
+          form_html = self.Base_mainConfiguratorFormTemplate(
+                                  current_form_number = form_counter + 1, 
+                                  max_form_numbers = forms_number,
+                                  form_title = form.title,                               
+                                  form_html = template_html)
+          html_forms.append(form_html)
+    else:
+      if not isMultiEntryTransition:
+        ## only one form saved under this context
+        form = getattr(context, form_id)
+        if validation_errors is not None:
+          self.REQUEST.set('field_errors', form.ErrorFields(validation_errors))
+        html = form()
+      else:
+        ## we have many forms saved under this context
+        form = getattr(self, form_id)
+        field_ids = form.get_fields()
+        for form_counter in range(0, forms_number):
+          ## fill REQUEST with data as it will be used to render form
+          for field in field_ids:
+            field_value = getattr(context, "field_%s" %field.id, None)
+            if field_value is not None and len(field_value) > form_counter:
+              field_value = field_value[form_counter]
+              self.REQUEST.set(field.id, field_value)
+            else:
+              self.REQUEST.set(field.id, '')
+          form_html = self.Base_mainConfiguratorFormTemplate( \
+                             current_form_number = form_counter +1, \
+                             max_form_numbers = forms_number, \
+                             form_html = getattr(context, form_id)())
+          html_forms.append(form_html)
+    if html_forms!=[]:
+      html = "\n".join(html_forms)
+    title = form.title  
+    return html, title
+
+  security.declarePrivate('_displayPreviousForm')
+  def _displayPreviousForm(self):
+    """ Render previous form using workflow history. """
+    workflow_history = self.getCurrentStateValue().getWorkflowHistory(self, remove_undo=1)
+    workflow_history.reverse()   
+    for wh in workflow_history:
+      ## go one step back
+      current_state = self.getCurrentStateValue()
+      current_state.undoTransition(self)
+      transition = self.unrestrictedTraverse(wh['transition'])
+      conf_save = self.unrestrictedTraverse(wh['configuration_save_url'])
+      ## check if this transition can be shown to user ...
+      if transition._checkPermission(self) and \
+           transition.getTransitionFormId() is not None:
+        return  self._displayNextForm(context=conf_save, transition=transition)
+  
+  security.declarePrivate('_validateNextForm')
+  def _validateNextForm(self, **kw):
+    """ Validate the form displayed to the user. """
+    REQUEST = self.REQUEST
+    form = getattr(self, self.getNextTransition().getTransitionFormId())
+    return _validateFormToRequest(form, REQUEST, **kw)
+
+  #############
+  ## misc    ##
+  #############
+  security.declarePrivate('_getConfigurationStack')
+  def _getConfigurationStack(self):
+    """ Return list of created by client configuration save objects 
+        sort on id which is an integer. """
+    result = self.objectValues('ERP5 Configuration Save')
+    result = map(None, result)
+    result.sort(lambda x, y: cmp(x.getIntIndex(x.getIntId()),
+                                 y.getIntIndex(y.getIntId())))
+    return result
+
+  security.declarePrivate('_getConfSaveForStateFromWorkflowHistory')
+  def _getConfSaveForStateFromWorkflowHistory(self):
+    """ Get from workflow history configuration save for this state """
+    configuration_save = None
+    current_state = self.getCurrentStateValue()
+    transition = self.getNextTransition()
+    next_state = self.unrestrictedTraverse(transition.getDestination())
+    workflow_history = current_state.getWorkflowHistory(self)
+    for wh in workflow_history:
+      wh_state = self.unrestrictedTraverse(wh['current_state'])
+      if wh_state == next_state:
+        configuration_save = self.unrestrictedTraverse(wh['configuration_save_url'])
+    return configuration_save
+
+  security.declarePrivate('_isAlreadyConfSaveInWorkflowHistory')
+  def _isAlreadyConfSaveInWorkflowHistory(self, transition):
+    """ check if we have an entry in worklow history for this state """
+    workflow_history = self.getCurrentStateValue().getWorkflowHistory(self, remove_undo=1)
+    workflow_history.reverse()
+    for wh in workflow_history:
+      wh_state = self.unrestrictedTraverse(wh['current_state'])
+      for wh_transition in wh_state.getAvailableTransitionList(self):
+        if wh_transition.getTransitionFormId() is not None and wh_transition!=transition:
+          return True
+    return False
+
+  security.declarePrivate('_isMultiEntryTransition')
+  def _isMultiEntryTransition(self):
+    """ Return number of multiple forms to show for a transition. """
+    next_transition = self.getNextTransition()
+    if next_transition is not None:
+      if getattr(aq_base(self), '_multi_entry_transitions', None) is not None:
+        multi_forms = self._multi_entry_transitions.get(next_transition.getRelativeUrl(), 0)
+        if multi_forms == 1:
+          # we have set '1' which means show one form which is not multiple forms
+          multi_forms = 0
+        return multi_forms
+      else:
+        return 0
+    else:
+      ## no transitions available
+      return 0
+
+  security.declareProtected(Permissions.ModifyPortalContent, 'setMultiEntryTransition')
+  def setMultiEntryTransition(self, transition_url, max_entry_number):
+    """ Set a transition as multiple - i.e max_entry_number of forms 
+        which will be rendered. This method is called in after scripts
+        and usually this number is set by user in a web form. """
+    if getattr(aq_base(self), '_multi_entry_transitions', None) is None:
+      self._multi_entry_transitions = PersistentMapping()
+    self._multi_entry_transitions[transition_url] = max_entry_number
+
+  security.declareProtected(Permissions.ModifyPortalContent, 'setServerBuffer')
+  def setServerBuffer(self, **kw):
+    """ Set what we should return to client. """
+    if getattr(aq_base(self), '_server_buffer', None) is None:
+      self._server_buffer = {}
+    for item, value in kw.items():
+      self._server_buffer[item] = value
+    self._p_changed = 1
+
+  security.declareProtected(Permissions.View, 'getServerBuffer')
+  def getServerBuffer(self):
+    """ Get return buffer which will be sent to client and 
+    afterwards deleted. """
+    server_buffer = getattr(aq_base(self), '_server_buffer', {})
+    self._server_buffer = {}
+    return server_buffer
+
+  security.declareProtected(Permissions.ModifyPortalContent, 'setGlobalConfigurationAttr')
+  def setGlobalConfigurationAttr(self, **kw):
+    """ Set global business configuration attribute. """
+    if getattr(aq_base(self),
+               '_global_configuration_attributes', None) is None:
+      self._global_configuration_attributes = PersistentMapping()
+    for key, value in kw.items():
+      self._global_configuration_attributes[key] = value
+
+  security.declareProtected(Permissions.View, 'getGlobalConfigurationAttr')
+  def getGlobalConfigurationAttr(self, key, default = None):
+    """ Get global business configuration attribute. """
+    global_configuration_attributes = getattr(self, '_global_configuration_attributes', {})
+    return global_configuration_attributes.get(key, default)
+
+  security.declareProtected(Permissions.View, 'getBuiltBusinessConfigurationBT5List')
+  def getBuiltBusinessConfigurationBT5List(self):
+    """
+      Get list of built business templates in a Wizard format.
+    """
+    bt5_file_list = []
+    portal = self.getPortalObject()
+    for bt_link in self.contentValues(portal_type="Link"):
+      bt5_item = dict(bt5_id = bt_link.getUrlString(), 
+                      bt5_filedata = "")
+      bt5_file_list.append(bt5_item)
+
+    for bt_file in self.contentValues(portal_type="File"):
+      bt5_item = dict(bt5_id = bt_file.getId(),
+                      bt5_filedata = bt_file.getData())
+      bt5_file_list.append(bt5_item)
+    return bt5_file_list
+
+  ############# Instance and Business Configuration ########################
+  security.declareProtected(Permissions.ModifyPortalContent, 'buildConfiguration')
+  def buildConfiguration(self):
+    """ 
+      Build list of business templates according to already saved 
+      Configuration Saves (i.e. user input).
+      This is the actual implementation which can be used from workflow 
+      actions and Configurator requets
+    """
+    bt5_file_list = []
+    start = time.time()
+    bc_id = self.getId()
+    LOG("CONFIGURATOR", INFO, 
+        'Build process started for %s' % self.getRelativeUrl())
+    conf_item_list = []
+    # build
+    for conf_save in self._getConfigurationStack():
+      # XXX: check which items are configure-able
+      conf_item_list = [x for x in conf_save.contentValues()]
+      conf_item_list.sort(lambda x,y: cmp(x.getIntId(), y.getIntId()))
+      for conf_item in conf_item_list:
+        conf_save_id = conf_save.getId()
+        configuration_item_object = conf_item
+        LOG('CONFIGURATOR', INFO, 'Building --> %s' % conf_item)
+        start_build = time.time()
+        build_result = conf_item.build(self)
+        LOG('CONFIGURATOR', INFO, 'Built    --> %s (%.02fs)' \
+                          % (conf_item, time.time()-start_build))
+       
+    # save list of generated or reused bt5 ids in bc
+    LOG('CONFIGURATOR', INFO, 
+        'Build process started for %s ended after %.02fs' 
+          %(self.getRelativeUrl(), time.time()-start))
+    return bt5_file_list
+
+  security.declareProtected(Permissions.ModifyPortalContent, 'resetBusinessConfiguration')
+  def resetBusinessConfiguration(self):
+    """ 
+      Reset Business Confiration at server side.
+      Remove all traces from user input (i.e. Configuration Saves, workflow history).
+    """
+    object_ids = []
+    for obj in self.contentValues(filter = {'portal_type': ['Configuration Save', 'File', 'Link']}):
+      object_ids.append(obj.getId())
+    self.manage_delObjects(object_ids)
+    del self.workflow_history
+    # ERP5 Workflow initialization
+    erp5_workflow = self.getResourceValue()
+    erp5_workflow.initializeDocument(self)
+
+  def isStandardBT5(self, bt5_id):
+    """Is bt5_id standard gzipped bt5 id?
+       Use ERP5 site portal_templates to get list of bt5_ids from configured
+       repository. This relies on the fact that the host site have a
+       configured repository.
+    """
+    # XXX This should be one API from portal_templates
+    bt5_title_list = []
+    bt5_title = bt5_id.split('.')[0]
+    for bt5 in self.getPortalObject().portal_templates\
+        .getRepositoryBusinessTemplateList():
+      bt5_title_list.append(bt5.getTitle())
+    return bt5_title in bt5_title_list
+
+  def getPublicUrlForBT5Id(self, bt5_id):
+    """ Generate publicly accessible URL for business template """
+    portal = self.getPortalObject()
+    return portal.portal_templates.getBusinessTemplateUrl(None, bt5_id)
+
+  security.declareProtected(Permissions.ModifyPortalContent, 'installConfiguration')
+  def installConfiguration(self, execute_after_setup_script = 1):
+    """ 
+      Install in remote instance already built list of business templates 
+      which are saved in the Business Configuration.
+    """
+    kw = dict(tag="start")
+    bt5_file_list = []
+    portal = self.getPortalObject()
+    for bt_link in self.contentValues(portal_type="Link"):
+      portal.portal_templates.activate(**kw).updateBusinessTemplateFromUrl(
+                                        bt_link.getUrlString())
+      LOG("Business COnfiguration", INFO,
+          "Install %s to %s" % (bt_link.getUrlString(), self.getRelativeUrl()))
+      kw["after_tag"] = kw["tag"]
+      kw["tag"] = bt_link.getTitle()
+
+    for bt_file in self.contentValues(portal_type="File"):
+      if bt_file.getTitle("").replace(".bt5", "") == self.getSpecialiseTitle():
+        bt5_io = StringIO(str(bt_file.getData()))
+
+        # XXX FIXME (lucas): Why FAIL on the log message? 
+        LOG("Business Configuration", INFO, 
+            "[FAIL] Import of bt5 file (%s - %s)" % \
+                                      (bt_file.getId(), bt_file.getTitle()))
+
+        bc = portal.portal_templates.importFile(import_file=bt5_io,
+                                         batch_mode=1)
+        bc.activate(**kw).install()
+        kw["after_tag"] = kw["tag"]
+        kw["tag"] = bt_file.getTitle()
+
+    if execute_after_setup_script:
+      customer_template = self.getSpecialiseValue()
+      customer_template_relative_url = customer_template.getRelativeUrl()
+      self.activate(**kw).ERP5Site_afterConfigurationSetup(
+                customer_template_relative_url=customer_template_relative_url,
+                alter_preferences=True)
+      LOG("Business Configuration", INFO,
+          "After setup script called (force) for %s : %s" %
+                    (self.getRelativeUrl(), self.getSpecialise()))
+
diff --git a/product/ERP5Configurator/Document/CatalogKeywordKeyConfiguratorItem.py b/product/ERP5Configurator/Document/CatalogKeywordKeyConfiguratorItem.py
new file mode 100644
index 0000000000..637f4f69c1
--- /dev/null
+++ b/product/ERP5Configurator/Document/CatalogKeywordKeyConfiguratorItem.py
@@ -0,0 +1,65 @@
+##############################################################################
+#
+# Copyright (c) 2008 Nexedi SARL and Contributors. All Rights Reserved.
+#                    Yoshinori Okuji <yo@nexedi.com>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# 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 AccessControl import ClassSecurityInfo
+from Products.ERP5Type import Permissions, PropertySheet
+from Products.ERP5Type.XMLObject import XMLObject
+
+from Products.ERP5Configurator.mixin.configurator_item import ConfiguratorItemMixin
+
+class CatalogKeywordKeyConfiguratorItem(ConfiguratorItemMixin, XMLObject):
+  """Set up catalog keyword keys."""
+
+  meta_type = 'ERP5 Catalog Keyword Key Configurator Item'
+  portal_type = 'Catalog Keyword Key Configurator Item'
+  add_permission = Permissions.AddPortalContent
+  isPortalContent = 1
+  isRADContent = 1
+
+  # Declarative security
+  security = ClassSecurityInfo()
+  security.declareObjectProtected(Permissions.AccessContentsInformation)
+
+  # Declarative properties
+  property_sheets = ( PropertySheet.Base
+                    , PropertySheet.XMLObject
+                    , PropertySheet.CategoryCore
+                    , PropertySheet.DublinCore )
+
+  def build(self, business_configuration):
+    portal = self.getPortalObject()
+    catalog = portal.portal_catalog.getSQLCatalog()
+    key_list = list(catalog.getProperty('sql_catalog_keyword_search_keys', ()))
+    for k in self.key_list:
+      if k not in key_list:
+        key_list.append(k)
+    key_list = tuple(key_list)
+    catalog._setPropValue('sql_catalog_keyword_search_keys', key_list)
+    bt = business_configuration.getSpecialiseValue()
+    bt.edit(template_catalog_keyword_key_list=key_list)
+
diff --git a/product/ERP5Configurator/Document/CategoriesSpreadsheetConfiguratorItem.py b/product/ERP5Configurator/Document/CategoriesSpreadsheetConfiguratorItem.py
new file mode 100644
index 0000000000..0defee9149
--- /dev/null
+++ b/product/ERP5Configurator/Document/CategoriesSpreadsheetConfiguratorItem.py
@@ -0,0 +1,155 @@
+##############################################################################
+#
+# Copyright (c) 2008 Nexedi SA and Contributors. All Rights Reserved.
+#                    Jerome Perrin <jerome@nexedi.com>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# 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 StringIO import StringIO
+
+from Acquisition import aq_base
+
+from AccessControl import ClassSecurityInfo
+from Products.ERP5Type import Permissions, PropertySheet
+from Products.ERP5Type.XMLObject import XMLObject
+from Products.ERP5Configurator.mixin.configurator_item import ConfiguratorItemMixin
+
+
+class UnrestrictedStringIO(StringIO):
+  __allow_access_to_unprotected_subobjects__ = 1
+
+
+class CategoriesSpreadsheetConfiguratorItem(ConfiguratorItemMixin, XMLObject):
+  """Import a categories spreadsheet.
+  """
+
+  meta_type = 'ERP5 Categories Spreadsheet Configurator Item'
+  portal_type = 'Categories Spreadsheet Configurator Item'
+  add_permission = Permissions.AddPortalContent
+  isPortalContent = 1
+  isRADContent = 1
+
+  # Declarative security
+  security = ClassSecurityInfo()
+  security.declareObjectProtected(Permissions.AccessContentsInformation)
+
+  # Declarative properties
+  property_sheets = ( PropertySheet.Base
+                    , PropertySheet.XMLObject
+                    , PropertySheet.CategoryCore
+                    , PropertySheet.DublinCore
+                    , PropertySheet.CategoriesSpreadsheetConfiguratorItem
+                    )
+
+  def build(self, business_configuration):
+    portal = self.getPortalObject()
+    ctool = portal.portal_categories
+
+    self._readSpreadSheet()
+    cache = self._category_cache
+    for bc_id, category_list in cache.items():
+      if bc_id in ctool.objectIds():
+        bc = ctool._getOb(bc_id)
+      else:
+        # TODO: test bc creation
+        # the bc should be added as base category in bt5 ?
+        bc = ctool.newContent(id=bc_id)
+
+      for category_info in category_list:
+        path = bc
+        for cat in category_info['path'].split("/")[1:]:
+          if not cat in path.objectIds():
+            path = path.newContent(
+              portal_type='Category',
+              id=cat,)
+          else:
+            path = path[cat]
+
+        edit_dict = category_info.copy()
+        edit_dict.pop('path')
+        path.edit(**edit_dict)
+        ## add to customer template
+        self.install(path, business_configuration)
+
+  def _readSpreadSheet(self):
+    """Read the spreadsheet and prepare internal category cache.
+    """
+    aq_self = aq_base(self)
+    if getattr(aq_self, '_category_cache', None) is None:
+      # TODO use a invalid_spreadsheet_error_handler to report invalid
+      # spreadsheet messages (see http://svn.erp5.org?rev=24908&view=rev )
+      aq_self._category_cache = self.Base_getCategoriesSpreadSheetMapping(
+                    UnrestrictedStringIO(self.getDefaultCategoriesSpreadsheetData()))
+
+  security.declareProtected(Permissions.ModifyPortalContent,
+                           'setDefaultCategoriesSpreadsheetFile')
+  def setDefaultCategoriesSpreadsheetFile(self, *args, **kw):
+    """Reset the spreadsheet cache."""
+    self._setDefaultCategoriesSpreadsheetFile(*args, **kw)
+    self._category_cache = None
+    self.reindexObject()
+
+  security.declareProtected(Permissions.ModifyPortalContent,
+                           'setCategoriesSpreadsheetFile')
+  setCategoriesSpreadsheetFile = setDefaultCategoriesSpreadsheetFile
+
+  security.declareProtected(Permissions.AccessContentsInformation,
+                           'getCategoryTitleItemList')
+  def getCategoryTitleItemList(self, base_category_id, base=0):
+    """Returns title item list for a base category contained in this
+    spreadsheet.
+    """
+    self._readSpreadSheet()
+    cache = self._category_cache
+
+    result = [('', '')]
+    if base_category_id not in cache:
+      return result # TODO: return some kind of default. Where is this
+                    # default ??? configurator_%s % base_category_id ?
+                    # If we add default here, it should also be used in build
+                    # ...
+
+    category_path_dict = dict()
+    for item in cache[base_category_id]:
+      category_path_dict[item['path']] = item
+
+    for item in cache[base_category_id]:
+      # the first item in this list is the base category itself, so we skip it.
+      if item['path'] == base_category_id:
+        continue
+
+      # recreate logical path
+      path_element_list = []
+      title_list = []
+      for path_element in item['path'].split('/'):
+        path_element_list.append(path_element)
+        title_list.append(category_path_dict['/'.join(path_element_list)]['title'])
+
+      if base:
+        result.append(('/'.join(title_list[1:]), item['path']))
+      else:
+        result.append(('/'.join(title_list[1:]),
+                       '/'.join(item['path'].split('/')[1:])))
+
+    return result
diff --git a/product/ERP5Configurator/Document/CategoryConfiguratorItem.py b/product/ERP5Configurator/Document/CategoryConfiguratorItem.py
new file mode 100644
index 0000000000..59390a1c2b
--- /dev/null
+++ b/product/ERP5Configurator/Document/CategoryConfiguratorItem.py
@@ -0,0 +1,64 @@
+##############################################################################
+#
+# Copyright (c) 2006 Nexedi SARL and Contributors. All Rights Reserved.
+#                    Ivan Tyagov <ivan@nexedi.com>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# 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 AccessControl import ClassSecurityInfo
+from Products.ERP5Type import Permissions, PropertySheet
+from Products.ERP5Type.XMLObject import XMLObject
+
+from Products.ERP5Configurator.mixin.configurator_item import ConfiguratorItemMixin
+
+class CategoryConfiguratorItem(ConfiguratorItemMixin, XMLObject):
+  """This class is meta build step for customization of ERP5 site."""
+
+  meta_type = 'ERP5 Category Configurator Item'
+  portal_type = 'Category Configurator Item'
+  add_permission = Permissions.AddPortalContent
+  isPortalContent = 1
+  isRADContent = 1
+
+  # Declarative security
+  security = ClassSecurityInfo()
+  security.declareObjectProtected(Permissions.AccessContentsInformation)
+
+  # Declarative properties
+  property_sheets = ( PropertySheet.Base
+                    , PropertySheet.XMLObject
+                    , PropertySheet.CategoryCore
+                    , PropertySheet.DublinCore)
+
+  def build(self, business_configuration):
+    portal = self.getPortalObject()
+    category_root = portal.portal_categories[self.category_root]
+    object_id = self.object_id
+    if object_id in category_root.objectIds():
+      category_root.manage_delObjects(object_id)
+    category = category_root.newContent(portal_type='Category',
+                                        id = object_id,
+                                        title = self.getTitle())
+    ## add to customer template
+    self.install(category, business_configuration)
diff --git a/product/ERP5Configurator/Document/ConfigurationSave.py b/product/ERP5Configurator/Document/ConfigurationSave.py
new file mode 100644
index 0000000000..fae53d8f26
--- /dev/null
+++ b/product/ERP5Configurator/Document/ConfigurationSave.py
@@ -0,0 +1,61 @@
+##############################################################################
+#
+# Copyright (c) 2006 Nexedi SARL and Contributors. All Rights Reserved.
+#                    Romain Courteaud <romain@nexedi.com>
+#                    Ivan Tyagov <ivan@nexedi.com>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# 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 AccessControl import ClassSecurityInfo
+from Products.ERP5Type import Permissions, PropertySheet
+from Products.ERP5.Document.Path import Path
+
+class ConfigurationSave(Path):
+  """ This class is the base class for all template items. """
+
+  portal_type = 'Configuration Save'
+  meta_type = 'ERP5 Configuration Save'
+  add_permission = Permissions.AddPortalContent
+  isPortalContent = 1
+  isRADContent = 1
+
+  # Declarative security
+  security = ClassSecurityInfo()
+  security.declareObjectProtected(Permissions.AccessContentsInformation)
+
+  # Declarative properties
+  property_sheets = ( PropertySheet.Base
+                    , PropertySheet.XMLObject
+                    , PropertySheet.CategoryCore
+                    , PropertySheet.DublinCore
+                    , PropertySheet.SortIndex )
+
+  def addConfigurationItem(self, configuration_item_class_name, **kw):
+    """ Add new configuration item. """
+    ## remove manually specified a configration title
+    if kw.has_key('conf_title'):
+      self.setTitle(kw['conf_title'])
+      kw.pop('conf_title')
+    conf_item = self.newContent(portal_type = configuration_item_class_name, **kw)
+    return conf_item
diff --git a/product/ERP5Configurator/Document/CurrencyConfiguratorItem.py b/product/ERP5Configurator/Document/CurrencyConfiguratorItem.py
new file mode 100644
index 0000000000..504ba62b77
--- /dev/null
+++ b/product/ERP5Configurator/Document/CurrencyConfiguratorItem.py
@@ -0,0 +1,73 @@
+##############################################################################
+#
+# Copyright (c) 2006 Nexedi SARL and Contributors. All Rights Reserved.
+#                    Ivan Tyagov <ivan@nexedi.com>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# 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 AccessControl import ClassSecurityInfo
+from Products.ERP5Type import Permissions, PropertySheet
+from Products.ERP5Type.XMLObject import XMLObject
+
+from Products.ERP5Configurator.mixin.configurator_item import ConfiguratorItemMixin
+
+class CurrencyConfiguratorItem(ConfiguratorItemMixin, XMLObject):
+  """ Setup currency. """
+
+  meta_type = 'ERP5 Currency Configurator Item'
+  portal_type = 'Currency Configurator Item'
+  add_permission = Permissions.AddPortalContent
+  isPortalContent = 1
+  isRADContent = 1
+
+  # Declarative security
+  security = ClassSecurityInfo()
+  security.declareObjectProtected(Permissions.AccessContentsInformation)
+
+  # Declarative properties
+  property_sheets = ( PropertySheet.Base
+                    , PropertySheet.XMLObject
+                    , PropertySheet.CategoryCore
+                    , PropertySheet.DublinCore
+                    , PropertySheet.Price
+                    , PropertySheet.Resource
+                    , PropertySheet.Reference )
+
+  def build(self, business_configuration):
+    portal = self.getPortalObject()
+    currency_module = portal._getOb('currency_module')
+    title = self.getTitle()
+    reference = self.getReference()
+    base_unit_quantity = self.getBaseUnitQuantity()
+    # XXX FIXME This is not exactly desired behaviour
+    currency = self.portal_catalog.getResultValue(id=reference,
+                                                  portal_type="Currency")
+    if currency is None:
+      currency = currency_module.newContent(portal_type = "Currency",
+                                          id = reference,
+                                          title = title,
+                                          reference = reference,
+                                          base_unit_quantity = base_unit_quantity)
+    ## add to customer template
+    self.install(currency, business_configuration)
diff --git a/product/ERP5Configurator/Document/CustomerBT5ConfiguratorItem.py b/product/ERP5Configurator/Document/CustomerBT5ConfiguratorItem.py
new file mode 100644
index 0000000000..d15cd561d0
--- /dev/null
+++ b/product/ERP5Configurator/Document/CustomerBT5ConfiguratorItem.py
@@ -0,0 +1,64 @@
+##############################################################################
+#
+# Copyright (c) 2006 Nexedi SARL and Contributors. All Rights Reserved.
+#                    Ivan Tyagov <ivan@nexedi.com>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# 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 AccessControl import ClassSecurityInfo
+from Products.ERP5Type import Permissions, PropertySheet
+from Products.ERP5Type.XMLObject import XMLObject
+from Products.CMFCore.utils import getToolByName
+from Products.ERP5Configurator.mixin.configurator_item import ConfiguratorItemMixin
+
+class CustomerBT5ConfiguratorItem(ConfiguratorItemMixin, XMLObject):
+  """ Create a new bt5 for customer configuration.
+
+      This business template is not installed locally, only build.
+  """
+
+  meta_type = 'ERP5 Customer BT5 Configurator Item'
+  portal_type = 'Customer BT5 Configurator Item'
+  add_permission = Permissions.AddPortalContent
+  isPortalContent = 1
+  isRADContent = 1
+
+  # Declarative security
+  security = ClassSecurityInfo()
+  security.declareObjectProtected(Permissions.AccessContentsInformation)
+
+  # Declarative properties
+  property_sheets = ( PropertySheet.Base
+                    , PropertySheet.XMLObject
+                    , PropertySheet.CategoryCore
+                    , PropertySheet.DublinCore )
+
+  def build(self, business_configuration):
+    template_tool = getToolByName(self.getPortalObject(),
+                                  'portal_templates')
+    bt5 = template_tool.newContent(portal_type="Business Template", \
+                                   title=self.bt5_id)
+
+    ## ..and set it as current
+    business_configuration.setSpecialise(bt5.getRelativeUrl())
diff --git a/product/ERP5Configurator/Document/ExportCustomerBT5ConfiguratorItem.py b/product/ERP5Configurator/Document/ExportCustomerBT5ConfiguratorItem.py
new file mode 100644
index 0000000000..89d3d9b2c3
--- /dev/null
+++ b/product/ERP5Configurator/Document/ExportCustomerBT5ConfiguratorItem.py
@@ -0,0 +1,66 @@
+##############################################################################
+#
+# Copyright (c) 2006 Nexedi SARL and Contributors. All Rights Reserved.
+#                    Ivan Tyagov <ivan@nexedi.com>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# 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 AccessControl import ClassSecurityInfo
+from Products.ERP5Type import Permissions, PropertySheet
+from Products.ERP5Type.XMLObject import XMLObject
+from Products.CMFCore.utils import getToolByName
+from Products.ERP5Configurator.mixin.configurator_item import ConfiguratorItemMixin
+
+class ExportCustomerBT5ConfiguratorItem(XMLObject, ConfiguratorItemMixin):
+  """ Create a new bt5 for customer configuration. """
+  
+  meta_type = 'ERP5 Export Customer BT5 Configurator Item'
+  portal_type = 'Export Customer BT5 Configurator Item'
+  add_permission = Permissions.AddPortalContent
+  isPortalContent = 1
+  isRADContent = 1
+
+  # Declarative security
+  security = ClassSecurityInfo()
+  security.declareObjectProtected(Permissions.AccessContentsInformation)
+
+  # Declarative properties
+  property_sheets = ( PropertySheet.Base
+                    , PropertySheet.XMLObject
+                    , PropertySheet.CategoryCore
+                    , PropertySheet.DublinCore )
+
+  def build(self, business_configuration):
+    portal = self.getPortalObject()
+    template_tool = getToolByName(portal, 'portal_templates')
+    bt5_obj = business_configuration.getSpecialiseValue()
+    if bt5_obj.getBuildingState() != 'built':
+      ## build template so it can be exported
+      bt5_obj.edit()
+      bt5_obj.build()
+    bt5_data = template_tool.export(bt5_obj)
+    business_configuration.newContent(
+                        portal_type='File',
+                        title = bt5_obj.getTitle(),
+                        data = bt5_data)
diff --git a/product/ERP5Configurator/Document/OrganisationConfiguratorItem.py b/product/ERP5Configurator/Document/OrganisationConfiguratorItem.py
new file mode 100644
index 0000000000..128229c258
--- /dev/null
+++ b/product/ERP5Configurator/Document/OrganisationConfiguratorItem.py
@@ -0,0 +1,78 @@
+##############################################################################
+#
+# Copyright (c) 2006 Nexedi SARL and Contributors. All Rights Reserved.
+#                    Ivan Tyagov <ivan@nexedi.com>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# 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 AccessControl import ClassSecurityInfo
+from Products.ERP5Type import Permissions, PropertySheet
+from Products.ERP5Type.XMLObject import XMLObject
+
+from Products.ERP5Configurator.mixin.configurator_item import ConfiguratorItemMixin
+
+class OrganisationConfiguratorItem(ConfiguratorItemMixin, XMLObject):
+  """ This class install a Organisation."""
+
+  meta_type = 'ERP5 Organisation Configurator Item'
+  portal_type = 'Organisation Configurator Item'
+  add_permission = Permissions.AddPortalContent
+  isPortalContent = 1
+  isRADContent = 1
+
+  # Declarative security
+  security = ClassSecurityInfo()
+  security.declareObjectProtected(Permissions.AccessContentsInformation)
+
+  # Declarative properties
+  property_sheets = ( PropertySheet.Base
+                    , PropertySheet.XMLObject
+                    , PropertySheet.CategoryCore
+                    , PropertySheet.DublinCore
+                    , PropertySheet.Organisation )
+
+  def build(self, business_configuration):
+    """ Setup organisation. """
+    portal = self.getPortalObject()
+    organisation = portal.organisation_module.newContent(portal_type="Organisation")
+
+    org_dict = {'price_currency': 'currency_module/%s' % self.getPriceCurrency(),
+                'group': self.getGroup(),
+                'title': self.getTitle(),
+                'corporate_name': self.getCorporateName(), 
+                'default_address_city': self.getDefaultAddressCity(),
+                'default_email_text': self.getDefaultEmailText(), 
+                'default_telephone_text': self.getDefaultTelephoneText(), 
+                'default_address_zip_code': self.getDefaultAddressZipCode(), 
+                'default_address_region': self.getDefaultAddressRegion(),
+                'default_address_street_address': self.getDefaultAddressStreetAddress(),
+                'site':'main', # First customer's organisation is always main site.
+                }
+    organisation.edit(**org_dict)
+    
+    # store globally organization_id 
+    business_configuration.setGlobalConfigurationAttr(organisation_id=organisation.getId())
+
+    ## add to customer template
+    self.install(organisation, business_configuration)
diff --git a/product/ERP5Configurator/Document/PermissionConfiguratorItem.py b/product/ERP5Configurator/Document/PermissionConfiguratorItem.py
new file mode 100644
index 0000000000..8c635eefe7
--- /dev/null
+++ b/product/ERP5Configurator/Document/PermissionConfiguratorItem.py
@@ -0,0 +1,83 @@
+##############################################################################
+#
+# Copyright (c) 2006 Nexedi SARL and Contributors. All Rights Reserved.
+#                    Ivan Tyagov <ivan@nexedi.com>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# 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 AccessControl import ClassSecurityInfo
+from Products.ERP5Type import Permissions, PropertySheet
+from Products.ERP5Type.XMLObject import XMLObject
+from Products.ERP5Configurator.mixin.configurator_item import ConfiguratorItemMixin
+
+class PermissionConfiguratorItem(ConfiguratorItemMixin, XMLObject):
+  """ Set permission matrix on module."""
+
+  meta_type = 'ERP5 Permission Configurator Item'
+  portal_type = 'Permission Configurator Item'
+  add_permission = Permissions.AddPortalContent
+  isPortalContent = 1
+  isRADContent = 1
+
+  # Declarative security
+  security = ClassSecurityInfo()
+  security.declareObjectProtected(Permissions.AccessContentsInformation)
+
+  # Declarative properties
+  property_sheets = ( PropertySheet.Base
+                    , PropertySheet.XMLObject
+                    , PropertySheet.CategoryCore
+                    , PropertySheet.DublinCore )
+
+  def build(self, business_configuration):
+    template_module_id_list = []
+    module_permissions_map = {}
+    sheets_dict = business_configuration.ConfigurationTemplate_readOOCalcFile(\
+                                                                              self.filename)
+    for module_id, permissions in sheets_dict.items():
+      module_permissions = {}
+      for permission in permissions:
+        roles = []
+        permission_name = permission.pop('permission')
+        for role, checked in permission.items():
+          if checked == '1':  roles.append(role)
+        module_permissions[permission_name] = roles
+      # add to module map
+      module_permissions_map[module_id] = module_permissions
+
+    # set permissions in fake site
+    portal = self.getPortalObject()
+    for module_id, permissions_map in module_permissions_map.items():
+      if permissions_map != {}:
+        template_module_id_list.append(module_id)
+        module = portal[module_id]
+        for permission_name, roles in permissions_map.items():
+          # we must alway include additionally 'Manager' and 'Owner'
+          roles.extend(['Manager', 'Owner'])
+          module.manage_permission(permission_name, tuple(roles), 0)
+
+    # add customized module to customer's bt5
+    if len(template_module_id_list):
+      bt5_obj = business_configuration.getSpecialiseValue()
+      bt5_obj.setTemplateModuleIdList(template_module_id_list)
\ No newline at end of file
diff --git a/product/ERP5Configurator/Document/PersonConfiguratorItem.py b/product/ERP5Configurator/Document/PersonConfiguratorItem.py
new file mode 100644
index 0000000000..5207872d4b
--- /dev/null
+++ b/product/ERP5Configurator/Document/PersonConfiguratorItem.py
@@ -0,0 +1,96 @@
+##############################################################################
+#
+# Copyright (c) 2006 Nexedi SARL and Contributors. All Rights Reserved.
+#                    Ivan Tyagov <ivan@nexedi.com>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# 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 Acquisition import aq_base
+from AccessControl import ClassSecurityInfo
+from Products.ERP5Type import Permissions, PropertySheet
+from Products.ERP5Type.XMLObject import XMLObject
+from DateTime import DateTime
+from Products.ERP5Configurator.mixin.configurator_item import ConfiguratorItemMixin
+
+class PersonConfiguratorItem(XMLObject, ConfiguratorItemMixin):
+  """ Setup user. """
+
+  meta_type = 'ERP5 Person Configurator Item'
+  portal_type = 'Person Configurator Item'
+  add_permission = Permissions.AddPortalContent
+  isPortalContent = 1
+  isRADContent = 1
+
+  # Declarative security
+  security = ClassSecurityInfo()
+  security.declareObjectProtected(Permissions.AccessContentsInformation)
+
+  # Declarative properties
+  property_sheets = ( PropertySheet.Base
+                    , PropertySheet.XMLObject
+                    , PropertySheet.CategoryCore
+                    , PropertySheet.DublinCore
+                    , PropertySheet.Reference
+                    , PropertySheet.Person 
+                    , PropertySheet.Login)
+
+  def build(self, business_configuration):
+    portal = self.getPortalObject()
+    person = portal.person_module.newContent(portal_type="Person")
+    group_id = getattr(aq_base(self), 'group_id', None)
+    site_id = getattr(aq_base(self), 'site_id', None)
+
+    if getattr(aq_base(self), 'organisation_id', None) is not None:
+      person.setCareerSubordination('organisation_module/%s' %self.organisation_id)
+
+    # save
+    person_dict = {'default_email_text': self.getDefaultEmailText(),
+                   'default_telephone_text': self.getDefaultTelephoneText(),
+                   'first_name': self.getFirstName(),
+                   'career_function': self.getFunction(),
+                   'last_name': self.getLastName(),
+                   'password': self.getPassword(),
+                    } 
+    person.edit(**person_dict)
+
+    # explicitly use direct mutator to avoid uniqueness checks in Person.setReference 
+    # which work in main ERP5 site context (uses catalog and cache)
+    # this is a problem when customer's entered reference is the same as 
+    # already exisitng one in main ERP5 site one
+    person._setReference(self.getReference())
+
+    assignment = person.newContent(portal_type="Assignment")
+    assignment.setFunction(self.getFunction())
+    assignment.setGroup(group_id)
+    assignment.setSite(site_id)
+
+    # Set dates are required to create valid assigments.
+    now = DateTime()
+    assignment.setStartDate(now)
+    # XXX Is it required to set stop date?
+    # Define valid for 10 years.
+    assignment.setStopDate(now + (365*10))
+    
+    ## add to customer template
+    self.install(person, business_configuration)
diff --git a/product/ERP5Configurator/Document/PortalTypeConfiguratorItem.py b/product/ERP5Configurator/Document/PortalTypeConfiguratorItem.py
new file mode 100644
index 0000000000..a091efef57
--- /dev/null
+++ b/product/ERP5Configurator/Document/PortalTypeConfiguratorItem.py
@@ -0,0 +1,79 @@
+##############################################################################
+#
+# Copyright (c) 2008 Nexedi SA and Contributors. All Rights Reserved.
+#                    TAHARA Yusei <yusei@nexedi.com>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# 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 AccessControl import ClassSecurityInfo
+from Products.ERP5Type import Permissions, PropertySheet
+from Products.ERP5Type.XMLObject import XMLObject
+from Products.ERP5Configurator.mixin.configurator_item import ConfiguratorItemMixin
+
+class PortalTypeConfiguratorItem(ConfiguratorItemMixin, XMLObject):
+  """Configure Portal Type."""
+
+  meta_type = 'ERP5 Portal Type Configurator Item'
+  portal_type = 'Portal Type Configurator Item'
+  add_permission = Permissions.AddPortalContent
+  isPortalContent = 1
+  isRADContent = 1
+
+  # Declarative security
+  security = ClassSecurityInfo()
+  security.declareObjectProtected(Permissions.AccessContentsInformation)
+
+  # Declarative properties
+  property_sheets = ( PropertySheet.Base
+                    , PropertySheet.XMLObject
+                    , PropertySheet.CategoryCore
+                    , PropertySheet.DublinCore )
+
+  def build(self, business_configuration):
+    portal = self.getPortalObject()
+
+    # Support adding new property sheet to portal type information.
+    # arguments:
+    #  * target_portal_type
+    #  * add_propertysheet_list
+    type_information = getattr(portal.portal_types, self.target_portal_type)
+    for name in self.add_propertysheet_list:
+      if not name in type_information.property_sheet_list:
+        new_property_sheet_list = list(type_information.property_sheet_list)
+        new_property_sheet_list.append(name)
+        type_information.property_sheet_list = tuple(new_property_sheet_list)
+    bt5_obj = business_configuration.getSpecialiseValue()
+
+    old_property_sheet_list = bt5_obj.getTemplatePortalTypePropertySheetList()
+    new_property_sheet_list = (list(old_property_sheet_list) +
+                               ['%s | %s' % (self.target_portal_type, name)
+                                for name in self.add_propertysheet_list]
+                               )
+
+    bt5_obj.edit(
+      template_portal_type_property_sheet_list=new_property_sheet_list)
+
+    #
+    # TODO:This class must support many other features we can use in ZMI.
+    #
diff --git a/product/ERP5Configurator/Document/PortalTypeRolesSpreadsheetConfiguratorItem.py b/product/ERP5Configurator/Document/PortalTypeRolesSpreadsheetConfiguratorItem.py
new file mode 100644
index 0000000000..32765bb59e
--- /dev/null
+++ b/product/ERP5Configurator/Document/PortalTypeRolesSpreadsheetConfiguratorItem.py
@@ -0,0 +1,103 @@
+##############################################################################
+#
+# Copyright (c) 2008 Nexedi SA and Contributors. All Rights Reserved.
+#                    Jerome Perrin <jerome@nexedi.com>
+#
+##############################################################################
+
+from Acquisition import aq_base
+
+from AccessControl import ClassSecurityInfo
+from Products.ERP5Type import Permissions, PropertySheet
+from Products.ERP5Type.XMLObject import XMLObject
+from Products.ERP5Configurator.mixin.configurator_item import ConfiguratorItemMixin
+
+
+class PortalTypeRolesSpreadsheetConfiguratorItem(ConfiguratorItemMixin, XMLObject):
+  """Import a portal type roles spreadsheet.
+  """
+
+  meta_type = 'ERP5 Portal Type Roles Spreadsheet Configurator Item'
+  portal_type = 'Portal Type Roles Spreadsheet Configurator Item'
+  add_permission = Permissions.AddPortalContent
+  isPortalContent = 1
+  isRADContent = 1
+
+  # Declarative security
+  security = ClassSecurityInfo()
+  security.declareObjectProtected(Permissions.AccessContentsInformation)
+
+  # Declarative properties
+  property_sheets = ( PropertySheet.Base
+                    , PropertySheet.XMLObject
+                    , PropertySheet.CategoryCore
+                    , PropertySheet.DublinCore
+                    , PropertySheet.PortalTypeRolesSpreadsheetConfiguratorItem
+                    )
+
+  def build(self, business_configuration):
+    portal = self.getPortalObject()
+    self._readSpreadSheet()
+    for type_name, role_list in self._spreadsheet_cache.items():
+      portal_type = portal.portal_types.getTypeInfo(type_name)
+      for role in role_list:
+        # rebuild a category from Group / Site & Function
+        category_list = []
+        for bc in ('Group', 'Site', 'Function'):
+          if role.get(bc):
+            category_list.append(role[bc])
+        #category = '\n'.join(category_list)
+        role_dict = {
+                     #'title': 'role',
+                     'description': role.get('Description', ''),
+                     'role_name_list': role.get('Role'),
+                     'role_category_list': category_list,
+                     'role_base_category_list': role.get('Base_Category', ''),
+                     'role_base_category_script_id': role.get('Base_Category_Script',
+                                           role.get('Script', ''))}
+        portal_type.newContent(portal_type='Role Information', \
+                               **role_dict)
+
+    ## Update BT5
+    bt5_obj = business_configuration.getSpecialiseValue()
+    bt5_obj.edit(template_portal_type_roles_list=self._spreadsheet_cache.keys())
+
+  def checkSpreadSheetConsistency(self):
+    """Check that the spread sheet is consistent with categories spreadsheet.
+
+     - all roles have a name ('Name' or 'Role')
+     - all roles have a portal type ('Name' or 'Role')
+     - all roles uses valid group & function categories
+
+    XXX do we want to use constraint framework here ?
+    """
+
+  def _readSpreadSheet(self):
+    """Read the spreadsheet and prepare internal category cache.
+    """
+    aq_self = aq_base(self)
+    if getattr(aq_self, '_spreadsheet_cache', None) is None:
+      role_dict = dict()
+      info_dict = self.ConfigurationTemplate_readOOCalcFile(
+                      'default_portal_type_roles_spreadsheet')
+      for sheet_name, table in self.ConfigurationTemplate_readOOCalcFile(
+                          'default_portal_type_roles_spreadsheet').items():
+        for line in table:
+          if 'Portal_Type' in line:
+            ptype_role_list = role_dict.setdefault(line['Portal_Type'], [])
+            ptype_role_list.append(line)
+
+      aq_self._spreadsheet_cache = role_dict
+
+  security.declareProtected(Permissions.ModifyPortalContent,
+                           'setDefaultPortalTypeRolesSpreadsheetFile')
+  def setDefaultPortalTypeRolesSpreadsheetFile(self, *args, **kw):
+    """Reset the spreadsheet cache."""
+    self._setDefaultPortalTypeRolesSpreadsheetFile(*args, **kw)
+    self._spreadsheet_cache = None
+    self.reindexObject()
+
+  security.declareProtected(Permissions.ModifyPortalContent,
+                           'setPortalTypeRolesSpreadsheetFile')
+  setPortalTypeRolesSpreadsheetFile = setDefaultPortalTypeRolesSpreadsheetFile
+
diff --git a/product/ERP5Configurator/Document/PreferenceConfiguratorItem.py b/product/ERP5Configurator/Document/PreferenceConfiguratorItem.py
new file mode 100644
index 0000000000..a3835e64e6
--- /dev/null
+++ b/product/ERP5Configurator/Document/PreferenceConfiguratorItem.py
@@ -0,0 +1,118 @@
+##############################################################################
+#
+# Copyright (c) 2006 Nexedi SARL and Contributors. All Rights Reserved.
+#                    Ivan Tyagov <ivan@nexedi.com>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# 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 AccessControl import ClassSecurityInfo
+from Products.ERP5Type import Permissions, PropertySheet
+from Products.ERP5Type.XMLObject import XMLObject
+from Products.ERP5Configurator.mixin.configurator_item import ConfiguratorItemMixin
+
+class PreferenceConfiguratorItem(ConfiguratorItemMixin, XMLObject):
+  """ Setup preference. """
+
+  meta_type = 'ERP5 Preference Configurator Item'
+  portal_type = 'Preference Configurator Item'
+  add_permission = Permissions.AddPortalContent
+  isPortalContent = 1
+  isRADContent = 1
+
+  # Declarative security
+  security = ClassSecurityInfo()
+  security.declareObjectProtected(Permissions.AccessContentsInformation)
+
+  # Declarative properties
+  property_sheets = ( PropertySheet.Base
+                    , PropertySheet.XMLObject
+                    , PropertySheet.CategoryCore
+                    , PropertySheet.DublinCore )
+
+  def _getPreferenceNameList(self):
+    """Returns all existing preference names.
+
+    TODO: this should be done by introspecting property sheet.
+    """
+    return ( 'preferred_category_child_item_list_method_id',
+             'preferred_accounting_transaction_from_date',
+             'preferred_accounting_transaction_at_date',
+             'preferred_section_category',
+             'preferred_section',
+             'preferred_accounting_transaction_section_category',
+             'preferred_accounting_transaction_source_section',
+             'preferred_accounting_transaction_currency',
+             'preferred_accounting_transaction_gap',
+             'preferred_accounting_transaction_simulation_state_list',
+             'preferred_text_format',
+             'preferred_text_editor',
+             'preferred_date_order',
+             'preferred_listbox_view_mode_line_count',
+             'preferred_listbox_list_mode_line_count',
+             'preferred_string_field_width',
+             'preferred_textarea_width',
+             'preferred_textarea_height',
+             'preferred_money_quantity_field_width',
+             'preferred_quantity_field_width',
+             'preferred_report_style',
+             'preferred_report_format',
+             'preferred_html_style_access_tab',
+             )
+
+
+  def build(self, business_configuration):
+    portal = self.getPortalObject()
+    organisation_id = business_configuration.\
+                                 getGlobalConfigurationAttr('organisation_id')
+    organisation_path = 'organisation_module/%s' % organisation_id
+
+    preference = portal.portal_preferences._getOb(self.object_id, None)
+    if preference is None:
+      preference = portal.portal_preferences.newContent(
+                              portal_type='Preference',
+                              id=self.object_id,
+                              title = self.title,
+                              description = self.description,
+                              priority = 1)
+
+    # XXX this have to be translated in user language.
+    preference_dict = {}
+
+    marker = []
+    for preference_name in self._getPreferenceNameList():
+      preference_value = getattr(self, preference_name,
+                     preference.getProperty(preference_name, marker))
+      if preference_value is not marker:
+        preference_dict[preference_name] = preference_value
+
+    preference_dict['preferred_accounting_transaction_source_section'] = \
+                                                             organisation_path
+    preference_dict['preferred_section'] = organisation_path
+    preference.edit(**preference_dict)
+    bt5_obj = business_configuration.getSpecialiseValue()
+    current_template_preference_list = list(bt5_obj.getTemplatePreferenceList())
+    if preference.getId() not in current_template_preference_list:
+      current_template_preference_list.append(preference.getId())
+      bt5_obj.edit(template_preference_list=current_template_preference_list,)
+
diff --git a/product/ERP5Configurator/Document/RoleConfiguratorItem.py b/product/ERP5Configurator/Document/RoleConfiguratorItem.py
new file mode 100644
index 0000000000..ada138083e
--- /dev/null
+++ b/product/ERP5Configurator/Document/RoleConfiguratorItem.py
@@ -0,0 +1,98 @@
+##############################################################################
+#
+# Copyright (c) 2006 Nexedi SARL and Contributors. All Rights Reserved.
+#                    Ivan Tyagov <ivan@nexedi.com>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# 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 Acquisition import aq_base
+from AccessControl import ClassSecurityInfo
+from Products.ERP5Type import Permissions, PropertySheet
+from Products.ERP5Type.XMLObject import XMLObject
+from Products.ERP5Configurator.mixin.configurator_item import ConfiguratorItemMixin
+from zLOG import LOG, INFO
+
+class RoleConfiguratorItem(ConfiguratorItemMixin, XMLObject):
+  """ Setup role per module basis. """
+
+  meta_type = 'ERP5 Role Configurator Item'
+  portal_type = 'Role Configurator Item'
+  add_permission = Permissions.AddPortalContent
+  isPortalContent = 1
+  isRADContent = 1
+
+  # Declarative security
+  security = ClassSecurityInfo()
+  security.declareObjectProtected(Permissions.AccessContentsInformation)
+
+  # Declarative properties
+  property_sheets = ( PropertySheet.Base
+                    , PropertySheet.XMLObject
+                    , PropertySheet.CategoryCore
+                    , PropertySheet.DublinCore )
+
+  def build(self, business_configuration):
+    object_list = business_configuration.ConfigurationTemplate_readOOCalcFile(self.filename)
+    portal = self.getPortalObject()
+
+    portal_type_dict = {}
+    # we may pass some override dynamic values from outside
+    # Example:we post 'group_id' and in column we have it then 
+    # it will be replaced with value if not configuration file matters
+    dynamic_values = dict(group_id = getattr(aq_base(self), 'group_id', None),
+                          function_id = getattr(aq_base(self), 'function_id', None),
+                          site_id = getattr(aq_base(self), 'site_id', None),)
+    for oo_module_dict in object_list:
+      mod_conf_list = []
+      portal_type = oo_module_dict.pop('portal_type')
+      for category, role_list_string in oo_module_dict.items():
+        # passed from outside (it has higher priority than configuratiohn file)
+        category = dynamic_values.get(category, category)
+        title = category.replace('/', '_')
+        role_name_list = [x.strip() for x in role_list_string.split(';')]
+        role_category_list=[category]
+        conf_dict =  {'title': title,
+                      'description': 'Configured by Nexedi Configurator',
+                      'role_name_list': role_name_list,
+                      'role_category_list': role_category_list}
+        mod_conf_list.append(conf_dict)
+      portal_type_dict[portal_type] = mod_conf_list
+    ## Update fake site
+    # XXX rafael: improve this, the ignore list is quite ugly.
+    ignore_list = []
+    portal_type_id_list = portal.portal_types.objectIds()
+    for portal_type, role_list in portal_type_dict.items():
+      for role_dict in role_list:
+       if portal_type in portal_type_id_list:
+         portal.portal_types[portal_type].newContent(portal_type='Role Information', \
+                                                       **role_dict)
+       else:
+         ignore_list.append(portal_type)
+         LOG("CONFIGURATOR", INFO, "Fail to define Roles for %s" % portal_type)
+    ## Update BT5
+    bt5_obj = business_configuration.getSpecialiseValue()
+    # keep existing roles definition (from previous configuration saves)
+    for existing_type in bt5_obj.getTemplatePortalTypeRolesList():
+      portal_type_dict[existing_type] = 1
+    bt5_obj.edit(template_portal_type_roles_list=[i for i in portal_type_dict.keys() if i not in ignore_list])
diff --git a/product/ERP5Configurator/Document/RuleConfiguratorItem.py b/product/ERP5Configurator/Document/RuleConfiguratorItem.py
new file mode 100644
index 0000000000..d0c3193f3d
--- /dev/null
+++ b/product/ERP5Configurator/Document/RuleConfiguratorItem.py
@@ -0,0 +1,71 @@
+##############################################################################
+#
+# Copyright (c) 2010 Nexedi SA and Contributors. All Rights Reserved.
+#                    Lucas Carvalho <lucas@nexedi.com>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# 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 Acquisition import aq_base
+from AccessControl import ClassSecurityInfo
+from Products.ERP5Type import Permissions, PropertySheet
+from Products.ERP5Type.XMLObject import XMLObject
+from Products.ERP5Configurator.mixin.configurator_item import ConfiguratorItemMixin
+from zLOG import LOG, INFO
+
+class RuleConfiguratorItem(ConfiguratorItemMixin, XMLObject):
+  """ Setup Rules. """
+
+  meta_type = 'ERP5 Rule Configurator Item'
+  portal_type = 'Rule Configurator Item'
+  add_permission = Permissions.AddPortalContent
+  isPortalContent = 1
+  isRADContent = 1
+
+  # Declarative security
+  security = ClassSecurityInfo()
+  security.declareObjectProtected(Permissions.AccessContentsInformation)
+
+  # Declarative properties
+  property_sheets = ( PropertySheet.Base
+                    , PropertySheet.XMLObject
+                    , PropertySheet.CategoryCore
+                    , PropertySheet.DublinCore )
+
+  def build(self, business_configuration):
+    portal = self.getPortalObject()
+    simulation_rule_dict = portal.ERPSite_getConfiguratorSimulationRuleDict()
+    for key, value in simulation_rule_dict.iteritems():
+      reference = value.get('default_reference')
+      result = portal.portal_rules.searchFolder(sort_on='version',
+                                                sort_order='descending',
+                                                reference=reference)
+      if len(result):
+        value['version'] = int(result[0].getVersion()) + 1
+      rule = portal.portal_rules.newContent(**value)
+
+      content_list = value.pop('content_list')
+      for content_dict in content_list:
+        sub_object = rule.newContent(**content_dict)
+
+      self.install(rule, business_configuration)
diff --git a/product/ERP5Configurator/Document/ServiceConfiguratorItem.py b/product/ERP5Configurator/Document/ServiceConfiguratorItem.py
new file mode 100644
index 0000000000..6bc6feaac1
--- /dev/null
+++ b/product/ERP5Configurator/Document/ServiceConfiguratorItem.py
@@ -0,0 +1,66 @@
+##############################################################################
+#
+# Copyright (c) 2008 Nexedi SA and Contributors. All Rights Reserved.
+#                    TAHARA Yusei <yusei@nexedi.com>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# 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 AccessControl import ClassSecurityInfo
+from Products.ERP5Type import Permissions, PropertySheet
+from Products.ERP5Type.XMLObject import XMLObject
+from Products.ERP5Configurator.mixin.configurator_item import ConfiguratorItemMixin
+
+class ServiceConfiguratorItem(ConfiguratorItemMixin, XMLObject):
+  """Create default service documents."""
+
+  meta_type = 'ERP5 Service Configurator Item'
+  portal_type = 'Service Configurator Item'
+  add_permission = Permissions.AddPortalContent
+  isPortalContent = 1
+  isRADContent = 1
+
+  # Declarative security
+  security = ClassSecurityInfo()
+  security.declareObjectProtected(Permissions.AccessContentsInformation)
+
+  # Declarative properties
+  property_sheets = ( PropertySheet.Base
+                    , PropertySheet.XMLObject
+                    , PropertySheet.CategoryCore
+                    , PropertySheet.DublinCore
+                    , PropertySheet.ServiceConfiguratorItem )
+
+  def build(self, business_configuration):
+    portal = self.getPortalObject()
+    module = portal.service_module
+    for service_id, service_title in self.getServiceList():
+      # XXX FIXME We cannot define service_id like this, 
+      # because it cause conflict when configurator is
+      # used twice.
+      document = module.newContent(portal_type='Service',
+                                   #id=service_id,
+                                   title=service_title,
+                                   )
+      ## add to customer template
+      self.install(document, business_configuration)
diff --git a/product/ERP5Configurator/Document/SitePropertyConfiguratorItem.py b/product/ERP5Configurator/Document/SitePropertyConfiguratorItem.py
new file mode 100644
index 0000000000..139b7f6600
--- /dev/null
+++ b/product/ERP5Configurator/Document/SitePropertyConfiguratorItem.py
@@ -0,0 +1,64 @@
+##############################################################################
+#
+# Copyright (c) 2008 Nexedi SARL and Contributors. All Rights Reserved.
+#                    Yoshinori Okuji <yo@nexedi.com>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# 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 AccessControl import ClassSecurityInfo
+from Products.ERP5Type import Permissions, PropertySheet
+from Products.ERP5Type.XMLObject import XMLObject
+from Products.ERP5Configurator.mixin.configurator_item import ConfiguratorItemMixin
+
+class SitePropertyConfiguratorItem(ConfiguratorItemMixin, XMLObject):
+  """Set up site properties."""
+
+  meta_type = 'ERP5 Site Property Configurator Item'
+  portal_type = 'Site Property Configurator Item'
+  add_permission = Permissions.AddPortalContent
+  isPortalContent = 1
+  isRADContent = 1
+
+  # Declarative security
+  security = ClassSecurityInfo()
+  security.declareObjectProtected(Permissions.AccessContentsInformation)
+
+  # Declarative properties
+  property_sheets = ( PropertySheet.Base
+                    , PropertySheet.XMLObject
+                    , PropertySheet.CategoryCore
+                    , PropertySheet.DublinCore
+                    , PropertySheet.SitePropertyConfiguratorItem )
+
+  def build(self, business_configuration):
+    portal = self.getPortalObject()
+    id_list = []
+    for id, value, prop_type in self.getSitePropertyList():
+      if portal.hasProperty(id):
+        portal._delProperty(id)
+      portal._setProperty(id, value, type=prop_type)
+      id_list.append(id)
+    bt = business_configuration.getSpecialiseValue()
+    bt.edit(template_site_property_id_list=id_list)
+
diff --git a/product/ERP5Configurator/Document/StandardBT5ConfiguratorItem.py b/product/ERP5Configurator/Document/StandardBT5ConfiguratorItem.py
new file mode 100644
index 0000000000..aec4d411a7
--- /dev/null
+++ b/product/ERP5Configurator/Document/StandardBT5ConfiguratorItem.py
@@ -0,0 +1,107 @@
+##############################################################################
+#
+# Copyright (c) 2006 Nexedi SARL and Contributors. All Rights Reserved.
+#                    Ivan Tyagov <ivan@nexedi.com>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# 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 Products.CMFCore.utils import getToolByName
+from AccessControl import ClassSecurityInfo
+from Products.ERP5Type import Permissions, PropertySheet
+from Products.ERP5Type.XMLObject import XMLObject
+from Products.ERP5Configurator.mixin.configurator_item import ConfiguratorItemMixin
+import transaction
+
+class StandardBT5ConfiguratorItem(ConfiguratorItemMixin, XMLObject):
+  """ This class will install standard ERP5 template from a repository to
+  fake site. """
+
+  meta_type = 'ERP5 Standard BT5 Configurator Item'
+  portal_type = 'Standard BT5 Configurator Item'
+  add_permission = Permissions.AddPortalContent
+  isPortalContent = 1
+  isRADContent = 1
+
+  # Declarative security
+  security = ClassSecurityInfo()
+  security.declareObjectProtected(Permissions.AccessContentsInformation)
+
+  # Declarative properties
+  property_sheets = ( PropertySheet.Base
+                    , PropertySheet.XMLObject
+                    , PropertySheet.CategoryCore
+                    , PropertySheet.DublinCore
+                    , PropertySheet.StandardBT5ConfiguratorItem
+                    )
+
+  def build(self, business_configuration):
+    bt5_id = self.getBt5Id()
+    bt5_copy_id = '%s_copy' % bt5_id
+    portal = self.getPortalObject()
+    template_tool = getToolByName(portal, 'portal_templates')
+
+    ## Is this standard template already gzipped?
+    filename_bt5_id = '%s.bt5' % bt5_id
+    if business_configuration.isStandardBT5(filename_bt5_id):
+      bt_url = business_configuration.getPublicUrlForBT5Id(filename_bt5_id)
+
+      business_configuration.newContent(portal_type='Link',
+                    url_string = bt_url, title = filename_bt5_id) 
+    else:
+      ## we need to make a copy of template to be able to export it
+      if not bt5_copy_id in template_tool.objectIds():
+        bt5 = template_tool.getInstalledBusinessTemplate(bt5_id)
+        template_copy = template_tool.manage_copyObjects(ids=(bt5.getId(),))
+        new_id_list = template_tool.manage_pasteObjects(template_copy)
+        new_bt5_id = new_id_list[0]['new_id']
+        template_tool.manage_renameObject(new_bt5_id, bt5_copy_id)
+      ## we are sure that we have this business template
+      self._current_bt_id = bt5_copy_id
+      return self.get_it_built(business_configuration)
+
+  def get_it_built(self, business_configuration):
+    portal = self.getPortalObject()
+    template_tool = getToolByName(portal, 'portal_templates')
+    bt5_obj = self._getCurrentBT(business_configuration)
+    if bt5_obj.getBuildingState() != 'built':
+      ## build template so it can be exported
+      bt5_obj.edit()
+      bt5_obj.build()
+      # XXX Due a bug into Business Templates it is not possible build
+      # the business template and export when this have one 
+      # ActionTemplateItem. This is a TEMPORARY CHANGE and it should be
+      # removed as soon as Business Template is FIXED.
+      transaction.savepoint(optimistic=True)
+    bt5_data = template_tool.export(bt5_obj)
+    business_configuration.newContent(portal_type='File',
+                                      title = '%s.bt5' % bt5_obj.getId(),
+                                      data = bt5_data)
+
+  def _getCurrentBT(self, business_configuration):
+    """ Return current bt5 file. """
+    portal = self.getPortalObject()
+    template_tool = portal.portal_templates
+    bt5_id = self._current_bt_id
+    bt5_obj = portal.portal_templates[bt5_id]
+    return bt5_obj
diff --git a/product/ERP5Configurator/Document/SystemPreferenceConfiguratorItem.py b/product/ERP5Configurator/Document/SystemPreferenceConfiguratorItem.py
new file mode 100644
index 0000000000..6ec1e4cd3b
--- /dev/null
+++ b/product/ERP5Configurator/Document/SystemPreferenceConfiguratorItem.py
@@ -0,0 +1,120 @@
+##############################################################################
+#
+# Copyright (c) 2006 Nexedi SARL and Contributors. All Rights Reserved.
+#                    Ivan Tyagov <ivan@nexedi.com>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# 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 AccessControl import ClassSecurityInfo
+from Products.ERP5Type import Permissions, PropertySheet
+from Products.ERP5Type.XMLObject import XMLObject
+from Products.ERP5Configurator.mixin.configurator_item import ConfiguratorItemMixin
+
+class SystemPreferenceConfiguratorItem(ConfiguratorItemMixin, XMLObject):
+  """ 
+    Setup system preference. 
+  """
+
+  meta_type = 'ERP5 System Preference Configurator Item'
+  portal_type = 'System Preference Configurator Item'
+  add_permission = Permissions.AddPortalContent
+  isPortalContent = 1
+  isRADContent = 1
+
+  # Declarative security
+  security = ClassSecurityInfo()
+  security.declareObjectProtected(Permissions.AccessContentsInformation)
+
+  # Declarative properties
+  property_sheets = ( PropertySheet.Base
+                    , PropertySheet.XMLObject
+                    , PropertySheet.CategoryCore
+                    , PropertySheet.DublinCore )
+
+  def _getPreferenceNameList(self):
+    """Returns all existing preference names.
+
+    TODO: this should be done by introspecting property sheet.
+    """
+    return ( # CRM
+             'preferred_campaign_resource_list',
+             'preferred_event_assessment_form_id_list',
+             'preferred_event_resource_list',
+             'preferred_event_sender_email',
+             'preferred_meeting_resource_list',
+             'preferred_sale_opportunity_resource_list',
+             'preferred_support_request_resource_list',
+             # DMS
+             'preferred_ooodoc_server_address',
+             'preferred_ooodoc_server_port_number',
+             'preferred_conversion_cache_factory',
+             'preferred_document_email_ingestion_address',
+             'preferred_document_reference_method_id',
+             'preferred_document_file_name_regular_expression',
+             'preferred_document_reference_regular_expression',
+             'preferred_document_classification',
+             'preferred_synchronous_metadata_discovery',
+             'preferred_redirect_to_document',
+             # PDM
+             'preferred_product_individual_variation_base_category_list',
+             'preferred_component_individual_variation_base_category_list',
+             'preferred_service_individual_variation_base_category_list',
+             # Trade
+             'preferred_supplier_role_list',
+             'preferred_client_role_list',
+             'preferred_sale_use_list',
+             'preferred_purchase_use_list',
+             'preferred_packing_use_list',
+             # Express
+             )
+
+
+  def build(self, business_configuration):
+    portal = self.getPortalObject()
+    preference = portal.portal_preferences._getOb(self.object_id, None)
+    if preference is None:
+      preference = portal.portal_preferences.newContent(
+                               portal_type = 'System Preference',
+                               id = self.object_id,
+                               title = self.title,
+                               description = self.description,
+                               priority = 1)
+
+    # XXX this have to be translated in user language.
+    preference_dict = {}
+
+    marker = []
+    for preference_name in self._getPreferenceNameList():
+      preference_value = getattr(self, preference_name,
+                     preference.getProperty(preference_name, marker))
+      if preference_value is not marker:
+        preference_dict[preference_name] = preference_value
+
+    preference.edit(**preference_dict)
+    bt5_obj = business_configuration.getSpecialiseValue()
+    current_template_preference_list = list(bt5_obj.getTemplatePreferenceList())
+    if preference.getId() not in current_template_preference_list:
+      current_template_preference_list.append(preference.getId())
+      bt5_obj.edit(template_preference_list=current_template_preference_list,)
+
diff --git a/product/ERP5Configurator/Document/WorkflowSecurityConfiguratorItem.py b/product/ERP5Configurator/Document/WorkflowSecurityConfiguratorItem.py
new file mode 100644
index 0000000000..00e001a8ba
--- /dev/null
+++ b/product/ERP5Configurator/Document/WorkflowSecurityConfiguratorItem.py
@@ -0,0 +1,123 @@
+##############################################################################
+#
+# Copyright (c) 2006 Nexedi SARL and Contributors. All Rights Reserved.
+#                    Ivan Tyagov <ivan@nexedi.com>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# 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 AccessControl import ClassSecurityInfo
+from Products.ERP5Type import Permissions, PropertySheet
+from Products.ERP5Type.XMLObject import XMLObject
+from Products.ERP5Configurator.mixin.configurator_item import ConfiguratorItemMixin
+
+class WorkflowSecurityConfiguratorItem(ConfiguratorItemMixin, XMLObject):
+  """ Setup workflow for different roles. Use passed OO file. """
+
+  meta_type = 'ERP5 Workflow Security Configurator Item'
+  portal_type = 'Workflow Security Configurator Item'
+  add_permission = Permissions.AddPortalContent
+  isPortalContent = 1
+  isRADContent = 1
+
+  # Declarative security
+  security = ClassSecurityInfo()
+  security.declareObjectProtected(Permissions.AccessContentsInformation)
+
+  # Declarative properties
+  property_sheets = ( PropertySheet.Base
+                    , PropertySheet.XMLObject
+                    , PropertySheet.CategoryCore
+                    , PropertySheet.DublinCore )
+
+  def build(self, business_configuration):
+    ## NOT TESTED
+    return
+    table_dict = business_configuration.ConfigurationTemplate_readOOCalcFile(self.filename)
+    portal = self.getPortalObject()
+    suffix = '_security'
+    suffix_len = len(suffix)
+    if self.filename[-suffix_len:] == suffix:
+      workflow_id = self.filename[:-suffix_len]
+    else:
+      raise "NoValidName"
+
+    # Configure state permission
+    view_permission_list = ['View']
+    access_permission_list = ['Access contents information']
+    modify_permission_list = ['Modify portal content']
+    add_content_permission_list = ['Add portal content']
+    # Configure list of variable on the workflow
+    permission_list = view_permission_list + \
+                      access_permission_list + \
+                      modify_permission_list + \
+                      add_content_permission_list
+    # Remove permission list
+    workflow = portal.portal_workflow[workflow_id]
+    workflow.delManagedPermissions(workflow.permissions)
+    # Add new permission list
+    for permission in permission_list:
+      workflow.addManagedPermission(permission)
+    # Configure state permission matrix
+    state_list = table_dict['state']
+    for state_config in state_list:
+      state_id = state_config.pop('state')
+      state = workflow.states[state_id]
+      # Clean the state matrix
+      for permission in permission_list:
+        state.setPermission(permission, 0, [])
+      # Update state matrix
+      permission_dict = dict([(x, []) for x in permission_list])
+      for role, perm_symbol in state_config.items():
+        managed_permission_list = []
+        if 'A' in perm_symbol:
+          managed_permission_list.extend(access_permission_list)
+        if 'V' in perm_symbol:
+          managed_permission_list.extend(view_permission_list)
+        if 'C' in perm_symbol:
+          managed_permission_list.extend(add_content_permission_list)
+        if 'M' in perm_symbol:
+          managed_permission_list.extend(modify_permission_list)
+        for permission in managed_permission_list:
+          permission_dict[permission].append(role.capitalize())
+      for permission, roles in permission_dict.items():
+        state.setPermission(permission, 0, roles)
+      # XXX To be deleted  
+      #       for permission in permission_list:
+      #         module.manage_permission(permission, ['Manager'], 0)
+
+    # Configure transition guard
+    transition_list = table_dict['transition']
+    for transition_conf in transition_list:
+      transition_id = transition_conf.pop('transition')
+      transition = workflow.transitions[transition_id]
+      guard = transition.getGuard()
+      role_list = [x.capitalize() for x in transition_conf.keys()]
+      role_string = ';'.join(role_list)
+      guard.changeFromProperties({'guard_roles': role_string})
+    # Update business template
+    bt5_obj = business_configuration.getSpecialiseValue()
+    template_workflow_id_list = list(bt5_obj.getTemplateWorkflowIdList())
+    if workflow_id not in template_workflow_id_list:
+      template_workflow_id_list.append(workflow_id)
+    bt5_obj.edit(template_workflow_id_list=template_workflow_id_list,)
diff --git a/product/ERP5Configurator/Document/__init__.py b/product/ERP5Configurator/Document/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/product/ERP5Configurator/Extensions/ConfigurationTemplate_readOOoCalcFile.py b/product/ERP5Configurator/Extensions/ConfigurationTemplate_readOOoCalcFile.py
new file mode 100644
index 0000000000..4a5711720e
--- /dev/null
+++ b/product/ERP5Configurator/Extensions/ConfigurationTemplate_readOOoCalcFile.py
@@ -0,0 +1,126 @@
+##############################################################################
+#
+# Copyright (c) 2006 Nexedi SARL and Contributors. All Rights Reserved.
+#                    Romain Courteaud <romain@nexedi.com>
+#                    Ivan Tyagov <ivan@nexedi.com>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# 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.
+#
+##############################################################################
+
+import StringIO
+
+def read(self, filename):
+  """
+  Return a OOCalc as a StringIO
+  """
+  oo_template_file = getattr(self, filename)
+  fp = StringIO.StringIO(oo_template_file)
+  fp.filename = filename
+  return fp
+
+def getIdFromString(string):
+  """
+    This function transform a string to a safe id.
+    It is used here to create a safe category id from a string.
+  """
+  if string is None:
+    return None
+  clean_id = ''
+  translation_map = { "a": ['\xe0']
+                    , "e": ['\xe9', '\xe8']
+                    }
+  #string = string.lower()
+  string = string.strip()
+  # oocalc inserts some strange chars when you press - key in a text cell.
+  # Following line is a workaround for this, 
+  # because \u2013 does not exist in latin1
+  string = string.replace(u'\u2013', '-')
+  for char in string.encode('utf-8'):#('iso8859_1'):
+    if char == '_' or char.isalnum():
+      clean_id += char
+    elif char.isspace() or char in ('+', '-'):
+      clean_id += '_'
+    else:
+      for (safe_char, char_list) in translation_map.items():
+        if char in char_list:
+          clean_id += safe_char
+          break
+  return clean_id
+
+def convert(self, filename):
+  from Products.ERP5OOo.OOoUtils import OOoParser
+  OOoParser = OOoParser()
+  import_file = read(self, filename)
+
+  # Extract tables from the speadsheet file
+  OOoParser.openFile(import_file)
+  filename = OOoParser.getFilename()
+  spreadsheets = OOoParser.getSpreadsheetsMapping()
+
+  table_dict = {}
+  for table_name, table in spreadsheets.items():
+    if not table:
+      continue
+    # Get the header of the table
+    columns_header = table[0]
+    # Get the mapping to help us to know the property according a cell index
+    property_map = {}
+    column_index = 0
+    for column in columns_header:
+      column_id = getIdFromString(column)
+      # The column has no header information
+      # The column has a normal header
+      property_map[column_index] = column_id
+      column_index += 1
+
+    # Construct categories data (with absolut path) from table lines
+    object_list = []
+
+    for line in table[1:]:
+      object_property_dict = {}
+
+      # Exclude empty lines
+      if line.count('') + line.count(None) == len(line):
+        continue
+
+      # Analyse every cells of the line
+      cell_index = 0
+      for cell in line:
+        # Ignore empty cells, do the test on the generated id 
+        # because getIdFromString() is more restrictive
+        cell_id = getIdFromString(cell)
+        if cell_id not in ('', None):
+          # Get the property corresponding to the cell data
+          property_id = property_map[cell_index]
+          # Convert the value to something like '\xc3\xa9' not '\xc3\xa9'
+          object_property_dict[property_id] = cell.encode('UTF-8')
+        cell_index += 1
+
+      if len(object_property_dict) > 0:
+        object_list.append(object_property_dict)
+    table_dict[table_name.encode('UTF-8')] = object_list
+
+  if len(table_dict.keys()) == 1:
+    return object_list
+  else:
+    return table_dict
diff --git a/product/ERP5Configurator/Permissions.py b/product/ERP5Configurator/Permissions.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/product/ERP5Configurator/PropertySheet/BusinessConfiguration.py b/product/ERP5Configurator/PropertySheet/BusinessConfiguration.py
new file mode 100644
index 0000000000..d04fd8bb57
--- /dev/null
+++ b/product/ERP5Configurator/PropertySheet/BusinessConfiguration.py
@@ -0,0 +1,42 @@
+##############################################################################
+#
+# Copyright (c) 2006 Nexedi SARL and Contributors. All Rights Reserved.
+#                    Romain Courteaud <romain@nexedi.com>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# 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.
+#
+##############################################################################
+
+class BusinessConfiguration:
+  """
+  Properties of a Business Configuration.
+  """
+  _properties = (
+    { 'id'          : 'configuration_after_script_id',
+      'description' : 'Defines the Id of the script to be ran after'
+                      'the configuration.',
+      'type'        : 'string',
+      'default'     : 'BusinessConfiguration_afterConfiguration',
+      'mode'        : 'w' },
+  )
+
+  _categories = ("current_state", "resource", "specialise")
diff --git a/product/ERP5Configurator/PropertySheet/CategoriesSpreadsheetConfiguratorItem.py b/product/ERP5Configurator/PropertySheet/CategoriesSpreadsheetConfiguratorItem.py
new file mode 100644
index 0000000000..9800d59abd
--- /dev/null
+++ b/product/ERP5Configurator/PropertySheet/CategoriesSpreadsheetConfiguratorItem.py
@@ -0,0 +1,47 @@
+##############################################################################
+#
+# Copyright (c) 2008 Nexedi SA and Contributors. All Rights Reserved.
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# 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.
+#
+##############################################################################
+
+class CategoriesSpreadsheetConfiguratorItem:
+  """ Properties of a CategoriesSpreadsheetConfiguratorItem."""
+
+  _properties = (
+    { 'id'          : 'categories_spreadsheet',
+      'storage_id'  : 'default_categories_spreadsheet',
+      'description' : 'A spreadsheet with categories definition',
+      'type'        : 'content',
+      # XXX maybe it can just be a File, so that we don't have to depend on DMS
+      'portal_type' : ('Spreadsheet',),
+      'acquired_property_id'      : ('file', 'content_type', 'data'),
+      'acquisition_base_category' : (),
+      'acquisition_portal_type'   : (),
+      'acquisition_copy_value'    : 0,
+      'acquisition_mask_value'    : 1,
+      'acquisition_sync_value'    : 0,
+      'acquisition_accessor_id'   : 'getDefaultCategoriesSpreadsheetValue',
+      'acquisition_depends'       : None,
+      'mode'        : 'w' },
+   )
diff --git a/product/ERP5Configurator/PropertySheet/ConfigurationItem.py b/product/ERP5Configurator/PropertySheet/ConfigurationItem.py
new file mode 100644
index 0000000000..c2411a18f4
--- /dev/null
+++ b/product/ERP5Configurator/PropertySheet/ConfigurationItem.py
@@ -0,0 +1,37 @@
+##############################################################################
+#
+# Copyright (c) 2006 Nexedi SARL and Contributors. All Rights Reserved.
+#                    Ivan Tyagov <ivan@nexedi.com>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# 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.
+#
+##############################################################################
+
+class ConfigurationItem:
+  """ Properties of a Configuration Item."""
+
+  _properties = ({'id'          : 'configuration_class_name',
+                  'description' : 'Configuration class name',
+                  'type'        : 'string',
+                  'mode'        : 'w',
+                  'default'     : '' },  
+                )
diff --git a/product/ERP5Configurator/PropertySheet/PortalTypeRolesSpreadsheetConfiguratorItem.py b/product/ERP5Configurator/PropertySheet/PortalTypeRolesSpreadsheetConfiguratorItem.py
new file mode 100644
index 0000000000..da76b867af
--- /dev/null
+++ b/product/ERP5Configurator/PropertySheet/PortalTypeRolesSpreadsheetConfiguratorItem.py
@@ -0,0 +1,46 @@
+##############################################################################
+#
+# Copyright (c) 2008 Nexedi SA and Contributors. All Rights Reserved.
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# 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.
+#
+##############################################################################
+
+class PortalTypeRolesSpreadsheetConfiguratorItem:
+  """ Properties of a PortalTypeRolesSpreadsheetConfiguratorItem."""
+
+  _properties = (
+    { 'id'          : 'portal_type_roles_spreadsheet',
+      'storage_id'  : 'default_portal_type_roles_spreadsheet',
+      'description' : 'The spreadsheet for portal type roles configuration',
+      'type'        : 'content',
+      'portal_type' : ('Spreadsheet',),
+      'acquired_property_id'      : ('file', 'content_type', 'data'),
+      'acquisition_base_category' : (),
+      'acquisition_portal_type'   : (),
+      'acquisition_copy_value'    : 0,
+      'acquisition_mask_value'    : 1,
+      'acquisition_sync_value'    : 0,
+      'acquisition_accessor_id'   : 'getDefaultPortalTypeRolesSpreadsheetValue',
+      'acquisition_depends'       : None,
+      'mode'        : 'w' },
+   )
diff --git a/product/ERP5Configurator/PropertySheet/ServiceConfiguratorItem.py b/product/ERP5Configurator/PropertySheet/ServiceConfiguratorItem.py
new file mode 100644
index 0000000000..12269f1e5b
--- /dev/null
+++ b/product/ERP5Configurator/PropertySheet/ServiceConfiguratorItem.py
@@ -0,0 +1,37 @@
+##############################################################################
+#
+# Copyright (c) 2006 Nexedi SARL and Contributors. All Rights Reserved.
+#                    Ivan Tyagov <ivan@nexedi.com>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# 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.
+#
+##############################################################################
+
+class ServiceConfiguratorItem:
+  """ Properties of a ServiceConfiguratorItem."""
+
+  _properties = ({'id'          : 'service',
+                  'description' : 'Services',
+                  'type'        : 'lines',
+                  'mode'        : 'w',
+                  'default'     : []},
+                )
diff --git a/product/ERP5Configurator/PropertySheet/SitePropertyConfiguratorItem.py b/product/ERP5Configurator/PropertySheet/SitePropertyConfiguratorItem.py
new file mode 100644
index 0000000000..e835e05e13
--- /dev/null
+++ b/product/ERP5Configurator/PropertySheet/SitePropertyConfiguratorItem.py
@@ -0,0 +1,36 @@
+##############################################################################
+#
+# Copyright (c) 2006 Nexedi SARL and Contributors. All Rights Reserved.
+#                    Ivan Tyagov <ivan@nexedi.com>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# 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.
+#
+##############################################################################
+
+class SitePropertyConfiguratorItem:
+  """ Site Properties Configurator Item."""
+
+  _properties = ({'id'          : 'site_property_list',
+                  'description' : 'Site Property List',
+                  'type'        : 'lines',
+                  'mode'        : 'w',
+                  'default'     : []},)
diff --git a/product/ERP5Configurator/PropertySheet/StandardBT5ConfiguratorItem.py b/product/ERP5Configurator/PropertySheet/StandardBT5ConfiguratorItem.py
new file mode 100644
index 0000000000..930a33723b
--- /dev/null
+++ b/product/ERP5Configurator/PropertySheet/StandardBT5ConfiguratorItem.py
@@ -0,0 +1,37 @@
+##############################################################################
+#
+# Copyright (c) 2006 Nexedi SARL and Contributors. All Rights Reserved.
+#                    Ivan Tyagov <ivan@nexedi.com>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# 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.
+#
+##############################################################################
+
+class StandardBT5ConfiguratorItem:
+  """ Properties of a ReturnStandardBT5ConfiguratorItem."""
+
+  _properties = ({'id'          : 'bt5_id',
+                  'description' : 'Business Template ID',
+                  'type'        : 'string',
+                  'mode'        : 'w',
+                  'default'     : 'erp5_base' },  
+                )
diff --git a/product/ERP5Configurator/PropertySheet/__init__.py b/product/ERP5Configurator/PropertySheet/__init__.py
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/product/ERP5Configurator/PropertySheet/__init__.py
@@ -0,0 +1 @@
+
diff --git a/product/ERP5Configurator/Tool/ConfiguratorTool.py b/product/ERP5Configurator/Tool/ConfiguratorTool.py
new file mode 100644
index 0000000000..9640e683f6
--- /dev/null
+++ b/product/ERP5Configurator/Tool/ConfiguratorTool.py
@@ -0,0 +1,476 @@
+##############################################################################
+#
+# Copyright (c) 2006-2010 Nexedi SA and Contributors. All Rights Reserved.
+#                    Romain Courteaud <romain@nexedi.com>
+#                    Ivan Tyagov <ivan@nexedi.com>
+#                    Rafael Monnerat <rafael@nexedi.com>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# 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 AccessControl import ClassSecurityInfo
+from Products.ERP5Type.Globals import DTMLFile
+from Products.ERP5Type.Accessor.Constant import PropertyGetter as \
+    ConstantGetter
+from Products.ERP5Type.Tool.BaseTool import BaseTool
+from Products.ERP5Type import Permissions
+from Products.ERP5Configurator import _dtmldir
+from Products.CMFCore.utils import getToolByName
+from Products.Formulator.Errors import FormValidationError
+import cookielib
+from base64 import encodestring
+from urllib import quote
+from DateTime import DateTime
+
+# global (RAM) cookie storage
+cookiejar = cookielib.CookieJar()
+last_loggedin_user_and_password = None
+referer  = None
+installation_status = {'bt5': {'current': 0,
+                               'all': 0,},
+                       'activity_list': [],}
+
+# cookie name to store user's preferred language name
+LANGUAGE_COOKIE_NAME = 'configurator_user_preferred_language'
+BUSINESS_CONFIGURATION_COOKIE_NAME = 'business_configuration_key'
+
+def getAvailableLanguageFromHttpAcceptLanguage(http_accept_language,
+                                               available_language_list,
+                                               default='en'):
+  for language_set in http_accept_language.split(','):
+    language_tag = language_set.split(';')[0]
+    language = language_tag.split('-')[0]
+    if language in available_language_list:
+      return language
+  return default
+
+def _isUserAcknowledged(cookiejar):
+  """ Is user authenticated to remote system through a cookie. """
+  for cookie in cookiejar:
+    if cookie.name == '__ac' and cookie.value != '':
+      return 1
+  return 0
+
+def _validateFormToRequest(form, REQUEST, **kw):
+    """ Validate form to REQUEST. """
+    form_kw = {}
+    REQUEST.form = kw
+    try:
+      form.validate_all_to_request(REQUEST)
+      validation_status = 0
+      validation_errors = None
+    except FormValidationError, validation_errors:
+      ## not all fields valid
+      validation_status = 1
+    except Exception, validation_errors:
+      ## missing fields
+      validation_status = 2
+    ## extract form arguments and remove leading prefixes
+    if validation_status==0:
+      for field in form.get_fields():
+        field_id = field.id
+        value = getattr(REQUEST, field_id, None)
+        for prefix in ('my_', 'your_',):
+          if field_id.startswith(prefix):
+            attr_id = field_id[len(prefix):]
+            form_kw[attr_id] = value
+            for del_key in (field.generate_field_key(validation=1), field_id):
+              try:
+                REQUEST.other.pop(del_key)
+              except KeyError:
+                pass
+    return validation_status, form_kw, validation_errors
+
+
+class ConfiguratorTool(BaseTool):
+  """                                       
+    This tool provides a Configurator Tool.
+  """                 
+
+  id = 'portal_configurator'
+  title = 'Configurator Tool'
+  meta_type = 'ERP5 Configurator Tool'
+  portal_type = 'Configurator Tool'   
+
+  isPortalContent = ConstantGetter('isPortalContent', value=True)
+
+  security = ClassSecurityInfo()
+
+  security.declareProtected(Permissions.ManagePortal, 'manage_overview')
+  manage_overview = DTMLFile('explainConfiguratorTool', _dtmldir )
+
+  def getConfiguratorUserPreferredLanguage(self):
+    """ Get configuration language as selected by user """
+    REQUEST = getattr(self, 'REQUEST', None)
+    configurator_user_preferred_language = None
+    if REQUEST is not None:
+      # language value will be in cookie or REQUEST itself.
+      configurator_user_preferred_language = REQUEST.get(LANGUAGE_COOKIE_NAME,
+          None)
+      if configurator_user_preferred_language is None:
+        # Find a preferred language from HTTP_ACCEPT_LANGUAGE
+        available_language_list = [i[1] for i in self\
+            .ConfiguratorTool_getConfigurationLanguageList()]
+        configurator_user_preferred_language = \
+            getAvailableLanguageFromHttpAcceptLanguage(
+          REQUEST.get('HTTP_ACCEPT_LANGUAGE', 'en'),
+          available_language_list)
+    if configurator_user_preferred_language is None:
+      configurator_user_preferred_language = 'en'
+    return configurator_user_preferred_language
+
+  ######################################################
+  ##               Navigation                         ##
+  ######################################################
+  def login(self, REQUEST):
+    """ Login client and show next form. """
+    password = REQUEST.get('field_my_ac_key', '')
+    if self._isCorrectConfigurationKey(password):
+      # set user preferred configuration language
+      user_preferred_language = REQUEST.get(
+          'field_my_user_preferred_language', None)
+      if user_preferred_language:
+        # Set language value to request so that next page after login
+        # can get the value. Because cookie value is available from
+        # next request.
+        REQUEST.set(LANGUAGE_COOKIE_NAME, user_preferred_language)
+        REQUEST.RESPONSE.setCookie(LANGUAGE_COOKIE_NAME,
+                                   user_preferred_language,
+                                   path='/',
+                                   expires=(DateTime()+30).rfc822())
+      # set encoded __ac_key cookie at client's browser
+      __ac_key = quote(encodestring(password))
+      expires = (DateTime() + 1).toZone('GMT').rfc822()
+      REQUEST.RESPONSE.setCookie('__ac_key',
+                                 __ac_key,
+                                 expires = expires)
+      REQUEST.set('__ac_key', __ac_key)
+      bc = REQUEST.get('field_your_business_configuration')
+      REQUEST.RESPONSE.setCookie(BUSINESS_CONFIGURATION_COOKIE_NAME, 
+                                 bc, 
+                                 expires = expires)
+      REQUEST.set(BUSINESS_CONFIGURATION_COOKIE_NAME, bc)
+      return self.next(REQUEST=REQUEST)
+    else:
+      REQUEST.set('portal_status_message', 
+                   self.Base_translateString('Incorrect Configuration Key'))
+      return self.view()
+
+  def _isCorrectConfigurationKey(self, password=None):
+    """ Is configuration key correct """
+    if password is None:
+      password = self.REQUEST.get('__ac_key', None)
+    # Not still not finished yet.
+    return 1
+
+  #security.declareProtected(Permissions.ModifyPortalContent, 'next')
+  def next(self, REQUEST):
+    """ Validate settings and return a new form to the user.  """
+    # check if user is allowed to access service
+    portal = self.getPortalObject()
+    if not self._isCorrectConfigurationKey():
+      REQUEST.set('portal_status_message', 
+                  self.Base_translateString('Incorrect Configuration Key'))
+      return self.view()
+    kw = self.REQUEST.form.copy()
+    business_configuration = REQUEST.get(BUSINESS_CONFIGURATION_COOKIE_NAME)
+    bc = portal.restrictedTraverse(business_configuration)
+    if bc is None:
+      REQUEST.set('portal_status_message', 
+                   self.Base_translateString(
+                     'You cannot Continue. Unable to find your Business Configuration.'))
+      return self.view()
+    response = self._next(business_configuration=bc,kw=kw)
+    ## Parse server response
+    command = response["command"]
+    if command == "show":
+      return self.ConfiguratorTool_dialogForm(previous=response['previous'],
+                                        form_html=response["data"],
+                                        next = response['next'])
+    elif command == "install":
+      return self.startInstallation(bc, REQUEST=REQUEST)
+
+  def _next(self, business_configuration, kw):
+    """ Return next configuration form and validate previous. """
+    form_kw = {}
+    need_validation = 1
+    validation_errors = None
+    response = {}
+    portal = self.getPortalObject()
+
+    ## initial state no previous form to validate
+    if business_configuration.isInitialConfigurationState():
+      need_validation = 0
+
+    ## client can not go further hist business configuration is already built
+    if business_configuration.isEndConfigurationState() or \
+         business_configuration.getNextTransition() == None:
+      return self._terminateConfigurationProcess(response,
+          'no_available_transitions')
+
+    isMultiEntryTransition = business_configuration._isMultiEntryTransition()
+    ## validate multiple forms
+    if isMultiEntryTransition:
+      html_forms = []
+      failed_forms_counter = 0
+      transition = business_configuration.getNextTransition()
+      form = getattr(business_configuration, transition.getTransitionFormId())
+      for form_key in filter(lambda x: x.startswith('field_'), kw.keys()):
+        form_kw[form_key] = kw[form_key]
+      ## iterate all forms
+      for form_counter in range(0, isMultiEntryTransition):
+        single_form_kw = {}
+        for key,value in form_kw.items():
+          if isinstance(value, list) or isinstance(value, tuple):
+            ## we have more than one form shown
+            single_form_kw[key] = value[form_counter]
+            # save original value in request in some cases of multiple forms
+            # we need it for validation
+            single_form_kw['_original_%s' %key] = value
+          else:
+            ## even though we have multiple entry transition customer wants
+            ## ONE form!
+            single_form_kw[key] = value
+        ## update properly REQUEST with current form data
+        for key,value in single_form_kw.items():
+          self.REQUEST.set(key, value)
+        ## get validation status
+        validation_status, dummy, validation_errors = \
+           business_configuration._validateNextForm(**single_form_kw)
+
+        ## clean up REQUEST from traces from validate_all_to_request
+        ## otherwise next form will use previous forms details
+        cleanup_keys = filter(lambda x: x.startswith('my_') or
+                                x.startswith('your_'),
+                                self.REQUEST.other.keys())
+        for key in cleanup_keys:
+          self.REQUEST.other.pop(key, None)
+        ## render HTML code
+        if validation_status != 0:
+          failed_forms_counter += 1
+          ## XXX: form can fail because a new
+          ## http://localhost:9080/erp5/portal_wizard/next is issued
+          ## without arguments. Improve this
+          try:
+            self.REQUEST.set('field_errors',
+                form.ErrorFields(validation_errors))
+          except:
+            pass
+          single_form_html = form()
+          self.REQUEST.other.pop('field_errors', None)
+          self.REQUEST.form = {}
+        else:
+          single_form_html = form()
+        ## wrap in form template
+        single_form_html = self.Base_mainConfiguratorFormTemplate(
+                                current_form_number = form_counter +1,
+                                max_form_numbers = isMultiEntryTransition,
+                                form_html = single_form_html)
+        ## add to list of forms as html code
+        html_forms.append(single_form_html)
+      ## return if failure
+      if failed_forms_counter > 0:
+        next_state = self.restrictedTraverse(business_configuration.getNextTransition()\
+            .getDestination())
+        html_data = self.Base_mainConfiguratorTemplate(
+            form_html = "\n".join(html_forms),
+            current_state = next_state,
+            business_configuration = business_configuration)
+        response.update(command = "show",
+                  previous = self.Base_translateString("Previous"),
+                  next = self.Base_translateString(transition.getTitle()),
+                  data = html_data)
+        return response
+    
+    ## show next form in transitions
+    rendered = False
+    while rendered is False:
+      if need_validation == 1:
+        if isMultiEntryTransition:
+          ## multiple forms must be validated before
+          validation_status = 0
+        else:
+          validation_status, form_kw, validation_errors = \
+              business_configuration._validateNextForm(**kw)
+        if validation_status==1:
+          need_validation = 0
+        elif validation_status==2:
+          rendered = True
+          need_validation = 0
+          if business_configuration.getNextTransition() == None:
+            ### client can not continue at the momen
+            return self._terminateConfigurationProcess(response,
+                reason='no_available_transitions')
+          response["previous"], html, form_title, response["next"], \
+              response['server_buffer'] = business_configuration._displayNextForm()
+        else:
+          ## validation passed
+          need_validation = 0
+          business_configuration._executeTransition(form_kw=form_kw, request_kw=kw)
+      elif need_validation == 0:
+        if business_configuration.getNextTransition() == None:
+          return self._terminateConfigurationProcess(response,
+              'no_available_transitions')
+        ## validation failure
+        rendered = True
+        response["previous"], html, form_title, response["next"], \
+            response['server_buffer'] = business_configuration.\
+            _displayNextForm(validation_errors=validation_errors)
+
+    if html is None:
+      ## we have no more forms proceed to build
+      response.update(command = "install", data = None)
+    else:
+      ## we have more forms
+      next_state = self.restrictedTraverse(business_configuration.getNextTransition()\
+          .getDestination())
+      html_data = self.Base_mainConfiguratorTemplate(
+          form_html = html,
+          current_state = next_state,
+          business_configuration = business_configuration)
+      response.update(command = "show", data = html_data)
+    return response
+
+  def _terminateConfigurationProcess(self, response, reason=''):
+    """ Terminate process and return some explanations to client why
+        he can no longer continue. """
+    if reason == 'no_available_transitions':
+      form_html = self.BusinessConfiguration_viewStopForm()
+      response.update(command = "show", next = None, \
+                      previous = None, data = form_html)
+    elif reason == 'authentification_failure':
+      form_html = self.BusinessConfiguration_viewUnauthenticatedForm()
+      response.update(command = "show", data = form_html,
+                      next = None, previous = None,)
+
+    return response
+
+  #security.declareProtected(Permissions.ModifyPortalContent, 'previous')
+  def previous(self, REQUEST):
+    """ Display the previous form. """
+    # check if user is allowed to access service
+    portal = self.getPortalObject()
+    if not self._isCorrectConfigurationKey():
+      REQUEST.set('portal_status_message',
+                  self.Base_translateString('Incorrect Configuration Key'))
+      return self.view()
+    kw = self.REQUEST.form.copy()
+    business_configuration = REQUEST.get(BUSINESS_CONFIGURATION_COOKIE_NAME)
+    bc = portal.restrictedTraverse(business_configuration)
+    response = self._previous(business_configuration=bc, kw=kw)
+    return self.ConfiguratorTool_dialogForm(previous=response['previous'],
+                                      form_html=response['data'],
+                                      next=response['next'])
+
+  def _previous(self, business_configuration, kw):
+    """ Returns previous form. """
+    response = {}
+    ## is client is not allowed access ?
+    if business_configuration is None:
+      form_html = self.BusinessConfiguration_viewUnauthenticatedForm()
+      return self.ConfiguratorTool_dialogForm(form_html = form_html)
+    ## client can not go further his business configuration is already built
+    if business_configuration.isEndConfigurationState():
+      form_html = self.BusinessConfiguration_viewStopForm()
+      return self.ConfiguratorTool_dialogForm(form_html = form_html,
+                                        next = "Next")
+
+    response['previous'], form_html, form_title, response['next'], server_buffer = \
+        business_configuration._displayPreviousForm()
+
+    next_state = self.restrictedTraverse(
+        business_configuration.getNextTransition().getDestination())
+
+    response['data'] = self.Base_mainConfiguratorTemplate(
+        form_html = form_html,
+        current_state = next_state,
+        business_configuration = business_configuration)
+    return response
+
+  security.declarePublic(Permissions.AccessContentsInformation,
+                         'getInstallationStatusReport')
+  def getInstallationStatusReport(self,
+                          active_process_id=None, REQUEST=None):
+    """ Query local ERP5 instance for installation status.
+        If installation is over the installation activities and reindexing
+        activities should not exists.
+    """
+    global installation_status
+    portal_activities = getToolByName(self.getPortalObject(),
+        'portal_activities')
+    is_bt5_installation_over = (portal_activities.countMessageWithTag(
+      'initialERP5Setup')==0)
+    if 0 == len(portal_activities.getMessageList()) and \
+        is_bt5_installation_over:
+      html = self.ConfiguratorTool_viewSuccessfulConfigurationMessageRenderer()
+    else:
+      if is_bt5_installation_over:
+        # only if bt5s are installed start tracking number of activities
+        activity_list = portal_activities.getMessageList()
+        installation_status['activity_list'].append(len(activity_list))
+      html = self.ConfiguratorTool_viewRunningInstallationMessage(
+          installation_status = installation_status)
+    # set encoding as this is usually called from asynchronous JavaScript call
+    self.REQUEST.RESPONSE.setHeader('Content-Type',
+        'text/html; charset=utf-8')
+    return html
+
+  security.declareProtected(Permissions.ModifyPortalContent, 'startInstallation')
+  def startInstallation(self, business_configuration, REQUEST):
+    """ Start installation process as an activity which will query generation
+        server and download/install bt5 template files and meanwhile offer
+        user a nice GUI to observe what's happening. """
+    global installation_status
+    # init installation status
+    bt5_file_list = len(business_configuration.contentValues(
+                                portal_types=["File", "Link"])) or 1
+    installation_status['bt5']['all'] = bt5_file_list
+    installation_status['bt5']['current'] = 0
+    installation_status['activity_list'] = []
+    active_process = self.portal_activities.newActiveProcess()
+    REQUEST.set('active_process_id', active_process.getId())
+    request_restore_dict = {'__ac_key': REQUEST.get('__ac_key',
+      None),}
+    self.activate(active_process=active_process, tag = 'initialERP5Setup'
+        ).initialERP5Setup(business_configuration.getRelativeUrl(), request_restore_dict)
+    return self.ConfiguratorTool_viewInstallationStatus(REQUEST)
+
+  security.declareProtected(Permissions.ModifyPortalContent,
+      'initialERP5Setup')
+  def initialERP5Setup(self, business_configuration, request_restore_dict={}):
+    """ Get from remote generation server customized bt5 template files
+        and then install them. """
+    # restore some REQUEST variables as this method is executed in an activity
+    # and there's no access to real original REQUEST
+    for key, value in request_restore_dict.items():
+      self.REQUEST.set(key, value)
+
+    bc = self.restrictedTraverse(business_configuration)
+    # XXX FIXME we just have to build once.
+    bc.build()
+    bc.install()
+
+    finalize_method = getattr(self, 'ConfiguratorTool_finalizeInstallation', None)
+    if finalize_method is not None and callable(finalize_method):
+      finalize_method(business_configuration = bc,
+                      **request_restore_dict)
diff --git a/product/ERP5Configurator/Tool/__init__.py b/product/ERP5Configurator/Tool/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/product/ERP5Configurator/VERSION.txt b/product/ERP5Configurator/VERSION.txt
new file mode 100644
index 0000000000..8542ced240
--- /dev/null
+++ b/product/ERP5Configurator/VERSION.txt
@@ -0,0 +1 @@
+ERP5Configurator 5.4.7
diff --git a/product/ERP5Configurator/__init__.py b/product/ERP5Configurator/__init__.py
new file mode 100644
index 0000000000..373672c675
--- /dev/null
+++ b/product/ERP5Configurator/__init__.py
@@ -0,0 +1,60 @@
+##############################################################################
+#
+# Copyright (c) 2006 Nexedi SARL and Contributors. All Rights Reserved.
+#                    Romain Courteaud <romain@nexedi.com>
+#                    Ivan Tyagov <ivan@nexedi.com>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# 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.
+#
+##############################################################################
+"""
+    ERP5Configurator is a product containing everything needed to the
+    configurator.
+"""
+
+# Update ERP5 Globals
+from Products.ERP5Type.Utils import initializeProduct, updateGlobals
+import sys, Permissions
+this_module = sys.modules[ __name__ ]
+document_classes = updateGlobals(this_module, globals(),
+                                 permissions_module=Permissions)
+
+from Tool import ConfiguratorTool
+
+# Define object classes and tools
+object_classes = ()
+portal_tools = (ConfiguratorTool.ConfiguratorTool,
+                )
+
+content_classes = ()
+content_constructors = ()
+
+# Finish installation
+def initialize(context):
+  import Document
+  initializeProduct(context, this_module, globals(),
+                    document_module=Document,
+                    document_classes=document_classes,
+                    object_classes=object_classes,
+                    portal_tools=portal_tools,
+                    content_constructors=content_constructors,
+                    content_classes=content_classes)
diff --git a/product/ERP5Configurator/help/README b/product/ERP5Configurator/help/README
new file mode 100644
index 0000000000..a763127a26
--- /dev/null
+++ b/product/ERP5Configurator/help/README
@@ -0,0 +1 @@
+ERP5Configurator help
diff --git a/product/ERP5Configurator/interfaces/__init__.py b/product/ERP5Configurator/interfaces/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/product/ERP5Configurator/interfaces/configurator_item.py b/product/ERP5Configurator/interfaces/configurator_item.py
new file mode 100644
index 0000000000..8a2e6692d3
--- /dev/null
+++ b/product/ERP5Configurator/interfaces/configurator_item.py
@@ -0,0 +1,46 @@
+##############################################################################
+#
+# Copyright (c) 2010 Nexedi SARL and Contributors. All Rights Reserved.
+#                    Rafael Monnerat <rafael@nexedi.com>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# 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 Interface
+
+class IConfiguratorItem(Interface):
+  """ 
+  Configurator Item interface specification.
+
+   Documents which implement the IConfiguratorItem interface
+   can be used to build an ERP5 Configuration.
+  """
+
+  def build(business_configuration):
+    """ 
+    Build new ERP5 Documents based on stored parameters during
+    the configuraton process.
+
+    business_configuration - Business Configuration Document that is
+                             been used to configure.
+    """
diff --git a/product/ERP5Configurator/mixin/__init__.py b/product/ERP5Configurator/mixin/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/product/ERP5Configurator/mixin/configurator_item.py b/product/ERP5Configurator/mixin/configurator_item.py
new file mode 100644
index 0000000000..04facf1ef3
--- /dev/null
+++ b/product/ERP5Configurator/mixin/configurator_item.py
@@ -0,0 +1,50 @@
+##############################################################################
+#
+# Copyright (c) 2006 Nexedi SARL and Contributors. All Rights Reserved.
+#                    Romain Courteaud <romain@nexedi.com>
+#                    Ivan Tyagov <ivan@nexedi.com>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# 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.
+#
+##############################################################################
+
+class ConfiguratorItemMixin:
+  """ This is the base class for all configurator item. """
+ 
+  def install(self, object, business_configuration, prefix = ''):
+    """ Add object to customer customization template. """
+    bt5_obj = business_configuration.getSpecialiseValue()
+    if object.getPortalType() in ['Category', 'Base Category']:
+      prefix = "portal_categories/"
+    template_path_list = ['%s%s' % (prefix, object.getRelativeUrl()),
+                          '%s%s/**' % (prefix, object.getRelativeUrl())]
+    current_template_path_list = list(bt5_obj.getTemplatePathList())
+    current_template_path_list.extend(template_path_list)
+    bt5_obj.edit(template_path_list=current_template_path_list)
+  
+  def addToCustomerBT5ByRelativeUrl(self, business_configuration, relative_url_list):
+    """ Add object to customer customization template object by its relative url. """
+    bt5_obj = business_configuration.getSpecialiseValue()
+    current_template_path_list = list(bt5_obj.getTemplatePathList())
+    current_template_path_list.extend(relative_url_list)
+    bt5_obj.edit(template_path_list=current_template_path_list)
+
diff --git a/product/ERP5Configurator/skins/__init__.py b/product/ERP5Configurator/skins/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/product/ERP5Configurator/tests/__init__.py b/product/ERP5Configurator/tests/__init__.py
new file mode 100644
index 0000000000..e16c76dff8
--- /dev/null
+++ b/product/ERP5Configurator/tests/__init__.py
@@ -0,0 +1 @@
+""
diff --git a/product/ERP5Configurator/tool.png b/product/ERP5Configurator/tool.png
new file mode 100644
index 0000000000000000000000000000000000000000..681679219796ba6657aba4a13518b632ac71e05f
GIT binary patch
literal 287
zcmeAS@N?(olHy`uVBq!ia0vp^0wB!73?$#)eFPFP2=EDU{r~@eSlZ52yFYq_OpMDr
zIA`r!-?$~0@BhlIJyp?l)6%(U*NLyreszX6DL|F2GwyBQ$vPdVm$4+sFPOpM*^M+H
zN36s(q9iy!t)x7$D3u{SGtH<VFI~Y%&qU8?ahy9JOoelPZf<H`34?E9N~%J6W=V#E
zyQgnJU8?L<pbD0BPq%a+%~ccmHW)~Idb&7<RLn_EaA5qy6wtPbd5U1z2iZdx-pBAJ
z*4Sk;EQ{oKp1MM|;li7^fW}Fz0(F_<GoF?8>|rpht@LE@InT~<Hic)iloB(;m2G@x
U`&o}v01ajEboFyt=akR{0Hft)-v9sr

literal 0
HcmV?d00001

-- 
2.30.9