← Back to team overview

openerp-dev-web team mailing list archive

[Merge] lp:~xrg/openobject-addons/trunk-patch18 into lp:~openerp-dev/openobject-addons/trunk-dev-addons1

 

xrg has proposed merging lp:~xrg/openobject-addons/trunk-patch18 into lp:~openerp-dev/openobject-addons/trunk-dev-addons1.

Requested reviews:
  OpenERP R&D Team (openerp-dev)

For more details, see:
https://code.launchpad.net/~xrg/openobject-addons/trunk-patch18/+merge/44589

You can merge this on top of, or instead of "patch17".
-- 
https://code.launchpad.net/~xrg/openobject-addons/trunk-patch18/+merge/44589
Your team OpenERP R&D Team is requested to review the proposed merge of lp:~xrg/openobject-addons/trunk-patch18 into lp:~openerp-dev/openobject-addons/trunk-dev-addons1.
=== modified file 'base_iban/base_iban.py'
--- base_iban/base_iban.py	2010-12-06 13:11:02 +0000
+++ base_iban/base_iban.py	2010-12-23 16:04:51 +0000
@@ -117,7 +117,7 @@
         iban_country = self.browse(cr, uid, ids)[0].iban[:2]
         if default_iban_check(iban_country):
             iban_example = iban_country in _ref_iban and _ref_iban[iban_country] + ' \nWhere A = Account number, B = National bank code, S = Branch code, C = account No, N = branch No, K = National check digits....' or ''
-            return _('The IBAN does not seems to be correct. You should have entered something like this %s'), (iban_example)
+            return _('The IBAN does not seem to be correct. You should have entered something like this %s'), (iban_example)
         return _('The IBAN is invalid, It should begin with the country code'), ()
 
     def name_get(self, cr, uid, ids, context=None):

=== modified file 'caldav/caldav_view.xml'
--- caldav/caldav_view.xml	2010-11-18 05:21:19 +0000
+++ caldav/caldav_view.xml	2010-12-23 16:04:51 +0000
@@ -168,6 +168,7 @@
             <field name="res_model">basic.calendar</field>
             <field name="view_type">form</field>
             <field name="view_mode">tree,form</field>
+            <field name="help">"Calendars" allow you to Customize calendar event and todo attribute with any of OpenERP model.Caledars provide iCal Import/Export functionality.Webdav server that provides remote access to calendar.Help You to synchronize Meeting with Calendars client.You can access Calendars using CalDAV clients, like sunbird, Calendar Evaluation, Mobile.</field>
         </record>
 
         <record id="action_caldav_view1" model="ir.actions.act_window.view">

=== modified file 'caldav/calendar.py'
--- caldav/calendar.py	2010-12-20 14:02:20 +0000
+++ caldav/calendar.py	2010-12-23 16:04:51 +0000
@@ -164,6 +164,8 @@
         res[attr] = {}
         res[attr]['field'] = field.field_id.name
         res[attr]['type'] = field.field_id.ttype
+        if field.fn == 'datetime_utc':
+            res[attr]['type'] = 'utc'
         if field.fn == 'hours':
             res[attr]['type'] = "timedelta"
         if res[attr]['type'] in ('one2many', 'many2many', 'many2one'):
@@ -254,7 +256,6 @@
          @param name: Get Attribute Name
          @param type: Get Attribute Type
         """
-
         if self.__attribute__.get(name):
             val = self.__attribute__.get(name).get(type, None)
             valtype =  self.__attribute__.get(name).get('type', None)
@@ -273,7 +274,6 @@
          @param self: The object pointer,
          @param type: Get Attribute Type
         """
-
         for name in self.__attribute__:
             if self.__attribute__[name]:
                 self.__attribute__[name][type] = None
@@ -446,6 +446,21 @@
                                 dtfield.value = self.format_date_tz(parser.parse(data[map_field]), tzval.title())
                             else:
                                 dtfield.value = parser.parse(data[map_field])
+                                
+                        elif map_type == 'utc'and data[map_field]:
+                            if tzval:
+                                local = pytz.timezone (tzval.title())
+                                naive = datetime.strptime (data[map_field], "%Y-%m-%d %H:%M:%S")
+                                local_dt = naive.replace (tzinfo = local)
+                                utc_dt = local_dt.astimezone (pytz.utc)
+                                vevent.add(field).value = utc_dt
+                            else:
+                               utc_timezone = pytz.timezone ('UTC')
+                               naive = datetime.strptime (data[map_field], "%Y-%m-%d %H:%M:%S")
+                               local_dt = naive.replace (tzinfo = utc_timezone)
+                               utc_dt = local_dt.astimezone (pytz.utc)
+                               vevent.add(field).value = utc_dt
+
                         elif map_type == "timedelta":
                             vevent.add(field).value = timedelta(hours=data[map_field])
                         elif map_type == "many2one":
@@ -829,6 +844,7 @@
         'fn': fields.selection([('field', 'Use the field'),
                         ('const', 'Expression as constant'),
                         ('hours', 'Interval in hours'),
+                        ('datetime_utc', 'Datetime In UTC'),
                         ], 'Function'),
         'mapping': fields.text('Mapping'),
     }

=== modified file 'crm_caldav/crm_caldav_data.xml'
--- crm_caldav/crm_caldav_data.xml	2010-11-24 04:53:34 +0000
+++ crm_caldav/crm_caldav_data.xml	2010-12-23 16:04:51 +0000
@@ -117,8 +117,8 @@
         <record model="basic.calendar.fields" id="map_event_13">
             <field name="name" ref="caldav.field_event_dtstamp"/>
             <field name="type_id" ref="base_calendar.calendar_lines_event" />
-            <field name="field_id" search="[('name','=','date'),('model_id.model','=','calendar.event')]" />
-            <field name="fn">field</field>
+            <field name="field_id" search="[('name','=','write_date'),('model_id.model','=','crm.meeting')]" />
+            <field name="fn">datetime_utc</field>
         </record>
 
         <record model="basic.calendar.fields" id="map_event_14">

=== modified file 'crm_caldav/crm_caldav_setup.xml'
--- crm_caldav/crm_caldav_setup.xml	2010-11-22 11:42:07 +0000
+++ crm_caldav/crm_caldav_setup.xml	2010-12-23 16:04:51 +0000
@@ -176,8 +176,8 @@
     <record id="basic_calendar_fields_24" model="basic.calendar.fields">
         <field name="name" ref="caldav.field_event_dtstamp"/>
         <field name="type_id" ref="basic_calendar_lines_vevent0"/>
-        <field name="field_id" ref="base_calendar.field_calendar_event_date"/>
-        <field name="fn">field</field>
+        <field name="field_id" ref="crm.field_crm_meeting_write_date"/>
+        <field name="fn">datetime_utc</field>
     </record>
     <record id="basic_calendar_fields_25" model="basic.calendar.fields">
         <field name="name" ref="caldav.field_event_description"/>

