diff --git a/product/ERP5SyncML/Conduit/ERP5ShopOrderConduit.py b/product/ERP5SyncML/Conduit/ERP5ShopOrderConduit.py
new file mode 100755
index 0000000000000000000000000000000000000000..ee16c3fb3cb651fc5385109ec8727edf10bc5be8
--- /dev/null
+++ b/product/ERP5SyncML/Conduit/ERP5ShopOrderConduit.py
@@ -0,0 +1,764 @@
+##############################################################################
+#
+# Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved.
+#                    Kevin Deldycke <kevin@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.ERP5SyncML.Conduit.ERP5Conduit import ERP5Conduit
+from AccessControl import ClassSecurityInfo
+from Products.ERP5Type import Permissions
+from Products.CMFCore.utils import getToolByName
+from Acquisition import aq_base, aq_inner, aq_chain, aq_acquire
+
+from xml.dom import implementation
+from xml.dom.ext import PrettyPrint
+from xml.dom import Node
+
+import random
+from cStringIO import StringIO
+
+from zLOG import LOG
+
+
+
+class ERP5ShopOrderConduit(ERP5Conduit):
+  """
+  This conduit is used in the synchronisation process of Storever and ERP5 to convert
+  a Storever Shop Order to a ERP5 Sale Order.
+  """
+
+
+#   TODO: tester ce script sur le serveur de backup (qui semble ĂȘtre different)
+
+
+  # Declarative security
+  security = ClassSecurityInfo()
+
+  # Initialize the random function
+  random.seed()
+
+
+
+  security.declareProtected(Permissions.ModifyPortalContent, 'constructContent')
+  def constructContent(self, object, object_id, docid, portal_type):
+    """
+    This is a redefinition of the original ERP5Conduit.constructContent function to
+    allow the creation of a ERP5 Sale Order instead of a Storever Shop Order.
+    """
+    portal_types = getToolByName(object, 'portal_types')
+    subobject = None
+    if portal_type == 'Shop Order':
+      # The random part of the id can be removed. It's only used for the developpement
+      new_object_id = 'storever-' + object_id  + '-' + str(random.randint(1000, 9999))
+      object.newContent ( portal_type = 'Sale Order'
+                        , id          = new_object_id)
+    if portal_type == 'Order Line':
+      last_line_num = self.getLastOrderLineNumber(object)
+      new_object_id = "storever-" + str(last_line_num + 1) + "-" + object_id
+      object.newContent ( portal_type = 'Sale Order Line'
+                        , id          = new_object_id)
+    subobject = object._getOb(new_object_id)
+    return subobject
+
+
+#   # Not needed yet
+#   security.declareProtected(Permissions.ModifyPortalContent, 'addWorkflowNode')
+#   def addWorkflowNode(self, object, xml, simulate):
+#     """
+#     This is a redefinition of the original ERP5Conduit.addWorkflowNode function to
+#     allow the translation of a Storever Shop Order workflow to a ERP5 Sale Order one.
+#     """
+#     conflict_list = []
+#     status = self.getStatusFromXml(xml)
+# #     if status['action'] == 'ship':
+# #       status['time']
+#     return conflict_list
+
+
+
+  security.declarePrivate('dom2str')
+  def dom2str(self, xml_root=None):
+    """
+    This function transform a DOM tree to string.
+    This function is only usefull for debugging.
+    """
+    xml_str = StringIO()
+    PrettyPrint(xml_root, xml_str)
+    xml_string = xml_str.getvalue()
+    LOG('XML output: ', 0, xml_string)
+    return xml_string
+
+
+
+  security.declarePrivate('str2id')
+  def str2id(self, string=None):
+    """
+    This function transform a string to a safe id.
+    It is also used here to create a safe category id from a string.
+    """
+    out = ''
+    if string == None:
+      return None
+    string = string.lower()
+    # We ignore the discontinued information to allow the use of the same category
+    # even if the option is discontinued on the storever side
+    string = string.replace('discontinued', '')
+    string = string.strip()
+#     # TODO: manage accent
+    for char in string:
+      if char == '_' or char.isalnum():
+        pass
+      elif char.isspace() or char in ('+', '-'):
+        char = '_'
+      else:
+        char = None
+      if char != None:
+        out += char
+    # LOG('Category name output (using str2id) >>>>>>> ', 0, out)
+    return out
+
+
+
+  security.declarePrivate('countrySearch')
+  def countrySearch(self, site_root, category_path=None, country=None):
+    """
+    This recursive function try to find the region category from the name of a country
+    """
+    if category_path == None:
+      portal_categories = getToolByName(site_root, 'portal_categories')
+      categories_path = portal_categories.absolute_url(relative=1)
+      category_path = categories_path + '/region'
+    region_folder = site_root.restrictedTraverse(category_path)
+    for region_id in region_folder.objectIds():
+      region_path = category_path + '/' + region_id
+      splitted_path = region_path.split("/")
+      cat_region = "/".join(splitted_path[3:])
+      if region_id.lower() == country.lower():
+        return cat_region
+      find_path = self.countrySearch(site_root, region_path, country)
+      if find_path != None:
+        return find_path
+    return None
+
+
+
+  security.declarePrivate('createOrFindProduct')
+  def createOrFindProduct(self, erp5_site, erp5_product_id):
+    """
+    This function try to find a previous product with the same id,
+    and create it if the search is unsuccessful
+    """
+    erp5_site_path = erp5_site.absolute_url(relative=1)
+    product_path = erp5_site_path + '/product'
+    product_folder = erp5_site.restrictedTraverse(product_path)
+    # Try to find a previous product
+    for product_id in product_folder.objectIds():
+      if product_id == erp5_product_id:
+        return erp5_site.restrictedTraverse(erp5_site_path + '/product/' + erp5_product_id)
+    # We have to create a new product
+    product_folder.newContent ( portal_type = 'Product'
+                              , id          = erp5_product_id)
+    return product_folder._getOb(erp5_product_id)
+
+
+
+  security.declarePrivate('setProductWorkflow')
+  def setProductWorkflow(self, product_object, product_title):
+    """
+    This function set the validation workflow to indicate if a product
+    is discontinued (workflow state = invalidate) or not (workflow state = validate)
+    """
+    action = None
+    if hasattr(product_object, 'workflow_history'):
+      workflow_state = product_object.portal_workflow.getInfoFor(product_object, 'validation_state')
+    if product_title.lower().find('discontinued') != -1:
+      if workflow_state != 'invalidated':
+        action = 'invalidate_action'
+    elif workflow_state in ('draft', 'invalidated'):
+      action = 'validate_action'
+    if action != None:
+      product_object.portal_workflow.doActionFor( product_object
+                                                , action
+                                                , wf_id = 'validation_workflow')
+
+
+
+  security.declarePrivate('niceTitle')
+  def niceTitle(self, title):
+    """
+    This function create a nice title without the discontinued information
+    """
+    splitted_title = title.strip().split(" ")
+    nice_title = ''
+    for string in splitted_title:
+      if string.lower().find('discontinued') == -1:
+        nice_title += string + ' '
+    return nice_title.strip()
+
+
+
+  security.declarePrivate('getLastOrderLineNumber')
+  def getLastOrderLineNumber(self, order_object):
+    """
+    This function give the number of the last Storever Shop Order Line processed
+    """
+    # Scan existing order line id to get the last order line number
+    maximum_order_num = 0
+    for order_line_id in order_object.objectIds():
+      splitted_line_id = order_line_id.split("-")
+      current_line_num = int(splitted_line_id[1])
+      if current_line_num > maximum_order_num:
+        maximum_order_num = current_line_num
+    LOG('getLastOrderLineNumber return  >>>>>>>> ', 0, repr(maximum_order_num))
+    return int(maximum_order_num)
+
+
+
+#     # Not needed yet because we prefer using my own owner_account_id property
+#   security.declareProtected(Permissions.ModifyPortalContent, 'addLocalRoleNode')
+#   def addLocalRoleNode(self, object, xml):
+#     """
+#     """
+#     conflict_list = []
+#     LOG('object >>>>>>>> ', 0, object)
+#     LOG('xml >>>>>>>> ', 0, self.dom2str(xml))
+#     return conflict_list
+
+
+
+  security.declareProtected(Permissions.ModifyPortalContent, 'editDocument')
+  def editDocument(self, object=None, **kw):
+    """
+    This function use the properties of the object to convert a Storever ShopOrder to an ERP5 SaleOrder.
+    """
+    if object == None:
+      return
+
+    LOG('KW >>>>>>>> ', 0, kw)
+
+    # Get the ERP5 root object
+    portal_types = getToolByName(object, 'portal_types')
+    erp5_site = portal_types.getPortalObject()
+    erp5_site_path = erp5_site.absolute_url(relative=1)
+
+    # The object is a ShopOrder
+    if kw.has_key('country'):
+
+      # Find the organisation and the person folder
+      person_path = erp5_site_path + '/person'
+      person_folder = erp5_site.restrictedTraverse(person_path)
+      organisation_path = erp5_site_path + '/organisation'
+      organisation_folder = erp5_site.restrictedTraverse(organisation_path)
+      # Find the service folder
+      service_path = erp5_site_path + '/service'
+      service_folder = erp5_site.restrictedTraverse(service_path)
+
+#       # TODO : if storever-id exist dans ERP5 --> prendre en charge l'update de la facture
+
+      # Get the id of the owner account in storever
+      owner_account_id = kw['owner_account_id']
+      # Set the id of the owner in ERP5 (the owner could be an Organisation or a Person)
+      owner_id = "storever-" + owner_account_id
+
+      # Try to find the identity created for a previous ShopOrder of the same Storever member account
+      person_object = None
+      organisation_object = None
+      for person_id in person_folder.objectIds():
+        if person_id == owner_id:
+          person_object = erp5_site.restrictedTraverse(erp5_site_path + '/person/' + person_id)
+          LOG("Previous person found ! >>>>>>>>",0,repr(person_object))
+          break
+      for organisation_id in organisation_folder.objectIds():
+        if organisation_id == owner_id:
+          organisation_object = erp5_site.restrictedTraverse(erp5_site_path + '/organisation/' + organisation_id)
+          LOG("Previous organisation found ! >>>>>>>>",0,repr(organisation_object))
+          break
+
+      # Define the previous customer structure
+      previous_owner_type = ''
+      if person_object != None:
+        previous_owner_type += 'p'
+      if organisation_object != None:
+        previous_owner_type += 'o'
+      if len(previous_owner_type) == 0:
+        previous_owner_type = None
+      LOG("Previous customer structure >>>>>>>>",0,repr(previous_owner_type))
+
+      # Try to know the type of the current storever customer
+      owner_type = ''
+      if kw.has_key('name') and kw['name'] not in (None, ''):
+        owner_type += 'p'
+      if kw.has_key('organisation') and kw['organisation'] not in (None, '', 'none'):
+        owner_type += 'o'
+      if kw.has_key('eu_vat') and kw['eu_vat'] not in (None, '') and owner_type.find('o') == -1:
+        owner_type += 'o'
+      if len(owner_type) == 0:
+        owner_type = None
+      LOG("Current customer structure >>>>>>>>",0,repr(owner_type))
+
+#       # TODO : in this part of the script, add the possibility to find an existing
+#       # ERP5 person/organisation according to the name of that person/organisation
+      # Compare the current representation of the member account with the previous one
+      if previous_owner_type != owner_type:
+        # There is difference between the two (previous and current) representation of the customer
+        # We have to manage the differences to create a unique customer representation
+        LOG("There is difference between previous and current >>>>>>>>",0,None)
+        if previous_owner_type == None:
+          # No previous customer found, create one
+          if owner_type.find('o') != -1:
+            organisation_folder.newContent ( portal_type = 'Organisation'
+                                           , id          = owner_id)
+            organisation_object = organisation_folder._getOb(owner_id)
+            LOG("new organisation created >>>>>>>>",0,repr(organisation_object))
+          if owner_type.find('p') != -1:
+            person_folder.newContent ( portal_type = 'Person'
+                                     , id          = owner_id)
+            person_object = person_folder._getOb(owner_id)
+            LOG("new person created >>>>>>>>",0,repr(person_object))
+        else:
+          if owner_type == None:
+            # Use the previous Structure
+            owner_type = previous_owner_type
+            LOG("Use the previous Structure >>>>>>>>",0,None)
+          else:
+            LOG("We have to convert the structure >>>>>>>>",0,None)
+#         #  XXX Be aware of that problem: the invoice for a sale order must be the same
+#           Case to process :
+#           previous current
+#           o  -->   p
+#           o  -->   op
+#           p  -->   o
+#           op -->   o
+#           op -->   p
+#           p  -->   op  - in progress
+
+            # The previous customer was detected as a person only
+            if previous_owner_type.find('p') != -1 and previous_owner_type.find('o') == -1:
+#             Case to process :
+#             previous current
+#             p  -->   o
+#             p  -->   op  - in progress
+              # The customer has now an organisation, we have to create this organisation and link the person to
+              if owner_type.find('p') != -1 and owner_type.find('o') != -1:
+                # Create a new organisation
+#                 # TODO : factorise this code with the same above
+                organisation_folder.newContent ( portal_type = 'Organisation'
+                                               , id          = owner_id)
+                organisation_object = organisation_folder._getOb(owner_id)
+              else:
+#                 # TODO : Transform a person to an organisation ? Is it a good idea ?
+                pass
+            # The previous customer was detected as an organisation only
+            elif previous_owner_type.find('p') == -1 and previous_owner_type.find('o') != -1:
+#             Case to process :
+#             previous current
+#             o  -->   p
+#             o  -->   op
+              pass
+            # The previous customer was detected as a person in an organisation
+            else:
+#             Case to process :
+#             previous current
+#             op -->   o
+#             op -->   p
+              pass
+      else:
+        if previous_owner_type == None or owner_type == None:
+          # There is not enough informations to know if the customer is an organisation or
+          # a person and there is no previous record
+          # By default, we consider the customer as a person, so we have to force to create one
+          owner_type = 'p'
+          person_folder.newContent ( portal_type = 'Person'
+                                   , id          = owner_id)
+          person_object = person_folder._getOb(owner_id)
+          LOG("Create a person by default  >>>>>>>>",0,repr(person_object))
+        else:
+          # The structure is the same
+          # We only need to be aware of data fusion between the previous and the current representation
+          # So we don't need to do something because the information fusion process take place below
+          LOG("The structure is the same. don't do anything >>>>>>>>",0,None)
+          pass
+
+      LOG("Person object >>>>>>>>",0,repr(person_object))
+      LOG("Organisation object >>>>>>>>",0,repr(organisation_object))
+
+      # Copy informations related to the customer in the ERP5 representation of the customer
+      # Be carefull because all informations from the storever ShopOrder are optionnals
+      if owner_type.find('p') != -1:
+        # Link the customer with the Sale Order
+        object.setDestination("person/" + owner_id)
+        object.setDestinationDecision("person/" + owner_id)
+
+#         # TODO : do the same things for each single information
+#         # TODO : before doing something working well in every case, copy the previou value in the comment field to traceback the modification and let me evaluate the solidity of my algorithm
+#         # TODO : perhaps it's possible to factorize the code using a generic function
+        # Synchronise the street address
+        if kw.has_key('address'):
+          previous_address = person_object.getDefaultAddressStreetAddress()
+          if len(previous_address) == 0:
+            person_object.setDefaultAddressStreetAddress(kw['address'].title())
+          elif previous_address.strip().lower() != kw['address'].strip().lower():
+            LOG('We have to make the fusion of previous address with the current one  >>>>>>>', 0, None)
+
+        person_object.setDefaultAddressCity(kw['city'].title())
+        person_object.setDefaultAddressZipCode(kw['zipcode'])
+#         # TODO : set the person products interest (storever, etc)
+        # Search the country in the region category
+        if kw['country'] != None:
+          region_path = self.countrySearch(erp5_site, None, kw['country'])
+          if region_path != None:
+            person_object.setDefaultAddressRegion(region_path)
+#           else:
+#             # TODO : Ask the user to select an appropriate region
+        person_object.setDefaultEmailText(kw['email'])
+        person_object.setDefaultTelephoneText(kw['phone'])
+#         # TODO : Don't work
+#         person_object.setDefaultCareerRole("client")
+        # Split the name to give at least a required LastName
+        # Then the title will be automaticaly created by the Person object from this data
+        if kw.has_key('name') and kw['name'] != None:
+          splitted_name = kw['name'].strip().split(" ")
+          person_object.setLastName((splitted_name[-1]).title())
+          if len(splitted_name) > 1:
+            person_object.setFirstName((" ".join(splitted_name[:-1])).title())
+        else:
+          # We have to find a title to have something to show in the RelationField of the SaleOrderForm
+          person_object.setTitle(owner_account_id.title())
+        # The Person is subordinated to an Organisation ?
+        if owner_type.find('o') != -1:
+#           # TODO : fix this
+#           person_object.setSubordination("organisation/" + owner_id)
+          organisation_object.setTitle(kw['organisation'].title())
+          organisation_object.setCorporateName(kw['organisation'].title())
+          organisation_object.setRole("client")
+          if kw.has_key('eu_vat'):
+            organisation_object.setEuVatCode(kw['eu_vat'])
+      # The customer is not a person or a person of an organisation, so the customer is an organisation...
+      else:
+        # Link the customer with the Sale Order
+        object.setDestination("organisation/" + owner_id)
+        object.setDestinationDecision("organisation/" + owner_id)
+        # All informations describe the organisation
+        organisation_object.setTitle(kw['organisation'].title())
+        organisation_object.setCorporateName(kw['organisation'].title())
+        organisation_object.setRole("client")
+        organisation_object.setEuVatCode(kw['eu_vat'])
+        organisation_object.setDefaultAddressStreetAddress(kw['address'].title())
+        organisation_object.setDefaultAddressCity(kw['city'].title())
+        organisation_object.setDefaultAddressZipCode(kw['zipcode'])
+        # Search the country in the region category
+        if kw['country'] != None:
+          region_path = self.countrySearch(erp5_site, None, kw['country'])
+          if region_path != None:
+            organisation_object.setDefaultAddressRegion(region_path)
+#           else:
+#             # TODO : Ask the user to select an appropriate region
+        organisation_object.setDefaultEmailText(kw['email'])
+        organisation_object.setDefaultTelephoneText(kw['phone'])
+
+      # Save the billing address in the description, because there is no dedicated place for it
+      if kw.has_key('billing_address') and len(kw['billing_address']) > 0:
+        object.setDescription("Send the bill to : " + kw['billing_address'])
+      # Set the Title because its required
+      object.setTitle("Storever Order " + kw['order_id'])
+
+#       # ONLY for information (will be used in the future)
+      object.setDescription(str(object.getDescription()) + "\n\nTotal Price (with transport fees) :" + str(kw['total_price']))
+
+      # Add a new orderLine for the shipment
+      stor_ship_title = kw['send_fee_title'].strip()
+      erp5_ship_title = stor_ship_title + ' Shipment'
+      my_shipment_id = 'storever-' + self.str2id(stor_ship_title)
+
+      # Try to find an existing shipment service using several methods
+      shipment_id = None
+      for service_id in service_folder.objectIds():
+        service_object = erp5_site.restrictedTraverse(erp5_site_path + '/service/' + service_id)
+        # First method: compare the id with my standard layout
+        if service_id.strip() == my_shipment_id:
+          shipment_id = my_shipment_id
+          LOG("Service found with method 1 ! >>>>>>>>", 0, repr(shipment_id))
+          break
+        # Second method: use a standard title layout
+        if service_object.getTitle().lower().strip() == erp5_ship_title.lower().strip():
+          shipment_id = service_id
+          LOG("Service found with method 2 ! >>>>>>>>", 0, repr(shipment_id))
+          break
+        # Third method: compare words
+        erp5_ship_id_word_list = self.str2id(service_id).split("_")
+        stor_ship_id_word_list = self.str2id(stor_ship_title).split("_")
+        erp5_ship_title_word_list = self.str2id(erp5_ship_title).split("_")
+        erp5_ship_id_word_list.sort(lambda x, y: cmp(str(x), str(y)))
+        stor_ship_id_word_list.sort(lambda x, y: cmp(str(x), str(y)))
+        erp5_ship_title_word_list.sort(lambda x, y: cmp(str(x), str(y)))
+        if stor_ship_id_word_list in (erp5_ship_id_word_list, erp5_ship_title_word_list):
+          shipment_id = service_id
+          LOG("Service found with method 3 ! >>>>>>>>", 0, repr(shipment_id))
+          break
+
+      # No previous shipment service found, so create a new one
+      if shipment_id == None:
+#         TODO : implement the code here to follow the comment in the LOG below
+        LOG("We have to create the shipping service with this id >>>>>>>>", 0, repr(my_shipment_id))
+        # Create a new shipment service
+        shipment_id = my_shipment_id
+
+      # Get the object of the shipment service
+#       shipment_path = erp5_site_path + '/service/' + shipment_id
+#       shipment_object = erp5_site.restrictedTraverse(shipment_path)
+
+      # Create a new order line in this order to represent the shipment service
+      ship_order_line_id = "storever-" + shipment_id
+      object.newContent( portal_type = 'Sale Order Line'
+                       , id          = ship_order_line_id)
+      ship_order_object = object._getOb(ship_order_line_id)
+      ship_order_object.setQuantity(1.0)
+      ship_order_object.setPrice(kw['send_fee'])
+      ship_order_object.setQuantityUnit('Unit')
+      ship_order_object.setResource("service/" + shipment_id)
+
+
+
+
+
+
+
+
+
+
+    # The object is an OrderLine
+    else:
+      # Find the product folder
+      product_path = erp5_site_path + '/product'
+      product_folder = erp5_site.restrictedTraverse(product_path)
+
+      # Find the parent order object
+      parent_order_object = object.aq_parent
+
+      # Get the id of the product in storever
+      storever_product_id = kw['product_id']
+
+      # Set the id of the product in ERP5
+      erp5_product_id = "storever-" + storever_product_id
+
+      # Try to find a previous product or create a new one
+      product_object = self.createOrFindProduct(erp5_site, erp5_product_id)
+
+      # Create a nice title (without discontinued) from the product title
+      product_title = self.niceTitle(kw['product_title'])
+
+      # Synchronise every data
+      product_object.setDescription(kw['product_description'])
+      product_object.setTitle(product_title)
+#       # TODO : I don't know where to put this value,
+#       #   because there is no "delivery days"-like property for a product
+#       product_object.setDeliveryDays(kw['product_delivery_days'])
+      if kw['product_expiration_date'] != None:
+        product_object.setSourceBasePriceValidity(kw['product_expiration_date'])
+      product_object.setBasePrice(kw['product_price'])
+      product_object.setQuantityUnit('Unit')
+
+      # Set the worflow status
+      self.setProductWorkflow(product_object, kw['product_title'])
+
+      # In storever, every option are set as string in the title of the OrderLine
+      # This part of code create a list of all options choosen by the customer for this product
+      splitted_title = kw['title'].strip().split(":")
+      option_list = (":".join(splitted_title[1:])).split("/")
+      LOG('Customer option list: ', 0, repr(option_list))
+
+      # Now, we will find the price of each option
+      option_classes = [ kw['product_disk_price']
+                       , kw['product_memory_price']
+                       , kw['product_option_price']
+                       , kw['product_processor_price']
+                       ]
+      priced_list = {}
+      for option_item in option_list:
+        option = option_item.strip()
+        for option_class in option_classes:
+          for option_key in option_class.keys():
+            if option == option_key.strip():
+              priced_list[option] = option_class[option_key]
+#       # TODO : there is no default options in the final priced_list. Is the option 'default' important ?
+      LOG('Customer option priced list: ', 0, repr(priced_list))
+
+      # In ERP5, we have decided to represent some options as variation of a product
+      #   and some options as new order line of product
+      # Now we will update or create the variation categories related to the initial product
+      # Don't forget to add this base categories in portal_category :
+      #   'hd_size', 'memory_size', 'optical_drive', 'keyboad_layout', 'cpu_type'
+      portal_cat = product_object.portal_categories
+
+      # Get all keyboard related options and all optical drive related options
+      keyboard_options = {}
+      optical_options = {}
+      options_prices = kw['product_option_price']
+      for option_key in options_prices.keys():
+        if option_key.lower().find("keyboard") != -1:
+          keyboard_options[option_key] = options_prices[option_key]
+        elif option_key.lower().find("cd") != -1 or option_key.lower().find("dvd") != -1:
+          optical_options[option_key] = options_prices[option_key]
+      LOG('Product keyboard layout priced list: ', 0, repr(keyboard_options))
+      LOG('Product optical drive priced list: ', 0, repr(optical_options))
+
+      # Create a data structure containing all allowed variations
+      variant_category_list = [ ('hd_size',        kw['product_disk_price'])
+                              , ('memory_size',    kw['product_memory_price'])
+                              , ('cpu_type',       kw['product_processor_price'])
+                              , ('optical_drive',  optical_options)
+                              , ('keyboad_layout', keyboard_options)]
+      # Create or update every category representing all variantions
+      base_cat_list = []
+      cat_list = []
+      for (cat_base, cat_data) in variant_category_list:
+        if len(cat_data) > 0 and portal_cat.resolveCategory(cat_base) != None:
+          base_cat_list.append(cat_base)
+          for disk_variant_key in cat_data.keys():
+            cat_id = self.str2id(disk_variant_key)
+            cat_path = cat_base + '/' + cat_id
+            cat_list.append(cat_path)
+            if portal_cat.resolveCategory(cat_path) == None:
+              cat_base_object = portal_cat._getOb(cat_base)
+              cat_base_object.newContent ( portal_type = 'Category'
+                                         , id          = cat_id)
+              LOG("New category '", 0, cat_path + "' created")
+
+      # Set the base variation of the product
+      product_object.setVariationBaseCategoryList(base_cat_list)
+
+      # Set the variation range of the product
+      product_object.setVariationCategoryList(cat_list)
+
+      LOG("cat_list >>>>>>>>>>", 0, repr(cat_list))
+
+      # Now we seperate options and variations of the initial product ordered by the customer
+      customer_product_option_list = {}
+      customer_product_variation_list = {}
+      customer_product_base_variation_list = []
+      for option in priced_list:
+        option_is_variant = None
+        for (cat_base, cat_data) in variant_category_list:
+          base_cat_object = portal_cat.resolveCategory(cat_base)
+          cat_list = base_cat_object.getCategoryChildIdItemList()
+          for (category, category_bis) in cat_list:
+            if self.str2id(option) == category:
+              customer_product_variation_list[category] = cat_base + '/' + category
+              if cat_base not in customer_product_base_variation_list:
+                customer_product_base_variation_list.append(cat_base)
+              option_is_variant = 1
+              break
+          if option_is_variant == 1:
+            break
+        if option_is_variant == None:
+          customer_product_option_list[option] = priced_list[option]
+      if len(customer_product_option_list) + len(customer_product_variation_list) != len(priced_list):
+        LOG('Wrong repartition of the customer priced list', 200)
+      LOG('>>>>>> Customer product option priced list: ', 0, repr(customer_product_option_list))
+      LOG('>>>>>> Customer product variation priced list: ', 0, repr(customer_product_variation_list))
+      LOG('>>>>>> Customer product base variation list: ', 0, repr(customer_product_base_variation_list))
+
+      # This variable repesent the sum of every option prices
+      options_price_sum = 0.0
+
+      # We have to create a new product for every option not included in the variation system
+      for opt_prod_key in customer_product_option_list.keys():
+        opt_prod_price = customer_product_option_list[opt_prod_key]
+
+        # Set the id of the optionnal product
+        opt_prod_key = self.str2id(opt_prod_key)
+        opt_prod_id = "storever-" + opt_prod_key
+
+        # Create the optionnal product or get it if it already exist
+        opt_prod_object = self.createOrFindProduct(erp5_site, opt_prod_id)
+
+        # Remove the "discontinued" string in the title
+        opt_prod_title = self.niceTitle(opt_prod_key)
+        # Set some properties of the optionnal product
+        opt_prod_object.setTitle(opt_prod_title.title())
+        opt_prod_object.setBasePrice(opt_prod_price)
+        opt_prod_object.setQuantityUnit('Unit')
+        # Set the workflow state of the optionnal product
+        self.setProductWorkflow(opt_prod_object, opt_prod_key)
+
+        # Get the last number of order lines
+        # This process is needed to distinguish the same option created for two different product
+        #   and avoid problem when a new Order line is created for a option product already used
+        #   inside the same Sale Order
+        last_line_num = self.getLastOrderLineNumber(parent_order_object)
+        opt_prod_line_id = "storever-" + str(last_line_num) + "-" + opt_prod_key
+        # Create an order line for the product
+        parent_order_object.newContent ( portal_type = 'Sale Order Line'
+                                       , id          = opt_prod_line_id)
+        opt_order_line_object = parent_order_object._getOb(opt_prod_line_id)
+
+        # Set several properties of the new orderLine
+        opt_order_line_object.setQuantityUnit('Unit')
+        opt_order_line_object.setPrice(opt_prod_price)
+        # There is the same quantity of the base product
+        opt_order_line_object.setQuantity(kw['quantity'])
+        # Link the Order Line with the product
+        opt_order_line_object.setResource("product/" + opt_prod_id)
+
+        # Calcul the sum of option prices
+        options_price_sum += float(opt_prod_price)
+
+
+
+
+
+
+
+
+#       # TODO: don't forget to manage the VAT values
+
+#      TODO: # Try to find a previous OrderLine to update
+#       line_object = None
+#       for product_id in product_folder.objectIds():
+#         if product_id == erp5_product_id:
+#           product_object = erp5_site.restrictedTraverse(erp5_site_path + '/product/' + erp5_product_id)
+#           break
+
+      # Migrate the line informations
+      object.setQuantity(kw['quantity'])
+      object.setDescription(kw['title'])
+      object.setQuantityUnit('Unit')
+
+      # Substract to the product price the sum of options prices
+      initial_prod_price = float(kw['price']) - options_price_sum
+      object.setPrice(initial_prod_price)
+
+      # Link the Order Line with the product
+      object.setResource("product/" + erp5_product_id)
+
+      # Set variations of the order line product choosen by the customer
+      category_list = []
+      for variation_key in customer_product_variation_list.keys():
+        category_list.append(customer_product_variation_list[variation_key])
+      object.setVariationBaseCategoryList(customer_product_base_variation_list)
+#       # TODO : fix this
+      # object.setVariationCategoryList(category_list)
+
+      return
\ No newline at end of file