From aa0db4fec71afc248f14dc58a179f19c8e585f13 Mon Sep 17 00:00:00 2001 From: Kevin Deldycke <kevin@nexedi.com> Date: Tue, 15 Mar 2005 13:58:25 +0000 Subject: [PATCH] initial import git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@2713 20353a03-c40f-0410-a6d1-a30d3c3de9de --- product/ERP5SyncML/Conduit/BaobabConduit.py | 439 ++++++++++++++++++++ 1 file changed, 439 insertions(+) create mode 100755 product/ERP5SyncML/Conduit/BaobabConduit.py diff --git a/product/ERP5SyncML/Conduit/BaobabConduit.py b/product/ERP5SyncML/Conduit/BaobabConduit.py new file mode 100755 index 0000000000..16e883422d --- /dev/null +++ b/product/ERP5SyncML/Conduit/BaobabConduit.py @@ -0,0 +1,439 @@ +############################################################################## +# +# Copyright (c) 2005 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.ERP5Type.Utils import convertToUpperCase +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 +import datetime +from cStringIO import StringIO + +from zLOG import LOG + + + +class BaobabConduit(ERP5Conduit): + + global property_map + + # Declarative security + security = ClassSecurityInfo() + + + # This data structure associate a xml property to an ERP5 object property in certain conditions + property_map = \ + [ { 'xml_property' : 'nom' + , 'erp5_property': 'first_name' + , 'conditions' : {'erp5_portal_type':'Person'} + } + , { 'xml_property' : 'nom' + , 'erp5_property': 'title' + , 'conditions' : {'erp5_portal_type':'Organisation'} + } + , { 'xml_property' : 'adresse' + , 'erp5_property': 'default_address_street_address' + , 'conditions' : [{'erp5_portal_type':'Organisation'} + ,{'erp5_portal_type':'Person'}] + } + , { 'xml_property' : 'zone_residence' + , 'erp5_property': 'default_address_region' + , 'conditions' : [{'erp5_portal_type':'Organisation'} + ,{'erp5_portal_type':'Person'}] + } + , { 'xml_property' : 'titre' + , 'erp5_property': 'prefix' + , 'conditions' : {'erp5_portal_type':'Person'} + } + , { 'xml_property' : 'telephone' + , 'erp5_property': 'default_telephone_number' + , 'conditions' : [{'erp5_portal_type':'Organisation'} + ,{'erp5_portal_type':'Person'}] + } + , { 'xml_property' : 'telex' + , 'erp5_property': 'default_fax_number' + , 'conditions' : [{'erp5_portal_type':'Organisation'} + ,{'erp5_portal_type':'Person'}] + } + , { 'xml_property' : 'prenom' + , 'erp5_property': 'last_name' + , 'conditions' : {'erp5_portal_type':'Person'} + } + , { 'xml_property' : 'date_naissance' + , 'erp5_property': 'birthday' + , 'conditions' : {'erp5_portal_type':'Person'} + } + , { 'xml_property' : 'code_bic' + , 'erp5_property': 'bic_code' + , 'conditions' : {'erp5_portal_type':'Organisation'} + } + , { 'xml_property' : 'intitule' + , 'erp5_property': 'title' + , 'conditions' : {'erp5_portal_type':'Bank Account'} + } + , { 'xml_property' : 'montant_maxi' + , 'erp5_property': 'operation_upper_limit' + , 'conditions' : {'erp5_portal_type':'Agent Privilege'} + } + , { 'xml_property' : 'description' + , 'erp5_property': 'description' + , 'conditions' : {'erp5_portal_type':'Agent Privilege'} + } + ] + + """ + Methods below are tools to use the property_map. + """ + + security.declarePrivate('buildConditions') + def buildConditions(self, object): + """ + Build a condition dictionnary + """ + dict = {} + dict['erp5_portal_type'] = object.getPortalType() + return dict + + security.declarePrivate('findPropertyMapItem') + def findPropertyMapItem(self, xml_property_name, conditions): + """ + Find the property_map item that match conditions + """ + for item in property_map: + if item['xml_property'] == xml_property_name: + c = item['conditions'] + if type(c) == type([]): + if conditions in c: + return item + else: + if conditions == c: + return item + return None + + + + 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 create Baobab objects + """ + erp5_site_path = object.absolute_url(relative=1) + person_folder = object.restrictedTraverse(erp5_site_path + '/person') + organisation_folder = object.restrictedTraverse(erp5_site_path + '/organisation') + + subobject = None + + # Function to search the parent object where the new content must be construct + # Given parameter is the special encoded portal type that represent the path to the wanted destination + def findObjectFromSpecialPortalType(special_portal_type): + source_portal_type = special_portal_type.split('_')[0] + construction_location = '/'.join(special_portal_type.split('_')[1:][::-1]) + parent_object = None + for search_folder in ('person', 'organisation'): + path = '/' + search_folder + '/' + construction_location + try: + parent_object = object.restrictedTraverse(erp5_site_path + path) + except: + LOG('BaobabConduit:',100, "parent object of '%s' not found in %s" % (source_portal_type, erp5_site_path + path)) + if parent_object == None: + LOG('BaobabConduit:',100, "parent object of '%s' not found !" % (source_portal_type)) + else: + LOG('BaobabConduit:',0,"parent object of '%s' found (%s)" % (source_portal_type, repr(parent_object))) + return parent_object + + # handle client objects + if portal_type.startswith('Client'): + if portal_type[-3:] == 'PER': + subobject = person_folder.newContent( portal_type = 'Person' + , id = object_id) + subobject.setCareerRole('client') + else: + subobject = organisation_folder.newContent( portal_type = 'Organisation' + , id = object_id) + subobject.setRole('client') + + # handle bank account objects + elif portal_type.startswith('Compte'): + owner = findObjectFromSpecialPortalType(portal_type) + if owner == None: return None + subobject = owner.newContent( portal_type = 'Bank Account' + , id = object_id) + # set the bank account owner as agent with no-limit privileges (only for persons) + if owner.getPortalType() == 'Person': + new_agent = subobject.newContent( portal_type = 'Agent' + , id = 'owner') + new_agent.setAgent(owner.getRelativeUrl()) + privileges = ( 'circularization' + , 'cash_out' + , 'withdrawal_and_payment' + , 'account_document_view' + , 'signature' + , 'treasury' + ) + for privilege in privileges: + new_priv = new_agent.newContent(portal_type = 'Agent Privilege') + new_priv.setAgentPrivilege(privilege) + + # handle agent objects + elif portal_type.startswith('Mandataire'): + dest = findObjectFromSpecialPortalType(portal_type) + if dest == None: return None + subobject = dest.newContent( portal_type = 'Agent' + , id = object_id) + # try to get the agent in the person module + person = findObjectFromSpecialPortalType('Person_' + object_id) + if person == None: + person = person_folder.newContent( portal_type = 'Person' + , id = object_id + 'a') + subobject.setAgent(person.getRelativeUrl()) + + # handle privilege objects + elif portal_type.startswith('Pouvoir'): + dest = findObjectFromSpecialPortalType(portal_type) + if dest == None: return None + subobject = dest.newContent( portal_type = 'Agent Privilege' + , id = object_id) + + return subobject + + + + # EXPERIMENTAL +# security.declareProtected(Permissions.ModifyPortalContent, 'getProperty') +# def getProperty(self, object, kw): +# """ +# This is the default getProperty method. This method +# can easily be overwritten. +# """ +# # Try to find a translation rule in the property_map +# cond = self.buildConditions(object) +# map_item = self.findPropertyMapItem(kw, cond) +# if map_item != None: +# method_id = "get" + convertToUpperCase(map_item['erp5_property']) +# LOG('BaobabConduit:',0,"try to call object method %s on %s" % (repr(method_id), repr(object))) +# if v not in ('', None): +# if hasattr(object, method_id): +# method = getattr(object, method_id) +# method(v) +# else: +# LOG('BaobabConduit:',100,'property map item don\'t match object properties') +# return object.getProperty(kw) + + + + security.declareProtected(Permissions.ModifyPortalContent, 'editDocument') + def editDocument(self, object=None, **kw): + """ + This function transfer datas from the dictionary to the baobab document object given in parameters + """ + + # EXPERIMENTAL + # This message help to track in the log when an object is edited + # It permit us to verify the consistency of a synchronisation process : + # 1- Launch a normal synchronisation. + # 2- When the syncho process is finished, launch (without reseting, it's important) an other synchronisation, the same way as above. + # 3- Monitor the zope log. + # 4- If the message log below appear it mean that a client piece of data is missing or wrong comparing to the master. + # In other words, it mean that the first synchronisation process didn't ensure the integrity of data. + #LOG('BaobabConduit:',0, "An object need to be edited") + + if object == None: return + + # Set properties of the destination baobab object + for k,v in kw.items(): + # Try to find a translation rule in the property_map + cond = self.buildConditions(object) + map_item = self.findPropertyMapItem(k, cond) + # No translation rule found, try to find a hard-coded translation method in the conduit + if map_item == None: + method_id = "edit%s%s" % (kw['type'], convertToUpperCase(k)) + LOG('BaobabConduit:',0,"try to call conduit method %s on %s" % (repr(method_id), repr(object))) + if v not in ('', None): + if hasattr(self, method_id): + method = getattr(self, method_id) + method(object, v) + else: + LOG('BaobabConduit:',100,"there is no method to handle <%s>%s</%s> data" % (k,repr(v),k)) + + # There is a translation rule, so call the right setProperty() method + else: + method_id = "set" + convertToUpperCase(map_item['erp5_property']) + LOG('BaobabConduit:',0,"try to call object method %s on %s" % (repr(method_id), repr(object))) + if v not in ('', None): + if hasattr(object, method_id): + method = getattr(object, method_id) + method(v) + else: + LOG('BaobabConduit:',100,'property map item don\'t match object properties') + + + + """ + All functions below are defined to set a document's property to a value given in parameters. + The name of those functions are chosen to help the transfert of datas from a given XML format to standard Baobab objects. + """ + + # Client-related-properties functions + def editClientCategorie(self, document, value): + if document.getPortalType() == 'Organisation': + id_table = { 'BIF': 'institution/world/bank' + , 'PFR': 'institution/world/institution' + , 'ICU': 'institution/local/common' + , 'BET': 'institution/local/institution' + , 'ETF': 'institution/local/bank' + , 'BTR': 'treasury/national' + , 'ORP': 'treasury/other' + , 'ORI': 'organism/international' + , 'ORR': 'organism/local' + , 'COR': 'intermediaries' + , 'DIV': 'depositories/various' + , 'DER': 'depositories/savings' + , 'DAU': 'depositories/other' + } + document.setActivity('banking_finance/' + id_table[value]) + else: + LOG('BaobabConduit:',0,'Person\'s category ignored') + + def editClientNatureEconomique(self, document, value): + if document.getPortalType() == 'Organisation': + # build the economical class category path + c = '' + path = '' + for i in value[1:]: + c += i + if c == '13': + path += '/S13' + if value != 'S13': + path += '/' + value + break + path += '/S' + c + document.setEconomicalClass(path) + else: + LOG('BaobabConduit inconsistency:',200,'a non-Organisation client can\'t have an economical class') + + def editClientSituationMatrimoniale(self, document, value): + if document.getPortalType() == 'Person': + id_table = { 'VEU' : 'widowed' + , 'DIV' : 'divorced' + , 'MAR' : 'married' + , 'CEL' : 'never_married' + } + document.setMaritalStatus(id_table[value]) + else: + LOG('BaobabConduit inconsistency:',200,'a non-Person client can\'t have a marital status') + + + + # BankAccount-related-properties functions + def editCompteDevise(self, document, value): + document.setPriceCurrency('currency/' + value) + + def editCompteDateOuverture(self, document, value): + if document.getStopDate() in ('', None): + document.setStopDate(str(datetime.datetime.max)) + document.setStartDate(value) + + def editCompteDateFermeture(self, document, value): + if document.getStartDate() in ('', None): + document.setStartDate(str(datetime.datetime.min)) + document.setStopDate(value) + + def editCompteNumero(self, document, value): + document.setBankCode(value[0]) + document.setBranch(value[1:3]) + document.setBankAccountNumber(value) + + + + # Agent-related-properties functions + def editMandataireNom(self, document, value): + old_value = document.getAgentValue().getFirstName() + new_value = value + if old_value != new_value: + LOG('BaobabConduit:',200,'old value of agent first name (%s) was replaced by a new one (%s)' % (old_value, new_value)) + document.getAgentValue().setFirstName(new_value) + + def editMandatairePrenom(self, document, value): + old_value = document.getAgentValue().getLastName() + new_value = value + if old_value != new_value: + LOG('BaobabConduit:',200,'old value of agent last name (%s) was replaced by a new one (%s)' % (old_value, new_value)) + document.getAgentValue().setLastName(new_value) + + def editMandataireService(self, document, value): + assignment = document.getAgentValue().newContent( portal_type = 'Assignment' + , id = 'service') + assignment.setGroup(value) + return + + def editMandataireFonction(self, document, value): + document.getAgentValue().setDefaultCareerGrade(value) + return + + def editMandataireTelephone(self, document, value): + old_value = document.getAgentValue().getDefaultTelephoneNumber() + new_value = value + if old_value != new_value: + LOG('BaobabConduit:',200,"old value of agent's telephone (%s) was replaced by a new one (%s)" % (old_value, new_value)) + document.getAgentValue().setDefaultTelephoneNumber(new_value) + + def editMandataireDateCreation(self, document, value): + if document.getStopDate() in ('', None): + document.setStopDate(str(datetime.datetime.max)) + document.setStartDate(value) + + + + # AgentPrivilege-related-properties functions + def editPouvoirCategorie(self, document, value): + id_table = { 'COM' : 'clearing' + , 'CIR' : 'circularization' + , 'REM' : 'cash_out' + , 'RET' : 'withdrawal_and_payment' + , 'RTE' : 'account_document_view' + , 'SIG' : 'signature' + , 'TRE' : 'treasury' + } + document.setAgentPrivilege(id_table[value]) + + def editPouvoirDateDebut(self, document, value): + if document.getStopDate() in ('', None): + document.setStopDate(str(datetime.datetime.max)) + document.setStartDate(value) + + def editPouvoirDateFin(self, document, value): + if document.getStartDate() in ('', None): + document.setStartDate(str(datetime.datetime.min)) + document.setStopDate(value) \ No newline at end of file -- GitLab