=== modified file 'crm_claim/report/crm_claim_report.py'
--- crm_claim/report/crm_claim_report.py	2010-12-09 12:56:35 +0000
+++ crm_claim/report/crm_claim_report.py	2010-12-23 16:04:51 +0000
@@ -60,12 +60,14 @@
                                   ('11', 'November'), ('12', 'December')], 'Month', readonly=True),
         'company_id': fields.many2one('res.company', 'Company', readonly=True),
         'create_date': fields.datetime('Create Date', readonly=True),
+        'email': fields.integer('# Emails', size=128, readonly=True),
         'day': fields.char('Day', size=128, readonly=True), 
         'delay_close': fields.float('Delay to close', digits=(16,2),readonly=True, group_operator="avg",help="Number of Days to close the case"),
         'stage_id': fields.many2one ('crm.case.stage', 'Stage', readonly=True, domain="[('type','=','claim')]"),
         'categ_id': fields.many2one('crm.case.categ', 'Category',\
                          domain="[('section_id','=',section_id),\
                         ('object_id.model', '=', 'crm.claim')]", readonly=True),
+        'probability': fields.float('Probability',digits=(16,2),readonly=True, group_operator="avg"),
         'partner_id': fields.many2one('res.partner', 'Partner', readonly=True),
         'company_id': fields.many2one('res.company', 'Company', readonly=True),
         'priority': fields.selection(AVAILABLE_PRIORITIES, 'Priority'),
@@ -101,6 +103,7 @@
                     count(*) as nbr,
                     c.priority as priority,
                     c.type_action as type_action,
+                    (SELECT count(id) FROM mailgate_message WHERE model='crm.claim' AND res_id=id AND history=True) AS email,
                     date_trunc('day',c.create_date) as create_date,
                     avg(extract('epoch' from (c.date_closed-c.create_date)))/(3600*24) as  delay_close,
                     extract('epoch' from (c.date_deadline - c.date_closed))/(3600*24) as  delay_expected

=== modified file 'crm_claim/report/crm_claim_report_view.xml'
--- crm_claim/report/crm_claim_report_view.xml	2010-12-09 12:56:35 +0000
+++ crm_claim/report/crm_claim_report_view.xml	2010-12-23 16:04:51 +0000
@@ -18,6 +18,7 @@
                     <field name="partner_id" invisible="1"/>
                     <field name="day" invisible="1"/>
                     <field name="nbr" string="#Claim" sum="#Claim"/>
+                    <field name="email" sum="# Mails"/>
                     <field name="delay_close" avg="Avg Closing Delay"/>
                     <field name="delay_expected"/>
                     <field name="state" invisible="1"/>

=== modified file 'document/document.py'
--- document/document.py	2010-12-06 13:11:02 +0000
+++ document/document.py	2010-12-23 16:04:51 +0000
@@ -35,6 +35,36 @@
 class document_file(osv.osv):
     _inherit = 'ir.attachment'
     _rec_name = 'datas_fname'
+
+    def _attach_parent_id(self, cr, uid, ids=None, context=None):
+        """Migrate ir.attachments to the document module.
+        
+        When the 'document' module is loaded on a db that has had plain attachments,
+        they will need to be attached to some parent folder, and be converted from 
+        base64-in-bytea to raw-in-bytea format.
+        This function performs the internal migration, once and forever, for these
+        attachments. It cannot be done through the nominal ORM maintenance code,
+        because the root folder is only created after the document_data.xml file
+        is loaded.
+        It also establishes the parent_id NOT NULL constraint that ir.attachment
+        should have had (but would have failed if plain attachments contained null
+        values).
+        """
+        
+        parent_id = self.pool.get('document.directory')._get_root_directory(cr,uid)
+        if not parent_id:
+            logging.getLogger('document').warning("at _attach_parent_id(), still not able to set the parent!")
+            return False
+
+        if ids is not None:
+            raise NotImplementedError("Ids is just there by convention! Don't use it yet, please.")
+
+        cr.execute("UPDATE ir_attachment " \
+                    "SET parent_id = %s, db_datas = decode(encode(db_datas,'escape'), 'base64') " \
+                    "WHERE parent_id IS NULL", (parent_id,))
+        cr.execute("ALTER TABLE ir_attachment ALTER parent_id SET NOT NULL")
+        return True
+        
     def _get_filestore(self, cr):
         return os.path.join(DMS_ROOT_PATH, cr.dbname)
 
@@ -135,6 +165,15 @@
                 return False
         return True
 
+    def check(self, cr, uid, ids, mode, context=None, values=None):
+        """Check access wrt. res_model, relax the rule of ir.attachment parent
+        
+        With 'document' installed, everybody will have access to attachments of
+        any resources they can *read*.
+        """
+        return super(document_file, self).check(cr, uid, ids, mode='read',
+                                            context=context, values=values)
+
     def copy(self, cr, uid, id, default=None, context=None):
         if not default:
             default = {}
@@ -168,7 +207,7 @@
             ids2 = []
             for fbro in self.browse(cr, uid, ids, context=context):
                 if ('parent_id' not in vals or fbro.parent_id.id == vals['parent_id']) \
-                    and ('name' not in vals or fbro.name == vals['name']) :
+                    and ('name' not in vals or fbro.name == vals['name']):
                         ids2.append(fbro.id)
                         continue
                 fnode = nctx.get_file_node(cr, fbro)

=== modified file 'document/document_data.xml'
--- document/document_data.xml	2010-10-27 10:24:28 +0000
+++ document/document_data.xml	2010-12-23 16:04:51 +0000
@@ -97,8 +97,11 @@
         <field name="user_id" eval="False"/>
         <field name="parent_id" ref="dir_root"/>
         <field name="ressource_id">0</field>
-
     </record>
 
+    <!-- After we have setup the root directory, migrate the attachments
+        to point to that. -->
+    <function model="ir.attachment" name="_attach_parent_id"/>
+
 </data>
 </openerp>

=== modified file 'document/std_index.py'
--- document/std_index.py	2010-11-12 10:14:16 +0000
+++ document/std_index.py	2010-12-23 16:04:51 +0000
@@ -95,13 +95,9 @@
         return ['.doc']
 
     def _doIndexFile(self,fname):
-        fp = Popen(['antiword', fname], shell=False, stdout=PIPE).stdout
-        try:
-            file_data = _to_unicode(fp.read())
-        finally:
-            fp.close()
-            
-        return file_data
+        pop = Popen(['antiword', fname], shell=False, stdout=PIPE)
+        (data, _) = pop.communicate()
+        return _to_unicode(data)
     
 cntIndex.register(DocIndex())
 
@@ -162,13 +158,9 @@
         return ['.pdf']
 
     def _doIndexFile(self,fname):
-        fp = Popen(['pdftotext', '-enc', 'UTF-8', '-nopgbrk', fname, '-'], shell=False, stdout=PIPE).stdout
-        try:
-           file_data = _to_unicode( fp.read())
-        finally:
-            fp.close()
-                 
-        return file_data
+        pop = Popen(['pdftotext', '-enc', 'UTF-8', '-nopgbrk', fname, '-'], shell=False, stdout=PIPE)
+        (data, _) = pop.communicate()
+        return _to_unicode(data)
 
 cntIndex.register(PdfIndex())
 

=== modified file 'document/wizard/document_configuration.py'
--- document/wizard/document_configuration.py	2010-11-23 07:05:05 +0000
+++ document/wizard/document_configuration.py	2010-12-23 16:04:51 +0000
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 ##############################################################################
-#    
+#
 #    OpenERP, Open Source Management Solution
 #    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
 #
@@ -15,22 +15,24 @@
 #    GNU Affero General Public License for more details.
 #
 #    You should have received a copy of the GNU Affero General Public License
-#    along with this program.  If not, see <http://www.gnu.org/licenses/>.     
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #
 ##############################################################################
+
 from osv import osv, fields
+
 class document_configuration(osv.osv_memory):
 
     _name='document.configuration'
     _description = 'Auto Directory Configuration'
-    _inherit = 'res.config'   
+    _inherit = 'res.config'
 
     _columns = {
         'sale_order' : fields.boolean('Sale Order', help="Auto directory configuration for Sale Orders and Quotation with report."),
         'product' : fields.boolean('Product', help="Auto directory configuration for Products."),
         'project': fields.boolean('Project', help="Auto directory configuration for Projects."),
     }
-    
+
 
     def execute(self, cr, uid, ids, context=None):
         conf_id = ids and ids[0] or False
@@ -58,7 +60,7 @@
                 quta_dir_id = data_pool.browse(cr, uid, dir_data_id, context=context).res_id
             else:
                 quta_dir_id = data_pool.create(cr, uid, {'name': 'Sale Quotations'})
-            
+
             dir_pool.write(cr, uid, [quta_dir_id], {
                 'type':'ressource',
                 'ressource_type_id': mid[0],
@@ -86,7 +88,7 @@
                     'include_name': 1,
                     'directory_id': quta_dir_id,
                 })
-            
+
 
         if conf.product and self.pool.get('product.product'):
             # Product
@@ -95,12 +97,12 @@
                 product_dir_id = data_pool.browse(cr, uid, dir_data_id, context=context).res_id
             else:
                 product_dir_id = data_pool.create(cr, uid, {'name': 'Products'})
-            
+
             mid = model_pool.search(cr, uid, [('model','=','product.product')])
             dir_pool.write(cr, uid, [product_dir_id], {
                 'type':'ressource',
                 'ressource_type_id': mid[0],
-            })           
+            })
 
         if conf.project and self.pool.get('account.analytic.account'):
             # Project
@@ -109,12 +111,12 @@
                 project_dir_id = data_pool.browse(cr, uid, dir_data_id, context=context).res_id
             else:
                 project_dir_id = data_pool.create(cr, uid, {'name': 'Projects'})
-            
+
             mid = model_pool.search(cr, uid, [('model','=','account.analytic.account')])
             dir_pool.write(cr, uid, [project_dir_id], {
                 'type':'ressource',
                 'ressource_type_id': mid[0],
                 'domain': '[]',
                 'ressource_tree': 1
-        })        
+        })
 document_configuration()

=== modified file 'document/wizard/document_configuration_view.xml'
--- document/wizard/document_configuration_view.xml	2010-09-15 06:58:45 +0000
+++ document/wizard/document_configuration_view.xml	2010-12-23 16:04:51 +0000
@@ -45,6 +45,8 @@
     <record model="ir.actions.todo" id="config_auto_directory">
         <field name="action_id" ref="action_config_auto_directory"/>
         <field name="groups_id" eval="[(6,0,[ref('base.group_extended')])]"/>
+        <field name="state" eval="'skip'" />
+        <field name="restart" eval="'onskip'" />
     </record>
   </data>
 </openerp>

=== modified file 'document_ftp/wizard/ftp_browse.py'
--- document_ftp/wizard/ftp_browse.py	2010-12-06 13:11:02 +0000
+++ document_ftp/wizard/ftp_browse.py	2010-12-23 16:04:51 +0000
@@ -43,9 +43,11 @@
             url = ftp_url.url and ftp_url.url.split('ftp://') or []
             if url:
                 url = url[1]
+                if url[-1] == '/':
+                    url = url[:-1]
             else:
                 url = '%s:%s' %(ftpserver.HOST, ftpserver.PORT)
-            res['url'] = 'ftp://%s@%s'%(current_user.login, url)
+            res['url'] = 'ftp://%s@%s/%s'%(current_user.login, url, cr.dbname)
         return res
 
     def browse_ftp(self, cr, uid, ids, context=None):

=== modified file 'document_ftp/wizard/ftp_configuration.py'
--- document_ftp/wizard/ftp_configuration.py	2010-12-06 13:11:02 +0000
+++ document_ftp/wizard/ftp_configuration.py	2010-12-23 16:04:51 +0000
@@ -44,6 +44,7 @@
         # Update the action for FTP browse.
         aid = data_pool._get_id(cr, uid, 'document_ftp', 'action_document_browse')
         aid = data_pool.browse(cr, uid, aid, context=context).res_id
-        self.pool.get('ir.actions.url').write(cr, uid, [aid], {'url': 'ftp://'+(conf.host or 'localhost:8021')+'/'})
+        self.pool.get('ir.actions.url').write(cr, uid, [aid], 
+                {'url': 'ftp://'+(conf.host or 'localhost:8021')+'/' + cr.dbname+'/'})
 
 document_ftp_configuration()

=== modified file 'document_webdav/__openerp__.py'
--- document_webdav/__openerp__.py	2010-11-12 09:48:36 +0000
+++ document_webdav/__openerp__.py	2010-12-23 16:04:51 +0000
@@ -30,7 +30,7 @@
 
 {
         "name" : "WebDAV server for Document Management",
-        "version" : "2.2",
+        "version" : "2.3",
         "author" : "OpenERP SA",
         "category" : "Generic Modules/Others",
         "website": "http://www.openerp.com";,
@@ -49,6 +49,9 @@
           ; since the messages are routed to the python logging, with
           ; levels "debug" and "debug_rpc" respectively, you can leave
           ; these options on
+          
+        Also implements IETF RFC 5785 for services discovery on a http server,
+        which needs explicit configuration in openerp-server.conf, too.
 """,
         "depends" : ["base", "document"],
         "init_xml" : [],

=== modified file 'document_webdav/dav_fs.py'
--- document_webdav/dav_fs.py	2010-12-07 13:40:41 +0000
+++ document_webdav/dav_fs.py	2010-12-23 16:04:51 +0000
@@ -686,7 +686,7 @@
         except Exception:
             node = False
         
-        objname = uri2[-1]
+        objname = misc.ustr(uri2[-1])
         
         ret = None
         if not node:
@@ -719,7 +719,7 @@
                 etag = str(newchild.get_etag(cr))
             except Exception, e:
                 self.parent.log_error("Cannot get etag for node: %s" % e)
-            ret = (hurl, etag)
+            ret = (str(hurl), etag)
         else:
             self._try_function(node.set_data, (cr, data), "save %s" % objname, cr=cr)
             

=== added directory 'document_webdav/doc'
=== added file 'document_webdav/doc/well-known.rst'
--- document_webdav/doc/well-known.rst	1970-01-01 00:00:00 +0000
+++ document_webdav/doc/well-known.rst	2010-12-23 16:04:51 +0000
@@ -0,0 +1,29 @@
+=================
+Well-known URIs
+=================
+
+In accordance to IETF RFC 5785 [1], we shall publish a few locations
+on the root of our http server, so that clients can discover our 
+services (CalDAV, eg.).
+
+This module merely installs a special http request handler, that will
+redirect the URIs from "http://our-server:port/.well-known/xxx' to 
+the correct path for each xxx service.
+
+Note that well-known URIs cannot have a database-specific behaviour, 
+they are server-wide. So, we have to explicitly chose one of our databases
+to serve at them. By default, the database of the configuration file
+is chosen
+
+Example config:
+
+[http-well-known]
+num_services = 2
+db_name = openerp-main ; must define that for path_1 below
+service_1 = caldav
+path_1 = /webdav/%(db_name)s/Calendars/
+service_2 = foo
+path_2 = /other_db/static/Foo.html
+
+
+[1] http://www.rfc-editor.org/rfc/rfc5785.txt
\ No newline at end of file

=== modified file 'document_webdav/nodes.py'
--- document_webdav/nodes.py	2010-12-07 13:40:41 +0000
+++ document_webdav/nodes.py	2010-12-23 16:04:51 +0000
@@ -299,7 +299,7 @@
         return ''
 
     def get_dav_props(self, cr):
-        return self._get_dav_props_hlpr(cr, nodes.node_dir, 
+        return self._get_dav_props_hlpr(cr, nodes.node_file, 
                 'document.webdav.file.property', 'file_id', self.file_id)
 
     def dav_lock(self, cr, lock_data):
@@ -345,11 +345,11 @@
         return ('collection', 'DAV:')
 
     def get_dav_props(self, cr):
-        return self._get_dav_props_hlpr(cr, nodes.node_dir,
+        return self._get_dav_props_hlpr(cr, nodes.node_database,
                 'document.webdav.dir.property', 'dir_id', False)
 
     def get_dav_eprop(self, cr, ns, prop):
-        return self._get_dav_eprop_hlpr(cr, nodes.node_dir, ns, prop,
+        return self._get_dav_eprop_hlpr(cr, nodes.node_database, ns, prop,
                 'document.webdav.dir.property', 'dir_id', False)
 
 class node_res_obj(node_acl_mixin, nodes.node_res_obj):

=== added directory 'document_webdav/public_html'
=== added file 'document_webdav/public_html/index.html'
--- document_webdav/public_html/index.html	1970-01-01 00:00:00 +0000
+++ document_webdav/public_html/index.html	2010-12-23 16:04:51 +0000
@@ -0,0 +1,9 @@
+<html>
+<head>
+<title>OpenERP server</title>
+</head>
+<body>
+This is an OpenERP server. Nothing to GET here.
+</body>
+</html>
+

=== added file 'document_webdav/redirect.py'
--- document_webdav/redirect.py	1970-01-01 00:00:00 +0000
+++ document_webdav/redirect.py	2010-12-23 16:04:51 +0000
@@ -0,0 +1,108 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    OpenERP, Open Source Management Solution
+#    Copyright (C) 2010 OpenERP s.a. (<http://openerp.com>).
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+
+import logging
+import urlparse
+from service.websrv_lib import FixSendError, HTTPHandler, HttpOptions
+from service.http_server import HttpLogHandler
+
+class RedirectHTTPHandler(HttpLogHandler, FixSendError, HttpOptions, HTTPHandler):
+    _logger = logging.getLogger('httpd.well-known')
+    _HTTP_OPTIONS = { 'Allow': ['OPTIONS', 'GET', 'HEAD', 'PROPFIND'] }
+    redirect_paths = {}
+
+    def __init__(self,request, client_address, server):
+        HTTPHandler.__init__(self,request,client_address,server)
+
+    def send_head(self):
+        """Common code for GET and HEAD commands.
+
+        It will either send the correct redirect (Location) response
+        or a 404.
+        """
+
+        if self.path.endswith('/'):
+            self.path = self.path[:-1]
+        
+        if not self.path:
+            # Return an empty page
+            self.send_response(200)
+            self.send_header("Content-Length", 0)
+            self.end_headers()
+            return None
+        
+        redir_path = self._find_redirect()
+        if redir_path is False:
+            self.send_error(404, "File not found")
+            return None
+        elif redir_path is None:
+            return None
+
+        server_proto = getattr(self.server, 'proto', 'http').lower()
+        addr, port = self.server.server_name, self.server.server_port
+        try:
+            addr, port = self.request.getsockname()
+        except Exception, e:
+            self.log_error("Cannot calculate own address:" , e)
+        
+        if self.headers.has_key('Host'):
+            uparts = list(urlparse.urlparse("%s://%s:%d"% (server_proto, addr,port)))
+            uparts[1] = self.headers['Host']
+            baseuri = urlparse.urlunparse(uparts)
+        else:
+            baseuri = "%s://%s:%d"% (server_proto, addr, port )
+
+
+        location = baseuri + redir_path
+        # relative uri: location = self.redirect_paths[self.path]
+
+        self.send_response(301)
+        self.send_header("Location", location)
+        self.send_header("Content-Length", 0)
+        self.end_headers()
+        # Do we need a Cache-content: header here?
+        self._logger.debug("redirecting %s to %s", self.path, redir_path)
+        return None
+
+    def do_PROPFIND(self):
+        self._get_ignore_body()
+        return self.do_HEAD()
+
+    def _find_redirect(self):
+        """ Locate self.path among the redirects we can handle
+        @return The new path, False for 404 or None for already sent error
+        """
+        return self.redirect_paths.get(self.path, False)
+
+    def _get_ignore_body(self):
+        if not self.headers.has_key("content-length"):
+            return
+        max_chunk_size = 10*1024*1024
+        size_remaining = int(self.headers["content-length"])
+        got = ''
+        while size_remaining:
+            chunk_size = min(size_remaining, max_chunk_size)
+            got = self.rfile.read(chunk_size)
+            size_remaining -= len(got)
+
+#eof
+

=== modified file 'document_webdav/webdav.py'
--- document_webdav/webdav.py	2010-12-07 13:40:41 +0000
+++ document_webdav/webdav.py	2010-12-23 16:04:51 +0000
@@ -45,35 +45,33 @@
         data = data.replace(">", "&gt;")
         writer.write(data)
 
-def createText2Node(doc, data):
-    if not isinstance(data, StringTypes):
-        raise TypeError, "node contents must be a string"
-    t = Text2()
-    t.data = data
-    t.ownerDocument = doc
-    return t
-
-
-super_mk_prop_response = PROPFIND.mk_prop_response
-def mk_prop_response(self, uri, good_props, bad_props, doc):
-    """ make a new <prop> result element
-
-    We differ between the good props and the bad ones for
-    each generating an extra <propstat>-Node (for each error
-    one, that means).
-
+class Prop2xml(object):
+    """ A helper class to convert property structs to DAV:XML
+    
+        Written to generalize the use of _prop_child(), a class is 
+        needed to hold some persistent data accross the recursions 
+        of _prop_elem_child().
     """
-    re=doc.createElement("D:response")
-    # append namespaces to response
-    nsnum=0
-    namespaces = self.namespaces[:]
-    if 'DAV:' in namespaces:
-        namespaces.remove('DAV:')
-    for nsname in namespaces:
-        re.setAttribute("xmlns:ns"+str(nsnum),nsname)
-        nsnum=nsnum+1
-
-    def _prop_child(xnode, ns, prop, value):
+    
+    def __init__(self, doc, namespaces, nsnum):
+        """ Init the structure
+        @param doc the xml doc element
+        @param namespaces a dict of namespaces
+        @param nsnum the next namespace number to define
+        """
+        self.doc = doc
+        self.namespaces = namespaces
+        self.nsnum = nsnum
+
+    def createText2Node(self, data):
+        if not isinstance(data, StringTypes):
+            raise TypeError, "node contents must be a string"
+        t = Text2()
+        t.data = data
+        t.ownerDocument = self.doc
+        return t
+
+    def _prop_child(self, xnode, ns, prop, value):
         """Append a property xml node to xnode, with <prop>value</prop>
 
            And a little smarter than that, it will consider namespace and
@@ -92,49 +90,49 @@
         if ns == 'DAV:':
             ns_prefix = 'D:'
         else:
-            ns_prefix="ns"+str(namespaces.index(ns))+":"
+            ns_prefix="ns"+str(self.namespaces.index(ns))+":"
 
-        pe=doc.createElement(ns_prefix+str(prop))
+        pe = self.doc.createElement(ns_prefix+str(prop))
         if hasattr(value, '__class__') and value.__class__.__name__ == 'Element':
             pe.appendChild(value)
         else:
             if ns == 'DAV:' and prop=="resourcetype" and isinstance(value, int):
                 # hack, to go..
                 if value == 1:
-                    ve=doc.createElement("D:collection")
+                    ve = self.doc.createElement("D:collection")
                     pe.appendChild(ve)
             else:
-                _prop_elem_child(pe, ns, value, ns_prefix)
+                self._prop_elem_child(pe, ns, value, ns_prefix)
 
             xnode.appendChild(pe)
 
-    def _prop_elem_child(pnode, pns, v, pns_prefix):
+    def _prop_elem_child(self, pnode, pns, v, pns_prefix):
 
         if isinstance(v, list):
             for vit in v:
-                _prop_elem_child(pnode, pns, vit, pns_prefix)
+                self._prop_elem_child(pnode, pns, vit, pns_prefix)
         elif isinstance(v,tuple):
             need_ns = False
             if v[1] == pns:
                 ns_prefix = pns_prefix
             elif v[1] == 'DAV:':
                 ns_prefix = 'D:'
-            elif v[1] in namespaces:
-                ns_prefix="ns"+str(namespaces.index(v[1]))+":"
+            elif v[1] in self.namespaces:
+                ns_prefix="ns"+str(self.namespaces.index(v[1]))+":"
             else:
-                ns_prefix="ns"+str(nsnum)+":"
+                ns_prefix="ns"+str(self.nsnum)+":"
                 need_ns = True
 
-            ve=doc.createElement(ns_prefix+v[0])
+            ve = self.doc.createElement(ns_prefix+v[0])
             if need_ns:
-                ve.setAttribute("xmlns:ns"+str(nsnum), v[1])
+                ve.setAttribute("xmlns:ns"+str(self.nsnum), v[1])
             if len(v) > 2 and v[2] is not None:
                 if isinstance(v[2], (list, tuple)):
                     # support nested elements like:
                     # ( 'elem', 'ns:', [('sub-elem1', 'ns1'), ...]
-                    _prop_elem_child(ve, v[1], v[2], ns_prefix)
+                    self._prop_elem_child(ve, v[1], v[2], ns_prefix)
                 else:
-                    vt=createText2Node(doc,tools.ustr(v[2]))
+                    vt = self.createText2Node(tools.ustr(v[2]))
                     ve.appendChild(vt)
             if len(v) > 3 and v[3]:
                 assert isinstance(v[3], dict)
@@ -142,9 +140,30 @@
                     ve.setAttribute(ak, av)
             pnode.appendChild(ve)
         else:
-            ve=createText2Node(doc, tools.ustr(v))
+            ve = self.createText2Node(tools.ustr(v))
             pnode.appendChild(ve)
 
+
+super_mk_prop_response = PROPFIND.mk_prop_response
+def mk_prop_response(self, uri, good_props, bad_props, doc):
+    """ make a new <prop> result element
+
+    We differ between the good props and the bad ones for
+    each generating an extra <propstat>-Node (for each error
+    one, that means).
+
+    """
+    re=doc.createElement("D:response")
+    # append namespaces to response
+    nsnum=0
+    namespaces = self.namespaces[:]
+    if 'DAV:' in namespaces:
+        namespaces.remove('DAV:')
+    for nsname in namespaces:
+        re.setAttribute("xmlns:ns"+str(nsnum),nsname)
+        nsnum=nsnum+1
+
+    propgen = Prop2xml(doc, namespaces, nsnum)
     # write href information
     uparts=urlparse.urlparse(uri)
     fileloc=uparts[2]
@@ -180,7 +199,7 @@
         for p,v in good_props[ns].items():
             if v is None:
                 continue
-            _prop_child(gp, ns, p, v)
+            propgen._prop_child(gp, ns, p, v)
 
     ps.appendChild(gp)
     re.appendChild(ps)

=== modified file 'document_webdav/webdav_server.py'
--- document_webdav/webdav_server.py	2010-12-09 09:59:50 +0000
+++ document_webdav/webdav_server.py	2010-12-23 16:04:51 +0000
@@ -1,7 +1,8 @@
 # -*- encoding: utf-8 -*-
-
+############################################################################9
 #
 # Copyright P. Christeas <p_christ@xxxxxx> 2008-2010
+# Copyright OpenERP SA, 2010 (http://www.openerp.com )
 #
 # Disclaimer: Many of the functions below borrow code from the
 #   python-webdav library (http://code.google.com/p/pywebdav/ ),
@@ -19,7 +20,7 @@
 #
 # 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
+# as published by the Free Software Foundation; either version 3
 # of the License, or (at your option) any later version.
 #
 # This program is distributed in the hope that it will be useful,
@@ -33,20 +34,26 @@
 ###############################################################################
 
 
+import logging
 import netsvc
 from dav_fs import openerp_dav_handler
 from tools.config import config
 from DAV.WebDAVServer import DAVRequestHandler
+from service import http_server
 from service.websrv_lib import HTTPDir, FixSendError, HttpOptions
 from BaseHTTPServer import BaseHTTPRequestHandler
 import urlparse
 import urllib
 import re
+import time
 from string import atoi
-from DAV.errors import *
+import addons
 from DAV.utils import IfParser, TagList
+from DAV.errors import DAV_Error, DAV_Forbidden, DAV_NotFound
+from DAV.propfind import PROPFIND
 # from DAV.constants import DAV_VERSION_1, DAV_VERSION_2
 from xml.dom import minidom
+from redirect import RedirectHTTPHandler
 
 khtml_re = re.compile(r' KHTML/([0-9\.]+) ')
 
@@ -66,6 +73,7 @@
 
 class DAVHandler(HttpOptions, FixSendError, DAVRequestHandler):
     verbose = False
+    _logger = logging.getLogger('webdav')
     protocol_version = 'HTTP/1.1'
     _HTTP_OPTIONS= { 'DAV' : ['1', '2'],
                     'Allow' : [ 'GET', 'HEAD', 'COPY', 'MOVE', 'POST', 'PUT',
@@ -75,8 +83,9 @@
 
     def get_userinfo(self,user,pw):
         return False
+
     def _log(self, message):
-        netsvc.Logger().notifyChannel("webdav",netsvc.LOG_DEBUG,message)
+        self._logger.debug(message)
 
     def handle(self):
         self._init_buffer()
@@ -118,10 +127,10 @@
         return self.davpath
 
     def log_message(self, format, *args):
-        netsvc.Logger().notifyChannel('webdav', netsvc.LOG_DEBUG_RPC, format % args)
+        self._logger.log(netsvc.logging.DEBUG_RPC,format % args)
 
     def log_error(self, format, *args):
-        netsvc.Logger().notifyChannel('xmlrpc', netsvc.LOG_WARNING, format % args)
+        self._logger.warning(format % args)
 
     def _prep_OPTIONS(self, opts):
         ret = opts
@@ -415,6 +424,140 @@
             return True
         return OpenERPAuthProvider.authenticate(self, db, user, passwd, client_address)
 
+
+class dummy_dav_interface(object):
+    """ Dummy dav interface """
+    verbose = True
+
+    PROPS={"DAV:" : ('creationdate',
+                     'displayname',
+                     'getlastmodified',
+                     'resourcetype',
+                     ),
+           }
+
+    M_NS={"DAV:" : "_get_dav", }
+
+    def __init__(self, parent):
+        self.parent = parent
+
+    def get_propnames(self,uri):
+        return self.PROPS
+
+    def get_prop(self,uri,ns,propname):
+        if self.M_NS.has_key(ns):
+            prefix=self.M_NS[ns]
+        else:
+            raise DAV_NotFound
+        mname=prefix+"_"+propname.replace('-', '_')
+        try:
+            m=getattr(self,mname)
+            r=m(uri)
+            return r
+        except AttributeError:
+            raise DAV_NotFound
+
+    def get_data(self, uri, range=None):
+        raise DAV_NotFound
+
+    def _get_dav_creationdate(self,uri):
+        return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
+
+    def _get_dav_getlastmodified(self,uri):
+        return time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime())
+
+    def _get_dav_displayname(self, uri):
+        return uri
+
+    def _get_dav_resourcetype(self, uri):
+        return ('collection', 'DAV:')
+
+    def exists(self, uri):
+        """ return 1 or None depending on if a resource exists """
+        uri2 = uri.split('/')
+        if len(uri2) < 3:
+            return True
+        logging.getLogger('webdav').debug("Requested uri: %s", uri)
+        return None # no
+
+    def is_collection(self, uri):
+        """ return 1 or None depending on if a resource is a collection """
+        return None # no
+
+class DAVStaticHandler(http_server.StaticHTTPHandler):
+    """ A variant of the Static handler, which will serve dummy DAV requests
+    """
+    verbose = False
+    protocol_version = 'HTTP/1.1'
+    _HTTP_OPTIONS= { 'DAV' : ['1', '2'],
+                    'Allow' : [ 'GET', 'HEAD',
+                            'PROPFIND', 'OPTIONS', 'REPORT', ]
+                    }
+
+    def send_body(self, content, code, message='OK', content_type='text/xml'):
+        self.send_response(int(code), message)
+        self.send_header("Content-Type", content_type)
+        # self.send_header('Connection', 'close')
+        self.send_header('Content-Length', len(content) or 0)
+        self.end_headers()
+        if hasattr(self, '_flush'):
+            self._flush()
+        
+        if self.command != 'HEAD':
+            self.wfile.write(content)
+
+    def do_PROPFIND(self):
+        """Answer to PROPFIND with generic data.
+        
+        A rough copy of python-webdav's do_PROPFIND, but hacked to work
+        statically.
+        """
+
+        dc = dummy_dav_interface(self)
+
+        # read the body containing the xml request
+        # iff there is no body then this is an ALLPROP request
+        body = None
+        if self.headers.has_key('Content-Length'):
+            l = self.headers['Content-Length']
+            body = self.rfile.read(atoi(l))
+
+        path = self.path.rstrip('/')
+        uri = urllib.unquote(path)
+
+        pf = PROPFIND(uri, dc, self.headers.get('Depth', 'infinity'), body)
+
+        try:
+            DATA = '%s\n' % pf.createResponse()
+        except DAV_Error, (ec,dd):
+            return self.send_error(ec,dd)
+        except Exception:
+            self.log_exception("Cannot PROPFIND")
+            raise
+
+        # work around MSIE DAV bug for creation and modified date
+        # taken from Resource.py @ Zope webdav
+        if (self.headers.get('User-Agent') ==
+            'Microsoft Data Access Internet Publishing Provider DAV 1.1'):
+            DATA = DATA.replace('<ns0:getlastmodified xmlns:ns0="DAV:">',
+                                    '<ns0:getlastmodified xmlns:n="DAV:" xmlns:b="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/" b:dt="dateTime.rfc1123">')
+            DATA = DATA.replace('<ns0:creationdate xmlns:ns0="DAV:">',
+                                    '<ns0:creationdate xmlns:n="DAV:" xmlns:b="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/" b:dt="dateTime.tz">')
+
+        self.send_body(DATA, '207','Multi-Status','Multiple responses')
+
+    def not_get_baseuri(self):
+        baseuri = '/'
+        if self.headers.has_key('Host'):
+            uparts = list(urlparse.urlparse('/'))
+            uparts[1] = self.headers['Host']
+            baseuri = urlparse.urlunparse(uparts)
+        return baseuri
+
+    def get_davpath(self):
+        return ''
+
+
 try:
 
     if (config.get_misc('webdav','enable',True)):
@@ -430,10 +573,80 @@
         conf = OpenDAVConfig(**_dc)
         handler._config = conf
         reg_http_service(HTTPDir(directory,DAVHandler,DAVAuthProvider()))
-        netsvc.Logger().notifyChannel('webdav', netsvc.LOG_INFO, "WebDAV service registered at path: %s/ "% directory)
+        logging.getLogger('webdav').info("WebDAV service registered at path: %s/ "% directory)
+        
+        if not (config.get_misc('webdav', 'no_root_hack', False)):
+            # Now, replace the static http handler with the dav-enabled one.
+            # If a static-http service has been specified for our server, then
+            # read its configuration and use that dir_path.
+            # NOTE: this will _break_ any other service that would be registered
+            # at the root path in future.
+            base_path = False
+            if config.get_misc('static-http','enable', False):
+                base_path = config.get_misc('static-http', 'base_path', '/')
+            if base_path and base_path == '/':
+                dir_path = config.get_misc('static-http', 'dir_path', False)
+            else:
+                dir_path = addons.get_module_resource('document_webdav','public_html')
+                # an _ugly_ hack: we put that dir back in tools.config.misc, so that
+                # the StaticHttpHandler can find its dir_path.
+                config.misc.setdefault('static-http',{})['dir_path'] = dir_path
+    
+            if reg_http_service(HTTPDir('/', DAVStaticHandler)):
+                logging.getLogger("web-services").info("WebDAV registered HTTP dir %s for /" % \
+                                (dir_path))
+
 except Exception, e:
-    logger = netsvc.Logger()
-    logger.notifyChannel('webdav', netsvc.LOG_ERROR, 'Cannot launch webdav: %s' % e)
+    logging.getLogger('webdav').error('Cannot launch webdav: %s' % e)
+
+
+def init_well_known():
+    reps = RedirectHTTPHandler.redirect_paths
+
+    num_svcs = config.get_misc('http-well-known', 'num_services', '0')
+
+    for nsv in range(1, int(num_svcs)+1):
+        uri = config.get_misc('http-well-known', 'service_%d' % nsv, False)
+        path = config.get_misc('http-well-known', 'path_%d' % nsv, False)
+        if not (uri and path):
+            continue
+        reps['/'+uri] = path
+
+    if int(num_svcs):
+        if http_server.reg_http_service(HTTPDir('/.well-known', RedirectHTTPHandler)):
+            logging.getLogger("web-services").info("Registered HTTP redirect handler at /.well-known" )
+
+init_well_known()
+
+class PrincipalsRedirect(RedirectHTTPHandler):
+    redirect_paths = {}
+    
+    def _find_redirect(self):
+        for b, r in self.redirect_paths.items():
+            if self.path.startswith(b):
+                return r + self.path[len(b):]
+        return False
+
+def init_principals_redirect():
+    """ Some devices like the iPhone will look under /principals/users/xxx for 
+    the user's properties. In OpenERP we _cannot_ have a stray /principals/...
+    working path, since we have a database path and the /webdav/ component. So,
+    the best solution is to redirect the url with 301. Luckily, it does work in
+    the device. The trick is that we need to hard-code the database to use, either
+    the one centrally defined in the config, or a "forced" one in the webdav
+    section.
+    """
+    dbname = config.get_misc('webdav', 'principal_dbname', False)
+    if (not dbname) and not config.get_misc('webdav', 'no_principals_redirect', False):
+        dbname = config.get('db_name', False)
+    if dbname:
+        PrincipalsRedirect.redirect_paths[''] = '/webdav/%s/principals' % dbname
+        reg_http_service(HTTPDir('/principals', PrincipalsRedirect))
+        logging.getLogger("web-services").info(
+                "Registered HTTP redirect handler for /principals to the %s db.",
+                dbname)
+
+init_principals_redirect()
 
 #eof
 

=== modified file 'event_project/__init__.py'
--- event_project/__init__.py	2010-11-18 12:12:42 +0000
+++ event_project/__init__.py	2010-12-23 16:04:51 +0000
@@ -20,5 +20,6 @@
 ##############################################################################
 import event_project
 import wizard
+
 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
 

=== removed file 'point_of_sale/pos_workflow.xml.orig'
--- point_of_sale/pos_workflow.xml.orig	2010-08-13 12:20:05 +0000
+++ point_of_sale/pos_workflow.xml.orig	1970-01-01 00:00:00 +0000
@@ -1,238 +0,0 @@
-<?xml version="1.0"?>
-<openerp>
-  <data>
-	<record model="workflow" id="wkf_pos">
-	  <field name="name">Pos workflow</field>
-	  <field name="osv">pos.order</field>
-	  <field name="on_create">True</field>
-	</record>
-
-    <!-- Roles definition -->
-
-	<record model="res.roles" id="role_pos">
-		<field name="name">POS - Confirmation</field>
-	</record>
-
-    <!--Activities-->
-
-	<record model="workflow.activity" id="act_draft">
-	  <field name="wkf_id" ref="wkf_pos"/>
-	  <field name="flow_start">True</field>
-	  <field name="name">draft</field>
-	</record>
-
-    <record model="workflow.activity" id="act_payment">
-        <field name="wkf_id" ref="wkf_pos" />
-        <field name="name">payment</field>
-        <field name="kind">function</field>
-        <field name="action">write({'state': 'payment'})</field>
-    </record>
-
-    <record model="workflow.activity" id="act_rebate">
-        <field name="wkf_id" ref="wkf_pos" />
-        <field name="name">rebate</field>
-        <field name="kind">function</field>
-        <field name="action">write({'state': 'rebate'})</field>
-    </record>
-
-    <record model="workflow.activity" id="act_unbalanced">
-        <field name="wkf_id" ref="wkf_pos" />
-        <field name="name">unbalanced</field>
-        <field name="kind">function</field>
-        <field name="action">write({'state': 'unbalanced'})</field>
-    </record>
-
-    <record model="workflow.activity" id="act_cofinoga">
-        <field name="wkf_id" ref="wkf_pos" />
-        <field name="name">cofinoga</field>
-        <field name="kind">function</field>
-        <field name="action">write({'state': 'cofinoga'})</field>
-    </record>
-
-    <record model="workflow.activity" id="act_collectivites">
-        <field name="wkf_id" ref="wkf_pos" />
-        <field name="name">collectivites</field>
-        <field name="kind">function</field>
-        <field name="action">write({'state': 'collectivites'})</field>
-    </record>
-
-    <record model="workflow.activity" id="act_cadeaux">
-        <field name="wkf_id" ref="wkf_pos" />
-        <field name="name">cadeaux</field>
-        <field name="kind">function</field>
-        <field name="action">write({'state': 'cadeaux'})</field>
-    </record>
-
-    <record model="workflow.activity" id="act_collectivites">
-        <field name="wkf_id" ref="wkf_pos" />
-        <field name="name">collectivites</field>
-        <field name="kind">function</field>
-        <field name="action">write({'state': 'collectivites', 'invoice_wanted': True})</field>
-    </record>
-
-    <record model="workflow.activity" id="act_cadeaux">
-        <field name="wkf_id" ref="wkf_pos" />
-        <field name="name">cadeaux</field>
-        <field name="kind">function</field>
-        <field name="action">write({'state': 'cadeaux'})</field>
-    </record>
-
-	<record model="workflow.activity" id="act_paid">
-        <field name="wkf_id" ref="wkf_pos"/>
-        <field name="name">paid</field>
-        <field name="action">action_paid()</field>
-        <field name="kind">function</field>
-	</record>
-
-	<record model="workflow.activity" id="act_done">
-	  <field name="wkf_id" ref="wkf_pos"/>
-	  <field name="name">done</field>
-	  <field name="flow_stop">True</field>
-	  <field name="action">action_done()</field>
-	  <field name="kind">function</field>
-	</record>
-
-	<record model="workflow.activity" id="act_invoiced">
-	  <field name="wkf_id" ref="wkf_pos"/>
-	  <field name="name">invoiced</field>
-	  <field name="flow_stop">True</field>
-	  <field name="action">action_invoice()</field>
-	  <field name="kind">function</field>
-	</record>
-
-	<record model="workflow.activity" id="act_cancel">
-	  <field name="wkf_id" ref="wkf_pos"/>
-	  <field name="name">cancel</field>
-	  <field name="flow_stop">True</field>
-	  <field name="action">action_cancel()</field>
-	  <field name="kind">function</field>
-	</record>
-
-
-    <!--Transitions-->
-
-    <record model="workflow.transition" id="trans_draft_payment">
-        <field name="act_from" ref="act_draft" />
-        <field name="act_to" ref="act_payment" />
-        <field name="signal">start_payment</field>
-    </record>
-
-	<record model="workflow.transition" id="trans_payment_paid">
-        <field name="act_from" ref="act_payment"/>
-        <field name="act_to" ref="act_paid"/>
-        <field name="condition">test_paid() and not(test_rebate() or test_cofinoga() or test_cadeaux() or test_collectivites())</field>
-        <field name="signal">payment</field>
-	</record>
-
-    <record model="workflow.transition" id="trans_payment_rebate">
-        <field name="act_from" ref="act_payment" />
-        <field name="act_to" ref="act_rebate" />
-        <field name="condition">test_rebate()</field>
-        <field name="signal">payment</field>
-    </record>
-
-    <record model="workflow.transition" id="trans_rebate_paid">
-        <field name="act_from" ref="act_rebate" />
-        <field name="act_to" ref="act_paid" />
-        <field name="signal">ok_rebate</field>
-    </record>
-
-    <record model="workflow.transition" id="trans_payment_unbalanced">
-        <field name="act_from" ref="act_payment" />
-        <field name="act_to" ref="act_unbalanced" />
-        <field name="condition">not test_paid()</field>
-        <field name="signal">payment</field>
-    </record>
-
-    <record model="workflow.transition" id="trans_unbalanced">
-        <field name="act_from" ref="act_unbalanced" />
-        <field name="act_to" ref="act_paid" />
-        <field name="condition">test_paid()</field>
-    </record>
-
-    <record model="workflow.transition" id="trans_payment_cofinoga">
-        <field name="act_from" ref="act_payment" />
-        <field name="act_to" ref="act_cofinoga" />
-        <field name="condition">test_cofinoga()</field>
-        <field name="signal">payment</field>
-    </record>
-
-    <record model="workflow.transition" id="trans_cofinoga_paid">
-        <field name="act_from" ref="act_cofinoga" />
-        <field name="act_to" ref="act_paid" />
-        <field name="signal">ok_cofinoga</field>
-    </record>
-
-    <record model="workflow.transition" id="trans_payment_collectivites">
-        <field name="act_from" ref="act_payment" />
-        <field name="act_to" ref="act_collectivites" />
-        <field name="condition">test_collectivites()</field>
-        <field name="signal">payment</field>
-    </record>
-
-    <record model="workflow.transition" id="trans_collectivites_paid">
-        <field name="act_from" ref="act_collectivites" />
-        <field name="act_to" ref="act_paid" />
-        <field name="signal">ok_collectivites</field>
-    </record>
-
-    <record model="workflow.transition" id="trans_payment_cadeaux">
-        <field name="act_from" ref="act_payment" />
-        <field name="act_to" ref="act_cadeaux" />
-        <field name="condition">test_cadeaux()</field>
-        <field name="signal">payment</field>
-    </record>
-
-    <record model="workflow.transition" id="trans_cadeaux_paid">
-        <field name="act_from" ref="act_cadeaux" />
-        <field name="act_to" ref="act_paid" />
-        <field name="signal">ok_cadeaux</field>
-    </record>
-
-    <record model="workflow.transition" id="trans_payment_collectivites">
-        <field name="act_from" ref="act_payment" />
-        <field name="act_to" ref="act_collectivites" />
-        <field name="condition">test_collectivites()</field>
-        <field name="signal">payment</field>
-    </record>
-
-    <record model="workflow.transition" id="trans_collectivites_paid">
-        <field name="act_from" ref="act_collectivites" />
-        <field name="act_to" ref="act_invoiced" />
-        <field name="signal">ok_collectivites</field>
-    </record>
-
-    <record model="workflow.transition" id="trans_payment_cadeaux">
-        <field name="act_from" ref="act_payment" />
-        <field name="act_to" ref="act_cadeaux" />
-        <field name="condition">test_cadeaux()</field>
-        <field name="signal">payment</field>
-    </record>
-
-    <record model="workflow.transition" id="trans_cadeaux_paid">
-        <field name="act_from" ref="act_cadeaux" />
-        <field name="act_to" ref="act_paid" />
-        <field name="signal">ok_cadeaux</field>
-    </record>
-
-	<record model="workflow.transition" id="trans_paid_done">
-		<field name="act_from" ref="act_paid"/>
-		<field name="act_to" ref="act_done"/>
-		<field name="signal">done</field>
-	</record>
-
-	<record model="workflow.transition" id="trans_paid_invoice">
-	  <field name="act_from" ref="act_paid"/>
-	  <field name="act_to" ref="act_invoiced"/>
-	  <field name="signal">invoice</field>
-	</record>
-
-	<record model="workflow.transition" id="trans_paid_cancel">
-	  <field name="act_from" ref="act_paid"/>
-	  <field name="act_to" ref="act_cancel"/>
-	  <field name="signal">cancel</field>
-	</record>
-
-
-  </data>
-</openerp>


Follow ups